diff --git a/contracts/suilend/Move.lock b/contracts/suilend/Move.lock index 7bca1f3..d397ba8 100644 --- a/contracts/suilend/Move.lock +++ b/contracts/suilend/Move.lock @@ -1,44 +1,44 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "97E92C3AE2671D15B98EDF2F75D00F01F060C660AA87BCA5FB95A6792D62C242" deps_digest = "3C4103934B1E040BB6B23F1D610B4EF9F2F1166A50A104EADCF77467C004C600" dependencies = [ - { name = "Pyth" }, - { name = "Sui" }, + { id = "Pyth", name = "Pyth" }, + { id = "Sui", name = "Sui" }, ] [[move.package]] -name = "MoveStdlib" +id = "MoveStdlib" source = { git = "https://github.com/MystenLabs/sui.git", rev = "framework/mainnet", subdir = "crates/sui-framework/packages/move-stdlib" } [[move.package]] -name = "Pyth" +id = "Pyth" source = { git = "https://github.com/solendprotocol/pyth-crosschain.git", rev = "98e218c64bb75cf1350eb7b021e1ffcc3aedfd62", subdir = "target_chains/sui/contracts" } dependencies = [ - { name = "Sui" }, - { name = "Wormhole" }, + { id = "Sui", name = "Sui" }, + { id = "Wormhole", name = "Wormhole" }, ] [[move.package]] -name = "Sui" +id = "Sui" source = { git = "https://github.com/MystenLabs/sui.git", rev = "framework/mainnet", subdir = "crates/sui-framework/packages/sui-framework" } dependencies = [ - { name = "MoveStdlib" }, + { id = "MoveStdlib", name = "MoveStdlib" }, ] [[move.package]] -name = "Wormhole" +id = "Wormhole" source = { git = "https://github.com/solendprotocol/wormhole.git", rev = "e1698d3c72b15cdddd7da98ad43e151f83b72a0a", subdir = "sui/wormhole" } dependencies = [ - { name = "Sui" }, + { id = "Sui", name = "Sui" }, ] [move.toolchain-version] -compiler-version = "1.34.1" +compiler-version = "1.35.3" edition = "2024.beta" flavor = "sui" diff --git a/contracts/suilend/sources/lending_market.move b/contracts/suilend/sources/lending_market.move index c21531e..875ec74 100644 --- a/contracts/suilend/sources/lending_market.move +++ b/contracts/suilend/sources/lending_market.move @@ -769,6 +769,15 @@ module suilend::lending_market { object_table::borrow(&lending_market.obligations, obligation_id) } + public fun fee_receiver

(lending_market: &LendingMarket

): address { + lending_market.fee_receiver + } + + public use fun rate_limiter_exemption_amount as RateLimiterExemption.amount; + public fun rate_limiter_exemption_amount(exemption: &RateLimiterExemption): u64 { + exemption.amount + } + // === Admin Functions === entry fun migrate

