* Add address lookup table program (#21616)
* Add address lookup table program
* feedback
(cherry picked from commit 9b41ddd9ba
)
# Conflicts:
# runtime/Cargo.toml
* resolve conflicts
Co-authored-by: Justin Starry <justin@solana.com>
This commit is contained in:
22
programs/address-lookup-table-tests/Cargo.toml
Normal file
22
programs/address-lookup-table-tests/Cargo.toml
Normal file
@ -0,0 +1,22 @@
|
||||
# This package only exists to avoid circular dependencies during cargo publish:
|
||||
# solana-runtime -> solana-address-program-runtime -> solana-program-test -> solana-runtime
|
||||
|
||||
[package]
|
||||
name = "solana-address-lookup-table-program-tests"
|
||||
version = "1.9.0"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dev-dependencies]
|
||||
assert_matches = "1.5.0"
|
||||
bincode = "1.3.3"
|
||||
solana-address-lookup-table-program = { path = "../address-lookup-table", version = "=1.9.0" }
|
||||
solana-program-test = { path = "../../program-test", version = "=1.9.0" }
|
||||
solana-sdk = { path = "../../sdk", version = "=1.9.0" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
@ -0,0 +1,151 @@
|
||||
use {
|
||||
assert_matches::assert_matches,
|
||||
common::{
|
||||
add_lookup_table_account, assert_ix_error, new_address_lookup_table,
|
||||
overwrite_slot_hashes_with_slots, setup_test_context,
|
||||
},
|
||||
solana_address_lookup_table_program::instruction::close_lookup_table,
|
||||
solana_program_test::*,
|
||||
solana_sdk::{
|
||||
instruction::InstructionError,
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signer},
|
||||
transaction::Transaction,
|
||||
},
|
||||
};
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_close_lookup_table() {
|
||||
let mut context = setup_test_context().await;
|
||||
overwrite_slot_hashes_with_slots(&mut context, &[]);
|
||||
|
||||
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 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());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_close_lookup_table_too_recent() {
|
||||
let mut context = setup_test_context().await;
|
||||
|
||||
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 ix = close_lookup_table(
|
||||
lookup_table_address,
|
||||
authority_keypair.pubkey(),
|
||||
context.payer.pubkey(),
|
||||
);
|
||||
|
||||
// Context sets up the slot hashes sysvar to have an entry
|
||||
// for slot 0 which is what the default initialized table
|
||||
// has as its derivation slot. Because that slot is present,
|
||||
// the ix should fail.
|
||||
assert_ix_error(
|
||||
&mut context,
|
||||
ix,
|
||||
Some(&authority_keypair),
|
||||
InstructionError::InvalidArgument,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_close_immutable_lookup_table() {
|
||||
let mut context = setup_test_context().await;
|
||||
|
||||
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 authority = Keypair::new();
|
||||
let ix = close_lookup_table(
|
||||
lookup_table_address,
|
||||
authority.pubkey(),
|
||||
Pubkey::new_unique(),
|
||||
);
|
||||
|
||||
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;
|
||||
|
||||
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 ix = close_lookup_table(
|
||||
lookup_table_address,
|
||||
wrong_authority.pubkey(),
|
||||
Pubkey::new_unique(),
|
||||
);
|
||||
|
||||
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;
|
||||
|
||||
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 mut ix = close_lookup_table(
|
||||
lookup_table_address,
|
||||
authority.pubkey(),
|
||||
Pubkey::new_unique(),
|
||||
);
|
||||
ix.accounts[1].is_signer = false;
|
||||
|
||||
assert_ix_error(
|
||||
&mut context,
|
||||
ix,
|
||||
None,
|
||||
InstructionError::MissingRequiredSignature,
|
||||
)
|
||||
.await;
|
||||
}
|
103
programs/address-lookup-table-tests/tests/common.rs
Normal file
103
programs/address-lookup-table-tests/tests/common.rs
Normal file
@ -0,0 +1,103 @@
|
||||
#![allow(dead_code)]
|
||||
use {
|
||||
solana_address_lookup_table_program::{
|
||||
id,
|
||||
processor::process_instruction,
|
||||
state::{AddressLookupTable, LookupTableMeta},
|
||||
},
|
||||
solana_program_test::*,
|
||||
solana_sdk::{
|
||||
account::AccountSharedData,
|
||||
clock::Slot,
|
||||
hash::Hash,
|
||||
instruction::Instruction,
|
||||
instruction::InstructionError,
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signer},
|
||||
slot_hashes::SlotHashes,
|
||||
transaction::{Transaction, TransactionError},
|
||||
},
|
||||
std::borrow::Cow,
|
||||
};
|
||||
|
||||
pub async fn setup_test_context() -> ProgramTestContext {
|
||||
let program_test = ProgramTest::new("", id(), Some(process_instruction));
|
||||
program_test.start_with_context().await
|
||||
}
|
||||
|
||||
pub async fn assert_ix_error(
|
||||
context: &mut ProgramTestContext,
|
||||
ix: Instruction,
|
||||
authority_keypair: Option<&Keypair>,
|
||||
expected_err: InstructionError,
|
||||
) {
|
||||
let client = &mut context.banks_client;
|
||||
let payer = &context.payer;
|
||||
let recent_blockhash = context.last_blockhash;
|
||||
|
||||
let mut signers = vec![payer];
|
||||
if let Some(authority) = authority_keypair {
|
||||
signers.push(authority);
|
||||
}
|
||||
|
||||
let transaction = Transaction::new_signed_with_payer(
|
||||
&[ix],
|
||||
Some(&payer.pubkey()),
|
||||
&signers,
|
||||
recent_blockhash,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
client
|
||||
.process_transaction(transaction)
|
||||
.await
|
||||
.unwrap_err()
|
||||
.unwrap(),
|
||||
TransactionError::InstructionError(0, expected_err),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn new_address_lookup_table(
|
||||
authority: Option<Pubkey>,
|
||||
num_addresses: usize,
|
||||
) -> AddressLookupTable<'static> {
|
||||
let mut addresses = Vec::with_capacity(num_addresses);
|
||||
addresses.resize_with(num_addresses, Pubkey::new_unique);
|
||||
AddressLookupTable {
|
||||
meta: LookupTableMeta {
|
||||
authority,
|
||||
..LookupTableMeta::default()
|
||||
},
|
||||
addresses: Cow::Owned(addresses),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn add_lookup_table_account(
|
||||
context: &mut ProgramTestContext,
|
||||
account_address: Pubkey,
|
||||
address_lookup_table: AddressLookupTable<'static>,
|
||||
) -> AccountSharedData {
|
||||
let mut data = Vec::new();
|
||||
address_lookup_table.serialize_for_tests(&mut data).unwrap();
|
||||
|
||||
let rent = context.banks_client.get_rent().await.unwrap();
|
||||
let rent_exempt_balance = rent.minimum_balance(data.len());
|
||||
|
||||
let mut account = AccountSharedData::new(
|
||||
rent_exempt_balance,
|
||||
data.len(),
|
||||
&solana_address_lookup_table_program::id(),
|
||||
);
|
||||
account.set_data(data);
|
||||
context.set_account(&account_address, &account);
|
||||
|
||||
account
|
||||
}
|
||||
|
||||
pub fn overwrite_slot_hashes_with_slots(context: &mut ProgramTestContext, slots: &[Slot]) {
|
||||
let mut slot_hashes = SlotHashes::default();
|
||||
for slot in slots {
|
||||
slot_hashes.add(*slot, Hash::new_unique());
|
||||
}
|
||||
context.set_sysvar(&slot_hashes);
|
||||
}
|
@ -0,0 +1,158 @@
|
||||
use {
|
||||
assert_matches::assert_matches,
|
||||
common::{assert_ix_error, overwrite_slot_hashes_with_slots, setup_test_context},
|
||||
solana_address_lookup_table_program::{
|
||||
id,
|
||||
instruction::create_lookup_table,
|
||||
state::{AddressLookupTable, LOOKUP_TABLE_META_SIZE},
|
||||
},
|
||||
solana_program_test::*,
|
||||
solana_sdk::{
|
||||
clock::Slot, instruction::InstructionError, pubkey::Pubkey, rent::Rent, signature::Signer,
|
||||
signer::keypair::Keypair, transaction::Transaction,
|
||||
},
|
||||
};
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_lookup_table() {
|
||||
let mut context = setup_test_context().await;
|
||||
|
||||
let test_recent_slot = 123;
|
||||
overwrite_slot_hashes_with_slots(&mut context, &[test_recent_slot]);
|
||||
|
||||
let client = &mut context.banks_client;
|
||||
let payer = &context.payer;
|
||||
let recent_blockhash = context.last_blockhash;
|
||||
let authority_keypair = Keypair::new();
|
||||
let authority_address = authority_keypair.pubkey();
|
||||
let (create_lookup_table_ix, lookup_table_address) =
|
||||
create_lookup_table(authority_address, payer.pubkey(), test_recent_slot);
|
||||
|
||||
// First create should succeed
|
||||
{
|
||||
let transaction = Transaction::new_signed_with_payer(
|
||||
&[create_lookup_table_ix.clone()],
|
||||
Some(&payer.pubkey()),
|
||||
&[payer, &authority_keypair],
|
||||
recent_blockhash,
|
||||
);
|
||||
|
||||
assert_matches!(client.process_transaction(transaction).await, Ok(()));
|
||||
let lookup_table_account = client
|
||||
.get_account(lookup_table_address)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(lookup_table_account.owner, crate::id());
|
||||
assert_eq!(lookup_table_account.data.len(), LOOKUP_TABLE_META_SIZE);
|
||||
assert_eq!(
|
||||
lookup_table_account.lamports,
|
||||
Rent::default().minimum_balance(LOOKUP_TABLE_META_SIZE)
|
||||
);
|
||||
let lookup_table = AddressLookupTable::deserialize(&lookup_table_account.data).unwrap();
|
||||
assert_eq!(lookup_table.meta.derivation_slot, test_recent_slot);
|
||||
assert_eq!(lookup_table.meta.authority, Some(authority_address));
|
||||
assert_eq!(lookup_table.meta.last_extended_slot, 0);
|
||||
assert_eq!(lookup_table.meta.last_extended_slot_start_index, 0);
|
||||
assert_eq!(lookup_table.addresses.len(), 0);
|
||||
}
|
||||
|
||||
// Second create should fail
|
||||
{
|
||||
context.last_blockhash = client
|
||||
.get_new_latest_blockhash(&recent_blockhash)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_ix_error(
|
||||
&mut context,
|
||||
create_lookup_table_ix,
|
||||
Some(&authority_keypair),
|
||||
InstructionError::AccountAlreadyInitialized,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_lookup_table_use_payer_as_authority() {
|
||||
let mut context = setup_test_context().await;
|
||||
|
||||
let test_recent_slot = 123;
|
||||
overwrite_slot_hashes_with_slots(&mut context, &[test_recent_slot]);
|
||||
|
||||
let client = &mut context.banks_client;
|
||||
let payer = &context.payer;
|
||||
let recent_blockhash = context.last_blockhash;
|
||||
let authority_address = payer.pubkey();
|
||||
let transaction = Transaction::new_signed_with_payer(
|
||||
&[create_lookup_table(authority_address, payer.pubkey(), test_recent_slot).0],
|
||||
Some(&payer.pubkey()),
|
||||
&[payer],
|
||||
recent_blockhash,
|
||||
);
|
||||
|
||||
assert_matches!(client.process_transaction(transaction).await, Ok(()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_lookup_table_without_signer() {
|
||||
let mut context = setup_test_context().await;
|
||||
let unsigned_authority_address = Pubkey::new_unique();
|
||||
|
||||
let mut ix = create_lookup_table(
|
||||
unsigned_authority_address,
|
||||
context.payer.pubkey(),
|
||||
Slot::MAX,
|
||||
)
|
||||
.0;
|
||||
ix.accounts[1].is_signer = false;
|
||||
|
||||
assert_ix_error(
|
||||
&mut context,
|
||||
ix,
|
||||
None,
|
||||
InstructionError::MissingRequiredSignature,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_lookup_table_not_recent_slot() {
|
||||
let mut context = setup_test_context().await;
|
||||
let payer = &context.payer;
|
||||
let authority_keypair = Keypair::new();
|
||||
let authority_address = authority_keypair.pubkey();
|
||||
|
||||
let ix = create_lookup_table(authority_address, payer.pubkey(), Slot::MAX).0;
|
||||
|
||||
assert_ix_error(
|
||||
&mut context,
|
||||
ix,
|
||||
Some(&authority_keypair),
|
||||
InstructionError::InvalidInstructionData,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_lookup_table_pda_mismatch() {
|
||||
let mut context = setup_test_context().await;
|
||||
let test_recent_slot = 123;
|
||||
overwrite_slot_hashes_with_slots(&mut context, &[test_recent_slot]);
|
||||
let payer = &context.payer;
|
||||
let authority_keypair = Keypair::new();
|
||||
let authority_address = authority_keypair.pubkey();
|
||||
|
||||
let mut ix = create_lookup_table(authority_address, payer.pubkey(), test_recent_slot).0;
|
||||
ix.accounts[0].pubkey = Pubkey::new_unique();
|
||||
|
||||
assert_ix_error(
|
||||
&mut context,
|
||||
ix,
|
||||
Some(&authority_keypair),
|
||||
InstructionError::InvalidArgument,
|
||||
)
|
||||
.await;
|
||||
}
|
@ -0,0 +1,214 @@
|
||||
use {
|
||||
assert_matches::assert_matches,
|
||||
common::{add_lookup_table_account, 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,
|
||||
instruction::InstructionError,
|
||||
pubkey::{Pubkey, PUBKEY_BYTES},
|
||||
signature::{Keypair, Signer},
|
||||
transaction::{Transaction, TransactionError},
|
||||
},
|
||||
std::borrow::Cow,
|
||||
std::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
|
||||
},
|
||||
derivation_slot: lookup_table.meta.derivation_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_addresses_authority_errors() {
|
||||
let mut context = setup_test_context().await;
|
||||
let authority = Keypair::new();
|
||||
|
||||
for (existing_authority, ix_authority, use_signer, expected_err) in [
|
||||
(
|
||||
Some(authority.pubkey()),
|
||||
Keypair::new(),
|
||||
true,
|
||||
InstructionError::IncorrectAuthority,
|
||||
),
|
||||
(
|
||||
Some(authority.pubkey()),
|
||||
authority,
|
||||
false,
|
||||
InstructionError::MissingRequiredSignature,
|
||||
),
|
||||
(None, Keypair::new(), true, InstructionError::Immutable),
|
||||
] {
|
||||
let lookup_table = new_address_lookup_table(existing_authority, 0);
|
||||
let lookup_table_address = Pubkey::new_unique();
|
||||
let _ = add_lookup_table_account(&mut context, lookup_table_address, lookup_table.clone())
|
||||
.await;
|
||||
|
||||
let num_new_addresses = 1;
|
||||
let mut new_addresses = Vec::with_capacity(num_new_addresses);
|
||||
new_addresses.resize_with(num_new_addresses, Pubkey::new_unique);
|
||||
let mut instruction = extend_lookup_table(
|
||||
lookup_table_address,
|
||||
ix_authority.pubkey(),
|
||||
context.payer.pubkey(),
|
||||
new_addresses.clone(),
|
||||
);
|
||||
if !use_signer {
|
||||
instruction.accounts[1].is_signer = false;
|
||||
}
|
||||
|
||||
let mut expected_addresses: Vec<Pubkey> = lookup_table.addresses.to_vec();
|
||||
expected_addresses.extend(new_addresses);
|
||||
|
||||
let extra_signer = if use_signer {
|
||||
Some(&ix_authority)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let test_case = TestCase {
|
||||
lookup_table_address,
|
||||
instruction,
|
||||
extra_signer,
|
||||
expected_result: Err(expected_err),
|
||||
};
|
||||
|
||||
run_test_case(&mut context, test_case).await;
|
||||
}
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
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::freeze_lookup_table, state::AddressLookupTable,
|
||||
},
|
||||
solana_program_test::*,
|
||||
solana_sdk::{
|
||||
instruction::InstructionError,
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signer},
|
||||
transaction::Transaction,
|
||||
},
|
||||
};
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_freeze_lookup_table() {
|
||||
let mut context = setup_test_context().await;
|
||||
|
||||
let authority = Keypair::new();
|
||||
let mut 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.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
let client = &mut context.banks_client;
|
||||
let payer = &context.payer;
|
||||
let recent_blockhash = context.last_blockhash;
|
||||
let transaction = Transaction::new_signed_with_payer(
|
||||
&[freeze_lookup_table(
|
||||
lookup_table_address,
|
||||
authority.pubkey(),
|
||||
)],
|
||||
Some(&payer.pubkey()),
|
||||
&[payer, &authority],
|
||||
recent_blockhash,
|
||||
);
|
||||
|
||||
assert_matches!(client.process_transaction(transaction).await, Ok(()));
|
||||
let table_account = client
|
||||
.get_account(lookup_table_address)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let lookup_table = AddressLookupTable::deserialize(&table_account.data).unwrap();
|
||||
assert_eq!(lookup_table.meta.authority, None);
|
||||
|
||||
// Check that only the authority changed
|
||||
initialized_table.meta.authority = None;
|
||||
assert_eq!(initialized_table, lookup_table);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_freeze_immutable_lookup_table() {
|
||||
let mut context = setup_test_context().await;
|
||||
|
||||
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 authority = Keypair::new();
|
||||
let ix = freeze_lookup_table(lookup_table_address, authority.pubkey());
|
||||
|
||||
assert_ix_error(
|
||||
&mut context,
|
||||
ix,
|
||||
Some(&authority),
|
||||
InstructionError::Immutable,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_freeze_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()), 10);
|
||||
let lookup_table_address = Pubkey::new_unique();
|
||||
add_lookup_table_account(&mut context, lookup_table_address, initialized_table).await;
|
||||
|
||||
let ix = freeze_lookup_table(lookup_table_address, wrong_authority.pubkey());
|
||||
|
||||
assert_ix_error(
|
||||
&mut context,
|
||||
ix,
|
||||
Some(&wrong_authority),
|
||||
InstructionError::IncorrectAuthority,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_freeze_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 mut ix = freeze_lookup_table(lookup_table_address, authority.pubkey());
|
||||
ix.accounts[1].is_signer = false;
|
||||
|
||||
assert_ix_error(
|
||||
&mut context,
|
||||
ix,
|
||||
None,
|
||||
InstructionError::MissingRequiredSignature,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_freeze_empty_lookup_table() {
|
||||
let mut context = setup_test_context().await;
|
||||
|
||||
let 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 ix = freeze_lookup_table(lookup_table_address, authority.pubkey());
|
||||
|
||||
assert_ix_error(
|
||||
&mut context,
|
||||
ix,
|
||||
Some(&authority),
|
||||
InstructionError::InvalidInstructionData,
|
||||
)
|
||||
.await;
|
||||
}
|
Reference in New Issue
Block a user