Warning
This software is experimental and not intended for production use. Use at your own risk.
Lightweight formal verification for DAML contracts using the Z3 SMT solver. Proves that critical invariants hold for all possible inputs — not just the ones you thought to test.
daml-verify ships with 9 properties across three categories:
| ID | Property | What it proves |
|---|---|---|
| C1 | Conservation total | Total output equals total input across transfer paths |
| C2 | Receiver amount | Receiver gets exactly the requested amount |
| C3 | Sender change | Sender change equals totalInput - requested |
| D1 | scaleFees safety | No division-by-zero when amuletPrice > 0 |
| D2 | Issuance safety | No division-by-zero when capPerCoupon > 0 and totalCoupons > 0 |
| D3 | Ensure sufficient | ensure amount > 0.0 guards all division sites |
| T1 | Transfer temporal | requestedAt < executeBefore follows from preconditions |
| T2 | Allocation temporal | Full temporal chain is consistent |
| T3 | Lock expiry | Lock is always active at creation time |
# Clone and set up
git clone https://github.com/OpenZeppelin/daml-verify.git && cd daml-verify
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
# Run all 9 proofs
python main.pyExpected output:
daml-verify: 9 properties, 9 proved, 0 disproved
[PROVED] C1: conservation total
[PROVED] C2: receiver amount
[PROVED] C3: sender change
[PROVED] D1: scaleFees safety
[PROVED] D2: issuance safety
[PROVED] D3: ensure sufficient
[PROVED] T1: transfer temporal
[PROVED] T2: allocation temporal
[PROVED] T3: lock expiry
# Run all properties
python main.py
# Run a single property class
python main.py --class conservation # C1, C2, C3
python main.py --class division # D1, D2, D3
python main.py --class temporal # T1, T2, T3
# Run a single property
python main.py C1
# Run tests
pytestThe exit code is 0 if all proofs pass, 1 if any fail.
Each property is expressed as a precondition/goal pair over symbolic variables (Z3 reals or integers). The prover checks whether preconditions AND NOT goal is satisfiable:
- UNSAT — the property holds for all inputs (proved).
- SAT — a counterexample exists (disproved), and the model is printed.
- UNKNOWN — the solver could not decide within its limits.
The symbolic models in daml_verify/model/ mirror the logic of the corresponding DAML contract functions. Properties in daml_verify/props/ state what should be true about those models.
daml_verify/
prover.py # Z3 solver integration
reporter.py # Output formatting
model/
transfer.py # CNTS transfer logic
allocation.py # CNTS allocation logic
fees.py # Splice fee computation
props/
conservation.py # Conservation invariants (C1-C3)
division.py # Division safety (D1-D3)
temporal.py # Temporal ordering (T1-T3)
tests/ # pytest suite (one test per property)
main.py # CLI entry point
- Model your DAML contract logic in
daml_verify/model/using Z3 symbolic variables. - Define a property in
daml_verify/props/as a function returning(preconditions, goal). - Register it in
ALL_PROPERTIESindaml_verify/prover.py. - Add a test in
tests/that assertsProofStatus.PROVED.
Example property:
from z3 import Reals, And
def prop_my_invariant():
x, y = Reals("x y")
preconditions = And(x > 0, y > 0)
goal = x + y > 0
return preconditions, goal- Python 3.10+
- z3-solver >= 4.12