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