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

Commit e91725a

Browse files
token: Add close_authority and re-enable CloseAccount for non-native Accounts (#314)
* Add close_authority and re-enable CloseAccount for non-native Accounts * C headers
1 parent c00adbe commit e91725a

File tree

5 files changed

+215
-28
lines changed

5 files changed

+215
-28
lines changed

token/program2/inc/token2.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ enum Token_AuthorityType
6868
* Holder of a given token account
6969
*/
7070
Token_AuthorityType_AccountHolder,
71+
/**
72+
* Authority to close a token account
73+
*/
74+
Token_AuthorityType_CloseAccount,
7175
};
7276
#ifndef __cplusplus
7377
typedef uint8_t Token_AuthorityType;
@@ -439,6 +443,10 @@ typedef struct Token_Account {
439443
* The amount delegated
440444
*/
441445
uint64_t delegated_amount;
446+
/**
447+
* Optional authority to close the account.
448+
*/
449+
Token_COption_Pubkey close_authority;
442450
} Token_Account;
443451

444452
/**

token/program2/src/error.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ pub enum TokenError {
3737
/// Instruction does not support native tokens
3838
#[error("Instruction does not support native tokens")]
3939
NativeNotSupported,
40-
/// Instruction does not support non-native tokens
41-
#[error("Instruction does not support non-native tokens")]
42-
NonNativeNotSupported,
40+
/// Non-native account can only be closed if its balance is zero
41+
#[error("Non-native account can only be closed if its balance is zero")]
42+
NonNativeHasBalance,
4343
/// Invalid instruction
4444
#[error("Invalid instruction")]
4545
InvalidInstruction,

token/program2/src/instruction.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,8 @@ pub enum AuthorityType {
451451
FreezeAccount,
452452
/// Holder of a given token account
453453
AccountHolder,
454+
/// Authority to close a token account
455+
CloseAccount,
454456
}
455457

456458
impl AuthorityType {
@@ -459,6 +461,7 @@ impl AuthorityType {
459461
AuthorityType::MintTokens => 0,
460462
AuthorityType::FreezeAccount => 1,
461463
AuthorityType::AccountHolder => 2,
464+
AuthorityType::CloseAccount => 3,
462465
}
463466
}
464467

@@ -467,6 +470,7 @@ impl AuthorityType {
467470
0 => Ok(AuthorityType::MintTokens),
468471
1 => Ok(AuthorityType::FreezeAccount),
469472
2 => Ok(AuthorityType::AccountHolder),
473+
3 => Ok(AuthorityType::CloseAccount),
470474
_ => Err(TokenError::InvalidInstruction.into()),
471475
}
472476
}

token/program2/src/processor.rs

Lines changed: 198 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -254,34 +254,50 @@ impl Processor {
254254
let authority_info = next_account_info(account_info_iter)?;
255255

256256
if account_info.data_len() == size_of::<Account>() {
257-
if authority_type != AuthorityType::AccountHolder {
258-
return Err(TokenError::AuthorityTypeNotSupported.into());
259-
}
260257
let mut account_data = account_info.data.borrow_mut();
261258
let mut account: &mut Account = state::unpack(&mut account_data)?;
262259

263260
if account.is_frozen() {
264261
return Err(TokenError::AccountFrozen.into());
265262
}
266263

267-
Self::validate_owner(
268-
program_id,
269-
&account.owner,
270-
authority_info,
271-
account_info_iter.as_slice(),
272-
)?;
264+
match authority_type {
265+
AuthorityType::AccountHolder => {
266+
Self::validate_owner(
267+
program_id,
268+
&account.owner,
269+
authority_info,
270+
account_info_iter.as_slice(),
271+
)?;
273272

274-
if let COption::Some(authority) = new_authority {
275-
account.owner = authority;
276-
} else {
277-
return Err(TokenError::InvalidInstruction.into());
273+
if let COption::Some(authority) = new_authority {
274+
account.owner = authority;
275+
} else {
276+
return Err(TokenError::InvalidInstruction.into());
277+
}
278+
}
279+
AuthorityType::CloseAccount => {
280+
let authority = account.close_authority.unwrap_or(account.owner);
281+
Self::validate_owner(
282+
program_id,
283+
&authority,
284+
authority_info,
285+
account_info_iter.as_slice(),
286+
)?;
287+
account.close_authority = new_authority;
288+
}
289+
_ => {
290+
return Err(TokenError::AuthorityTypeNotSupported.into());
291+
}
278292
}
279293
} else if account_info.data_len() == size_of::<Mint>() {
280294
let mut account_data = account_info.data.borrow_mut();
281295
let mut mint: &mut Mint = state::unpack(&mut account_data)?;
282296

283297
match authority_type {
284298
AuthorityType::MintTokens => {
299+
// Once a mint's supply is fixed, it cannot be undone by setting a new
300+
// mint_authority
285301
let mint_authority = mint
286302
.mint_authority
287303
.ok_or(Into::<ProgramError>::into(TokenError::FixedSupply))?;
@@ -294,6 +310,8 @@ impl Processor {
294310
mint.mint_authority = new_authority;
295311
}
296312
AuthorityType::FreezeAccount => {
313+
// Once a mint's freeze authority is disabled, it cannot be re-enabled by
314+
// setting a new freeze_authority
297315
let freeze_authority = mint
298316
.freeze_authority
299317
.ok_or(Into::<ProgramError>::into(TokenError::MintCannotFreeze))?;
@@ -429,13 +447,16 @@ impl Processor {
429447
let mut source_data = source_account_info.data.borrow_mut();
430448
let source_account: &mut Account = state::unpack(&mut source_data)?;
431449

432-
if !source_account.is_native {
433-
return Err(TokenError::NonNativeNotSupported.into());
450+
if !source_account.is_native && source_account.amount != 0 {
451+
return Err(TokenError::NonNativeHasBalance.into());
434452
}
435453

454+
let authority = source_account
455+
.close_authority
456+
.unwrap_or(source_account.owner);
436457
Self::validate_owner(
437458
program_id,
438-
&source_account.owner,
459+
&authority,
439460
authority_info,
440461
account_info_iter.as_slice(),
441462
)?;
@@ -626,8 +647,8 @@ impl PrintProgramError for TokenError {
626647
TokenError::NativeNotSupported => {
627648
info!("Error: Instruction does not support native tokens")
628649
}
629-
TokenError::NonNativeNotSupported => {
630-
info!("Error: Instruction does not support non-native tokens")
650+
TokenError::NonNativeHasBalance => {
651+
info!("Error: Non-native account can only be closed if its balance is zero")
631652
}
632653
TokenError::InvalidInstruction => info!("Error: Invalid instruction"),
633654
TokenError::InvalidState => info!("Error: Invalid account state for operation"),
@@ -1444,7 +1465,7 @@ mod tests {
14441465
&[]
14451466
)
14461467
.unwrap(),
1447-
vec![&mut account_account, &mut owner_account,],
1468+
vec![&mut account_account, &mut owner_account],
14481469
)
14491470
);
14501471

@@ -1479,7 +1500,7 @@ mod tests {
14791500
&[]
14801501
)
14811502
.unwrap(),
1482-
vec![&mut account_account, &mut owner2_account,],
1503+
vec![&mut account_account, &mut owner2_account],
14831504
)
14841505
);
14851506

@@ -1512,7 +1533,24 @@ mod tests {
15121533
&[],
15131534
)
15141535
.unwrap(),
1515-
vec![&mut account_account, &mut owner_account,],
1536+
vec![&mut account_account, &mut owner_account],
1537+
)
1538+
);
1539+
1540+
// account owner may not be set to None
1541+
assert_eq!(
1542+
Err(TokenError::InvalidInstruction.into()),
1543+
do_process_instruction(
1544+
set_authority(
1545+
&program_id,
1546+
&account_key,
1547+
None,
1548+
AuthorityType::AccountHolder,
1549+
&owner_key,
1550+
&[],
1551+
)
1552+
.unwrap(),
1553+
vec![&mut account_account, &mut owner_account],
15161554
)
15171555
);
15181556

@@ -1531,6 +1569,36 @@ mod tests {
15311569
)
15321570
.unwrap();
15331571

1572+
// set close_authority
1573+
do_process_instruction(
1574+
set_authority(
1575+
&program_id,
1576+
&account_key,
1577+
Some(&owner2_key),
1578+
AuthorityType::CloseAccount,
1579+
&owner2_key,
1580+
&[],
1581+
)
1582+
.unwrap(),
1583+
vec![&mut account_account, &mut owner2_account],
1584+
)
1585+
.unwrap();
1586+
1587+
// close_authority may be set to None
1588+
do_process_instruction(
1589+
set_authority(
1590+
&program_id,
1591+
&account_key,
1592+
None,
1593+
AuthorityType::CloseAccount,
1594+
&owner2_key,
1595+
&[],
1596+
)
1597+
.unwrap(),
1598+
vec![&mut account_account, &mut owner2_account],
1599+
)
1600+
.unwrap();
1601+
15341602
// create new mint with owner
15351603
do_process_instruction(
15361604
initialize_mint(
@@ -2486,12 +2554,28 @@ mod tests {
24862554
)
24872555
);
24882556

2489-
// initialize non-native account
2557+
// initialize and mint to non-native account
24902558
do_process_instruction(
24912559
initialize_account(&program_id, &account_key, &mint_key, &owner_key).unwrap(),
24922560
vec![&mut account_account, &mut mint_account, &mut owner_account],
24932561
)
24942562
.unwrap();
2563+
do_process_instruction(
2564+
initialize_mint(
2565+
&program_id,
2566+
&mint_key,
2567+
Some(&account_key),
2568+
None,
2569+
None,
2570+
42,
2571+
2,
2572+
)
2573+
.unwrap(),
2574+
vec![&mut mint_account, &mut account_account, &mut owner_account],
2575+
)
2576+
.unwrap();
2577+
let account: &mut Account = state::unpack(&mut account_account.data).unwrap();
2578+
assert_eq!(account.amount, 42);
24952579

24962580
// initialize native account
24972581
do_process_instruction(
@@ -2509,9 +2593,9 @@ mod tests {
25092593
assert!(account.is_native);
25102594
assert_eq!(account.amount, 2);
25112595

2512-
// close non-native account
2596+
// close non-native account with balance
25132597
assert_eq!(
2514-
Err(TokenError::NonNativeNotSupported.into()),
2598+
Err(TokenError::NonNativeHasBalance.into()),
25152599
do_process_instruction(
25162600
close_account(&program_id, &account_key, &account3_key, &owner_key, &[]).unwrap(),
25172601
vec![
@@ -2523,6 +2607,94 @@ mod tests {
25232607
);
25242608
assert_eq!(account_account.lamports, 42);
25252609

2610+
// empty account
2611+
do_process_instruction(
2612+
burn(&program_id, &account_key, &owner_key, &[], 42).unwrap(),
2613+
vec![&mut account_account, &mut owner_account],
2614+
)
2615+
.unwrap();
2616+
2617+
// wrong owner
2618+
assert_eq!(
2619+
Err(TokenError::OwnerMismatch.into()),
2620+
do_process_instruction(
2621+
close_account(&program_id, &account_key, &account3_key, &owner2_key, &[]).unwrap(),
2622+
vec![
2623+
&mut account_account,
2624+
&mut account3_account,
2625+
&mut owner2_account,
2626+
],
2627+
)
2628+
);
2629+
2630+
// close account
2631+
do_process_instruction(
2632+
close_account(&program_id, &account_key, &account3_key, &owner_key, &[]).unwrap(),
2633+
vec![
2634+
&mut account_account,
2635+
&mut account3_account,
2636+
&mut owner_account,
2637+
],
2638+
)
2639+
.unwrap();
2640+
let account: &mut Account = state::unpack_unchecked(&mut account_account.data).unwrap();
2641+
assert_eq!(account_account.lamports, 0);
2642+
assert_eq!(account.amount, 0);
2643+
assert_eq!(account3_account.lamports, 44);
2644+
2645+
// fund and initialize new non-native account to test close authority
2646+
let account_key = pubkey_rand();
2647+
let mut account_account = SolanaAccount::new(42, size_of::<Account>(), &program_id);
2648+
let owner2_key = pubkey_rand();
2649+
let mut owner2_account = SolanaAccount::new(42, size_of::<Account>(), &program_id);
2650+
do_process_instruction(
2651+
initialize_account(&program_id, &account_key, &mint_key, &owner_key).unwrap(),
2652+
vec![&mut account_account, &mut mint_account, &mut owner_account],
2653+
)
2654+
.unwrap();
2655+
account_account.lamports = 2;
2656+
2657+
do_process_instruction(
2658+
set_authority(
2659+
&program_id,
2660+
&account_key,
2661+
Some(&owner2_key),
2662+
AuthorityType::CloseAccount,
2663+
&owner_key,
2664+
&[],
2665+
)
2666+
.unwrap(),
2667+
vec![&mut account_account, &mut owner_account],
2668+
)
2669+
.unwrap();
2670+
2671+
// account owner cannot authorize close if close_authority is set
2672+
assert_eq!(
2673+
Err(TokenError::OwnerMismatch.into()),
2674+
do_process_instruction(
2675+
close_account(&program_id, &account_key, &account3_key, &owner_key, &[]).unwrap(),
2676+
vec![
2677+
&mut account_account,
2678+
&mut account3_account,
2679+
&mut owner_account,
2680+
],
2681+
)
2682+
);
2683+
2684+
// close non-native account with close_authority
2685+
do_process_instruction(
2686+
close_account(&program_id, &account_key, &account3_key, &owner2_key, &[]).unwrap(),
2687+
vec![
2688+
&mut account_account,
2689+
&mut account3_account,
2690+
&mut owner2_account,
2691+
],
2692+
)
2693+
.unwrap();
2694+
assert_eq!(account_account.lamports, 0);
2695+
assert_eq!(account.amount, 0);
2696+
assert_eq!(account3_account.lamports, 46);
2697+
25262698
// close native account
25272699
do_process_instruction(
25282700
close_account(&program_id, &account2_key, &account3_key, &owner_key, &[]).unwrap(),
@@ -2535,8 +2707,9 @@ mod tests {
25352707
.unwrap();
25362708
let account: &mut Account = state::unpack_unchecked(&mut account2_account.data).unwrap();
25372709
assert!(account.is_native);
2710+
assert_eq!(account_account.lamports, 0);
25382711
assert_eq!(account.amount, 0);
2539-
assert_eq!(account3_account.lamports, 4);
2712+
assert_eq!(account3_account.lamports, 48);
25402713
}
25412714

25422715
#[test]

0 commit comments

Comments
 (0)