solana.js: added buffer-relayer tests
This commit is contained in:
parent
9f3a99db5e
commit
a198db022d
|
@ -18,12 +18,17 @@ import { BN } from 'bn.js';
|
|||
import * as errors from '../errors';
|
||||
import * as types from '../generated';
|
||||
import { SwitchboardProgram } from '../SwitchboardProgram';
|
||||
import { TransactionObject } from '../TransactionObject';
|
||||
import {
|
||||
TransactionObject,
|
||||
TransactionObjectOptions,
|
||||
} from '../TransactionObject';
|
||||
import { Account, OnAccountChangeCallback } from './account';
|
||||
import { JobAccount } from './jobAccount';
|
||||
import { PermissionAccount } from './permissionAccount';
|
||||
import { QueueAccount } from './queueAccount';
|
||||
import { promiseWithTimeout } from '@switchboard-xyz/common';
|
||||
import { OracleAccount } from './oracleAccount';
|
||||
import { bufferRelayerSaveResult } from '../generated';
|
||||
|
||||
/**
|
||||
* Account type holding a buffer of data sourced from the buffers sole {@linkcode JobAccount}. A buffer relayer has no consensus mechanism and relies on trusting an {@linkcode OracleAccount} to respond honestly. A buffer relayer has a max capacity of 500 bytes.
|
||||
|
@ -186,27 +191,44 @@ export class BufferRelayerAccount extends Account<types.BufferRelayerAccountData
|
|||
|
||||
public async openRoundInstructions(
|
||||
payer: PublicKey,
|
||||
params: BufferRelayerOpenRoundParams
|
||||
params?: BufferRelayerOpenRoundParams
|
||||
): Promise<TransactionObject> {
|
||||
const ixns: TransactionInstruction[] = [];
|
||||
const bufferRelayer = params.bufferRelayer ?? (await this.loadData());
|
||||
const txns: TransactionObject[] = [];
|
||||
const bufferRelayer = params?.bufferRelayer ?? (await this.loadData());
|
||||
|
||||
const queueAccount =
|
||||
params.queueAccount ??
|
||||
params?.queueAccount ??
|
||||
new QueueAccount(this.program, bufferRelayer.queuePubkey);
|
||||
const queue = params.queue ?? (await queueAccount.loadData());
|
||||
const queue = params?.queue ?? (await queueAccount.loadData());
|
||||
|
||||
const openRoundAmount = this.program.mint.fromTokenAmountBN(queue.reward);
|
||||
|
||||
let tokenWallet: PublicKey;
|
||||
if (params?.tokenWallet) {
|
||||
tokenWallet = params.tokenWallet;
|
||||
// check if we need to wrap any funds
|
||||
const tokenAccount = await getAccount(
|
||||
this.program.connection,
|
||||
params.tokenWallet
|
||||
tokenWallet
|
||||
);
|
||||
const tokenAmountBN = new BN(tokenAccount.amount.toString());
|
||||
if (tokenAmountBN.lt(queue.reward)) {
|
||||
const wrapTxn = await this.program.mint.wrapInstructions(payer, {
|
||||
fundUpTo: this.program.mint.fromTokenAmountBN(queue.reward),
|
||||
fundUpTo: openRoundAmount,
|
||||
});
|
||||
ixns.push(...wrapTxn.ixns);
|
||||
txns.push(wrapTxn);
|
||||
}
|
||||
} else {
|
||||
const [userTokenWallet, txn] =
|
||||
await this.program.mint.getOrCreateWrappedUserInstructions(payer, {
|
||||
fundUpTo: openRoundAmount,
|
||||
});
|
||||
tokenWallet = userTokenWallet;
|
||||
if (txn !== undefined) {
|
||||
txns.push(txn);
|
||||
}
|
||||
}
|
||||
|
||||
const [permissionAccount, permissionBump] = PermissionAccount.fromSeed(
|
||||
this.program,
|
||||
queue.authority,
|
||||
|
@ -214,9 +236,11 @@ export class BufferRelayerAccount extends Account<types.BufferRelayerAccountData
|
|||
this.publicKey
|
||||
);
|
||||
|
||||
ixns.push(
|
||||
const openRoundTxn = new TransactionObject(
|
||||
payer,
|
||||
[
|
||||
createTransferInstruction(
|
||||
params.tokenWallet,
|
||||
tokenWallet,
|
||||
bufferRelayer.escrow,
|
||||
payer,
|
||||
BigInt(queue.reward.toString())
|
||||
|
@ -237,14 +261,22 @@ export class BufferRelayerAccount extends Account<types.BufferRelayerAccountData
|
|||
escrow: bufferRelayer.escrow,
|
||||
programState: this.program.programState.publicKey,
|
||||
}
|
||||
)
|
||||
),
|
||||
],
|
||||
[]
|
||||
);
|
||||
txns.push(openRoundTxn);
|
||||
|
||||
return new TransactionObject(payer, ixns, []);
|
||||
const packed = TransactionObject.pack(txns);
|
||||
if (packed.length > 1) {
|
||||
throw new Error(`Failed to pack instructions into a single txn`);
|
||||
}
|
||||
|
||||
return packed[0];
|
||||
}
|
||||
|
||||
public async openRound(
|
||||
params: BufferRelayerOpenRoundParams
|
||||
params?: BufferRelayerOpenRoundParams
|
||||
): Promise<TransactionSignature> {
|
||||
const openRound = await this.openRoundInstructions(
|
||||
this.program.walletPubkey,
|
||||
|
@ -303,15 +335,109 @@ export class BufferRelayerAccount extends Account<types.BufferRelayerAccountData
|
|||
return [state, openRoundSignature];
|
||||
}
|
||||
|
||||
public getAccounts(params: {
|
||||
queueAccount: QueueAccount;
|
||||
queueAuthority: PublicKey;
|
||||
}) {
|
||||
const queueAccount = params.queueAccount;
|
||||
public async saveResultInstructions(
|
||||
payer: PublicKey,
|
||||
params: BufferRelayerSaveResultParams,
|
||||
options?: TransactionObjectOptions
|
||||
): Promise<TransactionObject> {
|
||||
const bufferRelayer = await this.loadData();
|
||||
|
||||
const [queueAccount, queue] = await QueueAccount.load(
|
||||
this.program,
|
||||
bufferRelayer.queuePubkey
|
||||
);
|
||||
|
||||
const { permissionAccount, permissionBump } = this.getAccounts(
|
||||
queueAccount,
|
||||
queue.authority
|
||||
);
|
||||
|
||||
const [oracleAccount, oracle] = await OracleAccount.load(
|
||||
this.program,
|
||||
bufferRelayer.currentRound.oraclePubkey
|
||||
);
|
||||
|
||||
return this.saveResultSyncInstructions(
|
||||
payer,
|
||||
{
|
||||
...params,
|
||||
escrow: bufferRelayer.escrow,
|
||||
queueAccount: queueAccount,
|
||||
queueAuthority: queue.authority,
|
||||
queueDataBuffer: queue.dataBuffer,
|
||||
oracleAccount: oracleAccount,
|
||||
oracleAuthority: oracle.oracleAuthority,
|
||||
oracleTokenAccount: oracle.tokenAccount,
|
||||
permissionAccount: permissionAccount,
|
||||
permissionBump: permissionBump,
|
||||
},
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
public async saveResult(
|
||||
params: BufferRelayerSaveResultParams,
|
||||
options?: TransactionObjectOptions
|
||||
): Promise<TransactionSignature> {
|
||||
const saveResult = await this.saveResultInstructions(
|
||||
this.program.walletPubkey,
|
||||
params,
|
||||
options
|
||||
);
|
||||
const txnSignature = await this.program.signAndSend(saveResult);
|
||||
return txnSignature;
|
||||
}
|
||||
|
||||
public async saveResultSyncInstructions(
|
||||
payer: PublicKey,
|
||||
params: BufferRelayerSaveResultSyncParams,
|
||||
options?: TransactionObjectOptions
|
||||
): Promise<TransactionObject> {
|
||||
const saveResultIxn = bufferRelayerSaveResult(
|
||||
this.program,
|
||||
{
|
||||
params: {
|
||||
stateBump: this.program.programState.bump,
|
||||
permissionBump: params.permissionBump,
|
||||
result: params.result,
|
||||
success: params.success,
|
||||
},
|
||||
},
|
||||
{
|
||||
bufferRelayer: this.publicKey,
|
||||
oracleAuthority: params.oracleAuthority,
|
||||
oracle: params.oracleAccount.publicKey,
|
||||
oracleQueue: params.queueAccount.publicKey,
|
||||
dataBuffer: params.queueDataBuffer,
|
||||
queueAuthority: params.queueAuthority,
|
||||
permission: params.permissionAccount.publicKey,
|
||||
escrow: params.escrow,
|
||||
programState: this.program.programState.publicKey,
|
||||
oracleWallet: params.oracleTokenAccount,
|
||||
tokenProgram: spl.TOKEN_PROGRAM_ID,
|
||||
}
|
||||
);
|
||||
|
||||
return new TransactionObject(payer, [saveResultIxn], [], options);
|
||||
}
|
||||
|
||||
public async saveResultSync(
|
||||
params: BufferRelayerSaveResultSyncParams,
|
||||
options?: TransactionObjectOptions
|
||||
): Promise<TransactionSignature> {
|
||||
const saveResult = await this.saveResultSyncInstructions(
|
||||
this.program.walletPubkey,
|
||||
params,
|
||||
options
|
||||
);
|
||||
const txnSignature = await this.program.signAndSend(saveResult);
|
||||
return txnSignature;
|
||||
}
|
||||
|
||||
public getAccounts(queueAccount: QueueAccount, queueAuthority: PublicKey) {
|
||||
const [permissionAccount, permissionBump] = PermissionAccount.fromSeed(
|
||||
this.program,
|
||||
params.queueAuthority,
|
||||
queueAuthority,
|
||||
queueAccount.publicKey,
|
||||
this.publicKey
|
||||
);
|
||||
|
@ -359,10 +485,10 @@ export class BufferRelayerAccount extends Account<types.BufferRelayerAccountData
|
|||
new QueueAccount(this.program, bufferRelayer.queuePubkey);
|
||||
const queue = _queue ?? (await queueAccount.loadData());
|
||||
|
||||
const { permissionAccount, permissionBump } = this.getAccounts({
|
||||
const { permissionAccount, permissionBump } = this.getAccounts(
|
||||
queueAccount,
|
||||
queueAuthority: queue.authority,
|
||||
});
|
||||
queue.authority
|
||||
);
|
||||
const permission = await permissionAccount.loadData();
|
||||
|
||||
const bufferEscrow = await this.program.mint.getAccount(
|
||||
|
@ -439,8 +565,26 @@ export type BufferRelayerAccountsJSON = types.BufferRelayerAccountDataJSON & {
|
|||
};
|
||||
|
||||
export type BufferRelayerOpenRoundParams = {
|
||||
tokenWallet: PublicKey;
|
||||
tokenWallet?: PublicKey;
|
||||
bufferRelayer?: types.BufferRelayerAccountData;
|
||||
queueAccount?: QueueAccount;
|
||||
queue?: types.OracleQueueAccountData;
|
||||
};
|
||||
|
||||
export type BufferRelayerSaveResultParams = {
|
||||
result: Buffer;
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
export type BufferRelayerSaveResultSyncParams =
|
||||
BufferRelayerSaveResultParams & {
|
||||
escrow: PublicKey;
|
||||
queueAccount: QueueAccount;
|
||||
queueAuthority: PublicKey;
|
||||
queueDataBuffer: PublicKey;
|
||||
oracleAccount: OracleAccount;
|
||||
oracleAuthority: PublicKey;
|
||||
oracleTokenAccount: PublicKey;
|
||||
permissionAccount: PermissionAccount;
|
||||
permissionBump: number;
|
||||
};
|
||||
|
|
|
@ -32,7 +32,7 @@ export class QueueJson implements IQueueInitParams {
|
|||
// accounts
|
||||
authority?: Keypair;
|
||||
keypair: Keypair;
|
||||
dataBuffer: Keypair;
|
||||
dataBufferKeypair: Keypair;
|
||||
|
||||
constructor(object: Record<string, any>) {
|
||||
this.name = parseString(object, 'name', '');
|
||||
|
@ -82,7 +82,7 @@ export class QueueJson implements IQueueInitParams {
|
|||
this.authority = authorityPath ? loadKeypair(authorityPath) : undefined;
|
||||
|
||||
const dataBufferPath = parseString(object, 'dataBufferKeypair');
|
||||
this.dataBuffer = dataBufferPath
|
||||
this.dataBufferKeypair = dataBufferPath
|
||||
? loadKeypair(dataBufferPath)
|
||||
: Keypair.generate();
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ export class QueueJson implements IQueueInitParams {
|
|||
enableBufferRelayers: this.enableBufferRelayers,
|
||||
authority: this.authority ? keypairToString(this.authority) : undefined,
|
||||
keypair: keypairToString(this.keypair),
|
||||
dataBuffer: keypairToString(this.dataBuffer),
|
||||
dataBufferKeypair: keypairToString(this.dataBufferKeypair),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
import 'mocha';
|
||||
import assert from 'assert';
|
||||
|
||||
import { setupTest, TestContext } from './utilts';
|
||||
import { Keypair, PublicKey } from '@solana/web3.js';
|
||||
import {
|
||||
BufferRelayerAccount,
|
||||
OracleAccount,
|
||||
QueueAccount,
|
||||
types,
|
||||
} from '../src';
|
||||
import { OracleJob } from '@switchboard-xyz/common';
|
||||
|
||||
describe('BufferRelayer Tests', () => {
|
||||
let ctx: TestContext;
|
||||
|
||||
const queueAuthority = Keypair.generate();
|
||||
let queueAccount: QueueAccount;
|
||||
|
||||
let oracleAccount: OracleAccount;
|
||||
let oracle: types.OracleAccountData;
|
||||
|
||||
let bufferAccount: BufferRelayerAccount;
|
||||
const expectedResult: Buffer = Buffer.from(
|
||||
JSON.stringify({
|
||||
userId: 1,
|
||||
id: 1,
|
||||
title: 'delectus aut autem',
|
||||
completed: false,
|
||||
}),
|
||||
'utf-8'
|
||||
);
|
||||
|
||||
let userTokenAddress: PublicKey;
|
||||
|
||||
before(async () => {
|
||||
ctx = await setupTest();
|
||||
|
||||
[queueAccount] = await QueueAccount.create(ctx.program, {
|
||||
name: 'buffer-relayer-queue',
|
||||
metadata: '',
|
||||
authority: queueAuthority.publicKey,
|
||||
queueSize: 1,
|
||||
reward: 0,
|
||||
minStake: 0,
|
||||
oracleTimeout: 86400,
|
||||
slashingEnabled: false,
|
||||
unpermissionedFeeds: true,
|
||||
unpermissionedVrf: true,
|
||||
enableBufferRelayers: true,
|
||||
});
|
||||
|
||||
[oracleAccount] = await queueAccount.createOracle({
|
||||
name: 'oracle-1',
|
||||
metadata: 'oracle-1',
|
||||
queueAuthority,
|
||||
enable: true,
|
||||
});
|
||||
await oracleAccount.heartbeat();
|
||||
oracle = await oracleAccount.loadData();
|
||||
|
||||
assert(
|
||||
oracle.oracleAuthority.equals(ctx.payer.publicKey),
|
||||
'Incorrect oracle authority'
|
||||
);
|
||||
|
||||
[userTokenAddress] = await ctx.program.mint.getOrCreateWrappedUser(
|
||||
ctx.payer.publicKey,
|
||||
{ fundUpTo: 0.1 }
|
||||
);
|
||||
});
|
||||
|
||||
it('Creates a Buffer Relayer', async () => {
|
||||
[bufferAccount] = await queueAccount.createBufferRelayer({
|
||||
name: 'My Buffer',
|
||||
minUpdateDelaySeconds: 30,
|
||||
enable: true,
|
||||
queueAuthorityPubkey: queueAuthority.publicKey,
|
||||
queueAuthority: queueAuthority,
|
||||
job: {
|
||||
name: 'Buffer Job',
|
||||
data: Buffer.from(
|
||||
OracleJob.encodeDelimited(
|
||||
OracleJob.create({
|
||||
tasks: [
|
||||
OracleJob.Task.create({
|
||||
httpTask: OracleJob.HttpTask.create({
|
||||
url: 'https://jsonplaceholder.typicode.com/todos/1',
|
||||
}),
|
||||
}),
|
||||
],
|
||||
})
|
||||
).finish()
|
||||
),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('Calls openRound on a BufferRelayer', async () => {
|
||||
if (!bufferAccount) {
|
||||
throw new Error(`No BufferRelayer account`);
|
||||
}
|
||||
|
||||
await bufferAccount.openRound({
|
||||
tokenWallet: userTokenAddress,
|
||||
});
|
||||
|
||||
const bufferRelayer = await bufferAccount.loadData();
|
||||
|
||||
assert(
|
||||
bufferRelayer.currentRound.oraclePubkey.equals(oracleAccount.publicKey),
|
||||
`Oracle assignment mismatch, expected ${oracleAccount.publicKey}, received ${bufferRelayer.currentRound.oraclePubkey}`
|
||||
);
|
||||
});
|
||||
|
||||
it('Calls saveResult on a BufferRelayer', async () => {
|
||||
if (!bufferAccount) {
|
||||
throw new Error(`No BufferRelayer account`);
|
||||
}
|
||||
|
||||
await bufferAccount.saveResult({
|
||||
result: expectedResult,
|
||||
success: true,
|
||||
});
|
||||
|
||||
const bufferRelayer = await bufferAccount.loadData();
|
||||
|
||||
assert(
|
||||
Buffer.compare(expectedResult, bufferRelayer.result) === 0,
|
||||
`BufferRelayer result mismatch, expected [${new Uint8Array(
|
||||
expectedResult
|
||||
)}], received [${new Uint8Array(bufferRelayer.result)}]`
|
||||
);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue