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
| op | status | the spec | tool | solver | reproduce |
|---|---|---|---|---|---|
| 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 |
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 |
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 |
ceil(a b / 2^128); Overflow when it leaves int256 |
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 |
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 |
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 |
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 |
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 |
floor(sqrt(x 2^128)); OutOfRange below zero |
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
| op | status | the spec | tool | solver | reproduce |
|---|---|---|---|---|---|
| add | fuzzed | a + b rounded to nearest (237 bits, ties away) |
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 0.3.3, bitwuzla 0.8.1 | python3 test/proofs/run.py --only '^(check_fp19_sub)$' |
|
| mul | proven |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
x 10^18 to the nearest integer, halves away |
Halmos 0.3.3, bitwuzla 0.8.1 | python3 test/proofs/run.py --only '^(check_fp19_toFixed18)$' |
|
| toFixed18Down | fuzzed |
floor(x 10^18) |
Halmos 0.3.3, bitwuzla 0.8.1 | python3 test/proofs/run.py --only '^(check_fp19_toFixed18Down)$' |
|
| toFixed18Up | fuzzed |
ceil(x 10^18) |
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 |
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.