OverlayerWrapCore tracks OFT bridge flow using totalBridgedOut.
The source-side _debit() increments it, while destination-side _credit() decrements it.
Because each chain has isolated storage, a destination chain can receive its first inbound transfer with totalBridgedOut == 0. In that case, _credit() executes:
totalBridgedOut -= amountReceivedLDwhich underflows and reverts in Solidity ^0.8.x, reverting the destination credit execution.
In OFT flow, source-side debit burns tokens and destination-side credit mints tokens. When destination execution reverts deterministically, the message remains retryable but keeps failing until contract logic changes. This makes the issue a funds-stranding condition, not only a transient availability failure.
contracts/overlayer/OverlayerWrapCore.sol
_debit(...) increments totalBridgedOut_credit(...) decrements totalBridgedOutThe design assumes a single coherent totalBridgedOut counter, but it is actually per-chain state:
_debit() updates Chain A storage_credit() updates Chain B storageThese are not the same slot/state.
The system is explicitly intended to run on non-hub chains:
_initialize(...) has hub/non-hub branch behavior (hubChainId != block.chainid path exists)onlyHubChain(block.chainid))A fresh satellite deployment therefore starts with:
totalBridgedOut == 0and has no local prerequisite requiring a prior _debit() before receiving inbound credits.
_debit() burn path)._credit() deterministically reverts on totalBridgedOut == 0 state.This is a direct user-fund impact path (effective loss/lock) on a core protocol flow, so Critical is justified.
Apply one of:
_credit(), decrement by min(totalBridgedOut, amountReceivedLD), never underflow.OverlayerWrap/OverlayerWrapCore are constructor-based and there is no in-contract recovery hook for this accounting path, which strengthens permanence risk on deployed instances.A reproducible Hardhat PoC is provided at:
OverlayerWrap_TotalBridgedOut_Underflow.poc.tsIt deploys a non-hub instance and directly exercises test-exposed _credit() before any local _debit(), demonstrating revert caused by arithmetic underflow.
OverlayerWrapMock with hubChainId != block.chainid.totalBridgedOut == 0.testCredit(receiver, amount, srcEid) with amount > 0.