Skip to content

fix: prevent pendingBridgeFees underflow on truncated batch fees (DEC-820)#775

Open
clemsos wants to merge 2 commits intomainfrom
claude/sweet-buck-cd9865
Open

fix: prevent pendingBridgeFees underflow on truncated batch fees (DEC-820)#775
clemsos wants to merge 2 commits intomainfrom
claude/sweet-buck-cd9865

Conversation

@clemsos
Copy link
Copy Markdown
Collaborator

@clemsos clemsos commented Apr 30, 2026

Summary

  • Per-message fees in handle() use integer division and truncate to 0 for small message.amount. The batch fee in claim() is computed off the aggregated claim amount, where floor(sum) >= sum(floor). When the gap is non-zero, pendingBridgeFees -= feeAmount underflows and reverts the claim.
  • Tracks accumulated fees per (chainId, bridge) and bounds the claim-time subtraction with Math.min(accumulatedFeesByOrigin, batchFee). This also stops one origin from silently consuming another origin's accrued fees.
  • Adds fee-truncation.hardhat.ts with three scenarios: (1) 1000 dust messages — claim panics under old code, succeeds with 0 fee under fix; (2) a dust origin's claim does not eat the OP origin's pending fees; (3) a normal claim still drains accumulated fees.

Detail

In handle():

uint256 feeAmount = (message.amount * origin.bridgeFee) / FRACTIONAL_BPS_DENOMINATOR; // can be 0
accumulatedFeesByOrigin[chainId][bridge] += feeAmount;
pendingBridgeFees += feeAmount;

In _claim():

uint256 batchFee = (amount * origin.bridgeFee) / FRACTIONAL_BPS_DENOMINATOR;
feeAmount = Math.min(accumulatedFeesByOrigin[chainId][bridge], batchFee);
accumulatedFeesByOrigin[chainId][bridge] -= feeAmount;
pendingBridgeFees -= feeAmount;

The processFailedHandler rescue path is unchanged — it skips fee processing on both sides and so does not need to touch the new mapping (consistent with the DEC-652 fix in #768).

Impact

  • Old behavior: a sequence of dust messages on a quiet origin can DoS claim() for that origin until unrelated handle() activity refills pendingBridgeFees.
  • New behavior: dust messages stay accounted for, and even the worst case (per-message fees all truncate to 0) cleanly results in a 0-fee claim instead of an arithmetic panic.

Deployment note

RelayPool is deployed by RelayPoolFactory.deployPool via new RelayPool(...) — it is not a proxy and not upgradeable. Already-deployed pools will not pick up this change; only newly deployed pools will. The existing pools should be redeployed if this fix needs to apply to them.

Test plan

  • yarn hardhat test test/RelayPool/*.hardhat.ts — 86 passing, including the 3 new tests in fee-truncation.hardhat.ts
  • yarn lint:contracts — 0 errors (warnings unchanged from main)
  • Existing failed-hyperlane.hardhat.ts tests still pass, including the cross-origin fee preservation and normal claim path tests

🤖 Generated with Claude Code

…-820)

Per-message fees in handle() are computed via integer division and can
truncate to 0 for small message.amount values. The matching subtraction
in claim() is computed off the *aggregated* claim amount, where the
floor of the sum may exceed the sum of the per-message floors
(floor(A) + floor(B) <= floor(A+B)). When the batch fee exceeds what
was actually accumulated, pendingBridgeFees -= feeAmount underflows
and reverts the claim, locking bridged funds for that origin until
unrelated handle() calls happen to bring pendingBridgeFees back up.

Track accumulated fees per (chainId, bridge) in addition to the global
pendingBridgeFees, and at claim time bound the deduction by what was
actually accrued for that origin. This also keeps origins from
accidentally consuming each other's pending fees.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@clemsos clemsos requested a review from julien51 April 30, 2026 13:19
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant