فهرست منبع

[message-buffer 10/X] - Message Buffer Admin IXs & variable length (#779)

* ok

* ok

* it runs

* add this stuff

* working on tests

* feat(message-buffer): finish create_buffer ix, update put_all

* feat: rename bufferheader to messageBuffer, add delete_buffer impl

* feat(message-buffer): remove unused code, add additional checks, update unit tests

* style(message-buffer): fix pre-commit, run fmt & clippy

* fix(message-buffer): add verification checks, fix ts test

* refactor(message-buffer): rename update_whitelist_authority to admin

* fix(message-buffer): address PR comments

---------

Co-authored-by: Jayant Krishnamurthy <jayantkrishnamurthy@gmail.com>
swimricky 2 سال پیش
والد
کامیت
794bd84c6f

+ 2 - 2
.pre-commit-config.yaml

@@ -71,13 +71,13 @@ repos:
         language: "rust"
         entry: cargo +nightly fmt --manifest-path ./message_buffer/Cargo.toml --all -- --config-path rustfmt.toml
         pass_filenames: false
-        files: accumulator_updater
+        files: message_buffer
       - id: cargo-clippy-message-buffer
         name: Cargo clippy for message buffer contract
         language: "rust"
         entry: cargo +nightly clippy --manifest-path ./message_buffer/Cargo.toml --tests --fix --allow-dirty --allow-staged -- -D warnings
         pass_filenames: false
-        files: accumulator_updater
+        files: message_buffer
       # Hooks for solana receiver contract
       - id: cargo-fmt-solana-receiver
         name: Cargo format for solana target chain contract

+ 152 - 0
message_buffer/programs/message_buffer/src/instructions/create_buffer.rs

@@ -0,0 +1,152 @@
+use {
+    crate::{
+        instructions::is_uninitialized_account,
+        state::*,
+        MessageBufferError,
+        MESSAGE,
+    },
+    anchor_lang::{
+        prelude::*,
+        solana_program::entrypoint::MAX_PERMITTED_DATA_INCREASE,
+        system_program::{
+            self,
+            Allocate,
+            Assign,
+            Transfer,
+        },
+    },
+};
+
+pub fn create_buffer<'info>(
+    ctx: Context<'_, '_, '_, 'info, CreateBuffer<'info>>,
+    allowed_program_auth: Pubkey,
+    base_account_key: Pubkey,
+    target_size: u32,
+) -> Result<()> {
+    let buffer_account = ctx
+        .remaining_accounts
+        .first()
+        .ok_or(MessageBufferError::MessageBufferNotProvided)?;
+
+    ctx.accounts
+        .whitelist
+        .is_allowed_program_auth(&allowed_program_auth)?;
+
+    require_gte!(
+        target_size,
+        MessageBuffer::HEADER_LEN as u32,
+        MessageBufferError::MessageBufferTooSmall
+    );
+
+    require_gte!(
+        MAX_PERMITTED_DATA_INCREASE,
+        target_size as usize,
+        MessageBufferError::TargetSizeDeltaExceeded
+    );
+    if is_uninitialized_account(buffer_account) {
+        let (pda, bump) = Pubkey::find_program_address(
+            &[
+                allowed_program_auth.as_ref(),
+                MESSAGE.as_bytes(),
+                base_account_key.as_ref(),
+            ],
+            &crate::ID,
+        );
+        require_keys_eq!(buffer_account.key(), pda);
+        let signer_seeds = [
+            allowed_program_auth.as_ref(),
+            MESSAGE.as_bytes(),
+            base_account_key.as_ref(),
+            &[bump],
+        ];
+
+        CreateBuffer::create_account(
+            buffer_account,
+            target_size as usize,
+            &ctx.accounts.admin,
+            &[signer_seeds.as_slice()],
+            &ctx.accounts.system_program,
+        )?;
+
+        let loader =
+            AccountLoader::<MessageBuffer>::try_from_unchecked(&crate::ID, buffer_account)?;
+        {
+            let mut message_buffer = loader.load_init()?;
+            *message_buffer = MessageBuffer::new(bump);
+        }
+        loader.exit(&crate::ID)?;
+    }
+
+    Ok(())
+}
+
+
+#[derive(Accounts)]
+pub struct CreateBuffer<'info> {
+    #[account(
+    seeds = [b"message".as_ref(), b"whitelist".as_ref()],
+    bump = whitelist.bump,
+    has_one = admin,
+    )]
+    pub whitelist: Account<'info, Whitelist>,
+
+    // Also pays for account creation
+    #[account(mut)]
+    pub admin: Signer<'info>,
+
+    pub system_program: Program<'info, System>,
+    // remaining_accounts:  - [AccumulatorInput PDA]
+}
+
+
+impl<'info> CreateBuffer<'info> {
+    /// Manually invoke transfer, allocate & assign ixs to create an account
+    /// to handle situation where an account already has lamports
+    /// since system_program::create_account will fail in this case
+    fn create_account<'a>(
+        new_account_info: &AccountInfo<'a>,
+        space: usize,
+        payer: &Signer<'a>,
+        seeds: &[&[&[u8]]],
+        system_program: &AccountInfo<'a>,
+    ) -> Result<()> {
+        let target_rent = Rent::get()?.minimum_balance(space);
+        if new_account_info.lamports() < target_rent {
+            system_program::transfer(
+                CpiContext::new_with_signer(
+                    system_program.to_account_info(),
+                    Transfer {
+                        from: payer.to_account_info(),
+                        to:   new_account_info.to_account_info(),
+                    },
+                    seeds,
+                ),
+                target_rent - new_account_info.lamports(),
+            )?;
+        };
+
+        system_program::allocate(
+            CpiContext::new_with_signer(
+                system_program.to_account_info(),
+                Allocate {
+                    account_to_allocate: new_account_info.to_account_info(),
+                },
+                seeds,
+            ),
+            space.try_into().unwrap(),
+        )?;
+
+        system_program::assign(
+            CpiContext::new_with_signer(
+                system_program.to_account_info(),
+                Assign {
+                    account_to_assign: new_account_info.to_account_info(),
+                },
+                seeds,
+            ),
+            &crate::ID,
+        )?;
+
+        Ok(())
+    }
+}

+ 65 - 0
message_buffer/programs/message_buffer/src/instructions/delete_buffer.rs

@@ -0,0 +1,65 @@
+use {
+    crate::{
+        instructions::verify_message_buffer,
+        state::*,
+        MessageBufferError,
+        MESSAGE,
+    },
+    anchor_lang::prelude::*,
+};
+
+pub fn delete_buffer<'info>(
+    ctx: Context<'_, '_, '_, 'info, DeleteBuffer<'info>>,
+    allowed_program_auth: Pubkey,
+    base_account_key: Pubkey,
+    bump: u8,
+) -> Result<()> {
+    let message_buffer_account_info = ctx
+        .remaining_accounts
+        .first()
+        .ok_or(MessageBufferError::MessageBufferNotProvided)?;
+
+    ctx.accounts
+        .whitelist
+        .is_allowed_program_auth(&allowed_program_auth)?;
+
+    verify_message_buffer(message_buffer_account_info)?;
+
+    let expected_key = Pubkey::create_program_address(
+        &[
+            allowed_program_auth.as_ref(),
+            MESSAGE.as_bytes(),
+            base_account_key.as_ref(),
+            &[bump],
+        ],
+        &crate::ID,
+    )
+    .map_err(|_| MessageBufferError::InvalidPDA)?;
+
+    require_keys_eq!(
+        message_buffer_account_info.key(),
+        expected_key,
+        MessageBufferError::InvalidPDA
+    );
+    let loader = AccountLoader::<MessageBuffer>::try_from_unchecked(
+        &crate::ID,
+        message_buffer_account_info,
+    )?;
+    loader.close(ctx.accounts.admin.to_account_info())?;
+    Ok(())
+}
+
+#[derive(Accounts)]
+pub struct DeleteBuffer<'info> {
+    #[account(
+        seeds = [b"message".as_ref(), b"whitelist".as_ref()],
+        bump = whitelist.bump,
+        has_one = admin,
+    )]
+    pub whitelist: Account<'info, Whitelist>,
+
+    // Also the recipient of the lamports from closing the buffer account
+    #[account(mut)]
+    pub admin: Signer<'info>,
+    // remaining_account:  - [AccumulatorInput PDA]
+}

+ 54 - 1
message_buffer/programs/message_buffer/src/instructions/mod.rs

@@ -1,3 +1,56 @@
-pub use put_all::*;
+use {
+    crate::{
+        state::MessageBuffer,
+        MessageBufferError,
+    },
+    anchor_lang::{
+        prelude::*,
+        system_program,
+        Discriminator,
+    },
+};
+pub use {
+    create_buffer::*,
+    delete_buffer::*,
+    put_all::*,
+    resize_buffer::*,
+};
 
+
+mod create_buffer;
+mod delete_buffer;
 mod put_all;
+mod resize_buffer;
+
+// String constants for deriving PDAs.
+// An authorized program's message buffer will have PDA seeds [authorized_program_pda, MESSAGE, base_account_key],
+// where authorized_program_pda is the
+pub const MESSAGE: &str = "message";
+pub const FUND: &str = "fund";
+
+
+pub fn is_uninitialized_account(ai: &AccountInfo) -> bool {
+    ai.data_is_empty() && ai.owner == &system_program::ID
+}
+
+/// Verify message buffer account is initialized and has the correct discriminator.
+///
+/// Note: manually checking because using anchor's `AccountLoader.load()`
+/// will panic since the `AccountInfo.data_len()` will not match the
+/// size of the `MessageBuffer` since the `MessageBuffer` struct does not
+/// include the messages.
+pub fn verify_message_buffer(message_buffer_account_info: &AccountInfo) -> Result<()> {
+    if is_uninitialized_account(message_buffer_account_info) {
+        return err!(MessageBufferError::MessageBufferUninitialized);
+    }
+    let data = message_buffer_account_info.try_borrow_data()?;
+    if data.len() < MessageBuffer::discriminator().len() {
+        return Err(ErrorCode::AccountDiscriminatorNotFound.into());
+    }
+
+    let disc_bytes = &data[0..8];
+    if disc_bytes != &MessageBuffer::discriminator() {
+        return Err(ErrorCode::AccountDiscriminatorMismatch.into());
+    }
+    Ok(())
+}

