From c474782bcd67639de9a9a47cea86551018a8137a Mon Sep 17 00:00:00 2001 From: Maksym <3645723+makarychev@users.noreply.github.com> Date: Tue, 16 Jul 2024 10:54:46 +0300 Subject: [PATCH] Add heap tests (#2) * add heap size tests * add instructions --- Cargo.lock | 4 + programs/transfer-extensions/Cargo.toml | 4 + .../src/instructions/mod.rs | 3 + .../src/instructions/multi_transfers.rs | 15 +- .../src/instructions/multi_transfers_heap.rs | 40 +++ programs/transfer-extensions/src/lib.rs | 9 + programs/transfer-extensions/src/sol_sdk.rs | 314 ++++++++++++++++++ .../initialize_extra_meta_list.rs | 3 +- tests/transfer-extensions.ts | 182 +++++++++- 9 files changed, 565 insertions(+), 9 deletions(-) create mode 100644 programs/transfer-extensions/src/instructions/multi_transfers_heap.rs create mode 100644 programs/transfer-extensions/src/sol_sdk.rs diff --git a/Cargo.lock b/Cargo.lock index 8f7924e..ef726ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2416,6 +2416,10 @@ version = "0.1.0" dependencies = [ "anchor-lang", "anchor-spl", + "solana-program", + "spl-tlv-account-resolution", + "spl-transfer-hook-interface", + "spl-type-length-value", ] [[package]] diff --git a/programs/transfer-extensions/Cargo.toml b/programs/transfer-extensions/Cargo.toml index 14a2b00..c8dc94b 100644 --- a/programs/transfer-extensions/Cargo.toml +++ b/programs/transfer-extensions/Cargo.toml @@ -19,3 +19,7 @@ idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] [dependencies] anchor-lang = "0.30.1" anchor-spl = { version = "0.30.1" } +solana-program = "1.18.15" +spl-transfer-hook-interface = { version = "0.6.3" } +spl-tlv-account-resolution ={ version = "0.6.3" } +spl-type-length-value = { version = "0.4.3" } diff --git a/programs/transfer-extensions/src/instructions/mod.rs b/programs/transfer-extensions/src/instructions/mod.rs index 195334e..0e4fb44 100644 --- a/programs/transfer-extensions/src/instructions/mod.rs +++ b/programs/transfer-extensions/src/instructions/mod.rs @@ -15,3 +15,6 @@ pub use initialize_mint_counter_out::*; pub mod multi_transfers; pub use multi_transfers::*; + +pub mod multi_transfers_heap; +pub use multi_transfers_heap::*; diff --git a/programs/transfer-extensions/src/instructions/multi_transfers.rs b/programs/transfer-extensions/src/instructions/multi_transfers.rs index e25d37c..3346503 100644 --- a/programs/transfer-extensions/src/instructions/multi_transfers.rs +++ b/programs/transfer-extensions/src/instructions/multi_transfers.rs @@ -1,8 +1,8 @@ use anchor_lang::prelude::*; -use anchor_spl::token_2022::spl_token_2022::onchain::invoke_transfer_checked; +// use anchor_spl::token_2022::spl_token_2022::onchain::invoke_transfer_checked; use anchor_spl::token_interface::{Mint, Token2022, TokenAccount}; -use crate::errors::TransferExtensionsError; +use crate::{errors::TransferExtensionsError, sol_sdk::invoke_transfer_checked}; #[derive(Accounts)] pub struct MultiTransfers<'info> { @@ -11,24 +11,24 @@ pub struct MultiTransfers<'info> { associated_token::mint = mint, associated_token::authority = signer, )] - source_account: Box>, + pub source_account: Box>, #[account(mut, token::mint = mint, token::token_program = token_program, )] - destination_account_1: Box>, + pub destination_account_1: Box>, #[account(mut, token::mint = mint, token::token_program = token_program, )] - destination_account_2: Box>, + pub destination_account_2: Box>, #[account( token::token_program = token_program, )] - mint: Box>, + pub mint: Box>, #[account(mut)] - signer: Signer<'info>, + pub signer: Signer<'info>, #[account( constraint = token_program.key() == anchor_spl::token_interface::spl_token_2022::id(), @@ -52,6 +52,7 @@ pub fn multi_transfers<'info>( let split_at_pos = ctx.remaining_accounts.len() / 2; msg!("Invoke transfer 1"); + msg!("Source balance: {}", ctx.accounts.source_account.amount); invoke_transfer_checked( ctx.accounts.token_program.key, ctx.accounts.source_account.to_account_info().clone(), diff --git a/programs/transfer-extensions/src/instructions/multi_transfers_heap.rs b/programs/transfer-extensions/src/instructions/multi_transfers_heap.rs new file mode 100644 index 0000000..bea82a3 --- /dev/null +++ b/programs/transfer-extensions/src/instructions/multi_transfers_heap.rs @@ -0,0 +1,40 @@ +use anchor_lang::prelude::*; +// use anchor_spl::token_2022::spl_token_2022::onchain::invoke_transfer_checked; + +use crate::{errors::TransferExtensionsError, sol_sdk::invoke_transfer_checked, MultiTransfers}; + +pub fn multi_transfers_heap<'info>( + ctx: Context<'_, '_, '_, 'info, MultiTransfers<'info>>, + amount1: u64, + amount2: u64, +) -> Result<()> { + msg!("Multi transfers"); + require!( + amount1 > 0 && amount2 > 0, + TransferExtensionsError::AmountMustBeGreaterThanZero + ); + + let mint = &ctx.accounts.mint; + let decimals = mint.decimals; + + msg!("Invoke transfer 1"); + msg!("Source balance: {}", ctx.accounts.source_account.amount); + invoke_transfer_checked( + ctx.accounts.token_program.key, + ctx.accounts.source_account.to_account_info().clone(), + mint.to_account_info().clone(), + ctx.accounts.destination_account_1.to_account_info().clone(), + ctx.accounts.signer.to_account_info().clone(), + &ctx.remaining_accounts, + amount1, + decimals, + &[], + )?; + + msg!("Invoke transfer 2"); + let mut heap_data: Box<[u8; 13_859]> = Box::new([0; 13_859]); // 13_859 - OK; 13_869 - FAILED + heap_data[0] = 1; + heap_data[10333] = 3; + + Ok(()) +} diff --git a/programs/transfer-extensions/src/lib.rs b/programs/transfer-extensions/src/lib.rs index 91e2c11..454f722 100644 --- a/programs/transfer-extensions/src/lib.rs +++ b/programs/transfer-extensions/src/lib.rs @@ -4,6 +4,7 @@ use instructions::*; pub mod states; pub mod seeds; pub mod errors; +pub mod sol_sdk; declare_id!("4MNxsMM7niQkurWFyDvzhVbD3wHQFyAhnGjrvuYPi6Zu"); @@ -48,6 +49,14 @@ pub mod transfer_extensions { ) -> Result<()> { instructions::multi_transfers(ctx, amount1, amount2) } + + pub fn multi_transfers_heap<'info>( + ctx: Context<'_, '_, '_, 'info, MultiTransfers<'info>>, + amount1: u64, + amount2: u64, + ) -> Result<()> { + instructions::multi_transfers_heap(ctx, amount1, amount2) + } } diff --git a/programs/transfer-extensions/src/sol_sdk.rs b/programs/transfer-extensions/src/sol_sdk.rs new file mode 100644 index 0000000..a4e61fb --- /dev/null +++ b/programs/transfer-extensions/src/sol_sdk.rs @@ -0,0 +1,314 @@ +use anchor_lang::prelude::*; +use anchor_spl::token_2022::spl_token_2022::extension::{transfer_hook, StateWithExtensions}; +use anchor_spl::token_2022::spl_token_2022::instruction; +use anchor_spl::token_2022::spl_token_2022::solana_program::entrypoint::ProgramResult; +use anchor_spl::token_2022::spl_token_2022::solana_program::program::invoke_signed; +use anchor_spl::token_interface::spl_pod::slice::PodSlice; +use solana_program::instruction::Instruction; +use spl_tlv_account_resolution::account::ExtraAccountMeta; +use spl_tlv_account_resolution::error::AccountResolutionError; +use spl_transfer_hook_interface::error::TransferHookError; +use spl_transfer_hook_interface::get_extra_account_metas_address; +use spl_type_length_value::state::{TlvState, TlvStateBorrowed}; + +/// Helper to CPI into token-2022 on-chain, looking through the additional +/// account infos to create the proper instruction with the proper account infos +#[allow(clippy::too_many_arguments)] +pub fn invoke_transfer_checked<'a>( + token_program_id: &Pubkey, + source_info: AccountInfo<'a>, + mint_info: AccountInfo<'a>, + destination_info: AccountInfo<'a>, + authority_info: AccountInfo<'a>, + additional_accounts: &[AccountInfo<'a>], + amount: u64, + decimals: u8, + seeds: &[&[&[u8]]], +) -> ProgramResult { + let mut cpi_instruction = instruction::transfer_checked( + token_program_id, + source_info.key, + mint_info.key, + destination_info.key, + authority_info.key, + &[], // add them later, to avoid unnecessary clones + amount, + decimals, + )?; + + let mut cpi_account_infos = vec![ + source_info.clone(), + mint_info.clone(), + destination_info.clone(), + authority_info.clone(), + ]; + + // if it's a signer, it might be a multisig signer, throw it in! + additional_accounts + .iter() + .filter(|ai| ai.is_signer) + .for_each(|ai| { + cpi_account_infos.push(ai.clone()); + cpi_instruction + .accounts + .push(AccountMeta::new_readonly(*ai.key, ai.is_signer)); + }); + msg!("====!! Going to add extra accounts !!===="); + // scope the borrowing to avoid a double-borrow during CPI + { + let mint_data = mint_info.try_borrow_data()?; + let mint = + StateWithExtensions::::unpack( + &mint_data, + )?; + if let Some(program_id) = transfer_hook::get_program_id(&mint) { + add_extra_accounts_for_execute_cpi( + &mut cpi_instruction, + &mut cpi_account_infos, + &program_id, + source_info, + mint_info.clone(), + destination_info, + authority_info, + amount, + additional_accounts, + )?; + } + } + msg!("====!! Going to invoke_signed !!===="); + + invoke_signed(&cpi_instruction, &cpi_account_infos, seeds) +} + +#[allow(clippy::too_many_arguments)] +pub fn add_extra_accounts_for_execute_cpi<'a>( + cpi_instruction: &mut Instruction, + cpi_account_infos: &mut Vec>, + program_id: &Pubkey, + source_info: AccountInfo<'a>, + mint_info: AccountInfo<'a>, + destination_info: AccountInfo<'a>, + authority_info: AccountInfo<'a>, + amount: u64, + additional_accounts: &[AccountInfo<'a>], +) -> ProgramResult { + msg!("\t====!! Inside add_extra_accounts_for_execute_cpi !!===="); + let validate_state_pubkey = get_extra_account_metas_address(mint_info.key, program_id); + let validate_state_info = additional_accounts + .iter() + .find(|&x| *x.key == validate_state_pubkey) + .ok_or(TransferHookError::IncorrectAccount)?; + + let program_info = additional_accounts + .iter() + .find(|&x| x.key == program_id) + .ok_or(TransferHookError::IncorrectAccount)?; + + let mut execute_instruction = spl_transfer_hook_interface::instruction::execute( + program_id, + source_info.key, + mint_info.key, + destination_info.key, + authority_info.key, + &validate_state_pubkey, + amount, + ); + // Vec::with_capacity(); + let mut execute_account_infos = vec![ + source_info, + mint_info, + destination_info, + authority_info, + validate_state_info.clone(), + ]; + + msg!("\t====!! ExtraAccountMetaList::add_to_cpi_instruction !!===="); + // NOTE: Replaces sdk function with the same name custom implementation (now same impl as in SDK) + // ExtraAccountMetaList::add_to_cpi_instruction::( + add_to_cpi_instruction( + &mut execute_instruction, + &mut execute_account_infos, + &validate_state_info.try_borrow_data()?, + additional_accounts, + )?; + + msg!("====!! Adding accounts from execute_instruction !!===="); + // Add only the extra accounts resolved from the validation state + cpi_instruction + .accounts + .extend_from_slice(&execute_instruction.accounts[5..]); + cpi_account_infos.extend_from_slice(&execute_account_infos[5..]); + + // Add the program id and validation state account + msg!("\t====!! Adding program_id and validate_state_pubkey !!===="); + cpi_instruction + .accounts + .push(AccountMeta::new_readonly(*program_id, false)); + cpi_instruction + .accounts + .push(AccountMeta::new_readonly(validate_state_pubkey, false)); + cpi_account_infos.push(program_info.clone()); + cpi_account_infos.push(validate_state_info.clone()); + + Ok(()) +} + +/// Add the additional account metas and account infos for a CPI +pub fn add_to_cpi_instruction<'a>( + cpi_instruction: &mut Instruction, + cpi_account_infos: &mut Vec>, + data: &[u8], + account_infos: &[AccountInfo<'a>], +) -> std::result::Result<(), ProgramError> { + msg!("??? inside ::add_to_cpi_instruction !!===="); + let state = TlvStateBorrowed::unpack(data)?; + let bytes = + state.get_first_bytes::()?; + let extra_account_metas = PodSlice::::unpack(bytes)?; + + msg!( + "extra_account_metas.data() = {}", + extra_account_metas.data().len() + ); + // let mut it_idx = 0; + // cpi_account_infos.iter().for_each(|x| { + // msg!( + // "account index [{}], data len: {}", + // it_idx, + // x.try_borrow_data().unwrap().len() + // ); + // it_idx += 1; + // }); + let mut idx = 5; + for extra_meta in extra_account_metas.data().iter() { + let mut meta = { + // Create a list of `Ref`s so we can reference account data in the + // resolution step + let account_key_data_refs = cpi_account_infos + .iter() + .map(|info| { + let key = *info.key; + let data = info.try_borrow_data()?; + // msg!("key = {}, data length = {}", key, data.len()); + // msg!("data = {:?}", data); // a lot of compute units + Ok((key, data)) + }) + .collect::, ProgramError>>()?; + + extra_meta.resolve( + &cpi_instruction.data, + &cpi_instruction.program_id, + |usize| { + account_key_data_refs + .get(usize) + .map(|(pubkey, opt_data)| (pubkey, Some(opt_data.as_ref()))) + }, + )? + }; + // msg!("de_escalate_account_meta"); + de_escalate_account_meta(&mut meta, &cpi_instruction.accounts); + + let account_info = account_infos + .iter() + .find(|&x| *x.key == meta.pubkey) + .ok_or(AccountResolutionError::IncorrectAccount)? + .clone(); + + // msg!("====>>> [going to push meta to cpi_instruction.accounts and account_info to cpi_account_infos]"); + cpi_instruction.accounts.push(meta); + // msg!( + // "account index [{}], data len: {}", + // idx, + // account_info.try_borrow_data()?.len() + // ); + cpi_account_infos.push(account_info); + idx += 1; + } + Ok(()) +} + +// /// Rewrite of the `add_to_cpi_instruction` function from the SDK +// pub fn add_to_cpi_instruction<'a>( +// cpi_instruction: &mut Instruction, +// cpi_account_infos: &mut Vec>, +// data: &[u8], +// account_infos: &[AccountInfo<'a>], +// ) -> std::result::Result<(), ProgramError> { +// msg!("??? inside ::add_to_cpi_instruction !!===="); +// let state = TlvStateBorrowed::unpack(data)?; +// let bytes = +// state.get_first_bytes::()?; +// let extra_account_metas = PodSlice::::unpack(bytes)?; + +// msg!("extra_account_metas.data() = {}", extra_account_metas.data().len()); +// // Create a list of `Ref`s so we can reference account data in the +// // resolution step +// let mut acc_info = cpi_account_infos.clone(); +// let cpi_account_infos_len = cpi_account_infos.len(); +// { +// let account_key_data_refs = cpi_account_infos +// .iter() +// .map(|info| { +// let key = *info.key; +// let data = info.try_borrow_data()?; +// // msg!("key = {}, data length = {}", key, data.len()); +// // msg!("data = {:?}", data); // a lot of compute units +// Ok((key, data)) +// }) +// .collect::, ProgramError>>()?; +// for extra_meta in extra_account_metas.data().iter() { +// let mut meta = { +// extra_meta.resolve( +// &cpi_instruction.data, +// &cpi_instruction.program_id, +// |usize| { +// account_key_data_refs +// .get(usize) +// .map(|(pubkey, opt_data)| (pubkey, Some(opt_data.as_ref()))) +// }, +// )? +// }; +// msg!("de_escalate_account_meta"); +// de_escalate_account_meta(&mut meta, &cpi_instruction.accounts); + +// let account_info = account_infos +// .iter() +// .find(|&x| *x.key == meta.pubkey) +// .ok_or(AccountResolutionError::IncorrectAccount)? +// .clone(); + +// msg!("====>>> [going to push meta to cpi_instruction.accounts and account_info to cpi_account_infos]"); +// cpi_instruction.accounts.push(meta); +// account_key_data_refs.push((*account_info.key, account_info.try_borrow_data()?)); +// acc_info.push(account_info); +// } +// } +// cpi_account_infos.extend_from_slice(&acc_info[cpi_account_infos_len..]); +// Ok(()) +// } + +/// De-escalate an account meta if necessary +fn de_escalate_account_meta(account_meta: &mut AccountMeta, account_metas: &[AccountMeta]) { + // This is a little tricky to read, but the idea is to see if + // this account is marked as writable or signer anywhere in + // the instruction at the start. If so, DON'T escalate it to + // be a writer or signer in the CPI + let maybe_highest_privileges = account_metas + .iter() + .filter(|&x| x.pubkey == account_meta.pubkey) + .map(|x| (x.is_signer, x.is_writable)) + .reduce(|acc, x| (acc.0 || x.0, acc.1 || x.1)); + // If `Some`, then the account was found somewhere in the instruction + if let Some((is_signer, is_writable)) = maybe_highest_privileges { + if !is_signer && is_signer != account_meta.is_signer { + // Existing account is *NOT* a signer already, but the CPI + // wants it to be, so de-escalate to not be a signer + account_meta.is_signer = false; + } + if !is_writable && is_writable != account_meta.is_writable { + // Existing account is *NOT* writable already, but the CPI + // wants it to be, so de-escalate to not be writable + account_meta.is_writable = false; + } + } +} diff --git a/programs/transfer-hook/src/instructions/initialize_extra_meta_list.rs b/programs/transfer-hook/src/instructions/initialize_extra_meta_list.rs index e022fba..ef6207f 100644 --- a/programs/transfer-hook/src/instructions/initialize_extra_meta_list.rs +++ b/programs/transfer-hook/src/instructions/initialize_extra_meta_list.rs @@ -61,7 +61,8 @@ pub fn get_extra_account_metas(program_id: &Pubkey) -> Result { console.log("Recipient2 CounterOut transaction signature", recipient2CounterOutTxSignature); }); + const COUNTER_IN_SEED = "counter-in"; + const COUNTER_OUT_SEED = "counter-out"; + const GLOBAL_PROGRAM_DATA_SEED = "global-program-data"; + let recipient1walletCounterInPda: PublicKey; + let recipient1walletCounterOutPda: PublicKey; + let recipient2walletCounterInPda: PublicKey; + let recipient2walletCounterOutPda: PublicKey; + let mintCounterInPda: PublicKey; + let mintCounterOutPda: PublicKey; + let globalProgramDataPda: PublicKey; + let senderWalletCounterInPda: PublicKey; + let senderWalletCounterOutPda: PublicKey; it("multiple transfers", async () => { + [recipient1walletCounterInPda] = PublicKey.findProgramAddressSync( + [ + Buffer.from(COUNTER_IN_SEED), + recipientTokenAccountPubkey.toBuffer(), + ], + program.programId + ); + [recipient1walletCounterOutPda] = PublicKey.findProgramAddressSync( + [ + Buffer.from(COUNTER_OUT_SEED), + recipientTokenAccountPubkey.toBuffer(), + ], + program.programId + ); + [recipient2walletCounterInPda] = PublicKey.findProgramAddressSync( + [ + Buffer.from(COUNTER_IN_SEED), + recipient2TokenAccountPubkey.toBuffer(), + ], + program.programId + ); + [recipient2walletCounterOutPda] = PublicKey.findProgramAddressSync( + [ + Buffer.from(COUNTER_OUT_SEED), + recipient2TokenAccountPubkey.toBuffer(), + ], + program.programId + ); + [mintCounterInPda] = PublicKey.findProgramAddressSync( + [ + Buffer.from(COUNTER_IN_SEED), + mint.publicKey.toBuffer(), + ], + program.programId + ); + [mintCounterOutPda] = PublicKey.findProgramAddressSync( + [ + Buffer.from(COUNTER_OUT_SEED), + mint.publicKey.toBuffer(), + ], + program.programId + ); + [globalProgramDataPda] = PublicKey.findProgramAddressSync( + [ + Buffer.from(GLOBAL_PROGRAM_DATA_SEED), + ], + program.programId + ); + [senderWalletCounterInPda] = PublicKey.findProgramAddressSync( + [ + Buffer.from(COUNTER_IN_SEED), + senderTokenAccountPubkey.toBuffer(), + ], + program.programId + ); + [senderWalletCounterOutPda] = PublicKey.findProgramAddressSync( + [ + Buffer.from(COUNTER_OUT_SEED), + senderTokenAccountPubkey.toBuffer(), + ], + program.programId + ); // 1 tokens const amount1 = 1 * 10 ** decimals; const amount2 = 2 * 10 ** decimals; @@ -337,9 +411,30 @@ describe("transfer-extensions", () => { ); const modifyComputeUnitsInstruction = ComputeBudgetProgram.setComputeUnitLimit({ - units: 400000, + units: 1000000, }); + console.log("*".repeat(50)); + console.log("*".repeat(50)); + console.log("*".repeat(50)); + console.log("Recipient1AssocAccount:", recipientTokenAccountPubkey.toString()); + console.log("Recipient2AssocAccount:", recipient2TokenAccountPubkey.toString()); + console.log("mint:", mint.publicKey.toString()); + console.log("SenderAssocAccount:", senderTokenAccountPubkey.toString()); + console.log("Sender Wallet Counter In PDA:", senderWalletCounterInPda.toString()); + console.log("Sender Wallet Counter Out PDA:", senderWalletCounterOutPda.toString()); + console.log("Recipient1 Wallet Counter In PDA:", recipient1walletCounterInPda.toString()); + console.log("Recipient2 Wallet Counter In PDA:", recipient2walletCounterInPda.toString()); + console.log("Recipient1 Wallet Counter Out PDA:", recipient1walletCounterOutPda.toString()); + console.log("Recipient2 Wallet Counter Out PDA:", recipient2walletCounterOutPda.toString()); + console.log("Mint Counter In PDA:", mintCounterInPda.toString()); + console.log("Mint Counter Out PDA:", mintCounterOutPda.toString()); + console.log("Global Program Data PDA:", globalProgramDataPda.toString()); + console.log("*".repeat(50)); + console.log("*".repeat(50)); + console.log("*".repeat(50)); + + const transaction = new Transaction().add(...[modifyComputeUnitsInstruction, multiTransfersInstruction]); try { console.log("Going to send transaction"); @@ -357,4 +452,89 @@ describe("transfer-extensions", () => { const tokenAccount = await getAccount(provider.connection, recipient2TokenAccountPubkey, undefined, TOKEN_2022_PROGRAM_ID); assert.equal(Number(tokenAccount.amount), amount2); }); + + it("multiple transfers heap size", async () => { + const amount1 = 1 * 10 ** decimals; + const amount2 = 2 * 10 ** decimals; + + const multiTransfersInstruction = program.instruction.multiTransfersHeap( + new anchor.BN(amount1), + new anchor.BN(amount2), + { + accounts: { + sourceAccount: senderTokenAccountPubkey, + destinationAccount1: recipientTokenAccountPubkey, + destinationAccount2: recipient2TokenAccountPubkey, + mint: mint.publicKey, + signer: sender.publicKey, + tokenProgram: TOKEN_2022_PROGRAM_ID, + }, + signers: [sender], + }) + const mintInfo = await getMint( + provider.connection, + mint.publicKey, + undefined, + TOKEN_2022_PROGRAM_ID + ); + const transferHook = getTransferHook(mintInfo); + assert.ok(transferHook); + + await addExtraAccountMetasForExecute( + provider.connection, + multiTransfersInstruction, + transferHook.programId, + senderTokenAccountPubkey, + mint.publicKey, + recipientTokenAccountPubkey, + sender.publicKey, + amount1, + undefined + ); + + const modifyComputeUnitsInstruction = + ComputeBudgetProgram.setComputeUnitLimit({ + units: 400000, + }); + + console.log("*".repeat(50)); + console.log("*".repeat(50)); + console.log("*".repeat(50)); + console.log("Recipient1AssocAccount:", recipientTokenAccountPubkey.toString()); + console.log("Recipient2AssocAccount:", recipient2TokenAccountPubkey.toString()); + console.log("mint:", mint.publicKey.toString()); + console.log("SenderAssocAccount:", senderTokenAccountPubkey.toString()); + console.log("Sender Wallet Counter In PDA:", senderWalletCounterInPda.toString()); + console.log("Sender Wallet Counter Out PDA:", senderWalletCounterOutPda.toString()); + console.log("Recipient1 Wallet Counter In PDA:", recipient1walletCounterInPda.toString()); + console.log("Recipient2 Wallet Counter In PDA:", recipient2walletCounterInPda.toString()); + console.log("Recipient1 Wallet Counter Out PDA:", recipient1walletCounterOutPda.toString()); + console.log("Recipient2 Wallet Counter Out PDA:", recipient2walletCounterOutPda.toString()); + console.log("Mint Counter In PDA:", mintCounterInPda.toString()); + console.log("Mint Counter Out PDA:", mintCounterOutPda.toString()); + console.log("Global Program Data PDA:", globalProgramDataPda.toString()); + console.log("*".repeat(50)); + console.log("*".repeat(50)); + console.log("*".repeat(50)); + + let tokenAccount = await getAccount(provider.connection, recipientTokenAccountPubkey, undefined, TOKEN_2022_PROGRAM_ID); + const recipientBalanceBefore = Number(tokenAccount.amount); + + const transaction = new Transaction().add(...[modifyComputeUnitsInstruction, multiTransfersInstruction]); + try { + console.log("Going to send transaction"); + const txSig = await sendAndConfirmTransaction( + provider.connection, + transaction, + [sender] + ); + console.log(`Multi Transfer Transaction Signature: ${txSig}`); + } catch (error) { + console.log(error); + throw error; + } + + tokenAccount = await getAccount(provider.connection, recipientTokenAccountPubkey, undefined, TOKEN_2022_PROGRAM_ID); + assert.equal(Number(tokenAccount.amount), recipientBalanceBefore + amount1); + }); });