use crate::curve::CurveCalculator;
use crate::curve::RoundDirection;
use crate::error::ErrorCode;
use crate::states::*;
use crate::utils::token::*;
use anchor_lang::prelude::*;
use anchor_spl::token::Token;
use anchor_spl::token_interface::{Mint, Token2022, TokenAccount};
#[derive(Accounts)]
pub struct Withdraw<'info> {
pub owner: Signer<'info>,
#[account(
seeds = [
crate::AUTH_SEED.as_bytes(),
],
bump,
)]
pub authority: UncheckedAccount<'info>,
#[account(mut)]
pub pool_state: AccountLoader<'info, PoolState>,
#[account(
mut,
token::authority = owner
)]
pub owner_lp_token: Box<InterfaceAccount<'info, TokenAccount>>,
#[account(
mut,
token::mint = token_0_vault.mint,
token::authority = owner
)]
pub token_0_account: Box<InterfaceAccount<'info, TokenAccount>>,
#[account(
mut,
token::mint = token_1_vault.mint,
token::authority = owner
)]
pub token_1_account: Box<InterfaceAccount<'info, TokenAccount>>,
#[account(
mut,
constraint = token_0_vault.key() == pool_state.load()?.token_0_vault
)]
pub token_0_vault: Box<InterfaceAccount<'info, TokenAccount>>,
#[account(
mut,
constraint = token_1_vault.key() == pool_state.load()?.token_1_vault
)]
pub token_1_vault: Box<InterfaceAccount<'info, TokenAccount>>,
pub token_program: Program<'info, Token>,
pub token_program_2022: Program<'info, Token2022>,
#[account(
address = token_0_vault.mint
)]
pub vault_0_mint: Box<InterfaceAccount<'info, Mint>>,
#[account(
address = token_1_vault.mint
)]
pub vault_1_mint: Box<InterfaceAccount<'info, Mint>>,
#[account(
mut,
address = pool_state.load()?.lp_mint @ ErrorCode::IncorrectLpMint)
]
pub lp_mint: Box<InterfaceAccount<'info, Mint>>,
#[account(
address = spl_memo::id()
)]
pub memo_program: UncheckedAccount<'info>,
}
pub fn withdraw(
ctx: Context<Withdraw>,
lp_token_amount: u64,
minimum_token_0_amount: u64,
minimum_token_1_amount: u64,
) -> Result<()> {
require_gt!(ctx.accounts.lp_mint.supply, 0);
let pool_id = ctx.accounts.pool_state.key();
let pool_state = &mut ctx.accounts.pool_state.load_mut()?;
if !pool_state.get_status_by_bit(PoolStatusBitIndex::Withdraw) {
return err!(ErrorCode::NotApproved);
}
let (total_token_0_amount, total_token_1_amount) = pool_state.vault_amount_without_fee(
ctx.accounts.token_0_vault.amount,
ctx.accounts.token_1_vault.amount,
);
let mut amm = pool_state.amm;
let current_supply = ctx.accounts.lp_mint.supply;
let virtual_liquidity = current_supply.checked_add(lp_token_amount).unwrap();
let real_liquidity = amm.apply_sell(virtual_liquidity as u128);
if real_liquidity.is_none() {
return err!(ErrorCode::SellResultNone);
}
let real_liquidity = pool_state.lp_supply.checked_sub(real_liquidity.unwrap() as u64).unwrap();
let results = CurveCalculator::lp_tokens_to_trading_tokens(
u128::from(real_liquidity),
u128::from(pool_state.lp_supply),
u128::from(total_token_0_amount),
u128::from(total_token_1_amount),
RoundDirection::Floor,
)
.ok_or(ErrorCode::ZeroTradingTokens)?;
let token_0_amount = u64::try_from(results.token_0_amount).unwrap();
let token_0_amount = std::cmp::min(total_token_0_amount, token_0_amount);
let (receive_token_0_amount, token_0_transfer_fee) = {
let transfer_fee = get_transfer_fee(&ctx.accounts.vault_0_mint.to_account_info(), token_0_amount)?;
(
token_0_amount.checked_sub(transfer_fee).unwrap(),
transfer_fee,
)
};
let token_1_amount = u64::try_from(results.token_1_amount).unwrap();
let token_1_amount = std::cmp::min(total_token_1_amount, token_1_amount);
let (receive_token_1_amount, token_1_transfer_fee) = {
let transfer_fee = get_transfer_fee(&ctx.accounts.vault_1_mint.to_account_info(), token_1_amount)?;
(
token_1_amount.checked_sub(transfer_fee).unwrap(),
transfer_fee,
)
};
pool_state.amm = amm;
#[cfg(feature = "enable-log")]
msg!(
"results.token_0_amount;{}, results.token_1_amount:{},receive_token_0_amount:{},token_0_transfer_fee:{},
receive_token_1_amount:{},token_1_transfer_fee:{}",
results.token_0_amount,
results.token_1_amount,
receive_token_0_amount,
token_0_transfer_fee,
receive_token_1_amount,
token_1_transfer_fee
);
emit!(LpChangeEvent {
pool_id,
lp_amount_before: pool_state.lp_supply,
token_0_vault_before: total_token_0_amount,
token_1_vault_before: total_token_1_amount,
token_0_amount: receive_token_0_amount,
token_1_amount: receive_token_1_amount,
token_0_transfer_fee,
token_1_transfer_fee,
change_type: 1
});
msg!("pool_state: {:?}", pool_state);
msg!("lp_token_amount: {}", lp_token_amount);
msg!("receive_token_0_amount: {:?}", receive_token_0_amount);
msg!("minimum_token_0_amount: {:?}", minimum_token_0_amount);
msg!("receive_token_1_amount: {:?}", receive_token_1_amount);
msg!("minimum_token_1_amount: {}", minimum_token_1_amount);
if receive_token_0_amount < minimum_token_0_amount
|| receive_token_1_amount < minimum_token_1_amount
{
return Err(ErrorCode::ExceededSlippage.into());
}
pool_state.lp_supply = pool_state.lp_supply.checked_sub(real_liquidity as u64).unwrap();
token_burn(
ctx.accounts.owner.to_account_info(),
ctx.accounts.token_program.to_account_info(),
ctx.accounts.lp_mint.to_account_info(),
ctx.accounts.owner_lp_token.to_account_info(),
lp_token_amount,
&[&[crate::AUTH_SEED.as_bytes(), &[pool_state.auth_bump]]],
)?;
transfer_from_pool_vault_to_user(
ctx.accounts.authority.to_account_info(),
ctx.accounts.token_0_vault.to_account_info(),
ctx.accounts.token_0_account.to_account_info(),
ctx.accounts.vault_0_mint.to_account_info(),
if ctx.accounts.vault_0_mint.to_account_info().owner == ctx.accounts.token_program.key {
ctx.accounts.token_program.to_account_info()
} else {
ctx.accounts.token_program_2022.to_account_info()
},
receive_token_0_amount,
ctx.accounts.vault_0_mint.decimals,
&[&[crate::AUTH_SEED.as_bytes(), &[pool_state.auth_bump]]],
)?;
transfer_from_pool_vault_to_user(
ctx.accounts.authority.to_account_info(),
ctx.accounts.token_1_vault.to_account_info(),
ctx.accounts.token_1_account.to_account_info(),
ctx.accounts.vault_1_mint.to_account_info(),
if ctx.accounts.vault_1_mint.to_account_info().owner == ctx.accounts.token_program.key {
ctx.accounts.token_program.to_account_info()
} else {
ctx.accounts.token_program_2022.to_account_info()
},
receive_token_1_amount,
ctx.accounts.vault_1_mint.decimals,
&[&[crate::AUTH_SEED.as_bytes(), &[pool_state.auth_bump]]],
)?;
pool_state.recent_epoch = Clock::get()?.epoch;
Ok(())
}