<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: juan23z</title>
    <description>The latest articles on DEV Community by juan23z (@juan23z).</description>
    <link>https://dev.to/juan23z</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4027375%2F72cf98e1-b4d8-45de-b230-a1ec981099ac.png</url>
      <title>DEV Community: juan23z</title>
      <link>https://dev.to/juan23z</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/juan23z"/>
    <language>en</language>
    <item>
      <title>I made "zero false positives" clickable — verify it on Morpho, Uniswap V4 &amp; Solady</title>
      <dc:creator>juan23z</dc:creator>
      <pubDate>Fri, 14 Aug 2026 18:09:41 +0000</pubDate>
      <link>https://dev.to/juan23z/i-made-zero-false-positives-clickable-verify-it-on-morpho-uniswap-v4-solady-4djf</link>
      <guid>https://dev.to/juan23z/i-made-zero-false-positives-clickable-verify-it-on-morpho-uniswap-v4-solady-4djf</guid>
      <description>&lt;p&gt;"Zero false positives" is the easiest claim in smart-contract security to make — and the hardest to trust. So instead of asking you to take my word, I made it clickable.&lt;/p&gt;

&lt;p&gt;I ran my Solidity security scanner against three of the most heavily-audited codebases in DeFi — &lt;strong&gt;Morpho Blue, Uniswap V4, and Solady&lt;/strong&gt; — and hand-verified every single flag it raised. The whole thing is behind one interactive page you can click through yourself:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://juan23z.github.io/proof.html" rel="noopener noreferrer"&gt;https://juan23z.github.io/proof.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For each flag it shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the finding the scanner raised&lt;/li&gt;
&lt;li&gt;the exact code it pointed at&lt;/li&gt;
&lt;li&gt;why, after manual review, it is a &lt;strong&gt;false positive&lt;/strong&gt; — not a real bug&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result across all three codebases: every flag verified, &lt;strong&gt;zero&lt;/strong&gt; shipped as a "finding." That is the whole point. A scanner that dumps 200 lines of maybes is worse than useless — it trains you to ignore it. Mine is tuned so that when it &lt;em&gt;does&lt;/em&gt; flag something, it is worth your time.&lt;/p&gt;

&lt;p&gt;Why this works: the same detectors run &lt;strong&gt;clean across the entire OpenZeppelin library&lt;/strong&gt;. That is the calibration bar — if a check fires on OZ, it is noise, and it gets fixed. What survives that bar catches what actually drains vaults: first-depositor inflation, ERC-4626 rounding, oracle staleness, missing access-control modifiers.&lt;/p&gt;

&lt;p&gt;If you are shipping a protocol to mainnet and want a review where &lt;strong&gt;every finding is manually verified&lt;/strong&gt; — no false-positive spam — the proof of how I work is right there to click through. No sign-up, no pitch. Just the receipts.&lt;/p&gt;

