Skip to content
This repository was archived by the owner on Jan 13, 2025. It is now read-only.

Commit f29860b

Browse files
authored
Merge pull request #6 from AdrenaDEX/feat/get_lp_price
Implement get_lp_token_price view instruction
2 parents 34f9bbb + 92ab707 commit f29860b

File tree

12 files changed

+529
-6
lines changed

12 files changed

+529
-6
lines changed

programs/perpetuals/src/instructions.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub mod get_entry_price_and_fee;
2626
pub mod get_exit_price_and_fee;
2727
pub mod get_liquidation_price;
2828
pub mod get_liquidation_state;
29+
pub mod get_lp_token_price;
2930
pub mod get_oracle_price;
3031
pub mod get_pnl;
3132
pub mod get_remove_liquidity_amount_and_fee;
@@ -41,7 +42,7 @@ pub use {
4142
add_collateral::*, add_custody::*, add_liquidity::*, add_pool::*, close_position::*,
4243
get_add_liquidity_amount_and_fee::*, get_assets_under_management::*,
4344
get_entry_price_and_fee::*, get_exit_price_and_fee::*, get_liquidation_price::*,
44-
get_liquidation_state::*, get_oracle_price::*, get_pnl::*,
45+
get_liquidation_state::*, get_lp_token_price::*, get_oracle_price::*, get_pnl::*,
4546
get_remove_liquidity_amount_and_fee::*, get_swap_amount_and_fees::*, init::*, liquidate::*,
4647
open_position::*, remove_collateral::*, remove_custody::*, remove_liquidity::*, remove_pool::*,
4748
set_admin_signers::*, set_custody_config::*, set_permissions::*, set_test_oracle_price::*,
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//! GetLpTokenPrice instruction handler
2+
3+
use {
4+
crate::{
5+
math,
6+
state::{
7+
perpetuals::Perpetuals,
8+
pool::{AumCalcMode, Pool},
9+
},
10+
},
11+
anchor_lang::prelude::*,
12+
anchor_spl::token::Mint,
13+
num_traits::Zero,
14+
};
15+
16+
#[derive(Accounts)]
17+
pub struct GetLpTokenPrice<'info> {
18+
#[account(
19+
seeds = [b"perpetuals"],
20+
bump = perpetuals.perpetuals_bump
21+
)]
22+
pub perpetuals: Box<Account<'info, Perpetuals>>,
23+
24+
#[account(
25+
seeds = [b"pool",
26+
pool.name.as_bytes()],
27+
bump = pool.bump
28+
)]
29+
pub pool: Box<Account<'info, Pool>>,
30+
31+
#[account(
32+
seeds = [b"lp_token_mint",
33+
pool.key().as_ref()],
34+
bump = pool.lp_token_bump
35+
)]
36+
pub lp_token_mint: Box<Account<'info, Mint>>,
37+
// remaining accounts:
38+
// pool.tokens.len() custody accounts (read-only, unsigned)
39+
// pool.tokens.len() custody oracles (read-only, unsigned)
40+
}
41+
42+
#[derive(AnchorSerialize, AnchorDeserialize)]
43+
pub struct GetLpTokenPriceParams {}
44+
45+
pub fn get_lp_token_price(
46+
ctx: Context<GetLpTokenPrice>,
47+
_params: &GetLpTokenPriceParams,
48+
) -> Result<u64> {
49+
let aum_usd = math::checked_as_u64(ctx.accounts.pool.get_assets_under_management_usd(
50+
AumCalcMode::EMA,
51+
ctx.remaining_accounts,
52+
ctx.accounts.perpetuals.get_time()?,
53+
)?)?;
54+
55+
msg!("aum_usd: {}", aum_usd);
56+
57+
let lp_supply = ctx.accounts.lp_token_mint.supply;
58+
59+
msg!("lp_supply: {}", lp_supply);
60+
61+
if lp_supply.is_zero() {
62+
return Ok(0);
63+
}
64+
65+
let price_usd = math::checked_decimal_div(
66+
aum_usd,
67+
-(Perpetuals::USD_DECIMALS as i32),
68+
lp_supply,
69+
-(Perpetuals::LP_DECIMALS as i32),
70+
-(Perpetuals::USD_DECIMALS as i32),
71+
)?;
72+
73+
msg!("price_usd: {}", price_usd);
74+
75+
Ok(price_usd)
76+
}

