From 3aa4a770865ee71f21af2a4599368b9eedbe2036 Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Tue, 21 May 2024 14:53:18 +0100 Subject: [PATCH 01/39] preinitialize_canopy method with the major modifications to the canopy tests for the canopy are included, the bpf/sbf tests for the contract will follow --- .../account-compression/src/canopy.rs | 238 +++++++++++++++++- .../programs/account-compression/src/error.rs | 4 + .../programs/account-compression/src/lib.rs | 53 +++- .../sdk/idl/spl_account_compression.json | 70 +++++- .../sdk/src/generated/errors/index.ts | 20 ++ .../sdk/src/generated/instructions/index.ts | 1 + .../instructions/preinitializeCanopy.ts | 109 ++++++++ .../types/AccountCompressionEvent.ts | 2 +- .../generated/types/ApplicationDataEvent.ts | 2 +- .../sdk/src/generated/types/ChangeLogEvent.ts | 2 +- .../types/ConcurrentMerkleTreeHeaderData.ts | 2 +- libraries/concurrent-merkle-tree/src/node.rs | 20 +- 12 files changed, 510 insertions(+), 13 deletions(-) create mode 100644 account-compression/sdk/src/generated/instructions/preinitializeCanopy.ts diff --git a/account-compression/programs/account-compression/src/canopy.rs b/account-compression/programs/account-compression/src/canopy.rs index bdf85d7d5d0..0e6cfc6b97d 100644 --- a/account-compression/programs/account-compression/src/canopy.rs +++ b/account-compression/programs/account-compression/src/canopy.rs @@ -20,9 +20,13 @@ use crate::error::AccountCompressionError; use crate::events::ChangeLogEvent; use anchor_lang::prelude::*; use bytemuck::{cast_slice, cast_slice_mut}; -use spl_concurrent_merkle_tree::node::{empty_node_cached, Node, EMPTY}; +use solana_program::keccak::hashv; +use spl_concurrent_merkle_tree::node::{empty_node_cached, empty_node_cached_mut, Node, EMPTY}; use std::mem::size_of; +// 30 is hard coded as it is the current max depth that SPL Compression supports +const MAX_SUPPORTED_DEPTH: usize = 30; + #[inline(always)] pub fn check_canopy_bytes(canopy_bytes: &[u8]) -> Result<()> { if canopy_bytes.len() % size_of::() != 0 { @@ -94,8 +98,7 @@ pub fn fill_in_proof_from_canopy( index: u32, proof: &mut Vec, ) -> Result<()> { - // 30 is hard coded as it is the current max depth that SPL Compression supports - let mut empty_node_cache = Box::new([EMPTY; 30]); + let mut empty_node_cache = Box::new([EMPTY; MAX_SUPPORTED_DEPTH]); check_canopy_bytes(canopy_bytes)?; let canopy = cast_slice::(canopy_bytes); let path_len = get_cached_path_length(canopy, max_depth)?; @@ -114,7 +117,7 @@ pub fn fill_in_proof_from_canopy( }; if canopy[cached_idx] == EMPTY { let level = max_depth - (31 - node_idx.leading_zeros()); - let empty_node = empty_node_cached::<30>(level, &mut empty_node_cache); + let empty_node = empty_node_cached::(level, &mut empty_node_cache); inferred_nodes.push(empty_node); } else { inferred_nodes.push(canopy[cached_idx]); @@ -128,3 +131,230 @@ pub fn fill_in_proof_from_canopy( proof.extend(inferred_nodes.iter().skip(overlap)); Ok(()) } + +/// Sets the leaf nodes of the canopy. The leaf nodes are the lowest level of the canopy, representing the leaves of the canopy-tree. +/// The method will update the parent nodes of all the modified subtrees up to the uppermost level of the canopy. +/// The leaf nodes indexing is 0-based for the start_index. +pub fn set_canopy_leaf_nodes( + canopy_bytes: &mut [u8], + max_depth: u32, + start_index: u32, + nodes: &[Node], +) -> Result<()> { + check_canopy_bytes(canopy_bytes)?; + let canopy = cast_slice_mut::(canopy_bytes); + let path_len = get_cached_path_length(canopy, max_depth)?; + + let start_canopy_node = leaf_node_index_to_canopy_index(path_len, start_index); + let start_canopy_idx = start_canopy_node - 2; + // set the "leaf" nodes of the canopy first - that's the lowest level of the canopy + for (i, node) in nodes.iter().enumerate() { + canopy[start_canopy_idx + i] = *node; + } + let mut start_canopy_node = start_canopy_node; + let mut end_canopy_node = start_canopy_node + nodes.len() - 1 as usize; + let mut empty_node_cache = Box::new([EMPTY; MAX_SUPPORTED_DEPTH]); + let leaf_node_level = max_depth - path_len; + // traverse up the tree and update the parent nodes in the modified subtree + for level in leaf_node_level + 1..max_depth { + start_canopy_node >>= 1; + end_canopy_node >>= 1; + for node in start_canopy_node..end_canopy_node + 1 { + let left_child = get_value_for_node::( + node << 1, + level - 1, + &canopy, + &mut empty_node_cache, + ); + let right_child = get_value_for_node::( + (node << 1) + 1, + level - 1, + &canopy, + &mut empty_node_cache, + ); + canopy[node - 2 as usize].copy_from_slice(hashv(&[&left_child, &right_child]).as_ref()); + } + } + Ok(()) +} + +#[inline(always)] +fn get_value_for_node( + node_idx: usize, + level: u32, + canopy: &[Node], + empty_node_cache: &mut [Node; N], +) -> Node { + if canopy[node_idx - 2] != EMPTY { + return canopy[node_idx - 2]; + } + empty_node_cached_mut::(level, empty_node_cache) +} + +#[inline(always)] +fn leaf_node_index_to_canopy_index(path_len: u32, index: u32) -> usize { + (1 << path_len) + index as usize +} + +#[cfg(test)] +mod tests { + use super::*; + use spl_concurrent_merkle_tree::node::empty_node; + + fn test_leaf_node_index_to_canopy_index_impl(path_len: u32, index: u32, expected: usize) { + assert_eq!(leaf_node_index_to_canopy_index(path_len, index), expected); + } + + // todo: 0,0,0? + + #[test] + fn test_1_level_0_index() { + test_leaf_node_index_to_canopy_index_impl(1, 0, 2); + } + + #[test] + fn test_1_level_1_index() { + test_leaf_node_index_to_canopy_index_impl(1, 1, 3); + } + + #[test] + fn test_2_level_0_index() { + test_leaf_node_index_to_canopy_index_impl(2, 0, 4); + } + #[test] + fn test_2_level_3_index() { + test_leaf_node_index_to_canopy_index_impl(2, 3, 7); + } + + #[test] + fn test_10_level_0_index() { + test_leaf_node_index_to_canopy_index_impl(10, 0, 1024); + } + + #[test] + fn test_10_level_1023_index() { + test_leaf_node_index_to_canopy_index_impl(10, 1023, 2047); + } + + #[test] + fn test_simple_single_level_canopy_set_canopy_leaf_nodes_with_empty_nodes() { + let mut canopy_bytes = vec![0_u8; 2 * size_of::()]; + let nodes = vec![EMPTY; 2]; + set_canopy_leaf_nodes(&mut canopy_bytes, 1, 0, &nodes).unwrap(); + let canopy = cast_slice::(&canopy_bytes); + + assert_eq!(canopy[0], EMPTY); + assert_eq!(canopy[1], EMPTY); + } + + #[test] + fn test_simple_single_level_canopy_set_canopy_leaf_nodes_non_empty_nodes() { + let mut canopy_bytes = vec![0_u8; 2 * size_of::()]; + let nodes = vec![[1_u8; 32], [2_u8; 32]]; + set_canopy_leaf_nodes(&mut canopy_bytes, 1, 0, &nodes).unwrap(); + let canopy = cast_slice::(&canopy_bytes); + + assert_eq!(canopy[0], [1_u8; 32]); + assert_eq!(canopy[1], [2_u8; 32]); + } + + #[test] + fn test_2levels_canopy_set_canopy_leaf_nodes_first_2_elements_provided() { + let mut canopy_bytes = vec![0_u8; 6 * size_of::()]; + let nodes = vec![[1_u8; 32], [2_u8; 32]]; + set_canopy_leaf_nodes(&mut canopy_bytes, 2, 0, &nodes).unwrap(); + let canopy = cast_slice::(&canopy_bytes); + + assert_eq!(canopy[0], hashv(&[&[1_u8; 32], &[2_u8; 32]]).to_bytes()); + assert_eq!(canopy[1], EMPTY); // is not updated + assert_eq!(canopy[2], [1_u8; 32]); + assert_eq!(canopy[3], [2_u8; 32]); + assert_eq!(canopy[4], EMPTY); + assert_eq!(canopy[5], EMPTY); + } + + #[test] + fn test_2levels_canopy_set_canopy_leaf_nodes_last_2_elements_provided() { + let mut canopy_bytes = vec![0_u8; 6 * size_of::()]; + let nodes = vec![[1_u8; 32], [2_u8; 32]]; + set_canopy_leaf_nodes(&mut canopy_bytes, 2, 2, &nodes).unwrap(); + let canopy = cast_slice::(&canopy_bytes); + + assert_eq!(canopy[0], EMPTY); // is not updated + assert_eq!(canopy[1], hashv(&[&[1_u8; 32], &[2_u8; 32]]).to_bytes()); + assert_eq!(canopy[2], EMPTY); + assert_eq!(canopy[3], EMPTY); + assert_eq!(canopy[4], [1_u8; 32]); + assert_eq!(canopy[5], [2_u8; 32]); + } + + #[test] + fn test_2levels_canopy_set_canopy_leaf_nodes_middle_2_elements_provided() { + let mut canopy_bytes = vec![0_u8; 6 * size_of::()]; + let nodes = vec![[1_u8; 32], [2_u8; 32]]; + set_canopy_leaf_nodes(&mut canopy_bytes, 2, 1, &nodes).unwrap(); + let canopy = cast_slice::(&canopy_bytes); + + assert_eq!(canopy[2], EMPTY); + assert_eq!(canopy[3], [1_u8; 32]); + assert_eq!(canopy[4], [2_u8; 32]); + assert_eq!(canopy[5], EMPTY); + assert_eq!(canopy[0], hashv(&[&EMPTY, &[1_u8; 32]]).to_bytes()); + assert_eq!(canopy[1], hashv(&[&[2_u8; 32], &EMPTY]).to_bytes()); + } + + #[test] + fn test_3level_canopy_in_10_level_tree_set_canopy_leaf_nodes_first_2_elements_provided() { + let mut canopy_bytes = vec![0_u8; 14 * size_of::()]; + let nodes = vec![[1_u8; 32], [2_u8; 32]]; + set_canopy_leaf_nodes(&mut canopy_bytes, 10, 0, &nodes).unwrap(); + let canopy = cast_slice::(&canopy_bytes); + + let expected_hash12 = hashv(&[&[1_u8; 32], &[2_u8; 32]]).to_bytes(); + assert_eq!( + canopy[0], + hashv(&[&expected_hash12, &empty_node(8)]).to_bytes() + ); + assert_eq!(canopy[1], EMPTY); // is not updated + assert_eq!(canopy[2], expected_hash12); + assert_eq!(canopy[3], EMPTY); // is not updated + assert_eq!(canopy[4], EMPTY); // is not updated + assert_eq!(canopy[5], EMPTY); // is not updated + assert_eq!(canopy[6], [1_u8; 32]); + assert_eq!(canopy[7], [2_u8; 32]); + } + + #[test] + fn test_3level_canopy_in_10_level_tree_set_canopy_leaf_nodes_middle_2_elements_provided() { + let mut canopy_bytes = vec![0_u8; 14 * size_of::()]; + let nodes = vec![[1_u8; 32], [2_u8; 32]]; + set_canopy_leaf_nodes(&mut canopy_bytes, 10, 3, &nodes).unwrap(); + let canopy = cast_slice::(&canopy_bytes); + + let expected_hash_empty_1 = hashv(&[&empty_node(7), &[1_u8; 32]]).to_bytes(); + let expected_hash_2_empty = hashv(&[&[2_u8; 32], &empty_node(7)]).to_bytes(); + + assert_eq!( + canopy[0], + hashv(&[&empty_node(8), &expected_hash_empty_1]).to_bytes() + ); + assert_eq!( + canopy[1], + hashv(&[&expected_hash_2_empty, &empty_node(8)]).to_bytes() + ); + assert_eq!(canopy[2], EMPTY); // is not updated + assert_eq!(canopy[3], expected_hash_empty_1); + assert_eq!(canopy[4], expected_hash_2_empty); + assert_eq!(canopy[5], EMPTY); // is not updated + assert_eq!(canopy[9], [1_u8; 32]); + assert_eq!(canopy[10], [2_u8; 32]); + } + + #[test] + fn test_3level_canopy_empty_set_canopy_leaf_nodes_no_impact() { + let mut canopy_bytes = vec![0_u8; 14 * size_of::()]; + let nodes = vec![]; + set_canopy_leaf_nodes(&mut canopy_bytes, 10, 0, &nodes).unwrap(); + assert_eq!(canopy_bytes, vec![0_u8; 14 * size_of::()]); + } +} diff --git a/account-compression/programs/account-compression/src/error.rs b/account-compression/programs/account-compression/src/error.rs index fc7efdb215a..e684bb52454 100644 --- a/account-compression/programs/account-compression/src/error.rs +++ b/account-compression/programs/account-compression/src/error.rs @@ -47,6 +47,10 @@ pub enum AccountCompressionError { /// is out of bounds of tree's maximum leaf capacity #[msg("Leaf index of concurrent merkle tree is out of bounds")] LeafIndexOutOfBounds, + + /// When initializing a canopy of the tree, the underlying tree was allocated without space for the canopy + #[msg("Tree was initialized without allocating space for the canopy")] + CanopyNotAllocated, } impl From<&ConcurrentMerkleTreeError> for AccountCompressionError { diff --git a/account-compression/programs/account-compression/src/lib.rs b/account-compression/programs/account-compression/src/lib.rs index 152cd5f745b..e0f5229dcdc 100644 --- a/account-compression/programs/account-compression/src/lib.rs +++ b/account-compression/programs/account-compression/src/lib.rs @@ -40,7 +40,7 @@ pub mod zero_copy; pub use crate::noop::{wrap_application_data_v1, Noop}; -use crate::canopy::{fill_in_proof_from_canopy, update_canopy}; +use crate::canopy::{fill_in_proof_from_canopy, set_canopy_leaf_nodes, update_canopy}; use crate::concurrent_tree_wrapper::*; pub use crate::error::AccountCompressionError; pub use crate::events::{AccountCompressionEvent, ChangeLogEvent}; @@ -180,6 +180,57 @@ pub mod spl_account_compression { update_canopy(canopy_bytes, header.get_max_depth(), None) } + /// The tree might contain a canopy, which is a cache of the uppermost nodes. + /// The canopy is used to decrease the size of the proof required to update the tree. + /// There are 2 ways to initialize a merkle tree: + /// 1. Initialize an empty tree + /// 2. Initialize a tree with a root and leaf + /// For the former case, the canopy will be empty which is expected for an empty tree. + /// For the latter case, the canopy should be filled with the necessary nodes to render the tree usable. + /// Thus we need to prefill the canopy with the necessary nodes. + /// + /// This instruction pre-initializes the canopy with the leftmost leaf nodes of the canopy. + pub fn preinitialize_canopy( + ctx: Context, + max_depth: u32, + max_buffer_size: u32, + start_index: u32, + canopy_nodes: Vec<[u8; 32]>, + ) -> Result<()> { + require_eq!( + *ctx.accounts.merkle_tree.owner, + crate::id(), + AccountCompressionError::IncorrectAccountOwner + ); + let mut merkle_tree_bytes = ctx.accounts.merkle_tree.try_borrow_mut_data()?; + + let (mut header_bytes, rest) = + merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1); + + let mut header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?; + header.initialize( + max_depth, + max_buffer_size, + &ctx.accounts.authority.key(), + Clock::get()?.slot, + ); + let merkle_tree_size = merkle_tree_get_size(&header)?; + let (_tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size); + // Ensure canopy is allocated before writing the header to the state + require_gt!( + canopy_bytes.len(), + 0, + AccountCompressionError::CanopyNotAllocated + ); + header.serialize(&mut header_bytes)?; + set_canopy_leaf_nodes( + canopy_bytes, + header.get_max_depth(), + start_index, + &canopy_nodes, + ) + } + /// Note: /// Supporting this instruction open a security vulnerability for indexers. /// This instruction has been deemed unusable for publicly indexed compressed NFTs. diff --git a/account-compression/sdk/idl/spl_account_compression.json b/account-compression/sdk/idl/spl_account_compression.json index 1a5de6f83d9..f8876446b2b 100644 --- a/account-compression/sdk/idl/spl_account_compression.json +++ b/account-compression/sdk/idl/spl_account_compression.json @@ -1,5 +1,5 @@ { - "version": "0.2.0", + "version": "0.3.0", "name": "spl_account_compression", "instructions": [ { @@ -51,6 +51,65 @@ } ] }, + { + "name": "preinitializeCanopy", + "docs": [ + "The tree might contain a canopy, which is a cache of the uppermost nodes.", + "The canopy is used to decrease the size of the proof required to update the tree.", + "There are 2 ways to initialize a merkle tree:", + "1. Initialize an empty tree", + "2. Initialize a tree with a root and leaf", + "For the former case, the canopy will be empty which is expected for an empty tree.", + "For the latter case, the canopy should be filled with the necessary nodes to render the tree usable.", + "Thus we need to prefill the canopy with the necessary nodes.", + "", + "This instruction pre-initializes the canopy with the leftmost leaf nodes of the canopy." + ], + "accounts": [ + { + "name": "merkleTree", + "isMut": true, + "isSigner": false + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "docs": [ + "Authority that controls write-access to the tree", + "Typically a program, e.g., the Bubblegum contract validates that leaves are valid NFTs." + ] + }, + { + "name": "noop", + "isMut": false, + "isSigner": false, + "docs": ["Program used to emit changelogs as cpi instruction data."] + } + ], + "args": [ + { + "name": "maxDepth", + "type": "u32" + }, + { + "name": "maxBufferSize", + "type": "u32" + }, + { + "name": "startIndex", + "type": "u32" + }, + { + "name": "canopyNodes", + "type": { + "vec": { + "array": ["u8", 32] + } + } + } + ] + }, { "name": "replaceLeaf", "docs": [ @@ -570,12 +629,17 @@ "code": 6008, "name": "LeafIndexOutOfBounds", "msg": "Leaf index of concurrent merkle tree is out of bounds" + }, + { + "code": 6009, + "name": "CanopyNotAllocated", + "msg": "Tree was initialized without allocating space for the canopy" } ], "metadata": { "address": "cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK", "origin": "anchor", - "binaryVersion": "0.25.0", - "libVersion": "0.25.0" + "binaryVersion": "0.29.0", + "libVersion": "0.29.0" } } diff --git a/account-compression/sdk/src/generated/errors/index.ts b/account-compression/sdk/src/generated/errors/index.ts index 4600510da7f..44d6837ae2f 100644 --- a/account-compression/sdk/src/generated/errors/index.ts +++ b/account-compression/sdk/src/generated/errors/index.ts @@ -194,6 +194,26 @@ export class LeafIndexOutOfBoundsError extends Error { createErrorFromCodeLookup.set(0x1778, () => new LeafIndexOutOfBoundsError()); createErrorFromNameLookup.set('LeafIndexOutOfBounds', () => new LeafIndexOutOfBoundsError()); +/** + * CanopyNotAllocated: 'Tree was initialized without allocating space for the canopy' + * + * @category Errors + * @category generated + */ +export class CanopyNotAllocatedError extends Error { + readonly code: number = 0x1779; + readonly name: string = 'CanopyNotAllocated'; + constructor() { + super('Tree was initialized without allocating space for the canopy'); + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(this, CanopyNotAllocatedError); + } + } +} + +createErrorFromCodeLookup.set(0x1779, () => new CanopyNotAllocatedError()); +createErrorFromNameLookup.set('CanopyNotAllocated', () => new CanopyNotAllocatedError()); + /** * Attempts to resolve a custom program error from the provided error code. * @category Errors diff --git a/account-compression/sdk/src/generated/instructions/index.ts b/account-compression/sdk/src/generated/instructions/index.ts index 7194bbb5b9e..7f8cfa5b9ac 100644 --- a/account-compression/sdk/src/generated/instructions/index.ts +++ b/account-compression/sdk/src/generated/instructions/index.ts @@ -2,6 +2,7 @@ export * from './append'; export * from './closeEmptyTree'; export * from './initEmptyMerkleTree'; export * from './insertOrAppend'; +export * from './preinitializeCanopy'; export * from './replaceLeaf'; export * from './transferAuthority'; export * from './verifyLeaf'; diff --git a/account-compression/sdk/src/generated/instructions/preinitializeCanopy.ts b/account-compression/sdk/src/generated/instructions/preinitializeCanopy.ts new file mode 100644 index 00000000000..513a2c27f02 --- /dev/null +++ b/account-compression/sdk/src/generated/instructions/preinitializeCanopy.ts @@ -0,0 +1,109 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet'; +import * as web3 from '@solana/web3.js'; + +/** + * @category Instructions + * @category PreinitializeCanopy + * @category generated + */ +export type PreinitializeCanopyInstructionArgs = { + canopyNodes: number[] /* size: 32 */[]; + maxBufferSize: number; + maxDepth: number; + startIndex: number; +}; +/** + * @category Instructions + * @category PreinitializeCanopy + * @category generated + */ +export const preinitializeCanopyStruct = new beet.FixableBeetArgsStruct< + PreinitializeCanopyInstructionArgs & { + instructionDiscriminator: number[] /* size: 8 */; + } +>( + [ + ['instructionDiscriminator', beet.uniformFixedSizeArray(beet.u8, 8)], + ['maxDepth', beet.u32], + ['maxBufferSize', beet.u32], + ['startIndex', beet.u32], + ['canopyNodes', beet.array(beet.uniformFixedSizeArray(beet.u8, 32))], + ], + 'PreinitializeCanopyInstructionArgs', +); +/** + * Accounts required by the _preinitializeCanopy_ instruction + * + * @property [_writable_] merkleTree + * @property [**signer**] authority + * @property [] noop + * @category Instructions + * @category PreinitializeCanopy + * @category generated + */ +export type PreinitializeCanopyInstructionAccounts = { + anchorRemainingAccounts?: web3.AccountMeta[]; + authority: web3.PublicKey; + merkleTree: web3.PublicKey; + noop: web3.PublicKey; +}; + +export const preinitializeCanopyInstructionDiscriminator = [233, 92, 157, 34, 63, 20, 168, 13]; + +/** + * Creates a _PreinitializeCanopy_ instruction. + * + * @param accounts that will be accessed while the instruction is processed + * @param args to provide as instruction data to the program + * + * @category Instructions + * @category PreinitializeCanopy + * @category generated + */ +export function createPreinitializeCanopyInstruction( + accounts: PreinitializeCanopyInstructionAccounts, + args: PreinitializeCanopyInstructionArgs, + programId = new web3.PublicKey('cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK'), +) { + const [data] = preinitializeCanopyStruct.serialize({ + instructionDiscriminator: preinitializeCanopyInstructionDiscriminator, + ...args, + }); + const keys: web3.AccountMeta[] = [ + { + isSigner: false, + isWritable: true, + pubkey: accounts.merkleTree, + }, + { + isSigner: true, + isWritable: false, + pubkey: accounts.authority, + }, + { + isSigner: false, + isWritable: false, + pubkey: accounts.noop, + }, + ]; + + if (accounts.anchorRemainingAccounts != null) { + for (const acc of accounts.anchorRemainingAccounts) { + keys.push(acc); + } + } + + const ix = new web3.TransactionInstruction({ + data, + keys, + programId, + }); + return ix; +} diff --git a/account-compression/sdk/src/generated/types/AccountCompressionEvent.ts b/account-compression/sdk/src/generated/types/AccountCompressionEvent.ts index 4bce6560620..ee25ced07a1 100644 --- a/account-compression/sdk/src/generated/types/AccountCompressionEvent.ts +++ b/account-compression/sdk/src/generated/types/AccountCompressionEvent.ts @@ -62,4 +62,4 @@ export const accountCompressionEventBeet = beet.dataEnum; +]) as beet.FixableBeet; diff --git a/account-compression/sdk/src/generated/types/ApplicationDataEvent.ts b/account-compression/sdk/src/generated/types/ApplicationDataEvent.ts index eef6c13b391..d6fd2aff4bf 100644 --- a/account-compression/sdk/src/generated/types/ApplicationDataEvent.ts +++ b/account-compression/sdk/src/generated/types/ApplicationDataEvent.ts @@ -49,4 +49,4 @@ export const applicationDataEventBeet = beet.dataEnum; +]) as beet.FixableBeet; diff --git a/account-compression/sdk/src/generated/types/ChangeLogEvent.ts b/account-compression/sdk/src/generated/types/ChangeLogEvent.ts index ffd34de7791..9418cff3bcf 100644 --- a/account-compression/sdk/src/generated/types/ChangeLogEvent.ts +++ b/account-compression/sdk/src/generated/types/ChangeLogEvent.ts @@ -48,4 +48,4 @@ export const changeLogEventBeet = beet.dataEnum([ 'ChangeLogEventRecord["V1"]', ), ], -]) as beet.FixableBeet; +]) as beet.FixableBeet; diff --git a/account-compression/sdk/src/generated/types/ConcurrentMerkleTreeHeaderData.ts b/account-compression/sdk/src/generated/types/ConcurrentMerkleTreeHeaderData.ts index f49f8f38996..3c1c8011f10 100644 --- a/account-compression/sdk/src/generated/types/ConcurrentMerkleTreeHeaderData.ts +++ b/account-compression/sdk/src/generated/types/ConcurrentMerkleTreeHeaderData.ts @@ -53,4 +53,4 @@ export const concurrentMerkleTreeHeaderDataBeet = beet.dataEnum; +]) as beet.FixableBeet; diff --git a/libraries/concurrent-merkle-tree/src/node.rs b/libraries/concurrent-merkle-tree/src/node.rs index 876ce61f095..f7fdeb8973c 100644 --- a/libraries/concurrent-merkle-tree/src/node.rs +++ b/libraries/concurrent-merkle-tree/src/node.rs @@ -11,7 +11,7 @@ pub fn empty_node(level: u32) -> Node { empty_node_cached::<0>(level, &[]) } -/// Calculates and caches the hash of empty nodes up to level i +/// Calculates the hash of empty nodes up to level i using an existing cache pub fn empty_node_cached(level: u32, cache: &[Node; N]) -> Node { let mut data = EMPTY; if level != 0 { @@ -26,3 +26,21 @@ pub fn empty_node_cached(level: u32, cache: &[Node; N]) -> Node } data } + + +/// Calculates and caches the hash of empty nodes up to level i +pub fn empty_node_cached_mut(level: u32, cache: &mut[Node; N]) -> Node { + let mut data = EMPTY; + if level != 0 { + let target = (level - 1) as usize; + let lower_empty = if target < cache.len() && cache[target] != EMPTY { + cache[target] + } else { + empty_node(target as u32) + }; + let hash = hashv(&[lower_empty.as_ref(), lower_empty.as_ref()]); + data.copy_from_slice(hash.as_ref()); + } + cache[level as usize] = data; + data +} From 4265777ff465e7e493bb030e2bf6ec949b36895f Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Tue, 21 May 2024 17:41:51 +0100 Subject: [PATCH 02/39] append canopy nodes implementation tests will be added later in a form of integration tests next steps: - verify the zero-ed check is valid for the tree; - drop is_initialized_wrapped method. --- .../programs/account-compression/src/error.rs | 4 + .../programs/account-compression/src/lib.rs | 54 ++++++++- .../state/concurrent_merkle_tree_header.rs | 13 +++ .../sdk/idl/spl_account_compression.json | 47 +++++++- .../sdk/src/generated/errors/index.ts | 20 ++++ .../instructions/appendCanopyNodes.ts | 105 ++++++++++++++++++ .../sdk/src/generated/instructions/index.ts | 1 + .../src/concurrent_merkle_tree.rs | 5 + 8 files changed, 247 insertions(+), 2 deletions(-) create mode 100644 account-compression/sdk/src/generated/instructions/appendCanopyNodes.ts diff --git a/account-compression/programs/account-compression/src/error.rs b/account-compression/programs/account-compression/src/error.rs index e684bb52454..c9378c4dc84 100644 --- a/account-compression/programs/account-compression/src/error.rs +++ b/account-compression/programs/account-compression/src/error.rs @@ -51,6 +51,10 @@ pub enum AccountCompressionError { /// When initializing a canopy of the tree, the underlying tree was allocated without space for the canopy #[msg("Tree was initialized without allocating space for the canopy")] CanopyNotAllocated, + + /// The tree was already initialized + #[msg("Tree was already initialized")] + TreeAlreadyInitialized, } impl From<&ConcurrentMerkleTreeError> for AccountCompressionError { diff --git a/account-compression/programs/account-compression/src/lib.rs b/account-compression/programs/account-compression/src/lib.rs index e0f5229dcdc..7cf151475fb 100644 --- a/account-compression/programs/account-compression/src/lib.rs +++ b/account-compression/programs/account-compression/src/lib.rs @@ -55,6 +55,7 @@ pub use spl_concurrent_merkle_tree::{ concurrent_merkle_tree::{ConcurrentMerkleTree, FillEmptyOrAppendArgs}, error::ConcurrentMerkleTreeError, node::Node, + node::EMPTY, }; declare_id!("cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK"); @@ -189,7 +190,8 @@ pub mod spl_account_compression { /// For the latter case, the canopy should be filled with the necessary nodes to render the tree usable. /// Thus we need to prefill the canopy with the necessary nodes. /// - /// This instruction pre-initializes the canopy with the leftmost leaf nodes of the canopy. + /// This instruction pre-initializes the canopy with the leftmost leaf nodes of the canopy. It initializes the tree header while leaving the tree itself uninitialized. + /// This is intended to be used in conjunction with the `init_merkle_tree_with_root` instruction that'll finalize the tree initialization. pub fn preinitialize_canopy( ctx: Context, max_depth: u32, @@ -222,6 +224,7 @@ pub mod spl_account_compression { 0, AccountCompressionError::CanopyNotAllocated ); + // todo: assert the start index is within bounds header.serialize(&mut header_bytes)?; set_canopy_leaf_nodes( canopy_bytes, @@ -231,6 +234,55 @@ pub mod spl_account_compression { ) } + pub fn append_canopy_nodes( + ctx: Context, + start_index: u32, + canopy_nodes: Vec<[u8; 32]>, + ) -> Result<()> { + require_eq!( + *ctx.accounts.merkle_tree.owner, + crate::id(), + AccountCompressionError::IncorrectAccountOwner + ); + let mut merkle_tree_bytes = ctx.accounts.merkle_tree.try_borrow_mut_data()?; + + let (header_bytes, rest) = + merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1); + + let header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?; + header.assert_valid_authority(&ctx.accounts.authority.key())?; + // todo: assert the start index is within bounds + // assert the tree is not initialized yet, we don't want to overwrite the canopy of an initialized tree + let merkle_tree_size = merkle_tree_get_size(&header)?; + let (tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size); + // ensure the tree is not initialized, the hacky way + require!( + tree_bytes.iter().all(|&x| x == 0), + AccountCompressionError::TreeAlreadyInitialized + ); + // TODO: remove the block below + // an alternative of the above line is to check the last node of the last change log event, which should contain an empty root: + let id = ctx.accounts.merkle_tree.key(); + // A call is made to ConcurrentMerkleTree::is_initialized_wrapped() + let change_log_event = + merkle_tree_apply_fn!(header, id, tree_bytes, is_initialized_wrapped,)?; + match *change_log_event { + ChangeLogEvent::V1(is_initialized_change_log) => { + // todo: consider removing the unwrap + require!( + is_initialized_change_log.path.last().unwrap().node == EMPTY, + AccountCompressionError::TreeAlreadyInitialized + ); + } + } + set_canopy_leaf_nodes( + canopy_bytes, + header.get_max_depth(), + start_index, + &canopy_nodes, + ) + } + /// Note: /// Supporting this instruction open a security vulnerability for indexers. /// This instruction has been deemed unusable for publicly indexed compressed NFTs. diff --git a/account-compression/programs/account-compression/src/state/concurrent_merkle_tree_header.rs b/account-compression/programs/account-compression/src/state/concurrent_merkle_tree_header.rs index 6d326b76142..6120f13333e 100644 --- a/account-compression/programs/account-compression/src/state/concurrent_merkle_tree_header.rs +++ b/account-compression/programs/account-compression/src/state/concurrent_merkle_tree_header.rs @@ -99,6 +99,19 @@ impl ConcurrentMerkleTreeHeader { } } + pub fn is_initialized(&self) -> bool { + match self.account_type { + CompressionAccountType::Uninitialized => false, + CompressionAccountType::ConcurrentMerkleTree => { + match &self.header { + ConcurrentMerkleTreeHeaderData::V1(header) => { + header.max_buffer_size != 0 && header.max_depth != 0 + } + } + } + } + } + pub fn get_max_depth(&self) -> u32 { match &self.header { ConcurrentMerkleTreeHeaderData::V1(header) => header.max_depth, diff --git a/account-compression/sdk/idl/spl_account_compression.json b/account-compression/sdk/idl/spl_account_compression.json index f8876446b2b..3243c8e57c5 100644 --- a/account-compression/sdk/idl/spl_account_compression.json +++ b/account-compression/sdk/idl/spl_account_compression.json @@ -63,7 +63,8 @@ "For the latter case, the canopy should be filled with the necessary nodes to render the tree usable.", "Thus we need to prefill the canopy with the necessary nodes.", "", - "This instruction pre-initializes the canopy with the leftmost leaf nodes of the canopy." + "This instruction pre-initializes the canopy with the leftmost leaf nodes of the canopy. It initializes the tree header while leaving the tree itself uninitialized.", + "This is intended to be used in conjunction with the `init_merkle_tree_with_root` instruction that'll finalize the tree initialization." ], "accounts": [ { @@ -110,6 +111,45 @@ } ] }, + { + "name": "appendCanopyNodes", + "accounts": [ + { + "name": "merkleTree", + "isMut": true, + "isSigner": false + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "docs": [ + "Authority that controls write-access to the tree", + "Typically a program, e.g., the Bubblegum contract validates that leaves are valid NFTs." + ] + }, + { + "name": "noop", + "isMut": false, + "isSigner": false, + "docs": ["Program used to emit changelogs as cpi instruction data."] + } + ], + "args": [ + { + "name": "startIndex", + "type": "u32" + }, + { + "name": "canopyNodes", + "type": { + "vec": { + "array": ["u8", 32] + } + } + } + ] + }, { "name": "replaceLeaf", "docs": [ @@ -634,6 +674,11 @@ "code": 6009, "name": "CanopyNotAllocated", "msg": "Tree was initialized without allocating space for the canopy" + }, + { + "code": 6010, + "name": "TreeAlreadyInitialized", + "msg": "Tree was already initialized" } ], "metadata": { diff --git a/account-compression/sdk/src/generated/errors/index.ts b/account-compression/sdk/src/generated/errors/index.ts index 44d6837ae2f..0bf612e38b6 100644 --- a/account-compression/sdk/src/generated/errors/index.ts +++ b/account-compression/sdk/src/generated/errors/index.ts @@ -214,6 +214,26 @@ export class CanopyNotAllocatedError extends Error { createErrorFromCodeLookup.set(0x1779, () => new CanopyNotAllocatedError()); createErrorFromNameLookup.set('CanopyNotAllocated', () => new CanopyNotAllocatedError()); +/** + * TreeAlreadyInitialized: 'Tree was already initialized' + * + * @category Errors + * @category generated + */ +export class TreeAlreadyInitializedError extends Error { + readonly code: number = 0x177a; + readonly name: string = 'TreeAlreadyInitialized'; + constructor() { + super('Tree was already initialized'); + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(this, TreeAlreadyInitializedError); + } + } +} + +createErrorFromCodeLookup.set(0x177a, () => new TreeAlreadyInitializedError()); +createErrorFromNameLookup.set('TreeAlreadyInitialized', () => new TreeAlreadyInitializedError()); + /** * Attempts to resolve a custom program error from the provided error code. * @category Errors diff --git a/account-compression/sdk/src/generated/instructions/appendCanopyNodes.ts b/account-compression/sdk/src/generated/instructions/appendCanopyNodes.ts new file mode 100644 index 00000000000..5cf4ddff765 --- /dev/null +++ b/account-compression/sdk/src/generated/instructions/appendCanopyNodes.ts @@ -0,0 +1,105 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet'; +import * as web3 from '@solana/web3.js'; + +/** + * @category Instructions + * @category AppendCanopyNodes + * @category generated + */ +export type AppendCanopyNodesInstructionArgs = { + canopyNodes: number[] /* size: 32 */[]; + startIndex: number; +}; +/** + * @category Instructions + * @category AppendCanopyNodes + * @category generated + */ +export const appendCanopyNodesStruct = new beet.FixableBeetArgsStruct< + AppendCanopyNodesInstructionArgs & { + instructionDiscriminator: number[] /* size: 8 */; + } +>( + [ + ['instructionDiscriminator', beet.uniformFixedSizeArray(beet.u8, 8)], + ['startIndex', beet.u32], + ['canopyNodes', beet.array(beet.uniformFixedSizeArray(beet.u8, 32))], + ], + 'AppendCanopyNodesInstructionArgs', +); +/** + * Accounts required by the _appendCanopyNodes_ instruction + * + * @property [_writable_] merkleTree + * @property [**signer**] authority + * @property [] noop + * @category Instructions + * @category AppendCanopyNodes + * @category generated + */ +export type AppendCanopyNodesInstructionAccounts = { + anchorRemainingAccounts?: web3.AccountMeta[]; + authority: web3.PublicKey; + merkleTree: web3.PublicKey; + noop: web3.PublicKey; +}; + +export const appendCanopyNodesInstructionDiscriminator = [139, 155, 238, 167, 11, 243, 132, 205]; + +/** + * Creates a _AppendCanopyNodes_ instruction. + * + * @param accounts that will be accessed while the instruction is processed + * @param args to provide as instruction data to the program + * + * @category Instructions + * @category AppendCanopyNodes + * @category generated + */ +export function createAppendCanopyNodesInstruction( + accounts: AppendCanopyNodesInstructionAccounts, + args: AppendCanopyNodesInstructionArgs, + programId = new web3.PublicKey('cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK'), +) { + const [data] = appendCanopyNodesStruct.serialize({ + instructionDiscriminator: appendCanopyNodesInstructionDiscriminator, + ...args, + }); + const keys: web3.AccountMeta[] = [ + { + isSigner: false, + isWritable: true, + pubkey: accounts.merkleTree, + }, + { + isSigner: true, + isWritable: false, + pubkey: accounts.authority, + }, + { + isSigner: false, + isWritable: false, + pubkey: accounts.noop, + }, + ]; + + if (accounts.anchorRemainingAccounts != null) { + for (const acc of accounts.anchorRemainingAccounts) { + keys.push(acc); + } + } + + const ix = new web3.TransactionInstruction({ + data, + keys, + programId, + }); + return ix; +} diff --git a/account-compression/sdk/src/generated/instructions/index.ts b/account-compression/sdk/src/generated/instructions/index.ts index 7f8cfa5b9ac..b81af6ee324 100644 --- a/account-compression/sdk/src/generated/instructions/index.ts +++ b/account-compression/sdk/src/generated/instructions/index.ts @@ -1,4 +1,5 @@ export * from './append'; +export * from './appendCanopyNodes'; export * from './closeEmptyTree'; export * from './initEmptyMerkleTree'; export * from './insertOrAppend'; diff --git a/libraries/concurrent-merkle-tree/src/concurrent_merkle_tree.rs b/libraries/concurrent-merkle-tree/src/concurrent_merkle_tree.rs index c0dc495dc6a..9a0bcb0bcea 100644 --- a/libraries/concurrent-merkle-tree/src/concurrent_merkle_tree.rs +++ b/libraries/concurrent-merkle-tree/src/concurrent_merkle_tree.rs @@ -141,6 +141,11 @@ impl !(self.buffer_size == 0 && self.sequence_number == 0 && self.active_index == 0) } + // The initialization method is wrapped to allow for calls through the macro + pub fn is_initialized_wrapped(&self) -> Result { + Ok(self.is_initialized()) + } + /// This is the trustless initialization method that should be used in most /// cases. pub fn initialize(&mut self) -> Result { From 1d94f80a75f22e13e0a17d4911078043d17904bc Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Wed, 22 May 2024 16:08:19 +0100 Subject: [PATCH 03/39] init_merkle_tree_with_root and refactored prepare_tree --- .../account-compression/src/canopy.rs | 207 ++++++++++++++++-- .../programs/account-compression/src/error.rs | 8 + .../programs/account-compression/src/lib.rs | 165 +++++++------- .../sdk/idl/spl_account_compression.json | 84 +++++-- .../sdk/src/generated/errors/index.ts | 40 ++++ .../sdk/src/generated/instructions/index.ts | 3 +- .../instructions/initMerkleTreeWithRoot.ts | 107 +++++++++ ...{preinitializeCanopy.ts => prepareTree.ts} | 38 ++-- 8 files changed, 508 insertions(+), 144 deletions(-) create mode 100644 account-compression/sdk/src/generated/instructions/initMerkleTreeWithRoot.ts rename account-compression/sdk/src/generated/instructions/{preinitializeCanopy.ts => prepareTree.ts} (65%) diff --git a/account-compression/programs/account-compression/src/canopy.rs b/account-compression/programs/account-compression/src/canopy.rs index 0e6cfc6b97d..78b9ad15195 100644 --- a/account-compression/programs/account-compression/src/canopy.rs +++ b/account-compression/programs/account-compression/src/canopy.rs @@ -134,7 +134,7 @@ pub fn fill_in_proof_from_canopy( /// Sets the leaf nodes of the canopy. The leaf nodes are the lowest level of the canopy, representing the leaves of the canopy-tree. /// The method will update the parent nodes of all the modified subtrees up to the uppermost level of the canopy. -/// The leaf nodes indexing is 0-based for the start_index. +/// The leaf nodes indexing for the start_index is 0-based without regards to the full tree indexes or the node indexes. The start_index is the index of the first leaf node to be updated. pub fn set_canopy_leaf_nodes( canopy_bytes: &mut [u8], max_depth: u32, @@ -144,8 +144,10 @@ pub fn set_canopy_leaf_nodes( check_canopy_bytes(canopy_bytes)?; let canopy = cast_slice_mut::(canopy_bytes); let path_len = get_cached_path_length(canopy, max_depth)?; - - let start_canopy_node = leaf_node_index_to_canopy_index(path_len, start_index); + if path_len == 0 { + return err!(AccountCompressionError::CanopyNotAllocated); + } + let start_canopy_node = leaf_node_index_to_canopy_index(path_len, start_index)?; let start_canopy_idx = start_canopy_node - 2; // set the "leaf" nodes of the canopy first - that's the lowest level of the canopy for (i, node) in nodes.iter().enumerate() { @@ -178,6 +180,70 @@ pub fn set_canopy_leaf_nodes( Ok(()) } +/// Checks the root of the canopy against the expected root. +pub fn check_canopy_root(canopy_bytes: &[u8], expected_root: &Node) -> Result<()> { + check_canopy_bytes(canopy_bytes)?; + let canopy = cast_slice::(canopy_bytes); + if canopy.len() < 1 { + return Ok(()); // Canopy is empty + } + let actual_root = hashv(&[&canopy[0], &canopy[1]]).to_bytes(); + if actual_root != *expected_root { + msg!( + "Canopy root mismatch. Expected: {:?}, Actual: {:?}", + expected_root, + actual_root + ); + err!(AccountCompressionError::CanopyRootMismatch) + } else { + Ok(()) + } +} + +/// Checks the canopy doesn't have any nodes to the right of the provided index in the full tree. +/// This is done by iterating through the canopy nodes to the right of the provided index and finding the top-most node that has the node as its left child. +/// The node should be empty. The iteration contains following the previous checked node on the same level until the last node on the level is reached. +pub fn check_canopy_no_nodes_to_right_of_index( + canopy_bytes: &[u8], + max_depth: u32, + index: u32, +) -> Result<()> { + check_canopy_bytes(canopy_bytes)?; + check_index(index, max_depth)?; + let canopy = cast_slice::(canopy_bytes); + let path_len = get_cached_path_length(canopy, max_depth)?; + + let mut node_idx = ((1 << max_depth) + index) >> (max_depth - path_len); + // no need to check the node_idx as it's the leaf continaing the index underneath it + while node_idx & node_idx + 1 != 0 { + // check the next node to the right + node_idx += 1; + // find the top-most node that has the node as its left-most child + node_idx >>= node_idx.trailing_zeros(); + + let shifted_index = node_idx as usize - 2; + if canopy[shifted_index] != EMPTY { + msg!("Canopy node at index {} is not empty", shifted_index); + return err!(AccountCompressionError::CanopyRightmostLeafMismatch); + } + } + Ok(()) +} + +#[inline(always)] +fn check_index(index: u32, at_depth: u32) -> Result<()> { + if at_depth > MAX_SUPPORTED_DEPTH as u32 { + return err!(AccountCompressionError::ConcurrentMerkleTreeConstantsError); + } + if at_depth == 0 { + return err!(AccountCompressionError::ConcurrentMerkleTreeConstantsError); + } + if index >= (1 << at_depth) { + return err!(AccountCompressionError::LeafIndexOutOfBounds); + } + Ok(()) +} + #[inline(always)] fn get_value_for_node( node_idx: usize, @@ -192,8 +258,9 @@ fn get_value_for_node( } #[inline(always)] -fn leaf_node_index_to_canopy_index(path_len: u32, index: u32) -> usize { - (1 << path_len) + index as usize +fn leaf_node_index_to_canopy_index(path_len: u32, index: u32) -> Result { + check_index(index, path_len)?; + Ok((1 << path_len) + index as usize) } #[cfg(test)] @@ -201,39 +268,48 @@ mod tests { use super::*; use spl_concurrent_merkle_tree::node::empty_node; - fn test_leaf_node_index_to_canopy_index_impl(path_len: u32, index: u32, expected: usize) { - assert_eq!(leaf_node_index_to_canopy_index(path_len, index), expected); + fn success_leaf_node_index_to_canopy_index(path_len: u32, index: u32, expected: usize) { + assert_eq!( + leaf_node_index_to_canopy_index(path_len, index).unwrap(), + expected + ); } - // todo: 0,0,0? + #[test] + fn test_zero_length_tree() { + assert_eq!( + leaf_node_index_to_canopy_index(0, 0).unwrap_err(), + AccountCompressionError::ConcurrentMerkleTreeConstantsError.into() + ); + } #[test] fn test_1_level_0_index() { - test_leaf_node_index_to_canopy_index_impl(1, 0, 2); + success_leaf_node_index_to_canopy_index(1, 0, 2); } #[test] fn test_1_level_1_index() { - test_leaf_node_index_to_canopy_index_impl(1, 1, 3); + success_leaf_node_index_to_canopy_index(1, 1, 3); } #[test] fn test_2_level_0_index() { - test_leaf_node_index_to_canopy_index_impl(2, 0, 4); + success_leaf_node_index_to_canopy_index(2, 0, 4); } #[test] fn test_2_level_3_index() { - test_leaf_node_index_to_canopy_index_impl(2, 3, 7); + success_leaf_node_index_to_canopy_index(2, 3, 7); } #[test] fn test_10_level_0_index() { - test_leaf_node_index_to_canopy_index_impl(10, 0, 1024); + success_leaf_node_index_to_canopy_index(10, 0, 1024); } #[test] fn test_10_level_1023_index() { - test_leaf_node_index_to_canopy_index_impl(10, 1023, 2047); + success_leaf_node_index_to_canopy_index(10, 1023, 2047); } #[test] @@ -357,4 +433,107 @@ mod tests { set_canopy_leaf_nodes(&mut canopy_bytes, 10, 0, &nodes).unwrap(); assert_eq!(canopy_bytes, vec![0_u8; 14 * size_of::()]); } + + #[test] + fn test_success_check_canopy_root() { + let mut canopy_bytes = vec![0_u8; 2 * size_of::()]; + let expected_root = hashv(&[&[1_u8; 32], &[2_u8; 32]]).to_bytes(); + let nodes = vec![[1_u8; 32], [2_u8; 32]]; + set_canopy_leaf_nodes(&mut canopy_bytes, 1, 0, &nodes).unwrap(); + check_canopy_root(&canopy_bytes, &expected_root).unwrap(); + } + + #[test] + fn test_failure_check_canopy_root() { + let mut canopy_bytes = vec![0_u8; 2 * size_of::()]; + let expected_root = hashv(&[&[1_u8; 32], &[2_u8; 32]]).to_bytes(); + let nodes = vec![[1_u8; 32], [2_u8; 32]]; + set_canopy_leaf_nodes(&mut canopy_bytes, 1, 0, &nodes).unwrap(); + let mut expected_root = expected_root; + expected_root[0] = 0; + assert_eq!( + check_canopy_root(&canopy_bytes, &expected_root).unwrap_err(), + AccountCompressionError::CanopyRootMismatch.into() + ); + } + + #[test] + fn test_success_check_canopy_no_nodes_to_right_of_index_empty_tree_first_index() { + let canopy_bytes = vec![0_u8; 6 * size_of::()]; + check_canopy_no_nodes_to_right_of_index(&canopy_bytes, 20, 0).unwrap(); + } + + #[test] + fn test_success_check_canopy_no_nodes_to_right_of_index_empty_tree_last_index() { + let canopy_bytes = vec![0_u8; 6 * size_of::()]; + check_canopy_no_nodes_to_right_of_index(&canopy_bytes, 20, (1 << 20) - 1).unwrap(); + } + + #[test] + fn test_success_check_canopy_no_nodes_to_right_of_index_empty_canopy_only_tree_first_index() { + let canopy_bytes = vec![0_u8; 6 * size_of::()]; + check_canopy_no_nodes_to_right_of_index(&canopy_bytes, 2, 0).unwrap(); + } + + #[test] + fn test_success_check_canopy_no_nodes_to_right_of_index_empty_canopy_only_tree_last_index() { + let canopy_bytes = vec![0_u8; 6 * size_of::()]; + check_canopy_no_nodes_to_right_of_index(&canopy_bytes, 2, (1 << 2) - 1).unwrap(); + } + + #[test] + fn test_failure_check_canopy_no_nodes_to_right_of_index_empty_tree_index_out_of_range() { + let canopy_bytes = vec![0_u8; 6 * size_of::()]; + assert_eq!( + check_canopy_no_nodes_to_right_of_index(&canopy_bytes, 2, 1 << 20).unwrap_err(), + AccountCompressionError::LeafIndexOutOfBounds.into() + ); + } + + #[test] + fn test_failure_check_canopy_no_nodes_to_right_of_index_full_tree_index_out_of_range() { + let canopy_bytes = vec![1_u8; 6 * size_of::()]; + assert_eq!( + check_canopy_no_nodes_to_right_of_index(&canopy_bytes, 2, 1 << 21).unwrap_err(), + AccountCompressionError::LeafIndexOutOfBounds.into() + ); + } + + #[test] + fn test_success_check_canopy_no_nodes_to_right_of_index_full_tree_last_index() { + let canopy_bytes = vec![1_u8; 6 * size_of::()]; + check_canopy_no_nodes_to_right_of_index(&canopy_bytes, 20, (1 << 20) - 1).unwrap(); + } + + #[test] + fn test_success_check_canopy_no_nodes_to_right_of_index_full_tree_first_child_of_last_canopy_node_leaf( + ) { + let canopy_bytes = vec![1_u8; 6 * size_of::()]; + check_canopy_no_nodes_to_right_of_index(&canopy_bytes, 20, 3 << (20 - 2)).unwrap(); + } + + #[test] + fn test_failure_check_canopy_no_nodes_to_right_of_index_full_tree_last_child_of_second_to_last_canopy_node_leaf( + ) { + let canopy_bytes = vec![1_u8; 6 * size_of::()]; + assert_eq!( + check_canopy_no_nodes_to_right_of_index(&canopy_bytes, 20, (3 << (20 - 2)) - 1) + .unwrap_err(), + AccountCompressionError::CanopyRightmostLeafMismatch.into() + ); + } + + #[test] + fn test_success_check_canopy_no_nodes_to_right_of_index_last_child_of_second_to_last_canopy_node_leaf( + ) { + let mut canopy_bytes = vec![1_u8; 6 * size_of::()]; + canopy_bytes[5 * size_of::()..].fill(0); + check_canopy_no_nodes_to_right_of_index(&canopy_bytes, 20, (3 << (20 - 2)) - 1).unwrap(); + } + + #[test] + fn test_succes_check_canopy_no_nodes_to_right_of_index_no_canopy() { + let canopy_bytes = vec![]; + check_canopy_no_nodes_to_right_of_index(&canopy_bytes, 20, 0).unwrap(); + } } diff --git a/account-compression/programs/account-compression/src/error.rs b/account-compression/programs/account-compression/src/error.rs index c9378c4dc84..60433bb65e3 100644 --- a/account-compression/programs/account-compression/src/error.rs +++ b/account-compression/programs/account-compression/src/error.rs @@ -55,6 +55,14 @@ pub enum AccountCompressionError { /// The tree was already initialized #[msg("Tree was already initialized")] TreeAlreadyInitialized, + + /// The canopy root doesn't match the root of the tree + #[msg("Canopy root does not match the root of the tree")] + CanopyRootMismatch, + + /// The canopy contains nodes to the right of the rightmost leaf of the tree + #[msg("Canopy contains nodes to the right of the rightmost leaf of the tree")] + CanopyRightmostLeafMismatch, } impl From<&ConcurrentMerkleTreeError> for AccountCompressionError { diff --git a/account-compression/programs/account-compression/src/lib.rs b/account-compression/programs/account-compression/src/lib.rs index 7cf151475fb..6a27718da9f 100644 --- a/account-compression/programs/account-compression/src/lib.rs +++ b/account-compression/programs/account-compression/src/lib.rs @@ -40,7 +40,10 @@ pub mod zero_copy; pub use crate::noop::{wrap_application_data_v1, Noop}; -use crate::canopy::{fill_in_proof_from_canopy, set_canopy_leaf_nodes, update_canopy}; +use crate::canopy::{ + check_canopy_bytes, check_canopy_no_nodes_to_right_of_index, check_canopy_root, + fill_in_proof_from_canopy, set_canopy_leaf_nodes, update_canopy, +}; use crate::concurrent_tree_wrapper::*; pub use crate::error::AccountCompressionError; pub use crate::events::{AccountCompressionEvent, ChangeLogEvent}; @@ -181,23 +184,23 @@ pub mod spl_account_compression { update_canopy(canopy_bytes, header.get_max_depth(), None) } + /// In order to initialize a tree with a root, we need to create the tree on-chain first with the proper authority. /// The tree might contain a canopy, which is a cache of the uppermost nodes. /// The canopy is used to decrease the size of the proof required to update the tree. + /// If the tree is expected to have a canopy, it needs to be prefilled with the necessary nodes. /// There are 2 ways to initialize a merkle tree: /// 1. Initialize an empty tree /// 2. Initialize a tree with a root and leaf - /// For the former case, the canopy will be empty which is expected for an empty tree. + /// For the former case, the canopy will be empty which is expected for an empty tree. The expected flow is `init_empty_merkle_tree`. /// For the latter case, the canopy should be filled with the necessary nodes to render the tree usable. - /// Thus we need to prefill the canopy with the necessary nodes. - /// - /// This instruction pre-initializes the canopy with the leftmost leaf nodes of the canopy. It initializes the tree header while leaving the tree itself uninitialized. - /// This is intended to be used in conjunction with the `init_merkle_tree_with_root` instruction that'll finalize the tree initialization. - pub fn preinitialize_canopy( + /// Thus we need to prefill the canopy with the necessary nodes. + /// The expected flow for a tree without canopy is `prepare_tree` -> `init_merkle_tree_with_root`. + /// The expected flow for a tree with canopy is `prepare_tree` -> `append_canopy_nodes` (multiple times until all of the canopy is filled) -> `init_merkle_tree_with_root`. + /// This instruction initializes the tree header while leaving the tree itself uninitialized. This allows distinguishing between an empty tree and a tree prepare to be initialized with a root. + pub fn prepare_tree( ctx: Context, max_depth: u32, max_buffer_size: u32, - start_index: u32, - canopy_nodes: Vec<[u8; 32]>, ) -> Result<()> { require_eq!( *ctx.accounts.merkle_tree.owner, @@ -216,24 +219,14 @@ pub mod spl_account_compression { &ctx.accounts.authority.key(), Clock::get()?.slot, ); + header.serialize(&mut header_bytes)?; let merkle_tree_size = merkle_tree_get_size(&header)?; let (_tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size); - // Ensure canopy is allocated before writing the header to the state - require_gt!( - canopy_bytes.len(), - 0, - AccountCompressionError::CanopyNotAllocated - ); - // todo: assert the start index is within bounds - header.serialize(&mut header_bytes)?; - set_canopy_leaf_nodes( - canopy_bytes, - header.get_max_depth(), - start_index, - &canopy_nodes, - ) + check_canopy_bytes(canopy_bytes) } + /// This instruction pre-initializes the canopy with the specified leaf nodes of the canopy. + /// This is intended to be used after `prepare_tree` and in conjunction with the `init_merkle_tree_with_root` instruction that'll finalize the tree initialization. pub fn append_canopy_nodes( ctx: Context, start_index: u32, @@ -251,7 +244,6 @@ pub mod spl_account_compression { let header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?; header.assert_valid_authority(&ctx.accounts.authority.key())?; - // todo: assert the start index is within bounds // assert the tree is not initialized yet, we don't want to overwrite the canopy of an initialized tree let merkle_tree_size = merkle_tree_get_size(&header)?; let (tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size); @@ -283,69 +275,68 @@ pub mod spl_account_compression { ) } - /// Note: - /// Supporting this instruction open a security vulnerability for indexers. - /// This instruction has been deemed unusable for publicly indexed compressed NFTs. - /// Indexing batched data in this way requires indexers to read in the `uri`s onto physical storage - /// and then into their database. This opens up a DOS attack vector, whereby this instruction is - /// repeatedly invoked, causing indexers to fail. - /// - /// Because this instruction was deemed insecure, this instruction has been removed - /// until secure usage is available on-chain. - // pub fn init_merkle_tree_with_root( - // ctx: Context, - // max_depth: u32, - // max_buffer_size: u32, - // root: [u8; 32], - // leaf: [u8; 32], - // index: u32, - // _changelog_db_uri: String, - // _metadata_db_uri: String, - // ) -> Result<()> { - // require_eq!( - // *ctx.accounts.merkle_tree.owner, - // crate::id(), - // AccountCompressionError::IncorrectAccountOwner - // ); - // let mut merkle_tree_bytes = ctx.accounts.merkle_tree.try_borrow_mut_data()?; - - // let (mut header_bytes, rest) = - // merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1); - - // let mut header = ConcurrentMerkleTreeHeader::try_from_slice(&header_bytes)?; - // header.initialize( - // max_depth, - // max_buffer_size, - // &ctx.accounts.authority.key(), - // Clock::get()?.slot, - // ); - // header.serialize(&mut header_bytes)?; - // let merkle_tree_size = merkle_tree_get_size(&header)?; - // let (tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size); - - // // Get rightmost proof from accounts - // let mut proof = vec![]; - // for node in ctx.remaining_accounts.iter() { - // proof.push(node.key().to_bytes()); - // } - // fill_in_proof_from_canopy(canopy_bytes, header.max_depth, index, &mut proof)?; - // assert_eq!(proof.len(), max_depth as usize); - - // let id = ctx.accounts.merkle_tree.key(); - // // A call is made to ConcurrentMerkleTree::initialize_with_root(root, leaf, proof, index) - // let change_log = merkle_tree_apply_fn!( - // header, - // id, - // tree_bytes, - // initialize_with_root, - // root, - // leaf, - // &proof, - // index - // )?; - // wrap_event(change_log.try_to_vec()?, &ctx.accounts.log_wrapper)?; - // update_canopy(canopy_bytes, header.max_depth, Some(change_log)) - // } + /// Initializes a tree with a root and a rightmost leaf. The rightmost leaf is used to verify the canopy if the tree has it. Before calling this instruction, the tree should be prepared with `prepare_tree` and the canopy should be filled with the necessary nodes with `append_canopy_nodes`. + /// This method should be used for rolluped creation of trees. The indexing of such rollups should be done off-chain. The programs calling this instruction should take care of ensuring the indexing is possible. For example, staking may be required to ensure the tree creator has some responsibility for what is being indexed. If indexing is not possible, there should be a mechanism to penalize the tree creator. + pub fn init_merkle_tree_with_root( + ctx: Context, + root: [u8; 32], + rightmost_leaf: [u8; 32], + rightmost_index: u32, + ) -> Result<()> { + require_eq!( + *ctx.accounts.merkle_tree.owner, + crate::id(), + AccountCompressionError::IncorrectAccountOwner + ); + let mut merkle_tree_bytes = ctx.accounts.merkle_tree.try_borrow_mut_data()?; + + let (header_bytes, rest) = + merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1); + // the header should already be initialized with prepare_tree + let header = ConcurrentMerkleTreeHeader::try_from_slice(&header_bytes)?; + header.assert_valid_authority(&ctx.accounts.authority.key())?; + let merkle_tree_size = merkle_tree_get_size(&header)?; + let (tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size); + // check the canopy root matches the tree root + check_canopy_root(canopy_bytes, &root)?; + // verify the canopy does not conain any nodes to the right of the rightmost leaf + check_canopy_no_nodes_to_right_of_index( + canopy_bytes, + header.get_max_depth(), + rightmost_index, + )?; + + // Get rightmost proof from accounts + let mut proof = vec![]; + for node in ctx.remaining_accounts.iter() { + proof.push(node.key().to_bytes()); + } + fill_in_proof_from_canopy( + canopy_bytes, + header.get_max_depth(), + rightmost_index, + &mut proof, + )?; + assert_eq!(proof.len(), header.get_max_depth() as usize); + + let id = ctx.accounts.merkle_tree.key(); + // A call is made to ConcurrentMerkleTree::initialize_with_root(root, rightmost_leaf, proof, rightmost_index) + let change_log = merkle_tree_apply_fn_mut!( + header, + id, + tree_bytes, + initialize_with_root, + root, + rightmost_leaf, + &proof, + rightmost_index + )?; + update_canopy(canopy_bytes, header.get_max_depth(), Some(&change_log))?; + wrap_event( + &AccountCompressionEvent::ChangeLog(*change_log), + &ctx.accounts.noop, + ) + } /// Executes an instruction that overwrites a leaf node. /// Composing programs should check that the data hashed into previous_leaf diff --git a/account-compression/sdk/idl/spl_account_compression.json b/account-compression/sdk/idl/spl_account_compression.json index 3243c8e57c5..eff7dc11893 100644 --- a/account-compression/sdk/idl/spl_account_compression.json +++ b/account-compression/sdk/idl/spl_account_compression.json @@ -52,19 +52,21 @@ ] }, { - "name": "preinitializeCanopy", + "name": "prepareTree", "docs": [ + "In order to initialize a tree with a root, we need to create the tree on-chain first with the proper authority.", "The tree might contain a canopy, which is a cache of the uppermost nodes.", "The canopy is used to decrease the size of the proof required to update the tree.", + "If the tree is expected to have a canopy, it needs to be prefilled with the necessary nodes.", "There are 2 ways to initialize a merkle tree:", "1. Initialize an empty tree", "2. Initialize a tree with a root and leaf", - "For the former case, the canopy will be empty which is expected for an empty tree.", + "For the former case, the canopy will be empty which is expected for an empty tree. The expected flow is `init_empty_merkle_tree`.", "For the latter case, the canopy should be filled with the necessary nodes to render the tree usable.", "Thus we need to prefill the canopy with the necessary nodes.", - "", - "This instruction pre-initializes the canopy with the leftmost leaf nodes of the canopy. It initializes the tree header while leaving the tree itself uninitialized.", - "This is intended to be used in conjunction with the `init_merkle_tree_with_root` instruction that'll finalize the tree initialization." + "The expected flow for a tree without canopy is `prepare_tree` -> `init_merkle_tree_with_root`.", + "The expected flow for a tree with canopy is `prepare_tree` -> `append_canopy_nodes` (multiple times until all of the canopy is filled) -> `init_merkle_tree_with_root`.", + "This instruction initializes the tree header while leaving the tree itself uninitialized. This allows distinguishing between an empty tree and a tree prepare to be initialized with a root." ], "accounts": [ { @@ -96,7 +98,38 @@ { "name": "maxBufferSize", "type": "u32" + } + ] + }, + { + "name": "appendCanopyNodes", + "docs": [ + "This instruction pre-initializes the canopy with the specified leaf nodes of the canopy.", + "This is intended to be used after `prepare_tree` and in conjunction with the `init_merkle_tree_with_root` instruction that'll finalize the tree initialization." + ], + "accounts": [ + { + "name": "merkleTree", + "isMut": true, + "isSigner": false }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "docs": [ + "Authority that controls write-access to the tree", + "Typically a program, e.g., the Bubblegum contract validates that leaves are valid NFTs." + ] + }, + { + "name": "noop", + "isMut": false, + "isSigner": false, + "docs": ["Program used to emit changelogs as cpi instruction data."] + } + ], + "args": [ { "name": "startIndex", "type": "u32" @@ -112,7 +145,11 @@ ] }, { - "name": "appendCanopyNodes", + "name": "initMerkleTreeWithRoot", + "docs": [ + "Initializes a tree with a root and a rightmost leaf. The rightmost leaf is used to verify the canopy if the tree has it. Before calling this instruction, the tree should be prepared with `prepare_tree` and the canopy should be filled with the necessary nodes with `append_canopy_nodes`.", + "This method should be used for rolluped creation of trees. The indexing of such rollups should be done off-chain. The programs calling this instruction should take care of ensuring the indexing is possible. For example, staking may be required to ensure the tree creator has some responsibility for what is being indexed. If indexing is not possible, there should be a mechanism to penalize the tree creator." + ], "accounts": [ { "name": "merkleTree", @@ -137,31 +174,26 @@ ], "args": [ { - "name": "startIndex", - "type": "u32" + "name": "root", + "type": { + "array": ["u8", 32] + } }, { - "name": "canopyNodes", + "name": "rightmostLeaf", "type": { - "vec": { - "array": ["u8", 32] - } + "array": ["u8", 32] } + }, + { + "name": "rightmostIndex", + "type": "u32" } ] }, { "name": "replaceLeaf", "docs": [ - "Note:", - "Supporting this instruction open a security vulnerability for indexers.", - "This instruction has been deemed unusable for publicly indexed compressed NFTs.", - "Indexing batched data in this way requires indexers to read in the `uri`s onto physical storage", - "and then into their database. This opens up a DOS attack vector, whereby this instruction is", - "repeatedly invoked, causing indexers to fail.", - "", - "Because this instruction was deemed insecure, this instruction has been removed", - "until secure usage is available on-chain.", "Executes an instruction that overwrites a leaf node.", "Composing programs should check that the data hashed into previous_leaf", "matches the authority information necessary to execute this instruction." @@ -679,6 +711,16 @@ "code": 6010, "name": "TreeAlreadyInitialized", "msg": "Tree was already initialized" + }, + { + "code": 6011, + "name": "CanopyRootMismatch", + "msg": "Canopy root does not match the root of the tree" + }, + { + "code": 6012, + "name": "CanopyRightmostLeafMismatch", + "msg": "Canopy contains nodes to the right of the rightmost leaf of the tree" } ], "metadata": { diff --git a/account-compression/sdk/src/generated/errors/index.ts b/account-compression/sdk/src/generated/errors/index.ts index 0bf612e38b6..02d025bd762 100644 --- a/account-compression/sdk/src/generated/errors/index.ts +++ b/account-compression/sdk/src/generated/errors/index.ts @@ -234,6 +234,46 @@ export class TreeAlreadyInitializedError extends Error { createErrorFromCodeLookup.set(0x177a, () => new TreeAlreadyInitializedError()); createErrorFromNameLookup.set('TreeAlreadyInitialized', () => new TreeAlreadyInitializedError()); +/** + * CanopyRootMismatch: 'Canopy root does not match the root of the tree' + * + * @category Errors + * @category generated + */ +export class CanopyRootMismatchError extends Error { + readonly code: number = 0x177b; + readonly name: string = 'CanopyRootMismatch'; + constructor() { + super('Canopy root does not match the root of the tree'); + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(this, CanopyRootMismatchError); + } + } +} + +createErrorFromCodeLookup.set(0x177b, () => new CanopyRootMismatchError()); +createErrorFromNameLookup.set('CanopyRootMismatch', () => new CanopyRootMismatchError()); + +/** + * CanopyRightmostLeafMismatch: 'Canopy contains nodes to the right of the rightmost leaf of the tree' + * + * @category Errors + * @category generated + */ +export class CanopyRightmostLeafMismatchError extends Error { + readonly code: number = 0x177c; + readonly name: string = 'CanopyRightmostLeafMismatch'; + constructor() { + super('Canopy contains nodes to the right of the rightmost leaf of the tree'); + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(this, CanopyRightmostLeafMismatchError); + } + } +} + +createErrorFromCodeLookup.set(0x177c, () => new CanopyRightmostLeafMismatchError()); +createErrorFromNameLookup.set('CanopyRightmostLeafMismatch', () => new CanopyRightmostLeafMismatchError()); + /** * Attempts to resolve a custom program error from the provided error code. * @category Errors diff --git a/account-compression/sdk/src/generated/instructions/index.ts b/account-compression/sdk/src/generated/instructions/index.ts index b81af6ee324..8da0721b389 100644 --- a/account-compression/sdk/src/generated/instructions/index.ts +++ b/account-compression/sdk/src/generated/instructions/index.ts @@ -2,8 +2,9 @@ export * from './append'; export * from './appendCanopyNodes'; export * from './closeEmptyTree'; export * from './initEmptyMerkleTree'; +export * from './initMerkleTreeWithRoot'; export * from './insertOrAppend'; -export * from './preinitializeCanopy'; +export * from './prepareTree'; export * from './replaceLeaf'; export * from './transferAuthority'; export * from './verifyLeaf'; diff --git a/account-compression/sdk/src/generated/instructions/initMerkleTreeWithRoot.ts b/account-compression/sdk/src/generated/instructions/initMerkleTreeWithRoot.ts new file mode 100644 index 00000000000..6cd76e72ab2 --- /dev/null +++ b/account-compression/sdk/src/generated/instructions/initMerkleTreeWithRoot.ts @@ -0,0 +1,107 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet'; +import * as web3 from '@solana/web3.js'; + +/** + * @category Instructions + * @category InitMerkleTreeWithRoot + * @category generated + */ +export type InitMerkleTreeWithRootInstructionArgs = { + rightmostIndex: number; + rightmostLeaf: number[] /* size: 32 */; + root: number[] /* size: 32 */; +}; +/** + * @category Instructions + * @category InitMerkleTreeWithRoot + * @category generated + */ +export const initMerkleTreeWithRootStruct = new beet.BeetArgsStruct< + InitMerkleTreeWithRootInstructionArgs & { + instructionDiscriminator: number[] /* size: 8 */; + } +>( + [ + ['instructionDiscriminator', beet.uniformFixedSizeArray(beet.u8, 8)], + ['root', beet.uniformFixedSizeArray(beet.u8, 32)], + ['rightmostLeaf', beet.uniformFixedSizeArray(beet.u8, 32)], + ['rightmostIndex', beet.u32], + ], + 'InitMerkleTreeWithRootInstructionArgs', +); +/** + * Accounts required by the _initMerkleTreeWithRoot_ instruction + * + * @property [_writable_] merkleTree + * @property [**signer**] authority + * @property [] noop + * @category Instructions + * @category InitMerkleTreeWithRoot + * @category generated + */ +export type InitMerkleTreeWithRootInstructionAccounts = { + anchorRemainingAccounts?: web3.AccountMeta[]; + authority: web3.PublicKey; + merkleTree: web3.PublicKey; + noop: web3.PublicKey; +}; + +export const initMerkleTreeWithRootInstructionDiscriminator = [67, 221, 160, 236, 108, 179, 112, 198]; + +/** + * Creates a _InitMerkleTreeWithRoot_ instruction. + * + * @param accounts that will be accessed while the instruction is processed + * @param args to provide as instruction data to the program + * + * @category Instructions + * @category InitMerkleTreeWithRoot + * @category generated + */ +export function createInitMerkleTreeWithRootInstruction( + accounts: InitMerkleTreeWithRootInstructionAccounts, + args: InitMerkleTreeWithRootInstructionArgs, + programId = new web3.PublicKey('cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK'), +) { + const [data] = initMerkleTreeWithRootStruct.serialize({ + instructionDiscriminator: initMerkleTreeWithRootInstructionDiscriminator, + ...args, + }); + const keys: web3.AccountMeta[] = [ + { + isSigner: false, + isWritable: true, + pubkey: accounts.merkleTree, + }, + { + isSigner: true, + isWritable: false, + pubkey: accounts.authority, + }, + { + isSigner: false, + isWritable: false, + pubkey: accounts.noop, + }, + ]; + + if (accounts.anchorRemainingAccounts != null) { + for (const acc of accounts.anchorRemainingAccounts) { + keys.push(acc); + } + } + + const ix = new web3.TransactionInstruction({ + data, + keys, + programId, + }); + return ix; +} diff --git a/account-compression/sdk/src/generated/instructions/preinitializeCanopy.ts b/account-compression/sdk/src/generated/instructions/prepareTree.ts similarity index 65% rename from account-compression/sdk/src/generated/instructions/preinitializeCanopy.ts rename to account-compression/sdk/src/generated/instructions/prepareTree.ts index 513a2c27f02..7e832a8ee15 100644 --- a/account-compression/sdk/src/generated/instructions/preinitializeCanopy.ts +++ b/account-compression/sdk/src/generated/instructions/prepareTree.ts @@ -10,22 +10,20 @@ import * as web3 from '@solana/web3.js'; /** * @category Instructions - * @category PreinitializeCanopy + * @category PrepareTree * @category generated */ -export type PreinitializeCanopyInstructionArgs = { - canopyNodes: number[] /* size: 32 */[]; +export type PrepareTreeInstructionArgs = { maxBufferSize: number; maxDepth: number; - startIndex: number; }; /** * @category Instructions - * @category PreinitializeCanopy + * @category PrepareTree * @category generated */ -export const preinitializeCanopyStruct = new beet.FixableBeetArgsStruct< - PreinitializeCanopyInstructionArgs & { +export const prepareTreeStruct = new beet.BeetArgsStruct< + PrepareTreeInstructionArgs & { instructionDiscriminator: number[] /* size: 8 */; } >( @@ -33,47 +31,45 @@ export const preinitializeCanopyStruct = new beet.FixableBeetArgsStruct< ['instructionDiscriminator', beet.uniformFixedSizeArray(beet.u8, 8)], ['maxDepth', beet.u32], ['maxBufferSize', beet.u32], - ['startIndex', beet.u32], - ['canopyNodes', beet.array(beet.uniformFixedSizeArray(beet.u8, 32))], ], - 'PreinitializeCanopyInstructionArgs', + 'PrepareTreeInstructionArgs', ); /** - * Accounts required by the _preinitializeCanopy_ instruction + * Accounts required by the _prepareTree_ instruction * * @property [_writable_] merkleTree * @property [**signer**] authority * @property [] noop * @category Instructions - * @category PreinitializeCanopy + * @category PrepareTree * @category generated */ -export type PreinitializeCanopyInstructionAccounts = { +export type PrepareTreeInstructionAccounts = { anchorRemainingAccounts?: web3.AccountMeta[]; authority: web3.PublicKey; merkleTree: web3.PublicKey; noop: web3.PublicKey; }; -export const preinitializeCanopyInstructionDiscriminator = [233, 92, 157, 34, 63, 20, 168, 13]; +export const prepareTreeInstructionDiscriminator = [41, 56, 189, 77, 58, 12, 142, 71]; /** - * Creates a _PreinitializeCanopy_ instruction. + * Creates a _PrepareTree_ instruction. * * @param accounts that will be accessed while the instruction is processed * @param args to provide as instruction data to the program * * @category Instructions - * @category PreinitializeCanopy + * @category PrepareTree * @category generated */ -export function createPreinitializeCanopyInstruction( - accounts: PreinitializeCanopyInstructionAccounts, - args: PreinitializeCanopyInstructionArgs, +export function createPrepareTreeInstruction( + accounts: PrepareTreeInstructionAccounts, + args: PrepareTreeInstructionArgs, programId = new web3.PublicKey('cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK'), ) { - const [data] = preinitializeCanopyStruct.serialize({ - instructionDiscriminator: preinitializeCanopyInstructionDiscriminator, + const [data] = prepareTreeStruct.serialize({ + instructionDiscriminator: prepareTreeInstructionDiscriminator, ...args, }); const keys: web3.AccountMeta[] = [ From 4274c9b1f03e53ca38e6fb2929a8a8c2c838a16c Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Fri, 24 May 2024 10:57:27 +0100 Subject: [PATCH 04/39] rename init_merkle_tree_with_root into finalize_merkle_tree_with_root --- .../programs/account-compression/src/lib.rs | 4 +-- .../sdk/idl/spl_account_compression.json | 4 +-- ...hRoot.ts => finalizeMerkleTreeWithRoot.ts} | 34 +++++++++---------- .../sdk/src/generated/instructions/index.ts | 2 +- 4 files changed, 22 insertions(+), 22 deletions(-) rename account-compression/sdk/src/generated/instructions/{initMerkleTreeWithRoot.ts => finalizeMerkleTreeWithRoot.ts} (68%) diff --git a/account-compression/programs/account-compression/src/lib.rs b/account-compression/programs/account-compression/src/lib.rs index 6a27718da9f..006f6a88f2a 100644 --- a/account-compression/programs/account-compression/src/lib.rs +++ b/account-compression/programs/account-compression/src/lib.rs @@ -275,9 +275,9 @@ pub mod spl_account_compression { ) } - /// Initializes a tree with a root and a rightmost leaf. The rightmost leaf is used to verify the canopy if the tree has it. Before calling this instruction, the tree should be prepared with `prepare_tree` and the canopy should be filled with the necessary nodes with `append_canopy_nodes`. + /// Initializes a prepared tree with a root and a rightmost leaf. The rightmost leaf is used to verify the canopy if the tree has it. Before calling this instruction, the tree should be prepared with `prepare_tree` and the canopy should be filled with the necessary nodes with `append_canopy_nodes` (if the canopy is used). /// This method should be used for rolluped creation of trees. The indexing of such rollups should be done off-chain. The programs calling this instruction should take care of ensuring the indexing is possible. For example, staking may be required to ensure the tree creator has some responsibility for what is being indexed. If indexing is not possible, there should be a mechanism to penalize the tree creator. - pub fn init_merkle_tree_with_root( + pub fn finalize_merkle_tree_with_root( ctx: Context, root: [u8; 32], rightmost_leaf: [u8; 32], diff --git a/account-compression/sdk/idl/spl_account_compression.json b/account-compression/sdk/idl/spl_account_compression.json index eff7dc11893..f79e7a85059 100644 --- a/account-compression/sdk/idl/spl_account_compression.json +++ b/account-compression/sdk/idl/spl_account_compression.json @@ -145,9 +145,9 @@ ] }, { - "name": "initMerkleTreeWithRoot", + "name": "finalizeMerkleTreeWithRoot", "docs": [ - "Initializes a tree with a root and a rightmost leaf. The rightmost leaf is used to verify the canopy if the tree has it. Before calling this instruction, the tree should be prepared with `prepare_tree` and the canopy should be filled with the necessary nodes with `append_canopy_nodes`.", + "Initializes a prepared tree with a root and a rightmost leaf. The rightmost leaf is used to verify the canopy if the tree has it. Before calling this instruction, the tree should be prepared with `prepare_tree` and the canopy should be filled with the necessary nodes with `append_canopy_nodes` (if the canopy is used).", "This method should be used for rolluped creation of trees. The indexing of such rollups should be done off-chain. The programs calling this instruction should take care of ensuring the indexing is possible. For example, staking may be required to ensure the tree creator has some responsibility for what is being indexed. If indexing is not possible, there should be a mechanism to penalize the tree creator." ], "accounts": [ diff --git a/account-compression/sdk/src/generated/instructions/initMerkleTreeWithRoot.ts b/account-compression/sdk/src/generated/instructions/finalizeMerkleTreeWithRoot.ts similarity index 68% rename from account-compression/sdk/src/generated/instructions/initMerkleTreeWithRoot.ts rename to account-compression/sdk/src/generated/instructions/finalizeMerkleTreeWithRoot.ts index 6cd76e72ab2..823c5e94d1a 100644 --- a/account-compression/sdk/src/generated/instructions/initMerkleTreeWithRoot.ts +++ b/account-compression/sdk/src/generated/instructions/finalizeMerkleTreeWithRoot.ts @@ -10,21 +10,21 @@ import * as web3 from '@solana/web3.js'; /** * @category Instructions - * @category InitMerkleTreeWithRoot + * @category FinalizeMerkleTreeWithRoot * @category generated */ -export type InitMerkleTreeWithRootInstructionArgs = { +export type FinalizeMerkleTreeWithRootInstructionArgs = { rightmostIndex: number; rightmostLeaf: number[] /* size: 32 */; root: number[] /* size: 32 */; }; /** * @category Instructions - * @category InitMerkleTreeWithRoot + * @category FinalizeMerkleTreeWithRoot * @category generated */ -export const initMerkleTreeWithRootStruct = new beet.BeetArgsStruct< - InitMerkleTreeWithRootInstructionArgs & { +export const finalizeMerkleTreeWithRootStruct = new beet.BeetArgsStruct< + FinalizeMerkleTreeWithRootInstructionArgs & { instructionDiscriminator: number[] /* size: 8 */; } >( @@ -34,44 +34,44 @@ export const initMerkleTreeWithRootStruct = new beet.BeetArgsStruct< ['rightmostLeaf', beet.uniformFixedSizeArray(beet.u8, 32)], ['rightmostIndex', beet.u32], ], - 'InitMerkleTreeWithRootInstructionArgs', + 'FinalizeMerkleTreeWithRootInstructionArgs', ); /** - * Accounts required by the _initMerkleTreeWithRoot_ instruction + * Accounts required by the _finalizeMerkleTreeWithRoot_ instruction * * @property [_writable_] merkleTree * @property [**signer**] authority * @property [] noop * @category Instructions - * @category InitMerkleTreeWithRoot + * @category FinalizeMerkleTreeWithRoot * @category generated */ -export type InitMerkleTreeWithRootInstructionAccounts = { +export type FinalizeMerkleTreeWithRootInstructionAccounts = { anchorRemainingAccounts?: web3.AccountMeta[]; authority: web3.PublicKey; merkleTree: web3.PublicKey; noop: web3.PublicKey; }; -export const initMerkleTreeWithRootInstructionDiscriminator = [67, 221, 160, 236, 108, 179, 112, 198]; +export const finalizeMerkleTreeWithRootInstructionDiscriminator = [112, 137, 139, 87, 67, 99, 164, 82]; /** - * Creates a _InitMerkleTreeWithRoot_ instruction. + * Creates a _FinalizeMerkleTreeWithRoot_ instruction. * * @param accounts that will be accessed while the instruction is processed * @param args to provide as instruction data to the program * * @category Instructions - * @category InitMerkleTreeWithRoot + * @category FinalizeMerkleTreeWithRoot * @category generated */ -export function createInitMerkleTreeWithRootInstruction( - accounts: InitMerkleTreeWithRootInstructionAccounts, - args: InitMerkleTreeWithRootInstructionArgs, +export function createFinalizeMerkleTreeWithRootInstruction( + accounts: FinalizeMerkleTreeWithRootInstructionAccounts, + args: FinalizeMerkleTreeWithRootInstructionArgs, programId = new web3.PublicKey('cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK'), ) { - const [data] = initMerkleTreeWithRootStruct.serialize({ - instructionDiscriminator: initMerkleTreeWithRootInstructionDiscriminator, + const [data] = finalizeMerkleTreeWithRootStruct.serialize({ + instructionDiscriminator: finalizeMerkleTreeWithRootInstructionDiscriminator, ...args, }); const keys: web3.AccountMeta[] = [ diff --git a/account-compression/sdk/src/generated/instructions/index.ts b/account-compression/sdk/src/generated/instructions/index.ts index 8da0721b389..e291db12cf6 100644 --- a/account-compression/sdk/src/generated/instructions/index.ts +++ b/account-compression/sdk/src/generated/instructions/index.ts @@ -1,8 +1,8 @@ export * from './append'; export * from './appendCanopyNodes'; export * from './closeEmptyTree'; +export * from './finalizeMerkleTreeWithRoot'; export * from './initEmptyMerkleTree'; -export * from './initMerkleTreeWithRoot'; export * from './insertOrAppend'; export * from './prepareTree'; export * from './replaceLeaf'; From a4f8fcba5fbdc4c51a75c48d367272fb480cfe8b Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Fri, 24 May 2024 16:39:08 +0100 Subject: [PATCH 05/39] using the Modify context for append_canopy_nodes and finalize_merkle_tree_with_root as the merkle tree account will not be zeroed at that point --- account-compression/programs/account-compression/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/account-compression/programs/account-compression/src/lib.rs b/account-compression/programs/account-compression/src/lib.rs index 006f6a88f2a..c1a3c2f46be 100644 --- a/account-compression/programs/account-compression/src/lib.rs +++ b/account-compression/programs/account-compression/src/lib.rs @@ -228,7 +228,7 @@ pub mod spl_account_compression { /// This instruction pre-initializes the canopy with the specified leaf nodes of the canopy. /// This is intended to be used after `prepare_tree` and in conjunction with the `init_merkle_tree_with_root` instruction that'll finalize the tree initialization. pub fn append_canopy_nodes( - ctx: Context, + ctx: Context, start_index: u32, canopy_nodes: Vec<[u8; 32]>, ) -> Result<()> { @@ -278,7 +278,7 @@ pub mod spl_account_compression { /// Initializes a prepared tree with a root and a rightmost leaf. The rightmost leaf is used to verify the canopy if the tree has it. Before calling this instruction, the tree should be prepared with `prepare_tree` and the canopy should be filled with the necessary nodes with `append_canopy_nodes` (if the canopy is used). /// This method should be used for rolluped creation of trees. The indexing of such rollups should be done off-chain. The programs calling this instruction should take care of ensuring the indexing is possible. For example, staking may be required to ensure the tree creator has some responsibility for what is being indexed. If indexing is not possible, there should be a mechanism to penalize the tree creator. pub fn finalize_merkle_tree_with_root( - ctx: Context, + ctx: Context, root: [u8; 32], rightmost_leaf: [u8; 32], rightmost_index: u32, From 7193f09b6db011902a1af2c195d8845488ba3d6d Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Mon, 27 May 2024 10:31:56 +0100 Subject: [PATCH 06/39] tests for preparing and finalizing an offline built tree --- .../sdk/src/instructions/index.ts | 91 ++++++ .../sdk/tests/accountCompression.test.ts | 284 +++++++++++++++++- account-compression/sdk/tests/utils.ts | 38 ++- 3 files changed, 411 insertions(+), 2 deletions(-) diff --git a/account-compression/sdk/src/instructions/index.ts b/account-compression/sdk/src/instructions/index.ts index 150acfea3b8..cd7ff4351dd 100644 --- a/account-compression/sdk/src/instructions/index.ts +++ b/account-compression/sdk/src/instructions/index.ts @@ -3,9 +3,12 @@ import { Connection, PublicKey, SystemProgram, TransactionInstruction } from '@s import { getConcurrentMerkleTreeAccountSize } from '../accounts'; import { SPL_NOOP_PROGRAM_ID, ValidDepthSizePair } from '../constants'; import { + createAppendCanopyNodesInstruction, createAppendInstruction, createCloseEmptyTreeInstruction, + createFinalizeMerkleTreeWithRootInstruction, createInitEmptyMerkleTreeInstruction, + createPrepareTreeInstruction, createReplaceLeafInstruction, createTransferAuthorityInstruction, createVerifyLeafInstruction, @@ -53,6 +56,94 @@ export function createInitEmptyMerkleTreeIx( ); } +/** + * Helper function for {@link createPrepareTreeInstruction} + * @param merkleTree + * @param authority + * @param depthSizePair + * @returns + */ +export function prepareTreeIx( + merkleTree: PublicKey, + authority: PublicKey, + depthSizePair: ValidDepthSizePair, +): TransactionInstruction { + return createPrepareTreeInstruction( + { + authority: authority, + merkleTree, + noop: SPL_NOOP_PROGRAM_ID, + }, + depthSizePair, + ); +} + +/** + * Helper function for {@link createAppendCanopyNodesInstruction} + * @param merkleTree + * @param authority + * @param canopyNodes + * @param startIndex + * @returns + */ +export function createAppendCanopyNodesIx( + merkleTree: PublicKey, + authority: PublicKey, + canopyNodes: ArrayLike[] | Buffer[], + startIndex: number, +): TransactionInstruction { + return createAppendCanopyNodesInstruction( + { + authority, + merkleTree, + noop: SPL_NOOP_PROGRAM_ID, + }, + { + canopyNodes: canopyNodes.map(node => Array.from(node)), + startIndex, + }, + ); +} + +/** + * Helper function for {@link createFinalizeMerkleTreeWithRootInstruction} + * @param merkleTree + * @param authority + * @param root + * @param rightmostLeaf + * @param rightmostIndex + * @param proof + * @returns + */ +export function createFinalizeMerkleTreeWithRootIx( + merkleTree: PublicKey, + authority: PublicKey, + root: ArrayLike | Buffer, + rightmostLeaf: ArrayLike | Buffer, + rightmostIndex: number, + proof: Buffer[], +): TransactionInstruction { + return createFinalizeMerkleTreeWithRootInstruction( + { + anchorRemainingAccounts: proof.map(node => { + return { + isSigner: false, + isWritable: false, + pubkey: new PublicKey(node), + }; + }), + authority, + merkleTree, + noop: SPL_NOOP_PROGRAM_ID, + }, + { + rightmostIndex, + rightmostLeaf: Array.from(rightmostLeaf), + root: Array.from(root), + }, + ); +} + /** * Helper function for {@link createReplaceLeafInstruction} * @param merkleTree diff --git a/account-compression/sdk/tests/accountCompression.test.ts b/account-compression/sdk/tests/accountCompression.test.ts index d7a3338b7eb..2d4dc6ce873 100644 --- a/account-compression/sdk/tests/accountCompression.test.ts +++ b/account-compression/sdk/tests/accountCompression.test.ts @@ -8,15 +8,18 @@ import * as crypto from 'crypto'; import { ConcurrentMerkleTreeAccount, + createAppendCanopyNodesIx, createAppendIx, createCloseEmptyTreeInstruction, + createFinalizeMerkleTreeWithRootIx, + createInitEmptyMerkleTreeIx, createReplaceIx, createTransferAuthorityIx, createVerifyLeafIx, ValidDepthSizePair, } from '../src'; import { hash, MerkleTree } from '../src/merkle-tree'; -import { createTreeOnChain, execute } from './utils'; +import { createTreeOnChain, execute, prepareTree } from './utils'; // eslint-disable-next-line no-empty describe('Account Compression', () => { @@ -54,6 +57,285 @@ describe('Account Compression', () => { ); }); + describe('Having prepared a tree without canopy', () => { + const depth = 3; + const size = 8; + const canopyDepth = 0; + const leaves = [ + crypto.randomBytes(32), + crypto.randomBytes(32), + crypto.randomBytes(32), + crypto.randomBytes(32), + crypto.randomBytes(32), + crypto.randomBytes(32), + crypto.randomBytes(32), + crypto.randomBytes(32), + ]; + let anotherKeyPair: Keypair; + let another: PublicKey; + + beforeEach(async () => { + const cmtKeypair = await prepareTree({ + canopyDepth, + depthSizePair: { + maxBufferSize: size, + maxDepth: depth, + }, + payer: payerKeypair, + provider, + }); + cmt = cmtKeypair.publicKey; + anotherKeyPair = Keypair.generate(); + another = anotherKeyPair.publicKey; + await provider.connection.confirmTransaction( + await provider.connection.requestAirdrop(another, 1e10), + 'confirmed' + ); + }); + it('Should be able to finalize the tree', async () => { + const merkleTreeRaw = new MerkleTree(leaves); + const root = merkleTreeRaw.root; + const leaf = leaves[leaves.length - 1]; + + const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); + + await execute(provider, [finalize], [payerKeypair]); + + const splCMT = await ConcurrentMerkleTreeAccount.fromAccountAddress(connection, cmt); + assert(splCMT.getMaxBufferSize() === size, 'Buffer size does not match'); + assert(splCMT.getCanopyDepth() === canopyDepth, 'Canopy depth does not match: expected ' + canopyDepth + ' but got ' + splCMT.getCanopyDepth()); + assert(splCMT.getBufferSize() == 1, 'Buffer size does not match'); + }); + it('Should fail to append canopy node for a tree without canopy', async () => { + const appendIx = createAppendCanopyNodesIx(cmt, payer, [crypto.randomBytes(32)], 0); + try { + await execute(provider, [appendIx], [payerKeypair]); + assert(false, 'Canopy appending should have failed to execute for a tree without canopy'); + } catch { } + }); + it('Should fail to finalize the tree with another payer authority', async () => { + const merkleTreeRaw = new MerkleTree(leaves); + const root = merkleTreeRaw.root; + const leaf = leaves[leaves.length - 1]; + + const finalize = createFinalizeMerkleTreeWithRootIx(cmt, another, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); + + try { + await execute(provider, [finalize], [anotherKeyPair]); + assert(false, 'Finalizing with another payer should have failed'); + } catch { } + }); + it('Should fail to finalize the tree with a wrong proof', async () => { + const merkleTreeRaw = new MerkleTree(leaves); + const root = merkleTreeRaw.root; + const leaf = leaves[leaves.length - 1]; + // Replace valid proof with random bytes so it is wrong + const proof = merkleTreeRaw.getProof(leaves.length - 1); + proof.proof = proof.proof.map(_ => { + return crypto.randomBytes(32); + }); + + const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, proof.proof); + + try { + await execute(provider, [finalize], [payerKeypair]); + assert(false, 'Finalizing with a wrong proof should have failed'); + } catch { } + }); + it('Should fail to double finalize the tree', async () => { + const merkleTreeRaw = new MerkleTree(leaves); + const root = merkleTreeRaw.root; + const leaf = leaves[leaves.length - 1]; + + const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); + + await execute(provider, [finalize], [payerKeypair]); + + try { + await execute(provider, [finalize], [payerKeypair]); + assert(false, 'Double finalizing should have failed'); + } catch { } + }); + } + + ); + describe('Having prepared a tree with canopy', () => { + const depth = 3; + const size = 8; + const canopyDepth = 2; + const leaves = [ + crypto.randomBytes(32), + crypto.randomBytes(32), + crypto.randomBytes(32), + crypto.randomBytes(32), + crypto.randomBytes(32), + crypto.randomBytes(32), + crypto.randomBytes(32), + crypto.randomBytes(32), + ]; + let anotherKeyPair: Keypair; + let another: PublicKey; + beforeEach(async () => { + const cmtKeypair = await prepareTree({ + canopyDepth, + depthSizePair: { + maxBufferSize: size, + maxDepth: depth, + }, + payer: payerKeypair, + provider, + }); + cmt = cmtKeypair.publicKey; + anotherKeyPair = Keypair.generate(); + another = anotherKeyPair.publicKey; + await provider.connection.confirmTransaction( + await provider.connection.requestAirdrop(another, 1e10), + 'confirmed' + ); + }); + it('Should be able to append a single canopy node', async () => { + const appendIx = createAppendCanopyNodesIx(cmt, payer, [crypto.randomBytes(32)], 0); + await execute(provider, [appendIx], [payerKeypair]); + }); + it('Should be able to append a single canopy node at the index more then 0', async () => { + const appendIx = createAppendCanopyNodesIx(cmt, payer, [crypto.randomBytes(32)], 1); + await execute(provider, [appendIx], [payerKeypair]); + }); + it('Should be able to append several canopy nodes at the start of the node leaves', async () => { + const appendIx = createAppendCanopyNodesIx(cmt, payer, [crypto.randomBytes(32), crypto.randomBytes(32)], 0); + await execute(provider, [appendIx], [payerKeypair]); + }); + it('Should fail to append canopy node with another payer authority', async () => { + const appendIx = createAppendCanopyNodesIx(cmt, another, [crypto.randomBytes(32)], 0); + try { + await execute(provider, [appendIx], [anotherKeyPair]); + assert(false, 'Appending with another payer should have failed'); + } catch { } + }); + it('Should fail to append canopy nodes over the limit', async () => { + const appendIx = createAppendCanopyNodesIx(cmt, payer, Array.from({ length: 3 }, () => crypto.randomBytes(32)), 0); + try { + await execute(provider, [appendIx], [payerKeypair]); + assert(false, 'Appending over the limit should have failed'); + } catch { } + }); + it('Should fail to append canopy nodes over the limit starting from the last index', async () => { + const appendIx = createAppendCanopyNodesIx(cmt, payer, Array.from({ length: 2 }, () => crypto.randomBytes(32)), 1); + try { + await execute(provider, [appendIx], [payerKeypair]); + assert(false, 'Appending over the limit should have failed'); + } catch { } + }); + it('Should fail to append 0 canopy nodes', async () => { + const appendIx = createAppendCanopyNodesIx(cmt, payer, [], 0); + try { + await execute(provider, [appendIx], [payerKeypair]); + assert(false, 'Appending 0 nodes should have failed'); + } catch { } + }); + it('Should fail to finalize the tree without canopy', async () => { + const merkleTreeRaw = new MerkleTree(leaves); + const root = merkleTreeRaw.root; + const leaf = leaves[leaves.length - 1]; + + const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); + + try { + await execute(provider, [finalize], [payerKeypair]); + assert(false, 'Finalizing without canopy should have failed'); + } catch { } + }); + it('Should fail to finalize the tree with an incomplete canopy', async () => { + const merkleTreeRaw = new MerkleTree(leaves); + const root = merkleTreeRaw.root; + const leaf = leaves[leaves.length - 1]; + + const appendIx = createAppendCanopyNodesIx(cmt, payer, [merkleTreeRaw.leaves[0].parent?.node!], 0); + await execute(provider, [appendIx], [payerKeypair]); + const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); + + try { + await execute(provider, [finalize], [payerKeypair]); + assert(false, 'Finalization for an incomplete canopy should have failed'); + } catch { } + }); + it('Should finalize the tree with a complete canopy', async () => { + const merkleTreeRaw = new MerkleTree(leaves); + const root = merkleTreeRaw.root; + const leaf = leaves[leaves.length - 1]; + + // take every second leaf and append it's parent node to the canopy + const appendIx = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.filter((_, i) => i % 2 === 0).map(leaf => leaf.parent?.node!), 0); + await execute(provider, [appendIx], [payerKeypair]); + const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); + await execute(provider, [finalize], [payerKeypair]); + }); + it('Should be able to setup canopy with several transactions', async () => { + const merkleTreeRaw = new MerkleTree(leaves); + const root = merkleTreeRaw.root; + const leaf = leaves[leaves.length - 1]; + // take every second leaf of the first half of a tree and append it's parent node to the canopy + const appendIx = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.slice(0, leaves.length / 2).filter((_, i) => i % 2 === 0).map(leaf => leaf.parent?.node!), 0); + await execute(provider, [appendIx], [payerKeypair]); + // take every second leaf of the second half of a tree and append it's parent node to the canopy + const appendIx2 = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.slice(leaves.length / 2).filter((_, i) => i % 2 === 0).map(leaf => leaf.parent?.node!), 2); + await execute(provider, [appendIx2], [payerKeypair]); + const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); + await execute(provider, [finalize], [payerKeypair]); + }); + it('Should be able to setup canopy with several transactions in reverse order', async () => { + const merkleTreeRaw = new MerkleTree(leaves); + const root = merkleTreeRaw.root; + const leaf = leaves[leaves.length - 1]; + + const appendIx = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.slice(leaves.length / 2).filter((_, i) => i % 2 === 0).map(leaf => leaf.parent?.node!), 2); + await execute(provider, [appendIx], [payerKeypair]); + const appendIx2 = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.slice(0, leaves.length / 2).filter((_, i) => i % 2 === 0).map(leaf => leaf.parent?.node!), 0); + await execute(provider, [appendIx2], [payerKeypair]); + const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); + await execute(provider, [finalize], [payerKeypair]); + }); + it('Should be able to replace a canopy node', async () => { + const merkleTreeRaw = new MerkleTree(leaves); + const root = merkleTreeRaw.root; + const leaf = leaves[leaves.length - 1]; + + const appendIx = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.slice(0, leaves.length / 2).filter((_, i) => i % 2 === 0).map(leaf => leaf.parent?.node!), 0); + await execute(provider, [appendIx], [payerKeypair]); + const appendIx2 = createAppendCanopyNodesIx(cmt, payer, [crypto.randomBytes(32)], 2); + await execute(provider, [appendIx2], [payerKeypair]); + const replaceIx = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.slice(leaves.length / 2).filter((_, i) => i % 2 === 0).map(leaf => leaf.parent?.node!), 2); + await execute(provider, [replaceIx], [payerKeypair]); + const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); + await execute(provider, [finalize], [payerKeypair]); + }); + it('Should fail to replace a canopy node for a finalised tree', async () => { + const merkleTreeRaw = new MerkleTree(leaves); + const root = merkleTreeRaw.root; + const leaf = leaves[leaves.length - 1]; + + const appendIx = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.filter((_, i) => i % 2 === 0).map(leaf => leaf.parent?.node!), 0); + await execute(provider, [appendIx], [payerKeypair]); + const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); + await execute(provider, [finalize], [payerKeypair]); + const replaceIx = createAppendCanopyNodesIx(cmt, payer, [crypto.randomBytes(32)], 0); + try { + await execute(provider, [replaceIx], [payerKeypair]); + assert(false, 'Replacing a canopy node for a finalised tree should have failed'); + } catch { } + }); + it('Should fail to initialize an empty tree after preparing a tree', async () => { + const ixs = [createInitEmptyMerkleTreeIx(cmt, payer, { + maxBufferSize: size, + maxDepth: depth, + })]; + try { + await execute(provider, ixs, [payerKeypair]); + assert(false, 'Initializing an empty tree after preparing a tree should have failed'); + } catch { } + }); + }); + describe('Having created a tree with a single leaf', () => { beforeEach(async () => { [cmtKeypair, offChainTree] = await createTreeOnChain(provider, payerKeypair, 1, DEPTH_SIZE_PAIR); diff --git a/account-compression/sdk/tests/utils.ts b/account-compression/sdk/tests/utils.ts index c7737120dd9..901c9f61ddf 100644 --- a/account-compression/sdk/tests/utils.ts +++ b/account-compression/sdk/tests/utils.ts @@ -2,7 +2,7 @@ import { AnchorProvider } from '@coral-xyz/anchor'; import { Keypair, SendTransactionError, Signer, Transaction, TransactionInstruction } from '@solana/web3.js'; import * as crypto from 'crypto'; -import { createAllocTreeIx, createAppendIx, createInitEmptyMerkleTreeIx, ValidDepthSizePair } from '../src'; +import { createAllocTreeIx, createAppendIx, createInitEmptyMerkleTreeIx, prepareTreeIx, ValidDepthSizePair } from '../src'; import { MerkleTree } from '../src/merkle-tree'; /// Wait for a transaction of a certain id to confirm and optionally log its messages @@ -115,3 +115,39 @@ export async function createEmptyTreeOnChain( return cmtKeypair; } + +export type PrepareTreeArgs = { + canopyDepth: number, + depthSizePair: ValidDepthSizePair, + payer: Keypair, + provider: AnchorProvider +}; + +export async function prepareTree( + args: PrepareTreeArgs +): Promise { + const { provider, + payer, + depthSizePair, + canopyDepth + } = args; + const cmtKeypair = Keypair.generate(); + const allocAccountIx = await createAllocTreeIx( + provider.connection, + cmtKeypair.publicKey, + payer.publicKey, + depthSizePair, + canopyDepth + ); + + const ixs = [allocAccountIx, prepareTreeIx( + cmtKeypair.publicKey, + payer.publicKey, + depthSizePair, + ) + ]; + + const txId = await execute(provider, ixs, [payer, cmtKeypair]); + await confirmAndLogTx(provider, txId as string); + return cmtKeypair; +} From f2d2a460ea220a64eb70184d2a3e5c35a96144a6 Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Mon, 27 May 2024 10:46:35 +0100 Subject: [PATCH 07/39] cleanup and formatting for rust code --- .../account-compression/src/canopy.rs | 16 ++++--- .../programs/account-compression/src/lib.rs | 42 +++++++------------ .../src/concurrent_merkle_tree.rs | 5 --- 3 files changed, 26 insertions(+), 37 deletions(-) diff --git a/account-compression/programs/account-compression/src/canopy.rs b/account-compression/programs/account-compression/src/canopy.rs index 78b9ad15195..66ff9dc1708 100644 --- a/account-compression/programs/account-compression/src/canopy.rs +++ b/account-compression/programs/account-compression/src/canopy.rs @@ -132,9 +132,11 @@ pub fn fill_in_proof_from_canopy( Ok(()) } -/// Sets the leaf nodes of the canopy. The leaf nodes are the lowest level of the canopy, representing the leaves of the canopy-tree. -/// The method will update the parent nodes of all the modified subtrees up to the uppermost level of the canopy. -/// The leaf nodes indexing for the start_index is 0-based without regards to the full tree indexes or the node indexes. The start_index is the index of the first leaf node to be updated. +/// Sets the leaf nodes of the canopy. The leaf nodes are the lowest level of the canopy, +/// representing the leaves of the canopy-tree. The method will update the parent nodes of all the +/// modified subtrees up to the uppermost level of the canopy. The leaf nodes indexing for the +/// start_index is 0-based without regards to the full tree indexes or the node indexes. The +/// start_index is the index of the first leaf node to be updated. pub fn set_canopy_leaf_nodes( canopy_bytes: &mut [u8], max_depth: u32, @@ -201,8 +203,10 @@ pub fn check_canopy_root(canopy_bytes: &[u8], expected_root: &Node) -> Result<() } /// Checks the canopy doesn't have any nodes to the right of the provided index in the full tree. -/// This is done by iterating through the canopy nodes to the right of the provided index and finding the top-most node that has the node as its left child. -/// The node should be empty. The iteration contains following the previous checked node on the same level until the last node on the level is reached. +/// This is done by iterating through the canopy nodes to the right of the provided index and +/// finding the top-most node that has the node as its left child. The node should be empty. The +/// iteration contains following the previous checked node on the same level until the last node on +/// the level is reached. pub fn check_canopy_no_nodes_to_right_of_index( canopy_bytes: &[u8], max_depth: u32, @@ -530,7 +534,7 @@ mod tests { canopy_bytes[5 * size_of::()..].fill(0); check_canopy_no_nodes_to_right_of_index(&canopy_bytes, 20, (3 << (20 - 2)) - 1).unwrap(); } - + #[test] fn test_succes_check_canopy_no_nodes_to_right_of_index_no_canopy() { let canopy_bytes = vec![]; diff --git a/account-compression/programs/account-compression/src/lib.rs b/account-compression/programs/account-compression/src/lib.rs index c1a3c2f46be..4d2f8b4f80d 100644 --- a/account-compression/programs/account-compression/src/lib.rs +++ b/account-compression/programs/account-compression/src/lib.rs @@ -184,19 +184,22 @@ pub mod spl_account_compression { update_canopy(canopy_bytes, header.get_max_depth(), None) } - /// In order to initialize a tree with a root, we need to create the tree on-chain first with the proper authority. - /// The tree might contain a canopy, which is a cache of the uppermost nodes. - /// The canopy is used to decrease the size of the proof required to update the tree. + /// In order to initialize a tree with a root, we need to create the tree on-chain first with + /// the proper authority. The tree might contain a canopy, which is a cache of the uppermost + /// nodes. The canopy is used to decrease the size of the proof required to update the tree. /// If the tree is expected to have a canopy, it needs to be prefilled with the necessary nodes. /// There are 2 ways to initialize a merkle tree: /// 1. Initialize an empty tree /// 2. Initialize a tree with a root and leaf - /// For the former case, the canopy will be empty which is expected for an empty tree. The expected flow is `init_empty_merkle_tree`. - /// For the latter case, the canopy should be filled with the necessary nodes to render the tree usable. - /// Thus we need to prefill the canopy with the necessary nodes. - /// The expected flow for a tree without canopy is `prepare_tree` -> `init_merkle_tree_with_root`. - /// The expected flow for a tree with canopy is `prepare_tree` -> `append_canopy_nodes` (multiple times until all of the canopy is filled) -> `init_merkle_tree_with_root`. - /// This instruction initializes the tree header while leaving the tree itself uninitialized. This allows distinguishing between an empty tree and a tree prepare to be initialized with a root. + /// For the former case, the canopy will be empty which is expected for an empty tree. The + /// expected flow is `init_empty_merkle_tree`. For the latter case, the canopy should be + /// filled with the necessary nodes to render the tree usable. Thus we need to prefill the + /// canopy with the necessary nodes. The expected flow for a tree without canopy is + /// `prepare_tree` -> `init_merkle_tree_with_root`. The expected flow for a tree with canopy + /// is `prepare_tree` -> `append_canopy_nodes` (multiple times until all of the canopy is + /// filled) -> `init_merkle_tree_with_root`. This instruction initializes the tree header + /// while leaving the tree itself uninitialized. This allows distinguishing between an empty + /// tree and a tree prepare to be initialized with a root. pub fn prepare_tree( ctx: Context, max_depth: u32, @@ -226,7 +229,8 @@ pub mod spl_account_compression { } /// This instruction pre-initializes the canopy with the specified leaf nodes of the canopy. - /// This is intended to be used after `prepare_tree` and in conjunction with the `init_merkle_tree_with_root` instruction that'll finalize the tree initialization. + /// This is intended to be used after `prepare_tree` and in conjunction with the + /// `init_merkle_tree_with_root` instruction that'll finalize the tree initialization. pub fn append_canopy_nodes( ctx: Context, start_index: u32, @@ -244,7 +248,8 @@ pub mod spl_account_compression { let header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?; header.assert_valid_authority(&ctx.accounts.authority.key())?; - // assert the tree is not initialized yet, we don't want to overwrite the canopy of an initialized tree + // assert the tree is not initialized yet, we don't want to overwrite the canopy of an + // initialized tree let merkle_tree_size = merkle_tree_get_size(&header)?; let (tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size); // ensure the tree is not initialized, the hacky way @@ -252,21 +257,6 @@ pub mod spl_account_compression { tree_bytes.iter().all(|&x| x == 0), AccountCompressionError::TreeAlreadyInitialized ); - // TODO: remove the block below - // an alternative of the above line is to check the last node of the last change log event, which should contain an empty root: - let id = ctx.accounts.merkle_tree.key(); - // A call is made to ConcurrentMerkleTree::is_initialized_wrapped() - let change_log_event = - merkle_tree_apply_fn!(header, id, tree_bytes, is_initialized_wrapped,)?; - match *change_log_event { - ChangeLogEvent::V1(is_initialized_change_log) => { - // todo: consider removing the unwrap - require!( - is_initialized_change_log.path.last().unwrap().node == EMPTY, - AccountCompressionError::TreeAlreadyInitialized - ); - } - } set_canopy_leaf_nodes( canopy_bytes, header.get_max_depth(), diff --git a/libraries/concurrent-merkle-tree/src/concurrent_merkle_tree.rs b/libraries/concurrent-merkle-tree/src/concurrent_merkle_tree.rs index 9a0bcb0bcea..c0dc495dc6a 100644 --- a/libraries/concurrent-merkle-tree/src/concurrent_merkle_tree.rs +++ b/libraries/concurrent-merkle-tree/src/concurrent_merkle_tree.rs @@ -141,11 +141,6 @@ impl !(self.buffer_size == 0 && self.sequence_number == 0 && self.active_index == 0) } - // The initialization method is wrapped to allow for calls through the macro - pub fn is_initialized_wrapped(&self) -> Result { - Ok(self.is_initialized()) - } - /// This is the trustless initialization method that should be used in most /// cases. pub fn initialize(&mut self) -> Result { From a6e3c67943735af47167ff7a8a90af4a1a380606 Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Mon, 27 May 2024 10:53:43 +0100 Subject: [PATCH 08/39] regenerated sdk --- .../sdk/idl/spl_account_compression.json | 24 +++++++++++-------- .../sdk/tests/accountCompression.test.ts | 18 +++++++------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/account-compression/sdk/idl/spl_account_compression.json b/account-compression/sdk/idl/spl_account_compression.json index f79e7a85059..8aec552e0b3 100644 --- a/account-compression/sdk/idl/spl_account_compression.json +++ b/account-compression/sdk/idl/spl_account_compression.json @@ -54,19 +54,22 @@ { "name": "prepareTree", "docs": [ - "In order to initialize a tree with a root, we need to create the tree on-chain first with the proper authority.", - "The tree might contain a canopy, which is a cache of the uppermost nodes.", - "The canopy is used to decrease the size of the proof required to update the tree.", + "In order to initialize a tree with a root, we need to create the tree on-chain first with", + "the proper authority. The tree might contain a canopy, which is a cache of the uppermost", + "nodes. The canopy is used to decrease the size of the proof required to update the tree.", "If the tree is expected to have a canopy, it needs to be prefilled with the necessary nodes.", "There are 2 ways to initialize a merkle tree:", "1. Initialize an empty tree", "2. Initialize a tree with a root and leaf", - "For the former case, the canopy will be empty which is expected for an empty tree. The expected flow is `init_empty_merkle_tree`.", - "For the latter case, the canopy should be filled with the necessary nodes to render the tree usable.", - "Thus we need to prefill the canopy with the necessary nodes.", - "The expected flow for a tree without canopy is `prepare_tree` -> `init_merkle_tree_with_root`.", - "The expected flow for a tree with canopy is `prepare_tree` -> `append_canopy_nodes` (multiple times until all of the canopy is filled) -> `init_merkle_tree_with_root`.", - "This instruction initializes the tree header while leaving the tree itself uninitialized. This allows distinguishing between an empty tree and a tree prepare to be initialized with a root." + "For the former case, the canopy will be empty which is expected for an empty tree. The", + "expected flow is `init_empty_merkle_tree`. For the latter case, the canopy should be", + "filled with the necessary nodes to render the tree usable. Thus we need to prefill the", + "canopy with the necessary nodes. The expected flow for a tree without canopy is", + "`prepare_tree` -> `init_merkle_tree_with_root`. The expected flow for a tree with canopy", + "is `prepare_tree` -> `append_canopy_nodes` (multiple times until all of the canopy is", + "filled) -> `init_merkle_tree_with_root`. This instruction initializes the tree header", + "while leaving the tree itself uninitialized. This allows distinguishing between an empty", + "tree and a tree prepare to be initialized with a root." ], "accounts": [ { @@ -105,7 +108,8 @@ "name": "appendCanopyNodes", "docs": [ "This instruction pre-initializes the canopy with the specified leaf nodes of the canopy.", - "This is intended to be used after `prepare_tree` and in conjunction with the `init_merkle_tree_with_root` instruction that'll finalize the tree initialization." + "This is intended to be used after `prepare_tree` and in conjunction with the", + "`init_merkle_tree_with_root` instruction that'll finalize the tree initialization." ], "accounts": [ { diff --git a/account-compression/sdk/tests/accountCompression.test.ts b/account-compression/sdk/tests/accountCompression.test.ts index 2d4dc6ce873..1f0dd8801d8 100644 --- a/account-compression/sdk/tests/accountCompression.test.ts +++ b/account-compression/sdk/tests/accountCompression.test.ts @@ -250,7 +250,7 @@ describe('Account Compression', () => { const root = merkleTreeRaw.root; const leaf = leaves[leaves.length - 1]; - const appendIx = createAppendCanopyNodesIx(cmt, payer, [merkleTreeRaw.leaves[0].parent?.node!], 0); + const appendIx = createAppendCanopyNodesIx(cmt, payer, [merkleTreeRaw.leaves[0].parent!.node!], 0); await execute(provider, [appendIx], [payerKeypair]); const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); @@ -265,7 +265,7 @@ describe('Account Compression', () => { const leaf = leaves[leaves.length - 1]; // take every second leaf and append it's parent node to the canopy - const appendIx = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.filter((_, i) => i % 2 === 0).map(leaf => leaf.parent?.node!), 0); + const appendIx = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.filter((_, i) => i % 2 === 0).map(leaf => leaf.parent!.node!), 0); await execute(provider, [appendIx], [payerKeypair]); const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); await execute(provider, [finalize], [payerKeypair]); @@ -275,10 +275,10 @@ describe('Account Compression', () => { const root = merkleTreeRaw.root; const leaf = leaves[leaves.length - 1]; // take every second leaf of the first half of a tree and append it's parent node to the canopy - const appendIx = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.slice(0, leaves.length / 2).filter((_, i) => i % 2 === 0).map(leaf => leaf.parent?.node!), 0); + const appendIx = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.slice(0, leaves.length / 2).filter((_, i) => i % 2 === 0).map(leaf => leaf.parent!.node!), 0); await execute(provider, [appendIx], [payerKeypair]); // take every second leaf of the second half of a tree and append it's parent node to the canopy - const appendIx2 = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.slice(leaves.length / 2).filter((_, i) => i % 2 === 0).map(leaf => leaf.parent?.node!), 2); + const appendIx2 = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.slice(leaves.length / 2).filter((_, i) => i % 2 === 0).map(leaf => leaf.parent!.node!), 2); await execute(provider, [appendIx2], [payerKeypair]); const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); await execute(provider, [finalize], [payerKeypair]); @@ -288,9 +288,9 @@ describe('Account Compression', () => { const root = merkleTreeRaw.root; const leaf = leaves[leaves.length - 1]; - const appendIx = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.slice(leaves.length / 2).filter((_, i) => i % 2 === 0).map(leaf => leaf.parent?.node!), 2); + const appendIx = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.slice(leaves.length / 2).filter((_, i) => i % 2 === 0).map(leaf => leaf.parent!.node!), 2); await execute(provider, [appendIx], [payerKeypair]); - const appendIx2 = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.slice(0, leaves.length / 2).filter((_, i) => i % 2 === 0).map(leaf => leaf.parent?.node!), 0); + const appendIx2 = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.slice(0, leaves.length / 2).filter((_, i) => i % 2 === 0).map(leaf => leaf.parent!.node!), 0); await execute(provider, [appendIx2], [payerKeypair]); const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); await execute(provider, [finalize], [payerKeypair]); @@ -300,11 +300,11 @@ describe('Account Compression', () => { const root = merkleTreeRaw.root; const leaf = leaves[leaves.length - 1]; - const appendIx = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.slice(0, leaves.length / 2).filter((_, i) => i % 2 === 0).map(leaf => leaf.parent?.node!), 0); + const appendIx = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.slice(0, leaves.length / 2).filter((_, i) => i % 2 === 0).map(leaf => leaf.parent!.node!), 0); await execute(provider, [appendIx], [payerKeypair]); const appendIx2 = createAppendCanopyNodesIx(cmt, payer, [crypto.randomBytes(32)], 2); await execute(provider, [appendIx2], [payerKeypair]); - const replaceIx = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.slice(leaves.length / 2).filter((_, i) => i % 2 === 0).map(leaf => leaf.parent?.node!), 2); + const replaceIx = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.slice(leaves.length / 2).filter((_, i) => i % 2 === 0).map(leaf => leaf.parent!.node!), 2); await execute(provider, [replaceIx], [payerKeypair]); const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); await execute(provider, [finalize], [payerKeypair]); @@ -314,7 +314,7 @@ describe('Account Compression', () => { const root = merkleTreeRaw.root; const leaf = leaves[leaves.length - 1]; - const appendIx = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.filter((_, i) => i % 2 === 0).map(leaf => leaf.parent?.node!), 0); + const appendIx = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.filter((_, i) => i % 2 === 0).map(leaf => leaf.parent!.node!), 0); await execute(provider, [appendIx], [payerKeypair]); const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); await execute(provider, [finalize], [payerKeypair]); From f8263c1ad18cf6ca52e765331038e8f86227b091 Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Mon, 27 May 2024 11:03:04 +0100 Subject: [PATCH 09/39] fmt/prettier on the new changes in js/ts --- .../sdk/tests/accountCompression.test.ts | 225 ++++++++++++++---- account-compression/sdk/tests/utils.ts | 37 ++- 2 files changed, 199 insertions(+), 63 deletions(-) diff --git a/account-compression/sdk/tests/accountCompression.test.ts b/account-compression/sdk/tests/accountCompression.test.ts index 1f0dd8801d8..2d7ee3273f4 100644 --- a/account-compression/sdk/tests/accountCompression.test.ts +++ b/account-compression/sdk/tests/accountCompression.test.ts @@ -89,7 +89,7 @@ describe('Account Compression', () => { another = anotherKeyPair.publicKey; await provider.connection.confirmTransaction( await provider.connection.requestAirdrop(another, 1e10), - 'confirmed' + 'confirmed', ); }); it('Should be able to finalize the tree', async () => { @@ -97,13 +97,23 @@ describe('Account Compression', () => { const root = merkleTreeRaw.root; const leaf = leaves[leaves.length - 1]; - const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); + const finalize = createFinalizeMerkleTreeWithRootIx( + cmt, + payer, + root, + leaf, + leaves.length - 1, + merkleTreeRaw.getProof(leaves.length - 1).proof, + ); await execute(provider, [finalize], [payerKeypair]); const splCMT = await ConcurrentMerkleTreeAccount.fromAccountAddress(connection, cmt); assert(splCMT.getMaxBufferSize() === size, 'Buffer size does not match'); - assert(splCMT.getCanopyDepth() === canopyDepth, 'Canopy depth does not match: expected ' + canopyDepth + ' but got ' + splCMT.getCanopyDepth()); + assert( + splCMT.getCanopyDepth() === canopyDepth, + 'Canopy depth does not match: expected ' + canopyDepth + ' but got ' + splCMT.getCanopyDepth(), + ); assert(splCMT.getBufferSize() == 1, 'Buffer size does not match'); }); it('Should fail to append canopy node for a tree without canopy', async () => { @@ -111,19 +121,26 @@ describe('Account Compression', () => { try { await execute(provider, [appendIx], [payerKeypair]); assert(false, 'Canopy appending should have failed to execute for a tree without canopy'); - } catch { } + } catch {} }); it('Should fail to finalize the tree with another payer authority', async () => { const merkleTreeRaw = new MerkleTree(leaves); const root = merkleTreeRaw.root; const leaf = leaves[leaves.length - 1]; - const finalize = createFinalizeMerkleTreeWithRootIx(cmt, another, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); + const finalize = createFinalizeMerkleTreeWithRootIx( + cmt, + another, + root, + leaf, + leaves.length - 1, + merkleTreeRaw.getProof(leaves.length - 1).proof, + ); try { await execute(provider, [finalize], [anotherKeyPair]); assert(false, 'Finalizing with another payer should have failed'); - } catch { } + } catch {} }); it('Should fail to finalize the tree with a wrong proof', async () => { const merkleTreeRaw = new MerkleTree(leaves); @@ -140,25 +157,30 @@ describe('Account Compression', () => { try { await execute(provider, [finalize], [payerKeypair]); assert(false, 'Finalizing with a wrong proof should have failed'); - } catch { } + } catch {} }); it('Should fail to double finalize the tree', async () => { const merkleTreeRaw = new MerkleTree(leaves); const root = merkleTreeRaw.root; const leaf = leaves[leaves.length - 1]; - const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); + const finalize = createFinalizeMerkleTreeWithRootIx( + cmt, + payer, + root, + leaf, + leaves.length - 1, + merkleTreeRaw.getProof(leaves.length - 1).proof, + ); await execute(provider, [finalize], [payerKeypair]); try { await execute(provider, [finalize], [payerKeypair]); assert(false, 'Double finalizing should have failed'); - } catch { } + } catch {} }); - } - - ); + }); describe('Having prepared a tree with canopy', () => { const depth = 3; const size = 8; @@ -190,7 +212,7 @@ describe('Account Compression', () => { another = anotherKeyPair.publicKey; await provider.connection.confirmTransaction( await provider.connection.requestAirdrop(another, 1e10), - 'confirmed' + 'confirmed', ); }); it('Should be able to append a single canopy node', async () => { @@ -210,40 +232,57 @@ describe('Account Compression', () => { try { await execute(provider, [appendIx], [anotherKeyPair]); assert(false, 'Appending with another payer should have failed'); - } catch { } + } catch {} }); it('Should fail to append canopy nodes over the limit', async () => { - const appendIx = createAppendCanopyNodesIx(cmt, payer, Array.from({ length: 3 }, () => crypto.randomBytes(32)), 0); + const appendIx = createAppendCanopyNodesIx( + cmt, + payer, + Array.from({ length: 3 }, () => crypto.randomBytes(32)), + 0, + ); try { await execute(provider, [appendIx], [payerKeypair]); assert(false, 'Appending over the limit should have failed'); - } catch { } + } catch {} }); it('Should fail to append canopy nodes over the limit starting from the last index', async () => { - const appendIx = createAppendCanopyNodesIx(cmt, payer, Array.from({ length: 2 }, () => crypto.randomBytes(32)), 1); + const appendIx = createAppendCanopyNodesIx( + cmt, + payer, + Array.from({ length: 2 }, () => crypto.randomBytes(32)), + 1, + ); try { await execute(provider, [appendIx], [payerKeypair]); assert(false, 'Appending over the limit should have failed'); - } catch { } + } catch {} }); it('Should fail to append 0 canopy nodes', async () => { const appendIx = createAppendCanopyNodesIx(cmt, payer, [], 0); try { await execute(provider, [appendIx], [payerKeypair]); assert(false, 'Appending 0 nodes should have failed'); - } catch { } + } catch {} }); it('Should fail to finalize the tree without canopy', async () => { const merkleTreeRaw = new MerkleTree(leaves); const root = merkleTreeRaw.root; const leaf = leaves[leaves.length - 1]; - const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); + const finalize = createFinalizeMerkleTreeWithRootIx( + cmt, + payer, + root, + leaf, + leaves.length - 1, + merkleTreeRaw.getProof(leaves.length - 1).proof, + ); try { await execute(provider, [finalize], [payerKeypair]); assert(false, 'Finalizing without canopy should have failed'); - } catch { } + } catch {} }); it('Should fail to finalize the tree with an incomplete canopy', async () => { const merkleTreeRaw = new MerkleTree(leaves); @@ -252,12 +291,19 @@ describe('Account Compression', () => { const appendIx = createAppendCanopyNodesIx(cmt, payer, [merkleTreeRaw.leaves[0].parent!.node!], 0); await execute(provider, [appendIx], [payerKeypair]); - const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); + const finalize = createFinalizeMerkleTreeWithRootIx( + cmt, + payer, + root, + leaf, + leaves.length - 1, + merkleTreeRaw.getProof(leaves.length - 1).proof, + ); try { await execute(provider, [finalize], [payerKeypair]); assert(false, 'Finalization for an incomplete canopy should have failed'); - } catch { } + } catch {} }); it('Should finalize the tree with a complete canopy', async () => { const merkleTreeRaw = new MerkleTree(leaves); @@ -265,9 +311,21 @@ describe('Account Compression', () => { const leaf = leaves[leaves.length - 1]; // take every second leaf and append it's parent node to the canopy - const appendIx = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.filter((_, i) => i % 2 === 0).map(leaf => leaf.parent!.node!), 0); + const appendIx = createAppendCanopyNodesIx( + cmt, + payer, + merkleTreeRaw.leaves.filter((_, i) => i % 2 === 0).map(leaf => leaf.parent!.node!), + 0, + ); await execute(provider, [appendIx], [payerKeypair]); - const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); + const finalize = createFinalizeMerkleTreeWithRootIx( + cmt, + payer, + root, + leaf, + leaves.length - 1, + merkleTreeRaw.getProof(leaves.length - 1).proof, + ); await execute(provider, [finalize], [payerKeypair]); }); it('Should be able to setup canopy with several transactions', async () => { @@ -275,12 +333,35 @@ describe('Account Compression', () => { const root = merkleTreeRaw.root; const leaf = leaves[leaves.length - 1]; // take every second leaf of the first half of a tree and append it's parent node to the canopy - const appendIx = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.slice(0, leaves.length / 2).filter((_, i) => i % 2 === 0).map(leaf => leaf.parent!.node!), 0); + const appendIx = createAppendCanopyNodesIx( + cmt, + payer, + merkleTreeRaw.leaves + .slice(0, leaves.length / 2) + .filter((_, i) => i % 2 === 0) + .map(leaf => leaf.parent!.node!), + 0, + ); await execute(provider, [appendIx], [payerKeypair]); // take every second leaf of the second half of a tree and append it's parent node to the canopy - const appendIx2 = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.slice(leaves.length / 2).filter((_, i) => i % 2 === 0).map(leaf => leaf.parent!.node!), 2); + const appendIx2 = createAppendCanopyNodesIx( + cmt, + payer, + merkleTreeRaw.leaves + .slice(leaves.length / 2) + .filter((_, i) => i % 2 === 0) + .map(leaf => leaf.parent!.node!), + 2, + ); await execute(provider, [appendIx2], [payerKeypair]); - const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); + const finalize = createFinalizeMerkleTreeWithRootIx( + cmt, + payer, + root, + leaf, + leaves.length - 1, + merkleTreeRaw.getProof(leaves.length - 1).proof, + ); await execute(provider, [finalize], [payerKeypair]); }); it('Should be able to setup canopy with several transactions in reverse order', async () => { @@ -288,11 +369,34 @@ describe('Account Compression', () => { const root = merkleTreeRaw.root; const leaf = leaves[leaves.length - 1]; - const appendIx = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.slice(leaves.length / 2).filter((_, i) => i % 2 === 0).map(leaf => leaf.parent!.node!), 2); + const appendIx = createAppendCanopyNodesIx( + cmt, + payer, + merkleTreeRaw.leaves + .slice(leaves.length / 2) + .filter((_, i) => i % 2 === 0) + .map(leaf => leaf.parent!.node!), + 2, + ); await execute(provider, [appendIx], [payerKeypair]); - const appendIx2 = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.slice(0, leaves.length / 2).filter((_, i) => i % 2 === 0).map(leaf => leaf.parent!.node!), 0); + const appendIx2 = createAppendCanopyNodesIx( + cmt, + payer, + merkleTreeRaw.leaves + .slice(0, leaves.length / 2) + .filter((_, i) => i % 2 === 0) + .map(leaf => leaf.parent!.node!), + 0, + ); await execute(provider, [appendIx2], [payerKeypair]); - const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); + const finalize = createFinalizeMerkleTreeWithRootIx( + cmt, + payer, + root, + leaf, + leaves.length - 1, + merkleTreeRaw.getProof(leaves.length - 1).proof, + ); await execute(provider, [finalize], [payerKeypair]); }); it('Should be able to replace a canopy node', async () => { @@ -300,13 +404,36 @@ describe('Account Compression', () => { const root = merkleTreeRaw.root; const leaf = leaves[leaves.length - 1]; - const appendIx = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.slice(0, leaves.length / 2).filter((_, i) => i % 2 === 0).map(leaf => leaf.parent!.node!), 0); + const appendIx = createAppendCanopyNodesIx( + cmt, + payer, + merkleTreeRaw.leaves + .slice(0, leaves.length / 2) + .filter((_, i) => i % 2 === 0) + .map(leaf => leaf.parent!.node!), + 0, + ); await execute(provider, [appendIx], [payerKeypair]); const appendIx2 = createAppendCanopyNodesIx(cmt, payer, [crypto.randomBytes(32)], 2); await execute(provider, [appendIx2], [payerKeypair]); - const replaceIx = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.slice(leaves.length / 2).filter((_, i) => i % 2 === 0).map(leaf => leaf.parent!.node!), 2); + const replaceIx = createAppendCanopyNodesIx( + cmt, + payer, + merkleTreeRaw.leaves + .slice(leaves.length / 2) + .filter((_, i) => i % 2 === 0) + .map(leaf => leaf.parent!.node!), + 2, + ); await execute(provider, [replaceIx], [payerKeypair]); - const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); + const finalize = createFinalizeMerkleTreeWithRootIx( + cmt, + payer, + root, + leaf, + leaves.length - 1, + merkleTreeRaw.getProof(leaves.length - 1).proof, + ); await execute(provider, [finalize], [payerKeypair]); }); it('Should fail to replace a canopy node for a finalised tree', async () => { @@ -314,25 +441,39 @@ describe('Account Compression', () => { const root = merkleTreeRaw.root; const leaf = leaves[leaves.length - 1]; - const appendIx = createAppendCanopyNodesIx(cmt, payer, merkleTreeRaw.leaves.filter((_, i) => i % 2 === 0).map(leaf => leaf.parent!.node!), 0); + const appendIx = createAppendCanopyNodesIx( + cmt, + payer, + merkleTreeRaw.leaves.filter((_, i) => i % 2 === 0).map(leaf => leaf.parent!.node!), + 0, + ); await execute(provider, [appendIx], [payerKeypair]); - const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, merkleTreeRaw.getProof(leaves.length - 1).proof); + const finalize = createFinalizeMerkleTreeWithRootIx( + cmt, + payer, + root, + leaf, + leaves.length - 1, + merkleTreeRaw.getProof(leaves.length - 1).proof, + ); await execute(provider, [finalize], [payerKeypair]); const replaceIx = createAppendCanopyNodesIx(cmt, payer, [crypto.randomBytes(32)], 0); try { await execute(provider, [replaceIx], [payerKeypair]); assert(false, 'Replacing a canopy node for a finalised tree should have failed'); - } catch { } + } catch {} }); it('Should fail to initialize an empty tree after preparing a tree', async () => { - const ixs = [createInitEmptyMerkleTreeIx(cmt, payer, { - maxBufferSize: size, - maxDepth: depth, - })]; + const ixs = [ + createInitEmptyMerkleTreeIx(cmt, payer, { + maxBufferSize: size, + maxDepth: depth, + }), + ]; try { await execute(provider, ixs, [payerKeypair]); assert(false, 'Initializing an empty tree after preparing a tree should have failed'); - } catch { } + } catch {} }); }); diff --git a/account-compression/sdk/tests/utils.ts b/account-compression/sdk/tests/utils.ts index 901c9f61ddf..4e49c305c71 100644 --- a/account-compression/sdk/tests/utils.ts +++ b/account-compression/sdk/tests/utils.ts @@ -2,7 +2,13 @@ import { AnchorProvider } from '@coral-xyz/anchor'; import { Keypair, SendTransactionError, Signer, Transaction, TransactionInstruction } from '@solana/web3.js'; import * as crypto from 'crypto'; -import { createAllocTreeIx, createAppendIx, createInitEmptyMerkleTreeIx, prepareTreeIx, ValidDepthSizePair } from '../src'; +import { + createAllocTreeIx, + createAppendIx, + createInitEmptyMerkleTreeIx, + prepareTreeIx, + ValidDepthSizePair, +} from '../src'; import { MerkleTree } from '../src/merkle-tree'; /// Wait for a transaction of a certain id to confirm and optionally log its messages @@ -117,37 +123,26 @@ export async function createEmptyTreeOnChain( } export type PrepareTreeArgs = { - canopyDepth: number, - depthSizePair: ValidDepthSizePair, - payer: Keypair, - provider: AnchorProvider + canopyDepth: number; + depthSizePair: ValidDepthSizePair; + payer: Keypair; + provider: AnchorProvider; }; -export async function prepareTree( - args: PrepareTreeArgs -): Promise { - const { provider, - payer, - depthSizePair, - canopyDepth - } = args; +export async function prepareTree(args: PrepareTreeArgs): Promise { + const { provider, payer, depthSizePair, canopyDepth } = args; const cmtKeypair = Keypair.generate(); const allocAccountIx = await createAllocTreeIx( provider.connection, cmtKeypair.publicKey, payer.publicKey, depthSizePair, - canopyDepth + canopyDepth, ); - const ixs = [allocAccountIx, prepareTreeIx( - cmtKeypair.publicKey, - payer.publicKey, - depthSizePair, - ) - ]; + const ixs = [allocAccountIx, prepareTreeIx(cmtKeypair.publicKey, payer.publicKey, depthSizePair)]; const txId = await execute(provider, ixs, [payer, cmtKeypair]); await confirmAndLogTx(provider, txId as string); return cmtKeypair; -} +} From eb80c935520c21d9fc957c7a6138757010452412 Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Mon, 27 May 2024 11:07:55 +0100 Subject: [PATCH 10/39] cleanup --- .../src/state/concurrent_merkle_tree_header.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/account-compression/programs/account-compression/src/state/concurrent_merkle_tree_header.rs b/account-compression/programs/account-compression/src/state/concurrent_merkle_tree_header.rs index 6120f13333e..6d326b76142 100644 --- a/account-compression/programs/account-compression/src/state/concurrent_merkle_tree_header.rs +++ b/account-compression/programs/account-compression/src/state/concurrent_merkle_tree_header.rs @@ -99,19 +99,6 @@ impl ConcurrentMerkleTreeHeader { } } - pub fn is_initialized(&self) -> bool { - match self.account_type { - CompressionAccountType::Uninitialized => false, - CompressionAccountType::ConcurrentMerkleTree => { - match &self.header { - ConcurrentMerkleTreeHeaderData::V1(header) => { - header.max_buffer_size != 0 && header.max_depth != 0 - } - } - } - } - } - pub fn get_max_depth(&self) -> u32 { match &self.header { ConcurrentMerkleTreeHeaderData::V1(header) => header.max_depth, From c547cff3321f9ec23bb9f7e4e1a775951218c6e6 Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Mon, 27 May 2024 14:48:06 +0100 Subject: [PATCH 11/39] cargo clippy over new code --- .../programs/account-compression/src/canopy.rs | 12 ++++++------ .../programs/account-compression/src/lib.rs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/account-compression/programs/account-compression/src/canopy.rs b/account-compression/programs/account-compression/src/canopy.rs index 66ff9dc1708..35a071c6de0 100644 --- a/account-compression/programs/account-compression/src/canopy.rs +++ b/account-compression/programs/account-compression/src/canopy.rs @@ -156,7 +156,7 @@ pub fn set_canopy_leaf_nodes( canopy[start_canopy_idx + i] = *node; } let mut start_canopy_node = start_canopy_node; - let mut end_canopy_node = start_canopy_node + nodes.len() - 1 as usize; + let mut end_canopy_node = start_canopy_node + nodes.len() - 1; let mut empty_node_cache = Box::new([EMPTY; MAX_SUPPORTED_DEPTH]); let leaf_node_level = max_depth - path_len; // traverse up the tree and update the parent nodes in the modified subtree @@ -167,16 +167,16 @@ pub fn set_canopy_leaf_nodes( let left_child = get_value_for_node::( node << 1, level - 1, - &canopy, + canopy, &mut empty_node_cache, ); let right_child = get_value_for_node::( (node << 1) + 1, level - 1, - &canopy, + canopy, &mut empty_node_cache, ); - canopy[node - 2 as usize].copy_from_slice(hashv(&[&left_child, &right_child]).as_ref()); + canopy[node - 2].copy_from_slice(hashv(&[&left_child, &right_child]).as_ref()); } } Ok(()) @@ -186,7 +186,7 @@ pub fn set_canopy_leaf_nodes( pub fn check_canopy_root(canopy_bytes: &[u8], expected_root: &Node) -> Result<()> { check_canopy_bytes(canopy_bytes)?; let canopy = cast_slice::(canopy_bytes); - if canopy.len() < 1 { + if canopy.is_empty() { return Ok(()); // Canopy is empty } let actual_root = hashv(&[&canopy[0], &canopy[1]]).to_bytes(); @@ -219,7 +219,7 @@ pub fn check_canopy_no_nodes_to_right_of_index( let mut node_idx = ((1 << max_depth) + index) >> (max_depth - path_len); // no need to check the node_idx as it's the leaf continaing the index underneath it - while node_idx & node_idx + 1 != 0 { + while node_idx & (node_idx + 1) != 0 { // check the next node to the right node_idx += 1; // find the top-most node that has the node as its left-most child diff --git a/account-compression/programs/account-compression/src/lib.rs b/account-compression/programs/account-compression/src/lib.rs index 4d2f8b4f80d..b55241c82c5 100644 --- a/account-compression/programs/account-compression/src/lib.rs +++ b/account-compression/programs/account-compression/src/lib.rs @@ -283,7 +283,7 @@ pub mod spl_account_compression { let (header_bytes, rest) = merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1); // the header should already be initialized with prepare_tree - let header = ConcurrentMerkleTreeHeader::try_from_slice(&header_bytes)?; + let header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?; header.assert_valid_authority(&ctx.accounts.authority.key())?; let merkle_tree_size = merkle_tree_get_size(&header)?; let (tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size); From 41e5017a4dcc90c96fcf181974211bd6fa16d3b9 Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Mon, 10 Jun 2024 16:56:55 +0100 Subject: [PATCH 12/39] nit: updating a comment for Modify context on account compression to better reflect its usage --- account-compression/programs/account-compression/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/account-compression/programs/account-compression/src/lib.rs b/account-compression/programs/account-compression/src/lib.rs index b55241c82c5..1b979dbd9d0 100644 --- a/account-compression/programs/account-compression/src/lib.rs +++ b/account-compression/programs/account-compression/src/lib.rs @@ -78,7 +78,8 @@ pub struct Initialize<'info> { pub noop: Program<'info, Noop>, } -/// Context for inserting, appending, or replacing a leaf in the tree +/// Context for modifying a tree: inserting, appending, or replacing a leaf in +/// the existing tree and setting the canopy or finalizing a prepared tree. /// /// Modification instructions also require the proof to the leaf to be provided /// as 32-byte nodes via "remaining accounts". From 1a759e17340c70924950b8548a4cc112d49430b1 Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Tue, 25 Jun 2024 12:41:57 +0100 Subject: [PATCH 13/39] using the stackoverflow-safe version of the initialize_with_root call + minor fmt --- .../programs/account-compression/src/lib.rs | 15 ++++++--------- libraries/concurrent-merkle-tree/src/node.rs | 3 +-- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/account-compression/programs/account-compression/src/lib.rs b/account-compression/programs/account-compression/src/lib.rs index 1b979dbd9d0..fc68dd48bb9 100644 --- a/account-compression/programs/account-compression/src/lib.rs +++ b/account-compression/programs/account-compression/src/lib.rs @@ -311,17 +311,14 @@ pub mod spl_account_compression { assert_eq!(proof.len(), header.get_max_depth() as usize); let id = ctx.accounts.merkle_tree.key(); - // A call is made to ConcurrentMerkleTree::initialize_with_root(root, rightmost_leaf, proof, rightmost_index) - let change_log = merkle_tree_apply_fn_mut!( - header, - id, - tree_bytes, - initialize_with_root, + // A call is made to ConcurrentMerkleTree::initialize_with_root + let args = &InitializeWithRootArgs { root, rightmost_leaf, - &proof, - rightmost_index - )?; + proof_vec: proof, + index: rightmost_index, + }; + let change_log = merkle_tree_initialize_with_root(&header, id, tree_bytes, args)?; update_canopy(canopy_bytes, header.get_max_depth(), Some(&change_log))?; wrap_event( &AccountCompressionEvent::ChangeLog(*change_log), diff --git a/libraries/concurrent-merkle-tree/src/node.rs b/libraries/concurrent-merkle-tree/src/node.rs index f7fdeb8973c..e04613a4f6b 100644 --- a/libraries/concurrent-merkle-tree/src/node.rs +++ b/libraries/concurrent-merkle-tree/src/node.rs @@ -27,9 +27,8 @@ pub fn empty_node_cached(level: u32, cache: &[Node; N]) -> Node data } - /// Calculates and caches the hash of empty nodes up to level i -pub fn empty_node_cached_mut(level: u32, cache: &mut[Node; N]) -> Node { +pub fn empty_node_cached_mut(level: u32, cache: &mut [Node; N]) -> Node { let mut data = EMPTY; if level != 0 { let target = (level - 1) as usize; From 6e7a3470c7ee414c5eb77413dcacfecfd2cd9548 Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Thu, 27 Jun 2024 10:37:45 +0100 Subject: [PATCH 14/39] fixed an issue with addressing a canopy directly, without the check for emptiness --- .../account-compression/src/canopy.rs | 30 +++++++++++++++---- .../programs/account-compression/src/lib.rs | 2 +- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/account-compression/programs/account-compression/src/canopy.rs b/account-compression/programs/account-compression/src/canopy.rs index 35a071c6de0..66c79214ebf 100644 --- a/account-compression/programs/account-compression/src/canopy.rs +++ b/account-compression/programs/account-compression/src/canopy.rs @@ -183,13 +183,19 @@ pub fn set_canopy_leaf_nodes( } /// Checks the root of the canopy against the expected root. -pub fn check_canopy_root(canopy_bytes: &[u8], expected_root: &Node) -> Result<()> { +pub fn check_canopy_root(canopy_bytes: &[u8], expected_root: &Node, max_depth: u32) -> Result<()> { check_canopy_bytes(canopy_bytes)?; let canopy = cast_slice::(canopy_bytes); if canopy.is_empty() { return Ok(()); // Canopy is empty } - let actual_root = hashv(&[&canopy[0], &canopy[1]]).to_bytes(); + let mut empty_node_cache = Box::new([EMPTY; MAX_SUPPORTED_DEPTH]); + // first two nodes are the children of the root, they have index 2 and 3 respectively + let left_root_child = + get_value_for_node::(2, max_depth - 1, canopy, &mut empty_node_cache); + let right_root_child = + get_value_for_node::(3, max_depth - 1, canopy, &mut empty_node_cache); + let actual_root = hashv(&[&left_root_child, &right_root_child]).to_bytes(); if actual_root != *expected_root { msg!( "Canopy root mismatch. Expected: {:?}, Actual: {:?}", @@ -269,8 +275,7 @@ fn leaf_node_index_to_canopy_index(path_len: u32, index: u32) -> Result { #[cfg(test)] mod tests { - use super::*; - use spl_concurrent_merkle_tree::node::empty_node; + use {super::*, spl_concurrent_merkle_tree::node::empty_node}; fn success_leaf_node_index_to_canopy_index(path_len: u32, index: u32, expected: usize) { assert_eq!( @@ -444,7 +449,20 @@ mod tests { let expected_root = hashv(&[&[1_u8; 32], &[2_u8; 32]]).to_bytes(); let nodes = vec![[1_u8; 32], [2_u8; 32]]; set_canopy_leaf_nodes(&mut canopy_bytes, 1, 0, &nodes).unwrap(); - check_canopy_root(&canopy_bytes, &expected_root).unwrap(); + check_canopy_root(&canopy_bytes, &expected_root, 30).unwrap(); + } + + #[test] + fn test_success_check_canopy_root_with_empty_right_branch() { + let mut canopy_bytes = vec![0_u8; 2 * size_of::()]; + let mut empty_node_cache = Box::new([EMPTY; MAX_SUPPORTED_DEPTH]); + let top_level = (MAX_SUPPORTED_DEPTH - 1) as u32; + let right_branch = + empty_node_cached_mut::(top_level, &mut empty_node_cache); + let expected_root = hashv(&[&[1_u8; 32], &right_branch]).to_bytes(); + let nodes = vec![[1_u8; 32], EMPTY]; + set_canopy_leaf_nodes(&mut canopy_bytes, MAX_SUPPORTED_DEPTH as u32, 0, &nodes).unwrap(); + check_canopy_root(&canopy_bytes, &expected_root, 30).unwrap(); } #[test] @@ -456,7 +474,7 @@ mod tests { let mut expected_root = expected_root; expected_root[0] = 0; assert_eq!( - check_canopy_root(&canopy_bytes, &expected_root).unwrap_err(), + check_canopy_root(&canopy_bytes, &expected_root, 30).unwrap_err(), AccountCompressionError::CanopyRootMismatch.into() ); } diff --git a/account-compression/programs/account-compression/src/lib.rs b/account-compression/programs/account-compression/src/lib.rs index fc68dd48bb9..0c4b3683352 100644 --- a/account-compression/programs/account-compression/src/lib.rs +++ b/account-compression/programs/account-compression/src/lib.rs @@ -289,7 +289,7 @@ pub mod spl_account_compression { let merkle_tree_size = merkle_tree_get_size(&header)?; let (tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size); // check the canopy root matches the tree root - check_canopy_root(canopy_bytes, &root)?; + check_canopy_root(canopy_bytes, &root, header.get_max_depth())?; // verify the canopy does not conain any nodes to the right of the rightmost leaf check_canopy_no_nodes_to_right_of_index( canopy_bytes, From 5dc6dd77342f310909df3632d2f0ce278dbcee72 Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Thu, 18 Jul 2024 10:48:37 +0100 Subject: [PATCH 15/39] alligned the comment style for a constant with other comments --- account-compression/programs/account-compression/src/canopy.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/account-compression/programs/account-compression/src/canopy.rs b/account-compression/programs/account-compression/src/canopy.rs index 66c79214ebf..284b2fbbe73 100644 --- a/account-compression/programs/account-compression/src/canopy.rs +++ b/account-compression/programs/account-compression/src/canopy.rs @@ -24,7 +24,7 @@ use solana_program::keccak::hashv; use spl_concurrent_merkle_tree::node::{empty_node_cached, empty_node_cached_mut, Node, EMPTY}; use std::mem::size_of; -// 30 is hard coded as it is the current max depth that SPL Compression supports +/// Maximum depth of the tree, supported by the SPL Compression const MAX_SUPPORTED_DEPTH: usize = 30; #[inline(always)] From d49dc5befcc2e1aa66496675ba1eb0c667b10cb5 Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Tue, 23 Jul 2024 16:14:28 +0100 Subject: [PATCH 16/39] extended the concurrent merkle tree header with a is_batch_initialized flag + comments fixed --- .../programs/account-compression/src/error.rs | 4 ++ .../programs/account-compression/src/lib.rs | 10 +++-- .../state/concurrent_merkle_tree_header.rs | 45 ++++++++++++++++--- .../sdk/idl/spl_account_compression.json | 17 +++++-- account-compression/sdk/package.json | 2 +- .../accounts/ConcurrentMerkleTreeAccount.ts | 8 ++++ .../types/ConcurrentMerkleTreeHeaderDataV1.ts | 6 ++- .../sdk/tests/accountCompression.test.ts | 11 +++-- .../concurrentMerkleTreeAccount.test.ts | 29 +++++++++++- .../fixtures/pre-batch-init-tree-account.json | 14 ++++++ 10 files changed, 123 insertions(+), 23 deletions(-) create mode 100644 account-compression/sdk/tests/fixtures/pre-batch-init-tree-account.json diff --git a/account-compression/programs/account-compression/src/error.rs b/account-compression/programs/account-compression/src/error.rs index 60433bb65e3..ef682300ebc 100644 --- a/account-compression/programs/account-compression/src/error.rs +++ b/account-compression/programs/account-compression/src/error.rs @@ -56,6 +56,10 @@ pub enum AccountCompressionError { #[msg("Tree was already initialized")] TreeAlreadyInitialized, + /// The tree header was not initialized for batch processing + #[msg("Tree header was not initialized for batch processing")] + BatchNotInitialized, + /// The canopy root doesn't match the root of the tree #[msg("Canopy root does not match the root of the tree")] CanopyRootMismatch, diff --git a/account-compression/programs/account-compression/src/lib.rs b/account-compression/programs/account-compression/src/lib.rs index 0c4b3683352..1407a74f6c1 100644 --- a/account-compression/programs/account-compression/src/lib.rs +++ b/account-compression/programs/account-compression/src/lib.rs @@ -196,9 +196,9 @@ pub mod spl_account_compression { /// expected flow is `init_empty_merkle_tree`. For the latter case, the canopy should be /// filled with the necessary nodes to render the tree usable. Thus we need to prefill the /// canopy with the necessary nodes. The expected flow for a tree without canopy is - /// `prepare_tree` -> `init_merkle_tree_with_root`. The expected flow for a tree with canopy + /// `prepare_tree` -> `finalize_merkle_tree_with_root`. The expected flow for a tree with canopy /// is `prepare_tree` -> `append_canopy_nodes` (multiple times until all of the canopy is - /// filled) -> `init_merkle_tree_with_root`. This instruction initializes the tree header + /// filled) -> `finalize_merkle_tree_with_root`. This instruction initializes the tree header /// while leaving the tree itself uninitialized. This allows distinguishing between an empty /// tree and a tree prepare to be initialized with a root. pub fn prepare_tree( @@ -217,7 +217,7 @@ pub mod spl_account_compression { merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1); let mut header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?; - header.initialize( + header.initialize_batched( max_depth, max_buffer_size, &ctx.accounts.authority.key(), @@ -231,7 +231,7 @@ pub mod spl_account_compression { /// This instruction pre-initializes the canopy with the specified leaf nodes of the canopy. /// This is intended to be used after `prepare_tree` and in conjunction with the - /// `init_merkle_tree_with_root` instruction that'll finalize the tree initialization. + /// `finalize_merkle_tree_with_root` instruction that'll finalize the tree initialization. pub fn append_canopy_nodes( ctx: Context, start_index: u32, @@ -249,6 +249,7 @@ pub mod spl_account_compression { let header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?; header.assert_valid_authority(&ctx.accounts.authority.key())?; + header.assert_is_batch_initialized()?; // assert the tree is not initialized yet, we don't want to overwrite the canopy of an // initialized tree let merkle_tree_size = merkle_tree_get_size(&header)?; @@ -286,6 +287,7 @@ pub mod spl_account_compression { // the header should already be initialized with prepare_tree let header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?; header.assert_valid_authority(&ctx.accounts.authority.key())?; + header.assert_is_batch_initialized()?; let merkle_tree_size = merkle_tree_get_size(&header)?; let (tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size); // check the canopy root matches the tree root diff --git a/account-compression/programs/account-compression/src/state/concurrent_merkle_tree_header.rs b/account-compression/programs/account-compression/src/state/concurrent_merkle_tree_header.rs index 6d326b76142..1ac8ed77964 100644 --- a/account-compression/programs/account-compression/src/state/concurrent_merkle_tree_header.rs +++ b/account-compression/programs/account-compression/src/state/concurrent_merkle_tree_header.rs @@ -65,9 +65,14 @@ pub struct ConcurrentMerkleTreeHeaderDataV1 { /// Provides a lower-bound on what slot to start (re-)building a tree from. creation_slot: u64, + /// A flag indicating whether the tree has been initialized with a root. + /// This field was added together with the `finalize_tree_with_root` instruction. + /// It takes 1 byte of space taken from the previous padding for existing accounts. + is_batch_initialized: bool, + /// Needs padding for the account to be 8-byte aligned /// 8-byte alignment is necessary to zero-copy the SPL ConcurrentMerkleTree - _padding: [u8; 6], + _padding: [u8; 5], } #[repr(C)] @@ -95,6 +100,24 @@ impl ConcurrentMerkleTreeHeader { header.max_depth = max_depth; header.authority = *authority; header.creation_slot = creation_slot; + // is_batch_initialized is left false by default + } + } + } + + /// Initializes the header with the given parameters and sets the `is_batch_initialized` flag to + /// true. + pub fn initialize_batched( + &mut self, + max_depth: u32, + max_buffer_size: u32, + authority: &Pubkey, + creation_slot: u64, + ) { + self.initialize(max_depth, max_buffer_size, authority, creation_slot); + match self.header { + ConcurrentMerkleTreeHeaderData::V1(ref mut header) => { + header.is_batch_initialized = true; } } } @@ -155,6 +178,18 @@ impl ConcurrentMerkleTreeHeader { } Ok(()) } + + pub fn assert_is_batch_initialized(&self) -> Result<()> { + match &self.header { + ConcurrentMerkleTreeHeaderData::V1(header) => { + require!( + header.is_batch_initialized, + AccountCompressionError::BatchNotInitialized + ); + } + } + Ok(()) + } } pub fn merkle_tree_get_size(header: &ConcurrentMerkleTreeHeader) -> Result { @@ -166,10 +201,10 @@ pub fn merkle_tree_get_size(header: &ConcurrentMerkleTreeHeader) -> Result Ok(size_of::>()), (8, 16) => Ok(size_of::>()), (9, 16) => Ok(size_of::>()), - (10, 32)=> Ok(size_of::>()), - (11, 32)=> Ok(size_of::>()), - (12, 32)=> Ok(size_of::>()), - (13, 32)=> Ok(size_of::>()), + (10, 32) => Ok(size_of::>()), + (11, 32) => Ok(size_of::>()), + (12, 32) => Ok(size_of::>()), + (13, 32) => Ok(size_of::>()), (14, 64) => Ok(size_of::>()), (14, 256) => Ok(size_of::>()), (14, 1024) => Ok(size_of::>()), diff --git a/account-compression/sdk/idl/spl_account_compression.json b/account-compression/sdk/idl/spl_account_compression.json index 8aec552e0b3..9bc4cecd66d 100644 --- a/account-compression/sdk/idl/spl_account_compression.json +++ b/account-compression/sdk/idl/spl_account_compression.json @@ -65,9 +65,9 @@ "expected flow is `init_empty_merkle_tree`. For the latter case, the canopy should be", "filled with the necessary nodes to render the tree usable. Thus we need to prefill the", "canopy with the necessary nodes. The expected flow for a tree without canopy is", - "`prepare_tree` -> `init_merkle_tree_with_root`. The expected flow for a tree with canopy", + "`prepare_tree` -> `finalize_merkle_tree_with_root`. The expected flow for a tree with canopy", "is `prepare_tree` -> `append_canopy_nodes` (multiple times until all of the canopy is", - "filled) -> `init_merkle_tree_with_root`. This instruction initializes the tree header", + "filled) -> `finalize_merkle_tree_with_root`. This instruction initializes the tree header", "while leaving the tree itself uninitialized. This allows distinguishing between an empty", "tree and a tree prepare to be initialized with a root." ], @@ -109,7 +109,7 @@ "docs": [ "This instruction pre-initializes the canopy with the specified leaf nodes of the canopy.", "This is intended to be used after `prepare_tree` and in conjunction with the", - "`init_merkle_tree_with_root` instruction that'll finalize the tree initialization." + "`finalize_merkle_tree_with_root` instruction that'll finalize the tree initialization." ], "accounts": [ { @@ -542,6 +542,15 @@ ], "type": "u64" }, + { + "name": "isBatchInitialized", + "docs": [ + "A flag indicating whether the tree has been initialized with a root.", + "This field was added together with the `finalize_tree_with_root` instruction.", + "It takes 1 byte of space taken from the previous padding for existing accounts." + ], + "type": "bool" + }, { "name": "padding", "docs": [ @@ -549,7 +558,7 @@ "8-byte alignment is necessary to zero-copy the SPL ConcurrentMerkleTree" ], "type": { - "array": ["u8", 6] + "array": ["u8", 5] } } ] diff --git a/account-compression/sdk/package.json b/account-compression/sdk/package.json index 5d3e0584f74..8687bda2204 100644 --- a/account-compression/sdk/package.json +++ b/account-compression/sdk/package.json @@ -39,7 +39,7 @@ "lint:fix": "eslint . --fix --ext .js,.ts", "docs": "rm -rf docs/ && typedoc --out docs", "deploy:docs": "npm run docs && gh-pages --dest account-compression/sdk --dist docs --dotfiles", - "start-validator": "solana-test-validator --reset --quiet --bpf-program cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK ../target/deploy/spl_account_compression.so --bpf-program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV ../target/deploy/spl_noop.so", + "start-validator": "solana-test-validator --reset --quiet --bpf-program cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK ../target/deploy/spl_account_compression.so --bpf-program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV ../target/deploy/spl_noop.so --account 27QMkDMpBoAhmWj6xxQNYdqXZL5nnC8tkZcEtkNxCqeX pre-batch-init-tree-account.json", "run-tests": "jest tests --detectOpenHandles", "run-tests:events": "jest tests/events --detectOpenHandles", "run-tests:accounts": "jest tests/accounts --detectOpenHandles", diff --git a/account-compression/sdk/src/accounts/ConcurrentMerkleTreeAccount.ts b/account-compression/sdk/src/accounts/ConcurrentMerkleTreeAccount.ts index 2e602599b18..bef2f4954cb 100644 --- a/account-compression/sdk/src/accounts/ConcurrentMerkleTreeAccount.ts +++ b/account-compression/sdk/src/accounts/ConcurrentMerkleTreeAccount.ts @@ -121,6 +121,14 @@ export class ConcurrentMerkleTreeAccount { getCanopyDepth(): number { return getCanopyDepth(this.canopy.canopyBytes.length); } + + /** + * Returns the flag that indicates if the tree has been batch initialized + * @returns the flag + */ + getIsBatchInitialized(): boolean { + return this.getHeaderV1().isBatchInitialized; + } } /** diff --git a/account-compression/sdk/src/generated/types/ConcurrentMerkleTreeHeaderDataV1.ts b/account-compression/sdk/src/generated/types/ConcurrentMerkleTreeHeaderDataV1.ts index 19a48b0d366..7e3b0e54e78 100644 --- a/account-compression/sdk/src/generated/types/ConcurrentMerkleTreeHeaderDataV1.ts +++ b/account-compression/sdk/src/generated/types/ConcurrentMerkleTreeHeaderDataV1.ts @@ -11,9 +11,10 @@ import * as web3 from '@solana/web3.js'; export type ConcurrentMerkleTreeHeaderDataV1 = { authority: web3.PublicKey; creationSlot: beet.bignum; + isBatchInitialized: boolean; maxBufferSize: number; maxDepth: number; - padding: number[] /* size: 6 */; + padding: number[] /* size: 5 */; }; /** @@ -26,7 +27,8 @@ export const concurrentMerkleTreeHeaderDataV1Beet = new beet.BeetArgsStruct { const merkleTreeRaw = new MerkleTree(leaves); const root = merkleTreeRaw.root; const leaf = leaves[leaves.length - 1]; - + const canopyDepth = 0; const finalize = createFinalizeMerkleTreeWithRootIx( cmt, payer, @@ -109,11 +110,7 @@ describe('Account Compression', () => { await execute(provider, [finalize], [payerKeypair]); const splCMT = await ConcurrentMerkleTreeAccount.fromAccountAddress(connection, cmt); - assert(splCMT.getMaxBufferSize() === size, 'Buffer size does not match'); - assert( - splCMT.getCanopyDepth() === canopyDepth, - 'Canopy depth does not match: expected ' + canopyDepth + ' but got ' + splCMT.getCanopyDepth(), - ); + assertCMTProperties(splCMT, depth, size, payer, root, canopyDepth, true); assert(splCMT.getBufferSize() == 1, 'Buffer size does not match'); }); it('Should fail to append canopy node for a tree without canopy', async () => { @@ -327,6 +324,8 @@ describe('Account Compression', () => { merkleTreeRaw.getProof(leaves.length - 1).proof, ); await execute(provider, [finalize], [payerKeypair]); + const splCMT = await ConcurrentMerkleTreeAccount.fromAccountAddress(connection, cmt); + assertCMTProperties(splCMT, depth, size, payer, root, canopyDepth, true); }); it('Should be able to setup canopy with several transactions', async () => { const merkleTreeRaw = new MerkleTree(leaves); diff --git a/account-compression/sdk/tests/accounts/concurrentMerkleTreeAccount.test.ts b/account-compression/sdk/tests/accounts/concurrentMerkleTreeAccount.test.ts index 0b6d4a25e00..c3dcbcb85c9 100644 --- a/account-compression/sdk/tests/accounts/concurrentMerkleTreeAccount.test.ts +++ b/account-compression/sdk/tests/accounts/concurrentMerkleTreeAccount.test.ts @@ -8,13 +8,14 @@ import { ALL_DEPTH_SIZE_PAIRS, ConcurrentMerkleTreeAccount, getConcurrentMerkleT import { emptyNode, MerkleTree } from '../../src/merkle-tree'; import { createEmptyTreeOnChain, createTreeOnChain } from '../utils'; -function assertCMTProperties( +export function assertCMTProperties( onChainCMT: ConcurrentMerkleTreeAccount, expectedMaxDepth: number, expectedMaxBufferSize: number, expectedAuthority: PublicKey, expectedRoot: Buffer, expectedCanopyDepth?: number, + expectedIsBatchInitialized = false, ) { assert( onChainCMT.getMaxDepth() === expectedMaxDepth, @@ -32,6 +33,10 @@ function assertCMTProperties( 'On chain canopy depth does not match expected canopy depth', ); } + assert( + onChainCMT.getIsBatchInitialized() === expectedIsBatchInitialized, + 'On chain isBatchInitialized does not match expected value' + ); } describe('ConcurrentMerkleTreeAccount tests', () => { @@ -142,4 +147,26 @@ describe('ConcurrentMerkleTreeAccount tests', () => { } }); }); + + describe('Can deserialize an existing CMTAccount from a real on-chain CMT created before the is_batch_initialized field was introduced inplace of the first byte of _padding', () => { + it('Interpreted on-chain fields correctly', async () => { + // The account data was generated by running: + // $ solana account 27QMkDMpBoAhmWj6xxQNYdqXZL5nnC8tkZcEtkNxCqeX \ + // --output-file tests/fixtures/pre-batch-init-tree-account.json \ + // --output json + const deployedAccount = new PublicKey('27QMkDMpBoAhmWj6xxQNYdqXZL5nnC8tkZcEtkNxCqeX'); + const cmt = await ConcurrentMerkleTreeAccount.fromAccountAddress( + connection, + deployedAccount, + 'confirmed' + ); + const expectedMaxDepth = 10; + const expectedMaxBufferSize = 32; + const expectedCanopyDepth = 0; + const expectedAuthority = new PublicKey('BFNT941iRwYPe2Js64dTJSoksGCptWAwrkKMaSN73XK2'); + const expectedRoot = new PublicKey('83UjseEuEgxyVyDTmrJCQ9QbeksdRZ7KPDZGQYc5cAgF').toBuffer(); + const expectedIsBatchInitialized = false; + await assertCMTProperties(cmt, expectedMaxDepth, expectedMaxBufferSize, expectedAuthority, expectedRoot, expectedCanopyDepth, expectedIsBatchInitialized); + }); + }); }); diff --git a/account-compression/sdk/tests/fixtures/pre-batch-init-tree-account.json b/account-compression/sdk/tests/fixtures/pre-batch-init-tree-account.json new file mode 100644 index 00000000000..98adaa774dc --- /dev/null +++ b/account-compression/sdk/tests/fixtures/pre-batch-init-tree-account.json @@ -0,0 +1,14 @@ +{ + "pubkey": "27QMkDMpBoAhmWj6xxQNYdqXZL5nnC8tkZcEtkNxCqeX", + "account": { + "lamports": 84132480, + "data": [ + "AQAgAAAACgAAAJhDQUO8VHxyuU2+8Gbazk9rGe2MW3Xu3mPjn5qN+Mnd8qEyDgAAAAAAAAAAAAAJAAAAAAAAAAkAAAAAAAAACgAAAAAAAAD53D5/4BbgUO/yYDNPGKXU/jkdggkjGfWWTy4ut8HDpQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAArTIotnb3081ChKVEPxfxlis25JGzCkCyQFhJ5Ze6X7W0wRlRlXxvj2QsSvYc1rJGQP7G3H/GB+6CBqmekkENMCHduaNWgVw/rBAmtt7F3zEkr7rbSFybpaPjOYoEt7qF5Ydpsyob6vHqJzdaRAlaDR+2ZM4t01jn/L+3jCahk0QOsB6/ye0nUAzU38l5Jy0fCRPMn2ZUDX6ABYERCeHPLYh8Ir2HUNNAFqw8ZrX/EC2s3XP2sBTnELUegCKvmhlo/9cBV+SAY/wzyXoFD39kAjO/ZGzJjZUkxrkrzzq1b4OYZ8xffxlrk7rh4n5jIHQkRdKQ8iY4J0mLVP7FOfdWr8761OUIwJi5p+HY/rGZVfsCupZ1WFB4cQlp00QPUFTgAAAAAAAAAABCQcymud9cvMqkqaz7rb8B4fJGipunRCEkcpIwwf1bqgG0jc8ty2NKzqQ11mfpEgaWHx71Zkd+KhVW6g9RF++oo50UCdctGN9/Yj9M997BjhZjf9P0egTPElcLkXHmGEjtm+CkQBf+3Sbkkq2UmWgP+WnCKKzej7Rk25CTxmlTwR0oez8apwcYlgkfdQNxr3jMbmyJlZungLUL0qdF54IhXXYkra5BQiu8imQXVs5vHpaJmrjUhQCiQT0GdoO+zap3nFWBCJyTSPURgM3qEQhr6WeAPbarDAZnu2Hn6KOJBJiGW8/Mdyb2UHzslHH5v92pUD262jHxv9Dxka11yDv8R21qTggJggnyd9Y9xfFQJmMOlsHxK/OXAbJx15vj+kmxMg/C5+70VPL35eDhb5k8H351PyxQoZH0RK2eXBcIbwO6kdDpcOt1lYsFGxVJOtPzJ+Eq2c5sWwdFSlaopv7WAAAAAAAAAAC4lUsocxFm8dZSnJRk0dztzMPjp1Tw++Vr9kJl2nwJK9rUpl3vmHv+1y5vONxkqse+I11hYnCbfPjlMwi1H3TGbXub1fM1JIxVi6J8yee8q1pLyvHD6l8RgltY7GeLcH/U4Ya37FWODitELZNDut7d8YRI66XliIh458WBFM9ZkWXQAtLrm0fso9F+CE4K2hmZHGSd1tnCGMe1CN5PYnAd32gpAsQAHgFgjiYdxQ+Ezfk2PTE2KL6ltcvyeQ2fVk/xBR50NHfWV4D8JytRVz0YXRQ3fYf4EniFtQtPmQ2CntMT1AroOs/TbheIWHf427xgdUB6Nbd0xZqaRnp3PgDnBy4iDWhVohnVlke8mes2HV+4PTvKtS8QvSek76DrEV0oeh2SM/j5OOPFLfsK2gHRGX20vcKOYa3Rcs9g7X+F3uMazFbBC/vFwbCK/Pfjhmj6ZWod2MSVhsq2Xn9ezssqAQAAAAAAAAARmQ/IUesWNRXIOks0o34cvGsvVMbFCIZqluF+zkUg3hdpJ3DYGBOMaX3Jtz5u3/HqSrb4Uio0kx5VrkfTuCStLPB7V+PqjzBlN326Cg9WoxfTC6nEgo++DbKYbMIW+kYXnWqnD0lliL2PQlq/MxvtOYbZSWYNIg687iKjYnZpTKfNIilBb7JXh6KfMm2xDEpsmNy+djJBXPNfcB9y90yP7xXZA4/4HL5hPpQTNnestf54Wvoo2ZFUKHF/b1hmH7EzF4X5SJlrI/RlinkvdEjGtPzaIL2jYEjQ2S0rSYMD9impk0XbfWrsrHNs8UwnVVrAzmYtnvI0q4YFRoHl0hO45BUJhpj4wEBrSEt11HdPrSj9ENeYuMHmDeN8j7I3oZ0YQ8BhI/FSTMKss2tGL8oa0Z0QPCvQHmL+6bAj38xXGmE/mHkgRdP2Cfy9f++ql/p2oA0Fh6zlzDHT5GjR1V4aAgAAAAAAAAB8dN1Ir+WmUJgJmzlUI8WF6f4d7ht9QkK/wnbsSBjBRpJkz7UEmWcwil8CTYmNwaV2Du1lW32sVcgVEJujtpMImqpKqpjfJAV7T+VhZOwOrz4Cxbhy0Uk6AP5Dzt03oE2am+sbNkvl+u4EBJ7nI9+OF5a2fd+T8QZNfFKuK+sbcJ5MDyIH+szPX7DliqrMLVHTov3qzMMT1BnoZIM7oxf94ksHIuS7WwOlsyXxkLuD/r+QiLUacD4bdxSxxR8+5YlhEhEUKbOZvMJCq4o+0LWkNvYGY7Dy4J3MbXY4yzp/VEu7WuifydqNvwfmiHT9Z/o5T4z1ApBoFyXyblXRAJH9FwVy0dLmQP9M13f6Akv1vxEzPg4SaSZcwK1onN8GLnvz2b4OH2bpevfc8pZId4ukEjaMURY9n7GtYyuvIK8rZS9OoNUDYlgULbDKfheL6sYW/j5plzTZOxRDjZIMyX4pAwAAAAAAAADNp9MXV4O0q8K4yJ4rPS4VXwuq4t6hgk27MVZJKGlunsiS1BPzCUsYrjY3HC+M6PP9IYX6/C1lnCIJ0o+cGJbYD19oYWHldkOUktrvzv1YeIT02RPpXgD7krjwyw6GMkhnmRHM08ykLrx+su31lZ/7InhGHjMGkXE2RSSpISmXIKyTFqBiz4VpTggxrfX9+GjlvMNkhfOFIfePNnEkf+68dCkpR1dgZNsp8j7B2L6NTDXn0xlzzOFCPdYIW+khPYsvornwd8zpbJ5MrdNJ83iC52PruW+e81UFB2tsjX/V8hfGsXTW9Himu9kyZl20vhO5ySw0Z75NoEX2FF5w7z1I9BgV2QhCFgzt0zrTY6OZbibtCjzVuhbEcXSt78iQY6P4u2kfGGabhJ8jRzmrTl7fXpjX/NASK+oOTkveW5gvCh+4k9wetZPvkoCJOSj7rPsxrFs4ac13sWE6OK/Vu+yABAAAAAAAAACMUJhFEGPVsytUOFCvzFCerXY67FhmJvjd7P8/Ab3J9VSkSuNkq51kaKlKK1xhWXrj1UgdBRbh1Esks6jgfWyPNinZuBr83cBaOA09aMy5pE4aD3NM9VnTXi/qfIRcTBhYE15K4fr+Qq+1stfqGG79wM9CwprrCpltIbuAoTjySt4mWHDzbROyEl4QNmaCm/GMyIT5joPvINeBDngrSmzV7J1lAC0hj27wLpADHGbGmL21SgKBIka32HJvVbRnN6GCL+Bk7OmUUNnUoKK0c/mlFguIN7DoiyOWdhCHN3YbM23aZIHefz1HkjUMnW4sIP3jF5I9Za+lJLOR+Y80vL/X8P2Ti5wEFqYOTNjg+y6Yz20KF7Z2GCwOTSgIEshFms939zWviwqjq1KT5KeHoyAVUuHXe6ZaI2l9FIhLOlFDZFz/39I24yvRY3ZCp6XhC5X9EbwbXJ7+dwCwQgbzES1ABQAAAAAAAABjqS9H5fcSRNDGyBaFKz79o8xeMIRKzzlT7sFa/bMX3kkyS5iXq/g0JVqbjB2dQajXxf9E3+IOICDQd1ZPSxHDFJSUjQD9vX44uD9ITmVSGxgS7j59qA7RCYXZadRg+TMmfB9Ji2xLITEiD31ZnNzCAjTvf2nYowheYXqzIK2OpXKvcAwtvYZ6jQUXYQN8MbRpugjApKVETYk3uXwnZE3VK8i5NLxjGokDVRdbGjlKV4Whugr31MZOvQrOhpW0vO3C97zgdgI/HyNGJeILnH3Xjn0b47Lzq1r/0FJx/ECc5D2kXvbpgCQLc1b69+hVg1N4H9/qvMCtpYwILqNbk0My1FzSfUupEqsFEcfhiVXz2uM5JaUm5ei0h8S2zmH/eBAz4smlHxG/xtAT3NnLmEUkI1jtSnIquo2kuKrOoU7Dyv2AE0zHGJRLQGwDCzfDasneG68qjdK7WioqemFHYB5GBgAAAAAAAAC67j8goNDnD6JGvBqXvwGS77IyYa56EExRm96UvuqOjz6G8UCwmDpNdjVbd0qr1ViFXTtoozYLJPsM1Q3xmxPxAKRZU70n/FaS1N4p4SPQ/TN7i+a/rph3DHMrpd3RsI7OyYZIRPCCqLdQrpA7xz4lXRaEt9hqN96n4UJvafy3d1CQJss98bv5Y+8je4Wb+bqavGOBnT7XQlrcTPnMTYIsJjE5Wjv8iP1IUuo0C4QsTF6CFGT7dfIf3sQx8O7Coz1dficqvARL6riF3SlIZ9V0K7YWDinCWUSqt9s2jul37QjE/wYvGg7E8lz2yeVpXXjDxl4gh1GzOEn0OWDTc+XzJ/yNCzDeI0Z/52C1rj9hMf2D5/Cl5DJAR4HJsk7nVMV9QT88uOxYi5TWVR2qChpSJ0pULmoPTLAiyW47/g7yfqLPJ+8vhMP2TXv7rcnviSHPo2CGxSBc4ZmhpojK3pY1BwAAAAAAAABopFW8eCE4hGyMUCyvKkQ+GSMhBfrBc6HcBAOIRfpeAPWxOuEEHeBOEdf46B5t7j+RhQ6FEju+knqr9ZyHLaiTGWyc6JkD47GcNMQmldLaVovSYKtB+BESBYjL0jLzGCp58k+YbjuCUpp6iImugZizQSMOYtYVUjbc1DyM3IdHVsohegHZOROewEmAImpFV56O8G+tCcYlnRWQI2cfduj9GjJOGcVqB3xYTcV9WEc1Yrr1JtHSWI4W3r3zo2UbnSpDmpI3fmG2VuvHQLh5h8sFt/1fgrRkI5yZ5eFPHsp1E7LHzoD5y/rVHBYsqKhxKPOgWrUJnXjUrwNJfB1RNWQPzXMz/WJ1OdGvxQOsnVfqLrh7VS9pDPqS+FuJPoX66qT3pdemBFWruc87OG9JPadnG6whSDrCnPiAswOYQL0ArJGPPuHqjtPw/OTL2DDIvOy08OYVQfJiJ2cHEuJaSNrICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK0yKLZ299PNQoSlRD8X8ZYrNuSRswpAskBYSeWXul+1tMEZUZV8b49kLEr2HNayRkD+xtx/xgfuggapnpJBDTBQkCbLPfG7+WPvI3uFm/m6mrxjgZ0+10Ja3Ez5zE2CLOWHabMqG+rx6ic3WkQJWg0ftmTOLdNY5/y/t4wmoZNEDrAev8ntJ1AM1N/JeSctHwkTzJ9mVA1+gAWBEQnhzy2IfCK9h1DTQBasPGa1/xAtrN1z9rAU5xC1HoAir5oZaP/XAVfkgGP8M8l6BQ9/ZAIzv2RsyY2VJMa5K886tW+DmGfMX38Za5O64eJ+YyB0JEXSkPImOCdJi1T+xTn3Vq/O+tTlCMCYuafh2P6xmVX7ArqWdVhQeHEJadNED1BU4PWxOuEEHeBOEdf46B5t7j+RhQ6FEju+knqr9ZyHLaiTCQAAAAAAAAA=", + "base64" + ], + "owner": "cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK", + "executable": false, + "rentEpoch": 18446744073709551615, + "space": 11960 + } +} \ No newline at end of file From c94a3fae871c0d2a7cc6d440ff30a090120ef06d Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Tue, 23 Jul 2024 19:25:29 +0100 Subject: [PATCH 17/39] updated close account to allow closing prepared and not finalized accounts + updated comments on append_canopy_nodes to reflect the possibility to replace those --- .../programs/account-compression/src/lib.rs | 29 +++- .../state/concurrent_merkle_tree_header.rs | 6 + .../sdk/idl/spl_account_compression.json | 31 ++++- .../sdk/src/generated/errors/index.ts | 28 +++- .../sdk/tests/accountCompression.test.ts | 124 ++++++++++++++++++ 5 files changed, 206 insertions(+), 12 deletions(-) diff --git a/account-compression/programs/account-compression/src/lib.rs b/account-compression/programs/account-compression/src/lib.rs index 1407a74f6c1..f2a8dddd095 100644 --- a/account-compression/programs/account-compression/src/lib.rs +++ b/account-compression/programs/account-compression/src/lib.rs @@ -232,6 +232,17 @@ pub mod spl_account_compression { /// This instruction pre-initializes the canopy with the specified leaf nodes of the canopy. /// This is intended to be used after `prepare_tree` and in conjunction with the /// `finalize_merkle_tree_with_root` instruction that'll finalize the tree initialization. + /// The canopy is used to cache the uppermost nodes of the tree, which allows for a smaller + /// proof size when updating the tree. The canopy should be filled with the necessary nodes + /// before calling `finalize_merkle_tree_with_root`. You may call this instruction multiple + /// times to fill the canopy with the necessary nodes. The canopy may be filled with the + /// nodes in any order. The already filled nodes may be replaced with new nodes before calling + /// `finalize_merkle_tree_with_root` if the step was done in error. + /// The canopy should be filled with all the nodes that are to the left of the rightmost + /// leaf of the tree before calling `finalize_merkle_tree_with_root`. The canopy should not + /// contain any nodes to the right of the rightmost leaf of the tree. + /// This instruction calculates and filles in all the canopy nodes "above" the provided ones. + /// The validation of the canopy is done in the `finalize_merkle_tree_with_root` instruction. pub fn append_canopy_nodes( ctx: Context, start_index: u32, @@ -267,8 +278,15 @@ pub mod spl_account_compression { ) } - /// Initializes a prepared tree with a root and a rightmost leaf. The rightmost leaf is used to verify the canopy if the tree has it. Before calling this instruction, the tree should be prepared with `prepare_tree` and the canopy should be filled with the necessary nodes with `append_canopy_nodes` (if the canopy is used). - /// This method should be used for rolluped creation of trees. The indexing of such rollups should be done off-chain. The programs calling this instruction should take care of ensuring the indexing is possible. For example, staking may be required to ensure the tree creator has some responsibility for what is being indexed. If indexing is not possible, there should be a mechanism to penalize the tree creator. + /// Initializes a prepared tree with a root and a rightmost leaf. The rightmost leaf is used to + /// verify the canopy if the tree has it. Before calling this instruction, the tree should be + /// prepared with `prepare_tree` and the canopy should be filled with the necessary nodes with + /// `append_canopy_nodes` (if the canopy is used). This method should be used for rolluped + /// creation of trees. The indexing of such rollups should be done off-chain. The programs + /// calling this instruction should take care of ensuring the indexing is possible. For example, + /// staking may be required to ensure the tree creator has some responsibility for what is being + /// indexed. If indexing is not possible, there should be a mechanism to penalize the tree + /// creator. pub fn finalize_merkle_tree_with_root( ctx: Context, root: [u8; 32], @@ -549,8 +567,11 @@ pub mod spl_account_compression { let merkle_tree_size = merkle_tree_get_size(&header)?; let (tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size); - let id = ctx.accounts.merkle_tree.key(); - merkle_tree_apply_fn_mut!(header, id, tree_bytes, prove_tree_is_empty,)?; + // Check if the tree is either empty or is batch initialized and not finalized yet. + if !header.get_is_batch_initialized() || !tree_bytes.iter().all(|&x| x == 0) { + let id = ctx.accounts.merkle_tree.key(); + merkle_tree_apply_fn_mut!(header, id, tree_bytes, prove_tree_is_empty,)?; + } // Close merkle tree account // 1. Move lamports diff --git a/account-compression/programs/account-compression/src/state/concurrent_merkle_tree_header.rs b/account-compression/programs/account-compression/src/state/concurrent_merkle_tree_header.rs index 1ac8ed77964..29c0a38f71c 100644 --- a/account-compression/programs/account-compression/src/state/concurrent_merkle_tree_header.rs +++ b/account-compression/programs/account-compression/src/state/concurrent_merkle_tree_header.rs @@ -140,6 +140,12 @@ impl ConcurrentMerkleTreeHeader { } } + pub fn get_is_batch_initialized(&self) -> bool { + match &self.header { + ConcurrentMerkleTreeHeaderData::V1(header) => header.is_batch_initialized, + } + } + pub fn set_new_authority(&mut self, new_authority: &Pubkey) { match self.header { ConcurrentMerkleTreeHeaderData::V1(ref mut header) => { diff --git a/account-compression/sdk/idl/spl_account_compression.json b/account-compression/sdk/idl/spl_account_compression.json index 9bc4cecd66d..c0af69a2e0d 100644 --- a/account-compression/sdk/idl/spl_account_compression.json +++ b/account-compression/sdk/idl/spl_account_compression.json @@ -109,7 +109,18 @@ "docs": [ "This instruction pre-initializes the canopy with the specified leaf nodes of the canopy.", "This is intended to be used after `prepare_tree` and in conjunction with the", - "`finalize_merkle_tree_with_root` instruction that'll finalize the tree initialization." + "`finalize_merkle_tree_with_root` instruction that'll finalize the tree initialization.", + "The canopy is used to cache the uppermost nodes of the tree, which allows for a smaller", + "proof size when updating the tree. The canopy should be filled with the necessary nodes", + "before calling `finalize_merkle_tree_with_root`. You may call this instruction multiple", + "times to fill the canopy with the necessary nodes. The canopy may be filled with the", + "nodes in any order. The already filled nodes may be replaced with new nodes before calling", + "`finalize_merkle_tree_with_root` if the step was done in error.", + "The canopy should be filled with all the nodes that are to the left of the rightmost", + "leaf of the tree before calling `finalize_merkle_tree_with_root`. The canopy should not", + "contain any nodes to the right of the rightmost leaf of the tree.", + "This instruction calculates and filles in all the canopy nodes \"above\" the provided ones.", + "The validation of the canopy is done in the `finalize_merkle_tree_with_root` instruction." ], "accounts": [ { @@ -151,8 +162,15 @@ { "name": "finalizeMerkleTreeWithRoot", "docs": [ - "Initializes a prepared tree with a root and a rightmost leaf. The rightmost leaf is used to verify the canopy if the tree has it. Before calling this instruction, the tree should be prepared with `prepare_tree` and the canopy should be filled with the necessary nodes with `append_canopy_nodes` (if the canopy is used).", - "This method should be used for rolluped creation of trees. The indexing of such rollups should be done off-chain. The programs calling this instruction should take care of ensuring the indexing is possible. For example, staking may be required to ensure the tree creator has some responsibility for what is being indexed. If indexing is not possible, there should be a mechanism to penalize the tree creator." + "Initializes a prepared tree with a root and a rightmost leaf. The rightmost leaf is used to", + "verify the canopy if the tree has it. Before calling this instruction, the tree should be", + "prepared with `prepare_tree` and the canopy should be filled with the necessary nodes with", + "`append_canopy_nodes` (if the canopy is used). This method should be used for rolluped", + "creation of trees. The indexing of such rollups should be done off-chain. The programs", + "calling this instruction should take care of ensuring the indexing is possible. For example,", + "staking may be required to ensure the tree creator has some responsibility for what is being", + "indexed. If indexing is not possible, there should be a mechanism to penalize the tree", + "creator." ], "accounts": [ { @@ -727,11 +745,16 @@ }, { "code": 6011, + "name": "BatchNotInitialized", + "msg": "Tree header was not initialized for batch processing" + }, + { + "code": 6012, "name": "CanopyRootMismatch", "msg": "Canopy root does not match the root of the tree" }, { - "code": 6012, + "code": 6013, "name": "CanopyRightmostLeafMismatch", "msg": "Canopy contains nodes to the right of the rightmost leaf of the tree" } diff --git a/account-compression/sdk/src/generated/errors/index.ts b/account-compression/sdk/src/generated/errors/index.ts index 02d025bd762..03066ebcf49 100644 --- a/account-compression/sdk/src/generated/errors/index.ts +++ b/account-compression/sdk/src/generated/errors/index.ts @@ -234,6 +234,26 @@ export class TreeAlreadyInitializedError extends Error { createErrorFromCodeLookup.set(0x177a, () => new TreeAlreadyInitializedError()); createErrorFromNameLookup.set('TreeAlreadyInitialized', () => new TreeAlreadyInitializedError()); +/** + * BatchNotInitialized: 'Tree header was not initialized for batch processing' + * + * @category Errors + * @category generated + */ +export class BatchNotInitializedError extends Error { + readonly code: number = 0x177b; + readonly name: string = 'BatchNotInitialized'; + constructor() { + super('Tree header was not initialized for batch processing'); + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(this, BatchNotInitializedError); + } + } +} + +createErrorFromCodeLookup.set(0x177b, () => new BatchNotInitializedError()); +createErrorFromNameLookup.set('BatchNotInitialized', () => new BatchNotInitializedError()); + /** * CanopyRootMismatch: 'Canopy root does not match the root of the tree' * @@ -241,7 +261,7 @@ createErrorFromNameLookup.set('TreeAlreadyInitialized', () => new TreeAlreadyIni * @category generated */ export class CanopyRootMismatchError extends Error { - readonly code: number = 0x177b; + readonly code: number = 0x177c; readonly name: string = 'CanopyRootMismatch'; constructor() { super('Canopy root does not match the root of the tree'); @@ -251,7 +271,7 @@ export class CanopyRootMismatchError extends Error { } } -createErrorFromCodeLookup.set(0x177b, () => new CanopyRootMismatchError()); +createErrorFromCodeLookup.set(0x177c, () => new CanopyRootMismatchError()); createErrorFromNameLookup.set('CanopyRootMismatch', () => new CanopyRootMismatchError()); /** @@ -261,7 +281,7 @@ createErrorFromNameLookup.set('CanopyRootMismatch', () => new CanopyRootMismatch * @category generated */ export class CanopyRightmostLeafMismatchError extends Error { - readonly code: number = 0x177c; + readonly code: number = 0x177d; readonly name: string = 'CanopyRightmostLeafMismatch'; constructor() { super('Canopy contains nodes to the right of the rightmost leaf of the tree'); @@ -271,7 +291,7 @@ export class CanopyRightmostLeafMismatchError extends Error { } } -createErrorFromCodeLookup.set(0x177c, () => new CanopyRightmostLeafMismatchError()); +createErrorFromCodeLookup.set(0x177d, () => new CanopyRightmostLeafMismatchError()); createErrorFromNameLookup.set('CanopyRightmostLeafMismatch', () => new CanopyRightmostLeafMismatchError()); /** diff --git a/account-compression/sdk/tests/accountCompression.test.ts b/account-compression/sdk/tests/accountCompression.test.ts index b7168c27a37..76df32b140d 100644 --- a/account-compression/sdk/tests/accountCompression.test.ts +++ b/account-compression/sdk/tests/accountCompression.test.ts @@ -11,6 +11,7 @@ import { createAppendCanopyNodesIx, createAppendIx, createCloseEmptyTreeInstruction, + createCloseEmptyTreeIx, createFinalizeMerkleTreeWithRootIx, createInitEmptyMerkleTreeIx, createReplaceIx, @@ -177,6 +178,27 @@ describe('Account Compression', () => { assert(false, 'Double finalizing should have failed'); } catch {} }); + + it('Should be able to close a prepared tree', async () => { + let payerInfo = await provider.connection.getAccountInfo(payer, 'confirmed')!; + let treeInfo = await provider.connection.getAccountInfo(cmt, 'confirmed')!; + + const payerLamports = payerInfo!.lamports; + const treeLamports = treeInfo!.lamports; + + const closeIx = createCloseEmptyTreeIx(cmt, payer, payer); + await execute(provider, [closeIx], [payerKeypair]); + + payerInfo = await provider.connection.getAccountInfo(payer, 'confirmed')!; + const finalLamports = payerInfo!.lamports; + assert( + finalLamports === payerLamports + treeLamports - 5000, + 'Expected payer to have received the lamports from the closed tree account' + ); + + treeInfo = await provider.connection.getAccountInfo(cmt, 'confirmed'); + assert(treeInfo === null, 'Expected the merkle tree account info to be null'); + }); }); describe('Having prepared a tree with canopy', () => { const depth = 3; @@ -474,6 +496,108 @@ describe('Account Compression', () => { assert(false, 'Initializing an empty tree after preparing a tree should have failed'); } catch {} }); + it('Should be able to close a prepared tree after setting the canopy', async () => { + const merkleTreeRaw = new MerkleTree(leaves); + + const appendIx = createAppendCanopyNodesIx( + cmt, + payer, + merkleTreeRaw.leaves + .slice(0, leaves.length / 2) + .filter((_, i) => i % 2 === 0) + .map(leaf => leaf.parent!.node!), + 0, + ); + await execute(provider, [appendIx], [payerKeypair]); + let payerInfo = await provider.connection.getAccountInfo(payer, 'confirmed')!; + let treeInfo = await provider.connection.getAccountInfo(cmt, 'confirmed')!; + + const payerLamports = payerInfo!.lamports; + const treeLamports = treeInfo!.lamports; + + const closeIx = createCloseEmptyTreeIx(cmt, payer, payer); + await execute(provider, [closeIx], [payerKeypair]); + + payerInfo = await provider.connection.getAccountInfo(payer, 'confirmed')!; + const finalLamports = payerInfo!.lamports; + assert( + finalLamports === payerLamports + treeLamports - 5000, + 'Expected payer to have received the lamports from the closed tree account' + ); + + treeInfo = await provider.connection.getAccountInfo(cmt, 'confirmed'); + assert(treeInfo === null, 'Expected the merkle tree account info to be null'); + }); + }); + describe('Having prepared an empty tree with canopy', () => { + const depth = 3; + const size = 8; + const canopyDepth = 2; + // empty leaves represent the empty tree + const leaves = [ + Buffer.alloc(32), + Buffer.alloc(32), + Buffer.alloc(32), + Buffer.alloc(32), + Buffer.alloc(32), + Buffer.alloc(32), + Buffer.alloc(32), + Buffer.alloc(32), + ]; + let anotherKeyPair: Keypair; + let another: PublicKey; + beforeEach(async () => { + const cmtKeypair = await prepareTree({ + canopyDepth, + depthSizePair: { + maxBufferSize: size, + maxDepth: depth, + }, + payer: payerKeypair, + provider, + }); + cmt = cmtKeypair.publicKey; + anotherKeyPair = Keypair.generate(); + another = anotherKeyPair.publicKey; + await provider.connection.confirmTransaction( + await provider.connection.requestAirdrop(another, 1e10), + 'confirmed', + ); + }); + + it('Should be able to finalize an empty tree with empty canopy and close it afterwards', async () => { + const merkleTreeRaw = new MerkleTree(leaves); + const root = merkleTreeRaw.root; + const leaf = leaves[leaves.length - 1]; + + const finalize = createFinalizeMerkleTreeWithRootIx( + cmt, + payer, + root, + leaf, + leaves.length - 1, + merkleTreeRaw.getProof(leaves.length - 1).proof, + ); + await execute(provider, [finalize], [payerKeypair]); + let payerInfo = await provider.connection.getAccountInfo(payer, 'confirmed')!; + let treeInfo = await provider.connection.getAccountInfo(cmt, 'confirmed')!; + + const payerLamports = payerInfo!.lamports; + const treeLamports = treeInfo!.lamports; + + const closeIx = createCloseEmptyTreeIx(cmt, payer, payer); + await execute(provider, [closeIx], [payerKeypair]); + + payerInfo = await provider.connection.getAccountInfo(payer, 'confirmed')!; + const finalLamports = payerInfo!.lamports; + assert( + finalLamports === payerLamports + treeLamports - 5000, + 'Expected payer to have received the lamports from the closed tree account' + ); + + treeInfo = await provider.connection.getAccountInfo(cmt, 'confirmed'); + assert(treeInfo === null, 'Expected the merkle tree account info to be null'); + }); }); describe('Having created a tree with a single leaf', () => { From 48a7b965896ff46a79c3d6bd2784bad648ef3936 Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Fri, 26 Jul 2024 13:02:43 +0100 Subject: [PATCH 18/39] adding more tests --- .../sdk/tests/accountCompression.test.ts | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/account-compression/sdk/tests/accountCompression.test.ts b/account-compression/sdk/tests/accountCompression.test.ts index 76df32b140d..f23c2367d5b 100644 --- a/account-compression/sdk/tests/accountCompression.test.ts +++ b/account-compression/sdk/tests/accountCompression.test.ts @@ -17,6 +17,7 @@ import { createReplaceIx, createTransferAuthorityIx, createVerifyLeafIx, + prepareTreeIx, ValidDepthSizePair, } from '../src'; import { hash, MerkleTree } from '../src/merkle-tree'; @@ -708,6 +709,30 @@ describe('Account Compression', () => { 'Updated on chain root matches root of updated off chain tree', ); }); + + it('Should fail to prepare a batch ready tree for an existing tree', async () => { + const prepareIx = prepareTreeIx(cmt, payer, DEPTH_SIZE_PAIR); + try { + await execute(provider, [prepareIx], [payerKeypair]); + assert(false, 'Prepare a batch tree should have failed for the existing tree'); + } catch {} + }); + + it('Should fail to finalize an existing tree', async () => { + const index = offChainTree.leaves.length - 1; + const finalizeIx = createFinalizeMerkleTreeWithRootIx( + cmt, + payer, + offChainTree.root, + offChainTree.leaves[index].node, + index, + offChainTree.getProof(index).proof, + ); + try { + await execute(provider, [finalizeIx], [payerKeypair]); + assert(false, 'Finalize an existing tree should have failed'); + } catch {} + }); }); describe('Examples transferring authority', () => { @@ -1022,6 +1047,22 @@ describe('Account Compression', () => { await execute(provider, [replaceIx, replaceBackIx], [payerKeypair], true, true); } }); + + it('Should fail to append a canopy node for an existing tree', async () => { + [cmtKeypair, offChainTree] = await createTreeOnChain( + provider, + payerKeypair, + 0, + { maxBufferSize: 8, maxDepth: DEPTH }, + DEPTH // Store full tree on chain + ); + cmt = cmtKeypair.publicKey; + const appendIx = createAppendCanopyNodesIx(cmt, payer, [crypto.randomBytes(32)], 0); + try { + await execute(provider, [appendIx], [payerKeypair]); + assert(false, 'Appending a canopy node for an existing tree should have failed'); + } catch {} + }); }); describe(`Having created a tree with 8 leaves`, () => { beforeEach(async () => { From 1ddb5ceecb544d2246b3041a483c77125a283000 Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Fri, 26 Jul 2024 14:51:52 +0100 Subject: [PATCH 19/39] refactored tree unitialized check on bytes directly, also moved tree initialization call in a wrapper as it started reporting a stack overflow --- .../src/concurrent_tree_wrapper.rs | 21 +++++++++++++++++++ .../programs/account-compression/src/lib.rs | 9 +++----- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/account-compression/programs/account-compression/src/concurrent_tree_wrapper.rs b/account-compression/programs/account-compression/src/concurrent_tree_wrapper.rs index 23c518c731f..ec53ba061c2 100644 --- a/account-compression/programs/account-compression/src/concurrent_tree_wrapper.rs +++ b/account-compression/programs/account-compression/src/concurrent_tree_wrapper.rs @@ -86,3 +86,24 @@ pub fn merkle_tree_append_leaf( ) -> Result> { merkle_tree_apply_fn_mut!(header, tree_id, tree_bytes, append, *args) } + +pub fn tree_bytes_unititialized(tree_bytes: &[u8]) -> bool { + tree_bytes.iter().all(|&x| x == 0) +} + +#[inline(never)] +pub fn assert_tree_is_empty( + header: &ConcurrentMerkleTreeHeader, + tree_id: Pubkey, + tree_bytes: &mut [u8], +) -> Result<()> { + // If the tree is batch initialized and not finalized yet, we can treat it as empty. + // Before the tree is finalized, the tree_bytes will be all 0 as only the header will be + // initialized at that point, so we may skip the deserialization. + if header.get_is_batch_initialized() && tree_bytes_unititialized(tree_bytes) { + return Ok(()); + } + // check the tree is empty + merkle_tree_apply_fn_mut!(header, tree_id, tree_bytes, prove_tree_is_empty,)?; + Ok(()) +} diff --git a/account-compression/programs/account-compression/src/lib.rs b/account-compression/programs/account-compression/src/lib.rs index f2a8dddd095..b7cd36585c5 100644 --- a/account-compression/programs/account-compression/src/lib.rs +++ b/account-compression/programs/account-compression/src/lib.rs @@ -267,7 +267,7 @@ pub mod spl_account_compression { let (tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size); // ensure the tree is not initialized, the hacky way require!( - tree_bytes.iter().all(|&x| x == 0), + tree_bytes_unititialized(tree_bytes), AccountCompressionError::TreeAlreadyInitialized ); set_canopy_leaf_nodes( @@ -567,11 +567,8 @@ pub mod spl_account_compression { let merkle_tree_size = merkle_tree_get_size(&header)?; let (tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size); - // Check if the tree is either empty or is batch initialized and not finalized yet. - if !header.get_is_batch_initialized() || !tree_bytes.iter().all(|&x| x == 0) { - let id = ctx.accounts.merkle_tree.key(); - merkle_tree_apply_fn_mut!(header, id, tree_bytes, prove_tree_is_empty,)?; - } + let id = ctx.accounts.merkle_tree.key(); + assert_tree_is_empty(&header, id, tree_bytes)?; // Close merkle tree account // 1. Move lamports From a93638356f4043bea6ee61585da78a0884e09953 Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Mon, 29 Jul 2024 16:56:41 +0100 Subject: [PATCH 20/39] typo fix + a docstring for tree_bytes_uninitialized + removed unused import --- .../account-compression/src/concurrent_tree_wrapper.rs | 10 ++++++++-- .../programs/account-compression/src/lib.rs | 3 +-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/account-compression/programs/account-compression/src/concurrent_tree_wrapper.rs b/account-compression/programs/account-compression/src/concurrent_tree_wrapper.rs index ec53ba061c2..c9cc0b6f8a2 100644 --- a/account-compression/programs/account-compression/src/concurrent_tree_wrapper.rs +++ b/account-compression/programs/account-compression/src/concurrent_tree_wrapper.rs @@ -87,7 +87,13 @@ pub fn merkle_tree_append_leaf( merkle_tree_apply_fn_mut!(header, tree_id, tree_bytes, append, *args) } -pub fn tree_bytes_unititialized(tree_bytes: &[u8]) -> bool { +/// Checks whether the tree in not initialized yet without doing the deserialization. A rought +/// equivalent to deserializing the tree and calling is_initialized() on it without the heavy +/// lifting with macros. An empty account is a zero'd account. The tree is considered empty if the +/// tree_bytes are all 0. A regular non-batch initialized tree is initialized early on when the +/// init_empty_merkle_tree is called. A batch initialized tree stays uninitialized until the +/// finalize_merkle_tree_with_root is called. +pub fn tree_bytes_uninitialized(tree_bytes: &[u8]) -> bool { tree_bytes.iter().all(|&x| x == 0) } @@ -100,7 +106,7 @@ pub fn assert_tree_is_empty( // If the tree is batch initialized and not finalized yet, we can treat it as empty. // Before the tree is finalized, the tree_bytes will be all 0 as only the header will be // initialized at that point, so we may skip the deserialization. - if header.get_is_batch_initialized() && tree_bytes_unititialized(tree_bytes) { + if header.get_is_batch_initialized() && tree_bytes_uninitialized(tree_bytes) { return Ok(()); } // check the tree is empty diff --git a/account-compression/programs/account-compression/src/lib.rs b/account-compression/programs/account-compression/src/lib.rs index b7cd36585c5..9cd2faab0b4 100644 --- a/account-compression/programs/account-compression/src/lib.rs +++ b/account-compression/programs/account-compression/src/lib.rs @@ -51,7 +51,6 @@ use crate::noop::wrap_event; use crate::state::{ merkle_tree_get_size, ConcurrentMerkleTreeHeader, CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1, }; -use crate::zero_copy::ZeroCopy; /// Exported for Anchor / Solita pub use spl_concurrent_merkle_tree::{ @@ -267,7 +266,7 @@ pub mod spl_account_compression { let (tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size); // ensure the tree is not initialized, the hacky way require!( - tree_bytes_unititialized(tree_bytes), + tree_bytes_uninitialized(tree_bytes), AccountCompressionError::TreeAlreadyInitialized ); set_canopy_leaf_nodes( From 26fb7c8c70165a324d2046d10222933f54a02d9b Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Mon, 29 Jul 2024 17:19:03 +0100 Subject: [PATCH 21/39] naming changes --- .../src/concurrent_tree_wrapper.rs | 2 +- .../programs/account-compression/src/lib.rs | 42 +++++++++---------- .../sdk/idl/spl_account_compression.json | 40 +++++++++--------- .../sdk/src/generated/instructions/index.ts | 4 +- ...ithRoot.ts => initPreparedTreeWithRoot.ts} | 34 +++++++-------- ...epareTree.ts => prepareBatchMerkleTree.ts} | 34 +++++++-------- .../sdk/src/instructions/index.ts | 14 +++---- .../sdk/tests/accountCompression.test.ts | 28 ++++++------- 8 files changed, 99 insertions(+), 99 deletions(-) rename account-compression/sdk/src/generated/instructions/{finalizeMerkleTreeWithRoot.ts => initPreparedTreeWithRoot.ts} (68%) rename account-compression/sdk/src/generated/instructions/{prepareTree.ts => prepareBatchMerkleTree.ts} (68%) diff --git a/account-compression/programs/account-compression/src/concurrent_tree_wrapper.rs b/account-compression/programs/account-compression/src/concurrent_tree_wrapper.rs index c9cc0b6f8a2..e954fea5c57 100644 --- a/account-compression/programs/account-compression/src/concurrent_tree_wrapper.rs +++ b/account-compression/programs/account-compression/src/concurrent_tree_wrapper.rs @@ -92,7 +92,7 @@ pub fn merkle_tree_append_leaf( /// lifting with macros. An empty account is a zero'd account. The tree is considered empty if the /// tree_bytes are all 0. A regular non-batch initialized tree is initialized early on when the /// init_empty_merkle_tree is called. A batch initialized tree stays uninitialized until the -/// finalize_merkle_tree_with_root is called. +/// init_prepared_tree_with_root is called. pub fn tree_bytes_uninitialized(tree_bytes: &[u8]) -> bool { tree_bytes.iter().all(|&x| x == 0) } diff --git a/account-compression/programs/account-compression/src/lib.rs b/account-compression/programs/account-compression/src/lib.rs index 9cd2faab0b4..a246a25798a 100644 --- a/account-compression/programs/account-compression/src/lib.rs +++ b/account-compression/programs/account-compression/src/lib.rs @@ -195,12 +195,12 @@ pub mod spl_account_compression { /// expected flow is `init_empty_merkle_tree`. For the latter case, the canopy should be /// filled with the necessary nodes to render the tree usable. Thus we need to prefill the /// canopy with the necessary nodes. The expected flow for a tree without canopy is - /// `prepare_tree` -> `finalize_merkle_tree_with_root`. The expected flow for a tree with canopy - /// is `prepare_tree` -> `append_canopy_nodes` (multiple times until all of the canopy is - /// filled) -> `finalize_merkle_tree_with_root`. This instruction initializes the tree header - /// while leaving the tree itself uninitialized. This allows distinguishing between an empty - /// tree and a tree prepare to be initialized with a root. - pub fn prepare_tree( + /// `prepare_batch_merkle_tree` -> `init_prepared_tree_with_root`. The expected flow for a tree + /// with canopy is `prepare_batch_merkle_tree` -> `append_canopy_nodes` (multiple times + /// until all of the canopy is filled) -> `init_prepared_tree_with_root`. This instruction + /// initializes the tree header while leaving the tree itself uninitialized. This allows + /// distinguishing between an empty tree and a tree prepare to be initialized with a root. + pub fn prepare_batch_merkle_tree( ctx: Context, max_depth: u32, max_buffer_size: u32, @@ -229,19 +229,19 @@ pub mod spl_account_compression { } /// This instruction pre-initializes the canopy with the specified leaf nodes of the canopy. - /// This is intended to be used after `prepare_tree` and in conjunction with the - /// `finalize_merkle_tree_with_root` instruction that'll finalize the tree initialization. + /// This is intended to be used after `prepare_batch_merkle_tree` and in conjunction with the + /// `init_prepared_tree_with_root` instruction that'll finalize the tree initialization. /// The canopy is used to cache the uppermost nodes of the tree, which allows for a smaller /// proof size when updating the tree. The canopy should be filled with the necessary nodes - /// before calling `finalize_merkle_tree_with_root`. You may call this instruction multiple + /// before calling `init_prepared_tree_with_root`. You may call this instruction multiple /// times to fill the canopy with the necessary nodes. The canopy may be filled with the /// nodes in any order. The already filled nodes may be replaced with new nodes before calling - /// `finalize_merkle_tree_with_root` if the step was done in error. + /// `init_prepared_tree_with_root` if the step was done in error. /// The canopy should be filled with all the nodes that are to the left of the rightmost - /// leaf of the tree before calling `finalize_merkle_tree_with_root`. The canopy should not + /// leaf of the tree before calling `init_prepared_tree_with_root`. The canopy should not /// contain any nodes to the right of the rightmost leaf of the tree. /// This instruction calculates and filles in all the canopy nodes "above" the provided ones. - /// The validation of the canopy is done in the `finalize_merkle_tree_with_root` instruction. + /// The validation of the canopy is done in the `init_prepared_tree_with_root` instruction. pub fn append_canopy_nodes( ctx: Context, start_index: u32, @@ -279,14 +279,14 @@ pub mod spl_account_compression { /// Initializes a prepared tree with a root and a rightmost leaf. The rightmost leaf is used to /// verify the canopy if the tree has it. Before calling this instruction, the tree should be - /// prepared with `prepare_tree` and the canopy should be filled with the necessary nodes with - /// `append_canopy_nodes` (if the canopy is used). This method should be used for rolluped - /// creation of trees. The indexing of such rollups should be done off-chain. The programs - /// calling this instruction should take care of ensuring the indexing is possible. For example, - /// staking may be required to ensure the tree creator has some responsibility for what is being - /// indexed. If indexing is not possible, there should be a mechanism to penalize the tree - /// creator. - pub fn finalize_merkle_tree_with_root( + /// prepared with `prepare_batch_merkle_tree` and the canopy should be filled with the necessary + /// nodes with `append_canopy_nodes` (if the canopy is used). This method should be used for + /// rolluped creation of trees. The indexing of such rollups should be done off-chain. The + /// programs calling this instruction should take care of ensuring the indexing is possible. + /// For example, staking may be required to ensure the tree creator has some responsibility + /// for what is being indexed. If indexing is not possible, there should be a mechanism to + /// penalize the tree creator. + pub fn init_prepared_tree_with_root( ctx: Context, root: [u8; 32], rightmost_leaf: [u8; 32], @@ -301,7 +301,7 @@ pub mod spl_account_compression { let (header_bytes, rest) = merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1); - // the header should already be initialized with prepare_tree + // the header should already be initialized with prepare_batch_merkle_tree let header = ConcurrentMerkleTreeHeader::try_from_slice(header_bytes)?; header.assert_valid_authority(&ctx.accounts.authority.key())?; header.assert_is_batch_initialized()?; diff --git a/account-compression/sdk/idl/spl_account_compression.json b/account-compression/sdk/idl/spl_account_compression.json index c0af69a2e0d..2a6ccf70195 100644 --- a/account-compression/sdk/idl/spl_account_compression.json +++ b/account-compression/sdk/idl/spl_account_compression.json @@ -52,7 +52,7 @@ ] }, { - "name": "prepareTree", + "name": "prepareBatchMerkleTree", "docs": [ "In order to initialize a tree with a root, we need to create the tree on-chain first with", "the proper authority. The tree might contain a canopy, which is a cache of the uppermost", @@ -65,11 +65,11 @@ "expected flow is `init_empty_merkle_tree`. For the latter case, the canopy should be", "filled with the necessary nodes to render the tree usable. Thus we need to prefill the", "canopy with the necessary nodes. The expected flow for a tree without canopy is", - "`prepare_tree` -> `finalize_merkle_tree_with_root`. The expected flow for a tree with canopy", - "is `prepare_tree` -> `append_canopy_nodes` (multiple times until all of the canopy is", - "filled) -> `finalize_merkle_tree_with_root`. This instruction initializes the tree header", - "while leaving the tree itself uninitialized. This allows distinguishing between an empty", - "tree and a tree prepare to be initialized with a root." + "`prepare_batch_merkle_tree` -> `init_prepared_tree_with_root`. The expected flow for a tree", + "with canopy is `prepare_batch_merkle_tree` -> `append_canopy_nodes` (multiple times", + "until all of the canopy is filled) -> `init_prepared_tree_with_root`. This instruction", + "initializes the tree header while leaving the tree itself uninitialized. This allows", + "distinguishing between an empty tree and a tree prepare to be initialized with a root." ], "accounts": [ { @@ -108,19 +108,19 @@ "name": "appendCanopyNodes", "docs": [ "This instruction pre-initializes the canopy with the specified leaf nodes of the canopy.", - "This is intended to be used after `prepare_tree` and in conjunction with the", - "`finalize_merkle_tree_with_root` instruction that'll finalize the tree initialization.", + "This is intended to be used after `prepare_batch_merkle_tree` and in conjunction with the", + "`init_prepared_tree_with_root` instruction that'll finalize the tree initialization.", "The canopy is used to cache the uppermost nodes of the tree, which allows for a smaller", "proof size when updating the tree. The canopy should be filled with the necessary nodes", - "before calling `finalize_merkle_tree_with_root`. You may call this instruction multiple", + "before calling `init_prepared_tree_with_root`. You may call this instruction multiple", "times to fill the canopy with the necessary nodes. The canopy may be filled with the", "nodes in any order. The already filled nodes may be replaced with new nodes before calling", - "`finalize_merkle_tree_with_root` if the step was done in error.", + "`init_prepared_tree_with_root` if the step was done in error.", "The canopy should be filled with all the nodes that are to the left of the rightmost", - "leaf of the tree before calling `finalize_merkle_tree_with_root`. The canopy should not", + "leaf of the tree before calling `init_prepared_tree_with_root`. The canopy should not", "contain any nodes to the right of the rightmost leaf of the tree.", "This instruction calculates and filles in all the canopy nodes \"above\" the provided ones.", - "The validation of the canopy is done in the `finalize_merkle_tree_with_root` instruction." + "The validation of the canopy is done in the `init_prepared_tree_with_root` instruction." ], "accounts": [ { @@ -160,17 +160,17 @@ ] }, { - "name": "finalizeMerkleTreeWithRoot", + "name": "initPreparedTreeWithRoot", "docs": [ "Initializes a prepared tree with a root and a rightmost leaf. The rightmost leaf is used to", "verify the canopy if the tree has it. Before calling this instruction, the tree should be", - "prepared with `prepare_tree` and the canopy should be filled with the necessary nodes with", - "`append_canopy_nodes` (if the canopy is used). This method should be used for rolluped", - "creation of trees. The indexing of such rollups should be done off-chain. The programs", - "calling this instruction should take care of ensuring the indexing is possible. For example,", - "staking may be required to ensure the tree creator has some responsibility for what is being", - "indexed. If indexing is not possible, there should be a mechanism to penalize the tree", - "creator." + "prepared with `prepare_batch_merkle_tree` and the canopy should be filled with the necessary", + "nodes with `append_canopy_nodes` (if the canopy is used). This method should be used for", + "rolluped creation of trees. The indexing of such rollups should be done off-chain. The", + "programs calling this instruction should take care of ensuring the indexing is possible.", + "For example, staking may be required to ensure the tree creator has some responsibility", + "for what is being indexed. If indexing is not possible, there should be a mechanism to", + "penalize the tree creator." ], "accounts": [ { diff --git a/account-compression/sdk/src/generated/instructions/index.ts b/account-compression/sdk/src/generated/instructions/index.ts index e291db12cf6..10605ab4704 100644 --- a/account-compression/sdk/src/generated/instructions/index.ts +++ b/account-compression/sdk/src/generated/instructions/index.ts @@ -1,10 +1,10 @@ export * from './append'; export * from './appendCanopyNodes'; export * from './closeEmptyTree'; -export * from './finalizeMerkleTreeWithRoot'; export * from './initEmptyMerkleTree'; +export * from './initPreparedTreeWithRoot'; export * from './insertOrAppend'; -export * from './prepareTree'; +export * from './prepareBatchMerkleTree'; export * from './replaceLeaf'; export * from './transferAuthority'; export * from './verifyLeaf'; diff --git a/account-compression/sdk/src/generated/instructions/finalizeMerkleTreeWithRoot.ts b/account-compression/sdk/src/generated/instructions/initPreparedTreeWithRoot.ts similarity index 68% rename from account-compression/sdk/src/generated/instructions/finalizeMerkleTreeWithRoot.ts rename to account-compression/sdk/src/generated/instructions/initPreparedTreeWithRoot.ts index 823c5e94d1a..b729a877a95 100644 --- a/account-compression/sdk/src/generated/instructions/finalizeMerkleTreeWithRoot.ts +++ b/account-compression/sdk/src/generated/instructions/initPreparedTreeWithRoot.ts @@ -10,21 +10,21 @@ import * as web3 from '@solana/web3.js'; /** * @category Instructions - * @category FinalizeMerkleTreeWithRoot + * @category InitPreparedTreeWithRoot * @category generated */ -export type FinalizeMerkleTreeWithRootInstructionArgs = { +export type InitPreparedTreeWithRootInstructionArgs = { rightmostIndex: number; rightmostLeaf: number[] /* size: 32 */; root: number[] /* size: 32 */; }; /** * @category Instructions - * @category FinalizeMerkleTreeWithRoot + * @category InitPreparedTreeWithRoot * @category generated */ -export const finalizeMerkleTreeWithRootStruct = new beet.BeetArgsStruct< - FinalizeMerkleTreeWithRootInstructionArgs & { +export const initPreparedTreeWithRootStruct = new beet.BeetArgsStruct< + InitPreparedTreeWithRootInstructionArgs & { instructionDiscriminator: number[] /* size: 8 */; } >( @@ -34,44 +34,44 @@ export const finalizeMerkleTreeWithRootStruct = new beet.BeetArgsStruct< ['rightmostLeaf', beet.uniformFixedSizeArray(beet.u8, 32)], ['rightmostIndex', beet.u32], ], - 'FinalizeMerkleTreeWithRootInstructionArgs', + 'InitPreparedTreeWithRootInstructionArgs', ); /** - * Accounts required by the _finalizeMerkleTreeWithRoot_ instruction + * Accounts required by the _initPreparedTreeWithRoot_ instruction * * @property [_writable_] merkleTree * @property [**signer**] authority * @property [] noop * @category Instructions - * @category FinalizeMerkleTreeWithRoot + * @category InitPreparedTreeWithRoot * @category generated */ -export type FinalizeMerkleTreeWithRootInstructionAccounts = { +export type InitPreparedTreeWithRootInstructionAccounts = { anchorRemainingAccounts?: web3.AccountMeta[]; authority: web3.PublicKey; merkleTree: web3.PublicKey; noop: web3.PublicKey; }; -export const finalizeMerkleTreeWithRootInstructionDiscriminator = [112, 137, 139, 87, 67, 99, 164, 82]; +export const initPreparedTreeWithRootInstructionDiscriminator = [218, 248, 192, 55, 91, 205, 122, 10]; /** - * Creates a _FinalizeMerkleTreeWithRoot_ instruction. + * Creates a _InitPreparedTreeWithRoot_ instruction. * * @param accounts that will be accessed while the instruction is processed * @param args to provide as instruction data to the program * * @category Instructions - * @category FinalizeMerkleTreeWithRoot + * @category InitPreparedTreeWithRoot * @category generated */ -export function createFinalizeMerkleTreeWithRootInstruction( - accounts: FinalizeMerkleTreeWithRootInstructionAccounts, - args: FinalizeMerkleTreeWithRootInstructionArgs, +export function createInitPreparedTreeWithRootInstruction( + accounts: InitPreparedTreeWithRootInstructionAccounts, + args: InitPreparedTreeWithRootInstructionArgs, programId = new web3.PublicKey('cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK'), ) { - const [data] = finalizeMerkleTreeWithRootStruct.serialize({ - instructionDiscriminator: finalizeMerkleTreeWithRootInstructionDiscriminator, + const [data] = initPreparedTreeWithRootStruct.serialize({ + instructionDiscriminator: initPreparedTreeWithRootInstructionDiscriminator, ...args, }); const keys: web3.AccountMeta[] = [ diff --git a/account-compression/sdk/src/generated/instructions/prepareTree.ts b/account-compression/sdk/src/generated/instructions/prepareBatchMerkleTree.ts similarity index 68% rename from account-compression/sdk/src/generated/instructions/prepareTree.ts rename to account-compression/sdk/src/generated/instructions/prepareBatchMerkleTree.ts index 7e832a8ee15..0d5aa007db4 100644 --- a/account-compression/sdk/src/generated/instructions/prepareTree.ts +++ b/account-compression/sdk/src/generated/instructions/prepareBatchMerkleTree.ts @@ -10,20 +10,20 @@ import * as web3 from '@solana/web3.js'; /** * @category Instructions - * @category PrepareTree + * @category PrepareBatchMerkleTree * @category generated */ -export type PrepareTreeInstructionArgs = { +export type PrepareBatchMerkleTreeInstructionArgs = { maxBufferSize: number; maxDepth: number; }; /** * @category Instructions - * @category PrepareTree + * @category PrepareBatchMerkleTree * @category generated */ -export const prepareTreeStruct = new beet.BeetArgsStruct< - PrepareTreeInstructionArgs & { +export const prepareBatchMerkleTreeStruct = new beet.BeetArgsStruct< + PrepareBatchMerkleTreeInstructionArgs & { instructionDiscriminator: number[] /* size: 8 */; } >( @@ -32,44 +32,44 @@ export const prepareTreeStruct = new beet.BeetArgsStruct< ['maxDepth', beet.u32], ['maxBufferSize', beet.u32], ], - 'PrepareTreeInstructionArgs', + 'PrepareBatchMerkleTreeInstructionArgs', ); /** - * Accounts required by the _prepareTree_ instruction + * Accounts required by the _prepareBatchMerkleTree_ instruction * * @property [_writable_] merkleTree * @property [**signer**] authority * @property [] noop * @category Instructions - * @category PrepareTree + * @category PrepareBatchMerkleTree * @category generated */ -export type PrepareTreeInstructionAccounts = { +export type PrepareBatchMerkleTreeInstructionAccounts = { anchorRemainingAccounts?: web3.AccountMeta[]; authority: web3.PublicKey; merkleTree: web3.PublicKey; noop: web3.PublicKey; }; -export const prepareTreeInstructionDiscriminator = [41, 56, 189, 77, 58, 12, 142, 71]; +export const prepareBatchMerkleTreeInstructionDiscriminator = [230, 124, 120, 196, 249, 134, 199, 128]; /** - * Creates a _PrepareTree_ instruction. + * Creates a _PrepareBatchMerkleTree_ instruction. * * @param accounts that will be accessed while the instruction is processed * @param args to provide as instruction data to the program * * @category Instructions - * @category PrepareTree + * @category PrepareBatchMerkleTree * @category generated */ -export function createPrepareTreeInstruction( - accounts: PrepareTreeInstructionAccounts, - args: PrepareTreeInstructionArgs, +export function createPrepareBatchMerkleTreeInstruction( + accounts: PrepareBatchMerkleTreeInstructionAccounts, + args: PrepareBatchMerkleTreeInstructionArgs, programId = new web3.PublicKey('cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK'), ) { - const [data] = prepareTreeStruct.serialize({ - instructionDiscriminator: prepareTreeInstructionDiscriminator, + const [data] = prepareBatchMerkleTreeStruct.serialize({ + instructionDiscriminator: prepareBatchMerkleTreeInstructionDiscriminator, ...args, }); const keys: web3.AccountMeta[] = [ diff --git a/account-compression/sdk/src/instructions/index.ts b/account-compression/sdk/src/instructions/index.ts index cd7ff4351dd..6c78142bdb0 100644 --- a/account-compression/sdk/src/instructions/index.ts +++ b/account-compression/sdk/src/instructions/index.ts @@ -6,9 +6,9 @@ import { createAppendCanopyNodesInstruction, createAppendInstruction, createCloseEmptyTreeInstruction, - createFinalizeMerkleTreeWithRootInstruction, + createInitPreparedTreeWithRootInstruction, createInitEmptyMerkleTreeInstruction, - createPrepareTreeInstruction, + createPrepareBatchMerkleTreeInstruction, createReplaceLeafInstruction, createTransferAuthorityInstruction, createVerifyLeafInstruction, @@ -57,7 +57,7 @@ export function createInitEmptyMerkleTreeIx( } /** - * Helper function for {@link createPrepareTreeInstruction} + * Helper function for {@link createPrepareBatchMerkleTreeInstruction} * @param merkleTree * @param authority * @param depthSizePair @@ -68,7 +68,7 @@ export function prepareTreeIx( authority: PublicKey, depthSizePair: ValidDepthSizePair, ): TransactionInstruction { - return createPrepareTreeInstruction( + return createPrepareBatchMerkleTreeInstruction( { authority: authority, merkleTree, @@ -106,7 +106,7 @@ export function createAppendCanopyNodesIx( } /** - * Helper function for {@link createFinalizeMerkleTreeWithRootInstruction} + * Helper function for {@link createInitPreparedTreeWithRootInstruction} * @param merkleTree * @param authority * @param root @@ -115,7 +115,7 @@ export function createAppendCanopyNodesIx( * @param proof * @returns */ -export function createFinalizeMerkleTreeWithRootIx( +export function createInitPreparedTreeWithRootIx( merkleTree: PublicKey, authority: PublicKey, root: ArrayLike | Buffer, @@ -123,7 +123,7 @@ export function createFinalizeMerkleTreeWithRootIx( rightmostIndex: number, proof: Buffer[], ): TransactionInstruction { - return createFinalizeMerkleTreeWithRootInstruction( + return createInitPreparedTreeWithRootInstruction( { anchorRemainingAccounts: proof.map(node => { return { diff --git a/account-compression/sdk/tests/accountCompression.test.ts b/account-compression/sdk/tests/accountCompression.test.ts index f23c2367d5b..8fa21862970 100644 --- a/account-compression/sdk/tests/accountCompression.test.ts +++ b/account-compression/sdk/tests/accountCompression.test.ts @@ -12,7 +12,7 @@ import { createAppendIx, createCloseEmptyTreeInstruction, createCloseEmptyTreeIx, - createFinalizeMerkleTreeWithRootIx, + createInitPreparedTreeWithRootIx, createInitEmptyMerkleTreeIx, createReplaceIx, createTransferAuthorityIx, @@ -100,7 +100,7 @@ describe('Account Compression', () => { const root = merkleTreeRaw.root; const leaf = leaves[leaves.length - 1]; const canopyDepth = 0; - const finalize = createFinalizeMerkleTreeWithRootIx( + const finalize = createInitPreparedTreeWithRootIx( cmt, payer, root, @@ -127,7 +127,7 @@ describe('Account Compression', () => { const root = merkleTreeRaw.root; const leaf = leaves[leaves.length - 1]; - const finalize = createFinalizeMerkleTreeWithRootIx( + const finalize = createInitPreparedTreeWithRootIx( cmt, another, root, @@ -151,7 +151,7 @@ describe('Account Compression', () => { return crypto.randomBytes(32); }); - const finalize = createFinalizeMerkleTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, proof.proof); + const finalize = createInitPreparedTreeWithRootIx(cmt, payer, root, leaf, leaves.length - 1, proof.proof); try { await execute(provider, [finalize], [payerKeypair]); @@ -163,7 +163,7 @@ describe('Account Compression', () => { const root = merkleTreeRaw.root; const leaf = leaves[leaves.length - 1]; - const finalize = createFinalizeMerkleTreeWithRootIx( + const finalize = createInitPreparedTreeWithRootIx( cmt, payer, root, @@ -290,7 +290,7 @@ describe('Account Compression', () => { const root = merkleTreeRaw.root; const leaf = leaves[leaves.length - 1]; - const finalize = createFinalizeMerkleTreeWithRootIx( + const finalize = createInitPreparedTreeWithRootIx( cmt, payer, root, @@ -311,7 +311,7 @@ describe('Account Compression', () => { const appendIx = createAppendCanopyNodesIx(cmt, payer, [merkleTreeRaw.leaves[0].parent!.node!], 0); await execute(provider, [appendIx], [payerKeypair]); - const finalize = createFinalizeMerkleTreeWithRootIx( + const finalize = createInitPreparedTreeWithRootIx( cmt, payer, root, @@ -338,7 +338,7 @@ describe('Account Compression', () => { 0, ); await execute(provider, [appendIx], [payerKeypair]); - const finalize = createFinalizeMerkleTreeWithRootIx( + const finalize = createInitPreparedTreeWithRootIx( cmt, payer, root, @@ -376,7 +376,7 @@ describe('Account Compression', () => { 2, ); await execute(provider, [appendIx2], [payerKeypair]); - const finalize = createFinalizeMerkleTreeWithRootIx( + const finalize = createInitPreparedTreeWithRootIx( cmt, payer, root, @@ -411,7 +411,7 @@ describe('Account Compression', () => { 0, ); await execute(provider, [appendIx2], [payerKeypair]); - const finalize = createFinalizeMerkleTreeWithRootIx( + const finalize = createInitPreparedTreeWithRootIx( cmt, payer, root, @@ -448,7 +448,7 @@ describe('Account Compression', () => { 2, ); await execute(provider, [replaceIx], [payerKeypair]); - const finalize = createFinalizeMerkleTreeWithRootIx( + const finalize = createInitPreparedTreeWithRootIx( cmt, payer, root, @@ -470,7 +470,7 @@ describe('Account Compression', () => { 0, ); await execute(provider, [appendIx], [payerKeypair]); - const finalize = createFinalizeMerkleTreeWithRootIx( + const finalize = createInitPreparedTreeWithRootIx( cmt, payer, root, @@ -571,7 +571,7 @@ describe('Account Compression', () => { const root = merkleTreeRaw.root; const leaf = leaves[leaves.length - 1]; - const finalize = createFinalizeMerkleTreeWithRootIx( + const finalize = createInitPreparedTreeWithRootIx( cmt, payer, root, @@ -720,7 +720,7 @@ describe('Account Compression', () => { it('Should fail to finalize an existing tree', async () => { const index = offChainTree.leaves.length - 1; - const finalizeIx = createFinalizeMerkleTreeWithRootIx( + const finalizeIx = createInitPreparedTreeWithRootIx( cmt, payer, offChainTree.root, From 29ec0136a85b1db3b6efc5b42ba0d6f48c395ecf Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Mon, 29 Jul 2024 17:45:01 +0100 Subject: [PATCH 22/39] lint the ts --- account-compression/sdk/src/instructions/index.ts | 2 +- account-compression/sdk/tests/accountCompression.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/account-compression/sdk/src/instructions/index.ts b/account-compression/sdk/src/instructions/index.ts index 6c78142bdb0..e7d80eb5f5e 100644 --- a/account-compression/sdk/src/instructions/index.ts +++ b/account-compression/sdk/src/instructions/index.ts @@ -6,8 +6,8 @@ import { createAppendCanopyNodesInstruction, createAppendInstruction, createCloseEmptyTreeInstruction, - createInitPreparedTreeWithRootInstruction, createInitEmptyMerkleTreeInstruction, + createInitPreparedTreeWithRootInstruction, createPrepareBatchMerkleTreeInstruction, createReplaceLeafInstruction, createTransferAuthorityInstruction, diff --git a/account-compression/sdk/tests/accountCompression.test.ts b/account-compression/sdk/tests/accountCompression.test.ts index 8fa21862970..02e77cfdab6 100644 --- a/account-compression/sdk/tests/accountCompression.test.ts +++ b/account-compression/sdk/tests/accountCompression.test.ts @@ -12,8 +12,8 @@ import { createAppendIx, createCloseEmptyTreeInstruction, createCloseEmptyTreeIx, - createInitPreparedTreeWithRootIx, createInitEmptyMerkleTreeIx, + createInitPreparedTreeWithRootIx, createReplaceIx, createTransferAuthorityIx, createVerifyLeafIx, From 13ba21d3d1781f86e502d18aa19384b3ae60fa68 Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Tue, 30 Jul 2024 11:30:02 +0100 Subject: [PATCH 23/39] minor naming adjustment in comments --- account-compression/programs/account-compression/src/lib.rs | 2 +- account-compression/sdk/idl/spl_account_compression.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/account-compression/programs/account-compression/src/lib.rs b/account-compression/programs/account-compression/src/lib.rs index a246a25798a..342ef7287d2 100644 --- a/account-compression/programs/account-compression/src/lib.rs +++ b/account-compression/programs/account-compression/src/lib.rs @@ -281,7 +281,7 @@ pub mod spl_account_compression { /// verify the canopy if the tree has it. Before calling this instruction, the tree should be /// prepared with `prepare_batch_merkle_tree` and the canopy should be filled with the necessary /// nodes with `append_canopy_nodes` (if the canopy is used). This method should be used for - /// rolluped creation of trees. The indexing of such rollups should be done off-chain. The + /// batch creation of trees. The indexing of such batches should be done off-chain. The /// programs calling this instruction should take care of ensuring the indexing is possible. /// For example, staking may be required to ensure the tree creator has some responsibility /// for what is being indexed. If indexing is not possible, there should be a mechanism to diff --git a/account-compression/sdk/idl/spl_account_compression.json b/account-compression/sdk/idl/spl_account_compression.json index 2a6ccf70195..fc0825eed53 100644 --- a/account-compression/sdk/idl/spl_account_compression.json +++ b/account-compression/sdk/idl/spl_account_compression.json @@ -166,7 +166,7 @@ "verify the canopy if the tree has it. Before calling this instruction, the tree should be", "prepared with `prepare_batch_merkle_tree` and the canopy should be filled with the necessary", "nodes with `append_canopy_nodes` (if the canopy is used). This method should be used for", - "rolluped creation of trees. The indexing of such rollups should be done off-chain. The", + "batch creation of trees. The indexing of such batches should be done off-chain. The", "programs calling this instruction should take care of ensuring the indexing is possible.", "For example, staking may be required to ensure the tree creator has some responsibility", "for what is being indexed. If indexing is not possible, there should be a mechanism to", From 7cda6c427f422059b2b65d00854021175de74459 Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Thu, 22 Aug 2024 15:59:02 +0100 Subject: [PATCH 24/39] fix formatting, update lock file and generated idl versions --- account-compression/Cargo.lock | 6 +++--- .../sdk/idl/spl_account_compression.json | 2 +- .../sdk/tests/accountCompression.test.ts | 20 +++++++++---------- .../concurrentMerkleTreeAccount.test.ts | 20 +++++++++++-------- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/account-compression/Cargo.lock b/account-compression/Cargo.lock index c5764a640be..495e39b6904 100644 --- a/account-compression/Cargo.lock +++ b/account-compression/Cargo.lock @@ -544,9 +544,9 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.16.1" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" +checksum = "6fd4c6dcc3b0aea2f5c0b4b82c2b15fe39ddbc76041a310848f4706edf76bb31" dependencies = [ "bytemuck_derive", ] @@ -1563,7 +1563,7 @@ dependencies = [ [[package]] name = "spl-account-compression" -version = "0.3.0" +version = "0.3.1" dependencies = [ "anchor-lang", "bytemuck", diff --git a/account-compression/sdk/idl/spl_account_compression.json b/account-compression/sdk/idl/spl_account_compression.json index fc0825eed53..a9477f25eca 100644 --- a/account-compression/sdk/idl/spl_account_compression.json +++ b/account-compression/sdk/idl/spl_account_compression.json @@ -1,5 +1,5 @@ { - "version": "0.3.0", + "version": "0.3.1", "name": "spl_account_compression", "instructions": [ { diff --git a/account-compression/sdk/tests/accountCompression.test.ts b/account-compression/sdk/tests/accountCompression.test.ts index 02e77cfdab6..deb7a2e3173 100644 --- a/account-compression/sdk/tests/accountCompression.test.ts +++ b/account-compression/sdk/tests/accountCompression.test.ts @@ -179,7 +179,7 @@ describe('Account Compression', () => { assert(false, 'Double finalizing should have failed'); } catch {} }); - + it('Should be able to close a prepared tree', async () => { let payerInfo = await provider.connection.getAccountInfo(payer, 'confirmed')!; let treeInfo = await provider.connection.getAccountInfo(cmt, 'confirmed')!; @@ -189,12 +189,12 @@ describe('Account Compression', () => { const closeIx = createCloseEmptyTreeIx(cmt, payer, payer); await execute(provider, [closeIx], [payerKeypair]); - + payerInfo = await provider.connection.getAccountInfo(payer, 'confirmed')!; const finalLamports = payerInfo!.lamports; assert( finalLamports === payerLamports + treeLamports - 5000, - 'Expected payer to have received the lamports from the closed tree account' + 'Expected payer to have received the lamports from the closed tree account', ); treeInfo = await provider.connection.getAccountInfo(cmt, 'confirmed'); @@ -499,7 +499,7 @@ describe('Account Compression', () => { }); it('Should be able to close a prepared tree after setting the canopy', async () => { const merkleTreeRaw = new MerkleTree(leaves); - + const appendIx = createAppendCanopyNodesIx( cmt, payer, @@ -518,12 +518,12 @@ describe('Account Compression', () => { const closeIx = createCloseEmptyTreeIx(cmt, payer, payer); await execute(provider, [closeIx], [payerKeypair]); - + payerInfo = await provider.connection.getAccountInfo(payer, 'confirmed')!; const finalLamports = payerInfo!.lamports; assert( finalLamports === payerLamports + treeLamports - 5000, - 'Expected payer to have received the lamports from the closed tree account' + 'Expected payer to have received the lamports from the closed tree account', ); treeInfo = await provider.connection.getAccountInfo(cmt, 'confirmed'); @@ -565,7 +565,7 @@ describe('Account Compression', () => { 'confirmed', ); }); - + it('Should be able to finalize an empty tree with empty canopy and close it afterwards', async () => { const merkleTreeRaw = new MerkleTree(leaves); const root = merkleTreeRaw.root; @@ -588,12 +588,12 @@ describe('Account Compression', () => { const closeIx = createCloseEmptyTreeIx(cmt, payer, payer); await execute(provider, [closeIx], [payerKeypair]); - + payerInfo = await provider.connection.getAccountInfo(payer, 'confirmed')!; const finalLamports = payerInfo!.lamports; assert( finalLamports === payerLamports + treeLamports - 5000, - 'Expected payer to have received the lamports from the closed tree account' + 'Expected payer to have received the lamports from the closed tree account', ); treeInfo = await provider.connection.getAccountInfo(cmt, 'confirmed'); @@ -1054,7 +1054,7 @@ describe('Account Compression', () => { payerKeypair, 0, { maxBufferSize: 8, maxDepth: DEPTH }, - DEPTH // Store full tree on chain + DEPTH, // Store full tree on chain ); cmt = cmtKeypair.publicKey; const appendIx = createAppendCanopyNodesIx(cmt, payer, [crypto.randomBytes(32)], 0); diff --git a/account-compression/sdk/tests/accounts/concurrentMerkleTreeAccount.test.ts b/account-compression/sdk/tests/accounts/concurrentMerkleTreeAccount.test.ts index c3dcbcb85c9..5e85aaca0c9 100644 --- a/account-compression/sdk/tests/accounts/concurrentMerkleTreeAccount.test.ts +++ b/account-compression/sdk/tests/accounts/concurrentMerkleTreeAccount.test.ts @@ -35,7 +35,7 @@ export function assertCMTProperties( } assert( onChainCMT.getIsBatchInitialized() === expectedIsBatchInitialized, - 'On chain isBatchInitialized does not match expected value' + 'On chain isBatchInitialized does not match expected value', ); } @@ -153,20 +153,24 @@ describe('ConcurrentMerkleTreeAccount tests', () => { // The account data was generated by running: // $ solana account 27QMkDMpBoAhmWj6xxQNYdqXZL5nnC8tkZcEtkNxCqeX \ // --output-file tests/fixtures/pre-batch-init-tree-account.json \ - // --output json + // --output json const deployedAccount = new PublicKey('27QMkDMpBoAhmWj6xxQNYdqXZL5nnC8tkZcEtkNxCqeX'); - const cmt = await ConcurrentMerkleTreeAccount.fromAccountAddress( - connection, - deployedAccount, - 'confirmed' - ); + const cmt = await ConcurrentMerkleTreeAccount.fromAccountAddress(connection, deployedAccount, 'confirmed'); const expectedMaxDepth = 10; const expectedMaxBufferSize = 32; const expectedCanopyDepth = 0; const expectedAuthority = new PublicKey('BFNT941iRwYPe2Js64dTJSoksGCptWAwrkKMaSN73XK2'); const expectedRoot = new PublicKey('83UjseEuEgxyVyDTmrJCQ9QbeksdRZ7KPDZGQYc5cAgF').toBuffer(); const expectedIsBatchInitialized = false; - await assertCMTProperties(cmt, expectedMaxDepth, expectedMaxBufferSize, expectedAuthority, expectedRoot, expectedCanopyDepth, expectedIsBatchInitialized); + await assertCMTProperties( + cmt, + expectedMaxDepth, + expectedMaxBufferSize, + expectedAuthority, + expectedRoot, + expectedCanopyDepth, + expectedIsBatchInitialized, + ); }); }); }); From 6a3c9cb3a2e94b442ccd309d1177137fb0f964e6 Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Wed, 11 Sep 2024 22:43:25 +0100 Subject: [PATCH 25/39] using node 20.5 for 2 workflows that are failing bumped the node version to the latest used in some other flows trying to narrow down the build issue --- .github/workflows/pull-request-account-compression.yml | 2 +- .github/workflows/pull-request-libraries.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull-request-account-compression.yml b/.github/workflows/pull-request-account-compression.yml index 9504a1bcc46..a3e936f8c8a 100644 --- a/.github/workflows/pull-request-account-compression.yml +++ b/.github/workflows/pull-request-account-compression.yml @@ -79,7 +79,7 @@ jobs: js-test-account-compression: runs-on: ubuntu-latest env: - NODE_VERSION: 16.x + NODE_VERSION: 20.5 steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ env.NODE_VERSION }} diff --git a/.github/workflows/pull-request-libraries.yml b/.github/workflows/pull-request-libraries.yml index 18ca39623a9..2b4eeef1e33 100644 --- a/.github/workflows/pull-request-libraries.yml +++ b/.github/workflows/pull-request-libraries.yml @@ -66,7 +66,7 @@ jobs: js-test: runs-on: ubuntu-latest env: - NODE_VERSION: 16.x + NODE_VERSION: 20.5 steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ env.NODE_VERSION }} From 984299293a02d95414006ef1ebeb965e4095b12a Mon Sep 17 00:00:00 2001 From: tonton <19677766+tonton-sol@users.noreply.github.com> Date: Thu, 12 Sep 2024 15:39:31 +0900 Subject: [PATCH 26/39] [token-cli] Add support for transfer-hook account resolution for transfers with the transfer-fee extension. (#7171) * Added transfer_fee extension * added create_transfer_checked_with_fee_instruction_with_extra_metas * add support for transfer-hook account resolution in transfer_with_fee * add offchain helper `invoke_transfer_checked_with_fee` * Nit: Added better description to function * add test for offchain helper `create_transfer_checked_with_fee_instruction_with_extra_metas` * add `success_transfer_with_fee` test * add test `success_transfers_with_fee_using_onchain_helper` * Add cli test `transfer_hook_with_transfer_fee` * fix: correctly use the new onchain helper in test * Remove unneeded helpers --- token/cli/tests/command.rs | 102 ++++++ token/client/src/token.rs | 37 +- .../program-2022-test/tests/transfer_hook.rs | 332 ++++++++++++++++++ token/program-2022/src/offchain.rs | 167 ++++++++- token/program-2022/src/onchain.rs | 70 +++- 5 files changed, 699 insertions(+), 9 deletions(-) diff --git a/token/cli/tests/command.rs b/token/cli/tests/command.rs index e13ce549c09..1f8f41e1c6d 100644 --- a/token/cli/tests/command.rs +++ b/token/cli/tests/command.rs @@ -139,6 +139,7 @@ async fn main() { async_trial!(group_pointer, test_validator, payer), async_trial!(group_member_pointer, test_validator, payer), async_trial!(transfer_hook, test_validator, payer), + async_trial!(transfer_hook_with_transfer_fee, test_validator, payer), async_trial!(metadata, test_validator, payer), async_trial!(group, test_validator, payer), async_trial!(confidential_transfer_with_fee, test_validator, payer), @@ -3872,6 +3873,107 @@ async fn transfer_hook(test_validator: &TestValidator, payer: &Keypair) { assert_eq!(extension.program_id, None.try_into().unwrap()); } +async fn transfer_hook_with_transfer_fee(test_validator: &TestValidator, payer: &Keypair) { + let program_id = spl_token_2022::id(); + let mut config = test_config_with_default_signer(test_validator, payer, &program_id); + let transfer_hook_program_id = Pubkey::new_unique(); + + let transfer_fee_basis_points = 100; + let maximum_fee: u64 = 10_000_000_000; + + let result = process_test_command( + &config, + payer, + &[ + "spl-token", + CommandName::CreateToken.into(), + "--program-id", + &program_id.to_string(), + "--transfer-hook", + &transfer_hook_program_id.to_string(), + "--transfer-fee", + &transfer_fee_basis_points.to_string(), + &maximum_fee.to_string(), + ], + ) + .await; + + // Check that the transfer-hook extension is correctly configured + let value: serde_json::Value = serde_json::from_str(&result.unwrap()).unwrap(); + let mint = Pubkey::from_str(value["commandOutput"]["address"].as_str().unwrap()).unwrap(); + let account = config.rpc_client.get_account(&mint).await.unwrap(); + let mint_state = StateWithExtensionsOwned::::unpack(account.data).unwrap(); + let extension = mint_state.get_extension::().unwrap(); + assert_eq!( + extension.program_id, + Some(transfer_hook_program_id).try_into().unwrap() + ); + + // Check that the transfer-fee extension is correctly configured + let extension = mint_state.get_extension::().unwrap(); + assert_eq!( + u16::from(extension.older_transfer_fee.transfer_fee_basis_points), + transfer_fee_basis_points + ); + assert_eq!( + u64::from(extension.older_transfer_fee.maximum_fee), + maximum_fee + ); + assert_eq!( + u16::from(extension.newer_transfer_fee.transfer_fee_basis_points), + transfer_fee_basis_points + ); + assert_eq!( + u64::from(extension.newer_transfer_fee.maximum_fee), + maximum_fee + ); + + // Make sure that parsing transfer hook accounts and expected-fee works + let blockhash = Hash::default(); + let program_client: Arc> = Arc::new( + ProgramOfflineClient::new(blockhash, ProgramRpcClientSendTransaction), + ); + config.program_client = program_client; + + let _result = exec_test_cmd( + &config, + &[ + "spl-token", + CommandName::Transfer.into(), + &mint.to_string(), + "100", + &Pubkey::new_unique().to_string(), + "--blockhash", + &blockhash.to_string(), + "--nonce", + &Pubkey::new_unique().to_string(), + "--nonce-authority", + &Pubkey::new_unique().to_string(), + "--sign-only", + "--mint-decimals", + &format!("{}", TEST_DECIMALS), + "--from", + &Pubkey::new_unique().to_string(), + "--owner", + &Pubkey::new_unique().to_string(), + "--transfer-hook-account", + &format!("{}:readonly", Pubkey::new_unique()), + "--transfer-hook-account", + &format!("{}:writable", Pubkey::new_unique()), + "--transfer-hook-account", + &format!("{}:readonly-signer", Pubkey::new_unique()), + "--transfer-hook-account", + &format!("{}:writable-signer", Pubkey::new_unique()), + "--expected-fee", + "1", + "--program-id", + &program_id.to_string(), + ], + ) + .await + .unwrap(); +} + async fn metadata(test_validator: &TestValidator, payer: &Keypair) { let program_id = spl_token_2022::id(); let config = test_config_with_default_signer(test_validator, payer, &program_id); diff --git a/token/client/src/token.rs b/token/client/src/token.rs index 33352f62c27..165616a645f 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -1166,21 +1166,44 @@ where let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys); let decimals = self.decimals.ok_or(TokenError::MissingDecimals)?; - self.process_ixs( - &[transfer_fee::instruction::transfer_checked_with_fee( + let fetch_account_data_fn = |address| { + self.client + .get_account(address) + .map_ok(|opt| opt.map(|acc| acc.data)) + }; + + let instruction = if let Some(transfer_hook_accounts) = &self.transfer_hook_accounts { + let mut instruction = transfer_fee::instruction::transfer_checked_with_fee( &self.program_id, source, - &self.pubkey, + self.get_address(), destination, authority, &multisig_signers, amount, decimals, fee, - )?], - signing_keypairs, - ) - .await + )?; + instruction.accounts.extend(transfer_hook_accounts.clone()); + instruction + } else { + offchain::create_transfer_checked_with_fee_instruction_with_extra_metas( + &self.program_id, + source, + self.get_address(), + destination, + authority, + &multisig_signers, + amount, + decimals, + fee, + fetch_account_data_fn, + ) + .await + .map_err(|_| TokenError::AccountNotFound)? + }; + + self.process_ixs(&[instruction], signing_keypairs).await } /// Burn tokens from account diff --git a/token/program-2022-test/tests/transfer_hook.rs b/token/program-2022-test/tests/transfer_hook.rs index d26511bd9fd..584b88e133d 100644 --- a/token/program-2022-test/tests/transfer_hook.rs +++ b/token/program-2022-test/tests/transfer_hook.rs @@ -13,6 +13,7 @@ use { entrypoint::ProgramResult, instruction::{AccountMeta, Instruction, InstructionError}, program_error::ProgramError, + program_option::COption, pubkey::Pubkey, signature::Signer, signer::keypair::Keypair, @@ -23,6 +24,7 @@ use { spl_token_2022::{ error::TokenError, extension::{ + transfer_fee::{TransferFee, TransferFeeAmount, TransferFeeConfig}, transfer_hook::{TransferHook, TransferHookAccount}, BaseStateWithExtensions, }, @@ -36,6 +38,9 @@ use { std::{convert::TryInto, sync::Arc}, }; +const TEST_MAXIMUM_FEE: u64 = 10_000_000; +const TEST_FEE_BASIS_POINTS: u16 = 100; + /// Test program to fail transfer hook, conforms to transfer-hook-interface pub fn process_instruction_fail( _program_id: &Pubkey, @@ -139,6 +144,58 @@ pub fn process_instruction_swap( Ok(()) } +// Test program to transfer two types of tokens with transfer hooks at once with +// fees +pub fn process_instruction_swap_with_fee( + _program_id: &Pubkey, + accounts: &[AccountInfo], + _input: &[u8], +) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + + let source_a_account_info = next_account_info(account_info_iter)?; + let mint_a_info = next_account_info(account_info_iter)?; + let destination_a_account_info = next_account_info(account_info_iter)?; + let authority_a_info = next_account_info(account_info_iter)?; + let token_program_a_info = next_account_info(account_info_iter)?; + + let source_b_account_info = next_account_info(account_info_iter)?; + let mint_b_info = next_account_info(account_info_iter)?; + let destination_b_account_info = next_account_info(account_info_iter)?; + let authority_b_info = next_account_info(account_info_iter)?; + let token_program_b_info = next_account_info(account_info_iter)?; + + let remaining_accounts = account_info_iter.as_slice(); + + onchain::invoke_transfer_checked_with_fee( + token_program_a_info.key, + source_a_account_info.clone(), + mint_a_info.clone(), + destination_a_account_info.clone(), + authority_a_info.clone(), + remaining_accounts, + 1_000_000_000, + 9, + 10_000_000, + &[], + )?; + + onchain::invoke_transfer_checked_with_fee( + token_program_b_info.key, + source_b_account_info.clone(), + mint_b_info.clone(), + destination_b_account_info.clone(), + authority_b_info.clone(), + remaining_accounts, + 1_000_000_000, + 9, + 10_000_000, + &[], + )?; + + Ok(()) +} + async fn setup_accounts( token_context: &TokenContext, alice_account: Keypair, @@ -261,6 +318,44 @@ async fn setup(mint: Keypair, program_id: &Pubkey, authority: &Pubkey) -> TestCo context } +async fn setup_with_fee(mint: Keypair, program_id: &Pubkey, authority: &Pubkey) -> TestContext { + let mut program_test = setup_program_test(program_id); + + let transfer_fee_config_authority = Keypair::new(); + let withdraw_withheld_authority = Keypair::new(); + let transfer_fee_basis_points = TEST_FEE_BASIS_POINTS; + let maximum_fee = TEST_MAXIMUM_FEE; + add_validation_account(&mut program_test, &mint.pubkey(), program_id); + + let context = program_test.start_with_context().await; + let context = Arc::new(tokio::sync::Mutex::new(context)); + + let mut context = TestContext { + context, + token_context: None, + }; + context + .init_token_with_mint_keypair_and_freeze_authority( + mint, + vec![ + ExtensionInitializationParams::TransferHook { + authority: Some(*authority), + program_id: Some(*program_id), + }, + ExtensionInitializationParams::TransferFeeConfig { + transfer_fee_config_authority: transfer_fee_config_authority.pubkey().into(), + withdraw_withheld_authority: withdraw_withheld_authority.pubkey().into(), + transfer_fee_basis_points, + maximum_fee, + }, + ], + None, + ) + .await + .unwrap(); + context +} + async fn setup_with_confidential_transfers( mint: Keypair, program_id: &Pubkey, @@ -549,6 +644,99 @@ async fn success_transfer() { ); } +#[tokio::test] +async fn success_transfer_with_fee() { + let authority = Keypair::new(); + let program_id = Pubkey::new_unique(); + let mint_keypair = Keypair::new(); + + let maximum_fee = TEST_MAXIMUM_FEE; + let alice_amount = maximum_fee * 100; + let transfer_amount = maximum_fee; + + let token_context = setup_with_fee(mint_keypair, &program_id, &authority.pubkey()) + .await + .token_context + .take() + .unwrap(); + + let (alice_account, bob_account) = + setup_accounts(&token_context, Keypair::new(), Keypair::new(), alice_amount).await; + + let transfer_fee = TransferFee { + epoch: 0.into(), + transfer_fee_basis_points: TEST_FEE_BASIS_POINTS.into(), + maximum_fee: TEST_MAXIMUM_FEE.into(), + }; + let transfer_fee_config = TransferFeeConfig { + transfer_fee_config_authority: COption::Some(Pubkey::new_unique()).try_into().unwrap(), + withdraw_withheld_authority: COption::Some(Pubkey::new_unique()).try_into().unwrap(), + withheld_amount: 0.into(), + older_transfer_fee: transfer_fee, + newer_transfer_fee: transfer_fee, + }; + let fee = transfer_fee_config + .calculate_epoch_fee(0, transfer_amount) + .unwrap(); + + token_context + .token + .transfer_with_fee( + &alice_account, + &bob_account, + &token_context.alice.pubkey(), + transfer_amount, + fee, + &[&token_context.alice], + ) + .await + .unwrap(); + + // Get the accounts' state after the transfer + let alice_state = token_context + .token + .get_account_info(&alice_account) + .await + .unwrap(); + let bob_state = token_context + .token + .get_account_info(&bob_account) + .await + .unwrap(); + + // Check that the correct amount was deducted from Alice's account + assert_eq!(alice_state.base.amount, alice_amount - transfer_amount); + + // Check the there are no tokens withheld in Alice's account + let extension = alice_state.get_extension::().unwrap(); + assert_eq!(extension.withheld_amount, 0.into()); + + // Check the fee tokens are withheld in Bobs's account + let extension = bob_state.get_extension::().unwrap(); + assert_eq!(extension.withheld_amount, fee.into()); + + // Check that the correct amount was added to Bobs's account + assert_eq!(bob_state.base.amount, transfer_amount - fee); + + // the example program checks that the transferring flag was set to true, + // so make sure that it was correctly unset by the token program + assert_eq!( + bob_state + .get_extension::() + .unwrap() + .transferring, + false.into() + ); + + assert_eq!( + alice_state + .get_extension::() + .unwrap() + .transferring, + false.into() + ); +} + #[tokio::test] async fn fail_transfer_hook_program() { let authority = Pubkey::new_unique(); @@ -821,6 +1009,150 @@ async fn success_transfers_using_onchain_helper() { .unwrap(); } +#[tokio::test] +async fn success_transfers_with_fee_using_onchain_helper() { + let authority = Pubkey::new_unique(); + let program_id = Pubkey::new_unique(); + let mint_a_keypair = Keypair::new(); + let mint_a = mint_a_keypair.pubkey(); + let mint_b_keypair = Keypair::new(); + let mint_b = mint_b_keypair.pubkey(); + let amount = 10_000_000_000; + + let transfer_fee_config_authority = Keypair::new(); + let withdraw_withheld_authority = Keypair::new(); + let transfer_fee_basis_points = TEST_FEE_BASIS_POINTS; + let maximum_fee = TEST_MAXIMUM_FEE; + + let swap_program_id = Pubkey::new_unique(); + let mut program_test = setup_program_test(&program_id); + program_test.add_program( + "my_swap", + swap_program_id, + processor!(process_instruction_swap_with_fee), + ); + add_validation_account(&mut program_test, &mint_a, &program_id); + add_validation_account(&mut program_test, &mint_b, &program_id); + + let context = program_test.start_with_context().await; + let context = Arc::new(tokio::sync::Mutex::new(context)); + let mut context_a = TestContext { + context: context.clone(), + token_context: None, + }; + context_a + .init_token_with_mint_keypair_and_freeze_authority( + mint_a_keypair, + vec![ + ExtensionInitializationParams::TransferHook { + authority: Some(authority), + program_id: Some(program_id), + }, + ExtensionInitializationParams::TransferFeeConfig { + transfer_fee_config_authority: transfer_fee_config_authority.pubkey().into(), + withdraw_withheld_authority: withdraw_withheld_authority.pubkey().into(), + transfer_fee_basis_points, + maximum_fee, + }, + ], + None, + ) + .await + .unwrap(); + let token_a_context = context_a.token_context.unwrap(); + let (source_a_account, destination_a_account) = + setup_accounts(&token_a_context, Keypair::new(), Keypair::new(), amount).await; + let authority_a = token_a_context.alice; + let token_a = token_a_context.token; + let mut context_b = TestContext { + context, + token_context: None, + }; + context_b + .init_token_with_mint_keypair_and_freeze_authority( + mint_b_keypair, + vec![ + ExtensionInitializationParams::TransferHook { + authority: Some(authority), + program_id: Some(program_id), + }, + ExtensionInitializationParams::TransferFeeConfig { + transfer_fee_config_authority: transfer_fee_config_authority.pubkey().into(), + withdraw_withheld_authority: withdraw_withheld_authority.pubkey().into(), + transfer_fee_basis_points, + maximum_fee, + }, + ], + None, + ) + .await + .unwrap(); + let token_b_context = context_b.token_context.unwrap(); + let (source_b_account, destination_b_account) = + setup_accounts(&token_b_context, Keypair::new(), Keypair::new(), amount).await; + let authority_b = token_b_context.alice; + let account_metas = vec![ + AccountMeta::new(source_a_account, false), + AccountMeta::new_readonly(mint_a, false), + AccountMeta::new(destination_a_account, false), + AccountMeta::new_readonly(authority_a.pubkey(), true), + AccountMeta::new_readonly(spl_token_2022::id(), false), + AccountMeta::new(source_b_account, false), + AccountMeta::new_readonly(mint_b, false), + AccountMeta::new(destination_b_account, false), + AccountMeta::new_readonly(authority_b.pubkey(), true), + AccountMeta::new_readonly(spl_token_2022::id(), false), + ]; + + let mut instruction = Instruction::new_with_bytes(swap_program_id, &[], account_metas); + + add_extra_account_metas_for_execute( + &mut instruction, + &program_id, + &source_a_account, + &mint_a, + &destination_a_account, + &authority_a.pubkey(), + amount, + |address| { + token_a.get_account(address).map_ok_or_else( + |e| match e { + TokenClientError::AccountNotFound => Ok(None), + _ => Err(offchain::AccountFetchError::from(e)), + }, + |acc| Ok(Some(acc.data)), + ) + }, + ) + .await + .unwrap(); + add_extra_account_metas_for_execute( + &mut instruction, + &program_id, + &source_b_account, + &mint_b, + &destination_b_account, + &authority_b.pubkey(), + amount, + |address| { + token_a.get_account(address).map_ok_or_else( + |e| match e { + TokenClientError::AccountNotFound => Ok(None), + _ => Err(offchain::AccountFetchError::from(e)), + }, + |acc| Ok(Some(acc.data)), + ) + }, + ) + .await + .unwrap(); + + token_a + .process_ixs(&[instruction], &[&authority_a, &authority_b]) + .await + .unwrap(); +} + #[tokio::test] async fn success_confidential_transfer() { let authority = Keypair::new(); diff --git a/token/program-2022/src/offchain.rs b/token/program-2022/src/offchain.rs index 800f3dd2b31..eda4d658620 100644 --- a/token/program-2022/src/offchain.rs +++ b/token/program-2022/src/offchain.rs @@ -3,7 +3,7 @@ pub use spl_transfer_hook_interface::offchain::{AccountDataResult, AccountFetchError}; use { crate::{ - extension::{transfer_hook, StateWithExtensions}, + extension::{transfer_fee, transfer_hook, StateWithExtensions}, state::Mint, }, solana_program::{instruction::Instruction, program_error::ProgramError, pubkey::Pubkey}, @@ -74,6 +74,72 @@ where Ok(transfer_instruction) } +/// Offchain helper to create a `TransferCheckedWithFee` instruction with all +/// additional required account metas for a transfer, including the ones +/// required by the transfer hook. +/// +/// To be client-agnostic and to avoid pulling in the full solana-sdk, this +/// simply takes a function that will return its data as `Future>` for +/// the given address. Can be called in the following way: +/// +/// ```rust,ignore +/// let instruction = create_transfer_checked_with_fee_instruction_with_extra_metas( +/// &spl_token_2022::id(), +/// &source, +/// &mint, +/// &destination, +/// &authority, +/// &[], +/// amount, +/// decimals, +/// fee, +/// |address| self.client.get_account(&address).map_ok(|opt| opt.map(|acc| acc.data)), +/// ) +/// .await? +/// ``` +#[allow(clippy::too_many_arguments)] +pub async fn create_transfer_checked_with_fee_instruction_with_extra_metas( + token_program_id: &Pubkey, + source_pubkey: &Pubkey, + mint_pubkey: &Pubkey, + destination_pubkey: &Pubkey, + authority_pubkey: &Pubkey, + signer_pubkeys: &[&Pubkey], + amount: u64, + decimals: u8, + fee: u64, + fetch_account_data_fn: F, +) -> Result +where + F: Fn(Pubkey) -> Fut, + Fut: Future, +{ + let mut transfer_instruction = transfer_fee::instruction::transfer_checked_with_fee( + token_program_id, + source_pubkey, + mint_pubkey, + destination_pubkey, + authority_pubkey, + signer_pubkeys, + amount, + decimals, + fee, + )?; + + add_extra_account_metas( + &mut transfer_instruction, + source_pubkey, + mint_pubkey, + destination_pubkey, + authority_pubkey, + amount, + fetch_account_data_fn, + ) + .await?; + + Ok(transfer_instruction) +} + /// Offchain helper to add required account metas to an instruction, including /// the ones required by the transfer hook. /// @@ -318,4 +384,103 @@ mod tests { assert_eq!(instruction.accounts, check_metas); } + + #[tokio::test] + async fn test_create_transfer_checked_with_fee_instruction_with_extra_metas() { + let source = Pubkey::new_unique(); + let destination = Pubkey::new_unique(); + let authority = Pubkey::new_unique(); + let amount = 100u64; + let fee = 1u64; + + let validate_state_pubkey = + get_extra_account_metas_address(&MINT_PUBKEY, &TRANSFER_HOOK_PROGRAM_ID); + let extra_meta_3_pubkey = Pubkey::find_program_address( + &[ + source.as_ref(), + destination.as_ref(), + validate_state_pubkey.as_ref(), + ], + &TRANSFER_HOOK_PROGRAM_ID, + ) + .0; + let extra_meta_4_pubkey = Pubkey::find_program_address( + &[ + amount.to_le_bytes().as_ref(), + destination.as_ref(), + EXTRA_META_1.as_ref(), + extra_meta_3_pubkey.as_ref(), + ], + &TRANSFER_HOOK_PROGRAM_ID, + ) + .0; + + let instruction = create_transfer_checked_with_fee_instruction_with_extra_metas( + &crate::id(), + &source, + &MINT_PUBKEY, + &destination, + &authority, + &[], + amount, + DECIMALS, + fee, + mock_fetch_account_data_fn, + ) + .await + .unwrap(); + + let check_metas = [ + AccountMeta::new(source, false), + AccountMeta::new_readonly(MINT_PUBKEY, false), + AccountMeta::new(destination, false), + AccountMeta::new_readonly(authority, true), + AccountMeta::new_readonly(EXTRA_META_1, true), + AccountMeta::new_readonly(EXTRA_META_2, true), + AccountMeta::new(extra_meta_3_pubkey, false), + AccountMeta::new(extra_meta_4_pubkey, false), + AccountMeta::new_readonly(TRANSFER_HOOK_PROGRAM_ID, false), + AccountMeta::new_readonly(validate_state_pubkey, false), + ]; + + assert_eq!(instruction.accounts, check_metas); + + // With additional signers + let signer_1 = Pubkey::new_unique(); + let signer_2 = Pubkey::new_unique(); + let signer_3 = Pubkey::new_unique(); + + let instruction = create_transfer_checked_with_fee_instruction_with_extra_metas( + &crate::id(), + &source, + &MINT_PUBKEY, + &destination, + &authority, + &[&signer_1, &signer_2, &signer_3], + amount, + DECIMALS, + fee, + mock_fetch_account_data_fn, + ) + .await + .unwrap(); + + let check_metas = [ + AccountMeta::new(source, false), + AccountMeta::new_readonly(MINT_PUBKEY, false), + AccountMeta::new(destination, false), + AccountMeta::new_readonly(authority, false), // False because of additional signers + AccountMeta::new_readonly(signer_1, true), + AccountMeta::new_readonly(signer_2, true), + AccountMeta::new_readonly(signer_3, true), + AccountMeta::new_readonly(EXTRA_META_1, true), + AccountMeta::new_readonly(EXTRA_META_2, true), + AccountMeta::new(extra_meta_3_pubkey, false), + AccountMeta::new(extra_meta_4_pubkey, false), + AccountMeta::new_readonly(TRANSFER_HOOK_PROGRAM_ID, false), + AccountMeta::new_readonly(validate_state_pubkey, false), + ]; + + assert_eq!(instruction.accounts, check_metas); + } } diff --git a/token/program-2022/src/onchain.rs b/token/program-2022/src/onchain.rs index 255800d7877..874822f00fc 100644 --- a/token/program-2022/src/onchain.rs +++ b/token/program-2022/src/onchain.rs @@ -3,7 +3,7 @@ use { crate::{ - extension::{transfer_hook, StateWithExtensions}, + extension::{transfer_fee, transfer_hook, StateWithExtensions}, instruction, state::Mint, }, @@ -78,3 +78,71 @@ pub fn invoke_transfer_checked<'a>( invoke_signed(&cpi_instruction, &cpi_account_infos, seeds) } + +/// Helper to CPI into token-2022 on-chain, looking through the additional +/// account infos to create the proper instruction with the fee +/// and proper account infos +#[allow(clippy::too_many_arguments)] +pub fn invoke_transfer_checked_with_fee<'a>( + token_program_id: &Pubkey, + source_info: AccountInfo<'a>, + mint_info: AccountInfo<'a>, + destination_info: AccountInfo<'a>, + authority_info: AccountInfo<'a>, + additional_accounts: &[AccountInfo<'a>], + amount: u64, + decimals: u8, + fee: u64, + seeds: &[&[&[u8]]], +) -> ProgramResult { + let mut cpi_instruction = transfer_fee::instruction::transfer_checked_with_fee( + token_program_id, + source_info.key, + mint_info.key, + destination_info.key, + authority_info.key, + &[], // add them later, to avoid unnecessary clones + amount, + decimals, + fee, + )?; + + let mut cpi_account_infos = vec![ + source_info.clone(), + mint_info.clone(), + destination_info.clone(), + authority_info.clone(), + ]; + + // if it's a signer, it might be a multisig signer, throw it in! + additional_accounts + .iter() + .filter(|ai| ai.is_signer) + .for_each(|ai| { + cpi_account_infos.push(ai.clone()); + cpi_instruction + .accounts + .push(AccountMeta::new_readonly(*ai.key, ai.is_signer)); + }); + + // scope the borrowing to avoid a double-borrow during CPI + { + let mint_data = mint_info.try_borrow_data()?; + let mint = StateWithExtensions::::unpack(&mint_data)?; + if let Some(program_id) = transfer_hook::get_program_id(&mint) { + add_extra_accounts_for_execute_cpi( + &mut cpi_instruction, + &mut cpi_account_infos, + &program_id, + source_info, + mint_info.clone(), + destination_info, + authority_info, + amount, + additional_accounts, + )?; + } + } + + invoke_signed(&cpi_instruction, &cpi_account_infos, seeds) +} From 7209fc514d9e2f56b660dc7769bbd65932ba0413 Mon Sep 17 00:00:00 2001 From: Joe C Date: Thu, 12 Sep 2024 18:12:47 +0800 Subject: [PATCH 27/39] Revert ESLint plugin bump (#7260) * Revert "build(deps-dev): bump @typescript-eslint/parser from 8.4.0 to 8.5.0 (#7257)" This reverts commit 3522730070da7b7b6c19513b288c9faebd0be2ab. * Revert "build(deps-dev): bump @typescript-eslint/eslint-plugin from 8.4.0 to 8.5.0 (#7258)" This reverts commit eeb0b2968f93b4f169a589287cde445100f811d6. --- account-compression/sdk/package.json | 4 +- libraries/type-length-value/js/package.json | 4 +- memo/js/package.json | 4 +- name-service/js/package.json | 4 +- pnpm-lock.yaml | 223 +++++++------------ single-pool/js/packages/classic/package.json | 2 +- single-pool/js/packages/modern/package.json | 2 +- stake-pool/js/package.json | 4 +- token-group/js/package.json | 4 +- token-lending/js/package.json | 4 +- token-metadata/js/package.json | 4 +- token-swap/js/package.json | 4 +- token/js/package.json | 4 +- 13 files changed, 104 insertions(+), 163 deletions(-) diff --git a/account-compression/sdk/package.json b/account-compression/sdk/package.json index 5d3e0584f74..780bb5c1fb9 100644 --- a/account-compression/sdk/package.json +++ b/account-compression/sdk/package.json @@ -69,8 +69,8 @@ "@types/jest": "^29.5.12", "@types/node": "^22.5.4", "@types/node-fetch": "^2.6.11", - "@typescript-eslint/eslint-plugin": "^8.5.0", - "@typescript-eslint/parser": "^8.5.0", + "@typescript-eslint/eslint-plugin": "^8.4.0", + "@typescript-eslint/parser": "^8.4.0", "eslint": "^8.57.0", "eslint-config-turbo": "^2.1.1", "eslint-plugin-import": "^2.30.0", diff --git a/libraries/type-length-value/js/package.json b/libraries/type-length-value/js/package.json index b8ea3af1fe3..0d03550e6e9 100644 --- a/libraries/type-length-value/js/package.json +++ b/libraries/type-length-value/js/package.json @@ -49,8 +49,8 @@ "@types/chai": "^4.3.19", "@types/mocha": "^10.0.7", "@types/node": "^22.5.4", - "@typescript-eslint/eslint-plugin": "^8.5.0", - "@typescript-eslint/parser": "^8.5.0", + "@typescript-eslint/eslint-plugin": "^8.4.0", + "@typescript-eslint/parser": "^8.4.0", "chai": "^5.1.1", "eslint": "^8.57.0", "eslint-plugin-require-extensions": "^0.1.1", diff --git a/memo/js/package.json b/memo/js/package.json index 0c90eb61425..e137f4c6720 100644 --- a/memo/js/package.json +++ b/memo/js/package.json @@ -56,8 +56,8 @@ "@types/jest": "^29.5.12", "@types/node": "^22.5.4", "@types/node-fetch": "^2.6.11", - "@typescript-eslint/eslint-plugin": "^8.5.0", - "@typescript-eslint/parser": "^8.5.0", + "@typescript-eslint/eslint-plugin": "^8.4.0", + "@typescript-eslint/parser": "^8.4.0", "chai": "^5.1.1", "eslint": "^8.57.0", "eslint-plugin-require-extensions": "^0.1.1", diff --git a/name-service/js/package.json b/name-service/js/package.json index b3a9c82c838..968c445774a 100644 --- a/name-service/js/package.json +++ b/name-service/js/package.json @@ -46,8 +46,8 @@ "@types/bn.js": "^5.1.1", "@types/jest": "^29.5.12", "@types/node": "^22.5.4", - "@typescript-eslint/eslint-plugin": "^8.5.0", - "@typescript-eslint/parser": "^8.5.0", + "@typescript-eslint/eslint-plugin": "^8.4.0", + "@typescript-eslint/parser": "^8.4.0", "eslint": "^8.57.0", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-functional": "^7.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 85704d2c1cf..337dbe7e1d7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,7 +62,7 @@ importers: version: 0.20.1 '@solana/eslint-config-solana': specifier: ^3.0.3 - version: 3.0.3(@typescript-eslint/eslint-plugin@8.5.0)(@typescript-eslint/parser@8.5.0)(eslint-plugin-jest@28.8.3)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-simple-import-sort@12.1.1)(eslint-plugin-sort-keys-fix@1.1.2)(eslint-plugin-typescript-sort-keys@3.2.0)(eslint@8.57.0)(typescript@5.6.2) + version: 3.0.3(@typescript-eslint/eslint-plugin@8.4.0)(@typescript-eslint/parser@8.4.0)(eslint-plugin-jest@28.8.3)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-simple-import-sort@12.1.1)(eslint-plugin-sort-keys-fix@1.1.2)(eslint-plugin-typescript-sort-keys@3.2.0)(eslint@8.57.0)(typescript@5.6.2) '@types/bn.js': specifier: ^5.1.0 version: 5.1.5 @@ -76,11 +76,11 @@ importers: specifier: ^2.6.11 version: 2.6.11 '@typescript-eslint/eslint-plugin': - specifier: ^8.5.0 - version: 8.5.0(@typescript-eslint/parser@8.5.0)(eslint@8.57.0)(typescript@5.6.2) + specifier: ^8.4.0 + version: 8.4.0(@typescript-eslint/parser@8.4.0)(eslint@8.57.0)(typescript@5.6.2) '@typescript-eslint/parser': - specifier: ^8.5.0 - version: 8.5.0(eslint@8.57.0)(typescript@5.6.2) + specifier: ^8.4.0 + version: 8.4.0(eslint@8.57.0)(typescript@5.6.2) eslint: specifier: ^8.57.0 version: 8.57.0 @@ -89,10 +89,10 @@ importers: version: 2.1.1(eslint@8.57.0) eslint-plugin-import: specifier: ^2.30.0 - version: 2.30.0(@typescript-eslint/parser@8.5.0)(eslint@8.57.0) + version: 2.30.0(@typescript-eslint/parser@8.4.0)(eslint@8.57.0) eslint-plugin-jest: specifier: ^28.8.3 - version: 28.8.3(@typescript-eslint/eslint-plugin@8.5.0)(eslint@8.57.0)(jest@29.7.0)(typescript@5.6.2) + version: 28.8.3(@typescript-eslint/eslint-plugin@8.4.0)(eslint@8.57.0)(jest@29.7.0)(typescript@5.6.2) eslint-plugin-mocha: specifier: ^10.5.0 version: 10.5.0(eslint@8.57.0) @@ -146,11 +146,11 @@ importers: specifier: ^22.5.4 version: 22.5.4 '@typescript-eslint/eslint-plugin': - specifier: ^8.5.0 - version: 8.5.0(@typescript-eslint/parser@8.5.0)(eslint@8.57.0)(typescript@5.6.2) + specifier: ^8.4.0 + version: 8.4.0(@typescript-eslint/parser@8.4.0)(eslint@8.57.0)(typescript@5.6.2) '@typescript-eslint/parser': - specifier: ^8.5.0 - version: 8.5.0(eslint@8.57.0)(typescript@5.6.2) + specifier: ^8.4.0 + version: 8.4.0(eslint@8.57.0)(typescript@5.6.2) chai: specifier: ^5.1.1 version: 5.1.1 @@ -201,11 +201,11 @@ importers: specifier: ^2.6.11 version: 2.6.11 '@typescript-eslint/eslint-plugin': - specifier: ^8.5.0 - version: 8.5.0(@typescript-eslint/parser@8.5.0)(eslint@8.57.0)(typescript@5.6.2) + specifier: ^8.4.0 + version: 8.4.0(@typescript-eslint/parser@8.4.0)(eslint@8.57.0)(typescript@5.6.2) '@typescript-eslint/parser': - specifier: ^8.5.0 - version: 8.5.0(eslint@8.57.0)(typescript@5.6.2) + specifier: ^8.4.0 + version: 8.4.0(eslint@8.57.0)(typescript@5.6.2) chai: specifier: ^5.1.1 version: 5.1.1 @@ -268,11 +268,11 @@ importers: specifier: ^22.5.4 version: 22.5.4 '@typescript-eslint/eslint-plugin': - specifier: ^8.5.0 - version: 8.5.0(@typescript-eslint/parser@8.5.0)(eslint@8.57.0)(typescript@5.6.2) + specifier: ^8.4.0 + version: 8.4.0(@typescript-eslint/parser@8.4.0)(eslint@8.57.0)(typescript@5.6.2) '@typescript-eslint/parser': - specifier: ^8.5.0 - version: 8.5.0(eslint@8.57.0)(typescript@5.6.2) + specifier: ^8.4.0 + version: 8.4.0(eslint@8.57.0)(typescript@5.6.2) eslint: specifier: ^8.57.0 version: 8.57.0 @@ -284,7 +284,7 @@ importers: version: 7.0.2(eslint@8.57.0)(typescript@5.6.2) eslint-plugin-import: specifier: ^2.30.0 - version: 2.30.0(@typescript-eslint/parser@8.5.0)(eslint@8.57.0) + version: 2.30.0(@typescript-eslint/parser@8.4.0)(eslint@8.57.0) jest: specifier: ^29.7.0 version: 29.7.0(@types/node@22.5.4)(ts-node@10.9.2) @@ -323,8 +323,8 @@ importers: specifier: ^22.5.4 version: 22.5.4 '@typescript-eslint/eslint-plugin': - specifier: ^8.5.0 - version: 8.5.0(@typescript-eslint/parser@8.5.0)(eslint@8.57.0)(typescript@5.6.2) + specifier: ^8.4.0 + version: 8.4.0(@typescript-eslint/parser@8.4.0)(eslint@8.57.0)(typescript@5.6.2) ava: specifier: ^6.1.3 version: 6.1.3(@ava/typescript@5.0.0) @@ -357,8 +357,8 @@ importers: specifier: ^22.5.4 version: 22.5.4 '@typescript-eslint/eslint-plugin': - specifier: ^8.5.0 - version: 8.5.0(@typescript-eslint/parser@8.5.0)(eslint@8.57.0)(typescript@5.6.2) + specifier: ^8.4.0 + version: 8.4.0(@typescript-eslint/parser@8.4.0)(eslint@8.57.0)(typescript@5.6.2) eslint: specifier: ^8.57.0 version: 8.57.0 @@ -424,11 +424,11 @@ importers: specifier: ^2.6.11 version: 2.6.11 '@typescript-eslint/eslint-plugin': - specifier: ^8.5.0 - version: 8.5.0(@typescript-eslint/parser@8.5.0)(eslint@8.57.0)(typescript@5.6.2) + specifier: ^8.4.0 + version: 8.4.0(@typescript-eslint/parser@8.4.0)(eslint@8.57.0)(typescript@5.6.2) '@typescript-eslint/parser': - specifier: ^8.5.0 - version: 8.5.0(eslint@8.57.0)(typescript@5.6.2) + specifier: ^8.4.0 + version: 8.4.0(eslint@8.57.0)(typescript@5.6.2) cross-env: specifier: ^7.0.3 version: 7.0.3 @@ -476,11 +476,11 @@ importers: specifier: ^22.5.4 version: 22.5.4 '@typescript-eslint/eslint-plugin': - specifier: ^8.5.0 - version: 8.5.0(@typescript-eslint/parser@8.5.0)(eslint@8.57.0)(typescript@5.6.2) + specifier: ^8.4.0 + version: 8.4.0(@typescript-eslint/parser@8.4.0)(eslint@8.57.0)(typescript@5.6.2) '@typescript-eslint/parser': - specifier: ^8.5.0 - version: 8.5.0(eslint@8.57.0)(typescript@5.6.2) + specifier: ^8.4.0 + version: 8.4.0(eslint@8.57.0)(typescript@5.6.2) chai: specifier: ^5.1.1 version: 5.1.1 @@ -549,11 +549,11 @@ importers: specifier: ^22.5.4 version: 22.5.4 '@typescript-eslint/eslint-plugin': - specifier: ^8.5.0 - version: 8.5.0(@typescript-eslint/parser@8.5.0)(eslint@8.57.0)(typescript@5.6.2) + specifier: ^8.4.0 + version: 8.4.0(@typescript-eslint/parser@8.4.0)(eslint@8.57.0)(typescript@5.6.2) '@typescript-eslint/parser': - specifier: ^8.5.0 - version: 8.5.0(eslint@8.57.0)(typescript@5.6.2) + specifier: ^8.4.0 + version: 8.4.0(eslint@8.57.0)(typescript@5.6.2) eslint: specifier: ^8.57.0 version: 8.57.0 @@ -598,11 +598,11 @@ importers: specifier: ^22.5.4 version: 22.5.4 '@typescript-eslint/eslint-plugin': - specifier: ^8.5.0 - version: 8.5.0(@typescript-eslint/parser@8.5.0)(eslint@8.57.0)(typescript@5.6.2) + specifier: ^8.4.0 + version: 8.4.0(@typescript-eslint/parser@8.4.0)(eslint@8.57.0)(typescript@5.6.2) '@typescript-eslint/parser': - specifier: ^8.5.0 - version: 8.5.0(eslint@8.57.0)(typescript@5.6.2) + specifier: ^8.4.0 + version: 8.4.0(eslint@8.57.0)(typescript@5.6.2) chai: specifier: ^5.1.1 version: 5.1.1 @@ -662,17 +662,17 @@ importers: specifier: ^10.0.7 version: 10.0.7 '@typescript-eslint/eslint-plugin': - specifier: ^8.5.0 - version: 8.5.0(@typescript-eslint/parser@8.5.0)(eslint@8.57.0)(typescript@5.6.2) + specifier: ^8.4.0 + version: 8.4.0(@typescript-eslint/parser@8.4.0)(eslint@8.57.0)(typescript@5.6.2) '@typescript-eslint/parser': - specifier: ^8.5.0 - version: 8.5.0(eslint@8.57.0)(typescript@5.6.2) + specifier: ^8.4.0 + version: 8.4.0(eslint@8.57.0)(typescript@5.6.2) eslint: specifier: ^8.57.0 version: 8.57.0 eslint-plugin-import: specifier: ^2.30.0 - version: 2.30.0(@typescript-eslint/parser@8.5.0)(eslint@8.57.0) + version: 2.30.0(@typescript-eslint/parser@8.4.0)(eslint@8.57.0) eslint-plugin-require-extensions: specifier: ^0.1.1 version: 0.1.3(eslint@8.57.0) @@ -732,11 +732,11 @@ importers: specifier: ^2.6.11 version: 2.6.11 '@typescript-eslint/eslint-plugin': - specifier: ^8.5.0 - version: 8.5.0(@typescript-eslint/parser@8.5.0)(eslint@8.57.0)(typescript@5.6.2) + specifier: ^8.4.0 + version: 8.4.0(@typescript-eslint/parser@8.4.0)(eslint@8.57.0)(typescript@5.6.2) '@typescript-eslint/parser': - specifier: ^8.5.0 - version: 8.5.0(eslint@8.57.0)(typescript@5.6.2) + specifier: ^8.4.0 + version: 8.4.0(eslint@8.57.0)(typescript@5.6.2) chai: specifier: ^5.1.1 version: 5.1.1 @@ -2395,7 +2395,7 @@ packages: typescript: 5.6.2 dev: true - /@solana/eslint-config-solana@3.0.3(@typescript-eslint/eslint-plugin@8.5.0)(@typescript-eslint/parser@8.5.0)(eslint-plugin-jest@28.8.3)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-simple-import-sort@12.1.1)(eslint-plugin-sort-keys-fix@1.1.2)(eslint-plugin-typescript-sort-keys@3.2.0)(eslint@8.57.0)(typescript@5.6.2): + /@solana/eslint-config-solana@3.0.3(@typescript-eslint/eslint-plugin@8.4.0)(@typescript-eslint/parser@8.4.0)(eslint-plugin-jest@28.8.3)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-simple-import-sort@12.1.1)(eslint-plugin-sort-keys-fix@1.1.2)(eslint-plugin-typescript-sort-keys@3.2.0)(eslint@8.57.0)(typescript@5.6.2): resolution: {integrity: sha512-yTaeCbOBwjmK4oUkknixOpwOzzAK8+4YWvJEJFNHuueESetieDnAeEHV7rzJllFgHEWa9nXps9Q3aD4/XJp71A==} peerDependencies: '@typescript-eslint/eslint-plugin': ^6.0.0 @@ -2408,14 +2408,14 @@ packages: eslint-plugin-typescript-sort-keys: ^3.2.0 typescript: ^5.1.6 dependencies: - '@typescript-eslint/eslint-plugin': 8.5.0(@typescript-eslint/parser@8.5.0)(eslint@8.57.0)(typescript@5.6.2) - '@typescript-eslint/parser': 8.5.0(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/eslint-plugin': 8.4.0(@typescript-eslint/parser@8.4.0)(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/parser': 8.4.0(eslint@8.57.0)(typescript@5.6.2) eslint: 8.57.0 - eslint-plugin-jest: 28.8.3(@typescript-eslint/eslint-plugin@8.5.0)(eslint@8.57.0)(jest@29.7.0)(typescript@5.6.2) + eslint-plugin-jest: 28.8.3(@typescript-eslint/eslint-plugin@8.4.0)(eslint@8.57.0)(jest@29.7.0)(typescript@5.6.2) eslint-plugin-react-hooks: 4.6.0(eslint@8.57.0) eslint-plugin-simple-import-sort: 12.1.1(eslint@8.57.0) eslint-plugin-sort-keys-fix: 1.1.2 - eslint-plugin-typescript-sort-keys: 3.2.0(@typescript-eslint/parser@8.5.0)(eslint@8.57.0)(typescript@5.6.2) + eslint-plugin-typescript-sort-keys: 3.2.0(@typescript-eslint/parser@8.4.0)(eslint@8.57.0)(typescript@5.6.2) typescript: 5.6.2 dev: true @@ -2805,8 +2805,8 @@ packages: - supports-color dev: true - /@typescript-eslint/eslint-plugin@8.5.0(@typescript-eslint/parser@8.5.0)(eslint@8.57.0)(typescript@5.6.2): - resolution: {integrity: sha512-lHS5hvz33iUFQKuPFGheAB84LwcJ60G8vKnEhnfcK1l8kGVLro2SFYW6K0/tj8FUhRJ0VHyg1oAfg50QGbPPHw==} + /@typescript-eslint/eslint-plugin@8.4.0(@typescript-eslint/parser@8.4.0)(eslint@8.57.0)(typescript@5.6.2): + resolution: {integrity: sha512-rg8LGdv7ri3oAlenMACk9e+AR4wUV0yrrG+XKsGKOK0EVgeEDqurkXMPILG2836fW4ibokTB5v4b6Z9+GYQDEw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 @@ -2817,11 +2817,11 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 8.5.0(eslint@8.57.0)(typescript@5.6.2) - '@typescript-eslint/scope-manager': 8.5.0 - '@typescript-eslint/type-utils': 8.5.0(eslint@8.57.0)(typescript@5.6.2) - '@typescript-eslint/utils': 8.5.0(eslint@8.57.0)(typescript@5.6.2) - '@typescript-eslint/visitor-keys': 8.5.0 + '@typescript-eslint/parser': 8.4.0(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/scope-manager': 8.4.0 + '@typescript-eslint/type-utils': 8.4.0(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/utils': 8.4.0(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/visitor-keys': 8.4.0 eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.1 @@ -2866,8 +2866,8 @@ packages: - supports-color dev: true - /@typescript-eslint/parser@8.5.0(eslint@8.57.0)(typescript@5.6.2): - resolution: {integrity: sha512-gF77eNv0Xz2UJg/NbpWJ0kqAm35UMsvZf1GHj8D9MRFTj/V3tAciIWXfmPLsAAF/vUlpWPvUDyH1jjsr0cMVWw==} + /@typescript-eslint/parser@8.4.0(eslint@8.57.0)(typescript@5.6.2): + resolution: {integrity: sha512-NHgWmKSgJk5K9N16GIhQ4jSobBoJwrmURaLErad0qlLjrpP5bECYg+wxVTGlGZmJbU03jj/dfnb6V9bw+5icsA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -2876,11 +2876,11 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 8.5.0 - '@typescript-eslint/types': 8.5.0 - '@typescript-eslint/typescript-estree': 8.5.0(typescript@5.6.2) - '@typescript-eslint/visitor-keys': 8.5.0 - debug: 4.3.7 + '@typescript-eslint/scope-manager': 8.4.0 + '@typescript-eslint/types': 8.4.0 + '@typescript-eslint/typescript-estree': 8.4.0(typescript@5.6.2) + '@typescript-eslint/visitor-keys': 8.4.0 + debug: 4.3.6(supports-color@8.1.1) eslint: 8.57.0 typescript: 5.6.2 transitivePeerDependencies: @@ -2919,14 +2919,6 @@ packages: '@typescript-eslint/visitor-keys': 8.4.0 dev: true - /@typescript-eslint/scope-manager@8.5.0: - resolution: {integrity: sha512-06JOQ9Qgj33yvBEx6tpC8ecP9o860rsR22hWMEd12WcTRrfaFgHr2RB/CA/B+7BMhHkXT4chg2MyboGdFGawYg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - dependencies: - '@typescript-eslint/types': 8.5.0 - '@typescript-eslint/visitor-keys': 8.5.0 - dev: true - /@typescript-eslint/type-utils@6.21.0(eslint@8.57.0)(typescript@5.6.2): resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} engines: {node: ^16.0.0 || >=18.0.0} @@ -2947,8 +2939,8 @@ packages: - supports-color dev: true - /@typescript-eslint/type-utils@8.5.0(eslint@8.57.0)(typescript@5.6.2): - resolution: {integrity: sha512-N1K8Ix+lUM+cIDhL2uekVn/ZD7TZW+9/rwz8DclQpcQ9rk4sIL5CAlBC0CugWKREmDjBzI/kQqU4wkg46jWLYA==} + /@typescript-eslint/type-utils@8.4.0(eslint@8.57.0)(typescript@5.6.2): + resolution: {integrity: sha512-pu2PAmNrl9KX6TtirVOrbLPLwDmASpZhK/XU7WvoKoCUkdtq9zF7qQ7gna0GBZFN0hci0vHaSusiL2WpsQk37A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -2956,8 +2948,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 8.5.0(typescript@5.6.2) - '@typescript-eslint/utils': 8.5.0(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/typescript-estree': 8.4.0(typescript@5.6.2) + '@typescript-eslint/utils': 8.4.0(eslint@8.57.0)(typescript@5.6.2) debug: 4.3.7 ts-api-utils: 1.3.0(typescript@5.6.2) typescript: 5.6.2 @@ -2986,11 +2978,6 @@ packages: engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dev: true - /@typescript-eslint/types@8.5.0: - resolution: {integrity: sha512-qjkormnQS5wF9pjSi6q60bKUHH44j2APxfh9TQRXK8wbYVeDYYdYJGIROL87LGZZ2gz3Rbmjc736qyL8deVtdw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - dev: true - /@typescript-eslint/typescript-estree@5.62.0(typescript@5.6.2): resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3078,28 +3065,6 @@ packages: - supports-color dev: true - /@typescript-eslint/typescript-estree@8.5.0(typescript@5.6.2): - resolution: {integrity: sha512-vEG2Sf9P8BPQ+d0pxdfndw3xIXaoSjliG0/Ejk7UggByZPKXmJmw3GW5jV2gHNQNawBUyfahoSiCFVov0Ruf7Q==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/types': 8.5.0 - '@typescript-eslint/visitor-keys': 8.5.0 - debug: 4.3.7 - fast-glob: 3.3.2 - is-glob: 4.0.3 - minimatch: 9.0.5 - semver: 7.6.3 - ts-api-utils: 1.3.0(typescript@5.6.2) - typescript: 5.6.2 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.6.2): resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3171,22 +3136,6 @@ packages: - typescript dev: true - /@typescript-eslint/utils@8.5.0(eslint@8.57.0)(typescript@5.6.2): - resolution: {integrity: sha512-6yyGYVL0e+VzGYp60wvkBHiqDWOpT63pdMV2CVG4LVDd5uR6q1qQN/7LafBZtAtNIn/mqXjsSeS5ggv/P0iECw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) - '@typescript-eslint/scope-manager': 8.5.0 - '@typescript-eslint/types': 8.5.0 - '@typescript-eslint/typescript-estree': 8.5.0(typescript@5.6.2) - eslint: 8.57.0 - transitivePeerDependencies: - - supports-color - - typescript - dev: true - /@typescript-eslint/visitor-keys@5.62.0: resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3219,14 +3168,6 @@ packages: eslint-visitor-keys: 3.4.3 dev: true - /@typescript-eslint/visitor-keys@8.5.0: - resolution: {integrity: sha512-yTPqMnbAZJNy2Xq2XU8AdtOW9tJIr+UQb64aXB9f3B1498Zx9JorVgFJcZpEc9UBuCCrdzKID2RGAMkYcDtZOw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - dependencies: - '@typescript-eslint/types': 8.5.0 - eslint-visitor-keys: 3.4.3 - dev: true - /@ungap/structured-clone@1.2.0: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} dev: true @@ -4610,7 +4551,7 @@ packages: - supports-color dev: true - /eslint-module-utils@2.9.0(@typescript-eslint/parser@8.5.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): + /eslint-module-utils@2.9.0(@typescript-eslint/parser@8.4.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): resolution: {integrity: sha512-McVbYmwA3NEKwRQY5g4aWMdcZE5xZxV8i8l7CqJSrameuGSQJtSWaL/LxTEzSKKaCcOhlpDR8XEfYXWPrdo/ZQ==} engines: {node: '>=4'} peerDependencies: @@ -4631,7 +4572,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 8.5.0(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/parser': 8.4.0(eslint@8.57.0)(typescript@5.6.2) debug: 3.2.7 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 @@ -4672,7 +4613,7 @@ packages: - supports-color dev: true - /eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.5.0)(eslint@8.57.0): + /eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.4.0)(eslint@8.57.0): resolution: {integrity: sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==} engines: {node: '>=4'} peerDependencies: @@ -4683,7 +4624,7 @@ packages: optional: true dependencies: '@rtsao/scc': 1.1.0 - '@typescript-eslint/parser': 8.5.0(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/parser': 8.4.0(eslint@8.57.0)(typescript@5.6.2) array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 @@ -4692,7 +4633,7 @@ packages: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.9.0(@typescript-eslint/parser@8.5.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0) + eslint-module-utils: 2.9.0(@typescript-eslint/parser@8.4.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -4729,7 +4670,7 @@ packages: - typescript dev: true - /eslint-plugin-jest@28.8.3(@typescript-eslint/eslint-plugin@8.5.0)(eslint@8.57.0)(jest@29.7.0)(typescript@5.6.2): + /eslint-plugin-jest@28.8.3(@typescript-eslint/eslint-plugin@8.4.0)(eslint@8.57.0)(jest@29.7.0)(typescript@5.6.2): resolution: {integrity: sha512-HIQ3t9hASLKm2IhIOqnu+ifw7uLZkIlR7RYNv7fMcEi/p0CIiJmfriStQS2LDkgtY4nyLbIZAD+JL347Yc2ETQ==} engines: {node: ^16.10.0 || ^18.12.0 || >=20.0.0} peerDependencies: @@ -4742,7 +4683,7 @@ packages: jest: optional: true dependencies: - '@typescript-eslint/eslint-plugin': 8.5.0(@typescript-eslint/parser@8.5.0)(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/eslint-plugin': 8.4.0(@typescript-eslint/parser@8.4.0)(eslint@8.57.0)(typescript@5.6.2) '@typescript-eslint/utils': 8.4.0(eslint@8.57.0)(typescript@5.6.2) eslint: 8.57.0 jest: 29.7.0(@types/node@22.5.4)(ts-node@10.9.2) @@ -4855,7 +4796,7 @@ packages: - supports-color dev: true - /eslint-plugin-typescript-sort-keys@3.2.0(@typescript-eslint/parser@8.5.0)(eslint@8.57.0)(typescript@5.6.2): + /eslint-plugin-typescript-sort-keys@3.2.0(@typescript-eslint/parser@8.4.0)(eslint@8.57.0)(typescript@5.6.2): resolution: {integrity: sha512-GutszvriaVtwmn7pQjuj9/9o0iXhD7XZs0/424+zsozdRr/fdg5e8206t478Vnqnqi1GjuxcAolj1kf74KnhPA==} engines: {node: '>= 16'} peerDependencies: @@ -4864,7 +4805,7 @@ packages: typescript: ^3 || ^4 || ^5 dependencies: '@typescript-eslint/experimental-utils': 5.62.0(eslint@8.57.0)(typescript@5.6.2) - '@typescript-eslint/parser': 8.5.0(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/parser': 8.4.0(eslint@8.57.0)(typescript@5.6.2) eslint: 8.57.0 json-schema: 0.4.0 natural-compare-lite: 1.4.0 @@ -5806,7 +5747,7 @@ packages: eslint: '*' typescript: '>=4.7.4' dependencies: - '@typescript-eslint/type-utils': 8.5.0(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/type-utils': 8.4.0(eslint@8.57.0)(typescript@5.6.2) eslint: 8.57.0 ts-api-utils: 1.3.0(typescript@5.6.2) ts-declaration-location: 1.0.4(typescript@5.6.2) diff --git a/single-pool/js/packages/classic/package.json b/single-pool/js/packages/classic/package.json index 9abca332fbe..636cbd9f0ea 100644 --- a/single-pool/js/packages/classic/package.json +++ b/single-pool/js/packages/classic/package.json @@ -20,7 +20,7 @@ "devDependencies": { "@types/node": "^22.5.4", "@ava/typescript": "^5.0.0", - "@typescript-eslint/eslint-plugin": "^8.5.0", + "@typescript-eslint/eslint-plugin": "^8.4.0", "ava": "^6.1.3", "eslint": "^8.57.0", "solana-bankrun": "^0.2.0", diff --git a/single-pool/js/packages/modern/package.json b/single-pool/js/packages/modern/package.json index 77aa8228f20..31463ca173c 100644 --- a/single-pool/js/packages/modern/package.json +++ b/single-pool/js/packages/modern/package.json @@ -17,7 +17,7 @@ }, "devDependencies": { "@types/node": "^22.5.4", - "@typescript-eslint/eslint-plugin": "^8.5.0", + "@typescript-eslint/eslint-plugin": "^8.4.0", "eslint": "^8.57.0", "typescript": "^5.6.2" }, diff --git a/stake-pool/js/package.json b/stake-pool/js/package.json index b58d63fd3e9..d0d6a61028e 100644 --- a/stake-pool/js/package.json +++ b/stake-pool/js/package.json @@ -63,8 +63,8 @@ "@types/jest": "^29.5.12", "@types/node": "^22.5.4", "@types/node-fetch": "^2.6.11", - "@typescript-eslint/eslint-plugin": "^8.5.0", - "@typescript-eslint/parser": "^8.5.0", + "@typescript-eslint/eslint-plugin": "^8.4.0", + "@typescript-eslint/parser": "^8.4.0", "cross-env": "^7.0.3", "eslint": "^8.57.0", "jest": "^29.0.0", diff --git a/token-group/js/package.json b/token-group/js/package.json index 4cbd520f5eb..87c80d64af0 100644 --- a/token-group/js/package.json +++ b/token-group/js/package.json @@ -54,8 +54,8 @@ "@types/chai": "^4.3.19", "@types/mocha": "^10.0.7", "@types/node": "^22.5.4", - "@typescript-eslint/eslint-plugin": "^8.5.0", - "@typescript-eslint/parser": "^8.5.0", + "@typescript-eslint/eslint-plugin": "^8.4.0", + "@typescript-eslint/parser": "^8.4.0", "chai": "^5.1.1", "eslint": "^8.57.0", "eslint-plugin-require-extensions": "^0.1.1", diff --git a/token-lending/js/package.json b/token-lending/js/package.json index b5eb21472a4..12b6299a7e7 100644 --- a/token-lending/js/package.json +++ b/token-lending/js/package.json @@ -48,8 +48,8 @@ "@solana/web3.js": "^1.95.3", "@types/eslint": "^8.56.7", "@types/node": "^22.5.4", - "@typescript-eslint/eslint-plugin": "^8.5.0", - "@typescript-eslint/parser": "^8.5.0", + "@typescript-eslint/eslint-plugin": "^8.4.0", + "@typescript-eslint/parser": "^8.4.0", "eslint": "^8.57.0", "gh-pages": "^6.1.1", "rollup": "^4.21.2", diff --git a/token-metadata/js/package.json b/token-metadata/js/package.json index 446c0cd367e..a277a0ff008 100644 --- a/token-metadata/js/package.json +++ b/token-metadata/js/package.json @@ -54,8 +54,8 @@ "@types/chai": "^4.3.19", "@types/mocha": "^10.0.7", "@types/node": "^22.5.4", - "@typescript-eslint/eslint-plugin": "^8.5.0", - "@typescript-eslint/parser": "^8.5.0", + "@typescript-eslint/eslint-plugin": "^8.4.0", + "@typescript-eslint/parser": "^8.4.0", "chai": "^5.1.1", "eslint": "^8.57.0", "eslint-plugin-require-extensions": "^0.1.1", diff --git a/token-swap/js/package.json b/token-swap/js/package.json index 424ab0793f0..937d0de02d2 100644 --- a/token-swap/js/package.json +++ b/token-swap/js/package.json @@ -55,8 +55,8 @@ "@types/chai-as-promised": "^8.0.0", "@types/chai": "^4.3.19", "@types/mocha": "^10.0.7", - "@typescript-eslint/eslint-plugin": "^8.5.0", - "@typescript-eslint/parser": "^8.5.0", + "@typescript-eslint/eslint-plugin": "^8.4.0", + "@typescript-eslint/parser": "^8.4.0", "eslint": "^8.57.0", "eslint-plugin-import": "^2.30.0", "eslint-plugin-require-extensions": "^0.1.1", diff --git a/token/js/package.json b/token/js/package.json index 4cb51302f37..43e57b3529e 100644 --- a/token/js/package.json +++ b/token/js/package.json @@ -68,8 +68,8 @@ "@types/mocha": "^10.0.7", "@types/node": "^22.5.4", "@types/node-fetch": "^2.6.11", - "@typescript-eslint/eslint-plugin": "^8.5.0", - "@typescript-eslint/parser": "^8.5.0", + "@typescript-eslint/eslint-plugin": "^8.4.0", + "@typescript-eslint/parser": "^8.4.0", "chai": "^5.1.1", "chai-as-promised": "^8.0.0", "eslint": "^8.57.0", From 4ea1cf38b5a73acc985e39b2b63a7b3d7b6c4719 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Sep 2024 12:19:24 +0200 Subject: [PATCH 28/39] build(deps): bump body-parser and express in /docs (#7263) Bumps [body-parser](https://github.com/expressjs/body-parser) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together. Updates `body-parser` from 1.20.2 to 1.20.3 - [Release notes](https://github.com/expressjs/body-parser/releases) - [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md) - [Commits](https://github.com/expressjs/body-parser/compare/1.20.2...1.20.3) Updates `express` from 4.19.2 to 4.21.0 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md) - [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.0) --- updated-dependencies: - dependency-name: body-parser dependency-type: indirect - dependency-name: express dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/package-lock.json | 467 +++++++++++++++++++++++++++++------------ 1 file changed, 335 insertions(+), 132 deletions(-) diff --git a/docs/package-lock.json b/docs/package-lock.json index a89dea5153d..b38b7e9722e 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -5646,9 +5646,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -5658,7 +5658,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -5906,12 +5906,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7094,6 +7100,22 @@ "node": ">=10" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/define-lazy-prop": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", @@ -7452,9 +7474,9 @@ } }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "engines": { "node": ">= 0.8" } @@ -7534,6 +7556,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz", @@ -8088,36 +8129,36 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -8158,9 +8199,9 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/express/node_modules/range-parser": { "version": "1.2.1", @@ -8379,12 +8420,12 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -8740,9 +8781,12 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/functional-red-black-tree": { "version": "1.0.1", @@ -8777,13 +8821,18 @@ } }, "node_modules/get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8952,6 +9001,17 @@ "node": ">= 4" } }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/got": { "version": "12.6.1", "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", @@ -9041,10 +9101,32 @@ "node": ">=4" } }, - "node_modules/has-symbols": { + "node_modules/has-property-descriptors": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "engines": { "node": ">= 0.4" }, @@ -9082,6 +9164,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hast-util-from-parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-6.0.1.tgz", @@ -11712,9 +11805,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -13859,9 +13955,12 @@ } }, "node_modules/object-inspect": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", - "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -15179,11 +15278,11 @@ "integrity": "sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA==" }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -16937,9 +17036,9 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -16972,6 +17071,14 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -17079,19 +17186,35 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -17162,13 +17285,17 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -23317,9 +23444,9 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" }, "body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "requires": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -23329,7 +23456,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -23496,12 +23623,15 @@ } }, "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" } }, "callsites": { @@ -24301,6 +24431,16 @@ "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, "define-lazy-prop": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", @@ -24567,9 +24707,9 @@ "integrity": "sha512-dqx7eA9YaqyvYtUhJwT4rC1HIp82j5ybS1/vQ42ur+jBe17dJMwZE4+gvL1XadSFfxaPFFGt3Xsw+Y8akThDlw==" }, "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" }, "enhanced-resolve": { "version": "5.17.1", @@ -24628,6 +24768,19 @@ "unbox-primitive": "^1.0.1" } }, + "es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "requires": { + "get-intrinsic": "^1.2.4" + } + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, "es-module-lexer": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz", @@ -25023,36 +25176,36 @@ } }, "express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -25087,9 +25240,9 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "range-parser": { "version": "1.2.1", @@ -25253,12 +25406,12 @@ } }, "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "requires": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -25495,9 +25648,9 @@ "optional": true }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "functional-red-black-tree": { "version": "1.0.1", @@ -25526,13 +25679,15 @@ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" }, "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" } }, "get-own-enumerable-property-symbols": { @@ -25653,6 +25808,14 @@ } } }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, "got": { "version": "12.6.1", "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", @@ -25718,10 +25881,23 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, - "has-symbols": { + "has-property-descriptors": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "requires": { + "es-define-property": "^1.0.0" + } + }, + "has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==" + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, "has-tostringtag": { "version": "1.0.0", @@ -25741,6 +25917,14 @@ "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-3.0.0.tgz", "integrity": "sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==" }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, "hast-util-from-parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-6.0.1.tgz", @@ -27626,9 +27810,9 @@ } }, "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" }, "merge-stream": { "version": "2.0.0", @@ -28820,9 +29004,9 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-inspect": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", - "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==" }, "object-keys": { "version": "1.1.1", @@ -29697,11 +29881,11 @@ "integrity": "sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA==" }, "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "requires": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" } }, "queue": { @@ -30961,9 +31145,9 @@ } }, "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "requires": { "debug": "2.6.9", "depd": "2.0.0", @@ -30995,6 +31179,11 @@ } } }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -31091,14 +31280,27 @@ } }, "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "requires": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" + } + }, + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" } }, "setimmediate": { @@ -31153,13 +31355,14 @@ } }, "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" } }, "signal-exit": { From d86ecb30873a63c75f25596109a76a4df0b9d162 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Sep 2024 12:43:18 +0200 Subject: [PATCH 29/39] build(deps): bump uint from 0.9.1 to 0.10.0 (#7261) * build(deps): bump uint from 0.9.1 to 0.10.0 Bumps [uint](https://github.com/paritytech/parity-common) from 0.9.1 to 0.10.0. - [Commits](https://github.com/paritytech/parity-common/compare/uint-v0.9.1...uint-v0.10.0) --- updated-dependencies: - dependency-name: uint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Allow missing docs for new version --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jon C --- Cargo.lock | 4 ++-- binary-option/program/Cargo.toml | 2 +- binary-oracle-pair/program/Cargo.toml | 2 +- libraries/math/Cargo.toml | 2 +- libraries/math/src/uint.rs | 1 + token-lending/program/Cargo.toml | 2 +- token-lending/program/src/math/decimal.rs | 1 + token-lending/program/src/math/rate.rs | 1 + 8 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f3a8ca3820..4a88221431a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8839,9 +8839,9 @@ checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" [[package]] name = "uint" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6470ab50f482bde894a037a57064480a246dbfdd5960bd65a44824693f08da5f" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" dependencies = [ "byteorder", "crunchy", diff --git a/binary-option/program/Cargo.toml b/binary-option/program/Cargo.toml index fc7d4c68d69..001184cd43b 100644 --- a/binary-option/program/Cargo.toml +++ b/binary-option/program/Cargo.toml @@ -16,7 +16,7 @@ spl-token = { version = "6.0", path = "../../token/program", features = [ ] } arrayref = "0.3.8" borsh = "1.5.1" -uint = "0.9" +uint = "0.10" [lib] crate-type = ["cdylib", "lib"] diff --git a/binary-oracle-pair/program/Cargo.toml b/binary-oracle-pair/program/Cargo.toml index 2042a33c970..92579f83566 100644 --- a/binary-oracle-pair/program/Cargo.toml +++ b/binary-oracle-pair/program/Cargo.toml @@ -18,7 +18,7 @@ spl-token = { version = "6.0", path = "../../token/program", features = [ "no-entrypoint", ] } thiserror = "1.0" -uint = "0.9" +uint = "0.10" borsh = "1.5.1" [dev-dependencies] diff --git a/libraries/math/Cargo.toml b/libraries/math/Cargo.toml index 0c9d3a7838f..d2ae096756b 100644 --- a/libraries/math/Cargo.toml +++ b/libraries/math/Cargo.toml @@ -17,7 +17,7 @@ num-derive = "0.4" num-traits = "0.2" solana-program = "2.0.3" thiserror = "1.0" -uint = "0.9" +uint = "0.10" [dev-dependencies] proptest = "1.5.0" diff --git a/libraries/math/src/uint.rs b/libraries/math/src/uint.rs index da7adc00d20..43f952fc3b8 100644 --- a/libraries/math/src/uint.rs +++ b/libraries/math/src/uint.rs @@ -5,6 +5,7 @@ #![allow(clippy::assign_op_pattern)] #![allow(clippy::ptr_offset_with_cast)] #![allow(clippy::manual_range_contains)] +#![allow(missing_docs)] use uint::construct_uint; diff --git a/token-lending/program/Cargo.toml b/token-lending/program/Cargo.toml index f6f8871bcf7..9bdfb9d8b83 100644 --- a/token-lending/program/Cargo.toml +++ b/token-lending/program/Cargo.toml @@ -19,7 +19,7 @@ num-traits = "0.2" solana-program = "2.0.3" spl-token = { version = "6.0", path = "../../token/program", features = [ "no-entrypoint" ] } thiserror = "1.0" -uint = "0.9" +uint = "0.10" [dev-dependencies] assert_matches = "1.5.0" diff --git a/token-lending/program/src/math/decimal.rs b/token-lending/program/src/math/decimal.rs index 8e835b15981..957778ab163 100644 --- a/token-lending/program/src/math/decimal.rs +++ b/token-lending/program/src/math/decimal.rs @@ -11,6 +11,7 @@ #![allow(clippy::assign_op_pattern)] #![allow(clippy::ptr_offset_with_cast)] #![allow(clippy::manual_range_contains)] +#![allow(missing_docs)] use { crate::{ diff --git a/token-lending/program/src/math/rate.rs b/token-lending/program/src/math/rate.rs index 166b6d5bf9f..12d3090486f 100644 --- a/token-lending/program/src/math/rate.rs +++ b/token-lending/program/src/math/rate.rs @@ -17,6 +17,7 @@ #![allow(clippy::ptr_offset_with_cast)] #![allow(clippy::reversed_empty_ranges)] #![allow(clippy::manual_range_contains)] +#![allow(missing_docs)] use { crate::{ From 1f8b57d35b4bc00dac8fb2426ac8a1e998882a3e Mon Sep 17 00:00:00 2001 From: Joe C Date: Thu, 12 Sep 2024 19:29:17 +0800 Subject: [PATCH 30/39] CI: add matrix for node versions to JS PRs (#7266) --- .github/workflows/pull-request-js.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pull-request-js.yml b/.github/workflows/pull-request-js.yml index 88d018875cc..29ce10a05bf 100644 --- a/.github/workflows/pull-request-js.yml +++ b/.github/workflows/pull-request-js.yml @@ -40,29 +40,32 @@ jobs: js-test: strategy: matrix: + node-version: [16.x, 18.x, 20.x] package: [ account-compression, libraries, memo, name-service, - single-pool, stake-pool, token, token-group, - token-lending, token-metadata, token-swap, ] + include: + # Restrict single-pool and token-lending to supported Node.js versions. + - package: single-pool + node-version: 20.5 + - package: token-lending + node-version: 18.5 runs-on: ubuntu-latest - env: - NODE_VERSION: 20.5 steps: - uses: actions/checkout@v4 - - name: Use Node.js ${{ env.NODE_VERSION }} + - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: - node-version: ${{ env.NODE_VERSION }} + node-version: ${{ matrix.node-version }} - uses: pnpm/action-setup@v4 - uses: actions/cache@v4 with: From 34295e5f2114bd1a1102bcc632ac210465b5e3f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Sep 2024 13:53:21 +0200 Subject: [PATCH 31/39] build(deps-dev): bump rollup from 4.21.2 to 4.21.3 (#7268) Bumps [rollup](https://github.com/rollup/rollup) from 4.21.2 to 4.21.3. - [Release notes](https://github.com/rollup/rollup/releases) - [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md) - [Commits](https://github.com/rollup/rollup/compare/v4.21.2...v4.21.3) --- updated-dependencies: - dependency-name: rollup dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 184 +++++++++++++++++----------------- stake-pool/js/package.json | 2 +- token-lending/js/package.json | 2 +- 3 files changed, 94 insertions(+), 94 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 337dbe7e1d7..20c04e5b58c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -392,25 +392,25 @@ importers: devDependencies: '@rollup/plugin-alias': specifier: ^5.1.0 - version: 5.1.0(rollup@4.21.2) + version: 5.1.0(rollup@4.21.3) '@rollup/plugin-commonjs': specifier: ^26.0.1 - version: 26.0.1(rollup@4.21.2) + version: 26.0.1(rollup@4.21.3) '@rollup/plugin-json': specifier: ^6.1.0 - version: 6.1.0(rollup@4.21.2) + version: 6.1.0(rollup@4.21.3) '@rollup/plugin-multi-entry': specifier: ^6.0.0 - version: 6.0.1(rollup@4.21.2) + version: 6.0.1(rollup@4.21.3) '@rollup/plugin-node-resolve': specifier: ^15.0.2 - version: 15.2.3(rollup@4.21.2) + version: 15.2.3(rollup@4.21.3) '@rollup/plugin-terser': specifier: ^0.4.4 - version: 0.4.4(rollup@4.21.2) + version: 0.4.4(rollup@4.21.3) '@rollup/plugin-typescript': specifier: ^11.1.6 - version: 11.1.6(rollup@4.21.2)(tslib@2.7.0)(typescript@5.6.2) + version: 11.1.6(rollup@4.21.3)(tslib@2.7.0)(typescript@5.6.2) '@types/bn.js': specifier: ^5.1.0 version: 5.1.5 @@ -442,11 +442,11 @@ importers: specifier: ^6.0.1 version: 6.0.1 rollup: - specifier: ^4.21.2 - version: 4.21.2 + specifier: ^4.21.3 + version: 4.21.3 rollup-plugin-dts: specifier: ^6.1.1 - version: 6.1.1(rollup@4.21.2)(typescript@5.6.2) + version: 6.1.1(rollup@4.21.3)(typescript@5.6.2) ts-jest: specifier: ^29.2.5 version: 29.2.5(@babel/core@7.23.2)(jest@29.7.0)(typescript@5.6.2) @@ -529,13 +529,13 @@ importers: devDependencies: '@rollup/plugin-commonjs': specifier: ^26.0.1 - version: 26.0.1(rollup@4.21.2) + version: 26.0.1(rollup@4.21.3) '@rollup/plugin-node-resolve': specifier: ^15.0.2 - version: 15.2.3(rollup@4.21.2) + version: 15.2.3(rollup@4.21.3) '@rollup/plugin-typescript': specifier: ^11.1.6 - version: 11.1.6(rollup@4.21.2)(tslib@2.7.0)(typescript@5.6.2) + version: 11.1.6(rollup@4.21.3)(tslib@2.7.0)(typescript@5.6.2) '@solana/spl-token': specifier: 0.4.6 version: 0.4.6(@solana/web3.js@1.95.3)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.2) @@ -561,8 +561,8 @@ importers: specifier: ^6.1.1 version: 6.1.1 rollup: - specifier: ^4.21.2 - version: 4.21.2 + specifier: ^4.21.3 + version: 4.21.3 ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@22.5.4)(typescript@5.6.2) @@ -1881,7 +1881,7 @@ packages: engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} dev: true - /@rollup/plugin-alias@5.1.0(rollup@4.21.2): + /@rollup/plugin-alias@5.1.0(rollup@4.21.3): resolution: {integrity: sha512-lpA3RZ9PdIG7qqhEfv79tBffNaoDuukFDrmhLqg9ifv99u/ehn+lOg30x2zmhf8AQqQUZaMk/B9fZraQ6/acDQ==} engines: {node: '>=14.0.0'} peerDependencies: @@ -1890,11 +1890,11 @@ packages: rollup: optional: true dependencies: - rollup: 4.21.2 + rollup: 4.21.3 slash: 4.0.0 dev: true - /@rollup/plugin-commonjs@26.0.1(rollup@4.21.2): + /@rollup/plugin-commonjs@26.0.1(rollup@4.21.3): resolution: {integrity: sha512-UnsKoZK6/aGIH6AdkptXhNvhaqftcjq3zZdT+LY5Ftms6JR06nADcDsYp5hTU9E2lbJUEOhdlY5J4DNTneM+jQ==} engines: {node: '>=16.0.0 || 14 >= 14.17'} peerDependencies: @@ -1903,16 +1903,16 @@ packages: rollup: optional: true dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.21.2) + '@rollup/pluginutils': 5.1.0(rollup@4.21.3) commondir: 1.0.1 estree-walker: 2.0.2 glob: 10.4.1 is-reference: 1.2.1 magic-string: 0.30.10 - rollup: 4.21.2 + rollup: 4.21.3 dev: true - /@rollup/plugin-json@6.1.0(rollup@4.21.2): + /@rollup/plugin-json@6.1.0(rollup@4.21.3): resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==} engines: {node: '>=14.0.0'} peerDependencies: @@ -1921,11 +1921,11 @@ packages: rollup: optional: true dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.21.2) - rollup: 4.21.2 + '@rollup/pluginutils': 5.1.0(rollup@4.21.3) + rollup: 4.21.3 dev: true - /@rollup/plugin-multi-entry@6.0.1(rollup@4.21.2): + /@rollup/plugin-multi-entry@6.0.1(rollup@4.21.3): resolution: {integrity: sha512-AXm6toPyTSfbYZWghQGbom1Uh7dHXlrGa+HoiYNhQtDUE3Q7LqoUYdVQx9E1579QWS1uOiu+cZRSE4okO7ySgw==} engines: {node: '>=14.0.0'} peerDependencies: @@ -1934,12 +1934,12 @@ packages: rollup: optional: true dependencies: - '@rollup/plugin-virtual': 3.0.2(rollup@4.21.2) + '@rollup/plugin-virtual': 3.0.2(rollup@4.21.3) matched: 5.0.1 - rollup: 4.21.2 + rollup: 4.21.3 dev: true - /@rollup/plugin-node-resolve@15.2.3(rollup@4.21.2): + /@rollup/plugin-node-resolve@15.2.3(rollup@4.21.3): resolution: {integrity: sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==} engines: {node: '>=14.0.0'} peerDependencies: @@ -1948,16 +1948,16 @@ packages: rollup: optional: true dependencies: - '@rollup/pluginutils': 5.0.5(rollup@4.21.2) + '@rollup/pluginutils': 5.0.5(rollup@4.21.3) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-builtin-module: 3.2.1 is-module: 1.0.0 resolve: 1.22.8 - rollup: 4.21.2 + rollup: 4.21.3 dev: true - /@rollup/plugin-terser@0.4.4(rollup@4.21.2): + /@rollup/plugin-terser@0.4.4(rollup@4.21.3): resolution: {integrity: sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==} engines: {node: '>=14.0.0'} peerDependencies: @@ -1966,13 +1966,13 @@ packages: rollup: optional: true dependencies: - rollup: 4.21.2 + rollup: 4.21.3 serialize-javascript: 6.0.1 smob: 1.4.1 terser: 5.24.0 dev: true - /@rollup/plugin-typescript@11.1.6(rollup@4.21.2)(tslib@2.7.0)(typescript@5.6.2): + /@rollup/plugin-typescript@11.1.6(rollup@4.21.3)(tslib@2.7.0)(typescript@5.6.2): resolution: {integrity: sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==} engines: {node: '>=14.0.0'} peerDependencies: @@ -1985,14 +1985,14 @@ packages: tslib: optional: true dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.21.2) + '@rollup/pluginutils': 5.1.0(rollup@4.21.3) resolve: 1.22.8 - rollup: 4.21.2 + rollup: 4.21.3 tslib: 2.7.0 typescript: 5.6.2 dev: true - /@rollup/plugin-virtual@3.0.2(rollup@4.21.2): + /@rollup/plugin-virtual@3.0.2(rollup@4.21.3): resolution: {integrity: sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==} engines: {node: '>=14.0.0'} peerDependencies: @@ -2001,7 +2001,7 @@ packages: rollup: optional: true dependencies: - rollup: 4.21.2 + rollup: 4.21.3 dev: true /@rollup/pluginutils@4.2.1: @@ -2012,7 +2012,7 @@ packages: picomatch: 2.3.1 dev: true - /@rollup/pluginutils@5.0.5(rollup@4.21.2): + /@rollup/pluginutils@5.0.5(rollup@4.21.3): resolution: {integrity: sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==} engines: {node: '>=14.0.0'} peerDependencies: @@ -2024,10 +2024,10 @@ packages: '@types/estree': 1.0.5 estree-walker: 2.0.2 picomatch: 2.3.1 - rollup: 4.21.2 + rollup: 4.21.3 dev: true - /@rollup/pluginutils@5.1.0(rollup@4.21.2): + /@rollup/pluginutils@5.1.0(rollup@4.21.3): resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} engines: {node: '>=14.0.0'} peerDependencies: @@ -2039,131 +2039,131 @@ packages: '@types/estree': 1.0.5 estree-walker: 2.0.2 picomatch: 2.3.1 - rollup: 4.21.2 + rollup: 4.21.3 dev: true - /@rollup/rollup-android-arm-eabi@4.21.2: - resolution: {integrity: sha512-fSuPrt0ZO8uXeS+xP3b+yYTCBUd05MoSp2N/MFOgjhhUhMmchXlpTQrTpI8T+YAwAQuK7MafsCOxW7VrPMrJcg==} + /@rollup/rollup-android-arm-eabi@4.21.3: + resolution: {integrity: sha512-MmKSfaB9GX+zXl6E8z4koOr/xU63AMVleLEa64v7R0QF/ZloMs5vcD1sHgM64GXXS1csaJutG+ddtzcueI/BLg==} cpu: [arm] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-android-arm64@4.21.2: - resolution: {integrity: sha512-xGU5ZQmPlsjQS6tzTTGwMsnKUtu0WVbl0hYpTPauvbRAnmIvpInhJtgjj3mcuJpEiuUw4v1s4BimkdfDWlh7gA==} + /@rollup/rollup-android-arm64@4.21.3: + resolution: {integrity: sha512-zrt8ecH07PE3sB4jPOggweBjJMzI1JG5xI2DIsUbkA+7K+Gkjys6eV7i9pOenNSDJH3eOr/jLb/PzqtmdwDq5g==} cpu: [arm64] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-arm64@4.21.2: - resolution: {integrity: sha512-99AhQ3/ZMxU7jw34Sq8brzXqWH/bMnf7ZVhvLk9QU2cOepbQSVTns6qoErJmSiAvU3InRqC2RRZ5ovh1KN0d0Q==} + /@rollup/rollup-darwin-arm64@4.21.3: + resolution: {integrity: sha512-P0UxIOrKNBFTQaXTxOH4RxuEBVCgEA5UTNV6Yz7z9QHnUJ7eLX9reOd/NYMO3+XZO2cco19mXTxDMXxit4R/eQ==} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-x64@4.21.2: - resolution: {integrity: sha512-ZbRaUvw2iN/y37x6dY50D8m2BnDbBjlnMPotDi/qITMJ4sIxNY33HArjikDyakhSv0+ybdUxhWxE6kTI4oX26w==} + /@rollup/rollup-darwin-x64@4.21.3: + resolution: {integrity: sha512-L1M0vKGO5ASKntqtsFEjTq/fD91vAqnzeaF6sfNAy55aD+Hi2pBI5DKwCO+UNDQHWsDViJLqshxOahXyLSh3EA==} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm-gnueabihf@4.21.2: - resolution: {integrity: sha512-ztRJJMiE8nnU1YFcdbd9BcH6bGWG1z+jP+IPW2oDUAPxPjo9dverIOyXz76m6IPA6udEL12reYeLojzW2cYL7w==} + /@rollup/rollup-linux-arm-gnueabihf@4.21.3: + resolution: {integrity: sha512-btVgIsCjuYFKUjopPoWiDqmoUXQDiW2A4C3Mtmp5vACm7/GnyuprqIDPNczeyR5W8rTXEbkmrJux7cJmD99D2g==} cpu: [arm] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm-musleabihf@4.21.2: - resolution: {integrity: sha512-flOcGHDZajGKYpLV0JNc0VFH361M7rnV1ee+NTeC/BQQ1/0pllYcFmxpagltANYt8FYf9+kL6RSk80Ziwyhr7w==} + /@rollup/rollup-linux-arm-musleabihf@4.21.3: + resolution: {integrity: sha512-zmjbSphplZlau6ZTkxd3+NMtE4UKVy7U4aVFMmHcgO5CUbw17ZP6QCgyxhzGaU/wFFdTfiojjbLG3/0p9HhAqA==} cpu: [arm] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-gnu@4.21.2: - resolution: {integrity: sha512-69CF19Kp3TdMopyteO/LJbWufOzqqXzkrv4L2sP8kfMaAQ6iwky7NoXTp7bD6/irKgknDKM0P9E/1l5XxVQAhw==} + /@rollup/rollup-linux-arm64-gnu@4.21.3: + resolution: {integrity: sha512-nSZfcZtAnQPRZmUkUQwZq2OjQciR6tEoJaZVFvLHsj0MF6QhNMg0fQ6mUOsiCUpTqxTx0/O6gX0V/nYc7LrgPw==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-musl@4.21.2: - resolution: {integrity: sha512-48pD/fJkTiHAZTnZwR0VzHrao70/4MlzJrq0ZsILjLW/Ab/1XlVUStYyGt7tdyIiVSlGZbnliqmult/QGA2O2w==} + /@rollup/rollup-linux-arm64-musl@4.21.3: + resolution: {integrity: sha512-MnvSPGO8KJXIMGlQDYfvYS3IosFN2rKsvxRpPO2l2cum+Z3exiExLwVU+GExL96pn8IP+GdH8Tz70EpBhO0sIQ==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-powerpc64le-gnu@4.21.2: - resolution: {integrity: sha512-cZdyuInj0ofc7mAQpKcPR2a2iu4YM4FQfuUzCVA2u4HI95lCwzjoPtdWjdpDKyHxI0UO82bLDoOaLfpZ/wviyQ==} + /@rollup/rollup-linux-powerpc64le-gnu@4.21.3: + resolution: {integrity: sha512-+W+p/9QNDr2vE2AXU0qIy0qQE75E8RTwTwgqS2G5CRQ11vzq0tbnfBd6brWhS9bCRjAjepJe2fvvkvS3dno+iw==} cpu: [ppc64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-riscv64-gnu@4.21.2: - resolution: {integrity: sha512-RL56JMT6NwQ0lXIQmMIWr1SW28z4E4pOhRRNqwWZeXpRlykRIlEpSWdsgNWJbYBEWD84eocjSGDu/XxbYeCmwg==} + /@rollup/rollup-linux-riscv64-gnu@4.21.3: + resolution: {integrity: sha512-yXH6K6KfqGXaxHrtr+Uoy+JpNlUlI46BKVyonGiaD74ravdnF9BUNC+vV+SIuB96hUMGShhKV693rF9QDfO6nQ==} cpu: [riscv64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-s390x-gnu@4.21.2: - resolution: {integrity: sha512-PMxkrWS9z38bCr3rWvDFVGD6sFeZJw4iQlhrup7ReGmfn7Oukrr/zweLhYX6v2/8J6Cep9IEA/SmjXjCmSbrMQ==} + /@rollup/rollup-linux-s390x-gnu@4.21.3: + resolution: {integrity: sha512-R8cwY9wcnApN/KDYWTH4gV/ypvy9yZUHlbJvfaiXSB48JO3KpwSpjOGqO4jnGkLDSk1hgjYkTbTt6Q7uvPf8eg==} cpu: [s390x] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-gnu@4.21.2: - resolution: {integrity: sha512-B90tYAUoLhU22olrafY3JQCFLnT3NglazdwkHyxNDYF/zAxJt5fJUB/yBoWFoIQ7SQj+KLe3iL4BhOMa9fzgpw==} + /@rollup/rollup-linux-x64-gnu@4.21.3: + resolution: {integrity: sha512-kZPbX/NOPh0vhS5sI+dR8L1bU2cSO9FgxwM8r7wHzGydzfSjLRCFAT87GR5U9scj2rhzN3JPYVC7NoBbl4FZ0g==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-musl@4.21.2: - resolution: {integrity: sha512-7twFizNXudESmC9oneLGIUmoHiiLppz/Xs5uJQ4ShvE6234K0VB1/aJYU3f/4g7PhssLGKBVCC37uRkkOi8wjg==} + /@rollup/rollup-linux-x64-musl@4.21.3: + resolution: {integrity: sha512-S0Yq+xA1VEH66uiMNhijsWAafffydd2X5b77eLHfRmfLsRSpbiAWiRHV6DEpz6aOToPsgid7TI9rGd6zB1rhbg==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-arm64-msvc@4.21.2: - resolution: {integrity: sha512-9rRero0E7qTeYf6+rFh3AErTNU1VCQg2mn7CQcI44vNUWM9Ze7MSRS/9RFuSsox+vstRt97+x3sOhEey024FRQ==} + /@rollup/rollup-win32-arm64-msvc@4.21.3: + resolution: {integrity: sha512-9isNzeL34yquCPyerog+IMCNxKR8XYmGd0tHSV+OVx0TmE0aJOo9uw4fZfUuk2qxobP5sug6vNdZR6u7Mw7Q+Q==} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-ia32-msvc@4.21.2: - resolution: {integrity: sha512-5rA4vjlqgrpbFVVHX3qkrCo/fZTj1q0Xxpg+Z7yIo3J2AilW7t2+n6Q8Jrx+4MrYpAnjttTYF8rr7bP46BPzRw==} + /@rollup/rollup-win32-ia32-msvc@4.21.3: + resolution: {integrity: sha512-nMIdKnfZfzn1Vsk+RuOvl43ONTZXoAPUUxgcU0tXooqg4YrAqzfKzVenqqk2g5efWh46/D28cKFrOzDSW28gTA==} cpu: [ia32] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-x64-msvc@4.21.2: - resolution: {integrity: sha512-6UUxd0+SKomjdzuAcp+HAmxw1FlGBnl1v2yEPSabtx4lBfdXHDVsW7+lQkgz9cNFJGY3AWR7+V8P5BqkD9L9nA==} + /@rollup/rollup-win32-x64-msvc@4.21.3: + resolution: {integrity: sha512-fOvu7PCQjAj4eWDEuD8Xz5gpzFqXzGlxHZozHP4b9Jxv9APtdxL6STqztDzMLuRXEc4UpXGGhx029Xgm91QBeA==} cpu: [x64] os: [win32] requiresBuild: true @@ -7413,7 +7413,7 @@ packages: package-json-from-dist: 1.0.0 dev: true - /rollup-plugin-dts@6.1.1(rollup@4.21.2)(typescript@5.6.2): + /rollup-plugin-dts@6.1.1(rollup@4.21.3)(typescript@5.6.2): resolution: {integrity: sha512-aSHRcJ6KG2IHIioYlvAOcEq6U99sVtqDDKVhnwt70rW6tsz3tv5OSjEiWcgzfsHdLyGXZ/3b/7b/+Za3Y6r1XA==} engines: {node: '>=16'} peerDependencies: @@ -7421,35 +7421,35 @@ packages: typescript: ^4.5 || ^5.0 dependencies: magic-string: 0.30.10 - rollup: 4.21.2 + rollup: 4.21.3 typescript: 5.6.2 optionalDependencies: '@babel/code-frame': 7.24.2 dev: true - /rollup@4.21.2: - resolution: {integrity: sha512-e3TapAgYf9xjdLvKQCkQTnbTKd4a6jwlpQSJJFokHGaX2IVjoEqkIIhiQfqsi0cdwlOD+tQGuOd5AJkc5RngBw==} + /rollup@4.21.3: + resolution: {integrity: sha512-7sqRtBNnEbcBtMeRVc6VRsJMmpI+JU1z9VTvW8D4gXIYQFz0aLcsE6rRkyghZkLfEgUZgVvOG7A5CVz/VW5GIA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true dependencies: '@types/estree': 1.0.5 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.21.2 - '@rollup/rollup-android-arm64': 4.21.2 - '@rollup/rollup-darwin-arm64': 4.21.2 - '@rollup/rollup-darwin-x64': 4.21.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.21.2 - '@rollup/rollup-linux-arm-musleabihf': 4.21.2 - '@rollup/rollup-linux-arm64-gnu': 4.21.2 - '@rollup/rollup-linux-arm64-musl': 4.21.2 - '@rollup/rollup-linux-powerpc64le-gnu': 4.21.2 - '@rollup/rollup-linux-riscv64-gnu': 4.21.2 - '@rollup/rollup-linux-s390x-gnu': 4.21.2 - '@rollup/rollup-linux-x64-gnu': 4.21.2 - '@rollup/rollup-linux-x64-musl': 4.21.2 - '@rollup/rollup-win32-arm64-msvc': 4.21.2 - '@rollup/rollup-win32-ia32-msvc': 4.21.2 - '@rollup/rollup-win32-x64-msvc': 4.21.2 + '@rollup/rollup-android-arm-eabi': 4.21.3 + '@rollup/rollup-android-arm64': 4.21.3 + '@rollup/rollup-darwin-arm64': 4.21.3 + '@rollup/rollup-darwin-x64': 4.21.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.21.3 + '@rollup/rollup-linux-arm-musleabihf': 4.21.3 + '@rollup/rollup-linux-arm64-gnu': 4.21.3 + '@rollup/rollup-linux-arm64-musl': 4.21.3 + '@rollup/rollup-linux-powerpc64le-gnu': 4.21.3 + '@rollup/rollup-linux-riscv64-gnu': 4.21.3 + '@rollup/rollup-linux-s390x-gnu': 4.21.3 + '@rollup/rollup-linux-x64-gnu': 4.21.3 + '@rollup/rollup-linux-x64-musl': 4.21.3 + '@rollup/rollup-win32-arm64-msvc': 4.21.3 + '@rollup/rollup-win32-ia32-msvc': 4.21.3 + '@rollup/rollup-win32-x64-msvc': 4.21.3 fsevents: 2.3.3 dev: true diff --git a/stake-pool/js/package.json b/stake-pool/js/package.json index d0d6a61028e..0fdd517a7fa 100644 --- a/stake-pool/js/package.json +++ b/stake-pool/js/package.json @@ -69,7 +69,7 @@ "eslint": "^8.57.0", "jest": "^29.0.0", "rimraf": "^6.0.1", - "rollup": "^4.21.2", + "rollup": "^4.21.3", "rollup-plugin-dts": "^6.1.1", "ts-jest": "^29.2.5", "typescript": "^5.6.2" diff --git a/token-lending/js/package.json b/token-lending/js/package.json index 12b6299a7e7..f753e5bd125 100644 --- a/token-lending/js/package.json +++ b/token-lending/js/package.json @@ -52,7 +52,7 @@ "@typescript-eslint/parser": "^8.4.0", "eslint": "^8.57.0", "gh-pages": "^6.1.1", - "rollup": "^4.21.2", + "rollup": "^4.21.3", "ts-node": "^10.9.2", "tslib": "^2.7.0", "typedoc": "^0.26.7", From 012106eadecd690607d3f49d4f1d791c0b89671a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Sep 2024 13:53:29 +0200 Subject: [PATCH 32/39] build(deps-dev): bump tsx from 4.19.0 to 4.19.1 (#7270) Bumps [tsx](https://github.com/privatenumber/tsx) from 4.19.0 to 4.19.1. - [Release notes](https://github.com/privatenumber/tsx/releases) - [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs) - [Commits](https://github.com/privatenumber/tsx/compare/v4.19.0...v4.19.1) --- updated-dependencies: - dependency-name: tsx dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 8 ++++---- single-pool/js/packages/classic/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 20c04e5b58c..deb899d5546 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -335,8 +335,8 @@ importers: specifier: ^0.2.0 version: 0.2.0 tsx: - specifier: ^4.19.0 - version: 4.19.0 + specifier: ^4.19.1 + version: 4.19.1 typescript: specifier: ^5.6.2 version: 5.6.2 @@ -8154,8 +8154,8 @@ packages: typescript: 5.6.2 dev: true - /tsx@4.19.0: - resolution: {integrity: sha512-bV30kM7bsLZKZIOCHeMNVMJ32/LuJzLVajkQI/qf92J2Qr08ueLQvW00PUZGiuLPP760UINwupgUj8qrSCPUKg==} + /tsx@4.19.1: + resolution: {integrity: sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==} engines: {node: '>=18.0.0'} hasBin: true dependencies: diff --git a/single-pool/js/packages/classic/package.json b/single-pool/js/packages/classic/package.json index 636cbd9f0ea..f04939387bb 100644 --- a/single-pool/js/packages/classic/package.json +++ b/single-pool/js/packages/classic/package.json @@ -24,7 +24,7 @@ "ava": "^6.1.3", "eslint": "^8.57.0", "solana-bankrun": "^0.2.0", - "tsx": "^4.19.0", + "tsx": "^4.19.1", "typescript": "^5.6.2" }, "dependencies": { From df498f360402b98dde17922c41fe15e788743aff Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Fri, 13 Sep 2024 17:02:49 +0900 Subject: [PATCH 33/39] [confidential-transfer] Add confidential burn proof generation and extraction (#6955) * add proof generation logic for confidential mint and burn * add proof extraction logic for confidential mint and burn * add tests for confidential mint and burn * Apply suggestions from code review Co-authored-by: Jon C * add equality and range proof to confidential mint proof * rename `aes_key` to `source_aes_key` in confidential burn --------- Co-authored-by: Jon C --- .../proof-extraction/src/burn.rs | 130 ++++++++++++++++ .../proof-extraction/src/encryption.rs | 8 + .../proof-extraction/src/lib.rs | 2 + .../proof-extraction/src/mint.rs | 130 ++++++++++++++++ .../proof-generation/src/burn.rs | 146 ++++++++++++++++++ .../proof-generation/src/encryption.rs | 52 +++++++ .../proof-generation/src/lib.rs | 2 + .../proof-generation/src/mint.rs | 146 ++++++++++++++++++ .../proof-tests/tests/proof_test.rs | 116 +++++++++++++- 9 files changed, 730 insertions(+), 2 deletions(-) create mode 100644 token/confidential-transfer/proof-extraction/src/burn.rs create mode 100644 token/confidential-transfer/proof-extraction/src/mint.rs create mode 100644 token/confidential-transfer/proof-generation/src/burn.rs create mode 100644 token/confidential-transfer/proof-generation/src/mint.rs diff --git a/token/confidential-transfer/proof-extraction/src/burn.rs b/token/confidential-transfer/proof-extraction/src/burn.rs new file mode 100644 index 00000000000..1bfc706e369 --- /dev/null +++ b/token/confidential-transfer/proof-extraction/src/burn.rs @@ -0,0 +1,130 @@ +use { + crate::{encryption::PodBurnAmountCiphertext, errors::TokenProofExtractionError}, + solana_zk_sdk::{ + encryption::pod::elgamal::{PodElGamalCiphertext, PodElGamalPubkey}, + zk_elgamal_proof_program::proof_data::{ + BatchedGroupedCiphertext3HandlesValidityProofContext, BatchedRangeProofContext, + CiphertextCommitmentEqualityProofContext, + }, + }, +}; + +/// The public keys associated with a confidential burn +pub struct BurnPubkeys { + pub source: PodElGamalPubkey, + pub auditor: PodElGamalPubkey, + pub supply: PodElGamalPubkey, +} + +/// The proof context information needed to process a confidential burn +/// instruction +pub struct BurnProofContext { + pub burn_amount_ciphertext_lo: PodBurnAmountCiphertext, + pub burn_amount_ciphertext_hi: PodBurnAmountCiphertext, + pub burn_pubkeys: BurnPubkeys, + pub remaining_balance_ciphertext: PodElGamalCiphertext, +} + +impl BurnProofContext { + pub fn verify_and_extract( + equality_proof_context: &CiphertextCommitmentEqualityProofContext, + ciphertext_validity_proof_context: &BatchedGroupedCiphertext3HandlesValidityProofContext, + range_proof_context: &BatchedRangeProofContext, + ) -> Result { + // The equality proof context consists of the source ElGamal public key, the new + // source available balance ciphertext, and the new source avaialble + // balance commitment. The public key should be checked with ciphertext + // validity proof context for consistency and the commitment should be + // checked with range proof for consistency. The public key and + // the cihpertext should be returned as part of `BurnProofContext`. + let CiphertextCommitmentEqualityProofContext { + pubkey: source_elgamal_pubkey_from_equality_proof, + ciphertext: remaining_balance_ciphertext, + commitment: remaining_balance_commitment, + } = equality_proof_context; + + // The ciphertext validity proof context consists of the source ElGamal public + // key, the auditor ElGamal public key, and the grouped ElGamal + // ciphertexts for the low and high bits of the burn amount. The source + // ElGamal public key should be checked with equality + // proof for consistency and the rest of the data should be returned as part of + // `BurnProofContext`. + let BatchedGroupedCiphertext3HandlesValidityProofContext { + first_pubkey: source_elgamal_pubkey_from_validity_proof, + second_pubkey: auditor_elgamal_pubkey, + third_pubkey: supply_elgamal_pubkey, + grouped_ciphertext_lo: burn_amount_ciphertext_lo, + grouped_ciphertext_hi: burn_amount_ciphertext_hi, + } = ciphertext_validity_proof_context; + + // The range proof context consists of the Pedersen commitments and bit-lengths + // for which the range proof is proved. The commitments must consist of + // three commitments pertaining to the new source available balance, the + // low bits of the burn amount, and high bits of the burn + // amount. These commitments must be checked for bit lengths `64`, `16`, + // and `32`. + let BatchedRangeProofContext { + commitments: range_proof_commitments, + bit_lengths: range_proof_bit_lengths, + } = range_proof_context; + + // check that the source pubkey is consistent between equality and ciphertext + // validity proofs + if source_elgamal_pubkey_from_equality_proof != source_elgamal_pubkey_from_validity_proof { + return Err(TokenProofExtractionError::ElGamalPubkeyMismatch); + } + + // check that the range proof was created for the correct set of Pedersen + // commitments + let burn_amount_commitment_lo = burn_amount_ciphertext_lo.extract_commitment(); + let burn_amount_commitment_hi = burn_amount_ciphertext_hi.extract_commitment(); + + let expected_commitments = [ + *remaining_balance_commitment, + burn_amount_commitment_lo, + burn_amount_commitment_hi, + ]; + + if !range_proof_commitments + .iter() + .zip(expected_commitments.iter()) + .all(|(proof_commitment, expected_commitment)| proof_commitment == expected_commitment) + { + return Err(TokenProofExtractionError::PedersenCommitmentMismatch); + } + + // check that the range proof was created for the correct number of bits + const REMAINING_BALANCE_BIT_LENGTH: u8 = 64; + const BURN_AMOUNT_LO_BIT_LENGTH: u8 = 16; + const BURN_AMOUNT_HI_BIT_LENGTH: u8 = 32; + const PADDING_BIT_LENGTH: u8 = 16; + let expected_bit_lengths = [ + REMAINING_BALANCE_BIT_LENGTH, + BURN_AMOUNT_LO_BIT_LENGTH, + BURN_AMOUNT_HI_BIT_LENGTH, + PADDING_BIT_LENGTH, + ] + .iter(); + + if !range_proof_bit_lengths + .iter() + .zip(expected_bit_lengths) + .all(|(proof_len, expected_len)| proof_len == expected_len) + { + return Err(TokenProofExtractionError::RangeProofLengthMismatch); + } + + let burn_pubkeys = BurnPubkeys { + source: *source_elgamal_pubkey_from_equality_proof, + auditor: *auditor_elgamal_pubkey, + supply: *supply_elgamal_pubkey, + }; + + Ok(BurnProofContext { + burn_amount_ciphertext_lo: PodBurnAmountCiphertext(*burn_amount_ciphertext_lo), + burn_amount_ciphertext_hi: PodBurnAmountCiphertext(*burn_amount_ciphertext_hi), + burn_pubkeys, + remaining_balance_ciphertext: *remaining_balance_ciphertext, + }) + } +} diff --git a/token/confidential-transfer/proof-extraction/src/encryption.rs b/token/confidential-transfer/proof-extraction/src/encryption.rs index 34ca2fff156..b0a991235f3 100644 --- a/token/confidential-transfer/proof-extraction/src/encryption.rs +++ b/token/confidential-transfer/proof-extraction/src/encryption.rs @@ -46,3 +46,11 @@ impl PodFeeCiphertext { .map_err(|_| TokenProofExtractionError::CiphertextExtraction) } } + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(C)] +pub struct PodBurnAmountCiphertext(pub(crate) PodGroupedElGamalCiphertext3Handles); + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(C)] +pub struct PodMintAmountCiphertext(pub(crate) PodGroupedElGamalCiphertext3Handles); diff --git a/token/confidential-transfer/proof-extraction/src/lib.rs b/token/confidential-transfer/proof-extraction/src/lib.rs index 15cee9bdc5d..f3f99bc1072 100644 --- a/token/confidential-transfer/proof-extraction/src/lib.rs +++ b/token/confidential-transfer/proof-extraction/src/lib.rs @@ -1,5 +1,7 @@ +pub mod burn; pub mod encryption; pub mod errors; +pub mod mint; pub mod transfer; pub mod transfer_with_fee; pub mod withdraw; diff --git a/token/confidential-transfer/proof-extraction/src/mint.rs b/token/confidential-transfer/proof-extraction/src/mint.rs new file mode 100644 index 00000000000..cffe8616dc6 --- /dev/null +++ b/token/confidential-transfer/proof-extraction/src/mint.rs @@ -0,0 +1,130 @@ +use { + crate::{encryption::PodMintAmountCiphertext, errors::TokenProofExtractionError}, + solana_zk_sdk::{ + encryption::pod::elgamal::{PodElGamalCiphertext, PodElGamalPubkey}, + zk_elgamal_proof_program::proof_data::{ + BatchedGroupedCiphertext3HandlesValidityProofContext, BatchedRangeProofContext, + CiphertextCommitmentEqualityProofContext, + }, + }, +}; + +/// The public keys associated with a confidential mint +pub struct MintPubkeys { + pub destination: PodElGamalPubkey, + pub auditor: PodElGamalPubkey, + pub supply: PodElGamalPubkey, +} + +/// The proof context information needed to process a confidential mint +/// instruction +pub struct MintProofContext { + pub mint_amount_ciphertext_lo: PodMintAmountCiphertext, + pub mint_amount_ciphertext_hi: PodMintAmountCiphertext, + pub mint_pubkeys: MintPubkeys, + pub new_supply_ciphertext: PodElGamalCiphertext, +} + +impl MintProofContext { + pub fn verify_and_extract( + equality_proof_context: &CiphertextCommitmentEqualityProofContext, + ciphertext_validity_proof_context: &BatchedGroupedCiphertext3HandlesValidityProofContext, + range_proof_context: &BatchedRangeProofContext, + ) -> Result { + // The equality proof context consists of the supply ElGamal public key, the new + // supply ciphertext, and the new supply commitment. The supply ElGamal + // public key should be checked with ciphertext validity proof for + // consistency and the new supply commitment should be checked with + // range proof for consistency. The new supply ciphertext should be + // returned as part of `MintProofContext`. + let CiphertextCommitmentEqualityProofContext { + pubkey: supply_elgamal_pubkey_from_equality_proof, + ciphertext: new_supply_ciphertext, + commitment: new_supply_commitment, + } = equality_proof_context; + + // The ciphertext validity proof context consists of the destination ElGamal + // public key, the auditor ElGamal public key, and the grouped ElGamal + // ciphertexts for the low and high bits of the mint amount. These + // fields should be returned as part of `MintProofContext`. + let BatchedGroupedCiphertext3HandlesValidityProofContext { + first_pubkey: destination_elgamal_pubkey, + second_pubkey: auditor_elgamal_pubkey, + third_pubkey: supply_elgamal_pubkey_from_ciphertext_validity_proof, + grouped_ciphertext_lo: mint_amount_ciphertext_lo, + grouped_ciphertext_hi: mint_amount_ciphertext_hi, + } = ciphertext_validity_proof_context; + + // The range proof context consists of the Pedersen commitments and bit-lengths + // for which the range proof is proved. The commitments must consist of + // two commitments pertaining to the the + // low bits of the mint amount, and high bits of the mint + // amount. These commitments must be checked for bit lengths `16` and + // and `32`. + let BatchedRangeProofContext { + commitments: range_proof_commitments, + bit_lengths: range_proof_bit_lengths, + } = range_proof_context; + + // check that the supply pubkey is consistent between equality and ciphertext + // validity proofs + if supply_elgamal_pubkey_from_equality_proof + != supply_elgamal_pubkey_from_ciphertext_validity_proof + { + return Err(TokenProofExtractionError::ElGamalPubkeyMismatch); + } + + // check that the range proof was created for the correct set of Pedersen + // commitments + let mint_amount_commitment_lo = mint_amount_ciphertext_lo.extract_commitment(); + let mint_amount_commitment_hi = mint_amount_ciphertext_hi.extract_commitment(); + + let expected_commitments = [ + *new_supply_commitment, + mint_amount_commitment_lo, + mint_amount_commitment_hi, + ]; + + if !range_proof_commitments + .iter() + .zip(expected_commitments.iter()) + .all(|(proof_commitment, expected_commitment)| proof_commitment == expected_commitment) + { + return Err(TokenProofExtractionError::PedersenCommitmentMismatch); + } + + // check that the range proof was created for the correct number of bits + const NEW_SUPPLY_BIT_LENGTH: u8 = 64; + const MINT_AMOUNT_LO_BIT_LENGTH: u8 = 16; + const MINT_AMOUNT_HI_BIT_LENGTH: u8 = 32; + const PADDING_BIT_LENGTH: u8 = 16; + let expected_bit_lengths = [ + NEW_SUPPLY_BIT_LENGTH, + MINT_AMOUNT_LO_BIT_LENGTH, + MINT_AMOUNT_HI_BIT_LENGTH, + PADDING_BIT_LENGTH, + ] + .iter(); + + if !range_proof_bit_lengths + .iter() + .zip(expected_bit_lengths) + .all(|(proof_len, expected_len)| proof_len == expected_len) + { + return Err(TokenProofExtractionError::RangeProofLengthMismatch); + } + + let mint_pubkeys = MintPubkeys { + destination: *destination_elgamal_pubkey, + auditor: *auditor_elgamal_pubkey, + supply: *supply_elgamal_pubkey_from_equality_proof, + }; + + Ok(MintProofContext { + mint_amount_ciphertext_lo: PodMintAmountCiphertext(*mint_amount_ciphertext_lo), + mint_amount_ciphertext_hi: PodMintAmountCiphertext(*mint_amount_ciphertext_hi), + mint_pubkeys, + new_supply_ciphertext: *new_supply_ciphertext, + }) + } +} diff --git a/token/confidential-transfer/proof-generation/src/burn.rs b/token/confidential-transfer/proof-generation/src/burn.rs new file mode 100644 index 00000000000..9b927384ac8 --- /dev/null +++ b/token/confidential-transfer/proof-generation/src/burn.rs @@ -0,0 +1,146 @@ +use { + crate::{ + encryption::BurnAmountCiphertext, errors::TokenProofGenerationError, + try_combine_lo_hi_ciphertexts, try_split_u64, + }, + solana_zk_sdk::{ + encryption::{ + auth_encryption::{AeCiphertext, AeKey}, + elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey}, + pedersen::Pedersen, + }, + zk_elgamal_proof_program::proof_data::{ + BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data, + CiphertextCommitmentEqualityProofData, + }, + }, +}; + +const REMAINING_BALANCE_BIT_LENGTH: usize = 64; +const BURN_AMOUNT_LO_BIT_LENGTH: usize = 16; +const BURN_AMOUNT_HI_BIT_LENGTH: usize = 32; +/// The padding bit length in range proofs to make the bit-length power-of-2 +const RANGE_PROOF_PADDING_BIT_LENGTH: usize = 16; + +/// The proof data required for a confidential burn instruction +pub struct BurnProofData { + pub equality_proof_data: CiphertextCommitmentEqualityProofData, + pub ciphertext_validity_proof_data: BatchedGroupedCiphertext3HandlesValidityProofData, + pub range_proof_data: BatchedRangeProofU128Data, +} + +pub fn burn_split_proof_data( + current_available_balance_ciphertext: &ElGamalCiphertext, + current_decryptable_available_balance: &AeCiphertext, + burn_amount: u64, + source_elgamal_keypair: &ElGamalKeypair, + source_aes_key: &AeKey, + auditor_elgamal_pubkey: &ElGamalPubkey, + supply_elgamal_pubkey: &ElGamalPubkey, +) -> Result { + // split the burn amount into low and high bits + let (burn_amount_lo, burn_amount_hi) = try_split_u64(burn_amount, BURN_AMOUNT_LO_BIT_LENGTH) + .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?; + + // encrypt the burn amount under the source and auditor's ElGamal public key + let (burn_amount_ciphertext_lo, burn_amount_opening_lo) = BurnAmountCiphertext::new( + burn_amount_lo, + source_elgamal_keypair.pubkey(), + auditor_elgamal_pubkey, + supply_elgamal_pubkey, + ); + + let (burn_amount_ciphertext_hi, burn_amount_opening_hi) = BurnAmountCiphertext::new( + burn_amount_hi, + source_elgamal_keypair.pubkey(), + auditor_elgamal_pubkey, + supply_elgamal_pubkey, + ); + + // decrypt the current available balance at the source + let current_decrypted_available_balance = current_decryptable_available_balance + .decrypt(source_aes_key) + .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?; + + // compute the remaining balance ciphertext + let burn_amount_ciphertext_source_lo = burn_amount_ciphertext_lo + .0 + .to_elgamal_ciphertext(0) + .unwrap(); + let burn_amount_ciphertext_source_hi = burn_amount_ciphertext_hi + .0 + .to_elgamal_ciphertext(0) + .unwrap(); + + #[allow(clippy::arithmetic_side_effects)] + let new_available_balance_ciphertext = current_available_balance_ciphertext + - try_combine_lo_hi_ciphertexts( + &burn_amount_ciphertext_source_lo, + &burn_amount_ciphertext_source_hi, + BURN_AMOUNT_LO_BIT_LENGTH, + ) + .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?; + + // compute the remaining balance at the source + let remaining_balance = current_decrypted_available_balance + .checked_sub(burn_amount) + .ok_or(TokenProofGenerationError::NotEnoughFunds)?; + + let (new_available_balance_commitment, new_available_balance_opening) = + Pedersen::new(remaining_balance); + + // generate equality proof data + let equality_proof_data = CiphertextCommitmentEqualityProofData::new( + source_elgamal_keypair, + &new_available_balance_ciphertext, + &new_available_balance_commitment, + &new_available_balance_opening, + remaining_balance, + ) + .map_err(TokenProofGenerationError::from)?; + + // generate ciphertext validity data + let ciphertext_validity_proof_data = BatchedGroupedCiphertext3HandlesValidityProofData::new( + source_elgamal_keypair.pubkey(), + auditor_elgamal_pubkey, + supply_elgamal_pubkey, + &burn_amount_ciphertext_lo.0, + &burn_amount_ciphertext_hi.0, + burn_amount_lo, + burn_amount_hi, + &burn_amount_opening_lo, + &burn_amount_opening_hi, + ) + .map_err(TokenProofGenerationError::from)?; + + // generate range proof data + let (padding_commitment, padding_opening) = Pedersen::new(0_u64); + let range_proof_data = BatchedRangeProofU128Data::new( + vec![ + &new_available_balance_commitment, + burn_amount_ciphertext_lo.get_commitment(), + burn_amount_ciphertext_hi.get_commitment(), + &padding_commitment, + ], + vec![remaining_balance, burn_amount_lo, burn_amount_hi, 0], + vec![ + REMAINING_BALANCE_BIT_LENGTH, + BURN_AMOUNT_LO_BIT_LENGTH, + BURN_AMOUNT_HI_BIT_LENGTH, + RANGE_PROOF_PADDING_BIT_LENGTH, + ], + vec![ + &new_available_balance_opening, + &burn_amount_opening_lo, + &burn_amount_opening_hi, + &padding_opening, + ], + ) + .map_err(TokenProofGenerationError::from)?; + + Ok(BurnProofData { + equality_proof_data, + ciphertext_validity_proof_data, + range_proof_data, + }) +} diff --git a/token/confidential-transfer/proof-generation/src/encryption.rs b/token/confidential-transfer/proof-generation/src/encryption.rs index 8583cc7120c..3f3f875aa44 100644 --- a/token/confidential-transfer/proof-generation/src/encryption.rs +++ b/token/confidential-transfer/proof-generation/src/encryption.rs @@ -86,3 +86,55 @@ impl FeeCiphertext { self.0.handles.get(1).unwrap() } } + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(C)] +pub struct BurnAmountCiphertext(pub(crate) GroupedElGamalCiphertext<3>); + +impl BurnAmountCiphertext { + pub fn new( + amount: u64, + source_pubkey: &ElGamalPubkey, + auditor_pubkey: &ElGamalPubkey, + supply_pubkey: &ElGamalPubkey, + ) -> (Self, PedersenOpening) { + let opening = PedersenOpening::new_rand(); + let grouped_ciphertext = GroupedElGamal::<3>::encrypt_with( + [source_pubkey, auditor_pubkey, supply_pubkey], + amount, + &opening, + ); + + (Self(grouped_ciphertext), opening) + } + + pub fn get_commitment(&self) -> &PedersenCommitment { + &self.0.commitment + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(C)] +pub struct MintAmountCiphertext(pub(crate) GroupedElGamalCiphertext<3>); + +impl MintAmountCiphertext { + pub fn new( + amount: u64, + source_pubkey: &ElGamalPubkey, + auditor_pubkey: &ElGamalPubkey, + supply_pubkey: &ElGamalPubkey, + ) -> (Self, PedersenOpening) { + let opening = PedersenOpening::new_rand(); + let grouped_ciphertext = GroupedElGamal::<3>::encrypt_with( + [source_pubkey, auditor_pubkey, supply_pubkey], + amount, + &opening, + ); + + (Self(grouped_ciphertext), opening) + } + + pub fn get_commitment(&self) -> &PedersenCommitment { + &self.0.commitment + } +} diff --git a/token/confidential-transfer/proof-generation/src/lib.rs b/token/confidential-transfer/proof-generation/src/lib.rs index ba769a06fb4..f8883f31954 100644 --- a/token/confidential-transfer/proof-generation/src/lib.rs +++ b/token/confidential-transfer/proof-generation/src/lib.rs @@ -6,8 +6,10 @@ use { }, }; +pub mod burn; pub mod encryption; pub mod errors; +pub mod mint; pub mod transfer; pub mod transfer_with_fee; pub mod withdraw; diff --git a/token/confidential-transfer/proof-generation/src/mint.rs b/token/confidential-transfer/proof-generation/src/mint.rs new file mode 100644 index 00000000000..e25dc70a210 --- /dev/null +++ b/token/confidential-transfer/proof-generation/src/mint.rs @@ -0,0 +1,146 @@ +use { + crate::{ + encryption::MintAmountCiphertext, errors::TokenProofGenerationError, + try_combine_lo_hi_ciphertexts, try_split_u64, + }, + solana_zk_sdk::{ + encryption::{ + auth_encryption::{AeCiphertext, AeKey}, + elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey}, + pedersen::Pedersen, + }, + zk_elgamal_proof_program::proof_data::{ + BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data, + CiphertextCommitmentEqualityProofData, + }, + }, +}; + +const NEW_SUPPLY_BIT_LENGTH: usize = 64; +const MINT_AMOUNT_LO_BIT_LENGTH: usize = 16; +const MINT_AMOUNT_HI_BIT_LENGTH: usize = 32; +/// The padding bit length in range proofs to make the bit-length power-of-2 +const RANGE_PROOF_PADDING_BIT_LENGTH: usize = 16; + +/// The proof data required for a confidential mint instruction +pub struct MintProofData { + pub equality_proof_data: CiphertextCommitmentEqualityProofData, + pub ciphertext_validity_proof_data: BatchedGroupedCiphertext3HandlesValidityProofData, + pub range_proof_data: BatchedRangeProofU128Data, +} + +pub fn mint_split_proof_data( + current_supply_ciphertext: &ElGamalCiphertext, + current_decryptable_supply: &AeCiphertext, + mint_amount: u64, + supply_elgamal_keypair: &ElGamalKeypair, + supply_aes_key: &AeKey, + destination_elgamal_pubkey: &ElGamalPubkey, + auditor_elgamal_pubkey: &ElGamalPubkey, +) -> Result { + // split the mint amount into low and high bits + let (mint_amount_lo, mint_amount_hi) = try_split_u64(mint_amount, MINT_AMOUNT_LO_BIT_LENGTH) + .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?; + + // encrypt the mint amount under the destination and auditor's ElGamal public + // keys + let (mint_amount_grouped_ciphertext_lo, mint_amount_opening_lo) = MintAmountCiphertext::new( + mint_amount_lo, + destination_elgamal_pubkey, + auditor_elgamal_pubkey, + supply_elgamal_keypair.pubkey(), + ); + + let (mint_amount_grouped_ciphertext_hi, mint_amount_opening_hi) = MintAmountCiphertext::new( + mint_amount_hi, + destination_elgamal_pubkey, + auditor_elgamal_pubkey, + supply_elgamal_keypair.pubkey(), + ); + + // compute the new supply ciphertext + let mint_amount_ciphertext_supply_lo = mint_amount_grouped_ciphertext_lo + .0 + .to_elgamal_ciphertext(2) + .unwrap(); + let mint_amount_ciphertext_supply_hi = mint_amount_grouped_ciphertext_hi + .0 + .to_elgamal_ciphertext(2) + .unwrap(); + + #[allow(clippy::arithmetic_side_effects)] + let new_supply_ciphertext = current_supply_ciphertext + + try_combine_lo_hi_ciphertexts( + &mint_amount_ciphertext_supply_lo, + &mint_amount_ciphertext_supply_hi, + MINT_AMOUNT_LO_BIT_LENGTH, + ) + .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?; + + // decrypt the current supply + let current_supply = current_decryptable_supply + .decrypt(supply_aes_key) + .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?; + + // compute the new supply + let new_supply = current_supply + .checked_add(mint_amount) + .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?; + + let (new_supply_commitment, new_supply_opening) = Pedersen::new(new_supply); + + // generate equality proof data + let equality_proof_data = CiphertextCommitmentEqualityProofData::new( + supply_elgamal_keypair, + &new_supply_ciphertext, + &new_supply_commitment, + &new_supply_opening, + new_supply, + ) + .map_err(TokenProofGenerationError::from)?; + + // generate ciphertext validity proof data + let ciphertext_validity_proof_data = BatchedGroupedCiphertext3HandlesValidityProofData::new( + destination_elgamal_pubkey, + auditor_elgamal_pubkey, + supply_elgamal_keypair.pubkey(), + &mint_amount_grouped_ciphertext_lo.0, + &mint_amount_grouped_ciphertext_hi.0, + mint_amount_lo, + mint_amount_hi, + &mint_amount_opening_lo, + &mint_amount_opening_hi, + ) + .map_err(TokenProofGenerationError::from)?; + + // generate range proof data + let (padding_commitment, padding_opening) = Pedersen::new(0_u64); + let range_proof_data = BatchedRangeProofU128Data::new( + vec![ + &new_supply_commitment, + mint_amount_grouped_ciphertext_lo.get_commitment(), + mint_amount_grouped_ciphertext_hi.get_commitment(), + &padding_commitment, + ], + vec![new_supply, mint_amount_lo, mint_amount_hi, 0], + vec![ + NEW_SUPPLY_BIT_LENGTH, + MINT_AMOUNT_LO_BIT_LENGTH, + MINT_AMOUNT_HI_BIT_LENGTH, + RANGE_PROOF_PADDING_BIT_LENGTH, + ], + vec![ + &new_supply_opening, + &mint_amount_opening_lo, + &mint_amount_opening_hi, + &padding_opening, + ], + ) + .map_err(TokenProofGenerationError::from)?; + + Ok(MintProofData { + equality_proof_data, + ciphertext_validity_proof_data, + range_proof_data, + }) +} diff --git a/token/confidential-transfer/proof-tests/tests/proof_test.rs b/token/confidential-transfer/proof-tests/tests/proof_test.rs index 0796cbb9a6a..f4c3a7f3a9e 100644 --- a/token/confidential-transfer/proof-tests/tests/proof_test.rs +++ b/token/confidential-transfer/proof-tests/tests/proof_test.rs @@ -4,10 +4,12 @@ use { zk_elgamal_proof_program::proof_data::ZkProofData, }, spl_token_confidential_transfer_proof_extraction::{ - transfer::TransferProofContext, transfer_with_fee::TransferWithFeeProofContext, - withdraw::WithdrawProofContext, + burn::BurnProofContext, mint::MintProofContext, transfer::TransferProofContext, + transfer_with_fee::TransferWithFeeProofContext, withdraw::WithdrawProofContext, }, spl_token_confidential_transfer_proof_generation::{ + burn::{burn_split_proof_data, BurnProofData}, + mint::{mint_split_proof_data, MintProofData}, transfer::{transfer_split_proof_data, TransferProofData}, transfer_with_fee::{transfer_with_fee_split_proof_data, TransferWithFeeProofData}, withdraw::{withdraw_proof_data, WithdrawProofData}, @@ -182,3 +184,113 @@ fn test_withdraw_validity(spendable_balance: u64, withdraw_amount: u64) { ) .unwrap(); } + +#[test] +fn test_mint_proof_correctness() { + test_mint_validity(0, 0); + test_mint_validity(1, 0); + test_mint_validity(65535, 0); + test_mint_validity(65536, 0); + test_mint_validity(281474976710655, 0); + + test_mint_validity(0, 65535); + test_mint_validity(1, 65535); + test_mint_validity(65535, 65535); + test_mint_validity(65536, 65535); + test_mint_validity(281474976710655, 65535); + + test_mint_validity(0, 281474976710655); + test_mint_validity(1, 281474976710655); + test_mint_validity(65535, 281474976710655); + test_mint_validity(65536, 281474976710655); + test_mint_validity(281474976710655, 281474976710655); +} + +fn test_mint_validity(mint_amount: u64, supply: u64) { + let destination_keypair = ElGamalKeypair::new_rand(); + let destination_pubkey = destination_keypair.pubkey(); + + let auditor_keypair = ElGamalKeypair::new_rand(); + let auditor_pubkey = auditor_keypair.pubkey(); + + let supply_keypair = ElGamalKeypair::new_rand(); + let supply_aes_key = AeKey::new_rand(); + + let supply_ciphertext = supply_keypair.pubkey().encrypt(supply); + let decryptable_supply = supply_aes_key.encrypt(supply); + + let MintProofData { + equality_proof_data, + ciphertext_validity_proof_data, + range_proof_data, + } = mint_split_proof_data( + &supply_ciphertext, + &decryptable_supply, + mint_amount, + &supply_keypair, + &supply_aes_key, + destination_pubkey, + auditor_pubkey, + ) + .unwrap(); + + equality_proof_data.verify_proof().unwrap(); + ciphertext_validity_proof_data.verify_proof().unwrap(); + range_proof_data.verify_proof().unwrap(); + + MintProofContext::verify_and_extract( + equality_proof_data.context_data(), + ciphertext_validity_proof_data.context_data(), + range_proof_data.context_data(), + ) + .unwrap(); +} + +#[test] +fn test_burn_proof_correctness() { + test_burn_validity(0, 0); + test_burn_validity(77, 55); + test_burn_validity(65535, 65535); + test_burn_validity(65536, 65536); + test_burn_validity(281474976710655, 281474976710655); +} + +fn test_burn_validity(spendable_balance: u64, burn_amount: u64) { + let source_keypair = ElGamalKeypair::new_rand(); + let aes_key = AeKey::new_rand(); + + let auditor_keypair = ElGamalKeypair::new_rand(); + let auditor_pubkey = auditor_keypair.pubkey(); + + let supply_keypair = ElGamalKeypair::new_rand(); + let supply_pubkey = supply_keypair.pubkey(); + + let spendable_balance_ciphertext = source_keypair.pubkey().encrypt(spendable_balance); + let decryptable_balance = aes_key.encrypt(spendable_balance); + + let BurnProofData { + equality_proof_data, + ciphertext_validity_proof_data, + range_proof_data, + } = burn_split_proof_data( + &spendable_balance_ciphertext, + &decryptable_balance, + burn_amount, + &source_keypair, + &aes_key, + auditor_pubkey, + supply_pubkey, + ) + .unwrap(); + + equality_proof_data.verify_proof().unwrap(); + ciphertext_validity_proof_data.verify_proof().unwrap(); + range_proof_data.verify_proof().unwrap(); + + BurnProofContext::verify_and_extract( + equality_proof_data.context_data(), + ciphertext_validity_proof_data.context_data(), + range_proof_data.context_data(), + ) + .unwrap(); +} From 6260a8690a987d7f166e8374087aca36ebbab7b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Sep 2024 13:01:56 +0200 Subject: [PATCH 34/39] build(deps-dev): bump turbo from 2.1.1 to 2.1.2 (#7272) Bumps [turbo](https://github.com/vercel/turborepo) from 2.1.1 to 2.1.2. - [Release notes](https://github.com/vercel/turborepo/releases) - [Changelog](https://github.com/vercel/turborepo/blob/main/release.md) - [Commits](https://github.com/vercel/turborepo/compare/v2.1.1...v2.1.2) --- updated-dependencies: - dependency-name: turbo dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 44 ++++++++++++++++++++++---------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index dae3414b3dd..031db2fbad3 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "eslint-config-turbo": "^2.1.1", "eslint-plugin-prettier": "^5.2.1", "prettier": "^3.3.3", - "turbo": "^2.1.1" + "turbo": "^2.1.2" }, "engines": { "node": ">=14.0.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index deb899d5546..423b68de5b3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,8 +27,8 @@ importers: specifier: ^3.3.3 version: 3.3.3 turbo: - specifier: ^2.1.1 - version: 2.1.1 + specifier: ^2.1.2 + version: 2.1.2 account-compression/sdk: dependencies: @@ -8165,64 +8165,64 @@ packages: fsevents: 2.3.3 dev: true - /turbo-darwin-64@2.1.1: - resolution: {integrity: sha512-aYNuJpZlCoi0Htd79fl/2DywpewGKijdXeOfg9KzNuPVKzSMYlAXuAlNGh0MKjiOcyqxQGL7Mq9LFhwA0VpDpQ==} + /turbo-darwin-64@2.1.2: + resolution: {integrity: sha512-3TEBxHWh99h2yIzkuIigMEOXt/ItYQp0aPiJjPd1xN4oDcsKK5AxiFKPH9pdtfIBzYsY59kQhZiFj0ELnSP7Bw==} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /turbo-darwin-arm64@2.1.1: - resolution: {integrity: sha512-tifJKD8yHY48rHXPMcM8o1jI/Jk2KCaXiNjTKvvy9Zsim61BZksNVLelIbrRoCGwAN6PUBZO2lGU5iL/TQJ5Pw==} + /turbo-darwin-arm64@2.1.2: + resolution: {integrity: sha512-he0miWNq2WxJzsH82jS2Z4MXpnkzn9SH8a79iPXiJkq25QREImucscM4RPasXm8wARp91pyysJMq6aasD45CeA==} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /turbo-linux-64@2.1.1: - resolution: {integrity: sha512-Js6d/bSQe9DuV9c7ITXYpsU/ADzFHABdz1UIHa7Oqjj9VOEbFeA9WpAn0c+mdJrVD+IXJFbbDZUjN7VYssmtcg==} + /turbo-linux-64@2.1.2: + resolution: {integrity: sha512-fKUBcc0rK8Vdqv5a/E3CSpMBLG1bzwv+Q0Q83F8fG2ZfNCNKGbcEYABdonNZkkx141Rj03cZQFCgxu3MVEGU+A==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /turbo-linux-arm64@2.1.1: - resolution: {integrity: sha512-LidzTCq0yvQ+N8w8Qub9FmhQ/mmEIeoqFi7DSupekEV2EjvE9jw/zYc9Pk67X+g7dHVfgOnvVzmrjChdxpFePw==} + /turbo-linux-arm64@2.1.2: + resolution: {integrity: sha512-sV8Bpmm0WiuxgbhxymcC7wSsuxfBBieI98GegSwbr/bs1ANAgzCg93urIrdKdQ3/b31zZxQwcaP4FBF1wx1Qdg==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /turbo-windows-64@2.1.1: - resolution: {integrity: sha512-GKc9ZywKwy4xLDhwXd6H07yzl0TB52HjXMrFLyHGhCVnf/w0oq4sLJv2sjbvuarPjsyx4xnCBJ3m3oyL2XmFtA==} + /turbo-windows-64@2.1.2: + resolution: {integrity: sha512-wcmIJZI9ORT9ykHGliFE6kWRQrlH930QGSjSgWC8uFChFFuOyUlvC7ttcxuSvU9VqC7NF4C+GVAcFJQ8lTjN7g==} cpu: [x64] os: [win32] requiresBuild: true dev: true optional: true - /turbo-windows-arm64@2.1.1: - resolution: {integrity: sha512-oFKkMj11KKUv3xSK9/fhAEQTxLUp1Ol1EOktwc32+SFtEU0uls7kosAz0b+qe8k3pJGEMFdDPdqoEjyJidbxtQ==} + /turbo-windows-arm64@2.1.2: + resolution: {integrity: sha512-zdnXjrhk7YO6CP+Q5wPueEvOCLH4lDa6C4rrwiakcWcPgcQGbVozJlo4uaQ6awo8HLWQEvOwu84RkWTdLAc/Hw==} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /turbo@2.1.1: - resolution: {integrity: sha512-u9gUDkmR9dFS8b5kAYqIETK4OnzsS4l2ragJ0+soSMHh6VEeNHjTfSjk1tKxCqLyziCrPogadxP680J+v6yGHw==} + /turbo@2.1.2: + resolution: {integrity: sha512-Jb0rbU4iHEVQ18An/YfakdIv9rKnd3zUfSE117EngrfWXFHo3RndVH96US3GsT8VHpwTncPePDBT2t06PaFLrw==} hasBin: true optionalDependencies: - turbo-darwin-64: 2.1.1 - turbo-darwin-arm64: 2.1.1 - turbo-linux-64: 2.1.1 - turbo-linux-arm64: 2.1.1 - turbo-windows-64: 2.1.1 - turbo-windows-arm64: 2.1.1 + turbo-darwin-64: 2.1.2 + turbo-darwin-arm64: 2.1.2 + turbo-linux-64: 2.1.2 + turbo-linux-arm64: 2.1.2 + turbo-windows-64: 2.1.2 + turbo-windows-arm64: 2.1.2 dev: true /type-check@0.4.0: From 1be96c6f57e4a0bdfe1050359800d4a9934c2fd2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Sep 2024 13:02:04 +0200 Subject: [PATCH 35/39] build(deps-dev): bump eslint-config-turbo from 2.1.1 to 2.1.2 (#7273) Bumps [eslint-config-turbo](https://github.com/vercel/turborepo/tree/HEAD/packages/eslint-config-turbo) from 2.1.1 to 2.1.2. - [Release notes](https://github.com/vercel/turborepo/releases) - [Changelog](https://github.com/vercel/turborepo/blob/main/release.md) - [Commits](https://github.com/vercel/turborepo/commits/v2.1.2/packages/eslint-config-turbo) --- updated-dependencies: - dependency-name: eslint-config-turbo dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- account-compression/sdk/package.json | 2 +- package.json | 2 +- pnpm-lock.yaml | 18 +++++++++--------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/account-compression/sdk/package.json b/account-compression/sdk/package.json index 780bb5c1fb9..0f443141259 100644 --- a/account-compression/sdk/package.json +++ b/account-compression/sdk/package.json @@ -72,7 +72,7 @@ "@typescript-eslint/eslint-plugin": "^8.4.0", "@typescript-eslint/parser": "^8.4.0", "eslint": "^8.57.0", - "eslint-config-turbo": "^2.1.1", + "eslint-config-turbo": "^2.1.2", "eslint-plugin-import": "^2.30.0", "eslint-plugin-jest": "^28.8.3", "eslint-plugin-mocha": "^10.5.0", diff --git a/package.json b/package.json index 031db2fbad3..26b0cc1730c 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "@solana/eslint-config-solana": "^3.0.3", "@solana/prettier-config-solana": "^0.0.5", "eslint-config-prettier": "^9.1.0", - "eslint-config-turbo": "^2.1.1", + "eslint-config-turbo": "^2.1.2", "eslint-plugin-prettier": "^5.2.1", "prettier": "^3.3.3", "turbo": "^2.1.2" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 423b68de5b3..3f80955ba3d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,8 +18,8 @@ importers: specifier: ^9.1.0 version: 9.1.0(eslint@8.57.0) eslint-config-turbo: - specifier: ^2.1.1 - version: 2.1.1(eslint@8.57.0) + specifier: ^2.1.2 + version: 2.1.2(eslint@8.57.0) eslint-plugin-prettier: specifier: ^5.2.1 version: 5.2.1(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.3.3) @@ -85,8 +85,8 @@ importers: specifier: ^8.57.0 version: 8.57.0 eslint-config-turbo: - specifier: ^2.1.1 - version: 2.1.1(eslint@8.57.0) + specifier: ^2.1.2 + version: 2.1.2(eslint@8.57.0) eslint-plugin-import: specifier: ^2.30.0 version: 2.30.0(@typescript-eslint/parser@8.4.0)(eslint@8.57.0) @@ -4532,13 +4532,13 @@ packages: eslint: 8.57.0 dev: true - /eslint-config-turbo@2.1.1(eslint@8.57.0): - resolution: {integrity: sha512-JJF8SZErmgKCGkt124WUmTt0sQ5YLvPo2YxDsfzn9avGJC7/BQIa+3FZoDb3zeYYsZx91pZ6htQAJaKK8NQQAg==} + /eslint-config-turbo@2.1.2(eslint@8.57.0): + resolution: {integrity: sha512-UCNwxBrTOx0K41h1OrwMg7vPdGvcGSAlj40ZzpuUi0S2Muac2UOs+6F2dMYQiKg7lX2HAtyHXlF0T2wlWNHjGg==} peerDependencies: eslint: '>6.6.0' dependencies: eslint: 8.57.0 - eslint-plugin-turbo: 2.1.1(eslint@8.57.0) + eslint-plugin-turbo: 2.1.2(eslint@8.57.0) dev: true /eslint-import-resolver-node@0.3.9: @@ -4769,8 +4769,8 @@ packages: requireindex: 1.2.0 dev: true - /eslint-plugin-turbo@2.1.1(eslint@8.57.0): - resolution: {integrity: sha512-E/34kdQd0n3RP18+e0DSV0f3YTSCOojUh1p4X0Xrho2PBYmJ3umSnNo9FhkZt6UDACl+nBQcYTFkRHMz76lJdw==} + /eslint-plugin-turbo@2.1.2(eslint@8.57.0): + resolution: {integrity: sha512-q2ikGubfVLZDPEKliiuubZc3sI5oqbKIZJ6fRi6Bldv8E3cMNH3Qt7g6hXZV4+GxwQbzEEteCYSBNbOn1DBqRg==} peerDependencies: eslint: '>6.6.0' dependencies: From 7002ad147969717a029857dc0d3819a4a08fb9ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Sep 2024 13:02:13 +0200 Subject: [PATCH 36/39] build(deps-dev): bump @types/jest from 29.5.12 to 29.5.13 (#7274) Bumps [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest) from 29.5.12 to 29.5.13. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest) --- updated-dependencies: - dependency-name: "@types/jest" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- account-compression/sdk/package.json | 2 +- memo/js/package.json | 2 +- name-service/js/package.json | 2 +- pnpm-lock.yaml | 20 ++++++++++---------- stake-pool/js/package.json | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/account-compression/sdk/package.json b/account-compression/sdk/package.json index 0f443141259..3b44d78d9c2 100644 --- a/account-compression/sdk/package.json +++ b/account-compression/sdk/package.json @@ -66,7 +66,7 @@ "@coral-xyz/anchor": "^0.29.0", "@solana/eslint-config-solana": "^3.0.3", "@types/bn.js": "^5.1.0", - "@types/jest": "^29.5.12", + "@types/jest": "^29.5.13", "@types/node": "^22.5.4", "@types/node-fetch": "^2.6.11", "@typescript-eslint/eslint-plugin": "^8.4.0", diff --git a/memo/js/package.json b/memo/js/package.json index e137f4c6720..bd54b379420 100644 --- a/memo/js/package.json +++ b/memo/js/package.json @@ -53,7 +53,7 @@ "devDependencies": { "@solana/web3.js": "^1.95.3", "@types/chai": "^4.3.19", - "@types/jest": "^29.5.12", + "@types/jest": "^29.5.13", "@types/node": "^22.5.4", "@types/node-fetch": "^2.6.11", "@typescript-eslint/eslint-plugin": "^8.4.0", diff --git a/name-service/js/package.json b/name-service/js/package.json index 968c445774a..a11f2352e10 100644 --- a/name-service/js/package.json +++ b/name-service/js/package.json @@ -44,7 +44,7 @@ "devDependencies": { "@jest/globals": "^29.7.0", "@types/bn.js": "^5.1.1", - "@types/jest": "^29.5.12", + "@types/jest": "^29.5.13", "@types/node": "^22.5.4", "@typescript-eslint/eslint-plugin": "^8.4.0", "@typescript-eslint/parser": "^8.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3f80955ba3d..06ecd64fb96 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -67,8 +67,8 @@ importers: specifier: ^5.1.0 version: 5.1.5 '@types/jest': - specifier: ^29.5.12 - version: 29.5.12 + specifier: ^29.5.13 + version: 29.5.13 '@types/node': specifier: ^22.5.4 version: 22.5.4 @@ -192,8 +192,8 @@ importers: specifier: ^4.3.19 version: 4.3.19 '@types/jest': - specifier: ^29.5.12 - version: 29.5.12 + specifier: ^29.5.13 + version: 29.5.13 '@types/node': specifier: ^22.5.4 version: 22.5.4 @@ -262,8 +262,8 @@ importers: specifier: ^5.1.1 version: 5.1.5 '@types/jest': - specifier: ^29.5.12 - version: 29.5.12 + specifier: ^29.5.13 + version: 29.5.13 '@types/node': specifier: ^22.5.4 version: 22.5.4 @@ -415,8 +415,8 @@ importers: specifier: ^5.1.0 version: 5.1.5 '@types/jest': - specifier: ^29.5.12 - version: 29.5.12 + specifier: ^29.5.13 + version: 29.5.13 '@types/node': specifier: ^22.5.4 version: 22.5.4 @@ -2703,8 +2703,8 @@ packages: '@types/istanbul-lib-report': 3.0.3 dev: true - /@types/jest@29.5.12: - resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==} + /@types/jest@29.5.13: + resolution: {integrity: sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg==} dependencies: expect: 29.7.0 pretty-format: 29.7.0 diff --git a/stake-pool/js/package.json b/stake-pool/js/package.json index 0fdd517a7fa..4998f5c107c 100644 --- a/stake-pool/js/package.json +++ b/stake-pool/js/package.json @@ -60,7 +60,7 @@ "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^11.1.6", "@types/bn.js": "^5.1.0", - "@types/jest": "^29.5.12", + "@types/jest": "^29.5.13", "@types/node": "^22.5.4", "@types/node-fetch": "^2.6.11", "@typescript-eslint/eslint-plugin": "^8.4.0", From 3567838ae10ac5ee7c477104f5414427b36cab57 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Sep 2024 13:02:22 +0200 Subject: [PATCH 37/39] build(deps-dev): bump @types/mocha from 10.0.7 to 10.0.8 (#7275) Bumps [@types/mocha](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/mocha) from 10.0.7 to 10.0.8. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/mocha) --- updated-dependencies: - dependency-name: "@types/mocha" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- libraries/type-length-value/js/package.json | 2 +- pnpm-lock.yaml | 24 ++++++++++----------- token-group/js/package.json | 2 +- token-metadata/js/package.json | 2 +- token-swap/js/package.json | 2 +- token/js/package.json | 2 +- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/libraries/type-length-value/js/package.json b/libraries/type-length-value/js/package.json index 0d03550e6e9..7f1d43e65a5 100644 --- a/libraries/type-length-value/js/package.json +++ b/libraries/type-length-value/js/package.json @@ -47,7 +47,7 @@ }, "devDependencies": { "@types/chai": "^4.3.19", - "@types/mocha": "^10.0.7", + "@types/mocha": "^10.0.8", "@types/node": "^22.5.4", "@typescript-eslint/eslint-plugin": "^8.4.0", "@typescript-eslint/parser": "^8.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 06ecd64fb96..b0328fb487d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -140,8 +140,8 @@ importers: specifier: ^4.3.19 version: 4.3.19 '@types/mocha': - specifier: ^10.0.7 - version: 10.0.7 + specifier: ^10.0.8 + version: 10.0.8 '@types/node': specifier: ^22.5.4 version: 22.5.4 @@ -470,8 +470,8 @@ importers: specifier: ^4.3.19 version: 4.3.19 '@types/mocha': - specifier: ^10.0.7 - version: 10.0.7 + specifier: ^10.0.8 + version: 10.0.8 '@types/node': specifier: ^22.5.4 version: 22.5.4 @@ -592,8 +592,8 @@ importers: specifier: ^4.3.19 version: 4.3.19 '@types/mocha': - specifier: ^10.0.7 - version: 10.0.7 + specifier: ^10.0.8 + version: 10.0.8 '@types/node': specifier: ^22.5.4 version: 22.5.4 @@ -659,8 +659,8 @@ importers: specifier: ^8.0.0 version: 8.0.0 '@types/mocha': - specifier: ^10.0.7 - version: 10.0.7 + specifier: ^10.0.8 + version: 10.0.8 '@typescript-eslint/eslint-plugin': specifier: ^8.4.0 version: 8.4.0(@typescript-eslint/parser@8.4.0)(eslint@8.57.0)(typescript@5.6.2) @@ -723,8 +723,8 @@ importers: specifier: ^8.0.0 version: 8.0.0 '@types/mocha': - specifier: ^10.0.7 - version: 10.0.7 + specifier: ^10.0.8 + version: 10.0.8 '@types/node': specifier: ^22.5.4 version: 22.5.4 @@ -2718,8 +2718,8 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true - /@types/mocha@10.0.7: - resolution: {integrity: sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==} + /@types/mocha@10.0.8: + resolution: {integrity: sha512-HfMcUmy9hTMJh66VNcmeC9iVErIZJli2bszuXc6julh5YGuRb/W5OnkHjwLNYdFlMis0sY3If5SEAp+PktdJjw==} dev: true /@types/node-fetch@2.6.11: diff --git a/token-group/js/package.json b/token-group/js/package.json index 87c80d64af0..24a82f0b7bc 100644 --- a/token-group/js/package.json +++ b/token-group/js/package.json @@ -52,7 +52,7 @@ "devDependencies": { "@solana/web3.js": "^1.95.3", "@types/chai": "^4.3.19", - "@types/mocha": "^10.0.7", + "@types/mocha": "^10.0.8", "@types/node": "^22.5.4", "@typescript-eslint/eslint-plugin": "^8.4.0", "@typescript-eslint/parser": "^8.4.0", diff --git a/token-metadata/js/package.json b/token-metadata/js/package.json index a277a0ff008..83abcea1e6c 100644 --- a/token-metadata/js/package.json +++ b/token-metadata/js/package.json @@ -52,7 +52,7 @@ "devDependencies": { "@solana/web3.js": "^1.95.3", "@types/chai": "^4.3.19", - "@types/mocha": "^10.0.7", + "@types/mocha": "^10.0.8", "@types/node": "^22.5.4", "@typescript-eslint/eslint-plugin": "^8.4.0", "@typescript-eslint/parser": "^8.4.0", diff --git a/token-swap/js/package.json b/token-swap/js/package.json index 937d0de02d2..a818aa6eac0 100644 --- a/token-swap/js/package.json +++ b/token-swap/js/package.json @@ -54,7 +54,7 @@ "@types/bn.js": "^5.1.0", "@types/chai-as-promised": "^8.0.0", "@types/chai": "^4.3.19", - "@types/mocha": "^10.0.7", + "@types/mocha": "^10.0.8", "@typescript-eslint/eslint-plugin": "^8.4.0", "@typescript-eslint/parser": "^8.4.0", "eslint": "^8.57.0", diff --git a/token/js/package.json b/token/js/package.json index 43e57b3529e..10efa0a53b6 100644 --- a/token/js/package.json +++ b/token/js/package.json @@ -65,7 +65,7 @@ "@solana/web3.js": "^1.95.3", "@types/chai-as-promised": "^8.0.0", "@types/chai": "^4.3.19", - "@types/mocha": "^10.0.7", + "@types/mocha": "^10.0.8", "@types/node": "^22.5.4", "@types/node-fetch": "^2.6.11", "@typescript-eslint/eslint-plugin": "^8.4.0", From 992c2992b32a67f1400ffd8ef16373390b7ff9df Mon Sep 17 00:00:00 2001 From: Asten Date: Fri, 13 Sep 2024 20:22:51 +0800 Subject: [PATCH 38/39] Fix Transfer amount for 1 token in token-2022.md (#7271) * Fix Transfer amount for 1 token in token-2022.md * Change the token number to 0.75 for clarity Co-authored-by: Jon C * Update the byte-represented data --------- Co-authored-by: Jon C --- docs/src/token-2022.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/src/token-2022.md b/docs/src/token-2022.md index fee446e6be0..21fb3d866b2 100644 --- a/docs/src/token-2022.md +++ b/docs/src/token-2022.md @@ -43,13 +43,14 @@ Token-2022 are a strict superset of Token. ### Instructions Token-2022 supports the exact same instruction layouts as Token, byte for -byte. For example, if you want to transfer 100 tokens on a mint with 2 decimals, -you create a `TransferChecked` instruction, with this byte-represented data: +byte. For example, if you want to transfer 0.75 tokens in UI amount, on a mint with 2 decimals, +then the transfer amount is 75 tokens. You create a `TransferChecked` instruction, with +this byte-represented data: ``` -[12, 100, 0, 0, 0, 0, 0, 0, 0, 2] +[12, 75, 0, 0, 0, 0, 0, 0, 0, 2] ^^ TransferChecked enum - ^^^^^^^^^^^^^^^^^^^^^^^^ 100, as a little-endian 64-bit unsigned integer + ^^^^^^^^^^^^^^^^^^^^^^^^ 75, as a little-endian 64-bit unsigned integer ^ 2, as a byte ``` From 4d2153f24bce7311f271849106a4b10e7c9e8484 Mon Sep 17 00:00:00 2001 From: Noah Gundotra Date: Fri, 13 Sep 2024 17:02:45 -0400 Subject: [PATCH 39/39] update versions for release --- account-compression/Cargo.lock | 8 ++++---- .../programs/account-compression/Cargo.toml | 4 ++-- .../programs/account-compression/src/lib.rs | 6 +++--- libraries/concurrent-merkle-tree/Cargo.toml | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/account-compression/Cargo.lock b/account-compression/Cargo.lock index 495e39b6904..4ad012b4ab3 100644 --- a/account-compression/Cargo.lock +++ b/account-compression/Cargo.lock @@ -544,9 +544,9 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fd4c6dcc3b0aea2f5c0b4b82c2b15fe39ddbc76041a310848f4706edf76bb31" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" dependencies = [ "bytemuck_derive", ] @@ -1563,7 +1563,7 @@ dependencies = [ [[package]] name = "spl-account-compression" -version = "0.3.1" +version = "0.4.0" dependencies = [ "anchor-lang", "bytemuck", @@ -1574,7 +1574,7 @@ dependencies = [ [[package]] name = "spl-concurrent-merkle-tree" -version = "0.3.0" +version = "0.4.0" dependencies = [ "bytemuck", "solana-program", diff --git a/account-compression/programs/account-compression/Cargo.toml b/account-compression/programs/account-compression/Cargo.toml index a57df385abe..6aac624cf76 100644 --- a/account-compression/programs/account-compression/Cargo.toml +++ b/account-compression/programs/account-compression/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spl-account-compression" -version = "0.3.1" +version = "0.4.0" description = "Solana Program Library Account Compression Program" authors = ["Solana Labs Maintainers "] repository = "https://github.com/solana-labs/solana-program-library" @@ -21,7 +21,7 @@ default = [] anchor-lang = "0.29.0" bytemuck = "1.13" solana-program = ">=1.18.11,<=2" -spl-concurrent-merkle-tree = { version = "0.3.0", path = "../../../libraries/concurrent-merkle-tree" } +spl-concurrent-merkle-tree = { version = "0.4.0", path = "../../../libraries/concurrent-merkle-tree" } spl-noop = { version = "0.2.0", path = "../noop", features = ["no-entrypoint"] } [profile.release] diff --git a/account-compression/programs/account-compression/src/lib.rs b/account-compression/programs/account-compression/src/lib.rs index 342ef7287d2..93d8411b3c4 100644 --- a/account-compression/programs/account-compression/src/lib.rs +++ b/account-compression/programs/account-compression/src/lib.rs @@ -184,7 +184,7 @@ pub mod spl_account_compression { update_canopy(canopy_bytes, header.get_max_depth(), None) } - /// In order to initialize a tree with a root, we need to create the tree on-chain first with + /// (Devnet only) In order to initialize a tree with a root, we need to create the tree on-chain first with /// the proper authority. The tree might contain a canopy, which is a cache of the uppermost /// nodes. The canopy is used to decrease the size of the proof required to update the tree. /// If the tree is expected to have a canopy, it needs to be prefilled with the necessary nodes. @@ -228,7 +228,7 @@ pub mod spl_account_compression { check_canopy_bytes(canopy_bytes) } - /// This instruction pre-initializes the canopy with the specified leaf nodes of the canopy. + /// (Devnet only) This instruction pre-initializes the canopy with the specified leaf nodes of the canopy. /// This is intended to be used after `prepare_batch_merkle_tree` and in conjunction with the /// `init_prepared_tree_with_root` instruction that'll finalize the tree initialization. /// The canopy is used to cache the uppermost nodes of the tree, which allows for a smaller @@ -277,7 +277,7 @@ pub mod spl_account_compression { ) } - /// Initializes a prepared tree with a root and a rightmost leaf. The rightmost leaf is used to + /// (Devnet only) Initializes a prepared tree with a root and a rightmost leaf. The rightmost leaf is used to /// verify the canopy if the tree has it. Before calling this instruction, the tree should be /// prepared with `prepare_batch_merkle_tree` and the canopy should be filled with the necessary /// nodes with `append_canopy_nodes` (if the canopy is used). This method should be used for diff --git a/libraries/concurrent-merkle-tree/Cargo.toml b/libraries/concurrent-merkle-tree/Cargo.toml index 0d0c7490ce5..0c3c91ec723 100644 --- a/libraries/concurrent-merkle-tree/Cargo.toml +++ b/libraries/concurrent-merkle-tree/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spl-concurrent-merkle-tree" -version = "0.3.0" +version = "0.4.0" description = "Solana Program Library Concurrent Merkle Tree" authors = ["Solana Labs Maintainers "] repository = "https://github.com/solana-labs/solana-program-library"