PoCs & detectors

Every detector is backed by a runnable proof. Model PoCs live in poc/ as a minimal Vulnerable<X> + Safe<X> + exploit test — the exploit profits / breaks the invariant on the vulnerable contract, and the same test shows the fix holds. Real-incident replays live in sim/. Run all: cd poc && forge test.

  1. erc4626-inflation
  2. read-only-reentrancy
  3. balancer-v2-rounding
  4. cashio-infinite-mint
  5. cetus-amm-overflow
  6. loopscale-oracle-spot-price
  7. loopscale-ratex-cpi
  8. mango-oracle-manipulation
  9. beanstalk-governance-flashloan
  10. rhea-finance-slippage
  11. trustedvolumes-access-control
  12. verus-bridge-merkle-forgery
  13. thorchain-tss-gg20-key-extraction
  14. ekubo-callback-approval-drain
  15. kelp-dao-layerzero-dvn-1-1
  16. ctoken-empty-market-exchange-rate
  17. approval-drain-arbitrary-call
  18. proxy-storage-collision
  19. signature-replay-malleability
  20. unprotected-privileged-fn
  21. insecure-randomness
  22. weird-erc20-accounting
  23. incorrect-reward-accounting
  24. unverified-flashloan-callback
  25. bridge-deposit-no-code-token
  26. first-deposit-amm-skim
  27. cei-reentrancy
  28. meta-tx-msgsender-spoof
  29. calldata-abi-smuggling
  30. forced-ether-balance-assumption
  31. dos-griefing-revert
  32. ecdsa-nonce-reuse-key-extraction
  33. tac-bridge-jetton-impersonation
  34. zeta-chain-gatewayevm
  35. ato-hook-storage-slot-collision

erc4626-inflation

Class SC07/SC02 · Chains evm · Status coded

A vault that prices shares off asset.balanceOf(this) and mints the first depositor 1:1 with no virtual offset lets that depositor donate tokens to inflate price-per-share, so a later depositor’s round-down share math mints them fewer shares than fair; the attacker redeems the remainder.

Invariant: shares are priced fairly; no external donation can shift price-per-share between depositors

cd poc && forge test --match-contract InflationAttack -vv

PoC test · case study


read-only-reentrancy

Class SC08 · Chains evm · Status coded

A pool updates supply before an external call but updates its reserve after, so during the callback a VIEW function returns an inconsistent (inflated) price. A consumer that reads that view as a collateral oracle during the callback lets the attacker over-borrow. nonReentrant on state-changing functions does NOT protect the view.

Invariant: a pool’s price view is never read while the pool’s internal state is mid-update

cd poc && forge test --match-contract ReadOnlyReentrancy -vv

PoC test · case study


balancer-v2-rounding

Class SC07 · Chains evm/multi · Status coded · Loss $128,000,000

_upscale() always rounds down (mulDown) but the paired downscaling uses directional rounding; _swapGivenOut rounds the requested output down, then underestimates the required input. 65 micro-swaps (8-19 wei) in one batchSwap compounded the error, deflating the pool invariant D and the BPT price, which the attacker then redeemed at full value across 6 chains.

Invariant: the pool invariant D never decreases due to a round-trip swap; BPT price = D / totalSupply holds

cd poc && forge test --match-contract BalancerRounding -vv

PoC test · case study


cashio-infinite-mint

Class SC05/SC02 · Chains solana · Status coded · Loss $52,800,000

The brrr program validated that accounts matched each other but never validated the mint field on the Saber Arrow account — so an attacker built a parallel tree of fake accounts (fake bank -> fake swap -> fake LP) that passed every relative check, minting 2B CASH backed by worthless collateral. No trusted root: validation chains that never anchor to a known mint/program are meaningless.

Invariant: every minted unit of stablecoin is backed by collateral whose mint is a known, trusted token

cd poc && forge test --match-contract CashioInfiniteMint -vv

PoC test · case study


cetus-amm-overflow

Class SC07/SC09 · Chains sui-move · Status coded · Loss $223,000,000

