Add address lookup table program (#21616)
* Add address lookup table program * feedback
This commit is contained in:
parent
a5a0dabe7b
commit
9b41ddd9ba
|
@ -4459,6 +4459,35 @@ dependencies = [
|
|||
"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]]
|
||||
name = "solana-banking-bench"
|
||||
version = "1.10.0"
|
||||
|
@ -5714,6 +5743,7 @@ dependencies = [
|
|||
"rustc_version 0.4.0",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"solana-address-lookup-table-program",
|
||||
"solana-bucket-map",
|
||||
"solana-compute-budget-program",
|
||||
"solana-config-program",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -41,7 +41,7 @@ use {
|
|||
sysvar::{
|
||||
clock, epoch_schedule,
|
||||
fees::{self},
|
||||
rent, Sysvar,
|
||||
rent, Sysvar, SysvarId,
|
||||
},
|
||||
},
|
||||
solana_vote_program::vote_state::{VoteState, VoteStateVersions},
|
||||
|
@ -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();
|
||||
|
|
|
@ -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"]
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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"]
|
|
@ -0,0 +1 @@
|
|||
../../frozen-abi/build.rs
|
|
@ -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),
|
||||
],
|
||||
)
|
||||
}
|
|
@ -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");
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2571,6 +2571,24 @@ dependencies = [
|
|||
"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]]
|
||||
name = "solana-banks-client"
|
||||
version = "1.10.0"
|
||||
|
@ -3415,6 +3433,7 @@ dependencies = [
|
|||
"rustc_version 0.4.0",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"solana-address-lookup-table-program",
|
||||
"solana-bucket-map",
|
||||
"solana-compute-budget-program",
|
||||
"solana-config-program",
|
||||
|
|
|
@ -34,6 +34,7 @@ rayon = "1.5.1"
|
|||
regex = "1.5.4"
|
||||
serde = { version = "1.0.131", features = ["rc"] }
|
||||
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-compute-budget-program = { path = "../programs/compute-budget", version = "=1.10.0" }
|
||||
solana-frozen-abi = { path = "../frozen-abi", version = "=1.10.0" }
|
||||
|
|
|
@ -121,7 +121,7 @@ use {
|
|||
slot_hashes::SlotHashes,
|
||||
slot_history::SlotHistory,
|
||||
system_transaction,
|
||||
sysvar::{self},
|
||||
sysvar::{self, Sysvar, SysvarId},
|
||||
timing::years_as_slots,
|
||||
transaction::{
|
||||
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) {
|
||||
self.update_sysvar_account(&sysvar::slot_history::id(), |account| {
|
||||
let mut slot_history = account
|
||||
|
|
|
@ -172,6 +172,15 @@ fn feature_builtins() -> Vec<(Builtin, Pubkey, ActivationType)> {
|
|||
feature_set::prevent_calling_precompiles_as_programs::id(),
|
||||
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,
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,9 @@ impl SlotHashes {
|
|||
}
|
||||
(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)]
|
||||
pub fn get(&self, slot: &Slot) -> Option<&Hash> {
|
||||
self.binary_search_by(|(probe, _)| slot.cmp(probe))
|
||||
|
|
|
@ -103,6 +103,7 @@ pub trait WritableAccount: ReadableAccount {
|
|||
);
|
||||
Ok(())
|
||||
}
|
||||
fn data_mut(&mut self) -> &mut Vec<u8>;
|
||||
fn data_as_mut_slice(&mut self) -> &mut [u8];
|
||||
fn set_owner(&mut self, owner: Pubkey);
|
||||
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) {
|
||||
self.lamports = lamports;
|
||||
}
|
||||
fn data_mut(&mut self) -> &mut Vec<u8> {
|
||||
&mut self.data
|
||||
}
|
||||
fn data_as_mut_slice(&mut self) -> &mut [u8] {
|
||||
&mut self.data
|
||||
}
|
||||
|
@ -192,9 +196,11 @@ impl WritableAccount for AccountSharedData {
|
|||
fn set_lamports(&mut self, lamports: u64) {
|
||||
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] {
|
||||
let data = Arc::make_mut(&mut self.data);
|
||||
&mut data[..]
|
||||
&mut self.data_mut()[..]
|
||||
}
|
||||
fn set_owner(&mut self, owner: Pubkey) {
|
||||
self.owner = owner;
|
||||
|
|
Loading…
Reference in New Issue