diff --git a/.gitignore b/.gitignore index 3d2b29e..4b41c38 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ contracts/suilend/build suilend-cli/node_modules -contracts/oracle/build \ No newline at end of file +contracts/oracle/build +contracts/suilend/build diff --git a/contracts/suilend/sources/cell.move b/contracts/suilend/sources/cell.move index e364f06..5f85115 100644 --- a/contracts/suilend/sources/cell.move +++ b/contracts/suilend/sources/cell.move @@ -16,6 +16,10 @@ module suilend::cell { public fun get(cell: &Cell): &Element { option::borrow(&cell.element) } + + public fun get_mut(cell: &mut Cell): &mut Element { + option::borrow_mut(&mut cell.element) + } public fun destroy(cell: Cell): Element { let Cell { element } = cell; diff --git a/contracts/suilend/sources/lending_market.move b/contracts/suilend/sources/lending_market.move index 875ec74..ccde192 100644 --- a/contracts/suilend/sources/lending_market.move +++ b/contracts/suilend/sources/lending_market.move @@ -356,7 +356,9 @@ module suilend::lending_market { let (receive_balance, borrow_amount_with_fees) = reserve::borrow_liquidity(reserve, amount); let origination_fee_amount = borrow_amount_with_fees - balance::value(&receive_balance); - obligation::borrow

(obligation, reserve, clock, borrow_amount_with_fees); + obligation::borrow

(obligation, &mut lending_market.reserves, reserve_array_index, clock, borrow_amount_with_fees); + + let reserve = vector::borrow_mut(&mut lending_market.reserves, reserve_array_index); let borrow_value = reserve::market_value_upper_bound(reserve, decimal::from(borrow_amount_with_fees)); rate_limiter::process_qty( @@ -377,6 +379,26 @@ module suilend::lending_market { obligation::zero_out_rewards_if_looped(obligation, &mut lending_market.reserves, clock); coin::from_balance(receive_balance, ctx) } + + /// Set emode for obligation - T is the deposit coin type + public fun set_emode

( + lending_market: &mut LendingMarket

, + obligation_owner_cap: &ObligationOwnerCap

, + clock: &Clock + ) { + assert!(lending_market.version == CURRENT_VERSION, EIncorrectVersion); + + let obligation = object_table::borrow_mut( + &mut lending_market.obligations, + obligation_owner_cap.obligation_id + ); + + obligation::set_emode( + obligation, + &mut lending_market.reserves, + clock, + ); + } public fun withdraw_ctokens( lending_market: &mut LendingMarket

, diff --git a/contracts/suilend/sources/obligation.move b/contracts/suilend/sources/obligation.move index b536bd5..45a96bf 100644 --- a/contracts/suilend/sources/obligation.move +++ b/contracts/suilend/sources/obligation.move @@ -8,14 +8,18 @@ module suilend::obligation { use sui::tx_context::{TxContext}; use suilend::reserve::{Self, Reserve, config}; use suilend::reserve_config::{ + Self, open_ltv, close_ltv, borrow_weight, liquidation_bonus, protocol_liquidation_fee, isolated, + open_ltv_emode, + close_ltv_emode, }; use sui::clock::{Clock}; + use sui::dynamic_field::{Self as df}; use suilend::decimal::{Self, Decimal, mul, add, sub, div, gt, lt, min, floor, le, eq, saturating_sub}; use suilend::liquidity_mining::{Self, UserRewardManager, PoolRewardManager}; @@ -29,12 +33,20 @@ module suilend::obligation { const ETooManyBorrows: u64 = 6; const EObligationIsNotForgivable: u64 = 7; const ECannotDepositAndBorrowSameAsset: u64 = 8; + const EEModeNotValidWithCrossMargin: u64 = 9; + const ENoEmodeConfigForGivenDepositReserve : u64 = 10; + const EInvalidEModeDeposit: u64 = 13; + const EInvalidEModeBorrow: u64 = 14; + const EEModeAlreadySet: u64 = 15; // === Constants === const CLOSE_FACTOR_PCT: u8 = 20; const MAX_DEPOSITS: u64 = 5; const MAX_BORROWS: u64 = 5; + // === Dynamic Field Keys === + public struct EModeFlag has store, copy, drop {} + // === public structs === public struct Obligation has key, store { id: UID, @@ -157,6 +169,30 @@ module suilend::obligation { } } + public(package) fun set_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); + + if (vector::length(&obligation.deposits) == 1) { + let reserve_array_index = vector::borrow(&obligation.deposits, 0).reserve_array_index; + let deposit_reserve = vector::borrow(reserves, reserve_array_index); + + assert!( + reserve_config::has_emode_config(config(deposit_reserve)), + ENoEmodeConfigForGivenDepositReserve, + ); + + refresh(obligation, reserves, clock); + + }; + + df::add(&mut obligation.id, EModeFlag {}, true); + } /// update the obligation's borrowed amounts and health values. this is /// called by the lending market prior to any borrow, withdraw, or liquidate operation. @@ -169,6 +205,7 @@ module suilend::obligation { 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); + let is_emode = is_emode(obligation); while (i < vector::length(&obligation.deposits)) { let deposit = vector::borrow_mut(&mut obligation.deposits, i); @@ -189,19 +226,27 @@ module suilend::obligation { deposit.market_value = market_value; deposited_value_usd = add(deposited_value_usd, market_value); + + let (open_ltv, close_ltv) = get_ltvs( + obligation, + deposit_reserve, + is_emode, + ); + allowed_borrow_value_usd = add( allowed_borrow_value_usd, mul( market_value_lower_bound, - open_ltv(config(deposit_reserve)) - ) + open_ltv, + ), ); + unhealthy_borrow_value_usd = add( unhealthy_borrow_value_usd, mul( market_value, - close_ltv(config(deposit_reserve)) - ) + close_ltv, + ), ); i = i + 1; @@ -230,22 +275,29 @@ module suilend::obligation { let market_value_upper_bound = reserve::market_value_upper_bound( borrow_reserve, borrow.borrowed_amount - ); + ); borrow.market_value = market_value; unweighted_borrowed_value_usd = add(unweighted_borrowed_value_usd, market_value); + + 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(config(borrow_reserve)) + borrow_weight ) ); weighted_borrowed_value_upper_bound_usd = add( weighted_borrowed_value_upper_bound_usd, mul( market_value_upper_bound, - borrow_weight(config(borrow_reserve)) + borrow_weight ) ); @@ -276,6 +328,13 @@ module suilend::obligation { let borrow_index = find_borrow_index(obligation, reserve); assert!(borrow_index == vector::length(&obligation.borrows), ECannotDepositAndBorrowSameAsset); + let is_emode = is_emode(obligation); + let (open_ltv, close_ltv) = get_ltvs( + obligation, + reserve, + is_emode, + ); + let deposit = vector::borrow_mut(&mut obligation.deposits, deposit_index); deposit.deposited_ctoken_amount = deposit.deposited_ctoken_amount + ctoken_amount; @@ -291,14 +350,14 @@ module suilend::obligation { obligation.allowed_borrow_value_usd, mul( reserve::ctoken_market_value_lower_bound(reserve, ctoken_amount), - open_ltv(config(reserve)) + open_ltv, ) ); obligation.unhealthy_borrow_value_usd = add( obligation.unhealthy_borrow_value_usd, mul( deposit_value, - close_ltv(config(reserve)) + close_ltv, ) ); @@ -309,28 +368,46 @@ module suilend::obligation { deposit.deposited_ctoken_amount, clock ); + + if (is_emode) { + assert!(vector::length(&obligation.deposits) == 1, EIsolatedAssetViolation); + let target_reserve_index = emode_deposit_reserve_array_index(obligation); + let deposit = vector::borrow(&obligation.deposits, deposit_index); + assert!(deposit.reserve_array_index == target_reserve_index, EInvalidEModeDeposit); + }; + log_obligation_data(obligation); } /// Process a borrow action. Makes sure that the obligation is healthy after the borrow. public(package) fun borrow

( obligation: &mut Obligation

, - reserve: &mut Reserve

, + reserves: &mut vector>, + borrow_reserve_array_index: u64, clock: &Clock, amount: u64, ) { - let borrow_index = find_or_add_borrow(obligation, reserve, clock); + let borrow_reserve = vector::borrow_mut(reserves, borrow_reserve_array_index); + + let borrow_index = find_or_add_borrow(obligation, borrow_reserve, clock); assert!(vector::length(&obligation.borrows) <= MAX_BORROWS, ETooManyBorrows); + + let is_emode = is_emode(obligation); + let borrow_weight = if (is_emode) { + decimal::from(1) + } else { + borrow_weight(config(borrow_reserve)) + }; - let deposit_index = find_deposit_index(obligation, reserve); + let deposit_index = find_deposit_index(obligation, borrow_reserve); assert!(deposit_index == vector::length(&obligation.deposits), ECannotDepositAndBorrowSameAsset); let borrow = vector::borrow_mut(&mut obligation.borrows, borrow_index); borrow.borrowed_amount = add(borrow.borrowed_amount, decimal::from(amount)); // update health values - let borrow_market_value = reserve::market_value(reserve, decimal::from(amount)); - let borrow_market_value_upper_bound = reserve::market_value_upper_bound(reserve, decimal::from(amount)); + let borrow_market_value = reserve::market_value(borrow_reserve, decimal::from(amount)); + let borrow_market_value_upper_bound = reserve::market_value_upper_bound(borrow_reserve, decimal::from(amount)); borrow.market_value = add(borrow.market_value, borrow_market_value); obligation.unweighted_borrowed_value_usd = add( @@ -339,16 +416,16 @@ module suilend::obligation { ); obligation.weighted_borrowed_value_usd = add( obligation.weighted_borrowed_value_usd, - mul(borrow_market_value, borrow_weight(config(reserve))) + mul(borrow_market_value, borrow_weight) ); obligation.weighted_borrowed_value_upper_bound_usd = add( obligation.weighted_borrowed_value_upper_bound_usd, - mul(borrow_market_value_upper_bound, borrow_weight(config(reserve))) + mul(borrow_market_value_upper_bound, borrow_weight) ); let user_reward_manager = vector::borrow_mut(&mut obligation.user_reward_managers, borrow.user_reward_manager_index); liquidity_mining::change_user_reward_manager_share( - reserve::borrows_pool_reward_manager_mut(reserve), + reserve::borrows_pool_reward_manager_mut(borrow_reserve), user_reward_manager, liability_shares(borrow), clock @@ -356,9 +433,40 @@ module suilend::obligation { assert!(is_healthy(obligation), EObligationIsNotHealthy); - if (isolated(config(reserve)) || obligation.borrowing_isolated_asset) { + if (isolated(config(borrow_reserve)) || obligation.borrowing_isolated_asset || is_emode) { assert!(vector::length(&obligation.borrows) == 1, EIsolatedAssetViolation); }; + + if (is_emode) { + let target_reserve_index = emode_borrow_reserve_array_index(obligation); + let borrow = vector::borrow(&obligation.borrows, borrow_index); + assert!(borrow.reserve_array_index == target_reserve_index, EInvalidEModeBorrow); + + let emode_deposit_reserve_array_index = emode_deposit_reserve_array_index(obligation); + + let deposit_reserve = vector::borrow(reserves, emode_deposit_reserve_array_index); + + let (open_ltv, close_ltv) = get_ltvs( + obligation, + deposit_reserve, + is_emode, + ); + + let deposit_index = option::destroy_some(get_single_deposit_array_reserve_if_any(obligation)); + + let deposit = vector::borrow_mut(&mut obligation.deposits, deposit_index); + let deposit_value = reserve::ctoken_market_value(deposit_reserve, deposit.deposited_ctoken_amount); + + obligation.allowed_borrow_value_usd = mul( + reserve::ctoken_market_value_lower_bound(deposit_reserve, deposit.deposited_ctoken_amount), + open_ltv, + ); + obligation.unhealthy_borrow_value_usd = mul( + deposit_value, + close_ltv, + ); + }; + log_obligation_data(obligation); } @@ -369,6 +477,12 @@ module suilend::obligation { clock: &Clock, max_repay_amount: Decimal, ): Decimal { + let borrow_weight = if (is_emode(obligation)) { + decimal::from(1) + } else { + borrow_weight(config(reserve)) + }; + let borrow_index = find_borrow_index(obligation, reserve); assert!(borrow_index < vector::length(&obligation.borrows), EBorrowNotFound); let borrow = vector::borrow_mut(&mut obligation.borrows, borrow_index); @@ -397,11 +511,11 @@ module suilend::obligation { ); obligation.weighted_borrowed_value_usd = saturating_sub( obligation.weighted_borrowed_value_usd, - mul(repay_value, borrow_weight(config(reserve))) + mul(repay_value, borrow_weight) ); obligation.weighted_borrowed_value_upper_bound_usd = saturating_sub( obligation.weighted_borrowed_value_upper_bound_usd, - mul(repay_value_upper_bound, borrow_weight(config(reserve))) + mul(repay_value_upper_bound, borrow_weight) ); } else { @@ -416,11 +530,11 @@ module suilend::obligation { ); obligation.weighted_borrowed_value_usd = add( obligation.weighted_borrowed_value_usd, - mul(additional_borrow_value, borrow_weight(config(reserve))) + mul(additional_borrow_value, borrow_weight) ); obligation.weighted_borrowed_value_upper_bound_usd = add( obligation.weighted_borrowed_value_upper_bound_usd, - mul(additional_borrow_value_upper_bound, borrow_weight(config(reserve))) + mul(additional_borrow_value_upper_bound, borrow_weight) ); }; @@ -970,6 +1084,14 @@ module suilend::obligation { ) { let deposit_index = find_deposit_index(obligation, reserve); assert!(deposit_index < vector::length(&obligation.deposits), EDepositNotFound); + + let is_emode = is_emode(obligation); + let (open_ltv, close_ltv) = get_ltvs( + obligation, + reserve, + is_emode, + ); + let deposit = vector::borrow_mut(&mut obligation.deposits, deposit_index); let withdraw_market_value = reserve::ctoken_market_value(reserve, ctoken_amount); @@ -984,14 +1106,14 @@ module suilend::obligation { mul( // need to use lower bound to keep calculation consistent reserve::ctoken_market_value_lower_bound(reserve, ctoken_amount), - open_ltv(config(reserve)) + open_ltv ) ); obligation.unhealthy_borrow_value_usd = sub( obligation.unhealthy_borrow_value_usd, mul( withdraw_market_value, - close_ltv(config(reserve)) + close_ltv ) ); @@ -1221,4 +1343,78 @@ module suilend::obligation { user_reward_manager_index } } + + fun get_ltvs

( + 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); + + assert!( + reserve_config::has_emode_config(config(deposit_reserve)), + ENoEmodeConfigForGivenDepositReserve, + ); + + 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_data = reserve_config::try_get_emode_data( + config(deposit_reserve), + option::borrow(&borrow_reserve_index) + ); + + if (option::is_some(&emode_data)) { + // When obligation is emode and there is a borrow AND there is a matching emode config + (open_ltv_emode(option::borrow(&emode_data)), close_ltv_emode(option::borrow(&emode_data))) + } 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))) + } + } + } else { + // When obligation normal mode + (open_ltv(config(deposit_reserve)), close_ltv(config(deposit_reserve))) + } + } + + fun is_emode

(obligation: &Obligation

): bool { + df::exists_(&obligation.id, EModeFlag {}) + } + + fun emode_borrow_reserve_array_index

(obligation: &Obligation

): u64 { + let borrow_reserve_index = get_single_borrow_array_reserve_if_any(obligation); + *option::borrow(&borrow_reserve_index) + } + + fun emode_deposit_reserve_array_index

(obligation: &Obligation

): u64 { + let deposit_reserve_index = get_single_deposit_array_reserve_if_any(obligation); + *option::borrow(&deposit_reserve_index) + } + + fun get_single_borrow_array_reserve_if_any

( + obligation: &Obligation

, + ): Option { + let len = vector::length(&obligation.borrows); + if (len != 1) { + option::none() + } else { + option::some(vector::borrow(&obligation.borrows, 0).reserve_array_index) + } + } + + fun get_single_deposit_array_reserve_if_any

( + obligation: &Obligation

, + ): Option { + let len = vector::length(&obligation.deposits); + if (len != 1) { + option::none() + } else { + option::some(vector::borrow(&obligation.deposits, 0).reserve_array_index) + } + } } diff --git a/contracts/suilend/sources/reserve.move b/contracts/suilend/sources/reserve.move index 929e043..9ba3b3d 100644 --- a/contracts/suilend/sources/reserve.move +++ b/contracts/suilend/sources/reserve.move @@ -1031,4 +1031,29 @@ module suilend::reserve { reserve } + + #[test_only] + public fun set_emode_for_pair

( + reserve: &mut Reserve

, + reserve_array_index: u64, + open_ltv_pct: u8, + close_ltv_pct: u8, + ctx: &mut TxContext, + ) { + let config = cell::get_mut(&mut reserve.config); + + let mut builder = reserve_config::from(config, ctx); + + let emode_data = reserve_config::create_emode_data( + reserve_array_index, + open_ltv_pct, + close_ltv_pct, + ); + + reserve_config::set_emode_ltv_for_borrow(&mut builder, emode_data); + + reserve_config::destroy( + cell::set(&mut reserve.config, reserve_config::build(builder, ctx)) + ); + } } diff --git a/contracts/suilend/sources/reserve_config.move b/contracts/suilend/sources/reserve_config.move index cd13f7e..612264c 100644 --- a/contracts/suilend/sources/reserve_config.move +++ b/contracts/suilend/sources/reserve_config.move @@ -4,12 +4,19 @@ module suilend::reserve_config { use suilend::decimal::{Decimal, Self, add, sub, mul, div, ge, le}; use sui::tx_context::{TxContext}; use sui::bag::{Self, Bag}; + use std::option::{Self, Option}; + use sui::vec_map::{Self, VecMap}; #[test_only] use sui::test_scenario::{Self}; const EInvalidReserveConfig: u64 = 0; const EInvalidUtil: u64 = 1; + const ENoEModeConfigForDepositReserve: u64 = 2; + const ENormalOpenLtvBetterThanEModeLtvs: u64 = 3; + const ENormalCloseLtvBetterThanEModeLtvs: u64 = 4; + + public struct EModeKey has copy, store, drop {} public struct ReserveConfig has store { // risk params @@ -57,6 +64,13 @@ module suilend::reserve_config { fields: Bag } + public struct EModeData has store, copy, drop { + // Corresponding borrow reserve index + reserve_array_index: u64, + open_ltv_pct: u8, + close_ltv_pct: u8, + } + public fun create_reserve_config( open_ltv_pct: u8, close_ltv_pct: u8, @@ -78,7 +92,65 @@ module suilend::reserve_config { close_attributed_borrow_limit_usd: u64, ctx: &mut TxContext ): ReserveConfig { - let config = ReserveConfig { + create_reserve_config_( + open_ltv_pct, + close_ltv_pct, + max_close_ltv_pct, + borrow_weight_bps, + deposit_limit, + borrow_limit, + liquidation_bonus_bps, + max_liquidation_bonus_bps, + deposit_limit_usd, + borrow_limit_usd, + borrow_fee_bps, + spread_fee_bps, + protocol_liquidation_fee_bps, + interest_rate_utils, + interest_rate_aprs, + isolated, + open_attributed_borrow_limit_usd, + close_attributed_borrow_limit_usd, + option::none(), + ctx, + ) + } + + public fun create_emode_data( + reserve_array_index: u64, + open_ltv_pct: u8, + close_ltv_pct: u8, + ): EModeData { + EModeData { + reserve_array_index, + open_ltv_pct, + close_ltv_pct, + } + } + + fun create_reserve_config_( + open_ltv_pct: u8, + close_ltv_pct: u8, + max_close_ltv_pct: u8, + borrow_weight_bps: u64, + deposit_limit: u64, + borrow_limit: u64, + liquidation_bonus_bps: u64, + max_liquidation_bonus_bps: u64, + deposit_limit_usd: u64, + borrow_limit_usd: u64, + borrow_fee_bps: u64, + spread_fee_bps: u64, + protocol_liquidation_fee_bps: u64, + interest_rate_utils: vector, + interest_rate_aprs: vector, + isolated: bool, + open_attributed_borrow_limit_usd: u64, + close_attributed_borrow_limit_usd: u64, + emode_config: Option>, + ctx: &mut TxContext + ): ReserveConfig { + let mut config = ReserveConfig { open_ltv_pct, close_ltv_pct, max_close_ltv_pct, @@ -100,6 +172,14 @@ module suilend::reserve_config { additional_fields: bag::new(ctx) }; + if (option::is_some(&emode_config)) { + bag::add( + &mut config.additional_fields, + EModeKey {}, + option::destroy_some(emode_config), + ); + }; + validate_reserve_config(&config); config } @@ -132,6 +212,7 @@ module suilend::reserve_config { ); validate_utils_and_aprs(&config.interest_rate_utils, &config.interest_rate_aprs); + validate_emode_ltvs(config); } fun validate_utils_and_aprs(utils: &vector, aprs: &vector) { @@ -156,6 +237,21 @@ module suilend::reserve_config { i = i + 1; } } + + fun validate_emode_ltvs(config: &ReserveConfig) { + if (bag::contains(&config.additional_fields, EModeKey {})) { + let emode_ltvs: &VecMap = bag::borrow(&config.additional_fields, EModeKey {}); + + let mut keys = vec_map::keys(emode_ltvs); + + 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); + }; + }; + } public fun open_ltv(config: &ReserveConfig): Decimal { decimal::from_percent(config.open_ltv_pct) @@ -264,9 +360,18 @@ module suilend::reserve_config { isolated: _, open_attributed_borrow_limit_usd: _, close_attributed_borrow_limit_usd: _, - additional_fields + mut additional_fields } = config; + let has_emode_field = bag::contains(&additional_fields, EModeKey {}); + + if (has_emode_field) { + let _emode_config: VecMap = bag::remove( + &mut additional_fields, + EModeKey {}, + ); + }; + bag::destroy_empty(additional_fields); } @@ -293,6 +398,10 @@ module suilend::reserve_config { set_open_attributed_borrow_limit_usd(&mut builder, config.open_attributed_borrow_limit_usd); set_close_attributed_borrow_limit_usd(&mut builder, config.close_attributed_borrow_limit_usd); + if (bag::contains(&config.additional_fields, EModeKey {})) { + set_emode_ltv_for_borrow(&mut builder, *bag::borrow(&config.additional_fields, EModeKey {})) + }; + builder } @@ -377,8 +486,35 @@ module suilend::reserve_config { set(builder, b"close_attributed_borrow_limit_usd", close_attributed_borrow_limit_usd); } + public fun set_emode_ltv_for_borrow( + builder : &mut ReserveConfigBuilder, + emode_data: EModeData + ) { + if (bag::contains(&builder.fields, b"emode")) { + let emode_config: &mut VecMap = bag::borrow_mut(&mut builder.fields, b"emode"); + if (vec_map::contains(emode_config, &emode_data.reserve_array_index)) { + let data: &mut EModeData = bag::borrow_mut(&mut builder.fields, emode_data.reserve_array_index); + *data = emode_data; + } else { + vec_map::insert(emode_config, emode_data.reserve_array_index, emode_data) + }; + } else { + let mut emode_config: VecMap = vec_map::empty(); + vec_map::insert(&mut emode_config, emode_data.reserve_array_index, emode_data); + + bag::add(&mut builder.fields, b"emode", emode_config); + } + } + public fun build(mut builder: ReserveConfigBuilder, tx_context: &mut TxContext): ReserveConfig { - let config = create_reserve_config( + + let emode_config = if (bag::contains(&builder.fields, b"emode")) { + option::some(bag::remove(&mut builder.fields, b"emode")) + } else { + option::none() + }; + + let config = create_reserve_config_( bag::remove(&mut builder.fields, b"open_ltv_pct"), bag::remove(&mut builder.fields, b"close_ltv_pct"), bag::remove(&mut builder.fields, b"max_close_ltv_pct"), @@ -397,6 +533,7 @@ module suilend::reserve_config { bag::remove(&mut builder.fields, b"isolated"), bag::remove(&mut builder.fields, b"open_attributed_borrow_limit_usd"), bag::remove(&mut builder.fields, b"close_attributed_borrow_limit_usd"), + emode_config, tx_context ); @@ -406,6 +543,55 @@ module suilend::reserve_config { } + // === eMode Package Functions == + + public(package) fun check_has_emode_pair( + reserve_config: &ReserveConfig, + reserve_array_index: &u64, + ): bool { + let emode_config = get_emode_config_checked(reserve_config); + vec_map::contains(emode_config, reserve_array_index) + } + + public(package) fun get_emode_config_checked( + reserve_config: &ReserveConfig, + ): &VecMap { + assert!(bag::contains(&reserve_config.additional_fields, EModeKey {}), ENoEModeConfigForDepositReserve); + bag::borrow(&reserve_config.additional_fields, EModeKey {}) + } + + public(package) fun has_emode_config( + reserve_config: &ReserveConfig, + ): bool { + bag::contains(&reserve_config.additional_fields, EModeKey {}) + } + + public(package) fun open_ltv_emode( + emode_data: &EModeData, + ): Decimal { + decimal::from_percent(emode_data.open_ltv_pct) + } + + public(package) fun close_ltv_emode( + emode_data: &EModeData, + ): Decimal { + decimal::from_percent(emode_data.close_ltv_pct) + } + + public(package) fun try_get_emode_data( + reserve_config: &ReserveConfig, + reserve_array_index: &u64, + ): Option { + let emode_config = get_emode_config_checked(reserve_config); + let has_pair = vec_map::contains(emode_config, reserve_array_index); + + if (has_pair) { + vec_map::try_get(emode_config, reserve_array_index) + } else { + option::none() + } + } + // === Tests == #[test] fun test_calculate_apr() { @@ -587,4 +773,113 @@ module suilend::reserve_config { config } + #[test] + fun test_emode_reserve_config() { + use sui::test_utils::assert_eq; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + + let mut utils = vector::empty(); + vector::push_back(&mut utils, 0); + vector::push_back(&mut utils, 100); + + let mut aprs = vector::empty(); + vector::push_back(&mut aprs, 0); + vector::push_back(&mut aprs, 100); + + let emode_ltvs = create_emode_data( + 1, + 60, + 80, + ); + let mut emode_config = vec_map::empty(); + vec_map::insert(&mut emode_config, 1, emode_ltvs); + + let config = create_reserve_config_( + 10, + 10, + 10, + 10_000, + 1, + 1, + 5, + 5, + 100000, + 100000, + 10, + 2000, + 30, + utils, + aprs, + false, + 0, + 0, + option::some(emode_config), + test_scenario::ctx(&mut scenario) + ); + + check_has_emode_pair(&config, &1); + + assert!(has_emode_config(&config), 0); + let emode_data = option::destroy_some(try_get_emode_data(&config, &1)); + assert_eq(open_ltv_emode(&emode_data), decimal::from_percent(60)); + assert_eq(close_ltv_emode(&emode_data), decimal::from_percent(80)); + + destroy(config); + test_scenario::end(scenario); + } + + #[test] + fun test_fail_emode_validity() { + use sui::test_utils::assert_eq; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + + let mut utils = vector::empty(); + vector::push_back(&mut utils, 0); + vector::push_back(&mut utils, 100); + + let mut aprs = vector::empty(); + vector::push_back(&mut aprs, 0); + vector::push_back(&mut aprs, 100); + + let emode_ltvs = create_emode_data( + 1, + 60, + 80, + ); + let mut emode_config = vec_map::empty(); + vec_map::insert(&mut emode_config, 1, emode_ltvs); + + let config = create_reserve_config_( + 10, + 10, + 10, + 10_000, + 1, + 1, + 5, + 5, + 100000, + 100000, + 10, + 2000, + 30, + utils, + aprs, + false, + 0, + 0, + option::some(emode_config), + test_scenario::ctx(&mut scenario) + ); + + assert_eq(check_has_emode_pair(&config, &2), false); + + + destroy(config); + test_scenario::end(scenario); + } } diff --git a/contracts/suilend/tests/obligation_tests.move b/contracts/suilend/tests/obligation_tests.move index aa0c970..02b3906 100644 --- a/contracts/suilend/tests/obligation_tests.move +++ b/contracts/suilend/tests/obligation_tests.move @@ -327,17 +327,18 @@ module suilend::obligation_tests { let mut scenario = test_scenario::begin(owner); let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); - let mut usdc_reserve = usdc_reserve(&mut scenario); - let mut sui_reserve = sui_reserve(&mut scenario); + let mut reserves = reserves(&mut scenario); let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); - deposit(&mut obligation, &mut sui_reserve, &clock, 100 * 1_000_000_000); - borrow(&mut obligation, &mut usdc_reserve, &clock, 200 * 1_000_000 + 1); + let sui_reserve = get_reserve_mut(&mut reserves); + + deposit(&mut obligation, sui_reserve, &clock, 100 * 1_000_000_000); + let borrow_idx = get_reserve_array_index(&reserves); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 200 * 1_000_000 + 1); sui::test_utils::destroy(lending_market_id); - sui::test_utils::destroy(usdc_reserve); - sui::test_utils::destroy(sui_reserve); + sui::test_utils::destroy(reserves); sui::test_utils::destroy(obligation); sui::test_utils::destroy(clock); test_scenario::end(scenario); @@ -352,18 +353,21 @@ module suilend::obligation_tests { let mut scenario = test_scenario::begin(owner); let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); - let mut usdc_reserve = usdc_reserve(&mut scenario); - let mut sui_reserve = sui_reserve(&mut scenario); + + let mut reserves = reserves(&mut scenario); let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); + let sui_reserve = get_reserve_mut(&mut reserves); - deposit(&mut obligation, &mut sui_reserve, &clock, 100 * 1_000_000_000); - borrow(&mut obligation, &mut usdc_reserve, &clock, 1); - deposit(&mut obligation, &mut usdc_reserve, &clock, 1); + deposit(&mut obligation, sui_reserve, &clock, 100 * 1_000_000_000); + + let borrow_idx = get_reserve_array_index(&reserves); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 1); + let usdc_reserve = get_reserve_mut(&mut reserves); + deposit(&mut obligation, usdc_reserve, &clock, 1); sui::test_utils::destroy(lending_market_id); - sui::test_utils::destroy(usdc_reserve); - sui::test_utils::destroy(sui_reserve); + sui::test_utils::destroy(reserves); sui::test_utils::destroy(obligation); sui::test_utils::destroy(clock); test_scenario::end(scenario); @@ -378,15 +382,18 @@ module suilend::obligation_tests { let mut scenario = test_scenario::begin(owner); let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); - let mut sui_reserve = sui_reserve(&mut scenario); - + + let mut reserves = reserves(&mut scenario); let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); - deposit(&mut obligation, &mut sui_reserve, &clock, 100 * 1_000_000_000); - borrow(&mut obligation, &mut sui_reserve, &clock, 1); + let sui_reserve = get_reserve_mut(&mut reserves); + + deposit(&mut obligation, sui_reserve, &clock, 100 * 1_000_000_000); + let borrow_idx = get_reserve_array_index(&reserves); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 1); sui::test_utils::destroy(lending_market_id); - sui::test_utils::destroy(sui_reserve); + sui::test_utils::destroy(reserves); sui::test_utils::destroy(obligation); sui::test_utils::destroy(clock); test_scenario::end(scenario); @@ -427,9 +434,11 @@ module suilend::obligation_tests { config ); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 1 ); @@ -439,7 +448,8 @@ module suilend::obligation_tests { // this fails borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 1 ); @@ -487,9 +497,11 @@ module suilend::obligation_tests { config ); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 1 ); @@ -497,9 +509,11 @@ module suilend::obligation_tests { refresh(&mut obligation, &mut reserves, &clock); // this fails + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 1 ); @@ -547,20 +561,24 @@ module suilend::obligation_tests { config ); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), - &clock, + &mut reserves, + borrow_idx, + &clock, 1 ); refresh(&mut obligation, &mut reserves, &clock); // this fails + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), - &clock, + &mut reserves, + borrow_idx, + &clock, 1 ); @@ -619,28 +637,30 @@ module suilend::obligation_tests { let mut scenario = test_scenario::begin(owner); let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); - let mut usdc_reserve = usdc_reserve(&mut scenario); - let mut sui_reserve = sui_reserve(&mut scenario); + let mut reserves = reserves(&mut scenario); let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + let usdc_reserve = get_reserve_mut(&mut reserves); reserve::update_price_for_testing( - &mut usdc_reserve, + usdc_reserve, &clock, decimal::from(1), decimal::from(2) ); + let sui_reserve = get_reserve_mut(&mut reserves); reserve::update_price_for_testing( - &mut sui_reserve, + sui_reserve, &clock, decimal::from(10), decimal::from(5) ); - deposit(&mut obligation, &mut sui_reserve, &clock, 100 * 1_000_000_000); - borrow(&mut obligation, &mut usdc_reserve, &clock, 12_500_000); - borrow(&mut obligation, &mut usdc_reserve, &clock, 12_500_000); + deposit(&mut obligation, sui_reserve, &clock, 100 * 1_000_000_000); + let borrow_idx = get_reserve_array_index(&reserves); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 12_500_000); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 12_500_000); assert!(obligation.deposits().length() == 1, 0); @@ -669,8 +689,7 @@ module suilend::obligation_tests { assert!(obligation.weighted_borrowed_value_upper_bound_usd() == decimal::from(100), 4); sui::test_utils::destroy(lending_market_id); - sui::test_utils::destroy(usdc_reserve); - sui::test_utils::destroy(sui_reserve); + sui::test_utils::destroy(reserves); sui::test_utils::destroy(obligation); clock::destroy_for_testing(clock); test_scenario::end(scenario); @@ -685,20 +704,21 @@ module suilend::obligation_tests { let mut scenario = test_scenario::begin(owner); let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); - let mut usdc_reserve = usdc_reserve(&mut scenario); - let mut sui_reserve = sui_reserve(&mut scenario); + let mut reserves = reserves(&mut scenario); let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); - let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); - deposit(&mut obligation, &mut sui_reserve, &clock, 100 * 1_000_000_000); - borrow(&mut obligation, &mut usdc_reserve, &clock, 50 * 1_000_000); + let sui_reserve = get_reserve_mut(&mut reserves); + deposit(&mut obligation, sui_reserve, &clock, 100 * 1_000_000_000); - withdraw(&mut obligation, &mut sui_reserve, &clock, 50 * 1_000_000_000 + 1); + let borrow_idx = get_reserve_array_index(&reserves); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 50 * 1_000_000); + + let sui_reserve = get_reserve_mut(&mut reserves); + withdraw(&mut obligation, sui_reserve, &clock, 50 * 1_000_000_000 + 1); sui::test_utils::destroy(lending_market_id); - sui::test_utils::destroy(usdc_reserve); - sui::test_utils::destroy(sui_reserve); + sui::test_utils::destroy(reserves); sui::test_utils::destroy(obligation); sui::test_utils::destroy(clock); test_scenario::end(scenario); @@ -713,20 +733,22 @@ module suilend::obligation_tests { let mut scenario = test_scenario::begin(owner); let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); - let mut usdc_reserve = usdc_reserve(&mut scenario); - let mut sui_reserve = sui_reserve(&mut scenario); + let mut reserves = reserves(&mut scenario); let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); - deposit(&mut obligation, &mut sui_reserve, &clock, 100 * 1_000_000_000); - borrow(&mut obligation, &mut usdc_reserve, &clock, 50 * 1_000_000); + let sui_reserve = get_reserve_mut(&mut reserves); + deposit(&mut obligation, sui_reserve, &clock, 100 * 1_000_000_000); - withdraw(&mut obligation, &mut usdc_reserve, &clock, 1); + let borrow_idx = get_reserve_array_index(&reserves); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 50 * 1_000_000); + + let usdc_reserve = get_reserve_mut(&mut reserves); + withdraw(&mut obligation, usdc_reserve, &clock, 1); sui::test_utils::destroy(lending_market_id); - sui::test_utils::destroy(usdc_reserve); - sui::test_utils::destroy(sui_reserve); + sui::test_utils::destroy(reserves); sui::test_utils::destroy(obligation); sui::test_utils::destroy(clock); test_scenario::end(scenario); @@ -740,55 +762,52 @@ module suilend::obligation_tests { let mut scenario = test_scenario::begin(owner); let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); - let mut usdc_reserve = usdc_reserve(&mut scenario); - let mut usdt_reserve = usdt_reserve(&mut scenario); - let mut sui_reserve = sui_reserve(&mut scenario); + let mut reserves = reserves(&mut scenario); let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); reserve::update_price_for_testing( - &mut usdc_reserve, + get_reserve_mut(&mut reserves), &clock, decimal::from(1), decimal::from(2) ); reserve::update_price_for_testing( - &mut usdt_reserve, + get_reserve_mut(&mut reserves), &clock, decimal::from(1), decimal::from(2) ); reserve::update_price_for_testing( - &mut sui_reserve, + get_reserve_mut(&mut reserves), &clock, decimal::from(10), decimal::from(5) ); - deposit(&mut obligation, &mut sui_reserve, &clock, 100 * 1_000_000_000); + deposit(&mut obligation, get_reserve_mut(&mut reserves), &clock, 100 * 1_000_000_000); - let amount = obligation.max_withdraw_amount(&sui_reserve); + let amount = obligation.max_withdraw_amount(get_reserve(&reserves)); assert!(amount == 100 * 1_000_000_000, 0); - borrow(&mut obligation, &mut usdc_reserve, &clock, 20 * 1_000_000); + let borrow_idx = get_reserve_array_index(&reserves); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 20 * 1_000_000); // sui open ltv is 0.2 // allowed borrow value = 100 * 0.2 * 5 = 100 // weighted upper bound borrow value = 20 * 2 * 2 = 80 // => max withdraw amount should be 20 - let amount = obligation.max_withdraw_amount(&sui_reserve); + let amount = obligation.max_withdraw_amount(get_reserve(&mut reserves)); assert!(amount == 20 * 1_000_000_000, 0); - deposit(&mut obligation, &mut usdt_reserve, &clock, 100 * 1_000_000); + deposit(&mut obligation, get_reserve_mut(&mut reserves),&clock, 100 * 1_000_000); - let amount = obligation.max_withdraw_amount(&usdt_reserve); + let amount = obligation.max_withdraw_amount(get_reserve(&mut reserves)); assert!(amount == 100 * 1_000_000, 0); sui::test_utils::destroy(lending_market_id); - sui::test_utils::destroy(usdc_reserve); - sui::test_utils::destroy(usdt_reserve); - sui::test_utils::destroy(sui_reserve); + sui::test_utils::destroy(reserves); sui::test_utils::destroy(obligation); clock::destroy_for_testing(clock); test_scenario::end(scenario); @@ -803,28 +822,29 @@ module suilend::obligation_tests { let mut scenario = test_scenario::begin(owner); let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); - let mut usdc_reserve = usdc_reserve(&mut scenario); - let mut sui_reserve = sui_reserve(&mut scenario); + let mut reserves = reserves(&mut scenario); let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); reserve::update_price_for_testing( - &mut usdc_reserve, - &clock, + get_reserve_mut(&mut reserves), + &clock, decimal::from(1), decimal::from(2) ); reserve::update_price_for_testing( - &mut sui_reserve, + get_reserve_mut(&mut reserves), &clock, decimal::from(10), decimal::from(5) ); - deposit(&mut obligation, &mut sui_reserve, &clock, 100 * 1_000_000_000); - borrow(&mut obligation, &mut usdc_reserve, &clock, 20 * 1_000_000); - withdraw(&mut obligation, &mut sui_reserve, &clock, 20 * 1_000_000_000); + deposit(&mut obligation, get_reserve_mut(&mut reserves), &clock, 100 * 1_000_000_000); + + let borrow_idx = get_reserve_array_index(&reserves); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 20 * 1_000_000); + withdraw(&mut obligation, get_reserve_mut(&mut reserves), &clock, 20 * 1_000_000_000); assert!(obligation.deposits().length() == 1, 0); @@ -853,8 +873,7 @@ module suilend::obligation_tests { assert!(obligation.weighted_borrowed_value_upper_bound_usd() == decimal::from(80), 4); sui::test_utils::destroy(lending_market_id); - sui::test_utils::destroy(usdc_reserve); - sui::test_utils::destroy(sui_reserve); + sui::test_utils::destroy(reserves); clock::destroy_for_testing(clock); sui::test_utils::destroy(obligation); test_scenario::end(scenario); @@ -871,33 +890,34 @@ module suilend::obligation_tests { let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); clock::set_for_testing(&mut clock, 0); - let mut usdc_reserve = usdc_reserve(&mut scenario); - let mut sui_reserve = sui_reserve(&mut scenario); + let mut reserves = reserves(&mut scenario); let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); reserve::update_price_for_testing( - &mut usdc_reserve, + get_reserve_mut(&mut reserves), &clock, decimal::from(1), decimal::from(2) ); reserve::update_price_for_testing( - &mut sui_reserve, + get_reserve_mut(&mut reserves), &clock, decimal::from(10), decimal::from(5) ); - deposit(&mut obligation, &mut sui_reserve, &clock, 100 * 1_000_000_000); - borrow(&mut obligation, &mut usdc_reserve, &clock, 25 * 1_000_000); + deposit(&mut obligation, get_reserve_mut(&mut reserves), &clock, 100 * 1_000_000_000); + + let borrow_idx = get_reserve_array_index(&reserves); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 25 * 1_000_000); clock::set_for_testing(&mut clock, 1000); - reserve::compound_interest(&mut usdc_reserve, &clock); + reserve::compound_interest(get_reserve_mut(&mut reserves), &clock); let repay_amount = repay( &mut obligation, - &mut usdc_reserve, + get_reserve_mut(&mut reserves), &clock, decimal::from(25 * 1_000_000) ); @@ -932,8 +952,7 @@ module suilend::obligation_tests { assert!(obligation.weighted_borrowed_value_upper_bound_usd() == decimal::from(1), 4); sui::test_utils::destroy(lending_market_id); - sui::test_utils::destroy(usdc_reserve); - sui::test_utils::destroy(sui_reserve); + sui::test_utils::destroy(reserves); clock::destroy_for_testing(clock); sui::test_utils::destroy(obligation); test_scenario::end(scenario); @@ -950,18 +969,18 @@ module suilend::obligation_tests { let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); clock::set_for_testing(&mut clock, 0); - let mut usdc_reserve = usdc_reserve(&mut scenario); - let mut sui_reserve = sui_reserve(&mut scenario); + let mut reserves = reserves(&mut scenario); let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); - deposit(&mut obligation, &mut sui_reserve, &clock, 100 * 1_000_000_000); - borrow(&mut obligation, &mut usdc_reserve, &clock, 100 * 1_000_000); + deposit(&mut obligation, get_reserve_mut(&mut reserves), &clock, 100 * 1_000_000_000); + let borrow_idx = get_reserve_array_index(&reserves); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 100 * 1_000_000); clock::set_for_testing(&mut clock, 1000); - reserve::compound_interest(&mut usdc_reserve, &clock); + reserve::compound_interest(get_reserve_mut(&mut reserves), &clock); - let repay_amount = repay(&mut obligation, &mut usdc_reserve, &clock, decimal::from(500_000)); + let repay_amount = repay(&mut obligation, get_reserve_mut(&mut reserves), &clock, decimal::from(500_000)); assert!(repay_amount == decimal::from(500_000), 0); assert!(obligation.deposits().length() == 1, 0); @@ -992,8 +1011,7 @@ module suilend::obligation_tests { assert!(obligation.weighted_borrowed_value_usd() == decimal::from_percent_u64(20_100), 4); sui::test_utils::destroy(lending_market_id); - sui::test_utils::destroy(usdc_reserve); - sui::test_utils::destroy(sui_reserve); + sui::test_utils::destroy(reserves); clock::destroy_for_testing(clock); sui::test_utils::destroy(obligation); test_scenario::end(scenario); @@ -1010,33 +1028,33 @@ module suilend::obligation_tests { let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); clock::set_for_testing(&mut clock, 0); - let mut usdc_reserve = usdc_reserve(&mut scenario); - let mut sui_reserve = sui_reserve(&mut scenario); + let mut reserves = reserves(&mut scenario); let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); - deposit(&mut obligation, &mut sui_reserve, &clock, 100 * 1_000_000_000); - borrow(&mut obligation, &mut usdc_reserve, &clock, 100 * 1_000_000); + deposit(&mut obligation, get_reserve_mut(&mut reserves), &clock, 100 * 1_000_000_000); + + let borrow_idx = get_reserve_array_index(&reserves); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 100 * 1_000_000); clock::set_for_testing(&mut clock, 1000); reserve::update_price_for_testing( - &mut usdc_reserve, - &clock, + get_reserve_mut(&mut reserves), + &clock, decimal::from(10), decimal::from(10) ); - reserve::compound_interest(&mut usdc_reserve, &clock); + reserve::compound_interest(get_reserve_mut(&mut reserves), &clock); let repay_amount = repay( &mut obligation, - &mut usdc_reserve, + get_reserve_mut(&mut reserves), &clock, decimal::from(100 * 1_000_000) ); sui::test_utils::destroy(lending_market_id); - sui::test_utils::destroy(usdc_reserve); - sui::test_utils::destroy(sui_reserve); + sui::test_utils::destroy(reserves); clock::destroy_for_testing(clock); sui::test_utils::destroy(obligation); test_scenario::end(scenario); @@ -1053,18 +1071,18 @@ module suilend::obligation_tests { let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); clock::set_for_testing(&mut clock, 0); - let mut usdc_reserve = usdc_reserve(&mut scenario); - let mut sui_reserve = sui_reserve(&mut scenario); + let mut reserves = reserves(&mut scenario); let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); - deposit(&mut obligation, &mut sui_reserve, &clock, 100 * 1_000_000_000); - borrow(&mut obligation, &mut usdc_reserve, &clock, 100 * 1_000_000); - + deposit(&mut obligation, get_reserve_mut(&mut reserves), &clock, 100 * 1_000_000_000); + + let borrow_idx = get_reserve_array_index(&reserves); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 100 * 1_000_000); let repay_amount = repay( &mut obligation, - &mut usdc_reserve, + get_reserve_mut(&mut reserves), &clock, decimal::from(101 * 1_000_000) ); @@ -1079,7 +1097,7 @@ module suilend::obligation_tests { assert!(obligation.borrows().length() == 0, 0); let user_reward_manager_index = obligation.find_user_reward_manager_index( - reserve::borrows_pool_reward_manager_mut(&mut usdc_reserve) + reserve::borrows_pool_reward_manager_mut(get_reserve_mut(&mut reserves)) ); let user_reward_manager = &obligation.user_reward_managers()[user_reward_manager_index]; assert!(liquidity_mining::shares(user_reward_manager) == 0, 0); @@ -1091,8 +1109,7 @@ module suilend::obligation_tests { assert!(obligation.weighted_borrowed_value_usd() == decimal::from_percent_u64(0), 4); sui::test_utils::destroy(lending_market_id); - sui::test_utils::destroy(usdc_reserve); - sui::test_utils::destroy(sui_reserve); + sui::test_utils::destroy(reserves); clock::destroy_for_testing(clock); sui::test_utils::destroy(obligation); test_scenario::end(scenario); @@ -1103,6 +1120,7 @@ module suilend::obligation_tests { public fun test_refresh_fail_deposit_price_stale() { use sui::test_scenario::{Self}; use sui::clock::{Self}; + use sui::test_utils::{Self}; let owner = @0x26; let mut scenario = test_scenario::begin(owner); @@ -1128,7 +1146,7 @@ module suilend::obligation_tests { &clock ); - sui::test_utils::destroy(reserves); + test_utils::destroy(reserves); sui::test_utils::destroy(lending_market_id); clock::destroy_for_testing(clock); sui::test_utils::destroy(obligation); @@ -1157,9 +1175,11 @@ module suilend::obligation_tests { &clock, 100 * 1_000_000_000 ); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 100 * 1_000_000 ); @@ -1212,9 +1232,11 @@ module suilend::obligation_tests { &clock, 100 * 1_000_000 ); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 100 * 1_000_000 ); @@ -1298,9 +1320,11 @@ module suilend::obligation_tests { &clock, 100 * 1_000_000_000 ); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 100 * 1_000_000 ); @@ -1347,15 +1371,19 @@ module suilend::obligation_tests { &clock, 100 * 1_000_000_000 ); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 50 * 1_000_000 ); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 50 * 1_000_000 ); @@ -1458,9 +1486,11 @@ module suilend::obligation_tests { &clock, 2 * 100_000_000 ); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 100 * 1_000_000 ); @@ -1513,7 +1543,7 @@ module suilend::obligation_tests { assert!(obligation.deposits().length() == 1, 0); - let user_reward_manager_index = obligation.find_user_reward_manager_index( + let user_reward_manager_index = obligation.find_user_reward_manager_index( reserve::deposits_pool_reward_manager_mut(get_reserve_mut(&mut reserves)) ); let user_reward_manager = &obligation.user_reward_managers()[user_reward_manager_index]; @@ -1569,9 +1599,11 @@ module suilend::obligation_tests { &clock, 100 * 1_000_000_000 ); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 1 * 1_000_000 ); @@ -1623,7 +1655,7 @@ module suilend::obligation_tests { let user_reward_manager_index = obligation.find_user_reward_manager_index( reserve::borrows_pool_reward_manager_mut(get_reserve_mut(&mut reserves)) ); - let user_reward_manager = &obligation.user_reward_managers()[user_reward_manager_index]; + let user_reward_manager = vector::borrow(obligation.user_reward_managers(), user_reward_manager_index); assert!(liquidity_mining::shares(user_reward_manager) == 0, 5); assert!(obligation.deposited_value_usd() == add(decimal::from(998), decimal::from_percent(90)), 0); @@ -1666,9 +1698,11 @@ module suilend::obligation_tests { &clock, 550_000 ); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 10 * 1_000_000 ); @@ -1782,9 +1816,11 @@ module suilend::obligation_tests { &clock, 10 * 1_000_000_000 ); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 1_000_000 ); @@ -1827,18 +1863,22 @@ module suilend::obligation_tests { &clock, 100 * 1_000_000 ); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 1_000_000_000 ); assert!(!obligation.is_looped(), 0); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 1_000_000 ); @@ -1899,27 +1939,33 @@ module suilend::obligation_tests { &clock, 100 * 1_000_000 ); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 1_000_000_000 ); assert!(!obligation.is_looped(), 0); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 1_000 ); assert!(!obligation.is_looped(), 0); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 1_000_000 ); @@ -1935,9 +1981,11 @@ module suilend::obligation_tests { assert!(!obligation.is_looped(), 0); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 1_000_000 ); @@ -1979,27 +2027,34 @@ module suilend::obligation_tests { &clock, 100 * 1_000_000 ); + + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 1_000_000_000 ); assert!(!obligation.is_looped(), 0); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 1_000 ); assert!(!obligation.is_looped(), 0); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 1_000_000 ); @@ -2015,9 +2070,11 @@ module suilend::obligation_tests { assert!(!obligation.is_looped(), 0); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 1_000_000 ); @@ -2059,27 +2116,34 @@ module suilend::obligation_tests { &clock, 100 * 1_000_000 ); + + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 1_000_000_000 ); assert!(!obligation.is_looped(), 0); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 1_000 ); assert!(!obligation.is_looped(), 0); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 1_000_000 ); @@ -2095,9 +2159,11 @@ module suilend::obligation_tests { assert!(!obligation.is_looped(), 0); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 1_000_000 ); @@ -2155,27 +2221,33 @@ module suilend::obligation_tests { sui::test_utils::destroy(vector::pop_back(obligation.borrows_mut())); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 1_000 ); assert!(!obligation.is_looped(), 0); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 1_000 ); assert!(!obligation.is_looped(), 0); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 1_000 ); @@ -2224,9 +2296,11 @@ module suilend::obligation_tests { &clock, 100 * 1_000_000 ); + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 1_000_000_000 ); @@ -2242,9 +2316,11 @@ module suilend::obligation_tests { }; // actually loop + let borrow_idx = get_reserve_array_index(&reserves); borrow( &mut obligation, - get_reserve_mut(&mut reserves), + &mut reserves, + borrow_idx, &clock, 1_000_000 ); @@ -2264,4 +2340,841 @@ module suilend::obligation_tests { sui::test_utils::destroy(obligation); test_scenario::end(scenario); } + + #[test] + public fun test_emode_deposit_borrow() { + use sui::test_scenario::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + + let mut reserves = reserves(&mut scenario); + + let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); + let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(10), + decimal::from(5) + ); + + let borrow_idx = get_reserve_array_index(&reserves); + reserve::set_emode_for_pair( + get_reserve_mut(&mut reserves), + borrow_idx, + 40, // 20 + 80, // 50 + test_scenario::ctx(&mut scenario) + ); + + obligation.set_emode(&mut reserves, &clock); + + deposit(&mut obligation, get_reserve_mut(&mut reserves), &clock, 100 * 1_000_000_000); + let borrow_idx = get_reserve_array_index(&reserves); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 12_500_000); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 12_500_000); + + assert!(vector::length(obligation.deposits()) == 1, 0); + + let sui_deposit = vector::borrow(obligation.deposits(), 0); + assert!(sui_deposit.deposited_ctoken_amount() == 100 * 1_000_000_000, 3); + assert!(sui_deposit.market_value() == decimal::from(1000), 4); + + let user_reward_manager = vector::borrow(obligation.user_reward_managers(), sui_deposit.user_reward_manager_index()); + assert!(liquidity_mining::shares(user_reward_manager) == 100 * 1_000_000_000, 3); + + assert!(obligation.borrows().length() == 1, 0); + + let usdc_borrow = vector::borrow(obligation.borrows(), 0); + assert!(usdc_borrow.borrowed_amount() == decimal::from(25 * 1_000_000), 1); + assert!(usdc_borrow.cumulative_borrow_rate() == decimal::from(2), 2); + assert!(usdc_borrow.market_value() == decimal::from(25), 3); + + let user_reward_manager = vector::borrow(obligation.user_reward_managers(), usdc_borrow.user_reward_manager_index()); + assert!(liquidity_mining::shares(user_reward_manager) == 25 * 1_000_000 / 2, 4); + + // Values unchanged due to emode + assert!(obligation.deposited_value_usd() == decimal::from(1000), 0); + assert!(obligation.unweighted_borrowed_value_usd() == decimal::from(25), 3); + + // Values changed due to emode + assert!(obligation.allowed_borrow_value_usd() == decimal::from(200), 1); + assert!(obligation.unhealthy_borrow_value_usd() == decimal::from(800), 2); + assert!(obligation.weighted_borrowed_value_usd() == decimal::from(25), 4); + assert!(obligation.weighted_borrowed_value_upper_bound_usd() == decimal::from(50), 4); + + sui::test_utils::destroy(lending_market_id); + sui::test_utils::destroy(reserves); + sui::test_utils::destroy(obligation); + clock::destroy_for_testing(clock); + test_scenario::end(scenario); + } + + #[test] + public fun test_emode_multiple_borrows_happy() { + use sui::test_scenario::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + + let mut reserves = reserves(&mut scenario); + + let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); + let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(10), + decimal::from(5) + ); + + let borrow_idx = get_reserve_array_index(&reserves); + reserve::set_emode_for_pair( + get_reserve_mut(&mut reserves), + borrow_idx, + 40, + 60, + test_scenario::ctx(&mut scenario) + ); + + obligation.set_emode(&mut reserves, &clock); + + deposit(&mut obligation, get_reserve_mut(&mut reserves), &clock, 100 * 1_000_000_000); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 12_500_000); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 12_500_000); + + sui::test_utils::destroy(lending_market_id); + sui::test_utils::destroy(reserves); + sui::test_utils::destroy(obligation); + clock::destroy_for_testing(clock); + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = suilend::obligation::EIsolatedAssetViolation)] + public fun test_emode_multiple_borrows_fail() { + use sui::test_scenario::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + + let mut reserves = reserves(&mut scenario); + + let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); + let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(10), + decimal::from(5) + ); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1), + decimal::from(2) + ); + + let borrow_idx = get_reserve_array_index(&reserves); + reserve::set_emode_for_pair( + get_reserve_mut(&mut reserves), + borrow_idx, + 40, + 60, + test_scenario::ctx(&mut scenario) + ); + + obligation.set_emode(&mut reserves, &clock); + + deposit(&mut obligation, get_reserve_mut(&mut reserves), &clock, 100 * 1_000_000_000); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 12_500_000); + let borrow_idx = get_reserve_array_index(&reserves); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 12_500_000); + + sui::test_utils::destroy(lending_market_id); + sui::test_utils::destroy(reserves); + sui::test_utils::destroy(obligation); + clock::destroy_for_testing(clock); + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = suilend::obligation::EIsolatedAssetViolation)] + public fun test_emode_multiple_deposits_fail() { + use sui::test_scenario::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + + let mut reserves = reserves(&mut scenario); + + let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); + let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(10), + decimal::from(5) + ); + + let usdc_index = get_reserve_array_index(&reserves); + reserve::set_emode_for_pair( + get_reserve_mut(&mut reserves), + usdc_index, + 40, + 60, + test_scenario::ctx(&mut scenario) + ); + + reserve::set_emode_for_pair( + get_reserve_mut(&mut reserves), + usdc_index, + 60, + 90, + test_scenario::ctx(&mut scenario) + ); + + obligation.set_emode(&mut reserves, &clock); + + deposit(&mut obligation, get_reserve_mut(&mut reserves), &clock, 100 * 1_000_000_000); + deposit(&mut obligation, get_reserve_mut(&mut reserves), &clock, 12_500_000); + + sui::test_utils::destroy(lending_market_id); + sui::test_utils::destroy(reserves); + sui::test_utils::destroy(obligation); + clock::destroy_for_testing(clock); + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = suilend::obligation::ENoEmodeConfigForGivenDepositReserve)] + public fun test_set_emode_invalid_deposit_reserve() { + use sui::test_scenario::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + + let mut reserves = reserves(&mut scenario); + + let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); + let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(10), + decimal::from(5) + ); + + deposit(&mut obligation, get_reserve_mut(&mut reserves), &clock, 12_500_000); + + obligation.set_emode(&mut reserves, &clock); + + sui::test_utils::destroy(lending_market_id); + sui::test_utils::destroy(reserves); + sui::test_utils::destroy(obligation); + clock::destroy_for_testing(clock); + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = suilend::obligation::EEModeAlreadySet)] + public fun test_emode_already_set() { + use sui::test_scenario::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + + let mut reserves = reserves(&mut scenario); + + let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); + let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(10), + decimal::from(5) + ); + + let usdc_index = get_reserve_array_index(&reserves); + reserve::set_emode_for_pair( + get_reserve_mut(&mut reserves), + usdc_index, + 40, + 60, + test_scenario::ctx(&mut scenario) + ); + + obligation.set_emode(&mut reserves, &clock); + obligation.set_emode(&mut reserves, &clock); + + sui::test_utils::destroy(lending_market_id); + sui::test_utils::destroy(reserves); + sui::test_utils::destroy(obligation); + clock::destroy_for_testing(clock); + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = suilend::obligation::EEModeNotValidWithCrossMargin)] + public fun test_emode_cross_borrow_fail() { + use sui::test_scenario::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + + let mut reserves = reserves(&mut scenario); + + let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); + let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(10), + decimal::from(5) + ); + + deposit(&mut obligation, get_reserve_mut(&mut reserves), &clock, 100 * 1_000_000_000); + let borrow_idx = get_reserve_array_index(&reserves); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 12_500_000); + let borrow_idx = get_reserve_array_index(&reserves); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 12_500_000); + + obligation.set_emode(&mut reserves, &clock); + + sui::test_utils::destroy(lending_market_id); + sui::test_utils::destroy(reserves); + sui::test_utils::destroy(obligation); + clock::destroy_for_testing(clock); + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = suilend::obligation::EEModeNotValidWithCrossMargin)] + public fun test_emode_cross_deposit_fail() { + use sui::test_scenario::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + + let mut reserves = reserves(&mut scenario); + + let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); + let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(10), + decimal::from(5) + ); + + deposit(&mut obligation, get_reserve_mut(&mut reserves), &clock, 100 * 1_000_000_000); + deposit(&mut obligation, get_reserve_mut(&mut reserves),&clock, 100 * 1_000_000_000); + let borrow_idx = get_reserve_array_index(&reserves); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 12_500_000); + + obligation.set_emode(&mut reserves, &clock); + + sui::test_utils::destroy(lending_market_id); + sui::test_utils::destroy(reserves); + sui::test_utils::destroy(obligation); + clock::destroy_for_testing(clock); + test_scenario::end(scenario); + } + + #[test] + public fun test_emode_with_normal_borrow_reserve_ok_ltvs() { + use sui::test_scenario::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + + let mut reserves = reserves(&mut scenario); + + let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); + let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(10), + decimal::from(5) + ); + + let borrow_idx = get_reserve_array_index(&reserves); + reserve::set_emode_for_pair( + get_reserve_mut(&mut reserves), + borrow_idx, + 40, + 60, + test_scenario::ctx(&mut scenario) + ); + + deposit(&mut obligation, get_reserve_mut(&mut reserves), &clock, 100 * 1_000_000_000); + let borrow_idx = get_reserve_array_index(&reserves); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 12_500_000); + + obligation.set_emode(&mut reserves, &clock); + + sui::test_utils::destroy(lending_market_id); + sui::test_utils::destroy(reserves); + sui::test_utils::destroy(obligation); + clock::destroy_for_testing(clock); + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = reserve_config::ENormalOpenLtvBetterThanEModeLtvs)] + public fun test_emode_with_normal_borrow_reserve_fail_open_ltv_1() { + use sui::test_scenario::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + + let mut reserves = reserves(&mut scenario); + + let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); + let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(10), + decimal::from(5) + ); + + let borrow_idx = get_reserve_array_index(&reserves); + reserve::set_emode_for_pair( + get_reserve_mut(&mut reserves), + borrow_idx, + 20 - 1, + 80, + test_scenario::ctx(&mut scenario) + ); + + obligation.set_emode(&mut reserves, &clock); + + sui::test_utils::destroy(lending_market_id); + sui::test_utils::destroy(reserves); + sui::test_utils::destroy(obligation); + clock::destroy_for_testing(clock); + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = reserve_config::ENormalCloseLtvBetterThanEModeLtvs)] + public fun test_emode_with_normal_borrow_reserve_fail_close_ltv_1() { + use sui::test_scenario::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + + let mut reserves = reserves(&mut scenario); + + let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); + let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(10), + decimal::from(5) + ); + + let borrow_idx = get_reserve_array_index(&reserves); + reserve::set_emode_for_pair( + get_reserve_mut(&mut reserves), + borrow_idx, + 30, + 50 - 1, + test_scenario::ctx(&mut scenario) + ); + + obligation.set_emode(&mut reserves, &clock); + deposit(&mut obligation, get_reserve_mut(&mut reserves), &clock, 100 * 1_000_000_000); + let borrow_idx = get_reserve_array_index(&reserves); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 12_500_000); + + sui::test_utils::destroy(lending_market_id); + sui::test_utils::destroy(reserves); + sui::test_utils::destroy(obligation); + clock::destroy_for_testing(clock); + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = reserve_config::ENormalOpenLtvBetterThanEModeLtvs)] + public fun test_emode_with_normal_borrow_reserve_fail_open_ltv_2() { + use sui::test_scenario::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + + let mut reserves = reserves(&mut scenario); + + let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); + let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(10), + decimal::from(5) + ); + + let borrow_idx = get_reserve_array_index(&reserves); + reserve::set_emode_for_pair( + get_reserve_mut(&mut reserves), + borrow_idx, + 20 - 1, + 80, + test_scenario::ctx(&mut scenario) + ); + + deposit(&mut obligation, get_reserve_mut(&mut reserves), &clock, 100 * 1_000_000_000); + let borrow_idx = get_reserve_array_index(&reserves); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 12_500_000); + obligation.set_emode(&mut reserves, &clock); + + sui::test_utils::destroy(lending_market_id); + sui::test_utils::destroy(reserves); + sui::test_utils::destroy(obligation); + clock::destroy_for_testing(clock); + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = reserve_config::ENormalCloseLtvBetterThanEModeLtvs)] + public fun test_emode_with_normal_borrow_reserve_fail_close_ltv_2() { + use sui::test_scenario::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + + let mut reserves = reserves(&mut scenario); + + let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); + let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(10), + decimal::from(5) + ); + + let borrow_idx = get_reserve_array_index(&reserves); + reserve::set_emode_for_pair( + get_reserve_mut(&mut reserves), + borrow_idx, + 30, + 50 - 1, + test_scenario::ctx(&mut scenario) + ); + + deposit(&mut obligation, get_reserve_mut(&mut reserves), &clock, 100 * 1_000_000_000); + let borrow_idx = get_reserve_array_index(&reserves); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 12_500_000); + obligation.set_emode(&mut reserves, &clock); + + sui::test_utils::destroy(lending_market_id); + sui::test_utils::destroy(reserves); + sui::test_utils::destroy(obligation); + clock::destroy_for_testing(clock); + test_scenario::end(scenario); + } + + #[test] + public fun test_emode_refresh() { + use sui::test_scenario::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + + let mut reserves = reserves(&mut scenario); + + let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); + let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(10), + decimal::from(5) + ); + + let borrow_idx = get_reserve_array_index(&reserves); + reserve::set_emode_for_pair( + get_reserve_mut(&mut reserves), + borrow_idx, + 40, + 60, + test_scenario::ctx(&mut scenario) + ); + + obligation.set_emode(&mut reserves, &clock); + + deposit(&mut obligation, get_reserve_mut(&mut reserves), &clock, 100 * 1_000_000_000); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 12_500_000); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 12_500_000); + + refresh(&mut obligation, &mut reserves, &clock); + + sui::test_utils::destroy(lending_market_id); + sui::test_utils::destroy(reserves); + sui::test_utils::destroy(obligation); + clock::destroy_for_testing(clock); + test_scenario::end(scenario); + } + + #[test] + public fun test_emode_withdraw() { + use sui::test_scenario::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + + let mut reserves = reserves(&mut scenario); + + let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); + let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(10), + decimal::from(5) + ); + + let borrow_idx = get_reserve_array_index(&reserves); + reserve::set_emode_for_pair( + get_reserve_mut(&mut reserves), + borrow_idx, + 40, + 60, + test_scenario::ctx(&mut scenario) + ); + + obligation.set_emode(&mut reserves, &clock); + + deposit(&mut obligation, get_reserve_mut(&mut reserves), &clock, 100 * 1_000_000_000); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 12_500_000); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 12_500_000); + withdraw(&mut obligation, get_reserve_mut(&mut reserves), &clock, 20 * 1_000_000_000); + + sui::test_utils::destroy(lending_market_id); + sui::test_utils::destroy(reserves); + sui::test_utils::destroy(obligation); + clock::destroy_for_testing(clock); + test_scenario::end(scenario); + } + + #[test] + public fun test_emode_repay() { + use sui::test_scenario::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + + let mut reserves = reserves(&mut scenario); + + let mut obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); + let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(10), + decimal::from(5) + ); + + let borrow_idx = get_reserve_array_index(&reserves); + reserve::set_emode_for_pair( + get_reserve_mut(&mut reserves), + borrow_idx, + 40, + 60, + test_scenario::ctx(&mut scenario) + ); + + obligation.set_emode(&mut reserves, &clock); + + deposit(&mut obligation, get_reserve_mut(&mut reserves), &clock, 100 * 1_000_000_000); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 12_500_000); + borrow(&mut obligation, &mut reserves, borrow_idx, &clock, 12_500_000); + repay( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + decimal::from(12_500_000) + ); + + sui::test_utils::destroy(lending_market_id); + sui::test_utils::destroy(reserves); + sui::test_utils::destroy(obligation); + clock::destroy_for_testing(clock); + test_scenario::end(scenario); + } }