programs/perpetuals/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,4 +231,11 @@ pub mod perpetuals {
231231
) -> Result<u128> {
232232
instructions::get_assets_under_management(ctx, &params)
233233
}
234+
235+
pub fn get_lp_token_price(
236+
ctx: Context<GetLpTokenPrice>,
237+
params: GetLpTokenPriceParams,
238+
) -> Result<u64> {
239+
instructions::get_lp_token_price(ctx, &params)
240+
}
234241
}

programs/perpetuals/src/math.rs

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,19 @@ where
353353
}
354354
}
355355

356+
pub fn checked_as_f64<T>(arg: T) -> Result<f64>
357+
where
358+
T: Display + num_traits::ToPrimitive + Clone,
359+
{
360+
let option: Option<f64> = num_traits::NumCast::from(arg.clone());
361+
if let Some(res) = option {
362+
Ok(res)
363+
} else {
364+
msg!("Error: Overflow in {} as f64", arg);
365+
err!(PerpetualsError::MathOverflow)
366+
}
367+
}
368+
356369
pub fn scale_to_exponent(arg: u64, exponent: i32, target_exponent: i32) -> Result<u64> {
357370
if target_exponent == exponent {
358371
return Ok(arg);
@@ -366,7 +379,10 @@ pub fn scale_to_exponent(arg: u64, exponent: i32, target_exponent: i32) -> Resul
366379
}
367380

368381
pub fn to_ui_amount(amount: u64, decimals: u8) -> Result<f64> {
369-
checked_float_div(amount as f64, checked_powi(10.0, decimals as i32)?)
382+
checked_float_div(
383+
checked_as_f64(amount)?,
384+
checked_powi(10.0, decimals as i32)?,
385+
)
370386
}
371387

372388
pub fn to_token_amount(ui_amount: f64, decimals: u8) -> Result<u64> {
@@ -375,3 +391,69 @@ pub fn to_token_amount(ui_amount: f64, decimals: u8) -> Result<u64> {
375391
checked_powi(10.0, decimals as i32)?,
376392
)?)
377393
}
394+
395+
#[cfg(test)]
396+
mod tests {
397+
use super::*;
398+
399+
#[test]
400+
fn test_checked_decimal_div_ok() {
401+
// Nominal test
402+
assert_eq!(
403+
2_000_000,
404+
checked_decimal_div(1_000, -6, 500, -6, -6).unwrap()
405+
);
406+
407+
// Different exponents
408+
assert_eq!(
409+
2_000_000_000,
410+
checked_decimal_div(1_000, -6, 500, -9, -6).unwrap()
411+
);
412+
413+
// Different exponents
414+
assert_eq!(2_000, checked_decimal_div(1_000, -9, 500, -6, -6).unwrap());
415+
416+
// MAX coefficient values
417+
assert_eq!(
418+
1_000_000,
419+
checked_decimal_div(u64::MAX, -6, u64::MAX, -6, -6).unwrap()
420+
);
421+
422+
assert_eq!(0, checked_decimal_div(0, -6, u64::MAX, -6, -6).unwrap());
423+
424+
// Maximum coefficients delta depends on target exponent
425+
assert_eq!(
426+
18_446_744_073_709_000_000,
427+
checked_decimal_div(
428+
checked_div(u64::MAX, checked_pow(10u64, 6).unwrap()).unwrap(),
429+
-6,
430+
1,
431+
-6,
432+
-6,
433+
)
434+
.unwrap()
435+
);
436+
437+
// Maximum coefficients delta depends on target exponent
438+
assert_eq!(
439+
18_446_744_073_000_000_000,
440+
checked_decimal_div(
441+
checked_div(u64::MAX, checked_pow(10u64, 9).unwrap()).unwrap(),
442+
-6,
443+
1,
444+
-6,
445+
-9,
446+
)
447+
.unwrap()
448+
);
449+
}
450+
451+
#[test]
452+
fn test_checked_decimal_div_ko() {
453+
// Division by zero
454+
assert!(checked_decimal_div(1_000_000, -6, 0, -6, -6).is_err());
455+
456+
// Overflowing result
457+
assert!(checked_decimal_div(u64::MAX, -6, 1, -6, -6).is_err());
458+
}
459+
}

