Skip to content

Conversation

@ananas-block
Copy link
Contributor

@ananas-block ananas-block commented Jan 23, 2026

Summary by CodeRabbit

Release Notes

  • Documentation
    • Added comprehensive documentation suite for the Light System Program, including architecture overviews, instruction specifications, account layouts, CPI context management, processing pipelines, and initialization/invocation workflows with practical examples and error code references.

✏️ Tip: You can customize this high-level summary in your review settings.

@ananas-block ananas-block marked this pull request as draft January 23, 2026 15:12
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 23, 2026

📝 Walkthrough

Walkthrough

This PR adds comprehensive documentation for the Light System Program across 12 markdown files, detailing account layouts (CPI context in V1/V2), instruction specifications (Invoke, InvokeCpi variants), a 19-step processing pipeline, CPI context feature, initialization workflows, and error references.

Changes

Cohort / File(s) Summary
Core Program Documentation
programs/system/CLAUDE.md, programs/system/docs/CLAUDE.md
Main overview and documentation index for Light System Program, including folder structure, navigation, and task-oriented reference.
Account & Data Structure Docs
programs/system/docs/ACCOUNTS.md
Comprehensive CPI context account layouts covering V1 (legacy) and V2 (zero-copy) with discriminators, serialization formats, memory layout, capacity planning, and lifecycle management. Includes supporting type definitions (ZPackedReadOnlyAddress, ZPackedMerkleContext, etc.).
Instruction Reference
programs/system/docs/INSTRUCTIONS.md
Discriminator table mapping instructions to enum variants, instruction categories (CPI Context Account Management, Direct Invocation, CPI Invocation), input matrices, and comprehensive error code reference.
Processing Pipeline
programs/system/docs/PROCESSING_PIPELINE.md
19-step processing flow detailing input types, sequential operations (deserialization, validation, address derivation, SOL compression/decompression, ZK proof verification, fee transfers), proof verification modes (by index vs. ZKP), and related implementation modules.
CPI Context Feature
programs/system/docs/CPI_CONTEXT.md
CPI context state machine, multi-invocation flow (First Set → Set Context → Execute), data structures (CompressedCpiContext), validation rules, error codes, limitations, and step-by-step processing for each invocation.
Initialization Instructions
programs/system/docs/init/INIT_CPI_CONTEXT_ACCOUNT.md, programs/system/docs/init/REINIT_CPI_CONTEXT_ACCOUNT.md
InitializeCpiContextAccount instruction (discriminator, accounts, validation logic, default capacity) and ReInitCpiContextAccount migration (V1→V2 in-place migration workflow with feature gate, resize steps, version comparison).
Invoke Instructions
programs/system/docs/invoke/INVOKE.md
Direct invocation mode (no CPI), instruction data layout (ZInstructionDataInvoke), account validations, and comparison with InvokeCpi.
InvokeCpi Variants
programs/system/docs/invoke_cpi/INVOKE_CPI.md, programs/system/docs/invoke_cpi/INVOKE_CPI_WITH_ACCOUNT_INFO.md, programs/system/docs/invoke_cpi/INVOKE_CPI_WITH_READ_ONLY.md
Core InvokeCpi (CPI context modes, comparison with Invoke, signer checks). InvokeCpiWithAccountInfo in V2 mode with dynamic account configuration via AccountOptions struct. InvokeCpiWithReadOnly enabling read-only verification of compressed accounts/addresses with Merkle proof and bloom-filter semantics.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Review requires verification of documentation accuracy against implementation, cross-referencing across 12 heterogeneous files covering instructions, account layouts, pipeline steps, and error codes; moderate complexity in explaining multi-step flows and data structures.

Possibly related PRs

Suggested labels

ai-review

Suggested reviewers

  • sergeytimoshin
  • SwenSchaeferjohann

Poem

📚 Twelve docs unfold the system's light,
From CPI contexts, flowing right,
Nineteen steps in perfect sequence shine,
Account layouts and errors align,
Now developers see what was hidden from sight.

🚥 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 clearly and specifically describes the main change: comprehensive documentation additions for the Light System Program across multiple files and topics.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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/docs-system-program

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: 13

