1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
use {
crate::{
math,
state::{
custody::Custody,
oracle::OraclePrice,
perpetuals::{AmountAndFee, Perpetuals},
pool::{AumCalcMode, Pool},
},
},
anchor_lang::prelude::*,
anchor_spl::token::Mint,
solana_program::program_error::ProgramError,
};
#[derive(Accounts)]
pub struct GetRemoveLiquidityAmountAndFee<'info> {
#[account(
seeds = [b"perpetuals"],
bump = perpetuals.perpetuals_bump
)]
pub perpetuals: Box<Account<'info, Perpetuals>>,
#[account(
seeds = [b"pool",
pool.name.as_bytes()],
bump = pool.bump
)]
pub pool: Box<Account<'info, Pool>>,
#[account(
seeds = [b"custody",
pool.key().as_ref(),
custody.mint.as_ref()],
bump = custody.bump
)]
pub custody: Box<Account<'info, Custody>>,
#[account(
constraint = custody_oracle_account.key() == custody.oracle.oracle_account
)]
pub custody_oracle_account: AccountInfo<'info>,
#[account(
seeds = [b"lp_token_mint",
pool.key().as_ref()],
bump = pool.lp_token_bump
)]
pub lp_token_mint: Box<Account<'info, Mint>>,
}
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct GetRemoveLiquidityAmountAndFeeParams {
lp_amount_in: u64,
}
pub fn get_remove_liquidity_amount_and_fee(
ctx: Context<GetRemoveLiquidityAmountAndFee>,
params: &GetRemoveLiquidityAmountAndFeeParams,
) -> Result<AmountAndFee> {
if params.lp_amount_in == 0 {
return Err(ProgramError::InvalidArgument.into());
}
let pool = &ctx.accounts.pool;
let custody = ctx.accounts.custody.as_mut();
let token_id = pool.get_token_id(&custody.key())?;
let curtime = ctx.accounts.perpetuals.get_time()?;
let token_price = OraclePrice::new_from_oracle(
custody.oracle.oracle_type,
&ctx.accounts.custody_oracle_account.to_account_info(),
custody.oracle.max_price_error,
custody.oracle.max_price_age_sec,
curtime,
false,
)?;
let token_ema_price = OraclePrice::new_from_oracle(
custody.oracle.oracle_type,
&ctx.accounts.custody_oracle_account.to_account_info(),
custody.oracle.max_price_error,
custody.oracle.max_price_age_sec,
curtime,
custody.pricing.use_ema,
)?;
let pool_amount_usd =
pool.get_assets_under_management_usd(AumCalcMode::Min, ctx.remaining_accounts, curtime)?;
let remove_amount_usd = math::checked_as_u64(math::checked_div(
math::checked_mul(pool_amount_usd, params.lp_amount_in as u128)?,
ctx.accounts.lp_token_mint.supply as u128,
)?)?;
let max_price = if token_price > token_ema_price {
token_price
} else {
token_ema_price
};
let remove_amount = max_price.get_token_amount(remove_amount_usd, custody.decimals)?;
let fee_amount =
pool.get_remove_liquidity_fee(token_id, remove_amount, custody, &token_price)?;
let transfer_amount = math::checked_sub(remove_amount, fee_amount)?;
Ok(AmountAndFee {
amount: transfer_amount,
fee: fee_amount,
})
}