( _: &LendingMarketOwnerCap

, @@ -1071,1606 +1080,4 @@ module suilend::lending_market { let LendingMarketOwnerCap { id, lending_market_id: _ } = lending_market_owner_cap; object::delete(id); } - - #[test_only] - use sui::test_scenario::{Self, Scenario}; - - #[test] - fun test_create_lending_market() { - use sui::test_scenario::{Self}; - use sui::test_utils::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - - let (owner_cap, lending_market) = create_lending_market( - test_scenario::ctx(&mut scenario) - ); - - test_utils::destroy(owner_cap); - test_utils::destroy(lending_market); - test_scenario::end(scenario); - } - - #[test_only] - use suilend::mock_pyth::{PriceState}; - - #[test_only] - public struct State { - clock: Clock, - owner_cap: LendingMarketOwnerCap, - lending_market: LendingMarket, - prices: PriceState, - type_to_index: Bag - } - - #[test_only] - public struct ReserveArgs has store { - config: ReserveConfig, - initial_deposit: u64 - } - - #[test] - #[expected_failure(abort_code = EDuplicateReserve)] - fun duplicate_reserves() { - use suilend::test_usdc::{TEST_USDC}; - use suilend::test_sui::{TEST_SUI}; - use suilend::reserve_config::{Self}; - use sui::test_utils::{Self}; - use suilend::mock_pyth::{Self}; - use suilend::mock_metadata::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - - let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); - let metadata = mock_metadata::init_metadata(test_scenario::ctx(&mut scenario)); - - let (owner_cap, mut lending_market) = create_lending_market( - test_scenario::ctx(&mut scenario) - ); - - let mut prices = mock_pyth::init_state(test_scenario::ctx(&mut scenario)); - mock_pyth::register(&mut prices, test_scenario::ctx(&mut scenario)); - mock_pyth::register(&mut prices, test_scenario::ctx(&mut scenario)); - - add_reserve( - &owner_cap, - &mut lending_market, - mock_pyth::get_price_obj(&prices), - reserve_config::default_reserve_config(), - mock_metadata::get(&metadata), - &clock, - test_scenario::ctx(&mut scenario) - ); - - add_reserve( - &owner_cap, - &mut lending_market, - mock_pyth::get_price_obj(&prices), - reserve_config::default_reserve_config(), - mock_metadata::get(&metadata), - &clock, - test_scenario::ctx(&mut scenario) - ); - - test_utils::destroy(owner_cap); - test_utils::destroy(lending_market); - test_utils::destroy(clock); - test_utils::destroy(prices); - test_utils::destroy(metadata); - test_scenario::end(scenario); - } - - #[test_only] - fun setup(mut reserve_args: Bag, scenario: &mut Scenario): State { - use suilend::test_usdc::{TEST_USDC}; - use suilend::test_sui::{TEST_SUI}; - use suilend::reserve_config::{Self}; - use sui::test_utils::{Self}; - use suilend::mock_pyth::{Self}; - use suilend::mock_metadata::{Self}; - use std::type_name::{Self}; - - - let clock = clock::create_for_testing(test_scenario::ctx(scenario)); - let metadata = mock_metadata::init_metadata(test_scenario::ctx(scenario)); - - let (owner_cap, mut lending_market) = create_lending_market( - test_scenario::ctx(scenario) - ); - - let mut prices = mock_pyth::init_state(test_scenario::ctx(scenario)); - mock_pyth::register(&mut prices, test_scenario::ctx(scenario)); - mock_pyth::register(&mut prices, test_scenario::ctx(scenario)); - - let mut type_to_index = bag::new(test_scenario::ctx(scenario)); - bag::add(&mut type_to_index, type_name::get(), 0); - bag::add(&mut type_to_index, type_name::get(), 1); - - add_reserve( - &owner_cap, - &mut lending_market, - mock_pyth::get_price_obj(&prices), - reserve_config::default_reserve_config(), - mock_metadata::get(&metadata), - &clock, - test_scenario::ctx(scenario) - ); - - add_reserve( - &owner_cap, - &mut lending_market, - mock_pyth::get_price_obj(&prices), - reserve_config::default_reserve_config(), - mock_metadata::get(&metadata), - &clock, - test_scenario::ctx(scenario) - ); - - if (bag::contains(&reserve_args, type_name::get())) { - let ReserveArgs { config, initial_deposit } = bag::remove( - &mut reserve_args, - type_name::get() - ); - let coins = coin::mint_for_testing( - initial_deposit, - test_scenario::ctx(scenario) - ); - - let ctokens = deposit_liquidity_and_mint_ctokens( - &mut lending_market, - 0, - &clock, - coins, - test_scenario::ctx(scenario) - ); - - update_reserve_config( - &owner_cap, - &mut lending_market, - 0, - config - ); - - test_utils::destroy(ctokens); - }; - if (bag::contains(&reserve_args, type_name::get())) { - let ReserveArgs { config, initial_deposit } = bag::remove( - &mut reserve_args, - type_name::get() - ); - let coins = coin::mint_for_testing( - initial_deposit, - test_scenario::ctx(scenario) - ); - - let ctokens = deposit_liquidity_and_mint_ctokens( - &mut lending_market, - 1, - &clock, - coins, - test_scenario::ctx(scenario) - ); - - update_reserve_config( - &owner_cap, - &mut lending_market, - 1, - config - ); - - test_utils::destroy(ctokens); - }; - - test_utils::destroy(reserve_args); - test_utils::destroy(metadata); - - return State { - clock, - owner_cap, - lending_market, - prices, - type_to_index - } - } - - #[test] - public fun test_deposit() { - use sui::test_utils::{Self}; - use suilend::test_usdc::{TEST_USDC}; - use std::type_name::{Self}; - use suilend::reserve_config::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - let State { clock, owner_cap, mut lending_market, prices, type_to_index } = setup({ - let mut bag = bag::new(test_scenario::ctx(&mut scenario)); - bag::add( - &mut bag, - type_name::get(), - ReserveArgs { - config: reserve_config::default_reserve_config(), - initial_deposit: 100 * 1_000_000 - } - ); - - bag - }, &mut scenario); - - let obligation_owner_cap = create_obligation( - &mut lending_market, - test_scenario::ctx(&mut scenario) - ); - - let coins = coin::mint_for_testing(100 * 1_000_000, test_scenario::ctx(&mut scenario)); - - let ctokens = deposit_liquidity_and_mint_ctokens( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - coins, - test_scenario::ctx(&mut scenario) - ); - assert!(coin::value(&ctokens) == 100 * 1_000_000, 0); - - let usdc_reserve = reserve(&lending_market); - assert!(reserve::available_amount(usdc_reserve) == 200 * 1_000_000, 0); - - deposit_ctokens_into_obligation( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &obligation_owner_cap, - &clock, - ctokens, - test_scenario::ctx(&mut scenario) - ); - - let obligation = obligation(&lending_market, obligation_id(&obligation_owner_cap)); - assert!(obligation::deposited_ctoken_amount(obligation) == 100 * 1_000_000, 0); - - test_utils::destroy(obligation_owner_cap); - test_utils::destroy(owner_cap); - test_utils::destroy(lending_market); - test_utils::destroy(clock); - test_utils::destroy(prices); - test_utils::destroy(type_to_index); - test_scenario::end(scenario); - } - - #[test] - public fun test_redeem() { - use sui::test_utils::{Self}; - use suilend::test_usdc::{TEST_USDC}; - use std::type_name::{Self}; - use suilend::reserve_config::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - let State { clock, owner_cap, mut lending_market, prices, type_to_index } = setup({ - let mut bag = bag::new(test_scenario::ctx(&mut scenario)); - bag::add( - &mut bag, - type_name::get(), - ReserveArgs { - config: reserve_config::default_reserve_config(), - initial_deposit: 100 * 1_000_000 - } - ); - - bag - }, &mut scenario); - - let coins = coin::mint_for_testing(100 * 1_000_000, test_scenario::ctx(&mut scenario)); - let ctokens = deposit_liquidity_and_mint_ctokens( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - coins, - test_scenario::ctx(&mut scenario) - ); - assert!(coin::value(&ctokens) == 100 * 1_000_000, 0); - - let usdc_reserve = reserve(&lending_market); - let old_available_amount = reserve::available_amount(usdc_reserve); - - let tokens = redeem_ctokens_and_withdraw_liquidity( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - ctokens, - option::none(), - test_scenario::ctx(&mut scenario) - ); - assert!(coin::value(&tokens) == 100 * 1_000_000, 0); - - let usdc_reserve = reserve(&lending_market); - let new_available_amount = reserve::available_amount(usdc_reserve); - assert!(new_available_amount == old_available_amount - 100 * 1_000_000, 0); - - test_utils::destroy(tokens); - test_utils::destroy(owner_cap); - test_utils::destroy(lending_market); - test_utils::destroy(clock); - test_utils::destroy(prices); - test_utils::destroy(type_to_index); - test_scenario::end(scenario); - } - - - #[test] - public fun test_borrow_and_repay() { - use sui::test_utils::{Self}; - use suilend::test_usdc::{TEST_USDC}; - use suilend::test_sui::{TEST_SUI}; - use suilend::mock_pyth::{Self}; - use suilend::reserve_config::{Self, default_reserve_config}; - - use std::type_name::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - let State { mut clock, owner_cap, mut lending_market, mut prices, type_to_index } = setup({ - let mut bag = bag::new(test_scenario::ctx(&mut scenario)); - bag::add( - &mut bag, - type_name::get(), - ReserveArgs { - config: { - let config = default_reserve_config(); - let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); - reserve_config::set_open_ltv_pct(&mut builder, 50); - reserve_config::set_close_ltv_pct(&mut builder, 50); - reserve_config::set_max_close_ltv_pct(&mut builder, 50); - sui::test_utils::destroy(config); - - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }, - initial_deposit: 100 * 1_000_000 - } - ); - bag::add( - &mut bag, - type_name::get(), - ReserveArgs { - config: { - let config = reserve_config::default_reserve_config(); - let mut builder = reserve_config::from( - &config, - test_scenario::ctx(&mut scenario) - ); - - test_utils::destroy(config); - - reserve_config::set_borrow_fee_bps(&mut builder, 10); - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }, - initial_deposit: 100 * 1_000_000_000 - } - ); - - bag - }, &mut scenario); - - clock::set_for_testing(&mut clock, 1 * 1000); - - // set reserve parameters and prices - mock_pyth::update_price(&mut prices, 1, 0, &clock); // $1 - mock_pyth::update_price(&mut prices, 1, 1, &clock); // $10 - - // create obligation - let obligation_owner_cap = create_obligation( - &mut lending_market, - test_scenario::ctx(&mut scenario) - ); - - let coins = coin::mint_for_testing(100 * 1_000_000, test_scenario::ctx(&mut scenario)); - let ctokens = deposit_liquidity_and_mint_ctokens( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - coins, - test_scenario::ctx(&mut scenario) - ); - deposit_ctokens_into_obligation( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &obligation_owner_cap, - &clock, - ctokens, - test_scenario::ctx(&mut scenario) - ); - - refresh_reserve_price( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - mock_pyth::get_price_obj(&prices) - ); - refresh_reserve_price( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - mock_pyth::get_price_obj(&prices) - ); - - let mut sui = borrow( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &obligation_owner_cap, - &clock, - 1 * 1_000_000_000, - test_scenario::ctx(&mut scenario) - ); - - assert!(coin::value(&sui) == 1 * 1_000_000_000, 0); - - // state checks - let sui_reserve = reserve(&lending_market); - assert!(reserve::borrowed_amount(sui_reserve) == decimal::from(1_001_000_000), 0); - - let obligation = obligation(&lending_market, obligation_id(&obligation_owner_cap)); - assert!(obligation::borrowed_amount(obligation) == decimal::from(1_001_000_000), 0); - - repay( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - obligation_id(&obligation_owner_cap), - &clock, - &mut sui, - test_scenario::ctx(&mut scenario) - ); - - assert!(coin::value(&sui) == 0, 0); - test_utils::destroy(sui); - - let sui_reserve = reserve(&lending_market); - assert!(reserve::borrowed_amount(sui_reserve) == decimal::from(1_000_000), 0); - - let obligation = obligation(&lending_market, obligation_id(&obligation_owner_cap)); - assert!(obligation::borrowed_amount(obligation) == decimal::from(1_000_000), 0); - - let mut sui = coin::mint_for_testing(1_000_000_000, test_scenario::ctx(&mut scenario)); - repay( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - obligation_id(&obligation_owner_cap), - &clock, - &mut sui, - test_scenario::ctx(&mut scenario) - ); - assert!(coin::value(&sui) == 1_000_000_000 - 1_000_000, 0); - - let sui_reserve = reserve(&lending_market); - assert!(reserve::borrowed_amount(sui_reserve) == decimal::from(0), 0); - - let obligation = obligation(&lending_market, obligation_id(&obligation_owner_cap)); - assert!(obligation::borrowed_amount(obligation) == decimal::from(0), 0); - - test_scenario::next_tx(&mut scenario, owner); - - claim_fees( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - test_scenario::ctx(&mut scenario) - ); - - test_scenario::next_tx(&mut scenario, owner); - - let fees: Coin = test_scenario::take_from_address(&scenario, lending_market.fee_receiver); - assert!(coin::value(&fees) == 1_000_000, 0); - - test_utils::destroy(fees); - - test_utils::destroy(sui); - test_utils::destroy(obligation_owner_cap); - test_utils::destroy(owner_cap); - test_utils::destroy(lending_market); - test_utils::destroy(clock); - test_utils::destroy(prices); - test_utils::destroy(type_to_index); - test_scenario::end(scenario); - } - - #[test] - public fun test_withdraw() { - use sui::test_utils::{Self}; - use suilend::test_usdc::{TEST_USDC}; - use suilend::test_sui::{TEST_SUI}; - use suilend::mock_pyth::{Self}; - use suilend::reserve_config::{Self, default_reserve_config}; - - use std::type_name::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - let State { mut clock, owner_cap, mut lending_market, mut prices, type_to_index } = setup({ - let mut bag = bag::new(test_scenario::ctx(&mut scenario)); - bag::add( - &mut bag, - type_name::get(), - ReserveArgs { - config: { - let config = default_reserve_config(); - let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); - reserve_config::set_open_ltv_pct(&mut builder, 50); - reserve_config::set_close_ltv_pct(&mut builder, 50); - reserve_config::set_max_close_ltv_pct(&mut builder, 50); - sui::test_utils::destroy(config); - - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }, - initial_deposit: 100 * 1_000_000 - } - ); - bag::add( - &mut bag, - type_name::get(), - ReserveArgs { - config: reserve_config::default_reserve_config(), - initial_deposit: 100 * 1_000_000_000 - } - ); - - bag - }, &mut scenario); - - clock::set_for_testing(&mut clock, 1 * 1000); - - // set reserve parameters and prices - mock_pyth::update_price(&mut prices, 1, 0, &clock); // $1 - mock_pyth::update_price(&mut prices, 1, 1, &clock); // $10 - - // create obligation - let obligation_owner_cap = create_obligation( - &mut lending_market, - test_scenario::ctx(&mut scenario) - ); - - let coins = coin::mint_for_testing(100 * 1_000_000, test_scenario::ctx(&mut scenario)); - let ctokens = deposit_liquidity_and_mint_ctokens( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - coins, - test_scenario::ctx(&mut scenario) - ); - deposit_ctokens_into_obligation( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &obligation_owner_cap, - &clock, - ctokens, - test_scenario::ctx(&mut scenario) - ); - - refresh_reserve_price( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - mock_pyth::get_price_obj(&prices) - ); - refresh_reserve_price( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - mock_pyth::get_price_obj(&prices) - ); - - let sui = borrow( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &obligation_owner_cap, - &clock, - 2_500_000_000, - test_scenario::ctx(&mut scenario) - ); - - - let obligation = obligation(&lending_market, obligation_id(&obligation_owner_cap)); - let old_deposited_amount = obligation::deposited_ctoken_amount(obligation); - - let usdc = withdraw_ctokens( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &obligation_owner_cap, - &clock, - 50 * 1_000_000, - test_scenario::ctx(&mut scenario) - ); - - let obligation = obligation(&lending_market, obligation_id(&obligation_owner_cap)); - let deposited_amount = obligation::deposited_ctoken_amount(obligation); - - assert!(coin::value(&usdc) == 50_000_000, 0); - assert!(deposited_amount == old_deposited_amount - 50 * 1_000_000, 0); - - test_utils::destroy(sui); - test_utils::destroy(usdc); - test_utils::destroy(obligation_owner_cap); - test_utils::destroy(owner_cap); - test_utils::destroy(lending_market); - test_utils::destroy(clock); - test_utils::destroy(prices); - test_utils::destroy(type_to_index); - test_scenario::end(scenario); - } - - #[test] - public fun test_liquidate() { - use sui::test_utils::{Self}; - use suilend::test_usdc::{TEST_USDC}; - use suilend::test_sui::{TEST_SUI}; - use suilend::mock_pyth::{Self}; - use suilend::reserve_config::{Self, default_reserve_config}; - use suilend::decimal::{sub}; - - use std::type_name::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - let State { mut clock, owner_cap, mut lending_market, mut prices, type_to_index } = setup({ - let mut bag = bag::new(test_scenario::ctx(&mut scenario)); - bag::add( - &mut bag, - type_name::get(), - ReserveArgs { - config: { - let config = default_reserve_config(); - let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); - reserve_config::set_open_ltv_pct(&mut builder, 50); - reserve_config::set_close_ltv_pct(&mut builder, 50); - reserve_config::set_max_close_ltv_pct(&mut builder, 50); - sui::test_utils::destroy(config); - - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }, - initial_deposit: 100 * 1_000_000 - } - ); - bag::add( - &mut bag, - type_name::get(), - ReserveArgs { - config: reserve_config::default_reserve_config(), - initial_deposit: 100 * 1_000_000_000 - } - ); - - bag - }, &mut scenario); - - clock::set_for_testing(&mut clock, 1 * 1000); - - // set reserve parameters and prices - mock_pyth::update_price(&mut prices, 1, 0, &clock); // $1 - mock_pyth::update_price(&mut prices, 1, 1, &clock); // $10 - - // create obligation - let obligation_owner_cap = create_obligation( - &mut lending_market, - test_scenario::ctx(&mut scenario) - ); - - let coins = coin::mint_for_testing(100 * 1_000_000, test_scenario::ctx(&mut scenario)); - let ctokens = deposit_liquidity_and_mint_ctokens( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - coins, - test_scenario::ctx(&mut scenario) - ); - deposit_ctokens_into_obligation( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &obligation_owner_cap, - &clock, - ctokens, - test_scenario::ctx(&mut scenario) - ); - - refresh_reserve_price( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - mock_pyth::get_price_obj(&prices) - ); - refresh_reserve_price( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - mock_pyth::get_price_obj(&prices) - ); - - let sui = borrow( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &obligation_owner_cap, - &clock, - 5 * 1_000_000_000, - test_scenario::ctx(&mut scenario) - ); - test_utils::destroy(sui); - - // set the open and close ltvs of the usdc reserve to 0 - let usdc_reserve = reserve(&lending_market); - update_reserve_config( - &owner_cap, - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - { - let mut builder = reserve_config::from( - reserve::config(usdc_reserve), - test_scenario::ctx(&mut scenario) - ); - reserve_config::set_open_ltv_pct(&mut builder, 0); - reserve_config::set_close_ltv_pct(&mut builder, 0); - reserve_config::set_max_close_ltv_pct(&mut builder, 0); - reserve_config::set_liquidation_bonus_bps(&mut builder, 400); - reserve_config::set_max_liquidation_bonus_bps(&mut builder, 400); - reserve_config::set_protocol_liquidation_fee_bps(&mut builder, 600); - - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - } - ); - - let obligation = obligation(&lending_market, obligation_id(&obligation_owner_cap)); - - let sui_reserve = reserve(&lending_market); - let old_reserve_borrowed_amount = reserve::borrowed_amount(sui_reserve); - - let old_deposited_amount = obligation::deposited_ctoken_amount(obligation); - let old_borrowed_amount = obligation::borrowed_amount(obligation); - - // liquidate the obligation - let mut sui = coin::mint_for_testing(5 * 1_000_000_000, test_scenario::ctx(&mut scenario)); - let (usdc, exemption) = liquidate( - &mut lending_market, - obligation_id(&obligation_owner_cap), - *bag::borrow(&type_to_index, type_name::get()), - *bag::borrow(&type_to_index, type_name::get()), - &clock, - &mut sui, - test_scenario::ctx(&mut scenario) - ); - - assert!(coin::value(&sui) == 4 * 1_000_000_000, 0); - assert!(coin::value(&usdc) == 10 * 1_000_000 + 400_000, 0); - assert!(exemption.amount == 10 * 1_000_000 + 400_000, 0); - - let obligation = obligation(&lending_market, obligation_id(&obligation_owner_cap)); - - let sui_reserve = reserve(&lending_market); - let reserve_borrowed_amount = reserve::borrowed_amount(sui_reserve); - - let deposited_amount = obligation::deposited_ctoken_amount(obligation); - let borrowed_amount = obligation::borrowed_amount(obligation); - - assert!(reserve_borrowed_amount == sub(old_reserve_borrowed_amount, decimal::from(1_000_000_000)), 0); - assert!(borrowed_amount == sub(old_borrowed_amount, decimal::from(1_000_000_000)), 0); - assert!(deposited_amount == old_deposited_amount - 11 * 1_000_000, 0); - - // check to see if we can do a full redeem even with rate limiter is disabled - update_rate_limiter_config( - &owner_cap, - &mut lending_market, - &clock, - rate_limiter::new_config(1, 0) // disabled - ); - - let tokens = redeem_ctokens_and_withdraw_liquidity( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - usdc, - option::some(exemption), - test_scenario::ctx(&mut scenario) - ); - assert!(coin::value(&tokens) == 10 * 1_000_000 + 400_000, 0); - - // claim fees - test_scenario::next_tx(&mut scenario, owner); - claim_fees( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - test_scenario::ctx(&mut scenario) - ); - - test_scenario::next_tx(&mut scenario, owner); - let ctoken_fees: Coin> = test_scenario::take_from_address( - &scenario, - lending_market.fee_receiver - ); - assert!(coin::value(&ctoken_fees) == 600_000, 0); - - test_utils::destroy(ctoken_fees); - test_utils::destroy(sui); - test_utils::destroy(tokens); - test_utils::destroy(obligation_owner_cap); - test_utils::destroy(owner_cap); - test_utils::destroy(lending_market); - test_utils::destroy(clock); - test_utils::destroy(prices); - test_utils::destroy(type_to_index); - test_scenario::end(scenario); - } - - #[test_only] - const MILLISECONDS_IN_DAY: u64 = 86_400_000; - - #[test] - fun test_liquidity_mining() { - use sui::test_utils::{Self}; - use suilend::test_usdc::{TEST_USDC}; - use suilend::test_sui::{TEST_SUI}; - use suilend::reserve_config::{Self, default_reserve_config}; - use suilend::mock_pyth::{Self}; - - use std::type_name::{Self}; - - let owner = @0x26; - - let mut scenario = test_scenario::begin(owner); - let State { mut clock, owner_cap, mut lending_market, mut prices, type_to_index } = setup({ - let mut bag = bag::new(test_scenario::ctx(&mut scenario)); - bag::add( - &mut bag, - type_name::get(), - ReserveArgs { - config: { - let config = default_reserve_config(); - let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); - reserve_config::set_open_ltv_pct(&mut builder, 50); - reserve_config::set_close_ltv_pct(&mut builder, 50); - reserve_config::set_max_close_ltv_pct(&mut builder, 50); - sui::test_utils::destroy(config); - - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }, - initial_deposit: 100 * 1_000_000 - } - ); - bag::add( - &mut bag, - type_name::get(), - ReserveArgs { - config: reserve_config::default_reserve_config(), - initial_deposit: 100 * 1_000_000_000 - } - ); - - bag - }, &mut scenario); - - let usdc_rewards = coin::mint_for_testing(100 * 1_000_000, test_scenario::ctx(&mut scenario)); - let sui_rewards = coin::mint_for_testing(100 * 1_000_000_000, test_scenario::ctx(&mut scenario)); - - add_pool_reward( - &owner_cap, - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - true, - usdc_rewards, - 0, - 10 * MILLISECONDS_IN_DAY, - &clock, - test_scenario::ctx(&mut scenario) - ); - - add_pool_reward( - &owner_cap, - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - true, - sui_rewards, - 4 * MILLISECONDS_IN_DAY, - 14 * MILLISECONDS_IN_DAY, - &clock, - test_scenario::ctx(&mut scenario) - ); - - clock::set_for_testing(&mut clock, 1 * MILLISECONDS_IN_DAY); - - // create obligation - let obligation_owner_cap = create_obligation( - &mut lending_market, - test_scenario::ctx(&mut scenario) - ); - - let coins = coin::mint_for_testing(100 * 1_000_000, test_scenario::ctx(&mut scenario)); - let ctokens = deposit_liquidity_and_mint_ctokens( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - coins, - test_scenario::ctx(&mut scenario) - ); - deposit_ctokens_into_obligation( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &obligation_owner_cap, - &clock, - ctokens, - test_scenario::ctx(&mut scenario) - ); - - - // set reserve parameters and prices - mock_pyth::update_price(&mut prices, 1, 0, &clock); // $1 - mock_pyth::update_price(&mut prices, 1, 1, &clock); // $10 - - refresh_reserve_price( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - mock_pyth::get_price_obj(&prices) - ); - refresh_reserve_price( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - mock_pyth::get_price_obj(&prices) - ); - let sui = borrow( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &obligation_owner_cap, - &clock, - 1_000_000_000, - test_scenario::ctx(&mut scenario) - ); - - clock::set_for_testing(&mut clock, 9 * MILLISECONDS_IN_DAY); - let claimed_usdc = claim_rewards( - &mut lending_market, - &obligation_owner_cap, - &clock, - *bag::borrow(&type_to_index, type_name::get()), - 0, - true, - test_scenario::ctx(&mut scenario) - ); - assert!(coin::value(&claimed_usdc) == 80 * 1_000_000, 0); - - // this fails because but rewards period is not over - // claim_rewards_and_deposit( - // &mut lending_market, - // obligation_owner_cap.obligation_id, - // &clock, - // *bag::borrow(&type_to_index, type_name::get()), - // 1, - // true, - // *bag::borrow(&type_to_index, type_name::get()), - // test_scenario::ctx(&mut scenario) - // ); - - let remaining_sui_rewards = cancel_pool_reward( - &owner_cap, - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - true, - 1, - &clock, - test_scenario::ctx(&mut scenario) - ); - assert!(coin::value(&remaining_sui_rewards) == 50 * 1_000_000_000, 0); - - claim_rewards_and_deposit( - &mut lending_market, - obligation_owner_cap.obligation_id, - &clock, - *bag::borrow(&type_to_index, type_name::get()), - 1, - true, - *bag::borrow(&type_to_index, type_name::get()), - test_scenario::ctx(&mut scenario) - ); - - assert!(obligation::deposited_ctoken_amount( - obligation(&lending_market, obligation_id(&obligation_owner_cap)) - ) == 49 * 1_000_000_000, 0); - assert!(obligation::borrowed_amount( - obligation(&lending_market, obligation_id(&obligation_owner_cap)) - ) == decimal::from(0), 0); - - let dust_sui_rewards = close_pool_reward( - &owner_cap, - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - true, - 1, - &clock, - test_scenario::ctx(&mut scenario) - ); - - assert!(coin::value(&dust_sui_rewards) == 0, 0); - - test_utils::destroy(dust_sui_rewards); - test_utils::destroy(remaining_sui_rewards); - test_utils::destroy(sui); - test_utils::destroy(owner_cap); - test_utils::destroy(obligation_owner_cap); - test_utils::destroy(claimed_usdc); - test_utils::destroy(lending_market); - test_utils::destroy(clock); - test_utils::destroy(prices); - test_utils::destroy(type_to_index); - test_scenario::end(scenario); - - } - - #[test] - public fun test_forgive_debt() { - use sui::test_utils::{Self}; - use suilend::test_usdc::{TEST_USDC}; - use suilend::test_sui::{TEST_SUI}; - use suilend::mock_pyth::{Self}; - use suilend::reserve_config::{Self, default_reserve_config}; - use suilend::decimal::{sub, eq}; - - use std::type_name::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - let State { mut clock, owner_cap, mut lending_market, mut prices, type_to_index } = setup({ - let mut bag = bag::new(test_scenario::ctx(&mut scenario)); - bag::add( - &mut bag, - type_name::get(), - ReserveArgs { - config: { - let config = default_reserve_config(); - let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); - reserve_config::set_open_ltv_pct(&mut builder, 50); - reserve_config::set_close_ltv_pct(&mut builder, 50); - reserve_config::set_max_close_ltv_pct(&mut builder, 50); - sui::test_utils::destroy(config); - - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }, - initial_deposit: 100 * 1_000_000 - } - ); - bag::add( - &mut bag, - type_name::get(), - ReserveArgs { - config: reserve_config::default_reserve_config(), - initial_deposit: 100 * 1_000_000_000 - } - ); - - bag - }, &mut scenario); - - clock::set_for_testing(&mut clock, 1 * 1000); - - // set reserve parameters and prices - mock_pyth::update_price(&mut prices, 1, 0, &clock); // $1 - mock_pyth::update_price(&mut prices, 1, 1, &clock); // $10 - - // create obligation - let obligation_owner_cap = create_obligation( - &mut lending_market, - test_scenario::ctx(&mut scenario) - ); - - let coins = coin::mint_for_testing(100 * 1_000_000, test_scenario::ctx(&mut scenario)); - let ctokens = deposit_liquidity_and_mint_ctokens( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - coins, - test_scenario::ctx(&mut scenario) - ); - deposit_ctokens_into_obligation( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &obligation_owner_cap, - &clock, - ctokens, - test_scenario::ctx(&mut scenario) - ); - - refresh_reserve_price( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - mock_pyth::get_price_obj(&prices) - ); - refresh_reserve_price( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - mock_pyth::get_price_obj(&prices) - ); - - let sui = borrow( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &obligation_owner_cap, - &clock, - 5 * 1_000_000_000, - test_scenario::ctx(&mut scenario) - ); - test_utils::destroy(sui); - - mock_pyth::update_price(&mut prices, 1, 2, &clock); // $10 - refresh_reserve_price( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - mock_pyth::get_price_obj(&prices) - ); - - // liquidate the obligation - let mut sui = coin::mint_for_testing(1 * 1_000_000_000, test_scenario::ctx(&mut scenario)); - let (usdc, _exemption) = liquidate( - &mut lending_market, - obligation_id(&obligation_owner_cap), - *bag::borrow(&type_to_index, type_name::get()), - *bag::borrow(&type_to_index, type_name::get()), - &clock, - &mut sui, - test_scenario::ctx(&mut scenario) - ); - - let obligation = obligation(&lending_market, obligation_id(&obligation_owner_cap)); - let sui_reserve = reserve(&lending_market); - let old_reserve_borrowed_amount = reserve::borrowed_amount(sui_reserve); - let old_borrowed_amount = obligation::borrowed_amount(obligation); - - forgive( - &owner_cap, - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - obligation_id(&obligation_owner_cap), - &clock, - 1_000_000_000, - ); - - let obligation = obligation(&lending_market, obligation_id(&obligation_owner_cap)); - let sui_reserve = reserve(&lending_market); - let reserve_borrowed_amount = reserve::borrowed_amount(sui_reserve); - let borrowed_amount = obligation::borrowed_amount(obligation); - - assert!(eq(sub(old_borrowed_amount, borrowed_amount), decimal::from(1_000_000_000)), 0); - assert!(eq(sub(old_reserve_borrowed_amount, reserve_borrowed_amount), decimal::from(1_000_000_000)), 0); - - test_utils::destroy(usdc); - test_utils::destroy(sui); - test_utils::destroy(obligation_owner_cap); - test_utils::destroy(owner_cap); - test_utils::destroy(lending_market); - test_utils::destroy(clock); - test_utils::destroy(prices); - test_utils::destroy(type_to_index); - test_scenario::end(scenario); - } - - #[test] - public fun test_max_borrow() { - use sui::test_utils::{Self}; - use suilend::test_usdc::{TEST_USDC}; - use suilend::test_sui::{TEST_SUI}; - use suilend::mock_pyth::{Self}; - use suilend::reserve_config::{Self, default_reserve_config}; - - use std::type_name::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - let State { mut clock, owner_cap, mut lending_market, mut prices, type_to_index } = setup({ - let mut bag = bag::new(test_scenario::ctx(&mut scenario)); - bag::add( - &mut bag, - type_name::get(), - ReserveArgs { - config: { - let config = default_reserve_config(); - let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); - reserve_config::set_open_ltv_pct(&mut builder, 50); - reserve_config::set_close_ltv_pct(&mut builder, 50); - reserve_config::set_max_close_ltv_pct(&mut builder, 50); - sui::test_utils::destroy(config); - - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }, - initial_deposit: 100 * 1_000_000 - } - ); - bag::add( - &mut bag, - type_name::get(), - ReserveArgs { - config: { - let config = reserve_config::default_reserve_config(); - let mut builder = reserve_config::from( - &config, - test_scenario::ctx(&mut scenario) - ); - - test_utils::destroy(config); - - reserve_config::set_borrow_fee_bps(&mut builder, 10); - // reserve_config::set_borrow_limit(&mut builder, 4 * 1_000_000_000); - // reserve_config::set_borrow_limit_usd(&mut builder, 20); - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }, - initial_deposit: 100 * 1_000_000_000 - } - ); - - bag - }, &mut scenario); - - clock::set_for_testing(&mut clock, 1 * 1000); - - // set reserve parameters and prices - mock_pyth::update_price(&mut prices, 1, 0, &clock); // $1 - mock_pyth::update_price(&mut prices, 1, 1, &clock); // $10 - - // create obligation - let obligation_owner_cap = create_obligation( - &mut lending_market, - test_scenario::ctx(&mut scenario) - ); - - let coins = coin::mint_for_testing(100 * 1_000_000, test_scenario::ctx(&mut scenario)); - let ctokens = deposit_liquidity_and_mint_ctokens( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - coins, - test_scenario::ctx(&mut scenario) - ); - deposit_ctokens_into_obligation( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &obligation_owner_cap, - &clock, - ctokens, - test_scenario::ctx(&mut scenario) - ); - - refresh_reserve_price( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - mock_pyth::get_price_obj(&prices) - ); - refresh_reserve_price( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - mock_pyth::get_price_obj(&prices) - ); - - let sui = borrow( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &obligation_owner_cap, - &clock, - U64_MAX, - test_scenario::ctx(&mut scenario) - ); - - assert!(coin::value(&sui) == 4_995_004_995, 0); - - test_utils::destroy(sui); - test_utils::destroy(obligation_owner_cap); - test_utils::destroy(owner_cap); - test_utils::destroy(lending_market); - test_utils::destroy(clock); - test_utils::destroy(prices); - test_utils::destroy(type_to_index); - test_scenario::end(scenario); - } - - #[test] - public fun test_max_withdraw() { - use sui::test_utils::{Self}; - use suilend::test_usdc::{TEST_USDC}; - use suilend::test_sui::{TEST_SUI}; - use suilend::mock_pyth::{Self}; - use suilend::reserve_config::{Self, default_reserve_config}; - - use std::type_name::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - let State { mut clock, owner_cap, mut lending_market, mut prices, type_to_index } = setup({ - let mut bag = bag::new(test_scenario::ctx(&mut scenario)); - bag::add( - &mut bag, - type_name::get(), - ReserveArgs { - config: { - let config = default_reserve_config(); - let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); - reserve_config::set_open_ltv_pct(&mut builder, 50); - reserve_config::set_close_ltv_pct(&mut builder, 50); - reserve_config::set_max_close_ltv_pct(&mut builder, 50); - sui::test_utils::destroy(config); - - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }, - initial_deposit: 100 * 1_000_000 - } - ); - bag::add( - &mut bag, - type_name::get(), - ReserveArgs { - config: { - let config = default_reserve_config(); - let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); - reserve_config::set_borrow_weight_bps(&mut builder, 20_000); - sui::test_utils::destroy(config); - - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }, - initial_deposit: 100 * 1_000_000_000 - } - ); - - bag - }, &mut scenario); - - clock::set_for_testing(&mut clock, 1 * 1000); - - // set reserve parameters and prices - mock_pyth::update_price(&mut prices, 1, 0, &clock); // $1 - mock_pyth::update_price(&mut prices, 1, 1, &clock); // $10 - - // create obligation - let obligation_owner_cap = create_obligation( - &mut lending_market, - test_scenario::ctx(&mut scenario) - ); - - let coins = coin::mint_for_testing(200 * 1_000_000, test_scenario::ctx(&mut scenario)); - let ctokens = deposit_liquidity_and_mint_ctokens( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - coins, - test_scenario::ctx(&mut scenario) - ); - - deposit_ctokens_into_obligation( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &obligation_owner_cap, - &clock, - ctokens, - test_scenario::ctx(&mut scenario) - ); - - refresh_reserve_price( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - mock_pyth::get_price_obj(&prices) - ); - refresh_reserve_price( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - mock_pyth::get_price_obj(&prices) - ); - - let sui = borrow( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &obligation_owner_cap, - &clock, - 2_500_000_000, - test_scenario::ctx(&mut scenario) - ); - - update_rate_limiter_config( - &owner_cap, - &mut lending_market, - &clock, - rate_limiter::new_config(1, 10) // disabled - ); - - let cusdc = withdraw_ctokens( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &obligation_owner_cap, - &clock, - U64_MAX, - test_scenario::ctx(&mut scenario) - ); - let usdc = redeem_ctokens_and_withdraw_liquidity( - &mut lending_market, - *bag::borrow(&type_to_index, type_name::get()), - &clock, - cusdc, - option::none(), - test_scenario::ctx(&mut scenario) - ); - - assert!(coin::value(&usdc) == 10 * 1_000_000, 0); - - test_utils::destroy(sui); - test_utils::destroy(usdc); - test_utils::destroy(obligation_owner_cap); - test_utils::destroy(owner_cap); - test_utils::destroy(lending_market); - test_utils::destroy(clock); - test_utils::destroy(prices); - test_utils::destroy(type_to_index); - test_scenario::end(scenario); - } - - #[test] - public fun test_change_pyth_price_feed() { - use sui::test_utils::{Self, assert_eq}; - use sui::test_scenario::ctx; - use suilend::test_usdc::{TEST_USDC}; - use suilend::test_sui::{TEST_SUI}; - use suilend::mock_pyth::{Self}; - use suilend::reserve_config::{Self, default_reserve_config}; - - use std::type_name::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - let State { mut clock, owner_cap, mut lending_market, mut prices, type_to_index } = setup({ - let mut bag = bag::new(test_scenario::ctx(&mut scenario)); - bag::add( - &mut bag, - type_name::get(), - ReserveArgs { - config: { - let config = default_reserve_config(); - let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); - reserve_config::set_open_ltv_pct(&mut builder, 50); - reserve_config::set_close_ltv_pct(&mut builder, 50); - reserve_config::set_max_close_ltv_pct(&mut builder, 50); - sui::test_utils::destroy(config); - - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }, - initial_deposit: 100 * 1_000_000 - } - ); - bag::add( - &mut bag, - type_name::get(), - ReserveArgs { - config: { - let config = default_reserve_config(); - let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); - reserve_config::set_borrow_weight_bps(&mut builder, 20_000); - sui::test_utils::destroy(config); - - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }, - initial_deposit: 100 * 1_000_000_000 - } - ); - - bag - }, &mut scenario); - - clock::set_for_testing(&mut clock, 1 * 1000); - - // change the price feed as admin - let new_price_info_obj = mock_pyth::new_price_info_obj(3_u8, ctx(&mut scenario)); - - let array_idx = *bag::borrow(&type_to_index, type_name::get()); - - change_reserve_price_feed( - &owner_cap, - &mut lending_market, - array_idx, - &new_price_info_obj, - &clock, - ); - - // TODO: assert changes - let reserve_ref = reserve(&lending_market); - let price_id = pyth::price_info::get_price_identifier( - &pyth::price_info::get_price_info_from_price_info_object(&new_price_info_obj) - ); - - assert_eq(*reserve::price_identifier(reserve_ref), price_id); - - test_utils::destroy(owner_cap); - test_utils::destroy(lending_market); - test_utils::destroy(clock); - test_utils::destroy(prices); - test_utils::destroy(type_to_index); - test_utils::destroy(new_price_info_obj); - test_scenario::end(scenario); - } - - #[test] - public fun test_admin_new_obligation_cap() { - use sui::test_utils::{Self}; - use suilend::test_usdc::{TEST_USDC}; - use suilend::test_sui::{TEST_SUI}; - use suilend::mock_pyth::{Self}; - use suilend::reserve_config::{Self, default_reserve_config}; - - use std::type_name::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - let State { mut clock, owner_cap, mut lending_market, mut prices, type_to_index } = setup({ - let mut bag = bag::new(test_scenario::ctx(&mut scenario)); - bag::add( - &mut bag, - type_name::get(), - ReserveArgs { - config: { - let config = default_reserve_config(); - let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); - reserve_config::set_open_ltv_pct(&mut builder, 50); - reserve_config::set_close_ltv_pct(&mut builder, 50); - reserve_config::set_max_close_ltv_pct(&mut builder, 50); - sui::test_utils::destroy(config); - - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }, - initial_deposit: 100 * 1_000_000 - } - ); - bag::add( - &mut bag, - type_name::get(), - ReserveArgs { - config: { - let config = default_reserve_config(); - let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); - reserve_config::set_borrow_weight_bps(&mut builder, 20_000); - sui::test_utils::destroy(config); - - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }, - initial_deposit: 100 * 1_000_000_000 - } - ); - - bag - }, &mut scenario); - - clock::set_for_testing(&mut clock, 1 * 1000); - - // set reserve parameters and prices - mock_pyth::update_price(&mut prices, 1, 0, &clock); // $1 - mock_pyth::update_price(&mut prices, 1, 1, &clock); // $10 - - // create obligation - let obligation_owner_cap = create_obligation( - &mut lending_market, - test_scenario::ctx(&mut scenario) - ); - - let obligation_id = obligation_owner_cap.obligation_id; - - // Mock accidental burning of obligation cap - transfer::public_transfer(obligation_owner_cap, @0x0); - - let obligation_owner_cap = new_obligation_owner_cap( - &owner_cap, - &lending_market, - obligation_id, - test_scenario::ctx(&mut scenario) - ); - - assert!(obligation_owner_cap.obligation_id == obligation_id, 0); - - test_utils::destroy(obligation_owner_cap); - test_utils::destroy(owner_cap); - test_utils::destroy(lending_market); - test_utils::destroy(clock); - test_utils::destroy(prices); - test_utils::destroy(type_to_index); - test_scenario::end(scenario); - } } diff --git a/contracts/suilend/sources/obligation.move b/contracts/suilend/sources/obligation.move index 98cd7b3..b536bd5 100644 --- a/contracts/suilend/sources/obligation.move +++ b/contracts/suilend/sources/obligation.move @@ -19,12 +19,6 @@ module suilend::obligation { use suilend::decimal::{Self, Decimal, mul, add, sub, div, gt, lt, min, floor, le, eq, saturating_sub}; use suilend::liquidity_mining::{Self, UserRewardManager, PoolRewardManager}; - #[test_only] - use sui::test_scenario::{Self, Scenario}; - - #[test_only] - use sui::clock::{Self}; - // === Errors === const EObligationIsNotLiquidatable: u64 = 0; const EObligationIsNotHealthy: u64 = 1; @@ -586,6 +580,101 @@ module suilend::obligation { } // === Public-View Functions + public fun deposits

(obligation: &Obligation

): &vector { + &obligation.deposits + } + + public fun borrows

(obligation: &Obligation

): &vector { + &obligation.borrows + } + + public fun deposited_value_usd

(obligation: &Obligation

): Decimal { + obligation.deposited_value_usd + } + + public fun allowed_borrow_value_usd

(obligation: &Obligation

): Decimal { + obligation.allowed_borrow_value_usd + } + + public fun unhealthy_borrow_value_usd

(obligation: &Obligation

): Decimal { + obligation.unhealthy_borrow_value_usd + } + + public fun unweighted_borrowed_value_usd

(obligation: &Obligation

): Decimal { + obligation.unweighted_borrowed_value_usd + } + + public fun weighted_borrowed_value_usd

(obligation: &Obligation

): Decimal { + obligation.weighted_borrowed_value_usd + } + + public fun weighted_borrowed_value_upper_bound_usd

(obligation: &Obligation

): Decimal { + obligation.weighted_borrowed_value_upper_bound_usd + } + + public fun borrowing_isolated_asset

(obligation: &Obligation

): bool { + obligation.borrowing_isolated_asset + } + + public fun user_reward_managers

(obligation: &Obligation

): &vector { + &obligation.user_reward_managers + } + + public use fun deposit_coin_type as Deposit.coin_type; + public fun deposit_coin_type(deposit: &Deposit): TypeName { + deposit.coin_type + } + + public use fun deposit_reserve_array_index as Deposit.reserve_array_index; + public fun deposit_reserve_array_index(deposit: &Deposit): u64 { + deposit.reserve_array_index + } + + public use fun deposit_deposited_ctoken_amount as Deposit.deposited_ctoken_amount; + public fun deposit_deposited_ctoken_amount(deposit: &Deposit): u64 { + deposit.deposited_ctoken_amount + } + + public use fun deposit_market_value as Deposit.market_value; + public fun deposit_market_value(deposit: &Deposit): Decimal { + deposit.market_value + } + + public use fun deposit_user_reward_manager_index as Deposit.user_reward_manager_index; + public fun deposit_user_reward_manager_index(deposit: &Deposit): u64 { + deposit.user_reward_manager_index + } + + public use fun borrow_coin_type as Borrow.coin_type; + public fun borrow_coin_type(borrow: &Borrow): TypeName { + borrow.coin_type + } + + public use fun borrow_reserve_array_index as Borrow.reserve_array_index; + public fun borrow_reserve_array_index(borrow: &Borrow): u64 { + borrow.reserve_array_index + } + + public use fun borrow_borrowed_amount as Borrow.borrowed_amount; + public fun borrow_borrowed_amount(borrow: &Borrow): Decimal { + borrow.borrowed_amount + } + + public use fun borrow_cumulative_borrow_rate as Borrow.cumulative_borrow_rate; + public fun borrow_cumulative_borrow_rate(borrow: &Borrow): Decimal { + borrow.cumulative_borrow_rate + } + + public use fun borrow_market_value as Borrow.market_value; + public fun borrow_market_value(borrow: &Borrow): Decimal { + borrow.market_value + } + + public use fun borrow_user_reward_manager_index as Borrow.user_reward_manager_index; + public fun borrow_user_reward_manager_index(borrow: &Borrow): u64 { + borrow.user_reward_manager_index + } + public fun deposited_ctoken_amount(obligation: &Obligation

): u64 { let mut i = 0; while (i < vector::length(&obligation.deposits)) { @@ -691,7 +780,7 @@ module suilend::obligation { } // === Private Functions === - fun is_looped

(obligation: &Obligation

): bool { + public(package) fun is_looped

(obligation: &Obligation

): bool { let target_reserve_array_indices = vector[ 1, 2, 5, 7, 3, 9 ]; @@ -997,7 +1086,7 @@ module suilend::obligation { i } - fun find_borrow

( + public(package) fun find_borrow

( obligation: &Obligation

, reserve: &Reserve

, ): &Borrow { @@ -1007,7 +1096,7 @@ module suilend::obligation { vector::borrow(&obligation.borrows, i) } - fun find_deposit

( + public(package) fun find_deposit

( obligation: &Obligation

, reserve: &Reserve

, ): &Deposit { @@ -1075,7 +1164,7 @@ module suilend::obligation { vector::length(&obligation.deposits) - 1 } - fun find_user_reward_manager_index

( + public(package) fun find_user_reward_manager_index

( obligation: &Obligation

, pool_reward_manager: &PoolRewardManager, ): u64 { @@ -1109,2279 +1198,27 @@ module suilend::obligation { (length - 1, vector::borrow_mut(&mut obligation.user_reward_managers, length - 1)) } - // === Test Functions === - #[test_only] - public struct TEST_MARKET {} - - #[test_only] - public struct TEST_SUI {} - - #[test_only] - public struct TEST_USDC {} - - #[test_only] - public struct TEST_USDT {} - - #[test_only] - public struct TEST_ETH {} - - #[test_only] - public struct TEST_AUSD {} - - #[test_only] - use suilend::reserve_config::{Self, default_reserve_config}; - - #[test_only] - fun sui_reserve

(scenario: &mut Scenario): Reserve

{ - let config = default_reserve_config(); - let mut builder = reserve_config::from(&config, test_scenario::ctx(scenario)); - reserve_config::set_open_ltv_pct(&mut builder, 20); - reserve_config::set_close_ltv_pct(&mut builder, 50); - reserve_config::set_max_close_ltv_pct(&mut builder, 50); - reserve_config::set_interest_rate_utils(&mut builder, { - let mut v = vector::empty(); - vector::push_back(&mut v, 0); - vector::push_back(&mut v, 100); - v - }); - reserve_config::set_interest_rate_aprs(&mut builder, { - let mut v = vector::empty(); - vector::push_back(&mut v, 31536000 * 4); - vector::push_back(&mut v, 31536000 * 8); - v - }); - - sui::test_utils::destroy(config); - let config = reserve_config::build(builder, test_scenario::ctx(scenario)); - reserve::create_for_testing( - config, - 0, - 9, - decimal::from(10), - 0, - 0, - 0, - decimal::from(0), - decimal::from(3), - 0, - test_scenario::ctx(scenario) - ) - } - - #[test_only] - fun usdc_reserve

(scenario: &mut Scenario): Reserve

{ - let config = default_reserve_config(); - let mut builder = reserve_config::from(&config, test_scenario::ctx(scenario)); - reserve_config::set_open_ltv_pct(&mut builder, 50); - reserve_config::set_close_ltv_pct(&mut builder, 80); - reserve_config::set_max_close_ltv_pct(&mut builder, 80); - reserve_config::set_borrow_weight_bps(&mut builder, 20_000); - reserve_config::set_interest_rate_utils(&mut builder, { - let mut v = vector::empty(); - vector::push_back(&mut v, 0); - vector::push_back(&mut v, 100); - v - }); - reserve_config::set_interest_rate_aprs(&mut builder, { - let mut v = vector::empty(); - vector::push_back(&mut v, 3153600000); - vector::push_back(&mut v, 3153600000 * 2); - v - }); - - sui::test_utils::destroy(config); - let config = reserve_config::build(builder, test_scenario::ctx(scenario)); - - reserve::create_for_testing( - config, - 1, - 6, - decimal::from(1), - 0, - 0, - 0, - decimal::from(0), - decimal::from(2), - 0, - test_scenario::ctx(scenario) - ) - } - - #[test_only] - fun usdt_reserve

(scenario: &mut Scenario): Reserve

{ - let config = default_reserve_config(); - let mut builder = reserve_config::from(&config, test_scenario::ctx(scenario)); - reserve_config::set_open_ltv_pct(&mut builder, 50); - reserve_config::set_close_ltv_pct(&mut builder, 80); - reserve_config::set_max_close_ltv_pct(&mut builder, 80); - reserve_config::set_borrow_weight_bps(&mut builder, 20_000); - reserve_config::set_interest_rate_utils(&mut builder, { - let mut v = vector::empty(); - vector::push_back(&mut v, 0); - vector::push_back(&mut v, 100); - v - }); - reserve_config::set_interest_rate_aprs(&mut builder, { - let mut v = vector::empty(); - vector::push_back(&mut v, 3153600000); - vector::push_back(&mut v, 3153600000 * 2); - - v - }); - - sui::test_utils::destroy(config); - let config = reserve_config::build(builder, test_scenario::ctx(scenario)); - - reserve::create_for_testing( - config, - 2, - 6, - decimal::from(1), - 0, - 0, - 0, - decimal::from(0), - decimal::from(2), - 0, - test_scenario::ctx(scenario) - ) - } - - #[test_only] - fun eth_reserve

(scenario: &mut Scenario): Reserve

{ - let config = default_reserve_config(); - let mut builder = reserve_config::from(&config, test_scenario::ctx(scenario)); - reserve_config::set_open_ltv_pct(&mut builder, 10); - reserve_config::set_close_ltv_pct(&mut builder, 20); - reserve_config::set_max_close_ltv_pct(&mut builder, 20); - reserve_config::set_borrow_weight_bps(&mut builder, 30_000); - reserve_config::set_interest_rate_utils(&mut builder, { - let mut v = vector::empty(); - vector::push_back(&mut v, 0); - vector::push_back(&mut v, 100); - v - }); - reserve_config::set_interest_rate_aprs(&mut builder, { - let mut v = vector::empty(); - vector::push_back(&mut v, 3153600000 * 10); - vector::push_back(&mut v, 3153600000 * 20); - - v - }); - - sui::test_utils::destroy(config); - let config = reserve_config::build(builder, test_scenario::ctx(scenario)); - - reserve::create_for_testing( - config, - 3, - 8, - decimal::from(2000), - 0, - 0, - 0, - decimal::from(0), - decimal::from(3), - 0, - test_scenario::ctx(scenario) - ) - } - - #[test_only] - fun ausd_reserve

(scenario: &mut Scenario): Reserve

{ - let config = default_reserve_config(); - let mut builder = reserve_config::from(&config, test_scenario::ctx(scenario)); - reserve_config::set_open_ltv_pct(&mut builder, 50); - reserve_config::set_close_ltv_pct(&mut builder, 80); - reserve_config::set_max_close_ltv_pct(&mut builder, 80); - reserve_config::set_borrow_weight_bps(&mut builder, 20_000); - reserve_config::set_interest_rate_utils(&mut builder, { - let mut v = vector::empty(); - vector::push_back(&mut v, 0); - vector::push_back(&mut v, 100); - v - }); - reserve_config::set_interest_rate_aprs(&mut builder, { - let mut v = vector::empty(); - vector::push_back(&mut v, 3153600000); - vector::push_back(&mut v, 3153600000 * 2); - - v - }); - - sui::test_utils::destroy(config); - let config = reserve_config::build(builder, test_scenario::ctx(scenario)); - - reserve::create_for_testing( - config, - 5, - 6, - decimal::from(1), - 0, - 0, - 0, - decimal::from(0), - decimal::from(2), - 0, - test_scenario::ctx(scenario) - ) - } - - #[test_only] - fun reserves

(scenario: &mut Scenario): vector> { - let mut v = vector::empty(); - vector::push_back(&mut v, sui_reserve(scenario)); - vector::push_back(&mut v, usdc_reserve(scenario)); - vector::push_back(&mut v, usdt_reserve(scenario)); - vector::push_back(&mut v, eth_reserve(scenario)); - vector::push_back(&mut v, ausd_reserve(scenario)); - - v - } - - #[test_only] - fun get_reserve_array_index(reserves: &vector>): u64 { - let mut i = 0; - while (i < vector::length(reserves)) { - let reserve = vector::borrow(reserves, i); - if (type_name::get() == reserve::coin_type(reserve)) { - return i - }; - - i = i + 1; - }; - - i - } - #[test_only] - fun get_reserve(reserves: &vector>): &Reserve

{ - let i = get_reserve_array_index(reserves); - assert!(i < vector::length(reserves), 0); - vector::borrow(reserves, i) + public(package) fun borrows_mut

(obligation: &mut Obligation

): &mut vector { + &mut obligation.borrows } #[test_only] - fun get_reserve_mut(reserves: &mut vector>): &mut Reserve

{ - let i = get_reserve_array_index(reserves); - assert!(i < vector::length(reserves), 0); - vector::borrow_mut(reserves, i) - } - - - #[test] - public fun test_deposit() { - use sui::test_utils::{Self}; - - let owner = @0x26; - 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 obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); - - reserve::update_price_for_testing( - &mut usdc_reserve, - &clock, - decimal::from(1), - decimal::from_percent(90) - ); - reserve::update_price_for_testing( - &mut sui_reserve, - &clock, - decimal::from(10), - decimal::from(9) - ); - - deposit(&mut obligation, &mut usdc_reserve, &clock, 100 * 1_000_000); - deposit(&mut obligation, &mut usdc_reserve, &clock, 100 * 1_000_000); - deposit(&mut obligation, &mut sui_reserve, &clock, 100 * 1_000_000_000); - - assert!(vector::length(&obligation.deposits) == 2, 0); - - let usdc_deposit = vector::borrow(&obligation.deposits, 0); - assert!(usdc_deposit.deposited_ctoken_amount == 200 * 1_000_000, 1); - assert!(usdc_deposit.market_value == decimal::from(200), 2); - - let user_reward_manager = vector::borrow(&obligation.user_reward_managers, usdc_deposit.user_reward_manager_index); - assert!(liquidity_mining::shares(user_reward_manager) == 200 * 1_000_000, 5); - - let sui_deposit = vector::borrow(&obligation.deposits, 1); - 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, 6); - - assert!(vector::length(&obligation.borrows) == 0, 0); - assert!(obligation.deposited_value_usd == decimal::from(1200), 0); - assert!(obligation.allowed_borrow_value_usd == decimal::from(270), 1); - assert!(obligation.unhealthy_borrow_value_usd == decimal::from(660), 2); - assert!(obligation.unweighted_borrowed_value_usd == decimal::from(0), 3); - assert!(obligation.weighted_borrowed_value_usd == decimal::from(0), 4); - - sui::test_utils::destroy(lending_market_id); - test_utils::destroy(usdc_reserve); - test_utils::destroy(sui_reserve); - test_utils::destroy(obligation); - clock::destroy_for_testing(clock); - test_scenario::end(scenario); - } - - #[test] - #[expected_failure(abort_code = EObligationIsNotHealthy)] - public fun test_borrow_fail() { - use sui::test_scenario::{Self}; - - let owner = @0x26; - 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 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); - - sui::test_utils::destroy(lending_market_id); - sui::test_utils::destroy(usdc_reserve); - sui::test_utils::destroy(sui_reserve); - sui::test_utils::destroy(obligation); - sui::test_utils::destroy(clock); - test_scenario::end(scenario); - } - - #[test] - #[expected_failure(abort_code = ECannotDepositAndBorrowSameAsset)] - public fun test_borrow_fail_deposit_borrow_same_asset_1() { - use sui::test_scenario::{Self}; - - let owner = @0x26; - 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 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, 1); - deposit(&mut obligation, &mut 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(obligation); - sui::test_utils::destroy(clock); - test_scenario::end(scenario); - } - - #[test] - #[expected_failure(abort_code = ECannotDepositAndBorrowSameAsset)] - public fun test_borrow_fail_deposit_borrow_same_asset_2() { - use sui::test_scenario::{Self}; - - let owner = @0x26; - 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 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); - - sui::test_utils::destroy(lending_market_id); - sui::test_utils::destroy(sui_reserve); - sui::test_utils::destroy(obligation); - sui::test_utils::destroy(clock); - test_scenario::end(scenario); - } - - #[test] - public fun test_borrow_isolated_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)); - - deposit( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 100 * 1_000_000_000 - ); - - let config = { - let mut builder = reserve_config::from( - config(get_reserve(&reserves)), - test_scenario::ctx(&mut scenario) - ); - reserve_config::set_open_ltv_pct(&mut builder, 0); - reserve_config::set_close_ltv_pct(&mut builder, 0); - reserve_config::set_isolated(&mut builder, true); - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }; - - reserve::update_reserve_config( - get_reserve_mut(&mut reserves), - config - ); - - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1 - ); - - refresh(&mut obligation, &mut reserves, &clock); - - // this fails - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1 - ); - - sui::test_utils::destroy(clock); - sui::test_utils::destroy(reserves); - sui::test_utils::destroy(lending_market_id); - sui::test_utils::destroy(obligation); - test_scenario::end(scenario); - } - - #[test] - #[expected_failure(abort_code = EIsolatedAssetViolation)] - public fun test_borrow_isolated_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)); - - deposit( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 100 * 1_000_000_000 - ); - - let config = { - let mut builder = reserve_config::from( - config(get_reserve(&reserves)), - test_scenario::ctx(&mut scenario) - ); - reserve_config::set_open_ltv_pct(&mut builder, 0); - reserve_config::set_close_ltv_pct(&mut builder, 0); - reserve_config::set_isolated(&mut builder, true); - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }; - - reserve::update_reserve_config( - get_reserve_mut(&mut reserves), - config - ); - - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1 - ); - - refresh(&mut obligation, &mut reserves, &clock); - - // this fails - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1 - ); - - sui::test_utils::destroy(clock); - sui::test_utils::destroy(reserves); - sui::test_utils::destroy(lending_market_id); - sui::test_utils::destroy(obligation); - test_scenario::end(scenario); - } - - #[test] - #[expected_failure(abort_code = EIsolatedAssetViolation)] - public fun test_borrow_isolated_fail_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)); - - deposit( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 100 * 1_000_000_000 - ); - - let config = { - let mut builder = reserve_config::from( - config(get_reserve(&reserves)), - test_scenario::ctx(&mut scenario) - ); - reserve_config::set_open_ltv_pct(&mut builder, 0); - reserve_config::set_close_ltv_pct(&mut builder, 0); - reserve_config::set_isolated(&mut builder, true); - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }; - - reserve::update_reserve_config( - get_reserve_mut(&mut reserves), - config - ); - - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1 - ); - - refresh(&mut obligation, &mut reserves, &clock); - - // this fails - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1 - ); - - sui::test_utils::destroy(clock); - sui::test_utils::destroy(reserves); - sui::test_utils::destroy(lending_market_id); - sui::test_utils::destroy(obligation); - test_scenario::end(scenario); - } - - #[test] - public fun test_max_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 usdc_reserve = usdc_reserve(&mut scenario); - let mut sui_reserve = sui_reserve(&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, - decimal::from(1), - decimal::from(2) - ); - reserve::update_price_for_testing( - &mut sui_reserve, - &clock, - decimal::from(10), - decimal::from(5) - ); - - deposit(&mut obligation, &mut sui_reserve, &clock, 100 * 1_000_000_000); - - let max_borrow = max_borrow_amount(&obligation, &usdc_reserve); - assert!(max_borrow == 25_000_000, 0); - - sui::test_utils::destroy(lending_market_id); - sui::test_utils::destroy(usdc_reserve); - sui::test_utils::destroy(sui_reserve); - sui::test_utils::destroy(obligation); - clock::destroy_for_testing(clock); - test_scenario::end(scenario); - } - - #[test] - public fun test_borrow_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 usdc_reserve = usdc_reserve(&mut scenario); - let mut sui_reserve = sui_reserve(&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, - decimal::from(1), - decimal::from(2) - ); - reserve::update_price_for_testing( - &mut 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); - - 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!(vector::length(&obligation.borrows) == 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); - - assert!(obligation.deposited_value_usd == decimal::from(1000), 0); - assert!(obligation.allowed_borrow_value_usd == decimal::from(100), 1); - assert!(obligation.unhealthy_borrow_value_usd == decimal::from(500), 2); - assert!(obligation.unweighted_borrowed_value_usd == decimal::from(25), 3); - assert!(obligation.weighted_borrowed_value_usd == decimal::from(50), 4); - 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(obligation); - clock::destroy_for_testing(clock); - test_scenario::end(scenario); - } - - #[test] - #[expected_failure(abort_code = EObligationIsNotHealthy)] - public fun test_withdraw_fail_unhealthy() { - 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 usdc_reserve = usdc_reserve(&mut scenario); - let mut sui_reserve = sui_reserve(&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); - - withdraw(&mut obligation, &mut 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(obligation); - sui::test_utils::destroy(clock); - test_scenario::end(scenario); - } - - #[test] - #[expected_failure(abort_code = EDepositNotFound)] - public fun test_withdraw_fail_deposit_not_found() { - 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 usdc_reserve = usdc_reserve(&mut scenario); - let mut sui_reserve = sui_reserve(&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); - - withdraw(&mut obligation, &mut 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(obligation); - sui::test_utils::destroy(clock); - test_scenario::end(scenario); - } - - #[test] - public fun test_max_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 usdc_reserve = usdc_reserve(&mut scenario); - let mut usdt_reserve = usdt_reserve(&mut scenario); - let mut sui_reserve = sui_reserve(&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, - decimal::from(1), - decimal::from(2) - ); - reserve::update_price_for_testing( - &mut usdt_reserve, - &clock, - decimal::from(1), - decimal::from(2) - ); - reserve::update_price_for_testing( - &mut sui_reserve, - &clock, - decimal::from(10), - decimal::from(5) - ); - - deposit(&mut obligation, &mut sui_reserve, &clock, 100 * 1_000_000_000); - - let amount = max_withdraw_amount(&obligation, &sui_reserve); - assert!(amount == 100 * 1_000_000_000, 0); - - borrow(&mut obligation, &mut usdc_reserve, &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 = max_withdraw_amount(&obligation, &sui_reserve); - assert!(amount == 20 * 1_000_000_000, 0); - - deposit(&mut obligation, &mut usdt_reserve, &clock, 100 * 1_000_000); - - let amount = max_withdraw_amount(&obligation, &usdt_reserve); - 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(obligation); - clock::destroy_for_testing(clock); - test_scenario::end(scenario); - } - - - #[test] - public fun test_withdraw_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 usdc_reserve = usdc_reserve(&mut scenario); - let mut sui_reserve = sui_reserve(&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, - decimal::from(1), - decimal::from(2) - ); - reserve::update_price_for_testing( - &mut 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, 20 * 1_000_000); - withdraw(&mut obligation, &mut sui_reserve, &clock, 20 * 1_000_000_000); - - assert!(vector::length(&obligation.deposits) == 1, 0); - - let sui_deposit = vector::borrow(&obligation.deposits, 0); - assert!(sui_deposit.deposited_ctoken_amount == 80 * 1_000_000_000, 3); - assert!(sui_deposit.market_value == decimal::from(800), 4); - - let user_reward_manager = vector::borrow(&obligation.user_reward_managers, sui_deposit.user_reward_manager_index); - assert!(liquidity_mining::shares(user_reward_manager) == 80 * 1_000_000_000, 3); - - assert!(vector::length(&obligation.borrows) == 1, 0); - - let usdc_borrow = vector::borrow(&obligation.borrows, 0); - assert!(usdc_borrow.borrowed_amount == decimal::from(20 * 1_000_000), 1); - assert!(usdc_borrow.cumulative_borrow_rate == decimal::from(2), 2); - assert!(usdc_borrow.market_value == decimal::from(20), 3); - - let user_reward_manager = vector::borrow(&obligation.user_reward_managers, usdc_borrow.user_reward_manager_index); - assert!(liquidity_mining::shares(user_reward_manager) == 20 * 1_000_000 / 2, 4); - - assert!(obligation.deposited_value_usd == decimal::from(800), 0); - assert!(obligation.allowed_borrow_value_usd == decimal::from(80), 1); - assert!(obligation.unhealthy_borrow_value_usd == decimal::from(400), 2); - assert!(obligation.unweighted_borrowed_value_usd == decimal::from(20), 3); - assert!(obligation.weighted_borrowed_value_usd == decimal::from(40), 4); - 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); - clock::destroy_for_testing(clock); - sui::test_utils::destroy(obligation); - test_scenario::end(scenario); - } - - #[test] - public fun test_repay_happy() { - use sui::test_scenario::{Self}; - use sui::clock::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); - 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 obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); - - reserve::update_price_for_testing( - &mut usdc_reserve, - &clock, - decimal::from(1), - decimal::from(2) - ); - reserve::update_price_for_testing( - &mut 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, 25 * 1_000_000); - - clock::set_for_testing(&mut clock, 1000); - reserve::compound_interest(&mut usdc_reserve, &clock); - - let repay_amount = repay( - &mut obligation, - &mut usdc_reserve, - &clock, - decimal::from(25 * 1_000_000) - ); - assert!(repay_amount == decimal::from(25 * 1_000_000), 0); - - 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, 5); - - assert!(vector::length(&obligation.borrows) == 1, 0); - - // borrow was compounded by 1% so there should be borrows outstanding - let usdc_borrow = vector::borrow(&obligation.borrows, 0); - assert!(usdc_borrow.borrowed_amount == decimal::from(250_000), 1); - assert!(usdc_borrow.cumulative_borrow_rate == decimal::from_percent(202), 2); - assert!(usdc_borrow.market_value == decimal::from_percent(25), 3); - - let user_reward_manager = vector::borrow(&obligation.user_reward_managers, usdc_borrow.user_reward_manager_index); - // 250_000 / 2.02 = 123762.376238 - assert!(liquidity_mining::shares(user_reward_manager) == 123_762, 5); - - assert!(obligation.deposited_value_usd == decimal::from(1000), 0); - assert!(obligation.allowed_borrow_value_usd == decimal::from(100), 1); - assert!(obligation.unhealthy_borrow_value_usd == decimal::from(500), 2); - assert!(obligation.unweighted_borrowed_value_usd == decimal::from_percent(25), 3); - assert!(obligation.weighted_borrowed_value_usd == decimal::from_percent(50), 4); - 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); - clock::destroy_for_testing(clock); - sui::test_utils::destroy(obligation); - test_scenario::end(scenario); - } - - #[test] - public fun test_repay_happy_2() { - use sui::test_scenario::{Self}; - use sui::clock::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); - 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 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); - - clock::set_for_testing(&mut clock, 1000); - reserve::compound_interest(&mut usdc_reserve, &clock); - - let repay_amount = repay(&mut obligation, &mut usdc_reserve, &clock, decimal::from(500_000)); - assert!(repay_amount == decimal::from(500_000), 0); - - 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, 5); - - assert!(vector::length(&obligation.borrows) == 1, 0); - - // borrow was compounded by 1% so there should be borrows outstanding - let usdc_borrow = vector::borrow(&obligation.borrows, 0); - assert!(usdc_borrow.borrowed_amount == decimal::from(101 * 1_000_000 - 500_000), 1); - assert!(usdc_borrow.cumulative_borrow_rate == decimal::from_percent(202), 2); - assert!(usdc_borrow.market_value == decimal::from_percent_u64(10_050), 3); - - let user_reward_manager = vector::borrow(&obligation.user_reward_managers, usdc_borrow.user_reward_manager_index); - // (101 * 1e6 - 500_000) / 2.02 == 49752475.2475 - assert!(liquidity_mining::shares(user_reward_manager) == 49752475, 5); - - assert!(obligation.deposited_value_usd == decimal::from(1000), 0); - assert!(obligation.allowed_borrow_value_usd == decimal::from(200), 1); - assert!(obligation.unhealthy_borrow_value_usd == decimal::from(500), 2); - assert!(obligation.unweighted_borrowed_value_usd == decimal::from_percent_u64(10_050), 3); - 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); - clock::destroy_for_testing(clock); - sui::test_utils::destroy(obligation); - test_scenario::end(scenario); - } - - #[test] - public fun test_repay_regression() { - use sui::test_scenario::{Self}; - use sui::clock::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); - 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 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); - - clock::set_for_testing(&mut clock, 1000); - reserve::update_price_for_testing( - &mut usdc_reserve, - &clock, - decimal::from(10), - decimal::from(10) - ); - - reserve::compound_interest(&mut usdc_reserve, &clock); - let repay_amount = repay( - &mut obligation, - &mut usdc_reserve, - &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); - clock::destroy_for_testing(clock); - sui::test_utils::destroy(obligation); - test_scenario::end(scenario); - } - - #[test] - public fun test_repay_max() { - use sui::test_scenario::{Self}; - use sui::clock::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); - 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 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); - - - let repay_amount = repay( - &mut obligation, - &mut usdc_reserve, - &clock, - decimal::from(101 * 1_000_000) - ); - assert!(repay_amount == decimal::from(100 * 1_000_000), 0); - - 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); - - assert!(vector::length(&obligation.borrows) == 0, 0); - - let user_reward_manager_index = find_user_reward_manager_index( - &obligation, - reserve::borrows_pool_reward_manager_mut(&mut usdc_reserve) - ); - let user_reward_manager = vector::borrow(&obligation.user_reward_managers, user_reward_manager_index); - assert!(liquidity_mining::shares(user_reward_manager) == 0, 0); - - assert!(obligation.deposited_value_usd == decimal::from(1000), 0); - assert!(obligation.allowed_borrow_value_usd == decimal::from(200), 1); - assert!(obligation.unhealthy_borrow_value_usd == decimal::from(500), 2); - assert!(obligation.unweighted_borrowed_value_usd == decimal::from_percent_u64(0), 3); - 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); - clock::destroy_for_testing(clock); - sui::test_utils::destroy(obligation); - test_scenario::end(scenario); - } - - #[test] - #[expected_failure(abort_code = 0, location = reserve)] // price stale - 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); - let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); - let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); - clock::set_for_testing(&mut clock, 0); - - 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, - get_reserve_mut(&mut reserves), - &clock, - 100 * 1_000_000 - ); - - clock::set_for_testing(&mut clock, 1000); - - refresh( - &mut obligation, - &mut reserves, - &clock - ); - - test_utils::destroy(reserves); - sui::test_utils::destroy(lending_market_id); - clock::destroy_for_testing(clock); - sui::test_utils::destroy(obligation); - test_scenario::end(scenario); - } - - #[test] - #[expected_failure(abort_code = 0, location = reserve)] // price stale - public fun test_refresh_fail_borrow_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); - let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); - let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); - clock::set_for_testing(&mut clock, 0); - - 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, - get_reserve_mut(&mut reserves), - &clock, - 100 * 1_000_000_000 - ); - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 100 * 1_000_000 - ); - - clock::set_for_testing(&mut clock, 1000); - reserve::update_price_for_testing( - get_reserve_mut(&mut reserves), - &clock, - decimal::from(10), - decimal::from(10) - ); - - refresh( - &mut obligation, - &mut reserves, - &clock - ); - - test_utils::destroy(reserves); - sui::test_utils::destroy(lending_market_id); - clock::destroy_for_testing(clock); - sui::test_utils::destroy(obligation); - test_scenario::end(scenario); - } - - #[test] - public fun test_refresh_happy() { - use sui::test_scenario::{Self}; - use sui::clock::{Self}; - use sui::test_utils::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); - let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); - clock::set_for_testing(&mut clock, 0); - - 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, - get_reserve_mut(&mut reserves), - &clock, - 100 * 1_000_000_000 - ); - deposit( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 100 * 1_000_000 - ); - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 100 * 1_000_000 - ); - - clock::set_for_testing(&mut clock, 1000); - reserve::update_price_for_testing( - get_reserve_mut(&mut reserves), - &clock, - decimal::from(10), - decimal::from(9) - ); - 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) - ); - - refresh( - &mut obligation, - &mut reserves, - &clock - ); - - assert!(vector::length(&obligation.deposits) == 2, 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 usdc_deposit = vector::borrow(&obligation.deposits, 1); - assert!(usdc_deposit.deposited_ctoken_amount == 100 * 1_000_000, 3); - assert!(usdc_deposit.market_value == decimal::from(100), 4); - - assert!(vector::length(&obligation.borrows) == 1, 0); - - let usdt_borrow = vector::borrow(&obligation.borrows, 0); - assert!(usdt_borrow.borrowed_amount == decimal::from(101 * 1_000_000), 1); - assert!(usdt_borrow.cumulative_borrow_rate == decimal::from_percent(202), 2); - assert!(usdt_borrow.market_value == decimal::from(101), 3); - - assert!(obligation.deposited_value_usd == decimal::from(1100), 0); - assert!(obligation.allowed_borrow_value_usd == decimal::from(230), 1); - assert!(obligation.unhealthy_borrow_value_usd == decimal::from(580), 2); - assert!(obligation.unweighted_borrowed_value_usd == decimal::from(101), 3); - assert!(obligation.weighted_borrowed_value_usd == decimal::from(202), 4); - assert!(obligation.weighted_borrowed_value_upper_bound_usd == decimal::from(404), 4); - - test_utils::destroy(reserves); - sui::test_utils::destroy(lending_market_id); - clock::destroy_for_testing(clock); - sui::test_utils::destroy(obligation); - test_scenario::end(scenario); - } - - #[test] - #[expected_failure(abort_code = EObligationIsNotLiquidatable)] - public fun test_liquidate_fail_healthy() { - use sui::test_scenario::{Self}; - use sui::clock::{Self}; - use sui::test_utils::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); - let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); - clock::set_for_testing(&mut clock, 0); - - 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, - get_reserve_mut(&mut reserves), - &clock, - 100 * 1_000_000_000 - ); - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 100 * 1_000_000 - ); - - refresh( - &mut obligation, - &mut reserves, - &clock - ); - liquidate( - &mut obligation, - &mut reserves, - 0, - 1, - &clock, - 100 * 1_000_000_000 - ); - - test_utils::destroy(reserves); - sui::test_utils::destroy(lending_market_id); - clock::destroy_for_testing(clock); - sui::test_utils::destroy(obligation); - test_scenario::end(scenario); - } - - #[test] - public fun test_liquidate_happy_1() { - use sui::test_scenario::{Self}; - use sui::clock::{Self}; - use sui::test_utils::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); - let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); - clock::set_for_testing(&mut clock, 0); - - 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, - get_reserve_mut(&mut reserves), - &clock, - 100 * 1_000_000_000 - ); - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 50 * 1_000_000 - ); - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 50 * 1_000_000 - ); - - let config = { - let mut builder = reserve_config::from( - reserve::config(get_reserve(&reserves)), - test_scenario::ctx(&mut scenario) - ); - reserve_config::set_open_ltv_pct(&mut builder, 0); - reserve_config::set_close_ltv_pct(&mut builder, 0); - reserve_config::set_liquidation_bonus_bps(&mut builder, 1000); - reserve_config::set_max_liquidation_bonus_bps(&mut builder, 1000); - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }; - reserve::update_reserve_config( - get_reserve_mut(&mut reserves), - config - ); - - refresh( - &mut obligation, - &mut reserves, - &clock - ); - let (withdraw_amount, repay_amount) = liquidate( - &mut obligation, - &mut reserves, - 1, - 0, - &clock, - 100 * 1_000_000_000 - ); - assert!(withdraw_amount == 4_400_000_000, 0); - assert!(repay_amount == decimal::from(40 * 1_000_000), 1); - - assert!(vector::length(&obligation.deposits) == 1, 0); - - // $40 was liquidated with a 10% bonus = $44 = 4.4 sui => 95.6 sui remaining - let sui_deposit = find_deposit(&obligation, get_reserve(&reserves)); - assert!(sui_deposit.deposited_ctoken_amount == 95 * 1_000_000_000 + 600_000_000, 3); - assert!(sui_deposit.market_value == decimal::from(956), 4); - - let user_reward_manager = vector::borrow(&obligation.user_reward_managers, sui_deposit.user_reward_manager_index); - assert!(liquidity_mining::shares(user_reward_manager) == 95 * 1_000_000_000 + 600_000_000, 5); - - assert!(vector::length(&obligation.borrows) == 2, 0); - - let usdc_borrow = vector::borrow(&obligation.borrows, 0); - assert!(usdc_borrow.borrowed_amount == decimal::from(10 * 1_000_000), 1); - assert!(usdc_borrow.market_value == decimal::from(10), 3); - - let user_reward_manager = vector::borrow(&obligation.user_reward_managers, usdc_borrow.user_reward_manager_index); - assert!(liquidity_mining::shares(user_reward_manager) == 10 * 1_000_000 / 2, 5); - - let usdt_borrow = vector::borrow(&obligation.borrows, 1); - assert!(usdt_borrow.borrowed_amount == decimal::from(50 * 1_000_000), 1); - assert!(usdt_borrow.market_value == decimal::from(50), 3); - - let user_reward_manager = vector::borrow(&obligation.user_reward_managers, usdt_borrow.user_reward_manager_index); - assert!(liquidity_mining::shares(user_reward_manager) == 50 * 1_000_000 / 2, 5); - - assert!(obligation.deposited_value_usd == decimal::from(956), 0); - assert!(obligation.allowed_borrow_value_usd == decimal::from(0), 1); - assert!(obligation.unhealthy_borrow_value_usd == decimal::from(0), 2); - assert!(obligation.unweighted_borrowed_value_usd == decimal::from(60), 3); - assert!(obligation.weighted_borrowed_value_usd == decimal::from(120), 4); - - test_utils::destroy(reserves); - sui::test_utils::destroy(lending_market_id); - clock::destroy_for_testing(clock); - sui::test_utils::destroy(obligation); - test_scenario::end(scenario); - } - - #[test] - public fun test_liquidate_happy_2() { - use sui::test_scenario::{Self}; - use sui::clock::{Self}; - use sui::test_utils::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); - let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); - clock::set_for_testing(&mut clock, 0); - - 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, - get_reserve_mut(&mut reserves), - &clock, - 1 * 1_000_000_000 + 100_000_000 - ); - deposit( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 2 * 100_000_000 - ); - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 100 * 1_000_000 - ); - - let eth_reserve = get_reserve_mut(&mut reserves); - let config = { - let mut builder = reserve_config::from( - reserve::config(eth_reserve), - test_scenario::ctx(&mut scenario) - ); - reserve_config::set_open_ltv_pct(&mut builder, 0); - reserve_config::set_close_ltv_pct(&mut builder, 0); - - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }; - reserve::update_reserve_config(eth_reserve, config); - - - let sui_reserve = get_reserve_mut(&mut reserves); - let config = { - let mut builder = reserve_config::from( - reserve::config(sui_reserve), - test_scenario::ctx(&mut scenario) - ); - reserve_config::set_open_ltv_pct(&mut builder, 0); - reserve_config::set_close_ltv_pct(&mut builder, 0); - reserve_config::set_liquidation_bonus_bps(&mut builder, 1000); - reserve_config::set_max_liquidation_bonus_bps(&mut builder, 1000); - - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }; - reserve::update_reserve_config(sui_reserve, config); - - refresh( - &mut obligation, - &mut reserves, - &clock - ); - - let (withdraw_amount, repay_amount) = liquidate( - &mut obligation, - &mut reserves, - 1, - 0, - &clock, - 100 * 1_000_000_000 - ); - assert!(withdraw_amount == 1_100_000_000, 0); - assert!(repay_amount == decimal::from(10 * 1_000_000), 1); - - assert!(vector::length(&obligation.deposits) == 1, 0); - - let user_reward_manager_index = find_user_reward_manager_index( - &obligation, - reserve::deposits_pool_reward_manager_mut(get_reserve_mut(&mut reserves)) - ); - let user_reward_manager = vector::borrow(&obligation.user_reward_managers, user_reward_manager_index); - assert!(liquidity_mining::shares(user_reward_manager) == 0, 5); - - let eth_deposit = vector::borrow(&obligation.deposits, 0); - assert!(eth_deposit.deposited_ctoken_amount == 2 * 100_000_000, 3); - assert!(eth_deposit.market_value == decimal::from(4000), 4); - - let user_reward_manager = vector::borrow(&obligation.user_reward_managers, eth_deposit.user_reward_manager_index); - assert!(liquidity_mining::shares(user_reward_manager) == 2 * 100_000_000, 5); - - assert!(vector::length(&obligation.borrows) == 1, 0); - - let usdc_borrow = vector::borrow(&obligation.borrows, 0); - assert!(usdc_borrow.borrowed_amount == decimal::from(90 * 1_000_000), 1); - assert!(usdc_borrow.market_value == decimal::from(90), 3); - - let user_reward_manager = vector::borrow(&obligation.user_reward_managers, usdc_borrow.user_reward_manager_index); - assert!(liquidity_mining::shares(user_reward_manager) == 90 * 1_000_000 / 2, 5); - - assert!(obligation.deposited_value_usd == decimal::from(4000), 4000); - assert!(obligation.allowed_borrow_value_usd == decimal::from(0), 0); - assert!(obligation.unhealthy_borrow_value_usd == decimal::from(0), 2); - assert!(obligation.unweighted_borrowed_value_usd == decimal::from(90), 3); - assert!(obligation.weighted_borrowed_value_usd == decimal::from(180), 4); - - test_utils::destroy(reserves); - sui::test_utils::destroy(lending_market_id); - clock::destroy_for_testing(clock); - sui::test_utils::destroy(obligation); - test_scenario::end(scenario); - } - - #[test] - public fun test_liquidate_full_1() { - use sui::test_scenario::{Self}; - use sui::clock::{Self}; - use sui::test_utils::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); - let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); - clock::set_for_testing(&mut clock, 0); - - 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, - get_reserve_mut(&mut reserves), - &clock, - 100 * 1_000_000_000 - ); - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1 * 1_000_000 - ); - - let config = { - let mut builder = reserve_config::from( - reserve::config(get_reserve(&reserves)), - test_scenario::ctx(&mut scenario) - ); - reserve_config::set_open_ltv_pct(&mut builder, 0); - reserve_config::set_close_ltv_pct(&mut builder, 0); - reserve_config::set_liquidation_bonus_bps(&mut builder, 1000); - reserve_config::set_max_liquidation_bonus_bps(&mut builder, 1000); - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }; - reserve::update_reserve_config( - get_reserve_mut(&mut reserves), - config - ); - - refresh( - &mut obligation, - &mut reserves, - &clock - ); - let (withdraw_amount, repay_amount) = liquidate( - &mut obligation, - &mut reserves, - 1, - 0, - &clock, - 1_000_000_000 - ); - assert!(withdraw_amount == 110_000_000, 0); - assert!(repay_amount == decimal::from(1_000_000), 1); - - assert!(vector::length(&obligation.deposits) == 1, 0); - - // $1 was liquidated with a 10% bonus = $1.1 => 0.11 sui => 99.89 sui remaining - let sui_deposit = find_deposit(&obligation, get_reserve(&reserves)); - assert!(sui_deposit.deposited_ctoken_amount == 99 * 1_000_000_000 + 890_000_000, 3); - assert!(sui_deposit.market_value == add(decimal::from(998), decimal::from_percent(90)), 4); - - let user_reward_manager = vector::borrow(&obligation.user_reward_managers, sui_deposit.user_reward_manager_index); - assert!(liquidity_mining::shares(user_reward_manager) == 99 * 1_000_000_000 + 890_000_000, 5); - - assert!(vector::length(&obligation.borrows) == 0, 0); - - let user_reward_manager_index = find_user_reward_manager_index( - &obligation, - reserve::borrows_pool_reward_manager_mut(get_reserve_mut(&mut reserves)) - ); - 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); - assert!(obligation.allowed_borrow_value_usd == decimal::from(0), 1); - assert!(obligation.unhealthy_borrow_value_usd == decimal::from(0), 2); - assert!(obligation.unweighted_borrowed_value_usd == decimal::from(0), 3); - assert!(obligation.weighted_borrowed_value_usd == decimal::from(0), 4); - - test_utils::destroy(reserves); - sui::test_utils::destroy(lending_market_id); - clock::destroy_for_testing(clock); - sui::test_utils::destroy(obligation); - test_scenario::end(scenario); - } - - #[test] - public fun test_liquidate_full_2() { - use sui::test_scenario::{Self}; - use sui::clock::{Self}; - use sui::test_utils::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); - let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); - clock::set_for_testing(&mut clock, 0); - - 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, - get_reserve_mut(&mut reserves), - &clock, - 10 * 1_000_000_000 - ); - deposit( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 550_000 - ); - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 10 * 1_000_000 - ); - - let usdc_reserve = get_reserve_mut(&mut reserves); - let config = { - let mut builder = reserve_config::from( - reserve::config(usdc_reserve), - test_scenario::ctx(&mut scenario) - ); - reserve_config::set_open_ltv_pct(&mut builder, 0); - reserve_config::set_close_ltv_pct(&mut builder, 0); - reserve_config::set_liquidation_bonus_bps(&mut builder, 1000); - reserve_config::set_max_liquidation_bonus_bps(&mut builder, 1000); - reserve_config::set_protocol_liquidation_fee_bps(&mut builder, 0); - - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }; - reserve::update_reserve_config(usdc_reserve, config); - - - let sui_reserve = get_reserve_mut(&mut reserves); - let config = { - let mut builder = reserve_config::from( - reserve::config(sui_reserve), - test_scenario::ctx(&mut scenario) - ); - reserve_config::set_open_ltv_pct(&mut builder, 0); - reserve_config::set_close_ltv_pct(&mut builder, 0); - reserve_config::set_liquidation_bonus_bps(&mut builder, 1000); - reserve_config::set_max_liquidation_bonus_bps(&mut builder, 1000); - - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }; - reserve::update_reserve_config(sui_reserve, config); - - refresh( - &mut obligation, - &mut reserves, - &clock - ); - - let (withdraw_amount, repay_amount) = liquidate( - &mut obligation, - &mut reserves, - 2, - 1, - &clock, - 100 * 1_000_000_000 - ); - assert!(withdraw_amount == 550_000, 0); - assert!(repay_amount == decimal::from(500_000), 1); - - assert!(vector::length(&obligation.deposits) == 1, 0); - - // unchanged - let sui_deposit = vector::borrow(&obligation.deposits, 0); - assert!(sui_deposit.deposited_ctoken_amount == 10_000_000_000, 3); - assert!(sui_deposit.market_value == decimal::from(100), 4); - - let user_reward_manager = vector::borrow(&obligation.user_reward_managers, sui_deposit.user_reward_manager_index); - assert!(liquidity_mining::shares(user_reward_manager) == 10_000_000_000, 5); - - let user_reward_manager_index = find_user_reward_manager_index( - &obligation, - reserve::deposits_pool_reward_manager_mut(get_reserve_mut(&mut reserves)) - ); - let user_reward_manager = vector::borrow(&obligation.user_reward_managers, user_reward_manager_index); - assert!(liquidity_mining::shares(user_reward_manager) == 0, 5); - - assert!(vector::length(&obligation.borrows) == 1, 0); - - let usdc_borrow = vector::borrow(&obligation.borrows, 0); - assert!(usdc_borrow.borrowed_amount == decimal::from(9_500_000), 1); - assert!(usdc_borrow.market_value == decimal::from_percent_u64(950), 3); - - let user_reward_manager = vector::borrow(&obligation.user_reward_managers, usdc_borrow.user_reward_manager_index); - assert!(liquidity_mining::shares(user_reward_manager) == 9_500_000 / 2, 5); - - assert!(obligation.deposited_value_usd == decimal::from(100), 4000); - assert!(obligation.allowed_borrow_value_usd == decimal::from(0), 0); - assert!(obligation.unhealthy_borrow_value_usd == decimal::from(0), 2); - assert!(obligation.unweighted_borrowed_value_usd == decimal::from_percent_u64(950), 3); - assert!(obligation.weighted_borrowed_value_usd == decimal::from(19), 4); - - test_utils::destroy(reserves); - sui::test_utils::destroy(lending_market_id); - clock::destroy_for_testing(clock); - sui::test_utils::destroy(obligation); - test_scenario::end(scenario); - } - - #[test] - #[expected_failure(abort_code = EObligationIsNotForgivable)] - fun test_forgive_debt_fail() { - use sui::test_scenario::{Self}; - use sui::clock::{Self}; - use sui::test_utils::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); - let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); - clock::set_for_testing(&mut clock, 0); - - 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, - get_reserve_mut(&mut reserves), - &clock, - 10 * 1_000_000_000 - ); - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1_000_000 - ); - - forgive( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - decimal::from(1_000_000_000) - ); - - test_utils::destroy(reserves); - sui::test_utils::destroy(lending_market_id); - clock::destroy_for_testing(clock); - sui::test_utils::destroy(obligation); - test_scenario::end(scenario); - } - - #[test] - fun test_is_looped() { - use sui::test_scenario::{Self}; - use sui::clock::{Self}; - use sui::test_utils::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); - let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); - clock::set_for_testing(&mut clock, 0); - - 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, - get_reserve_mut(&mut reserves), - &clock, - 100 * 1_000_000 - ); - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1_000_000_000 - ); - - assert!(!is_looped(&obligation), 0); - - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1_000_000 - ); - - assert!(is_looped(&obligation), 0); - - repay( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - decimal::from(1_000_000) - ); - - assert!(!is_looped(&obligation), 0); - - vector::push_back(&mut obligation.borrows, Borrow { - coin_type: type_name::get(), - reserve_array_index: 2, - borrowed_amount: decimal::from(1_000_000), - cumulative_borrow_rate: decimal::from_percent(100), - market_value: decimal::from(1), - user_reward_manager_index: 0, - }); - - assert!(is_looped(&obligation), 0); - - test_utils::destroy(reserves); - sui::test_utils::destroy(lending_market_id); - clock::destroy_for_testing(clock); - sui::test_utils::destroy(obligation); - test_scenario::end(scenario); - } - - - #[test] - fun test_is_looped_2() { - use sui::test_scenario::{Self}; - use sui::clock::{Self}; - use sui::test_utils::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); - let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); - clock::set_for_testing(&mut clock, 0); - - let mut reserves = reserves(&mut scenario); - - // Check USDC - { - let mut obligation = create_obligation( - object::uid_to_inner(&lending_market_id), - test_scenario::ctx(&mut scenario) - ); - - deposit( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 100 * 1_000_000 - ); - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1_000_000_000 - ); - - assert!(!is_looped(&obligation), 0); - - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1_000 - ); - - assert!(!is_looped(&obligation), 0); - - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1_000_000 - ); - - assert!(is_looped(&obligation), 0); - - repay( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - decimal::from(1_000_000) - ); - - assert!(!is_looped(&obligation), 0); - - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1_000_000 - ); - - assert!(is_looped(&obligation), 0); - - repay( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - decimal::from(1_000_000) - ); - - assert!(!is_looped(&obligation), 0); - - vector::push_back(&mut obligation.borrows, Borrow { - coin_type: type_name::get(), - reserve_array_index: 1, - borrowed_amount: decimal::from(1_000_000), - cumulative_borrow_rate: decimal::from_percent(100), - market_value: decimal::from(1), - user_reward_manager_index: 0, - }); - - assert!(is_looped(&obligation), 0); - sui::test_utils::destroy(obligation); - }; - - // Check USDT - { - let mut obligation = create_obligation( - object::uid_to_inner(&lending_market_id), - test_scenario::ctx(&mut scenario) - ); - - deposit( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 100 * 1_000_000 - ); - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1_000_000_000 - ); - - assert!(!is_looped(&obligation), 0); - - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1_000 - ); - - assert!(!is_looped(&obligation), 0); - - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1_000_000 - ); - - assert!(is_looped(&obligation), 0); - - repay( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - decimal::from(1_000_000) - ); - - assert!(!is_looped(&obligation), 0); - - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1_000_000 - ); - - assert!(is_looped(&obligation), 0); - - repay( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - decimal::from(1_000_000) - ); - - assert!(!is_looped(&obligation), 0); - - vector::push_back(&mut obligation.borrows, Borrow { - coin_type: type_name::get(), - reserve_array_index: 2, - borrowed_amount: decimal::from(1_000_000), - cumulative_borrow_rate: decimal::from_percent(100), - market_value: decimal::from(1), - user_reward_manager_index: 0, - }); - - assert!(is_looped(&obligation), 0); - sui::test_utils::destroy(obligation); - }; - - // Check AUSD - { - let mut obligation = create_obligation( - object::uid_to_inner(&lending_market_id), - test_scenario::ctx(&mut scenario) - ); - - deposit( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 100 * 1_000_000 - ); - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1_000_000_000 - ); - - assert!(!is_looped(&obligation), 0); - - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1_000 - ); - - assert!(!is_looped(&obligation), 0); - - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1_000_000 - ); - - assert!(is_looped(&obligation), 0); - - repay( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - decimal::from(1_000_000) - ); - - assert!(!is_looped(&obligation), 0); - - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1_000_000 - ); - - assert!(is_looped(&obligation), 0); - - repay( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - decimal::from(1_000_000) - ); - - assert!(!is_looped(&obligation), 0); - - vector::push_back(&mut obligation.borrows, Borrow { - coin_type: type_name::get(), - reserve_array_index: 5, - borrowed_amount: decimal::from(1_000_000), - cumulative_borrow_rate: decimal::from_percent(100), - market_value: decimal::from(1), - user_reward_manager_index: 0, - }); - - assert!(is_looped(&obligation), 0); - sui::test_utils::destroy(obligation); - }; - - // Check SUI - { - let mut obligation = create_obligation( - object::uid_to_inner(&lending_market_id), - test_scenario::ctx(&mut scenario) - ); - - deposit( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 100 * 1_000_000 - ); - - vector::push_back(&mut obligation.borrows, Borrow { - coin_type: type_name::get(), - reserve_array_index: 0, - borrowed_amount: decimal::from(1_000_000), - cumulative_borrow_rate: decimal::from_percent(100), - market_value: decimal::from(1), - user_reward_manager_index: 0, - }); - - // print(&obligation.borrows); - - assert!(is_looped(&obligation), 9); - - sui::test_utils::destroy(vector::pop_back(&mut obligation.borrows)); - - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1_000 - ); - - assert!(!is_looped(&obligation), 0); - - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1_000 - ); - - assert!(!is_looped(&obligation), 0); - - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1_000 - ); - - assert!(!is_looped(&obligation), 0); - - repay( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - decimal::from(1_000) - ); - - assert!(!is_looped(&obligation), 0); - - sui::test_utils::destroy(obligation); - }; - - test_utils::destroy(reserves); - sui::test_utils::destroy(lending_market_id); - clock::destroy_for_testing(clock); - test_scenario::end(scenario); - } - - #[test] - fun test_zero_out_rewards_if_looped() { - use sui::test_scenario::{Self}; - use sui::clock::{Self}; - use sui::test_utils::{Self}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); - let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); - clock::set_for_testing(&mut clock, 0); - - 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, - get_reserve_mut(&mut reserves), - &clock, - 100 * 1_000_000 - ); - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1_000_000_000 - ); - - // 1. shouldn't do anything - zero_out_rewards_if_looped(&mut obligation, &mut reserves, &clock); - - let mut i = 0; - while (i < vector::length(&obligation.user_reward_managers)) { - let user_reward_manager = vector::borrow(&obligation.user_reward_managers, i); - assert!(liquidity_mining::shares(user_reward_manager) != 0, 0); - i = i + 1; - }; - - // actually loop - borrow( - &mut obligation, - get_reserve_mut(&mut reserves), - &clock, - 1_000_000 - ); - - zero_out_rewards_if_looped(&mut obligation, &mut reserves, &clock); - - let mut i = 0; - while (i < vector::length(&obligation.user_reward_managers)) { - let user_reward_manager = vector::borrow(&obligation.user_reward_managers, i); - assert!(liquidity_mining::shares(user_reward_manager) == 0, 0); - i = i + 1; - }; - - test_utils::destroy(reserves); - sui::test_utils::destroy(lending_market_id); - clock::destroy_for_testing(clock); - sui::test_utils::destroy(obligation); - test_scenario::end(scenario); + public(package) fun create_borrow_for_testing( + coin_type: TypeName, + reserve_array_index: u64, + borrowed_amount: Decimal, + cumulative_borrow_rate: Decimal, + market_value: Decimal, + user_reward_manager_index: u64, + ): Borrow { + Borrow { + coin_type, + reserve_array_index, + borrowed_amount, + cumulative_borrow_rate, + market_value, + user_reward_manager_index + } } } diff --git a/contracts/suilend/sources/reserve.move b/contracts/suilend/sources/reserve.move index 4126d03..929e043 100644 --- a/contracts/suilend/sources/reserve.move +++ b/contracts/suilend/sources/reserve.move @@ -32,12 +32,6 @@ module suilend::reserve { }; use suilend::liquidity_mining::{Self, PoolRewardManager}; - #[test_only] - use sui::test_scenario::{Self}; - - #[test_only] - use std::vector::{Self}; - // === Errors === const EPriceStale: u64 = 0; const EPriceIdentifierMismatch: u64 = 1; @@ -446,6 +440,38 @@ module suilend::reserve { )) } + public fun ctoken_supply

(reserve: &Reserve

): u64 { + reserve.ctoken_supply + } + + public fun unclaimed_spread_fees

(reserve: &Reserve

): Decimal { + reserve.unclaimed_spread_fees + } + + public fun balances(reserve: &Reserve

): &Balances { + dynamic_field::borrow(&reserve.id, BalanceKey {}) + } + + public use fun balances_available_amount as Balances.available_amount; + public fun balances_available_amount(balances: &Balances): &Balance { + &balances.available_amount + } + + public use fun balances_ctoken_supply as Balances.ctoken_supply; + public fun balances_ctoken_supply(balances: &Balances): &Supply> { + &balances.ctoken_supply + } + + public use fun balances_fees as Balances.fees; + public fun balances_fees(balances: &Balances): &Balance { + &balances.fees + } + + public use fun balances_ctoken_fees as Balances.ctoken_fees; + public fun balances_ctoken_fees(balances: &Balances): &Balance> { + &balances.ctoken_fees + } + // === Public-Mutative Functions public(package) fun deposits_pool_reward_manager_mut

(reserve: &mut Reserve

): &mut PoolRewardManager { &mut reserve.deposits_pool_reward_manager @@ -814,6 +840,7 @@ module suilend::reserve { #[test] fun test_accessors() { + use sui::test_scenario::{Self}; use suilend::test_usdc::{TEST_USDC}; use suilend::reserve_config::{default_reserve_config}; @@ -935,480 +962,6 @@ module suilend::reserve { test_scenario::end(scenario); } - #[test_only] - public struct TEST_LM {} - - #[test] - fun test_deposit_happy() { - use suilend::test_usdc::{TEST_USDC}; - use sui::test_scenario::{Self}; - use suilend::reserve_config::{default_reserve_config}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - - - let mut reserve = create_for_testing( - default_reserve_config(), - 0, - 6, - decimal::from(1), - 0, - 500, - 200, - decimal::from(500), - decimal::from(1), - 1, - test_scenario::ctx(&mut scenario) - ); - - let ctokens = deposit_liquidity_and_mint_ctokens( - &mut reserve, - balance::create_for_testing(1000) - ); - - assert!(balance::value(&ctokens) == 200, 0); - assert!(reserve.available_amount == 1500, 0); - assert!(reserve.ctoken_supply == 400, 0); - - let balances: &mut Balances = dynamic_field::borrow_mut( - &mut reserve.id, - BalanceKey {} - ); - - assert!(balance::value(&balances.available_amount) == 1500, 0); - assert!(balance::supply_value(&balances.ctoken_supply) == 400, 0); - - sui::test_utils::destroy(reserve); - sui::test_utils::destroy(ctokens); - - test_scenario::end(scenario); - } - - #[test] - #[expected_failure(abort_code = EDepositLimitExceeded)] - fun test_deposit_fail() { - use suilend::test_usdc::{TEST_USDC}; - use sui::test_scenario::{Self}; - use suilend::reserve_config::{default_reserve_config}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - - let mut reserve = create_for_testing( - { - let config = default_reserve_config(); - let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); - reserve_config::set_deposit_limit(&mut builder, 1000); - sui::test_utils::destroy(config); - - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }, - 0, - 6, - decimal::from(1), - 0, - 500, - 200, - decimal::from(500), - decimal::from(1), - 1, - test_scenario::ctx(&mut scenario) - ); - - let coins = balance::create_for_testing(1); - let ctokens = deposit_liquidity_and_mint_ctokens(&mut reserve, coins); - - sui::test_utils::destroy(reserve); - sui::test_utils::destroy(ctokens); - test_scenario::end(scenario); - } - - #[test] - #[expected_failure(abort_code = EDepositLimitExceeded)] - fun test_deposit_fail_usd_limit() { - use suilend::test_usdc::{TEST_USDC}; - use sui::test_scenario::{Self}; - use suilend::reserve_config::{default_reserve_config}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - - let mut reserve = create_for_testing( - { - let config = default_reserve_config(); - let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); - reserve_config::set_deposit_limit(&mut builder, 18_446_744_073_709_551_615); - reserve_config::set_deposit_limit_usd(&mut builder, 1); - sui::test_utils::destroy(config); - - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }, - 0, - 6, - decimal::from(1), - 0, - 500_000, - 1_000_000, - decimal::from(500_000), - decimal::from(1), - 1, - test_scenario::ctx(&mut scenario) - ); - - let coins = balance::create_for_testing(1); - let ctokens = deposit_liquidity_and_mint_ctokens(&mut reserve, coins); - - sui::test_utils::destroy(reserve); - sui::test_utils::destroy(ctokens); - test_scenario::end(scenario); - } - - #[test] - fun test_redeem_happy() { - use suilend::test_usdc::{TEST_USDC}; - use sui::test_scenario::{Self}; - use suilend::reserve_config::{default_reserve_config}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - - let mut reserve = create_for_testing( - default_reserve_config(), - 0, - 6, - decimal::from(1), - 0, - 500, - 200, - decimal::from(500), - decimal::from(1), - 1, - test_scenario::ctx(&mut scenario) - ); - - let available_amount_old = reserve.available_amount; - let ctoken_supply_old = reserve.ctoken_supply; - - let ctokens = balance::create_for_testing(10); - let tokens = redeem_ctokens(&mut reserve, ctokens); - - assert!(balance::value(&tokens) == 50, 0); - assert!(reserve.available_amount == available_amount_old - 50, 0); - assert!(reserve.ctoken_supply == ctoken_supply_old - 10, 0); - - let balances: &mut Balances = dynamic_field::borrow_mut( - &mut reserve.id, - BalanceKey {} - ); - - assert!(balance::value(&balances.available_amount) == available_amount_old - 50, 0); - assert!(balance::supply_value(&balances.ctoken_supply) == ctoken_supply_old - 10, 0); - - sui::test_utils::destroy(reserve); - sui::test_utils::destroy(tokens); - - test_scenario::end(scenario); - } - - #[test] - fun test_borrow_happy() { - use suilend::test_usdc::{TEST_USDC}; - use sui::test_scenario::{Self}; - use suilend::reserve_config::{default_reserve_config}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - - let mut reserve = create_for_testing( - { - let config = default_reserve_config(); - let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); - reserve_config::set_borrow_fee_bps(&mut builder, 100); - sui::test_utils::destroy(config); - - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }, - 0, - 6, - decimal::from(1), - 0, - 0, - 0, - decimal::from(0), - decimal::from(1), - 1, - test_scenario::ctx(&mut scenario) - ); - - let ctokens = deposit_liquidity_and_mint_ctokens( - &mut reserve, - balance::create_for_testing(1000) - ); - - let available_amount_old = reserve.available_amount; - let borrowed_amount_old = reserve.borrowed_amount; - - let (tokens, borrowed_amount_with_fee) = borrow_liquidity(&mut reserve, 400); - assert!(balance::value(&tokens) == 400, 0); - assert!(borrowed_amount_with_fee == 404, 0); - - assert!(reserve.available_amount == available_amount_old - 404, 0); - assert!(reserve.borrowed_amount == add(borrowed_amount_old, decimal::from(404)), 0); - - let balances: &mut Balances = dynamic_field::borrow_mut( - &mut reserve.id, - BalanceKey {} - ); - - assert!(balance::value(&balances.available_amount) == available_amount_old - 404, 0); - assert!(balance::value(&balances.fees) == 4, 0); - - let (ctoken_fees, fees) = claim_fees(&mut reserve); - assert!(balance::value(&fees) == 4, 0); - assert!(balance::value(&ctoken_fees) == 0, 0); - - sui::test_utils::destroy(fees); - sui::test_utils::destroy(ctoken_fees); - sui::test_utils::destroy(reserve); - sui::test_utils::destroy(tokens); - sui::test_utils::destroy(ctokens); - - test_scenario::end(scenario); - } - - #[test] - #[expected_failure(abort_code = EBorrowLimitExceeded)] - fun test_borrow_fail() { - use suilend::test_usdc::{TEST_USDC}; - use sui::test_scenario::{Self}; - use suilend::reserve_config::{default_reserve_config}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - - let mut reserve = create_for_testing( - { - let config = default_reserve_config(); - let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); - reserve_config::set_borrow_limit(&mut builder, 0); - sui::test_utils::destroy(config); - - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }, - 0, - 6, - decimal::from(1), - 0, - 0, - 0, - decimal::from(0), - decimal::from(1), - 1, - test_scenario::ctx(&mut scenario) - ); - - let ctokens = deposit_liquidity_and_mint_ctokens( - &mut reserve, - balance::create_for_testing(1000) - ); - - let (tokens, _) = borrow_liquidity(&mut reserve, 1); - - sui::test_utils::destroy(reserve); - sui::test_utils::destroy(tokens); - sui::test_utils::destroy(ctokens); - - test_scenario::end(scenario); - } - - #[test] - #[expected_failure(abort_code = EBorrowLimitExceeded)] - fun test_borrow_fail_usd_limit() { - use suilend::test_usdc::{TEST_USDC}; - use sui::test_scenario::{Self}; - use suilend::reserve_config::{default_reserve_config}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - - let mut reserve = create_for_testing( - { - let config = default_reserve_config(); - let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); - reserve_config::set_borrow_limit_usd(&mut builder, 1); - sui::test_utils::destroy(config); - - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }, - 0, - 6, - decimal::from(1), - 0, - 0, - 0, - decimal::from(0), - decimal::from(1), - 1, - test_scenario::ctx(&mut scenario) - ); - - let ctokens = deposit_liquidity_and_mint_ctokens( - &mut reserve, - balance::create_for_testing(10_000_000) - ); - - let (tokens, _) = borrow_liquidity(&mut reserve, 1_000_000 + 1); - - sui::test_utils::destroy(reserve); - sui::test_utils::destroy(tokens); - sui::test_utils::destroy(ctokens); - - test_scenario::end(scenario); - } - - - #[test] - fun test_claim_fees() { - use suilend::test_usdc::{TEST_USDC}; - use sui::test_scenario::{Self}; - use suilend::reserve_config::{default_reserve_config}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); - - let mut reserve = create_for_testing( - { - let config = default_reserve_config(); - let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); - reserve_config::set_deposit_limit(&mut builder, 1000 * 1_000_000); - reserve_config::set_borrow_limit(&mut builder, 1000 * 1_000_000); - reserve_config::set_borrow_fee_bps(&mut builder, 0); - reserve_config::set_spread_fee_bps(&mut builder, 5000); - reserve_config::set_interest_rate_utils(&mut builder, { - let mut v = vector::empty(); - vector::push_back(&mut v, 0); - vector::push_back(&mut v, 100); - v - }); - reserve_config::set_interest_rate_aprs(&mut builder, { - let mut v = vector::empty(); - vector::push_back(&mut v, 0); - vector::push_back(&mut v, 3153600000); - v - }); - - sui::test_utils::destroy(config); - - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }, - 0, - 6, - decimal::from(1), - 0, - 0, - 0, - decimal::from(0), - decimal::from(1), - 0, - test_scenario::ctx(&mut scenario) - ); - - let ctokens = deposit_liquidity_and_mint_ctokens( - &mut reserve, - balance::create_for_testing(100 * 1_000_000) - ); - - let (tokens, _) = borrow_liquidity(&mut reserve, 50 * 1_000_000); - - clock::set_for_testing(&mut clock, 1000); - compound_interest(&mut reserve, &clock); - - let old_available_amount = reserve.available_amount; - let old_unclaimed_spread_fees = reserve.unclaimed_spread_fees; - - let (ctoken_fees, fees) = claim_fees(&mut reserve); - - // 0.5% interest a second with 50% take rate => 0.25% fee on 50 USDC = 0.125 USDC - assert!(balance::value(&fees) == 125_000, 0); - assert!(balance::value(&ctoken_fees) == 0, 0); - - assert!(reserve.available_amount == old_available_amount - 125_000, 0); - assert!(reserve.unclaimed_spread_fees == sub(old_unclaimed_spread_fees, decimal::from(125_000)), 0); - - let balances: &mut Balances = dynamic_field::borrow_mut( - &mut reserve.id, - BalanceKey {} - ); - assert!(balance::value(&balances.available_amount) == old_available_amount - 125_000, 0); - - sui::test_utils::destroy(clock); - sui::test_utils::destroy(ctoken_fees); - sui::test_utils::destroy(fees); - sui::test_utils::destroy(reserve); - sui::test_utils::destroy(tokens); - sui::test_utils::destroy(ctokens); - - test_scenario::end(scenario); - } - - #[test] - fun test_repay_happy() { - use suilend::test_usdc::{TEST_USDC}; - use sui::test_scenario::{Self}; - use suilend::reserve_config::{default_reserve_config}; - - let owner = @0x26; - let mut scenario = test_scenario::begin(owner); - - let mut reserve = create_for_testing( - { - let config = default_reserve_config(); - let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); - reserve_config::set_borrow_fee_bps(&mut builder, 100); - sui::test_utils::destroy(config); - - reserve_config::build(builder, test_scenario::ctx(&mut scenario)) - }, - 0, - 6, - decimal::from(1), - 0, - 0, - 0, - decimal::from(0), - decimal::from(1), - 1, - test_scenario::ctx(&mut scenario) - ); - - let ctokens = deposit_liquidity_and_mint_ctokens( - &mut reserve, - balance::create_for_testing(1000) - ); - - let (tokens, _) = borrow_liquidity(&mut reserve, 400); - - let available_amount_old = reserve.available_amount; - let borrowed_amount_old = reserve.borrowed_amount; - - repay_liquidity(&mut reserve, tokens, decimal::from_percent_u64(39_901)); - - assert!(reserve.available_amount == available_amount_old + 400, 0); - assert!(reserve.borrowed_amount == sub(borrowed_amount_old, decimal::from_percent_u64(39_901)), 0); - - let balances: &mut Balances = dynamic_field::borrow_mut( - &mut reserve.id, - BalanceKey {} - ); - assert!(balance::value(&balances.available_amount) == available_amount_old + 400, 0); - - sui::test_utils::destroy(reserve); - sui::test_utils::destroy(ctokens); - - test_scenario::end(scenario); - } #[test_only] public fun create_for_testing( diff --git a/contracts/suilend/tests/lending_market_tests.move b/contracts/suilend/tests/lending_market_tests.move new file mode 100644 index 0000000..44a3587 --- /dev/null +++ b/contracts/suilend/tests/lending_market_tests.move @@ -0,0 +1,1624 @@ +module suilend::lending_market_tests { + use sui::test_scenario::{Self, Scenario}; + use sui::object::{Self, ID, UID}; + use suilend::rate_limiter::{Self, RateLimiter, RateLimiterConfig}; + use std::ascii::{Self}; + use sui::event::{Self}; + use suilend::decimal::{Self, Decimal, mul, ceil, div, add, floor, gt, min, saturating_floor}; + use sui::object_table::{Self, ObjectTable}; + use sui::bag::{Self, Bag}; + use sui::clock::{Self, Clock}; + use sui::tx_context::{Self, TxContext}; + use sui::transfer; + use suilend::reserve::{Self, Reserve, CToken}; + use suilend::reserve_config::{ReserveConfig, borrow_fee}; + use suilend::obligation::{Self, Obligation}; + use sui::coin::{Self, Coin, CoinMetadata}; + use sui::balance::{Self}; + use pyth::price_info::{PriceInfoObject}; + use std::type_name::{Self, TypeName}; + use std::vector::{Self}; + use std::option::{Self, Option}; + use suilend::liquidity_mining::{Self}; + use sui::package; + use suilend::lending_market::{Self, create_lending_market, LendingMarketOwnerCap, LendingMarket}; + use suilend::mock_pyth::{PriceState}; + + + public struct LENDING_MARKET has drop {} + + const U64_MAX: u64 = 18446744073709551615; + + #[test] + fun test_create_lending_market() { + use sui::test_scenario::{Self}; + use sui::test_utils::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + + let (owner_cap, lending_market) = create_lending_market( + test_scenario::ctx(&mut scenario) + ); + + test_utils::destroy(owner_cap); + test_utils::destroy(lending_market); + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = suilend::lending_market::EDuplicateReserve)] + fun duplicate_reserves() { + use suilend::test_usdc::{TEST_USDC}; + use suilend::test_sui::{TEST_SUI}; + use suilend::reserve_config::{Self}; + use sui::test_utils::{Self}; + use suilend::mock_pyth::{Self}; + use suilend::mock_metadata::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + + let clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + let metadata = mock_metadata::init_metadata(test_scenario::ctx(&mut scenario)); + + let (owner_cap, mut lending_market) = create_lending_market( + test_scenario::ctx(&mut scenario) + ); + + let mut prices = mock_pyth::init_state(test_scenario::ctx(&mut scenario)); + mock_pyth::register(&mut prices, test_scenario::ctx(&mut scenario)); + mock_pyth::register(&mut prices, test_scenario::ctx(&mut scenario)); + + lending_market::add_reserve( + &owner_cap, + &mut lending_market, + mock_pyth::get_price_obj(&prices), + reserve_config::default_reserve_config(), + mock_metadata::get(&metadata), + &clock, + test_scenario::ctx(&mut scenario) + ); + + lending_market::add_reserve( + &owner_cap, + &mut lending_market, + mock_pyth::get_price_obj(&prices), + reserve_config::default_reserve_config(), + mock_metadata::get(&metadata), + &clock, + test_scenario::ctx(&mut scenario) + ); + + test_utils::destroy(owner_cap); + test_utils::destroy(lending_market); + test_utils::destroy(clock); + test_utils::destroy(prices); + test_utils::destroy(metadata); + test_scenario::end(scenario); + } + + public struct State { + clock: Clock, + owner_cap: LendingMarketOwnerCap, + lending_market: LendingMarket, + prices: PriceState, + type_to_index: Bag + } + + public struct ReserveArgs has store { + config: ReserveConfig, + initial_deposit: u64 + } + + #[test_only] + fun setup(mut reserve_args: Bag, scenario: &mut Scenario): State { + use suilend::test_usdc::{TEST_USDC}; + use suilend::test_sui::{TEST_SUI}; + use suilend::reserve_config::{Self}; + use sui::test_utils::{Self}; + use suilend::mock_pyth::{Self}; + use suilend::mock_metadata::{Self}; + use std::type_name::{Self}; + + + let clock = clock::create_for_testing(test_scenario::ctx(scenario)); + let metadata = mock_metadata::init_metadata(test_scenario::ctx(scenario)); + + let (owner_cap, mut lending_market) = create_lending_market( + test_scenario::ctx(scenario) + ); + + let mut prices = mock_pyth::init_state(test_scenario::ctx(scenario)); + mock_pyth::register(&mut prices, test_scenario::ctx(scenario)); + mock_pyth::register(&mut prices, test_scenario::ctx(scenario)); + + let mut type_to_index = bag::new(test_scenario::ctx(scenario)); + bag::add(&mut type_to_index, type_name::get(), 0); + bag::add(&mut type_to_index, type_name::get(), 1); + + lending_market::add_reserve( + &owner_cap, + &mut lending_market, + mock_pyth::get_price_obj(&prices), + reserve_config::default_reserve_config(), + mock_metadata::get(&metadata), + &clock, + test_scenario::ctx(scenario) + ); + + lending_market::add_reserve( + &owner_cap, + &mut lending_market, + mock_pyth::get_price_obj(&prices), + reserve_config::default_reserve_config(), + mock_metadata::get(&metadata), + &clock, + test_scenario::ctx(scenario) + ); + + if (bag::contains(&reserve_args, type_name::get())) { + let ReserveArgs { config, initial_deposit } = bag::remove( + &mut reserve_args, + type_name::get() + ); + let coins = coin::mint_for_testing( + initial_deposit, + test_scenario::ctx(scenario) + ); + + let ctokens = lending_market::deposit_liquidity_and_mint_ctokens( + &mut lending_market, + 0, + &clock, + coins, + test_scenario::ctx(scenario) + ); + + lending_market::update_reserve_config( + &owner_cap, + &mut lending_market, + 0, + config + ); + + test_utils::destroy(ctokens); + }; + if (bag::contains(&reserve_args, type_name::get())) { + let ReserveArgs { config, initial_deposit } = bag::remove( + &mut reserve_args, + type_name::get() + ); + let coins = coin::mint_for_testing( + initial_deposit, + test_scenario::ctx(scenario) + ); + + let ctokens = lending_market::deposit_liquidity_and_mint_ctokens( + &mut lending_market, + 1, + &clock, + coins, + test_scenario::ctx(scenario) + ); + + lending_market::update_reserve_config( + &owner_cap, + &mut lending_market, + 1, + config + ); + + test_utils::destroy(ctokens); + }; + + test_utils::destroy(reserve_args); + test_utils::destroy(metadata); + + return State { + clock, + owner_cap, + lending_market, + prices, + type_to_index + } + } + + + #[test] + public fun test_deposit() { + use sui::test_utils::{Self}; + use suilend::test_usdc::{TEST_USDC}; + use std::type_name::{Self}; + use suilend::reserve_config::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let State { clock, owner_cap, mut lending_market, prices, type_to_index } = setup({ + let mut bag = bag::new(test_scenario::ctx(&mut scenario)); + bag::add( + &mut bag, + type_name::get(), + ReserveArgs { + config: reserve_config::default_reserve_config(), + initial_deposit: 100 * 1_000_000 + } + ); + + bag + }, &mut scenario); + + let obligation_owner_cap = lending_market::create_obligation( + &mut lending_market, + test_scenario::ctx(&mut scenario) + ); + + let coins = coin::mint_for_testing(100 * 1_000_000, test_scenario::ctx(&mut scenario)); + + let ctokens = lending_market::deposit_liquidity_and_mint_ctokens( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + coins, + test_scenario::ctx(&mut scenario) + ); + assert!(coin::value(&ctokens) == 100 * 1_000_000, 0); + + let usdc_reserve = lending_market::reserve(&lending_market); + assert!(reserve::available_amount(usdc_reserve) == 200 * 1_000_000, 0); + + lending_market::deposit_ctokens_into_obligation( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &obligation_owner_cap, + &clock, + ctokens, + test_scenario::ctx(&mut scenario) + ); + + let obligation = lending_market::obligation(&lending_market, lending_market::obligation_id(&obligation_owner_cap)); + assert!(obligation::deposited_ctoken_amount(obligation) == 100 * 1_000_000, 0); + + test_utils::destroy(obligation_owner_cap); + test_utils::destroy(owner_cap); + test_utils::destroy(lending_market); + test_utils::destroy(clock); + test_utils::destroy(prices); + test_utils::destroy(type_to_index); + test_scenario::end(scenario); + } + + #[test] + public fun test_redeem() { + use sui::test_utils::{Self}; + use suilend::test_usdc::{TEST_USDC}; + use std::type_name::{Self}; + use suilend::reserve_config::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let State { clock, owner_cap, mut lending_market, prices, type_to_index } = setup({ + let mut bag = bag::new(test_scenario::ctx(&mut scenario)); + bag::add( + &mut bag, + type_name::get(), + ReserveArgs { + config: reserve_config::default_reserve_config(), + initial_deposit: 100 * 1_000_000 + } + ); + + bag + }, &mut scenario); + + let coins = coin::mint_for_testing(100 * 1_000_000, test_scenario::ctx(&mut scenario)); + let ctokens = lending_market::deposit_liquidity_and_mint_ctokens( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + coins, + test_scenario::ctx(&mut scenario) + ); + assert!(coin::value(&ctokens) == 100 * 1_000_000, 0); + + let usdc_reserve = lending_market::reserve(&lending_market); + let old_available_amount = reserve::available_amount(usdc_reserve); + + let tokens = lending_market::redeem_ctokens_and_withdraw_liquidity( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + ctokens, + option::none(), + test_scenario::ctx(&mut scenario) + ); + assert!(coin::value(&tokens) == 100 * 1_000_000, 0); + + let usdc_reserve = lending_market::reserve(&lending_market); + let new_available_amount = reserve::available_amount(usdc_reserve); + assert!(new_available_amount == old_available_amount - 100 * 1_000_000, 0); + + test_utils::destroy(tokens); + test_utils::destroy(owner_cap); + test_utils::destroy(lending_market); + test_utils::destroy(clock); + test_utils::destroy(prices); + test_utils::destroy(type_to_index); + test_scenario::end(scenario); + } + + #[test] + public fun test_borrow_and_repay() { + use sui::test_utils::{Self}; + use suilend::test_usdc::{TEST_USDC}; + use suilend::test_sui::{TEST_SUI}; + use suilend::mock_pyth::{Self}; + use suilend::reserve_config::{Self, default_reserve_config}; + + use std::type_name::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let State { mut clock, owner_cap, mut lending_market, mut prices, type_to_index } = setup({ + let mut bag = bag::new(test_scenario::ctx(&mut scenario)); + bag::add( + &mut bag, + type_name::get(), + ReserveArgs { + config: { + let config = default_reserve_config(); + let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); + reserve_config::set_open_ltv_pct(&mut builder, 50); + reserve_config::set_close_ltv_pct(&mut builder, 50); + reserve_config::set_max_close_ltv_pct(&mut builder, 50); + sui::test_utils::destroy(config); + + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }, + initial_deposit: 100 * 1_000_000 + } + ); + bag::add( + &mut bag, + type_name::get(), + ReserveArgs { + config: { + let config = reserve_config::default_reserve_config(); + let mut builder = reserve_config::from( + &config, + test_scenario::ctx(&mut scenario) + ); + + test_utils::destroy(config); + + reserve_config::set_borrow_fee_bps(&mut builder, 10); + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }, + initial_deposit: 100 * 1_000_000_000 + } + ); + + bag + }, &mut scenario); + + clock::set_for_testing(&mut clock, 1 * 1000); + + // set reserve parameters and prices + mock_pyth::update_price(&mut prices, 1, 0, &clock); // $1 + mock_pyth::update_price(&mut prices, 1, 1, &clock); // $10 + + // create obligation + let obligation_owner_cap = lending_market::create_obligation( + &mut lending_market, + test_scenario::ctx(&mut scenario) + ); + + let coins = coin::mint_for_testing(100 * 1_000_000, test_scenario::ctx(&mut scenario)); + let ctokens = lending_market::deposit_liquidity_and_mint_ctokens( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + coins, + test_scenario::ctx(&mut scenario) + ); + lending_market::deposit_ctokens_into_obligation( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &obligation_owner_cap, + &clock, + ctokens, + test_scenario::ctx(&mut scenario) + ); + + lending_market::refresh_reserve_price( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + mock_pyth::get_price_obj(&prices) + ); + lending_market::refresh_reserve_price( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + mock_pyth::get_price_obj(&prices) + ); + + let mut sui = lending_market::borrow( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &obligation_owner_cap, + &clock, + 1 * 1_000_000_000, + test_scenario::ctx(&mut scenario) + ); + + assert!(coin::value(&sui) == 1 * 1_000_000_000, 0); + + // state checks + let sui_reserve = lending_market::reserve(&lending_market); + assert!(reserve::borrowed_amount(sui_reserve) == decimal::from(1_001_000_000), 0); + + let obligation = lending_market::obligation(&lending_market, lending_market::obligation_id(&obligation_owner_cap)); + assert!(obligation::borrowed_amount(obligation) == decimal::from(1_001_000_000), 0); + + lending_market::repay( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + lending_market::obligation_id(&obligation_owner_cap), + &clock, + &mut sui, + test_scenario::ctx(&mut scenario) + ); + + assert!(coin::value(&sui) == 0, 0); + test_utils::destroy(sui); + + let sui_reserve = lending_market::reserve(&lending_market); + assert!(reserve::borrowed_amount(sui_reserve) == decimal::from(1_000_000), 0); + + let obligation = lending_market::obligation(&lending_market, lending_market::obligation_id(&obligation_owner_cap)); + assert!(obligation::borrowed_amount(obligation) == decimal::from(1_000_000), 0); + + let mut sui = coin::mint_for_testing(1_000_000_000, test_scenario::ctx(&mut scenario)); + lending_market::repay( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + lending_market::obligation_id(&obligation_owner_cap), + &clock, + &mut sui, + test_scenario::ctx(&mut scenario) + ); + assert!(coin::value(&sui) == 1_000_000_000 - 1_000_000, 0); + + let sui_reserve = lending_market::reserve(&lending_market); + assert!(reserve::borrowed_amount(sui_reserve) == decimal::from(0), 0); + + let obligation = lending_market::obligation(&lending_market, lending_market::obligation_id(&obligation_owner_cap)); + assert!(obligation::borrowed_amount(obligation) == decimal::from(0), 0); + + test_scenario::next_tx(&mut scenario, owner); + + lending_market::claim_fees( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + test_scenario::ctx(&mut scenario) + ); + + test_scenario::next_tx(&mut scenario, owner); + + let fees: Coin = test_scenario::take_from_address(&scenario, lending_market::fee_receiver(&lending_market)); + assert!(coin::value(&fees) == 1_000_000, 0); + + test_utils::destroy(fees); + + test_utils::destroy(sui); + test_utils::destroy(obligation_owner_cap); + test_utils::destroy(owner_cap); + test_utils::destroy(lending_market); + test_utils::destroy(clock); + test_utils::destroy(prices); + test_utils::destroy(type_to_index); + test_scenario::end(scenario); + } + + #[test] + public fun test_withdraw() { + use sui::test_utils::{Self}; + use suilend::test_usdc::{TEST_USDC}; + use suilend::test_sui::{TEST_SUI}; + use suilend::mock_pyth::{Self}; + use suilend::reserve_config::{Self, default_reserve_config}; + + use std::type_name::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let State { mut clock, owner_cap, mut lending_market, mut prices, type_to_index } = setup({ + let mut bag = bag::new(test_scenario::ctx(&mut scenario)); + bag::add( + &mut bag, + type_name::get(), + ReserveArgs { + config: { + let config = default_reserve_config(); + let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); + reserve_config::set_open_ltv_pct(&mut builder, 50); + reserve_config::set_close_ltv_pct(&mut builder, 50); + reserve_config::set_max_close_ltv_pct(&mut builder, 50); + sui::test_utils::destroy(config); + + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }, + initial_deposit: 100 * 1_000_000 + } + ); + bag::add( + &mut bag, + type_name::get(), + ReserveArgs { + config: reserve_config::default_reserve_config(), + initial_deposit: 100 * 1_000_000_000 + } + ); + + bag + }, &mut scenario); + + clock::set_for_testing(&mut clock, 1 * 1000); + + // set reserve parameters and prices + mock_pyth::update_price(&mut prices, 1, 0, &clock); // $1 + mock_pyth::update_price(&mut prices, 1, 1, &clock); // $10 + + // create obligation + let obligation_owner_cap = lending_market::create_obligation( + &mut lending_market, + test_scenario::ctx(&mut scenario) + ); + + let coins = coin::mint_for_testing(100 * 1_000_000, test_scenario::ctx(&mut scenario)); + let ctokens = lending_market::deposit_liquidity_and_mint_ctokens( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + coins, + test_scenario::ctx(&mut scenario) + ); + lending_market::deposit_ctokens_into_obligation( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &obligation_owner_cap, + &clock, + ctokens, + test_scenario::ctx(&mut scenario) + ); + + lending_market::refresh_reserve_price( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + mock_pyth::get_price_obj(&prices) + ); + lending_market::refresh_reserve_price( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + mock_pyth::get_price_obj(&prices) + ); + + let sui = lending_market::borrow( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &obligation_owner_cap, + &clock, + 2_500_000_000, + test_scenario::ctx(&mut scenario) + ); + + + let obligation = lending_market::obligation(&lending_market, lending_market::obligation_id(&obligation_owner_cap)); + let old_deposited_amount = obligation::deposited_ctoken_amount(obligation); + + let usdc = lending_market::withdraw_ctokens( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &obligation_owner_cap, + &clock, + 50 * 1_000_000, + test_scenario::ctx(&mut scenario) + ); + + let obligation = lending_market::obligation(&lending_market, lending_market::obligation_id(&obligation_owner_cap)); + let deposited_amount = obligation::deposited_ctoken_amount(obligation); + + assert!(coin::value(&usdc) == 50_000_000, 0); + assert!(deposited_amount == old_deposited_amount - 50 * 1_000_000, 0); + + test_utils::destroy(sui); + test_utils::destroy(usdc); + test_utils::destroy(obligation_owner_cap); + test_utils::destroy(owner_cap); + test_utils::destroy(lending_market); + test_utils::destroy(clock); + test_utils::destroy(prices); + test_utils::destroy(type_to_index); + test_scenario::end(scenario); + } + + #[test] + public fun test_liquidate() { + use sui::test_utils::{Self}; + use suilend::test_usdc::{TEST_USDC}; + use suilend::test_sui::{TEST_SUI}; + use suilend::mock_pyth::{Self}; + use suilend::reserve_config::{Self, default_reserve_config}; + use suilend::decimal::{sub}; + + use std::type_name::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let State { mut clock, owner_cap, mut lending_market, mut prices, type_to_index } = setup({ + let mut bag = bag::new(test_scenario::ctx(&mut scenario)); + bag::add( + &mut bag, + type_name::get(), + ReserveArgs { + config: { + let config = default_reserve_config(); + let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); + reserve_config::set_open_ltv_pct(&mut builder, 50); + reserve_config::set_close_ltv_pct(&mut builder, 50); + reserve_config::set_max_close_ltv_pct(&mut builder, 50); + sui::test_utils::destroy(config); + + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }, + initial_deposit: 100 * 1_000_000 + } + ); + bag::add( + &mut bag, + type_name::get(), + ReserveArgs { + config: reserve_config::default_reserve_config(), + initial_deposit: 100 * 1_000_000_000 + } + ); + + bag + }, &mut scenario); + + clock::set_for_testing(&mut clock, 1 * 1000); + + // set reserve parameters and prices + mock_pyth::update_price(&mut prices, 1, 0, &clock); // $1 + mock_pyth::update_price(&mut prices, 1, 1, &clock); // $10 + + // create obligation + let obligation_owner_cap = lending_market::create_obligation( + &mut lending_market, + test_scenario::ctx(&mut scenario) + ); + + let coins = coin::mint_for_testing(100 * 1_000_000, test_scenario::ctx(&mut scenario)); + let ctokens = lending_market::deposit_liquidity_and_mint_ctokens( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + coins, + test_scenario::ctx(&mut scenario) + ); + lending_market::deposit_ctokens_into_obligation( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &obligation_owner_cap, + &clock, + ctokens, + test_scenario::ctx(&mut scenario) + ); + + lending_market::refresh_reserve_price( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + mock_pyth::get_price_obj(&prices) + ); + lending_market::refresh_reserve_price( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + mock_pyth::get_price_obj(&prices) + ); + + let sui = lending_market::borrow( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &obligation_owner_cap, + &clock, + 5 * 1_000_000_000, + test_scenario::ctx(&mut scenario) + ); + test_utils::destroy(sui); + + // set the open and close ltvs of the usdc reserve to 0 + let usdc_reserve = lending_market::reserve(&lending_market); + lending_market::update_reserve_config( + &owner_cap, + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + { + let mut builder = reserve_config::from( + reserve::config(usdc_reserve), + test_scenario::ctx(&mut scenario) + ); + reserve_config::set_open_ltv_pct(&mut builder, 0); + reserve_config::set_close_ltv_pct(&mut builder, 0); + reserve_config::set_max_close_ltv_pct(&mut builder, 0); + reserve_config::set_liquidation_bonus_bps(&mut builder, 400); + reserve_config::set_max_liquidation_bonus_bps(&mut builder, 400); + reserve_config::set_protocol_liquidation_fee_bps(&mut builder, 600); + + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + } + ); + + let obligation = lending_market::obligation(&lending_market, lending_market::obligation_id(&obligation_owner_cap)); + + let sui_reserve = lending_market::reserve(&lending_market); + let old_reserve_borrowed_amount = reserve::borrowed_amount(sui_reserve); + + let old_deposited_amount = obligation::deposited_ctoken_amount(obligation); + let old_borrowed_amount = obligation::borrowed_amount(obligation); + + // liquidate the obligation + let mut sui = coin::mint_for_testing(5 * 1_000_000_000, test_scenario::ctx(&mut scenario)); + let (usdc, exemption) = lending_market::liquidate( + &mut lending_market, + lending_market::obligation_id(&obligation_owner_cap), + *bag::borrow(&type_to_index, type_name::get()), + *bag::borrow(&type_to_index, type_name::get()), + &clock, + &mut sui, + test_scenario::ctx(&mut scenario) + ); + + assert!(coin::value(&sui) == 4 * 1_000_000_000, 0); + assert!(coin::value(&usdc) == 10 * 1_000_000 + 400_000, 0); + assert!(exemption.amount() == 10 * 1_000_000 + 400_000, 0); + + let obligation = lending_market::obligation(&lending_market, lending_market::obligation_id(&obligation_owner_cap)); + + let sui_reserve = lending_market::reserve(&lending_market); + let reserve_borrowed_amount = reserve::borrowed_amount(sui_reserve); + + let deposited_amount = obligation::deposited_ctoken_amount(obligation); + let borrowed_amount = obligation::borrowed_amount(obligation); + + assert!(reserve_borrowed_amount == sub(old_reserve_borrowed_amount, decimal::from(1_000_000_000)), 0); + assert!(borrowed_amount == sub(old_borrowed_amount, decimal::from(1_000_000_000)), 0); + assert!(deposited_amount == old_deposited_amount - 11 * 1_000_000, 0); + + // check to see if we can do a full redeem even with rate limiter is disabled + lending_market::update_rate_limiter_config( + &owner_cap, + &mut lending_market, + &clock, + rate_limiter::new_config(1, 0) // disabled + ); + + let tokens = lending_market::redeem_ctokens_and_withdraw_liquidity( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + usdc, + option::some(exemption), + test_scenario::ctx(&mut scenario) + ); + assert!(coin::value(&tokens) == 10 * 1_000_000 + 400_000, 0); + + // claim fees + test_scenario::next_tx(&mut scenario, owner); + lending_market::claim_fees( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + test_scenario::ctx(&mut scenario) + ); + + test_scenario::next_tx(&mut scenario, owner); + let ctoken_fees: Coin> = test_scenario::take_from_address( + &scenario, + lending_market::fee_receiver(&lending_market) + ); + assert!(coin::value(&ctoken_fees) == 600_000, 0); + + test_utils::destroy(ctoken_fees); + test_utils::destroy(sui); + test_utils::destroy(tokens); + test_utils::destroy(obligation_owner_cap); + test_utils::destroy(owner_cap); + test_utils::destroy(lending_market); + test_utils::destroy(clock); + test_utils::destroy(prices); + test_utils::destroy(type_to_index); + test_scenario::end(scenario); + } + + const MILLISECONDS_IN_DAY: u64 = 86_400_000; + + #[test] + fun test_liquidity_mining() { + use sui::test_utils::{Self}; + use suilend::test_usdc::{TEST_USDC}; + use suilend::test_sui::{TEST_SUI}; + use suilend::reserve_config::{Self, default_reserve_config}; + use suilend::mock_pyth::{Self}; + + use std::type_name::{Self}; + + let owner = @0x26; + + let mut scenario = test_scenario::begin(owner); + let State { mut clock, owner_cap, mut lending_market, mut prices, type_to_index } = setup({ + let mut bag = bag::new(test_scenario::ctx(&mut scenario)); + bag::add( + &mut bag, + type_name::get(), + ReserveArgs { + config: { + let config = default_reserve_config(); + let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); + reserve_config::set_open_ltv_pct(&mut builder, 50); + reserve_config::set_close_ltv_pct(&mut builder, 50); + reserve_config::set_max_close_ltv_pct(&mut builder, 50); + sui::test_utils::destroy(config); + + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }, + initial_deposit: 100 * 1_000_000 + } + ); + bag::add( + &mut bag, + type_name::get(), + ReserveArgs { + config: reserve_config::default_reserve_config(), + initial_deposit: 100 * 1_000_000_000 + } + ); + + bag + }, &mut scenario); + + let usdc_rewards = coin::mint_for_testing(100 * 1_000_000, test_scenario::ctx(&mut scenario)); + let sui_rewards = coin::mint_for_testing(100 * 1_000_000_000, test_scenario::ctx(&mut scenario)); + + lending_market::add_pool_reward( + &owner_cap, + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + true, + usdc_rewards, + 0, + 10 * MILLISECONDS_IN_DAY, + &clock, + test_scenario::ctx(&mut scenario) + ); + + lending_market::add_pool_reward( + &owner_cap, + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + true, + sui_rewards, + 4 * MILLISECONDS_IN_DAY, + 14 * MILLISECONDS_IN_DAY, + &clock, + test_scenario::ctx(&mut scenario) + ); + + clock::set_for_testing(&mut clock, 1 * MILLISECONDS_IN_DAY); + + // create obligation + let obligation_owner_cap = lending_market::create_obligation( + &mut lending_market, + test_scenario::ctx(&mut scenario) + ); + + let coins = coin::mint_for_testing(100 * 1_000_000, test_scenario::ctx(&mut scenario)); + let ctokens = lending_market::deposit_liquidity_and_mint_ctokens( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + coins, + test_scenario::ctx(&mut scenario) + ); + lending_market::deposit_ctokens_into_obligation( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &obligation_owner_cap, + &clock, + ctokens, + test_scenario::ctx(&mut scenario) + ); + + + // set reserve parameters and prices + mock_pyth::update_price(&mut prices, 1, 0, &clock); // $1 + mock_pyth::update_price(&mut prices, 1, 1, &clock); // $10 + + lending_market::refresh_reserve_price( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + mock_pyth::get_price_obj(&prices) + ); + lending_market::refresh_reserve_price( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + mock_pyth::get_price_obj(&prices) + ); + let sui = lending_market::borrow( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &obligation_owner_cap, + &clock, + 1_000_000_000, + test_scenario::ctx(&mut scenario) + ); + + clock::set_for_testing(&mut clock, 9 * MILLISECONDS_IN_DAY); + let claimed_usdc = lending_market::claim_rewards( + &mut lending_market, + &obligation_owner_cap, + &clock, + *bag::borrow(&type_to_index, type_name::get()), + 0, + true, + test_scenario::ctx(&mut scenario) + ); + assert!(coin::value(&claimed_usdc) == 80 * 1_000_000, 0); + + // this fails because but rewards period is not over + // claim_rewards_and_deposit( + // &mut lending_market, + // obligation_owner_cap.obligation_id, + // &clock, + // *bag::borrow(&type_to_index, type_name::get()), + // 1, + // true, + // *bag::borrow(&type_to_index, type_name::get()), + // test_scenario::ctx(&mut scenario) + // ); + + let remaining_sui_rewards = lending_market::cancel_pool_reward( + &owner_cap, + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + true, + 1, + &clock, + test_scenario::ctx(&mut scenario) + ); + assert!(coin::value(&remaining_sui_rewards) == 50 * 1_000_000_000, 0); + + lending_market::claim_rewards_and_deposit( + &mut lending_market, + lending_market::obligation_id(&obligation_owner_cap), + &clock, + *bag::borrow(&type_to_index, type_name::get()), + 1, + true, + *bag::borrow(&type_to_index, type_name::get()), + test_scenario::ctx(&mut scenario) + ); + + assert!(obligation::deposited_ctoken_amount( + lending_market::obligation(&lending_market, lending_market::obligation_id(&obligation_owner_cap)) + ) == 49 * 1_000_000_000, 0); + assert!(obligation::borrowed_amount( + lending_market::obligation(&lending_market, lending_market::obligation_id(&obligation_owner_cap)) + ) == decimal::from(0), 0); + + let dust_sui_rewards = lending_market::close_pool_reward( + &owner_cap, + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + true, + 1, + &clock, + test_scenario::ctx(&mut scenario) + ); + + assert!(coin::value(&dust_sui_rewards) == 0, 0); + + test_utils::destroy(dust_sui_rewards); + test_utils::destroy(remaining_sui_rewards); + test_utils::destroy(sui); + test_utils::destroy(owner_cap); + test_utils::destroy(obligation_owner_cap); + test_utils::destroy(claimed_usdc); + test_utils::destroy(lending_market); + test_utils::destroy(clock); + test_utils::destroy(prices); + test_utils::destroy(type_to_index); + test_scenario::end(scenario); + + } + + #[test] + public fun test_forgive_debt() { + use sui::test_utils::{Self}; + use suilend::test_usdc::{TEST_USDC}; + use suilend::test_sui::{TEST_SUI}; + use suilend::mock_pyth::{Self}; + use suilend::reserve_config::{Self, default_reserve_config}; + use suilend::decimal::{sub, eq}; + + use std::type_name::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let State { mut clock, owner_cap, mut lending_market, mut prices, type_to_index } = setup({ + let mut bag = bag::new(test_scenario::ctx(&mut scenario)); + bag::add( + &mut bag, + type_name::get(), + ReserveArgs { + config: { + let config = default_reserve_config(); + let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); + reserve_config::set_open_ltv_pct(&mut builder, 50); + reserve_config::set_close_ltv_pct(&mut builder, 50); + reserve_config::set_max_close_ltv_pct(&mut builder, 50); + sui::test_utils::destroy(config); + + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }, + initial_deposit: 100 * 1_000_000 + } + ); + bag::add( + &mut bag, + type_name::get(), + ReserveArgs { + config: reserve_config::default_reserve_config(), + initial_deposit: 100 * 1_000_000_000 + } + ); + + bag + }, &mut scenario); + + clock::set_for_testing(&mut clock, 1 * 1000); + + // set reserve parameters and prices + mock_pyth::update_price(&mut prices, 1, 0, &clock); // $1 + mock_pyth::update_price(&mut prices, 1, 1, &clock); // $10 + + // create obligation + let obligation_owner_cap = lending_market::create_obligation( + &mut lending_market, + test_scenario::ctx(&mut scenario) + ); + + let coins = coin::mint_for_testing(100 * 1_000_000, test_scenario::ctx(&mut scenario)); + let ctokens = lending_market::deposit_liquidity_and_mint_ctokens( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + coins, + test_scenario::ctx(&mut scenario) + ); + lending_market::deposit_ctokens_into_obligation( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &obligation_owner_cap, + &clock, + ctokens, + test_scenario::ctx(&mut scenario) + ); + + lending_market::refresh_reserve_price( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + mock_pyth::get_price_obj(&prices) + ); + lending_market::refresh_reserve_price( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + mock_pyth::get_price_obj(&prices) + ); + + let sui = lending_market::borrow( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &obligation_owner_cap, + &clock, + 5 * 1_000_000_000, + test_scenario::ctx(&mut scenario) + ); + test_utils::destroy(sui); + + mock_pyth::update_price(&mut prices, 1, 2, &clock); // $10 + lending_market::refresh_reserve_price( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + mock_pyth::get_price_obj(&prices) + ); + + // liquidate the obligation + let mut sui = coin::mint_for_testing(1 * 1_000_000_000, test_scenario::ctx(&mut scenario)); + let (usdc, _exemption) = lending_market::liquidate( + &mut lending_market, + lending_market::obligation_id(&obligation_owner_cap), + *bag::borrow(&type_to_index, type_name::get()), + *bag::borrow(&type_to_index, type_name::get()), + &clock, + &mut sui, + test_scenario::ctx(&mut scenario) + ); + + let obligation = lending_market::obligation(&lending_market, lending_market::obligation_id(&obligation_owner_cap)); + let sui_reserve = lending_market::reserve(&lending_market); + let old_reserve_borrowed_amount = reserve::borrowed_amount(sui_reserve); + let old_borrowed_amount = obligation::borrowed_amount(obligation); + + lending_market::forgive( + &owner_cap, + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + lending_market::obligation_id(&obligation_owner_cap), + &clock, + 1_000_000_000, + ); + + let obligation = lending_market::obligation(&lending_market, lending_market::obligation_id(&obligation_owner_cap)); + let sui_reserve = lending_market::reserve(&lending_market); + let reserve_borrowed_amount = reserve::borrowed_amount(sui_reserve); + let borrowed_amount = obligation::borrowed_amount(obligation); + + assert!(eq(sub(old_borrowed_amount, borrowed_amount), decimal::from(1_000_000_000)), 0); + assert!(eq(sub(old_reserve_borrowed_amount, reserve_borrowed_amount), decimal::from(1_000_000_000)), 0); + + test_utils::destroy(usdc); + test_utils::destroy(sui); + test_utils::destroy(obligation_owner_cap); + test_utils::destroy(owner_cap); + test_utils::destroy(lending_market); + test_utils::destroy(clock); + test_utils::destroy(prices); + test_utils::destroy(type_to_index); + test_scenario::end(scenario); + } + + #[test] + public fun test_max_borrow() { + use sui::test_utils::{Self}; + use suilend::test_usdc::{TEST_USDC}; + use suilend::test_sui::{TEST_SUI}; + use suilend::mock_pyth::{Self}; + use suilend::reserve_config::{Self, default_reserve_config}; + + use std::type_name::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let State { mut clock, owner_cap, mut lending_market, mut prices, type_to_index } = setup({ + let mut bag = bag::new(test_scenario::ctx(&mut scenario)); + bag::add( + &mut bag, + type_name::get(), + ReserveArgs { + config: { + let config = default_reserve_config(); + let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); + reserve_config::set_open_ltv_pct(&mut builder, 50); + reserve_config::set_close_ltv_pct(&mut builder, 50); + reserve_config::set_max_close_ltv_pct(&mut builder, 50); + sui::test_utils::destroy(config); + + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }, + initial_deposit: 100 * 1_000_000 + } + ); + bag::add( + &mut bag, + type_name::get(), + ReserveArgs { + config: { + let config = reserve_config::default_reserve_config(); + let mut builder = reserve_config::from( + &config, + test_scenario::ctx(&mut scenario) + ); + + test_utils::destroy(config); + + reserve_config::set_borrow_fee_bps(&mut builder, 10); + // reserve_config::set_borrow_limit(&mut builder, 4 * 1_000_000_000); + // reserve_config::set_borrow_limit_usd(&mut builder, 20); + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }, + initial_deposit: 100 * 1_000_000_000 + } + ); + + bag + }, &mut scenario); + + clock::set_for_testing(&mut clock, 1 * 1000); + + // set reserve parameters and prices + mock_pyth::update_price(&mut prices, 1, 0, &clock); // $1 + mock_pyth::update_price(&mut prices, 1, 1, &clock); // $10 + + // create obligation + let obligation_owner_cap = lending_market::create_obligation( + &mut lending_market, + test_scenario::ctx(&mut scenario) + ); + + let coins = coin::mint_for_testing(100 * 1_000_000, test_scenario::ctx(&mut scenario)); + let ctokens = lending_market::deposit_liquidity_and_mint_ctokens( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + coins, + test_scenario::ctx(&mut scenario) + ); + lending_market::deposit_ctokens_into_obligation( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &obligation_owner_cap, + &clock, + ctokens, + test_scenario::ctx(&mut scenario) + ); + + lending_market::refresh_reserve_price( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + mock_pyth::get_price_obj(&prices) + ); + lending_market::refresh_reserve_price( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + mock_pyth::get_price_obj(&prices) + ); + + let sui = lending_market::borrow( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &obligation_owner_cap, + &clock, + U64_MAX, + test_scenario::ctx(&mut scenario) + ); + + assert!(coin::value(&sui) == 4_995_004_995, 0); + + test_utils::destroy(sui); + test_utils::destroy(obligation_owner_cap); + test_utils::destroy(owner_cap); + test_utils::destroy(lending_market); + test_utils::destroy(clock); + test_utils::destroy(prices); + test_utils::destroy(type_to_index); + test_scenario::end(scenario); + } + + #[test] + public fun test_max_withdraw() { + use sui::test_utils::{Self}; + use suilend::test_usdc::{TEST_USDC}; + use suilend::test_sui::{TEST_SUI}; + use suilend::mock_pyth::{Self}; + use suilend::reserve_config::{Self, default_reserve_config}; + + use std::type_name::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let State { mut clock, owner_cap, mut lending_market, mut prices, type_to_index } = setup({ + let mut bag = bag::new(test_scenario::ctx(&mut scenario)); + bag::add( + &mut bag, + type_name::get(), + ReserveArgs { + config: { + let config = default_reserve_config(); + let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); + reserve_config::set_open_ltv_pct(&mut builder, 50); + reserve_config::set_close_ltv_pct(&mut builder, 50); + reserve_config::set_max_close_ltv_pct(&mut builder, 50); + sui::test_utils::destroy(config); + + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }, + initial_deposit: 100 * 1_000_000 + } + ); + bag::add( + &mut bag, + type_name::get(), + ReserveArgs { + config: { + let config = default_reserve_config(); + let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); + reserve_config::set_borrow_weight_bps(&mut builder, 20_000); + sui::test_utils::destroy(config); + + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }, + initial_deposit: 100 * 1_000_000_000 + } + ); + + bag + }, &mut scenario); + + clock::set_for_testing(&mut clock, 1 * 1000); + + // set reserve parameters and prices + mock_pyth::update_price(&mut prices, 1, 0, &clock); // $1 + mock_pyth::update_price(&mut prices, 1, 1, &clock); // $10 + + // create obligation + let obligation_owner_cap = lending_market::create_obligation( + &mut lending_market, + test_scenario::ctx(&mut scenario) + ); + + let coins = coin::mint_for_testing(200 * 1_000_000, test_scenario::ctx(&mut scenario)); + let ctokens = lending_market::deposit_liquidity_and_mint_ctokens( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + coins, + test_scenario::ctx(&mut scenario) + ); + + lending_market::deposit_ctokens_into_obligation( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &obligation_owner_cap, + &clock, + ctokens, + test_scenario::ctx(&mut scenario) + ); + + lending_market::refresh_reserve_price( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + mock_pyth::get_price_obj(&prices) + ); + lending_market::refresh_reserve_price( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + mock_pyth::get_price_obj(&prices) + ); + + let sui = lending_market::borrow( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &obligation_owner_cap, + &clock, + 2_500_000_000, + test_scenario::ctx(&mut scenario) + ); + + lending_market::update_rate_limiter_config( + &owner_cap, + &mut lending_market, + &clock, + rate_limiter::new_config(1, 10) // disabled + ); + + let cusdc = lending_market::withdraw_ctokens( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &obligation_owner_cap, + &clock, + U64_MAX, + test_scenario::ctx(&mut scenario) + ); + let usdc = lending_market::redeem_ctokens_and_withdraw_liquidity( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + cusdc, + option::none(), + test_scenario::ctx(&mut scenario) + ); + + assert!(coin::value(&usdc) == 10 * 1_000_000, 0); + + test_utils::destroy(sui); + test_utils::destroy(usdc); + test_utils::destroy(obligation_owner_cap); + test_utils::destroy(owner_cap); + test_utils::destroy(lending_market); + test_utils::destroy(clock); + test_utils::destroy(prices); + test_utils::destroy(type_to_index); + test_scenario::end(scenario); + } + + #[test] + public fun test_change_pyth_price_feed() { + use sui::test_utils::{Self, assert_eq}; + use sui::test_scenario::ctx; + use suilend::test_usdc::{TEST_USDC}; + use suilend::test_sui::{TEST_SUI}; + use suilend::mock_pyth::{Self}; + use suilend::reserve_config::{Self, default_reserve_config}; + + use std::type_name::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let State { mut clock, owner_cap, mut lending_market, mut prices, type_to_index } = setup({ + let mut bag = bag::new(test_scenario::ctx(&mut scenario)); + bag::add( + &mut bag, + type_name::get(), + ReserveArgs { + config: { + let config = default_reserve_config(); + let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); + reserve_config::set_open_ltv_pct(&mut builder, 50); + reserve_config::set_close_ltv_pct(&mut builder, 50); + reserve_config::set_max_close_ltv_pct(&mut builder, 50); + sui::test_utils::destroy(config); + + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }, + initial_deposit: 100 * 1_000_000 + } + ); + bag::add( + &mut bag, + type_name::get(), + ReserveArgs { + config: { + let config = default_reserve_config(); + let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); + reserve_config::set_borrow_weight_bps(&mut builder, 20_000); + sui::test_utils::destroy(config); + + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }, + initial_deposit: 100 * 1_000_000_000 + } + ); + + bag + }, &mut scenario); + + clock::set_for_testing(&mut clock, 1 * 1000); + + // change the price feed as admin + let new_price_info_obj = mock_pyth::new_price_info_obj(3_u8, ctx(&mut scenario)); + + let array_idx = *bag::borrow(&type_to_index, type_name::get()); + + lending_market::change_reserve_price_feed( + &owner_cap, + &mut lending_market, + array_idx, + &new_price_info_obj, + &clock, + ); + + // TODO: assert changes + let reserve_ref = lending_market::reserve(&lending_market); + let price_id = pyth::price_info::get_price_identifier( + &pyth::price_info::get_price_info_from_price_info_object(&new_price_info_obj) + ); + + assert_eq(*reserve::price_identifier(reserve_ref), price_id); + + test_utils::destroy(owner_cap); + test_utils::destroy(lending_market); + test_utils::destroy(clock); + test_utils::destroy(prices); + test_utils::destroy(type_to_index); + test_utils::destroy(new_price_info_obj); + test_scenario::end(scenario); + } + + #[test] + public fun test_admin_new_obligation_cap() { + use sui::test_utils::{Self}; + use suilend::test_usdc::{TEST_USDC}; + use suilend::test_sui::{TEST_SUI}; + use suilend::mock_pyth::{Self}; + use suilend::reserve_config::{Self, default_reserve_config}; + + use std::type_name::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let State { mut clock, owner_cap, mut lending_market, mut prices, type_to_index } = setup({ + let mut bag = bag::new(test_scenario::ctx(&mut scenario)); + bag::add( + &mut bag, + type_name::get(), + ReserveArgs { + config: { + let config = default_reserve_config(); + let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); + reserve_config::set_open_ltv_pct(&mut builder, 50); + reserve_config::set_close_ltv_pct(&mut builder, 50); + reserve_config::set_max_close_ltv_pct(&mut builder, 50); + sui::test_utils::destroy(config); + + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }, + initial_deposit: 100 * 1_000_000 + } + ); + bag::add( + &mut bag, + type_name::get(), + ReserveArgs { + config: { + let config = default_reserve_config(); + let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); + reserve_config::set_borrow_weight_bps(&mut builder, 20_000); + sui::test_utils::destroy(config); + + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }, + initial_deposit: 100 * 1_000_000_000 + } + ); + + bag + }, &mut scenario); + + clock::set_for_testing(&mut clock, 1 * 1000); + + // set reserve parameters and prices + mock_pyth::update_price(&mut prices, 1, 0, &clock); // $1 + mock_pyth::update_price(&mut prices, 1, 1, &clock); // $10 + + // create obligation + let obligation_owner_cap = lending_market::create_obligation( + &mut lending_market, + test_scenario::ctx(&mut scenario) + ); + + let obligation_id = lending_market::obligation_id(&obligation_owner_cap); + + // Mock accidental burning of obligation cap + transfer::public_transfer(obligation_owner_cap, @0x0); + + let obligation_owner_cap = lending_market::new_obligation_owner_cap( + &owner_cap, + &lending_market, + obligation_id, + test_scenario::ctx(&mut scenario) + ); + + assert!(lending_market::obligation_id(&obligation_owner_cap) == obligation_id, 0); + + test_utils::destroy(obligation_owner_cap); + test_utils::destroy(owner_cap); + test_utils::destroy(lending_market); + test_utils::destroy(clock); + test_utils::destroy(prices); + test_utils::destroy(type_to_index); + test_scenario::end(scenario); + } +} \ No newline at end of file diff --git a/contracts/suilend/tests/obligation_tests.move b/contracts/suilend/tests/obligation_tests.move new file mode 100644 index 0000000..aa0c970 --- /dev/null +++ b/contracts/suilend/tests/obligation_tests.move @@ -0,0 +1,2267 @@ +#[test_only] +module suilend::obligation_tests { + // === Imports === + use std::type_name::{Self}; + use suilend::reserve::{Self, Reserve, config}; + use suilend::decimal::{Self, Decimal, mul, add, sub, div, gt, lt, min, floor, le, eq, saturating_sub}; + use suilend::liquidity_mining::{Self}; + + use sui::test_scenario::{Self, Scenario}; + + use sui::clock::{Self}; + use suilend::obligation::{Self, Obligation, create_obligation, deposit, borrow, refresh, withdraw, repay, liquidate, forgive, create_borrow_for_testing}; + + public struct TEST_MARKET {} + + public struct TEST_SUI {} + + public struct TEST_USDC {} + + public struct TEST_USDT {} + + public struct TEST_ETH {} + + public struct TEST_AUSD {} + + use suilend::reserve_config::{Self, default_reserve_config}; + + fun sui_reserve

(scenario: &mut Scenario): Reserve

{ + let config = default_reserve_config(); + let mut builder = reserve_config::from(&config, test_scenario::ctx(scenario)); + reserve_config::set_open_ltv_pct(&mut builder, 20); + reserve_config::set_close_ltv_pct(&mut builder, 50); + reserve_config::set_max_close_ltv_pct(&mut builder, 50); + reserve_config::set_interest_rate_utils(&mut builder, { + let mut v = vector::empty(); + vector::push_back(&mut v, 0); + vector::push_back(&mut v, 100); + v + }); + reserve_config::set_interest_rate_aprs(&mut builder, { + let mut v = vector::empty(); + vector::push_back(&mut v, 31536000 * 4); + vector::push_back(&mut v, 31536000 * 8); + v + }); + + sui::test_utils::destroy(config); + let config = reserve_config::build(builder, test_scenario::ctx(scenario)); + reserve::create_for_testing( + config, + 0, + 9, + decimal::from(10), + 0, + 0, + 0, + decimal::from(0), + decimal::from(3), + 0, + test_scenario::ctx(scenario) + ) + } + + fun usdc_reserve

(scenario: &mut Scenario): Reserve

{ + let config = default_reserve_config(); + let mut builder = reserve_config::from(&config, test_scenario::ctx(scenario)); + reserve_config::set_open_ltv_pct(&mut builder, 50); + reserve_config::set_close_ltv_pct(&mut builder, 80); + reserve_config::set_max_close_ltv_pct(&mut builder, 80); + reserve_config::set_borrow_weight_bps(&mut builder, 20_000); + reserve_config::set_interest_rate_utils(&mut builder, { + let mut v = vector::empty(); + vector::push_back(&mut v, 0); + vector::push_back(&mut v, 100); + v + }); + reserve_config::set_interest_rate_aprs(&mut builder, { + let mut v = vector::empty(); + vector::push_back(&mut v, 3153600000); + vector::push_back(&mut v, 3153600000 * 2); + v + }); + + sui::test_utils::destroy(config); + let config = reserve_config::build(builder, test_scenario::ctx(scenario)); + + reserve::create_for_testing( + config, + 1, + 6, + decimal::from(1), + 0, + 0, + 0, + decimal::from(0), + decimal::from(2), + 0, + test_scenario::ctx(scenario) + ) + } + + fun usdt_reserve

(scenario: &mut Scenario): Reserve

{ + let config = default_reserve_config(); + let mut builder = reserve_config::from(&config, test_scenario::ctx(scenario)); + reserve_config::set_open_ltv_pct(&mut builder, 50); + reserve_config::set_close_ltv_pct(&mut builder, 80); + reserve_config::set_max_close_ltv_pct(&mut builder, 80); + reserve_config::set_borrow_weight_bps(&mut builder, 20_000); + reserve_config::set_interest_rate_utils(&mut builder, { + let mut v = vector::empty(); + vector::push_back(&mut v, 0); + vector::push_back(&mut v, 100); + v + }); + reserve_config::set_interest_rate_aprs(&mut builder, { + let mut v = vector::empty(); + vector::push_back(&mut v, 3153600000); + vector::push_back(&mut v, 3153600000 * 2); + + v + }); + + sui::test_utils::destroy(config); + let config = reserve_config::build(builder, test_scenario::ctx(scenario)); + + reserve::create_for_testing( + config, + 2, + 6, + decimal::from(1), + 0, + 0, + 0, + decimal::from(0), + decimal::from(2), + 0, + test_scenario::ctx(scenario) + ) + } + + fun eth_reserve

(scenario: &mut Scenario): Reserve

{ + let config = default_reserve_config(); + let mut builder = reserve_config::from(&config, test_scenario::ctx(scenario)); + reserve_config::set_open_ltv_pct(&mut builder, 10); + reserve_config::set_close_ltv_pct(&mut builder, 20); + reserve_config::set_max_close_ltv_pct(&mut builder, 20); + reserve_config::set_borrow_weight_bps(&mut builder, 30_000); + reserve_config::set_interest_rate_utils(&mut builder, { + let mut v = vector::empty(); + vector::push_back(&mut v, 0); + vector::push_back(&mut v, 100); + v + }); + reserve_config::set_interest_rate_aprs(&mut builder, { + let mut v = vector::empty(); + vector::push_back(&mut v, 3153600000 * 10); + vector::push_back(&mut v, 3153600000 * 20); + + v + }); + + sui::test_utils::destroy(config); + let config = reserve_config::build(builder, test_scenario::ctx(scenario)); + + reserve::create_for_testing( + config, + 3, + 8, + decimal::from(2000), + 0, + 0, + 0, + decimal::from(0), + decimal::from(3), + 0, + test_scenario::ctx(scenario) + ) + } + + fun ausd_reserve

(scenario: &mut Scenario): Reserve

{ + let config = default_reserve_config(); + let mut builder = reserve_config::from(&config, test_scenario::ctx(scenario)); + reserve_config::set_open_ltv_pct(&mut builder, 50); + reserve_config::set_close_ltv_pct(&mut builder, 80); + reserve_config::set_max_close_ltv_pct(&mut builder, 80); + reserve_config::set_borrow_weight_bps(&mut builder, 20_000); + reserve_config::set_interest_rate_utils(&mut builder, { + let mut v = vector::empty(); + vector::push_back(&mut v, 0); + vector::push_back(&mut v, 100); + v + }); + reserve_config::set_interest_rate_aprs(&mut builder, { + let mut v = vector::empty(); + vector::push_back(&mut v, 3153600000); + vector::push_back(&mut v, 3153600000 * 2); + + v + }); + + sui::test_utils::destroy(config); + let config = reserve_config::build(builder, test_scenario::ctx(scenario)); + + reserve::create_for_testing( + config, + 5, + 6, + decimal::from(1), + 0, + 0, + 0, + decimal::from(0), + decimal::from(2), + 0, + test_scenario::ctx(scenario) + ) + } + + fun reserves

(scenario: &mut Scenario): vector> { + let mut v = vector::empty(); + vector::push_back(&mut v, sui_reserve(scenario)); + vector::push_back(&mut v, usdc_reserve(scenario)); + vector::push_back(&mut v, usdt_reserve(scenario)); + vector::push_back(&mut v, eth_reserve(scenario)); + vector::push_back(&mut v, ausd_reserve(scenario)); + + v + } + + fun get_reserve_array_index(reserves: &vector>): u64 { + let mut i = 0; + while (i < vector::length(reserves)) { + let reserve = vector::borrow(reserves, i); + if (type_name::get() == reserve::coin_type(reserve)) { + return i + }; + + i = i + 1; + }; + + i + } + + fun get_reserve(reserves: &vector>): &Reserve

{ + let i = get_reserve_array_index(reserves); + assert!(i < vector::length(reserves), 0); + vector::borrow(reserves, i) + } + + fun get_reserve_mut(reserves: &mut vector>): &mut Reserve

{ + let i = get_reserve_array_index(reserves); + assert!(i < vector::length(reserves), 0); + vector::borrow_mut(reserves, i) + } + + + #[test] + public fun test_deposit() { + use sui::test_utils::{Self}; + + let owner = @0x26; + 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 obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); + + reserve::update_price_for_testing( + &mut usdc_reserve, + &clock, + decimal::from(1), + decimal::from_percent(90) + ); + reserve::update_price_for_testing( + &mut sui_reserve, + &clock, + decimal::from(10), + decimal::from(9) + ); + + deposit(&mut obligation, &mut usdc_reserve, &clock, 100 * 1_000_000); + deposit(&mut obligation, &mut usdc_reserve, &clock, 100 * 1_000_000); + deposit(&mut obligation, &mut sui_reserve, &clock, 100 * 1_000_000_000); + + let deposits = obligation.deposits(); + assert!(deposits.length() == 2, 0); + + let usdc_deposit = &deposits[0]; + assert!(usdc_deposit.deposited_ctoken_amount() == 200 * 1_000_000, 1); + assert!(usdc_deposit.market_value() == decimal::from(200), 2); + + let user_reward_manager = &obligation.user_reward_managers()[usdc_deposit.user_reward_manager_index()]; + assert!(liquidity_mining::shares(user_reward_manager) == 200 * 1_000_000, 5); + + let sui_deposit = &deposits[1]; + 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 = &obligation.user_reward_managers()[sui_deposit.user_reward_manager_index()]; + assert!(liquidity_mining::shares(user_reward_manager) == 100 * 1_000_000_000, 6); + + assert!(obligation.borrows().length() == 0, 0); + assert!(obligation.deposited_value_usd() == decimal::from(1200), 0); + assert!(obligation.allowed_borrow_value_usd() == decimal::from(270), 1); + assert!(obligation.unhealthy_borrow_value_usd() == decimal::from(660), 2); + assert!(obligation.unweighted_borrowed_value_usd() == decimal::from(0), 3); + assert!(obligation.weighted_borrowed_value_usd() == decimal::from(0), 4); + + sui::test_utils::destroy(lending_market_id); + test_utils::destroy(usdc_reserve); + test_utils::destroy(sui_reserve); + test_utils::destroy(obligation); + clock::destroy_for_testing(clock); + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = suilend::obligation::EObligationIsNotHealthy)] + public fun test_borrow_fail() { + use sui::test_scenario::{Self}; + + let owner = @0x26; + 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 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); + + sui::test_utils::destroy(lending_market_id); + sui::test_utils::destroy(usdc_reserve); + sui::test_utils::destroy(sui_reserve); + sui::test_utils::destroy(obligation); + sui::test_utils::destroy(clock); + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = suilend::obligation::ECannotDepositAndBorrowSameAsset)] + public fun test_borrow_fail_deposit_borrow_same_asset_1() { + use sui::test_scenario::{Self}; + + let owner = @0x26; + 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 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, 1); + deposit(&mut obligation, &mut 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(obligation); + sui::test_utils::destroy(clock); + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = suilend::obligation::ECannotDepositAndBorrowSameAsset)] + public fun test_borrow_fail_deposit_borrow_same_asset_2() { + use sui::test_scenario::{Self}; + + let owner = @0x26; + 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 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); + + sui::test_utils::destroy(lending_market_id); + sui::test_utils::destroy(sui_reserve); + sui::test_utils::destroy(obligation); + sui::test_utils::destroy(clock); + test_scenario::end(scenario); + } + + #[test] + public fun test_borrow_isolated_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)); + + deposit( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 100 * 1_000_000_000 + ); + + let config = { + let mut builder = reserve_config::from( + config(get_reserve(&reserves)), + test_scenario::ctx(&mut scenario) + ); + reserve_config::set_open_ltv_pct(&mut builder, 0); + reserve_config::set_close_ltv_pct(&mut builder, 0); + reserve_config::set_isolated(&mut builder, true); + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }; + + reserve::update_reserve_config( + get_reserve_mut(&mut reserves), + config + ); + + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1 + ); + + refresh(&mut obligation, &mut reserves, &clock); + + // this fails + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1 + ); + + sui::test_utils::destroy(clock); + sui::test_utils::destroy(reserves); + sui::test_utils::destroy(lending_market_id); + sui::test_utils::destroy(obligation); + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = suilend::obligation::EIsolatedAssetViolation)] + public fun test_borrow_isolated_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)); + + deposit( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 100 * 1_000_000_000 + ); + + let config = { + let mut builder = reserve_config::from( + config(get_reserve(&reserves)), + test_scenario::ctx(&mut scenario) + ); + reserve_config::set_open_ltv_pct(&mut builder, 0); + reserve_config::set_close_ltv_pct(&mut builder, 0); + reserve_config::set_isolated(&mut builder, true); + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }; + + reserve::update_reserve_config( + get_reserve_mut(&mut reserves), + config + ); + + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1 + ); + + refresh(&mut obligation, &mut reserves, &clock); + + // this fails + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1 + ); + + sui::test_utils::destroy(clock); + sui::test_utils::destroy(reserves); + sui::test_utils::destroy(lending_market_id); + sui::test_utils::destroy(obligation); + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = suilend::obligation::EIsolatedAssetViolation)] + public fun test_borrow_isolated_fail_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)); + + deposit( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 100 * 1_000_000_000 + ); + + let config = { + let mut builder = reserve_config::from( + config(get_reserve(&reserves)), + test_scenario::ctx(&mut scenario) + ); + reserve_config::set_open_ltv_pct(&mut builder, 0); + reserve_config::set_close_ltv_pct(&mut builder, 0); + reserve_config::set_isolated(&mut builder, true); + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }; + + reserve::update_reserve_config( + get_reserve_mut(&mut reserves), + config + ); + + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1 + ); + + refresh(&mut obligation, &mut reserves, &clock); + + // this fails + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1 + ); + + sui::test_utils::destroy(clock); + sui::test_utils::destroy(reserves); + sui::test_utils::destroy(lending_market_id); + sui::test_utils::destroy(obligation); + test_scenario::end(scenario); + } + + #[test] + public fun test_max_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 usdc_reserve = usdc_reserve(&mut scenario); + let mut sui_reserve = sui_reserve(&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, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + &mut sui_reserve, + &clock, + decimal::from(10), + decimal::from(5) + ); + + deposit(&mut obligation, &mut sui_reserve, &clock, 100 * 1_000_000_000); + + let max_borrow = obligation.max_borrow_amount(&usdc_reserve); + assert!(max_borrow == 25_000_000, 0); + + sui::test_utils::destroy(lending_market_id); + sui::test_utils::destroy(usdc_reserve); + sui::test_utils::destroy(sui_reserve); + sui::test_utils::destroy(obligation); + clock::destroy_for_testing(clock); + test_scenario::end(scenario); + } + + #[test] + public fun test_borrow_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 usdc_reserve = usdc_reserve(&mut scenario); + let mut sui_reserve = sui_reserve(&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, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + &mut 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); + + assert!(obligation.deposits().length() == 1, 0); + + let sui_deposit = &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 = &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 = &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 = &obligation.user_reward_managers()[usdc_borrow.user_reward_manager_index()]; + assert!(liquidity_mining::shares(user_reward_manager) == 25 * 1_000_000 / 2, 4); + + assert!(obligation.deposited_value_usd() == decimal::from(1000), 0); + assert!(obligation.allowed_borrow_value_usd() == decimal::from(100), 1); + assert!(obligation.unhealthy_borrow_value_usd() == decimal::from(500), 2); + assert!(obligation.unweighted_borrowed_value_usd() == decimal::from(25), 3); + assert!(obligation.weighted_borrowed_value_usd() == decimal::from(50), 4); + 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(obligation); + clock::destroy_for_testing(clock); + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = suilend::obligation::EObligationIsNotHealthy)] + public fun test_withdraw_fail_unhealthy() { + 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 usdc_reserve = usdc_reserve(&mut scenario); + let mut sui_reserve = sui_reserve(&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); + + withdraw(&mut obligation, &mut 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(obligation); + sui::test_utils::destroy(clock); + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = suilend::obligation::EDepositNotFound)] + public fun test_withdraw_fail_deposit_not_found() { + 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 usdc_reserve = usdc_reserve(&mut scenario); + let mut sui_reserve = sui_reserve(&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); + + withdraw(&mut obligation, &mut 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(obligation); + sui::test_utils::destroy(clock); + test_scenario::end(scenario); + } + + #[test] + public fun test_max_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 usdc_reserve = usdc_reserve(&mut scenario); + let mut usdt_reserve = usdt_reserve(&mut scenario); + let mut sui_reserve = sui_reserve(&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, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + &mut usdt_reserve, + &clock, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + &mut sui_reserve, + &clock, + decimal::from(10), + decimal::from(5) + ); + + deposit(&mut obligation, &mut sui_reserve, &clock, 100 * 1_000_000_000); + + let amount = obligation.max_withdraw_amount(&sui_reserve); + assert!(amount == 100 * 1_000_000_000, 0); + + borrow(&mut obligation, &mut usdc_reserve, &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); + assert!(amount == 20 * 1_000_000_000, 0); + + deposit(&mut obligation, &mut usdt_reserve, &clock, 100 * 1_000_000); + + let amount = obligation.max_withdraw_amount(&usdt_reserve); + 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(obligation); + clock::destroy_for_testing(clock); + test_scenario::end(scenario); + } + + + #[test] + public fun test_withdraw_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 usdc_reserve = usdc_reserve(&mut scenario); + let mut sui_reserve = sui_reserve(&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, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + &mut 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, 20 * 1_000_000); + withdraw(&mut obligation, &mut sui_reserve, &clock, 20 * 1_000_000_000); + + assert!(obligation.deposits().length() == 1, 0); + + let sui_deposit = &obligation.deposits()[0]; + assert!(sui_deposit.deposited_ctoken_amount() == 80 * 1_000_000_000, 3); + assert!(sui_deposit.market_value() == decimal::from(800), 4); + + let user_reward_manager = &obligation.user_reward_managers()[sui_deposit.user_reward_manager_index()]; + assert!(liquidity_mining::shares(user_reward_manager) == 80 * 1_000_000_000, 3); + + assert!(obligation.borrows().length() == 1, 0); + + let usdc_borrow = &obligation.borrows()[0]; + assert!(usdc_borrow.borrowed_amount() == decimal::from(20 * 1_000_000), 1); + assert!(usdc_borrow.cumulative_borrow_rate() == decimal::from(2), 2); + assert!(usdc_borrow.market_value() == decimal::from(20), 3); + + let user_reward_manager = &obligation.user_reward_managers()[usdc_borrow.user_reward_manager_index()]; + assert!(liquidity_mining::shares(user_reward_manager) == 20 * 1_000_000 / 2, 4); + + assert!(obligation.deposited_value_usd() == decimal::from(800), 0); + assert!(obligation.allowed_borrow_value_usd() == decimal::from(80), 1); + assert!(obligation.unhealthy_borrow_value_usd() == decimal::from(400), 2); + assert!(obligation.unweighted_borrowed_value_usd() == decimal::from(20), 3); + assert!(obligation.weighted_borrowed_value_usd() == decimal::from(40), 4); + 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); + clock::destroy_for_testing(clock); + sui::test_utils::destroy(obligation); + test_scenario::end(scenario); + } + + #[test] + public fun test_repay_happy() { + use sui::test_scenario::{Self}; + use sui::clock::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + 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 obligation = create_obligation(object::uid_to_inner(&lending_market_id), test_scenario::ctx(&mut scenario)); + + reserve::update_price_for_testing( + &mut usdc_reserve, + &clock, + decimal::from(1), + decimal::from(2) + ); + reserve::update_price_for_testing( + &mut 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, 25 * 1_000_000); + + clock::set_for_testing(&mut clock, 1000); + reserve::compound_interest(&mut usdc_reserve, &clock); + + let repay_amount = repay( + &mut obligation, + &mut usdc_reserve, + &clock, + decimal::from(25 * 1_000_000) + ); + assert!(repay_amount == decimal::from(25 * 1_000_000), 0); + + assert!(obligation.deposits().length() == 1, 0); + + let sui_deposit = &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 = &obligation.user_reward_managers()[sui_deposit.user_reward_manager_index()]; + assert!(liquidity_mining::shares(user_reward_manager) == 100 * 1_000_000_000, 5); + + assert!(obligation.borrows().length() == 1, 0); + + // borrow was compounded by 1% so there should be borrows outstanding + let usdc_borrow = &obligation.borrows()[0]; + assert!(usdc_borrow.borrowed_amount() == decimal::from(250_000), 1); + assert!(usdc_borrow.cumulative_borrow_rate() == decimal::from_percent(202), 2); + assert!(usdc_borrow.market_value() == decimal::from_percent(25), 3); + + let user_reward_manager = &obligation.user_reward_managers()[usdc_borrow.user_reward_manager_index()]; + // 250_000 / 2.02 = 123762.376238 + assert!(liquidity_mining::shares(user_reward_manager) == 123_762, 5); + + assert!(obligation.deposited_value_usd() == decimal::from(1000), 0); + assert!(obligation.allowed_borrow_value_usd() == decimal::from(100), 1); + assert!(obligation.unhealthy_borrow_value_usd() == decimal::from(500), 2); + assert!(obligation.unweighted_borrowed_value_usd() == decimal::from_percent(25), 3); + assert!(obligation.weighted_borrowed_value_usd() == decimal::from_percent(50), 4); + 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); + clock::destroy_for_testing(clock); + sui::test_utils::destroy(obligation); + test_scenario::end(scenario); + } + + #[test] + public fun test_repay_happy_2() { + use sui::test_scenario::{Self}; + use sui::clock::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + 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 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); + + clock::set_for_testing(&mut clock, 1000); + reserve::compound_interest(&mut usdc_reserve, &clock); + + let repay_amount = repay(&mut obligation, &mut usdc_reserve, &clock, decimal::from(500_000)); + assert!(repay_amount == decimal::from(500_000), 0); + + assert!(obligation.deposits().length() == 1, 0); + + let sui_deposit = &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 = &obligation.user_reward_managers()[sui_deposit.user_reward_manager_index()]; + assert!(liquidity_mining::shares(user_reward_manager) == 100 * 1_000_000_000, 5); + + assert!(obligation.borrows().length() == 1, 0); + + // borrow was compounded by 1% so there should be borrows outstanding + let usdc_borrow = &obligation.borrows()[0]; + assert!(usdc_borrow.borrowed_amount() == decimal::from(101 * 1_000_000 - 500_000), 1); + assert!(usdc_borrow.cumulative_borrow_rate() == decimal::from_percent(202), 2); + assert!(usdc_borrow.market_value() == decimal::from_percent_u64(10_050), 3); + + let user_reward_manager = &obligation.user_reward_managers()[usdc_borrow.user_reward_manager_index()]; + // (101 * 1e6 - 500_000) / 2.02 == 49752475.2475 + assert!(liquidity_mining::shares(user_reward_manager) == 49752475, 5); + + assert!(obligation.deposited_value_usd() == decimal::from(1000), 0); + assert!(obligation.allowed_borrow_value_usd() == decimal::from(200), 1); + assert!(obligation.unhealthy_borrow_value_usd() == decimal::from(500), 2); + assert!(obligation.unweighted_borrowed_value_usd() == decimal::from_percent_u64(10_050), 3); + 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); + clock::destroy_for_testing(clock); + sui::test_utils::destroy(obligation); + test_scenario::end(scenario); + } + + #[test] + public fun test_repay_regression() { + use sui::test_scenario::{Self}; + use sui::clock::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + 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 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); + + clock::set_for_testing(&mut clock, 1000); + reserve::update_price_for_testing( + &mut usdc_reserve, + &clock, + decimal::from(10), + decimal::from(10) + ); + + reserve::compound_interest(&mut usdc_reserve, &clock); + let repay_amount = repay( + &mut obligation, + &mut usdc_reserve, + &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); + clock::destroy_for_testing(clock); + sui::test_utils::destroy(obligation); + test_scenario::end(scenario); + } + + #[test] + public fun test_repay_max() { + use sui::test_scenario::{Self}; + use sui::clock::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + 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 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); + + + let repay_amount = repay( + &mut obligation, + &mut usdc_reserve, + &clock, + decimal::from(101 * 1_000_000) + ); + assert!(repay_amount == decimal::from(100 * 1_000_000), 0); + + assert!(obligation.deposits().length() == 1, 0); + + let sui_deposit = &obligation.deposits()[0]; + assert!(sui_deposit.deposited_ctoken_amount() == 100 * 1_000_000_000, 3); + assert!(sui_deposit.market_value() == decimal::from(1000), 4); + + 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) + ); + let user_reward_manager = &obligation.user_reward_managers()[user_reward_manager_index]; + assert!(liquidity_mining::shares(user_reward_manager) == 0, 0); + + assert!(obligation.deposited_value_usd() == decimal::from(1000), 0); + assert!(obligation.allowed_borrow_value_usd() == decimal::from(200), 1); + assert!(obligation.unhealthy_borrow_value_usd() == decimal::from(500), 2); + assert!(obligation.unweighted_borrowed_value_usd() == decimal::from_percent_u64(0), 3); + 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); + clock::destroy_for_testing(clock); + sui::test_utils::destroy(obligation); + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = 0, location = reserve)] // price stale + public fun test_refresh_fail_deposit_price_stale() { + use sui::test_scenario::{Self}; + use sui::clock::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + clock::set_for_testing(&mut clock, 0); + + 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, + get_reserve_mut(&mut reserves), + &clock, + 100 * 1_000_000 + ); + + clock::set_for_testing(&mut clock, 1000); + + refresh( + &mut obligation, + &mut reserves, + &clock + ); + + sui::test_utils::destroy(reserves); + sui::test_utils::destroy(lending_market_id); + clock::destroy_for_testing(clock); + sui::test_utils::destroy(obligation); + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = 0, location = reserve)] // price stale + public fun test_refresh_fail_borrow_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); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + clock::set_for_testing(&mut clock, 0); + + 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, + get_reserve_mut(&mut reserves), + &clock, + 100 * 1_000_000_000 + ); + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 100 * 1_000_000 + ); + + clock::set_for_testing(&mut clock, 1000); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(10), + decimal::from(10) + ); + + refresh( + &mut obligation, + &mut reserves, + &clock + ); + + test_utils::destroy(reserves); + sui::test_utils::destroy(lending_market_id); + clock::destroy_for_testing(clock); + sui::test_utils::destroy(obligation); + test_scenario::end(scenario); + } + + #[test] + public fun test_refresh_happy() { + use sui::test_scenario::{Self}; + use sui::clock::{Self}; + use sui::test_utils::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + clock::set_for_testing(&mut clock, 0); + + 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, + get_reserve_mut(&mut reserves), + &clock, + 100 * 1_000_000_000 + ); + deposit( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 100 * 1_000_000 + ); + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 100 * 1_000_000 + ); + + clock::set_for_testing(&mut clock, 1000); + reserve::update_price_for_testing( + get_reserve_mut(&mut reserves), + &clock, + decimal::from(10), + decimal::from(9) + ); + 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) + ); + + refresh( + &mut obligation, + &mut reserves, + &clock + ); + + assert!(obligation.deposits().length() == 2, 0); + + let sui_deposit = &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 usdc_deposit = &obligation.deposits()[1]; + assert!(usdc_deposit.deposited_ctoken_amount() == 100 * 1_000_000, 3); + assert!(usdc_deposit.market_value() == decimal::from(100), 4); + + assert!(obligation.borrows().length() == 1, 0); + + let usdt_borrow = &obligation.borrows()[0]; + assert!(usdt_borrow.borrowed_amount() == decimal::from(101 * 1_000_000), 1); + assert!(usdt_borrow.cumulative_borrow_rate() == decimal::from_percent(202), 2); + assert!(usdt_borrow.market_value() == decimal::from(101), 3); + + assert!(obligation.deposited_value_usd() == decimal::from(1100), 0); + assert!(obligation.allowed_borrow_value_usd() == decimal::from(230), 1); + assert!(obligation.unhealthy_borrow_value_usd() == decimal::from(580), 2); + assert!(obligation.unweighted_borrowed_value_usd() == decimal::from(101), 3); + assert!(obligation.weighted_borrowed_value_usd() == decimal::from(202), 4); + assert!(obligation.weighted_borrowed_value_upper_bound_usd() == decimal::from(404), 4); + + test_utils::destroy(reserves); + sui::test_utils::destroy(lending_market_id); + clock::destroy_for_testing(clock); + sui::test_utils::destroy(obligation); + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = suilend::obligation::EObligationIsNotLiquidatable)] + public fun test_liquidate_fail_healthy() { + use sui::test_scenario::{Self}; + use sui::clock::{Self}; + use sui::test_utils::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + clock::set_for_testing(&mut clock, 0); + + 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, + get_reserve_mut(&mut reserves), + &clock, + 100 * 1_000_000_000 + ); + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 100 * 1_000_000 + ); + + refresh( + &mut obligation, + &mut reserves, + &clock + ); + liquidate( + &mut obligation, + &mut reserves, + 0, + 1, + &clock, + 100 * 1_000_000_000 + ); + + test_utils::destroy(reserves); + sui::test_utils::destroy(lending_market_id); + clock::destroy_for_testing(clock); + sui::test_utils::destroy(obligation); + test_scenario::end(scenario); + } + + #[test] + public fun test_liquidate_happy_1() { + use sui::test_scenario::{Self}; + use sui::clock::{Self}; + use sui::test_utils::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + clock::set_for_testing(&mut clock, 0); + + 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, + get_reserve_mut(&mut reserves), + &clock, + 100 * 1_000_000_000 + ); + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 50 * 1_000_000 + ); + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 50 * 1_000_000 + ); + + let config = { + let mut builder = reserve_config::from( + reserve::config(get_reserve(&reserves)), + test_scenario::ctx(&mut scenario) + ); + reserve_config::set_open_ltv_pct(&mut builder, 0); + reserve_config::set_close_ltv_pct(&mut builder, 0); + reserve_config::set_liquidation_bonus_bps(&mut builder, 1000); + reserve_config::set_max_liquidation_bonus_bps(&mut builder, 1000); + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }; + reserve::update_reserve_config( + get_reserve_mut(&mut reserves), + config + ); + + refresh( + &mut obligation, + &mut reserves, + &clock + ); + let (withdraw_amount, repay_amount) = liquidate( + &mut obligation, + &mut reserves, + 1, + 0, + &clock, + 100 * 1_000_000_000 + ); + assert!(withdraw_amount == 4_400_000_000, 0); + assert!(repay_amount == decimal::from(40 * 1_000_000), 1); + + assert!(obligation.deposits().length() == 1, 0); + + // $40 was liquidated with a 10% bonus = $44 = 4.4 sui => 95.6 sui remaining + let sui_deposit = &obligation.deposits()[0]; + assert!(sui_deposit.deposited_ctoken_amount() == 95 * 1_000_000_000 + 600_000_000, 3); + assert!(sui_deposit.market_value() == decimal::from(956), 4); + + let user_reward_manager = &obligation.user_reward_managers()[sui_deposit.user_reward_manager_index()]; + assert!(liquidity_mining::shares(user_reward_manager) == 95 * 1_000_000_000 + 600_000_000, 5); + + assert!(obligation.borrows().length() == 2, 0); + + let usdc_borrow = &obligation.borrows()[0]; + assert!(usdc_borrow.borrowed_amount() == decimal::from(10 * 1_000_000), 1); + assert!(usdc_borrow.market_value() == decimal::from(10), 3); + + let user_reward_manager = &obligation.user_reward_managers()[usdc_borrow.user_reward_manager_index()]; + assert!(liquidity_mining::shares(user_reward_manager) == 10 * 1_000_000 / 2, 5); + + let usdt_borrow = &obligation.borrows()[1]; + assert!(usdt_borrow.borrowed_amount() == decimal::from(50 * 1_000_000), 1); + assert!(usdt_borrow.market_value() == decimal::from(50), 3); + + let user_reward_manager = &obligation.user_reward_managers()[usdt_borrow.user_reward_manager_index()]; + assert!(liquidity_mining::shares(user_reward_manager) == 50 * 1_000_000 / 2, 5); + + assert!(obligation.deposited_value_usd() == decimal::from(956), 0); + assert!(obligation.allowed_borrow_value_usd() == decimal::from(0), 1); + assert!(obligation.unhealthy_borrow_value_usd() == decimal::from(0), 2); + assert!(obligation.unweighted_borrowed_value_usd() == decimal::from(60), 3); + assert!(obligation.weighted_borrowed_value_usd() == decimal::from(120), 4); + + test_utils::destroy(reserves); + sui::test_utils::destroy(lending_market_id); + clock::destroy_for_testing(clock); + sui::test_utils::destroy(obligation); + test_scenario::end(scenario); + } + + #[test] + public fun test_liquidate_happy_2() { + use sui::test_scenario::{Self}; + use sui::clock::{Self}; + use sui::test_utils::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + clock::set_for_testing(&mut clock, 0); + + 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, + get_reserve_mut(&mut reserves), + &clock, + 1 * 1_000_000_000 + 100_000_000 + ); + deposit( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 2 * 100_000_000 + ); + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 100 * 1_000_000 + ); + + let eth_reserve = get_reserve_mut(&mut reserves); + let config = { + let mut builder = reserve_config::from( + reserve::config(eth_reserve), + test_scenario::ctx(&mut scenario) + ); + reserve_config::set_open_ltv_pct(&mut builder, 0); + reserve_config::set_close_ltv_pct(&mut builder, 0); + + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }; + reserve::update_reserve_config(eth_reserve, config); + + + let sui_reserve = get_reserve_mut(&mut reserves); + let config = { + let mut builder = reserve_config::from( + reserve::config(sui_reserve), + test_scenario::ctx(&mut scenario) + ); + reserve_config::set_open_ltv_pct(&mut builder, 0); + reserve_config::set_close_ltv_pct(&mut builder, 0); + reserve_config::set_liquidation_bonus_bps(&mut builder, 1000); + reserve_config::set_max_liquidation_bonus_bps(&mut builder, 1000); + + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }; + reserve::update_reserve_config(sui_reserve, config); + + refresh( + &mut obligation, + &mut reserves, + &clock + ); + + let (withdraw_amount, repay_amount) = liquidate( + &mut obligation, + &mut reserves, + 1, + 0, + &clock, + 100 * 1_000_000_000 + ); + assert!(withdraw_amount == 1_100_000_000, 0); + assert!(repay_amount == decimal::from(10 * 1_000_000), 1); + + assert!(obligation.deposits().length() == 1, 0); + + 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]; + assert!(liquidity_mining::shares(user_reward_manager) == 0, 5); + + let eth_deposit = &obligation.deposits()[0]; + assert!(eth_deposit.deposited_ctoken_amount() == 2 * 100_000_000, 3); + assert!(eth_deposit.market_value() == decimal::from(4000), 4); + + let user_reward_manager = &obligation.user_reward_managers()[eth_deposit.user_reward_manager_index()]; + assert!(liquidity_mining::shares(user_reward_manager) == 2 * 100_000_000, 5); + + assert!(obligation.borrows().length() == 1, 0); + + let usdc_borrow = &obligation.borrows()[0]; + assert!(usdc_borrow.borrowed_amount() == decimal::from(90 * 1_000_000), 1); + assert!(usdc_borrow.market_value() == decimal::from(90), 3); + + let user_reward_manager = &obligation.user_reward_managers()[usdc_borrow.user_reward_manager_index()]; + assert!(liquidity_mining::shares(user_reward_manager) == 90 * 1_000_000 / 2, 5); + + assert!(obligation.deposited_value_usd() == decimal::from(4000), 4000); + assert!(obligation.allowed_borrow_value_usd() == decimal::from(0), 0); + assert!(obligation.unhealthy_borrow_value_usd() == decimal::from(0), 2); + assert!(obligation.unweighted_borrowed_value_usd() == decimal::from(90), 3); + assert!(obligation.weighted_borrowed_value_usd() == decimal::from(180), 4); + + test_utils::destroy(reserves); + sui::test_utils::destroy(lending_market_id); + clock::destroy_for_testing(clock); + sui::test_utils::destroy(obligation); + test_scenario::end(scenario); + } + + #[test] + public fun test_liquidate_full_1() { + use sui::test_scenario::{Self}; + use sui::clock::{Self}; + use sui::test_utils::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + clock::set_for_testing(&mut clock, 0); + + 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, + get_reserve_mut(&mut reserves), + &clock, + 100 * 1_000_000_000 + ); + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1 * 1_000_000 + ); + + let config = { + let mut builder = reserve_config::from( + reserve::config(get_reserve(&reserves)), + test_scenario::ctx(&mut scenario) + ); + reserve_config::set_open_ltv_pct(&mut builder, 0); + reserve_config::set_close_ltv_pct(&mut builder, 0); + reserve_config::set_liquidation_bonus_bps(&mut builder, 1000); + reserve_config::set_max_liquidation_bonus_bps(&mut builder, 1000); + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }; + reserve::update_reserve_config( + get_reserve_mut(&mut reserves), + config + ); + + refresh( + &mut obligation, + &mut reserves, + &clock + ); + let (withdraw_amount, repay_amount) = liquidate( + &mut obligation, + &mut reserves, + 1, + 0, + &clock, + 1_000_000_000 + ); + assert!(withdraw_amount == 110_000_000, 0); + assert!(repay_amount == decimal::from(1_000_000), 1); + + assert!(obligation.deposits().length() == 1, 0); + + // $1 was liquidated with a 10% bonus = $1.1 => 0.11 sui => 99.89 sui remaining + let sui_deposit = obligation.find_deposit(get_reserve(&reserves)); + assert!(sui_deposit.deposited_ctoken_amount() == 99 * 1_000_000_000 + 890_000_000, 3); + assert!(sui_deposit.market_value() == add(decimal::from(998), decimal::from_percent(90)), 4); + + let user_reward_manager = &obligation.user_reward_managers()[sui_deposit.user_reward_manager_index()]; + assert!(liquidity_mining::shares(user_reward_manager) == 99 * 1_000_000_000 + 890_000_000, 5); + + assert!(obligation.borrows().length() == 0, 0); + + 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]; + assert!(liquidity_mining::shares(user_reward_manager) == 0, 5); + + assert!(obligation.deposited_value_usd() == add(decimal::from(998), decimal::from_percent(90)), 0); + assert!(obligation.allowed_borrow_value_usd() == decimal::from(0), 1); + assert!(obligation.unhealthy_borrow_value_usd() == decimal::from(0), 2); + assert!(obligation.unweighted_borrowed_value_usd() == decimal::from(0), 3); + assert!(obligation.weighted_borrowed_value_usd() == decimal::from(0), 4); + + test_utils::destroy(reserves); + sui::test_utils::destroy(lending_market_id); + clock::destroy_for_testing(clock); + sui::test_utils::destroy(obligation); + test_scenario::end(scenario); + } + + #[test] + public fun test_liquidate_full_2() { + use sui::test_scenario::{Self}; + use sui::clock::{Self}; + use sui::test_utils::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + clock::set_for_testing(&mut clock, 0); + + 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, + get_reserve_mut(&mut reserves), + &clock, + 10 * 1_000_000_000 + ); + deposit( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 550_000 + ); + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 10 * 1_000_000 + ); + + let usdc_reserve = get_reserve_mut(&mut reserves); + let config = { + let mut builder = reserve_config::from( + reserve::config(usdc_reserve), + test_scenario::ctx(&mut scenario) + ); + reserve_config::set_open_ltv_pct(&mut builder, 0); + reserve_config::set_close_ltv_pct(&mut builder, 0); + reserve_config::set_liquidation_bonus_bps(&mut builder, 1000); + reserve_config::set_max_liquidation_bonus_bps(&mut builder, 1000); + reserve_config::set_protocol_liquidation_fee_bps(&mut builder, 0); + + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }; + reserve::update_reserve_config(usdc_reserve, config); + + + let sui_reserve = get_reserve_mut(&mut reserves); + let config = { + let mut builder = reserve_config::from( + reserve::config(sui_reserve), + test_scenario::ctx(&mut scenario) + ); + reserve_config::set_open_ltv_pct(&mut builder, 0); + reserve_config::set_close_ltv_pct(&mut builder, 0); + reserve_config::set_liquidation_bonus_bps(&mut builder, 1000); + reserve_config::set_max_liquidation_bonus_bps(&mut builder, 1000); + + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }; + reserve::update_reserve_config(sui_reserve, config); + + refresh( + &mut obligation, + &mut reserves, + &clock + ); + + let (withdraw_amount, repay_amount) = liquidate( + &mut obligation, + &mut reserves, + 2, + 1, + &clock, + 100 * 1_000_000_000 + ); + assert!(withdraw_amount == 550_000, 0); + assert!(repay_amount == decimal::from(500_000), 1); + + assert!(obligation.deposits().length() == 1, 0); + + // unchanged + let sui_deposit = obligation.find_deposit(get_reserve(&reserves)); + assert!(sui_deposit.deposited_ctoken_amount() == 10_000_000_000, 3); + assert!(sui_deposit.market_value() == decimal::from(100), 4); + + let user_reward_manager = &obligation.user_reward_managers()[sui_deposit.user_reward_manager_index()]; + assert!(liquidity_mining::shares(user_reward_manager) == 10_000_000_000, 5); + + 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]; + assert!(liquidity_mining::shares(user_reward_manager) == 0, 5); + + assert!(obligation.borrows().length() == 1, 0); + + let usdt_borrow = obligation.find_borrow(get_reserve(&reserves)); + assert!(usdt_borrow.borrowed_amount() == decimal::from(9_500_000), 1); + assert!(usdt_borrow.market_value() == decimal::from_percent_u64(950), 3); + + let user_reward_manager = &obligation.user_reward_managers()[usdt_borrow.user_reward_manager_index()]; + assert!(liquidity_mining::shares(user_reward_manager) == 9_500_000 / 2, 5); + + assert!(obligation.deposited_value_usd() == decimal::from(100), 4000); + assert!(obligation.allowed_borrow_value_usd() == decimal::from(0), 0); + assert!(obligation.unhealthy_borrow_value_usd() == decimal::from(0), 2); + assert!(obligation.unweighted_borrowed_value_usd() == decimal::from_percent_u64(950), 3); + assert!(obligation.weighted_borrowed_value_usd() == decimal::from(19), 4); + + test_utils::destroy(reserves); + sui::test_utils::destroy(lending_market_id); + clock::destroy_for_testing(clock); + sui::test_utils::destroy(obligation); + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = suilend::obligation::EObligationIsNotForgivable)] + fun test_forgive_debt_fail() { + use sui::test_scenario::{Self}; + use sui::clock::{Self}; + use sui::test_utils::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + clock::set_for_testing(&mut clock, 0); + + 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, + get_reserve_mut(&mut reserves), + &clock, + 10 * 1_000_000_000 + ); + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1_000_000 + ); + + forgive( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1_000_000_000) + ); + + test_utils::destroy(reserves); + sui::test_utils::destroy(lending_market_id); + clock::destroy_for_testing(clock); + sui::test_utils::destroy(obligation); + test_scenario::end(scenario); + } + + #[test] + fun test_is_looped() { + use sui::test_scenario::{Self}; + use sui::clock::{Self}; + use sui::test_utils::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + clock::set_for_testing(&mut clock, 0); + + 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, + get_reserve_mut(&mut reserves), + &clock, + 100 * 1_000_000 + ); + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1_000_000_000 + ); + + assert!(!obligation.is_looped(), 0); + + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1_000_000 + ); + + assert!(obligation.is_looped(), 0); + + repay( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1_000_000) + ); + + assert!(!obligation.is_looped(), 0); + + vector::push_back(obligation.borrows_mut(), create_borrow_for_testing( + type_name::get(), + 2, + decimal::from(1_000_000), + decimal::from_percent(100), + decimal::from(1), + 0, + )); + + assert!(obligation.is_looped(), 0); + + test_utils::destroy(reserves); + sui::test_utils::destroy(lending_market_id); + clock::destroy_for_testing(clock); + sui::test_utils::destroy(obligation); + test_scenario::end(scenario); + } + + #[test] + fun test_is_looped_2() { + use sui::test_scenario::{Self}; + use sui::clock::{Self}; + use sui::test_utils::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + clock::set_for_testing(&mut clock, 0); + + let mut reserves = reserves(&mut scenario); + + // Check USDC + { + let mut obligation = create_obligation( + object::uid_to_inner(&lending_market_id), + test_scenario::ctx(&mut scenario) + ); + + deposit( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 100 * 1_000_000 + ); + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1_000_000_000 + ); + + assert!(!obligation.is_looped(), 0); + + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1_000 + ); + + assert!(!obligation.is_looped(), 0); + + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1_000_000 + ); + + assert!(obligation.is_looped(), 0); + + repay( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1_000_000) + ); + + assert!(!obligation.is_looped(), 0); + + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1_000_000 + ); + + assert!(obligation.is_looped(), 0); + + repay( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1_000_000) + ); + + assert!(!obligation.is_looped(), 0); + + vector::push_back(obligation.borrows_mut(), create_borrow_for_testing( + type_name::get(), + 1, + decimal::from(1_000_000), + decimal::from_percent(100), + decimal::from(1), + 0, + )); + + assert!(obligation.is_looped(), 0); + sui::test_utils::destroy(obligation); + }; + + // Check USDT + { + let mut obligation = create_obligation( + object::uid_to_inner(&lending_market_id), + test_scenario::ctx(&mut scenario) + ); + + deposit( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 100 * 1_000_000 + ); + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1_000_000_000 + ); + + assert!(!obligation.is_looped(), 0); + + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1_000 + ); + + assert!(!obligation.is_looped(), 0); + + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1_000_000 + ); + + assert!(obligation.is_looped(), 0); + + repay( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1_000_000) + ); + + assert!(!obligation.is_looped(), 0); + + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1_000_000 + ); + + assert!(obligation.is_looped(), 0); + + repay( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1_000_000) + ); + + assert!(!obligation.is_looped(), 0); + + vector::push_back(obligation.borrows_mut(), create_borrow_for_testing( + type_name::get(), + 2, + decimal::from(1_000_000), + decimal::from_percent(100), + decimal::from(1), + 0, + )); + + assert!(obligation.is_looped(), 0); + sui::test_utils::destroy(obligation); + }; + + // Check AUSD + { + let mut obligation = create_obligation( + object::uid_to_inner(&lending_market_id), + test_scenario::ctx(&mut scenario) + ); + + deposit( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 100 * 1_000_000 + ); + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1_000_000_000 + ); + + assert!(!obligation.is_looped(), 0); + + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1_000 + ); + + assert!(!obligation.is_looped(), 0); + + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1_000_000 + ); + + assert!(obligation.is_looped(), 0); + + repay( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1_000_000) + ); + + assert!(!obligation.is_looped(), 0); + + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1_000_000 + ); + + assert!(obligation.is_looped(), 0); + + repay( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1_000_000) + ); + + assert!(!obligation.is_looped(), 0); + + vector::push_back(obligation.borrows_mut(), create_borrow_for_testing( + type_name::get(), + 5, + decimal::from(1_000_000), + decimal::from_percent(100), + decimal::from(1), + 0, + )); + + assert!(obligation.is_looped(), 0); + sui::test_utils::destroy(obligation); + }; + + // Check SUI + { + let mut obligation = create_obligation( + object::uid_to_inner(&lending_market_id), + test_scenario::ctx(&mut scenario) + ); + + deposit( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 100 * 1_000_000 + ); + + vector::push_back(obligation.borrows_mut(), create_borrow_for_testing( + type_name::get(), + 0, + decimal::from(1_000_000), + decimal::from_percent(100), + decimal::from(1), + 0, + )); + + // print(&obligation.borrows); + + assert!(obligation.is_looped(), 9); + + sui::test_utils::destroy(vector::pop_back(obligation.borrows_mut())); + + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1_000 + ); + + assert!(!obligation.is_looped(), 0); + + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1_000 + ); + + assert!(!obligation.is_looped(), 0); + + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1_000 + ); + + assert!(!obligation.is_looped(), 0); + + repay( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + decimal::from(1_000) + ); + + assert!(!obligation.is_looped(), 0); + + sui::test_utils::destroy(obligation); + }; + + test_utils::destroy(reserves); + sui::test_utils::destroy(lending_market_id); + clock::destroy_for_testing(clock); + test_scenario::end(scenario); + } + + #[test] + fun test_zero_out_rewards_if_looped() { + use sui::test_scenario::{Self}; + use sui::clock::{Self}; + use sui::test_utils::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let lending_market_id = object::new(test_scenario::ctx(&mut scenario)); + let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + clock::set_for_testing(&mut clock, 0); + + 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, + get_reserve_mut(&mut reserves), + &clock, + 100 * 1_000_000 + ); + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1_000_000_000 + ); + + // 1. shouldn't do anything + obligation.zero_out_rewards_if_looped(&mut reserves, &clock); + + let mut i = 0; + while (i < vector::length(obligation.user_reward_managers())) { + let user_reward_manager = vector::borrow(obligation.user_reward_managers(), i); + assert!(liquidity_mining::shares(user_reward_manager) != 0, 0); + i = i + 1; + }; + + // actually loop + borrow( + &mut obligation, + get_reserve_mut(&mut reserves), + &clock, + 1_000_000 + ); + + obligation.zero_out_rewards_if_looped(&mut reserves, &clock); + + let mut i = 0; + while (i < vector::length(obligation.user_reward_managers())) { + let user_reward_manager = vector::borrow(obligation.user_reward_managers(), i); + assert!(liquidity_mining::shares(user_reward_manager) == 0, 0); + i = i + 1; + }; + + test_utils::destroy(reserves); + sui::test_utils::destroy(lending_market_id); + clock::destroy_for_testing(clock); + sui::test_utils::destroy(obligation); + test_scenario::end(scenario); + } +} diff --git a/contracts/suilend/tests/reserve_tests.move b/contracts/suilend/tests/reserve_tests.move new file mode 100644 index 0000000..4dd1225 --- /dev/null +++ b/contracts/suilend/tests/reserve_tests.move @@ -0,0 +1,497 @@ +module suilend::reserve_tests { + // === Imports === + use sui::balance::{Self, Balance, Supply}; + use suilend::decimal::{Decimal, Self, add, sub, mul, div, eq, floor, pow, le, ceil, min, max, saturating_sub}; + use pyth::price_identifier::{PriceIdentifier}; + use suilend::reserve_config::{ + Self, + ReserveConfig, + calculate_apr, + calculate_supply_apr, + deposit_limit, + deposit_limit_usd, + borrow_limit, + borrow_limit_usd, + borrow_fee, + protocol_liquidation_fee, + spread_fee, + liquidation_bonus + }; + use suilend::reserve::{ + Self, + create_for_testing, + deposit_liquidity_and_mint_ctokens, + redeem_ctokens, + borrow_liquidity, + claim_fees, + compound_interest, + repay_liquidity, + Balances + }; + use sui::clock::{Self}; + use suilend::liquidity_mining::{Self, PoolRewardManager}; + + use sui::test_scenario::{Self}; + + #[test_only] + public struct TEST_LM {} + + #[test] + fun test_deposit_happy() { + use suilend::test_usdc::{TEST_USDC}; + use sui::test_scenario::{Self}; + use suilend::reserve_config::{default_reserve_config}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + + + let mut reserve = create_for_testing( + default_reserve_config(), + 0, + 6, + decimal::from(1), + 0, + 500, + 200, + decimal::from(500), + decimal::from(1), + 1, + test_scenario::ctx(&mut scenario) + ); + + let ctokens = deposit_liquidity_and_mint_ctokens( + &mut reserve, + balance::create_for_testing(1000) + ); + + assert!(balance::value(&ctokens) == 200, 0); + assert!(reserve.available_amount() == 1500, 0); + assert!(reserve.ctoken_supply() == 400, 0); + + let balances: &Balances = reserve::balances(&reserve); + + assert!(balance::value(balances.available_amount()) == 1500, 0); + assert!(balance::supply_value(balances.ctoken_supply()) == 400, 0); + + sui::test_utils::destroy(reserve); + sui::test_utils::destroy(ctokens); + + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = suilend::reserve::EDepositLimitExceeded)] + fun test_deposit_fail() { + use suilend::test_usdc::{TEST_USDC}; + use sui::test_scenario::{Self}; + use suilend::reserve_config::{default_reserve_config}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + + let mut reserve = create_for_testing( + { + let config = default_reserve_config(); + let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); + reserve_config::set_deposit_limit(&mut builder, 1000); + sui::test_utils::destroy(config); + + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }, + 0, + 6, + decimal::from(1), + 0, + 500, + 200, + decimal::from(500), + decimal::from(1), + 1, + test_scenario::ctx(&mut scenario) + ); + + let coins = balance::create_for_testing(1); + let ctokens = deposit_liquidity_and_mint_ctokens(&mut reserve, coins); + + sui::test_utils::destroy(reserve); + sui::test_utils::destroy(ctokens); + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = suilend::reserve::EDepositLimitExceeded)] + fun test_deposit_fail_usd_limit() { + use suilend::test_usdc::{TEST_USDC}; + use sui::test_scenario::{Self}; + use suilend::reserve_config::{default_reserve_config}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + + let mut reserve = create_for_testing( + { + let config = default_reserve_config(); + let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); + reserve_config::set_deposit_limit(&mut builder, 18_446_744_073_709_551_615); + reserve_config::set_deposit_limit_usd(&mut builder, 1); + sui::test_utils::destroy(config); + + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }, + 0, + 6, + decimal::from(1), + 0, + 500_000, + 1_000_000, + decimal::from(500_000), + decimal::from(1), + 1, + test_scenario::ctx(&mut scenario) + ); + + let coins = balance::create_for_testing(1); + let ctokens = deposit_liquidity_and_mint_ctokens(&mut reserve, coins); + + sui::test_utils::destroy(reserve); + sui::test_utils::destroy(ctokens); + test_scenario::end(scenario); + } + + #[test] + fun test_redeem_happy() { + use suilend::test_usdc::{TEST_USDC}; + use sui::test_scenario::{Self}; + use suilend::reserve_config::{default_reserve_config}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + + let mut reserve = create_for_testing( + default_reserve_config(), + 0, + 6, + decimal::from(1), + 0, + 500, + 200, + decimal::from(500), + decimal::from(1), + 1, + test_scenario::ctx(&mut scenario) + ); + + let available_amount_old = reserve.available_amount(); + let ctoken_supply_old = reserve.ctoken_supply(); + + let ctokens = balance::create_for_testing(10); + let tokens = redeem_ctokens(&mut reserve, ctokens); + + assert!(balance::value(&tokens) == 50, 0); + assert!(reserve.available_amount() == available_amount_old - 50, 0); + assert!(reserve.ctoken_supply() == ctoken_supply_old - 10, 0); + + let balances: &Balances = reserve::balances(&reserve); + + assert!(balance::value(balances.available_amount()) == available_amount_old - 50, 0); + assert!(balance::supply_value(balances.ctoken_supply()) == ctoken_supply_old - 10, 0); + + sui::test_utils::destroy(reserve); + sui::test_utils::destroy(tokens); + + test_scenario::end(scenario); + } + + #[test] + fun test_borrow_happy() { + use suilend::test_usdc::{TEST_USDC}; + use sui::test_scenario::{Self}; + use suilend::reserve_config::{default_reserve_config}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + + let mut reserve = create_for_testing( + { + let config = default_reserve_config(); + let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); + reserve_config::set_borrow_fee_bps(&mut builder, 100); + sui::test_utils::destroy(config); + + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }, + 0, + 6, + decimal::from(1), + 0, + 0, + 0, + decimal::from(0), + decimal::from(1), + 1, + test_scenario::ctx(&mut scenario) + ); + + let ctokens = deposit_liquidity_and_mint_ctokens( + &mut reserve, + balance::create_for_testing(1000) + ); + + let available_amount_old = reserve.available_amount(); + let borrowed_amount_old = reserve.borrowed_amount(); + + let (tokens, borrowed_amount_with_fee) = borrow_liquidity(&mut reserve, 400); + assert!(balance::value(&tokens) == 400, 0); + assert!(borrowed_amount_with_fee == 404, 0); + + assert!(reserve.available_amount() == available_amount_old - 404, 0); + assert!(reserve.borrowed_amount() == add(borrowed_amount_old, decimal::from(404)), 0); + + let balances: &Balances = reserve::balances(&reserve); + + assert!(balance::value(balances.available_amount()) == available_amount_old - 404, 0); + assert!(balance::value(balances.fees()) == 4, 0); + + let (ctoken_fees, fees) = claim_fees(&mut reserve); + assert!(balance::value(&fees) == 4, 0); + assert!(balance::value(&ctoken_fees) == 0, 0); + + sui::test_utils::destroy(fees); + sui::test_utils::destroy(ctoken_fees); + sui::test_utils::destroy(reserve); + sui::test_utils::destroy(tokens); + sui::test_utils::destroy(ctokens); + + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = suilend::reserve::EBorrowLimitExceeded)] + fun test_borrow_fail() { + use suilend::test_usdc::{TEST_USDC}; + use sui::test_scenario::{Self}; + use suilend::reserve_config::{default_reserve_config}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + + let mut reserve = create_for_testing( + { + let config = default_reserve_config(); + let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); + reserve_config::set_borrow_limit(&mut builder, 0); + sui::test_utils::destroy(config); + + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }, + 0, + 6, + decimal::from(1), + 0, + 0, + 0, + decimal::from(0), + decimal::from(1), + 1, + test_scenario::ctx(&mut scenario) + ); + + let ctokens = deposit_liquidity_and_mint_ctokens( + &mut reserve, + balance::create_for_testing(1000) + ); + + let (tokens, _) = borrow_liquidity(&mut reserve, 1); + + sui::test_utils::destroy(reserve); + sui::test_utils::destroy(tokens); + sui::test_utils::destroy(ctokens); + + test_scenario::end(scenario); + } + + #[test] + #[expected_failure(abort_code = suilend::reserve::EBorrowLimitExceeded)] + fun test_borrow_fail_usd_limit() { + use suilend::test_usdc::{TEST_USDC}; + use sui::test_scenario::{Self}; + use suilend::reserve_config::{default_reserve_config}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + + let mut reserve = create_for_testing( + { + let config = default_reserve_config(); + let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); + reserve_config::set_borrow_limit_usd(&mut builder, 1); + sui::test_utils::destroy(config); + + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }, + 0, + 6, + decimal::from(1), + 0, + 0, + 0, + decimal::from(0), + decimal::from(1), + 1, + test_scenario::ctx(&mut scenario) + ); + + let ctokens = deposit_liquidity_and_mint_ctokens( + &mut reserve, + balance::create_for_testing(10_000_000) + ); + + let (tokens, _) = borrow_liquidity(&mut reserve, 1_000_000 + 1); + + sui::test_utils::destroy(reserve); + sui::test_utils::destroy(tokens); + sui::test_utils::destroy(ctokens); + + test_scenario::end(scenario); + } + + + #[test] + fun test_claim_fees() { + use suilend::test_usdc::{TEST_USDC}; + use sui::test_scenario::{Self}; + use suilend::reserve_config::{default_reserve_config}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario)); + + let mut reserve = create_for_testing( + { + let config = default_reserve_config(); + let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); + reserve_config::set_deposit_limit(&mut builder, 1000 * 1_000_000); + reserve_config::set_borrow_limit(&mut builder, 1000 * 1_000_000); + reserve_config::set_borrow_fee_bps(&mut builder, 0); + reserve_config::set_spread_fee_bps(&mut builder, 5000); + reserve_config::set_interest_rate_utils(&mut builder, { + let mut v = vector::empty(); + vector::push_back(&mut v, 0); + vector::push_back(&mut v, 100); + v + }); + reserve_config::set_interest_rate_aprs(&mut builder, { + let mut v = vector::empty(); + vector::push_back(&mut v, 0); + vector::push_back(&mut v, 3153600000); + v + }); + + sui::test_utils::destroy(config); + + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }, + 0, + 6, + decimal::from(1), + 0, + 0, + 0, + decimal::from(0), + decimal::from(1), + 0, + test_scenario::ctx(&mut scenario) + ); + + let ctokens = deposit_liquidity_and_mint_ctokens( + &mut reserve, + balance::create_for_testing(100 * 1_000_000) + ); + + let (tokens, _) = borrow_liquidity(&mut reserve, 50 * 1_000_000); + + clock::set_for_testing(&mut clock, 1000); + compound_interest(&mut reserve, &clock); + + let old_available_amount = reserve.available_amount(); + let old_unclaimed_spread_fees = reserve.unclaimed_spread_fees(); + + let (ctoken_fees, fees) = claim_fees(&mut reserve); + + // 0.5% interest a second with 50% take rate => 0.25% fee on 50 USDC = 0.125 USDC + assert!(balance::value(&fees) == 125_000, 0); + assert!(balance::value(&ctoken_fees) == 0, 0); + + assert!(reserve.available_amount() == old_available_amount - 125_000, 0); + assert!(reserve.unclaimed_spread_fees() == sub(old_unclaimed_spread_fees, decimal::from(125_000)), 0); + + let balances: &Balances = reserve::balances(&reserve); + assert!(balance::value(balances.available_amount()) == old_available_amount - 125_000, 0); + + sui::test_utils::destroy(clock); + sui::test_utils::destroy(ctoken_fees); + sui::test_utils::destroy(fees); + sui::test_utils::destroy(reserve); + sui::test_utils::destroy(tokens); + sui::test_utils::destroy(ctokens); + + test_scenario::end(scenario); + } + + #[test] + fun test_repay_happy() { + use suilend::test_usdc::{TEST_USDC}; + use sui::test_scenario::{Self}; + use suilend::reserve_config::{default_reserve_config}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + + let mut reserve = create_for_testing( + { + let config = default_reserve_config(); + let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario)); + reserve_config::set_borrow_fee_bps(&mut builder, 100); + sui::test_utils::destroy(config); + + reserve_config::build(builder, test_scenario::ctx(&mut scenario)) + }, + 0, + 6, + decimal::from(1), + 0, + 0, + 0, + decimal::from(0), + decimal::from(1), + 1, + test_scenario::ctx(&mut scenario) + ); + + let ctokens = deposit_liquidity_and_mint_ctokens( + &mut reserve, + balance::create_for_testing(1000) + ); + + let (tokens, _) = borrow_liquidity(&mut reserve, 400); + + let available_amount_old = reserve.available_amount(); + let borrowed_amount_old = reserve.borrowed_amount(); + + repay_liquidity(&mut reserve, tokens, decimal::from_percent_u64(39_901)); + + assert!(reserve.available_amount() == available_amount_old + 400, 0); + assert!(reserve.borrowed_amount() == sub(borrowed_amount_old, decimal::from_percent_u64(39_901)), 0); + + let balances: &Balances = reserve::balances(&reserve); + assert!(balance::value(balances.available_amount()) == available_amount_old + 400, 0); + + sui::test_utils::destroy(reserve); + sui::test_utils::destroy(ctokens); + + test_scenario::end(scenario); + } + + +} \ No newline at end of file