Skip to content

fix(programs): interpret max_top_up as units of 1,000 lamports (L-07)#2265

Merged
ananas-block merged 2 commits intomainfrom
jorrit/fix-certora-l07-max-top-up-units
Feb 12, 2026
Merged

fix(programs): interpret max_top_up as units of 1,000 lamports (L-07)#2265
ananas-block merged 2 commits intomainfrom
jorrit/fix-certora-l07-max-top-up-units

Conversation

@ananas-block
Copy link
Contributor

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

Summary

  • Certora audit L-07: max_top_up is a u16, so its max value (65,535) only allows ~65K lamports of rent budget. Typical rent for a 270-byte account is ~2.5M lamports, making the cap ineffective.
  • Fix: Interpret max_top_up as units of 1,000 lamports, extending the effective range to ~65.5M lamports.

Changes

  • compressible_top_up.rs, compression/mod.rs, process_actions.rs: Changed budget calculation from (max_top_up as u64) + 1 to (max_top_up as u64) * 1000 + 1.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed max_top_up handling across the compressed token program so top-up limits and reserved lamport budgets scale correctly (interpreting max_top_up in units of 1,000 lamports), preventing under- or over-reserving of lamports during token operations.
  • Documentation

    • Updated instruction and API docs to clarify max_top_up units and provide examples for expected values.

max_top_up is u16 (max 65,535). As raw lamports this only allows
~0.065 SOL which is insufficient for many use cases. Interpreting
as units of 1,000 lamports gives a max of ~65.5M lamports (~0.065 SOL
becomes ~65 SOL), covering realistic rent top-up scenarios.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 9, 2026

📝 Walkthrough

Walkthrough

This PR changes interpretation of max_top_up to mean units of 1,000 lamports across code and docs, updating budget calculations from max_top_up + 1 to (max_top_up * 1000) + 1. Control flow and error handling are unchanged.

Changes