🤖 Fix all issues with AI agents
In `@programs/system/CLAUDE.md`:
- Line 9: The markdown in CLAUDE.md needs spacing and fence fixes: add a blank
line before the heading that currently reads "Handles SOL
compression/decompression" (and similarly add blank lines before headings at the
other flagged positions), ensure every fenced code block is surrounded by a
blank line (fix blocks at the positions flagged) and add language specifiers
(use "text" for the plain shell/example blocks) to the fenced code blocks
currently missing them; locate these edits by searching for the heading text
"Handles SOL compression/decompression" and the three fenced code blocks shown
in the diff and adjust spacing and fence headers accordingly.
- Around line 150-167: The header currently misstates "The 19-step processing
flow" while the list in the "Processing Pipeline" section (referencing
src/processor/process.rs) shows 12 grouped steps; update the text to either list
all 19 discrete steps from docs/PROCESSING_PIPELINE.md or change the sentence to
indicate this is a high-level summary (e.g., "A high-level 12-step summary of
the 19-step processing flow") and add a clarifying pointer to
docs/PROCESSING_PIPELINE.md; make the change near the "Processing Pipeline"
section heading to ensure consistency between the step count and the enumerated
items.
- Around line 11-24: Update the example transaction flow to match the
CompressedCpiContext state machine: replace the incorrect "execute=true" in the
Program C step with a call to InvokeCpiWithAccountInfo where
first_set_context=false and set_context=false (or omit both since false is
default) to represent the Execute state; reference the CompressedCpiContext
semantics (see CPI_CONTEXT.md) and keep the other steps (Program A:
first_set_context=true, Program B: set_context=true) unchanged.

In `@programs/system/docs/ACCOUNTS.md`:
- Around line 15-114: The documentation's V2 account size math is incorrect:
recompute and update the type sizes for ZPackedReadOnlyAddress,
ZPackedReadOnlyCompressedAccount, CpiContextInAccount, and CpiContextOutAccount
(use the actual sizes from zero_copy.rs and account.rs) and then either (A)
replace the per-type breakdown and total with the corrected totals that sum to
the actual layout, or (B) explicitly state that
DEFAULT_CPI_CONTEXT_ACCOUNT_SIZE_V2 (the 14020 constant) includes an additional
reserved buffer beyond the serialized fields; update the Size Calculation
Example and the "Total" line accordingly and reference the actual symbols/types
CpiContextNewAddressParamsAssignedPacked, ZPackedReadOnlyAddress,
ZPackedReadOnlyCompressedAccount, CpiContextInAccount, CpiContextOutAccount and
the constant DEFAULT_CPI_CONTEXT_ACCOUNT_SIZE_V2 so readers can verify against
program-libs/compressed-account/src/instruction_data/zero_copy.rs and
programs/system/src/cpi_context/{account.rs,address.rs}.

In `@programs/system/docs/init/INIT_CPI_CONTEXT_ACCOUNT.md`:
- Around line 5-101: The Errors table mixes generic names with actual error
variants returned by our account checks; update the documented errors to use the
real error codes and variant names produced by check_owner and check_signer
(e.g., AccountOwnedByWrongProgram 20001 for associated_merkle_tree ownership and
InvalidSigner 20009 for fee_payer signer), retain InvalidCpiContextOwner 6055
for cpi_context_account ownership, keep NotEnoughAccountKeys and the state-tree
discriminator error (StateMerkleTreeAccountDiscriminatorMismatch 6042), and
remove or replace any “IllegalOwner”/“MissingRequiredSignature” entries so the
table matches the actual return values from check_owner and check_signer in
InitializeCpiContextAccount/cpi_context_account_new.

In `@programs/system/docs/invoke_cpi/INVOKE_CPI_WITH_ACCOUNT_INFO.md`:
- Around line 11-29: The README is missing a dedicated CPI section for
InvokeCpiWithAccountInfo; add a "CPI Invocation" subsection that documents the
target program (program id / entrypoint), required PDA signer seeds and bump
values, how AccountOptions maps to concrete accounts (account mapping
passthrough), how raw instruction data and any contiguous data passthrough are
encoded/decoded for the callee (data passthrough handling), and include a full
end-to-end example that builds the instruction for InvokeCpiWithAccountInfo
using AccountOptions, signs with the PDA (showing seeds + bump), lists the exact
AccountInfo array order, and shows the final invoke_signed call; reference the
instruction name InvokeCpiWithAccountInfo and the AccountOptions encoding so
readers can map flags to accounts.
- Around line 110-147: The fenced code blocks under the "Account Layout
Examples" section (including the "Execute Mode (write_to_cpi_context = false)"
and "CPI Context Write Mode (write_to_cpi_context = true)" examples) need
markdownlint fixes: add a language identifier (e.g., text) after each opening
``` and ensure there is a blank line before and after each fenced block so the
blocks are properly separated from surrounding headings and paragraphs; update
all three blocks shown in the diff accordingly.
- Around line 39-241: The documented InstructionDataInvokeCpiWithAccountInfo
struct is out of sync with the real implementation; update the docs to exactly
match the actual struct in
program-libs/compressed-account/src/instruction_data/with_account_info.rs by
replacing the current fields with the actual ones: include mode:u8 and bump:u8,
invoking_program_id:Pubkey, compress_or_decompress_lamports:u64,
is_compress:bool, with_cpi_context:bool, with_transaction_hash:bool,
cpi_context:CompressedCpiContext, proof:Option<CompressedProof>,
new_address_params:Vec<NewAddressParamsAssignedPacked>, and
account_infos:Vec<CompressedAccountInfo>; remove the incorrect
account_option_config:AccountOptions, relay_fee, read_only_accounts, and
read_only_addresses entries and ensure the struct name
InstructionDataInvokeCpiWithAccountInfo and referenced types
(CompressedCpiContext, CompressedProof, NewAddressParamsAssignedPacked,
CompressedAccountInfo) are used exactly as in the source so the docs match the
implementation.

In `@programs/system/docs/invoke_cpi/INVOKE_CPI_WITH_READ_ONLY.md`:
- Around line 11-34: Update the InvokeCpiWithReadOnly documentation to add a
dedicated "CPI Invocation" section that specifies the target program ID
(explicit in instruction data), required PDA signer seeds and bump values, how
account mapping/passthrough should be handled for both writable and read-only
compressed accounts, and include a full runnable example showing: the
instruction construction (InvokeCpiWithReadOnly), how to populate the accounts
array (writable inputs nullified, outputs created, read-only accounts verified),
how to supply PDA seeds + bump to sign the CPI, and a minimal on-chain/off-chain
invocation snippet demonstrating execute mode for read-only addresses; ensure
the section references the existing terms InvokeCpiWithReadOnly, InvokeCpi,
"read-only addresses", and "account mode" so reviewers can locate the changes.
- Around line 41-61: Add proper blank lines before and after fenced code blocks
and label non-Rust blocks with a language identifier to satisfy markdownlint;
for the shown block containing the ZInstructionDataInvokeCpiWithReadOnly struct
ensure there is an empty line above the opening ```rust and below the closing
``` and similarly add language identifiers (e.g., text) for any non-Rust fenced
blocks referenced (also fix the other affected fenced blocks around lines
118-149). Locate the code fences around ZInstructionDataInvokeCpiWithReadOnly,
ZNewAddressParamsAssignedPacked, ZInAccount,
ZOutputCompressedAccountWithPackedContext, ZPackedReadOnlyAddress, and
ZPackedReadOnlyCompressedAccount and apply the blank-line and language-label
fixes consistently.
- Around line 39-179: The docs currently list is_compress, with_cpi_context, and
with_transaction_hash as public bool fields on
ZInstructionDataInvokeCpiWithReadOnly, and reference read-only verification as
steps 8/9; update the doc to state that the zero-copy meta struct
(ZInstructionDataInvokeCpiWithReadOnlyMeta /
ZInstructionDataInvokeCpiWithReadOnly) stores those flags as u8 and exposes them
via getter methods (e.g. is_compress(), with_cpi_context(),
with_transaction_hash()), and fix the processing step references to match the
implementation (read-only account verification at step 14 and proof verification
at step 16) or remove hard step numbers to avoid drift.

In `@programs/system/docs/invoke/INVOKE.md`:
- Around line 128-162: The markdown sections around the example code blocks lack
blank lines; update INVOKE.md so each fenced code block is preceded and followed
by an empty line for consistency (add blank lines around the blocks that contain
the instruction_data slice and zero_copy_at call, the
InvokeInstruction::from_account_infos call, the
input_compressed_accounts_signer_check call, and the process::<false,
InvokeInstruction, ZInstructionDataInvoke> call). Ensure the empty line is
inserted immediately before the opening ``` and immediately after the closing
``` for those code blocks so markdownlint rules are satisfied and readability is
consistent.
- Around line 63-67: Update the fenced code block that shows the data layout
(the three-line block starting with "[0..8]:    Discriminator") to include a
language specifier for syntax highlighting (for example change the opening ```
to ```text) so the block is rendered consistently; ensure only the opening fence
is modified and the block content remains unchanged.

- Verifies ZK proofs for compressed account state transitions
- Manages CPI context for multi-program transactions
- Coordinates with account-compression program via CPI
- Handles SOL compression/decompression
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Fix markdown formatting for consistency.

Multiple markdown formatting issues flagged by linter:

  • Missing blank lines around headings (lines 9, 15, 20, 144, 153, 174)
  • Code blocks without language specifiers (lines 76, 99)
  • Code blocks not surrounded by blank lines (lines 16, 76, 99, 154)

These issues affect readability and consistency with markdown best practices.

📝 Suggested formatting fixes
  1. Add blank lines before headings (lines 9, 15, 20, 144, 153, 174):
- - Handles SOL compression/decompression
+ - Handles SOL compression/decompression
+ 
  ## Used In
  1. Add language specifiers to code blocks (lines 76, 99):
- ```
+ ```text
  # Run all system program tests
  1. Add blank lines around code blocks (lines 16, 76, 99, 154):
  - **Compressed Token Program** - All compressed token operations (mint, transfer, burn) invoke this program via CPI
+ 
  **Example transaction flow:**

Also applies to: 15-16, 20-20, 76-76, 99-99, 144-144, 153-154, 174-174

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

9-9: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🤖 Prompt for AI Agents
In `@programs/system/CLAUDE.md` at line 9, The markdown in CLAUDE.md needs spacing
and fence fixes: add a blank line before the heading that currently reads
"Handles SOL compression/decompression" (and similarly add blank lines before
headings at the other flagged positions), ensure every fenced code block is
surrounded by a blank line (fix blocks at the positions flagged) and add
language specifiers (use "text" for the plain shell/example blocks) to the
fenced code blocks currently missing them; locate these edits by searching for
the heading text "Handles SOL compression/decompression" and the three fenced
code blocks shown in the diff and adjust spacing and fence headers accordingly.

Comment on lines +11 to +24
## Used In

The Light System Program is invoked by:

- **Compressed Token Program** - All compressed token operations (mint, transfer, burn) invoke this program via CPI
- **Custom Anchor Programs** - Programs using Light SDK invoke this for compressed PDA operations via `InvokeCpi` or `InvokeCpiWithAccountInfo`
- **Direct Clients** - For simple compressed SOL transfers using the `Invoke` instruction
- **Multi-Program Transactions** - Any transaction requiring multiple programs to coordinate via shared CPI context

**Example transaction flow:**
1. Program A calls `InvokeCpiWithAccountInfo` with `first_set_context=true` to write to CPI context
2. Program B calls `InvokeCpiWithAccountInfo` with `set_context=true` to append additional data
3. Program C calls `InvokeCpiWithAccountInfo` with `execute=true` to execute the combined state transition with a single ZK proof

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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the structure of programs/system directory
find programs/system -type f -name "*.md" | head -20
echo "---"
# Check if docs directory exists
ls -la programs/system/ 2>/dev/null | grep -E "^d"

Repository: Lightprotocol/light-protocol

Length of output: 897


🏁 Script executed:

#!/bin/bash
# Search for InvokeCpiWithAccountInfo in the codebase to find its actual implementation
rg "InvokeCpiWithAccountInfo" --type rust -A 5 -B 2 | head -100

Repository: Lightprotocol/light-protocol

Length of output: 7792


🏁 Script executed:

#!/bin/bash
# Also search for the parameter names mentioned in the example
rg "first_set_context|set_context.*execute" --type rust -B 3 -A 3 | head -100

Repository: Lightprotocol/light-protocol

Length of output: 9050


🏁 Script executed:

#!/bin/bash
# Read the INVOKE_CPI_WITH_ACCOUNT_INFO.md documentation
cat programs/system/docs/invoke_cpi/INVOKE_CPI_WITH_ACCOUNT_INFO.md

Repository: Lightprotocol/light-protocol

Length of output: 7118


🏁 Script executed:

#!/bin/bash
# Read the CPI_CONTEXT.md documentation
cat programs/system/docs/CPI_CONTEXT.md

Repository: Lightprotocol/light-protocol

Length of output: 8482


🏁 Script executed:

#!/bin/bash
# Search for "execute" parameter in the context of CPI/InvokeCpiWithAccountInfo
rg "execute" --type rust programs/system/src -B 2 -A 2 | head -80

Repository: Lightprotocol/light-protocol

Length of output: 1682


Correct the multi-program transaction flow example to accurately reflect the CompressedCpiContext state machine.

The example transaction flow describes a valid use case, and the referenced documentation files do exist. However, line 23 contains an inaccuracy: there is no execute=true parameter. According to the CPI_CONTEXT.md documentation, the final invocation should have both first_set_context and set_context set to false (or omitted, since false is the default), not execute=true. Update the example to show:

Program C calls `InvokeCpiWithAccountInfo` with `first_set_context=false` and `set_context=false` to execute the combined state transition with a single ZK proof

This accurately reflects the three-state machine: First Set (clear and write), Set Context (append), and Execute (both flags false).

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

15-15: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


16-16: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


20-20: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🤖 Prompt for AI Agents
In `@programs/system/CLAUDE.md` around lines 11 - 24, Update the example
transaction flow to match the CompressedCpiContext state machine: replace the
incorrect "execute=true" in the Program C step with a call to
InvokeCpiWithAccountInfo where first_set_context=false and set_context=false (or
omit both since false is default) to represent the Execute state; reference the
CompressedCpiContext semantics (see CPI_CONTEXT.md) and keep the other steps
(Program A: first_set_context=true, Program B: set_context=true) unchanged.

Comment on lines +150 to +167
## Processing Pipeline

The 19-step processing flow (`src/processor/process.rs`) is the core of the program:

1. **Allocate CPI Data** - Pre-allocate memory for account-compression CPI
2. **Deserialize Accounts** - Parse and validate Merkle tree accounts
3. **Process Addresses** - Derive new addresses, verify read-only addresses
4. **Process Outputs** - Hash output accounts, validate indices
5. **Process Inputs** - Hash input accounts, create transaction hash
6. **Sum Check** - Verify lamport conservation (inputs + compress = outputs + decompress)
7. **SOL Compress/Decompress** - Transfer SOL to/from Sol Pool PDA
8. **Verify Read-Only** - Verify read-only accounts by index
9. **Verify ZK Proof** - Validate zero-knowledge proof covering all inputs/outputs/addresses
10. **Transfer Fees** - Pay network, address, and rollover fees
11. **Copy CPI Context** - Copy outputs for indexing (when using CPI context)
12. **CPI Account Compression** - Execute state transition via CPI to account-compression program

**See:** [docs/PROCESSING_PIPELINE.md](docs/PROCESSING_PIPELINE.md) for detailed step-by-step breakdown
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, locate the PROCESSING_PIPELINE.md file
find programs/system -name "PROCESSING_PIPELINE.md" -type f

Repository: Lightprotocol/light-protocol

Length of output: 115


🏁 Script executed:

# Also check the CLAUDE.md file to see its full content
cat -n programs/system/CLAUDE.md | head -200

Repository: Lightprotocol/light-protocol

Length of output: 10269


🏁 Script executed:

# Search for step numbers in the docs directory
find programs/system/docs -type f -name "*.md" | head -20

Repository: Lightprotocol/light-protocol

Length of output: 571


🏁 Script executed:

# Check the detailed PROCESSING_PIPELINE.md to count the actual steps
cat -n programs/system/docs/PROCESSING_PIPELINE.md

Repository: Lightprotocol/light-protocol

Length of output: 17920


Update step count or clarify that this is a simplified overview.

Line 152 states "The 19-step processing flow" but lists only 12 steps (lines 154–166). The detailed PROCESSING_PIPELINE.md does contain 19 discrete steps, but this summary groups related steps together. Clarify whether this section should enumerate all 19 steps or note that this is a high-level summary.

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

153-153: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


154-154: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

🤖 Prompt for AI Agents
In `@programs/system/CLAUDE.md` around lines 150 - 167, The header currently
misstates "The 19-step processing flow" while the list in the "Processing
Pipeline" section (referencing src/processor/process.rs) shows 12 grouped steps;
update the text to either list all 19 discrete steps from
docs/PROCESSING_PIPELINE.md or change the sentence to indicate this is a
high-level summary (e.g., "A high-level 12-step summary of the 19-step
processing flow") and add a clarifying pointer to docs/PROCESSING_PIPELINE.md;
make the change near the "Processing Pipeline" section heading to ensure
consistency between the step count and the enumerated items.

Comment on lines +15 to +114
### Discriminator
```rust
CPI_CONTEXT_ACCOUNT_2_DISCRIMINATOR: [u8; 8] = [34, 184, 183, 14, 100, 80, 183, 124]
```

### Ownership
- **Owner:** Light System Program (`SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7`)

### State Layout

**Source:** `programs/system/src/cpi_context/state.rs`

```rust
pub struct ZCpiContextAccount2<'a> {
pub fee_payer: Ref<&'a mut [u8], Pubkey>, // 32 bytes - Transaction fee payer
pub associated_merkle_tree: Ref<&'a mut [u8], Pubkey>, // 32 bytes - Associated state Merkle tree
_associated_queue: Ref<&'a mut [u8], Pubkey>, // 32 bytes - Placeholder for future queue
_place_holder_bytes: Ref<&'a mut [u8], [u8; 32]>, // 32 bytes - Reserved
pub new_addresses: ZeroCopyVecU8<'a, CpiContextNewAddressParamsAssignedPacked>, // Variable
pub readonly_addresses: ZeroCopyVecU8<'a, ZPackedReadOnlyAddress>, // Variable
pub readonly_accounts: ZeroCopyVecU8<'a, ZPackedReadOnlyCompressedAccount>, // Variable
pub in_accounts: ZeroCopyVecU8<'a, CpiContextInAccount>, // Variable
pub out_accounts: ZeroCopyVecU8<'a, CpiContextOutAccount>, // Variable
total_output_data_len: Ref<&'a mut [u8], U16>, // 2 bytes - Total serialized output size
output_data_len: Ref<&'a mut [u8], U16>, // 2 bytes - Number of output data entries
pub output_data: Vec<ZeroCopySliceMut<'a, U16, u8>>, // Variable - Output account data
remaining_data: &'a mut [u8], // Remaining capacity
}
```

**Fixed Header Size:** 8 (discriminator) + 32 + 32 + 32 + 32 = 136 bytes

**Note:** The `Ref<&'a mut [u8], T>` wrapper is used for zero-copy access to fixed-size fields. The lifetime parameter `'a` ensures the account data remains borrowed for the duration of the operation.

### Initialization Parameters

**Source:** `programs/system/src/cpi_context/state.rs`

```rust
pub struct CpiContextAccountInitParams {
pub associated_merkle_tree: Pubkey,
pub associated_queue: Pubkey, // Currently placeholder (always Pubkey::default())
pub new_addresses_len: u8, // Default: 10 - Pre-allocated capacity
pub readonly_addresses_len: u8, // Default: 10 - Pre-allocated capacity
pub readonly_accounts_len: u8, // Default: 10 - Pre-allocated capacity
pub in_accounts_len: u8, // Default: 20 - Pre-allocated capacity
pub out_accounts_len: u8, // Default: 30 - Pre-allocated capacity
}
```

**Capacity Planning:**
The length parameters specify pre-allocated capacity for each vector collection. This capacity determines the maximum number of items that can be stored without resizing. Choose values based on expected transaction complexity:
- Simple transactions: Use defaults (10/10/10/20/30)
- Complex multi-program transactions: Increase capacity as needed
- Each item has a fixed size (see Supporting Types section for exact sizes)

**Default Account Size:** `DEFAULT_CPI_CONTEXT_ACCOUNT_SIZE_V2` = 14020 bytes

**Source:** `program-libs/batched-merkle-tree/src/constants.rs`

**Size Calculation Example with Defaults:**
```
Fixed header: 136 bytes
Vector metadata (5 vectors × 2 bytes): 10 bytes
New addresses (10 × 70 bytes): 700 bytes
Readonly addresses (10 × 36 bytes): 360 bytes
Readonly accounts (10 × 48 bytes): 480 bytes
Input accounts (20 × 144 bytes): 2880 bytes
Output accounts (30 × 128 bytes): 3840 bytes
Output data metadata: 4 bytes
Remaining buffer for output data: ~5610 bytes
Total: 14020 bytes
```

**Note:** The remaining buffer is used for variable-length output data storage (compressed account data payloads).

### Serialization

Uses zero-copy serialization via `light_zero_copy` crate for performance:
- **`Ref<&'a mut [u8], T>`** - Zero-copy wrapper for fixed-size fields (fee_payer, associated_merkle_tree, etc.)
- **`ZeroCopyVecU8<'a, T>`** - Variable-length vectors with u8 length prefix (2 bytes: 1 byte length + 1 byte capacity)
- **`ZeroCopySliceMut<'a, U16, u8>`** - Output data slices with u16 length prefix

