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