From 8b9b99063fa8dd7ade5716e1f7836836455a6dfe Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Fri, 14 Feb 2025 16:21:16 +0100 Subject: [PATCH 1/2] Unwrap instruction --- program/src/instruction.rs | 32 +-- program/src/processor.rs | 97 +++++++- program/tests/helpers/mod.rs | 1 + program/tests/helpers/unwrap_builder.rs | 299 ++++++++++++++++++++++++ program/tests/test_unwrap.rs | 236 +++++++++++++++++++ 5 files changed, 647 insertions(+), 18 deletions(-) create mode 100644 program/tests/helpers/unwrap_builder.rs create mode 100644 program/tests/test_unwrap.rs diff --git a/program/src/instruction.rs b/program/src/instruction.rs index 908742dc..590a7848 100644 --- a/program/src/instruction.rs +++ b/program/src/instruction.rs @@ -69,18 +69,20 @@ pub enum TokenWrapInstruction { /// /// Accounts expected by this instruction: /// - /// 0. `[writeable]` Wrapped token account to unwrap - /// 1. `[writeable]` Wrapped mint, address must be: + /// 0. `[writeable]` Escrow of unwrapped tokens, must be owned by: + /// `get_wrapped_mint_authority(wrapped_mint_address)` + /// 1. `[writeable]` Recipient unwrapped tokens + /// 2. `[]` Wrapped mint authority, address must be: + /// `get_wrapped_mint_authority(wrapped_mint)` + /// 3. `[]` Unwrapped token mint + /// 4. `[]` SPL Token program for wrapped mint + /// 5. `[]` SPL Token program for unwrapped mint + /// 6. `[writeable]` Wrapped token account to unwrap + /// 7. `[writeable]` Wrapped mint, address must be: /// `get_wrapped_mint_address(unwrapped_mint_address, /// wrapped_token_program_id)` - /// 2. `[writeable]` Escrow of unwrapped tokens, must be owned by: - /// `get_wrapped_mint_authority(wrapped_mint_address)` - /// 3. `[writeable]` Recipient unwrapped tokens - /// 4. `[]` Unwrapped token mint - /// 5. `[]` SPL Token program for wrapped mint - /// 6. `[]` SPL Token program for unwrapped mint - /// 7. `[signer]` Transfer authority on wrapped token account - /// 8. `..8+M` `[signer]` (Optional) M multisig signers on wrapped token + /// 8. `[signer]` Transfer authority on wrapped token account + /// 9. `..8+M` `[signer]` (Optional) M multisig signers on wrapped token /// account Unwrap { /// little-endian `u64` representing the amount to unwrap @@ -198,25 +200,27 @@ pub fn wrap( #[allow(clippy::too_many_arguments)] pub fn unwrap( program_id: &Pubkey, - wrapped_token_account_address: &Pubkey, - wrapped_mint_address: &Pubkey, unwrapped_escrow_address: &Pubkey, recipient_unwrapped_token_account_address: &Pubkey, + wrapped_mint_authority_address: &Pubkey, unwrapped_mint_address: &Pubkey, wrapped_token_program_id: &Pubkey, unwrapped_token_program_id: &Pubkey, + wrapped_token_account_address: &Pubkey, + wrapped_mint_address: &Pubkey, transfer_authority_address: &Pubkey, multisig_signer_pubkeys: &[&Pubkey], amount: u64, ) -> Instruction { let mut accounts = vec![ - AccountMeta::new(*wrapped_token_account_address, false), - AccountMeta::new(*wrapped_mint_address, false), AccountMeta::new(*unwrapped_escrow_address, false), AccountMeta::new(*recipient_unwrapped_token_account_address, false), + AccountMeta::new_readonly(*wrapped_mint_authority_address, false), AccountMeta::new_readonly(*unwrapped_mint_address, false), AccountMeta::new_readonly(*wrapped_token_program_id, false), AccountMeta::new_readonly(*unwrapped_token_program_id, false), + AccountMeta::new(*wrapped_token_account_address, false), + AccountMeta::new(*wrapped_mint_address, false), AccountMeta::new_readonly( *transfer_authority_address, multisig_signer_pubkeys.is_empty(), diff --git a/program/src/processor.rs b/program/src/processor.rs index daa677b1..0ba11817 100644 --- a/program/src/processor.rs +++ b/program/src/processor.rs @@ -164,7 +164,7 @@ pub fn process_create_mint( } /// Processes [`Wrap`](enum.TokenWrapInstruction.html) instruction. -pub fn process_wrap(_program_id: &Pubkey, accounts: &[AccountInfo], amount: u64) -> ProgramResult { +pub fn process_wrap(accounts: &[AccountInfo], amount: u64) -> ProgramResult { if amount == 0 { Err(TokenWrapError::ZeroWrapAmount)? } @@ -251,6 +251,95 @@ pub fn process_wrap(_program_id: &Pubkey, accounts: &[AccountInfo], amount: u64) Ok(()) } +/// Processes [`Unwrap`](enum.TokenWrapInstruction.html) instruction. +pub fn process_unwrap(accounts: &[AccountInfo], amount: u64) -> ProgramResult { + if amount == 0 { + Err(TokenWrapError::ZeroWrapAmount)? + } + + let account_info_iter = &mut accounts.iter(); + + let unwrapped_escrow = next_account_info(account_info_iter)?; + let recipient_unwrapped_token = next_account_info(account_info_iter)?; + let wrapped_mint_authority = next_account_info(account_info_iter)?; + let unwrapped_mint = next_account_info(account_info_iter)?; + let wrapped_token_program = next_account_info(account_info_iter)?; + let unwrapped_token_program = next_account_info(account_info_iter)?; + let wrapped_token_account = next_account_info(account_info_iter)?; + let wrapped_mint = next_account_info(account_info_iter)?; + let transfer_authority = next_account_info(account_info_iter)?; + let multisig_signer_accounts = account_info_iter.as_slice(); + + // Validate accounts + + let expected_wrapped_mint = + get_wrapped_mint_address(unwrapped_mint.key, wrapped_token_program.key); + if expected_wrapped_mint != *wrapped_mint.key { + Err(TokenWrapError::WrappedMintMismatch)? + } + + let (expected_authority, bump) = get_wrapped_mint_authority_with_seed(wrapped_mint.key); + if *wrapped_mint_authority.key != expected_authority { + Err(TokenWrapError::MintAuthorityMismatch)? + } + + { + let escrow_data = unwrapped_escrow.try_borrow_data()?; + let escrow_account = PodStateWithExtensions::::unpack(&escrow_data)?; + if escrow_account.base.owner != expected_authority { + Err(TokenWrapError::EscrowOwnerMismatch)? + } + } + + // Burn wrapped tokens + + let multisig_signer_pubkeys = multisig_signer_accounts + .iter() + .map(|account| account.key) + .collect::>(); + + invoke( + &spl_token_2022::instruction::burn( + wrapped_token_program.key, + wrapped_token_account.key, + wrapped_mint.key, + transfer_authority.key, + &multisig_signer_pubkeys, + amount, + )?, + &accounts[6..], + )?; + + // Transfer unwrapped tokens from escrow to recipient + + let unwrapped_mint_data = unwrapped_mint.try_borrow_data()?; + let unwrapped_mint_state = PodStateWithExtensions::::unpack(&unwrapped_mint_data)?; + let bump_seed = [bump]; + let signer_seeds = get_wrapped_mint_authority_signer_seeds(wrapped_mint.key, &bump_seed); + + invoke_signed( + &spl_token_2022::instruction::transfer_checked( + unwrapped_token_program.key, + unwrapped_escrow.key, + unwrapped_mint.key, + recipient_unwrapped_token.key, + wrapped_mint_authority.key, + &[], + amount, + unwrapped_mint_state.base.decimals, + )?, + &[ + unwrapped_escrow.clone(), + unwrapped_mint.clone(), + recipient_unwrapped_token.clone(), + wrapped_mint_authority.clone(), + ], + &[&signer_seeds], + )?; + + Ok(()) +} + /// Instruction processor pub fn process_instruction( program_id: &Pubkey, @@ -264,11 +353,11 @@ pub fn process_instruction( } TokenWrapInstruction::Wrap { amount } => { msg!("Instruction: Wrap"); - process_wrap(program_id, accounts, amount) + process_wrap(accounts, amount) } - TokenWrapInstruction::Unwrap { .. } => { + TokenWrapInstruction::Unwrap { amount } => { msg!("Instruction: Unwrap"); - unimplemented!(); + process_unwrap(accounts, amount) } } } diff --git a/program/tests/helpers/mod.rs b/program/tests/helpers/mod.rs index 6e386387..744aba6c 100644 --- a/program/tests/helpers/mod.rs +++ b/program/tests/helpers/mod.rs @@ -1,3 +1,4 @@ pub mod common; pub mod create_mint_builder; +pub mod unwrap_builder; pub mod wrap_builder; diff --git a/program/tests/helpers/unwrap_builder.rs b/program/tests/helpers/unwrap_builder.rs new file mode 100644 index 00000000..bccaf13e --- /dev/null +++ b/program/tests/helpers/unwrap_builder.rs @@ -0,0 +1,299 @@ +use { + crate::helpers::{ + common::{init_mollusk, setup_mint}, + create_mint_builder::{KeyedAccount, TokenProgram}, + wrap_builder::TransferAuthority, + }, + mollusk_svm::{result::Check, Mollusk}, + solana_account::Account, + solana_pubkey::Pubkey, + spl_token_2022::{ + extension::{ + transfer_fee::TransferFeeAmount, BaseStateWithExtensionsMut, ExtensionType, + PodStateWithExtensionsMut, + }, + pod::{PodAccount, PodCOption}, + }, + spl_token_wrap::{get_wrapped_mint_address, get_wrapped_mint_authority, instruction::unwrap}, +}; + +pub struct UnwrapBuilder<'a> { + mollusk: Mollusk, + unwrap_amount: Option, + checks: Vec>, + wrapped_mint: Option, + wrapped_mint_authority: Option, + escrow_starting_amount: Option, + unwrapped_escrow_owner: Option, + wrapped_token_starting_amount: Option, + recipient_starting_amount: Option, + unwrapped_token_program: Option, + wrapped_token_program: Option, + transfer_authority: Option, +} + +impl Default for UnwrapBuilder<'_> { + fn default() -> Self { + Self { + mollusk: init_mollusk(), + unwrap_amount: None, + checks: vec![], + wrapped_mint: None, + wrapped_mint_authority: None, + escrow_starting_amount: None, + unwrapped_escrow_owner: None, + wrapped_token_starting_amount: None, + recipient_starting_amount: None, + unwrapped_token_program: None, + wrapped_token_program: None, + transfer_authority: None, + } + } +} + +impl<'a> UnwrapBuilder<'a> { + pub fn unwrap_amount(mut self, amount: u64) -> Self { + self.unwrap_amount = Some(amount); + self + } + + pub fn wrapped_token_starting_amount(mut self, amount: u64) -> Self { + self.wrapped_token_starting_amount = Some(amount); + self + } + + pub fn escrow_starting_amount(mut self, amount: u64) -> Self { + self.escrow_starting_amount = Some(amount); + self + } + + pub fn unwrapped_escrow_owner(mut self, key: Pubkey) -> Self { + self.unwrapped_escrow_owner = Some(key); + self + } + + pub fn wrapped_mint(mut self, account: KeyedAccount) -> Self { + self.wrapped_mint = Some(account); + self + } + + pub fn wrapped_token_program(mut self, program: TokenProgram) -> Self { + self.wrapped_token_program = Some(program); + self + } + + pub fn unwrapped_token_program(mut self, program: TokenProgram) -> Self { + self.unwrapped_token_program = Some(program); + self + } + + pub fn wrapped_mint_authority(mut self, key: Pubkey) -> Self { + self.wrapped_mint_authority = Some(key); + self + } + + pub fn recipient_starting_amount(mut self, amount: u64) -> Self { + self.recipient_starting_amount = Some(amount); + self + } + + pub fn transfer_authority(mut self, auth: TransferAuthority) -> Self { + self.transfer_authority = Some(auth); + self + } + + pub fn check(mut self, check: Check<'a>) -> Self { + self.checks.push(check); + self + } + + fn get_wrapped_mint( + &self, + token_program: TokenProgram, + unwrapped_mint_addr: Pubkey, + ) -> KeyedAccount { + let wrapped_mint_addr = get_wrapped_mint_address(&unwrapped_mint_addr, &token_program.id()); + let mint_authority = get_wrapped_mint_authority(&wrapped_mint_addr); + + self.wrapped_mint.clone().unwrap_or(KeyedAccount { + key: wrapped_mint_addr, + account: setup_mint(token_program, &self.mollusk.sysvars.rent, mint_authority), + }) + } + + pub fn setup_token_account( + &self, + token_program: TokenProgram, + mint: &KeyedAccount, + owner: &Pubkey, + starting_amount: u64, + ) -> KeyedAccount { + let extensions = match token_program { + TokenProgram::SplToken => vec![], + TokenProgram::SplToken2022 => vec![ExtensionType::TransferFeeAmount], + }; + + let account_size = + ExtensionType::try_calculate_account_len::(&extensions).unwrap(); + + let mut token_account = Account { + lamports: 100_000_000, + owner: mint.account.owner, + data: vec![0; account_size], + ..Default::default() + }; + + let account_data = PodAccount { + mint: mint.key, + owner: *owner, + amount: starting_amount.into(), + delegate: PodCOption::none(), + state: spl_token_2022::state::AccountState::Initialized.into(), + is_native: PodCOption::none(), + delegated_amount: 0.into(), + close_authority: PodCOption::none(), + }; + + let mut state = + PodStateWithExtensionsMut::::unpack_uninitialized(&mut token_account.data) + .unwrap(); + *state.base = account_data; + state.init_account_type().unwrap(); + + if let TokenProgram::SplToken2022 = token_program { + state.init_extension::(true).unwrap(); + let fee_extension = state.get_extension_mut::().unwrap(); + fee_extension.withheld_amount = 12.into(); + } + + KeyedAccount { + key: Pubkey::new_unique(), + account: token_account, + } + } + + pub fn execute(mut self) -> UnwrapResult { + let unwrap_amount = self.unwrap_amount.unwrap_or(500); + let transfer_authority = self.transfer_authority.clone().unwrap_or_default(); + + let unwrapped_token_program = self + .unwrapped_token_program + .unwrap_or(TokenProgram::SplToken); + + let unwrapped_mint = KeyedAccount { + key: Pubkey::new_unique(), + account: setup_mint( + unwrapped_token_program, + &self.mollusk.sysvars.rent, + Pubkey::new_unique(), + ), + }; + + let wrapped_token_program = self + .wrapped_token_program + .unwrap_or(TokenProgram::SplToken2022); + + let wrapped_mint = self + .wrapped_mint + .clone() + .unwrap_or_else(|| self.get_wrapped_mint(wrapped_token_program, unwrapped_mint.key)); + + let wrapped_mint_authority = self + .wrapped_mint_authority + .unwrap_or_else(|| get_wrapped_mint_authority(&wrapped_mint.key)); + + // Setup wrapped token account to be unwrapped + let wrapped_token_account = self.setup_token_account( + wrapped_token_program, + &wrapped_mint, + &transfer_authority.keyed_account.key, + self.wrapped_token_starting_amount.unwrap_or(unwrap_amount), + ); + + // Setup escrow account + let escrow = self.setup_token_account( + unwrapped_token_program, + &unwrapped_mint, + &self + .unwrapped_escrow_owner + .unwrap_or(wrapped_mint_authority), + self.escrow_starting_amount.unwrap_or(100_000), + ); + + // Setup recipient account for unwrapped tokens + let recipient = self.setup_token_account( + unwrapped_token_program, + &unwrapped_mint, + &Pubkey::new_unique(), + self.recipient_starting_amount.unwrap_or(0), + ); + + let instruction = unwrap( + &spl_token_wrap::id(), + &escrow.key, + &recipient.key, + &wrapped_mint_authority, + &unwrapped_mint.key, + &wrapped_token_program.id(), + &unwrapped_token_program.id(), + &wrapped_token_account.key, + &wrapped_mint.key, + &transfer_authority.keyed_account.key, + &transfer_authority.signers.iter().collect::>(), + unwrap_amount, + ); + + let mut accounts = vec![ + escrow.pair(), + recipient.pair(), + (wrapped_mint_authority, Account::default()), + unwrapped_mint.pair(), + wrapped_token_program.keyed_account(), + unwrapped_token_program.keyed_account(), + wrapped_token_account.pair(), + wrapped_mint.pair(), + transfer_authority.keyed_account.pair(), + ]; + + for signer_key in &transfer_authority.signers { + accounts.push((*signer_key, Account::default())); + } + + if self.checks.is_empty() { + self.checks.push(Check::success()); + } + + let result = + self.mollusk + .process_and_validate_instruction(&instruction, &accounts, &self.checks); + + UnwrapResult { + wrapped_token_account: KeyedAccount { + key: wrapped_token_account.key, + account: result + .get_account(&wrapped_token_account.key) + .unwrap() + .clone(), + }, + unwrapped_escrow: KeyedAccount { + key: escrow.key, + account: result.get_account(&escrow.key).unwrap().clone(), + }, + wrapped_mint: KeyedAccount { + key: wrapped_mint.key, + account: result.get_account(&wrapped_mint.key).unwrap().clone(), + }, + recipient_unwrapped_token: KeyedAccount { + key: recipient.key, + account: result.get_account(&recipient.key).unwrap().clone(), + }, + } + } +} + +pub struct UnwrapResult { + pub wrapped_token_account: KeyedAccount, + pub unwrapped_escrow: KeyedAccount, + pub wrapped_mint: KeyedAccount, + pub recipient_unwrapped_token: KeyedAccount, +} diff --git a/program/tests/test_unwrap.rs b/program/tests/test_unwrap.rs new file mode 100644 index 00000000..c57b92dc --- /dev/null +++ b/program/tests/test_unwrap.rs @@ -0,0 +1,236 @@ +use { + crate::helpers::{ + common::{setup_multisig, MINT_SUPPLY}, + create_mint_builder::{CreateMintBuilder, KeyedAccount, TokenProgram}, + unwrap_builder::{UnwrapBuilder, UnwrapResult}, + }, + mollusk_svm::result::Check, + solana_pubkey::Pubkey, + spl_token_2022::{ + error::TokenError, + extension::PodStateWithExtensions, + pod::{PodAccount, PodMint}, + }, + spl_token_wrap::error::TokenWrapError, +}; + +pub mod helpers; + +#[test] +fn test_zero_amount_unwrap() { + UnwrapBuilder::default() + .unwrap_amount(0) + .check(Check::err(TokenWrapError::ZeroWrapAmount.into())) + .execute(); +} + +#[test] +fn test_incorrect_wrapped_mint_address() { + let mint_result = CreateMintBuilder::default().execute(); + + let incorrect_wrapped_mint = KeyedAccount { + key: Pubkey::new_unique(), // Wrong mint address + account: mint_result.wrapped_mint.account.clone(), + }; + + UnwrapBuilder::default() + .wrapped_mint(incorrect_wrapped_mint) + .check(Check::err(TokenWrapError::WrappedMintMismatch.into())) + .execute(); +} + +#[test] +fn test_incorrect_wrapped_mint_authority() { + let incorrect_authority = Pubkey::new_unique(); + UnwrapBuilder::default() + .wrapped_mint_authority(incorrect_authority) + .check(Check::err(TokenWrapError::MintAuthorityMismatch.into())) + .execute(); +} + +#[test] +fn test_incorrect_escrow_owner() { + let incorrect_escrow_owner = Pubkey::new_unique(); + UnwrapBuilder::default() + .unwrapped_escrow_owner(incorrect_escrow_owner) + .check(Check::err(TokenWrapError::EscrowOwnerMismatch.into())) + .execute(); +} + +#[test] +fn test_unwrap_amount_exceeds_unwrappers_balance() { + let wrapped_balance = 1_000; + let unwrap_amount = 42_000; + + UnwrapBuilder::default() + .unwrapped_token_program(TokenProgram::SplToken) + .wrapped_token_program(TokenProgram::SplToken2022) + .wrapped_token_starting_amount(wrapped_balance) + .unwrap_amount(unwrap_amount) + .check(Check::err(TokenError::InsufficientFunds.into())) + .execute(); +} + +fn assert_unwrap_result( + recipient_starting_amount: u64, + escrow_starting_amount: u64, + unwrap_amount: u64, + unwrap_result: &UnwrapResult, +) { + // Verify wrapped tokens were burned (source account) + let wrapped_token = PodStateWithExtensions::::unpack( + &unwrap_result.wrapped_token_account.account.data, + ) + .unwrap(); + assert_eq!(wrapped_token.base.amount, 0.into()); + + // Verify wrapped mint supply decreased + let mint = PodStateWithExtensions::::unpack(&unwrap_result.wrapped_mint.account.data) + .unwrap(); + assert_eq!( + u64::from(mint.base.supply), + MINT_SUPPLY.checked_sub(unwrap_amount).unwrap() + ); + + // Verify escrow was debited + let escrow_token = + PodStateWithExtensions::::unpack(&unwrap_result.unwrapped_escrow.account.data) + .unwrap(); + assert_eq!( + u64::from(escrow_token.base.amount), + escrow_starting_amount.checked_sub(unwrap_amount).unwrap() + ); + + // Verify recipient received unwrapped tokens + let recipient_token = PodStateWithExtensions::::unpack( + &unwrap_result.recipient_unwrapped_token.account.data, + ) + .unwrap(); + assert_eq!( + u64::from(recipient_token.base.amount), + recipient_starting_amount + .checked_add(unwrap_amount) + .unwrap() + ); +} + +#[test] +fn test_successful_spl_token_2022_to_spl_token_unwrap() { + let recipient_starting_amount = 50_000; + let escrow_starting_amount = 150_000; + let unwrap_amount = 12_555; + + let wrap_result = UnwrapBuilder::default() + .unwrapped_token_program(TokenProgram::SplToken) + .wrapped_token_program(TokenProgram::SplToken2022) + .escrow_starting_amount(escrow_starting_amount) + .recipient_starting_amount(recipient_starting_amount) + .unwrap_amount(unwrap_amount) + .check(Check::success()) + .execute(); + + assert_unwrap_result( + recipient_starting_amount, + escrow_starting_amount, + unwrap_amount, + &wrap_result, + ); +} + +#[test] +fn test_successful_spl_token_to_spl_token_2022_unwrap() { + let recipient_starting_amount = 25_000; + let escrow_starting_amount = 42_000; + let unwrap_amount = 40_000; + + let wrap_result = UnwrapBuilder::default() + .unwrapped_token_program(TokenProgram::SplToken2022) + .wrapped_token_program(TokenProgram::SplToken) + .escrow_starting_amount(escrow_starting_amount) + .recipient_starting_amount(recipient_starting_amount) + .unwrap_amount(unwrap_amount) + .check(Check::success()) + .execute(); + + assert_unwrap_result( + recipient_starting_amount, + escrow_starting_amount, + unwrap_amount, + &wrap_result, + ); +} + +#[test] +fn test_successful_token_2022_to_token_2022_unwrap() { + let recipient_starting_amount = 0; + let escrow_starting_amount = 100_000; + let unwrap_amount = 100_000; + + let wrap_result = UnwrapBuilder::default() + .unwrapped_token_program(TokenProgram::SplToken2022) + .wrapped_token_program(TokenProgram::SplToken2022) + .escrow_starting_amount(escrow_starting_amount) + .recipient_starting_amount(recipient_starting_amount) + .unwrap_amount(unwrap_amount) + .check(Check::success()) + .execute(); + + assert_unwrap_result( + recipient_starting_amount, + escrow_starting_amount, + unwrap_amount, + &wrap_result, + ); +} + +#[test] +fn test_unwrap_with_spl_token_multisig() { + let multisig = setup_multisig(TokenProgram::SplToken); + + let recipient_starting_amount = 0; + let escrow_starting_amount = 100_000; + let unwrap_amount = 100_000; + + let wrap_result = UnwrapBuilder::default() + .transfer_authority(multisig) + .unwrapped_token_program(TokenProgram::SplToken2022) + .wrapped_token_program(TokenProgram::SplToken) + .escrow_starting_amount(escrow_starting_amount) + .recipient_starting_amount(recipient_starting_amount) + .unwrap_amount(unwrap_amount) + .check(Check::success()) + .execute(); + + assert_unwrap_result( + recipient_starting_amount, + escrow_starting_amount, + unwrap_amount, + &wrap_result, + ); +} + +#[test] +fn test_unwrap_with_spl_token_2022_multisig() { + let multisig = setup_multisig(TokenProgram::SplToken2022); + + let recipient_starting_amount = 101; + let escrow_starting_amount = 202; + let unwrap_amount = 101; + + let wrap_result = UnwrapBuilder::default() + .transfer_authority(multisig) + .unwrapped_token_program(TokenProgram::SplToken) + .wrapped_token_program(TokenProgram::SplToken2022) + .escrow_starting_amount(escrow_starting_amount) + .recipient_starting_amount(recipient_starting_amount) + .unwrap_amount(unwrap_amount) + .check(Check::success()) + .execute(); + + assert_unwrap_result( + recipient_starting_amount, + escrow_starting_amount, + unwrap_amount, + &wrap_result, + ); +} From e00fd5057513793e9b76002c31442a60d7acf5cb Mon Sep 17 00:00:00 2001 From: Gabe Rodriguez Date: Tue, 18 Feb 2025 09:30:56 -0700 Subject: [PATCH 2/2] Review updates --- program/src/processor.rs | 8 -------- program/tests/test_unwrap.rs | 35 +++++++++++++++++++++++------------ 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/program/src/processor.rs b/program/src/processor.rs index 0ba11817..c51108b0 100644 --- a/program/src/processor.rs +++ b/program/src/processor.rs @@ -283,14 +283,6 @@ pub fn process_unwrap(accounts: &[AccountInfo], amount: u64) -> ProgramResult { Err(TokenWrapError::MintAuthorityMismatch)? } - { - let escrow_data = unwrapped_escrow.try_borrow_data()?; - let escrow_account = PodStateWithExtensions::::unpack(&escrow_data)?; - if escrow_account.base.owner != expected_authority { - Err(TokenWrapError::EscrowOwnerMismatch)? - } - } - // Burn wrapped tokens let multisig_signer_pubkeys = multisig_signer_accounts diff --git a/program/tests/test_unwrap.rs b/program/tests/test_unwrap.rs index c57b92dc..b8ee6685 100644 --- a/program/tests/test_unwrap.rs +++ b/program/tests/test_unwrap.rs @@ -48,23 +48,12 @@ fn test_incorrect_wrapped_mint_authority() { .execute(); } -#[test] -fn test_incorrect_escrow_owner() { - let incorrect_escrow_owner = Pubkey::new_unique(); - UnwrapBuilder::default() - .unwrapped_escrow_owner(incorrect_escrow_owner) - .check(Check::err(TokenWrapError::EscrowOwnerMismatch.into())) - .execute(); -} - #[test] fn test_unwrap_amount_exceeds_unwrappers_balance() { let wrapped_balance = 1_000; let unwrap_amount = 42_000; UnwrapBuilder::default() - .unwrapped_token_program(TokenProgram::SplToken) - .wrapped_token_program(TokenProgram::SplToken2022) .wrapped_token_starting_amount(wrapped_balance) .unwrap_amount(unwrap_amount) .check(Check::err(TokenError::InsufficientFunds.into())) @@ -72,6 +61,7 @@ fn test_unwrap_amount_exceeds_unwrappers_balance() { } fn assert_unwrap_result( + source_starting_amount: u64, recipient_starting_amount: u64, escrow_starting_amount: u64, unwrap_amount: u64, @@ -82,7 +72,13 @@ fn assert_unwrap_result( &unwrap_result.wrapped_token_account.account.data, ) .unwrap(); - assert_eq!(wrapped_token.base.amount, 0.into()); + assert_eq!( + wrapped_token.base.amount, + source_starting_amount + .checked_sub(unwrap_amount) + .unwrap() + .into() + ); // Verify wrapped mint supply decreased let mint = PodStateWithExtensions::::unpack(&unwrap_result.wrapped_mint.account.data) @@ -116,11 +112,13 @@ fn assert_unwrap_result( #[test] fn test_successful_spl_token_2022_to_spl_token_unwrap() { + let source_starting_amount = 50_000; let recipient_starting_amount = 50_000; let escrow_starting_amount = 150_000; let unwrap_amount = 12_555; let wrap_result = UnwrapBuilder::default() + .wrapped_token_starting_amount(source_starting_amount) .unwrapped_token_program(TokenProgram::SplToken) .wrapped_token_program(TokenProgram::SplToken2022) .escrow_starting_amount(escrow_starting_amount) @@ -130,6 +128,7 @@ fn test_successful_spl_token_2022_to_spl_token_unwrap() { .execute(); assert_unwrap_result( + source_starting_amount, recipient_starting_amount, escrow_starting_amount, unwrap_amount, @@ -139,6 +138,7 @@ fn test_successful_spl_token_2022_to_spl_token_unwrap() { #[test] fn test_successful_spl_token_to_spl_token_2022_unwrap() { + let source_starting_amount = 50_000; let recipient_starting_amount = 25_000; let escrow_starting_amount = 42_000; let unwrap_amount = 40_000; @@ -147,12 +147,14 @@ fn test_successful_spl_token_to_spl_token_2022_unwrap() { .unwrapped_token_program(TokenProgram::SplToken2022) .wrapped_token_program(TokenProgram::SplToken) .escrow_starting_amount(escrow_starting_amount) + .wrapped_token_starting_amount(source_starting_amount) .recipient_starting_amount(recipient_starting_amount) .unwrap_amount(unwrap_amount) .check(Check::success()) .execute(); assert_unwrap_result( + source_starting_amount, recipient_starting_amount, escrow_starting_amount, unwrap_amount, @@ -162,6 +164,7 @@ fn test_successful_spl_token_to_spl_token_2022_unwrap() { #[test] fn test_successful_token_2022_to_token_2022_unwrap() { + let source_starting_amount = 150_000; let recipient_starting_amount = 0; let escrow_starting_amount = 100_000; let unwrap_amount = 100_000; @@ -170,12 +173,14 @@ fn test_successful_token_2022_to_token_2022_unwrap() { .unwrapped_token_program(TokenProgram::SplToken2022) .wrapped_token_program(TokenProgram::SplToken2022) .escrow_starting_amount(escrow_starting_amount) + .wrapped_token_starting_amount(source_starting_amount) .recipient_starting_amount(recipient_starting_amount) .unwrap_amount(unwrap_amount) .check(Check::success()) .execute(); assert_unwrap_result( + source_starting_amount, recipient_starting_amount, escrow_starting_amount, unwrap_amount, @@ -187,6 +192,7 @@ fn test_successful_token_2022_to_token_2022_unwrap() { fn test_unwrap_with_spl_token_multisig() { let multisig = setup_multisig(TokenProgram::SplToken); + let source_starting_amount = 100_000; let recipient_starting_amount = 0; let escrow_starting_amount = 100_000; let unwrap_amount = 100_000; @@ -196,12 +202,14 @@ fn test_unwrap_with_spl_token_multisig() { .unwrapped_token_program(TokenProgram::SplToken2022) .wrapped_token_program(TokenProgram::SplToken) .escrow_starting_amount(escrow_starting_amount) + .wrapped_token_starting_amount(source_starting_amount) .recipient_starting_amount(recipient_starting_amount) .unwrap_amount(unwrap_amount) .check(Check::success()) .execute(); assert_unwrap_result( + source_starting_amount, recipient_starting_amount, escrow_starting_amount, unwrap_amount, @@ -213,6 +221,7 @@ fn test_unwrap_with_spl_token_multisig() { fn test_unwrap_with_spl_token_2022_multisig() { let multisig = setup_multisig(TokenProgram::SplToken2022); + let source_starting_amount = 101; let recipient_starting_amount = 101; let escrow_starting_amount = 202; let unwrap_amount = 101; @@ -222,12 +231,14 @@ fn test_unwrap_with_spl_token_2022_multisig() { .unwrapped_token_program(TokenProgram::SplToken) .wrapped_token_program(TokenProgram::SplToken2022) .escrow_starting_amount(escrow_starting_amount) + .wrapped_token_starting_amount(source_starting_amount) .recipient_starting_amount(recipient_starting_amount) .unwrap_amount(unwrap_amount) .check(Check::success()) .execute(); assert_unwrap_result( + source_starting_amount, recipient_starting_amount, escrow_starting_amount, unwrap_amount,