solana.js: added vrf pool tests

This commit is contained in:
Conner Gallagher 2023-03-01 17:48:27 -07:00
parent 037d74bdb9
commit ca2686995a
6 changed files with 475 additions and 19 deletions

View File

@ -1,5 +1,6 @@
name: solana-js-test
on:
workflow_dispatch:
push:
paths:
- "javascript/solana.js/src/**"

View File

@ -14,7 +14,7 @@
"@solana/spl-token": "^0.3.6",
"@solana/web3.js": "^1.73.0",
"@switchboard-xyz/common": "^2.1.33",
"@switchboard-xyz/oracle": "^2.1.6",
"@switchboard-xyz/oracle": "^2.1.7",
"dotenv": "^16.0.3",
"lodash": "^4.17.21"
},
@ -613,9 +613,9 @@
}
},
"node_modules/@switchboard-xyz/oracle": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/@switchboard-xyz/oracle/-/oracle-2.1.6.tgz",
"integrity": "sha512-gh7//Vy8CvJUwWpe6UuK9t1t5Ra6CjYd7+qQkYcDaaYVKj37zGvNTHcPDRcLkRQfxsHMvp2LtqaSGUE5CrjmHg==",
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/@switchboard-xyz/oracle/-/oracle-2.1.7.tgz",
"integrity": "sha512-m2YRDXCdAeCtllRRHvPZ4B0UcbgJ1zhTBzyBcNOTdoZGkm9CJaZqslFrvwSouBmhS1wHzFF49IyN/xCzCZlFHw==",
"dependencies": {
"@terascope/fetch-github-release": "^0.8.7",
"detect-port": "^1.5.1",
@ -6234,9 +6234,9 @@
}
},
"@switchboard-xyz/oracle": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/@switchboard-xyz/oracle/-/oracle-2.1.6.tgz",
"integrity": "sha512-gh7//Vy8CvJUwWpe6UuK9t1t5Ra6CjYd7+qQkYcDaaYVKj37zGvNTHcPDRcLkRQfxsHMvp2LtqaSGUE5CrjmHg==",
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/@switchboard-xyz/oracle/-/oracle-2.1.7.tgz",
"integrity": "sha512-m2YRDXCdAeCtllRRHvPZ4B0UcbgJ1zhTBzyBcNOTdoZGkm9CJaZqslFrvwSouBmhS1wHzFF49IyN/xCzCZlFHw==",
"requires": {
"@terascope/fetch-github-release": "^0.8.7",
"detect-port": "^1.5.1",

View File

@ -15,11 +15,7 @@
"import": "./lib/esm/index.js",
"require": "./lib/cjs/index.js"
},
"./package.json": "./package.json",
"./docker": {
"import": "./lib/esm/SolanaDockerOracle.js",
"require": "./lib/cjs/SolanaDockerOracle.js"
}
"./package.json": "./package.json"
},
"main": "lib/cjs/index.js",
"module": "lib/esm/index.js",
@ -37,9 +33,9 @@
"build": "npm run build:cjs && npm run build:esm",
"watch": "tsc -p tsconfig.cjs.json --watch",
"pretest": "npm run build",
"test": "node ./node_modules/mocha/bin/mocha --loader=ts-node/esm --extension ts --timeout 10000",
"test:localnet": "SOLANA_LOCALNET=1 node ./node_modules/mocha/bin/mocha --loader=ts-node/esm --extension ts --timeout 60000",
"test:localnet:mainnet": "SWITCHBOARD_PROGRAM_ID=SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f SOLANA_LOCALNET=1 node ./node_modules/mocha/bin/mocha --loader=ts-node/esm --extension ts --timeout 60000",
"test": "node ./node_modules/mocha/bin/mocha --loader=ts-node/esm --extension ts --timeout 150000",
"test:localnet": "SOLANA_LOCALNET=1 node ./node_modules/mocha/bin/mocha --loader=ts-node/esm --extension ts --timeout 150000",
"test:localnet:mainnet": "SWITCHBOARD_PROGRAM_ID=SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f SOLANA_LOCALNET=1 node ./node_modules/mocha/bin/mocha --loader=ts-node/esm --extension ts --timeout 150000",
"localnet:up": "npm run local:validator & sleep 20 && npm run test:localnet",
"localnet:down": "kill -9 $(pgrep command solana-test-validator) || exit 0",
"localnet": "npm run localnet:up; npm run localnet:down",
@ -56,7 +52,7 @@
"@solana/spl-token": "^0.3.6",
"@solana/web3.js": "^1.73.0",
"@switchboard-xyz/common": "^2.1.33",
"@switchboard-xyz/oracle": "^2.1.6",
"@switchboard-xyz/oracle": "^2.1.7",
"dotenv": "^16.0.3",
"lodash": "^4.17.21"
},

