Reproduced Exploit
Decent — Missing min-gas checks permanently block the LayerZero channel
1. Adapter gas is 100_000 + _dstGasForCall with no floor on the user parameter. 2. A malicious or mistaken user can pass ~1000 → destination OOGs / fails. 3. LayerZero treats uncaught failures as STORED, blocking the entire path until cleared. 4. HARM in the PoC: underfunded bridge STORES the path;…
Chain
Other
Category
dos
Date
Jan 2024
Source
AuditVault
EVM Playground
Source-level debugger — step opcodes and Solidity in sync
The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Balances (native / ERC-20 / NFT), Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.
Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 30560-h-02-due-to-missing-checks-on-minimum-gas-passed-through-lay. Standalone Foundry PoC and full write-up: 30560-h-02-due-to-missing-checks-on-minimum-gas-passed-through-lay_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/dos/unbounded-loop · impact/dos · genome/unbounded-loop · dos-resistance
Reproduction: a self-contained Foundry PoC that compiles & runs in an isolated project with only
forge-std— no fork, no RPC, noanvil_state. Full trace: output.txt. PoC: test/30560-h-02-due-to-missing-checks-on-minimum-gas-passed-through-lay_exp.sol.
AuditVault taxonomy: lang/solidity · platform/code4rena · has/github · has/poc · severity/high · sector/bridge · vuln/dos/unbounded-loop · novelty/known-pattern · genome: unbounded-loop · known-pattern · permanent · dos-resistance
Key info#
| Impact | HIGH — underfunded _dstGasForCall causes destination failure; LayerZero STORES the message and permanently blocks the source→dest channel for all future messages |
| Protocol | Decent — DecentEthRouter LayerZero OFT bridge |
| Vulnerable code | DecentEthRouter._getCallParams — gasAmount = GAS_FOR_RELAY + _dstGasForCall with no minimum |
| Bug class | Missing input validation on cross-chain gas budget |
| Finding | Code4rena — Decent, 2024-01 · #30560 (H-02) · reporter iamandreiski |
| Report | 2024-01-decent |
| Source | AuditVault |
| Status | Audit finding — acknowledged; team moved to LZ v2. Reproduced as local synthetic with mock LZ STORED semantics. |
| Compiler | ^0.8.24 (PoC) |
TL;DR#
- Adapter gas is
100_000 + _dstGasForCallwith no floor on the user parameter. - A malicious or mistaken user can pass ~1000 → destination OOGs / fails.
- LayerZero treats uncaught failures as STORED, blocking the entire path until cleared.
- HARM in the PoC: underfunded bridge STORES the path; a later well-funded honest message cannot deliver.
The vulnerable code#
From decentxyz/decent-bridge@7f90fd4 src/DecentEthRouter.sol:
uint256 GAS_FOR_RELAY = 100000;
gasAmount = GAS_FOR_RELAY + _dstGasForCall; // @> VULN: no minimum on _dstGasForCall
adapterParams = abi.encodePacked(PT_SEND_AND_CALL, gasAmount);
Fix (judge): enforce a minimum at the source transaction level and a maximum gas consumed at the executor so a failure can still be stored without OOG'ing the store path.
Root cause#
Cross-chain gas is a safety-critical parameter left to the caller. LayerZero's path-level STORED semantics turn a single underfunded message into a permanent channel DoS — not a one-off failed transfer.
Preconditions#
- User (or attacker) can call
bridge/bridgeWithPayloadwith arbitrary_dstGasForCall. - Destination work needs more gas than
100_000 + underfunded(especially Arbitrum / payload calls).
Attack walkthrough#
- Attacker bridges with
_dstGasForCall = 1000→ total gas 101_000. - Destination app requires ≥150k for its work → execution fails.
- Mock LZ marks path STORED (mirrors LZ INFLIGHT→STORED on uncaught errors).
- Subsequent honest, well-funded bridge on the same path is blocked.
Diagrams#
Remediation#
uint256 GAS_FOR_RELAY = 100000;
+ require(_dstGasForCall >= MIN_DST_GAS_FOR_CALL, "dst gas too low");
gasAmount = GAS_FOR_RELAY + _dstGasForCall;
// plus executor-side max gas so failures remain storable
How to reproduce#
cd ~/RustroverProjects/audits/evm-hack-registry/30560-h-02-due-to-missing-checks-on-minimum-gas-passed-through-lay_exp
forge test -vvv
# Fully local -- mock LZ STORED channel, no fork.
Sources#
- AuditVault finding: 30560-h-02-due-to-missing-checks-on-minimum-gas-passed-through-lay.md
- Code4rena report: 2024-01-decent
- Vulnerable repo: decentxyz/decent-bridge@7f90fd4 —
_getCallParams - LayerZero messaging properties: STORED blocks subsequent messages on the path
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 30560-h-02-due-to-missing-checks-on-minimum-gas-passed-through-lay_exp (evm-hack-registry mirror).
- AuditVault finding: 30560-h-02-due-to-missing-checks-on-minimum-gas-passed-through-lay.
Alerts & third-party analyses
These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.