added createStaticFeed to SwitchboardTestContext

This commit is contained in:
Conner Gallagher 2023-05-31 09:01:31 -06:00
parent ba5b6877a8
commit d52c89f811
29 changed files with 4598 additions and 95 deletions

View File

@ -26,7 +26,7 @@ jobs:
uses: ./.github/actions/setup-workspace
with:
solanaVersion: "v1.14.10"
anchorVersion: "0.27.0"
anchorVersion: "0.26.0"
nodeVersion: ${{ matrix.nodeVersion }}
- name: Cache Build
@ -69,7 +69,7 @@ jobs:
uses: ./.github/actions/setup-workspace
with:
solanaVersion: "v1.14.10"
anchorVersion: "0.27.0"
anchorVersion: "0.26.0"
nodeVersion: ${{ matrix.nodeVersion }}
- name: Cache Build

1
.gitignore vendored
View File

@ -16,7 +16,6 @@ __pycache__
# Rust
programs/**/target
Cargo.lock
.anchor
target
.crates

View File

@ -1,6 +1,6 @@
{
"name": "@switchboard-xyz/solana.js",
"version": "2.1.16",
"version": "2.1.18",
"author": "",
"license": "MIT",
"description": "A Typescript client to interact with Switchboard on Solana.",

View File

@ -1,5 +1,10 @@
import { AggregatorAccountData } from './generated';
import {
AggregatorAccount,
CreateQueueFeedParams,
CreateQueueOracleParams,
createStaticFeed,
JobAccount,
LoadedSwitchboardNetwork,
loadKeypair,
NetworkInitParams,
@ -7,10 +12,13 @@ import {
QueueAccount,
SwitchboardNetwork,
SwitchboardProgram,
TransactionObject,
updateStaticFeed,
} from '.';
import { AnchorProvider } from '@coral-xyz/anchor';
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import { OracleJob } from '@switchboard-xyz/common';
// import {
// IOracleConfig,
// NodeOracle,
@ -325,6 +333,70 @@ export class SwitchboardTestContext {
public static loadKeypair(keypairPath: string): Keypair {
return loadKeypair(keypairPath);
}
/**
* Create a feed and wait for it to resolve to a static value
* @param params - the aggregator init params and the static value to resolve the feed to
* @param timeout - the number of milliseconds to wait before timing out
*
* Basic usage example:
*
* ```ts
* let switchboard: SwitchboardTestContext;
*
* const [staticFeedAccount] = await switchboard.createStaticFeed({
* value: 10,
* })
* const staticFeedValue: Big = await staticFeedAccount.fetchLatestValue();
* assert(staticFeedValue.toNumber() === 10, "StaticFeedValueMismatch");
* ```
*/
public async createStaticFeed(
params: Partial<CreateQueueFeedParams> & { value: number },
timeout = 30000
): Promise<[AggregatorAccount, AggregatorAccountData]> {
const [aggregatorAccount, aggregatorState] = await createStaticFeed(
this.network.queue.account,
params,
timeout
);
return [aggregatorAccount, aggregatorState];
}
/**
* Update an existing aggregator that resolves to a new static value, then await the new result
* @param aggregatorAccount - the aggregator account to modify
* @param value - the static value the feed will resolve to
* @param timeout - the number of milliseconds to wait before timing out
*
* Basic usage example:
*
* ```ts
* let switchboard: SwitchboardTestContext;
* let staticFeedAccount: AggregatorAccount;
*
* [staticFeedAccount] = await switchboard.createStaticFeed({
* value: 10,
* });
* const staticFeedValue: Big = await staticFeedAccount.fetchLatestValue();
* assert(staticFeedValue.toNumber() === 10, "StaticFeedValueMismatch");
*
* await switchboard.updateStaticFeed(
* staticFeedAccount,
* 25
* );
* staticFeedValue = await staticFeedAccount.fetchLatestValue();
* assert(staticFeedValue.toNumber() === 25, "StaticFeedValueMismatch");
* ```
*/
public async updateStaticFeed(
aggregatorAccount: AggregatorAccount,
value: number,
timeout = 30000
): Promise<AggregatorAccountData> {
const state = await updateStaticFeed(aggregatorAccount, value, timeout);
return state;
}
}
export { SwitchboardTestContext as SwitchboardTestContextV2 };

View File

@ -76,6 +76,7 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
*/
public static getMetadata = (queue: types.OracleQueueAccountData) =>
toUtf8(queue.metadata);
/** Load an existing QueueAccount with its current on-chain state */
public static async load(
program: SwitchboardProgram,
@ -89,46 +90,6 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
return [account, state];
}
/**
* Return a oracle queue account state initialized to the default values.
*/
public static default(): types.OracleQueueAccountData {
const buffer = Buffer.alloc(1269, 0);
types.OracleQueueAccountData.discriminator.copy(buffer, 0);
return types.OracleQueueAccountData.decode(buffer);
}
/**
* Create a mock account info for a given oracle queue config. Useful for test integrations.
*/
public static createMock(
programId: PublicKey,
data: Partial<types.OracleQueueAccountData>,
options?: {
lamports?: number;
rentEpoch?: number;
}
): AccountInfo<Buffer> {
const fields: types.OracleQueueAccountDataFields = {
...QueueAccount.default(),
...data,
// any cleanup actions here
};
const state = new types.OracleQueueAccountData(fields);
const buffer = Buffer.alloc(QueueAccount.size, 0);
types.OracleQueueAccountData.discriminator.copy(buffer, 0);
types.OracleQueueAccountData.layout.encode(state, buffer, 8);
return {
executable: false,
owner: programId,
lamports: options?.lamports ?? 1 * LAMPORTS_PER_SOL,
data: buffer,
rentEpoch: options?.rentEpoch ?? 0,
};
}
/**
* Invoke a callback each time a QueueAccount's data has changed on-chain.
* @param callback - the callback invoked when the queues state changes

View File

@ -1,4 +1,14 @@
import { Keypair } from '@solana/web3.js';
import {
type AggregatorAccount,
type CreateQueueFeedParams,
JobAccount,
type QueueAccount,
} from './accounts';
import { type AggregatorAccountData } from './generated';
import { TransactionObject } from './TransactionObject';
import { Keypair, PublicKey } from '@solana/web3.js';
import { OracleJob } from '@switchboard-xyz/common';
import fs from 'fs';
import os from 'os';
import path from 'path';
@ -25,3 +35,143 @@ export function loadKeypair(keypairPath: string): Keypair {
new Uint8Array(JSON.parse(fs.readFileSync(fullPath, 'utf-8')))
);
}
/**
* Create a feed and wait for it to resolve to a static value
* @param queueAccount - the oracle queue to create the feed on
* @param params - the aggregator init params and the static value to resolve the feed to
* @param timeout - the number of milliseconds to wait before timing out
*
* Basic usage example:
*
* ```ts
* import { createStaticFeed, QueueAccount, AggregatorAccount } from "@switchboard-xyz/solana.js";
*
* let queueAccount: QueueAccount;
* let staticFeedAccount: AggregatorAccount;
*
* [staticFeedAccount] = await createStaticFeed(queueAccount, {
* value: 10,
* });
* const staticFeedValue: Big = await staticFeedAccount.fetchLatestValue();
* assert(staticFeedValue.toNumber() === 10, "StaticFeedValueMismatch");
* ```
*/
export async function createStaticFeed(
queueAccount: QueueAccount,
params: Partial<CreateQueueFeedParams> & { value: number },
timeout = 30000
): Promise<[AggregatorAccount, AggregatorAccountData]> {
const [aggregatorAccount] = await queueAccount.createFeed({
...params,
batchSize: params.batchSize ?? 1,
minUpdateDelaySeconds: params.minUpdateDelaySeconds ?? 10,
minRequiredOracleResults: params.minRequiredOracleResults ?? 1,
minRequiredJobResults: params.minRequiredJobResults ?? 1,
jobs: [
{
weight: 1,
data: OracleJob.encodeDelimited(
OracleJob.fromObject({
tasks: [
{
valueTask: {
value: params.value,
},
},
],
})
).finish(),
},
],
});
const [state] = await aggregatorAccount.openRoundAndAwaitResult(
undefined,
timeout
);
return [aggregatorAccount, state];
}
/**
* Update an existing aggregator that resolves to a new static value, then await the new result
* @param aggregatorAccount - the aggregator account to modify
* @param value - the static value the feed will resolve to
* @param timeout - the number of milliseconds to wait before timing out
*
* Basic usage example:
*
* ```ts
* import { updateStaticFeed, AggregatorAccount } from "@switchboard-xyz/solana.js";
*
* let staticFeedAccount: AggregatorAccount;
*
* const staticFeedState = await updateStaticFeed(staticFeedAccount, 100);
* staticFeedValue = AggregatorAccount.decodeLatestValue(staticFeedState);
* assert(staticFeedValue.toNumber() === 100, "StaticFeedValueMismatch");
* ```
*/
export async function updateStaticFeed(
aggregatorAccount: AggregatorAccount,
value: number,
timeout = 30000
): Promise<AggregatorAccountData> {
const aggregator = await aggregatorAccount.loadData();
const [jobAccount, jobInit] = JobAccount.createInstructions(
aggregatorAccount.program,
aggregatorAccount.program.walletPubkey,
{
data: OracleJob.encodeDelimited(
OracleJob.create({
tasks: [
OracleJob.Task.create({
valueTask: OracleJob.ValueTask.create({
value,
}),
}),
],
})
).finish(),
}
);
const oldJobKeys = aggregator.jobPubkeysData.filter(
pubkey => !pubkey.equals(PublicKey.default)
);
const oldJobs: Array<[JobAccount, number]> = oldJobKeys.map((pubkey, i) => [
new JobAccount(aggregatorAccount.program, pubkey),
i,
]);
const removeJobTxns = oldJobs.map(job =>
aggregatorAccount.removeJobInstruction(
aggregatorAccount.program.walletPubkey,
{
job: job[0],
jobIdx: job[1],
}
)
);
const addJobTxn = aggregatorAccount.addJobInstruction(
aggregatorAccount.program.walletPubkey,
{ job: jobAccount }
);
const txns = TransactionObject.pack([
...jobInit,
...removeJobTxns,
addJobTxn,
]);
await aggregatorAccount.program.signAndSendAll(txns);
const [state] = await aggregatorAccount.openRoundAndAwaitResult(
undefined,
timeout
);
return state;
}

View File

@ -18,5 +18,5 @@ cpi = ["no-entrypoint"]
[dependencies]
switchboard-v2 = { path = "../../rust/switchboard-v2" }
# switchboard-v2 = { version = "^0.1.23" }
anchor-lang = "^0.27.0"
solana-program = "~1.14.0"
anchor-lang = "^0.26.0"
solana-program = "~1.14.0"

2152
programs/anchor-feed-parser/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,6 @@ cpi = ["no-entrypoint"]
[dependencies]
switchboard-v2 = { path = "../../rust/switchboard-v2" }
# switchboard-v2 = { version = "0.1.23" }
anchor-lang = "^0.27.0"
anchor-lang = "^0.26.0"
solana-program = "~1.14.0"
bytemuck = "1.7.2"
bytemuck = "1.13.1"

View File

@ -1,8 +1,9 @@
import * as anchor from "@coral-xyz/anchor";
import { OracleJob, sleep } from "@switchboard-xyz/common";
import { Big, OracleJob, sleep } from "@switchboard-xyz/common";
import {
AggregatorAccount,
SwitchboardTestContext,
types,
} from "@switchboard-xyz/solana.js";
import assert from "assert";
import { AnchorFeedParser } from "../target/types/anchor_feed_parser";
@ -116,4 +117,26 @@ describe("anchor-feed-parser test", () => {
console.log(`Feed Result: ${feedResult}`);
assert(feedResult === 100, "FeedResultMismatch");
});
it("Creates a static feed then updates the value", async () => {
let staticFeedAccount: AggregatorAccount;
let staticFeedState: types.AggregatorAccountData;
let staticFeedValue: Big;
[staticFeedAccount, staticFeedState] = await switchboard.createStaticFeed({
value: 10,
minUpdateDelaySeconds: 5,
});
staticFeedValue = AggregatorAccount.decodeLatestValue(staticFeedState);
assert(staticFeedValue.toNumber() === 10, "StaticFeedValueMismatch");
await sleep(5000 * 2);
staticFeedState = await switchboard.updateStaticFeed(staticFeedAccount, 25);
staticFeedValue = AggregatorAccount.decodeLatestValue(staticFeedState);
assert(staticFeedValue.toNumber() === 25, "StaticFeedValueMismatch");
});
});

