Skip to content

refactor: light program pinocchio macro#2247

Merged
ananas-block merged 2 commits intomainfrom
jorrit/refactor-light-program-pinocchio-macro
Feb 10, 2026
Merged

refactor: light program pinocchio macro#2247
ananas-block merged 2 commits intomainfrom
jorrit/refactor-light-program-pinocchio-macro

Conversation

@ananas-block
Copy link
Contributor

@ananas-block ananas-block commented Feb 6, 2026

  1. New backend.rs module — Introduces a CodegenBackend trait with AnchorBackend and PinocchioBackend implementations to
    abstract framework-specific differences (serialization derives, crate paths, types, error handling).
  2. accounts/variant.rs refactored — Replaced duplicate generate_() and generate_pinocchio() method pairs with unified
    generate
    *_with_backend(backend) methods; build() and build_for_pinocchio() now delegate to build_with_backend().
  3. program/variant_enum.rs refactored — Unified LightVariantBuilder's duplicate Anchor/Pinocchio code paths into
    backend-parameterized methods (1451→961 lines).
  4. program/compress.rs refactored — CompressBuilder's _pinocchio methods consolidated into _with_backend variants using
    the CodegenBackend trait (557→520 lines).
  5. program/decompress.rs refactored — DecompressBuilder's duplicate Anchor/Pinocchio generation methods merged via
    _with_backend pattern (838→696 lines).
  6. program/instructions.rs refactored — Top-level generate_light_program_items() and
    generate_light_program_items_pinocchio() unified into generate_light_program_items_with_backend() (1424→1213 lines).
  7. mod.rs updated — Added pub mod backend; to expose the new module.

Summary by CodeRabbit

  • Refactor
    • Introduced a unified backend abstraction for code generation, consolidating separate Anchor and Pinocchio code paths into a single parameterized system. This improves maintainability and enables consistent support across both frameworks without changing external macro behavior.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 6, 2026

Warning

Rate limit exceeded

@ananas-block has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 23 minutes and 16 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

This PR introduces a unified backend-driven code generation abstraction for light_pdas macros. It replaces hard-coded per-framework (Anchor vs Pinocchio) generation paths with a CodegenBackend trait, enabling dynamic parameterization of serialization, types, and error handling across all affected macro modules.

Changes

Cohort / File(s) Summary
Backend Abstraction
sdk-libs/macros/src/light_pdas/backend.rs
Introduces new CodegenBackend trait with methods for serialization derives, crate paths, type representations, and error handling. Provides concrete implementations for AnchorBackend and PinocchioBackend to centralize framework-specific differences.
Module Exports
sdk-libs/macros/src/light_pdas/mod.rs
Exposes new backend module as public within the light_pdas crate.
Variant Account Generation
sdk-libs/macros/src/light_pdas/accounts/variant.rs
Replaces per-backend generation methods with unified backend-parameterized variants (e.g., build_with_backend, generate_seeds_struct_with_backend). All seed/variant/packing logic now adapts to backend-specific field types, derives, and serialization paths.
Program Compress
sdk-libs/macros/src/light_pdas/program/compress.rs
Converts generate_enum_dispatch_method to backend-aware variant. Adds generate_enum_process_compress_with_backend and generate_size_validation_with_backend to conditionally generate compression logic based on backend type.
Program Decompress
sdk-libs/macros/src/light_pdas/program/decompress.rs
Adds backend-parameterized methods (generate_seed_provider_impls_with_backend, generate_enum_process_decompress_with_backend, generate_enum_decompress_dispatch_with_backend). Replaces hard-coded Pinocchio checks with backend-driven selection of seed types and error handling.
Program Instructions
sdk-libs/macros/src/light_pdas/program/instructions.rs
Introduces generate_light_program_items_with_backend to consolidate Anchor/Pinocchio code paths. Existing generate_light_program_items now delegates to backend-aware variant via AnchorBackend. Refactors derives, type selections, and error handling to be backend-conditional.
Program Variant Enum
sdk-libs/macros/src/light_pdas/program/variant_enum.rs
Replaces public build method with build_with_backend. All internal generation pathways converted to backend-specific counterparts, affecting LightAccountData fields, seed structures, and trait implementations to adapt to Pinocchio vs Anchor semantics.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • fix: sdk macros #2190 — Modifies the same macro modules (decompress/instructions) for seed/provider generation handling in edge cases like mint-only or no-PDA scenarios, complementing this backend abstraction work.
  • fix: macro deps #2231 — Directly related refactoring of Pinocchio vs Anchor code paths affecting Pubkey/AccountMeta type selections, Pack implementation signatures, and seed/PDA generation logic across the same files.

