Skip to content

Commit

Permalink
update program
Browse files Browse the repository at this point in the history
  • Loading branch information
yimingWOW committed Jan 26, 2025
1 parent 9f9b50b commit ef8eec4
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 81 deletions.
13 changes: 7 additions & 6 deletions app/src/components/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,6 @@ const Dashboard: FC = () => {
return (
<AmmProvider>
<nav className="tab-nav">
<TabButton
isActive={activeTab === 'guide'}
onClick={() => setActiveTab('guide')}
icon="🧭"
label="Guide"
/>

{publicKey?.toBase58() == EXCLUDED_PUBLIC_KEY && (
<TabButton
Expand All @@ -51,6 +45,13 @@ const Dashboard: FC = () => {
label="AMM"
/>
)}

<TabButton
isActive={activeTab === 'guide'}
onClick={() => setActiveTab('guide')}
icon="🧭"
label="Guide"
/>

<TabButton
isActive={activeTab === 'pool'}
Expand Down
9 changes: 6 additions & 3 deletions fall/programs/fall/src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use anchor_lang::prelude::*;

#[constant]
pub const MINIMUM_LIQUIDITY: u64 = 100;
pub const MINIMUM_LIQUIDITY: u64 = 10;

#[constant]
pub const AUTHORITY_SEED: &[u8] = b"a"; // authority
Expand Down Expand Up @@ -32,7 +32,10 @@ pub const BORROWER_BORROW_BLOCK_HEIGHT_TOKEN_SEED: &[u8] = b"j"; // borrow_heigh


#[constant]
pub const MIN_COLLATERAL_RATIO_BASE: u64 = 10000; // 100%
pub const PERCENT_BASE: u64 = 10000; // 100%

#[constant]
pub const BASE_RATE: u64 = 10000; // 100%
pub const BASE_INTEREST_RATE: u64 = 5; // 0.05%

#[constant]
pub const MIN_COLLATERAL_RATIO: u64 = 10000; // 100%
2 changes: 2 additions & 0 deletions fall/programs/fall/src/instructions/create_amm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ pub fn create_amm(ctx: Context<CreateAmm>, id: Pubkey) -> Result<()> {
let amm = &mut ctx.accounts.amm;
amm.id = id;
amm.admin = ctx.accounts.admin.key();
amm.liquidity_fee = 10000; // 10%
amm.protocol_fee_percentage = 10000; // 10%

Ok(())
}
2 changes: 0 additions & 2 deletions fall/programs/fall/src/instructions/create_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,6 @@ pub fn create_pool(ctx: Context<CreatePool>, fee: u16) -> Result<()> {
pool.amm = ctx.accounts.amm.key();
pool.mint_a = ctx.accounts.mint_a.key();
pool.mint_b = ctx.accounts.mint_b.key();
pool.fee = fee;
pool.min_collateral_ratio = 20000;
pool.borrow_interest_accumulator_block_height = Clock::get()?.slot;
pool.borrow_interest_accumulator = 0;
pool.share_lending_block_height = Clock::get()?.slot;
Expand Down
68 changes: 56 additions & 12 deletions fall/programs/fall/src/instructions/deposit_liquidity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,21 @@ use anchor_spl::{
};
use fixed::types::I64F64;
use crate::{
constants::{AUTHORITY_SEED, LIQUIDITY_SEED, MINIMUM_LIQUIDITY},
state::Pool,
constants::{AUTHORITY_SEED, LIQUIDITY_SEED, MINIMUM_LIQUIDITY,PERCENT_BASE},
state::{Pool, Amm},
};


#[derive(Accounts)]
pub struct DepositLiquidity<'info> {
#[account(
seeds = [
amm.id.as_ref()
],
bump,
)]
pub amm: Box<Account<'info, Amm>>,

#[account(
mut,
seeds = [
Expand Down Expand Up @@ -92,6 +100,20 @@ pub struct DepositLiquidity<'info> {
)]
pub depositor_account_b: Box<Account<'info, TokenAccount>>,

/// CHECK: Admin account from AMM state
#[account(
constraint = admin.key() == amm.admin
)]
pub admin: AccountInfo<'info>,

