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
611#[derive(Clone, Copy, Debug, Default, PartialEq)]
617pub struct ReserveFees {
618 pub borrow_fee_wad: u64,
625 pub flash_loan_fee_wad: u64,
628 pub host_fee_percentage: u8,
630}
631
632impl ReserveFees {
633 pub fn calculate_borrow_fees(
635 &self,
636 borrow_amount: Decimal,
637 fee_calculation: FeeCalculation,
638 ) -> Result<(u64, u64), ProgramError> {
639 self.calculate_fees(borrow_amount, self.borrow_fee_wad, fee_calculation)
640 }
641
642 pub fn calculate_flash_loan_fees(
644 &self,
645 flash_loan_amount: Decimal,
646 ) -> Result<(u64, u64), ProgramError> {
647 self.calculate_fees(
648 flash_loan_amount,
649 self.flash_loan_fee_wad,
650 FeeCalculation::Exclusive,
651 )
652 }
653
654 fn calculate_fees(
655 &self,
656 amount: Decimal,
657 fee_wad: u64,
658 fee_calculation: FeeCalculation,
659 ) -> Result<(u64, u64), ProgramError> {
660 let borrow_fee_rate = Rate::from_scaled_val(fee_wad);
661 let host_fee_rate = Rate::from_percent(self.host_fee_percentage);
662 if borrow_fee_rate > Rate::zero() && amount > Decimal::zero() {
663 let need_to_assess_host_fee = host_fee_rate > Rate::zero();
664 let minimum_fee = if need_to_assess_host_fee {
665 2u64 } else {
667 1u64 };
669
670 let borrow_fee_amount = match fee_calculation {
671 FeeCalculation::Exclusive => amount.try_mul(borrow_fee_rate)?,
673 FeeCalculation::Inclusive => {
675 let borrow_fee_rate =
676 borrow_fee_rate.try_div(borrow_fee_rate.try_add(Rate::one())?)?;
677 amount.try_mul(borrow_fee_rate)?
678 }
679 };
680
681 let borrow_fee_decimal = borrow_fee_amount.max(minimum_fee.into());
682 if borrow_fee_decimal >= amount {
683 msg!("Borrow amount is too small to receive liquidity after fees");
684 return Err(LendingError::BorrowTooSmall.into());
685 }
686
687 let borrow_fee = borrow_fee_decimal.try_round_u64()?;
688 let host_fee = if need_to_assess_host_fee {
689 borrow_fee_decimal
690 .try_mul(host_fee_rate)?
691 .try_round_u64()?
692 .max(1u64)
693 } else {
694 0
695 };
696
697 Ok((borrow_fee, host_fee))
698 } else {
699 Ok((0, 0))
700 }
701 }
702}
703
704pub enum FeeCalculation {
706 Exclusive,
708 Inclusive,
710}
711
712impl Sealed for Reserve {}
713impl IsInitialized for Reserve {
714 fn is_initialized(&self) -> bool {
715 self.version != UNINITIALIZED_VERSION
716 }
717}
718
719const RESERVE_LEN: usize = 571; impl Pack for Reserve {
721 const LEN: usize = RESERVE_LEN;
722
723 fn pack_into_slice(&self, output: &mut [u8]) {
725 let output = array_mut_ref![output, 0, RESERVE_LEN];
726 #[allow(clippy::ptr_offset_with_cast)]
727 let (
728 version,
729 last_update_slot,
730 last_update_stale,
731 lending_market,
732 liquidity_mint_pubkey,
733 liquidity_mint_decimals,
734 liquidity_supply_pubkey,
735 liquidity_fee_receiver,
736 liquidity_oracle_pubkey,
737 liquidity_available_amount,
738 liquidity_borrowed_amount_wads,
739 liquidity_cumulative_borrow_rate_wads,
740 liquidity_market_price,
741 collateral_mint_pubkey,
742 collateral_mint_total_supply,
743 collateral_supply_pubkey,
744 config_optimal_utilization_rate,
745 config_loan_to_value_ratio,
746 config_liquidation_bonus,
747 config_liquidation_threshold,
748 config_min_borrow_rate,
749 config_optimal_borrow_rate,
750 config_max_borrow_rate,
751 config_fees_borrow_fee_wad,
752 config_fees_flash_loan_fee_wad,
753 config_fees_host_fee_percentage,
754 _padding,
755 ) = mut_array_refs![
756 output,
757 1,
758 8,
759 1,
760 PUBKEY_BYTES,
761 PUBKEY_BYTES,
762 1,
763 PUBKEY_BYTES,
764 PUBKEY_BYTES,
765 PUBKEY_BYTES,
766 8,
767 16,
768 16,
769 16,
770 PUBKEY_BYTES,
771 8,
772 PUBKEY_BYTES,
773 1,
774 1,
775 1,
776 1,
777 1,
778 1,
779 1,
780 8,
781 8,
782 1,
783 248
784 ];
785
786 *version = self.version.to_le_bytes();
788 *last_update_slot = self.last_update.slot.to_le_bytes();
789 pack_bool(self.last_update.stale, last_update_stale);
790 lending_market.copy_from_slice(self.lending_market.as_ref());
791
792 liquidity_mint_pubkey.copy_from_slice(self.liquidity.mint_pubkey.as_ref());
794 *liquidity_mint_decimals = self.liquidity.mint_decimals.to_le_bytes();
795 liquidity_supply_pubkey.copy_from_slice(self.liquidity.supply_pubkey.as_ref());
796 liquidity_fee_receiver.copy_from_slice(self.liquidity.fee_receiver.as_ref());
797 liquidity_oracle_pubkey.copy_from_slice(self.liquidity.oracle_pubkey.as_ref());
798 *liquidity_available_amount = self.liquidity.available_amount.to_le_bytes();
799 pack_decimal(
800 self.liquidity.borrowed_amount_wads,
801 liquidity_borrowed_amount_wads,
802 );
803 pack_decimal(
804 self.liquidity.cumulative_borrow_rate_wads,
805 liquidity_cumulative_borrow_rate_wads,
806 );
807 pack_decimal(self.liquidity.market_price, liquidity_market_price);
808
809 collateral_mint_pubkey.copy_from_slice(self.collateral.mint_pubkey.as_ref());
811 *collateral_mint_total_supply = self.collateral.mint_total_supply.to_le_bytes();
812 collateral_supply_pubkey.copy_from_slice(self.collateral.supply_pubkey.as_ref());
813
814 *config_optimal_utilization_rate = self.config.optimal_utilization_rate.to_le_bytes();
816 *config_loan_to_value_ratio = self.config.loan_to_value_ratio.to_le_bytes();
817 *config_liquidation_bonus = self.config.liquidation_bonus.to_le_bytes();
818 *config_liquidation_threshold = self.config.liquidation_threshold.to_le_bytes();
819 *config_min_borrow_rate = self.config.min_borrow_rate.to_le_bytes();
820 *config_optimal_borrow_rate = self.config.optimal_borrow_rate.to_le_bytes();
821 *config_max_borrow_rate = self.config.max_borrow_rate.to_le_bytes();
822 *config_fees_borrow_fee_wad = self.config.fees.borrow_fee_wad.to_le_bytes();
823 *config_fees_flash_loan_fee_wad = self.config.fees.flash_loan_fee_wad.to_le_bytes();
824 *config_fees_host_fee_percentage = self.config.fees.host_fee_percentage.to_le_bytes();
825 }
826
827 fn unpack_from_slice(input: &[u8]) -> Result<Self, ProgramError> {
829 let input = array_ref![input, 0, RESERVE_LEN];
830 #[allow(clippy::ptr_offset_with_cast)]
831 let (
832 version,
833 last_update_slot,
834 last_update_stale,
835 lending_market,
836 liquidity_mint_pubkey,
837 liquidity_mint_decimals,
838 liquidity_supply_pubkey,
839 liquidity_fee_receiver,
840 liquidity_oracle_pubkey,
841 liquidity_available_amount,
842 liquidity_borrowed_amount_wads,
843 liquidity_cumulative_borrow_rate_wads,
844 liquidity_market_price,
845 collateral_mint_pubkey,
846 collateral_mint_total_supply,
847 collateral_supply_pubkey,
848 config_optimal_utilization_rate,
849 config_loan_to_value_ratio,
850 config_liquidation_bonus,
851 config_liquidation_threshold,
852 config_min_borrow_rate,
853 config_optimal_borrow_rate,
854 config_max_borrow_rate,
855 config_fees_borrow_fee_wad,
856 config_fees_flash_loan_fee_wad,
857 config_fees_host_fee_percentage,
858 _padding,
859 ) = array_refs![
860 input,
861 1,
862 8,
863 1,
864 PUBKEY_BYTES,
865 PUBKEY_BYTES,
866 1,
867 PUBKEY_BYTES,
868 PUBKEY_BYTES,
869 PUBKEY_BYTES,
870 8,
871 16,
872 16,
873 16,
874 PUBKEY_BYTES,
875 8,
876 PUBKEY_BYTES,
877 1,
878 1,
879 1,
880 1,
881 1,
882 1,
883 1,
884 8,
885 8,
886 1,
887 248
888 ];
889
890 let version = u8::from_le_bytes(*version);
891 if version > PROGRAM_VERSION {
892 msg!("Reserve version does not match lending program version");
893 return Err(ProgramError::InvalidAccountData);
894 }
895
896 Ok(Self {
897 version,
898 last_update: LastUpdate {
899 slot: u64::from_le_bytes(*last_update_slot),
900 stale: unpack_bool(last_update_stale)?,
901 },
902 lending_market: Pubkey::new_from_array(*lending_market),
903 liquidity: ReserveLiquidity {
904 mint_pubkey: Pubkey::new_from_array(*liquidity_mint_pubkey),
905 mint_decimals: u8::from_le_bytes(*liquidity_mint_decimals),
906 supply_pubkey: Pubkey::new_from_array(*liquidity_supply_pubkey),
907 fee_receiver: Pubkey::new_from_array(*liquidity_fee_receiver),
908 oracle_pubkey: Pubkey::new_from_array(*liquidity_oracle_pubkey),
909 available_amount: u64::from_le_bytes(*liquidity_available_amount),
910 borrowed_amount_wads: unpack_decimal(liquidity_borrowed_amount_wads),
911 cumulative_borrow_rate_wads: unpack_decimal(liquidity_cumulative_borrow_rate_wads),
912 market_price: unpack_decimal(liquidity_market_price),
913 },
914 collateral: ReserveCollateral {
915 mint_pubkey: Pubkey::new_from_array(*collateral_mint_pubkey),
916 mint_total_supply: u64::from_le_bytes(*collateral_mint_total_supply),
917 supply_pubkey: Pubkey::new_from_array(*collateral_supply_pubkey),
918 },
919 config: ReserveConfig {
920 optimal_utilization_rate: u8::from_le_bytes(*config_optimal_utilization_rate),
921 loan_to_value_ratio: u8::from_le_bytes(*config_loan_to_value_ratio),
922 liquidation_bonus: u8::from_le_bytes(*config_liquidation_bonus),
923 liquidation_threshold: u8::from_le_bytes(*config_liquidation_threshold),
924 min_borrow_rate: u8::from_le_bytes(*config_min_borrow_rate),
925 optimal_borrow_rate: u8::from_le_bytes(*config_optimal_borrow_rate),
926 max_borrow_rate: u8::from_le_bytes(*config_max_borrow_rate),
927 fees: ReserveFees {
928 borrow_fee_wad: u64::from_le_bytes(*config_fees_borrow_fee_wad),
929 flash_loan_fee_wad: u64::from_le_bytes(*config_fees_flash_loan_fee_wad),
930 host_fee_percentage: u8::from_le_bytes(*config_fees_host_fee_percentage),
931 },
932 },
933 })
934 }
935}
936
937#[cfg(test)]
938mod test {
939 use super::*;
940 use crate::math::{PERCENT_SCALER, WAD};
941 use proptest::prelude::*;
942 use std::cmp::Ordering;
943
944 const MAX_LIQUIDITY: u64 = u64::MAX / 5;
945
946 prop_compose! {
948 fn borrow_rates()(optimal_rate in 0..=u8::MAX)(
949 min_rate in 0..=optimal_rate,
950 optimal_rate in Just(optimal_rate),
951 max_rate in optimal_rate..=u8::MAX,
952 ) -> (u8, u8, u8) {
953 (min_rate, optimal_rate, max_rate)
954 }
955 }
956
957 prop_compose! {
959 fn unhealthy_rates()(threshold in 2..=100u8)(
960 ltv_rate in threshold as u64..=1000u64,
961 threshold in Just(threshold),
962 ) -> (Decimal, u8) {
963 (Decimal::from_scaled_val(ltv_rate as u128 * PERCENT_SCALER as u128), threshold)
964 }
965 }
966
967 prop_compose! {
969 fn token_conversion_rate()(
970 conversion_rate in 1..=u16::MAX,
971 invert_conversion_rate: bool,
972 ) -> Decimal {
973 let conversion_rate = Decimal::from(conversion_rate as u64);
974 if invert_conversion_rate {
975 Decimal::one().try_div(conversion_rate).unwrap()
976 } else {
977 conversion_rate
978 }
979 }
980 }
981
982 prop_compose! {
984 fn collateral_exchange_rate_range()(percent in 1..=500u64) -> CollateralExchangeRate {
985 CollateralExchangeRate(Rate::from_scaled_val(percent * PERCENT_SCALER))
986 }
987 }
988
989 proptest! {
990 #[test]
991 fn current_borrow_rate(
992 total_liquidity in 0..=MAX_LIQUIDITY,
993 borrowed_percent in 0..=WAD,
994 optimal_utilization_rate in 0..=100u8,
995 (min_borrow_rate, optimal_borrow_rate, max_borrow_rate) in borrow_rates(),
996 ) {
997 let borrowed_amount_wads = Decimal::from(total_liquidity).try_mul(Rate::from_scaled_val(borrowed_percent))?;
998 let reserve = Reserve {
999 liquidity: ReserveLiquidity {
1000 borrowed_amount_wads,
1001 available_amount: total_liquidity - borrowed_amount_wads.try_round_u64()?,
1002 ..ReserveLiquidity::default()
1003 },
1004 config: ReserveConfig { optimal_utilization_rate, min_borrow_rate, optimal_borrow_rate, max_borrow_rate, ..ReserveConfig::default() },
1005 ..Reserve::default()
1006 };
1007
1008 let current_borrow_rate = reserve.current_borrow_rate()?;
1009 assert!(current_borrow_rate >= Rate::from_percent(min_borrow_rate));
1010 assert!(current_borrow_rate <= Rate::from_percent(max_borrow_rate));
1011
1012 let optimal_borrow_rate = Rate::from_percent(optimal_borrow_rate);
1013 let current_rate = reserve.liquidity.utilization_rate()?;
1014 match current_rate.cmp(&Rate::from_percent(optimal_utilization_rate)) {
1015 Ordering::Less => {
1016 if min_borrow_rate == reserve.config.optimal_borrow_rate {
1017 assert_eq!(current_borrow_rate, optimal_borrow_rate);
1018 } else {
1019 assert!(current_borrow_rate < optimal_borrow_rate);
1020 }
1021 }
1022 Ordering::Equal => assert!(current_borrow_rate == optimal_borrow_rate),
1023 Ordering::Greater => {
1024 if max_borrow_rate == reserve.config.optimal_borrow_rate {
1025 assert_eq!(current_borrow_rate, optimal_borrow_rate);
1026 } else {
1027 assert!(current_borrow_rate > optimal_borrow_rate);
1028 }
1029 }
1030 }
1031 }
1032
1033 #[test]
1034 fn current_utilization_rate(
1035 total_liquidity in 0..=MAX_LIQUIDITY,
1036 borrowed_percent in 0..=WAD,
1037 ) {
1038 let borrowed_amount_wads = Decimal::from(total_liquidity).try_mul(Rate::from_scaled_val(borrowed_percent))?;
1039 let liquidity = ReserveLiquidity {
1040 borrowed_amount_wads,
1041 available_amount: total_liquidity - borrowed_amount_wads.try_round_u64()?,
1042 ..ReserveLiquidity::default()
1043 };
1044
1045 let current_rate = liquidity.utilization_rate()?;
1046 assert!(current_rate <= Rate::one());
1047 }
1048
1049 #[test]
1050 fn collateral_exchange_rate(
1051 total_liquidity in 0..=MAX_LIQUIDITY,
1052 borrowed_percent in 0..=WAD,
1053 collateral_multiplier in 0..=(5*WAD),
1054 borrow_rate in 0..=u8::MAX,
1055 ) {
1056 let borrowed_liquidity_wads = Decimal::from(total_liquidity).try_mul(Rate::from_scaled_val(borrowed_percent))?;
1057 let available_liquidity = total_liquidity - borrowed_liquidity_wads.try_round_u64()?;
1058 let mint_total_supply = Decimal::from(total_liquidity).try_mul(Rate::from_scaled_val(collateral_multiplier))?.try_round_u64()?;
1059
1060 let mut reserve = Reserve {
1061 collateral: ReserveCollateral {
1062 mint_total_supply,
1063 ..ReserveCollateral::default()
1064 },
1065 liquidity: ReserveLiquidity {
1066 borrowed_amount_wads: borrowed_liquidity_wads,
1067 available_amount: available_liquidity,
1068 ..ReserveLiquidity::default()
1069 },
1070 config: ReserveConfig {
1071 min_borrow_rate: borrow_rate,
1072 optimal_borrow_rate: borrow_rate,
1073 optimal_utilization_rate: 100,
1074 ..ReserveConfig::default()
1075 },
1076 ..Reserve::default()
1077 };
1078
1079 let exchange_rate = reserve.collateral_exchange_rate()?;
1080 assert!(exchange_rate.0.to_scaled_val() <= 5u128 * WAD as u128);
1081
1082 reserve.accrue_interest(1)?;
1084
1085 let new_exchange_rate = reserve.collateral_exchange_rate()?;
1086 if borrow_rate > 0 && total_liquidity > 0 && borrowed_percent > 0 {
1087 assert!(new_exchange_rate.0 < exchange_rate.0);
1088 } else {
1089 assert_eq!(new_exchange_rate.0, exchange_rate.0);
1090 }
1091 }
1092
1093 #[test]
1094 fn compound_interest(
1095 slots_elapsed in 0..=SLOTS_PER_YEAR,
1096 borrow_rate in 0..=u8::MAX,
1097 ) {
1098 let mut reserve = Reserve::default();
1099 let borrow_rate = Rate::from_percent(borrow_rate);
1100
1101 for _ in 0..1000 {
1104 reserve.liquidity.compound_interest(borrow_rate, slots_elapsed)?;
1105 reserve.liquidity.cumulative_borrow_rate_wads.to_scaled_val()?;
1106 }
1107 }
1108
1109 #[test]
1110 fn reserve_accrue_interest(
1111 slots_elapsed in 0..=SLOTS_PER_YEAR,
1112 borrowed_liquidity in 0..=u64::MAX,
1113 borrow_rate in 0..=u8::MAX,
1114 ) {
1115 let borrowed_amount_wads = Decimal::from(borrowed_liquidity);
1116 let mut reserve = Reserve {
1117 liquidity: ReserveLiquidity {
1118 borrowed_amount_wads,
1119 ..ReserveLiquidity::default()
1120 },
1121 config: ReserveConfig {
1122 max_borrow_rate: borrow_rate,
1123 ..ReserveConfig::default()
1124 },
1125 ..Reserve::default()
1126 };
1127
1128 reserve.accrue_interest(slots_elapsed)?;
1129
1130 if borrow_rate > 0 && slots_elapsed > 0 {
1131 assert!(reserve.liquidity.borrowed_amount_wads > borrowed_amount_wads);
1132 } else {
1133 assert!(reserve.liquidity.borrowed_amount_wads == borrowed_amount_wads);
1134 }
1135 }
1136
1137 #[test]
1138 fn borrow_fee_calculation(
1139 borrow_fee_wad in 0..WAD, flash_loan_fee_wad in 0..WAD, host_fee_percentage in 0..=100u8,
1142 borrow_amount in 3..=u64::MAX, ) {
1146 let fees = ReserveFees {
1147 borrow_fee_wad,
1148 flash_loan_fee_wad,
1149 host_fee_percentage,
1150 };
1151 let (total_fee, host_fee) = fees.calculate_borrow_fees(Decimal::from(borrow_amount), FeeCalculation::Exclusive)?;
1152
1153 assert!(total_fee <= borrow_amount);
1158
1159 assert!(host_fee <= total_fee);
1161
1162 if borrow_fee_wad > 0 {
1164 assert!(total_fee > 0);
1165 }
1166
1167 if host_fee_percentage == 100 {
1168 assert_eq!(host_fee, total_fee);
1170 }
1171
1172 if host_fee_percentage > 0 && borrow_fee_wad > 0 {
1174 assert!(host_fee > 0);
1175 } else {
1176 assert_eq!(host_fee, 0);
1177 }
1178 }
1179
1180 #[test]
1181 fn flash_loan_fee_calculation(
1182 borrow_fee_wad in 0..WAD, flash_loan_fee_wad in 0..WAD, host_fee_percentage in 0..=100u8,
1185 borrow_amount in 3..=u64::MAX, ) {
1189 let fees = ReserveFees {
1190 borrow_fee_wad,
1191 flash_loan_fee_wad,
1192 host_fee_percentage,
1193 };
1194 let (total_fee, host_fee) = fees.calculate_flash_loan_fees(Decimal::from(borrow_amount))?;
1195
1196 assert!(total_fee <= borrow_amount);
1201
1202 assert!(host_fee <= total_fee);
1204
1205 if borrow_fee_wad > 0 {
1207 assert!(total_fee > 0);
1208 }
1209
1210 if host_fee_percentage == 100 {
1211 assert_eq!(host_fee, total_fee);
1213 }
1214
1215 if host_fee_percentage > 0 && borrow_fee_wad > 0 {
1217 assert!(host_fee > 0);
1218 } else {
1219 assert_eq!(host_fee, 0);
1220 }
1221 }
1222 }
1223
1224 #[test]
1225 fn borrow_fee_calculation_min_host() {
1226 let fees = ReserveFees {
1227 borrow_fee_wad: 10_000_000_000_000_000, flash_loan_fee_wad: 0,
1229 host_fee_percentage: 20,
1230 };
1231
1232 let err = fees
1234 .calculate_borrow_fees(Decimal::from(2u64), FeeCalculation::Exclusive)
1235 .unwrap_err();
1236 assert_eq!(err, LendingError::BorrowTooSmall.into()); let err = fees
1240 .calculate_borrow_fees(Decimal::one(), FeeCalculation::Exclusive)
1241 .unwrap_err();
1242 assert_eq!(err, LendingError::BorrowTooSmall.into());
1243
1244 let (total_fee, host_fee) = fees
1246 .calculate_borrow_fees(Decimal::zero(), FeeCalculation::Exclusive)
1247 .unwrap();
1248 assert_eq!(total_fee, 0);
1249 assert_eq!(host_fee, 0);
1250 }
1251
1252 #[test]
1253 fn borrow_fee_calculation_min_no_host() {
1254 let fees = ReserveFees {
1255 borrow_fee_wad: 10_000_000_000_000_000, flash_loan_fee_wad: 0,
1257 host_fee_percentage: 0,
1258 };
1259
1260 let (total_fee, host_fee) = fees
1262 .calculate_borrow_fees(Decimal::from(2u64), FeeCalculation::Exclusive)
1263 .unwrap();
1264 assert_eq!(total_fee, 1);
1265 assert_eq!(host_fee, 0);
1266
1267 let err = fees
1269 .calculate_borrow_fees(Decimal::one(), FeeCalculation::Exclusive)
1270 .unwrap_err();
1271 assert_eq!(err, LendingError::BorrowTooSmall.into()); let (total_fee, host_fee) = fees
1275 .calculate_borrow_fees(Decimal::zero(), FeeCalculation::Exclusive)
1276 .unwrap();
1277 assert_eq!(total_fee, 0);
1278 assert_eq!(host_fee, 0);
1279 }
1280
1281 #[test]
1282 fn borrow_fee_calculation_host() {
1283 let fees = ReserveFees {
1284 borrow_fee_wad: 10_000_000_000_000_000, flash_loan_fee_wad: 0,
1286 host_fee_percentage: 20,
1287 };
1288
1289 let (total_fee, host_fee) = fees
1290 .calculate_borrow_fees(Decimal::from(1000u64), FeeCalculation::Exclusive)
1291 .unwrap();
1292
1293 assert_eq!(total_fee, 10); assert_eq!(host_fee, 2); }
1296
1297 #[test]
1298 fn borrow_fee_calculation_no_host() {
1299 let fees = ReserveFees {
1300 borrow_fee_wad: 10_000_000_000_000_000, flash_loan_fee_wad: 0,
1302 host_fee_percentage: 0,
1303 };
1304
1305 let (total_fee, host_fee) = fees
1306 .calculate_borrow_fees(Decimal::from(1000u64), FeeCalculation::Exclusive)
1307 .unwrap();
1308
1309 assert_eq!(total_fee, 10); assert_eq!(host_fee, 0); }
1312}