Skip to content

Commit

Permalink
trying take rate other way (solana-labs#89)
Browse files Browse the repository at this point in the history
* started take rate

cleaning up all the boilerplate adding reserve config take rate

claim fees

config check + fixing/adding tests

fix comment in instruction.rs

fixed tests for native token and added pr feedback

properly keeping track of availible amount when redeeming fees

add in fees when compounding

try other method

* change to accumulating and redeem when can also added staleness checks

* fixed tests

* cleanup
  • Loading branch information
nope-finance authored Jun 13, 2022
1 parent a116cfc commit 46cdf9a
Show file tree
Hide file tree
Showing 13 changed files with 592 additions and 27 deletions.
37 changes: 35 additions & 2 deletions token-lending/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ struct PartialReserveConfig {
pub fee_receiver: Option<Pubkey>,
/// Cut of the liquidation bonus that the protocol receives, as a percentage
pub protocol_liquidation_fee: Option<u8>,
/// Protocol take rate is the amount borrowed interest protocol recieves, as a percentage
pub protocol_take_rate: Option<u8>,
}

/// Reserve Fees with optional fields
Expand Down Expand Up @@ -376,7 +378,16 @@ fn main() {
.takes_value(true)
.required(false)
.default_value("30")
.help("Amount of liquidation bonus going to fee reciever: [0, 100]"),
.help("Amount of liquidation bonus going to fee receiver: [0, 100]"),
)
.arg(
Arg::with_name("protocol_take_rate")
.long("protocol-take-rate")
.validator(is_parsable::<u8>)
.value_name("INTEGER_PERCENT")
.takes_value(true)
.required(false)
.help("Amount of interest spread going to fee receiver: [0, 100]"),
)
.arg(
Arg::with_name("deposit_limit")
Expand Down Expand Up @@ -527,7 +538,16 @@ fn main() {
.value_name("INTEGER_PERCENT")
.takes_value(true)
.required(false)
.help("Amount of liquidation bonus going to fee reciever: [0, 100]"),
.help("Amount of liquidation bonus going to fee receiver: [0, 100]"),
)
.arg(
Arg::with_name("protocol_take_rate")
.long("protocol-take-rate")
.validator(is_parsable::<u8>)
.value_name("INTEGER_PERCENT")
.takes_value(true)
.required(false)
.help("Amount of interest spread going to fee receiver: [0, 100]"),
)
.arg(
Arg::with_name("deposit_limit")
Expand Down Expand Up @@ -669,6 +689,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
Expand Down Expand Up @@ -707,6 +728,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,
Expand Down Expand Up @@ -738,6 +760,7 @@ fn main() {
let borrow_limit = value_of(arg_matches, "borrow_limit");
let fee_receiver = pubkey_of(arg_matches, "fee_receiver");
let protocol_liquidation_fee = value_of(arg_matches, "protocol_liquidation_fee");
let protocol_take_rate = value_of(arg_matches, "protocol_take_rate");
let pyth_product_pubkey = pubkey_of(arg_matches, "pyth_product");
let pyth_price_pubkey = pubkey_of(arg_matches, "pyth_price");
let switchboard_feed_pubkey = pubkey_of(arg_matches, "switchboard_feed");
Expand All @@ -764,6 +787,7 @@ fn main() {
borrow_limit,
fee_receiver,
protocol_liquidation_fee,
protocol_take_rate,
},
pyth_product_pubkey,
pyth_price_pubkey,
Expand Down Expand Up @@ -1199,6 +1223,15 @@ fn command_update_reserve(
reserve.config.protocol_liquidation_fee = reserve_config.protocol_liquidation_fee.unwrap();
}

if reserve_config.protocol_take_rate.is_some() {
println!(
"Updating protocol_take_rate from {} to {}",
reserve.config.protocol_take_rate,
reserve_config.protocol_take_rate.unwrap(),
);
reserve.config.protocol_take_rate = reserve_config.protocol_take_rate.unwrap();
}

let mut new_pyth_product_pubkey = solend_program::NULL_PUBKEY;
if pyth_price_pubkey.is_some() {
println!(
Expand Down
4 changes: 4 additions & 0 deletions token-lending/program/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,14 @@ pub enum LendingError {
/// Not enough liquidity after flash loan
#[error("Not enough liquidity after flash loan")]
NotEnoughLiquidityAfterFlashLoan,

// 45
/// Null oracle config
#[error("Null oracle config")]
NullOracleConfig,
/// Insufficent protocol fees to redeem or no liquidity availible to process redeem
#[error("Insufficent protocol fees to claim or no liquidity availible")]
InsufficientProtocolFeesToRedeem,
}

impl From<LendingError> for ProgramError {
Expand Down
52 changes: 50 additions & 2 deletions token-lending/program/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,16 @@ pub enum LendingInstruction {
/// Amount of liquidity to repay - u64::MAX for up to 100% of borrowed amount
liquidity_amount: u64,
},

// 18
/// 0. `[writable]` Reserve account.
/// 1. `[writable]` Borrow reserve liquidity fee receiver account.
/// Must be the fee account specified at InitReserve.
/// 2. `[writable]` Reserve liquidity supply SPL Token account.
/// 3. `[]` Lending market account.
/// 4. `[]` Derived lending market authority.
/// 5. `[]` Token program id.
RedeemFees,
}

impl LendingInstruction {
Expand Down Expand Up @@ -444,7 +454,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 {
Expand All @@ -464,6 +475,7 @@ impl LendingInstruction {
borrow_limit,
fee_receiver,
protocol_liquidation_fee,
protocol_take_rate,
},
}
}
Expand Down Expand Up @@ -524,7 +536,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,
Expand All @@ -543,13 +556,15 @@ impl LendingInstruction {
borrow_limit,
fee_receiver,
protocol_liquidation_fee,
protocol_take_rate,
},
}
}
17 => {
let (liquidity_amount, _rest) = Self::unpack_u64(rest)?;
Self::LiquidateObligationAndRedeemReserveCollateral { liquidity_amount }
}
18 => Self::RedeemFees,
_ => {
msg!("Instruction cannot be unpacked");
return Err(LendingError::InstructionUnpackError.into());
Expand Down Expand Up @@ -646,6 +661,7 @@ impl LendingInstruction {
borrow_limit,
fee_receiver,
protocol_liquidation_fee,
protocol_take_rate,
},
} => {
buf.push(2);
Expand All @@ -664,6 +680,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);
Expand Down Expand Up @@ -730,11 +747,15 @@ impl LendingInstruction {
buf.extend_from_slice(&config.borrow_limit.to_le_bytes());
buf.extend_from_slice(&config.fee_receiver.to_bytes());
buf.extend_from_slice(&config.protocol_liquidation_fee.to_le_bytes());
buf.extend_from_slice(&config.protocol_take_rate.to_le_bytes());
}
Self::LiquidateObligationAndRedeemReserveCollateral { liquidity_amount } => {
buf.push(17);
buf.extend_from_slice(&liquidity_amount.to_le_bytes());
}
Self::RedeemFees {} => {
buf.push(18);
}
}
buf
}
Expand Down Expand Up @@ -1352,3 +1373,30 @@ pub fn liquidate_obligation_and_redeem_reserve_collateral(
.pack(),
}
}

/// Creates a `RedeemFees` instruction
pub fn redeem_fees(
program_id: Pubkey,
reserve_pubkey: Pubkey,
reserve_liquidity_fee_receiver_pubkey: Pubkey,
reserve_supply_liquidity_pubkey: Pubkey,
lending_market_pubkey: Pubkey,
) -> Instruction {
let (lending_market_authority_pubkey, _bump_seed) = Pubkey::find_program_address(
&[&lending_market_pubkey.to_bytes()[..PUBKEY_BYTES]],
&program_id,
);
let accounts = vec![
AccountMeta::new(reserve_pubkey, false),
AccountMeta::new(reserve_liquidity_fee_receiver_pubkey, false),
AccountMeta::new(reserve_supply_liquidity_pubkey, false),
AccountMeta::new_readonly(lending_market_pubkey, false),
AccountMeta::new_readonly(lending_market_authority_pubkey, false),
AccountMeta::new_readonly(spl_token::id(), false),
];
Instruction {
program_id,
accounts,
data: LendingInstruction::RedeemFees.pack(),
}
}
89 changes: 89 additions & 0 deletions token-lending/program/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ pub fn process_instruction(
accounts,
)
}
LendingInstruction::RedeemFees => {
msg!("Instruction: RedeemFees");
process_redeem_fees(program_id, accounts)
}
}
}

