This commit is contained in:
De Facto 2021-02-05 21:40:09 +08:00
parent 01a910e7aa
commit 7a5112147f
11 changed files with 444 additions and 258 deletions

View File

@ -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)?;
```
```

View File

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

View File

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

View File

@ -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::<Error>();
// 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::<Error>();
// // msg!("{:?}", error);
// return Err(error);
// }
Ok(())
}

View File

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

View File

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

View File

@ -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::<Aggregator>()
);
println!(
"Submissions len: {}",
borsh_utils::get_packed_len::<Submissions>()
);
println!("Oracle len: {}", borsh_utils::get_packed_len::<Oracle>());
}
}

View File

@ -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<Account> {
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<Account> {
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<void> {
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<void> {
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<void> {
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 },
]
)
}
}
}

View File

@ -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 <address>")
@ -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 <string>", "feed pair name")
.option("--submitInterval <number>", "min wait time between submissions", "6")
.option("--submissionDecimals <number>", "submission decimals", "12")
.option("--minSubmissionValue <number>", "minSubmissionValue", "0")
.option("--maxSubmissionValue <number>", "maxSubmissionValue", "18446744073709551615")
.option("--decimals <number>", "submission decimals", "2")
.option("--minSubmissions <number>", "minSubmissions", "1")
.option("--maxSubmissions <number>", "maxSubmissions", "12")
.option("--rewardAmount <number>", "rewardAmount", "1")
.option("--restartDelay <number>", "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 <string>", "feed address to submit values to")
.option("--oracleAddress <string>", "feed address to submit values to")
.option("--pairSymbol <string>", "market pair to feed")
.option("--pairSymbol <string>", "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 <number>", "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)
cli.parse(process.argv)

View File

@ -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::<Submission>()?
//
// 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],

View File

@ -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<void> {
@ -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<Wallet> {
export async function walletFromEnv(
key: string,
conn: Connection
): Promise<Wallet> {
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<Deployer> {
}
return Deployer.open(deployFile)
}
}