Skip to content

Commit 15b2b2f

Browse files
authored
fix: validate account locks when sanitizing transaction (#293)
* fix: validate account locks when sanitizing transaction * tests: add account lock tests
1 parent 1df8757 commit 15b2b2f

2 files changed

Lines changed: 107 additions & 0 deletions

File tree

crates/litesvm/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,7 @@ impl LiteSVM {
919919
let tx = self.sanitize_transaction_no_verify_inner(tx)?;
920920

921921
tx.verify()?;
922+
SanitizedTransaction::validate_account_locks(tx.message(), 64)?;
922923

923924
Ok(tx)
924925
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
use {
2+
litesvm::LiteSVM,
3+
solana_address::Address,
4+
solana_hash::Hash,
5+
solana_instruction::Instruction,
6+
solana_keypair::{Keypair, Signature},
7+
solana_message::{Message, MessageHeader},
8+
solana_sdk_ids::system_program,
9+
solana_signer::Signer,
10+
solana_transaction::{CompiledInstruction, Transaction},
11+
solana_transaction_error::TransactionError,
12+
};
13+
14+
#[test]
15+
fn test_account_loaded_twice() {
16+
let mut svm = LiteSVM::new();
17+
let payer_kp = Keypair::new();
18+
let payer_pk = payer_kp.pubkey();
19+
svm.airdrop(&payer_pk, 1_000_000_000).unwrap();
20+
21+
// Create an account that we'll reference twice
22+
let duplicate_account = Address::new_unique();
23+
24+
let data = bincode::serialize(
25+
&solana_system_interface::instruction::SystemInstruction::Transfer { lamports: 500_000 },
26+
)
27+
.unwrap();
28+
// Construct a transaction that references the same account as twice
29+
let mut tx = Transaction {
30+
signatures: vec![Signature::default()],
31+
message: Message {
32+
header: MessageHeader {
33+
num_required_signatures: 1,
34+
num_readonly_signed_accounts: 0,
35+
num_readonly_unsigned_accounts: 1,
36+
},
37+
account_keys: vec![
38+
payer_pk,
39+
duplicate_account,
40+
duplicate_account,
41+
system_program::id(),
42+
],
43+
recent_blockhash: Hash::default(),
44+
instructions: vec![
45+
CompiledInstruction {
46+
program_id_index: 3,
47+
accounts: [0, 1].to_vec(),
48+
data: data.clone(),
49+
},
50+
CompiledInstruction {
51+
program_id_index: 3,
52+
accounts: [0, 2].to_vec(),
53+
data: data.clone(),
54+
},
55+
],
56+
},
57+
};
58+
59+
tx.sign(&[&payer_kp], svm.latest_blockhash());
60+
61+
let result = svm.send_transaction(tx);
62+
63+
assert_eq!(
64+
result.unwrap_err().err,
65+
TransactionError::AccountLoadedTwice,
66+
"Expected AccountLoadedTwice error when same account is both writable and read-only"
67+
);
68+
}
69+
70+
#[test]
71+
fn test_too_many_account_locks() {
72+
use solana_system_interface::instruction::transfer;
73+
74+
let mut svm = LiteSVM::new();
75+
let payer_kp = Keypair::new();
76+
let payer_pk = payer_kp.pubkey();
77+
svm.airdrop(&payer_pk, 1_000_000_000_000).unwrap();
78+
79+
// Create 64 transfer instructions to unique accounts.
80+
// Total unique accounts = payer (1) + 64 recipients = 65 > 64 limit
81+
// The system program is not counted as an account lock since it's a program.
82+
let compute_budget_ix =
83+
solana_compute_budget_interface::ComputeBudgetInstruction::set_compute_unit_limit(
84+
1_000_000,
85+
);
86+
let mut instructions: Vec<Instruction> = vec![compute_budget_ix];
87+
for _ in 0..64 {
88+
let recipient = Address::new_unique();
89+
let ix = transfer(&payer_pk, &recipient, 1_000_000);
90+
instructions.push(ix);
91+
}
92+
93+
let tx = Transaction::new(
94+
&[&payer_kp],
95+
Message::new(&instructions, Some(&payer_pk)),
96+
svm.latest_blockhash(),
97+
);
98+
99+
let result = svm.send_transaction(tx);
100+
101+
assert_eq!(
102+
result.unwrap_err().err,
103+
TransactionError::TooManyAccountLocks,
104+
"Expected TooManyAccountLocks error when transaction has more than 64 accounts"
105+
);
106+
}

0 commit comments

Comments
 (0)