Expand Down Expand Up @@ -2197,6 +2201,87 @@ fn process_update_reserve_config(
Ok(())
}

#[inline(never)] // avoid stack frame limit
fn process_redeem_fees(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)?;
let reserve_supply_liquidity_info = next_account_info(account_info_iter)?;
let lending_market_info = next_account_info(account_info_iter)?;
let lending_market_authority_info = next_account_info(account_info_iter)?;
let token_program_id = next_account_info(account_info_iter)?;
let clock = &Clock::get()?;

let mut reserve = Reserve::unpack(&reserve_info.data.borrow())?;
if reserve_info.owner != program_id {
msg!(
"Reserve provided is not owned by the lending program {} != {}",
&reserve_info.owner.to_string(),
&program_id.to_string(),
);
return Err(LendingError::InvalidAccountOwner.into());
}

if &reserve.config.fee_receiver != reserve_liquidity_fee_receiver_info.key {
msg!("Reserve liquidity fee receiver does not match the reserve liquidity fee receiver provided");
return Err(LendingError::InvalidAccountInput.into());
}
if &reserve.liquidity.supply_pubkey != reserve_supply_liquidity_info.key {
msg!("Reserve liquidity supply must be used as the reserve supply liquidity provided");
return Err(LendingError::InvalidAccountInput.into());
}
if &reserve.lending_market != lending_market_info.key {
msg!("Reserve lending market does not match the lending market provided");
return Err(LendingError::InvalidAccountInput.into());
}
if reserve.last_update.is_stale(clock.slot)? {
msg!("reserve is stale and must be refreshed in the current slot");
return Err(LendingError::ReserveStale.into());
}

