Skip to content

Commit 3a887bf

Browse files
committed
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 3cd0c9a82209eaf445662c00122188155594ad4a. * Revert "check that unpack is tolerant of small sizes (#3416)" This reverts commit c202d51918c994230fc71f9b8829a7f7300d5d12. * Also revert 842d4bfad96329968121d0460403268fa73ffe4a
1 parent 7b31806 commit 3a887bf

File tree

3 files changed

+263
-36
lines changed

3 files changed

+263
-36
lines changed

program/src/instruction.rs

Lines changed: 39 additions & 33 deletions
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
}

program/src/processor.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ use solana_program::{
1313
msg,
1414
program::set_return_data,
1515
program_error::ProgramError,
16-
program_memory::{sol_memcmp, sol_memset},
16+
program_memory::sol_memcmp,
1717
program_option::COption,
1818
program_pack::{IsInitialized, Pack},
1919
pubkey::{Pubkey, PUBKEY_BYTES},
20+
system_program,
2021
sysvar::{rent::Rent, Sysvar},
2122
};
2223

@@ -696,8 +697,7 @@ impl Processor {
696697
.ok_or(TokenError::Overflow)?;
697698

698699
**source_account_info.lamports.borrow_mut() = 0;
699-
700-
sol_memset(*source_account_info.data.borrow_mut(), 0, Account::LEN);
700+
delete_account(source_account_info)?;
701701

702702
Ok(())
703703
}
@@ -1007,6 +1007,25 @@ impl Processor {
10071007
}
10081008
}
10091009

