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

Commit e3d2b99

Browse files
authored
Account Compression: Change VerifyLeaf to avoid modifying canopy (#3977)
* Make verifyLeaf work for accounts with canopy * Add verifyLeaf tests * remove duplicated test * Make internal macros private, add docs, use enum instead of hard-coded bools * change macro names
1 parent 6ab15b3 commit e3d2b99

File tree

7 files changed

+212
-161
lines changed

7 files changed

+212
-161
lines changed

account-compression/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@
1919
use crate::error::AccountCompressionError;
2020
use crate::events::ChangeLogEvent;
2121
use anchor_lang::prelude::*;
22-
use bytemuck::cast_slice_mut;
22+
use bytemuck::{cast_slice, cast_slice_mut};
2323
use spl_concurrent_merkle_tree::node::{empty_node_cached, Node, EMPTY};
2424
use std::mem::size_of;
2525

2626
#[inline(always)]
27-
pub fn check_canopy_bytes(canopy_bytes: &mut [u8]) -> Result<()> {
27+
pub fn check_canopy_bytes(canopy_bytes: &[u8]) -> Result<()> {
2828
if canopy_bytes.len() % size_of::<Node>() != 0 {
2929
msg!(
3030
"Canopy byte length {} is not a multiple of {}",
@@ -38,7 +38,7 @@ pub fn check_canopy_bytes(canopy_bytes: &mut [u8]) -> Result<()> {
3838
}
3939

4040
#[inline(always)]
41-
fn get_cached_path_length(canopy: &mut [Node], max_depth: u32) -> Result<u32> {
41+
fn get_cached_path_length(canopy: &[Node], max_depth: u32) -> Result<u32> {
4242
// The offset of 2 is applied because the canopy is a full binary tree without the root node
4343
// Size: (2^n - 2) -> Size + 2 must be a power of 2
4444
let closest_power_of_2 = (canopy.len() + 2) as u32;
@@ -89,15 +89,15 @@ pub fn update_canopy(
8989
}
9090

9191
pub fn fill_in_proof_from_canopy(
92-
canopy_bytes: &mut [u8],
92+
canopy_bytes: &[u8],
9393
max_depth: u32,
9494
index: u32,
9595
proof: &mut Vec<Node>,
9696
) -> Result<()> {
9797
// 30 is hard coded as it is the current max depth that SPL Compression supports
9898
let mut empty_node_cache = Box::new([EMPTY; 30]);
9999
check_canopy_bytes(canopy_bytes)?;
100-
let canopy = cast_slice_mut::<u8, Node>(canopy_bytes);
100+
let canopy = cast_slice::<u8, Node>(canopy_bytes);
101101
let path_len = get_cached_path_length(canopy, max_depth)?;
102102

103103
// We want to compute the node index (w.r.t. the canopy) where the current path
@@ -115,7 +115,6 @@ pub fn fill_in_proof_from_canopy(
115115
if canopy[cached_idx] == EMPTY {
116116
let level = max_depth - (31 - node_idx.leading_zeros());
117117
let empty_node = empty_node_cached::<30>(level, &mut empty_node_cache);
118-
canopy[cached_idx] = empty_node;
119118
inferred_nodes.push(empty_node);
120119
} else {
121120
inferred_nodes.push(canopy[cached_idx]);

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

Lines changed: 13 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@ use anchor_lang::{
2727
solana_program::sysvar::{clock::Clock, rent::Rent},
2828
};
2929
use borsh::{BorshDeserialize, BorshSerialize};
30-
use std::mem::size_of;
3130

3231
pub mod canopy;
3332
pub mod error;
3433
pub mod events;
34+
#[macro_use]
35+
pub mod macros;
3536
mod noop;
3637
pub mod state;
3738
pub mod zero_copy;
@@ -42,7 +43,9 @@ use crate::canopy::{fill_in_proof_from_canopy, update_canopy};
4243
use crate::error::AccountCompressionError;
4344
use crate::events::{AccountCompressionEvent, ChangeLogEvent};
4445
use crate::noop::wrap_event;
45-
use crate::state::{ConcurrentMerkleTreeHeader, CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1};
46+
use crate::state::{
47+
merkle_tree_get_size, ConcurrentMerkleTreeHeader, CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1,
48+
};
4649
use crate::zero_copy::ZeroCopy;
4750

4851
/// Exported for Anchor / Solita
@@ -120,111 +123,6 @@ pub struct CloseTree<'info> {
120123
pub recipient: AccountInfo<'info>,
121124
}
122125

123-
/// This macro applies functions on a ConcurrentMerkleT:ee and emits leaf information
124-
/// needed to sync the merkle tree state with off-chain indexers.
125-
macro_rules! merkle_tree_depth_size_apply_fn {
126-
($max_depth:literal, $max_size:literal, $id:ident, $bytes:ident, $func:ident, $($arg:tt)*) => {
127-
match ConcurrentMerkleTree::<$max_depth, $max_size>::load_mut_bytes($bytes) {
128-
Ok(merkle_tree) => {
129-
match merkle_tree.$func($($arg)*) {
130-
Ok(_) => {
131-
Ok(Box::<ChangeLogEvent>::from((merkle_tree.get_change_log(), $id, merkle_tree.sequence_number)))
132-
}
133-
Err(err) => {
134-
msg!("Error using concurrent merkle tree: {}", err);
135-
err!(AccountCompressionError::ConcurrentMerkleTreeError)
136-
}
137-
}
138-
}
139-
Err(err) => {
140-
msg!("Error zero copying concurrent merkle tree: {}", err);
141-
err!(AccountCompressionError::ZeroCopyError)
142-
}
143-
}
144-
}
145-
}
146-
147-
fn merkle_tree_get_size(header: &ConcurrentMerkleTreeHeader) -> Result<usize> {
148-
// Note: max_buffer_size MUST be a power of 2
149-
match (header.get_max_depth(), header.get_max_buffer_size()) {
150-
(3, 8) => Ok(size_of::<ConcurrentMerkleTree<3, 8>>()),
151-
(5, 8) => Ok(size_of::<ConcurrentMerkleTree<5, 8>>()),
152-
(14, 64) => Ok(size_of::<ConcurrentMerkleTree<14, 64>>()),
153-
(14, 256) => Ok(size_of::<ConcurrentMerkleTree<14, 256>>()),
154-
(14, 1024) => Ok(size_of::<ConcurrentMerkleTree<14, 1024>>()),
155-
(14, 2048) => Ok(size_of::<ConcurrentMerkleTree<14, 2048>>()),
156-
(15, 64) => Ok(size_of::<ConcurrentMerkleTree<15, 64>>()),
157-
(16, 64) => Ok(size_of::<ConcurrentMerkleTree<16, 64>>()),
158-
(17, 64) => Ok(size_of::<ConcurrentMerkleTree<17, 64>>()),
159-
(18, 64) => Ok(size_of::<ConcurrentMerkleTree<18, 64>>()),
160-
(19, 64) => Ok(size_of::<ConcurrentMerkleTree<19, 64>>()),
161-
(20, 64) => Ok(size_of::<ConcurrentMerkleTree<20, 64>>()),
162-
(20, 256) => Ok(size_of::<ConcurrentMerkleTree<20, 256>>()),
163-
(20, 1024) => Ok(size_of::<ConcurrentMerkleTree<20, 1024>>()),
164-
(20, 2048) => Ok(size_of::<ConcurrentMerkleTree<20, 2048>>()),
165-
(24, 64) => Ok(size_of::<ConcurrentMerkleTree<24, 64>>()),
166-
(24, 256) => Ok(size_of::<ConcurrentMerkleTree<24, 256>>()),
167-
(24, 512) => Ok(size_of::<ConcurrentMerkleTree<24, 512>>()),
168-
(24, 1024) => Ok(size_of::<ConcurrentMerkleTree<24, 1024>>()),
169-
(24, 2048) => Ok(size_of::<ConcurrentMerkleTree<24, 2048>>()),
170-
(26, 512) => Ok(size_of::<ConcurrentMerkleTree<26, 512>>()),
171-
(26, 1024) => Ok(size_of::<ConcurrentMerkleTree<26, 1024>>()),
172-
(26, 2048) => Ok(size_of::<ConcurrentMerkleTree<26, 2048>>()),
173-
(30, 512) => Ok(size_of::<ConcurrentMerkleTree<30, 512>>()),
174-
(30, 1024) => Ok(size_of::<ConcurrentMerkleTree<30, 1024>>()),
175-
(30, 2048) => Ok(size_of::<ConcurrentMerkleTree<30, 2048>>()),
176-
_ => {
177-
msg!(
178-
"Failed to get size of max depth {} and max buffer size {}",
179-
header.get_max_depth(),
180-
header.get_max_buffer_size()
181-
);
182-
err!(AccountCompressionError::ConcurrentMerkleTreeConstantsError)
183-
}
184-
}
185-
}
186-
187-
/// This applies a given function on a ConcurrentMerkleTree by
188-
/// allowing the compiler to infer the size of the tree based
189-
/// upon the header information stored on-chain
190-
macro_rules! merkle_tree_apply_fn {
191-
($header:ident, $id:ident, $bytes:ident, $func:ident, $($arg:tt)*) => {
192-
// Note: max_buffer_size MUST be a power of 2
193-
match ($header.get_max_depth(), $header.get_max_buffer_size()) {
194-
(3, 8) => merkle_tree_depth_size_apply_fn!(3, 8, $id, $bytes, $func, $($arg)*),
195-
(5, 8) => merkle_tree_depth_size_apply_fn!(5, 8, $id, $bytes, $func, $($arg)*),
196-
(14, 64) => merkle_tree_depth_size_apply_fn!(14, 64, $id, $bytes, $func, $($arg)*),
197-
(14, 256) => merkle_tree_depth_size_apply_fn!(14, 256, $id, $bytes, $func, $($arg)*),
198-
(14, 1024) => merkle_tree_depth_size_apply_fn!(14, 1024, $id, $bytes, $func, $($arg)*),
199-
(14, 2048) => merkle_tree_depth_size_apply_fn!(14, 2048, $id, $bytes, $func, $($arg)*),
200-
(15, 64) => merkle_tree_depth_size_apply_fn!(15, 64, $id, $bytes, $func, $($arg)*),
201-
(16, 64) => merkle_tree_depth_size_apply_fn!(16, 64, $id, $bytes, $func, $($arg)*),
202-
(17, 64) => merkle_tree_depth_size_apply_fn!(17, 64, $id, $bytes, $func, $($arg)*),
203-
(18, 64) => merkle_tree_depth_size_apply_fn!(18, 64, $id, $bytes, $func, $($arg)*),
204-
(19, 64) => merkle_tree_depth_size_apply_fn!(19, 64, $id, $bytes, $func, $($arg)*),
205-
(20, 64) => merkle_tree_depth_size_apply_fn!(20, 64, $id, $bytes, $func, $($arg)*),
206-
(20, 256) => merkle_tree_depth_size_apply_fn!(20, 256, $id, $bytes, $func, $($arg)*),
207-
(20, 1024) => merkle_tree_depth_size_apply_fn!(20, 1024, $id, $bytes, $func, $($arg)*),
208-
(20, 2048) => merkle_tree_depth_size_apply_fn!(20, 2048, $id, $bytes, $func, $($arg)*),
209-
(24, 64) => merkle_tree_depth_size_apply_fn!(24, 64, $id, $bytes, $func, $($arg)*),
210-
(24, 256) => merkle_tree_depth_size_apply_fn!(24, 256, $id, $bytes, $func, $($arg)*),
211-
(24, 512) => merkle_tree_depth_size_apply_fn!(24, 512, $id, $bytes, $func, $($arg)*),
212-
(24, 1024) => merkle_tree_depth_size_apply_fn!(24, 1024, $id, $bytes, $func, $($arg)*),
213-
(24, 2048) => merkle_tree_depth_size_apply_fn!(24, 2048, $id, $bytes, $func, $($arg)*),
214-
(26, 512) => merkle_tree_depth_size_apply_fn!(26, 512, $id, $bytes, $func, $($arg)*),
215-
(26, 1024) => merkle_tree_depth_size_apply_fn!(26, 1024, $id, $bytes, $func, $($arg)*),
216-
(26, 2048) => merkle_tree_depth_size_apply_fn!(26, 2048, $id, $bytes, $func, $($arg)*),
217-
(30, 512) => merkle_tree_depth_size_apply_fn!(30, 512, $id, $bytes, $func, $($arg)*),
218-
(30, 1024) => merkle_tree_depth_size_apply_fn!(30, 1024, $id, $bytes, $func, $($arg)*),
219-
(30, 2048) => merkle_tree_depth_size_apply_fn!(30, 2048, $id, $bytes, $func, $($arg)*),
220-
_ => {
221-
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());
222-
err!(AccountCompressionError::ConcurrentMerkleTreeConstantsError)
223-
}
224-
}
225-
};
226-
}
227-
228126
#[program]
229127
pub mod spl_account_compression {
230128
use super::*;
@@ -267,7 +165,7 @@ pub mod spl_account_compression {
267165
let merkle_tree_size = merkle_tree_get_size(&header)?;
268166
let (tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size);
269167
let id = ctx.accounts.merkle_tree.key();
270-
let change_log_event = merkle_tree_apply_fn!(header, id, tree_bytes, initialize,)?;
168+
let change_log_event = merkle_tree_apply_fn_mut!(header, id, tree_bytes, initialize,)?;
271169
wrap_event(
272170
&AccountCompressionEvent::ChangeLog(*change_log_event),
273171
&ctx.accounts.noop,
@@ -372,7 +270,7 @@ pub mod spl_account_compression {
372270
fill_in_proof_from_canopy(canopy_bytes, header.get_max_depth(), index, &mut proof)?;
373271
let id = ctx.accounts.merkle_tree.key();
374272
// A call is made to ConcurrentMerkleTree::set_leaf(root, previous_leaf, new_leaf, proof, index)
375-
let change_log_event = merkle_tree_apply_fn!(
273+
let change_log_event = merkle_tree_apply_fn_mut!(
376274
header,
377275
id,
378276
tree_bytes,
@@ -431,16 +329,16 @@ pub mod spl_account_compression {
431329
crate::id(),
432330
AccountCompressionError::IncorrectAccountOwner
433331
);
434-
let mut merkle_tree_bytes = ctx.accounts.merkle_tree.try_borrow_mut_data()?;
332+
let merkle_tree_bytes = ctx.accounts.merkle_tree.try_borrow_data()?;
435333
let (header_bytes, rest) =
436-
merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1);
334+
merkle_tree_bytes.split_at(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1);
437335

438336
let header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?;
439337
header.assert_valid()?;
440338
header.assert_valid_leaf_index(index)?;
441339

442340
let merkle_tree_size = merkle_tree_get_size(&header)?;
443-
let (tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size);
341+
let (tree_bytes, canopy_bytes) = rest.split_at(merkle_tree_size);
444342

445343
let mut proof = vec![];
446344
for node in ctx.remaining_accounts.iter() {
@@ -475,7 +373,7 @@ pub mod spl_account_compression {
475373
let id = ctx.accounts.merkle_tree.key();
476374
let merkle_tree_size = merkle_tree_get_size(&header)?;
477375
let (tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size);
478-
let change_log_event = merkle_tree_apply_fn!(header, id, tree_bytes, append, leaf)?;
376+
let change_log_event = merkle_tree_apply_fn_mut!(header, id, tree_bytes, append, leaf)?;
479377
update_canopy(
480378
canopy_bytes,
481379
header.get_max_depth(),
@@ -520,7 +418,7 @@ pub mod spl_account_compression {
520418
fill_in_proof_from_canopy(canopy_bytes, header.get_max_depth(), index, &mut proof)?;
521419
// A call is made to ConcurrentMerkleTree::fill_empty_or_append
522420
let id = ctx.accounts.merkle_tree.key();
523-
let change_log_event = merkle_tree_apply_fn!(
421+
let change_log_event = merkle_tree_apply_fn_mut!(
524422
header,
525423
id,
526424
tree_bytes,
@@ -558,7 +456,7 @@ pub mod spl_account_compression {
558456
let (tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size);
559457

560458
let id = ctx.accounts.merkle_tree.key();
561-
merkle_tree_apply_fn!(header, id, tree_bytes, prove_tree_is_empty,)?;
459+
merkle_tree_apply_fn_mut!(header, id, tree_bytes, prove_tree_is_empty,)?;
562460

563461
// Close merkle tree account
564462
// 1. Move lamports
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#[allow(dead_code)]
2+
enum TreeLoad {
3+
Immutable,
4+
Mutable,
5+
}
6+
7+
/// This macro applies functions on a ConcurrentMerkleT:ee and emits leaf information
8+
/// needed to sync the merkle tree state with off-chain indexers.
9+
macro_rules! _merkle_tree_depth_size_apply_fn {
10+
($max_depth:literal, $max_size:literal, $id:ident, $bytes:ident, $func:ident, TreeLoad::Mutable, $($arg:tt)*)
11+
=> {
12+
match ConcurrentMerkleTree::<$max_depth, $max_size>::load_mut_bytes($bytes) {
13+
Ok(merkle_tree) => {
14+
match merkle_tree.$func($($arg)*) {
15+
Ok(_) => {
16+
Ok(Box::<ChangeLogEvent>::from((merkle_tree.get_change_log(), $id, merkle_tree.sequence_number)))
17+
}
18+
Err(err) => {
19+
msg!("Error using concurrent merkle tree: {}", err);
20+
err!(AccountCompressionError::ConcurrentMerkleTreeError)
21+
}
22+
}
23+
}
24+
Err(err) => {
25+
msg!("Error zero copying concurrent merkle tree: {}", err);
26+
err!(AccountCompressionError::ZeroCopyError)
27+
}
28+
}
29+
};
30+
($max_depth:literal, $max_size:literal, $id:ident, $bytes:ident, $func:ident, TreeLoad::Immutable, $($arg:tt)*) => {
31+
match ConcurrentMerkleTree::<$max_depth, $max_size>::load_bytes($bytes) {
32+
Ok(merkle_tree) => {
33+
match merkle_tree.$func($($arg)*) {
34+
Ok(_) => {
35+
Ok(Box::<ChangeLogEvent>::from((merkle_tree.get_change_log(), $id, merkle_tree.sequence_number)))
36+
}
37+
Err(err) => {
38+
msg!("Error using concurrent merkle tree: {}", err);
39+
err!(AccountCompressionError::ConcurrentMerkleTreeError)
40+
}
41+
}
42+
}
43+
Err(err) => {
44+
msg!("Error zero copying concurrent merkle tree: {}", err);
45+
err!(AccountCompressionError::ZeroCopyError)
46+
}
47+
}
48+
};
49+
}
50+
51+
/// This applies a given function on a ConcurrentMerkleTree by
52+
/// allowing the compiler to infer the size of the tree based
53+
/// upon the header information stored on-chain
54+
macro_rules! _merkle_tree_apply_fn {
55+
($header:ident, $($arg:tt)*) => {
56+
// Note: max_buffer_size MUST be a power of 2
57+
match ($header.get_max_depth(), $header.get_max_buffer_size()) {
58+
(3, 8) => _merkle_tree_depth_size_apply_fn!(3, 8, $($arg)*),
59+
(5, 8) => _merkle_tree_depth_size_apply_fn!(5, 8, $($arg)*),
60+
(14, 64) => _merkle_tree_depth_size_apply_fn!(14, 64, $($arg)*),
61+
(14, 256) => _merkle_tree_depth_size_apply_fn!(14, 256, $($arg)*),
62+
(14, 1024) => _merkle_tree_depth_size_apply_fn!(14, 1024, $($arg)*),
63+
(14, 2048) => _merkle_tree_depth_size_apply_fn!(14, 2048, $($arg)*),
64+
(15, 64) => _merkle_tree_depth_size_apply_fn!(15, 64, $($arg)*),
65+
(16, 64) => _merkle_tree_depth_size_apply_fn!(16, 64, $($arg)*),
66+
(17, 64) => _merkle_tree_depth_size_apply_fn!(17, 64, $($arg)*),
67+
(18, 64) => _merkle_tree_depth_size_apply_fn!(18, 64, $($arg)*),
68+
(19, 64) => _merkle_tree_depth_size_apply_fn!(19, 64, $($arg)*),
69+
(20, 64) => _merkle_tree_depth_size_apply_fn!(20, 64, $($arg)*),
70+
(20, 256) => _merkle_tree_depth_size_apply_fn!(20, 256, $($arg)*),
71+
(20, 1024) => _merkle_tree_depth_size_apply_fn!(20, 1024, $($arg)*),
72+
(20, 2048) => _merkle_tree_depth_size_apply_fn!(20, 2048, $($arg)*),
73+
(24, 64) => _merkle_tree_depth_size_apply_fn!(24, 64, $($arg)*),
74+
(24, 256) => _merkle_tree_depth_size_apply_fn!(24, 256, $($arg)*),
75+
(24, 512) => _merkle_tree_depth_size_apply_fn!(24, 512, $($arg)*),
76+
(24, 1024) => _merkle_tree_depth_size_apply_fn!(24, 1024, $($arg)*),
77+
(24, 2048) => _merkle_tree_depth_size_apply_fn!(24, 2048, $($arg)*),
78+
(26, 512) => _merkle_tree_depth_size_apply_fn!(26, 512, $($arg)*),
79+
(26, 1024) => _merkle_tree_depth_size_apply_fn!(26, 1024, $($arg)*),
80+
(26, 2048) => _merkle_tree_depth_size_apply_fn!(26, 2048, $($arg)*),
81+
(30, 512) => _merkle_tree_depth_size_apply_fn!(30, 512, $($arg)*),
82+
(30, 1024) => _merkle_tree_depth_size_apply_fn!(30, 1024, $($arg)*),
83+
(30, 2048) => _merkle_tree_depth_size_apply_fn!(30, 2048, $($arg)*),
84+
_ => {
85+
msg!("Failed to apply {} on concurrent merkle tree with max depth {} and max buffer size {}",
86+
stringify!($func),
87+
$header.get_max_depth(),
88+
$header.get_max_buffer_size()
89+
);
90+
err!(AccountCompressionError::ConcurrentMerkleTreeConstantsError)
91+
}
92+
}
93+
};
94+
}
95+
96+
/// This applies a given function on a mutable ConcurrentMerkleTree
97+
#[macro_export]
98+
macro_rules! merkle_tree_apply_fn_mut {
99+
($header:ident, $id:ident, $bytes:ident, $func:ident, $($arg:tt)*) => {
100+
_merkle_tree_apply_fn!($header, $id, $bytes, $func, TreeLoad::Mutable, $($arg)*)
101+
};
102+
}
103+
104+
/// This applies a given function on a read-only ConcurrentMerkleTree
105+
#[macro_export]
106+
macro_rules! merkle_tree_apply_fn {
107+
($header:ident, $id:ident, $bytes:ident, $func:ident, $($arg:tt)*) => {
108+
_merkle_tree_apply_fn!($header, $id, $bytes, $func, TreeLoad::Immutable, $($arg)*)
109+
};
110+
}

0 commit comments

Comments
 (0)