Overview
The Solidity frontend converts smart contract source files into ESBMC’s internal representation, allowing the engine’s bounded model checker and SMT backend to verify program properties.
What does ESBMC check?
ESBMC explores program executions symbolically — instead of running the contract on concrete inputs, it reasons about all inputs at once and asks an SMT solver whether a property can ever fail.
Two verification modes are relevant for Solidity:
- Bounded model checking (BMC) — Unrolls loops up to a fixed bound
N(--unwind N) and checks every reachable state for property violations. If a counterexample exists withinNiterations, ESBMC reports it. If no counterexample exists, the result is “no bug up to bound N” — soundness for bug-finding, but not a proof of full correctness beyondN. - k-induction (
--k-induction) — Combines a base-case BMC check with an inductive step that tries to prove the property holds for every iteration, regardless of bound. When successful, this yields a full proof of correctness.
You do not need to choose by hand: BMC is the default, and --k-induction opts into the inductive proof rule. See Usage for examples of each.
Pipeline
The frontend has four stages before ESBMC’s backend takes over:
contract.sol
│
▼ (1) solc --ast-compact-json
contract.solast (JSON AST)
│
▼ (2) AST normalisation & multi-file merge
clean AST
│
▼ (3) AST → IRep2 (type/expr/decl converters)
symbol table
│
▼ (4) harness generation
GOTO program ──► symbolic execution ──► SMT formula ──► solver1. Solc Invocation
ESBMC invokes the Solidity compiler to obtain a JSON Abstract Syntax Tree:
solc --ast-compact-json contract.solThe solc binary is resolved in this order: --solc-bin <path>, then the $SOLC environment variable, then solc on $PATH. Solidity ≥ 0.5.0 is supported; ≥ 0.7 is recommended.
You can also bypass solc entirely by supplying a pre-generated AST as contract.solast:
solc --ast-compact-json contract.sol > contract.solast
esbmc --sol contract.sol contract.solast --contract MyContract2. AST Normalisation
The frontend strips JSON-null fields (which different solc versions emit inconsistently) and merges multi-file imports into a single AST.
3. AST → IRep2
The cleaned AST is traversed and each contract, function, type, and expression is mapped to ESBMC’s typed internal representation (IRep2). State variables become module-level symbols; functions become first-class methods; mapping, dynamic arrays, and structs lower to operational models in src/c2goto/library/solidity/.
4. Harness Generation
Smart contract verification differs from C verification: there is no main(). ESBMC synthesises one automatically — a dispatcher harness:
constructor(); // run state initialisation
while (nondet_bool()) { // unbounded sequence of calls
set_msg_context(); // nondet sender, value, etc.
switch (nondet_uint()) { // nondet function selection
case 0: f1(...); break;
case 1: f2(...); break;
...
}
// assertions are checked after every call
}This explores every reachable contract state across every possible sequence of public-function calls. Use --focus-function <name> to restrict the dispatcher to a single function while still running the constructor.
5. Backend
Once the GOTO program is built, ESBMC’s backend runs symbolic execution to produce SSA (static single-assignment) form, encodes the result as a first-order SMT formula, and discharges it with an SMT solver.
The default solver for Solidity is Bitwuzla. Pass --bitwuzla, --z3, or --cvc5 to pick a specific backend.