Status DataClose notification

Overlayer Disclosed Report

Critical: _credit() underflow reverts hub receive path and can permanently lock bridged funds

Company
Created date
Apr 10 2026

Target

hidden

Vulnerability Details

OverlayerWrapCore._credit() unconditionally decrements totalBridgedOut on every inbound OFT receive on the hub chain.

Relevant logic: amountReceivedLD = super.credit(to, amountLD_, srcEid_); totalBridgedOut -= amountReceivedLD;

This assumes every spoke→hub inbound amount was previously accounted for by a corresponding hub-side _debit(). That assumption breaks once OVA circulates on spoke chains as a standard ERC20.

Because OverlayerWrap is an OFT / ERC20 on spoke chains, users can hold spoke-chain OVA without personally causing the corresponding hub-side _debit() for that exact balance source. On spoke chains, OVA can be transferred between users after bridge distribution, and holders can later attempt to bridge those tokens back to the hub.

When such a user bridges spoke→hub, _credit() on the hub attempts to subtract from totalBridgedOut even if the hub counter is already zero or smaller than the inbound amount.

In Solidity 0.8, this underflows and reverts.

Because _credit() is invoked inside the LayerZero receive path, the revert causes the inbound message delivery to fail. The token has already been burned on the source chain by OFT _debit(), so the user cannot recover the funds on the source side. Retrying the message re-executes the same reverting logic, so the failure persists unless state is manually repaired. There is no direct admin function in scope to repair totalBridgedOut.

As a result, this bug can permanently lock user funds.

Validation steps

Affected code

  • contracts/overlayer/OverlayerWrapCore.sol
  • _debit() lines 510–528
  • _credit() lines 537–544

Validation logic

  1. On the hub chain, _debit() increases totalBridgedOut when OVA is bridged out.

    Relevant line: totalBridgedOut += amountSentLD;

  2. On the hub chain, _credit() decreases totalBridgedOut for every inbound OFT message.

    Relevant line: totalBridgedOut -= amountReceivedLD;

  3. OverlayerWrap is an OFT and ERC20. On spoke chains, users cannot mint through hub-only mint paths, but they can hold and transfer OVA already present on that spoke.

  4. Therefore, a user can bridge spoke→hub an amount that is greater than the current hub totalBridgedOut value.

  5. When that happens, _credit() executes:

    totalBridgedOut -= amountReceivedLD;
    

    and reverts with arithmetic underflow.

  6. _credit() is called inside the LayerZero receive path, so the inbound delivery fails.

  7. The source-side OFT _debit() already burned the user's spoke tokens before message delivery, so the user’s funds are stuck.

  8. Retrying the failed message calls the same reverting _credit() path again, so the delivery remains blocked.

Minimal reproduction scenario

  1. Alice bridges OVA from hub to a spoke chain. This increases hub totalBridgedOut.

  2. Alice later returns the full bridged amount to the hub, bringing hub totalBridgedOut back to zero.

  3. Bob holds OVA on the spoke chain from secondary circulation after that initial distribution.

  4. Bob bridges his spoke-chain OVA back to the hub.

  5. The spoke-side OFT _debit() burns Bob’s tokens.

  6. The hub-side _credit() executes and reaches:

    totalBridgedOut -= amountReceivedLD;
    
  7. Because hub totalBridgedOut == 0 and amountReceivedLD > 0, the subtraction underflows and reverts.

  8. The inbound LayerZero message fails and remains undeliverable on retry unless state is manually repaired.

Expected result

Inbound spoke→hub bridge transfers should complete successfully even when the returning holder is not the same wallet that originally initiated the outbound bridge.

Actual result

The hub receive path reverts due to arithmetic underflow, causing the bridge return to fail and potentially permanently locking the user’s funds.

Attachments

hidden
CommentsReport History
Comments on this report are hidden
Details
Statedisclosed
Severity
Critical
Bounty$69
Visibilitypartially
VulnerabilityDoS with (Unexpected) revert
Participants
hidden