A single wrong constant in checked_shlw — mask 0xffff..f « 192 instead of 1 « 192 — let values in [2^192, 2^256-2^192) pass an overflow check. A crafted liquidity (~2^113) over a narrow tick range made an intermediate shift silently truncate (Move shifts are modular, unlike +/*/-), collapsing the token-amount to 1. Deposit 1 token, get a huge position, drain ~$223M.

Invariant: no input within type range causes an unchecked silent truncation in liquidity math

cd poc && forge test --match-contract CetusOverflow -vv

PoC test · case study


loopscale-oracle-spot-price

Class SC03/SC02 · Chains solana · Status coded · Loss $5,800,000

Loopscale valued RateX PT collateral from a single liquidity-pool spot price with no TWAP, no multi-source aggregation, no staleness check, and no flash-loan-aware validation. Flash loans skewed the thin pool, the protocol mispriced collateral, and the attacker drained $5.8M (returned via whitehat bounty).

Invariant: reported collateral price cannot be moved materially within a single transaction

cd poc && forge test --match-contract LoopscaleOracle -vv

PoC test · case study


loopscale-ratex-cpi

Class SC03/SC02/SC05 · Chains solana · Status coded · Loss $5,800,000

The loan-health check CPI’d into a user-supplied “RateX” program to read PT exchange rates without validating it against the real RateX program id. The attacker deployed a malicious program that spoofed the interface and returned inflated PT prices, enabling massively undercollateralized loans.

Invariant: all cross-program calls target a known, validated program id

cd poc && forge test --match-contract LoopscaleCpi -vv

PoC test · case study


mango-oracle-manipulation

Class SC03/SC02 · Chains solana · Status coded · Loss $114,000,000

MNGO (a thin governance token) was usable as cross-margin collateral at 100% weight with no circuit breakers or position limits. The attacker opened a huge MNGO-PERP long, spiked the MNGO oracle ~2,300% by buying on the same thin markets the oracle aggregated, then borrowed $116M against the inflated collateral. The code ran correctly; the economic design was the bug.

Invariant: borrowing power from any collateral is bounded by its real, manipulation-resistant liquidity

cd poc && forge test --match-contract MangoOracle -vv

PoC test · case study


beanstalk-governance-flashloan

Class SC02/SC04 · Chains evm · Status coded · Loss $181,000,000

Governance computed voting power (Roots) from real-time Silo deposit balances rather than snapshots at proposal creation. The attacker flash-loaned ~$1B in stablecoins from Aave/Uniswap/SushiSwap, converted to Curve LP, deposited into Beanstalk’s Silo for overwhelming Roots, voted on a pre-submitted malicious BIP-18 proposal, called emergencyCommit() with >67% supermajority, and drained all protocol assets — all in a single atomic transaction. Net profit ~$76M after flash-loan repayment.

Invariant: governance approval reflects long-term stakeholder will at proposal creation, not temporary flash-loan-acquired power

cd poc && forge test --match-contract BeanstalkGovernance -vv

PoC test · case study · fork replay


rhea-finance-slippage

Class SC02/SC07 · Chains near/multi · Status coded · Loss $18,400,000

Invariant: the validated minimum output must equal the actual terminal output of the swap route, not the sum of intermediate hop minimums

cd poc && forge test --match-contract RheaSlippage -vv

PoC test · case study


trustedvolumes-access-control

Class SC02 · Chains evm · Status coded · Loss $6,700,000

TrustedVolumes’ RFQ swap proxy had a setAuthorizedSigner(address, bool) function marked public with no access control modifier. The attacker called it to add themselves to the authorized signer whitelist, then created trade orders signed by their own key — the contract accepted them as legitimate. $6.7M drained in WETH, WBTC, USDT, USDC. Same attacker as the March 2025 1inch Fusion V1 exploit.

Invariant: only admin-controlled addresses can modify the authorized signer whitelist

cd poc && forge test --match-contract TrustedVolumesAccess -vv

PoC test · case study


verus-bridge-merkle-forgery

Class SC02 · Chains evm/multi · Status coded · Loss $11,600,000

Verus-Ethereum bridge accepted forged Merkle proofs as valid cross-chain withdrawal authorization. Attacker extracted 103.6 tBTC, 1,625 ETH, and 147,000 USDC (~$11.6M). Pattern similar to 2022 Wormhole and Nomad bridge exploits. Attacker returned $8.5M after 1,350 ETH bounty negotiation.

Invariant: cross-chain withdrawal requires proof of deposit on source chain, verified against authenticated root

cd poc && forge test --match-contract VerusMerkleForgery -vv

PoC test · case study


thorchain-tss-gg20-key-extraction

Class X04 · Chains multi · Status studied · Loss $10,800,000

An attacker bonded as a THORChain node operator and registered a malformed Paillier modulus during GG20 key generation. Because THORChain’s tss-lib fork skipped MOD/FAC proof checks (CVE-2023-33241), each signing round leaked key share residues. The attacker reconstructed the full vault private key and drained $10.8M across 10 chains.

Invariant: No single co-signer can extract the full TSS private key from signing ceremonies; all Paillier moduli are sound (product of two large primes with unknown factorization)

case study

Studied (deep-dive doc only — no runnable PoC; see the case study for why).


ekubo-callback-approval-drain

Class SC02/SC02-CB · Chains evm · Status studied · Loss $1,400,000

Ekubo’s pay() function forwards arbitrary trailing calldata to the caller’s callback and verifies only that Core’s own token balance increased. A malicious callback contract (0x8ccb…) extracted a victim address and amount from the forwarded calldata, then called transferFrom(victim, Core, amount) using the victim’s pre-existing unlimited approval. Core’s balance check passed because the tokens arrived from the victim, not from the callback’s own funds. The attacker withdrew 0.2 WBTC per iteration × 85 iterations = 17 WBTC ($1.4M). Ekubo Core’s net balance was zero — the protocol was a drain rail, not the loss source. This is the same calldata-injection class as the SwapNet $17M exploit (Jan 2026).

Invariant: A flash-accounting callback must only repay debt using funds from the locker itself or from addresses that explicitly approved the callback for this purpose. The contract must not accept balance increases from arbitrary transferFrom calls against third-party standing approvals.

case study

Studied (deep-dive doc only — no runnable PoC; see the case study for why).


kelp-dao-layerzero-dvn-1-1

Class X01/X01-BRIDGE · Chains ethereum/multi-chain · Status coded · Loss $292,000,000

Kelp DAO’s rsETH bridge used a 1-of-1 DVN (Decentralized Verifier Network) configuration on LayerZero — only a single verifier needed to sign cross-chain messages. An attacker (Lazarus Group) compromised 2 of LayerZero’s own RPC nodes, DDoS’d remaining nodes forcing failover to poisoned ones, and forged a cross-chain message. The single DVN accepted the forgery and the bridge released 116,500 rsETH ($292M). 47% of active LayerZero OApps used the same vulnerable 1/1 default config.

Invariant: Cross-chain message verification must require attestations from >=2 independent verifiers operating on separate infrastructure (different RPC endpoints, different operators, different admin keys), such that no single compromised entity can authorize a fraudulent transfer. Deployment scripts must reject configurations where requiredDVNs.length < 2.

cd poc && forge test --match-contract KelpDvnThreshold -vv

PoC test · case study


ctoken-empty-market-exchange-rate

Class SC07/SC02 · Chains evm · Status coded

A Compound V2-style cToken prices itself as exchangeRate = cash / totalSupply. In an empty/near-empty market the attacker mints 1 cToken, donates underlying directly to inflate the rate, and a later depositor’s round-down mint yields zero cTokens while the attacker (still 100% of supply) redeems the victim’s deposit. Distinct from the ERC-4626 vault case: the manipulable accounting is a lending market’s exchange rate, and the trigger is an emptied/permissionlessly-listed market.

Invariant: no external token donation can shift price-per-share between deposits, and any non-dust deposit mints > 0 shares

cd poc && forge test --match-contract CTokenInflation -vv

PoC test · case study


approval-drain-arbitrary-call

Class SC05/SC01 · Chains evm · Status coded

A swap/bridge router holds users’ standing ERC20 approvals and forwards a caller-supplied target.call(data) to an “adapter”. With target/data unrestricted, the attacker passes target = token, data = transferFrom(victim, attacker, amount); because the router is msg.sender and the victim approved it, every wallet with a live approval is drained. The high-frequency “arbitrary external call over standing approvals” confused-deputy class.

Invariant: a contract holding third-party approvals must only move those tokens via its own fixed logic; no attacker-chosen (target, selector) may reach a standing allowance

cd poc && forge test --match-contract ApprovalDrain -vv

PoC test · case study · fork replay


proxy-storage-collision

Class SC01 · Chains evm · Status coded · Loss $6,000,000

A delegatecall proxy keeps its admin/implementation pointers in low sequential storage slots, and the implementation declares state variables in those same slots. Because delegatecall executes against the proxy’s storage, an implementation function that writes its slot-0 var overwrites the proxy admin, letting an attacker seize upgrade rights (Audius-class, 2022).

Invariant: no implementation state variable may occupy the same storage slot as the proxy’s admin/implementation pointers

cd poc && forge test --match-contract ProxyCollision -vv

PoC test · case study · fork replay


signature-replay-malleability

Class SC01 · Chains evm · Status coded

A function authorizes an action from an off-chain signature but binds no consumed nonce and no EIP-712 domain (so one signature is replayed to repeat the action, or reused across chains/forks), and uses raw ecrecover with no low-s/zero-address check (so a malleated n-s signature is a second valid signature for the same message, defeating byte-dedup).

Invariant: each authorizing signature is valid for exactly one action in one place (nonce + domain bound) and only canonical low-s form is accepted

cd poc && forge test --match-contract SignatureReplay -vv

PoC test · case study


unprotected-privileged-fn

Class SC01 · Chains evm · Status coded

A state-changing privileged function (mint/burn/withdraw/setOwner/upgrade/initialize/pause/ setFee) is exposed with no access modifier, or an initialize has no once-guard. An attacker calls it to print supply, seize ownership, or drain funds. The largest DeFiHackLabs class by count; PAID Network (~$180M nominal) is the canonical ungated-mint case.

Invariant: every privileged state transition is reachable only by an authorized principal; one-shot initializers run exactly once

cd poc && forge test --match-contract UnprotectedPrivileged -vv

PoC test · case study · fork replay


insecure-randomness

Class SC09 · Chains evm · Status coded

A contract derives a winner / rarity / selection from block variables (timestamp, prevrandao, blockhash, number). Anything the EVM reads, an attacker contract reads in the same tx, so it precomputes the outcome and only commits when it would win; proposers can also grind the values. Fix: Chainlink VRF or commit-reveal so the outcome is unknown at commit.

Invariant: a participant cannot compute or influence the draw at the moment they commit their entry

cd poc && forge test --match-contract InsecureRandomness -vv

PoC test · case study


weird-erc20-accounting

Class SC02 · Chains evm · Status coded

A vault/pool credits the requested deposit amount, but a fee-on-transfer / deflationary / rebasing token delivers less than requested. Internal credited balances then exceed the tokens actually held, and late withdrawers are shorted. Fix: credit the measured balanceOf delta and use SafeERC20.

Invariant: sum of internally-credited balances never exceeds tokens actually held: sum(credited) <= balanceOf(this)

cd poc && forge test --match-contract WeirdErc20Accounting -vv

PoC test · case study


incorrect-reward-accounting

Class SC02 · Chains evm · Status coded

A reward farm pays pending = amount * accRewardPerShare - rewardDebt but fails to advance the user’s rewardDebt checkpoint after payout, so pending recomputes to the same value and the same reward is harvested repeatedly until the pool is drained. Fix: update rewardDebt on every harvest/deposit/withdraw, after updatePool and settling pending.

Invariant: immediately after harvest, pending(user) == 0; cumulative rewards paid <= accrued share

cd poc && forge test --match-contract IncorrectRewardAccounting -vv

PoC test · case study


unverified-flashloan-callback

Class SC05/SC01 · Chains evm · Status coded

A callback that the protocol calls back into (onFlashLoan / executeOperation / receiveFlashLoan / uniswapV2Call / uniswapV3SwapCallback / pancakeCall / tokensReceived) performs privileged logic but never checks msg.sender == the genuine lender/pool, nor (for flash loans) initiator == address(this). An attacker calls it directly with fabricated args and triggers the privileged path with no real loan. Fix: authenticate caller + initiator.

Invariant: a privileged callback can be entered only via the genuine protocol mid-operation (trusted msg.sender, self-initiated)

cd poc && forge test --match-contract UnverifiedCallback -vv

PoC test · case study


bridge-deposit-no-code-token

Class SC02 · Chains evm · Status coded · Loss $80,000,000

A bridge deposit takes an arbitrary token address and credits the bridged balance based on a low-level transferFrom call’s success flag. A call to a codeless address returns (true, “”) with no tokens moved, so an attacker deposits a no-code/zero token and is credited the full amount (Qubit qBridge, ~$80M). Fix: allow-list the token, require code.length > 0, and credit a measured balance delta.

Invariant: a deposit is credited only after real tokens of a known/allow-listed, code-bearing asset have actually arrived

cd poc && forge test --match-contract BridgeNoCodeToken -vv

PoC test · case study


first-deposit-amm-skim

Class SC07 · Chains evm · Status coded

A UniV2-style pair with no MINIMUM_LIQUIDITY lock lets the first LP mint 1 LP, donate tokens directly to inflate the value of that unit, and a later LP’s min() round-down mints zero LP while their deposit is absorbed; the first LP (100% of supply) redeems the whole pool. The AMM-LP variant of the inflation attack. Fix: burn MINIMUM_LIQUIDITY on first mint + require liquidity > 0.

Invariant: a later provider’s minted LP reflects their fair share; no first-depositor donation drives a subsequent mint to zero

cd poc && forge test --match-contract FirstDepositSkim -vv

PoC test · case study


cei-reentrancy

Class SC08 · Chains evm · Status coded

A state-changing function makes an external call (ETH send, ERC777/721 hook, arbitrary callback) BEFORE finalizing its own state, so a malicious callback re-enters while the state still reads its pre-call value and drains the contract. The classic CEI-violation reentrancy (The DAO, Lendf.me, Fei/Rari); the state-changing sibling of read-only-reentrancy.

Invariant: all state/effects are finalized before any external call; sum(balances) == contract balance across every call

cd poc && forge test --match-contract CeiReentrancy -vv

PoC test · case study · fork replay


meta-tx-msgsender-spoof

Class SC01 · Chains evm · Status coded

A target derives the logical caller from forwarder-appended calldata (ERC-2771 _msgSender() reads the trailing 20 bytes). A forwarder that relays an arbitrary from without an EIP-712 signature + nonce from from lets an attacker act as any victim, bypassing every _msgSender()-based authorization. Surfaced by DVD v4 Naive Receiver. Fix: restrict the forwarder set and verify a signed, nonced request.

Invariant: no forwarder call can change balances[X]/privileges of X unless X signed the request (or is msg.sender)

cd poc && forge test --match-contract MetaTxMsgSenderSpoof -vv

PoC test · case study


calldata-abi-smuggling

Class SC05/SC01 · Chains evm · Status coded

A gatekeeper reads the guarded selector from a fixed calldata offset (assuming canonical ABI encoding) but forwards a dynamic bytes param whose offset the attacker controls — so an allowed selector passes the check while sweepFunds actually runs. DVD v4 ABI Smuggling; Ethernaut Switch/HigherOrder. Fix: validate the selector of the exact bytes you dispatch (actionData[:4]).

Invariant: the function authorized by the guard == the function executed, for every crafted calldata layout

cd poc && forge test --match-contract CalldataAbiSmuggling -vv

PoC test · case study


forced-ether-balance-assumption

Class SC02 · Chains evm · Status coded

Logic that treats address(this).balance as if it changes only through its own payable entrypoints is wrong: selfdestruct(target) forces ETH into any account, and counterfactual addresses can be pre-funded. A strict-equality or threshold check on the raw balance can be bricked (DoS) or triggered early by one forced wei. Ethernaut Force/King. Fix: gate on internal accounting, use >=, sweep surplus.

Invariant: control flow depends only on values the contract authoritatively tracks; forced ether cannot change reachability

cd poc && forge test --match-contract ForcedEtherBalanceAssumption -vv

PoC test · case study


dos-griefing-revert

Class SC10/SC02 · Chains evm · Status coded

A flow that pushes ETH/tokens to an externally chosen address and requires the send to succeed hands that recipient a veto: a contract with a reverting receive freezes the whole path for everyone (also: unbounded loops over a user-growable list). Ethernaut King/Denial. Fix: pull-payment ledger + per-recipient failure isolation; bound or chunk loops.

Invariant: one participant’s failure (revert / gas / list size) cannot block another participant’s progress

cd poc && forge test --match-contract DosGriefingRevert -vv

PoC test · case study


ecdsa-nonce-reuse-key-extraction

Class SC01 · Chains evm · Status coded

ECDSA signs as s = k⁻¹(h + r·d) with a per-signature secret nonce k (and r = (k·G).x). If a signer reuses k for two different messages, both signatures share r, and the long-term private key falls out: k = (h1−h2)·inv(s1−s2), d = (s1·k − h1)·inv(r) (mod n). The attacker then forges signatures indistinguishable from the real signer’s. No verifier-side check (nonce binding, EIP-712 domain, low-s) prevents it — the defect is the signer’s nonce generation. The PoC recovers the key on-chain via the modexp precompile (0x05) and drains a signature-gated vault. Ethernaut ImpersonatorTwo; Sony PS3 (2010). Fix: RFC-6979 / HSM CSPRNG nonces; rotate any key whose signatures ever shared an r.

Invariant: the signer’s private key never leaks — only the key holder can produce a valid signature (requires a unique k per signature)

cd poc && forge test --match-contract NonceReuse -vv

PoC test · case study


tac-bridge-jetton-impersonation

Class SC02 · Chains ton/evm · Status coded

TAC Bridge ($2.85M, 2026-05-11). The sequencer authenticated inbound jetton-wallet messages by code hash only — it never checked the wallet’s storage pointed to the expected minter (jetton master) for the claimed asset. An attacker deployed a wallet with the canonical code hash but bound to their own minter, minting ~302M fake BLUM. EVM model: the minter lives in storage, so every wallet instance shares one runtime codehash; the vulnerable bridge accepts an impersonator and credits fake tokens, while the safe bridge adds the minter-provenance check. Fix: verify BOTH code hash AND minter/master binding (structure isn’t provenance).

Invariant: an inbound bridge message must originate from a contract with both the correct code hash AND the correct minter/master binding for the claimed asset

cd poc && forge test --match-contract JettonImpersonation -vv

PoC test · case study


zeta-chain-gatewayevm

Class SC02/SC05/SC01 · Chains evm/multi · Status coded

ZetaChain GatewayEVM ($334K, 2026-04-29). Three independent defects composed across two chains into a confused-deputy drain: (1) GatewayZEVM.call() was unauthenticated — anyone could emit a Called event; (2) GatewayEVM.execute() on the destination chain accepted arbitrary calldata and ran target.call(data), including token.transferFrom; (3) users had granted the gateway unlimited, never-revoked ERC20 approvals. The attacker emitted a crafted transferFrom payload, the TSS validators signed it, and the gateway executed it against its own standing approvals — the protocol’s infrastructure became the weapon. Fix: authenticate cross-chain messages, allow-list execute targets (reject token contracts), and avoid unlimited approvals to arbitrary-call executors.

Invariant: cross-chain messages must be authenticated; gateway execute must validate the target against an allow-list and reject token contracts; users must not grant unlimited approvals to contracts that execute arbitrary calldata

cd poc && forge test --match-contract ZetaGatewayEVM -vv

PoC test · case study


ato-hook-storage-slot-collision

Class SC-storage-layout · Chains evm · Status coded

ATO Hook ($14.4K, 2026-06-07). A rewards mapping(address => uint256) entry collided with Solady’s fixed _REENTRANCY_GUARD_SLOT: the attacker deployed a contract at a CREATE2 address where keccak256(addr, baseSlot) equals the guard slot. Every getReward() call writes the reentrancy sentinel into that slot — which is also the attacker’s reward balance — inflating it; repeated calls drained ~14.4 ETH. A named refinement of SC01 (storage-slot collision) that no static analyzer catches without computing mapping-slot addresses against library-reserved slots. Fix: never let an attacker-influenceable mapping entry share a slot with a security primitive’s reserved slot (namespaced/ERC-7201 storage).

Invariant: no attacker-influenceable mapping entry may share a storage slot with a security primitive’s reserved slot

cd poc && forge test --match-contract StorageSlotCollisionAtoHook -vv

PoC test · case study