The _credit override in OverlayerWrapCore.sol causes an arithmetic underflow on spoke chains, completely blocking cross-chain token bridging via LayerZero OFT and permanently losing bridged tokens.
OverlayerWrapCore._credit() (line 543) unconditionally decrements totalBridgedOut:
function _credit(
address to_, uint256 amountLD_, uint32 srcEid_
) internal virtual override returns (uint256 amountReceivedLD) {
amountReceivedLD = super._credit(to_, amountLD_, srcEid_);
totalBridgedOut -= amountReceivedLD; // L543: UNDERFLOW ON SPOKE
}
The LayerZero OFT flow is:
_debit() burns tokens + totalBridgedOut += amountSentLD ✓_credit() should mint tokens + totalBridgedOut -= amountReceivedLD ✗ REVERTSOn spoke chains, totalBridgedOut is initialized to 0 (Solidity default). The first operation is _credit (receiving tokens from hub). 0 - amountReceivedLD causes arithmetic underflow in Solidity 0.8.20 checked math, reverting the transaction.
The subtraction has no conditional guard — no if (block.chainid == hubChainId) or if (totalBridgedOut >= amountReceivedLD) check exists.
OverlayerWrapCore inherits OFT from LayerZero (line 10, 27)hubChainId parameter is separate from block.chainid, indicating deployment on chains where block.chainid != hubChainIdonlyHubChain modifier restricts mint/redeem to hub but allows OFT operations on spokeeth_sepolia and arbitrum_sepolia endpoints_debit says "Tracks tokens leaving the hub chain", _credit says "Tracks tokens returning to the hub chain"contracts/overlayer/OverlayerWrapCore.sol — _credit() lines 537-544, _debit() lines 510-528totalBridgedOut = 0 (fresh deployment, no prior _debit)send() on hub chain to bridge tokens to spoke_debit() burns tokens and increments totalBridgedOut — succeeds_lzReceive() → _credit()_credit() executes totalBridgedOut -= amountReceivedLD → 0 - amount → arithmetic underflow → revertFAILED state — retry produces the same revert (deterministic)_debit (no balance) and can't _credit (underflow)function _credit(
address to_, uint256 amountLD_, uint32 srcEid_
) internal virtual override returns (uint256 amountReceivedLD) {
amountReceivedLD = super._credit(to_, amountLD_, srcEid_);
// Only decrement on hub chain, where _debit incremented
if (block.chainid == hubChainId) {
totalBridgedOut -= amountReceivedLD;
}
}
git clone https://github.com/Overlayerfi/contracts at commit 519c9e92contracts/overlayer/OverlayerWrapCore.sol:
totalBridgedOut -= amountReceivedLD in _credit() with no conditional guardtotalBridgedOut += amountSentLD in _debit()if (block.chainid == hubChainId) or if (totalBridgedOut >= amountReceivedLD) checkOverlayerWrapCore inherits OFT (LayerZero)hubChainId is a constructor parameter separate from block.chainidonlyHubChain modifier exists — proves spoke deployments are expectedeth_sepolia + arbitrum_sepolia endpoints configuredtotalBridgedOut = 0_credit() call → 0 - amountReceivedLD → underflow in Solidity 0.8.20 → revert_lzReceive reverts, message enters FAILED state; retry produces the same revert (deterministic)forge test --match-contract C02_TotalBridgedOutUnderflow -vvv — all tests pass confirming the underflowSee attached C02_TotalBridgedOutUnderflow.t.sol