various fixes

This commit is contained in:
jonesmarvin8 2026-01-27 18:24:03 -05:00
parent 3205e69e11
commit 8d5e6a37e6
15 changed files with 253 additions and 316 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -6,11 +6,10 @@
//! AMM program accepts [`Instruction`] as input, refer to the corresponding documentation
//! for more details.
use std::num::NonZero;
use amm_core::Instruction;
use nssa_core::program::{
AccountPostState, ChainedCall, ProgramInput, read_nssa_inputs,
write_nssa_outputs_with_chained_call,
};
use nssa_core::program::{ProgramInput, read_nssa_inputs, write_nssa_outputs_with_chained_call};
fn main() {
let (
@ -23,8 +22,7 @@ fn main() {
let pre_states_clone = pre_states.clone();
let (post_states, chained_calls): (Vec<AccountPostState>, Vec<ChainedCall>) = match instruction
{
let (post_states, chained_calls) = match instruction {
Instruction::NewDefinition {
token_a_amount,
token_b_amount,
@ -49,8 +47,8 @@ fn main() {
user_holding_a,
user_holding_b,
user_holding_lp,
token_a_amount,
token_b_amount,
NonZero::new(token_a_amount).expect("Token A should have a nonzero amount"),
NonZero::new(token_b_amount).expect("Token B should have a nonzero amount"),
amm_program_id,
)
}
@ -78,7 +76,8 @@ fn main() {
user_holding_a,
user_holding_b,
user_holding_lp,
min_amount_liquidity,
NonZero::new(min_amount_liquidity)
.expect("Min amount of liquidity should be nonzero"),
max_amount_to_add_token_a,
max_amount_to_add_token_b,
)
@ -107,7 +106,8 @@ fn main() {
user_holding_a,
user_holding_b,
user_holding_lp,
remove_liquidity_amount,
NonZero::new(remove_liquidity_amount)
.expect("Remove liquidity amount must be nonzero"),
min_amount_to_remove_token_a,
min_amount_to_remove_token_b,
)

View File

@ -6,4 +6,5 @@ edition = "2024"
[dependencies]
nssa_core.workspace = true
serde.workspace = true
risc0-zkvm.workspace = true
risc0-zkvm.workspace = true
borsh.workspace = true

View File

@ -1,5 +1,6 @@
//! This crate contains core data structures and utilities for the AMM Program.
use borsh::{BorshDeserialize, BorshSerialize};
use nssa_core::{
account::{AccountId, Data},
program::{PdaSeed, ProgramId},
@ -74,9 +75,7 @@ pub enum Instruction {
},
}
const POOL_DEFINITION_DATA_SIZE: usize = 225;
#[derive(Clone, Default)]
#[derive(Clone, Default, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub struct PoolDefinition {
pub definition_token_a_id: AccountId,
pub definition_token_b_id: AccountId,
@ -94,73 +93,23 @@ pub struct PoolDefinition {
pub active: bool,
}
impl PoolDefinition {
pub fn into_data(self) -> Data {
let mut bytes = [0; POOL_DEFINITION_DATA_SIZE];
bytes[0..32].copy_from_slice(&self.definition_token_a_id.to_bytes());
bytes[32..64].copy_from_slice(&self.definition_token_b_id.to_bytes());
bytes[64..96].copy_from_slice(&self.vault_a_id.to_bytes());
bytes[96..128].copy_from_slice(&self.vault_b_id.to_bytes());
bytes[128..160].copy_from_slice(&self.liquidity_pool_id.to_bytes());
bytes[160..176].copy_from_slice(&self.liquidity_pool_supply.to_le_bytes());
bytes[176..192].copy_from_slice(&self.reserve_a.to_le_bytes());
bytes[192..208].copy_from_slice(&self.reserve_b.to_le_bytes());
bytes[208..224].copy_from_slice(&self.fees.to_le_bytes());
bytes[224] = self.active as u8;
impl TryFrom<&Data> for PoolDefinition {
type Error = std::io::Error;
bytes
.to_vec()
.try_into()
.expect("225 bytes should fit into Data")
fn try_from(data: &Data) -> Result<Self, Self::Error> {
PoolDefinition::try_from_slice(data.as_ref())
}
}
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() != POOL_DEFINITION_DATA_SIZE {
None
} else {
let definition_token_a_id = AccountId::new(data[0..32].try_into().expect("Parse data: The AMM program must be provided a valid AccountId for Token A definition"));
let definition_token_b_id = AccountId::new(data[32..64].try_into().expect("Parse data: The AMM program must be provided a valid AccountId for Vault B definition"));
let vault_a_id = AccountId::new(data[64..96].try_into().expect(
"Parse data: The AMM program must be provided a valid AccountId for Vault A",
));
let vault_b_id = AccountId::new(data[96..128].try_into().expect(
"Parse data: The AMM program must be provided a valid AccountId for Vault B",
));
let liquidity_pool_id = AccountId::new(data[128..160].try_into().expect("Parse data: The AMM program must be provided a valid AccountId for Token liquidity pool definition"));
let liquidity_pool_supply = u128::from_le_bytes(data[160..176].try_into().expect(
"Parse data: The AMM program must be provided a valid u128 for liquidity cap",
));
let reserve_a = u128::from_le_bytes(data[176..192].try_into().expect(
"Parse data: The AMM program must be provided a valid u128 for reserve A balance",
));
let reserve_b = u128::from_le_bytes(data[192..208].try_into().expect(
"Parse data: The AMM program must be provided a valid u128 for reserve B balance",
));
let fees = u128::from_le_bytes(
data[208..224]
.try_into()
.expect("Parse data: The AMM program must be provided a valid u128 for fees"),
);
impl From<&PoolDefinition> for Data {
fn from(definition: &PoolDefinition) -> Self {
// Using size_of_val as size hint for Vec allocation
let mut data = Vec::with_capacity(std::mem::size_of_val(definition));
let active = match data[224] {
0 => false,
1 => true,
_ => panic!("Parse data: The AMM program must be provided a valid bool for active"),
};
BorshSerialize::serialize(definition, &mut data)
.expect("Serialization to Vec should not fail");
Some(Self {
definition_token_a_id,
definition_token_b_id,
vault_a_id,
vault_b_id,
liquidity_pool_id,
liquidity_pool_supply,
reserve_a,
reserve_b,
fees,
active,
})
}
Data::try_from(data).expect("Token definition encoded data should fit into Data")
}
}

View File

@ -1,6 +1,8 @@
use std::num::NonZeroU128;
use amm_core::{PoolDefinition, compute_liquidity_token_pda_seed};
use nssa_core::{
account::AccountWithMetadata,
account::{AccountWithMetadata, Data},
program::{AccountPostState, ChainedCall},
};
@ -13,32 +15,33 @@ pub fn add_liquidity(
user_holding_a: AccountWithMetadata,
user_holding_b: AccountWithMetadata,
user_holding_lp: AccountWithMetadata,
min_amount_liquidity: u128,
min_amount_liquidity: NonZeroU128,
max_amount_to_add_token_a: u128,
max_amount_to_add_token_b: u128,
) -> (Vec<AccountPostState>, Vec<ChainedCall>) {
// 1. Fetch Pool state
let pool_def_data = PoolDefinition::parse(&pool.account.data)
let pool_def_data = PoolDefinition::try_from(&pool.account.data)
.expect("Add liquidity: AMM Program expects valid Pool Definition Account");
if vault_a.account_id != pool_def_data.vault_a_id {
panic!("Vault A was not provided");
}
if pool_def_data.liquidity_pool_id != pool_definition_lp.account_id {
panic!("LP definition mismatch");
}
assert_eq!(
vault_a.account_id, pool_def_data.vault_a_id,
"Vault A was not provided"
);
if vault_b.account_id != pool_def_data.vault_b_id {
panic!("Vault B was not provided");
}
assert_eq!(
pool_def_data.liquidity_pool_id, pool_definition_lp.account_id,
"LP definition mismatch"
);
if max_amount_to_add_token_a == 0 || max_amount_to_add_token_b == 0 {
panic!("Both max-balances must be nonzero");
}
assert_eq!(
vault_b.account_id, pool_def_data.vault_b_id,
"Vault B was not provided"
);
if min_amount_liquidity == 0 {
panic!("Min-lp must be nonzero");
}
assert!(
max_amount_to_add_token_a != 0 && max_amount_to_add_token_b != 0,
"Both max-balances must be nonzero"
);
// 2. Determine deposit amount
let vault_b_token_holding = token_core::TokenHolding::try_from(&vault_b.account.data)
@ -65,13 +68,16 @@ pub fn add_liquidity(
);
};
if pool_def_data.reserve_a == 0 || pool_def_data.reserve_b == 0 {
panic!("Reserves must be nonzero");
}
if vault_a_balance < pool_def_data.reserve_a || vault_b_balance < pool_def_data.reserve_b {
panic!("Vaults' balances must be at least the reserve amounts");
}
assert!(pool_def_data.reserve_a != 0, "Reserves must be nonzero");
assert!(pool_def_data.reserve_b != 0, "Reserves must be nonzero");
assert!(
vault_a_balance >= pool_def_data.reserve_a,
"Vaults' balances must be at least the reserve amounts"
);
assert!(
vault_b_balance >= pool_def_data.reserve_b,
"Vaults' balances must be at least the reserve amounts"
);
// Calculate actual_amounts
let ideal_a: u128 =
@ -91,13 +97,17 @@ pub fn add_liquidity(
};
// 3. Validate amounts
if max_amount_to_add_token_a < actual_amount_a || max_amount_to_add_token_b < actual_amount_b {
panic!("Actual trade amounts cannot exceed max_amounts");
}
assert!(
max_amount_to_add_token_a >= actual_amount_a,
"Actual trade amounts cannot exceed max_amounts"
);
assert!(
max_amount_to_add_token_b >= actual_amount_b,
"Actual trade amounts cannot exceed max_amounts"
);
if actual_amount_a == 0 || actual_amount_b == 0 {
panic!("A trade amount is 0");
}
assert!(actual_amount_a != 0, "A trade amount is 0");
assert!(actual_amount_b != 0, "A trade amount is 0");
// 4. Calculate LP to mint
let delta_lp = std::cmp::min(
@ -105,13 +115,12 @@ pub fn add_liquidity(
pool_def_data.liquidity_pool_supply * actual_amount_b / pool_def_data.reserve_b,
);
if delta_lp == 0 {
panic!("Payable LP must be nonzero");
}
assert!(delta_lp != 0, "Payable LP must be nonzero");
if delta_lp < min_amount_liquidity {
panic!("Payable LP is less than provided minimum LP amount");
}
assert!(
delta_lp >= min_amount_liquidity.into(),
"Payable LP is less than provided minimum LP amount"
);
// 5. Update pool account
let mut pool_post = pool.account.clone();
@ -122,7 +131,7 @@ pub fn add_liquidity(
..pool_def_data
};
pool_post.data = pool_post_definition.into_data();
pool_post.data = Data::from(&pool_post_definition);
let token_program_id = user_holding_a.account.program_owner;
// Chain call for Token A (UserHoldingA -> Vault_A)

View File

@ -1,6 +1,6 @@
//! The AMM Program implementation.
pub use token_core as core;
pub use amm_core as core;
pub mod add;
pub mod new_definition;

View File

@ -1,9 +1,11 @@
use std::num::NonZeroU128;
use amm_core::{
PoolDefinition, compute_liquidity_token_pda, compute_liquidity_token_pda_seed,
compute_pool_pda, compute_vault_pda,
};
use nssa_core::{
account::{Account, AccountWithMetadata},
account::{Account, AccountWithMetadata, Data},
program::{AccountPostState, ChainedCall, ProgramId},
};
@ -20,11 +22,6 @@ pub fn new_definition(
token_b_amount: NonZeroU128,
amm_program_id: ProgramId,
) -> (Vec<AccountPostState>, Vec<ChainedCall>) {
// Prevents pool constant coefficient (k) from being 0.
if token_a_amount == 0 || token_b_amount == 0 {
panic!("Balances must be nonzero")
}
// Verify token_a and token_b are different
let definition_token_a_id = token_core::TokenHolding::try_from(&user_holding_a.account.data)
.expect("New definition: AMM Program expects valid Token Holding account for Token A")
@ -36,43 +33,48 @@ pub fn new_definition(
// both instances of the same token program
let token_program = user_holding_a.account.program_owner;
if user_holding_b.account.program_owner != token_program {
panic!("User Token holdings must use the same Token Program");
}
if definition_token_a_id == definition_token_b_id {
panic!("Cannot set up a swap for a token with itself")
}
if pool.account_id
!= compute_pool_pda(amm_program_id, definition_token_a_id, definition_token_b_id)
{
panic!("Pool Definition Account ID does not match PDA");
}
if vault_a.account_id
!= compute_vault_pda(amm_program_id, pool.account_id, definition_token_a_id)
|| vault_b.account_id
!= compute_vault_pda(amm_program_id, pool.account_id, definition_token_b_id)
{
panic!("Vault ID does not match PDA");
}
if pool_definition_lp.account_id != compute_liquidity_token_pda(amm_program_id, pool.account_id)
{
panic!("Liquidity pool Token Definition Account ID does not match PDA");
}
assert_eq!(
user_holding_b.account.program_owner, token_program,
"User Token holdings must use the same Token Program"
);
assert!(
definition_token_a_id != definition_token_b_id,
"Cannot set up a swap for a token with itself"
);
assert_eq!(
pool.account_id,
compute_pool_pda(amm_program_id, definition_token_a_id, definition_token_b_id),
"Pool Definition Account ID does not match PDA"
);
assert_eq!(
vault_a.account_id,
compute_vault_pda(amm_program_id, pool.account_id, definition_token_a_id),
"Vault ID does not match PDA"
);
assert_eq!(
vault_b.account_id,
compute_vault_pda(amm_program_id, pool.account_id, definition_token_b_id),
"Vault ID does not match PDA"
);
assert_eq!(
pool_definition_lp.account_id,
compute_liquidity_token_pda(amm_program_id, pool.account_id),
"Liquidity pool Token Definition Account ID does not match PDA"
);
// TODO: return here
// Verify that Pool Account is not active
let pool_account_data = if pool.account == Account::default() {
PoolDefinition::default()
} else {
PoolDefinition::parse(&pool.account.data).expect("AMM program expects a valid Pool account")
PoolDefinition::try_from(&pool.account.data)
.expect("AMM program expects a valid Pool account")
};
if pool_account_data.active {
panic!("Cannot initialize an active Pool Definition")
}
assert!(
!pool_account_data.active,
"Cannot initialize an active Pool Definition"
);
// LP Token minting calculation
// We assume LP is based on the initial deposit amount for Token_A.
@ -85,14 +87,14 @@ pub fn new_definition(
vault_a_id: vault_a.account_id,
vault_b_id: vault_b.account_id,
liquidity_pool_id: pool_definition_lp.account_id,
liquidity_pool_supply: token_a_amount,
reserve_a: token_a_amount,
reserve_b: token_b_amount,
liquidity_pool_supply: token_a_amount.into(),
reserve_a: token_a_amount.into(),
reserve_b: token_b_amount.into(),
fees: 0u128, // TODO: we assume all fees are 0 for now.
active: true,
};
pool_post.data = pool_post_definition.into_data();
pool_post.data = Data::from(&pool_post_definition);
let pool_post: AccountPostState = if pool.account == Account::default() {
AccountPostState::new_claimed(pool_post.clone())
} else {
@ -106,7 +108,7 @@ pub fn new_definition(
token_program_id,
vec![user_holding_a.clone(), vault_a.clone()],
&token_core::Instruction::Transfer {
amount_to_transfer: token_a_amount,
amount_to_transfer: token_a_amount.into(),
},
);
// Chain call for Token B (user_holding_b -> Vault_B)
@ -114,7 +116,7 @@ pub fn new_definition(
token_program_id,
vec![user_holding_b.clone(), vault_b.clone()],
&token_core::Instruction::Transfer {
amount_to_transfer: token_b_amount,
amount_to_transfer: token_b_amount.into(),
},
);
@ -122,11 +124,11 @@ pub fn new_definition(
let instruction = if pool.account == Account::default() {
token_core::Instruction::NewFungibleDefinition {
name: String::from("LP Token"),
total_supply: token_a_amount,
total_supply: token_a_amount.into(),
}
} else {
token_core::Instruction::Mint {
amount_to_mint: token_a_amount,
amount_to_mint: token_a_amount.into(),
}
};

View File

@ -1,6 +1,8 @@
use std::num::NonZeroU128;
use amm_core::{PoolDefinition, compute_liquidity_token_pda_seed, compute_vault_pda_seed};
use nssa_core::{
account::AccountWithMetadata,
account::{AccountWithMetadata, Data},
program::{AccountPostState, ChainedCall},
};
@ -13,29 +15,29 @@ pub fn remove_liquidity(
user_holding_a: AccountWithMetadata,
user_holding_b: AccountWithMetadata,
user_holding_lp: AccountWithMetadata,
remove_liquidity_amount: u128,
remove_liquidity_amount: NonZeroU128,
min_amount_to_remove_token_a: u128,
min_amount_to_remove_token_b: u128,
) -> (Vec<AccountPostState>, Vec<ChainedCall>) {
let remove_liquidity_amount: u128 = remove_liquidity_amount.into();
// 1. Fetch Pool state
let pool_def_data = PoolDefinition::parse(&pool.account.data)
let pool_def_data = PoolDefinition::try_from(&pool.account.data)
.expect("Remove liquidity: AMM Program expects a valid Pool Definition Account");
if !pool_def_data.active {
panic!("Pool is inactive");
}
if pool_def_data.liquidity_pool_id != pool_definition_lp.account_id {
panic!("LP definition mismatch");
}
if vault_a.account_id != pool_def_data.vault_a_id {
panic!("Vault A was not provided");
}
if vault_b.account_id != pool_def_data.vault_b_id {
panic!("Vault B was not provided");
}
assert!(pool_def_data.active, "Pool is inactive");
assert_eq!(
pool_def_data.liquidity_pool_id, pool_definition_lp.account_id,
"LP definition mismatch"
);
assert_eq!(
vault_a.account_id, pool_def_data.vault_a_id,
"Vault A was not provided"
);
assert_eq!(
vault_b.account_id, pool_def_data.vault_b_id,
"Vault B was not provided"
);
// Vault addresses do not need to be checked with PDA
// calculation for setting authorization since stored
@ -45,13 +47,14 @@ pub fn remove_liquidity(
running_vault_a.is_authorized = true;
running_vault_b.is_authorized = true;
if min_amount_to_remove_token_a == 0 || min_amount_to_remove_token_b == 0 {
panic!("Minimum withdraw amount must be nonzero");
}
if remove_liquidity_amount == 0 {
panic!("Liquidity amount must be nonzero");
}
assert!(
min_amount_to_remove_token_a != 0,
"Minimum withdraw amount must be nonzero"
);
assert!(
min_amount_to_remove_token_b != 0,
"Minimum withdraw amount must be nonzero"
);
// 2. Compute withdrawal amounts
let user_holding_lp_data = token_core::TokenHolding::try_from(&user_holding_lp.account.data)
@ -66,11 +69,15 @@ pub fn remove_liquidity(
);
};
if user_lp_balance > pool_def_data.liquidity_pool_supply
|| user_holding_lp_data.definition_id() != pool_def_data.liquidity_pool_id
{
panic!("Invalid liquidity account provided");
}
assert!(
user_lp_balance <= pool_def_data.liquidity_pool_supply,
"Invalid liquidity account provided"
);
assert_eq!(
user_holding_lp_data.definition_id(),
pool_def_data.liquidity_pool_id,
"Invalid liquidity account provided"
);
let withdraw_amount_a =
(pool_def_data.reserve_a * remove_liquidity_amount) / pool_def_data.liquidity_pool_supply;
@ -78,12 +85,14 @@ pub fn remove_liquidity(
(pool_def_data.reserve_b * remove_liquidity_amount) / pool_def_data.liquidity_pool_supply;
// 3. Validate and slippage check
if withdraw_amount_a < min_amount_to_remove_token_a {
panic!("Insufficient minimal withdraw amount (Token A) provided for liquidity amount");
}
if withdraw_amount_b < min_amount_to_remove_token_b {
panic!("Insufficient minimal withdraw amount (Token B) provided for liquidity amount");
}
assert!(
withdraw_amount_a >= min_amount_to_remove_token_a,
"Insufficient minimal withdraw amount (Token A) provided for liquidity amount"
);
assert!(
withdraw_amount_b >= min_amount_to_remove_token_b,
"Insufficient minimal withdraw amount (Token B) provided for liquidity amount"
);
// 4. Calculate LP to reduce cap by
let delta_lp: u128 = (pool_def_data.liquidity_pool_supply * remove_liquidity_amount)
@ -101,7 +110,7 @@ pub fn remove_liquidity(
..pool_def_data.clone()
};
pool_post.data = pool_post_definition.into_data();
pool_post.data = Data::from(&pool_post_definition);
let token_program_id = user_holding_a.account.program_owner;

View File

@ -1,6 +1,6 @@
pub use amm_core::{PoolDefinition, compute_liquidity_token_pda_seed, compute_vault_pda_seed};
use nssa_core::{
account::{AccountId, AccountWithMetadata},
account::{AccountId, AccountWithMetadata, Data},
program::{AccountPostState, ChainedCall},
};
@ -16,20 +16,18 @@ pub fn swap(
token_in_id: AccountId,
) -> (Vec<AccountPostState>, Vec<ChainedCall>) {
// Verify vaults are in fact vaults
let pool_def_data = PoolDefinition::parse(&pool.account.data)
let pool_def_data = PoolDefinition::try_from(&pool.account.data)
.expect("Swap: AMM Program expects a valid Pool Definition Account");
if !pool_def_data.active {
panic!("Pool is inactive");
}
if vault_a.account_id != pool_def_data.vault_a_id {
panic!("Vault A was not provided");
}
if vault_b.account_id != pool_def_data.vault_b_id {
panic!("Vault B was not provided");
}
assert!(pool_def_data.active, "Pool is inactive");
assert_eq!(
vault_a.account_id, pool_def_data.vault_a_id,
"Vault A was not provided"
);
assert_eq!(
vault_b.account_id, pool_def_data.vault_b_id,
"Vault B was not provided"
);
// fetch pool reserves
// validates reserves is at least the vaults' balances
@ -42,9 +40,11 @@ pub fn swap(
else {
panic!("Swap: AMM Program expects a valid Fungible Token Holding Account for Vault A");
};
if vault_a_balance < pool_def_data.reserve_a {
panic!("Reserve for Token A exceeds vault balance");
}
assert!(
vault_a_balance >= pool_def_data.reserve_a,
"Reserve for Token A exceeds vault balance"
);
let vault_b_token_holding = token_core::TokenHolding::try_from(&vault_b.account.data)
.expect("Swap: AMM Program expects a valid Token Holding Account for Vault B");
@ -56,9 +56,10 @@ pub fn swap(
panic!("Swap: AMM Program expects a valid Fungible Token Holding Account for Vault B");
};
if vault_b_balance < pool_def_data.reserve_b {
panic!("Reserve for Token B exceeds vault balance");
}
assert!(
vault_b_balance >= pool_def_data.reserve_b,
"Reserve for Token B exceeds vault balance"
);
let (chained_calls, [deposit_a, withdraw_a], [deposit_b, withdraw_b]) =
if token_in_id == pool_def_data.definition_token_a_id {
@ -69,7 +70,8 @@ pub fn swap(
user_holding_b.clone(),
swap_amount_in,
min_amount_out,
&[pool_def_data.reserve_a, pool_def_data.reserve_b],
pool_def_data.reserve_a,
pool_def_data.reserve_b,
pool.account_id,
);
@ -82,7 +84,8 @@ pub fn swap(
user_holding_a.clone(),
swap_amount_in,
min_amount_out,
&[pool_def_data.reserve_b, pool_def_data.reserve_a],
pool_def_data.reserve_b,
pool_def_data.reserve_a,
pool.account_id,
);
@ -99,7 +102,7 @@ pub fn swap(
..pool_def_data
};
pool_post.data = pool_post_definition.into_data();
pool_post.data = Data::from(&pool_post_definition);
let post_states = vec![
AccountPostState::new(pool_post.clone()),
@ -120,12 +123,10 @@ fn swap_logic(
user_withdraw: AccountWithMetadata,
swap_amount_in: u128,
min_amount_out: u128,
reserve_amounts: &[u128],
reserve_deposit_vault_amount: u128,
reserve_withdraw_vault_amount: u128,
pool_id: AccountId,
) -> (Vec<ChainedCall>, u128, u128) {
let reserve_deposit_vault_amount = reserve_amounts[0];
let reserve_withdraw_vault_amount = reserve_amounts[1];
// Compute withdraw amount
// Maintains pool constant product
// k = pool_def_data.reserve_a * pool_def_data.reserve_b;
@ -133,13 +134,11 @@ fn swap_logic(
/ (reserve_deposit_vault_amount + swap_amount_in);
// Slippage check
if min_amount_out > withdraw_amount {
panic!("Withdraw amount is less than minimal amount out");
}
if withdraw_amount == 0 {
panic!("Withdraw amount should be nonzero");
}
assert!(
min_amount_out <= withdraw_amount,
"Withdraw amount is less than minimal amount out"
);
assert!(withdraw_amount != 0, "Withdraw amount should be nonzero");
let token_program_id = user_deposit.account.program_owner;

View File

@ -1,5 +1,7 @@
#![cfg(test)]
use std::num::NonZero;
use amm_core::{
PoolDefinition, compute_liquidity_token_pda, compute_liquidity_token_pda_seed,
compute_pool_pda, compute_vault_pda, compute_vault_pda_seed,
@ -630,7 +632,7 @@ impl AccountForTests {
account: Account {
program_owner: ProgramId::default(),
balance: 0u128,
data: PoolDefinition::into_data(PoolDefinition {
data: Data::from(&PoolDefinition {
definition_token_a_id: IdForTests::token_a_definition_id(),
definition_token_b_id: IdForTests::token_b_definition_id(),
vault_a_id: IdForTests::vault_a_id(),
@ -654,7 +656,7 @@ impl AccountForTests {
account: Account {
program_owner: ProgramId::default(),
balance: 0u128,
data: PoolDefinition::into_data(PoolDefinition {
data: Data::from(&PoolDefinition {
definition_token_a_id: IdForTests::token_a_definition_id(),
definition_token_b_id: IdForTests::token_b_definition_id(),
vault_a_id: IdForTests::vault_a_id(),
@ -678,7 +680,7 @@ impl AccountForTests {
account: Account {
program_owner: ProgramId::default(),
balance: 0u128,
data: PoolDefinition::into_data(PoolDefinition {
data: Data::from(&PoolDefinition {
definition_token_a_id: IdForTests::token_a_definition_id(),
definition_token_b_id: IdForTests::token_b_definition_id(),
vault_a_id: IdForTests::vault_a_id(),
@ -702,7 +704,7 @@ impl AccountForTests {
account: Account {
program_owner: ProgramId::default(),
balance: 0u128,
data: PoolDefinition::into_data(PoolDefinition {
data: Data::from(&PoolDefinition {
definition_token_a_id: IdForTests::token_a_definition_id(),
definition_token_b_id: IdForTests::token_b_definition_id(),
vault_a_id: IdForTests::vault_a_id(),
@ -726,7 +728,7 @@ impl AccountForTests {
account: Account {
program_owner: ProgramId::default(),
balance: 0u128,
data: PoolDefinition::into_data(PoolDefinition {
data: Data::from(&PoolDefinition {
definition_token_a_id: IdForTests::token_a_definition_id(),
definition_token_b_id: IdForTests::token_b_definition_id(),
vault_a_id: IdForTests::vault_a_id(),
@ -750,7 +752,7 @@ impl AccountForTests {
account: Account {
program_owner: ProgramId::default(),
balance: 0u128,
data: PoolDefinition::into_data(PoolDefinition {
data: Data::from(&PoolDefinition {
definition_token_a_id: IdForTests::token_a_definition_id(),
definition_token_b_id: IdForTests::token_b_definition_id(),
vault_a_id: IdForTests::vault_a_id(),
@ -774,7 +776,7 @@ impl AccountForTests {
account: Account {
program_owner: ProgramId::default(),
balance: 0u128,
data: PoolDefinition::into_data(PoolDefinition {
data: Data::from(&PoolDefinition {
definition_token_a_id: IdForTests::token_a_definition_id(),
definition_token_b_id: IdForTests::token_b_definition_id(),
vault_a_id: IdForTests::vault_a_id(),
@ -798,7 +800,7 @@ impl AccountForTests {
account: Account {
program_owner: ProgramId::default(),
balance: 0u128,
data: PoolDefinition::into_data(PoolDefinition {
data: Data::from(&PoolDefinition {
definition_token_a_id: IdForTests::token_a_definition_id(),
definition_token_b_id: IdForTests::token_b_definition_id(),
vault_a_id: IdForTests::vault_a_id(),
@ -822,7 +824,7 @@ impl AccountForTests {
account: Account {
program_owner: ProgramId::default(),
balance: 0u128,
data: PoolDefinition::into_data(PoolDefinition {
data: Data::from(&PoolDefinition {
definition_token_a_id: IdForTests::token_a_definition_id(),
definition_token_b_id: IdForTests::token_b_definition_id(),
vault_a_id: IdForTests::vault_a_id(),
@ -846,7 +848,7 @@ impl AccountForTests {
account: Account {
program_owner: ProgramId::default(),
balance: 0u128,
data: PoolDefinition::into_data(PoolDefinition {
data: Data::from(&PoolDefinition {
definition_token_a_id: IdForTests::token_a_definition_id(),
definition_token_b_id: IdForTests::token_b_definition_id(),
vault_a_id: IdForTests::vault_a_id(),
@ -870,7 +872,7 @@ impl AccountForTests {
account: Account {
program_owner: ProgramId::default(),
balance: 0u128,
data: PoolDefinition::into_data(PoolDefinition {
data: Data::from(&PoolDefinition {
definition_token_a_id: IdForTests::token_a_definition_id(),
definition_token_b_id: IdForTests::token_b_definition_id(),
vault_a_id: IdForTests::vault_a_id(),
@ -894,7 +896,7 @@ impl AccountForTests {
account: Account {
program_owner: ProgramId::default(),
balance: 0u128,
data: PoolDefinition::into_data(PoolDefinition {
data: Data::from(&PoolDefinition {
definition_token_a_id: IdForTests::token_a_definition_id(),
definition_token_b_id: IdForTests::token_b_definition_id(),
vault_a_id: IdForTests::vault_a_id(),
@ -950,7 +952,7 @@ impl AccountForTests {
account: Account {
program_owner: ProgramId::default(),
balance: 0u128,
data: PoolDefinition::into_data(PoolDefinition {
data: Data::from(&PoolDefinition {
definition_token_a_id: IdForTests::token_a_definition_id(),
definition_token_b_id: IdForTests::token_b_definition_id(),
vault_a_id: IdForTests::vault_a_id(),
@ -996,7 +998,7 @@ fn test_call_add_liquidity_vault_a_omitted() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_init(),
BalanceForTests::add_min_amount_lp(),
NonZero::new(BalanceForTests::add_min_amount_lp()).unwrap(),
BalanceForTests::add_max_amount_a(),
BalanceForTests::add_max_amount_b(),
);
@ -1013,7 +1015,7 @@ fn test_call_add_liquidity_vault_b_omitted() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_init(),
BalanceForTests::add_min_amount_lp(),
NonZero::new(BalanceForTests::add_min_amount_lp()).unwrap(),
BalanceForTests::add_max_amount_a(),
BalanceForTests::add_max_amount_b(),
);
@ -1030,7 +1032,7 @@ fn test_call_add_liquidity_lp_definition_mismatch() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_init(),
BalanceForTests::add_min_amount_lp(),
NonZero::new(BalanceForTests::add_min_amount_lp()).unwrap(),
BalanceForTests::add_max_amount_a(),
BalanceForTests::add_max_amount_b(),
);
@ -1047,7 +1049,7 @@ fn test_call_add_liquidity_zero_balance_1() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_init(),
BalanceForTests::add_min_amount_lp(),
NonZero::new(BalanceForTests::add_min_amount_lp()).unwrap(),
0,
BalanceForTests::add_max_amount_b(),
);
@ -1064,29 +1066,12 @@ fn test_call_add_liquidity_zero_balance_2() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_init(),
BalanceForTests::add_min_amount_lp(),
NonZero::new(BalanceForTests::add_min_amount_lp()).unwrap(),
0,
BalanceForTests::add_max_amount_a(),
);
}
#[should_panic(expected = "Min-lp must be nonzero")]
#[test]
fn test_call_add_liquidity_zero_min_lp() {
let _post_states = add_liquidity(
AccountForTests::pool_definition_init(),
AccountForTests::vault_a_init(),
AccountForTests::vault_b_init(),
AccountForTests::pool_lp_init(),
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_init(),
0,
BalanceForTests::add_max_amount_a(),
BalanceForTests::add_max_amount_b(),
);
}
#[should_panic(expected = "Vaults' balances must be at least the reserve amounts")]
#[test]
fn test_call_add_liquidity_vault_insufficient_balance_1() {
@ -1098,7 +1083,7 @@ fn test_call_add_liquidity_vault_insufficient_balance_1() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_init(),
BalanceForTests::add_max_amount_a(),
NonZero::new(BalanceForTests::add_max_amount_a()).unwrap(),
BalanceForTests::add_max_amount_b(),
BalanceForTests::add_min_amount_lp(),
);
@ -1115,7 +1100,7 @@ fn test_call_add_liquidity_vault_insufficient_balance_2() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_init(),
BalanceForTests::add_max_amount_a(),
NonZero::new(BalanceForTests::add_max_amount_a()).unwrap(),
BalanceForTests::add_max_amount_b(),
BalanceForTests::add_min_amount_lp(),
);
@ -1132,7 +1117,7 @@ fn test_call_add_liquidity_actual_amount_zero_1() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_init(),
BalanceForTests::add_min_amount_lp(),
NonZero::new(BalanceForTests::add_min_amount_lp()).unwrap(),
BalanceForTests::add_max_amount_a(),
BalanceForTests::add_max_amount_b(),
);
@ -1149,7 +1134,7 @@ fn test_call_add_liquidity_actual_amount_zero_2() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_init(),
BalanceForTests::add_min_amount_lp(),
NonZero::new(BalanceForTests::add_min_amount_lp()).unwrap(),
BalanceForTests::add_max_amount_a_low(),
BalanceForTests::add_max_amount_b_low(),
);
@ -1166,7 +1151,7 @@ fn test_call_add_liquidity_reserves_zero_1() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_init(),
BalanceForTests::add_min_amount_lp(),
NonZero::new(BalanceForTests::add_min_amount_lp()).unwrap(),
BalanceForTests::add_max_amount_a(),
BalanceForTests::add_max_amount_b(),
);
@ -1183,7 +1168,7 @@ fn test_call_add_liquidity_reserves_zero_2() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_init(),
BalanceForTests::add_min_amount_lp(),
NonZero::new(BalanceForTests::add_min_amount_lp()).unwrap(),
BalanceForTests::add_max_amount_a(),
BalanceForTests::add_max_amount_b(),
);
@ -1200,7 +1185,7 @@ fn test_call_add_liquidity_payable_lp_zero() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_init(),
BalanceForTests::add_min_amount_lp(),
NonZero::new(BalanceForTests::add_min_amount_lp()).unwrap(),
BalanceForTests::add_max_amount_a_low(),
BalanceForTests::add_max_amount_b_low(),
);
@ -1216,7 +1201,7 @@ fn test_call_add_liquidity_chained_call_successsful() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_init(),
BalanceForTests::add_min_amount_lp(),
NonZero::new(BalanceForTests::add_min_amount_lp()).unwrap(),
BalanceForTests::add_max_amount_a(),
BalanceForTests::add_max_amount_b(),
);
@ -1245,7 +1230,7 @@ fn test_call_remove_liquidity_vault_a_omitted() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_init(),
BalanceForTests::remove_amount_lp(),
NonZero::new(BalanceForTests::remove_amount_lp()).unwrap(),
BalanceForTests::remove_min_amount_a(),
BalanceForTests::remove_min_amount_b(),
);
@ -1262,7 +1247,7 @@ fn test_call_remove_liquidity_vault_b_omitted() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_init(),
BalanceForTests::remove_amount_lp(),
NonZero::new(BalanceForTests::remove_amount_lp()).unwrap(),
BalanceForTests::remove_min_amount_a(),
BalanceForTests::remove_min_amount_b(),
);
@ -1279,7 +1264,7 @@ fn test_call_remove_liquidity_lp_def_mismatch() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_init(),
BalanceForTests::remove_amount_lp(),
NonZero::new(BalanceForTests::remove_amount_lp()).unwrap(),
BalanceForTests::remove_min_amount_a(),
BalanceForTests::remove_min_amount_b(),
);
@ -1297,7 +1282,7 @@ fn test_call_remove_liquidity_insufficient_liquidity_amount() {
AccountForTests::user_holding_b(),
AccountForTests::user_holding_a(), /* different token account than lp to create desired
* error */
BalanceForTests::remove_amount_lp(),
NonZero::new(BalanceForTests::remove_amount_lp()).unwrap(),
BalanceForTests::remove_min_amount_a(),
BalanceForTests::remove_min_amount_b(),
);
@ -1316,7 +1301,7 @@ fn test_call_remove_liquidity_insufficient_balance_1() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_init(),
BalanceForTests::remove_amount_lp_1(),
NonZero::new(BalanceForTests::remove_amount_lp_1()).unwrap(),
BalanceForTests::remove_min_amount_a(),
BalanceForTests::remove_min_amount_b(),
);
@ -1335,7 +1320,7 @@ fn test_call_remove_liquidity_insufficient_balance_2() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_init(),
BalanceForTests::remove_amount_lp(),
NonZero::new(BalanceForTests::remove_amount_lp()).unwrap(),
BalanceForTests::remove_min_amount_a(),
BalanceForTests::remove_min_amount_b(),
);
@ -1352,7 +1337,7 @@ fn test_call_remove_liquidity_min_bal_zero_1() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_init(),
BalanceForTests::remove_amount_lp(),
NonZero::new(BalanceForTests::remove_amount_lp()).unwrap(),
0,
BalanceForTests::remove_min_amount_b(),
);
@ -1369,29 +1354,12 @@ fn test_call_remove_liquidity_min_bal_zero_2() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_init(),
BalanceForTests::remove_amount_lp(),
NonZero::new(BalanceForTests::remove_amount_lp()).unwrap(),
BalanceForTests::remove_min_amount_a(),
0,
);
}
#[should_panic(expected = "Liquidity amount must be nonzero")]
#[test]
fn test_call_remove_liquidity_lp_bal_zero() {
let _post_states = remove_liquidity(
AccountForTests::pool_definition_init(),
AccountForTests::vault_a_init(),
AccountForTests::vault_b_init(),
AccountForTests::pool_lp_init(),
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_init(),
0,
BalanceForTests::remove_min_amount_a(),
BalanceForTests::remove_min_amount_b(),
);
}
#[test]
fn test_call_remove_liquidity_chained_call_successful() {
let (post_states, chained_calls) = remove_liquidity(
@ -1402,7 +1370,7 @@ fn test_call_remove_liquidity_chained_call_successful() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_init(),
BalanceForTests::remove_amount_lp(),
NonZero::new(BalanceForTests::remove_amount_lp()).unwrap(),
BalanceForTests::remove_min_amount_a(),
BalanceForTests::remove_min_amount_b_low(),
);
@ -1431,8 +1399,8 @@ fn test_call_new_definition_with_zero_balance_1() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_uninit(),
0,
BalanceForTests::vault_b_reserve_init(),
NonZero::new(0).expect("Balances must be nonzero"),
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
AMM_PROGRAM_ID,
);
}
@ -1448,8 +1416,8 @@ fn test_call_new_definition_with_zero_balance_2() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_uninit(),
BalanceForTests::vault_a_reserve_init(),
0,
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
NonZero::new(0).expect("Balances must be nonzero"),
AMM_PROGRAM_ID,
);
}
@ -1465,8 +1433,8 @@ fn test_call_new_definition_same_token_definition() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_a(),
AccountForTests::user_holding_lp_uninit(),
BalanceForTests::vault_a_reserve_init(),
BalanceForTests::vault_b_reserve_init(),
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
AMM_PROGRAM_ID,
);
}
@ -1482,8 +1450,8 @@ fn test_call_new_definition_wrong_liquidity_id() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_uninit(),
BalanceForTests::vault_a_reserve_init(),
BalanceForTests::vault_b_reserve_init(),
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
AMM_PROGRAM_ID,
);
}
@ -1499,8 +1467,8 @@ fn test_call_new_definition_wrong_pool_id() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_uninit(),
BalanceForTests::vault_a_reserve_init(),
BalanceForTests::vault_b_reserve_init(),
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
AMM_PROGRAM_ID,
);
}
@ -1516,8 +1484,8 @@ fn test_call_new_definition_wrong_vault_id_1() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_uninit(),
BalanceForTests::vault_a_reserve_init(),
BalanceForTests::vault_b_reserve_init(),
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
AMM_PROGRAM_ID,
);
}
@ -1533,8 +1501,8 @@ fn test_call_new_definition_wrong_vault_id_2() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_uninit(),
BalanceForTests::vault_a_reserve_init(),
BalanceForTests::vault_b_reserve_init(),
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
AMM_PROGRAM_ID,
);
}
@ -1550,8 +1518,8 @@ fn test_call_new_definition_cannot_initialize_active_pool() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_uninit(),
BalanceForTests::vault_a_reserve_init(),
BalanceForTests::vault_b_reserve_init(),
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
AMM_PROGRAM_ID,
);
}
@ -1567,8 +1535,8 @@ fn test_call_new_definition_chained_call_successful() {
AccountForTests::user_holding_a(),
AccountForTests::user_holding_b(),
AccountForTests::user_holding_lp_uninit(),
BalanceForTests::vault_a_reserve_init(),
BalanceForTests::vault_b_reserve_init(),
NonZero::new(BalanceForTests::vault_a_reserve_init()).unwrap(),
NonZero::new(BalanceForTests::vault_b_reserve_init()).unwrap(),
AMM_PROGRAM_ID,
);