defituna_client/implementation/
tuna_position.rs1use crate::accounts::{Market, Vault};
2use crate::TunaError as ErrorCode;
3use solana_pubkey::Pubkey;
4use std::any::Any;
5
6#[derive(PartialEq, Eq)]
7pub enum TunaPositionKind {
8 Liquidity,
9 Spot,
10}
11
12#[repr(u8)]
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum TunaLimitOrderType {
15 StopLoss = 0,
16 TakeProfit = 1,
17}
18
19pub trait TunaPosition: Any {
20 fn kind(&self) -> TunaPositionKind;
21 fn get_version(&self) -> u16;
22 fn get_pool(&self) -> Pubkey;
23 fn get_authority(&self) -> Pubkey;
24 fn get_mint_a(&self) -> Pubkey;
25 fn get_mint_b(&self) -> Pubkey;
26 fn get_total_balance(&self, sqrt_price: u128) -> Result<(u64, u64), ErrorCode>;
27 fn get_leftovers(&self) -> (u64, u64);
28 fn get_loan_shares(&self) -> (u64, u64);
29 fn compute_total_and_debt(&self, sqrt_price: u128, vault_a: &Vault, vault_b: &Vault) -> Result<(u64, u64), ErrorCode>;
30 fn compute_leverage(&self, sqrt_price: u128, vault_a: &Vault, vault_b: &Vault) -> Result<f64, ErrorCode>;
31 fn is_limit_order_reached(&self, sqrt_price: u128) -> Option<TunaLimitOrderType>;
32 fn is_liquidated_or_closed(&self) -> bool;
33 fn is_healthy(&self, sqrt_price: u128, market: &Market, vault_a: &Vault, vault_b: &Vault) -> Result<(bool, u32), ErrorCode>;
34}
35
36#[macro_export]
37macro_rules! impl_tuna_position {
38 ($t:ty) => {
39 impl $t {
40 pub fn compute_total_and_debt(&self, sqrt_price: u128, vault_a: &Vault, vault_b: &Vault) -> Result<(u64, u64), ErrorCode> {
42 let (mut total_a, mut total_b) = self.get_total_balance(sqrt_price)?;
43
44 total_a += self.get_leftovers().0;
46 total_b += self.get_leftovers().1;
47
48 let price = sqrt_price_x64_to_price_x64(sqrt_price)?;
49
50 let total = U64F64::from(total_a)
51 .checked_mul(price)
52 .ok_or(ErrorCode::MathOverflow)?
53 .to_num::<u64>()
54 .checked_add(total_b)
55 .ok_or(ErrorCode::MathOverflow)?;
56
57 let (loan_shares_a, loan_shares_b) = self.get_loan_shares();
58
59 let debt_a = vault_a.calculate_borrowed_funds(loan_shares_a, Rounding::Up)?;
60 let debt_b = vault_b.calculate_borrowed_funds(loan_shares_b, Rounding::Up)?;
61
62 let debt = U64F64::from(debt_a)
63 .checked_mul(price)
64 .ok_or(ErrorCode::MathOverflow)?
65 .to_num::<u64>()
66 .checked_add(debt_b)
67 .ok_or(ErrorCode::MathOverflow)?;
68
69 Ok((total, debt))
70 }
71
72 pub fn compute_leverage(&self, sqrt_price: u128, vault_a: &Vault, vault_b: &Vault) -> Result<f64, ErrorCode> {
74 let (total, debt) = self.compute_total_and_debt(sqrt_price, vault_a, vault_b)?;
75
76 if total == 0 {
78 return Ok(1.0);
79 }
80
81 if debt >= total {
82 return Err(ErrorCode::LeverageIsOutOfRange.into());
83 }
84
85 let leverage = total as f64 / (total - debt) as f64;
86 Ok(leverage)
87 }
88
89 pub fn is_healthy(&self, sqrt_price: u128, market: &Market, vault_a: &Vault, vault_b: &Vault) -> Result<(bool, u32), ErrorCode> {
91 let (loan_shares_a, loan_shares_b) = self.get_loan_shares();
92
93 if loan_shares_a == 0 && loan_shares_b == 0 {
94 return Ok((true, 0));
95 }
96
97 if vault_a.mint != self.mint_a || vault_b.mint != self.mint_b {
98 return Err(ErrorCode::InvalidInstructionArguments.into());
99 }
100
101 let (total, debt) = self.compute_total_and_debt(sqrt_price, vault_a, vault_b)?;
102
103 let healthy = total == 0 || debt <= (total as u128 * market.liquidation_threshold as u128 / HUNDRED_PERCENT as u128) as u64;
105 let ratio = if total == 0 {
106 0
107 } else {
108 (debt as u128 * HUNDRED_PERCENT as u128 / total as u128) as u32
109 };
110
111 Ok((healthy, ratio))
112 }
113 }
114 };
115}