chore: migrated devnet program to same pubkey as mainnet (#100)

This commit is contained in:
gallynaut 2023-03-16 16:28:06 -06:00 committed by GitHub
parent 865da03b59
commit 91080af417
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
90 changed files with 4649 additions and 1137 deletions

View File

@ -106,11 +106,11 @@ jobs:
cluster: devnet
args:
"--url ${{ secrets.DEVNET_RPC_URL }} --clone
2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG --clone
J4CArpsbrZqu1axqQ4AnrqREs3jwoyA1M5LMiQQmAzB9 --clone
CKwZcshn4XDvhaWVH9EXnk3iu19t6t5xP2Sy2pD6TRDp --clone
BYM81n8HvTJuqZU1PmTVcwZ9G8uoji7FKM6EaPkwphPt --clone
FVLfR6C2ckZhbSwBzZY4CX7YBcddUSge5BNeGQv5eKhy"
SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f --clone
7nYabs9dUhvxYwdTnrWVBL9MYviKSfrEbdWCUbcnwkpF --clone
Fi8vncGpNKbq62gPo56G4toCehWNy77GgqGkTaAF5Lkk --clone
CyZuD7RPDcrqCGbNvLCyqk6Py9cEZTKmNKujfPi3ynDd --clone
7hkp1xfPBcD2t1vZMoWWQPzipHVcXeLAAaiGXdPSfDie"
- name: Run Tests
working-directory: javascript/solana.js
env:

View File

@ -55,10 +55,10 @@ pnpm build
## Program IDs
| **Network** | **Program ID** |
| ------------ | ---------------------------------------------- |
| Mainnet-Beta | `SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f` |
| Devnet | `2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG` |
| **Network** | **Program ID** |
| ------------ | --------------------------------------------- |
| Mainnet-Beta | `SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f` |
| Devnet | `SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f` |
See [switchboard.xyz/explorer](https://switchboard.xyz/explorer) for a list of
feeds deployed on Solana.

View File

@ -31,10 +31,10 @@ export class AnchorWallet implements anchor.Wallet {
export default class SwitchboardProgram {
/**
* Switchboard Devnet Program ID
* 2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG
* SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f
*/
public static devnetPid = new anchor.web3.PublicKey(
"2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG"
"SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f"
);
/**

View File

@ -100,7 +100,7 @@ export class SwitchboardTestContext implements ISwitchboardTestContext {
*/
static async loadDevnetQueue(
provider: anchor.AnchorProvider,
queueKey = "F8ce7MsckeZAbAGmxjJNetxYXQa9mKr9nnrC3qKubyYy",
queueKey = "uPeRMdfPmrPqgRWSrjAnAkH78RqAhe5kXoW6vBYRqFX",
tokenAmount = 0
) {
const payerKeypair = (provider.wallet as sbv2.AnchorWallet).payer;

View File

@ -55,7 +55,7 @@ describe("Feed tests", () => {
// await devnetConnection.confirmTransaction(airdropSignature);
switchboard = await SwitchboardTestContext.loadDevnetQueue(
provider,
"F8ce7MsckeZAbAGmxjJNetxYXQa9mKr9nnrC3qKubyYy"
"uPeRMdfPmrPqgRWSrjAnAkH78RqAhe5kXoW6vBYRqFX"
);
console.log("devnet detected");
return;

View File

@ -38,7 +38,7 @@ const program = await loadSwitchboardProgram(
const queueAccount = new OracleQueueAccount({
program: program,
// devnet permissionless queue
publicKey: new PublicKey("F8ce7MsckeZAbAGmxjJNetxYXQa9mKr9nnrC3qKubyYy"),
publicKey: new PublicKey("uPeRMdfPmrPqgRWSrjAnAkH78RqAhe5kXoW6vBYRqFX"),
});
const aggregatorAccount = await AggregatorAccount.create(program, {

View File

@ -30,10 +30,10 @@ export { SwitchboardDecimal } from "@switchboard-xyz/common";
/**
* Switchboard Devnet Program ID
* 2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG
* SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f
*/
export const SBV2_DEVNET_PID = new PublicKey(
"2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG"
"SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f"
);
/**
* Switchboard Mainnet Program ID

View File

@ -101,7 +101,7 @@ Then run the following command to create your own feed using the devnet
permissionless queue and crank
```bash
export QUEUE_KEY=F8ce7MsckeZAbAGmxjJNetxYXQa9mKr9nnrC3qKubyYy
export QUEUE_KEY=uPeRMdfPmrPqgRWSrjAnAkH78RqAhe5kXoW6vBYRqFX
export CRANK_KEY=GN9jjCy2THzZxhYqZETmPM3my8vg4R5JyNkgULddUMa5
sbv2 solana aggregator create "$QUEUE_KEY" \
--keypair ~/.config/solana/id.json \

View File

@ -29,7 +29,7 @@ import path from "path";
dotenv.config();
const DEVNET_PERMISSIONLESS_QUEUE = new PublicKey(
"F8ce7MsckeZAbAGmxjJNetxYXQa9mKr9nnrC3qKubyYy"
"uPeRMdfPmrPqgRWSrjAnAkH78RqAhe5kXoW6vBYRqFX"
);
const DEVNET_PERMISSIONLESS_CRANK = new PublicKey(

View File

@ -1 +1,4 @@
test/data/*-keypair.json
test/data/*-keypair.json
SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f
2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG

View File

@ -65,7 +65,7 @@ async function main() {
'anchor idl fetch -o ./src/idl/mainnet.json SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f --provider.cluster mainnet'
);
execSync(
'anchor idl fetch -o ./src/idl/devnet.json 2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG --provider.cluster devnet'
'anchor idl fetch -o ./src/idl/devnet.json SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f --provider.cluster devnet'
);
execSync(
@ -98,36 +98,40 @@ async function main() {
);
console.log(file);
// replace BN import
execSync(
`sed -i '' 's/import BN from \\"bn.js\\"/import { BN } from \\"@switchboard-xyz\\/common\\"/g' ${file}`
shell.sed(
'-i',
'import BN from "bn.js"',
'import { BN } from "@switchboard-xyz/common"',
file
);
// replace borsh import
execSync(`sed -i '' 's/@project-serum/@coral-xyz/g' ${file}`);
shell.sed('-i', '@project-serum', '@coral-xyz', file);
// remove PROGRAM_ID import, we will use SwitchboardProgram instead
execSync(
`sed -i '' 's/import { PROGRAM_ID } from "..\\/programId"/ /g' ${file}`
);
shell.sed('-i', 'import { PROGRAM_ID } from "../programId"', '', file);
// replace PROGRAM_ID with program.programId
execSync(`sed -i '' 's/PROGRAM_ID/program.programId/g' ${file}`);
shell.sed('-i', 'PROGRAM_ID', 'program.programId', file);
// replace Connection with SwitchboardProgram
execSync(
`sed -i '' 's/c: Connection,/program: SwitchboardProgram,/g' ${file}`
);
shell.sed('-i', 'c: Connection,', 'program: SwitchboardProgram,', file);
// replace c.getAccountInfo with the SwitchboardProgram connection
execSync(
`sed -i '' 's/c.getAccountInfo/program.connection.getAccountInfo/g' ${file}`
shell.sed(
'-i',
'c.getAccountInfo',
'program.connection.getAccountInfo',
file
);
// replace c.getMultipleAccountsInfo with the SwitchboardProgram connection
execSync(
`sed -i '' 's/c.getMultipleAccountsInfo/program.connection.getMultipleAccountsInfo/g' ${file}`
shell.sed(
'-i',
'c.getMultipleAccountsInfo',
'program.connection.getMultipleAccountsInfo',
file
);
// add program as first arguement to instructions
if (file.includes('/instructions/')) {
execSync(
`sed -i '' 's/args:/program: SwitchboardProgram, args:/g' ${file}`
);
shell.sed('-i', 'args:', 'program: SwitchboardProgram, args:', file);
}
}

View File

@ -0,0 +1,129 @@
import * as sbv2 from './src';
import { Job, setupOutputDir } from './utils';
import { clusterApiUrl, Connection } from '@solana/web3.js';
// import { backOff } from 'exponential-backoff';
import fs from 'fs';
import path from 'path';
const VERBOSE = process.env.VERBOSE || false;
const jobMapPath = path.join(
__dirname,
sbv2.SBV2_MAINNET_PID.toBase58(),
'job_map.csv'
);
async function main() {
const [oldDirPath, oldFeedDirPath, oldJobDirPath] = setupOutputDir(
'2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG'
);
const [newDirPath, newFeedDirPath, newJobDirPath] = setupOutputDir(
sbv2.SBV2_MAINNET_PID.toBase58()
);
// if (process.argv.length < 3) {
// throw new Error(
// `Usage: ts-node migrate [job|aggregator] [jobPubkey|aggregatorPubkey]`
// );
// }
const devnetConnection = new Connection(
process.env.SOLANA_DEVNET_RPC ?? clusterApiUrl('devnet')
);
console.log(`rpcUrl: ${devnetConnection.rpcEndpoint}`);
const payer = sbv2.SwitchboardTestContextV2.loadKeypair(
'~/switchboard_environments_v2/devnet/upgrade_authority/upgrade_authority.json'
);
console.log(`payer: ${payer.publicKey.toBase58()}`);
const newProgram = await sbv2.SwitchboardProgram.load(
'devnet',
devnetConnection,
payer,
sbv2.SBV2_MAINNET_PID
);
const jobs = new Map<string, Job>();
for (const file of fs.readdirSync(oldJobDirPath)) {
const fileName = path.basename(file).replace('.json', '');
const jobDef: Job = JSON.parse(
fs.readFileSync(path.join(oldJobDirPath, file), 'utf-8')
);
jobs.set(fileName, jobDef);
}
console.log(`Found ${jobs.size} jobs to migrate`);
const jobMap = loadJobMap();
console.log(`Found ${jobMap.size} jobs that have already been migrated`);
for (const [jobKey, job] of jobs.entries()) {
if (jobMap.has(jobKey)) {
continue;
}
const newJobPath = path.join(newJobDirPath, `${jobKey}.json`);
if (fs.existsSync(newJobPath)) {
continue;
}
try {
const [jobAccount] = await sbv2.JobAccount.create(newProgram, {
data: new Uint8Array(JSON.parse(job.definition.data)),
name: job.definition.name,
expiration: job.definition.expiration,
});
console.log(
`${jobKey.padEnd(44, ' ')} -> ${jobAccount.publicKey.toBase58()}`
);
fs.writeFileSync(
newJobPath,
JSON.stringify(
{
newPublicKey: jobAccount.publicKey.toBase58(),
oldPublicKey: jobKey,
job: job,
},
undefined,
2
)
);
jobMap.set(jobKey, jobAccount.publicKey.toBase58());
writeJobMap(jobMap);
} catch (error) {
console.error(`${jobKey} failed, ${error}`);
}
}
// for (const [jobKey, job] of jobs.entries()) {
// const jobAccount = new sbv2.JobAccount()
// }
}
main().catch(error => {
console.error(error);
});
function writeJobMap(map: Map<string, string>) {
const fileString = `oldPubkey, newPubkey\n${Array.from(map.entries())
.map(r => r.join(', '))
.join('\n')}`;
fs.writeFileSync(jobMapPath, fileString);
}
function loadJobMap(): Map<string, string> {
if (!fs.existsSync(jobMapPath)) {
return new Map();
}
const map = new Map();
const fileString = fs.readFileSync(jobMapPath, 'utf-8');
const fileLines = fileString.split('\n').slice(1);
fileLines.forEach(r => {
const [oldPubkey, newPubkey] = r.split(', ');
map.set(oldPubkey, newPubkey);
});
return map;
}

View File

@ -29,7 +29,7 @@
"localnet:up": "npm run local:validator & sleep 20 && npm run test:localnet",
"localnet:down": "kill -9 $(pgrep command solana-test-validator) || exit 0",
"localnet": "npm run localnet:up; npm run localnet:down",
"local:validator": "shx mkdir -p .anchor/test-ledger || true; solana-test-validator -q -r --ledger .anchor/test-ledger --mint $(solana-keygen pubkey ~/.config/solana/id.json) --bind-address 0.0.0.0 --url https://api.devnet.solana.com --rpc-port 8899 --clone 2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG `# programId` --clone J4CArpsbrZqu1axqQ4AnrqREs3jwoyA1M5LMiQQmAzB9 `# programDataAddress` --clone CKwZcshn4XDvhaWVH9EXnk3iu19t6t5xP2Sy2pD6TRDp `# idlAddress` --clone BYM81n8HvTJuqZU1PmTVcwZ9G8uoji7FKM6EaPkwphPt `# programState` --clone FVLfR6C2ckZhbSwBzZY4CX7YBcddUSge5BNeGQv5eKhy `# switchboardVault`",
"local:validator": "shx mkdir -p .anchor/test-ledger || true; solana-test-validator -q -r --ledger .anchor/test-ledger --mint $(solana-keygen pubkey ~/.config/solana/id.json) --bind-address 0.0.0.0 --url https://api.devnet.solana.com --rpc-port 8899 --clone SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f `# programId` --clone 7nYabs9dUhvxYwdTnrWVBL9MYviKSfrEbdWCUbcnwkpF `# programDataAddress` --clone Fi8vncGpNKbq62gPo56G4toCehWNy77GgqGkTaAF5Lkk `# idlAddress` --clone CyZuD7RPDcrqCGbNvLCyqk6Py9cEZTKmNKujfPi3ynDd `# programState` --clone 7hkp1xfPBcD2t1vZMoWWQPzipHVcXeLAAaiGXdPSfDie `# switchboardVault`",
"local:validator:mainnet": "solana-test-validator -q -r --ledger .anchor/test-ledger --mint $(solana-keygen pubkey ~/.config/solana/id.json) --bind-address 0.0.0.0 --rpc-port 8899 --url https://api.mainnet-beta.solana.com --clone SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f --clone 7nYabs9dUhvxYwdTnrWVBL9MYviKSfrEbdWCUbcnwkpF --clone Fi8vncGpNKbq62gPo56G4toCehWNy77GgqGkTaAF5Lkk --clone CyZuD7RPDcrqCGbNvLCyqk6Py9cEZTKmNKujfPi3ynDd --clone J7nSEX8ADf3pVVicd6yKy2Skvg8iLePEmkLUisAAaioD",
"generate": "node generate-client.js",
"build": "shx rm -rf lib || true; tsc -p tsconfig.cjs.json && tsc",
@ -43,8 +43,8 @@
"fix": "gts fix ./src ./test"
},
"dependencies": {
"@coral-xyz/anchor": "^0.26.0",
"@coral-xyz/borsh": "^0.26.0",
"@coral-xyz/anchor": "^0.27.0",
"@coral-xyz/borsh": "^0.27.0",
"@solana/spl-token": "^0.3.6",
"@solana/web3.js": "^1.73.0",
"@switchboard-xyz/common": "^2.1.33",
@ -62,6 +62,7 @@
"chai": "^4.3.7",
"chalk": "^4.1.2",
"eslint": "^8.35.0",
"exponential-backoff": "^3.1.1",
"gts": "^3.1.1",
"mocha": "^10.1.0",
"shelljs": "^0.8.5",

View File

@ -0,0 +1,356 @@
import * as sbv2 from './src';
import { jsonReplacers, setupOutputDir } from './utils';
import * as anchor from '@coral-xyz/anchor';
import * as spl from '@solana/spl-token';
import {
clusterApiUrl,
Connection,
LAMPORTS_PER_SOL,
PublicKey,
} from '@solana/web3.js';
import { OracleJob, toUtf8 } from '@switchboard-xyz/common';
// import { backOff } from 'exponential-backoff';
import fs from 'fs';
import _ from 'lodash';
import path from 'path';
const LEASE_THRESHOLD = (10 * 12500) / LAMPORTS_PER_SOL; // must have at least 10 queue rewards in the lease to migrate
const VERBOSE = process.env.VERBOSE || false;
interface JobDefinition {
account: sbv2.JobAccount;
data: sbv2.types.JobAccountData;
oracleJob: OracleJob;
}
interface AggregatorDefinition {
account: sbv2.AggregatorAccount;
data: sbv2.types.AggregatorAccountData;
historyBufferLength?: number;
// permissions
permissionAccount: sbv2.PermissionAccount;
permissions: sbv2.types.PermissionAccountData;
// lease
leaseAccount: sbv2.LeaseAccount;
lease: sbv2.types.LeaseAccountData;
// jobs
jobs: Array<JobDefinition>;
}
type AggregatorDefinitionWithLeaseBalance = AggregatorDefinition & {
balance: number;
};
async function main() {
const [dirPath, feedDirPath, jobDirPath] = setupOutputDir(
'2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG'
);
const devnetConnection = new Connection(
process.env.SOLANA_DEVNET_RPC ?? clusterApiUrl('devnet')
);
console.log(`rpcUrl: ${devnetConnection.rpcEndpoint}`);
const payer = sbv2.SwitchboardTestContextV2.loadKeypair(
'~/.config/solana/id.json'
);
console.log(`payer: ${payer.publicKey.toBase58()}`);
const oldProgram = await sbv2.SwitchboardProgram.load(
'devnet',
devnetConnection,
payer,
new PublicKey('2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG')
);
const oldProgramAccounts = await oldProgram.getProgramAccounts();
const [queueAccount, queue] = await sbv2.QueueAccount.load(
oldProgram,
sbv2.SWITCHBOARD_LABS_DEVNET_PERMISSIONLESS_QUEUE
);
// const newProgram = await sbv2.SwitchboardProgram.load(
// 'devnet',
// devnetConnection,
// payer,
// sbv2.SBV2_MAINNET_PID
// );
// const newProgramAccounts = await newProgram.getProgramAccounts();
// load all devnet permissionless aggregators
const allAggregators = Array.from(oldProgramAccounts.aggregators.entries());
console.log(`Found ${allAggregators.length} aggregators on old programId`);
const permissionlessAggregators = allAggregators.filter(
([aggregatorKey, aggregator]) =>
aggregator.queuePubkey.equals(
sbv2.SWITCHBOARD_LABS_DEVNET_PERMISSIONLESS_QUEUE
)
);
console.log(
`Found ${permissionlessAggregators.length} aggregators on permissionless queue`
);
const aggregators = new Map<string, AggregatorDefinition>();
for (const [aggregatorKey, aggregator] of permissionlessAggregators) {
// check permission account exists
const [permissionAccount] = sbv2.PermissionAccount.fromSeed(
oldProgram,
queue.authority,
queueAccount.publicKey,
new PublicKey(aggregatorKey)
);
const permissions = oldProgramAccounts.permissions.get(
permissionAccount.publicKey.toBase58()
);
if (!permissions) {
if (VERBOSE) {
console.error(`No permissions found for aggregator ${aggregatorKey}`);
}
continue;
}
// check lease account exists and has an active balance
const [leaseAccount] = sbv2.LeaseAccount.fromSeed(
oldProgram,
sbv2.SWITCHBOARD_LABS_DEVNET_PERMISSIONLESS_QUEUE,
new PublicKey(aggregatorKey)
);
const lease = oldProgramAccounts.leases.get(
leaseAccount.publicKey.toBase58()
);
if (!lease) {
if (VERBOSE) {
console.error(`No lease found for aggregator ${aggregatorKey}`);
}
continue;
}
const jobPubkeys = aggregator.jobPubkeysData.slice(
0,
aggregator.jobPubkeysSize
);
const aggregatorJobs: Array<JobDefinition> = [];
for (const job of jobPubkeys) {
const jobData = oldProgramAccounts.jobs.get(job.toBase58());
if (!jobData) {
if (VERBOSE) {
console.error(`Failed to find jobData for ${job}`);
}
continue;
}
let oracleJob: OracleJob | undefined = undefined;
try {
oracleJob = OracleJob.decodeDelimited(jobData.data);
} catch (error) {
if (VERBOSE) {
console.error(`Failed to decode job ${job.toBase58()}, ${error}`);
}
}
if (!oracleJob) {
continue;
}
const jobDefinition = {
account: new sbv2.JobAccount(oldProgram, job),
data: jobData,
oracleJob: oracleJob,
};
aggregatorJobs.push(jobDefinition);
}
let historyBufferLength: number | undefined = undefined;
if (!aggregator.historyBuffer.equals(PublicKey.default)) {
const buffer = oldProgramAccounts.buffers.get(
aggregator.historyBuffer.toBase58()
);
if (buffer) {
historyBufferLength = Math.ceil((buffer.byteLength - 8) / 28);
} else {
console.error(
`Failed to fetch history buffer for aggregator ${aggregatorKey} (${aggregator.historyBuffer})`
);
}
}
aggregators.set(aggregatorKey, {
account: new sbv2.AggregatorAccount(
oldProgram,
new PublicKey(aggregatorKey)
),
data: aggregator,
historyBufferLength,
permissionAccount,
permissions,
leaseAccount,
lease,
jobs: aggregatorJobs,
});
}
// fetch all lease balances in batches of 100
const leaseBalanceMap = new Map<string, number>();
const allLeaseEscrows: Array<PublicKey> = Array.from(
aggregators.entries()
).map(([aggregatorKey, aggregator]) => aggregator.lease.escrow);
const leaseBatches: Array<Array<PublicKey>> = _.chunk(allLeaseEscrows, 100);
for await (const batch of leaseBatches) {
const leaseEscrowAccountInfos = await anchor.utils.rpc.getMultipleAccounts(
oldProgram.connection,
batch
);
for (const leaseEscrowAccountInfo of leaseEscrowAccountInfos) {
try {
const account = spl.AccountLayout.decode(
leaseEscrowAccountInfo!.account.data!
);
const leaseBalance =
Number.parseInt(account.amount.toString()) / LAMPORTS_PER_SOL;
leaseBalanceMap.set(
leaseEscrowAccountInfo!.publicKey.toBase58(),
leaseBalance
);
} catch (error) {
if (VERBOSE) {
console.error(`Failed to fetch lease escrow balance`);
}
}
}
}
const aggregatorToMigrate = new Map<
string,
AggregatorDefinitionWithLeaseBalance
>();
const jobsToMigrate = new Map<string, JobDefinition>();
for (const [aggregatorKey, aggregator] of aggregators.entries()) {
const leaseBalance = leaseBalanceMap.get(
aggregator.lease.escrow.toBase58()
);
if (!leaseBalance) {
continue;
}
if (leaseBalance < LEASE_THRESHOLD) {
console.error(
`Lease is below threshold for aggregator ${aggregatorKey}, lease balance = ${leaseBalance}`
);
continue;
} else {
aggregatorToMigrate.set(aggregatorKey, {
...aggregator,
balance: leaseBalance,
});
for (const job of aggregator.jobs) {
jobsToMigrate.set(job.account.publicKey.toBase58(), job);
}
}
}
console.log(`Found ${aggregatorToMigrate.size} aggregators to migrate`);
console.log(`Found ${jobsToMigrate.size} jobs to migrate`);
const jobPubkeys: Array<string> = [];
for (const [jobKey, job] of jobsToMigrate.entries()) {
try {
const jobInitDefinition = {
data: `[${new Uint8Array(job.data.data)}]`,
name: toUtf8(job.data.name),
authority: job.data.authority.toBase58(),
expiration: job.data.expiration.toNumber(),
variables: undefined,
};
const fileString = JSON.stringify(
{
publicKey: jobKey,
definition: jobInitDefinition,
data: job,
},
jsonReplacers,
2
);
if (fileString.includes('twapTask')) {
const twapTaskPath = path.join(dirPath, 'twapJobs');
fs.mkdirSync(twapTaskPath, { recursive: true });
fs.writeFileSync(path.join(twapTaskPath, `${jobKey}.json`), fileString);
} else {
fs.writeFileSync(path.join(jobDirPath, `${jobKey}.json`), fileString);
}
jobPubkeys.push(jobKey);
} catch (error) {}
}
fs.writeFileSync(path.join(dirPath, 'jobs.txt'), jobPubkeys.join('\n'));
const aggregatorPubkeys: Array<string> = [];
for (const [aggregatorKey, aggregator] of aggregatorToMigrate.entries()) {
try {
const jobs: Array<{ pubkey: string; weight: number }> = [];
for (const [i, job] of aggregator.data.jobPubkeysData
.slice(0, aggregator.data.jobPubkeysSize)
.entries()) {
jobs.push({
pubkey: job.toBase58(),
weight:
aggregator.data.jobWeights.length >= i
? aggregator.data.jobWeights[i] ?? 1
: 1,
});
}
const aggregatorInitDefinition = {
name: toUtf8(aggregator.data.name),
metadata: toUtf8(aggregator.data.metadata),
batchSize: aggregator.data.oracleRequestBatchSize,
minRequiredOracleResults: aggregator.data.minOracleResults,
minRequiredJobResults: aggregator.data.minJobResults,
minUpdateDelaySeconds: aggregator.data.minUpdateDelaySeconds,
startAfter: aggregator.data.startAfter.toNumber(),
varianceThreshold: aggregator.data.varianceThreshold.toBig().toNumber(),
forceReportPeriod: aggregator.data.forceReportPeriod.toNumber(),
expiration: aggregator.data.expiration.toNumber(),
disableCrank: aggregator.data.disableCrank,
authority: aggregator.data.authority.toBase58(),
historyLimit: aggregator.historyBufferLength,
slidingWindow:
aggregator.data.resolutionMode.kind === 'ModeSlidingResolution',
basePriorityFee: aggregator.data.basePriorityFee,
priorityFeeBump: aggregator.data.priorityFeeBump,
priorityFeeBumpPeriod: aggregator.data.priorityFeeBumpPeriod,
maxPriorityFeeMultiplier: aggregator.data.maxPriorityFeeMultiplier,
jobs: jobs,
pushCrank: !aggregator.data.crankPubkey.equals(PublicKey.default),
};
fs.writeFileSync(
path.join(feedDirPath, `${aggregatorKey}.json`),
JSON.stringify(
{
publicKey: aggregatorKey,
definition: aggregatorInitDefinition,
data: aggregator,
},
jsonReplacers,
2
)
);
aggregatorPubkeys.push(aggregatorKey);
} catch (error) {}
}
fs.writeFileSync(
path.join(dirPath, 'aggregators.txt'),
aggregatorPubkeys.join('\n')
);
}
main().catch(error => {
console.error(error);
});

View File

@ -0,0 +1,220 @@
import * as sbv2 from './src';
import { TransactionObject } from './src';
import {
AccountMeta,
clusterApiUrl,
Connection,
PublicKey,
} from '@solana/web3.js';
// import { backOff } from 'exponential-backoff';
import chalk from 'chalk';
import _ from 'lodash';
export const CHECK_ICON = chalk.green('\u2714 ');
export const FAILED_ICON = chalk.red('\u2717 ');
const VERBOSE = process.env.VERBOSE || false;
const BATCH_SIZE = 20;
async function main() {
const devnetConnection = new Connection(
process.env.SOLANA_DEVNET_RPC ?? clusterApiUrl('devnet')
);
console.log(`rpcUrl: ${devnetConnection.rpcEndpoint}`);
const payer = sbv2.SwitchboardTestContextV2.loadKeypair(
'~/.config/solana/id.json'
);
console.log(`payer: ${payer.publicKey.toBase58()}`);
const oldProgram = await sbv2.SwitchboardProgram.load(
'devnet',
devnetConnection,
payer,
new PublicKey('2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG')
);
const programAccounts = await oldProgram.getProgramAccounts();
const remainingAccounts: Array<AccountMeta> = [];
for (const [accountKey, accountData] of Array.from(
programAccounts.permissions.entries()
)) {
if (accountData.bump === 0) {
remainingAccounts.push({
pubkey: new PublicKey(accountKey),
isSigner: false,
isWritable: true,
});
}
}
for (const [accountKey, accountData] of Array.from(
programAccounts.leases.entries()
)) {
if (accountData.bump === 0) {
remainingAccounts.push({
pubkey: new PublicKey(accountKey),
isSigner: false,
isWritable: true,
});
}
}
for (const [accountKey, accountData] of Array.from(
programAccounts.oracles.entries()
)) {
if (accountData.bump === 0) {
remainingAccounts.push({
pubkey: new PublicKey(accountKey),
isSigner: false,
isWritable: true,
});
}
}
if (remainingAccounts.length === 0) {
console.log(`${CHECK_ICON} ALL BUMPS SET`);
}
const batches = _.chunk(remainingAccounts, BATCH_SIZE);
console.log(`Found ${remainingAccounts.length} bumps to set`);
console.log(`Num Batches: ${batches.length}`);
for (const batch of batches) {
try {
const setBumpsIxn = sbv2.types.setBumps(
oldProgram,
{
params: {
stateBump: oldProgram.programState.bump,
},
},
{
state: oldProgram.programState.publicKey,
}
);
setBumpsIxn.keys.push(...batch);
const setBumpsTxn = new TransactionObject(
oldProgram.walletPubkey,
[setBumpsIxn],
[],
undefined
);
const setBumpsSignature = await oldProgram.signAndSend(setBumpsTxn, {
skipConfrimation: true,
});
console.log(`${CHECK_ICON} ${setBumpsSignature}`);
} catch (error) {
console.log(`${FAILED_ICON} failed to send batch, ${error}`);
for (const key of batch) {
console.log(`\t - ${key.pubkey.toBase58()}`);
}
}
}
// const [aggregatorAccount, aggregator] = await AggregatorAccount.load(
// oldProgram,
// '3pGhzWwW9AjnF2iRuRWvK2dQAt6YKLP4UJsibYBpozYS'
// );
// console.log(
// `${'AggregatorAccount'.padEnd(20, ' ')}: ${aggregatorAccount.publicKey}`
// );
// const [queueAccount, queue] = await QueueAccount.load(
// oldProgram,
// aggregator.queuePubkey
// );
// const accounts = aggregatorAccount.getAccounts(queueAccount, queue.authority);
// const initialPermissions = await accounts.permissionAccount.loadData();
// const initialLease = await accounts.leaseAccount.loadData();
// console.log(
// `${'PermissionAccount'.padEnd(20, ' ')}: ${
// accounts.permissionAccount.publicKey
// } (${initialPermissions.bump} => ${accounts.permissionBump})`
// );
// console.log(
// `${'LeaseAccount'.padEnd(20, ' ')}: ${
// accounts.permissionAccount.publicKey
// } (${initialLease.bump} => ${accounts.leaseBump})`
// );
// const setBumpsIxn = sbv2.types.setBumps(
// oldProgram,
// {
// params: {
// stateBump: oldProgram.programState.bump,
// },
// },
// {
// state: oldProgram.programState.publicKey,
// }
// );
// setBumpsIxn.keys.push({
// pubkey: accounts.permissionAccount.publicKey,
// isSigner: false,
// isWritable: true,
// });
// setBumpsIxn.keys.push({
// pubkey: accounts.leaseAccount.publicKey,
// isSigner: false,
// isWritable: true,
// });
// const setBumpsTxn = new TransactionObject(
// oldProgram.walletPubkey,
// [setBumpsIxn],
// [],
// undefined
// );
// const setBumpsSignature = await oldProgram.signAndSend(setBumpsTxn);
// console.log(`txnSignature: ${setBumpsSignature}`);
// await sleep(2000);
// const finalPermissionsAccountInfo =
// await oldProgram.connection.getAccountInfo(
// accounts.permissionAccount.publicKey,
// 'processed'
// );
// const finalPermissions = sbv2.types.PermissionAccountData.decode(
// finalPermissionsAccountInfo!.data
// );
// const finalLeaseAccountInfo = await oldProgram.connection.getAccountInfo(
// accounts.leaseAccount.publicKey,
// 'processed'
// );
// const finalLease = sbv2.types.LeaseAccountData.decode(
// finalLeaseAccountInfo!.data
// );
// const parsedTxnLogs =
// await queueAccount.program.connection.getParsedTransaction(
// setBumpsSignature,
// { commitment: 'confirmed' }
// );
// console.log(JSON.stringify(parsedTxnLogs?.meta?.logMessages, undefined, 2));
// assert(
// finalPermissions.bump === accounts.permissionBump,
// `PermissionAccount bump mismatch, expected ${accounts.permissionBump}, received ${finalPermissions.bump}`
// );
// assert(
// finalLease.bump === accounts.leaseBump,
// `LeaseAccount bump mismatch, expected ${accounts.leaseBump}, received ${finalLease.bump}`
// );
}
main().catch(error => {
console.error(error);
});

View File

@ -51,6 +51,7 @@ import {
SendOptions,
Transaction,
TransactionSignature,
VersionedTransaction,
} from '@solana/web3.js';
import { OracleJob } from '@switchboard-xyz/common';
@ -67,7 +68,7 @@ export const DEFAULT_SEND_TRANSACTION_OPTIONS: SendTransactionOptions = {
* Switchboard Devnet Program ID
*/
export const SBV2_DEVNET_PID = new PublicKey(
'2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG'
'SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f'
);
/**
@ -88,11 +89,10 @@ export const getSwitchboardProgramId = (
cluster: Cluster | 'localnet'
): PublicKey => {
switch (cluster) {
case 'mainnet-beta':
return SBV2_MAINNET_PID;
case 'localnet':
case 'devnet':
return SBV2_DEVNET_PID;
case 'mainnet-beta':
return SBV2_MAINNET_PID;
case 'testnet':
default:
throw new Error(`Switchboard PID not found for cluster (${cluster})`);
@ -291,15 +291,7 @@ export class SwitchboardProgram {
? 'devnet'
: 'localnet';
let pid = programId;
if (!pid) {
pid =
programId ?? cluster === 'mainnet-beta'
? SBV2_MAINNET_PID
: cluster === 'devnet'
? SBV2_DEVNET_PID
: SBV2_DEVNET_PID;
}
const pid = programId ?? SBV2_MAINNET_PID;
const programAccountInfo = await connection.getAccountInfo(pid);
if (programAccountInfo === null) {
@ -731,22 +723,46 @@ export class SwitchboardProgram {
}
}
/**
* Check if a transaction object is a VersionedTransaction or not
*
* @param tx
* @returns bool
*/
export const isVersionedTransaction = (tx): tx is VersionedTransaction => {
return 'version' in tx;
};
export class AnchorWallet implements anchor.Wallet {
constructor(readonly payer: Keypair) {
this.payer = payer;
}
constructor(readonly payer: Keypair) {}
get publicKey(): PublicKey {
return this.payer.publicKey;
}
private sign = (txn: Transaction): Transaction => {
txn.partialSign(this.payer);
return txn;
};
async signTransaction(txn: Transaction) {
return this.sign(txn);
async signTransaction<T extends Transaction | VersionedTransaction>(
tx: T
): Promise<T> {
if (isVersionedTransaction(tx)) {
tx.sign([this.payer]);
} else {
tx.partialSign(this.payer);
}
return tx;
}
async signAllTransactions(txns: Transaction[]) {
return txns.map(this.sign);
async signAllTransactions<T extends Transaction | VersionedTransaction>(
txs: T[]
): Promise<T[]> {
return txs.map(t => {
if (isVersionedTransaction(t)) {
t.sign([this.payer]);
} else {
t.partialSign(this.payer);
}
return t;
});
}
}

View File

@ -63,14 +63,14 @@ import { QueueDataBuffer } from './queueDataBuffer';
import { VrfAccount } from './vrfAccount';
export const BUFFER_DISCRIMINATOR = Buffer.from([
42,
55,
46,
46,
45,
52,
78,
78, // BUFFERxx
66,
85,
70,
70,
69,
82,
120,
120, // BUFFERxx
]);
export type SwitchboardAccountType =

View File

@ -163,7 +163,7 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
}
public get slidingWindowKey(): PublicKey {
return anchor.utils.publicKey.findProgramAddressSync(
return PublicKey.findProgramAddressSync(
[Buffer.from('SlidingResultAccountData'), this.publicKey.toBytes()],
this.program.programId
)[0];
@ -850,6 +850,12 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
`Failed to fetch account data for job ${j?.publicKey}`
);
}
if (!j.account.owner.equals(this.program.programId)) {
throw new errors.IncorrectOwner(
this.program.programId,
j.account.owner
);
}
const jobAccount = new JobAccount(this.program, j.publicKey);
const jobState: types.JobAccountData = this.program.coder.decode(
'JobAccountData',
@ -1661,14 +1667,14 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
},
permission: {
publicKey: accounts.permission.publicKey,
bump: accounts.permission.bump,
...accounts.permission.data.toJSON(),
bump: accounts.permission.bump,
},
lease: {
publicKey: accounts.lease.publicKey,
...accounts.lease.data.toJSON(),
bump: accounts.lease.bump,
balance: accounts.lease.balance,
...accounts.lease.data.toJSON(),
},
jobs: accounts.jobs.map(j => {
return {
@ -2043,6 +2049,132 @@ export class AggregatorAccount extends Account<types.AggregatorAccountData> {
const txnSignatures = await program.signAndSendAll(txns);
return txnSignatures;
}
public async closeInstructions(
payer: PublicKey,
params?: {
authority?: Keypair;
tokenWallet?: PublicKey;
},
opts?: TransactionObjectOptions
): Promise<TransactionObject> {
if (this.program.cluster === 'mainnet-beta') {
throw new Error(
`Aggregators can only be closed with the devnet version of Switchboard`
);
}
const [tokenWallet, tokenWalletInit] = params?.tokenWallet
? [params.tokenWallet, undefined]
: await this.program.mint.getOrCreateWrappedUserInstructions(payer, {
fundUpTo: 0,
});
const aggregator = await this.loadData();
const [queueAccount, queue] = await QueueAccount.load(
this.program,
aggregator.queuePubkey
);
const {
permissionAccount,
permissionBump,
leaseAccount,
leaseBump,
leaseEscrow,
} = this.getAccounts(queueAccount, queue.authority);
const crankPubkey: PublicKey | null = aggregator.crankPubkey.equals(
PublicKey.default
)
? null
: aggregator.crankPubkey;
let dataBuffer: PublicKey | null = null;
if (crankPubkey !== null) {
const [crankAccount, crank] = await CrankAccount.load(
this.program,
crankPubkey
);
dataBuffer = crank.dataBuffer;
}
const closeIxn = await (
(this.program as any)._program as anchor.Program
).methods
.aggregatorClose({
stateBump: this.program.programState.bump,
permissionBump: permissionBump,
leaseBump: leaseBump,
})
.accounts(
crankPubkey !== null && dataBuffer !== null
? {
authority: aggregator.authority,
aggregator: this.publicKey,
permission: permissionAccount.publicKey,
lease: leaseAccount.publicKey,
escrow: leaseEscrow,
oracleQueue: queueAccount.publicKey,
queueAuthority: queue.authority,
programState: queueAccount.program.programState.publicKey,
solDest: payer,
escrowDest: tokenWallet,
tokenProgram: TOKEN_PROGRAM_ID,
crank: crankPubkey,
dataBuffer: dataBuffer,
}
: ({
authority: aggregator.authority,
aggregator: this.publicKey,
permission: permissionAccount.publicKey,
lease: leaseAccount.publicKey,
escrow: leaseEscrow,
oracleQueue: queueAccount.publicKey,
queueAuthority: queue.authority,
programState: queueAccount.program.programState.publicKey,
solDest: payer,
escrowDest: tokenWallet,
tokenProgram: TOKEN_PROGRAM_ID,
crank: null,
dataBuffer: null,
} as any) // compiler expects all types to be pubkeys
)
.instruction();
const closeTxn = tokenWalletInit
? tokenWalletInit.add(
closeIxn,
params?.authority ? [params.authority] : undefined
)
: new TransactionObject(
payer,
[closeIxn],
params?.authority ? [params.authority] : []
);
// add txn options
return new TransactionObject(
closeTxn.payer,
closeTxn.ixns,
closeTxn.signers,
opts
);
}
public async close(
params?: {
authority?: Keypair;
tokenWallet?: PublicKey;
},
opts?: TransactionObjectOptions
): Promise<TransactionSignature> {
const closeTxn = await this.closeInstructions(
this.program.walletPubkey,
params,
opts
);
const txnSignature = await this.program.signAndSend(closeTxn);
return txnSignature;
}
}
/**

View File

@ -464,8 +464,8 @@ export class BufferRelayerAccount extends Account<types.BufferRelayerAccountData
},
permission: {
publicKey: permission.publicKey,
bump: permission.bump,
...permission.data.toJSON(),
bump: permission.bump,
},
};
}

View File

@ -8,7 +8,6 @@ import { AggregatorAccount } from './aggregatorAccount';
import { JobAccount } from './jobAccount';
import { QueueAccount } from './queueAccount';
import * as anchor from '@coral-xyz/anchor';
import * as spl from '@solana/spl-token';
import {
AccountInfo,
@ -103,7 +102,7 @@ export class LeaseAccount extends Account<types.LeaseAccountData> {
queue: PublicKey,
aggregator: PublicKey
): [LeaseAccount, number] {
const [publicKey, bump] = anchor.utils.publicKey.findProgramAddressSync(
const [publicKey, bump] = PublicKey.findProgramAddressSync(
[Buffer.from('LeaseAccountData'), queue.toBytes(), aggregator.toBytes()],
program.programId
);
@ -589,7 +588,7 @@ export class LeaseAccount extends Account<types.LeaseAccountData> {
if (!jobAuthority || PublicKey.default.equals(jobAuthority)) {
continue;
}
const [jobWallet, bump] = anchor.utils.publicKey.findProgramAddressSync(
const [jobWallet, bump] = PublicKey.findProgramAddressSync(
[
jobAuthority.toBuffer(),
spl.TOKEN_PROGRAM_ID.toBuffer(),

View File

@ -164,7 +164,7 @@ export class OracleAccount extends Account<types.OracleAccountData> {
queue: PublicKey,
wallet: PublicKey
): [OracleAccount, number] {
const [publicKey, bump] = anchor.utils.publicKey.findProgramAddressSync(
const [publicKey, bump] = PublicKey.findProgramAddressSync(
[Buffer.from('OracleAccountData'), queue.toBuffer(), wallet.toBuffer()],
program.programId
);

View File

@ -11,7 +11,6 @@ import { TransactionObject } from '../TransactionObject';
import { Account } from './account';
import * as anchor from '@coral-xyz/anchor';
import { ACCOUNT_DISCRIMINATOR_SIZE } from '@coral-xyz/anchor';
import {
AccountInfo,
@ -158,7 +157,7 @@ export class PermissionAccount extends Account<types.PermissionAccountData> {
granter: PublicKey,
grantee: PublicKey
): [PermissionAccount, number] {
const [publicKey, bump] = anchor.utils.publicKey.findProgramAddressSync(
const [publicKey, bump] = PublicKey.findProgramAddressSync(
[
Buffer.from('PermissionAccountData'),
authority.toBytes(),

View File

@ -239,7 +239,7 @@ export class ProgramStateAccount extends Account<types.SbState> {
public static fromSeed(
program: SwitchboardProgram
): [ProgramStateAccount, number] {
const [publicKey, bump] = anchor.utils.publicKey.findProgramAddressSync(
const [publicKey, bump] = PublicKey.findProgramAddressSync(
[Buffer.from('STATE')],
program.programId
);

View File

@ -20,20 +20,20 @@ export const SWITCHBOARD_LABS_MAINNET_PERMISSIONLESS_CRANK = new PublicKey(
// devnet permissioned queue
export const SWITCHBOARD_LABS_DEVNET_PERMISSIONED_QUEUE = new PublicKey(
'GhYg3R1V6DmJbwuc57qZeoYG6gUuvCotUF1zU3WCj98U'
'PeRMnAqNqHQYHUuCBEjhm1XPeVTh4BxjY4t4TPan1pG'
);
// devnet permissioned crank
export const SWITCHBOARD_LABS_DEVNET_PERMISSIONED_CRANK = new PublicKey(
'85L2cFUvXaeGQ4HrzP8RJEVCL7WvRrXM2msvEmQ82AVr'
'crnKsPsuP6f7uiDbAYYw66h2RNBrqoazmtZHwazkC6V'
);
// devnet permissionless queue
export const SWITCHBOARD_LABS_DEVNET_PERMISSIONLESS_QUEUE = new PublicKey(
'F8ce7MsckeZAbAGmxjJNetxYXQa9mKr9nnrC3qKubyYy'
'uPeRMdfPmrPqgRWSrjAnAkH78RqAhe5kXoW6vBYRqFX'
);
// devnet permissionless crank
export const SWITCHBOARD_LABS_DEVNET_PERMISSIONLESS_CRANK = new PublicKey(
'GN9jjCy2THzZxhYqZETmPM3my8vg4R5JyNkgULddUMa5'
'UcrnK4w2HXCEjY8z6TcQ9tysYr3c9VcFLdYAU9YQP5e'
);
export const DEVNET_GENESIS_HASH =

View File

@ -92,3 +92,11 @@ export class AggregatorConfigError extends Error {
Object.setPrototypeOf(this, AggregatorConfigError.prototype);
}
}
export class IncorrectOwner extends Error {
constructor(expectedOwner: PublicKey, receivedOwner: PublicKey) {
super(
`incorrect account owner, expected ${expectedOwner}, received ${receivedOwner}`
);
Object.setPrototypeOf(this, IncorrectOwner.prototype);
}
}

View File

@ -23,6 +23,8 @@ export interface LeaseAccountDataFields {
updateCount: BN;
/** Public key of keypair that may withdraw funds from the lease at any time */
withdrawAuthority: PublicKey;
/** The PDA bump to derive the pubkey. */
bump: number;
ebuf: Array<number>;
}
@ -45,6 +47,8 @@ export interface LeaseAccountDataJSON {
updateCount: string;
/** Public key of keypair that may withdraw funds from the lease at any time */
withdrawAuthority: string;
/** The PDA bump to derive the pubkey. */
bump: number;
ebuf: Array<number>;
}
@ -68,6 +72,8 @@ export class LeaseAccountData {
readonly updateCount: BN;
/** Public key of keypair that may withdraw funds from the lease at any time */
readonly withdrawAuthority: PublicKey;
/** The PDA bump to derive the pubkey. */
readonly bump: number;
readonly ebuf: Array<number>;
static readonly discriminator = Buffer.from([
@ -84,7 +90,8 @@ export class LeaseAccountData {
borsh.i64('createdAt'),
borsh.u128('updateCount'),
borsh.publicKey('withdrawAuthority'),
borsh.array(borsh.u8(), 256, 'ebuf'),
borsh.u8('bump'),
borsh.array(borsh.u8(), 255, 'ebuf'),
]);
constructor(fields: LeaseAccountDataFields) {
@ -97,6 +104,7 @@ export class LeaseAccountData {
this.createdAt = fields.createdAt;
this.updateCount = fields.updateCount;
this.withdrawAuthority = fields.withdrawAuthority;
this.bump = fields.bump;
this.ebuf = fields.ebuf;
}
@ -151,6 +159,7 @@ export class LeaseAccountData {
createdAt: dec.createdAt,
updateCount: dec.updateCount,
withdrawAuthority: dec.withdrawAuthority,
bump: dec.bump,
ebuf: dec.ebuf,
});
}
@ -166,6 +175,7 @@ export class LeaseAccountData {
createdAt: this.createdAt.toString(),
updateCount: this.updateCount.toString(),
withdrawAuthority: this.withdrawAuthority.toString(),
bump: this.bump,
ebuf: this.ebuf,
};
}
@ -181,6 +191,7 @@ export class LeaseAccountData {
createdAt: new BN(obj.createdAt),
updateCount: new BN(obj.updateCount),
withdrawAuthority: new PublicKey(obj.withdrawAuthority),
bump: obj.bump,
ebuf: obj.ebuf,
});
}

View File

@ -21,6 +21,8 @@ export interface OracleAccountDataFields {
queuePubkey: PublicKey;
/** Oracle track record. */
metrics: types.OracleMetricsFields;
/** The PDA bump to derive the pubkey. */
bump: number;
/** Reserved for future info. */
ebuf: Array<number>;
}
@ -42,6 +44,8 @@ export interface OracleAccountDataJSON {
queuePubkey: string;
/** Oracle track record. */
metrics: types.OracleMetricsJSON;
/** The PDA bump to derive the pubkey. */
bump: number;
/** Reserved for future info. */
ebuf: Array<number>;
}
@ -63,6 +67,8 @@ export class OracleAccountData {
readonly queuePubkey: PublicKey;
/** Oracle track record. */
readonly metrics: types.OracleMetrics;
/** The PDA bump to derive the pubkey. */
readonly bump: number;
/** Reserved for future info. */
readonly ebuf: Array<number>;
@ -79,7 +85,8 @@ export class OracleAccountData {
borsh.publicKey('tokenAccount'),
borsh.publicKey('queuePubkey'),
types.OracleMetrics.layout('metrics'),
borsh.array(borsh.u8(), 256, 'ebuf'),
borsh.u8('bump'),
borsh.array(borsh.u8(), 255, 'ebuf'),
]);
constructor(fields: OracleAccountDataFields) {
@ -91,6 +98,7 @@ export class OracleAccountData {
this.tokenAccount = fields.tokenAccount;
this.queuePubkey = fields.queuePubkey;
this.metrics = new types.OracleMetrics({ ...fields.metrics });
this.bump = fields.bump;
this.ebuf = fields.ebuf;
}
@ -144,6 +152,7 @@ export class OracleAccountData {
tokenAccount: dec.tokenAccount,
queuePubkey: dec.queuePubkey,
metrics: types.OracleMetrics.fromDecoded(dec.metrics),
bump: dec.bump,
ebuf: dec.ebuf,
});
}
@ -158,6 +167,7 @@ export class OracleAccountData {
tokenAccount: this.tokenAccount.toString(),
queuePubkey: this.queuePubkey.toString(),
metrics: this.metrics.toJSON(),
bump: this.bump,
ebuf: this.ebuf,
};
}
@ -172,6 +182,7 @@ export class OracleAccountData {
tokenAccount: new PublicKey(obj.tokenAccount),
queuePubkey: new PublicKey(obj.queuePubkey),
metrics: types.OracleMetrics.fromJSON(obj.metrics),
bump: obj.bump,
ebuf: obj.ebuf,
});
}

View File

@ -19,6 +19,8 @@ export interface PermissionAccountDataFields {
* per account makes sense for the infra. Dont over engineer.
*/
expiration: BN;
/** The PDA bump to derive the pubkey. */
bump: number;
/** Reserved for future info. */
ebuf: Array<number>;
}
@ -38,6 +40,8 @@ export interface PermissionAccountDataJSON {
* per account makes sense for the infra. Dont over engineer.
*/
expiration: string;
/** The PDA bump to derive the pubkey. */
bump: number;
/** Reserved for future info. */
ebuf: Array<number>;
}
@ -57,6 +61,8 @@ export class PermissionAccountData {
* per account makes sense for the infra. Dont over engineer.
*/
readonly expiration: BN;
/** The PDA bump to derive the pubkey. */
readonly bump: number;
/** Reserved for future info. */
readonly ebuf: Array<number>;
@ -70,7 +76,8 @@ export class PermissionAccountData {
borsh.publicKey('granter'),
borsh.publicKey('grantee'),
borsh.i64('expiration'),
borsh.array(borsh.u8(), 256, 'ebuf'),
borsh.u8('bump'),
borsh.array(borsh.u8(), 255, 'ebuf'),
]);
constructor(fields: PermissionAccountDataFields) {
@ -79,6 +86,7 @@ export class PermissionAccountData {
this.granter = fields.granter;
this.grantee = fields.grantee;
this.expiration = fields.expiration;
this.bump = fields.bump;
this.ebuf = fields.ebuf;
}
@ -129,6 +137,7 @@ export class PermissionAccountData {
granter: dec.granter,
grantee: dec.grantee,
expiration: dec.expiration,
bump: dec.bump,
ebuf: dec.ebuf,
});
}
@ -140,6 +149,7 @@ export class PermissionAccountData {
granter: this.granter.toString(),
grantee: this.grantee.toString(),
expiration: this.expiration.toString(),
bump: this.bump,
ebuf: this.ebuf,
};
}
@ -151,6 +161,7 @@ export class PermissionAccountData {
granter: new PublicKey(obj.granter),
grantee: new PublicKey(obj.grantee),
expiration: new BN(obj.expiration),
bump: obj.bump,
ebuf: obj.ebuf,
});
}

View File

@ -13,7 +13,11 @@ export interface SbStateFields {
tokenVault: PublicKey;
/** The token mint used by the DAO. */
daoMint: PublicKey;
/** Reserved for future info. */
/**
* Reserved for future info.
* The PDA bump to derive the pubkey.
*/
bump: number;
ebuf: Array<number>;
}
@ -26,7 +30,11 @@ export interface SbStateJSON {
tokenVault: string;
/** The token mint used by the DAO. */
daoMint: string;
/** Reserved for future info. */
/**
* Reserved for future info.
* The PDA bump to derive the pubkey.
*/
bump: number;
ebuf: Array<number>;
}
@ -39,7 +47,11 @@ export class SbState {
readonly tokenVault: PublicKey;
/** The token mint used by the DAO. */
readonly daoMint: PublicKey;
/** Reserved for future info. */
/**
* Reserved for future info.
* The PDA bump to derive the pubkey.
*/
readonly bump: number;
readonly ebuf: Array<number>;
static readonly discriminator = Buffer.from([
@ -51,7 +63,8 @@ export class SbState {
borsh.publicKey('tokenMint'),
borsh.publicKey('tokenVault'),
borsh.publicKey('daoMint'),
borsh.array(borsh.u8(), 992, 'ebuf'),
borsh.u8('bump'),
borsh.array(borsh.u8(), 991, 'ebuf'),
]);
constructor(fields: SbStateFields) {
@ -59,6 +72,7 @@ export class SbState {
this.tokenMint = fields.tokenMint;
this.tokenVault = fields.tokenVault;
this.daoMint = fields.daoMint;
this.bump = fields.bump;
this.ebuf = fields.ebuf;
}
@ -108,6 +122,7 @@ export class SbState {
tokenMint: dec.tokenMint,
tokenVault: dec.tokenVault,
daoMint: dec.daoMint,
bump: dec.bump,
ebuf: dec.ebuf,
});
}
@ -118,6 +133,7 @@ export class SbState {
tokenMint: this.tokenMint.toString(),
tokenVault: this.tokenVault.toString(),
daoMint: this.daoMint.toString(),
bump: this.bump,
ebuf: this.ebuf,
};
}
@ -128,6 +144,7 @@ export class SbState {
tokenMint: new PublicKey(obj.tokenMint),
tokenVault: new PublicKey(obj.tokenVault),
daoMint: new PublicKey(obj.daoMint),
bump: obj.bump,
ebuf: obj.ebuf,
});
}