View File

@ -21,5 +21,5 @@ overflow-checks = true
[dependencies]
switchboard-v2 = { path = "../../rust/switchboard-v2" }
# switchboard-v2 = { version = "^0.1.23" }
anchor-lang = "^0.27.0"
solana-program = "^1.13.6"
anchor-lang = "^0.26.0"
solana-program = "~1.14.0"

View File

@ -18,7 +18,7 @@ cpi = ["no-entrypoint"]
[dependencies]
switchboard-v2 = { path = "../../rust/switchboard-v2" }
# switchboard-v2 = { version = "^0.1.23" }
anchor-lang = "^0.27.0"
anchor-spl = "^0.27.0"
anchor-lang = "^0.26.0"
anchor-spl = "^0.26.0"
solana-program = "~1.14.0"
bytemuck = "1.7.2"
bytemuck = "1.13.1"

View File

@ -18,7 +18,7 @@ cpi = ["no-entrypoint"]
[dependencies]
switchboard-v2 = { path = "../../rust/switchboard-v2" }
# switchboard-v2 = { version = "^0.1.23" }
anchor-lang = "^0.27.0"
anchor-spl = "^0.27.0"
anchor-lang = "^0.26.0"
anchor-spl = "^0.26.0"
solana-program = "~1.14.0"
bytemuck = "1.7.2"
bytemuck = "1.13.1"