+ 24 - 113
message_buffer/programs/message_buffer/src/instructions/put_all.rs

@@ -1,144 +1,55 @@
 use {
     crate::{
+        instructions::verify_message_buffer,
         state::*,
-        AccumulatorUpdaterError,
-    },
-    anchor_lang::{
-        prelude::*,
-        system_program::{
-            self,
-            CreateAccount,
-        },
+        MessageBufferError,
     },
+    anchor_lang::prelude::*,
+    std::mem,
 };
 
 
-pub const MESSAGE: &str = "message";
-pub const FUND: &str = "fund";
-
-
 pub fn put_all<'info>(
     ctx: Context<'_, '_, '_, 'info, PutAll<'info>>,
     base_account_key: Pubkey,
     messages: Vec<Vec<u8>>,
 ) -> Result<()> {
     let cpi_caller_auth = ctx.accounts.whitelist_verifier.is_allowed()?;
-    let accumulator_input_ai = ctx
+    let message_buffer_account_info = ctx
         .remaining_accounts
         .first()
-        .ok_or(AccumulatorUpdaterError::MessageBufferNotProvided)?;
+        .ok_or(MessageBufferError::MessageBufferNotProvided)?;
 
-    let loader;
+    verify_message_buffer(message_buffer_account_info)?;
 
-    {
-        let accumulator_input = &mut (if is_uninitialized_account(accumulator_input_ai) {
-            let (pda, bump) = Pubkey::find_program_address(
-                &[
-                    cpi_caller_auth.as_ref(),
-                    MESSAGE.as_bytes(),
-                    base_account_key.as_ref(),
-                ],
-                &crate::ID,
-            );
-            require_keys_eq!(accumulator_input_ai.key(), pda);
-            let signer_seeds = [
-                cpi_caller_auth.as_ref(),
-                MESSAGE.as_bytes(),
-                base_account_key.as_ref(),
-                &[bump],
-            ];
-            let fund_pda_bump = *ctx
-                .bumps
-                .get(FUND)
-                .ok_or(AccumulatorUpdaterError::FundBumpNotFound)?;
-            let fund_signer_seeds = [FUND.as_bytes(), &[fund_pda_bump]];
-            PutAll::create_account(
-                accumulator_input_ai,
-                8 + MessageBuffer::INIT_SPACE,
-                &ctx.accounts.fund,
-                &[signer_seeds.as_slice(), fund_signer_seeds.as_slice()],
-                &ctx.accounts.system_program,
-            )?;
-            loader = AccountLoader::<MessageBuffer>::try_from_unchecked(
-                &crate::ID,
-                accumulator_input_ai,
-            )?;
-            let mut accumulator_input = loader.load_init()?;
-            accumulator_input.header = BufferHeader::new(bump);
-            accumulator_input
-        } else {
-            loader = AccountLoader::<MessageBuffer>::try_from(accumulator_input_ai)?;
-            let mut accumulator_input = loader.load_mut()?;
-            accumulator_input.header.set_version();
-            accumulator_input
-        });
-        // note: redundant for uninitialized code path but safer to check here.
-        // compute budget cost should be minimal
-        accumulator_input.validate(
-            accumulator_input_ai.key(),
-            cpi_caller_auth,
-            base_account_key,
-        )?;
+    let account_data = &mut message_buffer_account_info.try_borrow_mut_data()?;
+    let header_end_index = mem::size_of::<MessageBuffer>() + 8;
 
+    let (header_bytes, body_bytes) = account_data.split_at_mut(header_end_index);
 
-        let (num_msgs, num_bytes) = accumulator_input.put_all(&messages);
-        if num_msgs != messages.len() {
-            msg!("unable to fit all messages in accumulator input account. Wrote {}/{} messages and {} bytes", num_msgs, messages.len(), num_bytes);
-        }
-    }
+    let message_buffer: &mut MessageBuffer = bytemuck::from_bytes_mut(&mut header_bytes[8..]);
 
+    message_buffer.validate(
+        message_buffer_account_info.key(),
+        cpi_caller_auth,
+        base_account_key,
+    )?;
 
-    loader.exit(&crate::ID)?;
+    message_buffer.refresh_header();
 
-    Ok(())
-}
+    let (num_msgs, num_bytes) = message_buffer.put_all_in_buffer(body_bytes, &messages);
 
-pub fn is_uninitialized_account(ai: &AccountInfo) -> bool {
-    ai.data_is_empty() && ai.owner == &system_program::ID
-}
+    if num_msgs != messages.len() {
+        // FIXME: make this into an emit! event
+        msg!("unable to fit all messages in accumulator input account. Wrote {}/{} messages and {} bytes", num_msgs, messages.len(), num_bytes);
+    }
 
+    Ok(())
+}
 
 #[derive(Accounts)]
 #[instruction( base_account_key: Pubkey)]
 pub struct PutAll<'info> {
-    /// `Fund` is a system account that holds
-    /// the lamports that will be used to fund
-    /// `AccumulatorInput` account initialization
-    #[account(
-        mut,
-        seeds = [b"fund".as_ref()],
-        owner = system_program::System::id(),
-        bump,
-    )]
-    pub fund:               SystemAccount<'info>,
     pub whitelist_verifier: WhitelistVerifier<'info>,
-    pub system_program:     Program<'info, System>,
     // remaining_accounts:  - [AccumulatorInput PDA]
 }
-
-
-impl<'info> PutAll<'info> {
-    fn create_account<'a>(
-        account_info: &AccountInfo<'a>,
-        space: usize,
-        payer: &AccountInfo<'a>,
-        seeds: &[&[&[u8]]],
-        system_program: &AccountInfo<'a>,
-    ) -> Result<()> {
-        let lamports = Rent::get()?.minimum_balance(space);
-        system_program::create_account(
-            CpiContext::new_with_signer(
-                system_program.to_account_info(),
-                CreateAccount {
-                    from: payer.to_account_info(),
-                    to:   account_info.to_account_info(),
-                },
-                seeds,
-            ),
-            lamports,
-            space.try_into().unwrap(),
-            &crate::ID,
-        )?;
-        Ok(())
-    }
-}

+ 109 - 0
message_buffer/programs/message_buffer/src/instructions/resize_buffer.rs

@@ -0,0 +1,109 @@
+use {
+    crate::{
+        instructions::verify_message_buffer,
+        state::*,
+        MessageBufferError,
+        MESSAGE,
+    },
+    anchor_lang::{
+        prelude::*,
+        solana_program::entrypoint::MAX_PERMITTED_DATA_INCREASE,
+        system_program::{
+            self,
+            Transfer,
+        },
+    },
+};
+
+pub fn resize_buffer<'info>(
+    ctx: Context<'_, '_, '_, 'info, ResizeBuffer<'info>>,
+    allowed_program_auth: Pubkey,
+    base_account_key: Pubkey,
+    buffer_bump: u8,
+    target_size: u32,
+) -> Result<()> {
+    let message_buffer_account_info = ctx
+        .remaining_accounts
+        .first()
+        .ok_or(MessageBufferError::MessageBufferNotProvided)?;
+
+    ctx.accounts
+        .whitelist
+        .is_allowed_program_auth(&allowed_program_auth)?;
+    verify_message_buffer(message_buffer_account_info)?;
+
+    require_gte!(
+        target_size,
+        MessageBuffer::HEADER_LEN as u32,
+        MessageBufferError::MessageBufferTooSmall
+    );
+    let target_size = target_size as usize;
+    let target_size_delta = target_size.saturating_sub(message_buffer_account_info.data_len());
+    require_gte!(
+        MAX_PERMITTED_DATA_INCREASE,
+        target_size_delta,
+        MessageBufferError::TargetSizeDeltaExceeded
+    );
+
+    let expected_key = Pubkey::create_program_address(
+        &[
+            allowed_program_auth.as_ref(),
+            MESSAGE.as_bytes(),
+            base_account_key.as_ref(),
+            &[buffer_bump],
+        ],
+        &crate::ID,
+    )
+    .map_err(|_| MessageBufferError::InvalidPDA)?;
+
+    require_keys_eq!(
+        message_buffer_account_info.key(),
+        expected_key,
+        MessageBufferError::InvalidPDA
+    );
+
+    if target_size_delta > 0 {
+        let target_rent = Rent::get()?.minimum_balance(target_size);
+        if message_buffer_account_info.lamports() < target_rent {
+            system_program::transfer(
+                CpiContext::new(
+                    ctx.accounts.system_program.to_account_info(),
+                    Transfer {
+                        from: ctx.accounts.admin.to_account_info(),
+                        to:   message_buffer_account_info.to_account_info(),
+                    },
+                ),
+                target_rent - message_buffer_account_info.lamports(),
+            )?;
+        }
+        message_buffer_account_info
+            .realloc(target_size, false)
+            .map_err(|_| MessageBufferError::ReallocFailed)?;
+    } else {
+        // Not transferring excess lamports back to admin.
+        // Account will retain more lamports than necessary.
+        message_buffer_account_info.realloc(target_size, false)?;
+    }
+    Ok(())
+}
+
+#[derive(Accounts)]
+#[instruction(
+    allowed_program_auth: Pubkey, base_account_key: Pubkey,
+    buffer_bump: u8, target_size: u32
+)]
+pub struct ResizeBuffer<'info> {
+    #[account(
+        seeds = [b"message".as_ref(), b"whitelist".as_ref()],
+        bump = whitelist.bump,
+        has_one = admin,
+    )]
+    pub whitelist: Account<'info, Whitelist>,
+
+    // Also pays for account creation
+    #[account(mut)]
+    pub admin: Signer<'info>,
+
+    pub system_program: Program<'info, System>,
+    // remaining_accounts:  - [AccumulatorInput PDA]
+}

