<?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>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>
    <item>
      <title>Your audit expired the day you made the next commit</title>
      <dc:creator>juan23z</dc:creator>
      <pubDate>Tue, 14 Jul 2026 10:45:44 +0000</pubDate>
      <link>https://dev.to/juan23z/your-audit-expired-the-day-you-made-the-next-commit-48m2</link>
      <guid>https://dev.to/juan23z/your-audit-expired-the-day-you-made-the-next-commit-48m2</guid>
      <description>&lt;p&gt;Mirror/dev.to. Positions continuous monitoring (our wedge).*&lt;/p&gt;

&lt;p&gt;You paid for an audit. The firm reviewed a commit hash, handed you a PDF, and moved on. You shipped. Then, like&lt;br&gt;
every real project, you kept building: a new feature here, a parameter tweak there, a "quick fix" before a&lt;br&gt;
weekend deploy.&lt;/p&gt;

&lt;p&gt;Here's the uncomfortable truth: &lt;strong&gt;that audit covered a snapshot that no longer exists.&lt;/strong&gt; The moment your first&lt;br&gt;
post-audit commit landed, you were running unaudited code again — often the exact kind of rushed change where&lt;br&gt;
bugs are born.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the money actually leaks
&lt;/h2&gt;

&lt;p&gt;Look at the post-mortems. A huge share of DeFi losses aren't in the audited launch code — they're in what came&lt;br&gt;
after:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An "innocent" refactor that flips a &lt;code&gt;Floor&lt;/code&gt; to a &lt;code&gt;Ceil&lt;/code&gt; in a share-conversion path.&lt;/li&gt;
&lt;li&gt;A new integration that reads a manipulable &lt;code&gt;totalAssets&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;An upgrade whose access control wasn't gated as tightly as the original.&lt;/li&gt;
&lt;li&gt;A hotfix deployed on a Friday with no time for a fresh review.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The audit didn't miss these. They &lt;strong&gt;didn't exist yet&lt;/strong&gt; when the audit happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap is structural, not a failure of the auditor
&lt;/h2&gt;

&lt;p&gt;One-time audits are priced and scoped as snapshots. That's fine for a launch gate. But your risk surface is a&lt;br&gt;
&lt;em&gt;moving target&lt;/em&gt;, and a snapshot can't cover a moving target. For a small or fast-moving protocol, the window&lt;br&gt;
between "audited" and "materially different code in production" can be days.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually closes the gap: continuous review
&lt;/h2&gt;

&lt;p&gt;The fix isn't "audit more often" (expensive, and you'd still have gaps). It's to make review &lt;strong&gt;continuous&lt;/strong&gt;:&lt;br&gt;
watch the contracts, and re-review automatically on every change/deploy — so the delta between your live code and&lt;br&gt;
your last review is always small enough to reason about.&lt;/p&gt;

&lt;p&gt;That's exactly what we do. An autonomous engine watches your repo 24/7; every new commit or deploy triggers a&lt;br&gt;
fresh pass over the changed surface, and you get an alert plus a monthly report. It costs a fraction of an audit&lt;br&gt;
per year, because the marginal cost of another scan is near zero.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A one-time audit tells you your code was safe on a Tuesday in March. Continuous monitoring tells you it's still&lt;br&gt;
safe today.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;We do fast, affordable security reviews for small protocols — then keep watching. Sample report:&lt;br&gt;
&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; · First month of monitoring free with any review.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I offer continuous smart-contract monitoring — I re-audit your contracts on every change and send a monthly report, so security keeps up as your code evolves. Custom detectors + manual verification, 0 false positives on all of OpenZeppelin. &lt;a href="https://juan23z.github.io/sample-audit-report.html" rel="noopener noreferrer"&gt;Sample report&lt;/a&gt; · &lt;a href="https://juan23z.github.io" rel="noopener noreferrer"&gt;Get in touch&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>solidity</category>
      <category>web3</category>
      <category>devops</category>
    </item>
    <item>
      <title>7 ways to drain an ERC-4626 vault (and how a good protocol closes each one)</title>
      <dc:creator>juan23z</dc:creator>
      <pubDate>Tue, 14 Jul 2026 09:28:01 +0000</pubDate>
      <link>https://dev.to/juan23z/7-ways-to-drain-an-erc-4626-vault-and-how-a-good-protocol-closes-each-one-2h48</link>
      <guid>https://dev.to/juan23z/7-ways-to-drain-an-erc-4626-vault-and-how-a-good-protocol-closes-each-one-2h48</guid>
      <description>&lt;p&gt;&lt;em&gt;Based on real audits of production vaults and AMMs.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A tokenized vault (ERC-4626) looks simple: deposit assets, get shares; burn shares, get assets back. Yet the&lt;br&gt;
