Skip to content

Commit b57d070

Browse files
authored
feat: added AllRentExempt check (#140)
1 parent 8c9bc3b commit b57d070

File tree

2 files changed

+69
-0
lines changed

2 files changed

+69
-0
lines changed

harness/tests/bpf_program.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,56 @@ fn test_transfer() {
159159
Check::account(&recipient)
160160
.lamports(recipient_lamports + transfer_amount)
161161
.build(),
162+
Check::all_rent_exempt(),
162163
],
163164
);
164165
}
165166

167+
#[test]
168+
#[should_panic(
169+
expected = "Account 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi is not rent exempt after \
170+
execution (lamports: 1, data_len: 0)"
171+
)]
172+
fn test_non_rent_exempt_transfer() {
173+
std::env::set_var("SBF_OUT_DIR", "../target/deploy");
174+
175+
let program_id = Pubkey::new_unique();
176+
177+
let mollusk = Mollusk::new(&program_id, "test_program_primary");
178+
179+
let payer = Pubkey::new_unique();
180+
let payer_lamports = 100_000_000;
181+
let payer_account = Account::new(payer_lamports, 0, &solana_sdk_ids::system_program::id());
182+
183+
// Use deterministic address for explicit panic matching
184+
let recipient = Pubkey::new_from_array([0x01; 32]);
185+
186+
let instruction_non_rent_exempt = {
187+
let mut instruction_data = vec![2];
188+
instruction_data.extend_from_slice(&1u64.to_le_bytes());
189+
Instruction::new_with_bytes(
190+
program_id,
191+
&instruction_data,
192+
vec![
193+
AccountMeta::new(payer, true),
194+
AccountMeta::new(recipient, false),
195+
AccountMeta::new_readonly(solana_sdk_ids::system_program::id(), false),
196+
],
197+
)
198+
};
199+
200+
// Fail non-rent-exempt account.
201+
mollusk.process_and_validate_instruction(
202+
&instruction_non_rent_exempt,
203+
&[
204+
(payer, payer_account.clone()),
205+
(recipient, Account::default()),
206+
keyed_account_for_system_program(),
207+
],
208+
&[Check::all_rent_exempt()],
209+
);
210+
}
211+
166212
#[test]
167213
fn test_close_account() {
168214
std::env::set_var("SBF_OUT_DIR", "../target/deploy");

result/src/check.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ enum CheckType<'a> {
2222
ReturnData(&'a [u8]),
2323
/// Check a resulting account after executing the instruction.
2424
ResultingAccount(AccountCheck<'a>),
25+
/// Check that all accounts are rent exempt
26+
AllRentExempt,
2527
}
2628

2729
pub struct Check<'a> {
@@ -72,6 +74,11 @@ impl<'a> Check<'a> {
7274
pub fn account(pubkey: &Pubkey) -> AccountCheckBuilder {
7375
AccountCheckBuilder::new(pubkey)
7476
}
77+
78+
/// Check that all resulting accounts are rent exempt
79+
pub fn all_rent_exempt() -> Self {
80+
Check::new(CheckType::AllRentExempt)
81+
}
7582
}
7683

7784
enum AccountStateCheck {
@@ -271,6 +278,22 @@ impl InstructionResult {
271278
compare!(c, "account_data_slice", check_data_slice, actual_data_slice,);
272279
}
273280
}
281+
CheckType::AllRentExempt => {
282+
for (pubkey, account) in &self.resulting_accounts {
283+
let is_rent_exempt =
284+
context.is_rent_exempt(account.lamports(), account.data().len());
285+
if !is_rent_exempt {
286+
pass &= throw!(
287+
c,
288+
"Account {} is not rent exempt after execution (lamports: {}, \
289+
data_len: {})",
290+
pubkey,
291+
account.lamports(),
292+
account.data().len()
293+
);
294+
}
295+
}
296+
}
274297
}
275298
}
276299
pass

0 commit comments

Comments
 (0)