View File

@ -0,0 +1,71 @@
import { SwitchboardProgram } from '../../SwitchboardProgram';
import {
TransactionInstruction,
PublicKey,
AccountMeta,
} from '@solana/web3.js'; // eslint-disable-line @typescript-eslint/no-unused-vars
import { BN } from '@switchboard-xyz/common'; // eslint-disable-line @typescript-eslint/no-unused-vars
import * as borsh from '@coral-xyz/borsh'; // eslint-disable-line @typescript-eslint/no-unused-vars
import * as types from '../types'; // eslint-disable-line @typescript-eslint/no-unused-vars
export interface AggregatorCloseArgs {
params: types.AggregatorCloseParamsFields;
}
export interface AggregatorCloseAccounts {
authority: PublicKey;
aggregator: PublicKey;
permission: PublicKey;
lease: PublicKey;
escrow: PublicKey;
oracleQueue: PublicKey;
queueAuthority: PublicKey;
programState: PublicKey;
solDest: PublicKey;
escrowDest: PublicKey;
tokenProgram: PublicKey;
/** Optional accounts */
crank: PublicKey;
dataBuffer: PublicKey;
}
export const layout = borsh.struct([
types.AggregatorCloseParams.layout('params'),
]);
export function aggregatorClose(
program: SwitchboardProgram,
args: AggregatorCloseArgs,
accounts: AggregatorCloseAccounts
) {
const keys: Array<AccountMeta> = [
{ pubkey: accounts.authority, isSigner: true, isWritable: false },
{ pubkey: accounts.aggregator, isSigner: false, isWritable: true },
{ pubkey: accounts.permission, isSigner: false, isWritable: true },
{ pubkey: accounts.lease, isSigner: false, isWritable: true },
{ pubkey: accounts.escrow, isSigner: false, isWritable: true },
{ pubkey: accounts.oracleQueue, isSigner: false, isWritable: false },
{ pubkey: accounts.queueAuthority, isSigner: false, isWritable: false },
{ pubkey: accounts.programState, isSigner: false, isWritable: false },
{ pubkey: accounts.solDest, isSigner: false, isWritable: false },
{ pubkey: accounts.escrowDest, isSigner: false, isWritable: true },
{ pubkey: accounts.tokenProgram, isSigner: false, isWritable: false },
{ pubkey: accounts.crank, isSigner: false, isWritable: true },
{ pubkey: accounts.dataBuffer, isSigner: false, isWritable: true },
];
const identifier = Buffer.from([77, 29, 85, 88, 224, 181, 157, 69]);
const buffer = Buffer.alloc(1000);
const len = layout.encode(
{
params: types.AggregatorCloseParams.toEncodable(args.params),
},
buffer
);
const data = Buffer.concat([identifier, buffer]).slice(0, 8 + len);
const ix = new TransactionInstruction({
keys,
programId: program.programId,
data,
});
return ix;
}

