<?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: Dhruv Rastogi</title>
    <description>The latest articles on DEV Community by Dhruv Rastogi (@dhruv_rastogi_984b0107f19).</description>
    <link>https://dev.to/dhruv_rastogi_984b0107f19</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%2F3439471%2F836f3a4f-2900-4e96-a669-b96fe62c2d9b.png</url>
      <title>DEV Community: Dhruv Rastogi</title>
      <link>https://dev.to/dhruv_rastogi_984b0107f19</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dhruv_rastogi_984b0107f19"/>
    <language>en</language>
    <item>
      <title>I built a formal verifier for EVM contracts with no SMT solver</title>
      <dc:creator>Dhruv Rastogi</dc:creator>
      <pubDate>Wed, 01 Jul 2026 16:53:06 +0000</pubDate>
      <link>https://dev.to/dhruv_rastogi_984b0107f19/i-built-a-formal-verifier-for-evm-contracts-with-no-smt-solver-1oek</link>
      <guid>https://dev.to/dhruv_rastogi_984b0107f19/i-built-a-formal-verifier-for-evm-contracts-with-no-smt-solver-1oek</guid>
      <description>&lt;p&gt;Most smart-contract bugs aren't exotic. They're an &amp;gt; that should have been a &amp;gt;=, an&lt;br&gt;
invariant that quietly stops holding after one mutator, an off-by-one in accounting. Tests&lt;br&gt;
catch these only if you happen to test the input that breaks them. I wanted something that&lt;br&gt;
either proves a property holds for every input, or hands me a &lt;strong&gt;concrete&lt;br&gt;
counterexample&lt;/strong&gt; — and I wanted to build the proving core myself, with no Z3, no JavaSMT,&lt;br&gt;
no external solver.&lt;/p&gt;

&lt;p&gt;This is how DhrLang's contract prove works, and where I deliberately drew the line.&lt;/p&gt;

&lt;p&gt;The contract you write&lt;/p&gt;

