Basic Oracle Function
Switchboard Functions allow you to verifiably execute your own Rust code off-chain and submit transactions either on a pre-defined cron schedule or on-demand. Switchboard Functions gives smart contracts greater flexibility as they can delegate execution to an off-chain computational layer.
In this example we will build a custom oracle that pushes the latest price of BTC, ETH, and SOL to your program every 5 seconds.
Integration Checklist:
- Define Switchboard interface in your program
- Write a custom function in Rust
- Deploy function to Docker container
- Create Switchboard function account
Define Switchboard interface in your programโ
Your on-chain program needs to have an instruction which verifies the
function.enclaveSigner
has signed the transaction.
use switchboard_solana::{FunctionAccountData};
#[derive(Accounts)]
pub struct SaveDataInstruction<'info> {
// ... your required accounts to modify your program's state
// We use this to derive and verify the functions enclave state
#[account(
constraint =
function.load()?.validate(
&enclave_signer.to_account_info()
)?
)]
pub function: AccountLoader<'info, FunctionAccountData>,
pub enclave_signer: Signer<'info>,
}
The validate_enclave
method will verify:
- The provided enclave account corresponds to the provided function account
- The provided signer matches the function's enclave signer and has signed the transaction
- The provided enclave accounts mr_enclave value is present in the function accounts mr_enclaves array.
Write a custom function in Rustโ
Now we need to write our custom function which will invoke our program and save the trading prices to our program state.
Clone the solana-functions-template repository:
gh repo create myrepo --template switchboard-xyz/solana-functions-template
# or just git clone
git clone https://github.com/switchboard-xyz/solana-functions-template.git
You should see a file in src/main.rs
similar to:
pub use switchboard_solana::prelude::*;
#[tokio::main(worker_threads = 12)]
async fn main() {
// First, initialize the runner instance with a freshly generated Gramine keypair
let runner = FunctionRunner::new_from_cluster(Cluster::Devnet, None).unwrap();
// Then, write your own Rust logic and build a Vec of instructions.
// Should be under 700 bytes after serialization
let ixs: Vec<solana_program::instruction::Instruction> = vec![];
// Finally, emit the signed quote and partially signed transaction to the functionRunner oracle
// The functionRunner oracle will use the last outputted word to stdout as the serialized result. This is what gets executed on-chain.
runner.emit(ixs).await.unwrap();
}
To write your custom Switchboard function just append instructions to the ixs
array. The FunctionRunner will automatically prepend the function_verify
instruction with the quote generated from the enclave. The quote verifier oracle
running the function will verify this instruction before sending your
transaction.
See examples/functions/01_basic_oracle/sgx-function/src/main.rs in Switchboard's Solana SDK for the full example code.
Deploy function to Docker containerโ
The template repository includes a Makefile to streamline publishing your container to the docker repository along with outputting your MrEnclave measurement. This measurement corresponds to the code fingerprint of the outputted Rust binary. You should store this value in the function account we create in the following step. This will ensure that the only code that is allowed to add data to your smart contract must be generated from a binary with this signature. You can add multiple MrEnclave values to your function account to allow backwards compatibility and make upgrades easier.
Edit the Makefile and add your docker registry.
# Variables
## Cargo.toml name of the compiled binary
CARGO_NAME=switchboard-function
## Docker registry image name (Ex: switchboardlabs/basic-oracle-function)
DOCKER_IMAGE_NAME=switchboard-function
Use one of the following commands to compile your function:
Command | Description |
---|---|
make | Build the container locally and output the MrEnclave measurement |
make publish | Publish the container to the provided docker repository under the latest tag |
Create Switchboard function accountโ
Finally we just need to create our function account on-chain. We will define a
cron schedule of */5 * * * * *
which will invoke our function every 5 seconds.
We will need to pre-fund our function escrow account in order to reward verifier
oracles for running our function.
Visit the Switchboard app: app.switchboard.xyz
In the top right, sign in to your Solana wallet for the selected cluster.
Then click build and start configuring your function
import {
AttestationQueueAccount,
FunctionAccount,
parseMrEnclave,
SwitchboardProgram,
} from "@switchboard-xyz/solana.js";
// Load the Switchboard program and an existing Attestation Queue
const program = await SwitchboardProgram.load(
"devnet",
new Connection("https://api.devnet.solana.com"),
payerKeypair
);
const [attestationQueueAccount, queueAccountData] =
await AttestationQueueAccount.load(program, "My Attestation Queue Pubkey");
// Create the FunctionAccount
const [functionAccount, txnSignature] = await FunctionAccount.create(
ctx.program,
{
name: "FUNCTION_NAME",
metadata: "FUNCTION_METADATA",
schedule: "*/5 * * * * *",
container: "switchboardlabs/basic-oracle-function",
mrEnclave: parseMrEnclave("my MrEnclave value"),
attestationQueue: attestationQueueAccount,
}
);
// Wrap 0.25 SOL into the functionAccount wallet
await functionAccount.wrap(0.25);
sb solana function create CkvizjVnm2zA5Wuwan34NhVT3zFc7vqUyGnA6tuEF5aE \
--name "My Function" \
--metadata "Basic binance oracle with TWAP" \
--fundAmount 0.25 \
--schedule "*/30 * * * * *" \
--container switchboardlabs/basic-oracle-function \
--keypair ~/.config/solana/id.json
# or fund an existing function
sb solana function fund $MY_FUNCTION_PUBKEY \
--fundAmount 0.25 \
--keypair ~/.config/solana/id.json