Skip to content

Commit

Permalink
Add address lookup table program (#21616)
Browse files Browse the repository at this point in the history
* Add address lookup table program

* feedback
  • Loading branch information
jstarry authored Dec 10, 2021
1 parent a5a0dab commit 9b41ddd
Show file tree
Hide file tree
Showing 21 changed files with 1,665 additions and 4 deletions.
30 changes: 30 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ members = [
"poh",
"poh-bench",
"program-test",
"programs/address-lookup-table",
"programs/address-lookup-table-tests",
"programs/bpf_loader",
"programs/compute-budget",
"programs/config",
Expand Down
14 changes: 13 additions & 1 deletion program-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ use {
sysvar::{
clock, epoch_schedule,
fees::{self},
rent, Sysvar,
rent, Sysvar, SysvarId,
},
},
solana_vote_program::vote_state::{VoteState, VoteStateVersions},
Expand Down Expand Up @@ -1045,6 +1045,18 @@ impl ProgramTestContext {
bank.store_account(address, account);
}

/// Create or overwrite a sysvar, subverting normal runtime checks.
///
/// This method exists to make it easier to set up artificial situations
/// that would be difficult to replicate on a new test cluster. Beware
/// that it can be used to create states that would not be reachable
/// under normal conditions!
pub fn set_sysvar<T: SysvarId + Sysvar>(&self, sysvar: &T) {
let bank_forks = self.bank_forks.read().unwrap();
let bank = bank_forks.working_bank();
bank.set_sysvar_for_tests(sysvar);
}

/// Force the working bank ahead to a new slot
pub fn warp_to_slot(&mut self, warp_slot: Slot) -> Result<(), ProgramTestError> {
let mut bank_forks = self.bank_forks.write().unwrap();
Expand Down
22 changes: 22 additions & 0 deletions programs/address-lookup-table-tests/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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.10.0"
authors = ["Solana Maintainers <[email protected]>"]
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.10.0" }
solana-program-test = { path = "../../program-test", version = "=1.10.0" }
solana-sdk = { path = "../../sdk", version = "=1.10.0" }

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
151 changes: 151 additions & 0 deletions programs/address-lookup-table-tests/tests/close_lookup_table_ix.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 103 additions & 0 deletions programs/address-lookup-table-tests/tests/common.rs
Original file line number Diff line number Diff line change
@@ -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);
}
Loading

0 comments on commit 9b41ddd

Please sign in to comment.