diff --git a/interface/src/instruction.rs b/interface/src/instruction.rs index a829e9a..45e5bd7 100644 --- a/interface/src/instruction.rs +++ b/interface/src/instruction.rs @@ -5,7 +5,7 @@ use pinocchio::{program_error::ProgramError, pubkey::Pubkey}; use crate::error::TokenError; /// Instructions supported by the token program. -#[repr(C)] +#[repr(C, u8)] #[derive(Clone, Debug, PartialEq)] pub enum TokenInstruction<'a> { /// Initializes a new mint and optionally deposits all the newly minted @@ -477,6 +477,21 @@ pub enum TokenInstruction<'a> { /// The ui_amount of tokens to reformat. ui_amount: &'a str, }, + + /// Executes a batch of instructions. The instructions to be executed are specified + /// in sequence on the instruction data. Each instruction provides: + /// - `u8`: number of accounts + /// - `u8`: instruction data length (includes the discriminator) + /// - `u8`: instruction discriminator + /// - `[u8]`: instruction data + /// + /// Accounts follow a similar pattern, where accounts for each instruction are + /// specified in sequence. Therefore, the number of accounts expected by this + /// instruction is variable – i.e., it depends on the instructions provided. + /// + /// Both the number of accountsa and instruction data length are used to identify + /// the slice of accounts and instruction data for each instruction. + Batch = 255, // Any new variants also need to be added to program-2022 `TokenInstruction`, so that the // latter remains a superset of this instruction set. New variants also need to be added to // token/js/src/instructions/types.ts to maintain @solana/spl-token compatibility diff --git a/interface/src/state/account.rs b/interface/src/state/account.rs index 0f0ba64..761896b 100644 --- a/interface/src/state/account.rs +++ b/interface/src/state/account.rs @@ -26,7 +26,7 @@ pub struct Account { delegate: COption, /// The account's state. - pub state: AccountState, + pub state: u8, /// Indicates whether this account represents a native token or not. is_native: [u8; 4], @@ -131,7 +131,7 @@ impl Account { #[inline(always)] pub fn is_frozen(&self) -> bool { - self.state == AccountState::Frozen + self.state == AccountState::Frozen as u8 } #[inline(always)] @@ -147,6 +147,6 @@ impl RawType for Account { impl Initializable for Account { #[inline(always)] fn is_initialized(&self) -> bool { - self.state != AccountState::Uninitialized + self.state != AccountState::Uninitialized as u8 } } diff --git a/program/src/entrypoint.rs b/program/src/entrypoint.rs index 0780829..2429fd6 100644 --- a/program/src/entrypoint.rs +++ b/program/src/entrypoint.rs @@ -11,6 +11,27 @@ no_allocator!(); // Use the default panic handler. default_panic_handler!(); +#[inline(always)] +pub fn process_instruction( + _program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + let [discriminator, instruction_data @ ..] = instruction_data else { + return Err(ProgramError::InvalidInstructionData); + }; + + if *discriminator == 255 { + // 255 - Batch + #[cfg(feature = "logging")] + pinocchio::msg!("Instruction: Batch"); + + return process_batch(accounts, instruction_data); + } + + inner_process_instruction(accounts, instruction_data, *discriminator) +} + /// Process an instruction. /// /// The processor of the token program is divided into two parts to reduce the overhead @@ -21,23 +42,21 @@ default_panic_handler!(); /// /// Instructions on the first part of the processor: /// -/// - `0`: `InitializeMint` -/// - `3`: `Transfer` -/// - `7`: `MintTo` -/// - `9`: `CloseAccount` +/// - `0`: `InitializeMint` +/// - `1`: `InitializeAccount` +/// - `3`: `Transfer` +/// - `7`: `MintTo` +/// - `9`: `CloseAccount` +/// - `18`: `InitializeAccount2` /// - `18`: `InitializeAccount3` /// - `20`: `InitializeMint2` #[inline(always)] -pub fn process_instruction( - _program_id: &Pubkey, +pub fn inner_process_instruction( accounts: &[AccountInfo], instruction_data: &[u8], + discriminator: u8, ) -> ProgramResult { - let (discriminator, instruction_data) = instruction_data - .split_first() - .ok_or(ProgramError::InvalidInstructionData)?; - - match *discriminator { + match discriminator { // 0 - InitializeMint 0 => { #[cfg(feature = "logging")] @@ -45,7 +64,13 @@ pub fn process_instruction( process_initialize_mint(accounts, instruction_data, true) } + // 1 - InitializeAccount + 1 => { + #[cfg(feature = "logging")] + pinocchio::msg!("Instruction: InitializeAccount"); + process_initialize_account(accounts) + } // 3 - Transfer 3 => { #[cfg(feature = "logging")] @@ -67,6 +92,13 @@ pub fn process_instruction( process_close_account(accounts) } + // 16 - InitializeAccount2 + 16 => { + #[cfg(feature = "logging")] + pinocchio::msg!("Instruction: InitializeAccount2"); + + process_initialize_account2(accounts, instruction_data) + } // 18 - InitializeAccount3 18 => { #[cfg(feature = "logging")] @@ -81,7 +113,7 @@ pub fn process_instruction( process_initialize_mint2(accounts, instruction_data) } - _ => process_remaining_instruction(accounts, instruction_data, *discriminator), + _ => inner_process_remaining_instruction(accounts, instruction_data, discriminator), } } @@ -90,19 +122,12 @@ pub fn process_instruction( /// This function is called by the `process_instruction` function if the discriminator /// does not match any of the common instructions. This function is used to reduce the /// overhead of having a large `match` statement in the `process_instruction` function. -fn process_remaining_instruction( +fn inner_process_remaining_instruction( accounts: &[AccountInfo], instruction_data: &[u8], discriminator: u8, ) -> ProgramResult { match discriminator { - // 1 - InitializeAccount - 1 => { - #[cfg(feature = "logging")] - pinocchio::msg!("Instruction: InitializeAccount"); - - process_initialize_account(accounts) - } // 2 - InitializeMultisig 2 => { #[cfg(feature = "logging")] @@ -180,13 +205,6 @@ fn process_remaining_instruction( process_burn_checked(accounts, instruction_data) } - // 16 - InitializeAccount2 - 16 => { - #[cfg(feature = "logging")] - pinocchio::msg!("Instruction: InitializeAccount2"); - - process_initialize_account2(accounts, instruction_data) - } // 17 - SyncNative 17 => { #[cfg(feature = "logging")] diff --git a/program/src/processor/batch.rs b/program/src/processor/batch.rs new file mode 100644 index 0000000..95669d0 --- /dev/null +++ b/program/src/processor/batch.rs @@ -0,0 +1,54 @@ +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; + +use crate::entrypoint::inner_process_instruction; + +/// The size of the batch instruction header. +/// +/// The header of each instruction consists of two `u8` values: +/// * number of the accounts +/// * length of the instruction data +const IX_HEADER_SIZE: usize = 2; + +pub fn process_batch(mut accounts: &[AccountInfo], mut instruction_data: &[u8]) -> ProgramResult { + loop { + // Validates the instruction data and accounts offset. + + if instruction_data.len() < IX_HEADER_SIZE { + // The instruction data must have at least two bytes. + return Err(ProgramError::InvalidInstructionData); + } + + // SAFETY: The instruction data is guaranteed to have at least two bytes (header) + // + one byte (discriminator). + let expected_accounts = unsafe { *instruction_data.get_unchecked(0) as usize }; + let data_offset = IX_HEADER_SIZE + unsafe { *instruction_data.get_unchecked(1) as usize }; + + if instruction_data.len() < data_offset || data_offset == 0 { + return Err(ProgramError::InvalidInstructionData); + } + + if accounts.len() < expected_accounts { + return Err(ProgramError::NotEnoughAccountKeys); + } + + // Process the instruction. + + // SAFETY: The instruction data and accounts lengths are already validated so all + // the slices are guaranteed to be valid. + inner_process_instruction( + unsafe { accounts.get_unchecked(..expected_accounts) }, + unsafe { instruction_data.get_unchecked(IX_HEADER_SIZE + 1..data_offset) }, + unsafe { *instruction_data.get_unchecked(IX_HEADER_SIZE) }, + )?; + + if data_offset == instruction_data.len() { + // The batch is complete. + break; + } + + accounts = &accounts[expected_accounts..]; + instruction_data = &instruction_data[data_offset..]; + } + + Ok(()) +} diff --git a/program/src/processor/get_account_data_size.rs b/program/src/processor/get_account_data_size.rs index 7693b64..0131b48 100644 --- a/program/src/processor/get_account_data_size.rs +++ b/program/src/processor/get_account_data_size.rs @@ -10,7 +10,7 @@ use super::check_account_owner; #[inline(always)] pub fn process_get_account_data_size(accounts: &[AccountInfo]) -> ProgramResult { - let [mint_info, _remaning @ ..] = accounts else { + let [mint_info, _remaining @ ..] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; diff --git a/program/src/processor/mod.rs b/program/src/processor/mod.rs index 0f0592f..f2efa64 100644 --- a/program/src/processor/mod.rs +++ b/program/src/processor/mod.rs @@ -21,6 +21,7 @@ use token_interface::{ pub mod amount_to_ui_amount; pub mod approve; pub mod approve_checked; +pub mod batch; pub mod burn; pub mod burn_checked; pub mod close_account; @@ -49,6 +50,7 @@ pub mod shared; pub use amount_to_ui_amount::process_amount_to_ui_amount; pub use approve::process_approve; pub use approve_checked::process_approve_checked; +pub use batch::process_batch; pub use burn::process_burn; pub use burn_checked::process_burn_checked; pub use close_account::process_close_account; diff --git a/program/src/processor/revoke.rs b/program/src/processor/revoke.rs index 6361d99..50b01dd 100644 --- a/program/src/processor/revoke.rs +++ b/program/src/processor/revoke.rs @@ -8,7 +8,7 @@ use super::validate_owner; #[inline(always)] pub fn process_revoke(accounts: &[AccountInfo], _instruction_data: &[u8]) -> ProgramResult { - let [source_account_info, owner_info, remaning @ ..] = accounts else { + let [source_account_info, owner_info, remaining @ ..] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; @@ -21,7 +21,7 @@ pub fn process_revoke(accounts: &[AccountInfo], _instruction_data: &[u8]) -> Pro return Err(TokenError::AccountFrozen.into()); } - validate_owner(&source_account.owner, owner_info, remaning)?; + validate_owner(&source_account.owner, owner_info, remaining)?; source_account.clear_delegate(); source_account.set_delegated_amount(0); diff --git a/program/src/processor/set_authority.rs b/program/src/processor/set_authority.rs index 3ad4d12..fcfa9f1 100644 --- a/program/src/processor/set_authority.rs +++ b/program/src/processor/set_authority.rs @@ -22,7 +22,7 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) // Validates the accounts. - let [account_info, authority_info, remaning @ ..] = accounts else { + let [account_info, authority_info, remaining @ ..] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; @@ -37,7 +37,7 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) match authority_type { AuthorityType::AccountOwner => { - validate_owner(&account.owner, authority_info, remaning)?; + validate_owner(&account.owner, authority_info, remaining)?; if let Some(authority) = new_authority { account.owner = *authority; @@ -54,7 +54,7 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) } AuthorityType::CloseAccount => { let authority = account.close_authority().unwrap_or(&account.owner); - validate_owner(authority, authority_info, remaning)?; + validate_owner(authority, authority_info, remaining)?; if let Some(authority) = new_authority { account.set_close_authority(authority); @@ -77,7 +77,7 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) // mint_authority. let mint_authority = mint.mint_authority().ok_or(TokenError::FixedSupply)?; - validate_owner(mint_authority, authority_info, remaning)?; + validate_owner(mint_authority, authority_info, remaining)?; if let Some(authority) = new_authority { mint.set_mint_authority(authority); @@ -92,7 +92,7 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) .freeze_authority() .ok_or(TokenError::MintCannotFreeze)?; - validate_owner(freeze_authority, authority_info, remaning)?; + validate_owner(freeze_authority, authority_info, remaining)?; if let Some(authority) = new_authority { mint.set_freeze_authority(authority); diff --git a/program/src/processor/shared/approve.rs b/program/src/processor/shared/approve.rs index a9d2812..d5e90d5 100644 --- a/program/src/processor/shared/approve.rs +++ b/program/src/processor/shared/approve.rs @@ -17,7 +17,7 @@ pub fn process_approve( let (source_account_info, expected_mint_info, delegate_info, owner_info, remaining) = if let Some(expected_decimals) = expected_decimals { - let [source_account_info, expected_mint_info, delegate_info, owner_info, remaning @ ..] = + let [source_account_info, expected_mint_info, delegate_info, owner_info, remaining @ ..] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); @@ -28,10 +28,10 @@ pub fn process_approve( Some((expected_mint_info, expected_decimals)), delegate_info, owner_info, - remaning, + remaining, ) } else { - let [source_account_info, delegate_info, owner_info, remaning @ ..] = accounts else { + let [source_account_info, delegate_info, owner_info, remaining @ ..] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; ( @@ -39,7 +39,7 @@ pub fn process_approve( None, delegate_info, owner_info, - remaning, + remaining, ) }; diff --git a/program/src/processor/shared/initialize_account.rs b/program/src/processor/shared/initialize_account.rs index 952dd1f..ba418e5 100644 --- a/program/src/processor/shared/initialize_account.rs +++ b/program/src/processor/shared/initialize_account.rs @@ -24,16 +24,16 @@ pub fn process_initialize_account( ) -> ProgramResult { // Accounts expected depend on whether we have the `rent_sysvar` account or not. - let (new_account_info, mint_info, owner, remaning) = if let Some(owner) = owner { - let [new_account_info, mint_info, remaning @ ..] = accounts else { + let (new_account_info, mint_info, owner, remaining) = if let Some(owner) = owner { + let [new_account_info, mint_info, remaining @ ..] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; - (new_account_info, mint_info, owner, remaning) + (new_account_info, mint_info, owner, remaining) } else { - let [new_account_info, mint_info, owner_info, remaning @ ..] = accounts else { + let [new_account_info, mint_info, owner_info, remaining @ ..] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; - (new_account_info, mint_info, owner_info.key(), remaning) + (new_account_info, mint_info, owner_info.key(), remaining) }; // Check rent-exempt status of the token account. @@ -41,7 +41,9 @@ pub fn process_initialize_account( let new_account_info_data_len = new_account_info.data_len(); let minimum_balance = if rent_sysvar_account { - let rent_sysvar_info = remaning.first().ok_or(ProgramError::NotEnoughAccountKeys)?; + let rent_sysvar_info = remaining + .first() + .ok_or(ProgramError::NotEnoughAccountKeys)?; // SAFETY: single immutable borrow to `rent_sysvar_info`; account ID and length are // checked by `from_account_info_unchecked`. let rent = unsafe { Rent::from_account_info_unchecked(rent_sysvar_info)? }; @@ -76,7 +78,7 @@ pub fn process_initialize_account( }; } - account.state = AccountState::Initialized; + account.state = AccountState::Initialized as u8; account.mint = *mint_info.key(); account.owner = *owner; diff --git a/program/src/processor/shared/toggle_account_state.rs b/program/src/processor/shared/toggle_account_state.rs index c9ff11a..8231586 100644 --- a/program/src/processor/shared/toggle_account_state.rs +++ b/program/src/processor/shared/toggle_account_state.rs @@ -37,9 +37,9 @@ pub fn process_toggle_account_state(accounts: &[AccountInfo], freeze: bool) -> P }?; source_account.state = if freeze { - AccountState::Frozen + AccountState::Frozen as u8 } else { - AccountState::Initialized + AccountState::Initialized as u8 }; Ok(()) diff --git a/program/src/processor/shared/transfer.rs b/program/src/processor/shared/transfer.rs index aafd0a1..c71a5af 100644 --- a/program/src/processor/shared/transfer.rs +++ b/program/src/processor/shared/transfer.rs @@ -20,9 +20,9 @@ pub fn process_transfer( expected_mint_info, destination_account_info, authority_info, - remaning, + remaining, ) = if let Some(decimals) = expected_decimals { - let [source_account_info, mint_info, destination_account_info, authority_info, remaning @ ..] = + let [source_account_info, mint_info, destination_account_info, authority_info, remaining @ ..] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); @@ -32,10 +32,10 @@ pub fn process_transfer( Some((mint_info, decimals)), destination_account_info, authority_info, - remaning, + remaining, ) } else { - let [source_account_info, destination_account_info, authority_info, remaning @ ..] = + let [source_account_info, destination_account_info, authority_info, remaining @ ..] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); @@ -45,7 +45,7 @@ pub fn process_transfer( None, destination_account_info, authority_info, - remaning, + remaining, ) }; @@ -114,7 +114,7 @@ pub fn process_transfer( // Validates the authority (delegate or owner). if source_account.delegate() == Some(authority_info.key()) { - validate_owner(authority_info.key(), authority_info, remaning)?; + validate_owner(authority_info.key(), authority_info, remaining)?; let delegated_amount = source_account .delegated_amount() @@ -129,7 +129,7 @@ pub fn process_transfer( } } } else { - validate_owner(&source_account.owner, authority_info, remaning)?; + validate_owner(&source_account.owner, authority_info, remaining)?; } if self_transfer || amount == 0 { diff --git a/program/tests/batch.rs b/program/tests/batch.rs new file mode 100644 index 0000000..e94416c --- /dev/null +++ b/program/tests/batch.rs @@ -0,0 +1,225 @@ +#![cfg(feature = "test-sbf")] + +mod setup; + +use solana_program_test::{tokio, ProgramTest}; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + program_error::ProgramError, + program_pack::Pack, + pubkey::Pubkey, + signature::{Keypair, Signer}, + system_instruction, + transaction::Transaction, +}; + +fn batch_instruction(instructions: Vec) -> Result { + // Create a `Vec` of ordered `AccountMeta`s + let mut accounts: Vec = vec![]; + // Start with the batch discriminator + let mut data: Vec = vec![0xff]; + + for instruction in instructions { + // Error out on non-token IX. + if instruction.program_id.ne(&spl_token::ID) { + return Err(ProgramError::IncorrectProgramId); + } + + data.push(instruction.accounts.len() as u8); + data.push(instruction.data.len() as u8); + + data.extend_from_slice(&instruction.data); + accounts.extend_from_slice(&instruction.accounts); + } + + Ok(Instruction { + program_id: spl_token::ID, + data, + accounts, + }) +} + +#[test_case::test_case(spl_token::ID ; "spl-token")] +// #[test_case::test_case(TOKEN_PROGRAM_ID ; "p-token")] +#[tokio::test] +async fn batch(token_program: Pubkey) { + let mut context = ProgramTest::new("token_program", token_program, None) + .start_with_context() + .await; + + let rent = context.banks_client.get_rent().await.unwrap(); + + let mint_len = spl_token::state::Mint::LEN; + let mint_rent = rent.minimum_balance(mint_len); + + let account_len = spl_token::state::Account::LEN; + let account_rent = rent.minimum_balance(account_len); + + // Create a mint + let mint_a = Keypair::new(); + let mint_authority = Keypair::new(); + let create_mint_a = system_instruction::create_account( + &context.payer.pubkey(), + &mint_a.pubkey(), + mint_rent, + mint_len as u64, + &token_program, + ); + let initialize_mint_ix = spl_token::instruction::initialize_mint( + &token_program, + &mint_a.pubkey(), + &mint_authority.pubkey(), + None, + 6, + ) + .unwrap(); + + // Create a mint 2 with a freeze authority + let mint_b = Keypair::new(); + let freeze_authority = Pubkey::new_unique(); + let create_mint_b = system_instruction::create_account( + &context.payer.pubkey(), + &mint_b.pubkey(), + mint_rent, + mint_len as u64, + &token_program, + ); + let initialize_mint_with_freeze_authority_ix = spl_token::instruction::initialize_mint2( + &token_program, + &mint_b.pubkey(), + &mint_authority.pubkey(), + Some(&freeze_authority), + 6, + ) + .unwrap(); + + // Create 2 token accounts for mint A and 1 for mint B + let owner_a = Keypair::new(); + let owner_b = Keypair::new(); + let owner_a_ta_a = Keypair::new(); + let owner_b_ta_a = Keypair::new(); + + let create_owner_a_ta_a = system_instruction::create_account( + &context.payer.pubkey(), + &owner_a_ta_a.pubkey(), + account_rent, + account_len as u64, + &token_program, + ); + let create_owner_b_ta_a = system_instruction::create_account( + &context.payer.pubkey(), + &owner_b_ta_a.pubkey(), + account_rent, + account_len as u64, + &token_program, + ); + let intialize_owner_a_ta_a = spl_token::instruction::initialize_account3( + &token_program, + &owner_a_ta_a.pubkey(), + &mint_a.pubkey(), + &owner_a.pubkey(), + ) + .unwrap(); + let intialize_owner_b_ta_a = spl_token::instruction::initialize_account3( + &token_program, + &owner_b_ta_a.pubkey(), + &mint_a.pubkey(), + &owner_b.pubkey(), + ) + .unwrap(); + + // Mint Token A to Owner A + let mint_token_a_to_owner_a = spl_token::instruction::mint_to( + &token_program, + &mint_a.pubkey(), + &owner_a_ta_a.pubkey(), + &mint_authority.pubkey(), + &[], + 1_000_000, + ) + .unwrap(); + + // Transfer Token A from Owner A to Owner B + let transfer_token_a_to_owner_b = spl_token::instruction::transfer( + &token_program, + &owner_a_ta_a.pubkey(), + &owner_b_ta_a.pubkey(), + &owner_a.pubkey(), + &[], + 1_000_000, + ) + .unwrap(); + + // Close Token A + let close_owner_a_ta_a = spl_token::instruction::close_account( + &token_program, + &owner_a_ta_a.pubkey(), + &owner_a.pubkey(), + &owner_a.pubkey(), + &[], + ) + .unwrap(); + + let batch_ix = batch_instruction(vec![ + initialize_mint_ix, + initialize_mint_with_freeze_authority_ix, + intialize_owner_a_ta_a, + intialize_owner_b_ta_a, + mint_token_a_to_owner_a, + transfer_token_a_to_owner_b, + close_owner_a_ta_a, + ]) + .unwrap(); + + println!("{:?}", batch_ix); + + let tx = Transaction::new_signed_with_payer( + &[ + create_mint_a, + create_mint_b, + create_owner_a_ta_a, + create_owner_b_ta_a, + batch_ix, + ], + Some(&context.payer.pubkey()), + &vec![ + &context.payer, + &mint_a, + &mint_b, + &owner_a_ta_a, + &owner_b_ta_a, + &mint_authority, + &owner_a, + ], + context.last_blockhash, + ); + context.banks_client.process_transaction(tx).await.unwrap(); + + let mint_a_account = context + .banks_client + .get_account(mint_a.pubkey()) + .await + .unwrap(); + assert!(mint_a_account.is_some()); + let mint_a_account = spl_token::state::Mint::unpack(&mint_a_account.unwrap().data).unwrap(); + assert_eq!(mint_a_account.supply, 1000000); + + let mint_b_account = context + .banks_client + .get_account(mint_b.pubkey()) + .await + .unwrap(); + assert!(mint_b_account.is_some()); + let mint_b_account = spl_token::state::Mint::unpack(&mint_b_account.unwrap().data).unwrap(); + assert_eq!(mint_b_account.supply, 0); + + let owner_b_ta_a_account = context + .banks_client + .get_account(owner_b_ta_a.pubkey()) + .await + .unwrap(); + assert!(owner_b_ta_a_account.is_some()); + let owner_b_ta_a_account = + spl_token::state::Account::unpack(&owner_b_ta_a_account.unwrap().data).unwrap(); + assert_eq!(owner_b_ta_a_account.amount, 1000000); +} diff --git a/program/tests/setup/mod.rs b/program/tests/setup/mod.rs index e79b8ce..801eb56 100644 --- a/program/tests/setup/mod.rs +++ b/program/tests/setup/mod.rs @@ -5,4 +5,5 @@ pub mod account; #[allow(dead_code)] pub mod mint; +#[allow(dead_code)] pub const TOKEN_PROGRAM_ID: Pubkey = Pubkey::new_from_array(token_interface::program::ID);