defituna_client/implementation/
tuna_position.rs

1use 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            /// Returns the current position total and debt size.
41            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                // Add leftovers to the total position amount.
45                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            /// Returns the current leverage of a position. Vaults must be passed with accrued interest.
73            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                // We assume that the leverage of an empty position is always 1.0x.
77                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            /// Returns if the position is healthy or not. Vaults must be passed with accrued interest.
90            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                // Compute if the position is healthy. Can't overflow because liquidation_threshold <= 1e6 and total is a little bigger than u64::MAX.
104                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}