Devnet feature flag and more account structs

added mainnet feature flag to dynamically check account owners
added SbState, OracleQueueAccountData, OracleAccountData, CrankAccountData, LeaseAccountData, PermissionAccountData, and JobAccountData structs
added PermissionSet CPI instruction
removed map_err on invoke calls to prevent masking errors
This commit is contained in:
Conner Gallagher 2022-07-26 14:49:55 -06:00
parent 6a58d6e2f4
commit cb507d9be5
30 changed files with 782 additions and 164 deletions

View File

@ -7,8 +7,8 @@ members = [
]
[provider]
cluster = "devnet"
# cluster = "localnet"
# cluster = "devnet"
cluster = "localnet"
wallet = "../payer-keypair.json"
# wallet = "~/.config/solana/id.json"

View File

@ -16,7 +16,7 @@ cpi = ["no-entrypoint"]
default = []
[dependencies]
# switchboard-v2 = { path = "../../libraries/rs" }
switchboard-v2 = "^0.1.12"
switchboard-v2 = { path = "../../../libraries/rs", features = ["devnet"] }
# switchboard-v2 = { version = "^0.1.13", features = ["devnet"] }
anchor-lang = "^0.25.0"
solana-program = "~1.10.29"

View File

@ -1,13 +1,17 @@
use anchor_lang::prelude::*;
use anchor_lang::solana_program::clock;
pub use switchboard_v2::{BufferRelayerAccountData, SWITCHBOARD_V2_DEVNET, SWITCHBOARD_V2_MAINNET};
pub use switchboard_v2::{BufferRelayerAccountData, SWITCHBOARD_PROGRAM_ID};
declare_id!("96punQGZDShZGkzsBa3SsfTxfUnwu4XGpzXbhF7NTgcP");
#[derive(Accounts)]
#[instruction(params: ReadResultParams)]
pub struct ReadResult<'info> {
/// CHECK:
/// CHECK
#[account(mut,
constraint =
*buffer.owner == SWITCHBOARD_PROGRAM_ID @ BufferErrorCode::InvalidSwitchboardAccount
)]
pub buffer: AccountInfo<'info>,
}
@ -26,13 +30,6 @@ pub mod anchor_buffer_parser {
) -> anchor_lang::Result<()> {
let buffer_account = &ctx.accounts.buffer;
// check buffer owner
let owner = *buffer_account.owner;
if owner != SWITCHBOARD_V2_DEVNET && owner != SWITCHBOARD_V2_MAINNET {
msg!("Feed owner = {:?}", owner);
return Err(error!(BufferErrorCode::InvalidSwitchboardAccount));
}
// load and deserialize buffer
let buffer = BufferRelayerAccountData::new(buffer_account)?;

View File

@ -16,7 +16,8 @@ cpi = ["no-entrypoint"]
default = []
[dependencies]
# switchboard-v2 = { path = "../../libraries/rs" }
switchboard-v2 = "^0.1.12"
switchboard-v2 = { path = "../../../libraries/rs", features = ["devnet"] }
# switchboard-v2 = { version = "^0.1.13", features = ["devnet"] }
anchor-lang = "^0.25.0"
solana-program = "~1.10.29"
solana-program = "~1.10.29"
bytemuck = "1.7.2"

View File

@ -1,17 +1,20 @@
use anchor_lang::prelude::*;
use anchor_lang::solana_program::clock;
// use anchor_lang::{Discriminator, Owner, ZeroCopy};
// use bytemuck::{Pod, Zeroable};
use std::convert::TryInto;
pub use switchboard_v2::{
AggregatorAccountData, SwitchboardDecimal, SWITCHBOARD_V2_DEVNET, SWITCHBOARD_V2_MAINNET,
};
pub use switchboard_v2::{AggregatorAccountData, SwitchboardDecimal, SWITCHBOARD_PROGRAM_ID};
declare_id!("FnsPs665aBSwJRu2A8wGv6ZT76ipR41kHm4hoA3B1QGh");
#[derive(Accounts)]
#[instruction(params: ReadResultParams)]
pub struct ReadResult<'info> {
/// CHECK:
pub aggregator: AccountInfo<'info>,
#[account(
constraint =
*aggregator.to_account_info().owner == SWITCHBOARD_PROGRAM_ID @ FeedErrorCode::InvalidSwitchboardAccount
)]
pub aggregator: AccountLoader<'info, AggregatorAccountData>,
}
#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
@ -27,16 +30,7 @@ pub mod anchor_feed_parser {
ctx: Context<ReadResult>,
params: ReadResultParams,
) -> anchor_lang::Result<()> {
let aggregator = &ctx.accounts.aggregator;
// check feed owner
let owner = *aggregator.owner;
if owner != SWITCHBOARD_V2_DEVNET && owner != SWITCHBOARD_V2_MAINNET {
return Err(error!(FeedErrorCode::InvalidSwitchboardAccount));
}
// load and deserialize feed
let feed = AggregatorAccountData::new(aggregator)?;
let feed = &ctx.accounts.aggregator.load()?;
// get result
let val: f64 = feed.get_result()?.try_into()?;

View File

@ -2,7 +2,6 @@ import * as anchor from "@project-serum/anchor";
import { PublicKey } from "@solana/web3.js";
import { sleep, SwitchboardTestContext } from "@switchboard-xyz/sbv2-utils";
import type { AnchorWallet } from "@switchboard-xyz/switchboard-v2";
import assert from "assert";
import chai from "chai";
import {
AnchorFeedParser,
@ -82,24 +81,26 @@ describe("anchor-feed-parser test", () => {
});
it("Fails to read feed if confidence interval is exceeded", async () => {
// await assertThrowsAsync(
// async () =>
// feedParserProgram.methods
// await assert.rejects(
// async () => {
// await feedParserProgram.methods
// .readResult({ maxConfidenceInterval: 0.0000000001 })
// .accounts({ aggregator: aggregatorKey })
// .rpc(),
// /Error/
// .rpc();
// },
// /ConfidenceIntervalExceeded/,
// "Confidence interval was not exceeded"
// );
await assert.rejects(
async () => {
await feedParserProgram.methods
.readResult({ maxConfidenceInterval: 0.0000000001 })
.accounts({ aggregator: aggregatorKey })
.rpc();
},
/ConfidenceIntervalExceeded/,
"Confidence interval was not exceeded"
);
try {
await feedParserProgram.methods
.readResult({ maxConfidenceInterval: 0.0000000001 })
.accounts({ aggregator: aggregatorKey })
.rpc();
} catch (error: any) {
if (!error.toString().includes("ConfidenceIntervalExceeded")) {
throw error;
}
}
});
});

View File

@ -16,8 +16,8 @@ cpi = ["no-entrypoint"]
default = []
[dependencies]
# switchboard-v2 = { path = "../../libraries/rs" }
switchboard-v2 = "^0.1.12"
switchboard-v2 = { path = "../../../libraries/rs", features = ["devnet"] }
# switchboard-v2 = { version = "^0.1.13", features = ["devnet"] }
anchor-lang = "^0.25.0"
anchor-spl = "^0.25.0"
solana-program = "~1.10.29"

View File

