Skip to content

chore: check compress only is applied correctly#2238

Merged
ananas-block merged 2 commits intomainfrom
jorrit/chore-increase-compress-and-close-robustness
Feb 13, 2026
Merged

chore: check compress only is applied correctly#2238
ananas-block merged 2 commits intomainfrom
jorrit/chore-increase-compress-and-close-robustness

Conversation

@ananas-block
Copy link
Contributor

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

Summary by CodeRabbit

  • Bug Fixes

    • Stricter validation to reject compression-only extensions when a token's state doesn't require them.
    • Added an explicit error for unexpected compression-only extension cases to improve diagnosability.
    • Output validation now forbids specifying a delegate when the compression-only extension is absent.
  • New Features

    • Added helpers to identify marker extension types.
    • Added token-level checks to detect marker extensions and other state that require compression-only handling.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 5, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new Anchor error variant and tightens compress-and-close validation: determine when a CompressedOnly extension is required from compression settings or token state, enforce missing/unexpected extension errors, and add helper APIs for extension and token state queries.

Changes

Cohort / File(s) Summary
Anchor Error Definition
programs/compressed-token/anchor/src/lib.rs
Added CompressAndCloseUnexpectedCompressedOnlyExtension error variant (code 6173).
Compress-and-close validation
programs/compressed-token/program/src/compressed_token/transfer2/compression/ctoken/compress_and_close.rs
Reworked validate_compressed_token_account to compute needs_compressed_only_ext from compression or token state, perform a three-way check (missing / unexpected / ok), and consolidate delegated/fee/frozen/ATA validations into validate_compressed_only_ext.
Extension helpers
program-libs/token-interface/src/state/extensions/extension_struct.rs, program-libs/token-interface/src/state/extensions/extension_type.rs, program-libs/token-interface/src/state/extensions/compressible.rs
Exported extension_type() and is_marker_extension() on extension structs; added pub const fn is_marker_extension(ext: ExtensionType) and requires_compressed_only_ext() on compressible extension.
Token zero-copy helper
program-libs/token-interface/src/state/token/zero_copy.rs
Added ZTokenMut::has_marker_extensions() and ZTokenMut::has_state_requiring_compressed_only() to detect marker extensions and whether token state (frozen, delegate, marker) mandates CompressedOnly extension.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Program as compress_and_close program
    participant TokenLib as token-interface helpers
    participant Account as CompressedTokenAccount

    Client->>Program: invoke compress_and_close
    Program->>Account: read account & compression ext
    Program->>TokenLib: call compression.requires_compressed_only_ext()
    Program->>TokenLib: call ZTokenMut.has_state_requiring_compressed_only()
    TokenLib-->>Program: returns needs_compressed_only_ext (bool)
    Program->>Account: check compression_only ext presence
    alt required but missing
        Program-->>Client: error CompressAndCloseMissingCompressedOnlyExtension
    else present but not required
        Program-->>Client: error CompressAndCloseUnexpectedCompressedOnlyExtension
    else ok
        Program->>Program: validate compressed-only ext fields (delegate, amount, withheld, frozen, is_ata)
        Program-->>Client: success / proceed
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

ai-review

Suggested reviewers

  • sergeytimoshin
  • SwenSchaeferjohann

Poem

A tiny flag checks if a token's bound,
Frozen, marked, or pledged — the rule is found.
If extension fits, the close proceeds;
If not, an error humbly pleads. 🎈

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective: improving validation of the compressed-only extension application across the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 70.00%.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ 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/chore-increase-compress-and-close-robustness

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.

