apricot_client/
state.rs

1use crate::utils;
2use solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};
3use std::cell::Ref;
4use std::fmt::{Display, Formatter, Result as FormatResult};
5use std::ops::{Add, Mul, Sub};
6
7pub const NATIVE_RAW_SHIFT: usize = 24;
8
9#[repr(packed)]
10#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
11/**
12 * RawAmt to accrue interest with boosted precision
13*/
14pub struct RawAmt {
15    amt: u128,
16}
17
18impl RawAmt {
19    pub fn to_native_amount(&self) -> u64 {
20        (self.amt >> NATIVE_RAW_SHIFT) as u64
21    }
22}
23
24pub const MAX_ASSETS_PER_USER: usize = 16;
25
26#[repr(packed)]
27#[derive(Copy, Clone)]
28pub struct UserAssetInfo {
29    pub pool_id: u8,
30    pub use_as_collateral: u8,
31
32    pub deposit_amount: RawAmt,
33    pub deposit_interests: u64, // spl amount
34    pub deposit_index: f64,
35    pub reward_deposit_amount: f64, // spl amount in f64, doesn't have to be that precise
36    pub reward_deposit_index: f64,
37
38    pub borrow_amount: RawAmt,
39    pub borrow_interests: u64, // spl amount
40    pub borrow_index: f64,
41    pub reward_borrow_amount: f64, // spl amount in f64, doesn't have to be that precise
42    pub reward_borrow_index: f64,
43}
44
45impl Display for UserAssetInfo {
46    #[allow(unaligned_references)]
47    fn fmt(&self, f: &mut Formatter<'_>) -> FormatResult {
48        writeln!(
49            f,
50            "pool_id: {}, use_as_collateral: {}",
51            self.pool_id, self.use_as_collateral
52        )?;
53        writeln!(
54            f,
55            "deposit_native_amount: {}, deposit_native_interest: {}, deposit_apt_reward_native_amount: {}",
56            self.deposit_amount.to_native_amount(), self.deposit_interests, self.reward_deposit_amount
57        )?;
58        writeln!(
59            f,
60            "borrow_native_amount: {}, borrow_native_interest: {}, borrow_apt_reward_native_amount: {}",
61            self.borrow_amount.to_native_amount(), self.borrow_interests, self.reward_borrow_amount
62        )?;
63        Ok(())
64    }
65}
66
67#[repr(packed)]
68#[derive(Copy, Clone)]
69pub struct RewardInfo {
70    pub vesting: [f64; 4], // retro vesting
71    pub prev_week_apt: f64,
72    pub unused: [f64; 2],
73    pub vesting_apt: f64,
74    pub available_apt: f64,
75    pub available_mnde: f64,
76    pub available_wldo: f64,     // wormhole lido
77    pub available_b180socn: f64, // b180socn
78    pub available_wluna: f64,    // wormhole luna
79}
80
81impl Display for RewardInfo {
82    #[allow(unaligned_references)]
83    fn fmt(&self, f: &mut Formatter<'_>) -> FormatResult {
84        writeln!(
85            f,
86            "earning_apt: {}, vesting_apt: {}",
87            self.prev_week_apt, self.vesting_apt
88        )?;
89        writeln!(
90            f,
91            "available_apt: {}, available_mnde: {}, available_wldo: {}, available_b180socn: {}, available_wluna: {}",
92            self.available_apt,
93            self.available_mnde,
94            self.available_wldo,
95            self.available_b180socn,
96            self.available_wluna
97        )?;
98        Ok(())
99    }
100}
101
102#[repr(packed)]
103#[derive(Copy, Clone)]
104pub struct UserInfo {
105    pub page_id: u16,
106    pub num_assets: u8,
107    pub user_asset_info: [UserAssetInfo; MAX_ASSETS_PER_USER],
108    pub reward: RewardInfo,
109    pub pad: [u8; 8],
110    pub last_vest_cutoff_timestamp: u64,
111    pub last_update_timestamp: u64,
112}
113
114impl UserInfo {
115    pub fn from_account_info<'a>(acc_info: &'a AccountInfo) -> Result<Ref<'a, Self>, ProgramError> {
116        let data_ref = acc_info.try_borrow_data()?;
117        let bytes_ref = Ref::map(data_ref, |d| *d);
118        let user_info = Ref::map(bytes_ref, |b| utils::cast::<UserInfo>(b));
119        Ok(user_info)
120    }
121
122    pub fn from_bytes(data: &[u8]) -> &Self {
123        utils::cast::<Self>(data)
124    }
125}
126
127impl Display for UserInfo {
128    #[allow(unaligned_references)]
129    fn fmt(&self, f: &mut Formatter<'_>) -> FormatResult {
130        writeln!(
131            f,
132            "page_id: {}, num_assets: {}",
133            self.page_id, self.num_assets
134        )?;
135        for i in 0..self.num_assets as usize {
136            writeln!(f, "user_asset_info: {}", self.user_asset_info[i])?;
137        }
138        writeln!(f, "reward: {}", self.reward)?;
139        writeln!(
140            f,
141            "last_vest_cutoff_timestamp: {}, last_update_timestamp: {}",
142            self.last_vest_cutoff_timestamp, self.last_update_timestamp
143        )?;
144        Ok(())
145    }
146}
147
148pub const TOKEN_NAME_SIZE: usize = 32;
149#[repr(packed)]
150#[derive(Copy, Clone)]
151pub struct AssetPool {
152    pub token_name: [u8; TOKEN_NAME_SIZE],
153
154    pub mint_key: Pubkey,
155    // 9 - token_decimal
156    pub mint_decimal_multiplier: u64,
157    pub pool_id: u8,
158
159    // deposits
160    pub deposit_amount: RawAmt,
161    pub deposit_index: f64,
162
163    // borrows
164    pub borrow_amount: RawAmt,
165    pub borrow_index: f64,
166
167    // fees
168    pub reserve_factor: f64,
169    pub fee_amount: RawAmt,
170    pub fee_withdrawn_amount: u64,
171    pub current_fee_rate: f64,
172
173    pub last_update_time: u64,
174
175    // related keys
176    pub spl_key: Pubkey,
177    pub atoken_mint_key: Pubkey,
178    pub asset_price_key: Pubkey,
179    pub pyth_price_key: Pubkey,
180
181    // used for creating unique client order IDs when we place orders on serum
182    pub serum_next_cl_id: u64,
183
184    pub ltv: f64,
185    pub safe_factor: f64,
186    pub flags: u8,
187
188    // interest rate parameters
189    pub base_rate: f64,
190    pub multiplier: f64,
191    pub jump_multiplier: f64,
192    pub kink: f64,
193    pub current_borrow_rate: f64,  // interest per year, APR
194    pub current_deposit_rate: f64, // interest per year, APR
195
196    pub reward_multiplier: f64,
197    pub reward_deposit_intra_share: f64, // < 1.0
198
199    // reward/second allocated to the entire pool
200    pub reward_apr_per_year: u64,
201    pub deposit_apt_reward_amount_per_year: u64,
202    pub borrow_apt_reward_amount_per_year: u64,
203    pub apt_reward_per_year_per_deposit: f64,
204    pub apt_reward_per_year_per_borrow: f64,
205
206    // The integral in Equation (2) from design.md
207    pub reward_deposit_index: f64,
208    pub reward_borrow_index: f64,
209
210    pub deposit_cap: u64,
211    pub is_disabled: u8,
212
213    pub farm_yield: f64,
214}
215
216impl AssetPool {
217    pub fn from_account_info<'a>(acc_info: &'a AccountInfo) -> Result<Ref<'a, Self>, ProgramError> {
218        let data_ref = acc_info.try_borrow_data()?;
219        let bytes_ref = Ref::map(data_ref, |d| *d);
220        let asset_pool = Ref::map(bytes_ref, |b| utils::cast::<AssetPool>(b));
221        Ok(asset_pool)
222    }
223
224    pub fn from_bytes(data: &[u8]) -> &Self {
225        utils::cast::<Self>(data)
226    }
227
228    pub fn calculate_new_interest_rate(
229        self,
230        deposit_native_amt: u64,
231        borrow_native_amt: u64
232    ) -> (f64, f64) {
233        let new_deposit_native_amt = deposit_native_amt.add(self.deposit_amount.to_native_amount()) as f64;
234        let new_borrow_native_amt = borrow_native_amt.add(self.borrow_amount.to_native_amount()) as f64;
235
236        Self::calculate_interest_rate(
237            new_deposit_native_amt,
238            new_borrow_native_amt,
239            self.base_rate,
240            self.multiplier,
241            self.jump_multiplier,
242            self.kink,
243            self.reserve_factor
244        )
245    }
246
247    pub fn calculate_interest_rate(
248        deposit_amt: f64,
249        borrow_amt: f64,
250        base_rate: f64,
251        multiplier: f64,
252        jump_multiplier: f64,
253        kink: f64,
254        reserve_factor: f64,
255    ) -> (f64, f64) {
256        let utilization_rate = if deposit_amt == 0.0 {
257            0.0
258        } else {
259            borrow_amt / deposit_amt
260        };
261        let mut borrow_rate = base_rate;
262        if utilization_rate <= kink {
263            borrow_rate = borrow_rate.add(multiplier.mul(utilization_rate));
264        } else {
265            borrow_rate = borrow_rate.add(multiplier.mul(kink));
266            borrow_rate = borrow_rate.add(jump_multiplier.mul(utilization_rate.sub(kink)));
267        }
268
269        let deposit_rate = borrow_rate.mul(utilization_rate).mul(1.0 - reserve_factor);
270        (deposit_rate, borrow_rate)
271    }
272}
273
274impl Display for AssetPool {
275    #[allow(unaligned_references)]
276    fn fmt(&self, f: &mut Formatter<'_>) -> FormatResult {
277        writeln!(f, "pool_id: {}", self.pool_id)?;
278        writeln!(f, "mint: {}", self.mint_key)?;
279        writeln!(f, "last_update_time: {}", self.last_update_time)?;
280        writeln!(f, "ltv: {}", self.ltv)?;
281        writeln!(f, "safe_factor: {}", self.safe_factor)?;
282        writeln!(f, "deposit_cap: {}", self.deposit_cap)?;
283        writeln!(
284            f,
285            "deposit_amount: {}",
286            self.deposit_amount.to_native_amount()
287        )?;
288        writeln!(f, "deposit_interest_rate: {}", self.current_deposit_rate)?;
289        writeln!(
290            f,
291            "deposit_apt_reward_amount_per_year: {}",
292            self.deposit_apt_reward_amount_per_year
293        )?;
294        writeln!(
295            f,
296            "borrow_amount: {}",
297            self.borrow_amount.to_native_amount()
298        )?;
299        writeln!(f, "borrow_interest_rate: {}", self.current_borrow_rate)?;
300        writeln!(
301            f,
302            "borrow_apt_reward_amount_per_year: {}",
303            self.borrow_apt_reward_amount_per_year
304        )?;
305        writeln!(f, "farm_yield: {}", self.farm_yield)?;
306        Ok(())
307    }
308}
309
310#[cfg(test)]
311pub mod asset_pool_test {
312    use super::*;
313
314    #[test]
315    fn test_calculate_interest_rate() {
316        let (mut deposit_rate, mut borrow_rate) = AssetPool::calculate_interest_rate(
317            6674310936768f64,
318            4307894688295f64,
319            0.01,
320            0.0823529411764706,
321            6.133333333333333,
322            0.85,
323            0.2,
324        );
325
326        assert!(0.032610 - deposit_rate < 1.0e-6);
327        assert!(0.063154 - borrow_rate < 1.0e-6);
328
329        (deposit_rate, borrow_rate) = AssetPool::calculate_interest_rate(
330            (6674310936768u64 + 10_000_000_000) as f64,
331            (4307894688295u64 + 100_000_000) as f64,
332            0.01,
333            0.0823529411764706,
334            6.133333333333333,
335            0.85,
336            0.2,
337        );
338
339        assert!(0.032522 - deposit_rate < 1.0e-6, "deposit_rate:{} doesn't match", deposit_rate);
340        assert!(0.063076 - borrow_rate < 1.0e-6, "borrow_rate:{} doesn't match", borrow_rate);
341    }
342}