solana.js: added SwitchboardTestContext
This commit is contained in:
parent
7bcf2e52f8
commit
73e43c653d
|
@ -17,12 +17,16 @@
|
|||
},
|
||||
"./package.json": "./package.json",
|
||||
"./idl/mainnet": {
|
||||
"import": "./dist/esm/idl/mainnet.json",
|
||||
"require": "./dist/cjs/idl/mainnet.json"
|
||||
"import": "./lib/esm/idl/mainnet.json",
|
||||
"require": "./lib/cjs/idl/mainnet.json"
|
||||
},
|
||||
"./idl/devnet": {
|
||||
"import": "./dist/esm/idl/devnet.json",
|
||||
"require": "./dist/cjs/idl/devnet.json"
|
||||
"import": "./lib/esm/idl/devnet.json",
|
||||
"require": "./lib/cjs/idl/devnet.json"
|
||||
},
|
||||
"./test": {
|
||||
"import": "./lib/esm/test/index.js",
|
||||
"require": "./lib/cjs/test/index.js"
|
||||
}
|
||||
},
|
||||
"main": "lib/cjs/index.js",
|
||||
|
|
|
@ -96,7 +96,7 @@ export class CrankAccount extends Account<types.CrankAccountData> {
|
|||
const keypair = params.keypair ?? Keypair.generate();
|
||||
program.verifyNewKeypair(keypair);
|
||||
|
||||
const buffer = anchor.web3.Keypair.generate();
|
||||
const buffer = params.dataBufferKeypair ?? anchor.web3.Keypair.generate();
|
||||
program.verifyNewKeypair(buffer);
|
||||
|
||||
const maxRows = params.maxRows ?? 500;
|
||||
|
@ -464,6 +464,10 @@ export interface CrankInitParams {
|
|||
* Optional
|
||||
*/
|
||||
keypair?: Keypair;
|
||||
/**
|
||||
* Optional
|
||||
*/
|
||||
dataBufferKeypair?: Keypair;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -145,12 +145,10 @@ export class OracleAccount extends Account<types.OracleAccountData> {
|
|||
} & OracleInitParams &
|
||||
OracleStakeParams
|
||||
): Promise<[OracleAccount, TransactionObject]> {
|
||||
const tokenWallet = Keypair.generate();
|
||||
const tokenWallet = params.stakingWalletKeypair ?? Keypair.generate();
|
||||
|
||||
const authority = params.authority?.publicKey ?? payer;
|
||||
|
||||
const txns: TransactionObject[] = [];
|
||||
|
||||
const [oracleAccount, oracleBump] = OracleAccount.fromSeed(
|
||||
program,
|
||||
params.queueAccount.publicKey,
|
||||
|
@ -208,8 +206,6 @@ export class OracleAccount extends Account<types.OracleAccountData> {
|
|||
params.authority ? [params.authority, tokenWallet] : [tokenWallet]
|
||||
);
|
||||
|
||||
txns.push(oracleInit);
|
||||
|
||||
if (params.stakeAmount && params.stakeAmount > 0) {
|
||||
const depositTxn = await oracleAccount.stakeInstructions(payer, {
|
||||
stakeAmount: params.stakeAmount,
|
||||
|
@ -217,15 +213,10 @@ export class OracleAccount extends Account<types.OracleAccountData> {
|
|||
funderTokenAccount: params.funderTokenAccount,
|
||||
tokenAccount: tokenWallet.publicKey,
|
||||
});
|
||||
txns.push(depositTxn);
|
||||
oracleInit.combine(depositTxn);
|
||||
}
|
||||
|
||||
const packed = TransactionObject.pack(txns);
|
||||
if (packed.length > 1) {
|
||||
throw new Error(`Expected a single TransactionObject`);
|
||||
}
|
||||
|
||||
return [oracleAccount, packed[0]];
|
||||
return [oracleAccount, oracleInit];
|
||||
}
|
||||
|
||||
public static async create(
|
||||
|
@ -542,6 +533,10 @@ export interface OracleInitParams {
|
|||
metadata?: string;
|
||||
/** Alternative keypair that will be the authority for the oracle. If not set the payer will be used. */
|
||||
authority?: Keypair;
|
||||
/**
|
||||
* Optional,
|
||||
*/
|
||||
stakingWalletKeypair?: Keypair;
|
||||
}
|
||||
|
||||
export interface OracleStakeParams {
|
||||
|
|
|
@ -6,6 +6,7 @@ import * as spl from '@solana/spl-token';
|
|||
import * as errors from '../errors';
|
||||
import { Mint } from '../mint';
|
||||
import {
|
||||
Keypair,
|
||||
PublicKey,
|
||||
SystemProgram,
|
||||
TransactionInstruction,
|
||||
|
@ -45,105 +46,102 @@ export class ProgramStateAccount extends Account<types.SbState> {
|
|||
*/
|
||||
static async getOrCreate(
|
||||
program: SwitchboardProgram,
|
||||
params: {
|
||||
mint?: PublicKey;
|
||||
daoMint?: PublicKey;
|
||||
mint: PublicKey = Mint.native
|
||||
): Promise<[ProgramStateAccount, number, TransactionSignature | undefined]> {
|
||||
const [account, bump, txn] =
|
||||
await ProgramStateAccount.getOrCreateInstructions(
|
||||
program,
|
||||
program.walletPubkey,
|
||||
mint
|
||||
);
|
||||
|
||||
if (txn) {
|
||||
const txnSignature = await program.signAndSend(txn);
|
||||
return [account, bump, txnSignature];
|
||||
}
|
||||
): Promise<[ProgramStateAccount, number]> {
|
||||
const payer = program.wallet.payer;
|
||||
|
||||
return [account, bump, undefined];
|
||||
}
|
||||
|
||||
static async getOrCreateInstructions(
|
||||
program: SwitchboardProgram,
|
||||
payer: PublicKey,
|
||||
mint: PublicKey = Mint.native
|
||||
): Promise<[ProgramStateAccount, number, TransactionObject | undefined]> {
|
||||
const [account, bump] = ProgramStateAccount.fromSeed(program);
|
||||
|
||||
try {
|
||||
await account.loadData();
|
||||
} catch (e) {
|
||||
const vaultKeypair = Keypair.generate();
|
||||
const ixns: TransactionInstruction[] = [];
|
||||
|
||||
// load the mint
|
||||
let splMint: spl.Mint;
|
||||
try {
|
||||
const [mint, vault]: [PublicKey, PublicKey] = await (async () => {
|
||||
if (params.mint === undefined) {
|
||||
const mint = await spl.createMint(
|
||||
program.connection,
|
||||
payer,
|
||||
payer.publicKey,
|
||||
null,
|
||||
9
|
||||
);
|
||||
const vault = await spl.createAccount(
|
||||
program.connection,
|
||||
payer,
|
||||
mint,
|
||||
anchor.web3.Keypair.generate().publicKey
|
||||
);
|
||||
await spl.mintTo(
|
||||
program.connection,
|
||||
payer,
|
||||
mint,
|
||||
vault,
|
||||
payer,
|
||||
100_000_000
|
||||
);
|
||||
return [mint, vault];
|
||||
} else {
|
||||
return [
|
||||
params.mint,
|
||||
await spl.createAccount(
|
||||
program.connection,
|
||||
payer,
|
||||
params.mint,
|
||||
payer.publicKey
|
||||
),
|
||||
];
|
||||
}
|
||||
})();
|
||||
|
||||
const programInit = new TransactionObject(
|
||||
program.walletPubkey,
|
||||
[
|
||||
types.programInit(
|
||||
program,
|
||||
{ params: { stateBump: bump } },
|
||||
{
|
||||
state: account.publicKey,
|
||||
authority: program.wallet.publicKey,
|
||||
payer: program.wallet.publicKey,
|
||||
tokenMint: mint,
|
||||
vault: vault,
|
||||
systemProgram: SystemProgram.programId,
|
||||
tokenProgram: spl.TOKEN_PROGRAM_ID,
|
||||
daoMint: params.daoMint ?? mint,
|
||||
}
|
||||
),
|
||||
],
|
||||
[]
|
||||
// try to load mint if it exists
|
||||
splMint = await spl.getMint(program.connection, mint);
|
||||
} catch {
|
||||
// create new mint
|
||||
const mintIxn = spl.createInitializeMintInstruction(
|
||||
mint,
|
||||
9,
|
||||
payer,
|
||||
payer
|
||||
);
|
||||
await program.signAndSend(programInit);
|
||||
} catch {} // eslint-disable-line no-empty
|
||||
}
|
||||
return [account, bump];
|
||||
}
|
||||
|
||||
static createAccountInstruction(
|
||||
program: SwitchboardProgram,
|
||||
mint = Mint.native,
|
||||
daoMint = Mint.native
|
||||
): [ProgramStateAccount, TransactionInstruction] {
|
||||
const [programStateAccount, stateBump] =
|
||||
ProgramStateAccount.fromSeed(program);
|
||||
|
||||
const vault = anchor.web3.Keypair.generate();
|
||||
|
||||
const ixn = types.programInit(
|
||||
program,
|
||||
{ params: { stateBump: stateBump } },
|
||||
{
|
||||
state: programStateAccount.publicKey,
|
||||
authority: program.wallet.publicKey,
|
||||
payer: program.wallet.publicKey,
|
||||
tokenMint: mint,
|
||||
vault: vault.publicKey,
|
||||
systemProgram: SystemProgram.programId,
|
||||
tokenProgram: spl.TOKEN_PROGRAM_ID,
|
||||
daoMint: daoMint ?? mint,
|
||||
ixns.push(mintIxn);
|
||||
splMint = {
|
||||
address: mint,
|
||||
mintAuthority: payer,
|
||||
supply: BigInt('100000000000000000'),
|
||||
decimals: 9,
|
||||
isInitialized: true,
|
||||
freezeAuthority: payer,
|
||||
tlvData: Buffer.from(''),
|
||||
};
|
||||
}
|
||||
);
|
||||
return [programStateAccount, ixn];
|
||||
|
||||
// create the vault
|
||||
const vaultInitIxn = spl.createInitializeAccountInstruction(
|
||||
vaultKeypair.publicKey,
|
||||
splMint.address,
|
||||
payer
|
||||
);
|
||||
ixns.push(vaultInitIxn);
|
||||
|
||||
if (splMint.mintAuthority?.equals(payer)) {
|
||||
ixns.push(
|
||||
spl.createMintToInstruction(
|
||||
splMint.address,
|
||||
vaultKeypair.publicKey,
|
||||
payer,
|
||||
BigInt('100000000000000000')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
ixns.push(
|
||||
types.programInit(
|
||||
program,
|
||||
{ params: { stateBump: bump } },
|
||||
{
|
||||
state: account.publicKey,
|
||||
authority: program.wallet.publicKey,
|
||||
payer: program.wallet.publicKey,
|
||||
tokenMint: splMint.address,
|
||||
vault: vaultKeypair.publicKey,
|
||||
systemProgram: SystemProgram.programId,
|
||||
tokenProgram: spl.TOKEN_PROGRAM_ID,
|
||||
daoMint: splMint.address,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const programInit = new TransactionObject(payer, ixns, []);
|
||||
|
||||
return [account, bump, programInit];
|
||||
}
|
||||
return [account, bump, undefined];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -29,7 +29,19 @@ import {
|
|||
SlidingResultAccountData,
|
||||
VrfAccountData,
|
||||
} from './generated';
|
||||
import { CrankAccount, DISCRIMINATOR_MAP, QueueAccount } from './accounts';
|
||||
import {
|
||||
CrankAccount,
|
||||
CrankInitParams,
|
||||
DISCRIMINATOR_MAP,
|
||||
OracleAccount,
|
||||
OracleInitParams,
|
||||
OracleStakeParams,
|
||||
PermissionAccount,
|
||||
PermissionSetParams,
|
||||
ProgramStateAccount,
|
||||
QueueAccount,
|
||||
QueueInitParams,
|
||||
} from './accounts';
|
||||
import {
|
||||
SWITCHBOARD_LABS_DEVNET_PERMISSIONED_CRANK,
|
||||
SWITCHBOARD_LABS_DEVNET_PERMISSIONED_QUEUE,
|
||||
|
@ -41,7 +53,6 @@ import {
|
|||
SWITCHBOARD_LABS_MAINNET_PERMISSIONLESS_QUEUE,
|
||||
} from './const';
|
||||
import { types } from '.';
|
||||
|
||||
/**
|
||||
* Switchboard Devnet Program ID
|
||||
*/
|
||||
|
@ -691,6 +702,115 @@ export class SwitchboardProgram {
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
async createNetworkInstructions(
|
||||
payer: PublicKey,
|
||||
params: QueueInitParams & {
|
||||
cranks?: Array<Omit<CrankInitParams, 'queueAccount'>>;
|
||||
oracles?: Array<
|
||||
OracleInitParams &
|
||||
OracleStakeParams &
|
||||
Partial<PermissionSetParams> & {
|
||||
queueAuthorityPubkey?: PublicKey;
|
||||
}
|
||||
>;
|
||||
}
|
||||
): Promise<[Array<TransactionObject>, NetworkInitResponse]> {
|
||||
const txns: TransactionObject[] = [];
|
||||
|
||||
// get or create the program state
|
||||
const [programState, stateBump, programInit] =
|
||||
await ProgramStateAccount.getOrCreateInstructions(
|
||||
this,
|
||||
this.walletPubkey
|
||||
);
|
||||
if (programInit) {
|
||||
txns.push(programInit);
|
||||
}
|
||||
|
||||
// create a new queue
|
||||
const [queueAccount, queueInit] = await QueueAccount.createInstructions(
|
||||
this,
|
||||
this.walletPubkey,
|
||||
params
|
||||
);
|
||||
txns.push(queueInit);
|
||||
|
||||
const cranks: Array<[CrankAccount, TransactionObject]> = await Promise.all(
|
||||
(params.cranks ?? []).map(async crankInitParams => {
|
||||
return await queueAccount.createCrankInstructions(
|
||||
payer,
|
||||
crankInitParams
|
||||
);
|
||||
})
|
||||
);
|
||||
txns.push(...cranks.map(crank => crank[1]));
|
||||
|
||||
const oracles: Array<
|
||||
[TransactionObject[], OracleAccount, PermissionAccount, number]
|
||||
> = await Promise.all(
|
||||
(params.oracles ?? []).map(async oracleInitParams => {
|
||||
const [oracleAccount, oracleInit] =
|
||||
await queueAccount.createOracleInstructions(payer, oracleInitParams);
|
||||
|
||||
const [oraclePermissionAccount, oraclePermissionBump] =
|
||||
PermissionAccount.fromSeed(
|
||||
this,
|
||||
this.walletPubkey,
|
||||
queueAccount.publicKey,
|
||||
oracleAccount.publicKey
|
||||
);
|
||||
|
||||
return [
|
||||
oracleInit,
|
||||
oracleAccount,
|
||||
oraclePermissionAccount,
|
||||
oraclePermissionBump,
|
||||
];
|
||||
})
|
||||
);
|
||||
txns.push(...oracles.map(oracle => oracle[0]).flat());
|
||||
|
||||
const accounts: NetworkInitResponse = {
|
||||
programState: {
|
||||
account: programState,
|
||||
bump: stateBump,
|
||||
},
|
||||
queueAccount,
|
||||
cranks: cranks.map(c => c[0]),
|
||||
oracles: oracles.map(o => {
|
||||
return {
|
||||
account: o[1],
|
||||
permissions: {
|
||||
account: o[2],
|
||||
bump: o[3],
|
||||
},
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
return [TransactionObject.pack(txns), accounts];
|
||||
}
|
||||
|
||||
async createNetwork(
|
||||
params: QueueInitParams & {
|
||||
cranks?: Array<Omit<CrankInitParams, 'queueAccount'>>;
|
||||
oracles?: Array<
|
||||
OracleInitParams &
|
||||
OracleStakeParams &
|
||||
Partial<PermissionSetParams> & {
|
||||
queueAuthorityPubkey?: PublicKey;
|
||||
}
|
||||
>;
|
||||
}
|
||||
): Promise<[NetworkInitResponse, Array<TransactionSignature>]> {
|
||||
const [networkInit, accounts] = await this.createNetworkInstructions(
|
||||
this.walletPubkey,
|
||||
params
|
||||
);
|
||||
const txnSignatures = await this.signAndSendAll(networkInit);
|
||||
return [accounts, txnSignatures];
|
||||
}
|
||||
}
|
||||
|
||||
export class AnchorWallet implements anchor.Wallet {
|
||||
|
@ -716,3 +836,19 @@ interface AccountInfoResponse {
|
|||
pubkey: anchor.web3.PublicKey;
|
||||
account: anchor.web3.AccountInfo<Buffer>;
|
||||
}
|
||||
|
||||
export interface NetworkInitResponse {
|
||||
programState: {
|
||||
account: ProgramStateAccount;
|
||||
bump: number;
|
||||
};
|
||||
queueAccount: QueueAccount;
|
||||
cranks: Array<CrankAccount>;
|
||||
oracles: Array<{
|
||||
account: OracleAccount;
|
||||
permissions: {
|
||||
account: PermissionAccount;
|
||||
bump: number;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
|
|
@ -1,484 +0,0 @@
|
|||
// import * as anchor from '@project-serum/anchor';
|
||||
// import { clusterApiUrl, Connection, Keypair, PublicKey } from '@solana/web3.js';
|
||||
// import {
|
||||
// AggregatorAccount,
|
||||
// CrankAccount,
|
||||
// OracleAccount,
|
||||
// PermissionAccount,
|
||||
// ProgramStateAccount,
|
||||
// QueueAccount,
|
||||
// } from './accounts';
|
||||
// import {
|
||||
// AnchorWallet,
|
||||
// getSwitchboardProgramId,
|
||||
// SwitchboardProgram,
|
||||
// } from './program';
|
||||
// import fs from 'fs';
|
||||
// import path from 'path';
|
||||
|
||||
// export const LATEST_DOCKER_VERSION = '';
|
||||
|
||||
// export class SwitchboardTestContext {
|
||||
// constructor(
|
||||
// readonly program: SwitchboardProgram,
|
||||
// readonly queue: QueueAccount
|
||||
// ) {}
|
||||
|
||||
// public async isReady(): Promise<boolean> {
|
||||
// return await this.queue.isReady();
|
||||
// }
|
||||
|
||||
// // public async awaitOracleHeartbeat(timeout = 30): Promise<void> {
|
||||
// // throw new Error(`Not implemented yet`);
|
||||
// // }
|
||||
|
||||
// // static async create(
|
||||
// // program: SwitchboardProgram
|
||||
// // ): Promise<SwitchboardTestContext> {
|
||||
// // // Create queue
|
||||
// // // Create oracle
|
||||
// // // Create token accounts
|
||||
// // }
|
||||
|
||||
// static async load(): Promise<SwitchboardTestContext> {
|
||||
// throw new Error(`Not implemented yet`);
|
||||
// }
|
||||
|
||||
// public static findSwitchboardEnv(envFileName = 'switchboard.env'): string {
|
||||
// throw new Error(`Not implemented yet`);
|
||||
// }
|
||||
|
||||
// public async createStaticFeed(
|
||||
// value: number,
|
||||
// timeout = 30
|
||||
// ): Promise<AggregatorAccount> {
|
||||
// throw new Error(`Not implemented yet`);
|
||||
// }
|
||||
|
||||
// public async updateStaticFeed(
|
||||
// aggregatorAccount: AggregatorAccount,
|
||||
// value: number,
|
||||
// timeout = 30
|
||||
// ): Promise<AggregatorAccount> {
|
||||
// throw new Error(`Not implemented yet`);
|
||||
// }
|
||||
// }
|
||||
|
||||
// export interface ISwitchboardTestEnvironment {
|
||||
// programId: PublicKey;
|
||||
// programDataAddress: PublicKey;
|
||||
// idlAddress: PublicKey;
|
||||
// programState: PublicKey;
|
||||
// switchboardVault: PublicKey;
|
||||
// switchboardMint: PublicKey;
|
||||
// tokenWallet: PublicKey;
|
||||
// queue: PublicKey;
|
||||
// queueAuthority: PublicKey;
|
||||
// queueBuffer: PublicKey;
|
||||
// crank: PublicKey;
|
||||
// crankBuffer: PublicKey;
|
||||
// oracle: PublicKey;
|
||||
// oracleAuthority: PublicKey;
|
||||
// oracleEscrow: PublicKey;
|
||||
// oraclePermissions: PublicKey;
|
||||
// payerKeypairPath: string;
|
||||
// }
|
||||
// export class SwitchboardTestEnvironment implements ISwitchboardTestEnvironment {
|
||||
// programId: PublicKey;
|
||||
// programDataAddress: PublicKey;
|
||||
// idlAddress: PublicKey;
|
||||
// programState: PublicKey;
|
||||
// switchboardVault: PublicKey;
|
||||
// switchboardMint: PublicKey;
|
||||
// tokenWallet: PublicKey;
|
||||
// queue: PublicKey;
|
||||
// queueAuthority: PublicKey;
|
||||
// queueBuffer: PublicKey;
|
||||
// crank: PublicKey;
|
||||
// crankBuffer: PublicKey;
|
||||
// oracle: PublicKey;
|
||||
// oracleAuthority: PublicKey;
|
||||
// oracleEscrow: PublicKey;
|
||||
// oraclePermissions: PublicKey;
|
||||
// payerKeypairPath: string;
|
||||
|
||||
// constructor(ctx: ISwitchboardTestEnvironment) {
|
||||
// this.programId = ctx.programId;
|
||||
// this.programDataAddress = ctx.programDataAddress;
|
||||
// this.idlAddress = ctx.idlAddress;
|
||||
// this.programState = ctx.programState;
|
||||
// this.switchboardVault = ctx.switchboardVault;
|
||||
// this.switchboardMint = ctx.switchboardMint;
|
||||
// this.tokenWallet = ctx.tokenWallet;
|
||||
// this.queue = ctx.queue;
|
||||
// this.queueAuthority = ctx.queueAuthority;
|
||||
// this.queueBuffer = ctx.queueBuffer;
|
||||
// this.crank = ctx.crank;
|
||||
// this.crankBuffer = ctx.crankBuffer;
|
||||
// this.oracle = ctx.oracle;
|
||||
// this.oracleAuthority = ctx.oracleAuthority;
|
||||
// this.oracleEscrow = ctx.oracleEscrow;
|
||||
// this.oraclePermissions = ctx.oraclePermissions;
|
||||
// this.payerKeypairPath = ctx.payerKeypairPath;
|
||||
// }
|
||||
|
||||
// private getAccountCloneString(): string {
|
||||
// const accounts = Object.keys(this).map(key => {
|
||||
// // iterate over additionalClonedAccounts and collect pubkeys
|
||||
// if (typeof this[key] === 'string') {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// return `--clone ${(this[key] as PublicKey).toBase58()} \`# ${key}\` `;
|
||||
// });
|
||||
|
||||
// return accounts.filter(Boolean).join(`\\\n`);
|
||||
// }
|
||||
|
||||
// public toJSON(): ISwitchboardTestEnvironment {
|
||||
// return {
|
||||
// programId: this.programId,
|
||||
// programDataAddress: this.programDataAddress,
|
||||
// idlAddress: this.idlAddress,
|
||||
// programState: this.programState,
|
||||
// switchboardVault: this.switchboardVault,
|
||||
// switchboardMint: this.switchboardMint,
|
||||
// tokenWallet: this.tokenWallet,
|
||||
// queue: this.queue,
|
||||
// queueAuthority: this.queueAuthority,
|
||||
// queueBuffer: this.queueBuffer,
|
||||
// crank: this.crank,
|
||||
// crankBuffer: this.crankBuffer,
|
||||
// oracle: this.oracle,
|
||||
// oracleAuthority: this.oracleAuthority,
|
||||
// oracleEscrow: this.oracleEscrow,
|
||||
// oraclePermissions: this.oraclePermissions,
|
||||
// payerKeypairPath: this.payerKeypairPath,
|
||||
// };
|
||||
// }
|
||||
|
||||
// /** Write switchboard test environment to filesystem */
|
||||
// public writeAll(outputDir: string): void {
|
||||
// fs.mkdirSync(outputDir, { recursive: true });
|
||||
// this.writeEnv(outputDir);
|
||||
// this.writeJSON(outputDir);
|
||||
// this.writeScripts(outputDir);
|
||||
// this.writeDockerCompose(outputDir);
|
||||
// this.writeAnchorToml(outputDir);
|
||||
// }
|
||||
|
||||
// /** Write the env file to filesystem */
|
||||
// public writeEnv(filePath: string): void {
|
||||
// const ENV_FILE_PATH = path.join(filePath, 'switchboard.env');
|
||||
// let fileStr = '';
|
||||
// fileStr += `SWITCHBOARD_PROGRAM_ID="${this.programId.toBase58()}"\n`;
|
||||
// fileStr += `SWITCHBOARD_PROGRAM_DATA_ADDRESS="${this.programDataAddress.toBase58()}"\n`;
|
||||
// fileStr += `SWITCHBOARD_IDL_ADDRESS="${this.idlAddress.toBase58()}"\n`;
|
||||
// fileStr += `SWITCHBOARD_PROGRAM_STATE="${this.programState.toBase58()}"\n`;
|
||||
// fileStr += `SWITCHBOARD_VAULT="${this.switchboardVault.toBase58()}"\n`;
|
||||
// fileStr += `SWITCHBOARD_MINT="${this.switchboardMint.toBase58()}"\n`;
|
||||
// fileStr += `TOKEN_WALLET="${this.tokenWallet.toBase58()}"\n`;
|
||||
// fileStr += `ORACLE_QUEUE="${this.queue.toBase58()}"\n`;
|
||||
// fileStr += `ORACLE_QUEUE_AUTHORITY="${this.queueAuthority.toBase58()}"\n`;
|
||||
// fileStr += `ORACLE_QUEUE_BUFFER="${this.queueBuffer.toBase58()}"\n`;
|
||||
// fileStr += `CRANK="${this.crank.toBase58()}"\n`;
|
||||
// fileStr += `CRANK_BUFFER="${this.crankBuffer.toBase58()}"\n`;
|
||||
// fileStr += `ORACLE="${this.oracle.toBase58()}"\n`;
|
||||
// fileStr += `ORACLE_AUTHORITY="${this.oracleAuthority.toBase58()}"\n`;
|
||||
// fileStr += `ORACLE_ESCROW="${this.oracleEscrow.toBase58()}"\n`;
|
||||
// fileStr += `ORACLE_PERMISSIONS="${this.oraclePermissions.toBase58()}"\n`;
|
||||
// // fileStr += `SWITCHBOARD_ACCOUNTS="${this.getAccountCloneString()}"\n`;
|
||||
// fs.writeFileSync(ENV_FILE_PATH, fileStr);
|
||||
// // console.log(
|
||||
// // `${chalk.green('Env File saved to:')} ${ENV_FILE_PATH.replace(
|
||||
// // process.cwd(),
|
||||
// // '.'
|
||||
// // )}`
|
||||
// // );
|
||||
// }
|
||||
|
||||
// public writeJSON(outputDir: string): void {
|
||||
// const JSON_FILE_PATH = path.join(outputDir, 'switchboard.json');
|
||||
// fs.writeFileSync(
|
||||
// JSON_FILE_PATH,
|
||||
// JSON.stringify(
|
||||
// this.toJSON(),
|
||||
// (key, value) => {
|
||||
// if (value instanceof PublicKey) {
|
||||
// return value.toBase58();
|
||||
// }
|
||||
// return value;
|
||||
// },
|
||||
// 2
|
||||
// )
|
||||
// );
|
||||
// }
|
||||
|
||||
// public writeScripts(outputDir: string): void {
|
||||
// const LOCAL_VALIDATOR_SCRIPT = path.join(
|
||||
// outputDir,
|
||||
// 'start-local-validator.sh'
|
||||
// );
|
||||
// // create bash script to startup local validator with appropriate accounts cloned
|
||||
// const baseValidatorCommand = `solana-test-validator -r --ledger .anchor/test-ledger --mint ${this.oracleAuthority.toBase58()} --bind-address 0.0.0.0 --url ${clusterApiUrl(
|
||||
// 'devnet'
|
||||
// )} --rpc-port 8899 `;
|
||||
// const cloneAccountsString = this.getAccountCloneString();
|
||||
// const startValidatorCommand = `${baseValidatorCommand} ${cloneAccountsString}`;
|
||||
// fs.writeFileSync(
|
||||
// LOCAL_VALIDATOR_SCRIPT,
|
||||
|
||||
// `#!/bin/bash\n\nmkdir -p .anchor/test-ledger\n\n${startValidatorCommand}`
|
||||
// );
|
||||
// fs.chmodSync(LOCAL_VALIDATOR_SCRIPT, '755');
|
||||
// // console.log(
|
||||
// // `${chalk.green('Bash script saved to:')} ${LOCAL_VALIDATOR_SCRIPT.replace(
|
||||
// // process.cwd(),
|
||||
// // '.'
|
||||
// // )}`
|
||||
// // );
|
||||
|
||||
// // create bash script to start local oracle
|
||||
// const ORACLE_SCRIPT = path.join(outputDir, 'start-oracle.sh');
|
||||
// // const startOracleCommand = `docker-compose -f docker-compose.switchboard.yml up`;
|
||||
// fs.writeFileSync(
|
||||
// ORACLE_SCRIPT,
|
||||
// `#!/usr/bin/env bash
|
||||
|
||||
// script_dir=$( cd -- "$( dirname -- "\${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
|
||||
// docker-compose -f "$script_dir"/docker-compose.switchboard.yml up
|
||||
// `
|
||||
// // `#!/bin/bash\n\n${startOracleCommand}`
|
||||
// );
|
||||
// fs.chmodSync(ORACLE_SCRIPT, '755');
|
||||
// // console.log(
|
||||
// // `${chalk.green('Bash script saved to:')} ${ORACLE_SCRIPT.replace(
|
||||
// // process.cwd(),
|
||||
// // '.'
|
||||
// // )}`
|
||||
// // );
|
||||
// }
|
||||
|
||||
// public writeDockerCompose(outputDir: string): void {
|
||||
// const DOCKER_COMPOSE_FILEPATH = path.join(
|
||||
// outputDir,
|
||||
// 'docker-compose.switchboard.yml'
|
||||
// );
|
||||
// const dockerComposeString = `version: "3.3"
|
||||
// services:
|
||||
// oracle:
|
||||
// image: "switchboardlabs/node:\${SBV2_ORACLE_VERSION:-${LATEST_DOCKER_VERSION}}" # https://hub.docker.com/r/switchboardlabs/node/tags
|
||||
// network_mode: host
|
||||
// restart: always
|
||||
// secrets:
|
||||
// - PAYER_SECRETS
|
||||
// environment:
|
||||
// - VERBOSE=1
|
||||
// - LIVE=1
|
||||
// - CLUSTER=\${CLUSTER:-localnet}
|
||||
// - HEARTBEAT_INTERVAL=30 # Seconds
|
||||
// - ORACLE_KEY=${this.oracle.toBase58()}
|
||||
// # - RPC_URL=\${RPC_URL}
|
||||
// secrets:
|
||||
// PAYER_SECRETS:
|
||||
// file: ${this.payerKeypairPath}
|
||||
// `;
|
||||
// fs.writeFileSync(DOCKER_COMPOSE_FILEPATH, dockerComposeString);
|
||||
// // console.log(
|
||||
// // `${chalk.green(
|
||||
// // 'Docker-Compose saved to:'
|
||||
// // )} ${DOCKER_COMPOSE_FILEPATH.replace(process.cwd(), '.')}`
|
||||
// // );
|
||||
// }
|
||||
|
||||
// public writeAnchorToml(outputDir: string): void {
|
||||
// const ANCHOR_TOML_FILEPATH = path.join(
|
||||
// outputDir,
|
||||
// 'Anchor.switchboard.toml'
|
||||
// );
|
||||
// const anchorTomlString = `[provider]
|
||||
// cluster = "localnet"
|
||||
// wallet = "${this.payerKeypairPath}"
|
||||
|
||||
// [test]
|
||||
// startup_wait = 10000
|
||||
|
||||
// [test.validator]
|
||||
// url = "https://api.devnet.solana.com"
|
||||
|
||||
// [[test.validator.clone]] # programID
|
||||
// address = "${this.programId}"
|
||||
|
||||
// [[test.validator.clone]] # idlAddress
|
||||
// address = "${this.idlAddress}"
|
||||
|
||||
// [[test.validator.clone]] # programState
|
||||
// address = "${this.programState}"
|
||||
|
||||
// [[test.validator.clone]] # switchboardVault
|
||||
// address = "${this.switchboardVault}"
|
||||
|
||||
// [[test.validator.clone]] # tokenWallet
|
||||
// address = "${this.tokenWallet}"
|
||||
|
||||
// [[test.validator.clone]] # queue
|
||||
// address = "${this.queue}"
|
||||
|
||||
// [[test.validator.clone]] # queueAuthority
|
||||
// address = "${this.queueAuthority}"
|
||||
|
||||
// [[test.validator.clone]] # queueBuffer
|
||||
// address = "${this.queueBuffer}"
|
||||
|
||||
// [[test.validator.clone]] # crank
|
||||
// address = "${this.crank}"
|
||||
|
||||
// [[test.validator.clone]] # crankBuffer
|
||||
// address = "${this.crankBuffer}"
|
||||
|
||||
// [[test.validator.clone]] # oracle
|
||||
// address = "${this.oracle}"
|
||||
|
||||
// [[test.validator.clone]] # oracleAuthority
|
||||
// address = "${this.oracleAuthority}"
|
||||
|
||||
// [[test.validator.clone]] # oracleEscrow
|
||||
// address = "${this.oracleEscrow}"
|
||||
|
||||
// [[test.validator.clone]] # oraclePermissions
|
||||
// address = "${this.oraclePermissions}"
|
||||
// `;
|
||||
|
||||
// fs.writeFileSync(ANCHOR_TOML_FILEPATH, anchorTomlString);
|
||||
// // console.log(
|
||||
// // `${chalk.green('Anchor.toml saved to:')} ${ANCHOR_TOML_FILEPATH.replace(
|
||||
// // process.cwd(),
|
||||
// // '.'
|
||||
// // )}`
|
||||
// // );
|
||||
// }
|
||||
|
||||
// /** Build a devnet environment to later clone to localnet */
|
||||
// static async create(
|
||||
// payerKeypairPath: string,
|
||||
// additionalClonedAccounts?: Record<string, PublicKey>,
|
||||
// alternateProgramId?: PublicKey
|
||||
// ): Promise<SwitchboardTestEnvironment> {
|
||||
// const fullKeypairPath =
|
||||
// payerKeypairPath.charAt(0) === '/'
|
||||
// ? payerKeypairPath
|
||||
// : path.join(process.cwd(), payerKeypairPath);
|
||||
// if (!fs.existsSync(fullKeypairPath)) {
|
||||
// throw new Error('Failed to find payer keypair path');
|
||||
// }
|
||||
// const payerKeypair = Keypair.fromSecretKey(
|
||||
// new Uint8Array(
|
||||
// JSON.parse(
|
||||
// fs.readFileSync(fullKeypairPath, {
|
||||
// encoding: 'utf-8',
|
||||
// })
|
||||
// )
|
||||
// )
|
||||
// );
|
||||
// const connection = new Connection(clusterApiUrl('devnet'), {
|
||||
// commitment: 'confirmed',
|
||||
// });
|
||||
|
||||
// const programId = alternateProgramId ?? getSwitchboardProgramId('devnet');
|
||||
// const wallet = new AnchorWallet(payerKeypair);
|
||||
// const provider = new anchor.AnchorProvider(connection, wallet, {});
|
||||
|
||||
// const anchorIdl = await anchor.Program.fetchIdl(programId, provider);
|
||||
// if (!anchorIdl) {
|
||||
// throw new Error(`failed to read idl for ${programId}`);
|
||||
// }
|
||||
|
||||
// const switchboardProgram = new anchor.Program(
|
||||
// anchorIdl,
|
||||
// programId,
|
||||
// provider
|
||||
// );
|
||||
|
||||
// const programDataAddress = getProgramDataAddress(
|
||||
// switchboardProgram.programId
|
||||
// );
|
||||
// const idlAddress = await getIdlAddress(switchboardProgram.programId);
|
||||
|
||||
// const queueResponse = await createQueue(
|
||||
// switchboardProgram,
|
||||
// {
|
||||
// authority: payerKeypair.publicKey,
|
||||
// name: 'Test Queue',
|
||||
// metadata: `created ${anchorBNtoDateString(
|
||||
// new anchor.BN(Math.floor(Date.now() / 1000))
|
||||
// )}`,
|
||||
// minStake: new anchor.BN(0),
|
||||
// reward: new anchor.BN(0),
|
||||
// crankSize: 10,
|
||||
// oracleTimeout: 180,
|
||||
// numOracles: 1,
|
||||
// unpermissionedFeeds: true,
|
||||
// unpermissionedVrf: true,
|
||||
// enableBufferRelayers: true,
|
||||
// },
|
||||
// 10
|
||||
// );
|
||||
|
||||
// const queueAccount = queueResponse.queueAccount;
|
||||
// const queue = await queueAccount.loadData();
|
||||
|
||||
// const [programStateAccount, stateBump] =
|
||||
// ProgramStateAccount.fromSeed(switchboardProgram);
|
||||
// const programState = await programStateAccount.loadData();
|
||||
|
||||
// const mint = await queueAccount.loadMint();
|
||||
|
||||
// const payerSwitchboardWallet = await getOrCreateSwitchboardTokenAccount(
|
||||
// switchboardProgram,
|
||||
// mint
|
||||
// );
|
||||
|
||||
// const crankAccount = new CrankAccount(
|
||||
// switchboardProgram,
|
||||
// queueResponse.crankPubkey
|
||||
// );
|
||||
// const crank = await crankAccount.loadData();
|
||||
|
||||
// const oracleAccount = new OracleAccount(
|
||||
// switchboardProgram,
|
||||
// queueResponse.oracles[0]
|
||||
// );
|
||||
// const oracle = await oracleAccount.loadData();
|
||||
|
||||
// const [permissionAccount] = PermissionAccount.fromSeed(
|
||||
// switchboardProgram,
|
||||
// queue.authority,
|
||||
// queueAccount.publicKey,
|
||||
// oracleAccount.publicKey
|
||||
// );
|
||||
// const permission = await permissionAccount.loadData();
|
||||
|
||||
// const ctx: ISwitchboardTestEnvironment = {
|
||||
// programId: switchboardProgram.programId,
|
||||
// programDataAddress,
|
||||
// idlAddress,
|
||||
// programState: programStateAccount.publicKey,
|
||||
// switchboardVault: programState.tokenVault,
|
||||
// switchboardMint: mint.address,
|
||||
// tokenWallet: payerSwitchboardWallet,
|
||||
// queue: queueResponse.queueAccount.publicKey,
|
||||
// queueAuthority: queue.authority,
|
||||
// queueBuffer: queue.dataBuffer,
|
||||
// crank: crankAccount.publicKey,
|
||||
// crankBuffer: crank.dataBuffer,
|
||||
// oracle: oracleAccount.publicKey,
|
||||
// oracleAuthority: oracle.oracleAuthority,
|
||||
// oracleEscrow: oracle.tokenAccount,
|
||||
// oraclePermissions: permissionAccount.publicKey,
|
||||
// payerKeypairPath: fullKeypairPath,
|
||||
// };
|
||||
|
||||
// return new SwitchboardTestEnvironment(ctx);
|
||||
// }
|
||||
// }
|
|
@ -0,0 +1,413 @@
|
|||
import * as anchor from '@project-serum/anchor';
|
||||
import { clusterApiUrl, Connection, Keypair, PublicKey } from '@solana/web3.js';
|
||||
import { QueueAccount } from '../accounts';
|
||||
import { SwitchboardProgram } from '../program';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { BNtoDateTimeString } from '@switchboard-xyz/common';
|
||||
import { Mint } from '../mint';
|
||||
|
||||
export const LATEST_DOCKER_VERSION = 'dev-v2-RC_12_05_22_22_48';
|
||||
|
||||
/** Get the program data address for a given programId
|
||||
* @param programId the programId for a given on-chain program
|
||||
* @return the publicKey of the address holding the upgradeable program buffer
|
||||
*/
|
||||
export const getProgramDataAddress = (programId: PublicKey): PublicKey => {
|
||||
return anchor.utils.publicKey.findProgramAddressSync(
|
||||
[programId.toBytes()],
|
||||
new PublicKey('BPFLoaderUpgradeab1e11111111111111111111111')
|
||||
)[0];
|
||||
};
|
||||
|
||||
/** Get the IDL address for a given programId
|
||||
* @param programId the programId for a given on-chain program
|
||||
* @return the publicKey of the IDL address
|
||||
*/
|
||||
export const getIdlAddress = async (
|
||||
programId: PublicKey
|
||||
): Promise<PublicKey> => {
|
||||
const base = (await PublicKey.findProgramAddress([], programId))[0];
|
||||
return PublicKey.createWithSeed(base, 'anchor:idl', programId);
|
||||
};
|
||||
|
||||
export class SwitchboardTestContext {
|
||||
constructor(
|
||||
readonly program: SwitchboardProgram,
|
||||
readonly queue: QueueAccount
|
||||
) {}
|
||||
|
||||
// static async load(): Promise<SwitchboardTestContext> {
|
||||
// throw new Error(`Not implemented yet`);
|
||||
// }
|
||||
|
||||
// public static findSwitchboardEnv(envFileName = 'switchboard.env'): string {
|
||||
// throw new Error(`Not implemented yet`);
|
||||
// }
|
||||
|
||||
// public async createStaticFeed(
|
||||
// value: number,
|
||||
// timeout = 30
|
||||
// ): Promise<AggregatorAccount> {
|
||||
// throw new Error(`Not implemented yet`);
|
||||
// }
|
||||
|
||||
// public async updateStaticFeed(
|
||||
// aggregatorAccount: AggregatorAccount,
|
||||
// value: number,
|
||||
// timeout = 30
|
||||
// ): Promise<AggregatorAccount> {
|
||||
// throw new Error(`Not implemented yet`);
|
||||
// }
|
||||
|
||||
static async createEnvironment(
|
||||
payerKeypairPath: string,
|
||||
alternateProgramId?: PublicKey
|
||||
): Promise<SwitchboardTestEnvironment> {
|
||||
const fullKeypairPath =
|
||||
payerKeypairPath.startsWith('/') || payerKeypairPath.startsWith('C:')
|
||||
? payerKeypairPath
|
||||
: path.join(process.cwd(), payerKeypairPath);
|
||||
if (!fs.existsSync(fullKeypairPath)) {
|
||||
throw new Error('Failed to find payer keypair path');
|
||||
}
|
||||
const payerKeypair = Keypair.fromSecretKey(
|
||||
new Uint8Array(
|
||||
JSON.parse(
|
||||
fs.readFileSync(fullKeypairPath, {
|
||||
encoding: 'utf-8',
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const connection = new Connection(clusterApiUrl('devnet'), {
|
||||
commitment: 'confirmed',
|
||||
});
|
||||
|
||||
const program = await SwitchboardProgram.load(
|
||||
'devnet',
|
||||
connection,
|
||||
payerKeypair,
|
||||
alternateProgramId
|
||||
);
|
||||
|
||||
const [userTokenWallet, userTokenInit] =
|
||||
await program.mint.getOrCreateWrappedUserInstructions(
|
||||
program.walletPubkey,
|
||||
{ fundUpTo: 1 }
|
||||
);
|
||||
await program.signAndSend(userTokenInit);
|
||||
|
||||
const programDataAddress = getProgramDataAddress(program.programId);
|
||||
const idlAddress = await getIdlAddress(program.programId);
|
||||
|
||||
// use pre-generated keypairs so we dont need to rely on account loading
|
||||
const dataBufferKeypair = Keypair.generate();
|
||||
const crankBufferKeypair = Keypair.generate();
|
||||
const oracleStakingWalletKeypair = Keypair.generate();
|
||||
|
||||
const [accounts] = await program.createNetwork({
|
||||
name: 'Test Queue',
|
||||
metadata: `created ${BNtoDateTimeString(
|
||||
new anchor.BN(Math.floor(Date.now() / 1000))
|
||||
)}`,
|
||||
reward: 0,
|
||||
minStake: 0,
|
||||
queueSize: 10,
|
||||
dataBufferKeypair: dataBufferKeypair,
|
||||
cranks: [
|
||||
{
|
||||
name: 'Test Crank',
|
||||
maxRows: 100,
|
||||
dataBufferKeypair: crankBufferKeypair,
|
||||
},
|
||||
],
|
||||
oracles: [
|
||||
{
|
||||
name: 'Test Oracle',
|
||||
enable: true,
|
||||
stakingWalletKeypair: oracleStakingWalletKeypair,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const crank = accounts.cranks.shift();
|
||||
if (!crank) {
|
||||
throw new Error(`Failed to create the crank`);
|
||||
}
|
||||
|
||||
const oracle = accounts.oracles.shift();
|
||||
if (!oracle) {
|
||||
throw new Error(`Failed to create the oracle`);
|
||||
}
|
||||
|
||||
// async load the accounts
|
||||
const programState = await accounts.programState.account.loadData();
|
||||
|
||||
return new SwitchboardTestEnvironment({
|
||||
programId: program.programId,
|
||||
programDataAddress: programDataAddress,
|
||||
idlAddress: idlAddress,
|
||||
programState: accounts.programState.account.publicKey,
|
||||
switchboardVault: programState.tokenVault,
|
||||
switchboardMint: programState.tokenMint.equals(PublicKey.default)
|
||||
? Mint.native
|
||||
: programState.tokenMint,
|
||||
tokenWallet: userTokenWallet,
|
||||
queue: accounts.queueAccount.publicKey,
|
||||
queueAuthority: program.walletPubkey,
|
||||
queueBuffer: dataBufferKeypair.publicKey,
|
||||
crank: crank.publicKey,
|
||||
crankBuffer: crankBufferKeypair.publicKey,
|
||||
oracle: oracle.account.publicKey,
|
||||
oracleAuthority: program.walletPubkey,
|
||||
oracleEscrow: oracleStakingWalletKeypair.publicKey,
|
||||
oraclePermissions: oracle.permissions.account.publicKey,
|
||||
payerKeypairPath: fullKeypairPath,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export interface ISwitchboardTestEnvironment {
|
||||
programId: PublicKey;
|
||||
programDataAddress: PublicKey;
|
||||
idlAddress: PublicKey;
|
||||
programState: PublicKey;
|
||||
switchboardVault: PublicKey;
|
||||
switchboardMint: PublicKey;
|
||||
tokenWallet: PublicKey;
|
||||
queue: PublicKey;
|
||||
queueAuthority: PublicKey;
|
||||
queueBuffer: PublicKey;
|
||||
crank: PublicKey;
|
||||
crankBuffer: PublicKey;
|
||||
oracle: PublicKey;
|
||||
oracleAuthority: PublicKey;
|
||||
oracleEscrow: PublicKey;
|
||||
oraclePermissions: PublicKey;
|
||||
payerKeypairPath: string;
|
||||
}
|
||||
|
||||
export class SwitchboardTestEnvironment implements ISwitchboardTestEnvironment {
|
||||
programId: PublicKey;
|
||||
programDataAddress: PublicKey;
|
||||
idlAddress: PublicKey;
|
||||
programState: PublicKey;
|
||||
switchboardVault: PublicKey;
|
||||
switchboardMint: PublicKey;
|
||||
tokenWallet: PublicKey;
|
||||
queue: PublicKey;
|
||||
queueAuthority: PublicKey;
|
||||
queueBuffer: PublicKey;
|
||||
crank: PublicKey;
|
||||
crankBuffer: PublicKey;
|
||||
oracle: PublicKey;
|
||||
oracleAuthority: PublicKey;
|
||||
oracleEscrow: PublicKey;
|
||||
oraclePermissions: PublicKey;
|
||||
payerKeypairPath: string;
|
||||
|
||||
constructor(ctx: ISwitchboardTestEnvironment) {
|
||||
this.programId = ctx.programId;
|
||||
this.programDataAddress = ctx.programDataAddress;
|
||||
this.idlAddress = ctx.idlAddress;
|
||||
this.programState = ctx.programState;
|
||||
this.switchboardVault = ctx.switchboardVault;
|
||||
this.switchboardMint = ctx.switchboardMint;
|
||||
this.tokenWallet = ctx.tokenWallet;
|
||||
this.queue = ctx.queue;
|
||||
this.queueAuthority = ctx.queueAuthority;
|
||||
this.queueBuffer = ctx.queueBuffer;
|
||||
this.crank = ctx.crank;
|
||||
this.crankBuffer = ctx.crankBuffer;
|
||||
this.oracle = ctx.oracle;
|
||||
this.oracleAuthority = ctx.oracleAuthority;
|
||||
this.oracleEscrow = ctx.oracleEscrow;
|
||||
this.oraclePermissions = ctx.oraclePermissions;
|
||||
this.payerKeypairPath = ctx.payerKeypairPath;
|
||||
}
|
||||
|
||||
public get envFileString(): string {
|
||||
return Object.keys(this)
|
||||
.map(key => {
|
||||
if (this[key] instanceof PublicKey) {
|
||||
return `${camelCaseToUpperCaseWithUnderscores(key)}=${this[
|
||||
key
|
||||
].toBase58()}`;
|
||||
}
|
||||
return;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
public get anchorToml(): string {
|
||||
return [
|
||||
`[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "${this.payerKeypairPath}"
|
||||
|
||||
[test]
|
||||
startup_wait = 10000
|
||||
|
||||
[test.validator]
|
||||
url = "https://api.devnet.solana.com"
|
||||
`,
|
||||
Object.keys(this).map(
|
||||
key => `[[test.validator.clone]] # ${key}
|
||||
address = "${this[key]}"`
|
||||
),
|
||||
].join('\n\n');
|
||||
}
|
||||
|
||||
public get accountCloneString(): string {
|
||||
const accounts = Object.keys(this).map(key => {
|
||||
if (typeof this[key] === 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
return `--clone ${(this[key] as PublicKey).toBase58()} \`# ${key}\` `;
|
||||
});
|
||||
|
||||
return accounts.filter(Boolean).join(`\\\n`);
|
||||
}
|
||||
|
||||
public get dockerCompose(): string {
|
||||
return `version: "3.3"
|
||||
services:
|
||||
oracle:
|
||||
image: "switchboardlabs/node:\${SBV2_ORACLE_VERSION:-${LATEST_DOCKER_VERSION}}" # https://hub.docker.com/r/switchboardlabs/node/tags
|
||||
network_mode: host
|
||||
restart: always
|
||||
secrets:
|
||||
- PAYER_SECRETS
|
||||
environment:
|
||||
- VERBOSE=1
|
||||
- CLUSTER=\${CLUSTER:-localnet}
|
||||
- HEARTBEAT_INTERVAL=30 # Seconds
|
||||
- ORACLE_KEY=${this.oracle.toBase58()}
|
||||
- TASK_RUNNER_SOLANA_RPC=${clusterApiUrl('mainnet-beta')}
|
||||
# - RPC_URL=\${RPC_URL}
|
||||
secrets:
|
||||
PAYER_SECRETS:
|
||||
file: ${this.payerKeypairPath}`;
|
||||
}
|
||||
|
||||
public get localValidatorScript(): string {
|
||||
return `#!/bin/bash
|
||||
|
||||
mkdir -p .anchor/test-ledger
|
||||
|
||||
solana-test-validator -r --ledger .anchor/test-ledger --mint ${this.oracleAuthority.toBase58()} --bind-address 0.0.0.0 --url ${clusterApiUrl(
|
||||
'devnet'
|
||||
)} --rpc-port 8899 ${this.accountCloneString}`;
|
||||
}
|
||||
|
||||
public get startOracleScript(): string {
|
||||
return `#!/usr/bin/env bash
|
||||
|
||||
script_dir=$( cd -- "$( dirname -- "\${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
|
||||
docker-compose -f "$script_dir"/docker-compose.switchboard.yml up`;
|
||||
}
|
||||
|
||||
public toJSON(): ISwitchboardTestEnvironment {
|
||||
return {
|
||||
programId: this.programId,
|
||||
programDataAddress: this.programDataAddress,
|
||||
idlAddress: this.idlAddress,
|
||||
programState: this.programState,
|
||||
switchboardVault: this.switchboardVault,
|
||||
switchboardMint: this.switchboardMint,
|
||||
tokenWallet: this.tokenWallet,
|
||||
queue: this.queue,
|
||||
queueAuthority: this.queueAuthority,
|
||||
queueBuffer: this.queueBuffer,
|
||||
crank: this.crank,
|
||||
crankBuffer: this.crankBuffer,
|
||||
oracle: this.oracle,
|
||||
oracleAuthority: this.oracleAuthority,
|
||||
oracleEscrow: this.oracleEscrow,
|
||||
oraclePermissions: this.oraclePermissions,
|
||||
payerKeypairPath: this.payerKeypairPath,
|
||||
};
|
||||
}
|
||||
|
||||
/** Write switchboard test environment to filesystem */
|
||||
public writeAll(outputDir: string): void {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
this.writeEnv(outputDir);
|
||||
this.writeJSON(outputDir);
|
||||
this.writeScripts(outputDir);
|
||||
this.writeDockerCompose(outputDir);
|
||||
this.writeAnchorToml(outputDir);
|
||||
}
|
||||
|
||||
/** Write the env file to filesystem */
|
||||
public writeEnv(filePath: string): void {
|
||||
const ENV_FILE_PATH = path.join(filePath, 'switchboard.env');
|
||||
fs.writeFileSync(ENV_FILE_PATH, this.envFileString);
|
||||
}
|
||||
|
||||
public writeJSON(outputDir: string): void {
|
||||
const JSON_FILE_PATH = path.join(outputDir, 'switchboard.json');
|
||||
fs.writeFileSync(
|
||||
JSON_FILE_PATH,
|
||||
JSON.stringify(
|
||||
this.toJSON(),
|
||||
(key, value) => {
|
||||
if (value instanceof PublicKey) {
|
||||
return value.toBase58();
|
||||
}
|
||||
return value;
|
||||
},
|
||||
2
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public writeScripts(outputDir: string): void {
|
||||
// create script to start local validator with accounts cloned
|
||||
const LOCAL_VALIDATOR_SCRIPT = path.join(
|
||||
outputDir,
|
||||
'start-local-validator.sh'
|
||||
);
|
||||
fs.writeFileSync(LOCAL_VALIDATOR_SCRIPT, this.localValidatorScript);
|
||||
fs.chmodSync(LOCAL_VALIDATOR_SCRIPT, '755');
|
||||
|
||||
// create bash script to start local oracle
|
||||
const ORACLE_SCRIPT = path.join(outputDir, 'start-oracle.sh');
|
||||
fs.writeFileSync(ORACLE_SCRIPT, this.startOracleScript);
|
||||
fs.chmodSync(ORACLE_SCRIPT, '755');
|
||||
}
|
||||
|
||||
public writeDockerCompose(outputDir: string): void {
|
||||
const DOCKER_COMPOSE_FILEPATH = path.join(
|
||||
outputDir,
|
||||
'docker-compose.switchboard.yml'
|
||||
);
|
||||
fs.writeFileSync(DOCKER_COMPOSE_FILEPATH, this.dockerCompose);
|
||||
}
|
||||
|
||||
public writeAnchorToml(outputDir: string) {
|
||||
const ANCHOR_TOML_FILEPATH = path.join(
|
||||
outputDir,
|
||||
'Anchor.switchboard.toml'
|
||||
);
|
||||
fs.writeFileSync(ANCHOR_TOML_FILEPATH, this.anchorToml);
|
||||
}
|
||||
}
|
||||
|
||||
function camelCaseToUpperCaseWithUnderscores(input: string) {
|
||||
// Use a regular expression to match words that begin with a capital letter
|
||||
const matches = input.match(/[A-Z][a-z]*/g);
|
||||
|
||||
// If there are no matches, return the original string
|
||||
if (matches === null) {
|
||||
return input;
|
||||
}
|
||||
|
||||
// Convert the matches to upper case and join them with underscores
|
||||
return matches.map(match => match.toUpperCase()).join('_');
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from './SwitchboardTestContext';
|
Loading…
Reference in New Issue