Skip to content

Commit

Permalink
adding switchboard v2 support (solana-labs#73)
Browse files Browse the repository at this point in the history
* adding switchboard v2 support

* made a test that relied on switchboard v2

* implemented some of justins feedback
  • Loading branch information
nope-finance authored Mar 24, 2022
1 parent 35a282b commit 13107ca
Show file tree
Hide file tree
Showing 18 changed files with 898 additions and 152 deletions.
552 changes: 469 additions & 83 deletions Cargo.lock

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions token-lending/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ version = "0.1.0"

[dependencies]
clap = "=2.34.0"
solana-clap-utils = "=1.7.12"
solana-cli-config = "=1.7.12"
solana-client = "=1.7.12"
solana-logger = "=1.7.12"
solana-sdk = "=1.7.12"
solana-program = "=1.7.12"
solana-clap-utils = "=1.8.14"
solana-cli-config = "=1.8.14"
solana-client = "=1.8.14"
solana-logger = "=1.8.14"
solana-sdk = "=1.8.14"
solana-program = "=1.8.14"
spl-token-lending = { path="../program", features = [ "no-entrypoint" ] }
spl-token = { version = "3.2.0", features=["no-entrypoint"] }

Expand Down
7 changes: 4 additions & 3 deletions token-lending/program/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ arrayref = "0.3.6"
bytemuck = "1.5.1"
num-derive = "0.3"
num-traits = "0.2"
solana-program = "=1.7.12"
solana-program = "=1.8.14"
spl-token = { version = "3.2.0", features=["no-entrypoint"] }
switchboard-program = "0.2.0"
switchboard-v2 = "0.1.3"
thiserror = "1.0"
uint = "=0.9.0"

Expand All @@ -27,8 +28,8 @@ assert_matches = "1.5.0"
base64 = "0.13"
log = "0.4.14"
proptest = "1.0"
solana-program-test = "=1.7.12"
solana-sdk = "=1.7.12"
solana-program-test = "=1.8.14"
solana-sdk = "=1.8.14"
serde = "1.0"
serde_yaml = "0.8"

Expand Down
47 changes: 46 additions & 1 deletion token-lending/program/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ use std::{convert::TryInto, result::Result};
use switchboard_program::{
get_aggregator, get_aggregator_result, AggregatorState, RoundResult, SwitchboardAccountType,
};
use switchboard_v2::AggregatorAccountData;

/// Mainnet program id for Switchboard v2.
pub mod switchboard_v2_mainnet {
solana_program::declare_id!("SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f");
}

/// Devnet program id for Switchboard v2.
pub mod switchboard_v2_devnet {
solana_program::declare_id!("2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG");
}

/// Processes an instruction
pub fn process_instruction(
Expand Down Expand Up @@ -2353,6 +2364,11 @@ fn get_switchboard_price(
if *switchboard_feed_info.key == spl_token_lending::NULL_PUBKEY {
return Err(LendingError::NullOracleConfig.into());
}
if switchboard_feed_info.owner == &switchboard_v2_mainnet::id()
|| switchboard_feed_info.owner == &switchboard_v2_devnet::id()
{
return get_switchboard_price_v2(switchboard_feed_info, clock);
}

let account_buf = switchboard_feed_info.try_borrow_data()?;
// first byte type discriminator
Expand Down Expand Up @@ -2387,6 +2403,32 @@ fn get_switchboard_price(
Decimal::from(price).try_div(price_quotient)
}

fn get_switchboard_price_v2(
switchboard_feed_info: &AccountInfo,
clock: &Clock,
) -> Result<Decimal, ProgramError> {
const STALE_AFTER_SLOTS_ELAPSED: u64 = 240;

let feed = AggregatorAccountData::new(switchboard_feed_info)?;
let slots_elapsed = clock
.slot
.checked_sub(feed.latest_confirmed_round.round_open_slot)
.ok_or(LendingError::MathOverflow)?;
if slots_elapsed >= STALE_AFTER_SLOTS_ELAPSED {
msg!("Switchboard oracle price is stale");
return Err(LendingError::InvalidOracleConfig.into());
}

let price_switchboard_desc = feed.get_result()?;
if price_switchboard_desc.mantissa < 0 {
msg!("Switchboard oracle price is negative which is not allowed");
return Err(LendingError::InvalidOracleConfig.into());
}
let price = Decimal::from(price_switchboard_desc.mantissa as u128);
let exp = (10u64).checked_pow(price_switchboard_desc.scale).unwrap();
price.try_div(exp)
}

/// Issue a spl_token `InitializeAccount` instruction.
#[inline(always)]
fn spl_token_init_account(params: TokenInitializeAccountParams<'_>) -> ProgramResult {
Expand Down Expand Up @@ -2624,7 +2666,10 @@ fn validate_switchboard_keys(
if *switchboard_feed_info.key == spl_token_lending::NULL_PUBKEY {
return Ok(());
}
if &lending_market.switchboard_oracle_program_id != switchboard_feed_info.owner {
if switchboard_feed_info.owner != &lending_market.switchboard_oracle_program_id
&& switchboard_feed_info.owner != &switchboard_v2_mainnet::id()
&& switchboard_feed_info.owner != &switchboard_v2_devnet::id()
{
msg!("Switchboard account provided is not owned by the switchboard oracle program");
return Err(LendingError::InvalidOracleConfig.into());
}
Expand Down
4 changes: 2 additions & 2 deletions token-lending/program/tests/borrow_obligation_liquidity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(55_000);

const USDC_TOTAL_BORROW_FRACTIONAL: u64 = 1_000 * FRACTIONAL_TO_USDC;
const FEE_AMOUNT: u64 = 100;
Expand Down Expand Up @@ -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(60_000);

const FEE_AMOUNT: u64 = 5000;
const HOST_FEE_AMOUNT: u64 = 1000;
Expand Down
2 changes: 1 addition & 1 deletion token-lending/program/tests/deposit_reserve_liquidity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(50_000);

let user_accounts_owner = Keypair::new();
let lending_market = add_lending_market(&mut test);
Expand Down
Binary file not shown.
Binary file not shown.
4 changes: 4 additions & 0 deletions token-lending/program/tests/fixtures/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ solana account 3Mnn2fX6rQyUsyELYms1sBJyChWofzSNRoqYzvgMVz5E --output-file 3Mnn2f
solana account J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix --output-file J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix.bin
# Switchboard price: SOL/USD
solana account AdtRGGhmqvom3Jemp5YNrxd9q9unX36BZk1pujkkXijL --output-file AdtRGGhmqvom3Jemp5YNrxd9q9unX36BZk1pujkkXijL.bin
# Switchboardv2 price: SOL/USD
solana account GvDMxPzN1sCj7L26YDK2HnMRXEQmQ2aemov8YBtPS7vR --output-file GvDMxPzN1sCj7L26YDK2HnMRXEQmQ2aemov8YBtPS7vR.bin
# Pyth product: SRM/USD
solana account 6MEwdxe4g1NeAF9u6KDG14anJpFsVEa2cvr5H6iriFZ8 --output-file 6MEwdxe4g1NeAF9u6KDG14anJpFsVEa2cvr5H6iriFZ8.bin
# Pyth price: SRM/USD
solana account 992moaMQKs32GKZ9dxi8keyM2bUmbrwBZpK4p2K6X5Vs --output-file 992moaMQKs32GKZ9dxi8keyM2bUmbrwBZpK4p2K6X5Vs.bin
# Switchboard price: SRM/USD
solana account BAoygKcKN7wk8yKzLD6sxzUQUqLvhBV1rjMA4UJqfZuH --output-file BAoygKcKN7wk8yKzLD6sxzUQUqLvhBV1rjMA4UJqfZuH.bin
# Switchboardv2 price: SRM/USD
solana account CUgoqwiQ4wCt6Tthkrgx5saAEpLBjPCdHshVa4Pbfcx2 --output-file CUgoqwiQ4wCt6Tthkrgx5saAEpLBjPCdHshVa4Pbfcx2.bin
```
145 changes: 98 additions & 47 deletions token-lending/program/tests/helpers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use spl_token_lending::{
init_reserve, liquidate_obligation, refresh_reserve,
},
math::{Decimal, Rate, TryAdd, TryMul},
processor::switchboard_v2_mainnet,
pyth,
state::{
InitLendingMarketParams, InitObligationParams, InitReserveParams, LendingMarket,
Expand All @@ -32,6 +33,7 @@ use spl_token_lending::{
},
};
use std::{convert::TryInto, str::FromStr};
use switchboard_v2::AggregatorAccountData;

pub const QUOTE_CURRENCY: [u8; 32] =
*b"USD\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
Expand Down Expand Up @@ -60,13 +62,17 @@ pub fn test_reserve_config() -> ReserveConfig {
}
}

pub const NULL_PUBKEY: &str = "nu11111111111111111111111111111111111111111";

pub const SOL_PYTH_PRODUCT: &str = "3Mnn2fX6rQyUsyELYms1sBJyChWofzSNRoqYzvgMVz5E";
pub const SOL_PYTH_PRICE: &str = "J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix";
pub const SOL_SWITCHBOARD_FEED: &str = "AdtRGGhmqvom3Jemp5YNrxd9q9unX36BZk1pujkkXijL";
pub const SOL_SWITCHBOARDV2_FEED: &str = "GvDMxPzN1sCj7L26YDK2HnMRXEQmQ2aemov8YBtPS7vR";

pub const SRM_PYTH_PRODUCT: &str = "6MEwdxe4g1NeAF9u6KDG14anJpFsVEa2cvr5H6iriFZ8";
pub const SRM_PYTH_PRICE: &str = "992moaMQKs32GKZ9dxi8keyM2bUmbrwBZpK4p2K6X5Vs";
pub const SRM_SWITCHBOARD_FEED: &str = "BAoygKcKN7wk8yKzLD6sxzUQUqLvhBV1rjMA4UJqfZuH";
pub const SRM_SWITCHBOARDV2_FEED: &str = "CUgoqwiQ4wCt6Tthkrgx5saAEpLBjPCdHshVa4Pbfcx2";

pub const USDC_MINT: &str = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";

Expand Down Expand Up @@ -1162,6 +1168,17 @@ pub fn add_sol_oracle(test: &mut ProgramTest) -> TestOracle {
)
}

pub fn add_sol_oracle_switchboardv2(test: &mut ProgramTest) -> TestOracle {
add_oracle(
test,
Pubkey::from_str(NULL_PUBKEY).unwrap(),
Pubkey::from_str(NULL_PUBKEY).unwrap(),
Pubkey::from_str(SOL_SWITCHBOARDV2_FEED).unwrap(),
// Set SOL price to $20
Decimal::from(20u64),
)
}

pub fn add_usdc_oracle(test: &mut ProgramTest) -> TestOracle {
add_oracle(
test,
Expand All @@ -1174,6 +1191,18 @@ pub fn add_usdc_oracle(test: &mut ProgramTest) -> TestOracle {
)
}

pub fn add_usdc_oracle_switchboardv2(test: &mut ProgramTest) -> TestOracle {
add_oracle(
test,
// Mock with SRM since Pyth doesn't have USDC yet
Pubkey::from_str(NULL_PUBKEY).unwrap(),
Pubkey::from_str(NULL_PUBKEY).unwrap(),
Pubkey::from_str(SRM_SWITCHBOARDV2_FEED).unwrap(),
// Set USDC price to $1
Decimal::from(1u64),
)
}

pub fn add_oracle(
test: &mut ProgramTest,
pyth_product_pubkey: Pubkey,
Expand All @@ -1183,63 +1212,85 @@ pub fn add_oracle(
) -> TestOracle {
let oracle_program_id = read_keypair_file("tests/fixtures/oracle_program_id.json").unwrap();

// Add Pyth product account
test.add_account_with_file_data(
pyth_product_pubkey,
u32::MAX as u64,
oracle_program_id.pubkey(),
&format!("{}.bin", pyth_product_pubkey.to_string()),
);

// Add Pyth price account after setting the price
let filename = &format!("{}.bin", pyth_price_pubkey.to_string());
let mut pyth_price_data = read_file(find_file(filename).unwrap_or_else(|| {
panic!("Unable to locate {}", filename);
}));
if pyth_price_pubkey.to_string() != NULL_PUBKEY {
// Add Pyth product account
test.add_account_with_file_data(
pyth_product_pubkey,
u32::MAX as u64,
oracle_program_id.pubkey(),
&format!("{}.bin", pyth_product_pubkey.to_string()),
);
}
if pyth_price_pubkey.to_string() != NULL_PUBKEY {
// Add Pyth price account after setting the price
let filename = &format!("{}.bin", pyth_price_pubkey.to_string());
let mut pyth_price_data = read_file(find_file(filename).unwrap_or_else(|| {
panic!("Unable to locate {}", filename);
}));

let mut pyth_price = pyth::load_mut::<pyth::Price>(pyth_price_data.as_mut_slice()).unwrap();
let mut pyth_price = pyth::load_mut::<pyth::Price>(pyth_price_data.as_mut_slice()).unwrap();

let decimals = 10u64
.checked_pow(pyth_price.expo.checked_abs().unwrap().try_into().unwrap())
.unwrap();
let decimals = 10u64
.checked_pow(pyth_price.expo.checked_abs().unwrap().try_into().unwrap())
.unwrap();

pyth_price.valid_slot = 0;
pyth_price.agg.price = price
.try_round_u64()
.unwrap()
.checked_mul(decimals)
.unwrap()
.try_into()
.unwrap();
pyth_price.valid_slot = 0;
pyth_price.agg.price = price
.try_round_u64()
.unwrap()
.checked_mul(decimals)
.unwrap()
.try_into()
.unwrap();

test.add_account(
pyth_price_pubkey,
Account {
lamports: u32::MAX as u64,
data: pyth_price_data,
owner: oracle_program_id.pubkey(),
executable: false,
rent_epoch: 0,
},
);
test.add_account(
pyth_price_pubkey,
Account {
lamports: u32::MAX as u64,
data: pyth_price_data,
owner: oracle_program_id.pubkey(),
executable: false,
rent_epoch: 0,
},
);
}

// Add Switchboard price feed account after setting the price
let filename2 = &format!("{}.bin", switchboard_feed_pubkey.to_string());
// mut and set data here later
let switchboard_feed_data = read_file(find_file(filename2).unwrap_or_else(|| {
panic!("Unable to locate {}", filename2);
let mut switchboard_feed_data = read_file(find_file(filename2).unwrap_or_else(|| {
panic!("Unable tod locate {}", filename2);
}));

test.add_account(
switchboard_feed_pubkey,
Account {
lamports: u32::MAX as u64,
data: switchboard_feed_data,
owner: oracle_program_id.pubkey(),
executable: false,
rent_epoch: 0,
},
);
let is_v2 = switchboard_feed_pubkey.to_string() == SOL_SWITCHBOARDV2_FEED
|| switchboard_feed_pubkey.to_string() == SRM_SWITCHBOARDV2_FEED;
if is_v2 {
// let mut_switchboard_feed_data = &mut switchboard_feed_data[8..];
let agg_state =
bytemuck::from_bytes_mut::<AggregatorAccountData>(&mut switchboard_feed_data[8..]);
agg_state.latest_confirmed_round.round_open_slot = 0;
test.add_account(
switchboard_feed_pubkey,
Account {
lamports: u32::MAX as u64,
data: switchboard_feed_data,
owner: switchboard_v2_mainnet::id(),
executable: false,
rent_epoch: 0,
},
);
} else {
test.add_account(
switchboard_feed_pubkey,
Account {
lamports: u32::MAX as u64,
data: switchboard_feed_data,
owner: oracle_program_id.pubkey(),
executable: false,
rent_epoch: 0,
},
);
}

TestOracle {
pyth_product_pubkey,
Expand Down
2 changes: 1 addition & 1 deletion token-lending/program/tests/init_reserve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(75_000);

let user_accounts_owner = Keypair::new();
let lending_market = add_lending_market(&mut test);
Expand Down
2 changes: 1 addition & 1 deletion token-lending/program/tests/liquidate_obligation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(62_000);

// 100 SOL collateral
const SOL_DEPOSIT_AMOUNT_LAMPORTS: u64 = 100 * LAMPORTS_TO_SOL * INITIAL_COLLATERAL_RATIO;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(95_000);

// 100 SOL collateral
const SOL_DEPOSIT_AMOUNT_LAMPORTS: u64 = 100 * LAMPORTS_TO_SOL * INITIAL_COLLATERAL_RATIO;
Expand Down
Loading

0 comments on commit 13107ca

Please sign in to comment.