From 7a5112147fac2d6a43ca0b133325611f5ae5b24a Mon Sep 17 00:00:00 2001 From: De Facto Date: Fri, 5 Feb 2021 21:40:09 +0800 Subject: [PATCH] wtf --- README.md | 10 +- package.json | 2 +- program/Cargo.toml | 2 +- program/src/entrypoint.rs | 18 +- program/src/instruction.rs | 20 ++ program/src/processor.rs | 4 +- program/src/state.rs | 21 +++ src/FluxAggregator.ts | 368 ++++++++++++++++++++----------------- src/cli.ts | 128 ++++++++----- src/schema.ts | 104 ++++++++--- src/utils.ts | 25 ++- 11 files changed, 444 insertions(+), 258 deletions(-) diff --git a/README.md b/README.md index 42bc56a..1cc59cf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # solana-flux-aggregator -Solnana Flux Aggregator +Solnana Flux Aggregator Price Feeds: [https://sol.link](https://sol.link) @@ -51,11 +51,7 @@ Create the `btc:usd` feed (that accepts max and min u64 as valid submission valu ``` yarn solink add-aggregator \ - --feedName btc:usd \ - --submitInterval 6 \ - --minSubmissionValue 0 \ - --maxSubmissionValue 18446744073709551615 \ - --submissionDecimals 2 + --feedName btc:usd feed initialized, pubkey: 3aTBom2uodyWkuVPiUkwCZ2HiFywdUx9tp7su7U2H4Nx ``` @@ -137,4 +133,4 @@ use flux_aggregator; let feed_info = next_account_info(accounts_iter)?; let value = flux_aggregator::get_median(feed_info)?; -``` \ No newline at end of file +``` diff --git a/package.json b/package.json index ca81d62..59b9ad9 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "author": "", "license": "ISC", "dependencies": { - "@solana/web3.js": "^0.87.1", + "@solana/web3.js": "^0.90.5", "buffer-layout": "^1.2.0", "commander": "^6.2.0", "dotenv": "^8.2.0", diff --git a/program/Cargo.toml b/program/Cargo.toml index 9e3d2e4..f2502b9 100644 --- a/program/Cargo.toml +++ b/program/Cargo.toml @@ -16,10 +16,10 @@ thiserror = "1.0" num-derive = "0.3" num-traits = "0.2" num_enum = "0.5.1" +hex = "0.4" [dev-dependencies] solana-sdk = "1.4.8" -hex = "0.4" [lib] crate-type = ["cdylib", "lib"] diff --git a/program/src/entrypoint.rs b/program/src/entrypoint.rs index 0a02aee..36257c7 100644 --- a/program/src/entrypoint.rs +++ b/program/src/entrypoint.rs @@ -1,11 +1,14 @@ //! Program entrypoint -use crate::{processor::Processor}; +use crate::processor::Processor; use solana_program::{ account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey, + msg, }; +use hex; + entrypoint!(process_instruction); // Program entrypoint's implementation @@ -14,11 +17,12 @@ fn process_instruction<'a>( accounts: &'a [AccountInfo<'a>], instruction_data: &[u8], ) -> ProgramResult { - if let Err(error) = Processor::process(program_id, accounts, instruction_data) { - // catch the error so we can print it - // error.print::(); - // msg!("{:?}", error); - return Err(error); - } + msg!("instruction: {}", hex::encode(instruction_data)); + // if let Err(error) = Processor::process(program_id, accounts, instruction_data) { + // // catch the error so we can print it + // // error.print::(); + // // msg!("{:?}", error); + // return Err(error); + // } Ok(()) } diff --git a/program/src/instruction.rs b/program/src/instruction.rs index b00dd0c..0c3fad2 100644 --- a/program/src/instruction.rs +++ b/program/src/instruction.rs @@ -38,3 +38,23 @@ pub enum Instruction { faucet_owner_seed: [u8; 32], }, } + +#[cfg(test)] +mod tests { + use std::convert::TryFrom; + + use solana_program::{entrypoint::ProgramResult, program_error::ProgramError}; + + use super::*; + + #[test] + fn test_decode_instruction() -> ProgramResult { + let input = hex::decode("004254433a5553442020202020202020202020202020202020202020202020202002010c010100000000000000").map_err(|_| ProgramError::InvalidInstructionData)?; + + let inx = Instruction::try_from_slice(&input) + .map_err(|_| ProgramError::InvalidInstructionData)?; + println!("{:?}", inx); + + Ok(()) + } +} diff --git a/program/src/processor.rs b/program/src/processor.rs index 4a11cc4..f6d8b85 100644 --- a/program/src/processor.rs +++ b/program/src/processor.rs @@ -42,8 +42,8 @@ impl<'a, 'b> Accounts<'a, 'b> { struct InitializeContext<'a> { rent: Rent, aggregator: &'a AccountInfo<'a>, - aggregator_owner: &'a AccountInfo<'a>, - round_submissions: &'a AccountInfo<'a>, // belongs_to: aggregator + aggregator_owner: &'a AccountInfo<'a>, // signed + round_submissions: &'a AccountInfo<'a>, // belongs_to: aggregator answer_submissions: &'a AccountInfo<'a>, // belongs_to: aggregator config: AggregatorConfig, diff --git a/program/src/state.rs b/program/src/state.rs index 444186d..c91b6f2 100644 --- a/program/src/state.rs +++ b/program/src/state.rs @@ -187,3 +187,24 @@ impl IsInitialized for Oracle { } } impl InitBorshState for Oracle {} + +mod tests { + use crate::borsh_utils; + + use super::*; + + #[test] + fn test_packed_len() { + println!( + "Aggregator len: {}", + borsh_utils::get_packed_len::() + ); + + println!( + "Submissions len: {}", + borsh_utils::get_packed_len::() + ); + + println!("Oracle len: {}", borsh_utils::get_packed_len::()); + } +} diff --git a/src/FluxAggregator.ts b/src/FluxAggregator.ts index 90f25ed..264ab66 100644 --- a/src/FluxAggregator.ts +++ b/src/FluxAggregator.ts @@ -1,18 +1,27 @@ import { - PublicKey, BaseProgram, Account, - Wallet, System, SPLToken -} from "solray"; + 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, BufferLayout } from "solray/lib/util/encoding"; + SYSVAR_RENT_PUBKEY, + SYSVAR_CLOCK_PUBKEY, + TransactionInstruction, + SystemProgram, +} from "@solana/web3.js" import { - decodeOracleInfo -} from "./utils" + publicKey, + u64LEBuffer, + uint64, + BufferLayout, +} from "solray/lib/util/encoding" + +import { decodeOracleInfo } from "./utils" // @ts-ignore // import BufferLayout from "buffer-layout"; @@ -21,125 +30,147 @@ import { schema } from "./schema" import * as encoding from "./schema" import { deserialize, serialize } from "borsh" -export const AggregatorLayout = BufferLayout.struct([ - BufferLayout.blob(4, "submitInterval"), - uint64("minSubmissionValue"), - uint64("maxSubmissionValue"), - BufferLayout.u8("submissionDecimals"), - BufferLayout.blob(32, "description"), - BufferLayout.u8("isInitialized"), - publicKey('owner'), - BufferLayout.blob(576, "submissions"), -]); +export const AggregatorLayout = BufferLayout.struct([]) export const OracleLayout = BufferLayout.struct([ uint64("nextSubmitTime"), BufferLayout.blob(32, "description"), BufferLayout.u8("isInitialized"), uint64("withdrawable"), - publicKey('aggregator'), - publicKey('owner'), -]); + publicKey("aggregator"), + publicKey("owner"), +]) export const SubmissionLayout = BufferLayout.struct([ uint64("time"), uint64("value"), - publicKey('oracle'), -]); + publicKey("oracle"), +]) interface InitializeParams { - submitInterval: number; - minSubmissionValue: bigint; - maxSubmissionValue: bigint; - submissionDecimals: number; - description: string; - owner: Account; + config: encoding.AggregatorConfig + owner: Account } interface InitializeInstructionParams extends InitializeParams { - aggregator: PublicKey; + aggregator: PublicKey } interface AddOracleParams { - owner: PublicKey; - description: string; - aggregator: PublicKey; + owner: PublicKey + description: string + aggregator: PublicKey // To prove you are the aggregator owner - aggregatorOwner: Account; + aggregatorOwner: Account } interface AddOracleInstructionParams extends AddOracleParams { - oracle: PublicKey; + oracle: PublicKey } interface RemoveOracleParams { - aggregator: PublicKey; - oracle: PublicKey; + aggregator: PublicKey + oracle: PublicKey // To prove you are the aggregator owner - authority?: Account; + authority?: Account } -interface RemoveOracleInstructionParams extends RemoveOracleParams { -} +interface RemoveOracleInstructionParams extends RemoveOracleParams {} interface SubmitParams { - aggregator: PublicKey; - oracle: PublicKey; + aggregator: PublicKey + oracle: PublicKey // The oracle"s index - submission: bigint; + submission: bigint // oracle owner - owner: Account; + owner: Account } -interface SubmitInstructionParams extends SubmitParams { -} +interface SubmitInstructionParams extends SubmitParams {} interface WithdrawParams { - aggregator: PublicKey, + aggregator: PublicKey // withdraw to - receiver: PublicKey, + receiver: PublicKey // withdraw amount - amount: bigint, - tokenAccount: PublicKey, - tokenOwner: PublicKey, + amount: bigint + tokenAccount: PublicKey + tokenOwner: PublicKey // signer - authority: Account, + authority: Account } -interface WithdrawInstructionParams extends WithdrawParams { -} +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); + super(wallet, programID) + this.sys = new System(this.wallet) } public async initialize(params: InitializeParams): Promise { - const account = new Account(); + const aggregator = new Account() + const answer_submissions = new Account() + const round_submissions = 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]); + console.log({ config: params.config, program: this.programID.toString() }) - return account; + const input = encoding.Initialize.serialize({ config: params.config }) + // console.log("init instruction", input.toString("hex")) + console.log("init instruction", input.toString("hex")) + console.log("rent", [encoding.Aggregator.size, encoding.Submissions.size]) + console.log("wallet", this.account.publicKey.toString()) + + await this.sendTx( + [ + // await this.sys.createRentFreeAccountInstruction({ + // newPubicKey: aggregator.publicKey, + // space: encoding.Aggregator.size, + // programID: this.programID, + // }), + // await this.sys.createRentFreeAccountInstruction({ + // newPubicKey: answer_submissions.publicKey, + // space: encoding.Submissions.size, + // programID: this.programID, + // }), + // await this.sys.createRentFreeAccountInstruction({ + // newPubicKey: round_submissions.publicKey, + // space: encoding.Submissions.size, + // programID: this.programID, + // }), + this.instruction(Buffer.from("deadbeaf", "hex"), [ + // SYSVAR_RENT_PUBKEY, + // { write: aggregator.publicKey }, + // params.owner, // signed + // { write: round_submissions.publicKey }, + // { write: answer_submissions.publicKey }, + + // SYSVAR_RENT_PUBKEY, + // { write: aggregator }, + // params.owner, // signed + // { write: round_submissions }, + // { write: answer_submissions }, + ]), + ], + [ + this.account, + // aggregator, + // params.owner, + // round_submissions, + // answer_submissions, + ] + ) + + return aggregator } - private initializeInstruction(params: InitializeInstructionParams): TransactionInstruction { + private initializeInstruction( + params: InitializeInstructionParams + ): TransactionInstruction { let { aggregator, owner } = params const input = encoding.Initialize.serialize(params) - // console.log(input.toString("hex")) return this.instruction(input, [ SYSVAR_RENT_PUBKEY, @@ -149,21 +180,24 @@ export default class FluxAggregator extends BaseProgram { } public async addOracle(params: AddOracleParams): Promise { - const account = new Account(); + const oracle = 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]); + await this.sendTx( + [ + await this.sys.createRentFreeAccountInstruction({ + newPubicKey: oracle.publicKey, + space: encoding.Oracle.size, + programID: this.programID, + }), + this.addOracleInstruction({ + ...params, + oracle: oracle.publicKey, + }), + ], + [this.account, oracle, params.aggregatorOwner] + ) - return account; + return oracle } public async oracleInfo(pubkey: PublicKey) { @@ -171,99 +205,100 @@ export default class FluxAggregator extends BaseProgram { return decodeOracleInfo(info) } - private addOracleInstruction(params: AddOracleInstructionParams): TransactionInstruction { - const { - oracle, - owner, - description, - aggregator, - aggregatorOwner, - } = params; + 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, - ]); + 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 { - await this.sendTx([ - this.removeOracleInstruction(params) - ], [this.account, params.authority || this.wallet.account]); + await this.sendTx( + [this.removeOracleInstruction(params)], + [this.account, params.authority || this.wallet.account] + ) } - private removeOracleInstruction(params: RemoveOracleInstructionParams): TransactionInstruction { - const { - authority, - aggregator, - oracle, - } = params; + private removeOracleInstruction( + params: RemoveOracleInstructionParams + ): TransactionInstruction { + const { authority, aggregator, oracle } = params const layout = BufferLayout.struct([ BufferLayout.u8("instruction"), BufferLayout.blob(32, "oracle"), - ]); + ]) - return this.instructionEncode(layout, { - instruction: 2, // remove oracle instruction - oracle: oracle.toBuffer() - }, [ - // - { write: aggregator }, - authority || this.wallet.account, - ]); + return this.instructionEncode( + layout, + { + instruction: 2, // remove oracle instruction + oracle: oracle.toBuffer(), + }, + [ + // + { write: aggregator }, + authority || this.wallet.account, + ] + ) } public async submit(params: SubmitParams): Promise { - await this.sendTx([ - this.submitInstruction(params) - ], [this.account, params.owner]); - + await this.sendTx( + [this.submitInstruction(params)], + [this.account, params.owner] + ) } - private submitInstruction(params: SubmitInstructionParams): TransactionInstruction { - const { - aggregator, - oracle, - submission, - owner, - } = params; + 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, - ]); + 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 { - await this.sendTx([ - this.withdrawInstruction(params) - ], [this.account, params.authority]); - + await this.sendTx( + [this.withdrawInstruction(params)], + [this.account, params.authority] + ) } - private withdrawInstruction(params: WithdrawInstructionParams): TransactionInstruction { + private withdrawInstruction( + params: WithdrawInstructionParams + ): TransactionInstruction { const { aggregator, receiver, @@ -271,24 +306,27 @@ export default class FluxAggregator extends BaseProgram { tokenOwner, tokenAccount, authority, - } = params; + } = 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 }, - ]); + return this.instructionEncode( + layout, + { + instruction: 4, // withdraw instruction + amount: u64LEBuffer(amount), + }, + [ + { write: aggregator }, + { write: tokenAccount }, + { write: receiver }, + SPLToken.programID, + tokenOwner, + { write: authority }, + ] + ) } - -} \ No newline at end of file +} diff --git a/src/cli.ts b/src/cli.ts index 1fa602f..082a191 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -4,13 +4,19 @@ import fs from "fs" import path from "path" import { - BPFLoader, PublicKey, Wallet, NetworkName, - solana, Deployer, SPLToken, ProgramAccount + BPFLoader, + PublicKey, + Wallet, + NetworkName, + solana, + Deployer, + SPLToken, + ProgramAccount, } from "solray" import dotenv from "dotenv" -import FluxAggregator, { AggregatorLayout, OracleLayout } from "./FluxAggregator" +import FluxAggregator from "./FluxAggregator" import { decodeAggregatorInfo, @@ -20,17 +26,20 @@ import { } from "./utils" import * as feed from "./feed" +import { AggregatorConfig } from "./schema" dotenv.config() const cli = new Command() -const FLUX_AGGREGATOR_SO = path.resolve(__dirname, "../build/flux_aggregator.so") +const FLUX_AGGREGATOR_SO = path.resolve( + __dirname, + "../build/flux_aggregator.so" +) const network = (process.env.NETWORK || "local") as NetworkName const conn = solana.connect(network) class AppContext { - static readonly AGGREGATOR_PROGRAM = "aggregatorProgram" static async forAdmin() { @@ -47,7 +56,7 @@ class AppContext { return new AppContext(deployer, wallet) } - constructor(public deployer: Deployer, public wallet: Wallet) { } + constructor(public deployer: Deployer, public wallet: Wallet) {} get aggregatorProgramID() { return this.aggregatorProgramAccount.publicKey @@ -70,7 +79,16 @@ class AppContext { function color(s, c = "black", b = false): string { // 30m Black, 31m Red, 32m Green, 33m Yellow, 34m Blue, 35m Magenta, 36m Cyanic, 37m White - const cArr = ["black", "red", "green", "yellow", "blue", "megenta", "cyanic", "white"] + const cArr = [ + "black", + "red", + "green", + "yellow", + "blue", + "megenta", + "cyanic", + "white", + ] let cIdx = cArr.indexOf(c) let bold = b ? "\x1b[1m" : "" @@ -89,14 +107,13 @@ function log(message: any) { console.log(message) } -cli - .command("generate-wallet").action(async () => { - const mnemonic = Wallet.generateMnemonic() - const wallet = await Wallet.fromMnemonic(mnemonic, conn) +cli.command("generate-wallet").action(async () => { + const mnemonic = Wallet.generateMnemonic() + const wallet = await Wallet.fromMnemonic(mnemonic, conn) - log(`address: ${wallet.address}`) - log(`mnemonic: ${mnemonic}`) - }) + log(`address: ${wallet.address}`) + log(`mnemonic: ${mnemonic}`) +}) cli .command("airdrop
") @@ -118,40 +135,63 @@ cli .action(async () => { const { wallet, deployer } = await AppContext.forAdmin() - const programAccount = await deployer.ensure(AppContext.AGGREGATOR_PROGRAM, async () => { - const programBinary = fs.readFileSync(FLUX_AGGREGATOR_SO) + const programAccount = await deployer.ensure( + AppContext.AGGREGATOR_PROGRAM, + async () => { + const programBinary = fs.readFileSync(FLUX_AGGREGATOR_SO) - log(`deploying ${FLUX_AGGREGATOR_SO}...`) - const bpfLoader = new BPFLoader(wallet) + log(`deploying ${FLUX_AGGREGATOR_SO}...`) + const bpfLoader = new BPFLoader(wallet) - return bpfLoader.load(programBinary) - }) + return bpfLoader.load(programBinary) + } + ) - log(`deployed aggregator program. program id: ${color(programAccount.publicKey.toBase58(), "blue")}`) + log( + `deployed aggregator program. program id: ${color( + programAccount.publicKey.toBase58(), + "blue" + )}` + ) }) cli .command("add-aggregator") .description("create an aggregator") .option("--feedName ", "feed pair name") - .option("--submitInterval ", "min wait time between submissions", "6") - .option("--submissionDecimals ", "submission decimals", "12") - .option("--minSubmissionValue ", "minSubmissionValue", "0") - .option("--maxSubmissionValue ", "maxSubmissionValue", "18446744073709551615") + .option("--decimals ", "submission decimals", "2") + .option("--minSubmissions ", "minSubmissions", "1") + .option("--maxSubmissions ", "maxSubmissions", "12") + .option("--rewardAmount ", "rewardAmount", "1") + .option("--restartDelay ", "restartDelay", "1") .action(async (opts) => { - const { deployer, wallet, aggregatorProgramAccount: aggregatorProgram } = await AppContext.forAdmin() + const { + deployer, + wallet, + aggregatorProgramAccount: aggregatorProgram, + } = await AppContext.forAdmin() - const { feedName, submitInterval, minSubmissionValue, maxSubmissionValue, submissionDecimals } = opts + const { + feedName, + restartDelay, + minSubmissions, + maxSubmissions, + decimals, + rewardAmount, + } = opts const aggregator = new FluxAggregator(wallet, aggregatorProgram.publicKey) const feed = await deployer.ensure(feedName, async () => { return aggregator.initialize({ - submitInterval: parseInt(submitInterval), - minSubmissionValue: BigInt(minSubmissionValue), - maxSubmissionValue: BigInt(maxSubmissionValue), - description: feedName, - submissionDecimals, + config: new AggregatorConfig({ + description: feedName, + decimals, + minSubmissions, + maxSubmissions, + restartDelay, + rewardAmount: BigInt(rewardAmount), + }), owner: wallet.account, }) }) @@ -200,7 +240,7 @@ cli description: oracleName.substr(0, 32).padEnd(32), aggregator: new PublicKey(feedAddress), aggregatorOwner: wallet.account, - }); + }) log(`added oracle. pubkey: ${color(oracle.publicKey.toBase58(), "blue")}`) }) @@ -264,10 +304,12 @@ cli .description("oracle feeds to aggregator") .option("--feedAddress ", "feed address to submit values to") .option("--oracleAddress ", "feed address to submit values to") - .option("--pairSymbol ", "market pair to feed") + .option("--pairSymbol ", "market pair to feed") .action(async (opts) => { - - const { wallet, aggregatorProgramAccount: aggregatorProgram } = await AppContext.forOracle() + const { + wallet, + aggregatorProgramAccount: aggregatorProgram, + } = await AppContext.forOracle() const { feedAddress, oracleAddress, pairSymbol } = opts @@ -286,7 +328,11 @@ cli .description("create test token") .option("--amount ", "amount of the test token") .action(async (opts) => { - const { wallet, aggregatorProgramAccount: aggregatorProgram, deployer } = await AppContext.forAdmin() + const { + wallet, + aggregatorProgramAccount: aggregatorProgram, + deployer, + } = await AppContext.forAdmin() const { amount } = opts @@ -313,7 +359,7 @@ cli // 3. create token account const tokenAccount = await spltoken.initializeAccount({ token: token.publicKey, - owner: tokenOwner.pubkey + owner: tokenOwner.pubkey, }) log(`mint ${amount} token to token account...`) @@ -332,10 +378,8 @@ cli address: tokenOwner.address, seed: tokenOwner.noncedSeed.toString("hex"), nonce: tokenOwner.nonce, - } + }, }) - }) - -cli.parse(process.argv) \ No newline at end of file +cli.parse(process.argv) diff --git a/src/schema.ts b/src/schema.ts index 4d0f831..6d1240b 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -2,6 +2,8 @@ import { PublicKey, Account } from "solray" import BN from "bn.js" import { deserialize, serialize } from "borsh" +const MAX_ORACLES = 12 + const boolMapper = { encode: boolToInt, decode: intToBool, @@ -75,23 +77,75 @@ class Submission { } } -export class Aggregator extends Serialization { - public submitInterval!: number - public minSubmissionValue!: BN - public maxSubmissionValue!: BN - public submissions!: Submission[] +export class AggregatorConfig extends Serialization { + public decimals!: number + public description!: string + public restartDelay!: number + public rewardAmount!: number + public maxSubmissions!: number + public minSubmissions!: number public static schema = { kind: "struct", fields: [ - ["submitInterval", "u32"], - ["minSubmissionValue", "u64"], - ["maxSubmissionValue", "u64"], - ["submissionDecimals", "u8"], - ["description", [32], str32Mapper], // fixed-sized-u8-array - ["isInitialized", "u8", boolMapper], // no mapping for bool? + ["description", [32], str32Mapper], + ["decimals", "u8"], + ["restartDelay", "u8"], + ["maxSubmissions", "u8"], + ["minSubmissions", "u8"], + ["rewardAmount", "u64"], + ], + } +} + +export class Submissions extends Serialization { + public static size = 577 + public static schema = { + kind: "struct", + fields: [ + ["isInitialized", "u8", boolMapper], + ["submissions", [MAX_ORACLES, Submission]], + ], + } +} +class Round extends Serialization { + public static schema = { + kind: "struct", + fields: [ + ["id", "u64"], + ["created_at", "u64"], + ["updated_at", "u64"], + ], + } +} + +class Answer extends Serialization { + public static schema = { + kind: "struct", + fields: [ + ["round_id", "u64"], + ["created_at", "u64"], + ["updated_at", "u64"], + ], + } +} + +export class Aggregator extends Serialization { + public static size = 189 + + public config!: AggregatorConfig + // public submissions!: Submission[] + + public static schema = { + kind: "struct", + fields: [ + ["config", AggregatorConfig], + ["isInitialized", "u8", boolMapper], ["owner", [32], pubkeyMapper], - ["submissions", [Submission, 12]], + ["round", Round], + ["round_submissions", [32], pubkeyMapper], + ["answer", Answer], + ["answer_submissions", [32], pubkeyMapper], ], } } @@ -103,22 +157,16 @@ abstract class InstructionSerialization extends Serialization { } export class Initialize extends InstructionSerialization { - public submitInterval!: number - public minSubmissionValue!: number - public maxSubmissionValue!: number - public submissionDecimals!: number - /// A short description of what is being reported - public description!: string + // public submitInterval!: number + // public minSubmissionValue!: number + // public maxSubmissionValue!: number + // public submissionDecimals!: number + // /// A short description of what is being reported + // public description!: string public static schema = { kind: "struct", - fields: [ - ["submitInterval", "u32"], - ["minSubmissionValue", "u64"], - ["maxSubmissionValue", "u64"], - ["submissionDecimals", "u8"], - ["description", [32], str32Mapper], - ], + fields: [["config", AggregatorConfig]], } } @@ -167,12 +215,18 @@ function boolToInt(t: boolean) { } } +export class Oracle { + public static size = 113 +} + // if there is optional or variable length items, what is: borsh_utils::get_packed_len::()? // // would panic given variable sized types export const schema = new Map([ [Aggregator, Aggregator.schema], + [AggregatorConfig, AggregatorConfig.schema], + [Submissions, Submissions.schema], [Submission, Submission.schema], [Initialize, Initialize.schema], [Instruction, Instruction.schema], diff --git a/src/utils.ts b/src/utils.ts index c09a54d..681f6ed 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,10 @@ import { Connection, PublicKey } from "@solana/web3.js" -import { AggregatorLayout, SubmissionLayout, OracleLayout } from "./FluxAggregator" +import { + AggregatorLayout, + SubmissionLayout, + OracleLayout, +} from "./FluxAggregator" import { solana, Wallet, NetworkName, Deployer } from "solray" @@ -17,9 +21,8 @@ export function getMedian(submissions: number[]): number { return values[0] } else { let i = len / 2 - return len % 2 == 0 ? (values[i] + values[i-1])/2 : values[i] + return len % 2 == 0 ? (values[i] + values[i - 1]) / 2 : values[i] } - } export function sleep(ms: number): Promise { @@ -46,7 +49,10 @@ export function decodeAggregatorInfo(accountInfo) { for (let i = 0; i < aggregator.submissions.length / submissionSpace; i++) { let submission = SubmissionLayout.decode( - aggregator.submissions.slice(i*submissionSpace, (i+1)*submissionSpace) + aggregator.submissions.slice( + i * submissionSpace, + (i + 1) * submissionSpace + ) ) submission.oracle = new PublicKey(submission.oracle) @@ -68,8 +74,8 @@ export function decodeAggregatorInfo(accountInfo) { submissionValue: getMedian(submissions), submitInterval, description, - oracles: submissions.map(s => s.oracle.toString()), - latestUpdateTime: new Date(Number(latestUpdateTime)*1000), + oracles: submissions.map((s) => s.oracle.toString()), + latestUpdateTime: new Date(Number(latestUpdateTime) * 1000), } } @@ -88,7 +94,10 @@ export function decodeOracleInfo(accountInfo) { return oracle } -export async function walletFromEnv(key: string, conn: Connection): Promise { +export async function walletFromEnv( + key: string, + conn: Connection +): Promise { const mnemonic = process.env[key] if (!mnemonic) { throw new Error(`Set ${key} in .env to be a mnemonic`) @@ -107,4 +116,4 @@ export async function openDeployer(): Promise { } return Deployer.open(deployFile) -} \ No newline at end of file +}