Skip to content

fix(l2ps): canonicalise inner native amount across the osDenomination fork#926

Merged
tcsenpai merged 2 commits into
stabilisationfrom
fix/l2ps-executor-canonicalize-amount
Jun 8, 2026
Merged

fix(l2ps): canonicalise inner native amount across the osDenomination fork#926
tcsenpai merged 2 commits into
stabilisationfrom
fix/l2ps-executor-canonicalize-amount

Conversation

@Shitikyan

@Shitikyan Shitikyan commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Summary

The L2PS native-send executor was treating the inner amount as a raw DEM number. Post-osDenomination-fork this means every L2PS native transfer moves about one part in a billion of what the sender intended, and a post-fork OS string amount is rejected by the executor's number-only validation. Surfaced by the PATH-OS L2PS hardening report.

Mirror the L1 native path exactly:

  • Read isForkActive(\"osDenomination\", lastBlockNumber) from shared state
  • Pass the wire amount through canonicalizeAmountToOs to get a bigint magnitude regardless of pre- or post-fork shape
  • Canonicalise L2PS_TX_FEE the same way so the balance check arithmetic agrees on units (1 DEM = 10⁹ OS)
  • Emit GCR edits in the magnitude downstream consumers expect: OS string post-fork, legacy DEM number pre-fork

L2PS_TX_FEE itself stays at 1; only the arithmetic is canonicalised.

Test plan

  • tsc --noEmit --skipLibCheck clean on the touched file
  • End-to-end verification through the dev-node battery once the dev environment is rebooted (tracked under DEM-728)

No unit test added: the executor depends on the full DB stack (Datasource + GCRMain + HandleGCR), and the canonicalisation primitive it now calls is already covered in testing/forks/amountCanonical.test.ts.

Linked

  • Linear: DEM-755
  • Source: PATH-OS L2PS hardening report, item 3 (Medium severity)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved transaction amount handling to be fork-aware across protocol upgrades.
    • Added robust validation and graceful failure for invalid or uncanonicalizable amounts.
    • Updated balance checks to compare using canonicalized units, preventing incorrect rejections.
    • Ensure ledger edits (fees, debits, credits) emit amounts in the correct denomination format for the active protocol.

@qodo-code-review

Copy link
Copy Markdown
Contributor

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b4a8acd2-f6b9-4b3a-a924-78794f5d9987

📥 Commits

Reviewing files that changed from the base of the PR and between c98fdbc and 45605cb.

📒 Files selected for processing (1)
  • src/libs/l2ps/L2PSTransactionExecutor.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/libs/l2ps/L2PSTransactionExecutor.ts

Walkthrough

L2PSTransactionExecutor now enforces fork-aware denomination handling in native "send" transactions. Amount and fee canonicalization to OS units is gated by fork activation status, balance validation occurs at OS magnitudes, and generated GCR edits reflect fork-dependent amount formats. Error handling prevents invalid amounts from proceeding.

Changes

Fork-aware denomination handling in L2PS transactions

Layer / File(s) Summary
Fork-aware denomination imports
src/libs/l2ps/L2PSTransactionExecutor.ts
Imports shared-state block height retrieval and canonicalizeAmountToOs utilities to support fork-aware amount normalization.
Native send amount canonicalization and GCR edits
src/libs/l2ps/L2PSTransactionExecutor.ts
Accepts rawAmount as number | string, canonicalizes both transaction amount and L2PS_TX_FEE constant into OS units based on fork activation, validates sender balance sufficiency at OS magnitudes, and generates fee burn/removal GCR edits using fork-dependent amount representations (OS strings when fork is active, legacy numbers otherwise). Invalid/uncanonicalizable amounts trigger error responses.
Receiver balance edit fork-aware crediting
src/libs/l2ps/L2PSTransactionExecutor.ts
Updates receiver balance credit to use the fork-aware editAmount rather than the original raw numeric amount.

Sequence Diagram

sequenceDiagram
  participant Client
  participant L2PSTransactionExecutor
  participant SharedState
  participant Denomination
  participant GCR
  participant L1Account
  Client->>L2PSTransactionExecutor: submit native send(rawAmount, receiver)
  L2PSTransactionExecutor->>SharedState: getSharedState()
  SharedState-->>L2PSTransactionExecutor: referenceHeight
  L2PSTransactionExecutor->>Denomination: isForkActive("osDenomination", referenceHeight)
  Denomination-->>L2PSTransactionExecutor: forkActive
  L2PSTransactionExecutor->>Denomination: canonicalizeAmountToOs(rawAmount / L2PS_TX_FEE)
  Denomination-->>L2PSTransactionExecutor: amountCanonical, feeCanonical (or error)
  L2PSTransactionExecutor->>L1Account: compare balance >= amountCanonical + feeCanonical
  L2PSTransactionExecutor->>GCR: emit fee remove (editFee) and transfer remove/add (editAmount)
  GCR-->>Client: success/failure response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

  • kynesyslabs/node#861: Updates fork-aware osDenomination handling to canonicalize/serialize amounts into OS units for GCR edits and post-fork validation.
  • kynesyslabs/node#872: Adjusts post-fork OS-magnitude amount/fee handling to use number | string wire-safe values instead of throwing on conversion failures.

Poem

🐰 A fork in the road, amounts divide,
Legacy numbers and OS strings collide,
Canonicalize with grace, validate with care,
Fork-aware GCR edits float through the air!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(l2ps): canonicalise inner native amount across the osDenomination fork' directly and accurately describes the main change: adding fork-aware denomination handling to the L2PS native transaction executor.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/l2ps-executor-canonicalize-amount

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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.

@greptile-apps

greptile-apps Bot commented Jun 5, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes a critical denomination mismatch in the L2PS native-send executor where post-osDenomination fork transactions were either moved at ~10⁻⁹× the intended value (legacy DEM number treated as OS) or rejected outright (OS string amounts failing the old number-only guard).

  • Fork-aware canonicalization: Introduces canonicalizeAmountToOs + isForkActive("osDenomination", lastBlockNumber), mirroring executeNativeTransaction.ts exactly — both use the same helpers, the same reference height source, and the same denomination.toOsString for the GCR edit amounts.
  • Balance arithmetic fix: totalRequired is now computed in OS units post-fork and DEM units pre-fork, matching the unit in which senderAccount.balance is persisted at each fork stage.
  • Precision improvement: pre-fork GCR edits now emit amountCanonical.toString() (string DEM) instead of Number(bigint), eliminating silent truncation above Number.MAX_SAFE_INTEGER.

Confidence Score: 5/5

Safe to merge — the canonicalization logic directly mirrors the L1 native-send path in executeNativeTransaction.ts, using the same helpers, the same reference height source, and the same denomination functions.

The fork-aware amount handling is consistent with the established L1 path: same helpers, same reference height source, correct balance comparison units in both fork regimes. Both previously flagged issues are addressed. No logic errors or missing guards were found.

No files require special attention.

Important Files Changed

Filename Overview
src/libs/l2ps/L2PSTransactionExecutor.ts Replaces raw DEM handling with fork-aware canonicalization for the native-send path; mirrors the L1 executeNativeTransaction pattern exactly using the same helpers (canonicalizeAmountToOs, isForkActive, denomination.toOsString). Balance comparison and GCR edit magnitudes are consistent within each fork regime.

Reviews (3): Last reviewed commit: "review: address greptile P2 — uniform er..." | Re-trigger Greptile

Comment thread src/libs/l2ps/L2PSTransactionExecutor.ts Outdated
Comment thread src/libs/l2ps/L2PSTransactionExecutor.ts Outdated
Shitikyan added a commit that referenced this pull request Jun 5, 2026
Two P2 fixes from review on PR #926:

1. **Uniform error path for canonicalisation.** `L2PS_TX_FEE`
   canonicalisation was outside the try-catch that guards the wire-
   amount call. `L2PS_TX_FEE = 1` is a valid constant and won't throw
   in practice, but the inconsistency meant any unexpected drift in
   the helper's input shape would surface as the generic
   `"Execution failed"` from the outer catch instead of the specific
   `"Invalid amount"` wording. Move both calls inside the same guard.

2. **Avoid `Number(bigint)` for the GCR edit shape.** Pre-fork,
   `amountCanonical` is a raw DEM-magnitude `bigint` with no scaling.
   Converting via `Number()` silently truncates anything above
   `Number.MAX_SAFE_INTEGER` (~9 quadrillion DEM) while the balance
   check upstream had already passed the full-precision amount. Use
   `.toString()` on both edits — `GCREditBalance.amount` accepts
   `number | string`, the rest of the pipeline handles string already
   for post-fork OS magnitudes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Shitikyan and others added 2 commits June 8, 2026 17:26
… fork (DEM-755)

The L2PS native-send executor was treating the inner `amount` as a raw
DEM number and computing arithmetic via `BigInt(amount)` directly. The
L1 native path goes through `canonicalizeAmountToOs`; the L2PS executor
did not. Two failure modes follow:

1. After the osDenomination fork, an L2PS native transfer moves about
   one part in a billion of what the sender intended, because a post-
   fork OS magnitude is treated the same way a pre-fork DEM number
   was. The balance check uses the same wrong magnitude, so the
   transfer also charges a fee that does not match the wire shape.

2. The pre-edit validation rejected anything that was not a JS
   `number`, so any post-fork SDK that sends an OS-string amount was
   refused outright with "must be a positive integer".

Mirror the L1 pattern exactly:

- Pull the reference block height from shared state and read
  `isForkActive("osDenomination", ...)`.
- Pass the wire amount through `canonicalizeAmountToOs` to get a
  bigint magnitude regardless of pre- or post-fork shape; surface
  the helper's error message instead of the old number-only check.
- Canonicalise the fee the same way so the balance check arithmetic
  agrees on units (1 DEM = 10^9 OS).
- Emit GCR edits in the magnitude downstream consumers expect:
  OS string post-fork (matches what `serializerGate` writes for L1),
  legacy DEM number pre-fork.

`L2PS_TX_FEE` itself stays at 1 (in DEM); only the arithmetic is
canonicalised.

Surfaced by the PATH-OS L2PS hardening report. No unit test added in
this PR — the executor depends on the full DB stack (Datasource +
GCRMain + HandleGCR); the canonicalisation primitive itself is
already covered in `testing/forks/amountCanonical.test.ts`. End-to-end
verification through the existing dev-node battery once the dev
environment is rebooted (tracked under DEM-728).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two P2 fixes from review on PR #926:

1. **Uniform error path for canonicalisation.** `L2PS_TX_FEE`
   canonicalisation was outside the try-catch that guards the wire-
   amount call. `L2PS_TX_FEE = 1` is a valid constant and won't throw
   in practice, but the inconsistency meant any unexpected drift in
   the helper's input shape would surface as the generic
   `"Execution failed"` from the outer catch instead of the specific
   `"Invalid amount"` wording. Move both calls inside the same guard.

2. **Avoid `Number(bigint)` for the GCR edit shape.** Pre-fork,
   `amountCanonical` is a raw DEM-magnitude `bigint` with no scaling.
   Converting via `Number()` silently truncates anything above
   `Number.MAX_SAFE_INTEGER` (~9 quadrillion DEM) while the balance
   check upstream had already passed the full-precision amount. Use
   `.toString()` on both edits — `GCREditBalance.amount` accepts
   `number | string`, the rest of the pipeline handles string already
   for post-fork OS magnitudes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Shitikyan Shitikyan force-pushed the fix/l2ps-executor-canonicalize-amount branch from ead3d66 to 45605cb Compare June 8, 2026 13:26
@tcsenpai tcsenpai merged commit 1fd4aa4 into stabilisation Jun 8, 2026
4 checks passed
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