263 lines
7.5 KiB
TypeScript
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}`
|
|
);
|
|
});
|
|
});
|