#[account(
init_if_needed,
payer = payer,
associated_token::mint = liquidity_mint,
associated_token::authority = admin, // Now using the admin AccountInfo
)]
pub admin_fee_account: Box<Account<'info, TokenAccount>>,

/// The account paying for all rents
#[account(mut)]
pub payer: Signer<'info>,
Expand All @@ -109,7 +131,7 @@ pub fn deposit_liquidity(
amount_b: u64,
) -> Result<()> {
// Prevent depositing assets the depositor does not own
let mut amount_a = if amount_a > ctx.accounts.depositor_account_a.amount {
let mut amount_a: u64 = if amount_a > ctx.accounts.depositor_account_a.amount {
ctx.accounts.depositor_account_a.amount
} else {
amount_a
Expand All @@ -126,7 +148,6 @@ pub fn deposit_liquidity(
// Defining pool creation like this allows attackers to frontrun pool creation with bad ratios
let pool_creation = pool_a.amount == 0 && pool_b.amount == 0;
(amount_a, amount_b) = if pool_creation {
// Add as is if there is no liquidity
(amount_a, amount_b)
} else {
if pool_a.amount > pool_b.amount {
Expand Down Expand Up @@ -162,6 +183,15 @@ pub fn deposit_liquidity(
liquidity -= MINIMUM_LIQUIDITY;
}

// Calculate protocol fee
let protocol_fee = (liquidity as u128)
.checked_mul(ctx.accounts.amm.protocol_fee_percentage as u128)
.ok_or(DepositError::NumberOverflow)?
.checked_div(PERCENT_BASE as u128)
.ok_or(DepositError::NumberOverflow)? as u64;

let user_liquidity = liquidity.checked_sub(protocol_fee).ok_or(DepositError::NumberOverflow)?;

// Transfer tokens to the pool
token::transfer(
CpiContext::new(
Expand All @@ -186,14 +216,10 @@ pub fn deposit_liquidity(
amount_b,
)?;

// 更新池子状态
ctx.accounts.pool.token_a_amount += amount_a;
ctx.accounts.pool.token_b_amount += amount_b;
msg!("token_a_amount: {}", ctx.accounts.pool.token_a_amount);
msg!("token_b_amount: {}", ctx.accounts.pool.token_b_amount);

// Update pool state
ctx.accounts.pool.token_a_amount = ctx.accounts.pool.token_a_amount.checked_add(amount_a).ok_or(DepositError::NumberOverflow)?;
ctx.accounts.pool.token_b_amount = ctx.accounts.pool.token_b_amount.checked_add(amount_b).ok_or(DepositError::NumberOverflow)?;

// Mint the liquidity to user
let authority_bump = ctx.bumps.pool_authority;
let authority_seeds = &[
&ctx.accounts.pool.amm.to_bytes(),
Expand All @@ -203,6 +229,8 @@ pub fn deposit_liquidity(
&[authority_bump],
];
let signer_seeds = &[&authority_seeds[..]];

// Mint liquidity tokens to user
token::mint_to(
CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
Expand All @@ -213,9 +241,25 @@ pub fn deposit_liquidity(
},
signer_seeds,
),
liquidity,
user_liquidity,
)?;

// Mint protocol fee to admin if it's not zero
if protocol_fee > 0 {
token::mint_to(
CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
MintTo {
mint: ctx.accounts.liquidity_mint.to_account_info(),
to: ctx.accounts.admin_fee_account.to_account_info(),
authority: ctx.accounts.admin.to_account_info(),
},
signer_seeds,
),
protocol_fee,
)?;
}

Ok(())
}

Expand Down
122 changes: 80 additions & 42 deletions fall/programs/fall/src/instructions/swap_exact_tokens_for_tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,21 @@ use anchor_spl::{
associated_token::AssociatedToken,
token::{self, Mint, Token, TokenAccount, Transfer},
};
use fixed::types::I64F64;
use crate::{
constants::AUTHORITY_SEED,
state:: Pool,
constants::{AUTHORITY_SEED, PERCENT_BASE},
state::{Amm, Pool},
};

#[derive(Accounts)]
pub struct SwapExactTokensForTokens<'info> {
#[account(
seeds = [
amm.id.as_ref()
],
bump,
)]
pub amm: Box<Account<'info, Amm>>,

#[account(
seeds = [
pool.amm.as_ref(),
Expand Down Expand Up @@ -84,40 +91,62 @@ pub fn swap_exact_tokens_for_tokens<'info>(
input_amount: u64,
min_output_amount: u64,
) -> Result<()> {
// Zero amount check
require!(input_amount > 0, SwapError::InvalidInput);

// Check pool is not empty
require!(
ctx.accounts.pool_account_a.amount > 0 && ctx.accounts.pool_account_b.amount > 0,
SwapError::EmptyPool
);

// Prevent depositing assets the depositor does not own
let input = if swap_a && input_amount > ctx.accounts.trader_account_a.amount {
ctx.accounts.trader_account_a.amount
} else if !swap_a && input_amount > ctx.accounts.trader_account_b.amount {
ctx.accounts.trader_account_b.amount
let input = if swap_a {
require!(ctx.accounts.trader_account_a.amount >= input_amount, SwapError::InsufficientBalance);
input_amount
} else {
require!(ctx.accounts.trader_account_b.amount >= input_amount, SwapError::InsufficientBalance);
input_amount
};

// Apply trading fee, used to compute the output
let taxed_input = input - input * ctx.accounts.pool.fee as u64 / 10000;
// Apply trading fee with overflow protection
let fee_amount = input
.checked_mul(ctx.accounts.amm.liquidity_fee as u64)
.ok_or(SwapError::MathOverflow)?
.checked_div(PERCENT_BASE)
.ok_or(SwapError::MathOverflow)?;
let taxed_input = input.checked_sub(fee_amount).ok_or(SwapError::MathOverflow)?;

let pool_a = &ctx.accounts.pool_account_a;
let pool_b = &ctx.accounts.pool_account_b;

// Calculate output amount using u128 for better precision
let output = if swap_a {
// 用 token_a 交换 token_b
let new_pool_a_amount = I64F64::from_num(pool_a.amount) + I64F64::from_num(taxed_input); // 新的池子A的数量
let k = I64F64::from_num(pool_a.amount) * I64F64::from_num(pool_b.amount); // 恒定乘积
let new_pool_b_amount = k / new_pool_a_amount; // 新池子B的数量

// 输出的 token_b 数量是原池子中 token_b 的数量减去新的 token_b 数量
I64F64::from_num(pool_b.amount) - new_pool_b_amount
let new_pool_a: u128 = (pool_a.amount as u128)
.checked_add(taxed_input as u128)
.ok_or(SwapError::MathOverflow)?;
let k = (pool_a.amount as u128)
.checked_mul(pool_b.amount as u128)
.ok_or(SwapError::MathOverflow)?;
let output = (pool_b.amount as u128)
.checked_sub(k.checked_div(new_pool_a).ok_or(SwapError::MathOverflow)?)
.ok_or(SwapError::MathOverflow)?;
output as u64
} else {
// 用 token_b 交换 token_a
let new_pool_b_amount = I64F64::from_num(pool_b.amount) + I64F64::from_num(taxed_input); // 新的池子B的数量
let k = I64F64::from_num(pool_a.amount) * I64F64::from_num(pool_b.amount); // 恒定乘积
let new_pool_a_amount = k / new_pool_b_amount; // 新池子A的数量
// 输出的 token_a 数量是原池子中 token_a 的数量减去新的 token_a 数量
I64F64::from_num(pool_a.amount) - new_pool_a_amount
let new_pool_b: u128 = (pool_b.amount as u128)
.checked_add(taxed_input as u128)
.ok_or(SwapError::MathOverflow)?;
let k = (pool_a.amount as u128)
.checked_mul(pool_b.amount as u128)
.ok_or(SwapError::MathOverflow)?;
let output = (pool_a.amount as u128)
.checked_sub(k.checked_div(new_pool_b).ok_or(SwapError::MathOverflow)?)
.ok_or(SwapError::MathOverflow)?;
output as u64
};

if output < min_output_amount {
return err!(SwapError::OutputTooSmall);
}
// Slippage check
require!(output >= min_output_amount, SwapError::ExcessiveSlippage);

// Compute the invariant before the trade
let invariant: u64 = pool_a.amount * pool_b.amount;
Expand Down Expand Up @@ -153,21 +182,20 @@ pub fn swap_exact_tokens_for_tokens<'info>(
},
signer_seeds,
),
output.to_num::<u64>(),
output,
)?;
// Verify the invariant still holds
// Reload accounts because of the CPIs
// We tolerate if the new invariant is higher because it means a rounding error for LPs
if invariant > (ctx.accounts.pool_account_a.amount+taxed_input) * (ctx.accounts.pool_account_b.amount-output.to_num::<u64>()) {
if invariant > (ctx.accounts.pool_account_a.amount+taxed_input) * (ctx.accounts.pool_account_b.amount-output) {
return err!(SwapError::InvariantViolated);
}
// 更新池子状态
msg!("swap_exact_tokens_for_tokens: pool.token_a_amount: {}, pool.token_b_amount: {}",
ctx.accounts.pool.token_a_amount, ctx.accounts.pool.token_b_amount);
msg!("swap_exact_tokens_for_tokens: output: {}, taxed_input: {}",
output, taxed_input);
ctx.accounts.pool.token_a_amount += taxed_input;
ctx.accounts.pool.token_b_amount -= output.to_num::<u64>();
ctx.accounts.pool.token_a_amount = ctx.accounts.pool.token_a_amount
.checked_add(taxed_input)
.ok_or(SwapError::MathOverflow)?;
ctx.accounts.pool.token_b_amount = ctx.accounts.pool.token_b_amount
.checked_sub(output)
.ok_or(SwapError::MathOverflow)?;
} else {
token::transfer(
CpiContext::new_with_signer(
Expand All @@ -179,7 +207,7 @@ pub fn swap_exact_tokens_for_tokens<'info>(
},
signer_seeds,
),
output.to_num::<u64>(),
output,
)?;
token::transfer(
CpiContext::new(
Expand All @@ -195,16 +223,16 @@ pub fn swap_exact_tokens_for_tokens<'info>(
// Verify the invariant still holds
// Reload accounts because of the CPIs
// We tolerate if the new invariant is higher because it means a rounding error for LPs
if invariant > (ctx.accounts.pool_account_a.amount-output.to_num::<u64>()) * (ctx.accounts.pool_account_b.amount+taxed_input) {
if invariant > (ctx.accounts.pool_account_a.amount-output) * (ctx.accounts.pool_account_b.amount+taxed_input) {
return err!(SwapError::InvariantViolated);
}
// 更新池子状态
msg!("swap_exact_tokens_for_tokens: pool.token_a_amount: {}, pool.token_b_amount: {}",
ctx.accounts.pool.token_a_amount, ctx.accounts.pool.token_b_amount);
msg!("swap_exact_tokens_for_tokens: output: {}, taxed_input: {}",
output, taxed_input);
ctx.accounts.pool.token_a_amount -= output.to_num::<u64>();
ctx.accounts.pool.token_b_amount += taxed_input;
ctx.accounts.pool.token_a_amount = ctx.accounts.pool.token_a_amount
.checked_sub(output)
.ok_or(SwapError::MathOverflow)?;
ctx.accounts.pool.token_b_amount = ctx.accounts.pool.token_b_amount
.checked_add(taxed_input)
.ok_or(SwapError::MathOverflow)?;
}

Ok(())
Expand All @@ -216,4 +244,14 @@ pub enum SwapError {
OutputTooSmall,
#[msg("Invariant violated")]
InvariantViolated,
#[msg("Invalid input amount")]
InvalidInput,
#[msg("Pool is empty")]
EmptyPool,
#[msg("Insufficient balance")]
InsufficientBalance,
#[msg("Math overflow")]
MathOverflow,
#[msg("Slippage tolerance exceeded")]
ExcessiveSlippage,
}
Loading

0 comments on commit ef8eec4

Please sign in to comment.