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
20pub const LIQUIDATION_CLOSE_FACTOR: u8 = 50;
22
23pub const LIQUIDATION_CLOSE_AMOUNT: u64 = 2;
25
26#[derive(Clone, Debug, Default, PartialEq)]
28pub struct Reserve {
29 pub version: u8,
31 pub last_update: LastUpdate,
33 pub lending_market: Pubkey,
35 pub liquidity: ReserveLiquidity,
37 pub collateral: ReserveCollateral,
39 pub config: ReserveConfig,
41}
42
43impl Reserve {
44 pub fn new(params: InitReserveParams) -> Self {
46 let mut reserve = Self::default();
47 Self::init(&mut reserve, params);
48 reserve
49 }
50
51 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 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 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 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 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 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 pub fn calculate_borrow(
140 &self,
141 amount_to_borrow: u64,
142 max_borrow_value: Decimal,
143 ) -> Result<CalculateBorrowResult, ProgramError> {
144 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 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 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 if liquidity.borrowed_amount_wads < LIQUIDATION_CLOSE_AMOUNT.into() {
235 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 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
299pub struct InitReserveParams {
301 pub current_slot: Slot,
303 pub lending_market: Pubkey,
305 pub liquidity: ReserveLiquidity,
307 pub collateral: ReserveCollateral,
309 pub config: ReserveConfig,
311}
312
313#[derive(Debug)]
315pub struct CalculateBorrowResult {
316 pub borrow_amount: Decimal,
318 pub receive_amount: u64,
320 pub borrow_fee: u64,
322 pub host_fee: u64,
324}
325
326#[derive(Debug)]
328pub struct CalculateRepayResult {
329 pub settle_amount: Decimal,
331 pub repay_amount: u64,
333}
334
335#[derive(Debug)]
337pub struct CalculateLiquidationResult {
338 pub settle_amount: Decimal,
341 pub repay_amount: u64,
343 pub withdraw_amount: u64,
345}
346
347#[derive(Clone, Debug, Default, PartialEq)]
349pub struct ReserveLiquidity {
350 pub mint_pubkey: Pubkey,
352 pub mint_decimals: u8,
354 pub supply_pubkey: Pubkey,
356 pub fee_receiver: Pubkey,
358 pub oracle_pubkey: Pubkey,
360 pub available_amount: u64,
362 pub borrowed_amount_wads: Decimal,
364 pub cumulative_borrow_rate_wads: Decimal,
366 pub market_price: Decimal,
368}
369
370impl ReserveLiquidity {
371 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 pub fn total_supply(&self) -> Result<Decimal, ProgramError> {
388 Decimal::from(self.available_amount).try_add(self.borrowed_amount_wads)
389 }
390
391 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 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 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 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 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 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
470pub struct NewReserveLiquidityParams {
472 pub mint_pubkey: Pubkey,
474 pub mint_decimals: u8,
476 pub supply_pubkey: Pubkey,
478 pub fee_receiver: Pubkey,
480 pub oracle_pubkey: Pubkey,
482 pub market_price: Decimal,
484}
485
486#[derive(Clone, Debug, Default, PartialEq)]
488pub struct ReserveCollateral {
489 pub mint_pubkey: Pubkey,
491 pub mint_total_supply: u64,
493 pub supply_pubkey: Pubkey,
495}
496
497impl ReserveCollateral {
498 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 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 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 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
541pub struct NewReserveCollateralParams {
543 pub mint_pubkey: Pubkey,
545 pub supply_pubkey: Pubkey,
547}
548
549#[derive(Clone, Copy, Debug)]
551pub struct CollateralExchangeRate(Rate);
552
553impl CollateralExchangeRate {
554 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 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 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 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#[derive(Clone, Copy, Debug, Default, PartialEq)]
591pub struct ReserveConfig {
592 pub optimal_utilization_rate: u8,
594 pub loan_to_value_ratio: u8,
597 pub liquidation_bonus: u8,
599 pub liquidation_threshold: u8,
601 pub min_borrow_rate: u8,
603 pub optimal_borrow_rate: u8,
605 pub max_borrow_rate: u8,
607 pub fees: ReserveFees,
609}
610
611impl ReserveConfig {
612 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#[derive(Clone, Copy, Debug, Default, PartialEq)]
663pub struct ReserveFees {
664 pub borrow_fee_wad: u64,
671 pub flash_loan_fee_wad: u64,
674 pub host_fee_percentage: u8,
676}
677
678impl ReserveFees {
679 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 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 } else {
713 1u64 };
715
716 let borrow_fee_amount = match fee_calculation {
717 FeeCalculation::Exclusive => amount.try_mul(borrow_fee_rate)?,
719 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
750pub enum FeeCalculation {
752 Exclusive,
754 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; impl Pack for Reserve {
767 const LEN: usize = RESERVE_LEN;
768
769 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 *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_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_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_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 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 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 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 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 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 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 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, flash_loan_fee_wad in 0..WAD, host_fee_percentage in 0..=100u8,
1188 borrow_amount in 3..=u64::MAX, ) {
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 assert!(total_fee <= borrow_amount);
1204
1205 assert!(host_fee <= total_fee);
1207
1208 if borrow_fee_wad > 0 {
1210 assert!(total_fee > 0);
1211 }
1212
1213 if host_fee_percentage == 100 {
1214 assert_eq!(host_fee, total_fee);
1216 }
1217
1218 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, flash_loan_fee_wad in 0..WAD, host_fee_percentage in 0..=100u8,
1231 borrow_amount in 3..=u64::MAX, ) {
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 assert!(total_fee <= borrow_amount);
1247
1248 assert!(host_fee <= total_fee);
1250
1251 if borrow_fee_wad > 0 {
1253 assert!(total_fee > 0);
1254 }
1255
1256 if host_fee_percentage == 100 {
1257 assert_eq!(host_fee, total_fee);
1259 }
1260
1261 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, flash_loan_fee_wad: 0,
1275 host_fee_percentage: 20,
1276 };
1277
1278 let err = fees
1280 .calculate_borrow_fees(Decimal::from(2u64), FeeCalculation::Exclusive)
1281 .unwrap_err();
1282 assert_eq!(err, LendingError::BorrowTooSmall.into()); let err = fees
1286 .calculate_borrow_fees(Decimal::one(), FeeCalculation::Exclusive)
1287 .unwrap_err();
1288 assert_eq!(err, LendingError::BorrowTooSmall.into());
1289
1290 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, flash_loan_fee_wad: 0,
1303 host_fee_percentage: 0,
1304 };
1305
1306 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 let err = fees
1315 .calculate_borrow_fees(Decimal::one(), FeeCalculation::Exclusive)
1316 .unwrap_err();
1317 assert_eq!(err, LendingError::BorrowTooSmall.into()); 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, 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); assert_eq!(host_fee, 2); }
1342
1343 #[test]
1344 fn borrow_fee_calculation_no_host() {
1345 let fees = ReserveFees {
1346 borrow_fee_wad: 10_000_000_000_000_000, 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); assert_eq!(host_fee, 0); }
1358}