View File

@ -1,3 +1,10 @@
export { aggregatorClose } from './aggregatorClose';
export type {
AggregatorCloseArgs,
AggregatorCloseAccounts,
} from './aggregatorClose';
export { setBumps } from './setBumps';
export type { SetBumpsArgs, SetBumpsAccounts } from './setBumps';
export { aggregatorAddJob } from './aggregatorAddJob';
export type {
AggregatorAddJobArgs,

View File

@ -0,0 +1,44 @@
import { SwitchboardProgram } from '../../SwitchboardProgram';
import {
TransactionInstruction,
PublicKey,
AccountMeta,
} from '@solana/web3.js'; // eslint-disable-line @typescript-eslint/no-unused-vars
import { BN } from '@switchboard-xyz/common'; // eslint-disable-line @typescript-eslint/no-unused-vars
import * as borsh from '@coral-xyz/borsh'; // eslint-disable-line @typescript-eslint/no-unused-vars
import * as types from '../types'; // eslint-disable-line @typescript-eslint/no-unused-vars
export interface SetBumpsArgs {
params: types.SetBumpsParamsFields;
}
export interface SetBumpsAccounts {
state: PublicKey;
}
export const layout = borsh.struct([types.SetBumpsParams.layout('params')]);
export function setBumps(
program: SwitchboardProgram,
args: SetBumpsArgs,
accounts: SetBumpsAccounts
) {
const keys: Array<AccountMeta> = [
{ pubkey: accounts.state, isSigner: false, isWritable: true },
];
const identifier = Buffer.from([19, 216, 193, 244, 22, 47, 180, 64]);
const buffer = Buffer.alloc(1000);
const len = layout.encode(
{
params: types.SetBumpsParams.toEncodable(args.params),
},
buffer
);
const data = Buffer.concat([identifier, buffer]).slice(0, 8 + len);
const ix = new TransactionInstruction({
keys,
programId: program.programId,
data,
});
return ix;
}

View File

@ -0,0 +1,77 @@
import { SwitchboardProgram } from '../../SwitchboardProgram';
import { PublicKey } from '@solana/web3.js'; // eslint-disable-line @typescript-eslint/no-unused-vars
import { BN } from '@switchboard-xyz/common'; // eslint-disable-line @typescript-eslint/no-unused-vars
import * as types from '../types'; // eslint-disable-line @typescript-eslint/no-unused-vars
import * as borsh from '@coral-xyz/borsh';
export interface AggregatorCloseParamsFields {
stateBump: number;
permissionBump: number;
leaseBump: number;
}
export interface AggregatorCloseParamsJSON {
stateBump: number;
permissionBump: number;
leaseBump: number;
}
export class AggregatorCloseParams {
readonly stateBump: number;
readonly permissionBump: number;
readonly leaseBump: number;
constructor(fields: AggregatorCloseParamsFields) {
this.stateBump = fields.stateBump;
this.permissionBump = fields.permissionBump;
this.leaseBump = fields.leaseBump;
}
static layout(property?: string) {
return borsh.struct(
[
borsh.u8('stateBump'),
borsh.u8('permissionBump'),
borsh.u8('leaseBump'),
],
property
);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static fromDecoded(obj: any) {
return new AggregatorCloseParams({
stateBump: obj.stateBump,
permissionBump: obj.permissionBump,
leaseBump: obj.leaseBump,
});
}
static toEncodable(fields: AggregatorCloseParamsFields) {
return {
stateBump: fields.stateBump,
permissionBump: fields.permissionBump,
leaseBump: fields.leaseBump,
};
}
toJSON(): AggregatorCloseParamsJSON {
return {
stateBump: this.stateBump,
permissionBump: this.permissionBump,
leaseBump: this.leaseBump,
};
}
static fromJSON(obj: AggregatorCloseParamsJSON): AggregatorCloseParams {
return new AggregatorCloseParams({
stateBump: obj.stateBump,
permissionBump: obj.permissionBump,
leaseBump: obj.leaseBump,
});
}
toEncodable() {
return AggregatorCloseParams.toEncodable(this);
}
}

View File

@ -0,0 +1,54 @@
import { SwitchboardProgram } from '../../SwitchboardProgram';
import { PublicKey } from '@solana/web3.js'; // eslint-disable-line @typescript-eslint/no-unused-vars
import { BN } from '@switchboard-xyz/common'; // eslint-disable-line @typescript-eslint/no-unused-vars
import * as types from '../types'; // eslint-disable-line @typescript-eslint/no-unused-vars
import * as borsh from '@coral-xyz/borsh';
export interface SetBumpsParamsFields {
stateBump: number;
}
export interface SetBumpsParamsJSON {
stateBump: number;
}
export class SetBumpsParams {
readonly stateBump: number;
constructor(fields: SetBumpsParamsFields) {
this.stateBump = fields.stateBump;
}
static layout(property?: string) {
return borsh.struct([borsh.u8('stateBump')], property);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static fromDecoded(obj: any) {
return new SetBumpsParams({
stateBump: obj.stateBump,
});
}
static toEncodable(fields: SetBumpsParamsFields) {
return {
stateBump: fields.stateBump,
};
}
toJSON(): SetBumpsParamsJSON {
return {
stateBump: this.stateBump,
};
}
static fromJSON(obj: SetBumpsParamsJSON): SetBumpsParams {
return new SetBumpsParams({
stateBump: obj.stateBump,
});
}
toEncodable() {
return SetBumpsParams.toEncodable(this);
}
}

View File

@ -18,6 +18,11 @@ export type {
AggregatorAddJobParamsFields,
AggregatorAddJobParamsJSON,
} from './AggregatorAddJobParams';
export { AggregatorCloseParams } from './AggregatorCloseParams';
export type {
AggregatorCloseParamsFields,
AggregatorCloseParamsJSON,
} from './AggregatorCloseParams';
export { AggregatorHistoryRow } from './AggregatorHistoryRow';
export type {
AggregatorHistoryRowFields,
@ -271,6 +276,11 @@ export type {
} from './ProjectivePointZC';
export { Scalar } from './Scalar';
export type { ScalarFields, ScalarJSON } from './Scalar';
export { SetBumpsParams } from './SetBumpsParams';
export type {
SetBumpsParamsFields,
SetBumpsParamsJSON,
} from './SetBumpsParams';
export { SlidingWindowElement } from './SlidingWindowElement';
export type {
SlidingWindowElementFields,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -117,7 +117,7 @@ export class Mint {
owner: PublicKey,
mint: PublicKey
): PublicKey {
const [associatedToken] = anchor.utils.publicKey.findProgramAddressSync(
const [associatedToken] = PublicKey.findProgramAddressSync(
[owner.toBuffer(), spl.TOKEN_PROGRAM_ID.toBuffer(), mint.toBuffer()],
spl.ASSOCIATED_TOKEN_PROGRAM_ID
);

View File

@ -23,7 +23,7 @@ export const LATEST_DOCKER_VERSION = 'dev-v2-RC_01_05_23_03_24';
* @return the publicKey of the address holding the upgradeable program buffer
*/
export const getProgramDataAddress = (programId: PublicKey): PublicKey => {
return anchor.utils.publicKey.findProgramAddressSync(
return PublicKey.findProgramAddressSync(
[programId.toBytes()],
new PublicKey('BPFLoaderUpgradeab1e11111111111111111111111')
)[0];

View File

@ -0,0 +1,217 @@
/* eslint-disable no-unused-vars */
import 'mocha';
import * as sbv2 from '../src';
import { CrankAccount, QueueAccount } from '../src';
import { setupTest, TestContext } from './utils';
import { Keypair, PublicKey } from '@solana/web3.js';
import { OracleJob, sleep } from '@switchboard-xyz/common';
import assert from 'assert';
describe('Close Aggregator Tests', () => {
let ctx: TestContext;
const queueAuthority = Keypair.generate();
let queueAccount: QueueAccount;
let payerTokenWallet: PublicKey;
let crankAccount: CrankAccount;
before(async () => {
if (process.env.SOLANA_LOCALNET) {
return;
}
ctx = await setupTest();
[queueAccount] = await sbv2.QueueAccount.create(ctx.program, {
name: 'aggregator-queue',
metadata: '',
authority: queueAuthority.publicKey,
queueSize: 1,
reward: 0,
minStake: 0,
oracleTimeout: 86400,
slashingEnabled: false,
unpermissionedFeeds: true,
unpermissionedVrf: true,
enableBufferRelayers: false,
});
[crankAccount] = await queueAccount.createCrank({
maxRows: 10,
name: 'Crank-1',
});
payerTokenWallet = await ctx.program.mint.getOrCreateAssociatedUser(
ctx.program.walletPubkey
);
// add a single oracle for open round calls
const [oracleAccount] = await queueAccount.createOracle({
name: 'oracle-1',
enable: true,
queueAuthority: queueAuthority,
});
await oracleAccount.heartbeat();
});
it('Creates and closes an aggregator not on a crank', async () => {
const [aggregatorAccount] = await queueAccount.createFeed({
queueAuthority: queueAuthority,
batchSize: 1,
minRequiredOracleResults: 1,
minRequiredJobResults: 1,
minUpdateDelaySeconds: 60,
enable: true,
historyLimit: 1000,
jobs: [
{
data: OracleJob.encodeDelimited(
OracleJob.fromObject({
tasks: [
{
valueTask: {
value: 1,
},
},
],
})
).finish(),
name: 'Job1',
},
],
});
console.log(`Created aggregator ${aggregatorAccount.publicKey}`);
const {
permissionAccount,
permissionBump,
leaseAccount,
leaseBump,
leaseEscrow,
} = aggregatorAccount.getAccounts(queueAccount, queueAuthority.publicKey);
await sleep(1000);
const initialAggregatorState = await aggregatorAccount.loadData();
console.log(`loaded aggregator, ${aggregatorAccount.publicKey}`);
const closeTxn = await aggregatorAccount.closeInstructions(
ctx.program.walletPubkey
);
const closeSig = await ctx.program.signAndSend(closeTxn, {
skipPreflight: true,
});
console.log(closeSig);
const parsedTxnLogs =
await queueAccount.program.connection.getParsedTransaction(closeSig);
console.log(JSON.stringify(parsedTxnLogs?.meta?.logMessages, undefined, 2));
const finalAggregatorState =
await queueAccount.program.connection.getAccountInfo(
aggregatorAccount.publicKey
);
assert(finalAggregatorState === null, 'AggregatorAccount was not closed');
const finalPermissionState =
await queueAccount.program.connection.getAccountInfo(
permissionAccount.publicKey
);
assert(finalPermissionState === null, 'PermissionAccount was not closed');
const finalLeaseState =
await queueAccount.program.connection.getAccountInfo(
leaseAccount.publicKey
);
assert(finalLeaseState === null, 'LeaseAccount was not closed');
});
it('Creates and closes an aggregator with a crank', async () => {
const [aggregatorAccount] = await queueAccount.createFeed({
queueAuthority: queueAuthority,
batchSize: 1,
minRequiredOracleResults: 1,
minRequiredJobResults: 1,
minUpdateDelaySeconds: 60,
enable: true,
crankPubkey: crankAccount.publicKey,
historyLimit: 1000,
jobs: [
{
data: OracleJob.encodeDelimited(
OracleJob.fromObject({
tasks: [
{
valueTask: {
value: 1,
},
},
],
})
).finish(),
name: 'Job1',
},
],
});
console.log(`Created aggregator ${aggregatorAccount.publicKey}`);
const {
permissionAccount,
permissionBump,
leaseAccount,
leaseBump,
leaseEscrow,
} = aggregatorAccount.getAccounts(queueAccount, queueAuthority.publicKey);
console.log(`permission: ${permissionAccount.publicKey}`);
const initialAggregatorState = await aggregatorAccount.loadData();
const initialCrankRows = await crankAccount.loadCrank();
const crankIdx = initialCrankRows.findIndex(r =>
r.pubkey.equals(aggregatorAccount.publicKey)
);
assert(crankIdx !== -1, 'Aggregator initially missing from the crank');
const closeTxn = await aggregatorAccount.closeInstructions(
ctx.program.walletPubkey
);
const closeSig = await ctx.program.signAndSend(closeTxn, {
skipPreflight: true,
});
console.log(closeSig);
const parsedTxnLogs =
await queueAccount.program.connection.getParsedTransaction(closeSig);
console.log(JSON.stringify(parsedTxnLogs?.meta?.logMessages, undefined, 2));
const finalAggregatorState =
await queueAccount.program.connection.getAccountInfo(
aggregatorAccount.publicKey
);
assert(finalAggregatorState === null, 'AggregatorAccount was not closed');
const finalPermissionState =
await queueAccount.program.connection.getAccountInfo(
permissionAccount.publicKey
);
assert(finalPermissionState === null, 'PermissionAccount was not closed');
const finalLeaseState =
await queueAccount.program.connection.getAccountInfo(
leaseAccount.publicKey
);
assert(finalLeaseState === null, 'LeaseAccount was not closed');
const finalCrankRows = await crankAccount.loadCrank();
const newCrankIdx = finalCrankRows.findIndex(r =>
r.pubkey.equals(aggregatorAccount.publicKey)
);
assert(newCrankIdx === -1, 'Aggregator is still on the crank');
});
});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,219 @@
import * as sbv2 from './src';
import { PublicKey } from '@solana/web3.js';
import { Big, BN, IOracleJob, toUtf8 } from '@switchboard-xyz/common';
import fs from 'fs';
import path from 'path';
const ignoreFields = [
'program',
'jobHashes',
'jobsChecksum',
'currentRound',
'latestConfirmedRound',
];
export function setupOutputDir(programId: string) {
const dirPath = path.join(__dirname, programId);
const feedDirPath = path.join(dirPath, 'feeds');
if (!fs.existsSync(feedDirPath)) {
fs.mkdirSync(feedDirPath, { recursive: true });
}
const jobDirPath = path.join(dirPath, 'jobs');
if (!fs.existsSync(jobDirPath)) {
fs.mkdirSync(jobDirPath, { recursive: true });
}
// const jobCsv = path.join(__dirname, 'jobs.csv');
// if (!fs.existsSync(jobCsv)) {
// fs.writeFileSync(jobCsv, `oldPubkey, newPubkey\n`);
// }
// const aggregatorCsv = path.join(__dirname, 'aggregator.csv');
// if (!fs.existsSync(aggregatorCsv)) {
// fs.writeFileSync(aggregatorCsv, `oldPubkey, newPubkey\n`);
// }
return [dirPath, feedDirPath, jobDirPath];
}
export function jsonReplacers(key, value) {
if (ignoreFields.includes(key)) {
return undefined;
}
if (
!value ||
typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'boolean'
) {
return value;
}
if (
key === 'ebuf' ||
key === '_ebuf' ||
key === 'reserved' ||
key === 'reserved1'
) {
return;
}
if (value instanceof PublicKey) {
return value.toBase58();
}
if ((key === 'name' || key === 'metadata') && Array.isArray(value)) {
return toUtf8(value);
}
if (Array.isArray(value) && value.length > 0) {
if (typeof value[0] === 'number') {
if (value.every(item => item === 0)) {
return [];
}
return `[${value.join(',')}]`;
}
if (value[0] instanceof PublicKey) {
return value.filter(
pubkey => !(pubkey as PublicKey).equals(PublicKey.default)
);
}
}
if (
value instanceof sbv2.types.SwitchboardDecimal ||
('mantissa' in value && 'scale' in value)
) {
const big = sbv2.types.SwitchboardDecimal.from(value).toBig();
return big.toString();
}
if (value instanceof Big) {
return value.toString();
}
if (BN.isBN(value)) {
return value.toString();
}
return value;
}
export interface IAggregatorDefinition {
account: {
publicKey: string;
size: number;
};
data: {
name: string;
metadata: string;
queuePubkey: string;
oracleRequestBatchSize: number;
minOracleResults: number;
minJobResults: number;
minUpdateDelaySeconds: number;
startAfter: string;
varianceThreshold: string;
forceReportPeriod: string;
expiration: string;
consecutiveFailureCount: string;
nextAllowedUpdateTime: string;
isLocked: boolean;
crankPubkey: string;
jobPubkeysData: Array<string>;
authority: string;
historyBuffer: string;
resolutionMode: {
kind: string;
};
basePriorityFee: number;
priorityFeeBump: number;
priorityFeeBumpPeriod: number;
maxPriorityFeeMultiplier: number;
};
permissionAccount: {
publicKey: string;
size: number;
};
permissions: {
authority: string;
permissions: number;
granter: string;
grantee: string;
expiration: string;
};
leaseAccount: {
publicKey: string;
size: number;
};
lease: {
escrow: string;
queue: string;
aggregator: string;
tokenProgram: string;
isActive: boolean;
crankRowCount: number;
createdAt: string;
withdrawAuthority: string;
};
balance: number;
jobs: Array<IJobDefinition>;
}
export interface Aggregator {
publicKey: string;
definition: {
name: string;
metadata: string;
batchSize: number;
minRequiredOracleResults: number;
minRequiredJobResults: number;
minUpdateDelaySeconds: number;
historyBufferLength?: number;
startAfter: number;
varianceThreshold: number;
forceReportPeriod: number;
expiration: number;
pushCrank: boolean;
disableCrank: boolean;
authority: string;
slidingWindow: boolean;
basePriorityFee: number;
priorityFeeBump: number;
priorityFeeBumpPeriod: number;
maxPriorityFeeMultiplier: number;
jobs: Array<{ pubkey: string; weight: number }>;
};
data: IAggregatorDefinition;
}
export interface IJobDefinition {
account: {
publicKey: string;
};
data: {
name: string;
metadata: string;
authority: string;
expiration: string;
hash: string;
data: string;
referenceCount: number;
totalSpent: string;
createdAt: string;
isInitializing: number;
};
oracleJob: IOracleJob;
}
export interface Job {
publicKey: string;
definition: {
data: string;
name: string;
authority: string;
expiration: number;
};
data: IJobDefinition;
}

View File

@ -53,8 +53,8 @@ importers:
javascript/solana.js:
specifiers:
'@coral-xyz/anchor': ^0.26.0
'@coral-xyz/borsh': ^0.26.0
'@coral-xyz/anchor': ^0.27.0
'@coral-xyz/borsh': ^0.27.0
'@solana/spl-token': ^0.3.6
'@solana/web3.js': ^1.73.0
'@switchboard-xyz/common': ^2.1.33
@ -69,6 +69,7 @@ importers:
chalk: ^4.1.2
dotenv: ^16.0.3
eslint: ^8.35.0
exponential-backoff: ^3.1.1
gts: ^3.1.1
lodash: ^4.17.21
mocha: ^10.1.0
@ -79,8 +80,8 @@ importers:
typedoc: ^0.23.23
typescript: ^4.9.4
dependencies:
'@coral-xyz/anchor': 0.26.0
'@coral-xyz/borsh': 0.26.0_@solana+web3.js@1.73.0
'@coral-xyz/anchor': 0.27.0
'@coral-xyz/borsh': 0.27.0_@solana+web3.js@1.73.0
'@solana/spl-token': 0.3.6_@solana+web3.js@1.73.0
'@solana/web3.js': 1.73.0
'@switchboard-xyz/common': 2.1.33
@ -97,6 +98,7 @@ importers:
chai: 4.3.7
chalk: 4.1.2
eslint: 8.35.0
exponential-backoff: 3.1.1
gts: 3.1.1_typescript@4.9.4
mocha: 10.1.0
shelljs: 0.8.5
@ -108,7 +110,7 @@ importers:
programs/anchor-buffer-parser:
specifiers:
'@coral-xyz/anchor': ^0.26.0
'@coral-xyz/anchor': ^0.27.0
'@solana/web3.js': ^1.73.3
'@switchboard-xyz/common': ^2.1.33
'@switchboard-xyz/solana.js': workspace:*
@ -123,7 +125,7 @@ importers:
ts-node: ^10.4.0
typescript: ^4.7
dependencies:
'@coral-xyz/anchor': 0.26.0
'@coral-xyz/anchor': 0.27.0
'@solana/web3.js': 1.73.3
'@switchboard-xyz/common': 2.1.33
'@switchboard-xyz/solana.js': link:../../javascript/solana.js
@ -141,7 +143,7 @@ importers:
programs/anchor-feed-parser:
specifiers:
'@coral-xyz/anchor': ^0.26.0
'@coral-xyz/anchor': ^0.27.0
'@solana/web3.js': ^1.73.3
'@switchboard-xyz/common': ^2.1.33
'@switchboard-xyz/solana.js': workspace:*
@ -154,7 +156,7 @@ importers:
ts-node: ^10.4.0
typescript: ^4.7
dependencies:
'@coral-xyz/anchor': 0.26.0
'@coral-xyz/anchor': 0.27.0
'@solana/web3.js': 1.73.3
'@switchboard-xyz/common': 2.1.33
'@switchboard-xyz/solana.js': link:../../javascript/solana.js
@ -170,7 +172,7 @@ importers:
programs/anchor-history-parser:
specifiers:
'@coral-xyz/anchor': ^0.26.0
'@coral-xyz/anchor': ^0.27.0
'@switchboard-xyz/common': ^2.1.33
'@switchboard-xyz/solana.js': workspace:*
'@types/bn.js': ^5.1.0
@ -182,7 +184,7 @@ importers:
ts-mocha: ^10.0.0
typescript: ^4.3.5
dependencies:
'@coral-xyz/anchor': 0.26.0
'@coral-xyz/anchor': 0.27.0
'@switchboard-xyz/common': 2.1.33
'@switchboard-xyz/solana.js': link:../../javascript/solana.js
devDependencies:
@ -197,8 +199,8 @@ importers:
programs/anchor-vrf-parser:
specifiers:
'@coral-xyz/anchor': ^0.26.0
'@coral-xyz/borsh': ^0.26.0
'@coral-xyz/anchor': ^0.27.0
'@coral-xyz/borsh': ^0.27.0
'@project-serum/borsh': ^0.2.5
'@solana/spl-token': ^0.3.6
'@solana/web3.js': ^1.73.3
@ -223,8 +225,8 @@ importers:
typescript: ^4.9.3
yargs: ^17.5.1
dependencies:
'@coral-xyz/anchor': 0.26.0
'@coral-xyz/borsh': 0.26.0_@solana+web3.js@1.73.3
'@coral-xyz/anchor': 0.27.0
'@coral-xyz/borsh': 0.27.0_@solana+web3.js@1.73.3
'@project-serum/borsh': 0.2.5_@solana+web3.js@1.73.3
'@solana/spl-token': 0.3.6_@solana+web3.js@1.73.3
'@solana/web3.js': 1.73.3
@ -252,7 +254,7 @@ importers:
programs/native-feed-parser:
specifiers:
'@coral-xyz/anchor': ^0.26.0
'@coral-xyz/anchor': ^0.27.0
'@solana/web3.js': ^1.73.3
'@switchboard-xyz/common': ^2.1.33
'@switchboard-xyz/solana.js': workspace:*
@ -265,7 +267,7 @@ importers:
ts-node: ^10.4.0
typescript: ^4.7
dependencies:
'@coral-xyz/anchor': 0.26.0
'@coral-xyz/anchor': 0.27.0
'@solana/web3.js': 1.73.3
'@switchboard-xyz/common': 2.1.33
'@switchboard-xyz/solana.js': link:../../javascript/solana.js
@ -508,9 +510,46 @@ packages:
- utf-8-validate
dev: false
/@coral-xyz/borsh/0.26.0_@solana+web3.js@1.73.0:
/@coral-xyz/anchor/0.27.0:
resolution: {integrity: sha512-+P/vPdORawvg3A9Wj02iquxb4T0C5m4P6aZBVYysKl4Amk+r6aMPZkUhilBkD6E4Nuxnoajv3CFykUfkGE0n5g==}
engines: {node: '>=11'}
dependencies:
'@coral-xyz/borsh': 0.27.0_@solana+web3.js@1.73.3
'@solana/web3.js': 1.73.3
base64-js: 1.5.1
bn.js: 5.2.1
bs58: 4.0.1
buffer-layout: 1.2.2
camelcase: 6.3.0
cross-fetch: 3.1.5
crypto-hash: 1.3.0
eventemitter3: 4.0.7
js-sha256: 0.9.0
pako: 2.0.4
snake-case: 3.0.4
superstruct: 0.15.5
toml: 3.0.0
transitivePeerDependencies:
- bufferutil
- encoding
- supports-color
- utf-8-validate
dev: false
/@coral-xyz/borsh/0.26.0_@solana+web3.js@1.73.3:
resolution: {integrity: sha512-uCZ0xus0CszQPHYfWAqKS5swS1UxvePu83oOF+TWpUkedsNlg6p2p4azxZNSSqwXb9uXMFgxhuMBX9r3Xoi0vQ==}
engines: {node: '>=10'}
peerDependencies:
'@solana/web3.js': ^1.68.0
dependencies:
'@solana/web3.js': 1.73.3
bn.js: 5.2.1
buffer-layout: 1.2.2
dev: false
/@coral-xyz/borsh/0.27.0_@solana+web3.js@1.73.0:
resolution: {integrity: sha512-tJKzhLukghTWPLy+n8K8iJKgBq1yLT/AxaNd10yJrX8mI56ao5+OFAKAqW/h0i79KCvb4BK0VGO5ECmmolFz9A==}
engines: {node: '>=10'}
peerDependencies:
'@solana/web3.js': ^1.68.0
dependencies:
@ -519,8 +558,8 @@ packages:
buffer-layout: 1.2.2
dev: false
/@coral-xyz/borsh/0.26.0_@solana+web3.js@1.73.3:
resolution: {integrity: sha512-uCZ0xus0CszQPHYfWAqKS5swS1UxvePu83oOF+TWpUkedsNlg6p2p4azxZNSSqwXb9uXMFgxhuMBX9r3Xoi0vQ==}
/@coral-xyz/borsh/0.27.0_@solana+web3.js@1.73.3:
resolution: {integrity: sha512-tJKzhLukghTWPLy+n8K8iJKgBq1yLT/AxaNd10yJrX8mI56ao5+OFAKAqW/h0i79KCvb4BK0VGO5ECmmolFz9A==}
engines: {node: '>=10'}
peerDependencies:
'@solana/web3.js': ^1.68.0
@ -2803,6 +2842,10 @@ packages:
strip-final-newline: 2.0.0
dev: true
/exponential-backoff/3.1.1:
resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==}
dev: true
/external-editor/3.1.0:
resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}
engines: {node: '>=4'}

View File

@ -26,16 +26,16 @@ url = "https://api.devnet.solana.com"
startup_wait = 15000
[[test.validator.clone]] # sbv2 devnet programID
address = "2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG"
address = "SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f"
[[test.validator.clone]] # sbv2 devnet IDL
address = "CKwZcshn4XDvhaWVH9EXnk3iu19t6t5xP2Sy2pD6TRDp"
address = "Fi8vncGpNKbq62gPo56G4toCehWNy77GgqGkTaAF5Lkk"
[[test.validator.clone]] # sbv2 devnet SbState
address = "BYM81n8HvTJuqZU1PmTVcwZ9G8uoji7FKM6EaPkwphPt"
address = "CyZuD7RPDcrqCGbNvLCyqk6Py9cEZTKmNKujfPi3ynDd"
[[test.validator.clone]] # sbv2 devnet tokenVault
address = "FVLfR6C2ckZhbSwBzZY4CX7YBcddUSge5BNeGQv5eKhy"
address = "7hkp1xfPBcD2t1vZMoWWQPzipHVcXeLAAaiGXdPSfDie"
[[test.validator.clone]] # sbv2 SOL/USD Feed
address="GvDMxPzN1sCj7L26YDK2HnMRXEQmQ2aemov8YBtPS7vR"

View File

@ -19,5 +19,5 @@ cpi = ["no-entrypoint"]
[dependencies]
switchboard-v2 = { path = "../../rust/switchboard-v2" }
# switchboard-v2 = { version = "^0.1.20" }
anchor-lang = "^0.26.0"
anchor-lang = "^0.27.0"
solana-program = "~1.14.0"

View File

@ -12,7 +12,7 @@
"lint": "eslint --ext .js,.json,.ts 'src/**' --fix"
},
"dependencies": {
"@coral-xyz/anchor": "^0.26.0",
"@coral-xyz/anchor": "^0.27.0",
"@solana/web3.js": "^1.73.3",
"@switchboard-xyz/common": "^2.1.33",
"@switchboard-xyz/solana.js": "workspace:*",

View File

@ -26,16 +26,16 @@ url = "https://api.devnet.solana.com"
startup_wait = 15000
[[test.validator.clone]] # sbv2 devnet programID
address = "2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG"
address = "SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f"
[[test.validator.clone]] # sbv2 devnet IDL
address = "CKwZcshn4XDvhaWVH9EXnk3iu19t6t5xP2Sy2pD6TRDp"
address = "Fi8vncGpNKbq62gPo56G4toCehWNy77GgqGkTaAF5Lkk"
[[test.validator.clone]] # sbv2 devnet SbState
address = "BYM81n8HvTJuqZU1PmTVcwZ9G8uoji7FKM6EaPkwphPt"
address = "CyZuD7RPDcrqCGbNvLCyqk6Py9cEZTKmNKujfPi3ynDd"
[[test.validator.clone]] # sbv2 devnet tokenVault
address = "FVLfR6C2ckZhbSwBzZY4CX7YBcddUSge5BNeGQv5eKhy"
address = "7hkp1xfPBcD2t1vZMoWWQPzipHVcXeLAAaiGXdPSfDie"
[[test.validator.clone]] # sbv2 SOL/USD Feed
address="GvDMxPzN1sCj7L26YDK2HnMRXEQmQ2aemov8YBtPS7vR"

View File

@ -19,6 +19,6 @@ cpi = ["no-entrypoint"]
[dependencies]
switchboard-v2 = { path = "../../rust/switchboard-v2" }
# switchboard-v2 = { version = "0.1.20" }
anchor-lang = "^0.26.0"
anchor-lang = "^0.27.0"
solana-program = "~1.14.0"
bytemuck = "1.7.2"

View File

@ -12,7 +12,7 @@
"lint": "eslint --ext .js,.json,.ts 'src/**' --fix"
},
"dependencies": {
"@coral-xyz/anchor": "^0.26.0",
"@coral-xyz/anchor": "^0.27.0",
"@solana/web3.js": "^1.73.3",
"@switchboard-xyz/common": "^2.1.33",
"@switchboard-xyz/solana.js": "workspace:*"

View File

@ -29,16 +29,16 @@ url = "https://api.devnet.solana.com"
startup_wait = 15000
[[test.validator.clone]] # sbv2 devnet programID
address = "2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG"
address = "SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f"
[[test.validator.clone]] # sbv2 devnet IDL
address = "CKwZcshn4XDvhaWVH9EXnk3iu19t6t5xP2Sy2pD6TRDp"
address = "Fi8vncGpNKbq62gPo56G4toCehWNy77GgqGkTaAF5Lkk"
[[test.validator.clone]] # sbv2 devnet SbState
address = "BYM81n8HvTJuqZU1PmTVcwZ9G8uoji7FKM6EaPkwphPt"
address = "CyZuD7RPDcrqCGbNvLCyqk6Py9cEZTKmNKujfPi3ynDd"
[[test.validator.clone]] # sbv2 devnet tokenVault
address = "FVLfR6C2ckZhbSwBzZY4CX7YBcddUSge5BNeGQv5eKhy"
address = "7hkp1xfPBcD2t1vZMoWWQPzipHVcXeLAAaiGXdPSfDie"
[[test.validator.clone]] # sbv2 SOL/USD Feed
address="GvDMxPzN1sCj7L26YDK2HnMRXEQmQ2aemov8YBtPS7vR"

View File

@ -22,5 +22,5 @@ overflow-checks = true
[dependencies]
switchboard-v2 = { path = "../../rust/switchboard-v2" }
# switchboard-v2 = { version = "^0.1.20" }
anchor-lang = "^0.26.0"
anchor-lang = "^0.27.0"
solana-program = "^1.13.6"

View File

@ -13,7 +13,7 @@
"lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
},
"dependencies": {
"@coral-xyz/anchor": "^0.26.0",
"@coral-xyz/anchor": "^0.27.0",
"@switchboard-xyz/common": "^2.1.33",
"@switchboard-xyz/solana.js": "workspace:*"
},

View File

@ -27,13 +27,13 @@ url = "https://api.devnet.solana.com"
startup_wait = 15000
[[test.validator.clone]] # sbv2 devnet programID
address = "2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG"
address = "SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f"
[[test.validator.clone]] # sbv2 devnet IDL
address = "CKwZcshn4XDvhaWVH9EXnk3iu19t6t5xP2Sy2pD6TRDp"
address = "Fi8vncGpNKbq62gPo56G4toCehWNy77GgqGkTaAF5Lkk"
[[test.validator.clone]] # sbv2 devnet SbState
address = "BYM81n8HvTJuqZU1PmTVcwZ9G8uoji7FKM6EaPkwphPt"
address = "CyZuD7RPDcrqCGbNvLCyqk6Py9cEZTKmNKujfPi3ynDd"
[[test.validator.clone]] # sbv2 devnet tokenVault
address = "FVLfR6C2ckZhbSwBzZY4CX7YBcddUSge5BNeGQv5eKhy"
address = "7hkp1xfPBcD2t1vZMoWWQPzipHVcXeLAAaiGXdPSfDie"

View File

@ -9,8 +9,7 @@ crate-type = ["cdylib", "lib"]
name = "anchor_vrf_parser"
[features]
default = ["devnet"]
devnet = ["switchboard-v2/devnet"]
default = []
no-entrypoint = []
no-idl = []
no-log-ix-name = []
@ -19,7 +18,7 @@ cpi = ["no-entrypoint"]
[dependencies]
switchboard-v2 = { path = "../../rust/switchboard-v2" }
# switchboard-v2 = { version = "^0.1.20" }
anchor-lang = "^0.26.0"
anchor-spl = "^0.26.0"
anchor-lang = "^0.27.0"
anchor-spl = "^0.27.0"
solana-program = "~1.14.0"
bytemuck = "1.7.2"

View File

@ -1,769 +0,0 @@
#!/usr/bin/env ts-node-esm
/* eslint-disable @typescript-eslint/no-loop-func */
/* eslint-disable @typescript-eslint/no-unused-expressions */
/* eslint-disable @typescript-eslint/no-var-requires */
import * as anchor from "@project-serum/anchor";
import * as spl from "@solana/spl-token-v2";
import {
clusterApiUrl,
Connection,
ParsedTransactionWithMeta,
PublicKey,
} from "@solana/web3.js";
import * as sbv2Utils from "@switchboard-xyz/sbv2-utils";
import { toVrfStatusString } from "@switchboard-xyz/sbv2-utils";
import * as sbv2 from "@switchboard-xyz/switchboard-v2";
import chalk from "chalk";
const yargs = require("yargs");
const { hideBin } = require("yargs/helpers");
// import yargs from "yargs";
// import { hideBin } from "yargs/helpers";
import fs from "fs";
import path from "path";
import { AnchorVrfParser, IDL } from "./target/types/anchor_vrf_parser";
import { VrfClient } from "./client/accounts";
import { PROGRAM_ID } from "./client/programId";
// const DEFAULT_MAINNET_RPC = "https://ssc-dao.genesysgo.net";
// const DEFAULT_DEVNET_RPC = "https://devnet.genesysgo.net";
const DEFAULT_LOCALNET_RPC = "http://localhost:8899";
const DEFAULT_COMMITMENT = "confirmed";
const VRF_REQUEST_AMOUNT = BigInt(2_000_000);
interface RequestRandomnessResult {
success: boolean;
status: string;
counter: number;
txRemaining: number;
producer: string;
alpha: string;
alphaHex: string;
proof?: string;
proofHex?: string;
proofBase64?: string;
result: string;
stage?: number;
txs?: ParsedTransactionWithMeta[] | string[] | any[];
}
yargs(hideBin(process.argv))
.scriptName("sbv2-vrf-example")
.command(
"create [queueKey]",
"create a VRF client",
(y: any) => {
return y
.positional("queueKey", {
type: "string",
describe: "publicKey of the oracle queue to create a VRF for",
default: "F8ce7MsckeZAbAGmxjJNetxYXQa9mKr9nnrC3qKubyYy",
})
.option("maxResult", {
description: "test",
type: "number",
});
},
async function (argv: any) {
const { queueKey, rpcUrl, cluster, maxResult } = argv;
const { vrfClientProgram, switchboardProgram, payer, provider } =
await loadCli(rpcUrl, cluster);
const vrfSecret = anchor.web3.Keypair.generate();
const [vrfClientKey, vrfClientBump] =
anchor.utils.publicKey.findProgramAddressSync(
[
Buffer.from("STATE"),
vrfSecret.publicKey.toBytes(),
payer.publicKey.toBytes(),
],
vrfClientProgram.programId
);
const vrfIxCoder = new anchor.BorshInstructionCoder(vrfClientProgram.idl);
const vrfClientCallback = {
programId: vrfClientProgram.programId,
accounts: [
// ensure all accounts in updateResult are populated
{ pubkey: vrfClientKey, isSigner: false, isWritable: true },
{ pubkey: vrfSecret.publicKey, isSigner: false, isWritable: false },
],
ixData: vrfIxCoder.encode("updateResult", ""), // pass any params for instruction here
};
const queueAccount = new sbv2.OracleQueueAccount({
program: switchboardProgram,
publicKey: new anchor.web3.PublicKey(queueKey),
});
const { unpermissionedVrfEnabled, authority, dataBuffer } =
await queueAccount.loadData();
// Create Switchboard VRF and Permission account
const vrfAccount = await sbv2.VrfAccount.create(switchboardProgram, {
queue: queueAccount,
callback: vrfClientCallback,
authority: vrfClientKey, // vrf authority
keypair: vrfSecret,
});
await sbv2Utils.sleep(2000);
const { escrow } = await vrfAccount.loadData();
console.log(sbv2Utils.chalkString("VRF Account", vrfAccount.publicKey));
const permissionAccount = await sbv2.PermissionAccount.create(
switchboardProgram,
{
authority,
granter: queueAccount.publicKey,
grantee: vrfAccount.publicKey,
}
);
// console.log(`Created Permission Account: ${permissionAccount.publicKey}`);
// If queue requires permissions to use VRF, check the correct authority was provided
if (!unpermissionedVrfEnabled) {
if (!payer.publicKey.equals(authority)) {
throw new Error(
`queue requires PERMIT_VRF_REQUESTS and wrong queue authority provided`
);
}
await permissionAccount.set({
authority: payer,
permission: sbv2.SwitchboardPermission.PERMIT_VRF_REQUESTS,
enable: true,
});
}
// Create VRF Client account
await vrfClientProgram.methods
.initState({
maxResult: new anchor.BN(maxResult),
})
.accounts({
state: vrfClientKey,
vrf: vrfAccount.publicKey,
payer: payer.publicKey,
authority: payer.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
console.log(sbv2Utils.chalkString("VRF Client", vrfClientKey));
console.log(`\nRun the following command to request randomness`);
console.log(
`\t${chalk.yellow(
"npx sbv2-vrf-example request",
vrfClientKey.toString()
)}`
);
}
)
.command(
"request [vrfClientKey]",
"request randomness for a VRF client",
(y: any) => {
return y.positional("vrfClientKey", {
type: "string",
describe: "publicKey of the VRF client to request randomness for",
demand: true,
});
},
async function (argv: any) {
const { vrfClientKey, rpcUrl, cluster } = argv;
if (!vrfClientKey) {
throw new Error(`Must provide vrfClientKey arguement`);
}
const { vrfClientProgram, switchboardProgram, payer, provider } =
await loadCli((rpcUrl as string) ?? "", (cluster as string) ?? "");
const vrfClient = await VrfClient.fetch(
provider.connection,
vrfClientProgram.programId,
new anchor.web3.PublicKey(vrfClientKey)
);
if (!vrfClient) {
throw new Error(
`Failed to fetch VrfClient for account ${vrfClientKey}`
);
}
const vrfAccount = new sbv2.VrfAccount({
program: switchboardProgram,
publicKey: vrfClient.vrf,
});
const vrfData = await vrfAccount.loadData();
const vrfEscrow = await spl.getAccount(
provider.connection,
vrfData.escrow,
DEFAULT_COMMITMENT,
spl.TOKEN_PROGRAM_ID
);
const queueAccount = new sbv2.OracleQueueAccount({
program: switchboardProgram,
publicKey: vrfData.oracleQueue,
});
const queueData = await queueAccount.loadData();
const mint = await queueAccount.loadMint();
const payerTokenAccount = await spl.getOrCreateAssociatedTokenAccount(
provider.connection,
payer,
mint.address,
payer.publicKey,
undefined,
undefined,
undefined,
spl.TOKEN_PROGRAM_ID,
spl.ASSOCIATED_TOKEN_PROGRAM_ID
);
const tokensNeeded = VRF_REQUEST_AMOUNT - vrfEscrow.amount;
if (tokensNeeded > 0 && payerTokenAccount.amount < tokensNeeded) {
throw new Error(
`Payer token account has insufficient funds, need 2_000_000, current ${payerTokenAccount.amount}`
);
}
const [programStateAccount, programStateBump] =
sbv2.ProgramStateAccount.fromSeed(switchboardProgram);
const [permissionAccount, permissionBump] =
sbv2.PermissionAccount.fromSeed(
switchboardProgram,
queueData.authority,
queueAccount.publicKey,
vrfAccount.publicKey
);
let ws: number | undefined = undefined;
const waitForResultPromise = new Promise(
(
resolve: (result: anchor.BN) => void,
reject: (reason: string) => void
) => {
ws = vrfClientProgram.provider.connection.onAccountChange(
new anchor.web3.PublicKey(vrfClientKey),
async (
accountInfo: anchor.web3.AccountInfo<Buffer>,
context: anchor.web3.Context
) => {
const clientState = VrfClient.decode(accountInfo.data);
if (clientState.result.gt(new anchor.BN(0))) {
resolve(clientState.result);
}
}
);
}
).then(async (result) => {
console.log(
sbv2Utils.chalkString("Client Result", result.toString(10))
);
if (ws) {
await vrfClientProgram.provider.connection.removeAccountChangeListener(
ws
);
}
ws = undefined;
return result;
});
const requestTxnPromise = vrfClientProgram.methods
.requestResult({
switchboardStateBump: programStateBump,
permissionBump,
})
.accounts({
state: vrfClientKey,
authority: payer.publicKey,
switchboardProgram: switchboardProgram.programId,
vrf: vrfAccount.publicKey,
oracleQueue: queueAccount.publicKey,
queueAuthority: queueData.authority,
dataBuffer: queueData.dataBuffer,
permission: permissionAccount.publicKey,
escrow: vrfData.escrow,
payerWallet: payerTokenAccount.address,
payerAuthority: payer.publicKey,
recentBlockhashes: anchor.web3.SYSVAR_RECENT_BLOCKHASHES_PUBKEY,
programState: programStateAccount.publicKey,
tokenProgram: spl.TOKEN_PROGRAM_ID,
})
.signers([payer, payer])
.rpc()
.then((txnSignature) => {
console.log(
`${chalk.yellow("Randomness Requested")}\t${txnSignature}`
);
});
let result: anchor.BN;
try {
result = await sbv2Utils.promiseWithTimeout(
45_000,
waitForResultPromise,
new Error(`Timed out waiting for VRF Client callback`)
);
} catch (error) {
throw error;
} finally {
if (ws) {
await vrfClientProgram.provider.connection.removeAccountChangeListener(
ws
);
}
}
process.exit(0);
}
)
.command(
"loop [vrfClientKey] [numLoops]",
"request randomness for a VRF client N times and record the results",
(y: any) => {
return y
.positional("vrfClientKey", {
type: "string",
describe: "publicKey of the VRF client to request randomness for",
demand: true,
})
.positional("numLoops", {
type: "number",
describe: "number of times to request randomness",
default: 100,
});
},
async function (argv: any) {
const { vrfClientKey, rpcUrl, cluster, numLoops } = argv;
if (!vrfClientKey) {
throw new Error(`Must provide vrfClientKey arguement`);
}
const startTime = Math.floor(Date.now() / 1000);
const vrfClientPubkey = new anchor.web3.PublicKey(vrfClientKey);
const { vrfClientProgram, switchboardProgram, payer, provider } =
await loadCli(rpcUrl as string, cluster as string);
const vrfClient = await VrfClient.fetch(
provider.connection,
vrfClientProgram.programId,
vrfClientPubkey
);
if (!vrfClient) {
throw new Error(
`Failed to fetch VrfClient for account ${vrfClientKey}`
);
}
const vrfAccount = new sbv2.VrfAccount({
program: switchboardProgram,
publicKey: vrfClient.vrf,
});
const vrfData = await vrfAccount.loadData();
const vrfEscrow = await spl.getAccount(
provider.connection,
vrfData.escrow,
DEFAULT_COMMITMENT,
spl.TOKEN_PROGRAM_ID
);
const queueAccount = new sbv2.OracleQueueAccount({
program: switchboardProgram,
publicKey: vrfData.oracleQueue,
});
const queueData = await queueAccount.loadData();
const mint = await queueAccount.loadMint();
const payerTokenAccount = await spl.getOrCreateAssociatedTokenAccount(
provider.connection,
payer,
mint.address,
payer.publicKey,
undefined,
undefined,
undefined,
spl.TOKEN_PROGRAM_ID,
spl.ASSOCIATED_TOKEN_PROGRAM_ID
);
const tokensNeeded = VRF_REQUEST_AMOUNT - vrfEscrow.amount;
if (tokensNeeded > 0 && payerTokenAccount.amount < tokensNeeded) {
throw new Error(
`Payer token account has insufficient funds, need 2_000_000, current ${payerTokenAccount.amount}`
);
}
const [programStateAccount, programStateBump] =
sbv2.ProgramStateAccount.fromSeed(switchboardProgram);
const [permissionAccount, permissionBump] =
sbv2.PermissionAccount.fromSeed(
switchboardProgram,
queueData.authority,
queueAccount.publicKey,
vrfAccount.publicKey
);
const requestInstruction = await vrfClientProgram.methods
.requestResult({
switchboardStateBump: programStateBump,
permissionBump,
})
.accounts({
state: vrfClientPubkey,
authority: payer.publicKey,
switchboardProgram: switchboardProgram.programId,
vrf: vrfAccount.publicKey,
oracleQueue: queueAccount.publicKey,
queueAuthority: queueData.authority,
dataBuffer: queueData.dataBuffer,
permission: permissionAccount.publicKey,
escrow: vrfData.escrow,
payerWallet: payerTokenAccount.address,
payerAuthority: payer.publicKey,
recentBlockhashes: anchor.web3.SYSVAR_RECENT_BLOCKHASHES_PUBKEY,
programState: programStateAccount.publicKey,
tokenProgram: spl.TOKEN_PROGRAM_ID,
})
.signers([payer])
.instruction();
const results: RequestRandomnessResult[] = [];
let successes = 0;
let failures = 0;
try {
for await (const i of Array.from(Array(numLoops).keys())) {
let success = false;
try {
while (true) {
try {
await provider.sendAndConfirm(
new anchor.web3.Transaction().add(requestInstruction),
[payer]
);
// console.log("sent");
break;
} catch (error) {
if (!error.message.includes("0x17b5")) {
throw error;
}
await sbv2Utils.sleep(2500);
}
}
await awaitCallback(provider.connection, vrfClientPubkey, 90_000)
.then(() => {
success = true;
successes++;
clearStatus();
writeStatus(i, numLoops, successes, failures);
})
.catch(async (error) => {
failures++;
const vrf = await vrfAccount.loadData();
clearStatus();
console.error(error.message);
writeVrfState(vrf);
writeStatus(i, numLoops, successes, failures);
});
} catch (error) {}
const vrf = await vrfAccount.loadData();
const vrfStatus = sbv2Utils.toVrfStatusString(vrf.status);
const result: RequestRandomnessResult = {
success: vrfStatus === "StatusCallbackSuccess",
counter: vrf.counter.toString(10),
producer: vrf.builders[0].producer.toString(),
status: vrfStatus,
txRemaining: vrf.builders[0].txRemaining,
alpha: `[${vrf.currentRound.alpha.slice(0, 32).toString()}]`,
alphaHex: Buffer.from(vrf.currentRound.alpha.slice(0, 32)).toString(
"hex"
),
proof: `[${vrf.builders[0].reprProof.slice(0, 80).toString()}]`,
proofHex: Buffer.from(
vrf.builders[0].reprProof.slice(0, 80)
).toString("hex"),
proofBase64: Buffer.from(
vrf.builders[0].reprProof.slice(0, 80)
).toString("base64"),
result: `[${vrf.currentRound.result.toString()}]`,
stage: vrf.builders[0].stage,
txs:
vrfStatus === "StatusCallbackSuccess"
? undefined
: await fetchTransactions(
vrfAccount.program.provider.connection,
vrfAccount.publicKey,
15
),
};
results.push(result);
saveResults(startTime, vrfClientKey, results);
clearStatus();
writeStatus(i, numLoops, successes, failures);
}
} catch (error) {
console.error(`GLOBAL: ${error}`);
}
writeStatus(numLoops, numLoops, successes, failures);
saveResults(startTime, vrfClientKey, results);
process.exit(0);
}
)
.options({
cluster: {
type: "string",
alias: "c",
describe: "Solana cluster to interact with",
options: ["devnet", "mainnet-beta", "localnet"],
default: "devnet",
demand: false,
},
rpcUrl: {
type: "string",
alias: "u",
describe: "Alternative RPC URL",
},
})
.help().argv;
function getRpcUrl(cluster: string): string {
switch (cluster) {
case "mainnet-beta":
return clusterApiUrl("mainnet-beta");
case "devnet":
return clusterApiUrl("devnet");
case "localnet":
return DEFAULT_LOCALNET_RPC;
default:
throw new Error(`Failed to find RPC_URL for cluster ${cluster}`);
}
}
function writeVrfState(vrf: any) {
console.log(`Status: ${toVrfStatusString(vrf.builders[0]?.status) ?? ""}`);
console.log(`TxRemaining: ${vrf.builders[0]?.txRemaining ?? ""}`);
console.log(
`Alpha: [${vrf.currentRound.alpha
.slice(0, 32)
.map((value) => value.toString())}]`
);
console.log(
`AlphaHex: ${Buffer.from(vrf.currentRound.alpha.slice(0, 32)).toString(
"hex"
)}`
);
console.log(
`Proof: [${vrf.builders[0].reprProof
.slice(0, 80)
.map((value) => value.toString())}]`
);
console.log(
`ProofHex: ${Buffer.from(vrf.builders[0].reprProof.slice(0, 80)).toString(
"hex"
)}`
);
console.log(
`ProofBase64: ${Buffer.from(
vrf.builders[0].reprProof.slice(0, 80)
).toString("base64")}`
);
console.log(`Stage: ${vrf.builders[0].stage}`);
}
function saveResults(
timestamp: number,
vrfClientKey: anchor.web3.PublicKey,
results: RequestRandomnessResult[]
) {
if (results.length) {
fs.writeFileSync(
path.join(process.cwd(), `${vrfClientKey}_${timestamp}.json`),
JSON.stringify(results, undefined, 2),
"utf-8"
);
}
const errorResults = results.filter((result) => !result.success);
if (errorResults.length) {
fs.writeFileSync(
path.join(process.cwd(), `${vrfClientKey}_ERRORS_${timestamp}.json`),
JSON.stringify(errorResults, undefined, 2),
"utf-8"
);
}
}
async function loadCli(
rpcUrl: string,
cluster: string
): Promise<{
vrfClientProgram: anchor.Program<AnchorVrfParser>;
switchboardProgram: anchor.Program;
payer: anchor.web3.Keypair;
provider: anchor.AnchorProvider;
}> {
if (cluster !== "mainnet-beta" && cluster !== "devnet") {
throw new Error(
`cluster must be mainnet-beta or devnet, cluster = ${cluster}`
);
}
process.env.ANCHOR_WALLET = sbv2Utils.getAnchorWalletPath();
const url = rpcUrl || getRpcUrl(cluster);
const envProvider = anchor.AnchorProvider.local(url);
const provider = new anchor.AnchorProvider(
new anchor.web3.Connection(url, {
commitment: DEFAULT_COMMITMENT,
}),
envProvider.wallet,
{
commitment: DEFAULT_COMMITMENT,
}
);
const switchboardProgram = await sbv2.loadSwitchboardProgram(
cluster,
provider.connection,
(provider.wallet as sbv2.AnchorWallet).payer,
{
commitment: DEFAULT_COMMITMENT,
}
);
const payer = sbv2.programWallet(switchboardProgram);
// load VRF Client program
const vrfClientProgram = new anchor.Program(
IDL,
PROGRAM_ID,
provider,
new anchor.BorshCoder(IDL)
);
return {
vrfClientProgram,
switchboardProgram,
payer,
provider,
};
}
async function awaitCallback(
connection: anchor.web3.Connection,
vrfClientKey: anchor.web3.PublicKey,
timeoutInterval: number,
errorMsg = "Timed out waiting for VRF Client callback"
) {
let ws: number | undefined = undefined;
const result: anchor.BN = await sbv2Utils
.promiseWithTimeout(
timeoutInterval,
new Promise(
(
resolve: (result: anchor.BN) => void,
reject: (reason: string) => void
) => {
ws = connection.onAccountChange(
vrfClientKey,
async (
accountInfo: anchor.web3.AccountInfo<Buffer>,
context: anchor.web3.Context
) => {
const clientState = VrfClient.decode(accountInfo.data);
if (clientState.result.gt(new anchor.BN(0))) {
resolve(clientState.result);
}
}
);
}
),
new Error(errorMsg)
)
.finally(async () => {
if (ws) {
await connection.removeAccountChangeListener(ws);
}
ws = undefined;
});
return result;
}
const clearLastLine = () => {
process.stdout.moveCursor(0, -1); // up one line
process.stdout.clearLine(1); // from cursor to end
};
function writeStatus(
i: number,
numLoops: number,
successes: number,
failures: number
) {
process.stdout.write(`# ${i} / ${numLoops}\n`);
process.stdout.write(`${chalk.green("Success:", successes)} / ${i}\n`);
process.stdout.write(`${chalk.red("Errors: ", failures)} / ${i}\n`);
}
function clearStatus() {
process.stdout.moveCursor(0, -1); // up one line
process.stdout.clearLine(1); // from cursor to end
process.stdout.moveCursor(0, -1); // up one line
process.stdout.clearLine(1); // from cursor to end
process.stdout.moveCursor(0, -1); // up one line
process.stdout.clearLine(1); // from cursor to end
}
async function fetchTransactions(
connection: Connection,
pubkey: PublicKey,
numTransactions = 10
): Promise<any[]> {
const signatures = (
await connection.getSignaturesForAddress(
pubkey,
{ limit: numTransactions },
"confirmed"
)
).map((t) => t.signature);
console.log(`FETCHED ${signatures.length} transactions`);
let parsedTxns: ParsedTransactionWithMeta[] | null = null;
while (!parsedTxns) {
parsedTxns = await connection.getParsedTransactions(
signatures,
"confirmed"
);
if (!parsedTxns || parsedTxns.length !== signatures.length) {
await sbv2Utils.sleep(1000);
}
}
return parsedTxns.map((tx, i) => {
return {
signature: signatures[i],
logs: tx.meta.logMessages,
};
});
}

View File

@ -7,25 +7,15 @@
"url": "https://github.com/switchboard-xyz/sbv2-solana",
"directory": "programs/anchor-vrf-parser"
},
"bin": {
"sbv2-vrf-example": "./sbv2-vrf-example.sh"
},
"scripts": {
"lint": "eslint --ext .js,.json,.ts 'src/**' --fix",
"pubkey": "solana-keygen pubkey target/deploy/anchor_vrf_parser-keypair.json",
"localnet": "npm run localnet:down || exit 0; solana-test-validator -q -r --ledger .anchor/test-ledger --mint $(solana-keygen pubkey ~/.config/solana/id.json) --bind-address 0.0.0.0 --url https://api.devnet.solana.com --rpc-port 8899 --clone 2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG `# programId` --clone J4CArpsbrZqu1axqQ4AnrqREs3jwoyA1M5LMiQQmAzB9 `# programDataAddress` --clone CKwZcshn4XDvhaWVH9EXnk3iu19t6t5xP2Sy2pD6TRDp `# idlAddress` --clone BYM81n8HvTJuqZU1PmTVcwZ9G8uoji7FKM6EaPkwphPt `# programState` --clone FVLfR6C2ckZhbSwBzZY4CX7YBcddUSge5BNeGQv5eKhy `# switchboardVault` & npm run localnet:wait",
"localnet:wait": "for attempt in {1..30}; do sleep 1; if curl -sS http://localhost:8899 -X POST -H 'Content-Type: application/json' -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getBlockHeight\"}'; then echo ready; break; fi; done",
"localnet:down": "kill -9 $(pgrep command solana-test-validator) || exit 0",
"network:create": "sbv2 solana network create --keypair ~/.config/solana/id.json --configFile .switchboard/networks/default.config.json --schemaFile .switchboard/networks/default.json --cluster localnet --force",
"network:start": "sbv2 solana network start --keypair ~/.config/solana/id.json --schemaFile .switchboard/networks/default.json --cluster localnet --nodeImage dev-v2-RC_01_05_23_20_01-beta",
"network:start:dev": "VERBOSE=1 DEBUG=1 CHAIN=solana CLUSTER=localnet TASK_RUNNER_SOLANA_RPC=https://api.mainnet-beta.solana.com FS_PAYER_SECRET_PATH=~/.config/solana/id.json RPC_URL=http://localhost:8899 ORACLE_KEY=Ei4HcqRQtf6TfwbuRXKRwCtt8PDXhmq9NhYLWpoh23xp ts-node ../../../switchboard-oracle-v2/node/src/apps/oracle",
"build": "node ../build.js anchor-vrf-parser 4wTeTACfwiXqqvy44bNBB3V2rFjmSTXVoEr4ZAYamJEN",
"test": "npm run localnet && npm run network:create && npm run network:start & sleep 60 && anchor test --skip-local-validator",
"test:dev": "npm run localnet && npm run network:create && npm run network:start:dev & sleep 15 && anchor test --skip-local-validator"
},
"dependencies": {
"@coral-xyz/anchor": "^0.26.0",
"@coral-xyz/borsh": "^0.26.0",
"@coral-xyz/anchor": "^0.27.0",
"@coral-xyz/borsh": "^0.27.0",
"@project-serum/borsh": "^0.2.5",
"@solana/spl-token": "^0.3.6",
"@solana/web3.js": "^1.73.3",

View File

@ -1,8 +0,0 @@
#!/usr/bin/env bash
script_dir=$(dirname "$(readlink -f "$BASH_SOURCE")")
# echo "$script_dir"
# /usr/bin/env node --enable-source-maps "$script_dir"/cli.js "$@"
# "$script_dir"/node_modules/.bin/ts-node-esm "$script_dir"/cli.ts "$@"
/usr/bin/env ts-node-esm "$script_dir"/cli.ts "$@"

View File

@ -40,7 +40,7 @@ pub mod anchor_vrf_parser {
}
#[repr(packed)]
#[account(zero_copy)]
#[account(zero_copy(unsafe))]
pub struct VrfClient {
pub bump: u8,
pub max_result: u64,

View File

@ -4,6 +4,7 @@ import "mocha";
import * as anchor from "@coral-xyz/anchor";
import { AnchorProvider } from "@coral-xyz/anchor";
import {
PublicKey,
SystemProgram,
SYSVAR_RECENT_BLOCKHASHES_PUBKEY,
} from "@solana/web3.js";
@ -17,6 +18,7 @@ import {
QueueAccount,
SwitchboardProgram,
SwitchboardTestContextV2,
SWITCHBOARD_LABS_DEVNET_PERMISSIONLESS_QUEUE,
types,
} from "@switchboard-xyz/solana.js";
@ -34,15 +36,14 @@ describe("anchor-vrf-parser test", () => {
const vrfSecret = anchor.web3.Keypair.generate();
console.log(`VRF Account: ${vrfSecret.publicKey}`);
const [vrfClientKey, vrfClientBump] =
anchor.utils.publicKey.findProgramAddressSync(
[
Buffer.from("STATE"),
vrfSecret.publicKey.toBytes(),
payer.publicKey.toBytes(),
],
vrfClientProgram.programId
);
const [vrfClientKey, vrfClientBump] = PublicKey.findProgramAddressSync(
[
Buffer.from("STATE"),
vrfSecret.publicKey.toBytes(),
payer.publicKey.toBytes(),
],
vrfClientProgram.programId
);
const vrfIxCoder = new anchor.BorshInstructionCoder(vrfClientProgram.idl);
const vrfClientCallback: Callback = {
@ -66,7 +67,7 @@ describe("anchor-vrf-parser test", () => {
);
[queueAccount, queue] = await QueueAccount.load(
switchboardProgram,
"F8ce7MsckeZAbAGmxjJNetxYXQa9mKr9nnrC3qKubyYy"
SWITCHBOARD_LABS_DEVNET_PERMISSIONLESS_QUEUE
);
} else {
switchboard = await SwitchboardTestContextV2.loadFromProvider(provider, {
@ -216,7 +217,7 @@ describe("anchor-vrf-parser test", () => {
const newVrfSecret = anchor.web3.Keypair.generate();
const [newVrfClientKey, newVrfClientBump] =
anchor.utils.publicKey.findProgramAddressSync(
PublicKey.findProgramAddressSync(
[
Buffer.from("STATE"),
newVrfSecret.publicKey.toBytes(),

View File

@ -11,7 +11,7 @@ name = "native_feed_parser"
no-entrypoint = []
[dependencies]
switchboard-v2 = { path = "../../rust/switchboard-v2", features = ["devnet"] }
# switchboard-v2 = { version = "^0.1.20", features = ["devnet"] }
switchboard-v2 = { path = "../../rust/switchboard-v2" }
# switchboard-v2 = "^0.1.20"
solana-program = "~1.14.0"
anchor-lang = "^0.26.0"
anchor-lang = "^0.27.0"

View File

@ -13,7 +13,7 @@
"test": "echo \"For workspace native-feed-parser, use the anchor:test script\" && exit 0"
},
"dependencies": {
"@coral-xyz/anchor": "^0.26.0",
"@coral-xyz/anchor": "^0.27.0",
"@solana/web3.js": "^1.73.3",
"@switchboard-xyz/common": "^2.1.33",
"@switchboard-xyz/solana.js": "workspace:*"

View File

@ -2,9 +2,12 @@
---
SwitchboardPy is the Python client for [Switchboard](https://docs.switchboard.xyz/introduction). It provides wrappers to help you to interact with the Switchboard V2 program on-chain.
SwitchboardPy is the Python client for
[Switchboard](https://docs.switchboard.xyz/introduction). It provides wrappers
to help you to interact with the Switchboard V2 program on-chain.
Internally it uses [AnchorPy](https://kevinheavey.github.io/anchorpy/), an Anchor API implementation in Python.
Internally it uses [AnchorPy](https://kevinheavey.github.io/anchorpy/), an
Anchor API implementation in Python.
[![pypi](https://badgen.net/pypi/v/switchboardpy)](https://pypi.python.org/pypi/switchboardpy)&nbsp;&nbsp;
[![twitter](https://badgen.net/twitter/follow/switchboardxyz)](https://twitter.com/switchboardxyz)&nbsp;&nbsp;
@ -28,7 +31,7 @@ from switchboardpy import AggregatorAccount, AccountParams
# Devnet Program ID.
SBV2_DEVNET_PID = PublicKey(
'2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG'
'SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f'
)
async def main():

View File

@ -8,7 +8,7 @@ from switchboardpy import AggregatorAccount, AccountParams
# Devnet Program ID.
SBV2_DEVNET_PID = PublicKey(
'2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG'
'SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f'
)
async def main():

View File

@ -13,7 +13,7 @@ from switchboardpy.compiled import OracleJob
# Devnet Program ID.
SBV2_DEVNET_PID = PublicKey(
'2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG'
'SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f'
)
# Mainnet-Beta Program ID.

View File

@ -1,7 +1,7 @@
import os
from solana.publickey import PublicKey
os.environ.setdefault('SWITCHBOARD_PID', '2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG')
os.environ.setdefault('SWITCHBOARD_PID', 'SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f')
PROGRAM_ID = PublicKey(os.environ['SWITCHBOARD_PID'] or "SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f")

View File

@ -16,7 +16,7 @@ from .generated.accounts import SbState
# Devnet Program ID.
SBV2_DEVNET_PID = PublicKey(
'2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG'
'SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f'
)
# Mainnet-Beta Program ID.

View File

@ -83,7 +83,7 @@ async def test_create():
queue_account=OracleQueueAccount(
AccountParams(
program=program,
public_key=PublicKey("F8ce7MsckeZAbAGmxjJNetxYXQa9mKr9nnrC3qKubyYy")
public_key=PublicKey("uPeRMdfPmrPqgRWSrjAnAkH78RqAhe5kXoW6vBYRqFX")
)
),
)

View File

@ -54,7 +54,7 @@ async def test_create():
queue_account=OracleQueueAccount(
AccountParams(
program=program,
public_key=PublicKey("F8ce7MsckeZAbAGmxjJNetxYXQa9mKr9nnrC3qKubyYy")
public_key=PublicKey("uPeRMdfPmrPqgRWSrjAnAkH78RqAhe5kXoW6vBYRqFX")
)
),
)

View File

@ -53,7 +53,7 @@ async def test_create():
queue = OracleQueueAccount(
AccountParams(
program=program,
public_key=PublicKey("F8ce7MsckeZAbAGmxjJNetxYXQa9mKr9nnrC3qKubyYy")
public_key=PublicKey("uPeRMdfPmrPqgRWSrjAnAkH78RqAhe5kXoW6vBYRqFX")
)
)
@ -68,7 +68,7 @@ async def test_create():
queue_account=OracleQueueAccount(
AccountParams(
program=program,
public_key=PublicKey("F8ce7MsckeZAbAGmxjJNetxYXQa9mKr9nnrC3qKubyYy")
public_key=PublicKey("uPeRMdfPmrPqgRWSrjAnAkH78RqAhe5kXoW6vBYRqFX")
)
),
start_after=0,
@ -134,7 +134,7 @@ async def test_create():
oracle_queue_account=OracleQueueAccount(
AccountParams(
program=program,
public_key=PublicKey("F8ce7MsckeZAbAGmxjJNetxYXQa9mKr9nnrC3qKubyYy")
public_key=PublicKey("uPeRMdfPmrPqgRWSrjAnAkH78RqAhe5kXoW6vBYRqFX")
)
),
)

View File

@ -49,7 +49,7 @@ async def test_create():
queue = OracleQueueAccount(
AccountParams(
program=program,
public_key=PublicKey("F8ce7MsckeZAbAGmxjJNetxYXQa9mKr9nnrC3qKubyYy")
public_key=PublicKey("uPeRMdfPmrPqgRWSrjAnAkH78RqAhe5kXoW6vBYRqFX")
)
)
@ -64,7 +64,7 @@ async def test_create():
queue_account=OracleQueueAccount(
AccountParams(
program=program,
public_key=PublicKey("F8ce7MsckeZAbAGmxjJNetxYXQa9mKr9nnrC3qKubyYy")
public_key=PublicKey("uPeRMdfPmrPqgRWSrjAnAkH78RqAhe5kXoW6vBYRqFX")
)
),
start_after=0,
@ -131,7 +131,7 @@ async def test_create():
oracle_queue_account=OracleQueueAccount(
AccountParams(
program=program,
public_key=PublicKey("F8ce7MsckeZAbAGmxjJNetxYXQa9mKr9nnrC3qKubyYy")
public_key=PublicKey("uPeRMdfPmrPqgRWSrjAnAkH78RqAhe5kXoW6vBYRqFX")
)
),
)
@ -140,7 +140,7 @@ async def test_create():
await aggregator.open_round(AggregatorOpenRoundParams(OracleQueueAccount(
AccountParams(
program=program,
public_key=PublicKey("F8ce7MsckeZAbAGmxjJNetxYXQa9mKr9nnrC3qKubyYy")
public_key=PublicKey("uPeRMdfPmrPqgRWSrjAnAkH78RqAhe5kXoW6vBYRqFX")
)
), payout_wallet=tokenAccount))

View File

@ -62,7 +62,7 @@ async def test_create():
queue_account=OracleQueueAccount(
AccountParams(
program=program,
public_key=PublicKey("F8ce7MsckeZAbAGmxjJNetxYXQa9mKr9nnrC3qKubyYy")
public_key=PublicKey("uPeRMdfPmrPqgRWSrjAnAkH78RqAhe5kXoW6vBYRqFX")
)
),
start_after=0,
@ -91,7 +91,7 @@ async def test_create():
oracle_queue_account=OracleQueueAccount(
AccountParams(
program=program,
public_key=PublicKey("F8ce7MsckeZAbAGmxjJNetxYXQa9mKr9nnrC3qKubyYy")
public_key=PublicKey("uPeRMdfPmrPqgRWSrjAnAkH78RqAhe5kXoW6vBYRqFX")
)
),
)

View File

@ -16,7 +16,7 @@ from solana.rpc.async_api import AsyncClient
from anchorpy import Program, Provider, Wallet
from switchboardpy.program import ProgramStateAccount
ORACLE_QUEUE_STANDARD_DEVNET = 'F8ce7MsckeZAbAGmxjJNetxYXQa9mKr9nnrC3qKubyYy' # <-- new key | old key - 'B4yBQ3hYcjnrNLxUnauJqwpFJnjtm7s8gHybgkAdgXhQ';
ORACLE_QUEUE_STANDARD_DEVNET = 'uPeRMdfPmrPqgRWSrjAnAkH78RqAhe5kXoW6vBYRqFX' # <-- new key | old key - 'B4yBQ3hYcjnrNLxUnauJqwpFJnjtm7s8gHybgkAdgXhQ';
class SwitchboardProgram(object):

View File

@ -23,8 +23,8 @@ cpi = ["no-entrypoint"]
devnet = []
[dependencies]
anchor-lang = "~0.26.0"
anchor-spl = "~0.26.0"
anchor-lang = "~0.27.0"
anchor-spl = "~0.27.0"
rust_decimal = "=1.26.1"
solana-program = "^1.13.5"
bytemuck = "1.7.2"

View File

@ -45,39 +45,6 @@ Or add the following line to your Cargo.toml:
switchboard-v2 = "0.1.22"
```
## Featuresc
| Feature | Description |
| ------- | ----------------------------------------------------------------------------------------------------- |
| devnet | The devnet feature enables using the Switchboard Devnet Program ID instead of the Mainnet Program ID. |
Enable it in your Cargo.toml
```toml
[dependencies]
switchboard-v2 = { version = "0.1.22", features = ["devnet"] }
```
### Define Your Own Devnet Feature
You can also define your own devnet feature to dynamically swap the program IDs.
```toml
[features]
default = []
devnet = ["switchboard-v2/devnet"]
```
This allows you to build your program with a feature flag to automate devnet and
mainnet builds.
```bash
# Build with Mainnet Switchboard Program ID
cargo build-bpf
# Build with Devnet Switchboard Program ID
cargo build-bpf --features devnet
```
## Usage
### Aggregator
@ -111,8 +78,8 @@ feed.check_confidence_interval(SwitchboardDecimal::from_f64(0.80))?;
```
**Example(s)**:
[anchor-feed-parser](https://github.com/switchboard-xyz/sbv2-solana/blob/main/examples/programs/anchor-feed-parser/src/lib.rs),
[native-feed-parser](https://github.com/switchboard-xyz/sbv2-solana/blob/main/examples/programs/native-feed-parser/src/lib.rs)
[anchor-feed-parser](https://github.com/switchboard-xyz/sbv2-solana/blob/main/programs/anchor-feed-parser/src/lib.rs),
[native-feed-parser](https://github.com/switchboard-xyz/sbv2-solana/blob/main/programs/native-feed-parser/src/lib.rs)
#### Read Aggregator History
@ -146,7 +113,7 @@ let result = value[0] % 256000 as u128;
```
**Example**:
[anchor-vrf-parser](https://github.com/switchboard-xyz/sbv2-solana/blob/main/examples/programs/anchor-vrf-parser/src/actions/update_result.rs)
[anchor-vrf-parser](https://github.com/switchboard-xyz/sbv2-solana/blob/main/programs/anchor-vrf-parser/src/actions/update_result.rs)
#### RequestRandomness CPI
@ -190,7 +157,7 @@ vrf_request_randomness.invoke_signed(
```
**Example**:
[anchor-vrf-parser](https://github.com/switchboard-xyz/sbv2-solana/blob/main/examples/programs/anchor-vrf-parser/src/actions/request_result.rs)
[anchor-vrf-parser](https://github.com/switchboard-xyz/sbv2-solana/blob/main/programs/anchor-vrf-parser/src/actions/request_result.rs)
### Buffer Relayer Account
@ -223,7 +190,7 @@ msg!("Buffer string {:?}!", result_string);
```
**Example**:
[anchor-buffer-parser](https://github.com/switchboard-xyz/sbv2-solana/blob/main/examples/programs/anchor-buffer-parser/src/lib.rs)
[anchor-buffer-parser](https://github.com/switchboard-xyz/sbv2-solana/blob/main/programs/anchor-buffer-parser/src/lib.rs)
## Supported CPI Calls

View File

@ -5,7 +5,7 @@ use anchor_lang::Discriminator;
use rust_decimal::Decimal;
use std::cell::Ref;
#[zero_copy]
#[zero_copy(unsafe)]
#[repr(packed)]
#[derive(Default, Debug, PartialEq, Eq)]
pub struct Hash {
@ -13,7 +13,7 @@ pub struct Hash {
pub data: [u8; 32],
}
#[zero_copy]
#[zero_copy(unsafe)]
#[repr(packed)]
#[derive(Default, Debug, PartialEq, Eq)]
pub struct AggregatorRound {
@ -54,14 +54,14 @@ pub enum AggregatorResolutionMode {
ModeRoundResolution = 0,
ModeSlidingResolution = 1,
}
#[account(zero_copy)]
#[account(zero_copy(unsafe))]
#[repr(packed)]
pub struct SlidingResultAccountData {
pub data: [SlidingWindowElement; 16],
pub bump: u8,
pub _ebuf: [u8; 512],
}
#[zero_copy]
#[zero_copy(unsafe)]
#[derive(Default)]
#[repr(packed)]
pub struct SlidingWindowElement {
@ -72,7 +72,7 @@ pub struct SlidingWindowElement {
}
// #[zero_copy]
#[account(zero_copy)]
#[account(zero_copy(unsafe))]
#[repr(packed)]
#[derive(Debug, PartialEq)]
pub struct AggregatorAccountData {

View File

@ -104,7 +104,7 @@ impl Discriminator for BufferRelayerAccountData {
const DISCRIMINATOR: [u8; 8] = [50, 35, 51, 115, 169, 219, 158, 52];
}
impl Owner for BufferRelayerAccountData {
fn owner() -> solana_program::pubkey::Pubkey {
fn owner() -> Pubkey {
SWITCHBOARD_PROGRAM_ID
}
}

View File

@ -1,7 +1,7 @@
use anchor_lang::prelude::*;
use bytemuck::{Pod, Zeroable};
#[zero_copy]
#[zero_copy(unsafe)]
#[derive(Default)]
#[repr(packed)]
pub struct CrankRow {
@ -13,7 +13,7 @@ pub struct CrankRow {
unsafe impl Pod for CrankRow {}
unsafe impl Zeroable for CrankRow {}
#[account(zero_copy)]
#[account(zero_copy(unsafe))]
#[repr(packed)]
pub struct CrankAccountData {
/// Name of the crank to store on-chain.

View File

@ -5,7 +5,7 @@ use rust_decimal::prelude::{FromPrimitive, ToPrimitive};
use rust_decimal::Decimal;
use std::convert::{From, TryInto};
#[zero_copy]
#[zero_copy(unsafe)]
#[repr(packed)]
#[derive(Default, Debug, Eq, PartialEq)]
pub struct SwitchboardDecimal {

View File

@ -2,7 +2,7 @@
use anchor_lang::prelude::*;
use bytemuck::{Pod, Zeroable};
#[zero_copy]
#[zero_copy(unsafe)]
#[repr(packed)]
pub struct AccountMetaZC {
pub pubkey: Pubkey,
@ -10,7 +10,7 @@ pub struct AccountMetaZC {
pub is_writable: bool,
}
#[zero_copy]
#[zero_copy(unsafe)]
#[repr(packed)]
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct AccountMetaBorsh {
@ -19,7 +19,7 @@ pub struct AccountMetaBorsh {
pub is_writable: bool,
}
#[zero_copy]
#[zero_copy(unsafe)]
#[repr(packed)]
pub struct CallbackZC {
/// The program ID of the callback program being invoked.
@ -49,7 +49,7 @@ pub struct Callback {
pub ix_data: Vec<u8>,
}
#[zero_copy]
#[zero_copy(unsafe)]
#[repr(packed)]
pub struct VrfRound {
/// The alpha bytes used to calculate the VRF proof.
@ -101,7 +101,7 @@ impl std::fmt::Display for VrfStatus {
}
}
#[zero_copy]
#[zero_copy(unsafe)]
#[repr(packed)]
pub struct EcvrfProofZC {
pub Gamma: EdwardsPointZC, // RistrettoPoint
@ -117,7 +117,7 @@ impl Default for EcvrfProofZC {
/// The `Scalar` struct holds an integer \\(s < 2\^{255} \\) which
/// represents an element of \\(\mathbb Z / \ell\\).
#[allow(dead_code)]
#[zero_copy]
#[zero_copy(unsafe)]
#[repr(packed)]
pub struct Scalar {
/// `bytes` is a little-endian byte encoding of an integer representing a scalar modulo the
@ -158,7 +158,7 @@ pub struct FieldElement51(pub(crate) [u64; 5]);
unsafe impl Pod for FieldElement51 {}
unsafe impl Zeroable for FieldElement51 {}
#[zero_copy]
#[zero_copy(unsafe)]
#[repr(packed)]
pub struct FieldElementZC {
pub(crate) bytes: [u64; 5],
@ -197,7 +197,7 @@ pub struct CompletedPoint {
pub Z: FieldElement51,
pub T: FieldElement51,
}
#[zero_copy]
#[zero_copy(unsafe)]
#[repr(packed)]
pub struct CompletedPointZC {
pub X: FieldElementZC,
@ -243,7 +243,7 @@ pub struct EdwardsPoint {
pub(crate) T: FieldElement51,
}
#[allow(dead_code)]
#[zero_copy]
#[zero_copy(unsafe)]
#[repr(packed)]
pub struct EdwardsPointZC {
pub(crate) X: FieldElementZC,
@ -271,7 +271,7 @@ pub struct ProjectivePoint {
pub Y: FieldElement51,
pub Z: FieldElement51,
}
#[zero_copy]
#[zero_copy(unsafe)]
#[repr(packed)]
pub struct ProjectivePointZC {
pub(crate) X: FieldElementZC,
@ -304,7 +304,7 @@ impl Into<ProjectivePoint> for ProjectivePointZC {
}
}
#[zero_copy]
#[zero_copy(unsafe)]
#[repr(packed)]
pub struct EcvrfIntermediate {
pub r: FieldElementZC,
@ -317,7 +317,7 @@ unsafe impl Pod for EcvrfIntermediate {}
unsafe impl Zeroable for EcvrfIntermediate {}
#[allow(non_snake_case)]
#[zero_copy]
#[zero_copy(unsafe)]
#[repr(packed)]
pub struct VrfBuilder {
/// The OracleAccountData that is producing the randomness.

View File

@ -6,7 +6,7 @@ use bytemuck::{Pod, Zeroable};
use std::cell::Ref;
use superslice::*;
#[zero_copy]
#[zero_copy(unsafe)]
#[derive(Default)]
#[repr(packed)]
pub struct AggregatorHistoryRow {

View File

@ -20,6 +20,7 @@ pub struct JobAccountData {
pub total_spent: u64,
/// Unix timestamp when the job was created on-chain.
pub created_at: i64,
pub is_initializing: u8,
}
impl JobAccountData {}

View File

@ -0,0 +1,30 @@
use anchor_lang::prelude::*;
#[account(zero_copy(unsafe))]
#[repr(packed)]
pub struct LeaseAccountData {
/// Public key of the token account holding the lease contract funds until rewarded to oracles for successfully processing updates
pub escrow: Pubkey, // Needed, maybe derived, key + "update_escrow"?
/// Public key of the oracle queue that the lease contract is applicable for.
pub queue: Pubkey,
/// Public key of the aggregator that the lease contract is applicable for
pub aggregator: Pubkey,
/// Public key of the Solana token program ID.
pub token_program: Pubkey,
/// Whether the lease contract is still active.
pub is_active: bool,
/// Index of an aggregators position on a crank.
pub crank_row_count: u32,
/// Timestamp when the lease contract was created.
pub created_at: i64,
/// Counter keeping track of the number of updates for the given aggregator.
pub update_count: u128,
/// Public key of keypair that may withdraw funds from the lease at any time
pub withdraw_authority: Pubkey,
/// The PDA bump to derive the pubkey.
pub bump: u8,
// Reserved for future info.
pub _ebuf: [u8; 255],
}
impl LeaseAccountData {}

View File

@ -9,6 +9,7 @@ pub mod ecvrf;
pub mod error;
pub mod history_buffer;
pub mod job;
pub mod lease;
pub mod oracle;
pub mod permission;
pub mod queue;
@ -25,6 +26,7 @@ pub use ecvrf::*;
pub use error::SwitchboardError;
pub use history_buffer::*;
pub use job::*;
pub use lease::*;
pub use oracle::*;
pub use permission::*;
pub use queue::*;
@ -47,20 +49,7 @@ pub const BUFFER_DISCRIMINATOR: &[u8] = b"BUFFERxx";
/// Seed used to derive the SlidingWindow PDA.
// const SLIDING_RESULT_SEED: &[u8] = b"SlidingResultAccountData";
/// Mainnet program id for Switchboard v2
pub const SWITCHBOARD_V2_MAINNET: Pubkey = pubkey!("SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f");
/// Program id for Switchboard v2
pub const SWITCHBOARD_PROGRAM_ID: Pubkey = pubkey!("SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f");
/// Devnet program id for Switchboard v2
pub const SWITCHBOARD_V2_DEVNET: Pubkey = pubkey!("2TfB33aLaneQb5TNVwyDz3jSZXS6jdW2ARw1Dgf84XCG");
#[cfg(feature = "devnet")]
/// Switchboard Program ID.
pub const SWITCHBOARD_PROGRAM_ID: Pubkey = SWITCHBOARD_V2_DEVNET;
#[cfg(not(feature = "devnet"))]
/// Switchboard Program ID.
pub const SWITCHBOARD_PROGRAM_ID: Pubkey = SWITCHBOARD_V2_MAINNET;
#[cfg(feature = "devnet")]
declare_id!(SWITCHBOARD_V2_DEVNET);
#[cfg(not(feature = "devnet"))]
declare_id!(SWITCHBOARD_V2_MAINNET);
declare_id!(SWITCHBOARD_PROGRAM_ID);

View File

@ -7,7 +7,7 @@ pub enum OracleResponseType {
TypeDisagreement,
TypeNoResponse,
}
#[zero_copy]
#[zero_copy(unsafe)]
#[derive(Default)]
#[repr(packed)]
pub struct OracleMetrics {
@ -31,7 +31,7 @@ pub struct OracleMetrics {
pub total_late_response: u128,
}
#[account(zero_copy)]
#[account(zero_copy(unsafe))]
#[repr(packed)]
pub struct OracleAccountData {
/// Name of the oracle to store on-chain.
@ -51,8 +51,10 @@ pub struct OracleAccountData {
pub queue_pubkey: Pubkey,
/// Oracle track record.
pub metrics: OracleMetrics,
/// The PDA bump to derive the pubkey.
pub bump: u8,
/// Reserved for future info.
pub _ebuf: [u8; 256],
pub _ebuf: [u8; 255],
}
impl OracleAccountData {}

View File

@ -17,7 +17,7 @@ pub enum SwitchboardPermission {
PermitVrfRequests = 1 << 2,
}
#[account(zero_copy)]
#[account(zero_copy(unsafe))]
#[repr(packed)]
pub struct PermissionAccountData {
/// The authority that is allowed to set permissions for this account.
@ -32,8 +32,10 @@ pub struct PermissionAccountData {
/// unique expiration periods, BUT currently only one permission
/// per account makes sense for the infra. Dont over engineer.
pub expiration: i64,
/// The PDA bump to derive the pubkey.
pub bump: u8,
/// Reserved for future info.
pub _ebuf: [u8; 256],
pub _ebuf: [u8; 255],
}
impl PermissionAccountData {}

View File

@ -2,7 +2,7 @@ use super::decimal::SwitchboardDecimal;
use anchor_lang::prelude::*;
use bytemuck::try_cast_slice_mut;
#[account(zero_copy)]
#[account(zero_copy(unsafe))]
#[repr(packed)]
pub struct OracleQueueAccountData {
/// Name of the queue to store on-chain.

View File

@ -1,6 +1,6 @@
use anchor_lang::prelude::*;
#[account(zero_copy)]
#[account(zero_copy(unsafe))]
#[repr(packed)]
pub struct SbState {
/// The account authority permitted to make account changes.
@ -11,8 +11,10 @@ pub struct SbState {
pub token_vault: Pubkey,
/// The token mint used by the DAO.
pub dao_mint: Pubkey,
/// The PDA bump to derive the pubkey.
pub bump: u8,
/// Reserved for future info.
pub _ebuf: [u8; 992],
pub _ebuf: [u8; 991],
}
impl SbState {}

View File

@ -12,7 +12,7 @@ use std::cell::Ref;
// VrfSetCallback
// VrfClose
#[account(zero_copy)]
#[account(zero_copy(unsafe))]
#[repr(packed)]
pub struct VrfAccountData {
/// The current status of the VRF account.

View File

@ -10,7 +10,7 @@ use std::cell::Ref;
// VrfLiteRequestRandomnessParams
// VrfLiteCloseParams
#[account(zero_copy)]
#[account(zero_copy(unsafe))]
#[repr(packed)]
pub struct VrfLiteAccountData {
/// The bump used to derive the SbState account.

View File

@ -18,7 +18,7 @@ pub struct VrfPoolRow {
}
#[repr(packed)]
#[account(zero_copy)]
#[account(zero_copy(unsafe))]
pub struct VrfPoolAccountData {
/// ACCOUNTS
pub authority: Pubkey, // authority can never be changed or else vrf accounts are useless