OverlayerWrapCore is deployed on multiple chains, one hub chain, and one or more non-hub chains, forming a cross-chain OFT system via LayerZero. The contract tracks cross-chain token movements in a state variable called totalBridgedOut. This variable is incremented on the source chain when tokens leave (_debit) and decremented on the destination chain when tokens arrive (_credit).
The bug is that _credit() unconditionally decrements totalBridgedOut on any chain it runs on, including non-hub chains where totalBridgedOut is always 0. Since Solidity ^0.8.20 enables checked arithmetic by default, the subtraction of 0, amount triggers an arithmetic underflow panic, causing the entire LayerZero delivery transaction to revert. The tokens have already been burned on the source chain. They will never be minted on the destination. They are gone.
totalBridgedOut is a variable that only makes sense on the hub chain. Its entire purpose, as documented in both the Natspec comments and the AaveHandler.sol integration, is to let the Aave backing contract account for tokens currently in transit across chains:
// AaveHandler.sol:239–241
// Add totalBridgedOut to compensate for OFT cross-chain burns that reduce local totalSupply.
uint256 owTotalSupp = IOverlayerWrap(overlayerWrap).totalSupply() +
IOverlayerWrap(overlayerWrap).totalBridgedOut();
This accounting only happens on the hub chain, where AaveHandler lives. Despite this, both _debit() and _credit() mutate totalBridgedOut without any chain-ID guard:
// OverlayerWrapCore.sol:510–528
function _debit(...) internal virtual override returns (...) {
(amountSentLD, amountReceivedLD) = super._debit(...);
totalBridgedOut += amountSentLD; // ← no hub-chain guard
}
// OverlayerWrapCore.sol:537–544
function _credit(...) internal virtual override returns (uint256 amountReceivedLD) {
amountReceivedLD = super._credit(to_, amountLD_, srcEid_);
totalBridgedOut -= amountReceivedLD; // ← no hub-chain guard; underflows on non-hub chain
}
The onlyHubChain modifier already exists in this contract at line:77–82 precisely to prevent logic from running on the wrong chain. It was simply never applied here.
This is the vulnerable Code
// OverlayerWrapCore.sol — Line 537
function _credit(
address to_,
uint256 amountLD_,
uint32 srcEid_
) internal virtual override returns (uint256 amountReceivedLD) {
amountReceivedLD = super._credit(to_, amountLD_, srcEid_);
totalBridgedOut -= amountReceivedLD; // @audit UNDERFLOW on non-hub chain
}
On a non-hub chain deployment, totalBridgedOut is initialized to 0 and is never incremented by _debit() (bridging out of the hub happens on the hub chain, not here). The first time a user bridges tokens into the non-hub chain, _credit() is invoked by LayerZero with amountReceivedLD > 0. The subtraction becomes 0 - N, which panics under Solidity 0.8.x checked arithmetic.
Severity: Critical direct, unrecoverable loss of user funds.
Affects every user who bridges tokens from the hub chain to any non-hub chain deployment. This is a core protocol operation, not an edge case.
No privileged role, no special setup, no attacker needed. Any bridge transaction triggers the permanent freeze.
LayerZero's retry mechanism provides no relief because the revert is deterministic; the same input produces the same underflow every time.
The total BridgedOut inflation on the hub chain from the orphaned _debit() also silently corrupts AaveHandler's collateral accounting (AaveHandler.sol:240–241), causing the backing contract to overestimate effective supply and potentially under-collateralize the protocol over time.