mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-05-18 15:09:51 +00:00
feat(amm): add configurable fee tiers
- accept a supported fee tier in pool creation - store fee tiers in AMM pool state and validate them - update AMM tests and IDL for the new pool creation argument
This commit is contained in:
parent
d0f398814c
commit
9824cd8f90
@ -63,6 +63,10 @@
|
||||
"name": "token_b_amount",
|
||||
"type": "u128"
|
||||
},
|
||||
{
|
||||
"name": "fees",
|
||||
"type": "u128"
|
||||
},
|
||||
{
|
||||
"name": "amm_program_id",
|
||||
"type": "program_id"
|
||||
|
||||
@ -33,6 +33,7 @@ pub enum Instruction {
|
||||
NewDefinition {
|
||||
token_a_amount: u128,
|
||||
token_b_amount: u128,
|
||||
fees: u128,
|
||||
amm_program_id: ProgramId,
|
||||
},
|
||||
|
||||
@ -121,7 +122,7 @@ pub struct PoolDefinition {
|
||||
pub liquidity_pool_supply: u128,
|
||||
pub reserve_a: u128,
|
||||
pub reserve_b: u128,
|
||||
/// Fees are currently not used
|
||||
/// Fee tier in basis points.
|
||||
pub fees: u128,
|
||||
/// Indicates whether the pool is initialized for use.
|
||||
/// `MINIMUM_LIQUIDITY` LP tokens are permanently locked at initialization
|
||||
@ -133,6 +134,26 @@ pub struct PoolDefinition {
|
||||
pub active: bool,
|
||||
}
|
||||
|
||||
pub const FEE_BPS_DENOMINATOR: u128 = 10_000;
|
||||
pub const FEE_TIER_BPS_1: u128 = 1;
|
||||
pub const FEE_TIER_BPS_5: u128 = 5;
|
||||
pub const FEE_TIER_BPS_30: u128 = 30;
|
||||
pub const FEE_TIER_BPS_100: u128 = 100;
|
||||
|
||||
pub fn is_supported_fee_tier(fees: u128) -> bool {
|
||||
matches!(
|
||||
fees,
|
||||
FEE_TIER_BPS_1 | FEE_TIER_BPS_5 | FEE_TIER_BPS_30 | FEE_TIER_BPS_100
|
||||
)
|
||||
}
|
||||
|
||||
pub fn assert_supported_fee_tier(fees: u128) {
|
||||
assert!(
|
||||
is_supported_fee_tier(fees),
|
||||
"Fee tier must be one of 1, 5, 30, or 100 basis points"
|
||||
);
|
||||
}
|
||||
|
||||
impl TryFrom<&Data> for PoolDefinition {
|
||||
type Error = std::io::Error;
|
||||
|
||||
|
||||
@ -28,6 +28,7 @@ mod amm {
|
||||
user_holding_lp: AccountWithMetadata,
|
||||
token_a_amount: u128,
|
||||
token_b_amount: u128,
|
||||
fees: u128,
|
||||
amm_program_id: ProgramId,
|
||||
) -> SpelResult {
|
||||
let (post_states, chained_calls) = amm_program::new_definition::new_definition(
|
||||
@ -41,6 +42,7 @@ mod amm {
|
||||
user_holding_lp,
|
||||
NonZeroU128::new(token_a_amount).expect("token_a_amount must be nonzero"),
|
||||
NonZeroU128::new(token_b_amount).expect("token_b_amount must be nonzero"),
|
||||
fees,
|
||||
amm_program_id,
|
||||
);
|
||||
Ok(SpelOutput::with_chained_calls(post_states, chained_calls))
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use std::num::NonZeroU128;
|
||||
|
||||
use amm_core::{compute_liquidity_token_pda_seed, PoolDefinition};
|
||||
use amm_core::{assert_supported_fee_tier, compute_liquidity_token_pda_seed, PoolDefinition};
|
||||
use nssa_core::{
|
||||
account::{AccountWithMetadata, Data},
|
||||
program::{AccountPostState, ChainedCall},
|
||||
@ -22,6 +22,7 @@ pub fn add_liquidity(
|
||||
// 1. Fetch Pool state
|
||||
let pool_def_data = PoolDefinition::try_from(&pool.account.data)
|
||||
.expect("Add liquidity: AMM Program expects valid Pool Definition Account");
|
||||
assert_supported_fee_tier(pool_def_data.fees);
|
||||
|
||||
assert_eq!(
|
||||
vault_a.account_id, pool_def_data.vault_a_id,
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
use std::num::NonZeroU128;
|
||||
|
||||
use amm_core::{
|
||||
compute_liquidity_token_pda, compute_liquidity_token_pda_seed, compute_lp_lock_holding_pda,
|
||||
compute_pool_pda, compute_vault_pda, PoolDefinition, MINIMUM_LIQUIDITY,
|
||||
assert_supported_fee_tier, compute_liquidity_token_pda, compute_liquidity_token_pda_seed,
|
||||
compute_lp_lock_holding_pda, compute_pool_pda, compute_vault_pda, PoolDefinition,
|
||||
MINIMUM_LIQUIDITY,
|
||||
};
|
||||
use nssa_core::{
|
||||
account::{Account, AccountWithMetadata, Data},
|
||||
@ -22,6 +23,7 @@ pub fn new_definition(
|
||||
user_holding_lp: AccountWithMetadata,
|
||||
token_a_amount: NonZeroU128,
|
||||
token_b_amount: NonZeroU128,
|
||||
fees: u128,
|
||||
amm_program_id: ProgramId,
|
||||
) -> (Vec<AccountPostState>, Vec<ChainedCall>) {
|
||||
// Verify token_a and token_b are different
|
||||
@ -68,6 +70,7 @@ pub fn new_definition(
|
||||
compute_lp_lock_holding_pda(amm_program_id, pool.account_id),
|
||||
"LP lock holding Account ID does not match PDA"
|
||||
);
|
||||
assert_supported_fee_tier(fees);
|
||||
|
||||
// TODO: return here
|
||||
// Verify that Pool Account is not active
|
||||
@ -113,7 +116,7 @@ pub fn new_definition(
|
||||
liquidity_pool_supply: initial_lp,
|
||||
reserve_a: token_a_amount.into(),
|
||||
reserve_b: token_b_amount.into(),
|
||||
fees: 0u128, // TODO: we assume all fees are 0 for now.
|
||||
fees,
|
||||
active: true,
|
||||
};
|
||||
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
use std::num::NonZeroU128;
|
||||
|
||||
use amm_core::{
|
||||
compute_liquidity_token_pda_seed, compute_vault_pda_seed, PoolDefinition, MINIMUM_LIQUIDITY,
|
||||
assert_supported_fee_tier, compute_liquidity_token_pda_seed, compute_vault_pda_seed,
|
||||
PoolDefinition, MINIMUM_LIQUIDITY,
|
||||
};
|
||||
use nssa_core::{
|
||||
account::{AccountWithMetadata, Data},
|
||||
@ -26,6 +27,7 @@ pub fn remove_liquidity(
|
||||
// 1. Fetch Pool state
|
||||
let pool_def_data = PoolDefinition::try_from(&pool.account.data)
|
||||
.expect("Remove liquidity: AMM Program expects a valid Pool Definition Account");
|
||||
assert_supported_fee_tier(pool_def_data.fees);
|
||||
|
||||
assert!(pool_def_data.active, "Pool is inactive");
|
||||
assert_eq!(
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
use amm_core::assert_supported_fee_tier;
|
||||
pub use amm_core::{compute_liquidity_token_pda_seed, compute_vault_pda_seed, PoolDefinition};
|
||||
use nssa_core::{
|
||||
account::{AccountId, AccountWithMetadata, Data},
|
||||
@ -12,6 +13,7 @@ fn validate_swap_setup(
|
||||
) -> PoolDefinition {
|
||||
let pool_def_data = PoolDefinition::try_from(&pool.account.data)
|
||||
.expect("AMM Program expects a valid Pool Definition Account");
|
||||
assert_supported_fee_tier(pool_def_data.fees);
|
||||
|
||||
assert!(pool_def_data.active, "Pool is inactive");
|
||||
assert_eq!(
|
||||
|
||||
134
amm/src/tests.rs
134
amm/src/tests.rs
@ -4,7 +4,8 @@ use std::num::NonZero;
|
||||
|
||||
use amm_core::{
|
||||
compute_liquidity_token_pda, compute_liquidity_token_pda_seed, compute_lp_lock_holding_pda,
|
||||
compute_pool_pda, compute_vault_pda, compute_vault_pda_seed, PoolDefinition, MINIMUM_LIQUIDITY,
|
||||
compute_pool_pda, compute_vault_pda, compute_vault_pda_seed, PoolDefinition, FEE_TIER_BPS_1,
|
||||
FEE_TIER_BPS_100, FEE_TIER_BPS_30, FEE_TIER_BPS_5, MINIMUM_LIQUIDITY,
|
||||
};
|
||||
use nssa_core::{
|
||||
account::{Account, AccountId, AccountWithMetadata, Data, Nonce},
|
||||
@ -30,6 +31,10 @@ struct AccountWithMetadataForTests;
|
||||
type AccountForTests = AccountWithMetadataForTests;
|
||||
|
||||
impl BalanceForTests {
|
||||
fn fee_tier() -> u128 {
|
||||
FEE_TIER_BPS_30
|
||||
}
|
||||
|
||||
fn vault_a_reserve_init() -> u128 {
|
||||
5_000
|
||||
}
|
||||
@ -869,7 +874,7 @@ impl AccountWithMetadataForTests {
|
||||
liquidity_pool_supply: BalanceForTests::lp_supply_init(),
|
||||
reserve_a: BalanceForTests::vault_a_reserve_init(),
|
||||
reserve_b: BalanceForTests::vault_b_reserve_init(),
|
||||
fees: 0u128,
|
||||
fees: BalanceForTests::fee_tier(),
|
||||
active: true,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -897,7 +902,7 @@ impl AccountWithMetadataForTests {
|
||||
liquidity_pool_supply: BalanceForTests::lp_supply_init(),
|
||||
reserve_a: 1_000,
|
||||
reserve_b: 500,
|
||||
fees: 0u128,
|
||||
fees: BalanceForTests::fee_tier(),
|
||||
active: true,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -921,7 +926,7 @@ impl AccountWithMetadataForTests {
|
||||
liquidity_pool_supply: BalanceForTests::lp_supply_init(),
|
||||
reserve_a: 0,
|
||||
reserve_b: BalanceForTests::vault_b_reserve_init(),
|
||||
fees: 0u128,
|
||||
fees: BalanceForTests::fee_tier(),
|
||||
active: true,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -945,7 +950,7 @@ impl AccountWithMetadataForTests {
|
||||
liquidity_pool_supply: BalanceForTests::lp_supply_init(),
|
||||
reserve_a: BalanceForTests::vault_a_reserve_init(),
|
||||
reserve_b: 0,
|
||||
fees: 0u128,
|
||||
fees: BalanceForTests::fee_tier(),
|
||||
active: true,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -969,7 +974,7 @@ impl AccountWithMetadataForTests {
|
||||
liquidity_pool_supply: BalanceForTests::vault_a_reserve_low(),
|
||||
reserve_a: BalanceForTests::vault_a_reserve_low(),
|
||||
reserve_b: BalanceForTests::vault_b_reserve_high(),
|
||||
fees: 0u128,
|
||||
fees: BalanceForTests::fee_tier(),
|
||||
active: true,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -993,7 +998,7 @@ impl AccountWithMetadataForTests {
|
||||
liquidity_pool_supply: BalanceForTests::vault_a_reserve_high(),
|
||||
reserve_a: BalanceForTests::vault_a_reserve_high(),
|
||||
reserve_b: BalanceForTests::vault_b_reserve_low(),
|
||||
fees: 0u128,
|
||||
fees: BalanceForTests::fee_tier(),
|
||||
active: true,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -1017,7 +1022,7 @@ impl AccountWithMetadataForTests {
|
||||
liquidity_pool_supply: BalanceForTests::lp_supply_init(),
|
||||
reserve_a: BalanceForTests::vault_a_swap_test_1(),
|
||||
reserve_b: BalanceForTests::vault_b_swap_test_1(),
|
||||
fees: 0u128,
|
||||
fees: BalanceForTests::fee_tier(),
|
||||
active: true,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -1041,7 +1046,7 @@ impl AccountWithMetadataForTests {
|
||||
liquidity_pool_supply: BalanceForTests::lp_supply_init(),
|
||||
reserve_a: BalanceForTests::vault_a_swap_test_2(),
|
||||
reserve_b: BalanceForTests::vault_b_swap_test_2(),
|
||||
fees: 0u128,
|
||||
fees: BalanceForTests::fee_tier(),
|
||||
active: true,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -1065,7 +1070,7 @@ impl AccountWithMetadataForTests {
|
||||
liquidity_pool_supply: BalanceForTests::lp_supply_init(),
|
||||
reserve_a: 1498_u128,
|
||||
reserve_b: 334_u128,
|
||||
fees: 0_u128,
|
||||
fees: BalanceForTests::fee_tier(),
|
||||
active: true,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -1089,7 +1094,7 @@ impl AccountWithMetadataForTests {
|
||||
liquidity_pool_supply: BalanceForTests::lp_supply_init(),
|
||||
reserve_a: 715_u128,
|
||||
reserve_b: 700_u128,
|
||||
fees: 0_u128,
|
||||
fees: BalanceForTests::fee_tier(),
|
||||
active: true,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -1113,7 +1118,7 @@ impl AccountWithMetadataForTests {
|
||||
liquidity_pool_supply: BalanceForTests::vault_a_reserve_low(),
|
||||
reserve_a: BalanceForTests::vault_a_reserve_init(),
|
||||
reserve_b: BalanceForTests::vault_b_reserve_init(),
|
||||
fees: 0u128,
|
||||
fees: BalanceForTests::fee_tier(),
|
||||
active: true,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -1137,7 +1142,7 @@ impl AccountWithMetadataForTests {
|
||||
liquidity_pool_supply: BalanceForTests::add_lp_supply_successful(),
|
||||
reserve_a: BalanceForTests::vault_a_add_successful(),
|
||||
reserve_b: BalanceForTests::vault_b_add_successful(),
|
||||
fees: 0u128,
|
||||
fees: BalanceForTests::fee_tier(),
|
||||
active: true,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -1161,7 +1166,7 @@ impl AccountWithMetadataForTests {
|
||||
liquidity_pool_supply: BalanceForTests::remove_lp_supply_successful(),
|
||||
reserve_a: BalanceForTests::vault_a_remove_successful(),
|
||||
reserve_b: BalanceForTests::vault_b_remove_successful(),
|
||||
fees: 0u128,
|
||||
fees: BalanceForTests::fee_tier(),
|
||||
active: true,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -1185,7 +1190,7 @@ impl AccountWithMetadataForTests {
|
||||
liquidity_pool_supply: BalanceForTests::lp_supply_init(),
|
||||
reserve_a: BalanceForTests::vault_a_reserve_init(),
|
||||
reserve_b: BalanceForTests::vault_b_reserve_init(),
|
||||
fees: 0u128,
|
||||
fees: BalanceForTests::fee_tier(),
|
||||
active: false,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -1233,7 +1238,7 @@ impl AccountWithMetadataForTests {
|
||||
liquidity_pool_supply: BalanceForTests::lp_supply_init(),
|
||||
reserve_a: BalanceForTests::vault_a_reserve_init(),
|
||||
reserve_b: BalanceForTests::vault_b_reserve_init(),
|
||||
fees: 0u128,
|
||||
fees: BalanceForTests::fee_tier(),
|
||||
active: false,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -1289,7 +1294,7 @@ impl AccountWithMetadataForTests {
|
||||
liquidity_pool_supply: BalanceForTests::lp_supply_init(),
|
||||
reserve_a: BalanceForTests::vault_a_reserve_init(),
|
||||
reserve_b: BalanceForTests::vault_b_reserve_init(),
|
||||
fees: 0u128,
|
||||
fees: BalanceForTests::fee_tier(),
|
||||
active: true,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -1315,7 +1320,7 @@ impl AccountWithMetadataForTests {
|
||||
liquidity_pool_supply: MINIMUM_LIQUIDITY,
|
||||
reserve_a: BalanceForTests::vault_a_reserve_init(),
|
||||
reserve_b: BalanceForTests::vault_b_reserve_init(),
|
||||
fees: 0u128,
|
||||
fees: BalanceForTests::fee_tier(),
|
||||
active: true,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -1801,6 +1806,7 @@ fn test_call_new_definition_with_zero_balance_1() {
|
||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||
NonZero::new(0).expect("Balances must be nonzero"),
|
||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||
BalanceForTests::fee_tier(),
|
||||
AMM_PROGRAM_ID,
|
||||
);
|
||||
}
|
||||
@ -1819,6 +1825,7 @@ fn test_call_new_definition_with_zero_balance_2() {
|
||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||
NonZero::new(0).expect("Balances must be nonzero"),
|
||||
BalanceForTests::fee_tier(),
|
||||
AMM_PROGRAM_ID,
|
||||
);
|
||||
}
|
||||
@ -1837,6 +1844,7 @@ fn test_call_new_definition_same_token_definition() {
|
||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||
BalanceForTests::fee_tier(),
|
||||
AMM_PROGRAM_ID,
|
||||
);
|
||||
}
|
||||
@ -1855,6 +1863,7 @@ fn test_call_new_definition_wrong_liquidity_id() {
|
||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||
BalanceForTests::fee_tier(),
|
||||
AMM_PROGRAM_ID,
|
||||
);
|
||||
}
|
||||
@ -1873,6 +1882,7 @@ fn test_call_new_definition_wrong_lp_lock_holding_id() {
|
||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||
BalanceForTests::fee_tier(),
|
||||
AMM_PROGRAM_ID,
|
||||
);
|
||||
}
|
||||
@ -1891,6 +1901,7 @@ fn test_call_new_definition_wrong_pool_id() {
|
||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||
BalanceForTests::fee_tier(),
|
||||
AMM_PROGRAM_ID,
|
||||
);
|
||||
}
|
||||
@ -1909,6 +1920,7 @@ fn test_call_new_definition_wrong_vault_id_1() {
|
||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||
BalanceForTests::fee_tier(),
|
||||
AMM_PROGRAM_ID,
|
||||
);
|
||||
}
|
||||
@ -1927,6 +1939,7 @@ fn test_call_new_definition_wrong_vault_id_2() {
|
||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||
BalanceForTests::fee_tier(),
|
||||
AMM_PROGRAM_ID,
|
||||
);
|
||||
}
|
||||
@ -1945,6 +1958,7 @@ fn test_call_new_definition_cannot_initialize_active_pool() {
|
||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||
BalanceForTests::fee_tier(),
|
||||
AMM_PROGRAM_ID,
|
||||
);
|
||||
}
|
||||
@ -1964,6 +1978,7 @@ fn test_call_new_definition_initial_lp_too_small() {
|
||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||
NonZero::new(MINIMUM_LIQUIDITY).unwrap(),
|
||||
NonZero::new(MINIMUM_LIQUIDITY).unwrap(),
|
||||
BalanceForTests::fee_tier(),
|
||||
AMM_PROGRAM_ID,
|
||||
);
|
||||
}
|
||||
@ -1981,6 +1996,7 @@ fn test_call_new_definition_chained_call_successful() {
|
||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||
BalanceForTests::fee_tier(),
|
||||
AMM_PROGRAM_ID,
|
||||
);
|
||||
|
||||
@ -2089,6 +2105,26 @@ fn test_call_swap_ianctive() {
|
||||
);
|
||||
}
|
||||
|
||||
#[should_panic(expected = "Fee tier must be one of 1, 5, 30, or 100 basis points")]
|
||||
#[test]
|
||||
fn test_call_swap_rejects_unsupported_fee_tier() {
|
||||
let mut pool = AccountWithMetadataForTests::pool_definition_init();
|
||||
let mut pool_def = PoolDefinition::try_from(&pool.account.data).unwrap();
|
||||
pool_def.fees = 2;
|
||||
pool.account.data = Data::from(&pool_def);
|
||||
|
||||
let _post_states = swap_exact_input(
|
||||
pool,
|
||||
AccountWithMetadataForTests::vault_a_init(),
|
||||
AccountWithMetadataForTests::vault_b_init(),
|
||||
AccountWithMetadataForTests::user_holding_a(),
|
||||
AccountWithMetadataForTests::user_holding_b(),
|
||||
BalanceForTests::add_max_amount_a(),
|
||||
BalanceForTests::add_max_amount_a_low(),
|
||||
IdForTests::token_a_definition_id(),
|
||||
);
|
||||
}
|
||||
|
||||
#[should_panic(expected = "Withdraw amount is less than minimal amount out")]
|
||||
#[test]
|
||||
fn test_call_swap_below_min_out() {
|
||||
@ -2393,7 +2429,7 @@ fn swap_exact_output_overflow_protection() {
|
||||
liquidity_pool_supply: 1,
|
||||
reserve_a: large_reserve,
|
||||
reserve_b,
|
||||
fees: 0,
|
||||
fees: BalanceForTests::fee_tier(),
|
||||
active: true,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -2456,6 +2492,7 @@ fn test_new_definition_lp_asymmetric_amounts() {
|
||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||
BalanceForTests::fee_tier(),
|
||||
AMM_PROGRAM_ID,
|
||||
);
|
||||
|
||||
@ -2492,6 +2529,7 @@ fn test_new_definition_lp_symmetric_amounts() {
|
||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||
NonZero::new(token_a_amount).unwrap(),
|
||||
NonZero::new(token_b_amount).unwrap(),
|
||||
BalanceForTests::fee_tier(),
|
||||
AMM_PROGRAM_ID,
|
||||
);
|
||||
|
||||
@ -2552,6 +2590,7 @@ fn test_call_new_definition_reinitialization_requires_zero_pool_supply() {
|
||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||
BalanceForTests::fee_tier(),
|
||||
AMM_PROGRAM_ID,
|
||||
);
|
||||
}
|
||||
@ -2572,6 +2611,7 @@ fn test_call_new_definition_reinitialization_requires_zero_lp_definition_supply(
|
||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||
BalanceForTests::fee_tier(),
|
||||
AMM_PROGRAM_ID,
|
||||
);
|
||||
}
|
||||
@ -2599,6 +2639,7 @@ fn test_minimum_liquidity_lock_and_remove_all_user_lp() {
|
||||
AccountForTests::user_holding_lp_uninit(),
|
||||
NonZero::new(token_a_amount).unwrap(),
|
||||
NonZero::new(token_b_amount).unwrap(),
|
||||
BalanceForTests::fee_tier(),
|
||||
AMM_PROGRAM_ID,
|
||||
);
|
||||
|
||||
@ -2791,6 +2832,7 @@ fn new_definition_overflow_protection() {
|
||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||
NonZero::new(large_amount).unwrap(),
|
||||
NonZero::new(2).unwrap(),
|
||||
BalanceForTests::fee_tier(),
|
||||
AMM_PROGRAM_ID,
|
||||
);
|
||||
}
|
||||
@ -2814,7 +2856,7 @@ fn add_liquidity_overflow_protection() {
|
||||
liquidity_pool_supply: 1_000,
|
||||
reserve_a: large_reserve,
|
||||
reserve_b,
|
||||
fees: 0,
|
||||
fees: BalanceForTests::fee_tier(),
|
||||
active: true,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -2885,7 +2927,7 @@ fn remove_liquidity_overflow_protection() {
|
||||
liquidity_pool_supply: lp_supply,
|
||||
reserve_a: large_reserve,
|
||||
reserve_b,
|
||||
fees: 0,
|
||||
fees: BalanceForTests::fee_tier(),
|
||||
active: true,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -2969,7 +3011,7 @@ fn swap_exact_input_overflow_protection() {
|
||||
liquidity_pool_supply: 1,
|
||||
reserve_a: 1_000,
|
||||
reserve_b: large_reserve,
|
||||
fees: 0,
|
||||
fees: BalanceForTests::fee_tier(),
|
||||
active: true,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -3019,3 +3061,51 @@ fn swap_exact_input_overflow_protection() {
|
||||
IdForTests::token_a_definition_id(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_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 (post_states, _) = new_definition(
|
||||
AccountWithMetadataForTests::pool_definition_reinitializable(),
|
||||
AccountWithMetadataForTests::vault_a_init(),
|
||||
AccountWithMetadataForTests::vault_b_init(),
|
||||
AccountWithMetadataForTests::pool_lp_reinitializable(),
|
||||
AccountWithMetadataForTests::lp_lock_holding_uninit(),
|
||||
AccountWithMetadataForTests::user_holding_a(),
|
||||
AccountWithMetadataForTests::user_holding_b(),
|
||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||
fees,
|
||||
AMM_PROGRAM_ID,
|
||||
);
|
||||
|
||||
let pool_post = post_states[0].clone();
|
||||
let pool_def = PoolDefinition::try_from(&pool_post.account().data).unwrap();
|
||||
assert_eq!(pool_def.fees, fees);
|
||||
}
|
||||
}
|
||||
|
||||
#[should_panic(expected = "Fee tier must be one of 1, 5, 30, or 100 basis points")]
|
||||
#[test]
|
||||
fn test_new_definition_rejects_unsupported_fee_tier() {
|
||||
let _ = new_definition(
|
||||
AccountWithMetadataForTests::pool_definition_inactive(),
|
||||
AccountWithMetadataForTests::vault_a_init(),
|
||||
AccountWithMetadataForTests::vault_b_init(),
|
||||
AccountWithMetadataForTests::pool_lp_init(),
|
||||
AccountWithMetadataForTests::lp_lock_holding_uninit(),
|
||||
AccountWithMetadataForTests::user_holding_a(),
|
||||
AccountWithMetadataForTests::user_holding_b(),
|
||||
AccountWithMetadataForTests::user_holding_lp_uninit(),
|
||||
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
|
||||
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
|
||||
2,
|
||||
AMM_PROGRAM_ID,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
use amm_core::{PoolDefinition, MINIMUM_LIQUIDITY};
|
||||
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,
|
||||
};
|
||||
@ -88,6 +92,10 @@ impl Ids {
|
||||
}
|
||||
|
||||
impl Balances {
|
||||
fn fee_tier() -> u128 {
|
||||
FEE_TIER_BPS_30
|
||||
}
|
||||
|
||||
fn user_a_init() -> u128 {
|
||||
10_000
|
||||
}
|
||||
@ -291,7 +299,7 @@ impl Accounts {
|
||||
liquidity_pool_supply: Balances::pool_lp_supply_init(),
|
||||
reserve_a: Balances::vault_a_init(),
|
||||
reserve_b: Balances::vault_b_init(),
|
||||
fees: 0_u128,
|
||||
fees: Balances::fee_tier(),
|
||||
active: true,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -400,7 +408,7 @@ impl Accounts {
|
||||
liquidity_pool_supply: Balances::pool_lp_supply_init(),
|
||||
reserve_a: Balances::vault_a_swap_1(),
|
||||
reserve_b: Balances::vault_b_swap_1(),
|
||||
fees: 0_u128,
|
||||
fees: Balances::fee_tier(),
|
||||
active: true,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -468,7 +476,7 @@ impl Accounts {
|
||||
liquidity_pool_supply: Balances::pool_lp_supply_init(),
|
||||
reserve_a: Balances::vault_a_swap_2(),
|
||||
reserve_b: Balances::vault_b_swap_2(),
|
||||
fees: 0_u128,
|
||||
fees: Balances::fee_tier(),
|
||||
active: true,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -536,7 +544,7 @@ impl Accounts {
|
||||
liquidity_pool_supply: Balances::token_lp_supply_add(),
|
||||
reserve_a: Balances::vault_a_add(),
|
||||
reserve_b: Balances::vault_b_add(),
|
||||
fees: 0_u128,
|
||||
fees: Balances::fee_tier(),
|
||||
active: true,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -629,7 +637,7 @@ impl Accounts {
|
||||
liquidity_pool_supply: Balances::token_lp_supply_remove(),
|
||||
reserve_a: Balances::vault_a_remove(),
|
||||
reserve_b: Balances::vault_b_remove(),
|
||||
fees: 0_u128,
|
||||
fees: Balances::fee_tier(),
|
||||
active: true,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -759,7 +767,7 @@ impl Accounts {
|
||||
liquidity_pool_supply: 0,
|
||||
reserve_a: 0,
|
||||
reserve_b: 0,
|
||||
fees: 0_u128,
|
||||
fees: Balances::fee_tier(),
|
||||
active: false,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -840,7 +848,7 @@ impl Accounts {
|
||||
liquidity_pool_supply: Balances::lp_supply_init(),
|
||||
reserve_a: Balances::vault_a_init(),
|
||||
reserve_b: Balances::vault_b_init(),
|
||||
fees: 0_u128,
|
||||
fees: Balances::fee_tier(),
|
||||
active: true,
|
||||
}),
|
||||
nonce: Nonce(0),
|
||||
@ -917,6 +925,42 @@ fn state_for_amm_tests_with_new_def() -> V03State {
|
||||
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();
|
||||
@ -1022,34 +1066,7 @@ fn amm_new_definition_inactive_initialized_pool_and_uninit_user_lp() {
|
||||
Accounts::token_lp_definition_init_inactive(),
|
||||
);
|
||||
|
||||
let instruction = amm_core::Instruction::NewDefinition {
|
||||
token_a_amount: Balances::vault_a_init(),
|
||||
token_b_amount: Balances::vault_b_init(),
|
||||
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).unwrap();
|
||||
execute_new_definition(&mut state, Balances::fee_tier());
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::pool_definition()),
|
||||
@ -1097,34 +1114,7 @@ fn amm_new_definition_inactive_initialized_pool_init_user_lp() {
|
||||
);
|
||||
state.force_insert_account(Ids::user_lp(), Accounts::user_lp_holding_init_zero());
|
||||
|
||||
let instruction = amm_core::Instruction::NewDefinition {
|
||||
token_a_amount: Balances::vault_a_init(),
|
||||
token_b_amount: Balances::vault_b_init(),
|
||||
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).unwrap();
|
||||
execute_new_definition(&mut state, Balances::fee_tier());
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::pool_definition()),
|
||||
@ -1166,34 +1156,7 @@ fn amm_new_definition_uninitialized_pool() {
|
||||
state.force_insert_account(Ids::vault_a(), Accounts::vault_a_init_inactive());
|
||||
state.force_insert_account(Ids::vault_b(), Accounts::vault_b_init_inactive());
|
||||
|
||||
let instruction = amm_core::Instruction::NewDefinition {
|
||||
token_a_amount: Balances::vault_a_init(),
|
||||
token_b_amount: Balances::vault_b_init(),
|
||||
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).unwrap();
|
||||
execute_new_definition(&mut state, Balances::fee_tier());
|
||||
|
||||
assert_eq!(
|
||||
state.get_account_by_id(Ids::pool_definition()),
|
||||
@ -1229,6 +1192,72 @@ fn amm_new_definition_uninitialized_pool() {
|
||||
);
|
||||
}
|
||||
|
||||
#[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();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user