<?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>ERC-4626 first-depositor inflation, explained for founders</title>
      <dc:creator>juan23z</dc:creator>
      <pubDate>Wed, 30 Sep 2026 21:41:51 +0000</pubDate>
      <link>https://dev.to/juan23z/erc-4626-first-depositor-inflation-explained-for-founders-3ih1</link>
      <guid>https://dev.to/juan23z/erc-4626-first-depositor-inflation-explained-for-founders-3ih1</guid>
      <description>&lt;p&gt;If you're shipping an ERC-4626 vault, there's one attack you need to understand before mainnet — because it targets your &lt;strong&gt;very first real depositor&lt;/strong&gt;, and it's caused by a rounding detail that's easy to miss.&lt;/p&gt;

&lt;p&gt;It's called the &lt;strong&gt;first-depositor inflation attack&lt;/strong&gt; (or "empty-vault share inflation"). Here's how it works, in plain English.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup: shares vs. assets
&lt;/h2&gt;

&lt;p&gt;An ERC-4626 vault issues &lt;strong&gt;shares&lt;/strong&gt; representing a claim on the underlying &lt;strong&gt;assets&lt;/strong&gt;. The exchange rate is roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;shares_you_get&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;deposit_amount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;total_shares&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total_assets&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the vault is brand new, &lt;code&gt;total_shares&lt;/code&gt; and &lt;code&gt;total_assets&lt;/code&gt; are both zero, and the first deposit sets the initial rate. That empty starting state is the vulnerability.&lt;/p&gt;

&lt;h2&gt;
  
  
  The attack, step by step
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Attacker deposits 1 wei.&lt;/strong&gt; The vault mints them 1 share. Now &lt;code&gt;total_shares = 1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attacker "donates" a large amount directly&lt;/strong&gt; to the vault — a raw &lt;code&gt;transfer&lt;/code&gt; of, say, 10,000 tokens straight to the vault address, &lt;em&gt;not&lt;/em&gt; through &lt;code&gt;deposit()&lt;/code&gt;. Now &lt;code&gt;total_assets = 10,000e18 + 1&lt;/code&gt;, but &lt;code&gt;total_shares&lt;/code&gt; is still &lt;code&gt;1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One share is now worth ~10,000 tokens.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The victim deposits 10,000 tokens&lt;/strong&gt; expecting a fair share. The share math rounds &lt;em&gt;down&lt;/em&gt;:
&lt;code&gt;shares = 10,000e18 * 1 / 10,000e18 ≈ 1&lt;/code&gt;, but integer rounding gives them &lt;strong&gt;0 shares&lt;/strong&gt; (or far fewer than they paid for).&lt;/li&gt;
&lt;li&gt;The attacker now owns a share worth the victim's deposit. They &lt;strong&gt;redeem and walk away&lt;/strong&gt; with the victim's money.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The victim did nothing wrong. They deposited into a vault whose accounting could be manipulated because it trusted &lt;code&gt;balanceOf(address(this))&lt;/code&gt; and started from empty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it happens
&lt;/h2&gt;

&lt;p&gt;Two ingredients:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Accounting from raw balance.&lt;/strong&gt; Using &lt;code&gt;token.balanceOf(address(this))&lt;/code&gt; as &lt;code&gt;total_assets&lt;/code&gt; lets anyone inflate it with a direct transfer (a "donation"), bypassing &lt;code&gt;deposit()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rounding that favors the vault/attacker&lt;/strong&gt; on the first deposit, when supply is tiny.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The fix: virtual shares &amp;amp; assets (decimal offset)
&lt;/h2&gt;

