From 6e3cbe543048c96259147914da9504fd5f09d313 Mon Sep 17 00:00:00 2001
From: 0xripleys <0xripleys@solend.fi>
Date: Wed, 27 Nov 2024 16:53:03 -0500
Subject: [PATCH] optionally unstake when claiming sui spread fees
---
contracts/suilend/sources/lending_market.move | 7 +-
contracts/suilend/sources/reserve.move | 22 ++-
.../suilend/tests/lending_market_tests.move | 36 +++--
contracts/suilend/tests/reserve_tests.move | 146 +++++++++++++++++-
4 files changed, 183 insertions(+), 28 deletions(-)
diff --git a/contracts/suilend/sources/lending_market.move b/contracts/suilend/sources/lending_market.move
index 1dfacd0..bb96f62 100644
--- a/contracts/suilend/sources/lending_market.move
+++ b/contracts/suilend/sources/lending_market.move
@@ -809,7 +809,7 @@ module suilend::lending_market {
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 ===
@@ -1124,14 +1124,15 @@ module suilend::lending_market {
entry fun claim_fees
(
lending_market: &mut LendingMarket
,
reserve_array_index: u64,
- ctx: &mut TxContext,
+ system_state: &mut SuiSystemState,
+ ctx: &mut TxContext
) {
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);
- let (mut ctoken_fees, mut fees) = reserve::claim_fees(reserve);
+ let (mut ctoken_fees, mut fees) = reserve::claim_fees
(reserve, system_state, ctx);
let total_ctoken_fees = balance::value(&ctoken_fees);
let total_fees = balance::value(&fees);
diff --git a/contracts/suilend/sources/reserve.move b/contracts/suilend/sources/reserve.move
index 0147c91..421e81d 100644
--- a/contracts/suilend/sources/reserve.move
+++ b/contracts/suilend/sources/reserve.move
@@ -622,7 +622,11 @@ module suilend::reserve {
});
}
- public(package) fun claim_fees
(reserve: &mut Reserve
): (Balance>, Balance) {
+ public(package) fun claim_fees(
+ reserve: &mut Reserve
,
+ system_state: &mut SuiSystemState,
+ ctx: &mut TxContext
+ ): (Balance>, Balance) {
let balances: &mut Balances = dynamic_field::borrow_mut(&mut reserve.id, BalanceKey {});
let mut fees = balance::withdraw_all(&mut balances.fees);
let ctoken_fees = balance::withdraw_all(&mut balances.ctoken_fees);
@@ -634,7 +638,15 @@ module suilend::reserve {
decimal::from(reserve.available_amount - MIN_AVAILABLE_AMOUNT)
));
- let spread_fees = balance::split(&mut balances.available_amount, claimable_spread_fees);
+ let spread_fees = {
+ let liquidity_request = LiquidityRequest
{ amount: claimable_spread_fees, fee: 0 };
+
+ if (type_name::get() == type_name::get()) {
+ unstake_sui_from_staker(reserve, &liquidity_request, system_state, ctx);
+ };
+
+ fulfill_liquidity_request(reserve, liquidity_request)
+ };
reserve.unclaimed_spread_fees = sub(
reserve.unclaimed_spread_fees,
@@ -783,13 +795,13 @@ module suilend::reserve {
};
}
- public(package) fun unstake_sui_from_staker(
+ public(package) fun unstake_sui_from_staker
(
reserve: &mut Reserve
,
- liquidity_request: &LiquidityRequest
,
+ liquidity_request: &LiquidityRequest
,
system_state: &mut SuiSystemState,
ctx: &mut TxContext
) {
- assert!(reserve.coin_type == type_name::get(), EWrongType);
+ assert!(reserve.coin_type == type_name::get() && type_name::get() == type_name::get(), EWrongType);
if (!dynamic_field::exists_(&reserve.id, StakerKey {})) {
return
};
diff --git a/contracts/suilend/tests/lending_market_tests.move b/contracts/suilend/tests/lending_market_tests.move
index aed6fc5..f728407 100644
--- a/contracts/suilend/tests/lending_market_tests.move
+++ b/contracts/suilend/tests/lending_market_tests.move
@@ -440,6 +440,7 @@ module suilend::lending_market_tests {
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(
@@ -601,17 +602,13 @@ module suilend::lending_market_tests {
let sui_reserve = lending_market::reserve(&lending_market);
assert!(reserve::borrowed_amount(sui_reserve) == decimal::from(0), 0);
- let obligation = lending_market::obligation(
- &lending_market,
- lending_market::obligation_id(&obligation_owner_cap),
- );
- assert!(
- obligation::borrowed_amount(obligation) == decimal::from(0),
- 0,
- );
+ let obligation = lending_market::obligation(&lending_market, lending_market::obligation_id(&obligation_owner_cap));
+ assert!(obligation::borrowed_amount(obligation) == decimal::from(0), 0);
test_scenario::next_tx(&mut scenario, owner);
+ let mut system_state = test_scenario::take_shared(&mut scenario);
+
lending_market::set_fee_receivers(
&owner_cap,
&mut lending_market,
@@ -622,8 +619,17 @@ module suilend::lending_market_tests {
lending_market::claim_fees(
&mut lending_market,
*bag::borrow(&type_to_index, type_name::get()),
+ &mut system_state,
test_scenario::ctx(&mut scenario),
);
+ lending_market::claim_fees(
+ &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);
test_scenario::next_tx(&mut scenario, owner);
@@ -793,6 +799,8 @@ module suilend::lending_market_tests {
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(
@@ -979,13 +987,16 @@ module suilend::lending_market_tests {
// claim fees
test_scenario::next_tx(&mut scenario, owner);
+ let mut system_state = test_scenario::take_shared(&mut scenario);
lending_market::claim_fees(
&mut lending_market,
*bag::borrow(&type_to_index, type_name::get()),
- test_scenario::ctx(&mut scenario),
+ &mut system_state,
+ test_scenario::ctx(&mut scenario)
);
-
+ test_scenario::return_shared(system_state);
test_scenario::next_tx(&mut scenario, owner);
+
let ctoken_fees: Coin> = test_scenario::take_from_address(
&scenario,
lending_market::fee_receiver(&lending_market),
@@ -2187,7 +2198,6 @@ module suilend::lending_market_tests {
&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);
@@ -2199,9 +2209,11 @@ module suilend::lending_market_tests {
lending_market::claim_fees(
&mut lending_market,
*bag::borrow(&type_to_index, type_name::get()),
- test_scenario::ctx(&mut scenario),
+ &mut system_state,
+ test_scenario::ctx(&mut scenario)
);
+ test_scenario::return_shared(system_state);
test_scenario::next_tx(&mut scenario, owner);
let fees: Coin = test_scenario::take_from_address(
diff --git a/contracts/suilend/tests/reserve_tests.move b/contracts/suilend/tests/reserve_tests.move
index 1e1da62..2a51d64 100644
--- a/contracts/suilend/tests/reserve_tests.move
+++ b/contracts/suilend/tests/reserve_tests.move
@@ -1,8 +1,24 @@
module suilend::reserve_tests {
- use sui::balance;
- use sui::clock;
- use sui::test_scenario;
- use suilend::decimal::{Self, add, sub};
+ use sui::sui::{SUI};
+ use sui::balance::{Self, Balance, Supply};
+ use sprungsui::sprungsui::SPRUNGSUI;
+ use sui::coin::{Self};
+ use suilend::decimal::{Decimal, Self, add, sub, mul, div, eq, floor, pow, le, ceil, min, max, saturating_sub};
+ use pyth::price_identifier::{PriceIdentifier};
+ use suilend::reserve_config::{
+ Self,
+ ReserveConfig,
+ calculate_apr,
+ calculate_supply_apr,
+ deposit_limit,
+ deposit_limit_usd,
+ borrow_limit,
+ borrow_limit_usd,
+ borrow_fee,
+ protocol_liquidation_fee,
+ spread_fee,
+ liquidation_bonus
+ };
use suilend::reserve::{
Self,
create_for_testing,
@@ -14,7 +30,10 @@ module suilend::reserve_tests {
repay_liquidity,
Balances
};
- use suilend::reserve_config;
+ use sui::clock::{Self};
+ use suilend::liquidity_mining::{Self, PoolRewardManager};
+ use sui_system::sui_system::{SuiSystemState};
+ use sui::test_scenario::{Self, Scenario};
#[test_only]
public struct TEST_LM {}
@@ -192,7 +211,7 @@ module suilend::reserve_tests {
let owner = @0x26;
let mut scenario = test_scenario::begin(owner);
-
+ setup_sui_system(&mut scenario);
let mut reserve = create_for_testing(
{
let config = default_reserve_config();
@@ -237,7 +256,10 @@ module suilend::reserve_tests {
assert!(balance::value(balances.available_amount()) == available_amount_old - 404, 0);
assert!(balance::value(balances.fees()) == 4, 0);
- let (ctoken_fees, fees) = claim_fees(&mut reserve);
+ let mut system_state = test_scenario::take_shared(&mut scenario);
+ let (ctoken_fees, fees) = claim_fees(&mut reserve, &mut system_state, test_scenario::ctx(&mut scenario));
+ test_scenario::return_shared(system_state);
+
assert!(balance::value(&fees) == 4, 0);
assert!(balance::value(&ctoken_fees) == 0, 0);
@@ -345,6 +367,7 @@ module suilend::reserve_tests {
let owner = @0x26;
let mut scenario = test_scenario::begin(owner);
+ setup_sui_system(&mut scenario);
let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario));
let mut reserve = create_for_testing(
@@ -398,7 +421,9 @@ module suilend::reserve_tests {
let old_available_amount = reserve.available_amount();
let old_unclaimed_spread_fees = reserve.unclaimed_spread_fees();
- let (ctoken_fees, fees) = claim_fees(&mut reserve);
+ let mut system_state = test_scenario::take_shared(&mut scenario);
+ let (ctoken_fees, fees) = claim_fees(&mut reserve, &mut system_state, test_scenario::ctx(&mut scenario));
+ test_scenario::return_shared(system_state);
// 0.5% interest a second with 50% take rate => 0.25% fee on 50 USDC = 0.125 USDC
assert!(balance::value(&fees) == 125_000, 0);
@@ -423,6 +448,111 @@ module suilend::reserve_tests {
test_scenario::end(scenario);
}
+ 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]
+ fun test_claim_fees_with_staker() {
+ use sui::test_scenario::{Self};
+ use suilend::reserve_config::{default_reserve_config};
+
+ let owner = @0x26;
+ let mut scenario = test_scenario::begin(owner);
+ setup_sui_system(&mut scenario);
+
+ let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario));
+
+ let mut reserve = create_for_testing(
+ {
+ let config = default_reserve_config();
+ let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario));
+ reserve_config::set_spread_fee_bps(&mut builder, 5000);
+ reserve_config::set_interest_rate_utils(&mut builder, {
+ let mut v = vector::empty();
+ vector::push_back(&mut v, 0);
+ vector::push_back(&mut v, 100);
+ v
+ });
+ reserve_config::set_interest_rate_aprs(&mut builder, {
+ let mut v = vector::empty();
+ vector::push_back(&mut v, 0);
+ vector::push_back(&mut v, 3153600000);
+ v
+ });
+
+ sui::test_utils::destroy(config);
+
+ reserve_config::build(builder, test_scenario::ctx(&mut scenario))
+ },
+ 0,
+ 9,
+ decimal::from(1),
+ 0,
+ 0,
+ 0,
+ decimal::from(0),
+ decimal::from(1),
+ 0,
+ test_scenario::ctx(&mut scenario)
+ );
+
+ let ctokens = deposit_liquidity_and_mint_ctokens(
+ &mut reserve,
+ balance::create_for_testing(100 * 1_000_000_000)
+ );
+
+ let liquidity_request = borrow_liquidity(&mut reserve, 50 * 1_000_000_000);
+ let tokens = reserve::fulfill_liquidity_request(&mut reserve, liquidity_request);
+
+ clock::set_for_testing(&mut clock, 1000);
+ compound_interest(&mut reserve, &clock);
+
+ let old_available_amount = reserve.available_amount();
+ let old_unclaimed_spread_fees = reserve.unclaimed_spread_fees();
+
+ let mut system_state = test_scenario::take_shared(&scenario);
+ let treasury_cap = coin::create_treasury_cap_for_testing(scenario.ctx());
+ reserve::init_staker(&mut reserve, treasury_cap, test_scenario::ctx(&mut scenario));
+ reserve::rebalance_staker(&mut reserve, &mut system_state, test_scenario::ctx(&mut scenario));
+
+ let (ctoken_fees, fees) = claim_fees(&mut reserve, &mut system_state, test_scenario::ctx(&mut scenario));
+
+ // 0.5% interest a second with 50% take rate => 0.25% fee on 50 SUI = 0.125 SUI
+ assert!(balance::value(&fees) == 125_000_000, 0);
+ assert!(balance::value(&ctoken_fees) == 0, 0);
+
+ assert!(reserve.available_amount() == old_available_amount - 125_000_000, 0);
+ assert!(reserve.unclaimed_spread_fees() == sub(old_unclaimed_spread_fees, decimal::from(125_000_000)), 0);
+
+ let balances: &Balances = reserve::balances(&reserve);
+ assert!(balance::value(balances.available_amount()) == 0, 0); // all the sui has been staked
+
+ test_scenario::return_shared(system_state);
+
+ sui::test_utils::destroy(clock);
+ sui::test_utils::destroy(ctoken_fees);
+ sui::test_utils::destroy(fees);
+ sui::test_utils::destroy(reserve);
+ sui::test_utils::destroy(tokens);
+ sui::test_utils::destroy(ctokens);
+
+ test_scenario::end(scenario);
+ }
+
#[test]
fun test_repay_happy() {
use suilend::test_usdc::{TEST_USDC};