Skip to main content

Tools

The hpl-toolkit Rust crate is an essential component for interacting with Honeycomb's compressed account structures. Below is a comprehensive guide on its functionalities and how to utilize them within your program.

ControlledMerkleTrees

A struct that encapsulates the management of multiple Merkle trees associated with a compressed account.

  • pub schema: Schema - Defines the schema of the compressed account.
  • merkle_trees: Vec<Pubkey> - Holds the public keys of all Merkle trees created for a specific schema.
  • pub active: u8 - Indicates the active Merkle tree pointer.

Schema and SchemaValue

Enums derived from the serde_json crate that represent the JSON structure and value formats in Rust.

  • Schema - Enum for defining the structure of compressed account data.
  • SchemaValue - Enum for defining the values within the structure.

Traits

ToSchema

Defines the schema of the compressed data.

  • fn schema() -> Schema; - Returns the schema as a Schema enum.
  • fn schema_value(&self) -> SchemaValue; - Returns the value of the data as a SchemaValue enum.

ToNode

Converts the compressed data into a Merkle tree node.

  • fn to_node(&self) -> Node; - Converts the data into a node, where Node is an array of 32 bytes.

CompressedData & CompressedDataChunk

Deals with event streams derived from the schema value.

  • fn event_stream - Converts the schema_value into a compression event that the DAS can understand.

Macros

compressed_account

  • Automatically implements the ToSchema, ToNode, CompressedData, and CompressedDataChunk traits for the specified struct.

CompressedDataEvent

An enum that outlines the events related to compressed data operations.

  • TreeSchemaValue - Indicates the creation of a new tree with a specified schema for all leaves.
  • Leaf - Indicates the creation or update of a compressed account.

Examples

Compressed Account Structure

#[compressed_account]
pub struct ExampleAccount {
pub value: u64,

#[chunk]
pub inner_example: InnerExample
}

#[compressed_account(chunk = inner_example)]
pub struct InnerExample {
...
}

Creating a New Tree

// before creating a new tree, you must have a ControlledMerkleTree stored somewhere which can be created as follows
example_account_trees = ControlledMerkleTrees {
active: 0,
merkle_trees: Vec::new(),
schema: ExampleAccount::schema(),
};

// while creating new tree the ControlledMerkleTree must be already created
example_account_trees.merkle_trees.push(ctx.accounts.merkle_tree.key());
let event = CompressedDataEvent::tree(
merkle_tree_address,
example_account_trees.schema.clone(),
program_id,
String::from("ExampleAccount"),
);
event.wrap(&spl_noop_program)?;

Creating a New Account (Leaf)

// checks if the merkle tree provided is the current active merkle tree and also if it has only 1 capacity left and then increase active counter
let (leaf_idx, seq) = example_account_trees.assert_append(merkle_tree.to_account_info())?;

let event = CompressedDataEvent::Leaf {
slot: clock_slot,
tree_id: merkle_tree_address.to_bytes(),
leaf_idx,
seq: seq + 1,
stream_type: example_account.event_stream(),
};
event.wrap(log_wrapper)?;

// use the following function to get hash of the account to append leaf
append_leaf( ..., example_account.to_compressed().to_node(), ... )?;

Updating account chunk data

/// ExampleAccountCompressed is auto generated from `compressed_account` macro 
let current_data = ExampleAccountCompressed {
value: current_value,
inner_example: current_inner_example_hash,
};


let mut updated_data = UserCompressed {
value: ...,
inner_example: updated_inner_example.to_node()
};

let event = CompressedDataEvent::Leaf {
slot: ctx.accounts.clock.slot,
tree_id: ctx.accounts.merkle_tree.key().to_bytes(),
leaf_idx: args.leaf_idx,
seq: merkle_tree_apply_fn!(merkle_tree_bytes get_seq) + 1,
stream_type: updated_inner_example.event_stream(),
};
event.wrap(&spl_noop_program)?;

// use the following function to get hash of the account to replace leaf
replace_leaf( ..., current_data.to_node(), updated_data.to_node(), ... )?;

Deleting account

let current_data = ExampleAccountCompressed {
value: current_value,
inner_example: current_inner_example_hash,
};

let event = CompressedDataEvent::Leaf {
slot: ctx.accounts.clock.slot,
tree_id: ctx.accounts.merkle_tree.key().to_bytes(),
leaf_idx: args.leaf_idx,
seq: merkle_tree_apply_fn!(merkle_tree_bytes get_seq) + 1,
stream_type: CompressedDataEventStream::Empty,
};
event.wrap(&spl_noop_program)?;

// use the following function to get hash of the account to replace leaf
replace_leaf( ..., current_data.to_node(), [0; u32], ... )?;