Status DataClose notification

Overlayer Disclosed Report

_credit() arithmetic underflow on non-hub chains causes permanent loss of all cross-chain bridged tokens

Company
Created date
Mar 27 2026

Target

hidden

Vulnerability Details

The _credit() function in OverlayerWrapCore.sol (line 543) performs an
unconditional subtraction totalBridgedOut -= amountReceivedLD on every
chain where the contract is deployed. On non-hub chains, totalBridgedOut
is initialized to 0 and can never be incremented because:

(a) _debit() (the only increment path) requires tokens to exist on the
chain to burn them
(b) mint() is restricted to the hub chain via onlyHubChain modifier
(c) _credit() (the only way to receive tokens) always reverts due to
this underflow

This creates an unbreakable deadlock. When a user bridges tokens from the
hub chain via LayerZero OFT send():

  1. Hub chain: _debit() burns the user's tokens (irreversible)
  2. Destination chain: _credit() reverts with arithmetic underflow
    (Solidity 0.8.20 panic code 0x11)
  3. LayerZero message enters permanent retry loop (always fails)
  4. User's tokens are permanently lost — burned on hub, undeliverable
    on destination

The contract is not upgradeable and totalBridgedOut has no setter
function, so this cannot be fixed post-deployment.

Root cause:

commit f55efce (March 6, 2026) added totalBridgedOut tracking
to fix a backing contract underflow, but applied the _credit decrement on
ALL chains instead of only the hub chain where it's needed.

Validation steps

Step 1: Clone the repo at the in-scope commit
git clone https://github.com/Overlayerfi/contracts.git
cd contracts
git checkout 519c9e92fd9d80d11e35e9868130f6334b88d676

Step 2: Install dependencies
npm install --force

Step 3: Add the mock endpoint contract
Create contracts/test/MockLzEndpoint.sol with:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;                                                                
contract MockLzEndpoint {                               
    mapping(address => address) public delegates;
    function setDelegate(address _delegate) external {                                  
        delegates[msg.sender] = _delegate;
    }                                                                                   
    function eid() external pure returns (uint32) {     
        return 30101;                                                                   
    }
}                                                                                       

Step 4: Create hardhat.config.poc.ts (no fork needed)
import { HardhatUserConfig } from "hardhat/config"; import "@nomicfoundation/hardhat-toolbox";
import "@nomicfoundation/hardhat-ethers";
import "@nomicfoundation/hardhat-chai-matchers"; const config: HardhatUserConfig = {
solidity: { compilers: [
{ version: "0.8.20", settings: { optimizer: { enabled: true, runs: 300 } } }, { version: "0.8.10", settings: { optimizer: { enabled: true, runs: 999 } } },
{ version: "0.7.6", settings: { optimizer: { enabled: true, runs: 1000000 }, metadata: { bytecodeHash: "none" } } }
]},
networks: { hardhat: { allowUnlimitedContractSize: true, chainId: 31337 } },
};
export default config;

Step 5: Add the PoC test file test/CreditUnderflow.ts
(attached as file — 3 tests demonstrating underflow, fund loss, and deadlock)

Step 6: Compile and run
npx hardhat compile --config hardhat.config.poc.ts
npx hardhat test test/CreditUnderflow.ts --config hardhat.config.poc.ts

Step 7: Verify output — all 3 tests pass:
✔ _credit reverts with arithmetic underflow on non-hub chain (totalBridgedOut == 0)
✔ _debit works on hub chain but tokens are lost because _credit always fails on destination
✔ non-hub chain can NEVER have totalBridgedOut > 0 —
chicken-and-egg deadlock

Step 8: Verify the root cause at OverlayerWrapCore.sol:543
totalBridgedOut -= amountReceivedLD;
This line has no hub chain guard. On non-hub chains where
totalBridgedOut == 0, any positive amountReceivedLD causes
arithmetic underflow (panic 0x11).

Attachments

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