Skip to content

Commit

Permalink
Complete swb price fetching test and functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
jgur-psyops committed Aug 10, 2024
1 parent 04d2c73 commit fca412e
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 9 deletions.
95 changes: 92 additions & 3 deletions programs/marginfi/src/state/price.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,6 @@ impl PriceAdapter for PythLegacyPriceFeed {
}
}

// TODO lite version of feed account
#[cfg_attr(feature = "client", derive(Clone, Debug))]
pub struct SwitchboardPullPriceFeed {
feed: Box<LitePullFeedAccountData>,
Expand Down Expand Up @@ -360,8 +359,8 @@ impl SwitchboardPullPriceFeed {
.ok_or_else(math_error!())?;

// WARNING: Adding a line like the following will cause the entire project to silently fail
// to build, resulting in `Program not deployed` errors downstream when testing
// to build, resulting in `Program not deployed` errors downstream when testing

// msg!("recorded price: {:?}", price);

Ok(price)
Expand Down Expand Up @@ -901,6 +900,8 @@ mod tests {
use pretty_assertions::assert_eq;
use rust_decimal::Decimal;

use crate::utils::hex_to_bytes;

use super::*;
#[test]
fn swb_decimal_test_18() {
Expand Down Expand Up @@ -1183,4 +1184,92 @@ mod tests {
.unwrap()
);
}

use solana_sdk::account::Account;
use std::cell::RefCell;
use std::rc::Rc;

/// Convert an account to info, useful if you only care about data for testing purposes.
pub fn account_to_account_info<'a>(
account: &'a mut Account,
key: &'a Pubkey,
) -> AccountInfo<'a> {
AccountInfo {
key,
lamports: Rc::new(RefCell::new(&mut account.lamports)),
data: Rc::new(RefCell::new(&mut account.data[..])),
owner: &account.owner,
rent_epoch: account.rent_epoch,
is_signer: false,
is_writable: true,
executable: account.executable,
}
}

pub fn create_switch_pull_oracle_account_from_bytes(data: Vec<u8>) -> Account {
Account {
lamports: 1_000_000,
data,
owner: switchboard_on_demand::SWITCHBOARD_PROGRAM_ID,
executable: false,
rent_epoch: 361,
}
}

#[test]
fn swb_pull_get_price() {
// From mainnet: https://solana.fm/address/BSzfJs4d1tAkSDqkepnfzEVcx2WtDVnwwXa2giy9PLeP
// Actual price $155.59404527
// conf/Std_dev ~5%
let bytes = hex_to_bytes("c41b6cc40ad7db286f5e7566ac000a9530e56b1db49585772719aeaaeeadb4d9bd8c2357b88e9e782e53d81000000000000000000000000000985f538057856308000000000000005cba953f3f15356b17703e554d3983801916531d7976aa424ad64348ec50e4224650d81000000000000000000000000000a0d5a780cc7f580800000000000000a20b742cedab55efd1faf60aef2cb872a092d24dfba8a48c8b953a5e90ac7bbf874ed81000000000000000000000000000c04958360093580800000000000000e7ef024ea756f8beec2eaa40234070da356754a8eeb2ac6a17c32d17c3e99f8ddc50d81000000000000000000000000000bc8739b45d215b0800000000000000e3e5130902c3e9c27917789769f1ae05de15cf504658beafeed2c598a949b3b7bf53d810000000000000000000000000007cec168c94d667080000000000000020e270b743473d87eff321663e267ba1c9a151f7969cef8147f625e9a2af7287ea54d81000000000000000000000000000dc65eccc174d6f0800000000000000ab605484238ac93f225c65f24d7705bb74b00cdb576555c3995e196691a4de5f484ed8100000000000000000000000000088f28dc9271d59080000000000000015196392573dc9043242716f629d4c0fb93bc0cff7a1a10ede24281b0e98fb7d5454d810000000000000000000000000000441a10ca4a268080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000048ac38271f28ab1b12e49439bddf54871094e4832a56c7a8ec57bd18d357980086807068432f186a147cf0b13a30067d386204ea9d6c8b04743ac2ef010b07524c935636f2523f6aeeb6dc7b7dab0e86a13ff2c794f7895fc78851d69fdb593bdccdb36600000000000000000000000000e40b540200000001000000534f4c2f55534400000000000000000000000000000000000000000000000000000000019e9eb66600000000fca3d11000000000000000000000000000000000000000000000000000000000000000000000000000dc65eccc174d6f0800000000000000006c9225e039550300000000000000000070d3c6ecddf76b080000000000000000d8244bc073aa060000000000000000000441a10ca4a268080000000000000000dc65eccc174d6f08000000000000000200000000000000ea54d810000000005454d81000000000ea54d81000000000fa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
let mut acc = create_switch_pull_oracle_account_from_bytes(bytes);
let key = pubkey!("BSzfJs4d1tAkSDqkepnfzEVcx2WtDVnwwXa2giy9PLeP");
let ai = account_to_account_info(&mut acc, &key);
let ai_check = SwitchboardPullPriceFeed::check_ais(&ai);
assert!(ai_check.is_ok());

let current_timestamp = 42;
let max_age = 100;
let feed: SwitchboardPullPriceFeed =
SwitchboardPullPriceFeed::load_checked(&ai, current_timestamp, max_age).unwrap();
let price: I80F48 = feed.get_price().unwrap();
let conf: I80F48 = feed.get_confidence_interval().unwrap();

//println!("price: {:?}, conf: {:?}", price, conf);

let target_price: I80F48 = I80F48::from_num(155); // Target price is $155
let price_tolerance: I80F48 = target_price * I80F48::from_num(0.01);

let target_conf: I80F48 = target_price * I80F48::from_num(0.05);
let conf_tolerance: I80F48 = target_conf * I80F48::from_num(0.005);

let min_price: I80F48 = target_price.checked_sub(price_tolerance).unwrap();
let max_price: I80F48 = target_price.checked_add(price_tolerance).unwrap();
assert!(price >= min_price && price <= max_price);

let min_conf: I80F48 = target_conf.checked_sub(conf_tolerance).unwrap();
let max_conf: I80F48 = target_conf.checked_add(conf_tolerance).unwrap();
assert!(conf >= min_conf && conf <= max_conf);

let price_bias_none: I80F48 = feed
.get_price_of_type(OraclePriceType::RealTime, None)
.unwrap();
assert_eq!(price, price_bias_none);

let price_bias_low: I80F48 = feed
.get_price_of_type(OraclePriceType::RealTime, Some(PriceBias::Low))
.unwrap();
let target_price_low: I80F48 = target_price.checked_sub(target_conf).unwrap();
let min_price: I80F48 = target_price_low.checked_sub(price_tolerance).unwrap();
let max_price: I80F48 = target_price_low.checked_add(price_tolerance).unwrap();
assert!(price_bias_low >= min_price && price_bias_low <= max_price);

let price_bias_high: I80F48 = feed
.get_price_of_type(OraclePriceType::RealTime, Some(PriceBias::High))
.unwrap();
let target_price_high: I80F48 = target_price.checked_add(target_conf).unwrap();
let min_price: I80F48 = target_price_high.checked_sub(price_tolerance).unwrap();
let max_price: I80F48 = target_price_high.checked_add(price_tolerance).unwrap();
assert!(price_bias_high >= min_price && price_bias_high <= max_price);
}
}
14 changes: 14 additions & 0 deletions programs/marginfi/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,17 @@ fn ceil_div(numerator: u128, denominator: u128) -> Option<u128> {
.checked_sub(1)?
.checked_div(denominator)
}

/// A minimal tool to convert a hex string like "22f123639" into the byte equivalent.
pub fn hex_to_bytes(hex: &str) -> Vec<u8> {
hex.as_bytes()
.chunks(2)
.map(|chunk| {
let high = chunk[0] as char;
let low = chunk[1] as char;
let high = high.to_digit(16).expect("Invalid hex character") as u8;
let low = low.to_digit(16).expect("Invalid hex character") as u8;
(high << 4) | low
})
.collect()
}
3 changes: 2 additions & 1 deletion programs/marginfi/tests/user_actions/borrow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,14 +140,15 @@ async fn marginfi_account_borrow_success(
Ok(())
}

// TODO finish swbpull case
#[test_case(100., 9., 10.000000001, BankMint::Usdc, BankMint::Sol)]
#[test_case(123_456., 12_345.6, 12_345.9, BankMint::Usdc, BankMint::Sol)]
#[test_case(123_456., 10_000., 15_000., BankMint::UsdcSwb, BankMint::Sol)]
#[test_case(1., 5., 11.98224, BankMint::Sol, BankMint::Usdc)]
#[test_case(128_932., 10_000., 15_000.0, BankMint::PyUSD, BankMint::SolSwb)]
#[test_case(240., 0.092, 500., BankMint::PyUSD, BankMint::T22WithFee)]
#[test_case(36., 1.7, 1.9, BankMint::T22WithFee, BankMint::Sol)]
#[test_case(36., 1.7, 1.9, BankMint::SolSwbPull, BankMint::Sol)]
#[test_case(155., 0.1, 155.1, BankMint::SolSwbPull, BankMint::Sol)] // Sol @ $155
#[tokio::test]
async fn marginfi_account_borrow_failure_not_enough_collateral(
deposit_amount: f64,
Expand Down
Binary file not shown.
9 changes: 4 additions & 5 deletions test-utils/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ pub const PYTH_USDC_REAL_FEED: Pubkey = pubkey!("PythUsdcRea1Price11111111111111
pub const PYTH_PUSH_SOL_REAL_FEED: Pubkey = pubkey!("PythPushSo1Rea1Price11111111111111111111111");

pub const SWITCH_PULL_SOL_REAL_FEED: Pubkey =
pubkey!("56PpAg9fHx2yHEwdTkvCH5R5x2PAsDoNANUhdbvLXLM6");
pubkey!("BSzfJs4d1tAkSDqkepnfzEVcx2WtDVnwwXa2giy9PLeP");

pub fn get_oracle_id_from_feed_id(feed_id: Pubkey) -> Option<Pubkey> {
match feed_id.to_bytes() {
Expand Down Expand Up @@ -529,13 +529,12 @@ impl TestFixture {
),
);

// From mainnet: https://solana.fm/address/56PpAg9fHx2yHEwdTkvCH5R5x2PAsDoNANUhdbvLXLM6
// Alternative: BSzfJs4d1tAkSDqkepnfzEVcx2WtDVnwwXa2giy9PLeP
// Sol @ ~ $157
// From mainnet: https://solana.fm/address/BSzfJs4d1tAkSDqkepnfzEVcx2WtDVnwwXa2giy9PLeP
// Sol @ ~ $153
program.add_account(
SWITCH_PULL_SOL_REAL_FEED,
create_switch_pull_oracle_account_from_bytes(
include_bytes!("../data/56PpAg9fHx2yHEwdTkvCH5R5x2PAsDoNANUhdbvLXLM6.bin").to_vec(),
include_bytes!("../data/BSzfJs4d1tAkSDqkepnfzEVcx2WtDVnwwXa2giy9PLeP.bin").to_vec(),
),
);

Expand Down

0 comments on commit fca412e

Please sign in to comment.