Solidity formal verification front-end for the Strata framework. Translates annotated Solidity contracts through the Strata pipeline for verification via SMT solving.
Solidity (.sol) → solc --standard-json → SolcAstParser (Lean) → SolTranslator
→ Laurel IR → Laurel compilation pipeline → Strata Core → VCG → SMT solver (z3/cvc5)
The entire pipeline lives in the strataSolidity binary: it invokes solc,
parses the JSON AST into typed Lean structures, and verifies — no Python,
no intermediate serialization.
- Lean 4 (see
lean-toolchainfor the pinned version) - Strata — vendored as a git submodule at
./Strata(see Strata submodule below) - solc (Solidity compiler)
- z3 or cvc5 SMT solver
- Python 3.12+ — dev/CI tooling only (SARIF schema check, bench CSV enforcer); not needed to verify contracts
StrataSolidity vendors the Strata framework as a git submodule at ./Strata, wired through lakefile.lean's require Strata from "Strata" (see lake-manifest.json, which also pins Strata's own dependency, Strata-DDM). The submodule is pinned to an explicit upstream main commit; see docs/upstream-strata-migration-plan.md for the current pin and what changed upstream. Clone with submodules so the dependency is materialized before lake build:
git clone --recurse-submodules https://github.com/strata-org/Strata-Solidity.git
# or, after a plain clone:
git submodule update --init --recursivescripts/check-strata.sh exits non-zero with a friendly message when Strata/lakefile.toml is missing. It is invoked automatically at the top of Tools/Solidity/scripts/strata-verify.sh, bench/bench.sh, and test/run_tests.sh, so the same precondition is enforced for the verification pipeline, the benchmark harness, and the test runner.
lake build.github/workflows/ci.yml runs on push to main, feat/**, fix/**, and on every pull request. The workflow:
- Checks out StrataSolidity with
submodules: recursiveso the Strata submodule is materialized at the recorded SHA - Verifies the submodule layout via
scripts/check-strata.sh - Installs
solc(via theethereum/ethereumPPA) and the CI-only Python deps inTools/Solidity/requirements-dev.txt - Runs
lake buildandlake testvialeanprover/lean-action, which handles elan install and caches the root.lake; Strata builds intoStrata/.lake, which separate steps restore and save, keyed on the toolchain, the manifest and the submodule commit - Runs
test/run_tests.sh(golden suite + REQ-PBT-007 exit-code sub-stage) - Runs
python3 bench/check_csv_schema.py bench/results.csv(REQ-PBT-008 schema enforcer against the committed CSV) - Runs
python3 -m pytest Tools/Solidity/test(REQ-PBT-004 SARIF 2.1.0 schema compliance)
bench/bench.sh itself is intentionally not in the hot path (b6_erc20 is slow).
Lean panics are errors in CI. A panic! prints a message and continues with
a default value, which in the verifier could mean a wrong verdict, so every
step that compiles or runs StrataSolidity sets LEAN_ABORT_ON_PANIC=1. One
upstream module, Strata.DL.Lambda.LExprEvalProps, is built first without
it: Lean 4.31 panics while writing that module (a known upstream issue that
does not affect the checked proofs), and CI reports it as a warning. Set
LEAN_ABORT_ON_PANIC=1 locally to get the same behavior.
# One-step verification
Tools/Solidity/scripts/strata-verify.sh contract.sol
# With SARIF output
Tools/Solidity/scripts/strata-verify.sh contract.sol --sarifSpecifications are written as NatSpec @custom:strata annotations in doc comments. No stub functions or special imports needed.
| Annotation | Placement | Description |
|---|---|---|
@custom:strata requires <expr> |
function | Precondition — must hold on entry |
@custom:strata ensures <expr> |
function | Postcondition — must hold on exit; supports old(expr) for pre-state values |
@custom:strata invariant <expr> |
contract or state variable | Contract invariant — assumed on entry and checked on exit of every public/external state-changing function, assumed by view functions, and checked after the (explicit or implicit) constructor |
@custom:strata modifies <vars> |
function | Frame condition — only listed state variables may change |
@custom:strata loop-invariant <expr> |
function | Loop invariant — maintained across loop iterations |
@custom:strata forall v : T :: <expr> |
function | Universal quantifier |
@custom:strata exists v : T :: <expr> |
function | Existential quantifier |
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Token {
mapping(address => uint256) public balances;
/// @custom:strata invariant totalSupply == sum(balances)
uint256 public totalSupply;
/// @custom:strata modifies balances[to], totalSupply
/// @custom:strata ensures balances[to] == old(balances[to]) + amount
/// @custom:strata ensures totalSupply == old(totalSupply) + amount
function mint(address to, uint256 amount) external {
totalSupply += amount;
balances[to] += amount;
}
/// @custom:strata requires to != msg.sender
/// @custom:strata requires balances[msg.sender] >= amount
/// @custom:strata modifies balances[msg.sender], balances[to]
/// @custom:strata ensures balances[msg.sender] == old(balances[msg.sender]) - amount
/// @custom:strata ensures balances[to] == old(balances[to]) + amount
function transfer(address to, uint256 amount) external {
require(to != msg.sender, "self-transfer disallowed");
require(balances[msg.sender] >= amount, "insufficient balance");
balances[msg.sender] -= amount;
balances[to] += amount;
}
}Every annotation here pulls its weight:
- The invariant
totalSupply == sum(balances)is the central data-structure property of a token — it is checked on every mutating function, and deletingtotalSupply += amountfrommintmakes verification fail. modifiesis key-scoped:modifies balances[to], totalSupplygenerates a frame condition that every other holder's balance is unchanged — the property that traditional spec-file tools must assert by hand.- The
ensuresclauses state the exact effect in terms of pre-state.
Don't state what the type system or the tool already guarantees:
uint256values are non-negative — never writerequires amount >= 0orinvariant totalSupply >= 0; the verifier assumes non-negativity for every unsigned integer automatically.- State not listed in
modifiesis unchanged — never writeensures x == old(x)for a variable the function doesn't modify; the frame condition already asserts it. - Prefer
modifies m[k]overmodifies mwhenever the touched keys are expressible: the narrower frame is both a stronger theorem and free documentation of the function's write-set.
StrataSolidity accepts a subset of Solidity 0.8.x covering contracts and interfaces, functions and modifiers, common value and reference types (integers, bool, address, bytes32, mappings, arrays, structs, enums), standard control flow and expressions, and NatSpec-annotated specifications. Pipeline-specific conventions (events as no-ops, hashes and ABI encoders as uninterpreted functions, inline assembly and external calls under a havoc strategy) are part of the accepted subset.
For the authoritative, per-feature enumeration, see .kiro/specs/strata-solidity-baseline/requirements.md §3 — Supported Solidity Subset (REQ-SOL-001..REQ-SOL-100). That document is the source of truth; this README intentionally does not duplicate the list to avoid drift.
Plain text (default): per-assertion results with source locations.
==== Contract: Token ====
mint:
[line 8] postcondition: ✅ pass
transfer:
[line 14] precondition: ✅ pass
[line 16] postcondition: ❌ fail
SARIF 2.1.0 (--sarif): machine-readable JSON for CI integration, conforming to the SARIF standard.
Shell wrapper that runs the full pipeline (parse → translate → verify) in one command.
Tools/Solidity/scripts/strata-verify.sh <file.sol> [--sarif] [--verbose]Requires lake build to have been run first.
| Flag | Effect |
|---|---|
--sarif / --json / --pipeline-json |
Output format (default: plain text) |
--verbose |
Also print the generated Laurel and Core programs |
--timeout=<s> |
Per-obligation solver timeout (default 10) |
--no-array-theory |
Encode mappings with quantified select/update axioms instead of SMT arrays (the default since the 2026-10 Strata re-pin; --array-theory is accepted and has no effect) |
--dump-vc=<dir> |
Keep the generated .smt2 files in <dir>/<procedure>/ |
--assume-no-reentrancy |
Trust that external calls do not call back into the contract, so they leave its state unchanged (see Contract Invariants) |
StrataSolidity/ Lean library (dialect, parser, AST → Laurel translation)
StrataSolidityMain.lean CLI entry point (strataSolidity executable)
StrataSolidityTest/ Integration tests (.sol files + expected output)
Tools/Solidity/
scripts/strata-verify.sh Thin shell wrapper around the binary
test/ SARIF schema check (CI-only Python)
lake test # Lean unit + characterization tests (#guard suites, JSON/pipeline-JSON shape)
bash test/run_tests.sh # golden suite (test/examples/*.sol vs test/expected/) + exit-code contractCopyright Strata Contributors. SPDX-License-Identifier: Apache-2.0 OR MIT