diff --git a/token-lending/cli/src/main.rs b/token-lending/cli/src/main.rs index 4ff58f5ba9b..dc9b161c221 100644 --- a/token-lending/cli/src/main.rs +++ b/token-lending/cli/src/main.rs @@ -378,6 +378,15 @@ fn main() { .default_value("30") .help("Amount of liquidation bonus going to fee reciever: [0, 100]"), ) + .arg( + Arg::with_name("protocol_take_rate") + .long("protocol-take-rate") + .validator(is_parsable::) + .value_name("INTEGER_PERCENT") + .takes_value(true) + .required(false) + .help("Amount of interest spread going to fee reciever: [0, 100]"), + ) .arg( Arg::with_name("deposit_limit") .long("deposit-limit") @@ -529,6 +538,15 @@ fn main() { .required(false) .help("Amount of liquidation bonus going to fee reciever: [0, 100]"), ) + .arg( + Arg::with_name("protocol_take_rate") + .long("protocol-take-rate") + .validator(is_parsable::) + .value_name("INTEGER_PERCENT") + .takes_value(true) + .required(false) + .help("Amount of interest spread going to fee reciever: [0, 100]"), + ) .arg( Arg::with_name("deposit_limit") .long("deposit-limit") @@ -669,6 +687,7 @@ fn main() { let liquidity_fee_receiver_keypair = Keypair::new(); let protocol_liquidation_fee = value_of(arg_matches, "protocol_liquidation_fee").unwrap(); + let protocol_take_rate = value_of(arg_matches, "protocol_take_rate").unwrap(); let source_liquidity_account = config .rpc_client @@ -707,6 +726,7 @@ fn main() { borrow_limit, fee_receiver: liquidity_fee_receiver_keypair.pubkey(), protocol_liquidation_fee, + protocol_take_rate, }, source_liquidity_pubkey, source_liquidity_owner_keypair, diff --git a/token-lending/program/src/instruction.rs b/token-lending/program/src/instruction.rs index 37fdbe1bb8e..52aeb7484a5 100644 --- a/token-lending/program/src/instruction.rs +++ b/token-lending/program/src/instruction.rs @@ -448,7 +448,8 @@ impl LendingInstruction { let (deposit_limit, rest) = Self::unpack_u64(rest)?; let (borrow_limit, rest) = Self::unpack_u64(rest)?; let (fee_receiver, rest) = Self::unpack_pubkey(rest)?; - let (protocol_liquidation_fee, _rest) = Self::unpack_u8(rest)?; + let (protocol_liquidation_fee, rest) = Self::unpack_u8(rest)?; + let (protocol_take_rate, _rest) = Self::unpack_u8(rest)?; Self::InitReserve { liquidity_amount, config: ReserveConfig { @@ -468,6 +469,7 @@ impl LendingInstruction { borrow_limit, fee_receiver, protocol_liquidation_fee, + protocol_take_rate, }, } } @@ -528,7 +530,8 @@ impl LendingInstruction { let (deposit_limit, rest) = Self::unpack_u64(rest)?; let (borrow_limit, rest) = Self::unpack_u64(rest)?; let (fee_receiver, rest) = Self::unpack_pubkey(rest)?; - let (protocol_liquidation_fee, _rest) = Self::unpack_u8(rest)?; + let (protocol_liquidation_fee, rest) = Self::unpack_u8(rest)?; + let (protocol_take_rate, _rest) = Self::unpack_u8(rest)?; Self::UpdateReserveConfig { config: ReserveConfig { optimal_utilization_rate, @@ -547,6 +550,7 @@ impl LendingInstruction { borrow_limit, fee_receiver, protocol_liquidation_fee, + protocol_take_rate, }, } } @@ -651,6 +655,7 @@ impl LendingInstruction { borrow_limit, fee_receiver, protocol_liquidation_fee, + protocol_take_rate, }, } => { buf.push(2); @@ -669,6 +674,7 @@ impl LendingInstruction { buf.extend_from_slice(&borrow_limit.to_le_bytes()); buf.extend_from_slice(&fee_receiver.to_bytes()); buf.extend_from_slice(&protocol_liquidation_fee.to_le_bytes()); + buf.extend_from_slice(&protocol_take_rate.to_le_bytes()); } Self::RefreshReserve => { buf.push(3); @@ -741,7 +747,7 @@ impl LendingInstruction { buf.extend_from_slice(&liquidity_amount.to_le_bytes()); } Self::SyncCollateral {} => { - buf.push(18); + buf.push(18); } } buf @@ -1386,4 +1392,4 @@ pub fn sync_collateral( accounts, data: LendingInstruction::SyncCollateral.pack(), } -} \ No newline at end of file +} diff --git a/token-lending/program/src/processor.rs b/token-lending/program/src/processor.rs index cf68223ae2a..2f3bab84a5e 100644 --- a/token-lending/program/src/processor.rs +++ b/token-lending/program/src/processor.rs @@ -2201,12 +2201,8 @@ fn process_update_reserve_config( Ok(()) } - #[inline(never)] // avoid stack frame limit -fn process_sync_collateral( - program_id: &Pubkey, - accounts: &[AccountInfo], -) -> ProgramResult { +fn process_sync_collateral(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { let account_info_iter = &mut accounts.iter().peekable(); let reserve_info = next_account_info(account_info_iter)?; let reserve_liquidity_fee_receiver_info = next_account_info(account_info_iter)?; @@ -2215,7 +2211,6 @@ fn process_sync_collateral( let lending_market_authority_info = next_account_info(account_info_iter)?; let token_program_id = next_account_info(account_info_iter)?; - let reserve = Reserve::unpack(&reserve_info.data.borrow())?; if reserve_info.owner != program_id { msg!( @@ -2250,12 +2245,15 @@ fn process_sync_collateral( } let reserve_supply_liquidity = Account::unpack(&reserve_supply_liquidity_info.data.borrow())?; - let fee_reciever_claimable_amount = reserve_supply_liquidity.amount.checked_sub(reserve.liquidity.available_amount).unwrap(); - + let fee_reciever_claimable_amount = reserve_supply_liquidity + .amount + .checked_sub(reserve.liquidity.available_amount) + .unwrap(); + msg!("{}", fee_reciever_claimable_amount); msg!("{}", fee_reciever_claimable_amount); msg!("{}", fee_reciever_claimable_amount); - spl_token_transfer( TokenTransferParams { + spl_token_transfer(TokenTransferParams { source: reserve_supply_liquidity_info.clone(), destination: reserve_liquidity_fee_receiver_info.clone(), amount: fee_reciever_claimable_amount, @@ -2267,7 +2265,6 @@ fn process_sync_collateral( Ok(()) } - fn assert_rent_exempt(rent: &Rent, account_info: &AccountInfo) -> ProgramResult { if !rent.is_exempt(account_info.lamports(), account_info.data_len()) { msg!( diff --git a/token-lending/program/src/state/reserve.rs b/token-lending/program/src/state/reserve.rs index 34a27af8e76..b72cdb27972 100644 --- a/token-lending/program/src/state/reserve.rs +++ b/token-lending/program/src/state/reserve.rs @@ -129,8 +129,9 @@ impl Reserve { let slots_elapsed = self.last_update.slots_elapsed(current_slot)?; if slots_elapsed > 0 { let current_borrow_rate = self.current_borrow_rate()?; + let take_rate = Rate::from_percent(self.config.protocol_take_rate); self.liquidity - .compound_interest(current_borrow_rate, slots_elapsed)?; + .compound_interest(current_borrow_rate, slots_elapsed, take_rate)?; } Ok(()) } @@ -474,6 +475,7 @@ impl ReserveLiquidity { &mut self, current_borrow_rate: Rate, slots_elapsed: u64, + take_rate: Rate, ) -> ProgramResult { let slot_interest_rate = current_borrow_rate.try_div(SLOTS_PER_YEAR)?; let compounded_interest_rate = Rate::one() @@ -482,9 +484,22 @@ impl ReserveLiquidity { self.cumulative_borrow_rate_wads = self .cumulative_borrow_rate_wads .try_mul(compounded_interest_rate)?; + + let net_new_debt = self + .borrowed_amount_wads + .try_mul(compounded_interest_rate)? + .try_sub(self.borrowed_amount_wads)?; + + let delta_accumulated_protocol_fees = net_new_debt.try_mul(take_rate)?; + let delta_borrowed_amount_wads = net_new_debt.try_sub(delta_accumulated_protocol_fees)?; + + self.accumulated_protocol_fees_wads = self + .accumulated_protocol_fees_wads + .try_add(delta_accumulated_protocol_fees)?; + self.borrowed_amount_wads = self .borrowed_amount_wads - .try_mul(compounded_interest_rate)?; + .try_add(delta_borrowed_amount_wads)?; Ok(()) } } @@ -636,6 +651,8 @@ pub struct ReserveConfig { pub fee_receiver: Pubkey, /// Cut of the liquidation bonus that the protocol receives, as a percentage pub protocol_liquidation_fee: u8, + /// Protocol take rate is the amount borrowed interest protocol recieves, as a percentage + pub protocol_take_rate: u8, } /// Additional fee information on a reserve @@ -785,6 +802,7 @@ impl Pack for Reserve { config_borrow_limit, config_fee_receiver, config_protocol_liquidation_fee, + config_protocol_take_rate, liquidity_accumulated_protocol_fees_wads, _padding, ) = mut_array_refs![ @@ -819,8 +837,9 @@ impl Pack for Reserve { 8, PUBKEY_BYTES, 1, + 1, 16, - 231 + 230 ]; // reserve @@ -871,6 +890,7 @@ impl Pack for Reserve { *config_borrow_limit = self.config.borrow_limit.to_le_bytes(); config_fee_receiver.copy_from_slice(self.config.fee_receiver.as_ref()); *config_protocol_liquidation_fee = self.config.protocol_liquidation_fee.to_le_bytes(); + *config_protocol_take_rate = self.config.protocol_take_rate.to_le_bytes(); } /// Unpacks a byte buffer into a [ReserveInfo](struct.ReserveInfo.html). @@ -908,6 +928,7 @@ impl Pack for Reserve { config_borrow_limit, config_fee_receiver, config_protocol_liquidation_fee, + config_protocol_take_rate, liquidity_accumulated_protocol_fees_wads, _padding, ) = array_refs![ @@ -942,8 +963,9 @@ impl Pack for Reserve { 8, PUBKEY_BYTES, 1, + 1, 16, - 231 + 230 ]; let version = u8::from_le_bytes(*version); @@ -970,7 +992,9 @@ impl Pack for Reserve { available_amount: u64::from_le_bytes(*liquidity_available_amount), borrowed_amount_wads: unpack_decimal(liquidity_borrowed_amount_wads), cumulative_borrow_rate_wads: unpack_decimal(liquidity_cumulative_borrow_rate_wads), - accumulated_protocol_fees_wads: unpack_decimal(liquidity_accumulated_protocol_fees_wads), + accumulated_protocol_fees_wads: unpack_decimal( + liquidity_accumulated_protocol_fees_wads, + ), market_price: unpack_decimal(liquidity_market_price), }, collateral: ReserveCollateral { @@ -995,6 +1019,7 @@ impl Pack for Reserve { borrow_limit: u64::from_le_bytes(*config_borrow_limit), fee_receiver: Pubkey::new_from_array(*config_fee_receiver), protocol_liquidation_fee: u8::from_le_bytes(*config_protocol_liquidation_fee), + protocol_take_rate: u8::from_le_bytes(*config_protocol_take_rate), }, }) } @@ -1160,15 +1185,18 @@ mod test { fn compound_interest( slots_elapsed in 0..=SLOTS_PER_YEAR, borrow_rate in 0..=u8::MAX, + take_rate in 0..=100u8, ) { let mut reserve = Reserve::default(); let borrow_rate = Rate::from_percent(borrow_rate); + let take_rate = Rate::from_percent(take_rate); // Simulate running for max 1000 years, assuming that interest is // compounded at least once a year for _ in 0..1000 { - reserve.liquidity.compound_interest(borrow_rate, slots_elapsed)?; + reserve.liquidity.compound_interest(borrow_rate, slots_elapsed, take_rate)?; reserve.liquidity.cumulative_borrow_rate_wads.to_scaled_val()?; + reserve.liquidity.accumulated_protocol_fees_wads.to_scaled_val()?; } } diff --git a/token-lending/program/tests/helpers/mod.rs b/token-lending/program/tests/helpers/mod.rs index 50805befdab..96a81e3309b 100644 --- a/token-lending/program/tests/helpers/mod.rs +++ b/token-lending/program/tests/helpers/mod.rs @@ -60,6 +60,7 @@ pub fn test_reserve_config() -> ReserveConfig { borrow_limit: u64::MAX, fee_receiver: Keypair::new().pubkey(), protocol_liquidation_fee: 30, + protocol_take_rate: 10, } } diff --git a/token-lending/program/tests/init_reserve.rs b/token-lending/program/tests/init_reserve.rs index 66a8c75c32e..a3396758836 100644 --- a/token-lending/program/tests/init_reserve.rs +++ b/token-lending/program/tests/init_reserve.rs @@ -419,6 +419,7 @@ async fn test_update_reserve_config() { borrow_limit: 300_000, fee_receiver: Keypair::new().pubkey(), protocol_liquidation_fee: 30, + protocol_take_rate: 10, }; let (mut banks_client, payer, recent_blockhash) = test.start().await; diff --git a/token-lending/program/tests/sync_collateral.rs b/token-lending/program/tests/sync_collateral.rs index 3962b42a6da..b1b0519f9ac 100644 --- a/token-lending/program/tests/sync_collateral.rs +++ b/token-lending/program/tests/sync_collateral.rs @@ -10,7 +10,7 @@ use solana_sdk::{ transaction::Transaction, }; use solend_program::{ - instruction::{refresh_reserve,sync_collateral}, + instruction::{refresh_reserve, sync_collateral}, math::{Decimal, Rate, TryAdd, TryDiv, TryMul}, processor::process_instruction, state::SLOTS_PER_YEAR,