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

Commit 129b356

Browse files
authored
token-2022: Support approve / revoke with extensions (#2870)
1 parent d3539c1 commit 129b356

File tree

3 files changed

+354
-18
lines changed

3 files changed

+354
-18
lines changed

token/program-2022/src/processor.rs

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -434,35 +434,37 @@ impl Processor {
434434
let owner_info = next_account_info(account_info_iter)?;
435435
let owner_info_data_len = owner_info.data_len();
436436

437-
let mut source_account = Account::unpack(&source_account_info.data.borrow())?;
437+
let mut source_account_data = source_account_info.data.borrow_mut();
438+
let mut source_account =
439+
StateWithExtensionsMut::<Account>::unpack(&mut source_account_data)?;
438440

439-
if source_account.is_frozen() {
441+
if source_account.base.is_frozen() {
440442
return Err(TokenError::AccountFrozen.into());
441443
}
442444

443445
if let Some((mint_info, expected_decimals)) = expected_mint_info {
444-
if !cmp_pubkeys(&source_account.mint, mint_info.key) {
446+
if !cmp_pubkeys(&source_account.base.mint, mint_info.key) {
445447
return Err(TokenError::MintMismatch.into());
446448
}
447449

448-
let mint = Mint::unpack(&mint_info.data.borrow_mut())?;
449-
if expected_decimals != mint.decimals {
450+
let mint_data = mint_info.data.borrow();
451+
let mint = StateWithExtensions::<Mint>::unpack(&mint_data)?;
452+
if expected_decimals != mint.base.decimals {
450453
return Err(TokenError::MintDecimalsMismatch.into());
451454
}
452455
}
453456

454457
Self::validate_owner(
455458
program_id,
456-
&source_account.owner,
459+
&source_account.base.owner,
457460
owner_info,
458461
owner_info_data_len,
459462
account_info_iter.as_slice(),
460463
)?;
461464

462-
source_account.delegate = COption::Some(*delegate_info.key);
463-
source_account.delegated_amount = amount;
464-
465-
Account::pack(source_account, &mut source_account_info.data.borrow_mut())?;
465+
source_account.base.delegate = COption::Some(*delegate_info.key);
466+
source_account.base.delegated_amount = amount;
467+
source_account.pack_base();
466468

467469
Ok(())
468470
}
@@ -474,28 +476,29 @@ impl Processor {
474476
let authority_info = next_account_info(account_info_iter)?;
475477
let authority_info_data_len = authority_info.data_len();
476478

477-
let mut source_account = Account::unpack(&source_account_info.data.borrow())?;
478-
if source_account.is_frozen() {
479+
let mut source_account_data = source_account_info.data.borrow_mut();
480+
let mut source_account =
481+
StateWithExtensionsMut::<Account>::unpack(&mut source_account_data)?;
482+
if source_account.base.is_frozen() {
479483
return Err(TokenError::AccountFrozen.into());
480484
}
481485

482486
Self::validate_owner(
483487
program_id,
484-
match source_account.delegate {
488+
match source_account.base.delegate {
485489
COption::Some(ref delegate) if cmp_pubkeys(authority_info.key, delegate) => {
486490
delegate
487491
}
488-
_ => &source_account.owner,
492+
_ => &source_account.base.owner,
489493
},
490494
authority_info,
491495
authority_info_data_len,
492496
account_info_iter.as_slice(),
493497
)?;
494498

495-
source_account.delegate = COption::None;
496-
source_account.delegated_amount = 0;
497-
498-
Account::pack(source_account, &mut source_account_info.data.borrow_mut())?;
499+
source_account.base.delegate = COption::None;
500+
source_account.base.delegated_amount = 0;
501+
source_account.pack_base();
499502

500503
Ok(())
501504
}

token/program-2022/tests/delegate.rs

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
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+
#[derive(PartialEq)]
16+
enum TransferMode {
17+
All,
18+
CheckedOnly,
19+
}
20+
21+
#[derive(PartialEq)]
22+
enum ApproveMode {
23+
Unchecked,
24+
Checked,
25+
}
26+
27+
#[derive(PartialEq)]
28+
enum OwnerMode {
29+
SelfOwned,
30+
External,
31+
}
32+
33+
async fn run_basic(
34+
context: TestContext,
35+
owner_mode: OwnerMode,
36+
transfer_mode: TransferMode,
37+
approve_mode: ApproveMode,
38+
) {
39+
let TokenContext {
40+
decimals,
41+
mint_authority,
42+
token,
43+
alice,
44+
bob,
45+
..
46+
} = context.token_context.unwrap();
47+
48+
let alice_account = match owner_mode {
49+
OwnerMode::SelfOwned => token
50+
.create_auxiliary_token_account(&alice, &alice.pubkey())
51+
.await
52+
.unwrap(),
53+
OwnerMode::External => {
54+
let alice_account = Keypair::new();
55+
token
56+
.create_auxiliary_token_account(&alice_account, &alice.pubkey())
57+
.await
58+
.unwrap()
59+
}
60+
};
61+
let bob_account = Keypair::new();
62+
let bob_account = token
63+
.create_auxiliary_token_account(&bob_account, &bob.pubkey())
64+
.await
65+
.unwrap();
66+
67+
// mint tokens
68+
let amount = 100;
69+
token
70+
.mint_to(&alice_account, &mint_authority, amount)
71+
.await
72+
.unwrap();
73+
74+
// delegate to bob
75+
let delegated_amount = 10;
76+
match approve_mode {
77+
ApproveMode::Unchecked => token
78+
.approve(&alice_account, &bob.pubkey(), &alice, delegated_amount)
79+
.await
80+
.unwrap(),
81+
ApproveMode::Checked => token
82+
.approve_checked(
83+
&alice_account,
84+
&bob.pubkey(),
85+
&alice,
86+
delegated_amount,
87+
decimals,
88+
)
89+
.await
90+
.unwrap(),
91+
}
92+
93+
// transfer too much is not ok
94+
let error = token
95+
.transfer_checked(
96+
&alice_account,
97+
&bob_account,
98+
&bob,
99+
delegated_amount + 1,
100+
decimals,
101+
)
102+
.await
103+
.unwrap_err();
104+
assert_eq!(
105+
error,
106+
TokenClientError::Client(Box::new(TransportError::TransactionError(
107+
TransactionError::InstructionError(
108+
0,
109+
InstructionError::Custom(TokenError::InsufficientFunds as u32)
110+
)
111+
)))
112+
);
113+
114+
// transfer is ok
115+
if transfer_mode == TransferMode::All {
116+
token
117+
.transfer_unchecked(&alice_account, &bob_account, &bob, 1)
118+
.await
119+
.unwrap();
120+
}
121+
122+
token
123+
.transfer_checked(&alice_account, &bob_account, &bob, 1, decimals)
124+
.await
125+
.unwrap();
126+
127+
// burn is ok
128+
token.burn(&alice_account, &bob, 1).await.unwrap();
129+
token
130+
.burn_checked(&alice_account, &bob, 1, decimals)
131+
.await
132+
.unwrap();
133+
134+
// wrong signer
135+
let error = token
136+
.transfer_checked(&alice_account, &bob_account, &Keypair::new(), 1, decimals)
137+
.await
138+
.unwrap_err();
139+
assert_eq!(
140+
error,
141+
TokenClientError::Client(Box::new(TransportError::TransactionError(
142+
TransactionError::InstructionError(
143+
0,
144+
InstructionError::Custom(TokenError::OwnerMismatch as u32)
145+
)
146+
)))
147+
);
148+
149+
// revoke
150+
token.revoke(&alice_account, &alice).await.unwrap();
151+
152+
// now fails
153+
let error = token
154+
.transfer_checked(&alice_account, &bob_account, &bob, 1, decimals)
155+
.await
156+
.unwrap_err();
157+
assert_eq!(
158+
error,
159+
TokenClientError::Client(Box::new(TransportError::TransactionError(
160+
TransactionError::InstructionError(
161+
0,
162+
InstructionError::Custom(TokenError::OwnerMismatch as u32)
163+
)
164+
)))
165+
);
166+
}
167+
168+
#[tokio::test]
169+
async fn basic() {
170+
let mut context = TestContext::new().await;
171+
context.init_token_with_mint(vec![]).await.unwrap();
172+
run_basic(
173+
context,
174+
OwnerMode::External,
175+
TransferMode::All,
176+
ApproveMode::Unchecked,
177+
)
178+
.await;
179+
}
180+
181+
#[tokio::test]
182+
async fn basic_checked() {
183+
let mut context = TestContext::new().await;
184+
context.init_token_with_mint(vec![]).await.unwrap();
185+
run_basic(
186+
context,
187+
OwnerMode::External,
188+
TransferMode::All,
189+
ApproveMode::Checked,
190+
)
191+
.await;
192+
}
193+
194+
#[tokio::test]
195+
async fn basic_self_owned() {
196+
let mut context = TestContext::new().await;
197+
context.init_token_with_mint(vec![]).await.unwrap();
198+
run_basic(
199+
context,
200+
OwnerMode::SelfOwned,
201+
TransferMode::All,
202+
ApproveMode::Checked,
203+
)
204+
.await;
205+
}
206+
207+
#[tokio::test]
208+
async fn basic_with_extension() {
209+
let mut context = TestContext::new().await;
210+
context
211+
.init_token_with_mint(vec![ExtensionInitializationParams::TransferFeeConfig {
212+
transfer_fee_config_authority: Some(Pubkey::new_unique()),
213+
withdraw_withheld_authority: Some(Pubkey::new_unique()),
214+
transfer_fee_basis_points: 100u16,
215+
maximum_fee: 1_000u64,
216+
}])
217+
.await
218+
.unwrap();
219+
run_basic(
220+
context,
221+
OwnerMode::External,
222+
TransferMode::CheckedOnly,
223+
ApproveMode::Unchecked,
224+
)
225+
.await;
226+
}
227+
228+
#[tokio::test]
229+
async fn basic_with_extension_checked() {
230+
let mut context = TestContext::new().await;
231+
context
232+
.init_token_with_mint(vec![ExtensionInitializationParams::TransferFeeConfig {
233+
transfer_fee_config_authority: Some(Pubkey::new_unique()),
234+
withdraw_withheld_authority: Some(Pubkey::new_unique()),
235+
transfer_fee_basis_points: 100u16,
236+
maximum_fee: 1_000u64,
237+
}])
238+
.await
239+
.unwrap();
240+
run_basic(
241+
context,
242+
OwnerMode::External,
243+
TransferMode::CheckedOnly,
244+
ApproveMode::Checked,
245+
)
246+
.await;
247+
}
248+
249+
#[tokio::test]
250+
async fn basic_self_owned_with_extension() {
251+
let mut context = TestContext::new().await;
252+
context
253+
.init_token_with_mint(vec![ExtensionInitializationParams::TransferFeeConfig {
254+
transfer_fee_config_authority: Some(Pubkey::new_unique()),
255+
withdraw_withheld_authority: Some(Pubkey::new_unique()),
256+
transfer_fee_basis_points: 100u16,
257+
maximum_fee: 1_000u64,
258+
}])
259+
.await
260+
.unwrap();
261+
run_basic(
262+
context,
263+
OwnerMode::SelfOwned,
264+
TransferMode::CheckedOnly,
265+
ApproveMode::Checked,
266+
)
267+
.await;
268+
}

0 commit comments

Comments
 (0)