+ 91 - 20
message_buffer/programs/message_buffer/src/lib.rs

@@ -16,13 +16,13 @@ pub mod message_buffer {
     use super::*;
 
 
-    /// Initializes the whitelist and sets it's authority to the provided pubkey
+    /// Initializes the whitelist and sets it's admin to the provided pubkey
     /// Once initialized, the authority must sign all further changes to the whitelist.
-    pub fn initialize(ctx: Context<Initialize>, authority: Pubkey) -> Result<()> {
-        require_keys_neq!(authority, Pubkey::default());
+    pub fn initialize(ctx: Context<Initialize>, admin: Pubkey) -> Result<()> {
+        require_keys_neq!(admin, Pubkey::default());
         let whitelist = &mut ctx.accounts.whitelist;
         whitelist.bump = *ctx.bumps.get("whitelist").unwrap();
-        whitelist.authority = authority;
+        whitelist.admin = admin;
         Ok(())
     }
 
@@ -40,23 +40,19 @@ pub mod message_buffer {
         Ok(())
     }
 
-    /// Sets the new authority for the whitelist
-    pub fn update_whitelist_authority(
-        ctx: Context<UpdateWhitelist>,
-        new_authority: Pubkey,
-    ) -> Result<()> {
+    /// Sets the new admin for the whitelist
+    pub fn update_whitelist_admin(ctx: Context<UpdateWhitelist>, new_admin: Pubkey) -> Result<()> {
         let whitelist = &mut ctx.accounts.whitelist;
-        whitelist.validate_new_authority(new_authority)?;
-        whitelist.authority = new_authority;
+        whitelist.validate_new_admin(new_admin)?;
+        whitelist.admin = new_admin;
         Ok(())
     }
 
 
-    /// Insert messages/inputs for the Accumulator. All inputs derived from the
-    /// `base_account_key` will go into the same PDA. The PDA is derived with
-    /// seeds = [cpi_caller_auth, b"accumulator", base_account_key]
-    ///
-    ///
+    /// Put messages into the Accumulator. All messages put for the same
+    /// `base_account_key` go into the same buffer PDA. The PDA's address is
+    /// `[allowed_program_auth, MESSAGE, base_account_key]`, where `allowed_program_auth`
+    /// is the whitelisted pubkey who authorized this call.
     ///
     /// * `base_account_key`    - Pubkey of the original account the
     ///                           `MessageBuffer` is derived from
@@ -74,7 +70,6 @@ pub mod message_buffer {
     /// any existing contents.
     ///
     /// TODO:
-    ///     - try handling re-allocation of the accumulator_input space
     ///     - handle updates ("paging/batches of messages")
     ///
     pub fn put_all<'info>(
@@ -84,6 +79,74 @@ pub mod message_buffer {
     ) -> Result<()> {
         instructions::put_all(ctx, base_account_key, messages)
     }
+
+
+    /// Initializes the buffer account with the `target_size`
+    ///
+    /// *`allowed_program_auth` - The whitelisted pubkey representing an
+    ///                            allowed program. Used as one of the seeds
+    ///                            for deriving the `MessageBuffer` PDA.
+    /// * `base_account_key`    - Pubkey of the original account the
+    ///                           `MessageBuffer` is derived from
+    ///                           (e.g. pyth price account)
+    /// *`target_size`          - Initial size to allocate for the
+    ///                           `MessageBuffer` PDA. `target_size`
+    ///                           must be >= HEADER_LEN && <= 10240
+    pub fn create_buffer<'info>(
+        ctx: Context<'_, '_, '_, 'info, CreateBuffer<'info>>,
+        allowed_program_auth: Pubkey,
+        base_account_key: Pubkey,
+        target_size: u32,
+    ) -> Result<()> {
+        instructions::create_buffer(ctx, allowed_program_auth, base_account_key, target_size)
+    }
+
+    /// Resizes the buffer account to the `target_size`
+    ///
+    /// *`allowed_program_auth` - The whitelisted pubkey representing an
+    ///                            allowed program. Used as one of the seeds
+    ///                            for deriving the `MessageBuffer` PDA.
+    /// * `base_account_key`    - Pubkey of the original account the
+    ///                           `MessageBuffer` is derived from
+    ///                           (e.g. pyth price account)
+    /// *`target_size`          -  Size to re-allocate for the
+    ///                           `MessageBuffer` PDA. If increasing the size,
+    ///                           max delta of current_size & target_size is 10240
+    /// *`buffer_bump`          -  Bump seed for the `MessageBuffer` PDA
+    pub fn resize_buffer<'info>(
+        ctx: Context<'_, '_, '_, 'info, ResizeBuffer<'info>>,
+        allowed_program_auth: Pubkey,
+        base_account_key: Pubkey,
+        buffer_bump: u8,
+        target_size: u32,
+    ) -> Result<()> {
+        instructions::resize_buffer(
+            ctx,
+            allowed_program_auth,
+            base_account_key,
+            buffer_bump,
+            target_size,
+        )
+    }
+
+    /// Closes the buffer account and transfers the remaining lamports to the
+    /// `admin` account
+    ///
+    /// *`allowed_program_auth` - The whitelisted pubkey representing an
+    ///                            allowed program. Used as one of the seeds
+    ///                            for deriving the `MessageBuffer` PDA.
+    /// * `base_account_key`    - Pubkey of the original account the
+    ///                           `MessageBuffer` is derived from
+    ///                           (e.g. pyth price account)
+    /// *`buffer_bump`          -  Bump seed for the `MessageBuffer` PDA
+    pub fn delete_buffer<'info>(
+        ctx: Context<'_, '_, '_, 'info, DeleteBuffer<'info>>,
+        allowed_program_auth: Pubkey,
+        base_account_key: Pubkey,
+        buffer_bump: u8,
+    ) -> Result<()> {
+        instructions::delete_buffer(ctx, allowed_program_auth, base_account_key, buffer_bump)
+    }
 }
 
 #[derive(Accounts)]
@@ -107,19 +170,19 @@ pub struct UpdateWhitelist<'info> {
     #[account(mut)]
     pub payer: Signer<'info>,
 
-    pub authority: Signer<'info>,
+    pub admin:     Signer<'info>,
     #[account(
         mut,
         seeds = [b"message".as_ref(), b"whitelist".as_ref()],
         bump = whitelist.bump,
-        has_one = authority
+        has_one = admin
     )]
     pub whitelist: Account<'info, Whitelist>,
 }
 
 
 #[error_code]
-pub enum AccumulatorUpdaterError {
+pub enum MessageBufferError {
     #[msg("CPI Caller not allowed")]
     CallerNotAllowed,
     #[msg("Whitelist already contains program")]
@@ -140,6 +203,14 @@ pub enum AccumulatorUpdaterError {
     CurrentDataLengthExceeded,
     #[msg("Message Buffer not provided")]
     MessageBufferNotProvided,
+    #[msg("Message Buffer is not sufficiently large")]
+    MessageBufferTooSmall,
     #[msg("Fund Bump not found")]
     FundBumpNotFound,
+    #[msg("Reallocation failed")]
+    ReallocFailed,
+    #[msg("Target size too large for reallocation/initialization. Max delta is 10240")]
+    TargetSizeDeltaExceeded,
+    #[msg("MessageBuffer Uninitialized")]
+    MessageBufferUninitialized,
 }

+ 1 - 1
message_buffer/programs/message_buffer/src/macros.rs

@@ -5,7 +5,7 @@ macro_rules! accumulator_input_seeds {
             $cpi_caller_pid.as_ref(),
             b"message".as_ref(),
             $base_account.as_ref(),
-            &[$accumulator_input.header.bump],
+            &[$accumulator_input.bump],
         ]
     };
 }

+ 144 - 102
message_buffer/programs/message_buffer/src/state/message_buffer.rs

@@ -1,36 +1,31 @@
 use {
     crate::{
         accumulator_input_seeds,
-        AccumulatorUpdaterError,
+        MessageBufferError,
     },
     anchor_lang::prelude::*,
 };
 
-
-/// `MessageBuffer` is an arbitrary set of bytes
-/// that will be included in the AccumulatorSysvar
-///
+/// A MessageBuffer will have the following structure
+/// ```ignore
+/// struct MessageBuffer {
+///     header: BufferHeader,
+///     messages: [u8; accountInfo.data.len - header.header_len]
+/// }
+/// ```
 ///
-/// The actual contents of data are set/handled by
-/// the CPI calling program (e.g. Pyth Oracle)
+/// where `MESSAGES_LEN` can be dynamic. There is actual
+/// no messages field in the `MessageBuffer` struct definition due to messages
+/// needing to be a dynamic length while supporting zero_copy
+/// at the same time.
 ///
-/// TODO: implement custom serialization & set alignment
+/// A `MessageBuffer` AccountInfo.data will look like:
+/// [  <discrimintator>, <buffer_header>, <messages> ]
+///         (0..8)       (8..header_len) (header_len...accountInfo.data.len)
 #[account(zero_copy)]
