diff --git a/artifacts/program_methods/amm.bin b/artifacts/program_methods/amm.bin index 00876724..0fe48b45 100644 Binary files a/artifacts/program_methods/amm.bin and b/artifacts/program_methods/amm.bin differ diff --git a/artifacts/program_methods/authenticated_transfer.bin b/artifacts/program_methods/authenticated_transfer.bin index 96eea39d..ceba5878 100644 Binary files a/artifacts/program_methods/authenticated_transfer.bin and b/artifacts/program_methods/authenticated_transfer.bin differ diff --git a/artifacts/program_methods/pinata.bin b/artifacts/program_methods/pinata.bin index a2571874..8b61d2e2 100644 Binary files a/artifacts/program_methods/pinata.bin and b/artifacts/program_methods/pinata.bin differ diff --git a/artifacts/program_methods/pinata_token.bin b/artifacts/program_methods/pinata_token.bin index 5b4ff387..dc520773 100644 Binary files a/artifacts/program_methods/pinata_token.bin and b/artifacts/program_methods/pinata_token.bin differ diff --git a/artifacts/program_methods/privacy_preserving_circuit.bin b/artifacts/program_methods/privacy_preserving_circuit.bin index 4a377254..accb778f 100644 Binary files a/artifacts/program_methods/privacy_preserving_circuit.bin and b/artifacts/program_methods/privacy_preserving_circuit.bin differ diff --git a/artifacts/program_methods/token.bin b/artifacts/program_methods/token.bin index 75a31726..b4a20327 100644 Binary files a/artifacts/program_methods/token.bin and b/artifacts/program_methods/token.bin differ diff --git a/program_methods/guest/src/bin/amm.rs b/program_methods/guest/src/bin/amm.rs index 3df85a11..00fd39d3 100644 --- a/program_methods/guest/src/bin/amm.rs +++ b/program_methods/guest/src/bin/amm.rs @@ -6,11 +6,10 @@ //! AMM program accepts [`Instruction`] as input, refer to the corresponding documentation //! for more details. +use std::num::NonZero; + use amm_core::Instruction; -use nssa_core::program::{ - AccountPostState, ChainedCall, ProgramInput, read_nssa_inputs, - write_nssa_outputs_with_chained_call, -}; +use nssa_core::program::{ProgramInput, read_nssa_inputs, write_nssa_outputs_with_chained_call}; fn main() { let ( @@ -23,8 +22,7 @@ fn main() { let pre_states_clone = pre_states.clone(); - let (post_states, chained_calls): (Vec, Vec) = match instruction - { + let (post_states, chained_calls) = match instruction { Instruction::NewDefinition { token_a_amount, token_b_amount, @@ -49,8 +47,8 @@ fn main() { user_holding_a, user_holding_b, user_holding_lp, - token_a_amount, - token_b_amount, + NonZero::new(token_a_amount).expect("Token A should have a nonzero amount"), + NonZero::new(token_b_amount).expect("Token B should have a nonzero amount"), amm_program_id, ) } @@ -78,7 +76,8 @@ fn main() { user_holding_a, user_holding_b, user_holding_lp, - min_amount_liquidity, + NonZero::new(min_amount_liquidity) + .expect("Min amount of liquidity should be nonzero"), max_amount_to_add_token_a, max_amount_to_add_token_b, ) @@ -107,7 +106,8 @@ fn main() { user_holding_a, user_holding_b, user_holding_lp, - remove_liquidity_amount, + NonZero::new(remove_liquidity_amount) + .expect("Remove liquidity amount must be nonzero"), min_amount_to_remove_token_a, min_amount_to_remove_token_b, ) diff --git a/programs/amm/core/Cargo.toml b/programs/amm/core/Cargo.toml index 6b4987b2..076ee1f3 100644 --- a/programs/amm/core/Cargo.toml +++ b/programs/amm/core/Cargo.toml @@ -6,4 +6,5 @@ edition = "2024" [dependencies] nssa_core.workspace = true serde.workspace = true -risc0-zkvm.workspace = true \ No newline at end of file +risc0-zkvm.workspace = true +borsh.workspace = true \ No newline at end of file diff --git a/programs/amm/core/src/lib.rs b/programs/amm/core/src/lib.rs index af4c4ee7..f9d20dd3 100644 --- a/programs/amm/core/src/lib.rs +++ b/programs/amm/core/src/lib.rs @@ -1,5 +1,6 @@ //! This crate contains core data structures and utilities for the AMM Program. +use borsh::{BorshDeserialize, BorshSerialize}; use nssa_core::{ account::{AccountId, Data}, program::{PdaSeed, ProgramId}, @@ -74,9 +75,7 @@ pub enum Instruction { }, } -const POOL_DEFINITION_DATA_SIZE: usize = 225; - -#[derive(Clone, Default)] +#[derive(Clone, Default, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub struct PoolDefinition { pub definition_token_a_id: AccountId, pub definition_token_b_id: AccountId, @@ -94,73 +93,23 @@ pub struct PoolDefinition { pub active: bool, } -impl PoolDefinition { - pub fn into_data(self) -> Data { - let mut bytes = [0; POOL_DEFINITION_DATA_SIZE]; - bytes[0..32].copy_from_slice(&self.definition_token_a_id.to_bytes()); - bytes[32..64].copy_from_slice(&self.definition_token_b_id.to_bytes()); - bytes[64..96].copy_from_slice(&self.vault_a_id.to_bytes()); - bytes[96..128].copy_from_slice(&self.vault_b_id.to_bytes()); - bytes[128..160].copy_from_slice(&self.liquidity_pool_id.to_bytes()); - bytes[160..176].copy_from_slice(&self.liquidity_pool_supply.to_le_bytes()); - bytes[176..192].copy_from_slice(&self.reserve_a.to_le_bytes()); - bytes[192..208].copy_from_slice(&self.reserve_b.to_le_bytes()); - bytes[208..224].copy_from_slice(&self.fees.to_le_bytes()); - bytes[224] = self.active as u8; +impl TryFrom<&Data> for PoolDefinition { + type Error = std::io::Error; - bytes - .to_vec() - .try_into() - .expect("225 bytes should fit into Data") + fn try_from(data: &Data) -> Result { + PoolDefinition::try_from_slice(data.as_ref()) } +} - pub fn parse(data: &[u8]) -> Option { - if data.len() != POOL_DEFINITION_DATA_SIZE { - None - } else { - let definition_token_a_id = AccountId::new(data[0..32].try_into().expect("Parse data: The AMM program must be provided a valid AccountId for Token A definition")); - let definition_token_b_id = AccountId::new(data[32..64].try_into().expect("Parse data: The AMM program must be provided a valid AccountId for Vault B definition")); - let vault_a_id = AccountId::new(data[64..96].try_into().expect( - "Parse data: The AMM program must be provided a valid AccountId for Vault A", - )); - let vault_b_id = AccountId::new(data[96..128].try_into().expect( - "Parse data: The AMM program must be provided a valid AccountId for Vault B", - )); - let liquidity_pool_id = AccountId::new(data[128..160].try_into().expect("Parse data: The AMM program must be provided a valid AccountId for Token liquidity pool definition")); - let liquidity_pool_supply = u128::from_le_bytes(data[160..176].try_into().expect( - "Parse data: The AMM program must be provided a valid u128 for liquidity cap", - )); - let reserve_a = u128::from_le_bytes(data[176..192].try_into().expect( - "Parse data: The AMM program must be provided a valid u128 for reserve A balance", - )); - let reserve_b = u128::from_le_bytes(data[192..208].try_into().expect( - "Parse data: The AMM program must be provided a valid u128 for reserve B balance", - )); - let fees = u128::from_le_bytes( - data[208..224] - .try_into() - .expect("Parse data: The AMM program must be provided a valid u128 for fees"), - ); +impl From<&PoolDefinition> for Data { + fn from(definition: &PoolDefinition) -> Self { + // Using size_of_val as size hint for Vec allocation + let mut data = Vec::with_capacity(std::mem::size_of_val(definition)); - let active = match data[224] { - 0 => false, - 1 => true, - _ => panic!("Parse data: The AMM program must be provided a valid bool for active"), - }; + BorshSerialize::serialize(definition, &mut data) + .expect("Serialization to Vec should not fail"); - Some(Self { - definition_token_a_id, - definition_token_b_id, - vault_a_id, - vault_b_id, - liquidity_pool_id, - liquidity_pool_supply, - reserve_a, - reserve_b, - fees, - active, - }) - } + Data::try_from(data).expect("Token definition encoded data should fit into Data") } } diff --git a/programs/amm/src/add.rs b/programs/amm/src/add.rs index cf3395cf..cbecaa34 100644 --- a/programs/amm/src/add.rs +++ b/programs/amm/src/add.rs @@ -1,6 +1,8 @@ +use std::num::NonZeroU128; + use amm_core::{PoolDefinition, compute_liquidity_token_pda_seed}; use nssa_core::{ - account::AccountWithMetadata, + account::{AccountWithMetadata, Data}, program::{AccountPostState, ChainedCall}, }; @@ -13,32 +15,33 @@ pub fn add_liquidity( user_holding_a: AccountWithMetadata, user_holding_b: AccountWithMetadata, user_holding_lp: AccountWithMetadata, - min_amount_liquidity: u128, + min_amount_liquidity: NonZeroU128, max_amount_to_add_token_a: u128, max_amount_to_add_token_b: u128, ) -> (Vec, Vec) { // 1. Fetch Pool state - let pool_def_data = PoolDefinition::parse(&pool.account.data) + let pool_def_data = PoolDefinition::try_from(&pool.account.data) .expect("Add liquidity: AMM Program expects valid Pool Definition Account"); - if vault_a.account_id != pool_def_data.vault_a_id { - panic!("Vault A was not provided"); - } - if pool_def_data.liquidity_pool_id != pool_definition_lp.account_id { - panic!("LP definition mismatch"); - } + assert_eq!( + vault_a.account_id, pool_def_data.vault_a_id, + "Vault A was not provided" + ); - if vault_b.account_id != pool_def_data.vault_b_id { - panic!("Vault B was not provided"); - } + assert_eq!( + pool_def_data.liquidity_pool_id, pool_definition_lp.account_id, + "LP definition mismatch" + ); - if max_amount_to_add_token_a == 0 || max_amount_to_add_token_b == 0 { - panic!("Both max-balances must be nonzero"); - } + assert_eq!( + vault_b.account_id, pool_def_data.vault_b_id, + "Vault B was not provided" + ); - if min_amount_liquidity == 0 { - panic!("Min-lp must be nonzero"); - } + assert!( + max_amount_to_add_token_a != 0 && max_amount_to_add_token_b != 0, + "Both max-balances must be nonzero" + ); // 2. Determine deposit amount let vault_b_token_holding = token_core::TokenHolding::try_from(&vault_b.account.data) @@ -65,13 +68,16 @@ pub fn add_liquidity( ); }; - if pool_def_data.reserve_a == 0 || pool_def_data.reserve_b == 0 { - panic!("Reserves must be nonzero"); - } - - if vault_a_balance < pool_def_data.reserve_a || vault_b_balance < pool_def_data.reserve_b { - panic!("Vaults' balances must be at least the reserve amounts"); - } + assert!(pool_def_data.reserve_a != 0, "Reserves must be nonzero"); + assert!(pool_def_data.reserve_b != 0, "Reserves must be nonzero"); + assert!( + vault_a_balance >= pool_def_data.reserve_a, + "Vaults' balances must be at least the reserve amounts" + ); + assert!( + vault_b_balance >= pool_def_data.reserve_b, + "Vaults' balances must be at least the reserve amounts" + ); // Calculate actual_amounts let ideal_a: u128 = @@ -91,13 +97,17 @@ pub fn add_liquidity( }; // 3. Validate amounts - if max_amount_to_add_token_a < actual_amount_a || max_amount_to_add_token_b < actual_amount_b { - panic!("Actual trade amounts cannot exceed max_amounts"); - } + assert!( + max_amount_to_add_token_a >= actual_amount_a, + "Actual trade amounts cannot exceed max_amounts" + ); + assert!( + max_amount_to_add_token_b >= actual_amount_b, + "Actual trade amounts cannot exceed max_amounts" + ); - if actual_amount_a == 0 || actual_amount_b == 0 { - panic!("A trade amount is 0"); - } + assert!(actual_amount_a != 0, "A trade amount is 0"); + assert!(actual_amount_b != 0, "A trade amount is 0"); // 4. Calculate LP to mint let delta_lp = std::cmp::min( @@ -105,13 +115,12 @@ pub fn add_liquidity( pool_def_data.liquidity_pool_supply * actual_amount_b / pool_def_data.reserve_b, ); - if delta_lp == 0 { - panic!("Payable LP must be nonzero"); - } + assert!(delta_lp != 0, "Payable LP must be nonzero"); - if delta_lp < min_amount_liquidity { - panic!("Payable LP is less than provided minimum LP amount"); - } + assert!( + delta_lp >= min_amount_liquidity.into(), + "Payable LP is less than provided minimum LP amount" + ); // 5. Update pool account let mut pool_post = pool.account.clone(); @@ -122,7 +131,7 @@ pub fn add_liquidity( ..pool_def_data }; - pool_post.data = pool_post_definition.into_data(); + pool_post.data = Data::from(&pool_post_definition); let token_program_id = user_holding_a.account.program_owner; // Chain call for Token A (UserHoldingA -> Vault_A) diff --git a/programs/amm/src/lib.rs b/programs/amm/src/lib.rs index 154ad9ec..e50c738e 100644 --- a/programs/amm/src/lib.rs +++ b/programs/amm/src/lib.rs @@ -1,6 +1,6 @@ //! The AMM Program implementation. -pub use token_core as core; +pub use amm_core as core; pub mod add; pub mod new_definition; diff --git a/programs/amm/src/new_definition.rs b/programs/amm/src/new_definition.rs index ec9efc94..ab0b241a 100644 --- a/programs/amm/src/new_definition.rs +++ b/programs/amm/src/new_definition.rs @@ -1,9 +1,11 @@ +use std::num::NonZeroU128; + use amm_core::{ PoolDefinition, compute_liquidity_token_pda, compute_liquidity_token_pda_seed, compute_pool_pda, compute_vault_pda, }; use nssa_core::{ - account::{Account, AccountWithMetadata}, + account::{Account, AccountWithMetadata, Data}, program::{AccountPostState, ChainedCall, ProgramId}, }; @@ -20,11 +22,6 @@ pub fn new_definition( token_b_amount: NonZeroU128, amm_program_id: ProgramId, ) -> (Vec, Vec) { - // Prevents pool constant coefficient (k) from being 0. - if token_a_amount == 0 || token_b_amount == 0 { - panic!("Balances must be nonzero") - } - // Verify token_a and token_b are different let definition_token_a_id = token_core::TokenHolding::try_from(&user_holding_a.account.data) .expect("New definition: AMM Program expects valid Token Holding account for Token A") @@ -36,43 +33,48 @@ pub fn new_definition( // both instances of the same token program let token_program = user_holding_a.account.program_owner; - if user_holding_b.account.program_owner != token_program { - panic!("User Token holdings must use the same Token Program"); - } - - if definition_token_a_id == definition_token_b_id { - panic!("Cannot set up a swap for a token with itself") - } - - if pool.account_id - != compute_pool_pda(amm_program_id, definition_token_a_id, definition_token_b_id) - { - panic!("Pool Definition Account ID does not match PDA"); - } - - if vault_a.account_id - != compute_vault_pda(amm_program_id, pool.account_id, definition_token_a_id) - || vault_b.account_id - != compute_vault_pda(amm_program_id, pool.account_id, definition_token_b_id) - { - panic!("Vault ID does not match PDA"); - } - - if pool_definition_lp.account_id != compute_liquidity_token_pda(amm_program_id, pool.account_id) - { - panic!("Liquidity pool Token Definition Account ID does not match PDA"); - } + assert_eq!( + user_holding_b.account.program_owner, token_program, + "User Token holdings must use the same Token Program" + ); + assert!( + definition_token_a_id != definition_token_b_id, + "Cannot set up a swap for a token with itself" + ); + assert_eq!( + pool.account_id, + compute_pool_pda(amm_program_id, definition_token_a_id, definition_token_b_id), + "Pool Definition Account ID does not match PDA" + ); + assert_eq!( + vault_a.account_id, + compute_vault_pda(amm_program_id, pool.account_id, definition_token_a_id), + "Vault ID does not match PDA" + ); + assert_eq!( + vault_b.account_id, + compute_vault_pda(amm_program_id, pool.account_id, definition_token_b_id), + "Vault ID does not match PDA" + ); + assert_eq!( + pool_definition_lp.account_id, + compute_liquidity_token_pda(amm_program_id, pool.account_id), + "Liquidity pool Token Definition Account ID does not match PDA" + ); + // TODO: return here // Verify that Pool Account is not active let pool_account_data = if pool.account == Account::default() { PoolDefinition::default() } else { - PoolDefinition::parse(&pool.account.data).expect("AMM program expects a valid Pool account") + PoolDefinition::try_from(&pool.account.data) + .expect("AMM program expects a valid Pool account") }; - if pool_account_data.active { - panic!("Cannot initialize an active Pool Definition") - } + assert!( + !pool_account_data.active, + "Cannot initialize an active Pool Definition" + ); // LP Token minting calculation // We assume LP is based on the initial deposit amount for Token_A. @@ -85,14 +87,14 @@ pub fn new_definition( vault_a_id: vault_a.account_id, vault_b_id: vault_b.account_id, liquidity_pool_id: pool_definition_lp.account_id, - liquidity_pool_supply: token_a_amount, - reserve_a: token_a_amount, - reserve_b: token_b_amount, + liquidity_pool_supply: token_a_amount.into(), + reserve_a: token_a_amount.into(), + reserve_b: token_b_amount.into(), fees: 0u128, // TODO: we assume all fees are 0 for now. active: true, }; - pool_post.data = pool_post_definition.into_data(); + pool_post.data = Data::from(&pool_post_definition); let pool_post: AccountPostState = if pool.account == Account::default() { AccountPostState::new_claimed(pool_post.clone()) } else { @@ -106,7 +108,7 @@ pub fn new_definition( token_program_id, vec![user_holding_a.clone(), vault_a.clone()], &token_core::Instruction::Transfer { - amount_to_transfer: token_a_amount, + amount_to_transfer: token_a_amount.into(), }, ); // Chain call for Token B (user_holding_b -> Vault_B) @@ -114,7 +116,7 @@ pub fn new_definition( token_program_id, vec![user_holding_b.clone(), vault_b.clone()], &token_core::Instruction::Transfer { - amount_to_transfer: token_b_amount, + amount_to_transfer: token_b_amount.into(), }, ); @@ -122,11 +124,11 @@ pub fn new_definition( let instruction = if pool.account == Account::default() { token_core::Instruction::NewFungibleDefinition { name: String::from("LP Token"), - total_supply: token_a_amount, + total_supply: token_a_amount.into(), } } else { token_core::Instruction::Mint { - amount_to_mint: token_a_amount, + amount_to_mint: token_a_amount.into(), } }; diff --git a/programs/amm/src/remove.rs b/programs/amm/src/remove.rs index 9d4606ae..370b3609 100644 --- a/programs/amm/src/remove.rs +++ b/programs/amm/src/remove.rs @@ -1,6 +1,8 @@ +use std::num::NonZeroU128; + use amm_core::{PoolDefinition, compute_liquidity_token_pda_seed, compute_vault_pda_seed}; use nssa_core::{ - account::AccountWithMetadata, + account::{AccountWithMetadata, Data}, program::{AccountPostState, ChainedCall}, }; @@ -13,29 +15,29 @@ pub fn remove_liquidity( user_holding_a: AccountWithMetadata, user_holding_b: AccountWithMetadata, user_holding_lp: AccountWithMetadata, - remove_liquidity_amount: u128, + remove_liquidity_amount: NonZeroU128, min_amount_to_remove_token_a: u128, min_amount_to_remove_token_b: u128, ) -> (Vec, Vec) { + let remove_liquidity_amount: u128 = remove_liquidity_amount.into(); + // 1. Fetch Pool state - let pool_def_data = PoolDefinition::parse(&pool.account.data) + let pool_def_data = PoolDefinition::try_from(&pool.account.data) .expect("Remove liquidity: AMM Program expects a valid Pool Definition Account"); - if !pool_def_data.active { - panic!("Pool is inactive"); - } - - if pool_def_data.liquidity_pool_id != pool_definition_lp.account_id { - panic!("LP definition mismatch"); - } - - if vault_a.account_id != pool_def_data.vault_a_id { - panic!("Vault A was not provided"); - } - - if vault_b.account_id != pool_def_data.vault_b_id { - panic!("Vault B was not provided"); - } + assert!(pool_def_data.active, "Pool is inactive"); + assert_eq!( + pool_def_data.liquidity_pool_id, pool_definition_lp.account_id, + "LP definition mismatch" + ); + assert_eq!( + vault_a.account_id, pool_def_data.vault_a_id, + "Vault A was not provided" + ); + assert_eq!( + vault_b.account_id, pool_def_data.vault_b_id, + "Vault B was not provided" + ); // Vault addresses do not need to be checked with PDA // calculation for setting authorization since stored @@ -45,13 +47,14 @@ pub fn remove_liquidity( running_vault_a.is_authorized = true; running_vault_b.is_authorized = true; - if min_amount_to_remove_token_a == 0 || min_amount_to_remove_token_b == 0 { - panic!("Minimum withdraw amount must be nonzero"); - } - - if remove_liquidity_amount == 0 { - panic!("Liquidity amount must be nonzero"); - } + assert!( + min_amount_to_remove_token_a != 0, + "Minimum withdraw amount must be nonzero" + ); + assert!( + min_amount_to_remove_token_b != 0, + "Minimum withdraw amount must be nonzero" + ); // 2. Compute withdrawal amounts let user_holding_lp_data = token_core::TokenHolding::try_from(&user_holding_lp.account.data) @@ -66,11 +69,15 @@ pub fn remove_liquidity( ); }; - if user_lp_balance > pool_def_data.liquidity_pool_supply - || user_holding_lp_data.definition_id() != pool_def_data.liquidity_pool_id - { - panic!("Invalid liquidity account provided"); - } + assert!( + user_lp_balance <= pool_def_data.liquidity_pool_supply, + "Invalid liquidity account provided" + ); + assert_eq!( + user_holding_lp_data.definition_id(), + pool_def_data.liquidity_pool_id, + "Invalid liquidity account provided" + ); let withdraw_amount_a = (pool_def_data.reserve_a * remove_liquidity_amount) / pool_def_data.liquidity_pool_supply; @@ -78,12 +85,14 @@ pub fn remove_liquidity( (pool_def_data.reserve_b * remove_liquidity_amount) / pool_def_data.liquidity_pool_supply; // 3. Validate and slippage check - if withdraw_amount_a < min_amount_to_remove_token_a { - panic!("Insufficient minimal withdraw amount (Token A) provided for liquidity amount"); - } - if withdraw_amount_b < min_amount_to_remove_token_b { - panic!("Insufficient minimal withdraw amount (Token B) provided for liquidity amount"); - } + assert!( + withdraw_amount_a >= min_amount_to_remove_token_a, + "Insufficient minimal withdraw amount (Token A) provided for liquidity amount" + ); + assert!( + withdraw_amount_b >= min_amount_to_remove_token_b, + "Insufficient minimal withdraw amount (Token B) provided for liquidity amount" + ); // 4. Calculate LP to reduce cap by let delta_lp: u128 = (pool_def_data.liquidity_pool_supply * remove_liquidity_amount) @@ -101,7 +110,7 @@ pub fn remove_liquidity( ..pool_def_data.clone() }; - pool_post.data = pool_post_definition.into_data(); + pool_post.data = Data::from(&pool_post_definition); let token_program_id = user_holding_a.account.program_owner; diff --git a/programs/amm/src/swap.rs b/programs/amm/src/swap.rs index 524897c4..aa02ac24 100644 --- a/programs/amm/src/swap.rs +++ b/programs/amm/src/swap.rs @@ -1,6 +1,6 @@ pub use amm_core::{PoolDefinition, compute_liquidity_token_pda_seed, compute_vault_pda_seed}; use nssa_core::{ - account::{AccountId, AccountWithMetadata}, + account::{AccountId, AccountWithMetadata, Data}, program::{AccountPostState, ChainedCall}, }; @@ -16,20 +16,18 @@ pub fn swap( token_in_id: AccountId, ) -> (Vec, Vec) { // Verify vaults are in fact vaults - let pool_def_data = PoolDefinition::parse(&pool.account.data) + let pool_def_data = PoolDefinition::try_from(&pool.account.data) .expect("Swap: AMM Program expects a valid Pool Definition Account"); - if !pool_def_data.active { - panic!("Pool is inactive"); - } - - if vault_a.account_id != pool_def_data.vault_a_id { - panic!("Vault A was not provided"); - } - - if vault_b.account_id != pool_def_data.vault_b_id { - panic!("Vault B was not provided"); - } + assert!(pool_def_data.active, "Pool is inactive"); + assert_eq!( + vault_a.account_id, pool_def_data.vault_a_id, + "Vault A was not provided" + ); + assert_eq!( + vault_b.account_id, pool_def_data.vault_b_id, + "Vault B was not provided" + ); // fetch pool reserves // validates reserves is at least the vaults' balances @@ -42,9 +40,11 @@ pub fn swap( else { panic!("Swap: AMM Program expects a valid Fungible Token Holding Account for Vault A"); }; - if vault_a_balance < pool_def_data.reserve_a { - panic!("Reserve for Token A exceeds vault balance"); - } + + assert!( + vault_a_balance >= pool_def_data.reserve_a, + "Reserve for Token A exceeds vault balance" + ); let vault_b_token_holding = token_core::TokenHolding::try_from(&vault_b.account.data) .expect("Swap: AMM Program expects a valid Token Holding Account for Vault B"); @@ -56,9 +56,10 @@ pub fn swap( panic!("Swap: AMM Program expects a valid Fungible Token Holding Account for Vault B"); }; - if vault_b_balance < pool_def_data.reserve_b { - panic!("Reserve for Token B exceeds vault balance"); - } + assert!( + vault_b_balance >= pool_def_data.reserve_b, + "Reserve for Token B exceeds vault balance" + ); let (chained_calls, [deposit_a, withdraw_a], [deposit_b, withdraw_b]) = if token_in_id == pool_def_data.definition_token_a_id { @@ -69,7 +70,8 @@ pub fn swap( user_holding_b.clone(), swap_amount_in, min_amount_out, - &[pool_def_data.reserve_a, pool_def_data.reserve_b], + pool_def_data.reserve_a, + pool_def_data.reserve_b, pool.account_id, ); @@ -82,7 +84,8 @@ pub fn swap( user_holding_a.clone(), swap_amount_in, min_amount_out, - &[pool_def_data.reserve_b, pool_def_data.reserve_a], + pool_def_data.reserve_b, + pool_def_data.reserve_a, pool.account_id, ); @@ -99,7 +102,7 @@ pub fn swap( ..pool_def_data }; - pool_post.data = pool_post_definition.into_data(); + pool_post.data = Data::from(&pool_post_definition); let post_states = vec![ AccountPostState::new(pool_post.clone()), @@ -120,12 +123,10 @@ fn swap_logic( user_withdraw: AccountWithMetadata, swap_amount_in: u128, min_amount_out: u128, - reserve_amounts: &[u128], + reserve_deposit_vault_amount: u128, + reserve_withdraw_vault_amount: u128, pool_id: AccountId, ) -> (Vec, u128, u128) { - let reserve_deposit_vault_amount = reserve_amounts[0]; - let reserve_withdraw_vault_amount = reserve_amounts[1]; - // Compute withdraw amount // Maintains pool constant product // k = pool_def_data.reserve_a * pool_def_data.reserve_b; @@ -133,13 +134,11 @@ fn swap_logic( / (reserve_deposit_vault_amount + swap_amount_in); // Slippage check - if min_amount_out > withdraw_amount { - panic!("Withdraw amount is less than minimal amount out"); - } - - if withdraw_amount == 0 { - panic!("Withdraw amount should be nonzero"); - } + assert!( + min_amount_out <= withdraw_amount, + "Withdraw amount is less than minimal amount out" + ); + assert!(withdraw_amount != 0, "Withdraw amount should be nonzero"); let token_program_id = user_deposit.account.program_owner; diff --git a/programs/amm/src/tests.rs b/programs/amm/src/tests.rs index f3e4e01c..021e32b2 100644 --- a/programs/amm/src/tests.rs +++ b/programs/amm/src/tests.rs @@ -1,5 +1,7 @@ #![cfg(test)] +use std::num::NonZero; + use amm_core::{ PoolDefinition, compute_liquidity_token_pda, compute_liquidity_token_pda_seed, compute_pool_pda, compute_vault_pda, compute_vault_pda_seed, @@ -630,7 +632,7 @@ impl AccountForTests { account: Account { program_owner: ProgramId::default(), balance: 0u128, - data: PoolDefinition::into_data(PoolDefinition { + data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), vault_a_id: IdForTests::vault_a_id(), @@ -654,7 +656,7 @@ impl AccountForTests { account: Account { program_owner: ProgramId::default(), balance: 0u128, - data: PoolDefinition::into_data(PoolDefinition { + data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), vault_a_id: IdForTests::vault_a_id(), @@ -678,7 +680,7 @@ impl AccountForTests { account: Account { program_owner: ProgramId::default(), balance: 0u128, - data: PoolDefinition::into_data(PoolDefinition { + data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), vault_a_id: IdForTests::vault_a_id(), @@ -702,7 +704,7 @@ impl AccountForTests { account: Account { program_owner: ProgramId::default(), balance: 0u128, - data: PoolDefinition::into_data(PoolDefinition { + data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), vault_a_id: IdForTests::vault_a_id(), @@ -726,7 +728,7 @@ impl AccountForTests { account: Account { program_owner: ProgramId::default(), balance: 0u128, - data: PoolDefinition::into_data(PoolDefinition { + data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), vault_a_id: IdForTests::vault_a_id(), @@ -750,7 +752,7 @@ impl AccountForTests { account: Account { program_owner: ProgramId::default(), balance: 0u128, - data: PoolDefinition::into_data(PoolDefinition { + data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), vault_a_id: IdForTests::vault_a_id(), @@ -774,7 +776,7 @@ impl AccountForTests { account: Account { program_owner: ProgramId::default(), balance: 0u128, - data: PoolDefinition::into_data(PoolDefinition { + data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), vault_a_id: IdForTests::vault_a_id(), @@ -798,7 +800,7 @@ impl AccountForTests { account: Account { program_owner: ProgramId::default(), balance: 0u128, - data: PoolDefinition::into_data(PoolDefinition { + data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), vault_a_id: IdForTests::vault_a_id(), @@ -822,7 +824,7 @@ impl AccountForTests { account: Account { program_owner: ProgramId::default(), balance: 0u128, - data: PoolDefinition::into_data(PoolDefinition { + data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), vault_a_id: IdForTests::vault_a_id(), @@ -846,7 +848,7 @@ impl AccountForTests { account: Account { program_owner: ProgramId::default(), balance: 0u128, - data: PoolDefinition::into_data(PoolDefinition { + data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), vault_a_id: IdForTests::vault_a_id(), @@ -870,7 +872,7 @@ impl AccountForTests { account: Account { program_owner: ProgramId::default(), balance: 0u128, - data: PoolDefinition::into_data(PoolDefinition { + data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), vault_a_id: IdForTests::vault_a_id(), @@ -894,7 +896,7 @@ impl AccountForTests { account: Account { program_owner: ProgramId::default(), balance: 0u128, - data: PoolDefinition::into_data(PoolDefinition { + data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), vault_a_id: IdForTests::vault_a_id(), @@ -950,7 +952,7 @@ impl AccountForTests { account: Account { program_owner: ProgramId::default(), balance: 0u128, - data: PoolDefinition::into_data(PoolDefinition { + data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), vault_a_id: IdForTests::vault_a_id(), @@ -996,7 +998,7 @@ fn test_call_add_liquidity_vault_a_omitted() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), - BalanceForTests::add_min_amount_lp(), + NonZero::new(BalanceForTests::add_min_amount_lp()).unwrap(), BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_b(), ); @@ -1013,7 +1015,7 @@ fn test_call_add_liquidity_vault_b_omitted() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), - BalanceForTests::add_min_amount_lp(), + NonZero::new(BalanceForTests::add_min_amount_lp()).unwrap(), BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_b(), ); @@ -1030,7 +1032,7 @@ fn test_call_add_liquidity_lp_definition_mismatch() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), - BalanceForTests::add_min_amount_lp(), + NonZero::new(BalanceForTests::add_min_amount_lp()).unwrap(), BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_b(), ); @@ -1047,7 +1049,7 @@ fn test_call_add_liquidity_zero_balance_1() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), - BalanceForTests::add_min_amount_lp(), + NonZero::new(BalanceForTests::add_min_amount_lp()).unwrap(), 0, BalanceForTests::add_max_amount_b(), ); @@ -1064,29 +1066,12 @@ fn test_call_add_liquidity_zero_balance_2() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), - BalanceForTests::add_min_amount_lp(), + NonZero::new(BalanceForTests::add_min_amount_lp()).unwrap(), 0, BalanceForTests::add_max_amount_a(), ); } -#[should_panic(expected = "Min-lp must be nonzero")] -#[test] -fn test_call_add_liquidity_zero_min_lp() { - let _post_states = add_liquidity( - AccountForTests::pool_definition_init(), - AccountForTests::vault_a_init(), - AccountForTests::vault_b_init(), - AccountForTests::pool_lp_init(), - AccountForTests::user_holding_a(), - AccountForTests::user_holding_b(), - AccountForTests::user_holding_lp_init(), - 0, - BalanceForTests::add_max_amount_a(), - BalanceForTests::add_max_amount_b(), - ); -} - #[should_panic(expected = "Vaults' balances must be at least the reserve amounts")] #[test] fn test_call_add_liquidity_vault_insufficient_balance_1() { @@ -1098,7 +1083,7 @@ fn test_call_add_liquidity_vault_insufficient_balance_1() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), - BalanceForTests::add_max_amount_a(), + NonZero::new(BalanceForTests::add_max_amount_a()).unwrap(), BalanceForTests::add_max_amount_b(), BalanceForTests::add_min_amount_lp(), ); @@ -1115,7 +1100,7 @@ fn test_call_add_liquidity_vault_insufficient_balance_2() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), - BalanceForTests::add_max_amount_a(), + NonZero::new(BalanceForTests::add_max_amount_a()).unwrap(), BalanceForTests::add_max_amount_b(), BalanceForTests::add_min_amount_lp(), ); @@ -1132,7 +1117,7 @@ fn test_call_add_liquidity_actual_amount_zero_1() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), - BalanceForTests::add_min_amount_lp(), + NonZero::new(BalanceForTests::add_min_amount_lp()).unwrap(), BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_b(), ); @@ -1149,7 +1134,7 @@ fn test_call_add_liquidity_actual_amount_zero_2() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), - BalanceForTests::add_min_amount_lp(), + NonZero::new(BalanceForTests::add_min_amount_lp()).unwrap(), BalanceForTests::add_max_amount_a_low(), BalanceForTests::add_max_amount_b_low(), ); @@ -1166,7 +1151,7 @@ fn test_call_add_liquidity_reserves_zero_1() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), - BalanceForTests::add_min_amount_lp(), + NonZero::new(BalanceForTests::add_min_amount_lp()).unwrap(), BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_b(), ); @@ -1183,7 +1168,7 @@ fn test_call_add_liquidity_reserves_zero_2() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), - BalanceForTests::add_min_amount_lp(), + NonZero::new(BalanceForTests::add_min_amount_lp()).unwrap(), BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_b(), ); @@ -1200,7 +1185,7 @@ fn test_call_add_liquidity_payable_lp_zero() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), - BalanceForTests::add_min_amount_lp(), + NonZero::new(BalanceForTests::add_min_amount_lp()).unwrap(), BalanceForTests::add_max_amount_a_low(), BalanceForTests::add_max_amount_b_low(), ); @@ -1216,7 +1201,7 @@ fn test_call_add_liquidity_chained_call_successsful() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), - BalanceForTests::add_min_amount_lp(), + NonZero::new(BalanceForTests::add_min_amount_lp()).unwrap(), BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_b(), ); @@ -1245,7 +1230,7 @@ fn test_call_remove_liquidity_vault_a_omitted() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), - BalanceForTests::remove_amount_lp(), + NonZero::new(BalanceForTests::remove_amount_lp()).unwrap(), BalanceForTests::remove_min_amount_a(), BalanceForTests::remove_min_amount_b(), ); @@ -1262,7 +1247,7 @@ fn test_call_remove_liquidity_vault_b_omitted() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), - BalanceForTests::remove_amount_lp(), + NonZero::new(BalanceForTests::remove_amount_lp()).unwrap(), BalanceForTests::remove_min_amount_a(), BalanceForTests::remove_min_amount_b(), ); @@ -1279,7 +1264,7 @@ fn test_call_remove_liquidity_lp_def_mismatch() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), - BalanceForTests::remove_amount_lp(), + NonZero::new(BalanceForTests::remove_amount_lp()).unwrap(), BalanceForTests::remove_min_amount_a(), BalanceForTests::remove_min_amount_b(), ); @@ -1297,7 +1282,7 @@ fn test_call_remove_liquidity_insufficient_liquidity_amount() { AccountForTests::user_holding_b(), AccountForTests::user_holding_a(), /* different token account than lp to create desired * error */ - BalanceForTests::remove_amount_lp(), + NonZero::new(BalanceForTests::remove_amount_lp()).unwrap(), BalanceForTests::remove_min_amount_a(), BalanceForTests::remove_min_amount_b(), ); @@ -1316,7 +1301,7 @@ fn test_call_remove_liquidity_insufficient_balance_1() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), - BalanceForTests::remove_amount_lp_1(), + NonZero::new(BalanceForTests::remove_amount_lp_1()).unwrap(), BalanceForTests::remove_min_amount_a(), BalanceForTests::remove_min_amount_b(), ); @@ -1335,7 +1320,7 @@ fn test_call_remove_liquidity_insufficient_balance_2() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), - BalanceForTests::remove_amount_lp(), + NonZero::new(BalanceForTests::remove_amount_lp()).unwrap(), BalanceForTests::remove_min_amount_a(), BalanceForTests::remove_min_amount_b(), ); @@ -1352,7 +1337,7 @@ fn test_call_remove_liquidity_min_bal_zero_1() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), - BalanceForTests::remove_amount_lp(), + NonZero::new(BalanceForTests::remove_amount_lp()).unwrap(), 0, BalanceForTests::remove_min_amount_b(), ); @@ -1369,29 +1354,12 @@ fn test_call_remove_liquidity_min_bal_zero_2() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), - BalanceForTests::remove_amount_lp(), + NonZero::new(BalanceForTests::remove_amount_lp()).unwrap(), BalanceForTests::remove_min_amount_a(), 0, ); } -#[should_panic(expected = "Liquidity amount must be nonzero")] -#[test] -fn test_call_remove_liquidity_lp_bal_zero() { - let _post_states = remove_liquidity( - AccountForTests::pool_definition_init(), - AccountForTests::vault_a_init(), - AccountForTests::vault_b_init(), - AccountForTests::pool_lp_init(), - AccountForTests::user_holding_a(), - AccountForTests::user_holding_b(), - AccountForTests::user_holding_lp_init(), - 0, - BalanceForTests::remove_min_amount_a(), - BalanceForTests::remove_min_amount_b(), - ); -} - #[test] fn test_call_remove_liquidity_chained_call_successful() { let (post_states, chained_calls) = remove_liquidity( @@ -1402,7 +1370,7 @@ fn test_call_remove_liquidity_chained_call_successful() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), - BalanceForTests::remove_amount_lp(), + NonZero::new(BalanceForTests::remove_amount_lp()).unwrap(), BalanceForTests::remove_min_amount_a(), BalanceForTests::remove_min_amount_b_low(), ); @@ -1431,8 +1399,8 @@ fn test_call_new_definition_with_zero_balance_1() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_uninit(), - 0, - BalanceForTests::vault_b_reserve_init(), + NonZero::new(0).expect("Balances must be nonzero"), + NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(), AMM_PROGRAM_ID, ); } @@ -1448,8 +1416,8 @@ fn test_call_new_definition_with_zero_balance_2() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_uninit(), - BalanceForTests::vault_a_reserve_init(), - 0, + NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(), + NonZero::new(0).expect("Balances must be nonzero"), AMM_PROGRAM_ID, ); } @@ -1465,8 +1433,8 @@ fn test_call_new_definition_same_token_definition() { AccountForTests::user_holding_a(), AccountForTests::user_holding_a(), AccountForTests::user_holding_lp_uninit(), - BalanceForTests::vault_a_reserve_init(), - BalanceForTests::vault_b_reserve_init(), + NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(), + NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(), AMM_PROGRAM_ID, ); } @@ -1482,8 +1450,8 @@ fn test_call_new_definition_wrong_liquidity_id() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_uninit(), - BalanceForTests::vault_a_reserve_init(), - BalanceForTests::vault_b_reserve_init(), + NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(), + NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(), AMM_PROGRAM_ID, ); } @@ -1499,8 +1467,8 @@ fn test_call_new_definition_wrong_pool_id() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_uninit(), - BalanceForTests::vault_a_reserve_init(), - BalanceForTests::vault_b_reserve_init(), + NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(), + NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(), AMM_PROGRAM_ID, ); } @@ -1516,8 +1484,8 @@ fn test_call_new_definition_wrong_vault_id_1() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_uninit(), - BalanceForTests::vault_a_reserve_init(), - BalanceForTests::vault_b_reserve_init(), + NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(), + NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(), AMM_PROGRAM_ID, ); } @@ -1533,8 +1501,8 @@ fn test_call_new_definition_wrong_vault_id_2() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_uninit(), - BalanceForTests::vault_a_reserve_init(), - BalanceForTests::vault_b_reserve_init(), + NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(), + NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(), AMM_PROGRAM_ID, ); } @@ -1550,8 +1518,8 @@ fn test_call_new_definition_cannot_initialize_active_pool() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_uninit(), - BalanceForTests::vault_a_reserve_init(), - BalanceForTests::vault_b_reserve_init(), + NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(), + NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(), AMM_PROGRAM_ID, ); } @@ -1567,8 +1535,8 @@ fn test_call_new_definition_chained_call_successful() { AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_uninit(), - BalanceForTests::vault_a_reserve_init(), - BalanceForTests::vault_b_reserve_init(), + NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(), + NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(), AMM_PROGRAM_ID, );