sbv2-solana/javascript/solana.js/test/vrf-pool.spec.ts

460 lines
14 KiB
TypeScript

import "mocha";
import {
OracleAccount,
PermissionAccount,
QueueAccount,
TransactionObject,
types,
VrfLiteAccount,
VrfPoolAccount,
} from "../src/index.js";
import { setupTest, TestContext } from "./utils.js";
import { Keypair, PublicKey } from "@solana/web3.js";
import { sleep } from "@switchboard-xyz/common";
import { NodeOracle } from "@switchboard-xyz/oracle";
import assert from "assert";
describe("Vrf Pool Tests", () => {
let ctx: TestContext;
before(async () => {});
let userTokenAddress: PublicKey;
let queueAccount: QueueAccount;
// const queueAuthorityKeypair = Keypair.generate();
let queueAuthorityKeypair: Keypair;
let oracle1: OracleAccount;
let nodeOracle: NodeOracle;
let vrfPoolAccount: VrfPoolAccount;
let vrfLiteAccount: VrfLiteAccount;
before(async () => {
ctx = await setupTest();
queueAuthorityKeypair = ctx.payer;
[userTokenAddress] = await ctx.program.mint.getOrCreateWrappedUser(
ctx.program.walletPubkey,
{ fundUpTo: 1 }
);
[queueAccount] = await QueueAccount.create(ctx.program, {
name: "q1",
metadata: "",
queueSize: 2,
reward: 0,
minStake: 0,
oracleTimeout: 600,
slashingEnabled: false,
unpermissionedFeeds: false,
unpermissionedVrf: true,
enableBufferRelayers: false,
authority: queueAuthorityKeypair.publicKey,
});
const queue = await queueAccount.loadData();
assert(
queue.authority.equals(queueAuthorityKeypair.publicKey),
`Queue authority mismatch, expected ${queueAuthorityKeypair.publicKey}, received ${queue.authority}`
);
[oracle1] = await queueAccount.createOracle({
name: "Oracle 1",
enable: true,
queueAuthority: queueAuthorityKeypair,
});
await oracle1.heartbeat();
await oracle1.loadData();
nodeOracle = await NodeOracle.fromReleaseChannel({
releaseChannel: "testnet",
chain: "solana",
network: "localnet",
rpcUrl: "http://0.0.0.0:8899",
oracleKey: oracle1.publicKey.toBase58(),
secretPath: "~/.config/solana/id.json",
silent: true,
});
chalkString("payer", ctx.program.walletPubkey.toBase58());
chalkString("queueAuthority", queue.authority.toBase58());
chalkString("QueueAccount", queueAccount.publicKey.toBase58());
chalkString("OracleAccount", oracle1.publicKey.toBase58());
await nodeOracle.startAndAwait(180); // gh actions can be slow to pull this
});
after(async () => {
await nodeOracle?.stop();
});
describe("Vrf Pool Tests", () => {});
it("Creates a Vrf Pool", async () => {
[vrfPoolAccount] = await VrfPoolAccount.create(ctx.program, {
maxRows: 100,
minInterval: 60,
queueAccount: queueAccount,
});
chalkString("VrfPool", vrfPoolAccount.publicKey.toBase58());
await sleep(3000);
const vrfPool = await vrfPoolAccount.loadData();
console.log(vrfPool.toJSON());
assert(vrfPool.size === 0, `VrfPoolSizeMismatch`);
});
it("Creates a VrfLiteAccount", async () => {
[vrfLiteAccount] = await queueAccount.createVrfLite({
enable: true,
queueAuthority: queueAuthorityKeypair,
});
chalkString("VrfLite", vrfLiteAccount.publicKey.toBase58());
const [permissionAccount] = PermissionAccount.fromSeed(
ctx.program,
queueAuthorityKeypair.publicKey,
queueAccount.publicKey,
vrfLiteAccount.publicKey
);
chalkString("VrfLitePermission", permissionAccount.publicKey.toBase58());
const vrfLite = await vrfLiteAccount.loadData();
const permission = await permissionAccount.loadData();
console.log(permission.toJSON());
assert(
permission.permissions ===
types.SwitchboardPermission.PermitVrfRequests.discriminator,
`PermissionsMismatch, expected ${types.SwitchboardPermission.PermitVrfRequests.discriminator}, received ${permission.permissions}`
);
});
it("Pushes a VrfLiteAccount on to a pool", async () => {
const pushSig = await vrfPoolAccount.push({
vrf: vrfLiteAccount,
});
const vrfPool = await vrfPoolAccount.loadData();
chalkString("Size", vrfPool.pool.length);
assert(vrfPool.size === 1, `VrfPoolSizeMismatch`);
});
it("Pops a VrfLiteAccount from a pool", async () => {
const popSig = await vrfPoolAccount.pop();
const vrfPool = await vrfPoolAccount.loadData();
chalkString("Size", vrfPool.pool.length);
assert(vrfPool.size === 0, `VrfPoolSizeMismatch`);
});
it("Re-pushes a VrfLiteAccount on to a pool", async () => {
const pushSig = await vrfPoolAccount.push({
vrf: vrfLiteAccount,
});
const vrfPool = await vrfPoolAccount.loadData();
chalkString("Size", vrfPool.pool.length);
assert(vrfPool.size === 1, `VrfPoolSizeMismatch`);
});
it("Adds new VRF Lite account to pool", async () => {
const [newVrfLiteAccount] = await queueAccount.createVrfLite({
queueAuthority: queueAuthorityKeypair,
enable: true,
});
const newVrfLite = await vrfLiteAccount.loadData();
chalkString("New VrfLite", newVrfLiteAccount.publicKey.toBase58());
// const permissionAccount = await newVrfLiteAccount.permissionAccount;
// chalkString(
// "New VrfLitePermission",
// permissionAccount.publicKey.toBase58()
// );
const pushSig = await vrfPoolAccount.push({
vrf: newVrfLiteAccount,
});
const vrfPool = await vrfPoolAccount.loadData();
chalkString("Size", vrfPool.pool.length);
assert(vrfPool.size === 2, `VrfPoolSizeMismatch`);
});
it("Requests randomness from the VRF Pool", async () => {
chalkString(
"lastRequest",
(await vrfLiteAccount.loadData()).requestTimestamp.toNumber()
);
const transferSig = await vrfPoolAccount.deposit({
tokenWallet: userTokenAddress,
amount: 0.1,
});
console.log((await vrfPoolAccount.loadData()).pool.map((r) => r.toJSON()));
const [event, signature] = await vrfPoolAccount.requestAndAwaitEvent({});
console.log(signature);
const newVrfLiteState: types.VrfLiteAccountData =
await vrfLiteAccount.awaitRandomness({
requestSlot: event.slot,
});
console.log(
`Status: ${newVrfLiteState.status.kind} (${newVrfLiteState.status.discriminator})`
);
assert(
newVrfLiteState.status.kind ===
types.VrfStatus.StatusCallbackSuccess.kind ||
newVrfLiteState.status.kind === types.VrfStatus.StatusVerified.kind,
`VrfLiteStatusMismatch`
);
assert(
!newVrfLiteState.result.every((val) => val === 0),
`VrfLiteResultMissing`
);
});
it("Fails to request randomness back-to-back", async () => {
const signature = await vrfPoolAccount.request();
console.log("completed first request", signature);
await assert.rejects(async () => {
await vrfPoolAccount.request();
console.log("completed second request");
}, new RegExp(/VrfPoolRequestTooSoon|6096/g));
});
it("Create a VrfAccount and request randomness", async () => {
const [vrfAccount] = await queueAccount.createVrf({
vrfKeypair: Keypair.generate(),
callback: {
programId: PublicKey.default,
accounts: [],
ixData: Buffer.from(""),
},
enable: true,
queueAuthority: queueAuthorityKeypair,
});
const [permissionAccount] = PermissionAccount.fromSeed(
ctx.program,
queueAuthorityKeypair.publicKey,
queueAccount.publicKey,
vrfAccount.publicKey
);
chalkString("VrfAccount", vrfAccount.publicKey.toBase58());
chalkString("PermissionAccount", permissionAccount.publicKey.toBase58());
// await sleep(3000);
const vrfData = await vrfAccount.loadData();
console.log((await permissionAccount.loadData()).toJSON());
const [newVrfState] = await vrfAccount.requestAndAwaitResult({
payerTokenWallet: userTokenAddress,
vrf: vrfData,
queueAccount: queueAccount,
queue: await queueAccount.loadData(),
});
assert(
newVrfState.status.kind === "StatusVerified" ||
newVrfState.status.kind === "StatusCallbackSuccess",
"VrfStatusMismatch"
);
});
describe("Closes VrfAccounts", () => {
it("Closes a VrfLite and permission account", async () => {
const [myVrfLiteAccount] = await queueAccount.createVrfLite({
// keypair: vrfLiteKeypair,
enable: true,
queueAuthority: queueAuthorityKeypair,
});
chalkString("VrfLite", myVrfLiteAccount.publicKey.toBase58());
const [permissionAccount] = PermissionAccount.fromSeed(
ctx.program,
queueAuthorityKeypair.publicKey,
queueAccount.publicKey,
myVrfLiteAccount.publicKey
);
await sleep(3000);
await myVrfLiteAccount.deposit({ amount: 0.05 });
const vrfLite = await myVrfLiteAccount.loadData();
const permissions = await permissionAccount.loadData();
// close account
const closeSignature = await myVrfLiteAccount.closeAccount();
console.log(closeSignature);
// await sleep(15000);
// const vrfLiteAccountInfo = await ctx.program.connection.getAccountInfo(
// vrfLiteAccount.publicKey,
// { commitment: "single" }
// );
// assert(
// vrfLiteAccountInfo === null,
// `VrfLiteAccount not closed, expected null, received ${JSON.stringify(
// vrfLiteAccountInfo
// )}`
// );
// const permissionAccountInfo = await ctx.program.connection.getAccountInfo(
// permissionAccount.publicKey,
// { commitment: "single" }
// );
// assert(permissionAccountInfo === null, "PermissionAccount not closed");
});
it("Create and closes a VrfAccount", async () => {
const [vrfAccount] = await queueAccount.createVrf({
vrfKeypair: Keypair.generate(),
callback: {
programId: PublicKey.default,
accounts: [],
ixData: Buffer.from(""),
},
enable: true,
queueAuthority: queueAuthorityKeypair,
});
const [permissionAccount] = PermissionAccount.fromSeed(
ctx.program,
queueAuthorityKeypair.publicKey,
queueAccount.publicKey,
vrfAccount.publicKey
);
chalkString("VrfAccount", vrfAccount.publicKey.toBase58());
chalkString("PermissionAccount", permissionAccount.publicKey.toBase58());
// await sleep(3000);
const vrfDataBefore = await vrfAccount.loadData();
const closeAccountSig = await vrfAccount.closeAccount();
console.log(closeAccountSig);
const vrfAccountInfo = await ctx.program.connection.getAccountInfo(
vrfAccount.publicKey,
"processed"
);
assert(vrfAccountInfo === null, "VrfAccountNotClosed");
});
});
describe("Cycles through a VrfPool", () => {
it("Creates a 10 row VrfPool and cycles through them", async () => {
const POOL_SIZE = 10;
const [bigVrfPoolAccount] = await VrfPoolAccount.create(ctx.program, {
maxRows: POOL_SIZE,
minInterval: 0, // no delay if ready
queueAccount: queueAccount,
});
chalkString("VrfPool", bigVrfPoolAccount.publicKey.toBase58());
await sleep(3000);
const initialVrfPool = await bigVrfPoolAccount.loadData();
const txns: Array<[VrfLiteAccount, TransactionObject]> =
await Promise.all(
Array.from(Array(POOL_SIZE).keys()).map(async (n) => {
const [vrfLiteAccount, vrfLiteInit] =
await bigVrfPoolAccount.pushNewInstruction(ctx.payer.publicKey, {
enable: true,
queueAuthority: queueAuthorityKeypair,
});
return [vrfLiteAccount, vrfLiteInit];
})
);
const packed = TransactionObject.pack(txns.map((t) => t[1]));
await TransactionObject.signAndSendAll(
ctx.program.provider,
packed,
{ skipPreflight: true },
undefined,
10
);
await sleep(5000);
const newVrfPool = await bigVrfPoolAccount.loadData();
const pool = [...newVrfPool.pool];
assert(newVrfPool.pool.length === POOL_SIZE, "VrfPoolSizeMismatch");
const [tokenWallet] = await ctx.program.mint.getOrCreateWrappedUser(
ctx.program.walletPubkey,
{
amount: POOL_SIZE * 2 * 0.002,
}
);
await bigVrfPoolAccount.deposit({
tokenWallet,
amount: POOL_SIZE * 2 * 0.002,
});
let vrfPool = newVrfPool;
let ws: number | undefined = bigVrfPoolAccount.onChange((updVrfPool) => {
vrfPool = updVrfPool;
});
for await (const n of Array.from(
Array(Math.ceil(POOL_SIZE * 1.25)).keys()
)) {
const idx = n % pool.length;
assert(vrfPool.idx === idx, "VrfPoolIdxMismatch");
const [event, signature] =
await bigVrfPoolAccount.requestAndAwaitEvent();
const vrfLiteAccount = new VrfLiteAccount(
bigVrfPoolAccount.program,
pool[idx].pubkey
);
assert(
event.vrfPubkey.equals(vrfLiteAccount.publicKey),
"VrfRowMismatch"
);
await sleep(1500);
const nextIdx = (idx + 1) % pool.length;
assert(vrfPool.idx === nextIdx, "VrfPoolIdxMismatch");
}
if (ws) {
await bigVrfPoolAccount.program.connection
.removeAccountChangeListener(ws)
.then(() => {
ws = undefined;
})
.catch();
}
return;
});
});
});
const chalkString = (
key: string,
value: string | number | boolean | PublicKey
) =>
console.log(
`\x1b[34m${key.padEnd(16, " ")}\x1b[0m : \x1b[33m${value}\x1b[0m`
);