From 9b7da68be239abbab5b1514b5c0d294e340ecc3d Mon Sep 17 00:00:00 2001 From: nope <83512286+nope-finance@users.noreply.github.com> Date: Thu, 29 Sep 2022 16:56:13 -0700 Subject: [PATCH] allow passing just pyth to save bytes (#105) * oallow passing just pyth to save bytes * fixing to check that there is a next account and 1 other check * use option instead * added a test --- token-lending/program/src/processor.rs | 29 ++- .../program/tests/refresh_reserve.rs | 166 +++++++++++++++++- 2 files changed, 187 insertions(+), 8 deletions(-) diff --git a/token-lending/program/src/processor.rs b/token-lending/program/src/processor.rs index e007be39be1..78f214a8af1 100644 --- a/token-lending/program/src/processor.rs +++ b/token-lending/program/src/processor.rs @@ -320,7 +320,7 @@ fn process_init_reserve( validate_pyth_keys(&lending_market, pyth_product_info, pyth_price_info)?; validate_switchboard_keys(&lending_market, switchboard_feed_info)?; - let market_price = get_price(switchboard_feed_info, pyth_price_info, clock)?; + let market_price = get_price(Some(switchboard_feed_info), pyth_price_info, clock)?; let authority_signer_seeds = &[ lending_market_info.key.as_ref(), @@ -427,7 +427,13 @@ fn process_refresh_reserve(program_id: &Pubkey, accounts: &[AccountInfo]) -> Pro let account_info_iter = &mut accounts.iter().peekable(); let reserve_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)?; + // set switchboard to a placeholder account info + let mut switchboard_feed_info = None; + // if the next account info exists and is not the clock set it to be switchboard + let switchboard_peek = account_info_iter.peek().map(|a| a.key); + if switchboard_peek.is_some() && switchboard_peek != Some(&clock::ID) { + switchboard_feed_info = Some(next_account_info(account_info_iter)?); + } let clock = &Clock::get()?; if account_info_iter.peek().map(|a| a.key) == Some(&clock::ID) { next_account_info(account_info_iter)?; @@ -445,7 +451,7 @@ fn _refresh_reserve<'a>( program_id: &Pubkey, reserve_info: &AccountInfo<'a>, pyth_price_info: &AccountInfo<'a>, - switchboard_feed_info: &AccountInfo<'a>, + switchboard_feed_info: Option<&AccountInfo<'a>>, clock: &Clock, ) -> ProgramResult { let mut reserve = Reserve::unpack(&reserve_info.data.borrow())?; @@ -457,8 +463,11 @@ fn _refresh_reserve<'a>( msg!("Reserve liquidity pyth oracle does not match the reserve liquidity pyth oracle provided"); return Err(LendingError::InvalidAccountInput.into()); } - - if &reserve.liquidity.switchboard_oracle_pubkey != switchboard_feed_info.key { + // the first check is to allow for the only passing in pyth case + // TODO maybe change this to is_some_and later + if switchboard_feed_info.is_some() + && &reserve.liquidity.switchboard_oracle_pubkey != switchboard_feed_info.unwrap().key + { msg!("Reserve liquidity switchboard oracle does not match the reserve liquidity switchboard oracle provided"); return Err(LendingError::InvalidOracleConfig.into()); } @@ -2564,7 +2573,7 @@ fn get_pyth_product_quote_currency(pyth_product: &pyth::Product) -> Result<[u8; } fn get_price( - switchboard_feed_info: &AccountInfo, + switchboard_feed_info: Option<&AccountInfo>, pyth_price_account_info: &AccountInfo, clock: &Clock, ) -> Result { @@ -2572,7 +2581,13 @@ fn get_price( if pyth_price != Decimal::zero() { return Ok(pyth_price); } - get_switchboard_price(switchboard_feed_info, clock) + + // if switchboard was not passed in don't try to grab the price + if let Some(switchboard_feed_info_unwrapped) = switchboard_feed_info { + return get_switchboard_price(switchboard_feed_info_unwrapped, clock); + } + + Err(LendingError::InvalidOracleConfig.into()) } fn get_pyth_price(pyth_price_info: &AccountInfo, clock: &Clock) -> Result { diff --git a/token-lending/program/tests/refresh_reserve.rs b/token-lending/program/tests/refresh_reserve.rs index ce302ab13ac..a6b6d5b5b61 100644 --- a/token-lending/program/tests/refresh_reserve.rs +++ b/token-lending/program/tests/refresh_reserve.rs @@ -3,13 +3,18 @@ mod helpers; use helpers::*; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + sysvar, +}; use solana_program_test::*; use solana_sdk::{ signature::{Keypair, Signer}, transaction::Transaction, }; use solend_program::{ - instruction::refresh_reserve, + instruction::{refresh_reserve, LendingInstruction}, math::{Decimal, Rate, TryAdd, TryDiv, TryMul, TrySub}, processor::process_instruction, state::SLOTS_PER_YEAR, @@ -152,3 +157,162 @@ async fn test_success() { sol_reserve.liquidity.accumulated_protocol_fees_wads ); } + +#[tokio::test] +async fn test_success_no_switchboard() { + let mut test = ProgramTest::new( + "solend_program", + solend_program::id(), + processor!(process_instruction), + ); + + // limit to track compute unit increase + test.set_compute_max_units(31_000); + + const SOL_RESERVE_LIQUIDITY_LAMPORTS: u64 = 100 * LAMPORTS_TO_SOL; + const USDC_RESERVE_LIQUIDITY_FRACTIONAL: u64 = 100 * FRACTIONAL_TO_USDC; + const BORROW_AMOUNT: u64 = 100; + + let user_accounts_owner = Keypair::new(); + let lending_market = add_lending_market(&mut test); + + let mut reserve_config = test_reserve_config(); + reserve_config.loan_to_value_ratio = 80; + + // Configure reserve to a fixed borrow rate of 1% + const BORROW_RATE: u8 = 1; + reserve_config.min_borrow_rate = BORROW_RATE; + reserve_config.optimal_borrow_rate = BORROW_RATE; + reserve_config.optimal_utilization_rate = 100; + + let usdc_mint = add_usdc_mint(&mut test); + let usdc_oracle = add_usdc_oracle(&mut test); + let usdc_test_reserve = add_reserve( + &mut test, + &lending_market, + &usdc_oracle, + &user_accounts_owner, + AddReserveArgs { + borrow_amount: BORROW_AMOUNT, + liquidity_amount: USDC_RESERVE_LIQUIDITY_FRACTIONAL, + liquidity_mint_decimals: usdc_mint.decimals, + liquidity_mint_pubkey: usdc_mint.pubkey, + config: reserve_config, + slots_elapsed: 1, // elapsed from 1; clock.slot = 2 + ..AddReserveArgs::default() + }, + ); + + let sol_oracle = add_sol_oracle(&mut test); + let sol_test_reserve = add_reserve( + &mut test, + &lending_market, + &sol_oracle, + &user_accounts_owner, + AddReserveArgs { + borrow_amount: BORROW_AMOUNT, + liquidity_amount: SOL_RESERVE_LIQUIDITY_LAMPORTS, + liquidity_mint_decimals: 9, + liquidity_mint_pubkey: spl_token::native_mint::id(), + config: reserve_config, + slots_elapsed: 1, // elapsed from 1; clock.slot = 2 + ..AddReserveArgs::default() + }, + ); + + let mut test_context = test.start_with_context().await; + test_context.warp_to_slot(3).unwrap(); // clock.slot = 3 + + let ProgramTestContext { + mut banks_client, + payer, + last_blockhash: recent_blockhash, + .. + } = test_context; + + let mut transaction = Transaction::new_with_payer( + &[ + refresh_reserve_no_switchboard( + solend_program::id(), + usdc_test_reserve.pubkey, + usdc_oracle.pyth_price_pubkey, + false, + ), + refresh_reserve_no_switchboard( + solend_program::id(), + sol_test_reserve.pubkey, + sol_oracle.pyth_price_pubkey, + true, + ), + ], + Some(&payer.pubkey()), + ); + + transaction.sign(&[&payer], recent_blockhash); + assert!(banks_client.process_transaction(transaction).await.is_ok()); + + let sol_reserve = sol_test_reserve.get_state(&mut banks_client).await; + let usdc_reserve = usdc_test_reserve.get_state(&mut banks_client).await; + + let slot_rate = Rate::from_percent(BORROW_RATE) + .try_div(SLOTS_PER_YEAR) + .unwrap(); + let compound_rate = Rate::one().try_add(slot_rate).unwrap(); + let compound_borrow = Decimal::from(BORROW_AMOUNT).try_mul(compound_rate).unwrap(); + let net_new_debt = compound_borrow + .try_sub(Decimal::from(BORROW_AMOUNT)) + .unwrap(); + let protocol_take_rate = Rate::from_percent(usdc_reserve.config.protocol_take_rate); + let delta_accumulated_protocol_fees = net_new_debt.try_mul(protocol_take_rate).unwrap(); + + assert_eq!( + sol_reserve.liquidity.cumulative_borrow_rate_wads, + compound_rate.into() + ); + assert_eq!( + sol_reserve.liquidity.cumulative_borrow_rate_wads, + usdc_reserve.liquidity.cumulative_borrow_rate_wads + ); + assert_eq!(sol_reserve.liquidity.borrowed_amount_wads, compound_borrow); + assert_eq!( + sol_reserve.liquidity.borrowed_amount_wads, + usdc_reserve.liquidity.borrowed_amount_wads + ); + assert_eq!( + sol_reserve.liquidity.market_price, + sol_test_reserve.market_price + ); + assert_eq!( + usdc_reserve.liquidity.market_price, + usdc_test_reserve.market_price + ); + assert_eq!( + delta_accumulated_protocol_fees, + usdc_reserve.liquidity.accumulated_protocol_fees_wads + ); + assert_eq!( + delta_accumulated_protocol_fees, + sol_reserve.liquidity.accumulated_protocol_fees_wads + ); +} + +/// Creates a `RefreshReserve` instruction +pub fn refresh_reserve_no_switchboard( + program_id: Pubkey, + reserve_pubkey: Pubkey, + reserve_liquidity_pyth_oracle_pubkey: Pubkey, + with_clock: bool, +) -> Instruction { + let mut accounts = vec![ + AccountMeta::new(reserve_pubkey, false), + AccountMeta::new_readonly(reserve_liquidity_pyth_oracle_pubkey, false), + ]; + if with_clock { + accounts.push(AccountMeta::new_readonly(sysvar::clock::id(), false)) + } + Instruction { + program_id, + accounts, + data: LendingInstruction::RefreshReserve.pack(), + } +}