From d7cae351be6bd773d87c84806787b5fe31b03aea Mon Sep 17 00:00:00 2001 From: 0xxgen1 <0xxgen@solend.fi> Date: Fri, 8 Nov 2024 12:03:27 +0000 Subject: [PATCH] - Fixed assertion over emode ltv thresholds - Refresh after adding the emode flag - Ability to toggle emode on and off - Simplified refresh emode - Fixed borrow weight when no emode pair - Do not fail when getting ltvs - Simplied get_ltvs --- contracts/suilend/sources/lending_market.move | 11 +- contracts/suilend/sources/obligation.move | 217 ++++++++---------- contracts/suilend/sources/reserve_config.move | 23 +- contracts/suilend/tests/obligation_tests.move | 6 +- 4 files changed, 116 insertions(+), 141 deletions(-) diff --git a/contracts/suilend/sources/lending_market.move b/contracts/suilend/sources/lending_market.move index a81cac2..f1f877a 100644 --- a/contracts/suilend/sources/lending_market.move +++ b/contracts/suilend/sources/lending_market.move @@ -378,10 +378,11 @@ module suilend::lending_market { coin::from_balance(receive_balance, ctx) } - /// Set emode for obligation - T is the deposit coin type - public fun set_emode

( + /// Set and unset emode for obligation - T is the deposit coin type + public fun toggle_emode

( lending_market: &mut LendingMarket

, obligation_owner_cap: &ObligationOwnerCap

, + turn_on: bool, clock: &Clock ) { assert!(lending_market.version == CURRENT_VERSION, EIncorrectVersion); @@ -391,7 +392,11 @@ module suilend::lending_market { obligation_owner_cap.obligation_id ); - obligation::set_emode( + if (obligation.is_emode() == turn_on) { + return; + }; + + obligation::toggle_emode( obligation, &mut lending_market.reserves, clock, diff --git a/contracts/suilend/sources/obligation.move b/contracts/suilend/sources/obligation.move index 7de0321..65f0f36 100644 --- a/contracts/suilend/sources/obligation.move +++ b/contracts/suilend/sources/obligation.move @@ -169,18 +169,22 @@ module suilend::obligation { } } - public(package) fun set_emode

( + public(package) fun toggle_emode

( obligation: &mut Obligation

, reserves: &mut vector>, clock: &Clock, ) { assert!(!is_emode(obligation), EEModeAlreadySet); - assert!(vector::length(&obligation.borrows) <= 1, EEModeNotValidWithCrossMargin); - assert!(vector::length(&obligation.deposits) <= 1, EEModeNotValidWithCrossMargin); - refresh(obligation, reserves, clock); + if (is_emode(obligation)) { + df::remove(&mut obligation.id, EModeFlag {}); + } else { + assert!(vector::length(&obligation.borrows) <= 1, EEModeNotValidWithCrossMargin); + assert!(vector::length(&obligation.deposits) <= 1, EEModeNotValidWithCrossMargin); + df::add(&mut obligation.id, EModeFlag {}, true); + }; - df::add(&mut obligation.id, EModeFlag {}, true); + refresh(obligation, reserves, clock); } /// update the obligation's borrowed amounts and health values. this is @@ -197,7 +201,6 @@ module suilend::obligation { let is_emode = is_emode(obligation); if (is_emode) { obligation.refresh_emode(reserves, is_emode, clock) - } else { while (i < vector::length(&obligation.deposits)) { let deposit = vector::borrow_mut(&mut obligation.deposits, i); @@ -299,117 +302,90 @@ module suilend::obligation { is_emode: bool, clock: &Clock ) { - let mut i = 0; - let mut deposited_value_usd = decimal::from(0); - let mut allowed_borrow_value_usd = decimal::from(0); - let mut unhealthy_borrow_value_usd = decimal::from(0); + // Deposit - while (i < vector::length(&obligation.deposits)) { - let deposit = vector::borrow_mut(&mut obligation.deposits, i); + if (obligation.deposits.length() == 0) { + return + }; - let deposit_reserve = vector::borrow_mut(reserves, deposit.reserve_array_index); + assert!(vector::length(&obligation.deposits) == 1, 0); - reserve::compound_interest(deposit_reserve, clock); - reserve::assert_price_is_fresh(deposit_reserve, clock); + let deposit = obligation.deposits.borrow_mut(0); + let deposit_reserve = vector::borrow_mut(reserves, deposit.reserve_array_index); - let market_value = reserve::ctoken_market_value( - deposit_reserve, - deposit.deposited_ctoken_amount - ); - let market_value_lower_bound = reserve::ctoken_market_value_lower_bound( - deposit_reserve, - deposit.deposited_ctoken_amount - ); - - deposit.market_value = market_value; - deposited_value_usd = add(deposited_value_usd, market_value); + reserve::compound_interest(deposit_reserve, clock); + reserve::assert_price_is_fresh(deposit_reserve, clock); - let (open_ltv, close_ltv) = get_ltvs( - obligation, - deposit_reserve, - is_emode, - ); + let market_value = reserve::ctoken_market_value( + deposit_reserve, + deposit.deposited_ctoken_amount + ); + let market_value_lower_bound = reserve::ctoken_market_value_lower_bound( + deposit_reserve, + deposit.deposited_ctoken_amount + ); - allowed_borrow_value_usd = add( - allowed_borrow_value_usd, - mul( - market_value_lower_bound, - open_ltv, - ), - ); + deposit.market_value = market_value; + obligation.deposited_value_usd = market_value; - unhealthy_borrow_value_usd = add( - unhealthy_borrow_value_usd, - mul( - market_value, - close_ltv, - ), - ); + let (open_ltv, close_ltv, is_emode_ltvs) = get_ltvs( + obligation, + deposit_reserve, + is_emode, + ); - i = i + 1; - }; + obligation.allowed_borrow_value_usd = mul( + market_value_lower_bound, + open_ltv, + ); - obligation.deposited_value_usd = deposited_value_usd; - obligation.allowed_borrow_value_usd = allowed_borrow_value_usd; - obligation.unhealthy_borrow_value_usd = unhealthy_borrow_value_usd; + obligation.unhealthy_borrow_value_usd = mul( + market_value, + close_ltv, + ); - let mut i = 0; - let mut unweighted_borrowed_value_usd = decimal::from(0); - let mut weighted_borrowed_value_usd = decimal::from(0); - let mut weighted_borrowed_value_upper_bound_usd = decimal::from(0); - let mut borrowing_isolated_asset = false; + // Borrow - while (i < vector::length(&obligation.borrows)) { - let borrow = vector::borrow_mut(&mut obligation.borrows, i); + if (obligation.borrows.length() == 0) { + return + }; - let borrow_reserve = vector::borrow_mut(reserves, borrow.reserve_array_index); - reserve::compound_interest(borrow_reserve, clock); - reserve::assert_price_is_fresh(borrow_reserve, clock); + assert!(vector::length(&obligation.borrows) == 1, 0); - compound_debt(borrow, borrow_reserve); + let borrow = obligation.borrows.borrow_mut(0); - let market_value = reserve::market_value(borrow_reserve, borrow.borrowed_amount); - let market_value_upper_bound = reserve::market_value_upper_bound( - borrow_reserve, - borrow.borrowed_amount - ); + let borrow_reserve = vector::borrow_mut(reserves, borrow.reserve_array_index); + reserve::compound_interest(borrow_reserve, clock); + reserve::assert_price_is_fresh(borrow_reserve, clock); - borrow.market_value = market_value; - unweighted_borrowed_value_usd = add(unweighted_borrowed_value_usd, market_value); + compound_debt(borrow, borrow_reserve); - let borrow_weight = if (is_emode) { - decimal::from(1) - } else { - borrow_weight(config(borrow_reserve)) - }; - - weighted_borrowed_value_usd = add( - weighted_borrowed_value_usd, - mul( - market_value, - borrow_weight - ) - ); - weighted_borrowed_value_upper_bound_usd = add( - weighted_borrowed_value_upper_bound_usd, - mul( - market_value_upper_bound, - borrow_weight - ) - ); + let market_value = reserve::market_value(borrow_reserve, borrow.borrowed_amount); + let market_value_upper_bound = reserve::market_value_upper_bound( + borrow_reserve, + borrow.borrowed_amount + ); - if (isolated(config(borrow_reserve))) { - borrowing_isolated_asset = true; - }; + borrow.market_value = market_value; + obligation.unweighted_borrowed_value_usd = market_value; - i = i + 1; + let borrow_weight = if (is_emode_ltvs) { + decimal::from(1) + } else { + borrow_weight(config(borrow_reserve)) }; - obligation.unweighted_borrowed_value_usd = unweighted_borrowed_value_usd; - obligation.weighted_borrowed_value_usd = weighted_borrowed_value_usd; - obligation.weighted_borrowed_value_upper_bound_usd = weighted_borrowed_value_upper_bound_usd; + obligation.weighted_borrowed_value_usd = mul( + market_value, + borrow_weight + ); - obligation.borrowing_isolated_asset = borrowing_isolated_asset; + obligation.weighted_borrowed_value_upper_bound_usd = mul( + market_value_upper_bound, + borrow_weight + ); + + obligation.borrowing_isolated_asset = isolated(config(borrow_reserve)); } /// Process a deposit action @@ -1375,41 +1351,34 @@ module suilend::obligation { obligation: &Obligation

, deposit_reserve: &Reserve

, is_emode: bool, - ): (Decimal, Decimal) { - if (is_emode) { - // check_normal_ltv_against_emode_ltvs - let borrow_reserve_index = get_single_borrow_array_reserve_if_any(obligation); + ): (Decimal, Decimal, bool) { + if (!is_emode) { + // When obligation normal mode + return (open_ltv(config(deposit_reserve)), close_ltv(config(deposit_reserve)), false) + }; - assert!( - reserve_config::has_emode_config(config(deposit_reserve)), - ENoEmodeConfigForGivenDepositReserve, - ); + let borrow_reserve_index = get_single_borrow_array_reserve_if_any(obligation); + if (borrow_reserve_index.is_none()) { + // When obligation is emode but there is no borrows + return (open_ltv(config(deposit_reserve)), close_ltv(config(deposit_reserve)), false) + }; - if (option::is_none(&borrow_reserve_index)) { - // When obligation is emode but there is no borrows - (open_ltv(config(deposit_reserve)), close_ltv(config(deposit_reserve))) - } else { - // When obligation is emode and there is a borrow - let emode_entry = reserve_config::try_get_emode_entry( - config(deposit_reserve), - option::borrow(&borrow_reserve_index) - ); + // When obligation is emode and there is a borrow + let emode_entry = reserve_config::try_get_emode_entry( + config(deposit_reserve), + option::borrow(&borrow_reserve_index) + ); - if (option::is_some(&emode_entry)) { - // When obligation is emode and there is a borrow AND there is a matching emode config - (open_ltv_emode(option::borrow(&emode_entry)), close_ltv_emode(option::borrow(&emode_entry))) - } else { - // When obligation is emode and there is a borrow BUT there is no matching emode config - (open_ltv(config(deposit_reserve)), close_ltv(config(deposit_reserve))) - } - } + if (option::is_some(&emode_entry)) { + // When obligation is emode and there is a borrow AND there is a matching emode config + (open_ltv_emode(option::borrow(&emode_entry)), close_ltv_emode(option::borrow(&emode_entry)), true) } else { - // When obligation normal mode - (open_ltv(config(deposit_reserve)), close_ltv(config(deposit_reserve))) + // When obligation is emode and there is a borrow BUT there is no matching emode config + (open_ltv(config(deposit_reserve)), close_ltv(config(deposit_reserve)), false) } } - fun is_emode

(obligation: &Obligation

): bool { + public(package) fun is_emode

(obligation: &Obligation

): bool { df::exists_(&obligation.id, EModeFlag {}) } diff --git a/contracts/suilend/sources/reserve_config.move b/contracts/suilend/sources/reserve_config.move index 189e462..5cb39ff 100644 --- a/contracts/suilend/sources/reserve_config.move +++ b/contracts/suilend/sources/reserve_config.move @@ -247,8 +247,8 @@ module suilend::reserve_config { while (vector::length(&keys) > 0) { let emode = vec_map::get(emode_ltvs, &vector::pop_back(&mut keys)); - assert!(config.open_ltv_pct < emode.open_ltv_pct, ENormalOpenLtvBetterThanEModeLtvs); - assert!(config.close_ltv_pct < emode.close_ltv_pct, ENormalCloseLtvBetterThanEModeLtvs); + assert!(config.open_ltv_pct <= emode.open_ltv_pct, ENormalOpenLtvBetterThanEModeLtvs); + assert!(config.close_ltv_pct <= emode.close_ltv_pct, ENormalCloseLtvBetterThanEModeLtvs); }; }; } @@ -575,15 +575,12 @@ module suilend::reserve_config { reserve_config: &ReserveConfig, reserve_array_index: &u64, ): bool { - let emode_config = get_emode_config(reserve_config); - vec_map::contains(emode_config, reserve_array_index) - } + if (!bag::contains(&reserve_config.additional_fields, EModeKey {})) { + return false + }; - public(package) fun get_emode_config( - reserve_config: &ReserveConfig, - ): &VecMap { - assert!(bag::contains(&reserve_config.additional_fields, EModeKey {}), ENoEModeConfigForDepositReserve); - bag::borrow(&reserve_config.additional_fields, EModeKey {}) + let emode_config: &VecMap = bag::borrow(&reserve_config.additional_fields, EModeKey {}); + vec_map::contains(emode_config, reserve_array_index) } public(package) fun open_ltv_emode( @@ -602,7 +599,11 @@ module suilend::reserve_config { reserve_config: &ReserveConfig, reserve_array_index: &u64, ): Option { - let emode_config = get_emode_config(reserve_config); + if (!bag::contains(&reserve_config.additional_fields, EModeKey {})) { + return option::none() + }; + + let emode_config = bag::borrow(&reserve_config.additional_fields, EModeKey {}); let has_pair = vec_map::contains(emode_config, reserve_array_index); if (has_pair) { diff --git a/contracts/suilend/tests/obligation_tests.move b/contracts/suilend/tests/obligation_tests.move index 9922d2a..8a2d5df 100644 --- a/contracts/suilend/tests/obligation_tests.move +++ b/contracts/suilend/tests/obligation_tests.move @@ -2306,8 +2306,8 @@ module suilend::obligation_tests { let new_config = builder.build(test_scenario::ctx(&mut scenario)); sui_reserve.update_reserve_config(new_config); - obligation.set_emode(&mut reserves, &clock); - obligation.set_emode(&mut reserves, &clock); + obligation.toggle_emode(&mut reserves, &clock); + obligation.toggle_emode(&mut reserves, &clock); sui::test_utils::destroy(lending_market_id); sui::test_utils::destroy(reserves); @@ -2360,7 +2360,7 @@ module suilend::obligation_tests { borrow(&mut obligation, get_reserve_mut(&mut reserves), &clock, 12_500_000); borrow(&mut obligation, get_reserve_mut(&mut reserves), &clock, 12_500_000); - obligation.set_emode(&mut reserves, &clock); + obligation.toggle_emode(&mut reserves, &clock); refresh(&mut obligation, &mut reserves, &clock);