solend_sdk/state/
reserve.rs

1use super::*;
2use crate::{
3    error::LendingError,
4    math::{Decimal, Rate, TryAdd, TryDiv, TryMul, TrySub},
5};
6use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs};
7use solana_program::{
8    clock::Slot,
9    entrypoint::ProgramResult,
10    msg,
11    program_error::ProgramError,
12    program_pack::{IsInitialized, Pack, Sealed},
13    pubkey::{Pubkey, PUBKEY_BYTES},
14};
15use std::{
16    cmp::{min, Ordering},
17    convert::{TryFrom, TryInto},
18};
19
20/// Percentage of an obligation that can be repaid during each liquidation call
21pub const LIQUIDATION_CLOSE_FACTOR: u8 = 20;
22
23/// Obligation borrow amount that is small enough to close out
24pub const LIQUIDATION_CLOSE_AMOUNT: u64 = 2;
25
26/// Maximum quote currency value that can be liquidated in 1 liquidate_obligation call
27pub const MAX_LIQUIDATABLE_VALUE_AT_ONCE: u64 = 500_000;
28
29/// Lending market reserve state
30#[derive(Clone, Debug, Default, PartialEq)]
31pub struct Reserve {
32    /// Version of the struct
33    pub version: u8,
34    /// Last slot when supply and rates updated
35    pub last_update: LastUpdate,
36    /// Lending market address
37    pub lending_market: Pubkey,
38    /// Reserve liquidity
39    pub liquidity: ReserveLiquidity,
40    /// Reserve collateral
41    pub collateral: ReserveCollateral,
42    /// Reserve configuration values
43    pub config: ReserveConfig,
44}
45
46impl Reserve {
47    /// Create a new reserve
48    pub fn new(params: InitReserveParams) -> Self {
49        let mut reserve = Self::default();
50        Self::init(&mut reserve, params);
51        reserve
52    }
53
54    /// Initialize a reserve
55    pub fn init(&mut self, params: InitReserveParams) {
56        self.version = PROGRAM_VERSION;
57        self.last_update = LastUpdate::new(params.current_slot);
58        self.lending_market = params.lending_market;
59        self.liquidity = params.liquidity;
60        self.collateral = params.collateral;
61        self.config = params.config;
62    }
63
64    /// Record deposited liquidity and return amount of collateral tokens to mint
65    pub fn deposit_liquidity(&mut self, liquidity_amount: u64) -> Result<u64, ProgramError> {
66        let collateral_amount = self
67            .collateral_exchange_rate()?
68            .liquidity_to_collateral(liquidity_amount)?;
69
70        self.liquidity.deposit(liquidity_amount)?;
71        self.collateral.mint(collateral_amount)?;
72
73        Ok(collateral_amount)
74    }
75
76    /// Record redeemed collateral and return amount of liquidity to withdraw
77    pub fn redeem_collateral(&mut self, collateral_amount: u64) -> Result<u64, ProgramError> {
78        let collateral_exchange_rate = self.collateral_exchange_rate()?;
79        let liquidity_amount =
80            collateral_exchange_rate.collateral_to_liquidity(collateral_amount)?;
81
82        self.collateral.burn(collateral_amount)?;
83        self.liquidity.withdraw(liquidity_amount)?;
84
85        Ok(liquidity_amount)
86    }
87
88    /// Calculate the current borrow rate
89    pub fn current_borrow_rate(&self) -> Result<Rate, ProgramError> {
90        let utilization_rate = self.liquidity.utilization_rate()?;
91        let optimal_utilization_rate = Rate::from_percent(self.config.optimal_utilization_rate);
92        let low_utilization = utilization_rate < optimal_utilization_rate;
93        if low_utilization || self.config.optimal_utilization_rate == 100 {
94            let normalized_rate = utilization_rate.try_div(optimal_utilization_rate)?;
95            let min_rate = Rate::from_percent(self.config.min_borrow_rate);
96            let rate_range = Rate::from_percent(
97                self.config
98                    .optimal_borrow_rate
99                    .checked_sub(self.config.min_borrow_rate)
100                    .ok_or(LendingError::MathOverflow)?,
101            );
102
103            Ok(normalized_rate.try_mul(rate_range)?.try_add(min_rate)?)
104        } else {
105            if self.config.optimal_borrow_rate == self.config.max_borrow_rate {
106                let rate = Rate::from_percent(50u8);
107                return Ok(match self.config.max_borrow_rate {
108                    251u8 => rate.try_mul(6)?,  //300%
109                    252u8 => rate.try_mul(7)?,  //350%
110                    253u8 => rate.try_mul(8)?,  //400%
111                    254u8 => rate.try_mul(10)?, //500%
112                    255u8 => rate.try_mul(12)?, //600%
113                    250u8 => rate.try_mul(20)?, //1000%
114                    249u8 => rate.try_mul(30)?, //1500%
115                    248u8 => rate.try_mul(40)?, //2000%
116                    247u8 => rate.try_mul(50)?, //2500%
117                    _ => Rate::from_percent(self.config.max_borrow_rate),
118                });
119            }
120            let normalized_rate = utilization_rate
121                .try_sub(optimal_utilization_rate)?
122                .try_div(Rate::from_percent(
123                    100u8
124                        .checked_sub(self.config.optimal_utilization_rate)
125                        .ok_or(LendingError::MathOverflow)?,
126                ))?;
127            let min_rate = Rate::from_percent(self.config.optimal_borrow_rate);
128            let rate_range = Rate::from_percent(
129                self.config
130                    .max_borrow_rate
131                    .checked_sub(self.config.optimal_borrow_rate)
132                    .ok_or(LendingError::MathOverflow)?,
133            );
134
135            Ok(normalized_rate.try_mul(rate_range)?.try_add(min_rate)?)
136        }
137    }
138
139    /// Collateral exchange rate
140    pub fn collateral_exchange_rate(&self) -> Result<CollateralExchangeRate, ProgramError> {
141        let total_liquidity = self.liquidity.total_supply()?;
142        self.collateral.exchange_rate(total_liquidity)
143    }
144
145    /// Update borrow rate and accrue interest
146    pub fn accrue_interest(&mut self, current_slot: Slot) -> ProgramResult {
147        let slots_elapsed = self.last_update.slots_elapsed(current_slot)?;
148        if slots_elapsed > 0 {
149            let current_borrow_rate = self.current_borrow_rate()?;
150            let take_rate = Rate::from_percent(self.config.protocol_take_rate);
151            self.liquidity
152                .compound_interest(current_borrow_rate, slots_elapsed, take_rate)?;
153        }
154        Ok(())
155    }
156
157    /// Borrow liquidity up to a maximum market value
158    pub fn calculate_borrow(
159        &self,
160        amount_to_borrow: u64,
161        max_borrow_value: Decimal,
162        remaining_reserve_borrow: Decimal,
163    ) -> Result<CalculateBorrowResult, ProgramError> {
164        // @TODO: add lookup table https://git.io/JOCYq
165        let decimals = 10u64
166            .checked_pow(self.liquidity.mint_decimals as u32)
167            .ok_or(LendingError::MathOverflow)?;
168        if amount_to_borrow == u64::MAX {
169            let borrow_amount = max_borrow_value
170                .try_mul(decimals)?
171                .try_div(self.liquidity.market_price)?
172                .min(remaining_reserve_borrow)
173                .min(self.liquidity.available_amount.into());
174            let (borrow_fee, host_fee) = self
175                .config
176                .fees
177                .calculate_borrow_fees(borrow_amount, FeeCalculation::Inclusive)?;
178            let receive_amount = borrow_amount
179                .try_floor_u64()?
180                .checked_sub(borrow_fee)
181                .ok_or(LendingError::MathOverflow)?;
182
183            Ok(CalculateBorrowResult {
184                borrow_amount,
185                receive_amount,
186                borrow_fee,
187                host_fee,
188            })
189        } else {
190            let receive_amount = amount_to_borrow;
191            let borrow_amount = Decimal::from(receive_amount);
192            let (borrow_fee, host_fee) = self
193                .config
194                .fees
195                .calculate_borrow_fees(borrow_amount, FeeCalculation::Exclusive)?;
196
197            let borrow_amount = borrow_amount.try_add(borrow_fee.into())?;
198            let borrow_value = borrow_amount
199                .try_mul(self.liquidity.market_price)?
200                .try_div(decimals)?;
201            if borrow_value > max_borrow_value {
202                msg!("Borrow value cannot exceed maximum borrow value");
203                return Err(LendingError::BorrowTooLarge.into());
204            }
205
206            Ok(CalculateBorrowResult {
207                borrow_amount,
208                receive_amount,
209                borrow_fee,
210                host_fee,
211            })
212        }
213    }
214
215    /// Repay liquidity up to the borrowed amount
216    pub fn calculate_repay(
217        &self,
218        amount_to_repay: u64,
219        borrowed_amount: Decimal,
220    ) -> Result<CalculateRepayResult, ProgramError> {
221        let settle_amount = if amount_to_repay == u64::MAX {
222            borrowed_amount
223        } else {
224            Decimal::from(amount_to_repay).min(borrowed_amount)
225        };
226        let repay_amount = settle_amount.try_ceil_u64()?;
227
228        Ok(CalculateRepayResult {
229            settle_amount,
230            repay_amount,
231        })
232    }
233
234    /// Liquidate some or all of an unhealthy obligation
235    pub fn calculate_liquidation(
236        &self,
237        amount_to_liquidate: u64,
238        obligation: &Obligation,
239        liquidity: &ObligationLiquidity,
240        collateral: &ObligationCollateral,
241    ) -> Result<CalculateLiquidationResult, ProgramError> {
242        let bonus_rate = Rate::from_percent(self.config.liquidation_bonus).try_add(Rate::one())?;
243
244        let max_amount = if amount_to_liquidate == u64::MAX {
245            liquidity.borrowed_amount_wads
246        } else {
247            Decimal::from(amount_to_liquidate).min(liquidity.borrowed_amount_wads)
248        };
249
250        let settle_amount;
251        let repay_amount;
252        let withdraw_amount;
253
254        // Close out obligations that are too small to liquidate normally
255        if liquidity.borrowed_amount_wads < LIQUIDATION_CLOSE_AMOUNT.into() {
256            // settle_amount is fixed, calculate withdraw_amount and repay_amount
257            settle_amount = liquidity.borrowed_amount_wads;
258
259            let liquidation_value = liquidity.market_value.try_mul(bonus_rate)?;
260            match liquidation_value.cmp(&collateral.market_value) {
261                Ordering::Greater => {
262                    let repay_pct = collateral.market_value.try_div(liquidation_value)?;
263                    repay_amount = max_amount.try_mul(repay_pct)?.try_ceil_u64()?;
264                    withdraw_amount = collateral.deposited_amount;
265                }
266                Ordering::Equal => {
267                    repay_amount = max_amount.try_ceil_u64()?;
268                    withdraw_amount = collateral.deposited_amount;
269                }
270                Ordering::Less => {
271                    let withdraw_pct = liquidation_value.try_div(collateral.market_value)?;
272                    repay_amount = max_amount.try_ceil_u64()?;
273                    withdraw_amount = Decimal::from(collateral.deposited_amount)
274                        .try_mul(withdraw_pct)?
275                        .try_floor_u64()?;
276                }
277            }
278        } else {
279            // calculate settle_amount and withdraw_amount, repay_amount is settle_amount rounded
280            let liquidation_amount = obligation
281                .max_liquidation_amount(liquidity)?
282                .min(max_amount);
283            let liquidation_pct = liquidation_amount.try_div(liquidity.borrowed_amount_wads)?;
284            let liquidation_value = liquidity
285                .market_value
286                .try_mul(liquidation_pct)?
287                .try_mul(bonus_rate)?;
288
289            match liquidation_value.cmp(&collateral.market_value) {
290                Ordering::Greater => {
291                    let repay_pct = collateral.market_value.try_div(liquidation_value)?;
292                    settle_amount = liquidation_amount.try_mul(repay_pct)?;
293                    repay_amount = settle_amount.try_ceil_u64()?;
294                    withdraw_amount = collateral.deposited_amount;
295                }
296                Ordering::Equal => {
297                    settle_amount = liquidation_amount;
298                    repay_amount = settle_amount.try_ceil_u64()?;
299                    withdraw_amount = collateral.deposited_amount;
300                }
301                Ordering::Less => {
302                    let withdraw_pct = liquidation_value.try_div(collateral.market_value)?;
303                    settle_amount = liquidation_amount;
304                    repay_amount = settle_amount.try_ceil_u64()?;
305                    withdraw_amount = Decimal::from(collateral.deposited_amount)
306                        .try_mul(withdraw_pct)?
307                        .try_floor_u64()?;
308                }
309            }
310        }
311
312        Ok(CalculateLiquidationResult {
313            settle_amount,
314            repay_amount,
315            withdraw_amount,
316        })
317    }
318
319    /// Calculate protocol cut of liquidation bonus always at least 1 lamport
320    pub fn calculate_protocol_liquidation_fee(
321        &self,
322        amount_liquidated: u64,
323    ) -> Result<u64, ProgramError> {
324        let bonus_rate = Rate::from_percent(self.config.liquidation_bonus).try_add(Rate::one())?;
325        let amount_liquidated_wads = Decimal::from(amount_liquidated);
326
327        let bonus = amount_liquidated_wads.try_sub(amount_liquidated_wads.try_div(bonus_rate)?)?;
328
329        // After deploying must update all reserves to set liquidation fee then redeploy with this line instead of hardcode
330        let protocol_fee = std::cmp::max(
331            bonus
332                .try_mul(Rate::from_percent(self.config.protocol_liquidation_fee))?
333                .try_ceil_u64()?,
334            1,
335        );
336        Ok(protocol_fee)
337    }
338
339    /// Calculate protocol fee redemption accounting for availible liquidity and accumulated fees
340    pub fn calculate_redeem_fees(&self) -> Result<u64, ProgramError> {
341        Ok(min(
342            self.liquidity.available_amount,
343            self.liquidity
344                .accumulated_protocol_fees_wads
345                .try_floor_u64()?,
346        ))
347    }
348}
349
350/// Initialize a reserve
351pub struct InitReserveParams {
352    /// Last slot when supply and rates updated
353    pub current_slot: Slot,
354    /// Lending market address
355    pub lending_market: Pubkey,
356    /// Reserve liquidity
357    pub liquidity: ReserveLiquidity,
358    /// Reserve collateral
359    pub collateral: ReserveCollateral,
360    /// Reserve configuration values
361    pub config: ReserveConfig,
362}
363
364/// Calculate borrow result
365#[derive(Debug)]
366pub struct CalculateBorrowResult {
367    /// Total amount of borrow including fees
368    pub borrow_amount: Decimal,
369    /// Borrow amount portion of total amount
370    pub receive_amount: u64,
371    /// Loan origination fee
372    pub borrow_fee: u64,
373    /// Host fee portion of origination fee
374    pub host_fee: u64,
375}
376
377/// Calculate repay result
378#[derive(Debug)]
379pub struct CalculateRepayResult {
380    /// Amount of liquidity that is settled from the obligation.
381    pub settle_amount: Decimal,
382    /// Amount that will be repaid as u64
383    pub repay_amount: u64,
384}
385
386/// Calculate liquidation result
387#[derive(Debug, Clone, PartialEq, Eq)]
388pub struct CalculateLiquidationResult {
389    /// Amount of liquidity that is settled from the obligation. It includes
390    /// the amount of loan that was defaulted if collateral is depleted.
391    pub settle_amount: Decimal,
392    /// Amount that will be repaid as u64
393    pub repay_amount: u64,
394    /// Amount of collateral to withdraw in exchange for repay amount
395    pub withdraw_amount: u64,
396}
397
398/// Reserve liquidity
399#[derive(Clone, Debug, Default, PartialEq, Eq)]
400pub struct ReserveLiquidity {
401    /// Reserve liquidity mint address
402    pub mint_pubkey: Pubkey,
403    /// Reserve liquidity mint decimals
404    pub mint_decimals: u8,
405    /// Reserve liquidity supply address
406    pub supply_pubkey: Pubkey,
407    /// Reserve liquidity pyth oracle account
408    pub pyth_oracle_pubkey: Pubkey,
409    /// Reserve liquidity switchboard oracle account
410    pub switchboard_oracle_pubkey: Pubkey,
411    /// Reserve liquidity available
412    pub available_amount: u64,
413    /// Reserve liquidity borrowed
414    pub borrowed_amount_wads: Decimal,
415    /// Reserve liquidity cumulative borrow rate
416    pub cumulative_borrow_rate_wads: Decimal,
417    /// Reserve cumulative protocol fees
418    pub accumulated_protocol_fees_wads: Decimal,
419    /// Reserve liquidity market price in quote currency
420    pub market_price: Decimal,
421}
422
423impl ReserveLiquidity {
424    /// Create a new reserve liquidity
425    pub fn new(params: NewReserveLiquidityParams) -> Self {
426        Self {
427            mint_pubkey: params.mint_pubkey,
428            mint_decimals: params.mint_decimals,
429            supply_pubkey: params.supply_pubkey,
430            pyth_oracle_pubkey: params.pyth_oracle_pubkey,
431            switchboard_oracle_pubkey: params.switchboard_oracle_pubkey,
432            available_amount: 0,
433            borrowed_amount_wads: Decimal::zero(),
434            cumulative_borrow_rate_wads: Decimal::one(),
435            accumulated_protocol_fees_wads: Decimal::zero(),
436            market_price: params.market_price,
437        }
438    }
439
440    /// Calculate the total reserve supply including active loans
441    pub fn total_supply(&self) -> Result<Decimal, ProgramError> {
442        Decimal::from(self.available_amount)
443            .try_add(self.borrowed_amount_wads)?
444            .try_sub(self.accumulated_protocol_fees_wads)
445    }
446
447    /// Add liquidity to available amount
448    pub fn deposit(&mut self, liquidity_amount: u64) -> ProgramResult {
449        self.available_amount = self
450            .available_amount
451            .checked_add(liquidity_amount)
452            .ok_or(LendingError::MathOverflow)?;
453        Ok(())
454    }
455
456    /// Remove liquidity from available amount
457    pub fn withdraw(&mut self, liquidity_amount: u64) -> ProgramResult {
458        if liquidity_amount > self.available_amount {
459            msg!("Withdraw amount cannot exceed available amount");
460            return Err(LendingError::InsufficientLiquidity.into());
461        }
462        self.available_amount = self
463            .available_amount
464            .checked_sub(liquidity_amount)
465            .ok_or(LendingError::MathOverflow)?;
466        Ok(())
467    }
468
469    /// Subtract borrow amount from available liquidity and add to borrows
470    pub fn borrow(&mut self, borrow_decimal: Decimal) -> ProgramResult {
471        let borrow_amount = borrow_decimal.try_floor_u64()?;
472        if borrow_amount > self.available_amount {
473            msg!("Borrow amount cannot exceed available amount");
474            return Err(LendingError::InsufficientLiquidity.into());
475        }
476
477        self.available_amount = self
478            .available_amount
479            .checked_sub(borrow_amount)
480            .ok_or(LendingError::MathOverflow)?;
481        self.borrowed_amount_wads = self.borrowed_amount_wads.try_add(borrow_decimal)?;
482
483        Ok(())
484    }
485
486    /// Add repay amount to available liquidity and subtract settle amount from total borrows
487    pub fn repay(&mut self, repay_amount: u64, settle_amount: Decimal) -> ProgramResult {
488        self.available_amount = self
489            .available_amount
490            .checked_add(repay_amount)
491            .ok_or(LendingError::MathOverflow)?;
492        let safe_settle_amount = settle_amount.min(self.borrowed_amount_wads);
493        self.borrowed_amount_wads = self.borrowed_amount_wads.try_sub(safe_settle_amount)?;
494
495        Ok(())
496    }
497
498    /// Subtract settle amount from accumulated_protocol_fees_wads and withdraw_amount from available liquidity
499    pub fn redeem_fees(&mut self, withdraw_amount: u64) -> ProgramResult {
500        self.available_amount = self
501            .available_amount
502            .checked_sub(withdraw_amount)
503            .ok_or(LendingError::MathOverflow)?;
504        self.accumulated_protocol_fees_wads = self
505            .accumulated_protocol_fees_wads
506            .try_sub(Decimal::from(withdraw_amount))?;
507
508        Ok(())
509    }
510
511    /// Calculate the liquidity utilization rate of the reserve
512    pub fn utilization_rate(&self) -> Result<Rate, ProgramError> {
513        let total_supply = self.total_supply()?;
514        if total_supply == Decimal::zero() {
515            return Ok(Rate::zero());
516        }
517        self.borrowed_amount_wads.try_div(total_supply)?.try_into()
518    }
519
520    /// Compound current borrow rate over elapsed slots
521    fn compound_interest(
522        &mut self,
523        current_borrow_rate: Rate,
524        slots_elapsed: u64,
525        take_rate: Rate,
526    ) -> ProgramResult {
527        let slot_interest_rate = current_borrow_rate.try_div(SLOTS_PER_YEAR)?;
528        let compounded_interest_rate = Rate::one()
529            .try_add(slot_interest_rate)?
530            .try_pow(slots_elapsed)?;
531        self.cumulative_borrow_rate_wads = self
532            .cumulative_borrow_rate_wads
533            .try_mul(compounded_interest_rate)?;
534
535        let net_new_debt = self
536            .borrowed_amount_wads
537            .try_mul(compounded_interest_rate)?
538            .try_sub(self.borrowed_amount_wads)?;
539
540        self.accumulated_protocol_fees_wads = net_new_debt
541            .try_mul(take_rate)?
542            .try_add(self.accumulated_protocol_fees_wads)?;
543
544        self.borrowed_amount_wads = self.borrowed_amount_wads.try_add(net_new_debt)?;
545        Ok(())
546    }
547}
548
549/// Create a new reserve liquidity
550pub struct NewReserveLiquidityParams {
551    /// Reserve liquidity mint address
552    pub mint_pubkey: Pubkey,
553    /// Reserve liquidity mint decimals
554    pub mint_decimals: u8,
555    /// Reserve liquidity supply address
556    pub supply_pubkey: Pubkey,
557    /// Reserve liquidity pyth oracle account
558    pub pyth_oracle_pubkey: Pubkey,
559    /// Reserve liquidity switchboard oracle account
560    pub switchboard_oracle_pubkey: Pubkey,
561    /// Reserve liquidity market price in quote currency
562    pub market_price: Decimal,
563}
564
565/// Reserve collateral
566#[derive(Clone, Debug, Default, PartialEq, Eq)]
567pub struct ReserveCollateral {
568    /// Reserve collateral mint address
569    pub mint_pubkey: Pubkey,
570    /// Reserve collateral mint supply, used for exchange rate
571    pub mint_total_supply: u64,
572    /// Reserve collateral supply address
573    pub supply_pubkey: Pubkey,
574}
575
576impl ReserveCollateral {
577    /// Create a new reserve collateral
578    pub fn new(params: NewReserveCollateralParams) -> Self {
579        Self {
580            mint_pubkey: params.mint_pubkey,
581            mint_total_supply: 0,
582            supply_pubkey: params.supply_pubkey,
583        }
584    }
585
586    /// Add collateral to total supply
587    pub fn mint(&mut self, collateral_amount: u64) -> ProgramResult {
588        self.mint_total_supply = self
589            .mint_total_supply
590            .checked_add(collateral_amount)
591            .ok_or(LendingError::MathOverflow)?;
592        Ok(())
593    }
594
595    /// Remove collateral from total supply
596    pub fn burn(&mut self, collateral_amount: u64) -> ProgramResult {
597        self.mint_total_supply = self
598            .mint_total_supply
599            .checked_sub(collateral_amount)
600            .ok_or(LendingError::MathOverflow)?;
601        Ok(())
602    }
603
604    /// Return the current collateral exchange rate.
605    fn exchange_rate(
606        &self,
607        total_liquidity: Decimal,
608    ) -> Result<CollateralExchangeRate, ProgramError> {
609        let rate = if self.mint_total_supply == 0 || total_liquidity == Decimal::zero() {
610            Rate::from_scaled_val(INITIAL_COLLATERAL_RATE)
611        } else {
612            let mint_total_supply = Decimal::from(self.mint_total_supply);
613            Rate::try_from(mint_total_supply.try_div(total_liquidity)?)?
614        };
615
616        Ok(CollateralExchangeRate(rate))
617    }
618}
619
620/// Create a new reserve collateral
621pub struct NewReserveCollateralParams {
622    /// Reserve collateral mint address
623    pub mint_pubkey: Pubkey,
624    /// Reserve collateral supply address
625    pub supply_pubkey: Pubkey,
626}
627
628/// Collateral exchange rate
629#[derive(Clone, Copy, Debug)]
630pub struct CollateralExchangeRate(Rate);
631
632impl CollateralExchangeRate {
633    /// Convert reserve collateral to liquidity
634    pub fn collateral_to_liquidity(&self, collateral_amount: u64) -> Result<u64, ProgramError> {
635        self.decimal_collateral_to_liquidity(collateral_amount.into())?
636            .try_floor_u64()
637    }
638
639    /// Convert reserve collateral to liquidity
640    pub fn decimal_collateral_to_liquidity(
641        &self,
642        collateral_amount: Decimal,
643    ) -> Result<Decimal, ProgramError> {
644        collateral_amount.try_div(self.0)
645    }
646
647    /// Convert reserve liquidity to collateral
648    pub fn liquidity_to_collateral(&self, liquidity_amount: u64) -> Result<u64, ProgramError> {
649        self.decimal_liquidity_to_collateral(liquidity_amount.into())?
650            .try_floor_u64()
651    }
652
653    /// Convert reserve liquidity to collateral
654    pub fn decimal_liquidity_to_collateral(
655        &self,
656        liquidity_amount: Decimal,
657    ) -> Result<Decimal, ProgramError> {
658        liquidity_amount.try_mul(self.0)
659    }
660}
661
662impl From<CollateralExchangeRate> for Rate {
663    fn from(exchange_rate: CollateralExchangeRate) -> Self {
664        exchange_rate.0
665    }
666}
667
668/// Reserve configuration values
669#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
670pub struct ReserveConfig {
671    /// Optimal utilization rate, as a percentage
672    pub optimal_utilization_rate: u8,
673    /// Target ratio of the value of borrows to deposits, as a percentage
674    /// 0 if use as collateral is disabled
675    pub loan_to_value_ratio: u8,
676    /// Bonus a liquidator gets when repaying part of an unhealthy obligation, as a percentage
677    pub liquidation_bonus: u8,
678    /// Loan to value ratio at which an obligation can be liquidated, as a percentage
679    pub liquidation_threshold: u8,
680    /// Min borrow APY
681    pub min_borrow_rate: u8,
682    /// Optimal (utilization) borrow APY
683    pub optimal_borrow_rate: u8,
684    /// Max borrow APY
685    pub max_borrow_rate: u8,
686    /// Program owner fees assessed, separate from gains due to interest accrual
687    pub fees: ReserveFees,
688    /// Maximum deposit limit of liquidity in native units, u64::MAX for inf
689    pub deposit_limit: u64,
690    /// Borrows disabled
691    pub borrow_limit: u64,
692    /// Reserve liquidity fee receiver address
693    pub fee_receiver: Pubkey,
694    /// Cut of the liquidation bonus that the protocol receives, as a percentage
695    pub protocol_liquidation_fee: u8,
696    /// Protocol take rate is the amount borrowed interest protocol recieves, as a percentage  
697    pub protocol_take_rate: u8,
698}
699
700/// Additional fee information on a reserve
701///
702/// These exist separately from interest accrual fees, and are specifically for the program owner
703/// and frontend host. The fees are paid out as a percentage of liquidity token amounts during
704/// repayments and liquidations.
705#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
706pub struct ReserveFees {
707    /// Fee assessed on `BorrowObligationLiquidity`, expressed as a Wad.
708    /// Must be between 0 and 10^18, such that 10^18 = 1.  A few examples for
709    /// clarity:
710    /// 1% = 10_000_000_000_000_000
711    /// 0.01% (1 basis point) = 100_000_000_000_000
712    /// 0.00001% (Aave borrow fee) = 100_000_000_000
713    pub borrow_fee_wad: u64,
714    /// Fee for flash loan, expressed as a Wad.
715    /// 0.3% (Aave flash loan fee) = 3_000_000_000_000_000
716    pub flash_loan_fee_wad: u64,
717    /// Amount of fee going to host account, if provided in liquidate and repay
718    pub host_fee_percentage: u8,
719}
720
721impl ReserveFees {
722    /// Calculate the owner and host fees on borrow
723    pub fn calculate_borrow_fees(
724        &self,
725        borrow_amount: Decimal,
726        fee_calculation: FeeCalculation,
727    ) -> Result<(u64, u64), ProgramError> {
728        self.calculate_fees(borrow_amount, self.borrow_fee_wad, fee_calculation)
729    }
730
731    /// Calculate the owner and host fees on flash loan
732    pub fn calculate_flash_loan_fees(
733        &self,
734        flash_loan_amount: Decimal,
735    ) -> Result<(u64, u64), ProgramError> {
736        let (total_fees, host_fee) = self.calculate_fees(
737            flash_loan_amount,
738            self.flash_loan_fee_wad,
739            FeeCalculation::Exclusive,
740        )?;
741
742        let origination_fee = total_fees
743            .checked_sub(host_fee)
744            .ok_or(LendingError::MathOverflow)?;
745        Ok((origination_fee, host_fee))
746    }
747
748    fn calculate_fees(
749        &self,
750        amount: Decimal,
751        fee_wad: u64,
752        fee_calculation: FeeCalculation,
753    ) -> Result<(u64, u64), ProgramError> {
754        let borrow_fee_rate = Rate::from_scaled_val(fee_wad);
755        let host_fee_rate = Rate::from_percent(self.host_fee_percentage);
756        if borrow_fee_rate > Rate::zero() && amount > Decimal::zero() {
757            let need_to_assess_host_fee = host_fee_rate > Rate::zero();
758            let minimum_fee = if need_to_assess_host_fee {
759                2u64 // 1 token to owner, 1 to host
760            } else {
761                1u64 // 1 token to owner, nothing else
762            };
763
764            let borrow_fee_amount = match fee_calculation {
765                // Calculate fee to be added to borrow: fee = amount * rate
766                FeeCalculation::Exclusive => amount.try_mul(borrow_fee_rate)?,
767                // Calculate fee to be subtracted from borrow: fee = amount * (rate / (rate + 1))
768                FeeCalculation::Inclusive => {
769                    let borrow_fee_rate =
770                        borrow_fee_rate.try_div(borrow_fee_rate.try_add(Rate::one())?)?;
771                    amount.try_mul(borrow_fee_rate)?
772                }
773            };
774
775            let borrow_fee_decimal = borrow_fee_amount.max(minimum_fee.into());
776            if borrow_fee_decimal >= amount {
777                msg!("Borrow amount is too small to receive liquidity after fees");
778                return Err(LendingError::BorrowTooSmall.into());
779            }
780
781            let borrow_fee = borrow_fee_decimal.try_round_u64()?;
782            let host_fee = if need_to_assess_host_fee {
783                borrow_fee_decimal
784                    .try_mul(host_fee_rate)?
785                    .try_round_u64()?
786                    .max(1u64)
787            } else {
788                0
789            };
790
791            Ok((borrow_fee, host_fee))
792        } else {
793            Ok((0, 0))
794        }
795    }
796}
797
798/// Calculate fees exlusive or inclusive of an amount
799pub enum FeeCalculation {
800    /// Fee added to amount: fee = rate * amount
801    Exclusive,
802    /// Fee included in amount: fee = (rate / (1 + rate)) * amount
803    Inclusive,
804}
805
806impl Sealed for Reserve {}
807impl IsInitialized for Reserve {
808    fn is_initialized(&self) -> bool {
809        self.version != UNINITIALIZED_VERSION
810    }
811}
812
813const RESERVE_LEN: usize = 619; // 1 + 8 + 1 + 32 + 32 + 1 + 32 + 32 + 32 + 8 + 16 + 16 + 16 + 32 + 8 + 32 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 8 + 8 + 1 + 8 + 8 + 32 + 1 + 1 + 16 + 230
814impl Pack for Reserve {
815    const LEN: usize = RESERVE_LEN;
816
817    // @TODO: break this up by reserve / liquidity / collateral / config https://git.io/JOCca
818    fn pack_into_slice(&self, output: &mut [u8]) {
819        let output = array_mut_ref![output, 0, RESERVE_LEN];
820        #[allow(clippy::ptr_offset_with_cast)]
821        let (
822            version,
823            last_update_slot,
824            last_update_stale,
825            lending_market,
826            liquidity_mint_pubkey,
827            liquidity_mint_decimals,
828            liquidity_supply_pubkey,
829            liquidity_pyth_oracle_pubkey,
830            liquidity_switchboard_oracle_pubkey,
831            liquidity_available_amount,
832            liquidity_borrowed_amount_wads,
833            liquidity_cumulative_borrow_rate_wads,
834            liquidity_market_price,
835            collateral_mint_pubkey,
836            collateral_mint_total_supply,
837            collateral_supply_pubkey,
838            config_optimal_utilization_rate,
839            config_loan_to_value_ratio,
840            config_liquidation_bonus,
841            config_liquidation_threshold,
842            config_min_borrow_rate,
843            config_optimal_borrow_rate,
844            config_max_borrow_rate,
845            config_fees_borrow_fee_wad,
846            config_fees_flash_loan_fee_wad,
847            config_fees_host_fee_percentage,
848            config_deposit_limit,
849            config_borrow_limit,
850            config_fee_receiver,
851            config_protocol_liquidation_fee,
852            config_protocol_take_rate,
853            liquidity_accumulated_protocol_fees_wads,
854            _padding,
855        ) = mut_array_refs![
856            output,
857            1,
858            8,
859            1,
860            PUBKEY_BYTES,
861            PUBKEY_BYTES,
862            1,
863            PUBKEY_BYTES,
864            PUBKEY_BYTES,
865            PUBKEY_BYTES,
866            8,
867            16,
868            16,
869            16,
870            PUBKEY_BYTES,
871            8,
872            PUBKEY_BYTES,
873            1,
874            1,
875            1,
876            1,
877            1,
878            1,
879            1,
880            8,
881            8,
882            1,
883            8,
884            8,
885            PUBKEY_BYTES,
886            1,
887            1,
888            16,
889            230
890        ];
891
892        // reserve
893        *version = self.version.to_le_bytes();
894        *last_update_slot = self.last_update.slot.to_le_bytes();
895        pack_bool(self.last_update.stale, last_update_stale);
896        lending_market.copy_from_slice(self.lending_market.as_ref());
897
898        // liquidity
899        liquidity_mint_pubkey.copy_from_slice(self.liquidity.mint_pubkey.as_ref());
900        *liquidity_mint_decimals = self.liquidity.mint_decimals.to_le_bytes();
901        liquidity_supply_pubkey.copy_from_slice(self.liquidity.supply_pubkey.as_ref());
902        liquidity_pyth_oracle_pubkey.copy_from_slice(self.liquidity.pyth_oracle_pubkey.as_ref());
903        liquidity_switchboard_oracle_pubkey
904            .copy_from_slice(self.liquidity.switchboard_oracle_pubkey.as_ref());
905        *liquidity_available_amount = self.liquidity.available_amount.to_le_bytes();
906        pack_decimal(
907            self.liquidity.borrowed_amount_wads,
908            liquidity_borrowed_amount_wads,
909        );
910        pack_decimal(
911            self.liquidity.cumulative_borrow_rate_wads,
912            liquidity_cumulative_borrow_rate_wads,
913        );
914        pack_decimal(
915            self.liquidity.accumulated_protocol_fees_wads,
916            liquidity_accumulated_protocol_fees_wads,
917        );
918        pack_decimal(self.liquidity.market_price, liquidity_market_price);
919
920        // collateral
921        collateral_mint_pubkey.copy_from_slice(self.collateral.mint_pubkey.as_ref());
922        *collateral_mint_total_supply = self.collateral.mint_total_supply.to_le_bytes();
923        collateral_supply_pubkey.copy_from_slice(self.collateral.supply_pubkey.as_ref());
924
925        // config
926        *config_optimal_utilization_rate = self.config.optimal_utilization_rate.to_le_bytes();
927        *config_loan_to_value_ratio = self.config.loan_to_value_ratio.to_le_bytes();
928        *config_liquidation_bonus = self.config.liquidation_bonus.to_le_bytes();
929        *config_liquidation_threshold = self.config.liquidation_threshold.to_le_bytes();
930        *config_min_borrow_rate = self.config.min_borrow_rate.to_le_bytes();
931        *config_optimal_borrow_rate = self.config.optimal_borrow_rate.to_le_bytes();
932        *config_max_borrow_rate = self.config.max_borrow_rate.to_le_bytes();
933        *config_fees_borrow_fee_wad = self.config.fees.borrow_fee_wad.to_le_bytes();
934        *config_fees_flash_loan_fee_wad = self.config.fees.flash_loan_fee_wad.to_le_bytes();
935        *config_fees_host_fee_percentage = self.config.fees.host_fee_percentage.to_le_bytes();
936        *config_deposit_limit = self.config.deposit_limit.to_le_bytes();
937        *config_borrow_limit = self.config.borrow_limit.to_le_bytes();
938        config_fee_receiver.copy_from_slice(self.config.fee_receiver.as_ref());
939        *config_protocol_liquidation_fee = self.config.protocol_liquidation_fee.to_le_bytes();
940        *config_protocol_take_rate = self.config.protocol_take_rate.to_le_bytes();
941    }
942
943    /// Unpacks a byte buffer into a [ReserveInfo](struct.ReserveInfo.html).
944    fn unpack_from_slice(input: &[u8]) -> Result<Self, ProgramError> {
945        let input = array_ref![input, 0, RESERVE_LEN];
946        #[allow(clippy::ptr_offset_with_cast)]
947        let (
948            version,
949            last_update_slot,
950            last_update_stale,
951            lending_market,
952            liquidity_mint_pubkey,
953            liquidity_mint_decimals,
954            liquidity_supply_pubkey,
955            liquidity_pyth_oracle_pubkey,
956            liquidity_switchboard_oracle_pubkey,
957            liquidity_available_amount,
958            liquidity_borrowed_amount_wads,
959            liquidity_cumulative_borrow_rate_wads,
960            liquidity_market_price,
961            collateral_mint_pubkey,
962            collateral_mint_total_supply,
963            collateral_supply_pubkey,
964            config_optimal_utilization_rate,
965            config_loan_to_value_ratio,
966            config_liquidation_bonus,
967            config_liquidation_threshold,
968            config_min_borrow_rate,
969            config_optimal_borrow_rate,
970            config_max_borrow_rate,
971            config_fees_borrow_fee_wad,
972            config_fees_flash_loan_fee_wad,
973            config_fees_host_fee_percentage,
974            config_deposit_limit,
975            config_borrow_limit,
976            config_fee_receiver,
977            config_protocol_liquidation_fee,
978            config_protocol_take_rate,
979            liquidity_accumulated_protocol_fees_wads,
980            _padding,
981        ) = array_refs![
982            input,
983            1,
984            8,
985            1,
986            PUBKEY_BYTES,
987            PUBKEY_BYTES,
988            1,
989            PUBKEY_BYTES,
990            PUBKEY_BYTES,
991            PUBKEY_BYTES,
992            8,
993            16,
994            16,
995            16,
996            PUBKEY_BYTES,
997            8,
998            PUBKEY_BYTES,
999            1,
1000            1,
1001            1,
1002            1,
1003            1,
1004            1,
1005            1,
1006            8,
1007            8,
1008            1,
1009            8,
1010            8,
1011            PUBKEY_BYTES,
1012            1,
1013            1,
1014            16,
1015            230
1016        ];
1017
1018        let version = u8::from_le_bytes(*version);
1019        if version > PROGRAM_VERSION {
1020            msg!("Reserve version does not match lending program version");
1021            return Err(ProgramError::InvalidAccountData);
1022        }
1023
1024        Ok(Self {
1025            version,
1026            last_update: LastUpdate {
1027                slot: u64::from_le_bytes(*last_update_slot),
1028                stale: unpack_bool(last_update_stale)?,
1029            },
1030            lending_market: Pubkey::new_from_array(*lending_market),
1031            liquidity: ReserveLiquidity {
1032                mint_pubkey: Pubkey::new_from_array(*liquidity_mint_pubkey),
1033                mint_decimals: u8::from_le_bytes(*liquidity_mint_decimals),
1034                supply_pubkey: Pubkey::new_from_array(*liquidity_supply_pubkey),
1035                pyth_oracle_pubkey: Pubkey::new_from_array(*liquidity_pyth_oracle_pubkey),
1036                switchboard_oracle_pubkey: Pubkey::new_from_array(
1037                    *liquidity_switchboard_oracle_pubkey,
1038                ),
1039                available_amount: u64::from_le_bytes(*liquidity_available_amount),
1040                borrowed_amount_wads: unpack_decimal(liquidity_borrowed_amount_wads),
1041                cumulative_borrow_rate_wads: unpack_decimal(liquidity_cumulative_borrow_rate_wads),
1042                accumulated_protocol_fees_wads: unpack_decimal(
1043                    liquidity_accumulated_protocol_fees_wads,
1044                ),
1045                market_price: unpack_decimal(liquidity_market_price),
1046            },
1047            collateral: ReserveCollateral {
1048                mint_pubkey: Pubkey::new_from_array(*collateral_mint_pubkey),
1049                mint_total_supply: u64::from_le_bytes(*collateral_mint_total_supply),
1050                supply_pubkey: Pubkey::new_from_array(*collateral_supply_pubkey),
1051            },
1052            config: ReserveConfig {
1053                optimal_utilization_rate: u8::from_le_bytes(*config_optimal_utilization_rate),
1054                loan_to_value_ratio: u8::from_le_bytes(*config_loan_to_value_ratio),
1055                liquidation_bonus: u8::from_le_bytes(*config_liquidation_bonus),
1056                liquidation_threshold: u8::from_le_bytes(*config_liquidation_threshold),
1057                min_borrow_rate: u8::from_le_bytes(*config_min_borrow_rate),
1058                optimal_borrow_rate: u8::from_le_bytes(*config_optimal_borrow_rate),
1059                max_borrow_rate: u8::from_le_bytes(*config_max_borrow_rate),
1060                fees: ReserveFees {
1061                    borrow_fee_wad: u64::from_le_bytes(*config_fees_borrow_fee_wad),
1062                    flash_loan_fee_wad: u64::from_le_bytes(*config_fees_flash_loan_fee_wad),
1063                    host_fee_percentage: u8::from_le_bytes(*config_fees_host_fee_percentage),
1064                },
1065                deposit_limit: u64::from_le_bytes(*config_deposit_limit),
1066                borrow_limit: u64::from_le_bytes(*config_borrow_limit),
1067                fee_receiver: Pubkey::new_from_array(*config_fee_receiver),
1068                protocol_liquidation_fee: u8::from_le_bytes(*config_protocol_liquidation_fee),
1069                protocol_take_rate: u8::from_le_bytes(*config_protocol_take_rate),
1070            },
1071        })
1072    }
1073}
1074
1075#[cfg(test)]
1076mod test {
1077    use super::*;
1078    use crate::math::{PERCENT_SCALER, WAD};
1079    use proptest::prelude::*;
1080    use std::cmp::Ordering;
1081    use std::default::Default;
1082
1083    const MAX_LIQUIDITY: u64 = u64::MAX / 5;
1084
1085    // Creates rates (min, opt, max) where 0 <= min <= opt <= max <= MAX
1086    prop_compose! {
1087        fn borrow_rates()(optimal_rate in 0..=u8::MAX)(
1088            min_rate in 0..=optimal_rate,
1089            optimal_rate in Just(optimal_rate),
1090            max_rate in optimal_rate..=u8::MAX,
1091        ) -> (u8, u8, u8) {
1092            (min_rate, optimal_rate, max_rate)
1093        }
1094    }
1095
1096    // Creates rates (threshold, ltv) where 2 <= threshold <= 100 and threshold <= ltv <= 1,000%
1097    prop_compose! {
1098        fn unhealthy_rates()(threshold in 2..=100u8)(
1099            ltv_rate in threshold as u64..=1000u64,
1100            threshold in Just(threshold),
1101        ) -> (Decimal, u8) {
1102            (Decimal::from_scaled_val(ltv_rate as u128 * PERCENT_SCALER as u128), threshold)
1103        }
1104    }
1105
1106    // Creates a range of reasonable token conversion rates
1107    prop_compose! {
1108        fn token_conversion_rate()(
1109            conversion_rate in 1..=u16::MAX,
1110            invert_conversion_rate: bool,
1111        ) -> Decimal {
1112            let conversion_rate = Decimal::from(conversion_rate as u64);
1113            if invert_conversion_rate {
1114                Decimal::one().try_div(conversion_rate).unwrap()
1115            } else {
1116                conversion_rate
1117            }
1118        }
1119    }
1120
1121    // Creates a range of reasonable collateral exchange rates
1122    prop_compose! {
1123        fn collateral_exchange_rate_range()(percent in 1..=500u64) -> CollateralExchangeRate {
1124            CollateralExchangeRate(Rate::from_scaled_val(percent * PERCENT_SCALER))
1125        }
1126    }
1127
1128    proptest! {
1129        #[test]
1130        fn current_borrow_rate(
1131            total_liquidity in 0..=MAX_LIQUIDITY,
1132            borrowed_percent in 0..=WAD,
1133            optimal_utilization_rate in 0..=100u8,
1134            (min_borrow_rate, optimal_borrow_rate, max_borrow_rate) in borrow_rates(),
1135        ) {
1136            let borrowed_amount_wads = Decimal::from(total_liquidity).try_mul(Rate::from_scaled_val(borrowed_percent))?;
1137            let reserve = Reserve {
1138                liquidity: ReserveLiquidity {
1139                    borrowed_amount_wads,
1140                    available_amount: total_liquidity - borrowed_amount_wads.try_round_u64()?,
1141                    ..ReserveLiquidity::default()
1142                },
1143                config: ReserveConfig { optimal_utilization_rate, min_borrow_rate, optimal_borrow_rate, max_borrow_rate, ..ReserveConfig::default() },
1144                ..Reserve::default()
1145            };
1146
1147            if !(optimal_borrow_rate > 246 && optimal_borrow_rate == max_borrow_rate) {
1148                let current_borrow_rate = reserve.current_borrow_rate()?;
1149                assert!(current_borrow_rate >= Rate::from_percent(min_borrow_rate));
1150                assert!(current_borrow_rate <= Rate::from_percent(max_borrow_rate));
1151
1152                let optimal_borrow_rate = Rate::from_percent(optimal_borrow_rate);
1153                let current_rate = reserve.liquidity.utilization_rate()?;
1154                match current_rate.cmp(&Rate::from_percent(optimal_utilization_rate)) {
1155                    Ordering::Less => {
1156                        if min_borrow_rate == reserve.config.optimal_borrow_rate {
1157                            assert_eq!(current_borrow_rate, optimal_borrow_rate);
1158                        } else {
1159                            assert!(current_borrow_rate < optimal_borrow_rate);
1160                        }
1161                    }
1162                    Ordering::Equal => assert!(current_borrow_rate == optimal_borrow_rate),
1163                    Ordering::Greater => {
1164                        if max_borrow_rate == reserve.config.optimal_borrow_rate {
1165                            assert_eq!(current_borrow_rate, optimal_borrow_rate);
1166                        } else {
1167                            assert!(current_borrow_rate > optimal_borrow_rate);
1168                        }
1169                    }
1170                }
1171            }
1172        }
1173
1174        #[test]
1175        fn current_utilization_rate(
1176            total_liquidity in 0..=MAX_LIQUIDITY,
1177            borrowed_percent in 0..=WAD,
1178        ) {
1179            let borrowed_amount_wads = Decimal::from(total_liquidity).try_mul(Rate::from_scaled_val(borrowed_percent))?;
1180            let liquidity = ReserveLiquidity {
1181                borrowed_amount_wads,
1182                available_amount: total_liquidity - borrowed_amount_wads.try_round_u64()?,
1183                ..ReserveLiquidity::default()
1184            };
1185
1186            let current_rate = liquidity.utilization_rate()?;
1187            assert!(current_rate <= Rate::one());
1188        }
1189
1190        #[test]
1191        fn collateral_exchange_rate(
1192            total_liquidity in 0..=MAX_LIQUIDITY,
1193            borrowed_percent in 0..=WAD,
1194            collateral_multiplier in 0..=(5*WAD),
1195            borrow_rate in 0..=u8::MAX,
1196        ) {
1197            let borrowed_liquidity_wads = Decimal::from(total_liquidity).try_mul(Rate::from_scaled_val(borrowed_percent))?;
1198            let available_liquidity = total_liquidity - borrowed_liquidity_wads.try_round_u64()?;
1199            let mint_total_supply = Decimal::from(total_liquidity).try_mul(Rate::from_scaled_val(collateral_multiplier))?.try_round_u64()?;
1200
1201            let mut reserve = Reserve {
1202                collateral: ReserveCollateral {
1203                    mint_total_supply,
1204                    ..ReserveCollateral::default()
1205                },
1206                liquidity: ReserveLiquidity {
1207                    borrowed_amount_wads: borrowed_liquidity_wads,
1208                    available_amount: available_liquidity,
1209                    ..ReserveLiquidity::default()
1210                },
1211                config: ReserveConfig {
1212                    min_borrow_rate: borrow_rate,
1213                    optimal_borrow_rate: borrow_rate,
1214                    optimal_utilization_rate: 100,
1215                    ..ReserveConfig::default()
1216                },
1217                ..Reserve::default()
1218            };
1219
1220            let exchange_rate = reserve.collateral_exchange_rate()?;
1221            assert!(exchange_rate.0.to_scaled_val() <= 5u128 * WAD as u128);
1222
1223            // After interest accrual, total liquidity increases and collateral are worth more
1224            reserve.accrue_interest(1)?;
1225
1226            let new_exchange_rate = reserve.collateral_exchange_rate()?;
1227            if borrow_rate > 0 && total_liquidity > 0 && borrowed_percent > 0 {
1228                assert!(new_exchange_rate.0 < exchange_rate.0);
1229            } else {
1230                assert_eq!(new_exchange_rate.0, exchange_rate.0);
1231            }
1232        }
1233
1234        #[test]
1235        fn compound_interest(
1236            slots_elapsed in 0..=SLOTS_PER_YEAR,
1237            borrow_rate in 0..=u8::MAX,
1238            take_rate in 0..=100u8,
1239        ) {
1240            let mut reserve = Reserve::default();
1241            let borrow_rate = Rate::from_percent(borrow_rate);
1242            let take_rate = Rate::from_percent(take_rate);
1243
1244            // Simulate running for max 1000 years, assuming that interest is
1245            // compounded at least once a year
1246            for _ in 0..1000 {
1247                reserve.liquidity.compound_interest(borrow_rate, slots_elapsed, take_rate)?;
1248                reserve.liquidity.cumulative_borrow_rate_wads.to_scaled_val()?;
1249                reserve.liquidity.accumulated_protocol_fees_wads.to_scaled_val()?;
1250            }
1251        }
1252
1253        #[test]
1254        fn reserve_accrue_interest(
1255            slots_elapsed in 0..=SLOTS_PER_YEAR,
1256            borrowed_liquidity in 0..=u64::MAX,
1257            borrow_rate in 0..=u8::MAX,
1258        ) {
1259            let borrowed_amount_wads = Decimal::from(borrowed_liquidity);
1260            let mut reserve = Reserve {
1261                liquidity: ReserveLiquidity {
1262                    borrowed_amount_wads,
1263                    ..ReserveLiquidity::default()
1264                },
1265                config: ReserveConfig {
1266                    max_borrow_rate: borrow_rate,
1267                    ..ReserveConfig::default()
1268                },
1269                ..Reserve::default()
1270            };
1271
1272            reserve.accrue_interest(slots_elapsed)?;
1273
1274            if borrow_rate > 0 && slots_elapsed > 0 {
1275                assert!(reserve.liquidity.borrowed_amount_wads > borrowed_amount_wads);
1276            } else {
1277                assert!(reserve.liquidity.borrowed_amount_wads == borrowed_amount_wads);
1278            }
1279        }
1280
1281        #[test]
1282        fn borrow_fee_calculation(
1283            borrow_fee_wad in 0..WAD, // at WAD, fee == borrow amount, which fails
1284            flash_loan_fee_wad in 0..WAD, // at WAD, fee == borrow amount, which fails
1285            host_fee_percentage in 0..=100u8,
1286            borrow_amount in 3..=u64::MAX, // start at 3 to ensure calculation success
1287                                           // 0, 1, and 2 are covered in the minimum tests
1288                                           // @FIXME: ^ no longer true
1289        ) {
1290            let fees = ReserveFees {
1291                borrow_fee_wad,
1292                flash_loan_fee_wad,
1293                host_fee_percentage,
1294            };
1295            let (total_fee, host_fee) = fees.calculate_borrow_fees(Decimal::from(borrow_amount), FeeCalculation::Exclusive)?;
1296
1297            // The total fee can't be greater than the amount borrowed, as long
1298            // as amount borrowed is greater than 2.
1299            // At a borrow amount of 2, we can get a total fee of 2 if a host
1300            // fee is also specified.
1301            assert!(total_fee <= borrow_amount);
1302
1303            // the host fee can't be greater than the total fee
1304            assert!(host_fee <= total_fee);
1305
1306            // for all fee rates greater than 0, we must have some fee
1307            if borrow_fee_wad > 0 {
1308                assert!(total_fee > 0);
1309            }
1310
1311            if host_fee_percentage == 100 {
1312                // if the host fee percentage is maxed at 100%, it should get all the fee
1313                assert_eq!(host_fee, total_fee);
1314            }
1315
1316            // if there's a host fee and some borrow fee, host fee must be greater than 0
1317            if host_fee_percentage > 0 && borrow_fee_wad > 0 {
1318                assert!(host_fee > 0);
1319            } else {
1320                assert_eq!(host_fee, 0);
1321            }
1322        }
1323
1324        #[test]
1325        fn flash_loan_fee_calculation(
1326            borrow_fee_wad in 0..WAD, // at WAD, fee == borrow amount, which fails
1327            flash_loan_fee_wad in 0..WAD, // at WAD, fee == borrow amount, which fails
1328            host_fee_percentage in 0..=100u8,
1329            borrow_amount in 3..=u64::MAX, // start at 3 to ensure calculation success
1330                                           // 0, 1, and 2 are covered in the minimum tests
1331                                           // @FIXME: ^ no longer true
1332        ) {
1333            let fees = ReserveFees {
1334                borrow_fee_wad,
1335                flash_loan_fee_wad,
1336                host_fee_percentage,
1337            };
1338            let (origination_fee, host_fee) = fees.calculate_flash_loan_fees(Decimal::from(borrow_amount))?;
1339
1340            // The total fee can't be greater than the amount borrowed, as long
1341            // as amount borrowed is greater than 2.
1342            // At a borrow amount of 2, we can get a total fee of 2 if a host
1343            // fee is also specified.
1344            assert!(origination_fee + host_fee <= borrow_amount);
1345
1346            // for all fee rates greater than 0, we must have some fee
1347            if borrow_fee_wad > 0 {
1348                assert!(origination_fee + host_fee > 0);
1349            }
1350
1351            if host_fee_percentage == 100 {
1352                // if the host fee percentage is maxed at 100%, it should get all the fee
1353                assert_eq!(origination_fee, 0);
1354            }
1355
1356            // if there's a host fee and some borrow fee, host fee must be greater than 0
1357            if host_fee_percentage > 0 && borrow_fee_wad > 0 {
1358                assert!(host_fee > 0);
1359            } else {
1360                assert_eq!(host_fee, 0);
1361            }
1362        }
1363    }
1364
1365    #[test]
1366    fn borrow_fee_calculation_min_host() {
1367        let fees = ReserveFees {
1368            borrow_fee_wad: 10_000_000_000_000_000, // 1%
1369            flash_loan_fee_wad: 0,
1370            host_fee_percentage: 20,
1371        };
1372
1373        // only 2 tokens borrowed, get error
1374        let err = fees
1375            .calculate_borrow_fees(Decimal::from(2u64), FeeCalculation::Exclusive)
1376            .unwrap_err();
1377        assert_eq!(err, LendingError::BorrowTooSmall.into()); // minimum of 3 tokens
1378
1379        // only 1 token borrowed, get error
1380        let err = fees
1381            .calculate_borrow_fees(Decimal::one(), FeeCalculation::Exclusive)
1382            .unwrap_err();
1383        assert_eq!(err, LendingError::BorrowTooSmall.into());
1384
1385        // 0 amount borrowed, 0 fee
1386        let (total_fee, host_fee) = fees
1387            .calculate_borrow_fees(Decimal::zero(), FeeCalculation::Exclusive)
1388            .unwrap();
1389        assert_eq!(total_fee, 0);
1390        assert_eq!(host_fee, 0);
1391    }
1392
1393    #[test]
1394    fn borrow_fee_calculation_min_no_host() {
1395        let fees = ReserveFees {
1396            borrow_fee_wad: 10_000_000_000_000_000, // 1%
1397            flash_loan_fee_wad: 0,
1398            host_fee_percentage: 0,
1399        };
1400
1401        // only 2 tokens borrowed, ok
1402        let (total_fee, host_fee) = fees
1403            .calculate_borrow_fees(Decimal::from(2u64), FeeCalculation::Exclusive)
1404            .unwrap();
1405        assert_eq!(total_fee, 1);
1406        assert_eq!(host_fee, 0);
1407
1408        // only 1 token borrowed, get error
1409        let err = fees
1410            .calculate_borrow_fees(Decimal::one(), FeeCalculation::Exclusive)
1411            .unwrap_err();
1412        assert_eq!(err, LendingError::BorrowTooSmall.into()); // minimum of 2 tokens
1413
1414        // 0 amount borrowed, 0 fee
1415        let (total_fee, host_fee) = fees
1416            .calculate_borrow_fees(Decimal::zero(), FeeCalculation::Exclusive)
1417            .unwrap();
1418        assert_eq!(total_fee, 0);
1419        assert_eq!(host_fee, 0);
1420    }
1421
1422    #[test]
1423    fn borrow_fee_calculation_host() {
1424        let fees = ReserveFees {
1425            borrow_fee_wad: 10_000_000_000_000_000, // 1%
1426            flash_loan_fee_wad: 0,
1427            host_fee_percentage: 20,
1428        };
1429
1430        let (total_fee, host_fee) = fees
1431            .calculate_borrow_fees(Decimal::from(1000u64), FeeCalculation::Exclusive)
1432            .unwrap();
1433
1434        assert_eq!(total_fee, 10); // 1% of 1000
1435        assert_eq!(host_fee, 2); // 20% of 10
1436    }
1437
1438    #[test]
1439    fn borrow_fee_calculation_no_host() {
1440        let fees = ReserveFees {
1441            borrow_fee_wad: 10_000_000_000_000_000, // 1%
1442            flash_loan_fee_wad: 0,
1443            host_fee_percentage: 0,
1444        };
1445
1446        let (total_fee, host_fee) = fees
1447            .calculate_borrow_fees(Decimal::from(1000u64), FeeCalculation::Exclusive)
1448            .unwrap();
1449
1450        assert_eq!(total_fee, 10); // 1% of 1000
1451        assert_eq!(host_fee, 0); // 0 host fee
1452    }
1453
1454    #[derive(Debug, Clone)]
1455    struct LiquidationTestCase {
1456        deposit_amount: u64,
1457        deposit_market_value: u64,
1458        borrow_amount: u64,
1459        borrow_market_value: u64,
1460        liquidation_result: CalculateLiquidationResult,
1461    }
1462
1463    fn calculate_liquidation_test_cases() -> impl Strategy<Value = LiquidationTestCase> {
1464        let close_factor: Decimal = Rate::from_percent(LIQUIDATION_CLOSE_FACTOR)
1465            .try_into()
1466            .unwrap();
1467        let liquidation_bonus: Decimal = Rate::from_percent(5)
1468            .try_add(Rate::one())
1469            .unwrap()
1470            .try_into()
1471            .unwrap();
1472
1473        prop_oneof![
1474            // collateral market value > liquidation value
1475            Just(LiquidationTestCase {
1476                deposit_amount: 1000,
1477                deposit_market_value: 100,
1478                borrow_amount: 800,
1479                borrow_market_value: 80,
1480                liquidation_result: CalculateLiquidationResult {
1481                    settle_amount: close_factor.try_mul(Decimal::from(800u64)).unwrap(),
1482                    repay_amount: close_factor
1483                        .try_mul(Decimal::from(800u64))
1484                        .unwrap()
1485                        .try_ceil_u64()
1486                        .unwrap(),
1487                    withdraw_amount: close_factor
1488                        .try_mul(liquidation_bonus)
1489                        .unwrap()
1490                        .try_mul(Decimal::from(800u64))
1491                        .unwrap()
1492                        .try_floor_u64()
1493                        .unwrap(),
1494                },
1495            }),
1496            // collateral market value == liquidation_value
1497            Just(LiquidationTestCase {
1498                borrow_amount: 8000,
1499                borrow_market_value: 8000,
1500                deposit_amount: (8000 * LIQUIDATION_CLOSE_FACTOR as u64) * 105 / 10000,
1501                deposit_market_value: (8000 * LIQUIDATION_CLOSE_FACTOR as u64) * 105 / 10000,
1502
1503                liquidation_result: CalculateLiquidationResult {
1504                    settle_amount: Decimal::from((8000 * LIQUIDATION_CLOSE_FACTOR as u64) / 100),
1505                    repay_amount: (8000 * LIQUIDATION_CLOSE_FACTOR as u64) / 100,
1506                    withdraw_amount: (8000 * LIQUIDATION_CLOSE_FACTOR as u64) * 105 / 10000,
1507                },
1508            }),
1509            // collateral market value < liquidation_value
1510            Just(LiquidationTestCase {
1511                borrow_amount: 8000,
1512                borrow_market_value: 8000,
1513
1514                // half of liquidation value
1515                deposit_amount: (8000 * LIQUIDATION_CLOSE_FACTOR as u64) * 105 / 10000 / 2,
1516                deposit_market_value: (8000 * LIQUIDATION_CLOSE_FACTOR as u64) * 105 / 10000 / 2,
1517
1518                liquidation_result: CalculateLiquidationResult {
1519                    settle_amount: Decimal::from(
1520                        (8000 * LIQUIDATION_CLOSE_FACTOR as u64) / 100 / 2
1521                    ),
1522                    repay_amount: (8000 * LIQUIDATION_CLOSE_FACTOR as u64) / 100 / 2,
1523                    withdraw_amount: (8000 * LIQUIDATION_CLOSE_FACTOR as u64) * 105 / 10000 / 2,
1524                },
1525            }),
1526            // dust ObligationLiquidity where collateral market value > liquidation value
1527            Just(LiquidationTestCase {
1528                borrow_amount: 1,
1529                borrow_market_value: 1000,
1530                deposit_amount: 1000,
1531                deposit_market_value: 2100,
1532
1533                liquidation_result: CalculateLiquidationResult {
1534                    settle_amount: Decimal::from(1u64),
1535                    repay_amount: 1,
1536                    withdraw_amount: 500,
1537                },
1538            }),
1539            // dust ObligationLiquidity where collateral market value == liquidation value
1540            Just(LiquidationTestCase {
1541                borrow_amount: 1,
1542                borrow_market_value: 1000,
1543                deposit_amount: 1000,
1544                deposit_market_value: 1050,
1545
1546                liquidation_result: CalculateLiquidationResult {
1547                    settle_amount: Decimal::from(1u64),
1548                    repay_amount: 1,
1549                    withdraw_amount: 1000,
1550                },
1551            }),
1552            // dust ObligationLiquidity where collateral market value < liquidation value
1553            Just(LiquidationTestCase {
1554                borrow_amount: 1,
1555                borrow_market_value: 1000,
1556                deposit_amount: 1000,
1557                deposit_market_value: 1000,
1558
1559                liquidation_result: CalculateLiquidationResult {
1560                    settle_amount: Decimal::from(1u64),
1561                    repay_amount: 1,
1562                    withdraw_amount: 1000,
1563                },
1564            }),
1565        ]
1566    }
1567
1568    proptest! {
1569        #[test]
1570        fn calculate_liquidation(test_case in calculate_liquidation_test_cases()) {
1571            let reserve = Reserve {
1572                config: ReserveConfig {
1573                    liquidation_bonus: 5,
1574                    ..ReserveConfig::default()
1575                },
1576                ..Reserve::default()
1577            };
1578
1579            let obligation = Obligation {
1580                deposits: vec![ObligationCollateral {
1581                    deposit_reserve: Pubkey::new_unique(),
1582                    deposited_amount: test_case.deposit_amount,
1583                    market_value: Decimal::from(test_case.deposit_market_value),
1584                }],
1585                borrows: vec![ObligationLiquidity {
1586                    borrow_reserve: Pubkey::new_unique(),
1587                    cumulative_borrow_rate_wads: Decimal::one(),
1588                    borrowed_amount_wads: Decimal::from(test_case.borrow_amount),
1589                    market_value: Decimal::from(test_case.borrow_market_value),
1590                }],
1591                borrowed_value: Decimal::from(test_case.borrow_market_value),
1592                ..Obligation::default()
1593            };
1594
1595            assert_eq!(
1596                reserve.calculate_liquidation(
1597                    u64::MAX, &obligation, &obligation.borrows[0], &obligation.deposits[0]).unwrap(),
1598                test_case.liquidation_result);
1599        }
1600    }
1601}