From 65b11927f53ff52b59438d8ccf8d5b6c8ed63675 Mon Sep 17 00:00:00 2001 From: 0xripleys <0xripleys@solend.fi> Date: Sun, 3 Nov 2024 16:35:01 -0500 Subject: [PATCH 01/18] stake unused sui (WIP) --- contracts/suilend/Move.lock | 23 +++- contracts/suilend/Move.toml | 5 + contracts/suilend/sources/staker.move | 156 ++++++++++++++++++++++ contracts/suilend/tests/staker_tests.move | 75 +++++++++++ 4 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 contracts/suilend/sources/staker.move create mode 100644 contracts/suilend/tests/staker_tests.move diff --git a/contracts/suilend/Move.lock b/contracts/suilend/Move.lock index d397ba8..e4085b3 100644 --- a/contracts/suilend/Move.lock +++ b/contracts/suilend/Move.lock @@ -2,11 +2,12 @@ [move] version = 3 -manifest_digest = "97E92C3AE2671D15B98EDF2F75D00F01F060C660AA87BCA5FB95A6792D62C242" -deps_digest = "3C4103934B1E040BB6B23F1D610B4EF9F2F1166A50A104EADCF77467C004C600" +manifest_digest = "35F92FCDECB247F47BD4304AA29525DA91F7CF4EBCB8777527B825341BB52706" +deps_digest = "060AD7E57DFB13104F21BE5F5C3759D03F0553FC3229247D9A7A6B45F50D03A3" dependencies = [ { id = "Pyth", name = "Pyth" }, { id = "Sui", name = "Sui" }, + { id = "liquid_staking", name = "liquid_staking" }, ] [[move.package]] @@ -30,6 +31,15 @@ dependencies = [ { id = "MoveStdlib", name = "MoveStdlib" }, ] +[[move.package]] +id = "SuiSystem" +source = { git = "https://github.com/MystenLabs/sui.git", rev = "framework/mainnet", subdir = "crates/sui-framework/packages/sui-system" } + +dependencies = [ + { id = "MoveStdlib", name = "MoveStdlib" }, + { id = "Sui", name = "Sui" }, +] + [[move.package]] id = "Wormhole" source = { git = "https://github.com/solendprotocol/wormhole.git", rev = "e1698d3c72b15cdddd7da98ad43e151f83b72a0a", subdir = "sui/wormhole" } @@ -38,6 +48,15 @@ dependencies = [ { id = "Sui", name = "Sui" }, ] +[[move.package]] +id = "liquid_staking" +source = { git = "https://github.com/solendprotocol/liquid-staking.git", rev = "main", subdir = "contracts" } + +dependencies = [ + { id = "Sui", name = "Sui" }, + { id = "SuiSystem", name = "SuiSystem" }, +] + [move.toolchain-version] compiler-version = "1.35.3" edition = "2024.beta" diff --git a/contracts/suilend/Move.toml b/contracts/suilend/Move.toml index c083578..13a1d3a 100644 --- a/contracts/suilend/Move.toml +++ b/contracts/suilend/Move.toml @@ -15,6 +15,11 @@ git = "https://github.com/solendprotocol/pyth-crosschain.git" subdir = "target_chains/sui/contracts" rev = "98e218c64bb75cf1350eb7b021e1ffcc3aedfd62" +[dependencies.liquid_staking] +git = "https://github.com/solendprotocol/liquid-staking.git" +subdir = "contracts" +rev = "main" + [addresses] sui = "0x2" # suilend = "0x0" diff --git a/contracts/suilend/sources/staker.move b/contracts/suilend/sources/staker.move new file mode 100644 index 0000000..ee8d423 --- /dev/null +++ b/contracts/suilend/sources/staker.move @@ -0,0 +1,156 @@ +/// Stake unlent Sui. +module suilend::staker { + use liquid_staking::liquid_staking::{LiquidStakingInfo, AdminCap, Self, total_sui_supply, total_lst_supply}; + use liquid_staking::fees::{Self}; + use sui::balance::{Self, Balance}; + use sui::tx_context::{TxContext}; + use sui::coin::{Self, TreasuryCap}; + use sui_system::sui_system::{SuiSystemState}; + use sui::sui::SUI; + + // errors + const ETreasuryCapNonZeroSupply: u64 = 0; + const EInvariantViolation: u64 = 1; + + // constants + const U64_MAX: u64 = 18446744073709551615; + const SUILEND_VALIDATOR: address = @0xce8e537664ba5d1d5a6a857b17bd142097138706281882be6805e17065ecde89; + + // how much of the sui should be staked. Eventually this number will trend to 100%, + // but we start at 50% for safety reasons. + const TARGET_UTIL_BPS: u64 = 5000; // 50% + + public struct Staker has store { + admin: AdminCap

, + liquid_staking_info: LiquidStakingInfo

, + lst_balance: Balance

, + sui_balance: Balance, + liabilities: u64, // how much sui is owed to the reserve + } + + /* Public-View Functions */ + public(package) fun liabilities

(staker: &Staker

): u64 { + staker.liabilities + } + + public(package) fun lst_balance

(staker: &Staker

): &Balance

{ + &staker.lst_balance + } + + /* Public Mutative Functions */ + public(package) fun create_staker( + treasury_cap: TreasuryCap

, + ctx: &mut TxContext + ): Staker

{ + assert!(coin::total_supply(&treasury_cap) == 0, ETreasuryCapNonZeroSupply); + + let (admin_cap, liquid_staking_info) = liquid_staking::create_lst( + fees::new_builder(ctx).to_fee_config(), + treasury_cap, + ctx + ); + + Staker { + admin: admin_cap, + liquid_staking_info, + lst_balance: balance::zero(), + sui_balance: balance::zero(), + liabilities: 0, + } + } + + public(package) fun deposit( + staker: &mut Staker

, + sui: Balance, + ) { + staker.liabilities = staker.liabilities + sui.value(); + staker.sui_balance.join(sui); + } + + public(package) fun stake( + staker: &mut Staker

, + system_state: &mut SuiSystemState, + sui: Balance, + ctx: &mut TxContext + ) { + staker.liabilities = staker.liabilities + balance::value(&sui); + + let lst = staker.liquid_staking_info.mint( + system_state, + coin::from_balance(sui, ctx), + ctx + ); + + staker.liquid_staking_info.increase_validator_stake( + &staker.admin, + system_state, + SUILEND_VALIDATOR, + U64_MAX, + ctx + ); + + staker.lst_balance.join(lst.into_balance()); + assert!(staker.liquid_staking_info.total_sui_supply() >= staker.liabilities, EInvariantViolation); + } + + // unstake sui. this function can return less than the requested amount due to rounding + public(package) fun unstake( + staker: &mut Staker

, + system_state: &mut SuiSystemState, + unstake_amount: u64, + ctx: &mut TxContext + ): Balance { + staker.liquid_staking_info.refresh(system_state, ctx); + + let sui = staker.withdraw_n_sui(system_state, unstake_amount, ctx); + + staker.liabilities = staker.liabilities - unstake_amount; + + assert!(staker.liquid_staking_info.total_sui_supply() >= staker.liabilities, EInvariantViolation); + + sui + } + + public(package) fun claim_fees( + staker: &mut Staker

, + system_state: &mut SuiSystemState, + ctx: &mut TxContext + ): Balance { + liquid_staking::refresh(&mut staker.liquid_staking_info, system_state, ctx); + let total_sui_supply = total_sui_supply(&staker.liquid_staking_info); + let excess_sui = total_sui_supply - staker.liabilities; + + let sui = withdraw_n_sui(staker, system_state, excess_sui, ctx); + + assert!(total_sui_supply(&staker.liquid_staking_info) >= staker.liabilities, EInvariantViolation); + + sui + } + + /* Private Functions */ + + // liquid_staking_info must be refreshed before calling this + fun withdraw_n_sui( + staker: &mut Staker

, + system_state: &mut SuiSystemState, + sui_amount_out: u64, + ctx: &mut TxContext + ): Balance { + let total_sui_supply = (total_sui_supply(&staker.liquid_staking_info) as u128); + let total_lst_supply = (total_lst_supply(&staker.liquid_staking_info) as u128); + + let lst_to_redeem = (sui_amount_out as u128) * total_lst_supply / total_sui_supply; + let lst = balance::split(&mut staker.lst_balance, (lst_to_redeem as u64)); + + let sui = liquid_staking::redeem( + &mut staker.liquid_staking_info, + coin::from_balance(lst, ctx), + system_state, + ctx + ); + + assert!(coin::value(&sui) <= sui_amount_out, EInvariantViolation); + + coin::into_balance(sui) + } +} diff --git a/contracts/suilend/tests/staker_tests.move b/contracts/suilend/tests/staker_tests.move new file mode 100644 index 0000000..7ec4f78 --- /dev/null +++ b/contracts/suilend/tests/staker_tests.move @@ -0,0 +1,75 @@ +module suilend::staker_tests { + + public struct STAKER_TESTS has drop {} + + use sui::test_scenario::{Self, Scenario}; + use sui_system::governance_test_utils::{ + advance_epoch_with_reward_amounts, + create_validator_for_testing, + create_sui_system_state_for_testing, + }; + use sui::balance::{Self}; + use sui::coin::{Self}; + use suilend::staker::{create_staker}; + use sui_system::sui_system::{SuiSystemState}; + use sui::sui::{SUI}; + + /* Constants */ + const MIST_PER_SUI: u64 = 1_000_000_000; + const SUILEND_VALIDATOR: address = @0xce8e537664ba5d1d5a6a857b17bd142097138706281882be6805e17065ecde89; + + fun setup_sui_system(scenario: &mut Scenario) { + test_scenario::next_tx(scenario, SUILEND_VALIDATOR); + let validator = create_validator_for_testing(SUILEND_VALIDATOR, 100, test_scenario::ctx(scenario)); + create_sui_system_state_for_testing(vector[validator], 0, 0, test_scenario::ctx(scenario)); + + advance_epoch_with_reward_amounts(0, 0, scenario); + } + + #[test] + public fun test_end_to_end_happy() { + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + setup_sui_system(&mut scenario); + + let treasury_cap = coin::create_treasury_cap_for_testing(test_scenario::ctx(&mut scenario)); + + let mut staker = create_staker(treasury_cap, test_scenario::ctx(&mut scenario)); + // TODO: check more stuff + assert!(staker.liabilities() == 0, 0); + + let mut system_state = test_scenario::take_shared(&scenario); + + let sui = balance::create_for_testing(100 * MIST_PER_SUI); + staker.stake(&mut system_state, sui, test_scenario::ctx(&mut scenario)); + + assert!(staker.liabilities() == 100 * MIST_PER_SUI, 0); + assert!(staker.lst_balance().value() == 100 * MIST_PER_SUI, 0); + + test_scenario::return_shared(system_state); + advance_epoch_with_reward_amounts(0, 0, &mut scenario); + // 1 lst is worth 2 sui now + advance_epoch_with_reward_amounts(0, 200, &mut scenario); // 100 SUI + + let mut system_state = test_scenario::take_shared(&scenario); + let sui = staker.unstake(&mut system_state, 100 * MIST_PER_SUI, test_scenario::ctx(&mut scenario)); + + std::debug::print(&staker); + + assert!(staker.liabilities() == 0, 0); + assert!(staker.lst_balance().value() == 50 * MIST_PER_SUI, 0); + assert!(sui.value() == 100 * MIST_PER_SUI, 0); + + let fees = staker.claim_fees(&mut system_state, test_scenario::ctx(&mut scenario)); + assert!(fees.value() == 100 * MIST_PER_SUI, 0); + + std::debug::print(&staker); + + sui::test_utils::destroy(sui); + sui::test_utils::destroy(fees); + sui::test_utils::destroy(staker); + test_scenario::return_shared(system_state); + test_scenario::end(scenario); + } + +} \ No newline at end of file From 37194fd317f52625c387318296dd9e01af103989 Mon Sep 17 00:00:00 2001 From: 0xripleys <0xripleys@solend.fi> Date: Tue, 5 Nov 2024 16:52:18 -0500 Subject: [PATCH 02/18] refactor borrow, redeem so that they expose the LiquidityRequest type --- contracts/suilend/sources/lending_market.move | 80 +++++++++++++++---- contracts/suilend/sources/reserve.move | 54 ++++++++++--- contracts/suilend/tests/reserve_tests.move | 27 ++++--- 3 files changed, 126 insertions(+), 35 deletions(-) diff --git a/contracts/suilend/sources/lending_market.move b/contracts/suilend/sources/lending_market.move index 875ec74..d207a08 100644 --- a/contracts/suilend/sources/lending_market.move +++ b/contracts/suilend/sources/lending_market.move @@ -10,7 +10,7 @@ module suilend::lending_market { use sui::clock::{Self, Clock}; use sui::tx_context::{Self, TxContext}; use sui::transfer; - use suilend::reserve::{Self, Reserve, CToken}; + use suilend::reserve::{Self, Reserve, CToken, LiquidityRequest}; use suilend::reserve_config::{ReserveConfig, borrow_fee}; use suilend::obligation::{Self, Obligation}; use sui::coin::{Self, Coin, CoinMetadata}; @@ -21,6 +21,7 @@ module suilend::lending_market { use std::option::{Self, Option}; use suilend::liquidity_mining::{Self}; use sui::package; + use sui::sui::SUI; // === Errors === const EIncorrectVersion: u64 = 1; @@ -258,6 +259,25 @@ module suilend::lending_market { mut rate_limiter_exemption: Option>, ctx: &mut TxContext ): Coin { + let liquidity_request = redeem_ctokens_and_withdraw_liquidity_request( + lending_market, + reserve_array_index, + clock, + ctokens, + rate_limiter_exemption, + ctx + ); + fulfill_liquidity_request(lending_market, reserve_array_index, liquidity_request, ctx) + } + + public fun redeem_ctokens_and_withdraw_liquidity_request( + lending_market: &mut LendingMarket

, + reserve_array_index: u64, + clock: &Clock, + ctokens: Coin>, + mut rate_limiter_exemption: Option>, + ctx: &mut TxContext + ): LiquidityRequest { let lending_market_id = object::id_address(lending_market); assert!(lending_market.version == CURRENT_VERSION, EIncorrectVersion); assert!(coin::value(&ctokens) > 0, ETooSmall); @@ -285,22 +305,22 @@ module suilend::lending_market { ); }; - let liquidity = reserve::redeem_ctokens( + let liquidity_request = reserve::redeem_ctokens( reserve, coin::into_balance(ctokens) ); - assert!(balance::value(&liquidity) > 0, ETooSmall); + assert!(reserve::liquidity_request_amount(&liquidity_request) > 0, ETooSmall); event::emit(RedeemEvent { lending_market_id, coin_type: type_name::get(), reserve_id: object::id_address(reserve), ctoken_amount, - liquidity_amount: balance::value(&liquidity), + liquidity_amount: reserve::liquidity_request_amount(&liquidity_request), }); - coin::from_balance(liquidity, ctx) + liquidity_request } @@ -323,7 +343,6 @@ module suilend::lending_market { ) } - /// Borrow tokens of type T. A fee is charged. public fun borrow( lending_market: &mut LendingMarket

, @@ -333,6 +352,19 @@ module suilend::lending_market { mut amount: u64, ctx: &mut TxContext ): Coin { + let liquidity_request = borrow_request(lending_market, reserve_array_index, obligation_owner_cap, clock, amount, ctx); + fulfill_liquidity_request(lending_market, reserve_array_index, liquidity_request, ctx) + } + + /// Borrow tokens of type T. A fee is charged. + public fun borrow_request( + lending_market: &mut LendingMarket

, + reserve_array_index: u64, + obligation_owner_cap: &ObligationOwnerCap

