Skip to main content

VDD: Boundary-Only Spec to Verified Solution

This tutorial shows a VDD flow where a bodyless abstract spec is used as a temporary proof interface, and then refined into a concrete implementation that the verifier checks. We call the step of adding bodies refining. For the language rules behind this pattern, see pure functions and abstract functions.

1) Start with a declaration-only spec

We only write ensures clauses and leave the body out. The declaration is an assumption while it is abstract, so it belongs in the VDD scaffolding phase. The real proof happens when we refine it with a body.

Source: snippets/equation_system_body_sat_decl.mo

module {
public pure abstract func conditionalBounds(x : Int) : Int
ensures x == 5 ==> result > 10;
ensures x == 8 ==> result < 5;
ensures x == 12 ==> result > 14;
ensures x == 14 ==> result < 5;
ensures x == 16 ==> result > 18;
ensures (x == 5 or x == 12 or x == 16) ==> result % 2 == 1;
ensures (x == 8 or x == 14) ==> result % 2 == 0;
ensures (x == 5 or x == 12 or x == 16) ==> result * result > x * x;
ensures (x == 8 or x == 14) ==> result * result <= 16;
}

Check the declaration with --check or --viper. If it type-checks, callers can be written against the interface while the implementation is still open.

Here are the denied ranges at each x-value (the function must avoid these segments):

conditionalBounds denied ranges

2) Refine into a concrete solution

One way to satisfy pointwise constraints is to build a Lagrange interpolation polynomial that hits specific values at x = 5, 8, 12, 14, 16. An AI (or a CAS) can propose the coefficients, and the verifier confirms the refinement.

Source: snippets/equation_system_body_sat.mo

module {
public func conditionalBounds(x : Int) : Int
ensures x == 5 ==> result > 10;
ensures x == 8 ==> result < 5;
ensures x == 12 ==> result > 14;
ensures x == 14 ==> result < 5;
ensures x == 16 ==> result > 18;
ensures (x == 5 or x == 12 or x == 16) ==> result % 2 == 1;
ensures (x == 8 or x == 14) ==> result % 2 == 0;
ensures (x == 5 or x == 12 or x == 16) ==> result * result > x * x;
ensures (x == 8 or x == 14) ==> result * result <= 16;
{
let term1 = 11 * (x - 8) * (x - 12) * (x - 14) * (x - 16) / 2079;
let term2 = 15 * (x - 5) * (x - 8) * (x - 14) * (x - 16) / 224;
let term3 = 19 * (x - 5) * (x - 8) * (x - 12) * (x - 14) / 704;
term1 + term2 + term3
};
}

Verify the implementation, then visualize it against the denied ranges. The important shift is that the contracts stop being only an assumed interface: the body is now responsible for every ensures clause.

conditionalBounds with function