Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

token: Add close_authority and re-enable CloseAccount for non-native Accounts #314

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions token/program2/inc/token2.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ enum Token_AuthorityType
* Holder of a given token account
*/
Token_AuthorityType_AccountHolder,
/**
* Authority to close a token account
*/
Token_AuthorityType_CloseAccount,
};
#ifndef __cplusplus
typedef uint8_t Token_AuthorityType;
Expand Down Expand Up @@ -439,6 +443,10 @@ typedef struct Token_Account {
* The amount delegated
*/
uint64_t delegated_amount;
/**
* Optional authority to close the account.
*/
Token_COption_Pubkey close_authority;
} Token_Account;

/**
Expand Down
6 changes: 3 additions & 3 deletions token/program2/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ pub enum TokenError {
/// Instruction does not support native tokens
#[error("Instruction does not support native tokens")]
NativeNotSupported,
/// Instruction does not support non-native tokens
#[error("Instruction does not support non-native tokens")]
NonNativeNotSupported,
/// Non-native account can only be closed if its balance is zero
#[error("Non-native account can only be closed if its balance is zero")]
NonNativeHasBalance,
/// Invalid instruction
#[error("Invalid instruction")]
InvalidInstruction,
Expand Down
4 changes: 4 additions & 0 deletions token/program2/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,8 @@ pub enum AuthorityType {
FreezeAccount,
/// Holder of a given token account
AccountHolder,
/// Authority to close a token account
CloseAccount,
}

impl AuthorityType {
Expand All @@ -459,6 +461,7 @@ impl AuthorityType {
AuthorityType::MintTokens => 0,
AuthorityType::FreezeAccount => 1,
AuthorityType::AccountHolder => 2,
AuthorityType::CloseAccount => 3,
}
}

Expand All @@ -467,6 +470,7 @@ impl AuthorityType {
0 => Ok(AuthorityType::MintTokens),
1 => Ok(AuthorityType::FreezeAccount),
2 => Ok(AuthorityType::AccountHolder),
3 => Ok(AuthorityType::CloseAccount),
_ => Err(TokenError::InvalidInstruction.into()),
}
}
Expand Down
223 changes: 198 additions & 25 deletions token/program2/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,34 +254,50 @@ impl Processor {
let authority_info = next_account_info(account_info_iter)?;

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

if account.is_frozen() {
return Err(TokenError::AccountFrozen.into());
}

Self::validate_owner(
program_id,
&account.owner,
authority_info,
account_info_iter.as_slice(),
)?;
match authority_type {
AuthorityType::AccountHolder => {
Self::validate_owner(
program_id,
&account.owner,
authority_info,
account_info_iter.as_slice(),
)?;

if let COption::Some(authority) = new_authority {
account.owner = authority;
} else {
return Err(TokenError::InvalidInstruction.into());
if let COption::Some(authority) = new_authority {
account.owner = authority;
} else {
return Err(TokenError::InvalidInstruction.into());
}
}
AuthorityType::CloseAccount => {
let authority = account.close_authority.unwrap_or(account.owner);
Self::validate_owner(
program_id,
&authority,
authority_info,
account_info_iter.as_slice(),
)?;
account.close_authority = new_authority;
}
_ => {
return Err(TokenError::AuthorityTypeNotSupported.into());
}
}
} else if account_info.data_len() == size_of::<Mint>() {
let mut account_data = account_info.data.borrow_mut();
let mut mint: &mut Mint = state::unpack(&mut account_data)?;

match authority_type {
AuthorityType::MintTokens => {
// Once a mint's supply is fixed, it cannot be undone by setting a new
// mint_authority
let mint_authority = mint
.mint_authority
.ok_or(Into::<ProgramError>::into(TokenError::FixedSupply))?;
Expand All @@ -294,6 +310,8 @@ impl Processor {
mint.mint_authority = new_authority;
}
AuthorityType::FreezeAccount => {
// Once a mint's freeze authority is disabled, it cannot be re-enabled by
// setting a new freeze_authority
let freeze_authority = mint
.freeze_authority
.ok_or(Into::<ProgramError>::into(TokenError::MintCannotFreeze))?;
Expand Down Expand Up @@ -429,13 +447,16 @@ impl Processor {
let mut source_data = source_account_info.data.borrow_mut();
let source_account: &mut Account = state::unpack(&mut source_data)?;

if !source_account.is_native {
return Err(TokenError::NonNativeNotSupported.into());
if !source_account.is_native && source_account.amount != 0 {
return Err(TokenError::NonNativeHasBalance.into());
}

let authority = source_account
.close_authority
.unwrap_or(source_account.owner);
Self::validate_owner(
program_id,
&source_account.owner,
&authority,
authority_info,
account_info_iter.as_slice(),
)?;
Expand Down Expand Up @@ -626,8 +647,8 @@ impl PrintProgramError for TokenError {
TokenError::NativeNotSupported => {
info!("Error: Instruction does not support native tokens")
}
TokenError::NonNativeNotSupported => {
info!("Error: Instruction does not support non-native tokens")
TokenError::NonNativeHasBalance => {
info!("Error: Non-native account can only be closed if its balance is zero")
}
TokenError::InvalidInstruction => info!("Error: Invalid instruction"),
TokenError::InvalidState => info!("Error: Invalid account state for operation"),
Expand Down Expand Up @@ -1444,7 +1465,7 @@ mod tests {
&[]
)
.unwrap(),
vec![&mut account_account, &mut owner_account,],
vec![&mut account_account, &mut owner_account],
)
);

Expand Down Expand Up @@ -1479,7 +1500,7 @@ mod tests {
&[]
)
.unwrap(),
vec![&mut account_account, &mut owner2_account,],
vec![&mut account_account, &mut owner2_account],
)
);

Expand Down Expand Up @@ -1512,7 +1533,24 @@ mod tests {
&[],
)
.unwrap(),
vec![&mut account_account, &mut owner_account,],
vec![&mut account_account, &mut owner_account],
)
);

// account owner may not be set to None
assert_eq!(
Err(TokenError::InvalidInstruction.into()),
do_process_instruction(
set_authority(
&program_id,
&account_key,
None,
AuthorityType::AccountHolder,
&owner_key,
&[],
)
.unwrap(),
vec![&mut account_account, &mut owner_account],
)
);

Expand All @@ -1531,6 +1569,36 @@ mod tests {
)
.unwrap();

// set close_authority
do_process_instruction(
set_authority(
&program_id,
&account_key,
Some(&owner2_key),
AuthorityType::CloseAccount,
&owner2_key,
&[],
)
.unwrap(),
vec![&mut account_account, &mut owner2_account],
)
.unwrap();

// close_authority may be set to None
do_process_instruction(
set_authority(
&program_id,
&account_key,
None,
AuthorityType::CloseAccount,
&owner2_key,
&[],
)
.unwrap(),
vec![&mut account_account, &mut owner2_account],
)
.unwrap();

// create new mint with owner
do_process_instruction(
initialize_mint(
Expand Down Expand Up @@ -2486,12 +2554,28 @@ mod tests {
)
);

// initialize non-native account
// initialize and mint to non-native account
do_process_instruction(
initialize_account(&program_id, &account_key, &mint_key, &owner_key).unwrap(),
vec![&mut account_account, &mut mint_account, &mut owner_account],
)
.unwrap();
do_process_instruction(
initialize_mint(
&program_id,
&mint_key,
Some(&account_key),
None,
None,
42,
2,
)
.unwrap(),
vec![&mut mint_account, &mut account_account, &mut owner_account],
)
.unwrap();
let account: &mut Account = state::unpack(&mut account_account.data).unwrap();
assert_eq!(account.amount, 42);

// initialize native account
do_process_instruction(
Expand All @@ -2509,9 +2593,9 @@ mod tests {
assert!(account.is_native);
assert_eq!(account.amount, 2);

// close non-native account
// close non-native account with balance
assert_eq!(
Err(TokenError::NonNativeNotSupported.into()),
Err(TokenError::NonNativeHasBalance.into()),
do_process_instruction(
close_account(&program_id, &account_key, &account3_key, &owner_key, &[]).unwrap(),
vec![
Expand All @@ -2523,6 +2607,94 @@ mod tests {
);
assert_eq!(account_account.lamports, 42);

// empty account
do_process_instruction(
burn(&program_id, &account_key, &owner_key, &[], 42).unwrap(),
vec![&mut account_account, &mut owner_account],
)
.unwrap();

// wrong owner
assert_eq!(
Err(TokenError::OwnerMismatch.into()),
do_process_instruction(
close_account(&program_id, &account_key, &account3_key, &owner2_key, &[]).unwrap(),
vec![
&mut account_account,
&mut account3_account,
&mut owner2_account,
],
)
);

// close account
do_process_instruction(
close_account(&program_id, &account_key, &account3_key, &owner_key, &[]).unwrap(),
vec![
&mut account_account,
&mut account3_account,
&mut owner_account,
],
)
.unwrap();
let account: &mut Account = state::unpack_unchecked(&mut account_account.data).unwrap();
assert_eq!(account_account.lamports, 0);
assert_eq!(account.amount, 0);
assert_eq!(account3_account.lamports, 44);

// fund and initialize new non-native account to test close authority
let account_key = pubkey_rand();
let mut account_account = SolanaAccount::new(42, size_of::<Account>(), &program_id);
let owner2_key = pubkey_rand();
let mut owner2_account = SolanaAccount::new(42, size_of::<Account>(), &program_id);
do_process_instruction(
initialize_account(&program_id, &account_key, &mint_key, &owner_key).unwrap(),
vec![&mut account_account, &mut mint_account, &mut owner_account],
)
.unwrap();
account_account.lamports = 2;

do_process_instruction(
set_authority(
&program_id,
&account_key,
Some(&owner2_key),
AuthorityType::CloseAccount,
&owner_key,
&[],
)
.unwrap(),
vec![&mut account_account, &mut owner_account],
)
.unwrap();

// account owner cannot authorize close if close_authority is set
assert_eq!(
Err(TokenError::OwnerMismatch.into()),
do_process_instruction(
close_account(&program_id, &account_key, &account3_key, &owner_key, &[]).unwrap(),
vec![
&mut account_account,
&mut account3_account,
&mut owner_account,
],
)
);

// close non-native account with close_authority
do_process_instruction(
close_account(&program_id, &account_key, &account3_key, &owner2_key, &[]).unwrap(),
vec![
&mut account_account,
&mut account3_account,
&mut owner2_account,
],
)
.unwrap();
assert_eq!(account_account.lamports, 0);
assert_eq!(account.amount, 0);
assert_eq!(account3_account.lamports, 46);

// close native account
do_process_instruction(
close_account(&program_id, &account2_key, &account3_key, &owner_key, &[]).unwrap(),
Expand All @@ -2535,8 +2707,9 @@ mod tests {
.unwrap();
let account: &mut Account = state::unpack_unchecked(&mut account2_account.data).unwrap();
assert!(account.is_native);
assert_eq!(account_account.lamports, 0);
assert_eq!(account.amount, 0);
assert_eq!(account3_account.lamports, 4);
assert_eq!(account3_account.lamports, 48);
}

#[test]
Expand Down
Loading