-#[derive(Debug, InitSpace)]
-pub struct MessageBuffer {
-    pub header:   BufferHeader,
-    // 10KB - 8 (discriminator) - 514 (header)
-    // TODO: do we want to initialize this to the max size?
-    //   - will lead to more data being passed around for validators
-    pub messages: [u8; 9_718],
-}
-
-//TODO:
-// - implement custom serialization & set alignment
-// - what other fields are needed?
-#[zero_copy]
 #[derive(InitSpace, Debug)]
-pub struct BufferHeader {
+pub struct MessageBuffer {
+    /* header */
     pub bump:        u8, // 1
     pub version:     u8, // 1
     // byte offset of accounts where data starts
@@ -41,14 +36,19 @@ pub struct BufferHeader {
     /// => msg1 = account_info.data[(header_len + 0)..(header_len + 10)]
     /// => msg2 = account_info.data[(header_len + 10)..(header_len + 14)]
     pub end_offsets: [u16; 255], // 510
+
+                          /* messages */
+                          //  not defined in struct since needs to support variable length
+                          //  and work with zero_copy
+                          // pub messages: [u8; accountInfo.data.len - header_len]
 }
 
 
-impl BufferHeader {
+impl MessageBuffer {
     // HEADER_LEN allows for append-only forward-compatibility for the header.
     // this is the number of bytes from the beginning of the account_info.data
     // to the start of the `AccumulatorInput` data.
-    pub const HEADER_LEN: u16 = 8 + BufferHeader::INIT_SPACE as u16;
+    pub const HEADER_LEN: u16 = 8 + MessageBuffer::INIT_SPACE as u16;
 
     pub const CURRENT_VERSION: u8 = 1;
 
@@ -61,34 +61,35 @@ impl BufferHeader {
         }
     }
 
-    pub fn set_version(&mut self) {
+    pub fn refresh_header(&mut self) {
+        self.header_len = Self::HEADER_LEN;
         self.version = Self::CURRENT_VERSION;
-    }
-}
-impl MessageBuffer {
-    pub fn new(bump: u8) -> Self {
-        let header = BufferHeader::new(bump);
-        Self {
-            header,
-            messages: [0u8; 9_718],
-        }
+        self.end_offsets = [0u16; u8::MAX as usize];
     }
 
     /// `put_all` writes all the messages to the `AccumulatorInput` account
     /// and updates the `end_offsets` array.
     ///
+    /// TODO: the first byte of destination is the first non-header byte of the
+    /// message buffer account
+    ///
     /// Returns tuple of the number of messages written and the end_offset
     /// of the last message
     ///
     // TODO: add a end_offsets index parameter for "continuation"
     // TODO: test max size of parameters that can be passed into CPI call
-    pub fn put_all(&mut self, values: &Vec<Vec<u8>>) -> (usize, u16) {
+    pub fn put_all_in_buffer(
+        &mut self,
+        destination: &mut [u8],
+        values: &Vec<Vec<u8>>,
+    ) -> (usize, u16) {
         let mut offset = 0u16;
 
         for (i, v) in values.iter().enumerate() {
             let start = offset;
             let len = u16::try_from(v.len());
             if len.is_err() {
+                msg!("len err");
                 return (i, start);
             }
             let end = offset.checked_add(len.unwrap());
@@ -96,23 +97,22 @@ impl MessageBuffer {
                 return (i, start);
             }
             let end = end.unwrap();
-            if end > self.messages.len() as u16 {
+            if end > destination.len() as u16 {
                 return (i, start);
             }
-            self.header.end_offsets[i] = end;
-            self.messages[(start as usize)..(end as usize)].copy_from_slice(v);
+            self.end_offsets[i] = end;
+            destination[(start as usize)..(end as usize)].copy_from_slice(v);
             offset = end
         }
         (values.len(), offset)
     }
 
-
     fn derive_pda(&self, cpi_caller: Pubkey, base_account: Pubkey) -> Result<Pubkey> {
         let res = Pubkey::create_program_address(
             accumulator_input_seeds!(self, cpi_caller, base_account),
             &crate::ID,
         )
-        .map_err(|_| AccumulatorUpdaterError::InvalidPDA)?;
+        .map_err(|_| MessageBufferError::InvalidPDA)?;
         Ok(res)
     }
 
@@ -123,15 +123,18 @@ impl MessageBuffer {
     }
 }
 
-
 #[cfg(test)]
 mod test {
     use {
         super::*,
-        bytemuck::bytes_of,
-        std::mem::{
-            align_of,
-            size_of,
+        anchor_lang::solana_program::keccak::hashv,
+        bytemuck::bytes_of_mut,
+        std::{
+            io::Write,
+            mem::{
+                align_of,
+                size_of,
+            },
         },
     };
 
@@ -143,18 +146,22 @@ mod test {
         bytes
     }
 
+    fn sighash(namespace: &str, name: &str) -> [u8; 8] {
+        let preimage = format!("{namespace}:{name}");
+
+        let mut sighash = [0u8; 8];
+        sighash.copy_from_slice(&hashv(&[preimage.as_bytes()]).to_bytes()[..8]);
+        sighash
+    }
+
 
     #[test]
     fn test_sizes_and_alignments() {
-        let (header_idx_size, header_idx_align) =
-            (size_of::<BufferHeader>(), align_of::<BufferHeader>());
-
-        let (input_size, input_align) = (size_of::<MessageBuffer>(), align_of::<MessageBuffer>());
+        let (message_buffer_size, message_buffer_align) =
+            (size_of::<MessageBuffer>(), align_of::<MessageBuffer>());
 
-        assert_eq!(header_idx_size, 514);
-        assert_eq!(header_idx_align, 2);
-        assert_eq!(input_size, 10_232);
-        assert_eq!(input_align, 2);
+        assert_eq!(message_buffer_size, 514);
+        assert_eq!(message_buffer_align, 2);
     }
 
     #[test]
@@ -162,35 +169,50 @@ mod test {
         let data = vec![vec![12, 34], vec![56, 78, 90]];
         let data_bytes: Vec<Vec<u8>> = data.into_iter().map(data_bytes).collect();
 
-        let accumulator_input = &mut MessageBuffer::new(0);
+        let message_buffer = &mut MessageBuffer::new(0);
+        let header_len = message_buffer.header_len as usize;
+        let message_buffer_bytes = bytes_of_mut(message_buffer);
+        // assuming account_info.data.len() == 10KB
+        let messages = &mut vec![0u8; 10_240 - header_len];
+
+        let account_info_data = &mut vec![];
+        let discriminator = &mut sighash("accounts", "MessageBuffer");
+        account_info_data.write_all(discriminator).unwrap();
+        account_info_data.write_all(message_buffer_bytes).unwrap();
+        account_info_data
+            .write_all(messages.as_mut_slice())
+            .unwrap();
+
+        let _account_data_len = account_info_data.len();
+
+        let destination = &mut account_info_data[(message_buffer.header_len as usize)..];
+
+        let (num_msgs, num_bytes) = message_buffer.put_all_in_buffer(destination, &data_bytes);
 
-        let (num_msgs, num_bytes) = accumulator_input.put_all(&data_bytes);
         assert_eq!(num_msgs, 2);
         assert_eq!(num_bytes, 5);
 
 
-        assert_eq!(accumulator_input.header.end_offsets[0], 2);
-        assert_eq!(accumulator_input.header.end_offsets[1], 5);
+        assert_eq!(message_buffer.end_offsets[0], 2);
+        assert_eq!(message_buffer.end_offsets[1], 5);
 
 
-        let message_buffer_bytes = bytes_of(accumulator_input);
+        // let account_data = bytes_of(accumulator_input);
 
-        // The header_len field represents the size of all data prior to the message bytes.
-        // This includes the account discriminator, which is not part of the header struct.
-        // Subtract the size of the discriminator (8 bytes) to compensate
-        let header_len = accumulator_input.header.header_len as usize - 8;
 
+        // // The header_len field represents the size of all data prior to the message bytes.
+        // // This includes the account discriminator, which is not part of the header struct.
+        // // Subtract the size of the discriminator (8 bytes) to compensate
+        // let header_len = message_buffer.header_len as usize - 8;
+        let header_len = message_buffer.header_len as usize;
 
-        let iter = accumulator_input
-            .header
-            .end_offsets
-            .iter()
-            .take_while(|x| **x != 0);
+
+        let iter = message_buffer.end_offsets.iter().take_while(|x| **x != 0);
         let mut start = header_len;
         let mut data_iter = data_bytes.iter();
         for offset in iter {
             let end_offset = header_len + *offset as usize;
-            let message_buffer_data = &message_buffer_bytes[start..end_offset];
+            let message_buffer_data = &account_info_data[start..end_offset];
             let expected_data = data_iter.next().unwrap();
             assert_eq!(message_buffer_data, expected_data.as_slice());
             start = end_offset;
@@ -202,40 +224,48 @@ mod test {
         let data = vec![vec![0u8; 9_718 - 2], vec![0u8], vec![0u8; 2]];
 
         let data_bytes: Vec<Vec<u8>> = data.into_iter().map(data_bytes).collect();
+
         let message_buffer = &mut MessageBuffer::new(0);
-        let (num_msgs, num_bytes) = message_buffer.put_all(&data_bytes);
+        let header_len = message_buffer.header_len as usize;
+        let message_buffer_bytes = bytes_of_mut(message_buffer);
+        // assuming account_info.data.len() == 10KB
+        let messages = &mut vec![0u8; 10_240 - header_len];
+
+        let account_info_data = &mut vec![];
+        let discriminator = &mut sighash("accounts", "MessageBuffer");
+        account_info_data.write_all(discriminator).unwrap();
+        account_info_data.write_all(message_buffer_bytes).unwrap();
+        account_info_data
+            .write_all(messages.as_mut_slice())
+            .unwrap();
+
+        let _account_data_len = account_info_data.len();
+
+        let destination = &mut account_info_data[(message_buffer.header_len as usize)..];
+
+        let (num_msgs, num_bytes) = message_buffer.put_all_in_buffer(destination, &data_bytes);
+
         assert_eq!(num_msgs, 2);
         assert_eq!(
             num_bytes,
             data_bytes[0..2].iter().map(|x| x.len()).sum::<usize>() as u16
         );
 
-        let message_buffer_bytes = bytes_of(message_buffer);
-
-        // The header_len field represents the size of all data prior to the message bytes.
-        // This includes the account discriminator, which is not part of the header struct.
-        // Subtract the size of the discriminator (8 bytes) to compensate
-        let header_len = message_buffer.header.header_len as usize - 8;
 
-
-        let iter = message_buffer
-            .header
-            .end_offsets
-            .iter()
-            .take_while(|x| **x != 0);
+        let iter = message_buffer.end_offsets.iter().take_while(|x| **x != 0);
         let mut start = header_len;
         let mut data_iter = data_bytes.iter();
         for offset in iter {
             let end_offset = header_len + *offset as usize;
-            let message_buffer_data = &message_buffer_bytes[start..end_offset];
+            let message_buffer_data = &account_info_data[start..end_offset];
             let expected_data = data_iter.next().unwrap();
             assert_eq!(message_buffer_data, expected_data.as_slice());
             start = end_offset;
         }
 
-        assert_eq!(message_buffer.header.end_offsets[2], 0);
+        assert_eq!(message_buffer.end_offsets[2], 0);
     }
-
+    //
     #[test]
     fn test_put_all_long_vec() {
         let data = vec![
@@ -247,40 +277,52 @@ mod test {
         ];
 
         let data_bytes: Vec<Vec<u8>> = data.into_iter().map(data_bytes).collect();
+        // let message_buffer = &mut MessageBufferTemp::new(0);
+        // let (num_msgs, num_bytes) = message_buffer.put_all(&data_bytes);
+
         let message_buffer = &mut MessageBuffer::new(0);
-        let (num_msgs, num_bytes) = message_buffer.put_all(&data_bytes);
+        let header_len = message_buffer.header_len as usize;
+
+        let message_buffer_bytes = bytes_of_mut(message_buffer);
+        // assuming account_info.data.len() == 10KB
+        let messages = &mut vec![0u8; 10_240 - header_len];
+
+        let account_info_data = &mut vec![];
+        let discriminator = &mut sighash("accounts", "MessageBuffer");
+        account_info_data.write_all(discriminator).unwrap();
+        account_info_data.write_all(message_buffer_bytes).unwrap();
+        account_info_data
+            .write_all(messages.as_mut_slice())
+            .unwrap();
+
+        let _account_data_len = account_info_data.len();
+
+        let destination = &mut account_info_data[(message_buffer.header_len as usize)..];
+
+        let (num_msgs, num_bytes) = message_buffer.put_all_in_buffer(destination, &data_bytes);
+
         assert_eq!(num_msgs, 3);
         assert_eq!(
             num_bytes,
             data_bytes[0..3].iter().map(|x| x.len()).sum::<usize>() as u16
         );
 
-        let message_buffer_bytes = bytes_of(message_buffer);
-
-        // *note* minus 8 here since no account discriminator when using
-        // `bytes_of`directly on accumulator_input
-        let header_len = message_buffer.header.header_len as usize - 8;
-
 
-        let iter = message_buffer
-            .header
-            .end_offsets
-            .iter()
-            .take_while(|x| **x != 0);
+        let iter = message_buffer.end_offsets.iter().take_while(|x| **x != 0);
         let mut start = header_len;
         let mut data_iter = data_bytes.iter();
         for offset in iter {
             let end_offset = header_len + *offset as usize;
-            let message_buffer_data = &message_buffer_bytes[start..end_offset];
+            let message_buffer_data = &account_info_data[start..end_offset];
             let expected_data = data_iter.next().unwrap();
             assert_eq!(message_buffer_data, expected_data.as_slice());
             start = end_offset;
         }
 
-        assert_eq!(message_buffer.header.end_offsets[0], 9_715);
-        assert_eq!(message_buffer.header.end_offsets[1], 9_716);
-        assert_eq!(message_buffer.header.end_offsets[2], 9_717);
-        assert_eq!(message_buffer.header.end_offsets[3], 0);
-        assert_eq!(message_buffer.header.end_offsets[4], 0);
+        assert_eq!(message_buffer.end_offsets[0], 9_715);
+        assert_eq!(message_buffer.end_offsets[1], 9_716);
+        assert_eq!(message_buffer.end_offsets[2], 9_717);
+        assert_eq!(message_buffer.end_offsets[3], 0);
+        assert_eq!(message_buffer.end_offsets[4], 0);
     }
 }

+ 15 - 10
message_buffer/programs/message_buffer/src/state/whitelist.rs

@@ -1,5 +1,5 @@
 use {
-    crate::AccumulatorUpdaterError,
+    crate::MessageBufferError,
     anchor_lang::prelude::*,
 };
 
@@ -10,7 +10,7 @@ use {
 #[derive(InitSpace)]
 pub struct Whitelist {
     pub bump:             u8,
-    pub authority:        Pubkey,
+    pub admin:            Pubkey,
     #[max_len(32)]
     pub allowed_programs: Vec<Pubkey>,
 }
@@ -19,18 +19,26 @@ impl Whitelist {
     pub fn validate_programs(&self, allowed_programs: &[Pubkey]) -> Result<()> {
         require!(
             !self.allowed_programs.contains(&Pubkey::default()),
-            AccumulatorUpdaterError::InvalidAllowedProgram
+            MessageBufferError::InvalidAllowedProgram
         );
         require_gte!(
             32,
             allowed_programs.len(),
-            AccumulatorUpdaterError::MaximumAllowedProgramsExceeded
+            MessageBufferError::MaximumAllowedProgramsExceeded
         );
         Ok(())
     }
 
-    pub fn validate_new_authority(&self, new_authority: Pubkey) -> Result<()> {
-        require_keys_neq!(new_authority, Pubkey::default());
+    pub fn validate_new_admin(&self, new_admin: Pubkey) -> Result<()> {
+        require_keys_neq!(new_admin, Pubkey::default());
+        Ok(())
+    }
+
+    pub fn is_allowed_program_auth(&self, auth: &Pubkey) -> Result<()> {
+        require!(
+            self.allowed_programs.contains(auth),
+            MessageBufferError::CallerNotAllowed
+        );
         Ok(())
     }
 }
@@ -51,10 +59,7 @@ impl<'info> WhitelistVerifier<'info> {
     pub fn is_allowed(&self) -> Result<Pubkey> {
         let auth = self.cpi_caller_auth.key();
         let whitelist = &self.whitelist;
-        require!(
-            whitelist.allowed_programs.contains(&auth),
-            AccumulatorUpdaterError::CallerNotAllowed
-        );
+        whitelist.is_allowed_program_auth(&auth)?;
         Ok(auth)
     }
 }

+ 1 - 5
message_buffer/programs/mock-cpi-caller/src/instructions/add_price.rs

@@ -62,10 +62,8 @@ impl<'info> AddPrice<'info> {
         inputs: Vec<Vec<u8>>,
     ) -> anchor_lang::Result<()> {
         let mut accounts = vec![
-            AccountMeta::new(ctx.accounts.fund.key(), false),
             AccountMeta::new_readonly(ctx.accounts.accumulator_whitelist.key(), false),
             AccountMeta::new_readonly(ctx.accounts.auth.key(), true),
-            AccountMeta::new_readonly(ctx.accounts.system_program.key(), false),
         ];
         accounts.extend_from_slice(
             &ctx.remaining_accounts
@@ -131,8 +129,6 @@ pub struct AddPrice<'info> {
     pub pyth_price_account:     AccountLoader<'info, PriceAccount>,
     #[account(mut)]
     pub payer:                  Signer<'info>,
-    #[account(mut)]
-    pub fund:                   SystemAccount<'info>,
     /// also needed for accumulator_updater
     pub system_program:         Program<'info, System>,
     /// CHECK: whitelist
@@ -147,5 +143,5 @@ pub struct AddPrice<'info> {
     pub auth:                   SystemAccount<'info>,
     pub message_buffer_program: Program<'info, MessageBufferProgram>,
     // Remaining Accounts
-    // should all be new uninitialized accounts
+    // MessageBuffer PDA
 }

+ 0 - 6
message_buffer/programs/mock-cpi-caller/src/instructions/update_price.rs

@@ -42,10 +42,6 @@ pub struct UpdatePrice<'info> {
     bump,
     )]
     pub pyth_price_account:     AccountLoader<'info, PriceAccount>,
-    #[account(mut)]
-    pub fund:                   SystemAccount<'info>,
-    /// Needed for accumulator_updater
-    pub system_program:         Program<'info, System>,
     /// CHECK: whitelist
     pub accumulator_whitelist:  UncheckedAccount<'info>,
     #[account(
@@ -90,10 +86,8 @@ impl<'info> UpdatePrice<'info> {
         values: Vec<Vec<u8>>,
     ) -> anchor_lang::Result<()> {
         let mut accounts = vec![
-            AccountMeta::new(ctx.accounts.fund.key(), false),
             AccountMeta::new_readonly(ctx.accounts.accumulator_whitelist.key(), false),
             AccountMeta::new_readonly(ctx.accounts.auth.key(), true),
-            AccountMeta::new_readonly(ctx.accounts.system_program.key(), false),
         ];
         accounts.extend_from_slice(
             &ctx.remaining_accounts

+ 455 - 91
message_buffer/tests/message_buffer.ts

@@ -1,5 +1,10 @@
 import * as anchor from "@coral-xyz/anchor";
-import { IdlTypes, Program, BorshAccountsCoder } from "@coral-xyz/anchor";
+import {
+  IdlAccounts,
+  IdlTypes,
+  Program,
+  BorshAccountsCoder,
+} from "@coral-xyz/anchor";
 import { MessageBuffer } from "../target/types/message_buffer";
 import { MockCpiCaller } from "../target/types/mock_cpi_caller";
 import lumina from "@lumina-dev/test";
@@ -9,20 +14,17 @@ import bs58 from "bs58";
 
 // Enables tool that runs in local browser for easier debugging of
 // transactions in this test -  https://lumina.fyi/debug
-lumina();
+// lumina();
 
 const messageBufferProgram = anchor.workspace
   .MessageBuffer as Program<MessageBuffer>;
 const mockCpiProg = anchor.workspace.MockCpiCaller as Program<MockCpiCaller>;
-let whitelistAuthority = anchor.web3.Keypair.generate();
+let whitelistAdmin = anchor.web3.Keypair.generate();
+
 const [mockCpiCallerAuth] = anchor.web3.PublicKey.findProgramAddressSync(
   [messageBufferProgram.programId.toBuffer(), Buffer.from("cpi")],
   mockCpiProg.programId
 );
-const [fundPda] = anchor.web3.PublicKey.findProgramAddressSync(
-  [Buffer.from("fund")],
-  messageBufferProgram.programId
-);
 
 const pythPriceAccountId = new anchor.BN(1);
 const addPriceParams = {
@@ -41,12 +43,41 @@ const [pythPriceAccountPk] = anchor.web3.PublicKey.findProgramAddressSync(
   mockCpiProg.programId
 );
 const MESSAGE = Buffer.from("message");
-const [accumulatorPdaKey] = anchor.web3.PublicKey.findProgramAddressSync(
-  [mockCpiCallerAuth.toBuffer(), MESSAGE, pythPriceAccountPk.toBuffer()],
-  messageBufferProgram.programId
+const [accumulatorPdaKey, accumulatorPdaBump] =
+  anchor.web3.PublicKey.findProgramAddressSync(
+    [mockCpiCallerAuth.toBuffer(), MESSAGE, pythPriceAccountPk.toBuffer()],
+    messageBufferProgram.programId
+  );
+
+const pythPriceAccountId2 = new anchor.BN(2);
+const [pythPriceAccountPk2] = anchor.web3.PublicKey.findProgramAddressSync(
+  [
+    Buffer.from("pyth"),
+    Buffer.from("price"),
+    pythPriceAccountId2.toArrayLike(Buffer, "le", 8),
+  ],
+  mockCpiProg.programId
 );
 
+const [accumulatorPdaKey2, accumulatorPdaBump2] =
+  anchor.web3.PublicKey.findProgramAddressSync(
+    [mockCpiCallerAuth.toBuffer(), MESSAGE, pythPriceAccountPk2.toBuffer()],
+    messageBufferProgram.programId
+  );
+
+const accumulatorPdaMeta2 = {
+  pubkey: accumulatorPdaKey2,
+  isSigner: false,
+  isWritable: true,
+};
+
+console.log("3");
+
 let fundBalance = 100 * anchor.web3.LAMPORTS_PER_SOL;
+
+const discriminator = BorshAccountsCoder.accountDiscriminator("MessageBuffer");
+const messageBufferDiscriminator = bs58.encode(discriminator);
+
 describe("accumulator_updater", () => {
   // Configure the client to use the local cluster.
   let provider = anchor.AnchorProvider.env();
@@ -58,14 +89,26 @@ describe("accumulator_updater", () => {
       messageBufferProgram.programId
     );
 
-  before("transfer lamports to the fund", async () => {
-    await provider.connection.requestAirdrop(fundPda, fundBalance);
+  before("transfer lamports to needed accounts", async () => {
+    const airdropTxnSig = await provider.connection.requestAirdrop(
+      whitelistAdmin.publicKey,
+      fundBalance
+    );
+
+    await provider.connection.confirmTransaction({
+      signature: airdropTxnSig,
+      ...(await provider.connection.getLatestBlockhash()),
+    });
+    const whitelistAuthorityBalance = await provider.connection.getBalance(
+      whitelistAdmin.publicKey
+    );
+    assert.isTrue(whitelistAuthorityBalance === fundBalance);
   });
 
   it("Is initialized!", async () => {
     // Add your test here.
     const tx = await messageBufferProgram.methods
-      .initialize(whitelistAuthority.publicKey)
+      .initialize(whitelistAdmin.publicKey)
       .accounts({})
       .rpc();
     console.log("Your transaction signature", tx);
@@ -74,7 +117,7 @@ describe("accumulator_updater", () => {
       whitelistPubkey
     );
     assert.strictEqual(whitelist.bump, whitelistBump);
-    assert.isTrue(whitelist.authority.equals(whitelistAuthority.publicKey));
+    assert.isTrue(whitelist.admin.equals(whitelistAdmin.publicKey));
     console.info(`whitelist: ${JSON.stringify(whitelist)}`);
   });
 
@@ -83,9 +126,9 @@ describe("accumulator_updater", () => {
     await messageBufferProgram.methods
       .setAllowedPrograms(allowedProgramAuthorities)
       .accounts({
-        authority: whitelistAuthority.publicKey,
+        admin: whitelistAdmin.publicKey,
       })
-      .signers([whitelistAuthority])
+      .signers([whitelistAdmin])
       .rpc();
     const whitelist = await messageBufferProgram.account.whitelist.fetch(
       whitelistPubkey
@@ -100,29 +143,131 @@ describe("accumulator_updater", () => {
     );
   });
 
+  it("Creates a buffer", async () => {
+    const accumulatorPdaMetas = [
+      {
+        pubkey: accumulatorPdaKey,
+        isSigner: false,
+        isWritable: true,
+      },
+    ];
+
+    await messageBufferProgram.methods
+      .createBuffer(mockCpiCallerAuth, pythPriceAccountPk, 1024 * 8)
+      .accounts({
+        whitelist: whitelistPubkey,
+        admin: whitelistAdmin.publicKey,
+        systemProgram: anchor.web3.SystemProgram.programId,
+      })
+      .signers([whitelistAdmin])
+      .remainingAccounts(accumulatorPdaMetas)
+      .rpc({ skipPreflight: true });
+
+    const messageBufferAccountData = await getMessageBuffer(
+      provider.connection,
+      accumulatorPdaKey
+    );
+    const messageBufferHeader = deserializeMessageBufferHeader(
+      messageBufferProgram,
+      messageBufferAccountData
+    );
+    assert.equal(messageBufferHeader.version, 1);
+    assert.equal(messageBufferHeader.bump, accumulatorPdaBump);
+  });
+
+  it("Creates a buffer even if the account already has lamports", async () => {
+    const minimumEmptyRent =
+      await provider.connection.getMinimumBalanceForRentExemption(0);
+    await provider.sendAndConfirm(
+      (() => {
+        const tx = new anchor.web3.Transaction();
+        tx.add(
+          anchor.web3.SystemProgram.transfer({
+            fromPubkey: provider.wallet.publicKey,
+            toPubkey: accumulatorPdaKey2,
+            lamports: minimumEmptyRent,
+          })
+        );
+        return tx;
+      })()
+    );
+
+    const accumulatorPdaBalance = await provider.connection.getBalance(
+      accumulatorPdaKey2
+    );
+    console.log(`accumulatorPdaBalance: ${accumulatorPdaBalance}`);
+    assert.isTrue(accumulatorPdaBalance === minimumEmptyRent);
+
+    await messageBufferProgram.methods
+      .createBuffer(mockCpiCallerAuth, pythPriceAccountPk2, 1000)
+      .accounts({
+        whitelist: whitelistPubkey,
+        admin: whitelistAdmin.publicKey,
+        systemProgram: anchor.web3.SystemProgram.programId,
+      })
+      .signers([whitelistAdmin])
+      .remainingAccounts([accumulatorPdaMeta2])
+      .rpc({ skipPreflight: true });
+
+    const messageBufferAccountData = await getMessageBuffer(
+      provider.connection,
+      accumulatorPdaKey2
+    );
+
+    const minimumMessageBufferRent =
+      await provider.connection.getMinimumBalanceForRentExemption(
+        messageBufferAccountData.length
+      );
+    const accumulatorPdaBalanceAfter = await provider.connection.getBalance(
+      accumulatorPdaKey2
+    );
+    assert.isTrue(accumulatorPdaBalanceAfter === minimumMessageBufferRent);
+    const messageBufferHeader = deserializeMessageBufferHeader(
+      messageBufferProgram,
+      messageBufferAccountData
+    );
+
+    console.log(`header: ${JSON.stringify(messageBufferHeader)}`);
+    assert.equal(messageBufferHeader.bump, accumulatorPdaBump2);
+    assert.equal(messageBufferAccountData[8], accumulatorPdaBump2);
+
+    assert.equal(messageBufferHeader.version, 1);
+  });
+
   it("Updates the whitelist authority", async () => {
-    const newWhitelistAuthority = anchor.web3.Keypair.generate();
+    const newWhitelistAdmin = anchor.web3.Keypair.generate();
     await messageBufferProgram.methods
-      .updateWhitelistAuthority(newWhitelistAuthority.publicKey)
+      .updateWhitelistAdmin(newWhitelistAdmin.publicKey)
       .accounts({
-        authority: whitelistAuthority.publicKey,
+        admin: whitelistAdmin.publicKey,
       })
-      .signers([whitelistAuthority])
+      .signers([whitelistAdmin])
       .rpc();
 
-    const whitelist = await messageBufferProgram.account.whitelist.fetch(
+    let whitelist = await messageBufferProgram.account.whitelist.fetch(
       whitelistPubkey
     );
-    assert.isTrue(whitelist.authority.equals(newWhitelistAuthority.publicKey));
+    assert.isTrue(whitelist.admin.equals(newWhitelistAdmin.publicKey));
+
+    // swap back to original authority
+    await messageBufferProgram.methods
+      .updateWhitelistAdmin(whitelistAdmin.publicKey)
+      .accounts({
+        admin: newWhitelistAdmin.publicKey,
+      })
+      .signers([newWhitelistAdmin])
+      .rpc();
 
-    whitelistAuthority = newWhitelistAuthority;
+    whitelist = await messageBufferProgram.account.whitelist.fetch(
+      whitelistPubkey
+    );
+    assert.isTrue(whitelist.admin.equals(whitelistAdmin.publicKey));
   });
 
   it("Mock CPI program - AddPrice", async () => {
     const mockCpiCallerAddPriceTxPubkeys = await mockCpiProg.methods
       .addPrice(addPriceParams)
       .accounts({
-        fund: fundPda,
         systemProgram: anchor.web3.SystemProgram.programId,
         auth: mockCpiCallerAuth,
         accumulatorWhitelist: whitelistPubkey,
@@ -183,10 +328,14 @@ describe("accumulator_updater", () => {
       mockCpiCallerAddPriceTxPubkeys.pythPriceAccount
     );
 
-    const messageBuffer =
-      await messageBufferProgram.account.messageBuffer.fetch(accumulatorPdaKey);
+    const messageBufferAccount = await provider.connection.getAccountInfo(
+      accumulatorPdaKey
+    );
 
-    const accumulatorPriceMessages = parseMessageBuffer(messageBuffer);
+    const accumulatorPriceMessages = parseMessageBuffer(
+      messageBufferProgram,
+      messageBufferAccount.data
+    );
 
     console.log(
       `accumulatorPriceMessages: ${JSON.stringify(
@@ -200,29 +349,11 @@ describe("accumulator_updater", () => {
       assert.isTrue(pm.price.eq(addPriceParams.price));
       assert.isTrue(pm.priceExpo.eq(addPriceParams.priceExpo));
     });
-
-    const fundBalanceAfter = await provider.connection.getBalance(fundPda);
-    assert.isTrue(fundBalance > fundBalanceAfter);
   });
 
   it("Fetches MessageBuffer using getProgramAccounts with discriminator", async () => {
-    let discriminator =
-      BorshAccountsCoder.accountDiscriminator("MessageBuffer");
-    let messageBufferDiscriminator = bs58.encode(discriminator);
-
-    // fetch using `getProgramAccounts` and memcmp filter
-    const messageBufferAccounts = await provider.connection.getProgramAccounts(
-      messageBufferProgram.programId,
-      {
-        filters: [
-          {
-            memcmp: {
-              offset: 0,
-              bytes: messageBufferDiscriminator,
-            },
-          },
-        ],
-      }
+    const messageBufferAccounts = await getProgramAccountsForMessageBuffers(
+      provider.connection
     );
     const msgBufferAcctKeys = messageBufferAccounts.map((ai) =>
       ai.pubkey.toString()
@@ -231,7 +362,7 @@ describe("accumulator_updater", () => {
       `messageBufferAccounts: ${JSON.stringify(msgBufferAcctKeys, null, 2)}`
     );
 
-    assert.isTrue(messageBufferAccounts.length === 1);
+    assert.isTrue(messageBufferAccounts.length === 2);
     msgBufferAcctKeys.includes(accumulatorPdaKey.toString());
   });
 
@@ -250,7 +381,6 @@ describe("accumulator_updater", () => {
     await mockCpiProg.methods
       .updatePrice(updatePriceParams)
       .accounts({
-        fund: fundPda,
         pythPriceAccount: pythPriceAccountPk,
         auth: mockCpiCallerAuth,
         accumulatorWhitelist: whitelistPubkey,
@@ -271,11 +401,16 @@ describe("accumulator_updater", () => {
     assert.isTrue(pythPriceAccount.priceExpo.eq(updatePriceParams.priceExpo));
     assert.isTrue(pythPriceAccount.ema.eq(updatePriceParams.ema));
     assert.isTrue(pythPriceAccount.emaExpo.eq(updatePriceParams.emaExpo));
-    const messageBuffer =
-      await messageBufferProgram.account.messageBuffer.fetch(
-        accumulatorPdaMeta.pubkey
-      );
-    const updatedAccumulatorPriceMessages = parseMessageBuffer(messageBuffer);
+
+    const messageBufferAccountData = await getMessageBuffer(
+      provider.connection,
+      accumulatorPdaKey
+    );
+
+    const updatedAccumulatorPriceMessages = parseMessageBuffer(
+      messageBufferProgram,
+      messageBufferAccountData
+    );
 
     console.log(
       `updatedAccumulatorPriceMessages: ${JSON.stringify(
@@ -299,11 +434,12 @@ describe("accumulator_updater", () => {
       let testCase = testCases[i];
       console.info(`testCase: ${testCase}`);
       const updatePriceParams = {
-        price: new anchor.BN(10 * i + 5),
-        priceExpo: new anchor.BN(10 & (i + 6)),
+        price: new anchor.BN(10 * (i + 5)),
+        priceExpo: new anchor.BN(10 * (i + 6)),
         ema: new anchor.BN(10 * i + 7),
         emaExpo: new anchor.BN(10 * i + 8),
       };
+      console.log(`updatePriceParams: ${JSON.stringify(updatePriceParams)}`);
 
       let accumulatorPdaMeta = getAccumulatorPdaMeta(
         mockCpiCallerAuth,
@@ -312,7 +448,6 @@ describe("accumulator_updater", () => {
       await mockCpiProg.methods
         .cpiMaxTest(updatePriceParams, testCase)
         .accounts({
-          fund: fundPda,
           pythPriceAccount: pythPriceAccountPk,
           auth: mockCpiCallerAuth,
           accumulatorWhitelist: whitelistPubkey,
@@ -333,28 +468,36 @@ describe("accumulator_updater", () => {
       assert.isTrue(pythPriceAccount.priceExpo.eq(updatePriceParams.priceExpo));
       assert.isTrue(pythPriceAccount.ema.eq(updatePriceParams.ema));
       assert.isTrue(pythPriceAccount.emaExpo.eq(updatePriceParams.emaExpo));
-      const messageBuffer =
-        await messageBufferProgram.account.messageBuffer.fetch(
-          accumulatorPdaMeta.pubkey
-        );
-      const updatedAccumulatorPriceMessages = parseMessageBuffer(messageBuffer);
 
-      console.log(
-        `updatedAccumulatorPriceMessages: ${JSON.stringify(
-          updatedAccumulatorPriceMessages,
-          null,
-          2
-        )}`
+      const messageBufferAccountData = await getMessageBuffer(
+        provider.connection,
+        accumulatorPdaKey
       );
-      updatedAccumulatorPriceMessages.forEach((pm) => {
-        assert.isTrue(pm.id.eq(addPriceParams.id));
-        assert.isTrue(pm.price.eq(updatePriceParams.price));
-        assert.isTrue(pm.priceExpo.eq(updatePriceParams.priceExpo));
-      });
+
+      const messageBufferHeader = deserializeMessageBufferHeader(
+        messageBufferProgram,
+        messageBufferAccountData
+      );
+
+      console.log(`header: ${JSON.stringify(messageBufferHeader)}`);
+      let mockCpiMessageHeaderLen = 7;
+
+      let currentExpectedOffset = 0;
+      for (let j = 0; j < testCase.length; j++) {
+        currentExpectedOffset += testCase[j];
+        currentExpectedOffset += mockCpiMessageHeaderLen;
+        console.log(`
+          header.endOffsets[${j}]: ${messageBufferHeader.endOffsets[j]}
+          currentExpectedOffset: ${currentExpectedOffset}
+        `);
+        assert.isTrue(
+          messageBufferHeader.endOffsets[j] === currentExpectedOffset
+        );
+      }
     }
   });
 
-  it("Mock CPI Program - CPI Max Test Fail", async () => {
+  it("Mock CPI Program - Exceed CPI Max Test ", async () => {
     // with loosen CPI feature activated, max cpi instruction size len is 10KB
     let testCases = [[1024, 2048, 4096, 8192]];
     // for (let i = 1; i < 8; i++) {
@@ -363,7 +506,7 @@ describe("accumulator_updater", () => {
       console.info(`testCase: ${testCase}`);
       const updatePriceParams = {
         price: new anchor.BN(10 * i + 5),
-        priceExpo: new anchor.BN(10 & (i + 6)),
+        priceExpo: new anchor.BN(10 * (i + 6)),
         ema: new anchor.BN(10 * i + 7),
         emaExpo: new anchor.BN(10 * i + 8),
       };
@@ -377,7 +520,6 @@ describe("accumulator_updater", () => {
         await mockCpiProg.methods
           .cpiMaxTest(updatePriceParams, testCase)
           .accounts({
-            fund: fundPda,
             pythPriceAccount: pythPriceAccountPk,
             auth: mockCpiCallerAuth,
             accumulatorWhitelist: whitelistPubkey,
@@ -396,6 +538,192 @@ describe("accumulator_updater", () => {
       assert.ok(errorThrown);
     }
   });
+
+  it("Resizes a buffer to a valid larger size", async () => {
+    const messageBufferAccountDataBefore = await getMessageBuffer(
+      provider.connection,
+      accumulatorPdaKey2
+    );
+    const messageBufferAccountDataLenBefore =
+      messageBufferAccountDataBefore.length;
+
+    // check that header is stil the same as before
+    const messageBufferHeaderBefore = deserializeMessageBufferHeader(
+      messageBufferProgram,
+      messageBufferAccountDataBefore
+    );
+
+    const whitelistAuthorityBalanceBefore =
+      await provider.connection.getBalance(whitelistAdmin.publicKey);
+    console.log(
+      `whitelistAuthorityBalance: ${whitelistAuthorityBalanceBefore}`
+    );
+    const targetSize = 10 * 1024;
+    await messageBufferProgram.methods
+      .resizeBuffer(
+        mockCpiCallerAuth,
+        pythPriceAccountPk2,
+        accumulatorPdaBump2,
+        targetSize
+      )
+      .accounts({
+        whitelist: whitelistPubkey,
+        admin: whitelistAdmin.publicKey,
+        systemProgram: anchor.web3.SystemProgram.programId,
+      })
+      .signers([whitelistAdmin])
+      .remainingAccounts([accumulatorPdaMeta2])
+      .rpc({ skipPreflight: true });
+
+    const whitelistAuthorityBalanceAfter = await provider.connection.getBalance(
+      whitelistAdmin.publicKey
+    );
+    assert.isTrue(
+      whitelistAuthorityBalanceAfter < whitelistAuthorityBalanceBefore
+    );
+
+    const messageBufferAccountData = await getMessageBuffer(
+      provider.connection,
+      accumulatorPdaKey2
+    );
+    assert.equal(messageBufferAccountData.length, targetSize);
+
+    // check that header is still the same as before
+    const messageBufferHeader = deserializeMessageBufferHeader(
+      messageBufferProgram,
+      messageBufferAccountData
+    );
+    assert.deepEqual(
+      messageBufferHeader.endOffsets,
+      messageBufferHeaderBefore.endOffsets
+    );
+    assert.deepEqual(
+      messageBufferAccountData.subarray(0, messageBufferAccountDataLenBefore),
+      messageBufferAccountDataBefore
+    );
+  });
+
+  it("Resizes a buffer to a smaller size", async () => {
+    const targetSize = 4 * 1024;
+    await messageBufferProgram.methods
+      .resizeBuffer(
+        mockCpiCallerAuth,
+        pythPriceAccountPk2,
+        accumulatorPdaBump2,
+        targetSize
+      )
+      .accounts({
+        whitelist: whitelistPubkey,
+        admin: whitelistAdmin.publicKey,
+        systemProgram: anchor.web3.SystemProgram.programId,
+      })
+      .signers([whitelistAdmin])
+      .remainingAccounts([accumulatorPdaMeta2])
+      .rpc({ skipPreflight: true });
+
+    const messageBufferAccountData = await getMessageBuffer(
+      provider.connection,
+      accumulatorPdaKey2
+    );
+    assert.equal(messageBufferAccountData.length, targetSize);
+  });
+
+  it("Fails to resize buffers to invalid sizes", async () => {
+    // resize more than 10KB in one txn and less than header.header_len should be fail
+    const testCases = [20 * 1024, 2];
+    for (const testCase of testCases) {
+      let errorThrown = false;
+      try {
+        await messageBufferProgram.methods
+          .resizeBuffer(
+            mockCpiCallerAuth,
+            pythPriceAccountPk2,
+            accumulatorPdaBump2,
+            testCase
+          )
+          .accounts({
+            whitelist: whitelistPubkey,
+            admin: whitelistAdmin.publicKey,
+            systemProgram: anchor.web3.SystemProgram.programId,
+          })
+          .signers([whitelistAdmin])
+          .remainingAccounts([accumulatorPdaMeta2])
+          .rpc({ skipPreflight: true });
+      } catch (_err) {
+        errorThrown = true;
+      }
+      assert.ok(errorThrown);
+    }
+  });
+
+  it("Deletes a buffer", async () => {
+    await messageBufferProgram.methods
+      .deleteBuffer(mockCpiCallerAuth, pythPriceAccountPk2, accumulatorPdaBump2)
+      .accounts({
+        whitelist: whitelistPubkey,
+        admin: whitelistAdmin.publicKey,
+      })
+      .signers([whitelistAdmin])
+      .remainingAccounts([accumulatorPdaMeta2])
+      .rpc({ skipPreflight: true });
+
+    const messageBufferAccountData = await getMessageBuffer(
+      provider.connection,
+      accumulatorPdaKey2
+    );
+
+    if (messageBufferAccountData != null) {
+      assert.fail("messageBufferAccountData should be null");
+    }
+
+    const messageBufferAccounts = await getProgramAccountsForMessageBuffers(
+      provider.connection
+    );
+    assert.equal(messageBufferAccounts.length, 1);
+
+    assert.isFalse(
+      messageBufferAccounts
+        .map((a) => a.pubkey.toString())
+        .includes(accumulatorPdaKey2.toString())
+    );
+  });
+
+  it("Can recreate a buffer after it's been deleted", async () => {
+    await messageBufferProgram.methods
+      .createBuffer(mockCpiCallerAuth, pythPriceAccountPk2, 1000)
+      .accounts({
+        whitelist: whitelistPubkey,
+        admin: whitelistAdmin.publicKey,
+        systemProgram: anchor.web3.SystemProgram.programId,
+      })
+      .signers([whitelistAdmin])
+      .remainingAccounts([accumulatorPdaMeta2])
+      .rpc({ skipPreflight: true });
+
+    const messageBufferAccountData = await getMessageBuffer(
+      provider.connection,
+      accumulatorPdaKey2
+    );
+
+    const minimumMessageBufferRent =
+      await provider.connection.getMinimumBalanceForRentExemption(
+        messageBufferAccountData.length
+      );
+    const accumulatorPdaBalanceAfter = await provider.connection.getBalance(
+      accumulatorPdaKey2
+    );
+    assert.isTrue(accumulatorPdaBalanceAfter === minimumMessageBufferRent);
+    const messageBufferHeader = deserializeMessageBufferHeader(
+      messageBufferProgram,
+      messageBufferAccountData
+    );
+
+    console.log(`header: ${JSON.stringify(messageBufferHeader)}`);
+    assert.equal(messageBufferHeader.bump, accumulatorPdaBump2);
+    assert.equal(messageBufferAccountData[8], accumulatorPdaBump2);
+
+    assert.equal(messageBufferHeader.version, 1);
+  });
 });
 
 export const getAccumulatorPdaMeta = (
@@ -413,23 +741,35 @@ export const getAccumulatorPdaMeta = (
   };
 };
 
-type BufferHeader = IdlTypes<MessageBuffer>["BufferHeader"];
+async function getMessageBuffer(
+  connection: anchor.web3.Connection,
+  accountKey: anchor.web3.PublicKey
+): Promise<Buffer | null> {
+  let accountInfo = await connection.getAccountInfo(accountKey);
+  return accountInfo ? accountInfo.data : null;
+}
 
 // Parses MessageBuffer.data into a PriceAccount or PriceOnly object based on the
 // accountType and accountSchema.
-function parseMessageBuffer({
-  header,
-  messages,
-}: {
-  header: BufferHeader;
-  messages: number[];
-}): AccumulatorPriceMessage[] {
+function parseMessageBuffer(
+  messageBufferProgram: Program<MessageBuffer>,
+  accountData: Buffer
+): AccumulatorPriceMessage[] {
+  const msgBufferHeader = deserializeMessageBufferHeader(
+    messageBufferProgram,
+    accountData
+  );
+
   const accumulatorMessages = [];
-  let dataBuffer = Buffer.from(messages);
+  // let dataBuffer = Buffer.from(messages);
 
+  let dataBuffer = accountData.subarray(
+    msgBufferHeader.headerLen,
+    accountData.length
+  );
   let start = 0;
-  for (let i = 0; i < header.endOffsets.length; i++) {
-    const endOffset = header.endOffsets[i];
+  for (let i = 0; i < msgBufferHeader.endOffsets.length; i++) {
+    const endOffset = msgBufferHeader.endOffsets[i];
 
     if (endOffset == 0) {
       console.log(`endOffset = 0. breaking`);
@@ -459,12 +799,22 @@ type MessageHeader = {
   size: number;
 };
 
-type MessageBuffer = {
+type MessageBufferType = {
   header: MessageHeader;
   data: Buffer;
 };
 
-function parseMessageBytes(data: Buffer): MessageBuffer {
+function deserializeMessageBufferHeader(
+  messageBufferProgram: Program<MessageBuffer>,
+  accountData: Buffer
+): IdlAccounts<MessageBuffer>["messageBuffer"] {
+  return messageBufferProgram.coder.accounts.decode(
+    "MessageBuffer",
+    accountData
+  );
+}
+
+function parseMessageBytes(data: Buffer): MessageBufferType {
   let offset = 0;
 
   const schema = data.readInt8(offset);
@@ -488,8 +838,6 @@ function parseMessageBytes(data: Buffer): MessageBuffer {
   };
 }
 
-//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 = {
@@ -522,3 +870,19 @@ function parseCompactPriceMessage(data: Uint8Array): CompactPriceMessage {
     priceExpo: new anchor.BN(data.subarray(16, 24), "be"),
   };
 }
+
+// fetch MessageBuffer accounts using `getProgramAccounts` and memcmp filter
+async function getProgramAccountsForMessageBuffers(
+  connection: anchor.web3.Connection
+) {
+  return await connection.getProgramAccounts(messageBufferProgram.programId, {
+    filters: [
+      {
+        memcmp: {
+          offset: 0,
+          bytes: messageBufferDiscriminator,
+        },
+      },
+    ],
+  });
+}