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