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