Skip to content

Commit 794bd84

Browse files
swimrickyjayantk
andauthored
[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 <[email protected]>
1 parent 6a5b1c4 commit 794bd84

File tree

13 files changed

+1113
-351
lines changed

13 files changed

+1113
-351
lines changed

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,13 @@ repos:
7171
language: "rust"
7272
entry: cargo +nightly fmt --manifest-path ./message_buffer/Cargo.toml --all -- --config-path rustfmt.toml
7373
pass_filenames: false
74-
files: accumulator_updater
74+
files: message_buffer
7575
- id: cargo-clippy-message-buffer
7676
name: Cargo clippy for message buffer contract
7777
language: "rust"
7878
entry: cargo +nightly clippy --manifest-path ./message_buffer/Cargo.toml --tests --fix --allow-dirty --allow-staged -- -D warnings
7979
pass_filenames: false
80-
files: accumulator_updater
80+
files: message_buffer
8181
# Hooks for solana receiver contract
8282
- id: cargo-fmt-solana-receiver
8383
name: Cargo format for solana target chain contract
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
use {
2+
crate::{
3+
instructions::is_uninitialized_account,
4+
state::*,
5+
MessageBufferError,
6+
MESSAGE,
7+
},
8+
anchor_lang::{
9+
prelude::*,
10+
solana_program::entrypoint::MAX_PERMITTED_DATA_INCREASE,
11+
system_program::{
12+
self,
13+
Allocate,
14+
Assign,
15+
Transfer,
16+
},
17+
},
18+
};
19+
20+
pub fn create_buffer<'info>(
21+
ctx: Context<'_, '_, '_, 'info, CreateBuffer<'info>>,
22+
allowed_program_auth: Pubkey,
23+
base_account_key: Pubkey,
24+
target_size: u32,
25+
) -> Result<()> {
26+
let buffer_account = ctx
27+
.remaining_accounts
28+
.first()
29+
.ok_or(MessageBufferError::MessageBufferNotProvided)?;
30+
31+
ctx.accounts
32+
.whitelist
33+
.is_allowed_program_auth(&allowed_program_auth)?;
34+
35+
require_gte!(
36+
target_size,
37+
MessageBuffer::HEADER_LEN as u32,
38+
MessageBufferError::MessageBufferTooSmall
39+
);
40+
41+
require_gte!(
42+
MAX_PERMITTED_DATA_INCREASE,
43+
target_size as usize,
44+
MessageBufferError::TargetSizeDeltaExceeded
45+
);
46+
if is_uninitialized_account(buffer_account) {
47+
let (pda, bump) = Pubkey::find_program_address(
48+
&[
49+
allowed_program_auth.as_ref(),
50+
MESSAGE.as_bytes(),
51+
base_account_key.as_ref(),
52+
],
53+
&crate::ID,
54+
);
55+
require_keys_eq!(buffer_account.key(), pda);
56+
let signer_seeds = [
57+
allowed_program_auth.as_ref(),
58+
MESSAGE.as_bytes(),
59+
base_account_key.as_ref(),
60+
&[bump],
61+
];
62+
63+
CreateBuffer::create_account(
64+
buffer_account,
65+
target_size as usize,
66+
&ctx.accounts.admin,
67+
&[signer_seeds.as_slice()],
68+
&ctx.accounts.system_program,
69+
)?;
70+
71+
let loader =
72+
AccountLoader::<MessageBuffer>::try_from_unchecked(&crate::ID, buffer_account)?;
73+
{
74+
let mut message_buffer = loader.load_init()?;
75+
*message_buffer = MessageBuffer::new(bump);
76+
}
77+
loader.exit(&crate::ID)?;
78+
}
79+
80+
Ok(())
81+
}
82+
83+
84+
#[derive(Accounts)]
85+
pub struct CreateBuffer<'info> {
86+
#[account(
87+
seeds = [b"message".as_ref(), b"whitelist".as_ref()],
88+
bump = whitelist.bump,
89+
has_one = admin,
90+
)]
91+
pub whitelist: Account<'info, Whitelist>,
92+
93+
// Also pays for account creation
94+
#[account(mut)]
95+
pub admin: Signer<'info>,
96+
97+
pub system_program: Program<'info, System>,
98+
// remaining_accounts: - [AccumulatorInput PDA]
99+
}
100+
101+
102+
impl<'info> CreateBuffer<'info> {
103+
/// Manually invoke transfer, allocate & assign ixs to create an account
104+
/// to handle situation where an account already has lamports
105+
/// since system_program::create_account will fail in this case
106+
fn create_account<'a>(
107+
new_account_info: &AccountInfo<'a>,
108+
space: usize,
109+
payer: &Signer<'a>,
110+
seeds: &[&[&[u8]]],
111+
system_program: &AccountInfo<'a>,
112+
) -> Result<()> {
113+
let target_rent = Rent::get()?.minimum_balance(space);
114+
if new_account_info.lamports() < target_rent {
115+
system_program::transfer(
116+
CpiContext::new_with_signer(
117+
system_program.to_account_info(),
118+
Transfer {
119+
from: payer.to_account_info(),
120+
to: new_account_info.to_account_info(),
121+
},
122+
seeds,
123+
),
124+
target_rent - new_account_info.lamports(),
125+
)?;
126+
};
127+
128+
system_program::allocate(
129+
CpiContext::new_with_signer(
130+
system_program.to_account_info(),
131+
Allocate {
132+
account_to_allocate: new_account_info.to_account_info(),
133+
},
134+
seeds,
135+
),
136+
space.try_into().unwrap(),
137+
)?;
138+
139+
system_program::assign(
140+
CpiContext::new_with_signer(
141+
system_program.to_account_info(),
142+
Assign {
143+
account_to_assign: new_account_info.to_account_info(),
144+
},
145+
seeds,
146+
),
147+
&crate::ID,
148+
)?;
149+
150+
Ok(())
151+
}
152+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
use {
2+
crate::{
3+
instructions::verify_message_buffer,
4+
state::*,
5+
MessageBufferError,
6+
MESSAGE,
7+
},
8+
anchor_lang::prelude::*,
9+
};
10+
11+
pub fn delete_buffer<'info>(
12+
ctx: Context<'_, '_, '_, 'info, DeleteBuffer<'info>>,
13+
allowed_program_auth: Pubkey,
14+
base_account_key: Pubkey,
15+
bump: u8,
16+
) -> Result<()> {
17+
let message_buffer_account_info = ctx
18+
.remaining_accounts
19+
.first()
20+
.ok_or(MessageBufferError::MessageBufferNotProvided)?;
21+
22+
ctx.accounts
23+
.whitelist
24+
.is_allowed_program_auth(&allowed_program_auth)?;
25+
26+
verify_message_buffer(message_buffer_account_info)?;
27+
28+
let expected_key = Pubkey::create_program_address(
29+
&[
30+
allowed_program_auth.as_ref(),
31+
MESSAGE.as_bytes(),
32+
base_account_key.as_ref(),
33+
&[bump],
34+
],
35+
&crate::ID,
36+
)
37+
.map_err(|_| MessageBufferError::InvalidPDA)?;
38+
39+
require_keys_eq!(
40+
message_buffer_account_info.key(),
41+
expected_key,
42+
MessageBufferError::InvalidPDA
43+
);
44+
let loader = AccountLoader::<MessageBuffer>::try_from_unchecked(
45+
&crate::ID,
46+
message_buffer_account_info,
47+
)?;
48+
loader.close(ctx.accounts.admin.to_account_info())?;
49+
Ok(())
50+
}
51+
52+
#[derive(Accounts)]
53+
pub struct DeleteBuffer<'info> {
54+
#[account(
55+
seeds = [b"message".as_ref(), b"whitelist".as_ref()],
56+
bump = whitelist.bump,
57+
has_one = admin,
58+
)]
59+
pub whitelist: Account<'info, Whitelist>,
60+
61+
// Also the recipient of the lamports from closing the buffer account
62+
#[account(mut)]
63+
pub admin: Signer<'info>,
64+
// remaining_account: - [AccumulatorInput PDA]
65+
}
Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,56 @@
1-
pub use put_all::*;
1+
use {
2+
crate::{
3+
state::MessageBuffer,
4+
MessageBufferError,
5+
},
6+
anchor_lang::{
7+
prelude::*,
8+
system_program,
9+
Discriminator,
10+
},
11+
};
12+
pub use {
13+
create_buffer::*,
14+
delete_buffer::*,
15+
put_all::*,
16+
resize_buffer::*,
17+
};
218

