refactor(amm)!: derive pool active state from LP supply instead of explicit flag

Remove the `active: bool` field from `PoolDefinition` and replace it with an
implicit invariant: a pool is considered active when
`liquidity_pool_supply >= MINIMUM_LIQUIDITY`.

BREAKING CHANGE: `PoolDefinition` Borsh serialization format has changed.
Existing on-chain pool accounts encoded with the `active` field are
incompatible with this version.

Closes #25
This commit is contained in:
Ricardo Guilherme Schmidt 2026-04-10 15:43:13 -03:00 committed by r4bbit
parent 94f14ae305
commit 4a9a441ccd
8 changed files with 96 additions and 137 deletions

View File

@ -15,7 +15,7 @@ const LP_LOCK_HOLDING_PDA_SEED: [u8; 32] = [1; 32];
/// AMM Program Instruction. /// AMM Program Instruction.
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub enum Instruction { pub enum Instruction {
/// Initializes a new Pool (or re-initializes an inactive Pool). /// Initializes a new Pool (or re-initializes an existing zero-supply Pool).
/// ///
/// On initialization, `MINIMUM_LIQUIDITY` LP tokens are permanently locked /// On initialization, `MINIMUM_LIQUIDITY` LP tokens are permanently locked
/// in the LP-lock holding PDA; the caller receives `initial_lp - MINIMUM_LIQUIDITY`. /// in the LP-lock holding PDA; the caller receives `initial_lp - MINIMUM_LIQUIDITY`.
@ -104,7 +104,7 @@ pub enum Instruction {
/// Sync pool reserves with current vault balances. /// Sync pool reserves with current vault balances.
/// ///
/// Required accounts: /// Required accounts:
/// - AMM Pool (initialized, active) /// - AMM Pool (initialized, with LP supply at or above minimum liquidity)
/// - Vault Holding Account for Token A (initialized) /// - Vault Holding Account for Token A (initialized)
/// - Vault Holding Account for Token B (initialized) /// - Vault Holding Account for Token B (initialized)
SyncReserves, SyncReserves,
@ -119,19 +119,13 @@ pub struct PoolDefinition {
pub vault_a_id: AccountId, pub vault_a_id: AccountId,
pub vault_b_id: AccountId, pub vault_b_id: AccountId,
pub liquidity_pool_id: AccountId, pub liquidity_pool_id: AccountId,
/// Total LP supply tracked by the pool. After initialization it includes the permanently
/// locked `MINIMUM_LIQUIDITY`; a zero supply means the pool is uninitialized
pub liquidity_pool_supply: u128, pub liquidity_pool_supply: u128,
pub reserve_a: u128, pub reserve_a: u128,
pub reserve_b: u128, pub reserve_b: u128,
/// Fee tier in basis points. /// Fee tier in basis points.
pub fees: u128, pub fees: u128,
/// Indicates whether the pool is initialized for use.
/// `MINIMUM_LIQUIDITY` LP tokens are permanently locked at initialization
/// and cannot be removed, so `liquidity_pool_supply` will never drop below
/// `MINIMUM_LIQUIDITY` for pools created after the minimum-liquidity lock
/// was introduced. Reaching that floor does not by itself imply
/// `active = false`; pools may remain active with only the permanently
/// locked minimum liquidity remaining.
pub active: bool,
} }
pub const FEE_BPS_DENOMINATOR: u128 = 10_000; pub const FEE_BPS_DENOMINATOR: u128 = 10_000;

View File

@ -15,7 +15,7 @@ mod amm {
#[allow(unused_imports)] #[allow(unused_imports)]
use super::*; use super::*;
/// Initializes a new Pool (or re-initializes an inactive Pool). /// Initializes a new Pool (or re-initializes an existing zero-supply Pool).
#[instruction] #[instruction]
pub fn new_definition( pub fn new_definition(
pool: AccountWithMetadata, pool: AccountWithMetadata,

View File

@ -73,7 +73,7 @@ pub fn new_definition(
assert_supported_fee_tier(fees); assert_supported_fee_tier(fees);
// TODO: return here // TODO: return here
// Verify that Pool Account is not active // A pool can only be initialized from a fresh account state.
let is_new_pool = pool.account == Account::default(); let is_new_pool = pool.account == Account::default();
let pool_account_data = if is_new_pool { let pool_account_data = if is_new_pool {
PoolDefinition::default() PoolDefinition::default()
@ -82,16 +82,10 @@ pub fn new_definition(
.expect("AMM program expects a valid Pool account") .expect("AMM program expects a valid Pool account")
}; };
assert!( assert_eq!(
!pool_account_data.active, pool_account_data.liquidity_pool_supply, 0,
"Cannot initialize an active Pool Definition" "Cannot initialize a Pool Definition with nonzero LP supply"
); );
if !is_new_pool {
assert_eq!(
pool_account_data.liquidity_pool_supply, 0,
"New definition: inactive Pool Definition must have zero LP supply before reinitialization"
);
}
// LP Token minting calculation // LP Token minting calculation
let initial_lp = token_a_amount let initial_lp = token_a_amount
@ -117,11 +111,10 @@ pub fn new_definition(
reserve_a: token_a_amount.into(), reserve_a: token_a_amount.into(),
reserve_b: token_b_amount.into(), reserve_b: token_b_amount.into(),
fees, fees,
active: true,
}; };
pool_post.data = Data::from(&pool_post_definition); pool_post.data = Data::from(&pool_post_definition);
let pool_post: AccountPostState = if pool.account == Account::default() { let pool_post: AccountPostState = if is_new_pool {
AccountPostState::new_claimed(pool_post.clone()) AccountPostState::new_claimed(pool_post.clone())
} else { } else {
AccountPostState::new(pool_post.clone()) AccountPostState::new(pool_post.clone())

View File

@ -29,7 +29,10 @@ pub fn remove_liquidity(
.expect("Remove liquidity: AMM Program expects a valid Pool Definition Account"); .expect("Remove liquidity: AMM Program expects a valid Pool Definition Account");
assert_supported_fee_tier(pool_def_data.fees); assert_supported_fee_tier(pool_def_data.fees);
assert!(pool_def_data.active, "Pool is inactive"); assert!(
pool_def_data.liquidity_pool_supply >= MINIMUM_LIQUIDITY,
"Pool liquidity supply is below minimum liquidity"
);
assert_eq!( assert_eq!(
pool_def_data.liquidity_pool_id, pool_definition_lp.account_id, pool_def_data.liquidity_pool_id, pool_definition_lp.account_id,
"LP definition mismatch" "LP definition mismatch"
@ -135,7 +138,6 @@ pub fn remove_liquidity(
.reserve_b .reserve_b
.checked_sub(withdraw_amount_b) .checked_sub(withdraw_amount_b)
.expect("reserve_b - withdraw_amount_b underflows"), .expect("reserve_b - withdraw_amount_b underflows"),
active: true,
..pool_def_data.clone() ..pool_def_data.clone()
}; };

View File

@ -1,11 +1,11 @@
use amm_core::assert_supported_fee_tier; use amm_core::{assert_supported_fee_tier, MINIMUM_LIQUIDITY};
pub use amm_core::{compute_liquidity_token_pda_seed, compute_vault_pda_seed, PoolDefinition}; pub use amm_core::{compute_liquidity_token_pda_seed, compute_vault_pda_seed, PoolDefinition};
use nssa_core::{ use nssa_core::{
account::{AccountId, AccountWithMetadata, Data}, account::{AccountId, AccountWithMetadata, Data},
program::{AccountPostState, ChainedCall}, program::{AccountPostState, ChainedCall},
}; };
/// Validates swap setup: checks pool is active, vaults match, and reserves are sufficient. /// Validates swap setup: checks pool liquidity is ready, vaults match, and reserves are sufficient.
fn validate_swap_setup( fn validate_swap_setup(
pool: &AccountWithMetadata, pool: &AccountWithMetadata,
vault_a: &AccountWithMetadata, vault_a: &AccountWithMetadata,
@ -15,7 +15,10 @@ fn validate_swap_setup(
.expect("AMM Program expects a valid Pool Definition Account"); .expect("AMM Program expects a valid Pool Definition Account");
assert_supported_fee_tier(pool_def_data.fees); assert_supported_fee_tier(pool_def_data.fees);
assert!(pool_def_data.active, "Pool is inactive"); assert!(
pool_def_data.liquidity_pool_supply >= MINIMUM_LIQUIDITY,
"Pool liquidity supply is below minimum liquidity"
);
assert_eq!( assert_eq!(
vault_a.account_id, pool_def_data.vault_a_id, vault_a.account_id, pool_def_data.vault_a_id,
"Vault A was not provided" "Vault A was not provided"

View File

@ -1,4 +1,4 @@
use amm_core::{read_vault_fungible_balances, PoolDefinition}; use amm_core::{read_vault_fungible_balances, PoolDefinition, MINIMUM_LIQUIDITY};
use nssa_core::{ use nssa_core::{
account::{AccountWithMetadata, Data}, account::{AccountWithMetadata, Data},
program::{AccountPostState, ChainedCall}, program::{AccountPostState, ChainedCall},
@ -12,7 +12,10 @@ pub fn sync_reserves(
let pool_def_data = PoolDefinition::try_from(&pool.account.data) let pool_def_data = PoolDefinition::try_from(&pool.account.data)
.expect("Sync reserves: AMM Program expects a valid Pool Definition Account"); .expect("Sync reserves: AMM Program expects a valid Pool Definition Account");
assert!(pool_def_data.active, "Pool is inactive"); assert!(
pool_def_data.liquidity_pool_supply >= MINIMUM_LIQUIDITY,
"Pool liquidity supply is below minimum liquidity"
);
assert_eq!( assert_eq!(
vault_a.account_id, pool_def_data.vault_a_id, vault_a.account_id, pool_def_data.vault_a_id,
"Vault A was not provided" "Vault A was not provided"

View File

@ -875,7 +875,6 @@ impl AccountWithMetadataForTests {
reserve_a: BalanceForTests::vault_a_reserve_init(), reserve_a: BalanceForTests::vault_a_reserve_init(),
reserve_b: BalanceForTests::vault_b_reserve_init(), reserve_b: BalanceForTests::vault_b_reserve_init(),
fees: BalanceForTests::fee_tier(), fees: BalanceForTests::fee_tier(),
active: true,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
}, },
@ -903,7 +902,6 @@ impl AccountWithMetadataForTests {
reserve_a: 1_000, reserve_a: 1_000,
reserve_b: 500, reserve_b: 500,
fees: BalanceForTests::fee_tier(), fees: BalanceForTests::fee_tier(),
active: true,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
}, },
@ -927,7 +925,6 @@ impl AccountWithMetadataForTests {
reserve_a: 0, reserve_a: 0,
reserve_b: BalanceForTests::vault_b_reserve_init(), reserve_b: BalanceForTests::vault_b_reserve_init(),
fees: BalanceForTests::fee_tier(), fees: BalanceForTests::fee_tier(),
active: true,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
}, },
@ -951,7 +948,6 @@ impl AccountWithMetadataForTests {
reserve_a: BalanceForTests::vault_a_reserve_init(), reserve_a: BalanceForTests::vault_a_reserve_init(),
reserve_b: 0, reserve_b: 0,
fees: BalanceForTests::fee_tier(), fees: BalanceForTests::fee_tier(),
active: true,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
}, },
@ -975,7 +971,6 @@ impl AccountWithMetadataForTests {
reserve_a: BalanceForTests::vault_a_reserve_low(), reserve_a: BalanceForTests::vault_a_reserve_low(),
reserve_b: BalanceForTests::vault_b_reserve_high(), reserve_b: BalanceForTests::vault_b_reserve_high(),
fees: BalanceForTests::fee_tier(), fees: BalanceForTests::fee_tier(),
active: true,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
}, },
@ -999,7 +994,6 @@ impl AccountWithMetadataForTests {
reserve_a: BalanceForTests::vault_a_reserve_high(), reserve_a: BalanceForTests::vault_a_reserve_high(),
reserve_b: BalanceForTests::vault_b_reserve_low(), reserve_b: BalanceForTests::vault_b_reserve_low(),
fees: BalanceForTests::fee_tier(), fees: BalanceForTests::fee_tier(),
active: true,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
}, },
@ -1023,7 +1017,6 @@ impl AccountWithMetadataForTests {
reserve_a: BalanceForTests::vault_a_swap_test_1(), reserve_a: BalanceForTests::vault_a_swap_test_1(),
reserve_b: BalanceForTests::vault_b_swap_test_1(), reserve_b: BalanceForTests::vault_b_swap_test_1(),
fees: BalanceForTests::fee_tier(), fees: BalanceForTests::fee_tier(),
active: true,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
}, },
@ -1047,7 +1040,6 @@ impl AccountWithMetadataForTests {
reserve_a: BalanceForTests::vault_a_swap_test_2(), reserve_a: BalanceForTests::vault_a_swap_test_2(),
reserve_b: BalanceForTests::vault_b_swap_test_2(), reserve_b: BalanceForTests::vault_b_swap_test_2(),
fees: BalanceForTests::fee_tier(), fees: BalanceForTests::fee_tier(),
active: true,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
}, },
@ -1071,7 +1063,6 @@ impl AccountWithMetadataForTests {
reserve_a: 1498_u128, reserve_a: 1498_u128,
reserve_b: 334_u128, reserve_b: 334_u128,
fees: BalanceForTests::fee_tier(), fees: BalanceForTests::fee_tier(),
active: true,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
}, },
@ -1095,7 +1086,6 @@ impl AccountWithMetadataForTests {
reserve_a: 715_u128, reserve_a: 715_u128,
reserve_b: 700_u128, reserve_b: 700_u128,
fees: BalanceForTests::fee_tier(), fees: BalanceForTests::fee_tier(),
active: true,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
}, },
@ -1119,7 +1109,6 @@ impl AccountWithMetadataForTests {
reserve_a: BalanceForTests::vault_a_reserve_init(), reserve_a: BalanceForTests::vault_a_reserve_init(),
reserve_b: BalanceForTests::vault_b_reserve_init(), reserve_b: BalanceForTests::vault_b_reserve_init(),
fees: BalanceForTests::fee_tier(), fees: BalanceForTests::fee_tier(),
active: true,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
}, },
@ -1143,7 +1132,6 @@ impl AccountWithMetadataForTests {
reserve_a: BalanceForTests::vault_a_add_successful(), reserve_a: BalanceForTests::vault_a_add_successful(),
reserve_b: BalanceForTests::vault_b_add_successful(), reserve_b: BalanceForTests::vault_b_add_successful(),
fees: BalanceForTests::fee_tier(), fees: BalanceForTests::fee_tier(),
active: true,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
}, },
@ -1167,7 +1155,6 @@ impl AccountWithMetadataForTests {
reserve_a: BalanceForTests::vault_a_remove_successful(), reserve_a: BalanceForTests::vault_a_remove_successful(),
reserve_b: BalanceForTests::vault_b_remove_successful(), reserve_b: BalanceForTests::vault_b_remove_successful(),
fees: BalanceForTests::fee_tier(), fees: BalanceForTests::fee_tier(),
active: true,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
}, },
@ -1176,7 +1163,7 @@ impl AccountWithMetadataForTests {
} }
} }
fn pool_definition_inactive() -> AccountWithMetadata { fn pool_definition_below_minimum_liquidity() -> AccountWithMetadata {
AccountWithMetadata { AccountWithMetadata {
account: Account { account: Account {
program_owner: ProgramId::default(), program_owner: ProgramId::default(),
@ -1187,11 +1174,10 @@ impl AccountWithMetadataForTests {
vault_a_id: IdForTests::vault_a_id(), vault_a_id: IdForTests::vault_a_id(),
vault_b_id: IdForTests::vault_b_id(), vault_b_id: IdForTests::vault_b_id(),
liquidity_pool_id: IdForTests::token_lp_definition_id(), liquidity_pool_id: IdForTests::token_lp_definition_id(),
liquidity_pool_supply: BalanceForTests::lp_supply_init(), liquidity_pool_supply: MINIMUM_LIQUIDITY - 1,
reserve_a: BalanceForTests::vault_a_reserve_init(), reserve_a: BalanceForTests::vault_a_reserve_init(),
reserve_b: BalanceForTests::vault_b_reserve_init(), reserve_b: BalanceForTests::vault_b_reserve_init(),
fees: BalanceForTests::fee_tier(), fees: BalanceForTests::fee_tier(),
active: false,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
}, },
@ -1200,7 +1186,7 @@ impl AccountWithMetadataForTests {
} }
} }
fn pool_definition_reinitializable() -> AccountWithMetadata { fn pool_definition_zero_supply_reinitializable() -> AccountWithMetadata {
AccountWithMetadata { AccountWithMetadata {
account: Account { account: Account {
program_owner: ProgramId::default(), program_owner: ProgramId::default(),
@ -1215,7 +1201,6 @@ impl AccountWithMetadataForTests {
reserve_a: 0, reserve_a: 0,
reserve_b: 0, reserve_b: 0,
fees: 0u128, fees: 0u128,
active: false,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
}, },
@ -1239,7 +1224,6 @@ impl AccountWithMetadataForTests {
reserve_a: BalanceForTests::vault_a_reserve_init(), reserve_a: BalanceForTests::vault_a_reserve_init(),
reserve_b: BalanceForTests::vault_b_reserve_init(), reserve_b: BalanceForTests::vault_b_reserve_init(),
fees: BalanceForTests::fee_tier(), fees: BalanceForTests::fee_tier(),
active: false,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
}, },
@ -1280,30 +1264,6 @@ impl AccountWithMetadataForTests {
} }
} }
fn pool_definition_active() -> AccountWithMetadata {
AccountWithMetadata {
account: Account {
program_owner: ProgramId::default(),
balance: 0u128,
data: Data::from(&PoolDefinition {
definition_token_a_id: IdForTests::token_a_definition_id(),
definition_token_b_id: IdForTests::token_b_definition_id(),
vault_a_id: IdForTests::vault_a_id(),
vault_b_id: IdForTests::vault_b_id(),
liquidity_pool_id: IdForTests::token_lp_definition_id(),
liquidity_pool_supply: BalanceForTests::lp_supply_init(),
reserve_a: BalanceForTests::vault_a_reserve_init(),
reserve_b: BalanceForTests::vault_b_reserve_init(),
fees: BalanceForTests::fee_tier(),
active: true,
}),
nonce: Nonce(0),
},
is_authorized: true,
account_id: IdForTests::pool_definition_id(),
}
}
/// Legacy/corrupted pool state whose reported supply has already been drained down to the /// Legacy/corrupted pool state whose reported supply has already been drained down to the
/// permanent lock (liquidity_pool_supply == MINIMUM_LIQUIDITY). /// permanent lock (liquidity_pool_supply == MINIMUM_LIQUIDITY).
fn pool_definition_at_minimum_liquidity() -> AccountWithMetadata { fn pool_definition_at_minimum_liquidity() -> AccountWithMetadata {
@ -1321,7 +1281,6 @@ impl AccountWithMetadataForTests {
reserve_a: BalanceForTests::vault_a_reserve_init(), reserve_a: BalanceForTests::vault_a_reserve_init(),
reserve_b: BalanceForTests::vault_b_reserve_init(), reserve_b: BalanceForTests::vault_b_reserve_init(),
fees: BalanceForTests::fee_tier(), fees: BalanceForTests::fee_tier(),
active: true,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
}, },
@ -1944,11 +1903,11 @@ fn test_call_new_definition_wrong_vault_id_2() {
); );
} }
#[should_panic(expected = "Cannot initialize an active Pool Definition")] #[should_panic(expected = "Cannot initialize a Pool Definition with nonzero LP supply")]
#[test] #[test]
fn test_call_new_definition_cannot_initialize_active_pool() { fn test_call_new_definition_cannot_initialize_nonzero_supply_pool() {
let _post_states = new_definition( let _post_states = new_definition(
AccountWithMetadataForTests::pool_definition_active(), AccountWithMetadataForTests::pool_definition_init(),
AccountWithMetadataForTests::vault_a_init(), AccountWithMetadataForTests::vault_a_init(),
AccountWithMetadataForTests::vault_b_init(), AccountWithMetadataForTests::vault_b_init(),
AccountWithMetadataForTests::pool_lp_init(), AccountWithMetadataForTests::pool_lp_init(),
@ -1968,7 +1927,7 @@ fn test_call_new_definition_cannot_initialize_active_pool() {
fn test_call_new_definition_initial_lp_too_small() { fn test_call_new_definition_initial_lp_too_small() {
// isqrt(1000 * 1000) = 1000 == MINIMUM_LIQUIDITY, so the assertion fires. // isqrt(1000 * 1000) = 1000 == MINIMUM_LIQUIDITY, so the assertion fires.
let _post_states = new_definition( let _post_states = new_definition(
AccountWithMetadataForTests::pool_definition_reinitializable(), AccountWithMetadataForTests::pool_definition_zero_supply_reinitializable(),
AccountWithMetadataForTests::vault_a_init(), AccountWithMetadataForTests::vault_a_init(),
AccountWithMetadataForTests::vault_b_init(), AccountWithMetadataForTests::vault_b_init(),
AccountWithMetadataForTests::pool_lp_reinitializable(), AccountWithMetadataForTests::pool_lp_reinitializable(),
@ -1986,7 +1945,7 @@ fn test_call_new_definition_initial_lp_too_small() {
#[test] #[test]
fn test_call_new_definition_chained_call_successful() { fn test_call_new_definition_chained_call_successful() {
let (post_states, chained_calls) = new_definition( let (post_states, chained_calls) = new_definition(
AccountWithMetadataForTests::pool_definition_reinitializable(), AccountWithMetadataForTests::pool_definition_zero_supply_reinitializable(),
AccountWithMetadataForTests::vault_a_init(), AccountWithMetadataForTests::vault_a_init(),
AccountWithMetadataForTests::vault_b_init(), AccountWithMetadataForTests::vault_b_init(),
AccountWithMetadataForTests::pool_lp_reinitializable(), AccountWithMetadataForTests::pool_lp_reinitializable(),
@ -2090,11 +2049,11 @@ fn test_call_swap_reserves_vault_mismatch_2() {
); );
} }
#[should_panic(expected = "Pool is inactive")] #[should_panic(expected = "Pool liquidity supply is below minimum liquidity")]
#[test] #[test]
fn test_call_swap_ianctive() { fn test_call_swap_below_minimum_liquidity() {
let _post_states = swap_exact_input( let _post_states = swap_exact_input(
AccountWithMetadataForTests::pool_definition_inactive(), AccountWithMetadataForTests::pool_definition_below_minimum_liquidity(),
AccountWithMetadataForTests::vault_a_init(), AccountWithMetadataForTests::vault_a_init(),
AccountWithMetadataForTests::vault_b_init(), AccountWithMetadataForTests::vault_b_init(),
AccountWithMetadataForTests::user_holding_a(), AccountWithMetadataForTests::user_holding_a(),
@ -2279,11 +2238,11 @@ fn call_swap_exact_output_reserves_vault_mismatch_2() {
); );
} }
#[should_panic(expected = "Pool is inactive")] #[should_panic(expected = "Pool liquidity supply is below minimum liquidity")]
#[test] #[test]
fn call_swap_exact_output_inactive() { fn call_swap_exact_output_below_minimum_liquidity() {
let _post_states = swap_exact_output( let _post_states = swap_exact_output(
AccountWithMetadataForTests::pool_definition_inactive(), AccountWithMetadataForTests::pool_definition_below_minimum_liquidity(),
AccountWithMetadataForTests::vault_a_init(), AccountWithMetadataForTests::vault_a_init(),
AccountWithMetadataForTests::vault_b_init(), AccountWithMetadataForTests::vault_b_init(),
AccountWithMetadataForTests::user_holding_a(), AccountWithMetadataForTests::user_holding_a(),
@ -2426,11 +2385,10 @@ fn swap_exact_output_overflow_protection() {
vault_a_id: IdForTests::vault_a_id(), vault_a_id: IdForTests::vault_a_id(),
vault_b_id: IdForTests::vault_b_id(), vault_b_id: IdForTests::vault_b_id(),
liquidity_pool_id: IdForTests::token_lp_definition_id(), liquidity_pool_id: IdForTests::token_lp_definition_id(),
liquidity_pool_supply: 1, liquidity_pool_supply: MINIMUM_LIQUIDITY,
reserve_a: large_reserve, reserve_a: large_reserve,
reserve_b, reserve_b,
fees: BalanceForTests::fee_tier(), fees: BalanceForTests::fee_tier(),
active: true,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
}, },
@ -2482,7 +2440,7 @@ fn swap_exact_output_overflow_protection() {
#[test] #[test]
fn test_new_definition_lp_asymmetric_amounts() { fn test_new_definition_lp_asymmetric_amounts() {
let (post_states, chained_calls) = new_definition( let (post_states, chained_calls) = new_definition(
AccountWithMetadataForTests::pool_definition_reinitializable(), AccountWithMetadataForTests::pool_definition_zero_supply_reinitializable(),
AccountWithMetadataForTests::vault_a_init(), AccountWithMetadataForTests::vault_a_init(),
AccountWithMetadataForTests::vault_b_init(), AccountWithMetadataForTests::vault_b_init(),
AccountWithMetadataForTests::pool_lp_reinitializable(), AccountWithMetadataForTests::pool_lp_reinitializable(),
@ -2519,7 +2477,7 @@ fn test_new_definition_lp_symmetric_amounts() {
assert_eq!(expected_lp, 2_000); assert_eq!(expected_lp, 2_000);
let (post_states, chained_calls) = new_definition( let (post_states, chained_calls) = new_definition(
AccountWithMetadataForTests::pool_definition_reinitializable(), AccountWithMetadataForTests::pool_definition_zero_supply_reinitializable(),
AccountWithMetadataForTests::vault_a_init(), AccountWithMetadataForTests::vault_a_init(),
AccountWithMetadataForTests::vault_b_init(), AccountWithMetadataForTests::vault_b_init(),
AccountWithMetadataForTests::pool_lp_reinitializable(), AccountWithMetadataForTests::pool_lp_reinitializable(),
@ -2574,13 +2532,11 @@ fn test_new_definition_lp_symmetric_amounts() {
assert_eq!(chained_call_lp_user, expected_lp_user_call); assert_eq!(chained_call_lp_user, expected_lp_user_call);
} }
#[should_panic( #[should_panic(expected = "Cannot initialize a Pool Definition with nonzero LP supply")]
expected = "New definition: inactive Pool Definition must have zero LP supply before reinitialization"
)]
#[test] #[test]
fn test_call_new_definition_reinitialization_requires_zero_pool_supply() { fn test_call_new_definition_reinitialization_requires_zero_pool_supply() {
let _post_states = new_definition( let _post_states = new_definition(
AccountWithMetadataForTests::pool_definition_inactive(), AccountWithMetadataForTests::pool_definition_init(),
AccountWithMetadataForTests::vault_a_init(), AccountWithMetadataForTests::vault_a_init(),
AccountWithMetadataForTests::vault_b_init(), AccountWithMetadataForTests::vault_b_init(),
AccountWithMetadataForTests::pool_lp_reinitializable(), AccountWithMetadataForTests::pool_lp_reinitializable(),
@ -2601,7 +2557,7 @@ fn test_call_new_definition_reinitialization_requires_zero_pool_supply() {
#[test] #[test]
fn test_call_new_definition_reinitialization_requires_zero_lp_definition_supply() { fn test_call_new_definition_reinitialization_requires_zero_lp_definition_supply() {
let _post_states = new_definition( let _post_states = new_definition(
AccountWithMetadataForTests::pool_definition_reinitializable(), AccountWithMetadataForTests::pool_definition_zero_supply_reinitializable(),
AccountWithMetadataForTests::vault_a_init(), AccountWithMetadataForTests::vault_a_init(),
AccountWithMetadataForTests::vault_b_init(), AccountWithMetadataForTests::vault_b_init(),
AccountWithMetadataForTests::pool_lp_init(), AccountWithMetadataForTests::pool_lp_init(),
@ -2702,7 +2658,6 @@ fn test_minimum_liquidity_lock_and_remove_all_user_lp() {
assert_eq!(pool_after_remove.liquidity_pool_supply, MINIMUM_LIQUIDITY); assert_eq!(pool_after_remove.liquidity_pool_supply, MINIMUM_LIQUIDITY);
assert!(pool_after_remove.reserve_a > 0); assert!(pool_after_remove.reserve_a > 0);
assert!(pool_after_remove.reserve_b > 0); assert!(pool_after_remove.reserve_b > 0);
assert!(pool_after_remove.active);
} }
#[test] #[test]
@ -2754,6 +2709,16 @@ fn test_sync_reserves_panics_when_vault_b_under_collateralized() {
); );
} }
#[should_panic(expected = "Pool liquidity supply is below minimum liquidity")]
#[test]
fn test_sync_reserves_rejects_pool_below_minimum_liquidity() {
let _ = sync_reserves(
AccountWithMetadataForTests::pool_definition_below_minimum_liquidity(),
AccountWithMetadataForTests::vault_a_init(),
AccountWithMetadataForTests::vault_b_init(),
);
}
#[test] #[test]
fn test_donation_then_add_liquidity_sync_mitigates_mispricing() { fn test_donation_then_add_liquidity_sync_mitigates_mispricing() {
let donation_a = 100u128; let donation_a = 100u128;
@ -2822,7 +2787,7 @@ fn new_definition_overflow_protection() {
let large_amount = u128::MAX / 2 + 1; let large_amount = u128::MAX / 2 + 1;
let _result = new_definition( let _result = new_definition(
AccountWithMetadataForTests::pool_definition_reinitializable(), AccountWithMetadataForTests::pool_definition_zero_supply_reinitializable(),
AccountWithMetadataForTests::vault_a_init(), AccountWithMetadataForTests::vault_a_init(),
AccountWithMetadataForTests::vault_b_init(), AccountWithMetadataForTests::vault_b_init(),
AccountWithMetadataForTests::pool_lp_reinitializable(), AccountWithMetadataForTests::pool_lp_reinitializable(),
@ -2853,11 +2818,10 @@ fn add_liquidity_overflow_protection() {
vault_a_id: IdForTests::vault_a_id(), vault_a_id: IdForTests::vault_a_id(),
vault_b_id: IdForTests::vault_b_id(), vault_b_id: IdForTests::vault_b_id(),
liquidity_pool_id: IdForTests::token_lp_definition_id(), liquidity_pool_id: IdForTests::token_lp_definition_id(),
liquidity_pool_supply: 1_000, liquidity_pool_supply: MINIMUM_LIQUIDITY,
reserve_a: large_reserve, reserve_a: large_reserve,
reserve_b, reserve_b,
fees: BalanceForTests::fee_tier(), fees: BalanceForTests::fee_tier(),
active: true,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
}, },
@ -2928,7 +2892,6 @@ fn remove_liquidity_overflow_protection() {
reserve_a: large_reserve, reserve_a: large_reserve,
reserve_b, reserve_b,
fees: BalanceForTests::fee_tier(), fees: BalanceForTests::fee_tier(),
active: true,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
}, },
@ -3008,11 +2971,10 @@ fn swap_exact_input_overflow_protection() {
vault_a_id: IdForTests::vault_a_id(), vault_a_id: IdForTests::vault_a_id(),
vault_b_id: IdForTests::vault_b_id(), vault_b_id: IdForTests::vault_b_id(),
liquidity_pool_id: IdForTests::token_lp_definition_id(), liquidity_pool_id: IdForTests::token_lp_definition_id(),
liquidity_pool_supply: 1, liquidity_pool_supply: MINIMUM_LIQUIDITY,
reserve_a: 1_000, reserve_a: 1_000,
reserve_b: large_reserve, reserve_b: large_reserve,
fees: BalanceForTests::fee_tier(), fees: BalanceForTests::fee_tier(),
active: true,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
}, },
@ -3071,7 +3033,7 @@ fn test_new_definition_supports_all_fee_tiers() {
FEE_TIER_BPS_100, FEE_TIER_BPS_100,
] { ] {
let (post_states, _) = new_definition( let (post_states, _) = new_definition(
AccountWithMetadataForTests::pool_definition_reinitializable(), AccountWithMetadataForTests::pool_definition_zero_supply_reinitializable(),
AccountWithMetadataForTests::vault_a_init(), AccountWithMetadataForTests::vault_a_init(),
AccountWithMetadataForTests::vault_b_init(), AccountWithMetadataForTests::vault_b_init(),
AccountWithMetadataForTests::pool_lp_reinitializable(), AccountWithMetadataForTests::pool_lp_reinitializable(),
@ -3095,10 +3057,10 @@ fn test_new_definition_supports_all_fee_tiers() {
#[test] #[test]
fn test_new_definition_rejects_unsupported_fee_tier() { fn test_new_definition_rejects_unsupported_fee_tier() {
let _ = new_definition( let _ = new_definition(
AccountWithMetadataForTests::pool_definition_inactive(), AccountWithMetadataForTests::pool_definition_zero_supply_reinitializable(),
AccountWithMetadataForTests::vault_a_init(), AccountWithMetadataForTests::vault_a_init(),
AccountWithMetadataForTests::vault_b_init(), AccountWithMetadataForTests::vault_b_init(),
AccountWithMetadataForTests::pool_lp_init(), AccountWithMetadataForTests::pool_lp_reinitializable(),
AccountWithMetadataForTests::lp_lock_holding_uninit(), AccountWithMetadataForTests::lp_lock_holding_uninit(),
AccountWithMetadataForTests::user_holding_a(), AccountWithMetadataForTests::user_holding_a(),
AccountWithMetadataForTests::user_holding_b(), AccountWithMetadataForTests::user_holding_b(),

View File

@ -300,7 +300,6 @@ impl Accounts {
reserve_a: Balances::vault_a_init(), reserve_a: Balances::vault_a_init(),
reserve_b: Balances::vault_b_init(), reserve_b: Balances::vault_b_init(),
fees: Balances::fee_tier(), fees: Balances::fee_tier(),
active: true,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
} }
@ -409,7 +408,6 @@ impl Accounts {
reserve_a: Balances::vault_a_swap_1(), reserve_a: Balances::vault_a_swap_1(),
reserve_b: Balances::vault_b_swap_1(), reserve_b: Balances::vault_b_swap_1(),
fees: Balances::fee_tier(), fees: Balances::fee_tier(),
active: true,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
} }
@ -477,7 +475,6 @@ impl Accounts {
reserve_a: Balances::vault_a_swap_2(), reserve_a: Balances::vault_a_swap_2(),
reserve_b: Balances::vault_b_swap_2(), reserve_b: Balances::vault_b_swap_2(),
fees: Balances::fee_tier(), fees: Balances::fee_tier(),
active: true,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
} }
@ -545,7 +542,6 @@ impl Accounts {
reserve_a: Balances::vault_a_add(), reserve_a: Balances::vault_a_add(),
reserve_b: Balances::vault_b_add(), reserve_b: Balances::vault_b_add(),
fees: Balances::fee_tier(), fees: Balances::fee_tier(),
active: true,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
} }
@ -638,7 +634,6 @@ impl Accounts {
reserve_a: Balances::vault_a_remove(), reserve_a: Balances::vault_a_remove(),
reserve_b: Balances::vault_b_remove(), reserve_b: Balances::vault_b_remove(),
fees: Balances::fee_tier(), fees: Balances::fee_tier(),
active: true,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
} }
@ -717,7 +712,7 @@ impl Accounts {
} }
} }
fn token_lp_definition_init_inactive() -> Account { fn token_lp_definition_reinitializable() -> Account {
Account { Account {
program_owner: Ids::token_program(), program_owner: Ids::token_program(),
balance: 0_u128, balance: 0_u128,
@ -730,7 +725,7 @@ impl Accounts {
} }
} }
fn vault_a_init_inactive() -> Account { fn vault_a_reinitializable() -> Account {
Account { Account {
program_owner: Ids::token_program(), program_owner: Ids::token_program(),
balance: 0_u128, balance: 0_u128,
@ -742,7 +737,7 @@ impl Accounts {
} }
} }
fn vault_b_init_inactive() -> Account { fn vault_b_reinitializable() -> Account {
Account { Account {
program_owner: Ids::token_program(), program_owner: Ids::token_program(),
balance: 0_u128, balance: 0_u128,
@ -754,7 +749,7 @@ impl Accounts {
} }
} }
fn pool_definition_inactive() -> Account { fn pool_definition_zero_supply_reinitializable() -> Account {
Account { Account {
program_owner: Ids::amm_program(), program_owner: Ids::amm_program(),
balance: 0_u128, balance: 0_u128,
@ -768,7 +763,6 @@ impl Accounts {
reserve_a: 0, reserve_a: 0,
reserve_b: 0, reserve_b: 0,
fees: Balances::fee_tier(), fees: Balances::fee_tier(),
active: false,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
} }
@ -849,7 +843,6 @@ impl Accounts {
reserve_a: Balances::vault_a_init(), reserve_a: Balances::vault_a_init(),
reserve_b: Balances::vault_b_init(), reserve_b: Balances::vault_b_init(),
fees: Balances::fee_tier(), fees: Balances::fee_tier(),
active: true,
}), }),
nonce: Nonce(0), nonce: Nonce(0),
} }
@ -1056,14 +1049,17 @@ fn amm_remove_liquidity_insufficient_user_lp_fails() {
} }
#[test] #[test]
fn amm_new_definition_inactive_initialized_pool_and_uninit_user_lp() { fn amm_new_definition_zero_supply_initialized_pool_and_uninit_user_lp() {
let mut state = state_for_amm_tests_with_new_def(); 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_a(), Accounts::vault_a_reinitializable());
state.force_insert_account(Ids::vault_b(), Accounts::vault_b_init_inactive()); state.force_insert_account(Ids::vault_b(), Accounts::vault_b_reinitializable());
state.force_insert_account(Ids::pool_definition(), Accounts::pool_definition_inactive()); state.force_insert_account(
Ids::pool_definition(),
Accounts::pool_definition_zero_supply_reinitializable(),
);
state.force_insert_account( state.force_insert_account(
Ids::token_lp_definition(), Ids::token_lp_definition(),
Accounts::token_lp_definition_init_inactive(), Accounts::token_lp_definition_reinitializable(),
); );
execute_new_definition(&mut state, Balances::fee_tier()); execute_new_definition(&mut state, Balances::fee_tier());
@ -1103,14 +1099,17 @@ fn amm_new_definition_inactive_initialized_pool_and_uninit_user_lp() {
} }
#[test] #[test]
fn amm_new_definition_inactive_initialized_pool_init_user_lp() { fn amm_new_definition_zero_supply_initialized_pool_init_user_lp() {
let mut state = state_for_amm_tests_with_new_def(); 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_a(), Accounts::vault_a_reinitializable());
state.force_insert_account(Ids::vault_b(), Accounts::vault_b_init_inactive()); state.force_insert_account(Ids::vault_b(), Accounts::vault_b_reinitializable());
state.force_insert_account(Ids::pool_definition(), Accounts::pool_definition_inactive()); state.force_insert_account(
Ids::pool_definition(),
Accounts::pool_definition_zero_supply_reinitializable(),
);
state.force_insert_account( state.force_insert_account(
Ids::token_lp_definition(), Ids::token_lp_definition(),
Accounts::token_lp_definition_init_inactive(), Accounts::token_lp_definition_reinitializable(),
); );
state.force_insert_account(Ids::user_lp(), Accounts::user_lp_holding_init_zero()); state.force_insert_account(Ids::user_lp(), Accounts::user_lp_holding_init_zero());
@ -1153,8 +1152,8 @@ fn amm_new_definition_inactive_initialized_pool_init_user_lp() {
#[test] #[test]
fn amm_new_definition_uninitialized_pool() { fn amm_new_definition_uninitialized_pool() {
let mut state = state_for_amm_tests_with_new_def(); 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_a(), Accounts::vault_a_reinitializable());
state.force_insert_account(Ids::vault_b(), Accounts::vault_b_init_inactive()); state.force_insert_account(Ids::vault_b(), Accounts::vault_b_reinitializable());
execute_new_definition(&mut state, Balances::fee_tier()); execute_new_definition(&mut state, Balances::fee_tier());
@ -1201,8 +1200,8 @@ fn amm_new_definition_supports_all_fee_tiers() {
FEE_TIER_BPS_100, FEE_TIER_BPS_100,
] { ] {
let mut state = state_for_amm_tests_with_new_def(); 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_a(), Accounts::vault_a_reinitializable());
state.force_insert_account(Ids::vault_b(), Accounts::vault_b_init_inactive()); state.force_insert_account(Ids::vault_b(), Accounts::vault_b_reinitializable());
execute_new_definition(&mut state, fees); execute_new_definition(&mut state, fees);
@ -1216,12 +1215,15 @@ fn amm_new_definition_supports_all_fee_tiers() {
#[test] #[test]
fn amm_new_definition_rejects_unsupported_fee_tier_transaction() { fn amm_new_definition_rejects_unsupported_fee_tier_transaction() {
let mut state = state_for_amm_tests_with_new_def(); 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_a(), Accounts::vault_a_reinitializable());
state.force_insert_account(Ids::vault_b(), Accounts::vault_b_init_inactive()); state.force_insert_account(Ids::vault_b(), Accounts::vault_b_reinitializable());
state.force_insert_account(Ids::pool_definition(), Accounts::pool_definition_inactive()); state.force_insert_account(
Ids::pool_definition(),
Accounts::pool_definition_zero_supply_reinitializable(),
);
state.force_insert_account( state.force_insert_account(
Ids::token_lp_definition(), Ids::token_lp_definition(),
Accounts::token_lp_definition_init_inactive(), Accounts::token_lp_definition_reinitializable(),
); );
state.force_insert_account(Ids::user_lp(), Accounts::user_lp_holding_init_zero()); state.force_insert_account(Ids::user_lp(), Accounts::user_lp_holding_init_zero());
@ -1230,19 +1232,19 @@ fn amm_new_definition_rejects_unsupported_fee_tier_transaction() {
assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_)))); assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_))));
assert_eq!( assert_eq!(
state.get_account_by_id(Ids::pool_definition()), state.get_account_by_id(Ids::pool_definition()),
Accounts::pool_definition_inactive() Accounts::pool_definition_zero_supply_reinitializable()
); );
assert_eq!( assert_eq!(
state.get_account_by_id(Ids::vault_a()), state.get_account_by_id(Ids::vault_a()),
Accounts::vault_a_init_inactive() Accounts::vault_a_reinitializable()
); );
assert_eq!( assert_eq!(
state.get_account_by_id(Ids::vault_b()), state.get_account_by_id(Ids::vault_b()),
Accounts::vault_b_init_inactive() Accounts::vault_b_reinitializable()
); );
assert_eq!( assert_eq!(
state.get_account_by_id(Ids::token_lp_definition()), state.get_account_by_id(Ids::token_lp_definition()),
Accounts::token_lp_definition_init_inactive() Accounts::token_lp_definition_reinitializable()
); );
assert_eq!( assert_eq!(
state.get_account_by_id(Ids::user_a()), state.get_account_by_id(Ids::user_a()),