Skip to content

Commit

Permalink
Add ability to update oracle addresses
Browse files Browse the repository at this point in the history
 Please enter the commit message for your changes. Lines starting
  • Loading branch information
DaSichuan committed Aug 18, 2021
1 parent bf16547 commit 6ab9d7b
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 50 deletions.
67 changes: 66 additions & 1 deletion token-lending/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,33 @@ fn main() {
.required(false)
.help("Fee receiver address"),
)
.arg(
Arg::with_name("pyth_product")
.long("pyth-product")
.validator(is_pubkey)
.value_name("PUBKEY")
.takes_value(true)
.required(false)
.help("Pyth product account: https://pyth.network/developers/consumers/accounts"),
)
.arg(
Arg::with_name("pyth_price")
.long("pyth-price")
.validator(is_pubkey)
.value_name("PUBKEY")
.takes_value(true)
.required(false)
.help("Pyth price account: https://pyth.network/developers/consumers/accounts"),
)
.arg(
Arg::with_name("switchboard_feed")
.long("switchboard-feed")
.validator(is_pubkey)
.value_name("PUBKEY")
.takes_value(true)
.required(false)
.help("Switchboard price feed account: https://switchboard.xyz/#/explorer"),
)
)
.get_matches();

Expand Down Expand Up @@ -686,6 +713,9 @@ fn main() {
let deposit_limit = value_of(arg_matches, "deposit_limit");
let borrow_limit = value_of(arg_matches, "borrow_limit");
let fee_receiver = pubkey_of(arg_matches, "fee_receiver");
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");

let borrow_fee_wad = borrow_fee.map(|fee| (fee * WAD as f64) as u64);
let flash_loan_fee_wad = flash_loan_fee.map(|fee| (fee * WAD as f64) as u64);
Expand All @@ -709,6 +739,9 @@ fn main() {
borrow_limit,
fee_receiver,
},
pyth_product_pubkey,
pyth_price_pubkey,
switchboard_feed_pubkey,
reserve_pubkey,
lending_market_pubkey,
lending_market_owner_keypair,
Expand Down Expand Up @@ -992,10 +1025,13 @@ fn command_add_reserve(
Ok(())
}

#[allow(clippy::too_many_arguments)]
#[allow(clippy::too_many_arguments, clippy::unnecessary_unwrap)]
fn command_update_reserve(
config: &mut Config,
reserve_config: PartialReserveConfig,
pyth_product_pubkey: Option<Pubkey>,
pyth_price_pubkey: Option<Pubkey>,
switchboard_feed_pubkey: Option<Pubkey>,
reserve_pubkey: Pubkey,
lending_market_pubkey: Pubkey,
lending_market_owner_keypair: Keypair,
Expand Down Expand Up @@ -1128,13 +1164,36 @@ fn command_update_reserve(
reserve.config.fee_receiver = reserve_config.fee_receiver.unwrap();
}

let mut new_pyth_product_pubkey = null_pyth_product_oracle_pubkey();
if pyth_price_pubkey.is_some() {
println!(
"Updating pyth oracle pubkey from {} to {}",
reserve.liquidity.pyth_oracle_pubkey.to_string(),
pyth_price_pubkey.unwrap().to_string(),
);
reserve.liquidity.pyth_oracle_pubkey = pyth_price_pubkey.unwrap();
new_pyth_product_pubkey = pyth_product_pubkey.unwrap();
}

if switchboard_feed_pubkey.is_some() {
println!(
"Updating switchboard_oracle_pubkey {} to {}",
reserve.liquidity.switchboard_oracle_pubkey.to_string(),
switchboard_feed_pubkey.unwrap().to_string(),
);
reserve.liquidity.switchboard_oracle_pubkey = switchboard_feed_pubkey.unwrap();
}

let mut transaction = Transaction::new_with_payer(
&[update_reserve_config(
config.lending_program_id,
reserve.config,
reserve_pubkey,
lending_market_pubkey,
lending_market_owner_keypair.pubkey(),
new_pyth_product_pubkey,
reserve.liquidity.pyth_oracle_pubkey,
reserve.liquidity.switchboard_oracle_pubkey,
)],
Some(&config.fee_payer.pubkey()),
);
Expand Down Expand Up @@ -1198,3 +1257,9 @@ fn quote_currency_of(matches: &ArgMatches<'_>, name: &str) -> Option<[u8; 32]> {
None
}
}

/// We need a bogus value to send up when we don't want to change
/// the oracle addresses.
pub fn null_pyth_product_oracle_pubkey() -> Pubkey {
Pubkey::from_str("nu11orac1e111111111111111111111111111111111").unwrap()
}
11 changes: 10 additions & 1 deletion token-lending/program/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,14 +364,17 @@ pub enum LendingInstruction {
},

// 16
/// Updates a reserve config parameter
/// Updates a reserves config and a reserve price oracle pubkeys
///
/// Accounts expected by this instruction:
///
/// 1. `[writable]` Reserve account - refreshed
/// 2 `[]` Lending market account.
/// 3 `[]` Derived lending market authority.
/// 4 `[signer]` Lending market owner.
/// 5 `[]` Pyth product key.
/// 6 `[]` Pyth price key.
/// 7 `[]` Switchboard key.
UpdateReserveConfig {
/// Reserve config to update to
config: ReserveConfig,
Expand Down Expand Up @@ -1190,6 +1193,9 @@ pub fn update_reserve_config(
reserve_pubkey: Pubkey,
lending_market_pubkey: Pubkey,
lending_market_owner_pubkey: Pubkey,
pyth_product_pubkey: Pubkey,
pyth_price_pubkey: Pubkey,
switchboard_feed_pubkey: Pubkey,
) -> Instruction {
let (lending_market_authority_pubkey, _bump_seed) = Pubkey::find_program_address(
&[&lending_market_pubkey.to_bytes()[..PUBKEY_BYTES]],
Expand All @@ -1200,6 +1206,9 @@ pub fn update_reserve_config(
AccountMeta::new_readonly(lending_market_pubkey, false),
AccountMeta::new_readonly(lending_market_authority_pubkey, false),
AccountMeta::new_readonly(lending_market_owner_pubkey, true),
AccountMeta::new_readonly(pyth_product_pubkey, false),
AccountMeta::new_readonly(pyth_price_pubkey, false),
AccountMeta::new_readonly(switchboard_feed_pubkey, false),
];
Instruction {
program_id,
Expand Down
128 changes: 80 additions & 48 deletions token-lending/program/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ use solana_program::{
};
use spl_token::solana_program::instruction::AccountMeta;
use spl_token::state::{Account, Mint};
use std::convert::TryInto;
use std::result::Result;
use std::{convert::TryInto, result::Result};
use switchboard_program::{
get_aggregator, get_aggregator_result, AggregatorState, RoundResult, SwitchboardAccountType,
};
Expand Down Expand Up @@ -261,51 +260,9 @@ fn process_init_reserve(
return Err(LendingError::InvalidSigner.into());
}

if &lending_market.oracle_program_id != pyth_product_info.owner {
msg!("Pyth product account provided is not owned by the lending market oracle program");
return Err(LendingError::InvalidOracleConfig.into());
}
if &lending_market.oracle_program_id != pyth_price_info.owner {
msg!("Pyth price account provided is not owned by the lending market oracle program");
return Err(LendingError::InvalidOracleConfig.into());
}

let pyth_product_data = pyth_product_info.try_borrow_data()?;
let pyth_product = pyth::load::<pyth::Product>(&pyth_product_data)
.map_err(|_| ProgramError::InvalidAccountData)?;
if pyth_product.magic != pyth::MAGIC {
msg!("Pyth product account provided is not a valid Pyth account");
return Err(LendingError::InvalidOracleConfig.into());
}
if pyth_product.ver != pyth::VERSION_2 {
msg!("Pyth product account provided has a different version than expected");
return Err(LendingError::InvalidOracleConfig.into());
}
if pyth_product.atype != pyth::AccountType::Product as u32 {
msg!("Pyth product account provided is not a valid Pyth product account");
return Err(LendingError::InvalidOracleConfig.into());
}
validate_pyth_keys(&lending_market, pyth_product_info, pyth_price_info)?;
validate_switchboard_keys(&lending_market, switchboard_feed_info)?;

let pyth_price_pubkey_bytes: &[u8; 32] = pyth_price_info
.key
.as_ref()
.try_into()
.map_err(|_| LendingError::InvalidAccountInput)?;
if &pyth_product.px_acc.val != pyth_price_pubkey_bytes {
msg!("Pyth product price account does not match the Pyth price provided");
return Err(LendingError::InvalidOracleConfig.into());
}

let quote_currency = get_pyth_product_quote_currency(pyth_product)?;
if lending_market.quote_currency != quote_currency {
msg!("Lending market quote currency does not match the oracle quote currency");
return Err(LendingError::InvalidOracleConfig.into());
}

if &lending_market.switchboard_oracle_program_id != switchboard_feed_info.owner {
msg!("Switchboard account provided is not owned by the switchboard oracle program");
return Err(LendingError::InvalidOracleConfig.into());
}
let market_price = get_price(switchboard_feed_info, pyth_price_info, clock)?;

let authority_signer_seeds = &[
Expand Down Expand Up @@ -1980,19 +1937,21 @@ 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 account_info_iter = &mut accounts.iter();
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 pyth_product_info = next_account_info(account_info_iter)?;
let pyth_price_info = next_account_info(account_info_iter)?;
let switchboard_feed_info = next_account_info(account_info_iter)?;

let mut reserve = Reserve::unpack(&reserve_info.data.borrow())?;
if reserve_info.owner != program_id {
Expand Down Expand Up @@ -2034,6 +1993,17 @@ fn process_update_reserve_config(
);
return Err(LendingError::InvalidMarketAuthority.into());
}

if *pyth_price_info.key != reserve.liquidity.pyth_oracle_pubkey {
validate_pyth_keys(&lending_market, pyth_product_info, pyth_price_info)?;
reserve.liquidity.pyth_oracle_pubkey = *pyth_product_info.key;
}

if *switchboard_feed_info.key != reserve.liquidity.switchboard_oracle_pubkey {
validate_switchboard_keys(&lending_market, switchboard_feed_info)?;
reserve.liquidity.switchboard_oracle_pubkey = *switchboard_feed_info.key;
}

reserve.config = config;
Reserve::pack(reserve, &mut reserve_info.data.borrow_mut())?;
Ok(())
Expand Down Expand Up @@ -2384,6 +2354,68 @@ fn validate_reserve_config(config: ReserveConfig) -> ProgramResult {
Ok(())
}

/// validates pyth AccountInfos
#[inline(always)]
fn validate_pyth_keys(
lending_market: &LendingMarket,
pyth_product_info: &AccountInfo,
pyth_price_info: &AccountInfo,
) -> ProgramResult {
if &lending_market.oracle_program_id != pyth_product_info.owner {
msg!("Pyth product account provided is not owned by the lending market oracle program");
return Err(LendingError::InvalidOracleConfig.into());
}
if &lending_market.oracle_program_id != pyth_price_info.owner {
msg!("Pyth price account provided is not owned by the lending market oracle program");
return Err(LendingError::InvalidOracleConfig.into());
}

let pyth_product_data = pyth_product_info.try_borrow_data()?;
let pyth_product = pyth::load::<pyth::Product>(&pyth_product_data)
.map_err(|_| ProgramError::InvalidAccountData)?;
if pyth_product.magic != pyth::MAGIC {
msg!("Pyth product account provided is not a valid Pyth account");
return Err(LendingError::InvalidOracleConfig.into());
}
if pyth_product.ver != pyth::VERSION_2 {
msg!("Pyth product account provided has a different version than expected");
return Err(LendingError::InvalidOracleConfig.into());
}
if pyth_product.atype != pyth::AccountType::Product as u32 {
msg!("Pyth product account provided is not a valid Pyth product account");
return Err(LendingError::InvalidOracleConfig.into());
}

let pyth_price_pubkey_bytes: &[u8; 32] = pyth_price_info
.key
.as_ref()
.try_into()
.map_err(|_| LendingError::InvalidAccountInput)?;
if &pyth_product.px_acc.val != pyth_price_pubkey_bytes {
msg!("Pyth product price account does not match the Pyth price provided");
return Err(LendingError::InvalidOracleConfig.into());
}

let quote_currency = get_pyth_product_quote_currency(pyth_product)?;
if lending_market.quote_currency != quote_currency {
msg!("Lending market quote currency does not match the oracle quote currency");
return Err(LendingError::InvalidOracleConfig.into());
}
Ok(())
}

/// validates switchboard AccountInfo
fn validate_switchboard_keys(
lending_market: &LendingMarket,
switchboard_feed_info: &AccountInfo,
) -> ProgramResult {
if &lending_market.switchboard_oracle_program_id != switchboard_feed_info.owner {
msg!("Switchboard account provided is not owned by the switchboard oracle program");
return Err(LendingError::InvalidOracleConfig.into());
}
Ok(())
}

struct TokenInitializeMintParams<'a: 'b, 'b> {
mint: AccountInfo<'a>,
rent: AccountInfo<'a>,
Expand Down
3 changes: 3 additions & 0 deletions token-lending/program/tests/init_reserve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,9 @@ async fn test_update_reserve_config() {
test_reserve.pubkey,
lending_market.pubkey,
lending_market.owner.pubkey(),
oracle.pyth_product_pubkey,
oracle.pyth_price_pubkey,
oracle.switchboard_feed_pubkey,
)],
Some(&payer.pubkey()),
);
Expand Down

0 comments on commit 6ab9d7b

Please sign in to comment.