&lt;p&gt;You annotate a function with what should always be true, and run the prover:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@contract&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Adder&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@checked&lt;/span&gt;
    &lt;span class="nd"&gt;@ensures&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dhrlang contract prove &lt;span class="nt"&gt;--bound&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8 adder.dhr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every obligation comes back as exactly one of three verdicts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PROVED&lt;/strong&gt; — true for all inputs (here it is: &lt;code&gt;num&lt;/code&gt; parameters are non-negative 256-bit
values, so &lt;code&gt;a + b &amp;gt;= a&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;REFUTED&lt;/strong&gt; — false, with a reproducing counterexample.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UNKNOWN&lt;/strong&gt; — the prover can't decide, and says so instead of guessing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That third verdict is the whole ethic of the thing: it is &lt;strong&gt;sound&lt;/strong&gt;. It never says PROVED&lt;br&gt;
without a real proof, and never says REFUTED without a confirmed concrete failure.&lt;/p&gt;
&lt;h2&gt;
  
  
  The spec language
&lt;/h2&gt;

&lt;p&gt;Three annotations, plus an arithmetic-mode marker:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@requires(...)&lt;/code&gt; — a precondition the caller must satisfy.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@ensures(...)&lt;/code&gt; — a postcondition over parameters and &lt;code&gt;result&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@invariant(...)&lt;/code&gt; — a contract-wide property every mutator must preserve.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A guarded subtraction proves exactly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@contract&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Sub&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@requires&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@checked&lt;/span&gt;
    &lt;span class="nd"&gt;@ensures&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a contract invariant, checked against a state-mutating method (&lt;code&gt;kaam&lt;/code&gt; is DhrLang's&lt;br&gt;
keyword for a void method):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@invariant&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@contract&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Sum&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@storage&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;@storage&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;@storage&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;@checked&lt;/span&gt;
    &lt;span class="n"&gt;kaam&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The prover symbolically runs &lt;code&gt;set&lt;/code&gt; from the contract's genesis state and proves &lt;code&gt;total ==&lt;br&gt;
a + b&lt;/code&gt; still holds afterward — for all &lt;code&gt;x, y&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  How it works, part 1: symbolic execution
&lt;/h2&gt;

&lt;p&gt;Each function is analyzed from the canonical post-construction state (storage scalars &lt;code&gt;0&lt;/code&gt;).&lt;br&gt;
Parameters become fresh &lt;strong&gt;non-negative 256-bit symbols&lt;/strong&gt;. Then the body is interpreted&lt;br&gt;
symbolically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;straight-line code, local declarations/assignments, and scalar storage writes update a
symbolic environment;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;if&lt;/code&gt; / &lt;code&gt;else&lt;/code&gt; &lt;strong&gt;forks&lt;/strong&gt; the path, adding the branch condition (or its negation) as an
assumption;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;require&lt;/code&gt; / &lt;code&gt;assert&lt;/code&gt; / &lt;code&gt;revert&lt;/code&gt; either &lt;strong&gt;assume&lt;/strong&gt; a condition onward or &lt;strong&gt;prune&lt;/strong&gt; the
path entirely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At each non-reverting terminal path, the prover forms one Hoare-style obligation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(requires  ∧  path-conditions  ∧  every atom ≥ 0)   ⇒   goal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How it works, part 2: keeping it linear (the fun part)
&lt;/h2&gt;

&lt;p&gt;Terms are represented as an immutable &lt;strong&gt;linear form&lt;/strong&gt;, &lt;code&gt;constant + Σ coeffᵢ · atomᵢ&lt;/code&gt;. The&lt;br&gt;
decision procedure only needs operations that keep a form linear: add, subtract, negate,&lt;br&gt;
and multiply/divide by a &lt;em&gt;constant&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;So what about genuinely non-linear things — &lt;code&gt;a * b&lt;/code&gt;, division, remainder, shifts, a mapping&lt;br&gt;
read? Those get folded into a single &lt;strong&gt;opaque atom&lt;/strong&gt;: an interned identifier standing for&lt;br&gt;
"some non-negative 256-bit value I won't reason about further." The trick that makes this&lt;br&gt;
pay off is &lt;strong&gt;interning&lt;/strong&gt;: two structurally identical opaque sub-terms get the &lt;em&gt;same&lt;/em&gt; key.&lt;br&gt;
That means a goal like &lt;code&gt;result == a * b&lt;/code&gt; is provable by pure linear reasoning — &lt;code&gt;a * b&lt;/code&gt;&lt;br&gt;
appears on both sides as the same opaque atom and cancels — even though the prover never&lt;br&gt;
multiplies two unknowns. It reasons about the &lt;em&gt;shape&lt;/em&gt;, not the value.&lt;/p&gt;
&lt;h2&gt;
  
  
  How it works, part 3: deciding the obligation
&lt;/h2&gt;

&lt;p&gt;To decide &lt;code&gt;hypotheses ⇒ goal&lt;/code&gt;, I check whether &lt;code&gt;hypotheses ∧ ¬goal&lt;/code&gt; is unsatisfiable:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Negate the goal.&lt;/strong&gt; A strict inequality is tightened using integrality — &lt;code&gt;G &amp;gt; 0&lt;/code&gt;
becomes &lt;code&gt;G ≥ 1&lt;/code&gt; (i.e. &lt;code&gt;G - 1 ≥ 0&lt;/code&gt;) — and an equality goal &lt;code&gt;G == 0&lt;/code&gt; splits into two
independent refutations, &lt;code&gt;G ≥ 1&lt;/code&gt; and &lt;code&gt;G ≤ -1&lt;/code&gt;, both of which must be impossible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build the system.&lt;/strong&gt; Every hypothesis, the negated goal, and one &lt;code&gt;atom ≥ 0&lt;/code&gt; row per
atom (the uint256 domain constraint) become rows of the form
&lt;code&gt;Σ coeffᵢ·atomᵢ + constant ≥ 0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Eliminate.&lt;/strong&gt; Project atoms out one at a time with &lt;strong&gt;Fourier–Motzkin&lt;/strong&gt; — pairing each
positive-coefficient row with each negative one (scaling by positive integers only, so
no division). If the system ever collapses to a bare &lt;code&gt;constant &amp;lt; 0&lt;/code&gt;, it's contradictory:
the negation is impossible, so the spec is &lt;strong&gt;PROVED&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Why this is sound: the elimination decides satisfiability over the &lt;strong&gt;rationals&lt;/strong&gt;, and a&lt;br&gt;
system with no rational solution has no integer solution either, so rational-UNSAT ⇒&lt;br&gt;
integer-UNSAT. The prover therefore can't wrongly declare PROVED — at worst it fails to&lt;br&gt;
prove a true fact, which just becomes UNKNOWN.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why checked arithmetic is load-bearing
&lt;/h2&gt;

&lt;p&gt;The linear theory models &lt;code&gt;+&lt;/code&gt;, &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;*&lt;/code&gt; as &lt;strong&gt;mathematical&lt;/strong&gt; integers. That's only sound if&lt;br&gt;
overflow can't silently wrap. Under &lt;code&gt;@checked&lt;/code&gt;, an overflowing operation &lt;strong&gt;reverts&lt;/strong&gt;, so on&lt;br&gt;
every &lt;em&gt;non-reverting&lt;/em&gt; path the values equal their exact mathematical results and the proof&lt;br&gt;
transfers to the EVM. Under &lt;code&gt;@unchecked&lt;/code&gt;, proving is switched off and the obligation is&lt;br&gt;
UNKNOWN:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@unchecked&lt;/span&gt;
&lt;span class="nd"&gt;@ensures&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="nf"&gt;addU&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;// → UNKNOWN, by design&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is exactly why DhrLang v4.0.0 made checked arithmetic the &lt;strong&gt;default&lt;/strong&gt; (matching&lt;br&gt;
Solidity 0.8+): the safe semantics are also the &lt;em&gt;provable&lt;/em&gt; ones.&lt;/p&gt;
&lt;h2&gt;
  
  
  When it can't prove: refutation with zero false positives
