Skip to content

Commit 6e3cbe5

Browse files
committed
optionally unstake when claiming sui spread fees
1 parent ec92f21 commit 6e3cbe5

File tree

4 files changed

+183
-28
lines changed

4 files changed

+183
-28
lines changed

contracts/suilend/sources/lending_market.move

+4-3
Original file line numberDiff line numberDiff line change
@@ -809,7 +809,7 @@ module suilend::lending_market {
809809
return
810810
};
811811

812-
reserve::unstake_sui_from_staker<P>(reserve, liquidity_request, system_state, ctx);
812+
reserve::unstake_sui_from_staker<P, SUI>(reserve, liquidity_request, system_state, ctx);
813813
}
814814

815815
// === Public-View Functions ===
@@ -1124,14 +1124,15 @@ module suilend::lending_market {
11241124
entry fun claim_fees<P, T>(
11251125
lending_market: &mut LendingMarket<P>,
11261126
reserve_array_index: u64,
1127-
ctx: &mut TxContext,
1127+
system_state: &mut SuiSystemState,
1128+
ctx: &mut TxContext
11281129
) {
11291130
assert!(lending_market.version == CURRENT_VERSION, EIncorrectVersion);
11301131

11311132
let reserve = vector::borrow_mut(&mut lending_market.reserves, reserve_array_index);
11321133
assert!(reserve::coin_type(reserve) == type_name::get<T>(), EWrongType);
11331134

1134-
let (mut ctoken_fees, mut fees) = reserve::claim_fees<P, T>(reserve);
1135+
let (mut ctoken_fees, mut fees) = reserve::claim_fees<P, T>(reserve, system_state, ctx);
11351136
let total_ctoken_fees = balance::value(&ctoken_fees);
11361137
let total_fees = balance::value(&fees);
11371138

contracts/suilend/sources/reserve.move

+17-5
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,11 @@ module suilend::reserve {
622622
});
623623
}
624624