**Memory Layout:**
```
[8 bytes discriminator]
[32 bytes fee_payer]
[32 bytes associated_merkle_tree]
[32 bytes _associated_queue]
[32 bytes _place_holder_bytes]
[2 bytes new_addresses metadata][variable new_addresses data]
[2 bytes readonly_addresses metadata][variable readonly_addresses data]
[2 bytes readonly_accounts metadata][variable readonly_accounts data]
[2 bytes in_accounts metadata][variable in_accounts data]
[2 bytes out_accounts metadata][variable out_accounts data]
[2 bytes total_output_data_len]
[2 bytes output_data_len]
[variable output_data with u16 length prefixes]
[remaining unallocated space]
```
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, find the struct definitions
find . -name "state.rs" -path "*/cpi_context/*" | head -5

Repository: Lightprotocol/light-protocol

Length of output: 114


🏁 Script executed:

# Look for the struct definition and discriminator constant
rg -n "ZCpiContextAccount2|CPI_CONTEXT_ACCOUNT_2_DISCRIMINATOR" --type=rs -g "programs/system/**"

Repository: Lightprotocol/light-protocol

Length of output: 98


🏁 Script executed:

# Find the default size constant
rg -n "DEFAULT_CPI_CONTEXT_ACCOUNT_SIZE_V2" --type=rs