, + clock: &Clock, + mut amount: u64, + ctx: &mut TxContext + ): LiquidityRequest { let lending_market_id = object::id_address(lending_market); assert!(lending_market.version == CURRENT_VERSION, EIncorrectVersion); assert!(amount > 0, ETooSmall); @@ -354,11 +386,15 @@ module suilend::lending_market { assert!(amount > 0, ETooSmall); }; - let (receive_balance, borrow_amount_with_fees) = reserve::borrow_liquidity(reserve, amount); - let origination_fee_amount = borrow_amount_with_fees - balance::value(&receive_balance); - obligation::borrow

(obligation, reserve, clock, borrow_amount_with_fees); + let liquidity_request = reserve::borrow_liquidity(reserve, amount); + obligation::borrow

( + obligation, + reserve, + clock, + reserve::liquidity_request_amount(&liquidity_request) + ); - let borrow_value = reserve::market_value_upper_bound(reserve, decimal::from(borrow_amount_with_fees)); + let borrow_value = reserve::market_value_upper_bound(reserve, decimal::from(reserve::liquidity_request_amount(&liquidity_request))); rate_limiter::process_qty( &mut lending_market.rate_limiter, clock::timestamp_ms(clock) / 1000, @@ -370,12 +406,28 @@ module suilend::lending_market { coin_type: type_name::get(), reserve_id: object::id_address(reserve), obligation_id: object::id_address(obligation), - liquidity_amount: borrow_amount_with_fees, - origination_fee_amount, + liquidity_amount: reserve::liquidity_request_amount(&liquidity_request), + origination_fee_amount: reserve::liquidity_request_fee(&liquidity_request), }); - obligation::zero_out_rewards_if_looped(obligation, &mut lending_market.reserves, clock); - coin::from_balance(receive_balance, ctx) + liquidity_request + } + + public fun fulfill_liquidity_request( + lending_market: &mut LendingMarket

, + reserve_array_index: u64, + liquidity_request: LiquidityRequest, + ctx: &mut TxContext + ): Coin { + assert!(lending_market.version == CURRENT_VERSION, EIncorrectVersion); + + let reserve = vector::borrow_mut(&mut lending_market.reserves, reserve_array_index); + assert!(reserve::coin_type(reserve) == type_name::get(), EWrongType); + + coin::from_balance( + reserve::fulfill_liquidity_request(reserve, liquidity_request), + ctx + ) } public fun withdraw_ctokens( diff --git a/contracts/suilend/sources/reserve.move b/contracts/suilend/sources/reserve.move index 929e043..27f3523 100644 --- a/contracts/suilend/sources/reserve.move +++ b/contracts/suilend/sources/reserve.move @@ -85,6 +85,13 @@ module suilend::reserve { /// the underlying token + any interest earned. public struct CToken has drop {} + /// A request to withdraw liquidity from the reserve. This is a hot potato object. + public struct LiquidityRequest { + amount: u64, // includes fee + fee: u64, + } + + // === Dynamic Field Keys === public struct BalanceKey has copy, drop, store {} @@ -472,6 +479,13 @@ module suilend::reserve { &balances.ctoken_fees } + public fun liquidity_request_amount(request: &LiquidityRequest): u64 { + request.amount + } + public fun liquidity_request_fee(request: &LiquidityRequest): u64 { + request.fee + } + // === Public-Mutative Functions public(package) fun deposits_pool_reward_manager_mut

(reserve: &mut Reserve

): &mut PoolRewardManager { &mut reserve.deposits_pool_reward_manager @@ -656,7 +670,7 @@ module suilend::reserve { public(package) fun redeem_ctokens( reserve: &mut Reserve

, ctokens: Balance> - ): Balance { + ): LiquidityRequest { let ctoken_ratio = ctoken_ratio(reserve); let liquidity_amount = floor(mul( decimal::from(balance::value(&ctokens)), @@ -678,14 +692,35 @@ module suilend::reserve { ); balance::decrease_supply(&mut balances.ctoken_supply, ctokens); - balance::split(&mut balances.available_amount, liquidity_amount) + + LiquidityRequest { + amount: liquidity_amount, + fee: 0 + } + } + + public(package) fun fulfill_liquidity_request( + reserve: &mut Reserve

, + request: LiquidityRequest, + ): Balance { + let LiquidityRequest { amount, fee } = request; + + let balances: &mut Balances = dynamic_field::borrow_mut( + &mut reserve.id, + BalanceKey {} + ); + + let mut liquidity = balance::split(&mut balances.available_amount, amount); + balance::join(&mut balances.fees, balance::split(&mut liquidity, fee)); + + liquidity } /// Borrow tokens from the reserve. A fee is charged on the borrowed amount public(package) fun borrow_liquidity( reserve: &mut Reserve

, amount: u64 - ): (Balance, u64) { + ): LiquidityRequest { let borrow_fee = calculate_borrow_fee(reserve, amount); let borrow_amount_with_fees = amount + borrow_fee; @@ -712,16 +747,11 @@ module suilend::reserve { ); log_reserve_data(reserve); - let balances: &mut Balances = dynamic_field::borrow_mut( - &mut reserve.id, - BalanceKey {} - ); - - let mut receive_balance = balance::split(&mut balances.available_amount, borrow_amount_with_fees); - let fee_balance = balance::split(&mut receive_balance, borrow_fee); - balance::join(&mut balances.fees, fee_balance); - (receive_balance, borrow_amount_with_fees) + LiquidityRequest { + amount: borrow_amount_with_fees, + fee: borrow_fee + } } public(package) fun repay_liquidity( diff --git a/contracts/suilend/tests/reserve_tests.move b/contracts/suilend/tests/reserve_tests.move index 4dd1225..af39bfd 100644 --- a/contracts/suilend/tests/reserve_tests.move +++ b/contracts/suilend/tests/reserve_tests.move @@ -186,7 +186,11 @@ module suilend::reserve_tests { let ctoken_supply_old = reserve.ctoken_supply(); let ctokens = balance::create_for_testing(10); - let tokens = redeem_ctokens(&mut reserve, ctokens); + let liquidity_request = redeem_ctokens(&mut reserve, ctokens); + assert!(reserve::liquidity_request_amount(&liquidity_request) == 50, 0); + assert!(reserve::liquidity_request_fee(&liquidity_request) == 0, 0); + + let tokens = reserve::fulfill_liquidity_request(&mut reserve, liquidity_request); assert!(balance::value(&tokens) == 50, 0); assert!(reserve.available_amount() == available_amount_old - 50, 0); @@ -241,9 +245,12 @@ module suilend::reserve_tests { 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); + let liquidity_request = borrow_liquidity(&mut reserve, 400); + assert!(reserve::liquidity_request_amount(&liquidity_request) == 404, 0); + assert!(reserve::liquidity_request_fee(&liquidity_request) == 4, 0); + + let tokens = reserve::fulfill_liquidity_request(&mut reserve, liquidity_request); 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); @@ -302,10 +309,10 @@ module suilend::reserve_tests { balance::create_for_testing(1000) ); - let (tokens, _) = borrow_liquidity(&mut reserve, 1); + let liquidity_request = borrow_liquidity(&mut reserve, 1); + sui::test_utils::destroy(liquidity_request); sui::test_utils::destroy(reserve); - sui::test_utils::destroy(tokens); sui::test_utils::destroy(ctokens); test_scenario::end(scenario); @@ -347,10 +354,10 @@ module suilend::reserve_tests { balance::create_for_testing(10_000_000) ); - let (tokens, _) = borrow_liquidity(&mut reserve, 1_000_000 + 1); + let liquidity_request = borrow_liquidity(&mut reserve, 1_000_000 + 1); + sui::test_utils::destroy(liquidity_request); sui::test_utils::destroy(reserve); - sui::test_utils::destroy(tokens); sui::test_utils::destroy(ctokens); test_scenario::end(scenario); @@ -409,7 +416,8 @@ module suilend::reserve_tests { balance::create_for_testing(100 * 1_000_000) ); - let (tokens, _) = borrow_liquidity(&mut reserve, 50 * 1_000_000); + let liquidity_request = borrow_liquidity(&mut reserve, 50 * 1_000_000); + let tokens = reserve::fulfill_liquidity_request(&mut reserve, liquidity_request); clock::set_for_testing(&mut clock, 1000); compound_interest(&mut reserve, &clock); @@ -474,7 +482,8 @@ module suilend::reserve_tests { balance::create_for_testing(1000) ); - let (tokens, _) = borrow_liquidity(&mut reserve, 400); + let liquidity_request = borrow_liquidity(&mut reserve, 400); + let tokens = reserve::fulfill_liquidity_request(&mut reserve, liquidity_request); let available_amount_old = reserve.available_amount(); let borrowed_amount_old = reserve.borrowed_amount(); From 2b7c6ef3ad4fe05ffd9b523cf4b4c8bc50a9944e Mon Sep 17 00:00:00 2001 From: 0xripleys <0xripleys@solend.fi> Date: Wed, 6 Nov 2024 23:05:47 -0500 Subject: [PATCH 03/18] refactor staker module a little bit --- contracts/suilend/sources/staker.move | 128 ++++++++++++++-------- contracts/suilend/tests/staker_tests.move | 56 +++++++--- 2 files changed, 125 insertions(+), 59 deletions(-) diff --git a/contracts/suilend/sources/staker.move b/contracts/suilend/sources/staker.move index ee8d423..0ab3829 100644 --- a/contracts/suilend/sources/staker.move +++ b/contracts/suilend/sources/staker.move @@ -1,6 +1,6 @@ /// Stake unlent Sui. module suilend::staker { - use liquid_staking::liquid_staking::{LiquidStakingInfo, AdminCap, Self, total_sui_supply, total_lst_supply}; + use liquid_staking::liquid_staking::{LiquidStakingInfo, AdminCap, Self}; use liquid_staking::fees::{Self}; use sui::balance::{Self, Balance}; use sui::tx_context::{TxContext}; @@ -20,6 +20,9 @@ module suilend::staker { // but we start at 50% for safety reasons. const TARGET_UTIL_BPS: u64 = 5000; // 50% + // This is mostly so i don't hit the "zero lst coin mint" error. + const MIN_DEPLOY_AMOUNT: u64 = 1_000_000; // 1 SUI + public struct Staker has store { admin: AdminCap

, liquid_staking_info: LiquidStakingInfo

, @@ -37,6 +40,19 @@ module suilend::staker { &staker.lst_balance } + public(package) fun sui_balance

(staker: &Staker

): &Balance { + &staker.sui_balance + } + + // this value can be stale if the staker hasn't refreshed the liquid_staking_info + public(package) fun total_sui_supply

(staker: &Staker

): u64 { + staker.liquid_staking_info.total_sui_supply() + staker.sui_balance.value() + } + + public(package) fun liquid_staking_info

(staker: &Staker

): &LiquidStakingInfo

{ + &staker.liquid_staking_info + } + /* Public Mutative Functions */ public(package) fun create_staker( treasury_cap: TreasuryCap

, @@ -67,48 +83,61 @@ module suilend::staker { staker.sui_balance.join(sui); } - public(package) fun stake( - staker: &mut Staker

, + public(package) fun withdraw( + staker: &mut Staker

, + withdraw_amount: u64, system_state: &mut SuiSystemState, - sui: Balance, ctx: &mut TxContext - ) { - staker.liabilities = staker.liabilities + balance::value(&sui); + ): Balance { + staker.liquid_staking_info.refresh(system_state, ctx); - let lst = staker.liquid_staking_info.mint( - system_state, - coin::from_balance(sui, ctx), - ctx - ); + if (withdraw_amount > staker.sui_balance.value()) { + let unstake_amount = withdraw_amount - staker.sui_balance.value(); + staker.unstake_n_sui(system_state, unstake_amount, ctx); + }; - staker.liquid_staking_info.increase_validator_stake( - &staker.admin, - system_state, - SUILEND_VALIDATOR, - U64_MAX, - ctx - ); + let sui = staker.sui_balance.split(withdraw_amount); + staker.liabilities = staker.liabilities - sui.value(); - staker.lst_balance.join(lst.into_balance()); - assert!(staker.liquid_staking_info.total_sui_supply() >= staker.liabilities, EInvariantViolation); + sui } - // unstake sui. this function can return less than the requested amount due to rounding - public(package) fun unstake( - staker: &mut Staker

, + public(package) fun rebalance( + staker: &mut Staker

, system_state: &mut SuiSystemState, - unstake_amount: u64, ctx: &mut TxContext - ): Balance { + ) { staker.liquid_staking_info.refresh(system_state, ctx); - let sui = staker.withdraw_n_sui(system_state, unstake_amount, ctx); - - staker.liabilities = staker.liabilities - unstake_amount; - - assert!(staker.liquid_staking_info.total_sui_supply() >= staker.liabilities, EInvariantViolation); + let staked_sui = staker.liquid_staking_info.total_sui_supply(); + let target_staked_sui = (staker.total_sui_supply() * TARGET_UTIL_BPS) / 10000; + + if (target_staked_sui >= staked_sui + MIN_DEPLOY_AMOUNT) { + let sui = staker.sui_balance.split(target_staked_sui - staked_sui); + let lst = staker.liquid_staking_info.mint( + system_state, + coin::from_balance(sui, ctx), + ctx + ); + + staker.liquid_staking_info.increase_validator_stake( + &staker.admin, + system_state, + SUILEND_VALIDATOR, + U64_MAX, + ctx + ); + + staker.lst_balance.join(lst.into_balance()); + } + else { + staker.unstake_n_sui(system_state, staked_sui - target_staked_sui, ctx); + }; - sui + assert!( + staker.liquid_staking_info.total_sui_supply() + staker.sui_balance.value() >= staker.liabilities, + EInvariantViolation + ); } public(package) fun claim_fees( @@ -116,13 +145,22 @@ module suilend::staker { system_state: &mut SuiSystemState, ctx: &mut TxContext ): Balance { - liquid_staking::refresh(&mut staker.liquid_staking_info, system_state, ctx); - let total_sui_supply = total_sui_supply(&staker.liquid_staking_info); + staker.liquid_staking_info.refresh(system_state, ctx); + + let total_sui_supply = staker.total_sui_supply(); let excess_sui = total_sui_supply - staker.liabilities; - let sui = withdraw_n_sui(staker, system_state, excess_sui, ctx); + if (excess_sui > staker.sui_balance.value()) { + let unstake_amount = excess_sui - staker.sui_balance.value(); + staker.unstake_n_sui(system_state, unstake_amount, ctx); + }; - assert!(total_sui_supply(&staker.liquid_staking_info) >= staker.liabilities, EInvariantViolation); + let sui = staker.sui_balance.split(excess_sui); + + assert!( + staker.liquid_staking_info.total_sui_supply() + staker.sui_balance.value() >= staker.liabilities, + EInvariantViolation + ); sui } @@ -130,16 +168,22 @@ module suilend::staker { /* Private Functions */ // liquid_staking_info must be refreshed before calling this - fun withdraw_n_sui( + // this function can unstake slightly more sui than requested due to rounding. + fun unstake_n_sui( staker: &mut Staker

, system_state: &mut SuiSystemState, sui_amount_out: u64, ctx: &mut TxContext - ): Balance { - let total_sui_supply = (total_sui_supply(&staker.liquid_staking_info) as u128); - let total_lst_supply = (total_lst_supply(&staker.liquid_staking_info) as u128); + ) { + if (sui_amount_out == 0) { + return; + }; - let lst_to_redeem = (sui_amount_out as u128) * total_lst_supply / total_sui_supply; + let total_sui_supply = (staker.liquid_staking_info.total_sui_supply() as u128); + let total_lst_supply = (staker.liquid_staking_info.total_lst_supply() as u128); + + // ceil lst redemption amount + let lst_to_redeem = ((sui_amount_out as u128) * total_lst_supply + total_sui_supply - 1) / total_sui_supply; let lst = balance::split(&mut staker.lst_balance, (lst_to_redeem as u64)); let sui = liquid_staking::redeem( @@ -149,8 +193,6 @@ module suilend::staker { ctx ); - assert!(coin::value(&sui) <= sui_amount_out, EInvariantViolation); - - coin::into_balance(sui) + staker.sui_balance.join(sui.into_balance()); } } diff --git a/contracts/suilend/tests/staker_tests.move b/contracts/suilend/tests/staker_tests.move index 7ec4f78..2e54fdd 100644 --- a/contracts/suilend/tests/staker_tests.move +++ b/contracts/suilend/tests/staker_tests.move @@ -35,40 +35,64 @@ module suilend::staker_tests { let treasury_cap = coin::create_treasury_cap_for_testing(test_scenario::ctx(&mut scenario)); let mut staker = create_staker(treasury_cap, test_scenario::ctx(&mut scenario)); - // TODO: check more stuff + assert!(staker.sui_balance().value() == 0, 0); + assert!(staker.lst_balance().value() == 0, 0); assert!(staker.liabilities() == 0, 0); let mut system_state = test_scenario::take_shared(&scenario); + staker.rebalance(&mut system_state, scenario.ctx()); let sui = balance::create_for_testing(100 * MIST_PER_SUI); - staker.stake(&mut system_state, sui, test_scenario::ctx(&mut scenario)); + staker.deposit(sui); assert!(staker.liabilities() == 100 * MIST_PER_SUI, 0); - assert!(staker.lst_balance().value() == 100 * MIST_PER_SUI, 0); + assert!(staker.sui_balance().value() == 100 * MIST_PER_SUI, 0); + assert!(staker.lst_balance().value() == 0, 0); + + staker.rebalance(&mut system_state, scenario.ctx()); + + assert!(staker.liabilities() == 100 * MIST_PER_SUI, 0); + assert!(staker.sui_balance().value() == 50 * MIST_PER_SUI, 0); + assert!(staker.lst_balance().value() == 50 * MIST_PER_SUI, 0); test_scenario::return_shared(system_state); + advance_epoch_with_reward_amounts(0, 0, &mut scenario); // 1 lst is worth 2 sui now - advance_epoch_with_reward_amounts(0, 200, &mut scenario); // 100 SUI + advance_epoch_with_reward_amounts(0, 150, &mut scenario); // 100 SUI let mut system_state = test_scenario::take_shared(&scenario); - let sui = staker.unstake(&mut system_state, 100 * MIST_PER_SUI, test_scenario::ctx(&mut scenario)); - - std::debug::print(&staker); - - assert!(staker.liabilities() == 0, 0); - assert!(staker.lst_balance().value() == 50 * MIST_PER_SUI, 0); - assert!(sui.value() == 100 * MIST_PER_SUI, 0); - let fees = staker.claim_fees(&mut system_state, test_scenario::ctx(&mut scenario)); - assert!(fees.value() == 100 * MIST_PER_SUI, 0); + // before rebalance: + // 100 sui staked, 50 sui in sui_balance + // after rebalance: 75, 75 + staker.rebalance(&mut system_state, scenario.ctx()); + assert!(staker.liabilities() == 100 * MIST_PER_SUI, 0); + assert!(staker.sui_balance().value() == 75 * MIST_PER_SUI, 0); + assert!(staker.liquid_staking_info().total_sui_supply() == 75 * MIST_PER_SUI, 0); + assert!(staker.lst_balance().value() == 75 * MIST_PER_SUI / 2, 0); + assert!(staker.total_sui_supply() == 150 * MIST_PER_SUI, 0); - std::debug::print(&staker); + let sui = staker.claim_fees(&mut system_state, scenario.ctx()); + assert!(sui.value() == 50 * MIST_PER_SUI, 0); + assert!(staker.liabilities() == 100 * MIST_PER_SUI, 0); + assert!(staker.sui_balance().value() == 25 * MIST_PER_SUI, 0); + assert!(staker.liquid_staking_info().total_sui_supply() == 75 * MIST_PER_SUI, 0); + assert!(staker.lst_balance().value() == 75 * MIST_PER_SUI / 2, 0); + assert!(staker.total_sui_supply() == 100 * MIST_PER_SUI, 0); + sui::test_utils::destroy(sui); + let sui = staker.withdraw(100 * MIST_PER_SUI, &mut system_state, scenario.ctx()); + assert!(sui.value() == 100 * MIST_PER_SUI, 0); + assert!(staker.liabilities() == 0, 0); + assert!(staker.sui_balance().value() == 0, 0); + assert!(staker.liquid_staking_info().total_sui_supply() == 0, 0); + assert!(staker.lst_balance().value() == 0, 0); + assert!(staker.total_sui_supply() == 0, 0); sui::test_utils::destroy(sui); - sui::test_utils::destroy(fees); - sui::test_utils::destroy(staker); + test_scenario::return_shared(system_state); + sui::test_utils::destroy(staker); test_scenario::end(scenario); } From d9a01354aa803ca0978ba1319da96db5a01355a4 Mon Sep 17 00:00:00 2001 From: 0xripleys <0xripleys@solend.fi> Date: Thu, 7 Nov 2024 15:42:01 -0500 Subject: [PATCH 04/18] remove the target util stuff --- contracts/suilend/sources/staker.move | 46 ++++++++--------------- contracts/suilend/tests/staker_tests.move | 42 ++++++++++----------- 2 files changed, 37 insertions(+), 51 deletions(-) diff --git a/contracts/suilend/sources/staker.move b/contracts/suilend/sources/staker.move index 0ab3829..e8112de 100644 --- a/contracts/suilend/sources/staker.move +++ b/contracts/suilend/sources/staker.move @@ -16,10 +16,6 @@ module suilend::staker { const U64_MAX: u64 = 18446744073709551615; const SUILEND_VALIDATOR: address = @0xce8e537664ba5d1d5a6a857b17bd142097138706281882be6805e17065ecde89; - // how much of the sui should be staked. Eventually this number will trend to 100%, - // but we start at 50% for safety reasons. - const TARGET_UTIL_BPS: u64 = 5000; // 50% - // This is mostly so i don't hit the "zero lst coin mint" error. const MIN_DEPLOY_AMOUNT: u64 = 1_000_000; // 1 SUI @@ -109,34 +105,24 @@ module suilend::staker { ) { staker.liquid_staking_info.refresh(system_state, ctx); - let staked_sui = staker.liquid_staking_info.total_sui_supply(); - let target_staked_sui = (staker.total_sui_supply() * TARGET_UTIL_BPS) / 10000; - - if (target_staked_sui >= staked_sui + MIN_DEPLOY_AMOUNT) { - let sui = staker.sui_balance.split(target_staked_sui - staked_sui); - let lst = staker.liquid_staking_info.mint( - system_state, - coin::from_balance(sui, ctx), - ctx - ); - - staker.liquid_staking_info.increase_validator_stake( - &staker.admin, - system_state, - SUILEND_VALIDATOR, - U64_MAX, - ctx - ); - - staker.lst_balance.join(lst.into_balance()); - } - else { - staker.unstake_n_sui(system_state, staked_sui - target_staked_sui, ctx); + if (staker.sui_balance.value() < MIN_DEPLOY_AMOUNT) { + return }; - assert!( - staker.liquid_staking_info.total_sui_supply() + staker.sui_balance.value() >= staker.liabilities, - EInvariantViolation + let sui = staker.sui_balance.withdraw_all(); + let lst = staker.liquid_staking_info.mint( + system_state, + coin::from_balance(sui, ctx), + ctx + ); + staker.lst_balance.join(lst.into_balance()); + + staker.liquid_staking_info.increase_validator_stake( + &staker.admin, + system_state, + SUILEND_VALIDATOR, + U64_MAX, + ctx ); } diff --git a/contracts/suilend/tests/staker_tests.move b/contracts/suilend/tests/staker_tests.move index 2e54fdd..d1dd505 100644 --- a/contracts/suilend/tests/staker_tests.move +++ b/contracts/suilend/tests/staker_tests.move @@ -52,43 +52,43 @@ module suilend::staker_tests { staker.rebalance(&mut system_state, scenario.ctx()); assert!(staker.liabilities() == 100 * MIST_PER_SUI, 0); - assert!(staker.sui_balance().value() == 50 * MIST_PER_SUI, 0); - assert!(staker.lst_balance().value() == 50 * MIST_PER_SUI, 0); + assert!(staker.sui_balance().value() == 0, 0); + assert!(staker.lst_balance().value() == 100 * MIST_PER_SUI, 0); + assert!(staker.total_sui_supply() == 100 * MIST_PER_SUI, 0); + assert!(staker.liquid_staking_info().total_sui_supply() == 100 * MIST_PER_SUI, 0); + test_scenario::return_shared(system_state); advance_epoch_with_reward_amounts(0, 0, &mut scenario); // 1 lst is worth 2 sui now - advance_epoch_with_reward_amounts(0, 150, &mut scenario); // 100 SUI + advance_epoch_with_reward_amounts(0, 200, &mut scenario); // 100 SUI let mut system_state = test_scenario::take_shared(&scenario); - // before rebalance: - // 100 sui staked, 50 sui in sui_balance - // after rebalance: 75, 75 staker.rebalance(&mut system_state, scenario.ctx()); assert!(staker.liabilities() == 100 * MIST_PER_SUI, 0); - assert!(staker.sui_balance().value() == 75 * MIST_PER_SUI, 0); - assert!(staker.liquid_staking_info().total_sui_supply() == 75 * MIST_PER_SUI, 0); - assert!(staker.lst_balance().value() == 75 * MIST_PER_SUI / 2, 0); - assert!(staker.total_sui_supply() == 150 * MIST_PER_SUI, 0); + assert!(staker.sui_balance().value() == 0, 0); + assert!(staker.liquid_staking_info().total_sui_supply() == 200 * MIST_PER_SUI, 0); + assert!(staker.lst_balance().value() == 100 * MIST_PER_SUI, 0); + assert!(staker.total_sui_supply() == 200 * MIST_PER_SUI, 0); let sui = staker.claim_fees(&mut system_state, scenario.ctx()); - assert!(sui.value() == 50 * MIST_PER_SUI, 0); + assert!(sui.value() == 100 * MIST_PER_SUI, 0); assert!(staker.liabilities() == 100 * MIST_PER_SUI, 0); - assert!(staker.sui_balance().value() == 25 * MIST_PER_SUI, 0); - assert!(staker.liquid_staking_info().total_sui_supply() == 75 * MIST_PER_SUI, 0); - assert!(staker.lst_balance().value() == 75 * MIST_PER_SUI / 2, 0); + assert!(staker.sui_balance().value() == 0, 0); + assert!(staker.liquid_staking_info().total_sui_supply() == 100 * MIST_PER_SUI, 0); + assert!(staker.lst_balance().value() == 50 * MIST_PER_SUI, 0); assert!(staker.total_sui_supply() == 100 * MIST_PER_SUI, 0); sui::test_utils::destroy(sui); - let sui = staker.withdraw(100 * MIST_PER_SUI, &mut system_state, scenario.ctx()); - assert!(sui.value() == 100 * MIST_PER_SUI, 0); - assert!(staker.liabilities() == 0, 0); - assert!(staker.sui_balance().value() == 0, 0); - assert!(staker.liquid_staking_info().total_sui_supply() == 0, 0); - assert!(staker.lst_balance().value() == 0, 0); - assert!(staker.total_sui_supply() == 0, 0); + let sui = staker.withdraw(MIST_PER_SUI + 1, &mut system_state, scenario.ctx()); + assert!(sui.value() == MIST_PER_SUI + 1, 0); + assert!(staker.liabilities() == 99 * MIST_PER_SUI - 1, 0); + assert!(staker.sui_balance().value() == 1, 0); + assert!(staker.liquid_staking_info().total_sui_supply() == 99 * MIST_PER_SUI - 2, 0); + assert!(staker.lst_balance().value() == 49 * MIST_PER_SUI + 500_000_000 - 1, 0); + assert!(staker.total_sui_supply() == 99 * MIST_PER_SUI - 1, 0); sui::test_utils::destroy(sui); test_scenario::return_shared(system_state); From 8620329a05b20bded87f517aafe0e94b64d117a4 Mon Sep 17 00:00:00 2001 From: 0xripleys <0xripleys@solend.fi> Date: Thu, 7 Nov 2024 17:15:09 -0500 Subject: [PATCH 05/18] adding borrow_sui, redeem_sui functions --- contracts/suilend/sources/lending_market.move | 114 ++++++++++++++---- contracts/suilend/sources/reserve.move | 27 +++++ contracts/suilend/sources/staker.move | 2 + 3 files changed, 118 insertions(+), 25 deletions(-) diff --git a/contracts/suilend/sources/lending_market.move b/contracts/suilend/sources/lending_market.move index d207a08..75df9fc 100644 --- a/contracts/suilend/sources/lending_market.move +++ b/contracts/suilend/sources/lending_market.move @@ -1,6 +1,7 @@ module suilend::lending_market { // === Imports === use sui::object::{Self, ID, UID}; + use sui_system::sui_system::{SuiSystemState}; use suilend::rate_limiter::{Self, RateLimiter, RateLimiterConfig}; use std::ascii::{Self}; use sui::event::{Self}; @@ -251,12 +252,45 @@ module suilend::lending_market { coin::from_balance(ctokens, ctx) } + public fun redeem_ctokens_and_withdraw_liquidity_sui( + lending_market: &mut LendingMarket

, + system_state: &mut SuiSystemState, + reserve_array_index: u64, + clock: &Clock, + ctokens: Coin>, + rate_limiter_exemption: Option>, + ctx: &mut TxContext + ): Coin { + let liquidity_request = redeem_ctokens_and_withdraw_liquidity_request( + lending_market, + reserve_array_index, + clock, + ctokens, + rate_limiter_exemption, + ctx + ); + + let reserve = vector::borrow_mut(&mut lending_market.reserves, reserve_array_index); + assert!(reserve::coin_type(reserve) == type_name::get(), EWrongType); + + coin::from_balance( + reserve::unstake_sui_and_fulfill_liquidity_request( + reserve, + liquidity_request, + system_state, + ctx + ), + ctx + ) + } + + public fun redeem_ctokens_and_withdraw_liquidity( lending_market: &mut LendingMarket

, reserve_array_index: u64, clock: &Clock, ctokens: Coin>, - mut rate_limiter_exemption: Option>, + rate_limiter_exemption: Option>, ctx: &mut TxContext ): Coin { let liquidity_request = redeem_ctokens_and_withdraw_liquidity_request( @@ -267,7 +301,14 @@ module suilend::lending_market { rate_limiter_exemption, ctx ); - fulfill_liquidity_request(lending_market, reserve_array_index, liquidity_request, ctx) + + let reserve = vector::borrow_mut(&mut lending_market.reserves, reserve_array_index); + assert!(reserve::coin_type(reserve) == type_name::get(), EWrongType); + + coin::from_balance( + reserve::fulfill_liquidity_request(reserve, liquidity_request), + ctx + ) } public fun redeem_ctokens_and_withdraw_liquidity_request( @@ -323,7 +364,6 @@ module suilend::lending_market { liquidity_request } - public fun deposit_ctokens_into_obligation( lending_market: &mut LendingMarket

, reserve_array_index: u64, @@ -343,27 +383,68 @@ module suilend::lending_market { ) } + /// Borrow sui. + public fun borrow_sui( + lending_market: &mut LendingMarket

, + system_state: &mut SuiSystemState, + reserve_array_index: u64, + obligation_owner_cap: &ObligationOwnerCap

, + clock: &Clock, + amount: u64, + ctx: &mut TxContext + ): Coin { + let liquidity_request = borrow_request( + lending_market, + reserve_array_index, + obligation_owner_cap, + clock, + amount + ); + + let mut reserve = vector::borrow_mut(&mut lending_market.reserves, reserve_array_index); + let sui = reserve::unstake_sui_and_fulfill_liquidity_request( + reserve, + liquidity_request, + system_state, + ctx + ); + + coin::from_balance(sui, ctx) + } + /// Borrow tokens of type T. A fee is charged. public fun borrow( lending_market: &mut LendingMarket

, reserve_array_index: u64, obligation_owner_cap: &ObligationOwnerCap

, clock: &Clock, - mut amount: u64, + amount: u64, ctx: &mut TxContext ): Coin { - let liquidity_request = borrow_request(lending_market, reserve_array_index, obligation_owner_cap, clock, amount, ctx); - fulfill_liquidity_request(lending_market, reserve_array_index, liquidity_request, ctx) + let liquidity_request = borrow_request( + lending_market, + reserve_array_index, + obligation_owner_cap, + clock, + amount + ); + + let reserve = vector::borrow_mut(&mut lending_market.reserves, reserve_array_index); + assert!(reserve::coin_type(reserve) == type_name::get(), EWrongType); + + coin::from_balance( + reserve::fulfill_liquidity_request(reserve, liquidity_request), + ctx + ) } /// Borrow tokens of type T. A fee is charged. - public fun borrow_request( + fun borrow_request( lending_market: &mut LendingMarket

, reserve_array_index: u64, obligation_owner_cap: &ObligationOwnerCap

, clock: &Clock, mut amount: u64, - ctx: &mut TxContext ): LiquidityRequest { let lending_market_id = object::id_address(lending_market); assert!(lending_market.version == CURRENT_VERSION, EIncorrectVersion); @@ -413,23 +494,6 @@ module suilend::lending_market { liquidity_request } - public fun fulfill_liquidity_request( - lending_market: &mut LendingMarket

, - reserve_array_index: u64, - liquidity_request: LiquidityRequest, - ctx: &mut TxContext - ): Coin { - assert!(lending_market.version == CURRENT_VERSION, EIncorrectVersion); - - let reserve = vector::borrow_mut(&mut lending_market.reserves, reserve_array_index); - assert!(reserve::coin_type(reserve) == type_name::get(), EWrongType); - - coin::from_balance( - reserve::fulfill_liquidity_request(reserve, liquidity_request), - ctx - ) - } - public fun withdraw_ctokens( lending_market: &mut LendingMarket

, reserve_array_index: u64, diff --git a/contracts/suilend/sources/reserve.move b/contracts/suilend/sources/reserve.move index 27f3523..360a910 100644 --- a/contracts/suilend/sources/reserve.move +++ b/contracts/suilend/sources/reserve.move @@ -1,6 +1,7 @@ /// The reserve module holds the coins of a certain type for a given lending market. module suilend::reserve { // === Imports === + use sui::sui::SUI; use std::type_name::{Self, TypeName}; use sui::dynamic_field::{Self}; use sui::balance::{Self, Balance, Supply}; @@ -31,6 +32,8 @@ module suilend::reserve { liquidation_bonus }; use suilend::liquidity_mining::{Self, PoolRewardManager}; + use suilend::staker::{Self, Staker}; + use sui_system::sui_system::{SuiSystemState}; // === Errors === const EPriceStale: u64 = 0; @@ -40,6 +43,7 @@ module suilend::reserve { const EInvalidPrice: u64 = 4; const EMinAvailableAmountViolated: u64 = 5; const EInvalidRepayBalance: u64 = 6; + const EWrongType: u64 = 7; // === Constants === const PRICE_STALENESS_THRESHOLD_S: u64 = 0; @@ -94,6 +98,7 @@ module suilend::reserve { // === Dynamic Field Keys === public struct BalanceKey has copy, drop, store {} + public struct StakerKey has copy, drop, store {} /// Balances are stored in a dynamic field to avoid typing the Reserve with CoinType public struct Balances has store { @@ -716,6 +721,28 @@ module suilend::reserve { liquidity } + public(package) fun unstake_sui_and_fulfill_liquidity_request( + reserve: &mut Reserve

, + request: LiquidityRequest, + system_state: &mut SuiSystemState, + ctx: &mut TxContext, + ): Balance { + assert!(reserve.coin_type == type_name::get(), EWrongType); + + let LiquidityRequest { amount, fee } = request; + + let staker: &mut Staker = dynamic_field::borrow_mut(&mut reserve.id, StakerKey {}); + let mut liquidity = staker::withdraw(staker, amount, system_state, ctx); + + let balances: &mut Balances = dynamic_field::borrow_mut( + &mut reserve.id, + BalanceKey {} + ); + balance::join(&mut balances.fees, balance::split(&mut liquidity, fee)); + + liquidity + } + /// Borrow tokens from the reserve. A fee is charged on the borrowed amount public(package) fun borrow_liquidity( reserve: &mut Reserve

, diff --git a/contracts/suilend/sources/staker.move b/contracts/suilend/sources/staker.move index e8112de..b54cad6 100644 --- a/contracts/suilend/sources/staker.move +++ b/contracts/suilend/sources/staker.move @@ -7,6 +7,8 @@ module suilend::staker { use sui::coin::{Self, TreasuryCap}; use sui_system::sui_system::{SuiSystemState}; use sui::sui::SUI; + use std::option::{Self, Option}; + use sui::transfer::Self; // errors const ETreasuryCapNonZeroSupply: u64 = 0; From 9b87f409e4b5ad88de004739a4903627fc531cdf Mon Sep 17 00:00:00 2001 From: 0xripleys <0xripleys@solend.fi> Date: Thu, 7 Nov 2024 18:36:02 -0500 Subject: [PATCH 06/18] add staker functions --- contracts/suilend/sources/lending_market.move | 28 +++++++++++++-- contracts/suilend/sources/reserve.move | 36 +++++++++++++++++-- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/contracts/suilend/sources/lending_market.move b/contracts/suilend/sources/lending_market.move index 75df9fc..878683b 100644 --- a/contracts/suilend/sources/lending_market.move +++ b/contracts/suilend/sources/lending_market.move @@ -14,7 +14,7 @@ module suilend::lending_market { use suilend::reserve::{Self, Reserve, CToken, LiquidityRequest}; use suilend::reserve_config::{ReserveConfig, borrow_fee}; use suilend::obligation::{Self, Obligation}; - use sui::coin::{Self, Coin, CoinMetadata}; + use sui::coin::{Self, Coin, CoinMetadata, TreasuryCap}; use sui::balance::{Self}; use pyth::price_info::{PriceInfoObject}; use std::type_name::{Self, TypeName}; @@ -284,13 +284,12 @@ module suilend::lending_market { ) } - public fun redeem_ctokens_and_withdraw_liquidity( lending_market: &mut LendingMarket

, reserve_array_index: u64, clock: &Clock, ctokens: Coin>, - rate_limiter_exemption: Option>, + mut rate_limiter_exemption: Option>, ctx: &mut TxContext ): Coin { let liquidity_request = redeem_ctokens_and_withdraw_liquidity_request( @@ -780,6 +779,29 @@ module suilend::lending_market { } } + public fun init_staker( + lending_market: &mut LendingMarket

, + sui_reserve_array_index: u64, + treasury_cap: TreasuryCap, + ctx: &mut TxContext + ) { + let reserve = vector::borrow_mut(&mut lending_market.reserves, sui_reserve_array_index); + assert!(reserve::coin_type(reserve) == type_name::get(), EWrongType); + + reserve::init_staker(reserve, treasury_cap, ctx); + } + + public fun rebalance_staker( + lending_market: &mut LendingMarket

, + sui_reserve_array_index: u64, + system_state: &mut SuiSystemState, + ctx: &mut TxContext + ) { + let reserve = vector::borrow_mut(&mut lending_market.reserves, sui_reserve_array_index); + assert!(reserve::coin_type(reserve) == type_name::get(), EWrongType); + + reserve::rebalance_staker(reserve, system_state, ctx); + } // === Public-View Functions === fun max_borrow_amount

( diff --git a/contracts/suilend/sources/reserve.move b/contracts/suilend/sources/reserve.move index 360a910..4f78652 100644 --- a/contracts/suilend/sources/reserve.move +++ b/contracts/suilend/sources/reserve.move @@ -13,7 +13,7 @@ module suilend::reserve { use suilend::oracles::{Self}; use suilend::decimal::{Decimal, Self, add, sub, mul, div, eq, floor, pow, le, ceil, min, max, saturating_sub}; use sui::clock::{Self, Clock}; - use sui::coin::{Self, CoinMetadata}; + use sui::coin::{Self, CoinMetadata, TreasuryCap}; use sui::math::{Self}; use pyth::price_identifier::{PriceIdentifier}; use pyth::price_info::{PriceInfoObject}; @@ -44,7 +44,8 @@ module suilend::reserve { const EMinAvailableAmountViolated: u64 = 5; const EInvalidRepayBalance: u64 = 6; const EWrongType: u64 = 7; - + const EStakerAlreadyInitialized: u64 = 8; + const EStakerNotInitialized: u64 = 9; // === Constants === const PRICE_STALENESS_THRESHOLD_S: u64 = 0; // to prevent certain rounding bug attacks, we make sure that X amount of the underlying token amount @@ -95,7 +96,6 @@ module suilend::reserve { fee: u64, } - // === Dynamic Field Keys === public struct BalanceKey has copy, drop, store {} public struct StakerKey has copy, drop, store {} @@ -721,6 +721,35 @@ module suilend::reserve { liquidity } + public(package) fun init_staker( + reserve: &mut Reserve

, + treasury_cap: TreasuryCap, + ctx: &mut TxContext + ) { + assert!(!dynamic_field::exists_(&reserve.id, StakerKey {}), EStakerAlreadyInitialized); + + let staker = staker::create_staker(treasury_cap, ctx); + dynamic_field::add(&mut reserve.id, StakerKey {}, staker); + } + + public(package) fun rebalance_staker( + reserve: &mut Reserve

, + system_state: &mut SuiSystemState, + ctx: &mut TxContext + ) { + assert!(dynamic_field::exists_(&reserve.id, StakerKey {}), EStakerNotInitialized); + let balances: &mut Balances = dynamic_field::borrow_mut( + &mut reserve.id, + BalanceKey {} + ); + let sui = balance::withdraw_all(&mut balances.available_amount); + + let staker: &mut Staker = dynamic_field::borrow_mut(&mut reserve.id, StakerKey {}); + + staker::deposit(staker, sui); + staker::rebalance(staker, system_state, ctx); + } + public(package) fun unstake_sui_and_fulfill_liquidity_request( reserve: &mut Reserve

, request: LiquidityRequest, @@ -731,6 +760,7 @@ module suilend::reserve { let LiquidityRequest { amount, fee } = request; + assert!(dynamic_field::exists_(&reserve.id, StakerKey {}), EStakerNotInitialized); let staker: &mut Staker = dynamic_field::borrow_mut(&mut reserve.id, StakerKey {}); let mut liquidity = staker::withdraw(staker, amount, system_state, ctx); From c602763bc019b5e6e4b273d91662f0ccb1be598f Mon Sep 17 00:00:00 2001 From: 0xripleys <0xripleys@solend.fi> Date: Fri, 8 Nov 2024 18:40:55 -0500 Subject: [PATCH 07/18] new idea --- contracts/suilend/sources/lending_market.move | 42 +++++- contracts/suilend/sources/reserve.move | 39 ++++- .../suilend/tests/lending_market_tests.move | 134 +++++++++++++++++- 3 files changed, 211 insertions(+), 4 deletions(-) diff --git a/contracts/suilend/sources/lending_market.move b/contracts/suilend/sources/lending_market.move index 878683b..31d62e4 100644 --- a/contracts/suilend/sources/lending_market.move +++ b/contracts/suilend/sources/lending_market.move @@ -779,6 +779,7 @@ module suilend::lending_market { } } + /* Staker operations */ public fun init_staker( lending_market: &mut LendingMarket

, sui_reserve_array_index: u64, @@ -803,6 +804,19 @@ module suilend::lending_market { reserve::rebalance_staker(reserve, system_state, ctx); } + public fun unstake_sui_from_staker( + lending_market: &mut LendingMarket

, + sui_reserve_array_index: u64, + liquidity_request: &LiquidityRequest, + system_state: &mut SuiSystemState, + ctx: &mut TxContext + ) { + let reserve = vector::borrow_mut(&mut lending_market.reserves, sui_reserve_array_index); + assert!(reserve::coin_type(reserve) == type_name::get(), EWrongType); + + reserve::unstake_sui_from_staker(reserve, liquidity_request, system_state, ctx); + } + // === Public-View Functions === fun max_borrow_amount

( mut rate_limiter: RateLimiter, @@ -941,7 +955,7 @@ module suilend::lending_market { object::id(lending_market), config, vector::length(&lending_market.reserves), - coin_metadata, + coin::get_decimals(coin_metadata), price_info, clock, ctx @@ -1218,4 +1232,30 @@ module suilend::lending_market { let LendingMarketOwnerCap { id, lending_market_id: _ } = lending_market_owner_cap; object::delete(id); } + + #[test_only] + public fun add_reserve_for_testing( + _: &LendingMarketOwnerCap

, + lending_market: &mut LendingMarket

, + price_info: &PriceInfoObject, + config: ReserveConfig, + mint_decimals: u8, + clock: &Clock, + ctx: &mut TxContext + ) { + assert!(lending_market.version == CURRENT_VERSION, EIncorrectVersion); + assert!(reserve_array_index(lending_market) == vector::length(&lending_market.reserves), EDuplicateReserve); + + let reserve = reserve::create_reserve( + object::id(lending_market), + config, + vector::length(&lending_market.reserves), + mint_decimals, + price_info, + clock, + ctx + ); + + vector::push_back(&mut lending_market.reserves, reserve); + } } diff --git a/contracts/suilend/sources/reserve.move b/contracts/suilend/sources/reserve.move index 4f78652..daf76cd 100644 --- a/contracts/suilend/sources/reserve.move +++ b/contracts/suilend/sources/reserve.move @@ -155,7 +155,7 @@ module suilend::reserve { lending_market_id: ID, config: ReserveConfig, array_index: u64, - coin_metadata: &CoinMetadata, + mint_decimals: u8, price_info_obj: &PriceInfoObject, clock: &Clock, ctx: &mut TxContext @@ -170,7 +170,7 @@ module suilend::reserve { array_index, coin_type: type_name::get(), config: cell::new(config), - mint_decimals: coin::get_decimals(coin_metadata), + mint_decimals, price_identifier, price: option::extract(&mut price_decimal), smoothed_price: smoothed_price_decimal, @@ -491,6 +491,10 @@ module suilend::reserve { request.fee } + public fun staker(reserve: &Reserve

): &Staker { + dynamic_field::borrow(&reserve.id, StakerKey {}) + } + // === Public-Mutative Functions public(package) fun deposits_pool_reward_manager_mut

(reserve: &mut Reserve

): &mut PoolRewardManager { &mut reserve.deposits_pool_reward_manager @@ -750,6 +754,37 @@ module suilend::reserve { staker::rebalance(staker, system_state, ctx); } + public(package) fun unstake_sui_from_staker( + reserve: &mut Reserve

, + liquidity_request: &LiquidityRequest, + system_state: &mut SuiSystemState, + ctx: &mut TxContext + ) { + if (!dynamic_field::exists_(&reserve.id, StakerKey {})) { + return + }; + + let balances: &Balances = dynamic_field::borrow(&reserve.id, BalanceKey {}); + if (liquidity_request.amount <= balance::value(&balances.available_amount)) { + return + }; + let withdraw_amount = liquidity_request.amount - balance::value(&balances.available_amount); + + let staker: &mut Staker = dynamic_field::borrow_mut(&mut reserve.id, StakerKey {}); + let sui = staker::withdraw( + staker, + withdraw_amount, + system_state, + ctx + ); + + let balances: &mut Balances = dynamic_field::borrow_mut( + &mut reserve.id, + BalanceKey {} + ); + balance::join(&mut balances.available_amount, sui); + } + public(package) fun unstake_sui_and_fulfill_liquidity_request( reserve: &mut Reserve

, request: LiquidityRequest, diff --git a/contracts/suilend/tests/lending_market_tests.move b/contracts/suilend/tests/lending_market_tests.move index 44a3587..8a9afce 100644 --- a/contracts/suilend/tests/lending_market_tests.move +++ b/contracts/suilend/tests/lending_market_tests.move @@ -1,4 +1,5 @@ module suilend::lending_market_tests { + use sui_system::sui_system::{SuiSystemState}; use sui::test_scenario::{Self, Scenario}; use sui::object::{Self, ID, UID}; use suilend::rate_limiter::{Self, RateLimiter, RateLimiterConfig}; @@ -23,6 +24,7 @@ module suilend::lending_market_tests { use sui::package; use suilend::lending_market::{Self, create_lending_market, LendingMarketOwnerCap, LendingMarket}; use suilend::mock_pyth::{PriceState}; + use sui::sui::SUI; public struct LENDING_MARKET has drop {} @@ -132,10 +134,12 @@ module suilend::lending_market_tests { 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)); + 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); + bag::add(&mut type_to_index, type_name::get(), 2); lending_market::add_reserve( &owner_cap, @@ -157,6 +161,16 @@ module suilend::lending_market_tests { test_scenario::ctx(scenario) ); + lending_market::add_reserve_for_testing( + &owner_cap, + &mut lending_market, + mock_pyth::get_price_obj(&prices), + reserve_config::default_reserve_config(), + 9, + &clock, + test_scenario::ctx(scenario) + ); + if (bag::contains(&reserve_args, type_name::get())) { let ReserveArgs { config, initial_deposit } = bag::remove( &mut reserve_args, @@ -211,6 +225,33 @@ module suilend::lending_market_tests { 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, + 2, + &clock, + coins, + test_scenario::ctx(scenario) + ); + + lending_market::update_reserve_config( + &owner_cap, + &mut lending_market, + 2, + config + ); + + test_utils::destroy(ctokens); + }; test_utils::destroy(reserve_args); test_utils::destroy(metadata); @@ -1196,7 +1237,7 @@ module suilend::lending_market_tests { test_scenario::end(scenario); } - #[test] + #[test] public fun test_max_borrow() { use sui::test_utils::{Self}; use suilend::test_usdc::{TEST_USDC}; @@ -1621,4 +1662,95 @@ module suilend::lending_market_tests { test_utils::destroy(type_to_index); test_scenario::end(scenario); } + + public struct STAKER has drop {} + + use sui_system::governance_test_utils::{ + advance_epoch_with_reward_amounts, + create_validator_for_testing, + create_sui_system_state_for_testing, + }; + + const SUILEND_VALIDATOR: address = @0xce8e537664ba5d1d5a6a857b17bd142097138706281882be6805e17065ecde89; + + fun setup_sui_system(scenario: &mut Scenario) { + test_scenario::next_tx(scenario, SUILEND_VALIDATOR); + let validator = create_validator_for_testing(SUILEND_VALIDATOR, 100, test_scenario::ctx(scenario)); + create_sui_system_state_for_testing(vector[validator], 0, 0, test_scenario::ctx(scenario)); + + advance_epoch_with_reward_amounts(0, 0, scenario); + } + + #[test] + public fun test_staker_e2e() { + use sui::test_utils::{Self}; + use suilend::reserve_config::{Self, default_reserve_config}; + use suilend::test_usdc::{TEST_USDC}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + setup_sui_system(&mut scenario); + + 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: default_reserve_config(), + initial_deposit: 100 * 1_000_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_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 + }, &mut scenario); + + clock::set_for_testing(&mut clock, 1 * 1000); + let treasury_cap = coin::create_treasury_cap_for_testing(scenario.ctx()); + lending_market::init_staker( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + treasury_cap, + test_scenario::ctx(&mut scenario) + ); + + let mut system_state = test_scenario::take_shared(&scenario); + lending_market::rebalance_staker( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &mut system_state, + test_scenario::ctx(&mut scenario) + ); + + let sui_reserve = lending_market::reserve(&lending_market); + let staker = reserve::staker(sui_reserve); + std::debug::print(staker); + + test_scenario::return_shared(system_state); + + 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 From 0dc318a908517f7889204f712aedc21a8d655834 Mon Sep 17 00:00:00 2001 From: 0xripleys <0xripleys@solend.fi> Date: Fri, 8 Nov 2024 19:39:12 -0500 Subject: [PATCH 08/18] slightly nicer way of doing stuff --- contracts/suilend/sources/lending_market.move | 92 ++++--------------- contracts/suilend/sources/reserve.move | 24 +---- .../suilend/tests/lending_market_tests.move | 73 +++++++++++---- 3 files changed, 74 insertions(+), 115 deletions(-) diff --git a/contracts/suilend/sources/lending_market.move b/contracts/suilend/sources/lending_market.move index 31d62e4..ab9b30d 100644 --- a/contracts/suilend/sources/lending_market.move +++ b/contracts/suilend/sources/lending_market.move @@ -252,38 +252,6 @@ module suilend::lending_market { coin::from_balance(ctokens, ctx) } - public fun redeem_ctokens_and_withdraw_liquidity_sui( - lending_market: &mut LendingMarket

, - system_state: &mut SuiSystemState, - reserve_array_index: u64, - clock: &Clock, - ctokens: Coin>, - rate_limiter_exemption: Option>, - ctx: &mut TxContext - ): Coin { - let liquidity_request = redeem_ctokens_and_withdraw_liquidity_request( - lending_market, - reserve_array_index, - clock, - ctokens, - rate_limiter_exemption, - ctx - ); - - let reserve = vector::borrow_mut(&mut lending_market.reserves, reserve_array_index); - assert!(reserve::coin_type(reserve) == type_name::get(), EWrongType); - - coin::from_balance( - reserve::unstake_sui_and_fulfill_liquidity_request( - reserve, - liquidity_request, - system_state, - ctx - ), - ctx - ) - } - public fun redeem_ctokens_and_withdraw_liquidity( lending_market: &mut LendingMarket

, reserve_array_index: u64, @@ -301,13 +269,7 @@ module suilend::lending_market { ctx ); - let reserve = vector::borrow_mut(&mut lending_market.reserves, reserve_array_index); - assert!(reserve::coin_type(reserve) == type_name::get(), EWrongType); - - coin::from_balance( - reserve::fulfill_liquidity_request(reserve, liquidity_request), - ctx - ) + fulfill_liquidity_request(lending_market, reserve_array_index, liquidity_request, ctx) } public fun redeem_ctokens_and_withdraw_liquidity_request( @@ -382,35 +344,6 @@ module suilend::lending_market { ) } - /// Borrow sui. - public fun borrow_sui( - lending_market: &mut LendingMarket

, - system_state: &mut SuiSystemState, - reserve_array_index: u64, - obligation_owner_cap: &ObligationOwnerCap

, - clock: &Clock, - amount: u64, - ctx: &mut TxContext - ): Coin { - let liquidity_request = borrow_request( - lending_market, - reserve_array_index, - obligation_owner_cap, - clock, - amount - ); - - let mut reserve = vector::borrow_mut(&mut lending_market.reserves, reserve_array_index); - let sui = reserve::unstake_sui_and_fulfill_liquidity_request( - reserve, - liquidity_request, - system_state, - ctx - ); - - coin::from_balance(sui, ctx) - } - /// Borrow tokens of type T. A fee is charged. public fun borrow( lending_market: &mut LendingMarket

, @@ -428,13 +361,7 @@ module suilend::lending_market { amount ); - let reserve = vector::borrow_mut(&mut lending_market.reserves, reserve_array_index); - assert!(reserve::coin_type(reserve) == type_name::get(), EWrongType); - - coin::from_balance( - reserve::fulfill_liquidity_request(reserve, liquidity_request), - ctx - ) + fulfill_liquidity_request(lending_market, reserve_array_index, liquidity_request, ctx) } /// Borrow tokens of type T. A fee is charged. @@ -493,6 +420,21 @@ module suilend::lending_market { liquidity_request } + public fun fulfill_liquidity_request( + lending_market: &mut LendingMarket

, + reserve_array_index: u64, + liquidity_request: LiquidityRequest, + ctx: &mut TxContext + ): Coin { + let reserve = vector::borrow_mut(&mut lending_market.reserves, reserve_array_index); + assert!(reserve::coin_type(reserve) == type_name::get(), EWrongType); + + coin::from_balance( + reserve::fulfill_liquidity_request(reserve, liquidity_request), + ctx + ) + } + public fun withdraw_ctokens( lending_market: &mut LendingMarket

, reserve_array_index: u64, diff --git a/contracts/suilend/sources/reserve.move b/contracts/suilend/sources/reserve.move index daf76cd..bc2e22b 100644 --- a/contracts/suilend/sources/reserve.move +++ b/contracts/suilend/sources/reserve.move @@ -760,6 +760,7 @@ module suilend::reserve { system_state: &mut SuiSystemState, ctx: &mut TxContext ) { + assert!(reserve.coin_type == type_name::get(), EWrongType); if (!dynamic_field::exists_(&reserve.id, StakerKey {})) { return }; @@ -785,29 +786,6 @@ module suilend::reserve { balance::join(&mut balances.available_amount, sui); } - public(package) fun unstake_sui_and_fulfill_liquidity_request( - reserve: &mut Reserve

, - request: LiquidityRequest, - system_state: &mut SuiSystemState, - ctx: &mut TxContext, - ): Balance { - assert!(reserve.coin_type == type_name::get(), EWrongType); - - let LiquidityRequest { amount, fee } = request; - - assert!(dynamic_field::exists_(&reserve.id, StakerKey {}), EStakerNotInitialized); - let staker: &mut Staker = dynamic_field::borrow_mut(&mut reserve.id, StakerKey {}); - let mut liquidity = staker::withdraw(staker, amount, system_state, ctx); - - let balances: &mut Balances = dynamic_field::borrow_mut( - &mut reserve.id, - BalanceKey {} - ); - balance::join(&mut balances.fees, balance::split(&mut liquidity, fee)); - - liquidity - } - /// Borrow tokens from the reserve. A fee is charged on the borrowed amount public(package) fun borrow_liquidity( reserve: &mut Reserve

, diff --git a/contracts/suilend/tests/lending_market_tests.move b/contracts/suilend/tests/lending_market_tests.move index 8a9afce..cd76f62 100644 --- a/contracts/suilend/tests/lending_market_tests.move +++ b/contracts/suilend/tests/lending_market_tests.move @@ -30,6 +30,7 @@ module suilend::lending_market_tests { public struct LENDING_MARKET has drop {} const U64_MAX: u64 = 18446744073709551615; + const MIST_PER_SUI: u64 = 1_000_000_000; #[test] fun test_create_lending_market() { @@ -1701,23 +1702,6 @@ module suilend::lending_market_tests { initial_deposit: 100 * 1_000_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_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 }, &mut scenario); @@ -1743,8 +1727,63 @@ module suilend::lending_market_tests { let staker = reserve::staker(sui_reserve); std::debug::print(staker); + let sui = coin::mint_for_testing(100 * 1_000_000_000, test_scenario::ctx(&mut scenario)); + let c_sui =lending_market::deposit_liquidity_and_mint_ctokens( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + sui, + test_scenario::ctx(&mut scenario) + ); + + lending_market::rebalance_staker( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &mut system_state, + test_scenario::ctx(&mut scenario) + ); + + let sui_reserve = lending_market::reserve(&lending_market); + let staker = reserve::staker(sui_reserve); + std::debug::print(staker); + + // let sui = lending_market::redeem_ctokens_and_withdraw_liquidity( + // &mut lending_market, + // *bag::borrow(&type_to_index, type_name::get()), + // &clock, + // c_sui, + // option::none(), + // test_scenario::ctx(&mut scenario) + // ); + + let liquidity_request = lending_market::redeem_ctokens_and_withdraw_liquidity_request( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &clock, + c_sui, + option::none(), + test_scenario::ctx(&mut scenario) + ); + + lending_market::unstake_sui_from_staker( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &liquidity_request, + &mut system_state, + test_scenario::ctx(&mut scenario) + ); + + let sui = lending_market::fulfill_liquidity_request( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + liquidity_request, + test_scenario::ctx(&mut scenario) + ); + assert!(coin::value(&sui) == 100 * MIST_PER_SUI, 0); + test_scenario::return_shared(system_state); + test_utils::destroy(sui); test_utils::destroy(owner_cap); test_utils::destroy(lending_market); test_utils::destroy(clock); From 8dc0ad216cc208f563c96e2f97fdeba8b27c81c6 Mon Sep 17 00:00:00 2001 From: 0xripleys <0xripleys@solend.fi> Date: Fri, 8 Nov 2024 22:58:38 -0500 Subject: [PATCH 09/18] fixes --- contracts/suilend/sources/lending_market.move | 1 + contracts/suilend/tests/lending_market_tests.move | 11 +---------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/contracts/suilend/sources/lending_market.move b/contracts/suilend/sources/lending_market.move index ab9b30d..9f486c2 100644 --- a/contracts/suilend/sources/lending_market.move +++ b/contracts/suilend/sources/lending_market.move @@ -417,6 +417,7 @@ module suilend::lending_market { origination_fee_amount: reserve::liquidity_request_fee(&liquidity_request), }); + obligation::zero_out_rewards_if_looped(obligation, &mut lending_market.reserves, clock); liquidity_request } diff --git a/contracts/suilend/tests/lending_market_tests.move b/contracts/suilend/tests/lending_market_tests.move index cd76f62..0018dbd 100644 --- a/contracts/suilend/tests/lending_market_tests.move +++ b/contracts/suilend/tests/lending_market_tests.move @@ -1728,7 +1728,7 @@ module suilend::lending_market_tests { std::debug::print(staker); let sui = coin::mint_for_testing(100 * 1_000_000_000, test_scenario::ctx(&mut scenario)); - let c_sui =lending_market::deposit_liquidity_and_mint_ctokens( + let c_sui = lending_market::deposit_liquidity_and_mint_ctokens( &mut lending_market, *bag::borrow(&type_to_index, type_name::get()), &clock, @@ -1747,15 +1747,6 @@ module suilend::lending_market_tests { let staker = reserve::staker(sui_reserve); std::debug::print(staker); - // let sui = lending_market::redeem_ctokens_and_withdraw_liquidity( - // &mut lending_market, - // *bag::borrow(&type_to_index, type_name::get()), - // &clock, - // c_sui, - // option::none(), - // test_scenario::ctx(&mut scenario) - // ); - let liquidity_request = lending_market::redeem_ctokens_and_withdraw_liquidity_request( &mut lending_market, *bag::borrow(&type_to_index, type_name::get()), From 8b70d10a654a5fbc0bcbd84a15c4f6df42a2bb9d Mon Sep 17 00:00:00 2001 From: 0xripleys <0xripleys@solend.fi> Date: Fri, 8 Nov 2024 22:59:29 -0500 Subject: [PATCH 10/18] keep public funcsigs the same --- contracts/suilend/sources/lending_market.move | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/suilend/sources/lending_market.move b/contracts/suilend/sources/lending_market.move index 9f486c2..d0fe37a 100644 --- a/contracts/suilend/sources/lending_market.move +++ b/contracts/suilend/sources/lending_market.move @@ -350,7 +350,7 @@ module suilend::lending_market { reserve_array_index: u64, obligation_owner_cap: &ObligationOwnerCap

, clock: &Clock, - amount: u64, + mut amount: u64, ctx: &mut TxContext ): Coin { let liquidity_request = borrow_request( From eab45698daf46b82c5173f646066d5f5ee887071 Mon Sep 17 00:00:00 2001 From: 0xripleys <0xripleys@solend.fi> Date: Sun, 10 Nov 2024 18:11:57 -0500 Subject: [PATCH 11/18] review fixes --- contracts/suilend/sources/lending_market.move | 20 +++--- contracts/suilend/sources/reserve.move | 20 +++--- contracts/suilend/sources/staker.move | 61 ++++++++++++------- .../suilend/tests/lending_market_tests.move | 15 +++-- contracts/suilend/tests/staker_tests.move | 4 +- 5 files changed, 70 insertions(+), 50 deletions(-) diff --git a/contracts/suilend/sources/lending_market.move b/contracts/suilend/sources/lending_market.move index d0fe37a..8e228e1 100644 --- a/contracts/suilend/sources/lending_market.move +++ b/contracts/suilend/sources/lending_market.move @@ -23,6 +23,7 @@ module suilend::lending_market { use suilend::liquidity_mining::{Self}; use sui::package; use sui::sui::SUI; + use suilend::staker::{STAKER}; // === Errors === const EIncorrectVersion: u64 = 1; @@ -723,19 +724,20 @@ module suilend::lending_market { } /* Staker operations */ - public fun init_staker( + public fun init_staker

( lending_market: &mut LendingMarket

, + _: &LendingMarketOwnerCap

, sui_reserve_array_index: u64, - treasury_cap: TreasuryCap, + treasury_cap: TreasuryCap, ctx: &mut TxContext ) { let reserve = vector::borrow_mut(&mut lending_market.reserves, sui_reserve_array_index); assert!(reserve::coin_type(reserve) == type_name::get(), EWrongType); - reserve::init_staker(reserve, treasury_cap, ctx); + reserve::init_staker

(reserve, treasury_cap, ctx); } - public fun rebalance_staker( + public fun rebalance_staker

( lending_market: &mut LendingMarket

, sui_reserve_array_index: u64, system_state: &mut SuiSystemState, @@ -744,10 +746,10 @@ module suilend::lending_market { let reserve = vector::borrow_mut(&mut lending_market.reserves, sui_reserve_array_index); assert!(reserve::coin_type(reserve) == type_name::get(), EWrongType); - reserve::rebalance_staker(reserve, system_state, ctx); + reserve::rebalance_staker

(reserve, system_state, ctx); } - public fun unstake_sui_from_staker( + public fun unstake_sui_from_staker

( lending_market: &mut LendingMarket

, sui_reserve_array_index: u64, liquidity_request: &LiquidityRequest, @@ -755,9 +757,11 @@ module suilend::lending_market { ctx: &mut TxContext ) { let reserve = vector::borrow_mut(&mut lending_market.reserves, sui_reserve_array_index); - assert!(reserve::coin_type(reserve) == type_name::get(), EWrongType); + if (reserve::coin_type(reserve) != type_name::get()) { + return; + }; - reserve::unstake_sui_from_staker(reserve, liquidity_request, system_state, ctx); + reserve::unstake_sui_from_staker

(reserve, liquidity_request, system_state, ctx); } // === Public-View Functions === diff --git a/contracts/suilend/sources/reserve.move b/contracts/suilend/sources/reserve.move index bc2e22b..fc186d0 100644 --- a/contracts/suilend/sources/reserve.move +++ b/contracts/suilend/sources/reserve.move @@ -32,7 +32,7 @@ module suilend::reserve { liquidation_bonus }; use suilend::liquidity_mining::{Self, PoolRewardManager}; - use suilend::staker::{Self, Staker}; + use suilend::staker::{Self, Staker, STAKER}; use sui_system::sui_system::{SuiSystemState}; // === Errors === @@ -484,14 +484,14 @@ module suilend::reserve { &balances.ctoken_fees } - public fun liquidity_request_amount(request: &LiquidityRequest): u64 { + public(package) fun liquidity_request_amount(request: &LiquidityRequest): u64 { request.amount } - public fun liquidity_request_fee(request: &LiquidityRequest): u64 { + public(package) fun liquidity_request_fee(request: &LiquidityRequest): u64 { request.fee } - public fun staker(reserve: &Reserve

): &Staker { + public fun staker

(reserve: &Reserve

): &Staker { dynamic_field::borrow(&reserve.id, StakerKey {}) } @@ -725,9 +725,9 @@ module suilend::reserve { liquidity } - public(package) fun init_staker( + public(package) fun init_staker

( reserve: &mut Reserve

, - treasury_cap: TreasuryCap, + treasury_cap: TreasuryCap, ctx: &mut TxContext ) { assert!(!dynamic_field::exists_(&reserve.id, StakerKey {}), EStakerAlreadyInitialized); @@ -736,7 +736,7 @@ module suilend::reserve { dynamic_field::add(&mut reserve.id, StakerKey {}, staker); } - public(package) fun rebalance_staker( + public(package) fun rebalance_staker

( reserve: &mut Reserve

, system_state: &mut SuiSystemState, ctx: &mut TxContext @@ -748,13 +748,13 @@ module suilend::reserve { ); let sui = balance::withdraw_all(&mut balances.available_amount); - let staker: &mut Staker = dynamic_field::borrow_mut(&mut reserve.id, StakerKey {}); + let staker: &mut Staker = dynamic_field::borrow_mut(&mut reserve.id, StakerKey {}); staker::deposit(staker, sui); staker::rebalance(staker, system_state, ctx); } - public(package) fun unstake_sui_from_staker( + public(package) fun unstake_sui_from_staker

( reserve: &mut Reserve

, liquidity_request: &LiquidityRequest, system_state: &mut SuiSystemState, @@ -771,7 +771,7 @@ module suilend::reserve { }; let withdraw_amount = liquidity_request.amount - balance::value(&balances.available_amount); - let staker: &mut Staker = dynamic_field::borrow_mut(&mut reserve.id, StakerKey {}); + let staker: &mut Staker = dynamic_field::borrow_mut(&mut reserve.id, StakerKey {}); let sui = staker::withdraw( staker, withdraw_amount, diff --git a/contracts/suilend/sources/staker.move b/contracts/suilend/sources/staker.move index b54cad6..dfa5995 100644 --- a/contracts/suilend/sources/staker.move +++ b/contracts/suilend/sources/staker.move @@ -21,41 +21,58 @@ module suilend::staker { // This is mostly so i don't hit the "zero lst coin mint" error. const MIN_DEPLOY_AMOUNT: u64 = 1_000_000; // 1 SUI - public struct Staker has store { - admin: AdminCap

, - liquid_staking_info: LiquidStakingInfo

, - lst_balance: Balance

, + public struct STAKER has drop {} + + fun init(otw: STAKER, ctx: &mut TxContext) { + let (treasury, metadata) = coin::create_currency( + otw, + 9, + b"SprungSui", + b"", + b"", + option::none(), + ctx + ); + + transfer::public_freeze_object(metadata); + transfer::public_transfer(treasury, ctx.sender()) + } + + public struct Staker has store { + admin: AdminCap, + liquid_staking_info: LiquidStakingInfo, + lst_balance: Balance, sui_balance: Balance, liabilities: u64, // how much sui is owed to the reserve } /* Public-View Functions */ - public(package) fun liabilities

(staker: &Staker

): u64 { + public(package) fun liabilities(staker: &Staker): u64 { staker.liabilities } - public(package) fun lst_balance

(staker: &Staker

): &Balance

{ + public(package) fun lst_balance(staker: &Staker): &Balance { &staker.lst_balance } - public(package) fun sui_balance

(staker: &Staker

): &Balance { + public(package) fun sui_balance(staker: &Staker): &Balance { &staker.sui_balance } // this value can be stale if the staker hasn't refreshed the liquid_staking_info - public(package) fun total_sui_supply

(staker: &Staker

): u64 { + public(package) fun total_sui_supply(staker: &Staker): u64 { staker.liquid_staking_info.total_sui_supply() + staker.sui_balance.value() } - public(package) fun liquid_staking_info

(staker: &Staker

): &LiquidStakingInfo

{ + public(package) fun liquid_staking_info(staker: &Staker): &LiquidStakingInfo { &staker.liquid_staking_info } /* Public Mutative Functions */ - public(package) fun create_staker( - treasury_cap: TreasuryCap

, + public(package) fun create_staker( + treasury_cap: TreasuryCap, ctx: &mut TxContext - ): Staker

{ + ): Staker { assert!(coin::total_supply(&treasury_cap) == 0, ETreasuryCapNonZeroSupply); let (admin_cap, liquid_staking_info) = liquid_staking::create_lst( @@ -73,16 +90,16 @@ module suilend::staker { } } - public(package) fun deposit( - staker: &mut Staker

, + public(package) fun deposit( + staker: &mut Staker, sui: Balance, ) { staker.liabilities = staker.liabilities + sui.value(); staker.sui_balance.join(sui); } - public(package) fun withdraw( - staker: &mut Staker

, + public(package) fun withdraw( + staker: &mut Staker, withdraw_amount: u64, system_state: &mut SuiSystemState, ctx: &mut TxContext @@ -100,8 +117,8 @@ module suilend::staker { sui } - public(package) fun rebalance( - staker: &mut Staker

, + public(package) fun rebalance( + staker: &mut Staker, system_state: &mut SuiSystemState, ctx: &mut TxContext ) { @@ -128,8 +145,8 @@ module suilend::staker { ); } - public(package) fun claim_fees( - staker: &mut Staker

, + public(package) fun claim_fees( + staker: &mut Staker, system_state: &mut SuiSystemState, ctx: &mut TxContext ): Balance { @@ -157,8 +174,8 @@ module suilend::staker { // liquid_staking_info must be refreshed before calling this // this function can unstake slightly more sui than requested due to rounding. - fun unstake_n_sui( - staker: &mut Staker

, + fun unstake_n_sui( + staker: &mut Staker, system_state: &mut SuiSystemState, sui_amount_out: u64, ctx: &mut TxContext diff --git a/contracts/suilend/tests/lending_market_tests.move b/contracts/suilend/tests/lending_market_tests.move index 0018dbd..f780a12 100644 --- a/contracts/suilend/tests/lending_market_tests.move +++ b/contracts/suilend/tests/lending_market_tests.move @@ -25,7 +25,7 @@ module suilend::lending_market_tests { use suilend::lending_market::{Self, create_lending_market, LendingMarketOwnerCap, LendingMarket}; use suilend::mock_pyth::{PriceState}; use sui::sui::SUI; - + use suilend::staker::{STAKER}; public struct LENDING_MARKET has drop {} @@ -1664,8 +1664,6 @@ module suilend::lending_market_tests { test_scenario::end(scenario); } - public struct STAKER has drop {} - use sui_system::governance_test_utils::{ advance_epoch_with_reward_amounts, create_validator_for_testing, @@ -1708,15 +1706,16 @@ module suilend::lending_market_tests { clock::set_for_testing(&mut clock, 1 * 1000); let treasury_cap = coin::create_treasury_cap_for_testing(scenario.ctx()); - lending_market::init_staker( + lending_market::init_staker( &mut lending_market, + &owner_cap, *bag::borrow(&type_to_index, type_name::get()), treasury_cap, test_scenario::ctx(&mut scenario) ); let mut system_state = test_scenario::take_shared(&scenario); - lending_market::rebalance_staker( + lending_market::rebalance_staker( &mut lending_market, *bag::borrow(&type_to_index, type_name::get()), &mut system_state, @@ -1724,7 +1723,7 @@ module suilend::lending_market_tests { ); let sui_reserve = lending_market::reserve(&lending_market); - let staker = reserve::staker(sui_reserve); + let staker = reserve::staker(sui_reserve); std::debug::print(staker); let sui = coin::mint_for_testing(100 * 1_000_000_000, test_scenario::ctx(&mut scenario)); @@ -1736,7 +1735,7 @@ module suilend::lending_market_tests { test_scenario::ctx(&mut scenario) ); - lending_market::rebalance_staker( + lending_market::rebalance_staker( &mut lending_market, *bag::borrow(&type_to_index, type_name::get()), &mut system_state, @@ -1744,7 +1743,7 @@ module suilend::lending_market_tests { ); let sui_reserve = lending_market::reserve(&lending_market); - let staker = reserve::staker(sui_reserve); + let staker = reserve::staker(sui_reserve); std::debug::print(staker); let liquidity_request = lending_market::redeem_ctokens_and_withdraw_liquidity_request( diff --git a/contracts/suilend/tests/staker_tests.move b/contracts/suilend/tests/staker_tests.move index d1dd505..3f65871 100644 --- a/contracts/suilend/tests/staker_tests.move +++ b/contracts/suilend/tests/staker_tests.move @@ -10,7 +10,7 @@ module suilend::staker_tests { }; use sui::balance::{Self}; use sui::coin::{Self}; - use suilend::staker::{create_staker}; + use suilend::staker::{create_staker, STAKER}; use sui_system::sui_system::{SuiSystemState}; use sui::sui::{SUI}; @@ -32,7 +32,7 @@ module suilend::staker_tests { let mut scenario = test_scenario::begin(owner); setup_sui_system(&mut scenario); - let treasury_cap = coin::create_treasury_cap_for_testing(test_scenario::ctx(&mut scenario)); + let treasury_cap = coin::create_treasury_cap_for_testing(test_scenario::ctx(&mut scenario)); let mut staker = create_staker(treasury_cap, test_scenario::ctx(&mut scenario)); assert!(staker.sui_balance().value() == 0, 0); From 82d3f5114e2384bce7a2cc1ec9cfb7a114e3fe7b Mon Sep 17 00:00:00 2001 From: 0xripleys <0xripleys@solend.fi> Date: Sun, 10 Nov 2024 18:42:02 -0500 Subject: [PATCH 12/18] test edge case --- contracts/suilend/sources/staker.move | 5 +---- contracts/suilend/tests/lending_market_tests.move | 2 +- contracts/suilend/tests/staker_tests.move | 8 +++++++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/contracts/suilend/sources/staker.move b/contracts/suilend/sources/staker.move index dfa5995..47ecc20 100644 --- a/contracts/suilend/sources/staker.move +++ b/contracts/suilend/sources/staker.move @@ -162,10 +162,7 @@ module suilend::staker { let sui = staker.sui_balance.split(excess_sui); - assert!( - staker.liquid_staking_info.total_sui_supply() + staker.sui_balance.value() >= staker.liabilities, - EInvariantViolation - ); + assert!(staker.total_sui_supply() >= staker.liabilities, EInvariantViolation); sui } diff --git a/contracts/suilend/tests/lending_market_tests.move b/contracts/suilend/tests/lending_market_tests.move index f780a12..45287e7 100644 --- a/contracts/suilend/tests/lending_market_tests.move +++ b/contracts/suilend/tests/lending_market_tests.move @@ -1755,7 +1755,7 @@ module suilend::lending_market_tests { test_scenario::ctx(&mut scenario) ); - lending_market::unstake_sui_from_staker( + lending_market::unstake_sui_from_staker( &mut lending_market, *bag::borrow(&type_to_index, type_name::get()), &liquidity_request, diff --git a/contracts/suilend/tests/staker_tests.move b/contracts/suilend/tests/staker_tests.move index 3f65871..2c20723 100644 --- a/contracts/suilend/tests/staker_tests.move +++ b/contracts/suilend/tests/staker_tests.move @@ -49,6 +49,13 @@ module suilend::staker_tests { assert!(staker.sui_balance().value() == 100 * MIST_PER_SUI, 0); assert!(staker.lst_balance().value() == 0, 0); + let sui = staker.withdraw(100 * MIST_PER_SUI, &mut system_state, scenario.ctx()); + assert!(sui.value() == 100 * MIST_PER_SUI, 0); + assert!(staker.liabilities() == 0, 0); + assert!(staker.sui_balance().value() == 0, 0); + assert!(staker.lst_balance().value() == 0, 0); + + staker.deposit(sui); staker.rebalance(&mut system_state, scenario.ctx()); assert!(staker.liabilities() == 100 * MIST_PER_SUI, 0); @@ -57,7 +64,6 @@ module suilend::staker_tests { assert!(staker.total_sui_supply() == 100 * MIST_PER_SUI, 0); assert!(staker.liquid_staking_info().total_sui_supply() == 100 * MIST_PER_SUI, 0); - test_scenario::return_shared(system_state); advance_epoch_with_reward_amounts(0, 0, &mut scenario); From a2623e0cfa79cedd8279519250a45f33d9e2fe8d Mon Sep 17 00:00:00 2001 From: 0xripleys <0xripleys@solend.fi> Date: Sun, 10 Nov 2024 19:01:08 -0500 Subject: [PATCH 13/18] testing more edge cases --- contracts/suilend/sources/staker.move | 2 +- contracts/suilend/tests/staker_tests.move | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/contracts/suilend/sources/staker.move b/contracts/suilend/sources/staker.move index 47ecc20..5e5da9e 100644 --- a/contracts/suilend/sources/staker.move +++ b/contracts/suilend/sources/staker.move @@ -178,7 +178,7 @@ module suilend::staker { ctx: &mut TxContext ) { if (sui_amount_out == 0) { - return; + return }; let total_sui_supply = (staker.liquid_staking_info.total_sui_supply() as u128); diff --git a/contracts/suilend/tests/staker_tests.move b/contracts/suilend/tests/staker_tests.move index 2c20723..4c08d94 100644 --- a/contracts/suilend/tests/staker_tests.move +++ b/contracts/suilend/tests/staker_tests.move @@ -97,6 +97,14 @@ module suilend::staker_tests { assert!(staker.total_sui_supply() == 99 * MIST_PER_SUI - 1, 0); sui::test_utils::destroy(sui); + let sui = staker.claim_fees(&mut system_state, scenario.ctx()); + assert!(sui.value() == 0); + sui::test_utils::destroy(sui); + + let sui = staker.withdraw(0, &mut system_state, scenario.ctx()); + assert!(sui.value() == 0); + sui::test_utils::destroy(sui); + test_scenario::return_shared(system_state); sui::test_utils::destroy(staker); test_scenario::end(scenario); From 33493c958728ae00bed6815fd715f16c20ad79c1 Mon Sep 17 00:00:00 2001 From: 0xripleys <0xripleys@solend.fi> Date: Sun, 10 Nov 2024 22:55:06 -0500 Subject: [PATCH 14/18] add test for borrow_request --- contracts/suilend/sources/lending_market.move | 2 +- .../suilend/tests/lending_market_tests.move | 167 +++++++++++++++++- 2 files changed, 166 insertions(+), 3 deletions(-) diff --git a/contracts/suilend/sources/lending_market.move b/contracts/suilend/sources/lending_market.move index 8e228e1..56d3275 100644 --- a/contracts/suilend/sources/lending_market.move +++ b/contracts/suilend/sources/lending_market.move @@ -366,7 +366,7 @@ module suilend::lending_market { } /// Borrow tokens of type T. A fee is charged. - fun borrow_request( + public fun borrow_request( lending_market: &mut LendingMarket

, reserve_array_index: u64, obligation_owner_cap: &ObligationOwnerCap

, diff --git a/contracts/suilend/tests/lending_market_tests.move b/contracts/suilend/tests/lending_market_tests.move index 45287e7..41dd375 100644 --- a/contracts/suilend/tests/lending_market_tests.move +++ b/contracts/suilend/tests/lending_market_tests.move @@ -1681,7 +1681,7 @@ module suilend::lending_market_tests { } #[test] - public fun test_staker_e2e() { + public fun test_staker_e2e_redeem() { use sui::test_utils::{Self}; use suilend::reserve_config::{Self, default_reserve_config}; use suilend::test_usdc::{TEST_USDC}; @@ -1714,6 +1714,11 @@ module suilend::lending_market_tests { test_scenario::ctx(&mut scenario) ); + let sui_reserve = lending_market::reserve(&lending_market); + let staker = reserve::staker(sui_reserve); + assert!(staker.total_sui_supply() == 0); + assert!(staker.liabilities() == 0); + let mut system_state = test_scenario::take_shared(&scenario); lending_market::rebalance_staker( &mut lending_market, @@ -1724,7 +1729,8 @@ module suilend::lending_market_tests { let sui_reserve = lending_market::reserve(&lending_market); let staker = reserve::staker(sui_reserve); - std::debug::print(staker); + assert!(staker.total_sui_supply() == 100 * MIST_PER_SUI); + assert!(staker.liabilities() == 100 * MIST_PER_SUI); let sui = coin::mint_for_testing(100 * 1_000_000_000, test_scenario::ctx(&mut scenario)); let c_sui = lending_market::deposit_liquidity_and_mint_ctokens( @@ -1742,6 +1748,11 @@ module suilend::lending_market_tests { test_scenario::ctx(&mut scenario) ); + let sui_reserve = lending_market::reserve(&lending_market); + let staker = reserve::staker(sui_reserve); + assert!(staker.total_sui_supply() == 200 * MIST_PER_SUI); + assert!(staker.liabilities() == 200 * MIST_PER_SUI); + let sui_reserve = lending_market::reserve(&lending_market); let staker = reserve::staker(sui_reserve); std::debug::print(staker); @@ -1771,6 +1782,11 @@ module suilend::lending_market_tests { ); assert!(coin::value(&sui) == 100 * MIST_PER_SUI, 0); + let sui_reserve = lending_market::reserve(&lending_market); + let staker = reserve::staker(sui_reserve); + assert!(staker.total_sui_supply() == 100 * MIST_PER_SUI); + assert!(staker.liabilities() == 100 * MIST_PER_SUI); + test_scenario::return_shared(system_state); test_utils::destroy(sui); @@ -1782,4 +1798,151 @@ module suilend::lending_market_tests { test_scenario::end(scenario); } + #[test] + public fun test_staker_e2e_borrow() { + use sui::test_utils::{Self}; + use suilend::reserve_config::{Self, default_reserve_config}; + use suilend::test_usdc::{TEST_USDC}; + use suilend::mock_pyth::{Self}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + setup_sui_system(&mut scenario); + + 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 reserve_config = default_reserve_config(); + let mut builder = reserve_config::from( + &reserve_config, + test_scenario::ctx(&mut scenario) + ); + builder.set_borrow_fee_bps(100); + + sui::test_utils::destroy(reserve_config); + + builder.build(scenario.ctx()) + }, + initial_deposit: 100 * 1_000_000_000 + } + ); + bag::add( + &mut bag, + type_name::get(), + ReserveArgs { + config: { + let reserve_config = default_reserve_config(); + let mut builder = reserve_config::from( + &reserve_config, + test_scenario::ctx(&mut scenario) + ); + builder.set_open_ltv_pct(50); + builder.set_close_ltv_pct(50); + builder.set_max_close_ltv_pct(50); + + sui::test_utils::destroy(reserve_config); + + builder.build(scenario.ctx()) + }, + initial_deposit: 100 * 1_000_000 + } + ); + + bag + }, &mut scenario); + + clock::set_for_testing(&mut clock, 1 * 1000); + let treasury_cap = coin::create_treasury_cap_for_testing(scenario.ctx()); + lending_market::init_staker( + &mut lending_market, + &owner_cap, + *bag::borrow(&type_to_index, type_name::get()), + treasury_cap, + test_scenario::ctx(&mut scenario) + ); + + let sui_reserve = lending_market::reserve(&lending_market); + let staker = reserve::staker(sui_reserve); + assert!(staker.total_sui_supply() == 0); + assert!(staker.liabilities() == 0); + + + 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 liquidity_request = lending_market::borrow_request( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &obligation_owner_cap, + &clock, + 1 * 1_000_000_000, + ); + assert!(reserve::liquidity_request_amount(&liquidity_request) == 1 * 1_000_000_000 + 10_000_000); + assert!(reserve::liquidity_request_fee(&liquidity_request) == 10_000_000); + + let mut system_state = test_scenario::take_shared(&scenario); + + lending_market::unstake_sui_from_staker( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &liquidity_request, + &mut system_state, + test_scenario::ctx(&mut scenario) + ); + + let sui = lending_market::fulfill_liquidity_request( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + liquidity_request, + test_scenario::ctx(&mut scenario) + ); + assert!(coin::value(&sui) == 1 * MIST_PER_SUI, 0); + + test_scenario::return_shared(system_state); + + test_utils::destroy(sui); + test_utils::destroy(owner_cap); + test_utils::destroy(obligation_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 From 6417afb38783d64f92f715713d82c15338e3f68e Mon Sep 17 00:00:00 2001 From: 0xripleys <105607696+0xripleys@users.noreply.github.com> Date: Tue, 12 Nov 2024 22:20:01 -0500 Subject: [PATCH 15/18] Staker fees (#40) * claim fees when rebalancing staker * leave 1 sui of staker fees in the lst just in case * add more stuff to event --- contracts/suilend/sources/reserve.move | 26 +++++ contracts/suilend/sources/staker.move | 9 +- .../suilend/tests/lending_market_tests.move | 94 +++++++++++++++++++ contracts/suilend/tests/staker_tests.move | 24 +++-- 4 files changed, 145 insertions(+), 8 deletions(-) diff --git a/contracts/suilend/sources/reserve.move b/contracts/suilend/sources/reserve.move index fc186d0..969a699 100644 --- a/contracts/suilend/sources/reserve.move +++ b/contracts/suilend/sources/reserve.move @@ -149,6 +149,12 @@ module suilend::reserve { price_last_update_timestamp_s: u64, } + public struct ClaimStakingRewardsEvent has drop, copy { + lending_market_id: address, + coin_type: TypeName, + reserve_id: address, + amount: u64, + } // === Conpublic structor === public(package) fun create_reserve( @@ -752,6 +758,26 @@ module suilend::reserve { staker::deposit(staker, sui); staker::rebalance(staker, system_state, ctx); + + let fees = staker::claim_fees(staker, system_state, ctx); + if (balance::value(&fees) > 0) { + event::emit(ClaimStakingRewardsEvent { + lending_market_id: object::id_to_address(&reserve.lending_market_id), + coin_type: reserve.coin_type, + reserve_id: object::uid_to_address(&reserve.id), + amount: balance::value(&fees), + }); + + let balances: &mut Balances = dynamic_field::borrow_mut( + &mut reserve.id, + BalanceKey {} + ); + + balance::join(&mut balances.fees, fees); + } + else { + balance::destroy_zero(fees); + }; } public(package) fun unstake_sui_from_staker

( diff --git a/contracts/suilend/sources/staker.move b/contracts/suilend/sources/staker.move index 5e5da9e..13892b7 100644 --- a/contracts/suilend/sources/staker.move +++ b/contracts/suilend/sources/staker.move @@ -20,6 +20,7 @@ module suilend::staker { // This is mostly so i don't hit the "zero lst coin mint" error. const MIN_DEPLOY_AMOUNT: u64 = 1_000_000; // 1 SUI + const MIST_PER_SUI: u64 = 1_000_000_000; public struct STAKER has drop {} @@ -153,7 +154,13 @@ module suilend::staker { staker.liquid_staking_info.refresh(system_state, ctx); let total_sui_supply = staker.total_sui_supply(); - let excess_sui = total_sui_supply - staker.liabilities; + + // leave 1 SUI extra, just in case + let excess_sui = if (total_sui_supply > staker.liabilities + MIST_PER_SUI) { + total_sui_supply - staker.liabilities - MIST_PER_SUI + } else { + 0 + }; if (excess_sui > staker.sui_balance.value()) { let unstake_amount = excess_sui - staker.sui_balance.value(); diff --git a/contracts/suilend/tests/lending_market_tests.move b/contracts/suilend/tests/lending_market_tests.move index 41dd375..ee828da 100644 --- a/contracts/suilend/tests/lending_market_tests.move +++ b/contracts/suilend/tests/lending_market_tests.move @@ -1945,4 +1945,98 @@ module suilend::lending_market_tests { test_utils::destroy(type_to_index); test_scenario::end(scenario); } + + #[test] + public fun test_staker_e2e_claim_fees() { + use sui::test_utils::{Self}; + use suilend::reserve_config::{Self, default_reserve_config}; + use suilend::test_usdc::{TEST_USDC}; + + let owner = @0x26; + let mut scenario = test_scenario::begin(owner); + setup_sui_system(&mut scenario); + + 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: default_reserve_config(), + initial_deposit: 100 * 1_000_000_000 + } + ); + + bag + }, &mut scenario); + + clock::set_for_testing(&mut clock, 1 * 1000); + let treasury_cap = coin::create_treasury_cap_for_testing(scenario.ctx()); + lending_market::init_staker( + &mut lending_market, + &owner_cap, + *bag::borrow(&type_to_index, type_name::get()), + treasury_cap, + test_scenario::ctx(&mut scenario) + ); + + let sui_reserve = lending_market::reserve(&lending_market); + let staker = reserve::staker(sui_reserve); + assert!(staker.total_sui_supply() == 0); + assert!(staker.liabilities() == 0); + + let mut system_state = test_scenario::take_shared(&scenario); + lending_market::rebalance_staker( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &mut system_state, + test_scenario::ctx(&mut scenario) + ); + test_scenario::return_shared(system_state); + + let sui_reserve = lending_market::reserve(&lending_market); + let staker = reserve::staker(sui_reserve); + assert!(staker.total_sui_supply() == 100 * MIST_PER_SUI); + assert!(staker.sui_balance().value() == 0); + assert!(staker.liabilities() == 100 * MIST_PER_SUI); + + advance_epoch_with_reward_amounts(0, 0, &mut scenario); + advance_epoch_with_reward_amounts(0, 100 , &mut scenario); + + let mut system_state = test_scenario::take_shared(&scenario); + lending_market::rebalance_staker( + &mut lending_market, + *bag::borrow(&type_to_index, type_name::get()), + &mut system_state, + test_scenario::ctx(&mut scenario) + ); + test_scenario::return_shared(system_state); + + let sui_reserve = lending_market::reserve(&lending_market); + let staker = reserve::staker(sui_reserve); + std::debug::print(&staker.total_sui_supply()); + // the extra 50 sui gained has been transferred to the fees balance already + assert!(staker.total_sui_supply() == 101 * MIST_PER_SUI - 1); + assert!(staker.liabilities() == 100 * MIST_PER_SUI); + + 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) == 49 * MIST_PER_SUI, 0); + + test_utils::destroy(fees); + + 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/staker_tests.move b/contracts/suilend/tests/staker_tests.move index 4c08d94..cf8ce77 100644 --- a/contracts/suilend/tests/staker_tests.move +++ b/contracts/suilend/tests/staker_tests.move @@ -80,21 +80,31 @@ module suilend::staker_tests { assert!(staker.total_sui_supply() == 200 * MIST_PER_SUI, 0); let sui = staker.claim_fees(&mut system_state, scenario.ctx()); - assert!(sui.value() == 100 * MIST_PER_SUI, 0); + assert!(sui.value() == 99 * MIST_PER_SUI, 0); assert!(staker.liabilities() == 100 * MIST_PER_SUI, 0); assert!(staker.sui_balance().value() == 0, 0); - assert!(staker.liquid_staking_info().total_sui_supply() == 100 * MIST_PER_SUI, 0); - assert!(staker.lst_balance().value() == 50 * MIST_PER_SUI, 0); - assert!(staker.total_sui_supply() == 100 * MIST_PER_SUI, 0); + assert!(staker.liquid_staking_info().total_sui_supply() == 101 * MIST_PER_SUI, 0); + assert!(staker.lst_balance().value() == 50 * MIST_PER_SUI + 500_000_000, 0); + assert!(staker.total_sui_supply() == 101 * MIST_PER_SUI, 0); + sui::test_utils::destroy(sui); + + // should be no fees to claim + let sui = staker.claim_fees(&mut system_state, scenario.ctx()); + assert!(sui.value() == 0, 0); + assert!(staker.liabilities() == 100 * MIST_PER_SUI, 0); + assert!(staker.sui_balance().value() == 0, 0); + assert!(staker.liquid_staking_info().total_sui_supply() == 101 * MIST_PER_SUI, 0); + assert!(staker.lst_balance().value() == 50 * MIST_PER_SUI + 500_000_000, 0); + assert!(staker.total_sui_supply() == 101 * MIST_PER_SUI, 0); sui::test_utils::destroy(sui); let sui = staker.withdraw(MIST_PER_SUI + 1, &mut system_state, scenario.ctx()); assert!(sui.value() == MIST_PER_SUI + 1, 0); assert!(staker.liabilities() == 99 * MIST_PER_SUI - 1, 0); assert!(staker.sui_balance().value() == 1, 0); - assert!(staker.liquid_staking_info().total_sui_supply() == 99 * MIST_PER_SUI - 2, 0); - assert!(staker.lst_balance().value() == 49 * MIST_PER_SUI + 500_000_000 - 1, 0); - assert!(staker.total_sui_supply() == 99 * MIST_PER_SUI - 1, 0); + assert!(staker.liquid_staking_info().total_sui_supply() == 100 * MIST_PER_SUI - 2, 0); + assert!(staker.lst_balance().value() == 50 * MIST_PER_SUI - 1, 0); + assert!(staker.total_sui_supply() == 100 * MIST_PER_SUI - 1, 0); sui::test_utils::destroy(sui); let sui = staker.claim_fees(&mut system_state, scenario.ctx()); From ae70ef6aba5e9a01353d7c9a9090ae5812207005 Mon Sep 17 00:00:00 2001 From: 0xripleys <0xripleys@solend.fi> Date: Fri, 15 Nov 2024 14:41:04 -0500 Subject: [PATCH 16/18] version checks on the new functions --- contracts/suilend/sources/lending_market.move | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/contracts/suilend/sources/lending_market.move b/contracts/suilend/sources/lending_market.move index 56d3275..f158afd 100644 --- a/contracts/suilend/sources/lending_market.move +++ b/contracts/suilend/sources/lending_market.move @@ -428,6 +428,8 @@ module suilend::lending_market { liquidity_request: LiquidityRequest, ctx: &mut TxContext ): Coin { + assert!(lending_market.version == CURRENT_VERSION, EIncorrectVersion); + let reserve = vector::borrow_mut(&mut lending_market.reserves, reserve_array_index); assert!(reserve::coin_type(reserve) == type_name::get(), EWrongType); @@ -731,6 +733,8 @@ module suilend::lending_market { treasury_cap: TreasuryCap, ctx: &mut TxContext ) { + assert!(lending_market.version == CURRENT_VERSION, EIncorrectVersion); + let reserve = vector::borrow_mut(&mut lending_market.reserves, sui_reserve_array_index); assert!(reserve::coin_type(reserve) == type_name::get(), EWrongType); @@ -743,6 +747,8 @@ module suilend::lending_market { system_state: &mut SuiSystemState, ctx: &mut TxContext ) { + assert!(lending_market.version == CURRENT_VERSION, EIncorrectVersion); + let reserve = vector::borrow_mut(&mut lending_market.reserves, sui_reserve_array_index); assert!(reserve::coin_type(reserve) == type_name::get(), EWrongType); @@ -756,6 +762,8 @@ module suilend::lending_market { system_state: &mut SuiSystemState, ctx: &mut TxContext ) { + assert!(lending_market.version == CURRENT_VERSION, EIncorrectVersion); + let reserve = vector::borrow_mut(&mut lending_market.reserves, sui_reserve_array_index); if (reserve::coin_type(reserve) != type_name::get()) { return; From 30806c89dbe22b8434e0fd3c41d4a671f798122b Mon Sep 17 00:00:00 2001 From: 0xripleys <0xripleys@solend.fi> Date: Fri, 15 Nov 2024 21:58:15 -0500 Subject: [PATCH 17/18] test behavior changes a bit after liquid staking bugfix --- contracts/suilend/tests/lending_market_tests.move | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/suilend/tests/lending_market_tests.move b/contracts/suilend/tests/lending_market_tests.move index ee828da..a80a239 100644 --- a/contracts/suilend/tests/lending_market_tests.move +++ b/contracts/suilend/tests/lending_market_tests.move @@ -2016,7 +2016,7 @@ module suilend::lending_market_tests { let staker = reserve::staker(sui_reserve); std::debug::print(&staker.total_sui_supply()); // the extra 50 sui gained has been transferred to the fees balance already - assert!(staker.total_sui_supply() == 101 * MIST_PER_SUI - 1); + assert!(staker.total_sui_supply() == 101 * MIST_PER_SUI); assert!(staker.liabilities() == 100 * MIST_PER_SUI); lending_market::claim_fees( From de34fd0b3dc4202fa115bcbcd99cb9e83a7bda9d Mon Sep 17 00:00:00 2001 From: 0xripleys <105607696+0xripleys@users.noreply.github.com> Date: Sat, 16 Nov 2024 21:45:33 -0500 Subject: [PATCH 18/18] Type fixes (#41) --- contracts/sprungsui/Move.toml | 14 +++++ contracts/sprungsui/sources/sprungsui.move | 20 ++++++ contracts/suilend/Move.toml | 3 + contracts/suilend/sources/lending_market.move | 7 +-- contracts/suilend/sources/reserve.move | 14 +++-- contracts/suilend/sources/staker.move | 61 +++++++------------ .../suilend/tests/lending_market_tests.move | 34 +++++------ contracts/suilend/tests/staker_tests.move | 6 +- 8 files changed, 91 insertions(+), 68 deletions(-) create mode 100644 contracts/sprungsui/Move.toml create mode 100644 contracts/sprungsui/sources/sprungsui.move diff --git a/contracts/sprungsui/Move.toml b/contracts/sprungsui/Move.toml new file mode 100644 index 0000000..dbcc71b --- /dev/null +++ b/contracts/sprungsui/Move.toml @@ -0,0 +1,14 @@ +[package] +name = "sprungsui" +edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move + +[dependencies] +Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/testnet" } + +[addresses] +sprungsui = "0x0" + +[dev-dependencies] + +[dev-addresses] + diff --git a/contracts/sprungsui/sources/sprungsui.move b/contracts/sprungsui/sources/sprungsui.move new file mode 100644 index 0000000..4eb30db --- /dev/null +++ b/contracts/sprungsui/sources/sprungsui.move @@ -0,0 +1,20 @@ +module sprungsui::sprungsui { + use sui::coin::{Self}; + + public struct SPRUNGSUI has drop {} + + fun init(witness: SPRUNGSUI, ctx: &mut TxContext) { + let (treasury, metadata) = coin::create_currency( + witness, + 9, + b"", + b"Staked SUI", + b"", + option::none(), + ctx + ); + + transfer::public_share_object(metadata); + transfer::public_transfer(treasury, ctx.sender()) + } +} diff --git a/contracts/suilend/Move.toml b/contracts/suilend/Move.toml index 13a1d3a..7557900 100644 --- a/contracts/suilend/Move.toml +++ b/contracts/suilend/Move.toml @@ -20,6 +20,9 @@ git = "https://github.com/solendprotocol/liquid-staking.git" subdir = "contracts" rev = "main" +[dependencies.sprungsui] +local = "../sprungsui" + [addresses] sui = "0x2" # suilend = "0x0" diff --git a/contracts/suilend/sources/lending_market.move b/contracts/suilend/sources/lending_market.move index f158afd..18a3462 100644 --- a/contracts/suilend/sources/lending_market.move +++ b/contracts/suilend/sources/lending_market.move @@ -23,7 +23,6 @@ module suilend::lending_market { use suilend::liquidity_mining::{Self}; use sui::package; use sui::sui::SUI; - use suilend::staker::{STAKER}; // === Errors === const EIncorrectVersion: u64 = 1; @@ -726,11 +725,11 @@ module suilend::lending_market { } /* Staker operations */ - public fun init_staker

( + public fun init_staker( lending_market: &mut LendingMarket

, _: &LendingMarketOwnerCap

, sui_reserve_array_index: u64, - treasury_cap: TreasuryCap, + treasury_cap: TreasuryCap, ctx: &mut TxContext ) { assert!(lending_market.version == CURRENT_VERSION, EIncorrectVersion); @@ -738,7 +737,7 @@ module suilend::lending_market { let reserve = vector::borrow_mut(&mut lending_market.reserves, sui_reserve_array_index); assert!(reserve::coin_type(reserve) == type_name::get(), EWrongType); - reserve::init_staker

(reserve, treasury_cap, ctx); + reserve::init_staker(reserve, treasury_cap, ctx); } public fun rebalance_staker

( diff --git a/contracts/suilend/sources/reserve.move b/contracts/suilend/sources/reserve.move index 969a699..591c550 100644 --- a/contracts/suilend/sources/reserve.move +++ b/contracts/suilend/sources/reserve.move @@ -32,8 +32,9 @@ module suilend::reserve { liquidation_bonus }; use suilend::liquidity_mining::{Self, PoolRewardManager}; - use suilend::staker::{Self, Staker, STAKER}; + use suilend::staker::{Self, Staker}; use sui_system::sui_system::{SuiSystemState}; + use sprungsui::sprungsui::SPRUNGSUI; // === Errors === const EPriceStale: u64 = 0; @@ -497,7 +498,7 @@ module suilend::reserve { request.fee } - public fun staker

(reserve: &Reserve

): &Staker { + public fun staker(reserve: &Reserve

): &Staker { dynamic_field::borrow(&reserve.id, StakerKey {}) } @@ -731,12 +732,13 @@ module suilend::reserve { liquidity } - public(package) fun init_staker

( + public(package) fun init_staker( reserve: &mut Reserve

, - treasury_cap: TreasuryCap, + treasury_cap: TreasuryCap, ctx: &mut TxContext ) { assert!(!dynamic_field::exists_(&reserve.id, StakerKey {}), EStakerAlreadyInitialized); + assert!(type_name::get() == type_name::get(), EWrongType); let staker = staker::create_staker(treasury_cap, ctx); dynamic_field::add(&mut reserve.id, StakerKey {}, staker); @@ -754,7 +756,7 @@ module suilend::reserve { ); let sui = balance::withdraw_all(&mut balances.available_amount); - let staker: &mut Staker = dynamic_field::borrow_mut(&mut reserve.id, StakerKey {}); + let staker: &mut Staker = dynamic_field::borrow_mut(&mut reserve.id, StakerKey {}); staker::deposit(staker, sui); staker::rebalance(staker, system_state, ctx); @@ -797,7 +799,7 @@ module suilend::reserve { }; let withdraw_amount = liquidity_request.amount - balance::value(&balances.available_amount); - let staker: &mut Staker = dynamic_field::borrow_mut(&mut reserve.id, StakerKey {}); + let staker: &mut Staker = dynamic_field::borrow_mut(&mut reserve.id, StakerKey {}); let sui = staker::withdraw( staker, withdraw_amount, diff --git a/contracts/suilend/sources/staker.move b/contracts/suilend/sources/staker.move index 13892b7..f13dc4a 100644 --- a/contracts/suilend/sources/staker.move +++ b/contracts/suilend/sources/staker.move @@ -22,58 +22,41 @@ module suilend::staker { const MIN_DEPLOY_AMOUNT: u64 = 1_000_000; // 1 SUI const MIST_PER_SUI: u64 = 1_000_000_000; - public struct STAKER has drop {} - - fun init(otw: STAKER, ctx: &mut TxContext) { - let (treasury, metadata) = coin::create_currency( - otw, - 9, - b"SprungSui", - b"", - b"", - option::none(), - ctx - ); - - transfer::public_freeze_object(metadata); - transfer::public_transfer(treasury, ctx.sender()) - } - - public struct Staker has store { - admin: AdminCap, - liquid_staking_info: LiquidStakingInfo, - lst_balance: Balance, + public struct Staker has store { + admin: AdminCap

, + liquid_staking_info: LiquidStakingInfo

, + lst_balance: Balance

, sui_balance: Balance, liabilities: u64, // how much sui is owed to the reserve } /* Public-View Functions */ - public(package) fun liabilities(staker: &Staker): u64 { + public(package) fun liabilities

(staker: &Staker

): u64 { staker.liabilities } - public(package) fun lst_balance(staker: &Staker): &Balance { + public(package) fun lst_balance

(staker: &Staker

): &Balance

{ &staker.lst_balance } - public(package) fun sui_balance(staker: &Staker): &Balance { + public(package) fun sui_balance

(staker: &Staker

): &Balance { &staker.sui_balance } // this value can be stale if the staker hasn't refreshed the liquid_staking_info - public(package) fun total_sui_supply(staker: &Staker): u64 { + public(package) fun total_sui_supply

(staker: &Staker

): u64 { staker.liquid_staking_info.total_sui_supply() + staker.sui_balance.value() } - public(package) fun liquid_staking_info(staker: &Staker): &LiquidStakingInfo { + public(package) fun liquid_staking_info

(staker: &Staker

): &LiquidStakingInfo

{ &staker.liquid_staking_info } /* Public Mutative Functions */ - public(package) fun create_staker( - treasury_cap: TreasuryCap, + public(package) fun create_staker( + treasury_cap: TreasuryCap

, ctx: &mut TxContext - ): Staker { + ): Staker

{ assert!(coin::total_supply(&treasury_cap) == 0, ETreasuryCapNonZeroSupply); let (admin_cap, liquid_staking_info) = liquid_staking::create_lst( @@ -91,16 +74,16 @@ module suilend::staker { } } - public(package) fun deposit( - staker: &mut Staker, + public(package) fun deposit

( + staker: &mut Staker

, sui: Balance, ) { staker.liabilities = staker.liabilities + sui.value(); staker.sui_balance.join(sui); } - public(package) fun withdraw( - staker: &mut Staker, + public(package) fun withdraw( + staker: &mut Staker

, withdraw_amount: u64, system_state: &mut SuiSystemState, ctx: &mut TxContext @@ -118,8 +101,8 @@ module suilend::staker { sui } - public(package) fun rebalance( - staker: &mut Staker, + public(package) fun rebalance( + staker: &mut Staker

, system_state: &mut SuiSystemState, ctx: &mut TxContext ) { @@ -146,8 +129,8 @@ module suilend::staker { ); } - public(package) fun claim_fees( - staker: &mut Staker, + public(package) fun claim_fees( + staker: &mut Staker

, system_state: &mut SuiSystemState, ctx: &mut TxContext ): Balance { @@ -178,8 +161,8 @@ module suilend::staker { // liquid_staking_info must be refreshed before calling this // this function can unstake slightly more sui than requested due to rounding. - fun unstake_n_sui( - staker: &mut Staker, + fun unstake_n_sui( + staker: &mut Staker

, system_state: &mut SuiSystemState, sui_amount_out: u64, ctx: &mut TxContext diff --git a/contracts/suilend/tests/lending_market_tests.move b/contracts/suilend/tests/lending_market_tests.move index a80a239..872f19f 100644 --- a/contracts/suilend/tests/lending_market_tests.move +++ b/contracts/suilend/tests/lending_market_tests.move @@ -25,7 +25,7 @@ module suilend::lending_market_tests { use suilend::lending_market::{Self, create_lending_market, LendingMarketOwnerCap, LendingMarket}; use suilend::mock_pyth::{PriceState}; use sui::sui::SUI; - use suilend::staker::{STAKER}; + use sprungsui::sprungsui::SPRUNGSUI; public struct LENDING_MARKET has drop {} @@ -1705,8 +1705,8 @@ module suilend::lending_market_tests { }, &mut scenario); clock::set_for_testing(&mut clock, 1 * 1000); - let treasury_cap = coin::create_treasury_cap_for_testing(scenario.ctx()); - lending_market::init_staker( + let treasury_cap = coin::create_treasury_cap_for_testing(scenario.ctx()); + lending_market::init_staker( &mut lending_market, &owner_cap, *bag::borrow(&type_to_index, type_name::get()), @@ -1715,7 +1715,7 @@ module suilend::lending_market_tests { ); let sui_reserve = lending_market::reserve(&lending_market); - let staker = reserve::staker(sui_reserve); + let staker = reserve::staker(sui_reserve); assert!(staker.total_sui_supply() == 0); assert!(staker.liabilities() == 0); @@ -1728,7 +1728,7 @@ module suilend::lending_market_tests { ); let sui_reserve = lending_market::reserve(&lending_market); - let staker = reserve::staker(sui_reserve); + let staker = reserve::staker(sui_reserve); assert!(staker.total_sui_supply() == 100 * MIST_PER_SUI); assert!(staker.liabilities() == 100 * MIST_PER_SUI); @@ -1749,12 +1749,12 @@ module suilend::lending_market_tests { ); let sui_reserve = lending_market::reserve(&lending_market); - let staker = reserve::staker(sui_reserve); + let staker = reserve::staker(sui_reserve); assert!(staker.total_sui_supply() == 200 * MIST_PER_SUI); assert!(staker.liabilities() == 200 * MIST_PER_SUI); let sui_reserve = lending_market::reserve(&lending_market); - let staker = reserve::staker(sui_reserve); + let staker = reserve::staker(sui_reserve); std::debug::print(staker); let liquidity_request = lending_market::redeem_ctokens_and_withdraw_liquidity_request( @@ -1783,7 +1783,7 @@ module suilend::lending_market_tests { assert!(coin::value(&sui) == 100 * MIST_PER_SUI, 0); let sui_reserve = lending_market::reserve(&lending_market); - let staker = reserve::staker(sui_reserve); + let staker = reserve::staker(sui_reserve); assert!(staker.total_sui_supply() == 100 * MIST_PER_SUI); assert!(staker.liabilities() == 100 * MIST_PER_SUI); @@ -1856,8 +1856,8 @@ module suilend::lending_market_tests { }, &mut scenario); clock::set_for_testing(&mut clock, 1 * 1000); - let treasury_cap = coin::create_treasury_cap_for_testing(scenario.ctx()); - lending_market::init_staker( + let treasury_cap = coin::create_treasury_cap_for_testing(scenario.ctx()); + lending_market::init_staker( &mut lending_market, &owner_cap, *bag::borrow(&type_to_index, type_name::get()), @@ -1866,7 +1866,7 @@ module suilend::lending_market_tests { ); let sui_reserve = lending_market::reserve(&lending_market); - let staker = reserve::staker(sui_reserve); + let staker = reserve::staker(sui_reserve); assert!(staker.total_sui_supply() == 0); assert!(staker.liabilities() == 0); @@ -1971,8 +1971,8 @@ module suilend::lending_market_tests { }, &mut scenario); clock::set_for_testing(&mut clock, 1 * 1000); - let treasury_cap = coin::create_treasury_cap_for_testing(scenario.ctx()); - lending_market::init_staker( + let treasury_cap = coin::create_treasury_cap_for_testing(scenario.ctx()); + lending_market::init_staker( &mut lending_market, &owner_cap, *bag::borrow(&type_to_index, type_name::get()), @@ -1981,7 +1981,7 @@ module suilend::lending_market_tests { ); let sui_reserve = lending_market::reserve(&lending_market); - let staker = reserve::staker(sui_reserve); + let staker = reserve::staker(sui_reserve); assert!(staker.total_sui_supply() == 0); assert!(staker.liabilities() == 0); @@ -1995,7 +1995,7 @@ module suilend::lending_market_tests { test_scenario::return_shared(system_state); let sui_reserve = lending_market::reserve(&lending_market); - let staker = reserve::staker(sui_reserve); + let staker = reserve::staker(sui_reserve); assert!(staker.total_sui_supply() == 100 * MIST_PER_SUI); assert!(staker.sui_balance().value() == 0); assert!(staker.liabilities() == 100 * MIST_PER_SUI); @@ -2013,7 +2013,7 @@ module suilend::lending_market_tests { test_scenario::return_shared(system_state); let sui_reserve = lending_market::reserve(&lending_market); - let staker = reserve::staker(sui_reserve); + let staker = reserve::staker(sui_reserve); std::debug::print(&staker.total_sui_supply()); // the extra 50 sui gained has been transferred to the fees balance already assert!(staker.total_sui_supply() == 101 * MIST_PER_SUI); @@ -2039,4 +2039,4 @@ module suilend::lending_market_tests { test_utils::destroy(type_to_index); test_scenario::end(scenario); } -} \ No newline at end of file +} diff --git a/contracts/suilend/tests/staker_tests.move b/contracts/suilend/tests/staker_tests.move index cf8ce77..c6a3c23 100644 --- a/contracts/suilend/tests/staker_tests.move +++ b/contracts/suilend/tests/staker_tests.move @@ -10,7 +10,7 @@ module suilend::staker_tests { }; use sui::balance::{Self}; use sui::coin::{Self}; - use suilend::staker::{create_staker, STAKER}; + use suilend::staker::{create_staker}; use sui_system::sui_system::{SuiSystemState}; use sui::sui::{SUI}; @@ -18,6 +18,8 @@ module suilend::staker_tests { const MIST_PER_SUI: u64 = 1_000_000_000; const SUILEND_VALIDATOR: address = @0xce8e537664ba5d1d5a6a857b17bd142097138706281882be6805e17065ecde89; + public struct STAKER has drop {} + fun setup_sui_system(scenario: &mut Scenario) { test_scenario::next_tx(scenario, SUILEND_VALIDATOR); let validator = create_validator_for_testing(SUILEND_VALIDATOR, 100, test_scenario::ctx(scenario)); @@ -120,4 +122,4 @@ module suilend::staker_tests { test_scenario::end(scenario); } -} \ No newline at end of file +}