Skip to content

Commit

Permalink
Rewrite tests with Mollusk (#32)
Browse files Browse the repository at this point in the history
  • Loading branch information
buffalojoec authored Oct 18, 2024
1 parent 14ccb5f commit 7746868
Show file tree
Hide file tree
Showing 8 changed files with 857 additions and 3,673 deletions.
3,102 changes: 170 additions & 2,932 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion program/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ solana-program = "2.0.1"
spl-program-error = "0.5.0"

[dev-dependencies]
solana-program-test = "2.0.1"
mollusk-svm = "0.0.5"
solana-sdk = "2.0.1"
test-case = "3.3.1"

Expand Down
314 changes: 162 additions & 152 deletions program/tests/close_lookup_table_ix.rs
Original file line number Diff line number Diff line change
@@ -1,103 +1,99 @@
#![cfg(feature = "test-sbf")]

mod common;

use {
common::{
add_lookup_table_account, assert_ix_error, new_address_lookup_table,
overwrite_slot_hashes_with_slots, setup_test_context,
},
common::{lookup_table_account, new_address_lookup_table, setup},
mollusk_svm::result::Check,
solana_address_lookup_table_program::instruction::close_lookup_table,
solana_program_test::*,
solana_sdk::{
clock::Clock,
instruction::InstructionError,
pubkey::Pubkey,
signature::{Keypair, Signer},
transaction::Transaction,
account::AccountSharedData, program_error::ProgramError, pubkey::Pubkey,
slot_hashes::MAX_ENTRIES,
},
};

mod common;

#[tokio::test]
async fn test_close_lookup_table() {
#[test]
fn test_close_lookup_table() {
// Succesfully close a deactived lookup table.
let mut context = setup_test_context().await;

context.warp_to_slot(2).unwrap();
overwrite_slot_hashes_with_slots(&context, &[]);
let mut mollusk = setup();
mollusk.warp_to_slot(MAX_ENTRIES as u64 + 1);

let lookup_table_address = Pubkey::new_unique();
let authority_keypair = Keypair::new();
let recipient = Pubkey::new_unique();
let authority = Pubkey::new_unique();
let initialized_table = {
let mut table = new_address_lookup_table(Some(authority_keypair.pubkey()), 0);
table.meta.deactivation_slot = 1;
let mut table = new_address_lookup_table(Some(authority), 0);
table.meta.deactivation_slot = 0;
table
};
add_lookup_table_account(&mut context, lookup_table_address, initialized_table).await;

let client = &mut context.banks_client;
let payer = &context.payer;
let recent_blockhash = context.last_blockhash;
let transaction = Transaction::new_signed_with_payer(
&[close_lookup_table(
lookup_table_address,
authority_keypair.pubkey(),
context.payer.pubkey(),
)],
Some(&payer.pubkey()),
&[payer, &authority_keypair],
recent_blockhash,
);

assert!(matches!(
client.process_transaction(transaction).await,
Ok(())
));
assert!(client
.get_account(lookup_table_address)
.await
.unwrap()
.is_none());
let lookup_table_address = Pubkey::new_unique();
let lookup_table_account = lookup_table_account(initialized_table);

mollusk.process_and_validate_instruction(
&close_lookup_table(lookup_table_address, authority, recipient),
&[
(lookup_table_address, lookup_table_account),
(authority, AccountSharedData::default()),
(recipient, AccountSharedData::default()),
],
&[
Check::success(),
// Because lookup tables are not reassigned to the System program,
// we can't just check for the canonical "closed" here.
Check::account(&lookup_table_address)
.data(&[])
.lamports(0)
.owner(&solana_address_lookup_table_program::id())
.build(),
],
);
}

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

let authority_keypair = Keypair::new();
let initialized_table = new_address_lookup_table(Some(authority_keypair.pubkey()), 0);
let lookup_table_address = Pubkey::new_unique();
add_lookup_table_account(&mut context, lookup_table_address, initialized_table).await;
let recipient = Pubkey::new_unique();
let authority = Pubkey::new_unique();
let initialized_table = new_address_lookup_table(Some(authority), 0);

let ix = close_lookup_table(
lookup_table_address,
authority_keypair.pubkey(),
context.payer.pubkey(),
let lookup_table_address = Pubkey::new_unique();
let lookup_table_account = lookup_table_account(initialized_table);

mollusk.process_and_validate_instruction(
&close_lookup_table(lookup_table_address, authority, recipient),
&[
(lookup_table_address, lookup_table_account),
(authority, AccountSharedData::default()),
(recipient, AccountSharedData::default()),
],
&[
// The ix should fail because the table hasn't been deactivated yet
Check::err(ProgramError::InvalidArgument),
],
);

// The ix should fail because the table hasn't been deactivated yet
assert_ix_error(
&mut context,
ix.clone(),
Some(&authority_keypair),
InstructionError::InvalidArgument,
)
.await;
}

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

let authority_keypair = Keypair::new();
let recipient = Pubkey::new_unique();
let authority = Pubkey::new_unique();

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

let initialized_table = {
let mut table = new_address_lookup_table(Some(authority_keypair.pubkey()), 0);
let mut table = new_address_lookup_table(Some(authority), 0);
table.meta.deactivation_slot = deactivation_slot;
table
};
let lookup_table_address = Pubkey::new_unique();
add_lookup_table_account(&mut context, lookup_table_address, initialized_table).await;

let ix = close_lookup_table(
lookup_table_address,
authority_keypair.pubkey(),
context.payer.pubkey(),
);

let lookup_table_address = Pubkey::new_unique();
let lookup_table_account = lookup_table_account(initialized_table);

// [Core BPF]: This still holds true while using `Clock`.
// Context sets up the slot hashes sysvar to _not_ have an entry for
// the current slot, which is when the table was deactivated.
//
// When the curent slot from `Clock` is the same as the deactivation
// slot, `LookupTableMeta::status()` should evaluate to this branch:
//
// ```rust
// else if self.deactivation_slot == current_slot {
// LookupTableStatus::Deactivating {
// remaining_blocks: MAX_ENTRIES.saturating_add(1),
// }
// ````
//
// When the deactivation slot is a prior slot, but the cooldown period
// hasn't expired yet,`LookupTableMeta::status()` should evaluate to
// this branch:
//
// ```rust
// else if let Some(slot_position) =
// calculate_slot_position(&self.deactivation_slot, &current_slot)
// {
// LookupTableStatus::Deactivating {
// remaining_blocks: MAX_ENTRIES.saturating_sub(slot_position),
// }
// ````
//
// Because the response is not `LookupTableStatus::Deactivated`, the ix
// should fail.
assert_ix_error(
&mut context,
ix,
Some(&authority_keypair),
InstructionError::InvalidArgument,
)
.await;
mollusk.process_and_validate_instruction(
&close_lookup_table(lookup_table_address, authority, recipient),
&[
(lookup_table_address, lookup_table_account),
(authority, AccountSharedData::default()),
(recipient, AccountSharedData::default()),
],
&[Check::err(ProgramError::InvalidArgument)],
);
}
}

