Add address lookup table program (#21616)

* Add address lookup table program

* feedback
This commit is contained in:
Justin Starry 2021-12-10 16:02:16 -05:00 committed by GitHub
parent a5a0dabe7b
commit 9b41ddd9ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1665 additions and 4 deletions

30
Cargo.lock generated
View File

@ -4459,6 +4459,35 @@ dependencies = [
"tokio-postgres", "tokio-postgres",
] ]
[[package]]
name = "solana-address-lookup-table-program"
version = "1.10.0"
dependencies = [
"bincode",
"bytemuck",
"log 0.4.14",
"num-derive",
"num-traits",
"rustc_version 0.4.0",
"serde",
"solana-frozen-abi 1.10.0",
"solana-frozen-abi-macro 1.10.0",
"solana-program-runtime",
"solana-sdk",
"thiserror",
]
[[package]]
name = "solana-address-lookup-table-program-tests"
version = "1.10.0"
dependencies = [
"assert_matches",
"bincode",
"solana-address-lookup-table-program",
"solana-program-test",
"solana-sdk",
]
[[package]] [[package]]
name = "solana-banking-bench" name = "solana-banking-bench"
version = "1.10.0" version = "1.10.0"
@ -5714,6 +5743,7 @@ dependencies = [
"rustc_version 0.4.0", "rustc_version 0.4.0",
"serde", "serde",
"serde_derive", "serde_derive",
"solana-address-lookup-table-program",
"solana-bucket-map", "solana-bucket-map",
"solana-compute-budget-program", "solana-compute-budget-program",
"solana-config-program", "solana-config-program",

View File

@ -46,6 +46,8 @@ members = [
"poh", "poh",
"poh-bench", "poh-bench",
"program-test", "program-test",
"programs/address-lookup-table",
"programs/address-lookup-table-tests",
"programs/bpf_loader", "programs/bpf_loader",
"programs/compute-budget", "programs/compute-budget",
"programs/config", "programs/config",

View File

@ -41,7 +41,7 @@ use {
sysvar::{ sysvar::{
clock, epoch_schedule, clock, epoch_schedule,
fees::{self}, fees::{self},
rent, Sysvar, rent, Sysvar, SysvarId,
}, },
}, },
solana_vote_program::vote_state::{VoteState, VoteStateVersions}, solana_vote_program::vote_state::{VoteState, VoteStateVersions},
@ -1045,6 +1045,18 @@ impl ProgramTestContext {
bank.store_account(address, account); 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 /// Force the working bank ahead to a new slot
pub fn warp_to_slot(&mut self, warp_slot: Slot) -> Result<(), ProgramTestError> { pub fn warp_to_slot(&mut self, warp_slot: Slot) -> Result<(), ProgramTestError> {
let mut bank_forks = self.bank_forks.write().unwrap(); let mut bank_forks = self.bank_forks.write().unwrap();

View 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.10.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.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"]

View File

@ -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;
}

View 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);
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -0,0 +1,33 @@
[package]
name = "solana-address-lookup-table-program"
version = "1.10.0"
description = "Solana address lookup table program"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
documentation = "https://docs.rs/solana-address-loookup-table-program"
edition = "2021"
[dependencies]
bincode = "1.3.3"
bytemuck = "1.7.2"
log = "0.4.14"
num-derive = "0.3"
num-traits = "0.2"
serde = { version = "1.0.127", features = ["derive"] }
solana-frozen-abi = { path = "../../frozen-abi", version = "=1.10.0" }
solana-frozen-abi-macro = { path = "../../frozen-abi/macro", version = "=1.10.0" }
solana-program-runtime = { path = "../../program-runtime", version = "=1.10.0" }
solana-sdk = { path = "../../sdk", version = "=1.10.0" }
thiserror = "1.0"
[build-dependencies]
rustc_version = "0.4"
[lib]
crate-type = ["lib"]
name = "solana_address_lookup_table_program"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -0,0 +1 @@
../../frozen-abi/build.rs

View File

@ -0,0 +1,147 @@
use {
crate::id,
serde::{Deserialize, Serialize},
solana_sdk::{
clock::Slot,
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
system_program,
},
};
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum ProgramInstruction {
/// Create an address lookup table
///
/// # Account references
/// 0. `[WRITE]` Uninitialized address lookup table account
/// 1. `[SIGNER]` Account used to derive and control the new address lookup table.
/// 2. `[SIGNER, WRITE]` Account that will fund the new address lookup table.
/// 3. `[]` System program for CPI.
CreateLookupTable {
/// A recent slot must be used in the derivation path
/// for each initialized table. When closing table accounts,
/// the initialization slot must no longer be "recent" to prevent
/// address tables from being recreated with reordered or
/// otherwise malicious addresses.
recent_slot: Slot,
/// Address tables are always initialized at program-derived
/// addresses using the funding address, recent blockhash, and
/// the user-passed `bump_seed`.
bump_seed: u8,
},
/// Permanently freeze a address lookup table, making it immutable.
///
/// # Account references
/// 0. `[WRITE]` Address lookup table account to freeze
/// 1. `[SIGNER]` Current authority
FreezeLookupTable,
/// Extend an address lookup table with new addresses
///
/// # Account references
/// 0. `[WRITE]` Address lookup table account to extend
/// 1. `[SIGNER]` Current authority
/// 2. `[SIGNER, WRITE]` Account that will fund the table reallocation
/// 3. `[]` System program for CPI.
ExtendLookupTable { new_addresses: Vec<Pubkey> },
/// Close an address lookup table account
///
/// # Account references
/// 0. `[WRITE]` Address lookup table account to close
/// 1. `[SIGNER]` Current authority
/// 2. `[WRITE]` Recipient of closed account lamports
CloseLookupTable,
}
/// Derives the address of an address table account from a wallet address and a recent block's slot.
pub fn derive_lookup_table_address(
authority_address: &Pubkey,
recent_block_slot: Slot,
) -> (Pubkey, u8) {
Pubkey::find_program_address(
&[authority_address.as_ref(), &recent_block_slot.to_le_bytes()],
&id(),
)
}
/// Constructs an instruction to create a table account and returns
/// the instruction and the table account's derived address.
pub fn create_lookup_table(
authority_address: Pubkey,
payer_address: Pubkey,
recent_slot: Slot,
) -> (Instruction, Pubkey) {
let (lookup_table_address, bump_seed) =
derive_lookup_table_address(&authority_address, recent_slot);
let instruction = Instruction::new_with_bincode(
id(),
&ProgramInstruction::CreateLookupTable {
recent_slot,
bump_seed,
},
vec![
AccountMeta::new(lookup_table_address, false),
AccountMeta::new_readonly(authority_address, true),
AccountMeta::new(payer_address, true),
AccountMeta::new_readonly(system_program::id(), false),
],
);
(instruction, lookup_table_address)
}
/// Constructs an instruction that freezes an address lookup
/// table so that it can never be closed or extended again. Empty
/// lookup tables cannot be frozen.
pub fn freeze_lookup_table(lookup_table_address: Pubkey, authority_address: Pubkey) -> Instruction {
Instruction::new_with_bincode(
id(),
&ProgramInstruction::FreezeLookupTable,
vec![
AccountMeta::new(lookup_table_address, false),
AccountMeta::new_readonly(authority_address, true),
],
)
}
/// Constructs an instruction which extends an address lookup
/// table account with new addresses.
pub fn extend_lookup_table(
lookup_table_address: Pubkey,
authority_address: Pubkey,
payer_address: Pubkey,
new_addresses: Vec<Pubkey>,
) -> Instruction {
Instruction::new_with_bincode(
id(),
&ProgramInstruction::ExtendLookupTable { new_addresses },
vec![
AccountMeta::new(lookup_table_address, false),
AccountMeta::new_readonly(authority_address, true),
AccountMeta::new(payer_address, true),
AccountMeta::new_readonly(system_program::id(), false),
],
)
}
/// Returns an instruction that closes an address lookup table
/// account. The account will be deallocated and the lamports
/// will be drained to the recipient address.
pub fn close_lookup_table(
lookup_table_address: Pubkey,
authority_address: Pubkey,
recipient_address: Pubkey,
) -> Instruction {
Instruction::new_with_bincode(
id(),
&ProgramInstruction::CloseLookupTable,
vec![
AccountMeta::new(lookup_table_address, false),
AccountMeta::new_readonly(authority_address, true),
AccountMeta::new(recipient_address, false),
],
)
}

View File

@ -0,0 +1,11 @@
#![allow(incomplete_features)]
#![cfg_attr(RUSTC_WITH_SPECIALIZATION, feature(specialization))]
#![cfg_attr(RUSTC_NEEDS_PROC_MACRO_HYGIENE, feature(proc_macro_hygiene))]
use solana_sdk::declare_id;
pub mod instruction;
pub mod processor;
pub mod state;
declare_id!("AddressLookupTab1e1111111111111111111111111");

View File

@ -0,0 +1,388 @@
use {
crate::{
instruction::ProgramInstruction,
state::{
AddressLookupTable, LookupTableMeta, ProgramState, LOOKUP_TABLE_MAX_ADDRESSES,
LOOKUP_TABLE_META_SIZE,
},
},
solana_program_runtime::{ic_msg, invoke_context::InvokeContext},
solana_sdk::{
account::{ReadableAccount, WritableAccount},
account_utils::State,
clock::Slot,
instruction::InstructionError,
keyed_account::keyed_account_at_index,
program_utils::limited_deserialize,
pubkey::{Pubkey, PUBKEY_BYTES},
slot_hashes::{SlotHashes, MAX_ENTRIES},
system_instruction,
sysvar::{
clock::{self, Clock},
rent::{self, Rent},
slot_hashes,
},
},
std::convert::TryFrom,
};
pub fn process_instruction(
first_instruction_account: usize,
instruction_data: &[u8],
invoke_context: &mut InvokeContext,
) -> Result<(), InstructionError> {
match limited_deserialize(instruction_data)? {
ProgramInstruction::CreateLookupTable {
recent_slot,
bump_seed,
} => Processor::create_lookup_table(
invoke_context,
first_instruction_account,
recent_slot,
bump_seed,
),
ProgramInstruction::FreezeLookupTable => {
Processor::freeze_lookup_table(invoke_context, first_instruction_account)
}
ProgramInstruction::ExtendLookupTable { new_addresses } => {
Processor::extend_lookup_table(invoke_context, first_instruction_account, new_addresses)
}
ProgramInstruction::CloseLookupTable => {
Processor::close_lookup_table(invoke_context, first_instruction_account)
}
}
}
fn checked_add(a: usize, b: usize) -> Result<usize, InstructionError> {
a.checked_add(b).ok_or(InstructionError::ArithmeticOverflow)
}
pub struct Processor;
impl Processor {
fn create_lookup_table(
invoke_context: &mut InvokeContext,
first_instruction_account: usize,
untrusted_recent_slot: Slot,
bump_seed: u8,
) -> Result<(), InstructionError> {
let keyed_accounts = invoke_context.get_keyed_accounts()?;
let lookup_table_account =
keyed_account_at_index(keyed_accounts, first_instruction_account)?;
if lookup_table_account.data_len()? > 0 {
ic_msg!(invoke_context, "Table account must not be allocated");
return Err(InstructionError::AccountAlreadyInitialized);
}
let authority_account =
keyed_account_at_index(keyed_accounts, checked_add(first_instruction_account, 1)?)?;
let authority_key = *authority_account.signer_key().ok_or_else(|| {
ic_msg!(invoke_context, "Authority account must be a signer");
InstructionError::MissingRequiredSignature
})?;
let payer_account =
keyed_account_at_index(keyed_accounts, checked_add(first_instruction_account, 2)?)?;
let payer_key = *payer_account.signer_key().ok_or_else(|| {
ic_msg!(invoke_context, "Payer account must be a signer");
InstructionError::MissingRequiredSignature
})?;
let derivation_slot = {
let slot_hashes: SlotHashes = invoke_context.get_sysvar(&slot_hashes::id())?;
if slot_hashes.get(&untrusted_recent_slot).is_some() {
Ok(untrusted_recent_slot)
} else {
ic_msg!(
invoke_context,
"{} is not a recent slot",
untrusted_recent_slot
);
Err(InstructionError::InvalidInstructionData)
}
}?;
// Use a derived address to ensure that an address table can never be
// initialized more than once at the same address.
let derived_table_key = Pubkey::create_program_address(
&[
authority_key.as_ref(),
&derivation_slot.to_le_bytes(),
&[bump_seed],
],
&crate::id(),
)?;
let table_key = *lookup_table_account.unsigned_key();
if table_key != derived_table_key {
ic_msg!(
invoke_context,
"Table address must match derived address: {}",
derived_table_key
);
return Err(InstructionError::InvalidArgument);
}
let table_account_data_len = LOOKUP_TABLE_META_SIZE;
let rent: Rent = invoke_context.get_sysvar(&rent::id())?;
let required_lamports = rent
.minimum_balance(table_account_data_len)
.max(1)
.saturating_sub(lookup_table_account.lamports()?);
if required_lamports > 0 {
invoke_context.native_invoke(
system_instruction::transfer(&payer_key, &table_key, required_lamports),
&[payer_key],
)?;
}
invoke_context.native_invoke(
system_instruction::allocate(&table_key, table_account_data_len as u64),
&[table_key],
)?;
invoke_context.native_invoke(
system_instruction::assign(&table_key, &crate::id()),
&[table_key],
)?;
let keyed_accounts = invoke_context.get_keyed_accounts()?;
let lookup_table_account =
keyed_account_at_index(keyed_accounts, first_instruction_account)?;
lookup_table_account.set_state(&ProgramState::LookupTable(LookupTableMeta::new(
authority_key,
derivation_slot,
)))?;
Ok(())
}
fn freeze_lookup_table(
invoke_context: &mut InvokeContext,
first_instruction_account: usize,
) -> Result<(), InstructionError> {
let keyed_accounts = invoke_context.get_keyed_accounts()?;
let lookup_table_account =
keyed_account_at_index(keyed_accounts, first_instruction_account)?;
if lookup_table_account.owner()? != crate::id() {
return Err(InstructionError::InvalidAccountOwner);
}
let authority_account =
keyed_account_at_index(keyed_accounts, checked_add(first_instruction_account, 1)?)?;
if authority_account.signer_key().is_none() {
return Err(InstructionError::MissingRequiredSignature);
}
let lookup_table_account_ref = lookup_table_account.try_account_ref()?;
let lookup_table_data = lookup_table_account_ref.data();
let lookup_table = AddressLookupTable::deserialize(lookup_table_data)?;
if lookup_table.meta.authority.is_none() {
ic_msg!(invoke_context, "Lookup table is already frozen");
return Err(InstructionError::Immutable);
}
if lookup_table.meta.authority != Some(*authority_account.unsigned_key()) {
return Err(InstructionError::IncorrectAuthority);
}
if lookup_table.addresses.is_empty() {
ic_msg!(invoke_context, "Empty lookup tables cannot be frozen");
return Err(InstructionError::InvalidInstructionData);
}
let mut lookup_table_meta = lookup_table.meta;
drop(lookup_table_account_ref);
lookup_table_meta.authority = None;
AddressLookupTable::overwrite_meta_data(
lookup_table_account
.try_account_ref_mut()?
.data_as_mut_slice(),
lookup_table_meta,
)?;
Ok(())
}
fn extend_lookup_table(
invoke_context: &mut InvokeContext,
first_instruction_account: usize,
new_addresses: Vec<Pubkey>,
) -> Result<(), InstructionError> {
let keyed_accounts = invoke_context.get_keyed_accounts()?;
let lookup_table_account =
keyed_account_at_index(keyed_accounts, first_instruction_account)?;
if lookup_table_account.owner()? != crate::id() {
return Err(InstructionError::InvalidAccountOwner);
}
let authority_account =
keyed_account_at_index(keyed_accounts, checked_add(first_instruction_account, 1)?)?;
if authority_account.signer_key().is_none() {
return Err(InstructionError::MissingRequiredSignature);
}
let payer_account =
keyed_account_at_index(keyed_accounts, checked_add(first_instruction_account, 2)?)?;
let payer_key = if let Some(payer_key) = payer_account.signer_key() {
*payer_key
} else {
ic_msg!(invoke_context, "Payer account must be a signer");
return Err(InstructionError::MissingRequiredSignature);
};
let lookup_table_account_ref = lookup_table_account.try_account_ref()?;
let lookup_table_data = lookup_table_account_ref.data();
let mut lookup_table = AddressLookupTable::deserialize(lookup_table_data)?;
if lookup_table.meta.authority.is_none() {
return Err(InstructionError::Immutable);
}
if lookup_table.meta.authority != Some(*authority_account.unsigned_key()) {
return Err(InstructionError::IncorrectAuthority);
}
if lookup_table.addresses.len() >= LOOKUP_TABLE_MAX_ADDRESSES {
ic_msg!(
invoke_context,
"Lookup table is full and cannot contain more addresses"
);
return Err(InstructionError::InvalidArgument);
}
if new_addresses.is_empty() {
ic_msg!(invoke_context, "Must extend with at least one address");
return Err(InstructionError::InvalidInstructionData);
}
let new_table_addresses_len = lookup_table
.addresses
.len()
.saturating_add(new_addresses.len());
if new_table_addresses_len > LOOKUP_TABLE_MAX_ADDRESSES {
ic_msg!(
invoke_context,
"Extended lookup table length {} would exceed max capacity of {}",
new_table_addresses_len,
LOOKUP_TABLE_MAX_ADDRESSES
);
return Err(InstructionError::InvalidInstructionData);
}
let clock: Clock = invoke_context.get_sysvar(&clock::id())?;
if clock.slot != lookup_table.meta.last_extended_slot {
lookup_table.meta.last_extended_slot = clock.slot;
lookup_table.meta.last_extended_slot_start_index =
u8::try_from(lookup_table.addresses.len()).map_err(|_| {
// This is impossible as long as the length of new_addresses
// is non-zero and LOOKUP_TABLE_MAX_ADDRESSES == u8::MAX + 1.
InstructionError::InvalidAccountData
})?;
}
let lookup_table_meta = lookup_table.meta;
drop(lookup_table_account_ref);
let new_table_data_len = checked_add(
LOOKUP_TABLE_META_SIZE,
new_table_addresses_len.saturating_mul(PUBKEY_BYTES),
)?;
{
let mut lookup_table_account_ref_mut = lookup_table_account.try_account_ref_mut()?;
AddressLookupTable::overwrite_meta_data(
lookup_table_account_ref_mut.data_as_mut_slice(),
lookup_table_meta,
)?;
let table_data = lookup_table_account_ref_mut.data_mut();
for new_address in new_addresses {
table_data.extend_from_slice(new_address.as_ref());
}
}
let rent: Rent = invoke_context.get_sysvar(&rent::id())?;
let required_lamports = rent
.minimum_balance(new_table_data_len)
.max(1)
.saturating_sub(lookup_table_account.lamports()?);
let table_key = *lookup_table_account.unsigned_key();
if required_lamports > 0 {
invoke_context.native_invoke(
system_instruction::transfer(&payer_key, &table_key, required_lamports),
&[payer_key],
)?;
}
Ok(())
}
fn close_lookup_table(
invoke_context: &mut InvokeContext,
first_instruction_account: usize,
) -> Result<(), InstructionError> {
let keyed_accounts = invoke_context.get_keyed_accounts()?;
let lookup_table_account =
keyed_account_at_index(keyed_accounts, first_instruction_account)?;
if lookup_table_account.owner()? != crate::id() {
return Err(InstructionError::InvalidAccountOwner);
}
let authority_account =
keyed_account_at_index(keyed_accounts, checked_add(first_instruction_account, 1)?)?;
if authority_account.signer_key().is_none() {
return Err(InstructionError::MissingRequiredSignature);
}
let recipient_account =
keyed_account_at_index(keyed_accounts, checked_add(first_instruction_account, 2)?)?;
if recipient_account.unsigned_key() == lookup_table_account.unsigned_key() {
ic_msg!(
invoke_context,
"Lookup table cannot be the recipient of reclaimed lamports"
);
return Err(InstructionError::InvalidArgument);
}
let lookup_table_account_ref = lookup_table_account.try_account_ref()?;
let lookup_table_data = lookup_table_account_ref.data();
let lookup_table = AddressLookupTable::deserialize(lookup_table_data)?;
if lookup_table.meta.authority.is_none() {
return Err(InstructionError::Immutable);
}
if lookup_table.meta.authority != Some(*authority_account.unsigned_key()) {
return Err(InstructionError::IncorrectAuthority);
}
// Assert that the slot used in the derivation path of the lookup table address
// is no longer recent and can't be reused to initialize an account at the same address.
let slot_hashes: SlotHashes = invoke_context.get_sysvar(&slot_hashes::id())?;
if let Some(position) = slot_hashes.position(&lookup_table.meta.derivation_slot) {
let expiration = MAX_ENTRIES.saturating_sub(position);
ic_msg!(
invoke_context,
"Table cannot be closed until its derivation slot expires in {} blocks",
expiration
);
return Err(InstructionError::InvalidArgument);
}
drop(lookup_table_account_ref);
let withdrawn_lamports = lookup_table_account.lamports()?;
recipient_account
.try_account_ref_mut()?
.checked_add_lamports(withdrawn_lamports)?;
let mut lookup_table_account = lookup_table_account.try_account_ref_mut()?;
lookup_table_account.set_data(Vec::new());
lookup_table_account.set_lamports(0);
Ok(())
}
}

View File

@ -0,0 +1,198 @@
use {
serde::{Deserialize, Serialize},
solana_frozen_abi_macro::{AbiEnumVisitor, AbiExample},
solana_sdk::{clock::Slot, instruction::InstructionError, pubkey::Pubkey},
std::borrow::Cow,
};
/// The maximum number of addresses that a lookup table can hold
pub const LOOKUP_TABLE_MAX_ADDRESSES: usize = 256;
/// The serialized size of lookup table metadata
pub const LOOKUP_TABLE_META_SIZE: usize = 56;
/// Program account states
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, AbiExample, AbiEnumVisitor)]
#[allow(clippy::large_enum_variant)]
pub enum ProgramState {
/// Account is not initialized.
Uninitialized,
/// Initialized `LookupTable` account.
LookupTable(LookupTableMeta),
}
/// Address lookup table metadata
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone, AbiExample)]
pub struct LookupTableMeta {
/// The slot used to derive the table's address. The table cannot
/// be closed until the derivation slot is no longer "recent"
/// (not accessible in the `SlotHashes` sysvar).
pub derivation_slot: Slot,
/// The slot that the table was last extended. Address tables may
/// only be used to lookup addresses that were extended before
/// the current bank's slot.
pub last_extended_slot: Slot,
/// The start index where the table was last extended from during
/// the `last_extended_slot`.
pub last_extended_slot_start_index: u8,
/// Authority address which must sign for each modification.
pub authority: Option<Pubkey>,
// Padding to keep addresses 8-byte aligned
pub _padding: u16,
// Raw list of addresses follows this serialized structure in
// the account's data, starting from `LOOKUP_TABLE_META_SIZE`.
}
impl LookupTableMeta {
pub fn new(authority: Pubkey, derivation_slot: Slot) -> Self {
LookupTableMeta {
derivation_slot,
authority: Some(authority),
..LookupTableMeta::default()
}
}
}
#[derive(Debug, PartialEq, Clone, AbiExample)]
pub struct AddressLookupTable<'a> {
pub meta: LookupTableMeta,
pub addresses: Cow<'a, [Pubkey]>,
}
impl<'a> AddressLookupTable<'a> {
/// Serialize an address table's updated meta data and zero
/// any leftover bytes.
pub fn overwrite_meta_data(
data: &mut [u8],
lookup_table_meta: LookupTableMeta,
) -> Result<(), InstructionError> {
let meta_data = data
.get_mut(0..LOOKUP_TABLE_META_SIZE)
.ok_or(InstructionError::InvalidAccountData)?;
meta_data.fill(0);
bincode::serialize_into(meta_data, &ProgramState::LookupTable(lookup_table_meta))
.map_err(|_| InstructionError::GenericError)?;
Ok(())
}
/// Serialize an address table including its addresses
pub fn serialize_for_tests(self, data: &mut Vec<u8>) -> Result<(), InstructionError> {
data.resize(LOOKUP_TABLE_META_SIZE, 0);
Self::overwrite_meta_data(data, self.meta)?;
self.addresses.iter().for_each(|address| {
data.extend_from_slice(address.as_ref());
});
Ok(())
}
/// Efficiently deserialize an address table without allocating
/// for stored addresses.
pub fn deserialize(data: &'a [u8]) -> Result<AddressLookupTable<'a>, InstructionError> {
let program_state: ProgramState =
bincode::deserialize(data).map_err(|_| InstructionError::InvalidAccountData)?;
let meta = match program_state {
ProgramState::LookupTable(meta) => Ok(meta),
ProgramState::Uninitialized => Err(InstructionError::UninitializedAccount),
}?;
let raw_addresses_data = data.get(LOOKUP_TABLE_META_SIZE..).ok_or({
// Should be impossible because table accounts must
// always be LOOKUP_TABLE_META_SIZE in length
InstructionError::InvalidAccountData
})?;
let addresses: &[Pubkey] = bytemuck::try_cast_slice(raw_addresses_data).map_err(|_| {
// Should be impossible because raw address data
// should be aligned and sized in multiples of 32 bytes
InstructionError::InvalidAccountData
})?;
Ok(Self {
meta,
addresses: Cow::Borrowed(addresses),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
impl AddressLookupTable<'_> {
fn new_for_tests(meta: LookupTableMeta, num_addresses: usize) -> Self {
let mut addresses = Vec::with_capacity(num_addresses);
addresses.resize_with(num_addresses, Pubkey::new_unique);
AddressLookupTable {
meta,
addresses: Cow::Owned(addresses),
}
}
}
impl LookupTableMeta {
fn new_for_tests() -> Self {
Self {
authority: Some(Pubkey::new_unique()),
..LookupTableMeta::default()
}
}
}
#[test]
fn test_lookup_table_meta_size() {
let lookup_table = ProgramState::LookupTable(LookupTableMeta::new_for_tests());
let meta_size = bincode::serialized_size(&lookup_table).unwrap();
assert!(meta_size as usize <= LOOKUP_TABLE_META_SIZE);
assert_eq!(meta_size as usize, 56);
let lookup_table = ProgramState::LookupTable(LookupTableMeta::default());
let meta_size = bincode::serialized_size(&lookup_table).unwrap();
assert!(meta_size as usize <= LOOKUP_TABLE_META_SIZE);
assert_eq!(meta_size as usize, 24);
}
#[test]
fn test_overwrite_meta_data() {
let meta = LookupTableMeta::new_for_tests();
let empty_table = ProgramState::LookupTable(meta.clone());
let mut serialized_table_1 = bincode::serialize(&empty_table).unwrap();
serialized_table_1.resize(LOOKUP_TABLE_META_SIZE, 0);
let address_table = AddressLookupTable::new_for_tests(meta, 0);
let mut serialized_table_2 = Vec::new();
serialized_table_2.resize(LOOKUP_TABLE_META_SIZE, 0);
AddressLookupTable::overwrite_meta_data(&mut serialized_table_2, address_table.meta)
.unwrap();
assert_eq!(serialized_table_1, serialized_table_2);
}
#[test]
fn test_deserialize() {
assert_eq!(
AddressLookupTable::deserialize(&[]).err(),
Some(InstructionError::InvalidAccountData),
);
assert_eq!(
AddressLookupTable::deserialize(&[0u8; LOOKUP_TABLE_META_SIZE]).err(),
Some(InstructionError::UninitializedAccount),
);
fn test_case(num_addresses: usize) {
let lookup_table_meta = LookupTableMeta::new_for_tests();
let address_table = AddressLookupTable::new_for_tests(lookup_table_meta, num_addresses);
let mut address_table_data = Vec::new();
AddressLookupTable::serialize_for_tests(address_table.clone(), &mut address_table_data)
.unwrap();
assert_eq!(
AddressLookupTable::deserialize(&address_table_data).unwrap(),
address_table,
);
}
for case in [0, 1, 10, 255, 256] {
test_case(case);
}
}
}

View File

@ -2571,6 +2571,24 @@ dependencies = [
"zstd", "zstd",
] ]
[[package]]
name = "solana-address-lookup-table-program"
version = "1.10.0"
dependencies = [
"bincode",
"bytemuck",
"log",
"num-derive",
"num-traits",
"rustc_version 0.4.0",
"serde",
"solana-frozen-abi 1.10.0",
"solana-frozen-abi-macro 1.10.0",
"solana-program-runtime",
"solana-sdk",
"thiserror",
]
[[package]] [[package]]
name = "solana-banks-client" name = "solana-banks-client"
version = "1.10.0" version = "1.10.0"
@ -3415,6 +3433,7 @@ dependencies = [
"rustc_version 0.4.0", "rustc_version 0.4.0",
"serde", "serde",
"serde_derive", "serde_derive",
"solana-address-lookup-table-program",
"solana-bucket-map", "solana-bucket-map",
"solana-compute-budget-program", "solana-compute-budget-program",
"solana-config-program", "solana-config-program",

View File

@ -34,6 +34,7 @@ rayon = "1.5.1"
regex = "1.5.4" regex = "1.5.4"
serde = { version = "1.0.131", features = ["rc"] } serde = { version = "1.0.131", features = ["rc"] }
serde_derive = "1.0.103" serde_derive = "1.0.103"
solana-address-lookup-table-program = { path = "../programs/address-lookup-table", version = "=1.10.0" }
solana-config-program = { path = "../programs/config", version = "=1.10.0" } solana-config-program = { path = "../programs/config", version = "=1.10.0" }
solana-compute-budget-program = { path = "../programs/compute-budget", version = "=1.10.0" } solana-compute-budget-program = { path = "../programs/compute-budget", version = "=1.10.0" }
solana-frozen-abi = { path = "../frozen-abi", version = "=1.10.0" } solana-frozen-abi = { path = "../frozen-abi", version = "=1.10.0" }

View File

@ -121,7 +121,7 @@ use {
slot_hashes::SlotHashes, slot_hashes::SlotHashes,
slot_history::SlotHistory, slot_history::SlotHistory,
system_transaction, system_transaction,
sysvar::{self}, sysvar::{self, Sysvar, SysvarId},
timing::years_as_slots, timing::years_as_slots,
transaction::{ transaction::{
Result, SanitizedTransaction, Transaction, TransactionError, Result, SanitizedTransaction, Transaction, TransactionError,
@ -1898,6 +1898,18 @@ impl Bank {
}); });
} }
pub fn set_sysvar_for_tests<T>(&self, sysvar: &T)
where
T: Sysvar + SysvarId,
{
self.update_sysvar_account(&T::id(), |account| {
create_account(
sysvar,
self.inherit_specially_retained_account_fields(account),
)
});
}
fn update_slot_history(&self) { fn update_slot_history(&self) {
self.update_sysvar_account(&sysvar::slot_history::id(), |account| { self.update_sysvar_account(&sysvar::slot_history::id(), |account| {
let mut slot_history = account let mut slot_history = account

View File

@ -172,6 +172,15 @@ fn feature_builtins() -> Vec<(Builtin, Pubkey, ActivationType)> {
feature_set::prevent_calling_precompiles_as_programs::id(), feature_set::prevent_calling_precompiles_as_programs::id(),
ActivationType::RemoveProgram, ActivationType::RemoveProgram,
), ),
(
Builtin::new(
"address_lookup_table_program",
solana_address_lookup_table_program::id(),
solana_address_lookup_table_program::processor::process_instruction,
),
feature_set::versioned_tx_message_enabled::id(),
ActivationType::NewProgram,
),
] ]
} }

View File

@ -25,6 +25,9 @@ impl SlotHashes {
} }
(self.0).truncate(MAX_ENTRIES); (self.0).truncate(MAX_ENTRIES);
} }
pub fn position(&self, slot: &Slot) -> Option<usize> {
self.binary_search_by(|(probe, _)| slot.cmp(probe)).ok()
}
#[allow(clippy::trivially_copy_pass_by_ref)] #[allow(clippy::trivially_copy_pass_by_ref)]
pub fn get(&self, slot: &Slot) -> Option<&Hash> { pub fn get(&self, slot: &Slot) -> Option<&Hash> {
self.binary_search_by(|(probe, _)| slot.cmp(probe)) self.binary_search_by(|(probe, _)| slot.cmp(probe))

View File

@ -103,6 +103,7 @@ pub trait WritableAccount: ReadableAccount {
); );
Ok(()) Ok(())
} }
fn data_mut(&mut self) -> &mut Vec<u8>;
fn data_as_mut_slice(&mut self) -> &mut [u8]; fn data_as_mut_slice(&mut self) -> &mut [u8];
fn set_owner(&mut self, owner: Pubkey); fn set_owner(&mut self, owner: Pubkey);
fn copy_into_owner_from_slice(&mut self, source: &[u8]); fn copy_into_owner_from_slice(&mut self, source: &[u8]);
@ -156,6 +157,9 @@ impl WritableAccount for Account {
fn set_lamports(&mut self, lamports: u64) { fn set_lamports(&mut self, lamports: u64) {
self.lamports = lamports; self.lamports = lamports;
} }
fn data_mut(&mut self) -> &mut Vec<u8> {
&mut self.data
}
fn data_as_mut_slice(&mut self) -> &mut [u8] { fn data_as_mut_slice(&mut self) -> &mut [u8] {
&mut self.data &mut self.data
} }
@ -192,9 +196,11 @@ impl WritableAccount for AccountSharedData {
fn set_lamports(&mut self, lamports: u64) { fn set_lamports(&mut self, lamports: u64) {
self.lamports = lamports; self.lamports = lamports;
} }
fn data_mut(&mut self) -> &mut Vec<u8> {
Arc::make_mut(&mut self.data)
}
fn data_as_mut_slice(&mut self) -> &mut [u8] { fn data_as_mut_slice(&mut self) -> &mut [u8] {
let data = Arc::make_mut(&mut self.data); &mut self.data_mut()[..]
&mut data[..]
} }
fn set_owner(&mut self, owner: Pubkey) { fn set_owner(&mut self, owner: Pubkey) {
self.owner = owner; self.owner = owner;