Status DataClose notification

Overlayer Disclosed Report

Underflow in _credit() Permanently Blocks Cross-Chain Token Transfers on Satellite OverlayerWrap Deployments

Company
Created date
Apr 03 2026

Target

hidden

Vulnerability Details

We identified that the _credit() override in OverlayerWrapCore.sol (line 545) unconditionally decrements the totalBridgedOut state variable whenever tokens arrive via a LayerZero OFT cross-chain message:

File: contracts/overlayer/OverlayerWrapCore.sol Lines: 540-548

function _credit(
    address to_,
    uint256 amountLD_,
    uint32 srcEid_
) internal virtual override returns (uint256 amountReceivedLD) {
    amountReceivedLD = super._credit(to_, amountLD_, srcEid_);
    totalBridgedOut -= amountReceivedLD;   // ← UNDERFLOW HERE
}

On satellite (non-hub) chains, totalBridgedOut is initialized to 0 and can never be incremented because:

  1. _debit() (the only function that increments totalBridgedOut, line 528) requires tokens to already exist on the chain to send them outbound. But no tokens can ever exist on a satellite chain because...

  2. _managerMint() (line 403) is gated by onlyHubChain(block.chainid), preventing any minting on satellite chains.

  3. There is no setter, initializer, or admin function to set totalBridgedOut to any starting value.

This creates a permanent deadlock:

  • Cannot mint locally → no tokens exist
  • No tokens → cannot _debit() → totalBridgedOut stays 0
  • Any inbound _credit() does 0 - positiveAmount → arithmetic underflow
  • Solidity 0.8.x checked math reverts the transaction

HOW TO REPRODUCE:

Step 1: Deploy OverlayerWrap on Ethereum mainnet (hub) with hubChainId = 1. Step 2: Deploy OverlayerWrap on Arbitrum (satellite) with hubChainId = 1. Step 3: Configure LayerZero OFT peers between both deployments. Step 4: On Ethereum, a user mints 1,000 USDT+ by depositing 1,000 USDT. Step 5: User calls send() (LayerZero OFT) to bridge 100 USDT+ to Arbitrum. Step 6: On Ethereum: _debit() executes successfully. - Burns 100 USDT+ from user. - totalBridgedOut += 100 → totalBridgedOut = 100. ✓ Step 7: LayerZero relays the cross-chain message to Arbitrum. Step 8: On Arbitrum: _credit() is invoked by LayerZero endpoint. - super._credit() mints 100 USDT+ to user. ✓ - totalBridgedOut -= 100 → uint256(0) - 100 → UNDERFLOW REVERT ✗ Step 9: Entire receive transaction reverts on Arbitrum. Step 10: 100 USDT+ were burned on Ethereum but never minted on Arbitrum. Tokens are permanently stuck. Cross-chain is broken.

Validation steps

Your team can validate this finding by following these steps: • Open: contracts/overlayer/OverlayerWrapCore.sol • Go to line 540 (the _credit function) • URL: https://github.com/Overlayerfi/contracts/blob/519c9e92fd9d80d11e35e9868130f6334b88d676/contracts/overlayer/OverlayerWrapCore.sol#L540-L548 • Observe: totalBridgedOut -= amountReceivedLD executes unconditionally with no if (block.chainid == hubChainId) guard.

STEP 2 — Confirm totalBridgedOut is initialized to 0 with no setter • In the same file, line 66: uint256 public totalBridgedOut; • Search the entire file for any function that sets totalBridgedOut. • Result: Only _debit() (line 528) increments it. Only _credit() (line 547) decrements it. There is no initializer, setter, or admin override.

STEP 3 — Confirm minting is hub-chain-only • In the same file, line 403: _managerMint() has modifier onlyHubChain(block.chainid). • URL: https://github.com/Overlayerfi/contracts/blob/519c9e92fd9d80d11e35e9868130f6334b88d676/contracts/overlayer/OverlayerWrapCore.sol#L398-L413 • The modifier (line 78-82) reverts if block.chainid != hubChainId. • Therefore: on any satellite chain, no tokens can be minted.

STEP 4 — Trace the deadlock • Satellite chain state: totalBridgedOut = 0, totalSupply = 0. • No minting possible → no tokens to _debit() → totalBridgedOut stays 0. • Any _credit() call: 0 - amount → underflow → revert. • Deadlock confirmed.

STEP 5 — Confirm with Solidity behavior • Solidity 0.8.20 uses checked arithmetic by default. • uint256(0) - uint256(100) reverts with panic code 0x11 (arithmetic overflow/underflow). • The subtraction is NOT inside an unchecked {} block.

UPLOADED FILE: C01_CreditUnderflow_PoC.sol

This Foundry PoC test demonstrates the integer underflow on satellite chains. Your team can place it in your test/ directory and run:

forge test --match-contract CreditUnderflowPoC -vvv

The test contains:

  • test_creditUnderflowOnSatelliteChain(): Proves 0 - 100e18 underflows
  • test_creditUnderflowEvenForOneWei(): Proves even 1 wei triggers revert
  • test_creditWorksOnHubChainWithPriorDebit(): Shows hub chain works correctly

Key source code references:

_credit() with unconditional decrement: https://github.com/Overlayerfi/contracts/blob/519c9e92fd9d80d11e35e9868130f6334b88d676/contracts/overlayer/OverlayerWrapCore.sol#L540-L548

_debit() with unconditional increment: https://github.com/Overlayerfi/contracts/blob/519c9e92fd9d80d11e35e9868130f6334b88d676/contracts/overlayer/OverlayerWrapCore.sol#L510-L529

totalBridgedOut declaration (no initializer): https://github.com/Overlayerfi/contracts/blob/519c9e92fd9d80d11e35e9868130f6334b88d676/contracts/overlayer/OverlayerWrapCore.sol#L66

onlyHubChain modifier blocking satellite minting: https://github.com/Overlayerfi/contracts/blob/519c9e92fd9d80d11e35e9868130f6334b88d676/contracts/overlayer/OverlayerWrapCore.sol#L78-L82

Attachments

hidden
CommentsReport History
Comments on this report are hidden
Details
Statedisclosed
Severity
Critical
Bounty$69
Visibilitypartially
VulnerabilityInteger Underflow
Participants
hidden