#[tokio::test]
async fn test_close_immutable_lookup_table() {
let mut context = setup_test_context().await;
#[test]
fn test_close_immutable_lookup_table() {
let mollusk = setup();

let initialized_table = new_address_lookup_table(None, 10);
let lookup_table_address = Pubkey::new_unique();
add_lookup_table_account(&mut context, lookup_table_address, initialized_table).await;
let recipient = Pubkey::new_unique();
let authority = Pubkey::new_unique();
let initialized_table = new_address_lookup_table(None, 0);

let authority = Keypair::new();
let ix = close_lookup_table(
lookup_table_address,
authority.pubkey(),
Pubkey::new_unique(),
let lookup_table_address = Pubkey::new_unique();
let lookup_table_account = lookup_table_account(initialized_table);

mollusk.process_and_validate_instruction(
&close_lookup_table(lookup_table_address, authority, recipient),
&[
(lookup_table_address, lookup_table_account),
(authority, AccountSharedData::default()),
(recipient, AccountSharedData::default()),
],
&[Check::err(ProgramError::Immutable)],
);

assert_ix_error(
&mut context,
ix,
Some(&authority),
InstructionError::Immutable,
)
.await;
}

#[tokio::test]
async fn test_close_lookup_table_with_wrong_authority() {
let mut context = setup_test_context().await;
#[test]
fn test_close_lookup_table_with_wrong_authority() {
let mollusk = setup();

let authority = Keypair::new();
let wrong_authority = Keypair::new();
let initialized_table = new_address_lookup_table(Some(authority.pubkey()), 10);
let lookup_table_address = Pubkey::new_unique();
add_lookup_table_account(&mut context, lookup_table_address, initialized_table).await;
let recipient = Pubkey::new_unique();
let authority = Pubkey::new_unique();
let wrong_authority = Pubkey::new_unique();
let initialized_table = new_address_lookup_table(Some(authority), 10);

let ix = close_lookup_table(
lookup_table_address,
wrong_authority.pubkey(),
Pubkey::new_unique(),
let lookup_table_address = Pubkey::new_unique();
let lookup_table_account = lookup_table_account(initialized_table.clone());

mollusk.process_and_validate_instruction(
&close_lookup_table(lookup_table_address, wrong_authority, recipient),
&[
(lookup_table_address, lookup_table_account),
(wrong_authority, AccountSharedData::default()),
(recipient, AccountSharedData::default()),
],
&[Check::err(ProgramError::IncorrectAuthority)],
);

assert_ix_error(
&mut context,
ix,
Some(&wrong_authority),
InstructionError::IncorrectAuthority,
)
.await;
}

#[tokio::test]
async fn test_close_lookup_table_without_signing() {
let mut context = setup_test_context().await;
#[test]
fn test_close_lookup_table_without_signing() {
let mollusk = setup();

let authority = Keypair::new();
let initialized_table = new_address_lookup_table(Some(authority.pubkey()), 10);
let lookup_table_address = Pubkey::new_unique();
add_lookup_table_account(&mut context, lookup_table_address, initialized_table).await;
let recipient = Pubkey::new_unique();
let authority = Pubkey::new_unique();
let initialized_table = new_address_lookup_table(Some(authority), 10);

let mut ix = close_lookup_table(
lookup_table_address,
authority.pubkey(),
Pubkey::new_unique(),
let lookup_table_address = Pubkey::new_unique();
let lookup_table_account = lookup_table_account(initialized_table.clone());

let mut instruction = close_lookup_table(lookup_table_address, authority, recipient);
instruction.accounts[1].is_signer = false;

mollusk.process_and_validate_instruction(
&instruction,
&[
(lookup_table_address, lookup_table_account),
(authority, AccountSharedData::default()),
(recipient, AccountSharedData::default()),
],
&[Check::err(ProgramError::MissingRequiredSignature)],
);
ix.accounts[1].is_signer = false;

assert_ix_error(
&mut context,
ix,
None,
InstructionError::MissingRequiredSignature,
)
.await;
}
Loading

0 comments on commit 7746868

Please sign in to comment.