@ -22,9 +22,13 @@ pub struct InitState<'info> {
pub authority: AccountInfo<'info>,
/// CHECK:
#[account(mut, signer)]
pub payer: AccountInfo<'info>,
/// CHECK:
pub vrf: AccountInfo<'info>,
pub payer: AccountInfo<'info>,
#[account(
constraint =
*vrf.to_account_info().owner == SWITCHBOARD_PROGRAM_ID @ VrfErrorCode::InvalidSwitchboardAccount
)]
pub vrf: AccountLoader<'info, VrfAccountData>,
#[account(address = solana_program::system_program::ID)]
pub system_program: Program<'info, System>,
}
@ -48,20 +52,19 @@ impl InitState<'_> {
msg!("Actuate init");
msg!("Checking VRF Account");
let vrf_account_info = &ctx.accounts.vrf;
let vrf = VrfAccountData::new(vrf_account_info)
.map_err(|_| VrfErrorCode::InvalidSwitchboardVrfAccount)?;
let vrf = ctx.accounts.vrf.load()?;
// client state needs to be authority in order to sign request randomness instruction
if vrf.authority != ctx.accounts.state.key() {
return Err(error!(VrfErrorCode::InvalidSwitchboardVrfAccount));
return Err(error!(VrfErrorCode::InvalidAuthorityError));
}
drop(vrf);
msg!("Setting VrfClient state");
let mut state = ctx.accounts.state.load_init()?;
*state = VrfClient::default();
state.bump = ctx.bumps.get("state").unwrap().clone();
state.authority = ctx.accounts.authority.key.clone();
state.vrf = ctx.accounts.vrf.key.clone();
state.vrf = ctx.accounts.vrf.key();
msg!("Setting VrfClient max_result");
if params.max_result == 0 {

View File

@ -1,6 +1,6 @@
use crate::*;
use anchor_lang::prelude::*;
pub use switchboard_v2::{VrfAccountData, VrfRequestRandomness};
pub use switchboard_v2::{VrfAccountData, VrfRequestRandomness, OracleQueueAccountData, PermissionAccountData, SbState};
use anchor_spl::token::Token;
use anchor_lang::solana_program::clock;
@ -15,43 +15,78 @@ pub struct RequestResult<'info> {
authority.key().as_ref(),
],
bump = state.load()?.bump,
has_one = vrf,
has_one = authority
has_one = vrf @ VrfErrorCode::InvalidVrfAccount,
has_one = authority @ VrfErrorCode::InvalidAuthorityError
)]
pub state: AccountLoader<'info, VrfClient>,
/// CHECK:
#[account(signer)] // client authority needs to sign
pub authority: AccountInfo<'info>,
/// CHECK:
#[account(constraint = switchboard_program.executable == true)]
pub switchboard_program: AccountInfo<'info>,
/// CHECK: Will be checked in the CPI instruction
#[account(mut, constraint = vrf.owner.as_ref() == switchboard_program.key().as_ref())]
pub vrf: AccountInfo<'info>,
/// CHECK: Will be checked in the CPI instruction
#[account(mut, constraint = oracle_queue.owner.as_ref() == switchboard_program.key().as_ref())]
pub oracle_queue: AccountInfo<'info>,
// SWITCHBOARD ACCOUNTS
#[account(mut,
has_one = escrow,
constraint =
*vrf.to_account_info().owner == SWITCHBOARD_PROGRAM_ID @ VrfErrorCode::InvalidSwitchboardAccount
)]
pub vrf: AccountLoader<'info, VrfAccountData>,
/// CHECK
#[account(mut,
has_one = data_buffer,
constraint =
oracle_queue.load()?.authority == queue_authority.key()
&& *oracle_queue.to_account_info().owner == SWITCHBOARD_PROGRAM_ID @ VrfErrorCode::InvalidSwitchboardAccount
)]
pub oracle_queue: AccountLoader<'info, OracleQueueAccountData>,
/// CHECK: Will be checked in the CPI instruction
pub queue_authority: UncheckedAccount<'info>,
/// CHECK: Will be checked in the CPI instruction
#[account(constraint = data_buffer.owner.as_ref() == switchboard_program.key().as_ref())]
/// CHECK
#[account(mut,
constraint =
*data_buffer.owner == SWITCHBOARD_PROGRAM_ID @ VrfErrorCode::InvalidSwitchboardAccount
)]
pub data_buffer: AccountInfo<'info>,
/// CHECK: Will be checked in the CPI instruction
#[account(mut, constraint = permission.owner.as_ref() == switchboard_program.key().as_ref())]
pub permission: AccountInfo<'info>,
#[account(mut, constraint = escrow.owner == program_state.key())]
/// CHECK
#[account(mut,
constraint =
*permission.to_account_info().owner == SWITCHBOARD_PROGRAM_ID @ VrfErrorCode::InvalidSwitchboardAccount
)]
pub permission: AccountLoader<'info, PermissionAccountData>,
#[account(mut,
constraint =
escrow.owner == program_state.key()
&& escrow.mint == program_state.load()?.token_mint
)]
pub escrow: Account<'info, TokenAccount>,
#[account(mut, constraint = payer_wallet.owner == payer_authority.key())]
/// CHECK: Will be checked in the CPI instruction
#[account(mut,
constraint =
*program_state.to_account_info().owner == SWITCHBOARD_PROGRAM_ID @ VrfErrorCode::InvalidSwitchboardAccount
)]
pub program_state: AccountLoader<'info, SbState>,
/// CHECK:
#[account(
constraint =
switchboard_program.executable == true
&& *switchboard_program.key == SWITCHBOARD_PROGRAM_ID @ VrfErrorCode::InvalidSwitchboardAccount
)]
pub switchboard_program: AccountInfo<'info>,
// PAYER ACCOUNTS
#[account(mut,
constraint =
payer_wallet.owner == payer_authority.key()
&& escrow.mint == program_state.load()?.token_mint
)]
pub payer_wallet: Account<'info, TokenAccount>,
/// CHECK:
#[account(signer)]
pub payer_authority: AccountInfo<'info>,
// SYSTEM ACCOUNTS
/// CHECK:
#[account(address = solana_program::sysvar::recent_blockhashes::ID)]
pub recent_blockhashes: AccountInfo<'info>,
/// CHECK: Will be checked in the CPI instruction
#[account(constraint = program_state.owner.as_ref() == switchboard_program.key().as_ref())]
pub program_state: AccountInfo<'info>,
#[account(address = anchor_spl::token::ID)]
pub token_program: Program<'info, Token>,
}
@ -90,7 +125,7 @@ impl RequestResult<'_> {
token_program: ctx.accounts.token_program.to_account_info(),
};
let vrf_key = ctx.accounts.vrf.key.clone();
let vrf_key = ctx.accounts.vrf.key();
let authority_key = ctx.accounts.authority.key.clone();
msg!("bump: {}", bump);

View File

