Selaa lähdekoodia

[message-buffer 13/x] Rust Integration Tests (#794)

* test(message-buffer): add rust integration tests

add rust-toolchain.toml to pin rust version, add integration tests, update cpi caller auth seeds

* refactor(message-buffer): remove unused test ix

* chore(message-buffer): clean up

* refactor(message-buffer): simple refactor

* test(message-buffer): refactor integration test structure

* refactor(message-buffer): rename

* fix(message-buffer): fix min size check when shrinking msg buffer

* chore(message-buffer): cleanup

* fix(message-buffer): resize borrow bug fix

* test(message-buffer): refactor test util methods into MessageBufferTestContext for less duplication

* test(message-buffer): resolve merge conflicts from repo restructure

* chore(message-buffer): delete commented out code
swimricky 2 vuotta sitten
vanhempi
sitoutus
2e32a22725
25 muutettua tiedostoa jossa 7268 lisäystä ja 717 poistoa
  1. 1 1
      .pre-commit-config.yaml
  2. 6077 648
      pythnet/message_buffer/Cargo.lock
  3. 1 4
      pythnet/message_buffer/programs/message_buffer/src/instructions/delete_buffer.rs
  4. 1 2
      pythnet/message_buffer/programs/message_buffer/src/instructions/put_all.rs
  5. 23 8
      pythnet/message_buffer/programs/message_buffer/src/instructions/resize_buffer.rs
  6. 3 3
      pythnet/message_buffer/programs/message_buffer/src/lib.rs
  7. 0 1
      pythnet/message_buffer/programs/message_buffer/src/state/message_buffer.rs
  8. 2 0
      pythnet/message_buffer/programs/message_buffer/src/state/whitelist.rs
  9. 9 0
      pythnet/message_buffer/programs/mock-cpi-caller/Cargo.toml
  10. 7 7
      pythnet/message_buffer/programs/mock-cpi-caller/src/instructions/add_price.rs
  11. 1 1
      pythnet/message_buffer/programs/mock-cpi-caller/src/instructions/cpi_max_test.rs
  12. 1 1
      pythnet/message_buffer/programs/mock-cpi-caller/src/instructions/mod.rs
  13. 7 7
      pythnet/message_buffer/programs/mock-cpi-caller/src/instructions/update_price.rs
  14. 2 2
      pythnet/message_buffer/programs/mock-cpi-caller/src/message/price.rs
  15. 20 0
      pythnet/message_buffer/programs/mock-cpi-caller/tests/cases/mod.rs
  16. 106 0
      pythnet/message_buffer/programs/mock-cpi-caller/tests/cases/test_create_buffer.rs
  17. 39 0
      pythnet/message_buffer/programs/mock-cpi-caller/tests/cases/test_delete_buffer.rs
  18. 17 0
      pythnet/message_buffer/programs/mock-cpi-caller/tests/cases/test_initialize.rs
  19. 54 0
      pythnet/message_buffer/programs/mock-cpi-caller/tests/cases/test_put_all.rs
  20. 156 0
      pythnet/message_buffer/programs/mock-cpi-caller/tests/cases/test_resize_buffer.rs
  21. 22 0
      pythnet/message_buffer/programs/mock-cpi-caller/tests/cases/test_set_allowed_programs.rs
  22. 681 0
      pythnet/message_buffer/programs/mock-cpi-caller/tests/program_test/mod.rs
  23. 4 0
      pythnet/message_buffer/programs/mock-cpi-caller/tests/test_all.rs
  24. 2 0
      pythnet/message_buffer/rust-toolchain.toml
  25. 32 32
      pythnet/message_buffer/tests/message_buffer.ts

+ 1 - 1
.pre-commit-config.yaml

@@ -75,7 +75,7 @@ repos:
       - id: cargo-clippy-message-buffer
         name: Cargo clippy for message buffer contract
         language: "rust"
-        entry: cargo +nightly clippy --manifest-path ./pythnet/message_buffer/Cargo.toml --tests --fix --allow-dirty --allow-staged -- -D warnings
+        entry: cargo +nightly clippy --manifest-path ./pythnet/message_buffer/Cargo.toml --tests --fix --allow-dirty --allow-staged --features test-bpf -- -D warnings
         pass_filenames: false
         files: message_buffer
       # Hooks for solana receiver contract

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 6077 - 648
pythnet/message_buffer/Cargo.lock


+ 1 - 4
pythnet/message_buffer/programs/message_buffer/src/instructions/delete_buffer.rs

@@ -40,10 +40,7 @@ pub fn delete_buffer<'info>(
         expected_key,
         MessageBufferError::InvalidPDA
     );
-    let loader = AccountLoader::<MessageBuffer>::try_from_unchecked(
-        &crate::ID,
-        message_buffer_account_info,
-    )?;
+    let loader = AccountLoader::<MessageBuffer>::try_from(message_buffer_account_info)?;
     loader.close(ctx.accounts.admin.to_account_info())?;
     Ok(())
 }

+ 1 - 2
pythnet/message_buffer/programs/message_buffer/src/instructions/put_all.rs

@@ -39,8 +39,7 @@ pub fn put_all<'info>(
     let (num_msgs, num_bytes) = message_buffer.put_all_in_buffer(body_bytes, &messages);
 
     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);
+        msg!("unable to fit all messages in MessageBuffer account. Wrote {}/{} messages and {} bytes", num_msgs, messages.len(), num_bytes);
     }
 
     Ok(())

+ 23 - 8
pythnet/message_buffer/programs/message_buffer/src/instructions/resize_buffer.rs

@@ -31,13 +31,11 @@ pub fn resize_buffer<'info>(
         .is_allowed_program_auth(&allowed_program_auth)?;
     MessageBuffer::check_discriminator(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());
+
+    let current_account_size = message_buffer_account_info.data_len();
+    let target_size_delta = target_size.saturating_sub(current_account_size);
     require_gte!(
         MAX_PERMITTED_DATA_INCREASE,
         target_size_delta,
@@ -61,10 +59,10 @@ pub fn resize_buffer<'info>(
         MessageBufferError::InvalidPDA
     );
 
-    // allow for delta == 0 in case Rent requirements have changed
+    // allow for target_size == account_size in case Rent requirements have changed
     // and additional lamports need to be transferred.
     // the realloc step will be a no-op in this case.