4626 is one of the contract types where the most funds have been lost. These are the 7 cracks I check in&lt;br&gt;
&lt;strong&gt;every&lt;/strong&gt; vault, and how each is properly closed:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. First-depositor inflation.&lt;/strong&gt; The classic: the first depositor mints 1 wei of shares, donates assets to&lt;br&gt;
the vault, and rounding makes the second depositor receive 0 shares — losing their deposit. &lt;em&gt;Closed&lt;/em&gt; with&lt;br&gt;
virtual shares/assets (OpenZeppelin's &lt;code&gt;+1&lt;/code&gt; and &lt;code&gt;10**offset&lt;/code&gt; pattern) or a minimum initial deposit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Rounding in the user's favor.&lt;/strong&gt; On every asset↔share conversion, if rounding favors the withdrawer, the&lt;br&gt;
vault is drained penny by penny. Golden rule: &lt;strong&gt;deposit/mint round against the user; withdraw/redeem too.&lt;/strong&gt; A&lt;br&gt;
single &lt;code&gt;Floor&lt;/code&gt; where a &lt;code&gt;Ceil&lt;/code&gt; belonged = a leak.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Manipulable &lt;code&gt;totalAssets&lt;/code&gt;.&lt;/strong&gt; If the vault reads its assets from an external protocol (a "connector") and&lt;br&gt;
that balance can be inflated (donation, oracle), the share price is manipulated. &lt;em&gt;Closed&lt;/em&gt; by reading from&lt;br&gt;
sources that can't be inflated and using trusted connectors, immutable per pool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Reentrancy in hooks.&lt;/strong&gt; Modern vaults call extensions/callbacks during deposits and swaps. If a hook runs&lt;br&gt;
via &lt;code&gt;delegatecall&lt;/code&gt;, a malicious extension &lt;strong&gt;is&lt;/strong&gt; the vault. &lt;em&gt;Closed&lt;/em&gt; by using &lt;code&gt;call&lt;/code&gt; (not delegatecall),&lt;br&gt;
checking the return value, and isolating extensions per pool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Fee accounting as shares.&lt;/strong&gt; Charging the fee by minting shares to the protocol is correct — but an&lt;br&gt;
ordering bug (accruing without updating the "last total") means double-charging or misallocated dilution.&lt;br&gt;
&lt;em&gt;Closed&lt;/em&gt; by updating &lt;code&gt;lastTotalAssets&lt;/code&gt; on &lt;strong&gt;every&lt;/strong&gt; operation that touches the fee (deposit, withdraw, collect).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Access control on upgrades.&lt;/strong&gt; Many vaults are upgradeable via beacon/proxy. If the upgrade path isn't&lt;br&gt;
gated well, an attacker deploys malicious code into the vault. &lt;em&gt;Closed&lt;/em&gt; with strict roles and, where&lt;br&gt;
applicable, a timelock.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Bad debt not socialized correctly.&lt;/strong&gt; When a position ends up with uncollectable debt, the loss must be&lt;br&gt;
shared across LPs via a &lt;code&gt;lossFactor&lt;/code&gt; — and beware dividing by &lt;code&gt;totalUnits&lt;/code&gt; when it can be zero.&lt;/p&gt;




&lt;p&gt;The difference between a vault that holds and one that gets drained isn't a big idea: it's &lt;strong&gt;discipline in&lt;br&gt;
rounding, access control, and accounting&lt;/strong&gt;. When we audit, we check all 7, one by one, with the real code in&lt;br&gt;
front of us — not with assumptions.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Got a vault, an AMM, or any contract that moves funds and want a fast, honest review? See a sample report:&lt;br&gt;
&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; — or reach out: &lt;a href="mailto:naweljuan@gmail.com"&gt;naweljuan@gmail.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I run fast, honest Solidity security reviews (custom detectors + manual verification, 0 false positives on all of OpenZeppelin) plus continuous monitoring. &lt;a href="https://juan23z.github.io/sample-audit-report.html" rel="noopener noreferrer"&gt;Sample report&lt;/a&gt; · &lt;a href="http://www.fiverr.com/s/P2kNDP0" rel="noopener noreferrer"&gt;Order an audit&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>solidity</category>
      <category>security</category>
      <category>ethereum</category>
      <category>web3</category>
    </item>
    <item>
      <title>I open-sourced a Solidity security scanner with 0 false positives on all of OpenZeppelin</title>
      <dc:creator>juan23z</dc:creator>
      <pubDate>Mon, 13 Jul 2026 14:37:17 +0000</pubDate>
      <link>https://dev.to/juan23z/i-open-sourced-a-solidity-security-scanner-with-0-false-positives-on-all-of-openzeppelin-3o2h</link>
      <guid>https://dev.to/juan23z/i-open-sourced-a-solidity-security-scanner-with-0-false-positives-on-all-of-openzeppelin-3o2h</guid>
      <description>&lt;p&gt;I've spent months building an autonomous Web3 security system — it watches fresh protocols, pre-scans them, and re-audits client contracts on every change. I open-sourced its scanning core. MIT, needs only Python 3.9+ and git, zero API keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;Point it at a repo, get a professional report (Markdown + HTML) in seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python scan.py https://github.com/org/protocol &lt;span class="nt"&gt;--out&lt;/span&gt; ./report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or run it in CI on every PR with a GitHub Action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;juan23z/openclaw-audit@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;contracts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It ships &lt;strong&gt;12 heuristic detectors&lt;/strong&gt; aimed at the bugs that actually drain vaults:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ERC-4626 rounding &amp;amp; first-depositor inflation&lt;/li&gt;
&lt;li&gt;Donation / &lt;code&gt;totalAssets&lt;/code&gt; manipulation&lt;/li&gt;
&lt;li&gt;Oracle staleness (Chainlink &lt;code&gt;latestRoundData&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Cross-function &amp;amp; read-only reentrancy&lt;/li&gt;
&lt;li&gt;Access control on privileged functions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tx.origin&lt;/code&gt; auth, unchecked low-level calls&lt;/li&gt;
&lt;li&gt;ERC-20 / ERC-4626 compliance&lt;/li&gt;
&lt;li&gt;NatSpec-vs-code mismatches&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The hard part: not crying wolf
&lt;/h2&gt;

&lt;p&gt;Heuristic scanners have a reputation problem — they flag everything, so people stop trusting them. Reputation IS the product, so I obsessed over precision.&lt;/p&gt;

&lt;p&gt;The bar I set: &lt;strong&gt;0 false positives across the entire OpenZeppelin library.&lt;/strong&gt; It's verifiable — clone OZ, run the scanner, get zero findings. Getting there meant fixing real bugs in my own detectors, e.g.:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A cast like &lt;code&gt;uint48(bytes32(x).extract(...))&lt;/code&gt; was being read as an interface call (a case-insensitive regex matched &lt;code&gt;int48(...)&lt;/code&gt; as if it were &lt;code&gt;IFoo(...)&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The reentrancy detector didn't recognize Uniswap V2's classic &lt;code&gt;lock&lt;/code&gt;/&lt;code&gt;unlocked&lt;/code&gt; guard, so it flagged &lt;code&gt;swap&lt;/code&gt;/&lt;code&gt;burn&lt;/code&gt; — 13 findings on V2 dropped to 1, and that 1 is a genuine CEI-order candidate on &lt;code&gt;createPair&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The ERC-compliance check flagged every token that inherits its implementation from a parent as "missing &lt;code&gt;transfer()&lt;/code&gt;" — the most common real-world pattern.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Result across libraries: OpenZeppelin &lt;strong&gt;0&lt;/strong&gt;, forge-std &lt;strong&gt;0&lt;/strong&gt;, solady low single digits, Uniswap V2 down to &lt;strong&gt;1&lt;/strong&gt; defensible candidate.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's honest about being heuristic
&lt;/h2&gt;

&lt;p&gt;Everything it reports is a &lt;strong&gt;candidate — verify before acting&lt;/strong&gt;. It's a fast first pass and a CI safety net, not a replacement for a real audit. The report says so explicitly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Repo → &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;I'd genuinely love feedback — which detectors you'd want, false positives you hit, edge cases I'm missing.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Disclosure: I also do human-verified audits + continuous monitoring as a paid service for small/new DeFi teams — &lt;a href="https://juan23z.github.io" rel="noopener noreferrer"&gt;details here&lt;/a&gt; — but the scanner is free and standalone, so use it however you like.)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>solidity</category>
      <category>ethereum</category>
      <category>security</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
