Skip to content

Commit c034587

Browse files
authored
Configurable Fee receivers (#47)
* team / dao fee split * change claim rewards and deposit function * configurable fee receivers * minor nits + extra testing * version bump
1 parent 2512902 commit c034587

File tree

3 files changed

+107
-12
lines changed

3 files changed

+107
-12
lines changed

contracts/suilend/sources/lending_market.move

+89-9
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ module suilend::lending_market {
2323
use suilend::liquidity_mining::{Self};
2424
use sui::package;
2525
use sui::sui::SUI;
26+
use sui::dynamic_field::{Self};
2627

2728
// === Errors ===
2829
const EIncorrectVersion: u64 = 1;
@@ -32,9 +33,10 @@ module suilend::lending_market {
3233
const ERewardPeriodNotOver: u64 = 5;
3334
const ECannotClaimReward: u64 = 6;
3435
const EInvalidObligationId: u64 = 7;
36+
const EInvalidFeeReceivers: u64 = 8;
3537

3638
// === Constants ===
37-
const CURRENT_VERSION: u64 = 6;
39+
const CURRENT_VERSION: u64 = 7;
3840
const U64_MAX: u64 = 18_446_744_073_709_551_615;
3941

4042
// === One time Witness ===
@@ -54,7 +56,7 @@ module suilend::lending_market {
5456

5557
// window duration is in seconds
5658
rate_limiter: RateLimiter,
57-
fee_receiver: address,
59+
fee_receiver: address, // deprecated
5860

5961
/// unused
6062
bad_debt_usd: Decimal,
@@ -72,6 +74,15 @@ module suilend::lending_market {
7274
obligation_id: ID
7375
}
7476

77+
// === Dynamic Fields ===
78+
public struct FeeReceiversKey has copy, drop, store { }
79+
80+
public struct FeeReceivers has store {
81+
receivers: vector<address>,
82+
weights: vector<u64>,
83+
total_weight: u64,
84+
}
85+
7586
// cTokens redemptions and borrows are rate limited to mitigate exploits. however,
7687
// on a liquidation we don't want to rate limit redemptions because we don't want liquidators to
7788
// get stuck holding cTokens. So the liquidate function issues this exemption
@@ -168,7 +179,7 @@ module suilend::lending_market {
168179
LendingMarketOwnerCap<P>,
169180
LendingMarket<P>
170181
) {
171-
let lending_market = LendingMarket<P> {
182+
let mut lending_market = LendingMarket<P> {
172183
id: object::new(ctx),
173184
version: CURRENT_VERSION,
174185
reserves: vector::empty(),
@@ -178,12 +189,19 @@ module suilend::lending_market {
178189
bad_debt_usd: decimal::from(0),
179190
bad_debt_limit_usd: decimal::from(0),
180191
};
181-
192+
182193
let owner_cap = LendingMarketOwnerCap<P> {
183194
id: object::new(ctx),
184195
lending_market_id: object::id(&lending_market)
185196
};
186197

198+
set_fee_receivers(
199+
&owner_cap,
200+
&mut lending_market,
201+
vector[tx_context::sender(ctx)],
202+
vector[100]
203+
);
204+
187205
(owner_cap, lending_market)
188206
}
189207

@@ -689,8 +707,8 @@ module suilend::lending_market {
689707
);
690708
};
691709

710+
let deposit_reserve = vector::borrow_mut(&mut lending_market.reserves, deposit_reserve_id);
692711
let expected_ctokens = {
693-
let deposit_reserve = vector::borrow(&lending_market.reserves, deposit_reserve_id);
694712
assert!(reserve::coin_type(deposit_reserve) == type_name::get<RewardType>(), EWrongType);
695713

696714
floor(
@@ -702,7 +720,7 @@ module suilend::lending_market {
702720
};
703721

704722
if (expected_ctokens == 0) {
705-
transfer::public_transfer(rewards, lending_market.fee_receiver);
723+
reserve::join_fees<P, RewardType>(deposit_reserve, coin::into_balance(rewards));
706724
}
707725
else {
708726
let ctokens = deposit_liquidity_and_mint_ctokens<P, RewardType>(
@@ -1038,6 +1056,34 @@ module suilend::lending_market {
10381056
lending_market.rate_limiter = rate_limiter::new(config, clock::timestamp_ms(clock) / 1000);
10391057
}
10401058

1059+
public fun set_fee_receivers<P>(
1060+
_: &LendingMarketOwnerCap<P>,
1061+
lending_market: &mut LendingMarket<P>,
1062+
receivers: vector<address>,
1063+
weights: vector<u64>,
1064+
) {
1065+
assert!(lending_market.version == CURRENT_VERSION, EIncorrectVersion);
1066+
1067+
assert!(vector::length(&receivers) == vector::length(&weights), EInvalidFeeReceivers);
1068+
assert!(vector::length(&receivers) > 0, EInvalidFeeReceivers);
1069+
1070+
let total_weight = vector::fold!(weights, 0, |acc, weight| acc + weight);
1071+
assert!(total_weight > 0, EInvalidFeeReceivers);
1072+
1073+
if (dynamic_field::exists_(&lending_market.id, FeeReceiversKey {})) {
1074+
let FeeReceivers { .. } = dynamic_field::remove<FeeReceiversKey, FeeReceivers>(
1075+
&mut lending_market.id,
1076+
FeeReceiversKey {}
1077+
);
1078+
};
1079+
1080+
dynamic_field::add(
1081+
&mut lending_market.id,
1082+
FeeReceiversKey {},
1083+
FeeReceivers { receivers, weights, total_weight }
1084+
);
1085+
}
1086+
10411087
entry fun claim_fees<P, T>(
10421088
lending_market: &mut LendingMarket<P>,
10431089
reserve_array_index: u64,
@@ -1047,10 +1093,44 @@ module suilend::lending_market {
10471093

10481094
let reserve = vector::borrow_mut(&mut lending_market.reserves, reserve_array_index);
10491095
assert!(reserve::coin_type(reserve) == type_name::get<T>(), EWrongType);
1050-
let (ctoken_fees, fees) = reserve::claim_fees<P, T>(reserve);
10511096

1052-
transfer::public_transfer(coin::from_balance(ctoken_fees, ctx), lending_market.fee_receiver);
1053-
transfer::public_transfer(coin::from_balance(fees, ctx), lending_market.fee_receiver);
1097+
let (mut ctoken_fees, mut fees) = reserve::claim_fees<P, T>(reserve);
1098+
let total_ctoken_fees = balance::value(&ctoken_fees);
1099+
let total_fees = balance::value(&fees);
1100+
1101+
let fee_receivers: &FeeReceivers = dynamic_field::borrow(&lending_market.id, FeeReceiversKey {});
1102+
let num_fee_receivers = vector::length(&fee_receivers.weights);
1103+
1104+
num_fee_receivers.do!(|i| {
1105+
let fee_amount = (total_fees as u128) * (fee_receivers.weights[i] as u128) / (fee_receivers.total_weight as u128);
1106+
let fee = if (i == num_fee_receivers - 1) {
1107+
balance::withdraw_all(&mut fees)
1108+
} else {
1109+
balance::split(&mut fees, fee_amount as u64)
1110+
};
1111+
1112+
if (balance::value(&fee) > 0) {
1113+
transfer::public_transfer(coin::from_balance(fee, ctx), fee_receivers.receivers[i]);
1114+
} else {
1115+
balance::destroy_zero(fee);
1116+
};
1117+
1118+
let ctoken_fee_amount = (total_ctoken_fees as u128) * (fee_receivers.weights[i] as u128) / (fee_receivers.total_weight as u128);
1119+
let ctoken_fee = if (i == num_fee_receivers - 1) {
1120+
balance::withdraw_all(&mut ctoken_fees)
1121+
} else {
1122+
balance::split(&mut ctoken_fees, ctoken_fee_amount as u64)
1123+
};
1124+
1125+
if (balance::value(&ctoken_fee) > 0) {
1126+
transfer::public_transfer(coin::from_balance(ctoken_fee, ctx), fee_receivers.receivers[i]);
1127+
} else {
1128+
balance::destroy_zero(ctoken_fee);
1129+
};
1130+
});
1131+
1132+
balance::destroy_zero(fees);
1133+
balance::destroy_zero(ctoken_fees);
10541134
}
10551135

10561136
public fun new_obligation_owner_cap<P>(

contracts/suilend/sources/reserve.move

+6-1
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,11 @@ module suilend::reserve {
535535
(protocol_fee_amount, liquidator_bonus_amount)
536536
}
537537

538+
public(package) fun join_fees<P, T>(reserve: &mut Reserve<P>, fees: Balance<T>) {
539+
let balances: &mut Balances<P, T> = dynamic_field::borrow_mut(&mut reserve.id, BalanceKey {});
540+
balance::join(&mut balances.fees, fees);
541+
}
542+
538543
public(package) fun update_reserve_config<P>(
539544
reserve: &mut Reserve<P>,
540545
config: ReserveConfig,
@@ -910,7 +915,7 @@ module suilend::reserve {
910915
}
911916

912917
// === Private Functions ===
913-
fun log_reserve_data<P>(reserve: &Reserve<P>){
918+
fun log_reserve_data<P>(reserve: &Reserve<P>) {
914919
let available_amount_decimal = decimal::from(reserve.available_amount);
915920
let supply_amount = total_supply(reserve);
916921
let cur_util = calculate_utilization_rate(reserve);

contracts/suilend/tests/lending_market_tests.move

+12-2
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,13 @@ module suilend::lending_market_tests {
540540

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

543+
lending_market::set_fee_receivers<LENDING_MARKET>(
544+
&owner_cap,
545+
&mut lending_market,
546+
vector[tx_context::sender(test_scenario::ctx(&mut scenario)), @0x27],
547+
vector[1, 2]
548+
);
549+
543550
lending_market::claim_fees<LENDING_MARKET, TEST_SUI>(
544551
&mut lending_market,
545552
*bag::borrow(&type_to_index, type_name::get<TEST_SUI>()),
@@ -548,9 +555,12 @@ module suilend::lending_market_tests {
548555

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

551-
let fees: Coin<TEST_SUI> = test_scenario::take_from_address(&scenario, lending_market::fee_receiver(&lending_market));
552-
assert!(coin::value(&fees) == 1_000_000, 0);
558+
let fees: Coin<TEST_SUI> = test_scenario::take_from_address(&scenario, @0x26);
559+
assert!(coin::value(&fees) == 1_000_000 / 3, 0);
560+
test_utils::destroy(fees);
553561

562+
let fees: Coin<TEST_SUI> = test_scenario::take_from_address(&scenario, @0x27);
563+
assert!(coin::value(&fees) == 2 * 1_000_000 / 3 + 1, 0);
554564
test_utils::destroy(fees);
555565

556566
test_utils::destroy(sui);

0 commit comments

Comments
 (0)