diff --git a/token-lending/program/src/instruction.rs b/token-lending/program/src/instruction.rs index 1eab8cdf4cc..919f62e1d3d 100644 --- a/token-lending/program/src/instruction.rs +++ b/token-lending/program/src/instruction.rs @@ -362,6 +362,23 @@ 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. + /// 6 `[]` Clock sysvar. + UpdateReserveConfig { + /// Specifies which config to change + config_enum: u8, + /// New value for the config + new_value: u8, + }, } impl LendingInstruction { @@ -456,6 +473,11 @@ impl LendingInstruction { let (collateral_amount, _rest) = Self::unpack_u64(rest)?; Self::WithdrawObligationCollateralAndRedeemReserveCollateral { collateral_amount } } + 16 => { + let (config_enum, _rest) = Self::unpack_u8(rest)?; + let (new_value, _rest) = Self::unpack_u8(_rest)?; + Self::UpdateReserveConfig { config_enum, new_value } + } _ => { msg!("Instruction cannot be unpacked"); return Err(LendingError::InstructionUnpackError.into()); @@ -612,6 +634,11 @@ impl LendingInstruction { buf.push(15); buf.extend_from_slice(&collateral_amount.to_le_bytes()); } + Self::UpdateReserveConfig {config_enum, new_value} => { + buf.push(16); + buf.extend_from_slice(&config_enum.to_le_bytes()); + buf.extend_from_slice(&new_value.to_le_bytes()); + } } buf } diff --git a/token-lending/program/src/processor.rs b/token-lending/program/src/processor.rs index 08bf33c400b..8d367fa1fb0 100644 --- a/token-lending/program/src/processor.rs +++ b/token-lending/program/src/processor.rs @@ -9,7 +9,7 @@ use crate::{ CalculateBorrowResult, CalculateLiquidationResult, CalculateRepayResult, InitLendingMarketParams, InitObligationParams, InitReserveParams, LendingMarket, NewReserveCollateralParams, NewReserveLiquidityParams, Obligation, Reserve, - ReserveCollateral, ReserveConfig, ReserveLiquidity, + ReserveCollateral, ReserveConfig, ReserveConfigKey, ReserveLiquidity, }, }; use num_traits::FromPrimitive; @@ -114,13 +114,24 @@ 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 { + config_enum, + new_value, + } => { + msg!("Instruction: UpdateReserveConfig"); + process_update_reserve_config( + program_id, + ReserveConfigKey::from_u8(config_enum)?, + new_value, + accounts) + } } } @@ -1994,6 +2005,78 @@ fn process_withdraw_obligation_collateral_and_redeem_reserve_liquidity( ) } + +#[inline(never)] // avoid stack frame limit +fn process_update_reserve_config( + program_id: &Pubkey, + config_enum: ReserveConfigKey, + new_value: u8, + accounts: &[AccountInfo], +) -> ProgramResult { + + 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 clock = &Clock::from_account_info(next_account_info(account_info_iter)?)?; + + let mut reserve = assert_uninitialized::(reserve_info)?; + 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()); + } + match config_enum { + // Make this an enum, make this decimal friendly + ReserveConfigKey::LoanToValueRatio => { + reserve.config.loan_to_value_ratio = new_value; + } + _ => { + msg!("Did not recognize reserve config key"); + return Err(LendingError::InvalidConfig.into()); + } + }; + 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!( diff --git a/token-lending/program/src/state/reserve.rs b/token-lending/program/src/state/reserve.rs index 3306e7dd57a..994bc0a2359 100644 --- a/token-lending/program/src/state/reserve.rs +++ b/token-lending/program/src/state/reserve.rs @@ -613,6 +613,22 @@ pub struct ReserveConfig { pub fees: ReserveFees, } +/// ReserveConfig configurable values +#[repr(u8)] +pub enum ReserveConfigKey { + /// Enum to specify LoanToValueRatio + LoanToValueRatio = 0 +} + +impl ReserveConfigKey { + pub fn from_u8(value: u8) -> Result { + match value { + 0 => Ok(ReserveConfigKey::LoanToValueRatio) + _ => Err(LendingError::InvalidConfig.into()) + } + } +} + /// Additional fee information on a reserve /// /// These exist separately from interest accrual fees, and are specifically for the program owner