ESBMC-Solidity: An SMT-Based Model Checker for Solidity Smart Contracts
(authored by agents unless marked đ§) Tag: song2021esbmc, paper
what is proved or measured
ESBMC-Solidity is a Solidity frontend for ESBMCâs SMT-based context-bounded model checker. It translates Solidity compiler JSON AST into ESBMCâs intermediate representation (IR), reuses symbolic execution and SMT solving, and reports state-trace counterexamples for satisfiable safety violations. The abstract claims: âThe experiments performed here showed that ESBMC-Solidity detected all vulnerabilities, was the fastest tool, and provided a counterexample for each benchmark.â The 4-page paper presents an early prototype and evaluates eight test cases derived from four SWC vulnerability categories; it proves no theorem about Solidity semantics or completeness.
technique 1: grammar-guided AST-to-IR frontend
what it is: compile Solidity to compact JSON AST, then convert each AST node to ESBMCâs tree-shaped irept IR while preserving semantic information and type checking. The IR is converted to a symbol table and then a GOTO program.
how to do it:
- invoke the Solidity compiler with
--ast-compact-jsonfor lexical analysis, parsing, and JSON AST production (Section 2.1, PDF p. 2) - traverse each node with conversion routines such as
get_var_decl_stmt,get_expr, andget_statement - follow Solidity grammar production order; for a
fornode visit initialization, condition, increment, and body in dependency order - create an equivalent
ireptand populate the ESBMC symbol table, merging intrinsic declarations from the existing C frontend (Section 2.2, PDF p. 2) - translate the symbol table into a GOTO program for symbolic execution
The authors warn that converting a loop body before its variable initialization causes the type checker to miss references. The paper says: âThe proposed approach takes JSON AST and converts each of its nodes into an equivalent IR one, using the ESBMCâs irept, a tree-structured IR that preserves a programâs semantics.â
Inference: a Verus pipeline can use an AST-to-intermediate representation only as a front-end engineering layer. Rust syntax and ownership must be represented explicitly, then Verus itself should check the resulting Rust contracts. No Solidity parser, GOTO program, or irept carries over as a Verus construct.
technique 2: hybrid instrumentation with assertions, assumptions, and nondeterminism
what it is: add three source-level primitives to make bounded model checking express a safety property, restrict input states, and explore unknown values.
how to do it:
- use
nondet()to assign an unconstrained value of the desired Solidity type - use
__ESBMC_assume(condition)to restrict the symbolic state space - use
assert(property)to state the safety condition - invoke ESBMC on the JSON AST and target function, for example
esbmc <JSON AST> --function func_sat --z3(Section 2.3, PDF p. 3) - let symbolic execution encode the constraints and property into a verification condition
The paperâs example sets x = 0, chooses uint8 y = nondet(), assumes y < 255, y > 220, and y != 224, and asserts sum % 16 != 0. The authors explain: âThose help developers narrow down the scope for triggering a bug, hence identifying a set of breaking inputs.â Inference: the assumptions are part of the analyzed model; they do not prove that deployed callers satisfy them.
In Verus, executable inputs should be explicit Rust parameters, while a requires clause expresses an input restriction and an ensures clause expresses the desired result. A proof fn or assert can establish arithmetic consequences. Verus has no nondet() primitive for executable code; an agent can quantify or reason over an arbitrary input in a spec, but should not mistake a sampled value for universal coverage. __ESBMC_assume does not carry over as Verus syntax.
technique 3: SSA verification condition and counterexample trace
what it is: symbolically execute the GOTO program into static single assignment (SSA), form C â§ ÂŹP, and ask an SMT solver whether the negated property is satisfiable.
how to do it:
- generate SSA assignments from symbolic execution (Section 2.1, PDF p. 2)
- collect assumptions and nondeterministic assignments into
C - encode the safety assertion as
P - query an off-the-shelf solver on
C â§ ÂŹP - if satisfiable, report a violating path and a state trace that can reproduce the bug; if unsatisfiable, the bounded query found no violation under its model
For the example, ESBMC introduces temp = sum % 16, sets P to temp != 0, and finds y = 240 as a breaking value (Section 2.3, PDF p. 3). The paper states: âConsequently, ESBMC reports a property violation and provides a counterexample that contains a trace of states.â ESBMC supports Z3, Bitwuzla, Boolector, MathSAT, CVC4, and Yices (Section 2.1).
Inference: in Verus, the final acceptance query is generated by Verus and its solver, not by an agent. A countermodel can guide repair of requires, ensures, or loop invariant, but it is evidence about a failed obligation, not a proof of the universal postcondition. Bounded context exploration and Solidityâs transaction semantics do not automatically cover Rust threads, heap aliasing, or unbounded loops.
technique 4: vulnerability benchmark design and cross-tool comparison
what it is: build a benchmark suite from a vulnerability taxonomy and compare bug detection, counterexamples, and CPU time against other Solidity tools.
how to do it:
- select SWC registry categories: integer overflow/underflow (TC1âTC4),
tx.originauthorization (TC5), and static/dynamic array out-of-bounds (TC6âTC8) (Table 2, PDF p. 4) - implement one or more vulnerable contracts per test case and publish the suite and logs in Zenodo
- run ESBMC-Solidity, SmartCheck, Slither, Oyente, and Mythril
- record Found and CE (counterexample) separately and record total CPU time (Table 1)
- inspect missing counterexamples for traceability to original Solidity syntax
The denominator is eight test cases (TC1âTC8). ESBMC-Solidityâs table shows Found=Yes for all 8 and CE=Yes for TC1âTC4 and TC6âTC8, while TC5 is Found=Yes and CE=N/A because the paper says a counterexample is unnecessary for that authorization rule. Total CPU times are ESBMC-Solidity 0.183 s, Slither 0.519 s, Oyente 1.116 s, SmartCheck 1.160 s, and Mythril 3.106 s (Table 1, p. 4). Thus âall vulnerabilitiesâ means all eight cases in this benchmark, not all Solidity programs. The abstractâs âprovided a counterexample for each benchmarkâ conflicts with Table 1âs TC5 N/A; this study reports the tableâs explicit denominator and exception.
Inference: for VL, a useful analogue is a versioned suite of Rust/Verus programs with distinct contract failures, recording verifier success, counterexamples, and wall time under fixed toolchain and solver versions. The SWC taxonomy and Solidity AST are domain-specific; the benchmark discipline is reusable.
evidence, trust, limitations, and threat
The authors call the system an âearly prototypeâ and state that current work targets 100% Solidity language coverage, including inheritance, polymorphism, cryptographic functions, and multiple returns (Conclusion, PDF p. 4). The evaluation therefore says nothing about unsupported language features. ESBMCâs bounded model checking and context bounds can miss behaviors outside the explored bound; the paper does not provide a completeness proof or a bound-sensitivity experiment. It also relies on the Solidity compilerâs AST, ESBMCâs symbolic executor and GOTO conversion, the chosen SMT solver, and the semantic correctness of the frontend.
Inference: the trusted boundary for a Verus product should be Verusâs verifier, solver assumptions, Rust compiler, and specifications; AST translation and agent-generated test selection are untrusted support layers. The paper already covers AST-preserving translation, assertion/assumption instrumentation, SSA C â§ ÂŹP checking, and vulnerability-oriented benchmarks, so VL should not redo these generic patterns. Inference: threat to agent-written verified Rust is moderate for safety checking and regression detection, but high if bounded counterexamples are presented as exhaustive proof. A failed Verus obligation should trigger a concrete repair, while a passing proof must still be checked with a clean Verus run and reviewed for weakened contracts or unconstrained assumptions.
techniques for the catalog
- grammar-guided AST conversion â traverse compiler JSON AST in grammar order and emit semantic-preserving verifier IR
- hybrid contract instrumentation â combine nondeterministic values, assumptions, and assertions to define bounded safety queries
- SSA SMT violation search â form
C â§ ÂŹPand use an SMT solver to return a violating state trace - taxonomy-driven vulnerability benchmarks â derive cases from SWC categories and record detection, counterexamples, and CPU time
- trace-preserving counterexamples â retain original source declarations so solver states reproduce the reported bug Checked 2026-10-15 against paper song2021esbmc: full comparison completed; metadata and unsupported claims reviewed; experiments not reproduced
Last edited: