Skip to content

Fix L2 bridge withdraw balance check#3539

Open
TaprootFreak wants to merge 4 commits intodevelopfrom
fix/l2-bridge-withdraw-balance-check
Open

Fix L2 bridge withdraw balance check#3539
TaprootFreak wants to merge 4 commits intodevelopfrom
fix/l2-bridge-withdraw-balance-check

Conversation

@TaprootFreak
Copy link
Copy Markdown
Collaborator

@TaprootFreak TaprootFreak commented Apr 3, 2026

Summary

  • Bug 1 (L2 Bridge): Redundancy pipeline for Polygon/ZCHF (rule 171) fails with ERC20: burn amount exceeds balance because withdraw() uses order.maxAmount directly without verifying the actual on-chain L2 balance

  • Root cause: Util.round(..., 8) rounds 7605.78999999716... up to 7605.79, which converts to 7605790000000000000000 wei — 2,837,029,865 wei more than the actual on-chain balance (7605789999997162970135 wei)

  • Fix: Mirror the existing deposit() pattern in withdraw(): read L2 balance, reject if below minAmount, clamp amount to Math.min(maxAmount, l2Liquidity). Extend L2BridgeEvmClient interface with getNativeCoinBalance() and getTokenBalance() (already inherited from EvmClient by all L2 clients)

  • Bug 2 (DfxDex / ERC20 general): Redundancy pipeline for Ethereum/DAI (rule 82) fails with Dai/insufficient-balance — stuck InProgress since 2026-04-02

  • Root cause: fromWeiAmount uses parseFloat() which loses precision for 18-decimal tokens. The roundtrip Wei → parseFloat(Number) → BigNumber.toFixed(18) → parseUnits → Wei can produce a value 1-2 wei higher than the actual balance

  • Fix: In EvmClient.sendToken(), read the actual on-chain balance via contract.balanceOf() before calling contract.transfer(), and cap targetAmount to the balance if it exceeds it

Related

Test plan

  • Verify TypeScript compiles cleanly
  • Verify rule 171 (Polygon/ZCHF) redundancy pipeline completes successfully after deploy
  • Verify deposit flow still works (no changes to deposit logic)
  • Verify other L2 bridge withdrawals (Base, Optimism, Arbitrum) are unaffected
  • Verify normal ERC20 transfers (amounts well below balance) are unaffected

Withdraw used order.maxAmount directly without verifying the actual
on-chain balance, causing reverts when float-to-wei rounding produced
amounts slightly exceeding the real token balance (e.g. ~2.8 gwei
difference on Polygon/ZCHF rule 171).

Mirror the existing deposit() pattern: read L2 balance, reject if
below minAmount, and clamp to Math.min(maxAmount, l2Liquidity).
@TaprootFreak TaprootFreak marked this pull request as ready for review April 4, 2026 08:49
Prevents transfer failures caused by Number↔Wei precision rounding.
When converting token amounts through parseFloat (fromWeiAmount) and
back to Wei (toWeiAmount), floating-point precision loss can produce
a Wei amount 1-2 wei higher than the actual balance, causing reverts
like Dai/insufficient-balance.
Order 118943 has been InProgress since 2026-04-02 because the on-chain
TX reverted with Dai/insufficient-balance but the completion check only
looks for Binance deposits and never detects reverted TXs.
Sets order + pipeline to Failed and rule 82 to Active.
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