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", "license": "ISC",
"dependencies": { "dependencies": {
"@project-serum/anchor": "^0.25.0", "@project-serum/anchor": "^0.25.0",
"@solana/web3.js": "^1.33.0", "@solana/web3.js": "^1.67.2",
"@switchboard-xyz/switchboard-v2": "^0.0.165", "@switchboard-xyz/solana.js": "file:../solana.js",
"big.js": "^6.1.1" "big.js": "^6.1.1"
}, },
"devDependencies": { "devDependencies": {

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -19,9 +19,9 @@
"dependencies": { "dependencies": {
"@project-serum/anchor": "^0.25.0", "@project-serum/anchor": "^0.25.0",
"@solana/spl-token-v2": "npm:@solana/spl-token@^0.2.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/common": "^2.1.7",
"@switchboard-xyz/switchboard-v2": "^0.0.165", "@switchboard-xyz/solana.js": "file:../solana.js",
"chalk": "^4.1.2", "chalk": "^4.1.2",
"dotenv": "^16.0.1", "dotenv": "^16.0.1",
"readline-sync": "^1.4.10" "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 type { PublicKey } from "@solana/web3.js";
import { clusterApiUrl, Connection, Keypair } 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 { import {
AggregatorAccount,
CrankAccount, CrankAccount,
JobAccount, SwitchboardProgram,
LeaseAccount,
loadSwitchboardProgram,
OracleAccount, OracleAccount,
OracleQueueAccount, QueueAccount,
PermissionAccount, PermissionAccount,
ProgramStateAccount, types,
programWallet, } from "@switchboard-xyz/solana.js";
SwitchboardPermission,
} from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk"; import chalk from "chalk";
import dotenv from "dotenv"; import dotenv from "dotenv";
import fs from "fs"; import fs from "fs";
@ -89,137 +82,77 @@ async function main() {
cluster === "localnet" ? "http://localhost:8899" : clusterApiUrl(cluster); cluster === "localnet" ? "http://localhost:8899" : clusterApiUrl(cluster);
} }
const program = await loadSwitchboardProgram( const program = await SwitchboardProgram.load(
cluster === "localnet" ? "devnet" : cluster, cluster === "localnet" ? "devnet" : cluster,
new Connection(rpcUrl), new Connection(rpcUrl),
authority, authority
{
commitment: "finalized",
}
); );
console.log(chalk.yellow("######## Switchboard Setup ########")); console.log(chalk.yellow("######## Switchboard Setup ########"));
// Program State Account and token mint for payout rewards // create our token wallet for the wrapped SOL mint
const [programStateAccount] = ProgramStateAccount.fromSeed(program); const tokenAccount = await program.mint.getOrCreateAssociatedUser(
console.log(toAccountString("Program State", programStateAccount.publicKey)); program.walletPubkey
const mint = await programStateAccount.getTokenMint();
const tokenAccount = await spl.createAccount(
program.provider.connection,
programWallet(program),
mint.address,
authority.publicKey,
Keypair.generate()
); );
// Oracle Queue // Oracle Queue
const queueAccount = await OracleQueueAccount.create(program, { const [queueAccount] = await QueueAccount.create(program, {
name: Buffer.from("Queue-1"), name: "Queue-1",
mint: spl.NATIVE_MINT,
slashingEnabled: false, slashingEnabled: false,
reward: new anchor.BN(0), // no token account needed reward: 0,
minStake: new anchor.BN(0), minStake: 0,
authority: authority.publicKey,
}); });
console.log(toAccountString("Oracle Queue", queueAccount.publicKey)); console.log(toAccountString("Oracle Queue", queueAccount.publicKey));
// Crank // Crank
const crankAccount = await CrankAccount.create(program, { const [crankAccount] = await queueAccount.createCrank({
name: Buffer.from("Crank"), name: "Crank",
maxRows: 10, maxRows: 10,
queueAccount,
}); });
console.log(toAccountString("Crank", crankAccount.publicKey)); console.log(toAccountString("Crank", crankAccount.publicKey));
// Oracle // Oracle
const oracleAccount = await OracleAccount.create(program, { const [oracleAccount] = await queueAccount.createOracle({
name: Buffer.from("Oracle"), name: "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,
enable: true, enable: true,
}); });
console.log(toAccountString(` Permission`, oraclePermission.publicKey)); console.log(toAccountString("Oracle", oracleAccount.publicKey));
await oracleAccount.heartbeat(authority); await oracleAccount.heartbeat();
// Aggregator // Aggregator
const aggregatorAccount = await AggregatorAccount.create(program, {
name: Buffer.from("SOL_USD"), const [aggregatorAccount] = await queueAccount.createFeed({
name: "SOL_USD",
queueAuthority: authority,
batchSize: 1, batchSize: 1,
minRequiredOracleResults: 1, minRequiredOracleResults: 1,
minRequiredJobResults: 1, minRequiredJobResults: 1,
minUpdateDelaySeconds: 10, minUpdateDelaySeconds: 10,
queueAccount, fundAmount: 0.5,
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,
enable: true, enable: true,
}); crankPubkey: crankAccount.publicKey,
console.log(toAccountString(` Permission`, aggregatorPermission.publicKey)); jobs: [
// 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 = {
tasks: [
{ {
httpTask: { weight: 2,
url: `https://ftx.us/api/markets/SOL_USD`, data: OracleJob.encodeDelimited(
}, OracleJob.fromObject({
}, tasks: [
{ {
jsonParseTask: { path: "$.result.price" }, httpTask: {
url: `https://ftx.us/api/markets/SOL_USD`,
},
},
{
jsonParseTask: { path: "$.result.price" },
},
],
})
).finish(),
}, },
], ],
};
// 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,
}); });
console.log(toAccountString(` Job (FTX)`, jobAccount.publicKey)); console.log(toAccountString("Aggregator", aggregatorAccount.publicKey));
const aggregator = await aggregatorAccount.loadData();
await aggregatorAccount.addJob(jobAccount, authority); // Add Job to Aggregator
await crankAccount.push({ aggregatorAccount }); // Add Aggregator to Crank
console.log(chalk.green("\u2714 Switchboard setup complete")); console.log(chalk.green("\u2714 Switchboard setup complete"));
@ -241,23 +174,17 @@ async function main() {
} }
console.log(""); console.log("");
const confirmedRoundPromise = aggregatorAccount.nextRound();
// Turn the Crank // Turn the Crank
async function turnCrank(retryCount: number): Promise<number> { async function turnCrank(retryCount: number): Promise<number> {
try { try {
const readyPubkeys = await crankAccount.peakNextReady(5); const readyPubkeys = await crankAccount.peakNextReady(5);
if (readyPubkeys) { if (readyPubkeys) {
const crank = await crankAccount.loadData(); await crankAccount.pop({
const queue = await queueAccount.loadData();
const crankTurnSignature = await crankAccount.pop({
payoutWallet: tokenAccount, payoutWallet: tokenAccount,
queuePubkey: queueAccount.publicKey,
queueAuthority: queue.authority,
readyPubkeys, readyPubkeys,
nonce: 0, nonce: 0,
crank,
queue,
tokenMint: mint.address,
}); });
console.log(chalk.green("\u2714 Crank turned")); console.log(chalk.green("\u2714 Crank turned"));
return 0; return 0;
@ -278,28 +205,11 @@ async function main() {
// Read Aggregators latest result // Read Aggregators latest result
console.log(chalk.yellow("######## Aggregator Result ########")); console.log(chalk.yellow("######## Aggregator Result ########"));
await sleep(5000); const confirmedRound = await confirmedRoundPromise;
try { console.log(
let result = await aggregatorAccount.getLatestValue(); `${chalk.blue("Result:")} ${chalk.green(confirmedRound.result.toBig())}\r\n`
if (result === null) { );
// wait a bit longer console.log(chalk.green("\u2714 Aggregator succesfully updated!"));
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.") {
console.log(
chalk.red("\u2716 Aggregator holds no value, was the oracle running?")
);
return;
}
console.error(error);
}
} }
main().then( main().then(

View File

@ -1134,6 +1134,23 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
return result; 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,41 +392,36 @@ export class NativeMint extends Mint {
const signers: Keypair[] = user ? [user] : []; const signers: Keypair[] = user ? [user] : [];
const userAddress = this.getAssociatedAddress(owner); const userAddress = this.getAssociatedAddress(owner);
const userBalance = (await this.getBalance(owner)) ?? 0;
if (amount) { if (amount !== undefined && amount > 0) {
if (amount >= userBalance) { const ephemeralAccount = Keypair.generate();
ixns.push(spl.createCloseAccountInstruction(userAddress, owner, owner)); const ephemeralWallet = this.getAssociatedAddress(
} else { ephemeralAccount.publicKey
const ephemeralAccount = Keypair.generate(); );
const ephemeralWallet = this.getAssociatedAddress(
const unwrapAmountLamports = this.toTokenAmount(amount);
signers.push(ephemeralAccount);
ixns.push(
spl.createAssociatedTokenAccountInstruction(
payer,
ephemeralWallet,
ephemeralAccount.publicKey,
Mint.native
),
spl.createTransferInstruction(
userAddress,
ephemeralWallet,
owner,
unwrapAmountLamports
),
spl.createCloseAccountInstruction(
ephemeralWallet,
owner,
ephemeralAccount.publicKey ephemeralAccount.publicKey
); )
);
const unwrapAmountLamports = this.toTokenAmount(amount);
signers.push(ephemeralAccount);
ixns.push(
spl.createAssociatedTokenAccountInstruction(
payer,
ephemeralWallet,
ephemeralAccount.publicKey,
Mint.native
),
spl.createTransferInstruction(
userAddress,
ephemeralWallet,
owner,
unwrapAmountLamports
),
spl.createCloseAccountInstruction(
ephemeralWallet,
owner,
ephemeralAccount.publicKey
)
);
}
} else { } else {
ixns.push(spl.createCloseAccountInstruction(userAddress, owner, owner)); 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`
);
});
});