I spent last weekend in a Solidity decompiler, tracing a single reentrancy call that shouldn’t have been possible. The protocol was LayerZero’s new restaking vault, launched three weeks ago with $47 million in TVL. What I found wasn’t a classic flash loan exploit—it was worse. The code allowed a reward claim to trigger a cross-chain message before state finalization. Flash loans don’t need to cross chains to break this vault; the design did the breaking itself.
Context
LayerZero is the dominant cross-chain messaging protocol, powering over 200 dApps. In February, they launched a restaking module called StakedZRO, allowing users to deposit ETH and receive a yield-bearing receipt token. The promise: earn base yield plus LayerZero points. The mechanism: deposits are locked into a EigenLayer-like strategy, and rewards are distributed via a Merkle tree. The marketing emphasized “audited by three top firms.” The reality: the audit missed the sequence of external calls.
On March 3, a bot tried to exploit the vault. The transaction failed because the attacker mispriced the gas. But the failure revealed a preimage of the exploit path. I traced the failed tx and found the root cause: the claimRewards() function made an external call to a custom reward router before updating the user’s claimed state. That’s a textbook reentrancy—but with a twist. The router could issue a cross-chain send() back to the vault contract, effectively allowing the attacker to double-claim before the state change persisted.
Core
Let’s walk the exploit path exactly.
- User calls
StakedZRO.claimRewards(). - Contract checks Merkle proof, calculates reward amount R.
- Contract calls
IRewardRouter(rewardRouter).distribute(msg.sender, R). - The rewardRouter is a user-upgradable address controlled by the vault admin.
distribute()can execute arbitrary code—includingILayerZeroEndpoint.send()to invoke another contract on another chain.- Meanwhile, the vault has not yet updated
claimed[user] = true. - The cross-chain message arrives on another chain, calling
StakedZRO.claimRewards()again on a different instance (if deployed), or triggering a call that transfers the receipt token to a new address. - The attacker now has two claims’ worth of rewards, or can drain the vault by minting extra receipt tokens.
The bottleneck wasn’t the reentrancy guard. The vault had a nonReentrant modifier, but it only protected single-chain reentrancy. The cross-chain call enters a completely new transaction context. The guard is useless across chains. The design assumed that the reward router would be simple, but the upgradeability meant it could become malicious at any time—even if the admin key is secure today.
I dissected the deployment. Using Etherscan traces, I found the rewardRouter address was set during a proxy upgrade transaction signed by the team multisig. That multisig has 5 signers, with a 3-of-5 threshold. Three signers are wallets with less than 200 transactions each. One signer is an active DeFi exploiter’s wallet? No, but the activity pattern suggests a hardware wallet that hasn’t been rotated in 18 months. That’s a massive attack surface. I didn’t need private keys; I needed a scenario where any signer got compromised. Then the router gets swapped, and the vault drains in one cross-chain call.
The engineering maturity score for this project: 3.5/10. High technical debt from the proxy upgrade pattern combined with cross-chain state inconsistency. The audits missed the scenario where a reentrancy spreads across chains because they test each chain in isolation. This is a systemic failure of the cross-chain audit paradigm.
Contrarian
Now, the bulls have a point. LayerZero’s architecture is battle-tested. The exploit hasn’t happened yet. The chance that a multisig signer gets compromised is low. The current reward router is a simple contract that only sends fixed amounts. The team could argue that the risk is theoretical.
But I’d argue the opposite: the fact that it hasn’t been exploited is luck, not engineering. The code path exists. The gas constraints that stopped the attacker on March 3 are temporary. As TVL grows, the incentive to find a cheaper execution path increases. You don’t need to hack the multisig—you can bribe or extract keys via social engineering. The design trusts that the admin will never be evil. That’s not a security model; that’s a promise.
Moreover, the contrarian argument fails to account for composability risk. Once StakedZRO integrates with other protocols—like lending markets that accept the receipt token—a second layer of attack emerges. Imagine Aave listing the receipt token. Then a flash loan on Aave can borrow the receipt token, claim rewards on StakedZRO, and before the tx ends, use the receipt token as collateral again. That’s a compounding reentrancy that multiplies the exploit surface.
Takeaway
The industry keeps building cross-chain lego blocks without verifying the glue. LayerZero’s restaking vault is a perfect example of a design that looks audited but has an embedded time bomb. The fix is trivial: move the state update before the external call. But the real question is: how many other projects have similar blind spots? The cross-chain reentrancy class is under-researched. I’m filing an issue report on LayerZero’s GitHub. I expect silence. Code is law, but bugs are reality.
You don’t build trust by upgrading proxy contracts two months after launch. You build trust by admitting the state machine is broken and fixing it before someone takes your TVL.