can submit value
This commit is contained in:
parent
d445491cb3
commit
39476b0aec
|
@ -81,7 +81,7 @@ Next we create a new oracle to the feed we've created previously, and set its ow
|
|||
|
||||
```
|
||||
yarn solink add-oracle \
|
||||
--feedAddress 3aTBom2uodyWkuVPiUkwCZ2HiFywdUx9tp7su7U2H4Nx \
|
||||
--aggregatorAddress 3aTBom2uodyWkuVPiUkwCZ2HiFywdUx9tp7su7U2H4Nx \
|
||||
--oracleName solink-test \
|
||||
--oracleOwner FosLwbttPgkEDv36VJLU3wwXcBSSoUGkh7dyZPsXNtT4
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
use crate::processor::Processor;
|
||||
|
||||
use solana_program::{
|
||||
msg,
|
||||
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey,
|
||||
};
|
||||
|
||||
|
@ -14,11 +15,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!("call aggregator yo");
|
||||
// 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(())
|
||||
}
|
||||
|
|
|
@ -53,7 +53,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_decode_instruction() -> ProgramResult {
|
||||
let input = hex::decode("004254433a5553442020202020202020202020202020202020202020202020202002010c010100000000000000").map_err(|_| ProgramError::InvalidInstructionData)?;
|
||||
let mut input = hex::decode("004254433a5553442020202020202020202020202020202020202020202020202002010c010100000000000000").map_err(|_| ProgramError::InvalidInstructionData)?;
|
||||
let mut input = hex::decode("02534f4c494e4b2d54455354202020202020202020202020202020202020202020").map_err(|_| ProgramError::InvalidInstructionData)?;
|
||||
|
||||
let inx = Instruction::try_from_slice(&input)
|
||||
.map_err(|_| ProgramError::InvalidInstructionData)?;
|
||||
|
|
|
@ -7,6 +7,7 @@ use crate::{
|
|||
};
|
||||
|
||||
use solana_program::{
|
||||
msg,
|
||||
account_info::AccountInfo,
|
||||
clock::Clock,
|
||||
entrypoint::ProgramResult,
|
||||
|
@ -112,9 +113,11 @@ impl<'a> AddOracleContext<'a> {
|
|||
fn process(&self) -> ProgramResult {
|
||||
// Note: there can in fact be more oracles than max_submissions
|
||||
let aggregator = Aggregator::load_initialized(self.aggregator)?;
|
||||
msg!("loaded aggregator");
|
||||
aggregator.authorize(self.aggregator_owner)?;
|
||||
|
||||
let mut oracle = Oracle::init_uninitialized(self.oracle)?;
|
||||
msg!("loaded oracle");
|
||||
oracle.is_initialized = true;
|
||||
oracle.description = self.description;
|
||||
oracle.owner = self.oracle_owner.into();
|
||||
|
|
|
@ -29,6 +29,7 @@ import { decodeOracleInfo } from "./utils"
|
|||
import { schema } from "./schema"
|
||||
import * as encoding from "./schema"
|
||||
import { deserialize, serialize } from "borsh"
|
||||
import { conn } from "./context"
|
||||
|
||||
export const AggregatorLayout = BufferLayout.struct([])
|
||||
|
||||
|
@ -57,15 +58,11 @@ interface InitializeInstructionParams extends InitializeParams {
|
|||
}
|
||||
|
||||
interface AddOracleParams {
|
||||
owner: PublicKey
|
||||
description: string
|
||||
aggregator: PublicKey
|
||||
// To prove you are the aggregator owner
|
||||
aggregatorOwner: Account
|
||||
}
|
||||
|
||||
interface AddOracleInstructionParams extends AddOracleParams {
|
||||
oracle: PublicKey
|
||||
oracleOwner: PublicKey
|
||||
description: string
|
||||
}
|
||||
|
||||
interface RemoveOracleParams {
|
||||
|
@ -78,15 +75,17 @@ interface RemoveOracleParams {
|
|||
interface RemoveOracleInstructionParams extends RemoveOracleParams {}
|
||||
|
||||
interface SubmitParams {
|
||||
aggregator: PublicKey
|
||||
oracle: PublicKey
|
||||
// The oracle"s index
|
||||
submission: bigint
|
||||
// oracle owner
|
||||
owner: Account
|
||||
}
|
||||
accounts: {
|
||||
aggregator: { write: PublicKey }
|
||||
roundSubmissions: { write: PublicKey }
|
||||
answerSubmissions: { write: PublicKey }
|
||||
oracle: { write: PublicKey }
|
||||
oracle_owner: Account
|
||||
}
|
||||
|
||||
interface SubmitInstructionParams extends SubmitParams {}
|
||||
round_id: BigInt
|
||||
value: BigInt
|
||||
}
|
||||
|
||||
interface WithdrawParams {
|
||||
aggregator: PublicKey
|
||||
|
@ -114,8 +113,6 @@ export default class FluxAggregator extends BaseProgram {
|
|||
const answer_submissions = new Account()
|
||||
const round_submissions = new Account()
|
||||
|
||||
console.log({ config: params.config, program: this.programID.toString() })
|
||||
|
||||
const input = encoding.Initialize.serialize({ config: params.config })
|
||||
|
||||
await this.sendTx(
|
||||
|
@ -158,6 +155,10 @@ export default class FluxAggregator extends BaseProgram {
|
|||
public async addOracle(params: AddOracleParams): Promise<Account> {
|
||||
const oracle = new Account()
|
||||
|
||||
const input = encoding.AddOracle.serialize({
|
||||
description: params.description,
|
||||
})
|
||||
|
||||
await this.sendTx(
|
||||
[
|
||||
await this.sys.createRentFreeAccountInstruction({
|
||||
|
@ -165,10 +166,13 @@ export default class FluxAggregator extends BaseProgram {
|
|||
space: encoding.Oracle.size,
|
||||
programID: this.programID,
|
||||
}),
|
||||
this.addOracleInstruction({
|
||||
...params,
|
||||
oracle: oracle.publicKey,
|
||||
}),
|
||||
this.instruction(input, [
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
params.aggregator,
|
||||
params.aggregatorOwner, // signed
|
||||
oracle.publicKey,
|
||||
params.oracleOwner,
|
||||
]),
|
||||
],
|
||||
[this.account, oracle, params.aggregatorOwner]
|
||||
)
|
||||
|
@ -181,32 +185,6 @@ export default class FluxAggregator extends BaseProgram {
|
|||
return decodeOracleInfo(info)
|
||||
}
|
||||
|
||||
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)],
|
||||
|
@ -239,29 +217,18 @@ export default class FluxAggregator extends BaseProgram {
|
|||
}
|
||||
|
||||
public async submit(params: SubmitParams): Promise<void> {
|
||||
const input = encoding.Submit.serialize(params)
|
||||
|
||||
let auths = [
|
||||
SYSVAR_CLOCK_PUBKEY,
|
||||
...Object.values(params.accounts),
|
||||
]
|
||||
|
||||
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]
|
||||
[
|
||||
this.instruction(input, auths),
|
||||
],
|
||||
[this.account, params.accounts.oracle_owner]
|
||||
)
|
||||
}
|
||||
|
||||
|
|
51
src/cli.ts
51
src/cli.ts
|
@ -18,12 +18,7 @@ import dotenv from "dotenv"
|
|||
|
||||
import FluxAggregator from "./FluxAggregator"
|
||||
|
||||
import {
|
||||
decodeAggregatorInfo,
|
||||
walletFromEnv,
|
||||
openDeployer,
|
||||
sleep,
|
||||
} from "./utils"
|
||||
import { decodeAggregatorInfo, sleep } from "./utils"
|
||||
|
||||
import * as feed from "./feed"
|
||||
import { AggregatorConfig } from "./schema"
|
||||
|
@ -39,43 +34,7 @@ const FLUX_AGGREGATOR_SO = path.resolve(
|
|||
const network = (process.env.NETWORK || "local") as NetworkName
|
||||
const conn = solana.connect(network)
|
||||
|
||||
class AppContext {
|
||||
static readonly AGGREGATOR_PROGRAM = "aggregatorProgram"
|
||||
|
||||
static async forAdmin() {
|
||||
const deployer = await openDeployer()
|
||||
const admin = await walletFromEnv("ADMIN_MNEMONIC", conn)
|
||||
|
||||
return new AppContext(deployer, admin)
|
||||
}
|
||||
|
||||
static async forOracle() {
|
||||
const deployer = await openDeployer()
|
||||
const wallet = await walletFromEnv("ORACLE_MNEMONIC", conn)
|
||||
|
||||
return new AppContext(deployer, wallet)
|
||||
}
|
||||
|
||||
constructor(public deployer: Deployer, public wallet: Wallet) {}
|
||||
|
||||
get aggregatorProgramID() {
|
||||
return this.aggregatorProgramAccount.publicKey
|
||||
}
|
||||
|
||||
get aggregator() {
|
||||
return new FluxAggregator(this.wallet, this.aggregatorProgramID)
|
||||
}
|
||||
|
||||
get aggregatorProgramAccount() {
|
||||
const program = this.deployer.account(AppContext.AGGREGATOR_PROGRAM)
|
||||
|
||||
if (program == null) {
|
||||
throw new Error(`flux aggregator program is not yet deployed`)
|
||||
}
|
||||
|
||||
return program
|
||||
}
|
||||
}
|
||||
import { AppContext } from "./context"
|
||||
|
||||
function color(s, c = "black", b = false): string {
|
||||
// 30m Black, 31m Red, 32m Green, 33m Yellow, 34m Blue, 35m Magenta, 36m Cyanic, 37m White
|
||||
|
@ -226,7 +185,7 @@ cli
|
|||
cli
|
||||
.command("add-oracle")
|
||||
.description("add an oracle to aggregator")
|
||||
.option("--feedAddress <string>", "feed address")
|
||||
.option("--aggregatorAddress <string>", "aggregator address")
|
||||
.option("--oracleName <string>", "oracle name")
|
||||
.option("--oracleOwner <string>", "oracle owner address")
|
||||
.action(async (opts) => {
|
||||
|
@ -236,8 +195,8 @@ cli
|
|||
|
||||
log("add oracle...")
|
||||
const oracle = await aggregator.addOracle({
|
||||
owner: new PublicKey(oracleOwner),
|
||||
description: oracleName.substr(0, 32).padEnd(32),
|
||||
oracleOwner: new PublicKey(oracleOwner),
|
||||
description: oracleName,
|
||||
aggregator: new PublicKey(feedAddress),
|
||||
aggregatorOwner: wallet.account,
|
||||
})
|
||||
|
|
|
@ -2,7 +2,7 @@ import { PublicKey, Account } from "solray"
|
|||
import BN from "bn.js"
|
||||
import { deserialize, serialize } from "borsh"
|
||||
|
||||
const MAX_ORACLES = 12
|
||||
const MAX_ORACLES = 13
|
||||
|
||||
const boolMapper = {
|
||||
encode: boolToInt,
|
||||
|
@ -28,7 +28,7 @@ const pubkeyMapper = {
|
|||
// support strings that can be contained in at most 32 bytes
|
||||
const str32Mapper = {
|
||||
encode: (str: String) => {
|
||||
str = str.substr(0, 32).toUpperCase().padEnd(32)
|
||||
str = str.substr(0, 32).padEnd(32)
|
||||
return Buffer.from(str, "utf8").slice(0, 32) // truncate at 32 bytes
|
||||
},
|
||||
|
||||
|
@ -50,7 +50,11 @@ abstract class Serialization {
|
|||
}
|
||||
|
||||
public serialize(): Buffer {
|
||||
return Buffer.from(serialize(schema, this))
|
||||
let buf = Buffer.from(serialize(schema, this))
|
||||
if (buf.length == 0) {
|
||||
throw new Error("serialized buffer is 0. something wrong with schema")
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
constructor(data) {
|
||||
|
@ -66,7 +70,7 @@ class Submission {
|
|||
public static schema = {
|
||||
kind: "struct",
|
||||
fields: [
|
||||
["time", "u64"],
|
||||
["updated_at", "u64"],
|
||||
["value", "u64"],
|
||||
["oracle", [32], pubkeyMapper],
|
||||
],
|
||||
|
@ -104,7 +108,7 @@ export class Submissions extends Serialization {
|
|||
kind: "struct",
|
||||
fields: [
|
||||
["isInitialized", "u8", boolMapper],
|
||||
["submissions", [MAX_ORACLES, Submission]],
|
||||
["submissions", [Submission, MAX_ORACLES]],
|
||||
],
|
||||
}
|
||||
}
|
||||
|
@ -134,7 +138,8 @@ export class Aggregator extends Serialization {
|
|||
public static size = 189
|
||||
|
||||
public config!: AggregatorConfig
|
||||
// public submissions!: Submission[]
|
||||
public roundSubmissions!: PublicKey
|
||||
public answerSubmissions!: PublicKey
|
||||
|
||||
public static schema = {
|
||||
kind: "struct",
|
||||
|
@ -143,9 +148,9 @@ export class Aggregator extends Serialization {
|
|||
["isInitialized", "u8", boolMapper],
|
||||
["owner", [32], pubkeyMapper],
|
||||
["round", Round],
|
||||
["round_submissions", [32], pubkeyMapper],
|
||||
["roundSubmissions", [32], pubkeyMapper],
|
||||
["answer", Answer],
|
||||
["answer_submissions", [32], pubkeyMapper],
|
||||
["answerSubmissions", [32], pubkeyMapper],
|
||||
],
|
||||
}
|
||||
}
|
||||
|
@ -170,13 +175,51 @@ export class Initialize extends InstructionSerialization {
|
|||
}
|
||||
}
|
||||
|
||||
export class Configure extends InstructionSerialization {
|
||||
public static schema = {
|
||||
kind: "struct",
|
||||
fields: [["config", AggregatorConfig]],
|
||||
}
|
||||
}
|
||||
|
||||
export class AddOracle extends InstructionSerialization {
|
||||
public static schema = {
|
||||
kind: "struct",
|
||||
fields: [["description", [32], str32Mapper]],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class RemoveOracle extends InstructionSerialization {
|
||||
public static schema = {
|
||||
kind: "struct",
|
||||
fields: [],
|
||||
}
|
||||
}
|
||||
|
||||
export class Submit extends InstructionSerialization {
|
||||
public static schema = {
|
||||
kind: "struct",
|
||||
fields: [
|
||||
["round_id", "u64"],
|
||||
["value", "u64"],
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
export class Instruction extends Serialization {
|
||||
public enum!: string
|
||||
|
||||
public static schema = {
|
||||
kind: "enum",
|
||||
field: "enum",
|
||||
values: [[Initialize.name, Initialize]],
|
||||
values: [
|
||||
[Initialize.name, Initialize],
|
||||
[Configure.name, Configure],
|
||||
[AddOracle.name, AddOracle],
|
||||
[RemoveOracle.name, RemoveOracle],
|
||||
[Submit.name, Submit],
|
||||
],
|
||||
}
|
||||
|
||||
public constructor(prop: { [key: string]: any }) {
|
||||
|
@ -215,8 +258,20 @@ function boolToInt(t: boolean) {
|
|||
}
|
||||
}
|
||||
|
||||
export class Oracle {
|
||||
export class Oracle extends Serialization {
|
||||
public static size = 113
|
||||
|
||||
public static schema = {
|
||||
kind: "struct",
|
||||
fields: [
|
||||
["description", [32], str32Mapper],
|
||||
["isInitialized", "u8", boolMapper],
|
||||
["withdrawable", "u64"],
|
||||
["allow_start_round", "u64"],
|
||||
["aggregator", [32], pubkeyMapper],
|
||||
["owner", [32], pubkeyMapper],
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
// if there is optional or variable length items, what is: borsh_utils::get_packed_len::<Submission>()?
|
||||
|
@ -225,11 +280,16 @@ export class Oracle {
|
|||
|
||||
export const schema = new Map([
|
||||
[Aggregator, Aggregator.schema],
|
||||
[Oracle, Oracle.schema],
|
||||
[Round, Round.schema],
|
||||
[Answer, Answer.schema],
|
||||
[AggregatorConfig, AggregatorConfig.schema],
|
||||
[Submissions, Submissions.schema],
|
||||
[Submission, Submission.schema],
|
||||
[Initialize, Initialize.schema],
|
||||
|
||||
[Instruction, Instruction.schema],
|
||||
[Initialize, Initialize.schema],
|
||||
[AddOracle, AddOracle.schema],
|
||||
[Submit, Submit.schema],
|
||||
|
||||
] as any) as any
|
||||
|
|
Loading…
Reference in New Issue