A checklist for IMD's contract builds and token launches, drawn from 30 public DeFi exploits of 2025 and 2026: for each, what failed, the general pattern behind it, and the check that would have caught it before deployment.
Drawn from 30 public DeFi exploits of 2025–2026. For each incident: what failed, the general pattern, and the check that would have caught it before deployment.
Incident Table
#
Incident
Date
Chain
Loss (USD)
Pattern
SC Bug?
1
zkLend
2025-02-11
Starknet
~$9.5M
Rounding/precision loss in accumulator lending math
Yes
2
Four.meme
2025-02-11, 03-18
BNB Chain
~$183K + ~$130K
Unvalidated external pool state / pool-init front-run
Yes
3
Bybit
2025-02-21
Ethereum
~$1.43B
Key/signer compromise via frontend supply-chain attack
Fuzz the withdraw path with extreme accumulator values; assert a user's withdrawable amount never exceeds deposits plus accrued interest
Invariant test (Foundry/Medusa)
zkLend
A1.5
Any a / b where b derives from an external or attacker-influenceable source must carry a documented lower bound before feeding a health/solvency calculation
Review rule
Resupply [ackee.xyz; blocksec.com]
A1.6
For token sale contracts: compile with Solidity ≥0.8.0 (or use SafeMath); fuzz getPurchasePrice(amount) to type(uint256).max and assert non-zero price for non-zero amount
Deploy-time check + fuzz test
Truebit [certik.com; DeFiHackLabs]
A2. Uniswap v4 Hooks
#
Check
How
Would Have Caught
A2.1
Every hook callback (beforeSwap, afterSwap, beforeAddLiquidity, etc.) must revert when called directly by an arbitrary EOA — only the PoolManager may invoke it
Unit test: call each hook from a non-PoolManager address, assert revert
Enumerate every TSTORE/TLOAD in hook contracts; reject sharing a callback-authority transient slot with a numeric return value; use distinct slots and clear authority on swap completion
Same-transaction regression test: complete a legitimate mint/swap, then attempt another callback before the tx ends with varying amounts and unauthorized callers — only the original pool must succeed
Invariant test
SIR.trading
A2.4
Every mulDiv in hook state-update arithmetic must justify its rounding direction across sequences, not only singly — a rounding "safe" once can compound across many small operations
Every path that increases shares or totalUnderlying must increase the vault's actual custodied assets by at least the credited amount; fuzz all entry points
Invariant test
Meta Pool [solidityscan.com; quillaudits.com]
A3.2
When forking ERC-4626 and dropping the transfer step from _deposit, enumerate every inherited entry point reaching it (deposit, mint, depositETH, etc.) and either make it payable-with-check or disable it
Review rule
Meta Pool
A3.3
Fuzz the oracle/price path across the full uint256 range; assert exchangeRate > 0 and that a 1-wei-collateral position is never solvent while debt > 0
Validate initialization before enabling a market: require the ERC-4626 vault seeded with minimum non-zero liquidity, or reject a market whose convertToAssets(1e18) exceeds a sane upper bound
Deployment check
Resupply
A3.5
After every liquidate() call, a liquidated position must not pass borrow() and return a non-zero amount; every state-mutating function that empties a record must set an explicit isActive flag
Property-test _calc_supply (with add_liquidity/remove_liquidity/update_rates) over extreme imbalanced inputs; assert that the product term Π is never zero and LP minted per call is bounded by deposited value
Replace unsafe_* arithmetic with checked math so a domain violation reverts; permanently disable the bootstrap/initialization branch after the first deposit
Review rule + ops check
Yearn yETH
A4. Upgradeable Proxies
#
Check
How
Would Have Caught
A4.1
Deploy and initialize every upgradeable proxy atomically in one transaction: new ERC1967Proxy(impl, abi.encodeCall(Logic.initialize, (admin))) — never deploy with empty init data
Assert the EIP-1967 implementation slot equals the legitimate implementation immediately after deployment (no window between deploy and init)
Invariant check in deploy script
Kinto ($K)
A4.3
Monitor that the implementation slot never changes outside an event-logged upgrade; where delayed init is unavoidable, restrict initialize() to a single hard-coded deployer
Review rule + on-chain monitoring
Kinto ($K)
A5. Access Control
#
Check
How
Would Have Caught
A5.1
Every access-control require must name who is allowed (==), not who is excluded (!=); statically flag `require(msg.sender != X
msg.sender != Y)` as always-true tautologies
A5.2
Every security predicate returned by a pure function must be consumed by an assert!/if/require — flag discarded return values from authorization checks
Review rule (Move: no unused-return warning) + unit test from non-whitelisted caller
When inheriting a meta-transaction base (e.g. MinimalForwarderUpgradeable), require an explicit onlyOwner/AccessControl guard on execute(); never let msg.sender == forwarder alone substitute for checking the recovered signer against an allow-list
Enumerate every inherited public entry point on bridge receivers (e.g. expressExecute()); require symmetric validation between fast and normal execution paths
Review rule (Slither entrypoint list) + unit test from non-Gateway EOA
Assert no code path moves value out of a bridge vault unless gateway.validateContractCall() approved the (commandId, sourceChain, sourceAddress, payloadHash) tuple
Invariant fuzz test
CrossCurve
A6. External Calls and Calldata Validation
#
Check
How
Would Have Caught
A6.1
Every call()/delegatecall() to a caller-supplied address must validate the target against a hardcoded or governance whitelist, or restrict calldata to known function selectors
Review rule + unit test with non-whitelisted router and arbitrary calldata
Invariant: the contract's token balance must never decrease except through the intended swap/rebalance path; fuzz arbitrary _swapArgs/calldata payloads
Invariant fuzz test
Silo Finance; Arcadia Finance
A6.3
After createAndInitializePoolIfNecessary in a launch/migration, verify pool.slot0().sqrtPriceX96 equals the intended price; use non-zero amount0Min/amount1Min slippage guards
At the pool level: depositing x of token A never mints liquidity recovering more than x on withdrawal
Invariant test
Cetus Protocol
A7.5
Treat shared math libraries as in-scope for audit, not out-of-bounds; review every guard constant for correctness against the mathematical invariant it enforces
Review rule
Cetus Protocol
A7.6
Fuzz swap sequences through batchSwap/multi-hop and assert the invariant D_post >= D_pre - fees across extreme balance ratios, single-wei amounts, and varied token decimals
Round-trip property test: for every upscale(x, s) / downscale(y, s) pair, assert compose-and-round-trip never produces net invariant loss; every scaling path must round conservatively for the pool
Property test + review rule
Balancer v2
A7.8
Fuzz time-to-expiry T with random trade volumes in rollover/AMM pricing; assert risk premium rT < 10,000% and initialization price > 1% of reserve assets
Fuzz test
Cork Protocol
A8. Oracle and Price Validation
#
Check
How
Would Have Caught
A8.1
Route all collateral pricing through one verify_price_source(program_id, market) helper; require any PR touching a collateral integration to prove the check sits on every path
Review rule + invariant test with hostile source (fuzz remaining_accounts with attacker-owned programs)
Price-sanity bounds in every oracle adapter: require(price >= minPrice && price <= maxPrice) with bounds from the asset's known market price, run on every oracle update
Integration test comparing oracle output to a known reference price (e.g. Chainlink)
Apply nonReentrant to every order-execution or position-management path that makes an external call before finalizing state; unit test deploys a mock attacker as the account, reenters from the callback, and asserts revert
Unit test + review rule
GMX v1 [rekt.news/gmx-rekt; quillaudits.com]
A9.2
Fuzz/invariant test opening random positions from EOA and contract accounts, executing decrease-orders, and asserting global accumulators (globalShortAveragePrice or equivalent) never deviate more than a few basis points from the oracle price
Invariant fuzz test
GMX v1
Part B: Operational Controls (Keys, Multisigs, Signing, Configuration)
These incidents were not smart-contract bugs. Each exploited a key compromise, signing blind spot, or configuration gap that code alone could not prevent.
#
Check
How
Would Have Caught
B.1
Every multisig signer must decode and inspect raw transaction calldata on a separate, air-gapped device before signing — never trust the UI alone
Deploy a canary script that loads the signing frontend, simulates a transaction, and verifies calldata matches the displayed intent; alert on any divergence
Automated monitoring
Bybit
B.3
Pre-launch role inventory: assert no plain EOA holds any privileged role; every holder must be a multisig or timelock contract. Revoke deployer roles immediately after launch
After an airdrop claim window closes, audit every privileged function on distribution contracts; any mint/transfer/burn path must be timelock-gated, multisig-gated, or permanently disabled
Review rule + ops procedure
ZKsync Airdrop
B.5
Every completed mint/swap must satisfy mintedAmount <= collateralIn * collateralPrice / (1 - maxSlippageBps), bounded by hard per-tx and per-period caps and a total-supply ceiling — even when the signing key is valid
Move mint authorization off a single long-lived EOA: use multisig/MPC with one key per contract; add real-time monitoring wired to an automatic pause on anomalous mints
Operational design
Resolv
B.7
Every admin action that can move or rehypothecate user funds must pass a timelock (≥12h) plus an emergency pause any council member can trigger. Review rule: "Can any N-of-M signers move funds instantly? If yes, governance is incomplete"
Governance design rule + invariant test on admin capabilities
Drift Protocol [rekt.news/drift-protocol-rekt]
B.8
Assert no single admin tx, nor any 24h sequence, can reduce total vault TVL by more than N% without a timelock expiry — fuzz the admin action space on a mainnet-fork simulation
Invariant test
Drift Protocol
B.9
Deployment-gating config test: assert requiredDVNCount + optionalDVNCount >= 2 with independently operated DVNs and fail CI/deploy otherwise; refuse to sign as the sole required attestor on any bridge channel
Cross-chain invariant monitoring: continuously assert released_on_dest <= burned_on_src and dest_inbound_nonce <= src_outbound_nonce per channel; scan source-chain logs for every claimed PacketSent event
Operational monitoring
KelpDAO
B.11
Oracle configuration integration test: compare oracle output to a known reference price (e.g. Chainlink ETH/USD × derivative rate), asserting within ±10%, run in CI before every governance vote
Re-audit and re-run the full test suite on every post-audit change, however small — multiple exploits (SuperRare, GMX v1, Loopscale) were introduced after audits in changes that skipped re-review
All 15 incidents are present, the file is 32,108 bytes (well under the 64 KB limit), and the structure is clean. Every incident has all five sections preserved: what happened, root cause, pattern, check, and sources. Disagreements between sources are noted where they exist (zkLend loss figures, Infini role hex, KiloEx loss range, Cetus loss range, SIR.trading date discrepancy, Meta Pool loss range, Loopscale conflicting theories). No incident is missing, and no finding was invented — sources are carried through from the input notes.
The merged deliverable is at artifacts/goal/merges/01/result.md.
ran oncodex · deepseek-v4-pro · 6 turns · 4m 14s · 40.1K in · 11.7K out · 227.1K cached
Those matches are all legitimate code inside fenced blocks, not corruption. The deliverable is complete and verified.
Done. I merged all 15 incident notes into result.md.
What it contains:
A header noting all 15 inputs were present and non-empty (no incident note missing), plus a quick index table.
One section per incident (Resupply, GMX v1, Kinto, Arcadia, SuperRare, Bunni, Typus, Balancer, Yearn yETH, Truebit, CrossCurve, Moonwell, Resolv, Drift, KelpDAO), each keeping its loss, root cause, pattern, the check that would have caught it, and every source URL from the input note.
Explicit disagreement callouts where notes differ: loss ranges, conflicting dates, and whether a first-party post-mortem exists (e.g. Resupply's June 25 vs 26, Bunni's Sep 1 vs 2, Kinto's self-contradictory timeline, CrossCurve's Jan 31 vs Feb 1 vs Feb 2, and the ten incidents with differing USD figures).
A cross-incident pattern summary and a "noted source gap" list naming the incidents whose project post-mortem or named security-firm write-up was unavailable, with what each still rests on.
Verification: file is 63,956 bytes (under the 64,000 cap); 15 sections each carrying all five required elements; a scripted check confirmed every URL in all 15 input notes appears in the merged file; code fences are balanced (16 blocks); and the file is the only thing written under the allowed path (artifacts/goal/merges/02/), left untracked as required.
ran oncodex · deepseek-v4-1-flash · 138 turns · 7m 50s · 729.3K in · 121.7K out · 17.5M cached