Suggested labels

ai-review

Suggested reviewers

  • SwenSchaeferjohann
  • sergeytimoshin

Poem

🔧 From tangled paths of old we're freed,
One backend trait to guide the deed—
Anchor, Pinocchio, now unified,
Less duplication, better pride! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'refactor: light program pinocchio macro' directly describes the main structural change—introducing a unified backend-driven code generation abstraction to support both Anchor and Pinocchio frameworks.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 70.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jorrit/refactor-light-program-pinocchio-macro

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
sdk-libs/macros/src/light_pdas/accounts/variant.rs (2)

666-681: 🧹 Nitpick | 🔵 Trivial

Hard-coded light_account_pinocchio::solana_pubkey::Pubkey — same leaky-abstraction pattern.

Line 670 reaches directly into light_account_pinocchio::solana_pubkey::Pubkey. This is the third place where pinocchio-specific paths are hard-coded despite having a backend parameter. Consider a backend method like pubkey_from_seed_field(field_expr: TokenStream) -> TokenStream to encapsulate the conversion.


754-823: ⚠️ Potential issue | 🟠 Major

generate_packed_seeds_struct_with_backend uses unqualified Pubkey type for pinocchio—creates type mismatch with [u8; 32] seed values.

extract_seed_fields at lines 788 and 811 stores packed_field_type: quote! { Pubkey } for DataRooted and FunctionCall pubkey fields. The seeds struct generation correctly applies backend conversion at lines 145–153, mapping all pubkey types to [u8; 32] for pinocchio. However, generate_packed_seeds_struct_with_backend at line 213 uses sf.packed_field_type directly without any backend check.

For pinocchio with DataRooted pubkey seeds: the unpacked struct has pub field: [u8; 32], but the packed struct would emit pub field: Pubkey (bare, unqualified). In generate_pack_seed_fields at line 674, the code tries to assign self.seeds.#field ([u8; 32]) directly to the packed field (Pubkey), causing a type mismatch compile error.

Apply the same backend-aware conversion to generate_packed_seeds_struct_with_backend that is applied to generate_seeds_struct_with_backend. For pinocchio, map non-account pubkey fields to [u8; 32] in the packed struct, matching the unpacked struct's type.

