1use near_sdk::{
2 collections::{LookupMap, UnorderedMap},
3 env, near, AccountId, BorshStorageKey, IntoStorageKey,
4};
5
6use crate::{
7 accumulator::{AccumulationRecord, Accumulator},
8 asset::{BorrowAsset, BorrowAssetAmount, CollateralAssetAmount},
9 borrow::{BorrowPosition, BorrowPositionGuard, BorrowPositionRef},
10 chunked_append_only_list::ChunkedAppendOnlyList,
11 event::MarketEvent,
12 incoming_deposit::IncomingDeposit,
13 market::MarketConfiguration,
14 number::Decimal,
15 snapshot::Snapshot,
16 supply::{SupplyPosition, SupplyPositionGuard, SupplyPositionRef},
17 time_chunk::TimeChunk,
18 withdrawal_queue::WithdrawalQueue,
19 YEAR_PER_MS,
20};
21
22#[derive(Debug, Copy, Clone)]
23pub struct SnapshotProof(());
24
25#[derive(BorshStorageKey)]
26#[near]
27enum StorageKey {
28 SupplyPositions,
29 BorrowPositions,
30 FinalizedSnapshots,
31 WithdrawalQueue,
32 StaticYield,
33}
34
35#[near]
36pub struct Market {
37 prefix: Vec<u8>,
38 pub configuration: MarketConfiguration,
39 pub borrow_asset_balance: BorrowAssetAmount,
41 pub borrow_asset_deposited_active: BorrowAssetAmount,
43 pub borrow_asset_deposited_incoming: Vec<IncomingDeposit>,
45 pub borrow_asset_withdrawal_in_flight: BorrowAssetAmount,
46 pub borrow_asset_borrowed_in_flight: BorrowAssetAmount,
50 pub borrow_asset_borrowed: BorrowAssetAmount,
54 pub borrow_asset_paid_to_fees: BorrowAssetAmount,
56 pub collateral_asset_deposited: CollateralAssetAmount,
58 pub(crate) supply_positions: UnorderedMap<AccountId, SupplyPosition>,
59 pub(crate) borrow_positions: UnorderedMap<AccountId, BorrowPosition>,
60 pub current_time_chunk: TimeChunk,
61 pub current_yield_distribution: BorrowAssetAmount,
62 pub finalized_snapshots: ChunkedAppendOnlyList<Snapshot, 32>,
63 pub withdrawal_queue: WithdrawalQueue,
64 pub static_yield: LookupMap<AccountId, Accumulator<BorrowAsset>>,
65 single_snapshot_maximum_interest_precomputed: Decimal,
66}
67
68impl Market {
69 pub fn new(prefix: impl IntoStorageKey, configuration: MarketConfiguration) -> Self {
70 if let Err(e) = configuration.validate() {
71 crate::panic_with_message(&e.to_string());
72 }
73
74 let prefix = prefix.into_storage_key();
75 macro_rules! key {
76 ($key: ident) => {
77 [
78 prefix.as_slice(),
79 StorageKey::$key.into_storage_key().as_slice(),
80 ]
81 .concat()
82 };
83 }
84
85 let first_snapshot = Snapshot::new(configuration.time_chunk_configuration.previous());
86 let last_time_chunk = configuration.time_chunk_configuration.now();
87
88 let single_snapshot_maximum_interest_precomputed =
89 configuration.single_snapshot_maximum_interest();
90
91 let mut self_ = Self {
92 prefix: prefix.clone(),
93 configuration,
94 borrow_asset_balance: 0.into(),
95 borrow_asset_deposited_active: 0.into(),
96 borrow_asset_deposited_incoming: Vec::new(),
97 borrow_asset_withdrawal_in_flight: 0.into(),
98 borrow_asset_borrowed_in_flight: 0.into(),
99 borrow_asset_borrowed: 0.into(),
100 borrow_asset_paid_to_fees: 0.into(),
101 collateral_asset_deposited: 0.into(),
102 supply_positions: UnorderedMap::new(key!(SupplyPositions)),
103 borrow_positions: UnorderedMap::new(key!(BorrowPositions)),
104 current_time_chunk: last_time_chunk,
105 current_yield_distribution: 0.into(),
106 finalized_snapshots: ChunkedAppendOnlyList::new(key!(FinalizedSnapshots)),
107 withdrawal_queue: WithdrawalQueue::new(key!(WithdrawalQueue)),
108 static_yield: LookupMap::new(key!(StaticYield)),
109 single_snapshot_maximum_interest_precomputed,
110 };
111
112 self_.finalized_snapshots.push(first_snapshot);
113
114 self_
115 }
116
117 pub fn borrowed(&self) -> BorrowAssetAmount {
118 self.borrow_asset_borrowed + self.borrow_asset_borrowed_in_flight
119 }
120
121 pub fn total_incoming(&self) -> BorrowAssetAmount {
122 self.borrow_asset_deposited_incoming
123 .iter()
124 .fold(BorrowAssetAmount::zero(), |total_incoming, incoming| {
125 total_incoming + incoming.amount
126 })
127 }
128
129 pub fn incoming_at(&self, snapshot_index: u32) -> BorrowAssetAmount {
130 self.borrow_asset_deposited_incoming
131 .iter()
132 .find_map(|incoming| {
133 (incoming.activate_at_snapshot_index == snapshot_index).then_some(incoming.amount)
134 })
135 .unwrap_or(0.into())
136 }
137
138 pub fn get_last_finalized_snapshot(&self) -> &Snapshot {
139 #[allow(clippy::unwrap_used, reason = "Snapshots are never empty")]
140 self.finalized_snapshots
141 .get(self.finalized_snapshots.len() - 1)
142 .unwrap()
143 }
144
145 pub fn current_snapshot(&self) -> Snapshot {
146 let current_snapshot_index = self.finalized_snapshots.len();
147 let incoming = self.incoming_at(current_snapshot_index);
148
149 let active = self.borrow_asset_deposited_active + incoming;
150
151 let borrowed = self.borrowed();
152
153 let interest_rate = self
154 .configuration
155 .borrow_interest_rate_strategy
156 .at(usage_ratio(active, borrowed));
157
158 Snapshot {
159 time_chunk: self.current_time_chunk,
160 end_timestamp_ms: env::block_timestamp_ms().into(),
161 borrow_asset_deposited_active: active,
162 borrow_asset_borrowed: borrowed,
163 collateral_asset_deposited: self.collateral_asset_deposited,
164 yield_distribution: self.current_yield_distribution,
165 interest_rate,
166 }
167 }
168
169 pub fn snapshot(&mut self) -> SnapshotProof {
170 let now = self.configuration.time_chunk_configuration.now();
171
172 if self.current_time_chunk == now {
174 return SnapshotProof(());
175 }
176
177 let snapshot = self.current_snapshot();
178 let current_snapshot_index = self.finalized_snapshots.len();
179
180 MarketEvent::SnapshotFinalized {
182 index: current_snapshot_index,
183 snapshot: snapshot.clone(),
184 }
185 .emit();
186 self.finalized_snapshots.push(snapshot);
187
188 let current_snapshot_index = current_snapshot_index + 1;
190
191 for i in 0..self.borrow_asset_deposited_incoming.len() {
193 let incoming = &self.borrow_asset_deposited_incoming[i];
194 if incoming.activate_at_snapshot_index == current_snapshot_index {
195 self.borrow_asset_deposited_active += incoming.amount;
196 self.borrow_asset_deposited_incoming.remove(i);
197 break;
198 }
199 }
200
201 self.current_time_chunk = now;
203 self.current_yield_distribution = 0.into();
204
205 SnapshotProof(())
206 }
207
208 pub fn single_snapshot_fee(&self, amount: BorrowAssetAmount) -> Option<BorrowAssetAmount> {
209 (u128::from(amount) * self.single_snapshot_maximum_interest_precomputed)
210 .to_u128_ceil()
211 .map(Into::into)
212 }
213
214 pub fn interest_rate(&self) -> Decimal {
215 self.configuration
216 .borrow_interest_rate_strategy
217 .at(usage_ratio(
218 self.borrow_asset_deposited_active,
219 self.borrowed(),
220 ))
221 }
222
223 pub fn get_borrow_asset_available_to_borrow(&self) -> BorrowAssetAmount {
224 #[allow(
225 clippy::unwrap_used,
226 reason = "Factor is guaranteed to be <=1, so value must still fit in u128"
227 )]
228 let must_retain: BorrowAssetAmount = ((1u32
229 - self.configuration.borrow_asset_maximum_usage_ratio)
230 * Decimal::from(self.borrow_asset_deposited_active))
231 .to_u128_ceil()
232 .unwrap()
233 .into();
234
235 self.borrow_asset_deposited_active
236 .saturating_sub(self.borrowed())
237 .saturating_sub(must_retain)
238 }
239
240 pub fn iter_supply_positions(&self) -> impl Iterator<Item = (AccountId, SupplyPosition)> + '_ {
241 self.supply_positions.iter()
242 }
243
244 pub fn supply_position_ref(&self, account_id: AccountId) -> Option<SupplyPositionRef<&Self>> {
245 self.supply_positions
246 .get(&account_id)
247 .map(|position| SupplyPositionRef::new(self, account_id, position))
248 }
249
250 pub fn supply_position_guard(
251 &mut self,
252 _proof: SnapshotProof,
253 account_id: AccountId,
254 ) -> Option<SupplyPositionGuard> {
255 self.supply_positions
256 .get(&account_id)
257 .map(|position| SupplyPositionGuard::new(self, account_id, position))
258 }
259
260 pub fn get_or_create_supply_position_guard(
261 &mut self,
262 _proof: SnapshotProof,
263 account_id: AccountId,
264 ) -> SupplyPositionGuard {
265 let position = self
266 .supply_positions
267 .get(&account_id)
268 .unwrap_or_else(|| SupplyPosition::new(self.finalized_snapshots.len()));
269
270 SupplyPositionGuard::new(self, account_id, position)
271 }
272
273 pub fn cleanup_supply_position(&mut self, account_id: &AccountId) -> bool {
274 self.supply_positions
275 .get(account_id)
276 .filter(SupplyPosition::can_be_removed)
277 .and_then(|_| self.supply_positions.remove(account_id))
278 .is_some()
279 }
280
281 pub fn iter_borrow_positions(&self) -> impl Iterator<Item = (AccountId, BorrowPosition)> + '_ {
282 self.borrow_positions.iter()
283 }
284
285 pub fn borrow_position_ref(&self, account_id: AccountId) -> Option<BorrowPositionRef<&Self>> {
286 self.borrow_positions
287 .get(&account_id)
288 .map(|position| BorrowPositionRef::new(self, account_id, position))
289 }
290
291 pub fn borrow_position_guard(
292 &mut self,
293 _proof: SnapshotProof,
294 account_id: AccountId,
295 ) -> Option<BorrowPositionGuard> {
296 self.borrow_positions
297 .get(&account_id)
298 .map(|position| BorrowPositionGuard::new(self, account_id, position))
299 }
300
301 pub fn get_or_create_borrow_position_guard(
302 &mut self,
303 _proof: SnapshotProof,
304 account_id: AccountId,
305 ) -> BorrowPositionGuard {
306 let position = self
307 .borrow_positions
308 .get(&account_id)
309 .unwrap_or_else(|| BorrowPosition::new(self.finalized_snapshots.len()));
310
311 BorrowPositionGuard::new(self, account_id, position)
312 }
313
314 pub fn cleanup_borrow_position(&mut self, account_id: &AccountId) -> bool {
315 self.borrow_positions
316 .get(account_id)
317 .filter(|p| !p.exists())
318 .and_then(|_| self.borrow_positions.remove(account_id))
319 .is_some()
320 }
321
322 pub fn record_borrow_asset_protocol_yield(&mut self, amount: BorrowAssetAmount) {
323 let mut yield_record = self
324 .static_yield
325 .get(&self.configuration.protocol_account_id)
326 .unwrap_or_else(|| Accumulator::new(1));
327
328 yield_record.add_once(amount);
329
330 self.static_yield
331 .insert(&self.configuration.protocol_account_id, &yield_record);
332 }
333
334 pub fn record_borrow_asset_yield_distribution(&mut self, amount: BorrowAssetAmount) {
335 if amount.is_zero() {
337 return;
338 }
339
340 self.current_yield_distribution += amount;
341 }
342
343 pub fn accumulate_static_yield(
349 &mut self,
350 account_id: &AccountId,
351 snapshot_limit: u32,
352 ) -> Result<(), UnknownAccount> {
353 let weight_numerator = *self
354 .configuration
355 .yield_weights
356 .r#static
357 .get(account_id)
358 .ok_or(UnknownAccount)?;
359 let weight_denominator = self.configuration.yield_weights.total_weight().get();
360 let mut accumulator = self
361 .static_yield
362 .get(account_id)
363 .unwrap_or_else(|| Accumulator::new(1));
364
365 let mut next_snapshot_index = accumulator.get_next_snapshot_index();
366 let mut accumulated = Decimal::ZERO;
367
368 #[allow(clippy::unwrap_used, reason = "Guaranteed previous snapshot exists")]
369 let mut prev_end_timestamp_ms = self
370 .finalized_snapshots
371 .get(next_snapshot_index.checked_sub(1).unwrap())
372 .unwrap()
373 .end_timestamp_ms
374 .0;
375
376 #[allow(
377 clippy::cast_possible_truncation,
378 reason = "Assume # of snapshots is never >u32::MAX"
379 )]
380 for (i, snapshot) in self
381 .finalized_snapshots
382 .iter()
383 .enumerate()
384 .skip(next_snapshot_index as usize)
385 .take(snapshot_limit as usize)
386 {
387 let snapshot_duration_ms = snapshot.end_timestamp_ms.0 - prev_end_timestamp_ms;
388 let interest_paid_by_borrowers = Decimal::from(snapshot.borrow_asset_borrowed)
389 * snapshot.interest_rate
390 * snapshot_duration_ms
391 * YEAR_PER_MS;
392 let other_yield = Decimal::from(snapshot.yield_distribution);
393 accumulated +=
394 (interest_paid_by_borrowers + other_yield) * weight_numerator / weight_denominator;
395
396 next_snapshot_index = i as u32 + 1;
397 prev_end_timestamp_ms = snapshot.end_timestamp_ms.0;
398 }
399
400 let accumulation_record = AccumulationRecord {
401 #[allow(clippy::unwrap_used, reason = "Derived from real balances")]
404 amount: accumulated.to_u128_floor().unwrap().into(),
405 fraction_as_u128_dividend: accumulated.fractional_part_as_u128_dividend(),
406 next_snapshot_index,
407 };
408
409 accumulator.accumulate(accumulation_record);
410
411 self.static_yield.insert(account_id, &accumulator);
412
413 Ok(())
414 }
415}
416
417#[derive(Debug, thiserror::Error)]
418#[error("This account does not earn static yield")]
419pub struct UnknownAccount;
420
421fn usage_ratio(active: BorrowAssetAmount, borrowed: BorrowAssetAmount) -> Decimal {
422 if active.is_zero() || borrowed.is_zero() {
423 Decimal::ZERO
424 } else if borrowed >= active {
425 Decimal::ONE
426 } else {
427 Decimal::from(borrowed) / Decimal::from(active)
428 }
429}
430
431#[allow(clippy::too_many_lines)]
432#[cfg(test)]
433mod tests {
434 use near_sdk::{test_utils::*, testing_env, VMContext};
435
436 use crate::{
437 asset::FungibleAsset,
438 borrow::InitialBorrow,
439 dec,
440 fee::{Fee, TimeBasedFee},
441 interest_rate_strategy::InterestRateStrategy,
442 market::{PriceOracleConfiguration, Withdrawal, YieldWeights},
443 oracle::pyth::PriceIdentifier,
444 price::PricePair,
445 supply::WithdrawalAttempt,
446 time_chunk::TimeChunkConfiguration,
447 };
448
449 use super::*;
450
451 fn configuration() -> MarketConfiguration {
452 MarketConfiguration {
453 time_chunk_configuration: TimeChunkConfiguration::new(1),
454 borrow_asset: FungibleAsset::nep141("borrow.near".parse().unwrap()),
455 collateral_asset: FungibleAsset::nep141("collateral.near".parse().unwrap()),
456 price_oracle_configuration: PriceOracleConfiguration {
457 account_id: "pyth-oracle.near".parse().unwrap(),
458 collateral_asset_price_id: PriceIdentifier([0xcc; 32]),
459 collateral_asset_decimals: 24,
460 borrow_asset_price_id: PriceIdentifier([0xbb; 32]),
461 borrow_asset_decimals: 24,
462 price_maximum_age_s: 60,
463 },
464 borrow_mcr_maintenance: dec!("1.25"),
465 borrow_mcr_liquidation: dec!("1.2"),
466 borrow_asset_maximum_usage_ratio: dec!("0.9"),
467 borrow_origination_fee: Fee::Proportional(dec!("0.25")),
468 borrow_interest_rate_strategy: InterestRateStrategy::zero(),
469 borrow_maximum_duration_ms: None,
470 borrow_range: (1, None).try_into().unwrap(),
471 supply_range: (1, None).try_into().unwrap(),
472 supply_withdrawal_range: (1, None).try_into().unwrap(),
473 supply_withdrawal_fee: TimeBasedFee::zero(),
474 yield_weights: YieldWeights::new_with_supply_weight(9)
475 .with_static("revenue.tmplr.near".parse().unwrap(), 1),
476 protocol_account_id: "revenue.tmplr.near".parse().unwrap(),
477 liquidation_maximum_spread: dec!("0.05"),
478 }
479 }
480
481 fn price_pair(collateral: i64, borrow: i64) -> PricePair {
482 PricePair::new(
483 &crate::oracle::pyth::Price {
484 price: collateral.into(),
485 conf: 0.into(),
486 expo: 24,
487 publish_time: 10,
488 },
489 24,
490 &crate::oracle::pyth::Price {
491 price: borrow.into(),
492 conf: 0.into(),
493 expo: 24,
494 publish_time: 10,
495 },
496 24,
497 )
498 .unwrap()
499 }
500
501 struct TestMarketController {
502 pub context: VMContext,
503 pub market: Market,
504 }
505
506 impl TestMarketController {
507 pub fn print(&self) {
508 eprintln!(
509 "Available: {}",
510 self.market.get_borrow_asset_available_to_borrow(),
511 );
512 eprintln!("Balance: {}", self.market.borrow_asset_balance);
513 eprintln!("Borrowed: {}", self.market.borrow_asset_borrowed);
514 eprintln!("Supply: {}", self.market.borrow_asset_deposited_active);
515 eprintln!("Paid to fees: {}", self.market.borrow_asset_paid_to_fees);
516 eprintln!("{:#?}", self.market.borrow_asset_deposited_incoming);
517 }
518
519 pub fn new(configuration: MarketConfiguration) -> Self {
520 let context = VMContextBuilder::new()
521 .block_timestamp(1_000_000_000_000)
522 .build();
523 testing_env!(context.clone());
524
525 let market = Market::new(b"m", configuration);
526
527 Self { context, market }
528 }
529
530 pub fn tick(&mut self) -> SnapshotProof {
531 self.context.block_timestamp += 1_000_000;
532 testing_env!(self.context.clone());
533 self.market.snapshot()
534 }
535
536 pub fn supply(&mut self, account: AccountId, amount: u128) {
537 let snapshot = self.tick();
538 let mut supply_position = self
539 .market
540 .get_or_create_supply_position_guard(snapshot, account);
541 let yield_proof = supply_position.accumulate_yield();
542 supply_position.record_deposit(yield_proof, amount.into(), env::block_timestamp_ms());
543 }
544
545 pub fn collateralize(&mut self, account: AccountId, amount: u128) {
546 let snapshot_proof = self.tick();
547 let mut borrow_position = self
548 .market
549 .get_or_create_borrow_position_guard(snapshot_proof, account);
550 let interest_proof = borrow_position.accumulate_interest();
551 borrow_position.record_collateral_asset_deposit(interest_proof, amount.into());
552 }
553
554 pub fn borrow_initial(&mut self, account_id: AccountId, amount: u128) -> InitialBorrow {
555 let snapshot = self.tick();
556 let mut borrow_position = self
557 .market
558 .borrow_position_guard(snapshot, account_id)
559 .unwrap();
560 let interest_proof = borrow_position.accumulate_interest();
561 borrow_position
562 .record_borrow_initial(
563 snapshot,
564 interest_proof,
565 amount.into(),
566 &price_pair(1, 1),
567 env::block_timestamp_ms(),
568 )
569 .unwrap()
570 }
571
572 pub fn borrow_final(&mut self, account_id: AccountId, initial: &InitialBorrow) {
573 let snapshot = self.tick();
574 let mut borrow_position = self
575 .market
576 .borrow_position_guard(snapshot, account_id)
577 .unwrap();
578 let interest_proof = borrow_position.accumulate_interest();
579 borrow_position.record_borrow_final(
580 snapshot,
581 interest_proof,
582 initial,
583 true,
584 env::block_timestamp_ms(),
585 );
586 }
587
588 pub fn accumulate_interest(&mut self, account_id: AccountId) -> BorrowPosition {
589 let snapshot = self.tick();
590 let mut borrow_position = self
591 .market
592 .borrow_position_guard(snapshot, account_id)
593 .unwrap();
594 let _ = borrow_position.accumulate_interest();
595 borrow_position.inner().clone()
596 }
597
598 pub fn repay(
599 &mut self,
600 account_id: AccountId,
601 amount: impl Into<BorrowAssetAmount>,
602 ) -> BorrowAssetAmount {
603 let snapshot = self.tick();
604 let mut borrow_position = self
605 .market
606 .borrow_position_guard(snapshot, account_id)
607 .unwrap();
608 let interest_proof = borrow_position.accumulate_interest();
609 borrow_position.record_repay(interest_proof, amount.into())
610 }
611
612 pub fn withdraw_collateral_initial(&mut self, account_id: AccountId, amount: u128) {
613 let snapshot = self.tick();
614 let mut borrow_position = self
615 .market
616 .borrow_position_guard(snapshot, account_id)
617 .unwrap();
618 let interest_proof = borrow_position.accumulate_interest();
619 borrow_position
620 .record_collateral_asset_withdrawal_initial(interest_proof, amount.into());
621 }
622
623 pub fn withdraw_collateral_final(&mut self, account_id: AccountId, amount: u128) {
624 let snapshot = self.tick();
625 let mut borrow_position = self
626 .market
627 .borrow_position_guard(snapshot, account_id)
628 .unwrap();
629 let interest_proof = borrow_position.accumulate_interest();
630 borrow_position.record_collateral_asset_withdrawal_final(
631 interest_proof,
632 amount.into(),
633 true,
634 );
635 }
636
637 pub fn liquidate(
638 &mut self,
639 liquidator_id: AccountId,
640 position_id: AccountId,
641 send: u128,
642 request: u128,
643 price_pair: &PricePair,
644 ) {
645 let snapshot = self.tick();
646 let mut borrow_position = self
647 .market
648 .borrow_position_guard(snapshot, position_id)
649 .unwrap();
650 let interest_proof = borrow_position.accumulate_interest();
651 let liquidation = borrow_position
652 .record_liquidation(
653 interest_proof,
654 liquidator_id,
655 send.into(),
656 Some(request.into()),
657 price_pair,
658 env::block_timestamp_ms(),
659 )
660 .unwrap();
661 assert_eq!(u128::from(liquidation.liquidated), request);
662 }
663
664 pub fn accumulate_yield(&mut self, account_id: AccountId) -> SupplyPosition {
665 let snapshot = self.tick();
666 let mut supply_position = self
667 .market
668 .supply_position_guard(snapshot, account_id)
669 .unwrap();
670 let _ = supply_position.accumulate_yield();
671 supply_position.inner().clone()
672 }
673
674 pub fn withdraw_supply_initial(
675 &mut self,
676 account_id: AccountId,
677 amount: u128,
678 ) -> WithdrawalAttempt {
679 let snapshot = self.tick();
680 let mut supply_position = self
681 .market
682 .supply_position_guard(snapshot, account_id)
683 .unwrap();
684 let proof = supply_position.accumulate_yield();
685 supply_position.record_withdrawal_initial(
686 proof,
687 amount.into(),
688 env::block_timestamp_ms(),
689 )
690 }
691
692 pub fn withdraw_supply_final(&mut self, account_id: AccountId, initial: &Withdrawal) {
693 let snapshot = self.tick();
694 let mut supply_position = self
695 .market
696 .supply_position_guard(snapshot, account_id)
697 .unwrap();
698 supply_position.record_withdrawal_final(initial, true);
699 }
700 }
701
702 #[test]
703 fn balance_1() {
704 let supplier: AccountId = "supply.near".parse().unwrap();
705 let borrower: AccountId = "borrow.near".parse().unwrap();
706
707 let mut c = TestMarketController::new(configuration());
708
709 c.supply(supplier.clone(), 10_000_000);
711 assert_eq!(c.market.borrow_asset_balance, 10_000_000.into());
712 assert_eq!(
713 c.market.get_borrow_asset_available_to_borrow(),
714 0.into(),
715 "still incoming, not yet active",
716 );
717
718 c.collateralize(borrower.clone(), 4_000_000);
719 assert_eq!(c.market.borrow_asset_balance, 10_000_000.into());
720 assert_eq!(
721 c.market.get_borrow_asset_available_to_borrow(),
722 9_000_000.into()
723 );
724
725 let initial = c.borrow_initial(borrower.clone(), 2_000_000);
726 c.print();
727 assert_eq!(c.market.borrow_asset_balance, 8_000_000.into());
728 assert_eq!(
729 c.market.get_borrow_asset_available_to_borrow(),
730 7_000_000.into()
731 );
732
733 c.borrow_final(borrower.clone(), &initial);
734 assert_eq!(c.market.borrow_asset_borrowed, 2_000_000.into());
735 assert_eq!(c.market.borrow_asset_balance, 8_000_000.into());
736 assert_eq!(
737 c.market.get_borrow_asset_available_to_borrow(),
738 7_000_000.into()
739 );
740
741 c.repay(borrower.clone(), 1_500_000);
743 assert_eq!(c.market.borrow_asset_borrowed, 1_000_000.into());
744 assert_eq!(c.market.borrow_asset_balance, 9_500_000.into());
745 assert_eq!(
746 c.market.get_borrow_asset_available_to_borrow(),
747 8_000_000.into()
748 );
749
750 c.withdraw_collateral_initial(borrower.clone(), 2_000_000);
752 assert_eq!(c.market.borrow_asset_balance, 9_500_000.into());
753 assert_eq!(
754 c.market.get_borrow_asset_available_to_borrow(),
755 8_000_000.into()
756 );
757
758 c.withdraw_collateral_final(borrower.clone(), 2_000_000);
760 assert_eq!(c.market.borrow_asset_balance, 9_500_000.into());
761 assert_eq!(
762 c.market.get_borrow_asset_available_to_borrow(),
763 8_000_000.into()
764 );
765
766 let liquidator: AccountId = "liquidator.near".parse().unwrap();
768 c.liquidate(
769 liquidator.clone(),
770 borrower.clone(),
771 1_000_000,
772 2_000_000,
773 &price_pair(1, 2),
774 );
775 assert_eq!(c.market.borrow_asset_balance, 10_500_000.into());
776 assert_eq!(
777 c.market.get_borrow_asset_available_to_borrow(),
778 9_000_000.into()
779 );
780
781 let expected_yield_amount: BorrowAssetAmount = (500_000 * 9 / 10).into();
783 let yield_amount = c
784 .accumulate_yield(supplier.clone())
785 .borrow_asset_yield
786 .get_total();
787 assert_eq!(yield_amount, expected_yield_amount);
788 assert_eq!(
789 c.market.borrow_asset_balance,
790 10_500_000.into(),
791 "Yield compounding does not affect the market's recorded balance",
792 );
793 assert_eq!(
794 c.market.get_borrow_asset_available_to_borrow(),
795 BorrowAssetAmount::new(9_000_000), );
797
798 c.tick(); assert_eq!(c.market.borrow_asset_balance, 10_500_000.into());
800 assert_eq!(c.market.borrow_asset_deposited_active, 10_000_000.into());
801 assert_eq!(
803 c.market.get_borrow_asset_available_to_borrow(),
804 BorrowAssetAmount::new(10_000_000 * 9 / 10),
805 );
806
807 let initial = c.withdraw_supply_initial(supplier.clone(), 10_450_000);
809 let initial = match initial {
810 WithdrawalAttempt::Full(initial) => initial,
811 a => {
812 panic!("Should be full withdrawal: {a:?}");
813 }
814 };
815 assert_eq!(c.market.borrow_asset_balance, 50_000.into());
816 assert_eq!(c.market.get_borrow_asset_available_to_borrow(), 0.into());
817
818 c.withdraw_supply_final(supplier.clone(), &initial);
820 assert_eq!(c.market.borrow_asset_balance, 50_000.into());
821 assert_eq!(c.market.get_borrow_asset_available_to_borrow(), 0.into());
822 }
823
824 #[rstest::rstest]
825 #[should_panic = "InsufficientBorrowAssetAvailable"]
826 #[case(65_000_000)]
827 #[case(63_500_000)]
828 fn balance_2(#[case] second_borrow_amount: u128) {
829 let mut configuration = configuration();
830
831 configuration.borrow_origination_fee = Fee::Flat(15_000_000.into());
832 configuration.borrow_interest_rate_strategy = InterestRateStrategy::zero();
833 configuration.borrow_asset_maximum_usage_ratio = Decimal::ONE;
834
835 let mut c = TestMarketController::new(configuration);
836
837 let supply_id: AccountId = "supply.near".parse().unwrap();
838 let borrow_id: AccountId = "borrow.near".parse().unwrap();
839 let borrow_2_id: AccountId = "borrow2.near".parse().unwrap();
840
841 c.supply(supply_id.clone(), 100_000_000);
843 assert_eq!(c.market.borrow_asset_balance, 100_000_000.into());
844 assert_eq!(
845 c.market.get_borrow_asset_available_to_borrow(),
846 0.into() );
848
849 c.collateralize(borrow_id.clone(), 200_000_000);
851 assert_eq!(c.market.borrow_asset_balance, 100_000_000.into());
852 assert_eq!(
853 c.market.get_borrow_asset_available_to_borrow(),
854 100_000_000.into()
855 );
856
857 let initial = c.borrow_initial(borrow_id.clone(), 100_000_000);
859 assert_eq!(c.market.borrow_asset_balance, 0.into());
860 assert_eq!(c.market.get_borrow_asset_available_to_borrow(), 0.into());
861
862 c.borrow_final(borrow_id.clone(), &initial);
864 assert_eq!(c.market.borrow_asset_balance, 0.into());
865 assert_eq!(c.market.get_borrow_asset_available_to_borrow(), 0.into());
866
867 let amount_repaid = c
869 .accumulate_interest(borrow_id.clone())
870 .get_total_borrow_asset_liability();
871 let amount_remaining = c.repay(borrow_id.clone(), amount_repaid);
872 assert_eq!(amount_remaining, 0.into());
873 assert_eq!(c.market.borrow_asset_balance, amount_repaid);
874 assert_eq!(
875 c.market.get_borrow_asset_available_to_borrow(),
876 100_000_000.into()
877 );
878
879 let yield_amount = c
881 .accumulate_yield(supply_id.clone())
882 .borrow_asset_yield
883 .get_total();
884 assert_eq!(
885 c.market.get_borrow_asset_available_to_borrow(),
886 BorrowAssetAmount::new(100_000_000)
887 );
888 let withdrawal = c.withdraw_supply_initial(supply_id.clone(), 50_000_000);
889 let WithdrawalAttempt::Full(withdrawal) = withdrawal else {
890 panic!("Expected full withdrawal");
891 };
892 assert_eq!(
893 c.market.borrow_asset_balance,
894 amount_repaid - BorrowAssetAmount::new(50_000_000),
895 );
896 c.print();
897 assert_eq!(
898 BorrowAssetAmount::new(50_000_000) + yield_amount,
899 63_500_000.into(),
900 );
901 assert_eq!(
902 c.market.get_borrow_asset_available_to_borrow(),
903 63_500_000.into(),
904 );
905
906 c.withdraw_supply_final(supply_id.clone(), &withdrawal);
908 c.print();
909 assert_eq!(
910 c.market.borrow_asset_balance,
911 amount_repaid - BorrowAssetAmount::new(50_000_000),
912 );
913 assert_eq!(c.market.borrow_asset_deposited_incoming.len(), 0);
914 assert_eq!(
915 c.market.get_borrow_asset_available_to_borrow(),
916 63_500_000.into(),
917 );
918
919 c.accumulate_yield(supply_id.clone());
920 c.print();
921 assert_eq!(
922 c.market.get_borrow_asset_available_to_borrow(),
923 63_500_000.into(),
924 );
925
926 c.collateralize(borrow_2_id.clone(), 200_000_000);
928 assert_eq!(
929 c.market.borrow_asset_balance,
930 amount_repaid - BorrowAssetAmount::new(50_000_000),
931 );
932 assert_eq!(
933 c.market.get_borrow_asset_available_to_borrow(),
934 63_500_000.into(),
935 );
936
937 c.print();
938
939 let initial = c.borrow_initial(borrow_2_id.clone(), second_borrow_amount);
941 assert_eq!(
942 c.market.borrow_asset_balance,
943 amount_repaid - BorrowAssetAmount::new(50_000_000) - second_borrow_amount,
944 );
945 assert_eq!(
946 c.market.get_borrow_asset_available_to_borrow(),
947 BorrowAssetAmount::new(63_500_000) - second_borrow_amount,
948 );
949
950 eprintln!("Borrow2 final");
951 eprintln!("{initial:?}");
952 eprintln!("{}", c.market.borrow_asset_borrowed_in_flight);
953
954 c.borrow_final(borrow_2_id.clone(), &initial);
956 assert_eq!(
957 c.market.borrow_asset_balance,
958 amount_repaid - BorrowAssetAmount::new(50_000_000) - second_borrow_amount,
959 );
960
961 c.print();
962 }
963
964 #[test]
965 fn balance_3() {
966 let mut configuration = configuration();
967 configuration.borrow_origination_fee = Fee::Flat(10_000_000.into());
968 configuration.borrow_interest_rate_strategy = InterestRateStrategy::zero();
969 configuration.yield_weights = YieldWeights::new_with_supply_weight(1);
970 configuration.borrow_asset_maximum_usage_ratio = Decimal::ONE;
971
972 let supply_id: AccountId = "supply.near".parse().unwrap();
973 let borrow_id: AccountId = "borrow.near".parse().unwrap();
974
975 let mut c = TestMarketController::new(configuration);
976
977 c.supply(supply_id.clone(), 100_000_000);
979 assert_eq!(c.market.borrow_asset_balance, 100_000_000.into());
980 assert_eq!(
981 c.market.get_borrow_asset_available_to_borrow(),
982 0.into(),
983 "still incoming, not yet active",
984 );
985
986 c.collateralize(borrow_id.clone(), 100_000_000);
988 assert_eq!(c.market.borrow_asset_balance, 100_000_000.into());
989 assert_eq!(
990 c.market.get_borrow_asset_available_to_borrow(),
991 100_000_000.into()
992 );
993
994 let initial = c.borrow_initial(borrow_id.clone(), 60_000_000);
996 c.print();
997 assert_eq!(c.market.borrow_asset_balance, 40_000_000.into());
998 assert_eq!(
999 c.market.get_borrow_asset_available_to_borrow(),
1000 40_000_000.into()
1001 );
1002
1003 c.borrow_final(borrow_id.clone(), &initial);
1005 assert_eq!(c.market.borrow_asset_borrowed, 60_000_000.into());
1006 assert_eq!(c.market.borrow_asset_balance, 40_000_000.into());
1007 assert_eq!(
1008 c.market.get_borrow_asset_available_to_borrow(),
1009 40_000_000.into()
1010 );
1011
1012 c.accumulate_yield(supply_id.clone())
1013 .borrow_asset_yield
1014 .get_total();
1015 assert_eq!(c.market.borrow_asset_borrowed, 60_000_000.into());
1016 assert_eq!(c.market.borrow_asset_balance, 40_000_000.into());
1017 assert_eq!(
1018 c.market.get_borrow_asset_available_to_borrow(),
1019 40_000_000.into()
1020 );
1021
1022 c.tick();
1023 c.tick();
1024 c.print();
1025
1026 assert_eq!(c.market.borrow_asset_borrowed, 60_000_000.into());
1027 assert_eq!(c.market.borrow_asset_balance, 40_000_000.into());
1028 assert_eq!(
1029 c.market.get_borrow_asset_available_to_borrow(),
1030 40_000_000.into()
1031 );
1032 }
1033}