diff --git a/p-token/src/entrypoint-runtime-verification.rs b/p-token/src/entrypoint-runtime-verification.rs index 36d6487c..ecec8b39 100644 --- a/p-token/src/entrypoint-runtime-verification.rs +++ b/p-token/src/entrypoint-runtime-verification.rs @@ -21,7 +21,10 @@ use { }, pinocchio_token_interface::{ error::TokenError, - state::{Initializable, Transmutable}, + state::{ + account::Account, load_mut_unchecked, load_unchecked, mint::Mint, multisig::Multisig, + Initializable, Transmutable, + }, }, }; @@ -67,6 +70,12 @@ pub fn process_instruction( result.inspect_err(log_error) } +// Include prelude (macros, cheatcodes, helpers) +include!("../../specs/prelude-p-token.rs"); + +// Include inner_test_validate_owner +include!("../../specs/shared/inner_test_validate_owner.rs"); + /// Process a "regular" instruction. /// /// The processor of the token program is divided into two parts to reduce the @@ -639,131 +648,6 @@ fn inner_process_remaining_instruction( } } -// Cheatcodes to inject AccountInfo assumptions -#[inline(never)] -fn cheatcode_is_account(_: &AccountInfo) {} -#[inline(never)] -fn cheatcode_is_mint(_: &AccountInfo) {} -#[inline(never)] -fn cheatcode_is_multisig(_: &AccountInfo) {} // TODO: implement multisig cheatcode -#[inline(never)] -fn cheatcode_is_rent(_: &AccountInfo) {} - -use { - pinocchio::sysvars::rent::Rent, - pinocchio_token_interface::state::{ - account::Account, load_mut_unchecked, load_unchecked, mint::Mint, multisig::Multisig, - }, -}; - -fn get_account(account_info: &AccountInfo) -> &Account { - unsafe { - let byte_ptr = account_info.borrow_data_unchecked(); - let acc_ref = load_unchecked::(byte_ptr).unwrap(); - acc_ref - } -} - -fn get_mint(account_info: &AccountInfo) -> &Mint { - unsafe { - let byte_ptr = account_info.borrow_data_unchecked(); - let acc_ref = load_unchecked::(byte_ptr).unwrap(); - acc_ref - } -} - -fn get_multisig(account_info: &AccountInfo) -> &Multisig { - unsafe { - let byte_ptr = account_info.borrow_data_unchecked(); - let multisig_ref = load_unchecked::(byte_ptr).unwrap(); - multisig_ref - } -} - -fn get_rent(account_info: &AccountInfo) -> &Rent { - unsafe { Rent::from_bytes_unchecked(account_info.borrow_data_unchecked()) } -} - -/// This function encapsulates the specification of validating the signature -/// requirements In particular, code from mod.rs::validate_owner is checked -#[inline(never)] -fn inner_test_validate_owner( - expected_owner: &Pubkey, - owner_account_info: &AccountInfo, - tx_signers: &[AccountInfo], - maybe_multisig_is_initialised: Option>, - result: Result<(), ProgramError>, -) -> Result<(), ProgramError> { - use pinocchio_token_interface::program::ID; - - // Validate Owner - // Line 102-104 of validate_owner function in mod.rs - if expected_owner != owner_account_info.key() { - assert_eq!(result, Err(ProgramError::Custom(4))); - return result; - } - // Line 106-108 - // We add the `maybe_multisig_is_initialised.is_some()` to not branch vacuously in the - // non-multisig cases - else if maybe_multisig_is_initialised.is_some() - && owner_account_info.data_len() == Multisig::LEN - && owner_account_info.is_owned_by(&ID) - { - // Guaranteed to succeed by `cheatcode_is_multisig` - let multisig_is_initialised = maybe_multisig_is_initialised.unwrap(); - - // Line 114 - if multisig_is_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !multisig_is_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else { - // Lines 116-117 - let multisig = get_multisig(owner_account_info); - - // Lines 119-129: Did all declared and allowed signers sign? - let unsigned_exists = tx_signers.iter().any(|potential_signer| { - multisig.signers.iter().any(|registered_key| { - registered_key == potential_signer.key() && !potential_signer.is_signer() - }) - }); - - if unsigned_exists { - assert_eq!(result, Err(ProgramError::MissingRequiredSignature)); - return result; - } - - // Lines 130-132: Were enough signatures received? - let signers_count = multisig - .signers - .iter() - .filter_map(|registered_key| { - tx_signers.iter().find(|potential_signer| { - potential_signer.key() == registered_key && potential_signer.is_signer() - }) - }) - .count(); - - // Line 130-132: Check if we have enough signers (singers_count < multisig.m) - if signers_count < multisig.m as usize { - assert_eq!(result, Err(ProgramError::MissingRequiredSignature)); - return result; - } - - return Ok(()); - } - } - // Line 133-135: Non-multisig case - check if owner_account_info.is_signer() - else if !owner_account_info.is_signer() { - assert_eq!(result, Err(ProgramError::MissingRequiredSignature)); - return result; - } - - Ok(()) -} - // wrapper to ensure the test below is in the SMIR JSON #[no_mangle] pub unsafe extern "C" fn use_tests(acc: &AccountInfo) { @@ -815,4107 +699,49 @@ fn test_ptoken_domain_data(acc: &AccountInfo, mint: &AccountInfo, rent: &Account assert!(prent.burn_percent > 100 || burnt <= rent_collected && distributed <= rent_collected); } -// Hack Tests For Stable MIR JSON --------------------------------------------- -/// accounts[0] // Mint Info -/// accounts[1] // Rent Sysvar Info -/// instruction_data[0] // Decimals -/// instruction_data[1..33] // Mint Authority Pubkey -/// instruction_data[33] // Freeze Authority Exists? 1 for freeze -/// instruction_data[34..66] // instruction_data[33] == 1 ==> Freeze Authority -/// Pubkey -#[inline(never)] -pub fn test_process_initialize_mint_freeze( - accounts: &[AccountInfo; 2], - instruction_data: &[u8; 66], -) -> ProgramResult { - cheatcode_is_mint(&accounts[0]); - cheatcode_is_rent(&accounts[1]); - - //-Initial State----------------------------------------------------------- - let minimum_balance = get_rent(&accounts[1]).minimum_balance(accounts[0].data_len()); // TODO float problem - let mint_is_initialised_prior = get_mint(&accounts[0]).is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = process_initialize_mint(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 34 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if instruction_data[33] != 0 && instruction_data[33] != 1 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if instruction_data[33] == 1 && instruction_data.len() < 66 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 2 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if accounts[1].key() != &pinocchio::sysvars::rent::RENT_ID { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if mint_is_initialised_prior.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_is_initialised_prior.unwrap() { - assert_eq!(result, Err(ProgramError::Custom(6))) - } else if accounts[0].lamports() < minimum_balance { - assert_eq!(result, Err(ProgramError::Custom(0))) - } else { - assert!(result.is_ok()); - - let mint_new = get_mint(&accounts[0]); - assert!(mint_new.is_initialized().unwrap()); - assert_eq!(mint_new.mint_authority().unwrap(), &instruction_data[1..33]); - assert_eq!(mint_new.decimals, instruction_data[0]); - - if instruction_data[33] == 1 { - assert_eq!( - mint_new.freeze_authority().unwrap(), - &instruction_data[34..66] - ); - } - } - - result -} - -/// accounts[0] // Mint Info -/// accounts[1] // Rent Sysvar Info -/// instruction_data[0] // Decimals -/// instruction_data[1..33] // Mint Authority Pubkey -/// instruction_data[33] // Freeze Authority Exists? 0 for no freeze -#[inline(never)] -pub fn test_process_initialize_mint_no_freeze( - accounts: &[AccountInfo; 2], - instruction_data: &[u8; 34], -) -> ProgramResult { - cheatcode_is_mint(&accounts[0]); - cheatcode_is_rent(&accounts[1]); - - //-Initial State----------------------------------------------------------- - let minimum_balance = get_rent(&accounts[1]).minimum_balance(accounts[0].data_len()); // TODO float problem - let mint_is_initialised_prior = get_mint(&accounts[0]).is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = process_initialize_mint(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 34 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if instruction_data[33] != 0 && instruction_data[33] != 1 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if instruction_data[33] == 1 && instruction_data.len() < 66 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 2 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if accounts[1].key() != &pinocchio::sysvars::rent::RENT_ID { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if mint_is_initialised_prior.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_is_initialised_prior.unwrap() { - assert_eq!(result, Err(ProgramError::Custom(6))) - } else if accounts[0].lamports() < minimum_balance { - assert_eq!(result, Err(ProgramError::Custom(0))) - } else { - assert!(result.is_ok()); - - let mint_new = get_mint(&accounts[0]); - assert!(mint_new.is_initialized().unwrap()); - assert_eq!(mint_new.mint_authority().unwrap(), &instruction_data[1..33]); - assert_eq!(mint_new.decimals, instruction_data[0]); - - #[allow(clippy::out_of_bounds_indexing)] - // Guard above prevents this branch TODO: Perhaps remove? - if instruction_data[33] == 1 { - assert_eq!( - mint_new.freeze_authority().unwrap(), - &instruction_data[34..66] - ); - } - } - - result -} - -/// accounts[0] // New Account Info -/// accounts[1] // Mint Info -/// accounts[2] // Owner Info -/// accounts[3] // Rent Sysvar Info -#[inline(never)] -pub fn test_process_initialize_account(accounts: &[AccountInfo; 4]) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_account(&accounts[0]); - cheatcode_is_mint(&accounts[1]); - cheatcode_is_account(&accounts[2]); - cheatcode_is_rent(&accounts[3]); - - //-Initial State----------------------------------------------------------- - let initial_state_new_account = get_account(&accounts[0]).account_state(); - - let minimum_balance = get_rent(&accounts[3]).minimum_balance(accounts[0].data_len()); // TODO float problem - let is_native_mint = accounts[1].key() == &pinocchio_token_interface::native_mint::ID; - let mint_is_initialised = get_mint(&accounts[1]).is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = process_initialize_account(accounts); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() < 4 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - } else if accounts[3].key() != &pinocchio::sysvars::rent::RENT_ID { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if initial_state_new_account.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if initial_state_new_account.unwrap() != account_state::AccountState::Uninitialized { - assert_eq!(result, Err(ProgramError::Custom(6))) - } else if accounts[0].lamports() < minimum_balance { - assert_eq!(result, Err(ProgramError::Custom(0))) - } else if !is_native_mint && accounts[1].owner() != &pinocchio_token_interface::program::ID { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else if !is_native_mint - && accounts[1].owner() == &pinocchio_token_interface::program::ID - && mint_is_initialised.is_err() - { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !is_native_mint - && accounts[1].owner() == &pinocchio_token_interface::program::ID - && !mint_is_initialised.unwrap() - { - assert_eq!(result, Err(ProgramError::Custom(2))) - } else { - let new_account_new = get_account(&accounts[0]); - - assert!(result.is_ok()); - assert_eq!( - new_account_new.account_state().unwrap(), - account_state::AccountState::Initialized - ); - assert_eq!(new_account_new.mint, *accounts[1].key()); - assert_eq!(new_account_new.owner, *accounts[2].key()); - - if is_native_mint { - assert!(new_account_new.is_native()); - assert_eq!(new_account_new.native_amount().unwrap(), minimum_balance); - assert_eq!( - new_account_new.amount(), - accounts[0].lamports() - minimum_balance - ); - } - } - - result -} - -/// accounts[0] // Source Info -/// accounts[1] // Destination Info -/// accounts[2] // Authority Info -/// instruction_data[0..8] // Little Endian Bytes of u64 amount -#[inline(never)] -pub fn test_process_transfer( - accounts: &[AccountInfo; 3], - instruction_data: &[u8; 8], -) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_account(&accounts[0]); - cheatcode_is_account(&accounts[1]); - cheatcode_is_account(&accounts[2]); // Excluding the multisig case - - //-Initial State----------------------------------------------------------- - let src_old = get_account(&accounts[0]); - let amount = unsafe { u64::from_le_bytes(*(instruction_data.as_ptr() as *const [u8; 8])) }; - let src_initialised = src_old.is_initialized(); - let dst_initialised = get_account(&accounts[1]).is_initialized(); - let src_initial_amount = src_old.amount(); - let dst_initial_amount = get_account(&accounts[1]).amount(); - let src_initial_lamports = accounts[0].lamports(); - let dst_initial_lamports = accounts[1].lamports(); - let src_owner = src_old.owner; - let old_src_delgate = src_old.delegate().cloned(); - let old_src_delgated_amount = src_old.delegated_amount(); - let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account - - #[cfg(feature = "assumptions")] - // avoids potential overflow in destination account. assuming global supply bound by u64 - if accounts[0] != accounts[1] && dst_initial_amount.checked_add(amount).is_none() { - return Err(ProgramError::Custom(99)); - } - - //-Process Instruction----------------------------------------------------- - let result = process_transfer(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 8 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if accounts[0] != accounts[1] && dst_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if accounts[0] != accounts[1] && !dst_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if get_account(&accounts[0]).account_state().unwrap() - == account_state::AccountState::Frozen - { - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if accounts[0] != accounts[1] - && get_account(&accounts[1]).account_state().unwrap() == account_state::AccountState::Frozen - { - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if src_initial_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))); - return result; - } else if accounts[0] != accounts[1] - && get_account(&accounts[0]).mint != get_account(&accounts[1]).mint - { - assert_eq!(result, Err(ProgramError::Custom(3))); - return result; - } else { - let src_new = get_account(&accounts[0]); - if old_src_delgate == Some(*accounts[2].key()) { - // Validate Owner - inner_test_validate_owner( - &old_src_delgate.unwrap(), // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - if old_src_delgated_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))); - return result; - } - } else { - // Validate Owner - inner_test_validate_owner( - &src_owner, // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - } - - if (accounts[0] == accounts[1] || amount == 0) - && accounts[0].owner() != &pinocchio_token_interface::program::ID - { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if (accounts[0] == accounts[1] || amount == 0) - && accounts[1].owner() != &pinocchio_token_interface::program::ID - { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if accounts[0] != accounts[1] - && amount != 0 - && src_new.is_native() - && src_initial_lamports < amount - { - // Not sure how to fund native mint - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } else if accounts[0] != accounts[1] - && amount != 0 - && src_new.is_native() - && dst_initial_lamports.checked_add(amount).is_none() - { - // Not sure how to fund native mint - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } - - assert!(result.is_ok()); - - if accounts[0] != accounts[1] && amount != 0 { - assert_eq!(src_new.amount(), src_initial_amount - amount); - assert_eq!( - get_account(&accounts[1]).amount(), - dst_initial_amount + amount - ); - - if src_new.is_native() { - assert_eq!(accounts[0].lamports(), src_initial_lamports - amount); - assert_eq!(accounts[1].lamports(), dst_initial_lamports + amount); - } - } - - // Delegate updates - if old_src_delgate == Some(*accounts[2].key()) && accounts[0] != accounts[1] { - assert_eq!(src_new.delegated_amount(), old_src_delgated_amount - amount); - if old_src_delgated_amount - amount == 0 { - assert_eq!(src_new.delegate(), None); - } - } - } - - result -} - -/// accounts[0] // Source Info -/// accounts[1] // Destination Info -/// accounts[2] // Authority Info -/// accounts[3..14] // Signers -/// instruction_data[0..8] // Little Endian Bytes of u64 amount -#[inline(never)] -pub fn test_process_transfer_multisig( - accounts: &[AccountInfo; 4], - instruction_data: &[u8; 8], -) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_account(&accounts[0]); - cheatcode_is_account(&accounts[1]); - cheatcode_is_multisig(&accounts[2]); - - //-Initial State----------------------------------------------------------- - let src_old = get_account(&accounts[0]); - let amount = unsafe { u64::from_le_bytes(*(instruction_data.as_ptr() as *const [u8; 8])) }; - let src_initialised = src_old.is_initialized(); - let dst_initialised = get_account(&accounts[1]).is_initialized(); - let src_initial_amount = src_old.amount(); - let dst_initial_amount = get_account(&accounts[1]).amount(); - let src_initial_lamports = accounts[0].lamports(); - let dst_initial_lamports = accounts[1].lamports(); - let src_owner = src_old.owner; - let old_src_delgate = src_old.delegate().cloned(); - let old_src_delgated_amount = src_old.delegated_amount(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); - - //-Process Instruction----------------------------------------------------- - let result = process_transfer(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 8 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if accounts[0] != accounts[1] && dst_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if accounts[0] != accounts[1] && !dst_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if get_account(&accounts[0]).account_state().unwrap() - == account_state::AccountState::Frozen - { - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if accounts[0] != accounts[1] - && get_account(&accounts[1]).account_state().unwrap() == account_state::AccountState::Frozen - { - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if src_initial_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))); - return result; - } else if accounts[0] != accounts[1] - && get_account(&accounts[0]).mint != get_account(&accounts[1]).mint - { - assert_eq!(result, Err(ProgramError::Custom(3))); - return result; - } else { - let src_new = get_account(&accounts[0]); - if old_src_delgate == Some(*accounts[2].key()) { - // Validate Owner - inner_test_validate_owner( - &old_src_delgate.unwrap(), // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - if old_src_delgated_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))); - return result; - } - } else { - // Validate Owner - inner_test_validate_owner( - &src_owner, // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - } - - if (accounts[0] == accounts[1] || amount == 0) - && accounts[0].owner() != &pinocchio_token_interface::program::ID - { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if (accounts[0] == accounts[1] || amount == 0) - && accounts[1].owner() != &pinocchio_token_interface::program::ID - { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if accounts[0] != accounts[1] - && amount != 0 - && src_new.is_native() - && src_initial_lamports < amount - { - // Not sure how to fund native mint - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } else if accounts[0] != accounts[1] - && amount != 0 - && src_new.is_native() - && u64::MAX - amount < dst_initial_lamports - { - // Not sure how to fund native mint - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } else if accounts[0] != accounts[1] && amount != 0 { - assert_eq!(src_new.amount(), src_initial_amount - amount); - assert_eq!( - get_account(&accounts[1]).amount(), - dst_initial_amount + amount - ); - - if src_new.is_native() { - assert_eq!(accounts[0].lamports(), src_initial_lamports - amount); - assert_eq!(accounts[1].lamports(), dst_initial_lamports + amount); - } - } - - assert!(result.is_ok()); - - // Delegate updates - if old_src_delgate == Some(*accounts[2].key()) && accounts[0] != accounts[1] { - assert_eq!(src_new.delegated_amount(), old_src_delgated_amount - amount); - if old_src_delgated_amount - amount == 0 { - assert_eq!(src_new.delegate(), None); - } - } - } - - result -} - -/// accounts[0] // Mint Info -/// accounts[1] // Destination Info -/// accounts[2] // Owner Info -/// instruction_data[0..8] // Little Endian Bytes of u64 amount -#[inline(never)] -pub fn test_process_mint_to( - accounts: &[AccountInfo; 3], - instruction_data: &[u8; 8], -) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_mint(&accounts[0]); - cheatcode_is_account(&accounts[1]); - cheatcode_is_account(&accounts[2]); // Excluding the multisig case - - //-Initial State----------------------------------------------------------- - let mint_old = get_mint(&accounts[0]); - let dst_old = get_account(&accounts[1]); - let initial_supply = mint_old.supply(); - let initial_amount = dst_old.amount(); - let mint_initialised = mint_old.is_initialized(); - let dst_initialised = dst_old.is_initialized(); - let dst_init_state = dst_old.account_state(); - let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account - - #[cfg(feature = "assumptions")] - { - // Do not execute if adding to the account balance would overflow. - // shared::mint_to.rs,L68 is based on the assumption that initial_amount <= - // mint.supply and therefore cannot overflow because the minting itself - // would already error out. - let amount = unsafe { u64::from_le_bytes(*(instruction_data.as_ptr() as *const [u8; 8])) }; - if initial_amount.checked_add(amount).is_none() { - return Err(ProgramError::Custom(99)); - } - } - - //-Process Instruction----------------------------------------------------- - let result = process_mint_to(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 8 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if accounts[1].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if dst_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !dst_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if dst_init_state.unwrap() == account_state::AccountState::Frozen { - // unwrap must succeed due to dst_initialised not being err - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if get_account(&accounts[1]).is_native() { - assert_eq!(result, Err(ProgramError::Custom(10))); - return result; - } else if accounts[0].key() != &get_account(&accounts[1]).mint { - assert_eq!(result, Err(ProgramError::Custom(3))); - return result; - } else if accounts[0].data_len() != Mint::LEN { - // Not sure if this is even possible if we get past the case above - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else { - let mint_new = get_mint(&accounts[0]); - let mint_authority_new = mint_new.mint_authority(); - if mint_authority_new.is_some() { - // Validate Owner - inner_test_validate_owner( - mint_authority_new.unwrap(), // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - } else { - assert_eq!(result, Err(ProgramError::Custom(5))); - return result; - } - - let amount = unsafe { u64::from_le_bytes(*(instruction_data.as_ptr() as *const [u8; 8])) }; - - if amount == 0 && accounts[0].owner() != &pinocchio_token_interface::program::ID { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if amount == 0 && accounts[1].owner() != &pinocchio_token_interface::program::ID { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if amount != 0 && amount.checked_add(initial_supply).is_none() { - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } - - assert_eq!(mint_new.supply(), initial_supply + amount); - assert_eq!(get_account(&accounts[1]).amount(), initial_amount + amount); - assert!(result.is_ok()); - } - - result -} - -/// accounts[0] // Mint Info -/// accounts[1] // Destination Info -/// accounts[2] // Owner Info -/// accounts[3..14] // Signers -/// instruction_data[0..8] // Little Endian Bytes of u64 amount -#[inline(never)] -pub fn test_process_mint_to_multisig( - accounts: &[AccountInfo; 4], - instruction_data: &[u8; 8], -) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_mint(&accounts[0]); - cheatcode_is_account(&accounts[1]); - cheatcode_is_multisig(&accounts[2]); - - //-Initial State----------------------------------------------------------- - let mint_old = get_mint(&accounts[0]); - let dst_old = get_account(&accounts[1]); - let initial_supply = mint_old.supply(); - let initial_amount = dst_old.amount(); - let mint_initialised = mint_old.is_initialized(); - let dst_initialised = dst_old.is_initialized(); - let dst_init_state = dst_old.account_state(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); - - //-Process Instruction----------------------------------------------------- - let result = process_mint_to(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 8 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if accounts[1].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if dst_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !dst_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if dst_init_state.unwrap() == account_state::AccountState::Frozen { - // unwrap must succeed due to dst_initialised not being err - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if get_account(&accounts[1]).is_native() { - assert_eq!(result, Err(ProgramError::Custom(10))); - return result; - } else if accounts[0].key() != &get_account(&accounts[1]).mint { - assert_eq!(result, Err(ProgramError::Custom(3))); - return result; - } else if accounts[0].data_len() != Mint::LEN { - // Not sure if this is even possible if we get past the case above - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else { - let mint_new = get_mint(&accounts[0]); - let mint_authority_new = mint_new.mint_authority(); - if mint_authority_new.is_some() { - // Validate Owner - inner_test_validate_owner( - mint_authority_new.unwrap(), // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - } else { - assert_eq!(result, Err(ProgramError::Custom(5))); - return result; - } - - let amount = unsafe { u64::from_le_bytes(*(instruction_data.as_ptr() as *const [u8; 8])) }; - - if amount == 0 && accounts[0].owner() != &pinocchio_token_interface::program::ID { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if amount == 0 && accounts[1].owner() != &pinocchio_token_interface::program::ID { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if amount != 0 && u64::MAX - amount < initial_supply { - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } - - assert_eq!(mint_new.supply(), initial_supply + amount); - assert_eq!(get_account(&accounts[1]).amount(), initial_amount + amount); - assert!(result.is_ok()); - } - - result -} - -/// accounts[0] // Source Info -/// accounts[1] // Mint Info -/// accounts[2] // Authority Info -/// instruction_data[0..8] // Little Endian Bytes of u64 amount -#[inline(never)] -pub fn test_process_burn(accounts: &[AccountInfo; 3], instruction_data: &[u8; 8]) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_account(&accounts[0]); - cheatcode_is_mint(&accounts[1]); - cheatcode_is_account(&accounts[2]); // Excluding the multisig case - - //-Initial State----------------------------------------------------------- - let src_old = get_account(&accounts[0]); - let mint_old = get_mint(&accounts[1]); - let amount = unsafe { u64::from_le_bytes(*(instruction_data.as_ptr() as *const [u8; 8])) }; - let src_initialised = src_old.is_initialized(); - let src_init_amount = src_old.amount(); - let src_init_state = src_old.account_state(); - let src_is_native = src_old.is_native(); - let src_mint = src_old.mint; - let src_owned_sys_inc = src_old.is_owned_by_system_program_or_incinerator(); - let src_owner = src_old.owner; - let src_info_owner = accounts[0].owner(); - let old_src_delgate = src_old.delegate().cloned(); - let old_src_delgated_amount = src_old.delegated_amount(); - let mint_initialised = mint_old.is_initialized(); - let mint_init_supply = mint_old.supply(); - let mint_info_owner = *accounts[1].owner(); - let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account - - #[cfg(feature = "assumptions")] - // accoutn.amount() <= mint.supply(), account.delegated_amount() <= account.amount() - // otherwise processing could lead to overflows, see processor::shared::burn,L83 - if !(src_init_amount <= mint_init_supply && old_src_delgated_amount <= src_init_amount) { - return Err(ProgramError::Custom(99)); - } - - //-Process Instruction----------------------------------------------------- - let result = process_burn(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 8 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if accounts[1].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_init_state.unwrap() == account_state::AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(17))) - } else if src_is_native { - assert_eq!(result, Err(ProgramError::Custom(10))) - } else if src_init_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))) - } else if accounts[1].key() != &src_mint { - assert_eq!(result, Err(ProgramError::Custom(3))) - } else { - if !src_owned_sys_inc { - if old_src_delgate.is_some() && *accounts[2].key() == old_src_delgate.unwrap() { - // Validate Owner - inner_test_validate_owner( - &old_src_delgate.unwrap(), // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - if old_src_delgated_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))); - return result; - } - } else { - // Validate Owner - inner_test_validate_owner( - &src_owner, // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - } - } - - if amount == 0 && *src_info_owner != pinocchio_token_interface::program::ID { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else if amount == 0 && mint_info_owner != pinocchio_token_interface::program::ID { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else { - let src_new = get_account(&accounts[0]); - assert!(src_new.amount() == src_init_amount - amount); - assert!(get_mint(&accounts[1]).supply() == mint_init_supply - amount); - assert!(result.is_ok()); - - // Delegate updates - let new_src_delegate = src_new.delegate().cloned(); - let new_src_delegated_amount = src_new.delegated_amount(); - if !src_owned_sys_inc - && old_src_delgate.is_some() - && *accounts[2].key() == old_src_delgate.unwrap() - { - assert_eq!(new_src_delegated_amount, old_src_delgated_amount - amount); - if old_src_delgated_amount - amount == 0 { - assert_eq!(new_src_delegate, None); - } - } else { - assert_eq!(old_src_delgate, new_src_delegate); - assert_eq!(old_src_delgated_amount, new_src_delegated_amount); - } - } - } - - result -} - -/// accounts[0] // Source Info -/// accounts[1] // Mint Info -/// accounts[2] // Authority Info -/// accounts[3..14] // Signers -/// instruction_data[0..8] // Little Endian Bytes of u64 amount -#[inline(never)] -pub fn test_process_burn_multisig( - accounts: &[AccountInfo; 4], - instruction_data: &[u8; 8], -) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_account(&accounts[0]); - cheatcode_is_mint(&accounts[1]); - cheatcode_is_multisig(&accounts[2]); - - //-Initial State----------------------------------------------------------- - let src_old = get_account(&accounts[0]); - let mint_old = get_mint(&accounts[1]); - let amount = unsafe { u64::from_le_bytes(*(instruction_data.as_ptr() as *const [u8; 8])) }; - let src_initialised = src_old.is_initialized(); - let src_init_amount = src_old.amount(); - let src_init_state = src_old.account_state(); - let src_is_native = src_old.is_native(); - let src_mint = src_old.mint; - let src_owned_sys_inc = src_old.is_owned_by_system_program_or_incinerator(); - let src_owner = src_old.owner; - let old_src_delgate = src_old.delegate().cloned(); - let old_src_delgated_amount = src_old.delegated_amount(); - let mint_initialised = mint_old.is_initialized(); - let mint_init_supply = mint_old.supply(); - let mint_owner = *accounts[1].owner(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); - - //-Process Instruction----------------------------------------------------- - let result = process_burn(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 8 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if accounts[1].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_init_state.unwrap() == account_state::AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(17))) - } else if src_is_native { - assert_eq!(result, Err(ProgramError::Custom(10))) - } else if src_init_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))) - } else if accounts[1].key() != &src_mint { - assert_eq!(result, Err(ProgramError::Custom(3))) - } else { - if !src_owned_sys_inc { - if old_src_delgate.is_some() && *accounts[2].key() == old_src_delgate.unwrap() { - // Validate Owner - inner_test_validate_owner( - &old_src_delgate.unwrap(), // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - if old_src_delgated_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))); - return result; - } - } else { - // Validate Owner - inner_test_validate_owner( - &src_owner, // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - } - } - - if amount == 0 && src_owner != pinocchio_token_interface::program::ID { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else if amount == 0 && mint_owner != pinocchio_token_interface::program::ID { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else { - let src_new = get_account(&accounts[0]); - assert!(src_new.amount() == src_init_amount - amount); - assert!(get_mint(&accounts[1]).supply() == mint_init_supply - amount); - assert!(result.is_ok()); - - // Delegate updates - if old_src_delgate.is_some() && *accounts[2].key() == old_src_delgate.unwrap() { - assert_eq!(src_new.delegated_amount(), old_src_delgated_amount - amount); - if old_src_delgated_amount - amount == 0 { - assert_eq!(src_new.delegate(), None); - } - } - } - } - - result -} - -/// accounts[0] // Source Info -/// accounts[1] // Destination Info -/// accounts[2] // Authority Info -#[inline(never)] -pub fn test_process_close_account(accounts: &[AccountInfo; 3]) -> ProgramResult { - use pinocchio_token_interface::state::account::INCINERATOR_ID; - - cheatcode_is_account(&accounts[0]); - cheatcode_is_account(&accounts[1]); - cheatcode_is_account(&accounts[2]); // Excluding the multisig case - - //-Initial State----------------------------------------------------------- - let src_old = get_account(&accounts[0]); - let src_initialised = src_old.is_initialized(); - let src_data_len = accounts[0].data_len(); - let src_init_amount = src_old.amount(); - let src_init_lamports = accounts[0].lamports(); - let dst_init_lamports = accounts[1].lamports(); - let src_is_native = src_old.is_native(); - let src_owned_sys_inc = src_old.is_owned_by_system_program_or_incinerator(); - let authority = src_old.close_authority().cloned().unwrap_or(src_old.owner); - let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account - - //-Process Instruction----------------------------------------------------- - let result = process_close_account(accounts); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if accounts[0] == accounts[1] { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if src_data_len != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if !src_is_native && src_init_amount != 0 { - assert_eq!(result, Err(ProgramError::Custom(11))); - return result; - } else { - if !src_owned_sys_inc { - // Validate Owner - inner_test_validate_owner( - &authority, // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - } else if accounts[1].key() != &INCINERATOR_ID { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } - if dst_init_lamports.checked_add(src_init_lamports).is_none() { - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } - assert!(result.is_ok()); - - // Validate owner falls through to here if no error - assert_eq!( - accounts[1].lamports(), - dst_init_lamports + src_init_lamports - ); - #[cfg(any(target_os = "solana", target_arch = "bpf"))] - { - // Solana-RT only syscall - assert_eq!(*accounts[0].owner(), [0; 32]); - assert_eq!(accounts[0].lamports(), 0); - assert_eq!(accounts[0].data_len(), 0); - } - } - result -} - -/// accounts[0] // Source Info -/// accounts[1] // Destination Info -/// accounts[2] // Authority Info -/// accounts[3..14] // Multisig Signers -#[inline(never)] -pub fn test_process_close_account_multisig(accounts: &[AccountInfo; 4]) -> ProgramResult { - use pinocchio_token_interface::state::account::INCINERATOR_ID; - - cheatcode_is_account(&accounts[0]); - cheatcode_is_account(&accounts[1]); - cheatcode_is_multisig(&accounts[2]); - - //-Initial State----------------------------------------------------------- - let src_old = get_account(&accounts[0]); - let src_initialised = src_old.is_initialized(); - let src_data_len = accounts[0].data_len(); - let src_init_amount = src_old.amount(); - let src_init_lamports = accounts[0].lamports(); - let dst_init_lamports = accounts[1].lamports(); - let src_is_native = src_old.is_native(); - let src_owned_sys_inc = src_old.is_owned_by_system_program_or_incinerator(); - let authority = src_old.close_authority().cloned().unwrap_or(src_old.owner); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); - - //-Process Instruction----------------------------------------------------- - let result = process_close_account(accounts); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if accounts[0] == accounts[1] { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if src_data_len != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if !src_is_native && src_init_amount != 0 { - assert_eq!(result, Err(ProgramError::Custom(11))); - return result; - } else { - if !src_owned_sys_inc { - // Validate Owner - inner_test_validate_owner( - &authority, // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - } else if accounts[1].key() != &INCINERATOR_ID { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if u64::MAX - src_init_lamports < dst_init_lamports { - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } - - // Validate owner falls through to here if no error - assert_eq!(accounts[0].lamports(), 0); - assert_eq!( - accounts[1].lamports(), - dst_init_lamports + src_init_lamports - ); - assert_eq!(accounts[0].data_len(), 0); // TODO: More sol_memset stuff? - assert!(result.is_ok()); - } - result -} - -/// accounts[0] // Source Info -/// accounts[1] // Mint Info -/// accounts[2] // Destination Info -/// accounts[3] // Authority Info -/// instruction_data[0..9] // Little Endian Bytes of u64 amount, and decimals -#[inline(never)] -pub fn test_process_transfer_checked( - accounts: &[AccountInfo; 4], - instruction_data: &[u8; 9], -) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_account(&accounts[0]); - cheatcode_is_mint(&accounts[1]); - cheatcode_is_account(&accounts[2]); - cheatcode_is_account(&accounts[3]); // Excluding the multisig case - - //-Initial State----------------------------------------------------------- - let src_old = get_account(&accounts[0]); - let dst_old = get_account(&accounts[2]); - let amount = unsafe { u64::from_le_bytes(*(instruction_data.as_ptr() as *const [u8; 8])) }; - let src_initialised = src_old.is_initialized(); - let dst_initialised = dst_old.is_initialized(); - let src_initial_amount = src_old.amount(); - let dst_initial_amount = dst_old.amount(); - let src_initial_lamports = accounts[0].lamports(); - let dst_initial_lamports = accounts[2].lamports(); - let src_owner = src_old.owner; - let old_src_delgate = src_old.delegate().cloned(); - let old_src_delgated_amount = src_old.delegated_amount(); - let mint_initialised = get_mint(&accounts[1]).is_initialized(); - let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account - - #[cfg(feature = "assumptions")] - // avoids potential overflow in destination account. assuming global supply bound by u64 - if accounts[0] != accounts[2] && dst_initial_amount.checked_add(amount).is_none() { - return Err(ProgramError::Custom(99)); - } - - //-Process Instruction----------------------------------------------------- - let result = process_transfer_checked(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 9 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if accounts.len() < 4 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if accounts[0] != accounts[2] && dst_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if accounts[0] != accounts[2] && !dst_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if get_account(&accounts[0]).account_state().unwrap() - == account_state::AccountState::Frozen - { - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if accounts[0] != accounts[2] - && get_account(&accounts[2]).account_state().unwrap() == account_state::AccountState::Frozen - { - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if src_initial_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))); - return result; - } else if accounts[0] != accounts[2] - && get_account(&accounts[0]).mint != get_account(&accounts[2]).mint - { - assert_eq!(result, Err(ProgramError::Custom(3))); - return result; - } else if accounts[1].key() != &get_account(&accounts[0]).mint { - assert_eq!(result, Err(ProgramError::Custom(3))); - return result; - } else if accounts[1].data_len() != core::mem::size_of::() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if instruction_data[8] != get_mint(&accounts[1]).decimals { - assert_eq!(result, Err(ProgramError::Custom(18))); - return result; - } else { - if old_src_delgate == Some(*accounts[3].key()) { - // Because of the above if, there is a duplicated check in the following - // function Validate Owner - inner_test_validate_owner( - &old_src_delgate.unwrap(), // expected_owner - &accounts[3], // owner_account_info - &accounts[4..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - if old_src_delgated_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))); - return result; - } - } else { - // Validate Owner - inner_test_validate_owner( - &src_owner, // expected_owner - &accounts[3], // owner_account_info - &accounts[4..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - } - - let src_new = get_account(&accounts[0]); - - if (accounts[0] == accounts[2] || amount == 0) - && accounts[0].owner() != &pinocchio_token_interface::program::ID - { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if (accounts[0] == accounts[2] || amount == 0) - && accounts[2].owner() != &pinocchio_token_interface::program::ID - { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } - - if accounts[0] != accounts[2] && amount != 0 { - if src_new.is_native() && src_initial_lamports < amount { - // Not sure how to fund native mint - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } else if src_new.is_native() && dst_initial_lamports.checked_add(amount).is_none() { - // Not sure how to fund native mint - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } - - assert_eq!(src_new.amount(), src_initial_amount - amount); - assert_eq!( - get_account(&accounts[2]).amount(), - dst_initial_amount + amount - ); - - if src_new.is_native() { - assert_eq!(accounts[0].lamports(), src_initial_lamports - amount); - assert_eq!(accounts[2].lamports(), dst_initial_lamports + amount); - } - } - assert!(result.is_ok()); - - // Delegate updates - if old_src_delgate == Some(*accounts[3].key()) && accounts[0] != accounts[2] { - assert_eq!(src_new.delegated_amount(), old_src_delgated_amount - amount); - if old_src_delgated_amount - amount == 0 { - assert_eq!(src_new.delegate(), None); - } - } - } - - result -} - -/// accounts[0] // Source Info -/// accounts[1] // Mint Info -/// accounts[2] // Destination Info -/// accounts[3] // Authority Info -/// accounts[4..15] // Signers -/// instruction_data[0..9] // Little Endian Bytes of u64 amount, and decimals -#[inline(never)] -pub fn test_process_transfer_checked_multisig( - accounts: &[AccountInfo; 5], - instruction_data: &[u8; 9], -) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_account(&accounts[0]); - cheatcode_is_mint(&accounts[1]); - cheatcode_is_account(&accounts[2]); - cheatcode_is_multisig(&accounts[3]); - - //-Initial State----------------------------------------------------------- - let src_old = get_account(&accounts[0]); - let dst_old = get_account(&accounts[2]); - let amount = unsafe { u64::from_le_bytes(*(instruction_data.as_ptr() as *const [u8; 8])) }; - let src_initialised = src_old.is_initialized(); - let dst_initialised = dst_old.is_initialized(); - let src_initial_amount = src_old.amount(); - let dst_initial_amount = dst_old.amount(); - let src_initial_lamports = accounts[0].lamports(); - let dst_initial_lamports = accounts[2].lamports(); - let src_owner = src_old.owner; - let old_src_delgate = src_old.delegate().cloned(); - let old_src_delgated_amount = src_old.delegated_amount(); - let mint_initialised = get_mint(&accounts[1]).is_initialized(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[3]).is_initialized()); - - //-Process Instruction----------------------------------------------------- - let result = process_transfer_checked(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 9 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if accounts.len() < 4 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if accounts[0] != accounts[2] && dst_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if accounts[0] != accounts[2] && !dst_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if get_account(&accounts[0]).account_state().unwrap() - == account_state::AccountState::Frozen - { - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if accounts[0] != accounts[2] - && get_account(&accounts[2]).account_state().unwrap() == account_state::AccountState::Frozen - { - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if src_initial_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))); - return result; - } else if accounts[0] != accounts[2] - && get_account(&accounts[0]).mint != get_account(&accounts[2]).mint - { - assert_eq!(result, Err(ProgramError::Custom(3))); - return result; - } else if accounts[1].key() != &get_account(&accounts[0]).mint { - assert_eq!(result, Err(ProgramError::Custom(3))); - return result; - } else if accounts[1].data_len() != core::mem::size_of::() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if instruction_data[8] != get_mint(&accounts[1]).decimals { - assert_eq!(result, Err(ProgramError::Custom(18))); - return result; - } else { - if old_src_delgate == Some(*accounts[3].key()) { - // Because of the above if, there is a duplicated check in the following - // function Validate Owner - inner_test_validate_owner( - &old_src_delgate.unwrap(), // expected_owner - &accounts[3], // owner_account_info - &accounts[4..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - if old_src_delgated_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))); - return result; - } - } else { - // Validate Owner - inner_test_validate_owner( - &src_owner, // expected_owner - &accounts[3], // owner_account_info - &accounts[4..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - } - - let src_new = get_account(&accounts[0]); - - if (accounts[0] == accounts[2] || amount == 0) - && accounts[0].owner() != &pinocchio_token_interface::program::ID - { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if (accounts[0] == accounts[2] || amount == 0) - && accounts[2].owner() != &pinocchio_token_interface::program::ID - { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if accounts[0] != accounts[2] && amount != 0 { - if src_new.is_native() && src_initial_lamports < amount { - // Not sure how to fund native mint - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } else if src_new.is_native() && u64::MAX - amount < dst_initial_lamports { - // Not sure how to fund native mint - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } - - assert_eq!(src_new.amount(), src_initial_amount - amount); - assert_eq!( - get_account(&accounts[2]).amount(), - dst_initial_amount + amount - ); - - if src_new.is_native() { - assert_eq!(accounts[0].lamports(), src_initial_lamports - amount); - assert_eq!(accounts[1].lamports(), dst_initial_lamports + amount); - } - } - - assert!(result.is_ok()); - // Delegate updates - if old_src_delgate == Some(*accounts[3].key()) && accounts[0] != accounts[2] { - assert_eq!(src_new.delegated_amount(), old_src_delgated_amount - amount); - if old_src_delgated_amount - amount == 0 { - assert_eq!(src_new.delegate(), None); - } - } - } - - result -} - -/// accounts[0] // Source Info -/// accounts[1] // Mint Info -/// accounts[2] // Authority Info -/// instruction_data[0..9] // Little Endian Bytes of u64 amount, and decimals -#[inline(never)] -pub fn test_process_burn_checked( - accounts: &[AccountInfo; 3], - instruction_data: &[u8; 9], -) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_account(&accounts[0]); - cheatcode_is_mint(&accounts[1]); - cheatcode_is_account(&accounts[2]); // Excluding the multisig case - - //-Initial State----------------------------------------------------------- - let src_old = get_account(&accounts[0]); - let mint_old = get_mint(&accounts[1]); - let amount = unsafe { u64::from_le_bytes(*(instruction_data.as_ptr() as *const [u8; 8])) }; - let src_initialised = src_old.is_initialized(); - let src_init_amount = src_old.amount(); - let src_init_state = src_old.account_state(); - let src_is_native = src_old.is_native(); - let src_mint = src_old.mint; - let src_owned_sys_inc = src_old.is_owned_by_system_program_or_incinerator(); - let src_owner = src_old.owner; - let src_info_owner = accounts[0].owner(); - let old_src_delgate = src_old.delegate().cloned(); - let old_src_delgated_amount = src_old.delegated_amount(); - let mint_initialised = mint_old.is_initialized(); - let mint_init_supply = mint_old.supply(); - let mint_decimals = mint_old.decimals; - let mint_info_owner = *accounts[1].owner(); - let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account - - #[cfg(feature = "assumptions")] - // accoutn.amount() <= mint.supply(), account.delegated_amount() <= account.amount() - // otherwise processing could lead to overflows, see processor::shared::burn,L83 - if !(src_init_amount <= mint_init_supply && old_src_delgated_amount <= src_init_amount) { - return Err(ProgramError::Custom(99)); - } - - //-Process Instruction----------------------------------------------------- - let result = process_burn_checked(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 9 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if accounts[1].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_init_state.unwrap() == account_state::AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(17))) - } else if src_is_native { - assert_eq!(result, Err(ProgramError::Custom(10))) - } else if src_init_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))) - } else if accounts[1].key() != &src_mint { - assert_eq!(result, Err(ProgramError::Custom(3))) - } else if instruction_data[8] != mint_decimals { - assert_eq!(result, Err(ProgramError::Custom(18))) - } else { - if !src_owned_sys_inc { - if old_src_delgate.is_some() && *accounts[2].key() == old_src_delgate.unwrap() { - // Validate Owner - inner_test_validate_owner( - &old_src_delgate.unwrap(), // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - if old_src_delgated_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))); - return result; - } - } else { - // Validate Owner - inner_test_validate_owner( - &src_owner, // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - } - } - - if amount == 0 && *src_info_owner != pinocchio_token_interface::program::ID { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else if amount == 0 && mint_info_owner != pinocchio_token_interface::program::ID { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else { - let src_new = get_account(&accounts[0]); - assert!(src_new.amount() == src_init_amount - amount); - assert!(get_mint(&accounts[1]).supply() == mint_init_supply - amount); - assert!(result.is_ok()); - - // Delegate updates - let new_src_delegate = src_new.delegate().cloned(); - let new_src_delegated_amount = src_new.delegated_amount(); - if !src_owned_sys_inc - && old_src_delgate.is_some() - && *accounts[2].key() == old_src_delgate.unwrap() - { - assert_eq!(new_src_delegated_amount, old_src_delgated_amount - amount); - if old_src_delgated_amount - amount == 0 { - assert_eq!(new_src_delegate, None); - } - } else { - assert_eq!(old_src_delgate, new_src_delegate); - assert_eq!(old_src_delgated_amount, new_src_delegated_amount); - } - } - } - - result -} - -/// accounts[0] // Source Info -/// accounts[1] // Mint Info -/// accounts[2] // Authority Info -/// accounts[3..14] // Signers -/// instruction_data[0..9] // Little Endian Bytes of u64 amount, and decimals -#[inline(never)] -pub fn test_process_burn_checked_multisig( - accounts: &[AccountInfo; 4], - instruction_data: &[u8; 9], -) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_account(&accounts[0]); - cheatcode_is_mint(&accounts[1]); - cheatcode_is_multisig(&accounts[2]); - - //-Initial State----------------------------------------------------------- - let src_old = get_account(&accounts[0]); - let mint_old = get_mint(&accounts[1]); - let amount = unsafe { u64::from_le_bytes(*(instruction_data.as_ptr() as *const [u8; 8])) }; - let src_initialised = src_old.is_initialized(); - let src_init_amount = src_old.amount(); - let src_init_state = src_old.account_state(); - let src_is_native = src_old.is_native(); - let src_mint = src_old.mint; - let src_owned_sys_inc = src_old.is_owned_by_system_program_or_incinerator(); - let src_owner = src_old.owner; - let old_src_delgate = src_old.delegate().cloned(); - let old_src_delgated_amount = src_old.delegated_amount(); - let mint_initialised = mint_old.is_initialized(); - let mint_init_supply = mint_old.supply(); - let mint_decimals = mint_old.decimals; - let mint_owner = *accounts[1].owner(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); - - //-Process Instruction----------------------------------------------------- - let result = process_burn_checked(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 9 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if accounts[1].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_init_state.unwrap() == account_state::AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(17))) - } else if src_is_native { - assert_eq!(result, Err(ProgramError::Custom(10))) - } else if src_init_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))) - } else if accounts[1].key() != &src_mint { - assert_eq!(result, Err(ProgramError::Custom(3))) - } else if instruction_data[8] != mint_decimals { - assert_eq!(result, Err(ProgramError::Custom(18))) - } else { - if !src_owned_sys_inc { - if old_src_delgate.is_some() && *accounts[2].key() == old_src_delgate.unwrap() { - // Validate Owner - inner_test_validate_owner( - &old_src_delgate.unwrap(), // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - if old_src_delgated_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))); - return result; - } - } else { - // Validate Owner - inner_test_validate_owner( - &src_owner, // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - } - } - - if amount == 0 && src_owner != pinocchio_token_interface::program::ID { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else if amount == 0 && mint_owner != pinocchio_token_interface::program::ID { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else { - let src_new = get_account(&accounts[0]); - assert!(src_new.amount() == src_init_amount - amount); - assert!(get_mint(&accounts[1]).supply() == mint_init_supply - amount); - assert!(result.is_ok()); - - // Delegate updates - if old_src_delgate.is_some() && *accounts[2].key() == old_src_delgate.unwrap() { - assert_eq!(src_new.delegated_amount(), old_src_delgated_amount - amount); - if old_src_delgated_amount - amount == 0 { - assert_eq!(src_new.delegate(), None); - } - } - } - } - - result -} - -/// accounts[0] // New Account Info -/// accounts[1] // Mint Info -/// accounts[2] // Rent Sysvar Info -/// instruction_data[..] // Owner -#[inline(never)] -pub fn test_process_initialize_account2( - accounts: &[AccountInfo; 3], - instruction_data: &[u8; 32], -) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_account(&accounts[0]); - cheatcode_is_mint(&accounts[1]); - cheatcode_is_rent(&accounts[2]); - - //-Initial State----------------------------------------------------------- - let initial_state_new_account = get_account(&accounts[0]).account_state(); - - let minimum_balance = get_rent(&accounts[2]).minimum_balance(accounts[0].data_len()); - - let is_native_mint = accounts[1].key() == &pinocchio_token_interface::native_mint::ID; - - let mint_is_initialised = get_mint(&accounts[1]).is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = process_initialize_account2(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < pinocchio::pubkey::PUBKEY_BYTES { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - } else if accounts[2].key() != &pinocchio::sysvars::rent::RENT_ID { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if initial_state_new_account.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if initial_state_new_account.unwrap() != account_state::AccountState::Uninitialized { - assert_eq!(result, Err(ProgramError::Custom(6))) - } else if accounts[0].lamports() < minimum_balance { - assert_eq!(result, Err(ProgramError::Custom(0))) - } else if !is_native_mint && accounts[1].owner() != &pinocchio_token_interface::program::ID { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else if !is_native_mint - && accounts[1].owner() == &pinocchio_token_interface::program::ID - && mint_is_initialised.is_err() - { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !is_native_mint - && accounts[1].owner() == &pinocchio_token_interface::program::ID - && !mint_is_initialised.unwrap() - { - assert_eq!(result, Err(ProgramError::Custom(2))) - } else { - let new_account_new = get_account(&accounts[0]); - assert!(result.is_ok()); - assert_eq!( - new_account_new.account_state().unwrap(), - account_state::AccountState::Initialized - ); - assert_eq!(new_account_new.mint, *accounts[1].key()); - assert_eq!(new_account_new.owner, *instruction_data); - - if is_native_mint { - assert!(new_account_new.is_native()); - assert_eq!(new_account_new.native_amount().unwrap(), minimum_balance); - assert_eq!( - new_account_new.amount(), - accounts[0].lamports() - minimum_balance - ); - } - } - - result -} - -/// accounts[0] // New Account Info -/// accounts[1] // Mint Info -/// instruction_data[..] // Owner -#[inline(never)] -pub fn test_process_initialize_account3( - accounts: &[AccountInfo; 2], - instruction_data: &[u8; 32], -) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_account(&accounts[0]); - cheatcode_is_mint(&accounts[1]); - - //-Initial State----------------------------------------------------------- - let initial_state_new_account = get_account(&accounts[0]).account_state(); - - // Note: Rent is a supported sysvar so ProgramError::UnsupportedSysvar should be - // impossible - let rent = pinocchio::sysvars::rent::Rent::get().unwrap(); - let minimum_balance = rent.minimum_balance(accounts[0].data_len()); - - let is_native_mint = accounts[1].key() == &pinocchio_token_interface::native_mint::ID; - - let mint_is_initialised = get_mint(&accounts[1]).is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = process_initialize_account3(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < pinocchio::pubkey::PUBKEY_BYTES { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 2 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if initial_state_new_account.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if initial_state_new_account.unwrap() != account_state::AccountState::Uninitialized { - assert_eq!(result, Err(ProgramError::Custom(6))) - } else if accounts[0].lamports() < minimum_balance { - assert_eq!(result, Err(ProgramError::Custom(0))) - } else if !is_native_mint && accounts[1].owner() != &pinocchio_token_interface::program::ID { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else if !is_native_mint - && accounts[1].owner() == &pinocchio_token_interface::program::ID - && mint_is_initialised.is_err() - { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !is_native_mint - && accounts[1].owner() == &pinocchio_token_interface::program::ID - && !mint_is_initialised.unwrap() - { - assert_eq!(result, Err(ProgramError::Custom(2))) - } else { - let new_account_new = get_account(&accounts[0]); - assert!(result.is_ok()); - assert_eq!( - new_account_new.account_state().unwrap(), - account_state::AccountState::Initialized - ); - assert_eq!(new_account_new.mint, *accounts[1].key()); - assert_eq!(new_account_new.owner, *instruction_data); - - if is_native_mint { - assert!(new_account_new.is_native()); - assert_eq!(new_account_new.native_amount().unwrap(), minimum_balance); - assert_eq!( - new_account_new.amount(), - accounts[0].lamports() - minimum_balance - ); - } - } - - result -} - -/// accounts[0] // Mint Info -/// instruction_data[0] // Decimals -/// instruction_data[1..33] // Mint Authority Pubkey -/// instruction_data[33] // Freeze Authority Exists? 1 for freeze -/// instruction_data[34..66] // instruction_data[33] == 1 ==> Freeze Authority -/// Pubkey -#[inline(never)] -pub fn test_process_initialize_mint2_freeze( - accounts: &[AccountInfo; 1], - instruction_data: &[u8; 66], -) -> ProgramResult { - cheatcode_is_mint(&accounts[0]); - - //-Initial State----------------------------------------------------------- - // Note: Rent is a supported sysvar so ProgramError::UnsupportedSysvar should be - // impossible - let rent = pinocchio::sysvars::rent::Rent::get().unwrap(); - let minimum_balance = rent.minimum_balance(accounts[0].data_len()); - let mint_is_initialised_prior = get_mint(&accounts[0]).is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = process_initialize_mint2(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 34 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if instruction_data[33] != 0 && instruction_data[33] != 1 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if instruction_data[33] == 1 && instruction_data.len() < 66 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.is_empty() { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_is_initialised_prior.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_is_initialised_prior.unwrap() { - assert_eq!(result, Err(ProgramError::Custom(6))) - } else if accounts[0].lamports() < minimum_balance { - assert_eq!(result, Err(ProgramError::Custom(0))) - } else { - assert!(result.is_ok()); - - let mint_new = get_mint(&accounts[0]); - assert!(mint_new.is_initialized().unwrap()); - assert_eq!(mint_new.mint_authority().unwrap(), &instruction_data[1..33]); - assert_eq!(mint_new.decimals, instruction_data[0]); - - if instruction_data[33] == 1 { - assert_eq!( - mint_new.freeze_authority().unwrap(), - &instruction_data[34..66] - ); - } - } - - result -} - -/// accounts[0] // Mint Info -/// instruction_data[0] // Decimals -/// instruction_data[1..33] // Mint Authority Pubkey -/// instruction_data[33] // Freeze Authority Exists? 0 for no freeze -#[inline(never)] -pub fn test_process_initialize_mint2_no_freeze( - accounts: &[AccountInfo; 1], - instruction_data: &[u8; 34], -) -> ProgramResult { - cheatcode_is_mint(&accounts[0]); - - //-Initial State----------------------------------------------------------- - // Note: Rent is a supported sysvar so ProgramError::UnsupportedSysvar should be - // impossible - let rent = pinocchio::sysvars::rent::Rent::get().unwrap(); - let minimum_balance = rent.minimum_balance(accounts[0].data_len()); - let mint_is_initialised_prior = get_mint(&accounts[0]).is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = process_initialize_mint2(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 34 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if instruction_data[33] != 0 && instruction_data[33] != 1 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if instruction_data[33] == 1 && instruction_data.len() < 66 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.is_empty() { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_is_initialised_prior.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_is_initialised_prior.unwrap() { - assert_eq!(result, Err(ProgramError::Custom(6))) - } else if accounts[0].lamports() < minimum_balance { - assert_eq!(result, Err(ProgramError::Custom(0))) - } else { - assert!(result.is_ok()); - - let mint_new = get_mint(&accounts[0]); - assert!(mint_new.is_initialized().unwrap()); - assert_eq!(mint_new.mint_authority().unwrap(), &instruction_data[1..33]); - assert_eq!(mint_new.decimals, instruction_data[0]); - - #[allow(clippy::out_of_bounds_indexing)] - // Guard above prevents this branch TODO: Perhaps remove? - if instruction_data[33] == 1 { - assert_eq!( - mint_new.freeze_authority().unwrap(), - &instruction_data[34..66] - ); - } - } - - result -} - -/// accounts[0] // Multisig Info -/// accounts[1] // Rent Sysvar Info -/// accounts[2..] // Signers -/// accounts[2..].len() // n -/// instruction_data[1] // m -#[inline(never)] -fn test_process_initialize_multisig( - accounts: &[AccountInfo; 5], - instruction_data: &[u8; 1], -) -> ProgramResult { - cheatcode_is_multisig(&accounts[0]); - cheatcode_is_rent(&accounts[1]); - cheatcode_is_account(&accounts[2]); // Signer - cheatcode_is_account(&accounts[3]); // Signer - cheatcode_is_account(&accounts[4]); // Signer - - //-Initial State----------------------------------------------------------- - let multisig_already_initialised = get_multisig(&accounts[0]).is_initialized(); - let multisig_init_lamports = accounts[0].lamports(); - let minimum_balance = get_rent(&accounts[1]).minimum_balance(accounts[0].data_len()); - - //-Process Instruction----------------------------------------------------- - let result = process_initialize_multisig(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.is_empty() { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 2 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[1].key() != &pinocchio::sysvars::rent::RENT_ID { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if accounts[0].data_len() != Multisig::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if multisig_already_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if multisig_already_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::Custom(6))) - } else if multisig_init_lamports < minimum_balance { - assert_eq!(result, Err(ProgramError::Custom(0))) - } else if !Multisig::is_valid_signer_index((accounts.len() - 2) as u8) { - assert_eq!(result, Err(ProgramError::Custom(7))) - } else if !Multisig::is_valid_signer_index(instruction_data[0]) { - assert_eq!(result, Err(ProgramError::Custom(8))) - } else { - let multisig_new = get_multisig(&accounts[0]); - assert!(accounts[2..] - .iter() - .map(|signer| *signer.key()) - .eq(multisig_new - .signers - .iter() - .take(accounts[2..].len()) - .copied())); - assert_eq!(multisig_new.m, instruction_data[0]); - assert_eq!(multisig_new.n as usize, accounts.len() - 2); - assert!(multisig_new.is_initialized().is_ok()); - assert!(multisig_new.is_initialized().unwrap()); - assert!(result.is_ok()) - } - - result -} - -/// accounts[0] // Source Account Info -/// accounts[1] // Delegate Info -/// accounts[2] // Owner Info -/// instruction_data[0..8] // Little Endian Bytes of u64 amount -#[inline(never)] -fn test_process_approve(accounts: &[AccountInfo; 3], instruction_data: &[u8; 8]) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_account(&accounts[0]); // Source Account - cheatcode_is_account(&accounts[1]); // Delegate - cheatcode_is_account(&accounts[2]); // Excluding the multisig case - - //-Initial State----------------------------------------------------------- - let src_old = get_account(&accounts[0]); - let amount = unsafe { u64::from_le_bytes(*(instruction_data.as_ptr() as *const [u8; 8])) }; - let src_owner = src_old.owner; - let src_initialised = src_old.is_initialized(); - let src_init_state = src_old.account_state(); - let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account - - //-Process Instruction----------------------------------------------------- - let result = process_approve(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 8 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_init_state.unwrap() == account_state::AccountState::Frozen { - // This should be safe to unwrap due to above check passing - assert_eq!(result, Err(ProgramError::Custom(17))) - } else { - // Validate Owner - inner_test_validate_owner( - &src_owner, // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - let src_new = get_account(&accounts[0]); - assert_eq!(src_new.delegate().unwrap(), accounts[1].key()); - assert_eq!(src_new.delegated_amount(), amount); - assert!(result.is_ok()) - } - - result -} - -/// accounts[0] // Source Account Info -/// accounts[1] // Delegate Info -/// accounts[2] // Owner Info -/// accounts[3..14] // Signers -/// instruction_data[0..8] // Little Endian Bytes of u64 amount -#[inline(never)] -fn test_process_approve_multisig( - accounts: &[AccountInfo; 4], - instruction_data: &[u8; 8], -) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_account(&accounts[0]); // Source Account - cheatcode_is_account(&accounts[1]); // Delegate - cheatcode_is_multisig(&accounts[2]); // Owner - - //-Initial State----------------------------------------------------------- - let src_old = get_account(&accounts[0]); - let amount = unsafe { u64::from_le_bytes(*(instruction_data.as_ptr() as *const [u8; 8])) }; - let src_owner = src_old.owner; - let src_initialised = src_old.is_initialized(); - let src_init_state = src_old.account_state(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); - - //-Process Instruction----------------------------------------------------- - let result = process_approve(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 8 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_init_state.unwrap() == account_state::AccountState::Frozen { - // This should be safe to unwrap due to above check passing - assert_eq!(result, Err(ProgramError::Custom(17))) - } else { - // Validate Owner - inner_test_validate_owner( - &src_owner, // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - let src_new = get_account(&accounts[0]); - assert_eq!(src_new.delegate().unwrap(), accounts[1].key()); - assert_eq!(src_new.delegated_amount(), amount); - assert!(result.is_ok()) - } - - result -} - -/// accounts[0] // Source Account Info -/// accounts[1] // Owner Info -/// accounts[2..13] // Signers -#[inline(never)] -fn test_process_revoke(accounts: &[AccountInfo; 2]) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_account(&accounts[0]); // Source Account - cheatcode_is_account(&accounts[1]); // Owner - - //-Initial State----------------------------------------------------------- - let src_old = get_account(&accounts[0]); - let src_initialised = src_old.is_initialized(); - let src_init_state = src_old.account_state(); - let src_owner = src_old.owner; - let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account - - //-Process Instruction----------------------------------------------------- - let result = process_revoke(accounts); - - //-Assert Postconditions--------------------------------------------------- - if accounts.is_empty() { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if accounts.len() < 2 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if src_init_state.unwrap() == account_state::AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(17))) - } else { - // Validate Owner - inner_test_validate_owner( - &src_owner, // expected_owner - &accounts[1], // owner_account_info - &accounts[2..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - let src_new = get_account(&accounts[0]); - assert!(src_new.delegate().is_none()); - assert_eq!(src_new.delegated_amount(), 0); - assert!(result.is_ok()) - } - - result -} - -/// accounts[0] // Source Account Info -/// accounts[1] // Owner Info -/// accounts[2..13] // Signers -#[inline(never)] -fn test_process_revoke_multisig(accounts: &[AccountInfo; 3]) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_account(&accounts[0]); // Source Account - cheatcode_is_multisig(&accounts[1]); // Owner - - //-Initial State----------------------------------------------------------- - let src_old = get_account(&accounts[0]); - let src_initialised = src_old.is_initialized(); - let src_init_state = src_old.account_state(); - let src_owner = src_old.owner; - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[1]).is_initialized()); - - //-Process Instruction----------------------------------------------------- - let result = process_revoke(accounts); - - //-Assert Postconditions--------------------------------------------------- - if accounts.is_empty() { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if accounts.len() < 2 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if src_init_state.unwrap() == account_state::AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(17))) - } else { - // Validate Owner - inner_test_validate_owner( - &src_owner, // expected_owner - &accounts[1], // owner_account_info - &accounts[2..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - let src_new = get_account(&accounts[0]); - assert!(src_new.delegate().is_none()); - assert_eq!(src_new.delegated_amount(), 0); - assert!(result.is_ok()) - } - - result -} - -/// accounts[0] // Account Info - Account Case -/// accounts[1] // Authority Info -/// instruction_data[0] // Authority Type (instruction) -/// instruction_data[1] // New Authority Follows (0 -> No, 1 -> Yes) -/// instruction_data[2..34] // New Authority Pubkey -#[inline(never)] -fn test_process_set_authority_account( - accounts: &[AccountInfo; 2], - instruction_data: &[u8; 34], -) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_account(&accounts[0]); // Assume Account - cheatcode_is_account(&accounts[1]); // Excluding the multisig case - - //-Initial State----------------------------------------------------------- - let src_old = get_account(&accounts[0]); - let src_initialised = src_old.is_initialized(); - let src_init_state = src_old.account_state(); - let src_owner = src_old.owner; - let authority = src_old.close_authority().cloned().unwrap_or(src_old.owner); - let account_data_len = accounts[0].data_len(); - let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account - - //-Process Instruction----------------------------------------------------- - let result = process_set_authority(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 2 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if !(0..=3).contains(&instruction_data[0]) { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if instruction_data[1] != 0 && instruction_data[1] != 1 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if instruction_data[1] == 1 && instruction_data.len() < 34 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if accounts.len() < 2 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if account_data_len != Account::LEN && account_data_len != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidArgument)); - return result; - } else if account_data_len == Account::LEN { - // established by cheatcode_is_account - - let src_new = get_account(&accounts[0]); - - if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if src_init_state.unwrap() == account_state::AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if instruction_data[0] != 2 && instruction_data[0] != 3 { - // AuthorityType neither AccountOwner nor CloseAccount - assert_eq!(result, Err(ProgramError::Custom(15))); - return result; - } else if instruction_data[0] == 2 { - // AccountOwner - // Validate Owner - inner_test_validate_owner( - &src_owner, // expected_owner - &accounts[1], // owner_account_info - &accounts[2..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - if instruction_data[1] != 1 || instruction_data.len() < 34 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } - - assert_eq!(src_new.owner, instruction_data[2..34]); - assert_eq!(src_new.delegate(), None); - assert_eq!(src_new.delegated_amount(), 0); - if src_new.is_native() { - assert_eq!(src_new.close_authority(), None); - } - assert!(result.is_ok()) - } else { - // CloseAccount - assert_eq!(instruction_data[0], 3); // If not AccountOwner (2), must be CloseAccount (3) - - // Validate Owner - inner_test_validate_owner( - &authority, // expected_owner - &accounts[1], // owner_account_info - &accounts[2..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - if instruction_data[1] == 1 { - // 1 ==> 34 <= instruction_data.len() - assert_eq!(src_new.close_authority().unwrap(), &instruction_data[2..34]); - } else { - assert_eq!(src_new.close_authority(), None); - } - assert!(result.is_ok()) - } - } else { - unreachable!() // account_data_len == Account::LEN must hold - } - - result -} - -/// accounts[0] // Account Info - Account Case -/// accounts[1] // Authority Info -/// accounts[2..13] // Signers -/// instruction_data[0] // Authority Type (instruction) -/// instruction_data[1] // New Authority Follows (0 -> No, 1 -> Yes) -/// instruction_data[2..34] // New Authority Pubkey -#[inline(never)] -fn test_process_set_authority_account_multisig( - accounts: &[AccountInfo; 3], - instruction_data: &[u8; 34], -) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_account(&accounts[0]); // Assume Account - cheatcode_is_multisig(&accounts[1]); // Authority - - //-Initial State----------------------------------------------------------- - let src_old = get_account(&accounts[0]); - let src_initialised = src_old.is_initialized(); - let src_init_state = src_old.account_state(); - let src_owner = src_old.owner; - let authority = src_old.close_authority().cloned().unwrap_or(src_old.owner); - let account_data_len = accounts[0].data_len(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[1]).is_initialized()); - - //-Process Instruction----------------------------------------------------- - let result = process_set_authority(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 2 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if !(0..=3).contains(&instruction_data[0]) { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if instruction_data[1] != 0 && instruction_data[1] != 1 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if instruction_data[1] == 1 && instruction_data.len() < 34 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if accounts.len() < 2 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if account_data_len != Account::LEN && account_data_len != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidArgument)); - return result; - } else if account_data_len == Account::LEN { - // established by cheatcode_is_account - - let src_new = get_account(&accounts[0]); - - if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if src_init_state.unwrap() == account_state::AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if instruction_data[0] != 2 && instruction_data[0] != 3 { - // AuthorityType neither AccountOwner nor CloseAccount - assert_eq!(result, Err(ProgramError::Custom(15))); - return result; - } else if instruction_data[0] == 2 { - // AccountOwner - // Validate Owner - inner_test_validate_owner( - &src_owner, // expected_owner - &accounts[1], // owner_account_info - &accounts[2..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - if instruction_data[1] != 1 || instruction_data.len() < 34 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } - - assert_eq!(src_new.owner, instruction_data[2..34]); - assert_eq!(src_new.delegate(), None); - assert_eq!(src_new.delegated_amount(), 0); - if src_new.is_native() { - assert_eq!(src_new.close_authority(), None); - } - assert!(result.is_ok()) - } else { - // CloseAccount - assert_eq!(instruction_data[0], 3); // If not AccountOwner (2), must be CloseAccount (3) - - // Validate Owner - inner_test_validate_owner( - &authority, // expected_owner - &accounts[1], // owner_account_info - &accounts[2..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - if instruction_data[1] == 1 { - // 1 ==> 34 <= instruction_data.len() - assert_eq!(src_new.close_authority().unwrap(), &instruction_data[2..34]); - } else { - assert_eq!(src_new.close_authority(), None); - } - assert!(result.is_ok()) - } - } else { - unreachable!() // account_data_len == Account::LEN must hold - } - - result -} - -/// accounts[0] // Account Info - Mint Case -/// accounts[1] // Authority Info -/// instruction_data[0] // Authority Type (instruction) -/// instruction_data[1] // New Authority Follows (0 -> No, 1 -> Yes) -/// instruction_data[2..34] // New Authority Pubkey -#[inline(never)] -fn test_process_set_authority_mint( - accounts: &[AccountInfo; 2], - instruction_data: &[u8; 34], -) -> ProgramResult { - cheatcode_is_mint(&accounts[0]); // Assume Mint - cheatcode_is_account(&accounts[1]); // Authority - - //-Initial State----------------------------------------------------------- - let mint_old = get_mint(&accounts[0]); - let mint_data_len = accounts[0].data_len(); - let old_mint_authority_is_none = mint_old.mint_authority().is_none(); - let old_freeze_authority_is_none = mint_old.freeze_authority().is_none(); - let old_mint_authority = mint_old.mint_authority().cloned(); - let old_freeze_authority = mint_old.freeze_authority().cloned(); - let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account - let mint_is_initialised = mint_old.is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = process_set_authority(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 2 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if !(0..=3).contains(&instruction_data[0]) { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if instruction_data[1] != 0 && instruction_data[1] != 1 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if instruction_data[1] == 1 && instruction_data.len() < 34 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if accounts.len() < 2 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if mint_data_len != Account::LEN && mint_data_len != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidArgument)); - return result; - } else if mint_data_len == Mint::LEN { - // established by cheatcode_is_mint - - let mint_new = get_mint(&accounts[0]); - - if !mint_is_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if instruction_data[0] != 0 && instruction_data[0] != 1 { - // AuthorityType neither MintTokens nor FreezeAccount - assert_eq!(result, Err(ProgramError::Custom(15))); - return result; - } else if instruction_data[0] == 0 { - // MintTokens - if old_mint_authority_is_none { - assert_eq!(result, Err(ProgramError::Custom(5))); - return result; - } - - // Validate Owner - inner_test_validate_owner( - &old_mint_authority.unwrap(), // expected_owner - &accounts[1], // owner_account_info - &accounts[2..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - if instruction_data[1] == 1 { - // 1 ==> 34 <= instruction_data.len() - assert_eq!(mint_new.mint_authority().unwrap(), &instruction_data[2..34]); - } else { - assert_eq!(mint_new.mint_authority(), None); - } - assert!(result.is_ok()) - } else { - // FreezeAccount - assert_eq!(instruction_data[0], 1); // If not MintTokens (0), must be FreezeAccount (1) - if old_freeze_authority_is_none { - assert_eq!(result, Err(ProgramError::Custom(16))); - return result; - } - - // Validate Owner - inner_test_validate_owner( - &old_freeze_authority.unwrap(), // expected_owner - &accounts[1], // owner_account_info - &accounts[2..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - if instruction_data[1] == 1 { - // 1 ==> 34 <= instruction_data.len() - assert_eq!( - mint_new.freeze_authority().unwrap(), - &instruction_data[2..34] - ); - } else { - assert_eq!(mint_new.freeze_authority(), None); - } - assert!(result.is_ok()) - } - } else { - unreachable!(); // mint_data_len == Mint::LEN must hold - } - - result -} - -/// accounts[0] // Account Info - Mint Case -/// accounts[1] // Authority Info -/// accounts[2..13] // Signers -/// instruction_data[0] // Authority Type (instruction) -/// instruction_data[1] // New Authority Follows (0 -> No, 1 -> Yes) -/// instruction_data[2..34] // New Authority Pubkey -#[inline(never)] -fn test_process_set_authority_mint_multisig( - accounts: &[AccountInfo; 3], - instruction_data: &[u8; 34], -) -> ProgramResult { - cheatcode_is_mint(&accounts[0]); // Assume Mint - cheatcode_is_multisig(&accounts[1]); // Authority - - //-Initial State----------------------------------------------------------- - let mint_old = get_mint(&accounts[0]); - let mint_data_len = accounts[0].data_len(); - let old_mint_authority_is_none = mint_old.mint_authority().is_none(); - let old_freeze_authority_is_none = mint_old.freeze_authority().is_none(); - let old_mint_authority = mint_old.mint_authority().cloned(); - let old_freeze_authority = mint_old.freeze_authority().cloned(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[1]).is_initialized()); - let mint_is_initialised = mint_old.is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = process_set_authority(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 2 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if !(0..=3).contains(&instruction_data[0]) { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if instruction_data[1] != 0 && instruction_data[1] != 1 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if instruction_data[1] == 1 && instruction_data.len() < 34 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if accounts.len() < 2 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if mint_data_len != Account::LEN && mint_data_len != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidArgument)); - return result; - } else if mint_data_len == Mint::LEN { - // established by cheatcode_is_mint - - let mint_new = get_mint(&accounts[0]); - - if !mint_is_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if instruction_data[0] != 0 && instruction_data[0] != 1 { - // AuthorityType neither MintTokens nor FreezeAccount - assert_eq!(result, Err(ProgramError::Custom(15))); - return result; - } else if instruction_data[0] == 0 { - // MintTokens - if old_mint_authority_is_none { - assert_eq!(result, Err(ProgramError::Custom(5))); - return result; - } - - // Validate Owner - inner_test_validate_owner( - &old_mint_authority.unwrap(), // expected_owner - &accounts[1], // owner_account_info - &accounts[2..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - if instruction_data[1] == 1 { - // 1 ==> 34 <= instruction_data.len() - assert_eq!(mint_new.mint_authority().unwrap(), &instruction_data[2..34]); - } else { - assert_eq!(mint_new.mint_authority(), None); - } - assert!(result.is_ok()) - } else { - // FreezeAccount - assert_eq!(instruction_data[0], 1); // If not MintTokens (0), must be FreezeAccount (1) - if old_freeze_authority_is_none { - assert_eq!(result, Err(ProgramError::Custom(16))); - return result; - } - - // Validate Owner - inner_test_validate_owner( - &old_freeze_authority.unwrap(), // expected_owner - &accounts[1], // owner_account_info - &accounts[2..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - if instruction_data[1] == 1 { - // 1 ==> 34 <= instruction_data.len() - assert_eq!( - mint_new.freeze_authority().unwrap(), - &instruction_data[2..34] - ); - } else { - assert_eq!(mint_new.freeze_authority(), None); - } - assert!(result.is_ok()) - } - } else { - unreachable!(); // mint_data_len == Mint::LEN must hold - } - - result -} - -/// accounts[0] // Source Account Info -/// accounts[1] // Mint Info -/// accounts[2] // Authority Info -#[inline(never)] -fn test_process_freeze_account(accounts: &[AccountInfo; 3]) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_account(&accounts[0]); - cheatcode_is_mint(&accounts[1]); - cheatcode_is_account(&accounts[2]); // Excluding the multisig case - - //-Initial State----------------------------------------------------------- - let src_old = get_account(&accounts[0]); - let mint_old = get_mint(&accounts[1]); - let src_initialised = src_old.is_initialized(); - let src_init_state = src_old.account_state(); - let src_is_native = src_old.is_native(); - let src_mint = src_old.mint; - let mint_initialised = mint_old.is_initialized(); - let mint_freeze_auth = mint_old.freeze_authority().cloned(); - let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account - - //-Process Instruction----------------------------------------------------- - let result = process_freeze_account(accounts); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_init_state.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_init_state.unwrap() == account_state::AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(13))) - } else if src_is_native { - assert_eq!(result, Err(ProgramError::Custom(10))) - } else if accounts[1].key() != &src_mint { - assert_eq!(result, Err(ProgramError::Custom(3))) - } else if accounts[1].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if mint_freeze_auth.is_none() { - assert_eq!(result, Err(ProgramError::Custom(16))) - } else { - // Validate Owner - inner_test_validate_owner( - &mint_freeze_auth.unwrap(), // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - assert_eq!( - get_account(&accounts[0]).account_state().unwrap(), - account_state::AccountState::Frozen - ); - assert!(result.is_ok()) - } - result -} - -/// accounts[0] // Source Account Info -/// accounts[1] // Mint Info -/// accounts[2] // Authority Info -/// accounts[3..13] // Signers -#[inline(never)] -fn test_process_freeze_account_multisig(accounts: &[AccountInfo; 4]) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_account(&accounts[0]); - cheatcode_is_mint(&accounts[1]); - cheatcode_is_multisig(&accounts[2]); - - //-Initial State----------------------------------------------------------- - let src_old = get_account(&accounts[0]); - let mint_old = get_mint(&accounts[1]); - let src_initialised = src_old.is_initialized(); - let src_init_state = src_old.account_state(); - let src_is_native = src_old.is_native(); - let src_mint = src_old.mint; - let mint_initialised = mint_old.is_initialized(); - let mint_freeze_auth = mint_old.freeze_authority().cloned(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); - - //-Process Instruction----------------------------------------------------- - let result = process_freeze_account(accounts); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_init_state.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_init_state.unwrap() == account_state::AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(13))) - } else if src_is_native { - assert_eq!(result, Err(ProgramError::Custom(10))) - } else if accounts[1].key() != &src_mint { - assert_eq!(result, Err(ProgramError::Custom(3))) - } else if accounts[1].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if mint_freeze_auth.is_none() { - assert_eq!(result, Err(ProgramError::Custom(16))) - } else { - // Validate Owner - inner_test_validate_owner( - &mint_freeze_auth.unwrap(), // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - assert_eq!( - get_account(&accounts[0]).account_state().unwrap(), - account_state::AccountState::Frozen - ); - assert!(result.is_ok()) - } - result -} - -/// accounts[0] // Source Account Info -/// accounts[1] // Mint Info -/// accounts[2] // Authority Info -/// accounts[3..13] // Signers -#[inline(never)] -fn test_process_thaw_account(accounts: &[AccountInfo; 3]) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_account(&accounts[0]); - cheatcode_is_mint(&accounts[1]); - cheatcode_is_account(&accounts[2]); // Excluding the multisig case - - //-Initial State----------------------------------------------------------- - let src_old = get_account(&accounts[0]); - let mint_old = get_mint(&accounts[1]); - let src_initialised = src_old.is_initialized(); - let src_init_state = src_old.account_state(); - let src_is_native = src_old.is_native(); - let src_mint = src_old.mint; - let mint_initialised = mint_old.is_initialized(); - let mint_freeze_auth = mint_old.freeze_authority().cloned(); - let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account - - //-Process Instruction----------------------------------------------------- - let result = process_thaw_account(accounts); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_init_state.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_init_state.unwrap() != account_state::AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(13))) - } else if src_is_native { - assert_eq!(result, Err(ProgramError::Custom(10))) - } else if accounts[1].key() != &src_mint { - assert_eq!(result, Err(ProgramError::Custom(3))) - } else if accounts[1].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if mint_freeze_auth.is_none() { - assert_eq!(result, Err(ProgramError::Custom(16))) - } else { - // Validate Owner - inner_test_validate_owner( - &mint_freeze_auth.unwrap(), // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - assert_eq!( - get_account(&accounts[0]).account_state().unwrap(), - account_state::AccountState::Initialized - ); - assert!(result.is_ok()) - } - result -} - -/// accounts[0] // Source Account Info -/// accounts[1] // Mint Info -/// accounts[2] // Authority Info -/// accounts[3..13] // Signers -#[inline(never)] -fn test_process_thaw_account_multisig(accounts: &[AccountInfo; 4]) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_account(&accounts[0]); - cheatcode_is_mint(&accounts[1]); - cheatcode_is_multisig(&accounts[2]); - - //-Initial State----------------------------------------------------------- - let src_old = get_account(&accounts[0]); - let mint_old = get_mint(&accounts[1]); - let src_initialised = src_old.is_initialized(); - let src_init_state = src_old.account_state(); - let src_is_native = src_old.is_native(); - let src_mint = src_old.mint; - let mint_initialised = mint_old.is_initialized(); - let mint_freeze_auth = mint_old.freeze_authority().cloned(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); - - //-Process Instruction----------------------------------------------------- - let result = process_thaw_account(accounts); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_init_state.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_init_state.unwrap() != account_state::AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(13))) - } else if src_is_native { - assert_eq!(result, Err(ProgramError::Custom(10))) - } else if accounts[1].key() != &src_mint { - assert_eq!(result, Err(ProgramError::Custom(3))) - } else if accounts[1].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if mint_freeze_auth.is_none() { - assert_eq!(result, Err(ProgramError::Custom(16))) - } else { - // Validate Owner - inner_test_validate_owner( - &mint_freeze_auth.unwrap(), // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - assert_eq!( - get_account(&accounts[0]).account_state().unwrap(), - account_state::AccountState::Initialized - ); - assert!(result.is_ok()) - } - result -} - -/// accounts[0] // Source Account Info -/// accounts[1] // Expected Mint Info -/// accounts[2] // Delegate Info -/// accounts[3] // Owner Info -/// instruction_data[0..9] // Little Endian Bytes of u64 amount, and decimals -#[inline(never)] -fn test_process_approve_checked( - accounts: &[AccountInfo; 4], - instruction_data: &[u8; 9], -) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_account(&accounts[0]); // Source Account - cheatcode_is_mint(&accounts[1]); // Expected Mint - cheatcode_is_account(&accounts[2]); // Delegate - cheatcode_is_account(&accounts[3]); // Excluding the multisig case - - //-Initial State----------------------------------------------------------- - let src_old = get_account(&accounts[0]); - let amount = unsafe { u64::from_le_bytes(*(instruction_data.as_ptr() as *const [u8; 8])) }; - let src_owner = src_old.owner; - let src_initialised = src_old.is_initialized(); - let src_init_state = src_old.account_state(); - let mint_initialised = get_mint(&accounts[1]).is_initialized(); - let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account - - //-Process Instruction----------------------------------------------------- - let result = process_approve_checked(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 9 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 4 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_init_state.unwrap() == account_state::AccountState::Frozen { - // This should be safe to unwrap due to above check passing - assert_eq!(result, Err(ProgramError::Custom(17))) - } else if accounts[1].key() != &get_account(&accounts[0]).mint { - assert_eq!(result, Err(ProgramError::Custom(3))) - } else if accounts[1].data_len() != Mint::LEN { - // Not sure if this is even possible if we get past the case above - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if instruction_data[8] != get_mint(&accounts[1]).decimals { - assert_eq!(result, Err(ProgramError::Custom(18))) - } else { - // Validate Owner - inner_test_validate_owner( - &src_owner, // expected_owner - &accounts[3], // owner_account_info - &accounts[4..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - let src_new = get_account(&accounts[0]); - assert_eq!(src_new.delegate().unwrap(), accounts[2].key()); - assert_eq!(src_new.delegated_amount(), amount); - assert!(result.is_ok()) - } - - result -} - -/// accounts[0] // Source Account Info -/// accounts[1] // Expected Mint Info -/// accounts[2] // Delegate Info -/// accounts[3] // Owner Info -/// accounts[4..15] // Signers -/// instruction_data[0..9] // Little Endian Bytes of u64 amount, and decimals -#[inline(never)] -fn test_process_approve_checked_multisig( - accounts: &[AccountInfo; 5], - instruction_data: &[u8; 9], -) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_account(&accounts[0]); // Source Account - cheatcode_is_mint(&accounts[1]); // Expected Mint - cheatcode_is_account(&accounts[2]); // Delegate - cheatcode_is_multisig(&accounts[3]); // Owner - - //-Initial State----------------------------------------------------------- - let src_old = get_account(&accounts[0]); - let amount = unsafe { u64::from_le_bytes(*(instruction_data.as_ptr() as *const [u8; 8])) }; - let src_owner = src_old.owner; - let src_initialised = src_old.is_initialized(); - let src_init_state = src_old.account_state(); - let mint_initialised = get_mint(&accounts[1]).is_initialized(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[3]).is_initialized()); - - //-Process Instruction----------------------------------------------------- - let result = process_approve_checked(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 9 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 4 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_init_state.unwrap() == account_state::AccountState::Frozen { - // This should be safe to unwrap due to above check passing - assert_eq!(result, Err(ProgramError::Custom(17))) - } else if accounts[1].key() != &get_account(&accounts[0]).mint { - assert_eq!(result, Err(ProgramError::Custom(3))) - } else if accounts[1].data_len() != Mint::LEN { - // Not sure if this is even possible if we get past the case above - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if instruction_data[8] != get_mint(&accounts[1]).decimals { - assert_eq!(result, Err(ProgramError::Custom(18))) - } else { - // Validate Owner - inner_test_validate_owner( - &src_owner, // expected_owner - &accounts[3], // owner_account_info - &accounts[4..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - let src_new = get_account(&accounts[0]); - assert_eq!(src_new.delegate().unwrap(), accounts[2].key()); - assert_eq!(src_new.delegated_amount(), amount); - assert!(result.is_ok()) - } - - result -} - -/// accounts[0] // Mint Info -/// accounts[1] // Destination Info -/// accounts[2] // Owner Info -/// instruction_data[0..9] // Little Endian Bytes of u64 amount, and decimals -#[inline(never)] -fn test_process_mint_to_checked( - accounts: &[AccountInfo; 3], - instruction_data: &[u8; 9], -) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_mint(&accounts[0]); - cheatcode_is_account(&accounts[1]); - cheatcode_is_account(&accounts[2]); // Excluding the multisig case - - //-Initial State----------------------------------------------------------- - let mint_old = get_mint(&accounts[0]); - let dst_old = get_account(&accounts[1]); - let initial_supply = mint_old.supply(); - let initial_amount = dst_old.amount(); - let mint_initialised = mint_old.is_initialized(); - let dst_initialised = dst_old.is_initialized(); - let dst_init_state = dst_old.account_state(); - let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account - - #[cfg(feature = "assumptions")] - { - // Do not execute if adding to the account balance would overflow. - // shared::mint_to.rs,L68 is based on the assumption that initial_amount <= - // mint.supply and therefore cannot overflow because the minting itself - // would already error out. - let amount = unsafe { u64::from_le_bytes(*(instruction_data.as_ptr() as *const [u8; 8])) }; - if initial_amount.checked_add(amount).is_none() { - return Err(ProgramError::Custom(99)); - } - } - - //-Process Instruction----------------------------------------------------- - let result = process_mint_to_checked(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 9 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if accounts[1].data_len() != Account::LEN { - // TODO Daniel: is it possible for something to be provided that has the same - // len but is not an account? - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if dst_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !dst_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if dst_init_state.unwrap() == account_state::AccountState::Frozen { - // unwrap must succeed due to dst_initialised not being err - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if get_account(&accounts[1]).is_native() { - assert_eq!(result, Err(ProgramError::Custom(10))); - return result; - } else if accounts[0].key() != &get_account(&accounts[1]).mint { - assert_eq!(result, Err(ProgramError::Custom(3))); - return result; - } else if accounts[0].data_len() != Mint::LEN { - // Not sure if this is even possible if we get past the case above - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if instruction_data[8] != get_mint(&accounts[0]).decimals { - assert_eq!(result, Err(ProgramError::Custom(18))); - return result; - } else { - let mint_new = get_mint(&accounts[0]); - if mint_new.mint_authority().is_some() { - // Validate Owner - inner_test_validate_owner( - mint_new.mint_authority().unwrap(), // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - } else { - assert_eq!(result, Err(ProgramError::Custom(5))); - return result; - } - - let amount = unsafe { u64::from_le_bytes(*(instruction_data.as_ptr() as *const [u8; 8])) }; - - if amount == 0 && accounts[0].owner() != &pinocchio_token_interface::program::ID { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if amount == 0 && accounts[1].owner() != &pinocchio_token_interface::program::ID { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if amount != 0 && initial_supply.checked_add(amount).is_none() { - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } - - assert_eq!(mint_new.supply(), initial_supply + amount); - assert_eq!(get_account(&accounts[1]).amount(), initial_amount + amount); - assert!(result.is_ok()); - } - - result -} - -/// accounts[0] // Mint Info -/// accounts[1] // Destination Info -/// accounts[2] // Owner Info -/// accounts[3..14] // Signers -/// instruction_data[0..9] // Little Endian Bytes of u64 amount, and decimals -#[inline(never)] -fn test_process_mint_to_checked_multisig( - accounts: &[AccountInfo; 4], - instruction_data: &[u8; 9], -) -> ProgramResult { - use pinocchio_token_interface::state::account_state; - - cheatcode_is_mint(&accounts[0]); - cheatcode_is_account(&accounts[1]); - cheatcode_is_multisig(&accounts[2]); - - //-Initial State----------------------------------------------------------- - let mint_old = get_mint(&accounts[0]); - let dst_old = get_account(&accounts[1]); - let initial_supply = mint_old.supply(); - let initial_amount = dst_old.amount(); - let mint_initialised = mint_old.is_initialized(); - let dst_initialised = dst_old.is_initialized(); - let dst_init_state = dst_old.account_state(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); - - //-Process Instruction----------------------------------------------------- - let result = process_mint_to_checked(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 9 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if accounts[1].data_len() != Account::LEN { - // TODO Daniel: is it possible for something to be provided that has the same - // len but is not an account? - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if dst_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !dst_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if dst_init_state.unwrap() == account_state::AccountState::Frozen { - // unwrap must succeed due to dst_initialised not being err - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if get_account(&accounts[1]).is_native() { - assert_eq!(result, Err(ProgramError::Custom(10))); - return result; - } else if accounts[0].key() != &get_account(&accounts[1]).mint { - assert_eq!(result, Err(ProgramError::Custom(3))); - return result; - } else if accounts[0].data_len() != Mint::LEN { - // Not sure if this is even possible if we get past the case above - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if instruction_data[8] != get_mint(&accounts[0]).decimals { - assert_eq!(result, Err(ProgramError::Custom(18))); - return result; - } else { - let mint_new = get_mint(&accounts[0]); - if mint_new.mint_authority().is_some() { - // Validate Owner - inner_test_validate_owner( - mint_new.mint_authority().unwrap(), // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - } else { - assert_eq!(result, Err(ProgramError::Custom(5))); - return result; - } - - let amount = unsafe { u64::from_le_bytes(*(instruction_data.as_ptr() as *const [u8; 8])) }; - - if amount == 0 && accounts[0].owner() != &pinocchio_token_interface::program::ID { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if amount == 0 && accounts[1].owner() != &pinocchio_token_interface::program::ID { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if amount != 0 && u64::MAX - amount < initial_supply { - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } - - assert_eq!(mint_new.supply(), initial_supply + amount); - assert_eq!(get_account(&accounts[1]).amount(), initial_amount + amount); - assert!(result.is_ok()); - } - - result -} - -#[inline(never)] -fn test_process_sync_native(accounts: &[AccountInfo; 1]) -> ProgramResult { - use pinocchio_token_interface::program; - - cheatcode_is_account(&accounts[0]); - - //-Initial State----------------------------------------------------------- - let src_old = get_account(&accounts[0]); - let src_owner = accounts[0].owner(); - let src_initialised = src_old.is_initialized(); - let src_native_amount = src_old.native_amount(); - let src_init_lamports = accounts[0].lamports(); - let src_init_amount = src_old.amount(); - - //-Process Instruction----------------------------------------------------- - let result = process_sync_native(accounts); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() != 1 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if src_owner != &program::ID { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_native_amount.is_none() { - assert_eq!(result, Err(ProgramError::Custom(19))) - } else if src_init_lamports < src_native_amount.unwrap() { - assert_eq!(result, Err(ProgramError::Custom(14))) - } else if src_init_lamports - src_native_amount.unwrap() < src_init_amount { - assert_eq!(result, Err(ProgramError::Custom(13))) - } else { - assert_eq!( - get_account(&accounts[0]).amount(), - src_init_lamports - src_native_amount.unwrap() - ); - assert!(result.is_ok()) - } - result -} - -/// accounts[0] // Multisig Info -/// accounts[1..] // Signers -/// accounts[1..].len() // n -/// instruction_data[1] // m -#[inline(never)] -fn test_process_initialize_multisig2( - accounts: &[AccountInfo; 4], - instruction_data: &[u8; 1], -) -> ProgramResult { - cheatcode_is_multisig(&accounts[0]); - cheatcode_is_account(&accounts[1]); // Signer - cheatcode_is_account(&accounts[2]); // Signer - cheatcode_is_account(&accounts[3]); // Signer - - //-Initial State----------------------------------------------------------- - let multisig_already_initialised = get_multisig(&accounts[0]).is_initialized(); - let multisig_init_lamports = accounts[0].lamports(); - // Note: Rent is a supported sysvar so ProgramError::UnsupportedSysvar should be - // impossible - let rent = pinocchio::sysvars::rent::Rent::get().unwrap(); - let minimum_balance = rent.minimum_balance(accounts[0].data_len()); - - //-Process Instruction----------------------------------------------------- - let result = process_initialize_multisig2(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.is_empty() { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.is_empty() { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Multisig::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if multisig_already_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if multisig_already_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::Custom(6))) - } else if multisig_init_lamports < minimum_balance { - assert_eq!(result, Err(ProgramError::Custom(0))) - } else if !Multisig::is_valid_signer_index((accounts.len() - 1) as u8) { - assert_eq!(result, Err(ProgramError::Custom(7))) - } else if !Multisig::is_valid_signer_index(instruction_data[0]) { - assert_eq!(result, Err(ProgramError::Custom(8))) - } else { - let multisig_new = get_multisig(&accounts[0]); - assert!(accounts[1..] - .iter() - .map(|signer| *signer.key()) - .eq(multisig_new - .signers - .iter() - .take(accounts[1..].len()) - .copied())); - assert_eq!(multisig_new.m, instruction_data[0]); - assert_eq!(multisig_new.n as usize, accounts.len() - 1); - assert!(multisig_new.is_initialized().is_ok()); - assert!(multisig_new.is_initialized().unwrap()); - assert!(result.is_ok()) - } - - result -} - -/// accounts[0] // Mint Info -#[inline(never)] -fn test_process_get_account_data_size(accounts: &[AccountInfo; 1]) -> ProgramResult { - cheatcode_is_mint(&accounts[0]); - - //-Initial State----------------------------------------------------------- - let mint_initialised = get_mint(&accounts[0]).is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = process_get_account_data_size(accounts); - - //-Assert Postconditions--------------------------------------------------- - if accounts.is_empty() { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].owner() != &pinocchio_token_interface::program::ID { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else if accounts[0].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::Custom(2))) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::Custom(2))) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::Custom(2))) - } else { - // NOTE: This uses syscalls::sol_set_return_data - assert!(result.is_ok()) - } - result -} - -#[inline(never)] -fn test_process_initialize_immutable_owner(accounts: &[AccountInfo; 1]) -> ProgramResult { - cheatcode_is_account(&accounts[0]); - - //-Initial State----------------------------------------------------------- - let src_initialised = get_account(&accounts[0]).is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = process_initialize_immutable_owner(accounts); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() != 1 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::Custom(6))) - } else { - assert!(result.is_ok()) - } - result -} - -#[inline(never)] -fn test_process_amount_to_ui_amount( - accounts: &[AccountInfo; 1], - instruction_data: &[u8; 8], -) -> ProgramResult { - cheatcode_is_mint(&accounts[0]); - - //-Initial State----------------------------------------------------------- - let mint_initialised = get_mint(&accounts[0]).is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = process_amount_to_ui_amount(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 8 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.is_empty() { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].owner() != &pinocchio_token_interface::program::ID { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else if accounts[0].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::Custom(2))) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::Custom(2))) - } else { - assert!(result.is_ok()) - } - result -} - -#[inline(never)] -fn test_process_ui_amount_to_amount( - accounts: &[AccountInfo; 1], - instruction_data: &[u8], -) -> ProgramResult { - cheatcode_is_mint(&accounts[0]); - - //-Initial State----------------------------------------------------------- - let ui_amount = core::str::from_utf8(instruction_data); - let mint_initialised = get_mint(&accounts[0]).is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = process_ui_amount_to_amount(accounts, instruction_data); - - //-Assert Postconditions--------------------------------------------------- - // TODO: validations module is private, so we need a work around - if ui_amount.is_err() { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.is_empty() { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].owner() != &pinocchio_token_interface::program::ID { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else if accounts[0].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::Custom(2))) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::Custom(2))) - } else if ui_amount.unwrap().is_empty() { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if ui_amount.unwrap() == "." { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if 1 < ui_amount.unwrap().chars().filter(|&c| c == '.').count() { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if ui_amount.unwrap().starts_with('.') - && ui_amount.unwrap().chars().skip(1).all(|c| c == '0') - { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if ui_amount.unwrap().split_once('.').is_some_and(|(_, frac)| { - (get_mint(&accounts[0]).decimals as usize) < frac.trim_end_matches('0').len() - }) { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if ui_amount.unwrap().split_once('.').map_or( - 257_usize < ui_amount.unwrap().len() + (get_mint(&accounts[0]).decimals as usize), - |(ints, _)| 257_usize < ints.len() + (get_mint(&accounts[0]).decimals as usize), - ) { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } - /*else if ui_amount.unwrap() == "+." { - // TODO: Why is this valid? - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if ui_amount.unwrap() == "+" { - // TODO: Why is this valid? - assert_eq!(result, Err(ProgramError::InvalidArgument)) - }*/ - else if ui_amount.unwrap().starts_with('-') { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if ui_amount - .unwrap() - .contains(|c: char| !c.is_ascii_digit() && c != '+' && c != '.') - { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if ui_amount.unwrap().split_once('.').map_or( - { - const MAX_VAL: &str = "1844674407370955"; // TODO: What should this be? - let ui_amount = ui_amount.unwrap(); - let ui_amount = ui_amount.strip_prefix('+').unwrap_or(ui_amount); - let ui_amount = ui_amount.trim_start_matches('0'); - match ui_amount.len().cmp(&MAX_VAL.len()) { - core::cmp::Ordering::Less => false, - core::cmp::Ordering::Greater => true, - core::cmp::Ordering::Equal => MAX_VAL < ui_amount, - } - }, - |(ints, fracs)| { - const MAX_VAL: &str = "1844674407370955"; // TODO: What should this be? - let ints = ints.strip_prefix('+').unwrap_or(ints); - let hi = ints.trim_start_matches('0'); - let lo = if hi.is_empty() { - fracs.trim_start_matches('0') - } else { - fracs - }; - - let total_len = hi.len() + lo.len(); - - match total_len.cmp(&MAX_VAL.len()) { - core::cmp::Ordering::Less => false, - core::cmp::Ordering::Greater => true, - core::cmp::Ordering::Equal => { - if hi.len() > MAX_VAL.len() { - return true; - } - let (max_hi, max_lo) = MAX_VAL.split_at(hi.len()); - hi > max_hi || (hi == max_hi && lo > max_lo) - } - } - }, - ) { - // TODO: What is going on ??? Need to fix - // assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else { - assert!(result.is_ok()) - } - result -} - -/// accounts[0] // Source Account Info (Account) -/// accounts[1] // Destination Info -/// accounts[2] // Authority Info -#[inline(never)] -fn test_process_withdraw_excess_lamports_account(accounts: &[AccountInfo; 3]) -> ProgramResult { - cheatcode_is_account(&accounts[0]); // Source Account - cheatcode_is_account(&accounts[1]); // Destination - cheatcode_is_account(&accounts[2]); // Excluding the multisig case - - //-Initial State----------------------------------------------------------- - let src_old = get_account(&accounts[0]); - let src_data_len = accounts[0].data_len(); - let src_account_initialised = src_old.is_initialized(); - let src_account_owner = src_old.owner; - let src_account_is_native = src_old.is_native(); - let src_init_lamports = accounts[0].lamports(); - let dst_init_lamports = accounts[1].lamports(); - let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account - - // Note: Rent is a supported sysvar so ProgramError::UnsupportedSysvar should be - // impossible - let rent = pinocchio::sysvars::rent::Rent::get().unwrap(); - let minimum_balance = rent.minimum_balance(accounts[0].data_len()); - - //-Process Instruction----------------------------------------------------- - let result = process_withdraw_excess_lamports(accounts); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else { - assert_eq!(src_data_len, Account::LEN); // established by cheatcode_is_account - { - if src_account_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !src_account_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if src_account_is_native { - assert_eq!(result, Err(ProgramError::Custom(10))); - return result; - } - - // Validate Owner - inner_test_validate_owner( - &src_account_owner, // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - if src_init_lamports < minimum_balance { - assert_eq!(result, Err(ProgramError::Custom(0))); - return result; - } else if dst_init_lamports - .checked_add(src_init_lamports - minimum_balance) - .is_none() - { - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } - - assert_eq!(accounts[0].lamports(), minimum_balance); - assert_eq!( - accounts[1].lamports(), - dst_init_lamports + (src_init_lamports - minimum_balance) - ); - assert!(result.is_ok()) - } - } - - result -} - -/// accounts[0] // Source Account Info (Account) -/// accounts[1] // Destination Info -/// accounts[2] // Authority Info -/// accounts[3..14] // Signers -#[inline(never)] -fn test_process_withdraw_excess_lamports_account_multisig( - accounts: &[AccountInfo; 4], -) -> ProgramResult { - cheatcode_is_account(&accounts[0]); // Source Account - cheatcode_is_account(&accounts[1]); // Destination - cheatcode_is_multisig(&accounts[2]); // Authority - - //-Initial State----------------------------------------------------------- - let src_old = get_account(&accounts[0]); - let src_data_len = accounts[0].data_len(); - let src_account_initialised = src_old.is_initialized(); - let src_account_owner = src_old.owner; - let src_account_is_native = src_old.is_native(); - let src_init_lamports = accounts[0].lamports(); - let dst_init_lamports = accounts[1].lamports(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); - - // Note: Rent is a supported sysvar so ProgramError::UnsupportedSysvar should be - // impossible - let rent = pinocchio::sysvars::rent::Rent::get().unwrap(); - let minimum_balance = rent.minimum_balance(accounts[0].data_len()); - - //-Process Instruction----------------------------------------------------- - let result = process_withdraw_excess_lamports(accounts); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else { - assert_eq!(src_data_len, Account::LEN); // established by cheatcode_is_account - { - if src_account_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !src_account_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if src_account_is_native { - assert_eq!(result, Err(ProgramError::Custom(10))); - return result; - } - - // Validate Owner - inner_test_validate_owner( - &src_account_owner, // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - if src_init_lamports < minimum_balance { - assert_eq!(result, Err(ProgramError::Custom(0))); - return result; - } else if u64::MAX - src_init_lamports + minimum_balance < dst_init_lamports { - assert_eq!(result, Err(ProgramError::Custom(0))); - return result; - } - - assert_eq!(accounts[0].lamports(), minimum_balance); - assert_eq!( - accounts[1].lamports(), - dst_init_lamports + src_init_lamports - minimum_balance - ); - assert!(result.is_ok()) - } - } - - result -} - -/// accounts[0] // Source Account Info (Mint) -/// accounts[1] // Destination Info -/// accounts[2] // Authority Info -#[inline(never)] -fn test_process_withdraw_excess_lamports_mint(accounts: &[AccountInfo; 3]) -> ProgramResult { - cheatcode_is_mint(&accounts[0]); // Source Account (Mint) - cheatcode_is_account(&accounts[1]); // Destination - cheatcode_is_account(&accounts[2]); // Excluding the multisig case - - //-Initial State----------------------------------------------------------- - let src_old = get_mint(&accounts[0]); - let src_data_len = accounts[0].data_len(); - let src_mint_initialised = src_old.is_initialized(); - let src_mint_mint_authority = src_old.mint_authority().cloned(); - let src_init_lamports = accounts[0].lamports(); - let dst_init_lamports = accounts[1].lamports(); - let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account - - // Note: Rent is a supported sysvar so ProgramError::UnsupportedSysvar should be - // impossible - let rent = pinocchio::sysvars::rent::Rent::get().unwrap(); - let minimum_balance = rent.minimum_balance(accounts[0].data_len()); - - //-Process Instruction----------------------------------------------------- - let result = process_withdraw_excess_lamports(accounts); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else { - assert_eq!(src_data_len, Mint::LEN); // established by cheatcode_is_mint - { - if src_mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !src_mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if src_mint_mint_authority.is_some() { - // Validate Owner - inner_test_validate_owner( - &src_mint_mint_authority.unwrap(), // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - } else if accounts[0] != accounts[2] { - assert_eq!(result, Err(ProgramError::Custom(15))); - return result; - } else if !accounts[2].is_signer() { - assert_eq!(result, Err(ProgramError::MissingRequiredSignature)); - return result; - } - - if src_init_lamports < minimum_balance { - assert_eq!(result, Err(ProgramError::Custom(0))); - return result; - } else if dst_init_lamports - .checked_add(src_init_lamports - minimum_balance) - .is_none() - { - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } - - assert!(result.is_ok()); - assert_eq!(accounts[0].lamports(), minimum_balance); - assert_eq!( - accounts[1].lamports(), - dst_init_lamports - .checked_add(src_init_lamports - minimum_balance) - .unwrap() - ); - } - } - result -} - -/// accounts[0] // Source Account Info (Mint) -/// accounts[1] // Destination Info -/// accounts[2] // Authority Info -/// accounts[3..14] // Signers -#[inline(never)] -fn test_process_withdraw_excess_lamports_mint_multisig( - accounts: &[AccountInfo; 4], -) -> ProgramResult { - cheatcode_is_mint(&accounts[0]); // Source Account (Mint) - cheatcode_is_account(&accounts[1]); // Destination - cheatcode_is_multisig(&accounts[2]); // Authority - - //-Initial State----------------------------------------------------------- - let src_old = get_mint(&accounts[0]); - let src_data_len = accounts[0].data_len(); - let src_mint_initialised = src_old.is_initialized(); - let src_mint_mint_authority = src_old.mint_authority().cloned(); - let src_init_lamports = accounts[0].lamports(); - let dst_init_lamports = accounts[1].lamports(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); - - // Note: Rent is a supported sysvar so ProgramError::UnsupportedSysvar should be - // impossible - let rent = pinocchio::sysvars::rent::Rent::get().unwrap(); - let minimum_balance = rent.minimum_balance(accounts[0].data_len()); - - //-Process Instruction----------------------------------------------------- - let result = process_withdraw_excess_lamports(accounts); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else { - assert_eq!(src_data_len, Mint::LEN); // established by cheatcode_is_mint - { - if src_mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !src_mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if src_mint_mint_authority.is_some() { - // Validate Owner - inner_test_validate_owner( - &src_mint_mint_authority.unwrap(), // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - } else if accounts[0] != accounts[2] { - assert_eq!(result, Err(ProgramError::Custom(15))); - return result; - } else if !accounts[2].is_signer() { - assert_eq!(result, Err(ProgramError::MissingRequiredSignature)); - return result; - } else if src_init_lamports < minimum_balance { - assert_eq!(result, Err(ProgramError::Custom(0))); - return result; - } else if u64::MAX - src_init_lamports + minimum_balance < dst_init_lamports { - assert_eq!(result, Err(ProgramError::Custom(0))); - return result; - } - - assert_eq!(accounts[0].lamports(), minimum_balance); - assert_eq!( - accounts[1].lamports(), - dst_init_lamports + src_init_lamports - minimum_balance - ); - assert!(result.is_ok()) - } - } - - result -} - -/// accounts[0] // Source Account Info -/// accounts[1] // Destination Info -/// accounts[2] // Authority Info -#[inline(never)] -fn test_process_withdraw_excess_lamports_multisig(accounts: &[AccountInfo; 3]) -> ProgramResult { - cheatcode_is_multisig(&accounts[0]); // Source Account (Multisig) - cheatcode_is_account(&accounts[1]); // Destination - cheatcode_is_account(&accounts[2]); // Excluding the multisig case - - //-Initial State----------------------------------------------------------- - let src_data_len = accounts[0].data_len(); - let src_init_lamports = accounts[0].lamports(); - let dst_init_lamports = accounts[1].lamports(); - let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account - - // Note: Rent is a supported sysvar so ProgramError::UnsupportedSysvar should be - // impossible - let rent = pinocchio::sysvars::rent::Rent::get().unwrap(); - let minimum_balance = rent.minimum_balance(accounts[0].data_len()); - - //-Process Instruction----------------------------------------------------- - let result = process_withdraw_excess_lamports(accounts); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if src_data_len != Account::LEN - && src_data_len != Mint::LEN - && src_data_len != Multisig::LEN - { - assert_eq!(result, Err(ProgramError::Custom(13))); - return result; - } else { - assert_eq!(src_data_len, Multisig::LEN); // established by cheatcode_is_multisig - - // Validate Owner - inner_test_validate_owner( - accounts[0].key(), // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - if src_init_lamports < minimum_balance { - assert_eq!(result, Err(ProgramError::Custom(0))); - return result; - } else if dst_init_lamports - .checked_add(src_init_lamports - minimum_balance) - .is_none() - { - assert_eq!(result, Err(ProgramError::Custom(0))); - return result; - } - - assert_eq!(accounts[0].lamports(), minimum_balance); - assert_eq!( - accounts[1].lamports(), - dst_init_lamports + (src_init_lamports - minimum_balance) - ); - assert!(result.is_ok()) - } - - result -} - -/// accounts[0] // Source Account Info -/// accounts[1] // Destination Info -/// accounts[2] // Authority Info -/// accounts[3..14] // Signers -#[inline(never)] -fn test_process_withdraw_excess_lamports_multisig_multisig( - accounts: &[AccountInfo; 4], -) -> ProgramResult { - cheatcode_is_multisig(&accounts[0]); // Source Account (Multisig) - cheatcode_is_account(&accounts[1]); // Destination - cheatcode_is_multisig(&accounts[2]); // Authority - - //-Initial State----------------------------------------------------------- - let src_data_len = accounts[0].data_len(); - let src_init_lamports = accounts[0].lamports(); - let dst_init_lamports = accounts[1].lamports(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); - - // Note: Rent is a supported sysvar so ProgramError::UnsupportedSysvar should be - // impossible - let rent = pinocchio::sysvars::rent::Rent::get().unwrap(); - let minimum_balance = rent.minimum_balance(accounts[0].data_len()); - - //-Process Instruction----------------------------------------------------- - let result = process_withdraw_excess_lamports(accounts); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if src_data_len != Account::LEN - && src_data_len != Mint::LEN - && src_data_len != Multisig::LEN - { - assert_eq!(result, Err(ProgramError::Custom(13))); - return result; - } else { - assert_eq!(src_data_len, Multisig::LEN); // established by cheatcode_is_multisig - - // Validate Owner - inner_test_validate_owner( - accounts[0].key(), // expected_owner - &accounts[2], // owner_account_info - &accounts[3..], // tx_signers - maybe_multisig_is_initialised, - result.clone(), - )?; - - if src_init_lamports < minimum_balance { - assert_eq!(result, Err(ProgramError::Custom(0))); - return result; - } else if u64::MAX - src_init_lamports + minimum_balance < dst_init_lamports { - assert_eq!(result, Err(ProgramError::Custom(0))); - return result; - } - - assert_eq!(accounts[0].lamports(), minimum_balance); - assert_eq!( - accounts[1].lamports(), - dst_init_lamports + src_init_lamports - minimum_balance - ); - assert!(result.is_ok()) - } - - result -} +// Shared test harnesses --------------------------------------------------- +include!("../../specs/shared/test_process_approve_checked.rs"); +include!("../../specs/shared/test_process_approve_checked_multisig.rs"); +include!("../../specs/shared/test_process_approve.rs"); +include!("../../specs/shared/test_process_approve_multisig.rs"); +include!("../../specs/shared/test_process_freeze_account.rs"); +include!("../../specs/shared/test_process_freeze_account_multisig.rs"); +include!("../../specs/shared/test_process_get_account_data_size.rs"); +include!("../../specs/shared/test_process_initialize_account2.rs"); +include!("../../specs/shared/test_process_initialize_account3.rs"); +include!("../../specs/shared/test_process_initialize_account.rs"); +include!("../../specs/shared/test_process_initialize_immutable_owner.rs"); +include!("../../specs/shared/test_process_initialize_mint2_freeze.rs"); +include!("../../specs/shared/test_process_initialize_mint2_no_freeze.rs"); +include!("../../specs/shared/test_process_initialize_mint_freeze.rs"); +include!("../../specs/shared/test_process_initialize_mint_no_freeze.rs"); +include!("../../specs/shared/test_process_initialize_multisig.rs"); +include!("../../specs/shared/test_process_initialize_multisig2.rs"); +include!("../../specs/shared/test_process_mint_to_checked.rs"); +include!("../../specs/shared/test_process_mint_to_checked_multisig.rs"); +include!("../../specs/shared/test_process_mint_to.rs"); +include!("../../specs/shared/test_process_mint_to_multisig.rs"); +include!("../../specs/shared/test_process_revoke.rs"); +include!("../../specs/shared/test_process_revoke_multisig.rs"); +include!("../../specs/shared/test_process_set_authority_account.rs"); +include!("../../specs/shared/test_process_set_authority_account_multisig.rs"); +include!("../../specs/shared/test_process_set_authority_mint.rs"); +include!("../../specs/shared/test_process_set_authority_mint_multisig.rs"); +include!("../../specs/shared/test_process_sync_native.rs"); +include!("../../specs/shared/test_process_thaw_account.rs"); +include!("../../specs/shared/test_process_thaw_account_multisig.rs"); +include!("../../specs/shared/test_process_close_account.rs"); +include!("../../specs/shared/test_process_close_account_multisig.rs"); +include!("../../specs/shared/test_process_burn_checked.rs"); +include!("../../specs/shared/test_process_burn_checked_multisig.rs"); +include!("../../specs/shared/test_process_burn.rs"); +include!("../../specs/shared/test_process_burn_multisig.rs"); +include!("../../specs/shared/test_process_transfer_checked.rs"); +include!("../../specs/shared/test_process_transfer_checked_multisig.rs"); +include!("../../specs/shared/test_process_transfer.rs"); +include!("../../specs/shared/test_process_transfer_multisig.rs"); +include!("../../specs/shared/test_process_amount_to_ui_amount.rs"); +include!("../../specs/shared/test_process_ui_amount_to_amount.rs"); + +// Withdraw Excess Lamports test harnesses (p-token specific) +include!("../../specs/withdraw-p-token.rs"); diff --git a/program/src/entrypoint-runtime-verification.rs b/program/src/entrypoint-runtime-verification.rs index 2dbe6326..c61d2c18 100644 --- a/program/src/entrypoint-runtime-verification.rs +++ b/program/src/entrypoint-runtime-verification.rs @@ -1,13 +1,16 @@ //! Program entrypoint for runtime verification proofs of original spl token implmentation use { - crate::{processor::Processor, state::{Account, AccountState, Mint, Multisig}}, + crate::{ + processor::Processor, + state::{Account, AccountState, Mint, Multisig}, + }, solana_account_info::AccountInfo, solana_program_error::{ProgramError, ProgramResult}, solana_program_pack::Pack, - solana_pubkey::{self as pubkey, Pubkey}, + solana_pubkey::Pubkey, solana_sysvar::Sysvar, - spl_token_interface::{error::TokenError, native_mint}, + spl_token_interface::error::TokenError, std::intrinsics::assume, }; @@ -29,337 +32,11 @@ fn process_instruction( result } -struct MintWrapper(Result); +// Include prelude (macros, cheatcodes, wrappers, helpers) +include!("../../specs/prelude-spl-token.rs"); -impl MintWrapper { - fn is_initialized(&self) -> Result { - match &self.0 { - Ok(m) => Ok(m.is_initialized), - Err(e) => Err(e.clone()), - } - } - - fn mint_authority(&self) -> Option<&Pubkey> { - match &self.0 { - Ok(m) => match m.mint_authority.as_ref() { - solana_program_option::COption::Some(pk) => Some(pk), - solana_program_option::COption::None => None, - }, - Err(_) => None, - } - } - - fn freeze_authority(&self) -> Option<&Pubkey> { - match &self.0 { - Ok(m) => match m.freeze_authority.as_ref() { - solana_program_option::COption::Some(pk) => Some(pk), - solana_program_option::COption::None => None, - }, - Err(_) => None, - } - } - - fn supply(&self) -> u64 { - match &self.0 { - Ok(m) => m.supply, - Err(_) => 0, - } - } - - fn decimals(&self) -> u8 { - match &self.0 { - Ok(m) => m.decimals, - Err(_) => 0, - } - } -} - -fn get_mint(account_info: &AccountInfo) -> MintWrapper { - MintWrapper(Mint::unpack_unchecked(&account_info.data.borrow())) -} - -macro_rules! assert_pubkey_from_slice { - ($actual:expr, $slice:expr) => {{ - let expected_pubkey = Pubkey::new_from_array($slice.try_into().unwrap()); - assert_eq!($actual, expected_pubkey); - }}; -} - -/// A wrapper struct as middleware so that the same functions called -/// on the p-token Account are called on the spl Account. However, -/// this means that fields have to be accessed through functions. -struct AccountWrapper(Result); - -impl AccountWrapper { - fn is_initialized(&self) -> Result { - match &self.0 { - Ok(a) => Ok(a.state != AccountState::Uninitialized), - Err(e) => Err(e.clone()), - } - } - - fn amount(&self) -> u64 { - match &self.0 { - Ok(a) => a.amount, - Err(_) => panic!("AccountWrapper amount: underlying account missing"), - } - } - - fn mint(&self) -> Pubkey { - match &self.0 { - Ok(a) => a.mint, - Err(_) => panic!("AccountWrapper mint: underlying account missing"), - } - } - - fn owner(&self) -> Pubkey { - match &self.0 { - Ok(a) => a.owner, - Err(_) => panic!("AccountWrapper owner: underlying account missing"), - } - } - - fn delegate(&self) -> Option<&Pubkey> { - match &self.0 { - Ok(a) => match a.delegate.as_ref() { - solana_program_option::COption::None => None, - solana_program_option::COption::Some(delegate) => Some(delegate), - }, - Err(_) => None, - } - } - - fn delegated_amount(&self) -> u64 { - match &self.0 { - Ok(a) => a.delegated_amount, - Err(_) => panic!("AccountWrapper delegated_amount: underlying account missing"), - } - } - - fn account_state(&self) -> Result { - match &self.0 { - Ok(a) => Ok(a.state), - Err(e) => Err(e.clone()), - } - } - - fn is_native(&self) -> bool { - match &self.0 { - Ok(a) => a.is_native.is_some(), - Err(_) => false, - } - } - - fn native_amount(&self) -> Option { - match &self.0 { - Ok(a) => match a.is_native { - solana_program_option::COption::Some(amt) => Some(amt), - solana_program_option::COption::None => None, - }, - Err(_) => None, - } - } - - fn close_authority(&self) -> Option<&Pubkey> { - match &self.0 { - Ok(a) => match a.close_authority.as_ref() { - solana_program_option::COption::Some(pk) => Some(pk), - solana_program_option::COption::None => None, - }, - Err(_) => None, - } - } - - fn is_owned_by_system_program_or_incinerator(&self) -> bool { - match &self.0 { - Ok(a) => a.owner == solana_sdk_ids::system_program::ID || a.owner == solana_sdk_ids::incinerator::ID, - Err(_) => false, - } - } -} - -/// So the AccountWrapper derefs the wrapped Account -impl core::ops::Deref for AccountWrapper { - type Target = Result; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -/// Helper function from p-token must be implemented on AccountWrapper -fn get_account(account_info: &AccountInfo) -> AccountWrapper { - AccountWrapper(Account::unpack_unchecked(&account_info.data.borrow())) -} - -/// A wrapper struct as middleware so that the same functions called -/// on the p-token Multisig are called on the spl Multisig. However, -/// this means that fields have to be accessed through functions. -struct MultisigWrapper(Result); - -impl MultisigWrapper { - fn is_initialized(&self) -> Result { - match &self.0 { - Ok(m) => Ok(m.is_initialized), - Err(e) => Err(e.clone()), - } - } - - fn signers(&self) -> &[Pubkey] { - match &self.0 { - Ok(m) => &m.signers[..], - Err(_) => &[], - } - } - - fn m(&self) -> u8 { - match &self.0 { - Ok(m) => m.m, - Err(_) => 0, - } - } - - fn n(&self) -> u8 { - match &self.0 { - Ok(m) => m.n, - Err(_) => 0, - } - } -} - -/// So the MultisigWrapper derefs the wrapped Multisig -impl core::ops::Deref for MultisigWrapper { - type Target = Result; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -/// Helper function from p-token must be implemented on MultisigWrapper -fn get_multisig(account_info: &AccountInfo) -> MultisigWrapper { - MultisigWrapper(Multisig::unpack_unchecked(&account_info.data.borrow())) -} - -fn get_rent(account_info: &AccountInfo) -> solana_rent::Rent { - // Directly deserialize from account data without key check - bincode::deserialize(&account_info.data.borrow()).unwrap() -} - -#[inline(never)] -fn inner_test_validate_owner( - expected_owner: &Pubkey, - owner_account_info: &AccountInfo, - tx_signers: &[AccountInfo], - maybe_multisig_is_initialised: Option>, - result: Result<(), ProgramError>, -) -> Result<(), ProgramError> { - use crate::id; - - if expected_owner != owner_account_info.key { - assert_eq!(result, Err(ProgramError::Custom(4))); - return result; - } - // We add the `maybe_multisig_is_initialised.is_some()` to not branch vacuously in the - // non-multisig cases - else if maybe_multisig_is_initialised.is_some() - && owner_account_info.data_len() == Multisig::LEN - && owner_account_info.owner == &id() - { - let multisig_is_initialised = maybe_multisig_is_initialised.unwrap(); - if multisig_is_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !multisig_is_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } - - let multisig = get_multisig(owner_account_info); - let unsigned_exists = tx_signers.iter().any(|potential_signer| { - multisig.signers().iter().any(|registered_key| { - registered_key == potential_signer.key && !potential_signer.is_signer - }) - }); - if unsigned_exists { - assert_eq!(result, Err(ProgramError::MissingRequiredSignature)); - return result; - } - - let signers_count = multisig - .signers() - .iter() - .filter_map(|registered_key| { - tx_signers.iter().find(|potential_signer| { - potential_signer.key == registered_key && potential_signer.is_signer - }) - }) - .count(); - if signers_count < multisig.m() as usize { - assert_eq!(result, Err(ProgramError::MissingRequiredSignature)); - return result; - } - - return Ok(()); - } - // Non-multisig case - check if owner_account_info.is_signer - else if !owner_account_info.is_signer { - assert_eq!(result, Err(ProgramError::MissingRequiredSignature)); - return result; - } - - Ok(()) -} - -// TODO: Not sure if these are needed since there is no UB like p-token -#[inline(never)] -fn cheatcode_is_spl_account(_: &AccountInfo) {} -#[inline(never)] -fn cheatcode_is_spl_mint(_: &AccountInfo) {} -#[inline(never)] -fn cheatcode_is_spl_multisig(_: &AccountInfo) {} -#[inline(never)] -fn cheatcode_is_spl_rent(_: &AccountInfo) {} - -// special test for basic domain data access (SPL types) -#[inline(never)] -fn test_spltoken_domain_data(acc: &AccountInfo, mint: &AccountInfo, rent: &AccountInfo) { - // Mutate mint via standard unpack/pack flow; use unwraps for brevity in tests - cheatcode_is_spl_mint(mint); - let mut m = Mint::unpack_unchecked(&mint.data.borrow()).unwrap(); - m.is_initialized = true; - Mint::pack(m, &mut mint.data.borrow_mut()).unwrap(); - let m2 = Mint::unpack(&mint.data.borrow()).unwrap(); - assert!(m2.is_initialized); - - // Set Account.is_native in the simplest way (parity with p-token's boolean set_native(true)) - cheatcode_is_spl_account(acc); - let mut a = Account::unpack_unchecked(&acc.data.borrow()).unwrap(); - a.is_native = solana_program_option::COption::Some(0); - Account::pack(a, &mut acc.data.borrow_mut()).unwrap(); - // Verify via the same wrapper accessor used elsewhere - let iacc = get_account(acc); - assert!(iacc.is_native()); - - // Basic owner self-check - let owner = acc.owner; - assert_eq!(acc.owner, owner); - - // Compare Rent behavior using the sysvar getter and the provided account - let sysrent = solana_rent::Rent::get().unwrap(); - let rent_collected = 10; - let (sys_burnt, sys_distributed) = sysrent.calculate_burn(rent_collected); - assert!(sysrent.burn_percent > 100 || (sys_burnt <= rent_collected && sys_distributed <= rent_collected)); - - cheatcode_is_spl_rent(rent); - let prent = solana_rent::Rent::from_account_info(rent).unwrap_or(sysrent); - let (acct_burnt, acct_distributed) = prent.calculate_burn(rent_collected); - assert!(prent.burn_percent > 100 || (acct_burnt <= rent_collected && acct_distributed <= rent_collected)); -} - -// wrapper to ensure the test is retained in SMIR/IR outputs -#[no_mangle] -pub unsafe extern "C" fn use_tests(acc: &AccountInfo) { - test_spltoken_domain_data(acc, acc, acc); -} +// Include inner_test_validate_owner +include!("../../specs/shared/inner_test_validate_owner.rs"); // Inline `assume` is used directly in test harnesses; no helper functions needed. @@ -384,17 +61,15 @@ fn inner_process_instruction( match payload.len() { x if 66 <= x => { test_process_initialize_mint_freeze( - program_id, - accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 2] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, - ) + accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, + payload.first_chunk().ok_or(TokenError::InvalidInstruction)?, + ) } x if 34 <= x => { test_process_initialize_mint_no_freeze( - program_id, - accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 2] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, - ) + accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, + payload.first_chunk().ok_or(TokenError::InvalidInstruction)?, + ) } _ => Err(TokenError::InvalidInstruction.into()), } @@ -402,17 +77,14 @@ fn inner_process_instruction( // 1 - Initialize Account 1 => { test_process_initialize_account( - program_id, accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } // 2 - Initialize Multisig 2 => { test_process_initialize_multisig( - program_id, - accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 5] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data[1..].first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } // 3 - Transfer @@ -422,15 +94,13 @@ fn inner_process_instruction( && accounts[2].owner == &crate::id() { test_process_transfer_multisig( - program_id, accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 4] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data.last_chunk().ok_or(TokenError::InvalidInstruction)?, ) } else { test_process_transfer( - program_id, accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 3] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data.last_chunk().ok_or(TokenError::InvalidInstruction)?, ) } } @@ -441,15 +111,13 @@ fn inner_process_instruction( && accounts[2].owner == &crate::id() { test_process_approve_multisig( - program_id, accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 4] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data.last_chunk().ok_or(TokenError::InvalidInstruction)?, ) } else { test_process_approve( - program_id, accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 3] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data.last_chunk().ok_or(TokenError::InvalidInstruction)?, ) } } @@ -460,15 +128,11 @@ fn inner_process_instruction( && accounts[1].owner == &crate::id() { test_process_revoke_multisig( - program_id, accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } else { test_process_revoke( - program_id, accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } } @@ -484,15 +148,13 @@ fn inner_process_instruction( && accounts[1].owner == &crate::id() { test_process_set_authority_account_multisig( - program_id, - accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 3] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data[1..].first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } else { test_process_set_authority_account( - program_id, - accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 2] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data[1..].first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } } @@ -502,15 +164,13 @@ fn inner_process_instruction( && accounts[1].owner == &crate::id() { test_process_set_authority_mint_multisig( - program_id, accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 3] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data[1..].first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } else { test_process_set_authority_mint( - program_id, accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 2] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data[1..].first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } } @@ -527,15 +187,13 @@ fn inner_process_instruction( && accounts[2].owner == &crate::id() { test_process_mint_to_multisig( - program_id, accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 4] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data.last_chunk().ok_or(TokenError::InvalidInstruction)?, ) } else { test_process_mint_to( - program_id, accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 3] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data.last_chunk().ok_or(TokenError::InvalidInstruction)?, ) } } @@ -546,15 +204,13 @@ fn inner_process_instruction( && accounts[2].owner == &crate::id() { test_process_burn_multisig( - program_id, accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 4] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data.last_chunk().ok_or(TokenError::InvalidInstruction)?, ) } else { test_process_burn( - program_id, accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 3] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data.last_chunk().ok_or(TokenError::InvalidInstruction)?, ) } } @@ -565,15 +221,11 @@ fn inner_process_instruction( && accounts[2].owner == &crate::id() { test_process_close_account_multisig( - program_id, accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } else { test_process_close_account( - program_id, accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } } @@ -584,15 +236,11 @@ fn inner_process_instruction( && accounts[2].owner == &crate::id() { test_process_freeze_account_multisig( - program_id, accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } else { test_process_freeze_account( - program_id, accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } } @@ -603,15 +251,11 @@ fn inner_process_instruction( && accounts[2].owner == &crate::id() { test_process_thaw_account_multisig( - program_id, accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } else { test_process_thaw_account( - program_id, accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } } @@ -622,15 +266,13 @@ fn inner_process_instruction( && accounts[3].owner == &crate::id() { test_process_transfer_checked_multisig( - program_id, - accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 5] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data[1..].first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } else { test_process_transfer_checked( - program_id, - accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 4] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data[1..].first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } } @@ -641,15 +283,13 @@ fn inner_process_instruction( && accounts[3].owner == &crate::id() { test_process_approve_checked_multisig( - program_id, - accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 5] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data[1..].first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } else { test_process_approve_checked( - program_id, - accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 4] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data[1..].first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } } @@ -660,15 +300,13 @@ fn inner_process_instruction( && accounts[2].owner == &crate::id() { test_process_mint_to_checked_multisig( - program_id, - accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 4] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data[1..].first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } else { test_process_mint_to_checked( - program_id, accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 3] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data.last_chunk().ok_or(TokenError::InvalidInstruction)?, ) } } @@ -679,48 +317,41 @@ fn inner_process_instruction( && accounts[2].owner == &crate::id() { test_process_burn_checked_multisig( - program_id, - accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 4] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data[1..].first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } else { test_process_burn_checked( - program_id, - accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 3] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data[1..].first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } } // 16 - Initialize Account2 16 => { test_process_initialize_account2( - program_id, accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 3] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data[1..].first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } // 17 - Sync Native 17 => { test_process_sync_native( - program_id, accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } // 18 - Initialize Account3 18 => { test_process_initialize_account3( - program_id, accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 2] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data[1..].first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } // 19 - Initialize Multisig2 19 => { test_process_initialize_multisig2( - program_id, - accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 4] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data[1..].first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } // 20 - Initialize Mint2 Freeze @@ -733,17 +364,15 @@ fn inner_process_instruction( match payload.len() { x if 66 <= x => { test_process_initialize_mint2_freeze( - program_id, - accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 1] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, - ) + accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 1] + instruction_data[1..].first_chunk().ok_or(TokenError::InvalidInstruction)?, + ) } x if 34 <= x => { test_process_initialize_mint2_no_freeze( - program_id, - accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 1] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, - ) + accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 1] + instruction_data[1..].first_chunk().ok_or(TokenError::InvalidInstruction)?, + ) } _ => Err(TokenError::InvalidInstruction.into()), } @@ -751,33 +380,27 @@ fn inner_process_instruction( // 21 - Get Account Data Size 21 => { test_process_get_account_data_size( - program_id, accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } // 22 - Initialize Immutable Owner 22 => { test_process_initialize_immutable_owner( - program_id, accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } // 23 - Amount To Ui Amount 23 => { test_process_amount_to_ui_amount( - program_id, - accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 1] - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data[1..].first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } // 24 - Ui Amount To Amount 24 => { test_process_ui_amount_to_amount( - program_id, - accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, // CHANGE P-Token: accounts: &[AccountInfo; 1] - instruction_data, + accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, + &instruction_data[1..], ) } // 38 - Withdraw Excess Lamports @@ -786,7 +409,7 @@ fn inner_process_instruction( // msg!("Testing Instruction: Withdraw Excess Lamports"); test_process_withdraw_excess_lamports( program_id, - accounts, + accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, ) } @@ -797,4076 +420,91 @@ fn inner_process_instruction( } } -/// program_id // Token Program ID -/// accounts[0] // Mint Info -/// accounts[1] // Rent Sysvar Info -/// instruction_data[0] // Discriminator 0 (Initialize Mint Freeze) -/// instruction_data[1] // Decimals -/// instruction_data[2..34] // Mint Authority Pubkey -/// instruction_data[34] // Freeze Authority Exists? 1 for freeze -/// instruction_data[35..67] // instruction_data[34] == 1 ==> Freeze Authority Pubkey -#[inline(never)] -fn test_process_initialize_mint_freeze( - program_id: &Pubkey, - accounts: &[AccountInfo; 2], - instruction_data: &[u8; 67], -) -> ProgramResult { - // Constrain discriminator and program id - unsafe { assume(0 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 66] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_mint(&accounts[0]); - cheatcode_is_spl_rent(&accounts[1]); - - //-Initial State----------------------------------------------------------- - let minimum_balance = get_rent(&accounts[1]).minimum_balance(accounts[0].data_len()); // TODO float problem - let mint_is_initialised_prior = get_mint(&accounts[0]).is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 34 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if instruction_data[33] != 0 && instruction_data[33] != 1 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if instruction_data[33] == 1 && instruction_data.len() < 66 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 2 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if accounts[1].key != &solana_sysvar::rent::ID { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if mint_is_initialised_prior.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_is_initialised_prior.unwrap() { - assert_eq!(result, Err(ProgramError::Custom(6))) - } else if accounts[0].lamports() < minimum_balance { - assert_eq!(result, Err(ProgramError::Custom(0))) - } else { - assert!(result.is_ok()); - - let mint_new = get_mint(&accounts[0]); - assert!(mint_new.is_initialized().unwrap()); - assert_pubkey_from_slice!(*mint_new.mint_authority().unwrap(), instruction_data[1..33]); - assert_eq!(mint_new.decimals(), instruction_data[0]); - - if instruction_data[33] == 1 { - assert_pubkey_from_slice!(*mint_new.freeze_authority().unwrap(), instruction_data[34..66]); - } - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Mint Info -/// accounts[1] // Rent Sysvar Info -/// instruction_data[0] // Discriminator 0 (Initialize Mint No Freeze) -/// instruction_data[1] // Decimals -/// instruction_data[2..34] // Mint Authority Pubkey -/// instruction_data[34] // Freeze Authority Exists? 0 for no freeze -#[inline(never)] -fn test_process_initialize_mint_no_freeze( - program_id: &Pubkey, - accounts: &[AccountInfo; 2], - instruction_data: &[u8; 35], -) -> ProgramResult { - // Constrain discriminator and program id - unsafe { assume(0 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 34] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_mint(&accounts[0]); - cheatcode_is_spl_rent(&accounts[1]); - - //-Initial State----------------------------------------------------------- - let minimum_balance = get_rent(&accounts[1]).minimum_balance(accounts[0].data_len()); // TODO float problem - let mint_is_initialised_prior = get_mint(&accounts[0]).is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 34 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if instruction_data[33] != 0 && instruction_data[33] != 1 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if instruction_data[33] == 1 && instruction_data.len() < 66 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 2 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if accounts[1].key != &solana_sysvar::rent::ID { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if mint_is_initialised_prior.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_is_initialised_prior.unwrap() { - assert_eq!(result, Err(ProgramError::Custom(6))) - } else if accounts[0].lamports() < minimum_balance { - assert_eq!(result, Err(ProgramError::Custom(0))) - } else { - assert!(result.is_ok()); - - assert!(get_mint(&accounts[0]).is_initialized().unwrap()); - assert_pubkey_from_slice!(*get_mint(&accounts[0]).mint_authority().unwrap(), instruction_data[1..33]); - assert_eq!(get_mint(&accounts[0]).decimals(), instruction_data[0]); - - if instruction_data[33] == 1 { - assert_pubkey_from_slice!(*get_mint(&accounts[0]).freeze_authority().unwrap(), instruction_data[34..66]); - } - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result +// wrapper to ensure the test below is in the SMIR JSON +#[no_mangle] +pub unsafe extern "C" fn use_tests(acc: &AccountInfo) { + test_spltoken_domain_data(acc, acc, acc); } -/// program_id // Token Program ID -/// accounts[0] // Source Info -/// accounts[1] // Mint Info -/// accounts[2] // Authority Info -/// accounts[3..14] // Signers -/// instruction_data[0] // Discriminator 15 (Burn Checked) -/// instruction_data[1..10] // Little Endian Bytes of u64 amount, and decimals +// special test for basic domain data access (SPL types) #[inline(never)] -fn test_process_burn_checked_multisig( - program_id: &Pubkey, - accounts: &[AccountInfo; 4], - instruction_data: &[u8; 10], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(15 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 9] = instruction_data.last_chunk().unwrap(); +fn test_spltoken_domain_data(acc: &AccountInfo, mint: &AccountInfo, rent: &AccountInfo) { + // Mutate mint via standard unpack/pack flow; use unwraps for brevity in tests + cheatcode_is_spl_mint(mint); + let mut m = Mint::unpack_unchecked(&mint.data.borrow()).unwrap(); + m.is_initialized = true; + Mint::pack(m, &mut mint.data.borrow_mut()).unwrap(); + let m2 = Mint::unpack(&mint.data.borrow()).unwrap(); + assert!(m2.is_initialized); - cheatcode_is_spl_account(&accounts[0]); - cheatcode_is_spl_mint(&accounts[1]); - cheatcode_is_spl_multisig(&accounts[2]); + // Set Account.is_native in the simplest way (parity with p-token's boolean set_native(true)) + cheatcode_is_spl_account(acc); + let mut a = Account::unpack_unchecked(&acc.data.borrow()).unwrap(); + a.is_native = solana_program_option::COption::Some(0); + Account::pack(a, &mut acc.data.borrow_mut()).unwrap(); + // Verify via the same wrapper accessor used elsewhere + let iacc = get_account(acc); + assert!(iacc.is_native()); - //-Initial State----------------------------------------------------------- - let amount = u64::from_le_bytes([instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7]]); - let src_initialised = get_account(&accounts[0]).is_initialized(); - let src_init_amount = get_account(&accounts[0]).amount(); - let src_init_state = get_account(&accounts[0]).account_state(); - let src_is_native = get_account(&accounts[0]).is_native(); - let src_mint = get_account(&accounts[0]).mint(); - let src_owned_sys_inc = get_account(&accounts[0]).is_owned_by_system_program_or_incinerator(); - let src_owner = get_account(&accounts[0]).owner(); - let old_src_delgate = get_account(&accounts[0]).delegate().cloned(); - let old_src_delgated_amount = get_account(&accounts[0]).delegated_amount(); - let mint_initialised = get_mint(&accounts[1]).is_initialized(); - let mint_init_supply = get_mint(&accounts[1]).supply(); - let mint_decimals = get_mint(&accounts[1]).decimals(); - let mint_owner = *accounts[1].owner; - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); - let tx_signers: &[AccountInfo] = &accounts[3..]; + // Basic owner self-check + let owner = acc.owner; + assert_eq!(acc.owner, owner); - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); + // Compare Rent behavior using the sysvar getter and the provided account + let sysrent = solana_rent::Rent::get().unwrap(); + let rent_collected = 10; + let (sys_burnt, sys_distributed) = sysrent.calculate_burn(rent_collected); + assert!(sysrent.burn_percent > 100 || (sys_burnt <= rent_collected && sys_distributed <= rent_collected)); - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 9 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if accounts[1].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_init_state.unwrap() == AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(17))) - } else if src_is_native { - assert_eq!(result, Err(ProgramError::Custom(10))) - } else if src_init_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))) - } else if accounts[1].key != &src_mint { - assert_eq!(result, Err(ProgramError::Custom(3))) - } else if instruction_data[8] != mint_decimals { - assert_eq!(result, Err(ProgramError::Custom(18))) - } else { - if !src_owned_sys_inc { - if old_src_delgate == Some(*accounts[2].key) { - inner_test_validate_owner( - old_src_delgate.as_ref().unwrap(), - &accounts[2], - tx_signers, - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - - if old_src_delgated_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))); - return result; - } - } else { - inner_test_validate_owner( - &src_owner, - &accounts[2], - tx_signers, - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - } - } - - if amount == 0 && src_owner != crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else if amount == 0 && mint_owner != crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else { - assert!(get_account(&accounts[0]).amount() == src_init_amount - amount); - assert!(get_mint(&accounts[1]).supply() == mint_init_supply - amount); - assert!(result.is_ok()); - - // Delegate updates - if old_src_delgate.is_some() && *accounts[2].key == old_src_delgate.unwrap() { - assert_eq!(get_account(&accounts[0]).delegated_amount(), old_src_delgated_amount - amount); - if old_src_delgated_amount - amount == 0 { - assert_eq!(get_account(&accounts[0]).delegate(), None); - } - } - } - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // New Account Info -/// accounts[1] // Mint Info -/// accounts[2] // Owner Info -/// accounts[3] // Rent Sysvar Info -/// instruction_data[0] // Discriminator 1 (Initialize Account) -#[inline(never)] -fn test_process_initialize_account( - program_id: &Pubkey, - accounts: &[AccountInfo; 4], - instruction_data: &[u8; 1], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(1 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 0] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); - cheatcode_is_spl_mint(&accounts[1]); - cheatcode_is_spl_account(&accounts[2]); - cheatcode_is_spl_rent(&accounts[3]); - - //-Initial State----------------------------------------------------------- - let initial_state_new_account = get_account(&accounts[0]) - .account_state(); - - let minimum_balance = get_rent(&accounts[3]).minimum_balance(accounts[0].data_len()); // TODO float problem - let is_native_mint = accounts[1].key == &native_mint::ID; - let mint_is_initialised = get_mint(&accounts[1]).is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() < 4 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - } else if accounts[3].key != &solana_sysvar::rent::ID { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if initial_state_new_account.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if initial_state_new_account.unwrap() != AccountState::Uninitialized { - assert_eq!(result, Err(ProgramError::Custom(6))) - } else if accounts[0].lamports() < minimum_balance { - assert_eq!(result, Err(ProgramError::Custom(0))) - } else if !is_native_mint && accounts[1].owner != &crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else if !is_native_mint - && accounts[1].owner == &crate::id() - && mint_is_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !is_native_mint - && accounts[1].owner == &crate::id() - && !mint_is_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::Custom(2))) - } else { - assert!(result.is_ok()); - assert_eq!(get_account(&accounts[0]).account_state().unwrap(), AccountState::Initialized); - assert_eq!(get_account(&accounts[0]).mint(), *accounts[1].key); - assert_eq!(get_account(&accounts[0]).owner(), *accounts[2].key); - - if is_native_mint { - assert!(get_account(&accounts[0]).is_native()); - assert_eq!(get_account(&accounts[0]).native_amount().unwrap(), minimum_balance); - assert_eq!(get_account(&accounts[0]).amount(), accounts[0].lamports() - minimum_balance); - } - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Multisig Info -/// accounts[1] // Rent Sysvar Info -/// accounts[2..] // Signers -/// accounts[2..].len() // n -/// instruction_data[0] // Discriminator 2 (Initialize Multisig) -/// instruction_data[2] // m -#[inline(never)] -fn test_process_initialize_multisig( - program_id: &Pubkey, - accounts: &[AccountInfo; 5], - instruction_data: &[u8; 2], -) -> ProgramResult { - // Constrain discriminator and program id - unsafe { assume(2 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 1] = instruction_data.last_chunk().unwrap(); - - // ^ FIXME: totally arbitrary for the tests - cheatcode_is_spl_multisig(&accounts[0]); - cheatcode_is_spl_rent(&accounts[1]); - cheatcode_is_spl_account(&accounts[2]); // Signer - cheatcode_is_spl_account(&accounts[3]); // Signer - cheatcode_is_spl_account(&accounts[4]); // Signer - - //-Initial State----------------------------------------------------------- - let multisig_already_initialised = get_multisig(&accounts[0]).is_initialized(); - let multisig_init_lamports = accounts[0].lamports(); - let minimum_balance = get_rent(&accounts[1]).minimum_balance(accounts[0].data_len()); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.is_empty() { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 2 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[1].key != &solana_sysvar::rent::ID { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if accounts[0].data_len() != Multisig::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if multisig_already_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if multisig_already_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::Custom(6))) - } else if multisig_init_lamports < minimum_balance { - assert_eq!(result, Err(ProgramError::Custom(0))) - } else if !((((accounts.len() - 2) as u8) >= 1) && (((accounts.len() - 2) as u8) <= 11)) { - assert_eq!(result, Err(ProgramError::Custom(7))) - } else if !(((instruction_data[0]) >= 1) && ((instruction_data[0]) <= 11)) { - assert_eq!(result, Err(ProgramError::Custom(8))) - } else { - assert!(accounts[2..] - .iter() - .map(|signer| *signer.key) - .eq( - get_multisig(&accounts[0]) - .signers() - .iter() - .take(accounts[2..].len()) - .copied() - ) - ); - assert_eq!(get_multisig(&accounts[0]).m(), instruction_data[0]); - assert_eq!(get_multisig(&accounts[0]).n() as usize, accounts.len() - 2); - assert!(get_multisig(&accounts[0]).is_initialized().is_ok()); - assert!(get_multisig(&accounts[0]).is_initialized().unwrap()); - assert!(result.is_ok()) - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Mint Info -/// accounts[1] // Destination Info -/// accounts[2] // Owner Info -/// accounts[3..14] // Signers -/// instruction_data[0] // Discriminator 14 (Mint To Checked) -/// instruction_data[1..10] // Little Endian Bytes of u64 amount, and decimals -#[inline(never)] -fn test_process_mint_to_checked_multisig( - program_id: &Pubkey, - accounts: &[AccountInfo; 4], - instruction_data: &[u8; 10], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(14 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 9] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_mint(&accounts[0]); - cheatcode_is_spl_account(&accounts[1]); - cheatcode_is_spl_multisig(&accounts[2]); - - //-Initial State----------------------------------------------------------- - let initial_supply = get_mint(&accounts[0]).supply(); - let initial_amount = get_account(&accounts[1]).amount(); - let mint_initialised = get_mint(&accounts[0]).is_initialized(); - let dst_initialised = get_account(&accounts[1]).is_initialized(); - let dst_init_state = get_account(&accounts[1]).account_state(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 9 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if accounts[1].data_len() != Account::LEN { - // TODO Daniel: is it possible for something to be provided that has the same - // len but is not an account? - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if dst_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !dst_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if dst_init_state.unwrap() == AccountState::Frozen { - // unwrap must succeed due to dst_initialised not being err - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if get_account(&accounts[1]).is_native() { - assert_eq!(result, Err(ProgramError::Custom(10))); - return result; - } else if accounts[0].key != &get_account(&accounts[1]).mint() { - assert_eq!(result, Err(ProgramError::Custom(3))); - return result; - } else if accounts[0].data_len() != Mint::LEN { - // Not sure if this is even possible if we get past the case above - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if instruction_data[8] != get_mint(&accounts[0]).decimals() { - assert_eq!(result, Err(ProgramError::Custom(18))); - return result; - } else { - let mint_new = get_mint(&accounts[0]); - if mint_new.mint_authority().is_some() { - // Validate Owner - inner_test_validate_owner( - mint_new.mint_authority().unwrap(), - &accounts[2], - &accounts[3..], - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - } else { - assert_eq!(result, Err(ProgramError::Custom(5))); - return result; - } - - let amount = u64::from_le_bytes([instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7]]); - - if amount == 0 && accounts[0].owner != &crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if amount == 0 && accounts[1].owner != &crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if amount != 0 && amount.checked_add(initial_supply).is_none() { - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } - - assert_eq!(get_mint(&accounts[0]).supply(), initial_supply + amount); - assert_eq!(get_account(&accounts[1]).amount(), initial_amount + amount); - assert!(result.is_ok()); - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Source Info -/// accounts[1] // Mint Info -/// accounts[2] // Authority Info -/// accounts[3..14] // Signers -/// instruction_data[0] // Discriminator 8 (Burn) -/// instruction_data[1..9] // Little Endian Bytes of u64 amount -#[inline(never)] -fn test_process_burn_multisig( - program_id: &Pubkey, - accounts: &[AccountInfo; 4], - instruction_data: &[u8; 9], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(8 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 8] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); - cheatcode_is_spl_mint(&accounts[1]); - cheatcode_is_spl_multisig(&accounts[2]); - - //-Initial State----------------------------------------------------------- - let amount = u64::from_le_bytes([instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7]]); - let src_initialised = get_account(&accounts[0]).is_initialized(); - let src_init_amount = get_account(&accounts[0]).amount(); - let src_init_state = get_account(&accounts[0]).account_state(); - let src_is_native = get_account(&accounts[0]).is_native(); - let src_mint = get_account(&accounts[0]).mint(); - let src_owned_sys_inc = get_account(&accounts[0]).is_owned_by_system_program_or_incinerator(); - let src_owner = get_account(&accounts[0]).owner(); - let old_src_delgate = get_account(&accounts[0]).delegate().cloned(); - let old_src_delgated_amount = get_account(&accounts[0]).delegated_amount(); - let mint_initialised = get_mint(&accounts[1]).is_initialized(); - let mint_init_supply = get_mint(&accounts[1]).supply(); - let mint_owner = *accounts[1].owner; - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); - let tx_signers: &[AccountInfo] = &accounts[3..]; - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 8 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if accounts[1].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_init_state.unwrap() == AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(17))) - } else if src_is_native { - assert_eq!(result, Err(ProgramError::Custom(10))) - } else if src_init_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))) - } else if accounts[1].key != &src_mint { - assert_eq!(result, Err(ProgramError::Custom(3))) - } else { - if !src_owned_sys_inc { - if old_src_delgate == Some(*accounts[2].key) { - inner_test_validate_owner( - old_src_delgate.as_ref().unwrap(), - &accounts[2], - tx_signers, - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - - if old_src_delgated_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))); - return result; - } - } else { - inner_test_validate_owner( - &src_owner, - &accounts[2], - tx_signers, - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - } - } - - if amount == 0 && src_owner != crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else if amount == 0 && mint_owner != crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else { - assert!(get_account(&accounts[0]).amount() == src_init_amount - amount); - assert!(get_mint(&accounts[1]).supply() == mint_init_supply - amount); - assert!(result.is_ok()); - - // Delegate updates - if old_src_delgate.is_some() && *accounts[2].key == old_src_delgate.unwrap() { - assert_eq!(get_account(&accounts[0]).delegated_amount(), old_src_delgated_amount - amount); - if old_src_delgated_amount - amount == 0 { - assert_eq!(get_account(&accounts[0]).delegate(), None); - } - } - } - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Source Info -/// accounts[1] // Destination Info -/// accounts[2] // Authority Info -/// accounts[3..14] // Signers -/// instruction_data[0] // Discriminator 3 (Transfer) -/// instruction_data[1..9] // Little Endian Bytes of u64 amount -#[inline(never)] -fn test_process_transfer( - program_id: &Pubkey, - accounts: &[AccountInfo; 3], - instruction_data: &[u8; 9], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(3 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 8] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); - cheatcode_is_spl_account(&accounts[1]); - cheatcode_is_spl_account(&accounts[2]); - - //-Initial State----------------------------------------------------------- - let amount = u64::from_le_bytes([instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7]]); - let src_initialised = get_account(&accounts[0]).is_initialized(); - let dst_initialised = get_account(&accounts[1]).is_initialized(); - let src_initial_amount = get_account(&accounts[0]).amount(); - let dst_initial_amount = get_account(&accounts[1]).amount(); - let src_initial_lamports = accounts[0].lamports(); - let dst_initial_lamports = accounts[1].lamports(); - let src_owner = get_account(&accounts[0]).owner(); - let old_src_delgate = get_account(&accounts[0]).delegate().cloned(); - let old_src_delgated_amount = get_account(&accounts[0]).delegated_amount(); - let maybe_multisig_is_initialised = None; - - #[cfg(feature = "assumptions")] - // Avoid potential overflow in destination account; assuming total supply stays within u64 - if accounts[0].key != accounts[1].key && dst_initial_amount.checked_add(amount).is_none() { - return Err(ProgramError::Custom(99)); - } - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 8 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if accounts[0].key != accounts[1].key && dst_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if accounts[0].key != accounts[1].key && !dst_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if get_account(&accounts[0]).account_state().unwrap() == AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if accounts[0].key != accounts[1].key && get_account(&accounts[1]).account_state().unwrap() == AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if src_initial_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))); - return result; - } else if accounts[0].key != accounts[1].key && get_account(&accounts[0]).mint() != get_account(&accounts[1]).mint() { - assert_eq!(result, Err(ProgramError::Custom(3))); - return result; - } else { - let tx_signers: &[AccountInfo] = &accounts[3..]; - if old_src_delgate == Some(*accounts[2].key) { - inner_test_validate_owner( - old_src_delgate.as_ref().unwrap(), - &accounts[2], - tx_signers, - maybe_multisig_is_initialised, - result.clone(), - )?; - if old_src_delgated_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))); - return result; - } - } else { - inner_test_validate_owner( - &src_owner, - &accounts[2], - tx_signers, - maybe_multisig_is_initialised, - result.clone(), - )?; - } - - if (accounts[0].key == accounts[1].key || amount == 0) && accounts[0].owner != &crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if (accounts[0].key == accounts[1].key || amount == 0) && accounts[1].owner != &crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if accounts[0].key != accounts[1].key && amount != 0 && get_account(&accounts[0]).is_native() && src_initial_lamports < amount { - // Not sure how to fund native mint - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } else if accounts[0].key != accounts[1].key && amount != 0 && get_account(&accounts[0]).is_native() && dst_initial_lamports.checked_add(amount).is_none() { - // Not sure how to fund native mint - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } - - assert!(result.is_ok()); - - if accounts[0].key != accounts[1].key && amount != 0 { - assert_eq!(get_account(&accounts[0]).amount(), src_initial_amount - amount); - assert_eq!(get_account(&accounts[1]).amount(), dst_initial_amount + amount); - - if get_account(&accounts[0]).is_native() { - assert_eq!(accounts[0].lamports(), src_initial_lamports - amount); - assert_eq!(accounts[1].lamports(), dst_initial_lamports + amount); - } - } - - // Delegate updates - if old_src_delgate == Some(*accounts[2].key) && accounts[0].key != accounts[1].key { - assert_eq!(get_account(&accounts[0]).delegated_amount(), old_src_delgated_amount - amount); - if old_src_delgated_amount - amount == 0 { - assert_eq!(get_account(&accounts[0]).delegate(), None); - } - } - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Source Info -/// accounts[1] // Destination Info -/// accounts[2] // Authority Info -/// accounts[3..14] // Signers -/// instruction_data[0] // Discriminator 3 (Transfer) -/// instruction_data[1..9] // Little Endian Bytes of u64 amount -#[inline(never)] -fn test_process_transfer_multisig( - program_id: &Pubkey, - accounts: &[AccountInfo; 4], - instruction_data: &[u8; 9], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(3 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 8] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); - cheatcode_is_spl_account(&accounts[1]); - cheatcode_is_spl_multisig(&accounts[2]); - - //-Initial State----------------------------------------------------------- - let amount = u64::from_le_bytes([instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7]]); - let src_initialised = get_account(&accounts[0]).is_initialized(); - let dst_initialised = get_account(&accounts[1]).is_initialized(); - let src_initial_amount = get_account(&accounts[0]).amount(); - let dst_initial_amount = get_account(&accounts[1]).amount(); - let src_initial_lamports = accounts[0].lamports(); - let dst_initial_lamports = accounts[1].lamports(); - let src_owner = get_account(&accounts[0]).owner(); - let old_src_delgate = get_account(&accounts[0]).delegate().cloned(); - let old_src_delgated_amount = get_account(&accounts[0]).delegated_amount(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 8 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if accounts[0].key != accounts[1].key && dst_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if accounts[0].key != accounts[1].key && !dst_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if get_account(&accounts[0]).account_state().unwrap() == AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if accounts[0].key != accounts[1].key && get_account(&accounts[1]).account_state().unwrap() == AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if src_initial_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))); - return result; - } else if accounts[0].key != accounts[1].key && get_account(&accounts[0]).mint() != get_account(&accounts[1]).mint() { - assert_eq!(result, Err(ProgramError::Custom(3))); - return result; - } else { - let tx_signers: &[AccountInfo] = &accounts[3..]; - if old_src_delgate == Some(*accounts[2].key) { - inner_test_validate_owner( - old_src_delgate.as_ref().unwrap(), - &accounts[2], - tx_signers, - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - if old_src_delgated_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))); - return result; - } - } else { - inner_test_validate_owner( - &src_owner, - &accounts[2], - tx_signers, - maybe_multisig_is_initialised, - result.clone(), - )?; - } - - if (accounts[0].key == accounts[1].key || amount == 0) && accounts[0].owner != &crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if (accounts[0].key == accounts[1].key || amount == 0) && accounts[1].owner != &crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if accounts[0].key != accounts[1].key && amount != 0 && get_account(&accounts[0]).is_native() && src_initial_lamports < amount { - // Not sure how to fund native mint - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } else if accounts[0].key != accounts[1].key && amount != 0 && get_account(&accounts[0]).is_native() && dst_initial_lamports.checked_add(amount).is_none() { - // Not sure how to fund native mint - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } else if accounts[0].key != accounts[1].key && amount != 0 { - assert_eq!(get_account(&accounts[0]).amount(), src_initial_amount - amount); - assert_eq!(get_account(&accounts[1]).amount(), dst_initial_amount + amount); - - if get_account(&accounts[0]).is_native() { - assert_eq!(accounts[0].lamports(), src_initial_lamports - amount); - assert_eq!(accounts[1].lamports(), dst_initial_lamports + amount); - } - } - - assert!(result.is_ok()); - - // Delegate updates - if old_src_delgate == Some(*accounts[2].key) && accounts[0].key != accounts[1].key { - assert_eq!(get_account(&accounts[0]).delegated_amount(), old_src_delgated_amount - amount); - if old_src_delgated_amount - amount == 0 { - assert_eq!(get_account(&accounts[0]).delegate(), None); - } - } - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Source Info -/// accounts[1] // Mint Info -/// accounts[2] // Destination Info -/// accounts[3] // Authority Info -/// accounts[4..15] // Signers -/// instruction_data[0] // Discriminator 12 (Transfer Checked) -/// instruction_data[1..10] // Little Endian Bytes of u64 amount, and decimals -#[inline(never)] -fn test_process_transfer_checked_multisig( - program_id: &Pubkey, - accounts: &[AccountInfo; 5], - instruction_data: &[u8; 10], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(12 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 9] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); - cheatcode_is_spl_mint(&accounts[1]); - cheatcode_is_spl_account(&accounts[2]); - cheatcode_is_spl_multisig(&accounts[3]); - - //-Initial State----------------------------------------------------------- - let amount = u64::from_le_bytes([instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7]]); - let src_initialised = get_account(&accounts[0]).is_initialized(); - let dst_initialised = get_account(&accounts[2]).is_initialized(); - let src_initial_amount = get_account(&accounts[0]).amount(); - let dst_initial_amount = get_account(&accounts[2]).amount(); - let src_initial_lamports = accounts[0].lamports(); - let dst_initial_lamports = accounts[2].lamports(); - let src_owner = get_account(&accounts[0]).owner(); - let old_src_delgate = get_account(&accounts[0]).delegate().cloned(); - let old_src_delgated_amount = get_account(&accounts[0]).delegated_amount(); - let mint_initialised = get_mint(&accounts[1]).is_initialized(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[3]).is_initialized()); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 9 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if accounts.len() < 4 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if accounts[0].key != accounts[2].key && dst_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if accounts[0].key != accounts[2].key && !dst_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if get_account(&accounts[0]).account_state().unwrap() == AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if accounts[0].key != accounts[2].key - && get_account(&accounts[2]).account_state().unwrap() == AccountState::Frozen - { - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if src_initial_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))); - return result; - } else if accounts[0].key != accounts[2].key - && get_account(&accounts[0]).mint() != get_account(&accounts[2]).mint() - { - assert_eq!(result, Err(ProgramError::Custom(3))); - return result; - } else if accounts[1].key != &get_account(&accounts[0]).mint() { - assert_eq!(result, Err(ProgramError::Custom(3))); - return result; - } else if accounts[1].data_len() != core::mem::size_of::() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if instruction_data[8] != get_mint(&accounts[1]).decimals() { - assert_eq!(result, Err(ProgramError::Custom(18))); - return result; - } else { - let tx_signers: &[AccountInfo] = &accounts[4..]; - if old_src_delgate == Some(*accounts[3].key) { - // Because of the above if, there is a duplicated check in the following - // function Validate Owner - inner_test_validate_owner( - &old_src_delgate.unwrap(), // expected_owner - &accounts[3], // owner_account_info - tx_signers, // tx_signers - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - - if old_src_delgated_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))); - return result; - } - } else { - // Validate Owner - inner_test_validate_owner( - &src_owner, // expected_owner - &accounts[3], // owner_account_info - tx_signers, // tx_signers - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - } - - let src_new = get_account(&accounts[0]); - - if (accounts[0].key == accounts[2].key || amount == 0) - && accounts[0].owner != &crate::id() - { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if (accounts[0].key == accounts[2].key || amount == 0) - && accounts[2].owner != &crate::id() - { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if accounts[0].key != accounts[2].key && amount != 0 { - if src_new.is_native() && src_initial_lamports < amount { - // Not sure how to fund native mint - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } else if src_new.is_native() && dst_initial_lamports.checked_add(amount).is_none() { - // Not sure how to fund native mint - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } - - assert_eq!(src_new.amount(), src_initial_amount - amount); - assert_eq!( - get_account(&accounts[2]).amount(), - dst_initial_amount + amount - ); - - if src_new.is_native() { - assert_eq!(accounts[0].lamports(), src_initial_lamports - amount); - assert_eq!(accounts[1].lamports(), dst_initial_lamports + amount); - } - } - - assert!(result.is_ok()); - // Delegate updates - if old_src_delgate == Some(*accounts[3].key) && accounts[0].key != accounts[2].key { - assert_eq!(src_new.delegated_amount(), old_src_delgated_amount - amount); - if old_src_delgated_amount - amount == 0 { - assert_eq!(src_new.delegate(), None); - } - } - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Source Account Info -/// accounts[1] // Delegate Info -/// accounts[2] // Owner Info -/// accounts[3..14] // Signers -/// instruction_data[0] // Discriminator 4 (Approve) -/// instruction_data[1..9] // Little Endian Bytes of u64 amount -#[inline(never)] -fn test_process_approve( - program_id: &Pubkey, - accounts: &[AccountInfo; 3], - instruction_data: &[u8; 9], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(4 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 8] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); // Source Account - cheatcode_is_spl_account(&accounts[1]); // Delegate - cheatcode_is_spl_account(&accounts[2]); // Owner - - //-Initial State----------------------------------------------------------- - let amount = u64::from_le_bytes([instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7]]); - let src_owner = get_account(&accounts[0]).owner(); - let src_initialised = get_account(&accounts[0]).is_initialized(); - let src_init_state = get_account(&accounts[0]).account_state(); - let maybe_multisig_is_initialised = None; - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 8 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_init_state.unwrap() == AccountState::Frozen { // This should be safe to unwrap due to above check passing - assert_eq!(result, Err(ProgramError::Custom(17))) - } else { - inner_test_validate_owner( - &src_owner, - &accounts[2], - &accounts[3..], - maybe_multisig_is_initialised, - result.clone(), - )?; - - assert_eq!(get_account(&accounts[0]).delegate().unwrap(), accounts[1].key); - assert_eq!(get_account(&accounts[0]).delegated_amount(), amount); - assert!(result.is_ok()) - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Source Account Info -/// accounts[1] // Delegate Info -/// accounts[2] // Owner Info -/// accounts[3..14] // Signers -/// instruction_data[0] // Discriminator 4 (Approve) -/// instruction_data[1..9] // Little Endian Bytes of u64 amount -#[inline(never)] -fn test_process_approve_multisig( - program_id: &Pubkey, - accounts: &[AccountInfo; 4], - instruction_data: &[u8; 9], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(4 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 8] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); // Source Account - cheatcode_is_spl_account(&accounts[1]); // Delegate - cheatcode_is_spl_multisig(&accounts[2]); // Owner - - //-Initial State----------------------------------------------------------- - let amount = u64::from_le_bytes([instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7]]); - let src_owner = get_account(&accounts[0]).owner(); - let src_initialised = get_account(&accounts[0]).is_initialized(); - let src_init_state = get_account(&accounts[0]).account_state(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 8 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_init_state.unwrap() == AccountState::Frozen { // This should be safe to unwrap due to above check passing - assert_eq!(result, Err(ProgramError::Custom(17))) - } else { - inner_test_validate_owner( - &src_owner, - &accounts[2], - &accounts[3..], - maybe_multisig_is_initialised, - result.clone(), - )?; - - assert_eq!(get_account(&accounts[0]).delegate().unwrap(), accounts[1].key); - assert_eq!(get_account(&accounts[0]).delegated_amount(), amount); - assert!(result.is_ok()) - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Source Account Info -/// accounts[1] // Owner Info -/// accounts[2..13] // Signers -/// instruction_data[0] // Discriminator 5 (Revoke) -#[inline(never)] -fn test_process_revoke( - program_id: &Pubkey, - accounts: &[AccountInfo; 2], - instruction_data: &[u8; 1], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(5 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 0] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); // Source Account - cheatcode_is_spl_account(&accounts[1]); // Owner - - //-Initial State----------------------------------------------------------- - let src_initialised = get_account(&accounts[0]).is_initialized(); - let src_init_state = get_account(&accounts[0]).account_state(); - let src_owner = get_account(&accounts[0]).owner(); - let maybe_multisig_is_initialised = None; - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() < 1 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if accounts.len() < 2 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if src_init_state.unwrap() == AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(17))) - } else { - inner_test_validate_owner( - &src_owner, - &accounts[1], - &accounts[2..], - maybe_multisig_is_initialised, - result.clone(), - )?; - - assert!(get_account(&accounts[0]).delegate().is_none()); - assert_eq!(get_account(&accounts[0]).delegated_amount(), 0); - assert!(result.is_ok()) - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Source Account Info -/// accounts[1] // Owner Info -/// accounts[2..13] // Signers -/// instruction_data[0] // Discriminator 5 (Revoke) -#[inline(never)] -fn test_process_revoke_multisig( - program_id: &Pubkey, - accounts: &[AccountInfo; 3], - instruction_data: &[u8; 1], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(5 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 0] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); // Source Account - cheatcode_is_spl_multisig(&accounts[1]); // Owner - - //-Initial State----------------------------------------------------------- - let src_initialised = get_account(&accounts[0]).is_initialized(); - let src_init_state = get_account(&accounts[0]).account_state(); - let src_owner = get_account(&accounts[0]).owner(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[1]).is_initialized()); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() < 1 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if accounts.len() < 2 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if src_init_state.unwrap() == AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(17))) - } else { - inner_test_validate_owner( - &src_owner, - &accounts[1], - &accounts[2..], - maybe_multisig_is_initialised, - result.clone(), - )?; - - assert!(get_account(&accounts[0]).delegate().is_none()); - assert_eq!(get_account(&accounts[0]).delegated_amount(), 0); - assert!(result.is_ok()) - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Account Info - Account Case -/// accounts[1] // Authority Info -/// accounts[2..13] // Signers -/// instruction_data[0] // Discriminator 6 (Set Authority Account) -/// instruction_data[1] // Authority Type (instruction) -/// instruction_data[2] // New Authority Follows (0 -> No, 1 -> Yes) -/// instruction_data[3..35] // New Authority Pubkey -#[inline(never)] -fn test_process_set_authority_account( - program_id: &Pubkey, - accounts: &[AccountInfo; 2], - instruction_data: &[u8; 35], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(6 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 34] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); // Assume Account - cheatcode_is_spl_account(&accounts[1]); // Authority - - //-Initial State----------------------------------------------------------- - let src_initialised = get_account(&accounts[0]).is_initialized(); - let src_init_state = get_account(&accounts[0]).account_state(); - let src_owner = get_account(&accounts[0]).owner(); - let authority = get_account(&accounts[0]) - .close_authority() - .cloned() - .unwrap_or(get_account(&accounts[0]).owner()); - let account_data_len = accounts[0].data_len(); - let maybe_multisig_is_initialised = None; - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 2 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if !(0..=3).contains(&instruction_data[0]) { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if instruction_data[1] != 0 && instruction_data[1] != 1 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if instruction_data[1] == 1 && instruction_data.len() < 34 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if accounts.len() < 2 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if account_data_len != Account::LEN && account_data_len != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidArgument)); - return result; - } else { - assert_eq!(account_data_len, Account::LEN); // established by cheatcode_is_spl_account - if account_data_len == Account::LEN { - if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if src_init_state.unwrap() == AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if instruction_data[0] != 2 && instruction_data[0] != 3 { // AuthorityType neither AccountOwner nor CloseAccount - assert_eq!(result, Err(ProgramError::Custom(15))); - return result; - } else { - if instruction_data[0] == 2 { // AccountOwner - inner_test_validate_owner( - &src_owner, - &accounts[1], - &accounts[2..], - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - - if instruction_data[1] != 1 || instruction_data.len() < 34 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } - - assert_pubkey_from_slice!(get_account(&accounts[0]).owner(), instruction_data[2..34]); - assert_eq!(get_account(&accounts[0]).delegate(), None); - assert_eq!(get_account(&accounts[0]).delegated_amount(), 0); - if get_account(&accounts[0]).is_native() { - assert_eq!(get_account(&accounts[0]).close_authority(), None); - } - assert!(result.is_ok()) - - } else { // Close Account - - inner_test_validate_owner( - &authority, - &accounts[1], - &accounts[2..], - maybe_multisig_is_initialised, - result.clone(), - )?; - - if instruction_data[1] == 1 { // 1 ==> 34 <= instruction_data.len() - assert_pubkey_from_slice!(*get_account(&accounts[0]).close_authority().unwrap(), instruction_data[2..34]); - } else { - assert_eq!(get_account(&accounts[0]).close_authority(), None); - } - assert!(result.is_ok()) - } - } - } - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Account Info - Account Case -/// accounts[1] // Authority Info -/// accounts[2..13] // Signers -/// instruction_data[0] // Discriminator 6 (Set Authority Account) -/// instruction_data[1] // Authority Type (instruction) -/// instruction_data[2] // New Authority Follows (0 -> No, 1 -> Yes) -/// instruction_data[3..35] // New Authority Pubkey -#[inline(never)] -fn test_process_set_authority_account_multisig( - program_id: &Pubkey, - accounts: &[AccountInfo; 3], - instruction_data: &[u8; 35], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(6 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 34] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); // Assume Account - cheatcode_is_spl_multisig(&accounts[1]); // Authority - - //-Initial State----------------------------------------------------------- - let src_initialised = get_account(&accounts[0]).is_initialized(); - let src_init_state = get_account(&accounts[0]).account_state(); - let src_owner = get_account(&accounts[0]).owner(); - let authority = get_account(&accounts[0]) - .close_authority() - .cloned() - .unwrap_or(get_account(&accounts[0]).owner()); - let account_data_len = accounts[0].data_len(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[1]).is_initialized()); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 2 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if !(0..=3).contains(&instruction_data[0]) { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if instruction_data[1] != 0 && instruction_data[1] != 1 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if instruction_data[1] == 1 && instruction_data.len() < 34 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if accounts.len() < 2 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if account_data_len != Account::LEN && account_data_len != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidArgument)); - return result; - } else { - assert_eq!(account_data_len, Account::LEN); // established by cheatcode_is_spl_account - if account_data_len == Account::LEN { - if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if src_init_state.unwrap() == AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if instruction_data[0] != 2 && instruction_data[0] != 3 { // AuthorityType neither AccountOwner nor CloseAccount - assert_eq!(result, Err(ProgramError::Custom(15))); - return result; - } else { - if instruction_data[0] == 2 { // AccountOwner - inner_test_validate_owner( - &src_owner, - &accounts[1], - &accounts[2..], - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - - if instruction_data[1] != 1 || instruction_data.len() < 34 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } - - assert_pubkey_from_slice!(get_account(&accounts[0]).owner(), instruction_data[2..34]); - assert_eq!(get_account(&accounts[0]).delegate(), None); - assert_eq!(get_account(&accounts[0]).delegated_amount(), 0); - if get_account(&accounts[0]).is_native() { - assert_eq!(get_account(&accounts[0]).close_authority(), None); - } - assert!(result.is_ok()) - - } else { // Close Account - - inner_test_validate_owner( - &authority, - &accounts[1], - &accounts[2..], - maybe_multisig_is_initialised, - result.clone(), - )?; - - if instruction_data[1] == 1 { // 1 ==> 34 <= instruction_data.len() - assert_pubkey_from_slice!(*get_account(&accounts[0]).close_authority().unwrap(), instruction_data[2..34]); - } else { - assert_eq!(get_account(&accounts[0]).close_authority(), None); - } - assert!(result.is_ok()) - } - } - } - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Account Info - Mint Case -/// accounts[1] // Authority Info -/// accounts[2..13] // Signers -/// instruction_data[0] // Discriminator 6 (Set Authority Mint) -/// instruction_data[1] // Authority Type (instruction) -/// instruction_data[2] // New Authority Follows (0 -> No, 1 -> Yes) -/// instruction_data[3..35] // New Authority Pubkey -#[inline(never)] -fn test_process_set_authority_mint( - program_id: &Pubkey, - accounts: &[AccountInfo; 2], - instruction_data: &[u8; 35], -) -> ProgramResult { - // Constrain discriminator and program id - unsafe { assume(6 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 34] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_mint(&accounts[0]); // Assume Mint - cheatcode_is_spl_account(&accounts[1]); // Authority - - //-Initial State----------------------------------------------------------- - let mint_data_len = accounts[0].data_len(); - let old_mint_authority_is_none = get_mint(&accounts[0]).mint_authority().is_none(); - let old_freeze_authority_is_none = get_mint(&accounts[0]).freeze_authority().is_none(); - let old_mint_authority = get_mint(&accounts[0]).mint_authority().cloned(); - let old_freeze_authority = get_mint(&accounts[0]).freeze_authority().cloned(); - let maybe_multisig_is_initialised = None; - let mint_is_initialised = get_mint(&accounts[0]).is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 2 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if !(0..=3).contains(&instruction_data[0]) { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if instruction_data[1] != 0 && instruction_data[1] != 1 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if instruction_data[1] == 1 && instruction_data.len() < 34 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if accounts.len() < 2 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if mint_data_len != Account::LEN && mint_data_len != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidArgument)); - return result; - } else { - assert_eq!(mint_data_len, Mint::LEN); // established by cheatcode_is_spl_mint - if !mint_is_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if instruction_data[0] != 0 && instruction_data[0] != 1 { // AuthorityType neither MintTokens nor FreezeAccount - assert_eq!(result, Err(ProgramError::Custom(15))); - return result; - } else { - if instruction_data[0] == 0 { // MintTokens - if old_mint_authority_is_none { - assert_eq!(result, Err(ProgramError::Custom(5))); - return result; - } - - inner_test_validate_owner( - old_mint_authority.as_ref().unwrap(), - &accounts[1], - &accounts[2..], - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - - if instruction_data[1] == 1 { // 1 ==> 34 <= instruction_data.len() - assert_pubkey_from_slice!(*get_mint(&accounts[0]).mint_authority().unwrap(), instruction_data[2..34]); - } else { - assert_eq!(get_mint(&accounts[0]).mint_authority(), None); - } - assert!(result.is_ok()) - - } else { // FreezeAccount - if old_freeze_authority_is_none { - assert_eq!(result, Err(ProgramError::Custom(16))); - return result; - } - inner_test_validate_owner( - old_freeze_authority.as_ref().unwrap(), - &accounts[1], - &accounts[2..], - maybe_multisig_is_initialised, - result.clone(), - )?; - - if instruction_data[1] == 1 { // 1 ==> 34 <= instruction_data.len() - assert_pubkey_from_slice!(*get_mint(&accounts[0]).freeze_authority().unwrap(), instruction_data[2..34]); - } else { - assert_eq!(get_mint(&accounts[0]).freeze_authority(), None); - } - assert!(result.is_ok()) - } - } - - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Account Info - Mint Case -/// accounts[1] // Authority Info -/// accounts[2..13] // Signers -/// instruction_data[0] // Discriminator 6 (Set Authority Mint) -/// instruction_data[1] // Authority Type (instruction) -/// instruction_data[2] // New Authority Follows (0 -> No, 1 -> Yes) -/// instruction_data[3..35] // New Authority Pubkey -#[inline(never)] -fn test_process_set_authority_mint_multisig( - program_id: &Pubkey, - accounts: &[AccountInfo; 3], - instruction_data: &[u8; 35], -) -> ProgramResult { - // Constrain discriminator and program id - unsafe { assume(6 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 34] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_mint(&accounts[0]); // Assume Mint - cheatcode_is_spl_multisig(&accounts[1]); // Authority - - //-Initial State----------------------------------------------------------- - let mint_data_len = accounts[0].data_len(); - let old_mint_authority_is_none = get_mint(&accounts[0]).mint_authority().is_none(); - let old_freeze_authority_is_none = get_mint(&accounts[0]).freeze_authority().is_none(); - let old_mint_authority = get_mint(&accounts[0]).mint_authority().cloned(); - let old_freeze_authority = get_mint(&accounts[0]).freeze_authority().cloned(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[1]).is_initialized()); - let mint_is_initialised = get_mint(&accounts[0]).is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 2 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if !(0..=3).contains(&instruction_data[0]) { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if instruction_data[1] != 0 && instruction_data[1] != 1 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if instruction_data[1] == 1 && instruction_data.len() < 34 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if accounts.len() < 2 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if mint_data_len != Account::LEN && mint_data_len != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidArgument)); - return result; - } else { - assert_eq!(mint_data_len, Mint::LEN); // established by cheatcode_is_spl_mint - if !mint_is_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if instruction_data[0] != 0 && instruction_data[0] != 1 { // AuthorityType neither MintTokens nor FreezeAccount - assert_eq!(result, Err(ProgramError::Custom(15))); - return result; - } else { - if instruction_data[0] == 0 { // MintTokens - if old_mint_authority_is_none { - assert_eq!(result, Err(ProgramError::Custom(5))); - return result; - } - - inner_test_validate_owner( - old_mint_authority.as_ref().unwrap(), - &accounts[1], - &accounts[2..], - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - - if instruction_data[1] == 1 { // 1 ==> 34 <= instruction_data.len() - assert_pubkey_from_slice!(*get_mint(&accounts[0]).mint_authority().unwrap(), instruction_data[2..34]); - } else { - assert_eq!(get_mint(&accounts[0]).mint_authority(), None); - } - assert!(result.is_ok()) - - } else { // FreezeAccount - if old_freeze_authority_is_none { - assert_eq!(result, Err(ProgramError::Custom(16))); - return result; - } - inner_test_validate_owner( - old_freeze_authority.as_ref().unwrap(), - &accounts[1], - &accounts[2..], - maybe_multisig_is_initialised, - result.clone(), - )?; - - if instruction_data[1] == 1 { // 1 ==> 34 <= instruction_data.len() - assert_pubkey_from_slice!(*get_mint(&accounts[0]).freeze_authority().unwrap(), instruction_data[2..34]); - } else { - assert_eq!(get_mint(&accounts[0]).freeze_authority(), None); - } - assert!(result.is_ok()) - } - } - - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Source Account Info -/// accounts[1] // Expected Mint Info -/// accounts[2] // Delegate Info -/// accounts[3] // Owner Info -/// accounts[4..15] // Signers -/// instruction_data[0] // Discriminator 13 (Approve Checked) -/// instruction_data[1..10] // Little Endian Bytes of u64 amount, and decimals -#[inline(never)] -fn test_process_approve_checked_multisig( - program_id: &Pubkey, - accounts: &[AccountInfo; 5], - instruction_data: &[u8; 10], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(13 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 9] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); // Source Account - cheatcode_is_spl_mint(&accounts[1]); // Expected Mint - cheatcode_is_spl_account(&accounts[2]); // Delegate - cheatcode_is_spl_multisig(&accounts[3]); // Owner - - //-Initial State----------------------------------------------------------- - let amount = u64::from_le_bytes([instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7]]); - let src_owner = get_account(&accounts[0]).owner(); - let src_initialised = get_account(&accounts[0]).is_initialized(); - let src_init_state = get_account(&accounts[0]).account_state(); - let mint_initialised = get_mint(&accounts[1]).is_initialized(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[3]).is_initialized()); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 9 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 4 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_init_state.unwrap() == AccountState::Frozen { // This should be safe to unwrap due to above check passing - assert_eq!(result, Err(ProgramError::Custom(17))) - } else if accounts[1].key != &get_account(&accounts[0]).mint() { - assert_eq!(result, Err(ProgramError::Custom(3))) - } else if accounts[1].data_len() != Mint::LEN { - // Not sure if this is even possible if we get past the case above - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if instruction_data[8] != get_mint(&accounts[1]).decimals() { - assert_eq!(result, Err(ProgramError::Custom(18))) - } else { - inner_test_validate_owner( - &src_owner, - &accounts[3], - &accounts[4..], - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - - assert_eq!(get_account(&accounts[0]).delegate().unwrap(), accounts[2].key); - assert_eq!(get_account(&accounts[0]).delegated_amount(), amount); - assert!(result.is_ok()) - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Mint Info -/// accounts[1] // Destination Info -/// accounts[2] // Owner Info -/// accounts[3..14] // Signers -/// instruction_data[0] // Discriminator 7 (Mint To) -/// instruction_data[1..9] // Little Endian Bytes of u64 amount -#[inline(never)] -fn test_process_mint_to( - program_id: &Pubkey, - accounts: &[AccountInfo; 3], - instruction_data: &[u8; 9], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(7 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 8] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_mint(&accounts[0]); - cheatcode_is_spl_account(&accounts[1]); - cheatcode_is_spl_account(&accounts[2]); - - //-Initial State----------------------------------------------------------- - let initial_supply = get_mint(&accounts[0]).supply(); - let initial_amount = get_account(&accounts[1]).amount(); - let mint_initialised = get_mint(&accounts[0]).is_initialized(); - let dst_initialised = get_account(&accounts[1]).is_initialized(); - let dst_init_state = get_account(&accounts[1]).account_state(); - let maybe_multisig_is_initialised = None; - - #[cfg(feature = "assumptions")] - { - // Skip cases that would overflow the destination balance assuming total supply fits in u64 - let amount = - u64::from_le_bytes([instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7]]); - if initial_amount.checked_add(amount).is_none() { - return Err(ProgramError::Custom(99)); - } - } - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 8 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if accounts[1].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if dst_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !dst_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if dst_init_state.unwrap() == AccountState::Frozen { // unwrap must succeed due to dst_initialised not being err - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if get_account(&accounts[1]).is_native() { - assert_eq!(result, Err(ProgramError::Custom(10))); - return result; - } else if accounts[0].key != &get_account(&accounts[1]).mint() { - assert_eq!(result, Err(ProgramError::Custom(3))); - return result; - } else if accounts[0].data_len() != Mint::LEN { - // Not sure if this is even possible if we get past the case above - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else { - if get_mint(&accounts[0]).mint_authority().is_some() { - inner_test_validate_owner( - get_mint(&accounts[0]).mint_authority().unwrap(), - &accounts[2], - &accounts[3..], - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - } else { - assert_eq!(result, Err(ProgramError::Custom(5))); - return result; - } - - let amount = u64::from_le_bytes([instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7]]); - - if amount == 0 && accounts[0].owner != &crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if amount == 0 && accounts[1].owner != &crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if amount != 0 && amount.checked_add(initial_supply).is_none() { - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } - - assert_eq!(get_mint(&accounts[0]).supply(), initial_supply + amount); - assert_eq!(get_account(&accounts[1]).amount(), initial_amount + amount); - assert!(result.is_ok()); - - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Mint Info -/// accounts[1] // Destination Info -/// accounts[2] // Owner Info -/// accounts[3..14] // Signers -/// instruction_data[0] // Discriminator 7 (Mint To) -/// instruction_data[1..9] // Little Endian Bytes of u64 amount -#[inline(never)] -fn test_process_mint_to_multisig( - program_id: &Pubkey, - accounts: &[AccountInfo; 4], - instruction_data: &[u8; 9], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(7 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 8] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_mint(&accounts[0]); - cheatcode_is_spl_account(&accounts[1]); - cheatcode_is_spl_multisig(&accounts[2]); - - //-Initial State----------------------------------------------------------- - let initial_supply = get_mint(&accounts[0]).supply(); - let initial_amount = get_account(&accounts[1]).amount(); - let mint_initialised = get_mint(&accounts[0]).is_initialized(); - let dst_initialised = get_account(&accounts[1]).is_initialized(); - let dst_init_state = get_account(&accounts[1]).account_state(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 8 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if accounts[1].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if dst_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !dst_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if dst_init_state.unwrap() == AccountState::Frozen { // unwrap must succeed due to dst_initialised not being err - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if get_account(&accounts[1]).is_native() { - assert_eq!(result, Err(ProgramError::Custom(10))); - return result; - } else if accounts[0].key != &get_account(&accounts[1]).mint() { - assert_eq!(result, Err(ProgramError::Custom(3))); - return result; - } else if accounts[0].data_len() != Mint::LEN { - // Not sure if this is even possible if we get past the case above - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else { - if get_mint(&accounts[0]).mint_authority().is_some() { - inner_test_validate_owner( - get_mint(&accounts[0]).mint_authority().unwrap(), - &accounts[2], - &accounts[3..], - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - } else { - assert_eq!(result, Err(ProgramError::Custom(5))); - return result; - } - - let amount = u64::from_le_bytes([instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7]]); - - if amount == 0 && accounts[0].owner != &crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if amount == 0 && accounts[1].owner != &crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if amount != 0 && amount.checked_add(initial_supply).is_none() { - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } - - assert_eq!(get_mint(&accounts[0]).supply(), initial_supply + amount); - assert_eq!(get_account(&accounts[1]).amount(), initial_amount + amount); - assert!(result.is_ok()); - - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Source Info -/// accounts[1] // Mint Info -/// accounts[2] // Authority Info -/// accounts[3..14] // Signers -/// instruction_data[0] // Discriminator 8 (Burn) -/// instruction_data[1..9] // Little Endian Bytes of u64 amount -#[inline(never)] -fn test_process_burn( - program_id: &Pubkey, - accounts: &[AccountInfo; 3], - instruction_data: &[u8; 9], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(8 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 8] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); - cheatcode_is_spl_mint(&accounts[1]); - cheatcode_is_spl_account(&accounts[2]); - - //-Initial State----------------------------------------------------------- - let amount = u64::from_le_bytes([instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7]]); - let src_initialised = get_account(&accounts[0]).is_initialized(); - let src_init_amount = get_account(&accounts[0]).amount(); - let src_init_state = get_account(&accounts[0]).account_state(); - let src_is_native = get_account(&accounts[0]).is_native(); - let src_mint = get_account(&accounts[0]).mint(); - let src_owned_sys_inc = get_account(&accounts[0]).is_owned_by_system_program_or_incinerator(); - let src_owner = get_account(&accounts[0]).owner(); - let src_info_owner = accounts[0].owner; - let old_src_delgate = get_account(&accounts[0]).delegate().cloned(); - let old_src_delgated_amount = get_account(&accounts[0]).delegated_amount(); - let mint_initialised = get_mint(&accounts[1]).is_initialized(); - let mint_init_supply = get_mint(&accounts[1]).supply(); - let mint_info_owner = *accounts[1].owner; - let maybe_multisig_is_initialised = None; - let tx_signers: &[AccountInfo] = &accounts[3..]; - - #[cfg(feature = "assumptions")] - // Assume balances stay within u64 so processing cannot overflow - if !(src_init_amount <= mint_init_supply && old_src_delgated_amount <= src_init_amount) { - return Err(ProgramError::Custom(99)); - } - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 8 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if accounts[1].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_init_state.unwrap() == AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(17))) - } else if src_is_native { - assert_eq!(result, Err(ProgramError::Custom(10))) - } else if src_init_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))) - } else if accounts[1].key != &src_mint { - assert_eq!(result, Err(ProgramError::Custom(3))) - } else { - if !src_owned_sys_inc { - if old_src_delgate == Some(*accounts[2].key) { - inner_test_validate_owner( - old_src_delgate.as_ref().unwrap(), - &accounts[2], - tx_signers, - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - - if old_src_delgated_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))); - return result; - } - } else { - inner_test_validate_owner( - &src_owner, - &accounts[2], - tx_signers, - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - } - } - - if amount == 0 && *src_info_owner != crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else if amount == 0 && mint_info_owner != crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else { - let src_new = get_account(&accounts[0]); - assert!(src_new.amount() == src_init_amount - amount); - assert!(get_mint(&accounts[1]).supply() == mint_init_supply - amount); - assert!(result.is_ok()); - - // Delegate updates - let new_src_delegate = src_new.delegate().cloned(); - let new_src_delegated_amount = src_new.delegated_amount(); - if !src_owned_sys_inc - && old_src_delgate.is_some() - && *accounts[2].key == old_src_delgate.unwrap() - { - assert_eq!(new_src_delegated_amount, old_src_delgated_amount - amount); - if old_src_delgated_amount - amount == 0 { - assert_eq!(new_src_delegate, None); - } - } else { - assert_eq!(old_src_delgate, new_src_delegate); - assert_eq!(old_src_delgated_amount, new_src_delegated_amount); - } - } - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Source Info -/// accounts[1] // Destination Info -/// accounts[2] // Authority Info -/// accounts[3..14] // Multisig Signers -/// instruction_data[0] // Discriminator 9 (Close Account) -#[inline(never)] -fn test_process_close_account( - program_id: &Pubkey, - accounts: &[AccountInfo; 3], - instruction_data: &[u8; 1], -) -> ProgramResult { - use solana_sdk_ids::incinerator::ID as INCINERATOR_ID; - - // Constrain discriminator and program id - unsafe { assume(9 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 0] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); - cheatcode_is_spl_account(&accounts[1]); - cheatcode_is_spl_account(&accounts[2]); - - //-Initial State----------------------------------------------------------- - let src_initialised = get_account(&accounts[0]).is_initialized(); - let src_data_len = accounts[0].data_len(); - let src_init_amount = get_account(&accounts[0]).amount(); - let src_init_lamports = accounts[0].lamports(); - let dst_init_lamports = accounts[1].lamports(); - let src_is_native = get_account(&accounts[0]).is_native(); - let src_owned_sys_inc = get_account(&accounts[0]).is_owned_by_system_program_or_incinerator(); - let authority = get_account(&accounts[0]).close_authority().cloned().unwrap_or(get_account(&accounts[0]).owner()); - let maybe_multisig_is_initialised = None; - let tx_signers: &[AccountInfo] = &accounts[3..]; - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if accounts[0].key == accounts[1].key { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if src_data_len != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if !src_is_native && src_init_amount != 0 { - assert_eq!(result, Err(ProgramError::Custom(11))); - return result; - } else { - if !src_owned_sys_inc { - inner_test_validate_owner( - &authority, - &accounts[2], - tx_signers, - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - } else if accounts[1].key != &INCINERATOR_ID { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } - if dst_init_lamports.checked_add(src_init_lamports).is_none() { - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } - assert!(result.is_ok()); - - // Validate owner falls through to here if no error - assert_eq!( - accounts[1].lamports(), - dst_init_lamports + src_init_lamports - ); - #[cfg(any(target_os = "solana", target_arch = "bpf"))] - { - // Solana-RT only syscall - assert_eq!(*accounts[0].owner, Pubkey::from([0; 32])); - assert_eq!(accounts[0].lamports(), 0); - assert_eq!(accounts[0].data_len(), 0); - } - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Source Info -/// accounts[1] // Destination Info -/// accounts[2] // Authority Info -/// accounts[3..14] // Multisig Signers -/// instruction_data[0] // Discriminator 9 (Close Account) -#[inline(never)] -fn test_process_close_account_multisig( - program_id: &Pubkey, - accounts: &[AccountInfo; 4], - instruction_data: &[u8; 1], -) -> ProgramResult { - use solana_sdk_ids::incinerator::ID as INCINERATOR_ID; - - // Constrain discriminator and program id - unsafe { assume(9 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 0] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); - cheatcode_is_spl_account(&accounts[1]); - cheatcode_is_spl_multisig(&accounts[2]); - - //-Initial State----------------------------------------------------------- - let src_initialised = get_account(&accounts[0]).is_initialized(); - let src_data_len = accounts[0].data_len(); - let src_init_amount = get_account(&accounts[0]).amount(); - let src_init_lamports = accounts[0].lamports(); - let dst_init_lamports = accounts[1].lamports(); - let src_is_native = get_account(&accounts[0]).is_native(); - let src_owned_sys_inc = get_account(&accounts[0]).is_owned_by_system_program_or_incinerator(); - let authority = get_account(&accounts[0]).close_authority().cloned().unwrap_or(get_account(&accounts[0]).owner()); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); - let tx_signers: &[AccountInfo] = &accounts[3..]; - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if accounts[0].key == accounts[1].key { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if src_data_len != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if !src_is_native && src_init_amount != 0 { - assert_eq!(result, Err(ProgramError::Custom(11))); - return result; - } else { - if !src_owned_sys_inc { - inner_test_validate_owner( - &authority, - &accounts[2], - tx_signers, - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - } else if accounts[1].key != &INCINERATOR_ID { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if dst_init_lamports.checked_add(src_init_lamports).is_none() { - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } - - // Validate owner falls through to here if no error - assert_eq!(accounts[1].lamports(), dst_init_lamports + src_init_lamports); - assert_eq!(accounts[0].lamports(), 0); - assert_eq!(accounts[0].data_len(), 0); // TODO: More sol_memset stuff? - assert!(result.is_ok()); - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Source Account Info -/// accounts[1] // Mint Info -/// accounts[2] // Authority Info -/// accounts[3..13] // Signers -/// instruction_data[0] // Discriminator 10 (Freeze Account) -#[inline(never)] -fn test_process_freeze_account_multisig( - program_id: &Pubkey, - accounts: &[AccountInfo; 4], - instruction_data: &[u8; 1], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(10 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 0] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); - cheatcode_is_spl_mint(&accounts[1]); - cheatcode_is_spl_multisig(&accounts[2]); - - //-Initial State----------------------------------------------------------- - let src_initialised = get_account(&accounts[0]).is_initialized(); - let src_init_state = get_account(&accounts[0]).account_state(); - let src_is_native = get_account(&accounts[0]).is_native(); - let src_mint = get_account(&accounts[0]).mint(); - let mint_initialised = get_mint(&accounts[1]).is_initialized(); - let mint_freeze_auth = get_mint(&accounts[1]).freeze_authority().cloned(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_init_state.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_init_state.unwrap() == AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(13))) - } else if src_is_native { - assert_eq!(result, Err(ProgramError::Custom(10))) - } else if accounts[1].key != &src_mint { - assert_eq!(result, Err(ProgramError::Custom(3))) - } else if accounts[1].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if mint_freeze_auth.is_none() { - assert_eq!(result, Err(ProgramError::Custom(16))) - } else { - inner_test_validate_owner( - mint_freeze_auth.as_ref().unwrap(), - &accounts[2], - &accounts[3..], - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - - assert_eq!(get_account(&accounts[0]).account_state().unwrap(), AccountState::Frozen); - assert!(result.is_ok()) - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Source Account Info -/// accounts[1] // Mint Info -/// accounts[2] // Authority Info -/// accounts[3..13] // Signers -/// instruction_data[0] // Discriminator 10 (Freeze Account) -#[inline(never)] -fn test_process_freeze_account( - program_id: &Pubkey, - accounts: &[AccountInfo; 3], - instruction_data: &[u8; 1], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(10 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 0] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); - cheatcode_is_spl_mint(&accounts[1]); - cheatcode_is_spl_account(&accounts[2]); - - //-Initial State----------------------------------------------------------- - let src_initialised = get_account(&accounts[0]).is_initialized(); - let src_init_state = get_account(&accounts[0]).account_state(); - let src_is_native = get_account(&accounts[0]).is_native(); - let src_mint = get_account(&accounts[0]).mint(); - let mint_initialised = get_mint(&accounts[1]).is_initialized(); - let mint_freeze_auth = get_mint(&accounts[1]).freeze_authority().cloned(); - let maybe_multisig_is_initialised = None; - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_init_state.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_init_state.unwrap() == AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(13))) - } else if src_is_native { - assert_eq!(result, Err(ProgramError::Custom(10))) - } else if accounts[1].key != &src_mint { - assert_eq!(result, Err(ProgramError::Custom(3))) - } else if accounts[1].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if mint_freeze_auth.is_none() { - assert_eq!(result, Err(ProgramError::Custom(16))) - } else { - inner_test_validate_owner( - mint_freeze_auth.as_ref().unwrap(), - &accounts[2], - &accounts[3..], - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - - assert_eq!(get_account(&accounts[0]).account_state().unwrap(), AccountState::Frozen); - assert!(result.is_ok()) - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Source Account Info -/// accounts[1] // Mint Info -/// accounts[2] // Authority Info -/// accounts[3..13] // Signers -/// instruction_data[0] // Discriminator 11 (Thaw Account) -#[inline(never)] -fn test_process_thaw_account( - program_id: &Pubkey, - accounts: &[AccountInfo; 3], - instruction_data: &[u8; 1], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(11 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 0] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); - cheatcode_is_spl_mint(&accounts[1]); - cheatcode_is_spl_account(&accounts[2]); - - //-Initial State----------------------------------------------------------- - let src_initialised = get_account(&accounts[0]).is_initialized(); - let src_init_state = get_account(&accounts[0]).account_state(); - let src_is_native = get_account(&accounts[0]).is_native(); - let src_mint = get_account(&accounts[0]).mint(); - let mint_initialised = get_mint(&accounts[1]).is_initialized(); - let mint_freeze_auth = get_mint(&accounts[1]).freeze_authority().cloned(); - let maybe_multisig_is_initialised = None; - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_init_state.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_init_state.unwrap() != AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(13))) - } else if src_is_native { - assert_eq!(result, Err(ProgramError::Custom(10))) - } else if accounts[1].key != &src_mint { - assert_eq!(result, Err(ProgramError::Custom(3))) - } else if accounts[1].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if mint_freeze_auth.is_none() { - assert_eq!(result, Err(ProgramError::Custom(16))) - } else { - inner_test_validate_owner( - mint_freeze_auth.as_ref().unwrap(), - &accounts[2], - &accounts[3..], - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - - assert_eq!(get_account(&accounts[0]).account_state().unwrap(), AccountState::Initialized); - assert!(result.is_ok()) - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Source Account Info -/// accounts[1] // Mint Info -/// accounts[2] // Authority Info -/// accounts[3..13] // Signers -/// instruction_data[0] // Discriminator 11 (Thaw Account) -#[inline(never)] -fn test_process_thaw_account_multisig( - program_id: &Pubkey, - accounts: &[AccountInfo; 4], - instruction_data: &[u8; 1], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(11 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 0] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); - cheatcode_is_spl_mint(&accounts[1]); - cheatcode_is_spl_multisig(&accounts[2]); - - //-Initial State----------------------------------------------------------- - let src_initialised = get_account(&accounts[0]).is_initialized(); - let src_init_state = get_account(&accounts[0]).account_state(); - let src_is_native = get_account(&accounts[0]).is_native(); - let src_mint = get_account(&accounts[0]).mint(); - let mint_initialised = get_mint(&accounts[1]).is_initialized(); - let mint_freeze_auth = get_mint(&accounts[1]).freeze_authority().cloned(); - let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_init_state.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_init_state.unwrap() != AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(13))) - } else if src_is_native { - assert_eq!(result, Err(ProgramError::Custom(10))) - } else if accounts[1].key != &src_mint { - assert_eq!(result, Err(ProgramError::Custom(3))) - } else if accounts[1].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if mint_freeze_auth.is_none() { - assert_eq!(result, Err(ProgramError::Custom(16))) - } else { - inner_test_validate_owner( - mint_freeze_auth.as_ref().unwrap(), - &accounts[2], - &accounts[3..], - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - - assert_eq!(get_account(&accounts[0]).account_state().unwrap(), AccountState::Initialized); - assert!(result.is_ok()) - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Source Info -/// accounts[1] // Mint Info -/// accounts[2] // Destination Info -/// accounts[3] // Authority Info -/// accounts[4..15] // Signers -/// instruction_data[0] // Discriminator 12 (Transfer Checked) -/// instruction_data[1..10] // Little Endian Bytes of u64 amount, and decimals -#[inline(never)] -fn test_process_transfer_checked( - program_id: &Pubkey, - accounts: &[AccountInfo; 4], - instruction_data: &[u8; 10], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(12 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 9] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); - cheatcode_is_spl_mint(&accounts[1]); - cheatcode_is_spl_account(&accounts[2]); - cheatcode_is_spl_account(&accounts[3]); - - //-Initial State----------------------------------------------------------- - let amount = u64::from_le_bytes([instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7]]); - let src_initialised = get_account(&accounts[0]).is_initialized(); - let dst_initialised = get_account(&accounts[2]).is_initialized(); - let src_initial_amount = get_account(&accounts[0]).amount(); - let dst_initial_amount = get_account(&accounts[2]).amount(); - let src_initial_lamports = accounts[0].lamports(); - let dst_initial_lamports = accounts[2].lamports(); - let src_owner = get_account(&accounts[0]).owner(); - let old_src_delgate = get_account(&accounts[0]).delegate().cloned(); - let old_src_delgated_amount = get_account(&accounts[0]).delegated_amount(); - let mint_initialised = get_mint(&accounts[1]).is_initialized(); - let maybe_multisig_is_initialised = None; - let tx_signers: &[AccountInfo] = if accounts.len() > 4 { - &accounts[4..] - } else { - &[] - }; - - #[cfg(feature = "assumptions")] - // Avoid potential overflow in destination account; assuming total supply stays within u64 - if accounts[0].key != accounts[2].key && dst_initial_amount.checked_add(amount).is_none() { - return Err(ProgramError::Custom(99)); - } - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 9 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if accounts.len() < 4 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if accounts[0].key != accounts[2].key && dst_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if accounts[0].key != accounts[2].key && !dst_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if get_account(&accounts[0]).account_state().unwrap() == AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if accounts[0].key != accounts[2].key && get_account(&accounts[2]).account_state().unwrap() == AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if src_initial_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))); - return result; - } else if accounts[0].key != accounts[2].key && get_account(&accounts[0]).mint() != get_account(&accounts[2]).mint() { - assert_eq!(result, Err(ProgramError::Custom(3))); - return result; - } else if accounts[1].key != &get_account(&accounts[0]).mint() { - assert_eq!(result, Err(ProgramError::Custom(3))); - return result; - } else if accounts[1].data_len() != core::mem::size_of::() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if instruction_data[8] != get_mint(&accounts[1]).decimals() { - assert_eq!(result, Err(ProgramError::Custom(18))); - return result; - } else { - if old_src_delgate == Some(*accounts[3].key) { - inner_test_validate_owner( - old_src_delgate.as_ref().unwrap(), - &accounts[3], - tx_signers, - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - - if old_src_delgated_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))); - return result; - } - } else { - inner_test_validate_owner( - &src_owner, - &accounts[3], - tx_signers, - maybe_multisig_is_initialised, - result.clone(), - )?; - } - - if (accounts[0].key == accounts[2].key || amount == 0) && accounts[0].owner != &crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if (accounts[0].key == accounts[2].key || amount == 0) && accounts[2].owner != &crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } - - if accounts[0].key != accounts[2].key && amount != 0 { - if get_account(&accounts[0]).is_native() && src_initial_lamports < amount { - // Not sure how to fund native mint - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } else if get_account(&accounts[0]).is_native() - && dst_initial_lamports.checked_add(amount).is_none() - { - // Not sure how to fund native mint - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } - - assert_eq!(get_account(&accounts[0]).amount(), src_initial_amount - amount); - assert_eq!(get_account(&accounts[2]).amount(), dst_initial_amount + amount); - - if get_account(&accounts[0]).is_native() { - assert_eq!(accounts[0].lamports(), src_initial_lamports - amount); - assert_eq!(accounts[2].lamports(), dst_initial_lamports + amount); - } - } - assert!(result.is_ok()); - - // Delegate updates - if old_src_delgate == Some(*accounts[3].key) && accounts[0].key != accounts[2].key { - assert_eq!(get_account(&accounts[0]).delegated_amount(), old_src_delgated_amount - amount); - if old_src_delgated_amount - amount == 0 { - assert_eq!(get_account(&accounts[0]).delegate(), None); - } - } - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Source Account Info -/// accounts[1] // Expected Mint Info -/// accounts[2] // Delegate Info -/// accounts[3] // Owner Info -/// accounts[4..15] // Signers -/// instruction_data[0] // Discriminator 13 (Approve Checked) -/// instruction_data[1..10] // Little Endian Bytes of u64 amount, and decimals -#[inline(never)] -fn test_process_approve_checked( - program_id: &Pubkey, - accounts: &[AccountInfo; 4], - instruction_data: &[u8; 10], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(13 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 9] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); // Source Account - cheatcode_is_spl_mint(&accounts[1]); // Expected Mint - cheatcode_is_spl_account(&accounts[2]); // Delegate - cheatcode_is_spl_account(&accounts[3]); // Owner - - //-Initial State----------------------------------------------------------- - let amount = u64::from_le_bytes([instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7]]); - let src_owner = get_account(&accounts[0]).owner(); - let src_initialised = get_account(&accounts[0]).is_initialized(); - let src_init_state = get_account(&accounts[0]).account_state(); - let mint_initialised = get_mint(&accounts[1]).is_initialized(); - let maybe_multisig_is_initialised = None; - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 9 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 4 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_init_state.unwrap() == AccountState::Frozen { // This should be safe to unwrap due to above check passing - assert_eq!(result, Err(ProgramError::Custom(17))) - } else if accounts[1].key != &get_account(&accounts[0]).mint() { - assert_eq!(result, Err(ProgramError::Custom(3))) - } else if accounts[1].data_len() != Mint::LEN { - // Not sure if this is even possible if we get past the case above - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if instruction_data[8] != get_mint(&accounts[1]).decimals() { - assert_eq!(result, Err(ProgramError::Custom(18))) - } else { - inner_test_validate_owner( - &src_owner, - &accounts[3], - &accounts[4..], - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - - assert_eq!(get_account(&accounts[0]).delegate().unwrap(), accounts[2].key); - assert_eq!(get_account(&accounts[0]).delegated_amount(), amount); - assert!(result.is_ok()) - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Mint Info -/// accounts[1] // Destination Info -/// accounts[2] // Owner Info -/// accounts[3..14] // Signers -/// instruction_data[0] // Discriminator 14 (Mint To Checked) -/// instruction_data[1..10] // Little Endian Bytes of u64 amount, and decimals -#[inline(never)] -fn test_process_mint_to_checked( - program_id: &Pubkey, - accounts: &[AccountInfo; 3], - instruction_data: &[u8; 10], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(14 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 9] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_mint(&accounts[0]); - cheatcode_is_spl_account(&accounts[1]); - cheatcode_is_spl_account(&accounts[2]); - - //-Initial State----------------------------------------------------------- - let initial_supply = get_mint(&accounts[0]).supply(); - let initial_amount = get_account(&accounts[1]).amount(); - let mint_initialised = get_mint(&accounts[0]).is_initialized(); - let dst_initialised = get_account(&accounts[1]).is_initialized(); - let dst_init_state = get_account(&accounts[1]).account_state(); - let maybe_multisig_is_initialised = None; - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 9 { - assert_eq!(result, Err(ProgramError::Custom(12))); - return result; - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - return result; - } else if accounts[1].data_len() != Account::LEN { // TODO Daniel: is it possible for something to be provided that has the same len but is not an account? - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if dst_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !dst_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if dst_init_state.unwrap() == AccountState::Frozen { // unwrap must succeed due to dst_initialised not being err - assert_eq!(result, Err(ProgramError::Custom(17))); - return result; - } else if get_account(&accounts[1]).is_native() { - assert_eq!(result, Err(ProgramError::Custom(10))); - return result; - } else if accounts[0].key != &get_account(&accounts[1]).mint() { - assert_eq!(result, Err(ProgramError::Custom(3))); - return result; - } else if accounts[0].data_len() != Mint::LEN { - // Not sure if this is even possible if we get past the case above - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)); - return result; - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); - return result; - } else if instruction_data[8] != get_mint(&accounts[0]).decimals() { - assert_eq!(result, Err(ProgramError::Custom(18))); - return result; - } else { - if get_mint(&accounts[0]).mint_authority().is_some() { - inner_test_validate_owner( - get_mint(&accounts[0]).mint_authority().unwrap(), - &accounts[2], - &accounts[3..], - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - } else { - assert_eq!(result, Err(ProgramError::Custom(5))); - return result; - } - - let amount = u64::from_le_bytes([instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7]]); - - if amount == 0 && accounts[0].owner != &crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if amount == 0 && accounts[1].owner != &crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)); - return result; - } else if amount != 0 && amount.checked_add(initial_supply).is_none() { - assert_eq!(result, Err(ProgramError::Custom(14))); - return result; - } - - assert_eq!(get_mint(&accounts[0]).supply(), initial_supply + amount); - assert_eq!(get_account(&accounts[1]).amount(), initial_amount + amount); - assert!(result.is_ok()); - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Source Info -/// accounts[1] // Mint Info -/// accounts[2] // Authority Info -/// accounts[3..14] // Signers -/// instruction_data[0] // Discriminator 15 (Burn Checked) -/// instruction_data[1..10] // Little Endian Bytes of u64 amount, and decimals -#[inline(never)] -fn test_process_burn_checked( - program_id: &Pubkey, - accounts: &[AccountInfo; 3], - instruction_data: &[u8; 10], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(15 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 9] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); - cheatcode_is_spl_mint(&accounts[1]); - cheatcode_is_spl_account(&accounts[2]); - - //-Initial State----------------------------------------------------------- - let amount = u64::from_le_bytes([instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7]]); - let src_initialised = get_account(&accounts[0]).is_initialized(); - let src_init_amount = get_account(&accounts[0]).amount(); - let src_init_state = get_account(&accounts[0]).account_state(); - let src_is_native = get_account(&accounts[0]).is_native(); - let src_mint = get_account(&accounts[0]).mint(); - let src_owned_sys_inc = get_account(&accounts[0]).is_owned_by_system_program_or_incinerator(); - let src_owner = get_account(&accounts[0]).owner(); - let src_info_owner = accounts[0].owner; - let old_src_delgate = get_account(&accounts[0]).delegate().cloned(); - let old_src_delgated_amount = get_account(&accounts[0]).delegated_amount(); - let mint_initialised = get_mint(&accounts[1]).is_initialized(); - let mint_init_supply = get_mint(&accounts[1]).supply(); - let mint_decimals = get_mint(&accounts[1]).decimals(); - let mint_info_owner = *accounts[1].owner; - let maybe_multisig_is_initialised = None; - let tx_signers: &[AccountInfo] = &accounts[3..]; - - #[cfg(feature = "assumptions")] - // Assume balances stay within u64 so processing cannot overflow - if !(src_init_amount <= mint_init_supply && old_src_delgated_amount <= src_init_amount) { - return Err(ProgramError::Custom(99)); - } - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 9 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if accounts[1].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_init_state.unwrap() == AccountState::Frozen { - assert_eq!(result, Err(ProgramError::Custom(17))) - } else if src_is_native { - assert_eq!(result, Err(ProgramError::Custom(10))) - } else if src_init_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))) - } else if accounts[1].key != &src_mint { - assert_eq!(result, Err(ProgramError::Custom(3))) - } else if instruction_data[8] != mint_decimals { - assert_eq!(result, Err(ProgramError::Custom(18))) - } else { - if !src_owned_sys_inc { - if old_src_delgate == Some(*accounts[2].key) { - inner_test_validate_owner( - old_src_delgate.as_ref().unwrap(), - &accounts[2], - tx_signers, - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - - if old_src_delgated_amount < amount { - assert_eq!(result, Err(ProgramError::Custom(1))); - return result; - } - } else { - inner_test_validate_owner( - &src_owner, - &accounts[2], - tx_signers, - maybe_multisig_is_initialised.clone(), - result.clone(), - )?; - } - } - - if amount == 0 && *src_info_owner != crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else if amount == 0 && mint_info_owner != crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else { - let src_new = get_account(&accounts[0]); - assert!(src_new.amount() == src_init_amount - amount); - assert!(get_mint(&accounts[1]).supply() == mint_init_supply - amount); - assert!(result.is_ok()); - - // Delegate updates - let new_src_delegate = src_new.delegate().cloned(); - let new_src_delegated_amount = src_new.delegated_amount(); - if !src_owned_sys_inc - && old_src_delgate.is_some() - && *accounts[2].key == old_src_delgate.unwrap() - { - assert_eq!(new_src_delegated_amount, old_src_delgated_amount - amount); - if old_src_delgated_amount - amount == 0 { - assert_eq!(new_src_delegate, None); - } - } else { - assert_eq!(old_src_delgate, new_src_delegate); - assert_eq!(old_src_delgated_amount, new_src_delegated_amount); - } - } - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// Unified harness for WithdrawExcessLamports instruction (discriminator 38). -/// -/// This harness handles all source account types (Account, Mint, Multisig) and -/// authority types (single signer or multisig) in one function. -/// -/// program_id // Token Program ID -/// accounts[0] // Source Account Info (Account, Mint, or Multisig) -/// accounts[1] // Destination Info -/// accounts[2] // Authority Info -/// accounts[3..] // Signers (for multisig authority) -/// instruction_data[0] // Discriminator 38 (Withdraw Excess Lamports) -#[inline(never)] -fn test_process_withdraw_excess_lamports( - program_id: &Pubkey, - accounts: &[AccountInfo; 4], - instruction_data: &[u8; 1], -) -> ProgramResult { - // Constrain discriminator and program id - unsafe { assume(38 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - let instruction_data_with_discriminator = instruction_data; - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - // NOTE: WithdrawExcessLamports (discriminator 38) is a token-2022 instruction that does not - // exist in the original spl-token program. The original spl-token's TokenInstruction only - // supports discriminators 0-24. When Processor::process receives discriminator 38, it fails - // at TokenInstruction::unpack() with InvalidInstruction error. - assert_eq!(result, Err(TokenError::InvalidInstruction.into())); - result -} - -/// program_id // Token Program ID -/// accounts[0] // New Account Info -/// accounts[1] // Mint Info -/// accounts[2] // Rent Sysvar Info -/// instruction_data[0] // Discriminator 16 (Initialize Account2) -/// instruction_data[1..] // Owner -#[inline(never)] -fn test_process_initialize_account2( - program_id: &Pubkey, - accounts: &[AccountInfo; 3], - instruction_data: &[u8; 33], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(16 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 32] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); - cheatcode_is_spl_mint(&accounts[1]); - cheatcode_is_spl_rent(&accounts[2]); - - //-Initial State----------------------------------------------------------- - let initial_state_new_account = get_account(&accounts[0]) - .account_state(); - - let minimum_balance = get_rent(&accounts[2]).minimum_balance(accounts[0].data_len()); - - let is_native_mint = accounts[1].key == &native_mint::ID; - - let mint_is_initialised = get_mint(&accounts[1]).is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < pubkey::PUBKEY_BYTES { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 3 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - } else if accounts[2].key != &solana_sysvar::rent::ID { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if initial_state_new_account.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if initial_state_new_account.unwrap() != AccountState::Uninitialized { - assert_eq!(result, Err(ProgramError::Custom(6))) - } else if accounts[0].lamports() < minimum_balance { - assert_eq!(result, Err(ProgramError::Custom(0))) - } else if !is_native_mint && accounts[1].owner != &crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else if !is_native_mint - && accounts[1].owner == &crate::id() - && mint_is_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !is_native_mint - && accounts[1].owner == &crate::id() - && !mint_is_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::Custom(2))) - } else { - assert!(result.is_ok()); - assert_eq!(get_account(&accounts[0]).account_state().unwrap(), AccountState::Initialized); - assert_eq!(get_account(&accounts[0]).mint(), *accounts[1].key); - assert_eq!(get_account(&accounts[0]).owner(), (*instruction_data).into()); - - if is_native_mint { - assert!(get_account(&accounts[0]).is_native()); - assert_eq!(get_account(&accounts[0]).native_amount().unwrap(), minimum_balance); - assert_eq!(get_account(&accounts[0]).amount(), accounts[0].lamports() - minimum_balance); - } - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -#[inline(never)] -fn test_process_sync_native( - program_id: &Pubkey, - accounts: &[AccountInfo; 1], - instruction_data: &[u8; 1], -) -> ProgramResult { - // Constrain discriminator and program id - unsafe { assume(17 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 0] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); - - //-Initial State----------------------------------------------------------- - let src_owner = accounts[0].owner; - let src_initialised = get_account(&accounts[0]).is_initialized(); - let src_native_amount = get_account(&accounts[0]).native_amount(); - let src_init_lamports = accounts[0].lamports(); - let src_init_amount = get_account(&accounts[0]).amount(); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() != 1 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if src_owner != &crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)) - } else if src_native_amount.is_none() { - assert_eq!(result, Err(ProgramError::Custom(19))) - } else if src_init_lamports < src_native_amount.unwrap() { - assert_eq!(result, Err(ProgramError::Custom(14))) - } else if src_init_lamports - src_native_amount.unwrap() < src_init_amount { - assert_eq!(result, Err(ProgramError::Custom(13))) - } else { - assert_eq!(get_account(&accounts[0]).amount(), src_init_lamports - src_native_amount.unwrap()); - assert!(result.is_ok()) - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // New Account Info -/// accounts[1] // Mint Info -/// instruction_data[0] // Discriminator 18 (Initialize Account3) -/// instruction_data[1..] // Owner -#[inline(never)] -fn test_process_initialize_account3( - program_id: &Pubkey, - accounts: &[AccountInfo; 2], - instruction_data: &[u8; 33], -) -> ProgramResult { - use spl_token_interface::state::AccountState; - - // Constrain discriminator and program id - unsafe { assume(18 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 32] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); - cheatcode_is_spl_mint(&accounts[1]); - - //-Initial State----------------------------------------------------------- - let initial_state_new_account = get_account(&accounts[0]) - .account_state(); - - // Note: Rent is a supported sysvar so ProgramError::UnsupportedSysvar should be impossible - let rent = solana_rent::Rent::get().unwrap(); - let minimum_balance = rent.minimum_balance(accounts[0].data_len()); - - let is_native_mint = accounts[1].key == &native_mint::ID; - - let mint_is_initialised = get_mint(&accounts[1]).is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < pubkey::PUBKEY_BYTES { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 2 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if initial_state_new_account.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if initial_state_new_account.unwrap() != AccountState::Uninitialized { - assert_eq!(result, Err(ProgramError::Custom(6))) - } else if accounts[0].lamports() < minimum_balance { - assert_eq!(result, Err(ProgramError::Custom(0))) - } else if !is_native_mint && accounts[1].owner != &crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else if !is_native_mint - && accounts[1].owner == &crate::id() - && mint_is_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !is_native_mint - && accounts[1].owner == &crate::id() - && !mint_is_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::Custom(2))) - } else { - assert!(result.is_ok()); - assert_eq!(get_account(&accounts[0]).account_state().unwrap(), AccountState::Initialized); - assert_eq!(get_account(&accounts[0]).mint(), *accounts[1].key); - assert_eq!(get_account(&accounts[0]).owner(), (*instruction_data).into()); - - if is_native_mint { - assert!(get_account(&accounts[0]).is_native()); - assert_eq!(get_account(&accounts[0]).native_amount().unwrap(), minimum_balance); - assert_eq!(get_account(&accounts[0]).amount(), accounts[0].lamports() - minimum_balance); - } - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Multisig Info -/// accounts[1..] // Signers -/// accounts[1..].len() // n -/// instruction_data[0] // Discriminator 19 (Initialize Multisig2) -/// instruction_data[2] // m -#[inline(never)] -fn test_process_initialize_multisig2( - program_id: &Pubkey, - accounts: &[AccountInfo; 4], - instruction_data: &[u8; 2], -) -> ProgramResult { - // Constrain discriminator and program id - unsafe { assume(19 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 1] = instruction_data.last_chunk().unwrap(); - - // ^ FIXME: totally arbitrary for the tests - cheatcode_is_spl_multisig(&accounts[0]); - cheatcode_is_spl_account(&accounts[1]); // Signer - cheatcode_is_spl_account(&accounts[2]); // Signer - cheatcode_is_spl_account(&accounts[3]); // Signer - - //-Initial State----------------------------------------------------------- - let multisig_already_initialised = get_multisig(&accounts[0]).is_initialized(); - let multisig_init_lamports = accounts[0].lamports(); - // Note: Rent is a supported sysvar so ProgramError::UnsupportedSysvar should be impossible - let rent = solana_rent::Rent::get().unwrap(); - let minimum_balance = rent.minimum_balance(accounts[0].data_len()); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.is_empty() { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 1 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Multisig::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if multisig_already_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if multisig_already_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::Custom(6))) - } else if multisig_init_lamports < minimum_balance { - assert_eq!(result, Err(ProgramError::Custom(0))) - } else if !((((accounts.len() - 1) as u8) >= 1) && (((accounts.len() - 1) as u8) <= 11)) { - assert_eq!(result, Err(ProgramError::Custom(7))) - } else if !(((instruction_data[0]) >= 1) && ((instruction_data[0]) <= 11)) { - assert_eq!(result, Err(ProgramError::Custom(8))) - } else { - assert!(accounts[1..] - .iter() - .map(|signer| *signer.key) - .eq( - get_multisig(&accounts[0]) - .signers() - .iter() - .take(accounts[1..].len()) - .copied() - ) - ); - assert_eq!(get_multisig(&accounts[0]).m(), instruction_data[0]); - assert_eq!(get_multisig(&accounts[0]).n() as usize, accounts.len() - 1); - assert!(get_multisig(&accounts[0]).is_initialized().is_ok()); - assert!(get_multisig(&accounts[0]).is_initialized().unwrap()); - assert!(result.is_ok()) - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Mint Info -/// instruction_data[0] // Discriminator 20 (Initialize Mint2 Freeze) -/// instruction_data[1] // Decimals -/// instruction_data[2..34] // Mint Authority Pubkey -/// instruction_data[34] // Freeze Authority Exists? 1 for freeze -/// instruction_data[35..67] // instruction_data[34] == 1 ==> Freeze Authority Pubkey -#[inline(never)] -fn test_process_initialize_mint2_freeze( - program_id: &Pubkey, - accounts: &[AccountInfo; 1], - instruction_data: &[u8; 67], -) -> ProgramResult { - // Constrain discriminator and program id - unsafe { assume(20 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 66] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_mint(&accounts[0]); - - //-Initial State----------------------------------------------------------- - // Note: Rent is a supported sysvar so ProgramError::UnsupportedSysvar should be impossible - let rent = solana_rent::Rent::get().unwrap(); - let minimum_balance = rent.minimum_balance(accounts[0].data_len()); - let mint_is_initialised_prior = get_mint(&accounts[0]).is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 34 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if instruction_data[33] != 0 && instruction_data[33] != 1 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if instruction_data[33] == 1 && instruction_data.len() < 66 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 1 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_is_initialised_prior.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_is_initialised_prior.unwrap() { - assert_eq!(result, Err(ProgramError::Custom(6))) - } else if accounts[0].lamports() < minimum_balance { - assert_eq!(result, Err(ProgramError::Custom(0))) - } else { - assert!(result.is_ok()); - - let mint_new = get_mint(&accounts[0]); - assert!(mint_new.is_initialized().unwrap()); - assert_pubkey_from_slice!(*mint_new.mint_authority().unwrap(), instruction_data[1..33]); - assert_eq!(mint_new.decimals(), instruction_data[0]); - - if instruction_data[33] == 1 { - assert_pubkey_from_slice!(*mint_new.freeze_authority().unwrap(), instruction_data[34..66]); - } - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Mint Info -/// instruction_data[0] // Discriminator 20 (Initialize Mint2 No Freeze) -/// instruction_data[1] // Decimals -/// instruction_data[2..34] // Mint Authority Pubkey -/// instruction_data[34] // Freeze Authority Exists? 0 for no freeze -#[inline(never)] -fn test_process_initialize_mint2_no_freeze( - program_id: &Pubkey, - accounts: &[AccountInfo; 1], - instruction_data: &[u8; 35], -) -> ProgramResult { - // Constrain discriminator and program id - unsafe { assume(20 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 34] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_mint(&accounts[0]); - - //-Initial State----------------------------------------------------------- - // Note: Rent is a supported sysvar so ProgramError::UnsupportedSysvar should be impossible - let rent = solana_rent::Rent::get().unwrap(); - let minimum_balance = rent.minimum_balance(accounts[0].data_len()); - let mint_is_initialised_prior = get_mint(&accounts[0]).is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 34 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if instruction_data[33] != 0 && instruction_data[33] != 1 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if instruction_data[33] == 1 && instruction_data.len() < 66 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 1 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_is_initialised_prior.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if mint_is_initialised_prior.unwrap() { - assert_eq!(result, Err(ProgramError::Custom(6))) - } else if accounts[0].lamports() < minimum_balance { - assert_eq!(result, Err(ProgramError::Custom(0))) - } else { - assert!(result.is_ok()); - - let mint_new = get_mint(&accounts[0]); - assert!(mint_new.is_initialized().unwrap()); - assert_pubkey_from_slice!(*mint_new.mint_authority().unwrap(), instruction_data[1..33]); - assert_eq!(mint_new.decimals(), instruction_data[0]); - - if instruction_data[33] == 1 { - assert_pubkey_from_slice!(*mint_new.freeze_authority().unwrap(), instruction_data[34..66]); - } - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -/// program_id // Token Program ID -/// accounts[0] // Mint Info -/// instruction_data[0] // Discriminator 21 (Get Account Data Size) -#[inline(never)] -fn test_process_get_account_data_size( - program_id: &Pubkey, - accounts: &[AccountInfo; 1], - instruction_data: &[u8; 1], -) -> ProgramResult { - // Constrain discriminator and program id - unsafe { assume(21 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 0] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_mint(&accounts[0]); - - //-Initial State----------------------------------------------------------- - let mint_initialised = get_mint(&accounts[0]).is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() < 1 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].owner != &crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else if accounts[0].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::Custom(2))) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::Custom(2))) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::Custom(2))) - } else { - // NOTE: This uses syscalls::sol_set_return_data - assert!(result.is_ok()) - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -#[inline(never)] -fn test_process_initialize_immutable_owner( - program_id: &Pubkey, - accounts: &[AccountInfo; 1], - instruction_data: &[u8; 1], -) -> ProgramResult { - // Constrain discriminator and program id - unsafe { assume(22 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 0] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_account(&accounts[0]); - - //-Initial State----------------------------------------------------------- - let src_initialised = get_account(&accounts[0]).is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if accounts.len() != 1 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].data_len() != Account::LEN { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::Custom(6))) - } else { - assert!(result.is_ok()) - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -#[inline(never)] -fn test_process_amount_to_ui_amount( - program_id: &Pubkey, - accounts: &[AccountInfo; 1], - instruction_data: &[u8; 9], -) -> ProgramResult { - // Constrain discriminator and program id - unsafe { assume(23 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8; 8] = instruction_data.last_chunk().unwrap(); - - cheatcode_is_spl_mint(&accounts[0]); - - //-Initial State----------------------------------------------------------- - let mint_initialised = get_mint(&accounts[0]).is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - if instruction_data.len() < 8 { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 1 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].owner != &crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else if accounts[0].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::Custom(2))) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::Custom(2))) - } else { - assert!(result.is_ok()) - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result -} - -#[inline(never)] -fn test_process_ui_amount_to_amount( - program_id: &Pubkey, - accounts: &[AccountInfo; 1], - instruction_data: &[u8], -) -> ProgramResult { - // Constrain discriminator and program id - unsafe { assume(24 == instruction_data[0]); } - unsafe { assume(program_id == &crate::id()); } - - // Strip discriminator so instruction data is equivalent p-token harness - let instruction_data_with_discriminator = &instruction_data.clone(); - let instruction_data: &[u8] = &instruction_data[1..]; - - cheatcode_is_spl_mint(&accounts[0]); - - //-Initial State----------------------------------------------------------- - let ui_amount = core::str::from_utf8(instruction_data); - let mint_initialised = get_mint(&accounts[0]).is_initialized(); - - //-Process Instruction----------------------------------------------------- - let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); - - //-Assert Postconditions--------------------------------------------------- - // TODO: validations module is private, so we need a work around - if ui_amount.is_err() { - assert_eq!(result, Err(ProgramError::Custom(12))) - } else if accounts.len() < 1 { - assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) - } else if accounts[0].owner != &crate::id() { - assert_eq!(result, Err(ProgramError::IncorrectProgramId)) - } else if accounts[0].data_len() != Mint::LEN { - assert_eq!(result, Err(ProgramError::Custom(2))) - } else if mint_initialised.is_err() { - assert_eq!(result, Err(ProgramError::InvalidAccountData)) - } else if !mint_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::Custom(2))) - } else if ui_amount.unwrap().is_empty() { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if ui_amount.unwrap() == "." { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if 1 < ui_amount.unwrap().chars().filter(|&c| c == '.').count() { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if ui_amount.unwrap().starts_with('.') && ui_amount.unwrap().chars().skip(1).all(|c| c == '0') { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if ui_amount.unwrap().split_once('.').map_or(false, |(_, frac)| { (get_mint(&accounts[0]).decimals() as usize) < frac.trim_end_matches('0').len()}) { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if ui_amount.unwrap().split_once('.').map_or( - 257_usize < ui_amount.unwrap().len() + (get_mint(&accounts[0]).decimals() as usize), - |(ints, _)| { 257_usize < ints.len() + (get_mint(&accounts[0]).decimals() as usize) }) { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } /*else if ui_amount.unwrap() == "+." { - // TODO: Why is this valid? - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if ui_amount.unwrap() == "+" { - // TODO: Why is this valid? - assert_eq!(result, Err(ProgramError::InvalidArgument)) - }*/ else if ui_amount.unwrap().chars().nth(0).unwrap() == '-' { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if ui_amount.unwrap().contains(|c: char| !c.is_digit(10) && c != '+' && c != '.') { - assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else if ui_amount.unwrap().split_once('.').map_or( - { - const MAX_VAL: &str = "1844674407370955"; // TODO: What should this be? - let ui_amount = ui_amount.unwrap(); - let ui_amount = ui_amount.strip_prefix('+').unwrap_or(ui_amount); - let ui_amount = ui_amount.trim_start_matches('0'); - match ui_amount.len().cmp(&MAX_VAL.len()) { - core::cmp::Ordering::Less => false, - core::cmp::Ordering::Greater => true, - core::cmp::Ordering::Equal => MAX_VAL < ui_amount, - } - }, - |(ints, fracs)| { - const MAX_VAL: &str = "1844674407370955"; // TODO: What should this be? - let ints = ints.strip_prefix('+').unwrap_or(ints); - let hi = ints.trim_start_matches('0'); - let lo = if hi.is_empty() { fracs.trim_start_matches('0') } else { fracs }; - - let total_len = hi.len() + lo.len(); - - match total_len.cmp(&MAX_VAL.len()) { - core::cmp::Ordering::Less => false, - core::cmp::Ordering::Greater => { true }, - core::cmp::Ordering::Equal => { - if hi.len() > MAX_VAL.len() { - return true; - } - let (max_hi, max_lo) = MAX_VAL.split_at(hi.len()); - hi > max_hi || (hi == max_hi && lo > max_lo) - } - } - } - ) { - // TODO: What is going on ??? Need to fix - // assert_eq!(result, Err(ProgramError::InvalidArgument)) - } else { - assert!(result.is_ok()) - } - - // Ensure instruction_data was not mutated - assert_eq!(*instruction_data, instruction_data_with_discriminator[1..]); - - result + cheatcode_is_spl_rent(rent); + let prent = solana_rent::Rent::from_account_info(rent).unwrap_or(sysrent); + let (acct_burnt, acct_distributed) = prent.calculate_burn(rent_collected); + assert!(prent.burn_percent > 100 || (acct_burnt <= rent_collected && acct_distributed <= rent_collected)); } +// Shared test harnesses --------------------------------------------------- +include!("../../specs/shared/test_process_approve_checked.rs"); +include!("../../specs/shared/test_process_approve_checked_multisig.rs"); +include!("../../specs/shared/test_process_approve.rs"); +include!("../../specs/shared/test_process_approve_multisig.rs"); +include!("../../specs/shared/test_process_freeze_account.rs"); +include!("../../specs/shared/test_process_freeze_account_multisig.rs"); +include!("../../specs/shared/test_process_get_account_data_size.rs"); +include!("../../specs/shared/test_process_initialize_account2.rs"); +include!("../../specs/shared/test_process_initialize_account3.rs"); +include!("../../specs/shared/test_process_initialize_account.rs"); +include!("../../specs/shared/test_process_initialize_immutable_owner.rs"); +include!("../../specs/shared/test_process_initialize_mint2_freeze.rs"); +include!("../../specs/shared/test_process_initialize_mint2_no_freeze.rs"); +include!("../../specs/shared/test_process_initialize_mint_freeze.rs"); +include!("../../specs/shared/test_process_initialize_mint_no_freeze.rs"); +include!("../../specs/shared/test_process_initialize_multisig.rs"); +include!("../../specs/shared/test_process_initialize_multisig2.rs"); +include!("../../specs/shared/test_process_mint_to_checked.rs"); +include!("../../specs/shared/test_process_mint_to_checked_multisig.rs"); +include!("../../specs/shared/test_process_mint_to.rs"); +include!("../../specs/shared/test_process_mint_to_multisig.rs"); +include!("../../specs/shared/test_process_revoke.rs"); +include!("../../specs/shared/test_process_revoke_multisig.rs"); +include!("../../specs/shared/test_process_set_authority_account.rs"); +include!("../../specs/shared/test_process_set_authority_account_multisig.rs"); +include!("../../specs/shared/test_process_set_authority_mint.rs"); +include!("../../specs/shared/test_process_set_authority_mint_multisig.rs"); +include!("../../specs/shared/test_process_sync_native.rs"); +include!("../../specs/shared/test_process_thaw_account.rs"); +include!("../../specs/shared/test_process_thaw_account_multisig.rs"); +include!("../../specs/shared/test_process_close_account.rs"); +include!("../../specs/shared/test_process_close_account_multisig.rs"); +include!("../../specs/shared/test_process_burn_checked.rs"); +include!("../../specs/shared/test_process_burn_checked_multisig.rs"); +include!("../../specs/shared/test_process_burn.rs"); +include!("../../specs/shared/test_process_burn_multisig.rs"); +include!("../../specs/shared/test_process_transfer_checked.rs"); +include!("../../specs/shared/test_process_transfer_checked_multisig.rs"); +include!("../../specs/shared/test_process_transfer.rs"); +include!("../../specs/shared/test_process_transfer_multisig.rs"); +include!("../../specs/shared/test_process_amount_to_ui_amount.rs"); +include!("../../specs/shared/test_process_ui_amount_to_amount.rs"); + +// Withdraw Excess Lamports test harness (spl-token specific) +include!("../../specs/withdraw-spl-token.rs"); diff --git a/specs/README.md b/specs/README.md new file mode 100644 index 00000000..572cebca --- /dev/null +++ b/specs/README.md @@ -0,0 +1,109 @@ +# Specs - Runtime Verification Harness + +This directory contains shared runtime verification specifications for Solana token programs. The specs provide a common harness for verifying both +**p-token** (pinocchio-based) and **spl-token** (solana-based) implementations. The differences between each will be handled by macros, +allowing for small surface area of change that is easily reviewable. + +## Architecture + +``` +specs/ +├── prelude-p-token.rs # P-token specific macros and helpers +├── prelude-spl-token.rs # SPL-token specific macros and wrappers +└── shared/ # Common spec files (44 files) + ├── inner_test_validate_owner.rs + └── test_process_*.rs +``` + +### How It Works + +Both token implementations include the shared specs via the `include!` macro: + +**p-token** (`p-token/src/entrypoint-runtime-verification.rs`): +```rust +include!("../../specs/prelude-p-token.rs"); +include!("../../specs/shared/inner_test_validate_owner.rs"); +include!("../../specs/shared/test_process_transfer.rs"); +// ... more specs +``` + +**spl-token** (`program/src/entrypoint-runtime-verification.rs`): +```rust +include!("../../specs/prelude-spl-token.rs"); +include!("../../specs/shared/inner_test_validate_owner.rs"); +include!("../../specs/shared/test_process_transfer.rs"); +// ... more specs +``` + +## API Abstraction + +The preludes define macros that abstract API differences between implementations: + +### AccountInfo Access Macros + +| Macro | p-token (methods) | spl-token (fields) | +|-------|-------------------|-------------------| +| `key!($acc)` | `$acc.key()` | `$acc.key` | +| `owner!($acc)` | `$acc.owner()` | `$acc.owner` | +| `is_signer!($acc)` | `$acc.is_signer()` | `$acc.is_signer` | + +### Cheatcode Macros + +| Macro | p-token | spl-token | +|-------|---------|-----------| +| `cheatcode_account!($acc)` | `cheatcode_is_account($acc)` | `cheatcode_is_spl_account($acc)` | +| `cheatcode_mint!($acc)` | `cheatcode_is_mint($acc)` | `cheatcode_is_spl_mint($acc)` | + +### Process Call Macros + +| Macro | p-token | spl-token | +|-------|---------|-----------| +| `call_process_transfer!(...)` | Direct function call | `Processor::process_transfer(...)` with parsing | +| `call_process_mint_to!(...)` | Direct function call | `Processor::process_mint_to(...)` with parsing | + +### ID Aliases + +| Alias | p-token | spl-token | +|-------|---------|-----------| +| `PROGRAM_ID` | `pinocchio_token_interface::program::ID` | `crate::ID` | +| `RENT_ID` | `pinocchio::sysvars::rent::RENT_ID` | `solana_sysvar::rent::ID` | +| `NATIVE_MINT_ID` | `pinocchio_token_interface::native_mint::ID` | `spl_token_interface::native_mint::ID` | + +## Spec File Structure + +Each spec file follows a standard structure: + +```rust +/// accounts[0] // Source Info +/// accounts[1] // Destination Info +/// accounts[2] // Authority Info +/// instruction_data[0..8] // Little Endian Bytes of u64 amount +#[inline(never)] +fn test_process_transfer( + accounts: &[AccountInfo; 3], + instruction_data: &[u8; 8], +) -> ProgramResult { + // Cheatcodes for symbolic execution setup + cheatcode_account!(&accounts[0]); + cheatcode_account!(&accounts[1]); + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + // ... capture initial state + + //-Process Instruction----------------------------------------------------- + let result = call_process_transfer!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if /* error condition */ { + assert_eq!(result, Err(ProgramError::...)); + return result; + } + // ... more conditions + + assert!(result.is_ok()); + // ... verify state changes + + result +} +``` diff --git a/specs/prelude-p-token.rs b/specs/prelude-p-token.rs new file mode 100644 index 00000000..e519bf74 --- /dev/null +++ b/specs/prelude-p-token.rs @@ -0,0 +1,263 @@ +// ============================================================================= +// API Alignment Macros +// ============================================================================= +// +// Macros for consistency between SPL and P Token shared specifications + +// --- AccountInfo Macros --- + +macro_rules! key { + ($acc:expr) => { + $acc.key() + }; +} +macro_rules! owner { + ($acc:expr) => { + $acc.owner() + }; +} +macro_rules! is_signer { + ($acc:expr) => { + $acc.is_signer() + }; +} +macro_rules! same_account { + ($acc1:expr, $acc2:expr) => { + $acc1 == $acc2 + }; +} + +// --- Pubkey Macros --- + +// For reference types - in p-token, no dereference needed +macro_rules! assert_pubkey_from_slice { + ($actual:expr, $slice:expr) => {{ + assert_eq!($actual, $slice); + }}; +} + +// For value types - same as above for p-token +macro_rules! assert_pubkey_from_slice_val { + ($actual:expr, $slice:expr) => {{ + assert_eq!($actual, $slice); + }}; +} + +// ============================================================================= +// Cheatcodes +// ============================================================================= + +#[inline(never)] +fn cheatcode_is_account(_: &AccountInfo) {} +#[inline(never)] +fn cheatcode_is_mint(_: &AccountInfo) {} +#[inline(never)] +fn cheatcode_is_rent(_: &AccountInfo) {} +#[inline(never)] +fn cheatcode_is_multisig(_: &AccountInfo) {} + +/// Cheatcode macros to abstract naming differences. +macro_rules! cheatcode_account { + ($acc:expr) => { + cheatcode_is_account($acc) + }; +} +macro_rules! cheatcode_mint { + ($acc:expr) => { + cheatcode_is_mint($acc) + }; +} +macro_rules! cheatcode_rent { + ($acc:expr) => { + cheatcode_is_rent($acc) + }; +} +macro_rules! cheatcode_multisig { + ($acc:expr) => { + cheatcode_is_multisig($acc) + }; +} + +// ============================================================================= +// Helper functions +// ============================================================================= + +fn get_account(account_info: &AccountInfo) -> &Account { + unsafe { + let byte_ptr = account_info.borrow_data_unchecked(); + let acc_ref = load_unchecked::(byte_ptr).unwrap(); + acc_ref + } +} + +fn get_mint(account_info: &AccountInfo) -> &Mint { + unsafe { + let byte_ptr = account_info.borrow_data_unchecked(); + let acc_ref = load_unchecked::(byte_ptr).unwrap(); + acc_ref + } +} + +fn get_rent(account_info: &AccountInfo) -> &Rent { + unsafe { Rent::from_bytes_unchecked(account_info.borrow_data_unchecked()) } +} + +fn get_multisig(account_info: &AccountInfo) -> &Multisig { + unsafe { + let byte_ptr = account_info.borrow_data_unchecked(); + let multisig_ref = load_unchecked::(byte_ptr).unwrap(); + multisig_ref + } +} + +// ============================================================================= +// Aliases +// ============================================================================= + +use pinocchio_token_interface::program::ID as PROGRAM_ID; +use pinocchio::sysvars::rent::Rent; +use pinocchio::sysvars::rent::RENT_ID; +use pinocchio_token_interface::native_mint::ID as NATIVE_MINT_ID; +use pinocchio_token_interface::state::account::INCINERATOR_ID; +use pinocchio::pubkey::PUBKEY_BYTES; +use pinocchio_token_interface::state::account_state::AccountState; + +// ============================================================================= +// Process call macros (ordered same as includes) +// ============================================================================= + +macro_rules! call_process_approve_checked { + ($accounts:expr, $instruction_data:expr) => { + process_approve_checked($accounts, $instruction_data) + }; +} +macro_rules! call_process_approve { + ($accounts:expr, $instruction_data:expr) => { + process_approve($accounts, $instruction_data) + }; +} +macro_rules! call_process_freeze_account { + ($accounts:expr) => { + process_freeze_account($accounts) + }; +} +macro_rules! call_process_get_account_data_size { + ($accounts:expr) => { + process_get_account_data_size($accounts) + }; +} +macro_rules! call_process_initialize_account2 { + ($accounts:expr, $instruction_data:expr) => { + process_initialize_account2($accounts, $instruction_data) + }; +} +macro_rules! call_process_initialize_account3 { + ($accounts:expr, $instruction_data:expr) => { + process_initialize_account3($accounts, $instruction_data) + }; +} +macro_rules! call_process_initialize_account { + ($accounts:expr) => { + process_initialize_account($accounts) + }; +} +macro_rules! call_process_initialize_immutable_owner { + ($accounts:expr) => { + process_initialize_immutable_owner($accounts) + }; +} +macro_rules! call_process_initialize_mint2 { + ($accounts:expr, $instruction_data:expr) => { + process_initialize_mint2($accounts, $instruction_data) + }; +} +macro_rules! call_process_initialize_mint2_no_freeze { + ($accounts:expr, $instruction_data:expr) => { + process_initialize_mint2($accounts, $instruction_data) + }; +} +macro_rules! call_process_initialize_mint { + ($accounts:expr, $instruction_data:expr) => { + process_initialize_mint($accounts, $instruction_data) + }; +} +macro_rules! call_process_initialize_mint_no_freeze { + ($accounts:expr, $instruction_data:expr) => { + process_initialize_mint($accounts, $instruction_data) + }; +} +macro_rules! call_process_initialize_multisig { + ($accounts:expr, $instruction_data:expr) => { + process_initialize_multisig($accounts, $instruction_data) + }; +} +macro_rules! call_process_initialize_multisig2 { + ($accounts:expr, $instruction_data:expr) => { + process_initialize_multisig2($accounts, $instruction_data) + }; +} +macro_rules! call_process_mint_to_checked { + ($accounts:expr, $instruction_data:expr) => { + process_mint_to_checked($accounts, $instruction_data) + }; +} +macro_rules! call_process_mint_to { + ($accounts:expr, $instruction_data:expr) => { + process_mint_to($accounts, $instruction_data) + }; +} +macro_rules! call_process_revoke { + ($accounts:expr) => { + process_revoke($accounts) + }; +} +macro_rules! call_process_set_authority { + ($accounts:expr, $instruction_data:expr) => { + process_set_authority($accounts, $instruction_data) + }; +} +macro_rules! call_process_sync_native { + ($accounts:expr) => { + process_sync_native($accounts) + }; +} +macro_rules! call_process_thaw_account { + ($accounts:expr) => { + process_thaw_account($accounts) + }; +} +macro_rules! call_process_close_account { + ($accounts:expr) => { + process_close_account($accounts) + }; +} +macro_rules! call_process_burn_checked { + ($accounts:expr, $instruction_data:expr) => { + process_burn_checked($accounts, $instruction_data) + }; +} +macro_rules! call_process_burn { + ($accounts:expr, $instruction_data:expr) => { + process_burn($accounts, $instruction_data) + }; +} +macro_rules! call_process_transfer_checked { + ($accounts:expr, $instruction_data:expr) => { + process_transfer_checked($accounts, $instruction_data) + }; +} +macro_rules! call_process_transfer { + ($accounts:expr, $instruction_data:expr) => { + process_transfer($accounts, $instruction_data) + }; +} +macro_rules! call_process_amount_to_ui_amount { + ($accounts:expr, $instruction_data:expr) => { + process_amount_to_ui_amount($accounts, $instruction_data) + }; +} +macro_rules! call_process_ui_amount_to_amount { + ($accounts:expr, $instruction_data:expr) => { + process_ui_amount_to_amount($accounts, $instruction_data) + }; +} diff --git a/specs/prelude-spl-token.rs b/specs/prelude-spl-token.rs new file mode 100644 index 00000000..4a6763df --- /dev/null +++ b/specs/prelude-spl-token.rs @@ -0,0 +1,464 @@ +// ============================================================================= +// API Alignment (Wrappers and Macros) +// ============================================================================= +// +// Macros for consistency between SPL and P Token shared specifications. +// Wrapper types are required around `Account`, `Mint`, and `Multisig` provide +// an interface that is consistent with the P-Token types API. + +// --- Wrappers --- + +/// A wrapper struct for Account that provides Deref to access fields directly. +struct AccountWrapper(Result); + +impl AccountWrapper { + fn is_initialized(&self) -> Result { + match &self.0 { + Ok(a) => Ok(a.state != AccountState::Uninitialized), + Err(e) => Err(e.clone()), + } + } + + fn delegate(&self) -> Option<&Pubkey> { + match &self.0 { + Ok(a) => match a.delegate.as_ref() { + solana_program_option::COption::None => None, + solana_program_option::COption::Some(delegate) => Some(delegate), + }, + Err(_) => None, + } + } + + fn account_state(&self) -> Result { + match &self.0 { + Ok(a) => Ok(a.state), + Err(e) => Err(e.clone()), + } + } + + fn is_native(&self) -> bool { + match &self.0 { + Ok(a) => a.is_native.is_some(), + Err(_) => false, + } + } + + fn native_amount(&self) -> Option { + match &self.0 { + Ok(a) => match a.is_native { + solana_program_option::COption::Some(amt) => Some(amt), + solana_program_option::COption::None => None, + }, + Err(_) => None, + } + } + + fn close_authority(&self) -> Option<&Pubkey> { + match &self.0 { + Ok(a) => match a.close_authority.as_ref() { + solana_program_option::COption::Some(pk) => Some(pk), + solana_program_option::COption::None => None, + }, + Err(_) => None, + } + } + + fn is_owned_by_system_program_or_incinerator(&self) -> bool { + match &self.0 { + Ok(a) => a.owner == solana_sdk_ids::system_program::ID || a.owner == solana_sdk_ids::incinerator::ID, + Err(_) => false, + } + } + + fn amount(&self) -> u64 { + self.0.as_ref().expect("AccountWrapper: underlying account missing").amount + } + + fn delegated_amount(&self) -> u64 { + self.0.as_ref().expect("AccountWrapper: underlying account missing").delegated_amount + } +} + +impl core::ops::Deref for AccountWrapper { + type Target = Account; + fn deref(&self) -> &Self::Target { + self.0.as_ref().expect("AccountWrapper: underlying account missing") + } +} + +/// A wrapper struct for Mint that provides Deref to access fields directly. +struct MintWrapper(Result); + +impl MintWrapper { + fn is_initialized(&self) -> Result { + match &self.0 { + Ok(m) => Ok(m.is_initialized), + Err(e) => Err(e.clone()), + } + } + + fn mint_authority(&self) -> Option<&Pubkey> { + match &self.0 { + Ok(m) => match m.mint_authority.as_ref() { + solana_program_option::COption::Some(pk) => Some(pk), + solana_program_option::COption::None => None, + }, + Err(_) => None, + } + } + + fn freeze_authority(&self) -> Option<&Pubkey> { + match &self.0 { + Ok(m) => match m.freeze_authority.as_ref() { + solana_program_option::COption::Some(pk) => Some(pk), + solana_program_option::COption::None => None, + }, + Err(_) => None, + } + } + + fn supply(&self) -> u64 { + self.0.as_ref().expect("MintWrapper: underlying mint missing").supply + } +} + +impl core::ops::Deref for MintWrapper { + type Target = Mint; + fn deref(&self) -> &Self::Target { + self.0.as_ref().expect("MintWrapper: underlying mint missing") + } +} + +/// A wrapper struct for Multisig that provides Deref to access fields directly. +struct MultisigWrapper(Result); + +impl MultisigWrapper { + fn is_initialized(&self) -> Result { + match &self.0 { + Ok(m) => Ok(m.is_initialized), + Err(e) => Err(e.clone()), + } + } +} + +impl core::ops::Deref for MultisigWrapper { + type Target = Multisig; + fn deref(&self) -> &Self::Target { + self.0.as_ref().expect("MultisigWrapper: underlying multisig missing") + } +} + +// --- AccountInfo Macros --- + +macro_rules! key { + ($acc:expr) => { + $acc.key + }; +} +macro_rules! owner { + ($acc:expr) => { + $acc.owner + }; +} +macro_rules! is_signer { + ($acc:expr) => { + $acc.is_signer + }; +} +macro_rules! same_account { + ($acc1:expr, $acc2:expr) => { + key!($acc1) == key!($acc2) + }; +} + +// --- Pubkey Macros --- + +// For reference types (&Pubkey) - dereferences $actual +macro_rules! assert_pubkey_from_slice { + ($actual:expr, $slice:expr) => {{ + let expected_pubkey = Pubkey::new_from_array($slice.try_into().unwrap()); + assert_eq!(*$actual, expected_pubkey); + }}; +} + +// For value types (Pubkey) - no dereference +macro_rules! assert_pubkey_from_slice_val { + ($actual:expr, $slice:expr) => {{ + let expected_pubkey = Pubkey::new_from_array($slice.try_into().unwrap()); + assert_eq!($actual, expected_pubkey); + }}; +} + +// ============================================================================= +// Cheatcodes +// ============================================================================= + +#[inline(never)] +fn cheatcode_is_spl_account(_: &AccountInfo) {} +#[inline(never)] +fn cheatcode_is_spl_mint(_: &AccountInfo) {} +#[inline(never)] +fn cheatcode_is_spl_rent(_: &AccountInfo) {} +#[inline(never)] +fn cheatcode_is_spl_multisig(_: &AccountInfo) {} + +/// Cheatcode macros to abstract naming differences. +macro_rules! cheatcode_account { + ($acc:expr) => { + cheatcode_is_spl_account($acc) + }; +} +macro_rules! cheatcode_mint { + ($acc:expr) => { + cheatcode_is_spl_mint($acc) + }; +} +macro_rules! cheatcode_rent { + ($acc:expr) => { + cheatcode_is_spl_rent($acc) + }; +} +macro_rules! cheatcode_multisig { + ($acc:expr) => { + cheatcode_is_spl_multisig($acc) + }; +} + +// ============================================================================= +// Helper functions +// ============================================================================= + +fn get_account(account_info: &AccountInfo) -> AccountWrapper { + AccountWrapper(Account::unpack_unchecked(&account_info.data.borrow())) +} + +fn get_mint(account_info: &AccountInfo) -> MintWrapper { + MintWrapper(Mint::unpack_unchecked(&account_info.data.borrow())) +} + +fn get_rent(account_info: &AccountInfo) -> solana_rent::Rent { + bincode::deserialize(&account_info.data.borrow()).unwrap() +} + +fn get_multisig(account_info: &AccountInfo) -> MultisigWrapper { + MultisigWrapper(Multisig::unpack_unchecked(&account_info.data.borrow())) +} + +// ============================================================================= +// Aliases +// ============================================================================= + +use crate::ID as PROGRAM_ID; +use solana_rent::Rent; +use solana_sysvar::rent::ID as RENT_ID; +use spl_token_interface::native_mint::ID as NATIVE_MINT_ID; +use solana_sdk_ids::incinerator::ID as INCINERATOR_ID; +use solana_pubkey::PUBKEY_BYTES; +// Note: AccountState is already imported in the main file + +// ============================================================================= +// Process call macros (ordered same as includes) +// ============================================================================= + +macro_rules! call_process_approve_checked { + ($accounts:expr, $instruction_data:expr) => {{ + let data = $instruction_data; + let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); + let decimals = data[8]; + Processor::process_approve(&PROGRAM_ID, $accounts, amount, Some(decimals)) + }}; +} +macro_rules! call_process_approve { + ($accounts:expr, $instruction_data:expr) => {{ + let data = $instruction_data; + let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); + Processor::process_approve(&PROGRAM_ID, $accounts, amount, None) + }}; +} +macro_rules! call_process_freeze_account { + ($accounts:expr) => {{ + Processor::process_toggle_freeze_account(&PROGRAM_ID, $accounts, true) + }}; +} +macro_rules! call_process_get_account_data_size { + ($accounts:expr) => {{ + Processor::process_get_account_data_size(&PROGRAM_ID, $accounts) + }}; +} +macro_rules! call_process_initialize_account2 { + ($accounts:expr, $instruction_data:expr) => {{ + let data = $instruction_data; + let owner = Pubkey::new_from_array(data[0..32].try_into().unwrap()); + Processor::process_initialize_account2(&PROGRAM_ID, $accounts, owner) + }}; +} +macro_rules! call_process_initialize_account3 { + ($accounts:expr, $instruction_data:expr) => {{ + let data = $instruction_data; + let owner = Pubkey::new_from_array(data[0..32].try_into().unwrap()); + Processor::process_initialize_account3(&PROGRAM_ID, $accounts, owner) + }}; +} +macro_rules! call_process_initialize_account { + ($accounts:expr) => {{ + Processor::process_initialize_account(&PROGRAM_ID, $accounts) + }}; +} +macro_rules! call_process_initialize_immutable_owner { + ($accounts:expr) => {{ + Processor::process_initialize_immutable_owner($accounts) + }}; +} +macro_rules! call_process_initialize_mint2 { + ($accounts:expr, $instruction_data:expr) => {{ + let data = $instruction_data; + let decimals = data[0]; + let mint_authority = Pubkey::new_from_array(data[1..33].try_into().unwrap()); + let freeze_authority = if data[33] == 1 { + solana_program_option::COption::Some(Pubkey::new_from_array(data[34..66].try_into().unwrap())) + } else { + solana_program_option::COption::None + }; + Processor::process_initialize_mint2($accounts, decimals, mint_authority, freeze_authority) + }}; +} +macro_rules! call_process_initialize_mint2_no_freeze { + ($accounts:expr, $instruction_data:expr) => {{ + let data = $instruction_data; + let decimals = data[0]; + let mint_authority = Pubkey::new_from_array(data[1..33].try_into().unwrap()); + Processor::process_initialize_mint2($accounts, decimals, mint_authority, solana_program_option::COption::None) + }}; +} +macro_rules! call_process_initialize_mint { + ($accounts:expr, $instruction_data:expr) => {{ + let data = $instruction_data; + let decimals = data[0]; + let mint_authority = Pubkey::new_from_array(data[1..33].try_into().unwrap()); + let freeze_authority = if data[33] == 1 { + solana_program_option::COption::Some(Pubkey::new_from_array(data[34..66].try_into().unwrap())) + } else { + solana_program_option::COption::None + }; + Processor::process_initialize_mint($accounts, decimals, mint_authority, freeze_authority) + }}; +} +macro_rules! call_process_initialize_mint_no_freeze { + ($accounts:expr, $instruction_data:expr) => {{ + let data = $instruction_data; + let decimals = data[0]; + let mint_authority = Pubkey::new_from_array(data[1..33].try_into().unwrap()); + Processor::process_initialize_mint($accounts, decimals, mint_authority, solana_program_option::COption::None) + }}; +} +macro_rules! call_process_initialize_multisig { + ($accounts:expr, $instruction_data:expr) => {{ + let data = $instruction_data; + let m = data[0]; + Processor::process_initialize_multisig($accounts, m) + }}; +} +macro_rules! call_process_initialize_multisig2 { + ($accounts:expr, $instruction_data:expr) => {{ + let data = $instruction_data; + let m = data[0]; + Processor::process_initialize_multisig2($accounts, m) + }}; +} +macro_rules! call_process_mint_to_checked { + ($accounts:expr, $instruction_data:expr) => {{ + let data = $instruction_data; + let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); + let decimals = data[8]; + Processor::process_mint_to(&PROGRAM_ID, $accounts, amount, Some(decimals)) + }}; +} +macro_rules! call_process_mint_to { + ($accounts:expr, $instruction_data:expr) => {{ + let data = $instruction_data; + let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); + Processor::process_mint_to(&PROGRAM_ID, $accounts, amount, None) + }}; +} +macro_rules! call_process_revoke { + ($accounts:expr) => {{ + Processor::process_revoke(&PROGRAM_ID, $accounts) + }}; +} +macro_rules! call_process_set_authority { + ($accounts:expr, $instruction_data:expr) => {{ + let data = $instruction_data; + let authority_type = match data[0] { + 0 => crate::instruction::AuthorityType::MintTokens, + 1 => crate::instruction::AuthorityType::FreezeAccount, + 2 => crate::instruction::AuthorityType::AccountOwner, + 3 => crate::instruction::AuthorityType::CloseAccount, + _ => return Err(TokenError::InvalidInstruction.into()), + }; + let new_authority = if data[1] == 1 { + solana_program_option::COption::Some(Pubkey::new_from_array(data[2..34].try_into().unwrap())) + } else { + solana_program_option::COption::None + }; + Processor::process_set_authority(&PROGRAM_ID, $accounts, authority_type, new_authority) + }}; +} +macro_rules! call_process_sync_native { + ($accounts:expr) => {{ + Processor::process_sync_native(&PROGRAM_ID, $accounts) + }}; +} +macro_rules! call_process_thaw_account { + ($accounts:expr) => {{ + Processor::process_toggle_freeze_account(&PROGRAM_ID, $accounts, false) + }}; +} +macro_rules! call_process_close_account { + ($accounts:expr) => {{ + Processor::process_close_account(&PROGRAM_ID, $accounts) + }}; +} +macro_rules! call_process_burn_checked { + ($accounts:expr, $instruction_data:expr) => {{ + let data = $instruction_data; + let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); + let decimals = data[8]; + Processor::process_burn(&PROGRAM_ID, $accounts, amount, Some(decimals)) + }}; +} +macro_rules! call_process_burn { + ($accounts:expr, $instruction_data:expr) => {{ + let data = $instruction_data; + let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); + Processor::process_burn(&PROGRAM_ID, $accounts, amount, None) + }}; +} +macro_rules! call_process_transfer_checked { + ($accounts:expr, $instruction_data:expr) => {{ + let data = $instruction_data; + let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); + let decimals = data[8]; + Processor::process_transfer(&PROGRAM_ID, $accounts, amount, Some(decimals)) + }}; +} +macro_rules! call_process_transfer { + ($accounts:expr, $instruction_data:expr) => {{ + let data = $instruction_data; + let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); + Processor::process_transfer(&PROGRAM_ID, $accounts, amount, None) + }}; +} +macro_rules! call_process_amount_to_ui_amount { + ($accounts:expr, $instruction_data:expr) => {{ + let data = $instruction_data; + let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); + Processor::process_amount_to_ui_amount(&PROGRAM_ID, $accounts, amount) + }}; +} +macro_rules! call_process_ui_amount_to_amount { + ($accounts:expr, $instruction_data:expr) => {{ + let data = $instruction_data; + let ui_amount = core::str::from_utf8(data).map_err(|_| TokenError::InvalidInstruction)?; + Processor::process_ui_amount_to_amount(&PROGRAM_ID, $accounts, ui_amount) + }}; +} diff --git a/specs/shared/inner_test_validate_owner.rs b/specs/shared/inner_test_validate_owner.rs new file mode 100644 index 00000000..5f25fdf4 --- /dev/null +++ b/specs/shared/inner_test_validate_owner.rs @@ -0,0 +1,69 @@ +/// This function encapsulates the specification of validating the signature +/// requirements In particular, code from mod.rs::validate_owner is checked +#[inline(never)] +fn inner_test_validate_owner( + expected_owner: &Pubkey, + owner_account_info: &AccountInfo, + tx_signers: &[AccountInfo], + maybe_multisig_is_initialised: Option>, + result: Result<(), ProgramError>, +) -> Result<(), ProgramError> { + if expected_owner != key!(owner_account_info) { + assert_eq!(result, Err(ProgramError::Custom(4))); + return result; + } + // We add the `maybe_multisig_is_initialised.is_some()` to not branch vacuously in the + // non-multisig cases + else if maybe_multisig_is_initialised.is_some() + && owner_account_info.data_len() == Multisig::LEN + && (owner!(owner_account_info) == &PROGRAM_ID) + { + // Guaranteed to succeed by `cheatcode_is_multisig` + let multisig_is_initialised = maybe_multisig_is_initialised.unwrap(); + if multisig_is_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !multisig_is_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else { + let multisig = get_multisig(owner_account_info); + + // Did all declared and allowd signers sign? + let unsigned_exists = tx_signers.iter().any(|potential_signer| { + multisig.signers.iter().any(|registered_key| { + registered_key == key!(potential_signer) && !is_signer!(potential_signer) + }) + }); + if unsigned_exists { + assert_eq!(result, Err(ProgramError::MissingRequiredSignature)); + return result; + } + + // Were enough signatures received? + let signers_count = multisig.signers + .iter() + .filter_map(|registered_key| { + tx_signers.iter().find(|potential_signer| { + key!(potential_signer) == registered_key && is_signer!(potential_signer) + }) + }) + .count(); + + // Check if we have enough signers + if signers_count < multisig.m as usize { + assert_eq!(result, Err(ProgramError::MissingRequiredSignature)); + return result; + } + + return Ok(()); + } + } + // Non-multisig case - check if owner_account_info.is_signer() + else if !is_signer!(owner_account_info) { + assert_eq!(result, Err(ProgramError::MissingRequiredSignature)); + return result; + } + + Ok(()) +} diff --git a/specs/shared/test_process_amount_to_ui_amount.rs b/specs/shared/test_process_amount_to_ui_amount.rs new file mode 100644 index 00000000..7debb468 --- /dev/null +++ b/specs/shared/test_process_amount_to_ui_amount.rs @@ -0,0 +1,31 @@ +#[inline(never)] +fn test_process_amount_to_ui_amount( + accounts: &[AccountInfo; 1], + instruction_data: &[u8; 8], +) -> ProgramResult { + cheatcode_mint!(&accounts[0]); + + //-Initial State----------------------------------------------------------- + let mint_initialised = get_mint(&accounts[0]).is_initialized(); + + //-Process Instruction----------------------------------------------------- + let result = call_process_amount_to_ui_amount!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < 8 { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if accounts.is_empty() { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if owner!(&accounts[0]) != &PROGRAM_ID { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)) + } else if accounts[0].data_len() != Mint::LEN { + assert_eq!(result, Err(ProgramError::Custom(2))) + } else if mint_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !mint_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::Custom(2))) + } else { + assert!(result.is_ok()) + } + result +} diff --git a/specs/shared/test_process_approve.rs b/specs/shared/test_process_approve.rs new file mode 100644 index 00000000..9b6c8e7e --- /dev/null +++ b/specs/shared/test_process_approve.rs @@ -0,0 +1,48 @@ +fn test_process_approve(accounts: &[AccountInfo; 3], instruction_data: &[u8; 8]) -> ProgramResult { + cheatcode_account!(&accounts[0]); // Source Account + cheatcode_account!(&accounts[1]); // Delegate + cheatcode_account!(&accounts[2]); // Excluding the multisig case + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + let amount = unsafe { u64::from_le_bytes(*(instruction_data.as_ptr() as *const [u8; 8])) }; + let src_owner = src_old.owner; + let src_initialised = src_old.is_initialized(); + let src_init_state = src_old.account_state(); + let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account + + //-Process Instruction----------------------------------------------------- + let result = call_process_approve!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < 8 { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if accounts[0].data_len() != Account::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if src_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !src_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)) + } else if src_init_state.unwrap() == AccountState::Frozen { + // This should be safe to unwrap due to above check passing + assert_eq!(result, Err(ProgramError::Custom(17))) + } else { + // Validate Owner + inner_test_validate_owner( + &src_owner, // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + let src_new = get_account(&accounts[0]); + assert_eq!(src_new.delegate().unwrap(), key!(accounts[1])); + assert_eq!(src_new.delegated_amount(), amount); + assert!(result.is_ok()) + } + + result +} diff --git a/specs/shared/test_process_approve_checked.rs b/specs/shared/test_process_approve_checked.rs new file mode 100644 index 00000000..0d37173c --- /dev/null +++ b/specs/shared/test_process_approve_checked.rs @@ -0,0 +1,73 @@ +/// accounts[0] // Source Account Info +/// accounts[1] // Expected Mint Info +/// accounts[2] // Delegate Info +/// accounts[3] // Owner Info +/// instruction_data[0..9] // Little Endian Bytes of u64 amount, and decimals +#[inline(never)] +fn test_process_approve_checked( + accounts: &[AccountInfo; 4], + instruction_data: &[u8; 9], +) -> ProgramResult { + cheatcode_account!(&accounts[0]); // Source Account + cheatcode_mint!(&accounts[1]); // Expected Mint + cheatcode_account!(&accounts[2]); // Delegate + cheatcode_account!(&accounts[3]); // Excluding the multisig case + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + let amount = u64::from_le_bytes([ + instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], + instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7], + ]); + let src_owner = src_old.owner; + let src_initialised = src_old.is_initialized(); + let src_init_state = src_old.account_state(); + let mint_initialised = get_mint(&accounts[1]).is_initialized(); + let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account + + //-Process Instruction----------------------------------------------------- + let result = call_process_approve_checked!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < 9 { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if accounts.len() < 4 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if accounts[0].data_len() != Account::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if src_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !src_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)) + } else if src_init_state.unwrap() == AccountState::Frozen { + // This should be safe to unwrap due to above check passing + assert_eq!(result, Err(ProgramError::Custom(17))) + } else if key!(&accounts[1]) != &get_account(&accounts[0]).mint { + assert_eq!(result, Err(ProgramError::Custom(3))) + } else if accounts[1].data_len() != Mint::LEN { + // Not sure if this is even possible if we get past the case above + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if mint_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !mint_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)) + } else if instruction_data[8] != get_mint(&accounts[1]).decimals { + assert_eq!(result, Err(ProgramError::Custom(18))) + } else { + // Validate Owner + inner_test_validate_owner( + &src_owner, // expected_owner + &accounts[3], // owner_account_info + &accounts[4..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + let src_new = get_account(&accounts[0]); + assert_eq!(src_new.delegate().unwrap(), key!(&accounts[2])); + assert_eq!(src_new.delegated_amount(), amount); + assert!(result.is_ok()) + } + + result +} diff --git a/specs/shared/test_process_approve_checked_multisig.rs b/specs/shared/test_process_approve_checked_multisig.rs new file mode 100644 index 00000000..29737f6a --- /dev/null +++ b/specs/shared/test_process_approve_checked_multisig.rs @@ -0,0 +1,74 @@ +/// accounts[0] // Source Account Info +/// accounts[1] // Expected Mint Info +/// accounts[2] // Delegate Info +/// accounts[3] // Owner Info +/// accounts[4..15] // Signers +/// instruction_data[0..9] // Little Endian Bytes of u64 amount, and decimals +#[inline(never)] +fn test_process_approve_checked_multisig( + accounts: &[AccountInfo; 5], + instruction_data: &[u8; 9], +) -> ProgramResult { + cheatcode_account!(&accounts[0]); // Source Account + cheatcode_mint!(&accounts[1]); // Expected Mint + cheatcode_account!(&accounts[2]); // Delegate + cheatcode_multisig!(&accounts[3]); // Owner + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + let amount = u64::from_le_bytes([ + instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], + instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7], + ]); + let src_owner = src_old.owner; + let src_initialised = src_old.is_initialized(); + let src_init_state = src_old.account_state(); + let mint_initialised = get_mint(&accounts[1]).is_initialized(); + let maybe_multisig_is_initialised = Some(get_multisig(&accounts[3]).is_initialized()); + + //-Process Instruction----------------------------------------------------- + let result = call_process_approve_checked!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < 9 { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if accounts.len() < 4 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if accounts[0].data_len() != Account::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if src_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !src_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)) + } else if src_init_state.unwrap() == AccountState::Frozen { + // This should be safe to unwrap due to above check passing + assert_eq!(result, Err(ProgramError::Custom(17))) + } else if key!(&accounts[1]) != &get_account(&accounts[0]).mint { + assert_eq!(result, Err(ProgramError::Custom(3))) + } else if accounts[1].data_len() != Mint::LEN { + // Not sure if this is even possible if we get past the case above + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if mint_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !mint_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)) + } else if instruction_data[8] != get_mint(&accounts[1]).decimals { + assert_eq!(result, Err(ProgramError::Custom(18))) + } else { + // Validate Owner + inner_test_validate_owner( + &src_owner, // expected_owner + &accounts[3], // owner_account_info + &accounts[4..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + let src_new = get_account(&accounts[0]); + assert_eq!(src_new.delegate().unwrap(), key!(&accounts[2])); + assert_eq!(src_new.delegated_amount(), amount); + assert!(result.is_ok()) + } + + result +} diff --git a/specs/shared/test_process_approve_multisig.rs b/specs/shared/test_process_approve_multisig.rs new file mode 100644 index 00000000..87d1c6d4 --- /dev/null +++ b/specs/shared/test_process_approve_multisig.rs @@ -0,0 +1,57 @@ +/// accounts[0] // Source Account Info +/// accounts[1] // Delegate Info +/// accounts[2] // Owner Info +/// accounts[3..14] // Signers +/// instruction_data[0..8] // Little Endian Bytes of u64 amount +#[inline(never)] +fn test_process_approve_multisig( + accounts: &[AccountInfo; 4], + instruction_data: &[u8; 8], +) -> ProgramResult { + cheatcode_account!(&accounts[0]); // Source Account + cheatcode_account!(&accounts[1]); // Delegate + cheatcode_multisig!(&accounts[2]); // Owner + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + let amount = unsafe { u64::from_le_bytes(*(instruction_data.as_ptr() as *const [u8; 8])) }; + let src_owner = src_old.owner; + let src_initialised = src_old.is_initialized(); + let src_init_state = src_old.account_state(); + let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); + + //-Process Instruction----------------------------------------------------- + let result = call_process_approve!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < 8 { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if accounts[0].data_len() != Account::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if src_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !src_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)) + } else if src_init_state.unwrap() == AccountState::Frozen { + // This should be safe to unwrap due to above check passing + assert_eq!(result, Err(ProgramError::Custom(17))) + } else { + // Validate Owner + inner_test_validate_owner( + &src_owner, // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + let src_new = get_account(&accounts[0]); + assert_eq!(src_new.delegate().unwrap(), key!(accounts[1])); + assert_eq!(src_new.delegated_amount(), amount); + assert!(result.is_ok()) + } + + result +} diff --git a/specs/shared/test_process_burn.rs b/specs/shared/test_process_burn.rs new file mode 100644 index 00000000..a57c5ef3 --- /dev/null +++ b/specs/shared/test_process_burn.rs @@ -0,0 +1,123 @@ +/// accounts[0] // Source Info +/// accounts[1] // Mint Info +/// accounts[2] // Authority Info +/// instruction_data[0..8] // Little Endian Bytes of u64 amount +#[inline(never)] +pub fn test_process_burn(accounts: &[AccountInfo; 3], instruction_data: &[u8; 8]) -> ProgramResult { + cheatcode_account!(&accounts[0]); + cheatcode_mint!(&accounts[1]); + cheatcode_account!(&accounts[2]); // Excluding the multisig case + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + let mint_old = get_mint(&accounts[1]); + let amount = u64::from_le_bytes([ + instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], + instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7], + ]); + let src_initialised = src_old.is_initialized(); + let src_init_amount = src_old.amount(); + let src_init_state = src_old.account_state(); + let src_is_native = src_old.is_native(); + let src_mint = src_old.mint; + let src_owned_sys_inc = src_old.is_owned_by_system_program_or_incinerator(); + let src_owner = src_old.owner; + let src_info_owner = owner!(&accounts[0]); + let old_src_delgate = src_old.delegate().cloned(); + let old_src_delgated_amount = src_old.delegated_amount(); + let mint_initialised = mint_old.is_initialized(); + let mint_init_supply = mint_old.supply(); + let mint_info_owner = *owner!(&accounts[1]); + let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account + + #[cfg(feature = "assumptions")] + // account.amount() <= mint.supply(), account.delegated_amount() <= account.amount() + // otherwise processing could lead to overflows, see processor::shared::burn,L83 + if !(src_init_amount <= mint_init_supply && old_src_delgated_amount <= src_init_amount) { + return Err(ProgramError::Custom(99)); + } + + //-Process Instruction----------------------------------------------------- + let result = call_process_burn!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < 8 { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if accounts[0].data_len() != Account::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if src_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !src_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)) + } else if accounts[1].data_len() != Mint::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if mint_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !mint_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)) + } else if src_init_state.unwrap() == AccountState::Frozen { + assert_eq!(result, Err(ProgramError::Custom(17))) + } else if src_is_native { + assert_eq!(result, Err(ProgramError::Custom(10))) + } else if src_init_amount < amount { + assert_eq!(result, Err(ProgramError::Custom(1))) + } else if key!(&accounts[1]) != &src_mint { + assert_eq!(result, Err(ProgramError::Custom(3))) + } else { + if !src_owned_sys_inc { + if old_src_delgate.is_some() && *key!(&accounts[2]) == old_src_delgate.unwrap() { + inner_test_validate_owner( + &old_src_delgate.unwrap(), // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + if old_src_delgated_amount < amount { + assert_eq!(result, Err(ProgramError::Custom(1))); + return result; + } + } else { + inner_test_validate_owner( + &src_owner, // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + } + } + + if amount == 0 && *src_info_owner != PROGRAM_ID { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)) + } else if amount == 0 && mint_info_owner != PROGRAM_ID { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)) + } else { + let src_new = get_account(&accounts[0]); + assert!(src_new.amount() == src_init_amount - amount); + assert!(get_mint(&accounts[1]).supply() == mint_init_supply - amount); + assert!(result.is_ok()); + + // Delegate updates + let new_src_delegate = src_new.delegate().cloned(); + let new_src_delegated_amount = src_new.delegated_amount(); + if !src_owned_sys_inc + && old_src_delgate.is_some() + && *key!(&accounts[2]) == old_src_delgate.unwrap() + { + assert_eq!(new_src_delegated_amount, old_src_delgated_amount - amount); + if old_src_delgated_amount - amount == 0 { + assert_eq!(new_src_delegate, None); + } + } else { + assert_eq!(old_src_delgate, new_src_delegate); + assert_eq!(old_src_delgated_amount, new_src_delegated_amount); + } + } + } + + result +} diff --git a/specs/shared/test_process_burn_checked.rs b/specs/shared/test_process_burn_checked.rs new file mode 100644 index 00000000..c26d943e --- /dev/null +++ b/specs/shared/test_process_burn_checked.rs @@ -0,0 +1,131 @@ +/// accounts[0] // Source Info +/// accounts[1] // Mint Info +/// accounts[2] // Authority Info +/// instruction_data[0..9] // Little Endian Bytes of u64 amount, and decimals +#[inline(never)] +pub fn test_process_burn_checked( + accounts: &[AccountInfo; 3], + instruction_data: &[u8; 9], +) -> ProgramResult { + cheatcode_account!(&accounts[0]); + cheatcode_mint!(&accounts[1]); + cheatcode_account!(&accounts[2]); // Excluding the multisig case + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + let mint_old = get_mint(&accounts[1]); + let amount = u64::from_le_bytes([ + instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], + instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7], + ]); + let src_initialised = src_old.is_initialized(); + let src_init_amount = src_old.amount(); + let src_init_state = src_old.account_state(); + let src_is_native = src_old.is_native(); + let src_mint = src_old.mint; + let src_owned_sys_inc = src_old.is_owned_by_system_program_or_incinerator(); + let src_owner = src_old.owner; + let src_info_owner = owner!(&accounts[0]); + let old_src_delgate = src_old.delegate().cloned(); + let old_src_delgated_amount = src_old.delegated_amount(); + let mint_initialised = mint_old.is_initialized(); + let mint_init_supply = mint_old.supply(); + let mint_decimals = mint_old.decimals; + let mint_info_owner = *owner!(&accounts[1]); + let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account + + #[cfg(feature = "assumptions")] + // accoutn.amount() <= mint.supply(), account.delegated_amount() <= account.amount() + // otherwise processing could lead to overflows, see processor::shared::burn,L83 + if !(src_init_amount <= mint_init_supply && old_src_delgated_amount <= src_init_amount) { + return Err(ProgramError::Custom(99)); + } + + //-Process Instruction----------------------------------------------------- + let result = call_process_burn_checked!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < 9 { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if accounts[0].data_len() != Account::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if src_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !src_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)) + } else if accounts[1].data_len() != Mint::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if mint_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !mint_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)) + } else if src_init_state.unwrap() == AccountState::Frozen { + assert_eq!(result, Err(ProgramError::Custom(17))) + } else if src_is_native { + assert_eq!(result, Err(ProgramError::Custom(10))) + } else if src_init_amount < amount { + assert_eq!(result, Err(ProgramError::Custom(1))) + } else if key!(&accounts[1]) != &src_mint { + assert_eq!(result, Err(ProgramError::Custom(3))) + } else if instruction_data[8] != mint_decimals { + assert_eq!(result, Err(ProgramError::Custom(18))) + } else { + if !src_owned_sys_inc { + if old_src_delgate.is_some() && *key!(&accounts[2]) == old_src_delgate.unwrap() { + // Validate Owner + inner_test_validate_owner( + &old_src_delgate.unwrap(), // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + if old_src_delgated_amount < amount { + assert_eq!(result, Err(ProgramError::Custom(1))); + return result; + } + } else { + // Validate Owner + inner_test_validate_owner( + &src_owner, // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + } + } + + if amount == 0 && *src_info_owner != PROGRAM_ID { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)) + } else if amount == 0 && mint_info_owner != PROGRAM_ID { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)) + } else { + let src_new = get_account(&accounts[0]); + assert!(src_new.amount() == src_init_amount - amount); + assert!(get_mint(&accounts[1]).supply() == mint_init_supply - amount); + assert!(result.is_ok()); + + // Delegate updates + let new_src_delegate = src_new.delegate().cloned(); + let new_src_delegated_amount = src_new.delegated_amount(); + if !src_owned_sys_inc + && old_src_delgate.is_some() + && *key!(&accounts[2]) == old_src_delgate.unwrap() + { + assert_eq!(new_src_delegated_amount, old_src_delgated_amount - amount); + if old_src_delgated_amount - amount == 0 { + assert_eq!(new_src_delegate, None); + } + } else { + assert_eq!(old_src_delgate, new_src_delegate); + assert_eq!(old_src_delgated_amount, new_src_delegated_amount); + } + } + } + + result +} diff --git a/specs/shared/test_process_burn_checked_multisig.rs b/specs/shared/test_process_burn_checked_multisig.rs new file mode 100644 index 00000000..eb3ad228 --- /dev/null +++ b/specs/shared/test_process_burn_checked_multisig.rs @@ -0,0 +1,116 @@ +/// accounts[0] // Source Info +/// accounts[1] // Mint Info +/// accounts[2] // Authority Info +/// accounts[3..14] // Signers +/// instruction_data[0..9] // Little Endian Bytes of u64 amount, and decimals +#[inline(never)] +fn test_process_burn_checked_multisig( + accounts: &[AccountInfo; 4], + instruction_data: &[u8; 9], +) -> ProgramResult { + cheatcode_account!(&accounts[0]); + cheatcode_mint!(&accounts[1]); + cheatcode_multisig!(&accounts[2]); + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + let mint_old = get_mint(&accounts[1]); + let amount = u64::from_le_bytes([ + instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], + instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7], + ]); + let src_initialised = src_old.is_initialized(); + let src_init_amount = src_old.amount(); + let src_init_state = src_old.account_state(); + let src_is_native = src_old.is_native(); + let src_mint = src_old.mint; + let src_owned_sys_inc = src_old.is_owned_by_system_program_or_incinerator(); + let src_owner = src_old.owner; + let old_src_delgate = src_old.delegate().cloned(); + let old_src_delgated_amount = src_old.delegated_amount(); + let mint_initialised = mint_old.is_initialized(); + let mint_init_supply = mint_old.supply(); + let mint_decimals = mint_old.decimals; + let mint_owner = owner!(&accounts[1]); + let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); + + //-Process Instruction----------------------------------------------------- + let result = call_process_burn_checked!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < 9 { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if accounts[0].data_len() != Account::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if src_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !src_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)) + } else if accounts[1].data_len() != Mint::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if mint_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !mint_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)) + } else if src_init_state.unwrap() == AccountState::Frozen { + assert_eq!(result, Err(ProgramError::Custom(17))) + } else if src_is_native { + assert_eq!(result, Err(ProgramError::Custom(10))) + } else if src_init_amount < amount { + assert_eq!(result, Err(ProgramError::Custom(1))) + } else if key!(&accounts[1]) != &src_mint { + assert_eq!(result, Err(ProgramError::Custom(3))) + } else if instruction_data[8] != mint_decimals { + assert_eq!(result, Err(ProgramError::Custom(18))) + } else { + if !src_owned_sys_inc { + if old_src_delgate.is_some() && *key!(&accounts[2]) == old_src_delgate.unwrap() { + // Validate Owner + inner_test_validate_owner( + &old_src_delgate.unwrap(), // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + if old_src_delgated_amount < amount { + assert_eq!(result, Err(ProgramError::Custom(1))); + return result; + } + } else { + // Validate Owner + inner_test_validate_owner( + &src_owner, // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + } + } + + if amount == 0 && src_owner != PROGRAM_ID { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)) + } else if amount == 0 && mint_owner != &PROGRAM_ID { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)) + } else { + let src_new = get_account(&accounts[0]); + assert!(src_new.amount() == src_init_amount - amount); + assert!(get_mint(&accounts[1]).supply() == mint_init_supply - amount); + assert!(result.is_ok()); + + // Delegate updates + if old_src_delgate.is_some() && *key!(&accounts[2]) == old_src_delgate.unwrap() { + assert_eq!(src_new.delegated_amount(), old_src_delgated_amount - amount); + if old_src_delgated_amount - amount == 0 { + assert_eq!(src_new.delegate(), None); + } + } + } + } + + result +} diff --git a/specs/shared/test_process_burn_multisig.rs b/specs/shared/test_process_burn_multisig.rs new file mode 100644 index 00000000..bc41ec5b --- /dev/null +++ b/specs/shared/test_process_burn_multisig.rs @@ -0,0 +1,110 @@ +/// accounts[0] // Source Info +/// accounts[1] // Mint Info +/// accounts[2] // Authority Info +/// accounts[3..14] // Signers +/// instruction_data[0..8] // Little Endian Bytes of u64 amount +#[inline(never)] +fn test_process_burn_multisig( + accounts: &[AccountInfo; 4], + instruction_data: &[u8; 8], +) -> ProgramResult { + cheatcode_account!(&accounts[0]); + cheatcode_mint!(&accounts[1]); + cheatcode_multisig!(&accounts[2]); + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + let mint_old = get_mint(&accounts[1]); + let amount = u64::from_le_bytes([ + instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], + instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7], + ]); + let src_initialised = src_old.is_initialized(); + let src_init_amount = src_old.amount(); + let src_init_state = src_old.account_state(); + let src_is_native = src_old.is_native(); + let src_mint = src_old.mint; + let src_owned_sys_inc = src_old.is_owned_by_system_program_or_incinerator(); + let src_owner = src_old.owner; + let old_src_delgate = src_old.delegate().cloned(); + let old_src_delgated_amount = src_old.delegated_amount(); + let mint_initialised = mint_old.is_initialized(); + let mint_init_supply = mint_old.supply(); + let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); + + //-Process Instruction----------------------------------------------------- + let result = call_process_burn!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < 8 { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if accounts[0].data_len() != Account::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if src_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !src_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)) + } else if accounts[1].data_len() != Mint::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if mint_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !mint_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)) + } else if src_init_state.unwrap() == AccountState::Frozen { + assert_eq!(result, Err(ProgramError::Custom(17))) + } else if src_is_native { + assert_eq!(result, Err(ProgramError::Custom(10))) + } else if src_init_amount < amount { + assert_eq!(result, Err(ProgramError::Custom(1))) + } else if key!(&accounts[1]) != &src_mint { + assert_eq!(result, Err(ProgramError::Custom(3))) + } else { + if !src_owned_sys_inc { + if old_src_delgate.is_some() && *key!(&accounts[2]) == old_src_delgate.unwrap() { + inner_test_validate_owner( + &old_src_delgate.unwrap(), // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + if old_src_delgated_amount < amount { + assert_eq!(result, Err(ProgramError::Custom(1))); + return result; + } + } else { + inner_test_validate_owner( + &src_owner, // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + } + } + + if amount == 0 && owner!(&accounts[0]) != &PROGRAM_ID { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)) + } else if amount == 0 && owner!(&accounts[1]) != &PROGRAM_ID { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)) + } else { + let src_new = get_account(&accounts[0]); + assert!(src_new.amount() == src_init_amount - amount); + assert!(get_mint(&accounts[1]).supply() == mint_init_supply - amount); + assert!(result.is_ok()); + + // Delegate updates + if old_src_delgate.is_some() && *key!(&accounts[2]) == old_src_delgate.unwrap() { + assert_eq!(src_new.delegated_amount(), old_src_delgated_amount - amount); + if old_src_delgated_amount - amount == 0 { + assert_eq!(src_new.delegate(), None); + } + } + } + } + + result +} diff --git a/specs/shared/test_process_close_account.rs b/specs/shared/test_process_close_account.rs new file mode 100644 index 00000000..6483fa64 --- /dev/null +++ b/specs/shared/test_process_close_account.rs @@ -0,0 +1,78 @@ +/// accounts[0] // Source Info +/// accounts[1] // Destination Info +/// accounts[2] // Authority Info +#[inline(never)] +pub fn test_process_close_account(accounts: &[AccountInfo; 3]) -> ProgramResult { + cheatcode_account!(&accounts[0]); + cheatcode_account!(&accounts[1]); + cheatcode_account!(&accounts[2]); // Excluding the multisig case + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + let src_initialised = src_old.is_initialized(); + let src_data_len = accounts[0].data_len(); + let src_init_amount = src_old.amount(); + let src_init_lamports = accounts[0].lamports(); + let dst_init_lamports = accounts[1].lamports(); + let src_is_native = src_old.is_native(); + let src_owned_sys_inc = src_old.is_owned_by_system_program_or_incinerator(); + let authority = src_old.close_authority().cloned().unwrap_or(src_old.owner); + let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account + + //-Process Instruction----------------------------------------------------- + let result = call_process_close_account!(accounts); + + //-Assert Postconditions--------------------------------------------------- + if accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); + return result; + } else if same_account!(accounts[0], accounts[1]) { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if src_data_len != Account::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if src_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !src_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if !src_is_native && src_init_amount != 0 { + assert_eq!(result, Err(ProgramError::Custom(11))); + return result; + } else { + if !src_owned_sys_inc { + // Validate Owner + inner_test_validate_owner( + &authority, // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + } else if key!(&accounts[1]) != &INCINERATOR_ID { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } + if dst_init_lamports.checked_add(src_init_lamports).is_none() { + assert_eq!(result, Err(ProgramError::Custom(14))); + return result; + } + assert!(result.is_ok()); + + // Validate owner falls through to here if no error + assert_eq!( + accounts[1].lamports(), + dst_init_lamports + src_init_lamports + ); + #[cfg(any(target_os = "solana", target_arch = "bpf"))] + { + // Solana-RT only syscall + assert_eq!(*owner!(&accounts[0]), [0; 32]); + assert_eq!(accounts[0].lamports(), 0); + assert_eq!(accounts[0].data_len(), 0); + } + } + result +} diff --git a/specs/shared/test_process_close_account_multisig.rs b/specs/shared/test_process_close_account_multisig.rs new file mode 100644 index 00000000..5e4a0f35 --- /dev/null +++ b/specs/shared/test_process_close_account_multisig.rs @@ -0,0 +1,73 @@ +/// accounts[0] // Source Info +/// accounts[1] // Destination Info +/// accounts[2] // Authority Info +/// accounts[3..14] // Multisig Signers +#[inline(never)] +fn test_process_close_account_multisig(accounts: &[AccountInfo; 4]) -> ProgramResult { + cheatcode_account!(&accounts[0]); + cheatcode_account!(&accounts[1]); + cheatcode_multisig!(&accounts[2]); + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + let src_initialised = src_old.is_initialized(); + let src_data_len = accounts[0].data_len(); + let src_init_amount = src_old.amount(); + let src_init_lamports = accounts[0].lamports(); + let dst_init_lamports = accounts[1].lamports(); + let src_is_native = src_old.is_native(); + let src_owned_sys_inc = src_old.is_owned_by_system_program_or_incinerator(); + let authority = src_old.close_authority().cloned().unwrap_or(src_old.owner); + let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); + + //-Process Instruction----------------------------------------------------- + let result = call_process_close_account!(accounts); + + //-Assert Postconditions--------------------------------------------------- + if accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); + return result; + } else if same_account!(accounts[0], accounts[1]) { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if src_data_len != Account::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if src_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !src_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if !src_is_native && src_init_amount != 0 { + assert_eq!(result, Err(ProgramError::Custom(11))); + return result; + } else { + if !src_owned_sys_inc { + // Validate Owner + inner_test_validate_owner( + &authority, // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + } else if key!(&accounts[1]) != &INCINERATOR_ID { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if dst_init_lamports.checked_add(src_init_lamports).is_none() { + assert_eq!(result, Err(ProgramError::Custom(14))); + return result; + } + + // Validate owner falls through to here if no error + assert_eq!(accounts[0].lamports(), 0); + assert_eq!( + accounts[1].lamports(), + dst_init_lamports + src_init_lamports + ); + assert_eq!(accounts[0].data_len(), 0); + assert!(result.is_ok()); + } + result +} diff --git a/specs/shared/test_process_freeze_account.rs b/specs/shared/test_process_freeze_account.rs new file mode 100644 index 00000000..54f801e3 --- /dev/null +++ b/specs/shared/test_process_freeze_account.rs @@ -0,0 +1,66 @@ +/// accounts[0] // Source Account Info +/// accounts[1] // Mint Info +/// accounts[2] // Authority Info +#[inline(never)] +fn test_process_freeze_account(accounts: &[AccountInfo; 3]) -> ProgramResult { + cheatcode_account!(&accounts[0]); + cheatcode_mint!(&accounts[1]); + cheatcode_account!(&accounts[2]); // Excluding the multisig case + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + let mint_old = get_mint(&accounts[1]); + let src_initialised = src_old.is_initialized(); + let src_init_state = src_old.account_state(); + let src_is_native = src_old.is_native(); + let src_mint = src_old.mint; + let mint_initialised = mint_old.is_initialized(); + let mint_freeze_auth = mint_old.freeze_authority().cloned(); + let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account + + //-Process Instruction----------------------------------------------------- + let result = call_process_freeze_account!(accounts); + + //-Assert Postconditions--------------------------------------------------- + if accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if accounts[0].data_len() != Account::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if src_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !src_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)) + } else if src_init_state.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if src_init_state.unwrap() == AccountState::Frozen { + assert_eq!(result, Err(ProgramError::Custom(13))) + } else if src_is_native { + assert_eq!(result, Err(ProgramError::Custom(10))) + } else if key!(&accounts[1]) != &src_mint { + assert_eq!(result, Err(ProgramError::Custom(3))) + } else if accounts[1].data_len() != Mint::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if mint_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !mint_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)) + } else if mint_freeze_auth.is_none() { + assert_eq!(result, Err(ProgramError::Custom(16))) + } else { + // Validate Owner + inner_test_validate_owner( + &mint_freeze_auth.unwrap(), // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + assert_eq!( + get_account(&accounts[0]).account_state().unwrap(), + AccountState::Frozen + ); + assert!(result.is_ok()) + } + result +} diff --git a/specs/shared/test_process_freeze_account_multisig.rs b/specs/shared/test_process_freeze_account_multisig.rs new file mode 100644 index 00000000..c583f9de --- /dev/null +++ b/specs/shared/test_process_freeze_account_multisig.rs @@ -0,0 +1,67 @@ +/// accounts[0] // Source Account Info +/// accounts[1] // Mint Info +/// accounts[2] // Authority Info +/// accounts[3..13] // Signers +#[inline(never)] +fn test_process_freeze_account_multisig(accounts: &[AccountInfo; 4]) -> ProgramResult { + cheatcode_account!(&accounts[0]); + cheatcode_mint!(&accounts[1]); + cheatcode_multisig!(&accounts[2]); + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + let mint_old = get_mint(&accounts[1]); + let src_initialised = src_old.is_initialized(); + let src_init_state = src_old.account_state(); + let src_is_native = src_old.is_native(); + let src_mint = src_old.mint; + let mint_initialised = mint_old.is_initialized(); + let mint_freeze_auth = mint_old.freeze_authority().cloned(); + let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); + + //-Process Instruction----------------------------------------------------- + let result = call_process_freeze_account!(accounts); + + //-Assert Postconditions--------------------------------------------------- + if accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if accounts[0].data_len() != Account::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if src_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !src_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)) + } else if src_init_state.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if src_init_state.unwrap() == AccountState::Frozen { + assert_eq!(result, Err(ProgramError::Custom(13))) + } else if src_is_native { + assert_eq!(result, Err(ProgramError::Custom(10))) + } else if key!(&accounts[1]) != &src_mint { + assert_eq!(result, Err(ProgramError::Custom(3))) + } else if accounts[1].data_len() != Mint::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if mint_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !mint_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)) + } else if mint_freeze_auth.is_none() { + assert_eq!(result, Err(ProgramError::Custom(16))) + } else { + // Validate Owner + inner_test_validate_owner( + &mint_freeze_auth.unwrap(), // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + assert_eq!( + get_account(&accounts[0]).account_state().unwrap(), + AccountState::Frozen + ); + assert!(result.is_ok()) + } + result +} diff --git a/specs/shared/test_process_get_account_data_size.rs b/specs/shared/test_process_get_account_data_size.rs new file mode 100644 index 00000000..8a187c59 --- /dev/null +++ b/specs/shared/test_process_get_account_data_size.rs @@ -0,0 +1,28 @@ +/// accounts[0] // Mint Info +#[inline(never)] +fn test_process_get_account_data_size(accounts: &[AccountInfo; 1]) -> ProgramResult { + cheatcode_mint!(&accounts[0]); + + //-Initial State----------------------------------------------------------- + let mint_initialised = get_mint(&accounts[0]).is_initialized(); + + //-Process Instruction----------------------------------------------------- + let result = call_process_get_account_data_size!(accounts); + + //-Assert Postconditions--------------------------------------------------- + if accounts.is_empty() { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if owner!(&accounts[0]) != &PROGRAM_ID { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)) + } else if accounts[0].data_len() != Mint::LEN { + assert_eq!(result, Err(ProgramError::Custom(2))) + } else if mint_initialised.is_err() { + assert_eq!(result, Err(ProgramError::Custom(2))) + } else if !mint_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::Custom(2))) + } else { + // NOTE: This uses syscalls::sol_set_return_data + assert!(result.is_ok()) + } + result +} diff --git a/specs/shared/test_process_initialize_account.rs b/specs/shared/test_process_initialize_account.rs new file mode 100644 index 00000000..e631a267 --- /dev/null +++ b/specs/shared/test_process_initialize_account.rs @@ -0,0 +1,69 @@ +/// accounts[0] // New Account Info +/// accounts[1] // Mint Info +/// accounts[2] // Owner Info +/// accounts[3] // Rent Sysvar Info +#[inline(never)] +fn test_process_initialize_account(accounts: &[AccountInfo; 4]) -> ProgramResult { + cheatcode_account!(&accounts[0]); + cheatcode_mint!(&accounts[1]); + cheatcode_account!(&accounts[2]); + cheatcode_rent!(&accounts[3]); + + //-Initial State----------------------------------------------------------- + let initial_state_new_account = get_account(&accounts[0]).account_state(); + + let minimum_balance = get_rent(&accounts[3]).minimum_balance(accounts[0].data_len()); // TODO float problem + let is_native_mint = key!(accounts[1]) == &NATIVE_MINT_ID; + let mint_is_initialised = get_mint(&accounts[1]).is_initialized(); + + //-Process Instruction----------------------------------------------------- + let result = call_process_initialize_account!(accounts); + + //-Assert Postconditions--------------------------------------------------- + if accounts.len() < 4 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); + } else if key!(accounts[3]) != &RENT_ID { + assert_eq!(result, Err(ProgramError::InvalidArgument)) + } else if accounts[0].data_len() != Account::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if initial_state_new_account.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if initial_state_new_account.unwrap() != AccountState::Uninitialized { + assert_eq!(result, Err(ProgramError::Custom(6))) + } else if accounts[0].lamports() < minimum_balance { + assert_eq!(result, Err(ProgramError::Custom(0))) + } else if !is_native_mint && owner!(accounts[1]) != &PROGRAM_ID { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)) + } else if !is_native_mint + && (owner!(accounts[1]) == &PROGRAM_ID) + && mint_is_initialised.is_err() + { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !is_native_mint + && (owner!(accounts[1]) == &PROGRAM_ID) + && !mint_is_initialised.unwrap() + { + assert_eq!(result, Err(ProgramError::Custom(2))) + } else { + let new_account_new = get_account(&accounts[0]); + + assert!(result.is_ok()); + assert_eq!( + new_account_new.account_state().unwrap(), + AccountState::Initialized + ); + assert_eq!(new_account_new.mint, *key!(accounts[1])); + assert_eq!(new_account_new.owner, *key!(accounts[2])); + + if is_native_mint { + assert!(new_account_new.is_native()); + assert_eq!(new_account_new.native_amount().unwrap(), minimum_balance); + assert_eq!( + new_account_new.amount(), + accounts[0].lamports() - minimum_balance + ); + } + } + + result +} diff --git a/specs/shared/test_process_initialize_account2.rs b/specs/shared/test_process_initialize_account2.rs new file mode 100644 index 00000000..9ae636bd --- /dev/null +++ b/specs/shared/test_process_initialize_account2.rs @@ -0,0 +1,70 @@ +#[inline(never)] +pub fn test_process_initialize_account2( + accounts: &[AccountInfo; 3], + instruction_data: &[u8; 32], +) -> ProgramResult { + cheatcode_account!(&accounts[0]); + cheatcode_mint!(&accounts[1]); + cheatcode_rent!(&accounts[2]); + + //-Initial State----------------------------------------------------------- + let initial_state_new_account = get_account(&accounts[0]).account_state(); + + let minimum_balance = get_rent(&accounts[2]).minimum_balance(accounts[0].data_len()); + + let is_native_mint = key!(&accounts[1]) == &NATIVE_MINT_ID; + + let mint_is_initialised = get_mint(&accounts[1]).is_initialized(); + + //-Process Instruction----------------------------------------------------- + let result = call_process_initialize_account2!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < PUBKEY_BYTES { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); + } else if key!(&accounts[2]) != &RENT_ID { + assert_eq!(result, Err(ProgramError::InvalidArgument)) + } else if accounts[0].data_len() != Account::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if initial_state_new_account.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if initial_state_new_account.unwrap() != AccountState::Uninitialized { + assert_eq!(result, Err(ProgramError::Custom(6))) + } else if accounts[0].lamports() < minimum_balance { + assert_eq!(result, Err(ProgramError::Custom(0))) + } else if !is_native_mint && owner!(&accounts[1]) != &PROGRAM_ID { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)) + } else if !is_native_mint + && (owner!(&accounts[1]) == &PROGRAM_ID) + && mint_is_initialised.is_err() + { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !is_native_mint + && (owner!(&accounts[1]) == &PROGRAM_ID) + && !mint_is_initialised.unwrap() + { + assert_eq!(result, Err(ProgramError::Custom(2))) + } else { + let new_account_new = get_account(&accounts[0]); + assert!(result.is_ok()); + assert_eq!( + new_account_new.account_state().unwrap(), + AccountState::Initialized + ); + assert_eq!(new_account_new.mint, *key!(&accounts[1])); + assert_pubkey_from_slice_val!(new_account_new.owner, *instruction_data); + + if is_native_mint { + assert!(new_account_new.is_native()); + assert_eq!(new_account_new.native_amount().unwrap(), minimum_balance); + assert_eq!( + new_account_new.amount(), + accounts[0].lamports() - minimum_balance + ); + } + } + + result +} diff --git a/specs/shared/test_process_initialize_account3.rs b/specs/shared/test_process_initialize_account3.rs new file mode 100644 index 00000000..afd2f833 --- /dev/null +++ b/specs/shared/test_process_initialize_account3.rs @@ -0,0 +1,70 @@ +#[inline(never)] +pub fn test_process_initialize_account3( + accounts: &[AccountInfo; 2], + instruction_data: &[u8; 32], +) -> ProgramResult { + cheatcode_account!(&accounts[0]); + cheatcode_mint!(&accounts[1]); + + //-Initial State----------------------------------------------------------- + let initial_state_new_account = get_account(&accounts[0]).account_state(); + + // Note: Rent is a supported sysvar so ProgramError::UnsupportedSysvar should be + // impossible + let rent = Rent::get().unwrap(); + let minimum_balance = rent.minimum_balance(accounts[0].data_len()); + + let is_native_mint = key!(&accounts[1]) == &NATIVE_MINT_ID; + + let mint_is_initialised = get_mint(&accounts[1]).is_initialized(); + + //-Process Instruction----------------------------------------------------- + let result = call_process_initialize_account3!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < PUBKEY_BYTES { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if accounts.len() < 2 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); + } else if accounts[0].data_len() != Account::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if initial_state_new_account.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if initial_state_new_account.unwrap() != AccountState::Uninitialized { + assert_eq!(result, Err(ProgramError::Custom(6))) + } else if accounts[0].lamports() < minimum_balance { + assert_eq!(result, Err(ProgramError::Custom(0))) + } else if !is_native_mint && owner!(&accounts[1]) != &PROGRAM_ID { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)) + } else if !is_native_mint + && (owner!(&accounts[1]) == &PROGRAM_ID) + && mint_is_initialised.is_err() + { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !is_native_mint + && (owner!(&accounts[1]) == &PROGRAM_ID) + && !mint_is_initialised.unwrap() + { + assert_eq!(result, Err(ProgramError::Custom(2))) + } else { + let new_account_new = get_account(&accounts[0]); + assert!(result.is_ok()); + assert_eq!( + new_account_new.account_state().unwrap(), + AccountState::Initialized + ); + assert_eq!(new_account_new.mint, *key!(&accounts[1])); + assert_pubkey_from_slice_val!(new_account_new.owner, *instruction_data); + + if is_native_mint { + assert!(new_account_new.is_native()); + assert_eq!(new_account_new.native_amount().unwrap(), minimum_balance); + assert_eq!( + new_account_new.amount(), + accounts[0].lamports() - minimum_balance + ); + } + } + + result +} diff --git a/specs/shared/test_process_initialize_immutable_owner.rs b/specs/shared/test_process_initialize_immutable_owner.rs new file mode 100644 index 00000000..e5487366 --- /dev/null +++ b/specs/shared/test_process_initialize_immutable_owner.rs @@ -0,0 +1,24 @@ +#[inline(never)] +fn test_process_initialize_immutable_owner(accounts: &[AccountInfo; 1]) -> ProgramResult { + cheatcode_account!(&accounts[0]); + + //-Initial State----------------------------------------------------------- + let src_initialised = get_account(&accounts[0]).is_initialized(); + + //-Process Instruction----------------------------------------------------- + let result = call_process_initialize_immutable_owner!(accounts); + + //-Assert Postconditions--------------------------------------------------- + if accounts.len() != 1 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if accounts[0].data_len() != Account::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if src_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if src_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::Custom(6))) + } else { + assert!(result.is_ok()) + } + result +} diff --git a/specs/shared/test_process_initialize_mint2_freeze.rs b/specs/shared/test_process_initialize_mint2_freeze.rs new file mode 100644 index 00000000..c92d77bc --- /dev/null +++ b/specs/shared/test_process_initialize_mint2_freeze.rs @@ -0,0 +1,49 @@ +#[inline(never)] +pub fn test_process_initialize_mint2_freeze( + accounts: &[AccountInfo; 1], + instruction_data: &[u8; 66], +) -> ProgramResult { + cheatcode_mint!(&accounts[0]); + + //-Initial State----------------------------------------------------------- + // Note: Rent is a supported sysvar so ProgramError::UnsupportedSysvar should be + // impossible + let rent = Rent::get().unwrap(); + let minimum_balance = rent.minimum_balance(accounts[0].data_len()); + let mint_is_initialised_prior = get_mint(&accounts[0]).is_initialized(); + + //-Process Instruction----------------------------------------------------- + let result = call_process_initialize_mint2!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < 34 { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if instruction_data[33] != 0 && instruction_data[33] != 1 { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if instruction_data[33] == 1 && instruction_data.len() < 66 { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if accounts.is_empty() { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if accounts[0].data_len() != Mint::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if mint_is_initialised_prior.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if mint_is_initialised_prior.unwrap() { + assert_eq!(result, Err(ProgramError::Custom(6))) + } else if accounts[0].lamports() < minimum_balance { + assert_eq!(result, Err(ProgramError::Custom(0))) + } else { + assert!(result.is_ok()); + + let mint_new = get_mint(&accounts[0]); + assert!(mint_new.is_initialized().unwrap()); + assert_pubkey_from_slice!(mint_new.mint_authority().unwrap(), &instruction_data[1..33]); + assert_eq!(mint_new.decimals, instruction_data[0]); + + if instruction_data[33] == 1 { + assert_pubkey_from_slice!(mint_new.freeze_authority().unwrap(), &instruction_data[34..66]); + } + } + + result +} diff --git a/specs/shared/test_process_initialize_mint2_no_freeze.rs b/specs/shared/test_process_initialize_mint2_no_freeze.rs new file mode 100644 index 00000000..f5c0be5c --- /dev/null +++ b/specs/shared/test_process_initialize_mint2_no_freeze.rs @@ -0,0 +1,51 @@ +#[inline(never)] +pub fn test_process_initialize_mint2_no_freeze( + accounts: &[AccountInfo; 1], + instruction_data: &[u8; 34], +) -> ProgramResult { + cheatcode_mint!(&accounts[0]); + + //-Initial State----------------------------------------------------------- + // Note: Rent is a supported sysvar so ProgramError::UnsupportedSysvar should be + // impossible + let rent = Rent::get().unwrap(); + let minimum_balance = rent.minimum_balance(accounts[0].data_len()); + let mint_is_initialised_prior = get_mint(&accounts[0]).is_initialized(); + + //-Process Instruction----------------------------------------------------- + let result = call_process_initialize_mint2_no_freeze!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < 34 { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if instruction_data[33] != 0 && instruction_data[33] != 1 { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if instruction_data[33] == 1 && instruction_data.len() < 66 { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if accounts.is_empty() { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if accounts[0].data_len() != Mint::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if mint_is_initialised_prior.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if mint_is_initialised_prior.unwrap() { + assert_eq!(result, Err(ProgramError::Custom(6))) + } else if accounts[0].lamports() < minimum_balance { + assert_eq!(result, Err(ProgramError::Custom(0))) + } else { + assert!(result.is_ok()); + + let mint_new = get_mint(&accounts[0]); + assert!(mint_new.is_initialized().unwrap()); + assert_pubkey_from_slice!(mint_new.mint_authority().unwrap(), &instruction_data[1..33]); + assert_eq!(mint_new.decimals, instruction_data[0]); + + #[allow(clippy::out_of_bounds_indexing)] + // Guard above prevents this branch TODO: Perhaps remove? + if instruction_data[33] == 1 { + assert_pubkey_from_slice!(mint_new.freeze_authority().unwrap(), &instruction_data[34..66]); + } + } + + result +} diff --git a/specs/shared/test_process_initialize_mint_freeze.rs b/specs/shared/test_process_initialize_mint_freeze.rs new file mode 100644 index 00000000..da5cf82d --- /dev/null +++ b/specs/shared/test_process_initialize_mint_freeze.rs @@ -0,0 +1,56 @@ +/// accounts[0] // Mint Info +/// accounts[1] // Rent Sysvar Info +/// instruction_data[0] // Decimals +/// instruction_data[1..33] // Mint Authority Pubkey +/// instruction_data[33] // Freeze Authority Exists? 1 for freeze +/// instruction_data[34..66] // instruction_data[33] == 1 ==> Freeze Authority +/// Pubkey +#[inline(never)] +fn test_process_initialize_mint_freeze( + accounts: &[AccountInfo; 2], + instruction_data: &[u8; 66], +) -> ProgramResult { + cheatcode_mint!(&accounts[0]); + cheatcode_rent!(&accounts[1]); + + //-Initial State----------------------------------------------------------- + let minimum_balance = get_rent(&accounts[1]).minimum_balance(accounts[0].data_len()); // TODO float problem + let mint_is_initialised_prior = get_mint(&accounts[0]).is_initialized(); + + //-Process Instruction----------------------------------------------------- + let result = call_process_initialize_mint!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < 34 { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if instruction_data[33] != 0 && instruction_data[33] != 1 { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if instruction_data[33] == 1 && instruction_data.len() < 66 { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if accounts.len() < 2 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if accounts[0].data_len() != Mint::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if key!(accounts[1]) != &RENT_ID { + assert_eq!(result, Err(ProgramError::InvalidArgument)) + } else if mint_is_initialised_prior.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if mint_is_initialised_prior.unwrap() { + assert_eq!(result, Err(ProgramError::Custom(6))) + } else if accounts[0].lamports() < minimum_balance { + assert_eq!(result, Err(ProgramError::Custom(0))) + } else { + assert!(result.is_ok()); + + let mint_new = get_mint(&accounts[0]); + assert!(mint_new.is_initialized().unwrap()); + assert_pubkey_from_slice!(mint_new.mint_authority().unwrap(), &instruction_data[1..33]); + assert_eq!(mint_new.decimals, instruction_data[0]); + + if instruction_data[33] == 1 { + assert_pubkey_from_slice!(mint_new.freeze_authority().unwrap(), &instruction_data[34..66]); + } + } + + result +} diff --git a/specs/shared/test_process_initialize_mint_no_freeze.rs b/specs/shared/test_process_initialize_mint_no_freeze.rs new file mode 100644 index 00000000..bd6e44d1 --- /dev/null +++ b/specs/shared/test_process_initialize_mint_no_freeze.rs @@ -0,0 +1,56 @@ +/// accounts[0] // Mint Info +/// accounts[1] // Rent Sysvar Info +/// instruction_data[0] // Decimals +/// instruction_data[1..33] // Mint Authority Pubkey +/// instruction_data[33] // Freeze Authority Exists? 0 for no freeze +#[inline(never)] +fn test_process_initialize_mint_no_freeze( + accounts: &[AccountInfo; 2], + instruction_data: &[u8; 34], +) -> ProgramResult { + cheatcode_mint!(&accounts[0]); + cheatcode_rent!(&accounts[1]); + + //-Initial State----------------------------------------------------------- + let minimum_balance = get_rent(&accounts[1]).minimum_balance(accounts[0].data_len()); // TODO float problem + let mint_is_initialised_prior = get_mint(&accounts[0]).is_initialized(); + + //-Process Instruction----------------------------------------------------- + let result = call_process_initialize_mint_no_freeze!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < 34 { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if instruction_data[33] != 0 && instruction_data[33] != 1 { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if instruction_data[33] == 1 && instruction_data.len() < 66 { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if accounts.len() < 2 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if accounts[0].data_len() != Mint::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if key!(accounts[1]) != &RENT_ID { + assert_eq!(result, Err(ProgramError::InvalidArgument)) + } else if mint_is_initialised_prior.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if mint_is_initialised_prior.unwrap() { + assert_eq!(result, Err(ProgramError::Custom(6))) + } else if accounts[0].lamports() < minimum_balance { + assert_eq!(result, Err(ProgramError::Custom(0))) + } else { + assert!(result.is_ok()); + + let mint_new = get_mint(&accounts[0]); + assert!(mint_new.is_initialized().unwrap()); + assert_pubkey_from_slice!(mint_new.mint_authority().unwrap(), &instruction_data[1..33]); + assert_eq!(mint_new.decimals, instruction_data[0]); + + #[allow(clippy::out_of_bounds_indexing)] + // Guard above prevents this branch TODO: Perhaps remove? + if instruction_data[33] == 1 { + assert_pubkey_from_slice!(mint_new.freeze_authority().unwrap(), &instruction_data[34..66]); + } + } + + result +} diff --git a/specs/shared/test_process_initialize_multisig.rs b/specs/shared/test_process_initialize_multisig.rs new file mode 100644 index 00000000..ad6bbe25 --- /dev/null +++ b/specs/shared/test_process_initialize_multisig.rs @@ -0,0 +1,61 @@ +/// accounts[0] // Multisig Info +/// accounts[1] // Rent Sysvar Info +/// accounts[2..] // Signers +/// accounts[2..].len() // n +/// instruction_data[1] // m +#[inline(never)] +fn test_process_initialize_multisig( + accounts: &[AccountInfo; 5], + instruction_data: &[u8; 1], +) -> ProgramResult { + cheatcode_multisig!(&accounts[0]); + cheatcode_rent!(&accounts[1]); + cheatcode_account!(&accounts[2]); // Signer + cheatcode_account!(&accounts[3]); // Signer + cheatcode_account!(&accounts[4]); // Signer + + //-Initial State----------------------------------------------------------- + let multisig_already_initialised = get_multisig(&accounts[0]).is_initialized(); + let multisig_init_lamports = accounts[0].lamports(); + let minimum_balance = get_rent(&accounts[1]).minimum_balance(accounts[0].data_len()); + + //-Process Instruction----------------------------------------------------- + let result = call_process_initialize_multisig!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.is_empty() { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if accounts.len() < 2 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if key!(&accounts[1]) != &RENT_ID { + assert_eq!(result, Err(ProgramError::InvalidArgument)) + } else if accounts[0].data_len() != Multisig::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if multisig_already_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if multisig_already_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::Custom(6))) + } else if multisig_init_lamports < minimum_balance { + assert_eq!(result, Err(ProgramError::Custom(0))) + } else if !((1..=11).contains(&(accounts.len() - 2))) { + assert_eq!(result, Err(ProgramError::Custom(7))) + } else if !(1..=11).contains(&instruction_data[0]) { + assert_eq!(result, Err(ProgramError::Custom(8))) + } else { + let multisig_new = get_multisig(&accounts[0]); + assert!(accounts[2..] + .iter() + .map(|signer| *key!(signer)) + .eq(multisig_new.signers + .iter() + .take(accounts[2..].len()) + .copied())); + assert_eq!(multisig_new.m, instruction_data[0]); + assert_eq!(multisig_new.n as usize, accounts.len() - 2); + assert!(multisig_new.is_initialized().is_ok()); + assert!(multisig_new.is_initialized().unwrap()); + assert!(result.is_ok()) + } + + result +} diff --git a/specs/shared/test_process_initialize_multisig2.rs b/specs/shared/test_process_initialize_multisig2.rs new file mode 100644 index 00000000..607b6b62 --- /dev/null +++ b/specs/shared/test_process_initialize_multisig2.rs @@ -0,0 +1,60 @@ +/// accounts[0] // Multisig Info +/// accounts[1..] // Signers +/// accounts[1..].len() // n +/// instruction_data[1] // m +#[inline(never)] +fn test_process_initialize_multisig2( + accounts: &[AccountInfo; 4], + instruction_data: &[u8; 1], +) -> ProgramResult { + cheatcode_multisig!(&accounts[0]); + cheatcode_account!(&accounts[1]); // Signer + cheatcode_account!(&accounts[2]); // Signer + cheatcode_account!(&accounts[3]); // Signer + + //-Initial State----------------------------------------------------------- + let multisig_already_initialised = get_multisig(&accounts[0]).is_initialized(); + let multisig_init_lamports = accounts[0].lamports(); + // Note: Rent is a supported sysvar so ProgramError::UnsupportedSysvar should be + // impossible + let rent = Rent::get().unwrap(); + let minimum_balance = rent.minimum_balance(accounts[0].data_len()); + + //-Process Instruction----------------------------------------------------- + let result = call_process_initialize_multisig2!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.is_empty() { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if accounts.is_empty() { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if accounts[0].data_len() != Multisig::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if multisig_already_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if multisig_already_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::Custom(6))) + } else if multisig_init_lamports < minimum_balance { + assert_eq!(result, Err(ProgramError::Custom(0))) + } else if !((1..=11).contains(&(accounts.len() - 1))) { + assert_eq!(result, Err(ProgramError::Custom(7))) + } else if !(1..=11).contains(&instruction_data[0]) { + assert_eq!(result, Err(ProgramError::Custom(8))) + } else { + let multisig_new = get_multisig(&accounts[0]); + assert!(accounts[1..] + .iter() + .map(|signer| *key!(signer)) + .eq(multisig_new.signers + .iter() + .take(accounts[1..].len()) + .copied())); + assert_eq!(multisig_new.m, instruction_data[0]); + assert_eq!(multisig_new.n as usize, accounts.len() - 1); + assert!(multisig_new.is_initialized().is_ok()); + assert!(multisig_new.is_initialized().unwrap()); + assert!(result.is_ok()) + } + + result +} diff --git a/specs/shared/test_process_mint_to.rs b/specs/shared/test_process_mint_to.rs new file mode 100644 index 00000000..d97f319b --- /dev/null +++ b/specs/shared/test_process_mint_to.rs @@ -0,0 +1,114 @@ +/// accounts[0] // Mint Info +/// accounts[1] // Destination Info +/// accounts[2] // Owner Info +/// instruction_data[0..8] // Little Endian Bytes of u64 amount +#[inline(never)] +fn test_process_mint_to( + accounts: &[AccountInfo; 3], + instruction_data: &[u8; 8], +) -> ProgramResult { + cheatcode_mint!(&accounts[0]); + cheatcode_account!(&accounts[1]); + cheatcode_account!(&accounts[2]); // Excluding the multisig case + + //-Initial State----------------------------------------------------------- + let mint_old = get_mint(&accounts[0]); + let dst_old = get_account(&accounts[1]); + let initial_supply = mint_old.supply(); + let initial_amount = dst_old.amount(); + let mint_initialised = mint_old.is_initialized(); + let dst_initialised = dst_old.is_initialized(); + let dst_init_state = dst_old.account_state(); + let maybe_multisig_is_initialised = None; + + #[cfg(feature = "assumptions")] + { + // Do not execute if adding to the account balance would overflow. + // shared::mint_to.rs,L68 is based on the assumption that initial_amount <= + // mint.supply and therefore cannot overflow because the minting itself + // would already error out. + let amount = u64::from_le_bytes([ + instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], + instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7], + ]); + if initial_amount.checked_add(amount).is_none() { + return Err(ProgramError::Custom(99)); + } + } + + //-Process Instruction----------------------------------------------------- + let result = call_process_mint_to!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < 8 { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } else if accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); + return result; + } else if accounts[1].data_len() != Account::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if dst_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !dst_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if dst_init_state.unwrap() == AccountState::Frozen { + assert_eq!(result, Err(ProgramError::Custom(17))); + return result; + } else if get_account(&accounts[1]).is_native() { + assert_eq!(result, Err(ProgramError::Custom(10))); + return result; + } else if key!(&accounts[0]) != &get_account(&accounts[1]).mint { + assert_eq!(result, Err(ProgramError::Custom(3))); + return result; + } else if accounts[0].data_len() != Mint::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if mint_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !mint_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else { + let mint_new = get_mint(&accounts[0]); + let mint_authority_new = mint_new.mint_authority(); + if mint_authority_new.is_some() { + inner_test_validate_owner( + mint_authority_new.unwrap(), // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + } else { + assert_eq!(result, Err(ProgramError::Custom(5))); + return result; + } + + let amount = u64::from_le_bytes([ + instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], + instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7], + ]); + + if amount == 0 && owner!(&accounts[0]) != &PROGRAM_ID { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)); + return result; + } else if amount == 0 && owner!(&accounts[1]) != &PROGRAM_ID { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)); + return result; + } else if amount != 0 && amount.checked_add(initial_supply).is_none() { + assert_eq!(result, Err(ProgramError::Custom(14))); + return result; + } + + assert_eq!(mint_new.supply(), initial_supply + amount); + assert_eq!(get_account(&accounts[1]).amount(), initial_amount + amount); + assert!(result.is_ok()); + } + + result +} diff --git a/specs/shared/test_process_mint_to_checked.rs b/specs/shared/test_process_mint_to_checked.rs new file mode 100644 index 00000000..a3d08757 --- /dev/null +++ b/specs/shared/test_process_mint_to_checked.rs @@ -0,0 +1,117 @@ +/// accounts[0] // Mint Info +/// accounts[1] // Destination Info +/// accounts[2] // Owner Info +/// instruction_data[0..9] // Little Endian Bytes of u64 amount, and decimals +#[inline(never)] +fn test_process_mint_to_checked( + accounts: &[AccountInfo; 3], + instruction_data: &[u8; 9], +) -> ProgramResult { + cheatcode_mint!(&accounts[0]); + cheatcode_account!(&accounts[1]); + cheatcode_account!(&accounts[2]); // Excluding the multisig case + + //-Initial State----------------------------------------------------------- + let mint_old = get_mint(&accounts[0]); + let dst_old = get_account(&accounts[1]); + let initial_supply = mint_old.supply(); + let initial_amount = dst_old.amount(); + let mint_initialised = mint_old.is_initialized(); + let dst_initialised = dst_old.is_initialized(); + let dst_init_state = dst_old.account_state(); + let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account + + #[cfg(feature = "assumptions")] + { + // Do not execute if adding to the account balance would overflow. + // shared::mint_to.rs,L68 is based on the assumption that initial_amount <= + // mint.supply() and therefore cannot overflow because the minting itself + // would already error out. + let amount = unsafe { u64::from_le_bytes(*(instruction_data.as_ptr() as *const [u8; 8])) }; + if initial_amount.checked_add(amount).is_none() { + return Err(ProgramError::Custom(99)); + } + } + + //-Process Instruction----------------------------------------------------- + let result = call_process_mint_to_checked!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + let mint_new = get_mint(&accounts[0]); + let dst_new = get_account(&accounts[1]); + + if instruction_data.len() < 9 { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } else if accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); + return result; + } else if accounts[1].data_len() != Account::LEN { + // TODO Daniel: is it possible for something to be provided that has the same + // len but is not an account? + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if dst_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !dst_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if dst_init_state.unwrap() == AccountState::Frozen { + // unwrap must succeed due to dst_initialised not being err + assert_eq!(result, Err(ProgramError::Custom(17))); + return result; + } else if dst_new.is_native() { + assert_eq!(result, Err(ProgramError::Custom(10))); + return result; + } else if key!(accounts[0]) != &dst_new.mint { + assert_eq!(result, Err(ProgramError::Custom(3))); + return result; + } else if accounts[0].data_len() != Mint::LEN { + // Not sure if this is even possible if we get past the case above + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if mint_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !mint_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if instruction_data[8] != mint_new.decimals { + assert_eq!(result, Err(ProgramError::Custom(18))); + return result; + } else { + if mint_new.mint_authority().is_some() { + // Validate Owner + inner_test_validate_owner( + mint_new.mint_authority().unwrap(), // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + } else { + assert_eq!(result, Err(ProgramError::Custom(5))); + return result; + } + + let amount = unsafe { u64::from_le_bytes(*(instruction_data.as_ptr() as *const [u8; 8])) }; + + if amount == 0 && owner!(accounts[0]) != &PROGRAM_ID { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)); + return result; + } else if amount == 0 && owner!(accounts[1]) != &PROGRAM_ID { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)); + return result; + } else if amount != 0 && initial_supply.checked_add(amount).is_none() { + assert_eq!(result, Err(ProgramError::Custom(14))); + return result; + } + + assert_eq!(mint_new.supply(), initial_supply + amount); + assert_eq!(dst_new.amount(), initial_amount + amount); + assert!(result.is_ok()); + } + + result +} diff --git a/specs/shared/test_process_mint_to_checked_multisig.rs b/specs/shared/test_process_mint_to_checked_multisig.rs new file mode 100644 index 00000000..07deb2cd --- /dev/null +++ b/specs/shared/test_process_mint_to_checked_multisig.rs @@ -0,0 +1,107 @@ +/// accounts[0] // Mint Info +/// accounts[1] // Destination Info +/// accounts[2] // Owner Info +/// accounts[3..14] // Signers +/// instruction_data[0..9] // Little Endian Bytes of u64 amount, and decimals +#[inline(never)] +fn test_process_mint_to_checked_multisig( + accounts: &[AccountInfo; 4], + instruction_data: &[u8; 9], +) -> ProgramResult { + cheatcode_mint!(&accounts[0]); + cheatcode_account!(&accounts[1]); + cheatcode_multisig!(&accounts[2]); + + //-Initial State----------------------------------------------------------- + let mint_old = get_mint(&accounts[0]); + let dst_old = get_account(&accounts[1]); + let initial_supply = mint_old.supply(); + let initial_amount = dst_old.amount(); + let mint_initialised = mint_old.is_initialized(); + let dst_initialised = dst_old.is_initialized(); + let dst_init_state = dst_old.account_state(); + let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); + + //-Process Instruction----------------------------------------------------- + let result = call_process_mint_to_checked!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < 9 { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } else if accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); + return result; + } else if accounts[1].data_len() != Account::LEN { + // TODO Daniel: is it possible for something to be provided that has the same + // len but is not an account? + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if dst_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !dst_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if dst_init_state.unwrap() == AccountState::Frozen { + // unwrap must succeed due to dst_initialised not being err + assert_eq!(result, Err(ProgramError::Custom(17))); + return result; + } else if get_account(&accounts[1]).is_native() { + assert_eq!(result, Err(ProgramError::Custom(10))); + return result; + } else if key!(&accounts[0]) != &get_account(&accounts[1]).mint { + assert_eq!(result, Err(ProgramError::Custom(3))); + return result; + } else if accounts[0].data_len() != Mint::LEN { + // Not sure if this is even possible if we get past the case above + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if mint_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !mint_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if instruction_data[8] != get_mint(&accounts[0]).decimals { + assert_eq!(result, Err(ProgramError::Custom(18))); + return result; + } else { + let mint_new = get_mint(&accounts[0]); + if mint_new.mint_authority().is_some() { + // Validate Owner + inner_test_validate_owner( + mint_new.mint_authority().unwrap(), // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + } else { + assert_eq!(result, Err(ProgramError::Custom(5))); + return result; + } + + let amount = u64::from_le_bytes([ + instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], + instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7], + ]); + + if amount == 0 && owner!(&accounts[0]) != &PROGRAM_ID { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)); + return result; + } else if amount == 0 && owner!(&accounts[1]) != &PROGRAM_ID { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)); + return result; + } else if amount != 0 && amount.checked_add(initial_supply).is_none() { + assert_eq!(result, Err(ProgramError::Custom(14))); + return result; + } + + assert_eq!(get_mint(&accounts[0]).supply(), initial_supply + amount); + assert_eq!(get_account(&accounts[1]).amount(), initial_amount + amount); + assert!(result.is_ok()); + } + + result +} diff --git a/specs/shared/test_process_mint_to_multisig.rs b/specs/shared/test_process_mint_to_multisig.rs new file mode 100644 index 00000000..a48d094a --- /dev/null +++ b/specs/shared/test_process_mint_to_multisig.rs @@ -0,0 +1,100 @@ +/// accounts[0] // Mint Info +/// accounts[1] // Destination Info +/// accounts[2] // Owner Info +/// accounts[3..14] // Signers +/// instruction_data[0..8] // Little Endian Bytes of u64 amount +#[inline(never)] +fn test_process_mint_to_multisig( + accounts: &[AccountInfo; 4], + instruction_data: &[u8; 8], +) -> ProgramResult { + cheatcode_mint!(&accounts[0]); + cheatcode_account!(&accounts[1]); + cheatcode_multisig!(&accounts[2]); + + //-Initial State----------------------------------------------------------- + let mint_old = get_mint(&accounts[0]); + let dst_old = get_account(&accounts[1]); + let initial_supply = mint_old.supply(); + let initial_amount = dst_old.amount(); + let mint_initialised = mint_old.is_initialized(); + let dst_initialised = dst_old.is_initialized(); + let dst_init_state = dst_old.account_state(); + let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); + + //-Process Instruction----------------------------------------------------- + let result = call_process_mint_to!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < 8 { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } else if accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); + return result; + } else if accounts[1].data_len() != Account::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if dst_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !dst_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if dst_init_state.unwrap() == AccountState::Frozen { + assert_eq!(result, Err(ProgramError::Custom(17))); + return result; + } else if get_account(&accounts[1]).is_native() { + assert_eq!(result, Err(ProgramError::Custom(10))); + return result; + } else if key!(&accounts[0]) != &get_account(&accounts[1]).mint { + assert_eq!(result, Err(ProgramError::Custom(3))); + return result; + } else if accounts[0].data_len() != Mint::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if mint_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !mint_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else { + let mint_new = get_mint(&accounts[0]); + let mint_authority_new = mint_new.mint_authority(); + if mint_authority_new.is_some() { + inner_test_validate_owner( + mint_authority_new.unwrap(), // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + } else { + assert_eq!(result, Err(ProgramError::Custom(5))); + return result; + } + + let amount = u64::from_le_bytes([ + instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], + instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7], + ]); + + if amount == 0 && owner!(&accounts[0]) != &PROGRAM_ID { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)); + return result; + } else if amount == 0 && owner!(&accounts[1]) != &PROGRAM_ID { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)); + return result; + } else if amount != 0 && amount.checked_add(initial_supply).is_none() { + assert_eq!(result, Err(ProgramError::Custom(14))); + return result; + } + + assert_eq!(mint_new.supply(), initial_supply + amount); + assert_eq!(get_account(&accounts[1]).amount(), initial_amount + amount); + assert!(result.is_ok()); + } + + result +} diff --git a/specs/shared/test_process_revoke.rs b/specs/shared/test_process_revoke.rs new file mode 100644 index 00000000..6c083217 --- /dev/null +++ b/specs/shared/test_process_revoke.rs @@ -0,0 +1,49 @@ +/// accounts[0] // Source Account Info +/// accounts[1] // Owner Info +/// accounts[2..13] // Signers +#[inline(never)] +fn test_process_revoke(accounts: &[AccountInfo; 2]) -> ProgramResult { + cheatcode_account!(&accounts[0]); // Source Account + cheatcode_account!(&accounts[1]); // Owner + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + let src_initialised = src_old.is_initialized(); + let src_init_state = src_old.account_state(); + let src_owner = src_old.owner; + let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account + + //-Process Instruction----------------------------------------------------- + let result = call_process_revoke!(accounts); + + //-Assert Postconditions--------------------------------------------------- + if accounts.is_empty() { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if accounts[0].data_len() != Account::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if src_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !src_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)) + } else if accounts.len() < 2 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if src_init_state.unwrap() == AccountState::Frozen { + assert_eq!(result, Err(ProgramError::Custom(17))) + } else { + // Validate Owner + inner_test_validate_owner( + &src_owner, // expected_owner + &accounts[1], // owner_account_info + &accounts[2..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + let src_new = get_account(&accounts[0]); + assert!(src_new.delegate().is_none()); + assert_eq!(src_new.delegated_amount(), 0); + assert!(result.is_ok()) + } + + result +} diff --git a/specs/shared/test_process_revoke_multisig.rs b/specs/shared/test_process_revoke_multisig.rs new file mode 100644 index 00000000..1f30ad69 --- /dev/null +++ b/specs/shared/test_process_revoke_multisig.rs @@ -0,0 +1,49 @@ +/// accounts[0] // Source Account Info +/// accounts[1] // Owner Info +/// accounts[2..13] // Signers +#[inline(never)] +fn test_process_revoke_multisig(accounts: &[AccountInfo; 3]) -> ProgramResult { + cheatcode_account!(&accounts[0]); // Source Account + cheatcode_multisig!(&accounts[1]); // Owner + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + let src_initialised = src_old.is_initialized(); + let src_init_state = src_old.account_state(); + let src_owner = src_old.owner; + let maybe_multisig_is_initialised = Some(get_multisig(&accounts[1]).is_initialized()); + + //-Process Instruction----------------------------------------------------- + let result = call_process_revoke!(accounts); + + //-Assert Postconditions--------------------------------------------------- + if accounts.is_empty() { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if accounts[0].data_len() != Account::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if src_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !src_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)) + } else if accounts.len() < 2 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if src_init_state.unwrap() == AccountState::Frozen { + assert_eq!(result, Err(ProgramError::Custom(17))) + } else { + // Validate Owner + inner_test_validate_owner( + &src_owner, // expected_owner + &accounts[1], // owner_account_info + &accounts[2..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + let src_new = get_account(&accounts[0]); + assert!(src_new.delegate().is_none()); + assert_eq!(src_new.delegated_amount(), 0); + assert!(result.is_ok()) + } + + result +} diff --git a/specs/shared/test_process_set_authority_account.rs b/specs/shared/test_process_set_authority_account.rs new file mode 100644 index 00000000..7844abf2 --- /dev/null +++ b/specs/shared/test_process_set_authority_account.rs @@ -0,0 +1,112 @@ +/// accounts[0] // Account Info - Account Case +/// accounts[1] // Authority Info +/// instruction_data[0] // Authority Type (instruction) +/// instruction_data[1] // New Authority Follows (0 -> No, 1 -> Yes) +/// instruction_data[2..34] // New Authority Pubkey +#[inline(never)] +fn test_process_set_authority_account( + accounts: &[AccountInfo; 2], + instruction_data: &[u8; 34], +) -> ProgramResult { + cheatcode_account!(&accounts[0]); // Assume Account + cheatcode_account!(&accounts[1]); // Excluding the multisig case + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + let src_initialised = src_old.is_initialized(); + let src_init_state = src_old.account_state(); + let src_owner = src_old.owner; + let authority = src_old.close_authority().cloned().unwrap_or(src_old.owner); + let account_data_len = accounts[0].data_len(); + let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account + + //-Process Instruction----------------------------------------------------- + let result = call_process_set_authority!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < 2 { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } else if !(0..=3).contains(&instruction_data[0]) { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } else if instruction_data[1] != 0 && instruction_data[1] != 1 { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } else if instruction_data[1] == 1 && instruction_data.len() < 34 { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } else if accounts.len() < 2 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); + return result; + } else if account_data_len != Account::LEN && account_data_len != Mint::LEN { + assert_eq!(result, Err(ProgramError::InvalidArgument)); + return result; + } else if account_data_len == Account::LEN { + // established by cheatcode_is_account + + let src_new = get_account(&accounts[0]); + + if src_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !src_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if src_init_state.unwrap() == AccountState::Frozen { + assert_eq!(result, Err(ProgramError::Custom(17))); + return result; + } else if instruction_data[0] != 2 && instruction_data[0] != 3 { + // AuthorityType neither AccountOwner nor CloseAccount + assert_eq!(result, Err(ProgramError::Custom(15))); + return result; + } else if instruction_data[0] == 2 { + // AccountOwner + // Validate Owner + inner_test_validate_owner( + &src_owner, // expected_owner + &accounts[1], // owner_account_info + &accounts[2..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + if instruction_data[1] != 1 || instruction_data.len() < 34 { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } + + assert_pubkey_from_slice_val!(src_new.owner, instruction_data[2..34]); + assert_eq!(src_new.delegate(), None); + assert_eq!(src_new.delegated_amount(), 0); + if src_new.is_native() { + assert_eq!(src_new.close_authority(), None); + } + assert!(result.is_ok()) + } else { + // CloseAccount + assert_eq!(instruction_data[0], 3); // If not AccountOwner (2), must be CloseAccount (3) + + // Validate Owner + inner_test_validate_owner( + &authority, // expected_owner + &accounts[1], // owner_account_info + &accounts[2..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + if instruction_data[1] == 1 { + // 1 ==> 34 <= instruction_data.len() + assert_pubkey_from_slice!(src_new.close_authority().unwrap(), &instruction_data[2..34]); + } else { + assert_eq!(src_new.close_authority(), None); + } + assert!(result.is_ok()) + } + } else { + unreachable!() // account_data_len == Account::LEN must hold + } + + result +} diff --git a/specs/shared/test_process_set_authority_account_multisig.rs b/specs/shared/test_process_set_authority_account_multisig.rs new file mode 100644 index 00000000..e70033e3 --- /dev/null +++ b/specs/shared/test_process_set_authority_account_multisig.rs @@ -0,0 +1,113 @@ +/// accounts[0] // Account Info - Account Case +/// accounts[1] // Authority Info +/// accounts[2..13] // Signers +/// instruction_data[0] // Authority Type (instruction) +/// instruction_data[1] // New Authority Follows (0 -> No, 1 -> Yes) +/// instruction_data[2..34] // New Authority Pubkey +#[inline(never)] +fn test_process_set_authority_account_multisig( + accounts: &[AccountInfo; 3], + instruction_data: &[u8; 34], +) -> ProgramResult { + cheatcode_account!(&accounts[0]); // Assume Account + cheatcode_multisig!(&accounts[1]); // Authority + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + let src_initialised = src_old.is_initialized(); + let src_init_state = src_old.account_state(); + let src_owner = src_old.owner; + let authority = src_old.close_authority().cloned().unwrap_or(src_old.owner); + let account_data_len = accounts[0].data_len(); + let maybe_multisig_is_initialised = Some(get_multisig(&accounts[1]).is_initialized()); + + //-Process Instruction----------------------------------------------------- + let result = call_process_set_authority!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < 2 { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } else if !(0..=3).contains(&instruction_data[0]) { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } else if instruction_data[1] != 0 && instruction_data[1] != 1 { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } else if instruction_data[1] == 1 && instruction_data.len() < 34 { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } else if accounts.len() < 2 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); + return result; + } else if account_data_len != Account::LEN && account_data_len != Mint::LEN { + assert_eq!(result, Err(ProgramError::InvalidArgument)); + return result; + } else if account_data_len == Account::LEN { + // established by cheatcode_is_account + + let src_new = get_account(&accounts[0]); + + if src_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !src_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if src_init_state.unwrap() == AccountState::Frozen { + assert_eq!(result, Err(ProgramError::Custom(17))); + return result; + } else if instruction_data[0] != 2 && instruction_data[0] != 3 { + // AuthorityType neither AccountOwner nor CloseAccount + assert_eq!(result, Err(ProgramError::Custom(15))); + return result; + } else if instruction_data[0] == 2 { + // AccountOwner + // Validate Owner + inner_test_validate_owner( + &src_owner, // expected_owner + &accounts[1], // owner_account_info + &accounts[2..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + if instruction_data[1] != 1 || instruction_data.len() < 34 { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } + + assert_pubkey_from_slice_val!(src_new.owner, instruction_data[2..34]); + assert_eq!(src_new.delegate(), None); + assert_eq!(src_new.delegated_amount(), 0); + if src_new.is_native() { + assert_eq!(src_new.close_authority(), None); + } + assert!(result.is_ok()) + } else { + // CloseAccount + assert_eq!(instruction_data[0], 3); // If not AccountOwner (2), must be CloseAccount (3) + + // Validate Owner + inner_test_validate_owner( + &authority, // expected_owner + &accounts[1], // owner_account_info + &accounts[2..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + if instruction_data[1] == 1 { + // 1 ==> 34 <= instruction_data.len() + assert_pubkey_from_slice!(src_new.close_authority().unwrap(), &instruction_data[2..34]); + } else { + assert_eq!(src_new.close_authority(), None); + } + assert!(result.is_ok()) + } + } else { + unreachable!() // account_data_len == Account::LEN must hold + } + + result +} diff --git a/specs/shared/test_process_set_authority_mint.rs b/specs/shared/test_process_set_authority_mint.rs new file mode 100644 index 00000000..c3d3b356 --- /dev/null +++ b/specs/shared/test_process_set_authority_mint.rs @@ -0,0 +1,109 @@ +/// accounts[0] // Account Info - Mint Case +/// accounts[1] // Authority Info +/// instruction_data[0] // Authority Type (instruction) +#[inline(never)] +fn test_process_set_authority_mint( + accounts: &[AccountInfo; 2], + instruction_data: &[u8; 34], +) -> ProgramResult { + cheatcode_mint!(&accounts[0]); // Assume Mint + cheatcode_account!(&accounts[1]); // Authority + + //-Initial State----------------------------------------------------------- + let mint_old = get_mint(&accounts[0]); + let mint_data_len = accounts[0].data_len(); + let old_mint_authority_is_none = mint_old.mint_authority().is_none(); + let old_freeze_authority_is_none = mint_old.freeze_authority().is_none(); + let old_mint_authority = mint_old.mint_authority().cloned(); + let old_freeze_authority = mint_old.freeze_authority().cloned(); + let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account + let mint_is_initialised = mint_old.is_initialized(); + + //-Process Instruction----------------------------------------------------- + let result = call_process_set_authority!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < 2 { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } else if !(0..=3).contains(&instruction_data[0]) { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } else if instruction_data[1] != 0 && instruction_data[1] != 1 { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } else if instruction_data[1] == 1 && instruction_data.len() < 34 { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } else if accounts.len() < 2 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); + return result; + } else if mint_data_len != Account::LEN && mint_data_len != Mint::LEN { + assert_eq!(result, Err(ProgramError::InvalidArgument)); + return result; + } else if mint_data_len == Mint::LEN { + // established by cheatcode_mint + + let mint_new = get_mint(&accounts[0]); + + if !mint_is_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if instruction_data[0] != 0 && instruction_data[0] != 1 { + // AuthorityType neither MintTokens nor FreezeAccount + assert_eq!(result, Err(ProgramError::Custom(15))); + return result; + } else if instruction_data[0] == 0 { + // MintTokens + if old_mint_authority_is_none { + assert_eq!(result, Err(ProgramError::Custom(5))); + return result; + } + + // Validate Owner + inner_test_validate_owner( + &old_mint_authority.unwrap(), // expected_owner + &accounts[1], // owner_account_info + &accounts[2..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + if instruction_data[1] == 1 { + // 1 ==> 34 <= instruction_data.len() + assert_pubkey_from_slice!(mint_new.mint_authority().unwrap(), &instruction_data[2..34]); + } else { + assert_eq!(mint_new.mint_authority(), None); + } + assert!(result.is_ok()) + } else { + // FreezeAccount + assert_eq!(instruction_data[0], 1); // If not MintTokens (0), must be FreezeAccount (1) + if old_freeze_authority_is_none { + assert_eq!(result, Err(ProgramError::Custom(16))); + return result; + } + + // Validate Owner + inner_test_validate_owner( + &old_freeze_authority.unwrap(), // expected_owner + &accounts[1], // owner_account_info + &accounts[2..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + if instruction_data[1] == 1 { + // 1 ==> 34 <= instruction_data.len() + assert_pubkey_from_slice!(mint_new.freeze_authority().unwrap(), &instruction_data[2..34]); + } else { + assert_eq!(mint_new.freeze_authority(), None); + } + assert!(result.is_ok()) + } + } else { + unreachable!(); // mint_data_len == Mint::LEN must hold + } + + result +} diff --git a/specs/shared/test_process_set_authority_mint_multisig.rs b/specs/shared/test_process_set_authority_mint_multisig.rs new file mode 100644 index 00000000..cdb7a16c --- /dev/null +++ b/specs/shared/test_process_set_authority_mint_multisig.rs @@ -0,0 +1,106 @@ +#[inline(never)] +fn test_process_set_authority_mint_multisig( + accounts: &[AccountInfo; 3], + instruction_data: &[u8; 34], +) -> ProgramResult { + cheatcode_mint!(&accounts[0]); // Assume Mint + cheatcode_multisig!(&accounts[1]); // Authority + + //-Initial State----------------------------------------------------------- + let mint_old = get_mint(&accounts[0]); + let mint_data_len = accounts[0].data_len(); + let old_mint_authority_is_none = mint_old.mint_authority().is_none(); + let old_freeze_authority_is_none = mint_old.freeze_authority().is_none(); + let old_mint_authority = mint_old.mint_authority().cloned(); + let old_freeze_authority = mint_old.freeze_authority().cloned(); + let maybe_multisig_is_initialised = Some(get_multisig(&accounts[1]).is_initialized()); + let mint_is_initialised = mint_old.is_initialized(); + + //-Process Instruction----------------------------------------------------- + let result = call_process_set_authority!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < 2 { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } else if !(0..=3).contains(&instruction_data[0]) { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } else if instruction_data[1] != 0 && instruction_data[1] != 1 { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } else if instruction_data[1] == 1 && instruction_data.len() < 34 { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } else if accounts.len() < 2 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); + return result; + } else if mint_data_len != Account::LEN && mint_data_len != Mint::LEN { + assert_eq!(result, Err(ProgramError::InvalidArgument)); + return result; + } else if mint_data_len == Mint::LEN { + // established by cheatcode_mint + + let mint_new = get_mint(&accounts[0]); + + if !mint_is_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if instruction_data[0] != 0 && instruction_data[0] != 1 { + // AuthorityType neither MintTokens nor FreezeAccount + assert_eq!(result, Err(ProgramError::Custom(15))); + return result; + } else if instruction_data[0] == 0 { + // MintTokens + if old_mint_authority_is_none { + assert_eq!(result, Err(ProgramError::Custom(5))); + return result; + } + + // Validate Owner + inner_test_validate_owner( + &old_mint_authority.unwrap(), // expected_owner + &accounts[1], // owner_account_info + &accounts[2..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + if instruction_data[1] == 1 { + // 1 ==> 34 <= instruction_data.len() + assert_pubkey_from_slice!(mint_new.mint_authority().unwrap(), &instruction_data[2..34]); + } else { + assert_eq!(mint_new.mint_authority(), None); + } + assert!(result.is_ok()) + } else { + // FreezeAccount + assert_eq!(instruction_data[0], 1); // If not MintTokens (0), must be FreezeAccount (1) + if old_freeze_authority_is_none { + assert_eq!(result, Err(ProgramError::Custom(16))); + return result; + } + + // Validate Owner + inner_test_validate_owner( + &old_freeze_authority.unwrap(), // expected_owner + &accounts[1], // owner_account_info + &accounts[2..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + if instruction_data[1] == 1 { + // 1 ==> 34 <= instruction_data.len() + assert_pubkey_from_slice!(mint_new.freeze_authority().unwrap(), &instruction_data[2..34]); + } else { + assert_eq!(mint_new.freeze_authority(), None); + } + assert!(result.is_ok()) + } + } else { + unreachable!(); // mint_data_len == Mint::LEN must hold + } + + result +} diff --git a/specs/shared/test_process_sync_native.rs b/specs/shared/test_process_sync_native.rs new file mode 100644 index 00000000..be3f29a7 --- /dev/null +++ b/specs/shared/test_process_sync_native.rs @@ -0,0 +1,41 @@ +#[inline(never)] +fn test_process_sync_native(accounts: &[AccountInfo; 1]) -> ProgramResult { + cheatcode_account!(&accounts[0]); + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + let src_owner = owner!(&accounts[0]); + let src_initialised = src_old.is_initialized(); + let src_native_amount = src_old.native_amount(); + let src_init_lamports = accounts[0].lamports(); + let src_init_amount = src_old.amount(); + + //-Process Instruction----------------------------------------------------- + let result = call_process_sync_native!(accounts); + + //-Assert Postconditions--------------------------------------------------- + if accounts.len() != 1 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if src_owner != &PROGRAM_ID { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)) + } else if accounts[0].data_len() != Account::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if src_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !src_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)) + } else if src_native_amount.is_none() { + assert_eq!(result, Err(ProgramError::Custom(19))) + } else if src_init_lamports < src_native_amount.unwrap() { + assert_eq!(result, Err(ProgramError::Custom(14))) + } else if src_init_lamports - src_native_amount.unwrap() < src_init_amount { + assert_eq!(result, Err(ProgramError::Custom(13))) + } else { + assert_eq!( + get_account(&accounts[0]).amount(), + src_init_lamports - src_native_amount.unwrap() + ); + assert!(result.is_ok()) + } + result +} diff --git a/specs/shared/test_process_thaw_account.rs b/specs/shared/test_process_thaw_account.rs new file mode 100644 index 00000000..a6fdbeab --- /dev/null +++ b/specs/shared/test_process_thaw_account.rs @@ -0,0 +1,67 @@ +/// accounts[0] // Source Account Info +/// accounts[1] // Mint Info +/// accounts[2] // Authority Info +/// accounts[3..13] // Signers +#[inline(never)] +fn test_process_thaw_account(accounts: &[AccountInfo; 3]) -> ProgramResult { + cheatcode_account!(&accounts[0]); + cheatcode_mint!(&accounts[1]); + cheatcode_account!(&accounts[2]); // Excluding the multisig case + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + let mint_old = get_mint(&accounts[1]); + let src_initialised = src_old.is_initialized(); + let src_init_state = src_old.account_state(); + let src_is_native = src_old.is_native(); + let src_mint = src_old.mint; + let mint_initialised = mint_old.is_initialized(); + let mint_freeze_auth = mint_old.freeze_authority().cloned(); + let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account + + //-Process Instruction----------------------------------------------------- + let result = call_process_thaw_account!(accounts); + + //-Assert Postconditions--------------------------------------------------- + if accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if accounts[0].data_len() != Account::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if src_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !src_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)) + } else if src_init_state.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if src_init_state.unwrap() != AccountState::Frozen { + assert_eq!(result, Err(ProgramError::Custom(13))) + } else if src_is_native { + assert_eq!(result, Err(ProgramError::Custom(10))) + } else if key!(&accounts[1]) != &src_mint { + assert_eq!(result, Err(ProgramError::Custom(3))) + } else if accounts[1].data_len() != Mint::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if mint_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !mint_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)) + } else if mint_freeze_auth.is_none() { + assert_eq!(result, Err(ProgramError::Custom(16))) + } else { + // Validate Owner + inner_test_validate_owner( + &mint_freeze_auth.unwrap(), // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + assert_eq!( + get_account(&accounts[0]).account_state().unwrap(), + AccountState::Initialized + ); + assert!(result.is_ok()) + } + result +} diff --git a/specs/shared/test_process_thaw_account_multisig.rs b/specs/shared/test_process_thaw_account_multisig.rs new file mode 100644 index 00000000..5abbd200 --- /dev/null +++ b/specs/shared/test_process_thaw_account_multisig.rs @@ -0,0 +1,67 @@ +/// accounts[0] // Source Account Info +/// accounts[1] // Mint Info +/// accounts[2] // Authority Info +/// accounts[3..13] // Signers +#[inline(never)] +fn test_process_thaw_account_multisig(accounts: &[AccountInfo; 4]) -> ProgramResult { + cheatcode_account!(&accounts[0]); + cheatcode_mint!(&accounts[1]); + cheatcode_multisig!(&accounts[2]); + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + let mint_old = get_mint(&accounts[1]); + let src_initialised = src_old.is_initialized(); + let src_init_state = src_old.account_state(); + let src_is_native = src_old.is_native(); + let src_mint = src_old.mint; + let mint_initialised = mint_old.is_initialized(); + let mint_freeze_auth = mint_old.freeze_authority().cloned(); + let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); + + //-Process Instruction----------------------------------------------------- + let result = call_process_thaw_account!(accounts); + + //-Assert Postconditions--------------------------------------------------- + if accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if accounts[0].data_len() != Account::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if src_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !src_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)) + } else if src_init_state.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if src_init_state.unwrap() != AccountState::Frozen { + assert_eq!(result, Err(ProgramError::Custom(13))) + } else if src_is_native { + assert_eq!(result, Err(ProgramError::Custom(10))) + } else if key!(&accounts[1]) != &src_mint { + assert_eq!(result, Err(ProgramError::Custom(3))) + } else if accounts[1].data_len() != Mint::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if mint_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !mint_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)) + } else if mint_freeze_auth.is_none() { + assert_eq!(result, Err(ProgramError::Custom(16))) + } else { + // Validate Owner + inner_test_validate_owner( + &mint_freeze_auth.unwrap(), // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + assert_eq!( + get_account(&accounts[0]).account_state().unwrap(), + AccountState::Initialized + ); + assert!(result.is_ok()) + } + result +} diff --git a/specs/shared/test_process_transfer.rs b/specs/shared/test_process_transfer.rs new file mode 100644 index 00000000..a92c48d9 --- /dev/null +++ b/specs/shared/test_process_transfer.rs @@ -0,0 +1,150 @@ +/// accounts[0] // Source Info +/// accounts[1] // Destination Info +/// accounts[2] // Authority Info +/// instruction_data[0..8] // Little Endian Bytes of u64 amount +#[inline(never)] +pub fn test_process_transfer( + accounts: &[AccountInfo; 3], + instruction_data: &[u8; 8], +) -> ProgramResult { + cheatcode_account!(&accounts[0]); + cheatcode_account!(&accounts[1]); + cheatcode_account!(&accounts[2]); // Excluding the multisig case + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + let amount = u64::from_le_bytes([ + instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], + instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7], + ]); + let src_initialised = src_old.is_initialized(); + let dst_initialised = get_account(&accounts[1]).is_initialized(); + let src_initial_amount = src_old.amount(); + let dst_initial_amount = get_account(&accounts[1]).amount(); + let src_initial_lamports = accounts[0].lamports(); + let dst_initial_lamports = accounts[1].lamports(); + let src_owner = src_old.owner; + let old_src_delgate = src_old.delegate().cloned(); + let old_src_delgated_amount = src_old.delegated_amount(); + let maybe_multisig_is_initialised = None; + + #[cfg(feature = "assumptions")] + // avoids potential overflow in destination account. assuming global supply bound by u64 + if !same_account!(accounts[0], accounts[1]) && dst_initial_amount.checked_add(amount).is_none() { + return Err(ProgramError::Custom(99)); + } + + //-Process Instruction----------------------------------------------------- + let result = call_process_transfer!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < 8 { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } else if accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); + return result; + } else if src_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !src_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if !same_account!(accounts[0], accounts[1]) && dst_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !same_account!(accounts[0], accounts[1]) && !dst_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if get_account(&accounts[0]).account_state().unwrap() == AccountState::Frozen { + assert_eq!(result, Err(ProgramError::Custom(17))); + return result; + } else if !same_account!(accounts[0], accounts[1]) + && get_account(&accounts[1]).account_state().unwrap() == AccountState::Frozen + { + assert_eq!(result, Err(ProgramError::Custom(17))); + return result; + } else if src_initial_amount < amount { + assert_eq!(result, Err(ProgramError::Custom(1))); + return result; + } else if !same_account!(accounts[0], accounts[1]) + && get_account(&accounts[0]).mint != get_account(&accounts[1]).mint + { + assert_eq!(result, Err(ProgramError::Custom(3))); + return result; + } else { + let src_new = get_account(&accounts[0]); + if old_src_delgate == Some(*key!(&accounts[2])) { + inner_test_validate_owner( + &old_src_delgate.unwrap(), // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + if old_src_delgated_amount < amount { + assert_eq!(result, Err(ProgramError::Custom(1))); + return result; + } + } else { + inner_test_validate_owner( + &src_owner, // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + } + if ((same_account!(accounts[0], accounts[1])) || amount == 0) + && owner!(&accounts[0]) != &PROGRAM_ID + { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)); + return result; + } else if ((same_account!(accounts[0], accounts[1])) || amount == 0) + && owner!(&accounts[1]) != &PROGRAM_ID + { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)); + return result; + } else if !same_account!(accounts[0], accounts[1]) + && amount != 0 + && src_new.is_native() + && src_initial_lamports < amount + { + assert_eq!(result, Err(ProgramError::Custom(14))); + return result; + } else if !same_account!(accounts[0], accounts[1]) + && amount != 0 + && src_new.is_native() + && dst_initial_lamports.checked_add(amount).is_none() + { + assert_eq!(result, Err(ProgramError::Custom(14))); + return result; + } + + assert!(result.is_ok()); + + if !same_account!(accounts[0], accounts[1]) && amount != 0 { + assert_eq!(src_new.amount(), src_initial_amount - amount); + assert_eq!( + get_account(&accounts[1]).amount(), + dst_initial_amount + amount + ); + + if src_new.is_native() { + assert_eq!(accounts[0].lamports(), src_initial_lamports - amount); + assert_eq!(accounts[1].lamports(), dst_initial_lamports + amount); + } + } + + // Delegate updates + if old_src_delgate == Some(*key!(&accounts[2])) && !same_account!(accounts[0], accounts[1]) { + assert_eq!(src_new.delegated_amount(), old_src_delgated_amount - amount); + if old_src_delgated_amount - amount == 0 { + assert_eq!(src_new.delegate(), None); + } + } + } + + result +} diff --git a/specs/shared/test_process_transfer_checked.rs b/specs/shared/test_process_transfer_checked.rs new file mode 100644 index 00000000..00e51ae9 --- /dev/null +++ b/specs/shared/test_process_transfer_checked.rs @@ -0,0 +1,168 @@ +/// accounts[0] // Source Info +/// accounts[1] // Mint Info +/// accounts[2] // Destination Info +/// accounts[3] // Authority Info +/// instruction_data[0..9] // Little Endian Bytes of u64 amount, and decimals +#[inline(never)] +fn test_process_transfer_checked( + accounts: &[AccountInfo; 4], + instruction_data: &[u8; 9], +) -> ProgramResult { + cheatcode_account!(&accounts[0]); + cheatcode_mint!(&accounts[1]); + cheatcode_account!(&accounts[2]); + cheatcode_account!(&accounts[3]); // Excluding the multisig case + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + let dst_old = get_account(&accounts[2]); + let amount = u64::from_le_bytes([ + instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], + instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7], + ]); + let src_initialised = src_old.is_initialized(); + let dst_initialised = dst_old.is_initialized(); + let src_initial_amount = src_old.amount(); + let dst_initial_amount = dst_old.amount(); + let src_initial_lamports = accounts[0].lamports(); + let dst_initial_lamports = accounts[2].lamports(); + let src_owner = src_old.owner; + let old_src_delgate = src_old.delegate().cloned(); + let old_src_delgated_amount = src_old.delegated_amount(); + let mint_initialised = get_mint(&accounts[1]).is_initialized(); + let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account + + #[cfg(feature = "assumptions")] + // avoids potential overflow in destination account. assuming global supply bound by u64 + if !same_account!(accounts[0], accounts[2]) && dst_initial_amount.checked_add(amount).is_none() { + return Err(ProgramError::Custom(99)); + } + + //-Process Instruction----------------------------------------------------- + let result = call_process_transfer_checked!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < 9 { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } else if accounts.len() < 4 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); + return result; + } else if src_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !src_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if !same_account!(accounts[0], accounts[2]) && dst_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !same_account!(accounts[0], accounts[2]) && !dst_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if get_account(&accounts[0]).account_state().unwrap() == AccountState::Frozen { + assert_eq!(result, Err(ProgramError::Custom(17))); + return result; + } else if !same_account!(accounts[0], accounts[2]) + && get_account(&accounts[2]).account_state().unwrap() == AccountState::Frozen + { + assert_eq!(result, Err(ProgramError::Custom(17))); + return result; + } else if src_initial_amount < amount { + assert_eq!(result, Err(ProgramError::Custom(1))); + return result; + } else if !same_account!(accounts[0], accounts[2]) + && get_account(&accounts[0]).mint != get_account(&accounts[2]).mint + { + assert_eq!(result, Err(ProgramError::Custom(3))); + return result; + } else if key!(&accounts[1]) != &get_account(&accounts[0]).mint { + assert_eq!(result, Err(ProgramError::Custom(3))); + return result; + } else if accounts[1].data_len() != Mint::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if mint_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !mint_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if instruction_data[8] != get_mint(&accounts[1]).decimals { + assert_eq!(result, Err(ProgramError::Custom(18))); + return result; + } else { + if old_src_delgate == Some(*key!(&accounts[3])) { + // Because of the above if, there is a duplicated check in the following + // function Validate Owner + inner_test_validate_owner( + &old_src_delgate.unwrap(), // expected_owner + &accounts[3], // owner_account_info + &accounts[4..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + if old_src_delgated_amount < amount { + assert_eq!(result, Err(ProgramError::Custom(1))); + return result; + } + } else { + // Validate Owner + inner_test_validate_owner( + &src_owner, // expected_owner + &accounts[3], // owner_account_info + &accounts[4..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + } + + let src_new = get_account(&accounts[0]); + + if ((same_account!(accounts[0], accounts[2])) || amount == 0) + && owner!(&accounts[0]) != &PROGRAM_ID + { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)); + return result; + } else if ((same_account!(accounts[0], accounts[2])) || amount == 0) + && owner!(&accounts[2]) != &PROGRAM_ID + { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)); + return result; + } + if !same_account!(accounts[0], accounts[2]) && amount != 0 { + if src_new.is_native() && src_initial_lamports < amount { + // Not sure how to fund native mint + assert_eq!(result, Err(ProgramError::Custom(14))); + return result; + } else if src_new.is_native() && dst_initial_lamports.checked_add(amount).is_none() { + // Not sure how to fund native mint + assert_eq!(result, Err(ProgramError::Custom(14))); + return result; + } + + assert_eq!(src_new.amount(), src_initial_amount - amount); + assert_eq!( + get_account(&accounts[2]).amount(), + dst_initial_amount + amount + ); + + if src_new.is_native() { + assert_eq!(accounts[0].lamports(), src_initial_lamports - amount); + assert_eq!(accounts[2].lamports(), dst_initial_lamports + amount); + } + } + assert!(result.is_ok()); + + // Delegate updates + if old_src_delgate == Some(*key!(&accounts[3])) && !same_account!(accounts[0], accounts[2]) { + assert_eq!(src_new.delegated_amount(), old_src_delgated_amount - amount); + if old_src_delgated_amount - amount == 0 { + assert_eq!(src_new.delegate(), None); + } + } + } + + result +} diff --git a/specs/shared/test_process_transfer_checked_multisig.rs b/specs/shared/test_process_transfer_checked_multisig.rs new file mode 100644 index 00000000..e208d9a1 --- /dev/null +++ b/specs/shared/test_process_transfer_checked_multisig.rs @@ -0,0 +1,162 @@ +/// accounts[0] // Source Info +/// accounts[1] // Mint Info +/// accounts[2] // Destination Info +/// accounts[3] // Authority Info +/// accounts[4..15] // Signers +/// instruction_data[0..9] // Little Endian Bytes of u64 amount, and decimals +#[inline(never)] +fn test_process_transfer_checked_multisig( + accounts: &[AccountInfo; 5], + instruction_data: &[u8; 9], +) -> ProgramResult { + cheatcode_account!(&accounts[0]); + cheatcode_mint!(&accounts[1]); + cheatcode_account!(&accounts[2]); + cheatcode_multisig!(&accounts[3]); + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + let dst_old = get_account(&accounts[2]); + let amount = u64::from_le_bytes([ + instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], + instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7], + ]); + let src_initialised = src_old.is_initialized(); + let dst_initialised = dst_old.is_initialized(); + let src_initial_amount = src_old.amount(); + let dst_initial_amount = dst_old.amount(); + let src_initial_lamports = accounts[0].lamports(); + let dst_initial_lamports = accounts[2].lamports(); + let src_owner = src_old.owner; + let old_src_delgate = src_old.delegate().cloned(); + let old_src_delgated_amount = src_old.delegated_amount(); + let mint_initialised = get_mint(&accounts[1]).is_initialized(); + let maybe_multisig_is_initialised = Some(get_multisig(&accounts[3]).is_initialized()); + + //-Process Instruction----------------------------------------------------- + let result = call_process_transfer_checked!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < 9 { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } else if accounts.len() < 4 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); + return result; + } else if src_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !src_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if !same_account!(accounts[0], accounts[2]) && dst_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !same_account!(accounts[0], accounts[2]) && !dst_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if get_account(&accounts[0]).account_state().unwrap() == AccountState::Frozen { + assert_eq!(result, Err(ProgramError::Custom(17))); + return result; + } else if !same_account!(accounts[0], accounts[2]) + && get_account(&accounts[2]).account_state().unwrap() == AccountState::Frozen + { + assert_eq!(result, Err(ProgramError::Custom(17))); + return result; + } else if src_initial_amount < amount { + assert_eq!(result, Err(ProgramError::Custom(1))); + return result; + } else if !same_account!(accounts[0], accounts[2]) + && get_account(&accounts[0]).mint != get_account(&accounts[2]).mint + { + assert_eq!(result, Err(ProgramError::Custom(3))); + return result; + } else if key!(&accounts[1]) != &get_account(&accounts[0]).mint { + assert_eq!(result, Err(ProgramError::Custom(3))); + return result; + } else if accounts[1].data_len() != Mint::LEN { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if mint_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !mint_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if instruction_data[8] != get_mint(&accounts[1]).decimals { + assert_eq!(result, Err(ProgramError::Custom(18))); + return result; + } else { + if old_src_delgate == Some(*key!(&accounts[3])) { + // Because of the above if, there is a duplicated check in the following + // function Validate Owner + inner_test_validate_owner( + &old_src_delgate.unwrap(), // expected_owner + &accounts[3], // owner_account_info + &accounts[4..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + if old_src_delgated_amount < amount { + assert_eq!(result, Err(ProgramError::Custom(1))); + return result; + } + } else { + // Validate Owner + inner_test_validate_owner( + &src_owner, // expected_owner + &accounts[3], // owner_account_info + &accounts[4..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + } + + let src_new = get_account(&accounts[0]); + + if ((same_account!(accounts[0], accounts[2])) || amount == 0) + && owner!(&accounts[0]) != &PROGRAM_ID + { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)); + return result; + } else if ((same_account!(accounts[0], accounts[2])) || amount == 0) + && owner!(&accounts[2]) != &PROGRAM_ID + { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)); + return result; + } else if !same_account!(accounts[0], accounts[2]) && amount != 0 { + if src_new.is_native() && src_initial_lamports < amount { + // Not sure how to fund native mint + assert_eq!(result, Err(ProgramError::Custom(14))); + return result; + } else if src_new.is_native() && u64::MAX - amount < dst_initial_lamports { + // Not sure how to fund native mint + assert_eq!(result, Err(ProgramError::Custom(14))); + return result; + } + + assert_eq!(src_new.amount(), src_initial_amount - amount); + assert_eq!( + get_account(&accounts[2]).amount(), + dst_initial_amount + amount + ); + + if src_new.is_native() { + assert_eq!(accounts[0].lamports(), src_initial_lamports - amount); + assert_eq!(accounts[2].lamports(), dst_initial_lamports + amount); + } + } + + assert!(result.is_ok()); + // Delegate updates + if old_src_delgate == Some(*key!(&accounts[3])) && !same_account!(accounts[0], accounts[2]) { + assert_eq!(src_new.delegated_amount(), old_src_delgated_amount - amount); + if old_src_delgated_amount - amount == 0 { + assert_eq!(src_new.delegate(), None); + } + } + } + + result +} diff --git a/specs/shared/test_process_transfer_multisig.rs b/specs/shared/test_process_transfer_multisig.rs new file mode 100644 index 00000000..c768c847 --- /dev/null +++ b/specs/shared/test_process_transfer_multisig.rs @@ -0,0 +1,145 @@ +/// accounts[0] // Source Info +/// accounts[1] // Destination Info +/// accounts[2] // Authority Info +/// accounts[3..14] // Signers +/// instruction_data[0..8] // Little Endian Bytes of u64 amount +#[inline(never)] +fn test_process_transfer_multisig( + accounts: &[AccountInfo; 4], + instruction_data: &[u8; 8], +) -> ProgramResult { + cheatcode_account!(&accounts[0]); + cheatcode_account!(&accounts[1]); + cheatcode_multisig!(&accounts[2]); + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + let amount = u64::from_le_bytes([ + instruction_data[0], instruction_data[1], instruction_data[2], instruction_data[3], + instruction_data[4], instruction_data[5], instruction_data[6], instruction_data[7], + ]); + let src_initialised = src_old.is_initialized(); + let dst_initialised = get_account(&accounts[1]).is_initialized(); + let src_initial_amount = src_old.amount(); + let dst_initial_amount = get_account(&accounts[1]).amount(); + let src_initial_lamports = accounts[0].lamports(); + let dst_initial_lamports = accounts[1].lamports(); + let src_owner = src_old.owner; + let old_src_delgate = src_old.delegate().cloned(); + let old_src_delgated_amount = src_old.delegated_amount(); + let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); + + //-Process Instruction----------------------------------------------------- + let result = call_process_transfer!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + if instruction_data.len() < 8 { + assert_eq!(result, Err(ProgramError::Custom(12))); + return result; + } else if accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); + return result; + } else if src_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !src_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if !same_account!(accounts[0], accounts[1]) && dst_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !same_account!(accounts[0], accounts[1]) && !dst_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if get_account(&accounts[0]).account_state().unwrap() == AccountState::Frozen { + assert_eq!(result, Err(ProgramError::Custom(17))); + return result; + } else if !same_account!(accounts[0], accounts[1]) + && get_account(&accounts[1]).account_state().unwrap() == AccountState::Frozen + { + assert_eq!(result, Err(ProgramError::Custom(17))); + return result; + } else if src_initial_amount < amount { + assert_eq!(result, Err(ProgramError::Custom(1))); + return result; + } else if !same_account!(accounts[0], accounts[1]) + && get_account(&accounts[0]).mint != get_account(&accounts[1]).mint + { + assert_eq!(result, Err(ProgramError::Custom(3))); + return result; + } else { + let tx_signers: &[AccountInfo] = &accounts[3..]; + if old_src_delgate == Some(*key!(&accounts[2])) { + inner_test_validate_owner( + old_src_delgate.as_ref().unwrap(), // expected_owner + &accounts[2], // owner_account_info + tx_signers, // tx_signers + maybe_multisig_is_initialised.clone(), + result.clone(), + )?; + + if old_src_delgated_amount < amount { + assert_eq!(result, Err(ProgramError::Custom(1))); + return result; + } + } else { + inner_test_validate_owner( + &src_owner, // expected_owner + &accounts[2], // owner_account_info + tx_signers, // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + } + + let src_new = get_account(&accounts[0]); + if ((same_account!(accounts[0], accounts[1])) || amount == 0) + && owner!(&accounts[0]) != &PROGRAM_ID + { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)); + return result; + } else if ((same_account!(accounts[0], accounts[1])) || amount == 0) + && owner!(&accounts[1]) != &PROGRAM_ID + { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)); + return result; + } else if !same_account!(accounts[0], accounts[1]) + && amount != 0 + && src_new.is_native() + && src_initial_lamports < amount + { + assert_eq!(result, Err(ProgramError::Custom(14))); + return result; + } else if !same_account!(accounts[0], accounts[1]) + && amount != 0 + && src_new.is_native() + && dst_initial_lamports.checked_add(amount).is_none() + { + assert_eq!(result, Err(ProgramError::Custom(14))); + return result; + } else if !same_account!(accounts[0], accounts[1]) && amount != 0 { + assert_eq!(src_new.amount(), src_initial_amount - amount); + assert_eq!( + get_account(&accounts[1]).amount(), + dst_initial_amount + amount + ); + + if src_new.is_native() { + assert_eq!(accounts[0].lamports(), src_initial_lamports - amount); + assert_eq!(accounts[1].lamports(), dst_initial_lamports + amount); + } + } + + assert!(result.is_ok()); + + // Delegate updates + if old_src_delgate == Some(*key!(&accounts[2])) && !same_account!(accounts[0], accounts[1]) { + assert_eq!(src_new.delegated_amount(), old_src_delgated_amount - amount); + if old_src_delgated_amount - amount == 0 { + assert_eq!(src_new.delegate(), None); + } + } + } + + result +} diff --git a/specs/shared/test_process_ui_amount_to_amount.rs b/specs/shared/test_process_ui_amount_to_amount.rs new file mode 100644 index 00000000..b9bbd804 --- /dev/null +++ b/specs/shared/test_process_ui_amount_to_amount.rs @@ -0,0 +1,106 @@ +#[inline(never)] +fn test_process_ui_amount_to_amount( + accounts: &[AccountInfo; 1], + instruction_data: &[u8], +) -> ProgramResult { + cheatcode_mint!(&accounts[0]); + + //-Initial State----------------------------------------------------------- + let ui_amount = core::str::from_utf8(instruction_data); + let mint_initialised = get_mint(&accounts[0]).is_initialized(); + + //-Process Instruction----------------------------------------------------- + let result = call_process_ui_amount_to_amount!(accounts, instruction_data); + + //-Assert Postconditions--------------------------------------------------- + // TODO: validations module is private, so we need a work around + if ui_amount.is_err() { + assert_eq!(result, Err(ProgramError::Custom(12))) + } else if accounts.is_empty() { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)) + } else if owner!(&accounts[0]) != &PROGRAM_ID { + assert_eq!(result, Err(ProgramError::IncorrectProgramId)) + } else if accounts[0].data_len() != Mint::LEN { + assert_eq!(result, Err(ProgramError::Custom(2))) + } else if mint_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)) + } else if !mint_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::Custom(2))) + } else if ui_amount.unwrap().is_empty() { + assert_eq!(result, Err(ProgramError::InvalidArgument)) + } else if ui_amount.unwrap() == "." { + assert_eq!(result, Err(ProgramError::InvalidArgument)) + } else if 1 < ui_amount.unwrap().chars().filter(|&c| c == '.').count() { + assert_eq!(result, Err(ProgramError::InvalidArgument)) + } else if ui_amount.unwrap().starts_with('.') + && ui_amount.unwrap().chars().skip(1).all(|c| c == '0') + { + assert_eq!(result, Err(ProgramError::InvalidArgument)) + } else if ui_amount.unwrap().split_once('.').is_some_and(|(_, frac)| { + (get_mint(&accounts[0]).decimals as usize) < frac.trim_end_matches('0').len() + }) { + assert_eq!(result, Err(ProgramError::InvalidArgument)) + } else if ui_amount.unwrap().split_once('.').map_or( + 257_usize < ui_amount.unwrap().len() + (get_mint(&accounts[0]).decimals as usize), + |(ints, _)| 257_usize < ints.len() + (get_mint(&accounts[0]).decimals as usize), + ) { + assert_eq!(result, Err(ProgramError::InvalidArgument)) + } + /*else if ui_amount.unwrap() == "+." { + // TODO: Why is this valid? + assert_eq!(result, Err(ProgramError::InvalidArgument)) + } else if ui_amount.unwrap() == "+" { + // TODO: Why is this valid? + assert_eq!(result, Err(ProgramError::InvalidArgument)) + }*/ + else if ui_amount.unwrap().starts_with('-') { + assert_eq!(result, Err(ProgramError::InvalidArgument)) + } else if ui_amount + .unwrap() + .contains(|c: char| !c.is_ascii_digit() && c != '+' && c != '.') + { + assert_eq!(result, Err(ProgramError::InvalidArgument)) + } else if ui_amount.unwrap().split_once('.').map_or( + { + const MAX_VAL: &str = "1844674407370955"; // TODO: What should this be? + let ui_amount = ui_amount.unwrap(); + let ui_amount = ui_amount.strip_prefix('+').unwrap_or(ui_amount); + let ui_amount = ui_amount.trim_start_matches('0'); + match ui_amount.len().cmp(&MAX_VAL.len()) { + core::cmp::Ordering::Less => false, + core::cmp::Ordering::Greater => true, + core::cmp::Ordering::Equal => MAX_VAL < ui_amount, + } + }, + |(ints, fracs)| { + const MAX_VAL: &str = "1844674407370955"; // TODO: What should this be? + let ints = ints.strip_prefix('+').unwrap_or(ints); + let hi = ints.trim_start_matches('0'); + let lo = if hi.is_empty() { + fracs.trim_start_matches('0') + } else { + fracs + }; + + let total_len = hi.len() + lo.len(); + + match total_len.cmp(&MAX_VAL.len()) { + core::cmp::Ordering::Less => false, + core::cmp::Ordering::Greater => true, + core::cmp::Ordering::Equal => { + if hi.len() > MAX_VAL.len() { + return true; + } + let (max_hi, max_lo) = MAX_VAL.split_at(hi.len()); + hi > max_hi || (hi == max_hi && lo > max_lo) + } + } + }, + ) { + // TODO: What is going on ??? Need to fix + // assert_eq!(result, Err(ProgramError::InvalidArgument)) + } else { + assert!(result.is_ok()) + } + result +} diff --git a/specs/withdraw-p-token.rs b/specs/withdraw-p-token.rs new file mode 100644 index 00000000..4fa34b19 --- /dev/null +++ b/specs/withdraw-p-token.rs @@ -0,0 +1,444 @@ +/// accounts[0] // Source Account Info (Account) +/// accounts[1] // Destination Info +/// accounts[2] // Authority Info +#[inline(never)] +fn test_process_withdraw_excess_lamports_account(accounts: &[AccountInfo; 3]) -> ProgramResult { + cheatcode_is_account(&accounts[0]); // Source Account + cheatcode_is_account(&accounts[1]); // Destination + cheatcode_is_account(&accounts[2]); // Excluding the multisig case + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + let src_data_len = accounts[0].data_len(); + let src_account_initialised = src_old.is_initialized(); + let src_account_owner = src_old.owner; + let src_account_is_native = src_old.is_native(); + let src_init_lamports = accounts[0].lamports(); + let dst_init_lamports = accounts[1].lamports(); + let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account + + // Note: Rent is a supported sysvar so ProgramError::UnsupportedSysvar should be + // impossible + let rent = pinocchio::sysvars::rent::Rent::get().unwrap(); + let minimum_balance = rent.minimum_balance(accounts[0].data_len()); + + //-Process Instruction----------------------------------------------------- + let result = process_withdraw_excess_lamports(accounts); + + //-Assert Postconditions--------------------------------------------------- + if accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); + return result; + } else { + assert_eq!(src_data_len, Account::LEN); // established by cheatcode_is_account + { + if src_account_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !src_account_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if src_account_is_native { + assert_eq!(result, Err(ProgramError::Custom(10))); + return result; + } + + // Validate Owner + inner_test_validate_owner( + &src_account_owner, // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + if src_init_lamports < minimum_balance { + assert_eq!(result, Err(ProgramError::Custom(0))); + return result; + } else if dst_init_lamports + .checked_add(src_init_lamports - minimum_balance) + .is_none() + { + assert_eq!(result, Err(ProgramError::Custom(14))); + return result; + } + + assert_eq!(accounts[0].lamports(), minimum_balance); + assert_eq!( + accounts[1].lamports(), + dst_init_lamports + (src_init_lamports - minimum_balance) + ); + assert!(result.is_ok()) + } + } + + result +} + +/// accounts[0] // Source Account Info (Account) +/// accounts[1] // Destination Info +/// accounts[2] // Authority Info +/// accounts[3..14] // Signers +#[inline(never)] +fn test_process_withdraw_excess_lamports_account_multisig( + accounts: &[AccountInfo; 4], +) -> ProgramResult { + cheatcode_is_account(&accounts[0]); // Source Account + cheatcode_is_account(&accounts[1]); // Destination + cheatcode_is_multisig(&accounts[2]); // Authority + + //-Initial State----------------------------------------------------------- + let src_old = get_account(&accounts[0]); + let src_data_len = accounts[0].data_len(); + let src_account_initialised = src_old.is_initialized(); + let src_account_owner = src_old.owner; + let src_account_is_native = src_old.is_native(); + let src_init_lamports = accounts[0].lamports(); + let dst_init_lamports = accounts[1].lamports(); + let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); + + // Note: Rent is a supported sysvar so ProgramError::UnsupportedSysvar should be + // impossible + let rent = pinocchio::sysvars::rent::Rent::get().unwrap(); + let minimum_balance = rent.minimum_balance(accounts[0].data_len()); + + //-Process Instruction----------------------------------------------------- + let result = process_withdraw_excess_lamports(accounts); + + //-Assert Postconditions--------------------------------------------------- + if accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); + return result; + } else { + assert_eq!(src_data_len, Account::LEN); // established by cheatcode_is_account + { + if src_account_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !src_account_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if src_account_is_native { + assert_eq!(result, Err(ProgramError::Custom(10))); + return result; + } + + // Validate Owner + inner_test_validate_owner( + &src_account_owner, // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + if src_init_lamports < minimum_balance { + assert_eq!(result, Err(ProgramError::Custom(0))); + return result; + } else if u64::MAX - src_init_lamports + minimum_balance < dst_init_lamports { + assert_eq!(result, Err(ProgramError::Custom(0))); + return result; + } + + assert_eq!(accounts[0].lamports(), minimum_balance); + assert_eq!( + accounts[1].lamports(), + dst_init_lamports + src_init_lamports - minimum_balance + ); + assert!(result.is_ok()) + } + } + + result +} + +/// accounts[0] // Source Account Info (Mint) +/// accounts[1] // Destination Info +/// accounts[2] // Authority Info +#[inline(never)] +fn test_process_withdraw_excess_lamports_mint(accounts: &[AccountInfo; 3]) -> ProgramResult { + cheatcode_is_mint(&accounts[0]); // Source Account (Mint) + cheatcode_is_account(&accounts[1]); // Destination + cheatcode_is_account(&accounts[2]); // Excluding the multisig case + + //-Initial State----------------------------------------------------------- + let src_old = get_mint(&accounts[0]); + let src_data_len = accounts[0].data_len(); + let src_mint_initialised = src_old.is_initialized(); + let src_mint_mint_authority = src_old.mint_authority().cloned(); + let src_init_lamports = accounts[0].lamports(); + let dst_init_lamports = accounts[1].lamports(); + let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account + + // Note: Rent is a supported sysvar so ProgramError::UnsupportedSysvar should be + // impossible + let rent = pinocchio::sysvars::rent::Rent::get().unwrap(); + let minimum_balance = rent.minimum_balance(accounts[0].data_len()); + + //-Process Instruction----------------------------------------------------- + let result = process_withdraw_excess_lamports(accounts); + + //-Assert Postconditions--------------------------------------------------- + if accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); + return result; + } else { + assert_eq!(src_data_len, Mint::LEN); // established by cheatcode_is_mint + { + if src_mint_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !src_mint_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if src_mint_mint_authority.is_some() { + // Validate Owner + inner_test_validate_owner( + &src_mint_mint_authority.unwrap(), // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + } else if accounts[0] != accounts[2] { + assert_eq!(result, Err(ProgramError::Custom(15))); + return result; + } else if !accounts[2].is_signer() { + assert_eq!(result, Err(ProgramError::MissingRequiredSignature)); + return result; + } + + if src_init_lamports < minimum_balance { + assert_eq!(result, Err(ProgramError::Custom(0))); + return result; + } else if dst_init_lamports + .checked_add(src_init_lamports - minimum_balance) + .is_none() + { + assert_eq!(result, Err(ProgramError::Custom(14))); + return result; + } + + assert!(result.is_ok()); + assert_eq!(accounts[0].lamports(), minimum_balance); + assert_eq!( + accounts[1].lamports(), + dst_init_lamports + .checked_add(src_init_lamports - minimum_balance) + .unwrap() + ); + } + } + result +} + +/// accounts[0] // Source Account Info (Mint) +/// accounts[1] // Destination Info +/// accounts[2] // Authority Info +/// accounts[3..14] // Signers +#[inline(never)] +fn test_process_withdraw_excess_lamports_mint_multisig( + accounts: &[AccountInfo; 4], +) -> ProgramResult { + cheatcode_is_mint(&accounts[0]); // Source Account (Mint) + cheatcode_is_account(&accounts[1]); // Destination + cheatcode_is_multisig(&accounts[2]); // Authority + + //-Initial State----------------------------------------------------------- + let src_old = get_mint(&accounts[0]); + let src_data_len = accounts[0].data_len(); + let src_mint_initialised = src_old.is_initialized(); + let src_mint_mint_authority = src_old.mint_authority().cloned(); + let src_init_lamports = accounts[0].lamports(); + let dst_init_lamports = accounts[1].lamports(); + let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); + + // Note: Rent is a supported sysvar so ProgramError::UnsupportedSysvar should be + // impossible + let rent = pinocchio::sysvars::rent::Rent::get().unwrap(); + let minimum_balance = rent.minimum_balance(accounts[0].data_len()); + + //-Process Instruction----------------------------------------------------- + let result = process_withdraw_excess_lamports(accounts); + + //-Assert Postconditions--------------------------------------------------- + if accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); + return result; + } else { + assert_eq!(src_data_len, Mint::LEN); // established by cheatcode_is_mint + { + if src_mint_initialised.is_err() { + assert_eq!(result, Err(ProgramError::InvalidAccountData)); + return result; + } else if !src_mint_initialised.unwrap() { + assert_eq!(result, Err(ProgramError::UninitializedAccount)); + return result; + } else if src_mint_mint_authority.is_some() { + // Validate Owner + inner_test_validate_owner( + &src_mint_mint_authority.unwrap(), // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + } else if accounts[0] != accounts[2] { + assert_eq!(result, Err(ProgramError::Custom(15))); + return result; + } else if !accounts[2].is_signer() { + assert_eq!(result, Err(ProgramError::MissingRequiredSignature)); + return result; + } else if src_init_lamports < minimum_balance { + assert_eq!(result, Err(ProgramError::Custom(0))); + return result; + } else if u64::MAX - src_init_lamports + minimum_balance < dst_init_lamports { + assert_eq!(result, Err(ProgramError::Custom(0))); + return result; + } + + assert_eq!(accounts[0].lamports(), minimum_balance); + assert_eq!( + accounts[1].lamports(), + dst_init_lamports + src_init_lamports - minimum_balance + ); + assert!(result.is_ok()) + } + } + + result +} + +/// accounts[0] // Source Account Info +/// accounts[1] // Destination Info +/// accounts[2] // Authority Info +#[inline(never)] +fn test_process_withdraw_excess_lamports_multisig(accounts: &[AccountInfo; 3]) -> ProgramResult { + cheatcode_is_multisig(&accounts[0]); // Source Account (Multisig) + cheatcode_is_account(&accounts[1]); // Destination + cheatcode_is_account(&accounts[2]); // Excluding the multisig case + + //-Initial State----------------------------------------------------------- + let src_data_len = accounts[0].data_len(); + let src_init_lamports = accounts[0].lamports(); + let dst_init_lamports = accounts[1].lamports(); + let maybe_multisig_is_initialised = None; // Value set to `None` since authority is an account + + // Note: Rent is a supported sysvar so ProgramError::UnsupportedSysvar should be + // impossible + let rent = pinocchio::sysvars::rent::Rent::get().unwrap(); + let minimum_balance = rent.minimum_balance(accounts[0].data_len()); + + //-Process Instruction----------------------------------------------------- + let result = process_withdraw_excess_lamports(accounts); + + //-Assert Postconditions--------------------------------------------------- + if accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); + return result; + } else if src_data_len != Account::LEN + && src_data_len != Mint::LEN + && src_data_len != Multisig::LEN + { + assert_eq!(result, Err(ProgramError::Custom(13))); + return result; + } else { + assert_eq!(src_data_len, Multisig::LEN); // established by cheatcode_is_multisig + + // Validate Owner + inner_test_validate_owner( + accounts[0].key(), // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + if src_init_lamports < minimum_balance { + assert_eq!(result, Err(ProgramError::Custom(0))); + return result; + } else if dst_init_lamports + .checked_add(src_init_lamports - minimum_balance) + .is_none() + { + assert_eq!(result, Err(ProgramError::Custom(0))); + return result; + } + + assert_eq!(accounts[0].lamports(), minimum_balance); + assert_eq!( + accounts[1].lamports(), + dst_init_lamports + (src_init_lamports - minimum_balance) + ); + assert!(result.is_ok()) + } + + result +} + +/// accounts[0] // Source Account Info +/// accounts[1] // Destination Info +/// accounts[2] // Authority Info +/// accounts[3..14] // Signers +#[inline(never)] +fn test_process_withdraw_excess_lamports_multisig_multisig( + accounts: &[AccountInfo; 4], +) -> ProgramResult { + cheatcode_is_multisig(&accounts[0]); // Source Account (Multisig) + cheatcode_is_account(&accounts[1]); // Destination + cheatcode_is_multisig(&accounts[2]); // Authority + + //-Initial State----------------------------------------------------------- + let src_data_len = accounts[0].data_len(); + let src_init_lamports = accounts[0].lamports(); + let dst_init_lamports = accounts[1].lamports(); + let maybe_multisig_is_initialised = Some(get_multisig(&accounts[2]).is_initialized()); + + // Note: Rent is a supported sysvar so ProgramError::UnsupportedSysvar should be + // impossible + let rent = pinocchio::sysvars::rent::Rent::get().unwrap(); + let minimum_balance = rent.minimum_balance(accounts[0].data_len()); + + //-Process Instruction----------------------------------------------------- + let result = process_withdraw_excess_lamports(accounts); + + //-Assert Postconditions--------------------------------------------------- + if accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); + return result; + } else if src_data_len != Account::LEN + && src_data_len != Mint::LEN + && src_data_len != Multisig::LEN + { + assert_eq!(result, Err(ProgramError::Custom(13))); + return result; + } else { + assert_eq!(src_data_len, Multisig::LEN); // established by cheatcode_is_multisig + + // Validate Owner + inner_test_validate_owner( + accounts[0].key(), // expected_owner + &accounts[2], // owner_account_info + &accounts[3..], // tx_signers + maybe_multisig_is_initialised, + result.clone(), + )?; + + if src_init_lamports < minimum_balance { + assert_eq!(result, Err(ProgramError::Custom(0))); + return result; + } else if u64::MAX - src_init_lamports + minimum_balance < dst_init_lamports { + assert_eq!(result, Err(ProgramError::Custom(0))); + return result; + } + + assert_eq!(accounts[0].lamports(), minimum_balance); + assert_eq!( + accounts[1].lamports(), + dst_init_lamports + src_init_lamports - minimum_balance + ); + assert!(result.is_ok()) + } + + result +} diff --git a/specs/withdraw-spl-token.rs b/specs/withdraw-spl-token.rs new file mode 100644 index 00000000..caa5a426 --- /dev/null +++ b/specs/withdraw-spl-token.rs @@ -0,0 +1,34 @@ +/// Unified harness for WithdrawExcessLamports instruction (discriminator 38). +/// +/// This harness handles all source account types (Account, Mint, Multisig) and +/// authority types (single signer or multisig) in one function. +/// +/// program_id // Token Program ID +/// accounts[0] // Source Account Info (Account, Mint, or Multisig) +/// accounts[1] // Destination Info +/// accounts[2] // Authority Info +/// accounts[3..] // Signers (for multisig authority) +/// instruction_data[0] // Discriminator 38 (Withdraw Excess Lamports) +#[inline(never)] +fn test_process_withdraw_excess_lamports( + program_id: &Pubkey, + accounts: &[AccountInfo; 4], + instruction_data: &[u8; 1], +) -> ProgramResult { + // Constrain discriminator and program id + unsafe { assume(38 == instruction_data[0]); } + unsafe { assume(program_id == &crate::id()); } + + let instruction_data_with_discriminator = instruction_data; + + //-Process Instruction----------------------------------------------------- + let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); + + //-Assert Postconditions--------------------------------------------------- + // NOTE: WithdrawExcessLamports (discriminator 38) is a token-2022 instruction that does not + // exist in the original spl-token program. The original spl-token's TokenInstruction only + // supports discriminators 0-24. When Processor::process receives discriminator 38, it fails + // at TokenInstruction::unpack() with InvalidInstruction error. + assert_eq!(result, Err(TokenError::InvalidInstruction.into())); + result +}