CryptoNetHub

I ran my Solidity scanner on 6 top-audited DeFi protocols. Every 'critical' was a false positive — here's why.

Most Solidity scanners are high-recall, low-precision. They flag 40 things, 38 are noise, and after the third report you stop reading them — so the one real bug ships. Precision, not recall, is what makes a. . .

· 4 min read

I ran my Solidity scanner on 6 top-audited DeFi protocols. Every 'critical' was a false positive — here's why.

Most Solidity scanners are high-recall, low-precision. They flag 40 things, 38 are noise, and after the third report you stop reading them — so the one real bug ships. Precision, not recall, is what makes a security tool actually get used. I've been buildingOpenClaw, a heuristic Solidity scanner with the opposite bar:silence on sound code. To pressure-test it, I pointed it at six codebases that top firms have already audited — Yearn, Sablier, Ajna, Liquity, and a couple of smaller protocols — andhand-verified every single flag. The result:14 HIGH/CRITICAL candidates across the six. Every one was a false positive. That sounds like a failure. It's the whole point — and each FP is a lesson in the exact traps that fool most scanners. Here they are. 1. The "donation attack" that isn't (a PaymentSplitter)The scanner flaggedbalanceOf(address(this))used in accounting as a donation / share-inflation attack. But the contract was a fork of OpenZeppelin'sPaymentSplitter:shares are fixed at deployment, and a "donation" is exactly the input that gets split proportionally among those fixed shares. There's no share to mint, no share price to manipulate, no first-depositor. balance + totalReleasedis the canonical, correct accounting. Lesson:balanceOf(this)in accounting is only a donation risk whenshares are minted against it. In a fixed-share pull-payment splitter, it's intended behavior. 2. The "unprotected initialize" with a guard the scanner didn't recognize (Ajna)Ajna's pools were flagged as "unprotectedinitialize()". But there it was, first line:if (isPoolInitialized) revert AlreadyInitialized();Enter fullscreen modeExit fullscreen modeA one-time-init guard. Plenty of non-OpenZeppelin protocols protectinitializewith a boolean flag + revert instead of theinitializermodifier. A detector that only knows the OZ modifier misses it and screams. Lesson:an initializer is protected if it hasanyone-time guard — the OZ modifier, a boolean flag that reverts, factory/clone init, or_disableInitializers(). 3. "Read-only reentrancy" in functions nobody uses as an oracle (Ajna, Liquity)poolBalanceDetails(),flashLoan(),simulateRedemption()— all flagged for reading a balance after an external call. Read-only reentrancy (the Curve/Balancer class that paid $100k+ bounties) is only exploitableif an external protocol consumes the function as a price oracle. A view utility in aMulticallhelper, a flash loan's repayment check, asimulate*function — none of those are oracles. Lesson:read-only reentrancy needs an oracle consumer. simulate/preview/*Details/multicall/flashLoanaren't oracles → not exploitable. 4. The "manipulable rate" that's bounded and role-gated (Ember)Flagged CRITICAL: "rate can be updated. " But the rate wasmanaged and bounded— arateManagerrole, amaxRateChangePerUpdatecap, arateUpdateInterval, and events on every change. A trusted, bounded, time-gated exchange rate is intended design, not an exploit. Even a malicious manager can only nudge it by the cap per interval. Lesson:a role-updatable value bounded by max-change + interval is amanaged parameter, not a vulnerability (at most a centralization note). 5. "Fee-on-transfer not supported" — by design (Liquity)Flagged for not handling fee-on-transfer tokens. But Liquity's BOLD uses a vetted, fixed set of collateral (WETH/wstETH-class), not arbitrary ERC-20s. "Doesn't support FoT" is a deliberate decision for a protocol with curated collateral. Lesson:FoT-not-supported is only a bug if the protocol acceptsarbitrarytokens. With vetted collateral, it's intended. Why this mattersEvery one of these is a place a naive scanner shouts "CRITICAL" and a good auditor quietly says "no. " The value of a low false-positive rate isn't that the tool finds less — it's thatwhen it (or I) stay silent, the silence means something. A tool that cries wolf 38 times out of 40 trains you to ignore the 39th. The 39th is the real one. Each of these five FP classes is now a permanent fix in the detector, not a patch — so the next PaymentSplitter, the next flag-guardedinitialize, the next view utility, doesn't fire. That's how you drive false positives toward zero without going blind to the real thing. The scannerOpenClaw is calibrated against the codebases everyone treats as a gold standard — OpenZeppelin, Solady, Solmate, Uniswap v2/v3/v4, Permit2, Morpho Blue, PRBMath. 608 source files, 14 total flags, 0 across the entire OpenZeppelin library. MIT, pure Python, runs as a GitHub Action that comments on every PR:pipx run--specgit+https://github. com/juan23z/openclaw-audit openclaw-audit --out. /reportEnter fullscreen modeExit fullscreen modeHonest about what it isnot: it's heuristic, not formal verification. It catchesclassesof bugs (access control, vault math, reentrancy shape, oracle staleness, upgradeability) — not your protocol's bespoke economic logic bug, the one where two functions interact in a way nobody drew on the whiteboard. That still needs a human. But the silence is honest. If you're shipping to mainnet and want a second set of eyes — hand-verified findings, zero false-positive spam, a plain-English report in 48h — I do fastpre-mainnet reviews. Or just run the free scanner and keep the signal. I'd genuinely like feedback on the false-positive classes above — which ones have bitten you with Slither/others?

Source: dev.to

More DeFi news