From 62a4878682959cfa78d7b69060ce70e32ffb6603 Mon Sep 17 00:00:00 2001 From: Conner Gallagher Date: Fri, 9 Dec 2022 12:02:57 -0700 Subject: [PATCH] solana.js: added createMock methods to some accounts --- .../src/accounts/aggregatorAccount.ts | 35 ++++++++- .../solana.js/src/accounts/jobAccount.ts | 61 ++++++++++++++- .../solana.js/src/accounts/leaseAccount.ts | 34 ++++++++- .../solana.js/src/accounts/oracleAccount.ts | 34 ++++++++- .../src/accounts/permissionAccount.ts | 43 +++++++++-- .../src/accounts/programStateAccount.ts | 74 ++++++++++++------- .../solana.js/src/accounts/queueAccount.ts | 45 +++++++++-- .../solana.js/src/accounts/queueDataBuffer.ts | 60 ++++++++++++--- 8 files changed, 334 insertions(+), 52 deletions(-) diff --git a/javascript/solana.js/src/accounts/aggregatorAccount.ts b/javascript/solana.js/src/accounts/aggregatorAccount.ts index fe2bb71..24b8d04 100644 --- a/javascript/solana.js/src/accounts/aggregatorAccount.ts +++ b/javascript/solana.js/src/accounts/aggregatorAccount.ts @@ -5,9 +5,11 @@ import * as errors from '../errors'; import Big from 'big.js'; import { SwitchboardProgram } from '../program'; import { + AccountInfo, AccountMeta, Commitment, Keypair, + LAMPORTS_PER_SOL, PublicKey, SystemProgram, TransactionInstruction, @@ -57,6 +59,9 @@ export class AggregatorAccount extends Account { */ public static getMetadata = (aggregator: types.AggregatorAccountData) => toUtf8(aggregator.metadata); + + public static size = 3851; + /** * Get the size of an {@linkcode AggregatorAccount} on-chain. */ @@ -74,11 +79,39 @@ export class AggregatorAccount extends Account { } public static default(): types.AggregatorAccountData { - const buffer = Buffer.alloc(3851, 0); + const buffer = Buffer.alloc(AggregatorAccount.size, 0); types.AggregatorAccountData.discriminator.copy(buffer, 0); return types.AggregatorAccountData.decode(buffer); } + public static createMock( + programId: PublicKey, + data: Partial, + options?: { + lamports?: number; + rentEpoch?: number; + } + ): AccountInfo { + const fields: types.AggregatorAccountDataFields = { + ...AggregatorAccount.default(), + ...data, + // any cleanup actions here + }; + const state = new types.AggregatorAccountData(fields); + + const buffer = Buffer.alloc(AggregatorAccount.size, 0); + types.AggregatorAccountData.discriminator.copy(buffer, 0); + types.AggregatorAccountData.layout.encode(state, buffer, 8); + + return { + executable: false, + owner: programId, + lamports: options?.lamports ?? 1 * LAMPORTS_PER_SOL, + data: buffer, + rentEpoch: options?.rentEpoch ?? 0, + }; + } + /** * Invoke a callback each time an AggregatorAccount's data has changed on-chain. * @param callback - the callback invoked when the aggregator state changes diff --git a/javascript/solana.js/src/accounts/jobAccount.ts b/javascript/solana.js/src/accounts/jobAccount.ts index 6fe3420..a05f1da 100644 --- a/javascript/solana.js/src/accounts/jobAccount.ts +++ b/javascript/solana.js/src/accounts/jobAccount.ts @@ -2,6 +2,7 @@ import { AccountInfo, Commitment, Keypair, + LAMPORTS_PER_SOL, PublicKey, SystemProgram, TransactionSignature, @@ -15,7 +16,7 @@ import { Account } from './account'; import { TransactionObject } from '../transaction'; /** - * Account type storing a list of SwitchboardTasks {@linkcode OracleJob.ITask} dictating how to source data off-chain. + * Account type storing a list of SwitchboardTasks {@linkcode OracleJob.Task} dictating how to source data off-chain. * * Data: {@linkcode types.JobAccountData} */ @@ -36,6 +37,64 @@ export class JobAccount extends Account { */ public size = this.program.account.jobAccountData.size; + public static getAccountSize(byteLength: number): number { + return 181 + byteLength; + } + + public static default(byteLength: number): types.LeaseAccountData { + const buffer = Buffer.alloc(JobAccount.getAccountSize(byteLength), 0); + types.LeaseAccountData.discriminator.copy(buffer, 0); + return types.LeaseAccountData.decode(buffer); + } + + public static createMock( + programId: PublicKey, + data: Partial & + ({ job: OracleJob } | { tasks: Array }), + options?: { + lamports?: number; + rentEpoch?: number; + } + ): AccountInfo { + let jobData: Buffer | undefined = undefined; + if ('data' in data && data.data && data.data.byteLength > 0) { + jobData = Buffer.from(data.data); + } + if ('job' in data) { + jobData = Buffer.from(OracleJob.encodeDelimited(data.job).finish()); + } else if ('tasks' in data) { + jobData = Buffer.from( + OracleJob.encodeDelimited(OracleJob.fromObject(data.tasks)).finish() + ); + } + if (!jobData) { + throw new Error(`No job data found to create mock`); + } + + const fields: types.LeaseAccountDataFields = { + ...JobAccount.default(jobData.byteLength), + ...data, + // any cleanup actions here + }; + const state = new types.LeaseAccountData(fields); + + const buffer = Buffer.alloc( + JobAccount.getAccountSize(jobData.byteLength), + 0 + ); + types.LeaseAccountData.discriminator.copy(buffer, 0); + types.LeaseAccountData.layout.encode(state, buffer, 8); + jobData.copy(buffer, 181); + + return { + executable: false, + owner: programId, + lamports: options?.lamports ?? 1 * LAMPORTS_PER_SOL, + data: buffer, + rentEpoch: options?.rentEpoch ?? 0, + }; + } + /** Load an existing JobAccount with its current on-chain state */ public static async load( program: SwitchboardProgram, diff --git a/javascript/solana.js/src/accounts/leaseAccount.ts b/javascript/solana.js/src/accounts/leaseAccount.ts index 3e5635b..4df76c1 100644 --- a/javascript/solana.js/src/accounts/leaseAccount.ts +++ b/javascript/solana.js/src/accounts/leaseAccount.ts @@ -5,8 +5,10 @@ import { SwitchboardProgram } from '../program'; import { Account } from './account'; import * as spl from '@solana/spl-token'; import { + AccountInfo, AccountMeta, Keypair, + LAMPORTS_PER_SOL, PublicKey, SystemProgram, TransactionInstruction, @@ -26,17 +28,47 @@ import Big from 'big.js'; export class LeaseAccount extends Account { static accountName = 'LeaseAccountData'; + public static size = 453; + /** * Get the size of an {@linkcode LeaseAccount} on-chain. */ public size = this.program.account.leaseAccountData.size; public static default(): types.LeaseAccountData { - const buffer = Buffer.alloc(453, 0); + const buffer = Buffer.alloc(LeaseAccount.size, 0); types.LeaseAccountData.discriminator.copy(buffer, 0); return types.LeaseAccountData.decode(buffer); } + public static createMock( + programId: PublicKey, + data: Partial, + options?: { + lamports?: number; + rentEpoch?: number; + } + ): AccountInfo { + const fields: types.LeaseAccountDataFields = { + ...LeaseAccount.default(), + ...data, + // any cleanup actions here + }; + const state = new types.LeaseAccountData(fields); + + const buffer = Buffer.alloc(LeaseAccount.size, 0); + types.LeaseAccountData.discriminator.copy(buffer, 0); + types.LeaseAccountData.layout.encode(state, buffer, 8); + + return { + executable: false, + owner: programId, + lamports: options?.lamports ?? 1 * LAMPORTS_PER_SOL, + data: buffer, + rentEpoch: options?.rentEpoch ?? 0, + }; + } + /** Load an existing LeaseAccount with its current on-chain state */ public static async load( program: SwitchboardProgram, diff --git a/javascript/solana.js/src/accounts/oracleAccount.ts b/javascript/solana.js/src/accounts/oracleAccount.ts index 86bb2e5..e8b9a50 100644 --- a/javascript/solana.js/src/accounts/oracleAccount.ts +++ b/javascript/solana.js/src/accounts/oracleAccount.ts @@ -4,8 +4,10 @@ import { Account, OnAccountChangeCallback } from './account'; import * as anchor from '@project-serum/anchor'; import { SwitchboardProgram } from '../program'; import { + AccountInfo, Commitment, Keypair, + LAMPORTS_PER_SOL, PublicKey, SystemProgram, TransactionSignature, @@ -25,17 +27,47 @@ import { TransactionObject } from '../transaction'; export class OracleAccount extends Account { static accountName = 'OracleAccountData'; + public static size = 636; + /** * Get the size of an {@linkcode OracleAccount} on-chain. */ public size = this.program.account.oracleAccountData.size; public static default(): types.OracleAccountData { - const buffer = Buffer.alloc(636, 0); + const buffer = Buffer.alloc(OracleAccount.size, 0); types.OracleAccountData.discriminator.copy(buffer, 0); return types.OracleAccountData.decode(buffer); } + public static createMock( + programId: PublicKey, + data: Partial, + options?: { + lamports?: number; + rentEpoch?: number; + } + ): AccountInfo { + const fields: types.OracleAccountDataFields = { + ...OracleAccount.default(), + ...data, + // any cleanup actions here + }; + const state = new types.OracleAccountData(fields); + + const buffer = Buffer.alloc(OracleAccount.size, 0); + types.OracleAccountData.discriminator.copy(buffer, 0); + types.OracleAccountData.layout.encode(state, buffer, 8); + + return { + executable: false, + owner: programId, + lamports: options?.lamports ?? 1 * LAMPORTS_PER_SOL, + data: buffer, + rentEpoch: options?.rentEpoch ?? 0, + }; + } + /** Load an existing OracleAccount with its current on-chain state */ public static async load( program: SwitchboardProgram, diff --git a/javascript/solana.js/src/accounts/permissionAccount.ts b/javascript/solana.js/src/accounts/permissionAccount.ts index 108792d..65cf0a5 100644 --- a/javascript/solana.js/src/accounts/permissionAccount.ts +++ b/javascript/solana.js/src/accounts/permissionAccount.ts @@ -3,6 +3,7 @@ import { ACCOUNT_DISCRIMINATOR_SIZE } from '@project-serum/anchor'; import { AccountInfo, Keypair, + LAMPORTS_PER_SOL, PublicKey, SystemProgram, TransactionSignature, @@ -67,6 +68,13 @@ export interface PermissionSetParams { export class PermissionAccount extends Account { static accountName = 'PermissionAccountData'; + public static size = 372; + + /** + * Returns the size of an on-chain {@linkcode PermissionAccount}. + */ + public readonly size = this.program.account.permissionAccountData.size; + static getPermissions( permission: types.PermissionAccountData ): types.SwitchboardPermissionKind | PermitNone { @@ -87,11 +95,39 @@ export class PermissionAccount extends Account { } public static default(): types.PermissionAccountData { - const buffer = Buffer.alloc(372, 0); + const buffer = Buffer.alloc(PermissionAccount.size, 0); types.PermissionAccountData.discriminator.copy(buffer, 0); return types.PermissionAccountData.decode(buffer); } + public static createMock( + programId: PublicKey, + data: Partial, + options?: { + lamports?: number; + rentEpoch?: number; + } + ): AccountInfo { + const fields: types.PermissionAccountDataFields = { + ...PermissionAccount.default(), + ...data, + // any cleanup actions here + }; + const state = new types.PermissionAccountData(fields); + + const buffer = Buffer.alloc(PermissionAccount.size, 0); + types.PermissionAccountData.discriminator.copy(buffer, 0); + types.PermissionAccountData.layout.encode(state, buffer, 8); + + return { + executable: false, + owner: programId, + lamports: options?.lamports ?? 1 * LAMPORTS_PER_SOL, + data: buffer, + rentEpoch: options?.rentEpoch ?? 0, + }; + } + /** Load an existing PermissionAccount with its current on-chain state */ public static async load( program: SwitchboardProgram, @@ -174,11 +210,6 @@ export class PermissionAccount extends Account { return [account, txSignature]; } - /** - * Returns the size of an on-chain {@linkcode PermissionAccount}. - */ - public readonly size = this.program.account.permissionAccountData.size; - /** * Retrieve and decode the {@linkcode types.PermissionAccountData} stored in this account. */ diff --git a/javascript/solana.js/src/accounts/programStateAccount.ts b/javascript/solana.js/src/accounts/programStateAccount.ts index 330ead0..79c254d 100644 --- a/javascript/solana.js/src/accounts/programStateAccount.ts +++ b/javascript/solana.js/src/accounts/programStateAccount.ts @@ -6,7 +6,9 @@ import * as spl from '@solana/spl-token'; import * as errors from '../errors'; import { Mint } from '../mint'; import { + AccountInfo, Keypair, + LAMPORTS_PER_SOL, PublicKey, SystemProgram, TransactionInstruction, @@ -22,12 +24,47 @@ import { TransactionObject } from '../transaction'; export class ProgramStateAccount extends Account { static accountName = 'SbState'; + public static size = 1128; + + /** + * @return account size of the global {@linkcode ProgramStateAccount}. + */ + public readonly size = this.program.account.sbState.size; + public static default(): types.SbState { - const buffer = Buffer.alloc(1128, 0); + const buffer = Buffer.alloc(ProgramStateAccount.size, 0); types.SbState.discriminator.copy(buffer, 0); return types.SbState.decode(buffer); } + public static createMock( + programId: PublicKey, + data: Partial, + options?: { + lamports?: number; + rentEpoch?: number; + } + ): AccountInfo { + const fields: types.SbStateFields = { + ...ProgramStateAccount.default(), + ...data, + // any cleanup actions here + }; + const state = new types.SbState(fields); + + const buffer = Buffer.alloc(ProgramStateAccount.size, 0); + types.SbState.discriminator.copy(buffer, 0); + types.SbState.layout.encode(state, buffer, 8); + + return { + executable: false, + owner: programId, + lamports: options?.lamports ?? 1 * LAMPORTS_PER_SOL, + data: buffer, + rentEpoch: options?.rentEpoch ?? 0, + }; + } + /** Load the ProgramStateAccount with its current on-chain state */ public static async load( program: SwitchboardProgram, @@ -41,6 +78,16 @@ export class ProgramStateAccount extends Account { return [account, state]; } + /** + * Retrieve and decode the {@linkcode types.SbState} stored in this account. + */ + public async loadData(): Promise { + const data = await types.SbState.fetch(this.program, this.publicKey); + if (data === null) + throw new errors.AccountNotFoundError('Program State', this.publicKey); + return data; + } + /** * Retrieves the {@linkcode ProgramStateAccount}, creates it if it doesn't exist; */ @@ -157,6 +204,7 @@ export class ProgramStateAccount extends Account { ); return [new ProgramStateAccount(program, publicKey), bump]; } + /** * Transfer N tokens from the program vault to a specified account. * @param to The recipient of the vault tokens. @@ -193,28 +241,4 @@ export class ProgramStateAccount extends Account { const txnSignature = await program.signAndSend(vaultTransfer); return txnSignature; } - /** - * @return account size of the global {@linkcode ProgramStateAccount}. - */ - public readonly size = this.program.account.sbState.size; - /** - * Retrieve and decode the {@linkcode types.SbState} stored in this account. - */ - public async loadData(): Promise { - const data = await types.SbState.fetch(this.program, this.publicKey); - if (data === null) - throw new errors.AccountNotFoundError('Program State', this.publicKey); - return data; - } - /** - * Fetch the Switchboard token mint specified in the program state account. - */ - public async getTokenMint(): Promise { - const state = await this.loadData(); - const switchTokenMint = spl.getMint( - this.program.connection, - state.tokenMint - ); - return switchTokenMint; - } } diff --git a/javascript/solana.js/src/accounts/queueAccount.ts b/javascript/solana.js/src/accounts/queueAccount.ts index 122a635..a1009e0 100644 --- a/javascript/solana.js/src/accounts/queueAccount.ts +++ b/javascript/solana.js/src/accounts/queueAccount.ts @@ -1,8 +1,10 @@ import * as anchor from '@project-serum/anchor'; import * as spl from '@solana/spl-token'; import { + AccountInfo, Commitment, Keypair, + LAMPORTS_PER_SOL, PublicKey, SystemProgram, TransactionSignature, @@ -47,15 +49,11 @@ import { VrfAccount, VrfInitParams } from './vrfAccount'; export class QueueAccount extends Account { static accountName = 'OracleQueueAccountData'; - public static default(): types.OracleQueueAccountData { - const buffer = Buffer.alloc(1269, 0); - types.OracleQueueAccountData.discriminator.copy(buffer, 0); - return types.OracleQueueAccountData.decode(buffer); - } - /** The {@linkcode QueueDataBuffer} storing a list of oracle's that are actively heartbeating */ dataBuffer?: QueueDataBuffer; + public static size = 1269; + /** * Get the size of an {@linkcode QueueAccount} on-chain. */ @@ -66,6 +64,7 @@ export class QueueAccount extends Account { */ public static getName = (queue: types.OracleQueueAccountData) => toUtf8(queue.name); + /** * Returns the queue's metadata buffer in a stringified format. */ @@ -84,6 +83,40 @@ export class QueueAccount extends Account { return [account, state]; } + public static default(): types.OracleQueueAccountData { + const buffer = Buffer.alloc(1269, 0); + types.OracleQueueAccountData.discriminator.copy(buffer, 0); + return types.OracleQueueAccountData.decode(buffer); + } + + public static createMock( + programId: PublicKey, + data: Partial, + options?: { + lamports?: number; + rentEpoch?: number; + } + ): AccountInfo { + const fields: types.OracleQueueAccountDataFields = { + ...QueueAccount.default(), + ...data, + // any cleanup actions here + }; + const state = new types.OracleQueueAccountData(fields); + + const buffer = Buffer.alloc(QueueAccount.size, 0); + types.OracleQueueAccountData.discriminator.copy(buffer, 0); + types.OracleQueueAccountData.layout.encode(state, buffer, 8); + + return { + executable: false, + owner: programId, + lamports: options?.lamports ?? 1 * LAMPORTS_PER_SOL, + data: buffer, + rentEpoch: options?.rentEpoch ?? 0, + }; + } + /** * Invoke a callback each time a QueueAccount's data has changed on-chain. * @param callback - the callback invoked when the queues state changes diff --git a/javascript/solana.js/src/accounts/queueDataBuffer.ts b/javascript/solana.js/src/accounts/queueDataBuffer.ts index cc121ae..12b5d64 100644 --- a/javascript/solana.js/src/accounts/queueDataBuffer.ts +++ b/javascript/solana.js/src/accounts/queueDataBuffer.ts @@ -1,4 +1,10 @@ -import { AccountInfo, Commitment, PublicKey } from '@solana/web3.js'; +import { + AccountInfo, + Commitment, + LAMPORTS_PER_SOL, + PublicKey, +} from '@solana/web3.js'; +import assert from 'assert'; import * as errors from '../errors'; import * as types from '../generated'; import { SwitchboardProgram } from '../program'; @@ -18,6 +24,48 @@ export class QueueDataBuffer extends Account> { public size = 32; + public static getAccountSize(size: number): number { + return 8 + size * 32; + } + + public static default(size = 100): Buffer { + const buffer = Buffer.alloc(QueueDataBuffer.getAccountSize(size), 0); + BUFFER_DISCRIMINATOR.copy(buffer, 0); + return buffer; + } + + public static createMock( + programId: PublicKey, + data: { size?: number; oracles?: Array }, + options?: { + lamports?: number; + rentEpoch?: number; + } + ): AccountInfo { + const size = data.size ?? 100; + + const oracles: Array = Array(size).fill(PublicKey.default); + for (const [n, oracle] of (data.oracles ?? []).entries()) { + oracles[n] = oracle; + } + + const buffer = Buffer.alloc(QueueDataBuffer.getAccountSize(size), 0); + BUFFER_DISCRIMINATOR.copy(buffer, 0); + for (const [n, oracle] of oracles.entries()) { + const oracleBuffer = oracle.toBuffer(); + assert(oracleBuffer.byteLength === 32); + oracleBuffer.copy(buffer, 8 + n * 32); + } + + return { + executable: false, + owner: programId, + lamports: options?.lamports ?? 1 * LAMPORTS_PER_SOL, + data: buffer, + rentEpoch: options?.rentEpoch ?? 0, + }; + } + /** * Invoke a callback each time a QueueAccount's oracle queue buffer has changed on-chain. The buffer stores a list of oracle's and their last heartbeat timestamp. * @param callback - the callback invoked when the queues buffer changes @@ -82,16 +130,6 @@ export class QueueDataBuffer extends Account> { return oracles; } - public static getAccountSize(size: number): number { - return 8 + size * 32; - } - - public static default(size = 100): Buffer { - const buffer = Buffer.alloc(QueueDataBuffer.getAccountSize(size), 0); - BUFFER_DISCRIMINATOR.copy(buffer, 0); - return buffer; - } - /** * Return a queues dataBuffer *