sbv2-solana/javascript/solana.js/test/attestation-oracle.spec.ts

371 lines
11 KiB
TypeScript

import "mocha";
import { programConfig } from "../src/generated/index.js";
import * as sbv2 from "../src/index.js";
import { setupTest, TestContext } from "./utils.js";
import { NATIVE_MINT, transfer } from "@solana/spl-token";
import {
Keypair,
LAMPORTS_PER_SOL,
PublicKey,
SystemProgram,
} from "@solana/web3.js";
import { Big, BN, OracleJob, sleep } from "@switchboard-xyz/common";
import assert from "assert";
describe("Attestation Oracle Tests", () => {
let ctx: TestContext;
let queueAccount: sbv2.QueueAccount;
let oracleAccount: sbv2.OracleAccount;
let oracleQuoteAccount: sbv2.QuoteAccount;
const oracleQuoteKeypair = Keypair.generate();
let attestationQueueAccount: sbv2.AttestationQueueAccount;
const quoteKeypair = Keypair.generate();
const quoteSigner = Keypair.generate();
let attestationQuoteAccount: sbv2.QuoteAccount;
const quoteVerifierMrEnclave = Array.from(
Buffer.from("This is the quote verifier MrEnclave")
)
.concat(Array(32).fill(0))
.slice(0, 32);
const mrEnclave = Array.from(
Buffer.from("This is the NodeJS oracle MrEnclave")
)
.concat(Array(32).fill(0))
.slice(0, 32);
before(async () => {
ctx = await setupTest();
const programStateData = await new sbv2.ProgramStateAccount(
ctx.program,
ctx.program.programState.publicKey
).loadData();
[queueAccount] = await sbv2.QueueAccount.create(ctx.program, {
reward: 0,
minStake: 0,
enableTeeOnly: true,
});
[oracleAccount] = await queueAccount.createOracle({
enable: true,
queueAuthorityPubkey: ctx.program.walletPubkey,
authority: oracleQuoteKeypair,
teeOracle: true,
});
const oracleData = await oracleAccount.loadData();
assert(
oracleData.oracleAuthority.equals(oracleQuoteKeypair.publicKey),
"OracleAuthorityMismatch"
);
if (
sbv2.ProgramStateAccount.findEnclaveIdx(
programStateData.mrEnclaves.map((e) => new Uint8Array(e)),
new Uint8Array(mrEnclave)
) === -1
) {
const daoMint = programStateData.daoMint.equals(PublicKey.default)
? NATIVE_MINT
: programStateData.daoMint;
ctx.program.signAndSend(
new sbv2.TransactionObject(
ctx.program.walletPubkey,
[
programConfig(
ctx.program,
{
params: {
token: programStateData.tokenMint,
bump: ctx.program.programState.bump,
daoMint: daoMint,
addEnclaves: [mrEnclave],
rmEnclaves: [],
},
},
{
authority: programStateData.authority,
programState: ctx.program.programState.publicKey,
daoMint: daoMint,
}
),
],
[]
)
);
}
});
it("Creates an Attestation Queue", async () => {
[attestationQueueAccount] = await sbv2.AttestationQueueAccount.create(
ctx.program,
{
reward: 0,
allowAuthorityOverrideAfter: 60, // should increase this
maxQuoteVerificationAge: 604800,
requireAuthorityHeartbeatPermission: false,
requireUsagePermissions: false,
nodeTimeout: 604800,
}
);
await attestationQueueAccount.addMrEnclave({
mrEnclave: new Uint8Array(quoteVerifierMrEnclave),
});
const attestationQueueState = await attestationQueueAccount.loadData();
assert(
Buffer.compare(
Buffer.from(
attestationQueueState.mrEnclaves.slice(
0,
attestationQueueState.mrEnclavesLen
)[0]
),
Buffer.from(quoteVerifierMrEnclave)
) === 0,
`Attestation queue does not have the correct MRENCLAVE`
);
[attestationQuoteAccount] = await attestationQueueAccount.createQuote({
registryKey: new Uint8Array(Array(64).fill(1)),
keypair: quoteKeypair,
enable: true,
queueAuthorityPubkey: ctx.program.walletPubkey,
});
await attestationQuoteAccount.rotate({
securedSigner: quoteSigner,
registryKey: new Uint8Array(Array(64).fill(1)),
});
const quoteState = await attestationQuoteAccount.loadData();
const verificationStatus =
sbv2.QuoteAccount.getVerificationStatus(quoteState);
assert(
verificationStatus.kind === "VerificationOverride",
`Quote account has not been verified`
);
// join the queue so we can verify other quotes
await attestationQuoteAccount.heartbeat({ securedSigner: quoteSigner });
const payer2 = Keypair.generate();
await ctx.program.signAndSend(
new sbv2.TransactionObject(
ctx.payer.publicKey,
[
SystemProgram.transfer({
fromPubkey: ctx.payer.publicKey,
toPubkey: payer2.publicKey,
lamports: LAMPORTS_PER_SOL,
}),
],
[]
)
);
const quoteKeypair2 = Keypair.generate();
const quoteSigner2 = Keypair.generate();
const [attestationQuoteAccount2] =
await attestationQueueAccount.createQuote({
registryKey: new Uint8Array(Array(64).fill(1)),
keypair: quoteKeypair2,
enable: true,
queueAuthorityPubkey: ctx.program.walletPubkey,
authority: payer2.publicKey,
});
await attestationQuoteAccount2.rotate({
securedSigner: quoteSigner2,
registryKey: new Uint8Array(Array(64).fill(1)),
authority: payer2,
});
await attestationQuoteAccount2.verify({
timestamp: new BN(Math.floor(Date.now() / 1000)),
mrEnclave: new Uint8Array(quoteVerifierMrEnclave),
verifierSecuredSigner: quoteSigner,
verifier: attestationQuoteAccount.publicKey,
});
const quoteState2 = await attestationQuoteAccount2.loadData();
const verificationStatus2 =
sbv2.QuoteAccount.getVerificationStatus(quoteState2);
assert(
verificationStatus2.kind === "VerificationSuccess",
`Quote account has not been verified`
);
// join the queue so we can verify the overrridden quote
await attestationQuoteAccount2.heartbeat({ securedSigner: quoteSigner2 });
await attestationQuoteAccount.verify({
timestamp: new BN(Math.floor(Date.now() / 1000)),
mrEnclave: new Uint8Array(quoteVerifierMrEnclave),
verifierSecuredSigner: quoteSigner2,
verifier: attestationQuoteAccount2.publicKey,
});
const newQuoteState = await attestationQuoteAccount.loadData();
const newVerificationStatus =
sbv2.QuoteAccount.getVerificationStatus(newQuoteState);
assert(
newVerificationStatus.kind === "VerificationSuccess",
`Quote account has not been verified`
);
const newQueueState = await attestationQueueAccount.loadData();
assert(newQueueState.dataLen === 2, "AttestationQueue incorrect size");
});
it("Creates a TEE oracle", async () => {
[oracleQuoteAccount] = await attestationQueueAccount.createQuote({
registryKey: new Uint8Array(Array(64).fill(1)),
keypair: oracleQuoteKeypair,
});
assert(
oracleQuoteKeypair.publicKey.equals(oracleQuoteAccount.publicKey),
"QuotePubkeyMismatch"
);
await oracleQuoteAccount.verify({
timestamp: new BN(Math.floor(Date.now() / 1000)),
mrEnclave: new Uint8Array(mrEnclave),
verifierSecuredSigner: quoteSigner,
verifier: attestationQuoteAccount.publicKey,
});
const quoteData = await oracleQuoteAccount.loadData();
assert(
Buffer.compare(
new Uint8Array(mrEnclave),
new Uint8Array(quoteData.mrEnclave)
) === 0,
"QuoteData MRECLAVE mismatch"
);
const queueData = await queueAccount.loadData();
const [permissionAccount, permissionBump] =
oracleAccount.getPermissionAccount(
queueAccount.publicKey,
queueData.authority,
oracleQuoteKeypair.publicKey
);
// Perform tee heartbeat.
await oracleAccount.teeHeartbeat({
quoteKeypair: oracleQuoteKeypair,
permission: [permissionAccount, permissionBump],
authority: oracleQuoteKeypair,
});
});
it("Calls TeeSaveResult", async () => {
const programStateData = await new sbv2.ProgramStateAccount(
ctx.program,
ctx.program.programState.publicKey
).loadData();
assert(
sbv2.ProgramStateAccount.findEnclaveIdx(
programStateData.mrEnclaves.map((e) => new Uint8Array(e)),
new Uint8Array(mrEnclave)
) !== -1,
"ProgramState does not have the NodeJS MRENCLAVE measurement"
);
// 1. Create basic aggregator with valueTask
const [aggregatorAccount] = await queueAccount.createFeed({
queueAuthority: ctx.payer,
batchSize: 1,
minRequiredOracleResults: 1,
minRequiredJobResults: 1,
minUpdateDelaySeconds: 60,
fundAmount: 0.15,
enable: true,
historyLimit: 1000,
slidingWindow: true,
jobs: [
{
weight: 2,
data: OracleJob.encodeDelimited(
OracleJob.fromObject({
tasks: [
{
valueTask: {
value: 1,
},
},
],
})
).finish(),
},
],
});
const aggregator = await aggregatorAccount.loadData();
const jobs = await aggregatorAccount.loadJobs(aggregator);
assert(
aggregator.resolutionMode.kind === "ModeSlidingResolution",
"Aggregator account needs to be sliding window mode"
);
// 2. Call openRound
await aggregatorAccount.openRound();
// 3. Send tee_save_result
const updatedAggregatorState = await aggregatorAccount.loadData();
const oracles = await aggregatorAccount.loadCurrentRoundOracles(
updatedAggregatorState
);
const saveResultTxn = aggregatorAccount.teeSaveResultInstructionSync(
ctx.payer.publicKey,
{
...aggregatorAccount.getAccounts(
queueAccount,
ctx.program.walletPubkey
),
oraclePermission: oracleAccount.getPermissionAccount(
queueAccount.publicKey,
ctx.payer.publicKey,
oracleQuoteKeypair.publicKey
),
jobs: jobs.map((j) => j.job),
value: new Big(1),
minResponse: new Big(1),
maxResponse: new Big(1),
queueAccount: queueAccount,
queueAuthority: ctx.program.walletPubkey,
oracles: oracles,
oracleIdx: 0,
aggregator: updatedAggregatorState,
quotePubkey: oracleQuoteAccount.publicKey,
authority: oracleQuoteKeypair,
}
);
await ctx.program.signAndSend(saveResultTxn, {
skipPreflight: true,
});
const finalAggregatorState = await aggregatorAccount.loadData();
const finalValue =
sbv2.AggregatorAccount.decodeLatestValue(finalAggregatorState);
assert(
finalValue !== null && finalValue.toNumber() === 1,
`AggregatorValue is incorrect, ${finalValue}`
);
});
});