Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 93c7ad7

Browse files
authored
token-2022: Support extensions in burn (#2869)
1 parent 9b52455 commit 93c7ad7

File tree

3 files changed

+218
-17
lines changed

3 files changed

+218
-17
lines changed

token/program-2022/src/processor.rs

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -735,29 +735,32 @@ impl Processor {
735735
let authority_info = next_account_info(account_info_iter)?;
736736
let authority_info_data_len = authority_info.data_len();
737737

738-
let mut source_account = Account::unpack(&source_account_info.data.borrow())?;
739-
let mut mint = Mint::unpack(&mint_info.data.borrow())?;
738+
let mut source_account_data = source_account_info.data.borrow_mut();
739+
let mut source_account =
740+
StateWithExtensionsMut::<Account>::unpack(&mut source_account_data)?;
741+
let mut mint_data = mint_info.data.borrow_mut();
742+
let mut mint = StateWithExtensionsMut::<Mint>::unpack(&mut mint_data)?;
740743

741-
if source_account.is_frozen() {
744+
if source_account.base.is_frozen() {
742745
return Err(TokenError::AccountFrozen.into());
743746
}
744-
if source_account.is_native() {
747+
if source_account.base.is_native() {
745748
return Err(TokenError::NativeNotSupported.into());
746749
}
747-
if source_account.amount < amount {
750+
if source_account.base.amount < amount {
748751
return Err(TokenError::InsufficientFunds.into());
749752
}
750-
if mint_info.key != &source_account.mint {
753+
if mint_info.key != &source_account.base.mint {
751754
return Err(TokenError::MintMismatch.into());
752755
}
753756

754757
if let Some(expected_decimals) = expected_decimals {
755-
if expected_decimals != mint.decimals {
758+
if expected_decimals != mint.base.decimals {
756759
return Err(TokenError::MintDecimalsMismatch.into());
757760
}
758761
}
759762

760-
match source_account.delegate {
763+
match source_account.base.delegate {
761764
COption::Some(ref delegate) if cmp_pubkeys(authority_info.key, delegate) => {
762765
Self::validate_owner(
763766
program_id,
@@ -767,20 +770,21 @@ impl Processor {
767770
account_info_iter.as_slice(),
768771
)?;
769772

770-
if source_account.delegated_amount < amount {
773+
if source_account.base.delegated_amount < amount {
771774
return Err(TokenError::InsufficientFunds.into());
772775
}
773-
source_account.delegated_amount = source_account
776+
source_account.base.delegated_amount = source_account
777+
.base
774778
.delegated_amount
775779
.checked_sub(amount)
776780
.ok_or(TokenError::Overflow)?;
777-
if source_account.delegated_amount == 0 {
778-
source_account.delegate = COption::None;
781+
if source_account.base.delegated_amount == 0 {
782+
source_account.base.delegate = COption::None;
779783
}
780784
}
781785
_ => Self::validate_owner(
782786
program_id,
783-
&source_account.owner,
787+
&source_account.base.owner,
784788
authority_info,
785789
authority_info_data_len,
786790
account_info_iter.as_slice(),
@@ -793,17 +797,19 @@ impl Processor {
793797
check_program_account(source_account_info.owner)?;
794798
check_program_account(mint_info.owner)?;
795799

796-
source_account.amount = source_account
800+
source_account.base.amount = source_account
801+
.base
797802
.amount
798803
.checked_sub(amount)
799804
.ok_or(TokenError::Overflow)?;
800-
mint.supply = mint
805+
mint.base.supply = mint
806+
.base
801807
.supply
802808
.checked_sub(amount)
803809
.ok_or(TokenError::Overflow)?;
804810

805-
Account::pack(source_account, &mut source_account_info.data.borrow_mut())?;
806-
Mint::pack(mint, &mut mint_info.data.borrow_mut())?;
811+
source_account.pack_base();
812+
mint.pack_base();
807813

808814
Ok(())
809815
}

token/program-2022/tests/burn.rs

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
#![cfg(feature = "test-bpf")]
2+
3+
mod program_test;
4+
use {
5+
program_test::{TestContext, TokenContext},
6+
solana_program_test::tokio,
7+
solana_sdk::{
8+
instruction::InstructionError, pubkey::Pubkey, signature::Signer, signer::keypair::Keypair,
9+
transaction::TransactionError, transport::TransportError,
10+
},
11+
spl_token_2022::error::TokenError,
12+
spl_token_client::token::{ExtensionInitializationParams, TokenError as TokenClientError},
13+
};
14+
15+
async fn run_basic(context: TestContext) {
16+
let TokenContext {
17+
decimals,
18+
mint_authority,
19+
token,
20+
alice,
21+
bob,
22+
..
23+
} = context.token_context.unwrap();
24+
25+
let alice_account = Keypair::new();
26+
let alice_account = token
27+
.create_auxiliary_token_account(&alice_account, &alice.pubkey())
28+
.await
29+
.unwrap();
30+
31+
// mint a token
32+
let amount = 10;
33+
token
34+
.mint_to(&alice_account, &mint_authority, amount)
35+
.await
36+
.unwrap();
37+
38+
// unchecked is ok
39+
token.burn(&alice_account, &alice, 1).await.unwrap();
40+
41+
// checked is ok
42+
token
43+
.burn_checked(&alice_account, &alice, 1, decimals)
44+
.await
45+
.unwrap();
46+
47+
// burn too much is not ok
48+
let error = token
49+
.burn_checked(&alice_account, &alice, amount, decimals)
50+
.await
51+
.unwrap_err();
52+
assert_eq!(
53+
error,
54+
TokenClientError::Client(Box::new(TransportError::TransactionError(
55+
TransactionError::InstructionError(
56+
0,
57+
InstructionError::Custom(TokenError::InsufficientFunds as u32)
58+
)
59+
)))
60+
);
61+
62+
// wrong signer
63+
let error = token
64+
.burn_checked(&alice_account, &bob, 1, decimals)
65+
.await
66+
.unwrap_err();
67+
assert_eq!(
68+
error,
69+
TokenClientError::Client(Box::new(TransportError::TransactionError(
70+
TransactionError::InstructionError(
71+
0,
72+
InstructionError::Custom(TokenError::OwnerMismatch as u32)
73+
)
74+
)))
75+
);
76+
}
77+
78+
#[tokio::test]
79+
async fn basic() {
80+
let mut context = TestContext::new().await;
81+
context.init_token_with_mint(vec![]).await.unwrap();
82+
run_basic(context).await;
83+
}
84+
85+
#[tokio::test]
86+
async fn basic_with_extension() {
87+
let mut context = TestContext::new().await;
88+
context
89+
.init_token_with_mint(vec![ExtensionInitializationParams::TransferFeeConfig {
90+
transfer_fee_config_authority: Some(Pubkey::new_unique()),
91+
withdraw_withheld_authority: Some(Pubkey::new_unique()),
92+
transfer_fee_basis_points: 100u16,
93+
maximum_fee: 1_000u64,
94+
}])
95+
.await
96+
.unwrap();
97+
run_basic(context).await;
98+
}
99+
100+
async fn run_self_owned(context: TestContext) {
101+
let TokenContext {
102+
decimals,
103+
mint_authority,
104+
token,
105+
alice,
106+
..
107+
} = context.token_context.unwrap();
108+
109+
let alice_account = token
110+
.create_auxiliary_token_account(&alice, &alice.pubkey())
111+
.await
112+
.unwrap();
113+
114+
// mint a token
115+
let amount = 10;
116+
token
117+
.mint_to(&alice_account, &mint_authority, amount)
118+
.await
119+
.unwrap();
120+
121+
// unchecked is ok
122+
token.burn(&alice_account, &alice, 1).await.unwrap();
123+
124+
// checked is ok
125+
token
126+
.burn_checked(&alice_account, &alice, 1, decimals)
127+
.await
128+
.unwrap();
129+
}
130+
131+
#[tokio::test]
132+
async fn self_owned() {
133+
let mut context = TestContext::new().await;
134+
context.init_token_with_mint(vec![]).await.unwrap();
135+
run_self_owned(context).await;
136+
}
137+
138+
#[tokio::test]
139+
async fn self_owned_with_extension() {
140+
let mut context = TestContext::new().await;
141+
context
142+
.init_token_with_mint(vec![ExtensionInitializationParams::TransferFeeConfig {
143+
transfer_fee_config_authority: Some(Pubkey::new_unique()),
144+
withdraw_withheld_authority: Some(Pubkey::new_unique()),
145+
transfer_fee_basis_points: 100u16,
146+
maximum_fee: 1_000u64,
147+
}])
148+
.await
149+
.unwrap();
150+
run_self_owned(context).await;
151+
}

token/rust/src/token.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,50 @@ where
461461
.await
462462
}
463463

464+
/// Burn tokens from account
465+
pub async fn burn<S2: Signer>(
466+
&self,
467+
source: &Pubkey,
468+
authority: &S2,
469+
amount: u64,
470+
) -> TokenResult<T::Output> {
471+
self.process_ixs(
472+
&[instruction::burn(
473+
&self.program_id,
474+
source,
475+
&self.pubkey,
476+
&authority.pubkey(),
477+
&[],
478+
amount,
479+
)?],
480+
&[authority],
481+
)
482+
.await
483+
}
484+
485+
/// Burn tokens from account
486+
pub async fn burn_checked<S2: Signer>(
487+
&self,
488+
source: &Pubkey,
489+
authority: &S2,
490+
amount: u64,
491+
decimals: u8,
492+
) -> TokenResult<T::Output> {
493+
self.process_ixs(
494+
&[instruction::burn_checked(
495+
&self.program_id,
496+
source,
497+
&self.pubkey,
498+
&authority.pubkey(),
499+
&[],
500+
amount,
501+
decimals,
502+
)?],
503+
&[authority],
504+
)
505+
.await
506+
}
507+
464508
/// Close account into another
465509
pub async fn close_account<S2: Signer>(
466510
&self,

0 commit comments

Comments
 (0)