Skip to content

Commit 38893bd

Browse files
ehildenbclaude
andauthored
Remove looping asWord-eq-false lemma and expose --equation-max-local-steps (#2863)
* kevm-pyk/{cli,__main__}, tests/{conftest,test_prove}: thread --equation-max-local-steps to booster servers Exposes the haskell-backend's new recursive-simplification budget (equation evaluation applied in place at rewritten subterms, hb #4153; backend default 0 = restart-only) through kevm prove and the pytest harness: - cli.py: ProveOptions.equation_max_local_steps + kevm prove flag. - __main__.py: appends the flag to the resolved kore-rpc-booster / booster-dev command; rejects non-booster servers explicitly. - conftest.py / test_prove.py: pytest --equation-max-local-steps option threaded through all prove suites. (cherry picked from commit e6597556e913f1a6340fcb7f959a84422976a772) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * kevm-pyk/{cli,__main__}, tests/{conftest,test_prove}: default --equation-max-local-steps to 20 Turn the booster recursive-simplification budget on by default (20) for kevm prove and all pytest prove suites, instead of deferring to the backend's restart-only default of 0: - cli.py / conftest.py: default 20; option type narrows to int. - __main__.py: with the budget now always set, a non-booster server skips the flag with a warning instead of raising. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * tests/specs/functional/asword-eq-false-loop-spec.k, scripts/asword_loop_harness.py: asWord-eq-false loop regression test Adds a functional regression test that guards against reintroducing the non-terminating `asWord-eq-false` simplification (evm-semantics #2859 / kontrol #1153): three claims that evaluate `#asWord ==Int #asWord` known-unequal from the path condition. With the looping rule present each claim's simplification does not terminate and the proof times out; with the rule gone they discharge. Collected by the functional suite (`functional/*-spec.k`) against the shared VERIFICATION definition. `scripts/asword_loop_harness.py` runs such loop specs standalone under a per-claim timeout (LOOP / PASS / FAILED) for local triage, since a non-terminating RPC request produces no per-request log bundle. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * lemmas/evm-int-simplification.k: remove non-terminating asWord-eq-false simplification `asWord-eq-false` (added in #2859) loops in the Kore simplifier when both operands of the `==Int` are `#asWord` terms: applying it rewrites the equality into its own `requires` with the operands swapped (still `#asWord`-on-the-left), so it re-matches and recurses without bound. This was the root cause of the CSE-proof timeouts (kontrol #1153). Removing the rule ends the loop; the `<Int`/`<=Int` siblings are antisymmetric and stay. Regression test: tests/specs/functional/asword-eq-false-loop-spec.k. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * scripts/asword_loop_harness.py: drop loop-triage harness from the PR The asWord-eq-false regression is guarded by the functional spec alone (tests/specs/functional/asword-eq-false-loop-spec.k); the standalone triage harness is not needed in this branch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * tests/specs/functional/loops/eq-false-lt-loop-spec.k: exemplar for the relocated eq-false-lt CSE loop Captures the second CSE non-termination layer (evm-semantics #2859 / kontrol #1153) that remains after asWord-eq-false is removed: eq-false-lt × AccountCellMap definedness, a Kore constraint-simplifier fixpoint failure on undetermined account-id comparisons. Kept under loops/ (not collected by the functional suite) because it is UNVERIFIED and, per the change request, does not reproduce in a fresh single proof — the spin only arises on a long-lived server once constraints accumulate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * lemmas/int-simplification.k: temporarily disable eq-false-lt (drives CSE Kore non-termination) eq-false-lt (`A ==Int B => false requires A <Int B [simplification, concrete(B)]`, added in #2859) is the rule that drives the CSE Kore-simplifier non-termination that removing asWord-eq-false only relocated to (kontrol #1153). During the Kore definedness check of `#Ceil(<accounts> AccountCellMap)`, the account-distinctness predicates `ACCT_ID ==Int <concrete address> => false` invite this rule, whose side condition `A <Int B` is undetermined for a range-bounded symbolic account id vs a large concrete address, spawning a ~500ms SMT round-trip per attempt that the constraint simplifier repeats without reaching a fixpoint. Confirmed via per-request logs to be the sole rule driving those SMT calls (removing it took the rule-driven SMT count from 45 to 0 on the exemplar). Disabled for now; re-introduce once Booster performs definedness without falling back to the Kore simplifier. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * tests/specs/functional/eq-false-lt-loop-spec.k: promote eq-false-lt exemplar for CI collection Move the eq-false-lt loop exemplar out of the non-collected loops/ subdir into tests/specs/functional/ so the functional test harness picks it up via spec_files('functional', '*-spec.k'). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * lemmas/buf.md, lemmas-no-smt-spec.k: add powByteLen-lt-concrete-false to close asWord-unif-03/04 without asWord-eq-false Removing the looping asWord-eq-false lemma un-masked a coverage gap: the lemmas-spec asWord-unif-03/04 claims (#2604) decompose an out-of-range #asWord(B) ==Int CONST via asWord-eq-num into CONST <Int #powByteLen(N), which must reduce to false. #powByteLen is [no-evaluators] and #2859 added only the true-direction powByteLen-lt-concrete rule, leaving the false direction to asWord-eq-false. Add the mutually-exclusive, loop-safe false-direction companion so the concrete comparison is total, plus a focused regression claim alongside the existing true-direction test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent fb58d3b commit 38893bd

10 files changed

Lines changed: 184 additions & 7 deletions

File tree

kevm-pyk/src/kevm_pyk/__main__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,13 @@ def exec_prove(options: ProveOptions) -> None:
254254
else:
255255
kore_rpc_command = options.kore_rpc_command
256256

257+
# Both booster servers parse the flag (shared Booster.CLOptions); plain kore-rpc does not,
258+
# so the budget is skipped (with a warning) rather than rejected now that it defaults on.
259+
if Path(kore_rpc_command[0]).name in ('kore-rpc-booster', 'booster-dev'):
260+
kore_rpc_command += ('--equation-max-local-steps', str(options.equation_max_local_steps))
261+
else:
262+
_LOGGER.warning(f'Ignoring --equation-max-local-steps for non-booster server: {kore_rpc_command[0]}')
263+
257264
def is_functional(claim: KClaim) -> bool:
258265
claim_lhs = claim.body
259266
if type(claim_lhs) is KRewrite:

kevm-pyk/src/kevm_pyk/cli.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,17 @@ def _create_argument_parser() -> ArgumentParser:
192192
action='store_true',
193193
help='Skip the Kore simplification pass after Booster; assume_defined still uses Kore for #Ceil evaluation.',
194194
)
195+
prove_args.add_argument(
196+
'--equation-max-local-steps',
197+
dest='equation_max_local_steps',
198+
type=int,
199+
default=None,
200+
help=(
201+
'Booster equation budget for in-place evaluation at rewritten subterms before restarting '
202+
'traversal from the top (default: 20; 0 = restart-only). Passed through to '
203+
'kore-rpc-booster / booster-dev; requires a haskell-backend that supports the flag.'
204+
),
205+
)
195206
prove_args.add_argument(
196207
'--max-frontier-parallel',
197208
type=int,
@@ -611,13 +622,15 @@ class ProveOptions(
611622
):
612623
reinit: bool
613624
booster_only_simplify: bool
625+
equation_max_local_steps: int
614626
max_frontier_parallel: int
615627

616628
@staticmethod
617629
def default() -> dict[str, Any]:
618630
return {
619631
'reinit': False,
620632
'booster_only_simplify': False,
633+
'equation_max_local_steps': 20,
621634
'max_frontier_parallel': 1,
622635
}
623636

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ module BUF
4848
rule [powByteLen-lt-concrete]: CONST <Int #powByteLen(N) => true
4949
requires 0 <=Int N andBool CONST <Int 2 ^Int (8 *Int N)
5050
[simplification, concrete(CONST, N), preserves-definedness]
51+
// Companion false-direction: a concrete CONST that does not fit into N bytes is not
52+
// less than #powByteLen(N). Together with the rule above this makes the concrete
53+
// comparison total. The two requires are mutually exclusive and neither RHS re-exposes
54+
// `_ <Int #powByteLen(_)`, so the pair is loop-safe.
55+
rule [powByteLen-lt-concrete-false]: CONST <Int #powByteLen(N) => false
56+
requires 0 <=Int N andBool 2 ^Int (8 *Int N) <=Int CONST
57+
[simplification, concrete(CONST, N), preserves-definedness]
5158
rule #write(WM, IDX, VAL) => WM [ IDX := #buf(1, VAL) ] [simplification]
5259
5360
rule #bufStrict(SIZE, DATA) => #buf(SIZE, DATA)

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,12 +136,12 @@ module EVM-INT-SIMPLIFICATION
136136
// neither rule fires on the other's requires.
137137
rule [asWord-lt-false]: #asWord ( B:Bytes ) <Int X:Int => false requires X <=Int #asWord ( B ) [simplification]
138138
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]
139+
// Note: there is intentionally no `asWord-eq-false` analogue of the `<Int`/`<=Int` rules above.
140+
// Such a rule (`#asWord(B) ==Int X => false requires X =/=Int #asWord(B)`) is not loop-safe: when
141+
// both operands are `#asWord`, applying it rewrites the equality into its own `requires` with the
142+
// two operands swapped — still `#asWord`-on-the-left — so it re-matches and recurses without bound
143+
// in the Kore simplifier. `<Int`/`<=Int` are antisymmetric, so their swapped requires is a
144+
// different relation and does not re-match. See `docs/asword-eq-false-loop.md`.
145145

146146
// Comparison: `#asWord` of `+Bytes` when lower bytes match, with `<Int`
147147
rule [asWord-concat-lt]:

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,15 @@ module INT-SIMPLIFICATION-COMMON
287287
rule A ==Int B => false requires 0 <=Int A andBool B <Int 0 [simplification, concrete(B)]
288288

289289
// 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)]
290+
// TEMPORARILY DISABLED (evm-semantics #2859 / kontrol #1153): this rule drives the CSE Kore-simplifier
291+
// non-termination. During the Kore definedness check of `#Ceil(<accounts> AccountCellMap)`, the account
292+
// distinctness predicates `ACCT_ID ==Int <concrete address> => false` invite this rule, whose side
293+
// condition `A <Int B` is undetermined for a range-bounded symbolic account id and a large concrete
294+
// address — so it cannot fire but spawns a ~500ms SMT round-trip per attempt, repeated as the constraint
295+
// simplifier re-presents the predicate without reaching a fixpoint. Confirmed the sole rule driving those
296+
// undetermined SMT calls (removing it took the rule-driven SMT count from 45 to 0 on the exemplar).
297+
// Re-introduce once Booster performs definedness without falling back to the Kore simplifier.
298+
// rule [eq-false-lt]: A:Int ==Int B:Int => false requires A <Int B [simplification, concrete(B)]
291299

292300
rule 0 <Int X => true requires 0 <=Int X andBool notBool (X ==Int 0) [simplification(60)]
293301

kevm-pyk/src/tests/conftest.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,16 @@ def pytest_addoption(parser: Parser) -> None:
9191
default=False,
9292
help='Skip the Kore simplification pass after Booster for all simplify/execute/implies calls.',
9393
)
94+
parser.addoption(
95+
'--equation-max-local-steps',
96+
type=int,
97+
default=20,
98+
help=(
99+
'Booster equation budget for in-place evaluation at rewritten subterms '
100+
'(default: 20; 0 = restart-only). Threaded to kore-rpc-booster / booster-dev '
101+
'for all proof tests.'
102+
),
103+
)
94104

