Skip to content

feat(symbolic): add spl-token lemma for inner_test_validate_owner#1010

Draft
Stevengre wants to merge 17 commits intofeature/p-tokenfrom
mk/lemmas-inner_test_validate_owner-spl
Draft

feat(symbolic): add spl-token lemma for inner_test_validate_owner#1010
Stevengre wants to merge 17 commits intofeature/p-tokenfrom
mk/lemmas-inner_test_validate_owner-spl

Conversation

@Stevengre
Copy link
Copy Markdown
Contributor

Summary

SPL-Token version of the inner_test_validate_owner lemma rules (adapted from the p-token version in PR #1001).

Key differences from p-token lemma

  • Different intercept strategy: spl-token's AccountInfo uses raw Aggregate values instead of p-token's PAccountAccount/PAcc wrappers. The intercept rules extract individual fields (key, is_signer, owner) from AccountInfo using field projections (fields 0, 5, 3 respectively).
  • Simplified case rules: match pre-extracted Values directly instead of destructuring PAccount wrappers.
  • Symbolic bool handling: uses _IS_SIGNER wildcard since spl-token's is_signer may be a symbolic BoolVal(VAR:Bool) that doesn't match BoolVal(true) or BoolVal(false).
  • Case 1b: handles keys-match + Err(Custom(4)) path via LHS pattern match (avoids booster sort injection errors from requires ==K).
  • Cases 8-10: multisig signer-checking falls through to small-step execution (sufficient for n==1).

Also included

Proof result

test_validate_owner_multisig (spl-token, n==1):

  • With lemma: ✅ PASSED, 27 nodes, 2886s
  • Without lemma: ✅ PASSED, 123 nodes, 4877s
  • Speedup: 41% faster, 78% fewer nodes

Test plan

  • test_validate_owner_multisig proof passes with lemma enabled (n==1 constraint)
  • K semantics builds successfully (make build)

🤖 Generated with Claude Code

mariaKt and others added 16 commits March 19, 2026 13:29
…ng wrappers. (#989)

This is a bit of everything in one go, but what I was trying to do was
isolate the case seen in [this Kompass
test](https://github.com/runtimeverification/kompass/blob/master/src/tests/integration/data/token/spl-token/show/spl_token_domain_data-fail.main.expected)
that was discussed in #987. The `volatile_load` is coming from
`Box::new` which we are not supporting right now as that does heap
allocation.

This PR:
- Adds failing tests for statics (not decoding properly)
- Adds a failing test for `Box::new`
- Supports sound transmutation between transparent wrappers (in
`#[repr(rust)]` case and their wrapped type. Seen as a `thunk` earlier
in the `Box::new` proof and I thought I would solve it now. There are
tests for this changed from failing passing in the commit history.

So the `volatile_load` is expected to fail until we figure out some
support for the things that these tests fail on. This is not blocking
any work now so I think it is fine just to add the failing tests

---------

Co-authored-by: automergerpr-permission-manager[bot] <190534181+automergerpr-permission-manager[bot]@users.noreply.github.com>
## Summary

- Skip LLVM backend `test_exec_smir` tests in CI — keep Haskell only,
since it's the backend used for proving and bugs there have higher
impact
- Skip `test_prove_termination` in CI — the same 19 programs are already
executed via `test_exec_smir[*-haskell]`

This is done via a pytest `-k` filter in the CI workflow only — **no
test code is modified**, so all tests remain available for local
development.

**Deselected: 58 of 247 tests** (39 LLVM exec_smir + 19
prove_termination)

## Risk Analysis

- LLVM backend regression will be missed in the previous test, which
should be handled by future test framework refactoring. But if we don't
add new `exec_smir` test or add new `exec_smir` test with llvm to update
expected files, the result is the same as we run CI before.
- `test_prove_termination` just uses `prove-rs` to validate the
termination, which is the same as the comparison with the current
expected files. I believe that there is no risk to remove them in this
way.

## Expected CI time reduction

~2h37m → ~1h20m (based on [this
run](https://github.com/runtimeverification/mir-semantics/actions/runs/22658250856/job/65672639933?pr=957))

## Test plan

- [x] Integration tests pass with the `-k` filter applied
- [x] Verify CI time improvement

Resolves #971 (Phase 1)

---------

Co-authored-by: Everett Hildenbrandt <everett.hildenbrandt@gmail.com>
Co-authored-by: dkcumming <daniel.cumming@runtimeverification.com>
This PR adds basic support for [IEEE 754 Floating Point
Numbers](https://en.wikipedia.org/wiki/IEEE_754), specifically `f16`,
`f32`, `f64`, `f128`.

These are already supported in K through builtins (see
[domains.md](https://github.com/runtimeverification/k/blob/master/k-distribution/include/kframework/builtin/domains.md#ieee-754-floating-point-numbers)).
This work is derivative of the implementation in the c-semantics (see
ieee754.k in that repository).

In particular:
- 6 tests are added that tests each type for equality, negation,
comparison, casting, arithmetic, and special values (NaN and Infinity)
- Documentation and helpers for floats are added to numbers.md
- Decoding is added for floats from bytes in numbers.md (see note on
haskell backend below)
- Semantic rules for arithmetic are extended for floats in data.md

### Haskell Backend 
The haskell backend has no `Float` builtins (no Float.hs in
[kore/src/Kore/Builtin/](https://github.com/runtimeverification/haskell-backend/tree/master/kore/src/Kore/Builtin)).
This means `kore-exec` crashes with "missing hook FLOAT.int2float" when
attempting to evaluate a float. The booster avoids this by delegating
Float evaluation to the LLVM shared library via `simplifyTerm` in
[booster/library/Booster/LLVM.hs](https://github.com/runtimeverification/haskell-backend/blob/master/booster/library/Booster/LLVM.hs).

Prior to being able to decode floats, they were left as `UnableToDecode`
which did not throw and error in the exec-smir test suite for
`--haskell-backend`. Now that they are able to be decoded, the haskell
backend throws on these decoded values. So I am now skipping any tests
with decoded floats for exec-smir with haskell backend.
## Summary
- remove the redundant `#forceSetLocal` helper now that `#setLocalValue`
no longer guards on mutability
- route moved-writeback and zero-sized-local initialization through
`#setLocalValue`
- drop the obsolete interior-mutability fast-path introduced by the old
branch state

## Why
After rebasing onto current `origin/master`, the original
interior-mutability writeback fast-path became obsolete:
`#setLocalValue` already permits writes to immutable locals, so the
extra helper and its special-case callsites no longer buy any behavior.

This PR turns the branch into the cleanup that current master actually
needs.

## Validation
- `make build`
- `cd kmir && uv run pytest src/tests/integration/test_integration.py -v
--timeout=600 -k "interior-mut or transmute-maybe-uninit-fail" -x`
  - 4 passed, 261 deselected
- started `make test-integration`; no early failures before stopping due
runtime
…pping strategy (#988)

## Summary
- Fixes stuck execution caused by incorrect projection chains from
`#pointeeProjection`
- Root cause: non-deterministic overlap between struct and array rules —
when source is a struct and target is an array (or vice versa), both
rules match and the LLVM backend's choice determines correctness
- Commit history shows why `[priority(45)]` alone doesn't work (it fixes
one case but regresses the other)
- Fix: always unwrap the source type first; target-side unwrapping
deferred to `#pointeeProjectionTarget`
- Reproducer from @dkcumming's review comment (minimal `Wrapper([u8;
2])` cast)

## Test plan
- [x] `ptr-cast-wrapper-to-array` passes (new reproducer)
- [x] `iter_next_2` still passes (regression test for opposite overlap:
`[Thing;3] → Thing`)
- [x] Full integration test suite passes

---------

Co-authored-by: Daniel Cumming <124537596+dkcumming@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…on (#813)

## Summary

Fix two issues preventing correct handling of zero-sized function item
types (e.g., non-capturing closures):

1. **`#zeroSizedType` did not recognize `typeInfoFunType`** (`types.md`)
— Function items are inherently zero-sized (`#elemSize` already returns
0), but `#zeroSizedType` returned `false`, preventing `rvalueRef` from
materializing uninitialized closure locals.

2. **`#decodeConstant` had no rule for `typeInfoFunType`** (`data.md`)
— Zero-sized function constants fell through to the `thunk` wrapper
instead of decoding to `Aggregate(variantIdx(0), .List)`.

## Evidence

`closure-staged.rs` with `terminate-on-thunk`:

| | Steps | Result |
|---|---|---|
| Before | 44 | `thunk(#decodeConstant(constantKindZeroSized, _,
typeInfoFunType(_)))` |
| After | 266 | `#EndProgram` |

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Daniel Cumming <124537596+dkcumming@users.noreply.github.com>
@Stevengre Stevengre marked this pull request as draft April 1, 2026 09:19
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6320cdfe40

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +211 to +215
OWNER_KEY, OWNER_KEY,
_IS_SIGNER, _OWNER_OF_ACCOUNT, _DATA, _TX_SIGNERS,
_MAYBE_MULTISIG,
Aggregate(variantIdx(1), ListItem(Aggregate(variantIdx(0), ListItem(Integer(4, 32, false))))),
DEST, TARGET)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject Err(Custom(4)) when owner keys are equal

The inner-case1b-keys-match-custom4-spl rule accepts OWNER_KEY == OWNER_KEY together with Err(Custom(4)) and immediately continues, which bypasses the assertion behavior that should fail when keys match but the result is still Custom(4). This makes the SPL lemma unsound for this mismatch case and can let an incorrect validate_owner implementation pass symbolic proofs.

Useful? React with 👍 / 👎.

Comment on lines +221 to +225
<k> #validateOwnerResultSPL(
OWNER_KEY, OWNER_KEY,
_IS_SIGNER, _OWNER_OF_ACCOUNT, _DATA, _TX_SIGNERS,
Aggregate(variantIdx(0), .List),
Aggregate(variantIdx(1), ListItem(Aggregate(variantIdx(7), .List))),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Constrain non-multisig SPL lemma cases by is_signer

This rule (and the corresponding inner-case3-spl) ignores is_signer via _IS_SIGNER, so contradictory states are accepted: e.g. is_signer == true with Err(MissingRequiredSignature) (or false with Ok(())) still rewrites as success instead of surfacing an assertion failure. That weakens inner_test_validate_owner checking and can hide signer-handling regressions in proofs.

Useful? React with 👍 / 👎.

Comment on lines +1431 to +1432
rule <k> #cast(Float(VAL, _WIDTH), castKindFloatToInt, _, TY)
=> #intAsType(Float2Int(VAL), 128, #intTypeOf(lookupTy(TY)))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Implement saturating FloatToInt cast semantics

Rust as casts from float to int are saturating for NaN/out-of-range inputs, but this rule uses Float2Int followed by integer truncation, which models truncation/wrapping behavior instead of saturation. Programs such as f32::INFINITY as i32 or NaN as u8 will be mis-modeled, producing incorrect proof outcomes around numeric bounds.

Useful? React with 👍 / 👎.

Adapted from the p-token version (PR #1001). Key differences from
the p-token lemma:

- spl-token AccountInfo uses raw Aggregate values instead of p-token's
  PAccountAccount/PAcc wrappers, so the intercept rules extract
  individual fields (key, is_signer, owner) from AccountInfo using
  field projections (field 0, 5, 3 respectively)
- Case rules match pre-extracted Values directly instead of
  destructuring PAccount wrappers
- Uses _IS_SIGNER wildcard for symbolic bool handling (spl-token's
  is_signer may be a symbolic BoolVal)
- Case 1b handles keys-match + Err(Custom(4)) path via LHS pattern
  match (avoids booster sort injection errors from requires ==K)
- Cases 8-10 (multisig signer-checking) fall through to small-step
  execution (sufficient for n==1)

Also fixes #forceSetLocal -> #setLocalValue in spl-token.md for
compatibility with origin/master which removed the redundant helper.

Proof result: test_validate_owner_multisig PASSED with 27 nodes
in 2886s (vs 123 nodes / 4877s without lemma, 41% speedup).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Stevengre Stevengre force-pushed the mk/lemmas-inner_test_validate_owner-spl branch from 6320cdf to 2fc994b Compare April 2, 2026 06:57
@Stevengre
Copy link
Copy Markdown
Contributor Author

Update: disabled inner_test_validate_owner lemma

The INNER-TEST-VALIDATE-OWNER-SPL-TOKEN-LEMMA module is now commented out in kmir.md. Only EXPECTED-VALIDATE-OWNER-RESULT-SPL-TOKEN-LEMMA is active.

Root cause

inner_test_validate_owner receives result.clone() as its 5th argument via operandMove. In spl-token, the cloned Result value contains a nested thunk(operandMove(...)) — the inner () of Ok(()) isn't eagerly resolved. K's seqstrict evaluates the top-level Aggregate (which is a Value), but doesn't force thunks nested inside ListItems.

This causes every spec that calls inner_test_validate_owner to get stuck on #validateOwnerResultSPL, because no case rule can match the thunked result.

Fix

  • expected_validate_owner_result lemma: kept active — its args don't involve result.clone(), so no thunks
  • inner_test_validate_owner lemma: disabled — falls through to small-step execution, which naturally resolves thunks

Validation

  • test_process_approve_checked_multisig: ✅ PASSED (71 nodes, was stuck before)
  • test_validate_owner_multisig: ✅ PASSED (27 nodes, 2886s)

The inner_test module is still in the file for reference but not imported. A proper fix would require the K runtime to force thunks inside Aggregate arguments during seqstrict evaluation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants