Skip to content

Commit

Permalink
Merge pull request #16 from solendprotocol/admin_controls
Browse files Browse the repository at this point in the history
Add instruction to update reserve configs
  • Loading branch information
DaSichuan authored Jul 20, 2021
2 parents b7f7207 + aaef4f5 commit 3f00ace
Show file tree
Hide file tree
Showing 4 changed files with 292 additions and 39 deletions.
82 changes: 82 additions & 0 deletions token-lending/program/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,20 @@ 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 {
/// Reserve config to update to
config: ReserveConfig,
},
}

impl LendingInstruction {
Expand Down Expand Up @@ -456,6 +470,35 @@ 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 {
config: ReserveConfig {
optimal_utilization_rate,
loan_to_value_ratio,
liquidation_bonus,
liquidation_threshold,
min_borrow_rate,
optimal_borrow_rate,
max_borrow_rate,
fees: ReserveFees {
borrow_fee_wad,
flash_loan_fee_wad,
host_fee_percentage,
},
},
}
}
_ => {
msg!("Instruction cannot be unpacked");
return Err(LendingError::InstructionUnpackError.into());
Expand Down Expand Up @@ -612,6 +655,19 @@ impl LendingInstruction {
buf.push(15);
buf.extend_from_slice(&collateral_amount.to_le_bytes());
}
Self::UpdateReserveConfig { config } => {
buf.push(16);
buf.extend_from_slice(&config.optimal_utilization_rate.to_le_bytes());
buf.extend_from_slice(&config.loan_to_value_ratio.to_le_bytes());
buf.extend_from_slice(&config.liquidation_bonus.to_le_bytes());
buf.extend_from_slice(&config.liquidation_threshold.to_le_bytes());
buf.extend_from_slice(&config.min_borrow_rate.to_le_bytes());
buf.extend_from_slice(&config.optimal_borrow_rate.to_le_bytes());
buf.extend_from_slice(&config.max_borrow_rate.to_le_bytes());
buf.extend_from_slice(&config.fees.borrow_fee_wad.to_le_bytes());
buf.extend_from_slice(&config.fees.flash_loan_fee_wad.to_le_bytes());
buf.extend_from_slice(&config.fees.host_fee_percentage.to_le_bytes());
}
}
buf
}
Expand Down Expand Up @@ -1108,3 +1164,29 @@ pub fn flash_loan(
data: LendingInstruction::FlashLoan { amount }.pack(),
}
}

/// Creates an 'UpdateReserveConfig' instruction.
#[allow(clippy::too_many_arguments)]
pub fn update_reserve_config(
program_id: Pubkey,
config: ReserveConfig,
reserve_pubkey: Pubkey,
lending_market_pubkey: Pubkey,
lending_market_owner_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_readonly(lending_market_pubkey, false),
AccountMeta::new_readonly(lending_market_authority_pubkey, false),
AccountMeta::new_readonly(lending_market_owner_pubkey, true),
];
Instruction {
program_id,
accounts,
data: LendingInstruction::UpdateReserveConfig { config }.pack(),
}
}
141 changes: 105 additions & 36 deletions token-lending/program/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,17 @@ 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 } => {
msg!("Instruction: UpdateReserveConfig");
process_update_reserve_config(program_id, config, accounts)
}
}
}

Expand Down Expand Up @@ -195,41 +199,7 @@ fn process_init_reserve(
msg!("Reserve must be initialized with liquidity");
return Err(LendingError::InvalidAmount.into());
}
if config.optimal_utilization_rate > 100 {
msg!("Optimal utilization rate must be in range [0, 100]");
return Err(LendingError::InvalidConfig.into());
}
if config.loan_to_value_ratio >= 100 {
msg!("Loan to value ratio must be in range [0, 100)");
return Err(LendingError::InvalidConfig.into());
}
if config.liquidation_bonus > 100 {
msg!("Liquidation bonus must be in range [0, 100]");
return Err(LendingError::InvalidConfig.into());
}
if config.liquidation_threshold <= config.loan_to_value_ratio
|| config.liquidation_threshold > 100
{
msg!("Liquidation threshold must be in range (LTV, 100]");
return Err(LendingError::InvalidConfig.into());
}
if config.optimal_borrow_rate < config.min_borrow_rate {
msg!("Optimal borrow rate must be >= min borrow rate");
return Err(LendingError::InvalidConfig.into());
}
if config.optimal_borrow_rate > config.max_borrow_rate {
msg!("Optimal borrow rate must be <= max borrow rate");
return Err(LendingError::InvalidConfig.into());
}
if config.fees.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 config.fees.host_fee_percentage > 100 {
msg!("Host fee percentage must be in range [0, 100]");
return Err(LendingError::InvalidConfig.into());
}

validate_reserve_config(config)?;
let account_info_iter = &mut accounts.iter().peekable();
let source_liquidity_info = next_account_info(account_info_iter)?;
let destination_collateral_info = next_account_info(account_info_iter)?;
Expand Down Expand Up @@ -1994,6 +1964,65 @@ 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,
config: ReserveConfig,
accounts: &[AccountInfo],
) -> ProgramResult {
validate_reserve_config(config)?;
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 = config;
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!(
Expand Down Expand Up @@ -2298,6 +2327,46 @@ fn spl_token_burn(params: TokenBurnParams<'_, '_>) -> ProgramResult {
result.map_err(|_| LendingError::TokenBurnFailed.into())
}

/// validates reserve configs
#[inline(always)]
fn validate_reserve_config(config: ReserveConfig) -> ProgramResult {
if config.optimal_utilization_rate > 100 {
msg!("Optimal utilization rate must be in range [0, 100]");
return Err(LendingError::InvalidConfig.into());
}
if config.loan_to_value_ratio >= 100 {
msg!("Loan to value ratio must be in range [0, 100)");
return Err(LendingError::InvalidConfig.into());
}
if config.liquidation_bonus > 100 {
msg!("Liquidation bonus must be in range [0, 100]");
return Err(LendingError::InvalidConfig.into());
}
if config.liquidation_threshold <= config.loan_to_value_ratio
|| config.liquidation_threshold > 100
{
msg!("Liquidation threshold must be in range (LTV, 100]");
return Err(LendingError::InvalidConfig.into());
}
if config.optimal_borrow_rate < config.min_borrow_rate {
msg!("Optimal borrow rate must be >= min borrow rate");
return Err(LendingError::InvalidConfig.into());
}
if config.optimal_borrow_rate > config.max_borrow_rate {
msg!("Optimal borrow rate must be <= max borrow rate");
return Err(LendingError::InvalidConfig.into());
}
if config.fees.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 config.fees.host_fee_percentage > 100 {
msg!("Host fee percentage must be in range [0, 100]");
return Err(LendingError::InvalidConfig.into());
}
Ok(())
}

struct TokenInitializeMintParams<'a: 'b, 'b> {
mint: AccountInfo<'a>,
rent: AccountInfo<'a>,
Expand Down
2 changes: 1 addition & 1 deletion token-lending/program/tests/init_lending_market.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ async fn test_success() {
);

// limit to track compute unit increase
test.set_bpf_compute_max_units(15_000);
test.set_bpf_compute_max_units(17_000);

let (mut banks_client, payer, _recent_blockhash) = test.start().await;

Expand Down
Loading

0 comments on commit 3f00ace

Please sign in to comment.