solana.js: updated tests

This commit is contained in:
Conner Gallagher 2022-12-01 14:35:09 -07:00
parent 9ad1c80de5
commit ba24f9b38d
10 changed files with 1642 additions and 6063 deletions

File diff suppressed because it is too large Load Diff

View File

@ -19,8 +19,8 @@
"license": "ISC",
"dependencies": {
"@project-serum/anchor": "^0.25.0",
"@solana/web3.js": "^1.33.0",
"@switchboard-xyz/switchboard-v2": "^0.0.165",
"@solana/web3.js": "^1.67.2",
"@switchboard-xyz/solana.js": "file:../solana.js",
"big.js": "^6.1.1"
},
"devDependencies": {

View File

@ -2,8 +2,8 @@
import { clusterApiUrl, Connection, Keypair, PublicKey } from "@solana/web3.js";
import {
AggregatorAccount,
loadSwitchboardProgram,
} from "@switchboard-xyz/switchboard-v2";
SwitchboardProgram,
} from "@switchboard-xyz/solana.js";
// SOL/USD Feed https://switchboard.xyz/explorer/2/GvDMxPzN1sCj7L26YDK2HnMRXEQmQ2aemov8YBtPS7vR
// Create your own feed here https://publish.switchboard.xyz/
@ -13,20 +13,17 @@ const switchboardFeed = new PublicKey(
async function main() {
// load the switchboard program
const program = await loadSwitchboardProgram(
const program = await SwitchboardProgram.load(
"devnet",
new Connection(clusterApiUrl("devnet")),
Keypair.fromSeed(new Uint8Array(32).fill(1)) // using dummy keypair since we wont be submitting any transactions
);
// load the switchboard aggregator
const aggregator = new AggregatorAccount({
program,
publicKey: switchboardFeed,
});
const aggregator = new AggregatorAccount(program, switchboardFeed);
// get the result
const result = await aggregator.getLatestValue();
const result = await aggregator.fetchLatestValue();
console.log(`Switchboard Result: ${result}`);
}

View File

@ -22,11 +22,11 @@
"outDir": "dist",
"rootDir": "src",
"paths": {
"@switchboard-xyz/switchboard-v2": ["../switchboard-v2"]
"@switchboard-xyz/solana.js": ["../solana.js"]
}
},
"include": ["src/**/*"],
"exclude": ["esbuild.js", "dist"],
"references": [{ "path": "../switchboard-v2" }],
"references": [{ "path": "../solana.js" }],
"files": ["src/main.ts"]
}

File diff suppressed because it is too large Load Diff

View File

