Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make payer and system program optional when extending lookup tables #23678

Merged
merged 1 commit into from
Mar 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use {
},
solana_program_test::*,
solana_sdk::{
account::ReadableAccount,
account::{ReadableAccount, WritableAccount},
clock::Clock,
instruction::{Instruction, InstructionError},
pubkey::{Pubkey, PUBKEY_BYTES},
signature::{Keypair, Signer},
Expand Down Expand Up @@ -111,7 +112,7 @@ async fn test_extend_lookup_table() {
let instruction = extend_lookup_table(
lookup_table_address,
authority.pubkey(),
context.payer.pubkey(),
Some(context.payer.pubkey()),
new_addresses.clone(),
);

Expand Down Expand Up @@ -169,7 +170,7 @@ async fn test_extend_lookup_table_with_wrong_authority() {
let ix = extend_lookup_table(
lookup_table_address,
wrong_authority.pubkey(),
context.payer.pubkey(),
Some(context.payer.pubkey()),
new_addresses,
);

Expand All @@ -195,7 +196,7 @@ async fn test_extend_lookup_table_without_signing() {
let mut ix = extend_lookup_table(
lookup_table_address,
authority.pubkey(),
context.payer.pubkey(),
Some(context.payer.pubkey()),
new_addresses,
);
ix.accounts[1].is_signer = false;
Expand Down Expand Up @@ -226,7 +227,7 @@ async fn test_extend_deactivated_lookup_table() {
let ix = extend_lookup_table(
lookup_table_address,
authority.pubkey(),
context.payer.pubkey(),
Some(context.payer.pubkey()),
new_addresses,
);

Expand All @@ -252,7 +253,7 @@ async fn test_extend_immutable_lookup_table() {
let ix = extend_lookup_table(
lookup_table_address,
authority.pubkey(),
context.payer.pubkey(),
Some(context.payer.pubkey()),
new_addresses,
);

Expand All @@ -264,3 +265,83 @@ async fn test_extend_immutable_lookup_table() {
)
.await;
}

#[tokio::test]
async fn test_extend_lookup_table_without_payer() {
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 new_addresses = vec![Pubkey::new_unique()];
let ix = extend_lookup_table(
lookup_table_address,
authority.pubkey(),
None,
new_addresses,
);

assert_ix_error(
&mut context,
ix,
Some(&authority),
InstructionError::NotEnoughAccountKeys,
)
.await;
}

#[tokio::test]
async fn test_extend_prepaid_lookup_table_without_payer() {
let mut context = setup_test_context().await;

let authority = Keypair::new();
let lookup_table_address = Pubkey::new_unique();

let expected_state = {
// initialize lookup table
let empty_lookup_table = new_address_lookup_table(Some(authority.pubkey()), 0);
let mut lookup_table_account =
add_lookup_table_account(&mut context, lookup_table_address, empty_lookup_table).await;

// calculate required rent exempt balance for adding one address
let mut temp_lookup_table = new_address_lookup_table(Some(authority.pubkey()), 1);
let data = temp_lookup_table.clone().serialize_for_tests().unwrap();
let rent = context.banks_client.get_rent().await.unwrap();
let rent_exempt_balance = rent.minimum_balance(data.len());

// prepay for one address
lookup_table_account.set_lamports(rent_exempt_balance);
context.set_account(&lookup_table_address, &lookup_table_account);

// test will extend table in the current bank's slot
let clock = context.banks_client.get_sysvar::<Clock>().await.unwrap();
temp_lookup_table.meta.last_extended_slot = clock.slot;

ExpectedTableAccount {
lamports: rent_exempt_balance,
data_len: data.len(),
state: temp_lookup_table,
}
};

let new_addresses = expected_state.state.addresses.to_vec();
let instruction = extend_lookup_table(
lookup_table_address,
authority.pubkey(),
None,
new_addresses,
);

run_test_case(
&mut context,
TestCase {
lookup_table_address,
instruction,
extra_signer: Some(&authority),
expected_result: Ok(expected_state),
},
)
.await;
}
30 changes: 20 additions & 10 deletions programs/address-lookup-table/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,16 @@ pub enum ProgramInstruction {
/// 1. `[SIGNER]` Current authority
FreezeLookupTable,

/// Extend an address lookup table with new addresses
/// Extend an address lookup table with new addresses. Funding account and
/// system program account references are only required if the lookup table
/// account requires additional lamports to cover the rent-exempt balance
/// after being extended.
///
/// # 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.
/// 2. `[SIGNER, WRITE, OPTIONAL]` Account that will fund the table reallocation
/// 3. `[OPTIONAL]` System program for CPI.
ExtendLookupTable { new_addresses: Vec<Pubkey> },

/// Deactivate an address lookup table, making it unusable and
Expand Down Expand Up @@ -120,18 +123,25 @@ pub fn freeze_lookup_table(lookup_table_address: Pubkey, authority_address: Pubk
pub fn extend_lookup_table(
lookup_table_address: Pubkey,
authority_address: Pubkey,
payer_address: Pubkey,
payer_address: Option<Pubkey>,
new_addresses: Vec<Pubkey>,
) -> Instruction {
let mut accounts = vec![
AccountMeta::new(lookup_table_address, false),
AccountMeta::new_readonly(authority_address, true),
];

if let Some(payer_address) = payer_address {
accounts.extend([
AccountMeta::new(payer_address, true),
AccountMeta::new_readonly(system_program::id(), false),
]);
}

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),
],
accounts,
)
}

Expand Down
18 changes: 9 additions & 9 deletions programs/address-lookup-table/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,15 +225,6 @@ impl Processor {
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)?;
Expand Down Expand Up @@ -315,6 +306,15 @@ impl Processor {

let table_key = *lookup_table_account.unsigned_key();
if required_lamports > 0 {
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);
};

invoke_context.native_invoke(
system_instruction::transfer(&payer_key, &table_key, required_lamports),
&[payer_key],
Expand Down