diff --git a/program/src/entrypoint-runtime-verification.rs b/program/src/entrypoint-runtime-verification.rs index 158b087d..8b7aa93d 100644 --- a/program/src/entrypoint-runtime-verification.rs +++ b/program/src/entrypoint-runtime-verification.rs @@ -371,27 +371,60 @@ fn inner_process_instruction( } // 3 - Transfer 3 => { - 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)?, - ) + if accounts.len() >= 4 + && accounts[2].data_len() == Multisig::LEN + && 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)?, + ) + } 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)?, + ) + } } // 4 - Approve 4 => { - 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)?, - ) + if accounts.len() >= 4 + && accounts[2].data_len() == Multisig::LEN + && 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)?, + ) + } 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)?, + ) + } } // 5 - Revoke 5 => { - test_process_revoke( - program_id, - accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, - ) + if accounts.len() >= 3 + && accounts[1].data_len() == Multisig::LEN + && 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)?, + ) + } } // 6 - Set Authority Account 6 => { @@ -400,18 +433,40 @@ fn inner_process_instruction( if let Some(first_account) = accounts.first() { match first_account.data_len() { Account::LEN => { - 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)?, - ) + if accounts.len() >= 3 + && accounts[1].data_len() == Multisig::LEN + && 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)?, + ) + } 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)?, + ) + } } Mint::LEN => { - 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)?, - ) + if accounts.len() >= 3 + && accounts[1].data_len() == Multisig::LEN + && 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)?, + ) + } 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)?, + ) + } } _ => Err(TokenError::InvalidInstruction.into()), } @@ -421,75 +476,174 @@ fn inner_process_instruction( } // 7 - Mint To 7 => { - 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)?, - ) + if accounts.len() >= 4 + && accounts[2].data_len() == Multisig::LEN + && 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)?, + ) + } 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)?, + ) + } } // 8 - Burn 8 => { - 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)?, - ) + if accounts.len() >= 4 + && accounts[2].data_len() == Multisig::LEN + && 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)?, + ) + } 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)?, + ) + } } // 9 - Close Account 9 => { - test_process_close_account( - program_id, - accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, - ) + if accounts.len() >= 4 + && accounts[2].data_len() == Multisig::LEN + && 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)?, + ) + } } // 10 - Freeze Account 10 => { - test_process_freeze_account( - program_id, - accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, - ) + if accounts.len() >= 4 + && accounts[2].data_len() == Multisig::LEN + && 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)?, + ) + } } // 11 - Thaw Account 11 => { - test_process_thaw_account( - program_id, - accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, - ) + if accounts.len() >= 4 + && accounts[2].data_len() == Multisig::LEN + && 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)?, + ) + } } // 12 - Transfer Checked 12 => { - 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)?, - ) + if accounts.len() >= 5 + && accounts[3].data_len() == Multisig::LEN + && 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)?, + ) + } 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)?, + ) + } } // 13 - Approve Checked 13 => { - 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)?, - ) + if accounts.len() >= 5 + && accounts[3].data_len() == Multisig::LEN + && 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)?, + ) + } 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)?, + ) + } } // 14 - Mint To Checked 14 => { - 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)?, - ) + if accounts.len() >= 4 + && accounts[2].data_len() == Multisig::LEN + && 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)?, + ) + } 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)?, + ) + } } // 15 - Burn Checked 15 => { - 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)?, - ) + if accounts.len() >= 4 + && accounts[2].data_len() == Multisig::LEN + && 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)?, + ) + } 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)?, + ) + } } // 16 - Initialize Account2 16 => { @@ -587,25 +741,58 @@ fn inner_process_instruction( if let Some(acc) = accounts.first() { match acc.data_len() { Account::LEN => { - test_process_withdraw_excess_lamports_account( - program_id, - accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, - ) + if accounts.len() >= 4 + && accounts[2].data_len() == Multisig::LEN + && accounts[2].owner == &crate::id() + { + test_process_withdraw_excess_lamports_account_multisig( + program_id, + accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + ) + } else { + test_process_withdraw_excess_lamports_account( + program_id, + accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + ) + } } Mint::LEN => { - test_process_withdraw_excess_lamports_mint( - program_id, - accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, - ) + if accounts.len() >= 4 + && accounts[2].data_len() == Multisig::LEN + && accounts[2].owner == &crate::id() + { + test_process_withdraw_excess_lamports_mint_multisig( + program_id, + accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + ) + } else { + test_process_withdraw_excess_lamports_mint( + program_id, + accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + ) + } } Multisig::LEN => { - test_process_withdraw_excess_lamports_multisig( - program_id, - accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, - instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, - ) + if accounts.len() >= 4 + && accounts[2].data_len() == Multisig::LEN + && accounts[2].owner == &crate::id() + { + test_process_withdraw_excess_lamports_multisig_multisig( + program_id, + accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + ) + } else { + test_process_withdraw_excess_lamports_multisig( + program_id, + accounts.first_chunk().ok_or(TokenError::InvalidInstruction)?, + instruction_data.first_chunk().ok_or(TokenError::InvalidInstruction)?, + ) + } } _ => Err(TokenError::InvalidInstruction.into()), } @@ -753,6 +940,132 @@ fn test_process_initialize_mint_no_freeze( 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_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(); + + 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_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..]; + + //-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_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 @@ -914,69 +1227,302 @@ fn test_process_initialize_multisig( } /// program_id // Token Program ID -/// accounts[0] // Source Info +/// accounts[0] // Mint Info /// accounts[1] // Destination Info -/// accounts[2] // Authority Info +/// accounts[2] // Owner Info /// accounts[3..14] // Signers -/// instruction_data[0] // Discriminator 3 (Transfer) -/// instruction_data[1..9] // Little Endian Bytes of u64 amount +/// 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_transfer( +fn test_process_mint_to_checked_multisig( program_id: &Pubkey, - accounts: &[AccountInfo; 3], - instruction_data: &[u8; 9], + accounts: &[AccountInfo; 4], + instruction_data: &[u8; 10], ) -> ProgramResult { use spl_token_interface::state::AccountState; // Constrain discriminator and program id - unsafe { assume(3 == instruction_data[0]); } + 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; 8] = instruction_data.last_chunk().unwrap(); + let instruction_data: &[u8; 9] = instruction_data.last_chunk().unwrap(); - cheatcode_is_spl_account(&accounts[0]); + cheatcode_is_spl_mint(&accounts[0]); cheatcode_is_spl_account(&accounts[1]); - // #[cfg(not(feature="multisig"))] - cheatcode_is_spl_account(&accounts[2]); - // #[cfg(feature="multisig")] - // cheatcode_is_spl_multisig(&accounts[2]); + 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 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 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 = if accounts[2].data_len() == Multisig::LEN - && accounts[2].owner == &crate::id() - { - Some(get_multisig(&accounts[2]).is_initialized()) - } else { - None - }; + 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 { + 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 src_initialised.is_err() { + } 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 !src_initialised.unwrap() { - assert_eq!(result, Err(ProgramError::UninitializedAccount)); + } 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 && u64::MAX - amount < initial_supply { + 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; + + //-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)); @@ -997,17 +1543,13 @@ fn test_process_transfer( assert_eq!(result, Err(ProgramError::Custom(3))); return result; } else { - let tx_signers: &[AccountInfo] = if accounts.len() > 3 { - &accounts[3..] - } 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(), + maybe_multisig_is_initialised, result.clone(), )?; if old_src_delgated_amount < amount { @@ -1066,47 +1608,422 @@ fn test_process_transfer( } /// program_id // Token Program ID -/// accounts[0] // Source Account Info -/// accounts[1] // Delegate Info -/// accounts[2] // Owner Info +/// accounts[0] // Source Info +/// accounts[1] // Destination Info +/// accounts[2] // Authority Info /// accounts[3..14] // Signers -/// instruction_data[0] // Discriminator 4 (Approve) +/// instruction_data[0] // Discriminator 3 (Transfer) /// instruction_data[1..9] // Little Endian Bytes of u64 amount #[inline(never)] -fn test_process_approve( +fn test_process_transfer_multisig( program_id: &Pubkey, - accounts: &[AccountInfo; 3], + 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(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]); // Source Account - cheatcode_is_spl_account(&accounts[1]); // Delegate - // #[cfg(not(feature="multisig"))] - cheatcode_is_spl_account(&accounts[2]); // Owner - // #[cfg(feature="multisig")] - // cheatcode_is_spl_multisig(&accounts[2]); // Owner - + 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() && 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].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 src_init_state = get_account(&accounts[0]).account_state(); - let maybe_multisig_is_initialised = if accounts[2].data_len() == Multisig::LEN - && accounts[2].owner == &crate::id() + 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() { - Some(get_multisig(&accounts[2]).is_initialized()) + 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 { - None - }; + 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() && 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].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); @@ -1122,19 +2039,85 @@ fn test_process_approve( 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 + } 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[2], - &accounts[3..], + &accounts[1], + &accounts[2..], 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!(get_account(&accounts[0]).delegate().is_none()); + assert_eq!(get_account(&accounts[0]).delegated_amount(), 0); assert!(result.is_ok()) } @@ -1150,9 +2133,9 @@ fn test_process_approve( /// accounts[2..13] // Signers /// instruction_data[0] // Discriminator 5 (Revoke) #[inline(never)] -fn test_process_revoke( +fn test_process_revoke_multisig( program_id: &Pubkey, - accounts: &[AccountInfo; 2], + accounts: &[AccountInfo; 3], instruction_data: &[u8; 1], ) -> ProgramResult { use spl_token_interface::state::AccountState; @@ -1166,22 +2149,13 @@ fn test_process_revoke( let instruction_data: &[u8; 0] = instruction_data.last_chunk().unwrap(); cheatcode_is_spl_account(&accounts[0]); // Source Account - // #[cfg(not(feature="multisig"))] - cheatcode_is_spl_account(&accounts[1]); // Owner - // #[cfg(feature="multisig")] - // cheatcode_is_spl_multisig(&accounts[1]); // Owner + 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 = if accounts[1].data_len() == Multisig::LEN - && accounts[1].owner == &crate::id() - { - Some(get_multisig(&accounts[1]).is_initialized()) - } else { - None - }; + 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); @@ -1244,10 +2218,7 @@ fn test_process_set_authority_account( let instruction_data: &[u8; 34] = instruction_data.last_chunk().unwrap(); cheatcode_is_spl_account(&accounts[0]); // Assume Account - // #[cfg(not(feature="multisig"))] cheatcode_is_spl_account(&accounts[1]); // Authority - // #[cfg(feature="multisig")] - // cheatcode_is_spl_multisig(&accounts[1]); // Authority //-Initial State----------------------------------------------------------- let src_initialised = get_account(&accounts[0]).is_initialized(); @@ -1258,13 +2229,132 @@ fn test_process_set_authority_account( .cloned() .unwrap_or(get_account(&accounts[0]).owner()); let account_data_len = accounts[0].data_len(); - let maybe_multisig_is_initialised = if accounts[1].data_len() == Multisig::LEN - && accounts[1].owner == &crate::id() - { - Some(get_multisig(&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() < 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 { - None - }; + 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_eq!(get_account(&accounts[0]).owner().as_ref(), &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_eq!(get_account(&accounts[0]).close_authority().unwrap().as_ref(), &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); @@ -1297,39 +2387,155 @@ fn test_process_set_authority_account( } 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 + } 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_eq!(get_account(&accounts[0]).owner().as_ref(), &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_eq!(get_account(&accounts[0]).close_authority().unwrap().as_ref(), &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] == 2 { // AccountOwner + 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( - &src_owner, + old_mint_authority.as_ref().unwrap(), &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_eq!(get_account(&accounts[0]).owner().as_ref(), &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); + if instruction_data[1] == 1 { // 1 ==> 34 <= instruction_data.len() + assert_eq!(get_mint(&accounts[0]).mint_authority().unwrap().as_ref(), &instruction_data[2..34]); + } else { + assert_eq!(get_mint(&accounts[0]).mint_authority(), None); } assert!(result.is_ok()) - } else { // Close Account - + } else { // FreezeAccount + if old_freeze_authority_is_none { + assert_eq!(result, Err(ProgramError::Custom(16))); + return result; + } inner_test_validate_owner( - &authority, + old_freeze_authority.as_ref().unwrap(), &accounts[1], &accounts[2..], maybe_multisig_is_initialised, @@ -1337,14 +2543,14 @@ fn test_process_set_authority_account( )?; if instruction_data[1] == 1 { // 1 ==> 34 <= instruction_data.len() - assert_eq!(get_account(&accounts[0]).close_authority().unwrap().as_ref(), &instruction_data[2..34]); + assert_eq!(get_mint(&accounts[0]).freeze_authority().unwrap().as_ref(), &instruction_data[2..34]); } else { - assert_eq!(get_account(&accounts[0]).close_authority(), None); + assert_eq!(get_mint(&accounts[0]).freeze_authority(), None); } assert!(result.is_ok()) } } - } + } // Ensure instruction_data was not mutated @@ -1362,9 +2568,9 @@ fn test_process_set_authority_account( /// 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( +fn test_process_set_authority_mint_multisig( program_id: &Pubkey, - accounts: &[AccountInfo; 2], + accounts: &[AccountInfo; 3], instruction_data: &[u8; 35], ) -> ProgramResult { // Constrain discriminator and program id @@ -1376,10 +2582,7 @@ fn test_process_set_authority_mint( let instruction_data: &[u8; 34] = instruction_data.last_chunk().unwrap(); cheatcode_is_spl_mint(&accounts[0]); // Assume Mint - // #[cfg(not(feature="multisig"))] - cheatcode_is_spl_account(&accounts[1]); // Authority - // #[cfg(feature="multisig")] - // cheatcode_is_spl_multisig(&accounts[1]); // Authority + cheatcode_is_spl_multisig(&accounts[1]); // Authority //-Initial State----------------------------------------------------------- let mint_data_len = accounts[0].data_len(); @@ -1387,13 +2590,7 @@ fn test_process_set_authority_mint( 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 = if accounts[1].data_len() == Multisig::LEN - && accounts[1].owner == &crate::id() - { - Some(get_multisig(&accounts[1]).is_initialized()) - } else { - None - }; + let maybe_multisig_is_initialised = Some(get_multisig(&accounts[1]).is_initialized()); let mint_is_initialised = get_mint(&accounts[0]).is_initialized(); //-Process Instruction----------------------------------------------------- @@ -1478,6 +2675,90 @@ fn test_process_set_authority_mint( 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 @@ -1503,10 +2784,7 @@ fn test_process_mint_to( cheatcode_is_spl_mint(&accounts[0]); cheatcode_is_spl_account(&accounts[1]); - // #[cfg(not(feature="multisig"))] cheatcode_is_spl_account(&accounts[2]); - // #[cfg(feature="multisig")] - // cheatcode_is_spl_multisig(&accounts[2]); //-Initial State----------------------------------------------------------- let initial_supply = get_mint(&accounts[0]).supply(); @@ -1514,13 +2792,119 @@ fn test_process_mint_to( 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 = if accounts[2].data_len() == Multisig::LEN - && accounts[2].owner == &crate::id() - { - Some(get_multisig(&accounts[2]).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() < 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 { - None - }; + 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 && u64::MAX - amount < initial_supply { + 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); @@ -1624,10 +3008,7 @@ fn test_process_burn( cheatcode_is_spl_account(&accounts[0]); cheatcode_is_spl_mint(&accounts[1]); - // #[cfg(not(feature="multisig"))] cheatcode_is_spl_account(&accounts[2]); - // #[cfg(feature="multisig")] - // 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]]); @@ -1638,23 +3019,13 @@ fn test_process_burn( 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 = if accounts[2].data_len() == Multisig::LEN - && accounts[2].owner == &crate::id() - { - Some(get_multisig(&accounts[2]).is_initialized()) - } else { - None - }; - let tx_signers: &[AccountInfo] = if accounts.len() > 3 { - &accounts[3..] - } else { - &[] - }; + 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 = None; + let tx_signers: &[AccountInfo] = &accounts[3..]; //-Process Instruction----------------------------------------------------- let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); @@ -1759,10 +3130,7 @@ fn test_process_close_account( cheatcode_is_spl_account(&accounts[0]); cheatcode_is_spl_account(&accounts[1]); - // #[cfg(not(feature="multisig"))] cheatcode_is_spl_account(&accounts[2]); - // #[cfg(feature="multisig")] - // cheatcode_is_spl_multisig(&accounts[2]); //-Initial State----------------------------------------------------------- let src_initialised = get_account(&accounts[0]).is_initialized(); @@ -1773,18 +3141,98 @@ fn test_process_close_account( 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 = if accounts[2].data_len() == Multisig::LEN - && accounts[2].owner == &crate::id() - { - Some(get_multisig(&accounts[2]).is_initialized()) - } else { - None - }; - let tx_signers: &[AccountInfo] = if accounts.len() > 3 { - &accounts[3..] + 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; + } else if u64::MAX - dst_init_lamports < src_init_lamports { + 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 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); @@ -1838,6 +3286,88 @@ fn test_process_close_account( 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 @@ -1862,10 +3392,7 @@ fn test_process_freeze_account( cheatcode_is_spl_account(&accounts[0]); cheatcode_is_spl_mint(&accounts[1]); - // #[cfg(not(feature="multisig"))] cheatcode_is_spl_account(&accounts[2]); - // #[cfg(feature="multisig")] - // cheatcode_is_spl_multisig(&accounts[2]); //-Initial State----------------------------------------------------------- let src_initialised = get_account(&accounts[0]).is_initialized(); @@ -1874,13 +3401,7 @@ fn test_process_freeze_account( 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 = if accounts[2].data_len() == Multisig::LEN - && accounts[2].owner == &crate::id() - { - Some(get_multisig(&accounts[2]).is_initialized()) - } else { - None - }; + let maybe_multisig_is_initialised = None; //-Process Instruction----------------------------------------------------- let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); @@ -1953,10 +3474,7 @@ fn test_process_thaw_account( cheatcode_is_spl_account(&accounts[0]); cheatcode_is_spl_mint(&accounts[1]); - // #[cfg(not(feature="multisig"))] cheatcode_is_spl_account(&accounts[2]); - // #[cfg(feature="multisig")] - // cheatcode_is_spl_multisig(&accounts[2]); //-Initial State----------------------------------------------------------- let src_initialised = get_account(&accounts[0]).is_initialized(); @@ -1965,13 +3483,89 @@ fn test_process_thaw_account( 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 = if accounts[2].data_len() == Multisig::LEN - && accounts[2].owner == &crate::id() - { - Some(get_multisig(&accounts[2]).is_initialized()) + 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 { - None - }; + 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); @@ -2047,10 +3641,7 @@ fn test_process_transfer_checked( cheatcode_is_spl_account(&accounts[0]); cheatcode_is_spl_mint(&accounts[1]); cheatcode_is_spl_account(&accounts[2]); - // #[cfg(not(feature="multisig"))] cheatcode_is_spl_account(&accounts[3]); - // #[cfg(feature="multisig")] - // 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]]); @@ -2064,13 +3655,7 @@ fn test_process_transfer_checked( 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 = if accounts[3].data_len() == Multisig::LEN - && accounts[3].owner == &crate::id() - { - Some(get_multisig(&accounts[3]).is_initialized()) - } else { - None - }; + let maybe_multisig_is_initialised = None; let tx_signers: &[AccountInfo] = if accounts.len() > 4 { &accounts[4..] } else { @@ -2219,10 +3804,7 @@ fn test_process_approve_checked( cheatcode_is_spl_account(&accounts[0]); // Source Account cheatcode_is_spl_mint(&accounts[1]); // Expected Mint cheatcode_is_spl_account(&accounts[2]); // Delegate - // #[cfg(not(feature="multisig"))] cheatcode_is_spl_account(&accounts[3]); // Owner - // #[cfg(feature="multisig")] - // 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]]); @@ -2230,13 +3812,7 @@ fn test_process_approve_checked( 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 = if accounts[3].data_len() == Multisig::LEN - && accounts[3].owner == &crate::id() - { - Some(get_multisig(&accounts[3]).is_initialized()) - } else { - None - }; + let maybe_multisig_is_initialised = None; //-Process Instruction----------------------------------------------------- let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); @@ -2310,10 +3886,7 @@ fn test_process_mint_to_checked( cheatcode_is_spl_mint(&accounts[0]); cheatcode_is_spl_account(&accounts[1]); - // #[cfg(not(feature="multisig"))] cheatcode_is_spl_account(&accounts[2]); - // #[cfg(feature="multisig")] - // cheatcode_is_spl_multisig(&accounts[2]); //-Initial State----------------------------------------------------------- let initial_supply = get_mint(&accounts[0]).supply(); @@ -2321,13 +3894,7 @@ fn test_process_mint_to_checked( 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 = if accounts[2].data_len() == Multisig::LEN - && accounts[2].owner == &crate::id() - { - Some(get_multisig(&accounts[2]).is_initialized()) - } else { - None - }; + let maybe_multisig_is_initialised = None; //-Process Instruction----------------------------------------------------- let result = Processor::process(program_id, accounts, instruction_data_with_discriminator); @@ -2433,10 +4000,7 @@ fn test_process_burn_checked( cheatcode_is_spl_account(&accounts[0]); cheatcode_is_spl_mint(&accounts[1]); - // #[cfg(not(feature="multisig"))] cheatcode_is_spl_account(&accounts[2]); - // #[cfg(feature="multisig")] - // 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]]); @@ -2453,18 +4017,8 @@ fn test_process_burn_checked( 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 = if accounts[2].data_len() == Multisig::LEN - && accounts[2].owner == &crate::id() - { - Some(get_multisig(&accounts[2]).is_initialized()) - } else { - None - }; - let tx_signers: &[AccountInfo] = if accounts.len() > 3 { - &accounts[3..] - } else { - &[] - }; + 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); @@ -2547,6 +4101,79 @@ fn test_process_burn_checked( result } +/// program_id // Token Program ID +/// accounts[0] // Source Account Info (Multisig) +/// accounts[1] // Destination Info +/// accounts[2] // Authority Info (Multisig) +/// accounts[3..14] // Signers +/// instruction_data[0] // Discriminator 38 (Withdraw Excess Lamports Multisig) +#[inline(never)] +fn test_process_withdraw_excess_lamports_multisig_multisig( + 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()); } + + // 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_multisig(&accounts[0]); // Source Account (Multisig) + cheatcode_is_spl_account(&accounts[1]); // Destination + cheatcode_is_spl_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 = 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 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_spl_multisig + inner_test_validate_owner( + accounts[0].key, + &accounts[2], + &accounts[3..], + maybe_multisig_is_initialised.clone(), + 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()) + } + + // 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 @@ -3233,10 +4860,7 @@ fn test_process_withdraw_excess_lamports_account( cheatcode_is_spl_account(&accounts[0]); // Source Account cheatcode_is_spl_account(&accounts[1]); // Destination - // #[cfg(not(feature="multisig"))] cheatcode_is_spl_account(&accounts[2]); // Authority - // #[cfg(feature="multisig")] - // cheatcode_is_spl_multisig(&accounts[2]); // Authority //-Initial State----------------------------------------------------------- let src_data_len = accounts[0].data_len(); @@ -3245,13 +4869,92 @@ fn test_process_withdraw_excess_lamports_account( let src_account_is_native = get_account(&accounts[0]).is_native(); let src_init_lamports = accounts[0].lamports(); let dst_init_lamports = accounts[1].lamports(); - let maybe_multisig_is_initialised = if accounts[2].data_len() == Multisig::LEN - && accounts[2].owner == &crate::id() - { - Some(get_multisig(&accounts[2]).is_initialized()) + let maybe_multisig_is_initialised = None; + + // 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 accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); + return result; } else { - None - }; + assert_eq!(src_data_len, Account::LEN); // established by cheatcode_is_spl_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; + } + inner_test_validate_owner( + &src_account_owner, + &accounts[2], + &accounts[3..], + maybe_multisig_is_initialised.clone(), + 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()) + } + } + + // 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 (Account) +/// accounts[1] // Destination Info +/// accounts[2] // Authority Info +/// accounts[3..14] // Signers +/// instruction_data[0] // Discriminator 38 (Withdraw Excess Lamports Account) +#[inline(never)] +fn test_process_withdraw_excess_lamports_account_multisig( + 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()); } + + // 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]); // Destination + cheatcode_is_spl_multisig(&accounts[2]); // Authority + + //-Initial State----------------------------------------------------------- + let src_data_len = accounts[0].data_len(); + let src_account_initialised = get_account(&accounts[0]).is_initialized(); + let src_account_owner = get_account(&accounts[0]).owner(); + let src_account_is_native = get_account(&accounts[0]).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 = solana_rent::Rent::get().unwrap(); @@ -3327,10 +5030,7 @@ fn test_process_withdraw_excess_lamports_mint( cheatcode_is_spl_mint(&accounts[0]); // Source Account (Mint) cheatcode_is_spl_account(&accounts[1]); // Destination - // #[cfg(not(feature="multisig"))] cheatcode_is_spl_account(&accounts[2]); // Authority - // #[cfg(feature="multisig")] - // cheatcode_is_spl_multisig(&accounts[2]); // Authority //-Initial State----------------------------------------------------------- let src_data_len = accounts[0].data_len(); @@ -3338,13 +5038,95 @@ fn test_process_withdraw_excess_lamports_mint( let src_mint_mint_authority = get_mint(&accounts[0]).mint_authority().cloned(); let src_init_lamports = accounts[0].lamports(); let dst_init_lamports = accounts[1].lamports(); - let maybe_multisig_is_initialised = if accounts[2].data_len() == Multisig::LEN - && accounts[2].owner == &crate::id() - { - Some(get_multisig(&accounts[2]).is_initialized()) + let maybe_multisig_is_initialised = None; + + // 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 accounts.len() < 3 { + assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys)); + return result; } else { - None - }; + assert_eq!(src_data_len, Mint::LEN); // established by cheatcode_is_spl_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() { + inner_test_validate_owner( + src_mint_mint_authority.as_ref().unwrap(), + &accounts[2], + &accounts[3..], + maybe_multisig_is_initialised.clone(), + result.clone(), + )?; + } else if accounts[0].key != accounts[2].key { + 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()) + } + } + + // 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 (Mint) +/// accounts[1] // Destination Info +/// accounts[2] // Authority Info +/// accounts[3..14] // Signers +/// instruction_data[0] // Discriminator 38 (Withdraw Excess Lamports Mint) +#[inline(never)] +fn test_process_withdraw_excess_lamports_mint_multisig( + 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()); } + + // 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]); // Source Account (Mint) + cheatcode_is_spl_account(&accounts[1]); // Destination + cheatcode_is_spl_multisig(&accounts[2]); // Authority + + //-Initial State----------------------------------------------------------- + let src_data_len = accounts[0].data_len(); + let src_mint_initialised = get_mint(&accounts[0]).is_initialized(); + let src_mint_mint_authority = get_mint(&accounts[0]).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 = solana_rent::Rent::get().unwrap(); @@ -3424,22 +5206,13 @@ fn test_process_withdraw_excess_lamports_multisig( // cheatcode_is_spl_multisig(&accounts[0]); // Source Account (Multisig) cheatcode_is_spl_account(&accounts[1]); // Destination - // #[cfg(not(feature="multisig"))] cheatcode_is_spl_account(&accounts[2]); // Authority - // #[cfg(feature="multisig")] - // cheatcode_is_spl_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 = if accounts[2].data_len() == Multisig::LEN - && accounts[2].owner == &crate::id() - { - Some(get_multisig(&accounts[2]).is_initialized()) - } else { - None - }; + let maybe_multisig_is_initialised = None; // Note: Rent is a supported sysvar so ProgramError::UnsupportedSysvar should be impossible let rent = solana_rent::Rent::get().unwrap();