19+
20+
mod create_buffer;
21+
mod delete_buffer;
322
mod put_all;
23+
mod resize_buffer;
24+
25+
// String constants for deriving PDAs.
26+
// An authorized program's message buffer will have PDA seeds [authorized_program_pda, MESSAGE, base_account_key],
27+
// where authorized_program_pda is the
28+
pub const MESSAGE: &str = "message";
29+
pub const FUND: &str = "fund";
30+
31+
32+
pub fn is_uninitialized_account(ai: &AccountInfo) -> bool {
33+
ai.data_is_empty() && ai.owner == &system_program::ID
34+
}
35+
36+
/// Verify message buffer account is initialized and has the correct discriminator.
37+
///
38+
/// Note: manually checking because using anchor's `AccountLoader.load()`
39+
/// will panic since the `AccountInfo.data_len()` will not match the
40+
/// size of the `MessageBuffer` since the `MessageBuffer` struct does not
41+
/// include the messages.
42+
pub fn verify_message_buffer(message_buffer_account_info: &AccountInfo) -> Result<()> {
43+
if is_uninitialized_account(message_buffer_account_info) {
44+
return err!(MessageBufferError::MessageBufferUninitialized);
45+
}
46+
let data = message_buffer_account_info.try_borrow_data()?;
47+
if data.len() < MessageBuffer::discriminator().len() {
48+
return Err(ErrorCode::AccountDiscriminatorNotFound.into());
49+
}
50+
51+
let disc_bytes = &data[0..8];
52+
if disc_bytes != &MessageBuffer::discriminator() {
53+
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
54+
}
55+
Ok(())
56+
}

0 commit comments

Comments
 (0)