1010+
/// Helper function to mostly delete an account in a test environment. We could
1011+
/// potentially muck around the bytes assuming that a vec is passed in, but that
1012+
/// would be more trouble than it's worth.
1013+
#[cfg(not(target_arch = "bpf"))]
1014+
fn delete_account(account_info: &AccountInfo) -> Result<(), ProgramError> {
1015+
account_info.assign(&system_program::id());
1016+
let mut account_data = account_info.data.borrow_mut();
1017+
let data_len = account_data.len();
1018+
solana_program::program_memory::sol_memset(*account_data, 0, data_len);
1019+
Ok(())
1020+
}
1021+
1022+
/// Helper function to totally delete an account on-chain
1023+
#[cfg(target_arch = "bpf")]
1024+
fn delete_account(account_info: &AccountInfo) -> Result<(), ProgramError> {
1025+
account_info.assign(&system_program::id());
1026+
account_info.realloc(0, false)
1027+
}
1028+
10101029
#[cfg(test)]
10111030
mod tests {
10121031
use super::*;

program/tests/close_account.rs

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
#![cfg(feature = "test-bpf")]
2+
3+
use {
4+
solana_program_test::{processor, tokio, ProgramTest, ProgramTestContext},
5+
solana_sdk::{
6+
instruction::InstructionError,
7+
program_pack::Pack,
8+
pubkey::Pubkey,
9+
signature::Signer,
10+
signer::keypair::Keypair,
11+
system_instruction,
12+
transaction::{Transaction, TransactionError},
13+
},
14+
spl_token::{
15+
instruction,
16+
processor::Processor,
17+
state::{Account, Mint},
18+
},
19+
};
20+
21+
async fn setup_mint_and_account(
22+
context: &mut ProgramTestContext,
23+
mint: &Keypair,
24+
token_account: &Keypair,
25+
owner: &Pubkey,
26+
token_program_id: &Pubkey,
27+
) {
28+
let rent = context.banks_client.get_rent().await.unwrap();
29+
let mint_authority_pubkey = Pubkey::new_unique();
30+
31+
let space = Mint::LEN;
32+
let tx = Transaction::new_signed_with_payer(
33+
&[
34+
system_instruction::create_account(
35+
&context.payer.pubkey(),
36+
&mint.pubkey(),
37+
rent.minimum_balance(space),
38+
space as u64,
39+
&token_program_id,
40+
),
41+
instruction::initialize_mint(
42+
&token_program_id,
43+
&mint.pubkey(),
44+
&mint_authority_pubkey,
45+
None,
46+
9,
47+
)
48+
.unwrap(),
49+
],
50+
Some(&context.payer.pubkey()),
51+
&[&context.payer, mint],
52+
context.last_blockhash,
53+
);
54+
context.banks_client.process_transaction(tx).await.unwrap();
55+
let space = Account::LEN;
56+
let tx = Transaction::new_signed_with_payer(
57+
&[
58+
system_instruction::create_account(
59+
&context.payer.pubkey(),
60+
&token_account.pubkey(),
61+
rent.minimum_balance(space),
62+
space as u64,
63+
&token_program_id,
64+
),
65+
instruction::initialize_account(
66+
&token_program_id,
67+
&token_account.pubkey(),
68+
&mint.pubkey(),
69+
&owner,
70+
)
71+
.unwrap(),
72+
],
73+
Some(&context.payer.pubkey()),
74+
&[&context.payer, &token_account],
75+
context.last_blockhash,
76+
);
77+
context.banks_client.process_transaction(tx).await.unwrap();
78+
}
79+
80+
#[tokio::test]
81+
async fn success_init_after_close_account() {
82+
let program_test =
83+
ProgramTest::new("spl_token", spl_token::id(), processor!(Processor::process));
84+
let mut context = program_test.start_with_context().await;
85+
let mint = Keypair::new();
86+
let token_account = Keypair::new();
87+
let owner = Keypair::new();
88+
let token_program_id = spl_token::id();
89+
setup_mint_and_account(
90+
&mut context,
91+
&mint,
92+
&token_account,
93+
&owner.pubkey(),
94+
&token_program_id,
95+
)
96+
.await;
97+
98+
let destination = Pubkey::new_unique();
99+
let tx = Transaction::new_signed_with_payer(
100+
&[
101+
instruction::close_account(
102+
&token_program_id,
103+
&token_account.pubkey(),
104+
&destination,
105+
&owner.pubkey(),
106+
&[],
107+
)
108+
.unwrap(),
109+
system_instruction::create_account(
110+
&context.payer.pubkey(),
111+
&token_account.pubkey(),
112+
1_000_000_000,
113+
Account::LEN as u64,
114+
&token_program_id,
115+
),
116+
instruction::initialize_account(
117+
&token_program_id,
118+
&token_account.pubkey(),
119+
&mint.pubkey(),
120+
&owner.pubkey(),
121+
)
122+
.unwrap(),
123+
],
124+
Some(&context.payer.pubkey()),
125+
&[&context.payer, &owner, &token_account],
126+
context.last_blockhash,
127+
);
128+
context.banks_client.process_transaction(tx).await.unwrap();
129+
let destination = context
130+
.banks_client
131+
.get_account(destination)
132+
.await
133+
.unwrap()
134+
.unwrap();
135+
assert!(destination.lamports > 0);
136+
}
137+
138+
#[tokio::test]
139+
async fn fail_init_after_close_account() {
140+
let program_test =
141+
ProgramTest::new("spl_token", spl_token::id(), processor!(Processor::process));
142+
let mut context = program_test.start_with_context().await;
143+
let mint = Keypair::new();
144+
let token_account = Keypair::new();
145+
let owner = Keypair::new();
146+
let token_program_id = spl_token::id();
147+
setup_mint_and_account(
148+
&mut context,
149+
&mint,
150+
&token_account,
151+
&owner.pubkey(),
152+
&token_program_id,
153+
)
154+
.await;
155+
156+
let destination = Pubkey::new_unique();
157+
let tx = Transaction::new_signed_with_payer(
158+
&[
159+
instruction::close_account(
160+
&token_program_id,
161+
&token_account.pubkey(),
162+
&destination,
163+
&owner.pubkey(),
164+
&[],
165+
)
166+
.unwrap(),
167+
system_instruction::transfer(
168+
&context.payer.pubkey(),
169+
&token_account.pubkey(),
170+
1_000_000_000,
171+
),
172+
instruction::initialize_account(
173+
&token_program_id,
174+
&token_account.pubkey(),
175+
&mint.pubkey(),
176+
&owner.pubkey(),
177+
)
178+
.unwrap(),
179+
],
180+
Some(&context.payer.pubkey()),
181+
&[&context.payer, &owner],
182+
context.last_blockhash,
183+
);
184+
#[allow(clippy::useless_conversion)]
185+
let error: TransactionError = context
186+
.banks_client
187+
.process_transaction(tx)
188+
.await
189+
.unwrap_err()
190+
.unwrap()
191+
.into();
192+
assert_eq!(
193+
error,
194+
TransactionError::InstructionError(2, InstructionError::InvalidAccountData)
195+
);
196+
assert!(context
197+
.banks_client
198+
.get_account(destination)
199+
.await
200+
.unwrap()
201+
.is_none());
202+
}

0 commit comments

Comments
 (0)