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 MAX_OBLIGATION_RESERVES: usize = 10;
22
23#[derive(Clone, Debug, Default, PartialEq)]
25pub struct Obligation {
26 pub version: u8,
28 pub last_update: LastUpdate,
30 pub lending_market: Pubkey,
32 pub owner: Pubkey,
34 pub deposits: Vec<ObligationCollateral>,
36 pub borrows: Vec<ObligationLiquidity>,
38 pub deposited_value: Decimal,
40 pub borrowed_value: Decimal,
42 pub allowed_borrow_value: Decimal,
44 pub unhealthy_borrow_value: Decimal,
46}
47
48impl Obligation {
49 pub fn new(params: InitObligationParams) -> Self {
51 let mut obligation = Self::default();
52 Self::init(&mut obligation, params);
53 obligation
54 }
55
56 pub fn init(&mut self, params: InitObligationParams) {
58 self.version = PROGRAM_VERSION;
59 self.last_update = LastUpdate::new(params.current_slot);
60 self.lending_market = params.lending_market;
61 self.owner = params.owner;
62 self.deposits = params.deposits;
63 self.borrows = params.borrows;
64 }
65
66 pub fn loan_to_value(&self) -> Result<Decimal, ProgramError> {
68 self.borrowed_value.try_div(self.deposited_value)
69 }
70
71 pub fn repay(&mut self, settle_amount: Decimal, liquidity_index: usize) -> ProgramResult {
73 let liquidity = &mut self.borrows[liquidity_index];
74 if settle_amount == liquidity.borrowed_amount_wads {
75 self.borrows.remove(liquidity_index);
76 } else {
77 liquidity.repay(settle_amount)?;
78 }
79 Ok(())
80 }
81
82 pub fn withdraw(&mut self, withdraw_amount: u64, collateral_index: usize) -> ProgramResult {
84 let collateral = &mut self.deposits[collateral_index];
85 if withdraw_amount == collateral.deposited_amount {
86 self.deposits.remove(collateral_index);
87 } else {
88 collateral.withdraw(withdraw_amount)?;
89 }
90 Ok(())
91 }
92
93 pub fn max_withdraw_value(
95 &self,
96 withdraw_collateral_ltv: Rate,
97 ) -> Result<Decimal, ProgramError> {
98 if self.allowed_borrow_value <= self.borrowed_value {
99 return Ok(Decimal::zero());
100 }
101 if withdraw_collateral_ltv == Rate::zero() {
102 return Ok(self.deposited_value);
103 }
104 self.allowed_borrow_value
105 .try_sub(self.borrowed_value)?
106 .try_div(withdraw_collateral_ltv)
107 }
108
109 pub fn remaining_borrow_value(&self) -> Result<Decimal, ProgramError> {
111 self.allowed_borrow_value.try_sub(self.borrowed_value)
112 }
113
114 pub fn max_liquidation_amount(
116 &self,
117 liquidity: &ObligationLiquidity,
118 ) -> Result<Decimal, ProgramError> {
119 let max_liquidation_value = self
120 .borrowed_value
121 .try_mul(Rate::from_percent(LIQUIDATION_CLOSE_FACTOR))?
122 .min(liquidity.market_value)
123 .min(Decimal::from(MAX_LIQUIDATABLE_VALUE_AT_ONCE));
124
125 let max_liquidation_pct = max_liquidation_value.try_div(liquidity.market_value)?;
126 liquidity.borrowed_amount_wads.try_mul(max_liquidation_pct)
127 }
128
129 pub fn find_collateral_in_deposits(
131 &self,
132 deposit_reserve: Pubkey,
133 ) -> Result<(&ObligationCollateral, usize), ProgramError> {
134 if self.deposits.is_empty() {
135 msg!("Obligation has no deposits");
136 return Err(LendingError::ObligationDepositsEmpty.into());
137 }
138 let collateral_index = self
139 ._find_collateral_index_in_deposits(deposit_reserve)
140 .ok_or(LendingError::InvalidObligationCollateral)?;
141 Ok((&self.deposits[collateral_index], collateral_index))
142 }
143
144 pub fn find_or_add_collateral_to_deposits(
146 &mut self,
147 deposit_reserve: Pubkey,
148 ) -> Result<&mut ObligationCollateral, ProgramError> {
149 if let Some(collateral_index) = self._find_collateral_index_in_deposits(deposit_reserve) {
150 return Ok(&mut self.deposits[collateral_index]);
151 }
152 if self.deposits.len() + self.borrows.len() >= MAX_OBLIGATION_RESERVES {
153 msg!(
154 "Obligation cannot have more than {} deposits and borrows combined",
155 MAX_OBLIGATION_RESERVES
156 );
157 return Err(LendingError::ObligationReserveLimit.into());
158 }
159 let collateral = ObligationCollateral::new(deposit_reserve);
160 self.deposits.push(collateral);
161 Ok(self.deposits.last_mut().unwrap())
162 }
163
164 fn _find_collateral_index_in_deposits(&self, deposit_reserve: Pubkey) -> Option<usize> {
165 self.deposits
166 .iter()
167 .position(|collateral| collateral.deposit_reserve == deposit_reserve)
168 }
169
170 pub fn find_liquidity_in_borrows(
172 &self,
173 borrow_reserve: Pubkey,
174 ) -> Result<(&ObligationLiquidity, usize), ProgramError> {
175 if self.borrows.is_empty() {
176 msg!("Obligation has no borrows");
177 return Err(LendingError::ObligationBorrowsEmpty.into());
178 }
179 let liquidity_index = self
180 ._find_liquidity_index_in_borrows(borrow_reserve)
181 .ok_or(LendingError::InvalidObligationLiquidity)?;
182 Ok((&self.borrows[liquidity_index], liquidity_index))
183 }
184
185 pub fn find_liquidity_in_borrows_mut(
187 &mut self,
188 borrow_reserve: Pubkey,
189 ) -> Result<(&mut ObligationLiquidity, usize), ProgramError> {
190 if self.borrows.is_empty() {
191 msg!("Obligation has no borrows");
192 return Err(LendingError::ObligationBorrowsEmpty.into());
193 }
194 let liquidity_index = self
195 ._find_liquidity_index_in_borrows(borrow_reserve)
196 .ok_or(LendingError::InvalidObligationLiquidity)?;
197 Ok((&mut self.borrows[liquidity_index], liquidity_index))
198 }
199
200 pub fn find_or_add_liquidity_to_borrows(
202 &mut self,
203 borrow_reserve: Pubkey,
204 cumulative_borrow_rate_wads: Decimal,
205 ) -> Result<&mut ObligationLiquidity, ProgramError> {
206 if let Some(liquidity_index) = self._find_liquidity_index_in_borrows(borrow_reserve) {
207 return Ok(&mut self.borrows[liquidity_index]);
208 }
209 if self.deposits.len() + self.borrows.len() >= MAX_OBLIGATION_RESERVES {
210 msg!(
211 "Obligation cannot have more than {} deposits and borrows combined",
212 MAX_OBLIGATION_RESERVES
213 );
214 return Err(LendingError::ObligationReserveLimit.into());
215 }
216 let liquidity = ObligationLiquidity::new(borrow_reserve, cumulative_borrow_rate_wads);
217 self.borrows.push(liquidity);
218 Ok(self.borrows.last_mut().unwrap())
219 }
220
221 fn _find_liquidity_index_in_borrows(&self, borrow_reserve: Pubkey) -> Option<usize> {
222 self.borrows
223 .iter()
224 .position(|liquidity| liquidity.borrow_reserve == borrow_reserve)
225 }
226}
227
228pub struct InitObligationParams {
230 pub current_slot: Slot,
232 pub lending_market: Pubkey,
234 pub owner: Pubkey,
236 pub deposits: Vec<ObligationCollateral>,
238 pub borrows: Vec<ObligationLiquidity>,
240}
241
242impl Sealed for Obligation {}
243impl IsInitialized for Obligation {
244 fn is_initialized(&self) -> bool {
245 self.version != UNINITIALIZED_VERSION
246 }
247}
248
249#[derive(Clone, Debug, Default, PartialEq, Eq)]
251pub struct ObligationCollateral {
252 pub deposit_reserve: Pubkey,
254 pub deposited_amount: u64,
256 pub market_value: Decimal,
258}
259
260impl ObligationCollateral {
261 pub fn new(deposit_reserve: Pubkey) -> Self {
263 Self {
264 deposit_reserve,
265 deposited_amount: 0,
266 market_value: Decimal::zero(),
267 }
268 }
269
270 pub fn deposit(&mut self, collateral_amount: u64) -> ProgramResult {
272 self.deposited_amount = self
273 .deposited_amount
274 .checked_add(collateral_amount)
275 .ok_or(LendingError::MathOverflow)?;
276 Ok(())
277 }
278
279 pub fn withdraw(&mut self, collateral_amount: u64) -> ProgramResult {
281 self.deposited_amount = self
282 .deposited_amount
283 .checked_sub(collateral_amount)
284 .ok_or(LendingError::MathOverflow)?;
285 Ok(())
286 }
287}
288
289#[derive(Clone, Debug, Default, PartialEq, Eq)]
291pub struct ObligationLiquidity {
292 pub borrow_reserve: Pubkey,
294 pub cumulative_borrow_rate_wads: Decimal,
296 pub borrowed_amount_wads: Decimal,
298 pub market_value: Decimal,
300}
301
302impl ObligationLiquidity {
303 pub fn new(borrow_reserve: Pubkey, cumulative_borrow_rate_wads: Decimal) -> Self {
305 Self {
306 borrow_reserve,
307 cumulative_borrow_rate_wads,
308 borrowed_amount_wads: Decimal::zero(),
309 market_value: Decimal::zero(),
310 }
311 }
312
313 pub fn repay(&mut self, settle_amount: Decimal) -> ProgramResult {
315 self.borrowed_amount_wads = self.borrowed_amount_wads.try_sub(settle_amount)?;
316 Ok(())
317 }
318
319 pub fn borrow(&mut self, borrow_amount: Decimal) -> ProgramResult {
321 self.borrowed_amount_wads = self.borrowed_amount_wads.try_add(borrow_amount)?;
322 Ok(())
323 }
324
325 pub fn accrue_interest(&mut self, cumulative_borrow_rate_wads: Decimal) -> ProgramResult {
327 match cumulative_borrow_rate_wads.cmp(&self.cumulative_borrow_rate_wads) {
328 Ordering::Less => {
329 msg!("Interest rate cannot be negative");
330 return Err(LendingError::NegativeInterestRate.into());
331 }
332 Ordering::Equal => {}
333 Ordering::Greater => {
334 let compounded_interest_rate: Rate = cumulative_borrow_rate_wads
335 .try_div(self.cumulative_borrow_rate_wads)?
336 .try_into()?;
337
338 self.borrowed_amount_wads = self
339 .borrowed_amount_wads
340 .try_mul(compounded_interest_rate)?;
341 self.cumulative_borrow_rate_wads = cumulative_borrow_rate_wads;
342 }
343 }
344
345 Ok(())
346 }
347}
348
349const OBLIGATION_COLLATERAL_LEN: usize = 88; const OBLIGATION_LIQUIDITY_LEN: usize = 112; const OBLIGATION_LEN: usize = 1300; impl Pack for Obligation {
354 const LEN: usize = OBLIGATION_LEN;
355
356 fn pack_into_slice(&self, dst: &mut [u8]) {
357 let output = array_mut_ref![dst, 0, OBLIGATION_LEN];
358 #[allow(clippy::ptr_offset_with_cast)]
359 let (
360 version,
361 last_update_slot,
362 last_update_stale,
363 lending_market,
364 owner,
365 deposited_value,
366 borrowed_value,
367 allowed_borrow_value,
368 unhealthy_borrow_value,
369 _padding,
370 deposits_len,
371 borrows_len,
372 data_flat,
373 ) = mut_array_refs![
374 output,
375 1,
376 8,
377 1,
378 PUBKEY_BYTES,
379 PUBKEY_BYTES,
380 16,
381 16,
382 16,
383 16,
384 64,
385 1,
386 1,
387 OBLIGATION_COLLATERAL_LEN + (OBLIGATION_LIQUIDITY_LEN * (MAX_OBLIGATION_RESERVES - 1))
388 ];
389
390 *version = self.version.to_le_bytes();
392 *last_update_slot = self.last_update.slot.to_le_bytes();
393 pack_bool(self.last_update.stale, last_update_stale);
394 lending_market.copy_from_slice(self.lending_market.as_ref());
395 owner.copy_from_slice(self.owner.as_ref());
396 pack_decimal(self.deposited_value, deposited_value);
397 pack_decimal(self.borrowed_value, borrowed_value);
398 pack_decimal(self.allowed_borrow_value, allowed_borrow_value);
399 pack_decimal(self.unhealthy_borrow_value, unhealthy_borrow_value);
400 *deposits_len = u8::try_from(self.deposits.len()).unwrap().to_le_bytes();
401 *borrows_len = u8::try_from(self.borrows.len()).unwrap().to_le_bytes();
402
403 let mut offset = 0;
404
405 for collateral in &self.deposits {
407 let deposits_flat = array_mut_ref![data_flat, offset, OBLIGATION_COLLATERAL_LEN];
408 #[allow(clippy::ptr_offset_with_cast)]
409 let (deposit_reserve, deposited_amount, market_value, _padding_deposit) =
410 mut_array_refs![deposits_flat, PUBKEY_BYTES, 8, 16, 32];
411 deposit_reserve.copy_from_slice(collateral.deposit_reserve.as_ref());
412 *deposited_amount = collateral.deposited_amount.to_le_bytes();
413 pack_decimal(collateral.market_value, market_value);
414 offset += OBLIGATION_COLLATERAL_LEN;
415 }
416
417 for liquidity in &self.borrows {
419 let borrows_flat = array_mut_ref![data_flat, offset, OBLIGATION_LIQUIDITY_LEN];
420 #[allow(clippy::ptr_offset_with_cast)]
421 let (
422 borrow_reserve,
423 cumulative_borrow_rate_wads,
424 borrowed_amount_wads,
425 market_value,
426 _padding_borrow,
427 ) = mut_array_refs![borrows_flat, PUBKEY_BYTES, 16, 16, 16, 32];
428 borrow_reserve.copy_from_slice(liquidity.borrow_reserve.as_ref());
429 pack_decimal(
430 liquidity.cumulative_borrow_rate_wads,
431 cumulative_borrow_rate_wads,
432 );
433 pack_decimal(liquidity.borrowed_amount_wads, borrowed_amount_wads);
434 pack_decimal(liquidity.market_value, market_value);
435 offset += OBLIGATION_LIQUIDITY_LEN;
436 }
437 }
438
439 fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
441 let input = array_ref![src, 0, OBLIGATION_LEN];
442 #[allow(clippy::ptr_offset_with_cast)]
443 let (
444 version,
445 last_update_slot,
446 last_update_stale,
447 lending_market,
448 owner,
449 deposited_value,
450 borrowed_value,
451 allowed_borrow_value,
452 unhealthy_borrow_value,
453 _padding,
454 deposits_len,
455 borrows_len,
456 data_flat,
457 ) = array_refs![
458 input,
459 1,
460 8,
461 1,
462 PUBKEY_BYTES,
463 PUBKEY_BYTES,
464 16,
465 16,
466 16,
467 16,
468 64,
469 1,
470 1,
471 OBLIGATION_COLLATERAL_LEN + (OBLIGATION_LIQUIDITY_LEN * (MAX_OBLIGATION_RESERVES - 1))
472 ];
473
474 let version = u8::from_le_bytes(*version);
475 if version > PROGRAM_VERSION {
476 msg!("Obligation version does not match lending program version");
477 return Err(ProgramError::InvalidAccountData);
478 }
479
480 let deposits_len = u8::from_le_bytes(*deposits_len);
481 let borrows_len = u8::from_le_bytes(*borrows_len);
482 let mut deposits = Vec::with_capacity(deposits_len as usize + 1);
483 let mut borrows = Vec::with_capacity(borrows_len as usize + 1);
484
485 let mut offset = 0;
486 for _ in 0..deposits_len {
487 let deposits_flat = array_ref![data_flat, offset, OBLIGATION_COLLATERAL_LEN];
488 #[allow(clippy::ptr_offset_with_cast)]
489 let (deposit_reserve, deposited_amount, market_value, _padding_deposit) =
490 array_refs![deposits_flat, PUBKEY_BYTES, 8, 16, 32];
491 deposits.push(ObligationCollateral {
492 deposit_reserve: Pubkey::new(deposit_reserve),
493 deposited_amount: u64::from_le_bytes(*deposited_amount),
494 market_value: unpack_decimal(market_value),
495 });
496 offset += OBLIGATION_COLLATERAL_LEN;
497 }
498 for _ in 0..borrows_len {
499 let borrows_flat = array_ref![data_flat, offset, OBLIGATION_LIQUIDITY_LEN];
500 #[allow(clippy::ptr_offset_with_cast)]
501 let (
502 borrow_reserve,
503 cumulative_borrow_rate_wads,
504 borrowed_amount_wads,
505 market_value,
506 _padding_borrow,
507 ) = array_refs![borrows_flat, PUBKEY_BYTES, 16, 16, 16, 32];
508 borrows.push(ObligationLiquidity {
509 borrow_reserve: Pubkey::new(borrow_reserve),
510 cumulative_borrow_rate_wads: unpack_decimal(cumulative_borrow_rate_wads),
511 borrowed_amount_wads: unpack_decimal(borrowed_amount_wads),
512 market_value: unpack_decimal(market_value),
513 });
514 offset += OBLIGATION_LIQUIDITY_LEN;
515 }
516
517 Ok(Self {
518 version,
519 last_update: LastUpdate {
520 slot: u64::from_le_bytes(*last_update_slot),
521 stale: unpack_bool(last_update_stale)?,
522 },
523 lending_market: Pubkey::new_from_array(*lending_market),
524 owner: Pubkey::new_from_array(*owner),
525 deposits,
526 borrows,
527 deposited_value: unpack_decimal(deposited_value),
528 borrowed_value: unpack_decimal(borrowed_value),
529 allowed_borrow_value: unpack_decimal(allowed_borrow_value),
530 unhealthy_borrow_value: unpack_decimal(unhealthy_borrow_value),
531 })
532 }
533}
534
535#[cfg(test)]
536mod test {
537 use super::*;
538 use crate::math::TryAdd;
539 use proptest::prelude::*;
540
541 const MAX_COMPOUNDED_INTEREST: u64 = 100; #[test]
544 fn obligation_accrue_interest_failure() {
545 assert_eq!(
546 ObligationLiquidity {
547 cumulative_borrow_rate_wads: Decimal::zero(),
548 ..ObligationLiquidity::default()
549 }
550 .accrue_interest(Decimal::one()),
551 Err(LendingError::MathOverflow.into())
552 );
553
554 assert_eq!(
555 ObligationLiquidity {
556 cumulative_borrow_rate_wads: Decimal::from(2u64),
557 ..ObligationLiquidity::default()
558 }
559 .accrue_interest(Decimal::one()),
560 Err(LendingError::NegativeInterestRate.into())
561 );
562
563 assert_eq!(
564 ObligationLiquidity {
565 cumulative_borrow_rate_wads: Decimal::one(),
566 borrowed_amount_wads: Decimal::from(u64::MAX),
567 ..ObligationLiquidity::default()
568 }
569 .accrue_interest(Decimal::from(10 * MAX_COMPOUNDED_INTEREST)),
570 Err(LendingError::MathOverflow.into())
571 );
572 }
573
574 prop_compose! {
576 fn cumulative_rates()(rate in 1..=u128::MAX)(
577 current_rate in Just(rate),
578 max_new_rate in rate..=rate.saturating_mul(MAX_COMPOUNDED_INTEREST as u128),
579 ) -> (u128, u128) {
580 (current_rate, max_new_rate)
581 }
582 }
583
584 const MAX_BORROWED: u128 = u64::MAX as u128 * WAD as u128;
585
586 prop_compose! {
588 fn repay_partial_amounts()(amount in 1..=u64::MAX)(
589 repay_amount in Just(WAD as u128 * amount as u128),
590 borrowed_amount in (WAD as u128 * amount as u128 + 1)..=MAX_BORROWED,
591 ) -> (u128, u128) {
592 (repay_amount, borrowed_amount)
593 }
594 }
595
596 prop_compose! {
598 fn repay_full_amounts()(amount in 1..=u64::MAX)(
599 repay_amount in Just(WAD as u128 * amount as u128),
600 ) -> (u128, u128) {
601 (repay_amount, repay_amount)
602 }
603 }
604
605 proptest! {
606 #[test]
607 fn repay_partial(
608 (repay_amount, borrowed_amount) in repay_partial_amounts(),
609 ) {
610 let borrowed_amount_wads = Decimal::from_scaled_val(borrowed_amount);
611 let repay_amount_wads = Decimal::from_scaled_val(repay_amount);
612 let mut obligation = Obligation {
613 borrows: vec![ObligationLiquidity {
614 borrowed_amount_wads,
615 ..ObligationLiquidity::default()
616 }],
617 ..Obligation::default()
618 };
619
620 obligation.repay(repay_amount_wads, 0)?;
621 assert!(obligation.borrows[0].borrowed_amount_wads < borrowed_amount_wads);
622 assert!(obligation.borrows[0].borrowed_amount_wads > Decimal::zero());
623 }
624
625 #[test]
626 fn repay_full(
627 (repay_amount, borrowed_amount) in repay_full_amounts(),
628 ) {
629 let borrowed_amount_wads = Decimal::from_scaled_val(borrowed_amount);
630 let repay_amount_wads = Decimal::from_scaled_val(repay_amount);
631 let mut obligation = Obligation {
632 borrows: vec![ObligationLiquidity {
633 borrowed_amount_wads,
634 ..ObligationLiquidity::default()
635 }],
636 ..Obligation::default()
637 };
638
639 obligation.repay(repay_amount_wads, 0)?;
640 assert_eq!(obligation.borrows.len(), 0);
641 }
642
643 #[test]
644 fn accrue_interest(
645 (current_borrow_rate, new_borrow_rate) in cumulative_rates(),
646 borrowed_amount in 0..=u64::MAX,
647 ) {
648 let cumulative_borrow_rate_wads = Decimal::one().try_add(Decimal::from_scaled_val(current_borrow_rate))?;
649 let borrowed_amount_wads = Decimal::from(borrowed_amount);
650 let mut liquidity = ObligationLiquidity {
651 cumulative_borrow_rate_wads,
652 borrowed_amount_wads,
653 ..ObligationLiquidity::default()
654 };
655
656 let next_cumulative_borrow_rate = Decimal::one().try_add(Decimal::from_scaled_val(new_borrow_rate))?;
657 liquidity.accrue_interest(next_cumulative_borrow_rate)?;
658
659 if next_cumulative_borrow_rate > cumulative_borrow_rate_wads {
660 assert!(liquidity.borrowed_amount_wads > borrowed_amount_wads);
661 } else {
662 assert!(liquidity.borrowed_amount_wads == borrowed_amount_wads);
663 }
664 }
665 }
666
667 #[test]
668 fn max_liquidation_amount_normal() {
669 let obligation_liquidity = ObligationLiquidity {
670 borrowed_amount_wads: Decimal::from(50u64),
671 market_value: Decimal::from(100u64),
672 ..ObligationLiquidity::default()
673 };
674
675 let obligation = Obligation {
676 deposited_value: Decimal::from(100u64),
677 borrowed_value: Decimal::from(100u64),
678 borrows: vec![obligation_liquidity.clone()],
679 ..Obligation::default()
680 };
681
682 let expected_collateral = Decimal::from(50u64)
683 .try_mul(Decimal::from(LIQUIDATION_CLOSE_FACTOR as u64))
684 .unwrap()
685 .try_div(100)
686 .unwrap();
687
688 assert_eq!(
689 obligation
690 .max_liquidation_amount(&obligation_liquidity)
691 .unwrap(),
692 expected_collateral
693 );
694 }
695
696 #[test]
697 fn max_liquidation_amount_low_liquidity() {
698 let obligation_liquidity = ObligationLiquidity {
699 borrowed_amount_wads: Decimal::from(100u64),
700 market_value: Decimal::from(1u64),
701 ..ObligationLiquidity::default()
702 };
703
704 let obligation = Obligation {
705 deposited_value: Decimal::from(100u64),
706 borrowed_value: Decimal::from(100u64),
707 borrows: vec![obligation_liquidity.clone()],
708 ..Obligation::default()
709 };
710
711 assert_eq!(
712 obligation
713 .max_liquidation_amount(&obligation_liquidity)
714 .unwrap(),
715 Decimal::from(100u64)
716 );
717 }
718
719 #[test]
720 fn max_liquidation_amount_big_whale() {
721 let obligation_liquidity = ObligationLiquidity {
722 borrowed_amount_wads: Decimal::from(1_000_000_000u64),
723 market_value: Decimal::from(1_000_000_000u64),
724 ..ObligationLiquidity::default()
725 };
726
727 let obligation = Obligation {
728 deposited_value: Decimal::from(1_000_000_000u64),
729 borrowed_value: Decimal::from(1_000_000_000u64),
730 borrows: vec![obligation_liquidity.clone()],
731 ..Obligation::default()
732 };
733
734 assert_eq!(
735 obligation
736 .max_liquidation_amount(&obligation_liquidity)
737 .unwrap(),
738 Decimal::from(MAX_LIQUIDATABLE_VALUE_AT_ONCE)
739 );
740 }
741}