From e6bba4a20e41816a9f1f39c4e3b5836ba025c05e Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Sun, 26 Nov 2023 17:11:34 +0900 Subject: [PATCH 01/39] Added update function for ExtraAccountMetaList --- libraries/tlv-account-resolution/src/state.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/libraries/tlv-account-resolution/src/state.rs b/libraries/tlv-account-resolution/src/state.rs index 6aa84b8c903..60e3eebedff 100644 --- a/libraries/tlv-account-resolution/src/state.rs +++ b/libraries/tlv-account-resolution/src/state.rs @@ -182,6 +182,22 @@ impl ExtraAccountMetaList { Ok(()) } + /// Update pod slice data for the given instruction and its required + /// list of `ExtraAccountMeta`s + pub fn update( + data: &mut [u8], + extra_account_metas: &[ExtraAccountMeta], + ) -> Result<(), ProgramError> { + let mut state = TlvStateMut::unpack(data).unwrap(); + let tlv_size = PodSlice::::size_of(extra_account_metas.len())?; + let bytes = state.realloc_first::(tlv_size)?; + let mut validation_data = PodSliceMut::init(bytes)?; + for meta in extra_account_metas { + validation_data.push(*meta)?; + } + Ok(()) + } + /// Get the underlying `PodSlice` from an unpacked TLV /// /// Due to lifetime annoyances, this function can't just take in the bytes, From fda57125d09c9a05a6135ed570c1b06628ea76bd Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Sun, 26 Nov 2023 17:39:55 +0900 Subject: [PATCH 02/39] Updated interface to handle new update instruction --- .../interface/src/instruction.rs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/token/transfer-hook/interface/src/instruction.rs b/token/transfer-hook/interface/src/instruction.rs index 5784f1b0986..ecbd68b4e0f 100644 --- a/token/transfer-hook/interface/src/instruction.rs +++ b/token/transfer-hook/interface/src/instruction.rs @@ -45,6 +45,19 @@ pub enum TransferHookInstruction { /// List of `ExtraAccountMeta`s to write into the account extra_account_metas: Vec, }, + /// Updates the extra account metas on an account, writing into + /// the first open TLV space. + /// + /// Accounts expected by this instruction: + /// + /// 0. `[w]` Account with extra account metas + /// 1. `[]` Mint + /// 2. `[s]` Mint authority + /// 3. `[]` System program + UpdateExtraAccountMetaList { + /// List of `ExtraAccountMeta`s to write into the account + extra_account_metas: Vec, + }, } /// TLV instruction type only used to define the discriminator. The actual data /// is entirely managed by `ExtraAccountMetaList`, and it is the only data @@ -59,6 +72,12 @@ pub struct ExecuteInstruction; #[discriminator_hash_input("spl-transfer-hook-interface:initialize-extra-account-metas")] pub struct InitializeExtraAccountMetaListInstruction; +/// TLV instruction type used to update extra account metas +/// for the transfer hook +#[derive(SplDiscriminate)] +#[discriminator_hash_input("spl-transfer-hook-interface:update-extra-account-metas")] +pub struct UpdateExtraAccountMetaListInstruction; + impl TransferHookInstruction { /// Unpacks a byte buffer into a /// [TransferHookInstruction](enum.TransferHookInstruction.html). @@ -83,6 +102,13 @@ impl TransferHookInstruction { extra_account_metas, } } + UpdateExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE => { + let pod_slice = PodSlice::::unpack(rest)?; + let extra_account_metas = pod_slice.data().to_vec(); + Self::UpdateExtraAccountMetaList { + extra_account_metas, + } + } _ => return Err(ProgramError::InvalidInstructionData), }) } @@ -105,6 +131,15 @@ impl TransferHookInstruction { buf.extend_from_slice(&(extra_account_metas.len() as u32).to_le_bytes()); buf.extend_from_slice(pod_slice_to_bytes(extra_account_metas)); } + Self::UpdateExtraAccountMetaList { + extra_account_metas, + } => { + buf.extend_from_slice( + UpdateExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE, + ); + buf.extend_from_slice(&(extra_account_metas.len() as u32).to_le_bytes()); + buf.extend_from_slice(pod_slice_to_bytes(extra_account_metas)); + } }; buf } @@ -189,6 +224,33 @@ pub fn initialize_extra_account_meta_list( } } +/// Creates a `UpdateExtraAccountMetaList` instruction. +pub fn update_extra_account_meta_list( + program_id: &Pubkey, + extra_account_metas_pubkey: &Pubkey, + mint_pubkey: &Pubkey, + authority_pubkey: &Pubkey, + extra_account_metas: &[ExtraAccountMeta], +) -> Instruction { + let data = TransferHookInstruction::UpdateExtraAccountMetaList { + extra_account_metas: extra_account_metas.to_vec(), + } + .pack(); + + let accounts = vec![ + AccountMeta::new(*extra_account_metas_pubkey, false), + AccountMeta::new_readonly(*mint_pubkey, false), + AccountMeta::new_readonly(*authority_pubkey, true), + AccountMeta::new_readonly(system_program::id(), false), + ]; + + Instruction { + program_id: *program_id, + accounts, + data, + } +} + #[cfg(test)] mod test { use {super::*, crate::NAMESPACE, solana_program::hash, spl_pod::bytemuck::pod_from_bytes}; From cf132879879122e8388a6176180892f84b3b0d86 Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Sun, 26 Nov 2023 17:40:21 +0900 Subject: [PATCH 03/39] Updated Cli to handle update command --- token/transfer-hook/cli/src/main.rs | 150 +++++++++++++++++++++++++++- 1 file changed, 149 insertions(+), 1 deletion(-) diff --git a/token/transfer-hook/cli/src/main.rs b/token/transfer-hook/cli/src/main.rs index 63d11f8903f..a04dfd89ef0 100644 --- a/token/transfer-hook/cli/src/main.rs +++ b/token/transfer-hook/cli/src/main.rs @@ -17,7 +17,7 @@ use { }, spl_tlv_account_resolution::state::ExtraAccountMetaList, spl_transfer_hook_interface::{ - get_extra_account_metas_address, instruction::initialize_extra_account_meta_list, + get_extra_account_metas_address, instruction::{initialize_extra_account_meta_list, update_extra_account_meta_list}, }, std::{fmt, process::exit, rc::Rc, str::FromStr}, strum_macros::{EnumString, IntoStaticStr}, @@ -127,6 +127,71 @@ async fn process_create_extra_account_metas( .map_err(|err| format!("error: send transaction: {err}").into()) } +async fn process_update_extra_account_metas( + rpc_client: &RpcClient, + program_id: &Pubkey, + token: &Pubkey, + transfer_hook_accounts: Vec, + mint_authority: &dyn Signer, + payer: &dyn Signer, +) -> Result> { + let extra_account_metas_address = get_extra_account_metas_address(token, program_id); + let extra_account_metas = transfer_hook_accounts + .into_iter() + .map(|v| v.into()) + .collect::>(); + + let length = extra_account_metas.len(); + let account_size = ExtraAccountMetaList::size_of(length)?; + let required_lamports = rpc_client + .get_minimum_balance_for_rent_exemption(account_size) + .await + .map_err(|err| format!("error: unable to fetch rent-exemption: {err}"))?; + let extra_account_metas_account = rpc_client.get_account(&extra_account_metas_address).await; + if extra_account_metas_account.is_err() { + return Err(format!( + "error: extra account metas for mint {token} and program {program_id} does not exist" + ) + .into()); + } + let current_lamports = extra_account_metas_account.map(|a| a.lamports).unwrap_or(0); + let transfer_lamports = required_lamports.saturating_sub(current_lamports); + + let mut ixs = vec![]; + if transfer_lamports > 0 { + ixs.push(system_instruction::transfer( + &payer.pubkey(), + &extra_account_metas_address, + transfer_lamports, + )); + } + ixs.push(update_extra_account_meta_list( + program_id, + &extra_account_metas_address, + token, + &mint_authority.pubkey(), + &extra_account_metas, + )); + + let mut transaction = Transaction::new_with_payer(&ixs, Some(&payer.pubkey())); + let blockhash = rpc_client + .get_latest_blockhash() + .await + .map_err(|err| format!("error: unable to get latest blockhash: {err}"))?; + let mut signers = vec![payer]; + if payer.pubkey() != mint_authority.pubkey() { + signers.push(mint_authority); + } + transaction + .try_sign(&signers, blockhash) + .map_err(|err| format!("error: failed to sign transaction: {err}"))?; + + rpc_client + .send_and_confirm_transaction_with_spinner(&transaction) + .await + .map_err(|err| format!("error: send transaction: {err}").into()) +} + #[tokio::main] async fn main() -> Result<(), Box> { let app_matches = Command::new(crate_name!()) @@ -217,6 +282,50 @@ async fn main() -> Result<(), Box> { .global(true) .help("Filepath or URL to mint-authority keypair [default: client keypair]"), ) + + ) + .subcommand( + Command::new("update-extra-metas") + .about("Update the extra account metas account for a transfer hook program") + .arg( + Arg::with_name("program_id") + .validator(clap_is_valid_pubkey) + .value_name("TRANSFER_HOOK_PROGRAM") + .takes_value(true) + .index(1) + .required(true) + .help("The transfer hook program id"), + ) + .arg( + Arg::with_name("token") + .validator(clap_is_valid_pubkey) + .value_name("TOKEN_MINT_ADDRESS") + .takes_value(true) + .index(2) + .required(true) + .help("The token mint address for the transfer hook"), + ) + .arg( + Arg::with_name("transfer_hook_account") + .value_parser(parse_transfer_hook_account) + .value_name("PUBKEY:ROLE") + .takes_value(true) + .multiple(true) + .min_values(0) + .index(3) + .help("Additional pubkey(s) required for a transfer hook and their \ + role, in the format \":\". The role must be \ + \"readonly\", \"writable\". \"readonly-signer\", or \"writable-signer\".") + ) + .arg( + Arg::new("mint_authority") + .long("mint-authority") + .value_name("KEYPAIR") + .validator(|s| is_valid_signer(s)) + .takes_value(true) + .global(true) + .help("Filepath or URL to mint-authority keypair [default: client keypair]"), + ) ).get_matches(); let (command, matches) = app_matches.subcommand().unwrap(); @@ -303,6 +412,45 @@ async fn main() -> Result<(), Box> { }); println!("Signature: {signature}"); } + ("update-extra-metas", arg_matches) => { + let program_id = pubkey_of_signer(arg_matches, "program_id", &mut wallet_manager) + .unwrap() + .unwrap(); + let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager) + .unwrap() + .unwrap(); + let transfer_hook_accounts = arg_matches + .get_many::("transfer_hook_account") + .unwrap_or_default() + .cloned() + .collect(); + let mint_authority = DefaultSigner::new( + "mint_authority", + matches + .value_of("mint_authority") + .map(|s| s.to_string()) + .unwrap_or_else(|| cli_config.keypair_path.clone()), + ) + .signer_from_path(matches, &mut wallet_manager) + .unwrap_or_else(|err| { + eprintln!("error: {err}"); + exit(1); + }); + let signature = process_update_extra_account_metas( + &rpc_client, + &program_id, + &token, + transfer_hook_accounts, + mint_authority.as_ref(), + config.default_signer.as_ref(), + ) + .await + .unwrap_or_else(|err| { + eprintln!("error: send transaction: {err}"); + exit(1); + }); + println!("Signature: {signature}"); + } _ => unreachable!(), }; From 84d8ac064f344447dc60045cbfca79e88041db67 Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Sun, 26 Nov 2023 17:41:17 +0900 Subject: [PATCH 04/39] updated example program to handle updating --- token/transfer-hook/example/src/processor.rs | 60 ++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/token/transfer-hook/example/src/processor.rs b/token/transfer-hook/example/src/processor.rs index 16b2d1f11a7..d0108bd11bc 100644 --- a/token/transfer-hook/example/src/processor.rs +++ b/token/transfer-hook/example/src/processor.rs @@ -134,6 +134,60 @@ pub fn process_initialize_extra_account_meta_list( Ok(()) } +/// Processes a +/// [UpdateExtraAccountMetaList](enum.TransferHookInstruction.html) +/// instruction. +pub fn process_update_extra_account_meta_list( + program_id: &Pubkey, + accounts: &[AccountInfo], + extra_account_metas: &[ExtraAccountMeta], +) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + + let extra_account_metas_info = next_account_info(account_info_iter)?; + let mint_info = next_account_info(account_info_iter)?; + let authority_info = next_account_info(account_info_iter)?; + let _system_program_info = next_account_info(account_info_iter)?; + + // check that the mint authority is valid without fully deserializing + let mint_data = mint_info.try_borrow_data()?; + let mint = StateWithExtensions::::unpack(&mint_data)?; + let mint_authority = mint + .base + .mint_authority + .ok_or(TransferHookError::MintHasNoMintAuthority)?; + + // Check signers + if !authority_info.is_signer { + return Err(ProgramError::MissingRequiredSignature); + } + if *authority_info.key != mint_authority { + return Err(TransferHookError::IncorrectMintAuthority.into()); + } + + // Check validation account + let (expected_validation_address, bump_seed) = + get_extra_account_metas_address_and_bump_seed(mint_info.key, program_id); + if expected_validation_address != *extra_account_metas_info.key { + return Err(ProgramError::InvalidSeeds); + } + + let length = extra_account_metas.len(); + let account_size = ExtraAccountMetaList::size_of(length)?; + let original_account_size = extra_account_metas_info.data_len(); + + // If the new extra_account_metas is larger resize the account + if account_size > original_account_size { + extra_account_metas_info.realloc(account_size, true)?; + } + + // Update the data + let mut data = extra_account_metas_info.try_borrow_mut_data()?; + ExtraAccountMetaList::update::(&mut data, extra_account_metas)?; + + Ok(()) +} + /// Processes an [Instruction](enum.Instruction.html). pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult { let instruction = TransferHookInstruction::unpack(input)?; @@ -149,5 +203,11 @@ pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> P msg!("Instruction: InitializeExtraAccountMetaList"); process_initialize_extra_account_meta_list(program_id, accounts, &extra_account_metas) } + TransferHookInstruction::UpdateExtraAccountMetaList { + extra_account_metas, + } => { + msg!("Instruction: UpdateExtraAccountMetaList"); + process_update_extra_account_meta_list(program_id, accounts, &extra_account_metas) + } } } From ca29fdc62530f57dbb3587132734e8bf3933e58b Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:00:45 +0900 Subject: [PATCH 05/39] Rust fmt trailing whitespace fix --- token/transfer-hook/cli/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/token/transfer-hook/cli/src/main.rs b/token/transfer-hook/cli/src/main.rs index a04dfd89ef0..a45452096b4 100644 --- a/token/transfer-hook/cli/src/main.rs +++ b/token/transfer-hook/cli/src/main.rs @@ -17,7 +17,8 @@ use { }, spl_tlv_account_resolution::state::ExtraAccountMetaList, spl_transfer_hook_interface::{ - get_extra_account_metas_address, instruction::{initialize_extra_account_meta_list, update_extra_account_meta_list}, + get_extra_account_metas_address, + instruction::{initialize_extra_account_meta_list, update_extra_account_meta_list}, }, std::{fmt, process::exit, rc::Rc, str::FromStr}, strum_macros::{EnumString, IntoStaticStr}, @@ -282,7 +283,6 @@ async fn main() -> Result<(), Box> { .global(true) .help("Filepath or URL to mint-authority keypair [default: client keypair]"), ) - ) .subcommand( Command::new("update-extra-metas") From 2882a06b92c9305f9c7de11ec98de86c50c7dca0 Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:06:18 +0900 Subject: [PATCH 06/39] Removed unused variable --- token/transfer-hook/example/src/processor.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/token/transfer-hook/example/src/processor.rs b/token/transfer-hook/example/src/processor.rs index d0108bd11bc..799a0b09e0d 100644 --- a/token/transfer-hook/example/src/processor.rs +++ b/token/transfer-hook/example/src/processor.rs @@ -166,8 +166,7 @@ pub fn process_update_extra_account_meta_list( } // Check validation account - let (expected_validation_address, bump_seed) = - get_extra_account_metas_address_and_bump_seed(mint_info.key, program_id); + let expected_validation_address = get_extra_account_metas_address(mint_info.key, program_id); if expected_validation_address != *extra_account_metas_info.key { return Err(ProgramError::InvalidSeeds); } From 986e6399ef4a798e990dcefe8821f60ae838d5e1 Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Mon, 27 Nov 2023 23:33:01 +0900 Subject: [PATCH 07/39] Added more explicit update instruction doc comment --- token/transfer-hook/interface/src/instruction.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/token/transfer-hook/interface/src/instruction.rs b/token/transfer-hook/interface/src/instruction.rs index ecbd68b4e0f..a7c7eaaa7e4 100644 --- a/token/transfer-hook/interface/src/instruction.rs +++ b/token/transfer-hook/interface/src/instruction.rs @@ -45,8 +45,8 @@ pub enum TransferHookInstruction { /// List of `ExtraAccountMeta`s to write into the account extra_account_metas: Vec, }, - /// Updates the extra account metas on an account, writing into - /// the first open TLV space. + /// Updates the extra account metas by overwriting the existing on an account. + /// Must provide the full list of desired extra account metas. /// /// Accounts expected by this instruction: /// @@ -55,7 +55,7 @@ pub enum TransferHookInstruction { /// 2. `[s]` Mint authority /// 3. `[]` System program UpdateExtraAccountMetaList { - /// List of `ExtraAccountMeta`s to write into the account + /// Full list of `ExtraAccountMeta`s to overwrite into the account extra_account_metas: Vec, }, } From 3b177ce2c82c3d558b8838737fc09617040ff508 Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Tue, 28 Nov 2023 12:56:29 +0900 Subject: [PATCH 08/39] Allow for resizing to smaller account size --- token/transfer-hook/example/src/processor.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/token/transfer-hook/example/src/processor.rs b/token/transfer-hook/example/src/processor.rs index 799a0b09e0d..553d9e91ab4 100644 --- a/token/transfer-hook/example/src/processor.rs +++ b/token/transfer-hook/example/src/processor.rs @@ -175,15 +175,18 @@ pub fn process_update_extra_account_meta_list( let account_size = ExtraAccountMetaList::size_of(length)?; let original_account_size = extra_account_metas_info.data_len(); - // If the new extra_account_metas is larger resize the account - if account_size > original_account_size { + // If the new extra_account_metas length is different, resize the account and update + if account_size >= original_account_size { + extra_account_metas_info.realloc(account_size, true)?; + let mut data = extra_account_metas_info.try_borrow_mut_data()?; + ExtraAccountMetaList::update::(&mut data, extra_account_metas)?; + } else { + let mut data = extra_account_metas_info.try_borrow_mut_data()?; + ExtraAccountMetaList::update::(&mut data, extra_account_metas)?; + drop(data); extra_account_metas_info.realloc(account_size, true)?; } - // Update the data - let mut data = extra_account_metas_info.try_borrow_mut_data()?; - ExtraAccountMetaList::update::(&mut data, extra_account_metas)?; - Ok(()) } From 5dc4a1ac013d3847e0d0a560f12de973ec3adf73 Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Tue, 28 Nov 2023 13:48:05 +0900 Subject: [PATCH 09/39] Removed system program from update instruction --- token/transfer-hook/example/src/processor.rs | 2 +- token/transfer-hook/interface/src/instruction.rs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/token/transfer-hook/example/src/processor.rs b/token/transfer-hook/example/src/processor.rs index 553d9e91ab4..2af8e10c0ab 100644 --- a/token/transfer-hook/example/src/processor.rs +++ b/token/transfer-hook/example/src/processor.rs @@ -147,7 +147,7 @@ pub fn process_update_extra_account_meta_list( let extra_account_metas_info = next_account_info(account_info_iter)?; let mint_info = next_account_info(account_info_iter)?; let authority_info = next_account_info(account_info_iter)?; - let _system_program_info = next_account_info(account_info_iter)?; + // let _system_program_info = next_account_info(account_info_iter)?; // check that the mint authority is valid without fully deserializing let mint_data = mint_info.try_borrow_data()?; diff --git a/token/transfer-hook/interface/src/instruction.rs b/token/transfer-hook/interface/src/instruction.rs index a7c7eaaa7e4..17c29c1a32a 100644 --- a/token/transfer-hook/interface/src/instruction.rs +++ b/token/transfer-hook/interface/src/instruction.rs @@ -45,7 +45,7 @@ pub enum TransferHookInstruction { /// List of `ExtraAccountMeta`s to write into the account extra_account_metas: Vec, }, - /// Updates the extra account metas by overwriting the existing on an account. + /// Updates the extra account metas by overwriting the existing on the account. /// Must provide the full list of desired extra account metas. /// /// Accounts expected by this instruction: @@ -53,7 +53,6 @@ pub enum TransferHookInstruction { /// 0. `[w]` Account with extra account metas /// 1. `[]` Mint /// 2. `[s]` Mint authority - /// 3. `[]` System program UpdateExtraAccountMetaList { /// Full list of `ExtraAccountMeta`s to overwrite into the account extra_account_metas: Vec, @@ -241,7 +240,7 @@ pub fn update_extra_account_meta_list( AccountMeta::new(*extra_account_metas_pubkey, false), AccountMeta::new_readonly(*mint_pubkey, false), AccountMeta::new_readonly(*authority_pubkey, true), - AccountMeta::new_readonly(system_program::id(), false), + // AccountMeta::new_readonly(system_program::id(), false), ]; Instruction { From 12c84cdc51fba5c5405965de9323ec3ebc994dba Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Tue, 28 Nov 2023 17:03:47 +0900 Subject: [PATCH 10/39] Added helper fn to calculate transfer lamports --- token/transfer-hook/cli/src/main.rs | 46 +++++++++++++---------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/token/transfer-hook/cli/src/main.rs b/token/transfer-hook/cli/src/main.rs index a45452096b4..a239444aefe 100644 --- a/token/transfer-hook/cli/src/main.rs +++ b/token/transfer-hook/cli/src/main.rs @@ -80,18 +80,9 @@ async fn process_create_extra_account_metas( let length = extra_account_metas.len(); let account_size = ExtraAccountMetaList::size_of(length)?; - let required_lamports = rpc_client - .get_minimum_balance_for_rent_exemption(account_size) - .await - .map_err(|err| format!("error: unable to fetch rent-exemption: {err}"))?; - let extra_account_metas_account = rpc_client.get_account(&extra_account_metas_address).await; - if let Ok(account) = &extra_account_metas_account { - if account.owner != system_program::id() { - return Err(format!("error: extra account metas for mint {token} and program {program_id} already exists").into()); - } - } - let current_lamports = extra_account_metas_account.map(|a| a.lamports).unwrap_or(0); - let transfer_lamports = required_lamports.saturating_sub(current_lamports); + + let transfer_lamports = + calculate_transfer_lamports(rpc_client, &extra_account_metas_address, account_size).await?; let mut ixs = vec![]; if transfer_lamports > 0 { @@ -144,19 +135,9 @@ async fn process_update_extra_account_metas( let length = extra_account_metas.len(); let account_size = ExtraAccountMetaList::size_of(length)?; - let required_lamports = rpc_client - .get_minimum_balance_for_rent_exemption(account_size) - .await - .map_err(|err| format!("error: unable to fetch rent-exemption: {err}"))?; - let extra_account_metas_account = rpc_client.get_account(&extra_account_metas_address).await; - if extra_account_metas_account.is_err() { - return Err(format!( - "error: extra account metas for mint {token} and program {program_id} does not exist" - ) - .into()); - } - let current_lamports = extra_account_metas_account.map(|a| a.lamports).unwrap_or(0); - let transfer_lamports = required_lamports.saturating_sub(current_lamports); + + let transfer_lamports = + calculate_transfer_lamports(rpc_client, &extra_account_metas_address, account_size).await?; let mut ixs = vec![]; if transfer_lamports > 0 { @@ -193,6 +174,21 @@ async fn process_update_extra_account_metas( .map_err(|err| format!("error: send transaction: {err}").into()) } +// Helper function to calculate the required lamports +async fn calculate_transfer_lamports( + rpc_client: &RpcClient, + account_address: &Pubkey, + account_size: usize, +) -> Result> { + let required_lamports = rpc_client + .get_minimum_balance_for_rent_exemption(account_size) + .await + .map_err(|err| format!("error: unable to fetch rent-exemption: {err}"))?; + let account_info = rpc_client.get_account(account_address).await; + let current_lamports = account_info.map(|a| a.lamports).unwrap_or(0); + Ok(required_lamports.saturating_sub(current_lamports)) +} + #[tokio::main] async fn main() -> Result<(), Box> { let app_matches = Command::new(crate_name!()) From 155319d88e627d2104bf4b373cb3c0a9009337a4 Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Tue, 28 Nov 2023 17:53:50 +0900 Subject: [PATCH 11/39] Added unit tests for update function --- libraries/tlv-account-resolution/src/state.rs | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/libraries/tlv-account-resolution/src/state.rs b/libraries/tlv-account-resolution/src/state.rs index 60e3eebedff..ea5a6f8f8ff 100644 --- a/libraries/tlv-account-resolution/src/state.rs +++ b/libraries/tlv-account-resolution/src/state.rs @@ -1308,6 +1308,137 @@ mod tests { } } + #[tokio::test] + async fn update_extra_account_meta_list() { + // Create new ExtraAccountMeta + let metas = [ + ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, true).unwrap(), + ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, false).unwrap(), + ]; + + // Create a buffer and get its size + let account_size = ExtraAccountMetaList::size_of(metas.len()).unwrap(); + let mut buffer = vec![0; account_size]; + + // Initialize + ExtraAccountMetaList::init::(&mut buffer, &metas).unwrap(); + + // Create new metas to overwrite the initial + let updated_metas = [ + ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, true).unwrap(), + ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, false).unwrap(), + ]; + + // Update + ExtraAccountMetaList::update::(&mut buffer, &updated_metas).unwrap(); + + // Get the tlv state then unpack + let state = TlvStateBorrowed::unpack(&buffer).unwrap(); + let unpacked_metas = + ExtraAccountMetaList::unpack_with_tlv_state::(&state).unwrap(); + + // Convert to Vec + let unpacked_metas_vec = unpacked_metas.data().to_vec(); + + // Assert that the unpacked metas match the updated metas + assert_eq!(unpacked_metas_vec, updated_metas.to_vec(), "The updated ExtraAccountMetas in the buffer should match the ones provided to the update function."); + } + + #[tokio::test] + async fn update_extra_account_meta_list_to_larger() { + // Create new ExtraAccountMeta + let initial_metas = [ + ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, true).unwrap(), + ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, false).unwrap(), + ]; + + // Create a buffer and get its size + let initial_account_size = ExtraAccountMetaList::size_of(initial_metas.len()).unwrap(); + let mut buffer = vec![0; initial_account_size]; + + // Initialize + ExtraAccountMetaList::init::(&mut buffer, &initial_metas).unwrap(); + + // Create new metas to overwrite the initial, adding one + let updated_metas = [ + ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, true).unwrap(), + ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, false).unwrap(), + ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, true).unwrap(), + ]; + + // Calculate the new size + let updated_account_size = ExtraAccountMetaList::size_of(updated_metas.len()).unwrap(); + + // Must resize buffer first + buffer.resize(updated_account_size, 0); + + // Update + ExtraAccountMetaList::update::(&mut buffer, &updated_metas).unwrap(); + + // Get the tlv state then unpack + let state = TlvStateBorrowed::unpack(&buffer).unwrap(); + let unpacked_metas = + ExtraAccountMetaList::unpack_with_tlv_state::(&state).unwrap(); + + // Convert to Vec + let unpacked_metas_vec = unpacked_metas.data().to_vec(); + + // Assert that the unpacked metas match the updated metas and their lengths + assert_eq!( + unpacked_metas_vec.len(), + updated_metas.len(), + "The length of the updated ExtraAccountMetas should match the updated array length." + ); + assert_eq!(unpacked_metas_vec, updated_metas.to_vec(), "The updated ExtraAccountMetas in the buffer should match the ones provided to the update function."); + } + + #[tokio::test] + async fn update_extra_account_meta_list_to_smaller() { + // Create new ExtraAccountMeta + let initial_metas = [ + ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, true).unwrap(), + ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, false).unwrap(), + ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, true).unwrap(), + ]; + + // Create a buffer and get its size + let initial_account_size = ExtraAccountMetaList::size_of(initial_metas.len()).unwrap(); + let mut buffer = vec![0; initial_account_size]; + + // Initialize + ExtraAccountMetaList::init::(&mut buffer, &initial_metas).unwrap(); + + // Create new metas to overwrite the initial, with one fewer + let updated_metas = [ + ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, true).unwrap(), + ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, false).unwrap(), + ]; + + // Resizing the buffer gives InvalidAccountData from + // `let mut state = TlvStateMut::unpack(data).unwrap();`. + // This is because the size is checked against byte 9 in the buffer + // Not sure if this is the ideal behavior + + // Update + ExtraAccountMetaList::update::(&mut buffer, &updated_metas).unwrap(); + + // Get the tlv state then unpack + let state = TlvStateBorrowed::unpack(&buffer).unwrap(); + let unpacked_metas = + ExtraAccountMetaList::unpack_with_tlv_state::(&state).unwrap(); + + // Convert to Vec + let unpacked_metas_vec = unpacked_metas.data().to_vec(); + + // Assert that the unpacked metas match the updated metas and their lengths + assert_eq!( + unpacked_metas_vec.len(), + updated_metas.len(), + "The length of the updated ExtraAccountMetas should match the updated array length." + ); + assert_eq!(unpacked_metas_vec, updated_metas.to_vec(), "The updated ExtraAccountMetas in the buffer should match the ones provided to the update function."); + } + #[test] fn check_account_infos_test() { let program_id = Pubkey::new_unique(); From 2f3e041cd56c49c851406d9109dafa65bc0e9e1d Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Tue, 28 Nov 2023 19:59:51 +0900 Subject: [PATCH 12/39] Added unit test for update instruction --- .../transfer-hook/example/tests/functional.rs | 177 +++++++++++++++++- 1 file changed, 176 insertions(+), 1 deletion(-) diff --git a/token/transfer-hook/example/tests/functional.rs b/token/transfer-hook/example/tests/functional.rs index 989b6f901ce..ac56feb4498 100644 --- a/token/transfer-hook/example/tests/functional.rs +++ b/token/transfer-hook/example/tests/functional.rs @@ -28,7 +28,10 @@ use { spl_transfer_hook_interface::{ error::TransferHookError, get_extra_account_metas_address, - instruction::{execute_with_extra_account_metas, initialize_extra_account_meta_list}, + instruction::{ + execute_with_extra_account_metas, initialize_extra_account_meta_list, + update_extra_account_meta_list, + }, onchain, }, }; @@ -747,3 +750,175 @@ async fn fail_without_transferring_flag() { ) ); } + +#[tokio::test] +async fn success_on_chain_invoke_with_updated_extra_account_metas() { + let hook_program_id = Pubkey::new_unique(); + let mut program_test = setup(&hook_program_id); + let program_id = Pubkey::new_unique(); + program_test.add_program( + "test_cpi_program", + program_id, + processor!(process_instruction), + ); + + let token_program_id = spl_token_2022::id(); + let wallet = Keypair::new(); + let mint_address = Pubkey::new_unique(); + let mint_authority = Keypair::new(); + let mint_authority_pubkey = mint_authority.pubkey(); + let source = Pubkey::new_unique(); + let destination = Pubkey::new_unique(); + let decimals = 2; + let amount = 0u64; + + setup_token_accounts( + &mut program_test, + &token_program_id, + &mint_address, + &mint_authority_pubkey, + &source, + &destination, + &wallet.pubkey(), + decimals, + true, + ); + + let extra_account_metas_address = + get_extra_account_metas_address(&mint_address, &hook_program_id); + + // Create an initial acount metas list + let init_extra_account_metas = [ + ExtraAccountMeta::new_with_pubkey(&sysvar::instructions::id(), false, false).unwrap(), + ExtraAccountMeta::new_with_pubkey(&mint_authority_pubkey, true, false).unwrap(), + ExtraAccountMeta::new_with_seeds( + &[ + Seed::Literal { + bytes: b"init-seed-prefix".to_vec(), + }, + Seed::AccountKey { index: 0 }, + ], + false, + true, + ) + .unwrap(), + ]; + + let mut context = program_test.start_with_context().await; + let rent = context.banks_client.get_rent().await.unwrap(); + let rent_lamports = rent + .minimum_balance(ExtraAccountMetaList::size_of(init_extra_account_metas.len()).unwrap()); + let init_transaction = Transaction::new_signed_with_payer( + &[ + system_instruction::transfer( + &context.payer.pubkey(), + &extra_account_metas_address, + rent_lamports, + ), + initialize_extra_account_meta_list( + &hook_program_id, + &extra_account_metas_address, + &mint_address, + &mint_authority_pubkey, + &init_extra_account_metas, + ), + ], + Some(&context.payer.pubkey()), + &[&context.payer, &mint_authority], + context.last_blockhash, + ); + + context + .banks_client + .process_transaction(init_transaction) + .await + .unwrap(); + + // Create an updated account metas list + let updated_extra_account_metas = [ + ExtraAccountMeta::new_with_pubkey(&sysvar::instructions::id(), false, false).unwrap(), + ExtraAccountMeta::new_with_pubkey(&mint_authority_pubkey, true, false).unwrap(), + ExtraAccountMeta::new_with_seeds( + &[ + Seed::Literal { + bytes: b"updated-seed-prefix".to_vec(), + }, + Seed::AccountKey { index: 0 }, + ], + false, + true, + ) + .unwrap(), + ]; + + let rent = context.banks_client.get_rent().await.unwrap(); + let rent_lamports = rent + .minimum_balance(ExtraAccountMetaList::size_of(updated_extra_account_metas.len()).unwrap()); + let update_transaction = Transaction::new_signed_with_payer( + &[ + system_instruction::transfer( + &context.payer.pubkey(), + &extra_account_metas_address, + rent_lamports, + ), + update_extra_account_meta_list( + &hook_program_id, + &extra_account_metas_address, + &mint_address, + &mint_authority_pubkey, + &updated_extra_account_metas, + ), + ], + Some(&context.payer.pubkey()), + &[&context.payer, &mint_authority], + context.last_blockhash, + ); + + context + .banks_client + .process_transaction(update_transaction) + .await + .unwrap(); + + let updated_extra_pda = Pubkey::find_program_address( + &[ + b"updated-seed-prefix", // Literal prefix + source.as_ref(), // Account at index 0 + ], + &hook_program_id, + ) + .0; + + let test_updated_extra_account_metas = [ + AccountMeta::new_readonly(sysvar::instructions::id(), false), + AccountMeta::new_readonly(mint_authority_pubkey, true), + AccountMeta::new(updated_extra_pda, false), + ]; + + // Use updated account metas list + let mut test_instruction = execute_with_extra_account_metas( + &program_id, + &source, + &mint_address, + &destination, + &wallet.pubkey(), + &extra_account_metas_address, + &test_updated_extra_account_metas, + amount, + ); + test_instruction + .accounts + .insert(0, AccountMeta::new_readonly(hook_program_id, false)); + let transaction = Transaction::new_signed_with_payer( + &[test_instruction], + Some(&context.payer.pubkey()), + &[&context.payer, &mint_authority], + context.last_blockhash, + ); + + context + .banks_client + .process_transaction(transaction) + .await + .unwrap(); +} From 8b477d5dc0a5240514c2a146a252c09b26538e3e Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Tue, 28 Nov 2023 20:54:01 +0900 Subject: [PATCH 13/39] removed unnecessary commented out code --- token/transfer-hook/example/src/processor.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/token/transfer-hook/example/src/processor.rs b/token/transfer-hook/example/src/processor.rs index 2af8e10c0ab..8e4d5a08041 100644 --- a/token/transfer-hook/example/src/processor.rs +++ b/token/transfer-hook/example/src/processor.rs @@ -147,7 +147,6 @@ pub fn process_update_extra_account_meta_list( let extra_account_metas_info = next_account_info(account_info_iter)?; let mint_info = next_account_info(account_info_iter)?; let authority_info = next_account_info(account_info_iter)?; - // let _system_program_info = next_account_info(account_info_iter)?; // check that the mint authority is valid without fully deserializing let mint_data = mint_info.try_borrow_data()?; From 3a438b72adacea9cda9ce004edcb6b8db1e70ec5 Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Tue, 28 Nov 2023 21:16:26 +0900 Subject: [PATCH 14/39] re-added checks on initialization --- token/transfer-hook/cli/src/main.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/token/transfer-hook/cli/src/main.rs b/token/transfer-hook/cli/src/main.rs index a239444aefe..6adb6dd3a80 100644 --- a/token/transfer-hook/cli/src/main.rs +++ b/token/transfer-hook/cli/src/main.rs @@ -81,6 +81,13 @@ async fn process_create_extra_account_metas( let length = extra_account_metas.len(); let account_size = ExtraAccountMetaList::size_of(length)?; + let extra_account_metas_account = rpc_client.get_account(&extra_account_metas_address).await; + if let Ok(account) = &extra_account_metas_account { + if account.owner != system_program::id() { + return Err(format!("error: extra account metas for mint {token} and program {program_id} already exists").into()); + } + } + let transfer_lamports = calculate_transfer_lamports(rpc_client, &extra_account_metas_address, account_size).await?; @@ -136,6 +143,14 @@ async fn process_update_extra_account_metas( let length = extra_account_metas.len(); let account_size = ExtraAccountMetaList::size_of(length)?; + let extra_account_metas_account = rpc_client.get_account(&extra_account_metas_address).await; + if extra_account_metas_account.is_err() { + return Err(format!( + "error: extra account metas for mint {token} and program {program_id} does not exist" + ) + .into()); + } + let transfer_lamports = calculate_transfer_lamports(rpc_client, &extra_account_metas_address, account_size).await?; From 633412060d0666fc9b3f5e0a427128fd9054bc7d Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Tue, 28 Nov 2023 21:19:16 +0900 Subject: [PATCH 15/39] turned of zero_init for realloc for performance --- token/transfer-hook/example/src/processor.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/token/transfer-hook/example/src/processor.rs b/token/transfer-hook/example/src/processor.rs index 8e4d5a08041..b30b0b3c545 100644 --- a/token/transfer-hook/example/src/processor.rs +++ b/token/transfer-hook/example/src/processor.rs @@ -176,14 +176,14 @@ pub fn process_update_extra_account_meta_list( // If the new extra_account_metas length is different, resize the account and update if account_size >= original_account_size { - extra_account_metas_info.realloc(account_size, true)?; + extra_account_metas_info.realloc(account_size, false)?; let mut data = extra_account_metas_info.try_borrow_mut_data()?; ExtraAccountMetaList::update::(&mut data, extra_account_metas)?; } else { let mut data = extra_account_metas_info.try_borrow_mut_data()?; ExtraAccountMetaList::update::(&mut data, extra_account_metas)?; drop(data); - extra_account_metas_info.realloc(account_size, true)?; + extra_account_metas_info.realloc(account_size, false)?; } Ok(()) From b34c50c627c1c95fdf82cd5e286e754ddece5a21 Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Wed, 29 Nov 2023 10:25:22 +0900 Subject: [PATCH 16/39] Fixed update doc comments --- token/transfer-hook/interface/src/instruction.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/token/transfer-hook/interface/src/instruction.rs b/token/transfer-hook/interface/src/instruction.rs index 17c29c1a32a..29abc2b99fc 100644 --- a/token/transfer-hook/interface/src/instruction.rs +++ b/token/transfer-hook/interface/src/instruction.rs @@ -45,8 +45,7 @@ pub enum TransferHookInstruction { /// List of `ExtraAccountMeta`s to write into the account extra_account_metas: Vec, }, - /// Updates the extra account metas by overwriting the existing on the account. - /// Must provide the full list of desired extra account metas. + /// Updates the extra account metas on an account by overwriting the existing list. /// /// Accounts expected by this instruction: /// @@ -54,7 +53,7 @@ pub enum TransferHookInstruction { /// 1. `[]` Mint /// 2. `[s]` Mint authority UpdateExtraAccountMetaList { - /// Full list of `ExtraAccountMeta`s to overwrite into the account + /// The new list of `ExtraAccountMetas` to overwrite the existing entry in the account. extra_account_metas: Vec, }, } From 200351781a3bf34ca348bc348e2fc8dc071d1541 Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Wed, 29 Nov 2023 10:56:54 +0900 Subject: [PATCH 17/39] Used block-scoping rather than explicit drop() --- token/transfer-hook/example/src/processor.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/token/transfer-hook/example/src/processor.rs b/token/transfer-hook/example/src/processor.rs index b30b0b3c545..8d95a77d21e 100644 --- a/token/transfer-hook/example/src/processor.rs +++ b/token/transfer-hook/example/src/processor.rs @@ -180,9 +180,11 @@ pub fn process_update_extra_account_meta_list( let mut data = extra_account_metas_info.try_borrow_mut_data()?; ExtraAccountMetaList::update::(&mut data, extra_account_metas)?; } else { - let mut data = extra_account_metas_info.try_borrow_mut_data()?; - ExtraAccountMetaList::update::(&mut data, extra_account_metas)?; - drop(data); + { + let mut data = extra_account_metas_info.try_borrow_mut_data()?; + ExtraAccountMetaList::update::(&mut data, extra_account_metas)?; + } + extra_account_metas_info.realloc(account_size, false)?; } From a69cfef3f22dfd963d7ab94a2b068bd684661ddf Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Wed, 29 Nov 2023 11:11:43 +0900 Subject: [PATCH 18/39] Removed unnecessary convert to vec --- libraries/tlv-account-resolution/src/state.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/tlv-account-resolution/src/state.rs b/libraries/tlv-account-resolution/src/state.rs index ea5a6f8f8ff..e23b8584364 100644 --- a/libraries/tlv-account-resolution/src/state.rs +++ b/libraries/tlv-account-resolution/src/state.rs @@ -1334,14 +1334,14 @@ mod tests { // Get the tlv state then unpack let state = TlvStateBorrowed::unpack(&buffer).unwrap(); - let unpacked_metas = + let unpacked_metas_pod = ExtraAccountMetaList::unpack_with_tlv_state::(&state).unwrap(); // Convert to Vec - let unpacked_metas_vec = unpacked_metas.data().to_vec(); + let unpacked_metas = unpacked_metas_pod.data(); // Assert that the unpacked metas match the updated metas - assert_eq!(unpacked_metas_vec, updated_metas.to_vec(), "The updated ExtraAccountMetas in the buffer should match the ones provided to the update function."); + assert_eq!(unpacked_metas, updated_metas, "The updated ExtraAccountMetas in the buffer should match the ones provided to the update function."); } #[tokio::test] From 32d638b1e0193042ce8ce544db43e27fac3ed79e Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Wed, 29 Nov 2023 16:07:22 +0900 Subject: [PATCH 19/39] refactored updated test into single test --- libraries/tlv-account-resolution/src/state.rs | 133 ++++-------------- 1 file changed, 30 insertions(+), 103 deletions(-) diff --git a/libraries/tlv-account-resolution/src/state.rs b/libraries/tlv-account-resolution/src/state.rs index e23b8584364..3a4125f5f3b 100644 --- a/libraries/tlv-account-resolution/src/state.rs +++ b/libraries/tlv-account-resolution/src/state.rs @@ -1310,133 +1310,60 @@ mod tests { #[tokio::test] async fn update_extra_account_meta_list() { - // Create new ExtraAccountMeta - let metas = [ + // Create list of initial metas + let initial_metas = [ ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, true).unwrap(), ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, false).unwrap(), ]; - // Create a buffer and get its size - let account_size = ExtraAccountMetaList::size_of(metas.len()).unwrap(); - let mut buffer = vec![0; account_size]; - - // Initialize - ExtraAccountMetaList::init::(&mut buffer, &metas).unwrap(); - - // Create new metas to overwrite the initial - let updated_metas = [ + // Create updated metas list of the same size + let updated_metas_1 = [ ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, true).unwrap(), ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, false).unwrap(), ]; + initialize_update_and_assert_metas(&initial_metas, &updated_metas_1).await; - // Update - ExtraAccountMetaList::update::(&mut buffer, &updated_metas).unwrap(); - - // Get the tlv state then unpack - let state = TlvStateBorrowed::unpack(&buffer).unwrap(); - let unpacked_metas_pod = - ExtraAccountMetaList::unpack_with_tlv_state::(&state).unwrap(); - - // Convert to Vec - let unpacked_metas = unpacked_metas_pod.data(); - - // Assert that the unpacked metas match the updated metas - assert_eq!(unpacked_metas, updated_metas, "The updated ExtraAccountMetas in the buffer should match the ones provided to the update function."); - } - - #[tokio::test] - async fn update_extra_account_meta_list_to_larger() { - // Create new ExtraAccountMeta - let initial_metas = [ - ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, true).unwrap(), - ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, false).unwrap(), - ]; - - // Create a buffer and get its size - let initial_account_size = ExtraAccountMetaList::size_of(initial_metas.len()).unwrap(); - let mut buffer = vec![0; initial_account_size]; - - // Initialize - ExtraAccountMetaList::init::(&mut buffer, &initial_metas).unwrap(); - - // Create new metas to overwrite the initial, adding one - let updated_metas = [ + // Create updated and larger list of metas + let updated_metas_2 = [ ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, true).unwrap(), ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, false).unwrap(), ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, true).unwrap(), ]; + initialize_update_and_assert_metas(&initial_metas, &updated_metas_2).await; - // Calculate the new size - let updated_account_size = ExtraAccountMetaList::size_of(updated_metas.len()).unwrap(); - - // Must resize buffer first - buffer.resize(updated_account_size, 0); - - // Update - ExtraAccountMetaList::update::(&mut buffer, &updated_metas).unwrap(); - - // Get the tlv state then unpack - let state = TlvStateBorrowed::unpack(&buffer).unwrap(); - let unpacked_metas = - ExtraAccountMetaList::unpack_with_tlv_state::(&state).unwrap(); - - // Convert to Vec - let unpacked_metas_vec = unpacked_metas.data().to_vec(); - - // Assert that the unpacked metas match the updated metas and their lengths - assert_eq!( - unpacked_metas_vec.len(), - updated_metas.len(), - "The length of the updated ExtraAccountMetas should match the updated array length." - ); - assert_eq!(unpacked_metas_vec, updated_metas.to_vec(), "The updated ExtraAccountMetas in the buffer should match the ones provided to the update function."); + // Create updated and smaller list of metas + let updated_metas_3 = + [ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, true).unwrap()]; + initialize_update_and_assert_metas(&initial_metas, &updated_metas_3).await; } - #[tokio::test] - async fn update_extra_account_meta_list_to_smaller() { - // Create new ExtraAccountMeta - let initial_metas = [ - ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, true).unwrap(), - ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, false).unwrap(), - ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, true).unwrap(), - ]; - - // Create a buffer and get its size + async fn initialize_update_and_assert_metas( + initial_metas: &[ExtraAccountMeta], + updated_metas: &[ExtraAccountMeta], + ) { + // initialize let initial_account_size = ExtraAccountMetaList::size_of(initial_metas.len()).unwrap(); let mut buffer = vec![0; initial_account_size]; + ExtraAccountMetaList::init::(&mut buffer, initial_metas).unwrap(); - // Initialize - ExtraAccountMetaList::init::(&mut buffer, &initial_metas).unwrap(); - - // Create new metas to overwrite the initial, with one fewer - let updated_metas = [ - ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, true).unwrap(), - ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, false).unwrap(), - ]; - - // Resizing the buffer gives InvalidAccountData from - // `let mut state = TlvStateMut::unpack(data).unwrap();`. - // This is because the size is checked against byte 9 in the buffer - // Not sure if this is the ideal behavior + // resize buffere if necessary + let account_size = ExtraAccountMetaList::size_of(updated_metas.len()).unwrap(); + if account_size > initial_account_size { + buffer.resize(account_size, 0); + } - // Update - ExtraAccountMetaList::update::(&mut buffer, &updated_metas).unwrap(); + // update + ExtraAccountMetaList::update::(&mut buffer, updated_metas).unwrap(); - // Get the tlv state then unpack + // retreive metas and assert let state = TlvStateBorrowed::unpack(&buffer).unwrap(); - let unpacked_metas = + let unpacked_metas_pod = ExtraAccountMetaList::unpack_with_tlv_state::(&state).unwrap(); - - // Convert to Vec - let unpacked_metas_vec = unpacked_metas.data().to_vec(); - - // Assert that the unpacked metas match the updated metas and their lengths + let unpacked_metas = unpacked_metas_pod.data(); assert_eq!( - unpacked_metas_vec.len(), - updated_metas.len(), - "The length of the updated ExtraAccountMetas should match the updated array length." + unpacked_metas, updated_metas, + "The ExtraAccountMetas in the buffer should match the expected ones." ); - assert_eq!(unpacked_metas_vec, updated_metas.to_vec(), "The updated ExtraAccountMetas in the buffer should match the ones provided to the update function."); } #[test] From 93dfae6954eee9e818a419ddb8f9091b39d63403 Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Wed, 29 Nov 2023 17:48:38 +0900 Subject: [PATCH 20/39] added additional off-chain test of update instruct --- .../transfer-hook/example/tests/functional.rs | 415 +++++++++++++++++- 1 file changed, 414 insertions(+), 1 deletion(-) diff --git a/token/transfer-hook/example/tests/functional.rs b/token/transfer-hook/example/tests/functional.rs index ac56feb4498..f2b029efe43 100644 --- a/token/transfer-hook/example/tests/functional.rs +++ b/token/transfer-hook/example/tests/functional.rs @@ -787,7 +787,7 @@ async fn success_on_chain_invoke_with_updated_extra_account_metas() { let extra_account_metas_address = get_extra_account_metas_address(&mint_address, &hook_program_id); - // Create an initial acount metas list + // Create an initial account metas list let init_extra_account_metas = [ ExtraAccountMeta::new_with_pubkey(&sysvar::instructions::id(), false, false).unwrap(), ExtraAccountMeta::new_with_pubkey(&mint_authority_pubkey, true, false).unwrap(), @@ -922,3 +922,416 @@ async fn success_on_chain_invoke_with_updated_extra_account_metas() { .await .unwrap(); } + +#[tokio::test] +async fn success_execute_with_updated_extra_account_metas() { + let program_id = Pubkey::new_unique(); + let mut program_test = setup(&program_id); + + let token_program_id = spl_token_2022::id(); + let wallet = Keypair::new(); + let mint_address = Pubkey::new_unique(); + let mint_authority = Keypair::new(); + let mint_authority_pubkey = mint_authority.pubkey(); + let source = Pubkey::new_unique(); + let destination = Pubkey::new_unique(); + let decimals = 2; + let amount = 0u64; + + setup_token_accounts( + &mut program_test, + &token_program_id, + &mint_address, + &mint_authority_pubkey, + &source, + &destination, + &wallet.pubkey(), + decimals, + true, + ); + + let extra_account_metas_address = get_extra_account_metas_address(&mint_address, &program_id); + + let writable_pubkey = Pubkey::new_unique(); + + let init_extra_account_metas = [ + ExtraAccountMeta::new_with_pubkey(&sysvar::instructions::id(), false, false).unwrap(), + ExtraAccountMeta::new_with_pubkey(&mint_authority_pubkey, true, false).unwrap(), + ExtraAccountMeta::new_with_seeds( + &[ + Seed::Literal { + bytes: b"seed-prefix".to_vec(), + }, + Seed::AccountKey { index: 0 }, + ], + false, + true, + ) + .unwrap(), + ExtraAccountMeta::new_with_seeds( + &[ + Seed::InstructionData { + index: 8, // After instruction discriminator + length: 8, // `u64` (amount) + }, + Seed::AccountKey { index: 2 }, + ], + false, + true, + ) + .unwrap(), + ExtraAccountMeta::new_with_pubkey(&writable_pubkey, false, true).unwrap(), + ]; + + let extra_pda_1 = Pubkey::find_program_address( + &[ + b"seed-prefix", // Literal prefix + source.as_ref(), // Account at index 0 + ], + &program_id, + ) + .0; + let extra_pda_2 = Pubkey::find_program_address( + &[ + &amount.to_le_bytes(), // Instruction data bytes 8 to 16 + destination.as_ref(), // Account at index 2 + ], + &program_id, + ) + .0; + + let init_account_metas = [ + AccountMeta::new_readonly(sysvar::instructions::id(), false), + AccountMeta::new_readonly(mint_authority_pubkey, true), + AccountMeta::new(extra_pda_1, false), + AccountMeta::new(extra_pda_2, false), + AccountMeta::new(writable_pubkey, false), + ]; + + let mut context = program_test.start_with_context().await; + let rent = context.banks_client.get_rent().await.unwrap(); + let rent_lamports = rent + .minimum_balance(ExtraAccountMetaList::size_of(init_extra_account_metas.len()).unwrap()); + let transaction = Transaction::new_signed_with_payer( + &[ + system_instruction::transfer( + &context.payer.pubkey(), + &extra_account_metas_address, + rent_lamports, + ), + initialize_extra_account_meta_list( + &program_id, + &extra_account_metas_address, + &mint_address, + &mint_authority_pubkey, + &init_extra_account_metas, + ), + ], + Some(&context.payer.pubkey()), + &[&context.payer, &mint_authority], + context.last_blockhash, + ); + + context + .banks_client + .process_transaction(transaction) + .await + .unwrap(); + + //////////////////////// UPDATE HERE + + let updated_extra_account_metas = [ + ExtraAccountMeta::new_with_pubkey(&sysvar::instructions::id(), false, false).unwrap(), + ExtraAccountMeta::new_with_pubkey(&mint_authority_pubkey, true, false).unwrap(), + ExtraAccountMeta::new_with_seeds( + &[ + Seed::Literal { + bytes: b"updated-seed-prefix".to_vec(), + }, + Seed::AccountKey { index: 0 }, + ], + false, + true, + ) + .unwrap(), + ExtraAccountMeta::new_with_seeds( + &[ + Seed::InstructionData { + index: 8, // After instruction discriminator + length: 8, // `u64` (amount) + }, + Seed::AccountKey { index: 2 }, + ], + false, + true, + ) + .unwrap(), + ExtraAccountMeta::new_with_pubkey(&writable_pubkey, false, true).unwrap(), + ]; + + let updated_extra_pda_1 = Pubkey::find_program_address( + &[ + b"updated-seed-prefix", // Literal prefix + source.as_ref(), // Account at index 0 + ], + &program_id, + ) + .0; + let updated_extra_pda_2 = Pubkey::find_program_address( + &[ + &amount.to_le_bytes(), // Instruction data bytes 8 to 16 + destination.as_ref(), // Account at index 2 + ], + &program_id, + ) + .0; + + let updated_account_metas = [ + AccountMeta::new_readonly(sysvar::instructions::id(), false), + AccountMeta::new_readonly(mint_authority_pubkey, true), + AccountMeta::new(updated_extra_pda_1, false), + AccountMeta::new(updated_extra_pda_2, false), + AccountMeta::new(writable_pubkey, false), + ]; + + let update_transaction = Transaction::new_signed_with_payer( + &[ + system_instruction::transfer( + &context.payer.pubkey(), + &extra_account_metas_address, + rent_lamports, + ), + update_extra_account_meta_list( + &program_id, + &extra_account_metas_address, + &mint_address, + &mint_authority_pubkey, + &updated_extra_account_metas, + ), + ], + Some(&context.payer.pubkey()), + &[&context.payer, &mint_authority], + context.last_blockhash, + ); + + context + .banks_client + .process_transaction(update_transaction) + .await + .unwrap(); + + //////////////////// + + // fail with initial account metas list + { + let transaction = Transaction::new_signed_with_payer( + &[execute_with_extra_account_metas( + &program_id, + &source, + &mint_address, + &destination, + &wallet.pubkey(), + &extra_account_metas_address, + &init_account_metas, + amount, + )], + Some(&context.payer.pubkey()), + &[&context.payer, &mint_authority], + context.last_blockhash, + ); + let error = context + .banks_client + .process_transaction(transaction) + .await + .unwrap_err() + .unwrap(); + assert_eq!( + error, + TransactionError::InstructionError( + 0, + InstructionError::Custom(AccountResolutionError::IncorrectAccount as u32), + ) + ); + } + + // fail with missing account + { + let transaction = Transaction::new_signed_with_payer( + &[execute_with_extra_account_metas( + &program_id, + &source, + &mint_address, + &destination, + &wallet.pubkey(), + &extra_account_metas_address, + &updated_account_metas[..2], + amount, + )], + Some(&context.payer.pubkey()), + &[&context.payer, &mint_authority], + context.last_blockhash, + ); + let error = context + .banks_client + .process_transaction(transaction) + .await + .unwrap_err() + .unwrap(); + assert_eq!( + error, + TransactionError::InstructionError( + 0, + InstructionError::Custom(AccountResolutionError::IncorrectAccount as u32), + ) + ); + } + + // fail with wrong account + { + let extra_account_metas = [ + AccountMeta::new_readonly(sysvar::instructions::id(), false), + AccountMeta::new_readonly(mint_authority_pubkey, true), + AccountMeta::new(updated_extra_pda_1, false), + AccountMeta::new(updated_extra_pda_2, false), + AccountMeta::new(Pubkey::new_unique(), false), + ]; + let transaction = Transaction::new_signed_with_payer( + &[execute_with_extra_account_metas( + &program_id, + &source, + &mint_address, + &destination, + &wallet.pubkey(), + &extra_account_metas_address, + &extra_account_metas, + amount, + )], + Some(&context.payer.pubkey()), + &[&context.payer, &mint_authority], + context.last_blockhash, + ); + let error = context + .banks_client + .process_transaction(transaction) + .await + .unwrap_err() + .unwrap(); + assert_eq!( + error, + TransactionError::InstructionError( + 0, + InstructionError::Custom(AccountResolutionError::IncorrectAccount as u32), + ) + ); + } + + // fail with wrong PDA + let wrong_pda_2 = Pubkey::find_program_address( + &[ + &99u64.to_le_bytes(), // Wrong data + destination.as_ref(), + ], + &program_id, + ) + .0; + { + let extra_account_metas = [ + AccountMeta::new_readonly(sysvar::instructions::id(), false), + AccountMeta::new_readonly(mint_authority_pubkey, true), + AccountMeta::new(updated_extra_pda_1, false), + AccountMeta::new(wrong_pda_2, false), + AccountMeta::new(writable_pubkey, false), + ]; + let transaction = Transaction::new_signed_with_payer( + &[execute_with_extra_account_metas( + &program_id, + &source, + &mint_address, + &destination, + &wallet.pubkey(), + &extra_account_metas_address, + &extra_account_metas, + amount, + )], + Some(&context.payer.pubkey()), + &[&context.payer, &mint_authority], + context.last_blockhash, + ); + let error = context + .banks_client + .process_transaction(transaction) + .await + .unwrap_err() + .unwrap(); + assert_eq!( + error, + TransactionError::InstructionError( + 0, + InstructionError::Custom(AccountResolutionError::IncorrectAccount as u32), + ) + ); + } + + // fail with not signer + { + let extra_account_metas = [ + AccountMeta::new_readonly(sysvar::instructions::id(), false), + AccountMeta::new_readonly(mint_authority_pubkey, false), + AccountMeta::new(updated_extra_pda_1, false), + AccountMeta::new(updated_extra_pda_2, false), + AccountMeta::new(writable_pubkey, false), + ]; + let transaction = Transaction::new_signed_with_payer( + &[execute_with_extra_account_metas( + &program_id, + &source, + &mint_address, + &destination, + &wallet.pubkey(), + &extra_account_metas_address, + &extra_account_metas, + amount, + )], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + let error = context + .banks_client + .process_transaction(transaction) + .await + .unwrap_err() + .unwrap(); + assert_eq!( + error, + TransactionError::InstructionError( + 0, + InstructionError::Custom(AccountResolutionError::IncorrectAccount as u32), + ) + ); + } + + // success with correct params + { + let transaction = Transaction::new_signed_with_payer( + &[execute_with_extra_account_metas( + &program_id, + &source, + &mint_address, + &destination, + &wallet.pubkey(), + &extra_account_metas_address, + &updated_account_metas, + amount, + )], + Some(&context.payer.pubkey()), + &[&context.payer, &mint_authority], + context.last_blockhash, + ); + context + .banks_client + .process_transaction(transaction) + .await + .unwrap(); + } +} From 666a36bcf25a6b3e119ff479954c7181c65fc2bc Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Wed, 29 Nov 2023 17:58:17 +0900 Subject: [PATCH 21/39] made on-chain invoke update test to match original --- .../transfer-hook/example/tests/functional.rs | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/token/transfer-hook/example/tests/functional.rs b/token/transfer-hook/example/tests/functional.rs index f2b029efe43..96f3f973a70 100644 --- a/token/transfer-hook/example/tests/functional.rs +++ b/token/transfer-hook/example/tests/functional.rs @@ -786,6 +786,7 @@ async fn success_on_chain_invoke_with_updated_extra_account_metas() { let extra_account_metas_address = get_extra_account_metas_address(&mint_address, &hook_program_id); + let writable_pubkey = Pubkey::new_unique(); // Create an initial account metas list let init_extra_account_metas = [ @@ -802,6 +803,19 @@ async fn success_on_chain_invoke_with_updated_extra_account_metas() { true, ) .unwrap(), + ExtraAccountMeta::new_with_seeds( + &[ + Seed::InstructionData { + index: 8, // After instruction discriminator + length: 8, // `u64` (amount) + }, + Seed::AccountKey { index: 2 }, + ], + false, + true, + ) + .unwrap(), + ExtraAccountMeta::new_with_pubkey(&writable_pubkey, false, true).unwrap(), ]; let mut context = program_test.start_with_context().await; @@ -849,6 +863,19 @@ async fn success_on_chain_invoke_with_updated_extra_account_metas() { true, ) .unwrap(), + ExtraAccountMeta::new_with_seeds( + &[ + Seed::InstructionData { + index: 8, // After instruction discriminator + length: 8, // `u64` (amount) + }, + Seed::AccountKey { index: 2 }, + ], + false, + true, + ) + .unwrap(), + ExtraAccountMeta::new_with_pubkey(&writable_pubkey, false, true).unwrap(), ]; let rent = context.banks_client.get_rent().await.unwrap(); @@ -880,7 +907,7 @@ async fn success_on_chain_invoke_with_updated_extra_account_metas() { .await .unwrap(); - let updated_extra_pda = Pubkey::find_program_address( + let updated_extra_pda_1 = Pubkey::find_program_address( &[ b"updated-seed-prefix", // Literal prefix source.as_ref(), // Account at index 0 @@ -888,11 +915,21 @@ async fn success_on_chain_invoke_with_updated_extra_account_metas() { &hook_program_id, ) .0; + let extra_pda_2 = Pubkey::find_program_address( + &[ + &amount.to_le_bytes(), // Instruction data bytes 8 to 16 + destination.as_ref(), // Account at index 2 + ], + &hook_program_id, + ) + .0; let test_updated_extra_account_metas = [ AccountMeta::new_readonly(sysvar::instructions::id(), false), AccountMeta::new_readonly(mint_authority_pubkey, true), - AccountMeta::new(updated_extra_pda, false), + AccountMeta::new(updated_extra_pda_1, false), + AccountMeta::new(extra_pda_2, false), + AccountMeta::new(writable_pubkey, false), ]; // Use updated account metas list From 42621994d5ec0d13a3e5d3495df7862916448420 Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:05:42 +0900 Subject: [PATCH 22/39] moved helper function up to others --- token/transfer-hook/cli/src/main.rs | 30 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/token/transfer-hook/cli/src/main.rs b/token/transfer-hook/cli/src/main.rs index 6adb6dd3a80..970a39fc8f4 100644 --- a/token/transfer-hook/cli/src/main.rs +++ b/token/transfer-hook/cli/src/main.rs @@ -57,6 +57,21 @@ fn clap_is_valid_pubkey(arg: &str) -> Result<(), String> { is_valid_pubkey(arg) } +// Helper function to calculate the required lamports +async fn calculate_transfer_lamports( + rpc_client: &RpcClient, + account_address: &Pubkey, + account_size: usize, +) -> Result> { + let required_lamports = rpc_client + .get_minimum_balance_for_rent_exemption(account_size) + .await + .map_err(|err| format!("error: unable to fetch rent-exemption: {err}"))?; + let account_info = rpc_client.get_account(account_address).await; + let current_lamports = account_info.map(|a| a.lamports).unwrap_or(0); + Ok(required_lamports.saturating_sub(current_lamports)) +} + struct Config { commitment_config: CommitmentConfig, default_signer: Box, @@ -189,21 +204,6 @@ async fn process_update_extra_account_metas( .map_err(|err| format!("error: send transaction: {err}").into()) } -// Helper function to calculate the required lamports -async fn calculate_transfer_lamports( - rpc_client: &RpcClient, - account_address: &Pubkey, - account_size: usize, -) -> Result> { - let required_lamports = rpc_client - .get_minimum_balance_for_rent_exemption(account_size) - .await - .map_err(|err| format!("error: unable to fetch rent-exemption: {err}"))?; - let account_info = rpc_client.get_account(account_address).await; - let current_lamports = account_info.map(|a| a.lamports).unwrap_or(0); - Ok(required_lamports.saturating_sub(current_lamports)) -} - #[tokio::main] async fn main() -> Result<(), Box> { let app_matches = Command::new(crate_name!()) From 54e77becb75787b68772a0fcf8db6a56e35fa1e2 Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Wed, 29 Nov 2023 19:57:04 +0900 Subject: [PATCH 23/39] refactored create and update with helpers --- token/transfer-hook/cli/src/main.rs | 159 +++++++++++++++------------- 1 file changed, 88 insertions(+), 71 deletions(-) diff --git a/token/transfer-hook/cli/src/main.rs b/token/transfer-hook/cli/src/main.rs index 970a39fc8f4..2ae7027b98b 100644 --- a/token/transfer-hook/cli/src/main.rs +++ b/token/transfer-hook/cli/src/main.rs @@ -10,12 +10,13 @@ use { solana_sdk::{ commitment_config::CommitmentConfig, instruction::AccountMeta, + instruction::Instruction, pubkey::Pubkey, signature::{Signature, Signer}, system_instruction, system_program, transaction::Transaction, }, - spl_tlv_account_resolution::state::ExtraAccountMetaList, + spl_tlv_account_resolution::{account::ExtraAccountMeta, state::ExtraAccountMetaList}, spl_transfer_hook_interface::{ get_extra_account_metas_address, instruction::{initialize_extra_account_meta_list, update_extra_account_meta_list}, @@ -57,6 +58,13 @@ fn clap_is_valid_pubkey(arg: &str) -> Result<(), String> { is_valid_pubkey(arg) } +fn prepare_extra_account_metas(transfer_hook_accounts: Vec) -> Vec { + transfer_hook_accounts + .into_iter() + .map(|v| v.into()) + .collect::>() +} + // Helper function to calculate the required lamports async fn calculate_transfer_lamports( rpc_client: &RpcClient, @@ -72,6 +80,59 @@ async fn calculate_transfer_lamports( Ok(required_lamports.saturating_sub(current_lamports)) } +async fn prepare_transaction_with_initial_lamports_transfer( + rpc_client: &RpcClient, + payer: &dyn Signer, + recipient_address: &Pubkey, + extra_account_metas: &Vec, + instruction: Instruction, +) -> Result> { + let account_size = ExtraAccountMetaList::size_of(extra_account_metas.len())?; + let transfer_lamports = + calculate_transfer_lamports(rpc_client, recipient_address, account_size).await?; + + let mut instructions = vec![]; + if transfer_lamports > 0 { + instructions.push(system_instruction::transfer( + &payer.pubkey(), + recipient_address, + transfer_lamports, + )); + } + + instructions.push(instruction); + + let transaction = Transaction::new_with_payer(&instructions, Some(&payer.pubkey())); + + Ok(transaction) +} + +async fn finalize_and_send_transaction( + transaction: &mut Transaction, + rpc_client: &RpcClient, + payer: &dyn Signer, + mint_authority: &dyn Signer, +) -> Result> { + let mut signers = vec![payer]; + if payer.pubkey() != mint_authority.pubkey() { + signers.push(mint_authority); + } + + let blockhash = rpc_client + .get_latest_blockhash() + .await + .map_err(|err| format!("error: unable to get latest blockhash: {err}"))?; + + transaction + .try_sign(&signers, blockhash) + .map_err(|err| format!("error: failed to sign transaction: {err}"))?; + + rpc_client + .send_and_confirm_transaction_with_spinner(transaction) + .await + .map_err(|err| format!("error: send transaction: {err}").into()) +} + struct Config { commitment_config: CommitmentConfig, default_signer: Box, @@ -88,14 +149,8 @@ async fn process_create_extra_account_metas( payer: &dyn Signer, ) -> Result> { let extra_account_metas_address = get_extra_account_metas_address(token, program_id); - let extra_account_metas = transfer_hook_accounts - .into_iter() - .map(|v| v.into()) - .collect::>(); - - let length = extra_account_metas.len(); - let account_size = ExtraAccountMetaList::size_of(length)?; + // Check if the extra meta account has already been initialized let extra_account_metas_account = rpc_client.get_account(&extra_account_metas_address).await; if let Ok(account) = &extra_account_metas_account { if account.owner != system_program::id() { @@ -103,42 +158,26 @@ async fn process_create_extra_account_metas( } } - let transfer_lamports = - calculate_transfer_lamports(rpc_client, &extra_account_metas_address, account_size).await?; + let extra_account_metas = prepare_extra_account_metas(transfer_hook_accounts); - let mut ixs = vec![]; - if transfer_lamports > 0 { - ixs.push(system_instruction::transfer( - &payer.pubkey(), - &extra_account_metas_address, - transfer_lamports, - )); - } - ixs.push(initialize_extra_account_meta_list( + let instruction = initialize_extra_account_meta_list( program_id, &extra_account_metas_address, token, &mint_authority.pubkey(), &extra_account_metas, - )); + ); - let mut transaction = Transaction::new_with_payer(&ixs, Some(&payer.pubkey())); - let blockhash = rpc_client - .get_latest_blockhash() - .await - .map_err(|err| format!("error: unable to get latest blockhash: {err}"))?; - let mut signers = vec![payer]; - if payer.pubkey() != mint_authority.pubkey() { - signers.push(mint_authority); - } - transaction - .try_sign(&signers, blockhash) - .map_err(|err| format!("error: failed to sign transaction: {err}"))?; + let mut transaction = prepare_transaction_with_initial_lamports_transfer( + rpc_client, + payer, + &extra_account_metas_address, + &extra_account_metas, + instruction, + ) + .await?; - rpc_client - .send_and_confirm_transaction_with_spinner(&transaction) - .await - .map_err(|err| format!("error: send transaction: {err}").into()) + finalize_and_send_transaction(&mut transaction, rpc_client, payer, mint_authority).await } async fn process_update_extra_account_metas( @@ -150,14 +189,8 @@ async fn process_update_extra_account_metas( payer: &dyn Signer, ) -> Result> { let extra_account_metas_address = get_extra_account_metas_address(token, program_id); - let extra_account_metas = transfer_hook_accounts - .into_iter() - .map(|v| v.into()) - .collect::>(); - - let length = extra_account_metas.len(); - let account_size = ExtraAccountMetaList::size_of(length)?; + // Check if the extra meta account has been initialized first let extra_account_metas_account = rpc_client.get_account(&extra_account_metas_address).await; if extra_account_metas_account.is_err() { return Err(format!( @@ -166,42 +199,26 @@ async fn process_update_extra_account_metas( .into()); } - let transfer_lamports = - calculate_transfer_lamports(rpc_client, &extra_account_metas_address, account_size).await?; + let extra_account_metas = prepare_extra_account_metas(transfer_hook_accounts); - let mut ixs = vec![]; - if transfer_lamports > 0 { - ixs.push(system_instruction::transfer( - &payer.pubkey(), - &extra_account_metas_address, - transfer_lamports, - )); - } - ixs.push(update_extra_account_meta_list( + let instruction = update_extra_account_meta_list( program_id, &extra_account_metas_address, token, &mint_authority.pubkey(), &extra_account_metas, - )); + ); - let mut transaction = Transaction::new_with_payer(&ixs, Some(&payer.pubkey())); - let blockhash = rpc_client - .get_latest_blockhash() - .await - .map_err(|err| format!("error: unable to get latest blockhash: {err}"))?; - let mut signers = vec![payer]; - if payer.pubkey() != mint_authority.pubkey() { - signers.push(mint_authority); - } - transaction - .try_sign(&signers, blockhash) - .map_err(|err| format!("error: failed to sign transaction: {err}"))?; + let mut transaction = prepare_transaction_with_initial_lamports_transfer( + rpc_client, + payer, + &extra_account_metas_address, + &extra_account_metas, + instruction, + ) + .await?; - rpc_client - .send_and_confirm_transaction_with_spinner(&transaction) - .await - .map_err(|err| format!("error: send transaction: {err}").into()) + finalize_and_send_transaction(&mut transaction, rpc_client, payer, mint_authority).await } #[tokio::main] From de0e2f8c15c3b8e6e638eceddd90e5102f0d7f03 Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Thu, 30 Nov 2023 23:23:54 +0900 Subject: [PATCH 24/39] rustfmt: fix --- token/transfer-hook/interface/src/instruction.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/token/transfer-hook/interface/src/instruction.rs b/token/transfer-hook/interface/src/instruction.rs index 29abc2b99fc..546ebd781d2 100644 --- a/token/transfer-hook/interface/src/instruction.rs +++ b/token/transfer-hook/interface/src/instruction.rs @@ -32,8 +32,9 @@ pub enum TransferHookInstruction { /// Amount of tokens to transfer amount: u64, }, - /// Initializes the extra account metas on an account, writing into - /// the first open TLV space. + + /// Initializes the extra account metas on an account, writing into the + /// first open TLV space. /// /// Accounts expected by this instruction: /// @@ -45,7 +46,8 @@ pub enum TransferHookInstruction { /// List of `ExtraAccountMeta`s to write into the account extra_account_metas: Vec, }, - /// Updates the extra account metas on an account by overwriting the existing list. + /// Updates the extra account metas on an account by overwriting the + /// existing list. /// /// Accounts expected by this instruction: /// @@ -53,7 +55,8 @@ pub enum TransferHookInstruction { /// 1. `[]` Mint /// 2. `[s]` Mint authority UpdateExtraAccountMetaList { - /// The new list of `ExtraAccountMetas` to overwrite the existing entry in the account. + /// The new list of `ExtraAccountMetas` to overwrite the existing entry + /// in the account. extra_account_metas: Vec, }, } From f5b1a36ae389da2e5898bbc32cd0bbdc8de829e9 Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Thu, 30 Nov 2023 23:29:17 +0900 Subject: [PATCH 25/39] rustfmt: fix --- token/transfer-hook/cli/src/main.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/token/transfer-hook/cli/src/main.rs b/token/transfer-hook/cli/src/main.rs index 2ae7027b98b..59284ae5b65 100644 --- a/token/transfer-hook/cli/src/main.rs +++ b/token/transfer-hook/cli/src/main.rs @@ -9,8 +9,7 @@ use { solana_remote_wallet::remote_wallet::RemoteWalletManager, solana_sdk::{ commitment_config::CommitmentConfig, - instruction::AccountMeta, - instruction::Instruction, + instruction::{AccountMeta, Instruction}, pubkey::Pubkey, signature::{Signature, Signer}, system_instruction, system_program, From f20c00794ef630b685fbb1f7bc92b7723c5ae9f3 Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Thu, 30 Nov 2023 23:30:48 +0900 Subject: [PATCH 26/39] removed commented out system program in update --- token/transfer-hook/interface/src/instruction.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/token/transfer-hook/interface/src/instruction.rs b/token/transfer-hook/interface/src/instruction.rs index 546ebd781d2..39639d5d646 100644 --- a/token/transfer-hook/interface/src/instruction.rs +++ b/token/transfer-hook/interface/src/instruction.rs @@ -242,7 +242,6 @@ pub fn update_extra_account_meta_list( AccountMeta::new(*extra_account_metas_pubkey, false), AccountMeta::new_readonly(*mint_pubkey, false), AccountMeta::new_readonly(*authority_pubkey, true), - // AccountMeta::new_readonly(system_program::id(), false), ]; Instruction { From 5e62a04e6f2a2b01101d9b1abca63fec92cd013a Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Thu, 30 Nov 2023 23:56:30 +0900 Subject: [PATCH 27/39] renamed helpers and removed unnecessary helper --- token/transfer-hook/cli/src/main.rs | 35 ++++++++++++++--------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/token/transfer-hook/cli/src/main.rs b/token/transfer-hook/cli/src/main.rs index 59284ae5b65..45b1d47a7af 100644 --- a/token/transfer-hook/cli/src/main.rs +++ b/token/transfer-hook/cli/src/main.rs @@ -57,15 +57,8 @@ fn clap_is_valid_pubkey(arg: &str) -> Result<(), String> { is_valid_pubkey(arg) } -fn prepare_extra_account_metas(transfer_hook_accounts: Vec) -> Vec { - transfer_hook_accounts - .into_iter() - .map(|v| v.into()) - .collect::>() -} - -// Helper function to calculate the required lamports -async fn calculate_transfer_lamports( +// Helper function to calculate the required lamports for rent +async fn calculate_rent_lamports( rpc_client: &RpcClient, account_address: &Pubkey, account_size: usize, @@ -79,7 +72,7 @@ async fn calculate_transfer_lamports( Ok(required_lamports.saturating_sub(current_lamports)) } -async fn prepare_transaction_with_initial_lamports_transfer( +async fn build_transaction_with_rent_transfer( rpc_client: &RpcClient, payer: &dyn Signer, recipient_address: &Pubkey, @@ -88,7 +81,7 @@ async fn prepare_transaction_with_initial_lamports_transfer( ) -> Result> { let account_size = ExtraAccountMetaList::size_of(extra_account_metas.len())?; let transfer_lamports = - calculate_transfer_lamports(rpc_client, recipient_address, account_size).await?; + calculate_rent_lamports(rpc_client, recipient_address, account_size).await?; let mut instructions = vec![]; if transfer_lamports > 0 { @@ -106,7 +99,7 @@ async fn prepare_transaction_with_initial_lamports_transfer( Ok(transaction) } -async fn finalize_and_send_transaction( +async fn sign_and_send_transaction( transaction: &mut Transaction, rpc_client: &RpcClient, payer: &dyn Signer, @@ -157,7 +150,10 @@ async fn process_create_extra_account_metas( } } - let extra_account_metas = prepare_extra_account_metas(transfer_hook_accounts); + let extra_account_metas = transfer_hook_accounts + .into_iter() + .map(|v| v.into()) + .collect::>(); let instruction = initialize_extra_account_meta_list( program_id, @@ -167,7 +163,7 @@ async fn process_create_extra_account_metas( &extra_account_metas, ); - let mut transaction = prepare_transaction_with_initial_lamports_transfer( + let mut transaction = build_transaction_with_rent_transfer( rpc_client, payer, &extra_account_metas_address, @@ -176,7 +172,7 @@ async fn process_create_extra_account_metas( ) .await?; - finalize_and_send_transaction(&mut transaction, rpc_client, payer, mint_authority).await + sign_and_send_transaction(&mut transaction, rpc_client, payer, mint_authority).await } async fn process_update_extra_account_metas( @@ -198,7 +194,10 @@ async fn process_update_extra_account_metas( .into()); } - let extra_account_metas = prepare_extra_account_metas(transfer_hook_accounts); + let extra_account_metas = transfer_hook_accounts + .into_iter() + .map(|v| v.into()) + .collect::>(); let instruction = update_extra_account_meta_list( program_id, @@ -208,7 +207,7 @@ async fn process_update_extra_account_metas( &extra_account_metas, ); - let mut transaction = prepare_transaction_with_initial_lamports_transfer( + let mut transaction = build_transaction_with_rent_transfer( rpc_client, payer, &extra_account_metas_address, @@ -217,7 +216,7 @@ async fn process_update_extra_account_metas( ) .await?; - finalize_and_send_transaction(&mut transaction, rpc_client, payer, mint_authority).await + sign_and_send_transaction(&mut transaction, rpc_client, payer, mint_authority).await } #[tokio::main] From 1ba6c5972dd06f75ccecf437af1da5f76e07f1ac Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Fri, 1 Dec 2023 16:29:47 +0900 Subject: [PATCH 28/39] moved test helper up --- libraries/tlv-account-resolution/src/state.rs | 55 +++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/libraries/tlv-account-resolution/src/state.rs b/libraries/tlv-account-resolution/src/state.rs index 3a4125f5f3b..011e1c08657 100644 --- a/libraries/tlv-account-resolution/src/state.rs +++ b/libraries/tlv-account-resolution/src/state.rs @@ -1309,34 +1309,6 @@ mod tests { } #[tokio::test] - async fn update_extra_account_meta_list() { - // Create list of initial metas - let initial_metas = [ - ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, true).unwrap(), - ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, false).unwrap(), - ]; - - // Create updated metas list of the same size - let updated_metas_1 = [ - ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, true).unwrap(), - ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, false).unwrap(), - ]; - initialize_update_and_assert_metas(&initial_metas, &updated_metas_1).await; - - // Create updated and larger list of metas - let updated_metas_2 = [ - ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, true).unwrap(), - ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, false).unwrap(), - ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, true).unwrap(), - ]; - initialize_update_and_assert_metas(&initial_metas, &updated_metas_2).await; - - // Create updated and smaller list of metas - let updated_metas_3 = - [ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, true).unwrap()]; - initialize_update_and_assert_metas(&initial_metas, &updated_metas_3).await; - } - async fn initialize_update_and_assert_metas( initial_metas: &[ExtraAccountMeta], updated_metas: &[ExtraAccountMeta], @@ -1365,6 +1337,33 @@ mod tests { "The ExtraAccountMetas in the buffer should match the expected ones." ); } + async fn update_extra_account_meta_list() { + // Create list of initial metas + let initial_metas = [ + ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, true).unwrap(), + ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, false).unwrap(), + ]; + + // Create updated metas list of the same size + let updated_metas_1 = [ + ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, true).unwrap(), + ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, false).unwrap(), + ]; + initialize_update_and_assert_metas(&initial_metas, &updated_metas_1).await; + + // Create updated and larger list of metas + let updated_metas_2 = [ + ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, true).unwrap(), + ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, false).unwrap(), + ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, true).unwrap(), + ]; + initialize_update_and_assert_metas(&initial_metas, &updated_metas_2).await; + + // Create updated and smaller list of metas + let updated_metas_3 = + [ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, true).unwrap()]; + initialize_update_and_assert_metas(&initial_metas, &updated_metas_3).await; + } #[test] fn check_account_infos_test() { From 9a22ce8e9ad97c064523de3a2e2bc2bb802a9bfd Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Fri, 1 Dec 2023 16:58:50 +0900 Subject: [PATCH 29/39] fixed test attribute location --- libraries/tlv-account-resolution/src/state.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/tlv-account-resolution/src/state.rs b/libraries/tlv-account-resolution/src/state.rs index 011e1c08657..03ba22c54eb 100644 --- a/libraries/tlv-account-resolution/src/state.rs +++ b/libraries/tlv-account-resolution/src/state.rs @@ -1308,7 +1308,6 @@ mod tests { } } - #[tokio::test] async fn initialize_update_and_assert_metas( initial_metas: &[ExtraAccountMeta], updated_metas: &[ExtraAccountMeta], @@ -1337,6 +1336,8 @@ mod tests { "The ExtraAccountMetas in the buffer should match the expected ones." ); } + + #[tokio::test] async fn update_extra_account_meta_list() { // Create list of initial metas let initial_metas = [ From 9cf2129ae44e5131eefc4bc6e86878e85612e278 Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Fri, 1 Dec 2023 17:09:06 +0900 Subject: [PATCH 30/39] removed multiple init extra account metas in test --- libraries/tlv-account-resolution/src/state.rs | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/libraries/tlv-account-resolution/src/state.rs b/libraries/tlv-account-resolution/src/state.rs index 03ba22c54eb..6bbaa9e8aa3 100644 --- a/libraries/tlv-account-resolution/src/state.rs +++ b/libraries/tlv-account-resolution/src/state.rs @@ -1308,23 +1308,15 @@ mod tests { } } - async fn initialize_update_and_assert_metas( - initial_metas: &[ExtraAccountMeta], - updated_metas: &[ExtraAccountMeta], - ) { - // initialize - let initial_account_size = ExtraAccountMetaList::size_of(initial_metas.len()).unwrap(); - let mut buffer = vec![0; initial_account_size]; - ExtraAccountMetaList::init::(&mut buffer, initial_metas).unwrap(); - - // resize buffere if necessary + async fn update_and_assert_metas(buffer: &mut Vec, updated_metas: &[ExtraAccountMeta]) { + // resize buffer if necessary let account_size = ExtraAccountMetaList::size_of(updated_metas.len()).unwrap(); - if account_size > initial_account_size { + if account_size > buffer.len() { buffer.resize(account_size, 0); } // update - ExtraAccountMetaList::update::(&mut buffer, updated_metas).unwrap(); + ExtraAccountMetaList::update::(buffer, updated_metas).unwrap(); // retreive metas and assert let state = TlvStateBorrowed::unpack(&buffer).unwrap(); @@ -1345,12 +1337,17 @@ mod tests { ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, false).unwrap(), ]; + // initialize + let initial_account_size = ExtraAccountMetaList::size_of(initial_metas.len()).unwrap(); + let mut buffer = vec![0; initial_account_size]; + ExtraAccountMetaList::init::(&mut buffer, &initial_metas).unwrap(); + // Create updated metas list of the same size let updated_metas_1 = [ ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, true).unwrap(), ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, false).unwrap(), ]; - initialize_update_and_assert_metas(&initial_metas, &updated_metas_1).await; + update_and_assert_metas(&mut buffer, &updated_metas_1).await; // Create updated and larger list of metas let updated_metas_2 = [ @@ -1358,12 +1355,12 @@ mod tests { ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, false).unwrap(), ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, true).unwrap(), ]; - initialize_update_and_assert_metas(&initial_metas, &updated_metas_2).await; + update_and_assert_metas(&mut buffer, &updated_metas_2).await; // Create updated and smaller list of metas let updated_metas_3 = [ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, true).unwrap()]; - initialize_update_and_assert_metas(&initial_metas, &updated_metas_3).await; + update_and_assert_metas(&mut buffer, &updated_metas_3).await; } #[test] From 435092930639e6de470d38e3b4b0f4f0b2cbc6f7 Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Fri, 1 Dec 2023 19:16:28 +0900 Subject: [PATCH 31/39] added instruction assert to update test --- libraries/tlv-account-resolution/src/state.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/libraries/tlv-account-resolution/src/state.rs b/libraries/tlv-account-resolution/src/state.rs index 6bbaa9e8aa3..b2d779d10d0 100644 --- a/libraries/tlv-account-resolution/src/state.rs +++ b/libraries/tlv-account-resolution/src/state.rs @@ -1327,6 +1327,24 @@ mod tests { unpacked_metas, updated_metas, "The ExtraAccountMetas in the buffer should match the expected ones." ); + + let mock_rpc = MockRpc::setup(&[]); + + let mut instruction = Instruction::new_with_bytes(Pubkey::new_unique(), &[], vec![]); + ExtraAccountMetaList::add_to_instruction::( + &mut instruction, + |pubkey| mock_rpc.get_account_data(pubkey), + &buffer, + ) + .await + .unwrap(); + + let check_metas = updated_metas + .iter() + .map(|e| AccountMeta::try_from(e).unwrap()) + .collect::>(); + + assert_eq!(instruction.accounts, check_metas,); } #[tokio::test] From a3e6c7e7ad2d3416ce2b4b43c1ae1106b7d5da07 Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Sat, 2 Dec 2023 18:40:16 +0900 Subject: [PATCH 32/39] renamed transfer address to extra account metas --- token/transfer-hook/cli/src/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/token/transfer-hook/cli/src/main.rs b/token/transfer-hook/cli/src/main.rs index 45b1d47a7af..06b078887b2 100644 --- a/token/transfer-hook/cli/src/main.rs +++ b/token/transfer-hook/cli/src/main.rs @@ -75,19 +75,19 @@ async fn calculate_rent_lamports( async fn build_transaction_with_rent_transfer( rpc_client: &RpcClient, payer: &dyn Signer, - recipient_address: &Pubkey, + extra_account_metas_address: &Pubkey, extra_account_metas: &Vec, instruction: Instruction, ) -> Result> { let account_size = ExtraAccountMetaList::size_of(extra_account_metas.len())?; let transfer_lamports = - calculate_rent_lamports(rpc_client, recipient_address, account_size).await?; + calculate_rent_lamports(rpc_client, extra_account_metas_address, account_size).await?; let mut instructions = vec![]; if transfer_lamports > 0 { instructions.push(system_instruction::transfer( &payer.pubkey(), - recipient_address, + extra_account_metas_address, transfer_lamports, )); } From 58d7011e8042085bb10a5403ed2d7585cc1775e5 Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Sun, 3 Dec 2023 13:22:22 +0900 Subject: [PATCH 33/39] rustfmt: comment fix --- token/transfer-hook/example/src/processor.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/token/transfer-hook/example/src/processor.rs b/token/transfer-hook/example/src/processor.rs index 8d95a77d21e..f94f4f41840 100644 --- a/token/transfer-hook/example/src/processor.rs +++ b/token/transfer-hook/example/src/processor.rs @@ -174,7 +174,8 @@ pub fn process_update_extra_account_meta_list( let account_size = ExtraAccountMetaList::size_of(length)?; let original_account_size = extra_account_metas_info.data_len(); - // If the new extra_account_metas length is different, resize the account and update + // If the new extra_account_metas length is different, resize the account and + // update if account_size >= original_account_size { extra_account_metas_info.realloc(account_size, false)?; let mut data = extra_account_metas_info.try_borrow_mut_data()?; From 451aaf4f0fdd2397f84f93b90d72a488246c5248 Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Sun, 3 Dec 2023 13:29:31 +0900 Subject: [PATCH 34/39] clippy: fix --- libraries/tlv-account-resolution/src/state.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/tlv-account-resolution/src/state.rs b/libraries/tlv-account-resolution/src/state.rs index b2d779d10d0..e565179ffb2 100644 --- a/libraries/tlv-account-resolution/src/state.rs +++ b/libraries/tlv-account-resolution/src/state.rs @@ -1319,7 +1319,7 @@ mod tests { ExtraAccountMetaList::update::(buffer, updated_metas).unwrap(); // retreive metas and assert - let state = TlvStateBorrowed::unpack(&buffer).unwrap(); + let state = TlvStateBorrowed::unpack(buffer).unwrap(); let unpacked_metas_pod = ExtraAccountMetaList::unpack_with_tlv_state::(&state).unwrap(); let unpacked_metas = unpacked_metas_pod.data(); @@ -1334,7 +1334,7 @@ mod tests { ExtraAccountMetaList::add_to_instruction::( &mut instruction, |pubkey| mock_rpc.get_account_data(pubkey), - &buffer, + buffer, ) .await .unwrap(); From 9fe5c86a745b62eb44dc23bec10e882f96bf13f9 Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Sun, 3 Dec 2023 15:29:15 +0900 Subject: [PATCH 35/39] added update test with simple PDA --- libraries/tlv-account-resolution/src/state.rs | 65 ++++++++++++++++--- 1 file changed, 55 insertions(+), 10 deletions(-) diff --git a/libraries/tlv-account-resolution/src/state.rs b/libraries/tlv-account-resolution/src/state.rs index e565179ffb2..c206095347f 100644 --- a/libraries/tlv-account-resolution/src/state.rs +++ b/libraries/tlv-account-resolution/src/state.rs @@ -1308,7 +1308,12 @@ mod tests { } } - async fn update_and_assert_metas(buffer: &mut Vec, updated_metas: &[ExtraAccountMeta]) { + async fn update_and_assert_metas( + program_id: Pubkey, + buffer: &mut Vec, + updated_metas: &[ExtraAccountMeta], + check_metas: &[AccountMeta], + ) { // resize buffer if necessary let account_size = ExtraAccountMetaList::size_of(updated_metas.len()).unwrap(); if account_size > buffer.len() { @@ -1330,7 +1335,7 @@ mod tests { let mock_rpc = MockRpc::setup(&[]); - let mut instruction = Instruction::new_with_bytes(Pubkey::new_unique(), &[], vec![]); + let mut instruction = Instruction::new_with_bytes(program_id, &[], vec![]); ExtraAccountMetaList::add_to_instruction::( &mut instruction, |pubkey| mock_rpc.get_account_data(pubkey), @@ -1339,16 +1344,13 @@ mod tests { .await .unwrap(); - let check_metas = updated_metas - .iter() - .map(|e| AccountMeta::try_from(e).unwrap()) - .collect::>(); - assert_eq!(instruction.accounts, check_metas,); } #[tokio::test] async fn update_extra_account_meta_list() { + let program_id = Pubkey::new_unique(); + // Create list of initial metas let initial_metas = [ ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, true).unwrap(), @@ -1365,7 +1367,11 @@ mod tests { ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, true).unwrap(), ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, false).unwrap(), ]; - update_and_assert_metas(&mut buffer, &updated_metas_1).await; + let check_metas_1 = updated_metas_1 + .iter() + .map(|e| AccountMeta::try_from(e).unwrap()) + .collect::>(); + update_and_assert_metas(program_id, &mut buffer, &updated_metas_1, &check_metas_1).await; // Create updated and larger list of metas let updated_metas_2 = [ @@ -1373,12 +1379,51 @@ mod tests { ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, false).unwrap(), ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), false, true).unwrap(), ]; - update_and_assert_metas(&mut buffer, &updated_metas_2).await; + let check_metas_2 = updated_metas_2 + .iter() + .map(|e| AccountMeta::try_from(e).unwrap()) + .collect::>(); + update_and_assert_metas(program_id, &mut buffer, &updated_metas_2, &check_metas_2).await; // Create updated and smaller list of metas let updated_metas_3 = [ExtraAccountMeta::new_with_pubkey(&Pubkey::new_unique(), true, true).unwrap()]; - update_and_assert_metas(&mut buffer, &updated_metas_3).await; + let check_metas_3 = updated_metas_3 + .iter() + .map(|e| AccountMeta::try_from(e).unwrap()) + .collect::>(); + update_and_assert_metas(program_id, &mut buffer, &updated_metas_3, &check_metas_3).await; + + // Create updated list of metas with a simple PDA + let seed_pubkey = Pubkey::new_unique(); + let updated_metas_4 = [ + ExtraAccountMeta::new_with_pubkey(&seed_pubkey, true, true).unwrap(), + ExtraAccountMeta::new_with_seeds( + &[ + Seed::Literal { + bytes: b"seed-prefix".to_vec(), + }, + Seed::AccountKey { index: 0 }, + ], + false, + true, + ) + .unwrap(), + ]; + let simple_pda = Pubkey::find_program_address( + &[ + b"seed-prefix", // Literal prefix + seed_pubkey.as_ref(), // Account at index 0 + ], + &program_id, + ) + .0; + let check_metas_4 = [ + AccountMeta::new(seed_pubkey, true), + AccountMeta::new(simple_pda, false), + ]; + + update_and_assert_metas(program_id, &mut buffer, &updated_metas_4, &check_metas_4).await; } #[test] From a9449529f3ef436042c93ee8e6f4532165a73283 Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Sun, 3 Dec 2023 17:26:48 +0900 Subject: [PATCH 36/39] made more changes to updated metas in test --- .../transfer-hook/example/tests/functional.rs | 50 ++++++++++++++----- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/token/transfer-hook/example/tests/functional.rs b/token/transfer-hook/example/tests/functional.rs index 96f3f973a70..4e078c1fde0 100644 --- a/token/transfer-hook/example/tests/functional.rs +++ b/token/transfer-hook/example/tests/functional.rs @@ -1028,6 +1028,7 @@ async fn success_execute_with_updated_extra_account_metas() { &program_id, ) .0; + let extra_pda_2 = Pubkey::find_program_address( &[ &amount.to_le_bytes(), // Instruction data bytes 8 to 16 @@ -1075,8 +1076,10 @@ async fn success_execute_with_updated_extra_account_metas() { .await .unwrap(); - //////////////////////// UPDATE HERE + let updated_amount = 1u64; + let updated_writable_pubkey = Pubkey::new_unique(); + // Create updated extra account metas let updated_extra_account_metas = [ ExtraAccountMeta::new_with_pubkey(&sysvar::instructions::id(), false, false).unwrap(), ExtraAccountMeta::new_with_pubkey(&mint_authority_pubkey, true, false).unwrap(), @@ -1103,7 +1106,18 @@ async fn success_execute_with_updated_extra_account_metas() { true, ) .unwrap(), - ExtraAccountMeta::new_with_pubkey(&writable_pubkey, false, true).unwrap(), + ExtraAccountMeta::new_with_pubkey(&updated_writable_pubkey, false, true).unwrap(), + ExtraAccountMeta::new_with_seeds( + &[ + Seed::Literal { + bytes: b"new-seed-prefix".to_vec(), + }, + Seed::AccountKey { index: 0 }, + ], + false, + true, + ) + .unwrap(), ]; let updated_extra_pda_1 = Pubkey::find_program_address( @@ -1114,10 +1128,21 @@ async fn success_execute_with_updated_extra_account_metas() { &program_id, ) .0; + let updated_extra_pda_2 = Pubkey::find_program_address( &[ - &amount.to_le_bytes(), // Instruction data bytes 8 to 16 - destination.as_ref(), // Account at index 2 + &updated_amount.to_le_bytes(), // Instruction data bytes 8 to 16 + destination.as_ref(), // Account at index 2 + ], + &program_id, + ) + .0; + + // add another PDA + let new_extra_pda = Pubkey::find_program_address( + &[ + b"new-seed-prefix", // Literal prefix + source.as_ref(), // Account at index 0 ], &program_id, ) @@ -1128,7 +1153,8 @@ async fn success_execute_with_updated_extra_account_metas() { AccountMeta::new_readonly(mint_authority_pubkey, true), AccountMeta::new(updated_extra_pda_1, false), AccountMeta::new(updated_extra_pda_2, false), - AccountMeta::new(writable_pubkey, false), + AccountMeta::new(updated_writable_pubkey, false), + AccountMeta::new(new_extra_pda, false), ]; let update_transaction = Transaction::new_signed_with_payer( @@ -1157,8 +1183,6 @@ async fn success_execute_with_updated_extra_account_metas() { .await .unwrap(); - //////////////////// - // fail with initial account metas list { let transaction = Transaction::new_signed_with_payer( @@ -1170,7 +1194,7 @@ async fn success_execute_with_updated_extra_account_metas() { &wallet.pubkey(), &extra_account_metas_address, &init_account_metas, - amount, + updated_amount, )], Some(&context.payer.pubkey()), &[&context.payer, &mint_authority], @@ -1202,7 +1226,7 @@ async fn success_execute_with_updated_extra_account_metas() { &wallet.pubkey(), &extra_account_metas_address, &updated_account_metas[..2], - amount, + updated_amount, )], Some(&context.payer.pubkey()), &[&context.payer, &mint_authority], @@ -1241,7 +1265,7 @@ async fn success_execute_with_updated_extra_account_metas() { &wallet.pubkey(), &extra_account_metas_address, &extra_account_metas, - amount, + updated_amount, )], Some(&context.payer.pubkey()), &[&context.payer, &mint_authority], @@ -1288,7 +1312,7 @@ async fn success_execute_with_updated_extra_account_metas() { &wallet.pubkey(), &extra_account_metas_address, &extra_account_metas, - amount, + updated_amount, )], Some(&context.payer.pubkey()), &[&context.payer, &mint_authority], @@ -1327,7 +1351,7 @@ async fn success_execute_with_updated_extra_account_metas() { &wallet.pubkey(), &extra_account_metas_address, &extra_account_metas, - amount, + updated_amount, )], Some(&context.payer.pubkey()), &[&context.payer], @@ -1359,7 +1383,7 @@ async fn success_execute_with_updated_extra_account_metas() { &wallet.pubkey(), &extra_account_metas_address, &updated_account_metas, - amount, + updated_amount, )], Some(&context.payer.pubkey()), &[&context.payer, &mint_authority], From ce76bf95af3045fb6345a46cd57ff12a07da5464 Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Sun, 3 Dec 2023 21:26:15 +0900 Subject: [PATCH 37/39] added check for if extra metas have be initialized --- token/transfer-hook/example/src/processor.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/token/transfer-hook/example/src/processor.rs b/token/transfer-hook/example/src/processor.rs index f94f4f41840..8d1199256bb 100644 --- a/token/transfer-hook/example/src/processor.rs +++ b/token/transfer-hook/example/src/processor.rs @@ -170,12 +170,17 @@ pub fn process_update_extra_account_meta_list( return Err(ProgramError::InvalidSeeds); } - let length = extra_account_metas.len(); - let account_size = ExtraAccountMetaList::size_of(length)?; + // Check if the extra metas have been initialized + let min_account_size = ExtraAccountMetaList::size_of(0)?; let original_account_size = extra_account_metas_info.data_len(); + if program_id != extra_account_metas_info.owner || original_account_size <= min_account_size { + return Err(ProgramError::UninitializedAccount); + } // If the new extra_account_metas length is different, resize the account and // update + let length = extra_account_metas.len(); + let account_size = ExtraAccountMetaList::size_of(length)?; if account_size >= original_account_size { extra_account_metas_info.realloc(account_size, false)?; let mut data = extra_account_metas_info.try_borrow_mut_data()?; @@ -185,7 +190,6 @@ pub fn process_update_extra_account_meta_list( let mut data = extra_account_metas_info.try_borrow_mut_data()?; ExtraAccountMetaList::update::(&mut data, extra_account_metas)?; } - extra_account_metas_info.realloc(account_size, false)?; } From 20d9185643a525a39626255f80f39b529de583be Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Thu, 7 Dec 2023 15:07:06 +0900 Subject: [PATCH 38/39] spelling fix --- libraries/tlv-account-resolution/src/state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/tlv-account-resolution/src/state.rs b/libraries/tlv-account-resolution/src/state.rs index c206095347f..7d8234e6338 100644 --- a/libraries/tlv-account-resolution/src/state.rs +++ b/libraries/tlv-account-resolution/src/state.rs @@ -1323,7 +1323,7 @@ mod tests { // update ExtraAccountMetaList::update::(buffer, updated_metas).unwrap(); - // retreive metas and assert + // retrieve metas and assert let state = TlvStateBorrowed::unpack(buffer).unwrap(); let unpacked_metas_pod = ExtraAccountMetaList::unpack_with_tlv_state::(&state).unwrap(); From 9a508de862d7650566fbbe2a84496b0993bb7562 Mon Sep 17 00:00:00 2001 From: tonton-sol <19677766+tonton-sol@users.noreply.github.com> Date: Thu, 7 Dec 2023 15:09:12 +0900 Subject: [PATCH 39/39] fixed initialized condition --- token/transfer-hook/example/src/processor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/token/transfer-hook/example/src/processor.rs b/token/transfer-hook/example/src/processor.rs index 8d1199256bb..e398b207845 100644 --- a/token/transfer-hook/example/src/processor.rs +++ b/token/transfer-hook/example/src/processor.rs @@ -173,7 +173,7 @@ pub fn process_update_extra_account_meta_list( // Check if the extra metas have been initialized let min_account_size = ExtraAccountMetaList::size_of(0)?; let original_account_size = extra_account_metas_info.data_len(); - if program_id != extra_account_metas_info.owner || original_account_size <= min_account_size { + if program_id != extra_account_metas_info.owner || original_account_size < min_account_size { return Err(ProgramError::UninitializedAccount); }