Skip to content

Commit b460c09

Browse files
ehildenbclaude
andauthored
Simplification lemmas to improve Haskell booster coverage (#2859)
* lemmas/bytes-simplification.k: add preserves-definedness to lookup-as-asWord Without this attribute Booster refused to apply the rule because the LHS contains the non-total hook Lbl_[_]_BYTES-HOOKE. After the fix Booster correctly applies it (8× succeeded in the execute-node simplify request). Analysis: scripts/bench-prove.py --analyse-fallbacks shows the "Uncertain about definedness of rule due to: non-total symbol Lbl_[_]_BYTES-HOOKE" error gone, replaced by successful Booster applications. The rule-index gap and indeterminate-match cases for other equations remain and will be addressed in follow-up commits. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> (cherry picked from commit 25e00d935ac1f9ce25394e40865dbb36c0cbd80b) (cherry picked from commit 6ae0def0a1de63aa5ffb13d1cdb2abe7d5c3651d) * lemmas: Round 2 Booster gap fixes bytes-simplification.k: add preserves-definedness to lengthBytes-buf and lengthBytes-range. evm-int-simplification.k: add asWord-buf-inversion-rangeUInt256. (cherry picked from commit d4dad3335e7e98d76057c9c0b07387a21a20c0b2) (cherry picked from commit c60634bc589f05a98aa30b06409df4ed478d407f) * lemmas/int-simplification.k: normalize ==Int commutativity concrete-on-left Kore's execute-fallback applies INT-KORE.eq-int-true-rigth which can swap ==Int argument order in path conditions (e.g. lengthBytes(BA) ==Int 32 → 32 ==Int lengthBytes(BA)). This makes Booster's implies check fail due to a syntactic mismatch even when the logical content is identical. The new rule [int-eq-comm-concrete] at priority 30 fires before the +Int cancellation rules and normalises concrete-on-left to concrete-on-right, restoring the form the target spec expects. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> (cherry picked from commit 1d384970bda6669b479959a31f152c0e8aedde35) (cherry picked from commit 2cf8e8c620fee897758f02f5b2b512595428fbec) * lemmas/{int-simplification,lemmas}.k: add arithmetic + Bool simplification lemmas Adds to INT-SIMPLIFICATION-COMMON: - A <=Int A => true (reflexivity; fixes chop-overflow-02 under --booster-only-simplify) - A <=Int A +Int B => 0 <=Int B - A +Int ((B -Int A) +Int C) => B +Int C (4-term cancellation; fixes cancellation-02) - A <Int B andBool B <Int A => false (strict order contradiction; fixes b2w-03) Adds to INT-SIMPLIFICATION-HASKELL: - A <Int B andBool C <=Int A => false requires B <=Int C [concrete(B,C)] (range contradiction) - C <=Int A andBool A <Int B => false requires B <=Int C [concrete(B,C)] (range contradiction) (requires is purely concrete, evaluated by LLVM hook — no circular self-application risk) Adds to LEMMAS-HASKELL: - B orBool notBool B => true (and commuted variant; fixes b2w-05, chop-additional-knowledge) - B andBool notBool B => false (and commuted variant) Validation: full functional test suite passes in normal mode (slot-updates-spec.k failure is pre-existing, unrelated to these changes). Under --booster-only-simplify, lemmas-spec.k drops from 22 to 18 failing claims. No loops detected (checked via kevm-analyse hot-rules). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> (cherry picked from commit 3bb01297db74cacf75fa138f0550440882df8de1) (cherry picked from commit 4c13678fdb82d593bfc495cf0418804a3e505c90) * lemmas/int-simplification.k: short-circuit A <=Int A+Int B to true when 0 <=Int B Add a higher-priority (40) variant of `A <=Int A+B => 0 <=Int B` that short-circuits directly to `true` when `0 <=Int B` is already in the path condition, fixing `chop-overflow-01` in --booster-only-simplify mode where the intermediate `0 <=Int Y` form cannot be discharged at the implies check. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> (cherry picked from commit bfb0225f96a28cbf42ddd520dd49773b234944e5) * lemmas/bytes-simplification.k: add concrete b"" concat identity rules In K, .Bytes (the constructor) and b"" (LLVM-evaluated concrete empty bytes, a Kore domain value \dv{SortBytes}("")) are different Kore terms. The existing bytes-concat-empty-right/left rules use .Bytes and do not fire when LLVM hooks (e.g. #encodeArgs base case) return b"". New rules: [bytes-concat-empty-right-concrete]: B +Bytes b"" => B [bytes-concat-empty-left-concrete]: b"" +Bytes B => B Fixes booster-only failure for encodepacked-keccak01: the contract evaluates keccak(1 : #encodeArgs(#uint256(A0))) where the #encodeArgs base case produces b"", leaving keccak(X +Bytes b"") vs keccak(X) as the implies-check mismatch. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> (cherry picked from commit 829d293906d6efb769649002b2cea2ebf90235fc) (cherry picked from commit a4669b0942edb8c974750490affdad4e1ed1d54d) * buf.md, abi-spec.k: add [preserves-definedness] to 2^(8*SIZE) simplification, add coverage claim The rule `2 ^Int (8 *Int SIZE) => #powByteLen(SIZE)` was [mixed] in booster: it sometimes matched successfully but sometimes failed because booster rejected the rewrite without proof that #powByteLen(SIZE) is defined. Since #powByteLen is a function symbol with no-evaluators (always yields a defined term), adding [preserves-definedness] is safe and fixes the gap. This was the root cause of the kore-vs-booster gap in ecrecoverloop02-sig1-invalid and ecrecoverloop02-sigs-valid benchmarks specs. The new abi-spec.k claim pow-byte-len-simplify exercises this rule directly in booster-only mode and passes after the fix. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> (cherry picked from commit efa0e111609946d7c3506e3a7ae2c6d52cef16e4) (cherry picked from commit fcab3c84c70d9034bee5d6a3b3152c306b6e9d00) * int-simplification.k, int-simplifications-spec.k: add [preserves-definedness] to 46 comparison normalization rules, add coverage claims Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> (cherry picked from commit ccce6b5dc1442f51df8939c71b3e378cf74c5894) (cherry picked from commit 2b7099cf814c7ed27e8bc5af007cd52591e7b869) * lemmas.k, lemmas-no-smt-spec.k: add bool #Equals workaround rules for BOOL-KORE @-variable patterns, add coverage claim The BOOL-KORE module in K's domains.md defines 8 rules using @-prefixed "here variables" (e.g., {true #Equals notBool @b}) which booster cannot match. Add equivalent rules with regular B:Bool variables to LEMMAS-HASKELL so booster can apply the same normalizations. Add a functional spec claim exercising the notBool case, which passes in booster-only mode. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> (cherry picked from commit 80ed6938ac79faf1a98ef55ddc2a0226a3ae39b2) (cherry picked from commit c498024497ff2a65fd6cc58916db745813f5acf4) * lemmas/bytes-simplification.k, tests/functional/lemmas-no-smt-spec.k: add preserves-definedness to range-empty The range-empty rule (#range(_,S,W) => .Bytes when S<0 or W<=0) previously lacked [preserves-definedness], so booster refused to apply it during the implies check. Tag it accordingly and add two coverage claims to lemmas-no-smt-spec.k to verify it fires in booster. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> (cherry picked from commit 666ba06c5956d8b9b540446089f73b6a45fba000) (cherry picked from commit f30c2ff9ac9e7bdf0af6f10b47f716f5e0f1728e) * buf.md, lemmas-no-smt-spec.k: add powByteLen-lt-concrete concrete comparison rule After commit 5d0d8083c added [preserves-definedness] to the 2^Int(8*SIZE) => #powByteLen(SIZE) rule, booster fires it consistently even when SIZE is concrete (the rule fires before path-condition lookup makes the argument concrete). Because #powByteLen has no-evaluators, the result #powByteLen(N) cannot self-evaluate, leaving CONST <Int Add [powByteLen-lt-concrete] to bridge that gap: when both CONST and N are concrete the requires-clause delegates back to 2^Int arithmetic, which can be evaluated directly. Add a coverage claim in lemmas-no-smt-spec.k to verify the rule fires. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> (cherry picked from commit 8b4972c4cc410995c3ec2360ca6dd50c189d374b) (cherry picked from commit 11b7050dfc88d903d1e1c1cb62802b6db25b9187) * evm-semantics/evm-types.md: mark #asAccount as [total] #asAccount is total over its Bytes domain — the two rules (lengthBytes == 0 → .Account; [owise] → #asWord(BS)) are jointly exhaustive. Marking it [total] lets booster's definedness checker clear the RHS-definedness gate on rules that contain #asAccount in their RHS — notably [call.delegatedAuthority] at evm.md:1633, where the symbolic argument #range(CODE, 3, 20) prevents concrete evaluation and previously caused booster to abort with "Uncertain about definedness of rule due to: non-total symbol Lbl#asAccount", yielding to kore for the rewrite step. The recover-mode sweep across functional + examples + erc20 + benchmarks identified 12 kore-execute handoffs (all benchmarks ecrecover variants) that traced to exactly this definedness abort. Validated locally on benchmarks/ecrecover00-siginvalid-spec.k: 2 handoffs → 0, proof still passes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> (cherry picked from commit 33c04ff6558c339ffc06759dcaa49cd139222ef8) (cherry picked from commit 4ce956c09bb03a7d06fdf533f95b2cfaa40b0dc7) * lemmas/int-simplification.k: add minInt<maxInt / minInt<=maxInt base comparison rules Tier-1 subset of upstream 258a68c71: keep the two base rules (sound since minInt(A,_) <= A and C <= maxInt(C,_)); drop the non-linear shift/factored variants pending review. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * lemmas/evm-int-simplification.k: add asWord comparison-false simplifications Tier-1 subset of upstream 584462824: the asWord-{lt,le,eq}-false rules (contrapositives of path-condition facts; RHS is false, so trivially definedness-preserving). Drops that commit's slot-updates-spec.k edits (claims regressed PASS->FAIL and need investigation). Coverage claims for these rules are already in lemmas-no-smt-spec.k. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * lemmas/int-simplification.k: add eq-false-lt simplification Tier-1 subset of upstream d0f4bdec3: keep eq-false-lt (A ==Int B => false when A <Int B, B concrete). Drops le-1073741824-maxUInt64, whose 2^30 bound is a non-general magic constant pending review. Coverage claim is already in lemmas-no-smt-spec.k. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * evm.md, lemmas/lemmas.k: mark #widthOpCode [total, smtlib] and add bound + concat-lookup simplifications Consolidates the Tier-1 #widthOpCode work from upstream Rounds 2-5 (which iteratively rewrote the same lines): #widthOpCode is total (PUSH ops -> 2..33, owise -> 1) and gets an smtlib symbol; widthOpCode-lb/ub encode its 0..33 range as smt-lemmas; widthOpCode-concat-{left,right} push a concrete-prefix byte lookup through +Bytes. Drops the Round-4 asWord-range-buf and the #computeValidJumpDests preserves-definedness riders pending review. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * lemmas: drop redundant [preserves-definedness] from all-total simplification rules For a [simplification] rule the booster's definedness obligation is the set of non-total, non-constructor symbols on the LHS and RHS (Internalise.hs); when that set is empty the rule is determined definedness-preserving automatically and the attribute is a no-op. Removed it from the 66 rules added here whose every symbol is total (`+Int -Int *Int` / comparisons / `minInt maxInt lengthBytes +Bytes notBool` / the `total` functions `#asWord #buf #range #widthOpCode`), i.e. the comparison-normalization block, eq-false-lt, the minInt<maxInt base rules, asWord-{lt,le,eq}-false, asWord-buf-inversion, range-empty, lengthBytes-buf/range, b"" concat identities, and the Bool #Equals distributions. Kept on the 5 rules that root a partial symbol (where the attribute does real work): the two #powByteLen rules (no-evaluators), lookup-as-asWord and widthOpCode-concat-{left,right} (partial BYTES.get `_[_]`). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * evm.md: formatting * buf.md: relax powByteLen-lt-concrete to concrete(CONST) only (review) Per review: N need not be syntactically concrete -- once N is concrete the `requires CONST <Int 2^Int(8*N)` reduces to a linear inequality, and if N is symbolic the requires is undischargeable so the rule simply does not fire. (It does not subsume `SIZE <Int #powByteLen(SIZE)`, which is the diagonal same-variable case.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * lemmas/lemmas.k: drop widthOpCode-concat-{left,right} (review) Per review: bytes-simplification.k already provides bytes-concat-lookup-{left, right}, so these were only a workaround for booster not descending into #widthOpCode(...) to apply them. Remove rather than duplicate per-function; if the non-descent persists it should be addressed in the backend. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * lemmas/lemmas.k, tests/specs/functional/lemmas-no-smt-spec.k: drop unused bool-eq #Equals rules Whole-prove-suite rule-firing measurement (booster Simplify + kore SimplifyKore logging over functional/benchmarks/erc20/examples/kontrol/mcd) shows the eight bool-eq-* ML-#Equals distribution rules are never applied by the booster: five fire in no engine at all, and three (bool-eq-not-true-l/r, bool-eq-and-true-l) fire only in the kore-rpc implies/subsumption path. Since this PR targets booster performance and these proofs already discharge those goals under kore without the rules, all eight are removed. The bool-*-self rules are kept (they fire in the booster). The bool-eq-not-false coverage claim, which was discharged by bool-eq-not-true-l rather than by the not-false rules, is removed with them. Verified: lemmas-no-smt-spec proves all 22 remaining claims under --use-booster. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * tests/specs/mcd/verification.k: mark keccak disequality [smt-lemma] to fix DSS/MCD implies failures The new tier1 simplification lemmas simplify away the keccak path-condition constraint during EVM execution, so the final subsumption check can no longer prove `keccak(_) =/=Int smallInt` from the path condition alone. Encoding the rule as an SMT lemma lets Z3 discharge it independently of the path condition. This matches the treatment already present in mcd-structured/verification.k, whose sibling proofs pass; the plain mcd suite lacked the annotation. Fixes the heal/fold/frob-diff-zero-dart DSS proofs and the mcd flopper-tick/end-pack rule proofs. Verified mcd/end-pack-pass-rough-spec.k passes with the change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Revert "buf.md: relax powByteLen-lt-concrete to concrete(CONST) only (review)" This reverts commit 6cd5f0d. * tests/failing-symbolic.haskell-booster-dev: drop 5 specs now passing under booster-dev Verified by running the booster-dev binary over the full skip-list on this branch: mcd-structured/dsvalue-peek-pass-rough now passes under tier1's lemmas (failed on master), and 4 entries (functional/bitwise-{asword,simplifications}, mcd[-structured]/dsvalue-read-pass-summarize) were stale (already passing on master). bitwise-mask-shift-spec.k is intentionally kept: it regresses under booster-dev on this branch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * tests/failing-symbolic.haskell-booster-dev: re-add 2 dsvalue-read-pass-summarize specs failing booster-dev in CI PR #2859 CI 'Proofs: Rules (booster-dev)' failed on mcd/dsvalue-read-pass-summarize and mcd-structured/dsvalue-read-pass-summarize (SystemExit: 1); local verification diverged from CI. Re-add them; the other three drops (mcd-structured/dsvalue-peek-pass-rough and functional/bitwise-{asword,simplifications}) pass in CI and stay removed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 5388c8d commit b460c09

12 files changed

Lines changed: 157 additions & 19 deletions

File tree

kevm-pyk/src/kevm_pyk/kproj/evm-semantics/buf.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,17 @@ module BUF
3737
syntax Int ::= #powByteLen ( Int ) [symbol(#powByteLen), function, no-evaluators]
3838
// ---------------------------------------------------------------------------------
3939
// rule #powByteLen(SIZE) => 2 ^Int (8 *Int SIZE)
40-
rule 2 ^Int (8 *Int SIZE) => #powByteLen(SIZE) [symbolic(SIZE), simplification]
40+
rule 2 ^Int (8 *Int SIZE) => #powByteLen(SIZE) [symbolic(SIZE), simplification, preserves-definedness]
4141
4242
rule 0 <Int #powByteLen(SIZE) => true requires 0 <=Int SIZE [simplification, preserves-definedness]
4343
rule SIZE <Int #powByteLen(SIZE) => true requires 0 <=Int SIZE [simplification, preserves-definedness]
44+
// Concrete comparison: CONST < #powByteLen(N) evaluates when both arguments are concrete.
45+
// Needed because 2^Int(8*N) is rewritten to #powByteLen(N) even for concrete N (via the
46+
// symbolic(N) rule firing before path-condition lookup makes N concrete), but #powByteLen
47+
// has no-evaluators so the result can't be computed directly.
48+
rule [powByteLen-lt-concrete]: CONST <Int #powByteLen(N) => true
49+
requires 0 <=Int N andBool CONST <Int 2 ^Int (8 *Int N)
50+
[simplification, concrete(CONST, N), preserves-definedness]
4451
rule #write(WM, IDX, VAL) => WM [ IDX := #buf(1, VAL) ] [simplification]
4552
4653
rule #bufStrict(SIZE, DATA) => #buf(SIZE, DATA)

kevm-pyk/src/kevm_pyk/kproj/evm-semantics/evm-types.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,8 +358,8 @@ Bytes helper functions
358358
// -------------------------------------------------------------------------
359359
rule #asInteger(WS) => Bytes2Int(WS, BE, Unsigned) [concrete]
360360
361-
syntax Account ::= #asAccount ( Bytes ) [symbol(#asAccount), function]
362-
// ----------------------------------------------------------------------------------
361+
syntax Account ::= #asAccount ( Bytes ) [symbol(#asAccount), function, total]
362+
// -----------------------------------------------------------------------------
363363
rule #asAccount(BS) => .Account requires lengthBytes(BS) ==Int 0
364364
rule #asAccount(BS) => #asWord(BS) [owise]
365365

kevm-pyk/src/kevm_pyk/kproj/evm-semantics/evm.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1812,8 +1812,8 @@ System Transaction Configuration
18121812
```
18131813

18141814
```k
1815-
syntax Int ::= #widthOpCode(Int) [symbol(#widthOpCode), function]
1816-
// -----------------------------------------------------------------
1815+
syntax Int ::= #widthOpCode(Int) [symbol(#widthOpCode), function, total, smtlib(widthOpCode)]
1816+
// ---------------------------------------------------------------------------------------------
18171817
rule #widthOpCode(W) => W -Int 94 requires W >=Int 96 andBool W <=Int 127
18181818
rule #widthOpCode(_) => 1 [owise]
18191819

kevm-pyk/src/kevm_pyk/kproj/evm-semantics/lemmas/bytes-simplification.k

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ module BYTES-SIMPLIFICATION [symbolic]
2929

3030
rule [bytes-concat-empty-right]: B:Bytes +Bytes .Bytes => B [simplification]
3131
rule [bytes-concat-empty-left]: .Bytes +Bytes B:Bytes => B [simplification]
32+
// b"" is a Kore domain value (\dv{Bytes}("")) distinct from the .Bytes constructor;
33+
// LLVM hooks (e.g. #encodeArgs base case) return b"", not .Bytes, so we need both forms.
34+
rule [bytes-concat-empty-right-concrete]: B:Bytes +Bytes b"" => B [simplification]
35+
rule [bytes-concat-empty-left-concrete]: b"" +Bytes B:Bytes => B [simplification]
3236

3337
rule [bytes-concat-right-assoc-symb-l]: (B1:Bytes +Bytes B2:Bytes) +Bytes B3:Bytes => B1 +Bytes (B2 +Bytes B3) [symbolic(B1), simplification(40)]
3438
rule [bytes-concat-right-assoc-symb-r]: (B1:Bytes +Bytes B2:Bytes) +Bytes B3:Bytes => B1 +Bytes (B2 +Bytes B3) [symbolic(B2), simplification(40)]
@@ -306,5 +310,5 @@ module BYTES-SIMPLIFICATION [symbolic]
306310
rule [lookup-as-asWord]:
307311
B:Bytes [ I:Int ] => #asWord ( #range ( B, I, 1 ) )
308312
requires 0 <=Int I andBool I <Int lengthBytes(B)
309-
[simplification(60)]
313+
[simplification(60), preserves-definedness]
310314
endmodule

kevm-pyk/src/kevm_pyk/kproj/evm-semantics/lemmas/evm-int-simplification.k

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,14 @@ module EVM-INT-SIMPLIFICATION
101101
requires 0 <=Int WB andBool 0 <=Int X andBool X <Int minInt ( 2 ^Int (8 *Int WB), pow256 )
102102
[simplification, concrete(WB), preserves-definedness]
103103

104+
// Specialisation for 32-byte buf with rangeUInt(256) side-condition: avoids
105+
// the minInt(2^256, pow256) evaluation that Booster cannot match against the
106+
// path condition's 2^Int 256 tree form.
107+
rule [asWord-buf-inversion-rangeUInt256]:
108+
#asWord ( #buf ( 32, X:Int ) ) => X
109+
requires #rangeUInt ( 256, X )
110+
[simplification]
111+
104112
// Reduction: bitwise right shift in terms of `#range`
105113
rule [asWord-shr]:
106114
#asWord( BA ) >>Int N => #asWord ( #range ( BA, 0, lengthBytes( BA ) -Int ( N /Int 8 ) ) )
@@ -123,6 +131,18 @@ module EVM-INT-SIMPLIFICATION
123131
// Comparison: `#asWord(B)` is certainly less than or equal to something that is not less than the max number fitting into `lengthBytes(B)` bytes
124132
rule [asWord-le]: #asWord ( B ) <=Int X:Int => true requires ( 2 ^Int (8 *Int minInt(32, lengthBytes(B))) -Int 1 ) <=Int X [simplification, preserves-definedness]
125133
134+
// Comparison: `#asWord(B) < X` is false when the path condition says X <= #asWord(B).
135+
// Loop-safe with asWord-le-false: requires clauses have #asWord on the RIGHT, so
136+
// neither rule fires on the other's requires.
137+
rule [asWord-lt-false]: #asWord ( B:Bytes ) <Int X:Int => false requires X <=Int #asWord ( B ) [simplification]
138+
rule [asWord-le-false]: #asWord ( B:Bytes ) <=Int X:Int => false requires X <Int #asWord ( B ) [simplification]
139+
// Comparison: `#asWord(B) == X` is false when the path condition says X =/= #asWord(B).
140+
// The requires X =/=Int #asWord(B) expands to notBool(X ==Int #asWord(B)) via the K builtin;
141+
// since ==Int is comm, this matches the path condition fact notBool(#asWord(B) ==Int X).
142+
// No loop: our rule matches #asWord on the LEFT; after builtin expansion the recursive
143+
// term X ==Int #asWord(B) has X on the LEFT, so this rule does not fire on it.
144+
rule [asWord-eq-false]: #asWord ( B:Bytes ) ==Int X:Int => false requires X =/=Int #asWord ( B ) [simplification]
145+
126146
// Comparison: `#asWord` of `+Bytes` when lower bytes match, with `<Int`
127147
rule [asWord-concat-lt]:
128148
#asWord ( BA1 +Bytes BA2 ) <Int X:Int => #asWord ( BA1 ) <Int X /Int ( 2 ^Int ( 8 *Int lengthBytes ( BA2 ) ) )

kevm-pyk/src/kevm_pyk/kproj/evm-semantics/lemmas/int-simplification.k

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ module INT-SIMPLIFICATION-HASKELL [symbolic]
8686
rule A +Int B >Int C +Int D => A -Int C >Int D -Int B [concrete(B, D), symbolic(A, C), simplification(45)]
8787
rule A +Int B >=Int C +Int D => A -Int C >=Int D -Int B [concrete(B, D), symbolic(A, C), simplification(45)]
8888

89+
rule [int-eq-comm-concrete]:
90+
N:Int ==Int X:Int => X ==Int N
91+
[simplification(30), concrete(N), symbolic(X)]
92+
8993
rule A +Int B ==Int A => B ==Int 0 [simplification(40)]
9094
rule A +Int B ==Int B => A ==Int 0 [simplification(40)]
9195
rule A +Int B ==Int C +Int B => A ==Int C [simplification(40)]
@@ -101,6 +105,11 @@ module INT-SIMPLIFICATION-HASKELL [symbolic]
101105
rule A -Int B <Int 0 => A <Int B [simplification, symbolic(A, B)]
102106
rule A -Int B <=Int 0 => A <=Int B [simplification, symbolic(A, B)]
103107

108+
// Mixed range contradictions: A < B /\ C <= A where B <= C (all concrete B, C)
109+
// Requires is purely concrete (both B and C are concrete), so no circular self-application.
110+
rule A:Int <Int B:Int andBool C:Int <=Int A:Int => false requires B <=Int C [simplification, concrete(B, C)]
111+
rule C:Int <=Int A:Int andBool A:Int <Int B:Int => false requires B <=Int C [simplification, concrete(B, C)]
112+
104113
endmodule
105114

106115
module INT-SIMPLIFICATION-COMMON
@@ -133,6 +142,7 @@ module INT-SIMPLIFICATION-COMMON
133142
rule (A +Int B) +Int (C -Int B) => A +Int C [simplification]
134143
rule (A -Int B) -Int (C -Int B) => A -Int C [simplification]
135144
rule ((A -Int B) -Int C) +Int B => A -Int C [simplification]
145+
rule A +Int ((B -Int A) +Int C) => B +Int C [simplification]
136146

137147
// 5 terms
138148
// NOTE: required for `tests/specs/functional/infinite-gas-spec.k.prove` (haskell)
@@ -232,6 +242,18 @@ module INT-SIMPLIFICATION-COMMON
232242
rule [maxInt-factor-left]: maxInt ( A:Int +Int B:Int, A:Int +Int C:Int ) => A +Int maxInt ( B, C ) [simplification]
233243
rule [maxInt-factor-right]: maxInt ( A:Int +Int B:Int, C:Int +Int B:Int ) => maxInt ( A, C ) +Int B [simplification]
234244

245+
// minInt <Int maxInt / minInt <=Int maxInt: priority 40 fires before the default-priority (50) expansion rules,
246+
// so Booster sees `true` directly rather than a disjunction requiring path-condition reasoning.
247+
// Sound: minInt(A,_) <=Int A and C <=Int maxInt(C,_), so A <Int C implies minInt(A,_) <Int maxInt(C,_).
248+
rule [minint-lt-maxint-a]:
249+
minInt(A:Int, _B:Int) <Int maxInt(C:Int, _D:Int) => true
250+
requires A <Int C
251+
[simplification(40)]
252+
rule [minint-leq-maxint-b]:
253+
minInt(_A:Int, B:Int) <=Int maxInt(_C:Int, D:Int) => true
254+
requires B <=Int D
255+
[simplification(40)]
256+
235257
// ###########################################################################
236258
// inequality
237259
// ###########################################################################
@@ -241,6 +263,14 @@ module INT-SIMPLIFICATION-COMMON
241263

242264
rule A <Int A -Int B => false requires 0 <=Int B [simplification]
243265

266+
rule A:Int <=Int A:Int => true [simplification]
267+
268+
// Higher-priority: short-circuit to true directly when path condition already has 0 <=Int B.
269+
rule A:Int <=Int A:Int +Int B:Int => true requires 0 <=Int B [simplification(40)]
270+
rule A:Int <=Int A:Int +Int B:Int => 0 <=Int B [simplification]
271+
272+
rule A:Int <Int B:Int andBool B:Int <Int A:Int => false [simplification]
273+
244274
rule 0 <Int 1 <<Int A => true requires 0 <=Int A [simplification, preserves-definedness]
245275

246276
// inequality sign normalization
@@ -256,6 +286,9 @@ module INT-SIMPLIFICATION-COMMON
256286

257287
rule A ==Int B => false requires 0 <=Int A andBool B <Int 0 [simplification, concrete(B)]
258288

289+
// A ==Int B is false when A < B is a known path-condition fact (B concrete).
290+
rule [eq-false-lt]: A:Int ==Int B:Int => false requires A <Int B [simplification, concrete(B)]
291+
259292
rule 0 <Int X => true requires 0 <=Int X andBool notBool (X ==Int 0) [simplification(60)]
260293

261294
rule X <=Int maxUInt256 => X <Int pow256 [simplification]

kevm-pyk/src/kevm_pyk/kproj/evm-semantics/lemmas/lemmas.k

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,17 @@ module LEMMAS-HASKELL [symbolic]
245245
requires notBool ( I1 ==Int I2 )
246246
[simplification]
247247

248+
// ########################
249+
// #widthOpCode range
250+
// ########################
251+
252+
// #widthOpCode always returns 1..33 (PUSH ops 2..33, everything else 1).
253+
// smtlib(widthOpCode) on the declaration lets smt-lemma encode these as
254+
// universally-quantified bounds so the solver can discharge side conditions
255+
// containing #widthOpCode without needing to evaluate it concretely.
256+
rule [widthOpCode-lb]: 0 <=Int #widthOpCode(_:Int) => true [simplification, smt-lemma]
257+
rule [widthOpCode-ub]: #widthOpCode(_:Int) <=Int 33 => true [simplification, smt-lemma]
258+
248259
// ########################
249260
// Boolean Logic
250261
// ########################
@@ -259,4 +270,9 @@ module LEMMAS-HASKELL [symbolic]
259270
rule [notBool-or]: notBool ( A orBool B ) => ( notBool A ) andBool ( notBool B ) [simplification(60)]
260271
rule [notBool-and]: notBool ( A andBool B ) => ( notBool A ) orBool ( notBool B ) [simplification(60)]
261272

273+
rule [bool-or-not-self]: B:Bool orBool notBool B:Bool => true [simplification]
274+
rule [bool-not-or-self]: notBool B:Bool orBool B:Bool => true [simplification]
275+
rule [bool-and-not-self]: B:Bool andBool notBool B:Bool => false [simplification]
276+
rule [bool-not-and-self]: notBool B:Bool andBool B:Bool => false [simplification]
277+
262278
endmodule

tests/failing-symbolic.haskell-booster-dev

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,7 @@ tests/specs/examples/solidity-code-spec.md
5959
tests/specs/examples/storage-spec.md
6060
tests/specs/examples/sum-to-n-foundry-spec.k
6161
tests/specs/examples/sum-to-n-spec.k
62-
tests/specs/functional/bitwise-asword-spec.k
6362
tests/specs/functional/bitwise-mask-shift-spec.k
64-
tests/specs/functional/bitwise-simplifications-spec.k
6563
tests/specs/functional/bytes-range-spec.k
6664
tests/specs/functional/evm-int-simplifications-spec.k
6765
tests/specs/functional/infinite-gas-spec.k
@@ -116,7 +114,6 @@ tests/specs/mcd-structured/cat-exhaustiveness-spec.k
116114
tests/specs/mcd-structured/cat-file-addr-pass-rough-spec.k
117115
tests/specs/mcd-structured/dai-adduu-fail-rough-spec.k
118116
tests/specs/mcd-structured/dstoken-approve-fail-rough-spec.k
119-
tests/specs/mcd-structured/dsvalue-peek-pass-rough-spec.k
120117
tests/specs/mcd-structured/dsvalue-read-pass-summarize-spec.k
121118
tests/specs/mcd-structured/end-cash-pass-rough-spec.k
122119
tests/specs/mcd-structured/end-pack-pass-rough-spec.k

tests/specs/functional/abi-spec.k

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ requires "abi.md"
33
module VERIFICATION
44
imports EVM-ABI
55

6-
syntax Step ::= Bool | Bytes
7-
// ------------------------------
6+
syntax Step ::= Bool | Bytes | Int
7+
// -----------------------------------
88

99
syntax KItem ::= runLemma ( Step ) [symbol(runLemma)]
1010
| doneLemma( Step )
@@ -43,22 +43,26 @@ module ABI-SPEC
4343
<k> runLemma(
4444
#sizeOfDynamicType(
4545
#array(
46-
#bytes(b"\x00"),
47-
3,
46+
#bytes(b"\x00"),
47+
3,
4848
#bytes(b"\xaa"), #bytes(b"\xbb\xbb"), #bytes(b"\xcc\xcc\xcc")
4949
)
5050
)
5151
==Int
5252
lengthBytes(#enc(
5353
#array(
54-
#bytes(b"\x00"),
55-
3,
54+
#bytes(b"\x00"),
55+
3,
5656
#bytes(b"\xaa"), #bytes(b"\xbb\xbb"), #bytes(b"\xcc\xcc\xcc")
5757
)
5858
))
59-
)
60-
=> doneLemma(true)
61-
...
59+
)
60+
=> doneLemma(true)
61+
...
6262
</k>
6363

