Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite tests with Mollusk #32

Merged
merged 3 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
joncinque marked this conversation as resolved.
Show resolved Hide resolved

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