Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 9205a61

Browse files
authored
AC: version on-chain accounts (#3619)
1 parent 318ced1 commit 9205a61

25 files changed

+649
-298
lines changed

account-compression/programs/account-compression/src/lib.rs

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ use crate::canopy::{fill_in_proof_from_canopy, update_canopy};
4141
use crate::data_wrapper::{wrap_event, Wrapper};
4242
use crate::error::AccountCompressionError;
4343
use crate::events::ChangeLogEvent;
44-
use crate::state::ConcurrentMerkleTreeHeader;
44+
use crate::state::{ConcurrentMerkleTreeHeader, CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1};
4545
use crate::zero_copy::ZeroCopy;
4646

4747
/// Exported for Anchor / Solita
@@ -132,7 +132,7 @@ macro_rules! merkle_tree_depth_size_apply_fn {
132132

133133
fn merkle_tree_get_size(header: &ConcurrentMerkleTreeHeader) -> Result<usize> {
134134
// Note: max_buffer_size MUST be a power of 2
135-
match (header.max_depth, header.max_buffer_size) {
135+
match (header.get_max_depth(), header.get_max_buffer_size()) {
136136
(3, 8) => Ok(size_of::<ConcurrentMerkleTree<3, 8>>()),
137137
(5, 8) => Ok(size_of::<ConcurrentMerkleTree<5, 8>>()),
138138
(14, 64) => Ok(size_of::<ConcurrentMerkleTree<14, 64>>()),
@@ -157,8 +157,8 @@ fn merkle_tree_get_size(header: &ConcurrentMerkleTreeHeader) -> Result<usize> {
157157
_ => {
158158
msg!(
159159
"Failed to get size of max depth {} and max buffer size {}",
160-
header.max_depth,
161-
header.max_buffer_size
160+
header.get_max_depth(),
161+
header.get_max_buffer_size()
162162
);
163163
err!(AccountCompressionError::ConcurrentMerkleTreeConstantsError)
164164
}
@@ -171,7 +171,7 @@ fn merkle_tree_get_size(header: &ConcurrentMerkleTreeHeader) -> Result<usize> {
171171
macro_rules! merkle_tree_apply_fn {
172172
($header:ident, $id:ident, $bytes:ident, $func:ident, $($arg:tt)*) => {
173173
// Note: max_buffer_size MUST be a power of 2
174-
match ($header.max_depth, $header.max_buffer_size) {
174+
match ($header.get_max_depth(), $header.get_max_buffer_size()) {
175175
(3, 8) => merkle_tree_depth_size_apply_fn!(3, 8, $id, $bytes, $func, $($arg)*),
176176
(5, 8) => merkle_tree_depth_size_apply_fn!(5, 8, $id, $bytes, $func, $($arg)*),
177177
(14, 64) => merkle_tree_depth_size_apply_fn!(14, 64, $id, $bytes, $func, $($arg)*),
@@ -194,7 +194,7 @@ macro_rules! merkle_tree_apply_fn {
194194
(30, 1024) => merkle_tree_depth_size_apply_fn!(30, 1024, $id, $bytes, $func, $($arg)*),
195195
(30, 2048) => merkle_tree_depth_size_apply_fn!(30, 2048, $id, $bytes, $func, $($arg)*),
196196
_ => {
197-
msg!("Failed to apply {} on concurrent merkle tree with max depth {} and max buffer size {}", stringify!($func), $header.max_depth, $header.max_buffer_size);
197+
msg!("Failed to apply {} on concurrent merkle tree with max depth {} and max buffer size {}", stringify!($func), $header.get_max_depth(), $header.get_max_buffer_size());
198198
err!(AccountCompressionError::ConcurrentMerkleTreeConstantsError)
199199
}
200200
}
@@ -230,7 +230,7 @@ pub mod spl_account_compression {
230230
let mut merkle_tree_bytes = ctx.accounts.merkle_tree.try_borrow_mut_data()?;
231231

232232
let (mut header_bytes, rest) =
233-
merkle_tree_bytes.split_at_mut(size_of::<ConcurrentMerkleTreeHeader>());
233+
merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1);
234234

235235
let mut header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?;
236236
header.initialize(
@@ -246,7 +246,7 @@ pub mod spl_account_compression {
246246
let change_log = merkle_tree_apply_fn!(header, id, tree_bytes, initialize,)?;
247247
wrap_event(change_log.try_to_vec()?, &ctx.accounts.log_wrapper)?;
248248
emit!(*change_log);
249-
update_canopy(canopy_bytes, header.max_depth, None)
249+
update_canopy(canopy_bytes, header.get_max_depth(), None)
250250
}
251251

252252
/// Note:
@@ -276,7 +276,7 @@ pub mod spl_account_compression {
276276
// let mut merkle_tree_bytes = ctx.accounts.merkle_tree.try_borrow_mut_data()?;
277277

278278
// let (mut header_bytes, rest) =
279-
// merkle_tree_bytes.split_at_mut(size_of::<ConcurrentMerkleTreeHeader>());
279+
// merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1);
280280

281281
// let mut header = ConcurrentMerkleTreeHeader::try_from_slice(&header_bytes)?;
282282
// header.initialize(
@@ -331,7 +331,7 @@ pub mod spl_account_compression {
331331
);
332332
let mut merkle_tree_bytes = ctx.accounts.merkle_tree.try_borrow_mut_data()?;
333333
let (header_bytes, rest) =
334-
merkle_tree_bytes.split_at_mut(size_of::<ConcurrentMerkleTreeHeader>());
334+
merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1);
335335

336336
let header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?;
337337
header.assert_valid_authority(&ctx.accounts.authority.key())?;
@@ -343,7 +343,7 @@ pub mod spl_account_compression {
343343
for node in ctx.remaining_accounts.iter() {
344344
proof.push(node.key().to_bytes());
345345
}
346-
fill_in_proof_from_canopy(canopy_bytes, header.max_depth, index, &mut proof)?;
346+
fill_in_proof_from_canopy(canopy_bytes, header.get_max_depth(), index, &mut proof)?;
347347
let id = ctx.accounts.merkle_tree.key();
348348
// A call is made to ConcurrentMerkleTree::set_leaf(root, previous_leaf, new_leaf, proof, index)
349349
let change_log = merkle_tree_apply_fn!(
@@ -359,7 +359,7 @@ pub mod spl_account_compression {
359359
)?;
360360
wrap_event(change_log.try_to_vec()?, &ctx.accounts.log_wrapper)?;
361361
emit!(*change_log);
362-
update_canopy(canopy_bytes, header.max_depth, Some(change_log))
362+
update_canopy(canopy_bytes, header.get_max_depth(), Some(change_log))
363363
}
364364

365365
/// Transfers `authority`.
@@ -375,13 +375,12 @@ pub mod spl_account_compression {
375375
);
376376
let mut merkle_tree_bytes = ctx.accounts.merkle_tree.try_borrow_mut_data()?;
377377
let (mut header_bytes, _) =
378-
merkle_tree_bytes.split_at_mut(size_of::<ConcurrentMerkleTreeHeader>());
378+
merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1);
379379

380380
let mut header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?;
381381
header.assert_valid_authority(&ctx.accounts.authority.key())?;
382382

383-
header.authority = new_authority;
384-
msg!("Authority transferred to: {:?}", header.authority);
383+
header.set_new_authority(&new_authority);
385384
header.serialize(&mut header_bytes)?;
386385

387386
Ok(())
@@ -402,7 +401,7 @@ pub mod spl_account_compression {
402401
);
403402
let mut merkle_tree_bytes = ctx.accounts.merkle_tree.try_borrow_mut_data()?;
404403
let (header_bytes, rest) =
405-
merkle_tree_bytes.split_at_mut(size_of::<ConcurrentMerkleTreeHeader>());
404+
merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1);
406405

407406
let header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?;
408407
header.assert_valid()?;
@@ -414,7 +413,7 @@ pub mod spl_account_compression {
414413
for node in ctx.remaining_accounts.iter() {
415414
proof.push(node.key().to_bytes());
416415
}
417-
fill_in_proof_from_canopy(canopy_bytes, header.max_depth, index, &mut proof)?;
416+
fill_in_proof_from_canopy(canopy_bytes, header.get_max_depth(), index, &mut proof)?;
418417
let id = ctx.accounts.merkle_tree.key();
419418

420419
merkle_tree_apply_fn!(header, id, tree_bytes, prove_leaf, root, leaf, &proof, index)?;
@@ -435,7 +434,7 @@ pub mod spl_account_compression {
435434
);
436435
let mut merkle_tree_bytes = ctx.accounts.merkle_tree.try_borrow_mut_data()?;
437436
let (header_bytes, rest) =
438-
merkle_tree_bytes.split_at_mut(size_of::<ConcurrentMerkleTreeHeader>());
437+
merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1);
439438

440439
let header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?;
441440
header.assert_valid_authority(&ctx.accounts.authority.key())?;
@@ -446,7 +445,7 @@ pub mod spl_account_compression {
446445
let change_log = merkle_tree_apply_fn!(header, id, tree_bytes, append, leaf)?;
447446
wrap_event(change_log.try_to_vec()?, &ctx.accounts.log_wrapper)?;
448447
emit!(*change_log);
449-
update_canopy(canopy_bytes, header.max_depth, Some(change_log))
448+
update_canopy(canopy_bytes, header.get_max_depth(), Some(change_log))
450449
}
451450

452451
/// This instruction takes a proof, and will attempt to write the given leaf
@@ -466,7 +465,7 @@ pub mod spl_account_compression {
466465
);
467466
let mut merkle_tree_bytes = ctx.accounts.merkle_tree.try_borrow_mut_data()?;
468467
let (header_bytes, rest) =
469-
merkle_tree_bytes.split_at_mut(size_of::<ConcurrentMerkleTreeHeader>());
468+
merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1);
470469

471470
let header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?;
472471
header.assert_valid_authority(&ctx.accounts.authority.key())?;
@@ -478,7 +477,7 @@ pub mod spl_account_compression {
478477
for node in ctx.remaining_accounts.iter() {
479478
proof.push(node.key().to_bytes());
480479
}
481-
fill_in_proof_from_canopy(canopy_bytes, header.max_depth, index, &mut proof)?;
480+
fill_in_proof_from_canopy(canopy_bytes, header.get_max_depth(), index, &mut proof)?;
482481
// A call is made to ConcurrentMerkleTree::fill_empty_or_append
483482
let id = ctx.accounts.merkle_tree.key();
484483
let change_log = merkle_tree_apply_fn!(
@@ -493,6 +492,6 @@ pub mod spl_account_compression {
493492
)?;
494493
wrap_event(change_log.try_to_vec()?, &ctx.accounts.log_wrapper)?;
495494
emit!(*change_log);
496-
update_canopy(canopy_bytes, header.max_depth, Some(change_log))
495+
update_canopy(canopy_bytes, header.get_max_depth(), Some(change_log))
497496
}
498497
}

account-compression/programs/account-compression/src/state/concurrent_merkle_tree_header.rs

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use borsh::{BorshDeserialize, BorshSerialize};
33

44
use crate::error::AccountCompressionError;
55

6+
pub const CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1: usize = 2 + 54;
7+
68
#[derive(Debug, Copy, Clone, PartialEq, BorshDeserialize, BorshSerialize)]
79
#[repr(u8)]
810
pub enum CompressionAccountType {
@@ -31,32 +33,44 @@ impl std::fmt::Display for CompressionAccountType {
3133
/// | 26 | (64, 256, 512, 1024, 2048) |
3234
/// | 30 | (512, 1024, 2048) |
3335
///
34-
#[derive(BorshDeserialize, BorshSerialize)]
3536
#[repr(C)]
37+
#[derive(AnchorDeserialize, AnchorSerialize)]
3638
pub struct ConcurrentMerkleTreeHeader {
3739
/// Account type
3840
pub account_type: CompressionAccountType,
41+
/// Versioned header
42+
pub header: ConcurrentMerkleTreeHeaderData,
43+
}
3944

40-
/// Needs padding for the account to be 8-byte aligned
41-
/// 8-byte alignment is necessary to zero-copy the SPL ConcurrentMerkleTree
42-
pub _padding: [u8; 7],
43-
45+
#[repr(C)]
46+
#[derive(AnchorDeserialize, AnchorSerialize)]
47+
pub struct ConcurrentMerkleTreeHeaderDataV1 {
4448
/// Buffer of changelogs stored on-chain.
4549
/// Must be a power of 2; see above table for valid combinations.
46-
pub max_buffer_size: u32,
50+
max_buffer_size: u32,
4751

4852
/// Depth of the SPL ConcurrentMerkleTree to store.
4953
/// Tree capacity can be calculated as power(2, max_depth).
5054
/// See above table for valid options.
51-
pub max_depth: u32,
55+
max_depth: u32,
5256

5357
/// Authority that validates the content of the trees.
5458
/// Typically a program, e.g., the Bubblegum contract validates that leaves are valid NFTs.
55-
pub authority: Pubkey,
59+
authority: Pubkey,
5660

5761
/// Slot corresponding to when the Merkle tree was created.
5862
/// Provides a lower-bound on what slot to start (re-)building a tree from.
59-
pub creation_slot: u64,
63+
creation_slot: u64,
64+
65+
/// Needs padding for the account to be 8-byte aligned
66+
/// 8-byte alignment is necessary to zero-copy the SPL ConcurrentMerkleTree
67+
_padding: [u8; 6],
68+
}
69+
70+
#[repr(C)]
71+
#[derive(AnchorDeserialize, AnchorSerialize)]
72+
pub enum ConcurrentMerkleTreeHeaderData {
73+
V1(ConcurrentMerkleTreeHeaderDataV1),
6074
}
6175

6276
impl ConcurrentMerkleTreeHeader {
@@ -67,14 +81,40 @@ impl ConcurrentMerkleTreeHeader {
6781
authority: &Pubkey,
6882
creation_slot: u64,
6983
) {
70-
// Check header is empty
71-
assert_eq!(self.max_buffer_size, 0);
72-
assert_eq!(self.max_depth, 0);
7384
self.account_type = CompressionAccountType::ConcurrentMerkleTree;
74-
self.max_buffer_size = max_buffer_size;
75-
self.max_depth = max_depth;
76-
self.authority = *authority;
77-
self.creation_slot = creation_slot;
85+
86+
match self.header {
87+
ConcurrentMerkleTreeHeaderData::V1(ref mut header) => {
88+
// Double check header is empty after deserialization from zero'd bytes
89+
assert_eq!(header.max_buffer_size, 0);
90+
assert_eq!(header.max_depth, 0);
91+
header.max_buffer_size = max_buffer_size;
92+
header.max_depth = max_depth;
93+
header.authority = *authority;
94+
header.creation_slot = creation_slot;
95+
}
96+
}
97+
}
98+
99+
pub fn get_max_depth(&self) -> u32 {
100+
match &self.header {
101+
ConcurrentMerkleTreeHeaderData::V1(header) => header.max_depth,
102+
}
103+
}
104+
105+
pub fn get_max_buffer_size(&self) -> u32 {
106+
match &self.header {
107+
ConcurrentMerkleTreeHeaderData::V1(header) => header.max_buffer_size,
108+
}
109+
}
110+
111+
pub fn set_new_authority(&mut self, new_authority: &Pubkey) {
112+
match self.header {
113+
ConcurrentMerkleTreeHeaderData::V1(ref mut header) => {
114+
header.authority = new_authority.clone();
115+
msg!("Authority transferred to: {:?}", header.authority);
116+
}
117+
}
78118
}
79119

80120
pub fn assert_valid(&self) -> Result<()> {
@@ -88,11 +128,15 @@ impl ConcurrentMerkleTreeHeader {
88128

89129
pub fn assert_valid_authority(&self, expected_authority: &Pubkey) -> Result<()> {
90130
self.assert_valid()?;
91-
require_eq!(
92-
self.authority,
93-
*expected_authority,
94-
AccountCompressionError::IncorrectAuthority,
95-
);
131+
match &self.header {
132+
ConcurrentMerkleTreeHeaderData::V1(header) => {
133+
require_eq!(
134+
header.authority,
135+
*expected_authority,
136+
AccountCompressionError::IncorrectAuthority,
137+
);
138+
}
139+
}
96140
Ok(())
97141
}
98142
}

account-compression/programs/account-compression/src/state/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,7 @@
22
mod concurrent_merkle_tree_header;
33
mod path_node;
44

5-
pub use concurrent_merkle_tree_header::ConcurrentMerkleTreeHeader;
5+
pub use concurrent_merkle_tree_header::{
6+
ConcurrentMerkleTreeHeader, CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1,
7+
};
68
pub use path_node::PathNode;

0 commit comments

Comments
 (0)