🤖 Fix all issues with AI agents
In `@sdk-libs/macros/src/light_pdas/accounts/variant.rs`:
- Around line 586-592: The branching on backend.is_pinocchio() inside the
has_le_bytes case is redundant because extract_seed_fields already sets
sf.field_type to u64 for has_le_bytes seeds; remove the special-case branch and
always generate the same arm using sf.field_type (i.e., use `#field`:
`#ty`::from_le_bytes(self.seeds.#field) where ty = &sf.field_type) so the code no
longer implies a backend-specific difference; update the match arm that
currently checks sf.has_le_bytes and references backend.is_pinocchio()
accordingly.
- Around line 162-192: Refactor the repeated backend.is_pinocchio() branching in
generate_seeds_struct_with_backend, generate_packed_seeds_struct_with_backend,
generate_variant_struct_with_backend, and
generate_packed_variant_struct_with_backend by computing an optional doc
attribute once and reusing it in the quote! blocks (or add a doc_attr(&self,
text: &str) -> TokenStream helper on CodegenBackend and call that);
specifically, build let doc_attr = if backend.is_pinocchio() { quote!{} } else {
let doc = format!("Seeds for {} PDA.", self.variant_name); quote! { #[doc =
`#doc`] } } and then include `#doc_attr` in the struct generation quotes to remove
the duplicated if/else branches that only differ by the doc attribute.
- Around line 440-485: Replace the hard-coded account types in the Pack
implementations for `#variant_name` with the backend-provided types: use
backend.account_meta_type() instead of
`#account_crate`::solana_instruction::AccountMeta in the impl header, and use
backend.account_info_type() instead of pinocchio::account_info::AccountInfo and
`#account_crate`::AccountInfo<'static> when calling self.derive_pda::<...>();
update both the pinocchio branch and the generic AM branch so
derive_pda::<...>() and the Pack<AM> signature reference the
backend.account_info_type() / backend.account_meta_type() helpers (ensuring any
required lifetime is applied to account_info_type() where previously 'static was
used).
- Around line 373-389: The backend-specific branch in unpack_data is
unnecessary; replace the if/else on backend.is_pinocchio() with a single unified
block that always constructs ProgramPackedAccounts via
`#account_crate`::packed_accounts::ProgramPackedAccounts and calls <#inner_type as
`#account_crate`::LightAccount>::unpack(&self.data, &packed_accounts). Ensure the
error mapping still returns `#sdk_error`::InvalidInstructionData on unpack failure
and remove the special-case reference to
light_account_pinocchio::light_account_checks::packed_accounts::ProgramPackedAccounts.

In `@sdk-libs/macros/src/light_pdas/program/instructions.rs`:
- Around line 849-866: In process_update_config, the account variables are
reversed: it currently sets authority = &accounts[0] and config = &accounts[1]
but later builds remaining as [*config, *authority] and callers expect config
first (as declared earlier around lines 606-612); fix by swapping the
assignments so config = &accounts[0] and authority = &accounts[1] (so remaining
remains [*config, *authority]) before calling
account_crate::process_update_light_config.

In `@sdk-libs/macros/src/light_pdas/program/variant_enum.rs`:
- Around line 106-133: The generate_light_account_data_struct_with_backend
function hardcodes borsh derives for the non-pinocchio branch; update the
LightAccountData generation to use the backend-provided tokens serialize_derive
and deserialize_derive instead of borsh::BorshSerialize/borsh::BorshDeserialize
so both branches consistently use backend.serialize_derive() and
backend.deserialize_derive(); modify the else branch that builds the
LightAccountData struct to replace the hardcoded derives with `#serialize_derive`
and `#deserialize_derive` (same pattern used in the pinocchio branch).

Comment on lines +440 to 485
if backend.is_pinocchio() {
quote! {
#[cfg(not(target_os = "solana"))]
impl #account_crate::Pack<#account_crate::solana_instruction::AccountMeta> for #variant_name {
type Packed = #packed_variant_name;

fn pack(
&self,
accounts: &mut #account_crate::PackedAccounts,
) -> std::result::Result<Self::Packed, #sdk_error> {
use #account_crate::LightAccountVariantTrait;
let (_, bump) = self.derive_pda::<pinocchio::account_info::AccountInfo>();
Ok(#packed_variant_name {
seeds: #packed_seeds_struct_name {
#(#pack_seed_fields,)*
bump,
},
data: #pack_data,
})
}
}
}
} else {
quote! {
// Pack trait is only available off-chain (client-side packing)
#[cfg(not(target_os = "solana"))]
impl<AM: #account_crate::AccountMetaTrait> #account_crate::Pack<AM> for #variant_name {
type Packed = #packed_variant_name;

fn pack(
&self,
accounts: &mut #account_crate::interface::instruction::PackedAccounts<AM>,
) -> std::result::Result<Self::Packed, #sdk_error> {
use #account_crate::LightAccountVariantTrait;
let (_, bump) = self.derive_pda::<#account_crate::AccountInfo<'static>>();
Ok(#packed_variant_name {
seeds: #packed_seeds_struct_name {
#(#pack_seed_fields,)*
bump,
},
data: #pack_data,
})
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the CodegenBackend trait definition and its methods
fd -t f "backend" sdk-libs/macros --type rust | head -20

Repository: Lightprotocol/light-protocol

Length of output: 243


🏁 Script executed:

#!/bin/bash
# Search for CodegenBackend trait definition
rg -n "trait CodegenBackend" --type rust -A 30

Repository: Lightprotocol/light-protocol

Length of output: 2839


🏁 Script executed:

#!/bin/bash
# Check for account_info_type or account_meta_type methods
rg -n "fn account_info_type|fn account_meta_type|fn account_info_trait" --type rust -B 2 -A 5

Repository: Lightprotocol/light-protocol

Length of output: 4734


🏁 Script executed:

#!/bin/bash
# Read the exact lines 440-485 in variant.rs
sed -n '440,485p' sdk-libs/macros/src/light_pdas/accounts/variant.rs | cat -n

Repository: Lightprotocol/light-protocol

Length of output: 2509


🏁 Script executed:

#!/bin/bash
# Look for CLAUDE.md files that might guide macro implementation
find . -name "CLAUDE.md" -o -name "*.md" | grep -i "macro\|account" | head -10

Repository: Lightprotocol/light-protocol

Length of output: 569


Use backend.account_info_type() and backend.account_meta_type() instead of hard-coded type paths.

Lines 443, 451, and 464 hard-code type paths that the CodegenBackend trait already provides via account_info_type() and account_meta_type() methods. The backend abstraction exists specifically to centralize SDK-specific type selection—leverage it rather than bypassing it with hard-coded paths.