Repository: Lightprotocol/light-protocol

Length of output: 98


🏁 Script executed:

# Get the supporting types to verify their sizes
rg -n "CpiContextNewAddressParamsAssignedPacked|ZPackedReadOnlyAddress|ZPackedReadOnlyCompressedAccount|CpiContextInAccount|CpiContextOutAccount" --type=rs -g "programs/system/**"

Repository: Lightprotocol/light-protocol

Length of output: 98


🏁 Script executed:

# Read the state.rs file to find struct and constants
wc -l ./programs/system/src/cpi_context/state.rs

Repository: Lightprotocol/light-protocol

Length of output: 118


🏁 Script executed:

# Search without type flag
rg -n "ZCpiContextAccount2|CPI_CONTEXT_ACCOUNT_2_DISCRIMINATOR" programs/system/

Repository: Lightprotocol/light-protocol

Length of output: 3820


🏁 Script executed:

# Find DEFAULT_CPI_CONTEXT_ACCOUNT_SIZE_V2
rg -n "DEFAULT_CPI_CONTEXT_ACCOUNT_SIZE_V2" .

Repository: Lightprotocol/light-protocol

Length of output: 2597


🏁 Script executed:

# Find the supporting type definitions
rg -n "struct CpiContextNewAddressParamsAssignedPacked|struct ZPackedReadOnlyAddress|struct ZPackedReadOnlyCompressedAccount|struct CpiContextInAccount|struct CpiContextOutAccount" programs/system/

Repository: Lightprotocol/light-protocol

Length of output: 929


🏁 Script executed:

# Read the struct definition from state.rs
sed -n '44,60p' programs/system/src/cpi_context/state.rs

Repository: Lightprotocol/light-protocol

Length of output: 1098


🏁 Script executed:

# Read more of state.rs to see full struct
sed -n '44,150p' programs/system/src/cpi_context/state.rs

Repository: Lightprotocol/light-protocol

Length of output: 5167


🏁 Script executed:

# Read the constants file
cat programs/system/src/constants.rs | head -30

Repository: Lightprotocol/light-protocol

Length of output: 1558


🏁 Script executed:

# Read the default size constant
sed -n '30,40p' program-libs/batched-merkle-tree/src/constants.rs

Repository: Lightprotocol/light-protocol

Length of output: 644


🏁 Script executed:

# Read the supporting type definitions
cat programs/system/src/cpi_context/account.rs

Repository: Lightprotocol/light-protocol

Length of output: 4310


🏁 Script executed:

# Read the address types
cat programs/system/src/cpi_context/address.rs

Repository: Lightprotocol/light-protocol

Length of output: 1402


🏁 Script executed:

# Find ZPackedMerkleContext definition
rg -n "struct ZPackedMerkleContext" programs/system/

Repository: Lightprotocol/light-protocol

Length of output: 142


🏁 Script executed:

# Find ZPackedReadOnlyAddress definition in source
rg -n "struct ZPackedReadOnlyAddress" programs/system/src/

Repository: Lightprotocol/light-protocol

Length of output: 54


🏁 Script executed:

# Find ZPackedReadOnlyCompressedAccount definition in source
rg -n "struct ZPackedReadOnlyCompressedAccount" programs/system/src/

Repository: Lightprotocol/light-protocol

Length of output: 54


🏁 Script executed:

# Search more broadly for these types - they might be imported
rg -n "ZPackedMerkleContext|ZPackedReadOnlyAddress|ZPackedReadOnlyCompressedAccount" programs/system/src/cpi_context/ | head -20

Repository: Lightprotocol/light-protocol

Length of output: 2058


🏁 Script executed:

# Check the imports at the top of state.rs
head -30 programs/system/src/cpi_context/state.rs

Repository: Lightprotocol/light-protocol

Length of output: 1130


🏁 Script executed:

# Try to find the types in the workspace - they might be in another crate
fd -e rs | xargs grep -l "struct ZPackedMerkleContext" 2>/dev/null | head -5

Repository: Lightprotocol/light-protocol

Length of output: 137


🏁 Script executed:

# Let me check what's available in the repo
ls -la program-libs/

Repository: Lightprotocol/light-protocol

Length of output: 1515


🏁 Script executed:

# Read the zero_copy.rs file to find ZPackedMerkleContext, ZPackedReadOnlyAddress, ZPackedReadOnlyCompressedAccount
cat program-libs/compressed-account/src/instruction_data/zero_copy.rs

Repository: Lightprotocol/light-protocol

Length of output: 50384


🏁 Script executed:

# Let me double-check the struct size calculations using Rust's size_of
python3 << 'EOF'
# Struct sizes based on field types from source
types = {
    'ZPackedReadOnlyAddress': {
        'fields': {'address': 32, 'address_merkle_tree_root_index': 2, 'address_merkle_tree_account_index': 1},
        'doc_claims': 36
    },
    'ZPackedMerkleContext': {
        'fields': {'merkle_tree_pubkey_index': 1, 'queue_pubkey_index': 1, 'leaf_index': 4, 'prove_by_index': 1},
        'doc_claims': 'N/A (inferred from ReadOnlyCompressed)'
    },
    'ZPackedReadOnlyCompressedAccount': {
        'fields': {'account_hash': 32, 'merkle_context': 7, 'root_index': 2},
        'doc_claims': 48
    },
    'CpiContextOutAccount': {
        'fields': {'owner': 32, 'has_data': 1, 'discriminator': 8, 'data_hash': 32, 'output_merkle_tree_index': 1, 'lamports': 8, 'with_address': 1, 'address': 32},
        'doc_claims': 128
    },
    'CpiContextInAccount': {
        'fields': {'owner': 32, 'has_data': 1, 'discriminator': 8, 'data_hash': 32, 'merkle_context': 7, 'root_index': 2, 'lamports': 8, 'with_address': 1, 'address': 32},
        'doc_claims': 144
    },
    'CpiContextNewAddressParamsAssignedPacked': {
        'fields': {'owner': 32, 'seed': 32, 'address_queue_account_index': 1, 'address_merkle_tree_account_index': 1, 'address_merkle_tree_root_index': 2, 'assigned_to_account': 1, 'assigned_account_index': 1},
        'doc_claims': 70
    }
}

total_size_from_correct_sizes = 8 + 32 + 32 + 32 + 32  # fixed header
total_size_from_correct_sizes += 5 * 2  # vector metadata
total_size_from_correct_sizes += 10 * 70  # new_addresses
total_size_from_correct_sizes += 10 * 35  # readonly_addresses (correct: 35 not 36)
total_size_from_correct_sizes += 10 * 41  # readonly_accounts (correct: 41 not 48)
total_size_from_correct_sizes += 20 * 123  # in_accounts (correct: 123 not 144)
total_size_from_correct_sizes += 30 * 115  # out_accounts (correct: 115 not 128)
total_size_from_correct_sizes += 4  # output_data metadata

print("Struct Size Analysis:")
print("=" * 70)
for name, info in types.items():
    actual = sum(info['fields'].values())
    claimed = info['doc_claims']
    status = "✓" if actual == claimed else "✗ MISMATCH"
    print(f"{name:45} Actual: {actual:3} Doc: {claimed:>3} {status}")

print("\n" + "=" * 70)
print(f"Total size with CORRECT struct sizes: {total_size_from_correct_sizes} bytes")
print(f"Documented default size: 14020 bytes")
print(f"Difference: {14020 - total_size_from_correct_sizes} bytes")
EOF

Repository: Lightprotocol/light-protocol

Length of output: 158


Fix struct size calculations and totals in V2 account documentation.

The documented type sizes contain errors that compound in the total account size calculation. Verified against source:

Type Documented Actual Source
CpiContextNewAddressParamsAssignedPacked 70 70
ZPackedReadOnlyAddress 36 35 program-libs/compressed-account/src/instruction_data/zero_copy.rs:29-33
ZPackedReadOnlyCompressedAccount 48 41 zero_copy.rs:584-590 (includes ZPackedMerkleContext: 7 bytes)
CpiContextInAccount 144 123 programs/system/src/cpi_context/account.rs:37-52
CpiContextOutAccount 128 115 account.rs:19-35

The size calculation breakdown using these values yields ~7,520 bytes, not the documented 14,020. Either document the actual field breakdown with correct type sizes, or clarify that the 14020-byte constant includes substantial reserved buffer space beyond the documented structures. Reference the actual struct definitions in programs/system/src/cpi_context/{account.rs,address.rs} and program-libs/compressed-account/src/instruction_data/zero_copy.rs for authoritative sizing.

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

15-15: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


16-16: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


20-20: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


76-76: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


76-76: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


99-99: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


