Reproduced Exploit
Hinkal: Merkle insert never stores the top root once the tree is half-full, locking every deposit
Chain
Other
Category
untagged
Date
Jan 1970
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: 60149-all-funds-become-irredeemable-when-the-tree-is-halfway-popul. The historical source/toolchain is unavailable; this entry is documentation only and claims no executable Forge PoC.
Vulnerability classes: vuln/state-update-omission · vuln/permanent-fund-lock · vuln/merkle-tree
Reproduction: a faithful minimal reproduction of the vulnerable finding — the vulnerable function is reproduced verbatim (marked
@>) with faithful minimal doubles; local deploy, no fork.
Root cause#
The insert loop rebuilds the path into the local prevHash but only writes lower nodes to storage; at the top level the if (i != twoPower) guard skips the final write, so the computed root is never stored to tree[twoPower]. Once the tree is more than half populated the published root stays 0, no membership proof matches, every commitment becomes un-nullifiable, and all deposits are permanently locked.
tree[i] = prevHash; // Left side - value stored
if (i != twoPower) prevHash = hash(prevHash, 0);
} else {
prevHash = hash(tree[i], prevHash); // Right side - value cached // @> top root only cached, NEVER written to tree[twoPower]
}
Why it's exploitable here#
- The root is computed only into a local (
prevHash) and, at the top level, theif (i != twoPower)guard skips the final storage write. - A zero published root means no valid membership/nullifier proof can ever be produced.
- Every depositor is affected at once — this is a protocol-wide, permanent lock, not a per-user edge case.
Attack path#
Marked-line walkthrough (Playground)#
The EVM Playground pins each step to the exact executed source line in MiniMerkleBuggy:
- Line 52 — the loop rebuilds the path from the new leaf up toward the root.
- Line 53 — the node index is even, so the left-side path is rebuilt.
- Line 55 — VULN.
if (i != twoPower)skips the final write for the top level, so the computed root in prevHash is never written to tree[twoPower]; past halfway the stored root stays 0 → commitments un-nullifiable → funds locked.
PoC#
Registry (Foundry, local deploy — exploit path + a fixed-variant control):
cd 60149-all-funds-become-irredeemable-when-the-tree-is-halfway-pop_exp
forge test -vv
Expected: both tests PASS — the exploit test asserts the published root is 0 past halfway and 10 ETH is recorded locked; the fixed control stores the root so proofs still match. The browser EVM Playground is served at /hacks/60149-all-funds-become-irredeemable-when-the-tree-is-halfway-pop/.
Remediation#
Persist the computed top root to storage on every insert (write it to tree[twoPower]), not only the lower nodes.
References#
- AuditVault finding: https://github.com/Auditware/AuditVault/blob/main/findings/60149-all-funds-become-irredeemable-when-the-tree-is-halfway-popul.md
Sources & further analysis#
Reproductions & code
- No executable Forge reproduction is claimed; the historical source/toolchain was unavailable for this finding.
- AuditVault finding: 60149-all-funds-become-irredeemable-when-the-tree-is-halfway-popul.
- Upstream DeFiHackLabs PoC directory: src/test.
Alerts & third-party analyses
- DeFiHackLabs incident explorer: search "Hinkal: Merkle insert never stores the top root once the tree is half-full, locking every deposit".
- Web3Sec X hacked database: search.
- Rekt leaderboard: search.
- Solodit incident search: search.
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.