hpl_account_compression/
lib.rs

1//! SPL Account Compression is an on-chain program that exposes an interface to manipulating SPL ConcurrentMerkleTrees
2//!
3//! A buffer of proof-like changelogs is stored on-chain that allow multiple proof-based writes to succeed within the same slot.
4//! This is accomplished by fast-forwarding out-of-date (or possibly invalid) proofs based on information stored in the changelogs.
5//! See a copy of the whitepaper [here](https://drive.google.com/file/d/1BOpa5OFmara50fTvL0VIVYjtg-qzHCVc/view)
6//!
7//! To circumvent proof size restrictions stemming from Solana transaction size restrictions,
8//! SPL Account Compression also provides the ability to cache the upper most leaves of the
9//! concurrent merkle tree. This is called the "canopy", and is stored at the end of the
10//! ConcurrentMerkleTreeAccount. More information can be found in the initialization instruction
11//! documentation.
12//!
13//! While SPL ConcurrentMerkleTrees can generically store arbitrary information,
14//! one exemplified use-case is the [Bubblegum](https://github.com/metaplex-foundation/metaplex-program-library/tree/master/bubblegum) contract,
15//! which uses SPL-Compression to store encoded information about NFTs.
16//! The use of SPL-Compression within Bubblegum allows for:
17//! - up to 1 billion NFTs to be stored in a single account on-chain (>10,000x decrease in on-chain cost)
18//! - up to 2048 concurrent updates per slot
19//!
20//! Operationally, SPL ConcurrentMerkleTrees **must** be supplemented by off-chain indexers to cache information
21//! about leafs and to power an API that can supply up-to-date proofs to allow updates to the tree.
22//! All modifications to SPL ConcurrentMerkleTrees are settled on the Solana ledger via instructions against the SPL Compression contract.
23//! A production-ready indexer (Plerkle) can be found in the [Metaplex program library](https://github.com/metaplex-foundation/digital-asset-validator-plugin)
24
25use anchor_lang::{
26    prelude::*,
27    solana_program::sysvar::{clock::Clock, rent::Rent},
28};
29use borsh::{BorshDeserialize, BorshSerialize};
30
31pub mod canopy;
32pub mod concurrent_tree_wrapper;
33pub mod error;
34pub mod events;
35#[macro_use]
36pub mod macros;
37mod noop;
38pub mod state;
39pub mod zero_copy;
40
41pub use crate::noop::{wrap_application_data_v1, Noop};
42
43use crate::canopy::{
44    check_canopy_bytes, check_canopy_no_nodes_to_right_of_index, check_canopy_root,
45    fill_in_proof_from_canopy, set_canopy_leaf_nodes, update_canopy,
46};
47use crate::concurrent_tree_wrapper::*;
48pub use crate::error::AccountCompressionError;
49pub use crate::events::{AccountCompressionEvent, ChangeLogEvent};
50use crate::noop::wrap_event;
51use crate::state::{
52    merkle_tree_get_size, ConcurrentMerkleTreeHeader, CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1,
53};
54
55/// Exported for Anchor / Solita
56pub use spl_concurrent_merkle_tree::{
57    concurrent_merkle_tree::{ConcurrentMerkleTree, FillEmptyOrAppendArgs},
58    error::ConcurrentMerkleTreeError,
59    node::Node,
60    node::EMPTY,
61};
62
63declare_id!("hcmtvP1ACESumHKeVGwBciai2LXtRHp1Yz2JQAEvQnZ");
64
65/// Context for initializing a new SPL ConcurrentMerkleTree
66#[derive(Accounts)]
67pub struct Initialize<'info> {
68    #[account(zero)]
69    /// CHECK: This account will be zeroed out, and the size will be validated
70    pub merkle_tree: UncheckedAccount<'info>,
71
72    /// Authority that controls write-access to the tree
73    /// Typically a program, e.g., the Bubblegum contract validates that leaves are valid NFTs.
74    pub authority: Signer<'info>,
75
76    /// Program used to emit changelogs as cpi instruction data.
77    pub noop: Program<'info, Noop>,
78}
79
80/// Context for modifying a tree: inserting, appending, or replacing a leaf in
81/// the existing tree and setting the canopy or finalizing a prepared tree.
82///
83/// Modification instructions also require the proof to the leaf to be provided
84/// as 32-byte nodes via "remaining accounts".
85#[derive(Accounts)]
86pub struct Modify<'info> {
87    #[account(mut)]
88    /// CHECK: This account is validated in the instruction
89    pub merkle_tree: UncheckedAccount<'info>,
90
91    /// Authority that controls write-access to the tree
92    /// Typically a program, e.g., the Bubblegum contract validates that leaves are valid NFTs.
93    pub authority: Signer<'info>,
94
95    /// Program used to emit changelogs as cpi instruction data.
96    pub noop: Program<'info, Noop>,
97}
98
99/// Context for validating a provided proof against the SPL ConcurrentMerkleTree.
100/// Throws an error if provided proof is invalid.
101#[derive(Accounts)]
102pub struct VerifyLeaf<'info> {
103    /// CHECK: This account is validated in the instruction
104    pub merkle_tree: UncheckedAccount<'info>,
105}
106
107/// Context for transferring `authority`
108#[derive(Accounts)]
109pub struct TransferAuthority<'info> {
110    #[account(mut)]
111    /// CHECK: This account is validated in the instruction
112    pub merkle_tree: UncheckedAccount<'info>,
113
114    /// Authority that controls write-access to the tree
115    /// Typically a program, e.g., the Bubblegum contract validates that leaves are valid NFTs.
116    pub authority: Signer<'info>,
117}
118
119/// Context for closing a tree
120#[derive(Accounts)]
121pub struct CloseTree<'info> {
122    #[account(mut)]
123    /// CHECK: This account is validated in the instruction
124    pub merkle_tree: AccountInfo<'info>,
125
126    /// Authority that controls write-access to the tree
127    pub authority: Signer<'info>,
128
129    /// CHECK: Recipient of funds after
130    #[account(mut)]
131    pub recipient: AccountInfo<'info>,
132}
133
134#[program]
135pub mod spl_account_compression {
136    use super::*;
137
138    /// Creates a new merkle tree with maximum leaf capacity of `power(2, max_depth)`
139    /// and a minimum concurrency limit of `max_buffer_size`.
140    ///
141    /// Concurrency limit represents the # of replace instructions that can be successfully
142    /// executed with proofs dated for the same root. For example, a maximum buffer size of 1024
143    /// means that a minimum of 1024 replaces can be executed before a new proof must be
144    /// generated for the next replace instruction.
145    ///
146    /// Concurrency limit should be determined by empirically testing the demand for
147    /// state built on top of SPL Compression.
148    ///
149    /// For instructions on enabling the canopy, see [canopy].
150    pub fn init_empty_merkle_tree(
151        ctx: Context<Initialize>,
152        max_depth: u32,
153        max_buffer_size: u32,
154    ) -> Result<()> {
155        require_eq!(
156            *ctx.accounts.merkle_tree.owner,
157            crate::id(),
158            AccountCompressionError::IncorrectAccountOwner
159        );
160        let mut merkle_tree_bytes = ctx.accounts.merkle_tree.try_borrow_mut_data()?;
161
162        let (mut header_bytes, rest) =
163            merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1);
164
165        let mut header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?;
166        header.initialize(
167            max_depth,
168            max_buffer_size,
169            &ctx.accounts.authority.key(),
170            Clock::get()?.slot,
171        );
172        header.serialize(&mut header_bytes)?;
173
174        let merkle_tree_size = merkle_tree_get_size(&header)?;
175        let (tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size);
176        let id = ctx.accounts.merkle_tree.key();
177
178        let change_log_event = merkle_tree_initialize_empty(&header, id, tree_bytes)?;
179
180        wrap_event(
181            &AccountCompressionEvent::ChangeLog(*change_log_event),
182            &ctx.accounts.noop,
183        )?;
184        update_canopy(canopy_bytes, header.get_max_depth(), None)
185    }
186
187    /// (Devnet only) In order to initialize a tree with a root, we need to create the tree on-chain first with
188    /// the proper authority. The tree might contain a canopy, which is a cache of the uppermost
189    /// nodes. The canopy is used to decrease the size of the proof required to update the tree.
190    /// If the tree is expected to have a canopy, it needs to be prefilled with the necessary nodes.
191    /// There are 2 ways to initialize a merkle tree:
192    /// 1. Initialize an empty tree
193    /// 2. Initialize a tree with a root and leaf
194    /// For the former case, the canopy will be empty which is expected for an empty tree. The
195    /// expected flow is `init_empty_merkle_tree`. For the latter case, the canopy should be
196    /// filled with the necessary nodes to render the tree usable. Thus we need to prefill the
197    /// canopy with the necessary nodes. The expected flow for a tree without canopy is
198    /// `prepare_batch_merkle_tree` -> `init_prepared_tree_with_root`. The expected flow for a tree
199    /// with canopy is `prepare_batch_merkle_tree` -> `append_canopy_nodes` (multiple times
200    /// until all of the canopy is filled) -> `init_prepared_tree_with_root`. This instruction
201    /// initializes the tree header while leaving the tree itself uninitialized. This allows
202    /// distinguishing between an empty tree and a tree prepare to be initialized with a root.
203    pub fn prepare_batch_merkle_tree(
204        ctx: Context<Initialize>,
205        max_depth: u32,
206        max_buffer_size: u32,
207    ) -> Result<()> {
208        require_eq!(
209            *ctx.accounts.merkle_tree.owner,
210            crate::id(),
211            AccountCompressionError::IncorrectAccountOwner
212        );
213        let mut merkle_tree_bytes = ctx.accounts.merkle_tree.try_borrow_mut_data()?;
214
215        let (mut header_bytes, rest) =
216            merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1);
217
218        let mut header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?;
219        header.initialize_batched(
220            max_depth,
221            max_buffer_size,
222            &ctx.accounts.authority.key(),
223            Clock::get()?.slot,
224        );
225        header.serialize(&mut header_bytes)?;
226        let merkle_tree_size = merkle_tree_get_size(&header)?;
227        let (_tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size);
228        check_canopy_bytes(canopy_bytes)
229    }
230
231    /// (Devnet only) This instruction pre-initializes the canopy with the specified leaf nodes of the canopy.
232    /// This is intended to be used after `prepare_batch_merkle_tree` and in conjunction with the
233    /// `init_prepared_tree_with_root` instruction that'll finalize the tree initialization.
234    /// The canopy is used to cache the uppermost nodes of the tree, which allows for a smaller
235    /// proof size when updating the tree. The canopy should be filled with the necessary nodes
236    /// before calling `init_prepared_tree_with_root`. You may call this instruction multiple
237    /// times to fill the canopy with the necessary nodes. The canopy may be filled with the
238    /// nodes in any order. The already filled nodes may be replaced with new nodes before calling
239    /// `init_prepared_tree_with_root` if the step was done in error.
240    /// The canopy should be filled with all the nodes that are to the left of the rightmost
241    /// leaf of the tree before calling `init_prepared_tree_with_root`. The canopy should not
242    /// contain any nodes to the right of the rightmost leaf of the tree.
243    /// This instruction calculates and filles in all the canopy nodes "above" the provided ones.
244    /// The validation of the canopy is done in the `init_prepared_tree_with_root` instruction.
245    pub fn append_canopy_nodes(
246        ctx: Context<Modify>,
247        start_index: u32,
248        canopy_nodes: Vec<[u8; 32]>,
249    ) -> Result<()> {
250        require_eq!(
251            *ctx.accounts.merkle_tree.owner,
252            crate::id(),
253            AccountCompressionError::IncorrectAccountOwner
254        );
255        let mut merkle_tree_bytes = ctx.accounts.merkle_tree.try_borrow_mut_data()?;
256
257        let (header_bytes, rest) =
258            merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1);
259
260        let header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?;
261        header.assert_valid_authority(&ctx.accounts.authority.key())?;
262        header.assert_is_batch_initialized()?;
263        // assert the tree is not initialized yet, we don't want to overwrite the canopy of an
264        // initialized tree
265        let merkle_tree_size = merkle_tree_get_size(&header)?;
266        let (tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size);
267        // ensure the tree is not initialized, the hacky way
268        require!(
269            tree_bytes_uninitialized(tree_bytes),
270            AccountCompressionError::TreeAlreadyInitialized
271        );
272        set_canopy_leaf_nodes(
273            canopy_bytes,
274            header.get_max_depth(),
275            start_index,
276            &canopy_nodes,
277        )
278    }
279
280    /// (Devnet only) Initializes a prepared tree with a root and a rightmost leaf. The rightmost leaf is used to
281    /// verify the canopy if the tree has it. Before calling this instruction, the tree should be
282    /// prepared with `prepare_batch_merkle_tree` and the canopy should be filled with the necessary
283    /// nodes with `append_canopy_nodes` (if the canopy is used). This method should be used for
284    /// batch creation of trees. The indexing of such batches should be done off-chain. The
285    /// programs calling this instruction should take care of ensuring the indexing is possible.
286    /// For example, staking may be required to ensure the tree creator has some responsibility
287    /// for what is being indexed. If indexing is not possible, there should be a mechanism to
288    /// penalize the tree creator.
289    pub fn init_prepared_tree_with_root(
290        ctx: Context<Modify>,
291        root: [u8; 32],
292        rightmost_leaf: [u8; 32],
293        rightmost_index: u32,
294    ) -> Result<()> {
295        require_eq!(
296            *ctx.accounts.merkle_tree.owner,
297            crate::id(),
298            AccountCompressionError::IncorrectAccountOwner
299        );
300        let mut merkle_tree_bytes = ctx.accounts.merkle_tree.try_borrow_mut_data()?;
301
302        let (header_bytes, rest) =
303            merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1);
304        // the header should already be initialized with prepare_batch_merkle_tree
305        let header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?;
306        header.assert_valid_authority(&ctx.accounts.authority.key())?;
307        header.assert_is_batch_initialized()?;
308        let merkle_tree_size = merkle_tree_get_size(&header)?;
309        let (tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size);
310        // check the canopy root matches the tree root
311        check_canopy_root(canopy_bytes, &root, header.get_max_depth())?;
312        // verify the canopy does not conain any nodes to the right of the rightmost leaf
313        check_canopy_no_nodes_to_right_of_index(
314            canopy_bytes,
315            header.get_max_depth(),
316            rightmost_index,
317        )?;
318
319        // Get rightmost proof from accounts
320        let mut proof = vec![];
321        for node in ctx.remaining_accounts.iter() {
322            proof.push(node.key().to_bytes());
323        }
324        fill_in_proof_from_canopy(
325            canopy_bytes,
326            header.get_max_depth(),
327            rightmost_index,
328            &mut proof,
329        )?;
330        assert_eq!(proof.len(), header.get_max_depth() as usize);
331
332        let id = ctx.accounts.merkle_tree.key();
333        // A call is made to ConcurrentMerkleTree::initialize_with_root
334        let args = &InitializeWithRootArgs {
335            root,
336            rightmost_leaf,
337            proof_vec: proof,
338            index: rightmost_index,
339        };
340        let change_log = merkle_tree_initialize_with_root(&header, id, tree_bytes, args)?;
341        update_canopy(canopy_bytes, header.get_max_depth(), Some(&change_log))?;
342        wrap_event(
343            &AccountCompressionEvent::ChangeLog(*change_log),
344            &ctx.accounts.noop,
345        )
346    }
347
348    /// Executes an instruction that overwrites a leaf node.
349    /// Composing programs should check that the data hashed into previous_leaf
350    /// matches the authority information necessary to execute this instruction.
351    pub fn replace_leaf(
352        ctx: Context<Modify>,
353        root: [u8; 32],
354        previous_leaf: [u8; 32],
355        new_leaf: [u8; 32],
356        index: u32,
357    ) -> Result<()> {
358        require_eq!(
359            *ctx.accounts.merkle_tree.owner,
360            crate::id(),
361            AccountCompressionError::IncorrectAccountOwner
362        );
363        let mut merkle_tree_bytes = ctx.accounts.merkle_tree.try_borrow_mut_data()?;
364        let (header_bytes, rest) =
365            merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1);
366
367        let header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?;
368        header.assert_valid_authority(&ctx.accounts.authority.key())?;
369        header.assert_valid_leaf_index(index)?;
370
371        let merkle_tree_size = merkle_tree_get_size(&header)?;
372        let (tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size);
373
374        let mut proof = vec![];
375        for node in ctx.remaining_accounts.iter() {
376            proof.push(node.key().to_bytes());
377        }
378        fill_in_proof_from_canopy(canopy_bytes, header.get_max_depth(), index, &mut proof)?;
379        let id = ctx.accounts.merkle_tree.key();
380        // A call is made to ConcurrentMerkleTree::set_leaf(root, previous_leaf, new_leaf, proof, index)
381        let args = &SetLeafArgs {
382            current_root: root,
383            previous_leaf,
384            new_leaf,
385            proof_vec: proof,
386            index,
387        };
388        let change_log_event = merkle_tree_set_leaf(&header, id, tree_bytes, args)?;
389
390        update_canopy(
391            canopy_bytes,
392            header.get_max_depth(),
393            Some(&change_log_event),
394        )?;
395        wrap_event(
396            &AccountCompressionEvent::ChangeLog(*change_log_event),
397            &ctx.accounts.noop,
398        )
399    }
400
401    /// Transfers `authority`.
402    /// Requires `authority` to sign
403    pub fn transfer_authority(
404        ctx: Context<TransferAuthority>,
405        new_authority: Pubkey,
406    ) -> Result<()> {
407        require_eq!(
408            *ctx.accounts.merkle_tree.owner,
409            crate::id(),
410            AccountCompressionError::IncorrectAccountOwner
411        );
412        let mut merkle_tree_bytes = ctx.accounts.merkle_tree.try_borrow_mut_data()?;
413        let (mut header_bytes, _) =
414            merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1);
415
416        let mut header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?;
417        header.assert_valid_authority(&ctx.accounts.authority.key())?;
418
419        header.set_new_authority(&new_authority);
420        header.serialize(&mut header_bytes)?;
421
422        Ok(())
423    }
424
425    /// Verifies a provided proof and leaf.
426    /// If invalid, throws an error.
427    pub fn verify_leaf(
428        ctx: Context<VerifyLeaf>,
429        root: [u8; 32],
430        leaf: [u8; 32],
431        index: u32,
432    ) -> Result<()> {
433        require_eq!(
434            *ctx.accounts.merkle_tree.owner,
435            crate::id(),
436            AccountCompressionError::IncorrectAccountOwner
437        );
438        let merkle_tree_bytes = ctx.accounts.merkle_tree.try_borrow_data()?;
439        let (header_bytes, rest) =
440            merkle_tree_bytes.split_at(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1);
441
442        let header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?;
443        header.assert_valid()?;
444        header.assert_valid_leaf_index(index)?;
445
446        let merkle_tree_size = merkle_tree_get_size(&header)?;
447        let (tree_bytes, canopy_bytes) = rest.split_at(merkle_tree_size);
448
449        let mut proof = vec![];
450        for node in ctx.remaining_accounts.iter() {
451            proof.push(node.key().to_bytes());
452        }
453        fill_in_proof_from_canopy(canopy_bytes, header.get_max_depth(), index, &mut proof)?;
454        let id = ctx.accounts.merkle_tree.key();
455
456        let args = &ProveLeafArgs {
457            current_root: root,
458            leaf,
459            proof_vec: proof,
460            index,
461        };
462        merkle_tree_prove_leaf(&header, id, tree_bytes, args)?;
463
464        Ok(())
465    }
466
467    /// This instruction allows the tree's `authority` to append a new leaf to the tree
468    /// without having to supply a proof.
469    ///
470    /// Learn more about SPL
471    /// ConcurrentMerkleTree
472    /// [here](https://github.com/solana-labs/solana-program-library/tree/master/libraries/concurrent-merkle-tree)
473    pub fn append(ctx: Context<Modify>, leaf: [u8; 32]) -> Result<()> {
474        require_eq!(
475            *ctx.accounts.merkle_tree.owner,
476            crate::id(),
477            AccountCompressionError::IncorrectAccountOwner
478        );
479        let mut merkle_tree_bytes = ctx.accounts.merkle_tree.try_borrow_mut_data()?;
480        let (header_bytes, rest) =
481            merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1);
482
483        let header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?;
484        header.assert_valid_authority(&ctx.accounts.authority.key())?;
485
486        let id = ctx.accounts.merkle_tree.key();
487        let merkle_tree_size = merkle_tree_get_size(&header)?;
488        let (tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size);
489        let change_log_event = merkle_tree_append_leaf(&header, id, tree_bytes, &leaf)?;
490        update_canopy(
491            canopy_bytes,
492            header.get_max_depth(),
493            Some(&change_log_event),
494        )?;
495        wrap_event(
496            &AccountCompressionEvent::ChangeLog(*change_log_event),
497            &ctx.accounts.noop,
498        )
499    }
500
501    /// This instruction takes a proof, and will attempt to write the given leaf
502    /// to the specified index in the tree. If the insert operation fails, the leaf will be `append`-ed
503    /// to the tree.
504    /// It is up to the indexer to parse the final location of the leaf from the emitted changelog.
505    pub fn insert_or_append(
506        ctx: Context<Modify>,
507        root: [u8; 32],
508        leaf: [u8; 32],
509        index: u32,
510    ) -> Result<()> {
511        require_eq!(
512            *ctx.accounts.merkle_tree.owner,
513            crate::id(),
514            AccountCompressionError::IncorrectAccountOwner
515        );
516        let mut merkle_tree_bytes = ctx.accounts.merkle_tree.try_borrow_mut_data()?;
517        let (header_bytes, rest) =
518            merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1);
519
520        let header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?;
521        header.assert_valid_authority(&ctx.accounts.authority.key())?;
522        header.assert_valid_leaf_index(index)?;
523
524        let merkle_tree_size = merkle_tree_get_size(&header)?;
525        let (tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size);
526
527        let mut proof = vec![];
528        for node in ctx.remaining_accounts.iter() {
529            proof.push(node.key().to_bytes());
530        }
531        fill_in_proof_from_canopy(canopy_bytes, header.get_max_depth(), index, &mut proof)?;
532        // A call is made to ConcurrentMerkleTree::fill_empty_or_append
533        let id = ctx.accounts.merkle_tree.key();
534        let args = &FillEmptyOrAppendArgs {
535            current_root: root,
536            leaf,
537            proof_vec: proof,
538            index,
539        };
540        let change_log_event = merkle_tree_fill_empty_or_append(&header, id, tree_bytes, args)?;
541
542        update_canopy(
543            canopy_bytes,
544            header.get_max_depth(),
545            Some(&change_log_event),
546        )?;
547        wrap_event(
548            &AccountCompressionEvent::ChangeLog(*change_log_event),
549            &ctx.accounts.noop,
550        )
551    }
552
553    pub fn close_empty_tree(ctx: Context<CloseTree>) -> Result<()> {
554        require_eq!(
555            *ctx.accounts.merkle_tree.owner,
556            crate::id(),
557            AccountCompressionError::IncorrectAccountOwner
558        );
559        let mut merkle_tree_bytes = ctx.accounts.merkle_tree.try_borrow_mut_data()?;
560        let (header_bytes, rest) =
561            merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1);
562
563        let header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?;
564        header.assert_valid_authority(&ctx.accounts.authority.key())?;
565
566        let merkle_tree_size = merkle_tree_get_size(&header)?;
567        let (tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size);
568
569        let id = ctx.accounts.merkle_tree.key();
570        assert_tree_is_empty(&header, id, tree_bytes)?;
571
572        // Close merkle tree account
573        // 1. Move lamports
574        let dest_starting_lamports = ctx.accounts.recipient.lamports();
575        **ctx.accounts.recipient.lamports.borrow_mut() = dest_starting_lamports
576            .checked_add(ctx.accounts.merkle_tree.lamports())
577            .unwrap();
578        **ctx.accounts.merkle_tree.lamports.borrow_mut() = 0;
579
580        // 2. Set all CMT account bytes to 0
581        header_bytes.fill(0);
582        tree_bytes.fill(0);
583        canopy_bytes.fill(0);
584
585        Ok(())
586    }
587}