Skip to content

Commit fa7d90e

Browse files
authored
No refresh on some withdraws (#51)
* don't check price freshness when withdrawing _if_ obligation has no borrows tests hot potato PR fixes * nit
1 parent ec92f21 commit fa7d90e

File tree

5 files changed

+351
-28
lines changed

5 files changed

+351
-28
lines changed

contracts/suilend/sources/lending_market.move

+12-5
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,9 @@ module suilend::lending_market {
401401
&mut lending_market.obligations,
402402
obligation_owner_cap.obligation_id,
403403
);
404-
obligation::refresh<P>(obligation, &mut lending_market.reserves, clock);
404+
405+
let exist_stale_oracles = obligation::refresh<P>(obligation, &mut lending_market.reserves, clock);
406+
obligation::assert_no_stale_oracles(exist_stale_oracles);
405407

406408
let reserve = vector::borrow_mut(&mut lending_market.reserves, reserve_array_index);
407409
assert!(reserve::coin_type(reserve) == type_name::get<T>(), EWrongType);
@@ -478,7 +480,8 @@ module suilend::lending_market {
478480
&mut lending_market.obligations,
479481
obligation_owner_cap.obligation_id,
480482
);
481-
obligation::refresh<P>(obligation, &mut lending_market.reserves, clock);
483+
484+
let exist_stale_oracles = obligation::refresh<P>(obligation, &mut lending_market.reserves, clock);
482485

483486
let reserve = vector::borrow_mut(&mut lending_market.reserves, reserve_array_index);
484487
assert!(reserve::coin_type(reserve) == type_name::get<T>(), EWrongType);
@@ -488,7 +491,7 @@ module suilend::lending_market {
488491
max_withdraw_amount<P>(lending_market.rate_limiter, obligation, reserve, clock);
489492
};
490493

491-
obligation::withdraw<P>(obligation, reserve, clock, amount);
494+
obligation::withdraw<P>(obligation, reserve, clock, amount, exist_stale_oracles);
492495

493496
event::emit(WithdrawEvent {
494497
lending_market_id,
@@ -522,7 +525,9 @@ module suilend::lending_market {
522525
&mut lending_market.obligations,
523526
obligation_id,
524527
);
525-
obligation::refresh<P>(obligation, &mut lending_market.reserves, clock);
528+
529+
let exist_stale_oracles = obligation::refresh<P>(obligation, &mut lending_market.reserves, clock);
530+
obligation::assert_no_stale_oracles(exist_stale_oracles);
526531

527532
let (withdraw_ctoken_amount, required_repay_amount) = obligation::liquidate<P>(
528533
obligation,
@@ -644,7 +649,9 @@ module suilend::lending_market {
644649
&mut lending_market.obligations,
645650
obligation_id,
646651
);
647-
obligation::refresh<P>(obligation, &mut lending_market.reserves, clock);
652+
653+
let exist_stale_oracles = obligation::refresh<P>(obligation, &mut lending_market.reserves, clock);
654+
obligation::assert_no_stale_oracles(exist_stale_oracles);
648655

649656
let reserve = vector::borrow_mut(&mut lending_market.reserves, reserve_array_index);
650657
assert!(reserve::coin_type(reserve) == type_name::get<T>(), EWrongType);

contracts/suilend/sources/obligation.move

+32-3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ module suilend::obligation {
3939
const ETooManyBorrows: u64 = 6;
4040
const EObligationIsNotForgivable: u64 = 7;
4141
const ECannotDepositAndBorrowSameAsset: u64 = 8;
42+
const EOraclesAreStale: u64 = 9;
4243

4344
// === Constants ===
4445
const CLOSE_FACTOR_PCT: u8 = 20;
@@ -100,6 +101,9 @@ module suilend::obligation {
100101
user_reward_manager_index: u64,
101102
}
102103

104+
// hot potato. used by obligation::refresh to indicate that prices are stale.
105+
public struct ExistStaleOracles {}
106+
103107
// === Events ===
104108
public struct ObligationDataEvent has copy, drop {
105109
lending_market_id: address,
@@ -167,7 +171,9 @@ module suilend::obligation {
167171
obligation: &mut Obligation<P>,
168172
reserves: &mut vector<Reserve<P>>,
169173
clock: &Clock,
170-
) {
174+
): Option<ExistStaleOracles> {
175+
let mut exist_stale_oracles = false;
176+
171177
let mut i = 0;
172178
let mut deposited_value_usd = decimal::from(0);
173179
let mut allowed_borrow_value_usd = decimal::from(0);
@@ -179,7 +185,10 @@ module suilend::obligation {
179185
let deposit_reserve = vector::borrow_mut(reserves, deposit.reserve_array_index);
180186

181187
reserve::compound_interest(deposit_reserve, clock);
182-
reserve::assert_price_is_fresh(deposit_reserve, clock);
188+
189+
if (!reserve::is_price_fresh(deposit_reserve, clock)) {
190+
exist_stale_oracles = true;
191+
};
183192

184193
let market_value = reserve::ctoken_market_value(
185194
deposit_reserve,
@@ -227,7 +236,9 @@ module suilend::obligation {
227236

228237
let borrow_reserve = vector::borrow_mut(reserves, borrow.reserve_array_index);
229238
reserve::compound_interest(borrow_reserve, clock);
230-
reserve::assert_price_is_fresh(borrow_reserve, clock);
239+
if (!reserve::is_price_fresh(borrow_reserve, clock)) {
240+
exist_stale_oracles = true;
241+
};
231242

232243
compound_debt(borrow, borrow_reserve);
233244

@@ -269,6 +280,12 @@ module suilend::obligation {
269280
weighted_borrowed_value_upper_bound_usd;
270281

271282
obligation.borrowing_isolated_asset = borrowing_isolated_asset;
283+
284+
if (exist_stale_oracles) {
285+
return option::some(ExistStaleOracles {})
286+
};
287+
288+
option::none()
272289
}
273290

274291
/// Process a deposit action
@@ -492,7 +509,14 @@ module suilend::obligation {
492509
reserve: &mut Reserve<P>,
493510
clock: &Clock,
494511
ctoken_amount: u64,
512+
stale_oracles: Option<ExistStaleOracles>,
495513
) {
514+
if (stale_oracles.is_some() && vector::is_empty(&obligation.borrows)) {
515+
let ExistStaleOracles {} = option::destroy_some(stale_oracles);
516+
} else {
517+
assert_no_stale_oracles(stale_oracles);
518+
};
519+
496520
withdraw_unchecked(obligation, reserve, clock, ctoken_amount);
497521

498522
assert!(is_healthy(obligation), EObligationIsNotHealthy);
@@ -834,6 +858,11 @@ module suilend::obligation {
834858
)
835859
}
836860

861+
public(package) fun assert_no_stale_oracles(exist_stale_oracles: Option<ExistStaleOracles>) {
862+
assert!(option::is_none(&exist_stale_oracles), EOraclesAreStale);
863+
option::destroy_none(exist_stale_oracles);
864+
}
865+
837866
public(package) fun zero_out_rewards_if_looped<P>(
838867
obligation: &mut Obligation<P>,
839868
reserves: &mut vector<Reserve<P>>,

contracts/suilend/sources/reserve.move

+6-4
Original file line numberDiff line numberDiff line change
@@ -236,11 +236,13 @@ module suilend::reserve {
236236

237237
// make sure we are using the latest published price on sui
238238
public fun assert_price_is_fresh<P>(reserve: &Reserve<P>, clock: &Clock) {
239+
assert!(is_price_fresh(reserve, clock), EPriceStale);
240+
}
241+
242+
public(package) fun is_price_fresh<P>(reserve: &Reserve<P>, clock: &Clock): bool {
239243
let cur_time_s = clock::timestamp_ms(clock) / 1000;
240-
assert!(
241-
cur_time_s - reserve.price_last_update_timestamp_s <= PRICE_STALENESS_THRESHOLD_S,
242-
EPriceStale
243-
);
244+
245+
cur_time_s - reserve.price_last_update_timestamp_s <= PRICE_STALENESS_THRESHOLD_S
244246
}
245247

246248
// if SUI = $1, this returns decimal::from(1).

0 commit comments

Comments
 (0)