add client script, fix some bugs
This commit is contained in:
parent
afc7e766cf
commit
5334a4ebfb
|
@ -0,0 +1,5 @@
|
|||
# Network: local|main|dev
|
||||
NETWORK=local
|
||||
|
||||
# Payer wallet
|
||||
SOLANA_PAYER_MNEMONIC=spin canyon tuition upset pioneer celery liquid conduct boy bargain dust seed
|
|
@ -0,0 +1,2 @@
|
|||
node_modules
|
||||
dist
|
|
@ -0,0 +1,300 @@
|
|||
import {
|
||||
PublicKey, BaseProgram, Account,
|
||||
Wallet, System, SPLToken,
|
||||
} from "solray";
|
||||
|
||||
import {
|
||||
SYSVAR_RENT_PUBKEY, SYSVAR_CLOCK_PUBKEY,
|
||||
TransactionInstruction, SystemProgram
|
||||
} from "@solana/web3.js";
|
||||
|
||||
import { publicKey, u64LEBuffer, uint64 } from "solray/lib/util/encoding";
|
||||
|
||||
// @ts-ignore
|
||||
import BufferLayout from "buffer-layout";
|
||||
|
||||
export const AggregatorLayout = BufferLayout.struct([
|
||||
BufferLayout.blob(4, "submitInterval"),
|
||||
uint64("minSubmissionValue"),
|
||||
uint64("maxSubmissionValue"),
|
||||
BufferLayout.blob(32, "description"),
|
||||
BufferLayout.u8("isInitialized"),
|
||||
publicKey('owner'),
|
||||
BufferLayout.blob(1008, "submissions"),
|
||||
]);
|
||||
|
||||
export const OracleLayout = BufferLayout.struct([
|
||||
uint64("submission"),
|
||||
uint64("nextSubmitTime"),
|
||||
BufferLayout.blob(32, "description"),
|
||||
BufferLayout.u8("isInitialized"),
|
||||
uint64("withdrawable"),
|
||||
publicKey('aggregator'),
|
||||
publicKey('owner'),
|
||||
]);
|
||||
|
||||
export const SubmissionLayout = BufferLayout.struct([
|
||||
uint64("time"),
|
||||
uint64("value"),
|
||||
publicKey('oracle'),
|
||||
]);
|
||||
|
||||
interface InitializeParams {
|
||||
submitInterval: number;
|
||||
minSubmissionValue: bigint;
|
||||
maxSubmissionValue: bigint;
|
||||
description: string;
|
||||
owner: Account;
|
||||
}
|
||||
|
||||
interface InitializeInstructionParams extends InitializeParams {
|
||||
aggregator: PublicKey;
|
||||
}
|
||||
|
||||
interface AddOracleParams {
|
||||
owner: PublicKey;
|
||||
description: string;
|
||||
aggregator: PublicKey;
|
||||
// To prove you are the aggregator owner
|
||||
aggregatorOwner: Account;
|
||||
}
|
||||
|
||||
interface AddOracleInstructionParams extends AddOracleParams {
|
||||
oracle: PublicKey;
|
||||
}
|
||||
|
||||
interface RemoveOracleParams {
|
||||
aggregator: PublicKey,
|
||||
// The oracle key
|
||||
oracle: PublicKey,
|
||||
// To prove you are the aggregator owner
|
||||
authority: Account,
|
||||
}
|
||||
|
||||
interface RemoveOracleInstructionParams extends RemoveOracleParams {
|
||||
}
|
||||
|
||||
interface SubmitParams {
|
||||
aggregator: PublicKey;
|
||||
oracle: PublicKey;
|
||||
// The oracle"s index
|
||||
submission: bigint;
|
||||
// oracle owner
|
||||
owner: Account;
|
||||
}
|
||||
|
||||
interface SubmitInstructionParams extends SubmitParams {
|
||||
}
|
||||
|
||||
interface WithdrawParams {
|
||||
aggregator: PublicKey,
|
||||
// withdraw to
|
||||
receiver: PublicKey,
|
||||
// withdraw amount
|
||||
amount: bigint,
|
||||
tokenAccount: PublicKey,
|
||||
tokenOwner: PublicKey,
|
||||
// signer
|
||||
authority: Account,
|
||||
}
|
||||
|
||||
interface WithdrawInstructionParams extends WithdrawParams {
|
||||
}
|
||||
|
||||
export default class FluxAggregator extends BaseProgram {
|
||||
|
||||
private sys: System
|
||||
constructor(wallet: Wallet, programID: PublicKey) {
|
||||
super(wallet, programID);
|
||||
this.sys = new System(this.wallet);
|
||||
}
|
||||
|
||||
public async initialize(params: InitializeParams): Promise<PublicKey> {
|
||||
const account = new Account();
|
||||
await this.sendTx([
|
||||
await this.sys.createRentFreeAccountInstruction({
|
||||
newPubicKey: account.publicKey,
|
||||
space: AggregatorLayout.span,
|
||||
programID: this.programID,
|
||||
}),
|
||||
this.initializeInstruction({
|
||||
...params,
|
||||
aggregator: account.publicKey,
|
||||
})
|
||||
], [this.account, account, params.owner]);
|
||||
|
||||
return account.publicKey;
|
||||
}
|
||||
|
||||
private initializeInstruction(params: InitializeInstructionParams): TransactionInstruction {
|
||||
const {
|
||||
aggregator,
|
||||
description,
|
||||
submitInterval,
|
||||
minSubmissionValue,
|
||||
maxSubmissionValue,
|
||||
owner,
|
||||
} = params;
|
||||
|
||||
const layout = BufferLayout.struct([
|
||||
BufferLayout.u8("instruction"),
|
||||
BufferLayout.blob(4, "submitInterval"),
|
||||
uint64("minSubmissionValue"),
|
||||
uint64("maxSubmissionValue"),
|
||||
BufferLayout.blob(32, "description"),
|
||||
]);
|
||||
|
||||
const buf = Buffer.allocUnsafe(4);
|
||||
buf.writeUInt32LE(submitInterval);
|
||||
|
||||
return this.instructionEncode(layout, {
|
||||
instruction: 0, // initialize instruction
|
||||
submitInterval: buf,
|
||||
minSubmissionValue: u64LEBuffer(minSubmissionValue),
|
||||
maxSubmissionValue: u64LEBuffer(maxSubmissionValue),
|
||||
description: Buffer.from(description),
|
||||
}, [
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
{ write: aggregator },
|
||||
owner
|
||||
]);
|
||||
}
|
||||
|
||||
public async addOracle(params: AddOracleParams): Promise<PublicKey> {
|
||||
const account = new Account();
|
||||
|
||||
await this.sendTx([
|
||||
await this.sys.createRentFreeAccountInstruction({
|
||||
newPubicKey: account.publicKey,
|
||||
space: OracleLayout.span,
|
||||
programID: this.programID,
|
||||
}),
|
||||
this.addOracleInstruction({
|
||||
...params,
|
||||
oracle: account.publicKey,
|
||||
})
|
||||
], [this.account, account, params.aggregatorOwner]);
|
||||
|
||||
return account.publicKey;
|
||||
}
|
||||
|
||||
private addOracleInstruction(params: AddOracleInstructionParams): TransactionInstruction {
|
||||
const {
|
||||
oracle,
|
||||
owner,
|
||||
description,
|
||||
aggregator,
|
||||
aggregatorOwner,
|
||||
} = params;
|
||||
|
||||
const layout = BufferLayout.struct([
|
||||
BufferLayout.u8("instruction"),
|
||||
BufferLayout.blob(32, "description"),
|
||||
]);
|
||||
|
||||
return this.instructionEncode(layout, {
|
||||
instruction: 1, // add oracle instruction
|
||||
description: Buffer.from(description),
|
||||
}, [
|
||||
{ write: oracle },
|
||||
owner,
|
||||
SYSVAR_CLOCK_PUBKEY,
|
||||
{ write: aggregator },
|
||||
aggregatorOwner,
|
||||
]);
|
||||
}
|
||||
|
||||
public async removeOracle(params: RemoveOracleParams): Promise<void> {
|
||||
await this.sendTx([
|
||||
this.removeOracleInstruction(params)
|
||||
], [this.account, params.authority]);
|
||||
|
||||
}
|
||||
|
||||
private removeOracleInstruction(params: RemoveOracleInstructionParams): TransactionInstruction {
|
||||
const {
|
||||
aggregator,
|
||||
oracle,
|
||||
authority,
|
||||
} = params;
|
||||
|
||||
const layout = BufferLayout.struct([
|
||||
BufferLayout.u8("instruction"),
|
||||
publicKey("oracle"),
|
||||
]);
|
||||
|
||||
return this.instructionEncode(layout, {
|
||||
instruction: 2, // remove oracle instruction
|
||||
oracle: oracle.toBuffer(),
|
||||
}, [
|
||||
{ write: authority },
|
||||
]);
|
||||
}
|
||||
|
||||
public async submit(params: SubmitParams): Promise<void> {
|
||||
await this.sendTx([
|
||||
this.submitInstruction(params)
|
||||
], [this.account, params.owner]);
|
||||
|
||||
}
|
||||
|
||||
private submitInstruction(params: SubmitInstructionParams): TransactionInstruction {
|
||||
const {
|
||||
aggregator,
|
||||
oracle,
|
||||
submission,
|
||||
owner,
|
||||
} = params;
|
||||
|
||||
const layout = BufferLayout.struct([
|
||||
BufferLayout.u8("instruction"),
|
||||
uint64("submission"),
|
||||
]);
|
||||
|
||||
return this.instructionEncode(layout, {
|
||||
instruction: 3, // submit instruction
|
||||
submission: u64LEBuffer(submission),
|
||||
}, [
|
||||
{ write: aggregator },
|
||||
SYSVAR_CLOCK_PUBKEY,
|
||||
{ write: oracle },
|
||||
owner,
|
||||
]);
|
||||
}
|
||||
|
||||
public async withdraw(params: WithdrawParams): Promise<void> {
|
||||
await this.sendTx([
|
||||
this.withdrawInstruction(params)
|
||||
], [this.account, params.authority]);
|
||||
|
||||
}
|
||||
|
||||
private withdrawInstruction(params: WithdrawInstructionParams): TransactionInstruction {
|
||||
const {
|
||||
aggregator,
|
||||
receiver,
|
||||
amount,
|
||||
tokenOwner,
|
||||
tokenAccount,
|
||||
authority,
|
||||
} = params;
|
||||
|
||||
const layout = BufferLayout.struct([
|
||||
BufferLayout.u8("instruction"),
|
||||
uint64("amount"),
|
||||
]);
|
||||
|
||||
return this.instructionEncode(layout, {
|
||||
instruction: 4, // withdraw instruction
|
||||
amount: u64LEBuffer(amount),
|
||||
}, [
|
||||
{ write: aggregator },
|
||||
{ write: tokenAccount },
|
||||
{ write: receiver },
|
||||
SPLToken.programID,
|
||||
tokenOwner,
|
||||
{ write: authority },
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"programAccount": {
|
||||
"pubkey": "FfR1CiA4i2xvYLycCU8kv9mLpcT7Woyqkgvkz7hzjEfN",
|
||||
"secret": "5b0d673d12b9a811eb9f1a770f86e4c516879f95cf60191c78b3a8c5d6fb6f1ed9dad41af1513e8ab932aab3f56bafcdb84c5fedc9df8a299e7bf53ec84784a5"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
import dotenv from "dotenv"
|
||||
|
||||
import {
|
||||
Wallet, solana, NetworkName, BPFLoader,
|
||||
PublicKey, Deployer, Account,
|
||||
} from "solray"
|
||||
|
||||
import { promises as fs } from "fs"
|
||||
import { calculatePayfees, decodeAggregatorInfo, sleep } from "./utils"
|
||||
|
||||
import FluxAggregator, { AggregatorLayout } from "./FluxAggregator"
|
||||
|
||||
dotenv.config()
|
||||
|
||||
const { NETWORK, SOLANA_PAYER_MNEMONIC } = process.env
|
||||
|
||||
// so file path
|
||||
const soPath = "../build/flux_aggregator.so"
|
||||
|
||||
async function main() {
|
||||
|
||||
if (!SOLANA_PAYER_MNEMONIC || !NETWORK) {
|
||||
throw new Error("Config error.")
|
||||
}
|
||||
|
||||
const conn = solana.connect(NETWORK as NetworkName)
|
||||
const wallet = await Wallet.fromMnemonic(SOLANA_PAYER_MNEMONIC, conn)
|
||||
console.log("using wallet", wallet.address)
|
||||
|
||||
let walletBalance = await conn.getBalance(wallet.pubkey)
|
||||
console.log("wallet banalce:", walletBalance)
|
||||
|
||||
const deployer = await Deployer.open("deploy.json")
|
||||
|
||||
const programBinary = await fs.readFile(soPath)
|
||||
|
||||
let fees = await calculatePayfees(programBinary.length, conn)
|
||||
|
||||
const programAccount = await deployer.ensure("programAccount", async () => {
|
||||
console.log("loading program...")
|
||||
|
||||
if (walletBalance < fees) {
|
||||
// throw new Error("Insufficient balance to pay fees");
|
||||
// get airdrop
|
||||
console.log("insufficient balance to pay fees, request airdrop...")
|
||||
await conn.requestAirdrop(wallet.pubkey, fees)
|
||||
await sleep(1000)
|
||||
}
|
||||
|
||||
const bpfLoader = new BPFLoader(wallet)
|
||||
|
||||
const account = await bpfLoader.load(programBinary)
|
||||
|
||||
return account
|
||||
})
|
||||
|
||||
console.log("program loaded:", programAccount.publicKey.toBase58())
|
||||
|
||||
const program = new FluxAggregator(wallet, programAccount.publicKey)
|
||||
|
||||
const aggregatorOwner = new Account()
|
||||
console.log("initialize aggregator to owner:", aggregatorOwner.publicKey.toBase58())
|
||||
|
||||
walletBalance = await conn.getBalance(wallet.pubkey)
|
||||
fees = await calculatePayfees(AggregatorLayout.span, conn)
|
||||
|
||||
if (walletBalance < fees) {
|
||||
console.log("insufficient balance to pay fees, request airdrop...")
|
||||
await conn.requestAirdrop(wallet.pubkey, fees)
|
||||
await sleep(1000)
|
||||
}
|
||||
|
||||
const aggregator = await program.initialize({
|
||||
submitInterval: 6,
|
||||
minSubmissionValue: BigInt(1),
|
||||
maxSubmissionValue: BigInt(99999),
|
||||
description: "ETH/USDT".padEnd(32),
|
||||
owner: aggregatorOwner
|
||||
})
|
||||
console.log("aggregator initialized, pubkey:", aggregator.toBase58())
|
||||
|
||||
const oracleOwner = new Account()
|
||||
console.log("add an oracle...")
|
||||
const oracle = await program.addOracle({
|
||||
owner: oracleOwner.publicKey,
|
||||
description: "Solink".padEnd(32),
|
||||
aggregator,
|
||||
aggregatorOwner,
|
||||
})
|
||||
|
||||
console.log("oracle added, pubkey:", oracle.toBase58(), ", owner: ", oracleOwner.publicKey.toBase58())
|
||||
|
||||
console.log("oracle submiting...")
|
||||
await program.submit({
|
||||
aggregator,
|
||||
oracle,
|
||||
submission: BigInt(123),
|
||||
owner: oracleOwner,
|
||||
})
|
||||
|
||||
console.log("submit success! get aggregator info...")
|
||||
|
||||
const accountInfo = await conn.getAccountInfo(aggregator)
|
||||
|
||||
console.log("aggregator info:", decodeAggregatorInfo(accountInfo))
|
||||
}
|
||||
|
||||
|
||||
main().catch(err => console.log({ err }))
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "client",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"dotenv": "^8.2.0",
|
||||
"solray": "git+https://github.com/czl1378/solray.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/recommended": "^1.0.1",
|
||||
"@types/node": "^14.14.10",
|
||||
"typescript": "^4.1.2"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "ES2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
"declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
"declarationMap": false, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
"sourceMap": false, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "./dist", /* Redirect output structure to the directory. */
|
||||
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelp ers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
"noImplicitAny": false,
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
|
||||
/* Advanced Options */
|
||||
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
import { Connection, BpfLoader, PublicKey } from "@solana/web3.js"
|
||||
|
||||
import { AggregatorLayout, SubmissionLayout } from "./FluxAggregator"
|
||||
|
||||
export async function calculatePayfees(dataLength: number, connection: Connection): Promise<number> {
|
||||
let fees = 0;
|
||||
const {feeCalculator} = await connection.getRecentBlockhash();
|
||||
|
||||
const NUM_RETRIES = 500; // allow some number of retries
|
||||
fees +=
|
||||
feeCalculator.lamportsPerSignature *
|
||||
(BpfLoader.getMinNumSignatures(dataLength) + NUM_RETRIES) +
|
||||
(await connection.getMinimumBalanceForRentExemption(dataLength));
|
||||
|
||||
// Calculate the cost of sending the transactions
|
||||
fees += feeCalculator.lamportsPerSignature * 100; // wag
|
||||
|
||||
return fees;
|
||||
}
|
||||
|
||||
export function getSubmissionValue(submissions: []): number {
|
||||
const values = submissions
|
||||
.filter((s: any) => s.value != 0)
|
||||
.map((s: any) => s.value)
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
let len = values.length;
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
} else if (len == 1) {
|
||||
return values[0];
|
||||
} else {
|
||||
let i = len / 2;
|
||||
return len % 2 == 0 ? (values[i] + values[i-1])/2 : values[i];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, ms);
|
||||
});
|
||||
}
|
||||
|
||||
export function decodeAggregatorInfo(accountInfo) {
|
||||
|
||||
const data = Buffer.from(accountInfo.data);
|
||||
const aggregator = AggregatorLayout.decode(data);
|
||||
|
||||
const minSubmissionValue = aggregator.minSubmissionValue.readBigUInt64LE().toString();
|
||||
const maxSubmissionValue = aggregator.maxSubmissionValue.readBigUInt64LE().toString();
|
||||
const submitInterval = aggregator.submitInterval.readInt32LE();
|
||||
const description = aggregator.description.toString();
|
||||
|
||||
// decode oracles
|
||||
let submissions: [] = [];
|
||||
let oracles: [] = [];
|
||||
let submissionSpace = SubmissionLayout.span;
|
||||
let updateTime = '0';
|
||||
|
||||
for (let i = 0; i < aggregator.submissions.length / submissionSpace; i++) {
|
||||
let submission = SubmissionLayout.decode(
|
||||
aggregator.submissions.slice(i*submissionSpace, (i+1)*submissionSpace)
|
||||
);
|
||||
submission.oracle = new PublicKey(submission.oracle);
|
||||
|
||||
submission.time = submission.time.readBigInt64LE().toString();
|
||||
submission.value = submission.value.readBigInt64LE().toString()*1;
|
||||
if (!submission.oracle.equals(new PublicKey(0))) {
|
||||
submissions.push(submission as never);
|
||||
oracles.push(submission.oracle.toBase58() as never);
|
||||
|
||||
}
|
||||
if (submission.time > updateTime) {
|
||||
updateTime = submission.time;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
minSubmissionValue: minSubmissionValue,
|
||||
maxSubmissionValue: maxSubmissionValue,
|
||||
submissionValue: getSubmissionValue(submissions),
|
||||
submitInterval,
|
||||
description,
|
||||
oracles,
|
||||
updateTime,
|
||||
}
|
||||
}
|
|
@ -82,7 +82,8 @@ impl Processor {
|
|||
/// Accounts expected by this instruction:
|
||||
///
|
||||
/// 1. `[]` Sysvar rent
|
||||
/// 2. `[writable, signer]` The aggregator autority
|
||||
/// 2. `[writable]` The aggregator
|
||||
/// 3. `[signer]` The aggregator owner
|
||||
pub fn process_initialize(
|
||||
_program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
|
@ -96,9 +97,10 @@ impl Processor {
|
|||
|
||||
let rent_info = next_account_info(account_info_iter)?;
|
||||
let aggregator_info = next_account_info(account_info_iter)?;
|
||||
let owner_info = next_account_info(account_info_iter)?;
|
||||
|
||||
// check signer
|
||||
if !aggregator_info.is_signer {
|
||||
if !owner_info.is_signer {
|
||||
return Err(ProgramError::MissingRequiredSignature);
|
||||
}
|
||||
|
||||
|
@ -118,6 +120,7 @@ impl Processor {
|
|||
aggregator.max_submission_value = max_submission_value;
|
||||
aggregator.description = description;
|
||||
aggregator.is_initialized = true;
|
||||
aggregator.owner = *owner_info.key;
|
||||
|
||||
Aggregator::pack(aggregator, &mut aggregator_info.data.borrow_mut())?;
|
||||
|
||||
|
@ -130,9 +133,11 @@ impl Processor {
|
|||
///
|
||||
/// Accounts expected by this instruction:
|
||||
///
|
||||
/// 0. `[writable]` The oracle(key)
|
||||
/// 1. `[]` Clock sysvar
|
||||
/// 2. `[writable, signer]` The aggregator's authority.
|
||||
/// 0. `[writable]` The oracle
|
||||
/// 1. `[]` The oracle owner
|
||||
/// 2. `[]` Clock sysvar
|
||||
/// 3. `[writable]` The aggregator
|
||||
/// 4. `[signer]` The aggregator owner
|
||||
pub fn process_add_oracle(
|
||||
accounts: &[AccountInfo],
|
||||
description: [u8; 32],
|
||||
|
@ -140,15 +145,21 @@ impl Processor {
|
|||
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
let oracle_info = next_account_info(account_info_iter)?;
|
||||
let oracle_owner_info = next_account_info(account_info_iter)?;
|
||||
let clock_sysvar_info = next_account_info(account_info_iter)?;
|
||||
let aggregator_info = next_account_info(account_info_iter)?;
|
||||
let aggregator_owner_info = next_account_info(account_info_iter)?;
|
||||
|
||||
if !aggregator_info.is_signer {
|
||||
if !aggregator_owner_info.is_signer {
|
||||
return Err(ProgramError::MissingRequiredSignature);
|
||||
}
|
||||
|
||||
let mut aggregator = Aggregator::unpack_unchecked(&aggregator_info.data.borrow())?;
|
||||
|
||||
if &aggregator.owner != aggregator_owner_info.key {
|
||||
return Err(Error::OwnerMismatch.into());
|
||||
}
|
||||
|
||||
if !aggregator.is_initialized {
|
||||
return Err(Error::NotFoundAggregator.into());
|
||||
}
|
||||
|
@ -184,6 +195,7 @@ impl Processor {
|
|||
oracle.is_initialized = true;
|
||||
oracle.withdrawable = 0;
|
||||
oracle.aggregator = *aggregator_info.key;
|
||||
oracle.owner = *oracle_owner_info.key;
|
||||
|
||||
Oracle::pack(oracle, &mut oracle_info.data.borrow_mut())?;
|
||||
|
||||
|
@ -196,15 +208,17 @@ impl Processor {
|
|||
///
|
||||
/// Accounts expected by this instruction:
|
||||
///
|
||||
/// 0. `[writable, signer]` The aggregator's authority.
|
||||
/// 0. `[writable]` The aggregator.
|
||||
/// 1. `[signer]` The aggregator onwer.
|
||||
pub fn process_remove_oracle(
|
||||
accounts: &[AccountInfo],
|
||||
oracle: Pubkey,
|
||||
) -> ProgramResult {
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
let aggregator_info = next_account_info(account_info_iter)?;
|
||||
let owner_info = next_account_info(account_info_iter)?;
|
||||
|
||||
if !aggregator_info.is_signer {
|
||||
if !owner_info.is_signer {
|
||||
return Err(ProgramError::MissingRequiredSignature);
|
||||
}
|
||||
|
||||
|
@ -214,6 +228,10 @@ impl Processor {
|
|||
return Err(Error::NotFoundAggregator.into());
|
||||
}
|
||||
|
||||
if &aggregator.owner != owner_info.key {
|
||||
return Err(Error::OwnerMismatch.into());
|
||||
}
|
||||
|
||||
// remove submission
|
||||
let mut submissions = aggregator.submissions;
|
||||
|
||||
|
@ -243,7 +261,8 @@ impl Processor {
|
|||
///
|
||||
/// 0. `[writable]` The aggregator(key).
|
||||
/// 1. `[]` Clock sysvar
|
||||
/// 1. `[signer, writable]` The oracle's authority.
|
||||
/// 2. `[writable]` The oracle key.
|
||||
/// 3. `[signer]` The oracle owner.
|
||||
pub fn process_submit(
|
||||
accounts: &[AccountInfo],
|
||||
submission: u64,
|
||||
|
@ -252,6 +271,7 @@ impl Processor {
|
|||
let aggregator_info = next_account_info(account_info_iter)?;
|
||||
let clock_sysvar_info = next_account_info(account_info_iter)?;
|
||||
let oracle_info = next_account_info(account_info_iter)?;
|
||||
let oracle_owner_info = next_account_info(account_info_iter)?;
|
||||
|
||||
let mut aggregator = Aggregator::unpack_unchecked(&aggregator_info.data.borrow())?;
|
||||
if !aggregator.is_initialized {
|
||||
|
@ -262,7 +282,7 @@ impl Processor {
|
|||
return Err(Error::SubmissonValueOutOfRange.into());
|
||||
}
|
||||
|
||||
if !oracle_info.is_signer {
|
||||
if !oracle_owner_info.is_signer {
|
||||
return Err(ProgramError::MissingRequiredSignature);
|
||||
}
|
||||
|
||||
|
@ -271,6 +291,10 @@ impl Processor {
|
|||
return Err(Error::NotFoundOracle.into());
|
||||
}
|
||||
|
||||
if &oracle.owner != oracle_owner_info.key {
|
||||
return Err(Error::OwnerMismatch.into());
|
||||
}
|
||||
|
||||
if &oracle.aggregator != aggregator_info.key {
|
||||
return Err(Error::AggregatorKeyNotMatch.into());
|
||||
}
|
||||
|
|
36
src/state.rs
36
src/state.rs
|
@ -24,8 +24,11 @@ pub struct Aggregator {
|
|||
pub description: [u8; 32],
|
||||
/// is initialized
|
||||
pub is_initialized: bool,
|
||||
/// authority
|
||||
pub owner: Pubkey,
|
||||
/// submissions
|
||||
pub submissions: [Submission; MAX_ORACLES],
|
||||
|
||||
}
|
||||
|
||||
impl IsInitialized for Aggregator {
|
||||
|
@ -36,17 +39,18 @@ impl IsInitialized for Aggregator {
|
|||
|
||||
impl Sealed for Aggregator {}
|
||||
impl Pack for Aggregator {
|
||||
const LEN: usize = 53 + MAX_ORACLES*48;
|
||||
const LEN: usize = 85 + MAX_ORACLES*48;
|
||||
fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
|
||||
let src = array_ref![src, 0, 53 + MAX_ORACLES*48];
|
||||
let src = array_ref![src, 0, 85 + MAX_ORACLES*48];
|
||||
let (
|
||||
submit_interval,
|
||||
min_submission_value,
|
||||
max_submission_value,
|
||||
description,
|
||||
is_initialized,
|
||||
owner,
|
||||
submissions,
|
||||
) = array_refs![src, 4, 8, 8, 32, 1, MAX_ORACLES*48];
|
||||
) = array_refs![src, 4, 8, 8, 32, 1, 32, MAX_ORACLES*48];
|
||||
|
||||
let is_initialized = match is_initialized {
|
||||
[0] => false,
|
||||
|
@ -60,21 +64,23 @@ impl Pack for Aggregator {
|
|||
max_submission_value: u64::from_le_bytes(*max_submission_value),
|
||||
description: *description,
|
||||
is_initialized,
|
||||
owner: Pubkey::new_from_array(*owner),
|
||||
submissions: unpack_submissions(submissions),
|
||||
})
|
||||
}
|
||||
|
||||
fn pack_into_slice(&self, dst: &mut [u8]) {
|
||||
|
||||
let dst = array_mut_ref![dst, 0, 53 + MAX_ORACLES*48];
|
||||
let dst = array_mut_ref![dst, 0, 85 + MAX_ORACLES*48];
|
||||
let (
|
||||
submit_interval_dst,
|
||||
min_submission_value_dst,
|
||||
max_submission_value_dst,
|
||||
description_dst,
|
||||
is_initialized_dst,
|
||||
owner_dst,
|
||||
submissions_dst,
|
||||
) = mut_array_refs![dst, 4, 8, 8, 32, 1, MAX_ORACLES*48];
|
||||
) = mut_array_refs![dst, 4, 8, 8, 32, 1, 32, MAX_ORACLES*48];
|
||||
|
||||
let &Aggregator {
|
||||
submit_interval,
|
||||
|
@ -82,6 +88,7 @@ impl Pack for Aggregator {
|
|||
max_submission_value,
|
||||
description,
|
||||
is_initialized,
|
||||
owner,
|
||||
ref submissions,
|
||||
} = self;
|
||||
|
||||
|
@ -89,6 +96,7 @@ impl Pack for Aggregator {
|
|||
*min_submission_value_dst = min_submission_value.to_le_bytes();
|
||||
*max_submission_value_dst = max_submission_value.to_le_bytes();
|
||||
*description_dst = description;
|
||||
owner_dst.copy_from_slice(owner.as_ref());
|
||||
is_initialized_dst[0] = is_initialized as u8;
|
||||
|
||||
pack_submissions(submissions, submissions_dst);
|
||||
|
@ -111,6 +119,8 @@ pub struct Oracle {
|
|||
pub withdrawable: u64,
|
||||
/// aggregator
|
||||
pub aggregator: Pubkey,
|
||||
/// owner
|
||||
pub owner: Pubkey,
|
||||
}
|
||||
|
||||
impl IsInitialized for Oracle {
|
||||
|
@ -121,14 +131,14 @@ impl IsInitialized for Oracle {
|
|||
|
||||
impl Sealed for Oracle {}
|
||||
impl Pack for Oracle {
|
||||
const LEN: usize = 89;
|
||||
const LEN: usize = 121;
|
||||
fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
|
||||
|
||||
let src = array_ref![src, 0, 89];
|
||||
let src = array_ref![src, 0, 121];
|
||||
let (
|
||||
submission, next_submit_time, description, is_initialized,
|
||||
withdrawable, aggregator,
|
||||
) = array_refs![src, 8, 8, 32, 1, 8, 32];
|
||||
withdrawable, aggregator, owner,
|
||||
) = array_refs![src, 8, 8, 32, 1, 8, 32, 32];
|
||||
|
||||
let is_initialized = match is_initialized {
|
||||
[0] => false,
|
||||
|
@ -143,12 +153,13 @@ impl Pack for Oracle {
|
|||
is_initialized,
|
||||
withdrawable: u64::from_le_bytes(*withdrawable),
|
||||
aggregator: Pubkey::new_from_array(*aggregator),
|
||||
owner: Pubkey::new_from_array(*owner),
|
||||
})
|
||||
}
|
||||
|
||||
fn pack_into_slice(&self, dst: &mut [u8]) {
|
||||
|
||||
let dst = array_mut_ref![dst, 0, 89];
|
||||
let dst = array_mut_ref![dst, 0, 121];
|
||||
let (
|
||||
submission_dst,
|
||||
next_submit_time_dst,
|
||||
|
@ -156,7 +167,8 @@ impl Pack for Oracle {
|
|||
is_initialized_dst,
|
||||
withdrawable_dst,
|
||||
aggregator_dst,
|
||||
) = mut_array_refs![dst, 8, 8, 32, 1, 8, 32];
|
||||
owner_dst,
|
||||
) = mut_array_refs![dst, 8, 8, 32, 1, 8, 32, 32];
|
||||
|
||||
let &Oracle {
|
||||
submission,
|
||||
|
@ -165,6 +177,7 @@ impl Pack for Oracle {
|
|||
is_initialized,
|
||||
withdrawable,
|
||||
aggregator,
|
||||
owner,
|
||||
} = self;
|
||||
|
||||
*submission_dst = submission.to_le_bytes();
|
||||
|
@ -173,6 +186,7 @@ impl Pack for Oracle {
|
|||
is_initialized_dst[0] = is_initialized as u8;
|
||||
*withdrawable_dst = withdrawable.to_le_bytes();
|
||||
aggregator_dst.copy_from_slice(aggregator.as_ref());
|
||||
owner_dst.copy_from_slice(owner.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue