Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9,001 changes: 9,001 additions & 0 deletions pinocchio/counter/Cargo.lock

Large diffs are not rendered by default.

49 changes: 49 additions & 0 deletions pinocchio/counter/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
[package]
name = "pinocchio-counter"
version = "0.1.0"
description = "Pinocchio-based counter program using Light Protocol rent-free accounts"
edition = "2021"

[lib]
crate-type = ["cdylib", "lib"]
name = "pinocchio_counter"

[features]
no-entrypoint = []
default = []
test-sbf = []

[dependencies]
# Main Light Protocol dependencies
light-account-pinocchio = { version = "0.20", features = ["std"] }
light-hasher = { version = "5.0.0", features = ["solana"] }

# Serialization and utilities
borsh = { version = "0.10.4", default-features = false }
bytemuck = { version = "1.21", features = ["derive"] }

# Pinocchio and Solana
pinocchio = "0.9"
pinocchio-pubkey = { version = "0.3", features = ["const"] }
pinocchio-system = "0.3"
solana-pubkey = "2.2"
solana-msg = "2.2"
solana-program-error = "2.2"

[dev-dependencies]
light-program-test = "0.20"
light-client = { version = "0.20", features = ["v2", "anchor", "program-test"] }
light-account = "0.20"
tokio = { version = "1", features = ["full"] }
solana-sdk = "2.2"
solana-keypair = "2.2"
solana-signer = "2.2"
solana-instruction = "2.2"
blake3 = "=1.8.2"

[lints.rust.unexpected_cfgs]
level = "allow"
check-cfg = [
'cfg(target_os, values("solana"))',
'cfg(feature, values("frozen-abi", "no-entrypoint"))',
]
13 changes: 13 additions & 0 deletions pinocchio/counter/src/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//! Constants for the counter program including seeds and discriminators.

/// Seed for counter state PDA.
pub const COUNTER_SEED: &[u8] = b"counter";

/// Instruction discriminators (Anchor-compatible: sha256("global:{name}")[..8]).
pub mod discriminators {
/// Create counter instruction.
pub const CREATE_COUNTER: [u8; 8] = [174, 255, 78, 222, 78, 250, 200, 80];

/// Increment counter instruction.
pub const INCREMENT: [u8; 8] = [11, 18, 104, 9, 104, 174, 59, 33];
}
120 changes: 120 additions & 0 deletions pinocchio/counter/src/create_counter/accounts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
//! Create counter instruction account parsing.

use borsh::{BorshDeserialize, BorshSerialize};
use light_account_pinocchio::{CreateAccountsProof, LightAccount, LightDiscriminator};
use pinocchio::{
account_info::AccountInfo,
instruction::{Seed, Signer},
program_error::ProgramError,
sysvars::Sysvar,
};

use crate::constants::*;
use crate::state::CounterState;

/// Parameters for the create_counter instruction.
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
pub struct CreateCounterParams {
/// Proof data for creating compressed accounts.
pub create_accounts_proof: CreateAccountsProof,
/// Counter PDA bump.
pub counter_bump: u8,
}

/// Accounts for the create_counter instruction.
///
/// Layout:
/// [0] payer (signer, writable)
/// [1] owner (read-only)
/// [2] counter (writable) — PDA: [COUNTER_SEED, owner]
/// [3] compressible_config (read-only)
/// [4] system_program (read-only)
/// ... remaining_accounts (Light Protocol system accounts)
pub struct CreateCounterAccounts<'a> {
/// Payer/creator (signer, writable).
pub payer: &'a AccountInfo,
/// Counter owner (read-only).
pub owner: &'a AccountInfo,
/// Counter PDA (writable).
pub counter: &'a AccountInfo,
/// Compressible config account (read-only).
pub compressible_config: &'a AccountInfo,
/// System program (read-only).
pub system_program: &'a AccountInfo,
}

impl<'a> CreateCounterAccounts<'a> {
/// Number of fixed accounts for this instruction.
pub const FIXED_LEN: usize = 5;

/// Parse accounts from the account info slice.
pub fn parse(
accounts: &'a [AccountInfo],
params: &CreateCounterParams,
) -> Result<Self, ProgramError> {
if accounts.len() < Self::FIXED_LEN {
return Err(ProgramError::NotEnoughAccountKeys);
}

let payer = &accounts[0];
let owner = &accounts[1];
let counter = &accounts[2];
let compressible_config = &accounts[3];
let system_program = &accounts[4];

// Validate payer is signer
if !payer.is_signer() {
return Err(ProgramError::MissingRequiredSignature);
}

// Validate and create counter PDA
{
let owner_key = owner.key();
let seeds: &[&[u8]] = &[COUNTER_SEED, owner_key.as_ref()];
let (expected_pda, bump) =
pinocchio::pubkey::find_program_address(seeds, &crate::ID);
if counter.key() != &expected_pda {
return Err(ProgramError::InvalidSeeds);
}
if bump != params.counter_bump {
return Err(ProgramError::InvalidSeeds);
}

// Create counter account
let space = 8 + CounterState::INIT_SPACE;
let rent = pinocchio::sysvars::rent::Rent::get()
.map_err(|_| ProgramError::UnsupportedSysvar)?;
let lamports = rent.minimum_balance(space);

let bump_bytes = [bump];
let seed_array = [
Seed::from(COUNTER_SEED),
Seed::from(owner_key.as_ref()),
Seed::from(bump_bytes.as_ref()),
];
let signer = Signer::from(&seed_array);
pinocchio_system::instructions::CreateAccount {
from: payer,
to: counter,
lamports,
space: space as u64,
owner: &crate::ID,
}
.invoke_signed(&[signer])?;

// Write discriminator to first 8 bytes
let mut data = counter
.try_borrow_mut_data()
.map_err(|_| ProgramError::AccountBorrowFailed)?;
data[..8].copy_from_slice(&CounterState::LIGHT_DISCRIMINATOR);
}

Ok(Self {
payer,
owner,
counter,
compressible_config,
system_program,
})
}
}
6 changes: 6 additions & 0 deletions pinocchio/counter/src/create_counter/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//! Create counter instruction module.

pub mod accounts;
pub mod processor;

pub use accounts::CreateCounterParams;
102 changes: 102 additions & 0 deletions pinocchio/counter/src/create_counter/processor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//! Create counter instruction processor.
//!
//! Creates a counter PDA and registers it with Light Protocol for compression.

use light_account_pinocchio::{
prepare_compressed_account_on_init, CpiAccounts, CpiAccountsConfig,
InstructionDataInvokeCpiWithAccountInfo, InvokeLightSystemProgram, LightAccount, LightConfig,
LightSdkTypesError, PackedAddressTreeInfoExt,
};
use pinocchio::{
account_info::AccountInfo,
sysvars::{clock::Clock, Sysvar},
};

use super::accounts::{CreateCounterAccounts, CreateCounterParams};

