Reference
Verification
edda's central guarantee is Article IV: if edda build succeeds, every property it proved holds — except where a trust annotation says otherwise, and every exception is written down.
How a contract is discharged
Each refinement compiles to an obligation: a predicate to prove, a context of every fact in scope, and the sorts of the symbols involved. The compiler hands it to an SMT solver, which proves context implies predicate by checking the negation for unsatisfiability. An unsat result discharges the obligation. A sat result is a counterexample, rendered back into edda values rather than left as a raw solver model. There is no warning level for an obligation that does not discharge; the build fails.
The decidable fragment
The fragment is chosen so that every obligation terminates with a definite answer, rather than timing out. It is the combination of these theories:
| Theory | Covers |
|---|---|
| EUF | user types and constructors, by congruence closure |
| LIA | addition, subtraction, and multiplication by a constant, over integers |
| Bool | propositional connectives |
| Extensional arrays | read, write, select, store, with extensionality |
| Bounded quantifiers | forall i in 0..<n: P(i) over a finite, static domain |
In SMT-LIB terms, AUFLIA + extensionality + bounded quantifiers. Non-linear arithmetic, bitvectors, floating-point predicates, and unbounded quantifiers fall outside it. Where they are undecidable, they stay outside permanently.
The native compiler discharges this fragment with its own in-tree solver — a CDCL(T) core that runs in-process, with no external dependency. The Rust bootstrap that builds it today uses an embedded Z3 over the identical fragment, so a proof one accepts, the other accepts too.
Certificates and the re-checker
Each discharged obligation produces a certificate, one of five kinds: an SMT unsat core, a comptime trace, a built-in obligation, a recorded @unverified reason, or a recorded @trust reason. Certificates are content-addressed and cached under the obligation and its context.
A separate verifier, around 500 lines, sits between the cache and the build and re-checks every cached unsat claim. It reconstructs the obligation from the canonical encoder and re-runs the solver from scratch, ignoring the cached core. If the two disagree, the build fails. The cache is only a hint the verifier can reject: because every cached result is re-derived by an independent check before it is trusted, a poisoned or stale entry can never slip a false proof into a build.
Termination
A recursive function or an unbounded loop either supplies a decreases measure that strictly drops and is bounded below, or it admits effect divergence in its row. There is no third option, so divergence becomes a property a caller can see and decide to inherit.
function factorial(n: i64) -> i64
requires n >= 0
decreases n
{
if n == 0 { return 1 }
return n * factorial(n - 1)
}Properties, stability, contract diff
Property-based testing
Every requires and ensures is also a runnable property. The compiler builds input generators from the refinement structure and shrinks a failure toward a minimal counterexample.
Stability
The stable keyword asserts equal output for equal input across runs and machines. It is enforced by an effect-row whitelist, a callee whitelist, and a ban on iterating hash maps.
Contract diff
Each signature has a BLAKE3 contract hash. edda contract-diff reports the exact change between two versions, which is what makes an automated edit reviewable.
When a proof is out of reach
The solver is decidable, not omniscient. A claim can depend on a theory the fragment excludes, or come from an external proof, or be true for a reason the encoder will not accept. Two annotations cover these cases. Both require a written reason, both produce certificates that are visibly not SMT proofs, and both are listed by edda lint --trust-points.
@unverified(reason: "Knuth TAOCP 2 4.5.3")
function gcd_extended(a: i64, b: i64) -> GcdResult
requires a > 0 and b > 0
{ ... }Whole-function. Discharge is skipped, but the compiler still synthesizes property tests from the refinements, so the exemption is not silent.
@trust(reason: "bit-shift packing")
let header: u32 = (version << 8) or kindOne site. It accepts a single obligation the verifier cannot reach. There is no assume: an axiom could quietly produce a false certificate, and these cannot.
The trust-points list is the project's full exemption surface. A reviewer can read it, CI can fail on an unreviewed addition, and a published package carries its list in the manifest.