1use super::*;
2use crate::{
3 error::LendingError,
4 math::{Decimal, Rate, TryAdd, TryDiv, TryMul, TrySub},
5};
6use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs};
7use solana_program::{
8 clock::Slot,
9 entrypoint::ProgramResult,
10 msg,
11 program_error::ProgramError,
12 program_pack::{IsInitialized, Pack, Sealed},
13 pubkey::{Pubkey, PUBKEY_BYTES},
14};
15use std::{
16 cmp::{min, Ordering},
17 convert::{TryFrom, TryInto},
18};
19
20pub const LIQUIDATION_CLOSE_FACTOR: u8 = 20;
22
23pub const LIQUIDATION_CLOSE_AMOUNT: u64 = 2;
25
26pub const MAX_LIQUIDATABLE_VALUE_AT_ONCE: u64 = 500_000;
28
29#[derive(Clone, Debug, Default, PartialEq)]
31pub struct Reserve {
32 pub version: u8,
34 pub last_update: LastUpdate,
36 pub lending_market: Pubkey,
38 pub liquidity: ReserveLiquidity,
40 pub collateral: ReserveCollateral,
42 pub config: ReserveConfig,
44}
45
46impl Reserve {
47 pub fn new(params: InitReserveParams) -> Self {
49 let mut reserve = Self::default();
50 Self::init(&mut reserve, params);
51 reserve
52 }
53
54 pub fn init(&mut self, params: InitReserveParams) {
56 self.version = PROGRAM_VERSION;
57 self.last_update = LastUpdate::new(params.current_slot);
58 self.lending_market = params.lending_market;
59 self.liquidity = params.liquidity;
60 self.collateral = params.collateral;
61 self.config = params.config;
62 }
63
64 pub fn deposit_liquidity(&mut self, liquidity_amount: u64) -> Result<u64, ProgramError> {
66 let collateral_amount = self
67 .collateral_exchange_rate()?
68 .liquidity_to_collateral(liquidity_amount)?;
69
70 self.liquidity.deposit(liquidity_amount)?;
71 self.collateral.mint(collateral_amount)?;
72
73 Ok(collateral_amount)
74 }
75
76 pub fn redeem_collateral(&mut self, collateral_amount: u64) -> Result<u64, ProgramError> {
78 let collateral_exchange_rate = self.collateral_exchange_rate()?;
79 let liquidity_amount =
80 collateral_exchange_rate.collateral_to_liquidity(collateral_amount)?;
81
82 self.collateral.burn(collateral_amount)?;
83 self.liquidity.withdraw(liquidity_amount)?;
84
85 Ok(liquidity_amount)
86 }
87
88 pub fn current_borrow_rate(&self) -> Result<Rate, ProgramError> {
90 let utilization_rate = self.liquidity.utilization_rate()?;
91 let optimal_utilization_rate = Rate::from_percent(self.config.optimal_utilization_rate);
92 let low_utilization = utilization_rate < optimal_utilization_rate;
93 if low_utilization || self.config.optimal_utilization_rate == 100 {
94 let normalized_rate = utilization_rate.try_div(optimal_utilization_rate)?;
95 let min_rate = Rate::from_percent(self.config.min_borrow_rate);
96 let rate_range = Rate::from_percent(
97 self.config
98 .optimal_borrow_rate
99 .checked_sub(self.config.min_borrow_rate)
100 .ok_or(LendingError::MathOverflow)?,
101 );
102
103 Ok(normalized_rate.try_mul(rate_range)?.try_add(min_rate)?)
104 } else {
105 if self.config.optimal_borrow_rate == self.config.max_borrow_rate {
106 let rate = Rate::from_percent(50u8);
107 return Ok(match self.config.max_borrow_rate {
108 251u8 => rate.try_mul(6)?, 252u8 => rate.try_mul(7)?, 253u8 => rate.try_mul(8)?, 254u8 => rate.try_mul(10)?, 255u8 => rate.try_mul(12)?, 250u8 => rate.try_mul(20)?, 249u8 => rate.try_mul(30)?, 248u8 => rate.try_mul(40)?, 247u8 => rate.try_mul(50)?, _ => Rate::from_percent(self.config.max_borrow_rate),
118 });
119 }
120 let normalized_rate = utilization_rate
121 .try_sub(optimal_utilization_rate)?
122 .try_div(Rate::from_percent(
123 100u8
124 .checked_sub(self.config.optimal_utilization_rate)
125 .ok_or(LendingError::MathOverflow)?,
126 ))?;
127 let min_rate = Rate::from_percent(self.config.optimal_borrow_rate);
128 let rate_range = Rate::from_percent(
129 self.config
130 .max_borrow_rate
131 .checked_sub(self.config.optimal_borrow_rate)
132 .ok_or(LendingError::MathOverflow)?,
133 );
134
135 Ok(normalized_rate.try_mul(rate_range)?.try_add(min_rate)?)
136 }
137 }
138
139 pub fn collateral_exchange_rate(&self) -> Result<CollateralExchangeRate, ProgramError> {
141 let total_liquidity = self.liquidity.total_supply()?;
142 self.collateral.exchange_rate(total_liquidity)
143 }
144
145 pub fn accrue_interest(&mut self, current_slot: Slot) -> ProgramResult {
147 let slots_elapsed = self.last_update.slots_elapsed(current_slot)?;
148 if slots_elapsed > 0 {
149 let current_borrow_rate = self.current_borrow_rate()?;
150 let take_rate = Rate::from_percent(self.config.protocol_take_rate);
151 self.liquidity
152 .compound_interest(current_borrow_rate, slots_elapsed, take_rate)?;
153 }
154 Ok(())
155 }
156
157 pub fn calculate_borrow(
159 &self,
160 amount_to_borrow: u64,
161 max_borrow_value: Decimal,
162 remaining_reserve_borrow: Decimal,
163 ) -> Result<CalculateBorrowResult, ProgramError> {
164 let decimals = 10u64
166 .checked_pow(self.liquidity.mint_decimals as u32)
167 .ok_or(LendingError::MathOverflow)?;
168 if amount_to_borrow == u64::MAX {
169 let borrow_amount = max_borrow_value
170 .try_mul(decimals)?
171 .try_div(self.liquidity.market_price)?
172 .min(remaining_reserve_borrow)
173 .min(self.liquidity.available_amount.into());
174 let (borrow_fee, host_fee) = self
175 .config
176 .fees
177 .calculate_borrow_fees(borrow_amount, FeeCalculation::Inclusive)?;
178 let receive_amount = borrow_amount
179 .try_floor_u64()?
180 .checked_sub(borrow_fee)
181 .ok_or(LendingError::MathOverflow)?;
182
183 Ok(CalculateBorrowResult {
184 borrow_amount,
185 receive_amount,
186 borrow_fee,
187 host_fee,
188 })
189 } else {
190 let receive_amount = amount_to_borrow;
191 let borrow_amount = Decimal::from(receive_amount);
192 let (borrow_fee, host_fee) = self
193 .config
194 .fees
195 .calculate_borrow_fees(borrow_amount, FeeCalculation::Exclusive)?;
196
197 let borrow_amount = borrow_amount.try_add(borrow_fee.into())?;
198 let borrow_value = borrow_amount
199 .try_mul(self.liquidity.market_price)?
200 .try_div(decimals)?;
201 if borrow_value > max_borrow_value {
202 msg!("Borrow value cannot exceed maximum borrow value");
203 return Err(LendingError::BorrowTooLarge.into());
204 }
205
206 Ok(CalculateBorrowResult {
207 borrow_amount,
208 receive_amount,
209 borrow_fee,
210 host_fee,
211 })
212 }
213 }
214
215 pub fn calculate_repay(
217 &self,
218 amount_to_repay: u64,
219 borrowed_amount: Decimal,
220 ) -> Result<CalculateRepayResult, ProgramError> {
221 let settle_amount = if amount_to_repay == u64::MAX {
222 borrowed_amount
223 } else {
224 Decimal::from(amount_to_repay).min(borrowed_amount)
225 };
226 let repay_amount = settle_amount.try_ceil_u64()?;
227
228 Ok(CalculateRepayResult {
229 settle_amount,
230 repay_amount,
231 })
232 }
233
234 pub fn calculate_liquidation(
236 &self,
237 amount_to_liquidate: u64,
238 obligation: &Obligation,
239 liquidity: &ObligationLiquidity,
240 collateral: &ObligationCollateral,
241 ) -> Result<CalculateLiquidationResult, ProgramError> {
242 let bonus_rate = Rate::from_percent(self.config.liquidation_bonus).try_add(Rate::one())?;
243
244 let max_amount = if amount_to_liquidate == u64::MAX {
245 liquidity.borrowed_amount_wads
246 } else {
247 Decimal::from(amount_to_liquidate).min(liquidity.borrowed_amount_wads)
248 };
249
250 let settle_amount;
251 let repay_amount;
252 let withdraw_amount;
253
254 if liquidity.borrowed_amount_wads < LIQUIDATION_CLOSE_AMOUNT.into() {
256 settle_amount = liquidity.borrowed_amount_wads;
258
259 let liquidation_value = liquidity.market_value.try_mul(bonus_rate)?;
260 match liquidation_value.cmp(&collateral.market_value) {
261 Ordering::Greater => {
262 let repay_pct = collateral.market_value.try_div(liquidation_value)?;
263 repay_amount = max_amount.try_mul(repay_pct)?.try_ceil_u64()?;
264 withdraw_amount = collateral.deposited_amount;
265 }
266 Ordering::Equal => {
267 repay_amount = max_amount.try_ceil_u64()?;
268 withdraw_amount = collateral.deposited_amount;
269 }
270 Ordering::Less => {
271 let withdraw_pct = liquidation_value.try_div(collateral.market_value)?;
272 repay_amount = max_amount.try_ceil_u64()?;
273 withdraw_amount = Decimal::from(collateral.deposited_amount)
274 .try_mul(withdraw_pct)?
275 .try_floor_u64()?;
276 }
277 }
278 } else {
279 let liquidation_amount = obligation
281 .max_liquidation_amount(liquidity)?
282 .min(max_amount);
283 let liquidation_pct = liquidation_amount.try_div(liquidity.borrowed_amount_wads)?;
284 let liquidation_value = liquidity
285 .market_value
286 .try_mul(liquidation_pct)?
287 .try_mul(bonus_rate)?;
288
289 match liquidation_value.cmp(&collateral.market_value) {
290 Ordering::Greater => {
291 let repay_pct = collateral.market_value.try_div(liquidation_value)?;
292 settle_amount = liquidation_amount.try_mul(repay_pct)?;
293 repay_amount = settle_amount.try_ceil_u64()?;
294 withdraw_amount = collateral.deposited_amount;
295 }
296 Ordering::Equal => {
297 settle_amount = liquidation_amount;
298 repay_amount = settle_amount.try_ceil_u64()?;
299 withdraw_amount = collateral.deposited_amount;
300 }
301 Ordering::Less => {
302 let withdraw_pct = liquidation_value.try_div(collateral.market_value)?;
303 settle_amount = liquidation_amount;
304 repay_amount = settle_amount.try_ceil_u64()?;
305 withdraw_amount = Decimal::from(collateral.deposited_amount)
306 .try_mul(withdraw_pct)?
307 .try_floor_u64()?;
308 }
309 }
310 }
311
312 Ok(CalculateLiquidationResult {
313 settle_amount,
314 repay_amount,
315 withdraw_amount,
316 })
317 }
318
319 pub fn calculate_protocol_liquidation_fee(
321 &self,
322 amount_liquidated: u64,
323 ) -> Result<u64, ProgramError> {
324 let bonus_rate = Rate::from_percent(self.config.liquidation_bonus).try_add(Rate::one())?;
325 let amount_liquidated_wads = Decimal::from(amount_liquidated);
326
327 let bonus = amount_liquidated_wads.try_sub(amount_liquidated_wads.try_div(bonus_rate)?)?;
328
329 let protocol_fee = std::cmp::max(
331 bonus
332 .try_mul(Rate::from_percent(self.config.protocol_liquidation_fee))?
333 .try_ceil_u64()?,
334 1,
335 );
336 Ok(protocol_fee)
337 }
338
339 pub fn calculate_redeem_fees(&self) -> Result<u64, ProgramError> {
341 Ok(min(
342 self.liquidity.available_amount,
343 self.liquidity
344 .accumulated_protocol_fees_wads
345 .try_floor_u64()?,
346 ))
347 }
348}
349
350pub struct InitReserveParams {
352 pub current_slot: Slot,
354 pub lending_market: Pubkey,
356 pub liquidity: ReserveLiquidity,
358 pub collateral: ReserveCollateral,
360 pub config: ReserveConfig,
362}
363
364#[derive(Debug)]
366pub struct CalculateBorrowResult {
367 pub borrow_amount: Decimal,
369 pub receive_amount: u64,
371 pub borrow_fee: u64,
373 pub host_fee: u64,
375}
376
377#[derive(Debug)]
379pub struct CalculateRepayResult {
380 pub settle_amount: Decimal,
382 pub repay_amount: u64,
384}
385
386#[derive(Debug, Clone, PartialEq, Eq)]
388pub struct CalculateLiquidationResult {
389 pub settle_amount: Decimal,
392 pub repay_amount: u64,
394 pub withdraw_amount: u64,
396}
397
398#[derive(Clone, Debug, Default, PartialEq, Eq)]
400pub struct ReserveLiquidity {
401 pub mint_pubkey: Pubkey,
403 pub mint_decimals: u8,
405 pub supply_pubkey: Pubkey,
407 pub pyth_oracle_pubkey: Pubkey,
409 pub switchboard_oracle_pubkey: Pubkey,
411 pub available_amount: u64,
413 pub borrowed_amount_wads: Decimal,
415 pub cumulative_borrow_rate_wads: Decimal,
417 pub accumulated_protocol_fees_wads: Decimal,
419 pub market_price: Decimal,
421}
422
423impl ReserveLiquidity {
424 pub fn new(params: NewReserveLiquidityParams) -> Self {
426 Self {
427 mint_pubkey: params.mint_pubkey,
428 mint_decimals: params.mint_decimals,
429 supply_pubkey: params.supply_pubkey,
430 pyth_oracle_pubkey: params.pyth_oracle_pubkey,
431 switchboard_oracle_pubkey: params.switchboard_oracle_pubkey,
432 available_amount: 0,
433 borrowed_amount_wads: Decimal::zero(),
434 cumulative_borrow_rate_wads: Decimal::one(),
435 accumulated_protocol_fees_wads: Decimal::zero(),
436 market_price: params.market_price,
437 }
438 }
439
440 pub fn total_supply(&self) -> Result<Decimal, ProgramError> {
442 Decimal::from(self.available_amount)
443 .try_add(self.borrowed_amount_wads)?
444 .try_sub(self.accumulated_protocol_fees_wads)
445 }
446
447 pub fn deposit(&mut self, liquidity_amount: u64) -> ProgramResult {
449 self.available_amount = self
450 .available_amount
451 .checked_add(liquidity_amount)
452 .ok_or(LendingError::MathOverflow)?;
453 Ok(())
454 }
455
456 pub fn withdraw(&mut self, liquidity_amount: u64) -> ProgramResult {
458 if liquidity_amount > self.available_amount {
459 msg!("Withdraw amount cannot exceed available amount");
460 return Err(LendingError::InsufficientLiquidity.into());
461 }
462 self.available_amount = self
463 .available_amount
464 .checked_sub(liquidity_amount)
465 .ok_or(LendingError::MathOverflow)?;
466 Ok(())
467 }
468
469 pub fn borrow(&mut self, borrow_decimal: Decimal) -> ProgramResult {
471 let borrow_amount = borrow_decimal.try_floor_u64()?;
472 if borrow_amount > self.available_amount {
473 msg!("Borrow amount cannot exceed available amount");
474 return Err(LendingError::InsufficientLiquidity.into());
475 }
476
477 self.available_amount = self
478 .available_amount
479 .checked_sub(borrow_amount)
480 .ok_or(LendingError::MathOverflow)?;
481 self.borrowed_amount_wads = self.borrowed_amount_wads.try_add(borrow_decimal)?;
482
483 Ok(())
484 }
485
486 pub fn repay(&mut self, repay_amount: u64, settle_amount: Decimal) -> ProgramResult {
488 self.available_amount = self
489 .available_amount
490 .checked_add(repay_amount)
491 .ok_or(LendingError::MathOverflow)?;
492 let safe_settle_amount = settle_amount.min(self.borrowed_amount_wads);
493 self.borrowed_amount_wads = self.borrowed_amount_wads.try_sub(safe_settle_amount)?;
494
495 Ok(())
496 }
497
498 pub fn redeem_fees(&mut self, withdraw_amount: u64) -> ProgramResult {
500 self.available_amount = self
501 .available_amount
502 .checked_sub(withdraw_amount)
503 .ok_or(LendingError::MathOverflow)?;
504 self.accumulated_protocol_fees_wads = self
505 .accumulated_protocol_fees_wads
506 .try_sub(Decimal::from(withdraw_amount))?;
507
508 Ok(())
509 }
510
511 pub fn utilization_rate(&self) -> Result<Rate, ProgramError> {
513 let total_supply = self.total_supply()?;
514 if total_supply == Decimal::zero() {
515 return Ok(Rate::zero());
516 }
517 self.borrowed_amount_wads.try_div(total_supply)?.try_into()
518 }
519
520 fn compound_interest(
522 &mut self,
523 current_borrow_rate: Rate,
524 slots_elapsed: u64,
525 take_rate: Rate,
526 ) -> ProgramResult {
527 let slot_interest_rate = current_borrow_rate.try_div(SLOTS_PER_YEAR)?;
528 let compounded_interest_rate = Rate::one()
529 .try_add(slot_interest_rate)?
530 .try_pow(slots_elapsed)?;
531 self.cumulative_borrow_rate_wads = self
532 .cumulative_borrow_rate_wads
533 .try_mul(compounded_interest_rate)?;
534
535 let net_new_debt = self
536 .borrowed_amount_wads
537 .try_mul(compounded_interest_rate)?
538 .try_sub(self.borrowed_amount_wads)?;
539
540 self.accumulated_protocol_fees_wads = net_new_debt
541 .try_mul(take_rate)?
542 .try_add(self.accumulated_protocol_fees_wads)?;
543
544 self.borrowed_amount_wads = self.borrowed_amount_wads.try_add(net_new_debt)?;
545 Ok(())
546 }
547}
548
549pub struct NewReserveLiquidityParams {
551 pub mint_pubkey: Pubkey,
553 pub mint_decimals: u8,
555 pub supply_pubkey: Pubkey,
557 pub pyth_oracle_pubkey: Pubkey,
559 pub switchboard_oracle_pubkey: Pubkey,
561 pub market_price: Decimal,
563}
564
565#[derive(Clone, Debug, Default, PartialEq, Eq)]
567pub struct ReserveCollateral {
568 pub mint_pubkey: Pubkey,
570 pub mint_total_supply: u64,
572 pub supply_pubkey: Pubkey,
574}
575
576impl ReserveCollateral {
577 pub fn new(params: NewReserveCollateralParams) -> Self {
579 Self {
580 mint_pubkey: params.mint_pubkey,
581 mint_total_supply: 0,
582 supply_pubkey: params.supply_pubkey,
583 }
584 }
585
586 pub fn mint(&mut self, collateral_amount: u64) -> ProgramResult {
588 self.mint_total_supply = self
589 .mint_total_supply
590 .checked_add(collateral_amount)
591 .ok_or(LendingError::MathOverflow)?;
592 Ok(())
593 }
594
595 pub fn burn(&mut self, collateral_amount: u64) -> ProgramResult {
597 self.mint_total_supply = self
598 .mint_total_supply
599 .checked_sub(collateral_amount)
600 .ok_or(LendingError::MathOverflow)?;
601 Ok(())
602 }
603
604 fn exchange_rate(
606 &self,
607 total_liquidity: Decimal,
608 ) -> Result<CollateralExchangeRate, ProgramError> {
609 let rate = if self.mint_total_supply == 0 || total_liquidity == Decimal::zero() {
610 Rate::from_scaled_val(INITIAL_COLLATERAL_RATE)
611 } else {
612 let mint_total_supply = Decimal::from(self.mint_total_supply);
613 Rate::try_from(mint_total_supply.try_div(total_liquidity)?)?
614 };
615
616 Ok(CollateralExchangeRate(rate))
617 }
618}
619
620pub struct NewReserveCollateralParams {
622 pub mint_pubkey: Pubkey,
624 pub supply_pubkey: Pubkey,
626}
627
628#[derive(Clone, Copy, Debug)]
630pub struct CollateralExchangeRate(Rate);
631
632impl CollateralExchangeRate {
633 pub fn collateral_to_liquidity(&self, collateral_amount: u64) -> Result<u64, ProgramError> {
635 self.decimal_collateral_to_liquidity(collateral_amount.into())?
636 .try_floor_u64()
637 }
638
639 pub fn decimal_collateral_to_liquidity(
641 &self,
642 collateral_amount: Decimal,
643 ) -> Result<Decimal, ProgramError> {
644 collateral_amount.try_div(self.0)
645 }
646
647 pub fn liquidity_to_collateral(&self, liquidity_amount: u64) -> Result<u64, ProgramError> {
649 self.decimal_liquidity_to_collateral(liquidity_amount.into())?
650 .try_floor_u64()
651 }
652
653 pub fn decimal_liquidity_to_collateral(
655 &self,
656 liquidity_amount: Decimal,
657 ) -> Result<Decimal, ProgramError> {
658 liquidity_amount.try_mul(self.0)
659 }
660}
661
662impl From<CollateralExchangeRate> for Rate {
663 fn from(exchange_rate: CollateralExchangeRate) -> Self {
664 exchange_rate.0
665 }
666}
667
668#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
670pub struct ReserveConfig {
671 pub optimal_utilization_rate: u8,
673 pub loan_to_value_ratio: u8,
676 pub liquidation_bonus: u8,
678 pub liquidation_threshold: u8,
680 pub min_borrow_rate: u8,
682 pub optimal_borrow_rate: u8,
684 pub max_borrow_rate: u8,
686 pub fees: ReserveFees,
688 pub deposit_limit: u64,
690 pub borrow_limit: u64,
692 pub fee_receiver: Pubkey,
694 pub protocol_liquidation_fee: u8,
696 pub protocol_take_rate: u8,
698}
699
700#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
706pub struct ReserveFees {
707 pub borrow_fee_wad: u64,
714 pub flash_loan_fee_wad: u64,
717 pub host_fee_percentage: u8,
719}
720
721impl ReserveFees {
722 pub fn calculate_borrow_fees(
724 &self,
725 borrow_amount: Decimal,
726 fee_calculation: FeeCalculation,
727 ) -> Result<(u64, u64), ProgramError> {
728 self.calculate_fees(borrow_amount, self.borrow_fee_wad, fee_calculation)
729 }
730
731 pub fn calculate_flash_loan_fees(
733 &self,
734 flash_loan_amount: Decimal,
735 ) -> Result<(u64, u64), ProgramError> {
736 let (total_fees, host_fee) = self.calculate_fees(
737 flash_loan_amount,
738 self.flash_loan_fee_wad,
739 FeeCalculation::Exclusive,
740 )?;
741
742 let origination_fee = total_fees
743 .checked_sub(host_fee)
744 .ok_or(LendingError::MathOverflow)?;
745 Ok((origination_fee, host_fee))
746 }
747
748 fn calculate_fees(
749 &self,
750 amount: Decimal,
751 fee_wad: u64,
752 fee_calculation: FeeCalculation,
753 ) -> Result<(u64, u64), ProgramError> {
754 let borrow_fee_rate = Rate::from_scaled_val(fee_wad);
755 let host_fee_rate = Rate::from_percent(self.host_fee_percentage);
756 if borrow_fee_rate > Rate::zero() && amount > Decimal::zero() {
757 let need_to_assess_host_fee = host_fee_rate > Rate::zero();
758 let minimum_fee = if need_to_assess_host_fee {
759 2u64 } else {
761 1u64 };
763
764 let borrow_fee_amount = match fee_calculation {
765 FeeCalculation::Exclusive => amount.try_mul(borrow_fee_rate)?,
767 FeeCalculation::Inclusive => {
769 let borrow_fee_rate =
770 borrow_fee_rate.try_div(borrow_fee_rate.try_add(Rate::one())?)?;
771 amount.try_mul(borrow_fee_rate)?
772 }
773 };
774
775 let borrow_fee_decimal = borrow_fee_amount.max(minimum_fee.into());
776 if borrow_fee_decimal >= amount {
777 msg!("Borrow amount is too small to receive liquidity after fees");
778 return Err(LendingError::BorrowTooSmall.into());
779 }
780
781 let borrow_fee = borrow_fee_decimal.try_round_u64()?;
782 let host_fee = if need_to_assess_host_fee {
783 borrow_fee_decimal
784 .try_mul(host_fee_rate)?
785 .try_round_u64()?
786 .max(1u64)
787 } else {
788 0
789 };
790
791 Ok((borrow_fee, host_fee))
792 } else {
793 Ok((0, 0))
794 }
795 }
796}
797
798pub enum FeeCalculation {
800 Exclusive,
802 Inclusive,
804}
805
806impl Sealed for Reserve {}
807impl IsInitialized for Reserve {
808 fn is_initialized(&self) -> bool {
809 self.version != UNINITIALIZED_VERSION
810 }
811}
812
813const RESERVE_LEN: usize = 619; impl Pack for Reserve {
815 const LEN: usize = RESERVE_LEN;
816
817 fn pack_into_slice(&self, output: &mut [u8]) {
819 let output = array_mut_ref![output, 0, RESERVE_LEN];
820 #[allow(clippy::ptr_offset_with_cast)]
821 let (
822 version,
823 last_update_slot,
824 last_update_stale,
825 lending_market,
826 liquidity_mint_pubkey,
827 liquidity_mint_decimals,
828 liquidity_supply_pubkey,
829 liquidity_pyth_oracle_pubkey,
830 liquidity_switchboard_oracle_pubkey,
831 liquidity_available_amount,
832 liquidity_borrowed_amount_wads,
833 liquidity_cumulative_borrow_rate_wads,
834 liquidity_market_price,
835 collateral_mint_pubkey,
836 collateral_mint_total_supply,
837 collateral_supply_pubkey,
838 config_optimal_utilization_rate,
839 config_loan_to_value_ratio,
840 config_liquidation_bonus,
841 config_liquidation_threshold,
842 config_min_borrow_rate,
843 config_optimal_borrow_rate,
844 config_max_borrow_rate,
845 config_fees_borrow_fee_wad,
846 config_fees_flash_loan_fee_wad,
847 config_fees_host_fee_percentage,
848 config_deposit_limit,
849 config_borrow_limit,
850 config_fee_receiver,
851 config_protocol_liquidation_fee,
852 config_protocol_take_rate,
853 liquidity_accumulated_protocol_fees_wads,
854 _padding,
855 ) = mut_array_refs![
856 output,
857 1,
858 8,
859 1,
860 PUBKEY_BYTES,
861 PUBKEY_BYTES,
862 1,
863 PUBKEY_BYTES,
864 PUBKEY_BYTES,
865 PUBKEY_BYTES,
866 8,
867 16,
868 16,
869 16,
870 PUBKEY_BYTES,
871 8,
872 PUBKEY_BYTES,
873 1,
874 1,
875 1,
876 1,
877 1,
878 1,
879 1,
880 8,
881 8,
882 1,
883 8,
884 8,
885 PUBKEY_BYTES,
886 1,
887 1,
888 16,
889 230
890 ];
891
892 *version = self.version.to_le_bytes();
894 *last_update_slot = self.last_update.slot.to_le_bytes();
895 pack_bool(self.last_update.stale, last_update_stale);
896 lending_market.copy_from_slice(self.lending_market.as_ref());
897
898 liquidity_mint_pubkey.copy_from_slice(self.liquidity.mint_pubkey.as_ref());
900 *liquidity_mint_decimals = self.liquidity.mint_decimals.to_le_bytes();
901 liquidity_supply_pubkey.copy_from_slice(self.liquidity.supply_pubkey.as_ref());
902 liquidity_pyth_oracle_pubkey.copy_from_slice(self.liquidity.pyth_oracle_pubkey.as_ref());
903 liquidity_switchboard_oracle_pubkey
904 .copy_from_slice(self.liquidity.switchboard_oracle_pubkey.as_ref());
905 *liquidity_available_amount = self.liquidity.available_amount.to_le_bytes();
906 pack_decimal(
907 self.liquidity.borrowed_amount_wads,
908 liquidity_borrowed_amount_wads,
909 );
910 pack_decimal(
911 self.liquidity.cumulative_borrow_rate_wads,
912 liquidity_cumulative_borrow_rate_wads,
913 );
914 pack_decimal(
915 self.liquidity.accumulated_protocol_fees_wads,
916 liquidity_accumulated_protocol_fees_wads,
917 );
918 pack_decimal(self.liquidity.market_price, liquidity_market_price);
919
920 collateral_mint_pubkey.copy_from_slice(self.collateral.mint_pubkey.as_ref());
922 *collateral_mint_total_supply = self.collateral.mint_total_supply.to_le_bytes();
923 collateral_supply_pubkey.copy_from_slice(self.collateral.supply_pubkey.as_ref());
924
925 *config_optimal_utilization_rate = self.config.optimal_utilization_rate.to_le_bytes();
927 *config_loan_to_value_ratio = self.config.loan_to_value_ratio.to_le_bytes();
928 *config_liquidation_bonus = self.config.liquidation_bonus.to_le_bytes();
929 *config_liquidation_threshold = self.config.liquidation_threshold.to_le_bytes();
930 *config_min_borrow_rate = self.config.min_borrow_rate.to_le_bytes();
931 *config_optimal_borrow_rate = self.config.optimal_borrow_rate.to_le_bytes();
932 *config_max_borrow_rate = self.config.max_borrow_rate.to_le_bytes();
933 *config_fees_borrow_fee_wad = self.config.fees.borrow_fee_wad.to_le_bytes();
934 *config_fees_flash_loan_fee_wad = self.config.fees.flash_loan_fee_wad.to_le_bytes();
935 *config_fees_host_fee_percentage = self.config.fees.host_fee_percentage.to_le_bytes();
936 *config_deposit_limit = self.config.deposit_limit.to_le_bytes();
937 *config_borrow_limit = self.config.borrow_limit.to_le_bytes();
938 config_fee_receiver.copy_from_slice(self.config.fee_receiver.as_ref());
939 *config_protocol_liquidation_fee = self.config.protocol_liquidation_fee.to_le_bytes();
940 *config_protocol_take_rate = self.config.protocol_take_rate.to_le_bytes();
941 }
942
943 fn unpack_from_slice(input: &[u8]) -> Result<Self, ProgramError> {
945 let input = array_ref![input, 0, RESERVE_LEN];
946 #[allow(clippy::ptr_offset_with_cast)]
947 let (
948 version,
949 last_update_slot,
950 last_update_stale,
951 lending_market,
952 liquidity_mint_pubkey,
953 liquidity_mint_decimals,
954 liquidity_supply_pubkey,
955 liquidity_pyth_oracle_pubkey,
956 liquidity_switchboard_oracle_pubkey,
957 liquidity_available_amount,
958 liquidity_borrowed_amount_wads,
959 liquidity_cumulative_borrow_rate_wads,
960 liquidity_market_price,
961 collateral_mint_pubkey,
962 collateral_mint_total_supply,
963 collateral_supply_pubkey,
964 config_optimal_utilization_rate,
965 config_loan_to_value_ratio,
966 config_liquidation_bonus,
967 config_liquidation_threshold,
968 config_min_borrow_rate,
969 config_optimal_borrow_rate,
970 config_max_borrow_rate,
971 config_fees_borrow_fee_wad,
972 config_fees_flash_loan_fee_wad,
973 config_fees_host_fee_percentage,
974 config_deposit_limit,
975 config_borrow_limit,
976 config_fee_receiver,
977 config_protocol_liquidation_fee,
978 config_protocol_take_rate,
979 liquidity_accumulated_protocol_fees_wads,
980 _padding,
981 ) = array_refs![
982 input,
983 1,
984 8,
985 1,
986 PUBKEY_BYTES,
987 PUBKEY_BYTES,
988 1,
989 PUBKEY_BYTES,
990 PUBKEY_BYTES,
991 PUBKEY_BYTES,
992 8,
993 16,
994 16,
995 16,
996 PUBKEY_BYTES,
997 8,
998 PUBKEY_BYTES,
999 1,
1000 1,
1001 1,
1002 1,
1003 1,
1004 1,
1005 1,
1006 8,
1007 8,
1008 1,
1009 8,
1010 8,
1011 PUBKEY_BYTES,
1012 1,
1013 1,
1014 16,
1015 230
1016 ];
1017
1018 let version = u8::from_le_bytes(*version);
1019 if version > PROGRAM_VERSION {
1020 msg!("Reserve version does not match lending program version");
1021 return Err(ProgramError::InvalidAccountData);
1022 }
1023
1024 Ok(Self {
1025 version,
1026 last_update: LastUpdate {
1027 slot: u64::from_le_bytes(*last_update_slot),
1028 stale: unpack_bool(last_update_stale)?,
1029 },
1030 lending_market: Pubkey::new_from_array(*lending_market),
1031 liquidity: ReserveLiquidity {
1032 mint_pubkey: Pubkey::new_from_array(*liquidity_mint_pubkey),
1033 mint_decimals: u8::from_le_bytes(*liquidity_mint_decimals),
1034 supply_pubkey: Pubkey::new_from_array(*liquidity_supply_pubkey),
1035 pyth_oracle_pubkey: Pubkey::new_from_array(*liquidity_pyth_oracle_pubkey),
1036 switchboard_oracle_pubkey: Pubkey::new_from_array(
1037 *liquidity_switchboard_oracle_pubkey,
1038 ),
1039 available_amount: u64::from_le_bytes(*liquidity_available_amount),
1040 borrowed_amount_wads: unpack_decimal(liquidity_borrowed_amount_wads),
1041 cumulative_borrow_rate_wads: unpack_decimal(liquidity_cumulative_borrow_rate_wads),
1042 accumulated_protocol_fees_wads: unpack_decimal(
1043 liquidity_accumulated_protocol_fees_wads,
1044 ),
1045 market_price: unpack_decimal(liquidity_market_price),
1046 },
1047 collateral: ReserveCollateral {
1048 mint_pubkey: Pubkey::new_from_array(*collateral_mint_pubkey),
1049 mint_total_supply: u64::from_le_bytes(*collateral_mint_total_supply),
1050 supply_pubkey: Pubkey::new_from_array(*collateral_supply_pubkey),
1051 },
1052 config: ReserveConfig {
1053 optimal_utilization_rate: u8::from_le_bytes(*config_optimal_utilization_rate),
1054 loan_to_value_ratio: u8::from_le_bytes(*config_loan_to_value_ratio),
1055 liquidation_bonus: u8::from_le_bytes(*config_liquidation_bonus),
1056 liquidation_threshold: u8::from_le_bytes(*config_liquidation_threshold),
1057 min_borrow_rate: u8::from_le_bytes(*config_min_borrow_rate),
1058 optimal_borrow_rate: u8::from_le_bytes(*config_optimal_borrow_rate),
1059 max_borrow_rate: u8::from_le_bytes(*config_max_borrow_rate),
1060 fees: ReserveFees {
1061 borrow_fee_wad: u64::from_le_bytes(*config_fees_borrow_fee_wad),
1062 flash_loan_fee_wad: u64::from_le_bytes(*config_fees_flash_loan_fee_wad),
1063 host_fee_percentage: u8::from_le_bytes(*config_fees_host_fee_percentage),
1064 },
1065 deposit_limit: u64::from_le_bytes(*config_deposit_limit),
1066 borrow_limit: u64::from_le_bytes(*config_borrow_limit),
1067 fee_receiver: Pubkey::new_from_array(*config_fee_receiver),
1068 protocol_liquidation_fee: u8::from_le_bytes(*config_protocol_liquidation_fee),
1069 protocol_take_rate: u8::from_le_bytes(*config_protocol_take_rate),
1070 },
1071 })
1072 }
1073}
1074
1075#[cfg(test)]
1076mod test {
1077 use super::*;
1078 use crate::math::{PERCENT_SCALER, WAD};
1079 use proptest::prelude::*;
1080 use std::cmp::Ordering;
1081 use std::default::Default;
1082
1083 const MAX_LIQUIDITY: u64 = u64::MAX / 5;
1084
1085 prop_compose! {
1087 fn borrow_rates()(optimal_rate in 0..=u8::MAX)(
1088 min_rate in 0..=optimal_rate,
1089 optimal_rate in Just(optimal_rate),
1090 max_rate in optimal_rate..=u8::MAX,
1091 ) -> (u8, u8, u8) {
1092 (min_rate, optimal_rate, max_rate)
1093 }
1094 }
1095
1096 prop_compose! {
1098 fn unhealthy_rates()(threshold in 2..=100u8)(
1099 ltv_rate in threshold as u64..=1000u64,
1100 threshold in Just(threshold),
1101 ) -> (Decimal, u8) {
1102 (Decimal::from_scaled_val(ltv_rate as u128 * PERCENT_SCALER as u128), threshold)
1103 }
1104 }
1105
1106 prop_compose! {
1108 fn token_conversion_rate()(
1109 conversion_rate in 1..=u16::MAX,
1110 invert_conversion_rate: bool,
1111 ) -> Decimal {
1112 let conversion_rate = Decimal::from(conversion_rate as u64);
1113 if invert_conversion_rate {
1114 Decimal::one().try_div(conversion_rate).unwrap()
1115 } else {
1116 conversion_rate
1117 }
1118 }
1119 }
1120
1121 prop_compose! {
1123 fn collateral_exchange_rate_range()(percent in 1..=500u64) -> CollateralExchangeRate {
1124 CollateralExchangeRate(Rate::from_scaled_val(percent * PERCENT_SCALER))
1125 }
1126 }
1127
1128 proptest! {
1129 #[test]
1130 fn current_borrow_rate(
1131 total_liquidity in 0..=MAX_LIQUIDITY,
1132 borrowed_percent in 0..=WAD,
1133 optimal_utilization_rate in 0..=100u8,
1134 (min_borrow_rate, optimal_borrow_rate, max_borrow_rate) in borrow_rates(),
1135 ) {
1136 let borrowed_amount_wads = Decimal::from(total_liquidity).try_mul(Rate::from_scaled_val(borrowed_percent))?;
1137 let reserve = Reserve {
1138 liquidity: ReserveLiquidity {
1139 borrowed_amount_wads,
1140 available_amount: total_liquidity - borrowed_amount_wads.try_round_u64()?,
1141 ..ReserveLiquidity::default()
1142 },
1143 config: ReserveConfig { optimal_utilization_rate, min_borrow_rate, optimal_borrow_rate, max_borrow_rate, ..ReserveConfig::default() },
1144 ..Reserve::default()
1145 };
1146
1147 if !(optimal_borrow_rate > 246 && optimal_borrow_rate == max_borrow_rate) {
1148 let current_borrow_rate = reserve.current_borrow_rate()?;
1149 assert!(current_borrow_rate >= Rate::from_percent(min_borrow_rate));
1150 assert!(current_borrow_rate <= Rate::from_percent(max_borrow_rate));
1151
1152 let optimal_borrow_rate = Rate::from_percent(optimal_borrow_rate);
1153 let current_rate = reserve.liquidity.utilization_rate()?;
1154 match current_rate.cmp(&Rate::from_percent(optimal_utilization_rate)) {
1155 Ordering::Less => {
1156 if min_borrow_rate == reserve.config.optimal_borrow_rate {
1157 assert_eq!(current_borrow_rate, optimal_borrow_rate);
1158 } else {
1159 assert!(current_borrow_rate < optimal_borrow_rate);
1160 }
1161 }
1162 Ordering::Equal => assert!(current_borrow_rate == optimal_borrow_rate),
1163 Ordering::Greater => {
1164 if max_borrow_rate == reserve.config.optimal_borrow_rate {
1165 assert_eq!(current_borrow_rate, optimal_borrow_rate);
1166 } else {
1167 assert!(current_borrow_rate > optimal_borrow_rate);
1168 }
1169 }
1170 }
1171 }
1172 }
1173
1174 #[test]
1175 fn current_utilization_rate(
1176 total_liquidity in 0..=MAX_LIQUIDITY,
1177 borrowed_percent in 0..=WAD,
1178 ) {
1179 let borrowed_amount_wads = Decimal::from(total_liquidity).try_mul(Rate::from_scaled_val(borrowed_percent))?;
1180 let liquidity = ReserveLiquidity {
1181 borrowed_amount_wads,
1182 available_amount: total_liquidity - borrowed_amount_wads.try_round_u64()?,
1183 ..ReserveLiquidity::default()
1184 };
1185
1186 let current_rate = liquidity.utilization_rate()?;
1187 assert!(current_rate <= Rate::one());
1188 }
1189
1190 #[test]
1191 fn collateral_exchange_rate(
1192 total_liquidity in 0..=MAX_LIQUIDITY,
1193 borrowed_percent in 0..=WAD,
1194 collateral_multiplier in 0..=(5*WAD),
1195 borrow_rate in 0..=u8::MAX,
1196 ) {
1197 let borrowed_liquidity_wads = Decimal::from(total_liquidity).try_mul(Rate::from_scaled_val(borrowed_percent))?;
1198 let available_liquidity = total_liquidity - borrowed_liquidity_wads.try_round_u64()?;
1199 let mint_total_supply = Decimal::from(total_liquidity).try_mul(Rate::from_scaled_val(collateral_multiplier))?.try_round_u64()?;
1200
1201 let mut reserve = Reserve {
1202 collateral: ReserveCollateral {
1203 mint_total_supply,
1204 ..ReserveCollateral::default()
1205 },
1206 liquidity: ReserveLiquidity {
1207 borrowed_amount_wads: borrowed_liquidity_wads,
1208 available_amount: available_liquidity,
1209 ..ReserveLiquidity::default()
1210 },
1211 config: ReserveConfig {
1212 min_borrow_rate: borrow_rate,
1213 optimal_borrow_rate: borrow_rate,
1214 optimal_utilization_rate: 100,
1215 ..ReserveConfig::default()
1216 },
1217 ..Reserve::default()
1218 };
1219
1220 let exchange_rate = reserve.collateral_exchange_rate()?;
1221 assert!(exchange_rate.0.to_scaled_val() <= 5u128 * WAD as u128);
1222
1223 reserve.accrue_interest(1)?;
1225
1226 let new_exchange_rate = reserve.collateral_exchange_rate()?;
1227 if borrow_rate > 0 && total_liquidity > 0 && borrowed_percent > 0 {
1228 assert!(new_exchange_rate.0 < exchange_rate.0);
1229 } else {
1230 assert_eq!(new_exchange_rate.0, exchange_rate.0);
1231 }
1232 }
1233
1234 #[test]
1235 fn compound_interest(
1236 slots_elapsed in 0..=SLOTS_PER_YEAR,
1237 borrow_rate in 0..=u8::MAX,
1238 take_rate in 0..=100u8,
1239 ) {
1240 let mut reserve = Reserve::default();
1241 let borrow_rate = Rate::from_percent(borrow_rate);
1242 let take_rate = Rate::from_percent(take_rate);
1243
1244 for _ in 0..1000 {
1247 reserve.liquidity.compound_interest(borrow_rate, slots_elapsed, take_rate)?;
1248 reserve.liquidity.cumulative_borrow_rate_wads.to_scaled_val()?;
1249 reserve.liquidity.accumulated_protocol_fees_wads.to_scaled_val()?;
1250 }
1251 }
1252
1253 #[test]
1254 fn reserve_accrue_interest(
1255 slots_elapsed in 0..=SLOTS_PER_YEAR,
1256 borrowed_liquidity in 0..=u64::MAX,
1257 borrow_rate in 0..=u8::MAX,
1258 ) {
1259 let borrowed_amount_wads = Decimal::from(borrowed_liquidity);
1260 let mut reserve = Reserve {
1261 liquidity: ReserveLiquidity {
1262 borrowed_amount_wads,
1263 ..ReserveLiquidity::default()
1264 },
1265 config: ReserveConfig {
1266 max_borrow_rate: borrow_rate,
1267 ..ReserveConfig::default()
1268 },
1269 ..Reserve::default()
1270 };
1271
1272 reserve.accrue_interest(slots_elapsed)?;
1273
1274 if borrow_rate > 0 && slots_elapsed > 0 {
1275 assert!(reserve.liquidity.borrowed_amount_wads > borrowed_amount_wads);
1276 } else {
1277 assert!(reserve.liquidity.borrowed_amount_wads == borrowed_amount_wads);
1278 }
1279 }
1280
1281 #[test]
1282 fn borrow_fee_calculation(
1283 borrow_fee_wad in 0..WAD, flash_loan_fee_wad in 0..WAD, host_fee_percentage in 0..=100u8,
1286 borrow_amount in 3..=u64::MAX, ) {
1290 let fees = ReserveFees {
1291 borrow_fee_wad,
1292 flash_loan_fee_wad,
1293 host_fee_percentage,
1294 };
1295 let (total_fee, host_fee) = fees.calculate_borrow_fees(Decimal::from(borrow_amount), FeeCalculation::Exclusive)?;
1296
1297 assert!(total_fee <= borrow_amount);
1302
1303 assert!(host_fee <= total_fee);
1305
1306 if borrow_fee_wad > 0 {
1308 assert!(total_fee > 0);
1309 }
1310
1311 if host_fee_percentage == 100 {
1312 assert_eq!(host_fee, total_fee);
1314 }
1315
1316 if host_fee_percentage > 0 && borrow_fee_wad > 0 {
1318 assert!(host_fee > 0);
1319 } else {
1320 assert_eq!(host_fee, 0);
1321 }
1322 }
1323
1324 #[test]
1325 fn flash_loan_fee_calculation(
1326 borrow_fee_wad in 0..WAD, flash_loan_fee_wad in 0..WAD, host_fee_percentage in 0..=100u8,
1329 borrow_amount in 3..=u64::MAX, ) {
1333 let fees = ReserveFees {
1334 borrow_fee_wad,
1335 flash_loan_fee_wad,
1336 host_fee_percentage,
1337 };
1338 let (origination_fee, host_fee) = fees.calculate_flash_loan_fees(Decimal::from(borrow_amount))?;
1339
1340 assert!(origination_fee + host_fee <= borrow_amount);
1345
1346 if borrow_fee_wad > 0 {
1348 assert!(origination_fee + host_fee > 0);
1349 }
1350
1351 if host_fee_percentage == 100 {
1352 assert_eq!(origination_fee, 0);
1354 }
1355
1356 if host_fee_percentage > 0 && borrow_fee_wad > 0 {
1358 assert!(host_fee > 0);
1359 } else {
1360 assert_eq!(host_fee, 0);
1361 }
1362 }
1363 }
1364
1365 #[test]
1366 fn borrow_fee_calculation_min_host() {
1367 let fees = ReserveFees {
1368 borrow_fee_wad: 10_000_000_000_000_000, flash_loan_fee_wad: 0,
1370 host_fee_percentage: 20,
1371 };
1372
1373 let err = fees
1375 .calculate_borrow_fees(Decimal::from(2u64), FeeCalculation::Exclusive)
1376 .unwrap_err();
1377 assert_eq!(err, LendingError::BorrowTooSmall.into()); let err = fees
1381 .calculate_borrow_fees(Decimal::one(), FeeCalculation::Exclusive)
1382 .unwrap_err();
1383 assert_eq!(err, LendingError::BorrowTooSmall.into());
1384
1385 let (total_fee, host_fee) = fees
1387 .calculate_borrow_fees(Decimal::zero(), FeeCalculation::Exclusive)
1388 .unwrap();
1389 assert_eq!(total_fee, 0);
1390 assert_eq!(host_fee, 0);
1391 }
1392
1393 #[test]
1394 fn borrow_fee_calculation_min_no_host() {
1395 let fees = ReserveFees {
1396 borrow_fee_wad: 10_000_000_000_000_000, flash_loan_fee_wad: 0,
1398 host_fee_percentage: 0,
1399 };
1400
1401 let (total_fee, host_fee) = fees
1403 .calculate_borrow_fees(Decimal::from(2u64), FeeCalculation::Exclusive)
1404 .unwrap();
1405 assert_eq!(total_fee, 1);
1406 assert_eq!(host_fee, 0);
1407
1408 let err = fees
1410 .calculate_borrow_fees(Decimal::one(), FeeCalculation::Exclusive)
1411 .unwrap_err();
1412 assert_eq!(err, LendingError::BorrowTooSmall.into()); let (total_fee, host_fee) = fees
1416 .calculate_borrow_fees(Decimal::zero(), FeeCalculation::Exclusive)
1417 .unwrap();
1418 assert_eq!(total_fee, 0);
1419 assert_eq!(host_fee, 0);
1420 }
1421
1422 #[test]
1423 fn borrow_fee_calculation_host() {
1424 let fees = ReserveFees {
1425 borrow_fee_wad: 10_000_000_000_000_000, flash_loan_fee_wad: 0,
1427 host_fee_percentage: 20,
1428 };
1429
1430 let (total_fee, host_fee) = fees
1431 .calculate_borrow_fees(Decimal::from(1000u64), FeeCalculation::Exclusive)
1432 .unwrap();
1433
1434 assert_eq!(total_fee, 10); assert_eq!(host_fee, 2); }
1437
1438 #[test]
1439 fn borrow_fee_calculation_no_host() {
1440 let fees = ReserveFees {
1441 borrow_fee_wad: 10_000_000_000_000_000, flash_loan_fee_wad: 0,
1443 host_fee_percentage: 0,
1444 };
1445
1446 let (total_fee, host_fee) = fees
1447 .calculate_borrow_fees(Decimal::from(1000u64), FeeCalculation::Exclusive)
1448 .unwrap();
1449
1450 assert_eq!(total_fee, 10); assert_eq!(host_fee, 0); }
1453
1454 #[derive(Debug, Clone)]
1455 struct LiquidationTestCase {
1456 deposit_amount: u64,
1457 deposit_market_value: u64,
1458 borrow_amount: u64,
1459 borrow_market_value: u64,
1460 liquidation_result: CalculateLiquidationResult,
1461 }
1462
1463 fn calculate_liquidation_test_cases() -> impl Strategy<Value = LiquidationTestCase> {
1464 let close_factor: Decimal = Rate::from_percent(LIQUIDATION_CLOSE_FACTOR)
1465 .try_into()
1466 .unwrap();
1467 let liquidation_bonus: Decimal = Rate::from_percent(5)
1468 .try_add(Rate::one())
1469 .unwrap()
1470 .try_into()
1471 .unwrap();
1472
1473 prop_oneof![
1474 Just(LiquidationTestCase {
1476 deposit_amount: 1000,
1477 deposit_market_value: 100,
1478 borrow_amount: 800,
1479 borrow_market_value: 80,
1480 liquidation_result: CalculateLiquidationResult {
1481 settle_amount: close_factor.try_mul(Decimal::from(800u64)).unwrap(),
1482 repay_amount: close_factor
1483 .try_mul(Decimal::from(800u64))
1484 .unwrap()
1485 .try_ceil_u64()
1486 .unwrap(),
1487 withdraw_amount: close_factor
1488 .try_mul(liquidation_bonus)
1489 .unwrap()
1490 .try_mul(Decimal::from(800u64))
1491 .unwrap()
1492 .try_floor_u64()
1493 .unwrap(),
1494 },
1495 }),
1496 Just(LiquidationTestCase {
1498 borrow_amount: 8000,
1499 borrow_market_value: 8000,
1500 deposit_amount: (8000 * LIQUIDATION_CLOSE_FACTOR as u64) * 105 / 10000,
1501 deposit_market_value: (8000 * LIQUIDATION_CLOSE_FACTOR as u64) * 105 / 10000,
1502
1503 liquidation_result: CalculateLiquidationResult {
1504 settle_amount: Decimal::from((8000 * LIQUIDATION_CLOSE_FACTOR as u64) / 100),
1505 repay_amount: (8000 * LIQUIDATION_CLOSE_FACTOR as u64) / 100,
1506 withdraw_amount: (8000 * LIQUIDATION_CLOSE_FACTOR as u64) * 105 / 10000,
1507 },
1508 }),
1509 Just(LiquidationTestCase {
1511 borrow_amount: 8000,
1512 borrow_market_value: 8000,
1513
1514 deposit_amount: (8000 * LIQUIDATION_CLOSE_FACTOR as u64) * 105 / 10000 / 2,
1516 deposit_market_value: (8000 * LIQUIDATION_CLOSE_FACTOR as u64) * 105 / 10000 / 2,
1517
1518 liquidation_result: CalculateLiquidationResult {
1519 settle_amount: Decimal::from(
1520 (8000 * LIQUIDATION_CLOSE_FACTOR as u64) / 100 / 2
1521 ),
1522 repay_amount: (8000 * LIQUIDATION_CLOSE_FACTOR as u64) / 100 / 2,
1523 withdraw_amount: (8000 * LIQUIDATION_CLOSE_FACTOR as u64) * 105 / 10000 / 2,
1524 },
1525 }),
1526 Just(LiquidationTestCase {
1528 borrow_amount: 1,
1529 borrow_market_value: 1000,
1530 deposit_amount: 1000,
1531 deposit_market_value: 2100,
1532
1533 liquidation_result: CalculateLiquidationResult {
1534 settle_amount: Decimal::from(1u64),
1535 repay_amount: 1,
1536 withdraw_amount: 500,
1537 },
1538 }),
1539 Just(LiquidationTestCase {
1541 borrow_amount: 1,
1542 borrow_market_value: 1000,
1543 deposit_amount: 1000,
1544 deposit_market_value: 1050,
1545
1546 liquidation_result: CalculateLiquidationResult {
1547 settle_amount: Decimal::from(1u64),
1548 repay_amount: 1,
1549 withdraw_amount: 1000,
1550 },
1551 }),
1552 Just(LiquidationTestCase {
1554 borrow_amount: 1,
1555 borrow_market_value: 1000,
1556 deposit_amount: 1000,
1557 deposit_market_value: 1000,
1558
1559 liquidation_result: CalculateLiquidationResult {
1560 settle_amount: Decimal::from(1u64),
1561 repay_amount: 1,
1562 withdraw_amount: 1000,
1563 },
1564 }),
1565 ]
1566 }
1567
1568 proptest! {
1569 #[test]
1570 fn calculate_liquidation(test_case in calculate_liquidation_test_cases()) {
1571 let reserve = Reserve {
1572 config: ReserveConfig {
1573 liquidation_bonus: 5,
1574 ..ReserveConfig::default()
1575 },
1576 ..Reserve::default()
1577 };
1578
1579 let obligation = Obligation {
1580 deposits: vec![ObligationCollateral {
1581 deposit_reserve: Pubkey::new_unique(),
1582 deposited_amount: test_case.deposit_amount,
1583 market_value: Decimal::from(test_case.deposit_market_value),
1584 }],
1585 borrows: vec![ObligationLiquidity {
1586 borrow_reserve: Pubkey::new_unique(),
1587 cumulative_borrow_rate_wads: Decimal::one(),
1588 borrowed_amount_wads: Decimal::from(test_case.borrow_amount),
1589 market_value: Decimal::from(test_case.borrow_market_value),
1590 }],
1591 borrowed_value: Decimal::from(test_case.borrow_market_value),
1592 ..Obligation::default()
1593 };
1594
1595 assert_eq!(
1596 reserve.calculate_liquidation(
1597 u64::MAX, &obligation, &obligation.borrows[0], &obligation.deposits[0]).unwrap(),
1598 test_case.liquidation_result);
1599 }
1600 }
1601}