[message-buffer 14/x] - Message buffer/zero copy fixes (#811)
* 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 * feat(message-buffer): use AccountLoader for zero-copy for resize/delete & put_all * chore(message-buffer): clean up * style(message-buffer): clean up * fix(message-buffer): address PR feedback revert to old put_all impl of using bytemuck for deserializing header, update tests add back old check * chore(message-buffer): clean up
This commit is contained in:
parent
2e32a22725
commit
78d3c5c4ca
|
@ -76,7 +76,6 @@ pub fn create_buffer<'info>(
|
|||
}
|
||||
loader.exit(&crate::ID)?;
|
||||
} else {
|
||||
// FIXME: change this to be emit!(Event)
|
||||
msg!("Buffer account already initialized");
|
||||
}
|
||||
|
||||
|
|
|
@ -1,51 +1,21 @@
|
|||
use {
|
||||
crate::{
|
||||
state::*,
|
||||
MessageBufferError,
|
||||
MESSAGE,
|
||||
},
|
||||
crate::state::*,
|
||||
anchor_lang::prelude::*,
|
||||
};
|
||||
|
||||
pub fn delete_buffer<'info>(
|
||||
ctx: Context<'_, '_, '_, 'info, DeleteBuffer<'info>>,
|
||||
allowed_program_auth: Pubkey,
|
||||
base_account_key: Pubkey,
|
||||
bump: u8,
|
||||
_base_account_key: Pubkey,
|
||||
) -> Result<()> {
|
||||
let message_buffer_account_info = ctx
|
||||
.remaining_accounts
|
||||
.first()
|
||||
.ok_or(MessageBufferError::MessageBufferNotProvided)?;
|
||||
|
||||
ctx.accounts
|
||||
.whitelist
|
||||
.is_allowed_program_auth(&allowed_program_auth)?;
|
||||
|
||||
MessageBuffer::check_discriminator(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(message_buffer_account_info)?;
|
||||
loader.close(ctx.accounts.admin.to_account_info())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(allowed_program_auth: Pubkey, base_account_key: Pubkey)]
|
||||
pub struct DeleteBuffer<'info> {
|
||||
#[account(
|
||||
seeds = [b"message".as_ref(), b"whitelist".as_ref()],
|
||||
|
@ -57,5 +27,12 @@ pub struct DeleteBuffer<'info> {
|
|||
// Also the recipient of the lamports from closing the buffer account
|
||||
#[account(mut)]
|
||||
pub admin: Signer<'info>,
|
||||
// remaining_account: - [AccumulatorInput PDA]
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
close = admin,
|
||||
seeds = [allowed_program_auth.as_ref(), b"message".as_ref(), base_account_key.as_ref()],
|
||||
bump = message_buffer.load()?.bump,
|
||||
)]
|
||||
pub message_buffer: AccountLoader<'info, MessageBuffer>,
|
||||
}
|
||||
|
|
|
@ -1,53 +1,40 @@
|
|||
use {
|
||||
crate::{
|
||||
state::*,
|
||||
MessageBufferError,
|
||||
},
|
||||
crate::state::*,
|
||||
anchor_lang::prelude::*,
|
||||
std::mem,
|
||||
};
|
||||
|
||||
|
||||
pub fn put_all<'info>(
|
||||
ctx: Context<'_, '_, '_, 'info, PutAll<'info>>,
|
||||
base_account_key: Pubkey,
|
||||
_base_account_key: Pubkey,
|
||||
messages: Vec<Vec<u8>>,
|
||||
) -> Result<()> {
|
||||
let cpi_caller_auth = ctx.accounts.whitelist_verifier.is_allowed()?;
|
||||
let message_buffer_account_info = ctx
|
||||
.remaining_accounts
|
||||
.first()
|
||||
.ok_or(MessageBufferError::MessageBufferNotProvided)?;
|
||||
ctx.accounts.whitelist_verifier.is_allowed()?;
|
||||
|
||||
MessageBuffer::check_discriminator(message_buffer_account_info)?;
|
||||
|
||||
let account_data = &mut message_buffer_account_info.try_borrow_mut_data()?;
|
||||
let header_end_index = mem::size_of::<MessageBuffer>() + 8;
|
||||
let msg_buffer_ai = ctx.accounts.message_buffer.to_account_info();
|
||||
let account_data = &mut msg_buffer_ai.try_borrow_mut_data()?;
|
||||
let header_end_index = MessageBuffer::HEADER_LEN as usize;
|
||||
|
||||
let (header_bytes, body_bytes) = account_data.split_at_mut(header_end_index);
|
||||
|
||||
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,
|
||||
)?;
|
||||
|
||||
message_buffer.refresh_header();
|
||||
|
||||
let (num_msgs, num_bytes) = message_buffer.put_all_in_buffer(body_bytes, &messages);
|
||||
|
||||
if num_msgs != messages.len() {
|
||||
msg!("unable to fit all messages in MessageBuffer account. Wrote {}/{} messages and {} bytes", num_msgs, messages.len(), num_bytes);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction( base_account_key: Pubkey)]
|
||||
#[instruction(base_account_key: Pubkey)]
|
||||
pub struct PutAll<'info> {
|
||||
pub whitelist_verifier: WhitelistVerifier<'info>,
|
||||
// remaining_accounts: - [AccumulatorInput PDA]
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [whitelist_verifier.cpi_caller_auth.key().as_ref(), b"message".as_ref(), base_account_key.as_ref()],
|
||||
bump = message_buffer.load()?.bump,
|
||||
)]
|
||||
pub message_buffer: AccountLoader<'info, MessageBuffer>,
|
||||
}
|
||||
|
|
|
@ -2,112 +2,35 @@ use {
|
|||
crate::{
|
||||
state::*,
|
||||
MessageBufferError,
|
||||
MESSAGE,
|
||||
},
|
||||
anchor_lang::{
|
||||
prelude::*,
|
||||
solana_program::entrypoint::MAX_PERMITTED_DATA_INCREASE,
|
||||
system_program::{
|
||||
self,
|
||||
Transfer,
|
||||
},
|
||||
},
|
||||
anchor_lang::prelude::*,
|
||||
};
|
||||
|
||||
|
||||
pub fn resize_buffer<'info>(
|
||||
ctx: Context<'_, '_, '_, 'info, ResizeBuffer<'info>>,
|
||||
allowed_program_auth: Pubkey,
|
||||
base_account_key: Pubkey,
|
||||
buffer_bump: u8,
|
||||
_base_account_key: Pubkey,
|
||||
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)?;
|
||||
MessageBuffer::check_discriminator(message_buffer_account_info)?;
|
||||
|
||||
|
||||
let target_size = target_size as usize;
|
||||
|
||||
let current_account_size = message_buffer_account_info.data_len();
|
||||
let target_size_delta = target_size.saturating_sub(current_account_size);
|
||||
let message_buffer = &ctx.accounts.message_buffer.load()?;
|
||||
let max_end_offset = message_buffer.end_offsets.iter().max().unwrap();
|
||||
let minimum_size = max_end_offset + message_buffer.header_len;
|
||||
require_gte!(
|
||||
MAX_PERMITTED_DATA_INCREASE,
|
||||
target_size_delta,
|
||||
MessageBufferError::TargetSizeDeltaExceeded
|
||||
target_size as usize,
|
||||
minimum_size as usize,
|
||||
MessageBufferError::MessageBufferTooSmall
|
||||
);
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
// 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 >= current_account_size {
|
||||
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 {
|
||||
// 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)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(
|
||||
allowed_program_auth: Pubkey, base_account_key: Pubkey,
|
||||
buffer_bump: u8, target_size: u32
|
||||
allowed_program_auth: Pubkey, base_account_key: Pubkey, target_size: u32
|
||||
)]
|
||||
pub struct ResizeBuffer<'info> {
|
||||
#[account(
|
||||
|
@ -122,5 +45,18 @@ pub struct ResizeBuffer<'info> {
|
|||
pub admin: Signer<'info>,
|
||||
|
||||
pub system_program: Program<'info, System>,
|
||||
// remaining_accounts: - [AccumulatorInput PDA]
|
||||
|
||||
/// If decreasing, Anchor will automatically check
|
||||
/// if target_size is too small and if so,then load() will fail.
|
||||
/// If increasing, Anchor also automatically checks if target_size delta
|
||||
/// exceeds MAX_PERMITTED_DATA_INCREASE
|
||||
#[account(
|
||||
mut,
|
||||
realloc = target_size as usize,
|
||||
realloc::zero = false,
|
||||
realloc::payer = admin,
|
||||
seeds = [allowed_program_auth.as_ref(), b"message".as_ref(), base_account_key.as_ref()],
|
||||
bump = message_buffer.load()?.bump,
|
||||
)]
|
||||
pub message_buffer: AccountLoader<'info, MessageBuffer>,
|
||||
}
|
||||
|
|
|
@ -112,21 +112,13 @@ pub mod message_buffer {
|
|||
/// *`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,
|
||||
)
|
||||
instructions::resize_buffer(ctx, allowed_program_auth, base_account_key, target_size)
|
||||
}
|
||||
|
||||
/// Closes the buffer account and transfers the remaining lamports to the
|
||||
|
@ -138,14 +130,12 @@ pub mod message_buffer {
|
|||
/// * `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)
|
||||
instructions::delete_buffer(ctx, allowed_program_auth, base_account_key)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
use {
|
||||
crate::{
|
||||
accumulator_input_seeds,
|
||||
instructions,
|
||||
MessageBufferError,
|
||||
},
|
||||
anchor_lang::{
|
||||
prelude::*,
|
||||
Discriminator,
|
||||
},
|
||||
anchor_lang::prelude::*,
|
||||
};
|
||||
|
||||
/// A MessageBuffer will have the following structure
|
||||
|
@ -78,6 +74,7 @@ impl MessageBuffer {
|
|||
self.end_offsets = [0u16; u8::MAX as usize];
|
||||
}
|
||||
|
||||
|
||||
/// `put_all` writes all the messages to the `AccumulatorInput` account
|
||||
/// and updates the `end_offsets` array.
|
||||
///
|
||||
|
@ -131,28 +128,6 @@ impl MessageBuffer {
|
|||
require_keys_eq!(expected_key, key);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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 check_discriminator(message_buffer_account_info: &AccountInfo) -> Result<()> {
|
||||
if instructions::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(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -222,16 +197,11 @@ mod test {
|
|||
|
||||
let header_len = MessageBuffer::HEADER_LEN as usize;
|
||||
|
||||
|
||||
let (header_bytes, body_bytes) = account_info_data.split_at_mut(header_len);
|
||||
let message_buffer: &mut MessageBuffer = bytemuck::from_bytes_mut(&mut header_bytes[8..]);
|
||||
|
||||
let (num_msgs, num_bytes) = message_buffer.put_all_in_buffer(body_bytes, &data_bytes);
|
||||
|
||||
|
||||
let message_buffer: &MessageBuffer =
|
||||
bytemuck::from_bytes(&account_info_data.as_slice()[8..header_len]);
|
||||
|
||||
assert_eq!(num_msgs, 2);
|
||||
assert_eq!(num_bytes, 5);
|
||||
|
||||
|
@ -239,6 +209,8 @@ mod test {
|
|||
assert_eq!(message_buffer.end_offsets[0], 2);
|
||||
assert_eq!(message_buffer.end_offsets[1], 5);
|
||||
|
||||
let message_buffer: &MessageBuffer =
|
||||
bytemuck::from_bytes(&account_info_data.as_slice()[8..header_len]);
|
||||
|
||||
let iter = message_buffer.end_offsets.iter().take_while(|x| **x != 0);
|
||||
let mut start = header_len;
|
||||
|
@ -258,6 +230,7 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_put_all_exceed_max() {
|
||||
let data = vec![vec![0u8; 9_718 - 2], vec![0u8], vec![0u8; 2]];
|
||||
|
@ -269,20 +242,24 @@ mod test {
|
|||
let header_len = MessageBuffer::HEADER_LEN as usize;
|
||||
|
||||
let (header_bytes, body_bytes) = account_info_data.split_at_mut(header_len);
|
||||
|
||||
let message_buffer: &mut MessageBuffer = bytemuck::from_bytes_mut(&mut header_bytes[8..]);
|
||||
|
||||
let (num_msgs, num_bytes) = message_buffer.put_all_in_buffer(body_bytes, &data_bytes);
|
||||
|
||||
|
||||
let message_buffer: &MessageBuffer =
|
||||
bytemuck::from_bytes(&account_info_data.as_slice()[8..header_len]);
|
||||
|
||||
assert_eq!(num_msgs, 2);
|
||||
assert_eq!(
|
||||
num_bytes,
|
||||
data_bytes[0..2].iter().map(|x| x.len()).sum::<usize>() as u16
|
||||
);
|
||||
|
||||
assert_eq!(message_buffer.end_offsets[0], 9_718 - 2);
|
||||
assert_eq!(message_buffer.end_offsets[1], 9_718 - 1);
|
||||
|
||||
|
||||
let message_buffer: &MessageBuffer =
|
||||
bytemuck::from_bytes(&account_info_data.as_slice()[8..header_len]);
|
||||
|
||||
|
||||
let iter = message_buffer.end_offsets.iter().take_while(|x| **x != 0);
|
||||
let mut start = header_len;
|
||||
|
@ -298,7 +275,6 @@ mod test {
|
|||
assert_eq!(message_buffer.end_offsets[2], 0);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_put_all_long_vec() {
|
||||
let data = vec![
|
||||
|
@ -315,17 +291,12 @@ mod test {
|
|||
|
||||
let header_len = MessageBuffer::HEADER_LEN as usize;
|
||||
|
||||
|
||||
let (header_bytes, body_bytes) = account_info_data.split_at_mut(header_len);
|
||||
let message_buffer: &mut MessageBuffer = bytemuck::from_bytes_mut(&mut header_bytes[8..]);
|
||||
|
||||
let (num_msgs, num_bytes) = message_buffer.put_all_in_buffer(body_bytes, &data_bytes);
|
||||
|
||||
|
||||
let message_buffer: &MessageBuffer =
|
||||
bytemuck::from_bytes(&account_info_data.as_slice()[8..header_len]);
|
||||
|
||||
|
||||
assert_eq!(num_msgs, 3);
|
||||
assert_eq!(
|
||||
num_bytes,
|
||||
|
@ -333,6 +304,9 @@ mod test {
|
|||
);
|
||||
|
||||
|
||||
let message_buffer: &MessageBuffer =
|
||||
bytemuck::from_bytes(&account_info_data.as_slice()[8..header_len]);
|
||||
|
||||
let iter = message_buffer.end_offsets.iter().take_while(|x| **x != 0);
|
||||
let mut start = header_len;
|
||||
let mut data_iter = data_bytes.iter();
|
||||
|
@ -368,13 +342,9 @@ mod test {
|
|||
let message_buffer: &mut MessageBuffer = bytemuck::from_bytes_mut(&mut header_bytes[8..]);
|
||||
|
||||
let (num_msgs, num_bytes) = message_buffer.put_all_in_buffer(body_bytes, &data_bytes);
|
||||
|
||||
assert_eq!(num_msgs, 2);
|
||||
assert_eq!(num_bytes, 5);
|
||||
|
||||
|
||||
let message_buffer: &MessageBuffer =
|
||||
bytemuck::from_bytes(&account_info_data.as_slice()[8..header_len]);
|
||||
|
||||
assert_eq!(message_buffer.end_offsets[0], 2);
|
||||
assert_eq!(message_buffer.end_offsets[1], 5);
|
||||
|
||||
|
|
|
@ -58,10 +58,10 @@ pub struct WhitelistVerifier<'info> {
|
|||
}
|
||||
|
||||
impl<'info> WhitelistVerifier<'info> {
|
||||
pub fn is_allowed(&self) -> Result<Pubkey> {
|
||||
pub fn is_allowed(&self) -> Result<()> {
|
||||
let auth = self.cpi_caller_auth.key();
|
||||
let whitelist = &self.whitelist;
|
||||
whitelist.is_allowed_program_auth(&auth)?;
|
||||
Ok(auth)
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,6 +90,7 @@ async fn test_multiple_resize_buffer_ixs_in_same_txn() {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic]
|
||||
async fn fail_resize_buffer_invalid_increase() {
|
||||
let mut context = MessageBufferTestContext::initialize_with_default_test_buffer(
|
||||
false,
|
||||
|
@ -111,7 +112,6 @@ async fn fail_resize_buffer_invalid_increase() {
|
|||
let resize_ix = resize_msg_buffer_ix(
|
||||
cpi_caller_auth,
|
||||
pyth_price_acct,
|
||||
msg_buffer_bump,
|
||||
target_size,
|
||||
whitelist,
|
||||
admin.pubkey(),
|
||||
|
@ -125,16 +125,173 @@ async fn fail_resize_buffer_invalid_increase() {
|
|||
let err: ProgramError = res.unwrap_err().into();
|
||||
assert_eq!(
|
||||
err,
|
||||
ProgramError::Custom(MessageBufferError::TargetSizeDeltaExceeded.into())
|
||||
ProgramError::Custom(anchor_lang::error::ErrorCode::AccountReallocExceedsLimit.into())
|
||||
);
|
||||
|
||||
|
||||
// shrink buffer size to less than minimum allowed
|
||||
let target_size = 1;
|
||||
let msg_buffer_account_data = context
|
||||
.fetch_msg_buffer_account_data(&msg_buffer_pda)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
||||
assert_eq!(
|
||||
msg_buffer_account_data.len(),
|
||||
MessageBufferTestContext::DEFAULT_TARGET_SIZE 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]);
|
||||
|
||||
// shrink buffer size to less than MessageBuffer::HEADER_LEN
|
||||
let target_size = 15;
|
||||
let resize_ix = resize_msg_buffer_ix(
|
||||
cpi_caller_auth,
|
||||
pyth_price_acct,
|
||||
target_size,
|
||||
whitelist,
|
||||
admin.pubkey(),
|
||||
msg_buffer_pda,
|
||||
);
|
||||
|
||||
// a target_size less than the MessageBuffer::HEADER_LEN
|
||||
// will result in a `ProgramFailedToComplete` (or AccountDiscriminatorNotFound
|
||||
// if target_size < 8) since after the realloc,
|
||||
// the AccountLoadder.load/load_mut() calls will fail
|
||||
context
|
||||
.process_ixs(&[resize_ix], vec![&admin])
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_resize_initialized_buffer() {
|
||||
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();
|
||||
|
||||
|
||||
// increase buffer size should not edit the original data
|
||||
let 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();
|
||||
|
||||
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]
|
||||
async fn fail_resize_initialized_buffer() {
|
||||
let mut context = MessageBufferTestContext::initialize_with_default_test_buffer(
|
||||
false,
|
||||
MessageBufferTestContext::DEFAULT_TARGET_SIZE,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let payer = context.payer.pubkey();
|
||||
let admin = context.default_admin();
|
||||
let pyth_price_acct = MessageBufferTestContext::default_pyth_price_account();
|
||||
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 msg_buffer_account_data = context
|
||||
.fetch_msg_buffer_account_data(&msg_buffer_pda)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
||||
let (_, _version, header_len, end_offsets) =
|
||||
deserialize_msg_buffer_header(&msg_buffer_account_data);
|
||||
|
||||
let max_end_offset = end_offsets.iter().max().unwrap();
|
||||
let min_size = header_len + max_end_offset;
|
||||
|
||||
|
||||
// decrease buffer size to less than something that can fit the current messages
|
||||
let target_size = (min_size as u32) - 1;
|
||||
|
||||
|
||||
let resize_ix = resize_msg_buffer_ix(
|
||||
cpi_caller_auth,
|
||||
pyth_price_acct,
|
||||
msg_buffer_bump,
|
||||
target_size,
|
||||
whitelist,
|
||||
admin.pubkey(),
|
||||
|
@ -144,13 +301,32 @@ async fn fail_resize_buffer_invalid_increase() {
|
|||
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() {
|
||||
let target_size = (min_size as u32) + 1;
|
||||
|
||||
|
||||
let resize_ix = resize_msg_buffer_ix(
|
||||
cpi_caller_auth,
|
||||
pyth_price_acct,
|
||||
target_size,
|
||||
whitelist,
|
||||
admin.pubkey(),
|
||||
msg_buffer_pda,
|
||||
);
|
||||
|
||||
let res = context.process_ixs(&[resize_ix], vec![&admin]).await;
|
||||
|
||||
assert!(res.is_ok());
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -192,7 +192,10 @@ impl MessageBufferTestContext {
|
|||
let e = Custom(error_code);
|
||||
Err(e.into())
|
||||
}
|
||||
Err(_) => panic!("Unexpected error"),
|
||||
Err(e) => {
|
||||
println!("proces_ixs Error: {:?}", e);
|
||||
panic!("Non Custom Ix Error in process_ixs{:?}", e);
|
||||
}
|
||||
Ok(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
@ -287,14 +290,13 @@ impl MessageBufferTestContext {
|
|||
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) =
|
||||
let (msg_buffer_pda, _) =
|
||||
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,
|
||||
|
@ -310,7 +312,7 @@ impl MessageBufferTestContext {
|
|||
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) =
|
||||
let (msg_buffer_pda, _) =
|
||||
find_msg_buffer_pda(Self::get_mock_cpi_auth(), pyth_price_account);
|
||||
|
||||
let resize_ixs = &mut vec![];
|
||||
|
@ -322,7 +324,6 @@ impl MessageBufferTestContext {
|
|||
let resize_ix = resize_msg_buffer_ix(
|
||||
Self::get_mock_cpi_auth(),
|
||||
pyth_price_account,
|
||||
msg_buffer_bump,
|
||||
target_size,
|
||||
self.whitelist(),
|
||||
admin.pubkey(),
|
||||
|
@ -430,7 +431,6 @@ pub fn create_msg_buffer_ix(
|
|||
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,
|
||||
|
@ -444,7 +444,6 @@ pub fn resize_msg_buffer_ix(
|
|||
resize_ix_disc,
|
||||
cpi_caller_auth,
|
||||
pyth_price_acct,
|
||||
msg_buffer_bump,
|
||||
target_size,
|
||||
),
|
||||
vec![
|
||||
|
@ -460,7 +459,6 @@ pub fn resize_msg_buffer_ix(
|
|||
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,
|
||||
|
@ -469,12 +467,7 @@ pub fn delete_msg_buffer_ix(
|
|||
|
||||
Instruction::new_with_borsh(
|
||||
::message_buffer::id(),
|
||||
&(
|
||||
delete_ix_disc,
|
||||
cpi_caller_auth,
|
||||
pyth_price_acct,
|
||||
msg_buffer_bump,
|
||||
),
|
||||
&(delete_ix_disc, cpi_caller_auth, pyth_price_acct),
|
||||
vec![
|
||||
AccountMeta::new_readonly(whitelist, false),
|
||||
AccountMeta::new(admin, true),
|
||||
|
|
|
@ -65,7 +65,7 @@ const [messageBufferPda2, messageBufferBump2] =
|
|||
messageBufferProgram.programId
|
||||
);
|
||||
|
||||
const accumulatorPdaMeta2 = {
|
||||
const messageBufferPdaMeta2 = {
|
||||
pubkey: messageBufferPda2,
|
||||
isSigner: false,
|
||||
isWritable: true,
|
||||
|
@ -144,7 +144,7 @@ describe("message_buffer", () => {
|
|||
});
|
||||
|
||||
it("Creates a buffer", async () => {
|
||||
const accumulatorPdaMetas = [
|
||||
const msgBufferPdaMetas = [
|
||||
{
|
||||
pubkey: messageBufferPda,
|
||||
isSigner: false,
|
||||
|
@ -160,7 +160,7 @@ describe("message_buffer", () => {
|
|||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
})
|
||||
.signers([whitelistAdmin])
|
||||
.remainingAccounts(accumulatorPdaMetas)
|
||||
.remainingAccounts(msgBufferPdaMetas)
|
||||
.rpc({ skipPreflight: true });
|
||||
|
||||
const messageBufferAccountData = await getMessageBuffer(
|
||||
|
@ -206,7 +206,7 @@ describe("message_buffer", () => {
|
|||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
})
|
||||
.signers([whitelistAdmin])
|
||||
.remainingAccounts([accumulatorPdaMeta2])
|
||||
.remainingAccounts([messageBufferPdaMeta2])
|
||||
.rpc({ skipPreflight: true });
|
||||
|
||||
const messageBufferAccountData = await getMessageBuffer(
|
||||
|
@ -560,19 +560,13 @@ describe("message_buffer", () => {
|
|||
);
|
||||
const targetSize = 10 * 1024;
|
||||
await messageBufferProgram.methods
|
||||
.resizeBuffer(
|
||||
mockCpiCallerAuth,
|
||||
pythPriceAccountPk2,
|
||||
messageBufferBump2,
|
||||
targetSize
|
||||
)
|
||||
.resizeBuffer(mockCpiCallerAuth, pythPriceAccountPk2, 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(
|
||||
|
@ -606,19 +600,14 @@ describe("message_buffer", () => {
|
|||
it("Resizes a buffer to a smaller size", async () => {
|
||||
const targetSize = 4 * 1024;
|
||||
await messageBufferProgram.methods
|
||||
.resizeBuffer(
|
||||
mockCpiCallerAuth,
|
||||
pythPriceAccountPk2,
|
||||
messageBufferBump2,
|
||||
targetSize
|
||||
)
|
||||
.resizeBuffer(mockCpiCallerAuth, pythPriceAccountPk2, targetSize)
|
||||
.accounts({
|
||||
whitelist: whitelistPubkey,
|
||||
admin: whitelistAdmin.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
messageBuffer: messageBufferPda2,
|
||||
})
|
||||
.signers([whitelistAdmin])
|
||||
.remainingAccounts([accumulatorPdaMeta2])
|
||||
.rpc({ skipPreflight: true });
|
||||
|
||||
const messageBufferAccountData = await getMessageBuffer(
|
||||
|
@ -635,19 +624,14 @@ describe("message_buffer", () => {
|
|||
let errorThrown = false;
|
||||
try {
|
||||
await messageBufferProgram.methods
|
||||
.resizeBuffer(
|
||||
mockCpiCallerAuth,
|
||||
pythPriceAccountPk2,
|
||||
messageBufferBump2,
|
||||
testCase
|
||||
)
|
||||
.resizeBuffer(mockCpiCallerAuth, pythPriceAccountPk2, testCase)
|
||||
.accounts({
|
||||
whitelist: whitelistPubkey,
|
||||
admin: whitelistAdmin.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
messageBuffer: messageBufferPda2,
|
||||
})
|
||||
.signers([whitelistAdmin])
|
||||
.remainingAccounts([accumulatorPdaMeta2])
|
||||
.rpc({ skipPreflight: true });
|
||||
} catch (_err) {
|
||||
errorThrown = true;
|
||||
|
@ -658,13 +642,14 @@ describe("message_buffer", () => {
|
|||
|
||||
it("Deletes a buffer", async () => {
|
||||
await messageBufferProgram.methods
|
||||
.deleteBuffer(mockCpiCallerAuth, pythPriceAccountPk2, messageBufferBump2)
|
||||
.deleteBuffer(mockCpiCallerAuth, pythPriceAccountPk2)
|
||||
.accounts({
|
||||
whitelist: whitelistPubkey,
|
||||
admin: whitelistAdmin.publicKey,
|
||||
messageBuffer: messageBufferPda2,
|
||||
})
|
||||
.signers([whitelistAdmin])
|
||||
.remainingAccounts([accumulatorPdaMeta2])
|
||||
.remainingAccounts([messageBufferPdaMeta2])
|
||||
.rpc({ skipPreflight: true });
|
||||
|
||||
const messageBufferAccountData = await getMessageBuffer(
|
||||
|
@ -697,7 +682,7 @@ describe("message_buffer", () => {
|
|||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
})
|
||||
.signers([whitelistAdmin])
|
||||
.remainingAccounts([accumulatorPdaMeta2])
|
||||
.remainingAccounts([messageBufferPdaMeta2])
|
||||
.rpc({ skipPreflight: true });
|
||||
|
||||
const messageBufferAccountData = await getMessageBuffer(
|
||||
|
|
Loading…
Reference in New Issue