Demo
The following Anchor demo program serves as a practical guide to integrating Honeycomb's compression capabilities within Solana smart contracts.
Utilizing the hpl-toolkit, this program will guide you on how to create, update, and manage compressed accounts efficiently while employing Merkle trees to ensure data integrity and minimize storage costs.
By walking through this program, developers will learn to harness controlled Merkle trees, implement custom schemas, and interact with the Digital Asset RPC Infrastructure (DAS), all while adhering to Solana's high-throughput and low-cost transaction model.
Cargo.toml
[package]
name = "hpl-demo-program"
version = "0.0.1"
edition = "2021"
license = "MIT"
[lib]
crate-type = ["cdylib", "lib"]
name = "hpl_demo_program"
[features]
no-entrypoint = []
no-idl = []
no-log-ix-name = []
cpi = ["no-entrypoint"]
default = []
[dependencies]
anchor-lang = "0.29.0"
anchor-spl = "0.29.0"
hpl-toolkit = "0.0.3-beta.14"
lib.rs
use anchor_lang::prelude::*;
use hpl_toolkit::compression::*;
use spl_account_compression::{program::SplAccountCompression, Noop};
declare_id!("BNdAHQMniLicundk1jo4qKWyNr9C8bK7oUrzgSwoSGmZ");
mod state {
use anchor_lang::{prelude::*, solana_program::keccak};
use hpl_toolkit::{compression::*, schema::*};
use spl_account_compression::Node;
#[account]
#[derive(ToSchema)]
pub struct DemoAccountIndexer {
pub merkle_trees: ControlledMerkleTrees,
}
impl DemoAccountIndexer {
pub fn get_size() -> usize {
ControlledMerkleTrees::get_size_for_borsh(&DemoAccount::schema(), 0)
}
}
#[compressed_account]
pub struct DemoAccount {
pub value: u64,
#[chunk]
pub chunk_1: Chunk1,
#[chunk]
pub chunk_2: Chunk2,
}
#[compressed_account(chunk = chunk_1)]
pub struct Chunk1 {
pub chunk_value: u64,
}
#[compressed_account(chunk = chunk_2)]
pub struct Chunk2 {
pub chunk_value: String,
}
}
#[cfg(not(feature = "cpi"))]
use hpl_toolkit::schema::*;
use state::DemoAccountCompressed;
#[cfg_attr(not(feature = "cpi"), account_schemas_ix_injector(DemoAccountIndexer))]
#[program]
pub mod hpl_demo_program {
use super::*;
pub fn init_demo_account_indexer(ctx: Context<InitDemoAccountIndexer>) -> Result<()> {
ctx.accounts.demo_account_indexer.merkle_trees = ControlledMerkleTrees {
active: 0,
merkle_trees: Vec::new(),
schema: state::DemoAccount::schema(),
};
Ok(())
}
pub fn create_new_demo_account_tree(ctx: Context<CreateNewDemoAccountTree>) -> Result<()> {
let demo_account_indexer = &mut ctx.accounts.demo_account_indexer;
demo_account_indexer
.merkle_trees
.merkle_trees
.push(ctx.accounts.merkle_tree.key());
hpl_toolkit::reallocate(
32,
demo_account_indexer.to_account_info(),
ctx.accounts.authority.to_account_info(),
&ctx.accounts.rent_sysvar,
&ctx.accounts.system_program,
)?;
let event = CompressedDataEvent::tree(
ctx.accounts.merkle_tree.key(),
demo_account_indexer.merkle_trees.schema.clone(),
crate::ID,
String::from("User"),
);
event.wrap(&ctx.accounts.log_wrapper)?;
init_tree(
20,
1024,
&ctx.accounts.authority,
&ctx.accounts.merkle_tree,
&ctx.accounts.compression_program,
&ctx.accounts.log_wrapper,
None,
)
}
pub fn new_demo_account(ctx: Context<NewDemoAccount>) -> Result<()> {
let (leaf_idx, seq) = ctx
.accounts
.demo_account_indexer
.merkle_trees
.assert_append(ctx.accounts.merkle_tree.to_account_info())?;
let demo_account = state::DemoAccount {
value: 0,
chunk_1: state::Chunk1 { chunk_value: 0 },
chunk_2: state::Chunk2 {
chunk_value: String::from("User"),
},
};
let event = CompressedDataEvent::Leaf {
slot: ctx.accounts.clock.slot,
tree_id: ctx.accounts.merkle_tree.key().to_bytes(),
leaf_idx,
seq: seq + 1,
stream_type: demo_account.event_stream(),
};
event.wrap(&ctx.accounts.log_wrapper)?;
append_leaf(
demo_account.to_compressed().to_node(),
&ctx.accounts.authority.to_account_info(),
&ctx.accounts.merkle_tree,
&ctx.accounts.compression_program,
&ctx.accounts.log_wrapper,
None,
)?;
Ok(())
}
pub fn update_demo_account<'info>(
ctx: Context<'_, '_, '_, 'info, UpdateDemoAccount<'info>>,
args: UpdateDemoAccountArgs,
) -> Result<()> {
let mut current_demo_account_data = DemoAccountCompressed {
value: args.value,
chunk_1: [0; 32],
chunk_2: [0; 32],
};
let mut new_user_data = DemoAccountCompressed {
value: args.value,
chunk_1: [0; 32],
chunk_2: [0; 32],
};
// Event to throw when updating the profile
let merkle_tree_bytes = ctx.accounts.merkle_tree.try_borrow_data()?;
let mut 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,
};
match args.chunk_1 {
DataOrHash::Data(updates) => {
let mut chunk_1 = state::Chunk1 {
chunk_value: updates.current_value,
};
current_demo_account_data.chunk_1 = chunk_1.to_node();
chunk_1.chunk_value = updates.new_value;
new_user_data.chunk_1 = chunk_1.to_node();
if let CompressedDataEvent::Leaf { stream_type, .. } = &mut event {
*stream_type = chunk_1.event_stream();
event.wrap(&ctx.accounts.log_wrapper)?;
}
}
DataOrHash::Hash(hash) => {
current_demo_account_data.chunk_1 = hash;
new_user_data.chunk_1 = hash;
}
};
match args.chunk_2 {
DataOrHash::Data(updates) => {
let mut chunk_2 = state::Chunk2 {
chunk_value: updates.current_value,
};
current_demo_account_data.chunk_2 = chunk_2.to_node();
chunk_2.chunk_value = updates.new_value;
new_user_data.chunk_2 = chunk_2.to_node();
if let CompressedDataEvent::Leaf { stream_type, .. } = &mut event {
*stream_type = chunk_2.event_stream();
event.wrap(&ctx.accounts.log_wrapper)?;
}
}
DataOrHash::Hash(hash) => {
current_demo_account_data.chunk_2 = hash;
new_user_data.chunk_2 = hash;
}
};
replace_leaf(
args.root,
current_demo_account_data.to_node(),
new_user_data.to_node(),
args.leaf_idx,
&ctx.accounts.authority,
&ctx.accounts.merkle_tree,
&ctx.accounts.compression_program,
&ctx.accounts.log_wrapper,
ctx.remaining_accounts.to_vec(),
None,
)?;
Ok(())
}
}
#[derive(Accounts)]
pub struct InitDemoAccountIndexer<'info> {
/// Public info account
#[account(
init, payer = payer,
space = state::DemoAccountIndexer::get_size(),
)]
pub demo_account_indexer: Account<'info, state::DemoAccountIndexer>,
#[account(mut)]
pub payer: Signer<'info>,
/// The system program.
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct CreateNewDemoAccountTree<'info> {
#[account(mut)]
pub demo_account_indexer: Account<'info, state::DemoAccountIndexer>,
/// CHECK: This account must be all zeros
#[account(zero)]
pub merkle_tree: UncheckedAccount<'info>,
#[account(mut)]
pub authority: Signer<'info>,
/// NATIVE RENT SYSVAR
pub rent_sysvar: Sysvar<'info, Rent>,
/// The system program.
pub system_program: Program<'info, System>,
/// SPL Compression program.
pub compression_program: Program<'info, SplAccountCompression>,
/// SPL Noop program.
pub log_wrapper: Program<'info, Noop>,
}
#[derive(Accounts)]
pub struct NewDemoAccount<'info> {
#[account(mut)]
pub demo_account_indexer: Account<'info, state::DemoAccountIndexer>,
/// CHECK: unsafe
#[account(mut)]
pub merkle_tree: AccountInfo<'info>,
/// CHECK: unsafe
#[account()]
pub authority: Signer<'info>,
/// System Program
pub system_program: Program<'info, System>,
/// SPL account compression program.
pub compression_program: Program<'info, SplAccountCompression>,
/// SPL Noop program.
pub log_wrapper: Program<'info, Noop>,
/// NATIVE SYSVAR CLOCK
pub clock: Sysvar<'info, Clock>,
}
#[derive(Accounts)]
pub struct UpdateDemoAccount<'info> {
/// CHECK: unsafe
#[account(mut)]
pub merkle_tree: AccountInfo<'info>,
/// Honeycomb auth driver
pub authority: Signer<'info>,
/// System Program
pub system_program: Program<'info, System>,
/// SPL account compression program.
pub compression_program: Program<'info, SplAccountCompression>,
/// SPL Noop program.
pub log_wrapper: Program<'info, Noop>,
/// NATIVE SYSVAR CLOCK
pub clock: Sysvar<'info, Clock>,
}
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct UpdateDemoAccountArgs {
pub root: [u8; 32],
pub leaf_idx: u32,
pub value: u64,
pub chunk_1: DataOrHash<Chunk1Update>,
pub chunk_2: DataOrHash<Chunk2Update>,
}
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct Chunk1Update {
current_value: u64,
new_value: u64,
}
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct Chunk2Update {
current_value: String,
new_value: String,
}