From cb44e15f33815e22d88872975eb1f442f1a3d92c Mon Sep 17 00:00:00 2001 From: swimricky <86628128+swimricky@users.noreply.github.com> Date: Tue, 4 Apr 2023 06:34:14 -0700 Subject: [PATCH] [accumulator-updater 2/x] Manual Serialization & Zero-copy for Mock-cpi-program (#729) * feat(accumulator_updater): initial skeleton for accumulator_udpater program Initial layout for accumulator updater program. Includes mock-cpi-caller which is meant to represent pyth oracle(or any future allowed program) that will call the accumulator updater program. All implementation details are open for discussion/subject to change * test(accumulator_updater): add additional checks in tests and minor clean up * chore(accumulator_updater): misc clean-up * refactor(accumulator_updater): added comments & to-dos and minor refactoring to address PR comments * feat(accumulator_updater): nmanual serialization for mock-cpi-caller schemas & use zero-copy * chore(accumulator-updater): misc clean-up * refactor(accumulator-updater): rename parameter in accumulator-updater::initalize ix for consistency * style(accumulator-updater): switch PriceAccountType enum variants to camelcase * refactor(accumulator-updater): address PR comments rename schema to message & associated price messages, remove unncessary comments, changed addAllowedProgram to setAllowedPrograms * refactor(accumulator-updater): address more PR comments consolidate SetAllowedPrograms and UpdateWhitelistAuthority into one context * style(accumulator-updater): minor style fixes to address PR comments --- accumulator_updater/Cargo.lock | 1 + .../programs/accumulator_updater/src/lib.rs | 88 +++++++--- .../programs/mock-cpi-caller/Cargo.toml | 2 + .../programs/mock-cpi-caller/src/lib.rs | 108 +++++-------- .../programs/mock-cpi-caller/src/message.rs | 29 ++++ .../mock-cpi-caller/src/message/price.rs | 78 +++++++++ .../tests/accumulator_updater.ts | 151 ++++++++++++++---- 7 files changed, 328 insertions(+), 129 deletions(-) create mode 100644 accumulator_updater/programs/mock-cpi-caller/src/message.rs create mode 100644 accumulator_updater/programs/mock-cpi-caller/src/message/price.rs diff --git a/accumulator_updater/Cargo.lock b/accumulator_updater/Cargo.lock index e80f32c3..463f17b4 100644 --- a/accumulator_updater/Cargo.lock +++ b/accumulator_updater/Cargo.lock @@ -899,6 +899,7 @@ version = "0.1.0" dependencies = [ "accumulator_updater", "anchor-lang", + "bytemuck", ] [[package]] diff --git a/accumulator_updater/programs/accumulator_updater/src/lib.rs b/accumulator_updater/programs/accumulator_updater/src/lib.rs index 2cec2b2b..4169ebb7 100644 --- a/accumulator_updater/programs/accumulator_updater/src/lib.rs +++ b/accumulator_updater/programs/accumulator_updater/src/lib.rs @@ -18,34 +18,43 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); pub mod accumulator_updater { use super::*; - pub fn initialize(ctx: Context) -> Result<()> { + pub fn initialize(ctx: Context, authority: Pubkey) -> Result<()> { + require_keys_neq!(authority, Pubkey::default()); let whitelist = &mut ctx.accounts.whitelist; whitelist.bump = *ctx.bumps.get("whitelist").unwrap(); + whitelist.authority = authority; Ok(()) } - //TODO: add authorization mechanism for this - pub fn add_allowed_program( - ctx: Context, - allowed_program: Pubkey, + pub fn set_allowed_programs( + ctx: Context, + allowed_programs: Vec, ) -> Result<()> { let whitelist = &mut ctx.accounts.whitelist; - require_keys_neq!(allowed_program, Pubkey::default()); - require!( - !whitelist.allowed_programs.contains(&allowed_program), - AccumulatorUpdaterError::DuplicateAllowedProgram - ); - whitelist.allowed_programs.push(allowed_program); + whitelist.validate_programs(&allowed_programs)?; + whitelist.allowed_programs = allowed_programs; + Ok(()) + } + + pub fn update_whitelist_authority( + ctx: Context, + new_authority: Pubkey, + ) -> Result<()> { + let whitelist = &mut ctx.accounts.whitelist; + whitelist.validate_new_authority(new_authority)?; + whitelist.authority = new_authority; Ok(()) } /// Add new account(s) to be included in the accumulator /// - /// * `base_account` - Pubkey of the original account the AccumulatorInput(s) are derived from - /// * `data` - Vec of AccumulatorInput account data - /// * `account_type` - Marker to indicate base_account account_type - /// * `account_schemas` - Vec of markers to indicate schemas for AccumulatorInputs. In same respective - /// order as data + /// * `base_account` - Pubkey of the original account the + /// AccumulatorInput(s) are derived from + /// * `data` - Vec of AccumulatorInput account data + /// * `account_type` - Marker to indicate base_account account_type + /// * `account_schemas` - Vec of markers to indicate schemas for + /// AccumulatorInputs. In same respective + /// order as data pub fn create_inputs<'info>( ctx: Context<'_, '_, '_, 'info, CreateInputs<'info>>, base_account: Pubkey, @@ -105,10 +114,31 @@ pub mod accumulator_updater { #[derive(InitSpace)] pub struct Whitelist { pub bump: u8, + pub authority: Pubkey, #[max_len(32)] pub allowed_programs: Vec, } +impl Whitelist { + pub fn validate_programs(&self, allowed_programs: &[Pubkey]) -> Result<()> { + require!( + !self.allowed_programs.contains(&Pubkey::default()), + AccumulatorUpdaterError::InvalidAllowedProgram + ); + require_gte!( + 32, + allowed_programs.len(), + AccumulatorUpdaterError::MaximumAllowedProgramsExceeded + ); + Ok(()) + } + + pub fn validate_new_authority(&self, new_authority: Pubkey) -> Result<()> { + require_keys_neq!(new_authority, Pubkey::default()); + Ok(()) + } +} + #[derive(Accounts)] pub struct WhitelistVerifier<'info> { @@ -141,7 +171,8 @@ impl<'info> WhitelistVerifier<'info> { #[derive(Accounts)] pub struct Initialize<'info> { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer<'info>, + #[account( init, payer = payer, @@ -153,17 +184,20 @@ pub struct Initialize<'info> { pub system_program: Program<'info, System>, } + #[derive(Accounts)] -pub struct AddAllowedProgram<'info> { +pub struct UpdateWhitelist<'info> { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer<'info>, + + pub authority: Signer<'info>, #[account( - mut, - seeds = [b"accumulator".as_ref(), b"whitelist".as_ref()], - bump = whitelist.bump, + mut, + seeds = [b"accumulator".as_ref(), b"whitelist".as_ref()], + bump = whitelist.bump, + has_one = authority )] - pub whitelist: Account<'info, Whitelist>, - pub system_program: Program<'info, System>, + pub whitelist: Account<'info, Whitelist>, } @@ -294,4 +328,10 @@ pub enum AccumulatorUpdaterError { ConversionError, #[msg("Serialization Error")] SerializeError, + #[msg("Whitelist admin required on initialization")] + WhitelistAdminRequired, + #[msg("Invalid allowed program")] + InvalidAllowedProgram, + #[msg("Maximum number of allowed programs exceeded")] + MaximumAllowedProgramsExceeded, } diff --git a/accumulator_updater/programs/mock-cpi-caller/Cargo.toml b/accumulator_updater/programs/mock-cpi-caller/Cargo.toml index efc9127a..3e6d154b 100644 --- a/accumulator_updater/programs/mock-cpi-caller/Cargo.toml +++ b/accumulator_updater/programs/mock-cpi-caller/Cargo.toml @@ -18,3 +18,5 @@ default = [] [dependencies] anchor-lang = "0.27.0" accumulator_updater = { path = "../accumulator_updater", features = ["cpi"] } +# needed for the new #[account(zero_copy)] in anchor 0.27.0 +bytemuck = { version = "1.4.0", features = ["derive", "min_const_generics"]} diff --git a/accumulator_updater/programs/mock-cpi-caller/src/lib.rs b/accumulator_updater/programs/mock-cpi-caller/src/lib.rs index 05fac852..89738c3a 100644 --- a/accumulator_updater/programs/mock-cpi-caller/src/lib.rs +++ b/accumulator_updater/programs/mock-cpi-caller/src/lib.rs @@ -10,8 +10,14 @@ use { sysvar, }, }, + message::{ + get_schemas, + price::*, + AccumulatorSerializer, + }, }; +pub mod message; declare_id!("Dg5PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); #[program] @@ -22,26 +28,27 @@ pub mod mock_cpi_caller { ctx: Context<'_, '_, '_, 'info, AddPrice<'info>>, params: AddPriceParams, ) -> Result<()> { - let pyth_price_acct = &mut ctx.accounts.pyth_price_account; - pyth_price_acct.init(params)?; + let mut account_data: Vec> = vec![]; + let schemas = get_schemas(PythAccountType::Price); - let mut price_account_data_vec = vec![]; - AccountSerialize::try_serialize( - &pyth_price_acct.clone().into_inner(), - &mut price_account_data_vec, - )?; + { + let pyth_price_acct = &mut ctx.accounts.pyth_price_account.load_init()?; + + pyth_price_acct.init(params)?; + + let price_full_data = + FullPriceMessage::from(&**pyth_price_acct).accumulator_serialize()?; + + account_data.push(price_full_data); - let price_only_data = PriceOnly::from(&pyth_price_acct.clone().into_inner()) - .try_to_vec() - .unwrap(); + let price_compact_data = + CompactPriceMessage::from(&**pyth_price_acct).accumulator_serialize()?; + account_data.push(price_compact_data); + } - let account_data: Vec> = vec![price_account_data_vec, price_only_data]; - let account_schemas = [PythSchemas::Full, PythSchemas::Compact] - .into_iter() - .map(|s| s.to_u8()) - .collect::>(); + let account_schemas = schemas.into_iter().map(|s| s.to_u8()).collect::>(); // 44444 compute units // AddPrice::invoke_cpi_anchor(ctx, account_data, PythAccountType::Price, account_schemas) @@ -81,7 +88,6 @@ impl<'info> AddPrice<'info> { account_schemas: Vec, ) -> Result<()> { accumulator_updater::cpi::create_inputs( - // cpi_ctx, ctx.accounts.create_inputs_ctx(ctx.remaining_accounts), ctx.accounts.pyth_price_account.key(), account_data, @@ -111,7 +117,7 @@ impl<'info> AddPrice<'info> { .map(|a| AccountMeta::new(a.key(), false)) .collect::>(), ); - let add_accumulator_input_ix = anchor_lang::solana_program::instruction::Instruction { + let create_inputs_ix = anchor_lang::solana_program::instruction::Instruction { program_id: ctx.accounts.accumulator_program.key(), accounts, data: ( @@ -127,7 +133,7 @@ impl<'info> AddPrice<'info> { }; let account_infos = &mut ctx.accounts.to_account_infos(); account_infos.extend_from_slice(ctx.remaining_accounts); - anchor_lang::solana_program::program::invoke(&add_accumulator_input_ix, account_infos)?; + anchor_lang::solana_program::program::invoke(&create_inputs_ix, account_infos)?; Ok(()) } } @@ -153,6 +159,13 @@ pub struct AddPriceParams { pub ema_expo: u64, } +trait PythAccount { + const ACCOUNT_TYPE: PythAccountType; + fn account_type() -> PythAccountType { + Self::ACCOUNT_TYPE + } +} + #[derive(Copy, Clone)] #[repr(u32)] pub enum PythAccountType { @@ -168,20 +181,6 @@ impl PythAccountType { } } -#[derive(Copy, Clone)] -#[repr(u8)] -pub enum PythSchemas { - Full = 0, - Compact = 1, - Minimal = 2, -} - -impl PythSchemas { - fn to_u8(&self) -> u8 { - *self as u8 - } -} - #[derive(Accounts)] #[instruction(params: AddPriceParams)] pub struct AddPrice<'info> { @@ -192,7 +191,7 @@ pub struct AddPrice<'info> { bump, space = 8 + PriceAccount::INIT_SPACE )] - pub pyth_price_account: Account<'info, PriceAccount>, + pub pyth_price_account: AccountLoader<'info, PriceAccount>, #[account(mut)] pub payer: Signer<'info>, /// also needed for accumulator_updater @@ -208,8 +207,7 @@ pub struct AddPrice<'info> { } -//Note: this will use anchor's default borsh serialization schema with the header -#[account] +#[account(zero_copy)] #[derive(InitSpace)] pub struct PriceAccount { pub id: u64, @@ -217,6 +215,7 @@ pub struct PriceAccount { pub price_expo: u64, pub ema: u64, pub ema_expo: u64, + pub comp_: [Pubkey; 32], } impl PriceAccount { @@ -230,45 +229,10 @@ impl PriceAccount { } } -// #[derive(Default, Debug, borsh::BorshSerialize)] -#[derive(AnchorSerialize, AnchorDeserialize, Default, Debug, Clone)] -pub struct PriceOnly { - pub price_expo: u64, - pub price: u64, - pub id: u64, +impl PythAccount for PriceAccount { + const ACCOUNT_TYPE: PythAccountType = PythAccountType::Price; } -impl PriceOnly { - fn serialize(&self) -> Vec { - self.try_to_vec().unwrap() - } - - fn serialize_from_price_account(other: PriceAccount) -> Vec { - PriceOnly::from(&other).try_to_vec().unwrap() - } -} - - -impl From<&PriceAccount> for PriceOnly { - fn from(other: &PriceAccount) -> Self { - Self { - id: other.id, - price: other.price, - price_expo: other.price_expo, - } - } -} - - -impl From for PriceOnly { - fn from(other: PriceAccount) -> Self { - Self { - id: other.id, - price: other.price, - price_expo: other.price_expo, - } - } -} #[cfg(test)] mod test { diff --git a/accumulator_updater/programs/mock-cpi-caller/src/message.rs b/accumulator_updater/programs/mock-cpi-caller/src/message.rs new file mode 100644 index 00000000..a3c982a5 --- /dev/null +++ b/accumulator_updater/programs/mock-cpi-caller/src/message.rs @@ -0,0 +1,29 @@ +use crate::PythAccountType; + +pub mod price; + +#[derive(Copy, Clone)] +#[repr(u8)] +pub enum MessageSchema { + Full = 0, + Compact = 1, + Minimal = 2, +} + +impl MessageSchema { + pub fn to_u8(&self) -> u8 { + *self as u8 + } +} + + +pub fn get_schemas(account_type: PythAccountType) -> Vec { + match account_type { + PythAccountType::Price => vec![MessageSchema::Full, MessageSchema::Compact], + _ => vec![MessageSchema::Full], + } +} + +pub trait AccumulatorSerializer { + fn accumulator_serialize(&self) -> anchor_lang::Result>; +} diff --git a/accumulator_updater/programs/mock-cpi-caller/src/message/price.rs b/accumulator_updater/programs/mock-cpi-caller/src/message/price.rs new file mode 100644 index 00000000..7a0ac025 --- /dev/null +++ b/accumulator_updater/programs/mock-cpi-caller/src/message/price.rs @@ -0,0 +1,78 @@ +use { + crate::{ + message::AccumulatorSerializer, + PriceAccount, + }, + anchor_lang::prelude::*, + bytemuck::{ + Pod, + Zeroable, + }, + std::io::Write, +}; + +// TODO: should these schemas be "external" (protobuf?) + +#[repr(C)] +#[derive(Debug, Copy, Clone, Pod, Zeroable)] +pub struct CompactPriceMessage { + pub price_expo: u64, + pub price: u64, + pub id: u64, +} + + +impl AccumulatorSerializer for CompactPriceMessage { + fn accumulator_serialize(&self) -> Result> { + let mut bytes = vec![]; + bytes.write_all(&self.id.to_be_bytes())?; + bytes.write_all(&self.price.to_be_bytes())?; + bytes.write_all(&self.price_expo.to_be_bytes())?; + Ok(bytes) + } +} + +impl From<&PriceAccount> for CompactPriceMessage { + fn from(other: &PriceAccount) -> Self { + Self { + id: other.id, + price: other.price, + price_expo: other.price_expo, + } + } +} + + +#[repr(C)] +#[derive(Debug, Copy, Clone, Pod, Zeroable)] +pub struct FullPriceMessage { + pub id: u64, + pub price: u64, + pub price_expo: u64, + pub ema: u64, + pub ema_expo: u64, +} + +impl From<&PriceAccount> for FullPriceMessage { + fn from(other: &PriceAccount) -> Self { + Self { + id: other.id, + price: other.price, + price_expo: other.price_expo, + ema: other.ema, + ema_expo: other.ema_expo, + } + } +} + +impl AccumulatorSerializer for FullPriceMessage { + fn accumulator_serialize(&self) -> Result> { + let mut bytes = vec![]; + bytes.write_all(&self.id.to_be_bytes())?; + bytes.write_all(&self.price.to_be_bytes())?; + bytes.write_all(&self.price_expo.to_be_bytes())?; + bytes.write_all(&self.ema.to_be_bytes())?; + bytes.write_all(&self.ema_expo.to_be_bytes())?; + Ok(bytes) + } +} diff --git a/accumulator_updater/tests/accumulator_updater.ts b/accumulator_updater/tests/accumulator_updater.ts index 8fbefb43..1e6ff1ab 100644 --- a/accumulator_updater/tests/accumulator_updater.ts +++ b/accumulator_updater/tests/accumulator_updater.ts @@ -1,25 +1,27 @@ import * as anchor from "@coral-xyz/anchor"; -import { IdlTypes, Program, IdlAccounts } from "@coral-xyz/anchor"; +import { IdlTypes, Program, BorshAccountsCoder } from "@coral-xyz/anchor"; import { AccumulatorUpdater } from "../target/types/accumulator_updater"; import { MockCpiCaller } from "../target/types/mock_cpi_caller"; import lumina from "@lumina-dev/test"; import { assert } from "chai"; import { ComputeBudgetProgram } from "@solana/web3.js"; +import bs58 from "bs58"; -// Enables tool that runs in localbrowser for easier debugging of txns -// in this test - https://lumina.fyi/debug +// Enables tool that runs in local browser for easier debugging of +// transactions in this test - https://lumina.fyi/debug lumina(); const accumulatorUpdaterProgram = anchor.workspace .AccumulatorUpdater as Program; const mockCpiProg = anchor.workspace.MockCpiCaller as Program; +let whitelistAuthority = anchor.web3.Keypair.generate(); describe("accumulator_updater", () => { // Configure the client to use the local cluster. let provider = anchor.AnchorProvider.env(); anchor.setProvider(provider); - const [whitelistPda, whitelistBump] = + const [whitelistPubkey, whitelistBump] = anchor.web3.PublicKey.findProgramAddressSync( [Buffer.from("accumulator"), Buffer.from("whitelist")], accumulatorUpdaterProgram.programId @@ -28,33 +30,57 @@ describe("accumulator_updater", () => { it("Is initialized!", async () => { // Add your test here. const tx = await accumulatorUpdaterProgram.methods - .initialize() + .initialize(whitelistAuthority.publicKey) .accounts({}) .rpc(); console.log("Your transaction signature", tx); const whitelist = await accumulatorUpdaterProgram.account.whitelist.fetch( - whitelistPda + whitelistPubkey ); assert.strictEqual(whitelist.bump, whitelistBump); + assert.isTrue(whitelist.authority.equals(whitelistAuthority.publicKey)); console.info(`whitelist: ${JSON.stringify(whitelist)}`); }); - it("Adds a program to the whitelist", async () => { - const addToWhitelistTx = await accumulatorUpdaterProgram.methods - .addAllowedProgram(mockCpiProg.programId) - .accounts({}) + it("Sets allowed programs to the whitelist", async () => { + const allowedPrograms = [mockCpiProg.programId]; + await accumulatorUpdaterProgram.methods + .setAllowedPrograms(allowedPrograms) + .accounts({ + authority: whitelistAuthority.publicKey, + }) + .signers([whitelistAuthority]) .rpc(); const whitelist = await accumulatorUpdaterProgram.account.whitelist.fetch( - whitelistPda + whitelistPubkey ); console.info(`whitelist after add: ${JSON.stringify(whitelist)}`); - - assert.isTrue( - whitelist.allowedPrograms - .map((pk) => pk.toString()) - .includes(mockCpiProg.programId.toString()) + const whitelistAllowedPrograms = whitelist.allowedPrograms.map((pk) => + pk.toString() ); + assert.deepEqual( + whitelistAllowedPrograms, + allowedPrograms.map((p) => p.toString()) + ); + }); + + it("Updates the whitelist authority", async () => { + const newWhitelistAuthority = anchor.web3.Keypair.generate(); + await accumulatorUpdaterProgram.methods + .updateWhitelistAuthority(newWhitelistAuthority.publicKey) + .accounts({ + authority: whitelistAuthority.publicKey, + }) + .signers([whitelistAuthority]) + .rpc(); + + const whitelist = await accumulatorUpdaterProgram.account.whitelist.fetch( + whitelistPubkey + ); + assert.isTrue(whitelist.authority.equals(newWhitelistAuthority.publicKey)); + + whitelistAuthority = newWhitelistAuthority; }); it("Mock CPI program - AddPrice", async () => { @@ -71,7 +97,7 @@ describe("accumulator_updater", () => { .accounts({ systemProgram: anchor.web3.SystemProgram.programId, ixsSysvar: anchor.web3.SYSVAR_INSTRUCTIONS_PUBKEY, - accumulatorWhitelist: whitelistPda, + accumulatorWhitelist: whitelistPubkey, accumulatorProgram: accumulatorUpdaterProgram.programId, }) .pubkeys(); @@ -136,16 +162,18 @@ describe("accumulator_updater", () => { }); console.log(`addPriceTx: ${addPriceTx}`); - const accumulatorInputkeys = accumulatorPdas.map((a) => a.pubkey); + const pythPriceAccount = await provider.connection.getAccountInfo( + mockCpiCallerAddPriceTxPubkeys.pythPriceAccount + ); + console.log(`pythPriceAccount: ${pythPriceAccount.data.toString("hex")}`); + const accumulatorInputKeys = accumulatorPdas.map((a) => a.pubkey); const accumulatorInputs = await accumulatorUpdaterProgram.account.accumulatorInput.fetchMultiple( - accumulatorInputkeys + accumulatorInputKeys ); const accumulatorPriceAccounts = accumulatorInputs.map((ai) => { - const { header, data } = ai; - return parseAccumulatorInput(ai); }); console.log( @@ -160,36 +188,93 @@ describe("accumulator_updater", () => { assert.isTrue(pa.price.eq(addPriceParams.price)); assert.isTrue(pa.priceExpo.eq(addPriceParams.priceExpo)); }); + + let discriminator = + BorshAccountsCoder.accountDiscriminator("AccumulatorInput"); + let accumulatorInputDiscriminator = bs58.encode(discriminator); + + // fetch using `getProgramAccounts` and memcmp filter + const accumulatorAccounts = await provider.connection.getProgramAccounts( + accumulatorUpdaterProgram.programId, + { + filters: [ + { + memcmp: { + offset: 0, + bytes: accumulatorInputDiscriminator, + }, + }, + ], + } + ); + const accumulatorInputKeyStrings = accumulatorInputKeys.map((k) => + k.toString() + ); + accumulatorAccounts.forEach((a) => { + assert.isTrue(accumulatorInputKeyStrings.includes(a.pubkey.toString())); + }); }); }); type AccumulatorInputHeader = IdlTypes["AccumulatorHeader"]; -type AccumulatorInputPriceAccountTypes = - | IdlAccounts["priceAccount"] // case-sensitive - | IdlTypes["PriceOnly"]; // Parses AccumulatorInput.data into a PriceAccount or PriceOnly object based on the // accountType and accountSchema. -// -// AccumulatorInput.data for AccumulatorInput will -// have mockCpiCaller::PriceAccount.discriminator() -// AccumulatorInput will not since its not an account function parseAccumulatorInput({ header, data, }: { header: AccumulatorInputHeader; data: Buffer; -}): AccumulatorInputPriceAccountTypes { +}): AccumulatorPriceMessage { console.log(`header: ${JSON.stringify(header)}`); assert.strictEqual(header.accountType, 3); if (header.accountSchema === 0) { console.log(`[full]data: ${data.toString("hex")}`); - // case-sensitive. Note that "P" is capitalized here and not in - // the AccumulatorInputPriceAccountTypes type alias. - return mockCpiProg.coder.accounts.decode("PriceAccount", data); + return parseFullPriceMessage(data); } else { console.log(`[compact]data: ${data.toString("hex")}`); - return mockCpiProg.coder.types.decode("PriceOnly", data); + return parseCompactPriceMessage(data); } } + +//TODO: follow wormhole sdk parsing structure? +// - https://github.com/wormhole-foundation/wormhole/blob/main/sdk/js/src/vaa/generic.ts +type AccumulatorPriceMessage = FullPriceMessage | CompactPriceMessage; + +type FullPriceMessage = { + id: anchor.BN; + price: anchor.BN; + priceExpo: anchor.BN; + ema: anchor.BN; + emaExpo: anchor.BN; +}; + +function parseFullPriceMessage(data: Buffer): FullPriceMessage { + return { + id: new anchor.BN(data.subarray(0, 8), "be"), + price: new anchor.BN(data.subarray(8, 16), "be"), + priceExpo: new anchor.BN(data.subarray(16, 24), "be"), + ema: new anchor.BN(data.subarray(24, 32), "be"), + emaExpo: new anchor.BN(data.subarray(32, 40), "be"), + }; +} + +type CompactPriceMessage = { + id: anchor.BN; + price: anchor.BN; + priceExpo: anchor.BN; +}; + +function parseCompactPriceMessage(data: Buffer): CompactPriceMessage { + return { + id: new anchor.BN(data.subarray(0, 8), "be"), + price: new anchor.BN(data.subarray(8, 16), "be"), + priceExpo: new anchor.BN(data.subarray(16, 24), "be"), + }; +} + +interface AccumulatorInput { + header: AccumulatorInputHeader; + account: T; +}