&lt;/h2&gt;

&lt;p&gt;Change the spec to something false:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@checked&lt;/span&gt;
&lt;span class="nd"&gt;@ensures&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;// false when b == 0&lt;/span&gt;
&lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The prover does a bounded concrete search and finds &lt;code&gt;a=0, b=0&lt;/code&gt;. Crucially, it does &lt;strong&gt;not&lt;/strong&gt;&lt;br&gt;
report that on linear reasoning alone — it hands the candidate to DhrLang's existing&lt;br&gt;
concrete spec-fuzzer (the L3 engine) and only reports REFUTED once that engine &lt;em&gt;actually&lt;br&gt;
executes the function and reproduces the violation&lt;/em&gt;. The fuzzer is the ground-truth oracle,&lt;br&gt;
so there are &lt;strong&gt;zero false positives&lt;/strong&gt;. A broken invariant (&lt;code&gt;total = x + y + 1&lt;/code&gt;) is caught&lt;br&gt;
the same way and reported as &lt;code&gt;invariant violated&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it deliberately does NOT do
&lt;/h2&gt;

&lt;p&gt;This is the part I'd put first in any interview, because the honesty is the point:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Loops&lt;/strong&gt; aren't modeled — a &lt;code&gt;while&lt;/code&gt; makes the postcondition UNKNOWN.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mapping aliasing, external calls, and nonlinear combinations&lt;/strong&gt; the linear theory can't
see all collapse to UNKNOWN.&lt;/li&gt;
&lt;li&gt;It is &lt;strong&gt;experimental (L2b)&lt;/strong&gt;, not production, and it is &lt;strong&gt;not&lt;/strong&gt; a Certora/Halmos
competitor. Those are mature, full SMT-backed tools that verify real Solidity. This is a
from-scratch core I built to &lt;em&gt;understand&lt;/em&gt; symbolic execution and decision procedures from
the metal up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What it is: a small, &lt;strong&gt;sound&lt;/strong&gt; verifier with no external solver, that turns a class of&lt;br&gt;
real contract properties into a yes / counterexample / honest-shrug answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I took away
&lt;/h2&gt;

&lt;p&gt;Building this taught me the EVM execution model, design-by-contract, symbolic execution,&lt;br&gt;
and just enough proof theory to know why the rationals make Fourier–Motzkin sound here. The&lt;br&gt;
hardest and most satisfying decision was the discipline of UNKNOWN — a verifier that lies is&lt;br&gt;
worse than no verifier, and most of the engineering went into &lt;em&gt;not&lt;/em&gt; overclaiming.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Code: github.com/dhruv-15-03/DhrLang (&lt;code&gt;src/main/java/dhrlang/proving&lt;/code&gt;). More of my work:&lt;br&gt;
&lt;a href="https://dhruvrastogi.me" rel="noopener noreferrer"&gt;dhruvrastogi.me&lt;/a&gt;. I'm open to blockchain-infra / backend/tooling roles — happy to walk&lt;br&gt;
through any of this.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>blockchain</category>
      <category>webdev</category>
      <category>computerscience</category>
    </item>
  </channel>
</rss>