&lt;p&gt;OpenZeppelin's ERC-4626 implementation defends against this with &lt;strong&gt;virtual shares and virtual assets&lt;/strong&gt; — it adds a small virtual offset to both sides of the ratio so the vault never really starts from zero, and rounding always favors the vault over the attacker rather than the reverse. In practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;OpenZeppelin's &lt;code&gt;ERC4626&lt;/code&gt;&lt;/strong&gt; as your base (it ships the &lt;code&gt;_decimalsOffset()&lt;/code&gt; mitigation).&lt;/li&gt;
&lt;li&gt;If you roll your own, &lt;strong&gt;don't derive &lt;code&gt;totalAssets&lt;/code&gt; from raw &lt;code&gt;balanceOf&lt;/code&gt;&lt;/strong&gt; — track deposited assets internally, or add the virtual-offset defense.&lt;/li&gt;
&lt;li&gt;Consider &lt;strong&gt;seeding the vault&lt;/strong&gt; with a tiny initial deposit at deployment (a "dead shares" burn) so it's never empty for the first real user.&lt;/li&gt;
&lt;li&gt;Add an &lt;strong&gt;invariant/fuzz test&lt;/strong&gt;: "a depositor never receives 0 shares for a non-trivial deposit," and "no sequence of donate+deposit lets one account extract another's assets."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Solmate's minimal ERC-4626, by contrast, &lt;strong&gt;intentionally omits&lt;/strong&gt; this — it leaves the defense to the integrator. That's a valid library choice, but it means &lt;em&gt;you&lt;/em&gt; have to add it. (My scanner flags exactly this difference between the two.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Check your vault in one command
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/juan23z/openclaw-audit" rel="noopener noreferrer"&gt;OpenClaw Audit&lt;/a&gt; has a dedicated first-depositor-inflation detector, plus checks for donation-based accounting (&lt;code&gt;balanceOf(address(this))&lt;/code&gt;) and ERC-4626 rounding direction:&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;your-vault-repo&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Heuristic candidates — verify before acting. But for a vault, this is one of the first things to rule out.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Building an ERC-4626 vault and want a human to check the share math before you ship? A hand-verified &lt;a href="https://juan23z.github.io/pricing.html" rel="noopener noreferrer"&gt;Quick Scan&lt;/a&gt; is $49 (one contract, 48h, and if it's not useful you don't pay).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>solidity</category>
      <category>defi</category>
      <category>security</category>
      <category>ethereum</category>
    </item>
    <item>
      <title>5 reentrancy patterns I keep finding in small DeFi vaults</title>
      <dc:creator>juan23z</dc:creator>
      <pubDate>Wed, 30 Sep 2026 21:41:35 +0000</pubDate>
      <link>https://dev.to/juan23z/5-reentrancy-patterns-i-keep-finding-in-small-defi-vaults-4m8o</link>
      <guid>https://dev.to/juan23z/5-reentrancy-patterns-i-keep-finding-in-small-defi-vaults-4m8o</guid>
      <description>&lt;p&gt;Almost every small vault I review has a &lt;code&gt;nonReentrant&lt;/code&gt; modifier on &lt;code&gt;withdraw()&lt;/code&gt;. And almost every one still has a reentrancy issue somewhere else.&lt;/p&gt;

&lt;p&gt;A single guard on the obvious function creates a false sense of safety. Reentrancy is about &lt;strong&gt;state that's inconsistent during an external call&lt;/strong&gt; — and that happens in more places than the one function everyone remembers to protect. Here are five patterns I keep finding, and how to catch each.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Cross-function reentrancy
&lt;/h2&gt;

&lt;p&gt;The classic. &lt;code&gt;withdraw()&lt;/code&gt; is guarded, but it makes an external call &lt;strong&gt;before&lt;/strong&gt; updating a shared balance — and during that call the attacker re-enters a &lt;em&gt;different&lt;/em&gt;, unguarded function that reads the stale balance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function withdraw(uint256 amount) external nonReentrant {
    (bool ok,) = msg.sender.call{value: amount}("");   // external call first
    require(ok);
    balances[msg.sender] -= amount;                     // state updated after
}

function transfer(address to, uint256 amount) external {  // NOT guarded
    balances[msg.sender] -= amount;  // still sees the pre-withdraw balance
    balances[to] += amount;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;nonReentrant&lt;/code&gt; on &lt;code&gt;withdraw&lt;/code&gt; doesn't help: the re-entry lands in &lt;code&gt;transfer&lt;/code&gt;. &lt;strong&gt;Fix:&lt;/strong&gt; effects before interactions — update &lt;code&gt;balances&lt;/code&gt; &lt;em&gt;before&lt;/em&gt; the call — and apply the guard to every function that touches shared state, not just one.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Read-only reentrancy
&lt;/h2&gt;

&lt;p&gt;The one that bit Curve/Balancer integrators. A &lt;strong&gt;view&lt;/strong&gt; function (say &lt;code&gt;getPrice()&lt;/code&gt; or &lt;code&gt;totalAssets()&lt;/code&gt;) reads state that's temporarily inconsistent mid-transaction. Your vault calls that view during someone else's external call and gets a manipulated value — even though nothing "wrote" during your call.&lt;/p&gt;

&lt;p&gt;Views can't be &lt;code&gt;nonReentrant&lt;/code&gt; (they don't write), so the guard doesn't apply. &lt;strong&gt;Fix:&lt;/strong&gt; don't consume an external protocol's price/reserves view without checking whether it can be entered mid-operation; use a reentrancy-aware read (some protocols expose a "check" you can call), or a manipulation-resistant source.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. ERC-777 / ERC-677 / callback-token hooks
&lt;/h2&gt;

&lt;p&gt;You assumed the token is plain ERC-20. But ERC-777 calls &lt;code&gt;tokensReceived&lt;/code&gt; on the recipient, and ERC-677/1363 call hooks on transfer. If your accounting isn't settled before &lt;code&gt;transferFrom&lt;/code&gt;/&lt;code&gt;transfer&lt;/code&gt;, that hook is a re-entry point into your vault.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; strict checks-effects-interactions even around token transfers, and be explicit about which token standards you support (documented assumptions are spec, not luck).&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Cross-contract reentrancy
&lt;/h2&gt;

&lt;p&gt;Your vault and your strategy/adapter share state through storage or a shared accounting contract. &lt;code&gt;Vault&lt;/code&gt; is guarded, &lt;code&gt;Strategy&lt;/code&gt; is guarded — but a call chain &lt;code&gt;Vault → external → Strategy&lt;/code&gt; re-enters while the shared state is half-updated. Per-contract guards don't compose across contracts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; treat the trust boundary as the whole system. Settle shared accounting before any external call anywhere in the chain, and consider a shared reentrancy lock for tightly-coupled contracts.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The root cause: CEI violations
&lt;/h2&gt;

&lt;p&gt;Patterns 1–4 are symptoms. The disease is &lt;strong&gt;Checks-Effects-Interactions ordering&lt;/strong&gt;: any external call that happens &lt;em&gt;before&lt;/em&gt; the contract's state reflects what just occurred. Once you internalize CEI, most reentrancy disappears — the guard becomes a backstop, not the plan.&lt;/p&gt;

&lt;p&gt;A quick self-audit: search your contracts for &lt;code&gt;.call&lt;/code&gt;, &lt;code&gt;.transfer&lt;/code&gt;, &lt;code&gt;safeTransfer&lt;/code&gt;, and any external interface call, and ask for each — &lt;em&gt;"is all my state already updated at this line?"&lt;/em&gt; If not, that's a candidate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Catch them automatically
&lt;/h2&gt;

&lt;p&gt;My free scanner, &lt;a href="https://github.com/juan23z/openclaw-audit" rel="noopener noreferrer"&gt;OpenClaw Audit&lt;/a&gt;, has dedicated detectors for cross-function reentrancy and CEI-ordering candidates (and it's calibrated to stay quiet on correct code — Morpho Blue's formally-verified &lt;code&gt;safeTransfer&lt;/code&gt;s don't trip it). One command:&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;your-repo&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Findings are heuristic candidates — verify each. But it points your eyes at the right lines.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I review small DeFi protocols for exactly these issues — real, exploitable findings only, no padded reports. A hand-verified &lt;a href="https://juan23z.github.io/pricing.html" rel="noopener noreferrer"&gt;Quick Scan&lt;/a&gt; is $49 (one contract, 48h, and if it's not useful you don't pay).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>solidity</category>
      <category>security</category>
      <category>defi</category>
      <category>web3</category>
    </item>
    <item>
      <title>The honest pre-mainnet security checklist for small protocols</title>
      <dc:creator>juan23z</dc:creator>
      <pubDate>Wed, 30 Sep 2026 21:40:43 +0000</pubDate>
      <link>https://dev.to/juan23z/the-honest-pre-mainnet-security-checklist-for-small-protocols-4bl7</link>
      <guid>https://dev.to/juan23z/the-honest-pre-mainnet-security-checklist-for-small-protocols-4bl7</guid>
      <description>&lt;p&gt;Most "security checklists" are 80 items long and read like a compliance form. This one is short on purpose: these are the checks that actually catch &lt;strong&gt;loss of funds&lt;/strong&gt; in small protocols before mainnet. For each, the failure mode it prevents.&lt;/p&gt;

&lt;p&gt;Go through it honestly. A "no" isn't a shame — it's a to-do before you ship.&lt;/p&gt;

&lt;h2&gt;
  
  
  Access control
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Every privileged function (&lt;code&gt;mint&lt;/code&gt;, &lt;code&gt;pause&lt;/code&gt;, &lt;code&gt;setFee&lt;/code&gt;, &lt;code&gt;setOracle&lt;/code&gt;, &lt;code&gt;upgrade&lt;/code&gt;, &lt;code&gt;sweep&lt;/code&gt;, &lt;code&gt;initialize&lt;/code&gt;) is &lt;strong&gt;protected&lt;/strong&gt; by a modifier or role. &lt;em&gt;Prevents: anyone calling your admin functions.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Privileged roles sit behind a &lt;strong&gt;multisig or timelock&lt;/strong&gt;, not a single hot EOA. &lt;em&gt;Prevents: one leaked key draining everything.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;initialize()&lt;/code&gt; on upgradeable contracts &lt;strong&gt;can't be front-run&lt;/strong&gt; and can't be called twice. &lt;em&gt;Prevents: attacker becoming owner.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;[ ] No &lt;code&gt;tx.origin&lt;/code&gt; for authorization — use &lt;code&gt;msg.sender&lt;/code&gt;. &lt;em&gt;Prevents: phishing-based auth bypass.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Oracles &amp;amp; pricing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Price reads check &lt;strong&gt;staleness&lt;/strong&gt; (&lt;code&gt;updatedAt&lt;/code&gt;) and &lt;strong&gt;deviation&lt;/strong&gt; bounds. &lt;em&gt;Prevents: acting on a frozen or manipulated price.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;[ ] You don't price off a &lt;strong&gt;spot AMM reserve&lt;/strong&gt; that can be flash-loan-manipulated. &lt;em&gt;Prevents: flash-loan price attacks.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;[ ] No &lt;strong&gt;read-only reentrancy&lt;/strong&gt; on prices you consume from Curve/Balancer-style pools. &lt;em&gt;Prevents: manipulated mid-tx views.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reentrancy &amp;amp; external calls
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Checks-Effects-Interactions&lt;/strong&gt;: state updated &lt;em&gt;before&lt;/em&gt; any external call. &lt;em&gt;Prevents: the whole reentrancy family.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Guards on &lt;strong&gt;every&lt;/strong&gt; function touching shared state, not just &lt;code&gt;withdraw&lt;/code&gt;. &lt;em&gt;Prevents: cross-function reentrancy.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Low-level &lt;code&gt;call&lt;/code&gt;/&lt;code&gt;delegatecall&lt;/code&gt;/&lt;code&gt;send&lt;/code&gt; return values are &lt;strong&gt;checked&lt;/strong&gt;. &lt;em&gt;Prevents: silent failures treated as success.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Vault / share math (if ERC-4626 or similar)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Protected against &lt;strong&gt;first-depositor inflation&lt;/strong&gt; (virtual shares / dead shares). &lt;em&gt;Prevents: the empty-vault attack that robs your first user.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Rounding always favors the &lt;strong&gt;vault&lt;/strong&gt;, never the withdrawer. &lt;em&gt;Prevents: slow drain via rounding.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;totalAssets&lt;/code&gt; is &lt;strong&gt;not&lt;/strong&gt; derived from raw &lt;code&gt;balanceOf(address(this))&lt;/code&gt;. &lt;em&gt;Prevents: donation-based accounting manipulation.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tokens
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Handles &lt;strong&gt;fee-on-transfer / rebasing&lt;/strong&gt; tokens — or explicitly documents it doesn't support them. &lt;em&gt;Prevents: accounting drift.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Uses &lt;strong&gt;SafeERC20&lt;/strong&gt; for tokens with non-standard returns (USDT-style). &lt;em&gt;Prevents: transfers that "fail open."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Aware of &lt;strong&gt;callback tokens&lt;/strong&gt; (ERC-777/677/1363) as reentrancy vectors. &lt;em&gt;Prevents: hook-based re-entry.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Upgrades &amp;amp; storage
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] If upgradeable: &lt;strong&gt;storage layout&lt;/strong&gt; is append-only; no reordered/removed slots. &lt;em&gt;Prevents: storage collisions corrupting state.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Upgrades are gated behind &lt;strong&gt;governance/timelock&lt;/strong&gt;. &lt;em&gt;Prevents: instant malicious upgrade.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Testing &amp;amp; process
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Invariant/fuzz tests&lt;/strong&gt; on core economic properties (solvency, fund conservation, exchange rate). &lt;em&gt;Prevents: whole classes of edge cases.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;[ ] A &lt;strong&gt;security scan in CI&lt;/strong&gt; on every PR, so no diff merges unreviewed. &lt;em&gt;Prevents: the "quick change after the audit" incident.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Someone &lt;strong&gt;other than the author&lt;/strong&gt; has read the money-moving paths. &lt;em&gt;Prevents: author blind spots.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Automate the first pass
&lt;/h2&gt;

&lt;p&gt;You can knock out a big chunk of this list automatically. &lt;a href="https://github.com/juan23z/openclaw-audit" rel="noopener noreferrer"&gt;OpenClaw Audit&lt;/a&gt; is a free scanner with detectors for access control, oracle staleness, reentrancy, unchecked calls, &lt;code&gt;tx.origin&lt;/code&gt;, first-depositor inflation, fee-on-transfer and more — calibrated to stay quiet on clean code:&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="c1"&gt;# .github/workflows/security.yml — a scan on every PR&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;read&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;write&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;audit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&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;actions/checkout@v4&lt;/span&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Findings are heuristic candidates — verify before acting. Automation covers the mechanical checks so your (or a reviewer's) attention goes to the judgment calls.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Want a human to run this checklist against your contracts before mainnet? A hand-verified &lt;a href="https://juan23z.github.io/pricing.html" rel="noopener noreferrer"&gt;Quick Scan&lt;/a&gt; is $49 (one contract, 48h, and if it's not useful you don't pay). Interactive version of this checklist: &lt;a href="https://juan23z.github.io/checklist.html" rel="noopener noreferrer"&gt;juan23z.github.io/checklist.html&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>solidity</category>
      <category>security</category>
      <category>web3</category>
      <category>defi</category>
    </item>
    <item>
      <title>Your smart-contract audit expired the day you made your next commit</title>
      <dc:creator>juan23z</dc:creator>
      <pubDate>Wed, 30 Sep 2026 21:39:18 +0000</pubDate>
      <link>https://dev.to/juan23z/your-smart-contract-audit-expired-the-day-you-made-your-next-commit-3gf7</link>
      <guid>https://dev.to/juan23z/your-smart-contract-audit-expired-the-day-you-made-your-next-commit-3gf7</guid>
      <description>&lt;p&gt;Here's an uncomfortable truth about the $30k audit PDF sitting in your repo:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It describes code that no longer exists.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The auditor reviewed commit &lt;code&gt;abc123&lt;/code&gt;. Then you fixed a finding, tweaked a fee, added a function, bumped a dependency. Every one of those commits is code &lt;strong&gt;no one reviewed&lt;/strong&gt;. The audit didn't get less thorough — it just got &lt;em&gt;old&lt;/em&gt;, the moment you kept building.&lt;/p&gt;

&lt;p&gt;For a big protocol with a security team, that gap is managed. For a &lt;strong&gt;small team shipping weekly&lt;/strong&gt;, it's a canyon — and it's exactly where incidents happen: not in the audited code, but in the "quick change" after it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A snapshot vs. a smoke detector
&lt;/h2&gt;

&lt;p&gt;Think of a one-time audit as a &lt;strong&gt;home inspection&lt;/strong&gt; before you buy: essential, but a one-day photograph. Continuous monitoring is the &lt;strong&gt;smoke detector&lt;/strong&gt; you leave running afterward. You want both — but only one of them is watching while you sleep.&lt;/p&gt;

&lt;p&gt;Concretely, "continuous monitoring" for contracts means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The scanner runs in &lt;strong&gt;CI on every push and PR&lt;/strong&gt; — a security pass on the diff before it merges.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-audit on every deploy/upgrade&lt;/strong&gt;, not once a year.&lt;/li&gt;
&lt;li&gt;A short, plain-English report each month: what changed, what's clean, what to look at.&lt;/li&gt;
&lt;li&gt;Findings land &lt;strong&gt;where you work&lt;/strong&gt; (PR comments, the Security tab), not in a PDF you open once.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this is now cheap enough to make sense
&lt;/h2&gt;

&lt;p&gt;Continuous review used to mean a retainer only funded protocols could afford. Two things changed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A calibrated scanner does the first pass.&lt;/strong&gt; If your tooling stays quiet on good code (mine is calibrated to &lt;a href="https://github.com/juan23z/openclaw-audit" rel="noopener noreferrer"&gt;0 false positives across all of OpenZeppelin&lt;/a&gt;), a human only looks when something actually changed and actually looks risky. No wading through noise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation carries the boring 90%.&lt;/strong&gt; The diff triage, the regression checks, the "did this PR touch a money-moving path?" — automated. Human attention goes to the 10% that needs judgment.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result: monitoring a small protocol costs &lt;strong&gt;less than one audit a year&lt;/strong&gt;, and covers you on every commit instead of one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cheap setup you can do today (free)
&lt;/h2&gt;

&lt;p&gt;Even before hiring anyone, wire the free scanner into CI so no diff merges unreviewed:&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="c1"&gt;# .github/workflows/security.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;read&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;write&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;audit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&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;actions/checkout@v4&lt;/span&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now every PR gets a heuristic security pass and a summary comment. It's free, MIT, no API keys. Candidates are advisory — verify before acting.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to add a human
&lt;/h2&gt;

&lt;p&gt;Automation flags candidates; it doesn't confirm exploitability or reason about your protocol's economic invariants. That's the human layer. If you're moving real money, the sensible progression is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Free CI scan&lt;/strong&gt; on every PR (above).&lt;/li&gt;
&lt;li&gt;A hand-verified &lt;strong&gt;&lt;a href="https://juan23z.github.io/pricing.html" rel="noopener noreferrer"&gt;Quick Scan&lt;/a&gt;&lt;/strong&gt; ($49) before a big launch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuous monitoring&lt;/strong&gt; ($150/mo) once you're live and shipping — re-audited on every change, cancel anytime.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The one-time audit isn't wrong. It's just &lt;strong&gt;not the whole job&lt;/strong&gt; — because your code didn't stop changing when the auditor stopped reading.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I run fast, honest security reviews + continuous monitoring for small and new protocols — real issues only, no padded reports. Start with a $49 Quick Scan or ask about monitoring at &lt;a href="https://juan23z.github.io/pricing.html" rel="noopener noreferrer"&gt;juan23z.github.io&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>solidity</category>
      <category>security</category>
      <category>web3</category>
      <category>devops</category>
    </item>
    <item>
      <title>I ran my Solidity scanner on the 10 most-audited codebases in web3. Here's every flag.</title>
      <dc:creator>juan23z</dc:creator>
      <pubDate>Wed, 30 Sep 2026 21:39:16 +0000</pubDate>
      <link>https://dev.to/juan23z/i-ran-my-solidity-scanner-on-the-10-most-audited-codebases-in-web3-heres-every-flag-1d1j</link>
      <guid>https://dev.to/juan23z/i-ran-my-solidity-scanner-on-the-10-most-audited-codebases-in-web3-heres-every-flag-1d1j</guid>
      <description>&lt;p&gt;The hard part of a security scanner isn't &lt;em&gt;finding&lt;/em&gt; things. It's &lt;strong&gt;not drowning the real issues in noise&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A tool that flags forty non-issues trains you to ignore it — and then it misses the one that matters. So when I built &lt;a href="https://github.com/juan23z/openclaw-audit" rel="noopener noreferrer"&gt;OpenClaw Audit&lt;/a&gt; (a free, MIT heuristic Solidity scanner), I held it to one bar: &lt;strong&gt;silence on sound code.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To prove it stays quiet, I ran it across ten of the most-reviewed Solidity codebases in the ecosystem — libraries that thousands of protocols depend on and that have been audited many times over — and &lt;strong&gt;hand-verified every flag&lt;/strong&gt;. Here's exactly what came out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;p&gt;Run on each library's source (tests, mocks and dependencies excluded), latest &lt;code&gt;main&lt;/code&gt;:&lt;/p&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;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenZeppelin Contracts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;247&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Completely clean.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;forge-std&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Clean.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Uniswap Permit2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Clean.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PRBMath&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Clean.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Uniswap v2-core&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;A defensible CEI-ordering candidate on &lt;code&gt;createPair&lt;/code&gt; — worth a human's eyes, not a false alarm.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Uniswap v3-core&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;initialize()&lt;/code&gt; flagged — &lt;strong&gt;false positive&lt;/strong&gt;: pool init is permissionless by design.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Uniswap v4-core&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Same as v3 — permissionless &lt;code&gt;initialize()&lt;/code&gt;, &lt;strong&gt;false positive&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Solmate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;One &lt;strong&gt;real, known&lt;/strong&gt; flag (ERC-4626 first-depositor inflation, omitted by design); one rounding &lt;strong&gt;false positive&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Morpho Blue&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Two reentrancy &lt;strong&gt;false positives&lt;/strong&gt; — formally verified, correct effects-before-interactions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Solady&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;140&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;All &lt;strong&gt;false positives&lt;/strong&gt;: documented &lt;code&gt;tx.origin&lt;/code&gt; rescue, UUPS auth the heuristic can't parse, intentional math ordering.&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;td&gt;~2.3% of files flag anything; 4 of 10 codebases perfectly clean.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What this actually shows
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;It stays silent on sound code.&lt;/strong&gt; Zero findings across OpenZeppelin, forge-std, Permit2 and PRBMath — the most-audited code in web3, where naive tools carpet-bomb false positives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When it does flag, the flags are explainable — not random.&lt;/strong&gt; They cluster on genuinely interesting spots: a permissionless initializer, a documented rescue mechanism, a library that deliberately leaves inflation protection to the integrator. A human clears each in seconds. That's the point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It catches real, known design gaps.&lt;/strong&gt; The Solmate first-depositor-inflation flag is a &lt;em&gt;true&lt;/em&gt; observation: that ERC-4626 implementation omits the virtual-share defense OpenZeppelin's adds. The scanner surfaces the difference.&lt;/p&gt;

&lt;p&gt;My favorite result is Solady: &lt;strong&gt;zero flags across 140 files&lt;/strong&gt; of hand-written, gas-golfed assembly — the kind of code that makes lesser tools panic. The raw &lt;code&gt;call()&lt;/code&gt; in &lt;code&gt;SafeTransferLib&lt;/code&gt; &lt;em&gt;looks&lt;/em&gt; like the classic unchecked-call bug, but it verifies success AND the return value AND that the address has code. Correct restraint: there was nothing to report.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify it yourself — one command
&lt;/h2&gt;

&lt;p&gt;Every number above is reproducible. You don't have to trust me:&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 &lt;span class="se"&gt;\&lt;/span&gt;
  https://github.com/OpenZeppelin/openzeppelin-contracts
&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 the URL for any codebase above — or your own repo — and check the numbers. That's the whole idea: &lt;strong&gt;a claim you can verify, not one you have to trust.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Use it on your own code
&lt;/h2&gt;

&lt;p&gt;Point it at a repo and get a Markdown + HTML report in seconds, or drop it into CI as a GitHub Action and get a scan on every PR:&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="c1"&gt;# .github/workflows/security.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;read&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;write&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;audit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&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;actions/checkout@v4&lt;/span&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's free and MIT. Findings are &lt;strong&gt;heuristic candidates&lt;/strong&gt; — verify before acting (the report labels every one).&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Shipping to mainnet and want a human on it? I do fast, honest smart-contract reviews for small protocols — a hand-verified &lt;a href="https://juan23z.github.io/pricing.html" rel="noopener noreferrer"&gt;Quick Scan&lt;/a&gt; is $49 (one contract, 48h, and if the report isn't useful you don't pay). Full calibration report and services at &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>web3</category>
      <category>ethereum</category>
    </item>
    <item>
      <title>The fee-on-transfer bug that locks funds forever (with a working PoC)</title>
      <dc:creator>juan23z</dc:creator>
      <pubDate>Fri, 25 Sep 2026 18:04:01 +0000</pubDate>
      <link>https://dev.to/juan23z/the-fee-on-transfer-bug-that-locks-funds-forever-with-a-working-poc-1cm8</link>
      <guid>https://dev.to/juan23z/the-fee-on-transfer-bug-that-locks-funds-forever-with-a-working-poc-1cm8</guid>
      <description>&lt;p&gt;Last week I found this pattern in a real audit. I can't name the protocol — it's under&lt;br&gt;
responsible-disclosure until the program confirms a fix — but the bug itself is a generic pattern&lt;br&gt;
that shows up constantly, and it's worth understanding because most scanners miss it entirely.&lt;/p&gt;
&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;Any contract that does this is at risk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function topUp(address token, uint256 amount) external {
    IERC20(token).transferFrom(msg.sender, address(this), amount);
    accounted[token] += amount;   // &amp;lt;- the bug is right here
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks fine, right? &lt;code&gt;transferFrom&lt;/code&gt; moves &lt;code&gt;amount&lt;/code&gt; tokens, and you record &lt;code&gt;amount&lt;/code&gt; in your internal&lt;br&gt;
ledger. Standard accounting.&lt;/p&gt;

&lt;p&gt;Except: &lt;strong&gt;&lt;code&gt;transferFrom&lt;/code&gt; doesn't guarantee the contract received &lt;code&gt;amount&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;token&lt;/code&gt; charges a transfer fee (fee-on-transfer tokens, deflationary tokens, reflection tokens —&lt;br&gt;
all real, all still in production on mainnet today), the contract's actual balance goes up by&lt;br&gt;
&lt;code&gt;amount - fee&lt;/code&gt;, while &lt;code&gt;accounted[token]&lt;/code&gt; goes up by the full &lt;code&gt;amount&lt;/code&gt;. Your internal books just&lt;br&gt;
started lying to you.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why it matters
&lt;/h2&gt;

&lt;p&gt;The mismatch sits quietly until someone tries to pay out based on the accounted value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function withdraw(address token, address to) external {
    uint256 owed = accounted[token];
    accounted[token] = 0;
    IERC20(token).transfer(to, owed);   // reverts: contract doesn't have `owed` tokens
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;withdraw&lt;/code&gt; reverts. Every time. Forever. The contract's real balance is permanently less than what&lt;br&gt;
it thinks it owes, so the transfer call always fails with "transfer amount exceeds balance." There's&lt;br&gt;
no retry that fixes it — the accounted number never shrinks to match reality on its own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result: the deposited funds are stuck in the contract with no recovery path.&lt;/strong&gt; Not "hard to get&lt;br&gt;
back" — actually, structurally, permanently unreachable.&lt;/p&gt;
&lt;h2&gt;
  
  
  Proof, not theory
&lt;/h2&gt;

&lt;p&gt;I don't like reporting "this looks vulnerable" without running it. Here's a self-contained Foundry&lt;br&gt;
PoC — a minimal contract reproducing the exact pattern, plus a 5% fee-on-transfer token mock:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import { Test, console2 } from "forge-std/Test.sol";

contract FeeOnTransferToken {
    mapping(address =&amp;gt; uint256) public balanceOf;
    mapping(address =&amp;gt; mapping(address =&amp;gt; uint256)) public allowance;
    uint256 constant FEE_BPS = 500; // 5%

    constructor(uint256 supply) { balanceOf[msg.sender] = supply; }
    function approve(address s, uint256 a) external returns (bool) { allowance[msg.sender][s] = a; return true; }
    function transferFrom(address from, address to, uint256 amount) external returns (bool) {
        allowance[from][msg.sender] -= amount;
        uint256 fee = (amount * FEE_BPS) / 10_000;
        balanceOf[from] -= amount;
        balanceOf[to] += amount - fee;   // receiver gets LESS than `amount`
        return true;
    }
}

contract VulnerableVault {
    mapping(address =&amp;gt; uint256) public accounted;
    function topUp(address token, uint256 amount) external {
        FeeOnTransferToken(token).transferFrom(msg.sender, address(this), amount);
        accounted[token] += amount;
    }
    function withdraw(address token, address to) external {
        uint256 owed = accounted[token];
        accounted[token] = 0;
        require(FeeOnTransferToken(token).balanceOf(address(this)) &amp;gt;= owed, "insufficient");
        // (real code would use safeTransfer here — omitted, same failure either way)
    }
}

contract FeeOnTransferPoC is Test {
    function test_FundsLockedForever() public {
        VulnerableVault vault = new VulnerableVault();
        FeeOnTransferToken token = new FeeOnTransferToken(1_000_000e18);

        token.approve(address(vault), 1_000e18);
        vault.topUp(address(token), 1_000e18);

        uint256 accounted = vault.accounted(address(token));
        uint256 realBalance = token.balanceOf(address(vault));

        console2.log("accounted:", accounted);      // 1000e18 — what the vault THINKS it has
        console2.log("real balance:", realBalance);  // 950e18  — what it ACTUALLY has

        assertEq(accounted, 1_000e18);
        assertEq(realBalance, 950e18);

        vm.expectRevert("insufficient");
        vault.withdraw(address(token), address(this)); // always reverts, forever
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;[PASS] test_FundsLockedForever() (gas: 87211)
Logs:
  accounted:    1000000000000000000000
  real balance: 950000000000000000000
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;1000 requested, 950 actually received, and the vault will try to pay out 1000 every single time —&lt;br&gt;
forever failing.&lt;/p&gt;
&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Never trust the parameter you passed to &lt;code&gt;transferFrom&lt;/code&gt;. Measure what actually arrived:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function topUp(address token, uint256 amount) external {
    uint256 before = IERC20(token).balanceOf(address(this));
    IERC20(token).transferFrom(msg.sender, address(this), amount);
    uint256 received = IERC20(token).balanceOf(address(this)) - before;
    accounted[token] += received;   // credit what you actually got
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One extra &lt;code&gt;balanceOf&lt;/code&gt; call. That's the entire fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why scanners miss this
&lt;/h2&gt;

&lt;p&gt;Static analyzers flag &lt;code&gt;unchecked-transfer&lt;/code&gt; (missing return-value checks) constantly — that's a&lt;br&gt;
different, much shallower bug. This one requires understanding that &lt;code&gt;transferFrom&lt;/code&gt;'s &lt;em&gt;declared&lt;/em&gt;&lt;br&gt;
amount and the &lt;em&gt;received&lt;/em&gt; amount can diverge, and tracing that divergence all the way to a&lt;br&gt;
permanent-lock outcome several functions later. It's a reasoning bug, not a pattern-match bug —&lt;br&gt;
which is exactly why an LLM-assisted, hand-verified review catches it and a pure static scan usually&lt;br&gt;
doesn't.&lt;/p&gt;




&lt;p&gt;I do fast, affordable Solidity security reviews with a zero-false-positive pipeline (every flag is&lt;br&gt;
hand-verified before it reaches you — &lt;a href="https://juan23z.github.io/proof.html" rel="noopener noreferrer"&gt;see the proof&lt;/a&gt;). If&lt;br&gt;
you're shipping a vault, a router, or anything that moves other people's tokens: &lt;a href="https://juan23z.github.io/" rel="noopener noreferrer"&gt;let's talk&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>solidity</category>
      <category>web3</category>
      <category>smartcontracts</category>
    </item>
    <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>
  </channel>
</rss>
