use amm_core::{ PoolDefinition, FEE_TIER_BPS_1, FEE_TIER_BPS_100, FEE_TIER_BPS_30, FEE_TIER_BPS_5, MINIMUM_LIQUIDITY, }; use nssa::{ error::NssaError, program_deployment_transaction::{self, ProgramDeploymentTransaction}, public_transaction, PrivateKey, PublicKey, PublicTransaction, V03State, }; use nssa_core::account::{Account, AccountId, Data, Nonce}; use token_core::{TokenDefinition, TokenHolding}; struct Keys; struct Ids; struct Balances; struct Accounts; impl Keys { fn user_a() -> PrivateKey { PrivateKey::try_new([31; 32]).expect("valid private key") } fn user_b() -> PrivateKey { PrivateKey::try_new([32; 32]).expect("valid private key") } fn user_lp() -> PrivateKey { PrivateKey::try_new([33; 32]).expect("valid private key") } } impl Ids { fn token_program() -> nssa_core::program::ProgramId { token_methods::TOKEN_ID } fn amm_program() -> nssa_core::program::ProgramId { amm_methods::AMM_ID } fn token_a_definition() -> AccountId { AccountId::new([3; 32]) } fn token_b_definition() -> AccountId { AccountId::new([4; 32]) } fn pool_definition() -> AccountId { amm_core::compute_pool_pda( Self::amm_program(), Self::token_a_definition(), Self::token_b_definition(), ) } fn token_lp_definition() -> AccountId { amm_core::compute_liquidity_token_pda(Self::amm_program(), Self::pool_definition()) } fn lp_lock_holding() -> AccountId { amm_core::compute_lp_lock_holding_pda(Self::amm_program(), Self::pool_definition()) } fn vault_a() -> AccountId { amm_core::compute_vault_pda( Self::amm_program(), Self::pool_definition(), Self::token_a_definition(), ) } fn vault_b() -> AccountId { amm_core::compute_vault_pda( Self::amm_program(), Self::pool_definition(), Self::token_b_definition(), ) } fn user_a() -> AccountId { AccountId::from(&PublicKey::new_from_private_key(&Keys::user_a())) } fn user_b() -> AccountId { AccountId::from(&PublicKey::new_from_private_key(&Keys::user_b())) } fn user_lp() -> AccountId { AccountId::from(&PublicKey::new_from_private_key(&Keys::user_lp())) } } impl Balances { fn fee_tier() -> u128 { FEE_TIER_BPS_30 } fn user_a_init() -> u128 { 10_000 } fn user_b_init() -> u128 { 10_000 } fn user_lp_init() -> u128 { 2_000 } fn vault_a_init() -> u128 { 5_000 } fn vault_b_init() -> u128 { 2_500 } fn pool_lp_supply_init() -> u128 { 5_000 } fn token_a_supply() -> u128 { 100_000 } fn token_b_supply() -> u128 { 100_000 } fn token_lp_supply() -> u128 { 5_000 } fn remove_lp() -> u128 { 1_000 } fn remove_min_a() -> u128 { 500 } fn remove_min_b() -> u128 { 500 } fn add_min_lp() -> u128 { 1_000 } fn add_max_a() -> u128 { 2_000 } fn add_max_b() -> u128 { 1_000 } fn swap_amount_in() -> u128 { 1_000 } fn swap_min_out() -> u128 { 200 } fn vault_a_swap_1() -> u128 { 3_572 } fn vault_b_swap_1() -> u128 { 3_500 } fn user_a_swap_1() -> u128 { 11_428 } fn user_b_swap_1() -> u128 { 9_000 } fn vault_a_swap_2() -> u128 { 6_000 } fn vault_b_swap_2() -> u128 { 2_084 } fn user_a_swap_2() -> u128 { 9_000 } fn user_b_swap_2() -> u128 { 10_416 } fn vault_a_add() -> u128 { 7_000 } fn vault_b_add() -> u128 { 3_500 } fn user_a_add() -> u128 { 8_000 } fn user_b_add() -> u128 { 9_000 } fn user_lp_add() -> u128 { 4_000 } fn token_lp_supply_add() -> u128 { 7_000 } fn vault_a_remove() -> u128 { 4_000 } fn vault_b_remove() -> u128 { 2_000 } fn user_a_remove() -> u128 { 11_000 } fn user_b_remove() -> u128 { 10_500 } fn user_lp_remove() -> u128 { 1_000 } fn token_lp_supply_remove() -> u128 { 4_000 } fn user_a_new_definition() -> u128 { 5_000 } fn user_b_new_definition() -> u128 { 7_500 } fn lp_supply_init() -> u128 { (Self::vault_a_init() * Self::vault_b_init()).isqrt() } fn lp_user_init() -> u128 { Self::lp_supply_init() - MINIMUM_LIQUIDITY } } impl Accounts { fn user_a_holding() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_a_definition(), balance: Balances::user_a_init(), }), nonce: Nonce(0), } } fn user_b_holding() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_b_definition(), balance: Balances::user_b_init(), }), nonce: Nonce(0), } } fn pool_definition_init() -> Account { Account { program_owner: Ids::amm_program(), balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: Ids::token_a_definition(), definition_token_b_id: Ids::token_b_definition(), vault_a_id: Ids::vault_a(), vault_b_id: Ids::vault_b(), liquidity_pool_id: Ids::token_lp_definition(), liquidity_pool_supply: Balances::pool_lp_supply_init(), reserve_a: Balances::vault_a_init(), reserve_b: Balances::vault_b_init(), fees: Balances::fee_tier(), active: true, }), nonce: Nonce(0), } } fn token_a_definition_account() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("test"), total_supply: Balances::token_a_supply(), metadata_id: None, }), nonce: Nonce(0), } } fn token_b_definition_account() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("test"), total_supply: Balances::token_b_supply(), metadata_id: None, }), nonce: Nonce(0), } } fn token_lp_definition_account() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("LP Token"), total_supply: Balances::token_lp_supply(), metadata_id: None, }), nonce: Nonce(0), } } fn vault_a_init() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_a_definition(), balance: Balances::vault_a_init(), }), nonce: Nonce(0), } } fn vault_b_init() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_b_definition(), balance: Balances::vault_b_init(), }), nonce: Nonce(0), } } fn user_lp_holding() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_lp_definition(), balance: Balances::user_lp_init(), }), nonce: Nonce(0), } } fn user_lp_holding_with_balance(balance: u128) -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_lp_definition(), balance, }), nonce: Nonce(0), } } // --- Expected post-state accounts --- fn pool_definition_swap_1() -> Account { Account { program_owner: Ids::amm_program(), balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: Ids::token_a_definition(), definition_token_b_id: Ids::token_b_definition(), vault_a_id: Ids::vault_a(), vault_b_id: Ids::vault_b(), liquidity_pool_id: Ids::token_lp_definition(), liquidity_pool_supply: Balances::pool_lp_supply_init(), reserve_a: Balances::vault_a_swap_1(), reserve_b: Balances::vault_b_swap_1(), fees: Balances::fee_tier(), active: true, }), nonce: Nonce(0), } } fn vault_a_swap_1() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_a_definition(), balance: Balances::vault_a_swap_1(), }), nonce: Nonce(0), } } fn vault_b_swap_1() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_b_definition(), balance: Balances::vault_b_swap_1(), }), nonce: Nonce(0), } } fn user_a_holding_swap_1() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_a_definition(), balance: Balances::user_a_swap_1(), }), nonce: Nonce(0), } } fn user_b_holding_swap_1() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_b_definition(), balance: Balances::user_b_swap_1(), }), nonce: Nonce(1), } } fn pool_definition_swap_2() -> Account { Account { program_owner: Ids::amm_program(), balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: Ids::token_a_definition(), definition_token_b_id: Ids::token_b_definition(), vault_a_id: Ids::vault_a(), vault_b_id: Ids::vault_b(), liquidity_pool_id: Ids::token_lp_definition(), liquidity_pool_supply: Balances::pool_lp_supply_init(), reserve_a: Balances::vault_a_swap_2(), reserve_b: Balances::vault_b_swap_2(), fees: Balances::fee_tier(), active: true, }), nonce: Nonce(0), } } fn vault_a_swap_2() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_a_definition(), balance: Balances::vault_a_swap_2(), }), nonce: Nonce(0), } } fn vault_b_swap_2() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_b_definition(), balance: Balances::vault_b_swap_2(), }), nonce: Nonce(0), } } fn user_a_holding_swap_2() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_a_definition(), balance: Balances::user_a_swap_2(), }), nonce: Nonce(1), } } fn user_b_holding_swap_2() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_b_definition(), balance: Balances::user_b_swap_2(), }), nonce: Nonce(0), } } fn pool_definition_add() -> Account { Account { program_owner: Ids::amm_program(), balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: Ids::token_a_definition(), definition_token_b_id: Ids::token_b_definition(), vault_a_id: Ids::vault_a(), vault_b_id: Ids::vault_b(), liquidity_pool_id: Ids::token_lp_definition(), liquidity_pool_supply: Balances::token_lp_supply_add(), reserve_a: Balances::vault_a_add(), reserve_b: Balances::vault_b_add(), fees: Balances::fee_tier(), active: true, }), nonce: Nonce(0), } } fn vault_a_add() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_a_definition(), balance: Balances::vault_a_add(), }), nonce: Nonce(0), } } fn vault_b_add() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_b_definition(), balance: Balances::vault_b_add(), }), nonce: Nonce(0), } } fn user_a_holding_add() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_a_definition(), balance: Balances::user_a_add(), }), nonce: Nonce(1), } } fn user_b_holding_add() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_b_definition(), balance: Balances::user_b_add(), }), nonce: Nonce(1), } } fn user_lp_holding_add() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_lp_definition(), balance: Balances::user_lp_add(), }), nonce: Nonce(0), } } fn token_lp_definition_add() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("LP Token"), total_supply: Balances::token_lp_supply_add(), metadata_id: None, }), nonce: Nonce(0), } } fn pool_definition_remove() -> Account { Account { program_owner: Ids::amm_program(), balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: Ids::token_a_definition(), definition_token_b_id: Ids::token_b_definition(), vault_a_id: Ids::vault_a(), vault_b_id: Ids::vault_b(), liquidity_pool_id: Ids::token_lp_definition(), liquidity_pool_supply: Balances::token_lp_supply_remove(), reserve_a: Balances::vault_a_remove(), reserve_b: Balances::vault_b_remove(), fees: Balances::fee_tier(), active: true, }), nonce: Nonce(0), } } fn vault_a_remove() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_a_definition(), balance: Balances::vault_a_remove(), }), nonce: Nonce(0), } } fn vault_b_remove() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_b_definition(), balance: Balances::vault_b_remove(), }), nonce: Nonce(0), } } fn user_a_holding_remove() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_a_definition(), balance: Balances::user_a_remove(), }), nonce: Nonce(0), } } fn user_b_holding_remove() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_b_definition(), balance: Balances::user_b_remove(), }), nonce: Nonce(0), } } fn user_lp_holding_remove() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_lp_definition(), balance: Balances::user_lp_remove(), }), nonce: Nonce(1), } } fn token_lp_definition_remove() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("LP Token"), total_supply: Balances::token_lp_supply_remove(), metadata_id: None, }), nonce: Nonce(0), } } fn token_lp_definition_init_inactive() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("LP Token"), total_supply: 0, metadata_id: None, }), nonce: Nonce(0), } } fn vault_a_init_inactive() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_a_definition(), balance: 0, }), nonce: Nonce(0), } } fn vault_b_init_inactive() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_b_definition(), balance: 0, }), nonce: Nonce(0), } } fn pool_definition_inactive() -> Account { Account { program_owner: Ids::amm_program(), balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: Ids::token_a_definition(), definition_token_b_id: Ids::token_b_definition(), vault_a_id: Ids::vault_a(), vault_b_id: Ids::vault_b(), liquidity_pool_id: Ids::token_lp_definition(), liquidity_pool_supply: 0, reserve_a: 0, reserve_b: 0, fees: Balances::fee_tier(), active: false, }), nonce: Nonce(0), } } fn user_a_holding_new_init() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_a_definition(), balance: Balances::user_a_new_definition(), }), nonce: Nonce(1), } } fn user_b_holding_new_init() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_b_definition(), balance: Balances::user_b_new_definition(), }), nonce: Nonce(1), } } fn user_lp_holding_new_init() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_lp_definition(), balance: Balances::lp_user_init(), }), nonce: Nonce(0), } } fn token_lp_definition_new_init() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("LP Token"), total_supply: Balances::lp_supply_init(), metadata_id: None, }), nonce: Nonce(0), } } fn lp_lock_holding_new_init() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_lp_definition(), balance: MINIMUM_LIQUIDITY, }), nonce: Nonce(0), } } fn pool_definition_new_init() -> Account { Account { program_owner: Ids::amm_program(), balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: Ids::token_a_definition(), definition_token_b_id: Ids::token_b_definition(), vault_a_id: Ids::vault_a(), vault_b_id: Ids::vault_b(), liquidity_pool_id: Ids::token_lp_definition(), liquidity_pool_supply: Balances::lp_supply_init(), reserve_a: Balances::vault_a_init(), reserve_b: Balances::vault_b_init(), fees: Balances::fee_tier(), active: true, }), nonce: Nonce(0), } } fn user_lp_holding_init_zero() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: Ids::token_lp_definition(), balance: 0, }), nonce: Nonce(0), } } } fn deploy_programs(state: &mut V03State) { let token_message = program_deployment_transaction::Message::new(token_methods::TOKEN_ELF.to_vec()); state .transition_from_program_deployment_transaction(&ProgramDeploymentTransaction::new( token_message, )) .expect("token program deployment must succeed"); let amm_message = program_deployment_transaction::Message::new(amm_methods::AMM_ELF.to_vec()); state .transition_from_program_deployment_transaction(&ProgramDeploymentTransaction::new( amm_message, )) .expect("amm program deployment must succeed"); } fn state_for_amm_tests() -> V03State { let mut state = V03State::new_with_genesis_accounts(&[], &[]); deploy_programs(&mut state); state.force_insert_account(Ids::pool_definition(), Accounts::pool_definition_init()); state.force_insert_account( Ids::token_a_definition(), Accounts::token_a_definition_account(), ); state.force_insert_account( Ids::token_b_definition(), Accounts::token_b_definition_account(), ); state.force_insert_account( Ids::token_lp_definition(), Accounts::token_lp_definition_account(), ); state.force_insert_account(Ids::user_a(), Accounts::user_a_holding()); state.force_insert_account(Ids::user_b(), Accounts::user_b_holding()); state.force_insert_account(Ids::user_lp(), Accounts::user_lp_holding()); state.force_insert_account(Ids::vault_a(), Accounts::vault_a_init()); state.force_insert_account(Ids::vault_b(), Accounts::vault_b_init()); state } fn state_for_amm_tests_with_new_def() -> V03State { let mut state = V03State::new_with_genesis_accounts(&[], &[]); deploy_programs(&mut state); state.force_insert_account( Ids::token_a_definition(), Accounts::token_a_definition_account(), ); state.force_insert_account( Ids::token_b_definition(), Accounts::token_b_definition_account(), ); state.force_insert_account(Ids::user_a(), Accounts::user_a_holding()); state.force_insert_account(Ids::user_b(), Accounts::user_b_holding()); state } fn try_execute_new_definition(state: &mut V03State, fees: u128) -> Result<(), NssaError> { let instruction = amm_core::Instruction::NewDefinition { token_a_amount: Balances::vault_a_init(), token_b_amount: Balances::vault_b_init(), fees, amm_program_id: Ids::amm_program(), }; let message = public_transaction::Message::try_new( Ids::amm_program(), vec![ Ids::pool_definition(), Ids::vault_a(), Ids::vault_b(), Ids::token_lp_definition(), Ids::lp_lock_holding(), Ids::user_a(), Ids::user_b(), Ids::user_lp(), ], vec![Nonce(0), Nonce(0)], instruction, ) .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a(), &Keys::user_b()]); let tx = PublicTransaction::new(message, witness_set); state.transition_from_public_transaction(&tx, 0) } fn execute_new_definition(state: &mut V03State, fees: u128) { try_execute_new_definition(state, fees).unwrap(); } #[test] fn amm_remove_liquidity() { let mut state = state_for_amm_tests(); let instruction = amm_core::Instruction::RemoveLiquidity { remove_liquidity_amount: Balances::remove_lp(), min_amount_to_remove_token_a: Balances::remove_min_a(), min_amount_to_remove_token_b: Balances::remove_min_b(), }; let message = public_transaction::Message::try_new( Ids::amm_program(), vec![ Ids::pool_definition(), Ids::vault_a(), Ids::vault_b(), Ids::token_lp_definition(), Ids::user_a(), Ids::user_b(), Ids::user_lp(), ], vec![Nonce(0)], instruction, ) .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_lp()]); let tx = PublicTransaction::new(message, witness_set); state.transition_from_public_transaction(&tx, 0).unwrap(); assert_eq!( state.get_account_by_id(Ids::pool_definition()), Accounts::pool_definition_remove() ); assert_eq!( state.get_account_by_id(Ids::vault_a()), Accounts::vault_a_remove() ); assert_eq!( state.get_account_by_id(Ids::vault_b()), Accounts::vault_b_remove() ); assert_eq!( state.get_account_by_id(Ids::token_lp_definition()), Accounts::token_lp_definition_remove() ); assert_eq!( state.get_account_by_id(Ids::user_a()), Accounts::user_a_holding_remove() ); assert_eq!( state.get_account_by_id(Ids::user_b()), Accounts::user_b_holding_remove() ); assert_eq!( state.get_account_by_id(Ids::user_lp()), Accounts::user_lp_holding_remove() ); } #[test] fn amm_remove_liquidity_insufficient_user_lp_fails() { let mut state = state_for_amm_tests(); state.force_insert_account(Ids::user_lp(), Accounts::user_lp_holding_with_balance(500)); let instruction = amm_core::Instruction::RemoveLiquidity { remove_liquidity_amount: Balances::remove_lp(), min_amount_to_remove_token_a: Balances::remove_min_a(), min_amount_to_remove_token_b: Balances::remove_min_b(), }; let message = public_transaction::Message::try_new( Ids::amm_program(), vec![ Ids::pool_definition(), Ids::vault_a(), Ids::vault_b(), Ids::token_lp_definition(), Ids::user_a(), Ids::user_b(), Ids::user_lp(), ], vec![Nonce(0)], instruction, ) .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_lp()]); let tx = PublicTransaction::new(message, witness_set); assert!(state.transition_from_public_transaction(&tx, 0).is_err()); } #[test] fn amm_new_definition_inactive_initialized_pool_and_uninit_user_lp() { let mut state = state_for_amm_tests_with_new_def(); state.force_insert_account(Ids::vault_a(), Accounts::vault_a_init_inactive()); state.force_insert_account(Ids::vault_b(), Accounts::vault_b_init_inactive()); state.force_insert_account(Ids::pool_definition(), Accounts::pool_definition_inactive()); state.force_insert_account( Ids::token_lp_definition(), Accounts::token_lp_definition_init_inactive(), ); execute_new_definition(&mut state, Balances::fee_tier()); assert_eq!( state.get_account_by_id(Ids::pool_definition()), Accounts::pool_definition_new_init() ); assert_eq!( state.get_account_by_id(Ids::vault_a()), Accounts::vault_a_init() ); assert_eq!( state.get_account_by_id(Ids::vault_b()), Accounts::vault_b_init() ); assert_eq!( state.get_account_by_id(Ids::token_lp_definition()), Accounts::token_lp_definition_new_init() ); assert_eq!( state.get_account_by_id(Ids::lp_lock_holding()), Accounts::lp_lock_holding_new_init() ); assert_eq!( state.get_account_by_id(Ids::user_a()), Accounts::user_a_holding_new_init() ); assert_eq!( state.get_account_by_id(Ids::user_b()), Accounts::user_b_holding_new_init() ); assert_eq!( state.get_account_by_id(Ids::user_lp()), Accounts::user_lp_holding_new_init() ); } #[test] fn amm_new_definition_inactive_initialized_pool_init_user_lp() { let mut state = state_for_amm_tests_with_new_def(); state.force_insert_account(Ids::vault_a(), Accounts::vault_a_init_inactive()); state.force_insert_account(Ids::vault_b(), Accounts::vault_b_init_inactive()); state.force_insert_account(Ids::pool_definition(), Accounts::pool_definition_inactive()); state.force_insert_account( Ids::token_lp_definition(), Accounts::token_lp_definition_init_inactive(), ); state.force_insert_account(Ids::user_lp(), Accounts::user_lp_holding_init_zero()); execute_new_definition(&mut state, Balances::fee_tier()); assert_eq!( state.get_account_by_id(Ids::pool_definition()), Accounts::pool_definition_new_init() ); assert_eq!( state.get_account_by_id(Ids::vault_a()), Accounts::vault_a_init() ); assert_eq!( state.get_account_by_id(Ids::vault_b()), Accounts::vault_b_init() ); assert_eq!( state.get_account_by_id(Ids::token_lp_definition()), Accounts::token_lp_definition_new_init() ); assert_eq!( state.get_account_by_id(Ids::lp_lock_holding()), Accounts::lp_lock_holding_new_init() ); assert_eq!( state.get_account_by_id(Ids::user_a()), Accounts::user_a_holding_new_init() ); assert_eq!( state.get_account_by_id(Ids::user_b()), Accounts::user_b_holding_new_init() ); assert_eq!( state.get_account_by_id(Ids::user_lp()), Accounts::user_lp_holding_new_init() ); } #[test] fn amm_new_definition_uninitialized_pool() { let mut state = state_for_amm_tests_with_new_def(); state.force_insert_account(Ids::vault_a(), Accounts::vault_a_init_inactive()); state.force_insert_account(Ids::vault_b(), Accounts::vault_b_init_inactive()); execute_new_definition(&mut state, Balances::fee_tier()); assert_eq!( state.get_account_by_id(Ids::pool_definition()), Accounts::pool_definition_new_init() ); assert_eq!( state.get_account_by_id(Ids::vault_a()), Accounts::vault_a_init() ); assert_eq!( state.get_account_by_id(Ids::vault_b()), Accounts::vault_b_init() ); assert_eq!( state.get_account_by_id(Ids::token_lp_definition()), Accounts::token_lp_definition_new_init() ); assert_eq!( state.get_account_by_id(Ids::lp_lock_holding()), Accounts::lp_lock_holding_new_init() ); assert_eq!( state.get_account_by_id(Ids::user_a()), Accounts::user_a_holding_new_init() ); assert_eq!( state.get_account_by_id(Ids::user_b()), Accounts::user_b_holding_new_init() ); assert_eq!( state.get_account_by_id(Ids::user_lp()), Accounts::user_lp_holding_new_init() ); } #[test] fn amm_new_definition_supports_all_fee_tiers() { for fees in [ FEE_TIER_BPS_1, FEE_TIER_BPS_5, FEE_TIER_BPS_30, FEE_TIER_BPS_100, ] { let mut state = state_for_amm_tests_with_new_def(); state.force_insert_account(Ids::vault_a(), Accounts::vault_a_init_inactive()); state.force_insert_account(Ids::vault_b(), Accounts::vault_b_init_inactive()); execute_new_definition(&mut state, fees); let pool_definition = PoolDefinition::try_from(&state.get_account_by_id(Ids::pool_definition()).data) .expect("new definition should create a valid pool"); assert_eq!(pool_definition.fees, fees); } } #[test] fn amm_new_definition_rejects_unsupported_fee_tier_transaction() { let mut state = state_for_amm_tests_with_new_def(); state.force_insert_account(Ids::vault_a(), Accounts::vault_a_init_inactive()); state.force_insert_account(Ids::vault_b(), Accounts::vault_b_init_inactive()); state.force_insert_account(Ids::pool_definition(), Accounts::pool_definition_inactive()); state.force_insert_account( Ids::token_lp_definition(), Accounts::token_lp_definition_init_inactive(), ); state.force_insert_account(Ids::user_lp(), Accounts::user_lp_holding_init_zero()); let result = try_execute_new_definition(&mut state, 2); assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_)))); assert_eq!( state.get_account_by_id(Ids::pool_definition()), Accounts::pool_definition_inactive() ); assert_eq!( state.get_account_by_id(Ids::vault_a()), Accounts::vault_a_init_inactive() ); assert_eq!( state.get_account_by_id(Ids::vault_b()), Accounts::vault_b_init_inactive() ); assert_eq!( state.get_account_by_id(Ids::token_lp_definition()), Accounts::token_lp_definition_init_inactive() ); assert_eq!( state.get_account_by_id(Ids::user_a()), Accounts::user_a_holding() ); assert_eq!( state.get_account_by_id(Ids::user_b()), Accounts::user_b_holding() ); assert_eq!( state.get_account_by_id(Ids::user_lp()), Accounts::user_lp_holding_init_zero() ); } #[test] fn amm_add_liquidity() { let mut state = state_for_amm_tests(); let instruction = amm_core::Instruction::AddLiquidity { min_amount_liquidity: Balances::add_min_lp(), max_amount_to_add_token_a: Balances::add_max_a(), max_amount_to_add_token_b: Balances::add_max_b(), }; let message = public_transaction::Message::try_new( Ids::amm_program(), vec![ Ids::pool_definition(), Ids::vault_a(), Ids::vault_b(), Ids::token_lp_definition(), Ids::user_a(), Ids::user_b(), Ids::user_lp(), ], vec![Nonce(0), Nonce(0)], instruction, ) .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a(), &Keys::user_b()]); let tx = PublicTransaction::new(message, witness_set); state.transition_from_public_transaction(&tx, 0).unwrap(); assert_eq!( state.get_account_by_id(Ids::pool_definition()), Accounts::pool_definition_add() ); assert_eq!( state.get_account_by_id(Ids::vault_a()), Accounts::vault_a_add() ); assert_eq!( state.get_account_by_id(Ids::vault_b()), Accounts::vault_b_add() ); assert_eq!( state.get_account_by_id(Ids::token_lp_definition()), Accounts::token_lp_definition_add() ); assert_eq!( state.get_account_by_id(Ids::user_a()), Accounts::user_a_holding_add() ); assert_eq!( state.get_account_by_id(Ids::user_b()), Accounts::user_b_holding_add() ); assert_eq!( state.get_account_by_id(Ids::user_lp()), Accounts::user_lp_holding_add() ); } #[test] fn amm_swap_b_to_a() { let mut state = state_for_amm_tests(); let instruction = amm_core::Instruction::SwapExactInput { swap_amount_in: Balances::swap_amount_in(), min_amount_out: Balances::swap_min_out(), token_definition_id_in: Ids::token_b_definition(), }; let message = public_transaction::Message::try_new( Ids::amm_program(), vec![ Ids::pool_definition(), Ids::vault_a(), Ids::vault_b(), Ids::user_a(), Ids::user_b(), ], vec![Nonce(0)], instruction, ) .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_b()]); let tx = PublicTransaction::new(message, witness_set); state.transition_from_public_transaction(&tx, 0).unwrap(); assert_eq!( state.get_account_by_id(Ids::pool_definition()), Accounts::pool_definition_swap_1() ); assert_eq!( state.get_account_by_id(Ids::vault_a()), Accounts::vault_a_swap_1() ); assert_eq!( state.get_account_by_id(Ids::vault_b()), Accounts::vault_b_swap_1() ); assert_eq!( state.get_account_by_id(Ids::user_a()), Accounts::user_a_holding_swap_1() ); assert_eq!( state.get_account_by_id(Ids::user_b()), Accounts::user_b_holding_swap_1() ); } #[test] fn amm_swap_a_to_b() { let mut state = state_for_amm_tests(); let instruction = amm_core::Instruction::SwapExactInput { swap_amount_in: Balances::swap_amount_in(), min_amount_out: Balances::swap_min_out(), token_definition_id_in: Ids::token_a_definition(), }; let message = public_transaction::Message::try_new( Ids::amm_program(), vec![ Ids::pool_definition(), Ids::vault_a(), Ids::vault_b(), Ids::user_a(), Ids::user_b(), ], vec![Nonce(0)], instruction, ) .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::user_a()]); let tx = PublicTransaction::new(message, witness_set); state.transition_from_public_transaction(&tx, 0).unwrap(); assert_eq!( state.get_account_by_id(Ids::pool_definition()), Accounts::pool_definition_swap_2() ); assert_eq!( state.get_account_by_id(Ids::vault_a()), Accounts::vault_a_swap_2() ); assert_eq!( state.get_account_by_id(Ids::vault_b()), Accounts::vault_b_swap_2() ); assert_eq!( state.get_account_by_id(Ids::user_a()), Accounts::user_a_holding_swap_2() ); assert_eq!( state.get_account_by_id(Ids::user_b()), Accounts::user_b_holding_swap_2() ); }