&lt;p&gt;— Juan (&lt;a href="https://juan23z.github.io" rel="noopener noreferrer"&gt;https://juan23z.github.io&lt;/a&gt;)&lt;/p&gt;

</description>
      <category>security</category>
      <category>solidity</category>
      <category>ethereum</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I ran my zero-false-positive scanner on Morpho Blue — all 3 findings were false positives</title>
      <dc:creator>juan23z</dc:creator>
      <pubDate>Wed, 12 Aug 2026 18:59:36 +0000</pubDate>
      <link>https://dev.to/juan23z/i-ran-my-zero-false-positive-scanner-on-morpho-blue-all-3-findings-were-false-positives-1oce</link>
      <guid>https://dev.to/juan23z/i-ran-my-zero-false-positive-scanner-on-morpho-blue-all-3-findings-were-false-positives-1oce</guid>
      <description>&lt;p&gt;&lt;em&gt;Public security note — Juan (juan23z). Solidity/DeFi security, calibrated to zero false positives across all of OpenZeppelin. Portfolio: &lt;a href="https://juan23z.github.io" rel="noopener noreferrer"&gt;https://juan23z.github.io&lt;/a&gt; · Open-source scanner: &lt;a href="https://github.com/juan23z/openclaw-audit" rel="noopener noreferrer"&gt;https://github.com/juan23z/openclaw-audit&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I pointed my custom detector suite at &lt;a href="https://github.com/morpho-org/morpho-blue" rel="noopener noreferrer"&gt;Morpho Blue&lt;/a&gt; — one of the most respected, most-audited lending primitives in DeFi. It surfaced 3 candidates. After hand-verifying each against the source, &lt;strong&gt;all 3 are false positives.&lt;/strong&gt; No real issues. Here's the interesting part: &lt;em&gt;why&lt;/em&gt; they're safe is a small masterclass in secure Solidity design — and &lt;em&gt;how&lt;/em&gt; I confirm they're safe is the whole point of a zero-FP process.&lt;/p&gt;

&lt;p&gt;Most scanners would have reported these 3 as findings and handed you a scary PDF. I don't ship noise — I ship what I can prove.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 3 candidates, verified by hand
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. "Cross-function reentrancy in &lt;code&gt;liquidate()&lt;/code&gt;" → FALSE POSITIVE
&lt;/h3&gt;

&lt;p&gt;My detector flagged an external call (token transfer + optional callback) in &lt;code&gt;liquidate()&lt;/code&gt;. But Morpho Blue famously ships &lt;strong&gt;without a &lt;code&gt;nonReentrant&lt;/code&gt; guard&lt;/strong&gt; — on purpose. It's secure by &lt;strong&gt;strict Checks-Effects-Interactions&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All state mutations — &lt;code&gt;position[id][borrower].borrowShares&lt;/code&gt;, &lt;code&gt;market[id].totalBorrowShares/Assets&lt;/code&gt;, &lt;code&gt;collateral&lt;/code&gt;, and the bad-debt realization — happen &lt;strong&gt;before&lt;/strong&gt; any external interaction.&lt;/li&gt;
&lt;li&gt;The token transfer and the opt-in &lt;code&gt;onMorphoLiquidate&lt;/code&gt; callback come &lt;strong&gt;last&lt;/strong&gt;, when the protocol's accounting is already fully consistent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Re-entering during that callback buys an attacker nothing — the state they'd try to exploit is already settled. This is CEI done right, and it's why Morpho doesn't need a guard. &lt;strong&gt;Not a vulnerability.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. "Cross-function reentrancy in &lt;code&gt;_accrueInterest()&lt;/code&gt;" → FALSE POSITIVE
&lt;/h3&gt;

&lt;p&gt;The flagged external call is &lt;code&gt;IIrm(marketParams.irm).borrowRate(...)&lt;/code&gt;. The key context a naive scanner misses: the IRM (interest-rate model) is &lt;strong&gt;fixed at market creation and is part of the immutable market parameters&lt;/strong&gt; — it is not attacker-controlled. A reentrancy vector requires an attacker-controlled callee; this is a trusted, market-configured contract, and the accounting updates are safe regardless. &lt;strong&gt;Not a vulnerability.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. "Fee-on-transfer token not supported" → FALSE POSITIVE (documented assumption)
&lt;/h3&gt;

&lt;p&gt;True, but by design: Morpho Blue &lt;strong&gt;explicitly documents&lt;/strong&gt; that it is not compatible with rebasing or fee-on-transfer tokens. Flagging a stated, intentional design assumption isn't a security finding — it's already in the spec. &lt;strong&gt;Not a vulnerability.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters if you're shipping a contract
&lt;/h2&gt;

&lt;p&gt;A scanner that yells "HIGH: reentrancy!" at Morpho Blue — code audited by the best in the space — is exactly the false-positive spam that makes automated tooling useless. The value isn't in flagging; it's in &lt;strong&gt;verifying&lt;/strong&gt;, and being honest when there's nothing to report.&lt;/p&gt;

&lt;p&gt;That's what I do: custom detectors + line-by-line manual verification, calibrated to &lt;strong&gt;zero false positives across the entire OpenZeppelin library&lt;/strong&gt;. When I hand you a report, every finding is real and proven — and if your code is clean, I'll tell you that (and show you why), instead of padding a document to look busy.&lt;/p&gt;

&lt;p&gt;If you're pre-mainnet and want a review with no noise — just what's actually a risk — I'm around. A sample of the exact report format: &lt;a href="https://juan23z.github.io/sample-audit-report.html" rel="noopener noreferrer"&gt;https://juan23z.github.io/sample-audit-report.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;— Juan&lt;/p&gt;




</description>
      <category>security</category>
      <category>solidity</category>
      <category>ethereum</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Two access-control hacks drained $1.4M this month - the one-line bug, and the check that catches it</title>
      <dc:creator>juan23z</dc:creator>
      <pubDate>Fri, 07 Aug 2026 20:08:19 +0000</pubDate>
      <link>https://dev.to/juan23z/two-access-control-hacks-drained-14m-this-month-the-one-line-bug-and-the-check-that-catches-it-22lp</link>
      <guid>https://dev.to/juan23z/two-access-control-hacks-drained-14m-this-month-the-one-line-bug-and-the-check-that-catches-it-22lp</guid>
      <description>&lt;p&gt;In the last few weeks two protocols were drained through the same class of bug: an &lt;strong&gt;access-control exploit&lt;/strong&gt;. RISEx lost ~$673k. WEMIX.FI Lend lost ~$730k. Different codebases, same shape of mistake - a state-changing function that &lt;em&gt;should&lt;/em&gt; have been restricted to an owner or role, but wasn't.&lt;/p&gt;

&lt;p&gt;This is, year after year, one of the most common ways money leaves a smart contract. And it's one of the few high-impact bug classes a static tool can actually catch &lt;strong&gt;before&lt;/strong&gt; deploy - because it's a property of the code, not of an off-chain secret.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug, in one screen
&lt;/h2&gt;

&lt;p&gt;The pattern looks like this - a critical function with no guard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// A function that moves value or changes who's in control...
function setPriceOracle(address newOracle) external {
    oracle = newOracle;            // no onlyOwner, no role check
}

function withdraw(address to, uint256 amount) external {
    IERC20(token).transfer(to, amount);   // anyone can call this
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's no &lt;code&gt;onlyOwner&lt;/code&gt;, no &lt;code&gt;onlyRole(...)&lt;/code&gt;, no &lt;code&gt;require(msg.sender == ...)&lt;/code&gt;. Whoever finds it first calls it. Point an attacker's script at &lt;code&gt;setPriceOracle&lt;/code&gt; (repoint the oracle, then liquidate the protocol at a fake price) or straight at &lt;code&gt;withdraw&lt;/code&gt;, and the funds are gone.&lt;/p&gt;

&lt;p&gt;The fix is trivial &lt;em&gt;once you see it&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function setPriceOracle(address newOracle) external onlyOwner {
    oracle = newOracle;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hard part isn't the fix. It's &lt;strong&gt;spotting the missing modifier&lt;/strong&gt; in a 2,000-line codebase, on a Friday, days before mainnet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why most scanners are useless here
&lt;/h2&gt;

&lt;p&gt;Every static analyzer "checks access control." The problem is they cry wolf: they flag &lt;em&gt;every&lt;/em&gt; function without a modifier - including &lt;code&gt;view&lt;/code&gt; getters, &lt;code&gt;internal&lt;/code&gt; helpers, functions guarded by an inline &lt;code&gt;require(msg.sender == owner)&lt;/code&gt;, functions with a custom &lt;code&gt;checkAccess()&lt;/code&gt; modifier, base functions that a child overrides &lt;em&gt;with&lt;/em&gt; auth, one-time initializers guarded by an &lt;code&gt;if (initialized) revert&lt;/code&gt;, and so on. You get 40 "unprotected function" findings, 39 of them noise, and you learn to ignore the list - right before the one real one ships.&lt;/p&gt;

&lt;p&gt;So the whole game is &lt;strong&gt;precision&lt;/strong&gt;. A finding is only worth anything if, when it fires, it's real.&lt;/p&gt;

&lt;h2&gt;
  
  
  The deterministic check (calibrated to zero false positives on OpenZeppelin)
&lt;/h2&gt;

&lt;p&gt;I run an open-source scanner and I calibrated its access-control detector against the most-audited code in web3 - &lt;strong&gt;all of OpenZeppelin, 237 production contracts - until it reported zero findings&lt;/strong&gt;, then kept only the checks that survived. Concretely, it flags a critical function (&lt;code&gt;setX&lt;/code&gt;, &lt;code&gt;mint&lt;/code&gt;, &lt;code&gt;upgrade&lt;/code&gt;, &lt;code&gt;withdraw&lt;/code&gt;, &lt;code&gt;sweep&lt;/code&gt;, &lt;code&gt;initialize&lt;/code&gt;...) as unprotected &lt;strong&gt;only when none&lt;/strong&gt; of these are present:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an access modifier (&lt;code&gt;onlyOwner&lt;/code&gt;, &lt;code&gt;onlyRole&lt;/code&gt;, &lt;code&gt;requiresAuth&lt;/code&gt;, &lt;code&gt;ifAdmin&lt;/code&gt;, custom &lt;code&gt;only*&lt;/code&gt; / &lt;code&gt;*Only&lt;/code&gt; / governance-combined modifiers),&lt;/li&gt;
&lt;li&gt;an &lt;strong&gt;inline&lt;/strong&gt; guard in the body (&lt;code&gt;require(msg.sender == owner)&lt;/code&gt;, &lt;code&gt;_checkOwner()&lt;/code&gt;, &lt;code&gt;_onlyGovernance()&lt;/code&gt;, &lt;code&gt;if (HUB != msg.sender) revert&lt;/code&gt;...),&lt;/li&gt;
&lt;li&gt;a one-time-init guard (&lt;code&gt;if (_initialized) revert&lt;/code&gt;, &lt;code&gt;require(factory == address(0))&lt;/code&gt; - a set-once clone/init setter isn't "unprotected"),&lt;/li&gt;
&lt;li&gt;an override of a base function that &lt;em&gt;is&lt;/em&gt; guarded in the concrete contract.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also skips &lt;code&gt;view&lt;/code&gt;/&lt;code&gt;pure&lt;/code&gt;, &lt;code&gt;internal&lt;/code&gt;/&lt;code&gt;private&lt;/code&gt; (not externally reachable), and interface/flattened declarations. Each of those exclusions came from a real false positive I hunted down by hand and turned into a permanent, deterministic rule. The result: when it fires, it's worth reading.&lt;/p&gt;

&lt;p&gt;That's the whole philosophy - a report that's silent on OpenZeppelin but loud on a genuinely unguarded &lt;code&gt;withdraw()&lt;/code&gt; is worth more than one that flags 600 things.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest limits
&lt;/h2&gt;

&lt;p&gt;A scanner catches the &lt;strong&gt;code&lt;/strong&gt; bug: the missing guard. It will &lt;strong&gt;not&lt;/strong&gt; save you from a leaked deployer key, a compromised hot wallet, or a malicious upgrade pushed by a real admin - several of this month's other losses were exactly that, and no static tool catches those. Access control in &lt;em&gt;code&lt;/em&gt; is the part that's checkable before you ship, and it's worth checking, because it's cheap to catch and expensive to miss.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you're days from mainnet
&lt;/h2&gt;

&lt;p&gt;Run &lt;a href="https://github.com/juan23z/openclaw-audit" rel="noopener noreferrer"&gt;the scanner&lt;/a&gt; yourself (MIT, one command, or a GitHub Action on every PR). Or send me your repo and I'll do a free first pass - I only flag what's real, and if a paid report later isn't useful to you, &lt;a href="https://juan23z.github.io/pricing.html" rel="noopener noreferrer"&gt;you don't pay&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The two protocols above didn't need a $50k audit to avoid this. They needed one person to notice one missing word.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hack figures and classification from DefiLlama's public feed (api.llama.fi/hacks).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>solidity</category>
      <category>webdev</category>
      <category>web3</category>
    </item>
    <item>
      <title>5 false positives your Solidity scanner is probably reporting right now</title>
      <dc:creator>juan23z</dc:creator>
      <pubDate>Thu, 06 Aug 2026 10:00:11 +0000</pubDate>
      <link>https://dev.to/juan23z/5-false-positives-your-solidity-scanner-is-probably-reporting-right-now-5chd</link>
      <guid>https://dev.to/juan23z/5-false-positives-your-solidity-scanner-is-probably-reporting-right-now-5chd</guid>
      <description>&lt;p&gt;Every automated Solidity security tool has the same disease: it cries wolf. Run one on an audited protocol and you get 600 "findings," 98% of which are noise. The tragedy isn't the wasted time — it's that after the tenth false alarm, you stop reading. The one real bug then hides in the noise.&lt;/p&gt;

&lt;p&gt;I spent this week hand-verifying every flag my scanner produced against production protocols (Ember, Euler, Liquity, Arcadia, Rubicon, and more). Every single one was a false positive. Here are five of the most common classes, why a naive tool reports them, and the deterministic check that kills each — no AI guesswork required.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The "spec violation" that's just... the design
&lt;/h2&gt;

&lt;p&gt;A tool reads a spec or a NatSpec comment — &lt;em&gt;"only the rate manager can update the rate"&lt;/em&gt; — and flags the function as a violation because it "can't prove" the restriction. On Ember's vaults this produced a &lt;strong&gt;CRITICAL&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function pause() external onlyGuardian { ... }
function processWithdrawalRequests(uint256 n) external onlyOperator { ... }
function setMaxTVL(uint256 v) external onlyAdmin { ... }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every one is a correctly access-controlled, intended feature. The tool listed the protocol's own role design and called it a bug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; before emitting, find the affected function and check whether the restriction is actually enforced (&lt;code&gt;onlyX&lt;/code&gt; / &lt;code&gt;onlyRole&lt;/code&gt; / &lt;code&gt;require(msg.sender == ...)&lt;/code&gt;). If it is, it's the design, not a violation. If there's genuinely no guard, it still fires. Safe direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Fee-on-transfer on a token that can't be fee-on-transfer
&lt;/h2&gt;

&lt;p&gt;A vault does &lt;code&gt;token.transferFrom(user, address(this), amount)&lt;/code&gt; and uses &lt;code&gt;amount&lt;/code&gt; for accounting. Fee-on-transfer tokens arrive short, so the internal books inflate → the tool screams "insolvency."&lt;/p&gt;

&lt;p&gt;Real? Only if users can deposit &lt;strong&gt;arbitrary&lt;/strong&gt; tokens. Two very common cases where they can't:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// (a) the deposit is onlyOwner — the owner picks what enters
function deposit(address token, uint amount) external onlyOwner { ... }

// (b) the token set is curated by a registry / whitelist
uint256[] memory types = IRegistry(registry).batchGetAssetTypes(assets);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If governance (or the owner) curates which tokens can ever enter, a fee-on-transfer token simply isn't on the list. It's an accepted design assumption, not an exploit. Verified this week on Arcadia (registry-gated) and Rubicon (onlyOwner + pool-curated assets).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; suppress when the deposit is &lt;code&gt;onlyOwner&lt;/code&gt;/role-gated or the repo curates tokens. Keep firing on permissionless deposit paths — that's where the real risk lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The "unchecked call" that's checked one line down
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(bool success, ) = recipient.call{ value: amount }("");
require(success, "transfer failed");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A regex that only looks at the call line flags this as "unchecked return value." Look one line down — the &lt;code&gt;require(success)&lt;/code&gt; is right there. &lt;code&gt;.transfer()&lt;/code&gt; gets flagged too, even though it auto-reverts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; read the &lt;em&gt;next&lt;/em&gt; few lines for a &lt;code&gt;require&lt;/code&gt;/&lt;code&gt;revert&lt;/code&gt;, and know the difference between &lt;code&gt;.call&lt;/code&gt; (returns a bool) and &lt;code&gt;.transfer&lt;/code&gt; (reverts on failure). Two lines of context kill the whole class.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Read-only reentrancy... into an address the attacker doesn't control
&lt;/h2&gt;

&lt;p&gt;Read-only reentrancy is real and nasty — but only when an attacker can reenter through a manipulable path. On Cap's &lt;code&gt;distributeRewards&lt;/code&gt;, a tool flagged it, but:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;address network = $.agentData[_agent].network;  // governance-set, not attacker input
IERC20(_asset).safeTransfer(network, _amount);
ISymbioticNetworkMiddleware(network).distributeRewards(_agent, _asset);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The external callee is a governance-configured address, and there's no price-view being read mid-call. No attacker entry point, no manipulable value. Not exploitable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; check where the callee address comes from. If it's immutable or governance-set (not user input), and there's no manipulable view during the call, suppress.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Flagging a bug in code the client didn't write
&lt;/h2&gt;

&lt;p&gt;The last one is subtle: my scanner flagged an unchecked send in &lt;code&gt;LzApp.sol&lt;/code&gt; — LayerZero's base contract, vendored into the repo. It's not the client's code. Reporting it is noise (and slightly embarrassing — you're telling a founder to go fix LayerZero).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; skip vendored / third-party code (&lt;code&gt;node_modules&lt;/code&gt;, &lt;code&gt;@openzeppelin&lt;/code&gt;, LayerZero's &lt;code&gt;LzApp&lt;/code&gt; / &lt;code&gt;OApp&lt;/code&gt; / &lt;code&gt;OFT&lt;/code&gt; bases). Audit the client's contracts, not their dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  The point
&lt;/h2&gt;

&lt;p&gt;None of these fixes are clever AI. They're the exact checks a human auditor does in their head: &lt;em&gt;is it guarded? is the token curated? is success checked below? who controls the callee? is this even the client's code?&lt;/em&gt; Encode them deterministically and the false-positive flood drains away — permanently, on every future scan.&lt;/p&gt;

&lt;p&gt;I keep my pipeline at zero false positives across the entire OpenZeppelin library, because a report that cries wolf trains you to ignore it. Silence, on my reports, means something.&lt;/p&gt;

&lt;p&gt;If you're pre-mainnet and want a security first-pass that only surfaces real issues — hand-verified, no noise — I'm happy to take a look: &lt;a href="https://juan23z.github.io" rel="noopener noreferrer"&gt;juan23z.github.io&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>solidity</category>
      <category>security</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>My smart-contract scanner reports almost nothing — and that's the whole point</title>
      <dc:creator>juan23z</dc:creator>
      <pubDate>Wed, 05 Aug 2026 19:09:48 +0000</pubDate>
      <link>https://dev.to/juan23z/my-smart-contract-scanner-reports-almost-nothing-and-thats-the-whole-point-1gkk</link>
      <guid>https://dev.to/juan23z/my-smart-contract-scanner-reports-almost-nothing-and-thats-the-whole-point-1gkk</guid>
      <description>&lt;p&gt;Most Solidity security tools have the same failure mode: they cry wolf. You run them on an audited protocol and get 600 "findings," 98% of which are noise. The signal drowns. Worse — send a client a report full of false positives once, and you've burned your credibility.&lt;/p&gt;

&lt;p&gt;I've been building a scanner with the opposite goal: &lt;strong&gt;report almost nothing, but be right when it does.&lt;/strong&gt; Zero false positives, verified across the &lt;em&gt;entire&lt;/em&gt; OpenZeppelin library. Here's how that actually works, with three real examples from today's run against production protocols.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 1 — the "spec violation" that isn't
&lt;/h2&gt;

&lt;p&gt;A naive detector reads a NatSpec comment or a spec doc that says &lt;em&gt;"only the rate manager can update the rate"&lt;/em&gt;, then flags the function as a violation if it "can't prove" the restriction. On Ember's vaults today, that produced six &lt;code&gt;[real]&lt;/code&gt; findings — one of them a &lt;strong&gt;CRITICAL&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function pause() external onlyGuardian { ... }
function processWithdrawalRequests(uint256 n) external nonReentrant onlyOperator { ... }
function setMaxTVL(...) external onlyAdmin { ... }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every one of these is a &lt;em&gt;correctly access-controlled, intended&lt;/em&gt; feature. The "spec violation" was the tool listing the protocol's own role design and calling it a bug. All six were false positives.&lt;/p&gt;

&lt;p&gt;The fix is deterministic, not AI-guesswork: before emitting, &lt;strong&gt;find the affected function and check whether the restriction is actually enforced&lt;/strong&gt; (&lt;code&gt;onlyX&lt;/code&gt; / &lt;code&gt;onlyRole&lt;/code&gt; / &lt;code&gt;msg.sender ==&lt;/code&gt; / a &lt;code&gt;require&lt;/code&gt;). If it is — it's the design, not a violation. Suppress it. If there's genuinely &lt;em&gt;no&lt;/em&gt; enforcement, it still fires. Safe direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 2 — fee-on-transfer that's out of scope by design
&lt;/h2&gt;

&lt;p&gt;Another classic: a vault does &lt;code&gt;token.transferFrom(user, address(this), amount)&lt;/code&gt; and then uses &lt;code&gt;amount&lt;/code&gt; for accounting. Fee-on-transfer tokens send less than &lt;code&gt;amount&lt;/code&gt;, so the internal books inflate → the tool screams "insolvency."&lt;/p&gt;

&lt;p&gt;Real? Only if the protocol lets users deposit &lt;strong&gt;arbitrary&lt;/strong&gt; tokens. Most don't:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// deposit is gated by a curated registry / whitelist
uint256[] memory types = IRegistry(registry).batchGetAssetTypes(assets);
IRegistry(registry).batchProcessDeposit(creditor, assets, ids, amounts);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If governance curates which tokens can ever enter, a fee-on-transfer token simply isn't listed. It's an accepted design assumption, not an exploit. So the detector now checks for token curation (&lt;code&gt;registry&lt;/code&gt; / &lt;code&gt;isAllowedAsset&lt;/code&gt; / &lt;code&gt;whitelist&lt;/code&gt;) and stays quiet when it's present — but still fires on genuinely permissionless deposit paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 3 — the unchecked call that's actually checked
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(bool success, ) = recipient.call{ value: amount }("");
require(success, "transfer failed");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A regex that only looks at the call line flags this as "unchecked return value." Look one line down and the &lt;code&gt;require(success)&lt;/code&gt; is right there. &lt;code&gt;.transfer()&lt;/code&gt; gets flagged too — even though it auto-reverts. The fix: read the &lt;em&gt;next&lt;/em&gt; few lines, and know the difference between &lt;code&gt;.call&lt;/code&gt; and &lt;code&gt;.transfer&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why bother being this strict?
&lt;/h2&gt;

&lt;p&gt;Because the number that matters isn't how many findings you produce — it's how many are &lt;strong&gt;real&lt;/strong&gt;. A report a founder can trust is worth more than a wall of noise they have to triage themselves. I verified 13 flagged findings across audited protocols today; all 13 were false positives, and the pipeline now suppresses those classes automatically, forever. That's the moat: not more findings, fewer &lt;em&gt;wrong&lt;/em&gt; ones.&lt;/p&gt;

&lt;p&gt;If you're building something pre-mainnet and want a security first-pass that only tells you about real issues — no noise, hand-verified — I'm happy to take a look. You can find my work at &lt;a href="https://juan23z.github.io" rel="noopener noreferrer"&gt;juan23z.github.io&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written after a long day of teaching a scanner to shut up when it should.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>solidity</category>
      <category>security</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I ran my Solidity scanner on 6 top-audited DeFi protocols. Every 'critical' was a false positive — here's why.</title>
      <dc:creator>juan23z</dc:creator>
      <pubDate>Thu, 30 Jul 2026 22:08:33 +0000</pubDate>
      <link>https://dev.to/juan23z/i-ran-my-solidity-scanner-on-6-top-audited-defi-protocols-every-critical-was-a-false-positive--3jkd</link>
      <guid>https://dev.to/juan23z/i-ran-my-solidity-scanner-on-6-top-audited-defi-protocols-every-critical-was-a-false-positive--3jkd</guid>
      <description>&lt;p&gt;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. &lt;strong&gt;Precision, not recall, is what makes a security tool actually get used.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I've been building &lt;a href="https://github.com/juan23z/openclaw-audit" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt;, a heuristic Solidity scanner with the opposite bar: &lt;em&gt;silence on sound code.&lt;/em&gt; 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 — and &lt;strong&gt;hand-verified every single flag.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The result: &lt;strong&gt;14 HIGH/CRITICAL candidates across the six. Every one was a false positive.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The "donation attack" that isn't (a PaymentSplitter)
&lt;/h2&gt;

&lt;p&gt;The scanner flagged &lt;code&gt;balanceOf(address(this))&lt;/code&gt; used in accounting as a donation / share-inflation attack. But the contract was a fork of OpenZeppelin's &lt;code&gt;PaymentSplitter&lt;/code&gt;: &lt;strong&gt;shares are fixed at deployment&lt;/strong&gt;, 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. &lt;code&gt;balance + totalReleased&lt;/code&gt; is the canonical, correct accounting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; &lt;code&gt;balanceOf(this)&lt;/code&gt; in accounting is only a donation risk when &lt;em&gt;shares are minted against it&lt;/em&gt;. In a fixed-share pull-payment splitter, it's intended behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The "unprotected initialize" with a guard the scanner didn't recognize (Ajna)
&lt;/h2&gt;

&lt;p&gt;Ajna's pools were flagged as "unprotected &lt;code&gt;initialize()&lt;/code&gt;". But there it was, first line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (isPoolInitialized) revert AlreadyInitialized();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A one-time-init guard. Plenty of non-OpenZeppelin protocols protect &lt;code&gt;initialize&lt;/code&gt; with a boolean flag + revert instead of the &lt;code&gt;initializer&lt;/code&gt; modifier. A detector that only knows the OZ modifier misses it and screams.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; an initializer is protected if it has &lt;em&gt;any&lt;/em&gt; one-time guard — the OZ modifier, a boolean flag that reverts, factory/clone init, or &lt;code&gt;_disableInitializers()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. "Read-only reentrancy" in functions nobody uses as an oracle (Ajna, Liquity)
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;poolBalanceDetails()&lt;/code&gt;, &lt;code&gt;flashLoan()&lt;/code&gt;, &lt;code&gt;simulateRedemption()&lt;/code&gt; — all flagged for reading a balance after an external call. Read-only reentrancy (the Curve/Balancer class that paid $100k+ bounties) is only exploitable &lt;strong&gt;if an external protocol consumes the function as a price oracle.&lt;/strong&gt; A view utility in a &lt;code&gt;Multicall&lt;/code&gt; helper, a flash loan's repayment check, a &lt;code&gt;simulate*&lt;/code&gt; function — none of those are oracles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; read-only reentrancy needs an oracle consumer. &lt;code&gt;simulate&lt;/code&gt; / &lt;code&gt;preview&lt;/code&gt; / &lt;code&gt;*Details&lt;/code&gt; / &lt;code&gt;multicall&lt;/code&gt; / &lt;code&gt;flashLoan&lt;/code&gt; aren't oracles → not exploitable.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The "manipulable rate" that's bounded and role-gated (Ember)
&lt;/h2&gt;

&lt;p&gt;Flagged CRITICAL: "rate can be updated." But the rate was &lt;em&gt;managed and bounded&lt;/em&gt; — a &lt;code&gt;rateManager&lt;/code&gt; role, a &lt;code&gt;maxRateChangePerUpdate&lt;/code&gt; cap, a &lt;code&gt;rateUpdateInterval&lt;/code&gt;, 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; a role-updatable value bounded by max-change + interval is a &lt;em&gt;managed parameter&lt;/em&gt;, not a vulnerability (at most a centralization note).&lt;/p&gt;

&lt;h2&gt;
  
  
  5. "Fee-on-transfer not supported" — by design (Liquity)
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; FoT-not-supported is only a bug if the protocol accepts &lt;em&gt;arbitrary&lt;/em&gt; tokens. With vetted collateral, it's intended.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;Every 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 that &lt;strong&gt;when it (or I) stay silent, the silence means something.&lt;/strong&gt; A tool that cries wolf 38 times out of 40 trains you to ignore the 39th. The 39th is the real one.&lt;/p&gt;

&lt;p&gt;Each of these five FP classes is now a permanent fix in the detector, not a patch — so the next PaymentSplitter, the next flag-guarded &lt;code&gt;initialize&lt;/code&gt;, the next view utility, doesn't fire. That's how you drive false positives toward zero without going blind to the real thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scanner
&lt;/h2&gt;

&lt;p&gt;OpenClaw is calibrated against the codebases everyone treats as a gold standard — OpenZeppelin, Solady, Solmate, Uniswap v2/v3/v4, Permit2, Morpho Blue, PRBMath. &lt;strong&gt;608 source files, 14 total flags, 0 across the entire OpenZeppelin library.&lt;/strong&gt; MIT, pure Python, runs as a GitHub Action that comments on every PR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pipx run &lt;span class="nt"&gt;--spec&lt;/span&gt; git+https://github.com/juan23z/openclaw-audit openclaw-audit &amp;lt;repo&amp;gt; &lt;span class="nt"&gt;--out&lt;/span&gt; ./report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Honest about what it is &lt;em&gt;not&lt;/em&gt;: it's heuristic, not formal verification. It catches &lt;em&gt;classes&lt;/em&gt; of 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.&lt;/p&gt;

&lt;p&gt;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 fast &lt;a href="https://juan23z.github.io/pricing.html" rel="noopener noreferrer"&gt;pre-mainnet reviews&lt;/a&gt;. Or just run the free scanner and keep the signal.&lt;/p&gt;

&lt;p&gt;I'd genuinely like feedback on the false-positive classes above — which ones have bitten you with Slither/others?&lt;/p&gt;

</description>
      <category>solidity</category>
      <category>security</category>
      <category>ethereum</category>
      <category>smartcontracts</category>
    </item>
    <item>
      <title>What does a smart-contract audit actually cost? An honest breakdown for small protocols</title>
      <dc:creator>juan23z</dc:creator>
      <pubDate>Fri, 24 Jul 2026 09:15:57 +0000</pubDate>
      <link>https://dev.to/juan23z/what-does-a-smart-contract-audit-actually-cost-an-honest-breakdown-for-small-protocols-5gfm</link>
      <guid>https://dev.to/juan23z/what-does-a-smart-contract-audit-actually-cost-an-honest-breakdown-for-small-protocols-5gfm</guid>
      <description>&lt;p&gt;You've built a protocol. You know you should get the contracts reviewed before mainnet. So you ask around — and the answers range from "a friend will glance at it for free" to "$80k and a two-month waitlist." That spread is real, and it's paralyzing when you're a small team trying to ship.&lt;/p&gt;

&lt;p&gt;Here's an honest breakdown of what actually drives the number, and how to spend a small budget well.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest ranges (2026)
&lt;/h2&gt;

&lt;p&gt;Rough, real-world bands for EVM smart-contract security work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Community / friend review&lt;/strong&gt; — free to a few hundred. Better than nothing, wildly variable, no accountability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Independent auditor, small scope&lt;/strong&gt; — ~$1k–8k. A single experienced reviewer on a tight, well-defined scope (a token, a vault, a staking module). This is where most small protocols get the best value-per-dollar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boutique firm&lt;/strong&gt; — ~$10k–40k. A team, a formal report, a brand you can show investors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Top-tier firm / competitive audit&lt;/strong&gt; — $50k+. Big scope, big name, big timeline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If someone quotes you a flat number without asking about your scope first, be skeptical — the scope &lt;em&gt;is&lt;/em&gt; the price.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the spread is so wide
&lt;/h2&gt;

&lt;p&gt;Four things move the number:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lines of code in scope.&lt;/strong&gt; Someone has to read the money-moving paths line by line. Cost scales with that, not with your repo size — a 20k-line repo with a 600-line core is a small job if you scope it right.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complexity.&lt;/strong&gt; Novel math, cross-contract interactions, upgradeability, oracle dependencies, anything that "nobody drew on the whiteboard" — that's where time (and risk) lives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brand.&lt;/strong&gt; You pay a premium for a well-known name on the report. Sometimes worth it for fundraising optics; often not, if you just want the code to be safe.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeline.&lt;/strong&gt; Rush = premium.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What you're actually paying for
&lt;/h2&gt;

&lt;p&gt;Not a list of findings. You're paying for &lt;strong&gt;judgment&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A scanner will flag 40 things. Usually 1–2 are real. The entire value of a good reviewer is telling you &lt;em&gt;which&lt;/em&gt; — and staying silent on the 38 that aren't. A cheap review that cries wolf 40 times is worse than no review: it trains you to ignore the report, and the one finding that mattered goes out with the noise.&lt;/p&gt;

&lt;p&gt;So when you compare quotes, don't compare "number of findings." Compare &lt;strong&gt;signal&lt;/strong&gt;. Ask for a sample report. If it's 40 low-severity "consider using a constant here" items padding out two real issues, you're paying for noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to spend a small budget well
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clean the obvious stuff first.&lt;/strong&gt; Before you pay anyone, run a self-check: who can call what, is the initializer locked, is there a first-depositor guard on your vault, do rescue funds go to a fixed address. Fixing the boring bugs yourself means the reviewer's time goes to the hard ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scope tightly.&lt;/strong&gt; Get your &lt;em&gt;core&lt;/em&gt; — the contracts that hold or move funds — reviewed deeply. Don't spread a small budget thin across peripheral code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefer someone who'll tell you plainly when your code is solid.&lt;/strong&gt; An honest "this part is fine, spend your money on that part instead" is worth paying for. Overselling is the tell.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get a free first-pass before you commit.&lt;/strong&gt; It shows you the reviewer's signal-to-noise before any money changes hands.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The uncomfortable truth
&lt;/h2&gt;

&lt;p&gt;An audit is a &lt;strong&gt;snapshot&lt;/strong&gt;. It expires the moment you change a line of code. And it catches &lt;em&gt;classes&lt;/em&gt; of bugs — access control, vault math, reentrancy, oracle staleness — not your protocol's bespoke economic logic, the one where two functions interact in a way nobody modeled.&lt;/p&gt;

&lt;p&gt;So budget for two things, not one: a deep review of the &lt;strong&gt;novel&lt;/strong&gt; parts (where the real risk is), and &lt;strong&gt;ongoing monitoring&lt;/strong&gt; so a later change doesn't quietly reopen a hole. A one-time stamp on code that keeps changing is a false sense of safety.&lt;/p&gt;




&lt;p&gt;I run security reviews for small and new protocols — fast, honest, and I'll tell you plainly when your code is solid. I keep my open-source scanner at &lt;strong&gt;zero false positives across the entire OpenZeppelin library&lt;/strong&gt;, because a tool (or a person) that cries wolf isn't worth listening to.&lt;/p&gt;

&lt;p&gt;If you want a free first-pass before you ship, it's open source and runs in one command: &lt;strong&gt;&lt;a href="https://github.com/juan23z/openclaw-audit" rel="noopener noreferrer"&gt;https://github.com/juan23z/openclaw-audit&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Good luck on mainnet. 🚀&lt;/p&gt;

</description>
      <category>solidity</category>
      <category>security</category>
      <category>ethereum</category>
      <category>smartcontracts</category>
    </item>
    <item>
      <title>Shipping a Solidity contract to mainnet? Do this 20-minute self-check first</title>
      <dc:creator>juan23z</dc:creator>
      <pubDate>Thu, 23 Jul 2026 21:34:30 +0000</pubDate>
      <link>https://dev.to/juan23z/shipping-a-solidity-contract-to-mainnet-do-this-20-minute-self-check-first-399o</link>
      <guid>https://dev.to/juan23z/shipping-a-solidity-contract-to-mainnet-do-this-20-minute-self-check-first-399o</guid>
      <description>&lt;p&gt;You built something. Tests pass. You're days from mainnet. Before you either skip security entirely (please don't) or spend weeks lining up a full audit, here's a self-check you can run in 20 minutes that catches the mistakes I see most often in first-time deployments.&lt;/p&gt;

&lt;p&gt;I run security reviews for small and new protocols, and the same handful of issues come up again and again. None of these need a tool — just your eyes and this list.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Who can call what?
&lt;/h2&gt;

&lt;p&gt;Open every &lt;code&gt;external&lt;/code&gt;/&lt;code&gt;public&lt;/code&gt; function that moves funds, mints, pauses, or upgrades. For each, ask: &lt;em&gt;should a random address be able to call this?&lt;/em&gt; If not — is there an &lt;code&gt;onlyOwner&lt;/code&gt; / &lt;code&gt;onlyRole&lt;/code&gt; / &lt;code&gt;require(msg.sender == ...)&lt;/code&gt; guarding it, &lt;strong&gt;in the function itself or in every internal function it calls?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The classic bug isn't a missing modifier. It's a function that &lt;em&gt;looks&lt;/em&gt; unguarded but delegates to a guarded internal one (fine), or one that looks guarded but the guard is in a branch a caller can skip (not fine). Trace the call, don't trust the signature.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The first-depositor trap (if you have a vault)
&lt;/h2&gt;

&lt;p&gt;If you mint shares from deposits (ERC-4626 or anything share-based), the first depositor can sometimes donate assets directly to the contract to inflate the share price, so the &lt;em&gt;second&lt;/em&gt; depositor rounds down to zero shares and loses funds. Fix: virtual shares, a dead-shares mint at deploy, or a minimum-liquidity lock. OpenZeppelin's ERC-4626 handles this out of the box — a hand-rolled vault usually doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Reentrancy — but only the real kind
&lt;/h2&gt;

&lt;p&gt;Not every external call is reentrancy. It's a bug when an attacker-controlled call can re-enter and corrupt &lt;strong&gt;shared storage&lt;/strong&gt; before you've updated it. Quick checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do you update state &lt;em&gt;before&lt;/em&gt; the external transfer (checks-effects-interactions)?&lt;/li&gt;
&lt;li&gt;Is there a &lt;code&gt;nonReentrant&lt;/code&gt; on functions that move value?&lt;/li&gt;
&lt;li&gt;Is the call target a &lt;em&gt;trusted, immutable&lt;/em&gt; contract, or an arbitrary address the attacker supplies?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A call to a protocol-owned contract, or a &lt;code&gt;memory&lt;/code&gt;/local variable written after the call, is usually not exploitable. Don't flag it just because Slither did.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Where do "rescue" funds go?
&lt;/h2&gt;

&lt;p&gt;Got a &lt;code&gt;sweep&lt;/code&gt; / &lt;code&gt;rescue&lt;/code&gt; / &lt;code&gt;emergencyWithdraw&lt;/code&gt;? Check the destination. If funds can only go to a &lt;strong&gt;fixed, stored address&lt;/strong&gt; (treasury/owner), permissionless is fine — the caller can't redirect them. If the caller picks the destination, that's a drain waiting to happen. This one is a coin-flip in the reviews I do.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Oracle freshness (if you read a price)
&lt;/h2&gt;

&lt;p&gt;Reading Chainlink? You need an &lt;code&gt;updatedAt&lt;/code&gt; staleness check, not just &lt;code&gt;answer &amp;gt; 0&lt;/code&gt;. But — and this is where half the "findings" you'll read online are wrong — plenty of protocols check it one layer up or rely on a heartbeat. Look at the whole path before you panic.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Upgradeable? Check the initializer.
&lt;/h2&gt;

&lt;p&gt;Proxy pattern? Make sure &lt;code&gt;initialize()&lt;/code&gt; has the &lt;code&gt;initializer&lt;/code&gt; modifier, can't be called twice, and that the implementation contract itself can't be initialized-then-selfdestructed. A left-open initializer is one of the most common takeover bugs.&lt;/p&gt;




&lt;p&gt;That's the 20-minute pass. It catches the obvious stuff — and honestly, most exploits of small protocols &lt;em&gt;are&lt;/em&gt; the obvious stuff shipped in a hurry.&lt;/p&gt;

&lt;p&gt;What it &lt;strong&gt;doesn't&lt;/strong&gt; catch is the protocol-specific logic bug: the one where your staking math rounds the wrong way, or two functions interact in a way nobody drew on the whiteboard. That's what a human review is for.&lt;/p&gt;

&lt;p&gt;My rule when I do those reviews: I only flag what's actually exploitable. I keep my scanner at &lt;strong&gt;zero false positives across the entire OpenZeppelin library&lt;/strong&gt; — so when it (or I) stay silent on your code, that silence means something. A report that cries wolf 40 times trains you to ignore the one that matters.&lt;/p&gt;

&lt;p&gt;If you want a free first pass before you ship, the scanner is open-source and runs in one command: &lt;strong&gt;&lt;a href="https://github.com/juan23z/openclaw-audit" rel="noopener noreferrer"&gt;https://github.com/juan23z/openclaw-audit&lt;/a&gt;&lt;/strong&gt;. And if you'd rather have a person read the tricky parts — fast, honest, and I'll tell you plainly when your code is solid — that's what I do.&lt;/p&gt;

&lt;p&gt;Good luck on mainnet. 🚀&lt;/p&gt;

</description>
      <category>solidity</category>
      <category>security</category>
      <category>ethereum</category>
      <category>smartcontracts</category>
    </item>
    <item>
      <title>I ran my Solidity scanner across the 10 most-audited codebases in web3. Here's every flag.</title>
      <dc:creator>juan23z</dc:creator>
      <pubDate>Tue, 21 Jul 2026 16:36:35 +0000</pubDate>
      <link>https://dev.to/juan23z/i-ran-my-solidity-scanner-across-the-10-most-audited-codebases-in-web3-heres-every-flag-2mbk</link>
      <guid>https://dev.to/juan23z/i-ran-my-solidity-scanner-across-the-10-most-audited-codebases-in-web3-heres-every-flag-2mbk</guid>
      <description>&lt;p&gt;The dirty secret of automated smart-contract security tools isn't that they miss bugs. It's that they cry wolf. Point a typical static analyzer at a clean codebase and you'll get forty "criticals" — and after the third false alarm, your team stops reading the output entirely. A tool nobody trusts is worse than no tool at all.&lt;/p&gt;

&lt;p&gt;So when I built &lt;a href="https://github.com/juan23z/openclaw-audit" rel="noopener noreferrer"&gt;OpenClaw Audit&lt;/a&gt; — a free, open-source heuristic scanner for Solidity — I held it to a different bar. Not "how many things can it flag?" but the opposite: &lt;strong&gt;can it stay silent on code that's already sound?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There's a clean way to test that. Run it across the most-reviewed Solidity codebases on earth — the libraries that thousands of protocols depend on, audited many times over — and see how much noise it makes. A calibrated tool should be &lt;em&gt;quiet&lt;/em&gt; here.&lt;/p&gt;

&lt;p&gt;Here's what it found. Every number is reproducible in one command; every flag I checked by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  The results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Codebase&lt;/th&gt;
&lt;th&gt;Source files&lt;/th&gt;
&lt;th&gt;Candidates&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpenZeppelin Contracts&lt;/td&gt;
&lt;td&gt;247&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;forge-std&lt;/td&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Uniswap Permit2&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PRBMath&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Uniswap v2-core&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Uniswap v3-core&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Uniswap v4-core&lt;/td&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Solmate&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Morpho Blue&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Solady&lt;/td&gt;
&lt;td&gt;140&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;608&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;14&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Fourteen candidate observations across 608 source files. Four of the ten codebases came back &lt;strong&gt;completely clean&lt;/strong&gt;. That's the headline: on the most-audited Solidity in existence, the scanner is nearly silent.&lt;/p&gt;

&lt;p&gt;But the more interesting part is &lt;em&gt;what&lt;/em&gt; it flagged — because the flags aren't random noise. They cluster on genuinely notable spots.&lt;/p&gt;

&lt;h2&gt;
  
  
  The flags, one by one
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Uniswap v3 &amp;amp; v4 — &lt;code&gt;initialize()&lt;/code&gt; flagged as "unprotected."&lt;/strong&gt; False positive, and an instructive one. A Uniswap pool's &lt;code&gt;initialize()&lt;/code&gt; sets its starting price, and it's &lt;em&gt;permissionless by design&lt;/em&gt; — anyone can call it once on a fresh pool. A heuristic sees "an initialize function with no access control" and raises a hand. A human sees the design and clears it in five seconds. That's the model: the tool surfaces, the human decides.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solmate — a real, known design gap.&lt;/strong&gt; Solmate's minimal ERC-4626 deliberately omits the virtual-share protection against the &lt;a href="https://mixbytes.io/blog/overview-of-the-inflation-attack" rel="noopener noreferrer"&gt;first-depositor inflation attack&lt;/a&gt;, leaving it to the integrator. OpenZeppelin's ERC-4626 adds that defense; Solmate's doesn't. The scanner flags the difference — and it's &lt;em&gt;right&lt;/em&gt; to. This is signal, not noise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Morpho Blue — reentrancy candidates.&lt;/strong&gt; False positives. Morpho Blue is formally verified with the Certora Prover, and the external calls the heuristic latched onto are &lt;code&gt;safeTransfer&lt;/code&gt;s with correct effects-before-interactions accounting. A pattern-matcher can't see a formal proof; a reviewer can.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solady — seven flags, all false positives.&lt;/strong&gt; A documented &lt;code&gt;tx.origin&lt;/code&gt; rescue default in &lt;code&gt;Lifebuoy&lt;/code&gt; (with explicit warnings in the code), UUPS/ERC-1967 upgrade authorization the regex doesn't parse, and one intentional math ordering. Solady is some of the most carefully-optimized Solidity written; the "issues" are the tool not understanding assembly-level auth, not real holes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters more than a big number
&lt;/h2&gt;

&lt;p&gt;I could have tuned the detectors until they screamed on everything and called it "thorough." That's easy, and useless. The skill in this job isn't generating findings — it's &lt;strong&gt;not drowning the two that matter under thirty-eight that don't.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A clean run on OpenZeppelin, forge-std, Permit2 and PRBMath means that when this tool &lt;em&gt;does&lt;/em&gt; flag something in &lt;em&gt;your&lt;/em&gt; code, it's worth a look. And when a human (me) reviews the output, I'm clearing a handful of explainable candidates — not wading through a wall of red.&lt;/p&gt;

&lt;p&gt;The goal was never a tool that says "zero bugs." Nothing can promise that. The goal is &lt;strong&gt;signal over noise&lt;/strong&gt; — and a claim you can check yourself rather than one you have to trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check it yourself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &lt;span class="nt"&gt;--depth&lt;/span&gt; 1 https://github.com/OpenZeppelin/openzeppelin-contracts /tmp/oz
pipx run &lt;span class="nt"&gt;--spec&lt;/span&gt; git+https://github.com/juan23z/openclaw-audit openclaw-audit /tmp/oz
&lt;span class="c"&gt;# → 0 candidate observations across 247 client .sol contracts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap in any repo above — or your own. The full &lt;a href="https://github.com/juan23z/openclaw-audit/blob/main/CALIBRATION.md" rel="noopener noreferrer"&gt;Calibration Report&lt;/a&gt; and the scanner (MIT-licensed, no API keys, GitHub Action included) are on GitHub.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I do fast, honest Solidity security reviews — heuristics to surface, manual verification to confirm, and a straight answer when your code is solid. Free scanner: &lt;a href="https://github.com/juan23z/openclaw-audit" rel="noopener noreferrer"&gt;github.com/juan23z/openclaw-audit&lt;/a&gt; · human review + continuous monitoring: &lt;a href="https://juan23z.github.io" rel="noopener noreferrer"&gt;juan23z.github.io&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>solidity</category>
      <category>security</category>
      <category>ethereum</category>
      <category>smartcontracts</category>
    </item>
    <item>
      <title>How to tell if a smart-contract audit is worth paying for — a founder's checklist</title>
      <dc:creator>juan23z</dc:creator>
      <pubDate>Mon, 20 Jul 2026 14:19:52 +0000</pubDate>
      <link>https://dev.to/juan23z/how-to-tell-if-a-smart-contract-audit-is-worth-paying-for-a-founders-checklist-3d03</link>
      <guid>https://dev.to/juan23z/how-to-tell-if-a-smart-contract-audit-is-worth-paying-for-a-founders-checklist-3d03</guid>
      <description>&lt;p&gt;You're about to put real money on-chain, and you know you need a security review. So you go looking — and the market is bewildering. A firm quotes you $30,000 and a six-week wait. A Fiverr gig offers "full audit, 24h delivery" for $60. How are you supposed to tell what's real?&lt;/p&gt;

&lt;p&gt;I do these reviews for a living, so let me hand you the checklist I'd use if I were the one buying. Five questions. Ask every auditor.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. "Can I see a sample report — and does it include things that were &lt;em&gt;fine&lt;/em&gt;?"
&lt;/h2&gt;

&lt;p&gt;A report that's all red flags is a red flag. A real review says &lt;em&gt;"the access control is correct, here's why; the staking math is sound, here's the invariant I checked."&lt;/em&gt; A &lt;strong&gt;clean bill on what checks out&lt;/strong&gt; is a finding — it's the auditor telling you where they looked and found nothing, which is exactly what you need to know before you ship.&lt;/p&gt;

&lt;p&gt;If every sample report is a wall of "criticals," you're looking at a scanner's raw output, not an audit.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. "What's your false-positive rate — and can you prove it?"
&lt;/h2&gt;

&lt;p&gt;This is the whole game. Anyone can run a static analyzer and paste 40 findings. The skill is in &lt;em&gt;not&lt;/em&gt; drowning the two real bugs in thirty-eight non-issues that waste your engineers' week.&lt;/p&gt;

&lt;p&gt;Ask for a concrete, checkable claim. Mine, for example: my detectors run &lt;strong&gt;clean — zero findings — across the entire OpenZeppelin library&lt;/strong&gt; (all 247 source files). That's verifiable in one command; you can clone OZ and run it yourself. If a tool can't stay silent on the most-reviewed Solidity code on earth, it has no business flagging yours.&lt;/p&gt;

&lt;p&gt;Vague answers here ("very low", "we're very accurate") mean they've never measured it.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. "Every finding — is there a proof of concept?"
&lt;/h2&gt;

&lt;p&gt;A "High: reentrancy possible" with no PoC is a hypothesis, not a finding. The auditor should be able to show you &lt;em&gt;the exact call sequence&lt;/em&gt; that triggers the bug, ideally as a runnable test.&lt;/p&gt;

&lt;p&gt;The corollary matters just as much: a good auditor will &lt;strong&gt;retract&lt;/strong&gt; things that don't hold up. I recently spent an hour building the case for a fund-locking overflow — then checked one constant, realized it would take 10¹⁵ years to trigger, and deleted it. That deletion is the product. You're paying for the judgment to know the difference between "looks scary" and "is exploitable."&lt;/p&gt;

&lt;h2&gt;
  
  
  4. "What happens after I fix the bugs?"
&lt;/h2&gt;

&lt;p&gt;Two things separate a one-off from a real engagement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A free re-review after remediation.&lt;/strong&gt; Fixing a bug can open a new one. If "we found it, good luck" is the whole deal, that's half a service.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuous coverage.&lt;/strong&gt; Your code doesn't freeze at launch — every push can introduce a vulnerability. The best security posture isn't a one-time PDF; it's a re-scan on every commit plus a monthly report. Ask whether they offer ongoing monitoring, not just a snapshot.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. "Will you tell me plainly when my code is good?"
&lt;/h2&gt;

&lt;p&gt;This sounds soft. It's the most important one. An auditor with incentives to find problems will find "problems." You want the person who, when your contract is genuinely well-built, &lt;em&gt;says so&lt;/em&gt; — and tells you the one area that would actually benefit from a deeper look.&lt;/p&gt;

&lt;p&gt;Honesty is the only thing you can't verify from the outside until it's too late. So probe for it in the conversation. Do they hedge everything to cover themselves, or do they commit to clear judgments and explain their reasoning?&lt;/p&gt;




&lt;h2&gt;
  
  
  The uncomfortable middle
&lt;/h2&gt;

&lt;p&gt;Here's what the checklist reveals: the $60 gig usually fails #2 and #3 (it's scanner output, no PoCs, no measured FP rate). The $30k firm passes everything — but for a small protocol pre-mainnet, the price and timeline can be out of reach.&lt;/p&gt;

&lt;p&gt;There's a real middle: independent reviewers who are &lt;strong&gt;fast, affordable, and rigorous&lt;/strong&gt; — who measure their false-positive rate, ship PoCs, give you a clean bill when it's warranted, and offer ongoing monitoring. That's the segment I work in, and it's the right fit for most teams launching their first serious contract.&lt;/p&gt;

&lt;p&gt;Whoever you hire, run the five questions. The good ones will welcome them.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I do fast, honest Solidity security reviews — custom detectors plus manual verification, zero false-positive noise, PoCs for anything real, and a straight answer when your code is solid. Details in my profile.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>solidity</category>
      <category>smartcontracts</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I almost reported a critical bug that didn't exist. One constant saved me.</title>
      <dc:creator>juan23z</dc:creator>
      <pubDate>Sun, 19 Jul 2026 21:56:07 +0000</pubDate>
      <link>https://dev.to/juan23z/i-almost-reported-a-critical-bug-that-didnt-exist-one-constant-saved-me-137b</link>
      <guid>https://dev.to/juan23z/i-almost-reported-a-critical-bug-that-didnt-exist-one-constant-saved-me-137b</guid>
      <description>&lt;p&gt;Last week I was reviewing the staking engine of a protocol before its mainnet launch. Deep in a 1,400-line contract, I found what looked like a serious bug.&lt;/p&gt;

&lt;p&gt;The reward math multiplied three values before dividing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uint256 delta = (lot.amount * rBase * midpointRate) / (RAY * RAY);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multiply-before-divide. If that intermediate product overflows &lt;code&gt;uint256&lt;/code&gt;, the whole epoch settlement reverts — and since every &lt;code&gt;stake&lt;/code&gt;, &lt;code&gt;withdraw&lt;/code&gt;, and &lt;code&gt;setStake&lt;/code&gt; runs it, the post's funds get &lt;strong&gt;permanently frozen&lt;/strong&gt;. That's a High-severity, fund-locking DoS.&lt;/p&gt;

&lt;p&gt;I had the finding half-written. &lt;code&gt;lot.amount&lt;/code&gt; can reach 10M tokens (&lt;code&gt;1e25&lt;/code&gt;). &lt;code&gt;rBase&lt;/code&gt; grows with elapsed time. &lt;code&gt;midpointRate&lt;/code&gt; can hit &lt;code&gt;RAY&lt;/code&gt;. Multiply those and you blow past &lt;code&gt;2^256&lt;/code&gt;... I was ready to send it.&lt;/p&gt;

&lt;p&gt;Then I did the one thing that separates a real audit from false-positive spam: &lt;strong&gt;I checked the actual constants before writing the claim.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uint256 private constant RAY = 1e18;  // I'd assumed 1e27
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;RAY&lt;/code&gt; was &lt;code&gt;1e18&lt;/code&gt;, not the &lt;code&gt;1e27&lt;/code&gt; I'd been carrying in my head. And the interest rate had a hard cap — &lt;code&gt;MAX_RATE_MAX_RAY = 5e18&lt;/code&gt;, enforced even against a fully-captured timelock. I ran the numbers with the &lt;em&gt;real&lt;/em&gt; values:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For that multiplication to overflow, the protocol would need to go &lt;strong&gt;2.3 × 10¹⁵ years&lt;/strong&gt; without a single state update.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not reachable. &lt;strong&gt;The bug didn't exist.&lt;/strong&gt; I deleted the finding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters more than the bug would have
&lt;/h2&gt;

&lt;p&gt;If I'd sent that report, here's what happens: the team's engineer clones the repo, plugs in the real constants, and realizes in ten minutes that I flagged an overflow that can't happen. Every other finding in my report now gets read with a raised eyebrow. &lt;strong&gt;My credibility — the entire product — is gone.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the dirty secret of automated smart-contract auditing: &lt;strong&gt;the bottleneck isn't finding issues. It's not drowning the real ones in false positives.&lt;/strong&gt; Anyone can run a scanner and paste 40 "criticals." A team that has to triage 40 flags to find the 2 that matter will — correctly — stop trusting you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bar I hold: zero false positives on OpenZeppelin
&lt;/h2&gt;

&lt;p&gt;Here's a concrete, checkable standard I keep: &lt;strong&gt;my scanner runs clean — zero findings — across the entire OpenZeppelin contracts library.&lt;/strong&gt; All 247 source files. If a tool can't stay silent on the most-reviewed Solidity code on earth, it has no business flagging &lt;em&gt;your&lt;/em&gt; code.&lt;/p&gt;

&lt;p&gt;It's a claim I re-verify obsessively, because it's easy to break. In fact, I recently caught it broken. My access-control detector had started flagging OZ's &lt;code&gt;ERC1967Utils.upgradeToAndCall&lt;/code&gt; as "unprotected" — but that function is &lt;code&gt;internal&lt;/code&gt;. It isn't externally callable; it &lt;em&gt;can't&lt;/em&gt; be an access-control bug (the auth lives in the public entry point that calls it). One rule — &lt;em&gt;skip internal/private functions&lt;/em&gt; — and OZ was clean again.&lt;/p&gt;

&lt;p&gt;A few more false-positive classes I've had to kill, each one a lesson:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The virtual-shares &lt;code&gt;+ 1&lt;/code&gt; isn't "rounding up."&lt;/strong&gt; A correct ERC-4626 vault writes &lt;code&gt;totalSupply() + 10**offset&lt;/code&gt; and &lt;code&gt;totalAssets() + 1&lt;/code&gt; — that's the OZ inflation-attack mitigation. A naive detector reads the &lt;code&gt;+ 1&lt;/code&gt; as ceil-rounding and screams "share inflation risk" on &lt;em&gt;correct&lt;/em&gt; code. It would fire on almost every well-built vault.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A permissionless &lt;code&gt;emergencyWithdraw()&lt;/code&gt; that moves funds into internal claimable state isn't a fund-sweep.&lt;/strong&gt; The caller can't redirect anything; it's a keeper-resilience pattern. Flagging it wastes everyone's time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth written in assembly is still auth.&lt;/strong&gt; &lt;code&gt;if iszero(eq(sload(admin), caller())) { revert }&lt;/code&gt; protects a function just as well as &lt;code&gt;onlyOwner&lt;/code&gt;. Gas-optimized code isn't unprotected code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these looks like a bug to a pattern-matcher. None of them are. Knowing the difference is the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  The discipline, stated plainly
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Verify against reality, not against your assumptions.&lt;/strong&gt; I nearly shipped a bug because I remembered a constant wrong. Read the actual value. Every time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detectors surface; a human confirms.&lt;/strong&gt; Heuristics are for &lt;em&gt;recall&lt;/em&gt; — cast a wide net. Precision comes from a person who reads the code and asks "is this actually exploitable?" before it reaches the client.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A clean bill is a finding too.&lt;/strong&gt; When I dug into that vault's donation-attack surface and found the mitigation was there — reimplemented correctly, just not inherited from OZ — I wrote &lt;em&gt;that down&lt;/em&gt; and said so. "It's solid, and here's why" is worth as much as a bug. It's honest, and it's what a team actually needs before they ship.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The overflow that wasn't taught me the same thing the clean vault did: &lt;strong&gt;the value isn't in what you flag. It's in what you can stand behind.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I do fast, honest Solidity security reviews — custom detectors plus manual verification, no false-positive noise. I only report what's real, and I'll tell you plainly when your code is solid. If that's the kind of review you want before mainnet, my details are in my profile.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>solidity</category>
      <category>smartcontracts</category>
      <category>webdev</category>
    </item>
    <item>
      <title>5 false positives that fool naive Solidity scanners (and how to kill them at the root)</title>
      <dc:creator>juan23z</dc:creator>
      <pubDate>Fri, 17 Jul 2026 16:01:47 +0000</pubDate>
      <link>https://dev.to/juan23z/5-false-positives-that-fool-naive-solidity-scanners-and-how-to-kill-them-at-the-root-4oel</link>
      <guid>https://dev.to/juan23z/5-false-positives-that-fool-naive-solidity-scanners-and-how-to-kill-them-at-the-root-4oel</guid>
      <description>&lt;p&gt;In a paid audit, a false positive is not just noise — it's a credibility killer. Hand a founder a "CRITICAL: unprotected fund sweep" that turns out to be safe-by-design, and you've spent your only currency: trust.&lt;/p&gt;

&lt;p&gt;We run a heuristic scanner over Solidity before a human ever looks at the code. The scanner's job is &lt;strong&gt;recall&lt;/strong&gt; (surface everything suspicious); the human's job is &lt;strong&gt;precision&lt;/strong&gt; (confirm what's real). But every false positive the scanner emits is time the human burns — and if one ever leaks into a report, it's reputation burned.&lt;/p&gt;

&lt;p&gt;Here are five patterns that fool naive static heuristics, why each is a false positive, and how we fixed them &lt;strong&gt;at the root&lt;/strong&gt; — not by muting the rule, but by teaching it what it was missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The donation "vuln" that is actually the defense
&lt;/h2&gt;

&lt;p&gt;The classic donation/inflation check flags any &lt;code&gt;balanceOf(address(this))&lt;/code&gt; used in accounting. But look at this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uint256 balanceBefore = token.balanceOf(address(this));
token.safeTransferFrom(msg.sender, address(this), amount);
uint256 balanceAfter  = token.balanceOf(address(this));
uint256 received = balanceAfter - balanceBefore;   // credited to the user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the &lt;strong&gt;fee-on-transfer-safe&lt;/strong&gt; pattern. A donation before the call inflates &lt;code&gt;balanceBefore&lt;/code&gt; and &lt;code&gt;balanceAfter&lt;/code&gt; equally, so &lt;code&gt;received&lt;/code&gt; is unchanged. The &lt;code&gt;balanceOf&lt;/code&gt; here is the &lt;em&gt;mitigation&lt;/em&gt;, not the bug. A scanner that greps for &lt;code&gt;balanceOf(this)&lt;/code&gt; without recognizing the before/after &lt;strong&gt;delta&lt;/strong&gt; flags the exact code that defends against the attack it's warning about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root fix:&lt;/strong&gt; recognize the &lt;code&gt;balanceBefore&lt;/code&gt;/&lt;code&gt;balanceAfter&lt;/code&gt; delta idiom as safe; only flag when the raw balance feeds a share-price or ratio directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. &lt;code&gt;corruptedReserve =&lt;/code&gt; is not "reserve accounting"
&lt;/h2&gt;

&lt;p&gt;A subtler version of the same detector matched a share-accounting pattern via a loose regex like &lt;code&gt;reserve\s*=&lt;/code&gt;. It fired on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;corruptedReserve = toSweep;   // matched "Reserve =" as a substring
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a variable &lt;em&gt;name&lt;/em&gt;, not accounting. The lesson: &lt;strong&gt;word-boundary your patterns.&lt;/strong&gt; &lt;code&gt;\breserve\s*=&lt;/code&gt; doesn't match inside &lt;code&gt;corruptedReserve&lt;/code&gt;, &lt;code&gt;sharePrice&lt;/code&gt;, or &lt;code&gt;collateralRatio&lt;/code&gt;. A single missing &lt;code&gt;\b&lt;/code&gt; manufactures false criticals.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Permissionless ≠ unprotected, when the destination is fixed
&lt;/h2&gt;

&lt;p&gt;Access-control detectors love to flag &lt;code&gt;sweep&lt;/code&gt;/&lt;code&gt;rescue&lt;/code&gt;/&lt;code&gt;withdrawAll&lt;/code&gt; functions with no &lt;code&gt;onlyOwner&lt;/code&gt;. But callable-by-anyone is only dangerous if the caller can &lt;strong&gt;redirect the funds&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function sweepUnclaimed() external {          // no modifier — flagged CRITICAL
    uint256 amount = token.balanceOf(address(this));
    token.safeTransfer(recoveryAddress, amount);   // fixed, set at init
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whoever calls it, the money goes to a predetermined &lt;code&gt;recoveryAddress&lt;/code&gt;. This is a &lt;em&gt;keeper&lt;/em&gt; pattern — permissionless on purpose, safe by construction. It's a false positive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root fix:&lt;/strong&gt; for a sweep, only flag when the transfer destination is &lt;code&gt;msg.sender&lt;/code&gt;, &lt;code&gt;tx.origin&lt;/code&gt;, or a &lt;strong&gt;function parameter&lt;/strong&gt; (something the caller controls). A fixed state variable → safe. (And watch the error direction: when in doubt, &lt;em&gt;keep&lt;/em&gt; the finding — better a reviewed false positive than a blinded real one.)&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Reentrancy on a local variable is not reentrancy
&lt;/h2&gt;

&lt;p&gt;"External call before state update" is a great heuristic — until it counts a &lt;strong&gt;local&lt;/strong&gt; as state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;token.transfer(to, amount);
uint256 len = holders.length;   // "state written after external call" — but it's a local!
for (uint256 i; i &amp;lt; len; ++i) { ... }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;len&lt;/code&gt; is a stack variable. So is &lt;code&gt;uint256 gasUsed = ...&lt;/code&gt;. So is a write to a &lt;code&gt;memory&lt;/code&gt; array (&lt;code&gt;returnData[i] = ...&lt;/code&gt;). None of them is shared storage; none can be corrupted by re-entry. The detector was reading the assignment's variable name but not the &lt;strong&gt;type keyword&lt;/strong&gt; in front of it, so &lt;code&gt;uint256 len =&lt;/code&gt; looked like a storage write.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root fix:&lt;/strong&gt; classify the write target — declarations with a primitive/&lt;code&gt;memory&lt;/code&gt; type, and names declared &lt;code&gt;memory&lt;/code&gt;/&lt;code&gt;calldata&lt;/code&gt;/&lt;code&gt;new&lt;/code&gt; in the same function, are locals. Only a genuine &lt;strong&gt;storage&lt;/strong&gt; write after an attacker-controllable call is reentrancy.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Interface declarations are not unprotected functions
&lt;/h2&gt;

&lt;p&gt;Flattened files bundle interfaces and implementations together. A function &lt;em&gt;declaration&lt;/em&gt; has no body:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function setDepositLimit(uint256 x) external;   // interface — no body, no modifier possible
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Naive brace-matching grabbed the &lt;code&gt;{&lt;/code&gt; of some &lt;em&gt;later&lt;/em&gt; function and reported this declaration as an "unprotected config function." &lt;strong&gt;Root fix:&lt;/strong&gt; if the &lt;code&gt;;&lt;/code&gt; comes before the &lt;code&gt;{&lt;/code&gt;, it's a declaration — skip it. And skip &lt;code&gt;flatten&lt;/code&gt; files entirely; they duplicate code you'll analyze in its real location.&lt;/p&gt;




&lt;h2&gt;
  
  
  The meta-lesson
&lt;/h2&gt;

&lt;p&gt;Every one of these was fixed with a control test that proves two things at once: the false positive is gone &lt;strong&gt;and&lt;/strong&gt; the real bug still fires. That second half is the discipline that matters — it's easy to silence a noisy rule and go blind. Suppress in the &lt;em&gt;safe&lt;/em&gt; direction: when the classifier is unsure, keep the finding for a human.&lt;/p&gt;

&lt;p&gt;Because that's the real architecture: &lt;strong&gt;the scanner surfaces, the human confirms.&lt;/strong&gt; The tooling exists to make a careful auditor faster, not to replace their judgment. A zero-false-positive report isn't a model output — it's a heuristic that casts a wide net plus a human who verifies every survivor before their name goes on it.&lt;/p&gt;

&lt;p&gt;That's the standard we hold. If you're shipping a Solidity protocol and want a review that reads the code the way an attacker would — fast, honest, and without a wall of false positives — &lt;strong&gt;let's talk.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fiverr:&lt;/strong&gt; &lt;a href="https://fiverr.com/s/P2kNDP0" rel="noopener noreferrer"&gt;https://fiverr.com/s/P2kNDP0&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portfolio + sample report:&lt;/strong&gt; &lt;a href="https://juan23z.github.io" rel="noopener noreferrer"&gt;https://juan23z.github.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DM me on X:&lt;/strong&gt; @NawelJuan&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>blockchain</category>
      <category>ethereum</category>
      <category>security</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