programs/perpetuals/src/state/oracle.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,10 @@ impl OraclePrice {
186186
}
187187

188188
pub fn checked_as_f64(&self) -> Result<f64> {
189-
math::checked_float_mul(self.price as f64, math::checked_powi(10.0, self.exponent)?)
189+
math::checked_float_mul(
190+
math::checked_as_f64(self.price)?,
191+
math::checked_powi(10.0, self.exponent)?,
192+
)
190193
}
191194

192195
// private helpers

programs/perpetuals/tests/native/instructions/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub mod test_add_custody;
22
pub mod test_add_liquidity;
33
pub mod test_add_pool;
44
pub mod test_close_position;
5+
pub mod test_get_lp_token_price;
56
pub mod test_init;
67
pub mod test_liquidate;
78
pub mod test_open_position;
@@ -12,6 +13,7 @@ pub mod test_swap;
1213

1314
pub use {
1415
test_add_custody::*, test_add_liquidity::*, test_add_pool::*, test_close_position::*,
15-
test_init::*, test_liquidate::*, test_open_position::*, test_remove_liquidity::*,
16-
test_set_custody_config::*, test_set_test_oracle_price::*, test_swap::*,
16+
test_get_lp_token_price::*, test_init::*, test_liquidate::*, test_open_position::*,
17+
test_remove_liquidity::*, test_set_custody_config::*, test_set_test_oracle_price::*,
18+
test_swap::*,
1719
};
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
use {
2+
crate::utils::{self, pda},
3+
anchor_lang::{prelude::Pubkey, ToAccountMetas},
4+
perpetuals::{
5+
instructions::GetLpTokenPriceParams,
6+
state::{custody::Custody, pool::Pool},
7+
},
8+
solana_program::instruction::AccountMeta,
9+
solana_program_test::{BanksClientError, ProgramTestContext},
10+
solana_sdk::signer::keypair::Keypair,
11+
};
12+
13+
#[allow(clippy::too_many_arguments)]
14+
pub async fn test_get_lp_token_price(
15+
program_test_ctx: &mut ProgramTestContext,
16+
payer: &Keypair,
17+
pool_pda: &Pubkey,
18+
lp_token_mint_pda: &Pubkey,
19+
) -> std::result::Result<u64, BanksClientError> {
20+
// ==== WHEN ==============================================================
21+
let perpetuals_pda = pda::get_perpetuals_pda().0;
22+
23+
let accounts_meta = {
24+
let accounts = perpetuals::accounts::GetLpTokenPrice {
25+
perpetuals: perpetuals_pda,
26+
pool: *pool_pda,
27+
lp_token_mint: *lp_token_mint_pda,
28+
};
29+
30+
let mut accounts_meta = accounts.to_account_metas(None);
31+
32+
let pool_account = utils::get_account::<Pool>(program_test_ctx, *pool_pda).await;
33+
34+
// For each token, add custody account as remaining_account
35+
for custody in &pool_account.custodies {
36+
accounts_meta.push(AccountMeta {
37+
pubkey: *custody,
38+
is_signer: false,
39+
is_writable: false,
40+
});
41+
}
42+
43+
// For each token, add custody oracle account as remaining_account
44+
for custody in &pool_account.custodies {
45+
let custody_account = utils::get_account::<Custody>(program_test_ctx, *custody).await;
46+
47+
accounts_meta.push(AccountMeta {
48+
pubkey: custody_account.oracle.oracle_account,
49+
is_signer: false,
50+
is_writable: false,
51+
});
52+
}
53+
54+
accounts_meta
55+
};
56+
57+
let result: u64 = utils::create_and_simulate_perpetuals_view_ix(
58+
program_test_ctx,
59+
accounts_meta,
60+
perpetuals::instruction::GetLpTokenPrice {
61+
params: GetLpTokenPriceParams {},
62+
},
63+
payer,
64+
)
65+
.await?;
66+
67+
// ==== THEN ==============================================================
68+
Ok(result)
69+
}

programs/perpetuals/tests/native/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@ pub async fn test_integration() {
1515
tests_suite::position::min_max_leverage().await;
1616
tests_suite::position::liquidate_position().await;
1717
tests_suite::position::max_user_profit().await;
18+
19+
tests_suite::lp_token::lp_token_price().await;
1820
}

0 commit comments

Comments
 (0)