/// Process the create_counter instruction.
pub fn process(
ctx: &CreateCounterAccounts<'_>,
params: &CreateCounterParams,
remaining_accounts: &[AccountInfo],
) -> Result<(), LightSdkTypesError> {
// 1. Build CPI accounts (no CPI context — single PDA, no batching)
let system_accounts_offset = params.create_accounts_proof.system_accounts_offset as usize;
if remaining_accounts.len() < system_accounts_offset {
return Err(LightSdkTypesError::FewerAccountsThanSystemAccounts);
}
let config = CpiAccountsConfig::new(crate::LIGHT_CPI_SIGNER);
let cpi_accounts = CpiAccounts::new_with_config(
ctx.payer,
&remaining_accounts[system_accounts_offset..],
config,
);

// 2. Address tree info
let address_tree_info = &params.create_accounts_proof.address_tree_info;
let address_tree_pubkey = address_tree_info
.get_tree_pubkey(&cpi_accounts)
.map_err(|_| LightSdkTypesError::InvalidInstructionData)?;
let output_tree_index = params.create_accounts_proof.output_state_tree_index;

// 3. Load config, get slot
let light_config = LightConfig::load_checked(ctx.compressible_config, &crate::ID)
.map_err(|_| LightSdkTypesError::InvalidInstructionData)?;
let current_slot = Clock::get()
.map_err(|_| LightSdkTypesError::InvalidInstructionData)?
.slot;

// 4. Prepare compressed account on init
let mut new_address_params = Vec::with_capacity(1);
let mut account_infos = Vec::with_capacity(1);

let counter_key = *ctx.counter.key();
prepare_compressed_account_on_init(
&counter_key,
&address_tree_pubkey,
address_tree_info,
output_tree_index,
0,
&crate::ID,
&mut new_address_params,
&mut account_infos,
)?;

// 5. Initialize counter state data via zero-copy
{
let mut account_data = ctx
.counter
.try_borrow_mut_data()
.map_err(|_| LightSdkTypesError::Borsh)?;

let record_bytes =
&mut account_data[8..8 + core::mem::size_of::<crate::state::CounterState>()];
let counter_state: &mut crate::state::CounterState =
bytemuck::from_bytes_mut(record_bytes);

counter_state.set_decompressed(&light_config, current_slot);
counter_state.owner = *ctx.owner.key();
counter_state.count = 0;
}

// 6. Invoke Light system program CPI
let instruction_data = InstructionDataInvokeCpiWithAccountInfo {
mode: 1,
bump: crate::LIGHT_CPI_SIGNER.bump,
invoking_program_id: crate::LIGHT_CPI_SIGNER.program_id.into(),
compress_or_decompress_lamports: 0,
is_compress: false,
with_cpi_context: false,
with_transaction_hash: false,
cpi_context: Default::default(),
proof: params.create_accounts_proof.proof.0,
new_address_params,
account_infos,
read_only_addresses: vec![],
read_only_accounts: vec![],
};

instruction_data.invoke(cpi_accounts)?;

Ok(())
}
27 changes: 27 additions & 0 deletions pinocchio/counter/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//! Custom errors for the counter program.

use pinocchio::program_error::ProgramError;

/// Counter program errors.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum CounterError {
/// Invalid counter PDA seeds.
InvalidCounterSeeds = 6000,
/// Counter owner mismatch.
OwnerMismatch = 6001,
/// Math overflow.
MathOverflow = 6002,
}

impl From<CounterError> for ProgramError {
fn from(e: CounterError) -> Self {
ProgramError::Custom(e as u32)
}
}

impl From<CounterError> for u32 {
fn from(e: CounterError) -> Self {
e as u32
}
}
55 changes: 55 additions & 0 deletions pinocchio/counter/src/increment/accounts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//! Increment instruction account parsing.

use pinocchio::{account_info::AccountInfo, program_error::ProgramError};

use crate::constants::*;

/// Accounts for the increment instruction.
///
/// Layout:
/// [0] owner (signer)
/// [1] counter (writable) — validated PDA + program owner check
pub struct IncrementAccounts<'a> {
/// Counter owner (signer).
pub owner: &'a AccountInfo,
/// Counter PDA (writable).
pub counter: &'a AccountInfo,
}

impl<'a> IncrementAccounts<'a> {
/// Number of fixed accounts for this instruction.
pub const FIXED_LEN: usize = 2;

/// Parse accounts from the account info slice.
pub fn parse(accounts: &'a [AccountInfo]) -> Result<Self, ProgramError> {
if accounts.len() < Self::FIXED_LEN {
return Err(ProgramError::NotEnoughAccountKeys);
}

let owner = &accounts[0];
let counter = &accounts[1];

// Validate owner is signer
if !owner.is_signer() {
return Err(ProgramError::MissingRequiredSignature);
}

// Validate counter PDA
{
let owner_key = owner.key();
let seeds: &[&[u8]] = &[COUNTER_SEED, owner_key.as_ref()];
let (expected_pda, _) =
pinocchio::pubkey::find_program_address(seeds, &crate::ID);
if counter.key() != &expected_pda {
return Err(ProgramError::InvalidSeeds);
}
}

// Validate counter is owned by this program
if counter.owner() != &crate::ID {
return Err(ProgramError::IllegalOwner);
}

Ok(Self { owner, counter })
}
}
4 changes: 4 additions & 0 deletions pinocchio/counter/src/increment/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
//! Increment counter instruction module.

pub mod accounts;
pub mod processor;
Loading