From 72723b99fd4b4dace1c03104a62c5b52e36e5d14 Mon Sep 17 00:00:00 2001 From: dasichuan <0xdasichuan@gmail.com> Date: Fri, 25 Feb 2022 11:35:29 -0800 Subject: [PATCH 1/3] log price update --- token-lending/program/src/lib.rs | 1 + token-lending/program/src/logs.rs | 73 ++++++++++++++++++++++++++ token-lending/program/src/processor.rs | 1 + token-lending/program/src/pyth.rs | 14 ++++- 4 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 token-lending/program/src/logs.rs diff --git a/token-lending/program/src/lib.rs b/token-lending/program/src/lib.rs index d9d1f5265bc..8c73f9beadd 100644 --- a/token-lending/program/src/lib.rs +++ b/token-lending/program/src/lib.rs @@ -5,6 +5,7 @@ pub mod entrypoint; pub mod error; pub mod instruction; +pub mod logs; pub mod math; pub mod processor; pub mod pyth; diff --git a/token-lending/program/src/logs.rs b/token-lending/program/src/logs.rs new file mode 100644 index 00000000000..886406d9097 --- /dev/null +++ b/token-lending/program/src/logs.rs @@ -0,0 +1,73 @@ +#![allow(missing_docs)] +use solana_program::{ + msg, + pubkey::Pubkey, +}; +use std::{fmt}; +use crate::{ + pyth, + math::{Decimal}, +}; + + +#[derive(Debug)] +enum LogEventType { + PythOraclePriceUpdateType, + SwitchboardV1OraclePriceUpdateType, +} + +impl fmt::Display for LogEventType{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +pub fn emit_log_event(e: &dyn LogEvent) { + msg!("Solend Log Event"); + msg!(&e.to_string()); +} + + +pub trait LogEvent { + fn to_string(&self) -> String; +} + +pub struct PythOraclePriceUpdate { + oracle_pubkey: Pubkey, + price: i64, + conf: u64, + status: pyth::PriceStatus, + published_slot: u64, +} + +impl LogEvent for PythOraclePriceUpdate { + fn to_string(&self) -> String { + return format!( + "{},{},{},{},{},{}", + LogEventType::PythOraclePriceUpdateType.to_string(), + self.oracle_pubkey.to_string(), + self.price.to_string(), + self.conf.to_string(), + self.status.to_string(), + self.published_slot, + ); + } +} + +pub struct SwitchboardV1OraclePriceUpdate { + oracle_pubkey: Pubkey, + price: Decimal, + published_slot: u64, +} + +impl LogEvent for SwitchboardV1OraclePriceUpdate { + fn to_string(&self) -> String { + return format!( + "{},{},{},{}", + LogEventType::SwitchboardV1OraclePriceUpdateType.to_string(), + self.oracle_pubkey.to_string(), + self.price.to_string(), + self.published_slot, + ); + } +} diff --git a/token-lending/program/src/processor.rs b/token-lending/program/src/processor.rs index 3e0a802ec39..d9f2301ecfa 100644 --- a/token-lending/program/src/processor.rs +++ b/token-lending/program/src/processor.rs @@ -4,6 +4,7 @@ use crate::{ self as spl_token_lending, error::LendingError, instruction::LendingInstruction, + logs::{emit_log_event}, math::{Decimal, Rate, TryAdd, TryDiv, TryMul, TrySub, WAD}, pyth, state::{ diff --git a/token-lending/program/src/pyth.rs b/token-lending/program/src/pyth.rs index 10ec0408a62..bf83b2d5fee 100644 --- a/token-lending/program/src/pyth.rs +++ b/token-lending/program/src/pyth.rs @@ -4,7 +4,10 @@ use bytemuck::{ cast_slice, cast_slice_mut, from_bytes, from_bytes_mut, try_cast_slice, try_cast_slice_mut, Pod, PodCastError, Zeroable, }; -use std::mem::size_of; +use std::{ + mem::size_of, + fmt, +}; pub const MAGIC: u32 = 0xa1b2c3d4; pub const VERSION_2: u32 = 2; @@ -29,7 +32,7 @@ pub enum AccountType { Price, } -#[derive(PartialEq, Copy, Clone)] +#[derive(PartialEq, Copy, Clone, Debug)] #[repr(C)] pub enum PriceStatus { Unknown, @@ -38,6 +41,13 @@ pub enum PriceStatus { Auction, } +impl fmt::Display for PriceStatus{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + + #[derive(PartialEq, Copy, Clone)] #[repr(C)] pub enum CorpAction { From d346d0b26924758e614040954c2bcec45cc06801 Mon Sep 17 00:00:00 2001 From: dasichuan <0xdasichuan@gmail.com> Date: Fri, 25 Feb 2022 11:48:19 -0800 Subject: [PATCH 2/3] Log price update --- token-lending/program/src/logs.rs | 36 ++++++++++---------------- token-lending/program/src/processor.rs | 20 +++++++++++--- token-lending/program/src/pyth.rs | 14 ++-------- 3 files changed, 31 insertions(+), 39 deletions(-) diff --git a/token-lending/program/src/logs.rs b/token-lending/program/src/logs.rs index 886406d9097..d4267569195 100644 --- a/token-lending/program/src/logs.rs +++ b/token-lending/program/src/logs.rs @@ -1,14 +1,7 @@ #![allow(missing_docs)] -use solana_program::{ - msg, - pubkey::Pubkey, -}; -use std::{fmt}; -use crate::{ - pyth, - math::{Decimal}, -}; - +use crate::math::Decimal; +use solana_program::{msg, pubkey::Pubkey}; +use std::fmt; #[derive(Debug)] enum LogEventType { @@ -16,7 +9,7 @@ enum LogEventType { SwitchboardV1OraclePriceUpdateType, } -impl fmt::Display for LogEventType{ +impl fmt::Display for LogEventType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } @@ -27,43 +20,40 @@ pub fn emit_log_event(e: &dyn LogEvent) { msg!(&e.to_string()); } - pub trait LogEvent { fn to_string(&self) -> String; } pub struct PythOraclePriceUpdate { - oracle_pubkey: Pubkey, - price: i64, - conf: u64, - status: pyth::PriceStatus, - published_slot: u64, + pub oracle_pubkey: Pubkey, + pub price: Decimal, + pub conf: u64, + pub published_slot: u64, } impl LogEvent for PythOraclePriceUpdate { fn to_string(&self) -> String { return format!( - "{},{},{},{},{},{}", + "{},{},{},{},{}", LogEventType::PythOraclePriceUpdateType.to_string(), self.oracle_pubkey.to_string(), self.price.to_string(), self.conf.to_string(), - self.status.to_string(), self.published_slot, ); } } pub struct SwitchboardV1OraclePriceUpdate { - oracle_pubkey: Pubkey, - price: Decimal, - published_slot: u64, + pub oracle_pubkey: Pubkey, + pub price: Decimal, + pub published_slot: u64, } impl LogEvent for SwitchboardV1OraclePriceUpdate { fn to_string(&self) -> String { return format!( - "{},{},{},{}", + "{},{},{},{}", LogEventType::SwitchboardV1OraclePriceUpdateType.to_string(), self.oracle_pubkey.to_string(), self.price.to_string(), diff --git a/token-lending/program/src/processor.rs b/token-lending/program/src/processor.rs index d9f2301ecfa..6a25dfec404 100644 --- a/token-lending/program/src/processor.rs +++ b/token-lending/program/src/processor.rs @@ -4,7 +4,7 @@ use crate::{ self as spl_token_lending, error::LendingError, instruction::LendingInstruction, - logs::{emit_log_event}, + logs::{emit_log_event, PythOraclePriceUpdate, SwitchboardV1OraclePriceUpdate}, math::{Decimal, Rate, TryAdd, TryDiv, TryMul, TrySub, WAD}, pyth, state::{ @@ -2341,6 +2341,12 @@ fn get_pyth_price(pyth_price_info: &AccountInfo, clock: &Clock) -> Result= STALE_AFTER_SLOTS_ELAPSED { msg!("Switchboard oracle price is stale"); @@ -2385,7 +2391,13 @@ fn get_switchboard_price( let price_quotient = 10u64.pow(9); let price = ((price_quotient as f64) * price_float) as u128; - Decimal::from(price).try_div(price_quotient) + let market_price = Decimal::from(price).try_div(price_quotient)?; + emit_log_event(&SwitchboardV1OraclePriceUpdate { + oracle_pubkey: *switchboard_feed_info.key, + price: market_price, + published_slot: open_slot, + }); + Ok(market_price) } /// Issue a spl_token `InitializeAccount` instruction. diff --git a/token-lending/program/src/pyth.rs b/token-lending/program/src/pyth.rs index bf83b2d5fee..10ec0408a62 100644 --- a/token-lending/program/src/pyth.rs +++ b/token-lending/program/src/pyth.rs @@ -4,10 +4,7 @@ use bytemuck::{ cast_slice, cast_slice_mut, from_bytes, from_bytes_mut, try_cast_slice, try_cast_slice_mut, Pod, PodCastError, Zeroable, }; -use std::{ - mem::size_of, - fmt, -}; +use std::mem::size_of; pub const MAGIC: u32 = 0xa1b2c3d4; pub const VERSION_2: u32 = 2; @@ -32,7 +29,7 @@ pub enum AccountType { Price, } -#[derive(PartialEq, Copy, Clone, Debug)] +#[derive(PartialEq, Copy, Clone)] #[repr(C)] pub enum PriceStatus { Unknown, @@ -41,13 +38,6 @@ pub enum PriceStatus { Auction, } -impl fmt::Display for PriceStatus{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self) - } -} - - #[derive(PartialEq, Copy, Clone)] #[repr(C)] pub enum CorpAction { From 0f557dd826c36f0396e6b920701843a16ef2765e Mon Sep 17 00:00:00 2001 From: dasichuan <0xdasichuan@gmail.com> Date: Fri, 25 Feb 2022 14:01:07 -0800 Subject: [PATCH 3/3] Simplify stuff --- Cargo.lock | 9 ++ token-lending/program/Cargo.toml | 3 + token-lending/program/src/lib.rs | 3 + token-lending/program/src/logs.rs | 115 ++++++++++---- token-lending/program/src/math/decimal.rs | 3 +- token-lending/program/src/processor.rs | 111 ++++++++++--- token-lending/program/src/state/reserve.rs | 7 + .../tests/borrow_obligation_liquidity.rs | 150 +++++++++++++++++- .../tests/deposit_obligation_collateral.rs | 2 +- .../tests/deposit_reserve_liquidity.rs | 2 +- ...rve_liquidity_and_obligation_collateral.rs | 2 +- token-lending/program/tests/helpers/mod.rs | 2 +- token-lending/program/tests/init_reserve.rs | 4 +- .../program/tests/liquidate_obligation.rs | 2 +- ...uidate_obligation_and_redeem_collateral.rs | 2 +- .../program/tests/obligation_end_to_end.rs | 2 +- .../tests/redeem_reserve_collateral.rs | 2 +- .../program/tests/refresh_obligation.rs | 2 +- .../program/tests/refresh_reserve.rs | 3 +- .../tests/repay_obligation_liquidity.rs | 2 +- .../tests/withdraw_obligation_collateral.rs | 4 +- 21 files changed, 358 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f29a0f5bdbd..6044b0614bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,6 +86,12 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +[[package]] +name = "arrform" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7cf566ecc5c9d82b973e81d30babf6583c9b497f86295c952d538c3254ef4e6" + [[package]] name = "ascii" version = "0.9.3" @@ -3320,6 +3326,7 @@ name = "spl-token-lending" version = "0.1.0" dependencies = [ "arrayref", + "arrform", "assert_matches", "base64 0.13.0", "bytemuck", @@ -3328,6 +3335,8 @@ dependencies = [ "num-traits", "proptest", "serde", + "serde_derive", + "serde_json", "serde_yaml", "solana-program", "solana-program-test", diff --git a/token-lending/program/Cargo.toml b/token-lending/program/Cargo.toml index 6b1ba47e5b6..2b8fcc283af 100644 --- a/token-lending/program/Cargo.toml +++ b/token-lending/program/Cargo.toml @@ -21,6 +21,9 @@ spl-token = { version = "3.2.0", features=["no-entrypoint"] } switchboard-program = "0.2.0" thiserror = "1.0" uint = "=0.9.0" +serde = "1.0" +serde_derive = "1.0" +serde_json = "1.0" [dev-dependencies] assert_matches = "1.5.0" diff --git a/token-lending/program/src/lib.rs b/token-lending/program/src/lib.rs index 8c73f9beadd..070d58ed942 100644 --- a/token-lending/program/src/lib.rs +++ b/token-lending/program/src/lib.rs @@ -14,6 +14,9 @@ pub mod state; // Export current sdk types for downstream users building with a different sdk version pub use solana_program; +#[macro_use] +extern crate serde_derive; + solana_program::declare_id!("So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo"); /// Canonical null pubkey. Prints out as "nu11111111111111111111111111111111111111111" diff --git a/token-lending/program/src/logs.rs b/token-lending/program/src/logs.rs index d4267569195..902cf39e334 100644 --- a/token-lending/program/src/logs.rs +++ b/token-lending/program/src/logs.rs @@ -1,12 +1,20 @@ #![allow(missing_docs)] use crate::math::Decimal; -use solana_program::{msg, pubkey::Pubkey}; +use solana_program::pubkey::Pubkey; use std::fmt; -#[derive(Debug)] -enum LogEventType { - PythOraclePriceUpdateType, - SwitchboardV1OraclePriceUpdateType, +extern crate serde; +extern crate serde_json; + +#[derive(Debug, Serialize)] +pub enum LogEventType { + ObligationStateUpdate, + ProgramVersion, + PythError, + PythOraclePriceUpdate, + ReserveStateUpdate, + SwitchboardError, + SwitchboardV1OraclePriceUpdate, } impl fmt::Display for LogEventType { @@ -15,49 +23,92 @@ impl fmt::Display for LogEventType { } } -pub fn emit_log_event(e: &dyn LogEvent) { - msg!("Solend Log Event"); - msg!(&e.to_string()); +fn pubkey_serialize(x: &Pubkey, s: S) -> Result +where + S: serde::ser::Serializer, +{ + s.serialize_str(&x.to_string()) } -pub trait LogEvent { - fn to_string(&self) -> String; +#[macro_export] +macro_rules! emit_log_event { + ($e:expr) => { + msg!("solend-event-log:"); + msg!(&serde_json::to_string($e).unwrap()); + }; } +#[derive(Serialize)] pub struct PythOraclePriceUpdate { + pub event_type: LogEventType, + #[serde(serialize_with = "pubkey_serialize")] pub oracle_pubkey: Pubkey, pub price: Decimal, - pub conf: u64, + pub confidence: u64, pub published_slot: u64, } -impl LogEvent for PythOraclePriceUpdate { - fn to_string(&self) -> String { - return format!( - "{},{},{},{},{}", - LogEventType::PythOraclePriceUpdateType.to_string(), - self.oracle_pubkey.to_string(), - self.price.to_string(), - self.conf.to_string(), - self.published_slot, - ); - } +#[derive(Serialize)] +pub struct PythError { + pub event_type: LogEventType, + #[serde(serialize_with = "pubkey_serialize")] + pub oracle_pubkey: Pubkey, + pub error_message: String, } +#[derive(Serialize)] pub struct SwitchboardV1OraclePriceUpdate { + pub event_type: LogEventType, + #[serde(serialize_with = "pubkey_serialize")] pub oracle_pubkey: Pubkey, pub price: Decimal, pub published_slot: u64, } -impl LogEvent for SwitchboardV1OraclePriceUpdate { - fn to_string(&self) -> String { - return format!( - "{},{},{},{}", - LogEventType::SwitchboardV1OraclePriceUpdateType.to_string(), - self.oracle_pubkey.to_string(), - self.price.to_string(), - self.published_slot, - ); - } +#[derive(Serialize)] +pub struct SwitchboardError { + pub event_type: LogEventType, + #[serde(serialize_with = "pubkey_serialize")] + pub oracle_pubkey: Pubkey, + pub error_message: String, +} + +#[derive(Serialize)] +pub struct ProgramVersion { + pub event_type: LogEventType, + pub version: u8, +} + +#[derive(Serialize)] +pub struct ReserveStateUpdate { + pub event_type: LogEventType, + pub available_amount: u64, + pub borrowed_amount_wads: Decimal, + pub cumulative_borrow_rate_wads: Decimal, + pub collateral_mint_total_supply: u64, + pub collateral_exchange_rate: String, +} + +// ObligationStateUpdate intentionally does not contain the obligation ID +// to save on compute since it is contained in the transaction itself. +#[derive(Serialize)] +pub struct ObligationStateUpdate { + pub event_type: LogEventType, + pub allowed_borrow_value: Decimal, + pub unhealthy_borrow_value: Decimal, + pub deposits: Vec, + pub borrows: Vec, +} + +#[derive(Serialize)] +pub struct DepositLog { + pub reserve_id_index: u8, + pub deposited_amount: u64, +} + +#[derive(Serialize)] +pub struct BorrowLog { + pub reserve_id_index: u8, + pub borrowed_amount_wads: Decimal, + pub cumulative_borrow_rate_wads: Decimal, } diff --git a/token-lending/program/src/math/decimal.rs b/token-lending/program/src/math/decimal.rs index 8cc3fc8baa9..fe754d63dcc 100644 --- a/token-lending/program/src/math/decimal.rs +++ b/token-lending/program/src/math/decimal.rs @@ -22,11 +22,12 @@ use uint::construct_uint; // U192 with 192 bits consisting of 3 x 64-bit words construct_uint! { + #[derive(Serialize)] pub struct U192(3); } /// Large decimal values, precise to 18 digits -#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Eq, Ord)] +#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Eq, Ord, Serialize)] pub struct Decimal(pub U192); impl Decimal { diff --git a/token-lending/program/src/processor.rs b/token-lending/program/src/processor.rs index 6a25dfec404..3d1aac9b15b 100644 --- a/token-lending/program/src/processor.rs +++ b/token-lending/program/src/processor.rs @@ -1,17 +1,21 @@ //! Program state processor use crate::{ - self as spl_token_lending, + self as spl_token_lending, emit_log_event, error::LendingError, instruction::LendingInstruction, - logs::{emit_log_event, PythOraclePriceUpdate, SwitchboardV1OraclePriceUpdate}, + logs::{ + BorrowLog, DepositLog, LogEventType, ObligationStateUpdate, ProgramVersion, PythError, + PythOraclePriceUpdate, ReserveStateUpdate, SwitchboardError, + SwitchboardV1OraclePriceUpdate, + }, math::{Decimal, Rate, TryAdd, TryDiv, TryMul, TrySub, WAD}, pyth, state::{ CalculateBorrowResult, CalculateLiquidationResult, CalculateRepayResult, InitLendingMarketParams, InitObligationParams, InitReserveParams, LendingMarket, NewReserveCollateralParams, NewReserveLiquidityParams, Obligation, Reserve, - ReserveCollateral, ReserveConfig, ReserveLiquidity, + ReserveCollateral, ReserveConfig, ReserveLiquidity, PROGRAM_VERSION, }, }; use num_traits::FromPrimitive; @@ -40,6 +44,11 @@ pub fn process_instruction( accounts: &[AccountInfo], input: &[u8], ) -> ProgramResult { + emit_log_event!(&ProgramVersion { + event_type: LogEventType::ProgramVersion, + version: PROGRAM_VERSION, + }); + let instruction = LendingInstruction::unpack(input)?; match instruction { LendingInstruction::InitLendingMarket { @@ -440,6 +449,14 @@ fn _refresh_reserve_interest<'a>( reserve.accrue_interest(clock.slot)?; reserve.last_update.update_slot(clock.slot); + emit_log_event!(&ReserveStateUpdate { + event_type: LogEventType::ReserveStateUpdate, + available_amount: reserve.liquidity.available_amount, + borrowed_amount_wads: reserve.liquidity.borrowed_amount_wads, + cumulative_borrow_rate_wads: reserve.liquidity.cumulative_borrow_rate_wads, + collateral_mint_total_supply: reserve.collateral.mint_total_supply, + collateral_exchange_rate: reserve.collateral_exchange_rate()?.to_string(), + }); Reserve::pack(reserve, &mut reserve_info.data.borrow_mut())?; Ok(()) @@ -773,7 +790,7 @@ fn process_refresh_obligation(program_id: &Pubkey, accounts: &[AccountInfo]) -> let account_info_iter = &mut accounts.iter().peekable(); let obligation_info = next_account_info(account_info_iter)?; let clock = &Clock::from_account_info(next_account_info(account_info_iter)?)?; - + let non_reserve_accounts: u8 = 2; let mut obligation = Obligation::unpack(&obligation_info.data.borrow())?; if obligation_info.owner != program_id { msg!("Obligation provided is not owned by the lending program"); @@ -784,6 +801,8 @@ fn process_refresh_obligation(program_id: &Pubkey, accounts: &[AccountInfo]) -> let mut borrowed_value = Decimal::zero(); let mut allowed_borrow_value = Decimal::zero(); let mut unhealthy_borrow_value = Decimal::zero(); + let mut borrow_logs = Vec::new(); + let mut deposit_logs = Vec::new(); for (index, collateral) in obligation.deposits.iter_mut().enumerate() { let deposit_reserve_info = next_account_info(account_info_iter)?; @@ -815,7 +834,6 @@ fn process_refresh_obligation(program_id: &Pubkey, accounts: &[AccountInfo]) -> let decimals = 10u64 .checked_pow(deposit_reserve.liquidity.mint_decimals as u32) .ok_or(LendingError::MathOverflow)?; - let market_value = deposit_reserve .collateral_exchange_rate()? .decimal_collateral_to_liquidity(collateral.deposited_amount.into())? @@ -832,6 +850,11 @@ fn process_refresh_obligation(program_id: &Pubkey, accounts: &[AccountInfo]) -> allowed_borrow_value.try_add(market_value.try_mul(loan_to_value_rate)?)?; unhealthy_borrow_value = unhealthy_borrow_value.try_add(market_value.try_mul(liquidation_threshold_rate)?)?; + + deposit_logs.push(DepositLog { + reserve_id_index: non_reserve_accounts + (index as u8), + deposited_amount: collateral.deposited_amount, + }); } for (index, liquidity) in obligation.borrows.iter_mut().enumerate() { @@ -874,6 +897,12 @@ fn process_refresh_obligation(program_id: &Pubkey, accounts: &[AccountInfo]) -> liquidity.market_value = market_value; borrowed_value = borrowed_value.try_add(market_value)?; + + borrow_logs.push(BorrowLog { + reserve_id_index: non_reserve_accounts + ((obligation.deposits.len() + index) as u8), + borrowed_amount_wads: liquidity.borrowed_amount_wads, + cumulative_borrow_rate_wads: liquidity.cumulative_borrow_rate_wads, + }); } if account_info_iter.peek().is_some() { @@ -888,6 +917,13 @@ fn process_refresh_obligation(program_id: &Pubkey, accounts: &[AccountInfo]) -> obligation.last_update.update_slot(clock.slot); Obligation::pack(obligation, &mut obligation_info.data.borrow_mut())?; + emit_log_event!(&ObligationStateUpdate { + event_type: LogEventType::ObligationStateUpdate, + allowed_borrow_value, + unhealthy_borrow_value, + deposits: deposit_logs, + borrows: borrow_logs, + }); Ok(()) } @@ -2279,15 +2315,24 @@ fn get_pyth_price(pyth_price_info: &AccountInfo, clock: &Clock) -> Result Result= STALE_AFTER_SLOTS_ELAPSED { - msg!("Pyth oracle price is stale"); + emit_log_event!(&PythError { + event_type: LogEventType::PythError, + oracle_pubkey: *pyth_price_info.key, + error_message: format!("Pyth oracle price is stale: {} slots old.", slots_elapsed), + }); return Err(LendingError::InvalidOracleConfig.into()); } let price: u64 = pyth_price.agg.price.try_into().map_err(|_| { - msg!("Oracle price cannot be negative"); + emit_log_event!(&PythError { + event_type: LogEventType::PythError, + oracle_pubkey: *pyth_price_info.key, + error_message: "Oracle price cannot be negative".to_string(), + }); LendingError::InvalidOracleConfig })?; @@ -2312,11 +2365,15 @@ fn get_pyth_price(pyth_price_info: &AccountInfo, clock: &Clock) -> Result 10% of price if conf.checked_mul(confidence_ratio).unwrap() > price { - msg!( - "Oracle price confidence is too wide. price: {}, conf: {}", - price, - conf, - ); + emit_log_event!(&PythError { + event_type: LogEventType::PythError, + oracle_pubkey: *pyth_price_info.key, + error_message: format!( + "Oracle price confidence is too wide. price: {}, conf: {}", + price, conf + ), + }); + return Err(LendingError::InvalidOracleConfig.into()); } @@ -2341,10 +2398,11 @@ fn get_pyth_price(pyth_price_info: &AccountInfo, clock: &Clock) -> Result= STALE_AFTER_SLOTS_ELAPSED { - msg!("Switchboard oracle price is stale"); + emit_log_event!(&SwitchboardError { + event_type: LogEventType::SwitchboardError, + oracle_pubkey: *switchboard_feed_info.key, + error_message: format!("Oracle price is stale by {} slots", slots_elapsed), + }); return Err(LendingError::InvalidOracleConfig.into()); } @@ -2392,7 +2458,8 @@ fn get_switchboard_price( let price = ((price_quotient as f64) * price_float) as u128; let market_price = Decimal::from(price).try_div(price_quotient)?; - emit_log_event(&SwitchboardV1OraclePriceUpdate { + emit_log_event!(&SwitchboardV1OraclePriceUpdate { + event_type: LogEventType::SwitchboardV1OraclePriceUpdate, oracle_pubkey: *switchboard_feed_info.key, price: market_price, published_slot: open_slot, diff --git a/token-lending/program/src/state/reserve.rs b/token-lending/program/src/state/reserve.rs index 89cafa32b8d..6f987c091cf 100644 --- a/token-lending/program/src/state/reserve.rs +++ b/token-lending/program/src/state/reserve.rs @@ -15,6 +15,7 @@ use solana_program::{ use std::{ cmp::Ordering, convert::{TryFrom, TryInto}, + fmt, }; /// Percentage of an obligation that can be repaid during each liquidation call @@ -599,6 +600,12 @@ impl CollateralExchangeRate { } } +impl fmt::Display for CollateralExchangeRate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0.to_string()) + } +} + impl From for Rate { fn from(exchange_rate: CollateralExchangeRate) -> Self { exchange_rate.0 diff --git a/token-lending/program/tests/borrow_obligation_liquidity.rs b/token-lending/program/tests/borrow_obligation_liquidity.rs index 7cc7ba2ac63..80ec3f0b75b 100644 --- a/token-lending/program/tests/borrow_obligation_liquidity.rs +++ b/token-lending/program/tests/borrow_obligation_liquidity.rs @@ -15,7 +15,7 @@ use spl_token_lending::{ instruction::{borrow_obligation_liquidity, refresh_obligation, refresh_reserve}, math::Decimal, processor::process_instruction, - state::{FeeCalculation, INITIAL_COLLATERAL_RATIO}, + state::{FeeCalculation, ObligationLiquidity, INITIAL_COLLATERAL_RATIO}, }; use std::u64; @@ -28,7 +28,7 @@ async fn test_borrow_usdc_fixed_amount() { ); // limit to track compute unit increase - test.set_bpf_compute_max_units(45_000); + test.set_bpf_compute_max_units(42_000); const USDC_TOTAL_BORROW_FRACTIONAL: u64 = 1_000 * FRACTIONAL_TO_USDC; const FEE_AMOUNT: u64 = 100; @@ -172,7 +172,7 @@ async fn test_borrow_sol_max_amount() { ); // limit to track compute unit increase - test.set_bpf_compute_max_units(45_000); + test.set_bpf_compute_max_units(43_000); const FEE_AMOUNT: u64 = 5000; const HOST_FEE_AMOUNT: u64 = 1000; @@ -587,3 +587,147 @@ async fn test_borrow_limit() { ) ); } + +#[tokio::test] +async fn test_borrow_max_reserves() { + // This test is not intended to do much to test for correctness, but rather + // make sure to track the compute cost of having 6 reserves and making + // a borrow transaction. + let mut test = ProgramTest::new( + "spl_token_lending", + spl_token_lending::id(), + processor!(process_instruction), + ); + + // limit to track compute unit increase + test.set_bpf_compute_max_units(85_000); + + const DEPOSIT_AMOUNT_LAMPORTS: u64 = 100_000; + const BORROW_AMOUNT: u64 = 10; + const LIQUIDITY_AMOUNT: u64 = 100_000; + const COLLATERAL_AMOUNT: u64 = 100_000; + + let user_accounts_owner = Keypair::new(); + let lending_market = add_lending_market(&mut test); + + let reserve_config = test_reserve_config(); + + let oracle = add_sol_oracle(&mut test); + let mut reserves = Vec::new(); + for _n in 0..6 { + let reserve = add_reserve( + &mut test, + &lending_market, + &oracle, + &user_accounts_owner, + AddReserveArgs { + collateral_amount: COLLATERAL_AMOUNT, + liquidity_amount: LIQUIDITY_AMOUNT, + liquidity_mint_pubkey: spl_token::native_mint::id(), + liquidity_mint_decimals: 9, + config: reserve_config, + mark_fresh: false, + ..AddReserveArgs::default() + }, + ); + reserves.push(reserve); + } + + let test_obligation = add_obligation( + &mut test, + &lending_market, + &user_accounts_owner, + AddObligationArgs { + deposits: &[ + (&reserves[0], DEPOSIT_AMOUNT_LAMPORTS), + (&reserves[1], DEPOSIT_AMOUNT_LAMPORTS), + (&reserves[2], DEPOSIT_AMOUNT_LAMPORTS), + (&reserves[3], DEPOSIT_AMOUNT_LAMPORTS), + (&reserves[4], DEPOSIT_AMOUNT_LAMPORTS), + (&reserves[5], DEPOSIT_AMOUNT_LAMPORTS), + ], + ..AddObligationArgs::default() + }, + ); + + let (mut banks_client, payer, recent_blockhash) = test.start().await; + let mut transaction = Transaction::new_with_payer( + &[ + refresh_reserve( + spl_token_lending::id(), + reserves[0].pubkey, + oracle.pyth_price_pubkey, + oracle.switchboard_feed_pubkey, + ), + refresh_reserve( + spl_token_lending::id(), + reserves[1].pubkey, + oracle.pyth_price_pubkey, + oracle.switchboard_feed_pubkey, + ), + refresh_reserve( + spl_token_lending::id(), + reserves[2].pubkey, + oracle.pyth_price_pubkey, + oracle.switchboard_feed_pubkey, + ), + refresh_reserve( + spl_token_lending::id(), + reserves[3].pubkey, + oracle.pyth_price_pubkey, + oracle.switchboard_feed_pubkey, + ), + refresh_reserve( + spl_token_lending::id(), + reserves[4].pubkey, + oracle.pyth_price_pubkey, + oracle.switchboard_feed_pubkey, + ), + refresh_reserve( + spl_token_lending::id(), + reserves[5].pubkey, + oracle.pyth_price_pubkey, + oracle.switchboard_feed_pubkey, + ), + refresh_obligation( + spl_token_lending::id(), + test_obligation.pubkey, + vec![ + reserves[0].pubkey, + reserves[1].pubkey, + reserves[2].pubkey, + reserves[3].pubkey, + reserves[4].pubkey, + reserves[5].pubkey, + ], + ), + borrow_obligation_liquidity( + spl_token_lending::id(), + BORROW_AMOUNT, + reserves[0].liquidity_supply_pubkey, + reserves[0].user_liquidity_pubkey, + reserves[0].pubkey, + reserves[0].config.fee_receiver, + test_obligation.pubkey, + lending_market.pubkey, + test_obligation.owner, + Some(reserves[0].liquidity_host_pubkey), + ), + ], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &user_accounts_owner], recent_blockhash); + assert!(banks_client.process_transaction(transaction).await.is_ok()); + + let obligation = test_obligation.get_state(&mut banks_client).await; + assert_eq!(obligation.borrows.len(), 1); + assert_eq!( + obligation.borrows[0], + ObligationLiquidity { + borrow_reserve: reserves[0].pubkey, + cumulative_borrow_rate_wads: Decimal::from_scaled_val(1000000000000000000), + borrowed_amount_wads: Decimal::from_scaled_val(12000000000000000000), + market_value: Decimal::zero(), + } + ) +} diff --git a/token-lending/program/tests/deposit_obligation_collateral.rs b/token-lending/program/tests/deposit_obligation_collateral.rs index 9b5387919b5..4b585ac30a3 100644 --- a/token-lending/program/tests/deposit_obligation_collateral.rs +++ b/token-lending/program/tests/deposit_obligation_collateral.rs @@ -24,7 +24,7 @@ async fn test_success() { ); // limit to track compute unit increase - test.set_bpf_compute_max_units(38_000); + test.set_bpf_compute_max_units(54_000); const SOL_DEPOSIT_AMOUNT_LAMPORTS: u64 = 10 * LAMPORTS_TO_SOL * INITIAL_COLLATERAL_RATIO; const SOL_RESERVE_COLLATERAL_LAMPORTS: u64 = 2 * SOL_DEPOSIT_AMOUNT_LAMPORTS; diff --git a/token-lending/program/tests/deposit_reserve_liquidity.rs b/token-lending/program/tests/deposit_reserve_liquidity.rs index 62e9b8c6aa3..9b33cf871c9 100644 --- a/token-lending/program/tests/deposit_reserve_liquidity.rs +++ b/token-lending/program/tests/deposit_reserve_liquidity.rs @@ -16,7 +16,7 @@ async fn test_success() { ); // limit to track compute unit increase - test.set_bpf_compute_max_units(45_000); + test.set_bpf_compute_max_units(68_000); let user_accounts_owner = Keypair::new(); let lending_market = add_lending_market(&mut test); diff --git a/token-lending/program/tests/deposit_reserve_liquidity_and_obligation_collateral.rs b/token-lending/program/tests/deposit_reserve_liquidity_and_obligation_collateral.rs index 7efba9dcbdb..0e8b09a9c0a 100644 --- a/token-lending/program/tests/deposit_reserve_liquidity_and_obligation_collateral.rs +++ b/token-lending/program/tests/deposit_reserve_liquidity_and_obligation_collateral.rs @@ -16,7 +16,7 @@ async fn test_success() { ); // limit to track compute unit increase - test.set_bpf_compute_max_units(70_000); + test.set_bpf_compute_max_units(90_000); let user_accounts_owner = Keypair::new(); let lending_market = add_lending_market(&mut test); diff --git a/token-lending/program/tests/helpers/mod.rs b/token-lending/program/tests/helpers/mod.rs index 2b5db31ae6b..cfce594bdde 100644 --- a/token-lending/program/tests/helpers/mod.rs +++ b/token-lending/program/tests/helpers/mod.rs @@ -765,7 +765,7 @@ impl TestLendingMarket { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TestReserve { pub name: String, pub pubkey: Pubkey, diff --git a/token-lending/program/tests/init_reserve.rs b/token-lending/program/tests/init_reserve.rs index b80c2a93e3e..b7b2a40fc1e 100644 --- a/token-lending/program/tests/init_reserve.rs +++ b/token-lending/program/tests/init_reserve.rs @@ -27,7 +27,7 @@ async fn test_success() { ); // limit to track compute unit increase - test.set_bpf_compute_max_units(70_000); + test.set_bpf_compute_max_units(104_000); let user_accounts_owner = Keypair::new(); let lending_market = add_lending_market(&mut test); @@ -153,7 +153,7 @@ async fn test_null_switchboard() { ); // limit to track compute unit increase - test.set_bpf_compute_max_units(70_000); + test.set_bpf_compute_max_units(104_000); let user_accounts_owner = Keypair::new(); let lending_market = add_lending_market(&mut test); diff --git a/token-lending/program/tests/liquidate_obligation.rs b/token-lending/program/tests/liquidate_obligation.rs index 7b9c3d7fd0a..570071765d1 100644 --- a/token-lending/program/tests/liquidate_obligation.rs +++ b/token-lending/program/tests/liquidate_obligation.rs @@ -25,7 +25,7 @@ async fn test_success() { ); // limit to track compute unit increase - test.set_bpf_compute_max_units(51_000); + test.set_bpf_compute_max_units(43_000); // 100 SOL collateral const SOL_DEPOSIT_AMOUNT_LAMPORTS: u64 = 100 * LAMPORTS_TO_SOL * INITIAL_COLLATERAL_RATIO; diff --git a/token-lending/program/tests/liquidate_obligation_and_redeem_collateral.rs b/token-lending/program/tests/liquidate_obligation_and_redeem_collateral.rs index b31b02a8694..d1fb4d11d2f 100644 --- a/token-lending/program/tests/liquidate_obligation_and_redeem_collateral.rs +++ b/token-lending/program/tests/liquidate_obligation_and_redeem_collateral.rs @@ -25,7 +25,7 @@ async fn test_success() { ); // limit to track compute unit increase - test.set_bpf_compute_max_units(77_000); + test.set_bpf_compute_max_units(100_000); // 100 SOL collateral const SOL_DEPOSIT_AMOUNT_LAMPORTS: u64 = 100 * LAMPORTS_TO_SOL * INITIAL_COLLATERAL_RATIO; diff --git a/token-lending/program/tests/obligation_end_to_end.rs b/token-lending/program/tests/obligation_end_to_end.rs index 7ad58635e0e..4d452e94e76 100644 --- a/token-lending/program/tests/obligation_end_to_end.rs +++ b/token-lending/program/tests/obligation_end_to_end.rs @@ -32,7 +32,7 @@ async fn test_success() { ); // limit to track compute unit increase - test.set_bpf_compute_max_units(45_000); + test.set_bpf_compute_max_units(48_000); const FEE_AMOUNT: u64 = 100; const HOST_FEE_AMOUNT: u64 = 20; diff --git a/token-lending/program/tests/redeem_reserve_collateral.rs b/token-lending/program/tests/redeem_reserve_collateral.rs index a4ebb3385b7..0ae88717505 100644 --- a/token-lending/program/tests/redeem_reserve_collateral.rs +++ b/token-lending/program/tests/redeem_reserve_collateral.rs @@ -24,7 +24,7 @@ async fn test_success() { ); // limit to track compute unit increase - test.set_bpf_compute_max_units(45_000); + test.set_bpf_compute_max_units(68_000); let user_accounts_owner = Keypair::new(); let lending_market = add_lending_market(&mut test); diff --git a/token-lending/program/tests/refresh_obligation.rs b/token-lending/program/tests/refresh_obligation.rs index a2b0cc3e10d..fb10a644cf9 100644 --- a/token-lending/program/tests/refresh_obligation.rs +++ b/token-lending/program/tests/refresh_obligation.rs @@ -27,7 +27,7 @@ async fn test_success() { ); // limit to track compute unit increase - test.set_bpf_compute_max_units(28_000); + test.set_bpf_compute_max_units(56_000); const SOL_DEPOSIT_AMOUNT: u64 = 100; const USDC_BORROW_AMOUNT: u64 = 1_000; diff --git a/token-lending/program/tests/refresh_reserve.rs b/token-lending/program/tests/refresh_reserve.rs index 1eb0c9d4aef..4e30d01063c 100644 --- a/token-lending/program/tests/refresh_reserve.rs +++ b/token-lending/program/tests/refresh_reserve.rs @@ -25,7 +25,7 @@ async fn test_success() { ); // limit to track compute unit increase - test.set_bpf_compute_max_units(16_000); + test.set_bpf_compute_max_units(55_000); const SOL_RESERVE_LIQUIDITY_LAMPORTS: u64 = 100 * LAMPORTS_TO_SOL; const USDC_RESERVE_LIQUIDITY_FRACTIONAL: u64 = 100 * FRACTIONAL_TO_USDC; @@ -108,7 +108,6 @@ async fn test_success() { 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; diff --git a/token-lending/program/tests/repay_obligation_liquidity.rs b/token-lending/program/tests/repay_obligation_liquidity.rs index db6ca44a34f..eceaf578b70 100644 --- a/token-lending/program/tests/repay_obligation_liquidity.rs +++ b/token-lending/program/tests/repay_obligation_liquidity.rs @@ -26,7 +26,7 @@ async fn test_success() { ); // limit to track compute unit increase - test.set_bpf_compute_max_units(27_000); + test.set_bpf_compute_max_units(44_000); const SOL_DEPOSIT_AMOUNT_LAMPORTS: u64 = 100 * LAMPORTS_TO_SOL * INITIAL_COLLATERAL_RATIO; const USDC_BORROW_AMOUNT_FRACTIONAL: u64 = 1_000 * FRACTIONAL_TO_USDC; diff --git a/token-lending/program/tests/withdraw_obligation_collateral.rs b/token-lending/program/tests/withdraw_obligation_collateral.rs index 1c402419311..97ef21491bf 100644 --- a/token-lending/program/tests/withdraw_obligation_collateral.rs +++ b/token-lending/program/tests/withdraw_obligation_collateral.rs @@ -27,7 +27,7 @@ async fn test_withdraw_fixed_amount() { ); // limit to track compute unit increase - test.set_bpf_compute_max_units(33_000); + test.set_bpf_compute_max_units(28_000); const SOL_DEPOSIT_AMOUNT_LAMPORTS: u64 = 200 * LAMPORTS_TO_SOL * INITIAL_COLLATERAL_RATIO; const USDC_BORROW_AMOUNT_FRACTIONAL: u64 = 1_000 * FRACTIONAL_TO_USDC; @@ -154,7 +154,7 @@ async fn test_withdraw_max_amount() { ); // limit to track compute unit increase - test.set_bpf_compute_max_units(28_000); + test.set_bpf_compute_max_units(20_000); const USDC_DEPOSIT_AMOUNT_FRACTIONAL: u64 = 1_000 * FRACTIONAL_TO_USDC * INITIAL_COLLATERAL_RATIO;