@ -5,10 +5,15 @@ pub use switchboard_v2::VrfAccountData;
#[derive(Accounts)]
pub struct UpdateResult<'info> {
#[account(mut)]
#[account(mut,
has_one = vrf @ VrfErrorCode::InvalidVrfAccount
)]
pub state: AccountLoader<'info, VrfClient>,
/// CHECK:
pub vrf: AccountInfo<'info>,
#[account(
constraint =
*vrf.to_account_info().owner == SWITCHBOARD_PROGRAM_ID @ VrfErrorCode::InvalidSwitchboardAccount
)]
pub vrf: AccountLoader<'info, VrfAccountData>,
}
impl UpdateResult<'_> {
@ -26,8 +31,7 @@ impl UpdateResult<'_> {
timestamp: clock.unix_timestamp,
});
let vrf_account_info = &ctx.accounts.vrf;
let vrf = VrfAccountData::new(vrf_account_info)?;
let vrf = ctx.accounts.vrf.load()?;
let result_buffer = vrf.get_result()?;
if result_buffer == [0u8; 32] {
msg!("vrf buffer empty");

View File

@ -4,6 +4,8 @@ pub use actions::*;
pub use anchor_lang::prelude::*;
use anchor_spl::token::TokenAccount;
pub use switchboard_v2::SWITCHBOARD_PROGRAM_ID;
declare_id!("HjjRFjCyQH3ne6Gg8Yn3TQafrrYecRrphwLwnh2A26vM");
const MAX_RESULT: u64 = u64::MAX;
@ -71,12 +73,14 @@ pub struct VrfClientResultUpdated {
#[error_code]
#[derive(Eq, PartialEq)]
pub enum VrfErrorCode {
#[msg("Not a valid Switchboard VRF account")]
InvalidSwitchboardVrfAccount,
#[msg("Not a valid Switchboard account")]
InvalidSwitchboardAccount,
#[msg("The max result must not exceed u64")]
MaxResultExceedsMaximum,
#[msg("Current round result is empty")]
EmptyCurrentRoundResult,
#[msg("Invalid authority account provided.")]
InvalidAuthorityError,
#[msg("Invalid VRF account provided.")]
InvalidVrfAccount,
}

View File

@ -78,7 +78,7 @@ describe("anchor-vrf-parser test", () => {
return;
} catch (error: any) {
console.log(`Error: SBV2 Devnet - ${error.message}`);
console.error(error);
// console.error(error);
}
// If fails, fallback to looking for a local env file
try {

View File

@ -11,7 +11,7 @@ name = "native_feed_parser"
no-entrypoint = []
[dependencies]
# switchboard-v2 = { path = "../../libraries/rs" }
switchboard-v2 = "^0.1.12"
switchboard-v2 = { path = "../../../libraries/rs", features = ["devnet"] }
# switchboard-v2 = { version = "^0.1.13", features = ["devnet"] }
solana-program = "~1.10.29"
anchor-lang = "^0.25.0"

View File

@ -9,13 +9,13 @@ pub use solana_program::{
sysvar::Sysvar,
};
use std::convert::TryInto;
pub use switchboard_v2::{AggregatorAccountData, SWITCHBOARD_V2_DEVNET, SWITCHBOARD_V2_MAINNET};
pub use switchboard_v2::{AggregatorAccountData, SWITCHBOARD_PROGRAM_ID};
entrypoint!(process_instruction);
fn process_instruction<'a>(
_program_id: &'a Pubkey,
accounts: &'a [AccountInfo],
accounts: &'a [AccountInfo<'a>],
_instruction_data: &'a [u8],
) -> ProgramResult {
let accounts_iter = &mut accounts.iter();
@ -25,7 +25,7 @@ fn process_instruction<'a>(
// check feed owner
let owner = *aggregator.owner;
if owner != SWITCHBOARD_V2_DEVNET && owner != SWITCHBOARD_V2_MAINNET {
if owner != SWITCHBOARD_PROGRAM_ID {
return Err(ProgramError::IncorrectProgramId);
}

View File

@ -1,8 +1,8 @@
[package]
name = "switchboard-v2"
version = "0.1.12"
version = "0.1.13"
edition = "2021"
description = "A Rust library to interact with Switchboard V2 data feeds."
description = "A Rust library to interact with Switchboard V2 accounts."
readme = "README.md"
keywords = ["switchboard", "oracle", "solana"]
homepage = "https://docs.switchboard.xyz"
@ -19,6 +19,7 @@ doctest = false
default = ["cpi"]
no-entrypoint = []
cpi = ["no-entrypoint"]
devnet = []
[dependencies]
anchor-lang = "0.25.0"

View File

@ -1,35 +1,38 @@
# switchboard-v2
A Rust library to interact with Switchboard V2's hosted data feeds.
<!-- https://badgen.net/crates/v/switchboard-v2 -->
[![cargo](https://badgen.net/crates/v/switchboard-v2)](https://crates.io/crates/switchboard-v2)&nbsp;&nbsp;
[![twitter](https://badgen.net/twitter/follow/switchboardxyz)](https://twitter.com/switchboardxyz)&nbsp;&nbsp;
## Description
# switchboard-v2
This package can be used to manage Switchboard data feed account parsing.
A Rust library to interact with Switchboard V2 accounts.
Specifically, this package will return the most recent confirmed round result
from a provided data feed AccountInfo.
This package can be used to interact and deserialize Switchboard V2 accounts.
See the [documentation](https://docs.switchboard.xyz/introduction) for more info on Switchboard.
## Features
By default the crate will default to mainnet. You must explicitly enable the `devnet` feature to use on devnet.
## Usage
### Aggregator
#### Read Latest Result
```rust
use anchor_lang::solana_program::clock;
use std::convert::TryInto;
use switchboard_v2::{AggregatorAccountData, SwitchboardDecimal, SWITCHBOARD_V2_DEVNET, SWITCHBOARD_V2_MAINNET};
use switchboard_v2::{AggregatorAccountData, SwitchboardDecimal, SWITCHBOARD_PROGRAM_ID};
// check feed owner
let owner = *aggregator.owner;
if owner != SWITCHBOARD_V2_DEVNET && owner != SWITCHBOARD_V2_MAINNET {
if owner != SWITCHBOARD_PROGRAM_ID {
return Err(error!(ErrorCode::InvalidSwitchboardAccount));
}
// deserialize account info
let feed = ctx.accounts.aggregator.load()?;
// OR
let feed = AggregatorAccountData::new(feed_account_info)?;
// get result
@ -42,7 +45,11 @@ feed.check_staleness(clock::Clock::get().unwrap().unix_timestamp, 300)?;
feed.check_confidence_interval(SwitchboardDecimal::from_f64(0.80))?;
```
### Aggregator History
**Example(s)**: [anchor-feed-parser](https://github.com/switchboard-xyz/switchboard-v2/blob/main/examples/programs/anchor-feed-parser/src/lib.rs), [native-feed-parser](https://github.com/switchboard-xyz/switchboard-v2/blob/main/examples/programs/native-feed-parser/src/lib.rs)
#### Read Aggregator History
**_Note: The Aggregator must have a history buffer initialized before using_**
```rust
use switchboard_v2::AggregatorHistoryBuffer;
@ -55,25 +62,79 @@ let one_hour_ago: f64 = history_buffer.lower_bound(current_timestamp - 3600).unw
### VRF Account
#### Read Latest Result
```rust
use switchboard_v2::VrfAccountData;
// deserialize the account info
let vrf = ctx.accounts.vrf.load()?;
// OR
let vrf = VrfAccountData::new(vrf_account_info)?;
// read the result
let result_buffer = vrf.get_result()?;
let value: &[u128] = bytemuck::cast_slice(&result_buffer[..]);
let result = value[0] % 256000 as u128;
```
**Example**: [anchor-vrf-parser](https://github.com/switchboard-xyz/switchboard-v2/blob/main/examples/programs/anchor-vrf-parser/src/actions/update_result.rs)
#### RequestRandomness CPI
```rust
pub use switchboard_v2::{VrfAccountData, VrfRequestRandomness};
let switchboard_program = ctx.accounts.switchboard_program.to_account_info();
let vrf_request_randomness = VrfRequestRandomness {
authority: ctx.accounts.state.to_account_info(),
vrf: ctx.accounts.vrf.to_account_info(),
oracle_queue: ctx.accounts.oracle_queue.to_account_info(),
queue_authority: ctx.accounts.queue_authority.to_account_info(),
data_buffer: ctx.accounts.data_buffer.to_account_info(),
permission: ctx.accounts.permission.to_account_info(),
escrow: ctx.accounts.escrow.clone(),
payer_wallet: ctx.accounts.payer_wallet.clone(),
payer_authority: ctx.accounts.payer_authority.to_account_info(),
recent_blockhashes: ctx.accounts.recent_blockhashes.to_account_info(),
program_state: ctx.accounts.program_state.to_account_info(),
token_program: ctx.accounts.token_program.to_account_info(),
};
let vrf_key = ctx.accounts.vrf.key.clone();
let authority_key = ctx.accounts.authority.key.clone();
let state_seeds: &[&[&[u8]]] = &[&[
&STATE_SEED,
vrf_key.as_ref(),
authority_key.as_ref(),
&[bump],
]];
msg!("requesting randomness");
vrf_request_randomness.invoke_signed(
switchboard_program,
params.switchboard_state_bump,
params.permission_bump,
state_seeds,
)?;
```
**Example**: [anchor-vrf-parser](https://github.com/switchboard-xyz/switchboard-v2/blob/main/examples/programs/anchor-vrf-parser/src/actions/request_result.rs)
### Buffer Relayer Account
#### Read Latest Result
```rust
use anchor_lang::solana_program::clock;
use std::convert::TryInto;
use switchboard_v2::{BufferRelayerAccountData, SWITCHBOARD_V2_DEVNET, SWITCHBOARD_V2_MAINNET};
use switchboard_v2::{BufferRelayerAccountData, SWITCHBOARD_PROGRAM_ID};
// check feed owner
let owner = *aggregator.owner;
if owner != SWITCHBOARD_V2_DEVNET && owner != SWITCHBOARD_V2_MAINNET {
if owner != SWITCHBOARD_PROGRAM_ID {
return Err(error!(ErrorCode::InvalidSwitchboardAccount));
}
@ -91,3 +152,5 @@ let result_string = String::from_utf8(buffer.result)
.map_err(|_| error!(ErrorCode::StringConversionFailed))?;
msg!("Buffer string {:?}!", result_string);
```
**Example**: [anchor-buffer-parser](https://github.com/switchboard-xyz/switchboard-v2/blob/main/examples/programs/anchor-buffer-parser/src/lib.rs)

View File

@ -2,13 +2,13 @@
use super::decimal::SwitchboardDecimal;
use super::error::SwitchboardError;
use anchor_lang::prelude::*;
use bytemuck::{Pod, Zeroable};
use std::cell::Ref;
#[zero_copy]
#[repr(packed)]
#[derive(Default, Debug, PartialEq, Eq)]
pub struct Hash {
/// The bytes used to derive the hash.
pub data: [u8; 32],
}
@ -16,77 +16,106 @@ pub struct Hash {
#[repr(packed)]
#[derive(Default, Debug, PartialEq, Eq)]
pub struct AggregatorRound {
// Maintains the number of successful responses received from nodes.
// Nodes can submit one successful response per round.
/// Maintains the number of successful responses received from nodes.
/// Nodes can submit one successful response per round.
pub num_success: u32,
/// Number of error responses.
pub num_error: u32,
/// Whether an update request round has ended.
pub is_closed: bool,
// Maintains the `solana_program::clock::Slot` that the round was opened at.
/// Maintains the `solana_program::clock::Slot` that the round was opened at.
pub round_open_slot: u64,
// Maintains the `solana_program::clock::UnixTimestamp;` the round was opened at.
/// Maintains the `solana_program::clock::UnixTimestamp;` the round was opened at.
pub round_open_timestamp: i64,
// Maintains the current median of all successful round responses.
/// Maintains the current median of all successful round responses.
pub result: SwitchboardDecimal,
// Standard deviation of the accepted results in the round.
/// Standard deviation of the accepted results in the round.
pub std_deviation: SwitchboardDecimal,
// Maintains the minimum node response this round.
/// Maintains the minimum node response this round.
pub min_response: SwitchboardDecimal,
// Maintains the maximum node response this round.
/// Maintains the maximum node response this round.
pub max_response: SwitchboardDecimal,
// pub lease_key: Pubkey,
// Pubkeys of the oracles fulfilling this round.
/// Pubkeys of the oracles fulfilling this round.
pub oracle_pubkeys_data: [Pubkey; 16],
// pub oracle_pubkeys_size: u32, IMPLIED BY ORACLE_REQUEST_BATCH_SIZE
// Represents all successful node responses this round. `NaN` if empty.
/// Represents all successful node responses this round. `NaN` if empty.
pub medians_data: [SwitchboardDecimal; 16],
// Current rewards/slashes oracles have received this round.
/// Current rewards/slashes oracles have received this round.
pub current_payout: [i64; 16],
// Optionals do not work on zero_copy. Keep track of which responses are
// fulfilled here.
/// Keep track of which responses are fulfilled here.
pub medians_fulfilled: [bool; 16],
// could do specific error codes
/// Keeps track of which errors are fulfilled here.
pub errors_fulfilled: [bool; 16],
}
#[zero_copy]
// #[zero_copy]
#[account(zero_copy)]
#[repr(packed)]
#[derive(Debug, PartialEq)]
pub struct AggregatorAccountData {
/// Name of the aggregator to store on-chain.
pub name: [u8; 32],
/// Metadata of the aggregator to store on-chain.
pub metadata: [u8; 128],
/// Reserved.
pub _reserved1: [u8; 32],
/// Pubkey of the queue the aggregator belongs to.
pub queue_pubkey: Pubkey,
// CONFIGS
/// CONFIGS
/// Number of oracles assigned to an update request.
pub oracle_request_batch_size: u32,
/// Minimum number of oracle responses required before a round is validated.
pub min_oracle_results: u32,
/// Minimum number of job results before an oracle accepts a result.
pub min_job_results: u32,
/// Minimum number of seconds required between aggregator rounds.
pub min_update_delay_seconds: u32,
pub start_after: i64, // timestamp to start feed updates at
/// Unix timestamp for which no feed update will occur before.
pub start_after: i64,
/// Change percentage required between a previous round and the current round. If variance percentage is not met, reject new oracle responses.
pub variance_threshold: SwitchboardDecimal,
pub force_report_period: i64, // If no feed results after this period, trigger nodes to report
/// Number of seconds for which, even if the variance threshold is not passed, accept new responses from oracles.
pub force_report_period: i64,
/// Timestamp when the feed is no longer needed.
pub expiration: i64,
//
/// Counter for the number of consecutive failures before a feed is removed from a queue. If set to 0, failed feeds will remain on the queue.
pub consecutive_failure_count: u64,
/// Timestamp when the next update request will be available.
pub next_allowed_update_time: i64,
/// Flag for whether an aggregators configuration is locked for editing.
pub is_locked: bool,
/// Optional, public key of the crank the aggregator is currently using. Event based feeds do not need a crank.
pub crank_pubkey: Pubkey,
/// Latest confirmed update request result that has been accepted as valid.
pub latest_confirmed_round: AggregatorRound,
/// Oracle results from the current round of update request that has not been accepted as valid yet.
pub current_round: AggregatorRound,
/// List of public keys containing the job definitions for how data is sourced off-chain by oracles.
pub job_pubkeys_data: [Pubkey; 16],
/// Used to protect against malicious RPC nodes providing incorrect task definitions to oracles before fulfillment.
pub job_hashes: [Hash; 16],
/// Number of jobs assigned to an oracle.
pub job_pubkeys_size: u32,
pub jobs_checksum: [u8; 32], // Used to confirm with oracles they are answering what they think theyre answering
/// Used to protect against malicious RPC nodes providing incorrect task definitions to oracles before fulfillment.
pub jobs_checksum: [u8; 32],
//
/// The account delegated as the authority for making account changes.
pub authority: Pubkey,
/// Optional, public key of a history buffer account storing the last N accepted results and their timestamps.
pub history_buffer: Pubkey,
/// The previous confirmed round result.
pub previous_confirmed_round_result: SwitchboardDecimal,
/// The slot when the previous confirmed round was opened.
pub previous_confirmed_round_slot: u64,
/// Whether an aggregator is permitted to join a crank.
pub disable_crank: bool,
/// Job weights used for the weighted median of the aggregator's assigned job accounts.
pub job_weights: [u8; 16],
pub _ebuf: [u8; 147], // Buffer for future info
/// Unix timestamp when the feed was created.
pub creation_timestamp: i64,
/// Reserved for future info.
pub _ebuf: [u8; 139],
}
unsafe impl Pod for AggregatorAccountData {}
unsafe impl Zeroable for AggregatorAccountData {}
impl AggregatorAccountData {
/// Returns the deserialized Switchboard Aggregator account
@ -103,17 +132,22 @@ impl AggregatorAccountData {
/// let data_feed = AggregatorAccountData::new(feed_account_info)?;
/// ```
pub fn new<'info>(
switchboard_feed: &'info AccountInfo,
switchboard_feed: &'info AccountInfo<'info>,
) -> anchor_lang::Result<Ref<'info, AggregatorAccountData>> {
let data = switchboard_feed.try_borrow_data()?;
if data.len() < AggregatorAccountData::discriminator().len() {
return Err(ErrorCode::AccountDiscriminatorNotFound.into());
}
let mut disc_bytes = [0u8; 8];
disc_bytes.copy_from_slice(&data[..8]);
if disc_bytes != AggregatorAccountData::discriminator() {
return Err(SwitchboardError::AccountDiscriminatorMismatch.into());
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
}
Ok(Ref::map(data, |data| bytemuck::from_bytes(&data[8..])))
Ok(Ref::map(data, |data| {
bytemuck::from_bytes(&data[8..std::mem::size_of::<AggregatorAccountData>() + 8])
}))
}
/// If sufficient oracle responses, returns the latest on-chain result in SwitchboardDecimal format
///
@ -177,7 +211,7 @@ impl AggregatorAccountData {
}
fn discriminator() -> [u8; 8] {
return [217, 230, 65, 101, 201, 162, 27, 125];
[217, 230, 65, 101, 201, 162, 27, 125]
}
}

View File

@ -1,27 +1,45 @@
use super::error::SwitchboardError;
use super::SWITCHBOARD_PROGRAM_ID;
use anchor_lang::prelude::*;
use anchor_lang::{Discriminator, Owner};
#[derive(AnchorDeserialize, Default, Debug)]
pub struct BufferRelayerAccountData {
/// Name of the buffer account to store on-chain.
pub name: [u8; 32],
/// Public key of the OracleQueueAccountData that is currently assigned to fulfill buffer relayer update request.
pub queue_pubkey: Pubkey,
/// Token account to reward oracles for completing update request.
pub escrow: Pubkey,
/// The account delegated as the authority for making account changes.
pub authority: Pubkey,
/// Public key of the JobAccountData that defines how the buffer relayer is updated.
pub job_pubkey: Pubkey,
/// Used to protect against malicious RPC nodes providing incorrect task definitions to oracles before fulfillment
pub job_hash: [u8; 32],
/// Minimum delay between update request.
pub min_update_delay_seconds: u32,
/// Whether buffer relayer config is locked for further changes.
pub is_locked: bool,
/// The current buffer relayer update round that is yet to be confirmed.
pub current_round: BufferRelayerRound,
/// The latest confirmed buffer relayer update round.
pub latest_confirmed_round: BufferRelayerRound,
/// The buffer holding the latest confirmed result.
pub result: Vec<u8>,
}
#[derive(Default, Debug, Clone, AnchorSerialize, AnchorDeserialize)]
pub struct BufferRelayerRound {
/// Number of successful responses.
pub num_success: u32,
/// Number of error responses.
pub num_error: u32,
/// Slot when the buffer relayer round was opened.
pub round_open_slot: u64,
/// Timestamp when the buffer relayer round was opened.
pub round_open_timestamp: i64,
/// The public key of the oracle fulfilling the buffer relayer update request.
pub oracle_pubkey: Pubkey,
}
@ -80,8 +98,14 @@ impl BufferRelayerAccountData {
}
Ok(())
}
}
impl Discriminator for BufferRelayerAccountData {
fn discriminator() -> [u8; 8] {
return [50, 35, 51, 115, 169, 219, 158, 52];
[50, 35, 51, 115, 169, 219, 158, 52]
}
}
impl Owner for BufferRelayerAccountData {
fn owner() -> solana_program::pubkey::Pubkey {
SWITCHBOARD_PROGRAM_ID
}
}

38
libraries/rs/src/crank.rs Normal file
View File

@ -0,0 +1,38 @@
#[allow(unaligned_references)]
use anchor_lang::prelude::*;
use bytemuck::{Pod, Zeroable};
#[zero_copy]
#[derive(Default)]
#[repr(packed)]
pub struct CrankRow {
/// The PublicKey of the AggregatorAccountData.
pub pubkey: Pubkey,
/// The aggregator's next available update time.
pub next_timestamp: i64,
}
unsafe impl Pod for CrankRow {}
unsafe impl Zeroable for CrankRow {}
#[account(zero_copy)]
#[repr(packed)]
pub struct CrankAccountData {
/// Name of the crank to store on-chain.
pub name: [u8; 32],
/// Metadata of the crank to store on-chain.
pub metadata: [u8; 64],
/// Public key of the oracle queue who owns the crank.
pub queue_pubkey: Pubkey,
/// Number of aggregators added to the crank.
pub pq_size: u32,
/// Maximum number of aggregators allowed to be added to a crank.
pub max_rows: u32,
/// Pseudorandom value added to next aggregator update time.
pub jitter_modifier: u8,
/// Reserved for future info.
pub _ebuf: [u8; 255],
/// The public key of the CrankBuffer account holding a collection of Aggregator pubkeys and their next allowed update time.
pub data_buffer: Pubkey,
}
impl CrankAccountData {}

View File

@ -9,7 +9,9 @@ use std::convert::{From, TryInto};
#[repr(packed)]
#[derive(Default, Debug, Eq, PartialEq)]
pub struct SwitchboardDecimal {
/// The part of a floating-point number that represents the significant digits of that number, and that is multiplied by the base, 10, raised to the power of scale to give the actual value of the number.
pub mantissa: i128,
/// The number of decimal places to move to the left to yield the actual value.
pub scale: u32,
}

View File

@ -25,4 +25,6 @@ pub enum SwitchboardError {
StaleFeed,
#[msg("Switchboard feed exceeded the confidence interval threshold")]
ConfidenceIntervalExceeded,
#[msg("Invalid authority provided to Switchboard account")]
InvalidAuthority,
}

View File

@ -1,6 +1,6 @@
#[allow(unaligned_references)]
use super::decimal::SwitchboardDecimal;
use super::error::SwitchboardError;
#[allow(unaligned_references)]
use anchor_lang::prelude::*;
use bytemuck::{try_cast_slice, try_from_bytes};
use bytemuck::{Pod, Zeroable};
@ -11,14 +11,18 @@ use superslice::*;
#[derive(Default)]
#[repr(packed)]
pub struct AggregatorHistoryRow {
/// The timestamp of the sample.
pub timestamp: i64,
/// The value of the sample.
pub value: SwitchboardDecimal,
}
unsafe impl Pod for AggregatorHistoryRow {}
unsafe impl Zeroable for AggregatorHistoryRow {}
pub struct AggregatorHistoryBuffer<'a> {
/// The current index of the round robin buffer.
pub insertion_idx: usize,
/// The array of samples collected from the aggregator.
pub rows: Ref<'a, [AggregatorHistoryRow]>,
}
impl<'a> AggregatorHistoryBuffer<'a> {

25
libraries/rs/src/job.rs Normal file
View File

@ -0,0 +1,25 @@
use anchor_lang::prelude::*;
#[account]
pub struct JobAccountData {
/// Name of the job to store on-chain.
pub name: [u8; 32],
/// Metadata of the job to store on-chain.
pub metadata: [u8; 64],
/// The account delegated as the authority for making account changes.
pub authority: Pubkey,
/// Unix timestamp when the job is considered invalid
pub expiration: i64,
/// Hash of the serialized data to prevent tampering.
pub hash: [u8; 32],
/// Serialized protobuf containing the collection of task to retrieve data off-chain.
pub data: Vec<u8>,
/// The number of data feeds referencing the job account..
pub reference_count: u32,
/// The token amount funded into a feed that contains this job account.
pub total_spent: u64,
/// Unix timestamp when the job was created on-chain.
pub created_at: i64,
}
impl JobAccountData {}

View File

@ -3,20 +3,55 @@ use solana_program::pubkey;
pub mod aggregator;
pub mod buffer_relayer;
pub mod crank;
pub mod decimal;
pub mod error;
pub mod history_buffer;
pub mod job;
pub mod oracle;
pub mod permission;
pub mod queue;
pub mod sb_state;
pub mod vrf;
pub use aggregator::{AggregatorAccountData, AggregatorRound};
pub use buffer_relayer::{BufferRelayerAccountData, BufferRelayerRound};
pub use crank::{CrankAccountData, CrankRow};
pub use decimal::SwitchboardDecimal;
pub use error::SwitchboardError;
pub use history_buffer::{AggregatorHistoryBuffer, AggregatorHistoryRow};
pub use job::JobAccountData;
pub use oracle::{OracleAccountData, OracleMetrics};
pub use permission::{PermissionAccountData, PermissionSet, SwitchboardPermission};
pub use queue::OracleQueueAccountData;
pub use sb_state::SbState;
pub use vrf::{VrfAccountData, VrfRequestRandomness, VrfRound, VrfStatus};
/// Seed used to derive the SbState PDA.
pub const STATE_SEED: &[u8] = b"STATE";
/// Seed used to derive the PermissionAccountData PDA.
pub const PERMISSION_SEED: &[u8] = b"PermissionAccountData";
/// Seed used to derive the LeaseAccountData PDA.
pub const LEASE_SEED: &[u8] = b"LeaseAccountData";
/// Seed used to derive the OracleAccountData PDA.
pub const ORACLE_SEED: &[u8] = b"OracleAccountData";
/// Discriminator used for Switchboard buffer accounts.
pub const BUFFER_DISCRIMINATOR: &[u8] = b"BUFFERxx";
/// Mainnet program id for Switchboard v2
pub const SWITCHBOARD_V2_MAINNET: Pubkey = pubkey!("SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f");
/// Devnet program id for Switchboard v2
pub const SWITCHBOARD_V2_DEVNET: Pubkey = pubkey!("2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG");
#[cfg(feature = "devnet")]
/// Switchboard Program ID.
pub const SWITCHBOARD_PROGRAM_ID: Pubkey = SWITCHBOARD_V2_DEVNET;
#[cfg(not(feature = "devnet"))]
/// Switchboard Program ID.
pub const SWITCHBOARD_PROGRAM_ID: Pubkey = SWITCHBOARD_V2_MAINNET;
#[cfg(feature = "devnet")]
declare_id!(SWITCHBOARD_V2_DEVNET);
#[cfg(not(feature = "devnet"))]
declare_id!(SWITCHBOARD_V2_MAINNET);

View File

@ -0,0 +1,59 @@
#[allow(unaligned_references)]
use anchor_lang::prelude::*;
#[derive(Copy, Clone, AnchorSerialize, AnchorDeserialize)]
pub enum OracleResponseType {
TypeSuccess,
TypeError,
TypeDisagreement,
TypeNoResponse,
}
#[zero_copy]
#[derive(Default)]
#[repr(packed)]
pub struct OracleMetrics {
/// Number of consecutive successful update request.
pub consecutive_success: u64,
/// Number of consecutive update request that resulted in an error.
pub consecutive_error: u64,
/// Number of consecutive update request that resulted in a disagreement with the accepted median result.
pub consecutive_disagreement: u64,
/// Number of consecutive update request that were posted on-chain late and not included in an accepted result.
pub consecutive_late_response: u64,
/// Number of consecutive update request that resulted in a failure.
pub consecutive_failure: u64,
/// Total number of successful update request.
pub total_success: u128,
/// Total number of update request that resulted in an error.
pub total_error: u128,
/// Total number of update request that resulted in a disagreement with the accepted median result.
pub total_disagreement: u128,
/// Total number of update request that were posted on-chain late and not included in an accepted result.
pub total_late_response: u128,
}
#[account(zero_copy)]
#[repr(packed)]
pub struct OracleAccountData {
/// Name of the oracle to store on-chain.
pub name: [u8; 32],
/// Metadata of the oracle to store on-chain.
pub metadata: [u8; 128],
/// The account delegated as the authority for making account changes or withdrawing funds from a staking wallet.
pub oracle_authority: Pubkey,
/// Unix timestamp when the oracle last heartbeated
pub last_heartbeat: i64,
/// Flag dictating if an oracle is active and has heartbeated before the queue's oracle timeout parameter.
pub num_in_use: u32,
// Must be unique per oracle account and authority should be a pda
/// Stake account and reward/slashing wallet.
pub token_account: Pubkey,
/// Public key of the oracle queue who has granted it permission to use its resources.
pub queue_pubkey: Pubkey,
/// Oracle track record.
pub metrics: OracleMetrics,
/// Reserved for future info.
pub _ebuf: [u8; 256],
}
impl OracleAccountData {}

View File

@ -0,0 +1,127 @@
use super::error::SwitchboardError;
#[allow(unaligned_references)]
use anchor_lang::prelude::*;
// use bytemuck::{Pod, Zeroable};
use solana_program::entrypoint::ProgramResult;
use solana_program::instruction::Instruction;
use solana_program::program::{invoke, invoke_signed};
// use std::cell::Ref;
#[derive(Copy, Clone, AnchorSerialize, AnchorDeserialize, Eq, PartialEq)]
pub enum SwitchboardPermission {
/// queue authority has permitted an Oracle Account to heartbeat on it's queue and receive update requests. Oracles always need permissions to join a queue.
PermitOracleHeartbeat = 1 << 0,
/// queue authority has permitted an Aggregator Account to request updates from it's oracles or join an existing crank. Note: Not required if a queue has unpermissionedFeedsEnabled.
PermitOracleQueueUsage = 1 << 1, // TODO: rename
/// queue authority has permitted a VRF Account to request randomness from it's oracles. Note: Not required if a queue has unpermissionedVrfEnabled.
PermitVrfRequests = 1 << 2,
}
#[account(zero_copy)]
#[repr(packed)]
pub struct PermissionAccountData {
/// The authority that is allowed to set permissions for this account.
pub authority: Pubkey,
/// The SwitchboardPermission enumeration assigned by the granter to the grantee.
pub permissions: u32,
/// Public key of account that is granting permissions to use its resources.
pub granter: Pubkey,
/// Public key of account that is being assigned permissions to use a granters resources.
pub grantee: Pubkey,
/// unused currently. may want permission PDA per permission for
/// unique expiration periods, BUT currently only one permission
/// per account makes sense for the infra. Dont over engineer.
pub expiration: i64,
/// Reserved for future info.
pub _ebuf: [u8; 256],
}
impl PermissionAccountData {}
#[derive(Accounts)]
#[instruction(params: PermissionSetParams)] // rpc parameters hint
pub struct PermissionSet<'info> {
#[account(mut, has_one = authority @ SwitchboardError::InvalidAuthority )]
pub permission: AccountLoader<'info, PermissionAccountData>,
#[account(signer)]
pub authority: AccountInfo<'info>,
}
#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
pub struct PermissionSetParams {
pub permission: SwitchboardPermission,
pub enable: bool,
}
impl<'info> PermissionSet<'info> {
fn discriminator() -> [u8; 8] {
[47, 83, 96, 86, 228, 90, 153, 179]
}
pub fn get_instruction(
&self,
program_id: Pubkey,
params: PermissionSetParams,
) -> anchor_lang::Result<Instruction> {
let accounts = self.to_account_metas(None);
let mut data: Vec<u8> = PermissionSet::discriminator().try_to_vec()?;
let mut param_vec: Vec<u8> = params.try_to_vec()?;
data.append(&mut param_vec);
let instruction = Instruction::new_with_bytes(program_id, &data, accounts);
Ok(instruction)
}
pub fn invoke(
&self,
program: AccountInfo<'info>,
permission: SwitchboardPermission,
enable: bool,
) -> ProgramResult {
let cpi_params = PermissionSetParams { permission, enable };
let instruction = self.get_instruction(program.key.clone(), cpi_params)?;
let account_infos = self.to_account_infos();
invoke(&instruction, &account_infos[..])
// .map_err(|_| error!(SwitchboardError::VrfCpiError))
}
pub fn invoke_signed(
&self,
program: AccountInfo<'info>,
permission: SwitchboardPermission,
enable: bool,
signer_seeds: &[&[&[u8]]],
) -> ProgramResult {
let cpi_params = PermissionSetParams { permission, enable };
let instruction = self.get_instruction(program.key.clone(), cpi_params)?;
let account_infos = self.to_account_infos();
invoke_signed(&instruction, &account_infos[..], signer_seeds)
// .map_err(|_| error!(SwitchboardError::VrfCpiSignedError))
}
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
return vec![
self.permission.to_account_info().clone(),
self.authority.clone(),
];
}
#[allow(unused_variables)]
fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
return vec![
AccountMeta {
pubkey: self.permission.key(),
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: self.authority.key.clone(),
is_signer: true,
is_writable: false,
},
];
}
}

60
libraries/rs/src/queue.rs Normal file
View File

@ -0,0 +1,60 @@
#[allow(unaligned_references)]
use super::decimal::SwitchboardDecimal;
use anchor_lang::prelude::*;
#[account(zero_copy)]
#[repr(packed)]
pub struct OracleQueueAccountData {
/// Name of the queue to store on-chain.
pub name: [u8; 32],
/// Metadata of the queue to store on-chain.
pub metadata: [u8; 64],
/// The account delegated as the authority for making account changes or assigning permissions targeted at the queue.
pub authority: Pubkey,
/// Interval when stale oracles will be removed if they fail to heartbeat.
pub oracle_timeout: u32,
/// Rewards to provide oracles and round openers on this queue.
pub reward: u64,
/// The minimum amount of stake oracles must present to remain on the queue.
pub min_stake: u64,
/// Whether slashing is enabled on this queue.
pub slashing_enabled: bool,
/// The tolerated variance amount oracle results can have from the accepted round result before being slashed.
/// slashBound = varianceToleranceMultiplier * stdDeviation Default: 2
pub variance_tolerance_multiplier: SwitchboardDecimal,
/// Number of update rounds new feeds are on probation for.
/// If a feed returns 429s within probation period, auto disable permissions.
pub feed_probation_period: u32,
//
/// Current index of the oracle rotation.
pub curr_idx: u32,
/// Current number of oracles on a queue.
pub size: u32,
/// Garbage collection index.
pub gc_idx: u32,
/// Consecutive failure limit for a feed before feed permission is revoked.
pub consecutive_feed_failure_limit: u64,
/// Consecutive failure limit for an oracle before oracle permission is revoked.
pub consecutive_oracle_failure_limit: u64,
/// Enabling this setting means data feeds do not need explicit permission to join the queue and request new values from its oracles.
pub unpermissioned_feeds_enabled: bool,
/// Enabling this setting means VRF accounts do not need explicit permission to join the queue and request new values from its oracles.
pub unpermissioned_vrf_enabled: bool,
/// TODO: Revenue percentage rewarded to job curators overall.
pub curator_reward_cut: SwitchboardDecimal,
/// Prevent new leases from being funded n this queue.
/// Useful to turn down a queue for migrations, since authority is always immutable.
pub lock_lease_funding: bool,
/// Token mint used for the oracle queue rewards and slashing.
pub mint: Pubkey,
/// Whether oracles are permitted to fulfill buffer relayer update request.
pub enable_buffer_relayers: bool,
/// Reserved for future info.
pub _ebuf: [u8; 968],
/// Maximum number of oracles a queue can support.
pub max_size: u32,
/// The public key of the OracleQueueBuffer account holding a collection of Oracle pubkeys that haver successfully heartbeated before the queues `oracleTimeout`.
pub data_buffer: Pubkey,
}
impl OracleQueueAccountData {}

View File

@ -0,0 +1,19 @@
#[allow(unaligned_references)]
use anchor_lang::prelude::*;
#[account(zero_copy)]
#[repr(packed)]
pub struct SbState {
/// The account authority permitted to make account changes.
pub authority: Pubkey,
/// The token mint used for oracle rewards, aggregator leases, and other reward incentives.
pub token_mint: Pubkey,
/// Token vault used by the program to receive kickbacks.
pub token_vault: Pubkey,
/// The token mint used by the DAO.
pub dao_mint: Pubkey,
/// Reserved for future info.
pub _ebuf: [u8; 992],
}
impl SbState {}

View File

@ -4,6 +4,7 @@ use super::error::SwitchboardError;
use anchor_lang::prelude::*;
use anchor_spl::token::TokenAccount;
use bytemuck::{Pod, Zeroable};
use solana_program::entrypoint::ProgramResult;
use solana_program::instruction::Instruction;
use solana_program::program::{invoke, invoke_signed};
use std::cell::Ref;
@ -227,8 +228,11 @@ unsafe impl Zeroable for EcvrfIntermediate {}
#[zero_copy]
#[repr(packed)]
pub struct VrfBuilder {
/// The OracleAccountData that is producing the randomness.
pub producer: Pubkey,
/// The current status of the VRF verification.
pub status: VrfStatus,
/// The VRF proof sourced from the producer.
pub repr_proof: [u8; 80],
pub proof: EcvrfProofZC,
pub Y_point: Pubkey,
@ -262,8 +266,11 @@ pub struct VrfBuilder {
pub c_prime_hashbuf: [u8; 16],
pub m1: FieldElementZC,
pub m2: FieldElementZC,
/// The number of transactions remaining to verify the VRF proof.
pub tx_remaining: u32,
/// Whether the VRF proof has been verified on-chain.
pub verified: bool,
/// The VRF proof verification result. Will be zeroized if still awaiting fulfillment.
pub result: [u8; 32],
}
impl Default for VrfBuilder {
@ -292,10 +299,15 @@ pub struct AccountMetaBorsh {
#[zero_copy]
#[repr(packed)]
pub struct CallbackZC {
/// The program ID of the callback program being invoked.
pub program_id: Pubkey,
/// The accounts being used in the callback instruction.
pub accounts: [AccountMetaZC; 32],
/// The number of accounts used in the callback
pub accounts_len: u32,
/// The serialized instruction data.
pub ix_data: [u8; 1024],
/// The number of serialized bytes in the instruction data.
pub ix_data_len: u32,
}
impl Default for CallbackZC {
@ -306,23 +318,31 @@ impl Default for CallbackZC {
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct Callback {
/// The program ID of the callback program being invoked.
pub program_id: Pubkey,
/// The accounts being used in the callback instruction.
pub accounts: Vec<AccountMetaBorsh>,
/// The serialized instruction data.
pub ix_data: Vec<u8>,
}
#[zero_copy]
#[repr(packed)]
pub struct VrfRound {
// pub producer_pubkeys: [Pubkey; 16],
// pub producer_pubkeys_len: u32,
/// The alpha bytes used to calculate the VRF proof.
pub alpha: [u8; 256],
/// The number of bytes in the alpha buffer.
pub alpha_len: u32,
/// The Slot when the VRF round was opened.
pub request_slot: u64,
/// The unix timestamp when the VRF round was opened.
pub request_timestamp: i64,
/// The VRF round result. Will be zeroized if still awaiting fulfillment.
pub result: [u8; 32],
/// The number of builders who verified the VRF proof.
pub num_verified: u32,
pub _ebuf: [u8; 256], // Buffer for future info
/// Reserved for future info.
pub _ebuf: [u8; 256],
}
impl Default for VrfRound {
fn default() -> Self {
@ -332,31 +352,46 @@ impl Default for VrfRound {
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum VrfStatus {
/// VRF Account has not requested randomness yet.
StatusNone,
/// VRF Account has requested randomness but has yet to receive an oracle response.
StatusRequesting,
/// VRF Account has received a VRF proof that has yet to be verified on-chain.
StatusVerifying,
/// VRF Account has successfully requested and verified randomness on-chain.
StatusVerified,
/// VRF Account's callback was invoked successfully.
StatusCallbackSuccess,
/// Failed to verify VRF proof.
StatusVerifyFailure,
}
// #[derive(Copy, Clone)]
#[zero_copy]
#[account(zero_copy)]
#[repr(packed)]
pub struct VrfAccountData {
/// The current status of the VRF account.
pub status: VrfStatus,
/// Incremental counter for tracking VRF rounds.
pub counter: u128,
/// On-chain account delegated for making account changes.
pub authority: Pubkey,
/// The OracleQueueAccountData that is assigned to fulfill VRF update request.
pub oracle_queue: Pubkey,
/// The token account used to hold funds for VRF update request.
pub escrow: Pubkey,
/// The callback that is invoked when an update request is successfully verified.
pub callback: CallbackZC,
/// The number of oracles assigned to a VRF update request.
pub batch_size: u32,
/// Struct containing the intermediate state between VRF crank actions.
pub builders: [VrfBuilder; 8],
/// The number of builders.
pub builders_len: u32,
pub test_mode: bool,
// pub last_verified_round: VrfRound,
/// Oracle results from the current round of update request that has not been accepted as valid yet
pub current_round: VrfRound,
pub _ebuf: [u8; 1024], // Buffer for future info
/// Reserved for future info.
pub _ebuf: [u8; 1024],
}
impl Default for VrfAccountData {
fn default() -> Self {
@ -382,15 +417,21 @@ impl VrfAccountData {
switchboard_vrf: &'info AccountInfo,
) -> anchor_lang::Result<Ref<'info, VrfAccountData>> {
let data = switchboard_vrf.try_borrow_data()?;
if data.len() < VrfAccountData::discriminator().len() {
return Err(ErrorCode::AccountDiscriminatorNotFound.into());
}
let mut disc_bytes = [0u8; 8];
disc_bytes.copy_from_slice(&data[..8]);
if disc_bytes != VrfAccountData::discriminator() {
return Err(error!(SwitchboardError::AccountDiscriminatorMismatch));
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
}
Ok(Ref::map(data, |data| bytemuck::from_bytes(&data[8..])))
Ok(Ref::map(data, |data| {
bytemuck::from_bytes(&data[8..std::mem::size_of::<VrfAccountData>() + 8])
}))
}
/// Returns the current VRF round ID
pub fn get_current_randomness_round_id(&self) -> u128 {
self.counter
@ -401,11 +442,8 @@ impl VrfAccountData {
/// # Examples
///
/// ```ignore
/// use switchboard_v2::AggregatorAccountData;
/// use std::convert::TryInto;
/// use switchboard_v2::VrfAccountData;
///
/// let feed_result = AggregatorAccountData::new(feed_account_info)?.get_result()?;
/// let decimal: f64 = feed_result.try_into()?;
/// ```
pub fn get_result(&self) -> anchor_lang::Result<[u8; 32]> {
if self.current_round.result == [0u8; 32] {
@ -414,13 +452,10 @@ impl VrfAccountData {
Ok(self.current_round.result)
}
// Need to log and update to actual value
fn discriminator() -> [u8; 8] {
return [101, 35, 62, 239, 103, 151, 6, 18];
[101, 35, 62, 239, 103, 151, 6, 18]
}
}
unsafe impl Pod for VrfAccountData {}
unsafe impl Zeroable for VrfAccountData {}
#[derive(Accounts)]
#[instruction(params: VrfRequestRandomnessParams)] // rpc parameters hint
@ -464,7 +499,7 @@ pub struct VrfRequestRandomnessParams {
impl<'info> VrfRequestRandomness<'info> {
fn discriminator() -> [u8; 8] {
return [230, 121, 14, 164, 28, 222, 117, 118];
[230, 121, 14, 164, 28, 222, 117, 118]
}
pub fn get_instruction(
@ -487,7 +522,7 @@ impl<'info> VrfRequestRandomness<'info> {
program: AccountInfo<'info>,
state_bump: u8,
permission_bump: u8,
) -> anchor_lang::Result<()> {
) -> ProgramResult {
let cpi_params = VrfRequestRandomnessParams {
permission_bump: permission_bump,
state_bump: state_bump,
@ -495,7 +530,8 @@ impl<'info> VrfRequestRandomness<'info> {
let instruction = self.get_instruction(program.key.clone(), cpi_params)?;
let account_infos = self.to_account_infos();
invoke(&instruction, &account_infos[..]).map_err(|_| error!(SwitchboardError::VrfCpiError))
invoke(&instruction, &account_infos[..])
// .map_err(|_| error!(SwitchboardError::VrfCpiError))
}
pub fn invoke_signed(
@ -504,7 +540,7 @@ impl<'info> VrfRequestRandomness<'info> {
state_bump: u8,
permission_bump: u8,
signer_seeds: &[&[&[u8]]],
) -> anchor_lang::Result<()> {
) -> ProgramResult {
let cpi_params = VrfRequestRandomnessParams {
permission_bump: permission_bump,
state_bump: state_bump,
@ -513,7 +549,7 @@ impl<'info> VrfRequestRandomness<'info> {
let account_infos = self.to_account_infos();
invoke_signed(&instruction, &account_infos[..], signer_seeds)
.map_err(|_| error!(SwitchboardError::VrfCpiSignedError))
// .map_err(|_| error!(SwitchboardError::VrfCpiSignedError))
}
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {

View File

@ -0,0 +1,50 @@
import * as anchor from "@project-serum/anchor";
import { PublicKey } from "@solana/web3.js";
import { VrfAccount } from "@switchboard-xyz/switchboard-v2";
import { promiseWithTimeout } from "./async.js";
export async function awaitOpenRound(
vrfAccount: VrfAccount,
counter: anchor.BN,
timeout = 30
): Promise<Buffer> {
// call open round and wait for new value
const accountsCoder = new anchor.BorshAccountsCoder(vrfAccount.program.idl);
let accountWs: number;
const awaitUpdatePromise = new Promise((resolve: (value: Buffer) => void) => {
accountWs = vrfAccount.program.provider.connection.onAccountChange(
vrfAccount?.publicKey ?? PublicKey.default,
async (accountInfo) => {
const vrf = accountsCoder.decode("VrfAccountData", accountInfo.data);
if (!counter.eq(vrf.counter)) {
return;
}
if (vrf.result.every((val) => val === 0)) {
return;
}
resolve(vrf.result as Buffer);
}
);
});
const updatedValuePromise = promiseWithTimeout(
timeout * 1000,
awaitUpdatePromise,
new Error(`vrf failed to update in ${timeout} seconds`)
).finally(() => {
if (accountWs) {
vrfAccount.program.provider.connection.removeAccountChangeListener(
accountWs
);
}
});
const result = await updatedValuePromise;
if (!result) {
throw new Error(`failed to update VRF`);
}
return result;
}