@ -19,9 +19,9 @@
"dependencies": {
"@project-serum/anchor": "^0.25.0",
"@solana/spl-token-v2": "npm:@solana/spl-token@^0.2.0",
"@solana/web3.js": "^1.50.1",
"@solana/web3.js": "^1.67.2",
"@switchboard-xyz/common": "^2.1.7",
"@switchboard-xyz/switchboard-v2": "^0.0.165",
"@switchboard-xyz/solana.js": "file:../solana.js",
"chalk": "^4.1.2",
"dotenv": "^16.0.1",
"readline-sync": "^1.4.10"

View File

@ -1,21 +1,14 @@
import * as anchor from "@project-serum/anchor";
import * as spl from "@solana/spl-token-v2";
import type { PublicKey } from "@solana/web3.js";
import { clusterApiUrl, Connection, Keypair } from "@solana/web3.js";
import { IOracleJob, OracleJob } from "@switchboard-xyz/common";
import { OracleJob } from "@switchboard-xyz/common";
import {
AggregatorAccount,
CrankAccount,
JobAccount,
LeaseAccount,
loadSwitchboardProgram,
SwitchboardProgram,
OracleAccount,
OracleQueueAccount,
QueueAccount,
PermissionAccount,
ProgramStateAccount,
programWallet,
SwitchboardPermission,
} from "@switchboard-xyz/switchboard-v2";
types,
} from "@switchboard-xyz/solana.js";
import chalk from "chalk";
import dotenv from "dotenv";
import fs from "fs";
@ -89,111 +82,60 @@ async function main() {
cluster === "localnet" ? "http://localhost:8899" : clusterApiUrl(cluster);
}
const program = await loadSwitchboardProgram(
const program = await SwitchboardProgram.load(
cluster === "localnet" ? "devnet" : cluster,
new Connection(rpcUrl),
authority,
{
commitment: "finalized",
}
authority
);
console.log(chalk.yellow("######## Switchboard Setup ########"));
// Program State Account and token mint for payout rewards
const [programStateAccount] = ProgramStateAccount.fromSeed(program);
console.log(toAccountString("Program State", programStateAccount.publicKey));
const mint = await programStateAccount.getTokenMint();
const tokenAccount = await spl.createAccount(
program.provider.connection,
programWallet(program),
mint.address,
authority.publicKey,
Keypair.generate()
// create our token wallet for the wrapped SOL mint
const tokenAccount = await program.mint.getOrCreateAssociatedUser(
program.walletPubkey
);
// Oracle Queue
const queueAccount = await OracleQueueAccount.create(program, {
name: Buffer.from("Queue-1"),
mint: spl.NATIVE_MINT,
const [queueAccount] = await QueueAccount.create(program, {
name: "Queue-1",
slashingEnabled: false,
reward: new anchor.BN(0), // no token account needed
minStake: new anchor.BN(0),
authority: authority.publicKey,
reward: 0,
minStake: 0,
});
console.log(toAccountString("Oracle Queue", queueAccount.publicKey));
// Crank
const crankAccount = await CrankAccount.create(program, {
name: Buffer.from("Crank"),
const [crankAccount] = await queueAccount.createCrank({
name: "Crank",
maxRows: 10,
queueAccount,
});
console.log(toAccountString("Crank", crankAccount.publicKey));
// Oracle
const oracleAccount = await OracleAccount.create(program, {
name: Buffer.from("Oracle"),
queueAccount,
});
console.log(toAccountString("Oracle", oracleAccount.publicKey));
// Oracle permissions
const oraclePermission = await PermissionAccount.create(program, {
authority: authority.publicKey,
granter: queueAccount.publicKey,
grantee: oracleAccount.publicKey,
});
await oraclePermission.set({
authority,
permission: SwitchboardPermission.PERMIT_ORACLE_HEARTBEAT,
const [oracleAccount] = await queueAccount.createOracle({
name: "Oracle",
enable: true,
});
console.log(toAccountString(` Permission`, oraclePermission.publicKey));
await oracleAccount.heartbeat(authority);
console.log(toAccountString("Oracle", oracleAccount.publicKey));
await oracleAccount.heartbeat();
// Aggregator
const aggregatorAccount = await AggregatorAccount.create(program, {
name: Buffer.from("SOL_USD"),
const [aggregatorAccount] = await queueAccount.createFeed({
name: "SOL_USD",
queueAuthority: authority,
batchSize: 1,
minRequiredOracleResults: 1,
minRequiredJobResults: 1,
minUpdateDelaySeconds: 10,
queueAccount,
authority: authority.publicKey,
});
console.log(
toAccountString(`Aggregator (SOL/USD)`, aggregatorAccount.publicKey)
);
if (!aggregatorAccount.publicKey) {
throw new Error(`failed to read Aggregator publicKey`);
}
// Aggregator permissions
const aggregatorPermission = await PermissionAccount.create(program, {
authority: authority.publicKey,
granter: queueAccount.publicKey,
grantee: aggregatorAccount.publicKey,
});
await aggregatorPermission.set({
authority,
permission: SwitchboardPermission.PERMIT_ORACLE_QUEUE_USAGE,
fundAmount: 0.5,
enable: true,
});
console.log(toAccountString(` Permission`, aggregatorPermission.publicKey));
// Lease
const leaseContract = await LeaseAccount.create(program, {
loadAmount: new anchor.BN(0),
funder: tokenAccount,
funderAuthority: authority,
oracleQueueAccount: queueAccount,
aggregatorAccount,
});
console.log(toAccountString(` Lease`, leaseContract.publicKey));
// Job
const jobDefinition: IOracleJob = {
crankPubkey: crankAccount.publicKey,
jobs: [
{
weight: 2,
data: OracleJob.encodeDelimited(
OracleJob.fromObject({
tasks: [
{
httpTask: {
@ -204,22 +146,13 @@ async function main() {
jsonParseTask: { path: "$.result.price" },
},
],
};
// using OracleJob.fromObject will convert string enums to the correct format
// using OracleJob.create will not
const oracleJob = OracleJob.fromObject(jobDefinition);
const jobKeypair = anchor.web3.Keypair.generate();
const jobAccount = await JobAccount.create(program, {
data: Buffer.from(OracleJob.encodeDelimited(oracleJob).finish()),
keypair: jobKeypair,
authority: authority.publicKey,
})
).finish(),
},
],
});
console.log(toAccountString(` Job (FTX)`, jobAccount.publicKey));
await aggregatorAccount.addJob(jobAccount, authority); // Add Job to Aggregator
await crankAccount.push({ aggregatorAccount }); // Add Aggregator to Crank
console.log(toAccountString("Aggregator", aggregatorAccount.publicKey));
const aggregator = await aggregatorAccount.loadData();
console.log(chalk.green("\u2714 Switchboard setup complete"));
@ -241,23 +174,17 @@ async function main() {
}
console.log("");
const confirmedRoundPromise = aggregatorAccount.nextRound();
// Turn the Crank
async function turnCrank(retryCount: number): Promise<number> {
try {
const readyPubkeys = await crankAccount.peakNextReady(5);
if (readyPubkeys) {
const crank = await crankAccount.loadData();
const queue = await queueAccount.loadData();
const crankTurnSignature = await crankAccount.pop({
await crankAccount.pop({
payoutWallet: tokenAccount,
queuePubkey: queueAccount.publicKey,
queueAuthority: queue.authority,
readyPubkeys,
nonce: 0,
crank,
queue,
tokenMint: mint.address,
});
console.log(chalk.green("\u2714 Crank turned"));
return 0;
@ -278,28 +205,11 @@ async function main() {
// Read Aggregators latest result
console.log(chalk.yellow("######## Aggregator Result ########"));
await sleep(5000);
try {
let result = await aggregatorAccount.getLatestValue();
if (result === null) {
// wait a bit longer
await sleep(2500);
result = await aggregatorAccount.getLatestValue();
if (result === null) {
throw new Error(`Aggregator currently holds no value.`);
}
}
console.log(`${chalk.blue("Result:")} ${chalk.green(result)}\r\n`);
console.log(chalk.green("\u2714 Aggregator succesfully updated!"));
} catch (error: any) {
if (error.message === "Aggregator currently holds no value.") {
const confirmedRound = await confirmedRoundPromise;
console.log(
chalk.red("\u2716 Aggregator holds no value, was the oracle running?")
`${chalk.blue("Result:")} ${chalk.green(confirmedRound.result.toBig())}\r\n`
);
return;
}
console.error(error);
}
console.log(chalk.green("\u2714 Aggregator succesfully updated!"));
}
main().then(

View File

@ -1134,6 +1134,23 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
return result;
}
/**
* Load an aggregators {@linkcode AggregatorHistoryBuffer}.
* @return the list of historical samples attached to the aggregator.
*/
async loadHistory(): Promise<Array<types.AggregatorHistoryRow>> {
if (!this.history) {
this.history = new AggregatorHistoryBuffer(
this.program,
(await this.loadData()).historyBuffer
);
}
const history = await this.history.loadData();
return history;
}
}
/**

View File

@ -392,12 +392,8 @@ export class NativeMint extends Mint {
const signers: Keypair[] = user ? [user] : [];
const userAddress = this.getAssociatedAddress(owner);
const userBalance = (await this.getBalance(owner)) ?? 0;
if (amount) {
if (amount >= userBalance) {
ixns.push(spl.createCloseAccountInstruction(userAddress, owner, owner));
} else {
if (amount !== undefined && amount > 0) {
const ephemeralAccount = Keypair.generate();
const ephemeralWallet = this.getAssociatedAddress(
ephemeralAccount.publicKey
@ -426,7 +422,6 @@ export class NativeMint extends Mint {
ephemeralAccount.publicKey
)
);
}
} else {
ixns.push(spl.createCloseAccountInstruction(userAddress, owner, owner));
}

View File

@ -0,0 +1,54 @@
import 'mocha';
import assert from 'assert';
import * as sbv2 from '../src';
import { setupTest, TestContext } from './utilts';
import { Keypair } from '@solana/web3.js';
import { CrankAccount, QueueAccount, types } from '../src';
describe('Crank Tests', () => {
let ctx: TestContext;
const queueAuthority = Keypair.generate();
let queueAccount: QueueAccount;
let queue: types.OracleQueueAccountData;
let crankAccount: CrankAccount;
before(async () => {
ctx = await setupTest();
[queueAccount] = await sbv2.QueueAccount.create(ctx.program, {
name: 'q1',
metadata: '',
queueSize: 2,
reward: 0.0025,
minStake: 0,
oracleTimeout: 60,
slashingEnabled: false,
unpermissionedFeeds: false,
unpermissionedVrf: true,
enableBufferRelayers: false,
authority: queueAuthority.publicKey,
});
queue = await queueAccount.loadData();
assert(
queue.authority.equals(queueAuthority.publicKey),
'Incorrect queue authority'
);
});
it('Creates a Crank', async () => {
[crankAccount] = await queueAccount.createCrank({
name: 'Crank #1',
maxRows: 10,
});
const crank = await crankAccount.loadData();
const crankRows = await crankAccount.loadCrank();
assert(
crankRows.length === 0,
`Crank should be empty but found ${crankRows.length} rows`
);
});
});