Skip to content

Commit

Permalink
Add instruction to update the LTV of a reserve
Browse files Browse the repository at this point in the history
  • Loading branch information
DaSichuan committed Jul 15, 2021
1 parent b7f7207 commit 850e526
Show file tree
Hide file tree
Showing 3 changed files with 361 additions and 3 deletions.
119 changes: 119 additions & 0 deletions token-lending/program/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -1108,3 +1189,41 @@ 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 {
optimal_utilization_rate: config.optimal_utilization_rate,
loan_to_value_ratio: config.loan_to_value_ratio,
liquidation_bonus: config.liquidation_bonus,
liquidation_threshold: config.liquidation_threshold,
min_borrow_rate: config.min_borrow_rate,
optimal_borrow_rate: config.optimal_borrow_rate,
max_borrow_rate: config.max_borrow_rate,
borrow_fee_wad: config.fees.borrow_fee_wad,
flash_loan_fee_wad: config.fees.flash_loan_fee_wad,
host_fee_percentage: config.fees.host_fee_percentage,
}
.pack(),
}
}
139 changes: 138 additions & 1 deletion token-lending/program/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
}
}
}

Expand Down Expand Up @@ -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!(
Expand Down
Loading

0 comments on commit 850e526

Please sign in to comment.