View File

@ -1161,7 +1161,7 @@ export class QueueAccount extends Account<types.OracleQueueAccountData> {
oraclesNeeded = 1
): Promise<boolean> {
const activeOracles = await this.loadActiveOracleAccounts(_queue);
return activeOracles.length >= oraclesNeeded ? true : false;
return activeOracles.length >= oraclesNeeded;
}
public async setConfig(

View File

@ -259,7 +259,7 @@ export class VrfPoolAccount extends Account<VrfPoolAccountData> {
vrfPool.queue
);
const [permissionAccount] = this.getPermissionAccount(
const [permissionAccount] = params.vrf.getPermissionAccount(
queueAccount.publicKey,
queue.authority
);
@ -357,7 +357,8 @@ export class VrfPoolAccount extends Account<VrfPoolAccountData> {
const accountMetas = vrfRows.map((row): Array<AccountMeta> => {
const escrow = this.program.mint.getAssociatedAddress(row.pubkey);
const [permission] = this.getPermissionAccount(
const vrfLiteAccount = new VrfLiteAccount(this.program, row.pubkey);
const [permission] = vrfLiteAccount.getPermissionAccount(
vrfPool.queue,
queueAuthority
);

View File

@ -0,0 +1,458 @@
import { Keypair, PublicKey } from '@solana/web3.js';
import assert from 'assert';
import 'mocha';
import {
OracleAccount,
PermissionAccount,
QueueAccount,
TransactionObject,
types,
VrfLiteAccount,
VrfPoolAccount,
} from '../src';
import { setupTest, TestContext } from './utils';
import { sleep } from '@switchboard-xyz/common';
import { NodeOracle } from '@switchboard-xyz/oracle';
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();
});
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();
console.log(initialVrfPool.toJSON());
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]));
const signatures = await TransactionObject.signAndSendAll(
ctx.program.provider,
packed,
{ skipPreflight: true },
undefined,
10
);
console.log(signatures);
console.log(signatures.length, 'signatures');
await sleep(5000);
const newVrfPool = await bigVrfPoolAccount.loadData();
console.log(newVrfPool.toJSON());
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;
const ws = bigVrfPoolAccount.onChange(updVrfPool => {
vrfPool = updVrfPool;
});
for await (const n of Array.from(
Array(Math.ceil(POOL_SIZE * 1.25)).keys()
)) {
console.log(n);
const idx = n % pool.length;
console.log(`${vrfPool.idx} === ${idx}`);
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(1000);
const nextIdx = (idx + 1) % pool.length;
console.log(`${vrfPool.idx} === ${nextIdx}`);
assert(vrfPool.idx === nextIdx, 'VrfPoolIdxMismatch');
}
await bigVrfPoolAccount.program.connection
.removeAccountChangeListener(ws)
.catch();
});
});
});
const chalkString = (
key: string,
value: string | number | boolean | PublicKey
) =>
console.log(
`\x1b[34m${key.padEnd(16, ' ')}\x1b[0m : \x1b[33m${value}\x1b[0m`
);