64+
claim [pow-byte-len-simplify]:
65+
<k> runLemma ( 2 ^Int (8 *Int N:Int) ) => doneLemma ( #powByteLen(N:Int) ) ... </k>
66+
requires 0 <=Int N
67+
6468
endmodule

tests/specs/functional/int-simplifications-spec.k

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,19 @@ module INT-SIMPLIFICATIONS-SPEC
127127

128128
claim [le-maxuint256]: <k> runLemma ( X <=Int maxUInt256 ) => doneLemma ( X <Int pow256 ) ... </k>
129129

130+
// Comparison normalization coverage (int-simplification.k [preserves-definedness])
131+
// ---------------------------------------------------------------------------------
132+
133+
claim [int-comp-norm-add-lt]:
134+
<k> runLemma ( X:Int +Int 3 <Int 10 ) => doneLemma ( X:Int <Int 7 ) ... </k>
135+
136+
claim [int-comp-norm-add-le]:
137+
<k> runLemma ( X:Int +Int 3 <=Int 10 ) => doneLemma ( X:Int <=Int 7 ) ... </k>
138+
139+
claim [int-comp-norm-sub-lt]:
140+
<k> runLemma ( X:Int -Int 3 <Int 10 ) => doneLemma ( X:Int <Int 13 ) ... </k>
141+
142+
claim [int-comp-norm-rhs-add-lt]:
143+
<k> runLemma ( 7 <Int X:Int +Int 3 ) => doneLemma ( 4 <Int X:Int ) ... </k>
144+
130145
endmodule

0 commit comments

Comments
 (0)