Skip to content

Commit

Permalink
add bankrun test
Browse files Browse the repository at this point in the history
  • Loading branch information
NourAlharithi committed Jul 23, 2024
1 parent fae580f commit 498f8b9
Show file tree
Hide file tree
Showing 8 changed files with 847 additions and 32 deletions.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@
"dependencies": {
"@ellipsis-labs/phoenix-sdk": "1.4.2",
"@pythnetwork/pyth-solana-receiver": "^0.8.0",
"@switchboard-xyz/on-demand": "^1.2.10",
"anchor-bankrun": "^0.3.0",
"chai-bn": "^0.2.2",
"csvtojson": "^2.0.10",
"json2csv": "^5.0.7",
"rpc-websockets": "7.5.1",
"solana-bankrun": "^0.3.0",
"zstddec": "^0.1.0",
"rpc-websockets": "7.5.1"
"zstddec": "^0.1.0"
},
"scripts": {
"generate-docs": "typedoc",
Expand Down
26 changes: 19 additions & 7 deletions programs/drift/src/state/oracle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::math::casting::Cast;
use crate::math::constants::{PRICE_PRECISION, PRICE_PRECISION_I64, PRICE_PRECISION_U64};
use crate::math::safe_math::SafeMath;
use switchboard::{AggregatorAccountData, SwitchboardDecimal};
use switchboard_on_demand::PullFeedAccountData;
use switchboard_on_demand::{PullFeedAccountData, SB_ON_DEMAND_PRECISION};

use crate::error::ErrorCode::{InvalidOracle, UnableToLoadOracle};
use crate::math::safe_unwrap::SafeUnwrap;
Expand Down Expand Up @@ -314,11 +314,11 @@ pub fn get_sb_on_demand_price(
price_oracle: &AccountInfo,
clock_slot: u64,
) -> DriftResult<OraclePriceData> {
let aggregator_data: Ref<AggregatorAccountData> =
let pull_feed_account_info: Ref<PullFeedAccountData> =
load_ref(price_oracle).or(Err(ErrorCode::UnableToLoadOracle))?;

let price = convert_switchboard_decimal(
&aggregator_data
let price = convert_sb_i128(
&pull_feed_account_info
.value()
.ok_or(ErrorCode::UnableToLoadOracle)?,
)?
Expand All @@ -327,16 +327,16 @@ pub fn get_sb_on_demand_price(
// std deviation should always be positive, if we get a negative make it u128::MAX so it's flagged as bad value
// NOTE: previous switchboard impl uses std deviation on drift.
// Range offers better insight into the full consensus on the value.
let confidence = convert_switchboard_decimal(
&aggregator_data
let confidence = convert_sb_i128(
&pull_feed_account_info
.std_dev()
.ok_or(ErrorCode::UnableToLoadOracle)?,
)?
.cast::<i64>()?
.unsigned_abs();

let delay = clock_slot.cast::<i64>()?.safe_sub(
aggregator_data
pull_feed_account_info
.result
.result_slot()
.ok_or(ErrorCode::UnableToLoadOracle)?
Expand Down Expand Up @@ -369,6 +369,18 @@ fn convert_switchboard_decimal(switchboard_decimal: &SwitchboardDecimal) -> Drif
}
}

/// Given a decimal number represented as a mantissa (the digits) plus an
/// original_precision (10.pow(some number of decimals)), scale the
/// mantissa/digits to make sense with a new_precision.
fn convert_sb_i128(switchboard_i128: &i128) -> DriftResult<i128> {
let switchboard_precision = 10_u128.pow(SB_ON_DEMAND_PRECISION);
if switchboard_precision > PRICE_PRECISION {
switchboard_i128.safe_div((switchboard_precision / PRICE_PRECISION) as i128)
} else {
switchboard_i128.safe_mul((PRICE_PRECISION / switchboard_precision) as i128)
}
}

pub fn get_prelaunch_price(price_oracle: &AccountInfo, slot: u64) -> DriftResult<OraclePriceData> {
let oracle: Ref<PrelaunchOracle> = load_ref(price_oracle).or(Err(UnableToLoadOracle))?;

Expand Down
115 changes: 112 additions & 3 deletions programs/switchboard-on-demand/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ declare_id!("SBondMDrcV3K4kxZR1HNVT7osZxAHVHgYXL5Ze1oMUv");

#[program]
pub mod switchboard_on_demand {}

pub const PRECISION: u32 = 18;
pub const SB_ON_DEMAND_PRECISION: u32 = 18;

#[repr(C)]
#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
Expand All @@ -34,6 +33,76 @@ pub struct CurrentResult {
/// The slot at which the last considered submission was made
pub max_slot: u64,
}
impl CurrentResult {
/// The median value of the submissions needed for quorom size
pub fn value(&self) -> Option<i128> {
if self.slot == 0 {
return None;
}
Some(self.value)
}

/// The standard deviation of the submissions needed for quorom size
pub fn std_dev(&self) -> Option<i128> {
if self.slot == 0 {
return None;
}
Some(self.std_dev)
}

/// The mean of the submissions needed for quorom size
pub fn mean(&self) -> Option<i128> {
if self.slot == 0 {
return None;
}
Some(self.mean)
}

/// The range of the submissions needed for quorom size
pub fn range(&self) -> Option<i128> {
if self.slot == 0 {
return None;
}
Some(self.range)
}

/// The minimum value of the submissions needed for quorom size
pub fn min_value(&self) -> Option<i128> {
if self.slot == 0 {
return None;
}
Some(self.min_value)
}

/// The maximum value of the submissions needed for quorom size
pub fn max_value(&self) -> Option<i128> {
if self.slot == 0 {
return None;
}
Some(self.max_value)
}

pub fn result_slot(&self) -> Option<u64> {
if self.slot == 0 {
return None;
}
Some(self.slot)
}

pub fn min_slot(&self) -> Option<u64> {
if self.slot == 0 {
return None;
}
Some(self.min_slot)
}

pub fn max_slot(&self) -> Option<u64> {
if self.slot == 0 {
return None;
}
Some(self.max_slot)
}
}

#[repr(C)]
#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
Expand All @@ -51,11 +120,15 @@ impl OracleSubmission {
pub fn is_empty(&self) -> bool {
self.slot == 0
}

pub fn value(&self) -> i128 {
self.value
}
}

/// A representation of the data in a pull feed account.
#[repr(C)]
#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
#[account(zero_copy)]
pub struct PullFeedAccountData {
/// The oracle submissions for this feed.
pub submissions: [OracleSubmission; 32],
Expand Down Expand Up @@ -86,3 +159,39 @@ pub struct PullFeedAccountData {
_ebuf2: [u8; 256],
_ebuf1: [u8; 512],
}

impl PullFeedAccountData {
pub fn discriminator() -> [u8; 8] {
[196, 27, 108, 196, 10, 215, 219, 40]
}

/// The median value of the submissions needed for quorom size
pub fn value(&self) -> Option<i128> {
self.result.value()
}

/// The standard deviation of the submissions needed for quorom size
pub fn std_dev(&self) -> Option<i128> {
self.result.std_dev()
}

/// The mean of the submissions needed for quorom size
pub fn mean(&self) -> Option<i128> {
self.result.mean()
}

/// The range of the submissions needed for quorom size
pub fn range(&self) -> Option<i128> {
self.result.range()
}

/// The minimum value of the submissions needed for quorom size
pub fn min_value(&self) -> Option<i128> {
self.result.min_value()
}

/// The maximum value of the submissions needed for quorom size
pub fn max_value(&self) -> Option<i128> {
self.result.max_value()
}
}
10 changes: 10 additions & 0 deletions sdk/src/driftClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ export class DriftClient {
receiverProgram?: Program<PythSolanaReceiver>;
wormholeProgram?: Program<WormholeCoreBridgeSolana>;
sbOnDemandProgram?: Program<Idl>;
sbProgramFeedConfigs?: Map<string, any>;

public get isSubscribed() {
return this._isSubscribed && this.accountSubscriber.isSubscribed;
Expand Down Expand Up @@ -7285,8 +7286,17 @@ export class DriftClient {
const program = this.getSwitchboardOnDemandProgram();
// @ts-ignore
const feedAccount = new PullFeed(program, feed);
if (!this.sbProgramFeedConfigs) {
this.sbProgramFeedConfigs = new Map();
}
if (!this.sbProgramFeedConfigs.has(feedAccount.pubkey.toString())) {
const feedConfig = await feedAccount.loadConfigs();
this.sbProgramFeedConfigs.set(feed.toString(), feedConfig);
}

const [pullIx, _responses, success] = await feedAccount.fetchUpdateIx({
numSignatures,
feedConfigs: this.sbProgramFeedConfigs.get(feed.toString()),
});
if (!success) {
return undefined;
Expand Down
6 changes: 6 additions & 0 deletions sdk/src/idl/drift.json
Original file line number Diff line number Diff line change
Expand Up @@ -9919,6 +9919,9 @@
},
{
"name": "PythStableCoinPull"
},
{
"name": "SwitchboardOnDemand"
}
]
}
Expand Down Expand Up @@ -9994,6 +9997,9 @@
{
"name": "Fill"
},
{
"name": "Deposit"
},
{
"name": "Withdraw"
},
Expand Down
89 changes: 82 additions & 7 deletions tests/switchboardOnDemand.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,59 @@
import * as anchor from '@coral-xyz/anchor';
import { Program } from '@coral-xyz/anchor';
import { PublicKey } from '@solana/web3.js';
import { AccountInfo, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';
import { TestBulkAccountLoader } from '../sdk/src/accounts/testBulkAccountLoader';
import { BankrunContextWrapper } from '../sdk/src/bankrun/bankrunConnection';
import { initializeQuoteSpotMarket, mockUSDCMint } from './testHelpers';
import { OracleSource, TestClient } from '../sdk/src';
import { startAnchor } from 'solana-bankrun';
import {
ORACLE_ADDRESS_1,
ORACLE_ADDRESS_1_DATA,
ORACLE_ADDRESS_2,
ORACLE_ADDRESS_2_DATA,
ORACLE_ADDRESS_3,
ORACLE_ADDRESS_3_DATA,
ORACLE_ADDRESS_4,
ORACLE_ADDRESS_4_DATA,
ORACLE_ADDRESS_5,
ORACLE_ADDRESS_5_DATA,
ORACLE_ADDRESS_6,
ORACLE_ADDRESS_6_DATA,
ORACLE_ADDRESS_7,
ORACLE_ADDRESS_7_DATA,
PULL_FEED_ACCOUNT_DATA,
PULL_FEED_ADDRESS,
QUEUE_ACCOUNT_DATA,
QUEUE_ADDRESS,
} from './switchboardOnDemandData';

const SB_ON_DEMAND_PID = 'SBondMDrcV3K4kxZR1HNVT7osZxAHVHgYXL5Ze1oMUv';
const PULL_FEED_ADDRESS = new PublicKey(
'EZLBfnznMYKjFmaWYMEdhwnkiQF1WiP9jjTY6M8HpmGE'
);

const PULL_FEED_ACCOUNT_INFO: AccountInfo<Buffer> = {
executable: false,
lamports: LAMPORTS_PER_SOL,
owner: new PublicKey(SB_ON_DEMAND_PID),
rentEpoch: 0,
data: Buffer.from(PULL_FEED_ACCOUNT_DATA, 'base64'),
};

const QUEUE_ACCOUNT_INFO: AccountInfo<Buffer> = {
executable: false,
lamports: LAMPORTS_PER_SOL,
owner: new PublicKey(SB_ON_DEMAND_PID),
rentEpoch: 0,
data: Buffer.from(QUEUE_ACCOUNT_DATA, 'base64'),
};

const getOracleAccountInfo = (accountData: string): AccountInfo<Buffer> => {
return {
executable: false,
lamports: LAMPORTS_PER_SOL,
owner: new PublicKey(SB_ON_DEMAND_PID),
rentEpoch: 0,
data: Buffer.from(accountData, 'base64'),
};
};

describe('switchboard on demand', () => {
const chProgram = anchor.workspace.Drift as Program;
Expand All @@ -35,8 +78,40 @@ describe('switchboard on demand', () => {
[
// load account infos into banks client like this
{
address: undefined,
info: undefined,
address: PULL_FEED_ADDRESS,
info: PULL_FEED_ACCOUNT_INFO,
},
{
address: QUEUE_ADDRESS,
info: QUEUE_ACCOUNT_INFO,
},
{
address: ORACLE_ADDRESS_1,
info: getOracleAccountInfo(ORACLE_ADDRESS_1_DATA),
},
{
address: ORACLE_ADDRESS_2,
info: getOracleAccountInfo(ORACLE_ADDRESS_2_DATA),
},
{
address: ORACLE_ADDRESS_3,
info: getOracleAccountInfo(ORACLE_ADDRESS_3_DATA),
},
{
address: ORACLE_ADDRESS_4,
info: getOracleAccountInfo(ORACLE_ADDRESS_4_DATA),
},
{
address: ORACLE_ADDRESS_5,
info: getOracleAccountInfo(ORACLE_ADDRESS_5_DATA),
},
{
address: ORACLE_ADDRESS_6,
info: getOracleAccountInfo(ORACLE_ADDRESS_6_DATA),
},
{
address: ORACLE_ADDRESS_7,
info: getOracleAccountInfo(ORACLE_ADDRESS_7_DATA),
},
]
);
Expand Down Expand Up @@ -69,7 +144,7 @@ describe('switchboard on demand', () => {
oracleInfos: [
{
publicKey: PULL_FEED_ADDRESS,
source: OracleSource.PYTH_PULL,
source: OracleSource.SWITCHBOARD_ON_DEMAND,
},
],
// BANKRUN DOES NOT WORK WITH WEBSOCKET
Expand Down
Loading

0 comments on commit 498f8b9

Please sign in to comment.