-    if target_size_delta >= 0 {
+    if target_size >= current_account_size {
         let target_rent = Rent::get()?.minimum_balance(target_size);
         if message_buffer_account_info.lamports() < target_rent {
             system_program::transfer(
@@ -82,6 +80,23 @@ pub fn resize_buffer<'info>(
             .realloc(target_size, false)
             .map_err(|_| MessageBufferError::ReallocFailed)?;
     } else {
+        // Check that account doesn't get resized to smaller than the amount of
+        // data it is currently holding (if any)
+        {
+            let account_data = &message_buffer_account_info.try_borrow_data()?;
+            let header_end_index = std::mem::size_of::<MessageBuffer>() + 8;
+            let (header_bytes, _) = account_data.split_at(header_end_index);
+            let message_buffer: &MessageBuffer = bytemuck::from_bytes(&header_bytes[8..]);
+            let max_end_offset = message_buffer.end_offsets.iter().max().unwrap();
+            let minimum_size = max_end_offset + message_buffer.header_len;
+            require_gte!(
+                target_size,
+                minimum_size as usize,
+                MessageBufferError::MessageBufferTooSmall
+            );
+        }
+
+
         // Not transferring excess lamports back to admin.
         // Account will retain more lamports than necessary.
         message_buffer_account_info.realloc(target_size, false)?;

+ 3 - 3
pythnet/message_buffer/programs/message_buffer/src/lib.rs

@@ -1,4 +1,4 @@
-mod instructions;
+pub mod instructions;
 mod macros;
 mod state;
 
@@ -158,7 +158,7 @@ pub struct Initialize<'info> {
         payer = payer,
         seeds = [b"message".as_ref(), b"whitelist".as_ref()],
         bump,
-        space = 8 + Whitelist::INIT_SPACE
+        space = 8 + Whitelist::INIT_SPACE,
     )]
     pub whitelist:      Account<'info, Whitelist>,
     pub system_program: Program<'info, System>,
@@ -203,7 +203,7 @@ pub enum MessageBufferError {
     CurrentDataLengthExceeded,
     #[msg("Message Buffer not provided")]
     MessageBufferNotProvided,
-    #[msg("Message Buffer is not sufficiently large")]
+    #[msg("Message Buffer target size is not sufficiently large")]
     MessageBufferTooSmall,
     #[msg("Fund Bump not found")]
     FundBumpNotFound,

+ 0 - 1
pythnet/message_buffer/programs/message_buffer/src/state/message_buffer.rs

@@ -100,7 +100,6 @@ impl MessageBuffer {
             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());

+ 2 - 0
pythnet/message_buffer/programs/message_buffer/src/state/whitelist.rs

@@ -11,6 +11,8 @@ use {
 pub struct Whitelist {
     pub bump:             u8,
     pub admin:            Pubkey,
+    // This is used by the `#[derive(InitSpace)]`
+    // to determine initial account size
     #[max_len(32)]
     pub allowed_programs: Vec<Pubkey>,
 }

+ 9 - 0
pythnet/message_buffer/programs/mock-cpi-caller/Cargo.toml

@@ -14,9 +14,18 @@ no-idl = []
 no-log-ix-name = []
 cpi = ["no-entrypoint"]
 default = []
+test-bpf = []
 
 [dependencies]
 anchor-lang = "0.27.0"
 message_buffer = { path = "../message_buffer", features = ["cpi"] }
 # needed for the new #[account(zero_copy)] in anchor 0.27.0
 bytemuck = { version = "1.4.0", features = ["derive", "min_const_generics"]}
+
+[dev-dependencies]
+solana-sdk = "1.14.16"
+solana-program-test = "1.14.16"
+solana-validator = "1.14.16"
+assert_matches = "1.4.0"
+solana-logger = "1.14.16"
+byteorder = "1.4.3"

+ 7 - 7
pythnet/message_buffer/programs/mock-cpi-caller/src/instructions/add_price.rs

@@ -3,7 +3,7 @@ use {
         instructions::{
             sighash,
             ACCUMULATOR_UPDATER_IX_NAME,
-            CPI,
+            UPD_PRICE_WRITE,
         },
         message::{
             get_schemas,
@@ -51,13 +51,13 @@ pub fn add_price<'info>(
     // Note: normally pyth oracle add_price wouldn't call emit_accumulator_inputs
     // since add_price doesn't actually add/update any price data we would
     // want included in the accumulator anyways. This is just for testing
-    AddPrice::emit_accumulator_inputs(ctx, inputs)
+    AddPrice::emit_messages(ctx, inputs)
 }
 
 
 impl<'info> AddPrice<'info> {
-    /// Invoke accumulator-updater emit-inputs ix cpi call using solana
-    pub fn emit_accumulator_inputs(
+    /// Invoke message_buffer::put_all ix cpi call using solana
+    pub fn emit_messages(
         ctx: Context<'_, '_, '_, 'info, AddPrice<'info>>,
         inputs: Vec<Vec<u8>>,
     ) -> anchor_lang::Result<()> {
@@ -89,8 +89,8 @@ impl<'info> AddPrice<'info> {
         // that won't be available in the oracle program
         let (_, bump) = Pubkey::find_program_address(
             &[
+                UPD_PRICE_WRITE.as_bytes(),
                 ctx.accounts.message_buffer_program.key().as_ref(),
-                CPI.as_bytes(),
             ],
             &crate::ID,
         );
@@ -98,8 +98,8 @@ impl<'info> AddPrice<'info> {
             &create_inputs_ix,
             account_infos,
             &[&[
+                UPD_PRICE_WRITE.as_bytes(),
                 ctx.accounts.message_buffer_program.key().as_ref(),
-                CPI.as_bytes(),
                 &[bump],
             ]],
         )?;
@@ -136,7 +136,7 @@ pub struct AddPrice<'info> {
     /// PDA representing this program's authority
     /// to call the accumulator program
     #[account(
-        seeds = [message_buffer_program.key().as_ref(), b"cpi".as_ref()],
+        seeds = [b"upd_price_write".as_ref(), message_buffer_program.key().as_ref()],
         owner = system_program::System::id(),
         bump,
     )]

+ 1 - 1
pythnet/message_buffer/programs/mock-cpi-caller/src/instructions/cpi_max_test.rs

@@ -33,5 +33,5 @@ pub fn cpi_max_test<'info>(
     msg!("input_len: {}", input_len);
 
 
-    UpdatePrice::emit_accumulator_inputs(ctx, inputs)
+    UpdatePrice::emit_messages(ctx, inputs)
 }

+ 1 - 1
pythnet/message_buffer/programs/mock-cpi-caller/src/instructions/mod.rs

@@ -24,4 +24,4 @@ pub fn sighash(namespace: &str, name: &str) -> [u8; 8] {
 }
 
 pub const ACCUMULATOR_UPDATER_IX_NAME: &str = "put_all";
-pub const CPI: &str = "cpi";
+pub const UPD_PRICE_WRITE: &str = "upd_price_write";

+ 7 - 7
pythnet/message_buffer/programs/mock-cpi-caller/src/instructions/update_price.rs

@@ -3,7 +3,7 @@ use {
         instructions::{
             sighash,
             ACCUMULATOR_UPDATER_IX_NAME,
-            CPI,
+            UPD_PRICE_WRITE,
         },
         message::{
             price::{
@@ -45,7 +45,7 @@ pub struct UpdatePrice<'info> {
     /// CHECK: whitelist
     pub accumulator_whitelist:  UncheckedAccount<'info>,
     #[account(
-        seeds = [message_buffer_program.key().as_ref(), b"cpi".as_ref()],
+        seeds = [b"upd_price_write".as_ref(), message_buffer_program.key().as_ref()],
         owner = system_program::System::id(),
         bump,
     )]
@@ -76,12 +76,12 @@ pub fn update_price<'info>(
     }
 
 
-    UpdatePrice::emit_accumulator_inputs(ctx, inputs)
+    UpdatePrice::emit_messages(ctx, inputs)
 }
 
 impl<'info> UpdatePrice<'info> {
-    /// Invoke accumulator-updater emit-inputs ix cpi call
-    pub fn emit_accumulator_inputs(
+    /// Invoke message_buffer::put_all ix cpi call
+    pub fn emit_messages(
         ctx: Context<'_, '_, '_, 'info, UpdatePrice<'info>>,
         values: Vec<Vec<u8>>,
     ) -> anchor_lang::Result<()> {
@@ -113,8 +113,8 @@ impl<'info> UpdatePrice<'info> {
         // that won't be available in the oracle program
         let (_, bump) = Pubkey::find_program_address(
             &[
+                UPD_PRICE_WRITE.as_bytes(),
                 ctx.accounts.message_buffer_program.key().as_ref(),
-                CPI.as_bytes(),
             ],
             &crate::ID,
         );
@@ -122,8 +122,8 @@ impl<'info> UpdatePrice<'info> {
             &update_inputs_ix,
             account_infos,
             &[&[
+                UPD_PRICE_WRITE.as_bytes(),
                 ctx.accounts.message_buffer_program.key().as_ref(),
-                CPI.as_bytes(),
                 &[bump],
             ]],
         )?;

+ 2 - 2
pythnet/message_buffer/programs/mock-cpi-caller/src/message/price.rs

@@ -35,9 +35,9 @@ impl MessageHeader {
 #[derive(Clone, Default, Debug, Eq, PartialEq)]
 pub struct CompactPriceMessage {
     pub header:     MessageHeader,
-    pub price_expo: u64,
-    pub price:      u64,
     pub id:         u64,
+    pub price:      u64,
+    pub price_expo: u64,
 }
 
 

+ 20 - 0
pythnet/message_buffer/programs/mock-cpi-caller/tests/cases/mod.rs

@@ -0,0 +1,20 @@
+pub use {
+    super::program_test::*,
+    anchor_lang::prelude::ProgramError,
+    message_buffer::MessageBufferError,
+    solana_program_test::*,
+    solana_sdk::{
+        pubkey::Pubkey,
+        signature::{
+            Keypair,
+            Signer,
+        },
+    },
+};
+
+mod test_create_buffer;
+mod test_delete_buffer;
+mod test_initialize;
+mod test_put_all;
+mod test_resize_buffer;
+mod test_set_allowed_programs;

+ 106 - 0
pythnet/message_buffer/programs/mock-cpi-caller/tests/cases/test_create_buffer.rs

@@ -0,0 +1,106 @@
+use super::*;
+
+#[tokio::test]
+async fn test_create_buffer() {
+    let mut context =
+        MessageBufferTestContext::initialize_with_default_test_allowed_programs(false)
+            .await
+            .unwrap();
+
+    let space = 1024;
+
+    let (msg_buffer_pda, msg_buffer_bump) = context
+        .create_buffer(MessageBufferTestContext::DEFAULT_TEST_PRICE_ID, space)
+        .await
+        .unwrap();
+
+
+    let msg_buffer_account_data = context
+        .fetch_msg_buffer_account_data(&msg_buffer_pda)
+        .await
+        .unwrap();
+
+
+    assert_eq!(msg_buffer_account_data.len(), space as usize);
+
+    let (bump, _version, _header_len, end_offsets) =
+        deserialize_msg_buffer_header(&msg_buffer_account_data);
+
+    assert_eq!(bump, msg_buffer_bump);
+    assert_eq!(end_offsets, [0u16; 255]);
+}
+
+#[tokio::test]
+async fn create_buffer_with_invalid_admin_should_fail() {
+    let mut context =
+        MessageBufferTestContext::initialize_with_default_test_allowed_programs(false)
+            .await
+            .unwrap();
+
+    let pyth_price_acct = MessageBufferTestContext::get_mock_pyth_price_account(
+        MessageBufferTestContext::DEFAULT_TEST_PRICE_ID,
+    );
+    let cpi_caller_auth = MessageBufferTestContext::get_mock_cpi_auth();
+    let space = 1024;
+    let invalid_admin = Keypair::new();
+    let (msg_buffer_pda, _) = find_msg_buffer_pda(cpi_caller_auth, pyth_price_acct);
+    let invalid_create_buffer_ix = create_msg_buffer_ix(
+        cpi_caller_auth,
+        pyth_price_acct,
+        space,
+        context.whitelist(),
+        invalid_admin.pubkey(),
+        msg_buffer_pda,
+    );
+
+    let res = context
+        .process_ixs(&[invalid_create_buffer_ix], vec![&invalid_admin])
+        .await;
+
+    assert!(res.is_err());
+
+    let err: ProgramError = res.unwrap_err().into();
+    // violates the whitelist has_one = admin constraint
+    assert_eq!(
+        err,
+        ProgramError::Custom(anchor_lang::error::ErrorCode::ConstraintHasOne.into())
+    )
+}
+
+#[tokio::test]
+async fn create_buffer_with_invalid_size_should_fail() {
+    let mut context =
+        MessageBufferTestContext::initialize_with_default_test_allowed_programs(false)
+            .await
+            .unwrap();
+
+
+    let pyth_price_acct = MessageBufferTestContext::get_mock_pyth_price_account(
+        MessageBufferTestContext::DEFAULT_TEST_PRICE_ID,
+    );
+    let cpi_caller_auth = MessageBufferTestContext::get_mock_cpi_auth();
+    let _whitelist = context.whitelist();
+    let admin = context.default_admin();
+
+    let (msg_buffer_pda, _) = find_msg_buffer_pda(cpi_caller_auth, pyth_price_acct);
+    let invalid_create_buffer_ix = create_msg_buffer_ix(
+        cpi_caller_auth,
+        pyth_price_acct,
+        1,
+        context.whitelist(),
+        admin.pubkey(),
+        msg_buffer_pda,
+    );
+
+    let res = context
+        .process_ixs(&[invalid_create_buffer_ix], vec![&admin])
+        .await;
+
+    assert!(res.is_err());
+
+    let err: ProgramError = res.unwrap_err().into();
+    assert_eq!(
+        err,
+        ProgramError::Custom(MessageBufferError::MessageBufferTooSmall.into())
+    );
+}

+ 39 - 0
pythnet/message_buffer/programs/mock-cpi-caller/tests/cases/test_delete_buffer.rs

@@ -0,0 +1,39 @@
+use super::*;
+
+#[tokio::test]
+async fn test_delete_buffer() {
+    let mut context = MessageBufferTestContext::initialize_with_default_test_buffer(
+        false,
+        MessageBufferTestContext::DEFAULT_TARGET_SIZE,
+    )
+    .await
+    .unwrap();
+
+    let payer = context.payer.pubkey();
+    let (msg_buffer_pda, _) = MessageBufferTestContext::default_msg_buffer();
+
+    let payer_lamports_before = context.get_balance(payer).await;
+
+    context
+        .delete_buffer(MessageBufferTestContext::DEFAULT_TEST_PRICE_ID)
+        .await
+        .unwrap();
+
+    let msg_buffer_account_data = context.fetch_msg_buffer_account_data(&msg_buffer_pda).await;
+    assert!(msg_buffer_account_data.is_none());
+
+    let payer_lamports_after = context.get_balance(payer).await;
+    assert!(payer_lamports_before < payer_lamports_after);
+}
+
+#[tokio::test]
+#[should_panic]
+async fn fail_delete_buffer_invalid_admin() {
+    panic!()
+}
+
+#[tokio::test]
+#[should_panic]
+async fn fail_delete_buffer_invalid_account() {
+    panic!()
+}

+ 17 - 0
pythnet/message_buffer/programs/mock-cpi-caller/tests/cases/test_initialize.rs

@@ -0,0 +1,17 @@
+use super::*;
+
+#[tokio::test]
+async fn test_initialize() {
+    let context = &mut MessageBufferTestContext::initialize_context(false).await;
+    let admin = context.payer.insecure_clone();
+    let (_, whitelist_bump) = context.initialize(admin).await.unwrap();
+
+    let (whitelist_acct_bump, admin_pubkey, allowed_programs_len, allowed_programs) =
+        context.fetch_whitelist().await.unwrap();
+
+    assert_eq!(whitelist_bump, whitelist_acct_bump);
+    assert_eq!(context.payer.pubkey(), admin_pubkey);
+
+    assert_eq!(0, allowed_programs_len);
+    assert_eq!(allowed_programs, vec![]);
+}

+ 54 - 0
pythnet/message_buffer/programs/mock-cpi-caller/tests/cases/test_put_all.rs

@@ -0,0 +1,54 @@
+use super::*;
+
+#[tokio::test]
+async fn test_put_all() {
+    let mut context = MessageBufferTestContext::initialize_with_default_test_buffer(
+        false,
+        MessageBufferTestContext::DEFAULT_TARGET_SIZE,
+    )
+    .await
+    .unwrap();
+
+    let payer = context.payer.pubkey();
+
+    let whitelist = context.whitelist();
+    let cpi_caller_auth = MessageBufferTestContext::get_mock_cpi_auth();
+    let (msg_buffer_pda, msg_buffer_bump) = MessageBufferTestContext::default_msg_buffer();
+
+    let add_price_params = MessageBufferTestContext::DEFAULT_ADD_PRICE_PARAMS;
+    context
+        .add_price(add_price_params, payer, whitelist, cpi_caller_auth)
+        .await
+        .unwrap();
+
+    let (id, price, price_expo, ema, ema_expo) = add_price_params;
+
+    let msg_buffer_account_data = context
+        .fetch_msg_buffer_account_data(&msg_buffer_pda)
+        .await
+        .unwrap();
+
+
+    let (bump, _version, header_len, end_offsets) =
+        deserialize_msg_buffer_header(&msg_buffer_account_data);
+
+    assert_eq!(bump, msg_buffer_bump);
+    // size_of(price::MessageHeader) + FullPriceMessage::SIZE
+    let msg_size_0 = 7 + 40;
+    assert_eq!(&end_offsets[0], &msg_size_0);
+
+    // size_of(price::MessageHeader) + CompactPriceMessage::SIZE
+    let msg_size_1 = 7 + 24;
+    assert_eq!(&end_offsets[1], &(msg_size_0 + msg_size_1));
+
+    assert_eq!(&end_offsets[2..], &[0u16; 253]);
+
+    let msgs = extract_msg_buffer_messages(header_len, end_offsets, &msg_buffer_account_data);
+    validate_price_msgs(id, price, price_expo, ema, ema_expo, &msgs).unwrap();
+}
+
+#[tokio::test]
+#[should_panic]
+async fn fail_put_all_invalid_auth() {
+    panic!()
+}

+ 156 - 0
pythnet/message_buffer/programs/mock-cpi-caller/tests/cases/test_resize_buffer.rs

@@ -0,0 +1,156 @@
+use super::*;
+
+#[tokio::test]
+async fn test_resize_buffer() {
+    let mut context = MessageBufferTestContext::initialize_with_default_test_buffer(
+        false,
+        MessageBufferTestContext::DEFAULT_TARGET_SIZE,
+    )
+    .await
+    .unwrap();
+
+    let (msg_buffer_pda, _) = MessageBufferTestContext::default_msg_buffer();
+
+
+    // increase buffer size
+    let mut target_size = MessageBufferTestContext::DEFAULT_TARGET_SIZE + 10240;
+    let target_sizes = vec![target_size];
+    context
+        .resize_msg_buffer(
+            MessageBufferTestContext::DEFAULT_TEST_PRICE_ID,
+            target_sizes,
+        )
+        .await
+        .unwrap();
+
+
+    let msg_buffer_account_data = context
+        .fetch_msg_buffer_account_data(&msg_buffer_pda)
+        .await
+        .unwrap();
+
+
+    assert_eq!(msg_buffer_account_data.len(), target_size as usize);
+
+    // decrease buffer size to less than original
+    target_size -= 10340;
+    let target_sizes = vec![target_size];
+    context
+        .resize_msg_buffer(
+            MessageBufferTestContext::DEFAULT_TEST_PRICE_ID,
+            target_sizes,
+        )
+        .await
+        .unwrap();
+
+
+    let msg_buffer_account_data = context
+        .fetch_msg_buffer_account_data(&msg_buffer_pda)
+        .await
+        .unwrap();
+
+
+    assert_eq!(msg_buffer_account_data.len(), target_size as usize);
+}
+
+#[tokio::test]
+async fn test_multiple_resize_buffer_ixs_in_same_txn() {
+    let mut context = MessageBufferTestContext::initialize_with_default_test_buffer(
+        false,
+        MessageBufferTestContext::DEFAULT_TARGET_SIZE,
+    )
+    .await
+    .unwrap();
+
+    let (msg_buffer_pda, _) = MessageBufferTestContext::default_msg_buffer();
+
+
+    // increase buffer size
+    let mut target_size = MessageBufferTestContext::DEFAULT_TARGET_SIZE + 10240;
+    let mut target_sizes = vec![];
+    target_sizes.push(target_size);
+    target_size += 10240;
+    target_sizes.push(target_size);
+    context
+        .resize_msg_buffer(
+            MessageBufferTestContext::DEFAULT_TEST_PRICE_ID,
+            target_sizes,
+        )
+        .await
+        .unwrap();
+
+
+    let msg_buffer_account_data = context
+        .fetch_msg_buffer_account_data(&msg_buffer_pda)
+        .await
+        .unwrap();
+
+
+    assert_eq!(msg_buffer_account_data.len(), target_size as usize);
+}
+
+#[tokio::test]
+async fn fail_resize_buffer_invalid_increase() {
+    let mut context = MessageBufferTestContext::initialize_with_default_test_buffer(
+        false,
+        MessageBufferTestContext::DEFAULT_TARGET_SIZE,
+    )
+    .await
+    .unwrap();
+
+    let whitelist = context.whitelist();
+    let admin = context.default_admin();
+    let cpi_caller_auth = MessageBufferTestContext::get_mock_cpi_auth();
+    let pyth_price_acct = MessageBufferTestContext::default_pyth_price_account();
+    let (msg_buffer_pda, msg_buffer_bump) = MessageBufferTestContext::default_msg_buffer();
+
+
+    // increase buffer size beyond maximum allowed
+    let target_size = MessageBufferTestContext::DEFAULT_TARGET_SIZE + 10240 + 100;
+
+    let resize_ix = resize_msg_buffer_ix(
+        cpi_caller_auth,
+        pyth_price_acct,
+        msg_buffer_bump,
+        target_size,
+        whitelist,
+        admin.pubkey(),
+        msg_buffer_pda,
+    );
+
+    let res = context.process_ixs(&[resize_ix], vec![&admin]).await;
+
+    assert!(res.is_err());
+
+    let err: ProgramError = res.unwrap_err().into();
+    assert_eq!(
+        err,
+        ProgramError::Custom(MessageBufferError::TargetSizeDeltaExceeded.into())
+    );
+
+
+    // shrink buffer size to less than minimum allowed
+    let target_size = 1;
+    let resize_ix = resize_msg_buffer_ix(
+        cpi_caller_auth,
+        pyth_price_acct,
+        msg_buffer_bump,
+        target_size,
+        whitelist,
+        admin.pubkey(),
+        msg_buffer_pda,
+    );
+
+    let res = context.process_ixs(&[resize_ix], vec![&admin]).await;
+
+    assert!(res.is_err());
+    let err: ProgramError = res.unwrap_err().into();
+    assert_eq!(
+        err,
+        ProgramError::Custom(MessageBufferError::MessageBufferTooSmall.into())
+    );
+}
+
+#[tokio::test]
+async fn test_resize_initialized_buffer() {
+}

+ 22 - 0
pythnet/message_buffer/programs/mock-cpi-caller/tests/cases/test_set_allowed_programs.rs

@@ -0,0 +1,22 @@
+use super::*;
+
+#[tokio::test]
+async fn test_set_allowed_programs() {
+    let context = &mut MessageBufferTestContext::initialize_context(false).await;
+    let admin = context.default_admin();
+    context.initialize(admin).await.unwrap();
+
+
+    let mock_cpi_caller_auth = MessageBufferTestContext::get_mock_cpi_auth();
+    let allowed_programs = vec![mock_cpi_caller_auth];
+    context
+        .set_allowed_programs(&allowed_programs)
+        .await
+        .unwrap();
+
+    let (_, _, allowed_programs_len, updated_allowed_programs) =
+        context.fetch_whitelist().await.unwrap();
+
+    assert_eq!(1, allowed_programs_len);
+    assert_eq!(allowed_programs, updated_allowed_programs);
+}

+ 681 - 0
pythnet/message_buffer/programs/mock-cpi-caller/tests/program_test/mod.rs

@@ -0,0 +1,681 @@
+use {
+    anchor_lang::{
+        prelude::{
+            ProgramError::Custom,
+            *,
+        },
+        solana_program::{
+            hash::hashv,
+            instruction::{
+                Instruction,
+                InstructionError,
+            },
+        },
+        Id,
+    },
+    byteorder::{
+        BigEndian,
+        LittleEndian,
+        ReadBytesExt,
+    },
+    solana_program_test::{
+        BanksClientError,
+        ProgramTest,
+        ProgramTestContext,
+    },
+    solana_sdk::{
+        account::ReadableAccount,
+        signature::{
+            Keypair,
+            Signer,
+        },
+        transaction::{
+            Transaction,
+            TransactionError,
+        },
+    },
+    std::{
+        io::{
+            Cursor,
+            Read,
+        },
+        str::FromStr,
+    },
+};
+
+pub struct MessageBufferTestContext {
+    context:   ProgramTestContext,
+    pub payer: Keypair,
+    admin:     Option<Keypair>,
+    whitelist: Option<Pubkey>,
+}
+
+impl MessageBufferTestContext {
+    pub const DEFAULT_TEST_PRICE_ID: u64 = 0u64;
+    pub const DEFAULT_TARGET_SIZE: u32 = 1024;
+    pub const DEFAULT_ADD_PRICE_PARAMS: AddPriceParams = (
+        MessageBufferTestContext::DEFAULT_TEST_PRICE_ID,
+        2u64,
+        3u64,
+        4u64,
+        5u64,
+    );
+
+    pub async fn initialize_context(disable_loosen_cpi_limit: bool) -> Self {
+        let log_filter = "solana_rbpf=trace,\
+                    solana_runtime::message_processor=trace,\
+                    solana_runtime::system_instruction_processor=trace,\
+                    solana_program_test=debug";
+        solana_logger::setup_with(log_filter);
+
+
+        let mut pt = ProgramTest::new("message_buffer", ::message_buffer::id(), None);
+        pt.add_program("mock_cpi_caller", ::mock_cpi_caller::id(), None);
+        if disable_loosen_cpi_limit {
+            pt.deactivate_feature(
+                Pubkey::from_str("GDH5TVdbTPUpRnXaRyQqiKUa7uZAbZ28Q2N9bhbKoMLm").unwrap(),
+            );
+        }
+
+        let context = pt.start_with_context().await;
+
+        let payer = context.payer.insecure_clone();
+
+        Self {
+            context,
+            payer,
+            admin: None,
+            whitelist: None,
+        }
+    }
+
+    // Initialize with test default helper functions //
+    pub async fn initialize_with_default_test_allowed_programs(
+        disable_loosen_cpi_limit: bool,
+    ) -> Result<Self> {
+        let mut context = Self::initialize_context(disable_loosen_cpi_limit).await;
+        context.initialize(context.default_admin()).await.unwrap();
+        context
+            .set_allowed_programs(&Self::default_allowed_programs())
+            .await
+            .unwrap();
+        Ok(context)
+    }
+
+
+    pub async fn initialize_with_default_test_buffer(
+        disable_loosen_cpi_limit: bool,
+        target_size: u32,
+    ) -> Result<Self> {
+        let mut context =
+            Self::initialize_with_default_test_allowed_programs(disable_loosen_cpi_limit)
+                .await
+                .unwrap();
+        context
+            .create_buffer(Self::DEFAULT_TEST_PRICE_ID, target_size)
+            .await
+            .unwrap();
+        Ok(context)
+    }
+
+
+    pub async fn get_balance(&mut self, pubkey: Pubkey) -> u64 {
+        self.context.banks_client.get_balance(pubkey).await.unwrap()
+    }
+
+    pub fn default_admin(&self) -> Keypair {
+        self.payer.insecure_clone()
+    }
+
+    fn admin_pubkey(&self) -> Pubkey {
+        self.admin.as_ref().unwrap().pubkey()
+    }
+
+    pub fn whitelist(&self) -> Pubkey {
+        self.whitelist.unwrap()
+    }
+
+    pub fn get_mock_cpi_auth() -> Pubkey {
+        let (mock_cpi_caller_auth, _) = Pubkey::find_program_address(
+            &[b"upd_price_write".as_ref(), ::message_buffer::id().as_ref()],
+            &::mock_cpi_caller::id(),
+        );
+        mock_cpi_caller_auth
+    }
+
+    pub fn default_allowed_programs() -> Vec<Pubkey> {
+        vec![MessageBufferTestContext::get_mock_cpi_auth()]
+    }
+
+
+    pub fn default_pyth_price_account() -> Pubkey {
+        Self::get_mock_pyth_price_account(Self::DEFAULT_TEST_PRICE_ID)
+    }
+
+    pub fn get_mock_pyth_price_account(id: u64) -> Pubkey {
+        let (mock_pyth_price_acct, _) = Pubkey::find_program_address(
+            &[b"pyth".as_ref(), b"price".as_ref(), &id.to_le_bytes()],
+            &::mock_cpi_caller::id(),
+        );
+        mock_pyth_price_acct
+    }
+
+    pub fn default_msg_buffer() -> (Pubkey, u8) {
+        find_msg_buffer_pda(
+            Self::get_mock_cpi_auth(),
+            Self::default_pyth_price_account(),
+        )
+    }
+
+
+    pub async fn process_ixs(
+        &mut self,
+        instructions: &[Instruction],
+        signers: Vec<&Keypair>,
+    ) -> anchor_lang::Result<()> {
+        let recent_blockhash = self.context.get_new_latest_blockhash().await.unwrap();
+
+        let mut transaction = Transaction::new_with_payer(instructions, Some(&self.payer.pubkey()));
+        transaction.partial_sign(&[&self.payer], recent_blockhash);
+        transaction.partial_sign(&signers, recent_blockhash);
+
+        let res = self
+            .context
+            .banks_client
+            .process_transaction(transaction)
+            .await;
+        match res {
+            Err(BanksClientError::TransactionError(TransactionError::InstructionError(
+                _,
+                InstructionError::Custom(error_code),
+            ))) => {
+                let e = Custom(error_code);
+                Err(e.into())
+            }
+            Err(_) => panic!("Unexpected error"),
+            Ok(_) => Ok(()),
+        }
+    }
+
+    pub async fn initialize(&mut self, admin: Keypair) -> Result<(Pubkey, u8)> {
+        let (whitelist_pda, whitelist_bump) = Pubkey::find_program_address(
+            &[b"message".as_ref(), b"whitelist".as_ref()],
+            &::message_buffer::id(),
+        );
+
+        self.admin = Some(admin.insecure_clone());
+        self.whitelist = Some(whitelist_pda);
+
+        let init_message_buffer_ix =
+            initialize_ix(admin.pubkey(), self.payer.pubkey(), whitelist_pda);
+
+        self.process_ixs(
+            &[init_message_buffer_ix],
+            vec![&self.admin.as_ref().unwrap().insecure_clone()],
+        )
+        .await
+        .unwrap();
+
+        Ok((whitelist_pda, whitelist_bump))
+    }
+
+    pub async fn fetch_whitelist(&mut self) -> Result<(u8, Pubkey, u32, Vec<Pubkey>)> {
+        let whitelist_account = self
+            .context
+            .banks_client
+            .get_account(self.whitelist())
+            .await
+            .unwrap();
+        assert!(whitelist_account.is_some());
+        let whitelist_account = whitelist_account.unwrap();
+        let account_data = whitelist_account.data();
+
+        deserialize_whitelist(account_data)
+    }
+
+
+    pub async fn set_allowed_programs(&mut self, allowed_programs: &Vec<Pubkey>) -> Result<()> {
+        let set_allowed_programs_ix = set_allowed_programs_ix(
+            self.admin_pubkey(),
+            self.payer.pubkey(),
+            self.whitelist(),
+            allowed_programs,
+        );
+
+        self.process_ixs(
+            &[set_allowed_programs_ix],
+            vec![&self.admin.as_ref().unwrap().insecure_clone()],
+        )
+        .await
+        .unwrap();
+        Ok(())
+    }
+
+
+    pub async fn create_buffer(&mut self, id: u64, target_size: u32) -> Result<(Pubkey, u8)> {
+        let pyth_price_account = Self::get_mock_pyth_price_account(id);
+        let (msg_buffer_pda, msg_buffer_bump) =
+            find_msg_buffer_pda(Self::get_mock_cpi_auth(), pyth_price_account);
+
+        let admin = self.admin.as_ref().unwrap().insecure_clone();
+        let create_msg_buffer_ix = create_msg_buffer_ix(
+            Self::get_mock_cpi_auth(),
+            pyth_price_account,
+            target_size,
+            self.whitelist(),
+            admin.pubkey(),
+            msg_buffer_pda,
+        );
+        self.process_ixs(&[create_msg_buffer_ix], vec![&admin])
+            .await?;
+
+
+        Ok((msg_buffer_pda, msg_buffer_bump))
+    }
+
+    pub async fn fetch_msg_buffer_account_data(&mut self, msg_buffer: &Pubkey) -> Option<Vec<u8>> {
+        let msg_buffer_account = self
+            .context
+            .banks_client
+            .get_account(*msg_buffer)
+            .await
+            .unwrap();
+
+        msg_buffer_account.map(|a| a.data)
+    }
+
+    pub async fn delete_buffer(&mut self, id: u64) -> anchor_lang::Result<()> {
+        let pyth_price_account = Self::get_mock_pyth_price_account(id);
+
+        let (msg_buffer_pda, msg_buffer_bump) =
+            find_msg_buffer_pda(Self::get_mock_cpi_auth(), pyth_price_account);
+        let admin = self.admin.as_ref().unwrap().insecure_clone();
+
+        let delete_ix = delete_msg_buffer_ix(
+            Self::get_mock_cpi_auth(),
+            pyth_price_account,
+            msg_buffer_bump,
+            self.whitelist(),
+            admin.pubkey(),
+            msg_buffer_pda,
+        );
+
+        self.process_ixs(&[delete_ix], vec![&admin]).await?;
+        Ok(())
+    }
+
+    pub async fn resize_msg_buffer(
+        &mut self,
+        id: u64,
+        target_sizes: Vec<u32>,
+    ) -> anchor_lang::Result<()> {
+        let pyth_price_account = Self::get_mock_pyth_price_account(id);
+        let (msg_buffer_pda, msg_buffer_bump) =
+            find_msg_buffer_pda(Self::get_mock_cpi_auth(), pyth_price_account);
+
+        let resize_ixs = &mut vec![];
+
+        let admin = self.admin.as_ref().unwrap().insecure_clone();
+
+
+        for target_size in target_sizes {
+            let resize_ix = resize_msg_buffer_ix(
+                Self::get_mock_cpi_auth(),
+                pyth_price_account,
+                msg_buffer_bump,
+                target_size,
+                self.whitelist(),
+                admin.pubkey(),
+                msg_buffer_pda,
+            );
+            resize_ixs.push(resize_ix);
+        }
+
+        self.process_ixs(resize_ixs, vec![&admin]).await?;
+        Ok(())
+    }
+
+    pub async fn add_price(
+        &mut self,
+        add_price_params: AddPriceParams,
+        payer: Pubkey,
+        whitelist: Pubkey,
+        cpi_auth: Pubkey,
+    ) -> Result<()> {
+        let (id, price, price_expo, ema, ema_expo) = add_price_params;
+        let pyth_price_account = Self::get_mock_pyth_price_account(id);
+        let (msg_buffer_pda, _) = find_msg_buffer_pda(cpi_auth, pyth_price_account);
+
+        let add_price_ix = add_price_ix(
+            id,
+            price,
+            price_expo,
+            ema,
+            ema_expo,
+            pyth_price_account,
+            payer,
+            whitelist,
+            cpi_auth,
+            msg_buffer_pda,
+        );
+
+        self.process_ixs(&[add_price_ix], vec![]).await?;
+        Ok(())
+    }
+}
+
+pub type AddPriceParams = (u64, u64, u64, u64, u64);
+
+fn initialize_ix(admin: Pubkey, payer: Pubkey, whitelist_pda: Pubkey) -> Instruction {
+    let init_ix_discriminator = sighash("global", "initialize");
+
+    Instruction::new_with_borsh(
+        ::message_buffer::id(),
+        &(init_ix_discriminator, admin),
+        vec![
+            AccountMeta::new(payer, true),
+            AccountMeta::new(whitelist_pda, false),
+            AccountMeta::new_readonly(System::id(), false),
+        ],
+    )
+}
+
+fn set_allowed_programs_ix(
+    admin: Pubkey,
+    payer: Pubkey,
+    whitelist: Pubkey,
+    allowed_programs: &Vec<Pubkey>,
+) -> Instruction {
+    let ix_discriminator = sighash("global", "set_allowed_programs");
+
+    Instruction::new_with_borsh(
+        ::message_buffer::id(),
+        &(ix_discriminator, allowed_programs),
+        vec![
+            AccountMeta::new(admin, true),
+            AccountMeta::new(payer, true),
+            AccountMeta::new(whitelist, false),
+        ],
+    )
+}
+
+pub fn create_msg_buffer_ix(
+    cpi_caller_auth: Pubkey,
+    pyth_price_acct: Pubkey,
+    target_size: u32,
+    whitelist: Pubkey,
+    admin: Pubkey,
+    msg_buffer_pda: Pubkey,
+) -> Instruction {
+    let create_ix_discriminator = sighash("global", "create_buffer");
+
+    Instruction::new_with_borsh(
+        ::message_buffer::id(),
+        &(
+            create_ix_discriminator,
+            cpi_caller_auth,
+            pyth_price_acct,
+            target_size,
+        ),
+        vec![
+            AccountMeta::new_readonly(whitelist, false),
+            AccountMeta::new(admin, true),
+            AccountMeta::new_readonly(System::id(), false),
+            AccountMeta::new(msg_buffer_pda, false),
+        ],
+    )
+}
+
+
+pub fn resize_msg_buffer_ix(
+    cpi_caller_auth: Pubkey,
+    pyth_price_acct: Pubkey,
+    msg_buffer_bump: u8,
+    target_size: u32,
+    whitelist: Pubkey,
+    admin: Pubkey,
+    msg_buffer_pda: Pubkey,
+) -> Instruction {
+    let resize_ix_disc = sighash("global", "resize_buffer");
+
+    Instruction::new_with_borsh(
+        ::message_buffer::id(),
+        &(
+            resize_ix_disc,
+            cpi_caller_auth,
+            pyth_price_acct,
+            msg_buffer_bump,
+            target_size,
+        ),
+        vec![
+            AccountMeta::new_readonly(whitelist, false),
+            AccountMeta::new(admin, true),
+            AccountMeta::new_readonly(System::id(), false),
+            AccountMeta::new(msg_buffer_pda, false),
+        ],
+    )
+}
+
+
+pub fn delete_msg_buffer_ix(
+    cpi_caller_auth: Pubkey,
+    pyth_price_acct: Pubkey,
+    msg_buffer_bump: u8,
+    whitelist: Pubkey,
+    admin: Pubkey,
+    msg_buffer_pda: Pubkey,
+) -> Instruction {
+    let delete_ix_disc = sighash("global", "delete_buffer");
+
+    Instruction::new_with_borsh(
+        ::message_buffer::id(),
+        &(
+            delete_ix_disc,
+            cpi_caller_auth,
+            pyth_price_acct,
+            msg_buffer_bump,
+        ),
+        vec![
+            AccountMeta::new_readonly(whitelist, false),
+            AccountMeta::new(admin, true),
+            AccountMeta::new(msg_buffer_pda, false),
+        ],
+    )
+}
+
+fn add_price_ix(
+    id: u64,
+    price: u64,
+    price_expo: u64,
+    ema: u64,
+    ema_expo: u64,
+    pyth_price_account: Pubkey,
+    payer: Pubkey,
+    whitelist: Pubkey,
+    cpi_auth: Pubkey,
+    msg_buffer_pda: Pubkey,
+) -> Instruction {
+    let add_price_disc = sighash("global", "add_price");
+    Instruction::new_with_borsh(
+        ::mock_cpi_caller::id(),
+        &(add_price_disc, id, price, price_expo, ema, ema_expo),
+        vec![
+            AccountMeta::new(pyth_price_account, false),
+            AccountMeta::new(payer, true),
+            AccountMeta::new_readonly(System::id(), false),
+            AccountMeta::new_readonly(whitelist, false),
+            AccountMeta::new_readonly(cpi_auth, false),
+            AccountMeta::new_readonly(::message_buffer::id(), false),
+            AccountMeta::new(msg_buffer_pda, false),
+        ],
+    )
+}
+
+type Bump = u8;
+type Version = u8;
+type HeaderLen = u16;
+type EndOffsets = [u16; 255];
+
+
+pub fn deserialize_msg_buffer_header(
+    account_data: &[u8],
+) -> (Bump, Version, HeaderLen, EndOffsets) {
+    let mut cursor = Cursor::new(account_data);
+    let discriminator = &mut vec![0u8; 8];
+    cursor.read_exact(discriminator).unwrap();
+    assert_eq!(discriminator, &sighash("account", "MessageBuffer"));
+
+
+    let msg_buffer_acct_bump = cursor.read_u8().unwrap();
+    let version = cursor.read_u8().unwrap();
+    let header_len = cursor.read_u16::<LittleEndian>().unwrap();
+    let mut end_offsets = [0u16; 255];
+    for i in 0..255 {
+        let cur_end_offset = cursor.read_u16::<LittleEndian>().unwrap();
+        end_offsets[i] = cur_end_offset;
+    }
+
+    let mut messages = vec![];
+    cursor.read_to_end(&mut messages).unwrap();
+
+    (msg_buffer_acct_bump, version, header_len, end_offsets)
+}
+
+pub fn extract_msg_buffer_messages(
+    header_len: u16,
+    end_offsets: EndOffsets,
+    account_data: &[u8],
+) -> Vec<Vec<u8>> {
+    let mut msgs = vec![];
+    let mut msg_begin = header_len;
+    for end_offset in end_offsets {
+        if end_offset == 0 {
+            break;
+        }
+        let msg_end = header_len + end_offset;
+        msgs.push(account_data[(msg_begin as usize)..(msg_end as usize)].to_vec());
+        msg_begin = msg_end;
+    }
+    msgs
+}
+
+pub 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
+}
+
+// price::MessageHeader
+type PriceMsgSchema = u8;
+type PriceMsgVersion = u16;
+type PriceMsgSize = u32;
+
+
+pub fn validate_price_msgs(
+    id: u64,
+    price: u64,
+    price_expo: u64,
+    ema: u64,
+    ema_expo: u64,
+    msgs: &Vec<Vec<u8>>,
+) -> Result<()> {
+    for msg in msgs {
+        let (schema, _, _) = deserialize_price_msg_header(msg);
+        match schema {
+            0 => verify_full_price_msg(id, price, price_expo, ema, ema_expo, msg)?,
+            1 => verify_compact_price_msg(id, price, price_expo, msg)?,
+            _ => panic!(),
+        }
+    }
+    Ok(())
+}
+
+pub fn verify_full_price_msg(
+    id: u64,
+    price: u64,
+    price_expo: u64,
+    ema: u64,
+    ema_expo: u64,
+    msg: &[u8],
+) -> Result<()> {
+    let mut cursor = Cursor::new(&msg[7..]);
+    let msg_id = cursor.read_u64::<BigEndian>().unwrap();
+    assert_eq!(id, msg_id);
+    let msg_price = cursor.read_u64::<BigEndian>().unwrap();
+    assert_eq!(price, msg_price);
+    let msg_price_expo = cursor.read_u64::<BigEndian>().unwrap();
+    assert_eq!(price_expo, msg_price_expo);
+    let msg_ema = cursor.read_u64::<BigEndian>().unwrap();
+    assert_eq!(ema, msg_ema);
+    let msg_ema_expo = cursor.read_u64::<BigEndian>().unwrap();
+    assert_eq!(ema_expo, msg_ema_expo);
+    Ok(())
+}
+
+pub fn verify_compact_price_msg(id: u64, price: u64, price_expo: u64, msg: &[u8]) -> Result<()> {
+    let mut cursor = Cursor::new(&msg[7..]);
+    let msg_id = cursor.read_u64::<BigEndian>().unwrap();
+    assert_eq!(id, msg_id);
+    let msg_price = cursor.read_u64::<BigEndian>().unwrap();
+    assert_eq!(price, msg_price);
+    let msg_price_expo = cursor.read_u64::<BigEndian>().unwrap();
+    assert_eq!(price_expo, msg_price_expo);
+    Ok(())
+}
+
+pub fn validate_dummy_price_msgs(_msgs: &[&[u8]], _msg_sizes: &Vec<u16>) -> Result<()> {
+    Ok(())
+}
+
+pub fn deserialize_price_msg_header(msg: &[u8]) -> (PriceMsgSchema, PriceMsgVersion, PriceMsgSize) {
+    let mut cursor = Cursor::new(msg);
+    let schema = cursor.read_u8().unwrap();
+    let version = cursor.read_u16::<BigEndian>().unwrap();
+    let size = cursor.read_u32::<BigEndian>().unwrap();
+    (schema, version, size)
+}
+
+
+pub fn find_msg_buffer_pda(cpi_caller_auth: Pubkey, pyth_price_acct: Pubkey) -> (Pubkey, u8) {
+    Pubkey::find_program_address(
+        &[
+            cpi_caller_auth.as_ref(),
+            b"message".as_ref(),
+            pyth_price_acct.as_ref(),
+        ],
+        &::message_buffer::id(),
+    )
+}
+
+
+pub fn deserialize_whitelist(account_data: &[u8]) -> Result<(u8, Pubkey, u32, Vec<Pubkey>)> {
+    let mut cursor = Cursor::new(account_data);
+    let discriminator = &mut vec![0u8; 8];
+    cursor.read_exact(discriminator).unwrap();
+    assert_eq!(discriminator, &sighash("account", "Whitelist"));
+
+
+    let whitelist_acct_bump = cursor.read_u8().unwrap();
+
+    let admin_bytes = &mut vec![0u8; 32];
+    cursor.read_exact(admin_bytes).unwrap();
+    let admin_pubkey = Pubkey::try_from_slice(admin_bytes).unwrap();
+
+    let allowed_programs_len = cursor.read_u32::<LittleEndian>().unwrap();
+
+    let mut allowed_programs = vec![];
+    for _ in 0..allowed_programs_len {
+        let allowed_program_bytes = &mut vec![0u8; 32];
+        cursor.read_exact(allowed_program_bytes).unwrap();
+        let allowed_program_pubkey = Pubkey::try_from_slice(allowed_program_bytes).unwrap();
+        allowed_programs.push(allowed_program_pubkey);
+    }
+    Ok((
+        whitelist_acct_bump,
+        admin_pubkey,
+        allowed_programs_len,
+        allowed_programs,
+    ))
+}

+ 4 - 0
pythnet/message_buffer/programs/mock-cpi-caller/tests/test_all.rs

@@ -0,0 +1,4 @@
+#![cfg(feature = "test-bpf")]
+
+mod cases;
+pub mod program_test;

+ 2 - 0
pythnet/message_buffer/rust-toolchain.toml

@@ -0,0 +1,2 @@
+[toolchain]
+channel = "1.66.1"

+ 32 - 32
pythnet/message_buffer/tests/message_buffer.ts

@@ -22,7 +22,7 @@ const mockCpiProg = anchor.workspace.MockCpiCaller as Program<MockCpiCaller>;
 let whitelistAdmin = anchor.web3.Keypair.generate();
 
 const [mockCpiCallerAuth] = anchor.web3.PublicKey.findProgramAddressSync(
-  [messageBufferProgram.programId.toBuffer(), Buffer.from("cpi")],
+  [Buffer.from("upd_price_write"), messageBufferProgram.programId.toBuffer()],
   mockCpiProg.programId
 );
 
@@ -43,7 +43,7 @@ const [pythPriceAccountPk] = anchor.web3.PublicKey.findProgramAddressSync(
   mockCpiProg.programId
 );
 const MESSAGE = Buffer.from("message");
-const [accumulatorPdaKey, accumulatorPdaBump] =
+const [messageBufferPda, messageBufferBump] =
   anchor.web3.PublicKey.findProgramAddressSync(
     [mockCpiCallerAuth.toBuffer(), MESSAGE, pythPriceAccountPk.toBuffer()],
     messageBufferProgram.programId
@@ -59,14 +59,14 @@ const [pythPriceAccountPk2] = anchor.web3.PublicKey.findProgramAddressSync(
   mockCpiProg.programId
 );
 
-const [accumulatorPdaKey2, accumulatorPdaBump2] =
+const [messageBufferPda2, messageBufferBump2] =
   anchor.web3.PublicKey.findProgramAddressSync(
     [mockCpiCallerAuth.toBuffer(), MESSAGE, pythPriceAccountPk2.toBuffer()],
     messageBufferProgram.programId
   );
 
 const accumulatorPdaMeta2 = {
-  pubkey: accumulatorPdaKey2,
+  pubkey: messageBufferPda2,
   isSigner: false,
   isWritable: true,
 };
@@ -78,7 +78,7 @@ let fundBalance = 100 * anchor.web3.LAMPORTS_PER_SOL;
 const discriminator = BorshAccountsCoder.accountDiscriminator("MessageBuffer");
 const messageBufferDiscriminator = bs58.encode(discriminator);
 
-describe("accumulator_updater", () => {
+describe("message_buffer", () => {
   // Configure the client to use the local cluster.
   let provider = anchor.AnchorProvider.env();
   anchor.setProvider(provider);
@@ -146,7 +146,7 @@ describe("accumulator_updater", () => {
   it("Creates a buffer", async () => {
     const accumulatorPdaMetas = [
       {
-        pubkey: accumulatorPdaKey,
+        pubkey: messageBufferPda,
         isSigner: false,
         isWritable: true,
       },
@@ -165,14 +165,14 @@ describe("accumulator_updater", () => {
 
     const messageBufferAccountData = await getMessageBuffer(
       provider.connection,
-      accumulatorPdaKey
+      messageBufferPda
     );
     const messageBufferHeader = deserializeMessageBufferHeader(
       messageBufferProgram,
       messageBufferAccountData
     );
     assert.equal(messageBufferHeader.version, 1);
-    assert.equal(messageBufferHeader.bump, accumulatorPdaBump);
+    assert.equal(messageBufferHeader.bump, messageBufferBump);
   });
 
   it("Creates a buffer even if the account already has lamports", async () => {
@@ -184,7 +184,7 @@ describe("accumulator_updater", () => {
         tx.add(
           anchor.web3.SystemProgram.transfer({
             fromPubkey: provider.wallet.publicKey,
-            toPubkey: accumulatorPdaKey2,
+            toPubkey: messageBufferPda2,
             lamports: minimumEmptyRent,
           })
         );
@@ -193,7 +193,7 @@ describe("accumulator_updater", () => {
     );
 
     const accumulatorPdaBalance = await provider.connection.getBalance(
-      accumulatorPdaKey2
+      messageBufferPda2
     );
     console.log(`accumulatorPdaBalance: ${accumulatorPdaBalance}`);
     assert.isTrue(accumulatorPdaBalance === minimumEmptyRent);
@@ -211,7 +211,7 @@ describe("accumulator_updater", () => {
 
     const messageBufferAccountData = await getMessageBuffer(
       provider.connection,
-      accumulatorPdaKey2
+      messageBufferPda2
     );
 
     const minimumMessageBufferRent =
@@ -219,7 +219,7 @@ describe("accumulator_updater", () => {
         messageBufferAccountData.length
       );
     const accumulatorPdaBalanceAfter = await provider.connection.getBalance(
-      accumulatorPdaKey2
+      messageBufferPda2
     );
     assert.isTrue(accumulatorPdaBalanceAfter === minimumMessageBufferRent);
     const messageBufferHeader = deserializeMessageBufferHeader(
@@ -228,8 +228,8 @@ describe("accumulator_updater", () => {
     );
 
     console.log(`header: ${JSON.stringify(messageBufferHeader)}`);
-    assert.equal(messageBufferHeader.bump, accumulatorPdaBump2);
-    assert.equal(messageBufferAccountData[8], accumulatorPdaBump2);
+    assert.equal(messageBufferHeader.bump, messageBufferBump2);
+    assert.equal(messageBufferAccountData[8], messageBufferBump2);
 
     assert.equal(messageBufferHeader.version, 1);
   });
@@ -277,7 +277,7 @@ describe("accumulator_updater", () => {
 
     const accumulatorPdaMetas = [
       {
-        pubkey: accumulatorPdaKey,
+        pubkey: messageBufferPda,
         isSigner: false,
         isWritable: true,
       },
@@ -329,7 +329,7 @@ describe("accumulator_updater", () => {
     );
 
     const messageBufferAccount = await provider.connection.getAccountInfo(
-      accumulatorPdaKey
+      messageBufferPda
     );
 
     const accumulatorPriceMessages = parseMessageBuffer(
@@ -363,7 +363,7 @@ describe("accumulator_updater", () => {
     );
 
     assert.isTrue(messageBufferAccounts.length === 2);
-    msgBufferAcctKeys.includes(accumulatorPdaKey.toString());
+    msgBufferAcctKeys.includes(messageBufferPda.toString());
   });
 
   it("Mock CPI Program - UpdatePrice", async () => {
@@ -404,7 +404,7 @@ describe("accumulator_updater", () => {
 
     const messageBufferAccountData = await getMessageBuffer(
       provider.connection,
-      accumulatorPdaKey
+      messageBufferPda
     );
 
     const updatedAccumulatorPriceMessages = parseMessageBuffer(
@@ -471,7 +471,7 @@ describe("accumulator_updater", () => {
 
       const messageBufferAccountData = await getMessageBuffer(
         provider.connection,
-        accumulatorPdaKey
+        messageBufferPda
       );
 
       const messageBufferHeader = deserializeMessageBufferHeader(
@@ -542,7 +542,7 @@ describe("accumulator_updater", () => {
   it("Resizes a buffer to a valid larger size", async () => {
     const messageBufferAccountDataBefore = await getMessageBuffer(
       provider.connection,
-      accumulatorPdaKey2
+      messageBufferPda2
     );
     const messageBufferAccountDataLenBefore =
       messageBufferAccountDataBefore.length;
@@ -563,7 +563,7 @@ describe("accumulator_updater", () => {
       .resizeBuffer(
         mockCpiCallerAuth,
         pythPriceAccountPk2,
-        accumulatorPdaBump2,
+        messageBufferBump2,
         targetSize
       )
       .accounts({
@@ -584,7 +584,7 @@ describe("accumulator_updater", () => {
 
     const messageBufferAccountData = await getMessageBuffer(
       provider.connection,
-      accumulatorPdaKey2
+      messageBufferPda2
     );
     assert.equal(messageBufferAccountData.length, targetSize);
 
@@ -609,7 +609,7 @@ describe("accumulator_updater", () => {
       .resizeBuffer(
         mockCpiCallerAuth,
         pythPriceAccountPk2,
-        accumulatorPdaBump2,
+        messageBufferBump2,
         targetSize
       )
       .accounts({
@@ -623,7 +623,7 @@ describe("accumulator_updater", () => {
 
     const messageBufferAccountData = await getMessageBuffer(
       provider.connection,
-      accumulatorPdaKey2
+      messageBufferPda2
     );
     assert.equal(messageBufferAccountData.length, targetSize);
   });
@@ -638,7 +638,7 @@ describe("accumulator_updater", () => {
           .resizeBuffer(
             mockCpiCallerAuth,
             pythPriceAccountPk2,
-            accumulatorPdaBump2,
+            messageBufferBump2,
             testCase
           )
           .accounts({
@@ -658,7 +658,7 @@ describe("accumulator_updater", () => {
 
   it("Deletes a buffer", async () => {
     await messageBufferProgram.methods
-      .deleteBuffer(mockCpiCallerAuth, pythPriceAccountPk2, accumulatorPdaBump2)
+      .deleteBuffer(mockCpiCallerAuth, pythPriceAccountPk2, messageBufferBump2)
       .accounts({
         whitelist: whitelistPubkey,
         admin: whitelistAdmin.publicKey,
@@ -669,7 +669,7 @@ describe("accumulator_updater", () => {
 
     const messageBufferAccountData = await getMessageBuffer(
       provider.connection,
-      accumulatorPdaKey2
+      messageBufferPda2
     );
 
     if (messageBufferAccountData != null) {
@@ -684,7 +684,7 @@ describe("accumulator_updater", () => {
     assert.isFalse(
       messageBufferAccounts
         .map((a) => a.pubkey.toString())
-        .includes(accumulatorPdaKey2.toString())
+        .includes(messageBufferPda2.toString())
     );
   });
 
@@ -702,7 +702,7 @@ describe("accumulator_updater", () => {
 
     const messageBufferAccountData = await getMessageBuffer(
       provider.connection,
-      accumulatorPdaKey2
+      messageBufferPda2
     );
 
     const minimumMessageBufferRent =
@@ -710,7 +710,7 @@ describe("accumulator_updater", () => {
         messageBufferAccountData.length
       );
     const accumulatorPdaBalanceAfter = await provider.connection.getBalance(
-      accumulatorPdaKey2
+      messageBufferPda2
     );
     assert.isTrue(accumulatorPdaBalanceAfter === minimumMessageBufferRent);
     const messageBufferHeader = deserializeMessageBufferHeader(
@@ -719,8 +719,8 @@ describe("accumulator_updater", () => {
     );
 
     console.log(`header: ${JSON.stringify(messageBufferHeader)}`);
-    assert.equal(messageBufferHeader.bump, accumulatorPdaBump2);
-    assert.equal(messageBufferAccountData[8], accumulatorPdaBump2);
+    assert.equal(messageBufferHeader.bump, messageBufferBump2);
+    assert.equal(messageBufferAccountData[8], messageBufferBump2);
 
     assert.equal(messageBufferHeader.version, 1);
   });

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä