Add deactivation cooldown before address lookup tables can be closed (#22011)
This commit is contained in:
@ -21,9 +21,13 @@ async fn test_close_lookup_table() {
|
|||||||
let mut context = setup_test_context().await;
|
let mut context = setup_test_context().await;
|
||||||
overwrite_slot_hashes_with_slots(&mut context, &[]);
|
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();
|
let lookup_table_address = Pubkey::new_unique();
|
||||||
|
let authority_keypair = Keypair::new();
|
||||||
|
let initialized_table = {
|
||||||
|
let mut table = new_address_lookup_table(Some(authority_keypair.pubkey()), 0);
|
||||||
|
table.meta.deactivation_slot = 0;
|
||||||
|
table
|
||||||
|
};
|
||||||
add_lookup_table_account(&mut context, lookup_table_address, initialized_table).await;
|
add_lookup_table_account(&mut context, lookup_table_address, initialized_table).await;
|
||||||
|
|
||||||
let client = &mut context.banks_client;
|
let client = &mut context.banks_client;
|
||||||
@ -49,7 +53,7 @@ async fn test_close_lookup_table() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_close_lookup_table_too_recent() {
|
async fn test_close_lookup_table_not_deactivated() {
|
||||||
let mut context = setup_test_context().await;
|
let mut context = setup_test_context().await;
|
||||||
|
|
||||||
let authority_keypair = Keypair::new();
|
let authority_keypair = Keypair::new();
|
||||||
@ -63,10 +67,38 @@ async fn test_close_lookup_table_too_recent() {
|
|||||||
context.payer.pubkey(),
|
context.payer.pubkey(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// The ix should fail because the table hasn't been deactivated yet
|
||||||
|
assert_ix_error(
|
||||||
|
&mut context,
|
||||||
|
ix,
|
||||||
|
Some(&authority_keypair),
|
||||||
|
InstructionError::InvalidArgument,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_close_lookup_table_recently_deactivated() {
|
||||||
|
let mut context = setup_test_context().await;
|
||||||
|
|
||||||
|
let authority_keypair = Keypair::new();
|
||||||
|
let initialized_table = {
|
||||||
|
let mut table = new_address_lookup_table(Some(authority_keypair.pubkey()), 0);
|
||||||
|
table.meta.deactivation_slot = 0;
|
||||||
|
table
|
||||||
|
};
|
||||||
|
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
|
// Context sets up the slot hashes sysvar to have an entry
|
||||||
// for slot 0 which is what the default initialized table
|
// for slot 0 which is when the table was deactivated.
|
||||||
// has as its derivation slot. Because that slot is present,
|
// Because that slot is present, the ix should fail.
|
||||||
// the ix should fail.
|
|
||||||
assert_ix_error(
|
assert_ix_error(
|
||||||
&mut context,
|
&mut context,
|
||||||
ix,
|
ix,
|
||||||
|
@ -52,7 +52,7 @@ async fn test_create_lookup_table() {
|
|||||||
Rent::default().minimum_balance(LOOKUP_TABLE_META_SIZE)
|
Rent::default().minimum_balance(LOOKUP_TABLE_META_SIZE)
|
||||||
);
|
);
|
||||||
let lookup_table = AddressLookupTable::deserialize(&lookup_table_account.data).unwrap();
|
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.deactivation_slot, Slot::MAX);
|
||||||
assert_eq!(lookup_table.meta.authority, Some(authority_address));
|
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, 0);
|
||||||
assert_eq!(lookup_table.meta.last_extended_slot_start_index, 0);
|
assert_eq!(lookup_table.meta.last_extended_slot_start_index, 0);
|
||||||
|
@ -0,0 +1,145 @@
|
|||||||
|
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::deactivate_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_deactivate_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(
|
||||||
|
&[deactivate_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.deactivation_slot, 1);
|
||||||
|
|
||||||
|
// Check that only the deactivation slot changed
|
||||||
|
initialized_table.meta.deactivation_slot = 1;
|
||||||
|
assert_eq!(initialized_table, lookup_table);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_deactivate_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 = deactivate_lookup_table(lookup_table_address, authority.pubkey());
|
||||||
|
|
||||||
|
assert_ix_error(
|
||||||
|
&mut context,
|
||||||
|
ix,
|
||||||
|
Some(&authority),
|
||||||
|
InstructionError::Immutable,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_deactivate_already_deactivated() {
|
||||||
|
let mut context = setup_test_context().await;
|
||||||
|
|
||||||
|
let authority = Keypair::new();
|
||||||
|
let initialized_table = {
|
||||||
|
let mut table = new_address_lookup_table(Some(authority.pubkey()), 0);
|
||||||
|
table.meta.deactivation_slot = 0;
|
||||||
|
table
|
||||||
|
};
|
||||||
|
let lookup_table_address = Pubkey::new_unique();
|
||||||
|
add_lookup_table_account(&mut context, lookup_table_address, initialized_table).await;
|
||||||
|
|
||||||
|
let ix = deactivate_lookup_table(lookup_table_address, authority.pubkey());
|
||||||
|
|
||||||
|
assert_ix_error(
|
||||||
|
&mut context,
|
||||||
|
ix,
|
||||||
|
Some(&authority),
|
||||||
|
InstructionError::InvalidArgument,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_deactivate_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 = deactivate_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_deactivate_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 = deactivate_lookup_table(lookup_table_address, authority.pubkey());
|
||||||
|
ix.accounts[1].is_signer = false;
|
||||||
|
|
||||||
|
assert_ix_error(
|
||||||
|
&mut context,
|
||||||
|
ix,
|
||||||
|
None,
|
||||||
|
InstructionError::MissingRequiredSignature,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
use {
|
use {
|
||||||
assert_matches::assert_matches,
|
assert_matches::assert_matches,
|
||||||
common::{add_lookup_table_account, new_address_lookup_table, setup_test_context},
|
common::{
|
||||||
|
add_lookup_table_account, assert_ix_error, new_address_lookup_table, setup_test_context,
|
||||||
|
},
|
||||||
solana_address_lookup_table_program::{
|
solana_address_lookup_table_program::{
|
||||||
instruction::extend_lookup_table,
|
instruction::extend_lookup_table,
|
||||||
state::{AddressLookupTable, LookupTableMeta},
|
state::{AddressLookupTable, LookupTableMeta},
|
||||||
@ -130,7 +132,7 @@ async fn test_extend_lookup_table() {
|
|||||||
} else {
|
} else {
|
||||||
num_existing_addresses as u8
|
num_existing_addresses as u8
|
||||||
},
|
},
|
||||||
derivation_slot: lookup_table.meta.derivation_slot,
|
deactivation_slot: lookup_table.meta.deactivation_slot,
|
||||||
authority: lookup_table.meta.authority,
|
authority: lookup_table.meta.authority,
|
||||||
_padding: 0u16,
|
_padding: 0u16,
|
||||||
},
|
},
|
||||||
@ -156,59 +158,111 @@ async fn test_extend_lookup_table() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_extend_addresses_authority_errors() {
|
async fn test_extend_lookup_table_with_wrong_authority() {
|
||||||
let mut context = setup_test_context().await;
|
let mut context = setup_test_context().await;
|
||||||
|
|
||||||
let authority = Keypair::new();
|
let authority = Keypair::new();
|
||||||
|
let wrong_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;
|
||||||
|
|
||||||
for (existing_authority, ix_authority, use_signer, expected_err) in [
|
let new_addresses = vec![Pubkey::new_unique()];
|
||||||
(
|
let ix = extend_lookup_table(
|
||||||
Some(authority.pubkey()),
|
lookup_table_address,
|
||||||
Keypair::new(),
|
wrong_authority.pubkey(),
|
||||||
true,
|
context.payer.pubkey(),
|
||||||
InstructionError::IncorrectAuthority,
|
new_addresses,
|
||||||
),
|
);
|
||||||
(
|
|
||||||
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;
|
assert_ix_error(
|
||||||
let mut new_addresses = Vec::with_capacity(num_new_addresses);
|
&mut context,
|
||||||
new_addresses.resize_with(num_new_addresses, Pubkey::new_unique);
|
ix,
|
||||||
let mut instruction = extend_lookup_table(
|
Some(&wrong_authority),
|
||||||
lookup_table_address,
|
InstructionError::IncorrectAuthority,
|
||||||
ix_authority.pubkey(),
|
)
|
||||||
context.payer.pubkey(),
|
.await;
|
||||||
new_addresses.clone(),
|
}
|
||||||
);
|
|
||||||
if !use_signer {
|
#[tokio::test]
|
||||||
instruction.accounts[1].is_signer = false;
|
async fn test_extend_lookup_table_without_signing() {
|
||||||
}
|
let mut context = setup_test_context().await;
|
||||||
|
|
||||||
let mut expected_addresses: Vec<Pubkey> = lookup_table.addresses.to_vec();
|
let authority = Keypair::new();
|
||||||
expected_addresses.extend(new_addresses);
|
let initialized_table = new_address_lookup_table(Some(authority.pubkey()), 10);
|
||||||
|
let lookup_table_address = Pubkey::new_unique();
|
||||||
let extra_signer = if use_signer {
|
add_lookup_table_account(&mut context, lookup_table_address, initialized_table).await;
|
||||||
Some(&ix_authority)
|
|
||||||
} else {
|
let new_addresses = vec![Pubkey::new_unique()];
|
||||||
None
|
let mut ix = extend_lookup_table(
|
||||||
};
|
lookup_table_address,
|
||||||
|
authority.pubkey(),
|
||||||
let test_case = TestCase {
|
context.payer.pubkey(),
|
||||||
lookup_table_address,
|
new_addresses,
|
||||||
instruction,
|
);
|
||||||
extra_signer,
|
ix.accounts[1].is_signer = false;
|
||||||
expected_result: Err(expected_err),
|
|
||||||
};
|
assert_ix_error(
|
||||||
|
&mut context,
|
||||||
run_test_case(&mut context, test_case).await;
|
ix,
|
||||||
}
|
None,
|
||||||
|
InstructionError::MissingRequiredSignature,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_extend_deactivated_lookup_table() {
|
||||||
|
let mut context = setup_test_context().await;
|
||||||
|
|
||||||
|
let authority = Keypair::new();
|
||||||
|
let initialized_table = {
|
||||||
|
let mut table = new_address_lookup_table(Some(authority.pubkey()), 0);
|
||||||
|
table.meta.deactivation_slot = 0;
|
||||||
|
table
|
||||||
|
};
|
||||||
|
let lookup_table_address = Pubkey::new_unique();
|
||||||
|
add_lookup_table_account(&mut context, lookup_table_address, initialized_table).await;
|
||||||
|
|
||||||
|
let new_addresses = vec![Pubkey::new_unique()];
|
||||||
|
let ix = extend_lookup_table(
|
||||||
|
lookup_table_address,
|
||||||
|
authority.pubkey(),
|
||||||
|
context.payer.pubkey(),
|
||||||
|
new_addresses,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_ix_error(
|
||||||
|
&mut context,
|
||||||
|
ix,
|
||||||
|
Some(&authority),
|
||||||
|
InstructionError::InvalidArgument,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_extend_immutable_lookup_table() {
|
||||||
|
let mut context = setup_test_context().await;
|
||||||
|
|
||||||
|
let authority = Keypair::new();
|
||||||
|
let initialized_table = new_address_lookup_table(None, 1);
|
||||||
|
let lookup_table_address = Pubkey::new_unique();
|
||||||
|
add_lookup_table_account(&mut context, lookup_table_address, initialized_table).await;
|
||||||
|
|
||||||
|
let new_addresses = vec![Pubkey::new_unique()];
|
||||||
|
let ix = extend_lookup_table(
|
||||||
|
lookup_table_address,
|
||||||
|
authority.pubkey(),
|
||||||
|
context.payer.pubkey(),
|
||||||
|
new_addresses,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_ix_error(
|
||||||
|
&mut context,
|
||||||
|
ix,
|
||||||
|
Some(&authority),
|
||||||
|
InstructionError::Immutable,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,30 @@ async fn test_freeze_immutable_lookup_table() {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_freeze_deactivated_lookup_table() {
|
||||||
|
let mut context = setup_test_context().await;
|
||||||
|
|
||||||
|
let authority = Keypair::new();
|
||||||
|
let initialized_table = {
|
||||||
|
let mut table = new_address_lookup_table(Some(authority.pubkey()), 10);
|
||||||
|
table.meta.deactivation_slot = 0;
|
||||||
|
table
|
||||||
|
};
|
||||||
|
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::InvalidArgument,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_freeze_lookup_table_with_wrong_authority() {
|
async fn test_freeze_lookup_table_with_wrong_authority() {
|
||||||
let mut context = setup_test_context().await;
|
let mut context = setup_test_context().await;
|
||||||
|
@ -31,7 +31,7 @@ pub enum ProgramInstruction {
|
|||||||
bump_seed: u8,
|
bump_seed: u8,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Permanently freeze a address lookup table, making it immutable.
|
/// Permanently freeze an address lookup table, making it immutable.
|
||||||
///
|
///
|
||||||
/// # Account references
|
/// # Account references
|
||||||
/// 0. `[WRITE]` Address lookup table account to freeze
|
/// 0. `[WRITE]` Address lookup table account to freeze
|
||||||
@ -47,6 +47,14 @@ pub enum ProgramInstruction {
|
|||||||
/// 3. `[]` System program for CPI.
|
/// 3. `[]` System program for CPI.
|
||||||
ExtendLookupTable { new_addresses: Vec<Pubkey> },
|
ExtendLookupTable { new_addresses: Vec<Pubkey> },
|
||||||
|
|
||||||
|
/// Deactivate an address lookup table, making it unusable and
|
||||||
|
/// eligible for closure after a short period of time.
|
||||||
|
///
|
||||||
|
/// # Account references
|
||||||
|
/// 0. `[WRITE]` Address lookup table account to deactivate
|
||||||
|
/// 1. `[SIGNER]` Current authority
|
||||||
|
DeactivateLookupTable,
|
||||||
|
|
||||||
/// Close an address lookup table account
|
/// Close an address lookup table account
|
||||||
///
|
///
|
||||||
/// # Account references
|
/// # Account references
|
||||||
@ -127,6 +135,23 @@ pub fn extend_lookup_table(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Constructs an instruction that deactivates an address lookup
|
||||||
|
/// table so that it cannot be extended again and will be unusable
|
||||||
|
/// and eligible for closure after a short amount of time.
|
||||||
|
pub fn deactivate_lookup_table(
|
||||||
|
lookup_table_address: Pubkey,
|
||||||
|
authority_address: Pubkey,
|
||||||
|
) -> Instruction {
|
||||||
|
Instruction::new_with_bincode(
|
||||||
|
id(),
|
||||||
|
&ProgramInstruction::DeactivateLookupTable,
|
||||||
|
vec![
|
||||||
|
AccountMeta::new(lookup_table_address, false),
|
||||||
|
AccountMeta::new_readonly(authority_address, true),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns an instruction that closes an address lookup table
|
/// Returns an instruction that closes an address lookup table
|
||||||
/// account. The account will be deallocated and the lamports
|
/// account. The account will be deallocated and the lamports
|
||||||
/// will be drained to the recipient address.
|
/// will be drained to the recipient address.
|
||||||
|
@ -47,6 +47,9 @@ pub fn process_instruction(
|
|||||||
ProgramInstruction::ExtendLookupTable { new_addresses } => {
|
ProgramInstruction::ExtendLookupTable { new_addresses } => {
|
||||||
Processor::extend_lookup_table(invoke_context, first_instruction_account, new_addresses)
|
Processor::extend_lookup_table(invoke_context, first_instruction_account, new_addresses)
|
||||||
}
|
}
|
||||||
|
ProgramInstruction::DeactivateLookupTable => {
|
||||||
|
Processor::deactivate_lookup_table(invoke_context, first_instruction_account)
|
||||||
|
}
|
||||||
ProgramInstruction::CloseLookupTable => {
|
ProgramInstruction::CloseLookupTable => {
|
||||||
Processor::close_lookup_table(invoke_context, first_instruction_account)
|
Processor::close_lookup_table(invoke_context, first_instruction_account)
|
||||||
}
|
}
|
||||||
@ -152,7 +155,6 @@ impl Processor {
|
|||||||
keyed_account_at_index(keyed_accounts, first_instruction_account)?;
|
keyed_account_at_index(keyed_accounts, first_instruction_account)?;
|
||||||
lookup_table_account.set_state(&ProgramState::LookupTable(LookupTableMeta::new(
|
lookup_table_account.set_state(&ProgramState::LookupTable(LookupTableMeta::new(
|
||||||
authority_key,
|
authority_key,
|
||||||
derivation_slot,
|
|
||||||
)))?;
|
)))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -187,6 +189,10 @@ impl Processor {
|
|||||||
if lookup_table.meta.authority != Some(*authority_account.unsigned_key()) {
|
if lookup_table.meta.authority != Some(*authority_account.unsigned_key()) {
|
||||||
return Err(InstructionError::IncorrectAuthority);
|
return Err(InstructionError::IncorrectAuthority);
|
||||||
}
|
}
|
||||||
|
if lookup_table.meta.deactivation_slot != Slot::MAX {
|
||||||
|
ic_msg!(invoke_context, "Deactivated tables cannot be frozen");
|
||||||
|
return Err(InstructionError::InvalidArgument);
|
||||||
|
}
|
||||||
if lookup_table.addresses.is_empty() {
|
if lookup_table.addresses.is_empty() {
|
||||||
ic_msg!(invoke_context, "Empty lookup tables cannot be frozen");
|
ic_msg!(invoke_context, "Empty lookup tables cannot be frozen");
|
||||||
return Err(InstructionError::InvalidInstructionData);
|
return Err(InstructionError::InvalidInstructionData);
|
||||||
@ -244,6 +250,10 @@ impl Processor {
|
|||||||
if lookup_table.meta.authority != Some(*authority_account.unsigned_key()) {
|
if lookup_table.meta.authority != Some(*authority_account.unsigned_key()) {
|
||||||
return Err(InstructionError::IncorrectAuthority);
|
return Err(InstructionError::IncorrectAuthority);
|
||||||
}
|
}
|
||||||
|
if lookup_table.meta.deactivation_slot != Slot::MAX {
|
||||||
|
ic_msg!(invoke_context, "Deactivated tables cannot be extended");
|
||||||
|
return Err(InstructionError::InvalidArgument);
|
||||||
|
}
|
||||||
if lookup_table.addresses.len() >= LOOKUP_TABLE_MAX_ADDRESSES {
|
if lookup_table.addresses.len() >= LOOKUP_TABLE_MAX_ADDRESSES {
|
||||||
ic_msg!(
|
ic_msg!(
|
||||||
invoke_context,
|
invoke_context,
|
||||||
@ -320,6 +330,56 @@ impl Processor {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn deactivate_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 frozen");
|
||||||
|
return Err(InstructionError::Immutable);
|
||||||
|
}
|
||||||
|
if lookup_table.meta.authority != Some(*authority_account.unsigned_key()) {
|
||||||
|
return Err(InstructionError::IncorrectAuthority);
|
||||||
|
}
|
||||||
|
if lookup_table.meta.deactivation_slot != Slot::MAX {
|
||||||
|
ic_msg!(invoke_context, "Lookup table is already deactivated");
|
||||||
|
return Err(InstructionError::InvalidArgument);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut lookup_table_meta = lookup_table.meta;
|
||||||
|
drop(lookup_table_account_ref);
|
||||||
|
|
||||||
|
let clock: Clock = invoke_context.get_sysvar(&clock::id())?;
|
||||||
|
lookup_table_meta.deactivation_slot = clock.slot;
|
||||||
|
|
||||||
|
AddressLookupTable::overwrite_meta_data(
|
||||||
|
lookup_table_account
|
||||||
|
.try_account_ref_mut()?
|
||||||
|
.data_as_mut_slice(),
|
||||||
|
lookup_table_meta,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn close_lookup_table(
|
fn close_lookup_table(
|
||||||
invoke_context: &mut InvokeContext,
|
invoke_context: &mut InvokeContext,
|
||||||
first_instruction_account: usize,
|
first_instruction_account: usize,
|
||||||
@ -353,16 +413,24 @@ impl Processor {
|
|||||||
let lookup_table = AddressLookupTable::deserialize(lookup_table_data)?;
|
let lookup_table = AddressLookupTable::deserialize(lookup_table_data)?;
|
||||||
|
|
||||||
if lookup_table.meta.authority.is_none() {
|
if lookup_table.meta.authority.is_none() {
|
||||||
|
ic_msg!(invoke_context, "Lookup table is frozen");
|
||||||
return Err(InstructionError::Immutable);
|
return Err(InstructionError::Immutable);
|
||||||
}
|
}
|
||||||
if lookup_table.meta.authority != Some(*authority_account.unsigned_key()) {
|
if lookup_table.meta.authority != Some(*authority_account.unsigned_key()) {
|
||||||
return Err(InstructionError::IncorrectAuthority);
|
return Err(InstructionError::IncorrectAuthority);
|
||||||
}
|
}
|
||||||
|
if lookup_table.meta.deactivation_slot == Slot::MAX {
|
||||||
|
ic_msg!(invoke_context, "Lookup table is not deactivated");
|
||||||
|
return Err(InstructionError::InvalidArgument);
|
||||||
|
}
|
||||||
|
|
||||||
// Assert that the slot used in the derivation path of the lookup table address
|
// Assert that the deactivation slot is no longer recent to give in-flight transactions
|
||||||
// is no longer recent and can't be reused to initialize an account at the same address.
|
// enough time to land and to remove indeterminism caused by transactions loading
|
||||||
|
// addresses in the same slot when a table is closed. This enforced delay has a side
|
||||||
|
// effect of not allowing lookup tables to be recreated at the same derived address
|
||||||
|
// because tables must be created at an address derived from a recent slot.
|
||||||
let slot_hashes: SlotHashes = invoke_context.get_sysvar(&slot_hashes::id())?;
|
let slot_hashes: SlotHashes = invoke_context.get_sysvar(&slot_hashes::id())?;
|
||||||
if let Some(position) = slot_hashes.position(&lookup_table.meta.derivation_slot) {
|
if let Some(position) = slot_hashes.position(&lookup_table.meta.deactivation_slot) {
|
||||||
let expiration = MAX_ENTRIES.saturating_sub(position);
|
let expiration = MAX_ENTRIES.saturating_sub(position);
|
||||||
ic_msg!(
|
ic_msg!(
|
||||||
invoke_context,
|
invoke_context,
|
||||||
|
@ -22,12 +22,11 @@ pub enum ProgramState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Address lookup table metadata
|
/// Address lookup table metadata
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone, AbiExample)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, AbiExample)]
|
||||||
pub struct LookupTableMeta {
|
pub struct LookupTableMeta {
|
||||||
/// The slot used to derive the table's address. The table cannot
|
/// Lookup tables cannot be closed until the deactivation slot is
|
||||||
/// be closed until the derivation slot is no longer "recent"
|
/// no longer "recent" (not accessible in the `SlotHashes` sysvar).
|
||||||
/// (not accessible in the `SlotHashes` sysvar).
|
pub deactivation_slot: Slot,
|
||||||
pub derivation_slot: Slot,
|
|
||||||
/// The slot that the table was last extended. Address tables may
|
/// The slot that the table was last extended. Address tables may
|
||||||
/// only be used to lookup addresses that were extended before
|
/// only be used to lookup addresses that were extended before
|
||||||
/// the current bank's slot.
|
/// the current bank's slot.
|
||||||
@ -43,10 +42,21 @@ pub struct LookupTableMeta {
|
|||||||
// the account's data, starting from `LOOKUP_TABLE_META_SIZE`.
|
// the account's data, starting from `LOOKUP_TABLE_META_SIZE`.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for LookupTableMeta {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
deactivation_slot: Slot::MAX,
|
||||||
|
last_extended_slot: 0,
|
||||||
|
last_extended_slot_start_index: 0,
|
||||||
|
authority: None,
|
||||||
|
_padding: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl LookupTableMeta {
|
impl LookupTableMeta {
|
||||||
pub fn new(authority: Pubkey, derivation_slot: Slot) -> Self {
|
pub fn new(authority: Pubkey) -> Self {
|
||||||
LookupTableMeta {
|
LookupTableMeta {
|
||||||
derivation_slot,
|
|
||||||
authority: Some(authority),
|
authority: Some(authority),
|
||||||
..LookupTableMeta::default()
|
..LookupTableMeta::default()
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user