diff --git a/Cargo.lock b/Cargo.lock index 676d03a0bc..d15fccb7dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1364,11 +1364,13 @@ dependencies = [ "account-compression", "anchor-lang", "anchor-spl", + "light-batched-merkle-tree", "light-client", "light-compressed-account", "light-compressed-token", "light-program-test", "light-prover-client", + "light-registry", "light-sdk", "light-system-program", "light-test-utils", @@ -3218,11 +3220,13 @@ dependencies = [ "light-hasher", "light-heap", "light-system-program", + "light-zero-copy", "rand 0.8.5", "solana-sdk", "solana-security-txt", "spl-token", "spl-token-2022 3.0.5", + "zerocopy 0.8.14", ] [[package]] diff --git a/js/compressed-token/tests/e2e/mint-to.test.ts b/js/compressed-token/tests/e2e/mint-to.test.ts index 521578be95..00b877e1d9 100644 --- a/js/compressed-token/tests/e2e/mint-to.test.ts +++ b/js/compressed-token/tests/e2e/mint-to.test.ts @@ -108,9 +108,10 @@ describe('mintTo', () => { await assertMintTo(rpc, mint, amount, bob.publicKey); /// wrong authority + /// is not checked in cToken program, so it throws invalid owner inside spl token program. await expect( mintTo(rpc, payer, mint, bob.publicKey, Keypair.generate(), amount), - ).rejects.toThrowError(/custom program error: 0x1782/); + ).rejects.toThrowError(/custom program error: 0x4/); /// with output state merkle tree defined await mintTo( diff --git a/program-libs/compressed-account/src/pubkey.rs b/program-libs/compressed-account/src/pubkey.rs index 74ae6bb218..b7682a60bc 100644 --- a/program-libs/compressed-account/src/pubkey.rs +++ b/program-libs/compressed-account/src/pubkey.rs @@ -3,7 +3,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use bytemuck::{Pod, Zeroable}; use light_zero_copy::{borsh::Deserialize, errors::ZeroCopyError}; use solana_program::pubkey; -use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Ref, Unaligned}; +use zerocopy::{little_endian::U64, FromBytes, Immutable, IntoBytes, KnownLayout, Ref, Unaligned}; #[cfg(feature = "bytemuck-des")] #[derive( Pod, @@ -54,6 +54,12 @@ impl Pubkey { } } +impl AsRef for Pubkey { + fn as_ref(&self) -> &Self { + self + } +} + impl<'a> Deserialize<'a> for Pubkey { type Output = Ref<&'a [u8], Pubkey>; @@ -113,6 +119,7 @@ impl From<&anchor_lang::prelude::Pubkey> for Pubkey { Self(pubkey.to_bytes()) } } + impl Pubkey { pub fn new_unique() -> Self { Self(pubkey::Pubkey::new_unique().to_bytes()) @@ -122,3 +129,69 @@ impl Pubkey { self.0 } } + +pub trait PubkeyTrait { + fn trait_to_bytes(&self) -> [u8; 32]; + #[cfg(feature = "anchor")] + fn to_anchor_pubkey(&self) -> anchor_lang::prelude::Pubkey; +} + +impl PubkeyTrait for Pubkey { + fn trait_to_bytes(&self) -> [u8; 32] { + self.to_bytes() + } + #[cfg(feature = "anchor")] + fn to_anchor_pubkey(&self) -> anchor_lang::prelude::Pubkey { + self.into() + } +} + +#[cfg(feature = "anchor")] +impl PubkeyTrait for anchor_lang::prelude::Pubkey { + fn trait_to_bytes(&self) -> [u8; 32] { + self.to_bytes() + } + + #[cfg(feature = "anchor")] + fn to_anchor_pubkey(&self) -> Self { + *self + } +} + +#[cfg(not(feature = "anchor"))] +impl PubkeyTrait for solana_program::pubkey::Pubkey { + fn trait_to_bytes(&self) -> [u8; 32] { + self.to_bytes() + } +} + +use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; +pub trait ZeroCopyNumTrait: + Add + + Sub + + AddAssign + + SubAssign + + Div + + DivAssign + + Mul + + MulAssign + + std::marker::Sized + + From + + Into + + Copy + + std::convert::TryFrom +{ + fn to_bytes_le(&self) -> [u8; 8]; +} + +impl ZeroCopyNumTrait for u64 { + fn to_bytes_le(&self) -> [u8; 8] { + self.to_le_bytes() + } +} + +impl ZeroCopyNumTrait for U64 { + fn to_bytes_le(&self) -> [u8; 8] { + self.to_bytes() + } +} diff --git a/program-libs/zero-copy/src/borsh.rs b/program-libs/zero-copy/src/borsh.rs index 0811fa44cd..71a77ff846 100644 --- a/program-libs/zero-copy/src/borsh.rs +++ b/program-libs/zero-copy/src/borsh.rs @@ -4,7 +4,10 @@ use core::{ }; use std::vec::Vec; -use zerocopy::{little_endian::U32, FromBytes, Immutable, KnownLayout, Ref}; +use zerocopy::{ + little_endian::{U16, U32, U64}, + FromBytes, Immutable, KnownLayout, Ref, +}; use crate::errors::ZeroCopyError; @@ -77,6 +80,7 @@ macro_rules! impl_deserialize_for_primitive { } impl_deserialize_for_primitive!(u16, i16, u32, i32, u64, i64); +impl_deserialize_for_primitive!(U16, U32, U64); impl<'a, T: Deserialize<'a>> Deserialize<'a> for Vec { type Output = Vec; diff --git a/program-tests/compressed-token-test/Cargo.toml b/program-tests/compressed-token-test/Cargo.toml index 09cfacb98c..ab3f261c91 100644 --- a/program-tests/compressed-token-test/Cargo.toml +++ b/program-tests/compressed-token-test/Cargo.toml @@ -23,7 +23,8 @@ light-compressed-token = { workspace = true } light-system-program = { workspace = true } account-compression = { workspace = true } light-compressed-account = { workspace = true } - +light-registry = { workspace = true } +light-batched-merkle-tree = { workspace = true } [target.'cfg(not(target_os = "solana"))'.dependencies] solana-sdk = { workspace = true } diff --git a/program-tests/compressed-token-test/tests/test.rs b/program-tests/compressed-token-test/tests/test.rs index dfbaa8a111..39e3bd6f50 100644 --- a/program-tests/compressed-token-test/tests/test.rs +++ b/program-tests/compressed-token-test/tests/test.rs @@ -1,5 +1,7 @@ #![cfg(feature = "test-sbf")] +use std::assert_eq; + use account_compression::errors::AccountCompressionErrorCode; use anchor_lang::{ prelude::AccountMeta, system_program, AccountDeserialize, AnchorDeserialize, AnchorSerialize, @@ -7,7 +9,7 @@ use anchor_lang::{ }; use anchor_spl::{ token::{Mint, TokenAccount}, - token_2022::{spl_token_2022, spl_token_2022::extension::ExtensionType}, + token_2022::spl_token_2022::{self, extension::ExtensionType}, }; use light_client::indexer::Indexer; use light_compressed_account::{ @@ -15,6 +17,7 @@ use light_compressed_account::{ instruction_data::compressed_proof::CompressedProof, }; use light_compressed_token::{ + batch_compress::BatchCompressInstructionDataBorsh, constants::NUM_MAX_POOL_ACCOUNTS, delegation::sdk::{ create_approve_instruction, create_revoke_instruction, CreateApproveInstructionInputs, @@ -27,16 +30,20 @@ use light_compressed_token::{ get_cpi_authority_pda, transfer_sdk::create_transfer_instruction, TokenTransferOutputData, }, spl_compression::check_spl_token_pool_derivation_with_index, - token_data::TokenData, - ErrorCode, + ErrorCode, TokenData, }; use light_program_test::{ indexer::{TestIndexer, TestIndexerExtensions}, - test_env::setup_test_programs_with_accounts, + test_env::{ + setup_test_programs_with_accounts, + setup_test_programs_with_accounts_with_protocol_config_and_batched_tree_params, + }, test_rpc::ProgramTestRpcConnection, }; use light_prover_client::gnark::helpers::{kill_prover, spawn_prover, ProofType, ProverConfig}; +use light_registry::protocol_config::state::ProtocolConfig; use light_sdk::token::{AccountState, TokenDataWithMerkleContext}; +use light_system_program::utils::get_sol_pool_pda; use light_test_utils::{ airdrop_lamports, assert_custom_error_or_program_error, assert_rpc_error, conversions::sdk_to_program_token_data, @@ -44,8 +51,8 @@ use light_test_utils::{ spl::{ approve_test, burn_test, compress_test, compressed_transfer_22_test, compressed_transfer_test, create_additional_token_pools, create_burn_test_instruction, - create_mint_22_helper, create_mint_helper, create_token_2022_account, decompress_test, - freeze_test, mint_spl_tokens, mint_tokens_22_helper_with_lamports, + create_mint_22_helper, create_mint_helper, create_token_2022_account, create_token_account, + decompress_test, freeze_test, mint_spl_tokens, mint_tokens_22_helper_with_lamports, mint_tokens_22_helper_with_lamports_and_bump, mint_tokens_helper, mint_tokens_helper_with_lamports, mint_wrapped_sol, perform_compress_spl_token_account, revoke_test, thaw_test, BurnInstructionMode, @@ -978,7 +985,7 @@ async fn test_mint_to_failing() { .create_and_send_transaction(&[instruction], &payer_2.pubkey(), &[&payer_2]) .await; // Owner doesn't match the mint authority. - assert_rpc_error(result, 0, ErrorCode::InvalidAuthorityMint.into()).unwrap(); + assert_rpc_error(result, 0, TokenError::OwnerMismatch as u32).unwrap(); } // 2. Try to mint token from `mint_2` and sign the transaction with `mint_1` // authority. @@ -998,7 +1005,7 @@ async fn test_mint_to_failing() { .create_and_send_transaction(&[instruction], &payer_1.pubkey(), &[&payer_1]) .await; // Owner doesn't match the mint authority. - assert_rpc_error(result, 0, ErrorCode::InvalidAuthorityMint.into()).unwrap(); + assert_rpc_error(result, 0, TokenError::OwnerMismatch as u32).unwrap(); } // 3. Try to mint token to random token account. { @@ -1016,7 +1023,7 @@ async fn test_mint_to_failing() { fee_payer: payer_1.pubkey(), authority: payer_1.pubkey(), cpi_authority_pda: get_cpi_authority_pda().0, - mint: mint_1, + mint: Some(mint_1), token_pool_pda: token_account_keypair.pubkey(), token_program, light_system_program: light_system_program::ID, @@ -1051,7 +1058,7 @@ async fn test_mint_to_failing() { fee_payer: payer_2.pubkey(), authority: payer_2.pubkey(), cpi_authority_pda: get_cpi_authority_pda().0, - mint: mint_2, + mint: Some(mint_2), token_pool_pda: mint_pool_1, token_program, light_system_program: light_system_program::ID, @@ -1087,7 +1094,7 @@ async fn test_mint_to_failing() { fee_payer: payer_2.pubkey(), authority: payer_2.pubkey(), cpi_authority_pda: invalid_cpi_authority_pda.pubkey(), - mint: mint_1, + mint: Some(mint_1), token_pool_pda: mint_pool_1, token_program, light_system_program: light_system_program::ID, @@ -1114,12 +1121,7 @@ async fn test_mint_to_failing() { let result = rpc .create_and_send_transaction(&[instruction], &payer_2.pubkey(), &[&payer_2]) .await; - assert_rpc_error( - result, - 0, - anchor_lang::error::ErrorCode::ConstraintSeeds.into(), - ) - .unwrap(); + assert_rpc_error(result, 0, TokenError::OwnerMismatch as u32).unwrap(); } // 6. Invalid registered program. { @@ -1128,7 +1130,7 @@ async fn test_mint_to_failing() { fee_payer: payer_1.pubkey(), authority: payer_1.pubkey(), cpi_authority_pda: get_cpi_authority_pda().0, - mint: mint_1, + mint: Some(mint_1), token_pool_pda: mint_pool_1, token_program, light_system_program: light_system_program::ID, @@ -1160,45 +1162,6 @@ async fn test_mint_to_failing() { ) .unwrap(); } - // // 7. Invalid noop program. (not used anymore since we removed the event) - // { - // let invalid_noop_program = Keypair::new(); - // let accounts = light_compressed_token::accounts::MintToInstruction { - // fee_payer: payer_1.pubkey(), - // authority: payer_1.pubkey(), - // cpi_authority_pda: get_cpi_authority_pda().0, - // mint: mint_1, - // token_pool_pda: mint_pool_1, - // token_program, - // light_system_program: light_system_program::ID, - // registered_program_pda: light_system_program::utils::get_registered_program_pda( - // &light_system_program::ID, - // ), - // noop_program: invalid_noop_program.pubkey(), - // account_compression_authority: light_system_program::utils::get_cpi_authority_pda( - // &light_system_program::ID, - // ), - // account_compression_program: account_compression::ID, - // merkle_tree: merkle_tree_pubkey, - // self_program: light_compressed_token::ID, - // system_program: system_program::ID, - // sol_pool_pda: None, - // }; - // let instruction = Instruction { - // program_id: light_compressed_token::ID, - // accounts: accounts.to_account_metas(Some(true)), - // data: instruction_data.data(), - // }; - // let result = rpc - // .create_and_send_transaction(&[instruction], &payer_1.pubkey(), &[&payer_1]) - // .await; - // assert_rpc_error( - // result, - // 0, - // account_compression::errors::AccountCompressionErrorCode::InvalidNoopPubkey.into(), - // ) - // .unwrap(); - // } // 8. Invalid account compression authority. { let invalid_account_compression_authority = Keypair::new(); @@ -1206,7 +1169,7 @@ async fn test_mint_to_failing() { fee_payer: payer_1.pubkey(), authority: payer_1.pubkey(), cpi_authority_pda: get_cpi_authority_pda().0, - mint: mint_1, + mint: Some(mint_1), token_pool_pda: mint_pool_1, token_program, light_system_program: light_system_program::ID, @@ -1228,15 +1191,19 @@ async fn test_mint_to_failing() { accounts: accounts.to_account_metas(Some(true)), data: instruction_data.data(), }; + // TransactionError(InstructionError(0, PrivilegeEscalation) let result = rpc .create_and_send_transaction(&[instruction], &payer_1.pubkey(), &[&payer_1]) - .await; - assert_rpc_error( - result, - 0, - anchor_lang::error::ErrorCode::ConstraintSeeds.into(), - ) - .unwrap(); + .await + .unwrap_err(); + println!( + "result + .to_string() {}", + result.to_string() + ); + assert!(result + .to_string() + .contains("Error processing Instruction 0: Cross-program invocation with unauthorized signer or writable account")); } // 9. Invalid Merkle tree. { @@ -1262,50 +1229,6 @@ async fn test_mint_to_failing() { ) .unwrap(); } - // 10. Mint more than `u64::MAX` tokens. - { - // Overall sum greater than `u64::MAX` - let amounts = vec![u64::MAX / 5; MINTS]; - let instruction = create_mint_to_instruction( - &payer_1.pubkey(), - &payer_1.pubkey(), - &mint_1, - &merkle_tree_pubkey, - amounts, - recipients.clone(), - None, - is_token_22, - 0, - ); - let result = rpc - .create_and_send_transaction(&[instruction], &payer_1.pubkey(), &[&payer_1]) - .await; - assert_rpc_error(result, 0, ErrorCode::MintTooLarge.into()).unwrap(); - } - // 11. Multiple mints which overflow the token supply over `u64::MAX`. - { - let amounts = vec![u64::MAX / 10; MINTS]; - let instruction = create_mint_to_instruction( - &payer_1.pubkey(), - &payer_1.pubkey(), - &mint_1, - &merkle_tree_pubkey, - amounts, - recipients.clone(), - None, - is_token_22, - 0, - ); - // The first mint is still below `u64::MAX`. - rpc.create_and_send_transaction(&[instruction.clone()], &payer_1.pubkey(), &[&payer_1]) - .await - .unwrap(); - // The second mint should overflow. - let result = rpc - .create_and_send_transaction(&[instruction], &payer_1.pubkey(), &[&payer_1]) - .await; - assert_rpc_error(result, 0, TokenError::Overflow as u32).unwrap(); - } } } @@ -5413,3 +5336,477 @@ async fn test_transfer_with_batched_tree() { } } } +use light_batched_merkle_tree::{ + initialize_address_tree::InitAddressTreeAccountsInstructionData, + initialize_state_tree::InitStateTreeAccountsInstructionData, +}; +/// Test cases: +/// 1. Functional compress 0 to 26 recipients +/// 2. Failing unequal recipients amounts len +/// 3. Failing insufficient balance +/// 4. Failing sender account and token pool account with different mint +/// 5. Failing invalid derived token pool pda +/// 6. Failing invalid token pool pda derived from different index +/// 7. Failing no sender token account +#[serial] +#[tokio::test] +async fn batch_compress_with_batched_tree() { + let (mut rpc, env) = + setup_test_programs_with_accounts_with_protocol_config_and_batched_tree_params( + None, + ProtocolConfig::default(), + true, + InitStateTreeAccountsInstructionData::default(), + InitAddressTreeAccountsInstructionData::default(), + ) + .await; + let payer = rpc.get_payer().insecure_clone(); + let merkle_tree_pubkey = env.batched_output_queue; + let mut test_indexer = + TestIndexer::::init_from_env(&payer, &env, None).await; + let sender = Keypair::new(); + airdrop_lamports(&mut rpc, &sender.pubkey(), 1_000_000_000) + .await + .unwrap(); + let delegate = Keypair::new(); + airdrop_lamports(&mut rpc, &delegate.pubkey(), 1_000_000_000) + .await + .unwrap(); + let mint = create_mint_helper(&mut rpc, &payer).await; + let amount = 10000u64; + let token_account = Keypair::new(); + create_token_account(&mut rpc, &mint, &token_account, &payer) + .await + .unwrap(); + let token_account = token_account.pubkey(); + mint_spl_tokens( + &mut rpc, + &mint, + &token_account, + &payer.pubkey(), + &payer, + amount, + false, + ) + .await + .unwrap(); + // 1. Functional compress 0 to 26 recipients + for num_recipients in 1..=26 { + let recipients = (0..num_recipients) + .map(|_| Pubkey::new_unique()) + .collect::>(); + let amounts = (1..num_recipients + 1).collect::>(); + let sum_amounts: u64 = amounts.iter().sum(); + let ix = create_batch_compress_instruction( + &payer.pubkey(), + &payer.pubkey(), + &mint, + &merkle_tree_pubkey, + Some(amounts), + None, + recipients.clone(), + None, + false, + 0, + token_account, + BatchCompressTestMode::Functional, + None, + ); + let token_pool_pda = get_token_pool_pda_with_index(&mint, 0); + let token_pool_account = rpc.get_account(token_pool_pda).await.unwrap().unwrap(); + use std::borrow::Borrow; + let pre_token_pool_balance = + TokenAccount::try_deserialize_unchecked(&mut token_pool_account.data.borrow()) + .unwrap() + .amount; + + let (event, _, slot) = rpc + .create_and_send_transaction_with_public_event(&[ix], &payer.pubkey(), &[&payer], None) + .await + .unwrap() + .unwrap(); + test_indexer.add_compressed_accounts_with_token_data(slot, &event); + + for i in 0..(num_recipients as usize) { + let recipient_compressed_token_accounts = test_indexer + .get_compressed_token_accounts_by_owner(&recipients[i], None) + .await + .unwrap(); + assert_eq!(recipient_compressed_token_accounts.len(), 1); + let recipient_compressed_token_account = &recipient_compressed_token_accounts[0]; + let expected_token_data = light_sdk::token::TokenData { + mint, + owner: recipients[i], + amount: (i + 1) as u64, + delegate: None, + state: AccountState::Initialized, + tlv: None, + }; + assert_eq!( + recipient_compressed_token_account.token_data, + expected_token_data + ); + } + let token_pool_account = rpc.get_account(token_pool_pda).await.unwrap().unwrap(); + let token_pool_account = + TokenAccount::try_deserialize_unchecked(&mut token_pool_account.data.borrow()).unwrap(); + assert_eq!( + token_pool_account.amount, + sum_amounts + pre_token_pool_balance + ); + } + for num_recipients in 1..=26 { + let recipients = (0..num_recipients) + .map(|_| Pubkey::new_unique()) + .collect::>(); + let amount = 1; + let sum_amounts: u64 = recipients.len() as u64; + let ix = create_batch_compress_instruction( + &payer.pubkey(), + &payer.pubkey(), + &mint, + &merkle_tree_pubkey, + None, + Some(amount), + recipients.clone(), + None, + false, + 0, + token_account, + BatchCompressTestMode::Functional, + None, + ); + let token_pool_pda = get_token_pool_pda_with_index(&mint, 0); + let token_pool_account = rpc.get_account(token_pool_pda).await.unwrap().unwrap(); + use std::borrow::Borrow; + let pre_token_pool_balance = + TokenAccount::try_deserialize_unchecked(&mut token_pool_account.data.borrow()) + .unwrap() + .amount; + + let (event, _, slot) = rpc + .create_and_send_transaction_with_public_event(&[ix], &payer.pubkey(), &[&payer], None) + .await + .unwrap() + .unwrap(); + test_indexer.add_compressed_accounts_with_token_data(slot, &event); + + for i in 0..(num_recipients as usize) { + let recipient_compressed_token_accounts = test_indexer + .get_compressed_token_accounts_by_owner(&recipients[i], None) + .await + .unwrap(); + assert_eq!(recipient_compressed_token_accounts.len(), 1); + let recipient_compressed_token_account = &recipient_compressed_token_accounts[0]; + let expected_token_data = light_sdk::token::TokenData { + mint, + owner: recipients[i], + amount: amount as u64, + delegate: None, + state: AccountState::Initialized, + tlv: None, + }; + assert_eq!( + recipient_compressed_token_account.token_data, + expected_token_data + ); + } + let token_pool_account = rpc.get_account(token_pool_pda).await.unwrap().unwrap(); + let token_pool_account = + TokenAccount::try_deserialize_unchecked(&mut token_pool_account.data.borrow()).unwrap(); + assert_eq!( + token_pool_account.amount, + sum_amounts + pre_token_pool_balance + ); + } + + // 2. Failing unequal recipients amounts len + { + let num_recipients = 26; + let recipients = (0..num_recipients) + .map(|_| Pubkey::new_unique()) + .collect::>(); + let ix = create_batch_compress_instruction( + &payer.pubkey(), + &payer.pubkey(), + &mint, + &merkle_tree_pubkey, + Some((1..num_recipients).collect::>()), + None, + recipients.clone(), + None, + false, + 0, + token_account, + BatchCompressTestMode::Functional, + None, + ); + let result = rpc + .create_and_send_transaction_with_public_event(&[ix], &payer.pubkey(), &[&payer], None) + .await; + assert_rpc_error( + result, + 0, + light_compressed_token::ErrorCode::PublicKeyAmountMissmatch.into(), + ) + .unwrap(); + } + // 3. Failing insufficient balance + { + let num_recipients = 1; + let recipients = (0..num_recipients) + .map(|_| Pubkey::new_unique()) + .collect::>(); + let ix = create_batch_compress_instruction( + &payer.pubkey(), + &payer.pubkey(), + &mint, + &merkle_tree_pubkey, + Some(vec![10000; 1]), + None, + recipients.clone(), + None, + false, + 0, + token_account, + BatchCompressTestMode::Functional, + None, + ); + let result = rpc + .create_and_send_transaction_with_public_event(&[ix], &payer.pubkey(), &[&payer], None) + .await; + assert_rpc_error(result, 0, TokenError::InsufficientFunds as u32).unwrap(); + } + // 4. Sender account invalid mint + { + let invalid_mint = create_mint_helper(&mut rpc, &payer).await; + let invalid_token_account_invalid_mint = Keypair::new(); + create_token_account( + &mut rpc, + &invalid_mint, + &invalid_token_account_invalid_mint, + &payer, + ) + .await + .unwrap(); + let invalid_token_account_invalid_mint = invalid_token_account_invalid_mint.pubkey(); + mint_spl_tokens( + &mut rpc, + &invalid_mint, + &invalid_token_account_invalid_mint, + &payer.pubkey(), + &payer, + amount, + false, + ) + .await + .unwrap(); + let num_recipients = 1; + let recipients = (0..num_recipients) + .map(|_| Pubkey::new_unique()) + .collect::>(); + // Token account has different mint than token pool account + { + let ix = create_batch_compress_instruction( + &payer.pubkey(), + &payer.pubkey(), + &mint, + &merkle_tree_pubkey, + Some(vec![1; 1]), + None, + recipients.clone(), + None, + false, + 0, + invalid_token_account_invalid_mint, + BatchCompressTestMode::Functional, + None, + ); + let result = rpc + .create_and_send_transaction_with_public_event( + &[ix], + &payer.pubkey(), + &[&payer], + None, + ) + .await; + // spl_token::error::TokenError::InvalidMint + assert_rpc_error( + result, + 0, + light_compressed_token::ErrorCode::InvalidTokenPoolPda.into(), + ) + .unwrap(); + } + } + let num_recipients = 1; + let recipients = (0..num_recipients) + .map(|_| Pubkey::new_unique()) + .collect::>(); + // 5. Invalid derived token pool account + // just pass a normal token account instead. + { + let ix = create_batch_compress_instruction( + &payer.pubkey(), + &payer.pubkey(), + &mint, + &merkle_tree_pubkey, + Some(vec![1; 1]), + None, + recipients.clone(), + None, + false, + 0, + token_account, + BatchCompressTestMode::Functional, + Some(token_account), + ); + let result = rpc + .create_and_send_transaction_with_public_event(&[ix], &payer.pubkey(), &[&payer], None) + .await; + assert_rpc_error( + result, + 0, + light_compressed_token::ErrorCode::InvalidTokenPoolPda.into(), + ) + .unwrap(); + } + // 6. Failing, token pool account derived from invalid index + { + let ix = create_batch_compress_instruction( + &payer.pubkey(), + &payer.pubkey(), + &mint, + &merkle_tree_pubkey, + Some(vec![1; 1]), + None, + recipients.clone(), + None, + false, + 0, + token_account, + BatchCompressTestMode::Functional, + Some(token_account), + ); + let result = rpc + .create_and_send_transaction_with_public_event(&[ix], &payer.pubkey(), &[&payer], None) + .await; + assert_rpc_error( + result, + 0, + light_compressed_token::ErrorCode::InvalidTokenPoolPda.into(), + ) + .unwrap(); + } + // 7. Failing, pass no sender account. + { + let ix = create_batch_compress_instruction( + &payer.pubkey(), + &payer.pubkey(), + &mint, + &merkle_tree_pubkey, + Some(vec![1; 1]), + None, + recipients.clone(), + None, + false, + 0, + token_account, + BatchCompressTestMode::NoSender, + None, + ); + let result = rpc + .create_and_send_transaction_with_public_event(&[ix], &payer.pubkey(), &[&payer], None) + .await; + assert_rpc_error(result, 0, 0).unwrap(); + } +} + +#[derive(Debug, PartialEq, Eq)] +pub enum BatchCompressTestMode { + Functional, + NoSender, + InvalidTokenPoolWithIndex1, +} + +#[allow(clippy::too_many_arguments)] +pub fn create_batch_compress_instruction( + fee_payer: &Pubkey, + authority: &Pubkey, + mint: &Pubkey, + merkle_tree: &Pubkey, + amounts: Option>, + amount: Option, + public_keys: Vec, + lamports: Option, + token_2022: bool, + token_pool_index: u8, + sender: Pubkey, + mode: BatchCompressTestMode, + invalid_token_pool: Option, +) -> Instruction { + let token_pool_pda = if let Some(invalid_token_pool) = invalid_token_pool { + invalid_token_pool + } else if mode == BatchCompressTestMode::InvalidTokenPoolWithIndex1 { + get_token_pool_pda_with_index(mint, 1) + } else { + get_token_pool_pda_with_index(mint, token_pool_index) + }; + + let instruction_input = BatchCompressInstructionDataBorsh { + amounts, + amount, + pubkeys: public_keys, + lamports, + index: token_pool_index, + }; + let mut bytes = Vec::new(); + instruction_input.serialize(&mut bytes).unwrap(); + let instruction_data = light_compressed_token::instruction::BatchCompress { inputs: bytes }; + let sol_pool_pda = if lamports.is_some() { + Some(get_sol_pool_pda()) + } else { + None + }; + let token_program = if token_2022 { + anchor_spl::token_2022::ID + } else { + anchor_spl::token::ID + }; + + let accounts = light_compressed_token::accounts::MintToInstruction { + fee_payer: *fee_payer, + authority: *authority, + cpi_authority_pda: get_cpi_authority_pda().0, + mint: None, + token_pool_pda, + token_program, + light_system_program: light_system_program::ID, + registered_program_pda: light_system_program::utils::get_registered_program_pda( + &light_system_program::ID, + ), + noop_program: Pubkey::new_from_array(account_compression::utils::constants::NOOP_PUBKEY), + account_compression_authority: light_system_program::utils::get_cpi_authority_pda( + &light_system_program::ID, + ), + account_compression_program: account_compression::ID, + merkle_tree: *merkle_tree, + self_program: light_compressed_token::ID, + system_program: system_program::ID, + sol_pool_pda, + }; + let accounts = if mode == BatchCompressTestMode::NoSender { + accounts.to_account_metas(Some(true)) + } else { + [ + accounts.to_account_metas(Some(true)), + vec![AccountMeta::new(sender, false)], + ] + .concat() + }; + Instruction { + program_id: light_compressed_token::ID, + accounts, + data: instruction_data.data(), + } +} diff --git a/programs/compressed-token/Cargo.toml b/programs/compressed-token/Cargo.toml index 3ce879dfb5..d6cc3105f8 100644 --- a/programs/compressed-token/Cargo.toml +++ b/programs/compressed-token/Cargo.toml @@ -31,11 +31,15 @@ light-system-program = { workspace = true, features = ["cpi"] } solana-security-txt = "1.1.0" light-hasher = { workspace = true } light-heap = { workspace = true, optional = true } -light-compressed-account = { workspace = true } +light-compressed-account = { workspace = true, features = ["anchor"] } spl-token-2022 = { workspace = true } +light-zero-copy = { workspace = true } +zerocopy = { workspace = true } + [target.'cfg(not(target_os = "solana"))'.dependencies] solana-sdk = { workspace = true } + [dev-dependencies] rand = { workspace = true } diff --git a/programs/compressed-token/src/batch_compress.rs b/programs/compressed-token/src/batch_compress.rs new file mode 100644 index 0000000000..8df9ed4f99 --- /dev/null +++ b/programs/compressed-token/src/batch_compress.rs @@ -0,0 +1,96 @@ +use anchor_lang::{prelude::*, AnchorDeserialize, AnchorSerialize}; +use light_zero_copy::{borsh::Deserialize, errors::ZeroCopyError, slice::ZeroCopySliceBorsh}; +use zerocopy::{little_endian::U64, Ref}; + +#[derive(Debug, Default, Clone, PartialEq, AnchorSerialize, AnchorDeserialize)] +pub struct BatchCompressInstructionDataBorsh { + pub pubkeys: Vec, + pub amounts: Option>, + pub lamports: Option, + pub amount: Option, + pub index: u8, +} + +pub struct BatchCompressInstructionData<'a> { + pub pubkeys: ZeroCopySliceBorsh<'a, light_compressed_account::pubkey::Pubkey>, + pub amounts: Option>, + pub lamports: Option>, + pub amount: Option>, + pub index: u8, +} + +impl<'a> Deserialize<'a> for BatchCompressInstructionData<'a> { + type Output = Self; + + fn zero_copy_at(bytes: &'a [u8]) -> std::result::Result<(Self, &'a [u8]), ZeroCopyError> { + let (pubkeys, bytes) = ZeroCopySliceBorsh::from_bytes_at(bytes)?; + let (amounts, bytes) = Option::>::zero_copy_at(bytes)?; + let (lamports, bytes) = Option::::zero_copy_at(bytes)?; + let (amount, bytes) = Option::::zero_copy_at(bytes)?; + let (index, bytes) = u8::zero_copy_at(bytes)?; + Ok(( + Self { + pubkeys, + amounts, + lamports, + amount, + index, + }, + bytes, + )) + } +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn test_batch_compress_instruction_data() { + let data = super::BatchCompressInstructionDataBorsh { + pubkeys: vec![Pubkey::new_unique(), Pubkey::new_unique()], + amounts: Some(vec![1, 2]), + lamports: Some(3), + amount: Some(1), + index: 1, + }; + let mut vec = Vec::new(); + data.serialize(&mut vec).unwrap(); + let (decoded_data, _) = super::BatchCompressInstructionData::zero_copy_at(&vec).unwrap(); + assert_eq!(decoded_data.pubkeys.len(), 2); + assert_eq!(decoded_data.amounts.as_ref().unwrap().len(), 2); + assert_eq!(*decoded_data.lamports.unwrap(), U64::from(3)); + for (i, pubkey) in decoded_data.pubkeys.iter().enumerate() { + assert_eq!(data.pubkeys[i], pubkey.into(),); + } + for (i, amount) in decoded_data.amounts.as_ref().unwrap().iter().enumerate() { + assert_eq!(amount.get(), data.amounts.as_ref().unwrap()[i]); + } + assert_eq!(decoded_data.index, 1); + assert_eq!(*decoded_data.amount.unwrap(), data.amount.unwrap()); + } + + #[test] + fn test_batch_compress_instruction_data_none() { + let data = super::BatchCompressInstructionDataBorsh { + pubkeys: vec![Pubkey::new_unique(), Pubkey::new_unique()], + amounts: Some(vec![1, 2]), + amount: None, + lamports: None, + index: 0, + }; + let mut vec = Vec::new(); + data.serialize(&mut vec).unwrap(); + let (decoded_data, _) = super::BatchCompressInstructionData::zero_copy_at(&vec).unwrap(); + assert_eq!(decoded_data.pubkeys.len(), 2); + assert_eq!(decoded_data.amounts.as_ref().unwrap().len(), 2); + assert!(decoded_data.lamports.is_none()); + for (i, pubkey) in decoded_data.pubkeys.iter().enumerate() { + assert_eq!(data.pubkeys[i], (*pubkey).into(),); + } + for (i, amount) in decoded_data.amounts.as_ref().unwrap().iter().enumerate() { + assert_eq!(amount.get(), data.amounts.as_ref().unwrap()[i]); + } + assert_eq!(decoded_data.index, 0); + assert_eq!(decoded_data.amount, None); + } +} diff --git a/programs/compressed-token/src/instructions/create_token_pool.rs b/programs/compressed-token/src/instructions/create_token_pool.rs index 59925643d9..c8ea4477c0 100644 --- a/programs/compressed-token/src/instructions/create_token_pool.rs +++ b/programs/compressed-token/src/instructions/create_token_pool.rs @@ -118,6 +118,21 @@ pub fn check_spl_token_pool_derivation(token_pool_pda: &Pubkey, mint: &Pubkey) - } } +#[inline(always)] +pub fn check_spl_token_pool_derivation_with_index( + token_pool_pda: &Pubkey, + mint: &Pubkey, + index: u8, +) -> Result<()> { + let mint_bytes = mint.to_bytes(); + let is_valid = is_valid_token_pool_pda(mint_bytes.as_slice(), token_pool_pda, &[index]); + if !is_valid { + err!(crate::ErrorCode::InvalidTokenPoolPda) + } else { + Ok(()) + } +} + #[cfg(test)] mod test { use super::*; diff --git a/programs/compressed-token/src/lib.rs b/programs/compressed-token/src/lib.rs index a7e4e8f8f6..d004c2dbd5 100644 --- a/programs/compressed-token/src/lib.rs +++ b/programs/compressed-token/src/lib.rs @@ -15,6 +15,7 @@ pub mod instructions; pub use instructions::*; pub mod burn; pub use burn::*; +pub mod batch_compress; use light_compressed_account::instruction_data::cpi_context::CompressedCpiContext; use crate::process_transfer::CompressedTokenInstructionDataTransfer; @@ -33,6 +34,7 @@ solana_security_txt::security_txt! { pub mod light_compressed_token { use constants::{NOT_FROZEN, NUM_MAX_POOL_ACCOUNTS}; + use light_zero_copy::borsh::Deserialize; use spl_compression::check_spl_token_pool_derivation_with_index; use super::*; @@ -79,7 +81,40 @@ pub mod light_compressed_token { amounts: Vec, lamports: Option, ) -> Result<()> { - process_mint_to(ctx, public_keys, amounts, lamports) + process_mint_to::( + ctx, + public_keys.as_slice(), + amounts.as_slice(), + lamports, + None, + ) + } + + /// Batch compress tokens to a list of compressed accounts. + pub fn batch_compress<'info>( + ctx: Context<'_, '_, '_, 'info, MintToInstruction<'info>>, + inputs: Vec, + ) -> Result<()> { + let (inputs, _) = batch_compress::BatchCompressInstructionData::zero_copy_at(&inputs) + .map_err(ProgramError::from)?; + if inputs.amounts.is_some() && inputs.amount.is_some() { + return Err(crate::ErrorCode::AmountsAndAmountProvided.into()); + } + let amounts = if let Some(amount) = inputs.amount { + vec![*amount; inputs.pubkeys.len()] + } else if let Some(amounts) = inputs.amounts { + amounts.to_vec() + } else { + return Err(crate::ErrorCode::NoAmount.into()); + }; + + process_mint_to::( + ctx, + inputs.pubkeys.as_slice(), + amounts.as_slice(), + inputs.lamports.map(|x| (*x).into()), + Some(inputs.index), + ) } /// Compresses the balance of an spl token account sub an optional remaining @@ -234,4 +269,6 @@ pub enum ErrorCode { FailedToDecompress, FailedToBurnSplTokensFromTokenPool, NoMatchingBumpFound, + NoAmount, + AmountsAndAmountProvided, } diff --git a/programs/compressed-token/src/process_mint.rs b/programs/compressed-token/src/process_mint.rs index 6b7011e59a..6a59c972fa 100644 --- a/programs/compressed-token/src/process_mint.rs +++ b/programs/compressed-token/src/process_mint.rs @@ -1,18 +1,27 @@ -use account_compression::{program::AccountCompression, utils::constants::CPI_AUTHORITY_PDA_SEED}; +use account_compression::program::AccountCompression; use anchor_lang::prelude::*; -use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; -use light_compressed_account::instruction_data::data::OutputCompressedAccountWithPackedContext; +use anchor_spl::token_interface::{TokenAccount, TokenInterface}; +use light_compressed_account::{ + instruction_data::data::OutputCompressedAccountWithPackedContext, + pubkey::{PubkeyTrait, ZeroCopyNumTrait}, +}; use light_system_program::program::LightSystemProgram; #[cfg(target_os = "solana")] use { - crate::process_transfer::create_output_compressed_accounts, - crate::process_transfer::get_cpi_signer_seeds, + crate::{ + check_spl_token_pool_derivation_with_index, + process_transfer::create_output_compressed_accounts, + process_transfer::get_cpi_signer_seeds, spl_compression::spl_token_transfer, + }, light_compressed_account::hash_to_bn254_field_size_be, light_heap::{bench_sbf_end, bench_sbf_start, GLOBAL_ALLOCATOR}, }; use crate::{check_spl_token_pool_derivation, program::LightCompressedToken}; +pub const COMPRESS: bool = false; +pub const MINT_TO: bool = true; + /// Mints tokens from an spl token mint to a list of compressed accounts and /// stores minted tokens in spl token pool account. /// @@ -26,11 +35,12 @@ use crate::{check_spl_token_pool_derivation, program::LightCompressedToken}; /// pre_compressed_acounts_pos. /// 5. Invoke system program to execute the compressed transaction. #[allow(unused_variables)] -pub fn process_mint_to( - ctx: Context, - recipient_pubkeys: Vec, - amounts: Vec, +pub fn process_mint_to<'info, const IS_MINT_TO: bool>( + ctx: Context<'_, '_, '_, 'info, MintToInstruction<'info>>, + recipient_pubkeys: &[impl PubkeyTrait], + amounts: &[impl ZeroCopyNumTrait], lamports: Option, + index: Option, ) -> Result<()> { if recipient_pubkeys.len() != amounts.len() { msg!( @@ -64,21 +74,46 @@ pub fn process_mint_to( let pre_compressed_acounts_pos = GLOBAL_ALLOCATOR.get_heap_pos(); bench_sbf_start!("tm_mint_spl_to_pool_pda"); - // 7,912 CU - mint_spl_to_pool_pda(&ctx, &amounts)?; + let mint = if IS_MINT_TO { + // 7,978 CU + mint_spl_to_pool_pda(&ctx, &amounts)?; + ctx.accounts.mint.as_ref().unwrap().key() + } else { + let mut amount = 0u64; + for a in amounts { + amount += (*a).into(); + } + let index = if let Some(index) = index { + index + } else { + panic!("No index provided for batch compress."); + }; + let mint = + TokenAccount::try_deserialize(&mut &ctx.remaining_accounts[0].data.borrow()[..])? + .mint; + check_spl_token_pool_derivation_with_index( + &ctx.accounts.token_pool_pda.key(), + &mint, + index, + )?; + spl_token_transfer( + ctx.remaining_accounts[0].to_account_info(), + ctx.accounts.token_pool_pda.to_account_info(), + ctx.accounts.authority.to_account_info(), + ctx.accounts.token_program.to_account_info(), + amount, + )?; + mint + }; + let hashed_mint = hash_to_bn254_field_size_be(mint.as_ref()).unwrap().0; - bench_sbf_end!("tm_mint_spl_to_pool_pda"); - let hashed_mint = hash_to_bn254_field_size_be(ctx.accounts.mint.key().as_ref()) - .unwrap() - .0; - bench_sbf_start!("tm_output_compressed_accounts"); let mut output_compressed_accounts = vec![OutputCompressedAccountWithPackedContext::default(); recipient_pubkeys.len()]; let lamports_vec = lamports.map(|_| vec![lamports; amounts.len()]); create_output_compressed_accounts( &mut output_compressed_accounts, - ctx.accounts.mint.key(), - recipient_pubkeys.as_slice(), + mint, + recipient_pubkeys, None, None, &amounts, @@ -274,18 +309,27 @@ pub fn serialize_mint_to_cpi_instruction_data( } #[inline(never)] -pub fn mint_spl_to_pool_pda(ctx: &Context, amounts: &[u64]) -> Result<()> { - check_spl_token_pool_derivation(&ctx.accounts.token_pool_pda.key(), &ctx.accounts.mint.key())?; +pub fn mint_spl_to_pool_pda( + ctx: &Context, + amounts: &[impl ZeroCopyNumTrait], +) -> Result<()> { + check_spl_token_pool_derivation( + &ctx.accounts.token_pool_pda.key(), + &ctx.accounts.mint.as_ref().unwrap().key(), + )?; let mut mint_amount: u64 = 0; for amount in amounts.iter() { mint_amount = mint_amount - .checked_add(*amount) + .checked_add((*amount).into()) .ok_or(crate::ErrorCode::MintTooLarge)?; } - let pre_token_balance = ctx.accounts.token_pool_pda.amount; + let pre_token_balance = TokenAccount::try_deserialize( + &mut &ctx.accounts.token_pool_pda.to_account_info().data.borrow()[..], + )? + .amount; let cpi_accounts = anchor_spl::token_interface::MintTo { - mint: ctx.accounts.mint.to_account_info(), + mint: ctx.accounts.mint.as_ref().unwrap().to_account_info(), to: ctx.accounts.token_pool_pda.to_account_info(), authority: ctx.accounts.authority.to_account_info(), }; @@ -317,18 +361,14 @@ pub struct MintToInstruction<'info> { pub fee_payer: Signer<'info>, /// CHECK: is checked by mint account macro. pub authority: Signer<'info>, - /// CHECK: - #[account(seeds = [CPI_AUTHORITY_PDA_SEED], bump)] + /// CHECK: checked implicitly by signing the cpi pub cpi_authority_pda: UncheckedAccount<'info>, - #[account( - mut, - constraint = mint.mint_authority.unwrap() == authority.key() - @ crate::ErrorCode::InvalidAuthorityMint - )] - pub mint: InterfaceAccount<'info, Mint>, + /// CHECK: implicitly by invoking spl token program + #[account(mut)] + pub mint: Option>, /// CHECK: with check_spl_token_pool_derivation(). #[account(mut)] - pub token_pool_pda: InterfaceAccount<'info, TokenAccount>, + pub token_pool_pda: UncheckedAccount<'info>, pub token_program: Interface<'info, TokenInterface>, pub light_system_program: Program<'info, LightSystemProgram>, /// CHECK: (different program) checked in account compression program @@ -336,8 +376,7 @@ pub struct MintToInstruction<'info> { /// CHECK: (different program) checked in system and account compression /// programs pub noop_program: UncheckedAccount<'info>, - /// CHECK: this account in account compression program - #[account(seeds = [CPI_AUTHORITY_PDA_SEED], bump, seeds::program = light_system_program::ID)] + /// CHECK: pub account_compression_authority: UncheckedAccount<'info>, /// CHECK: this account in account compression program pub account_compression_program: Program<'info, AccountCompression>, @@ -458,7 +497,7 @@ pub mod mint_sdk { fee_payer: *fee_payer, authority: *authority, cpi_authority_pda: get_cpi_authority_pda().0, - mint: *mint, + mint: Some(*mint), token_pool_pda, token_program, light_system_program: light_system_program::ID, diff --git a/programs/compressed-token/src/process_transfer.rs b/programs/compressed-token/src/process_transfer.rs index 0973c82787..9d510c8187 100644 --- a/programs/compressed-token/src/process_transfer.rs +++ b/programs/compressed-token/src/process_transfer.rs @@ -10,6 +10,7 @@ use light_compressed_account::{ compressed_proof::CompressedProof, cpi_context::CompressedCpiContext, data::OutputCompressedAccountWithPackedContext, invoke_cpi::InstructionDataInvokeCpi, }, + pubkey::{PubkeyTrait, ZeroCopyNumTrait}, }; use light_hasher::Poseidon; use light_heap::{bench_sbf_end, bench_sbf_start}; @@ -185,18 +186,18 @@ pub fn process_transfer<'a, 'b, 'c, 'info: 'b + 'c>( #[allow(clippy::too_many_arguments)] pub fn create_output_compressed_accounts( output_compressed_accounts: &mut [OutputCompressedAccountWithPackedContext], - mint_pubkey: Pubkey, - pubkeys: &[Pubkey], + mint_pubkey: impl PubkeyTrait, + pubkeys: &[impl PubkeyTrait], delegate: Option, is_delegate: Option>, - amounts: &[u64], - lamports: Option>>, + amounts: &[impl ZeroCopyNumTrait], + lamports: Option>>, hashed_mint: &[u8; 32], merkle_tree_indices: &[u8], ) -> Result { let mut sum_lamports = 0; let hashed_delegate_store = if let Some(delegate) = delegate { - hash_to_bn254_field_size_be(delegate.to_bytes().as_slice()) + hash_to_bn254_field_size_be(delegate.trait_to_bytes().as_slice()) .unwrap() .0 } else { @@ -226,17 +227,19 @@ pub fn create_output_compressed_accounts( let mut token_data_bytes = Vec::with_capacity(capacity); // 1,000 CU token data and serialize let token_data = TokenData { - mint: mint_pubkey, - owner: *owner, - amount: *amount, + mint: (mint_pubkey).to_anchor_pubkey(), + owner: (*owner).to_anchor_pubkey(), + amount: (*amount).into(), delegate, state: AccountState::Initialized, tlv: None, }; token_data.serialize(&mut token_data_bytes).unwrap(); bench_sbf_start!("token_data_hash"); - let hashed_owner = hash_to_bn254_field_size_be(owner.as_ref()).unwrap().0; - let amount_bytes = amount.to_le_bytes(); + let hashed_owner = hash_to_bn254_field_size_be(&owner.trait_to_bytes()) + .unwrap() + .0; + let amount_bytes = amount.to_bytes_le(); let data_hash = TokenData::hash_with_hashed_values::( hashed_mint, &hashed_owner, @@ -254,12 +257,12 @@ pub fn create_output_compressed_accounts( let lamports = lamports .as_ref() .and_then(|lamports| lamports[i]) - .unwrap_or(0); - sum_lamports += lamports; + .unwrap_or(0u64.into()); + sum_lamports += lamports.into(); output_compressed_accounts[i] = OutputCompressedAccountWithPackedContext { compressed_account: CompressedAccount { owner: crate::ID, - lamports, + lamports: lamports.into(), data: Some(data), address: None, }, diff --git a/programs/compressed-token/src/spl_compression.rs b/programs/compressed-token/src/spl_compression.rs index 93f255db3b..b3b67d91f7 100644 --- a/programs/compressed-token/src/spl_compression.rs +++ b/programs/compressed-token/src/spl_compression.rs @@ -236,11 +236,29 @@ pub fn spl_token_transfer<'info>( token_program: AccountInfo<'info>, amount: u64, ) -> Result<()> { - let accounts = token_interface::Transfer { - from, - to, - authority, - }; - let cpi_ctx = CpiContext::new(token_program, accounts); - anchor_spl::token_interface::transfer(cpi_ctx, amount) + let instruction = match *token_program.key { + spl_token_2022::ID => spl_token_2022::instruction::transfer( + token_program.key, + from.key, + to.key, + authority.key, + &[], + amount, + ), + spl_token::ID => spl_token::instruction::transfer( + token_program.key, + from.key, + to.key, + authority.key, + &[], + amount, + ), + _ => return Err(anchor_lang::error::ErrorCode::InvalidProgramId.into()), + }?; + + anchor_lang::solana_program::program::invoke( + &instruction, + &[from, to, authority, token_program], + )?; + Ok(()) } diff --git a/sdk-libs/program-test/src/indexer/test_indexer.rs b/sdk-libs/program-test/src/indexer/test_indexer.rs index 35dd32e240..a7ed094845 100644 --- a/sdk-libs/program-test/src/indexer/test_indexer.rs +++ b/sdk-libs/program-test/src/indexer/test_indexer.rs @@ -1698,13 +1698,11 @@ where let mut new_addresses = vec![]; if event.output_compressed_accounts.len() > i { let compressed_account = &event.output_compressed_accounts[i]; - println!("output compressed account {:?}", compressed_account); if let Some(address) = compressed_account.compressed_account.address { if !input_addresses.iter().any(|x| x == &address) { new_addresses.push(address); } } - println!("event {:?}", event); let merkle_tree = self.state_merkle_trees.iter().find(|x| { x.accounts.merkle_tree @@ -1822,15 +1820,6 @@ where .push(event.output_compressed_account_hashes[i]); } } - println!("new addresses {:?}", new_addresses); - println!("event.pubkey_array {:?}", event.pubkey_array); - println!( - "address merkle trees {:?}", - self.address_merkle_trees - .iter() - .map(|x| x.accounts.merkle_tree) - .collect::>() - ); // checks whether there are addresses in outputs which don't exist in inputs. // if so check pubkey_array for the first address Merkle tree and append to the bundles queue elements. // Note: diff --git a/sdk-libs/program-test/src/test_rpc.rs b/sdk-libs/program-test/src/test_rpc.rs index 72d5719a11..a140a94b74 100644 --- a/sdk-libs/program-test/src/test_rpc.rs +++ b/sdk-libs/program-test/src/test_rpc.rs @@ -451,11 +451,9 @@ impl RpcConnection for ProgramTestRpcConnection { None:: }) }); - println!("vec: {:?}", vec); - println!("vec_accounts {:?}", vec_accounts); + let (event, _new_addresses) = event_from_light_transaction(vec.as_slice(), vec_accounts).unwrap(); - println!("event: {:?}", event); // If transaction was successful, execute it. if let Some(Ok(())) = simulation_result.result { let result = self