add client script, fix some bugs

This commit is contained in:
czl1378 2020-12-08 21:14:05 +08:00
parent afc7e766cf
commit 5334a4ebfb
10 changed files with 667 additions and 22 deletions

5
client/.env Normal file
View File

@ -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

2
client/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules
dist

300
client/FluxAggregator.ts Normal file
View File

@ -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 },
]);
}
}

6
client/deploy.json Normal file
View File

@ -0,0 +1,6 @@
{
"programAccount": {
"pubkey": "FfR1CiA4i2xvYLycCU8kv9mLpcT7Woyqkgvkz7hzjEfN",
"secret": "5b0d673d12b9a811eb9f1a770f86e4c516879f95cf60191c78b3a8c5d6fb6f1ed9dad41af1513e8ab932aab3f56bafcdb84c5fedc9df8a299e7bf53ec84784a5"
}
}

109
client/index.ts Normal file
View File

@ -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 }))

21
client/package.json Normal file
View File

@ -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"
}
}

74
client/tsconfig.json Normal file
View File

@ -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/**/*"
]
}

90
client/utils.ts Normal file
View File

@ -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,
}
}

View File

@ -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());
}

View File

@ -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());
}
}