95105

96106
@pytest.fixture
@@ -161,3 +171,8 @@ def booster_log_levels(request: FixtureRequest) -> list[str] | None:
161171
@pytest.fixture(scope='session')
162172
def booster_only_simplify(request: FixtureRequest) -> bool:
163173
return request.config.getoption('--booster-only-simplify')
174+
175+
176+
@pytest.fixture(scope='session')
177+
def equation_max_local_steps(request: FixtureRequest) -> int:
178+
return request.config.getoption('--equation-max-local-steps')

kevm-pyk/src/tests/integration/test_prove.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ def _test_prove(
198198
workers: int | None = None,
199199
direct_subproof_rules: bool = False,
200200
booster_only_simplify: bool = False,
201+
equation_max_local_steps: int = 20,
201202
) -> None:
202203
caplog.set_level(logging.INFO)
203204

@@ -254,6 +255,7 @@ def _test_prove(
254255
'direct_subproof_rules': direct_subproof_rules,
255256
'booster_only_simplify': booster_only_simplify,
256257
'claim_labels': claim_labels,
258+
'equation_max_local_steps': equation_max_local_steps,
257259
}
258260
if haskell_logging:
259261
options_dict['haskell_log_entries'] = booster_log_levels or list(HASKELL_LOGGING_ENTRIES)
@@ -362,6 +364,7 @@ def test_prove_rules(
362364
booster_log_levels: list[str] | None,
363365
claim_labels: list[str] | None,
364366
booster_only_simplify: bool,
367+
equation_max_local_steps: int,
365368
) -> None:
366369
_test_prove(
367370
spec_file,
@@ -378,6 +381,7 @@ def test_prove_rules(
378381
booster_log_levels=booster_log_levels,
379382
claim_labels=claim_labels,
380383
booster_only_simplify=booster_only_simplify,
384+
equation_max_local_steps=equation_max_local_steps,
381385
)
382386

383387

@@ -400,6 +404,7 @@ def test_prove_functional(
400404
booster_log_levels: list[str] | None,
401405
claim_labels: list[str] | None,
402406
booster_only_simplify: bool,
407+
equation_max_local_steps: int,
403408
) -> None:
404409
_test_prove(
405410
spec_file,
@@ -417,6 +422,7 @@ def test_prove_functional(
417422
claim_labels=claim_labels,
418423
workers=8,
419424
booster_only_simplify=booster_only_simplify,
425+
equation_max_local_steps=equation_max_local_steps,
420426
)
421427

422428

@@ -430,6 +436,7 @@ def test_prove_dss(
430436
booster_log_levels: list[str] | None,
431437
claim_labels: list[str] | None,
432438
booster_only_simplify: bool,
439+
equation_max_local_steps: int,
433440
) -> None:
434441
for spec_file in [REPO_ROOT / 'tests/specs/mcd/vat-spec.k', REPO_ROOT / 'tests/specs/mcd-structured/vat-spec.k']:
435442
_test_prove(
@@ -449,6 +456,7 @@ def test_prove_dss(
449456
workers=8,
450457
direct_subproof_rules=True,
451458
booster_only_simplify=booster_only_simplify,
459+
equation_max_local_steps=equation_max_local_steps,
452460
)
453461

454462

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
requires "verification.k"
2+
3+
module ASWORD-EQ-FALSE-LOOP-SPEC
4+
imports VERIFICATION
5+
6+
// Regression test for the non-terminating `asWord-eq-false` simplification loop
7+
// (evm-semantics #2859 / kontrol #1153). The offending rule has been removed from
8+
// `lemmas/evm-int-simplification.k`; these claims guard against it being reintroduced.
9+
//
10+
// The removed rule was:
11+
// rule [asWord-eq-false]: #asWord(B) ==Int X => false requires X =/=Int #asWord(B) [simplification]
12+
// It is loop-safe ONLY when `X` is not itself an `#asWord(_)`. When BOTH sides of the
13+
// `==Int` are `#asWord` terms, applying it rewrites the equality into its own `requires`
14+
// with the two `#asWord` operands swapped — still `#asWord`-on-the-left — so it matches
15+
// again, swaps back, and recurses without bound (observed: one request looping >21 min at
16+
// 96% CPU, the rule nested 5,406 deep in the Kore simplifier).
17+
//
18+
// Each claim below asks the simplifier to evaluate an equality with `#asWord` on BOTH
19+
// sides, known unequal from the path condition. Expected result: `false`, discharged
20+
// against the path condition. If the looping rule is reintroduced, simplification of the
21+
// left-hand `runLemma` term loops and the proof TIMES OUT. `asWord-lt-false` /
22+
// `asWord-le-false` need no analogue: `<Int` / `<=Int` are antisymmetric, so their
23+
// swapped requires is a different, non-matching relation.
24+
25+
// Minimal trigger: two symbolic byte-words, known unequal.
26+
claim [asword-eq-false-symmetric]:
27+
<k> runLemma ( #asWord ( B1:Bytes ) ==Int #asWord ( B2:Bytes ) ) => doneLemma ( false ) ... </k>
28+
requires #asWord ( B1 ) =/=Int #asWord ( B2 )
29+
30+
// The shape actually observed in the CSE proofs: a plain word vs an `#asWord` over a
31+
// buffer expression (a `+Bytes` of symbolic operands does not reduce, so the right side
32+
// stays `#asWord`, keeping both operands `#asWord` exactly as in the captured loop term).
33+
claim [asword-eq-false-buffer]:
34+
<k> runLemma ( #asWord ( B1:Bytes ) ==Int #asWord ( B2:Bytes +Bytes B3:Bytes ) ) => doneLemma ( false ) ... </k>
35+
requires #asWord ( B1 ) =/=Int #asWord ( B2 +Bytes B3 )
36+
37+
// Same hazard reached via `=/=Int` (which desugars to `notBool(_ ==Int_)`), the form in
38+
// which the loop term appeared inside another rule's requires.
39+
claim [asword-neq-true-symmetric]:
40+
<k> runLemma ( #asWord ( B1:Bytes ) =/=Int #asWord ( B2:Bytes ) ) => doneLemma ( true ) ... </k>
41+
requires #asWord ( B1 ) =/=Int #asWord ( B2 )
42+
43+
endmodule
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
requires "verification.k"
2+
3+
module EQ-FALSE-LT-LOOP-SPEC
4+
imports VERIFICATION
5+
6+
// Regression exemplar for the SECOND CSE non-termination layer (evm-semantics #2859 / kontrol #1153),
7+
// the one that remains after `asWord-eq-false` is removed. See `evm-semantics-cse-loop-change-request.md`.
8+
//
9+
// HOW THIS WAS FOUND: rebuilding KEVM with `asWord-eq-false` deleted (branch `asword-eq-false-loop-fix`,
10+
// == kontrol-side "variant A") does NOT stop the CSE proofs hanging — proof #4
11+
// `ArithmeticCallTest.test_double_add_sub_external` still spins >25 min in one `execute` request. The
12+
// loop simply RELOCATES. Server-side context logging of the hanging request shows two rules dominating,
13+
// ~16-17k attempts each, in two different engines under the same execute request:
14+
//
15+
// * booster > execute > simplify > … > [eq-false-lt] > smt (15 865 attempts)
16+
// rule [eq-false-lt]: A:Int ==Int B:Int => false requires A <Int B [simplification, concrete(B)]
17+
// * kore > execute > constraint > term:90151e4 > <AccountCellMap #Ceil> > success (17 082 attempts,
18+
// re-firing on the SAME constraint term)
19+
//
20+
// MECHANISM (a two-engine fixpoint failure, NOT a single self-loop like asWord-eq-false):
21+
// 1. `#Ceil(<accounts> AccountCellMap)` definedness requires the account ids be DISTINCT, i.e. it emits
22+
// predicates of the form `ACCOUNT_ID ==Int <concrete account address> => false`.
23+
// 2. `eq-false-lt` (concrete RHS) is invited to discharge each one via its side condition
24+
// `ACCOUNT_ID <Int <concrete address>`. With ACCOUNT_ID a symbolic account id constrained only to a
25+
// range (e.g. `[0, pow160)`) that does NOT decide the comparison, SMT returns (Sat,Sat) —
26+
// UNDETERMINED (2 518 such verdicts captured). The rule cannot fire and the predicate cannot reduce.
27+
// 3. Because the account-distinctness predicate never simplifies to a stable form, the Kore constraint
28+
// simplifier never reaches a fixpoint and re-evaluates the AccountCellMap `#Ceil` on the same term
29+
// thousands of times — each pass paying a fresh ~500 ms undetermined SMT round-trip per account id.
30+
//
31+
// The captured concrete address is `645326474426547203313410069153905908525362434349` (a 160-bit value);
32+
// the symbolic ids were CALLER_ID, ORIGIN_ID, and the deployed-contract ids.
33+
//
34+
// SCOPE / CAVEAT — READ BEFORE RELYING ON THIS:
35+
// The faithful trigger is the INTERACTION of AccountCellMap definedness with `eq-false-lt`, driven by
36+
// the Kore constraint simplifier's lack of a fixpoint guard. A bare `runLemma(ACCT ==Int <concrete>)`
37+
// (claim 1 below) reproduces the EXPENSIVE-UNDETERMINED-SMT step but will most likely return the term
38+
// undetermined ONCE rather than spin, because the AccountCellMap `#Ceil` that re-presents the predicate
39+
// is absent. Claim 2 adds a two-account `<accounts>` cell so the definedness machinery is in play; this
40+
// is the closer reproduction, but whether it spins depends on the backend's constraint-simplification
41+
// fixpoint behaviour, which is the real defect. Please run BOTH on the buggy build and report which
42+
// actually loops; the primary recommendation to the team is a backend fixpoint/termination guard plus
43+
// making `eq-false-lt` cheap (or skipped) when `A <Int B` is undetermined — not a lemma deletion.
44+
45+
// Claim 1 — the eq-false-lt undetermined-SMT step in isolation (the per-pass cost of the loop).
46+
// Exactly the captured comparison: symbolic account id vs the concrete address, range-bounded so that
47+
// `ACCT_ID <Int 645…349` is undetermined.
48+
claim [eq-false-lt-undetermined-acctid]:
49+
<k> runLemma ( ACCT_ID:Int ==Int 645326474426547203313410069153905908525362434349 )
50+
=> doneLemma ( ?_RESULT:Bool ) ... </k>
51+
requires 0 <=Int ACCT_ID andBool ACCT_ID <Int pow160
52+
53+
// Claim 2 — closer reproduction: two symbolic-id accounts in the <accounts> cell, so evaluating the
54+
// configuration's definedness invokes the AccountCellMap `#Ceil` that emits the account-distinctness
55+
// predicate `ACCT_ID_1 ==Int ACCT_ID_2 => false`, which `eq-false-lt` then tries (and fails) to discharge.
56+
// Both ids are range-bounded but unordered, so the distinctness comparison is undetermined — the captured
57+
// shape. (Adjust the cell context to match the project's VERIFICATION module if `<accounts>` is wrapped.)
58+
claim [eq-false-lt-acctmap-definedness]:
59+
<k> runLemma ( .Bytes ) => doneLemma ( .Bytes ) ... </k>
60+
<accounts>
61+
<account> <acctID> ACCT_ID_1:Int </acctID> ... </account>
62+
<account> <acctID> ACCT_ID_2:Int </acctID> ... </account>
63+
...
64+
</accounts>
65+
requires 0 <=Int ACCT_ID_1 andBool ACCT_ID_1 <Int pow160
66+
andBool 0 <=Int ACCT_ID_2 andBool ACCT_ID_2 <Int pow160
67+
68+
endmodule

tests/specs/functional/lemmas-no-smt-spec.k

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,12 @@ module LEMMAS-NO-SMT-SPEC
8585
claim [powByteLen-lt-concrete]:
8686
<k> runLemma ( 31 <Int #powByteLen(32) ) => doneLemma ( true ) ... </k>
8787

88+
// powByteLen-lt-concrete-false: CONST <Int #powByteLen(N) => false when CONST does
89+
// not fit into N bytes. The companion false-direction of the rule above; together they
90+
// make the concrete comparison total. Regression for the `asWord-unif-03/04` lemmas-spec
91+
// claims, which decompose an out-of-range `#asWord(B) ==Int CONST` into exactly this
92+
// comparison and previously relied on the (removed, looping) `asWord-eq-false` to close.
93+
claim [powByteLen-lt-concrete-false]:
94+
<k> runLemma ( 2 ^Int 160 <Int #powByteLen(16) ) => doneLemma ( false ) ... </k>
95+
8896
endmodule

0 commit comments

Comments
 (0)