if (compression.compression_only() || compression.is_ata()) && compression_only_ext.is_none() {
return Err(ErrorCode::CompressAndCloseMissingCompressedOnlyExtension.into());
}
if !compression.compression_only() && !compression.is_ata() && compression_only_ext.is_some() {
Copy link
Contributor

Choose a reason for hiding this comment

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

We'll also need the extension for a frozen account or when a delegate exists

@ananas-block ananas-block force-pushed the jorrit/chore-increase-compress-and-close-robustness branch from 7f5bc08 to 64efc53 Compare February 7, 2026 08:49
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: 1

🤖 Fix all issues with AI agents
In
`@programs/compressed-token/program/src/compressed_token/transfer2/compression/ctoken/compress_and_close.rs`:
- Around line 157-167: The check for the four "marker" extension types is
duplicated and should be extracted: add a helper method (e.g.,
ZTokenMut::has_marker_extensions(&self) -> bool) or a standalone utility
function (e.g., has_marker_extensions(exts: &Option<Vec<ZExtensionStructMut>>)
-> bool) that encapsulates the matches against
ZExtensionStructMut::PausableAccount, PermanentDelegateAccount,
TransferHookAccount, and TransferFeeAccount; then replace the inline logic that
sets has_marker_extensions in compress_and_close.rs with a call to that helper
(referencing has_marker_extensions, ZTokenMut, and ZExtensionStructMut to locate
the code).

@ananas-block ananas-block force-pushed the jorrit/chore-increase-compress-and-close-robustness branch from 64efc53 to 470865e Compare February 10, 2026 22:44
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: 2

Caution

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

⚠️ Outside diff range comments (1)
program-libs/token-interface/src/state/extensions/extension_struct.rs (1)

4-16: ⚠️ Potential issue | 🟡 Minor

Fix the lint-reported formatting issue in the imports.

The pipeline flags a formatting diff on these import lines. Run cargo fmt to resolve.

🤖 Fix all issues with AI agents
In `@program-libs/token-interface/src/state/extensions/extension_type.rs`:
- Around line 70-91: Add a compile-time sanity check to ensure
MARKER_EXTENSION_TYPES and is_marker_extension stay in sync by asserting the
array length equals the number of variants handled; for example, add a const
assertion like `const _: [(); MARKER_EXTENSION_TYPES.len()] = [(); 4];` adjacent
to the `MARKER_EXTENSION_TYPES`/`is_marker_extension` definitions (or use a
`const_assert!` if your project provides one) so any future addition forces
updating both places.

In
`@programs/compressed-token/program/src/compressed_token/transfer2/compression/ctoken/compress_and_close.rs`:
- Around line 154-163: The if condition starting with "if
!compression.compression_only() && !compression.is_ata() &&
compression_only_ext.is_some()" exceeds the rustfmt 100-char limit; reformat it
into multiple lines (e.g., break after each &&) so the same logic is preserved
and rustfmt passes, keeping the inner check using ctoken.is_frozen(),
ctoken.delegate().is_none(), ctoken.has_marker_extensions(), and returning
ErrorCode::CompressAndCloseUnexpectedCompressedOnlyExtension when appropriate.

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

🤖 Fix all issues with AI agents
In `@program-libs/token-interface/src/state/extensions/extension_struct.rs`:
- Around line 63-83: The match in ExtensionStruct::extension_type() currently
uses a catch-all arm returning ExtensionType::Placeholder0 which can silently
hide future real variants; update extension_type() to explicitly match each
Placeholder variant (e.g., ExtensionStruct::Placeholder0, Placeholder1, etc.)
instead of `_` so the compiler forces updates when new variants are added, or
replace the `_ => ExtensionType::Placeholder0` with a branch that
asserts/unreachable for non-placeholder discriminants (using debug_assert! or
unreachable!) and only return Placeholder0 for the explicit Placeholder0
variant; ensure is_marker_extension() remains calling extension_type().
- Around line 133-156: The catch-all `_ => ExtensionType::Placeholder0` in
ZExtensionStructMut::extension_type() silently maps any future/new variant to
Placeholder0; replace the wildcard with explicit match arms for every
ZExtensionStructMut variant (mapping the Placeholder0 variant to
ExtensionType::Placeholder0) and add a final unreachable panic (e.g.,
panic!("unhandled ZExtensionStructMut variant: {:?}", self)) or use
unreachable!() so any new variant fails loudly instead of silently mapping to
Placeholder0; update the match in ZExtensionStructMut::extension_type()
accordingly.

In
`@programs/compressed-token/program/src/compressed_token/transfer2/compression/ctoken/compress_and_close.rs`:
- Around line 154-163: The code's validation is correct but the file isn't
formatted per rustfmt; run cargo fmt (or rustfmt) to reformat the file
containing compress_and_close.rs so the guard block and surrounding whitespace
match the project style, then commit the formatting changes; the relevant
symbols to locate the code are compression.compression_only(),
compression.is_ata(), compression_only_ext, ctoken.is_frozen(),
ctoken.delegate(), ctoken.has_marker_extensions(), and
ErrorCode::CompressAndCloseUnexpectedCompressedOnlyExtension.

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.

Caution

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

⚠️ Outside diff range comments (1)
programs/compressed-token/program/src/compressed_token/transfer2/compression/ctoken/compress_and_close.rs (1)

163-171: 🧹 Nitpick | 🔵 Trivial

Step 6 comment at line 165 could be slightly more precise.

The comment says "ctoken state (frozen, delegate, markers) is already validated in step 5" — but step 5 validates that the extension presence matches the requirement. The actual implication is that reaching this code path means needs_compressed_only_ext was false, which transitively means the ctoken is not frozen, has no delegate, and has no marker extensions. Might be worth clarifying that nuance for future readers.

📝 Suggested comment wording
     let Some(ext) = compression_only_ext else {
-        // ctoken state (frozen, delegate, markers) is already validated in step 5.
-        // Here we only check that the output doesn't specify a delegate.
+        // Reaching here means needs_compressed_only_ext was false, i.e., the ctoken
+        // is not frozen, has no delegate, and has no marker extensions (step 5).
+        // We only need to verify the output doesn't fabricate a delegate.
         if compressed_token_account.has_delegate() {

@ananas-block ananas-block force-pushed the jorrit/chore-increase-compress-and-close-robustness branch from 8561b28 to 42cbcef Compare February 12, 2026 20:58
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: 1

🤖 Fix all issues with AI agents
In `@programs/compressed-token/anchor/src/lib.rs`:
- Around line 583-586: Update the diagnostic string for the enum variant
CompressAndCloseUnexpectedCompressedOnlyExtension to more accurately reflect the
validation logic: replace the current message about "non-compression_only
accounts" with a broader developer-facing message such as "CompressAndClose:
CompressedOnly extension present but not required by account state" (or similar)
so it covers ATA accounts, frozen/delegated accounts, and accounts with marker
extensions while keeping this as a debug-only message.

@ananas-block ananas-block force-pushed the jorrit/chore-increase-compress-and-close-robustness branch from 42cbcef to 8740c68 Compare February 12, 2026 22:33
@ananas-block ananas-block force-pushed the jorrit/chore-increase-compress-and-close-robustness branch from 8740c68 to 5b050f2 Compare February 12, 2026 22:43
@ananas-block ananas-block merged commit a3870ed into main Feb 13, 2026
30 checks passed
@ananas-block ananas-block deleted the jorrit/chore-increase-compress-and-close-robustness branch February 13, 2026 02:43
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.

3 participants