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

Commit 4fadd55

Browse files
authored
token: Reassign and reallocate accounts on close (#3415)
* token: Reassign and reallocate accounts on close * Revert "Refactor unpack and make test more robust (#3417)" This reverts commit c618de3. * Revert "check that unpack is tolerant of small sizes (#3416)" This reverts commit 22faa05. * Also revert d7f352b
1 parent 93ec6cf commit 4fadd55

File tree

6 files changed

+454
-52
lines changed

6 files changed

+454
-52
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
#![cfg(feature = "test-bpf")]
2+
3+
mod program_test;
4+
use {
5+
program_test::TestContext,
6+
solana_program_test::tokio,
7+
solana_sdk::{
8+
instruction::InstructionError, program_pack::Pack, pubkey::Pubkey, signature::Signer,
9+
signer::keypair::Keypair, system_instruction, transaction::TransactionError,
10+
transport::TransportError,
11+
},
12+
spl_token_2022::{instruction, state::Account},
13+
spl_token_client::token::{ExtensionInitializationParams, TokenError as TokenClientError},
14+
};
15+
16+
#[tokio::test]
17+
async fn success_init_after_close_account() {
18+
let mut context = TestContext::new().await;
19+
let payer = Keypair::from_bytes(&context.context.lock().await.payer.to_bytes()).unwrap();
20+
context.init_token_with_mint(vec![]).await.unwrap();
21+
let token = context.token_context.take().unwrap().token;
22+
let token_program_id = spl_token_2022::id();
23+
let owner = Keypair::new();
24+
let token_account_keypair = Keypair::new();
25+
let token_account = token
26+
.create_auxiliary_token_account(&token_account_keypair, &owner.pubkey())
27+
.await
28+
.unwrap();
29+
30+
let destination = Pubkey::new_unique();
31+
token
32+
.process_ixs(
33+
&[
34+
instruction::close_account(
35+
&token_program_id,
36+
&token_account,
37+
&destination,
38+
&owner.pubkey(),
39+
&[],
40+
)
41+
.unwrap(),
42+
system_instruction::create_account(
43+
&payer.pubkey(),
44+
&token_account,
45+
1_000_000_000,
46+
Account::LEN as u64,
47+
&token_program_id,
48+
),
49+
instruction::initialize_account(
50+
&token_program_id,
51+
&token_account,
52+
token.get_address(),
53+
&owner.pubkey(),
54+
)
55+
.unwrap(),
56+
],
57+
&[&owner, &payer, &token_account_keypair],
58+
)
59+
.await
60+
.unwrap();
61+
let destination = token.get_account(&destination).await.unwrap();
62+
assert!(destination.lamports > 0);
63+
}
64+
65+
#[tokio::test]
66+
async fn fail_init_after_close_account() {
67+
let mut context = TestContext::new().await;
68+
let payer = Keypair::from_bytes(&context.context.lock().await.payer.to_bytes()).unwrap();
69+
context.init_token_with_mint(vec![]).await.unwrap();
70+
let token = context.token_context.take().unwrap().token;
71+
let token_program_id = spl_token_2022::id();
72+
let owner = Keypair::new();
73+
let token_account = token
74+
.create_auxiliary_token_account(&Keypair::new(), &owner.pubkey())
75+
.await
76+
.unwrap();
77+
78+
let destination = Pubkey::new_unique();
79+
let error = token
80+
.process_ixs(
81+
&[
82+
instruction::close_account(
83+
&token_program_id,
84+
&token_account,
85+
&destination,
86+
&owner.pubkey(),
87+
&[],
88+
)
89+
.unwrap(),
90+
system_instruction::transfer(&payer.pubkey(), &token_account, 1_000_000_000),
91+
instruction::initialize_account(
92+
&token_program_id,
93+
&token_account,
94+
token.get_address(),
95+
&owner.pubkey(),
96+
)
97+
.unwrap(),
98+
],
99+
&[&owner, &payer],
100+
)
101+
.await
102+
.unwrap_err();
103+
assert_eq!(
104+
error,
105+
TokenClientError::Client(Box::new(TransportError::TransactionError(
106+
TransactionError::InstructionError(2, InstructionError::InvalidAccountData,)
107+
)))
108+
);
109+
let error = token.get_account(&destination).await.unwrap_err();
110+
assert_eq!(error, TokenClientError::AccountNotFound);
111+
}
112+
113+
#[tokio::test]
114+
async fn fail_init_after_close_mint() {
115+
let close_authority = Keypair::new();
116+
let mut context = TestContext::new().await;
117+
let payer = Keypair::from_bytes(&context.context.lock().await.payer.to_bytes()).unwrap();
118+
context
119+
.init_token_with_mint(vec![ExtensionInitializationParams::MintCloseAuthority {
120+
close_authority: Some(close_authority.pubkey()),
121+
}])
122+
.await
123+
.unwrap();
124+
let token = context.token_context.take().unwrap().token;
125+
let token_program_id = spl_token_2022::id();
126+
127+
let destination = Pubkey::new_unique();
128+
let error = token
129+
.process_ixs(
130+
&[
131+
instruction::close_account(
132+
&token_program_id,
133+
token.get_address(),
134+
&destination,
135+
&close_authority.pubkey(),
136+
&[],
137+
)
138+
.unwrap(),
139+
system_instruction::transfer(&payer.pubkey(), token.get_address(), 1_000_000_000),
140+
instruction::initialize_mint_close_authority(
141+
&token_program_id,
142+
token.get_address(),
143+
None,
144+
)
145+
.unwrap(),
146+
instruction::initialize_mint(
147+
&token_program_id,
148+
token.get_address(),
149+
&close_authority.pubkey(),
150+
None,
151+
0,
152+
)
153+
.unwrap(),
154+
],
155+
&[&close_authority, &payer],
156+
)
157+
.await
158+
.unwrap_err();
159+
assert_eq!(
160+
error,
161+
TokenClientError::Client(Box::new(TransportError::TransactionError(
162+
TransactionError::InstructionError(2, InstructionError::InvalidAccountData,)
163+
)))
164+
);
165+
let error = token.get_account(&destination).await.unwrap_err();
166+
assert_eq!(error, TokenClientError::AccountNotFound);
167+
}

token/program-2022/src/instruction.rs

+1-11
Original file line numberDiff line numberDiff line change
@@ -1787,7 +1787,7 @@ pub(crate) fn encode_instruction<T: Into<u8>, D: Pod>(
17871787

17881788
#[cfg(test)]
17891789
mod test {
1790-
use {super::*, proptest::prelude::*};
1790+
use super::*;
17911791

17921792
#[test]
17931793
fn test_instruction_packing() {
@@ -2284,14 +2284,4 @@ mod test {
22842284
ui_amount,
22852285
));
22862286
}
2287-
2288-
proptest! {
2289-
#![proptest_config(ProptestConfig::with_cases(1024))]
2290-
#[test]
2291-
fn test_instruction_unpack_panic(
2292-
data in prop::collection::vec(any::<u8>(), 0..255)
2293-
) {
2294-
let _no_panic = TokenInstruction::unpack(&data);
2295-
}
2296-
}
22972287
}

token/program-2022/src/processor.rs

+23-5
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,10 @@ use {
2727
msg,
2828
program::{invoke, invoke_signed, set_return_data},
2929
program_error::ProgramError,
30-
program_memory::sol_memset,
3130
program_option::COption,
3231
program_pack::Pack,
3332
pubkey::Pubkey,
34-
system_instruction,
33+
system_instruction, system_program,
3534
sysvar::{rent::Rent, Sysvar},
3635
},
3736
std::convert::{TryFrom, TryInto},
@@ -875,7 +874,7 @@ impl Processor {
875874
return Err(ProgramError::InvalidAccountData);
876875
}
877876

878-
let mut source_account_data = source_account_info.data.borrow_mut();
877+
let source_account_data = source_account_info.data.borrow();
879878
if let Ok(source_account) = StateWithExtensions::<Account>::unpack(&source_account_data) {
880879
if !source_account.base.is_native() && source_account.base.amount != 0 {
881880
return Err(TokenError::NonNativeHasBalance.into());
@@ -935,8 +934,8 @@ impl Processor {
935934
.ok_or(TokenError::Overflow)?;
936935

937936
**source_account_info.lamports.borrow_mut() = 0;
938-
let data_len = source_account_data.len();
939-
sol_memset(*source_account_data, 0, data_len);
937+
drop(source_account_data);
938+
delete_account(source_account_info)?;
940939

941940
Ok(())
942941
}
@@ -1388,6 +1387,25 @@ impl Processor {
13881387
}
13891388
}
13901389

1390+
/// Helper function to mostly delete an account in a test environment. We could
1391+
/// potentially muck around the bytes assuming that a vec is passed in, but that
1392+
/// would be more trouble than it's worth.
1393+
#[cfg(not(target_arch = "bpf"))]
1394+
fn delete_account(account_info: &AccountInfo) -> Result<(), ProgramError> {
1395+
account_info.assign(&system_program::id());
1396+
let mut account_data = account_info.data.borrow_mut();
1397+
let data_len = account_data.len();
1398+
solana_program::program_memory::sol_memset(*account_data, 0, data_len);
1399+
Ok(())
1400+
}
1401+
1402+
/// Helper function to totally delete an account on-chain
1403+
#[cfg(target_arch = "bpf")]
1404+
fn delete_account(account_info: &AccountInfo) -> Result<(), ProgramError> {
1405+
account_info.assign(&system_program::id());
1406+
account_info.realloc(0, false)
1407+
}
1408+
13911409
#[cfg(test)]
13921410
mod tests {
13931411
use {

token/program/src/instruction.rs

+39-33
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ use std::mem::size_of;
1515
pub const MIN_SIGNERS: usize = 1;
1616
/// Maximum number of multisignature signers (max N)
1717
pub const MAX_SIGNERS: usize = 11;
18-
/// Serialized length of a u64, for unpacking
19-
const U64_BYTES: usize = 8;
2018

2119
/// Instructions supported by the token program.
2220
#[repr(C)]
@@ -521,19 +519,47 @@ impl<'a> TokenInstruction<'a> {
521519
10 => Self::FreezeAccount,
522520
11 => Self::ThawAccount,
523521
12 => {
524-
let (amount, decimals, _rest) = Self::unpack_amount_decimals(rest)?;
522+
let (amount, rest) = rest.split_at(8);
523+
let amount = amount
524+
.try_into()
525+
.ok()
526+
.map(u64::from_le_bytes)
527+
.ok_or(InvalidInstruction)?;
528+
let (&decimals, _rest) = rest.split_first().ok_or(InvalidInstruction)?;
529+
525530
Self::TransferChecked { amount, decimals }
526531
}
527532
13 => {
528-
let (amount, decimals, _rest) = Self::unpack_amount_decimals(rest)?;
533+
let (amount, rest) = rest.split_at(8);
534+
let amount = amount
535+
.try_into()
536+
.ok()
537+
.map(u64::from_le_bytes)
538+
.ok_or(InvalidInstruction)?;
539+
let (&decimals, _rest) = rest.split_first().ok_or(InvalidInstruction)?;
540+
529541
Self::ApproveChecked { amount, decimals }
530542
}
531543
14 => {
532-
let (amount, decimals, _rest) = Self::unpack_amount_decimals(rest)?;
544+
let (amount, rest) = rest.split_at(8);
545+
let amount = amount
546+
.try_into()
547+
.ok()
548+
.map(u64::from_le_bytes)
549+
.ok_or(InvalidInstruction)?;
550+
let (&decimals, _rest) = rest.split_first().ok_or(InvalidInstruction)?;
551+
533552
Self::MintToChecked { amount, decimals }
534553
}
535554
15 => {
536-
let (amount, decimals, _rest) = Self::unpack_amount_decimals(rest)?;
555+
let (amount, rest) = rest.split_at(8);
556+
let amount = amount
557+
.try_into()
558+
.ok()
559+
.map(u64::from_le_bytes)
560+
.ok_or(InvalidInstruction)?;
561+
let (&decimals, _rest) = rest.split_first().ok_or(InvalidInstruction)?;
562+
537563
Self::BurnChecked { amount, decimals }
538564
}
539565
16 => {
@@ -562,7 +588,12 @@ impl<'a> TokenInstruction<'a> {
562588
21 => Self::GetAccountDataSize,
563589
22 => Self::InitializeImmutableOwner,
564590
23 => {
565-
let (amount, _rest) = Self::unpack_u64(rest)?;
591+
let (amount, _rest) = rest.split_at(8);
592+
let amount = amount
593+
.try_into()
594+
.ok()
595+
.map(u64::from_le_bytes)
596+
.ok_or(InvalidInstruction)?;
566597
Self::AmountToUiAmount { amount }
567598
}
568599
24 => {
@@ -714,21 +745,6 @@ impl<'a> TokenInstruction<'a> {
714745
COption::None => buf.push(0),
715746
}
716747
}
717-
718-
fn unpack_u64(input: &[u8]) -> Result<(u64, &[u8]), ProgramError> {
719-
let value = input
720-
.get(..U64_BYTES)
721-
.and_then(|slice| slice.try_into().ok())
722-
.map(u64::from_le_bytes)
723-
.ok_or(TokenError::InvalidInstruction)?;
724-
Ok((value, &input[U64_BYTES..]))
725-
}
726-
727-
fn unpack_amount_decimals(input: &[u8]) -> Result<(u64, u8, &[u8]), ProgramError> {
728-
let (amount, rest) = Self::unpack_u64(input)?;
729-
let (&decimals, rest) = rest.split_first().ok_or(TokenError::InvalidInstruction)?;
730-
Ok((amount, decimals, rest))
731-
}
732748
}
733749

734750
/// Specifies the authority type for SetAuthority instructions
@@ -1431,7 +1447,7 @@ pub fn is_valid_signer_index(index: usize) -> bool {
14311447

14321448
#[cfg(test)]
14331449
mod test {
1434-
use {super::*, proptest::prelude::*};
1450+
use super::*;
14351451

14361452
#[test]
14371453
fn test_instruction_packing() {
@@ -1673,14 +1689,4 @@ mod test {
16731689
let unpacked = TokenInstruction::unpack(&expect).unwrap();
16741690
assert_eq!(unpacked, check);
16751691
}
1676-
1677-
proptest! {
1678-
#![proptest_config(ProptestConfig::with_cases(1024))]
1679-
#[test]
1680-
fn test_instruction_unpack_panic(
1681-
data in prop::collection::vec(any::<u8>(), 0..255)
1682-
) {
1683-
let _no_panic = TokenInstruction::unpack(&data);
1684-
}
1685-
}
16861692
}

0 commit comments

Comments
 (0)