625-
public(package) fun claim_fees<P, T>(reserve: &mut Reserve<P>): (Balance<CToken<P, T>>, Balance<T>) {
625+
public(package) fun claim_fees<P, T>(
626+
reserve: &mut Reserve<P>,
627+
system_state: &mut SuiSystemState,
628+
ctx: &mut TxContext
629+
): (Balance<CToken<P, T>>, Balance<T>) {
626630
let balances: &mut Balances<P, T> = dynamic_field::borrow_mut(&mut reserve.id, BalanceKey {});
627631
let mut fees = balance::withdraw_all(&mut balances.fees);
628632
let ctoken_fees = balance::withdraw_all(&mut balances.ctoken_fees);
@@ -634,7 +638,15 @@ module suilend::reserve {
634638
decimal::from(reserve.available_amount - MIN_AVAILABLE_AMOUNT)
635639
));
636640

637-
let spread_fees = balance::split(&mut balances.available_amount, claimable_spread_fees);
641+
let spread_fees = {
642+
let liquidity_request = LiquidityRequest<P, T> { amount: claimable_spread_fees, fee: 0 };
643+
644+
if (type_name::get<T>() == type_name::get<SUI>()) {
645+
unstake_sui_from_staker(reserve, &liquidity_request, system_state, ctx);
646+
};
647+
648+
fulfill_liquidity_request(reserve, liquidity_request)
649+
};
638650

639651
reserve.unclaimed_spread_fees = sub(
640652
reserve.unclaimed_spread_fees,
@@ -783,13 +795,13 @@ module suilend::reserve {
783795
};
784796
}
785797

786-
public(package) fun unstake_sui_from_staker<P>(
798+
public(package) fun unstake_sui_from_staker<P, T>(
787799
reserve: &mut Reserve<P>,
788-
liquidity_request: &LiquidityRequest<P, SUI>,
800+
liquidity_request: &LiquidityRequest<P, T>,
789801
system_state: &mut SuiSystemState,
790802
ctx: &mut TxContext
791803
) {
792-
assert!(reserve.coin_type == type_name::get<SUI>(), EWrongType);
804+
assert!(reserve.coin_type == type_name::get<SUI>() && type_name::get<T>() == type_name::get<SUI>(), EWrongType);
793805
if (!dynamic_field::exists_(&reserve.id, StakerKey {})) {
794806
return
795807
};

contracts/suilend/tests/lending_market_tests.move

+24-12
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,7 @@ module suilend::lending_market_tests {
440440

441441
let owner = @0x26;
442442
let mut scenario = test_scenario::begin(owner);
443+
setup_sui_system(&mut scenario);
443444
let State { mut clock, owner_cap, mut lending_market, mut prices, type_to_index } = setup({
444445
let mut bag = bag::new(test_scenario::ctx(&mut scenario));
445446
bag::add(
@@ -601,17 +602,13 @@ module suilend::lending_market_tests {
601602
let sui_reserve = lending_market::reserve<LENDING_MARKET, TEST_SUI>(&lending_market);
602603
assert!(reserve::borrowed_amount<LENDING_MARKET>(sui_reserve) == decimal::from(0), 0);
603604

604-
let obligation = lending_market::obligation(
605-
&lending_market,
606-
lending_market::obligation_id(&obligation_owner_cap),
607-
);
608-
assert!(
609-
obligation::borrowed_amount<LENDING_MARKET, TEST_SUI>(obligation) == decimal::from(0),
610-
0,
611-
);
605+
let obligation = lending_market::obligation(&lending_market, lending_market::obligation_id(&obligation_owner_cap));
606+
assert!(obligation::borrowed_amount<LENDING_MARKET, TEST_SUI>(obligation) == decimal::from(0), 0);
612607

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

610+
let mut system_state = test_scenario::take_shared(&mut scenario);
611+
615612
lending_market::set_fee_receivers<LENDING_MARKET>(
616613
&owner_cap,
617614
&mut lending_market,
@@ -622,8 +619,17 @@ module suilend::lending_market_tests {
622619
lending_market::claim_fees<LENDING_MARKET, TEST_SUI>(
623620
&mut lending_market,
624621
*bag::borrow(&type_to_index, type_name::get<TEST_SUI>()),
622+
&mut system_state,
625623
test_scenario::ctx(&mut scenario),
626624
);
625+
lending_market::claim_fees<LENDING_MARKET, TEST_SUI>(
626+
&mut lending_market,
627+
*bag::borrow(&type_to_index, type_name::get<TEST_SUI>()),
628+
&mut system_state,
629+
test_scenario::ctx(&mut scenario)
630+
);
631+
632+
test_scenario::return_shared(system_state);
627633

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

@@ -793,6 +799,8 @@ module suilend::lending_market_tests {
793799

794800
let owner = @0x26;
795801
let mut scenario = test_scenario::begin(owner);
802+
setup_sui_system(&mut scenario);
803+
796804
let State { mut clock, owner_cap, mut lending_market, mut prices, type_to_index } = setup({
797805
let mut bag = bag::new(test_scenario::ctx(&mut scenario));
798806
bag::add(
@@ -979,13 +987,16 @@ module suilend::lending_market_tests {
979987

980988
// claim fees
981989
test_scenario::next_tx(&mut scenario, owner);
990+
let mut system_state = test_scenario::take_shared<SuiSystemState>(&mut scenario);
982991
lending_market::claim_fees<LENDING_MARKET, TEST_USDC>(
983992
&mut lending_market,
984993
*bag::borrow(&type_to_index, type_name::get<TEST_USDC>()),
985-
test_scenario::ctx(&mut scenario),
994+
&mut system_state,
995+
test_scenario::ctx(&mut scenario)
986996
);
987-
997+
test_scenario::return_shared(system_state);
988998
test_scenario::next_tx(&mut scenario, owner);
999+
9891000
let ctoken_fees: Coin<CToken<LENDING_MARKET, TEST_USDC>> = test_scenario::take_from_address(
9901001
&scenario,
9911002
lending_market::fee_receiver(&lending_market),
@@ -2187,7 +2198,6 @@ module suilend::lending_market_tests {
21872198
&mut system_state,
21882199
test_scenario::ctx(&mut scenario),
21892200
);
2190-
test_scenario::return_shared(system_state);
21912201

21922202
let sui_reserve = lending_market::reserve<LENDING_MARKET, SUI>(&lending_market);
21932203
let staker = reserve::staker<LENDING_MARKET, SPRUNGSUI>(sui_reserve);
@@ -2199,9 +2209,11 @@ module suilend::lending_market_tests {
21992209
lending_market::claim_fees<LENDING_MARKET, SUI>(
22002210
&mut lending_market,
22012211
*bag::borrow(&type_to_index, type_name::get<SUI>()),
2202-
test_scenario::ctx(&mut scenario),
2212+
&mut system_state,
2213+
test_scenario::ctx(&mut scenario)
22032214
);
22042215

2216+
test_scenario::return_shared(system_state);
22052217
test_scenario::next_tx(&mut scenario, owner);
22062218

22072219
let fees: Coin<SUI> = test_scenario::take_from_address(

contracts/suilend/tests/reserve_tests.move

+138-8
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,24 @@
11
module suilend::reserve_tests {
2-
use sui::balance;
3-
use sui::clock;
4-
use sui::test_scenario;
5-
use suilend::decimal::{Self, add, sub};
2+
use sui::sui::{SUI};
3+
use sui::balance::{Self, Balance, Supply};
4+
use sprungsui::sprungsui::SPRUNGSUI;
5+
use sui::coin::{Self};
6+
use suilend::decimal::{Decimal, Self, add, sub, mul, div, eq, floor, pow, le, ceil, min, max, saturating_sub};
7+
use pyth::price_identifier::{PriceIdentifier};
8+
use suilend::reserve_config::{
9+
Self,
10+
ReserveConfig,
11+
calculate_apr,
12+
calculate_supply_apr,
13+
deposit_limit,
14+
deposit_limit_usd,
15+
borrow_limit,
16+
borrow_limit_usd,
17+
borrow_fee,
18+
protocol_liquidation_fee,
19+
spread_fee,
20+
liquidation_bonus
21+
};
622
use suilend::reserve::{
723
Self,
824
create_for_testing,
@@ -14,7 +30,10 @@ module suilend::reserve_tests {
1430
repay_liquidity,
1531
Balances
1632
};
17-
use suilend::reserve_config;
33+
use sui::clock::{Self};
34+
use suilend::liquidity_mining::{Self, PoolRewardManager};
35+
use sui_system::sui_system::{SuiSystemState};
36+
use sui::test_scenario::{Self, Scenario};
1837

1938
#[test_only]
2039
public struct TEST_LM {}
@@ -192,7 +211,7 @@ module suilend::reserve_tests {
192211

193212
let owner = @0x26;
194213
let mut scenario = test_scenario::begin(owner);
195-
214+
setup_sui_system(&mut scenario);
196215
let mut reserve = create_for_testing<TEST_LM, TEST_USDC>(
197216
{
198217
let config = default_reserve_config();
@@ -237,7 +256,10 @@ module suilend::reserve_tests {
237256
assert!(balance::value(balances.available_amount()) == available_amount_old - 404, 0);
238257
assert!(balance::value(balances.fees()) == 4, 0);
239258

240-
let (ctoken_fees, fees) = claim_fees<TEST_LM, TEST_USDC>(&mut reserve);
259+
let mut system_state = test_scenario::take_shared<SuiSystemState>(&mut scenario);
260+
let (ctoken_fees, fees) = claim_fees<TEST_LM, TEST_USDC>(&mut reserve, &mut system_state, test_scenario::ctx(&mut scenario));
261+
test_scenario::return_shared(system_state);
262+
241263
assert!(balance::value(&fees) == 4, 0);
242264
assert!(balance::value(&ctoken_fees) == 0, 0);
243265

@@ -345,6 +367,7 @@ module suilend::reserve_tests {
345367

346368
let owner = @0x26;
347369
let mut scenario = test_scenario::begin(owner);
370+
setup_sui_system(&mut scenario);
348371
let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario));
349372

350373
let mut reserve = create_for_testing<TEST_LM, TEST_USDC>(
@@ -398,7 +421,9 @@ module suilend::reserve_tests {
398421
let old_available_amount = reserve.available_amount();
399422
let old_unclaimed_spread_fees = reserve.unclaimed_spread_fees();
400423

401-
let (ctoken_fees, fees) = claim_fees<TEST_LM, TEST_USDC>(&mut reserve);
424+
let mut system_state = test_scenario::take_shared<SuiSystemState>(&mut scenario);
425+
let (ctoken_fees, fees) = claim_fees<TEST_LM, TEST_USDC>(&mut reserve, &mut system_state, test_scenario::ctx(&mut scenario));
426+
test_scenario::return_shared(system_state);
402427

403428
// 0.5% interest a second with 50% take rate => 0.25% fee on 50 USDC = 0.125 USDC
404429
assert!(balance::value(&fees) == 125_000, 0);
@@ -423,6 +448,111 @@ module suilend::reserve_tests {
423448
test_scenario::end(scenario);
424449
}
425450

451+
use sui_system::governance_test_utils::{
452+
advance_epoch_with_reward_amounts,
453+
create_validator_for_testing,
454+
create_sui_system_state_for_testing,
455+
};
456+
457+
458+
const SUILEND_VALIDATOR: address = @0xce8e537664ba5d1d5a6a857b17bd142097138706281882be6805e17065ecde89;
459+
460+
fun setup_sui_system(scenario: &mut Scenario) {
461+
test_scenario::next_tx(scenario, SUILEND_VALIDATOR);
462+
let validator = create_validator_for_testing(SUILEND_VALIDATOR, 100, test_scenario::ctx(scenario));
463+
create_sui_system_state_for_testing(vector[validator], 0, 0, test_scenario::ctx(scenario));
464+
465+
advance_epoch_with_reward_amounts(0, 0, scenario);
466+
}
467+
468+
#[test]
469+
fun test_claim_fees_with_staker() {
470+
use sui::test_scenario::{Self};
471+
use suilend::reserve_config::{default_reserve_config};
472+
473+
let owner = @0x26;
474+
let mut scenario = test_scenario::begin(owner);
475+
setup_sui_system(&mut scenario);
476+
477+
let mut clock = clock::create_for_testing(test_scenario::ctx(&mut scenario));
478+
479+
let mut reserve = create_for_testing<TEST_LM, SUI>(
480+
{
481+
let config = default_reserve_config();
482+
let mut builder = reserve_config::from(&config, test_scenario::ctx(&mut scenario));
483+
reserve_config::set_spread_fee_bps(&mut builder, 5000);
484+
reserve_config::set_interest_rate_utils(&mut builder, {
485+
let mut v = vector::empty();
486+
vector::push_back(&mut v, 0);
487+
vector::push_back(&mut v, 100);
488+
v
489+
});
490+
reserve_config::set_interest_rate_aprs(&mut builder, {
491+
let mut v = vector::empty();
492+
vector::push_back(&mut v, 0);
493+
vector::push_back(&mut v, 3153600000);
494+
v
495+
});
496+
497+
sui::test_utils::destroy(config);
498+
499+
reserve_config::build(builder, test_scenario::ctx(&mut scenario))
500+
},
501+
0,
502+
9,
503+
decimal::from(1),
504+
0,
505+
0,
506+
0,
507+
decimal::from(0),
508+
decimal::from(1),
509+
0,
510+
test_scenario::ctx(&mut scenario)
511+
);
512+
513+
let ctokens = deposit_liquidity_and_mint_ctokens<TEST_LM, SUI>(
514+
&mut reserve,
515+
balance::create_for_testing(100 * 1_000_000_000)
516+
);
517+
518+
let liquidity_request = borrow_liquidity<TEST_LM, SUI>(&mut reserve, 50 * 1_000_000_000);
519+
let tokens = reserve::fulfill_liquidity_request(&mut reserve, liquidity_request);
520+
521+
clock::set_for_testing(&mut clock, 1000);
522+
compound_interest(&mut reserve, &clock);
523+
524+
let old_available_amount = reserve.available_amount();
525+
let old_unclaimed_spread_fees = reserve.unclaimed_spread_fees();
526+
527+
let mut system_state = test_scenario::take_shared<SuiSystemState>(&scenario);
528+
let treasury_cap = coin::create_treasury_cap_for_testing<SPRUNGSUI>(scenario.ctx());
529+
reserve::init_staker<TEST_LM, SPRUNGSUI>(&mut reserve, treasury_cap, test_scenario::ctx(&mut scenario));
530+
reserve::rebalance_staker<TEST_LM>(&mut reserve, &mut system_state, test_scenario::ctx(&mut scenario));
531+
532+
let (ctoken_fees, fees) = claim_fees<TEST_LM, SUI>(&mut reserve, &mut system_state, test_scenario::ctx(&mut scenario));
533+
534+
// 0.5% interest a second with 50% take rate => 0.25% fee on 50 SUI = 0.125 SUI
535+
assert!(balance::value(&fees) == 125_000_000, 0);
536+
assert!(balance::value(&ctoken_fees) == 0, 0);
537+
538+
assert!(reserve.available_amount() == old_available_amount - 125_000_000, 0);
539+
assert!(reserve.unclaimed_spread_fees() == sub(old_unclaimed_spread_fees, decimal::from(125_000_000)), 0);
540+
541+
let balances: &Balances<TEST_LM, SUI> = reserve::balances(&reserve);
542+
assert!(balance::value(balances.available_amount()) == 0, 0); // all the sui has been staked
543+
544+
test_scenario::return_shared(system_state);
545+
546+
sui::test_utils::destroy(clock);
547+
sui::test_utils::destroy(ctoken_fees);
548+
sui::test_utils::destroy(fees);
549+
sui::test_utils::destroy(reserve);
550+
sui::test_utils::destroy(tokens);
551+
sui::test_utils::destroy(ctokens);
552+
553+
test_scenario::end(scenario);
554+
}
555+
426556
#[test]
427557
fun test_repay_happy() {
428558
use suilend::test_usdc::{TEST_USDC};

0 commit comments

Comments
 (0)