let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
if lending_market_info.owner != program_id {
msg!("Lending market provided is not owned by the lending program");
return Err(LendingError::InvalidAccountOwner.into());
}
if &lending_market.token_program_id != token_program_id.key {
msg!("Lending market token program does not match the token program provided");
return Err(LendingError::InvalidTokenProgram.into());
}
let authority_signer_seeds = &[
lending_market_info.key.as_ref(),
&[lending_market.bump_seed],
];
let lending_market_authority_pubkey =
Pubkey::create_program_address(authority_signer_seeds, program_id)?;
if &lending_market_authority_pubkey != lending_market_authority_info.key {
msg!(
"Derived lending market authority does not match the lending market authority provided"
);
return Err(LendingError::InvalidMarketAuthority.into());
}

let withdraw_amount = reserve.calculate_redeem_fees()?;
if withdraw_amount == 0 {
return Err(LendingError::InsufficientProtocolFeesToRedeem.into());
}

reserve.liquidity.redeem_fees(withdraw_amount)?;
reserve.last_update.mark_stale();
Reserve::pack(reserve, &mut reserve_info.data.borrow_mut())?;

spl_token_transfer(TokenTransferParams {
source: reserve_supply_liquidity_info.clone(),
destination: reserve_liquidity_fee_receiver_info.clone(),
amount: withdraw_amount,
authority: lending_market_authority_info.clone(),
authority_signer_seeds,
token_program: token_program_id.clone(),
})?;

Ok(())
}

fn assert_rent_exempt(rent: &Rent, account_info: &AccountInfo) -> ProgramResult {
if !rent.is_exempt(account_info.lamports(), account_info.data_len()) {
msg!(
Expand Down Expand Up @@ -2605,6 +2690,10 @@ fn validate_reserve_config(config: ReserveConfig) -> ProgramResult {
msg!("Protocol liquidation fee must be in range [0, 100]");
return Err(LendingError::InvalidConfig.into());
}
if config.protocol_take_rate > 100 {
msg!("Protocol take rate must be in range [0, 100]");
return Err(LendingError::InvalidConfig.into());
}
Ok(())
}

Expand Down
Loading

0 comments on commit 46cdf9a

Please sign in to comment.