99-99: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In `@programs/system/docs/ACCOUNTS.md` around lines 15 - 114, The documentation's
V2 account size math is incorrect: recompute and update the type sizes for
ZPackedReadOnlyAddress, ZPackedReadOnlyCompressedAccount, CpiContextInAccount,
and CpiContextOutAccount (use the actual sizes from zero_copy.rs and account.rs)
and then either (A) replace the per-type breakdown and total with the corrected
totals that sum to the actual layout, or (B) explicitly state that
DEFAULT_CPI_CONTEXT_ACCOUNT_SIZE_V2 (the 14020 constant) includes an additional
reserved buffer beyond the serialized fields; update the Size Calculation
Example and the "Total" line accordingly and reference the actual symbols/types
CpiContextNewAddressParamsAssignedPacked, ZPackedReadOnlyAddress,
ZPackedReadOnlyCompressedAccount, CpiContextInAccount, CpiContextOutAccount and
the constant DEFAULT_CPI_CONTEXT_ACCOUNT_SIZE_V2 so readers can verify against
program-libs/compressed-account/src/instruction_data/zero_copy.rs and
programs/system/src/cpi_context/{account.rs,address.rs}.

Comment on lines +5 to +101
| Field | Value |
|-------|-------|
| **Discriminator** | `[233, 112, 71, 66, 121, 33, 178, 188]` |
| **Enum** | `InstructionDiscriminator::InitializeCpiContextAccount` |
| **Path** | `programs/system/src/accounts/init_context_account.rs` |

## Description

Initializes a new CPI context account (version 2) for use in multi-program compressed account transactions. The account is associated with a specific state Merkle tree and allocated with default capacity parameters.

### Use Cases
- Setting up a CPI context account for a new Merkle tree
- Preparing for multi-program transactions that share a single ZK proof
- Creating dedicated context accounts for specific applications

### State Changes
- Account data is initialized with version 2 discriminator
- Associated Merkle tree is set
- Default capacity parameters are applied

---

## Instruction Data

This instruction takes no additional data beyond the discriminator.

```rust
// Instruction data layout:
// [0..8]: Discriminator
```

**Total size:** 8 bytes

---

## Accounts

| Index | Name | Signer | Writable | Description |
|-------|------|--------|----------|-------------|
| 0 | `fee_payer` | Yes | Yes | Pays for account creation |
| 1 | `cpi_context_account` | No | Yes | Account to initialize (must be pre-allocated) |
| 2 | `associated_merkle_tree` | No | No | State Merkle tree to associate with |

### Account Validations

**fee_payer (index 0):**
- Must be a signer
- Must be writable

**cpi_context_account (index 1):**
- Must be pre-allocated with exactly 14020 bytes (`DEFAULT_CPI_CONTEXT_ACCOUNT_SIZE_V2`)
- Must be owned by Light System Program
- Discriminator must be zero (uninitialized)

**associated_merkle_tree (index 2):**
- Must be owned by Account Compression Program
- Discriminator must match state or batched Merkle tree

---

## Instruction Logic

### Step 1: Validate Accounts
```rust
let ctx = InitializeCpiContextAccount::from_account_infos(accounts)?;
```

### Step 2: Initialize Account
```rust
let params = CpiContextAccountInitParams::new(*ctx.associated_merkle_tree.key());
cpi_context_account_new::<false>(ctx.cpi_context_account, params)?;
```

Initialization:
1. Write version 2 discriminator: `[34, 184, 183, 14, 100, 80, 183, 124]`
2. Set fee_payer to zero (will be set during operation)
3. Set associated_merkle_tree
4. Set up vector capacities with defaults:
- `new_addresses_len`: 10
- `readonly_addresses_len`: 10
- `readonly_accounts_len`: 10
- `in_accounts_len`: 20
- `out_accounts_len`: 30

---

## Errors

| Code | Error | Cause |
|------|-------|-------|
| - | `NotEnoughAccountKeys` | Less than 3 accounts provided |
| - | `MissingRequiredSignature` | fee_payer is not a signer |
| - | `IllegalOwner` | associated_merkle_tree not owned by account-compression program OR cpi_context_account not owned by Light System Program |
| 6042 | `StateMerkleTreeAccountDiscriminatorMismatch` | associated_merkle_tree discriminator doesn't match state or batched Merkle tree |
| 6055 | `InvalidCpiContextOwner` | cpi_context_account not owned by Light System Program |
| 6056 | `InvalidCpiContextDiscriminator` | cpi_context_account discriminator is not zero (already initialized) |

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 init_context_account.rs file and related code
fd -type f -name "init_context_account.rs" programs/system/src/

# Check for constants and types
fd -type f -name "*.rs" programs/system/src/ | head -20

Repository: Lightprotocol/light-protocol

Length of output: 200


🏁 Script executed:

#!/bin/bash
# Search for the init_context_account implementation details
rg -n "InitializeCpiContextAccount|init_context_account" programs/system/src/ -t rs -A 5 | head -100

Repository: Lightprotocol/light-protocol

Length of output: 200


🏁 Script executed:

#!/bin/bash
# Find size and discriminator constants
rg -n "DEFAULT_CPI_CONTEXT_ACCOUNT_SIZE_V2|CPI_CONTEXT.*DISCRIMINATOR|14020" programs/system/src/ -t rs

Repository: Lightprotocol/light-protocol

Length of output: 200


🏁 Script executed:

#!/bin/bash
# Find error codes and definitions
rg -n "6042|6055|6056|InvalidCpiContextOwner|InvalidCpiContextDiscriminator|StateMerkleTreeAccountDiscriminatorMismatch" programs/system/src/ -t rs -B 2 -A 2

Repository: Lightprotocol/light-protocol

Length of output: 98


🏁 Script executed:

#!/bin/bash
# Look for the cpi_context_account_new function and initialization logic
rg -n "cpi_context_account_new|CpiContextAccountInitParams" programs/system/src/ -t rs -B 3 -A 10

Repository: Lightprotocol/light-protocol

Length of output: 98


🏁 Script executed:

#!/bin/bash
# Find init_context_account.rs and examine directory structure
find programs/system/src -name "*init*" -type f

Repository: Lightprotocol/light-protocol

Length of output: 124


🏁 Script executed:

#!/bin/bash
# Search without type flag to find relevant code
rg "InitializeCpiContextAccount|cpi_context_account_new" programs/system/src -A 5 | head -150

Repository: Lightprotocol/light-protocol

Length of output: 4591


🏁 Script executed:

#!/bin/bash
# Find size constants
rg "DEFAULT_CPI_CONTEXT_ACCOUNT_SIZE_V2|14020|CPI_CONTEXT" programs/system/src -B 2 -A 2 | head -100

Repository: Lightprotocol/light-protocol

Length of output: 5242


🏁 Script executed:

#!/bin/bash
# Find error code definitions  
rg "6042|6055|6056" programs/system/src -B 3 -A 1

Repository: Lightprotocol/light-protocol

Length of output: 1159


🏁 Script executed:

#!/bin/bash
# Look for discriminator definitions
rg "233.*112.*71.*66|34.*184.*183.*14" programs/system/src

Repository: Lightprotocol/light-protocol

Length of output: 330


🏁 Script executed:

#!/bin/bash
# Find the DEFAULT_CPI_CONTEXT_ACCOUNT_SIZE_V2 value
rg "DEFAULT_CPI_CONTEXT_ACCOUNT_SIZE_V2" programs/ -B 2 -A 2

Repository: Lightprotocol/light-protocol

Length of output: 4052


🏁 Script executed:

#!/bin/bash
# Check the full init_context_account.rs file to understand account validation
cat -n programs/system/src/accounts/init_context_account.rs | head -150

Repository: Lightprotocol/light-protocol

Length of output: 4138


🏁 Script executed:

#!/bin/bash
# Look for CpiContextAccountInitParams to verify vector defaults
rg "CpiContextAccountInitParams|new_addresses_len|readonly_addresses_len" programs/ -B 3 -A 5 | head -150

Repository: Lightprotocol/light-protocol

Length of output: 11009


🏁 Script executed:

#!/bin/bash
# Check account validation logic in cpi_context_account_new function
rg -A 40 "pub fn cpi_context_account_new" programs/system/src/cpi_context/state.rs

Repository: Lightprotocol/light-protocol

Length of output: 1953


🏁 Script executed:

#!/bin/bash
# Look for account ownership and signer checks
rg "check_owner|is_signer|writable|IllegalOwner|NotEnoughAccountKeys" programs/system/src/accounts/init_context_account.rs -B 2 -A 2

Repository: Lightprotocol/light-protocol

Length of output: 1067


🏁 Script executed:

#!/bin/bash
# Find the CpiContextAccountInitParams implementation and its defaults
rg -B 5 -A 15 "impl CpiContextAccountInitParams" programs/system/src/

Repository: Lightprotocol/light-protocol

Length of output: 1500


🏁 Script executed:

#!/bin/bash
# Look for the new() method specifically
rg -A 10 "fn new\(" programs/system/src/cpi_context/state.rs

Repository: Lightprotocol/light-protocol

Length of output: 483


🏁 Script executed:

#!/bin/bash
# Check if there are any account size validations
rg "account_info.data_len|data.len()" programs/system/src/accounts/init_context_account.rs -B 2 -A 2

Repository: Lightprotocol/light-protocol

Length of output: 54


Update error handling section with actual error codes and variant names returned by account-checks functions.

