[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>
This commit is contained in:
parent
6a5b1c434a
commit
794bd84c6f
|
@ -71,13 +71,13 @@ repos:
|
||||||
language: "rust"
|
language: "rust"
|
||||||
entry: cargo +nightly fmt --manifest-path ./message_buffer/Cargo.toml --all -- --config-path rustfmt.toml
|
entry: cargo +nightly fmt --manifest-path ./message_buffer/Cargo.toml --all -- --config-path rustfmt.toml
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
files: accumulator_updater
|
files: message_buffer
|
||||||
- id: cargo-clippy-message-buffer
|
- id: cargo-clippy-message-buffer
|
||||||
name: Cargo clippy for message buffer contract
|
name: Cargo clippy for message buffer contract
|
||||||
language: "rust"
|
language: "rust"
|
||||||
entry: cargo +nightly clippy --manifest-path ./message_buffer/Cargo.toml --tests --fix --allow-dirty --allow-staged -- -D warnings
|
entry: cargo +nightly clippy --manifest-path ./message_buffer/Cargo.toml --tests --fix --allow-dirty --allow-staged -- -D warnings
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
files: accumulator_updater
|
files: message_buffer
|
||||||
# Hooks for solana receiver contract
|
# Hooks for solana receiver contract
|
||||||
- id: cargo-fmt-solana-receiver
|
- id: cargo-fmt-solana-receiver
|
||||||
name: Cargo format for solana target chain contract
|
name: Cargo format for solana target chain contract
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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]
|
||||||
|
}
|
|
@ -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 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(())
|
||||||
|
}
|
||||||
|
|
|
@ -1,144 +1,55 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
|
instructions::verify_message_buffer,
|
||||||
state::*,
|
state::*,
|
||||||
AccumulatorUpdaterError,
|
MessageBufferError,
|
||||||
},
|
|
||||||
anchor_lang::{
|
|
||||||
prelude::*,
|
|
||||||
system_program::{
|
|
||||||
self,
|
|
||||||
CreateAccount,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
anchor_lang::prelude::*,
|
||||||
|
std::mem,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
pub const MESSAGE: &str = "message";
|
|
||||||
pub const FUND: &str = "fund";
|
|
||||||
|
|
||||||
|
|
||||||
pub fn put_all<'info>(
|
pub fn put_all<'info>(
|
||||||
ctx: Context<'_, '_, '_, 'info, PutAll<'info>>,
|
ctx: Context<'_, '_, '_, 'info, PutAll<'info>>,
|
||||||
base_account_key: Pubkey,
|
base_account_key: Pubkey,
|
||||||
messages: Vec<Vec<u8>>,
|
messages: Vec<Vec<u8>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let cpi_caller_auth = ctx.accounts.whitelist_verifier.is_allowed()?;
|
let cpi_caller_auth = ctx.accounts.whitelist_verifier.is_allowed()?;
|
||||||
let accumulator_input_ai = ctx
|
let message_buffer_account_info = ctx
|
||||||
.remaining_accounts
|
.remaining_accounts
|
||||||
.first()
|
.first()
|
||||||
.ok_or(AccumulatorUpdaterError::MessageBufferNotProvided)?;
|
.ok_or(MessageBufferError::MessageBufferNotProvided)?;
|
||||||
|
|
||||||
let loader;
|
verify_message_buffer(message_buffer_account_info)?;
|
||||||
|
|
||||||
{
|
let account_data = &mut message_buffer_account_info.try_borrow_mut_data()?;
|
||||||
let accumulator_input = &mut (if is_uninitialized_account(accumulator_input_ai) {
|
let header_end_index = mem::size_of::<MessageBuffer>() + 8;
|
||||||
let (pda, bump) = Pubkey::find_program_address(
|
|
||||||
&[
|
let (header_bytes, body_bytes) = account_data.split_at_mut(header_end_index);
|
||||||
cpi_caller_auth.as_ref(),
|
|
||||||
MESSAGE.as_bytes(),
|
let message_buffer: &mut MessageBuffer = bytemuck::from_bytes_mut(&mut header_bytes[8..]);
|
||||||
base_account_key.as_ref(),
|
|
||||||
],
|
message_buffer.validate(
|
||||||
&crate::ID,
|
message_buffer_account_info.key(),
|
||||||
);
|
|
||||||
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,
|
cpi_caller_auth,
|
||||||
base_account_key,
|
base_account_key,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
message_buffer.refresh_header();
|
||||||
|
|
||||||
|
let (num_msgs, num_bytes) = message_buffer.put_all_in_buffer(body_bytes, &messages);
|
||||||
|
|
||||||
let (num_msgs, num_bytes) = accumulator_input.put_all(&messages);
|
|
||||||
if num_msgs != messages.len() {
|
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 accumulator input account. Wrote {}/{} messages and {} bytes", num_msgs, messages.len(), num_bytes);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
loader.exit(&crate::ID)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_uninitialized_account(ai: &AccountInfo) -> bool {
|
|
||||||
ai.data_is_empty() && ai.owner == &system_program::ID
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
#[instruction( base_account_key: Pubkey)]
|
#[instruction( base_account_key: Pubkey)]
|
||||||
pub struct PutAll<'info> {
|
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 whitelist_verifier: WhitelistVerifier<'info>,
|
||||||
pub system_program: Program<'info, System>,
|
|
||||||
// remaining_accounts: - [AccumulatorInput PDA]
|
// 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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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]
|
||||||
|
}
|
|
@ -16,13 +16,13 @@ pub mod message_buffer {
|
||||||
use super::*;
|
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.
|
/// Once initialized, the authority must sign all further changes to the whitelist.
|
||||||
pub fn initialize(ctx: Context<Initialize>, authority: Pubkey) -> Result<()> {
|
pub fn initialize(ctx: Context<Initialize>, admin: Pubkey) -> Result<()> {
|
||||||
require_keys_neq!(authority, Pubkey::default());
|
require_keys_neq!(admin, Pubkey::default());
|
||||||
let whitelist = &mut ctx.accounts.whitelist;
|
let whitelist = &mut ctx.accounts.whitelist;
|
||||||
whitelist.bump = *ctx.bumps.get("whitelist").unwrap();
|
whitelist.bump = *ctx.bumps.get("whitelist").unwrap();
|
||||||
whitelist.authority = authority;
|
whitelist.admin = admin;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,23 +40,19 @@ pub mod message_buffer {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the new authority for the whitelist
|
/// Sets the new admin for the whitelist
|
||||||
pub fn update_whitelist_authority(
|
pub fn update_whitelist_admin(ctx: Context<UpdateWhitelist>, new_admin: Pubkey) -> Result<()> {
|
||||||
ctx: Context<UpdateWhitelist>,
|
|
||||||
new_authority: Pubkey,
|
|
||||||
) -> Result<()> {
|
|
||||||
let whitelist = &mut ctx.accounts.whitelist;
|
let whitelist = &mut ctx.accounts.whitelist;
|
||||||
whitelist.validate_new_authority(new_authority)?;
|
whitelist.validate_new_admin(new_admin)?;
|
||||||
whitelist.authority = new_authority;
|
whitelist.admin = new_admin;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Insert messages/inputs for the Accumulator. All inputs derived from the
|
/// Put messages into the Accumulator. All messages put for the same
|
||||||
/// `base_account_key` will go into the same PDA. The PDA is derived with
|
/// `base_account_key` go into the same buffer PDA. The PDA's address is
|
||||||
/// seeds = [cpi_caller_auth, b"accumulator", base_account_key]
|
/// `[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
|
/// * `base_account_key` - Pubkey of the original account the
|
||||||
/// `MessageBuffer` is derived from
|
/// `MessageBuffer` is derived from
|
||||||
|
@ -74,7 +70,6 @@ pub mod message_buffer {
|
||||||
/// any existing contents.
|
/// any existing contents.
|
||||||
///
|
///
|
||||||
/// TODO:
|
/// TODO:
|
||||||
/// - try handling re-allocation of the accumulator_input space
|
|
||||||
/// - handle updates ("paging/batches of messages")
|
/// - handle updates ("paging/batches of messages")
|
||||||
///
|
///
|
||||||
pub fn put_all<'info>(
|
pub fn put_all<'info>(
|
||||||
|
@ -84,6 +79,74 @@ pub mod message_buffer {
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
instructions::put_all(ctx, base_account_key, messages)
|
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)]
|
#[derive(Accounts)]
|
||||||
|
@ -107,19 +170,19 @@ pub struct UpdateWhitelist<'info> {
|
||||||
#[account(mut)]
|
#[account(mut)]
|
||||||
pub payer: Signer<'info>,
|
pub payer: Signer<'info>,
|
||||||
|
|
||||||
pub authority: Signer<'info>,
|
pub admin: Signer<'info>,
|
||||||
#[account(
|
#[account(
|
||||||
mut,
|
mut,
|
||||||
seeds = [b"message".as_ref(), b"whitelist".as_ref()],
|
seeds = [b"message".as_ref(), b"whitelist".as_ref()],
|
||||||
bump = whitelist.bump,
|
bump = whitelist.bump,
|
||||||
has_one = authority
|
has_one = admin
|
||||||
)]
|
)]
|
||||||
pub whitelist: Account<'info, Whitelist>,
|
pub whitelist: Account<'info, Whitelist>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[error_code]
|
#[error_code]
|
||||||
pub enum AccumulatorUpdaterError {
|
pub enum MessageBufferError {
|
||||||
#[msg("CPI Caller not allowed")]
|
#[msg("CPI Caller not allowed")]
|
||||||
CallerNotAllowed,
|
CallerNotAllowed,
|
||||||
#[msg("Whitelist already contains program")]
|
#[msg("Whitelist already contains program")]
|
||||||
|
@ -140,6 +203,14 @@ pub enum AccumulatorUpdaterError {
|
||||||
CurrentDataLengthExceeded,
|
CurrentDataLengthExceeded,
|
||||||
#[msg("Message Buffer not provided")]
|
#[msg("Message Buffer not provided")]
|
||||||
MessageBufferNotProvided,
|
MessageBufferNotProvided,
|
||||||
|
#[msg("Message Buffer is not sufficiently large")]
|
||||||
|
MessageBufferTooSmall,
|
||||||
#[msg("Fund Bump not found")]
|
#[msg("Fund Bump not found")]
|
||||||
FundBumpNotFound,
|
FundBumpNotFound,
|
||||||
|
#[msg("Reallocation failed")]
|
||||||
|
ReallocFailed,
|
||||||
|
#[msg("Target size too large for reallocation/initialization. Max delta is 10240")]
|
||||||
|
TargetSizeDeltaExceeded,
|
||||||
|
#[msg("MessageBuffer Uninitialized")]
|
||||||
|
MessageBufferUninitialized,
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ macro_rules! accumulator_input_seeds {
|
||||||
$cpi_caller_pid.as_ref(),
|
$cpi_caller_pid.as_ref(),
|
||||||
b"message".as_ref(),
|
b"message".as_ref(),
|
||||||
$base_account.as_ref(),
|
$base_account.as_ref(),
|
||||||
&[$accumulator_input.header.bump],
|
&[$accumulator_input.bump],
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +1,31 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
accumulator_input_seeds,
|
accumulator_input_seeds,
|
||||||
AccumulatorUpdaterError,
|
MessageBufferError,
|
||||||
},
|
},
|
||||||
anchor_lang::prelude::*,
|
anchor_lang::prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// A MessageBuffer will have the following structure
|
||||||
/// `MessageBuffer` is an arbitrary set of bytes
|
/// ```ignore
|
||||||
/// that will be included in the AccumulatorSysvar
|
/// struct MessageBuffer {
|
||||||
|
/// header: BufferHeader,
|
||||||
|
/// messages: [u8; accountInfo.data.len - header.header_len]
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
|
/// 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.
|
||||||
///
|
///
|
||||||
/// The actual contents of data are set/handled by
|
/// A `MessageBuffer` AccountInfo.data will look like:
|
||||||
/// the CPI calling program (e.g. Pyth Oracle)
|
/// [ <discrimintator>, <buffer_header>, <messages> ]
|
||||||
///
|
/// (0..8) (8..header_len) (header_len...accountInfo.data.len)
|
||||||
/// TODO: implement custom serialization & set alignment
|
|
||||||
#[account(zero_copy)]
|
#[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)]
|
#[derive(InitSpace, Debug)]
|
||||||
pub struct BufferHeader {
|
pub struct MessageBuffer {
|
||||||
|
/* header */
|
||||||
pub bump: u8, // 1
|
pub bump: u8, // 1
|
||||||
pub version: u8, // 1
|
pub version: u8, // 1
|
||||||
// byte offset of accounts where data starts
|
// byte offset of accounts where data starts
|
||||||
|
@ -41,14 +36,19 @@ pub struct BufferHeader {
|
||||||
/// => msg1 = account_info.data[(header_len + 0)..(header_len + 10)]
|
/// => msg1 = account_info.data[(header_len + 0)..(header_len + 10)]
|
||||||
/// => msg2 = account_info.data[(header_len + 10)..(header_len + 14)]
|
/// => msg2 = account_info.data[(header_len + 10)..(header_len + 14)]
|
||||||
pub end_offsets: [u16; 255], // 510
|
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.
|
// 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
|
// this is the number of bytes from the beginning of the account_info.data
|
||||||
// to the start of the `AccumulatorInput` 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;
|
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;
|
self.version = Self::CURRENT_VERSION;
|
||||||
}
|
self.end_offsets = [0u16; u8::MAX as usize];
|
||||||
}
|
|
||||||
impl MessageBuffer {
|
|
||||||
pub fn new(bump: u8) -> Self {
|
|
||||||
let header = BufferHeader::new(bump);
|
|
||||||
Self {
|
|
||||||
header,
|
|
||||||
messages: [0u8; 9_718],
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `put_all` writes all the messages to the `AccumulatorInput` account
|
/// `put_all` writes all the messages to the `AccumulatorInput` account
|
||||||
/// and updates the `end_offsets` array.
|
/// 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
|
/// Returns tuple of the number of messages written and the end_offset
|
||||||
/// of the last message
|
/// of the last message
|
||||||
///
|
///
|
||||||
// TODO: add a end_offsets index parameter for "continuation"
|
// TODO: add a end_offsets index parameter for "continuation"
|
||||||
// TODO: test max size of parameters that can be passed into CPI call
|
// 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;
|
let mut offset = 0u16;
|
||||||
|
|
||||||
for (i, v) in values.iter().enumerate() {
|
for (i, v) in values.iter().enumerate() {
|
||||||
let start = offset;
|
let start = offset;
|
||||||
let len = u16::try_from(v.len());
|
let len = u16::try_from(v.len());
|
||||||
if len.is_err() {
|
if len.is_err() {
|
||||||
|
msg!("len err");
|
||||||
return (i, start);
|
return (i, start);
|
||||||
}
|
}
|
||||||
let end = offset.checked_add(len.unwrap());
|
let end = offset.checked_add(len.unwrap());
|
||||||
|
@ -96,23 +97,22 @@ impl MessageBuffer {
|
||||||
return (i, start);
|
return (i, start);
|
||||||
}
|
}
|
||||||
let end = end.unwrap();
|
let end = end.unwrap();
|
||||||
if end > self.messages.len() as u16 {
|
if end > destination.len() as u16 {
|
||||||
return (i, start);
|
return (i, start);
|
||||||
}
|
}
|
||||||
self.header.end_offsets[i] = end;
|
self.end_offsets[i] = end;
|
||||||
self.messages[(start as usize)..(end as usize)].copy_from_slice(v);
|
destination[(start as usize)..(end as usize)].copy_from_slice(v);
|
||||||
offset = end
|
offset = end
|
||||||
}
|
}
|
||||||
(values.len(), offset)
|
(values.len(), offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn derive_pda(&self, cpi_caller: Pubkey, base_account: Pubkey) -> Result<Pubkey> {
|
fn derive_pda(&self, cpi_caller: Pubkey, base_account: Pubkey) -> Result<Pubkey> {
|
||||||
let res = Pubkey::create_program_address(
|
let res = Pubkey::create_program_address(
|
||||||
accumulator_input_seeds!(self, cpi_caller, base_account),
|
accumulator_input_seeds!(self, cpi_caller, base_account),
|
||||||
&crate::ID,
|
&crate::ID,
|
||||||
)
|
)
|
||||||
.map_err(|_| AccumulatorUpdaterError::InvalidPDA)?;
|
.map_err(|_| MessageBufferError::InvalidPDA)?;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,16 +123,19 @@ impl MessageBuffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use {
|
use {
|
||||||
super::*,
|
super::*,
|
||||||
bytemuck::bytes_of,
|
anchor_lang::solana_program::keccak::hashv,
|
||||||
std::mem::{
|
bytemuck::bytes_of_mut,
|
||||||
|
std::{
|
||||||
|
io::Write,
|
||||||
|
mem::{
|
||||||
align_of,
|
align_of,
|
||||||
size_of,
|
size_of,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn data_bytes(data: Vec<u8>) -> Vec<u8> {
|
fn data_bytes(data: Vec<u8>) -> Vec<u8> {
|
||||||
|
@ -143,18 +146,22 @@ mod test {
|
||||||
bytes
|
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]
|
#[test]
|
||||||
fn test_sizes_and_alignments() {
|
fn test_sizes_and_alignments() {
|
||||||
let (header_idx_size, header_idx_align) =
|
let (message_buffer_size, message_buffer_align) =
|
||||||
(size_of::<BufferHeader>(), align_of::<BufferHeader>());
|
(size_of::<MessageBuffer>(), align_of::<MessageBuffer>());
|
||||||
|
|
||||||
let (input_size, input_align) = (size_of::<MessageBuffer>(), align_of::<MessageBuffer>());
|
assert_eq!(message_buffer_size, 514);
|
||||||
|
assert_eq!(message_buffer_align, 2);
|
||||||
assert_eq!(header_idx_size, 514);
|
|
||||||
assert_eq!(header_idx_align, 2);
|
|
||||||
assert_eq!(input_size, 10_232);
|
|
||||||
assert_eq!(input_align, 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -162,35 +169,50 @@ mod test {
|
||||||
let data = vec![vec![12, 34], vec![56, 78, 90]];
|
let data = vec![vec![12, 34], vec![56, 78, 90]];
|
||||||
let data_bytes: Vec<Vec<u8>> = data.into_iter().map(data_bytes).collect();
|
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_msgs, 2);
|
||||||
assert_eq!(num_bytes, 5);
|
assert_eq!(num_bytes, 5);
|
||||||
|
|
||||||
|
|
||||||
assert_eq!(accumulator_input.header.end_offsets[0], 2);
|
assert_eq!(message_buffer.end_offsets[0], 2);
|
||||||
assert_eq!(accumulator_input.header.end_offsets[1], 5);
|
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;
|
|
||||||
|
|
||||||
|
|
||||||
let iter = accumulator_input
|
// // The header_len field represents the size of all data prior to the message bytes.
|
||||||
.header
|
// // This includes the account discriminator, which is not part of the header struct.
|
||||||
.end_offsets
|
// // Subtract the size of the discriminator (8 bytes) to compensate
|
||||||
.iter()
|
// let header_len = message_buffer.header_len as usize - 8;
|
||||||
.take_while(|x| **x != 0);
|
let header_len = message_buffer.header_len as usize;
|
||||||
|
|
||||||
|
|
||||||
|
let iter = message_buffer.end_offsets.iter().take_while(|x| **x != 0);
|
||||||
let mut start = header_len;
|
let mut start = header_len;
|
||||||
let mut data_iter = data_bytes.iter();
|
let mut data_iter = data_bytes.iter();
|
||||||
for offset in iter {
|
for offset in iter {
|
||||||
let end_offset = header_len + *offset as usize;
|
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();
|
let expected_data = data_iter.next().unwrap();
|
||||||
assert_eq!(message_buffer_data, expected_data.as_slice());
|
assert_eq!(message_buffer_data, expected_data.as_slice());
|
||||||
start = end_offset;
|
start = end_offset;
|
||||||
|
@ -202,40 +224,48 @@ mod test {
|
||||||
let data = vec![vec![0u8; 9_718 - 2], vec![0u8], vec![0u8; 2]];
|
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 data_bytes: Vec<Vec<u8>> = data.into_iter().map(data_bytes).collect();
|
||||||
|
|
||||||
let message_buffer = &mut MessageBuffer::new(0);
|
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_msgs, 2);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
num_bytes,
|
num_bytes,
|
||||||
data_bytes[0..2].iter().map(|x| x.len()).sum::<usize>() as u16
|
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.
|
let iter = message_buffer.end_offsets.iter().take_while(|x| **x != 0);
|
||||||
// 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 mut start = header_len;
|
let mut start = header_len;
|
||||||
let mut data_iter = data_bytes.iter();
|
let mut data_iter = data_bytes.iter();
|
||||||
for offset in iter {
|
for offset in iter {
|
||||||
let end_offset = header_len + *offset as usize;
|
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();
|
let expected_data = data_iter.next().unwrap();
|
||||||
assert_eq!(message_buffer_data, expected_data.as_slice());
|
assert_eq!(message_buffer_data, expected_data.as_slice());
|
||||||
start = end_offset;
|
start = end_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(message_buffer.header.end_offsets[2], 0);
|
assert_eq!(message_buffer.end_offsets[2], 0);
|
||||||
}
|
}
|
||||||
|
//
|
||||||
#[test]
|
#[test]
|
||||||
fn test_put_all_long_vec() {
|
fn test_put_all_long_vec() {
|
||||||
let data = vec![
|
let data = vec![
|
||||||
|
@ -247,40 +277,52 @@ mod test {
|
||||||
];
|
];
|
||||||
|
|
||||||
let data_bytes: Vec<Vec<u8>> = data.into_iter().map(data_bytes).collect();
|
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 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_msgs, 3);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
num_bytes,
|
num_bytes,
|
||||||
data_bytes[0..3].iter().map(|x| x.len()).sum::<usize>() as u16
|
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
|
let iter = message_buffer.end_offsets.iter().take_while(|x| **x != 0);
|
||||||
// `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 mut start = header_len;
|
let mut start = header_len;
|
||||||
let mut data_iter = data_bytes.iter();
|
let mut data_iter = data_bytes.iter();
|
||||||
for offset in iter {
|
for offset in iter {
|
||||||
let end_offset = header_len + *offset as usize;
|
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();
|
let expected_data = data_iter.next().unwrap();
|
||||||
assert_eq!(message_buffer_data, expected_data.as_slice());
|
assert_eq!(message_buffer_data, expected_data.as_slice());
|
||||||
start = end_offset;
|
start = end_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(message_buffer.header.end_offsets[0], 9_715);
|
assert_eq!(message_buffer.end_offsets[0], 9_715);
|
||||||
assert_eq!(message_buffer.header.end_offsets[1], 9_716);
|
assert_eq!(message_buffer.end_offsets[1], 9_716);
|
||||||
assert_eq!(message_buffer.header.end_offsets[2], 9_717);
|
assert_eq!(message_buffer.end_offsets[2], 9_717);
|
||||||
assert_eq!(message_buffer.header.end_offsets[3], 0);
|
assert_eq!(message_buffer.end_offsets[3], 0);
|
||||||
assert_eq!(message_buffer.header.end_offsets[4], 0);
|
assert_eq!(message_buffer.end_offsets[4], 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use {
|
use {
|
||||||
crate::AccumulatorUpdaterError,
|
crate::MessageBufferError,
|
||||||
anchor_lang::prelude::*,
|
anchor_lang::prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ use {
|
||||||
#[derive(InitSpace)]
|
#[derive(InitSpace)]
|
||||||
pub struct Whitelist {
|
pub struct Whitelist {
|
||||||
pub bump: u8,
|
pub bump: u8,
|
||||||
pub authority: Pubkey,
|
pub admin: Pubkey,
|
||||||
#[max_len(32)]
|
#[max_len(32)]
|
||||||
pub allowed_programs: Vec<Pubkey>,
|
pub allowed_programs: Vec<Pubkey>,
|
||||||
}
|
}
|
||||||
|
@ -19,18 +19,26 @@ impl Whitelist {
|
||||||
pub fn validate_programs(&self, allowed_programs: &[Pubkey]) -> Result<()> {
|
pub fn validate_programs(&self, allowed_programs: &[Pubkey]) -> Result<()> {
|
||||||
require!(
|
require!(
|
||||||
!self.allowed_programs.contains(&Pubkey::default()),
|
!self.allowed_programs.contains(&Pubkey::default()),
|
||||||
AccumulatorUpdaterError::InvalidAllowedProgram
|
MessageBufferError::InvalidAllowedProgram
|
||||||
);
|
);
|
||||||
require_gte!(
|
require_gte!(
|
||||||
32,
|
32,
|
||||||
allowed_programs.len(),
|
allowed_programs.len(),
|
||||||
AccumulatorUpdaterError::MaximumAllowedProgramsExceeded
|
MessageBufferError::MaximumAllowedProgramsExceeded
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate_new_authority(&self, new_authority: Pubkey) -> Result<()> {
|
pub fn validate_new_admin(&self, new_admin: Pubkey) -> Result<()> {
|
||||||
require_keys_neq!(new_authority, Pubkey::default());
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,10 +59,7 @@ impl<'info> WhitelistVerifier<'info> {
|
||||||
pub fn is_allowed(&self) -> Result<Pubkey> {
|
pub fn is_allowed(&self) -> Result<Pubkey> {
|
||||||
let auth = self.cpi_caller_auth.key();
|
let auth = self.cpi_caller_auth.key();
|
||||||
let whitelist = &self.whitelist;
|
let whitelist = &self.whitelist;
|
||||||
require!(
|
whitelist.is_allowed_program_auth(&auth)?;
|
||||||
whitelist.allowed_programs.contains(&auth),
|
|
||||||
AccumulatorUpdaterError::CallerNotAllowed
|
|
||||||
);
|
|
||||||
Ok(auth)
|
Ok(auth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,10 +62,8 @@ impl<'info> AddPrice<'info> {
|
||||||
inputs: Vec<Vec<u8>>,
|
inputs: Vec<Vec<u8>>,
|
||||||
) -> anchor_lang::Result<()> {
|
) -> anchor_lang::Result<()> {
|
||||||
let mut accounts = vec![
|
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.accumulator_whitelist.key(), false),
|
||||||
AccountMeta::new_readonly(ctx.accounts.auth.key(), true),
|
AccountMeta::new_readonly(ctx.accounts.auth.key(), true),
|
||||||
AccountMeta::new_readonly(ctx.accounts.system_program.key(), false),
|
|
||||||
];
|
];
|
||||||
accounts.extend_from_slice(
|
accounts.extend_from_slice(
|
||||||
&ctx.remaining_accounts
|
&ctx.remaining_accounts
|
||||||
|
@ -131,8 +129,6 @@ pub struct AddPrice<'info> {
|
||||||
pub pyth_price_account: AccountLoader<'info, PriceAccount>,
|
pub pyth_price_account: AccountLoader<'info, PriceAccount>,
|
||||||
#[account(mut)]
|
#[account(mut)]
|
||||||
pub payer: Signer<'info>,
|
pub payer: Signer<'info>,
|
||||||
#[account(mut)]
|
|
||||||
pub fund: SystemAccount<'info>,
|
|
||||||
/// also needed for accumulator_updater
|
/// also needed for accumulator_updater
|
||||||
pub system_program: Program<'info, System>,
|
pub system_program: Program<'info, System>,
|
||||||
/// CHECK: whitelist
|
/// CHECK: whitelist
|
||||||
|
@ -147,5 +143,5 @@ pub struct AddPrice<'info> {
|
||||||
pub auth: SystemAccount<'info>,
|
pub auth: SystemAccount<'info>,
|
||||||
pub message_buffer_program: Program<'info, MessageBufferProgram>,
|
pub message_buffer_program: Program<'info, MessageBufferProgram>,
|
||||||
// Remaining Accounts
|
// Remaining Accounts
|
||||||
// should all be new uninitialized accounts
|
// MessageBuffer PDA
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,10 +42,6 @@ pub struct UpdatePrice<'info> {
|
||||||
bump,
|
bump,
|
||||||
)]
|
)]
|
||||||
pub pyth_price_account: AccountLoader<'info, PriceAccount>,
|
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
|
/// CHECK: whitelist
|
||||||
pub accumulator_whitelist: UncheckedAccount<'info>,
|
pub accumulator_whitelist: UncheckedAccount<'info>,
|
||||||
#[account(
|
#[account(
|
||||||
|
@ -90,10 +86,8 @@ impl<'info> UpdatePrice<'info> {
|
||||||
values: Vec<Vec<u8>>,
|
values: Vec<Vec<u8>>,
|
||||||
) -> anchor_lang::Result<()> {
|
) -> anchor_lang::Result<()> {
|
||||||
let mut accounts = vec![
|
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.accumulator_whitelist.key(), false),
|
||||||
AccountMeta::new_readonly(ctx.accounts.auth.key(), true),
|
AccountMeta::new_readonly(ctx.accounts.auth.key(), true),
|
||||||
AccountMeta::new_readonly(ctx.accounts.system_program.key(), false),
|
|
||||||
];
|
];
|
||||||
accounts.extend_from_slice(
|
accounts.extend_from_slice(
|
||||||
&ctx.remaining_accounts
|
&ctx.remaining_accounts
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import * as anchor from "@coral-xyz/anchor";
|
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 { MessageBuffer } from "../target/types/message_buffer";
|
||||||
import { MockCpiCaller } from "../target/types/mock_cpi_caller";
|
import { MockCpiCaller } from "../target/types/mock_cpi_caller";
|
||||||
import lumina from "@lumina-dev/test";
|
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
|
// Enables tool that runs in local browser for easier debugging of
|
||||||
// transactions in this test - https://lumina.fyi/debug
|
// transactions in this test - https://lumina.fyi/debug
|
||||||
lumina();
|
// lumina();
|
||||||
|
|
||||||
const messageBufferProgram = anchor.workspace
|
const messageBufferProgram = anchor.workspace
|
||||||
.MessageBuffer as Program<MessageBuffer>;
|
.MessageBuffer as Program<MessageBuffer>;
|
||||||
const mockCpiProg = anchor.workspace.MockCpiCaller as Program<MockCpiCaller>;
|
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(
|
const [mockCpiCallerAuth] = anchor.web3.PublicKey.findProgramAddressSync(
|
||||||
[messageBufferProgram.programId.toBuffer(), Buffer.from("cpi")],
|
[messageBufferProgram.programId.toBuffer(), Buffer.from("cpi")],
|
||||||
mockCpiProg.programId
|
mockCpiProg.programId
|
||||||
);
|
);
|
||||||
const [fundPda] = anchor.web3.PublicKey.findProgramAddressSync(
|
|
||||||
[Buffer.from("fund")],
|
|
||||||
messageBufferProgram.programId
|
|
||||||
);
|
|
||||||
|
|
||||||
const pythPriceAccountId = new anchor.BN(1);
|
const pythPriceAccountId = new anchor.BN(1);
|
||||||
const addPriceParams = {
|
const addPriceParams = {
|
||||||
|
@ -41,12 +43,41 @@ const [pythPriceAccountPk] = anchor.web3.PublicKey.findProgramAddressSync(
|
||||||
mockCpiProg.programId
|
mockCpiProg.programId
|
||||||
);
|
);
|
||||||
const MESSAGE = Buffer.from("message");
|
const MESSAGE = Buffer.from("message");
|
||||||
const [accumulatorPdaKey] = anchor.web3.PublicKey.findProgramAddressSync(
|
const [accumulatorPdaKey, accumulatorPdaBump] =
|
||||||
|
anchor.web3.PublicKey.findProgramAddressSync(
|
||||||
[mockCpiCallerAuth.toBuffer(), MESSAGE, pythPriceAccountPk.toBuffer()],
|
[mockCpiCallerAuth.toBuffer(), MESSAGE, pythPriceAccountPk.toBuffer()],
|
||||||
messageBufferProgram.programId
|
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;
|
let fundBalance = 100 * anchor.web3.LAMPORTS_PER_SOL;
|
||||||
|
|
||||||
|
const discriminator = BorshAccountsCoder.accountDiscriminator("MessageBuffer");
|
||||||
|
const messageBufferDiscriminator = bs58.encode(discriminator);
|
||||||
|
|
||||||
describe("accumulator_updater", () => {
|
describe("accumulator_updater", () => {
|
||||||
// Configure the client to use the local cluster.
|
// Configure the client to use the local cluster.
|
||||||
let provider = anchor.AnchorProvider.env();
|
let provider = anchor.AnchorProvider.env();
|
||||||
|
@ -58,14 +89,26 @@ describe("accumulator_updater", () => {
|
||||||
messageBufferProgram.programId
|
messageBufferProgram.programId
|
||||||
);
|
);
|
||||||
|
|
||||||
before("transfer lamports to the fund", async () => {
|
before("transfer lamports to needed accounts", async () => {
|
||||||
await provider.connection.requestAirdrop(fundPda, fundBalance);
|
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 () => {
|
it("Is initialized!", async () => {
|
||||||
// Add your test here.
|
// Add your test here.
|
||||||
const tx = await messageBufferProgram.methods
|
const tx = await messageBufferProgram.methods
|
||||||
.initialize(whitelistAuthority.publicKey)
|
.initialize(whitelistAdmin.publicKey)
|
||||||
.accounts({})
|
.accounts({})
|
||||||
.rpc();
|
.rpc();
|
||||||
console.log("Your transaction signature", tx);
|
console.log("Your transaction signature", tx);
|
||||||
|
@ -74,7 +117,7 @@ describe("accumulator_updater", () => {
|
||||||
whitelistPubkey
|
whitelistPubkey
|
||||||
);
|
);
|
||||||
assert.strictEqual(whitelist.bump, whitelistBump);
|
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)}`);
|
console.info(`whitelist: ${JSON.stringify(whitelist)}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -83,9 +126,9 @@ describe("accumulator_updater", () => {
|
||||||
await messageBufferProgram.methods
|
await messageBufferProgram.methods
|
||||||
.setAllowedPrograms(allowedProgramAuthorities)
|
.setAllowedPrograms(allowedProgramAuthorities)
|
||||||
.accounts({
|
.accounts({
|
||||||
authority: whitelistAuthority.publicKey,
|
admin: whitelistAdmin.publicKey,
|
||||||
})
|
})
|
||||||
.signers([whitelistAuthority])
|
.signers([whitelistAdmin])
|
||||||
.rpc();
|
.rpc();
|
||||||
const whitelist = await messageBufferProgram.account.whitelist.fetch(
|
const whitelist = await messageBufferProgram.account.whitelist.fetch(
|
||||||
whitelistPubkey
|
whitelistPubkey
|
||||||
|
@ -100,29 +143,131 @@ describe("accumulator_updater", () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Updates the whitelist authority", async () => {
|
it("Creates a buffer", async () => {
|
||||||
const newWhitelistAuthority = anchor.web3.Keypair.generate();
|
const accumulatorPdaMetas = [
|
||||||
|
{
|
||||||
|
pubkey: accumulatorPdaKey,
|
||||||
|
isSigner: false,
|
||||||
|
isWritable: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
await messageBufferProgram.methods
|
await messageBufferProgram.methods
|
||||||
.updateWhitelistAuthority(newWhitelistAuthority.publicKey)
|
.createBuffer(mockCpiCallerAuth, pythPriceAccountPk, 1024 * 8)
|
||||||
.accounts({
|
.accounts({
|
||||||
authority: whitelistAuthority.publicKey,
|
whitelist: whitelistPubkey,
|
||||||
|
admin: whitelistAdmin.publicKey,
|
||||||
|
systemProgram: anchor.web3.SystemProgram.programId,
|
||||||
})
|
})
|
||||||
.signers([whitelistAuthority])
|
.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 newWhitelistAdmin = anchor.web3.Keypair.generate();
|
||||||
|
await messageBufferProgram.methods
|
||||||
|
.updateWhitelistAdmin(newWhitelistAdmin.publicKey)
|
||||||
|
.accounts({
|
||||||
|
admin: whitelistAdmin.publicKey,
|
||||||
|
})
|
||||||
|
.signers([whitelistAdmin])
|
||||||
.rpc();
|
.rpc();
|
||||||
|
|
||||||
const whitelist = await messageBufferProgram.account.whitelist.fetch(
|
let whitelist = await messageBufferProgram.account.whitelist.fetch(
|
||||||
whitelistPubkey
|
whitelistPubkey
|
||||||
);
|
);
|
||||||
assert.isTrue(whitelist.authority.equals(newWhitelistAuthority.publicKey));
|
assert.isTrue(whitelist.admin.equals(newWhitelistAdmin.publicKey));
|
||||||
|
|
||||||
whitelistAuthority = newWhitelistAuthority;
|
// swap back to original authority
|
||||||
|
await messageBufferProgram.methods
|
||||||
|
.updateWhitelistAdmin(whitelistAdmin.publicKey)
|
||||||
|
.accounts({
|
||||||
|
admin: newWhitelistAdmin.publicKey,
|
||||||
|
})
|
||||||
|
.signers([newWhitelistAdmin])
|
||||||
|
.rpc();
|
||||||
|
|
||||||
|
whitelist = await messageBufferProgram.account.whitelist.fetch(
|
||||||
|
whitelistPubkey
|
||||||
|
);
|
||||||
|
assert.isTrue(whitelist.admin.equals(whitelistAdmin.publicKey));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Mock CPI program - AddPrice", async () => {
|
it("Mock CPI program - AddPrice", async () => {
|
||||||
const mockCpiCallerAddPriceTxPubkeys = await mockCpiProg.methods
|
const mockCpiCallerAddPriceTxPubkeys = await mockCpiProg.methods
|
||||||
.addPrice(addPriceParams)
|
.addPrice(addPriceParams)
|
||||||
.accounts({
|
.accounts({
|
||||||
fund: fundPda,
|
|
||||||
systemProgram: anchor.web3.SystemProgram.programId,
|
systemProgram: anchor.web3.SystemProgram.programId,
|
||||||
auth: mockCpiCallerAuth,
|
auth: mockCpiCallerAuth,
|
||||||
accumulatorWhitelist: whitelistPubkey,
|
accumulatorWhitelist: whitelistPubkey,
|
||||||
|
@ -183,10 +328,14 @@ describe("accumulator_updater", () => {
|
||||||
mockCpiCallerAddPriceTxPubkeys.pythPriceAccount
|
mockCpiCallerAddPriceTxPubkeys.pythPriceAccount
|
||||||
);
|
);
|
||||||
|
|
||||||
const messageBuffer =
|
const messageBufferAccount = await provider.connection.getAccountInfo(
|
||||||
await messageBufferProgram.account.messageBuffer.fetch(accumulatorPdaKey);
|
accumulatorPdaKey
|
||||||
|
);
|
||||||
|
|
||||||
const accumulatorPriceMessages = parseMessageBuffer(messageBuffer);
|
const accumulatorPriceMessages = parseMessageBuffer(
|
||||||
|
messageBufferProgram,
|
||||||
|
messageBufferAccount.data
|
||||||
|
);
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`accumulatorPriceMessages: ${JSON.stringify(
|
`accumulatorPriceMessages: ${JSON.stringify(
|
||||||
|
@ -200,29 +349,11 @@ describe("accumulator_updater", () => {
|
||||||
assert.isTrue(pm.price.eq(addPriceParams.price));
|
assert.isTrue(pm.price.eq(addPriceParams.price));
|
||||||
assert.isTrue(pm.priceExpo.eq(addPriceParams.priceExpo));
|
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 () => {
|
it("Fetches MessageBuffer using getProgramAccounts with discriminator", async () => {
|
||||||
let discriminator =
|
const messageBufferAccounts = await getProgramAccountsForMessageBuffers(
|
||||||
BorshAccountsCoder.accountDiscriminator("MessageBuffer");
|
provider.connection
|
||||||
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 msgBufferAcctKeys = messageBufferAccounts.map((ai) =>
|
const msgBufferAcctKeys = messageBufferAccounts.map((ai) =>
|
||||||
ai.pubkey.toString()
|
ai.pubkey.toString()
|
||||||
|
@ -231,7 +362,7 @@ describe("accumulator_updater", () => {
|
||||||
`messageBufferAccounts: ${JSON.stringify(msgBufferAcctKeys, null, 2)}`
|
`messageBufferAccounts: ${JSON.stringify(msgBufferAcctKeys, null, 2)}`
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.isTrue(messageBufferAccounts.length === 1);
|
assert.isTrue(messageBufferAccounts.length === 2);
|
||||||
msgBufferAcctKeys.includes(accumulatorPdaKey.toString());
|
msgBufferAcctKeys.includes(accumulatorPdaKey.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -250,7 +381,6 @@ describe("accumulator_updater", () => {
|
||||||
await mockCpiProg.methods
|
await mockCpiProg.methods
|
||||||
.updatePrice(updatePriceParams)
|
.updatePrice(updatePriceParams)
|
||||||
.accounts({
|
.accounts({
|
||||||
fund: fundPda,
|
|
||||||
pythPriceAccount: pythPriceAccountPk,
|
pythPriceAccount: pythPriceAccountPk,
|
||||||
auth: mockCpiCallerAuth,
|
auth: mockCpiCallerAuth,
|
||||||
accumulatorWhitelist: whitelistPubkey,
|
accumulatorWhitelist: whitelistPubkey,
|
||||||
|
@ -271,11 +401,16 @@ describe("accumulator_updater", () => {
|
||||||
assert.isTrue(pythPriceAccount.priceExpo.eq(updatePriceParams.priceExpo));
|
assert.isTrue(pythPriceAccount.priceExpo.eq(updatePriceParams.priceExpo));
|
||||||
assert.isTrue(pythPriceAccount.ema.eq(updatePriceParams.ema));
|
assert.isTrue(pythPriceAccount.ema.eq(updatePriceParams.ema));
|
||||||
assert.isTrue(pythPriceAccount.emaExpo.eq(updatePriceParams.emaExpo));
|
assert.isTrue(pythPriceAccount.emaExpo.eq(updatePriceParams.emaExpo));
|
||||||
const messageBuffer =
|
|
||||||
await messageBufferProgram.account.messageBuffer.fetch(
|
const messageBufferAccountData = await getMessageBuffer(
|
||||||
accumulatorPdaMeta.pubkey
|
provider.connection,
|
||||||
|
accumulatorPdaKey
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedAccumulatorPriceMessages = parseMessageBuffer(
|
||||||
|
messageBufferProgram,
|
||||||
|
messageBufferAccountData
|
||||||
);
|
);
|
||||||
const updatedAccumulatorPriceMessages = parseMessageBuffer(messageBuffer);
|
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`updatedAccumulatorPriceMessages: ${JSON.stringify(
|
`updatedAccumulatorPriceMessages: ${JSON.stringify(
|
||||||
|
@ -299,11 +434,12 @@ describe("accumulator_updater", () => {
|
||||||
let testCase = testCases[i];
|
let testCase = testCases[i];
|
||||||
console.info(`testCase: ${testCase}`);
|
console.info(`testCase: ${testCase}`);
|
||||||
const updatePriceParams = {
|
const updatePriceParams = {
|
||||||
price: new anchor.BN(10 * i + 5),
|
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),
|
ema: new anchor.BN(10 * i + 7),
|
||||||
emaExpo: new anchor.BN(10 * i + 8),
|
emaExpo: new anchor.BN(10 * i + 8),
|
||||||
};
|
};
|
||||||
|
console.log(`updatePriceParams: ${JSON.stringify(updatePriceParams)}`);
|
||||||
|
|
||||||
let accumulatorPdaMeta = getAccumulatorPdaMeta(
|
let accumulatorPdaMeta = getAccumulatorPdaMeta(
|
||||||
mockCpiCallerAuth,
|
mockCpiCallerAuth,
|
||||||
|
@ -312,7 +448,6 @@ describe("accumulator_updater", () => {
|
||||||
await mockCpiProg.methods
|
await mockCpiProg.methods
|
||||||
.cpiMaxTest(updatePriceParams, testCase)
|
.cpiMaxTest(updatePriceParams, testCase)
|
||||||
.accounts({
|
.accounts({
|
||||||
fund: fundPda,
|
|
||||||
pythPriceAccount: pythPriceAccountPk,
|
pythPriceAccount: pythPriceAccountPk,
|
||||||
auth: mockCpiCallerAuth,
|
auth: mockCpiCallerAuth,
|
||||||
accumulatorWhitelist: whitelistPubkey,
|
accumulatorWhitelist: whitelistPubkey,
|
||||||
|
@ -333,28 +468,36 @@ describe("accumulator_updater", () => {
|
||||||
assert.isTrue(pythPriceAccount.priceExpo.eq(updatePriceParams.priceExpo));
|
assert.isTrue(pythPriceAccount.priceExpo.eq(updatePriceParams.priceExpo));
|
||||||
assert.isTrue(pythPriceAccount.ema.eq(updatePriceParams.ema));
|
assert.isTrue(pythPriceAccount.ema.eq(updatePriceParams.ema));
|
||||||
assert.isTrue(pythPriceAccount.emaExpo.eq(updatePriceParams.emaExpo));
|
assert.isTrue(pythPriceAccount.emaExpo.eq(updatePriceParams.emaExpo));
|
||||||
const messageBuffer =
|
|
||||||
await messageBufferProgram.account.messageBuffer.fetch(
|
|
||||||
accumulatorPdaMeta.pubkey
|
|
||||||
);
|
|
||||||
const updatedAccumulatorPriceMessages = parseMessageBuffer(messageBuffer);
|
|
||||||
|
|
||||||
console.log(
|
const messageBufferAccountData = await getMessageBuffer(
|
||||||
`updatedAccumulatorPriceMessages: ${JSON.stringify(
|
provider.connection,
|
||||||
updatedAccumulatorPriceMessages,
|
accumulatorPdaKey
|
||||||
null,
|
|
||||||
2
|
|
||||||
)}`
|
|
||||||
);
|
);
|
||||||
updatedAccumulatorPriceMessages.forEach((pm) => {
|
|
||||||
assert.isTrue(pm.id.eq(addPriceParams.id));
|
const messageBufferHeader = deserializeMessageBufferHeader(
|
||||||
assert.isTrue(pm.price.eq(updatePriceParams.price));
|
messageBufferProgram,
|
||||||
assert.isTrue(pm.priceExpo.eq(updatePriceParams.priceExpo));
|
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
|
// with loosen CPI feature activated, max cpi instruction size len is 10KB
|
||||||
let testCases = [[1024, 2048, 4096, 8192]];
|
let testCases = [[1024, 2048, 4096, 8192]];
|
||||||
// for (let i = 1; i < 8; i++) {
|
// for (let i = 1; i < 8; i++) {
|
||||||
|
@ -363,7 +506,7 @@ describe("accumulator_updater", () => {
|
||||||
console.info(`testCase: ${testCase}`);
|
console.info(`testCase: ${testCase}`);
|
||||||
const updatePriceParams = {
|
const updatePriceParams = {
|
||||||
price: new anchor.BN(10 * i + 5),
|
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),
|
ema: new anchor.BN(10 * i + 7),
|
||||||
emaExpo: new anchor.BN(10 * i + 8),
|
emaExpo: new anchor.BN(10 * i + 8),
|
||||||
};
|
};
|
||||||
|
@ -377,7 +520,6 @@ describe("accumulator_updater", () => {
|
||||||
await mockCpiProg.methods
|
await mockCpiProg.methods
|
||||||
.cpiMaxTest(updatePriceParams, testCase)
|
.cpiMaxTest(updatePriceParams, testCase)
|
||||||
.accounts({
|
.accounts({
|
||||||
fund: fundPda,
|
|
||||||
pythPriceAccount: pythPriceAccountPk,
|
pythPriceAccount: pythPriceAccountPk,
|
||||||
auth: mockCpiCallerAuth,
|
auth: mockCpiCallerAuth,
|
||||||
accumulatorWhitelist: whitelistPubkey,
|
accumulatorWhitelist: whitelistPubkey,
|
||||||
|
@ -396,6 +538,192 @@ describe("accumulator_updater", () => {
|
||||||
assert.ok(errorThrown);
|
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 = (
|
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
|
// Parses MessageBuffer.data into a PriceAccount or PriceOnly object based on the
|
||||||
// accountType and accountSchema.
|
// accountType and accountSchema.
|
||||||
function parseMessageBuffer({
|
function parseMessageBuffer(
|
||||||
header,
|
messageBufferProgram: Program<MessageBuffer>,
|
||||||
messages,
|
accountData: Buffer
|
||||||
}: {
|
): AccumulatorPriceMessage[] {
|
||||||
header: BufferHeader;
|
const msgBufferHeader = deserializeMessageBufferHeader(
|
||||||
messages: number[];
|
messageBufferProgram,
|
||||||
}): AccumulatorPriceMessage[] {
|
accountData
|
||||||
const accumulatorMessages = [];
|
);
|
||||||
let dataBuffer = Buffer.from(messages);
|
|
||||||
|
|
||||||
|
const accumulatorMessages = [];
|
||||||
|
// let dataBuffer = Buffer.from(messages);
|
||||||
|
|
||||||
|
let dataBuffer = accountData.subarray(
|
||||||
|
msgBufferHeader.headerLen,
|
||||||
|
accountData.length
|
||||||
|
);
|
||||||
let start = 0;
|
let start = 0;
|
||||||
for (let i = 0; i < header.endOffsets.length; i++) {
|
for (let i = 0; i < msgBufferHeader.endOffsets.length; i++) {
|
||||||
const endOffset = header.endOffsets[i];
|
const endOffset = msgBufferHeader.endOffsets[i];
|
||||||
|
|
||||||
if (endOffset == 0) {
|
if (endOffset == 0) {
|
||||||
console.log(`endOffset = 0. breaking`);
|
console.log(`endOffset = 0. breaking`);
|
||||||
|
@ -459,12 +799,22 @@ type MessageHeader = {
|
||||||
size: number;
|
size: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type MessageBuffer = {
|
type MessageBufferType = {
|
||||||
header: MessageHeader;
|
header: MessageHeader;
|
||||||
data: Buffer;
|
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;
|
let offset = 0;
|
||||||
|
|
||||||
const schema = data.readInt8(offset);
|
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 AccumulatorPriceMessage = FullPriceMessage | CompactPriceMessage;
|
||||||
|
|
||||||
type FullPriceMessage = {
|
type FullPriceMessage = {
|
||||||
|
@ -522,3 +870,19 @@ function parseCompactPriceMessage(data: Uint8Array): CompactPriceMessage {
|
||||||
priceExpo: new anchor.BN(data.subarray(16, 24), "be"),
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue