Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

optionally unstake when claiming sui spread fees #43

Merged
merged 1 commit into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions contracts/suilend/sources/lending_market.move
Original file line number Diff line number Diff line change
Expand Up @@ -809,7 +809,7 @@ module suilend::lending_market {
return
};

reserve::unstake_sui_from_staker<P>(reserve, liquidity_request, system_state, ctx);
reserve::unstake_sui_from_staker<P, SUI>(reserve, liquidity_request, system_state, ctx);
}

// === Public-View Functions ===
Expand Down Expand Up @@ -1124,14 +1124,15 @@ module suilend::lending_market {
entry fun claim_fees<P, T>(
lending_market: &mut LendingMarket<P>,
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<T>(), EWrongType);

let (mut ctoken_fees, mut fees) = reserve::claim_fees<P, T>(reserve);
let (mut ctoken_fees, mut fees) = reserve::claim_fees<P, T>(reserve, system_state, ctx);
let total_ctoken_fees = balance::value(&ctoken_fees);
let total_fees = balance::value(&fees);

Expand Down
22 changes: 17 additions & 5 deletions contracts/suilend/sources/reserve.move
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,11 @@ module suilend::reserve {
});
}

public(package) fun claim_fees<P, T>(reserve: &mut Reserve<P>): (Balance<CToken<P, T>>, Balance<T>) {
public(package) fun claim_fees<P, T>(
reserve: &mut Reserve<P>,
system_state: &mut SuiSystemState,
ctx: &mut TxContext
): (Balance<CToken<P, T>>, Balance<T>) {
let balances: &mut Balances<P, T> = 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);
Expand All @@ -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<P, T> { amount: claimable_spread_fees, fee: 0 };

if (type_name::get<T>() == type_name::get<SUI>()) {
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,
Expand Down Expand Up @@ -783,13 +795,13 @@ module suilend::reserve {
};
}

public(package) fun unstake_sui_from_staker<P>(
public(package) fun unstake_sui_from_staker<P, T>(
reserve: &mut Reserve<P>,
liquidity_request: &LiquidityRequest<P, SUI>,
liquidity_request: &LiquidityRequest<P, T>,
system_state: &mut SuiSystemState,
ctx: &mut TxContext
) {
assert!(reserve.coin_type == type_name::get<SUI>(), EWrongType);
assert!(reserve.coin_type == type_name::get<SUI>() && type_name::get<T>() == type_name::get<SUI>(), EWrongType);
if (!dynamic_field::exists_(&reserve.id, StakerKey {})) {
return
};
Expand Down
36 changes: 24 additions & 12 deletions contracts/suilend/tests/lending_market_tests.move
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -601,17 +602,13 @@ module suilend::lending_market_tests {
let sui_reserve = lending_market::reserve<LENDING_MARKET, TEST_SUI>(&lending_market);
assert!(reserve::borrowed_amount<LENDING_MARKET>(sui_reserve) == decimal::from(0), 0);

let obligation = lending_market::obligation(
&lending_market,
lending_market::obligation_id(&obligation_owner_cap),
);
assert!(
obligation::borrowed_amount<LENDING_MARKET, TEST_SUI>(obligation) == decimal::from(0),
0,
);
let obligation = lending_market::obligation(&lending_market, lending_market::obligation_id(&obligation_owner_cap));
assert!(obligation::borrowed_amount<LENDING_MARKET, TEST_SUI>(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<LENDING_MARKET>(
&owner_cap,
&mut lending_market,
Expand All @@ -622,8 +619,17 @@ module suilend::lending_market_tests {
lending_market::claim_fees<LENDING_MARKET, TEST_SUI>(
&mut lending_market,
*bag::borrow(&type_to_index, type_name::get<TEST_SUI>()),
&mut system_state,
test_scenario::ctx(&mut scenario),
);
lending_market::claim_fees<LENDING_MARKET, TEST_SUI>(
&mut lending_market,
*bag::borrow(&type_to_index, type_name::get<TEST_SUI>()),
&mut system_state,
test_scenario::ctx(&mut scenario)
);

test_scenario::return_shared(system_state);

test_scenario::next_tx(&mut scenario, owner);

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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<SuiSystemState>(&mut scenario);
lending_market::claim_fees<LENDING_MARKET, TEST_USDC>(
&mut lending_market,
*bag::borrow(&type_to_index, type_name::get<TEST_USDC>()),
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<CToken<LENDING_MARKET, TEST_USDC>> = test_scenario::take_from_address(
&scenario,
lending_market::fee_receiver(&lending_market),
Expand Down Expand Up @@ -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, SUI>(&lending_market);
let staker = reserve::staker<LENDING_MARKET, SPRUNGSUI>(sui_reserve);
Expand All @@ -2199,9 +2209,11 @@ module suilend::lending_market_tests {
lending_market::claim_fees<LENDING_MARKET, SUI>(
&mut lending_market,
*bag::borrow(&type_to_index, type_name::get<SUI>()),
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<SUI> = test_scenario::take_from_address(
Expand Down
146 changes: 138 additions & 8 deletions contracts/suilend/tests/reserve_tests.move
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 {}
Expand Down Expand Up @@ -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<TEST_LM, TEST_USDC>(
{
let config = default_reserve_config();
Expand Down Expand Up @@ -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<TEST_LM, TEST_USDC>(&mut reserve);
let mut system_state = test_scenario::take_shared<SuiSystemState>(&mut scenario);
let (ctoken_fees, fees) = claim_fees<TEST_LM, TEST_USDC>(&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);

Expand Down Expand Up @@ -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<TEST_LM, TEST_USDC>(
Expand Down Expand Up @@ -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<TEST_LM, TEST_USDC>(&mut reserve);
let mut system_state = test_scenario::take_shared<SuiSystemState>(&mut scenario);
let (ctoken_fees, fees) = claim_fees<TEST_LM, TEST_USDC>(&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);
Expand All @@ -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<TEST_LM, SUI>(
{
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<TEST_LM, SUI>(
&mut reserve,
balance::create_for_testing(100 * 1_000_000_000)
);

let liquidity_request = borrow_liquidity<TEST_LM, SUI>(&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<SuiSystemState>(&scenario);
let treasury_cap = coin::create_treasury_cap_for_testing<SPRUNGSUI>(scenario.ctx());
reserve::init_staker<TEST_LM, SPRUNGSUI>(&mut reserve, treasury_cap, test_scenario::ctx(&mut scenario));
reserve::rebalance_staker<TEST_LM>(&mut reserve, &mut system_state, test_scenario::ctx(&mut scenario));

let (ctoken_fees, fees) = claim_fees<TEST_LM, SUI>(&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<TEST_LM, SUI> = 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};
Expand Down