Apply backend methods for type consistency

Line 443: Replace #account_crate::solana_instruction::AccountMeta with backend.account_meta_type()

Line 451: Replace pinocchio::account_info::AccountInfo with backend.account_info_type()

Line 464: Replace #account_crate::AccountInfo<'static> with backend.account_info_type() (with proper lifetime handling)

🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/light_pdas/accounts/variant.rs` around lines 440 - 485,
Replace the hard-coded account types in the Pack implementations for
`#variant_name` with the backend-provided types: use backend.account_meta_type()
instead of `#account_crate`::solana_instruction::AccountMeta in the impl header,
and use backend.account_info_type() instead of
pinocchio::account_info::AccountInfo and `#account_crate`::AccountInfo<'static>
when calling self.derive_pda::<...>(); update both the pinocchio branch and the
generic AM branch so derive_pda::<...>() and the Pack<AM> signature reference
the backend.account_info_type() / backend.account_meta_type() helpers (ensuring
any required lifetime is applied to account_info_type() where previously 'static
was used).

- Fix account order bug in process_update_config (config=0, authority=1)
- Use backend-provided serialize/deserialize derives in LightAccountData
- Remove redundant is_pinocchio() branch for has_le_bytes unpack fields
- DRY doc attribute generation across 4 struct generation methods
- Unify unpack_data path using account_crate re-export for both backends
@ananas-block ananas-block merged commit 7e41a72 into main Feb 10, 2026
20 checks passed
@ananas-block ananas-block deleted the jorrit/refactor-light-program-pinocchio-macro branch February 10, 2026 01:13
SwenSchaeferjohann pushed a commit that referenced this pull request Feb 11, 2026
* refactor: light program pinocchio macro

* fix: address CodeRabbit review comments on macro codegen

- Fix account order bug in process_update_config (config=0, authority=1)
- Use backend-provided serialize/deserialize derives in LightAccountData
- Remove redundant is_pinocchio() branch for has_le_bytes unpack fields
- DRY doc attribute generation across 4 struct generation methods
- Unify unpack_data path using account_crate re-export for both backends
SwenSchaeferjohann added a commit that referenced this pull request Feb 11, 2026
refactor

remove fetch-accounts

renaming, simplify trait

fomat

excl photon-api submodule

fix: multi-pass cold account lookup in test indexer RPC

Align get_account_interface and get_multiple_account_interfaces with
Photon's lookup strategy: search compressed_accounts by onchain_pubkey,
then by PDA seed derivation, then token_compressed_accounts, then by
token_data.owner. Also fix as_mint() to accept ColdContext::Account
since Photon returns mints as generic compressed accounts.

Co-authored-by: Cursor <cursoragent@cursor.com>

lint

fix lint

