From f83ff8f49ee4f758deb16f73a987005f561e1f20 Mon Sep 17 00:00:00 2001 From: dasichuan <0xdasichuan@gmail.com> Date: Wed, 14 Jul 2021 16:34:03 -0700 Subject: [PATCH] Add instruction to update the LTV of a reserve --- token-lending/program/src/instruction.rs | 81 +++++++++++++ token-lending/program/src/processor.rs | 139 ++++++++++++++++++++++- 2 files changed, 219 insertions(+), 1 deletion(-) diff --git a/token-lending/program/src/instruction.rs b/token-lending/program/src/instruction.rs index 1eab8cdf4cc..46fa942942b 100644 --- a/token-lending/program/src/instruction.rs +++ b/token-lending/program/src/instruction.rs @@ -362,6 +362,38 @@ pub enum LendingInstruction { /// liquidity_amount is the amount of collateral tokens to withdraw collateral_amount: u64, }, + + // 16 + /// Updates a reserve config parameter + /// + /// Accounts expected by this instruction: + /// + /// 1. `[writable]` Reserve account - refreshed + /// 2 `[]` Lending market account. + /// 3 `[]` Derived lending market authority. + /// 4 `[signer]` Lending market owner. + UpdateReserveConfig { + /// optimal utilization rate + optimal_utilization_rate: u8, + /// loan to value ratio + loan_to_value_ratio: u8, + /// liquidation bonus + liquidation_bonus: u8, + /// liquidation threshold + liquidation_threshold: u8, + /// min borrow rate + min_borrow_rate: u8, + /// optimal borrow rate + optimal_borrow_rate: u8, + /// max borrow rate + max_borrow_rate: u8, + /// borrow fee wad + borrow_fee_wad: u64, + /// flash loan fee + flash_loan_fee_wad: u64, + /// host fee percentage + host_fee_percentage: u8, + }, } impl LendingInstruction { @@ -456,6 +488,31 @@ impl LendingInstruction { let (collateral_amount, _rest) = Self::unpack_u64(rest)?; Self::WithdrawObligationCollateralAndRedeemReserveCollateral { collateral_amount } } + 16 => { + let (optimal_utilization_rate, _rest) = Self::unpack_u8(rest)?; + let (loan_to_value_ratio, _rest) = Self::unpack_u8(_rest)?; + let (liquidation_bonus, _rest) = Self::unpack_u8(_rest)?; + let (liquidation_threshold, _rest) = Self::unpack_u8(_rest)?; + let (min_borrow_rate, _rest) = Self::unpack_u8(_rest)?; + let (optimal_borrow_rate, _rest) = Self::unpack_u8(_rest)?; + let (max_borrow_rate, _rest) = Self::unpack_u8(_rest)?; + let (borrow_fee_wad, _rest) = Self::unpack_u64(_rest)?; + let (flash_loan_fee_wad, _rest) = Self::unpack_u64(_rest)?; + let (host_fee_percentage, _rest) = Self::unpack_u8(_rest)?; + + Self::UpdateReserveConfig { + optimal_utilization_rate, + loan_to_value_ratio, + liquidation_bonus, + liquidation_threshold, + min_borrow_rate, + optimal_borrow_rate, + max_borrow_rate, + borrow_fee_wad, + flash_loan_fee_wad, + host_fee_percentage, + } + } _ => { msg!("Instruction cannot be unpacked"); return Err(LendingError::InstructionUnpackError.into()); @@ -612,6 +669,30 @@ impl LendingInstruction { buf.push(15); buf.extend_from_slice(&collateral_amount.to_le_bytes()); } + Self::UpdateReserveConfig { + optimal_utilization_rate, + loan_to_value_ratio, + liquidation_bonus, + liquidation_threshold, + min_borrow_rate, + optimal_borrow_rate, + max_borrow_rate, + borrow_fee_wad, + flash_loan_fee_wad, + host_fee_percentage, + } => { + buf.push(16); + buf.extend_from_slice(&optimal_utilization_rate.to_le_bytes()); + buf.extend_from_slice(&loan_to_value_ratio.to_le_bytes()); + buf.extend_from_slice(&liquidation_bonus.to_le_bytes()); + buf.extend_from_slice(&liquidation_threshold.to_le_bytes()); + buf.extend_from_slice(&min_borrow_rate.to_le_bytes()); + buf.extend_from_slice(&optimal_borrow_rate.to_le_bytes()); + buf.extend_from_slice(&max_borrow_rate.to_le_bytes()); + buf.extend_from_slice(&borrow_fee_wad.to_le_bytes()); + buf.extend_from_slice(&flash_loan_fee_wad.to_le_bytes()); + buf.extend_from_slice(&host_fee_percentage.to_le_bytes()); + } } buf } diff --git a/token-lending/program/src/processor.rs b/token-lending/program/src/processor.rs index 08bf33c400b..c7184486219 100644 --- a/token-lending/program/src/processor.rs +++ b/token-lending/program/src/processor.rs @@ -114,13 +114,41 @@ pub fn process_instruction( LendingInstruction::WithdrawObligationCollateralAndRedeemReserveCollateral { collateral_amount, } => { - msg!("Instruction: Withdraw Obligation Collateral and Redeem Reserve Collateral "); + msg!("Instruction: Withdraw Obligation Collateral and Redeem Reserve Collateral"); process_withdraw_obligation_collateral_and_redeem_reserve_liquidity( program_id, collateral_amount, accounts, ) } + LendingInstruction::UpdateReserveConfig { + optimal_utilization_rate, + loan_to_value_ratio, + liquidation_bonus, + liquidation_threshold, + min_borrow_rate, + optimal_borrow_rate, + max_borrow_rate, + borrow_fee_wad, + flash_loan_fee_wad, + host_fee_percentage, + } => { + msg!("Instruction: UpdateReserveConfig"); + process_update_reserve_config( + program_id, + optimal_utilization_rate, + loan_to_value_ratio, + liquidation_bonus, + liquidation_threshold, + min_borrow_rate, + optimal_borrow_rate, + max_borrow_rate, + borrow_fee_wad, + flash_loan_fee_wad, + host_fee_percentage, + accounts, + ) + } } } @@ -1994,6 +2022,115 @@ fn process_withdraw_obligation_collateral_and_redeem_reserve_liquidity( ) } +#[allow(clippy::too_many_arguments)] +#[inline(never)] // avoid stack frame limit +fn process_update_reserve_config( + program_id: &Pubkey, + optimal_utilization_rate: u8, + loan_to_value_ratio: u8, + liquidation_bonus: u8, + liquidation_threshold: u8, + min_borrow_rate: u8, + optimal_borrow_rate: u8, + max_borrow_rate: u8, + borrow_fee_wad: u64, + flash_loan_fee_wad: u64, + host_fee_percentage: u8, + accounts: &[AccountInfo], +) -> ProgramResult { + if optimal_utilization_rate > 100 { + msg!("Optimal utilization rate must be in range [0, 100]"); + return Err(LendingError::InvalidConfig.into()); + } + if loan_to_value_ratio >= 100 { + msg!("Loan to value ratio must be in range [0, 100)"); + return Err(LendingError::InvalidConfig.into()); + } + if liquidation_bonus > 100 { + msg!("Liquidation bonus must be in range [0, 100]"); + return Err(LendingError::InvalidConfig.into()); + } + if liquidation_threshold <= loan_to_value_ratio || liquidation_threshold > 100 { + msg!("Liquidation threshold must be in range (LTV, 100]"); + return Err(LendingError::InvalidConfig.into()); + } + if optimal_borrow_rate < min_borrow_rate { + msg!("Optimal borrow rate must be >= min borrow rate"); + return Err(LendingError::InvalidConfig.into()); + } + if optimal_borrow_rate > max_borrow_rate { + msg!("Optimal borrow rate must be <= max borrow rate"); + return Err(LendingError::InvalidConfig.into()); + } + if borrow_fee_wad >= WAD { + msg!("Borrow fee must be in range [0, 1_000_000_000_000_000_000)"); + return Err(LendingError::InvalidConfig.into()); + } + if host_fee_percentage > 100 { + msg!("Host fee percentage must be in range [0, 100]"); + return Err(LendingError::InvalidConfig.into()); + } + let account_info_iter = &mut accounts.iter().peekable(); + let reserve_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 lending_market_owner_info = next_account_info(account_info_iter)?; + + 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()); + } + + 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 {} != {}", + &lending_market_info.owner.to_string(), + &program_id.to_string(), + ); + return Err(LendingError::InvalidAccountOwner.into()); + } + if &lending_market.owner != lending_market_owner_info.key { + msg!("Lending market owner does not match the lending market owner provided"); + return Err(LendingError::InvalidMarketOwner.into()); + } + if !lending_market_owner_info.is_signer { + msg!("Lending market owner provided must be a signer"); + return Err(LendingError::InvalidSigner.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()); + } + + reserve.config.optimal_utilization_rate = optimal_utilization_rate; + reserve.config.loan_to_value_ratio = loan_to_value_ratio; + reserve.config.liquidation_bonus = liquidation_bonus; + reserve.config.liquidation_threshold = liquidation_threshold; + reserve.config.min_borrow_rate = min_borrow_rate; + reserve.config.optimal_borrow_rate = optimal_borrow_rate; + reserve.config.max_borrow_rate = max_borrow_rate; + reserve.config.fees.borrow_fee_wad = borrow_fee_wad; + reserve.config.fees.flash_loan_fee_wad = flash_loan_fee_wad; + reserve.config.fees.host_fee_percentage = host_fee_percentage; + Reserve::pack(reserve, &mut reserve_info.data.borrow_mut())?; + Ok(()) +} + fn assert_rent_exempt(rent: &Rent, account_info: &AccountInfo) -> ProgramResult { if !rent.is_exempt(account_info.lamports(), account_info.data_len()) { msg!(