diff --git a/README.md b/README.md index 1cc59cf..4416179 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/program/src/entrypoint.rs b/program/src/entrypoint.rs index 2a95fe2..4a9b973 100644 --- a/program/src/entrypoint.rs +++ b/program/src/entrypoint.rs @@ -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::(); - // 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::(); + // // msg!("{:?}", error); + // return Err(error); + // } Ok(()) } diff --git a/program/src/instruction.rs b/program/src/instruction.rs index e540ca1..b7d0526 100644 --- a/program/src/instruction.rs +++ b/program/src/instruction.rs @@ -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)?; diff --git a/program/src/processor.rs b/program/src/processor.rs index f6d8b85..74413c6 100644 --- a/program/src/processor.rs +++ b/program/src/processor.rs @@ -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(); diff --git a/src/FluxAggregator.ts b/src/FluxAggregator.ts index 74f1e21..30ef41f 100644 --- a/src/FluxAggregator.ts +++ b/src/FluxAggregator.ts @@ -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 { 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 { await this.sendTx( [this.removeOracleInstruction(params)], @@ -239,29 +217,18 @@ export default class FluxAggregator extends BaseProgram { } public async submit(params: SubmitParams): Promise { + 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] ) } diff --git a/src/cli.ts b/src/cli.ts index 082a191..b730b1c 100644 --- a/src/cli.ts +++ b/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 ", "feed address") + .option("--aggregatorAddress ", "aggregator address") .option("--oracleName ", "oracle name") .option("--oracleOwner ", "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, }) diff --git a/src/schema.ts b/src/schema.ts index 5057ff3..6f5558c 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -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::()? @@ -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