blockulator

35 of 41 core ops proven for every input, 0 for every input but a stated window, 6 fuzzed. 0 counterexamples found and pinned. 5,933 s of solver time.

A test checks the inputs it was given. These checks cover every input: Halmos runs each op of the inline libraries with symbolic arguments, all 2256 or 2512 of them at once, and the solver shows that no input makes the op return a different word, or revert with a different error, from a reference specification written separately from the library code. The specifications are plain Solidity on exact integers, short enough to read: Ref127.sol, Ref19.sol. FP19's reference rounds to 237 bits from comparisons on the exact value, not from the library's normalisation.

Where the proofs found something, it is on this page: two FP127 ops leave their documented semantics on a narrow family of inputs, each pinned by a test that fails once it is fixed. The square roots are fuzzed against exact integer roots, not proved. Method, trust base and limits: docs/proofs.md.

FP127

opstatusthe spectoolsolverreproduce
add proven a + b exact; Overflow outside int256 Halmos 0.3.3, bitwuzla 0.8.1 1 s python3 test/proofs/run.py --only '^(check_fp127_add)$'
sub proven a - b exact; Overflow outside int256 Halmos 0.3.3, bitwuzla 0.8.1 1 s python3 test/proofs/run.py --only '^(check_fp127_sub)$'
mul proven
L1, L2
floor(a b / 2^128); Overflow when it leaves int256 Halmos 0.3.3, bitwuzla 0.8.1 45 s python3 test/proofs/run.py --only '^(check_fp127_mul)$'
mulDown proven
L1, L2
floor(a b / 2^128) Halmos 0.3.3, bitwuzla 0.8.1 48 s python3 test/proofs/run.py --only '^(check_fp127_mulDown)$'
mulUp proven
L1, L2
ceil(a b / 2^128); Overflow when it leaves int256
two checks: every input outside the window where floor(a b / 2^128) = -2^255 - 1, and the window itself (fixed in #159)
Halmos 0.3.3, bitwuzla 0.8.1 419 s python3 test/proofs/run.py --only '^(check_fp127_mulUp_outside|check_fp127_mulUp_window)$'
div proven
K127, L5
trunc(a 2^128 / b); DivisionByZero; Overflow Halmos 0.3.3, bitwuzla 0.8.1 < 1 s python3 test/proofs/run.py --only '^(check_fp127_div)$'
divDown proven
K127, L5
floor(a 2^128 / b) Halmos 0.3.3, bitwuzla 0.8.1 3 s python3 test/proofs/run.py --only '^(check_fp127_divDown)$'
divUp proven
K127, L5
ceil(a 2^128 / b) Halmos 0.3.3, bitwuzla 0.8.1 2 s python3 test/proofs/run.py --only '^(check_fp127_divUp)$'
inv proven
L4, L5
trunc(2^256 / x); DivisionByZero; Overflow Halmos 0.3.3, bitwuzla 0.8.1 < 1 s python3 test/proofs/run.py --only '^(check_fp127_inv)$'
sqrt proven
R1, R2, L1
floor(sqrt(x 2^128)); OutOfRange below zero
a proof by exhaustion and error bounds (test/proofs/isqrt.py): the seed within 2^-4 for each of its 196,608 leading values, five Newton steps to below 2^-128, and the corrections exact; fixed in #159 where it once returned floor + 1
exact integer arithmetic, test/proofs/isqrt.py python3 test/proofs/isqrt.py
floor proven 2^128 floor(x / 2^128) Halmos 0.3.3, bitwuzla 0.8.1 < 1 s python3 test/proofs/run.py --only '^(check_fp127_floor)$'
ceil proven 2^128 ceil(x / 2^128); Overflow at 2^255 Halmos 0.3.3, bitwuzla 0.8.1 < 1 s python3 test/proofs/run.py --only '^(check_fp127_ceil)$'
round proven 2^128 floor(x / 2^128 + 1/2); Overflow at 2^255 Halmos 0.3.3, bitwuzla 0.8.1 < 1 s python3 test/proofs/run.py --only '^(check_fp127_round)$'
toFixed18 proven floor(x 10^18 / 2^128) Halmos 0.3.3, bitwuzla 0.8.1 20 s python3 test/proofs/run.py --only '^(check_fp127_toFixed18)$'
toFixed18Down proven floor(x 10^18 / 2^128) Halmos 0.3.3, bitwuzla 0.8.1 19 s python3 test/proofs/run.py --only '^(check_fp127_toFixed18Down)$'
toFixed18Up proven ceil(x 10^18 / 2^128) Halmos 0.3.3, bitwuzla 0.8.1 48 s python3 test/proofs/run.py --only '^(check_fp127_toFixed18Up)$'
fromFixed18 proven trunc(x 2^128 / 10^18); OutOfRange outside [-2^127, 2^127) Halmos 0.3.3, bitwuzla 0.8.1 < 1 s python3 test/proofs/run.py --only '^(check_fp127_fromFixed18)$'

FP19

opstatusthe spectoolsolverreproduce
add fuzzed a + b rounded to nearest (237 bits, ties away)
Halmos ran out of time (2000 s) on check_fp19_add; fuzzed against the exact spec
Halmos 0.3.3, bitwuzla 0.8.1 python3 test/proofs/run.py --only '^(check_fp19_add)$'
sub fuzzed a - b rounded to nearest
Halmos ran out of time (2000 s) on check_fp19_sub; fuzzed against the exact spec
Halmos 0.3.3, bitwuzla 0.8.1 python3 test/proofs/run.py --only '^(check_fp19_sub)$'
mul proven
L1, L3
a b rounded to nearest Halmos 0.3.3, bitwuzla 0.8.1 430 s python3 test/proofs/run.py --only '^(check_fp19_mul)$'
mulDown proven
L1, L3
a b rounded toward -inf Halmos 0.3.3, bitwuzla 0.8.1 1,023 s python3 test/proofs/run.py --only '^(check_fp19_mulDown)$'
mulUp proven
L1, L3
a b rounded toward +inf Halmos 0.3.3, bitwuzla 0.8.1 731 s python3 test/proofs/run.py --only '^(check_fp19_mulUp)$'
div proven
K19, L3
a / b rounded to nearest Halmos 0.3.3, bitwuzla 0.8.1 114 s python3 test/proofs/run.py --only '^(check_fp19_div)$'
divDown proven
K19, L3
a / b rounded toward -inf Halmos 0.3.3, bitwuzla 0.8.1 303 s python3 test/proofs/run.py --only '^(check_fp19_divDown)$'
divUp proven
K19, L3
a / b rounded toward +inf Halmos 0.3.3, bitwuzla 0.8.1 264 s python3 test/proofs/run.py --only '^(check_fp19_divUp)$'
inv proven
K19
1 / x rounded to nearest Halmos 0.3.3, bitwuzla 0.8.1 60 s python3 test/proofs/run.py --only '^(check_fp19_inv)$'
sqrt fuzzed sqrt(x) rounded to nearest
FuzzProofs against the exact integer root
forge fuzz, 3,000 runs FOUNDRY_PROFILE=proofs forge test --match-contract FuzzProofs --match-test fp19_sqrt
floor proven the integral number at or below x Halmos 0.3.3, bitwuzla 0.8.1 199 s python3 test/proofs/run.py --only '^(check_fp19_floor)$'
ceil proven the integral number at or above x Halmos 0.3.3, bitwuzla 0.8.1 202 s python3 test/proofs/run.py --only '^(check_fp19_ceil)$'
round proven the nearest integral number, halves away from zero Halmos 0.3.3, bitwuzla 0.8.1 206 s python3 test/proofs/run.py --only '^(check_fp19_round)$'
toUintDown proven floor(x) as uint256 Halmos 0.3.3, bitwuzla 0.8.1 < 1 s python3 test/proofs/run.py --only '^(check_fp19_toUintDown)$'
toUintUp proven ceil(x) as uint256 Halmos 0.3.3, bitwuzla 0.8.1 < 1 s python3 test/proofs/run.py --only '^(check_fp19_toUintUp)$'
toIntDown proven floor(x) as int256 Halmos 0.3.3, bitwuzla 0.8.1 1 s python3 test/proofs/run.py --only '^(check_fp19_toIntDown)$'
toIntUp proven ceil(x) as int256 Halmos 0.3.3, bitwuzla 0.8.1 1 s python3 test/proofs/run.py --only '^(check_fp19_toIntUp)$'
toFixed18 fuzzed
L1
x 10^18 to the nearest integer, halves away
Halmos ran out of time (2000 s) on check_fp19_toFixed18; fuzzed against the exact spec
Halmos 0.3.3, bitwuzla 0.8.1 python3 test/proofs/run.py --only '^(check_fp19_toFixed18)$'
toFixed18Down fuzzed
L1
floor(x 10^18)
Halmos ran out of time (2000 s) on check_fp19_toFixed18Down; fuzzed against the exact spec
Halmos 0.3.3, bitwuzla 0.8.1 python3 test/proofs/run.py --only '^(check_fp19_toFixed18Down)$'
toFixed18Up fuzzed
L1
ceil(x 10^18)
Halmos ran out of time (2000 s) on check_fp19_toFixed18Up; fuzzed against the exact spec
Halmos 0.3.3, bitwuzla 0.8.1 python3 test/proofs/run.py --only '^(check_fp19_toFixed18Up)$'
fromUint proven x rounded to nearest Halmos 0.3.3, bitwuzla 0.8.1 36 s python3 test/proofs/run.py --only '^(check_fp19_fromUint)$'
fromUintDown proven x truncated to 237 bits Halmos 0.3.3, bitwuzla 0.8.1 27 s python3 test/proofs/run.py --only '^(check_fp19_fromUintDown)$'
fromUintUp proven x rounded up to 237 bits Halmos 0.3.3, bitwuzla 0.8.1 64 s python3 test/proofs/run.py --only '^(check_fp19_fromUintUp)$'
fromFixed18 proven
K18, L5
round(round(x) / 10^18), each to nearest (one rounding below 2^237) Halmos 0.3.3, bitwuzla 0.8.1 1,661 s python3 test/proofs/run.py --only '^(check_fp19_fromFixed18)$'

Lemmas

The specifications reach exact integers through a few primitives built from the EVM's own opcodes; each is proved over unbounded integers with Z3 (4.12.6), and the op proofs keep multiplication and division uninterpreted, so they hold whatever the solver might assume about a 256-bit product.

  • L1 proved: umul: (MULMOD(a,b,2^256-1) - MUL(a,b) - borrow, MUL(a,b)) = a b
  • L2 proved: smul, signs (+, +): signed high word by the corrections
  • L3 proved: MUL of two exponent fields is zero iff one is
  • L4 proved: DIV(2^256 - u, u) + 1 = floor(2^256 / u)
  • L5 proved: (x XOR SAR(255, x)) - SAR(255, x) = |x|
  • L6 proved: ma mb in [2^472, 2^474) for significands
  • L7 proved: floor(ma 2^255 / mb) >= 2^254
  • K18 proved: q18: floor(m 2^60 / 5^18) by two word divisions, every intermediate a word
  • K127 proved: divq(a, b) = floor(a 2^128 / b): S1-S6, B1-B3
  • K19 proved: divh(a, b) = floor(a 2^255 / b): S1-S6, B1-B3
  • R1 proved: isqrtw(z) = floor(sqrt z) for 2^128 <= z < 2^256
  • R2 proved: FP127 sqrt(x) = floor(sqrt(x 2^128)) for 0 < x < 2^255

Status: proven, every input against the spec, its primitives proved; proven outside one window, every input but the stated family, where a pinned test shows the op leaving its documented result; fuzzed, no symbolic proof, the op is fuzzed against the exact spec; counterexample, fuzzed, with a pinned input where it leaves the spec. Solver time is Halmos's own on one machine, two solver threads per check; it varies run to run. Tools: Halmos 0.3.3 with CLZ added and commuted products (bitwuzla 0.8.1), forge 1.7.1, solc 0.8.38. Every check: make proofs PROOFS_FULL=1. Dataset: proofs.json.