The documented errors table conflates generic error concepts with actual implementation. Based on the code analysis:

  1. Associated merkle tree ownership validation returns AccountOwnedByWrongProgram (20001) from check_owner, not the generic IllegalOwner listed
  2. Fee payer signer validation returns InvalidSigner (20009) from check_signer, not MissingRequiredSignature
  3. CPI context account ownership validation correctly returns InvalidCpiContextOwner (6055)
Updated Errors section
Code Error Cause
- NotEnoughAccountKeys Less than 3 accounts provided
20009 InvalidSigner fee_payer is not a signer
20001 AccountOwnedByWrongProgram associated_merkle_tree not owned by Account Compression Program
6042 StateMerkleTreeAccountDiscriminatorMismatch associated_merkle_tree discriminator doesn't match state or batched Merkle tree
6055 InvalidCpiContextOwner cpi_context_account not owned by Light System Program
6056 InvalidCpiContextDiscriminator cpi_context_account discriminator is not zero (already initialized)

All other validation rules, discriminators, sizes, and initialization logic match the source code correctly.

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

18-18: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


23-23: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


62-62: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


63-63: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


67-67: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


68-68: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


77-77: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


78-78: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


84-84: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


85-85: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

🤖 Prompt for AI Agents
In `@programs/system/docs/init/INIT_CPI_CONTEXT_ACCOUNT.md` around lines 5 - 101,
The Errors table mixes generic names with actual error variants returned by our
account checks; update the documented errors to use the real error codes and
variant names produced by check_owner and check_signer (e.g.,
AccountOwnedByWrongProgram 20001 for associated_merkle_tree ownership and
InvalidSigner 20009 for fee_payer signer), retain InvalidCpiContextOwner 6055
for cpi_context_account ownership, keep NotEnoughAccountKeys and the state-tree
discriminator error (StateMerkleTreeAccountDiscriminatorMismatch 6042), and
remove or replace any “IllegalOwner”/“MissingRequiredSignature” entries so the
table matches the actual return values from check_owner and check_signer in
InitializeCpiContextAccount/cpi_context_account_new.

Comment on lines +11 to +34
## Description

Extended CPI invocation instruction that supports read-only compressed accounts and addresses. This enables programs to verify the existence of compressed state without modifying it, useful for authorization checks and multi-account validations.

### Use Cases
- Verify compressed account exists before performing operations
- Check compressed PDA state for authorization
- Read-only access to compressed token balances
- Proof-of-ownership checks without state modification

### Key Differences from InvokeCpi
| Feature | InvokeCpi | InvokeCpiWithReadOnly |
|---------|-----------|------------------------|
| **Read-only accounts** | Not supported | Supported |
| **Read-only addresses** | Not supported | Supported (execute mode only) |
| **Account mode** | Fixed (Anchor) | Configurable (Anchor/V2) |
| **Invoking program ID** | Implicit (from accounts) | Explicit (in instruction data) |

### State Changes
- Input compressed accounts are nullified (writable inputs only)
- Output compressed accounts are created
- Read-only accounts are verified but not modified
- Read-only addresses are verified to exist

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

Add required CPI invocation details and full example.

This CPI doc doesn’t include the required target program, PDA signer seeds/bump, account mapping passthrough, or a full CPI example. Please add a dedicated CPI section covering those items and a runnable example snippet. Based on learnings, CPI docs must include these elements.

If you want, I can draft the missing CPI section and example.
Based on learnings, …

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

15-15: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


21-21: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


22-22: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


29-29: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🤖 Prompt for AI Agents
In `@programs/system/docs/invoke_cpi/INVOKE_CPI_WITH_READ_ONLY.md` around lines 11
- 34, Update the InvokeCpiWithReadOnly documentation to add a dedicated "CPI
Invocation" section that specifies the target program ID (explicit in
instruction data), required PDA signer seeds and bump values, how account
mapping/passthrough should be handled for both writable and read-only compressed
accounts, and include a full runnable example showing: the instruction
construction (InvokeCpiWithReadOnly), how to populate the accounts array
(writable inputs nullified, outputs created, read-only accounts verified), how
to supply PDA seeds + bump to sign the CPI, and a minimal on-chain/off-chain
invocation snippet demonstrating execute mode for read-only addresses; ensure
the section references the existing terms InvokeCpiWithReadOnly, InvokeCpi,
"read-only addresses", and "account mode" so reviewers can locate the changes.

Comment on lines +39 to +179
**Source:** `program-libs/compressed-account/src/instruction_data/with_readonly.rs`

```rust
pub struct ZInstructionDataInvokeCpiWithReadOnly<'a> {
// Metadata (fixed layout)
pub mode: u8, // AccountMode: 0 = Anchor, 1 = V2
pub bump: u8,
pub invoking_program_id: Pubkey,
pub compress_or_decompress_lamports: U64,
pub is_compress: bool,
pub with_cpi_context: bool,
pub with_transaction_hash: bool,
pub cpi_context: ZCompressedCpiContext,

// Variable-length fields (zero-copy slices)
pub proof: Option<Ref<'a, [u8], CompressedProof>>,
pub new_address_params: ZeroCopySliceBorsh<'a, ZNewAddressParamsAssignedPacked>,
pub input_compressed_accounts: Vec<ZInAccount<'a>>,
pub output_compressed_accounts: Vec<ZOutputCompressedAccountWithPackedContext<'a>>,
pub read_only_addresses: ZeroCopySliceBorsh<'a, ZPackedReadOnlyAddress>,
pub read_only_accounts: ZeroCopySliceBorsh<'a, ZPackedReadOnlyCompressedAccount>,
}
```

### Key Fields

**Mode Selection:**
- `mode`: Account mode (0 = Anchor, 1 = V2)
- `invoking_program_id`: Program ID making the CPI (embedded in instruction data)
- `bump`: PDA bump seed for signer verification

**Read-Only Features:**
- `read_only_accounts`: Compressed accounts to verify existence without modification
- `read_only_addresses`: Addresses to verify existence in address tree

---

## Accounts

Account layout depends on the `mode` field:

### Anchor Mode (mode = 0)

Same 11 accounts as [INVOKE_CPI.md](INVOKE_CPI.md):

| Index | Name | Signer | Writable | Description |
|-------|------|--------|----------|-------------|
| 0-10 | (same as InvokeCpi) | - | - | See InvokeCpi documentation |
| 11+ | `remaining_accounts` | - | - | Merkle trees, queues |

### V2 Mode (mode = 1)

Dynamic account list based on `account_option_config`:

| Index | Name | Signer | Writable | Description |
|-------|------|--------|----------|-------------|
| 0 | `fee_payer` | Yes | Yes | Pays transaction fees |
| 1 | `authority` | Yes | No | Authority (signer in V2 mode) |
| 2+ | Dynamic accounts | - | - | Determined by account_option_config |

---

## Instruction Logic

### Processing Flow
1. **CPI signer checks:** Verify invoking program authority
2. **CPI context processing:** Handle first_set_context, set_context, or execute modes
3. **Main processing pipeline:** Execute 19-step pipeline
- Step 8: **Read-only account verification**
- By index: Direct leaf lookup (if prove_by_index = true)
- By ZKP: Inclusion proof verification (if prove_by_index = false)
- Duplicate check: Ensure read-only accounts don't overlap with writable inputs
- Step 9: **ZK proof verification** (includes read-only proofs)
4. **Read-only address verification** (execute mode only):
- Non-inclusion check: Verify address not in bloom filter queue
- Inclusion proof: Verify address exists in address Merkle tree

---

## Read-Only Account Processing

### Read-Only Compressed Accounts

```rust
pub struct ZPackedReadOnlyCompressedAccount {
pub account_hash: [u8; 32],
pub merkle_context: ZPackedMerkleContext,
pub root_index: U16,
}
```

- `account_hash`: Pre-computed hash of the compressed account
- `merkle_context`: Merkle tree location (tree index, queue index, leaf index)
- `root_index`: Index of the Merkle root to verify against

**Verification:** Account hash inclusion in Merkle tree (by index or ZKP). Account is NOT nullified.

### Read-Only Addresses

```rust
pub struct ZPackedReadOnlyAddress {
pub address: [u8; 32],
pub address_merkle_tree_account_index: u8,
pub address_merkle_tree_root_index: U16,
}
```

**Verification:**
1. Bloom filter check: Verify address is NOT in queue (not pending insertion)
2. Inclusion proof: Verify address exists in address Merkle tree

---

## Limitations

### Read-Only Addresses with CPI Context

Read-only addresses cannot be used when writing to CPI context account (first_set_context or set_context modes).

```rust
if let Some(readonly_addresses) = instruction_data.read_only_addresses() {
if !readonly_addresses.is_empty() {
return Err(SystemProgramError::Unimplemented)?;
}
}
```

**Workaround:** Perform read-only address verification in the final executing program.

---

## Errors

| Code | Error | Cause |
|------|-------|-------|
| 6034 | `ReadOnlyAddressAlreadyExists` | Read-only address found in bloom filter queue |
| 6035 | `ReadOnlyAccountDoesNotExist` | Read-only account hash not found in Merkle tree |
| 6053 | `DuplicateAccountInInputsAndReadOnly` | Same account in both writable inputs and read-only |
| 6063 | `Unimplemented` | Read-only addresses with CPI context write mode |

