solana/programs/address-lookup-table-tests/tests/extend_lookup_table_ix.rs

267 lines
8.5 KiB
Rust

use {
assert_matches::assert_matches,
common::{
add_lookup_table_account, assert_ix_error, new_address_lookup_table, setup_test_context,
},
solana_address_lookup_table_program::{
instruction::extend_lookup_table,
state::{AddressLookupTable, LookupTableMeta},
},
solana_program_test::*,
solana_sdk::{
account::ReadableAccount,
instruction::{Instruction, InstructionError},
pubkey::{Pubkey, PUBKEY_BYTES},
signature::{Keypair, Signer},
transaction::{Transaction, TransactionError},
},
std::{borrow::Cow, result::Result},
};
mod common;
struct ExpectedTableAccount {
lamports: u64,
data_len: usize,
state: AddressLookupTable<'static>,
}
struct TestCase<'a> {
lookup_table_address: Pubkey,
instruction: Instruction,
extra_signer: Option<&'a Keypair>,
expected_result: Result<ExpectedTableAccount, InstructionError>,
}
async fn run_test_case(context: &mut ProgramTestContext, test_case: TestCase<'_>) {
let client = &mut context.banks_client;
let payer = &context.payer;
let recent_blockhash = context.last_blockhash;
let mut signers = vec![payer];
if let Some(extra_signer) = test_case.extra_signer {
signers.push(extra_signer);
}
let transaction = Transaction::new_signed_with_payer(
&[test_case.instruction],
Some(&payer.pubkey()),
&signers,
recent_blockhash,
);
let process_result = client.process_transaction(transaction).await;
match test_case.expected_result {
Ok(expected_account) => {
assert_matches!(process_result, Ok(()));
let table_account = client
.get_account(test_case.lookup_table_address)
.await
.unwrap()
.unwrap();
let lookup_table = AddressLookupTable::deserialize(&table_account.data).unwrap();
assert_eq!(lookup_table, expected_account.state);
assert_eq!(table_account.lamports(), expected_account.lamports);
assert_eq!(table_account.data().len(), expected_account.data_len);
}
Err(expected_err) => {
assert_eq!(
process_result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, expected_err),
);
}
}
}
#[tokio::test]
async fn test_extend_lookup_table() {
let mut context = setup_test_context().await;
let authority = Keypair::new();
let current_bank_slot = 1;
let rent = context.banks_client.get_rent().await.unwrap();
for extend_same_slot in [true, false] {
for (num_existing_addresses, num_new_addresses, expected_result) in [
(0, 0, Err(InstructionError::InvalidInstructionData)),
(0, 1, Ok(())),
(0, 10, Ok(())),
(1, 1, Ok(())),
(1, 10, Ok(())),
(255, 1, Ok(())),
(255, 2, Err(InstructionError::InvalidInstructionData)),
(246, 10, Ok(())),
(256, 1, Err(InstructionError::InvalidArgument)),
] {
let mut lookup_table =
new_address_lookup_table(Some(authority.pubkey()), num_existing_addresses);
if extend_same_slot {
lookup_table.meta.last_extended_slot = current_bank_slot;
}
let lookup_table_address = Pubkey::new_unique();
let lookup_table_account =
add_lookup_table_account(&mut context, lookup_table_address, lookup_table.clone())
.await;
let mut new_addresses = Vec::with_capacity(num_new_addresses);
new_addresses.resize_with(num_new_addresses, Pubkey::new_unique);
let instruction = extend_lookup_table(
lookup_table_address,
authority.pubkey(),
context.payer.pubkey(),
new_addresses.clone(),
);
let mut expected_addresses: Vec<Pubkey> = lookup_table.addresses.to_vec();
expected_addresses.extend(new_addresses);
let expected_result = expected_result.map(|_| {
let expected_data_len =
lookup_table_account.data().len() + num_new_addresses * PUBKEY_BYTES;
let expected_lamports = rent.minimum_balance(expected_data_len);
let expected_lookup_table = AddressLookupTable {
meta: LookupTableMeta {
last_extended_slot: current_bank_slot,
last_extended_slot_start_index: if extend_same_slot {
0u8
} else {
num_existing_addresses as u8
},
deactivation_slot: lookup_table.meta.deactivation_slot,
authority: lookup_table.meta.authority,
_padding: 0u16,
},
addresses: Cow::Owned(expected_addresses),
};
ExpectedTableAccount {
lamports: expected_lamports,
data_len: expected_data_len,
state: expected_lookup_table,
}
});
let test_case = TestCase {
lookup_table_address,
instruction,
extra_signer: Some(&authority),
expected_result,
};
run_test_case(&mut context, test_case).await;
}
}
}
#[tokio::test]
async fn test_extend_lookup_table_with_wrong_authority() {
let mut context = setup_test_context().await;
let authority = Keypair::new();
let wrong_authority = Keypair::new();
let initialized_table = new_address_lookup_table(Some(authority.pubkey()), 0);
let lookup_table_address = Pubkey::new_unique();
add_lookup_table_account(&mut context, lookup_table_address, initialized_table).await;
let new_addresses = vec![Pubkey::new_unique()];
let ix = extend_lookup_table(
lookup_table_address,
wrong_authority.pubkey(),
context.payer.pubkey(),
new_addresses,
);
assert_ix_error(
&mut context,
ix,
Some(&wrong_authority),
InstructionError::IncorrectAuthority,
)
.await;
}
#[tokio::test]
async fn test_extend_lookup_table_without_signing() {
let mut context = setup_test_context().await;
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 new_addresses = vec![Pubkey::new_unique()];
let mut ix = extend_lookup_table(
lookup_table_address,
authority.pubkey(),
context.payer.pubkey(),
new_addresses,
);
ix.accounts[1].is_signer = false;
assert_ix_error(
&mut context,
ix,
None,
InstructionError::MissingRequiredSignature,
)
.await;
}
#[tokio::test]
async fn test_extend_deactivated_lookup_table() {
let mut context = setup_test_context().await;
let authority = Keypair::new();
let initialized_table = {
let mut table = new_address_lookup_table(Some(authority.pubkey()), 0);
table.meta.deactivation_slot = 0;
table
};
let lookup_table_address = Pubkey::new_unique();
add_lookup_table_account(&mut context, lookup_table_address, initialized_table).await;
let new_addresses = vec![Pubkey::new_unique()];
let ix = extend_lookup_table(
lookup_table_address,
authority.pubkey(),
context.payer.pubkey(),
new_addresses,
);
assert_ix_error(
&mut context,
ix,
Some(&authority),
InstructionError::InvalidArgument,
)
.await;
}
#[tokio::test]
async fn test_extend_immutable_lookup_table() {
let mut context = setup_test_context().await;
let authority = Keypair::new();
let initialized_table = new_address_lookup_table(None, 1);
let lookup_table_address = Pubkey::new_unique();
add_lookup_table_account(&mut context, lookup_table_address, initialized_table).await;
let new_addresses = vec![Pubkey::new_unique()];
let ix = extend_lookup_table(
lookup_table_address,
authority.pubkey(),
context.payer.pubkey(),
new_addresses,
);
assert_ix_error(
&mut context,
ix,
Some(&authority),
InstructionError::Immutable,
)
.await;
}