sbv2-solana/javascript/solana.js/test/utils.ts

267 lines
7.3 KiB
TypeScript

import {
AggregatorAccount,
CreateQueueFeedParams,
QueueAccount,
SB_V2_PID,
TransactionObject,
} from "../src/index.js";
import * as sbv2 from "../src/index.js";
import {
clusterApiUrl,
Connection,
Keypair,
LAMPORTS_PER_SOL,
PublicKey,
} from "@solana/web3.js";
import { OracleJob } from "@switchboard-xyz/common";
import assert from "assert";
import dotenv from "dotenv";
import fs from "fs";
import os from "os";
import path from "path";
dotenv.config();
type SolanaCluster = "localnet" | "devnet" | "mainnet-beta";
export const sleep = (ms: number): Promise<any> =>
new Promise((s) => setTimeout(s, ms));
export const DEFAULT_KEYPAIR_PATH = path.join(
os.homedir(),
".config/solana/id.json"
);
export interface TestContext {
cluster: SolanaCluster;
program: sbv2.SwitchboardProgram;
payer: Keypair;
toUrl: (signature: string) => string;
round: (num: number, decimalPlaces: number) => number;
}
export function isLocalnet(): boolean {
if (process.env.SOLANA_LOCALNET) {
switch (process.env.SOLANA_LOCALNET) {
case "1":
case "true":
case "localnet": {
return true;
}
}
}
return false;
}
export function getCluster(): SolanaCluster {
if (process.env.SOLANA_CLUSTER) {
const cluster = String(process.env.SOLANA_CLUSTER);
if (
cluster === "localnet" ||
cluster === "devnet" ||
cluster === "mainnet-beta"
) {
return cluster;
} else {
throw new Error(
`SOLANA_CLUSTER must be localnet, devnet, or mainnet-beta`
);
}
}
if (isLocalnet()) {
return "localnet";
}
return "devnet";
}
export function getProgramId(cluster: SolanaCluster): PublicKey {
if (process.env.SWITCHBOARD_PROGRAM_ID) {
return new PublicKey(process.env.SWITCHBOARD_PROGRAM_ID);
}
return SB_V2_PID;
}
export function getRpcUrl(cluster: SolanaCluster): string {
if (isLocalnet()) {
return "http://0.0.0.0:8899";
}
if (process.env.SOLANA_RPC_URL) {
return String(process.env.SOLANA_RPC_URL);
}
if (cluster === "localnet") {
return "http://0.0.0.0:8899";
}
return clusterApiUrl(cluster);
}
export async function setupTest(): Promise<TestContext> {
const cluster = getCluster();
const payer: Keypair = fs.existsSync(DEFAULT_KEYPAIR_PATH)
? Keypair.fromSecretKey(
new Uint8Array(
JSON.parse(fs.readFileSync(DEFAULT_KEYPAIR_PATH, "utf8"))
)
)
: Keypair.generate();
const programId = getProgramId(cluster);
const program = await sbv2.SwitchboardProgram.load(
cluster,
new Connection(getRpcUrl(cluster), { commitment: "confirmed" }),
payer,
programId
);
// request airdrop if low on funds
const payerBalance = await program.connection.getBalance(payer.publicKey);
if (payerBalance === 0) {
const airdropTxn = await program.connection.requestAirdrop(
payer.publicKey,
1 * LAMPORTS_PER_SOL
);
console.log(`Airdrop requested: ${airdropTxn}`);
await program.connection.confirmTransaction(airdropTxn);
}
// Check if programStateAccount exists
try {
const programState = await program.connection.getAccountInfo(
program.programState.publicKey
);
if (!programState || programState.data === null) {
await sbv2.ProgramStateAccount.getOrCreate(program);
}
} catch (e) {
console.error(e);
}
// Check if attestationProgramStateAccount exists
try {
const attestationProgramState = await program.connection.getAccountInfo(
program.attestationProgramState.publicKey
);
if (!attestationProgramState || attestationProgramState.data === null) {
await sbv2.AttestationProgramStateAccount.getOrCreate(program);
}
} catch (e) {
console.error(e);
}
await program.mint.getOrCreateAssociatedUser(program.walletPubkey);
return {
cluster,
program,
payer,
toUrl: (signature) =>
cluster === "localnet"
? `https://explorer.solana.com/tx/${signature}?cluster=custom&customUrl=http%3A%2F%2Flocalhost%3A8899`
: `https://explorer.solana.com/tx/${signature}${
cluster === "devnet" ? "?cluster=devnet" : ""
}`,
round: (num: number, decimalPlaces = 2): number => {
assert(decimalPlaces > 0 && decimalPlaces < 16);
const base = Math.pow(10, decimalPlaces);
return Math.round((num + Number.EPSILON) * base) / base;
},
};
}
export async function createFeed(
queueAccount: QueueAccount,
feedConfig?: Partial<CreateQueueFeedParams>
): Promise<AggregatorAccount> {
const [aggregatorAccount] = await queueAccount.createFeed({
name: feedConfig?.name ?? `Aggregator`,
queueAuthority: feedConfig?.queueAuthority,
batchSize: feedConfig?.batchSize ?? 1,
minRequiredOracleResults: feedConfig?.minRequiredOracleResults ?? 1,
minRequiredJobResults: feedConfig?.minRequiredOracleResults ?? 1,
minUpdateDelaySeconds: feedConfig?.minUpdateDelaySeconds ?? 10,
fundAmount: feedConfig?.fundAmount ?? 0,
enable: feedConfig?.enable ?? true,
jobs:
feedConfig?.jobs && feedConfig?.jobs.length > 0
? feedConfig?.jobs
: [
{
weight: 2,
data: OracleJob.encodeDelimited(
OracleJob.fromObject({
tasks: [
{
valueTask: {
value: 1,
},
},
],
})
).finish(),
},
],
});
return aggregatorAccount;
}
export async function createFeeds(
queueAccount: QueueAccount,
numFeeds: number,
feedConfig?: Partial<CreateQueueFeedParams>
): Promise<Array<AggregatorAccount>> {
const aggregators: Array<AggregatorAccount> = [];
const txns: Array<Array<TransactionObject>> = [];
for (const i of Array.from(Array(numFeeds).keys())) {
const [aggregatorAccount, txn] = await queueAccount.createFeedInstructions(
queueAccount.program.walletPubkey,
{
name: feedConfig?.name ?? `Aggregator-${i + 1}`,
queueAuthority: feedConfig?.queueAuthority,
batchSize: feedConfig?.batchSize ?? 1,
minRequiredOracleResults: feedConfig?.minRequiredOracleResults ?? 1,
minRequiredJobResults: feedConfig?.minRequiredOracleResults ?? 1,
minUpdateDelaySeconds:
feedConfig?.minUpdateDelaySeconds ??
5 + Math.floor(Math.random() * 25), // 5 - 30 sec,
fundAmount: feedConfig?.fundAmount ?? 0,
disableWrap: true,
enable: feedConfig?.enable ?? true,
slidingWindow: feedConfig?.slidingWindow ?? false,
jobs:
feedConfig?.jobs && feedConfig?.jobs.length > 0
? feedConfig?.jobs
: [
{
weight: 2,
data: OracleJob.encodeDelimited(
OracleJob.fromObject({
tasks: [
{
valueTask: {
value: 1,
},
},
],
})
).finish(),
},
],
}
);
aggregators.push(aggregatorAccount);
txns.push(txn);
}
await queueAccount.program.signAndSendAll(
TransactionObject.pack(txns.flat())
);
return aggregators;
}