Cohort / File(s) Summary
Core budget calculations
programs/compressed-token/program/src/compressed_token/mint_action/actions/process_actions.rs, programs/compressed-token/program/src/compressed_token/transfer2/compression/mod.rs, programs/compressed-token/program/src/shared/compressible_top_up.rs
Initialize lamports_budget as (max_top_up as u64).saturating_mul(1000) + 1 instead of max_top_up + 1; comments updated to state max_top_up is in 1,000-lamport units.
Top-up / transfer enforcement
programs/compressed-token/program/src/ctoken/approve_revoke.rs, programs/compressed-token/program/src/ctoken/transfer/shared.rs
Compare totals against (max_top_up as u64).saturating_mul(1000) (when non-zero) instead of raw max_top_up; clarifying comments added.
Documentation updates
programs/compressed-token/program/docs/compressed_token/MINT_ACTION.md, programs/compressed-token/program/docs/compressed_token/TRANSFER2.md, programs/compressed-token/program/docs/ctoken/*.md
Describe max_top_up as measured in units of 1,000 lamports across multiple instruction-format docs and examples; add examples and clarify 0 == no limit.

Sequence Diagram(s)

(omitted — changes are arithmetic/documentation and do not introduce new multi-component control flow)

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly Related PRs

Suggested Labels

ai-review

Suggested Reviewers

  • sergeytimoshin
  • SwenSchaeferjohann

Poem

🪙 A thousand lamports in each little heap,
Numbers realigned where budgets sleep.
Docs updated, checks still true—
Small math, big clarity, the code shines through. ✨

🚥 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 accurately reflects the main change: interpreting max_top_up as units of 1,000 lamports across multiple files to address the Certora L-07 audit finding.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 70.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jorrit/fix-certora-l07-max-top-up-units

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.

@ananas-block ananas-block changed the title fix(L-07): interpret max_top_up as units of 1,000 lamports fix(programs): interpret max_top_up as units of 1,000 lamports (L-07) Feb 9, 2026
@ananas-block ananas-block marked this pull request as draft February 10, 2026 00:48
@ananas-block ananas-block marked this pull request as ready for review February 10, 2026 22:23
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

Caution

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

⚠️ Outside diff range comments (2)
programs/compressed-token/program/src/shared/compressible_top_up.rs (1)

25-25: ⚠️ Potential issue | 🟡 Minor

Doc comment should reflect the new unit semantics.

The max_top_up parameter doc says "Maximum lamports for top-ups combined" but it's no longer raw lamports — it's in units of 1,000 lamports. This will confuse callers who read the doc without looking at the implementation.

📝 Suggested doc update
-/// * `max_top_up` - Maximum lamports for top-ups combined (0 = no limit)
+/// * `max_top_up` - Maximum top-up budget in units of 1,000 lamports (0 = no limit)
programs/compressed-token/program/src/compressed_token/transfer2/compression/mod.rs (1)

40-40: ⚠️ Potential issue | 🟡 Minor

Same doc staleness as the other file — update the units.

📝 Suggested doc update
-/// * `max_top_up` - Maximum lamports for rent and top-up combined. Transaction fails if exceeded. (0 = no limit)
+/// * `max_top_up` - Maximum top-up budget in units of 1,000 lamports. Transaction fails if exceeded. (0 = no limit)
🤖 Fix all issues with AI agents
In
`@programs/compressed-token/program/src/compressed_token/mint_action/actions/process_actions.rs`:
- Around line 52-56: The comment above the lamports_budget calculation is stale
and misleading: update or remove the line that says "+1 allows exact match
(total == max_top_up)" so the comment matches the actual math. Locate the code
around parsed_instruction_data.max_top_up and the variable lamports_budget (the
computation using (max_top_up as u64).saturating_mul(1000).saturating_add(1))
and either remove the redundant/incorrect sentence or reword it to reflect that
max_top_up is in units of 1,000 lamports and the +1 allows an exact match
against max_top_up * 1000.

Comment on lines 52 to +56
// Initialize budget: +1 allows exact match (total == max_top_up)
let max_top_up: u16 = parsed_instruction_data.max_top_up.get();
let mut lamports_budget = (max_top_up as u64).saturating_add(1);
// max_top_up is in units of 1,000 lamports (max ~65.5M lamports).
// +1 allows exact match (total == max_top_up * 1000).
let mut lamports_budget = (max_top_up as u64).saturating_mul(1000).saturating_add(1);
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

Stale comment on line 52 — it still references the old formula.

Line 52 says +1 allows exact match (total == max_top_up) which is the pre-change wording. Lines 54-55 already carry the correct updated explanation. Line 52 is now both redundant and misleading.

📝 Remove or update the stale comment
     // Array to accumulate transfer amounts by account index
     let mut transfer_map = [0u64; MAX_PACKED_ACCOUNTS];
-    // Initialize budget: +1 allows exact match (total == max_top_up)
     let max_top_up: u16 = parsed_instruction_data.max_top_up.get();
     // max_top_up is in units of 1,000 lamports (max ~65.5M lamports).
     // +1 allows exact match (total == max_top_up * 1000).
     let mut lamports_budget = (max_top_up as u64).saturating_mul(1000).saturating_add(1);
📝 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
// Initialize budget: +1 allows exact match (total == max_top_up)
let max_top_up: u16 = parsed_instruction_data.max_top_up.get();
let mut lamports_budget = (max_top_up as u64).saturating_add(1);
// max_top_up is in units of 1,000 lamports (max ~65.5M lamports).
// +1 allows exact match (total == max_top_up * 1000).
let mut lamports_budget = (max_top_up as u64).saturating_mul(1000).saturating_add(1);
let max_top_up: u16 = parsed_instruction_data.max_top_up.get();
// max_top_up is in units of 1,000 lamports (max ~65.5M lamports).
// +1 allows exact match (total == max_top_up * 1000).
let mut lamports_budget = (max_top_up as u64).saturating_mul(1000).saturating_add(1);
🤖 Prompt for AI Agents
In
`@programs/compressed-token/program/src/compressed_token/mint_action/actions/process_actions.rs`
around lines 52 - 56, The comment above the lamports_budget calculation is stale
and misleading: update or remove the line that says "+1 allows exact match
(total == max_top_up)" so the comment matches the actual math. Locate the code
around parsed_instruction_data.max_top_up and the variable lamports_budget (the
computation using (max_top_up as u64).saturating_mul(1000).saturating_add(1))
and either remove the redundant/incorrect sentence or reword it to reflect that
max_top_up is in units of 1,000 lamports and the +1 allows an exact match
against max_top_up * 1000.

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

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/docs/ctoken/MINT_TO.md (1)

92-96: ⚠️ Potential issue | 🟡 Minor

Line 93 doesn't reflect the *1000 scaling — update to match the implementation and earlier documentation.

Line 20 correctly states that max_top_up is in units of 1,000 lamports, but line 93 describes budget initialization as max_top_up.saturating_add(1) without mentioning the scaling. The actual implementation (in process_actions.rs) uses (max_top_up as u64).saturating_mul(1000).saturating_add(1). This inconsistency is confusing since a reader learns the unit specification at line 20, then sees line 93 with no scaling factor applied.

Fix
-      - Initialize budget to max_top_up.saturating_add(1) (allowing exact match)
+      - Initialize budget to (max_top_up as u64) * 1000 + 1 (units of 1,000 lamports; +1 allows exact match)
🤖 Fix all issues with AI agents
In `@programs/compressed-token/program/docs/ctoken/APPROVE.md`:
- Line 23: Add a brief audit/context note explaining why `max_top_up` is
interpreted in units of 1,000 lamports (e.g., to extend range while preserving
compatibility and reduce overflow risk) immediately after the "Bytes 8-9
(optional): `max_top_up`" line in APPROVE.md and replicate that same explanatory
sentence near the corresponding occurrences around the other documented uses of
`max_top_up` (the sections currently at the equivalents of lines 116-118 and
137-139) so readers understand the rationale for the scaling change.


- Bytes 0-7: `amount` (u64, little-endian) - Number of tokens to delegate
- Bytes 8-9 (optional): `max_top_up` (u16, little-endian) - Maximum lamports for top-up (0 = no limit, default for legacy format)
- Bytes 8-9 (optional): `max_top_up` (u16, little-endian) - Maximum lamports for top-up in units of 1,000 lamports (e.g., max_top_up=1 means 1,000 lamports, max_top_up=65535 means ~65.5M lamports). 0 = no limit, default for legacy format.
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

LGTM! Documentation accurately reflects the unit scaling change.

The documentation correctly explains that max_top_up is now interpreted as units of 1,000 lamports, with accurate examples and consistent code snippets. The math checks out: max_top_up=65535 → 65,535,000 lamports, which addresses the insufficient range issue mentioned in the PR objectives.

The use of saturating_mul(1000) is appropriate for defensive programming, and the "0 = no limit" semantics are clearly documented.

Optional enhancement: Add audit context

Consider adding a brief note explaining the rationale for this unit interpretation. For example, after line 23:

 - Bytes 8-9 (optional): `max_top_up` (u16, little-endian) - Maximum lamports for top-up in units of 1,000 lamports (e.g., max_top_up=1 means 1,000 lamports, max_top_up=65535 means ~65.5M lamports). 0 = no limit, default for legacy format.
+
+  **Note**: The 1,000-lamport unit interpretation extends the effective range to ~65.5M lamports, addressing the limitation where raw u16 values (max 65,535 lamports) were insufficient for typical rent requirements (e.g., ~2.5M lamports for a 270-byte account).

This would help readers understand why the scaling was introduced.

Also applies to: 116-118, 137-139

🤖 Prompt for AI Agents
In `@programs/compressed-token/program/docs/ctoken/APPROVE.md` at line 23, Add a
brief audit/context note explaining why `max_top_up` is interpreted in units of
1,000 lamports (e.g., to extend range while preserving compatibility and reduce
overflow risk) immediately after the "Bytes 8-9 (optional): `max_top_up`" line
in APPROVE.md and replicate that same explanatory sentence near the
corresponding occurrences around the other documented uses of `max_top_up` (the
sections currently at the equivalents of lines 116-118 and 137-139) so readers
understand the rationale for the scaling change.

@ananas-block ananas-block merged commit 655c10f into main Feb 12, 2026
31 checks passed
@ananas-block ananas-block deleted the jorrit/fix-certora-l07-max-top-up-units branch February 12, 2026 01:32
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