Add address lookup table program (backport #21616) (#21789)

* 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:
mergify[bot]
2021-12-11 05:26:46 +00:00
committed by GitHub
parent 7782d34bbf
commit 5bf4445ae6
21 changed files with 1665 additions and 4 deletions

View File

@ -0,0 +1,33 @@
[package]
name = "solana-address-lookup-table-program"
version = "1.9.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.9.0" }
solana-frozen-abi-macro = { path = "../../frozen-abi/macro", version = "=1.9.0" }
solana-program-runtime = { path = "../../program-runtime", version = "=1.9.0" }
solana-sdk = { path = "../../sdk", version = "=1.9.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);
}
}
}