use nssa_core::{ account::{Account, AccountId, AccountWithMetadata, Data}, program::{ AccountPostState, ChainedCall, PdaSeed, ProgramId, ProgramInput, read_nssa_inputs, write_nssa_outputs_with_chained_call, }, }; // The AMM program has five functions (four directly accessible via instructions): // 1. New AMM definition. Arguments to this function are: // * Seven accounts: [amm_pool, vault_holding_a, vault_holding_b, pool_lp, user_holding_a, // user_holding_b, user_holding_lp]. For new AMM Pool: amm_pool, vault_holding_a, // vault_holding_b, pool_lp and user_holding_lp are default accounts. amm_pool is a default // account that will initiate the amm definition account values vault_holding_a is a token // holding account for token a vault_holding_b is a token holding account for token b pool_lp // is a token holding account for the pool's lp token user_holding_a is a token holding // account for token a user_holding_b is a token holding account for token b user_holding_lp // is a token holding account for lp token // * PDA remark: Accounts amm_pool, vault_holding_a, vault_holding_b and pool_lp are PDA. The // AccountId for these accounts must be computed using: amm_pool AccountId <- // compute_pool_pda vault_holding_a, vault_holding_b <- compute_vault_pda pool_lp // <-compute_liquidity_token_pda // * Requires authorization: user_holding_a, user_holding_b // * An instruction data of 65-bytes, indicating the initial amm reserves' balances and // token_program_id with the following layout: [0x00 || array of balances (little-endian 16 // bytes) || AMM_PROGRAM_ID)] // * Internally, calls compute_liquidity_token_pda_seed, compute_vault_pda_seed to authorize // transfers. // * Internally, calls compute_pool_da, compute_vault_pda and compute_vault_pda to check // various AccountIds are correct. // 2. Swap assets Arguments to this function are: // * Five accounts: [amm_pool, vault_holding_a, vault_holding_b, user_holding_a, // user_holding_b]. // * Requires authorization: user holding account associated to TOKEN_DEFINITION_ID (either // user_holding_a or user_holding_b) // * An instruction data byte string of length 65, indicating which token type to swap, // quantity of tokens put into the swap (of type TOKEN_DEFINITION_ID) and min_amount_out. // [0x01 || amount (little-endian 16 bytes) || TOKEN_DEFINITION_ID]. // * Internally, calls swap logic. // * Four accounts: [user_deposit, vault_deposit, vault_withdraw, user_withdraw]. // user_deposit and vault_deposit define deposit transaction. vault_withdraw and // user_withdraw define withdraw transaction. // * deposit_amount is the amount for user_deposit -> vault_deposit transfer. // * reserve_amounts is the pool's reserves; used to compute the withdraw amount. // * Outputs the token transfers as a Vec and the withdraw amount. // 3. Add liquidity Arguments to this function are: // * Seven accounts: [amm_pool, vault_holding_a, vault_holding_b, pool_lp, user_holding_a, // user_holding_a, user_holding_lp]. // * Requires authorization: user_holding_a, user_holding_b // * An instruction data byte string of length 49, amounts for minimum amount of liquidity from // add (min_amount_lp), // * max amount added for each token (max_amount_a and max_amount_b); indicate [0x02 || array // of of balances (little-endian 16 bytes)]. // * Internally, calls compute_liquidity_token_pda_seed to compute liquidity pool PDA seed. // 4. Remove liquidity // * Seven accounts: [amm_pool, vault_holding_a, vault_holding_b, pool_lp, user_holding_a, // user_holding_a, user_holding_lp]. // * Requires authorization: user_holding_lp // * An instruction data byte string of length 49, amounts for minimum amount of liquidity to // redeem (balance_lp), // * minimum balance of each token to remove (min_amount_a and min_amount_b); indicate [0x03 || // array of balances (little-endian 16 bytes)]. // * Internally, calls compute_vault_pda_seed to compute vault_a and vault_b's PDA seed. const POOL_DEFINITION_DATA_SIZE: usize = 225; #[derive(Clone, Default)] struct PoolDefinition { definition_token_a_id: AccountId, definition_token_b_id: AccountId, vault_a_id: AccountId, vault_b_id: AccountId, liquidity_pool_id: AccountId, liquidity_pool_supply: u128, reserve_a: u128, reserve_b: u128, /// Fees are currently not used fees: u128, /// A pool becomes inactive (active = false) /// once all of its liquidity has been removed (e.g., reserves are emptied and /// liquidity_pool_supply = 0) active: bool, } impl PoolDefinition { 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; bytes .to_vec() .try_into() .expect("225 bytes should fit into Data") } 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"), ); let active = match data[224] { 0 => false, 1 => true, _ => panic!("Parse data: The AMM program must be provided a valid bool for active"), }; 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, }) } } } // TODO: remove repeated code for Token_Definition and TokenHoldling const TOKEN_HOLDING_TYPE: u8 = 1; const TOKEN_HOLDING_DATA_SIZE: usize = 49; struct TokenHolding { #[cfg_attr(not(test), expect(dead_code, reason = "TODO: fix later"))] account_type: u8, definition_id: AccountId, balance: u128, } impl TokenHolding { fn parse(data: &[u8]) -> Option { if data.len() != TOKEN_HOLDING_DATA_SIZE || data[0] != TOKEN_HOLDING_TYPE { None } else { let account_type = data[0]; let definition_id = AccountId::new( data[1..33] .try_into() .expect("Defintion ID must be 32 bytes long"), ); let balance = u128::from_le_bytes( data[33..] .try_into() .expect("balance must be 16 bytes little-endian"), ); Some(Self { definition_id, balance, account_type, }) } } #[cfg(test)] fn into_data(self) -> Data { let mut bytes = [0; TOKEN_HOLDING_DATA_SIZE]; bytes[0] = self.account_type; bytes[1..33].copy_from_slice(&self.definition_id.to_bytes()); bytes[33..].copy_from_slice(&self.balance.to_le_bytes()); bytes .to_vec() .try_into() .expect("49 bytes should fit into Data") } } type Instruction = Vec; fn main() { let ( ProgramInput { pre_states, instruction, }, instruction_words, ) = read_nssa_inputs::(); let (post_states, chained_calls) = match instruction[0] { 0 => { let balance_a: u128 = u128::from_le_bytes( instruction[1..17] .try_into() .expect("New definition: AMM Program expects u128 for balance a"), ); let balance_b: u128 = u128::from_le_bytes( instruction[17..33] .try_into() .expect("New definition: AMM Program expects u128 for balance b"), ); // Convert Vec to ProgramId ([u32;8]) let mut amm_program_id: [u32; 8] = [0; 8]; amm_program_id[0] = u32::from_le_bytes( instruction[33..37] .try_into() .expect("New definition: AMM Program expects valid u32"), ); amm_program_id[1] = u32::from_le_bytes( instruction[37..41] .try_into() .expect("New definition: AMM Program expects valid u32"), ); amm_program_id[2] = u32::from_le_bytes( instruction[41..45] .try_into() .expect("New definition: AMM Program expects valid u32"), ); amm_program_id[3] = u32::from_le_bytes( instruction[45..49] .try_into() .expect("New definition: AMM Program expects valid u32"), ); amm_program_id[4] = u32::from_le_bytes( instruction[49..53] .try_into() .expect("New definition: AMM Program expects valid u32"), ); amm_program_id[5] = u32::from_le_bytes( instruction[53..57] .try_into() .expect("New definition: AMM Program expects valid u32"), ); amm_program_id[6] = u32::from_le_bytes( instruction[57..61] .try_into() .expect("New definition: AMM Program expects valid u32"), ); amm_program_id[7] = u32::from_le_bytes( instruction[61..65] .try_into() .expect("New definition: AMM Program expects valid u32"), ); new_definition(&pre_states, &[balance_a, balance_b], amm_program_id) } 1 => { let mut token_in_id: [u8; 32] = [0; 32]; token_in_id[0..].copy_from_slice(&instruction[33..65]); let token_in_id = AccountId::new(token_in_id); let amount_in = u128::from_le_bytes( instruction[1..17] .try_into() .expect("Swap: AMM Program expects valid u128 for balance to move"), ); let min_amount_out = u128::from_le_bytes( instruction[17..33] .try_into() .expect("Swap: AMM Program expects valid u128 for balance to move"), ); swap(&pre_states, &[amount_in, min_amount_out], token_in_id) } 2 => { let min_amount_lp = u128::from_le_bytes(instruction[1..17].try_into().expect( "Add liquidity: AMM Program expects valid u128 for min amount liquidity", )); let max_amount_a = u128::from_le_bytes( instruction[17..33] .try_into() .expect("Add liquidity: AMM Program expects valid u128 for max amount a"), ); let max_amount_b = u128::from_le_bytes( instruction[33..49] .try_into() .expect("Add liquidity: AMM Program expects valid u128 for max amount b"), ); add_liquidity(&pre_states, &[min_amount_lp, max_amount_a, max_amount_b]) } 3 => { let balance_lp = u128::from_le_bytes(instruction[1..17].try_into().expect( "Remove liquidity: AMM Program expects valid u128 for balance liquidity", )); let min_amount_a = u128::from_le_bytes( instruction[17..33] .try_into() .expect("Remove liquidity: AMM Program expects valid u128 for balance a"), ); let min_amount_b = u128::from_le_bytes( instruction[33..49] .try_into() .expect("Remove liquidity: AMM Program expects valid u128 for balance b"), ); remove_liquidity(&pre_states, &[balance_lp, min_amount_a, min_amount_b]) } _ => panic!("Invalid instruction"), }; write_nssa_outputs_with_chained_call(instruction_words, pre_states, post_states, chained_calls); } fn compute_pool_pda( amm_program_id: ProgramId, definition_token_a_id: AccountId, definition_token_b_id: AccountId, ) -> AccountId { AccountId::from(( &amm_program_id, &compute_pool_pda_seed(definition_token_a_id, definition_token_b_id), )) } fn compute_pool_pda_seed( definition_token_a_id: AccountId, definition_token_b_id: AccountId, ) -> PdaSeed { use risc0_zkvm::sha::{Impl, Sha256}; let (token_1, token_2) = match definition_token_a_id .value() .cmp(definition_token_b_id.value()) { std::cmp::Ordering::Less => (definition_token_b_id, definition_token_a_id), std::cmp::Ordering::Greater => (definition_token_a_id, definition_token_b_id), std::cmp::Ordering::Equal => panic!("Definitions match"), }; let mut bytes = [0; 64]; bytes[0..32].copy_from_slice(&token_1.to_bytes()); bytes[32..].copy_from_slice(&token_2.to_bytes()); PdaSeed::new( Impl::hash_bytes(&bytes) .as_bytes() .try_into() .expect("Hash output must be exactly 32 bytes long"), ) } fn compute_vault_pda( amm_program_id: ProgramId, pool_id: AccountId, definition_token_id: AccountId, ) -> AccountId { AccountId::from(( &amm_program_id, &compute_vault_pda_seed(pool_id, definition_token_id), )) } fn compute_vault_pda_seed(pool_id: AccountId, definition_token_id: AccountId) -> PdaSeed { use risc0_zkvm::sha::{Impl, Sha256}; let mut bytes = [0; 64]; bytes[0..32].copy_from_slice(&pool_id.to_bytes()); bytes[32..].copy_from_slice(&definition_token_id.to_bytes()); PdaSeed::new( Impl::hash_bytes(&bytes) .as_bytes() .try_into() .expect("Hash output must be exactly 32 bytes long"), ) } fn compute_liquidity_token_pda(amm_program_id: ProgramId, pool_id: AccountId) -> AccountId { AccountId::from((&amm_program_id, &compute_liquidity_token_pda_seed(pool_id))) } fn compute_liquidity_token_pda_seed(pool_id: AccountId) -> PdaSeed { use risc0_zkvm::sha::{Impl, Sha256}; let mut bytes = [0; 64]; bytes[0..32].copy_from_slice(&pool_id.to_bytes()); bytes[32..].copy_from_slice(&[0; 32]); PdaSeed::new( Impl::hash_bytes(&bytes) .as_bytes() .try_into() .expect("Hash output must be exactly 32 bytes long"), ) } const TOKEN_PROGRAM_NEW: u8 = 0; const TOKEN_PROGRAM_TRANSFER: u8 = 1; const TOKEN_PROGRAM_MINT: u8 = 4; const TOKEN_PROGRAM_BURN: u8 = 3; fn initialize_token_transfer_chained_call( token_program_command: u8, sender: AccountWithMetadata, recipient: AccountWithMetadata, amount_to_move: u128, pda_seed: Vec, ) -> ChainedCall { let mut instruction_data = vec![0u8; 23]; instruction_data[0] = token_program_command; instruction_data[1..17].copy_from_slice(&amount_to_move.to_le_bytes()); let instruction_data = risc0_zkvm::serde::to_vec(&instruction_data) .expect("AMM Program expects valid token transfer instruction data"); ChainedCall { program_id: sender.account.program_owner, instruction_data, pre_states: vec![sender, recipient], pda_seeds: pda_seed, } } fn new_definition( pre_states: &[AccountWithMetadata], balance_in: &[u128], amm_program_id: ProgramId, ) -> (Vec, Vec) { // Pool accounts: pool itself, and its 2 vaults and LP token // 2 accounts for funding tokens // initial funder's LP account if pre_states.len() != 7 { panic!("Invalid number of input accounts") } if balance_in.len() != 2 { panic!("Invalid number of input balances") } let pool = &pre_states[0]; let vault_a = &pre_states[1]; let vault_b = &pre_states[2]; let pool_lp = &pre_states[3]; let user_holding_a = &pre_states[4]; let user_holding_b = &pre_states[5]; let user_holding_lp = &pre_states[6]; let amount_a = balance_in[0]; let amount_b = balance_in[1]; // Prevents pool constant coefficient (k) from being 0. if amount_a == 0 || amount_b == 0 { panic!("Balances must be nonzero") } // Verify token_a and token_b are different let definition_token_a_id = TokenHolding::parse(&user_holding_a.account.data) .expect("New definition: AMM Program expects valid Token Holding account for Token A") .definition_id; let definition_token_b_id = TokenHolding::parse(&user_holding_b.account.data) .expect("New definition: AMM Program expects valid Token Holding account for Token B") .definition_id; // 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_lp.account_id != compute_liquidity_token_pda(amm_program_id, pool.account_id) { panic!("Liquidity pool Token Definition Account ID does not match PDA"); } // 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") }; if pool_account_data.active { panic!("Cannot initialize an active Pool Definition") } // LP Token minting calculation // We assume LP is based on the initial deposit amount for Token_A. // Update pool account let mut pool_post = pool.account.clone(); let pool_post_definition = PoolDefinition { definition_token_a_id, definition_token_b_id, vault_a_id: vault_a.account_id, vault_b_id: vault_b.account_id, liquidity_pool_id: pool_lp.account_id, liquidity_pool_supply: amount_a, reserve_a: amount_a, reserve_b: amount_b, fees: 0u128, // TODO: we assume all fees are 0 for now. active: true, }; pool_post.data = pool_post_definition.into_data(); let pool_post: AccountPostState = if pool.account == Account::default() { AccountPostState::new_claimed(pool_post.clone()) } else { AccountPostState::new(pool_post.clone()) }; let mut chained_calls = Vec::::new(); // Chain call for Token A (user_holding_a -> Vault_A) let call_token_a = initialize_token_transfer_chained_call( TOKEN_PROGRAM_TRANSFER, user_holding_a.clone(), vault_a.clone(), amount_a, Vec::::new(), ); // Chain call for Token B (user_holding_b -> Vault_B) let call_token_b = initialize_token_transfer_chained_call( TOKEN_PROGRAM_TRANSFER, user_holding_b.clone(), vault_b.clone(), amount_b, Vec::::new(), ); // Chain call for liquidity token (TokenLP definition -> User LP Holding) let mut instruction_data = vec![0u8; 23]; instruction_data[0] = if pool.account == Account::default() { TOKEN_PROGRAM_NEW } else { TOKEN_PROGRAM_MINT }; //new or mint let nme = if pool.account == Account::default() { [1u8; 6] } else { [0u8; 6] }; instruction_data[1..17].copy_from_slice(&amount_a.to_le_bytes()); instruction_data[17..].copy_from_slice(&nme); let instruction_data = risc0_zkvm::serde::to_vec(&instruction_data) .expect("New definition: AMM Program expects valid instruction_data"); let mut pool_lp_auth = pool_lp.clone(); pool_lp_auth.is_authorized = true; let token_program_id = user_holding_a.account.program_owner; let call_token_lp = ChainedCall { program_id: token_program_id, instruction_data, pre_states: vec![pool_lp_auth.clone(), user_holding_lp.clone()], pda_seeds: vec![compute_liquidity_token_pda_seed(pool.account_id)], }; chained_calls.push(call_token_lp); chained_calls.push(call_token_b); chained_calls.push(call_token_a); let post_states = vec![ pool_post.clone(), AccountPostState::new(pre_states[1].account.clone()), AccountPostState::new(pre_states[2].account.clone()), AccountPostState::new(pre_states[3].account.clone()), AccountPostState::new(pre_states[4].account.clone()), AccountPostState::new(pre_states[5].account.clone()), AccountPostState::new(pre_states[6].account.clone()), ]; (post_states.clone(), chained_calls) } fn swap( pre_states: &[AccountWithMetadata], amounts: &[u128], token_in_id: AccountId, ) -> (Vec, Vec) { if pre_states.len() != 5 { panic!("Invalid number of input accounts"); } if amounts.len() != 2 { panic!("Invalid number of amounts provided"); } let pool = &pre_states[0]; let vault_a = &pre_states[1]; let vault_b = &pre_states[2]; let user_holding_a = &pre_states[3]; let user_holding_b = &pre_states[4]; // Verify vaults are in fact vaults let pool_def_data = PoolDefinition::parse(&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"); } // fetch pool reserves // validates reserves is at least the vaults' balances if TokenHolding::parse(&vault_a.account.data) .expect("Swap: AMM Program expects a valid Token Holding Account for Vault A") .balance < pool_def_data.reserve_a { panic!("Reserve for Token A exceeds vault balance"); } if TokenHolding::parse(&vault_b.account.data) .expect("Swap: AMM Program expects a valid Token Holding Account for Vault B") .balance < pool_def_data.reserve_b { panic!("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 { let (chained_calls, deposit_a, withdraw_b) = swap_logic( user_holding_a.clone(), vault_a.clone(), vault_b.clone(), user_holding_b.clone(), amounts[0], amounts[1], &[pool_def_data.reserve_a, pool_def_data.reserve_b], pool.account_id, ); (chained_calls, [deposit_a, 0], [0, withdraw_b]) } else if token_in_id == pool_def_data.definition_token_b_id { let (chained_calls, deposit_b, withdraw_a) = swap_logic( user_holding_b.clone(), vault_b.clone(), vault_a.clone(), user_holding_a.clone(), amounts[0], amounts[1], &[pool_def_data.reserve_b, pool_def_data.reserve_a], pool.account_id, ); (chained_calls, [0, withdraw_a], [deposit_b, 0]) } else { panic!("AccountId is not a token type for the pool"); }; // Update pool account let mut pool_post = pool.account.clone(); let pool_post_definition = PoolDefinition { reserve_a: pool_def_data.reserve_a + deposit_a - withdraw_a, reserve_b: pool_def_data.reserve_b + deposit_b - withdraw_b, ..pool_def_data }; pool_post.data = pool_post_definition.into_data(); let post_states = vec![ AccountPostState::new(pool_post.clone()), AccountPostState::new(pre_states[1].account.clone()), AccountPostState::new(pre_states[2].account.clone()), AccountPostState::new(pre_states[3].account.clone()), AccountPostState::new(pre_states[4].account.clone()), ]; (post_states, chained_calls) } #[expect(clippy::too_many_arguments, reason = "TODO: Fix later")] fn swap_logic( user_deposit: AccountWithMetadata, vault_deposit: AccountWithMetadata, vault_withdraw: AccountWithMetadata, user_withdraw: AccountWithMetadata, deposit_amount: u128, min_amount_out: u128, reserve_amounts: &[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; let withdraw_amount = (reserve_withdraw_vault_amount * deposit_amount) / (reserve_deposit_vault_amount + deposit_amount); // 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"); } let mut chained_calls = Vec::new(); chained_calls.push(initialize_token_transfer_chained_call( TOKEN_PROGRAM_TRANSFER, user_deposit.clone(), vault_deposit.clone(), deposit_amount, Vec::::new(), )); let mut vault_withdraw = vault_withdraw.clone(); vault_withdraw.is_authorized = true; chained_calls.push(initialize_token_transfer_chained_call( TOKEN_PROGRAM_TRANSFER, vault_withdraw.clone(), user_withdraw.clone(), withdraw_amount, vec![compute_vault_pda_seed( pool_id, TokenHolding::parse(&vault_withdraw.account.data) .expect("Swap Logic: AMM Program expects valid token data") .definition_id, )], )); (chained_calls, deposit_amount, withdraw_amount) } fn add_liquidity( pre_states: &[AccountWithMetadata], balances: &[u128], ) -> (Vec, Vec) { if pre_states.len() != 7 { panic!("Invalid number of input accounts"); } let pool = &pre_states[0]; let vault_a = &pre_states[1]; let vault_b = &pre_states[2]; let pool_definition_lp = &pre_states[3]; let user_holding_a = &pre_states[4]; let user_holding_b = &pre_states[5]; let user_holding_lp = &pre_states[6]; // 1. Fetch Pool state let pool_def_data = PoolDefinition::parse(&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"); } if vault_b.account_id != pool_def_data.vault_b_id { panic!("Vault B was not provided"); } if balances.len() != 3 { panic!("Invalid number of input balances"); } let min_amount_lp = balances[0]; let max_amount_a = balances[1]; let max_amount_b = balances[2]; if max_amount_a == 0 || max_amount_b == 0 { panic!("Both max-balances must be nonzero"); } if min_amount_lp == 0 { panic!("Min-lp must be nonzero"); } // 2. Determine deposit amount let vault_b_balance = TokenHolding::parse(&vault_b.account.data) .expect("Add liquidity: AMM Program expects valid Token Holding Account for Vault B") .balance; let vault_a_balance = TokenHolding::parse(&vault_a.account.data) .expect("Add liquidity: AMM Program expects valid Token Holding Account for Vault A") .balance; 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"); } // Calculate actual_amounts let ideal_a: u128 = (pool_def_data.reserve_a * max_amount_b) / pool_def_data.reserve_b; let ideal_b: u128 = (pool_def_data.reserve_b * max_amount_a) / pool_def_data.reserve_a; let actual_amount_a = if ideal_a > max_amount_a { max_amount_a } else { ideal_a }; let actual_amount_b = if ideal_b > max_amount_b { max_amount_b } else { ideal_b }; // 3. Validate amounts if max_amount_a < actual_amount_a || max_amount_b < actual_amount_b { panic!("Actual trade amounts cannot exceed max_amounts"); } if actual_amount_a == 0 || actual_amount_b == 0 { panic!("A trade amount is 0"); } // 4. Calculate LP to mint let delta_lp = std::cmp::min( pool_def_data.liquidity_pool_supply * actual_amount_a / pool_def_data.reserve_a, pool_def_data.liquidity_pool_supply * actual_amount_b / pool_def_data.reserve_b, ); if delta_lp == 0 { panic!("Payable LP must be nonzero"); } if delta_lp < min_amount_lp { panic!("Payable LP is less than provided minimum LP amount"); } // 5. Update pool account let mut pool_post = pool.account.clone(); let pool_post_definition = PoolDefinition { liquidity_pool_supply: pool_def_data.liquidity_pool_supply + delta_lp, reserve_a: pool_def_data.reserve_a + actual_amount_a, reserve_b: pool_def_data.reserve_b + actual_amount_b, ..pool_def_data }; pool_post.data = pool_post_definition.into_data(); let mut chained_call = Vec::new(); // Chain call for Token A (UserHoldingA -> Vault_A) let call_token_a = initialize_token_transfer_chained_call( TOKEN_PROGRAM_TRANSFER, user_holding_a.clone(), vault_a.clone(), actual_amount_a, Vec::::new(), ); // Chain call for Token B (UserHoldingB -> Vault_B) let call_token_b = initialize_token_transfer_chained_call( TOKEN_PROGRAM_TRANSFER, user_holding_b.clone(), vault_b.clone(), actual_amount_b, Vec::::new(), ); // Chain call for LP (mint new tokens for user_holding_lp) let mut pool_definition_lp_auth = pool_definition_lp.clone(); pool_definition_lp_auth.is_authorized = true; let call_token_lp = initialize_token_transfer_chained_call( TOKEN_PROGRAM_MINT, pool_definition_lp_auth.clone(), user_holding_lp.clone(), delta_lp, vec![compute_liquidity_token_pda_seed(pool.account_id)], ); chained_call.push(call_token_lp); chained_call.push(call_token_b); chained_call.push(call_token_a); let post_states = vec![ AccountPostState::new(pool_post), AccountPostState::new(pre_states[1].account.clone()), AccountPostState::new(pre_states[2].account.clone()), AccountPostState::new(pre_states[3].account.clone()), AccountPostState::new(pre_states[4].account.clone()), AccountPostState::new(pre_states[5].account.clone()), AccountPostState::new(pre_states[6].account.clone()), ]; (post_states, chained_call) } fn remove_liquidity( pre_states: &[AccountWithMetadata], amounts: &[u128], ) -> (Vec, Vec) { if pre_states.len() != 7 { panic!("Invalid number of input accounts"); } let pool = &pre_states[0]; let vault_a = &pre_states[1]; let vault_b = &pre_states[2]; let pool_definition_lp = &pre_states[3]; let user_holding_a = &pre_states[4]; let user_holding_b = &pre_states[5]; let user_holding_lp = &pre_states[6]; if amounts.len() != 3 { panic!("Invalid number of balances"); } let amount_lp = amounts[0]; let amount_min_a = amounts[1]; let amount_min_b = amounts[2]; // 1. Fetch Pool state let pool_def_data = PoolDefinition::parse(&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"); } // Vault addresses do not need to be checked with PDA // calculation for setting authorization since stored // in the Pool Definition. let mut running_vault_a = vault_a.clone(); let mut running_vault_b = vault_b.clone(); running_vault_a.is_authorized = true; running_vault_b.is_authorized = true; if amount_min_a == 0 || amount_min_b == 0 { panic!("Minimum withdraw amount must be nonzero"); } if amount_lp == 0 { panic!("Liquidity amount must be nonzero"); } // 2. Compute withdrawal amounts let user_holding_lp_data = TokenHolding::parse(&user_holding_lp.account.data) .expect("Remove liquidity: AMM Program expects a valid Token Account for liquidity token"); if user_holding_lp_data.balance > pool_def_data.liquidity_pool_supply || user_holding_lp_data.definition_id != pool_def_data.liquidity_pool_id { panic!("Invalid liquidity account provided"); } let withdraw_amount_a = (pool_def_data.reserve_a * amount_lp) / pool_def_data.liquidity_pool_supply; let withdraw_amount_b = (pool_def_data.reserve_b * amount_lp) / pool_def_data.liquidity_pool_supply; // 3. Validate and slippage check if withdraw_amount_a < amount_min_a { panic!("Insufficient minimal withdraw amount (Token A) provided for liquidity amount"); } if withdraw_amount_b < amount_min_b { panic!("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 * amount_lp) / pool_def_data.liquidity_pool_supply; let active: bool = pool_def_data.liquidity_pool_supply - delta_lp != 0; // 5. Update pool account let mut pool_post = pool.account.clone(); let pool_post_definition = PoolDefinition { liquidity_pool_supply: pool_def_data.liquidity_pool_supply - delta_lp, reserve_a: pool_def_data.reserve_a - withdraw_amount_a, reserve_b: pool_def_data.reserve_b - withdraw_amount_b, active, ..pool_def_data.clone() }; pool_post.data = pool_post_definition.into_data(); let mut chained_calls = Vec::new(); // Chaincall for Token A withdraw let call_token_a = initialize_token_transfer_chained_call( TOKEN_PROGRAM_TRANSFER, running_vault_a, user_holding_a.clone(), withdraw_amount_a, vec![compute_vault_pda_seed( pool.account_id, pool_def_data.definition_token_a_id, )], ); // Chaincall for Token B withdraw let call_token_b = initialize_token_transfer_chained_call( TOKEN_PROGRAM_TRANSFER, running_vault_b, user_holding_b.clone(), withdraw_amount_b, vec![compute_vault_pda_seed( pool.account_id, pool_def_data.definition_token_b_id, )], ); // Chaincall for LP adjustment let mut pool_definition_lp_auth = pool_definition_lp.clone(); pool_definition_lp_auth.is_authorized = true; let call_token_lp = initialize_token_transfer_chained_call( TOKEN_PROGRAM_BURN, pool_definition_lp_auth.clone(), user_holding_lp.clone(), delta_lp, vec![compute_liquidity_token_pda_seed(pool.account_id)], ); chained_calls.push(call_token_lp); chained_calls.push(call_token_b); chained_calls.push(call_token_a); let post_states = vec![ AccountPostState::new(pool_post.clone()), AccountPostState::new(pre_states[1].account.clone()), AccountPostState::new(pre_states[2].account.clone()), AccountPostState::new(pre_states[3].account.clone()), AccountPostState::new(pre_states[4].account.clone()), AccountPostState::new(pre_states[5].account.clone()), AccountPostState::new(pre_states[6].account.clone()), ]; (post_states, chained_calls) } #[cfg(test)] mod tests { use nssa_core::{ account::{Account, AccountId, AccountWithMetadata, Data}, program::{ChainedCall, PdaSeed, ProgramId}, }; use crate::{ PoolDefinition, TokenHolding, add_liquidity, compute_liquidity_token_pda, compute_liquidity_token_pda_seed, compute_pool_pda, compute_vault_pda, compute_vault_pda_seed, new_definition, remove_liquidity, swap, }; const TOKEN_PROGRAM_ID: ProgramId = [15; 8]; const AMM_PROGRAM_ID: ProgramId = [42; 8]; const TOKEN_DEFINITION_DATA_SIZE: usize = 55; struct TokenDefinition { account_type: u8, name: [u8; 6], total_supply: u128, metadata_id: AccountId, } impl TokenDefinition { fn into_data(self) -> Data { let mut bytes = Vec::::new(); bytes.extend_from_slice(&[self.account_type]); bytes.extend_from_slice(&self.name); bytes.extend_from_slice(&self.total_supply.to_le_bytes()); bytes.extend_from_slice(&self.metadata_id.to_bytes()); if bytes.len() != TOKEN_DEFINITION_DATA_SIZE { panic!("Invalid Token Definition data"); } Data::try_from(bytes).expect("Token definition data size must fit into data") } } struct BalanceForTests; impl BalanceForTests { fn vault_a_reserve_init() -> u128 { 1_000 } fn vault_b_reserve_init() -> u128 { 500 } fn vault_a_reserve_low() -> u128 { 10 } fn vault_b_reserve_low() -> u128 { 10 } fn vault_a_reserve_high() -> u128 { 500_000 } fn vault_b_reserve_high() -> u128 { 500_000 } fn user_token_a_balance() -> u128 { 1_000 } fn user_token_b_balance() -> u128 { 500 } fn user_token_lp_balance() -> u128 { 100 } fn remove_min_amount_a() -> u128 { 50 } fn remove_min_amount_b() -> u128 { 100 } fn remove_actual_a_successful() -> u128 { 100 } fn remove_min_amount_b_low() -> u128 { 50 } fn remove_amount_lp() -> u128 { 100 } fn remove_amount_lp_1() -> u128 { 30 } fn add_max_amount_a() -> u128 { 500 } fn add_max_amount_b() -> u128 { 200 } fn add_max_amount_a_low() -> u128 { 10 } fn add_max_amount_b_low() -> u128 { 10 } fn add_min_amount_lp() -> u128 { 20 } fn vault_a_swap_test_1() -> u128 { 1_500 } fn vault_a_swap_test_2() -> u128 { 715 } fn vault_b_swap_test_1() -> u128 { 334 } fn vault_b_swap_test_2() -> u128 { 700 } fn min_amount_out() -> u128 { 200 } fn vault_a_add_successful() -> u128 { 1_400 } fn vault_b_add_successful() -> u128 { 700 } fn add_successful_amount_a() -> u128 { 400 } fn add_successful_amount_b() -> u128 { 200 } fn vault_a_remove_successful() -> u128 { 900 } fn vault_b_remove_successful() -> u128 { 450 } } struct ChainedCallForTests; impl ChainedCallForTests { fn cc_swap_token_a_test_1() -> ChainedCall { let mut instruction_data = vec![0; 23]; instruction_data[0] = 1; instruction_data[1..17] .copy_from_slice(&BalanceForTests::add_max_amount_a().to_le_bytes()); let instruction_data = risc0_zkvm::serde::to_vec(&instruction_data) .expect("AMM Program expects valid transaction instruction data"); ChainedCall { program_id: TOKEN_PROGRAM_ID, instruction_data, pre_states: vec![ AccountForTests::user_holding_a(), AccountForTests::vault_a_init(), ], pda_seeds: Vec::::new(), } } fn cc_swap_token_b_test_1() -> ChainedCall { let swap_amount: u128 = 166; let mut vault_b_auth = AccountForTests::vault_b_init(); vault_b_auth.is_authorized = true; let mut instruction = vec![0; 23]; instruction[0] = 1; instruction[1..17].copy_from_slice(&swap_amount.to_le_bytes()); let instruction_data = risc0_zkvm::serde::to_vec(&instruction) .expect("AMM Program expects valid transaction instruction data"); ChainedCall { program_id: TOKEN_PROGRAM_ID, instruction_data, pre_states: vec![vault_b_auth, AccountForTests::user_holding_b()], pda_seeds: vec![compute_vault_pda_seed( IdForTests::pool_definition_id(), IdForTests::token_b_definition_id(), )], } } fn cc_swap_token_a_test_2() -> ChainedCall { let swap_amount: u128 = 285; let mut vault_a_auth = AccountForTests::vault_a_init(); vault_a_auth.is_authorized = true; let mut instruction_data = vec![0; 23]; instruction_data[0] = 1; instruction_data[1..17].copy_from_slice(&swap_amount.to_le_bytes()); let instruction_data = risc0_zkvm::serde::to_vec(&instruction_data) .expect("AMM Program expects valid transaction instruction data"); ChainedCall { program_id: TOKEN_PROGRAM_ID, instruction_data, pre_states: vec![vault_a_auth, AccountForTests::user_holding_a()], pda_seeds: vec![compute_vault_pda_seed( IdForTests::pool_definition_id(), IdForTests::token_a_definition_id(), )], } } fn cc_swap_token_b_test_2() -> ChainedCall { let mut instruction = vec![0; 23]; instruction[0] = 1; instruction[1..17].copy_from_slice(&BalanceForTests::add_max_amount_b().to_le_bytes()); let instruction_data = risc0_zkvm::serde::to_vec(&instruction) .expect("AMM Program expects valid transaction instruction data"); ChainedCall { program_id: TOKEN_PROGRAM_ID, instruction_data, pre_states: vec![ AccountForTests::user_holding_b(), AccountForTests::vault_b_init(), ], pda_seeds: Vec::::new(), } } fn cc_add_token_a() -> ChainedCall { let mut instruction = vec![0u8; 23]; instruction[0] = 1; instruction[1..17] .copy_from_slice(&BalanceForTests::add_successful_amount_a().to_le_bytes()); let instruction_data = risc0_zkvm::serde::to_vec(&instruction) .expect("AMM Program expects valid transaction instruction data"); ChainedCall { program_id: TOKEN_PROGRAM_ID, instruction_data, pre_states: vec![ AccountForTests::user_holding_a(), AccountForTests::vault_a_init(), ], pda_seeds: Vec::::new(), } } fn cc_add_token_b() -> ChainedCall { let mut instruction = vec![0u8; 23]; instruction[0] = 1; instruction[1..17] .copy_from_slice(&BalanceForTests::add_successful_amount_b().to_le_bytes()); let instruction_data = risc0_zkvm::serde::to_vec(&instruction) .expect("Swap Logic: AMM Program expects valid transaction instruction data"); ChainedCall { program_id: TOKEN_PROGRAM_ID, instruction_data, pre_states: vec![ AccountForTests::user_holding_b(), AccountForTests::vault_b_init(), ], pda_seeds: Vec::::new(), } } fn cc_add_pool_lp() -> ChainedCall { let mut pool_lp_auth = AccountForTests::pool_lp_init(); pool_lp_auth.is_authorized = true; let mut instruction = vec![0u8; 23]; instruction[0] = 4; instruction[1..17] .copy_from_slice(&BalanceForTests::add_successful_amount_a().to_le_bytes()); let instruction_data = risc0_zkvm::serde::to_vec(&instruction) .expect("Swap Logic: AMM Program expects valid transaction instruction data"); ChainedCall { program_id: TOKEN_PROGRAM_ID, instruction_data, pre_states: vec![pool_lp_auth, AccountForTests::user_holding_lp_init()], pda_seeds: vec![compute_liquidity_token_pda_seed( IdForTests::pool_definition_id(), )], } } fn cc_remove_token_a() -> ChainedCall { let mut vault_a_auth = AccountForTests::vault_a_init(); vault_a_auth.is_authorized = true; let mut instruction = vec![0; 23]; instruction[0] = 1; instruction[1..17] .copy_from_slice(&BalanceForTests::remove_actual_a_successful().to_le_bytes()); let instruction_data = risc0_zkvm::serde::to_vec(&instruction) .expect("AMM Program expects valid transaction instruction data"); ChainedCall { program_id: TOKEN_PROGRAM_ID, instruction_data, pre_states: vec![vault_a_auth, AccountForTests::user_holding_a()], pda_seeds: vec![compute_vault_pda_seed( IdForTests::pool_definition_id(), IdForTests::token_a_definition_id(), )], } } fn cc_remove_token_b() -> ChainedCall { let mut vault_b_auth = AccountForTests::vault_b_init(); vault_b_auth.is_authorized = true; let mut instruction = vec![0; 23]; instruction[0] = 1; instruction[1..17] .copy_from_slice(&BalanceForTests::remove_min_amount_b_low().to_le_bytes()); let instruction_data = risc0_zkvm::serde::to_vec(&instruction) .expect("AMM Program expects valid transaction instruction data"); ChainedCall { program_id: TOKEN_PROGRAM_ID, instruction_data, pre_states: vec![vault_b_auth, AccountForTests::user_holding_b()], pda_seeds: vec![compute_vault_pda_seed( IdForTests::pool_definition_id(), IdForTests::token_b_definition_id(), )], } } fn cc_remove_pool_lp() -> ChainedCall { let mut pool_lp_auth = AccountForTests::pool_lp_init(); pool_lp_auth.is_authorized = true; let mut instruction = vec![0; 23]; instruction[0] = 3; instruction[1..17] .copy_from_slice(&BalanceForTests::remove_actual_a_successful().to_le_bytes()); let instruction_data = risc0_zkvm::serde::to_vec(&instruction) .expect("AMM Program expects valid transaction instruction data"); ChainedCall { program_id: TOKEN_PROGRAM_ID, instruction_data, pre_states: vec![ AccountForTests::pool_lp_init(), AccountForTests::user_holding_lp_init(), ], pda_seeds: vec![compute_liquidity_token_pda_seed( IdForTests::pool_definition_id(), )], } } fn cc_new_definition_token_a() -> ChainedCall { let mut instruction = vec![0; 23]; instruction[0] = 1; instruction[1..17] .copy_from_slice(&BalanceForTests::add_successful_amount_a().to_le_bytes()); let instruction_data = risc0_zkvm::serde::to_vec(&instruction) .expect("AMM Program expects valid transaction instruction data"); ChainedCall { program_id: TOKEN_PROGRAM_ID, instruction_data, pre_states: vec![ AccountForTests::user_holding_a(), AccountForTests::vault_a_init(), ], pda_seeds: Vec::::new(), } } fn cc_new_definition_token_b() -> ChainedCall { let mut instruction = vec![0; 23]; instruction[0] = 1; instruction[1..17] .copy_from_slice(&BalanceForTests::add_successful_amount_b().to_le_bytes()); let instruction_data = risc0_zkvm::serde::to_vec(&instruction) .expect("Swap Logic: AMM Program expects valid transaction instruction data"); ChainedCall { program_id: TOKEN_PROGRAM_ID, instruction_data, pre_states: vec![ AccountForTests::user_holding_b(), AccountForTests::vault_b_init(), ], pda_seeds: Vec::::new(), } } fn cc_new_definition_token_lp() -> ChainedCall { let mut instruction = vec![0; 23]; instruction[0] = 1; instruction[1..17] .copy_from_slice(&BalanceForTests::add_successful_amount_a().to_le_bytes()); let instruction_data = risc0_zkvm::serde::to_vec(&instruction) .expect("AMM Program expects valid transaction instruction data"); ChainedCall { program_id: TOKEN_PROGRAM_ID, instruction_data, pre_states: vec![ AccountForTests::pool_lp_init(), AccountForTests::user_holding_lp_uninit(), ], pda_seeds: vec![compute_liquidity_token_pda_seed( IdForTests::pool_definition_id(), )], } } } struct IdForTests; impl IdForTests { fn token_a_definition_id() -> AccountId { AccountId::new([42; 32]) } fn token_b_definition_id() -> AccountId { AccountId::new([43; 32]) } fn token_lp_definition_id() -> AccountId { compute_liquidity_token_pda(AMM_PROGRAM_ID, IdForTests::pool_definition_id()) } fn user_token_a_id() -> AccountId { AccountId::new([45; 32]) } fn user_token_b_id() -> AccountId { AccountId::new([46; 32]) } fn user_token_lp_id() -> AccountId { AccountId::new([47; 32]) } fn pool_definition_id() -> AccountId { compute_pool_pda( AMM_PROGRAM_ID, IdForTests::token_a_definition_id(), IdForTests::token_b_definition_id(), ) } fn vault_a_id() -> AccountId { compute_vault_pda( AMM_PROGRAM_ID, IdForTests::pool_definition_id(), IdForTests::token_a_definition_id(), ) } fn vault_b_id() -> AccountId { compute_vault_pda( AMM_PROGRAM_ID, IdForTests::pool_definition_id(), IdForTests::token_b_definition_id(), ) } } struct AccountForTests; impl AccountForTests { fn user_holding_a() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: 1u8, definition_id: IdForTests::token_a_definition_id(), balance: BalanceForTests::user_token_a_balance(), }), nonce: 0, }, is_authorized: true, account_id: IdForTests::user_token_a_id(), } } fn user_holding_b() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: 1u8, definition_id: IdForTests::token_b_definition_id(), balance: BalanceForTests::user_token_b_balance(), }), nonce: 0, }, is_authorized: true, account_id: IdForTests::user_token_b_id(), } } fn vault_a_init() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: 1u8, definition_id: IdForTests::token_a_definition_id(), balance: BalanceForTests::vault_a_reserve_init(), }), nonce: 0, }, is_authorized: true, account_id: IdForTests::vault_a_id(), } } fn vault_b_init() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: 1u8, definition_id: IdForTests::token_b_definition_id(), balance: BalanceForTests::vault_b_reserve_init(), }), nonce: 0, }, is_authorized: true, account_id: IdForTests::vault_b_id(), } } fn vault_a_init_high() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: 1u8, definition_id: IdForTests::token_a_definition_id(), balance: BalanceForTests::vault_a_reserve_high(), }), nonce: 0, }, is_authorized: true, account_id: IdForTests::vault_a_id(), } } fn vault_b_init_high() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: 1u8, definition_id: IdForTests::token_b_definition_id(), balance: BalanceForTests::vault_b_reserve_high(), }), nonce: 0, }, is_authorized: true, account_id: IdForTests::vault_b_id(), } } fn vault_a_init_low() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: 1u8, definition_id: IdForTests::token_a_definition_id(), balance: BalanceForTests::vault_a_reserve_low(), }), nonce: 0, }, is_authorized: true, account_id: IdForTests::vault_a_id(), } } fn vault_b_init_low() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: 1u8, definition_id: IdForTests::token_b_definition_id(), balance: BalanceForTests::vault_b_reserve_low(), }), nonce: 0, }, is_authorized: true, account_id: IdForTests::vault_b_id(), } } fn vault_a_init_zero() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: 1u8, definition_id: IdForTests::token_a_definition_id(), balance: 0, }), nonce: 0, }, is_authorized: true, account_id: IdForTests::vault_a_id(), } } fn vault_b_init_zero() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: 1u8, definition_id: IdForTests::token_b_definition_id(), balance: 0, }), nonce: 0, }, is_authorized: true, account_id: IdForTests::vault_b_id(), } } fn pool_lp_init() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, balance: 0u128, data: TokenDefinition::into_data(TokenDefinition { account_type: 0u8, name: [1; 6], total_supply: BalanceForTests::vault_a_reserve_init(), metadata_id: AccountId::new([0; 32]), }), nonce: 0, }, is_authorized: true, account_id: IdForTests::token_lp_definition_id(), } } fn pool_lp_with_wrong_id() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, balance: 0u128, data: TokenDefinition::into_data(TokenDefinition { account_type: 0u8, name: [1; 6], total_supply: BalanceForTests::vault_a_reserve_init(), metadata_id: AccountId::new([0; 32]), }), nonce: 0, }, is_authorized: true, account_id: IdForTests::vault_a_id(), } } fn user_holding_lp_uninit() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: 1u8, definition_id: IdForTests::token_lp_definition_id(), balance: 0, }), nonce: 0, }, is_authorized: true, account_id: IdForTests::user_token_lp_id(), } } fn user_holding_lp_init() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: 1u8, definition_id: IdForTests::token_lp_definition_id(), balance: BalanceForTests::user_token_lp_balance(), }), nonce: 0, }, is_authorized: true, account_id: IdForTests::user_token_lp_id(), } } fn pool_definition_uninit() -> AccountWithMetadata { AccountWithMetadata { account: Account::default(), is_authorized: true, account_id: IdForTests::pool_definition_id(), } } fn pool_definition_init() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), balance: 0u128, data: PoolDefinition::into_data(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(), vault_b_id: IdForTests::vault_b_id(), liquidity_pool_id: IdForTests::token_lp_definition_id(), liquidity_pool_supply: BalanceForTests::vault_a_reserve_init(), reserve_a: BalanceForTests::vault_a_reserve_init(), reserve_b: BalanceForTests::vault_b_reserve_init(), fees: 0u128, active: true, }), nonce: 0, }, is_authorized: true, account_id: IdForTests::pool_definition_id(), } } fn pool_definition_init_reserve_a_zero() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), balance: 0u128, data: PoolDefinition::into_data(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(), vault_b_id: IdForTests::vault_b_id(), liquidity_pool_id: IdForTests::token_lp_definition_id(), liquidity_pool_supply: BalanceForTests::vault_a_reserve_init(), reserve_a: 0, reserve_b: BalanceForTests::vault_b_reserve_init(), fees: 0u128, active: true, }), nonce: 0, }, is_authorized: true, account_id: IdForTests::pool_definition_id(), } } fn pool_definition_init_reserve_b_zero() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), balance: 0u128, data: PoolDefinition::into_data(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(), vault_b_id: IdForTests::vault_b_id(), liquidity_pool_id: IdForTests::token_lp_definition_id(), liquidity_pool_supply: BalanceForTests::vault_a_reserve_init(), reserve_a: BalanceForTests::vault_a_reserve_init(), reserve_b: 0, fees: 0u128, active: true, }), nonce: 0, }, is_authorized: true, account_id: IdForTests::pool_definition_id(), } } fn pool_definition_init_reserve_a_low() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), balance: 0u128, data: PoolDefinition::into_data(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(), vault_b_id: IdForTests::vault_b_id(), liquidity_pool_id: IdForTests::token_lp_definition_id(), liquidity_pool_supply: BalanceForTests::vault_a_reserve_low(), reserve_a: BalanceForTests::vault_a_reserve_low(), reserve_b: BalanceForTests::vault_b_reserve_high(), fees: 0u128, active: true, }), nonce: 0, }, is_authorized: true, account_id: IdForTests::pool_definition_id(), } } fn pool_definition_init_reserve_b_low() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), balance: 0u128, data: PoolDefinition::into_data(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(), vault_b_id: IdForTests::vault_b_id(), liquidity_pool_id: IdForTests::token_lp_definition_id(), liquidity_pool_supply: BalanceForTests::vault_a_reserve_high(), reserve_a: BalanceForTests::vault_a_reserve_high(), reserve_b: BalanceForTests::vault_b_reserve_low(), fees: 0u128, active: true, }), nonce: 0, }, is_authorized: true, account_id: IdForTests::pool_definition_id(), } } fn pool_definition_swap_test_1() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), balance: 0u128, data: PoolDefinition::into_data(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(), vault_b_id: IdForTests::vault_b_id(), liquidity_pool_id: IdForTests::token_lp_definition_id(), liquidity_pool_supply: BalanceForTests::vault_a_reserve_init(), reserve_a: BalanceForTests::vault_a_swap_test_1(), reserve_b: BalanceForTests::vault_b_swap_test_1(), fees: 0u128, active: true, }), nonce: 0, }, is_authorized: true, account_id: IdForTests::pool_definition_id(), } } fn pool_definition_swap_test_2() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), balance: 0u128, data: PoolDefinition::into_data(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(), vault_b_id: IdForTests::vault_b_id(), liquidity_pool_id: IdForTests::token_lp_definition_id(), liquidity_pool_supply: BalanceForTests::vault_a_reserve_init(), reserve_a: BalanceForTests::vault_a_swap_test_2(), reserve_b: BalanceForTests::vault_b_swap_test_2(), fees: 0u128, active: true, }), nonce: 0, }, is_authorized: true, account_id: IdForTests::pool_definition_id(), } } fn pool_definition_add_zero_lp() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), balance: 0u128, data: PoolDefinition::into_data(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(), vault_b_id: IdForTests::vault_b_id(), liquidity_pool_id: IdForTests::token_lp_definition_id(), liquidity_pool_supply: BalanceForTests::vault_a_reserve_low(), reserve_a: BalanceForTests::vault_a_reserve_init(), reserve_b: BalanceForTests::vault_b_reserve_init(), fees: 0u128, active: true, }), nonce: 0, }, is_authorized: true, account_id: IdForTests::pool_definition_id(), } } fn pool_definition_add_successful() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), balance: 0u128, data: PoolDefinition::into_data(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(), vault_b_id: IdForTests::vault_b_id(), liquidity_pool_id: IdForTests::token_lp_definition_id(), liquidity_pool_supply: BalanceForTests::vault_a_add_successful(), reserve_a: BalanceForTests::vault_a_add_successful(), reserve_b: BalanceForTests::vault_b_add_successful(), fees: 0u128, active: true, }), nonce: 0, }, is_authorized: true, account_id: IdForTests::pool_definition_id(), } } fn pool_definition_remove_successful() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), balance: 0u128, data: PoolDefinition::into_data(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(), vault_b_id: IdForTests::vault_b_id(), liquidity_pool_id: IdForTests::token_lp_definition_id(), liquidity_pool_supply: BalanceForTests::vault_a_remove_successful(), reserve_a: BalanceForTests::vault_a_remove_successful(), reserve_b: BalanceForTests::vault_b_remove_successful(), fees: 0u128, active: true, }), nonce: 0, }, is_authorized: true, account_id: IdForTests::pool_definition_id(), } } fn pool_definition_inactive() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), balance: 0u128, data: PoolDefinition::into_data(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(), vault_b_id: IdForTests::vault_b_id(), liquidity_pool_id: IdForTests::token_lp_definition_id(), liquidity_pool_supply: BalanceForTests::vault_a_reserve_init(), reserve_a: BalanceForTests::vault_a_reserve_init(), reserve_b: BalanceForTests::vault_b_reserve_init(), fees: 0u128, active: false, }), nonce: 0, }, is_authorized: true, account_id: IdForTests::pool_definition_id(), } } fn pool_definition_with_wrong_id() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), balance: 0u128, data: PoolDefinition::into_data(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(), vault_b_id: IdForTests::vault_b_id(), liquidity_pool_id: IdForTests::token_lp_definition_id(), liquidity_pool_supply: BalanceForTests::vault_a_reserve_init(), reserve_a: BalanceForTests::vault_a_reserve_init(), reserve_b: BalanceForTests::vault_b_reserve_init(), fees: 0u128, active: false, }), nonce: 0, }, is_authorized: true, account_id: AccountId::new([4; 32]), } } fn vault_a_with_wrong_id() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: 1u8, definition_id: IdForTests::token_a_definition_id(), balance: BalanceForTests::vault_a_reserve_init(), }), nonce: 0, }, is_authorized: true, account_id: AccountId::new([4; 32]), } } fn vault_b_with_wrong_id() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, balance: 0u128, data: TokenHolding::into_data(TokenHolding { account_type: 1u8, definition_id: IdForTests::token_b_definition_id(), balance: BalanceForTests::vault_b_reserve_init(), }), nonce: 0, }, is_authorized: true, account_id: AccountId::new([4; 32]), } } fn pool_definition_active() -> AccountWithMetadata { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), balance: 0u128, data: PoolDefinition::into_data(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(), vault_b_id: IdForTests::vault_b_id(), liquidity_pool_id: IdForTests::token_lp_definition_id(), liquidity_pool_supply: BalanceForTests::vault_a_reserve_init(), reserve_a: BalanceForTests::vault_a_reserve_init(), reserve_b: BalanceForTests::vault_b_reserve_init(), fees: 0u128, active: true, }), nonce: 0, }, is_authorized: true, account_id: IdForTests::pool_definition_id(), } } } #[test] fn test_pool_pda_produces_unique_id_for_token_pair() { // compute_pool_pda(amm_program_id: ProgramId, definition_token_a_id: AccountId, // definition_token_b_id: AccountId) assert!( compute_pool_pda( AMM_PROGRAM_ID, IdForTests::token_a_definition_id(), IdForTests::token_b_definition_id() ) == compute_pool_pda( AMM_PROGRAM_ID, IdForTests::token_b_definition_id(), IdForTests::token_a_definition_id() ) ); } #[should_panic(expected = "Invalid number of input accounts")] #[test] fn test_call_new_definition_with_invalid_number_of_accounts_1() { let pre_states = vec![AccountForTests::pool_definition_uninit()]; let _post_states = new_definition( &pre_states, &[ BalanceForTests::vault_a_reserve_init(), BalanceForTests::vault_b_reserve_init(), ], AMM_PROGRAM_ID, ); } #[should_panic(expected = "Invalid number of input accounts")] #[test] fn test_call_new_definition_with_invalid_number_of_accounts_2() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), ]; let _post_states = new_definition( &pre_states, &[ BalanceForTests::vault_a_reserve_init(), BalanceForTests::vault_b_reserve_init(), ], AMM_PROGRAM_ID, ); } #[should_panic(expected = "Invalid number of input accounts")] #[test] fn test_call_new_definition_with_invalid_number_of_accounts_3() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), ]; let _post_states = new_definition( &pre_states, &[ BalanceForTests::vault_a_reserve_init(), BalanceForTests::vault_b_reserve_init(), ], AMM_PROGRAM_ID, ); } #[should_panic(expected = "Invalid number of input accounts")] #[test] fn test_call_new_definition_with_invalid_number_of_accounts_4() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), AccountForTests::pool_lp_init(), ]; let _post_states = new_definition( &pre_states, &[ BalanceForTests::vault_a_reserve_init(), BalanceForTests::vault_b_reserve_init(), ], AMM_PROGRAM_ID, ); } #[should_panic(expected = "Invalid number of input accounts")] #[test] fn test_call_new_definition_with_invalid_number_of_accounts_5() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), AccountForTests::pool_lp_init(), AccountForTests::user_holding_a(), ]; let _post_states = new_definition( &pre_states, &[ BalanceForTests::vault_a_reserve_init(), BalanceForTests::vault_b_reserve_init(), ], AMM_PROGRAM_ID, ); } #[should_panic(expected = "Invalid number of input accounts")] #[test] fn test_call_new_definition_with_invalid_number_of_accounts_6() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), AccountForTests::pool_lp_init(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), ]; let _post_states = new_definition( &pre_states, &[ BalanceForTests::vault_a_reserve_init(), BalanceForTests::vault_b_reserve_init(), ], AMM_PROGRAM_ID, ); } #[should_panic(expected = "Invalid number of input balances")] #[test] fn test_call_new_definition_with_invalid_number_of_balances() { let pre_states = vec![ 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_uninit(), ]; let _post_states = new_definition( &pre_states, &[BalanceForTests::vault_a_reserve_init()], AMM_PROGRAM_ID, ); } #[should_panic(expected = "Balances must be nonzero")] #[test] fn test_call_new_definition_with_zero_balance_1() { let pre_states = vec![ 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_uninit(), ]; let _post_states = new_definition( &pre_states, &[0, BalanceForTests::vault_b_reserve_init()], AMM_PROGRAM_ID, ); } #[should_panic(expected = "Balances must be nonzero")] #[test] fn test_call_new_definition_with_zero_balance_2() { let pre_states = vec![ 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_uninit(), ]; let _post_states = new_definition( &pre_states, &[BalanceForTests::vault_a_reserve_init(), 0], AMM_PROGRAM_ID, ); } #[should_panic(expected = "Cannot set up a swap for a token with itself")] #[test] fn test_call_new_definition_same_token_definition() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), AccountForTests::pool_lp_init(), AccountForTests::user_holding_a(), AccountForTests::user_holding_a(), AccountForTests::user_holding_lp_uninit(), ]; let _post_states = new_definition( &pre_states, &[ BalanceForTests::vault_a_reserve_init(), BalanceForTests::vault_b_reserve_init(), ], AMM_PROGRAM_ID, ); } #[should_panic(expected = "Liquidity pool Token Definition Account ID does not match PDA")] #[test] fn test_call_new_definition_wrong_liquidity_id() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), AccountForTests::pool_lp_with_wrong_id(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_uninit(), ]; let _post_states = new_definition( &pre_states, &[ BalanceForTests::vault_a_reserve_init(), BalanceForTests::vault_b_reserve_init(), ], AMM_PROGRAM_ID, ); } #[should_panic(expected = "Pool Definition Account ID does not match PDA")] #[test] fn test_call_new_definition_wrong_pool_id() { let pre_states = vec![ AccountForTests::pool_definition_with_wrong_id(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), AccountForTests::pool_lp_init(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_uninit(), ]; let _post_states = new_definition( &pre_states, &[ BalanceForTests::vault_a_reserve_init(), BalanceForTests::vault_b_reserve_init(), ], AMM_PROGRAM_ID, ); } #[should_panic(expected = "Vault ID does not match PDA")] #[test] fn test_call_new_definition_wrong_vault_id_1() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_with_wrong_id(), AccountForTests::vault_b_init(), AccountForTests::pool_lp_init(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_uninit(), ]; let _post_states = new_definition( &pre_states, &[ BalanceForTests::vault_a_reserve_init(), BalanceForTests::vault_b_reserve_init(), ], AMM_PROGRAM_ID, ); } #[should_panic(expected = "Vault ID does not match PDA")] #[test] fn test_call_new_definition_wrong_vault_id_2() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_with_wrong_id(), AccountForTests::pool_lp_init(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_uninit(), ]; let _post_states = new_definition( &pre_states, &[ BalanceForTests::vault_a_reserve_init(), BalanceForTests::vault_b_reserve_init(), ], AMM_PROGRAM_ID, ); } #[should_panic(expected = "Cannot initialize an active Pool Definition")] #[test] fn test_call_new_definition_cannot_initialize_active_pool() { let pre_states = vec![ AccountForTests::pool_definition_active(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), AccountForTests::pool_lp_init(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_uninit(), ]; let _post_states = new_definition( &pre_states, &[ BalanceForTests::vault_a_reserve_init(), BalanceForTests::vault_b_reserve_init(), ], AMM_PROGRAM_ID, ); } #[should_panic(expected = "Cannot initialize an active Pool Definition")] #[test] fn test_call_new_definition_chained_call_successful() { let pre_states = vec![ AccountForTests::pool_definition_active(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), AccountForTests::pool_lp_init(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_uninit(), ]; let (post_states, chained_calls) = new_definition( &pre_states, &[ BalanceForTests::vault_a_reserve_init(), BalanceForTests::vault_b_reserve_init(), ], AMM_PROGRAM_ID, ); let pool_post = post_states[0].clone(); assert!(AccountForTests::pool_definition_add_successful().account == *pool_post.account()); let chained_call_lp = chained_calls[0].clone(); let chained_call_b = chained_calls[1].clone(); let chained_call_a = chained_calls[2].clone(); assert!(chained_call_a == ChainedCallForTests::cc_new_definition_token_a()); assert!(chained_call_b == ChainedCallForTests::cc_new_definition_token_b()); assert!(chained_call_lp == ChainedCallForTests::cc_new_definition_token_lp()); } #[should_panic(expected = "Invalid number of input accounts")] #[test] fn test_call_remove_liquidity_with_invalid_number_of_accounts_2() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), ]; let _post_states = remove_liquidity( &pre_states, &[ BalanceForTests::remove_amount_lp(), BalanceForTests::remove_min_amount_a(), BalanceForTests::remove_min_amount_b(), ], ); } #[should_panic(expected = "Invalid number of input accounts")] #[test] fn test_call_remove_liquidity_with_invalid_number_of_accounts_3() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), ]; let _post_states = remove_liquidity( &pre_states, &[ BalanceForTests::remove_amount_lp(), BalanceForTests::remove_min_amount_a(), BalanceForTests::remove_min_amount_b(), ], ); } #[should_panic(expected = "Invalid number of input accounts")] #[test] fn test_call_remove_liquidity_with_invalid_number_of_accounts_4() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), AccountForTests::pool_lp_init(), ]; let _post_states = remove_liquidity( &pre_states, &[ BalanceForTests::remove_amount_lp(), BalanceForTests::remove_min_amount_a(), BalanceForTests::remove_min_amount_b(), ], ); } #[should_panic(expected = "Invalid number of input accounts")] #[test] fn test_call_remove_liquidity_with_invalid_number_of_accounts_5() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), AccountForTests::pool_lp_init(), AccountForTests::user_holding_a(), ]; let _post_states = remove_liquidity( &pre_states, &[ BalanceForTests::remove_amount_lp(), BalanceForTests::remove_min_amount_a(), BalanceForTests::remove_min_amount_b(), ], ); } #[should_panic(expected = "Invalid number of input accounts")] #[test] fn test_call_remove_liquidity_with_invalid_number_of_accounts_6() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), AccountForTests::pool_lp_init(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), ]; let _post_states = remove_liquidity( &pre_states, &[ BalanceForTests::remove_amount_lp(), BalanceForTests::remove_min_amount_a(), BalanceForTests::remove_min_amount_b(), ], ); } #[should_panic(expected = "Vault A was not provided")] #[test] fn test_call_remove_liquidity_vault_a_omitted() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_with_wrong_id(), AccountForTests::vault_b_init(), AccountForTests::pool_lp_init(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), ]; let _post_states = remove_liquidity( &pre_states, &[ BalanceForTests::remove_amount_lp(), BalanceForTests::remove_min_amount_a(), BalanceForTests::remove_min_amount_b(), ], ); } #[should_panic(expected = "Vault B was not provided")] #[test] fn test_call_remove_liquidity_vault_b_omitted() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_with_wrong_id(), AccountForTests::pool_lp_init(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), ]; let _post_states = remove_liquidity( &pre_states, &[ BalanceForTests::remove_amount_lp(), BalanceForTests::remove_min_amount_a(), BalanceForTests::remove_min_amount_b(), ], ); } #[should_panic(expected = "LP definition mismatch")] #[test] fn test_call_remove_liquidity_lp_def_mismatch() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), AccountForTests::pool_lp_with_wrong_id(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), ]; let _post_states = remove_liquidity( &pre_states, &[ BalanceForTests::remove_amount_lp(), BalanceForTests::remove_min_amount_a(), BalanceForTests::remove_min_amount_b(), ], ); } #[should_panic(expected = "Invalid liquidity account provided")] #[test] fn test_call_remove_liquidity_insufficient_liquidity_amount() { let pre_states = vec![ 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_a(), /* different token account than lp to create * desired error */ ]; let _post_states = remove_liquidity( &pre_states, &[ BalanceForTests::remove_amount_lp(), BalanceForTests::remove_min_amount_a(), BalanceForTests::remove_min_amount_b(), ], ); } #[should_panic( expected = "Insufficient minimal withdraw amount (Token A) provided for liquidity amount" )] #[test] fn test_call_remove_liquidity_insufficient_balance_1() { let pre_states = vec![ 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(), ]; let _post_states = remove_liquidity( &pre_states, &[ BalanceForTests::remove_amount_lp_1(), BalanceForTests::remove_min_amount_a(), BalanceForTests::remove_min_amount_b(), ], ); } #[should_panic( expected = "Insufficient minimal withdraw amount (Token B) provided for liquidity amount" )] #[test] fn test_call_remove_liquidity_insufficient_balance_2() { let pre_states = vec![ 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(), ]; let _post_states = remove_liquidity( &pre_states, &[ BalanceForTests::remove_amount_lp(), BalanceForTests::remove_min_amount_a(), BalanceForTests::remove_min_amount_b(), ], ); } #[should_panic(expected = "Minimum withdraw amount must be nonzero")] #[test] fn test_call_remove_liquidity_min_bal_zero_1() { let pre_states = vec![ 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(), ]; let _post_states = remove_liquidity( &pre_states, &[ BalanceForTests::remove_amount_lp(), 0, BalanceForTests::remove_min_amount_b(), ], ); } #[should_panic(expected = "Minimum withdraw amount must be nonzero")] #[test] fn test_call_remove_liquidity_min_bal_zero_2() { let pre_states = vec![ 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(), ]; let _post_states = remove_liquidity( &pre_states, &[ BalanceForTests::remove_amount_lp(), BalanceForTests::remove_min_amount_a(), 0, ], ); } #[should_panic(expected = "Liquidity amount must be nonzero")] #[test] fn test_call_remove_liquidity_lp_bal_zero() { let pre_states = vec![ 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(), ]; let _post_states = remove_liquidity( &pre_states, &[ 0, BalanceForTests::remove_min_amount_a(), BalanceForTests::remove_min_amount_b(), ], ); } #[test] fn test_call_remove_liquidity_chained_call_successful() { let pre_states = vec![ 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(), ]; let (post_states, chained_calls) = remove_liquidity( &pre_states, &[ BalanceForTests::remove_amount_lp(), BalanceForTests::remove_min_amount_a(), BalanceForTests::remove_min_amount_b_low(), ], ); let pool_post = post_states[0].clone(); assert!( AccountForTests::pool_definition_remove_successful().account == *pool_post.account() ); let chained_call_lp = chained_calls[0].clone(); let chained_call_b = chained_calls[1].clone(); let chained_call_a = chained_calls[2].clone(); assert!(chained_call_a == ChainedCallForTests::cc_remove_token_a()); assert!(chained_call_b == ChainedCallForTests::cc_remove_token_b()); assert!(chained_call_lp == ChainedCallForTests::cc_remove_pool_lp()); } #[should_panic(expected = "Invalid number of input accounts")] #[test] fn test_call_add_liquidity_with_invalid_number_of_accounts_1() { let pre_states = vec![AccountForTests::pool_definition_init()]; let _post_states = add_liquidity( &pre_states, &[ BalanceForTests::add_min_amount_lp(), BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_b(), ], ); } #[should_panic(expected = "Invalid number of input accounts")] #[test] fn test_call_add_liquidity_with_invalid_number_of_accounts_2() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), ]; let _post_states = add_liquidity( &pre_states, &[ BalanceForTests::add_min_amount_lp(), BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_b(), ], ); } #[should_panic(expected = "Invalid number of input accounts")] #[test] fn test_call_add_liquidity_with_invalid_number_of_accounts_3() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), ]; let _post_states = add_liquidity( &pre_states, &[ BalanceForTests::add_min_amount_lp(), BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_b(), ], ); } #[should_panic(expected = "Invalid number of input accounts")] #[test] fn test_call_add_liquidity_with_invalid_number_of_accounts_4() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), AccountForTests::pool_lp_init(), ]; let _post_states = add_liquidity( &pre_states, &[ BalanceForTests::add_min_amount_lp(), BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_b(), ], ); } #[should_panic(expected = "Invalid number of input accounts")] #[test] fn test_call_add_liquidity_with_invalid_number_of_accounts_5() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), AccountForTests::pool_lp_init(), AccountForTests::user_holding_a(), ]; let _post_states = add_liquidity( &pre_states, &[ BalanceForTests::add_min_amount_lp(), BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_b(), ], ); } #[should_panic(expected = "Invalid number of input accounts")] #[test] fn test_call_add_liquidity_with_invalid_number_of_accounts_6() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), AccountForTests::pool_lp_init(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), ]; let _post_states = add_liquidity( &pre_states, &[ BalanceForTests::add_min_amount_lp(), BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_b(), ], ); } #[should_panic(expected = "Invalid number of input balances")] #[test] fn test_call_add_liquidity_invalid_number_of_balances_1() { let pre_states = vec![ 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(), ]; let _post_states = add_liquidity(&pre_states, &[BalanceForTests::add_min_amount_lp()]); } #[should_panic(expected = "Invalid number of input balances")] #[test] fn test_call_add_liquidity_invalid_number_of_balances_2() { let pre_states = vec![ 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(), ]; let _post_states = add_liquidity( &pre_states, &[ BalanceForTests::add_min_amount_lp(), BalanceForTests::add_max_amount_a(), ], ); } #[should_panic(expected = "Vault A was not provided")] #[test] fn test_call_add_liquidity_vault_a_omitted() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_with_wrong_id(), AccountForTests::vault_b_init(), AccountForTests::pool_lp_init(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), ]; let _post_states = add_liquidity( &pre_states, &[ BalanceForTests::add_min_amount_lp(), BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_b(), ], ); } #[should_panic(expected = "Vault B was not provided")] #[test] fn test_call_add_liquidity_vault_b_omitted() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_with_wrong_id(), AccountForTests::pool_lp_init(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), ]; let _post_states = add_liquidity( &pre_states, &[ BalanceForTests::add_min_amount_lp(), BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_b(), ], ); } #[should_panic(expected = "LP definition mismatch")] #[test] fn test_call_add_liquidity_lp_definition_mismatch() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), AccountForTests::pool_lp_with_wrong_id(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), ]; let _post_states = add_liquidity( &pre_states, &[ BalanceForTests::add_min_amount_lp(), BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_b(), ], ); } #[should_panic(expected = "Both max-balances must be nonzero")] #[test] fn test_call_add_liquidity_zero_balance_1() { let pre_states = vec![ 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(), ]; let _post_states = add_liquidity( &pre_states, &[ BalanceForTests::add_min_amount_lp(), 0, BalanceForTests::add_max_amount_b(), ], ); } #[should_panic(expected = "Both max-balances must be nonzero")] #[test] fn test_call_add_liquidity_zero_balance_2() { let pre_states = vec![ 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(), ]; let _post_states = add_liquidity( &pre_states, &[ BalanceForTests::add_min_amount_lp(), 0, BalanceForTests::add_max_amount_a(), ], ); } #[should_panic(expected = "Min-lp must be nonzero")] #[test] fn test_call_add_liquidity_zero_min_lp() { let pre_states = vec![ 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(), ]; let _post_states = add_liquidity( &pre_states, &[ 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() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init_zero(), AccountForTests::vault_b_init(), AccountForTests::pool_lp_init(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), ]; let _post_states = add_liquidity( &pre_states, &[ BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_b(), BalanceForTests::add_min_amount_lp(), ], ); } #[should_panic(expected = "Vaults' balances must be at least the reserve amounts")] #[test] fn test_call_add_liquidity_vault_insufficient_balance_2() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init_zero(), AccountForTests::pool_lp_init(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), ]; let _post_states = add_liquidity( &pre_states, &[ BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_b(), BalanceForTests::add_min_amount_lp(), ], ); } #[should_panic(expected = "A trade amount is 0")] #[test] fn test_call_add_liquidity_actual_amount_zero_1() { let pre_states = vec![ AccountForTests::pool_definition_init_reserve_a_low(), AccountForTests::vault_a_init_low(), AccountForTests::vault_b_init_high(), AccountForTests::pool_lp_init(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), ]; let _post_states = add_liquidity( &pre_states, &[ BalanceForTests::add_min_amount_lp(), BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_b(), ], ); } #[should_panic(expected = "A trade amount is 0")] #[test] fn test_call_add_liquidity_actual_amount_zero_2() { let pre_states = vec![ AccountForTests::pool_definition_init_reserve_b_low(), AccountForTests::vault_a_init_high(), AccountForTests::vault_b_init_low(), AccountForTests::pool_lp_init(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), AccountForTests::user_holding_lp_init(), ]; let _post_states = add_liquidity( &pre_states, &[ BalanceForTests::add_min_amount_lp(), BalanceForTests::add_max_amount_a_low(), BalanceForTests::add_max_amount_b_low(), ], ); } #[should_panic(expected = "Reserves must be nonzero")] #[test] fn test_call_add_liquidity_reserves_zero_1() { let pre_states = vec![ AccountForTests::pool_definition_init_reserve_a_zero(), 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(), ]; let _post_states = add_liquidity( &pre_states, &[ BalanceForTests::add_min_amount_lp(), BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_b(), ], ); } #[should_panic(expected = "Reserves must be nonzero")] #[test] fn test_call_add_liquidity_reserves_zero_2() { let pre_states = vec![ AccountForTests::pool_definition_init_reserve_b_zero(), 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(), ]; let _post_states = add_liquidity( &pre_states, &[ BalanceForTests::add_min_amount_lp(), BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_b(), ], ); } #[should_panic(expected = "Payable LP must be nonzero")] #[test] fn test_call_add_liquidity_payable_lp_zero() { let pre_states = vec![ AccountForTests::pool_definition_add_zero_lp(), 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(), ]; let _post_states = add_liquidity( &pre_states, &[ BalanceForTests::add_min_amount_lp(), BalanceForTests::add_max_amount_a_low(), BalanceForTests::add_max_amount_b_low(), ], ); } #[test] fn test_call_add_liquidity_chained_call_successsful() { let pre_states = vec![ 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(), ]; let (post_states, chained_calls) = add_liquidity( &pre_states, &[ BalanceForTests::add_min_amount_lp(), BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_b(), ], ); let pool_post = post_states[0].clone(); assert!(AccountForTests::pool_definition_add_successful().account == *pool_post.account()); let chained_call_lp = chained_calls[0].clone(); let chained_call_b = chained_calls[1].clone(); let chained_call_a = chained_calls[2].clone(); assert!(chained_call_a == ChainedCallForTests::cc_add_token_a()); assert!(chained_call_b == ChainedCallForTests::cc_add_token_b()); assert!(chained_call_lp == ChainedCallForTests::cc_add_pool_lp()); } #[should_panic(expected = "Invalid number of input accounts")] #[test] fn test_call_swap_with_invalid_number_of_accounts_1() { let pre_states = vec![AccountForTests::pool_definition_init()]; let _post_states = swap( &pre_states, &[ BalanceForTests::add_max_amount_a(), BalanceForTests::min_amount_out(), ], IdForTests::token_a_definition_id(), ); } #[should_panic(expected = "Invalid number of input accounts")] #[test] fn test_call_swap_with_invalid_number_of_accounts_2() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), ]; let _post_states = swap( &pre_states, &[ BalanceForTests::add_max_amount_a(), BalanceForTests::min_amount_out(), ], IdForTests::token_a_definition_id(), ); } #[should_panic(expected = "Invalid number of input accounts")] #[test] fn test_call_swap_with_invalid_number_of_accounts_3() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), ]; let _post_states = swap( &pre_states, &[ BalanceForTests::add_max_amount_a(), BalanceForTests::min_amount_out(), ], IdForTests::token_a_definition_id(), ); } #[should_panic(expected = "Invalid number of input accounts")] #[test] fn test_call_swap_with_invalid_number_of_accounts_4() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), AccountForTests::user_holding_a(), ]; let _post_states = swap( &pre_states, &[ BalanceForTests::add_max_amount_a(), BalanceForTests::min_amount_out(), ], IdForTests::token_a_definition_id(), ); } #[should_panic(expected = "Invalid number of amounts provided")] #[test] fn test_call_swap_with_invalid_number_of_amounts() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), ]; let _post_states = swap( &pre_states, &[BalanceForTests::add_max_amount_a()], IdForTests::token_a_definition_id(), ); } #[should_panic(expected = "AccountId is not a token type for the pool")] #[test] fn test_call_swap_incorrect_token_type() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), ]; let _post_states = swap( &pre_states, &[ BalanceForTests::add_max_amount_a(), BalanceForTests::min_amount_out(), ], IdForTests::token_lp_definition_id(), ); } #[should_panic(expected = "Vault A was not provided")] #[test] fn test_call_swap_vault_a_omitted() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_with_wrong_id(), AccountForTests::vault_b_init(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), ]; let _post_states = swap( &pre_states, &[ BalanceForTests::add_max_amount_a(), BalanceForTests::min_amount_out(), ], IdForTests::token_a_definition_id(), ); } #[should_panic(expected = "Vault B was not provided")] #[test] fn test_call_swap_vault_b_omitted() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_with_wrong_id(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), ]; let _post_states = swap( &pre_states, &[ BalanceForTests::add_max_amount_a(), BalanceForTests::min_amount_out(), ], IdForTests::token_a_definition_id(), ); } #[should_panic(expected = "Reserve for Token A exceeds vault balance")] #[test] fn test_call_swap_reserves_vault_mismatch_1() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init_low(), AccountForTests::vault_b_init(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), ]; let _post_states = swap( &pre_states, &[ BalanceForTests::add_max_amount_a(), BalanceForTests::min_amount_out(), ], IdForTests::token_a_definition_id(), ); } #[should_panic(expected = "Reserve for Token B exceeds vault balance")] #[test] fn test_call_swap_reserves_vault_mismatch_2() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init_low(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), ]; let _post_states = swap( &pre_states, &[ BalanceForTests::add_max_amount_a(), BalanceForTests::min_amount_out(), ], IdForTests::token_a_definition_id(), ); } #[should_panic(expected = "Pool is inactive")] #[test] fn test_call_swap_ianctive() { let pre_states = vec![ AccountForTests::pool_definition_inactive(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), ]; let _post_states = swap( &pre_states, &[ BalanceForTests::add_max_amount_a(), BalanceForTests::min_amount_out(), ], IdForTests::token_a_definition_id(), ); } #[should_panic(expected = "Withdraw amount is less than minimal amount out")] #[test] fn test_call_swap_below_min_out() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), ]; let _post_states = swap( &pre_states, &[ BalanceForTests::add_max_amount_a(), BalanceForTests::min_amount_out(), ], IdForTests::token_a_definition_id(), ); } #[test] fn test_call_swap_chained_call_successful_1() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), ]; let (post_states, chained_calls) = swap( &pre_states, &[ BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_a_low(), ], IdForTests::token_a_definition_id(), ); let pool_post = post_states[0].clone(); assert!(AccountForTests::pool_definition_swap_test_1().account == *pool_post.account()); let chained_call_a = chained_calls[0].clone(); let chained_call_b = chained_calls[1].clone(); assert!(chained_call_a == ChainedCallForTests::cc_swap_token_a_test_1()); assert!(chained_call_b == ChainedCallForTests::cc_swap_token_b_test_1()); } #[test] fn test_call_swap_chained_call_successful_2() { let pre_states = vec![ AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), AccountForTests::vault_b_init(), AccountForTests::user_holding_a(), AccountForTests::user_holding_b(), ]; let (post_states, chained_calls) = swap( &pre_states, &[ BalanceForTests::add_max_amount_b(), BalanceForTests::min_amount_out(), ], IdForTests::token_b_definition_id(), ); let pool_post = post_states[0].clone(); assert!(AccountForTests::pool_definition_swap_test_2().account == *pool_post.account()); let chained_call_a = chained_calls[1].clone(); let chained_call_b = chained_calls[0].clone(); assert!(chained_call_a == ChainedCallForTests::cc_swap_token_a_test_2()); assert!(chained_call_b == ChainedCallForTests::cc_swap_token_b_test_2()); } }