See [INSTRUCTIONS.md](../INSTRUCTIONS.md) for complete error list.
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify instruction data structs and read-only types
echo "=== Searching for struct definitions ==="
rg -n --type=rs 'ZInstructionDataInvokeCpiWithReadOnly|ZPackedReadOnlyCompressedAccount|ZPackedReadOnlyAddress' --max-count=20

echo ""
echo "=== Searching for error codes ==="
rg -n --type=rs 'ReadOnlyAddressAlreadyExists|ReadOnlyAccountDoesNotExist|DuplicateAccountInInputsAndReadOnly' --max-count=20

Repository: Lightprotocol/light-protocol

Length of output: 200


🏁 Script executed:

#!/bin/bash
# Find the instruction processor and processing logic
echo "=== Searching for InvokeCpiWithReadOnly instruction handling ==="
fd -e rs . --search-path=programs/system/src | head -20

echo ""
echo "=== Search for invoke_cpi processing ==="
rg -n 'invoke_cpi_with_read_only|InvokeCpiWithReadOnly' --type=rs -C 2 | head -50

Repository: Lightprotocol/light-protocol

Length of output: 200


🏁 Script executed:

#!/bin/bash
# Verify struct field definitions match documentation
echo "=== Finding compressed-account instruction data definitions ==="
fd -e rs . --search-path=program-libs/compressed-account/src/instruction_data

echo ""
echo "=== Search for the with_readonly.rs file ==="
find . -name 'with_readonly.rs' -o -name '*readonly*' | grep -E '\.(rs|md)$'

Repository: Lightprotocol/light-protocol

Length of output: 1000


🏁 Script executed:

#!/bin/bash
# Read the with_readonly.rs file to verify struct definitions
wc -l program-libs/compressed-account/src/instruction_data/with_readonly.rs

Repository: Lightprotocol/light-protocol

Length of output: 145


🏁 Script executed:

#!/bin/bash
# Read the with_readonly.rs content
head -200 program-libs/compressed-account/src/instruction_data/with_readonly.rs

Repository: Lightprotocol/light-protocol

Length of output: 6048


🏁 Script executed:

#!/bin/bash
# Search for error codes in the system program
echo "=== Searching for error code definitions ==="
rg -n 'ReadOnlyAddressAlreadyExists|ReadOnlyAccountDoesNotExist|DuplicateAccountInInputsAndReadOnly|Unimplemented' --type=rs -B 2 -A 2

Repository: Lightprotocol/light-protocol

Length of output: 160


🏁 Script executed:

#!/bin/bash
# Search for processing pipeline and read-only verification logic
echo "=== Searching for read_only processing and verification ==="
rg -n 'read_only|ReadOnly' program-libs/compressed-account/src/instruction_data/with_readonly.rs -A 3 | head -100

Repository: Lightprotocol/light-protocol

Length of output: 3862


🏁 Script executed:

#!/bin/bash
# Read more of the with_readonly.rs to see struct definition details
sed -n '385,500p' program-libs/compressed-account/src/instruction_data/with_readonly.rs

Repository: Lightprotocol/light-protocol

Length of output: 4141


🏁 Script executed:

#!/bin/bash
# Search for error code definitions in system program
echo "=== Searching for error definitions ==="
find programs/system/src -name '*.rs' -exec grep -l 'ReadOnlyAddressAlreadyExists\|ReadOnlyAccountDoesNotExist\|DuplicateAccountInInputsAndReadOnly' {} \;

Repository: Lightprotocol/light-protocol

Length of output: 284


🏁 Script executed:

#!/bin/bash
# Search for error enum definitions more broadly
echo "=== Searching for SystemProgramError or similar ==="
rg -n 'enum.*Error|ReadOnlyAddress|ReadOnlyAccount' programs/system/src --type=rs -C 2 | head -80

Repository: Lightprotocol/light-protocol

Length of output: 167


🏁 Script executed:

#!/bin/bash
# Look for the instruction processor to verify processing steps
find programs/system/src -name '*.rs' | xargs grep -l 'invoke_cpi.*read_only\|process.*invoke.*cpi' | head -5

Repository: Lightprotocol/light-protocol

Length of output: 142


🏁 Script executed:

#!/bin/bash
# Read the errors file to verify error codes
echo "=== Reading errors.rs ==="
cat programs/system/src/errors.rs

Repository: Lightprotocol/light-protocol

Length of output: 11488


🏁 Script executed:

#!/bin/bash
# Read the read_only_account processor
echo "=== Reading read_only_account.rs ==="
head -200 programs/system/src/processor/read_only_account.rs

Repository: Lightprotocol/light-protocol

Length of output: 3863


🏁 Script executed:

#!/bin/bash
# Read the read_only_address processor
echo "=== Reading read_only_address.rs ==="
head -200 programs/system/src/processor/read_only_address.rs

Repository: Lightprotocol/light-protocol

Length of output: 1381


🏁 Script executed:

#!/bin/bash
# Search for the instruction processor to check processing steps
echo "=== Searching in invoke_cpi processor ==="
head -300 programs/system/src/invoke_cpi/processor.rs | tail -200

Repository: Lightprotocol/light-protocol

Length of output: 2337


🏁 Script executed:

#!/bin/bash
# Check for the 19-step processing pipeline and steps 8 and 9
echo "=== Searching for processing pipeline steps ==="
rg -n 'step.*8|step.*9|read_only.*verification|process.*pipeline' programs/system/src --type=rs -C 2 | head -100

Repository: Lightprotocol/light-protocol

Length of output: 163


🏁 Script executed:

#!/bin/bash
# Search for the limitation about read-only addresses with CPI context
echo "=== Searching for read-only addresses limitation ==="
rg -n 'readonly_addresses.*first_set_context|readonly_addresses.*set_context|Unimplemented.*readonly' programs/system/src --type=rs -C 3

Repository: Lightprotocol/light-protocol

Length of output: 168


🏁 Script executed:

#!/bin/bash
# Check the process.rs file to understand the processing steps
echo "=== Reading process.rs to find processing steps ==="
head -150 programs/system/src/processor/process.rs

Repository: Lightprotocol/light-protocol

Length of output: 6285


🏁 Script executed:

#!/bin/bash
# Continue reading process.rs to find all processing steps
sed -n '80,250p' programs/system/src/processor/process.rs

Repository: Lightprotocol/light-protocol

Length of output: 7189


🏁 Script executed:

#!/bin/bash
# Search more comprehensively for read_only and CPI context interaction
find programs/system/src -type f -name '*.rs' -exec grep -l 'read_only.*cpi_context\|cpi_context.*read_only' {} \;

Repository: Lightprotocol/light-protocol

Length of output: 54


🏁 Script executed:

#!/bin/bash
# Find where the limitation check is (Unimplemented error for read-only addresses with CPI context)
grep -n 'read_only_addresses\|readonly_addresses' programs/system/src/processor/process.rs -C 3 | head -80

Repository: Lightprotocol/light-protocol

Length of output: 2525


🏁 Script executed:

#!/bin/bash
# Look for the limitation check about read-only addresses with CPI context
grep -rn 'readonly_addresses.*Unimplemented\|read_only_addresses.*first_set_context\|read_only_addresses.*set_context' programs/system/src/ 2>/dev/null || echo "Not found in programs/system"

# Also search in compressed-account lib
grep -rn 'readonly_addresses.*Unimplemented\|read_only_addresses.*first_set_context\|read_only_addresses.*set_context' program-libs/compressed-account/ 2>/dev/null || echo "Not found in program-libs/compressed-account"

Repository: Lightprotocol/light-protocol

Length of output: 145


🏁 Script executed:

#!/bin/bash
# Look at the full process.rs to see steps 8 and 13
wc -l programs/system/src/processor/process.rs
sed -n '200,320p' programs/system/src/processor/process.rs

Repository: Lightprotocol/light-protocol

Length of output: 5712


🏁 Script executed:

#!/bin/bash
# Search specifically for the Unimplemented error being returned for read-only addresses
grep -n 'Unimplemented' programs/system/src/processor/process.rs programs/system/src/invoke_cpi/processor.rs programs/system/src/processor/read_only*.rs 2>/dev/null

Repository: Lightprotocol/light-protocol

Length of output: 54


🏁 Script executed:

#!/bin/bash
# Search more thoroughly for any validation of read-only addresses with CPI context
echo "=== Searching for CPI context validation with read-only ==="
grep -rn 'read_only\|cpi_context' program-libs/compressed-account/src/instruction_data/with_readonly.rs | head -50

Repository: Lightprotocol/light-protocol

Length of output: 2931


🏁 Script executed:

#!/bin/bash
# Check if there's validation in the invoke_cpi module initialization or validation
grep -n 'first_set_context\|set_context' programs/system/src/invoke_cpi/processor.rs -C 2

Repository: Lightprotocol/light-protocol

Length of output: 54


🏁 Script executed:

#!/bin/bash
# Search in the entire codebase for the specific error check
grep -rn 'is_empty().*readonly\|readonly.*is_empty()' programs/system/src/processor/ | grep -i 'return.*unimplemented\|first_set_context\|set_context'

