From eef847a25590e51b74588998cc71d697f6870ffe Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Wed, 16 Mar 2022 00:07:24 +0800 Subject: [PATCH] Make payer and system program optional when extending lookup tables --- .../tests/extend_lookup_table_ix.rs | 93 +++++++++++++++++-- .../address-lookup-table/src/instruction.rs | 30 ++++-- .../address-lookup-table/src/processor.rs | 18 ++-- 3 files changed, 116 insertions(+), 25 deletions(-) diff --git a/programs/address-lookup-table-tests/tests/extend_lookup_table_ix.rs b/programs/address-lookup-table-tests/tests/extend_lookup_table_ix.rs index ca88549fd3ba07..1bbc973c24f0cb 100644 --- a/programs/address-lookup-table-tests/tests/extend_lookup_table_ix.rs +++ b/programs/address-lookup-table-tests/tests/extend_lookup_table_ix.rs @@ -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}, @@ -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(), ); @@ -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, ); @@ -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; @@ -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, ); @@ -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, ); @@ -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::().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; +} diff --git a/programs/address-lookup-table/src/instruction.rs b/programs/address-lookup-table/src/instruction.rs index 5fca13b290e828..0777f40fa4aa02 100644 --- a/programs/address-lookup-table/src/instruction.rs +++ b/programs/address-lookup-table/src/instruction.rs @@ -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 }, /// Deactivate an address lookup table, making it unusable and @@ -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, new_addresses: Vec, ) -> 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, ) } diff --git a/programs/address-lookup-table/src/processor.rs b/programs/address-lookup-table/src/processor.rs index f5e810b8768253..8b52a121f120f5 100644 --- a/programs/address-lookup-table/src/processor.rs +++ b/programs/address-lookup-table/src/processor.rs @@ -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)?; @@ -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],