[Trust Reduction] Symbolic mapChain channel for hashed nested/struct mappings (G2 residual) - #2459
Conversation
…gs (G2 residual) #2432 stored nested-mapping, word-offset mapping and struct-mapping words in the flat `.slot` channel at the Solidity keccak slot. That made key-vs-key separation depend on `solidityMappingSlot_injective`, and it left mapping-vs-scalar separation (e.g. `requests[user][epoch]` vs slot 206) unprovable. These words now live on dedicated constructor-keyed channels: * `StorageKey.mapChain slot keys offset` (`ContractState.readMapChain` / `writeMapChain`, view `storageMapChain`), used by getMappingN/setMappingN, getMappingWord/setMappingWord, structMemberAt/structMember2At, setStructMemberAt/setStructMember2At and `structMembers` destructuring; * `StorageKey.transientMapChain slot keys` (`readTransientMapChain` / `writeTransientMapChain`, view `transientStorageMapChain`), used by getTransientMappingN/setTransientMappingN. The import harness' `beginTransaction` clears it. Both are plain non-slot keys, so hops namespace them per contract through `StorageKey.scoped`, and `Contract.run` rollback restores them. Lens laws: readMapChain_writeMapChain(_same/_of_ne/_keys_ne/_offset_ne/_slot_ne), independence from `.slot`, addr, map*, transient and arrays, context-field frames, and the hop lemmas (enterHop_/exitHop_readMapChain, readMapChain_exitHop_enterHop_of_frame). The Solidity layout is now an interpretation. `mappingChainSlotLocation` lives in MappingSlot, and Storage/HashedMappingLayout relates the channel to the flat `.slot` channel (`HashedCoherentOn`, preserved by aligned writes under a finite `LayoutNonAlias` certificate). No axiom is involved. The smoke proves key, member, mapping-vs-scalar and hop separation, using only propext / Classical.choice / Quot.sound. Closes #2456 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7007b4cbda
ℹ️ 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".
| fun state => | ||
| ContractResult.success () (state.writeMapChain field.slot (MappingKeyWord.words keys) 0 value) |
There was a problem hiding this comment.
Preserve the flat-slot coherence on mapping writes
Route this write through the coherence bridge (or integrate an equivalent two-state simulation): starting from defaultState, which satisfies HashedCoherentOn [e], setting a nonzero value here updates only .mapChain while storage_writeMapChain leaves the Solidity slot unchanged, so coherence is immediately false. HashedMappingLayout.alignedWrite_preserves only covers alignedWrite, and that operation is not used by this or the other changed mapping setters; consequently the executable contract can return the new value while the Denote/Yul model still reads the old flat-slot value, leaving the new proofs disconnected from compiled behavior.
Useful? React with 👍 / 👎.
| \n### CI Failure Hints\n\nFailed jobs: `build`\n\nCopy-paste local triage:\n```bash\nmake check\nlake build\nFOUNDRY_PROFILE=difftest forge test -vv\n``` |
|
CI note: the only failing step is 🤖 Generated with Claude Code |
|
CI note: the only failing job ( 🤖 Generated with Claude Code |
|
CI note: the only failing step, 🤖 Generated with Claude Code |
Summary
Closes #2456 (G2 residual; follow-up to #2416 / #2432).
Hashed nested / word-offset / struct mapping words now execute on a dedicated symbolic storage channel, not on
.slotat the keccak slot. In the executable plane, separating two entries is a matter of constructor injectivity:New
StorageKeyconstructors:mapChain (slot : Nat) (keys : List Nat) (offset : Nat)andtransientMapChain (slot : Nat) (keys : List Nat).Lenses (
Verity/Core.lean):ContractState.readMapChain/writeMapChain/storageMapChain, plusreadTransientMapChain/writeTransientMapChain/transientStorageMapChain.Routed through the new channel (
Contracts/Common.lean):getMappingN/setMappingN,getTransientMappingN/setTransientMappingN,getMappingWord/setMappingWord,structMemberAt/structMember2At/setStructMemberAt/setStructMember2At, and therefore alsostructMembersdestructuring. Packed members still do read-modify-write on the same entry.Lens laws:
readMapChain_writeMapChain_same,readMapChain_writeMapChain(simp, as anif), and_of_ne/_keys_ne/_offset_ne/_slot_ne;storage_writeMapChain,readSlot_writeMapChain,readMapChain_writeSlot/_writeAddrSlot/_writeMap/_writeMapUint/_writeMap2/_writeTransient/_writeArray, the transient counterparts, and context-field frames.Hops: both keys are plain non-slot keys (
isPlainNonSlot), soenterHop/exitHop/hopCall/hopCallViewnamespace them per contract viaStorageKey.scoped. New lemmas:enterHop_readMapChain,exitHop_readMapChain,storageWords_scoped_writeMapChain,readMapChain_exitHop_enterHop_of_frame,writeMapChain_enterHop_frame. Revert rollback covers these keys because they are part ofstorageWords.Transient:
Transactions.isTransientKeyclearstransientMapChainat transaction start.Interpretation, no axiom:
Compiler.Proofs.mappingChainSlotLocation(fold keccak + offset mod 2^256),_single/_pair/_zero;mappingChainSlot_eq_location,structSlot_eq_location,structSlot2_eq_location;Compiler/Proofs/Storage/HashedMappingLayout.lean:HashedCoherentOnrelates the channel to the flat.slotchannel the model plane reads. It is preserved by aligned writes (alignedWrite_preserves), by scalar writes that avoid the tracked locations (writeSlot_preserves), and by the other channels' writes. It takes a finiteLayoutNonAliascertificate (layoutNonAlias_of_certificatefrom the existingStorageSlotNonAliasCertificate) instead ofsolidityMappingSlot_injective. Transient chains have the analogueTransientHashedCoherentOn.MappingCoherence.storageKeySlotmapsmapChainto its Solidity location.Smoke (
Contracts/Smoke/HashedMappings.lean) proves the following through the generated functions:requestOf_other_after_setRequest);setRequest_leaves_scalar,requestOf_after_setWithdrawn);receiptOf_after_setReceipt,setReceipt_leaves_request_and_scalar);acquire_leaves_storage,locked_after_acquire);hop_setRequest_frame).All of them depend only on
propext/Classical.choice/Quot.sound.Migration. Contract-level statements that used
s.writeSlot (mappingChainSlot base keys) v,s.storage (structSlot base k w)ors.writeTransient (mappingChainSlot ..) vshould now uses.writeMapChain base (MappingKeyWord.words keys) 0 v(with keys written as words:[user.toNat, epoch.val]),s.readMapChain base [k] w, ors.writeTransientMapChain base keys v. Separation lemmas no longer need slot-inequality hypotheses.mappingChainSlot/structSlot/structSlot2remain as layout (interpretation) definitions.Note (found while doing this, not fixed here):
solidityMappingSlot_injectiveas stated over allNatis inconsistent.abiEncodeMappingSlotreduces keys mod 2^256, sosolidityMappingSlot 0 0 = solidityMappingSlot 0 (2^256)is provable axiom-free, and the axiom then yieldsFalse(repro in #2456). This PR adds no uses of the axiom. Restricting it to< 2^256inputs should be done separately.Test Plan
lake build Verity Contracts Compiler StorageLayoutReport PrintAxioms(2811 jobs) passes.make checkpasses: storage-lens freeze unchanged (new lenses go throughwithStorageWords), print-axioms / verification-status / macro property-test artifacts regenerated.lake env lean PrintAxioms.lean+scripts/check_axioms.py --logpasses. The new smoke / layout lemmas use only the three standard axioms.artifacts/lean_warning_baseline.json.Related Issues
Closes #2456. Refs #2416, #2432, #2441.
🤖 Generated with Claude Code