sbv2-solana/javascript/solana.js/test/open-round.spec.ts

263 lines
7.5 KiB
TypeScript

import "mocha";
import { PermitOracleQueueUsage } from "../src/generated/oracle-program/types/SwitchboardPermission.js";
import * as sbv2 from "../src/index.js";
import {
AggregatorAccount,
OracleAccount,
PermissionAccount,
QueueAccount,
types,
} from "../src/index.js";
import { setupTest, TestContext } from "./utils.js";
import { Keypair } from "@solana/web3.js";
import { Big, OracleJob, sleep } from "@switchboard-xyz/common";
import assert from "assert";
describe("Open Round Tests", () => {
let ctx: TestContext;
const queueAuthority = Keypair.generate();
let queueAccount: QueueAccount;
let queue: types.OracleQueueAccountData;
let oracleAccount1: OracleAccount;
let oracle1: types.OracleAccountData;
let oracleAccount2: OracleAccount;
let oracle2: types.OracleAccountData;
let aggregatorAccount: AggregatorAccount;
let aggregator: types.AggregatorAccountData;
let aggregatorPermissionAccount: PermissionAccount;
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"
);
[oracleAccount1] = await queueAccount.createOracle({
name: "oracle-1",
metadata: "oracle-1",
queueAuthority,
enable: true,
});
oracle1 = await oracleAccount1.loadData();
assert(
oracle1.oracleAuthority.equals(ctx.payer.publicKey),
"Incorrect oracle authority"
);
[oracleAccount2] = await queueAccount.createOracle({
name: "oracle-2",
metadata: "oracle-2",
queueAuthority,
enable: true,
});
oracle2 = await oracleAccount2.loadData();
assert(
oracle2.oracleAuthority.equals(ctx.payer.publicKey),
"Incorrect oracle authority"
);
[aggregatorAccount] = await queueAccount.createFeed({
batchSize: 2,
minRequiredOracleResults: 2,
minRequiredJobResults: 1,
minUpdateDelaySeconds: 5,
fundAmount: 1,
enable: false,
jobs: [
{
weight: 2,
data: OracleJob.encodeDelimited(
OracleJob.fromObject({
tasks: [
{
valueTask: {
value: 1337,
},
},
],
})
).finish(),
},
],
});
await aggregatorAccount.loadData();
[aggregatorPermissionAccount] = PermissionAccount.fromSeed(
ctx.program,
queueAuthority.publicKey,
queueAccount.publicKey,
aggregatorAccount.publicKey
);
});
it("fails to call open round when aggregator lacks permissions", async () => {
await assert.rejects(async () => {
await aggregatorAccount.openRound();
}, new RegExp(/PermissionDenied|6035|0x1793/g));
});
it("sets aggregator permissions", async () => {
await aggregatorPermissionAccount.set({
permission: new PermitOracleQueueUsage(),
enable: true,
queueAuthority,
});
const permissions = await aggregatorPermissionAccount.loadData();
assert(
permissions.permissions === PermitOracleQueueUsage.discriminator,
`Aggregator has incorrect permissions, expected ${
PermitOracleQueueUsage.kind
}, received ${PermissionAccount.getPermissions(permissions).kind}`
);
});
it("fails to call open round when not enough oracles are heartbeating", async () => {
await assert.rejects(async () => {
await aggregatorAccount.openRound();
}, new RegExp(/InsufficientOracleQueueError|6052|0x17a4/g));
// still fails when queueSize < batchSize
await oracleAccount1.heartbeat();
await assert.rejects(async () => {
await aggregatorAccount.openRound();
}, new RegExp(/InsufficientOracleQueueError|6052|0x17a4/g));
});
it("successfully calls open round", async () => {
await oracleAccount2.heartbeat();
// start heartbeating
await aggregatorAccount.openRound();
});
it("oracles successfully respond and close the round", async () => {
const aggregator = await aggregatorAccount.loadData();
const jobs = (await aggregatorAccount.loadJobs(aggregator)).map(
(jobs) => jobs.job
);
const result = new Big(1337);
const confirmedRoundPromise = aggregatorAccount.nextRound(
aggregator.currentRound.roundOpenSlot,
10000
);
await Promise.all(
[oracleAccount1, oracleAccount2].map(async (oracleAccount) => {
return await aggregatorAccount.saveResult({
jobs,
oracleAccount,
value: result,
minResponse: result,
maxResponse: result,
});
})
);
const currentRound = (await confirmedRoundPromise).latestConfirmedRound;
assert(
currentRound.roundOpenSlot.eq(aggregator.currentRound.roundOpenSlot),
`Current round open slot does not match expected round open slot, expected ${aggregator.currentRound.roundOpenSlot}, received ${currentRound.roundOpenSlot}`
);
assert(
currentRound.result.toBig().eq(result),
`Incorrect current round result, expected ${result}, received ${currentRound.result.toBig()}`
);
});
it("aggregator calls openRoundAndAwaitResult", async () => {
const result = new Big(1337);
const [newAggregatorAccount] = await queueAccount.createFeed({
batchSize: 2,
minRequiredOracleResults: 2,
minRequiredJobResults: 1,
minUpdateDelaySeconds: 5,
fundAmount: 1,
enable: true,
queueAuthority: queueAuthority,
jobs: [
{
weight: 2,
data: OracleJob.encodeDelimited(
OracleJob.fromObject({
tasks: [
{
valueTask: {
value: result.toNumber(),
},
},
],
})
).finish(),
},
],
});
const currentAggregatorState = await newAggregatorAccount.loadData();
const currentRequestOpenSlot =
currentAggregatorState.latestConfirmedRound.roundOpenSlot;
const jobs = (
await newAggregatorAccount.loadJobs(currentAggregatorState)
).map((jobs) => jobs.job);
const updatedAggregatorStatePromise =
newAggregatorAccount.openRoundAndAwaitResult(undefined, 10000);
await sleep(1000); // need time for open round to hit
await Promise.all(
[oracleAccount1, oracleAccount2].map(async (oracleAccount) => {
return await newAggregatorAccount.saveResult({
jobs,
oracleAccount,
value: result,
minResponse: result,
maxResponse: result,
});
})
);
const [updatedAggregatorState] = await updatedAggregatorStatePromise;
assert(
updatedAggregatorState.latestConfirmedRound.result.toBig().eq(result),
`Incorrect current round result, expected ${result}, received ${updatedAggregatorState.latestConfirmedRound.result.toBig()}`
);
assert(
updatedAggregatorState.latestConfirmedRound.roundOpenSlot.gt(
currentRequestOpenSlot
),
`Incorrect current round, expected currentRoundOpenSlot > previousRoundOpenSlot, received ${updatedAggregatorState.latestConfirmedRound.roundOpenSlot} > ${currentRequestOpenSlot}`
);
});
});