From e76bdfdc0c7fdcec0e3f95a7c5d07a1403391ba8 Mon Sep 17 00:00:00 2001 From: Jordan Prince Date: Sun, 28 Mar 2021 20:13:12 -0500 Subject: [PATCH 1/8] First pass at freeze authority in token swap but honestly im really not in love with the hack. --- .../program/fuzz/src/native_token_swap.rs | 6 +- token-swap/program/src/error.rs | 16 ++ token-swap/program/src/instruction.rs | 81 +++++- token-swap/program/src/processor.rs | 133 ++++++++- token-swap/program/src/state.rs | 262 +++++++++++++++++- 5 files changed, 490 insertions(+), 8 deletions(-) diff --git a/token-swap/program/fuzz/src/native_token_swap.rs b/token-swap/program/fuzz/src/native_token_swap.rs index fc20c8998e6..175dc26cd11 100644 --- a/token-swap/program/fuzz/src/native_token_swap.rs +++ b/token-swap/program/fuzz/src/native_token_swap.rs @@ -15,7 +15,9 @@ use spl_token_swap::{ use spl_token::instruction::approve; -use solana_program::{bpf_loader, entrypoint::ProgramResult, pubkey::Pubkey, system_program}; +use solana_program::{ + bpf_loader, entrypoint::ProgramResult, program_option::COption, pubkey::Pubkey, system_program, +}; pub struct NativeTokenSwap { pub user_account: NativeAccountData, @@ -89,6 +91,8 @@ impl NativeTokenSwap { nonce, fees.clone(), swap_curve.clone(), + COption::None, + 0, ) .unwrap(); diff --git a/token-swap/program/src/error.rs b/token-swap/program/src/error.rs index e8f3ed762a4..8e340a156f7 100644 --- a/token-swap/program/src/error.rs +++ b/token-swap/program/src/error.rs @@ -91,6 +91,22 @@ pub enum SwapError { /// The operation cannot be performed on the given curve #[error("The operation cannot be performed on the given curve")] UnsupportedCurveOperation, + + /// Attempted to access an invalid bit in the freeze authority bitmask + #[error("Attempted to access an invalid bit in the freeze authority bitmask")] + InvalidBitMaskOperation, + + /// This action has been frozen by the Freeze Authority + #[error("This action has been frozen by the Freeze Authority")] + FrozenAction, + + /// Unauthorized to freeze + #[error("Unauthorized to freeze")] + UnauthorizedToFreeze, + + /// Attempted to set bit mask on Swap V1 + #[error("Attempted to set bit mask on Swap V1")] + SwapV1UnsupportedAction, } impl From for ProgramError { fn from(e: SwapError) -> Self { diff --git a/token-swap/program/src/instruction.rs b/token-swap/program/src/instruction.rs index 3bb389d408c..8b123a769a0 100644 --- a/token-swap/program/src/instruction.rs +++ b/token-swap/program/src/instruction.rs @@ -7,6 +7,7 @@ use crate::error::SwapError; use solana_program::{ instruction::{AccountMeta, Instruction}, program_error::ProgramError, + program_option::COption, program_pack::Pack, pubkey::Pubkey, }; @@ -27,6 +28,15 @@ pub struct Initialize { /// swap curve info for pool, including CurveType and anything /// else that may be required pub swap_curve: SwapCurve, + /// The freeze authority of the swap. + pub freeze_authority: COption, + /// bits, from left to right - 1 disables, 0 enables the actions: + /// 0. process_swap, + /// 1. process_deposit_all_token_types, + /// 2. process_withdraw_all_token_types, + /// 3. process_deposit_single_token_type_exact_amount_in, + /// 4. process_withdraw_single_token_type_exact_amount_out, + pub freeze_authority_bit_mask: u8, } /// Swap instruction data @@ -92,6 +102,20 @@ pub struct WithdrawSingleTokenTypeExactAmountOut { pub maximum_pool_token_amount: u64, } +/// SetFreezeAuthorityBitMask instruction data +#[cfg_attr(feature = "fuzz", derive(Arbitrary))] +#[repr(C)] +#[derive(Clone, Debug, PartialEq)] +pub struct SetFreezeAuthorityBitMask { + /// bits, from left to right - 1 disables, 0 enables the actions: + /// 0. process_swap, + /// 1. process_deposit_all_token_types, + /// 2. process_withdraw_all_token_types, + /// 3. process_deposit_single_token_type_exact_amount_in, + /// 4. process_withdraw_single_token_type_exact_amount_out, + pub freeze_authority_bit_mask: u8, +} + /// Instructions supported by the token swap program. #[repr(C)] #[derive(Debug, PartialEq)] @@ -107,7 +131,8 @@ pub enum SwapInstruction { /// Must be empty, not owned by swap authority /// 6. `[writable]` Pool Token Account to deposit the initial pool token /// supply. Must be empty, not owned by swap authority. - /// 7. '[]` Token program id + /// 7. '[]` Freeze authority. Optional. + /// 8. '[]` Token program id Initialize(Initialize), /// Swap the tokens in the pool. @@ -187,6 +212,11 @@ pub enum SwapInstruction { /// 8. `[writable]` Fee account, to receive withdrawal fees /// 9. '[]` Token program id WithdrawSingleTokenTypeExactAmountOut(WithdrawSingleTokenTypeExactAmountOut), + + /// Update the freeze authority bit mask. + /// 0. `[writable]` Token-swap + /// 1. `[]` Freeze authority (must be signer) + SetFreezeAuthorityBitMask(SetFreezeAuthorityBitMask), } impl SwapInstruction { @@ -199,11 +229,16 @@ impl SwapInstruction { if rest.len() >= Fees::LEN { let (fees, rest) = rest.split_at(Fees::LEN); let fees = Fees::unpack_unchecked(fees)?; - let swap_curve = SwapCurve::unpack_unchecked(rest)?; + let (swap_curve_data, rest) = rest.split_at(SwapCurve::LEN); + let swap_curve = SwapCurve::unpack_unchecked(swap_curve_data)?; + let (freeze_authority, rest) = Self::unpack_pubkey_option(rest)?; + let freeze_authority_bit_mask = rest[0]; Self::Initialize(Initialize { nonce, fees, swap_curve, + freeze_authority, + freeze_authority_bit_mask, }) } else { return Err(SwapError::InvalidInstruction.into()); @@ -253,6 +288,9 @@ impl SwapInstruction { maximum_pool_token_amount, }) } + 6 => Self::SetFreezeAuthorityBitMask(SetFreezeAuthorityBitMask { + freeze_authority_bit_mask: rest[0], + }), _ => return Err(SwapError::InvalidInstruction.into()), }) } @@ -271,6 +309,28 @@ impl SwapInstruction { } } + fn unpack_pubkey_option(input: &[u8]) -> Result<(COption, &[u8]), ProgramError> { + match input.split_first() { + Option::Some((&0, rest)) => Ok((COption::None, rest)), + Option::Some((&1, rest)) if rest.len() >= 32 => { + let (key, rest) = rest.split_at(32); + let pk = Pubkey::new(key); + Ok((COption::Some(pk), rest)) + } + _ => Err(SwapError::InvalidInstruction.into()), + } + } + + fn pack_pubkey_option(value: &COption, buf: &mut Vec) { + match *value { + COption::Some(ref key) => { + buf.push(1); + buf.extend_from_slice(&key.to_bytes()); + } + COption::None => buf.push(0), + } + } + /// Packs a [SwapInstruction](enum.SwapInstruction.html) into a byte buffer. pub fn pack(&self) -> Vec { let mut buf = Vec::with_capacity(size_of::()); @@ -279,6 +339,8 @@ impl SwapInstruction { nonce, fees, swap_curve, + freeze_authority, + freeze_authority_bit_mask, }) => { buf.push(0); buf.push(*nonce); @@ -288,6 +350,8 @@ impl SwapInstruction { let mut swap_curve_slice = [0u8; SwapCurve::LEN]; Pack::pack_into_slice(swap_curve, &mut swap_curve_slice[..]); buf.extend_from_slice(&swap_curve_slice); + Self::pack_pubkey_option(freeze_authority, &mut buf); + buf.push(*freeze_authority_bit_mask); } Self::Swap(Swap { amount_in, @@ -335,6 +399,13 @@ impl SwapInstruction { buf.extend_from_slice(&destination_token_amount.to_le_bytes()); buf.extend_from_slice(&maximum_pool_token_amount.to_le_bytes()); } + + Self::SetFreezeAuthorityBitMask(SetFreezeAuthorityBitMask { + freeze_authority_bit_mask, + }) => { + buf.push(6); + buf.extend_from_slice(&freeze_authority_bit_mask.to_le_bytes()); + } } buf } @@ -354,11 +425,15 @@ pub fn initialize( nonce: u8, fees: Fees, swap_curve: SwapCurve, + freeze_authority: COption, + freeze_authority_bit_mask: u8, ) -> Result { let init_data = SwapInstruction::Initialize(Initialize { nonce, fees, swap_curve, + freeze_authority, + freeze_authority_bit_mask, }); let data = init_data.pack(); @@ -618,6 +693,8 @@ mod tests { nonce, fees, swap_curve, + freeze_authority: COption::None, + freeze_authority_bit_mask: 0, }); let packed = check.pack(); let mut expect = vec![0u8, nonce]; diff --git a/token-swap/program/src/processor.rs b/token-swap/program/src/processor.rs index 571d7eae31c..c48ae293778 100644 --- a/token-swap/program/src/processor.rs +++ b/token-swap/program/src/processor.rs @@ -1,6 +1,10 @@ //! Program state processor -use crate::constraints::{SwapConstraints, SWAP_CONSTRAINTS}; +use crate::{ + constraints::{SwapConstraints, SWAP_CONSTRAINTS}, + instruction::SetFreezeAuthorityBitMask, + state::{SwapV2, SwapVersionList}, +}; use crate::{ curve::{ base::SwapCurve, @@ -12,7 +16,7 @@ use crate::{ DepositAllTokenTypes, DepositSingleTokenTypeExactAmountIn, Initialize, Swap, SwapInstruction, WithdrawAllTokenTypes, WithdrawSingleTokenTypeExactAmountOut, }, - state::{SwapState, SwapV1, SwapVersion}, + state::{SwapState, SwapVersion}, }; use num_traits::FromPrimitive; use solana_program::{ @@ -149,6 +153,43 @@ impl Processor { signers, ) } + fn get_bit_at(input: u8, n: usize) -> Result { + if n < 8 { + Ok(input & (1 << n) != 0) + } else { + Err(()) + } + } + + fn check_allowed_to_freeze( + token_swap: &dyn SwapState, + freeze_authority_info: &AccountInfo, + ) -> ProgramResult { + return match token_swap.freeze_authority() { + COption::Some(key) => { + if key == *freeze_authority_info.key && freeze_authority_info.is_signer { + Ok(()) + } else { + Err(SwapError::UnauthorizedToFreeze.into()) + } + } + COption::None => Err(SwapError::UnauthorizedToFreeze.into()), + }; + } + + fn check_allowed_to_use(token_swap: &dyn SwapState, bit_position: usize) -> ProgramResult { + let bitmask: u8 = token_swap.freeze_authority_bit_mask(); + match Self::get_bit_at(bitmask, bit_position) { + Ok(val) => { + if val { + Ok(()) + } else { + return Err(SwapError::FrozenAction.into()); + } + } + Err(_) => return Err(SwapError::InvalidBitMaskOperation.into()), + } + } #[allow(clippy::too_many_arguments)] fn check_accounts( @@ -210,6 +251,8 @@ impl Processor { swap_curve: SwapCurve, accounts: &[AccountInfo], swap_constraints: &Option, + freeze_authority: COption, + freeze_authority_bit_mask: u8, ) -> ProgramResult { let account_info_iter = &mut accounts.iter(); let swap_info = next_account_info(account_info_iter)?; @@ -305,7 +348,7 @@ impl Processor { to_u64(initial_amount)?, )?; - let obj = SwapVersion::SwapV1(SwapV1 { + let obj = SwapVersion::SwapV2(SwapV2 { is_initialized: true, nonce, token_program_id, @@ -317,6 +360,8 @@ impl Processor { pool_fee_account: *fee_account_info.key, fees, swap_curve, + freeze_authority, + freeze_authority_bit_mask, }); SwapVersion::pack(obj, &mut swap_info.data.borrow_mut())?; Ok(()) @@ -345,6 +390,7 @@ impl Processor { return Err(ProgramError::IncorrectProgramId); } let token_swap = SwapVersion::unpack(&swap_info.data.borrow())?; + Self::check_allowed_to_use(token_swap.as_ref(), 0)?; if *authority_info.key != Self::authority_id(program_id, swap_info.key, token_swap.nonce())? { @@ -512,6 +558,8 @@ impl Processor { let token_program_info = next_account_info(account_info_iter)?; let token_swap = SwapVersion::unpack(&swap_info.data.borrow())?; + Self::check_allowed_to_use(token_swap.as_ref(), 1)?; + let calculator = &token_swap.swap_curve().calculator; if !calculator.allows_deposits() { return Err(SwapError::UnsupportedCurveOperation.into()); @@ -615,6 +663,8 @@ impl Processor { let token_program_info = next_account_info(account_info_iter)?; let token_swap = SwapVersion::unpack(&swap_info.data.borrow())?; + Self::check_allowed_to_use(token_swap.as_ref(), 2)?; + Self::check_accounts( token_swap.as_ref(), program_id, @@ -739,6 +789,8 @@ impl Processor { let token_program_info = next_account_info(account_info_iter)?; let token_swap = SwapVersion::unpack(&swap_info.data.borrow())?; + Self::check_allowed_to_use(token_swap.as_ref(), 3)?; + let source_account = Self::unpack_token_account(source_info, &token_swap.token_program_id())?; let swap_token_a = @@ -854,6 +906,9 @@ impl Processor { let token_program_info = next_account_info(account_info_iter)?; let token_swap = SwapVersion::unpack(&swap_info.data.borrow())?; + + Self::check_allowed_to_use(token_swap.as_ref(), 4)?; + let destination_account = Self::unpack_token_account(destination_info, &token_swap.token_program_id())?; let swap_token_a = @@ -992,6 +1047,40 @@ impl Processor { Ok(()) } + /// Processes a [SetFreezeAuthorityBitMask](enum.Instruction.html). + pub fn process_set_freeze_authority_bit_mask( + _: &Pubkey, + freeze_authority_bit_mask: u8, + accounts: &[AccountInfo], + ) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + let swap_info = next_account_info(account_info_iter)?; + let freeze_authority_info = next_account_info(account_info_iter)?; + let token_swap = SwapVersion::unpack(&swap_info.data.borrow())?; + + Self::check_allowed_to_freeze(token_swap.as_ref(), freeze_authority_info)?; + + // The way SwapVersion is setup, it is immutable, can't get at it except through this fashion, + // This is because unpack returns an immutable SwapState and pack expects a SwapVersion struct + // which we cannot get to from a SwapState unless we make a new Clone, which we can't really easily do + // (I tried it, causes compiler issues with the swap_curve.) Also is expensive on CPU. + + // With this version method here, and match, we are guaranteed any later swap versions + // must get a branch entry here and we can get direct access to their underlying mutable + // state objects and pack functionalities, bypassing the immutable SwapVersion structure. + + match token_swap.version() { + SwapVersionList::SwapV2 => { + let mut swap = SwapV2::unpack(&swap_info.data.borrow())?; + swap.freeze_authority_bit_mask = freeze_authority_bit_mask; + SwapV2::pack(swap, &mut swap_info.data.borrow_mut())?; + } + SwapVersionList::SwapV1 => return Err(SwapError::SwapV1UnsupportedAction.into()), + } + + Ok(()) + } + /// Processes an [Instruction](enum.Instruction.html). pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult { Self::process_with_constraints(program_id, accounts, input, &SWAP_CONSTRAINTS) @@ -1010,6 +1099,8 @@ impl Processor { nonce, fees, swap_curve, + freeze_authority, + freeze_authority_bit_mask, }) => { msg!("Instruction: Init"); Self::process_initialize( @@ -1019,6 +1110,8 @@ impl Processor { swap_curve, accounts, swap_constraints, + freeze_authority, + freeze_authority_bit_mask, ) } SwapInstruction::Swap(Swap { @@ -1084,6 +1177,16 @@ impl Processor { accounts, ) } + SwapInstruction::SetFreezeAuthorityBitMask(SetFreezeAuthorityBitMask { + freeze_authority_bit_mask, + }) => { + msg!("Instruction: SetFreezeAuthorityBitMask"); + Self::process_set_freeze_authority_bit_mask( + program_id, + freeze_authority_bit_mask, + accounts, + ) + } } } } @@ -1152,6 +1255,18 @@ impl PrintProgramError for SwapError { SwapError::UnsupportedCurveOperation => { msg!("Error: The operation cannot be performed on the given curve") } + SwapError::InvalidBitMaskOperation => { + msg!("Error: Attempted to access an invalid bit in the freeze authority bitmask") + } + SwapError::FrozenAction => { + msg!("Error: This action has been frozen by the Freeze Authority") + } + SwapError::UnauthorizedToFreeze => { + msg!("Error: Unauthorized to freeze") + } + SwapError::SwapV1UnsupportedAction => { + msg!("Error: Attempted to set bit mask on Swap V1") + } } } } @@ -1357,6 +1472,8 @@ mod tests { self.nonce, self.fees.clone(), self.swap_curve.clone(), + COption::None, + 0, ) .unwrap(), vec![ @@ -2453,6 +2570,8 @@ mod tests { accounts.nonce, accounts.fees.clone(), accounts.swap_curve.clone(), + COption::None, + 0, ) .unwrap(), vec![ @@ -2639,6 +2758,8 @@ mod tests { accounts.nonce, accounts.fees.clone(), accounts.swap_curve.clone(), + COption::None, + 0, ) .unwrap(), vec![ @@ -2711,6 +2832,8 @@ mod tests { accounts.nonce, accounts.fees.clone(), accounts.swap_curve.clone(), + COption::None, + 0, ) .unwrap(), vec![ @@ -2779,6 +2902,8 @@ mod tests { accounts.nonce, accounts.fees, accounts.swap_curve.clone(), + COption::None, + 0, ) .unwrap(), vec![ @@ -5754,6 +5879,8 @@ mod tests { accounts.nonce, accounts.fees.clone(), accounts.swap_curve.clone(), + COption::None, + 0, ) .unwrap(), vec![ diff --git a/token-swap/program/src/state.rs b/token-swap/program/src/state.rs index f928b581976..96b4cb10dd0 100644 --- a/token-swap/program/src/state.rs +++ b/token-swap/program/src/state.rs @@ -5,6 +5,7 @@ use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs}; use enum_dispatch::enum_dispatch; use solana_program::{ program_error::ProgramError, + program_option::COption, program_pack::{IsInitialized, Pack, Sealed}, pubkey::Pubkey, }; @@ -12,6 +13,8 @@ use solana_program::{ /// Trait representing access to program state across all versions #[enum_dispatch] pub trait SwapState { + /// The version of the object + fn version(&self) -> SwapVersionList; /// Is the swap initialized, with data written to it fn is_initialized(&self) -> bool; /// Bump seed used to generate the program address / authority @@ -37,12 +40,36 @@ pub trait SwapState { fn fees(&self) -> &Fees; /// Curve associated with swap fn swap_curve(&self) -> &SwapCurve; + + /// Freeze authority + fn freeze_authority(&self) -> COption; + + /// bits, from left to right - 1 disables, 0 enables the actions: + /// 0. process_swap, + /// 1. process_deposit_all_token_types, + /// 2. process_withdraw_all_token_types, + /// 3. process_deposit_single_token_type_exact_amount_in, + /// 4. process_withdraw_single_token_type_exact_amount_out, + fn freeze_authority_bit_mask(&self) -> u8; } /// All versions of SwapState #[enum_dispatch(SwapState)] pub enum SwapVersion { /// Latest version, used for all new swaps + SwapV2, + /// Deprecated version, used for some existing swaps + SwapV1, +} + +/// A hack introduced to allow processor's freeze authority action to figure out +/// Which swap version struct to unpack and pack with to make a mutable reference it can then update +/// the bitmask with. Can't use the SwapVersion enum because this requires a dereferenced SwapV1 or SwapV2 +/// which we can't use due to compiler reference sharing restriction. +pub enum SwapVersionList { + /// Latest version, used for all new swaps + SwapV2, + /// Deprecated version, used for some existing swaps SwapV1, } @@ -51,11 +78,15 @@ pub enum SwapVersion { /// special implementations are provided here impl SwapVersion { /// Size of the latest version of the SwapState - pub const LATEST_LEN: usize = 1 + SwapV1::LEN; // add one for the version enum + pub const LATEST_LEN: usize = 1 + SwapV2::LEN; // add one for the version enum /// Pack a swap into a byte array, based on its version pub fn pack(src: Self, dst: &mut [u8]) -> Result<(), ProgramError> { match src { + Self::SwapV2(swap_info) => { + dst[0] = 2; + SwapV2::pack(swap_info, &mut dst[1..]) + } Self::SwapV1(swap_info) => { dst[0] = 1; SwapV1::pack(swap_info, &mut dst[1..]) @@ -70,6 +101,7 @@ impl SwapVersion { .split_first() .ok_or(ProgramError::InvalidAccountData)?; match version { + 2 => Ok(Box::new(SwapV2::unpack(rest)?)), 1 => Ok(Box::new(SwapV1::unpack(rest)?)), _ => Err(ProgramError::UninitializedAccount), } @@ -84,6 +116,198 @@ impl SwapVersion { } } } +/// Program states. +#[repr(C)] +#[derive(Debug, Default, PartialEq)] +pub struct SwapV2 { + /// Initialized state. + pub is_initialized: bool, + /// Nonce used in program address. + /// The program address is created deterministically with the nonce, + /// swap program id, and swap account pubkey. This program address has + /// authority over the swap's token A account, token B account, and pool + /// token mint. + pub nonce: u8, + + /// Program ID of the tokens being exchanged. + pub token_program_id: Pubkey, + + /// Token A + pub token_a: Pubkey, + /// Token B + pub token_b: Pubkey, + + /// Pool tokens are issued when A or B tokens are deposited. + /// Pool tokens can be withdrawn back to the original A or B token. + pub pool_mint: Pubkey, + + /// Mint information for token A + pub token_a_mint: Pubkey, + /// Mint information for token B + pub token_b_mint: Pubkey, + + /// Pool token account to receive trading and / or withdrawal fees + pub pool_fee_account: Pubkey, + + /// All fee information + pub fees: Fees, + + /// Swap curve parameters, to be unpacked and used by the SwapCurve, which + /// calculates swaps, deposits, and withdrawals + pub swap_curve: SwapCurve, + + /// Freeze authority + pub freeze_authority: COption, + + /// bits, from left to right - 1 disables, 0 enables the actions: + /// 0. process_swap, + /// 1. process_deposit_all_token_types, + /// 2. process_withdraw_all_token_types, + /// 3. process_deposit_single_token_type_exact_amount_in, + /// 4. process_withdraw_single_token_type_exact_amount_out, + pub freeze_authority_bit_mask: u8, +} + +impl SwapState for SwapV2 { + fn version(&self) -> SwapVersionList { + SwapVersionList::SwapV2 + } + + fn is_initialized(&self) -> bool { + self.is_initialized + } + + fn nonce(&self) -> u8 { + self.nonce + } + + fn token_program_id(&self) -> &Pubkey { + &self.token_program_id + } + + fn token_a_account(&self) -> &Pubkey { + &self.token_a + } + + fn token_b_account(&self) -> &Pubkey { + &self.token_b + } + + fn pool_mint(&self) -> &Pubkey { + &self.pool_mint + } + + fn token_a_mint(&self) -> &Pubkey { + &self.token_a_mint + } + + fn token_b_mint(&self) -> &Pubkey { + &self.token_b_mint + } + + fn pool_fee_account(&self) -> &Pubkey { + &self.pool_fee_account + } + + fn fees(&self) -> &Fees { + &self.fees + } + + fn swap_curve(&self) -> &SwapCurve { + &self.swap_curve + } + + fn freeze_authority(&self) -> COption { + self.freeze_authority + } + + fn freeze_authority_bit_mask(&self) -> u8 { + return self.freeze_authority_bit_mask; + } +} + +impl Sealed for SwapV2 {} +impl IsInitialized for SwapV2 { + fn is_initialized(&self) -> bool { + self.is_initialized + } +} + +impl Pack for SwapV2 { + const LEN: usize = 360; + + fn pack_into_slice(&self, output: &mut [u8]) { + let output = array_mut_ref![output, 0, 360]; + let ( + is_initialized, + nonce, + token_program_id, + token_a, + token_b, + pool_mint, + token_a_mint, + token_b_mint, + pool_fee_account, + fees, + swap_curve, + freeze_authority, + freeze_authority_bit_mask, + ) = mut_array_refs![output, 1, 1, 32, 32, 32, 32, 32, 32, 32, 64, 33, 36, 1]; + is_initialized[0] = self.is_initialized as u8; + nonce[0] = self.nonce; + token_program_id.copy_from_slice(self.token_program_id.as_ref()); + token_a.copy_from_slice(self.token_a.as_ref()); + token_b.copy_from_slice(self.token_b.as_ref()); + pool_mint.copy_from_slice(self.pool_mint.as_ref()); + token_a_mint.copy_from_slice(self.token_a_mint.as_ref()); + token_b_mint.copy_from_slice(self.token_b_mint.as_ref()); + pool_fee_account.copy_from_slice(self.pool_fee_account.as_ref()); + self.fees.pack_into_slice(&mut fees[..]); + self.swap_curve.pack_into_slice(&mut swap_curve[..]); + pack_coption_key(&self.freeze_authority, freeze_authority); + *freeze_authority_bit_mask = self.freeze_authority_bit_mask.to_le_bytes(); + } + + /// Unpacks a byte buffer into a [SwapV2](struct.SwapV2.html). + fn unpack_from_slice(input: &[u8]) -> Result { + let input = array_ref![input, 0, 360]; + #[allow(clippy::ptr_offset_with_cast)] + let ( + is_initialized, + nonce, + token_program_id, + token_a, + token_b, + pool_mint, + token_a_mint, + token_b_mint, + pool_fee_account, + fees, + swap_curve, + freeze_authority, + freeze_authority_bit_mask, + ) = array_refs![input, 1, 1, 32, 32, 32, 32, 32, 32, 32, 64, 33, 36, 1]; + Ok(Self { + is_initialized: match is_initialized { + [0] => false, + [1] => true, + _ => return Err(ProgramError::InvalidAccountData), + }, + nonce: nonce[0], + token_program_id: Pubkey::new_from_array(*token_program_id), + token_a: Pubkey::new_from_array(*token_a), + token_b: Pubkey::new_from_array(*token_b), + pool_mint: Pubkey::new_from_array(*pool_mint), + token_a_mint: Pubkey::new_from_array(*token_a_mint), + token_b_mint: Pubkey::new_from_array(*token_b_mint), + pool_fee_account: Pubkey::new_from_array(*pool_fee_account), + fees: Fees::unpack_from_slice(fees)?, + swap_curve: SwapCurve::unpack_from_slice(swap_curve)?, + freeze_authority: unpack_coption_key(freeze_authority)?, + freeze_authority_bit_mask: u8::from_le_bytes(*freeze_authority_bit_mask), + }) + } +} /// Program states. #[repr(C)] @@ -121,12 +345,16 @@ pub struct SwapV1 { /// All fee information pub fees: Fees, - /// Swap curve parameters, to be unpacked and used by the SwapCurve, which + /// Swap curve parameters,to be unpacked and used by the SwapCurve, which /// calculates swaps, deposits, and withdrawals pub swap_curve: SwapCurve, } impl SwapState for SwapV1 { + fn version(&self) -> SwapVersionList { + SwapVersionList::SwapV1 + } + fn is_initialized(&self) -> bool { self.is_initialized } @@ -170,6 +398,14 @@ impl SwapState for SwapV1 { fn swap_curve(&self) -> &SwapCurve { &self.swap_curve } + + fn freeze_authority(&self) -> COption { + COption::None + } + + fn freeze_authority_bit_mask(&self) -> u8 { + return 0 as u8; + } } impl Sealed for SwapV1 {} @@ -247,6 +483,28 @@ impl Pack for SwapV1 { } } +fn pack_coption_key(src: &COption, dst: &mut [u8; 36]) { + let (tag, body) = mut_array_refs![dst, 4, 32]; + match src { + COption::Some(key) => { + *tag = [1, 0, 0, 0]; + body.copy_from_slice(key.as_ref()); + } + COption::None => { + *tag = [0; 4]; + } + } +} + +fn unpack_coption_key(src: &[u8; 36]) -> Result, ProgramError> { + let (tag, body) = array_refs![src, 4, 32]; + match *tag { + [0, 0, 0, 0] => Ok(COption::None), + [1, 0, 0, 0] => Ok(COption::Some(Pubkey::new_from_array(*body))), + _ => Err(ProgramError::InvalidAccountData), + } +} + #[cfg(test)] mod tests { use super::*; From 31c35877ae839db3861424ad1c4a67d1ee6af669 Mon Sep 17 00:00:00 2001 From: Jordan Prince Date: Sun, 28 Mar 2021 20:21:50 -0500 Subject: [PATCH 2/8] Think I figured out the curve cloning issue, and thus a better way to save freeze authority via cloning. I like this situation better - no extra enum and hackiness required. --- token-swap/program/src/curve/base.rs | 1 - token-swap/program/src/processor.rs | 35 ++++++++++++++-------------- token-swap/program/src/state.rs | 21 ----------------- 3 files changed, 17 insertions(+), 40 deletions(-) diff --git a/token-swap/program/src/curve/base.rs b/token-swap/program/src/curve/base.rs index dd106b78451..fb5bb1405cf 100644 --- a/token-swap/program/src/curve/base.rs +++ b/token-swap/program/src/curve/base.rs @@ -148,7 +148,6 @@ impl Default for SwapCurve { /// Clone takes advantage of pack / unpack to get around the difficulty of /// cloning dynamic objects. /// Note that this is only to be used for testing. -#[cfg(any(test, feature = "fuzz"))] impl Clone for SwapCurve { fn clone(&self) -> Self { let mut packed_self = [0u8; Self::LEN]; diff --git a/token-swap/program/src/processor.rs b/token-swap/program/src/processor.rs index c48ae293778..c71e27f3015 100644 --- a/token-swap/program/src/processor.rs +++ b/token-swap/program/src/processor.rs @@ -3,7 +3,7 @@ use crate::{ constraints::{SwapConstraints, SWAP_CONSTRAINTS}, instruction::SetFreezeAuthorityBitMask, - state::{SwapV2, SwapVersionList}, + state::SwapV2, }; use crate::{ curve::{ @@ -1060,23 +1060,22 @@ impl Processor { Self::check_allowed_to_freeze(token_swap.as_ref(), freeze_authority_info)?; - // The way SwapVersion is setup, it is immutable, can't get at it except through this fashion, - // This is because unpack returns an immutable SwapState and pack expects a SwapVersion struct - // which we cannot get to from a SwapState unless we make a new Clone, which we can't really easily do - // (I tried it, causes compiler issues with the swap_curve.) Also is expensive on CPU. - - // With this version method here, and match, we are guaranteed any later swap versions - // must get a branch entry here and we can get direct access to their underlying mutable - // state objects and pack functionalities, bypassing the immutable SwapVersion structure. - - match token_swap.version() { - SwapVersionList::SwapV2 => { - let mut swap = SwapV2::unpack(&swap_info.data.borrow())?; - swap.freeze_authority_bit_mask = freeze_authority_bit_mask; - SwapV2::pack(swap, &mut swap_info.data.borrow_mut())?; - } - SwapVersionList::SwapV1 => return Err(SwapError::SwapV1UnsupportedAction.into()), - } + let clone = SwapVersion::SwapV2(SwapV2 { + is_initialized: true, + nonce: token_swap.nonce(), + token_program_id: *token_swap.token_program_id(), + token_a: *token_swap.token_a_account(), + token_b: *token_swap.token_b_account(), + pool_mint: *token_swap.pool_mint(), + token_a_mint: *token_swap.token_a_mint(), + token_b_mint: *token_swap.token_b_mint(), + pool_fee_account: *token_swap.pool_fee_account(), + fees: token_swap.fees().clone(), + swap_curve: token_swap.swap_curve().clone(), + freeze_authority: token_swap.freeze_authority(), + freeze_authority_bit_mask, + }); + SwapVersion::pack(clone, &mut swap_info.data.borrow_mut())?; Ok(()) } diff --git a/token-swap/program/src/state.rs b/token-swap/program/src/state.rs index 96b4cb10dd0..282f9bb5da0 100644 --- a/token-swap/program/src/state.rs +++ b/token-swap/program/src/state.rs @@ -13,8 +13,6 @@ use solana_program::{ /// Trait representing access to program state across all versions #[enum_dispatch] pub trait SwapState { - /// The version of the object - fn version(&self) -> SwapVersionList; /// Is the swap initialized, with data written to it fn is_initialized(&self) -> bool; /// Bump seed used to generate the program address / authority @@ -62,17 +60,6 @@ pub enum SwapVersion { SwapV1, } -/// A hack introduced to allow processor's freeze authority action to figure out -/// Which swap version struct to unpack and pack with to make a mutable reference it can then update -/// the bitmask with. Can't use the SwapVersion enum because this requires a dereferenced SwapV1 or SwapV2 -/// which we can't use due to compiler reference sharing restriction. -pub enum SwapVersionList { - /// Latest version, used for all new swaps - SwapV2, - /// Deprecated version, used for some existing swaps - SwapV1, -} - /// SwapVersion does not implement program_pack::Pack because there are size /// checks on pack and unpack that would break backwards compatibility, so /// special implementations are provided here @@ -169,10 +156,6 @@ pub struct SwapV2 { } impl SwapState for SwapV2 { - fn version(&self) -> SwapVersionList { - SwapVersionList::SwapV2 - } - fn is_initialized(&self) -> bool { self.is_initialized } @@ -351,10 +334,6 @@ pub struct SwapV1 { } impl SwapState for SwapV1 { - fn version(&self) -> SwapVersionList { - SwapVersionList::SwapV1 - } - fn is_initialized(&self) -> bool { self.is_initialized } From a2355496a4cf0ad8616f0673e1c785e227072dbb Mon Sep 17 00:00:00 2001 From: Jordan Prince Date: Mon, 29 Mar 2021 12:00:20 -0500 Subject: [PATCH 3/8] Added some good tests to cover the new logic and some clippy fixes --- token-swap/program/src/instruction.rs | 26 +- token-swap/program/src/processor.rs | 1100 +++++++++++++++++++++++-- token-swap/program/src/state.rs | 6 +- 3 files changed, 1042 insertions(+), 90 deletions(-) diff --git a/token-swap/program/src/instruction.rs b/token-swap/program/src/instruction.rs index 8b123a769a0..fea5bc16c6e 100644 --- a/token-swap/program/src/instruction.rs +++ b/token-swap/program/src/instruction.rs @@ -30,7 +30,7 @@ pub struct Initialize { pub swap_curve: SwapCurve, /// The freeze authority of the swap. pub freeze_authority: COption, - /// bits, from left to right - 1 disables, 0 enables the actions: + /// bits, from right to left - 1 disables, 0 enables the actions: /// 0. process_swap, /// 1. process_deposit_all_token_types, /// 2. process_withdraw_all_token_types, @@ -107,7 +107,7 @@ pub struct WithdrawSingleTokenTypeExactAmountOut { #[repr(C)] #[derive(Clone, Debug, PartialEq)] pub struct SetFreezeAuthorityBitMask { - /// bits, from left to right - 1 disables, 0 enables the actions: + /// bits, from right to left - 1 disables, 0 enables the actions: /// 0. process_swap, /// 1. process_deposit_all_token_types, /// 2. process_withdraw_all_token_types, @@ -644,6 +644,28 @@ pub fn swap( }) } +/// Creates a set_freeze_authority_bit_mask instruction +/// Creates a 'swap' instruction. +pub fn set_freeze_authority_bit_mask( + program_id: &Pubkey, + swap_pubkey: &Pubkey, + freeze_authority_pubkey: &Pubkey, + instruction: SetFreezeAuthorityBitMask, +) -> Result { + let data = SwapInstruction::SetFreezeAuthorityBitMask(instruction).pack(); + + let accounts = vec![ + AccountMeta::new(*swap_pubkey, false), + AccountMeta::new_readonly(*freeze_authority_pubkey, true), + ]; + + Ok(Instruction { + program_id: *program_id, + accounts, + data, + }) +} + /// Unpacks a reference from a bytes buffer. /// TODO actually pack / unpack instead of relying on normal memory layout. pub fn unpack(input: &[u8]) -> Result<&T, ProgramError> { diff --git a/token-swap/program/src/processor.rs b/token-swap/program/src/processor.rs index c71e27f3015..00e80cae63a 100644 --- a/token-swap/program/src/processor.rs +++ b/token-swap/program/src/processor.rs @@ -165,7 +165,7 @@ impl Processor { token_swap: &dyn SwapState, freeze_authority_info: &AccountInfo, ) -> ProgramResult { - return match token_swap.freeze_authority() { + match token_swap.freeze_authority() { COption::Some(key) => { if key == *freeze_authority_info.key && freeze_authority_info.is_signer { Ok(()) @@ -174,20 +174,20 @@ impl Processor { } } COption::None => Err(SwapError::UnauthorizedToFreeze.into()), - }; + } } fn check_allowed_to_use(token_swap: &dyn SwapState, bit_position: usize) -> ProgramResult { let bitmask: u8 = token_swap.freeze_authority_bit_mask(); match Self::get_bit_at(bitmask, bit_position) { - Ok(val) => { - if val { - Ok(()) + Ok(frozen) => { + if frozen { + Err(SwapError::FrozenAction.into()) } else { - return Err(SwapError::FrozenAction.into()); + Ok(()) } } - Err(_) => return Err(SwapError::InvalidBitMaskOperation.into()), + Err(_) => Err(SwapError::InvalidBitMaskOperation.into()), } } @@ -244,6 +244,7 @@ impl Processor { } /// Processes an [Initialize](enum.Instruction.html). + #[allow(clippy::too_many_arguments)] pub fn process_initialize( program_id: &Pubkey, nonce: u8, @@ -1288,8 +1289,9 @@ mod tests { constant_product::ConstantProductCurve, offset::OffsetCurve, }, instruction::{ - deposit_all_token_types, deposit_single_token_type_exact_amount_in, initialize, swap, - withdraw_all_token_types, withdraw_single_token_type_exact_amount_out, + deposit_all_token_types, deposit_single_token_type_exact_amount_in, initialize, + set_freeze_authority_bit_mask, swap, withdraw_all_token_types, + withdraw_single_token_type_exact_amount_out, }, }; use solana_program::{instruction::Instruction, program_stubs, rent::Rent}; @@ -1378,6 +1380,7 @@ mod tests { token_b_account: Account, token_b_mint_key: Pubkey, token_b_mint_account: Account, + freeze_authority: COption, } impl SwapAccountInfo { @@ -1387,8 +1390,14 @@ mod tests { swap_curve: SwapCurve, token_a_amount: u64, token_b_amount: u64, + with_freeze: bool, ) -> Self { let swap_key = Pubkey::new_unique(); + + let freeze_authority = match with_freeze { + true => COption::Some(Pubkey::new_unique()), + false => COption::None, + }; let swap_account = Account::new(0, SwapVersion::LATEST_LEN, &SWAP_PROGRAM_ID); let (authority_key, nonce) = Pubkey::find_program_address(&[&swap_key.to_bytes()[..]], &SWAP_PROGRAM_ID); @@ -1453,6 +1462,7 @@ mod tests { token_b_account, token_b_mint_key, token_b_mint_account, + freeze_authority, } } @@ -1471,7 +1481,7 @@ mod tests { self.nonce, self.fees.clone(), self.swap_curve.clone(), - COption::None, + self.freeze_authority, 0, ) .unwrap(), @@ -1627,6 +1637,27 @@ mod tests { Ok(()) } + pub fn set_freeze_authority_bit_mask( + &mut self, + freeze_authority_bit_mask: u8, + ) -> ProgramResult { + match self.freeze_authority { + COption::Some(key) => do_process_instruction( + set_freeze_authority_bit_mask( + &SWAP_PROGRAM_ID, + &TOKEN_PROGRAM_ID, + &key, + SetFreezeAuthorityBitMask { + freeze_authority_bit_mask, + }, + ) + .unwrap(), + vec![&mut self.swap_account, &mut Account::default()], + ), + COption::None => return Err(SwapError::InvalidFreezeAuthority.into()), + } + } + #[allow(clippy::too_many_arguments)] pub fn deposit_all_token_types( &mut self, @@ -2115,8 +2146,14 @@ mod tests { calculator: Box::new(ConstantProductCurve {}), }; - let mut accounts = - SwapAccountInfo::new(&user_key, fees, swap_curve, token_a_amount, token_b_amount); + let mut accounts = SwapAccountInfo::new( + &user_key, + fees, + swap_curve, + token_a_amount, + token_b_amount, + false, + ); // wrong nonce for authority_key { @@ -2626,8 +2663,14 @@ mod tests { curve_type: CurveType::ConstantPrice, calculator: Box::new(ConstantPriceCurve { token_b_price }), }; - let mut accounts = - SwapAccountInfo::new(&user_key, fees, swap_curve, token_a_amount, token_b_amount); + let mut accounts = SwapAccountInfo::new( + &user_key, + fees, + swap_curve, + token_a_amount, + token_b_amount, + false, + ); assert_eq!( Err(SwapError::InvalidCurve.into()), accounts.initialize_swap() @@ -2651,8 +2694,14 @@ mod tests { curve_type: CurveType::ConstantPrice, calculator: Box::new(ConstantPriceCurve { token_b_price }), }; - let mut accounts = - SwapAccountInfo::new(&user_key, fees, swap_curve, token_a_amount, token_b_amount); + let mut accounts = SwapAccountInfo::new( + &user_key, + fees, + swap_curve, + token_a_amount, + token_b_amount, + false, + ); accounts.initialize_swap().unwrap(); } @@ -2673,8 +2722,14 @@ mod tests { curve_type: CurveType::Offset, calculator: Box::new(OffsetCurve { token_b_offset }), }; - let mut accounts = - SwapAccountInfo::new(&user_key, fees, swap_curve, token_a_amount, token_b_amount); + let mut accounts = SwapAccountInfo::new( + &user_key, + fees, + swap_curve, + token_a_amount, + token_b_amount, + false, + ); assert_eq!( Err(SwapError::InvalidCurve.into()), accounts.initialize_swap() @@ -2698,8 +2753,14 @@ mod tests { curve_type: CurveType::Offset, calculator: Box::new(OffsetCurve { token_b_offset }), }; - let mut accounts = - SwapAccountInfo::new(&user_key, fees, swap_curve, token_a_amount, token_b_amount); + let mut accounts = SwapAccountInfo::new( + &user_key, + fees, + swap_curve, + token_a_amount, + token_b_amount, + false, + ); accounts.initialize_swap().unwrap(); } @@ -2740,6 +2801,7 @@ mod tests { swap_curve, token_a_amount, token_b_amount, + false, ); assert_eq!( Err(SwapError::InvalidOwner.into()), @@ -2814,6 +2876,7 @@ mod tests { swap_curve, token_a_amount, token_b_amount, + false, ); assert_eq!( Err(SwapError::InvalidFee.into()), @@ -2886,6 +2949,7 @@ mod tests { swap_curve, token_a_amount, token_b_amount, + false, ); do_process_instruction_with_fee_constraints( initialize( @@ -2982,8 +3046,14 @@ mod tests { calculator: Box::new(ConstantProductCurve {}), }; - let mut accounts = - SwapAccountInfo::new(&user_key, fees, swap_curve, token_a_amount, token_b_amount); + let mut accounts = SwapAccountInfo::new( + &user_key, + fees, + swap_curve, + token_a_amount, + token_b_amount, + false, + ); // depositing 10% of the current pool amount in token A and B means // that our pool tokens will be worth 1 / 10 of the current pool amount @@ -3561,6 +3631,134 @@ mod tests { pool_account.amount + swap_pool_account.amount ); } + + // adding freeze authority and turning off deposit throws FrozenAction + { + let fees = Fees { + trade_fee_numerator, + trade_fee_denominator, + owner_trade_fee_numerator, + owner_trade_fee_denominator, + owner_withdraw_fee_numerator, + owner_withdraw_fee_denominator, + host_fee_numerator, + host_fee_denominator, + }; + + let curve_type = CurveType::ConstantProduct; + let swap_curve = SwapCurve { + curve_type, + calculator: Box::new(ConstantProductCurve {}), + }; + let mut accounts = SwapAccountInfo::new( + &user_key, + fees.clone(), + swap_curve.clone(), + token_a_amount, + token_b_amount, + true, + ); + + accounts.initialize_swap().unwrap(); + accounts.set_freeze_authority_bit_mask(1 << 1).unwrap(); + + let ( + token_a_key, + mut token_a_account, + token_b_key, + mut token_b_account, + pool_key, + mut pool_account, + ) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0); + assert_eq!( + Err(SwapError::FrozenAction.into()), + accounts.deposit_all_token_types( + &depositor_key, + &token_a_key, + &mut token_a_account, + &token_b_key, + &mut token_b_account, + &pool_key, + &mut pool_account, + pool_amount.try_into().unwrap(), + deposit_a, + deposit_b, + ) + ); + } + + // adding freeze authority and leaving on deposit works fine + { + let fees = Fees { + trade_fee_numerator, + trade_fee_denominator, + owner_trade_fee_numerator, + owner_trade_fee_denominator, + owner_withdraw_fee_numerator, + owner_withdraw_fee_denominator, + host_fee_numerator, + host_fee_denominator, + }; + + let curve_type = CurveType::ConstantProduct; + let swap_curve = SwapCurve { + curve_type, + calculator: Box::new(ConstantProductCurve {}), + }; + let mut accounts = SwapAccountInfo::new( + &user_key, + fees.clone(), + swap_curve.clone(), + token_a_amount, + token_b_amount, + true, + ); + + accounts.initialize_swap().unwrap(); + + let ( + token_a_key, + mut token_a_account, + token_b_key, + mut token_b_account, + pool_key, + mut pool_account, + ) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0); + accounts + .deposit_all_token_types( + &depositor_key, + &token_a_key, + &mut token_a_account, + &token_b_key, + &mut token_b_account, + &pool_key, + &mut pool_account, + pool_amount.try_into().unwrap(), + deposit_a, + deposit_b, + ) + .unwrap(); + + let swap_token_a = + spl_token::state::Account::unpack(&accounts.token_a_account.data).unwrap(); + assert_eq!(swap_token_a.amount, deposit_a + token_a_amount); + let swap_token_b = + spl_token::state::Account::unpack(&accounts.token_b_account.data).unwrap(); + assert_eq!(swap_token_b.amount, deposit_b + token_b_amount); + let token_a = spl_token::state::Account::unpack(&token_a_account.data).unwrap(); + assert_eq!(token_a.amount, 0); + let token_b = spl_token::state::Account::unpack(&token_b_account.data).unwrap(); + assert_eq!(token_b.amount, 0); + let pool_account = spl_token::state::Account::unpack(&pool_account.data).unwrap(); + let swap_pool_account = + spl_token::state::Account::unpack(&accounts.pool_token_account.data).unwrap(); + let pool_mint = + spl_token::state::Mint::unpack(&accounts.pool_mint_account.data).unwrap(); + assert_eq!( + pool_mint.supply, + pool_account.amount + swap_pool_account.amount + ); + } } #[test] @@ -3602,8 +3800,14 @@ mod tests { let minimum_token_a_amount = initial_a / 40; let minimum_token_b_amount = initial_b / 40; - let mut accounts = - SwapAccountInfo::new(&user_key, fees, swap_curve, token_a_amount, token_b_amount); + let mut accounts = SwapAccountInfo::new( + &user_key, + fees, + swap_curve, + token_a_amount, + token_b_amount, + false, + ); // swap not initialized { @@ -4368,66 +4572,240 @@ mod tests { TryInto::::try_into(results.token_b_amount).unwrap() ); } - } - - #[test] - fn test_deposit_one_exact_in() { - let user_key = Pubkey::new_unique(); - let depositor_key = Pubkey::new_unique(); - let trade_fee_numerator = 1; - let trade_fee_denominator = 2; - let owner_trade_fee_numerator = 1; - let owner_trade_fee_denominator = 10; - let owner_withdraw_fee_numerator = 1; - let owner_withdraw_fee_denominator = 5; - let host_fee_numerator = 20; - let host_fee_denominator = 100; - - let fees = Fees { - trade_fee_numerator, - trade_fee_denominator, - owner_trade_fee_numerator, - owner_trade_fee_denominator, - owner_withdraw_fee_numerator, - owner_withdraw_fee_denominator, - host_fee_numerator, - host_fee_denominator, - }; - let token_a_amount = 1000; - let token_b_amount = 9000; - let curve_type = CurveType::ConstantProduct; - let swap_curve = SwapCurve { - curve_type, - calculator: Box::new(ConstantProductCurve {}), - }; + // add freeze authority and freeze withdrawal throws FrozenAction error + { + let fees = Fees { + trade_fee_numerator, + trade_fee_denominator, + owner_trade_fee_numerator, + owner_trade_fee_denominator, + owner_withdraw_fee_numerator, + owner_withdraw_fee_denominator, + host_fee_numerator, + host_fee_denominator, + }; - let mut accounts = - SwapAccountInfo::new(&user_key, fees, swap_curve, token_a_amount, token_b_amount); + let curve_type = CurveType::ConstantProduct; + let swap_curve = SwapCurve { + curve_type, + calculator: Box::new(ConstantProductCurve {}), + }; - let deposit_a = token_a_amount / 10; - let deposit_b = token_b_amount / 10; - let pool_amount = to_u64(INITIAL_SWAP_POOL_AMOUNT / 100).unwrap(); + let mut accounts = SwapAccountInfo::new( + &user_key, + fees, + swap_curve, + token_a_amount, + token_b_amount, + true, + ); + accounts.initialize_swap().unwrap(); + accounts.set_freeze_authority_bit_mask(1 << 2).unwrap(); - // swap not initialized - { let ( token_a_key, mut token_a_account, - _token_b_key, - _token_b_account, - pool_key, - mut pool_account, - ) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0); - assert_eq!( - Err(ProgramError::UninitializedAccount), - accounts.deposit_single_token_type_exact_amount_in( - &depositor_key, - &token_a_key, - &mut token_a_account, - &pool_key, - &mut pool_account, - deposit_a, + token_b_key, + mut token_b_account, + _pool_key, + mut _pool_account, + ) = accounts.setup_token_accounts(&user_key, &withdrawer_key, 0, 0, 0); + + let pool_fee_key = accounts.pool_fee_key; + let mut pool_fee_account = accounts.pool_fee_account.clone(); + let fee_account = spl_token::state::Account::unpack(&pool_fee_account.data).unwrap(); + let pool_fee_amount = fee_account.amount; + + assert_eq!( + Err(SwapError::FrozenAction.into()), + accounts.withdraw_all_token_types( + &user_key, + &pool_fee_key, + &mut pool_fee_account, + &token_a_key, + &mut token_a_account, + &token_b_key, + &mut token_b_account, + pool_fee_amount, + 0, + 0, + ) + ) + } + // add freeze authority does not break withdrawal + { + let fees = Fees { + trade_fee_numerator, + trade_fee_denominator, + owner_trade_fee_numerator, + owner_trade_fee_denominator, + owner_withdraw_fee_numerator, + owner_withdraw_fee_denominator, + host_fee_numerator, + host_fee_denominator, + }; + + let curve_type = CurveType::ConstantProduct; + let swap_curve = SwapCurve { + curve_type, + calculator: Box::new(ConstantProductCurve {}), + }; + + let mut accounts = SwapAccountInfo::new( + &user_key, + fees, + swap_curve, + token_a_amount, + token_b_amount, + true, + ); + accounts.initialize_swap().unwrap(); + + let ( + token_a_key, + mut token_a_account, + token_b_key, + mut token_b_account, + pool_key, + mut pool_account, + ) = accounts.setup_token_accounts( + &user_key, + &withdrawer_key, + initial_a, + initial_b, + initial_pool.try_into().unwrap(), + ); + + accounts + .withdraw_all_token_types( + &withdrawer_key, + &pool_key, + &mut pool_account, + &token_a_key, + &mut token_a_account, + &token_b_key, + &mut token_b_account, + withdraw_amount.try_into().unwrap(), + minimum_token_a_amount, + minimum_token_b_amount, + ) + .unwrap(); + + let swap_token_a = + spl_token::state::Account::unpack(&accounts.token_a_account.data).unwrap(); + let swap_token_b = + spl_token::state::Account::unpack(&accounts.token_b_account.data).unwrap(); + let pool_mint = + spl_token::state::Mint::unpack(&accounts.pool_mint_account.data).unwrap(); + let withdraw_fee = accounts.fees.owner_withdraw_fee(withdraw_amount).unwrap(); + let results = accounts + .swap_curve + .calculator + .pool_tokens_to_trading_tokens( + withdraw_amount - withdraw_fee, + pool_mint.supply.try_into().unwrap(), + swap_token_a.amount.try_into().unwrap(), + swap_token_b.amount.try_into().unwrap(), + RoundDirection::Floor, + ) + .unwrap(); + assert_eq!( + swap_token_a.amount, + token_a_amount - to_u64(results.token_a_amount).unwrap() + ); + assert_eq!( + swap_token_b.amount, + token_b_amount - to_u64(results.token_b_amount).unwrap() + ); + let token_a = spl_token::state::Account::unpack(&token_a_account.data).unwrap(); + assert_eq!( + token_a.amount, + initial_a + to_u64(results.token_a_amount).unwrap() + ); + let token_b = spl_token::state::Account::unpack(&token_b_account.data).unwrap(); + assert_eq!( + token_b.amount, + initial_b + to_u64(results.token_b_amount).unwrap() + ); + let pool_account = spl_token::state::Account::unpack(&pool_account.data).unwrap(); + assert_eq!( + pool_account.amount, + to_u64(initial_pool - withdraw_amount).unwrap() + ); + let fee_account = + spl_token::state::Account::unpack(&accounts.pool_fee_account.data).unwrap(); + assert_eq!( + fee_account.amount, + TryInto::::try_into(withdraw_fee).unwrap() + ); + } + } + + #[test] + fn test_deposit_one_exact_in() { + let user_key = Pubkey::new_unique(); + let depositor_key = Pubkey::new_unique(); + let trade_fee_numerator = 1; + let trade_fee_denominator = 2; + let owner_trade_fee_numerator = 1; + let owner_trade_fee_denominator = 10; + let owner_withdraw_fee_numerator = 1; + let owner_withdraw_fee_denominator = 5; + let host_fee_numerator = 20; + let host_fee_denominator = 100; + + let fees = Fees { + trade_fee_numerator, + trade_fee_denominator, + owner_trade_fee_numerator, + owner_trade_fee_denominator, + owner_withdraw_fee_numerator, + owner_withdraw_fee_denominator, + host_fee_numerator, + host_fee_denominator, + }; + + let token_a_amount = 1000; + let token_b_amount = 9000; + let curve_type = CurveType::ConstantProduct; + let swap_curve = SwapCurve { + curve_type, + calculator: Box::new(ConstantProductCurve {}), + }; + + let mut accounts = SwapAccountInfo::new( + &user_key, + fees, + swap_curve, + token_a_amount, + token_b_amount, + false, + ); + + let deposit_a = token_a_amount / 10; + let deposit_b = token_b_amount / 10; + let pool_amount = to_u64(INITIAL_SWAP_POOL_AMOUNT / 100).unwrap(); + + // swap not initialized + { + let ( + token_a_key, + mut token_a_account, + _token_b_key, + _token_b_account, + pool_key, + mut pool_account, + ) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0); + assert_eq!( + Err(ProgramError::UninitializedAccount), + accounts.deposit_single_token_type_exact_amount_in( + &depositor_key, + &token_a_key, + &mut token_a_account, + &pool_key, + &mut pool_account, + deposit_a, pool_amount, ) ); @@ -4881,6 +5259,143 @@ mod tests { pool_account.amount + swap_pool_account.amount ); } + + // freeze authority throws FrozenAction error when bit mask turned on + { + let fees = Fees { + trade_fee_numerator, + trade_fee_denominator, + owner_trade_fee_numerator, + owner_trade_fee_denominator, + owner_withdraw_fee_numerator, + owner_withdraw_fee_denominator, + host_fee_numerator, + host_fee_denominator, + }; + + let curve_type = CurveType::ConstantProduct; + let swap_curve = SwapCurve { + curve_type, + calculator: Box::new(ConstantProductCurve {}), + }; + + let mut accounts = SwapAccountInfo::new( + &user_key, + fees, + swap_curve, + token_a_amount, + token_b_amount, + true, + ); + accounts.initialize_swap().unwrap(); + accounts.set_freeze_authority_bit_mask(1 << 3).unwrap(); + + let ( + token_a_key, + mut token_a_account, + _token_b_key, + mut _token_b_account, + pool_key, + mut pool_account, + ) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0); + assert_eq!( + Err(SwapError::FrozenAction.into()), + accounts.deposit_single_token_type_exact_amount_in( + &depositor_key, + &token_a_key, + &mut token_a_account, + &pool_key, + &mut pool_account, + deposit_a, + pool_amount, + ) + ); + } + + // freeze authority correctly deposits when no mask set + { + let fees = Fees { + trade_fee_numerator, + trade_fee_denominator, + owner_trade_fee_numerator, + owner_trade_fee_denominator, + owner_withdraw_fee_numerator, + owner_withdraw_fee_denominator, + host_fee_numerator, + host_fee_denominator, + }; + + let curve_type = CurveType::ConstantProduct; + let swap_curve = SwapCurve { + curve_type, + calculator: Box::new(ConstantProductCurve {}), + }; + + let mut accounts = SwapAccountInfo::new( + &user_key, + fees, + swap_curve, + token_a_amount, + token_b_amount, + true, + ); + accounts.initialize_swap().unwrap(); + + let ( + token_a_key, + mut token_a_account, + token_b_key, + mut token_b_account, + pool_key, + mut pool_account, + ) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0); + accounts + .deposit_single_token_type_exact_amount_in( + &depositor_key, + &token_a_key, + &mut token_a_account, + &pool_key, + &mut pool_account, + deposit_a, + pool_amount, + ) + .unwrap(); + + let swap_token_a = + spl_token::state::Account::unpack(&accounts.token_a_account.data).unwrap(); + assert_eq!(swap_token_a.amount, deposit_a + token_a_amount); + + let token_a = spl_token::state::Account::unpack(&token_a_account.data).unwrap(); + assert_eq!(token_a.amount, 0); + + accounts + .deposit_single_token_type_exact_amount_in( + &depositor_key, + &token_b_key, + &mut token_b_account, + &pool_key, + &mut pool_account, + deposit_b, + pool_amount, + ) + .unwrap(); + let swap_token_b = + spl_token::state::Account::unpack(&accounts.token_b_account.data).unwrap(); + assert_eq!(swap_token_b.amount, deposit_b + token_b_amount); + + let token_b = spl_token::state::Account::unpack(&token_b_account.data).unwrap(); + assert_eq!(token_b.amount, 0); + + let pool_account = spl_token::state::Account::unpack(&pool_account.data).unwrap(); + let swap_pool_account = + spl_token::state::Account::unpack(&accounts.pool_token_account.data).unwrap(); + let pool_mint = + spl_token::state::Mint::unpack(&accounts.pool_mint_account.data).unwrap(); + assert_eq!( + pool_mint.supply, + pool_account.amount + swap_pool_account.amount + ); + } } #[test] @@ -4922,8 +5437,14 @@ mod tests { let destination_a_amount = initial_a / 40; let destination_b_amount = initial_b / 40; - let mut accounts = - SwapAccountInfo::new(&user_key, fees, swap_curve, token_a_amount, token_b_amount); + let mut accounts = SwapAccountInfo::new( + &user_key, + fees, + swap_curve, + token_a_amount, + token_b_amount, + false, + ); // swap not initialized { @@ -5532,6 +6053,162 @@ mod tests { let token_a = spl_token::state::Account::unpack(&token_a_account.data).unwrap(); assert_eq!(token_a.amount, initial_a + fee_a_amount); } + + // freeze authority throws FrozenAction error when bit mask flipped + { + let fees = Fees { + trade_fee_numerator, + trade_fee_denominator, + owner_trade_fee_numerator, + owner_trade_fee_denominator, + owner_withdraw_fee_numerator, + owner_withdraw_fee_denominator, + host_fee_numerator, + host_fee_denominator, + }; + + let curve_type = CurveType::ConstantProduct; + let swap_curve = SwapCurve { + curve_type, + calculator: Box::new(ConstantProductCurve {}), + }; + + let mut accounts = SwapAccountInfo::new( + &user_key, + fees, + swap_curve, + token_a_amount, + token_b_amount, + true, + ); + accounts.initialize_swap().unwrap(); + // Let's add 1 here to test possibility of having additional bits flipped + accounts + .set_freeze_authority_bit_mask((1 << 4) + 1) + .unwrap(); + + let ( + token_a_key, + mut token_a_account, + _token_b_key, + _token_b_account, + pool_key, + mut pool_account, + ) = accounts.setup_token_accounts( + &user_key, + &withdrawer_key, + initial_a, + initial_b, + initial_pool.try_into().unwrap(), + ); + assert_eq!( + Err(SwapError::FrozenAction.into()), + accounts.withdraw_single_token_type_exact_amount_out( + &withdrawer_key, + &pool_key, + &mut pool_account, + &token_a_key, + &mut token_a_account, + destination_a_amount, + maximum_pool_token_amount, + ) + ); + } + + // freeze authority has correct withdrawal + { + let fees = Fees { + trade_fee_numerator, + trade_fee_denominator, + owner_trade_fee_numerator, + owner_trade_fee_denominator, + owner_withdraw_fee_numerator, + owner_withdraw_fee_denominator, + host_fee_numerator, + host_fee_denominator, + }; + + let curve_type = CurveType::ConstantProduct; + let swap_curve = SwapCurve { + curve_type, + calculator: Box::new(ConstantProductCurve {}), + }; + + let mut accounts = SwapAccountInfo::new( + &user_key, + fees, + swap_curve, + token_a_amount, + token_b_amount, + true, + ); + accounts.initialize_swap().unwrap(); + let ( + token_a_key, + mut token_a_account, + _token_b_key, + _token_b_account, + pool_key, + mut pool_account, + ) = accounts.setup_token_accounts( + &user_key, + &withdrawer_key, + initial_a, + initial_b, + initial_pool.try_into().unwrap(), + ); + + let swap_token_a = + spl_token::state::Account::unpack(&accounts.token_a_account.data).unwrap(); + let swap_token_b = + spl_token::state::Account::unpack(&accounts.token_b_account.data).unwrap(); + let pool_mint = + spl_token::state::Mint::unpack(&accounts.pool_mint_account.data).unwrap(); + + let pool_token_amount = accounts + .swap_curve + .trading_tokens_to_pool_tokens( + destination_a_amount.try_into().unwrap(), + (swap_token_a.amount - destination_a_amount) + .try_into() + .unwrap(), + swap_token_b.amount.try_into().unwrap(), + pool_mint.supply.try_into().unwrap(), + TradeDirection::AtoB, + RoundDirection::Ceiling, + &accounts.fees, + ) + .unwrap(); + let withdraw_fee = accounts.fees.owner_withdraw_fee(pool_token_amount).unwrap(); + + accounts + .withdraw_single_token_type_exact_amount_out( + &withdrawer_key, + &pool_key, + &mut pool_account, + &token_a_key, + &mut token_a_account, + destination_a_amount, + maximum_pool_token_amount, + ) + .unwrap(); + + let swap_token_a = + spl_token::state::Account::unpack(&accounts.token_a_account.data).unwrap(); + + assert_eq!(swap_token_a.amount, token_a_amount - destination_a_amount); + let token_a = spl_token::state::Account::unpack(&token_a_account.data).unwrap(); + assert_eq!(token_a.amount, initial_a + destination_a_amount); + + let pool_account = spl_token::state::Account::unpack(&pool_account.data).unwrap(); + assert_eq!( + pool_account.amount, + to_u64(initial_pool - pool_token_amount - withdraw_fee).unwrap() + ); + let fee_account = + spl_token::state::Account::unpack(&accounts.pool_fee_account.data).unwrap(); + assert_eq!(fee_account.amount, to_u64(withdraw_fee).unwrap()); + } } fn check_valid_swap_curve( @@ -5555,6 +6232,7 @@ mod tests { swap_curve.clone(), token_a_amount, token_b_amount, + false, ); let initial_a = token_a_amount / 5; let initial_b = token_b_amount / 5; @@ -5861,6 +6539,7 @@ mod tests { swap_curve, token_a_amount, token_b_amount, + false, ); // initialize swap @@ -5996,8 +6675,14 @@ mod tests { curve_type, calculator: Box::new(ConstantProductCurve {}), }; - let mut accounts = - SwapAccountInfo::new(&user_key, fees, swap_curve, token_a_amount, token_b_amount); + let mut accounts = SwapAccountInfo::new( + &user_key, + fees, + swap_curve, + token_a_amount, + token_b_amount, + false, + ); let initial_a = token_a_amount / 5; let initial_b = token_b_amount / 5; @@ -6626,6 +7311,239 @@ mod tests { } } + #[test] + fn test_freeze_authority_can_freeze_swap() { + let trade_fee_numerator = 1; + let trade_fee_denominator = 10; + let owner_trade_fee_numerator = 1; + let owner_trade_fee_denominator = 30; + let owner_withdraw_fee_numerator = 1; + let owner_withdraw_fee_denominator = 30; + let host_fee_numerator = 10; + let host_fee_denominator = 100; + + let token_a_amount = 1_000_000_000; + let token_b_amount = 0; + let fees = Fees { + trade_fee_numerator, + trade_fee_denominator, + owner_trade_fee_numerator, + owner_trade_fee_denominator, + owner_withdraw_fee_numerator, + owner_withdraw_fee_denominator, + host_fee_numerator, + host_fee_denominator, + }; + + let token_b_offset = 2_000_000; + let swap_curve = SwapCurve { + curve_type: CurveType::Offset, + calculator: Box::new(OffsetCurve { token_b_offset }), + }; + let user_key = Pubkey::new_unique(); + let swapper_key = Pubkey::new_unique(); + + let mut accounts = SwapAccountInfo::new( + &user_key, + fees, + swap_curve, + token_a_amount, + token_b_amount, + true, + ); + + accounts.initialize_swap().unwrap(); + accounts.set_freeze_authority_bit_mask(1 << 0).unwrap(); + + let swap_token_a_key = accounts.token_a_key; + let swap_token_b_key = accounts.token_b_key; + let initial_a = 500_000; + let initial_b = 1_000; + + let ( + token_a_key, + mut token_a_account, + token_b_key, + mut token_b_account, + _pool_key, + _pool_account, + ) = accounts.setup_token_accounts(&user_key, &swapper_key, initial_a, initial_b, 0); + + // swap a to b way, fails, there's no liquidity + let a_to_b_amount = initial_a; + let minimum_token_b_amount = 0; + + assert_eq!( + Err(SwapError::FrozenAction.into()), + accounts.swap( + &swapper_key, + &token_a_key, + &mut token_a_account, + &swap_token_a_key, + &swap_token_b_key, + &token_b_key, + &mut token_b_account, + a_to_b_amount, + minimum_token_b_amount, + ) + ); + } + + #[test] + fn test_freeze_authority_does_not_affect_swap() { + let trade_fee_numerator = 1; + let trade_fee_denominator = 10; + let owner_trade_fee_numerator = 1; + let owner_trade_fee_denominator = 30; + let owner_withdraw_fee_numerator = 1; + let owner_withdraw_fee_denominator = 30; + let host_fee_numerator = 10; + let host_fee_denominator = 100; + + let token_a_amount = 1_000_000_000; + let token_b_amount = 0; + let fees = Fees { + trade_fee_numerator, + trade_fee_denominator, + owner_trade_fee_numerator, + owner_trade_fee_denominator, + owner_withdraw_fee_numerator, + owner_withdraw_fee_denominator, + host_fee_numerator, + host_fee_denominator, + }; + + let token_b_offset = 2_000_000; + let swap_curve = SwapCurve { + curve_type: CurveType::Offset, + calculator: Box::new(OffsetCurve { token_b_offset }), + }; + let user_key = Pubkey::new_unique(); + let swapper_key = Pubkey::new_unique(); + + let mut accounts = SwapAccountInfo::new( + &user_key, + fees, + swap_curve, + token_a_amount, + token_b_amount, + true, + ); + + accounts.initialize_swap().unwrap(); + + let swap_token_a_key = accounts.token_a_key; + let swap_token_b_key = accounts.token_b_key; + let initial_a = 500_000; + let initial_b = 1_000; + + let ( + token_a_key, + mut token_a_account, + token_b_key, + mut token_b_account, + _pool_key, + _pool_account, + ) = accounts.setup_token_accounts(&user_key, &swapper_key, initial_a, initial_b, 0); + + // swap a to b way, fails, there's no liquidity + let a_to_b_amount = initial_a; + let minimum_token_b_amount = 0; + + assert_eq!( + Err(SwapError::ZeroTradingTokens.into()), + accounts.swap( + &swapper_key, + &token_a_key, + &mut token_a_account, + &swap_token_a_key, + &swap_token_b_key, + &token_b_key, + &mut token_b_account, + a_to_b_amount, + minimum_token_b_amount, + ) + ); + + // swap b to a, succeeds at offset price + let b_to_a_amount = initial_b; + let minimum_token_a_amount = 0; + accounts + .swap( + &swapper_key, + &token_b_key, + &mut token_b_account, + &swap_token_b_key, + &swap_token_a_key, + &token_a_key, + &mut token_a_account, + b_to_a_amount, + minimum_token_a_amount, + ) + .unwrap(); + + // try a to b again, succeeds due to new liquidity + accounts + .swap( + &swapper_key, + &token_a_key, + &mut token_a_account, + &swap_token_a_key, + &swap_token_b_key, + &token_b_key, + &mut token_b_account, + a_to_b_amount, + minimum_token_b_amount, + ) + .unwrap(); + + // try a to b again, fails due to no more liquidity + assert_eq!( + Err(SwapError::ZeroTradingTokens.into()), + accounts.swap( + &swapper_key, + &token_a_key, + &mut token_a_account, + &swap_token_a_key, + &swap_token_b_key, + &token_b_key, + &mut token_b_account, + a_to_b_amount, + minimum_token_b_amount, + ) + ); + + // Try to deposit, fails because deposits are not allowed for offset + // curve swaps + { + let initial_a = 100; + let initial_b = 100; + let pool_amount = 100; + let ( + token_a_key, + mut token_a_account, + token_b_key, + mut token_b_account, + pool_key, + mut pool_account, + ) = accounts.setup_token_accounts(&user_key, &swapper_key, initial_a, initial_b, 0); + assert_eq!( + Err(SwapError::UnsupportedCurveOperation.into()), + accounts.deposit_all_token_types( + &swapper_key, + &token_a_key, + &mut token_a_account, + &token_b_key, + &mut token_b_account, + &pool_key, + &mut pool_account, + pool_amount, + initial_a, + initial_b, + ) + ); + } + } #[test] fn test_overdraw_offset_curve() { let trade_fee_numerator = 1; @@ -6658,8 +7576,14 @@ mod tests { let user_key = Pubkey::new_unique(); let swapper_key = Pubkey::new_unique(); - let mut accounts = - SwapAccountInfo::new(&user_key, fees, swap_curve, token_a_amount, token_b_amount); + let mut accounts = SwapAccountInfo::new( + &user_key, + fees, + swap_curve, + token_a_amount, + token_b_amount, + false, + ); accounts.initialize_swap().unwrap(); @@ -6809,8 +7733,14 @@ mod tests { let user_key = Pubkey::new_unique(); let withdrawer_key = Pubkey::new_unique(); - let mut accounts = - SwapAccountInfo::new(&user_key, fees, swap_curve, token_a_amount, token_b_amount); + let mut accounts = SwapAccountInfo::new( + &user_key, + fees, + swap_curve, + token_a_amount, + token_b_amount, + false, + ); accounts.initialize_swap().unwrap(); diff --git a/token-swap/program/src/state.rs b/token-swap/program/src/state.rs index 282f9bb5da0..952565b2996 100644 --- a/token-swap/program/src/state.rs +++ b/token-swap/program/src/state.rs @@ -42,7 +42,7 @@ pub trait SwapState { /// Freeze authority fn freeze_authority(&self) -> COption; - /// bits, from left to right - 1 disables, 0 enables the actions: + /// bits, from right to left - 1 disables, 0 enables the actions: /// 0. process_swap, /// 1. process_deposit_all_token_types, /// 2. process_withdraw_all_token_types, @@ -205,7 +205,7 @@ impl SwapState for SwapV2 { } fn freeze_authority_bit_mask(&self) -> u8 { - return self.freeze_authority_bit_mask; + self.freeze_authority_bit_mask } } @@ -383,7 +383,7 @@ impl SwapState for SwapV1 { } fn freeze_authority_bit_mask(&self) -> u8 { - return 0 as u8; + 0_u8 } } From 6405a853b84ab4b25b738af3646cb8b0b0272f39 Mon Sep 17 00:00:00 2001 From: Jordan Prince Date: Mon, 29 Mar 2021 12:10:22 -0500 Subject: [PATCH 4/8] Some clippy fixes --- token-swap/program/src/processor.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/token-swap/program/src/processor.rs b/token-swap/program/src/processor.rs index 00e80cae63a..4d6d95b6767 100644 --- a/token-swap/program/src/processor.rs +++ b/token-swap/program/src/processor.rs @@ -1654,7 +1654,7 @@ mod tests { .unwrap(), vec![&mut self.swap_account, &mut Account::default()], ), - COption::None => return Err(SwapError::InvalidFreezeAuthority.into()), + COption::None => Err(SwapError::InvalidFreezeAuthority.into()), } } @@ -3652,8 +3652,8 @@ mod tests { }; let mut accounts = SwapAccountInfo::new( &user_key, - fees.clone(), - swap_curve.clone(), + fees, + swap_curve, token_a_amount, token_b_amount, true, @@ -3707,8 +3707,8 @@ mod tests { }; let mut accounts = SwapAccountInfo::new( &user_key, - fees.clone(), - swap_curve.clone(), + fees, + swap_curve, token_a_amount, token_b_amount, true, From c97db5c1f3b8688785fcc92877d7fe6a242e2348 Mon Sep 17 00:00:00 2001 From: Jordan Prince Date: Mon, 29 Mar 2021 12:46:59 -0500 Subject: [PATCH 5/8] Fix a couple more broken tests --- token-swap/program/src/instruction.rs | 2 +- token-swap/program/src/state.rs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/token-swap/program/src/instruction.rs b/token-swap/program/src/instruction.rs index fea5bc16c6e..be832b20010 100644 --- a/token-swap/program/src/instruction.rs +++ b/token-swap/program/src/instruction.rs @@ -730,7 +730,7 @@ mod tests { expect.extend_from_slice(&host_fee_denominator.to_le_bytes()); expect.push(curve_type as u8); expect.extend_from_slice(&.to_le_bytes()); - expect.extend_from_slice(&[0u8; 24]); + expect.extend_from_slice(&[0u8; 26]); assert_eq!(packed, expect); let unpacked = SwapInstruction::unpack(&expect).unwrap(); assert_eq!(unpacked, check); diff --git a/token-swap/program/src/state.rs b/token-swap/program/src/state.rs index 952565b2996..31c3d2a49cd 100644 --- a/token-swap/program/src/state.rs +++ b/token-swap/program/src/state.rs @@ -523,7 +523,7 @@ mod tests { curve_type, calculator, }; - let swap_info = SwapVersion::SwapV1(SwapV1 { + let swap_info = SwapVersion::SwapV2(SwapV2 { is_initialized: true, nonce: TEST_NONCE, token_program_id: TEST_TOKEN_PROGRAM_ID, @@ -535,6 +535,8 @@ mod tests { pool_fee_account: TEST_POOL_FEE_ACCOUNT, fees: TEST_FEES, swap_curve: swap_curve.clone(), + freeze_authority: COption::None, + freeze_authority_bit_mask: 0, }); let mut packed = [0u8; SwapVersion::LATEST_LEN]; From 489a90814b2f71ceaff9fa63b72ac5d54c7b55d4 Mon Sep 17 00:00:00 2001 From: Jordan Prince Date: Mon, 29 Mar 2021 18:17:49 -0500 Subject: [PATCH 6/8] Fix token swap js test --- token-swap/js/client/token-swap.js | 6 ++++++ token-swap/program/src/processor.rs | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/token-swap/js/client/token-swap.js b/token-swap/js/client/token-swap.js index 9e61aa410be..0da62c7f488 100644 --- a/token-swap/js/client/token-swap.js +++ b/token-swap/js/client/token-swap.js @@ -83,6 +83,9 @@ export const TokenSwapLayout: typeof BufferLayout.Structure = BufferLayout.struc Layout.uint64('hostFeeDenominator'), BufferLayout.u8('curveType'), BufferLayout.blob(32, 'curveParameters'), + BufferLayout.u8('freezeAuthorityOption'), + Layout.publicKey('freezeAuthority'), + BufferLayout.u8('freezeAuthorityBitMask') ], ); @@ -319,6 +322,9 @@ export class TokenSwap { BufferLayout.nu64('hostFeeDenominator'), BufferLayout.u8('curveType'), BufferLayout.blob(32, 'curveParameters'), + BufferLayout.u8('freezeAuthorityOption'), + Layout.publicKey('freezeAuthority'), + BufferLayout.u8('freezeAuthorityBitMask') ]); let data = Buffer.alloc(1024); { diff --git a/token-swap/program/src/processor.rs b/token-swap/program/src/processor.rs index 4d6d95b6767..657eeae7c59 100644 --- a/token-swap/program/src/processor.rs +++ b/token-swap/program/src/processor.rs @@ -1058,7 +1058,11 @@ impl Processor { let swap_info = next_account_info(account_info_iter)?; let freeze_authority_info = next_account_info(account_info_iter)?; let token_swap = SwapVersion::unpack(&swap_info.data.borrow())?; - + msg!( + "My freeze authority {:?} as compared to {:?}", + freeze_authority_info.key, + token_swap.as_ref().freeze_authority() + ); Self::check_allowed_to_freeze(token_swap.as_ref(), freeze_authority_info)?; let clone = SwapVersion::SwapV2(SwapV2 { From ad02fd5b64918a52a411250443105bbd67c2ff06 Mon Sep 17 00:00:00 2001 From: Jordan Prince Date: Mon, 29 Mar 2021 20:17:58 -0500 Subject: [PATCH 7/8] Remove unnecessary msg statement and add commas to get pretty to pass --- token-swap/js/client/token-swap.js | 4 ++-- token-swap/program/src/processor.rs | 6 +----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/token-swap/js/client/token-swap.js b/token-swap/js/client/token-swap.js index 0da62c7f488..0dc8fbc1a15 100644 --- a/token-swap/js/client/token-swap.js +++ b/token-swap/js/client/token-swap.js @@ -85,7 +85,7 @@ export const TokenSwapLayout: typeof BufferLayout.Structure = BufferLayout.struc BufferLayout.blob(32, 'curveParameters'), BufferLayout.u8('freezeAuthorityOption'), Layout.publicKey('freezeAuthority'), - BufferLayout.u8('freezeAuthorityBitMask') + BufferLayout.u8('freezeAuthorityBitMask'), ], ); @@ -324,7 +324,7 @@ export class TokenSwap { BufferLayout.blob(32, 'curveParameters'), BufferLayout.u8('freezeAuthorityOption'), Layout.publicKey('freezeAuthority'), - BufferLayout.u8('freezeAuthorityBitMask') + BufferLayout.u8('freezeAuthorityBitMask'), ]); let data = Buffer.alloc(1024); { diff --git a/token-swap/program/src/processor.rs b/token-swap/program/src/processor.rs index 657eeae7c59..4d6d95b6767 100644 --- a/token-swap/program/src/processor.rs +++ b/token-swap/program/src/processor.rs @@ -1058,11 +1058,7 @@ impl Processor { let swap_info = next_account_info(account_info_iter)?; let freeze_authority_info = next_account_info(account_info_iter)?; let token_swap = SwapVersion::unpack(&swap_info.data.borrow())?; - msg!( - "My freeze authority {:?} as compared to {:?}", - freeze_authority_info.key, - token_swap.as_ref().freeze_authority() - ); + Self::check_allowed_to_freeze(token_swap.as_ref(), freeze_authority_info)?; let clone = SwapVersion::SwapV2(SwapV2 { From 7c4f0f2c4d6ec9b3ae6359d16906dcc87ee7c38a Mon Sep 17 00:00:00 2001 From: Jordan Prince Date: Mon, 29 Mar 2021 22:00:47 -0500 Subject: [PATCH 8/8] Test fixes for token swap test --- token-swap/js/cli/token-swap-test.js | 2 +- token-swap/js/client/token-swap.js | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/token-swap/js/cli/token-swap-test.js b/token-swap/js/cli/token-swap-test.js index b18abcf51da..1bf5806e798 100644 --- a/token-swap/js/cli/token-swap-test.js +++ b/token-swap/js/cli/token-swap-test.js @@ -98,6 +98,7 @@ export async function createTokenSwap(): Promise { const connection = await getConnection(); const payer = await newAccountWithLamports(connection, 1000000000); owner = await newAccountWithLamports(connection, 1000000000); + const swapPayer = await newAccountWithLamports(connection, 10000000000); const tokenSwapAccount = new Account(); [authority, nonce] = await PublicKey.findProgramAddress( @@ -151,7 +152,6 @@ export async function createTokenSwap(): Promise { await mintB.mintTo(tokenAccountB, owner, [], currentSwapTokenB); console.log('creating token swap'); - const swapPayer = await newAccountWithLamports(connection, 10000000000); tokenSwap = await TokenSwap.createTokenSwap( connection, swapPayer, diff --git a/token-swap/js/client/token-swap.js b/token-swap/js/client/token-swap.js index 0dc8fbc1a15..c52463b767e 100644 --- a/token-swap/js/client/token-swap.js +++ b/token-swap/js/client/token-swap.js @@ -83,7 +83,7 @@ export const TokenSwapLayout: typeof BufferLayout.Structure = BufferLayout.struc Layout.uint64('hostFeeDenominator'), BufferLayout.u8('curveType'), BufferLayout.blob(32, 'curveParameters'), - BufferLayout.u8('freezeAuthorityOption'), + BufferLayout.u32('freezeAuthorityOption'), Layout.publicKey('freezeAuthority'), BufferLayout.u8('freezeAuthorityBitMask'), ], @@ -309,6 +309,7 @@ export class TokenSwap { {pubkey: tokenAccountPool, isSigner: false, isWritable: true}, {pubkey: tokenProgramId, isSigner: false, isWritable: false}, ]; + const commandDataLayout = BufferLayout.struct([ BufferLayout.u8('instruction'), BufferLayout.u8('nonce'), @@ -322,7 +323,7 @@ export class TokenSwap { BufferLayout.nu64('hostFeeDenominator'), BufferLayout.u8('curveType'), BufferLayout.blob(32, 'curveParameters'), - BufferLayout.u8('freezeAuthorityOption'), + BufferLayout.u32('freezeAuthorityOption'), Layout.publicKey('freezeAuthority'), BufferLayout.u8('freezeAuthorityBitMask'), ]); @@ -341,6 +342,9 @@ export class TokenSwap { hostFeeNumerator, hostFeeDenominator, curveType, + freezeAuthorityOption: 0, + freezeAuthority: undefined, + freezeAuthorityBitMask: 0, }, data, );