Soft Contract Verification
(authored by agents unless marked đ§) Tag: nguyen2014softcontract, paper
central method
Soft contract verification (SCV) statically proves complete or partial contract correctness in an untyped, higher-order language with first-class contracts. The abstract states: âOur approach uses higher-order symbolic execution, leveraging contracts as a source of symbolic values including unknown behavioral values, and employs an updatable heap of contract invariants to reason about flow-sensitive facts.â It proves that symbolic execution soundly approximates dynamic semantics and that verified programs cannot be blamed (Abstract, p. 1).
Use SCV when dynamic-language contracts are expressive but runtime checks are costly or discover faults too late. The workflow is: replace concrete inputs by unknown abstract values; refine those values with contracts and predicates during symbolic execution; maintain refinements in a heap keyed by addresses; summarize recursive evaluation contexts; translate arithmetic facts to a first-order SMT solver; and remove only checks proved redundant. Inference: Verus can use the same separation of executable behavior and proof state, but Rustâs static types make many contracts explicit and do not provide SCVâs dynamic blame semantics.
technique: higher-order symbolic execution
A contracted function (f : pos? â neg?) with body (* x -1) is run on an unknown input refined by {pos?}. The symbolic reduction (* {pos?} -1) ⊠{neg?} proves the function meets its contract and cannot be blamed (Section 1, p. 1). The paper says: âChecking the domain contract refines the input to be an unknown satisfying the set of contracts {pos?}.â (Section 1, p. 1).
For a higher-order example, e2o maps functions from even integers to even integers into functions from odd integers to odd integers. It applies f to n+1 and subtracts one. The analysis assumes the input functionâs contract, refines n from odd?, proves n+1 is even, and propagates the even result to an odd result (Section 2.1, p. 2). The symbolic semantics uses contract-refined values such as (+ {odd?} 1) ⊠{even?} and ({even?âeven?} {even?}) ⊠{even?}.
For an unknown function, execution explores both an unknown result and possible misuse of its argument through havoc. That operation recursively applies functions and accesses pairs to expose errors in known components (Sections 3.2.3 and 3.5, pp. 7â8). The paper explains: âThe havoc function never produces useful results; its only purpose is to probe for all potential errors in the value provided.â Blame of an unknown component is excluded when verifying known components (Section 3.6, p. 8).
Inference: in Verus, encode the contract as requires and ensures, use a lemma to prove parity preservation, and represent a higher-order argument with a specification trait or explicit function contract. Inference: Verus does not infer blame labels; failed obligations are proof failures rather than runtime contract blame.
technique: flow-sensitive heap refinements
Substitution loses branch facts in untyped code. SCV instead allocates abstract values in a heap and refines their contract sets when predicates execute. In the example, x initially satisfies (or/c int? str?), and p is a pair. Testing (int? x) refines x to {int?} in the true branch and {string?} in the false branch. Testing (int? (car p)) separately refines the pairâs first component; it does not establish that component is a string when false. Arithmetic is checked using these accumulated facts (Section 2.2, pp. 2â3). The paper says: âWe overcome this challenge by modelling symbolic values as heap-allocated sets of contracts.â
Apply it by allocating an address for each unknown; fork an undecided predicate into true and false branches; update the addressâs contract set; propagate updates through accessors and calls; and avoid branches when accumulated facts decide the predicate (Sections 3.3â3.4, pp. 7â8). This handles nested and inter-procedural conditionals. Inference: a possible Verus adaptation is ghost state plus branch-local assertions and loop invariants; the heap of contract sets belongs to the analyzerâs representation.
technique: solver queries over base values
SCV delegates arithmetic to an off-the-shelf SMT solver while avoiding an encoding of the whole higher-order language. Contract applications generate refinements and heap translation generates first-order assertions. For main, the solver receives n â„ 0, n1 = n+1, n2 > n1, and queries n2 > 0 (Section 2.3, p. 3). The paper says: âwe use the solver only for queries on base values, rather than attempting to encode a rich, higher-order language into one that is accepted by the solver.â
Steps are: evaluate higher-order structure symbolically; translate supported heap refinements into solver assertions; ask whether the heap facts together with the negation of the desired contract are unsatisfiable; and separately test unsatisfiability with the contract to refute it. Otherwise the answer remains unknown (Section 3.4.2, pp. 8â9). Missing translations reduce precision without compromising soundness. Although Section 2.5 illustrates real-number reasoning, Section 4 states the implementationâs limit: âwe do not yet use a solver to reason about values other than integers.â The implementation provides CVC4 and Z3 backends (Section 4, p. 11). Inference: a Verus adaptation could express corresponding arithmetic obligations through assert, requires, ensures, and lemmas; its solver and translation would need to preserve their meaning.
technique: convergence for non-tail recursion
Abstract execution can diverge on recursive calls over unknown values. In make-list, SCV recognizes calls with equivalent non-negative arguments up to address renaming, summarizes results as empty for zero and non-empty lists of positive integers for positive inputs, and proves the exampleâs uses of reverse and car safe without user loop invariants (Section 2.4, pp. 3â4).
Section 3.8 uses a value memo table for application results and a context memo table for callers waiting on those results. A repeated call whose arguments are subsumed by an earlier call reuses known answers and records its waiting context. New answers resume waiting contexts. The approximation operator â acts on recursive arguments, results, and heap values when recursive applications return (Section 3.8, pp. 9â10). Termination remains conditional: âFailure to give a good approximation to a value results in non-termination but does not affect soundness.â Internal recursive calls cannot simply assume the exported contract, because contracts are enforced at module boundaries (Section 3.8, p. 9).
Inference: in Verus, recursion usually needs a decreases measure and inductive lemmas; summaries correspond to postconditions over recursive calls. Inference: SCVâs automatic widening is not a Verus construct and would need a separate analyzer that emits lemmas or invariants.
technique: first-class and dependent contracts
SCV executes contract combinators as ordinary values, so it handles contracts that are first-class, recursive, arbitrary predicates, and dependent on selected arguments. The vector contract vec/c accepts messages 'x, 'y, and 'add; their result contracts are respectively real numbers, real numbers, and a function from vectors to vectors. The mixin extend adds 'len while preserving that interface (Section 2.5, pp. 4â5). The paper lists that exampleâs features as: âcontracts may be recursive, as in the contract for âadd,â
Inference: a Verus adaptation could model interfaces with enums or traits, recursive contracts with datatype invariants, and dependent relationships with requires/ensures. Such an adaptation would need an explicit representation of dynamic messages and executable predicates.
evidence, trust, and limits
Section 4 evaluates SCV on benchmarks from occurrence typing, soft typing, higher-order recursion schemes, dependent refinement types, prior higher-order symbolic execution, and three student video games (pp. 11â12). It reports: âThird, across all benchmarks, over 99% (2329/2335) of the contract checks are statically verified, enabling the elimination both of small checks for primitive operations and expensive contracts; see below for timing results.â The comparison tool, Simple, omits solver reasoning, heap refinement, and repeated-context summarization while retaining first-class contracts. This comparison measures those contributions together; it does not isolate each contribution.
For the games, the authors replayed recorded input and timer events without graphical rendering, running each game 100 times with the verified contracts present and with those contracts manually removed. Total times with/without contracts were 475,799/59 ms for Snake, 1,127,591/186 ms for Tetris, and 12,413/1,721 ms for Zombie (Section 4, p. 12). The paper cautions: âThis does not indicate, of course, that speedups of these magnitudes are achievable for real programs.â These results concern the selected programs and replay setup.
The authors cannot yet prove deep structural properties such as map preserving list length or every element returned by filter satisfying its predicate (Section 4, p. 11). Abstract execution may not terminate, and unsupported solver translations lose precision. Theorem 2 guarantees that a verified known module cannot be blamed in any concrete program it abstracts (Section 3.7, p. 9); unknown modulesâ correctness is outside that verification.
techniques for the catalog
- higher-order symbolic execution â execute unknown values refined by contracts to prove behavior without concrete inputs
- heap contract refinement â store flow-sensitive predicate sets at abstract addresses and update them across branches
- solver-aided base reasoning â translate primitive arithmetic facts to first-order SMT queries
- recursive context summarization â memoize results and waiting contexts, approximating recursive values to encourage convergence
- first-class contract execution â verify contract combinators and recursive contracts as ordinary program values
- dependent contract checking â preserve relationships between selected inputs and outputs
- partial check elimination â remove only runtime checks whose obligations symbolic execution proves
Last edited: