Skip to content

Commit 7746868

Browse files
authored
Rewrite tests with Mollusk (#32)
1 parent 14ccb5f commit 7746868

File tree

8 files changed

+857
-3673
lines changed

8 files changed

+857
-3673
lines changed

Cargo.lock

Lines changed: 170 additions & 2932 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

program/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ solana-program = "2.0.1"
2626
spl-program-error = "0.5.0"
2727

2828
[dev-dependencies]
29-
solana-program-test = "2.0.1"
29+
mollusk-svm = "0.0.5"
3030
solana-sdk = "2.0.1"
3131
test-case = "3.3.1"
3232

Lines changed: 162 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,99 @@
11
#![cfg(feature = "test-sbf")]
22

3+
mod common;
4+
35
use {
4-
common::{
5-
add_lookup_table_account, assert_ix_error, new_address_lookup_table,
6-
overwrite_slot_hashes_with_slots, setup_test_context,
7-
},
6+
common::{lookup_table_account, new_address_lookup_table, setup},
7+
mollusk_svm::result::Check,
88
solana_address_lookup_table_program::instruction::close_lookup_table,
9-
solana_program_test::*,
109
solana_sdk::{
11-
clock::Clock,
12-
instruction::InstructionError,
13-
pubkey::Pubkey,
14-
signature::{Keypair, Signer},
15-
transaction::Transaction,
10+
account::AccountSharedData, program_error::ProgramError, pubkey::Pubkey,
11+
slot_hashes::MAX_ENTRIES,
1612
},
1713
};
1814

19-
mod common;
20-
21-
#[tokio::test]
22-
async fn test_close_lookup_table() {
15+
#[test]
16+
fn test_close_lookup_table() {
2317
// Succesfully close a deactived lookup table.
24-
let mut context = setup_test_context().await;
25-
26-
context.warp_to_slot(2).unwrap();
27-
overwrite_slot_hashes_with_slots(&context, &[]);
18+
let mut mollusk = setup();
19+
mollusk.warp_to_slot(MAX_ENTRIES as u64 + 1);
2820

29-
let lookup_table_address = Pubkey::new_unique();
30-
let authority_keypair = Keypair::new();
21+
let recipient = Pubkey::new_unique();
22+
let authority = Pubkey::new_unique();
3123
let initialized_table = {
32-
let mut table = new_address_lookup_table(Some(authority_keypair.pubkey()), 0);
33-
table.meta.deactivation_slot = 1;
24+
let mut table = new_address_lookup_table(Some(authority), 0);
25+
table.meta.deactivation_slot = 0;
3426
table
3527
};
36-
add_lookup_table_account(&mut context, lookup_table_address, initialized_table).await;
37-
38-
let client = &mut context.banks_client;
39-
let payer = &context.payer;
40-
let recent_blockhash = context.last_blockhash;
41-
let transaction = Transaction::new_signed_with_payer(
42-
&[close_lookup_table(
43-
lookup_table_address,
44-
authority_keypair.pubkey(),
45-
context.payer.pubkey(),
46-
)],
47-
Some(&payer.pubkey()),
48-
&[payer, &authority_keypair],
49-
recent_blockhash,
50-
);
5128

52-
assert!(matches!(
53-
client.process_transaction(transaction).await,
54-
Ok(())
55-
));
56-
assert!(client
57-
.get_account(lookup_table_address)
58-
.await
59-
.unwrap()
60-
.is_none());
29+
let lookup_table_address = Pubkey::new_unique();
30+
let lookup_table_account = lookup_table_account(initialized_table);
31+
32+
mollusk.process_and_validate_instruction(
33+
&close_lookup_table(lookup_table_address, authority, recipient),
34+
&[
35+
(lookup_table_address, lookup_table_account),
36+
(authority, AccountSharedData::default()),
37+
(recipient, AccountSharedData::default()),
38+
],
39+
&[
40+
Check::success(),
41+
// Because lookup tables are not reassigned to the System program,
42+
// we can't just check for the canonical "closed" here.
43+
Check::account(&lookup_table_address)
44+
.data(&[])
45+
.lamports(0)
46+
.owner(&solana_address_lookup_table_program::id())
47+
.build(),
48+
],
49+
);
6150
}
6251

63-
#[tokio::test]
64-
async fn test_close_lookup_table_not_deactivated() {
52+
#[test]
53+
fn test_close_lookup_table_not_deactivated() {
6554
// Try to close a lookup table that hasn't first been deactivated.
6655
// No matter the slot, this will fail, since the lookup table must first
6756
// be deactived before it can be closed.
68-
let mut context = setup_test_context().await;
57+
let mollusk = setup();
6958

70-
let authority_keypair = Keypair::new();
71-
let initialized_table = new_address_lookup_table(Some(authority_keypair.pubkey()), 0);
72-
let lookup_table_address = Pubkey::new_unique();
73-
add_lookup_table_account(&mut context, lookup_table_address, initialized_table).await;
59+
let recipient = Pubkey::new_unique();
60+
let authority = Pubkey::new_unique();
61+
let initialized_table = new_address_lookup_table(Some(authority), 0);
7462

75-
let ix = close_lookup_table(
76-
lookup_table_address,
77-
authority_keypair.pubkey(),
78-
context.payer.pubkey(),
63+
let lookup_table_address = Pubkey::new_unique();
64+
let lookup_table_account = lookup_table_account(initialized_table);
65+
66+
mollusk.process_and_validate_instruction(
67+
&close_lookup_table(lookup_table_address, authority, recipient),
68+
&[
69+
(lookup_table_address, lookup_table_account),
70+
(authority, AccountSharedData::default()),
71+
(recipient, AccountSharedData::default()),
72+
],
73+
&[
74+
// The ix should fail because the table hasn't been deactivated yet
75+
Check::err(ProgramError::InvalidArgument),
76+
],
7977
);
80-
81-
// The ix should fail because the table hasn't been deactivated yet
82-
assert_ix_error(
83-
&mut context,
84-
ix.clone(),
85-
Some(&authority_keypair),
86-
InstructionError::InvalidArgument,
87-
)
88-
.await;
8978
}
9079

91-
#[tokio::test]
92-
async fn test_close_lookup_table_deactivated() {
80+
#[test]
81+
fn test_close_lookup_table_deactivated() {
9382
// Try to close a lookup table that was deactivated, but the cooldown
9483
// period hasn't expired yet.
9584
// This should fail because the table must be deactivated in a previous
9685
// slot and the cooldown period must expire before it can be closed.
97-
let mut context = setup_test_context().await;
86+
let mut mollusk = setup();
9887

99-
let authority_keypair = Keypair::new();
88+
let recipient = Pubkey::new_unique();
89+
let authority = Pubkey::new_unique();
10090

91+
// [Core BPF]: The original builtin implementation was relying on the fact
92+
// that the `SlotHashes` sysvar is initialized to have an entry for slot 0.
93+
// Program-Test does this to provide a more realistic test environment.
94+
// That means this test was running with the `Clock` current slot at 1.
95+
// In this implementation, we adapt the deactivation slot as well as the
96+
// current slot into tweakable test case values.
10197
for (deactivation_slot, current_slot) in [
10298
(1, 1), // Deactivated in the same slot
10399
(1, 2), // Deactivated one slot earlier
@@ -112,110 +108,124 @@ async fn test_close_lookup_table_deactivated() {
112108
(10_000, 10_000 + 115), // Arbitrary number within cooldown.
113109
(10_000, 10_000 + 511), // At the very edge of cooldown.
114110
] {
115-
// Unfortunately, Program-Test's `warp_to_slot` causes an accounts hash
116-
// mismatch if you try to warp after setting an account, so we have to just
117-
// manipulate the `Clock` directly here.
118-
let mut clock = context.banks_client.get_sysvar::<Clock>().await.unwrap();
119-
clock.slot = current_slot;
120-
context.set_sysvar::<Clock>(&clock);
121-
overwrite_slot_hashes_with_slots(&context, &[deactivation_slot]);
111+
mollusk.warp_to_slot(current_slot);
122112

123113
let initialized_table = {
124-
let mut table = new_address_lookup_table(Some(authority_keypair.pubkey()), 0);
114+
let mut table = new_address_lookup_table(Some(authority), 0);
125115
table.meta.deactivation_slot = deactivation_slot;
126116
table
127117
};
128-
let lookup_table_address = Pubkey::new_unique();
129-
add_lookup_table_account(&mut context, lookup_table_address, initialized_table).await;
130-
131-
let ix = close_lookup_table(
132-
lookup_table_address,
133-
authority_keypair.pubkey(),
134-
context.payer.pubkey(),
135-
);
136118

119+
let lookup_table_address = Pubkey::new_unique();
120+
let lookup_table_account = lookup_table_account(initialized_table);
121+
122+
// [Core BPF]: This still holds true while using `Clock`.
123+
// Context sets up the slot hashes sysvar to _not_ have an entry for
124+
// the current slot, which is when the table was deactivated.
125+
//
126+
// When the curent slot from `Clock` is the same as the deactivation
127+
// slot, `LookupTableMeta::status()` should evaluate to this branch:
128+
//
129+
// ```rust
130+
// else if self.deactivation_slot == current_slot {
131+
// LookupTableStatus::Deactivating {
132+
// remaining_blocks: MAX_ENTRIES.saturating_add(1),
133+
// }
134+
// ````
135+
//
136+
// When the deactivation slot is a prior slot, but the cooldown period
137+
// hasn't expired yet,`LookupTableMeta::status()` should evaluate to
138+
// this branch:
139+
//
140+
// ```rust
141+
// else if let Some(slot_position) =
142+
// calculate_slot_position(&self.deactivation_slot, &current_slot)
143+
// {
144+
// LookupTableStatus::Deactivating {
145+
// remaining_blocks: MAX_ENTRIES.saturating_sub(slot_position),
146+
// }
147+
// ````
148+
//
137149
// Because the response is not `LookupTableStatus::Deactivated`, the ix
138150
// should fail.
139-
assert_ix_error(
140-
&mut context,
141-
ix,
142-
Some(&authority_keypair),
143-
InstructionError::InvalidArgument,
144-
)
145-
.await;
151+
mollusk.process_and_validate_instruction(
152+
&close_lookup_table(lookup_table_address, authority, recipient),
153+
&[
154+
(lookup_table_address, lookup_table_account),
155+
(authority, AccountSharedData::default()),
156+
(recipient, AccountSharedData::default()),
157+
],
158+
&[Check::err(ProgramError::InvalidArgument)],
159+
);
146160
}
147161
}
148162

149-
#[tokio::test]
150-
async fn test_close_immutable_lookup_table() {
151-
let mut context = setup_test_context().await;
163+
#[test]
164+
fn test_close_immutable_lookup_table() {
165+
let mollusk = setup();
152166

153-
let initialized_table = new_address_lookup_table(None, 10);
154-
let lookup_table_address = Pubkey::new_unique();
155-
add_lookup_table_account(&mut context, lookup_table_address, initialized_table).await;
167+
let recipient = Pubkey::new_unique();
168+
let authority = Pubkey::new_unique();
169+
let initialized_table = new_address_lookup_table(None, 0);
156170

157-
let authority = Keypair::new();
158-
let ix = close_lookup_table(
159-
lookup_table_address,
160-
authority.pubkey(),
161-
Pubkey::new_unique(),
171+
let lookup_table_address = Pubkey::new_unique();
172+
let lookup_table_account = lookup_table_account(initialized_table);
173+
174+
mollusk.process_and_validate_instruction(
175+
&close_lookup_table(lookup_table_address, authority, recipient),
176+
&[
177+
(lookup_table_address, lookup_table_account),
178+
(authority, AccountSharedData::default()),
179+
(recipient, AccountSharedData::default()),
180+
],
181+
&[Check::err(ProgramError::Immutable)],
162182
);
163-
164-
assert_ix_error(
165-
&mut context,
166-
ix,
167-
Some(&authority),
168-
InstructionError::Immutable,
169-
)
170-
.await;
171183
}
172184

173-
#[tokio::test]
174-
async fn test_close_lookup_table_with_wrong_authority() {
175-
let mut context = setup_test_context().await;
185+
#[test]
186+
fn test_close_lookup_table_with_wrong_authority() {
187+
let mollusk = setup();
176188

177-
let authority = Keypair::new();
178-
let wrong_authority = Keypair::new();
179-
let initialized_table = new_address_lookup_table(Some(authority.pubkey()), 10);
180-
let lookup_table_address = Pubkey::new_unique();
181-
add_lookup_table_account(&mut context, lookup_table_address, initialized_table).await;
189+
let recipient = Pubkey::new_unique();
190+
let authority = Pubkey::new_unique();
191+
let wrong_authority = Pubkey::new_unique();
192+
let initialized_table = new_address_lookup_table(Some(authority), 10);
182193

183-
let ix = close_lookup_table(
184-
lookup_table_address,
185-
wrong_authority.pubkey(),
186-
Pubkey::new_unique(),
194+
let lookup_table_address = Pubkey::new_unique();
195+
let lookup_table_account = lookup_table_account(initialized_table.clone());
196+
197+
mollusk.process_and_validate_instruction(
198+
&close_lookup_table(lookup_table_address, wrong_authority, recipient),
199+
&[
200+
(lookup_table_address, lookup_table_account),
201+
(wrong_authority, AccountSharedData::default()),
202+
(recipient, AccountSharedData::default()),
203+
],
204+
&[Check::err(ProgramError::IncorrectAuthority)],
187205
);
188-
189-
assert_ix_error(
190-
&mut context,
191-
ix,
192-
Some(&wrong_authority),
193-
InstructionError::IncorrectAuthority,
194-
)
195-
.await;
196206
}
197207

198-
#[tokio::test]
199-
async fn test_close_lookup_table_without_signing() {
200-
let mut context = setup_test_context().await;
208+
#[test]
209+
fn test_close_lookup_table_without_signing() {
210+
let mollusk = setup();
201211

202-
let authority = Keypair::new();
203-
let initialized_table = new_address_lookup_table(Some(authority.pubkey()), 10);
204-
let lookup_table_address = Pubkey::new_unique();
205-
add_lookup_table_account(&mut context, lookup_table_address, initialized_table).await;
212+
let recipient = Pubkey::new_unique();
213+
let authority = Pubkey::new_unique();
214+
let initialized_table = new_address_lookup_table(Some(authority), 10);
206215

207-
let mut ix = close_lookup_table(
208-
lookup_table_address,
209-
authority.pubkey(),
210-
Pubkey::new_unique(),
216+
let lookup_table_address = Pubkey::new_unique();
217+
let lookup_table_account = lookup_table_account(initialized_table.clone());
218+
219+
let mut instruction = close_lookup_table(lookup_table_address, authority, recipient);
220+
instruction.accounts[1].is_signer = false;
221+
222+
mollusk.process_and_validate_instruction(
223+
&instruction,
224+
&[
225+
(lookup_table_address, lookup_table_account),
226+
(authority, AccountSharedData::default()),
227+
(recipient, AccountSharedData::default()),
228+
],
229+
&[Check::err(ProgramError::MissingRequiredSignature)],
211230
);
212-
ix.accounts[1].is_signer = false;
213-
214-
assert_ix_error(
215-
&mut context,
216-
ix,
217-
None,
218-
InstructionError::MissingRequiredSignature,
219-
)
220-
.await;
221231
}

0 commit comments

Comments
 (0)