Repository: Lightprotocol/light-protocol

Length of output: 54


🏁 Script executed:

#!/bin/bash
# Look at the full invoke_cpi processor
cat programs/system/src/invoke_cpi/processor.rs

Repository: Lightprotocol/light-protocol

Length of output: 2295


🏁 Script executed:

#!/bin/bash
# Look for where the read-only addresses with CPI context limitation might be checked
# Search in the entire system program for the pattern
grep -rn 'read_only_addresses\|readonly_addresses' programs/system/src --include='*.rs' | grep -i 'context\|unimplemented' | head -20

Repository: Lightprotocol/light-protocol

Length of output: 2129


🏁 Script executed:

#!/bin/bash
# Check if there's any validation in WrappedInstructionData or account_option_config
grep -rn 'account_option_config\|WrappedInstructionData' programs/system/src --include='*.rs' -A 5 | grep -B 5 'read_only' | head -50

Repository: Lightprotocol/light-protocol

Length of output: 54


🏁 Script executed:

#!/bin/bash
# Read the full process.rs file to get a complete picture of all steps
wc -l programs/system/src/processor/process.rs && echo "---" && tail -100 programs/system/src/processor/process.rs

Repository: Lightprotocol/light-protocol

Length of output: 4486


🏁 Script executed:

#!/bin/bash
# Check the exact error return in state.rs
sed -n '187,195p' programs/system/src/cpi_context/state.rs

Repository: Lightprotocol/light-protocol

Length of output: 534


🏁 Script executed:

#!/bin/bash
# Verify the exact struct field naming (is_compress vs is_compress: u8)
grep -n 'is_compress\|with_cpi_context\|with_transaction_hash' program-libs/compressed-account/src/instruction_data/with_readonly.rs | head -30

Repository: Lightprotocol/light-protocol

Length of output: 1636


Fix processing step references and clarify struct field representation.

The struct field documentation and processing step numbering have discrepancies with the implementation. The actual ZInstructionDataInvokeCpiWithReadOnlyMeta stores is_compress, with_cpi_context, and with_transaction_hash as u8 fields with getter methods (not public bool fields as shown). More significantly, the processing steps referenced in "Step 8: Read-only account verification" and "Step 9: ZK proof verification" don't match the actual pipeline—read-only account verification occurs at step 14 and proof verification at step 16.

Update the documentation to:

  1. Clarify that is_compress, with_cpi_context, and with_transaction_hash are accessed via getter methods in the zero-copy struct
  2. Correct the processing step numbers or remove specific step references to avoid drift

All error codes and the read-only addresses with CPI context limitation are correctly documented and implemented.

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

58-58: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


111-111: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


112-112: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


117-117: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


118-118: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


122-122: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


123-123: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


149-149: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

🤖 Prompt for AI Agents
In `@programs/system/docs/invoke_cpi/INVOKE_CPI_WITH_READ_ONLY.md` around lines 39
- 179, The docs currently list is_compress, with_cpi_context, and
with_transaction_hash as public bool fields on
ZInstructionDataInvokeCpiWithReadOnly, and reference read-only verification as
steps 8/9; update the doc to state that the zero-copy meta struct
(ZInstructionDataInvokeCpiWithReadOnlyMeta /
ZInstructionDataInvokeCpiWithReadOnly) stores those flags as u8 and exposes them
via getter methods (e.g. is_compress(), with_cpi_context(),
with_transaction_hash()), and fix the processing step references to match the
implementation (read-only account verification at step 14 and proof verification
at step 16) or remove hard step numbers to avoid drift.

Comment on lines +41 to +61
```rust
pub struct ZInstructionDataInvokeCpiWithReadOnly<'a> {
// Metadata (fixed layout)
pub mode: u8, // AccountMode: 0 = Anchor, 1 = V2
pub bump: u8,
pub invoking_program_id: Pubkey,
pub compress_or_decompress_lamports: U64,
pub is_compress: bool,
pub with_cpi_context: bool,
pub with_transaction_hash: bool,
pub cpi_context: ZCompressedCpiContext,

// Variable-length fields (zero-copy slices)
pub proof: Option<Ref<'a, [u8], CompressedProof>>,
pub new_address_params: ZeroCopySliceBorsh<'a, ZNewAddressParamsAssignedPacked>,
pub input_compressed_accounts: Vec<ZInAccount<'a>>,
pub output_compressed_accounts: Vec<ZOutputCompressedAccountWithPackedContext<'a>>,
pub read_only_addresses: ZeroCopySliceBorsh<'a, ZPackedReadOnlyAddress>,
pub read_only_accounts: ZeroCopySliceBorsh<'a, ZPackedReadOnlyCompressedAccount>,
}
```
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 | 🟡 Minor

Resolve markdownlint issues for fenced blocks.

Static analysis flags missing blank lines and missing language identifiers for some fenced code blocks. Please add blank lines around headings/tables and label non-Rust blocks (e.g., text).

Also applies to: 118-149

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

58-58: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In `@programs/system/docs/invoke_cpi/INVOKE_CPI_WITH_READ_ONLY.md` around lines 41
- 61, Add proper blank lines before and after fenced code blocks and label
non-Rust blocks with a language identifier to satisfy markdownlint; for the
shown block containing the ZInstructionDataInvokeCpiWithReadOnly struct ensure
there is an empty line above the opening ```rust and below the closing ``` and
similarly add language identifiers (e.g., text) for any non-Rust fenced blocks
referenced (also fix the other affected fenced blocks around lines 118-149).
Locate the code fences around ZInstructionDataInvokeCpiWithReadOnly,
ZNewAddressParamsAssignedPacked, ZInAccount,
ZOutputCompressedAccountWithPackedContext, ZPackedReadOnlyAddress, and
ZPackedReadOnlyCompressedAccount and apply the blank-line and language-label
fixes consistently.

Comment on lines +63 to +67
```
[0..8]: Discriminator
[8..12]: Vec length prefix (4 bytes, always skip)
[12..]: Serialized instruction data
```
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Add language specifier to code block.

The data layout code block should specify a language for proper syntax highlighting.

📝 Proposed fix for code block
-```
+```text
 [0..8]:    Discriminator
 [8..12]:   Vec length prefix (4 bytes, always skip)
 [12..]:    Serialized instruction data

Based on learnings, documentation formatting should be consistent.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
[0..8]: Discriminator
[8..12]: Vec length prefix (4 bytes, always skip)
[12..]: Serialized instruction data
```
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

63-63: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In `@programs/system/docs/invoke/INVOKE.md` around lines 63 - 67, Update the
fenced code block that shows the data layout (the three-line block starting with
"[0..8]:    Discriminator") to include a language specifier for syntax
highlighting (for example change the opening ``` to ```text) so the block is
rendered consistently; ensure only the opening fence is modified and the block
content remains unchanged.

Comment on lines +128 to +162
### Step 1: Parse Instruction Data
```rust
let instruction_data = &instruction_data[4..]; // Skip vec prefix
let (inputs, _) = ZInstructionDataInvoke::zero_copy_at(instruction_data)?;
```

### Step 2: Parse and Validate Accounts
```rust
let (ctx, remaining_accounts) = InvokeInstruction::from_account_infos(accounts)?;
```

### Step 3: Verify Authority Signature
```rust
input_compressed_accounts_signer_check(
&inputs.input_compressed_accounts_with_merkle_context,
ctx.authority.key(),
)?;
```

**For each input compressed account:**
- Account owner must equal authority pubkey
- Account must NOT have data (`data.is_none()`)

### Step 4: Process State Transition
```rust
process::<false, InvokeInstruction, ZInstructionDataInvoke>(
wrapped_inputs,
None, // No CPI context
&ctx,
0, // Default relay fee
remaining_accounts,
)?;
```

Executes the full 19-step processing pipeline (see [PROCESSING_PIPELINE.md](../PROCESSING_PIPELINE.md)).
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Add blank lines around code blocks for consistency.

Several code blocks in the Instruction Logic section are missing blank lines before/after, which violates markdown best practices and the markdownlint rules flagged in static analysis.

📝 Proposed formatting fixes

Add blank lines:

  • Before line 130 (code block)
  • After line 132
  • Before line 136 (code block)
  • After line 138
  • Before line 141 (code block)
  • After line 145
  • Before line 153 (code block)
  • After line 160

This improves readability and consistency with markdown standards.

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

128-128: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


129-129: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


134-134: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


135-135: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


139-139: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


140-140: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


151-151: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


152-152: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

🤖 Prompt for AI Agents
In `@programs/system/docs/invoke/INVOKE.md` around lines 128 - 162, The markdown
sections around the example code blocks lack blank lines; update INVOKE.md so
each fenced code block is preceded and followed by an empty line for consistency
(add blank lines around the blocks that contain the instruction_data slice and
zero_copy_at call, the InvokeInstruction::from_account_infos call, the
input_compressed_accounts_signer_check call, and the process::<false,
InvokeInstruction, ZInstructionDataInvoke> call). Ensure the empty line is
inserted immediately before the opening ``` and immediately after the closing
``` for those code blocks so markdownlint rules are satisfied and readability is
consistent.

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