1use {
2 crate::{check_program_account, error::TokenError, instruction::TokenInstruction},
3 solana_program::{
4 instruction::{AccountMeta, Instruction},
5 program_error::ProgramError,
6 program_option::COption,
7 pubkey::Pubkey,
8 },
9 std::convert::TryFrom,
10};
11
12#[cfg(feature = "serde-traits")]
13use {
14 crate::serialization::coption_fromstr,
15 serde::{Deserialize, Serialize},
16};
17
18#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
20#[derive(Clone, Copy, Debug, PartialEq)]
21#[repr(u8)]
22pub enum TransferFeeInstruction {
23 InitializeTransferFeeConfig {
36 #[cfg_attr(feature = "serde-traits", serde(with = "coption_fromstr"))]
38 transfer_fee_config_authority: COption<Pubkey>,
39 #[cfg_attr(feature = "serde-traits", serde(with = "coption_fromstr"))]
41 withdraw_withheld_authority: COption<Pubkey>,
42 transfer_fee_basis_points: u16,
45 maximum_fee: u64,
47 },
48 TransferCheckedWithFee {
65 amount: u64,
67 decimals: u8,
69 fee: u64,
72 },
73 WithdrawWithheldTokensFromMint,
90 WithdrawWithheldTokensFromAccounts {
109 num_token_accounts: u8,
111 },
112 HarvestWithheldTokensToMint,
124 SetTransferFee {
137 transfer_fee_basis_points: u16,
140 maximum_fee: u64,
142 },
143}
144impl TransferFeeInstruction {
145 pub fn unpack(input: &[u8]) -> Result<(Self, &[u8]), ProgramError> {
147 use TokenError::InvalidInstruction;
148
149 let (&tag, rest) = input.split_first().ok_or(InvalidInstruction)?;
150 Ok(match tag {
151 0 => {
152 let (transfer_fee_config_authority, rest) =
153 TokenInstruction::unpack_pubkey_option(rest)?;
154 let (withdraw_withheld_authority, rest) =
155 TokenInstruction::unpack_pubkey_option(rest)?;
156 let (transfer_fee_basis_points, rest) = TokenInstruction::unpack_u16(rest)?;
157 let (maximum_fee, rest) = TokenInstruction::unpack_u64(rest)?;
158 let instruction = Self::InitializeTransferFeeConfig {
159 transfer_fee_config_authority,
160 withdraw_withheld_authority,
161 transfer_fee_basis_points,
162 maximum_fee,
163 };
164 (instruction, rest)
165 }
166 1 => {
167 let (amount, decimals, rest) = TokenInstruction::unpack_amount_decimals(rest)?;
168 let (fee, rest) = TokenInstruction::unpack_u64(rest)?;
169 let instruction = Self::TransferCheckedWithFee {
170 amount,
171 decimals,
172 fee,
173 };
174 (instruction, rest)
175 }
176 2 => (Self::WithdrawWithheldTokensFromMint, rest),
177 3 => {
178 let (&num_token_accounts, rest) = rest.split_first().ok_or(InvalidInstruction)?;
179 let instruction = Self::WithdrawWithheldTokensFromAccounts { num_token_accounts };
180 (instruction, rest)
181 }
182 4 => (Self::HarvestWithheldTokensToMint, rest),
183 5 => {
184 let (transfer_fee_basis_points, rest) = TokenInstruction::unpack_u16(rest)?;
185 let (maximum_fee, rest) = TokenInstruction::unpack_u64(rest)?;
186 let instruction = Self::SetTransferFee {
187 transfer_fee_basis_points,
188 maximum_fee,
189 };
190 (instruction, rest)
191 }
192 _ => return Err(TokenError::InvalidInstruction.into()),
193 })
194 }
195
196 pub fn pack(&self, buffer: &mut Vec<u8>) {
198 match *self {
199 Self::InitializeTransferFeeConfig {
200 ref transfer_fee_config_authority,
201 ref withdraw_withheld_authority,
202 transfer_fee_basis_points,
203 maximum_fee,
204 } => {
205 buffer.push(0);
206 TokenInstruction::pack_pubkey_option(transfer_fee_config_authority, buffer);
207 TokenInstruction::pack_pubkey_option(withdraw_withheld_authority, buffer);
208 buffer.extend_from_slice(&transfer_fee_basis_points.to_le_bytes());
209 buffer.extend_from_slice(&maximum_fee.to_le_bytes());
210 }
211 Self::TransferCheckedWithFee {
212 amount,
213 decimals,
214 fee,
215 } => {
216 buffer.push(1);
217 buffer.extend_from_slice(&amount.to_le_bytes());
218 buffer.extend_from_slice(&decimals.to_le_bytes());
219 buffer.extend_from_slice(&fee.to_le_bytes());
220 }
221 Self::WithdrawWithheldTokensFromMint => {
222 buffer.push(2);
223 }
224 Self::WithdrawWithheldTokensFromAccounts { num_token_accounts } => {
225 buffer.push(3);
226 buffer.push(num_token_accounts);
227 }
228 Self::HarvestWithheldTokensToMint => {
229 buffer.push(4);
230 }
231 Self::SetTransferFee {
232 transfer_fee_basis_points,
233 maximum_fee,
234 } => {
235 buffer.push(5);
236 buffer.extend_from_slice(&transfer_fee_basis_points.to_le_bytes());
237 buffer.extend_from_slice(&maximum_fee.to_le_bytes());
238 }
239 }
240 }
241}
242
243pub fn initialize_transfer_fee_config(
245 token_program_id: &Pubkey,
246 mint: &Pubkey,
247 transfer_fee_config_authority: Option<&Pubkey>,
248 withdraw_withheld_authority: Option<&Pubkey>,
249 transfer_fee_basis_points: u16,
250 maximum_fee: u64,
251) -> Result<Instruction, ProgramError> {
252 check_program_account(token_program_id)?;
253 let transfer_fee_config_authority = transfer_fee_config_authority.cloned().into();
254 let withdraw_withheld_authority = withdraw_withheld_authority.cloned().into();
255 let data = TokenInstruction::TransferFeeExtension(
256 TransferFeeInstruction::InitializeTransferFeeConfig {
257 transfer_fee_config_authority,
258 withdraw_withheld_authority,
259 transfer_fee_basis_points,
260 maximum_fee,
261 },
262 )
263 .pack();
264
265 Ok(Instruction {
266 program_id: *token_program_id,
267 accounts: vec![AccountMeta::new(*mint, false)],
268 data,
269 })
270}
271
272#[allow(clippy::too_many_arguments)]
274pub fn transfer_checked_with_fee(
275 token_program_id: &Pubkey,
276 source: &Pubkey,
277 mint: &Pubkey,
278 destination: &Pubkey,
279 authority: &Pubkey,
280 signers: &[&Pubkey],
281 amount: u64,
282 decimals: u8,
283 fee: u64,
284) -> Result<Instruction, ProgramError> {
285 check_program_account(token_program_id)?;
286 let data =
287 TokenInstruction::TransferFeeExtension(TransferFeeInstruction::TransferCheckedWithFee {
288 amount,
289 decimals,
290 fee,
291 })
292 .pack();
293
294 let mut accounts = Vec::with_capacity(4 + signers.len());
295 accounts.push(AccountMeta::new(*source, false));
296 accounts.push(AccountMeta::new_readonly(*mint, false));
297 accounts.push(AccountMeta::new(*destination, false));
298 accounts.push(AccountMeta::new_readonly(*authority, signers.is_empty()));
299 for signer in signers.iter() {
300 accounts.push(AccountMeta::new_readonly(**signer, true));
301 }
302
303 Ok(Instruction {
304 program_id: *token_program_id,
305 accounts,
306 data,
307 })
308}
309
310pub fn withdraw_withheld_tokens_from_mint(
312 token_program_id: &Pubkey,
313 mint: &Pubkey,
314 destination: &Pubkey,
315 authority: &Pubkey,
316 signers: &[&Pubkey],
317) -> Result<Instruction, ProgramError> {
318 check_program_account(token_program_id)?;
319 let mut accounts = Vec::with_capacity(3 + signers.len());
320 accounts.push(AccountMeta::new(*mint, false));
321 accounts.push(AccountMeta::new(*destination, false));
322 accounts.push(AccountMeta::new_readonly(*authority, signers.is_empty()));
323 for signer in signers.iter() {
324 accounts.push(AccountMeta::new_readonly(**signer, true));
325 }
326
327 Ok(Instruction {
328 program_id: *token_program_id,
329 accounts,
330 data: TokenInstruction::TransferFeeExtension(
331 TransferFeeInstruction::WithdrawWithheldTokensFromMint,
332 )
333 .pack(),
334 })
335}
336
337pub fn withdraw_withheld_tokens_from_accounts(
339 token_program_id: &Pubkey,
340 mint: &Pubkey,
341 destination: &Pubkey,
342 authority: &Pubkey,
343 signers: &[&Pubkey],
344 sources: &[&Pubkey],
345) -> Result<Instruction, ProgramError> {
346 check_program_account(token_program_id)?;
347 let num_token_accounts =
348 u8::try_from(sources.len()).map_err(|_| ProgramError::InvalidInstructionData)?;
349 let mut accounts = Vec::with_capacity(3 + signers.len() + sources.len());
350 accounts.push(AccountMeta::new_readonly(*mint, false));
351 accounts.push(AccountMeta::new(*destination, false));
352 accounts.push(AccountMeta::new_readonly(*authority, signers.is_empty()));
353 for signer in signers.iter() {
354 accounts.push(AccountMeta::new_readonly(**signer, true));
355 }
356 for source in sources.iter() {
357 accounts.push(AccountMeta::new(**source, false));
358 }
359
360 Ok(Instruction {
361 program_id: *token_program_id,
362 accounts,
363 data: TokenInstruction::TransferFeeExtension(
364 TransferFeeInstruction::WithdrawWithheldTokensFromAccounts { num_token_accounts },
365 )
366 .pack(),
367 })
368}
369
370pub fn harvest_withheld_tokens_to_mint(
372 token_program_id: &Pubkey,
373 mint: &Pubkey,
374 sources: &[&Pubkey],
375) -> Result<Instruction, ProgramError> {
376 check_program_account(token_program_id)?;
377 let mut accounts = Vec::with_capacity(1 + sources.len());
378 accounts.push(AccountMeta::new(*mint, false));
379 for source in sources.iter() {
380 accounts.push(AccountMeta::new(**source, false));
381 }
382 Ok(Instruction {
383 program_id: *token_program_id,
384 accounts,
385 data: TokenInstruction::TransferFeeExtension(
386 TransferFeeInstruction::HarvestWithheldTokensToMint,
387 )
388 .pack(),
389 })
390}
391
392pub fn set_transfer_fee(
394 token_program_id: &Pubkey,
395 mint: &Pubkey,
396 authority: &Pubkey,
397 signers: &[&Pubkey],
398 transfer_fee_basis_points: u16,
399 maximum_fee: u64,
400) -> Result<Instruction, ProgramError> {
401 check_program_account(token_program_id)?;
402 let mut accounts = Vec::with_capacity(2 + signers.len());
403 accounts.push(AccountMeta::new(*mint, false));
404 accounts.push(AccountMeta::new_readonly(*authority, signers.is_empty()));
405 for signer in signers.iter() {
406 accounts.push(AccountMeta::new_readonly(**signer, true));
407 }
408
409 Ok(Instruction {
410 program_id: *token_program_id,
411 accounts,
412 data: TokenInstruction::TransferFeeExtension(TransferFeeInstruction::SetTransferFee {
413 transfer_fee_basis_points,
414 maximum_fee,
415 })
416 .pack(),
417 })
418}
419
420#[cfg(test)]
421mod test {
422 use super::*;
423
424 const TRANSFER_FEE_PREFIX: u8 = 26;
425
426 #[test]
427 fn test_instruction_packing() {
428 let check = TokenInstruction::TransferFeeExtension(
429 TransferFeeInstruction::InitializeTransferFeeConfig {
430 transfer_fee_config_authority: COption::Some(Pubkey::new(&[11u8; 32])),
431 withdraw_withheld_authority: COption::None,
432 transfer_fee_basis_points: 111,
433 maximum_fee: u64::MAX,
434 },
435 );
436 let packed = check.pack();
437 let mut expect = vec![TRANSFER_FEE_PREFIX, 0, 1];
438 expect.extend_from_slice(&[11u8; 32]);
439 expect.extend_from_slice(&[0]);
440 expect.extend_from_slice(&111u16.to_le_bytes());
441 expect.extend_from_slice(&u64::MAX.to_le_bytes());
442 assert_eq!(packed, expect);
443 let unpacked = TokenInstruction::unpack(&expect).unwrap();
444 assert_eq!(unpacked, check);
445
446 let check = TokenInstruction::TransferFeeExtension(
447 TransferFeeInstruction::TransferCheckedWithFee {
448 amount: 24,
449 decimals: 24,
450 fee: 23,
451 },
452 );
453 let packed = check.pack();
454 let mut expect = vec![TRANSFER_FEE_PREFIX, 1];
455 expect.extend_from_slice(&24u64.to_le_bytes());
456 expect.extend_from_slice(&[24u8]);
457 expect.extend_from_slice(&23u64.to_le_bytes());
458 assert_eq!(packed, expect);
459 let unpacked = TokenInstruction::unpack(&expect).unwrap();
460 assert_eq!(unpacked, check);
461
462 let check = TokenInstruction::TransferFeeExtension(
463 TransferFeeInstruction::WithdrawWithheldTokensFromMint,
464 );
465 let packed = check.pack();
466 let expect = [TRANSFER_FEE_PREFIX, 2];
467 assert_eq!(packed, expect);
468 let unpacked = TokenInstruction::unpack(&expect).unwrap();
469 assert_eq!(unpacked, check);
470
471 let num_token_accounts = 255;
472 let check = TokenInstruction::TransferFeeExtension(
473 TransferFeeInstruction::WithdrawWithheldTokensFromAccounts { num_token_accounts },
474 );
475 let packed = check.pack();
476 let expect = [TRANSFER_FEE_PREFIX, 3, num_token_accounts];
477 assert_eq!(packed, expect);
478 let unpacked = TokenInstruction::unpack(&expect).unwrap();
479 assert_eq!(unpacked, check);
480
481 let check = TokenInstruction::TransferFeeExtension(
482 TransferFeeInstruction::HarvestWithheldTokensToMint,
483 );
484 let packed = check.pack();
485 let expect = [TRANSFER_FEE_PREFIX, 4];
486 assert_eq!(packed, expect);
487 let unpacked = TokenInstruction::unpack(&expect).unwrap();
488 assert_eq!(unpacked, check);
489
490 let check =
491 TokenInstruction::TransferFeeExtension(TransferFeeInstruction::SetTransferFee {
492 transfer_fee_basis_points: u16::MAX,
493 maximum_fee: u64::MAX,
494 });
495 let packed = check.pack();
496 let mut expect = vec![TRANSFER_FEE_PREFIX, 5];
497 expect.extend_from_slice(&u16::MAX.to_le_bytes());
498 expect.extend_from_slice(&u64::MAX.to_le_bytes());
499 assert_eq!(packed, expect);
500 let unpacked = TokenInstruction::unpack(&expect).unwrap();
501 assert_eq!(unpacked, check);
502 }
503}