View File

@ -1,2 +1 @@
/target
Cargo.lock

2142
rust/switchboard-v2/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -21,10 +21,15 @@ no-entrypoint = []
cpi = ["no-entrypoint"]
[dependencies]
anchor-lang = "~0.27.0"
anchor-spl = "~0.27.0"
rust_decimal = "=1.26.1"
solana-program = "^1.13.5"
bytemuck = "1.7.2"
bytemuck = "1.13.1"
superslice = "1"
spl-token = "3.5"
solana-program = ">= 1.13.5, < 1.15.0"
anchor-lang = "0.26.0"
anchor-spl = "0.26.0"
# https://github.com/coral-xyz/anchor/issues/2502
# anchor-lang = { git = "https://github.com/coral-xyz/anchor", version = "0.27.0" }
# anchor-spl = { git = "https://github.com/coral-xyz/anchor", version = "0.27.0" }
toml_datetime = "=0.6.1"
winnow = "=0.4.1"
toml_edit = "=0.19.8"

View File

@ -5,7 +5,7 @@ use anchor_lang::Discriminator;
use rust_decimal::Decimal;
use std::cell::Ref;
#[zero_copy(unsafe)]
#[zero_copy]
#[repr(packed)]
#[derive(Default, Debug, PartialEq, Eq)]
pub struct Hash {
@ -13,7 +13,7 @@ pub struct Hash {
pub data: [u8; 32],
}
#[zero_copy(unsafe)]
#[zero_copy]
#[repr(packed)]
#[derive(Default, Debug, PartialEq, Eq)]
pub struct AggregatorRound {
@ -54,14 +54,14 @@ pub enum AggregatorResolutionMode {
ModeRoundResolution = 0,
ModeSlidingResolution = 1,
}
#[account(zero_copy(unsafe))]
#[account(zero_copy)]
#[repr(packed)]
pub struct SlidingResultAccountData {
pub data: [SlidingWindowElement; 16],
pub bump: u8,
pub _ebuf: [u8; 512],
}
#[zero_copy(unsafe)]
#[zero_copy]
#[derive(Default)]
#[repr(packed)]
pub struct SlidingWindowElement {
@ -72,7 +72,7 @@ pub struct SlidingWindowElement {
}
// #[zero_copy]
#[account(zero_copy(unsafe))]
#[account(zero_copy)]
#[repr(packed)]
#[derive(Debug, PartialEq)]
pub struct AggregatorAccountData {

View File

@ -1,7 +1,7 @@
use anchor_lang::prelude::*;
use bytemuck::{Pod, Zeroable};
#[zero_copy(unsafe)]
#[zero_copy]
#[derive(Default)]
#[repr(packed)]
pub struct CrankRow {
@ -13,7 +13,7 @@ pub struct CrankRow {
unsafe impl Pod for CrankRow {}
unsafe impl Zeroable for CrankRow {}
#[account(zero_copy(unsafe))]
#[account(zero_copy)]
#[repr(packed)]
pub struct CrankAccountData {
/// Name of the crank to store on-chain.

View File

@ -5,7 +5,7 @@ use rust_decimal::prelude::{FromPrimitive, ToPrimitive};
use rust_decimal::Decimal;
use std::convert::{From, TryInto};
#[zero_copy(unsafe)]
#[zero_copy]
#[repr(packed)]
#[derive(Default, Debug, Eq, PartialEq)]
pub struct SwitchboardDecimal {

View File

@ -2,7 +2,7 @@
use anchor_lang::prelude::*;
use bytemuck::{Pod, Zeroable};
#[zero_copy(unsafe)]
#[zero_copy]
#[repr(packed)]
pub struct AccountMetaZC {
pub pubkey: Pubkey,
@ -10,7 +10,7 @@ pub struct AccountMetaZC {
pub is_writable: bool,
}
#[zero_copy(unsafe)]
#[zero_copy]
#[repr(packed)]
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct AccountMetaBorsh {
@ -19,7 +19,7 @@ pub struct AccountMetaBorsh {
pub is_writable: bool,
}
#[zero_copy(unsafe)]
#[zero_copy]
#[repr(packed)]
pub struct CallbackZC {
/// The program ID of the callback program being invoked.
@ -49,7 +49,7 @@ pub struct Callback {
pub ix_data: Vec<u8>,
}
#[zero_copy(unsafe)]
#[zero_copy]
#[repr(packed)]
pub struct VrfRound {
/// The alpha bytes used to calculate the VRF proof.
@ -101,7 +101,7 @@ impl std::fmt::Display for VrfStatus {
}
}
#[zero_copy(unsafe)]
#[zero_copy]
#[repr(packed)]
pub struct EcvrfProofZC {
pub Gamma: EdwardsPointZC, // RistrettoPoint
@ -117,7 +117,7 @@ impl Default for EcvrfProofZC {
/// The `Scalar` struct holds an integer \\(s < 2\^{255} \\) which
/// represents an element of \\(\mathbb Z / \ell\\).
#[allow(dead_code)]
#[zero_copy(unsafe)]
#[zero_copy]
#[repr(packed)]
pub struct Scalar {
/// `bytes` is a little-endian byte encoding of an integer representing a scalar modulo the
@ -158,7 +158,7 @@ pub struct FieldElement51(pub(crate) [u64; 5]);
unsafe impl Pod for FieldElement51 {}
unsafe impl Zeroable for FieldElement51 {}
#[zero_copy(unsafe)]
#[zero_copy]
#[repr(packed)]
pub struct FieldElementZC {
pub(crate) bytes: [u64; 5],
@ -197,7 +197,7 @@ pub struct CompletedPoint {
pub Z: FieldElement51,
pub T: FieldElement51,
}
#[zero_copy(unsafe)]
#[zero_copy]
#[repr(packed)]
pub struct CompletedPointZC {
pub X: FieldElementZC,
@ -243,7 +243,7 @@ pub struct EdwardsPoint {
pub(crate) T: FieldElement51,
}
#[allow(dead_code)]
#[zero_copy(unsafe)]
#[zero_copy]
#[repr(packed)]
pub struct EdwardsPointZC {
pub(crate) X: FieldElementZC,
@ -271,7 +271,7 @@ pub struct ProjectivePoint {
pub Y: FieldElement51,
pub Z: FieldElement51,
}
#[zero_copy(unsafe)]
#[zero_copy]
#[repr(packed)]
pub struct ProjectivePointZC {
pub(crate) X: FieldElementZC,
@ -304,7 +304,7 @@ impl Into<ProjectivePoint> for ProjectivePointZC {
}
}
#[zero_copy(unsafe)]
#[zero_copy]
#[repr(packed)]
pub struct EcvrfIntermediate {
pub r: FieldElementZC,
@ -317,7 +317,7 @@ unsafe impl Pod for EcvrfIntermediate {}
unsafe impl Zeroable for EcvrfIntermediate {}
#[allow(non_snake_case)]
#[zero_copy(unsafe)]
#[zero_copy]
#[repr(packed)]
pub struct VrfBuilder {
/// The OracleAccountData that is producing the randomness.

View File

@ -6,7 +6,7 @@ use bytemuck::{Pod, Zeroable};
use std::cell::Ref;
use superslice::*;
#[zero_copy(unsafe)]
#[zero_copy]
#[derive(Default)]
#[repr(packed)]
pub struct AggregatorHistoryRow {

View File

@ -1,6 +1,6 @@
use anchor_lang::prelude::*;
#[account(zero_copy(unsafe))]
#[account(zero_copy)]
#[repr(packed)]
pub struct LeaseAccountData {
/// Public key of the token account holding the lease contract funds until rewarded to oracles for successfully processing updates

View File

@ -7,7 +7,7 @@ pub enum OracleResponseType {
TypeDisagreement,
TypeNoResponse,
}
#[zero_copy(unsafe)]
#[zero_copy]
#[derive(Default)]
#[repr(packed)]
pub struct OracleMetrics {
@ -31,7 +31,7 @@ pub struct OracleMetrics {
pub total_late_response: u128,
}
#[account(zero_copy(unsafe))]
#[account(zero_copy)]
#[repr(packed)]
pub struct OracleAccountData {
/// Name of the oracle to store on-chain.

View File

@ -17,7 +17,7 @@ pub enum SwitchboardPermission {
PermitVrfRequests = 1 << 2,
}
#[account(zero_copy(unsafe))]
#[account(zero_copy)]
#[repr(packed)]
pub struct PermissionAccountData {
/// The authority that is allowed to set permissions for this account.

View File

@ -2,7 +2,7 @@ use super::decimal::SwitchboardDecimal;
use anchor_lang::prelude::*;
use bytemuck::try_cast_slice_mut;
#[account(zero_copy(unsafe))]
#[account(zero_copy)]
#[repr(packed)]
pub struct OracleQueueAccountData {
/// Name of the queue to store on-chain.
@ -76,7 +76,7 @@ impl OracleQueueAccountData {
pub fn get_mint(&self) -> Pubkey {
if self.mint == Pubkey::default() {
return spl_token::native_mint::ID;
return anchor_spl::token::ID;
}
self.mint
}

View File

@ -1,6 +1,6 @@
use anchor_lang::prelude::*;
#[account(zero_copy(unsafe))]
#[account(zero_copy)]
#[repr(packed)]
pub struct SbState {
/// The account authority permitted to make account changes.

View File

@ -12,7 +12,7 @@ use std::cell::Ref;
// VrfSetCallback
// VrfClose
#[account(zero_copy(unsafe))]
#[account(zero_copy)]
#[repr(packed)]
pub struct VrfAccountData {
/// The current status of the VRF account.

View File

@ -10,7 +10,7 @@ use std::cell::Ref;
// VrfLiteRequestRandomnessParams
// VrfLiteCloseParams
#[account(zero_copy(unsafe))]
#[account(zero_copy)]
#[repr(packed)]
pub struct VrfLiteAccountData {
/// The bump used to derive the SbState account.

View File

@ -18,7 +18,7 @@ pub struct VrfPoolRow {
}
#[repr(packed)]
#[account(zero_copy(unsafe))]
#[account(zero_copy)]
pub struct VrfPoolAccountData {
/// ACCOUNTS
pub authority: Pubkey, // authority can never be changed or else vrf accounts are useless