fix: reject rent sponsor self-referencing the token account (#2257)

* fix: reject rent sponsor self-referencing the token account

Audit issue #9 (INFO): The rent payer could be the same account as
the target token account being created. Add a check that rejects
this self-reference to prevent accounting issues.

* test: add failing test rent sponsor self reference

fix: process metadata add/remove actions in sequential order (#2256)

* fix: process metadata add/remove actions in sequential order

Audit issue #16 (LOW): should_add_key checked for any add and any
remove independently, ignoring action ordering. An add-remove-add
sequence would incorrectly remove the key. Process actions
sequentially so the final state reflects the actual order.

* chore: format

* test: add randomized test for metadata action processing

Validates that process_extensions_config_with_actions produces correct
AdditionalMetadataConfig for random sequences of UpdateMetadataField
and RemoveMetadataKey actions, covering the add-remove-add bug from
audit issue #16.

* test: add integration test for audit issue #13 (no double rent charge)

Verifies that two compress operations targeting the same compressible
CToken account in a single Transfer2 instruction do not double-charge
the rent top-up budget.

* chore: format extensions_metadata test

fix: validate authority on self-transfer early return (#2252)

* fix: handle self-transfer in ctoken transfer and transfer_checked

Validate that the authority is a signer and is the owner or delegate
before allowing self-transfer early return. Previously the self-transfer
path returned Ok(()) without any authority validation.

* fix: simplify map_or to is_some_and per clippy

* fix: use pubkey_eq for self-transfer check

* refactor: extract self-transfer validation into shared function

Extract duplicate self-transfer check from default.rs and checked.rs
into validate_self_transfer() in shared.rs with cold path for authority
validation.

* chore: format

* fix: deduplicate random metadata keys in test_random_mint_action

Random key generation could produce duplicate keys, causing
DuplicateMetadataKey error (18040) with certain seeds.

fix: enforce mint extension checks in cToken-to-cToken decompress (#2246)

* fix: enforce mint extension checks in cToken-to-cToken decompress hot path

Add enforce_extension_state() to MintExtensionChecks and call it in the
Decompress branch when decompress_inputs is None (hot-path, not
CompressedOnly restore). This prevents cToken-to-cToken transfers from
bypassing pause, transfer fee, and transfer hook restrictions.

* fix test

chore: reject compress for mints with restricted extensions in build_mint_extension_cache (#2240)

* chore: reject compress for mints with restricted extensions in mint check

* Update programs/compressed-token/program/src/compressed_token/transfer2/check_extensions.rs

Co-authored-by: 0xa5df-c <172008956+0xa5df-c@users.noreply.github.com>

* fix: format else-if condition for lint

---------

Co-authored-by: 0xa5df-c <172008956+0xa5df-c@users.noreply.github.com>

fix: token-pool index 0 check (#2239)

fix(programs): add MintCloseAuthority as restricted extension (M-03) (#2263)

* fix: add MintCloseAuthority as restricted extension (M-03)

A mint with MintCloseAuthority can be closed and re-opened with
different extensions. Treating it as restricted ensures compressed
tokens from such mints require CompressedOnly mode.

* test: add MintCloseAuthority compression_only requirement tests

Add test coverage for MintCloseAuthority requiring compression_only mode,
complementing the fix in f2da063.

refactor: light program pinocchio macro (#2247)

* refactor: light program pinocchio macro

* fix: address CodeRabbit review comments on macro codegen

- Fix account order bug in process_update_config (config=0, authority=1)
- Use backend-provided serialize/deserialize derives in LightAccountData
- Remove redundant is_pinocchio() branch for has_le_bytes unpack fields
- DRY doc attribute generation across 4 struct generation methods
- Unify unpack_data path using account_crate re-export for both backends

chore(libs): bump versions (#2272)

fix(programs): allow account-level delegate to compress CToken (M-02) (#2262)

* fix: allow account-level delegate to compress tokens from CToken (M-02)

check_ctoken_owner() only checked owner and permanent delegate.
An account-level delegate (approved via CTokenApprove) could not
compress tokens. Added delegate check after permanent delegate.

* test: compress by delegate

fix: accumulate delegated amount at decompression (#2242)

* fix: accumulate delegated amount at decompression

* fix lint

* refactor: simplify apply_delegate to single accumulation path

* fix: ignore delegated_amount without delegate

* restore decompress amount check

fix programtest, wallet owner tracking for ata

fmt and lint
SwenSchaeferjohann added a commit that referenced this pull request Feb 11, 2026
refactor

remove fetch-accounts

renaming, simplify trait

fomat

excl photon-api submodule

fix: multi-pass cold account lookup in test indexer RPC

Align get_account_interface and get_multiple_account_interfaces with
Photon's lookup strategy: search compressed_accounts by onchain_pubkey,
then by PDA seed derivation, then token_compressed_accounts, then by
token_data.owner. Also fix as_mint() to accept ColdContext::Account
since Photon returns mints as generic compressed accounts.

Co-authored-by: Cursor <cursoragent@cursor.com>

lint

fix lint

fix: reject rent sponsor self-referencing the token account (#2257)

* fix: reject rent sponsor self-referencing the token account

Audit issue #9 (INFO): The rent payer could be the same account as
the target token account being created. Add a check that rejects
this self-reference to prevent accounting issues.

* test: add failing test rent sponsor self reference

fix: process metadata add/remove actions in sequential order (#2256)

* fix: process metadata add/remove actions in sequential order

Audit issue #16 (LOW): should_add_key checked for any add and any
remove independently, ignoring action ordering. An add-remove-add
sequence would incorrectly remove the key. Process actions
sequentially so the final state reflects the actual order.

* chore: format

* test: add randomized test for metadata action processing

Validates that process_extensions_config_with_actions produces correct
AdditionalMetadataConfig for random sequences of UpdateMetadataField
and RemoveMetadataKey actions, covering the add-remove-add bug from
audit issue #16.

* test: add integration test for audit issue #13 (no double rent charge)

Verifies that two compress operations targeting the same compressible
CToken account in a single Transfer2 instruction do not double-charge
the rent top-up budget.

* chore: format extensions_metadata test

fix: validate authority on self-transfer early return (#2252)

* fix: handle self-transfer in ctoken transfer and transfer_checked

Validate that the authority is a signer and is the owner or delegate
before allowing self-transfer early return. Previously the self-transfer
path returned Ok(()) without any authority validation.

* fix: simplify map_or to is_some_and per clippy

* fix: use pubkey_eq for self-transfer check

* refactor: extract self-transfer validation into shared function

Extract duplicate self-transfer check from default.rs and checked.rs
into validate_self_transfer() in shared.rs with cold path for authority
validation.

* chore: format

* fix: deduplicate random metadata keys in test_random_mint_action

Random key generation could produce duplicate keys, causing
DuplicateMetadataKey error (18040) with certain seeds.

fix: enforce mint extension checks in cToken-to-cToken decompress (#2246)

* fix: enforce mint extension checks in cToken-to-cToken decompress hot path

Add enforce_extension_state() to MintExtensionChecks and call it in the
Decompress branch when decompress_inputs is None (hot-path, not
CompressedOnly restore). This prevents cToken-to-cToken transfers from
bypassing pause, transfer fee, and transfer hook restrictions.

* fix test

chore: reject compress for mints with restricted extensions in build_mint_extension_cache (#2240)

* chore: reject compress for mints with restricted extensions in mint check

* Update programs/compressed-token/program/src/compressed_token/transfer2/check_extensions.rs

Co-authored-by: 0xa5df-c <172008956+0xa5df-c@users.noreply.github.com>

* fix: format else-if condition for lint

---------

Co-authored-by: 0xa5df-c <172008956+0xa5df-c@users.noreply.github.com>

fix: token-pool index 0 check (#2239)

fix(programs): add MintCloseAuthority as restricted extension (M-03) (#2263)

* fix: add MintCloseAuthority as restricted extension (M-03)

A mint with MintCloseAuthority can be closed and re-opened with
different extensions. Treating it as restricted ensures compressed
tokens from such mints require CompressedOnly mode.

* test: add MintCloseAuthority compression_only requirement tests

Add test coverage for MintCloseAuthority requiring compression_only mode,
complementing the fix in f2da063.

refactor: light program pinocchio macro (#2247)

* refactor: light program pinocchio macro

* fix: address CodeRabbit review comments on macro codegen

- Fix account order bug in process_update_config (config=0, authority=1)
- Use backend-provided serialize/deserialize derives in LightAccountData
- Remove redundant is_pinocchio() branch for has_le_bytes unpack fields
- DRY doc attribute generation across 4 struct generation methods
- Unify unpack_data path using account_crate re-export for both backends

chore(libs): bump versions (#2272)

fix(programs): allow account-level delegate to compress CToken (M-02) (#2262)

* fix: allow account-level delegate to compress tokens from CToken (M-02)

check_ctoken_owner() only checked owner and permanent delegate.
An account-level delegate (approved via CTokenApprove) could not
compress tokens. Added delegate check after permanent delegate.

* test: compress by delegate

fix: accumulate delegated amount at decompression (#2242)

* fix: accumulate delegated amount at decompression

* fix lint

* refactor: simplify apply_delegate to single accumulation path

* fix: ignore delegated_amount without delegate

* restore decompress amount check

fix programtest, wallet owner tracking for ata

fmt and lint
SwenSchaeferjohann added a commit that referenced this pull request Feb 11, 2026
refactor

remove fetch-accounts

renaming, simplify trait

fomat

excl photon-api submodule

fix: multi-pass cold account lookup in test indexer RPC

Align get_account_interface and get_multiple_account_interfaces with
Photon's lookup strategy: search compressed_accounts by onchain_pubkey,
then by PDA seed derivation, then token_compressed_accounts, then by
token_data.owner. Also fix as_mint() to accept ColdContext::Account
since Photon returns mints as generic compressed accounts.

Co-authored-by: Cursor <cursoragent@cursor.com>

lint

fix lint

fix: reject rent sponsor self-referencing the token account (#2257)

* fix: reject rent sponsor self-referencing the token account

Audit issue #9 (INFO): The rent payer could be the same account as
the target token account being created. Add a check that rejects
this self-reference to prevent accounting issues.

* test: add failing test rent sponsor self reference

fix: process metadata add/remove actions in sequential order (#2256)

* fix: process metadata add/remove actions in sequential order

Audit issue #16 (LOW): should_add_key checked for any add and any
remove independently, ignoring action ordering. An add-remove-add
sequence would incorrectly remove the key. Process actions
sequentially so the final state reflects the actual order.

* chore: format

* test: add randomized test for metadata action processing

Validates that process_extensions_config_with_actions produces correct
AdditionalMetadataConfig for random sequences of UpdateMetadataField
and RemoveMetadataKey actions, covering the add-remove-add bug from
audit issue #16.

* test: add integration test for audit issue #13 (no double rent charge)

Verifies that two compress operations targeting the same compressible
CToken account in a single Transfer2 instruction do not double-charge
the rent top-up budget.

* chore: format extensions_metadata test

fix: validate authority on self-transfer early return (#2252)

* fix: handle self-transfer in ctoken transfer and transfer_checked

Validate that the authority is a signer and is the owner or delegate
before allowing self-transfer early return. Previously the self-transfer
path returned Ok(()) without any authority validation.

* fix: simplify map_or to is_some_and per clippy

* fix: use pubkey_eq for self-transfer check

* refactor: extract self-transfer validation into shared function

Extract duplicate self-transfer check from default.rs and checked.rs
into validate_self_transfer() in shared.rs with cold path for authority
validation.

* chore: format

* fix: deduplicate random metadata keys in test_random_mint_action

Random key generation could produce duplicate keys, causing
DuplicateMetadataKey error (18040) with certain seeds.

fix: enforce mint extension checks in cToken-to-cToken decompress (#2246)

* fix: enforce mint extension checks in cToken-to-cToken decompress hot path

Add enforce_extension_state() to MintExtensionChecks and call it in the
Decompress branch when decompress_inputs is None (hot-path, not
CompressedOnly restore). This prevents cToken-to-cToken transfers from
bypassing pause, transfer fee, and transfer hook restrictions.

* fix test

chore: reject compress for mints with restricted extensions in build_mint_extension_cache (#2240)

* chore: reject compress for mints with restricted extensions in mint check

* Update programs/compressed-token/program/src/compressed_token/transfer2/check_extensions.rs

Co-authored-by: 0xa5df-c <172008956+0xa5df-c@users.noreply.github.com>

* fix: format else-if condition for lint

---------

Co-authored-by: 0xa5df-c <172008956+0xa5df-c@users.noreply.github.com>

fix: token-pool index 0 check (#2239)

fix(programs): add MintCloseAuthority as restricted extension (M-03) (#2263)

* fix: add MintCloseAuthority as restricted extension (M-03)

A mint with MintCloseAuthority can be closed and re-opened with
different extensions. Treating it as restricted ensures compressed
tokens from such mints require CompressedOnly mode.

* test: add MintCloseAuthority compression_only requirement tests

Add test coverage for MintCloseAuthority requiring compression_only mode,
complementing the fix in f2da063.

refactor: light program pinocchio macro (#2247)

* refactor: light program pinocchio macro

* fix: address CodeRabbit review comments on macro codegen

- Fix account order bug in process_update_config (config=0, authority=1)
- Use backend-provided serialize/deserialize derives in LightAccountData
- Remove redundant is_pinocchio() branch for has_le_bytes unpack fields
- DRY doc attribute generation across 4 struct generation methods
- Unify unpack_data path using account_crate re-export for both backends

chore(libs): bump versions (#2272)

fix(programs): allow account-level delegate to compress CToken (M-02) (#2262)

* fix: allow account-level delegate to compress tokens from CToken (M-02)

check_ctoken_owner() only checked owner and permanent delegate.
An account-level delegate (approved via CTokenApprove) could not
compress tokens. Added delegate check after permanent delegate.

* test: compress by delegate

fix: accumulate delegated amount at decompression (#2242)

* fix: accumulate delegated amount at decompression

* fix lint

* refactor: simplify apply_delegate to single accumulation path

* fix: ignore delegated_amount without delegate

* restore decompress amount check

fix programtest, wallet owner tracking for ata

fmt and lint

upd amm test

simplify client usage, remove unnecessary endpoints

clean

cleanup

lint
SwenSchaeferjohann added a commit that referenced this pull request Feb 11, 2026
refactor

remove fetch-accounts

renaming, simplify trait

fomat

excl photon-api submodule

fix: multi-pass cold account lookup in test indexer RPC

Align get_account_interface and get_multiple_account_interfaces with
Photon's lookup strategy: search compressed_accounts by onchain_pubkey,
then by PDA seed derivation, then token_compressed_accounts, then by
token_data.owner. Also fix as_mint() to accept ColdContext::Account
since Photon returns mints as generic compressed accounts.

Co-authored-by: Cursor <cursoragent@cursor.com>

lint

fix lint

fix: reject rent sponsor self-referencing the token account (#2257)

* fix: reject rent sponsor self-referencing the token account

Audit issue #9 (INFO): The rent payer could be the same account as
the target token account being created. Add a check that rejects
this self-reference to prevent accounting issues.

* test: add failing test rent sponsor self reference

fix: process metadata add/remove actions in sequential order (#2256)

* fix: process metadata add/remove actions in sequential order

Audit issue #16 (LOW): should_add_key checked for any add and any
remove independently, ignoring action ordering. An add-remove-add
sequence would incorrectly remove the key. Process actions
sequentially so the final state reflects the actual order.

* chore: format

* test: add randomized test for metadata action processing

Validates that process_extensions_config_with_actions produces correct
AdditionalMetadataConfig for random sequences of UpdateMetadataField
and RemoveMetadataKey actions, covering the add-remove-add bug from
audit issue #16.

* test: add integration test for audit issue #13 (no double rent charge)

Verifies that two compress operations targeting the same compressible
CToken account in a single Transfer2 instruction do not double-charge
the rent top-up budget.

* chore: format extensions_metadata test

fix: validate authority on self-transfer early return (#2252)

* fix: handle self-transfer in ctoken transfer and transfer_checked

Validate that the authority is a signer and is the owner or delegate
before allowing self-transfer early return. Previously the self-transfer
path returned Ok(()) without any authority validation.

* fix: simplify map_or to is_some_and per clippy

* fix: use pubkey_eq for self-transfer check

* refactor: extract self-transfer validation into shared function

Extract duplicate self-transfer check from default.rs and checked.rs
into validate_self_transfer() in shared.rs with cold path for authority
validation.

* chore: format

* fix: deduplicate random metadata keys in test_random_mint_action

Random key generation could produce duplicate keys, causing
DuplicateMetadataKey error (18040) with certain seeds.

fix: enforce mint extension checks in cToken-to-cToken decompress (#2246)

* fix: enforce mint extension checks in cToken-to-cToken decompress hot path

Add enforce_extension_state() to MintExtensionChecks and call it in the
Decompress branch when decompress_inputs is None (hot-path, not
CompressedOnly restore). This prevents cToken-to-cToken transfers from
bypassing pause, transfer fee, and transfer hook restrictions.

* fix test

chore: reject compress for mints with restricted extensions in build_mint_extension_cache (#2240)

* chore: reject compress for mints with restricted extensions in mint check

* Update programs/compressed-token/program/src/compressed_token/transfer2/check_extensions.rs

Co-authored-by: 0xa5df-c <172008956+0xa5df-c@users.noreply.github.com>

* fix: format else-if condition for lint

---------

Co-authored-by: 0xa5df-c <172008956+0xa5df-c@users.noreply.github.com>

fix: token-pool index 0 check (#2239)

fix(programs): add MintCloseAuthority as restricted extension (M-03) (#2263)

* fix: add MintCloseAuthority as restricted extension (M-03)

A mint with MintCloseAuthority can be closed and re-opened with
different extensions. Treating it as restricted ensures compressed
tokens from such mints require CompressedOnly mode.

* test: add MintCloseAuthority compression_only requirement tests

Add test coverage for MintCloseAuthority requiring compression_only mode,
complementing the fix in f2da063.

refactor: light program pinocchio macro (#2247)

* refactor: light program pinocchio macro

* fix: address CodeRabbit review comments on macro codegen

- Fix account order bug in process_update_config (config=0, authority=1)
- Use backend-provided serialize/deserialize derives in LightAccountData
- Remove redundant is_pinocchio() branch for has_le_bytes unpack fields
- DRY doc attribute generation across 4 struct generation methods
- Unify unpack_data path using account_crate re-export for both backends

chore(libs): bump versions (#2272)

fix(programs): allow account-level delegate to compress CToken (M-02) (#2262)

* fix: allow account-level delegate to compress tokens from CToken (M-02)

check_ctoken_owner() only checked owner and permanent delegate.
An account-level delegate (approved via CTokenApprove) could not
compress tokens. Added delegate check after permanent delegate.

* test: compress by delegate

fix: accumulate delegated amount at decompression (#2242)

* fix: accumulate delegated amount at decompression

* fix lint

* refactor: simplify apply_delegate to single accumulation path

* fix: ignore delegated_amount without delegate

* restore decompress amount check

fix programtest, wallet owner tracking for ata

fmt and lint

upd amm test

simplify client usage, remove unnecessary endpoints

clean

cleanup

lint
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants