Skip to content
This repository has been archived by the owner on Jan 10, 2025. It is now read-only.

Commit

Permalink
token-2022: Add init transfer fee config (#2757)
Browse files Browse the repository at this point in the history
  • Loading branch information
joncinque authored Jan 20, 2022
1 parent b6bafc7 commit ba46fed
Show file tree
Hide file tree
Showing 10 changed files with 281 additions and 65 deletions.
6 changes: 3 additions & 3 deletions token/program-2022/src/extension/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -906,7 +906,7 @@ mod test {
let new_extension = state.init_extension::<TransferFeeConfig>().unwrap();
new_extension.transfer_fee_config_authority =
mint_transfer_fee.transfer_fee_config_authority;
new_extension.withheld_withdraw_authority = mint_transfer_fee.withheld_withdraw_authority;
new_extension.withdraw_withheld_authority = mint_transfer_fee.withdraw_withheld_authority;
new_extension.withheld_amount = mint_transfer_fee.withheld_amount;
new_extension.older_transfer_fee = mint_transfer_fee.older_transfer_fee;
new_extension.newer_transfer_fee = mint_transfer_fee.newer_transfer_fee;
Expand Down Expand Up @@ -958,7 +958,7 @@ mod test {
let mint_transfer_fee = test_transfer_fee_config();
let extension = state.init_extension::<TransferFeeConfig>().unwrap();
extension.transfer_fee_config_authority = mint_transfer_fee.transfer_fee_config_authority;
extension.withheld_withdraw_authority = mint_transfer_fee.withheld_withdraw_authority;
extension.withdraw_withheld_authority = mint_transfer_fee.withdraw_withheld_authority;
extension.withheld_amount = mint_transfer_fee.withheld_amount;
extension.older_transfer_fee = mint_transfer_fee.older_transfer_fee;
extension.newer_transfer_fee = mint_transfer_fee.newer_transfer_fee;
Expand Down Expand Up @@ -990,7 +990,7 @@ mod test {
let mint_transfer_fee = test_transfer_fee_config();
let extension = state.init_extension::<TransferFeeConfig>().unwrap();
extension.transfer_fee_config_authority = mint_transfer_fee.transfer_fee_config_authority;
extension.withheld_withdraw_authority = mint_transfer_fee.withheld_withdraw_authority;
extension.withdraw_withheld_authority = mint_transfer_fee.withdraw_withheld_authority;
extension.withheld_amount = mint_transfer_fee.withheld_amount;
extension.older_transfer_fee = mint_transfer_fee.older_transfer_fee;
extension.newer_transfer_fee = mint_transfer_fee.newer_transfer_fee;
Expand Down
17 changes: 9 additions & 8 deletions token/program-2022/src/extension/transfer_fee/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub enum TransferFeeInstruction {
/// 0. `[writable]` The mint to initialize.
InitializeTransferFeeConfig {
/// Pubkey that may update the fees
fee_config_authority: COption<Pubkey>,
transfer_fee_config_authority: COption<Pubkey>,
/// Withdraw instructions must be signed by this key
withdraw_withheld_authority: COption<Pubkey>,
/// Amount of transfer collected as fees, expressed as basis points of the
Expand Down Expand Up @@ -137,7 +137,8 @@ impl TransferFeeInstruction {
let (&tag, rest) = input.split_first().ok_or(InvalidInstruction)?;
Ok(match tag {
0 => {
let (fee_config_authority, rest) = TokenInstruction::unpack_pubkey_option(rest)?;
let (transfer_fee_config_authority, rest) =
TokenInstruction::unpack_pubkey_option(rest)?;
let (withdraw_withheld_authority, rest) =
TokenInstruction::unpack_pubkey_option(rest)?;
let (transfer_fee_basis_points, rest) = rest.split_at(2);
Expand All @@ -153,7 +154,7 @@ impl TransferFeeInstruction {
.map(u64::from_le_bytes)
.ok_or(InvalidInstruction)?;
let instruction = Self::InitializeTransferFeeConfig {
fee_config_authority,
transfer_fee_config_authority,
withdraw_withheld_authority,
transfer_fee_basis_points,
maximum_fee,
Expand Down Expand Up @@ -211,13 +212,13 @@ impl TransferFeeInstruction {
pub fn pack(&self, buffer: &mut Vec<u8>) {
match *self {
Self::InitializeTransferFeeConfig {
ref fee_config_authority,
ref transfer_fee_config_authority,
ref withdraw_withheld_authority,
transfer_fee_basis_points,
maximum_fee,
} => {
buffer.push(0);
TokenInstruction::pack_pubkey_option(fee_config_authority, buffer);
TokenInstruction::pack_pubkey_option(transfer_fee_config_authority, buffer);
TokenInstruction::pack_pubkey_option(withdraw_withheld_authority, buffer);
buffer.extend_from_slice(&transfer_fee_basis_points.to_le_bytes());
buffer.extend_from_slice(&maximum_fee.to_le_bytes());
Expand Down Expand Up @@ -256,14 +257,14 @@ impl TransferFeeInstruction {
/// Create a `InitializeTransferFeeConfig` instruction
pub fn initialize_transfer_fee_config(
mint: Pubkey,
fee_config_authority: COption<Pubkey>,
transfer_fee_config_authority: COption<Pubkey>,
withdraw_withheld_authority: COption<Pubkey>,
transfer_fee_basis_points: u16,
maximum_fee: u64,
) -> Instruction {
let data = TokenInstruction::TransferFeeExtension(
TransferFeeInstruction::InitializeTransferFeeConfig {
fee_config_authority,
transfer_fee_config_authority,
withdraw_withheld_authority,
transfer_fee_basis_points,
maximum_fee,
Expand Down Expand Up @@ -419,7 +420,7 @@ mod test {
fn test_instruction_packing() {
let check = TokenInstruction::TransferFeeExtension(
TransferFeeInstruction::InitializeTransferFeeConfig {
fee_config_authority: COption::Some(Pubkey::new(&[11u8; 32])),
transfer_fee_config_authority: COption::Some(Pubkey::new(&[11u8; 32])),
withdraw_withheld_authority: COption::None,
transfer_fee_basis_points: 111,
maximum_fee: u64::MAX,
Expand Down
4 changes: 2 additions & 2 deletions token/program-2022/src/extension/transfer_fee/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub struct TransferFeeConfig {
/// Optional authority to set the fee
pub transfer_fee_config_authority: OptionalNonZeroPubkey,
/// Withdraw from mint instructions must be signed by this key
pub withheld_withdraw_authority: OptionalNonZeroPubkey,
pub withdraw_withheld_authority: OptionalNonZeroPubkey,
/// Withheld transfer fee tokens that have been moved to the mint for withdrawal
pub withheld_amount: PodU64,
/// Older transfer fee, used if the current epoch < new_transfer_fee.epoch
Expand Down Expand Up @@ -65,7 +65,7 @@ pub(crate) mod test {
&[10; 32],
)))
.unwrap(),
withheld_withdraw_authority: OptionalNonZeroPubkey::try_from(Some(Pubkey::new(
withdraw_withheld_authority: OptionalNonZeroPubkey::try_from(Some(Pubkey::new(
&[11; 32],
)))
.unwrap(),
Expand Down
52 changes: 42 additions & 10 deletions token/program-2022/src/extension/transfer_fee/processor.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,51 @@
use {
crate::{check_program_account, extension::transfer_fee::instruction::TransferFeeInstruction},
crate::{
check_program_account,
extension::{
transfer_fee::{instruction::TransferFeeInstruction, TransferFee, TransferFeeConfig},
StateWithExtensionsMut,
},
state::Mint,
},
solana_program::{
account_info::AccountInfo, entrypoint::ProgramResult, program_option::COption,
account_info::{next_account_info, AccountInfo},
clock::Clock,
entrypoint::ProgramResult,
program_option::COption,
pubkey::Pubkey,
sysvar::Sysvar,
},
std::convert::TryInto,
};

fn process_initialize_transfer_fee_config(
_accounts: &[AccountInfo],
_fee_config_authority: COption<Pubkey>,
_withdraw_withheld_authority: COption<Pubkey>,
_transfer_fee_basis_points: u16,
_maximum_fee: u64,
accounts: &[AccountInfo],
transfer_fee_config_authority: COption<Pubkey>,
withdraw_withheld_authority: COption<Pubkey>,
transfer_fee_basis_points: u16,
maximum_fee: u64,
) -> ProgramResult {
unimplemented!();
let account_info_iter = &mut accounts.iter();
let mint_account_info = next_account_info(account_info_iter)?;

let mut mint_data = mint_account_info.data.borrow_mut();
let mut mint = StateWithExtensionsMut::<Mint>::unpack_uninitialized(&mut mint_data)?;
let extension = mint.init_extension::<TransferFeeConfig>()?;
extension.transfer_fee_config_authority = transfer_fee_config_authority.try_into()?;
extension.withdraw_withheld_authority = withdraw_withheld_authority.try_into()?;
extension.withheld_amount = 0u64.into();
// To be safe, set newer and older transfer fees to the same thing on init,
// but only newer will actually be used
let epoch = Clock::get()?.epoch;
let transfer_fee = TransferFee {
epoch: epoch.into(),
transfer_fee_basis_points: transfer_fee_basis_points.into(),
maximum_fee: maximum_fee.into(),
};
extension.older_transfer_fee = transfer_fee;
extension.newer_transfer_fee = transfer_fee;

Ok(())
}

pub(crate) fn process_instruction(
Expand All @@ -25,13 +57,13 @@ pub(crate) fn process_instruction(

match instruction {
TransferFeeInstruction::InitializeTransferFeeConfig {
fee_config_authority,
transfer_fee_config_authority,
withdraw_withheld_authority,
transfer_fee_basis_points,
maximum_fee,
} => process_initialize_transfer_fee_config(
accounts,
fee_config_authority,
transfer_fee_config_authority,
withdraw_withheld_authority,
transfer_fee_basis_points,
maximum_fee,
Expand Down
11 changes: 10 additions & 1 deletion token/program-2022/src/pod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,23 @@ impl TryFrom<COption<Pubkey>> for OptionalNonZeroPubkey {
}
}
impl From<OptionalNonZeroPubkey> for Option<Pubkey> {
fn from(p: OptionalNonZeroPubkey) -> Option<Pubkey> {
fn from(p: OptionalNonZeroPubkey) -> Self {
if p.0 == Pubkey::default() {
None
} else {
Some(p.0)
}
}
}
impl From<OptionalNonZeroPubkey> for COption<Pubkey> {
fn from(p: OptionalNonZeroPubkey) -> Self {
if p.0 == Pubkey::default() {
COption::None
} else {
COption::Some(p.0)
}
}
}

/// The standard `bool` is not a `Pod`, define a replacement that is
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
Expand Down
69 changes: 61 additions & 8 deletions token/program-2022/tests/initialize_mint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use {
},
spl_token_2022::{
error::TokenError,
extension::{mint_close_authority::MintCloseAuthority, ExtensionType},
extension::{mint_close_authority::MintCloseAuthority, transfer_fee, ExtensionType},
id, instruction,
processor::Processor,
state::Mint,
Expand All @@ -32,7 +32,7 @@ async fn success_base() {
mint_authority,
token,
..
} = TestContext::new(vec![]).await;
} = TestContext::new(vec![]).await.unwrap();

let mint = token.get_mint_info().await.unwrap();
assert_eq!(mint.base.decimals, decimals);
Expand Down Expand Up @@ -159,12 +159,11 @@ async fn success_extension_and_base() {
mint_authority,
token,
..
} = TestContext::new(vec![
ExtensionInitializationParams::InitializeMintCloseAuthority {
close_authority: close_authority.clone(),
},
])
.await;
} = TestContext::new(vec![ExtensionInitializationParams::MintCloseAuthority {
close_authority: close_authority.clone(),
}])
.await
.unwrap();

let state = token.get_mint_info().await.unwrap();
assert_eq!(state.base.decimals, decimals);
Expand Down Expand Up @@ -415,3 +414,57 @@ async fn fail_account_init_after_mint_init_with_extension() {
TransactionError::InstructionError(3, InstructionError::InvalidAccountData)
);
}

#[tokio::test]
async fn fail_fee_init_after_mint_init() {
let program_test = ProgramTest::new("spl_token_2022", id(), processor!(Processor::process));
let mut ctx = program_test.start_with_context().await;
let rent = ctx.banks_client.get_rent().await.unwrap();
let mint_account = Keypair::new();
let mint_authority_pubkey = Pubkey::new_unique();

let space = ExtensionType::get_account_len::<Mint>(&[ExtensionType::TransferFeeConfig]);
let instructions = vec![
system_instruction::create_account(
&ctx.payer.pubkey(),
&mint_account.pubkey(),
rent.minimum_balance(space),
space as u64,
&spl_token_2022::id(),
),
instruction::initialize_mint(
&spl_token_2022::id(),
&mint_account.pubkey(),
&mint_authority_pubkey,
None,
9,
)
.unwrap(),
transfer_fee::instruction::initialize_transfer_fee_config(
mint_account.pubkey(),
COption::Some(Pubkey::new_unique()),
COption::Some(Pubkey::new_unique()),
10,
100,
),
];

let tx = Transaction::new_signed_with_payer(
&instructions,
Some(&ctx.payer.pubkey()),
&[&ctx.payer, &mint_account],
ctx.last_blockhash,
);
#[allow(clippy::useless_conversion)]
let err: TransactionError = ctx
.banks_client
.process_transaction(tx)
.await
.unwrap_err()
.unwrap()
.into();
assert_eq!(
err,
TransactionError::InstructionError(1, InstructionError::InvalidAccountData)
);
}
44 changes: 21 additions & 23 deletions token/program-2022/tests/mint_close_authority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,11 @@ async fn success_init() {
mint_authority,
token,
..
} = TestContext::new(vec![
ExtensionInitializationParams::InitializeMintCloseAuthority {
close_authority: close_authority.clone(),
},
])
.await;
} = TestContext::new(vec![ExtensionInitializationParams::MintCloseAuthority {
close_authority: close_authority.clone(),
}])
.await
.unwrap();

let state = token.get_mint_info().await.unwrap();
assert_eq!(state.base.decimals, decimals);
Expand All @@ -49,12 +48,12 @@ async fn success_init() {
#[tokio::test]
async fn set_authority() {
let close_authority = Keypair::new();
let TestContext { token, .. } = TestContext::new(vec![
ExtensionInitializationParams::InitializeMintCloseAuthority {
let TestContext { token, .. } =
TestContext::new(vec![ExtensionInitializationParams::MintCloseAuthority {
close_authority: COption::Some(close_authority.pubkey()),
},
])
.await;
}])
.await
.unwrap();
let new_authority = Keypair::new();

// fail, wrong signature
Expand Down Expand Up @@ -149,12 +148,12 @@ async fn set_authority() {
#[tokio::test]
async fn success_close() {
let close_authority = Keypair::new();
let TestContext { token, .. } = TestContext::new(vec![
ExtensionInitializationParams::InitializeMintCloseAuthority {
let TestContext { token, .. } =
TestContext::new(vec![ExtensionInitializationParams::MintCloseAuthority {
close_authority: COption::Some(close_authority.pubkey()),
},
])
.await;
}])
.await
.unwrap();

let destination = Pubkey::new_unique();
token
Expand All @@ -172,7 +171,7 @@ async fn fail_without_extension() {
mint_authority,
token,
..
} = TestContext::new(vec![]).await;
} = TestContext::new(vec![]).await.unwrap();

// fail set
let err = token
Expand Down Expand Up @@ -212,12 +211,11 @@ async fn fail_close_with_supply() {
token,
mint_authority,
..
} = TestContext::new(vec![
ExtensionInitializationParams::InitializeMintCloseAuthority {
close_authority: COption::Some(close_authority.pubkey()),
},
])
.await;
} = TestContext::new(vec![ExtensionInitializationParams::MintCloseAuthority {
close_authority: COption::Some(close_authority.pubkey()),
}])
.await
.unwrap();

// mint a token
let owner = Pubkey::new_unique();
Expand Down
Loading

0 comments on commit ba46fed

Please sign in to comment.