Skip to content

Language design axes: full implementation (25 steps across 6 axes)#1731

Open
Th0rgal wants to merge 43 commits intomainfrom
feature/language-design-axes-plan
Open

Language design axes: full implementation (25 steps across 6 axes)#1731
Th0rgal wants to merge 43 commits intomainfrom
feature/language-design-axes-plan

Conversation

@Th0rgal
Copy link
Copy Markdown
Member

@Th0rgal Th0rgal commented Apr 13, 2026

Summary

Implements the complete language design improvement plan for Verity's verity_contract EDSL — 25 steps across 6 axes, taking the language beyond Solidity parity with verified safety properties.

63 files changed, +3349 / -235 lines

Axis 1: Effect System (Steps 1a–1d)

  • @[view] theorem generation (1a): Auto-generates _is_view theorems proving functions make no state writes
  • modifies(...) annotation (1b): Declares which storage fields a function may write; generates _frame theorem proving all other fields are preserved
  • no_external_calls annotation (1c): Forbids external calls; generates _no_calls theorem
  • Annotation composition (1d): When multiple annotations are present, generates _effects conjunction theorem combining all individual guarantees

Axis 2: Safety Enforcement (Steps 2a–2c)

  • CEI static analysis (2a): Rejects state writes after external calls at compile time. Functions can opt out with allow_post_interaction_writes
  • CEI escalation ladder (2b): nonreentrant(lock) generates reentrancy guard with _nonreentrant theorem; cei_safe marks functions as CEI-compliant by developer assertion
  • requires(role) access control (2c): Guards function entry on msg.sender == role_field; generates _requires_role theorem

Axis 3: Newtypes (Steps 3a–3c)

  • types section (3a): verityNewtype syntax for zero-cost semantic wrappers (e.g., TokenId : Uint256)
  • IR + Yul erasure (3b–3c): ParamType.newtypeOf and ValueType.newtype propagate through all 11+ compilation model files; newtypes erase to their base type at every ABI/Yul boundary

Axis 4: Storage Safety (Steps 4a–4d)

  • EIP-7201 namespace (4a): computeStorageNamespace hashes the contract name via kernel Keccak at elaboration time
  • storage_namespace keyword (4b): Opt-in; when present, all slot N become slot (namespaceBase + N)
  • Custom namespace key (4c): storage_namespace "custom.key" overrides the default contract-name hash
  • ABI + tooling integration (4d): {contract}.storage.json includes storageNamespace; layout reports include the namespace base

Axis 5: ADTs + External Calls (Steps 5a–5f)

  • inductive section (5a): Grammar for algebraic data types with named variants and typed fields
  • ADT IR (5b): ParamType.adt, Expr.adtConstruct/adtTag/adtField, Stmt.matchAdt across ~17 compilation model files
  • ADT storage + Yul lowering (5c–5d): Tagged union encoding (uint8 tag, uint256 fields...); match compiles to Yul switch on tag
  • ADT ABI encoding (5e): Tuple-based ABI encoding/decoding for ADT values
  • tryExternalCall (5f): Non-reverting external calls with Call.Result return type; ! sugar for force-unwrap
  • ADT type definitions in ContractSpec (5b cont.): adtTypes field wired from parsed syntax through to spec

Axis 6: Unsafe Blocks (Steps 6a–6c)

  • unsafe "reason" do syntax (6a): Wraps low-level operations requiring explicit justification
  • Restricted operation gating (6b): Raw storage reads, inline Yul, and other low-level ops require unsafe wrapper
  • Trust report + --deny-unsafe (6c): Unsafe blocks appear in the trust report; --deny-unsafe flag rejects any unsafe usage

Bug Fixes (Post-Review)

Addresses all CI failures and review comments from Codex/Bugbot:

  • CI: MacroExternal.storeEcho CEI violation fixed with allow_post_interaction_writes
  • CEI analysis: seenCall propagated into branches; same-stmt call+write detection; stmtIsPersistentWrite made recursive for compound statements
  • exprContainsExternalCall: Added 11 missing Expr variants
  • Role guard: Expr.storageExpr.storageAddr for address fields
  • Frame predicates: mappingStruct split by key type (.addressstorageMap, .uint256storageMapUint)
  • Reserved names: All 10 generated theorem suffixes added to collision check
  • modifies(): sepBysepBy1 to reject empty clause
  • Internal function specs: Added modifies field

Test plan

  • All 29 #check_contract pass in Smoke.lean (including new contracts: EffectAnnotationSmoke, ModifiesSmoke, NoExternalCallsSmoke, EffectCompositionSmoke, CEISmoke, CEILadderSmoke, RolesSmoke, NewtypeSmoke, NamespacedStorageSmoke, CustomNamespacedSmoke, AdtSmoke, UnsafeBlockSmoke, UnsafeGatingAccepted, ExternalCallMultiReturn, TryExternalCallSmoke, CEIViolationRejected)
  • MacroTranslateInvariantTest updated for all new contracts
  • MacroTranslateRoundTripFuzz updated for all new contracts
  • 14 new Foundry property test files auto-generated
  • lake build passes (full build)
  • make check passes (425 tests)

🤖 Generated with Claude Code


Note

High Risk
High risk because this changes core IR types, ABI encoding/decoding, statement compilation, and validator logic (including CEI and trust-surface gates), which can affect generated Yul and safety checks across all contracts.

Overview
Implements new IR surface area for ADTs (tagged unions) and zero-cost newtypes, wiring them through ABI rendering, ABI encode/decode, param loading, event/custom-error support, and statement/expression compilation (including matchAdt lowering to Yul switch and storage-backed tag/field reads).

Adds new effect/safety validations on function specs: modifies(...) and no_external_calls enforcement, compile-time CEI (Checks-Effects-Interactions) ordering rejection with opt-outs (e.g. allow_post_interaction_writes), plus an unsafe "reason" do statement wrapper that scopes low-level mechanics and is surfaced in trust reporting.

Extends tooling outputs and CLI: emits {contract}.storage.json with optional EIP-7201 storageNamespace, augments layout/trust reports with namespace/unsafe-block info, and introduces --deny-unsafe to fail compilation when any unsafe blocks are present.

Reviewed by Cursor Bugbot for commit 9b9a3d5. Bugbot is set up for automated code reviews on this repo. Configure here.

claude added 2 commits April 13, 2026 13:37
Planning document for the 5 axes of language design improvements
that go beyond Solidity parity (#1726):

- Axis 1: Type system enrichment (newtypes, ADTs, Result types) #1727
- Axis 2: Compile-time safety enforcement (CEI, access control, unsafe) #1728
- Axis 3: Effect system & auto-theorem generation #1729
- Axis 4: Storage safety (EIP-7201 namespaces) #1730

Includes suggested implementation order (Axis 3 first — lowest
friction, highest proof payoff, builds theorem generation infra
reused by Axis 2), effort estimates, file-level impact analysis,
and integration mapping to #1724 parity waves.

No code changes — planning only.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ctions

Phase 1 of the Language Design Axes plan (#1729, Axis 3).

For every `view` function in a `verity_contract`, the macro now
auto-generates a `@[simp]` theorem `<fn>_is_view` stating:

    FunctionSpec.isView (<fn>_model) = true

This is provable by `rfl` because the macro already sets the flag.
The `stmtWritesState` validation in `Validation.lean` guarantees the
body contains no state-writing statements, so the flag accurately
reflects the function's behavior.

Changes:
- Specs/Common.lean: add `viewPreservesState` predicate (full state
  unchanged frame condition, covering all storage + context + events +
  knownAddresses) with `@[simp]` reflexivity theorem
- Macro/Bridge.lean: add `mkViewTheoremCommand` that generates the
  `_is_view` simp lemma for a view function
- Macro/Elaborate.lean: call `mkViewTheoremCommand` in the per-function
  loop for view functions
- MacroTranslateInvariantTest.lean: compile-time assertion that the
  `MutabilitySmoke.currentOwner_is_view` theorem exists

Verified: `lake build` succeeds, `make check` passes, all existing
contracts (ERC20, Vault, SimpleToken, Ledger, etc.) unaffected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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: b4a13f829a

ℹ️ 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 thread Verity/Macro/Bridge.lean
Emits a `@[simp]` lemma stating the model's `isView` flag is `true`, making this
fact available to downstream proof automation. Only called when `fnDecl.isView`. -/
def mkViewTheoremCommand (fnDecl : FunctionDecl) : CommandElabM Cmd := do
let viewName ← mkSuffixedIdent fnDecl.ident "_is_view"
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 Reserve generated _is_view helper names

Generating $fn_is_view introduces a new declaration name, but the existing collision validator still only reserves _modelBody, _model, _bridge, and _semantic_preservation in validateGeneratedDefNamesPublic (Verity/Macro/Translate.lean). If a contract already declares a symbol like currentOwner_is_view (function/field/constant), elaboration now fails with a duplicate-name error instead of being rejected up front with the normal validation path. Please add _is_view to generated-name conflict checks (at least for view functions) so these contracts don’t regress.

Useful? React with 👍 / 👎.

Implements Phase 1, Step 1b of the language design axes plan (#1729).

New `modifies(field1, field2)` annotation for verity_contract functions
declares which storage fields a function writes to. The compiler:

1. Validates modifies field names exist in the storage section
2. Validates the function body only writes to declared fields
   (via new `stmtWrittenFields` / `stmtListWrittenFields` walkers)
3. Auto-generates `_modifies` simp theorem recording the declared set
4. Auto-generates `_frame` definition: conjunction of unchanged
   predicates for every field NOT in the modifies set plus sameContext
5. Auto-generates `_frame_rfl` simp lemma proving the frame holds
   reflexively

Syntax: `function myFn (...) modifies(field1, field2) : RetTy := body`

Files changed:
- Macro/Syntax.lean: new `verityModifies` syntax category
- Macro/Translate.lean: FunctionDecl.modifies field, parser, validation
- CompilationModel/Types.lean: FunctionSpec.modifies field
- CompilationModel/Validation.lean: stmtWrittenFields walker + check
- Macro/Bridge.lean: mkModifiesTheoremCommand + mkFrameDefCommand
- Macro/Elaborate.lean: emit modifies/frame in per-function loop
- Contracts/Smoke.lean: ModifiesSmoke test contract
- Tests: invariant test + round-trip fuzz coverage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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: 405f491f9d

ℹ️ 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".

stmtListWrittenFields thenBranch ++ stmtListWrittenFields elseBranch
| Stmt.forEach _ _ body =>
stmtListWrittenFields body
| _ => []
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 Account for call statements in modifies validation

stmtWrittenFields falls through to [] for Stmt.internalCall, Stmt.internalCallAssign, Stmt.externalCallBind, and Stmt.ecm, so a function annotated modifies(counter) can still mutate other storage via a callee and pass validation. That makes the generated _frame theorem unsound (it can claim untouched fields are unchanged when they are not). Please either do interprocedural write-set checking or conservatively reject modifies(...) on statements with unknown write effects.

Useful? React with 👍 / 👎.

Comment thread Verity/Macro/Bridge.lean
Comment on lines +101 to +102
let frameName ← mkSuffixedIdent fnDecl.ident "_frame"
let frameRflName ← mkSuffixedIdent fnDecl.ident "_frame_rfl"
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 Reserve new frame-helper names before generation

This commit now generates <fn>_modifies, <fn>_frame, and <fn>_frame_rfl, but the generated-name validator still only reserves the older helper suffixes. A user-defined symbol with one of these new names (for example a function named transfer_frame) will pass upfront validation and then fail later with a duplicate declaration when these helpers are emitted. Add the new suffixes to generated-name conflict checks so errors are caught deterministically during validation.

Useful? React with 👍 / 👎.

Comment thread Compiler/CompilationModel/Validation.lean
…s theorem

Add the `no_external_calls` mutability modifier that rejects external call
statements at compile time and auto-generates a `_no_calls` simp theorem.

- Syntax: `function no_external_calls foo (...) : T := body`
- New `stmtContainsExternalCall` / `exprContainsExternalCall` walkers in
  Validation.lean detect call/staticcall/delegatecall/externalCall/ecm
- `noExternalCalls : Bool` added to FunctionDecl and FunctionSpec
- `mkNoCallsTheoremCommand` in Bridge.lean emits `@[simp] theorem _no_calls`
- NoExternalCallsSmoke test contract with compile-time theorem assertions
- All `make check` CI invariants pass

Phase 1 progress: Steps 1a, 1b, 1c done; Step 1d (annotation composition) remaining.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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: deca050b4f

ℹ️ 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".

exprContainsExternalCall offset
| Expr.keccak256 offset size =>
exprContainsExternalCall offset || exprContainsExternalCall size
| _ => false
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 Traverse all Expr variants in no_external_calls scan

exprContainsExternalCall falls back to _ => false for any unhandled expression node, and several constructors with child expressions (e.g. Expr.ge, Expr.le, Expr.logicalAnd, Expr.logicalOr, Expr.signextend, Expr.extcodesize, Expr.arrayElement, Expr.internalCall) are currently unhandled. This lets annotated functions bypass no_external_calls by nesting call/staticcall/delegatecall/externalCall under one of those wrappers (for example inside ge(...)), so the validator can accept code that still performs external calls.

Useful? React with 👍 / 👎.

Comment on lines +471 to +472
| Stmt.internalCall _ args | Stmt.internalCallAssign _ _ args =>
args.any exprContainsExternalCall
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 Treat internal calls as unknown external-call effects

For Stmt.internalCall/Stmt.internalCallAssign, the check only inspects argument expressions and never inspects the callee body, so a function marked no_external_calls can call an internal helper that performs externalCallBind, ecm, or low-level call expressions and still pass validation. That makes the annotation unsound unless the validator either performs interprocedural analysis or conservatively rejects internal calls under no_external_calls.

Useful? React with 👍 / 👎.

Comment thread Verity/Macro/Bridge.lean
(#1729, Axis 3 Step 1c). Emits a `@[simp]` lemma stating the model's
`noExternalCalls` flag is `true`. Only called when `fnDecl.noExternalCalls`. -/
def mkNoCallsTheoremCommand (fnDecl : FunctionDecl) : CommandElabM Cmd := do
let noCallsName ← mkSuffixedIdent fnDecl.ident "_no_calls"
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 Reserve generated _no_calls helper names

This commit starts auto-generating <fn>_no_calls declarations, but generated-name validation still only reserves the older helper suffixes, so user symbols like transfer_no_calls are accepted initially and then fail later with duplicate declarations when theorem generation runs. Please add _no_calls to the reserved/generated helper-name conflict checks to keep failures deterministic at validation time.

Useful? React with 👍 / 👎.

…on theorem

When a function has 2+ effect annotations (view, no_external_calls, modifies),
auto-generate an _effects theorem that bundles all individual effect facts into
a single And proposition, simplifying downstream proof automation.

- Add effectAnnotationCount and mkEffectsTheoremCommand in Bridge.lean
- Wire _effects emission in Elaborate.lean for functions with ≥2 annotations
- Add EffectCompositionSmoke test contract exercising all combinations
- Update MacroTranslateInvariantTest with specs, sigs, selectors, theorem checks
- Update MacroTranslateRoundTripFuzz with EffectCompositionSmoke.spec

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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

let helperNames :=
#[ s!"{fn.name}_modelBody"
, s!"{fn.name}_model"
, s!"{fn.name}_bridge"
, s!"{fn.name}_semantic_preservation"

P2 Badge Reserve generated _effects helper names

The elaborator now emits <fn>_effects declarations (mkEffectsTheoremCommand), but generated-name validation still only reserves _modelBody, _model, _bridge, and _semantic_preservation. This allows user symbols like transfer_effects to pass early validation and then fail later with duplicate declaration errors when theorem generation runs. Add _effects to the helper-name collision checks so the failure is caught deterministically during validation.

ℹ️ 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 thread Verity/Macro/Syntax.lean Outdated
syntax "payable" : verityMutability
syntax "view" : verityMutability
syntax "no_external_calls" : verityMutability
syntax "modifies(" sepBy(ident, ",") ")" : verityModifies
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 Reject empty modifies() clauses

Using sepBy(ident, ",") allows modifies() with zero fields, and downstream validation/theorem generation is gated on !modifies.isEmpty, so this annotation becomes a silent no-op: write-set checks are skipped and no frame artifacts are emitted. That can mislead users into believing writes are constrained when they are not. Either require at least one field (sepBy1) or treat an empty clause as “writes nothing” and enforce it.

Useful? React with 👍 / 👎.

Comment thread Verity/Macro/Bridge.lean Outdated
Comment on lines +97 to +99
| .mappingAddressToUint256 | .mappingChain _ | .mappingStruct _ _ =>
-- ∀ k, s'.storageMap slot k = s.storageMap slot k
`(∀ k, s'.storageMap $slotLit k = s.storageMap $slotLit k)
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 Preserve mapping key types in generated frame predicates

This branch emits frame conjuncts for all mappingStruct fields via storageMap (address-keyed), ignoring whether the struct mapping key type is Uint256. For contracts that use MappingStruct(Uint256, ...), the generated _frame predicate constrains the wrong state component, so frame obligations can be proven without actually preserving the uint-keyed mapping field. The adjacent mappingStruct2 wildcard has the same key-type erasure risk. Split these cases by key types and use the corresponding storage relation.

Useful? React with 👍 / 👎.

Comment thread Compiler/CompilationModel/Validation.lean
Comment thread Compiler/CompilationModel/Validation.lean
…n_writes opt-out

Add Checks-Effects-Interactions ordering enforcement as a default-on
static analysis pass.  Functions that write storage after an external
call are rejected at compile time unless annotated with
allow_post_interaction_writes.  Compliant functions receive an
auto-generated @[simp] _cei_compliant theorem.

- Add allow_post_interaction_writes syntax modifier and FunctionDecl/FunctionSpec field
- Implement stmtIsPersistentWrite, stmtIsDirectExternalCall, and mutual
  stmtListCEIViolation/stmtInternalCEIViolation walkers in Validation.lean
- Wire CEI validation into validateFunctionSpec (runs at compileChecked time)
- Generate _cei_compliant theorem in Bridge.lean/Elaborate.lean
- Add CEISmoke positive test and CEIViolationRejected negative test (#guard_msgs)
- Annotate 5 existing functions with allow_post_interaction_writes
- Update invariant test, fuzz coverage exclusion, and property test artifacts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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: 9fff4b4fba

ℹ️ 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".

own nested structure. -/
def stmtInternalCEIViolation : Stmt → Option String
| Stmt.ite _ thenBranch elseBranch =>
match stmtListCEIViolation thenBranch false with
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 Propagate prior external-call state into nested CEI checks

stmtInternalCEIViolation always checks if branches with seenCall = false, so an earlier interaction is forgotten once control reaches a compound statement. This lets patterns like externalCall ...; if cond then setStorage ... pass CEI validation even though the write is post-interaction on that path; the same reset happens for loop-body checks. The nested CEI walk needs to inherit the caller’s seenCall state.

Useful? React with 👍 / 👎.

Comment on lines +575 to +579
if seenCall && stmtIsPersistentWrite s then
some "state write after external call"
else
let newSeenCall := seenCall || stmtContainsExternalCall s
stmtListCEIViolation rest newSeenCall
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 CEI violations when call and write occur in one stmt

The CEI check only errors on writes when seenCall was already true before the current statement. For statements that both contain an external call and perform a persistent write (for example setStorage/setMapping with a call expression on the RHS), newSeenCall is set after the statement and no violation is raised, allowing an interaction-before-effect sequence to slip through.

Useful? React with 👍 / 👎.

Comment on lines +78 to +84
elabCommand (← mkEffectsTheoremCommand fn)

-- Emit per-function _cei_compliant theorem for functions that pass CEI
-- validation without opting out (#1728, Axis 2 Step 2a).
for fn in functions do
if !fn.allowPostInteractionWrites then
elabCommand (← mkCEICompliantTheoremCommand fn)
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 Reserve new _effects and _cei_compliant helper names

This commit now emits <fn>_effects and <fn>_cei_compliant declarations, but generated-name validation still reserves only the older helper suffixes in validateGeneratedDefNamesPublic (Verity/Macro/Translate.lean). A user symbol with either new suffix can pass early validation and then fail later with duplicate declarations during elaboration. Please add both suffixes to the generated-name conflict checks.

Useful? React with 👍 / 👎.

Comment thread Compiler/CompilationModel/Validation.lean
Comment thread Compiler/CompilationModel/Validation.lean Outdated
…i_safe

Complete the four-rung CEI escalation ladder:
  Rung 1: Safe by default (compiler enforces CEI ordering) [Step 2a]
  Rung 2: cei_safe (machine-checked proof obligation) [NEW]
  Rung 3: nonreentrant(field) (known-safe reentrancy guard) [NEW]
  Rung 4: allow_post_interaction_writes (explicit trust-surface opt-out) [Step 2a]

- Add nonreentrant(field) and cei_safe syntax modifiers
- Add nonReentrantLock/ceiSafe fields to FunctionDecl and FunctionSpec
- Refactor parseMutabilityModifiers to use ParsedMutability record (was 4-tuple)
- Update CEI enforcement to bypass for any escalation rung
- Validate nonreentrant lock field references and mutual exclusivity
- Auto-generate _nonreentrant and _cei_safe @[simp] theorems
- Add CEILadderSmoke test contract exercising all ladder rungs
- Update invariant, fuzz, and property test infrastructure

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread Compiler/CompilationModel/Validation.lean
Comment thread Verity/Macro/Translate.lean
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: b1fd6e3a44

ℹ️ 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".

-- Rung 2: cei_safe (machine-checked proof obligation)
-- Rung 3: nonreentrant(field) (known-safe reentrancy guard)
-- Rung 4: allow_post_interaction_writes (explicit trust-surface opt-out)
let ceiExempt := spec.allowPostInteractionWrites || spec.nonReentrantLock.isSome || spec.ceiSafe
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 Enforce proof before honoring cei_safe exemption

Including spec.ceiSafe in ceiExempt disables CEI validation outright, but this change set does not generate or verify any proof obligation for that annotation, so cei_safe currently behaves like an unchecked opt-out. A function can mark itself cei_safe and still perform interaction-then-write sequences (as in CEILadderSmoke.callThenStoreProved) without any machine-checked evidence, which defeats the stated safety ladder.

Useful? React with 👍 / 👎.

Comment on lines +4002 to +4006
| some lockField =>
let lockName := toString lockField.getId
let allFieldNames := fields.map (·.name)
if !allFieldNames.contains lockName then
throwErrorAt lockField s!"function '{fn.name}': nonreentrant references unknown storage field '{lockName}'; known fields: {allFieldNames.toList}"
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 Verify nonreentrant semantics before skipping CEI

The nonreentrant(...) path only checks that the named field exists, but CEI enforcement is later bypassed whenever nonReentrantLock.isSome; there is no validation that the function actually acquires/releases a lock or even touches that field. This allows unsafe post-interaction writes to pass simply by adding the annotation, turning a supposed "known-safe guard" into an unchecked bypass.

Useful? React with 👍 / 👎.

…ole theorem

Add `requires(field)` annotation for functions that auto-injects a
`require(caller == roleHolder)` check at the start of the function body.
The named field must be an Address-typed storage field. Both source-level
(DSL do-block) and IR-level (Stmt prelude) guards are generated.

Changes:
- Syntax.lean: new `verityRequiresRole` syntax category, `requires(ident)`
- Translate.lean: `requiresRole` field on FunctionDecl, `resolveRoleField`
  validation, `mkRoleGuardedBody` (source-level), `roleGuardPreludeStmtTerms`
  (IR-level), wired into `translateBodyToStmtTerms` and FunctionSpec construction
- Types.lean: `requiresRole : Option String` on FunctionSpec
- Bridge.lean: `mkRequiresRoleTheoremCommand` generates `_requires_role` theorem
- Elaborate.lean: emits `_requires_role` theorems for annotated functions
- Smoke.lean: RolesSmoke test contract with requires(admin)
- Integration tests: MacroTranslateInvariantTest, MacroTranslateRoundTripFuzz,
  PropertyRolesSmoke.t.sol

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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: d3a5e48d94

ℹ️ 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 thread Verity/Macro/Translate.lean Outdated
← `(Compiler.CompilationModel.Stmt.require
(Compiler.CompilationModel.Expr.eq
(Compiler.CompilationModel.Expr.caller)
(Compiler.CompilationModel.Expr.storage $(strTerm field.name)))
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 Read role guard from address storage accessor

The IR prelude generated for requires(...) compares Expr.caller against Expr.storage field, even though resolveRoleField enforces that the role field is Address-typed and the executable body uses getStorageAddr for the same check. In the model semantics this reads the wrong state component (storage vs storageAddr), so proofs/validation over FunctionSpec.body can disagree with the actual guarded function behavior for role-protected entrypoints.

Useful? React with 👍 / 👎.

Comment on lines +90 to +92
match fn.nonReentrantLock with
| some lockIdent =>
elabCommand (← mkNonReentrantTheoremCommand fn (toString lockIdent.getId))
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 Reserve new CEI/role helper theorem names

This commit now emits <fn>_nonreentrant, <fn>_cei_safe, and <fn>_requires_role theorems, but generated-name validation still only reserves the legacy helper suffixes, so user declarations with these names can pass early validation and then fail later with duplicate declarations during elaboration. Please add these new suffixes to the generated-name conflict checks to keep failures deterministic and upfront.

Useful? React with 👍 / 👎.

claude added 4 commits April 13, 2026 17:39
Add a `types` section to the verity_contract grammar that lets users
declare semantic newtypes (e.g. `TokenId : Uint256`).  At the language
level the types are distinct identifiers; at the EVM/Yul level they are
erased to their base types (zero overhead).

Changes:
- Syntax.lean: declare verityNewtype syntax category, add optional
  `types` section before `storage` in verityContractCmd
- Translate.lean: NewtypeDecl structure, parseNewtype with scalar-only
  validation, thread newtypes array through all parse* and
  valueTypeFromSyntax functions, resolve unknown idents against
  newtypes before erroring
- Elaborate.lean: destructure the new newtypes return value
- Smoke.lean: NewtypeSmoke test contract (TokenId, Amount, Owner)
  with #check_contract
- MacroTranslateInvariantTest: specs, signatures, selectors, theorems
- MacroTranslateRoundTripFuzz: specs
- generate_macro_property_tests.py: parse `types` section, resolve
  newtypes to base types in function params/return types
- PropertyNewtypeSmoke.t.sol: auto-generated property tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Every contract now emits a `storageNamespace : Nat` definition equal to
`keccak256("{ContractName}.storage.v0")` computed at elaboration time
using the vendored kernel-computable Keccak engine.  This hash can serve
as a base offset so different contracts occupy non-overlapping regions of
the 2^256 storage address space (Step 4b will wire the offset).

Changes:
- Translate.lean: import Compiler.Keccak.Sponge, add byteArrayToNatBE
  helper, computeStorageNamespace, mkStorageNamespaceCommand
- Elaborate.lean: emit storageNamespace after storage/immutable defs
- Smoke.lean: fix #check_contract ordering for RolesSmoke/NewtypeSmoke
  (forward-reference bug from Step 3a), add storageNamespace type
  verification examples

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add `storage_namespace` keyword to the `verity_contract` grammar.
When present, all user-declared `slot N` values are offset by
`keccak256("{ContractName}.storage.v0")`, giving each contract a
unique region in the 2^256 storage address space (EIP-7201 pattern).

Existing contracts without `storage_namespace` are unaffected —
the feature is strictly opt-in, merging the original plan's steps
4b (slot offsetting) and 4c (override attributes) into a single
backwards-compatible mechanism.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Thread `storageNamespace : Option Nat` through the CompilationModel so
downstream tooling can consume it:

- Add `storageNamespace` field to `CompilationModel` (default `none`)
- Pass namespace from `parseContractSyntax` → spec generation
- Emit `"storageNamespace"` key in layout report JSON
- Add `emitContractStorageLayoutJson` / `writeContractStorageLayoutFile`
  in ABI.lean — writes `{Contract}.storage.json` alongside ABI output
- Smoke tests verify `spec.storageNamespace.isSome` for namespaced
  contracts and `.isNone` for regular contracts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 13, 2026

\n### CI Failure Hints\n\nFailed jobs: `macro-fuzz`\n\nCopy-paste local triage:\n```bash\nmake check\nlake build\nFOUNDRY_PROFILE=difftest forge test -vv\n```

claude added 6 commits April 13, 2026 18:38
Add `unsafe "reason" do` syntax as a transparent wrapper around
statement blocks, laying the groundwork for restricted-operation
gating (Step 6b) and trust reporting (Step 6c).

- New Stmt.unsafeBlock variant with reason string and body
- doElem syntax: `unsafe "..." do <doSeq>`
- Translate.lean: validation + translation cases
- All 16 exhaustive Stmt pattern-match sites updated (transparent
  recurse-into-body semantics)
- UnsafeBlockSmoke test contract with #check_contract
- Integration tests updated (invariant, round-trip fuzz, property gen)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add unguarded mechanics collector that skips `unsafe "reason" do` block
bodies, so low-level operations (mstore, call, staticcall, etc.) inside
unsafe blocks no longer trigger the local_obligations requirement.

Functions/constructors using low-level mechanics *outside* an unsafe
block must still declare local_obligations or the validation pass rejects
them with a clear error referencing Issue #1424.

- TrustSurface: collectUnguardedLowLevelStmtMechanics (returns [] for
  unsafeBlock instead of recursing), plus public API wrappers
- Validation: validateFunctionSpec/validateConstructorSpec now use the
  unguarded collector
- Smoke tests: UnsafeGatingAccepted (mstore inside unsafe passes) and
  UnsafeGatingRejected (#guard_msgs negative test for mstore outside)
- Integration: MacroTranslateInvariantTest, MacroTranslateRoundTripFuzz,
  property tests, fuzz coverage exclusion all updated

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…afe flag

Add unsafe block tracking to the trust report infrastructure:

- TrustSurface: collectUnsafeBlockReasons extracts reason strings from
  Stmt.unsafeBlock nodes; UsageSiteSummary gains unsafeBlocks field;
  JSON trust report and verbose CLI output both include unsafe blocks;
  emitUnsafeBlockUsageSiteLines provides per-site diagnostic lines
- CompileDriverCommon: ensureNoUnsafeBlocks rejects contracts containing
  unsafe blocks when --deny-unsafe is set; verbose output includes
  "Unsafe block report" section
- MainDriver: --deny-unsafe CLI flag parsed and threaded through
- CompileDriverTest: deny-unsafe rejection test, pass-through test for
  contracts without unsafe blocks, trust report JSON content assertion

Phase 6 (Unsafe Blocks) is now complete: 6a (syntax), 6b (gating),
6c (trust report + deny flag).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add `inductive` section to `verity_contract` grammar for declaring
algebraic data types (tagged unions) with typed variant fields. ADTs
are parsed, validated (no duplicate names, no built-in shadowing),
and threaded through the parse pipeline. IR generation deferred to
Step 5b.

Grammar: `inductive OptionalUint := | Some(value : Uint256) | None`
Syntax categories: verityAdtVariant, verityAdtDecl
Structures: AdtVariantDecl, AdtDecl (in Translate.lean)
Parsers: parseAdtVariant, parseAdtDecl

Files: Syntax.lean, Translate.lean, Elaborate.lean, Smoke.lean,
MacroTranslateInvariantTest.lean, MacroTranslateRoundTripFuzz.lean

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add IR-level algebraic data type support across the compilation model:

- Types.lean: AdtVariant, AdtTypeDef structs; ParamType.adt variant;
  Expr.adtConstruct/adtTag/adtField; Stmt.matchAdt; adtTypes in spec
- Exhaustive pattern match coverage for ParamType.adt across AbiHelpers,
  AbiTypeLayout, AbiEncoding, EventAbiHelpers, ParamLoading
- Exhaustive Expr ADT cases in LogicalPurity, UsageAnalysis, TrustSurface,
  ExpressionCompile, ValidationHelpers, Validation
- Stmt.matchAdt handling with explicit recursive helpers (replacing
  branches.any/flatMap/all/for patterns that fail Lean termination) in
  Validation, ValidationCalls, ValidationEvents, ValidationInterop,
  ValidationHelpers, ScopeValidation, UsageAnalysis, LogicalPurity
- Param now derives BEq (needed for ADT field comparison)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement tagged-union storage layout for ADTs and wire compilation
through to Yul output. Key changes:

- New AdtStorageLayout.lean: layout helpers (tag slot, field slots,
  total footprint), Yul fragment generators for sload/sstore
- Expr.adtField now carries pre-resolved fieldIndex + storageField
  (avoids threading adtTypes through 90+ compileExpr call sites)
- compileExpr: adtTag → sload(base) & 0xFF, adtField → sload(base+i+1)
- compileStmt: matchAdt → YulStmt.switch on tag with field bindings
- compileMatchAdtBranches: explicit recursive helper (avoids for-loop
  termination issues in mutual blocks)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- exprReadsStateOrEnv: recurse into adtConstruct args instead of
  returning true unconditionally, matching exprWritesState behavior
  and avoiding false rejection of pure functions constructing ADTs
- CompileDriver: forward denyUnsafe through both compileSpecsWithOptions
  and compileModulesWithOptions wrappers so --deny-unsafe works via
  the public Compiler API
- CompileDriverTest: add allowPostInteractionWrites to linkedLibrarySpec's
  storeHash (external call followed by storage write is a CEI violation
  that new validation correctly catches)
- SourceSemantics proof: handle ADT expr cases in
  evalExprWithHelpers_eq_evalExpr_when_no_helpers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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: de34a5b3c4

ℹ️ 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".

-- instead of reverting on failure. (#1727, Axis 1 Step 5f)
| Stmt.tryExternalCallBind successVar resultVars externalName args => do
let argExprs ← compileExprList fields dynamicSource args
let tryFnName := s!"{externalName}_try"
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 Validate or generate _try wrapper before lowering calls

Stmt.tryExternalCallBind is lowered to a call to {externalName}_try (Compile.lean), but the target validation path only checks that externalName exists in spec.externals and never requires a corresponding _try symbol. In practice, a contract that declares external echo(...) and uses tryExternalCall "echo" emits Yul calling echo_try; linking libraries that satisfy ordinary externalCall now fails with unresolved references unless authors add an undocumented extra helper. Either synthesize the _try wrapper from the declared external or validate that _try is explicitly declared/provided.

Useful? React with 👍 / 👎.

…n pipeline

Propagate the new `adtTypes` parameter through all proof files in the
IRGeneration chain. The `compileFunctionSpec` and `compileInternalFunction`
functions gained an `adtTypes` positional parameter, requiring updates to
every call site in the proof files and threading `hnoAdtTypes` hypotheses
through the theorem signatures.

- SupportedSpec: add `noAdtTypes` field and shortcut theorems
- GenericInduction: thread `hnoAdtTypes` hypothesis through induction lemmas
- FunctionBody: add `hnoAdtTypes` to body correctness theorems
- Function: add `hnoAdtTypes` to function-level theorems, bridge type
  mismatches with rewrite/subst patterns
- Dispatch: insert `[]` for adtTypes at all `compileFunctionSpec` call sites
- Contract: insert `[]` for adtTypes, update `compileValidatedCore` rewrites
- ContractFeatureTest: add missing `noAdtTypes` field
- Regenerate PrintAxioms.lean

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread Compiler/CompilationModel/Compile.lean
Comment thread Compiler/CompilationModel/ExpressionCompile.lean
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: 962d000b3c

ℹ️ 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 +395 to +396
| Expr.adtConstruct adtName variantName _args =>
throw s!"Compilation error: ADT construct '{adtName}.{variantName}' cannot be used in expression position. ADT construction expands to multiple sstores and must be compiled at the statement level."
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 Lower Expr.adtConstruct instead of throwing

This new IR constructor is advertised as part of ADT support, but codegen currently aborts as soon as it encounters it. Any function body that constructs an ADT value (for example via a storage assignment or return expression that contains Expr.adtConstruct) will fail during Yul compilation, so ADT construction is effectively unusable even though the type and validation plumbing was added.

Useful? React with 👍 / 👎.

Comment thread Verity/Macro/Translate.lean Outdated
Comment on lines +572 to +573
params := ← params.mapM (valueTypeFromSyntax newtypes #[])
returnTys := ← returnTys.mapM (valueTypeFromSyntax newtypes #[])
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 Thread parsed ADTs into external signature parsing

External declarations are still parsed with valueTypeFromSyntax ... #[], so ADT names from the contract's inductive section are ignored at this boundary. In practice, external params/returns that reference a declared ADT are rejected as unsupported types, which blocks ADT-typed external-call signatures and undermines the new ADT ABI interop path.

Useful? React with 👍 / 👎.

Add mstore and tstore as first-class constructors in StmtCompileCore,
StmtListCompileCore, and StmtListTerminalCore, enabling these memory
and transient-storage operations to appear inside proven compile-core
sequences rather than only as singleton SupportedStmtList entries.
This is the highest-leverage proof surface widening for issue #1723.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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: a677c37841

ℹ️ 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 +477 to +478
| Expr.internalCall _ args | Expr.adtConstruct _ _ args =>
exprListContainsExternalCall args
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 Treat Expr.internalCall as effectful in external-call scans

exprContainsExternalCall currently treats Expr.internalCall as external-call-free unless its arguments contain a low-level call, so expression-form internal calls to helpers that perform externalCallBind/tryExternalCallBind/ecm are missed. Because both no_external_calls validation and CEI tracking flow through this scanner (via stmtContainsExternalCall), a function can bypass those checks by invoking an effectful internal helper in expression position (for example in letVar, assignVar, or RHS expressions) and still compile as compliant.

Useful? React with 👍 / 👎.

Internal calls (internalCall, internalCallAssign) have callee bodies that
are not visible at single-function validation scope. Previously:

1. `modifies(...)` only checked `stmtWrittenFields` which returns [] for
   call statements, silently allowing internal calls to write to any field.
2. `no_external_calls` only checked `stmtContainsExternalCall` which only
   looked at direct external calls, missing external calls inside callees.

Added two conservative validation functions:
- `stmtHasUntrackableWrites`: flags internal calls for modifies() validation
- `stmtMayContainExternalCall`: flags internal calls for no_external_calls

External calls (externalCallBind, tryExternalCallBind, ecm) are correctly
excluded from the modifies() check — they target other contracts and cannot
directly modify the current contract's storage fields.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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

| .scalar .address =>

P2 Badge Accept address newtypes in setStorageAddr translation

setStorageAddr only matches .scalar .address, so fields declared as address-based newtypes are rejected with "not Address" even though getStorageAddr already treats .newtype _ .address as valid. This makes address newtypes non-roundtrippable for storage updates and breaks the advertised zero-cost newtype behavior.

ℹ️ 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".

if bodyHasCall && bodyHasWrite then
some "loop body contains both external call and state write (subsequent iterations would violate CEI)"
else
match stmtListCEIViolation body seenCall with
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 Propagate loop-count calls into CEI body analysis

The CEI walker analyzes forEach bodies with stmtListCEIViolation body seenCall, but it does not include external calls that happen in the loop count expression. That lets patterns like forEach i (externalCall ...) do setStorage ... pass CEI even though the body writes after an interaction in the same statement. The loop-body check should receive a call-state that includes the count expression’s external-call status.

Useful? React with 👍 / 👎.

Comment thread Verity/Macro/Translate.lean Outdated
let f ← lookupStorageField fields (toString field.getId)
match f.ty with
| .scalar .uint256 =>
| .scalar .uint256 | .scalar (.adt _ _) =>
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 Allow uint-based newtypes in setStorage lowering

setStorage now accepts plain Uint256 and ADT fields, but not .newtype _ .uint256. As a result, storage fields declared with a uint-based newtype can be read (getStorage supports them) but cannot be written, so valid newtype contracts fail during macro translation when assigning to those fields.

Useful? React with 👍 / 👎.

…arbitrary

The statement-level compilation compatibility lemmas in GenericInduction.lean
previously hardcoded `[] []` for events and errors parameters. Since none of
the supported statement forms (letVar, assignVar, require, return, stop,
mstore, tstore, ite, setStorage, setMapping, etc.) consume events or errors
during compilation, these proofs hold for arbitrary event/error lists.

This generalization eliminates the need for Contract.lean to rewrite
`model.events → []` and `model.errors → []` in the compilation layer,
which is the foundational step toward removing `noEvents`/`noErrors`
from `SupportedSpecSurface` entirely once Function.lean is similarly
generalized.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread Compiler/CompileDriverCommon.lean
Comment thread Compiler/CompilationModel/Validation.lean
Comment thread Compiler/CompileDriverTest.lean
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: 1dc7cc87dc

ℹ️ 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 +451 to +453
def stmtHasUntrackableWrites : Stmt → Bool
| Stmt.internalCall _ _ | Stmt.internalCallAssign _ _ _ => true
| Stmt.ite _ thenBranch elseBranch =>
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 Flag expression internal calls as untrackable in modifies checks

The modifies(...) guard only treats statement-form internal calls as untrackable (Stmt.internalCall/Stmt.internalCallAssign), so internal helpers invoked inside expressions are missed. A function like setStorage declaredField (internalCall helper ...) can pass modifies validation even if helper writes additional storage fields, which makes generated _modifies/_frame guarantees unsound whenever expression-form internal calls are used.

Useful? React with 👍 / 👎.

Comment thread Verity/Macro/Translate.lean Outdated
ident := name
name := toString name.getId
params := ← params.mapM valueTypeFromSyntax
params := ← params.mapM (valueTypeFromSyntax newtypes #[])
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 Pass ADT declarations when parsing custom-error parameters

parseError parses parameter types with valueTypeFromSyntax newtypes #[], which drops all inductive ADT declarations at this boundary. As a result, error definitions that reference a declared ADT are rejected as unsupported types during parsing, so ADT-typed custom errors cannot be authored even though the rest of this change set adds ADT ABI/type plumbing.

Useful? React with 👍 / 👎.

- Support newtype-typed storage fields in setStorage/setStorageAddr
  (previously only reads worked; writes rejected newtypes as unknown type)
- Fix frame conjunct generation to use sameStorageAddrSlot for address
  newtypes (was incorrectly using sameStorageSlot)
- Add tstore to stmtIsPersistentWrite so transient storage writes
  trigger CEI violation detection
- Propagate external calls from ite conditions and forEach count
  expressions into CEI analysis (previously only body was checked)
- Add allow_post_interaction_writes to LowLevelTryCatchSmoke functions
  (pre-existing CEI violation from write-after-call in catch blocks)
- Add NewtypeStorageSmoke test contract exercising newtype-typed storage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread Compiler/CompilationModel/Validation.lean Outdated
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: f3514a8b57

ℹ️ 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".

let scrutineeExpr ← compileExpr fields dynamicSource scrutinee
-- Extract storage field name from scrutinee for field bindings
let storageFieldName ← match scrutinee with
| Expr.adtTag _ fieldName => pure fieldName
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 Validate matchAdt scrutinee type before codegen

Stmt.matchAdt drops the ADT name carried by Expr.adtTag (| Expr.adtTag _ fieldName => ...) and proceeds using only the storage field name. This allows mismatched IR such as matching ADT A while tagging a field of ADT B (or a non-ADT field), so case tags/field bindings are derived from A but reads come from a different slot layout. In that scenario codegen emits a semantically incorrect switch and sload(base+...) sequence instead of rejecting the statement; add a consistency check that the scrutinee’s ADT/field type matches adtName before compiling branches.

Useful? React with 👍 / 👎.

Add mstore and tstore induction alternatives to all StmtListCompileCore
and StmtListTerminalCore inductions that were missing them after the
compile-core fragment widening (a677c37). This fixes the CI build
failure on the `build` job.

Affected theorems:
- exec_compileStmtList_core (StmtListCompileCore, length+1 fuel)
- exec_compileStmtList_core_extraFuel (StmtListCompileCore, length+extraFuel+1 fuel)
- execStmtList_terminal_core_not_continue (StmtListTerminalCore, no IR)
- exec_compileStmtList_terminal_core_sizeOf_extraFuel (StmtListTerminalCore, sizeOf fuel)

Each mstore case: compile offset+value, show source continues with
updated memory, show IR continues with matching state, recurse on tail.
Each tstore case: same pattern but updating transientStorage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread Compiler/CompilationModel/Validation.lean
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: d1058a16d8

ℹ️ 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 +972 to +974
match field.ty with
| .scalar .address => pure field
| _ => throwErrorAt roleIdent s!"function '{toString fnIdent.getId}': requires({roleName}) must reference an Address-typed storage field, but '{roleName}' has a different type"
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 Accept address newtypes in requires(role) checks

resolveRoleField only accepts .scalar .address, so a role field declared as an address-backed newtype (for example types Owner : Address with owner : Owner) is rejected even though newtypes are otherwise erased and handled as their base types. This makes requires(...) unusable for valid address newtype storage fields and breaks the intended zero-cost newtype workflow for access control.

Useful? React with 👍 / 👎.

Comment thread Compiler/ABI.lean Outdated
let slot := f.slot.getD idx
let entry := "{" ++ joinJsonFields [
s!"\"name\": {jsonString f.name}",
s!"\"slot\": {toString slot}",
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 Serialize storage layout slots without precision loss

The storage layout writer emits slot as a bare JSON number. For namespaced contracts, slots are offset by a keccak-derived 256-bit namespace and routinely exceed IEEE-754 safe integer range, so common JSON consumers (especially JS-based tooling) will round these values and derive incorrect storage locations. Emit slots as strings (like storageNamespace) to preserve exact slot identities.

Useful? React with 👍 / 👎.

… newtype roles, slot precision)

- matchAdt CEI: propagate scrutinee external calls into branch analysis
- ADT custom errors: allow ParamType.adt in supportedCustomErrorParamType
- ADT error/external parsing: thread parsedAdts into parseError and parseExternal
  so ADT-typed parameters are recognized in error and external declarations
- requires(role): accept address-backed newtypes (.newtype _ .address)
- Storage layout JSON: emit slot as string to prevent JS precision loss on
  256-bit namespaced slot values

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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: 107d2c5aa7

ℹ️ 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 +301 to 303
| ParamType.uint256 | ParamType.int256 | ParamType.uint8 | ParamType.address | ParamType.bool | ParamType.bytes32
| ParamType.adt _ _ =>
let encoded ← encodeStaticCustomErrorArg errorDef.name ty argExpr
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 Route ADT custom-error args through composite encoding

This branch treats ParamType.adt as a scalar and forwards it to encodeStaticCustomErrorArg, but that helper only accepts uint/int/address/bool/bytes32 and throws on all other types. Since ADT custom-error params are now accepted by validation, requireError/revertError with an ADT argument will pass checks and then fail during codegen with an "unsupported non-scalar parameter type" error. ADT arguments need to be encoded via the composite path instead of the scalar helper.

Useful? React with 👍 / 👎.

Comment thread Verity/Macro/Bridge.lean
Comment on lines +149 to +150
| .mapping2AddressToAddressToUint256 | .mappingStruct2 _ _ _ =>
`(∀ k1 k2, s'.storageMap2 $slotLit k1 k2 = s.storageMap2 $slotLit k1 k2)
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 double-mapping frame predicates on hashed storage

The generated frame conjunct for both .mapping2AddressToAddressToUint256 and .mappingStruct2 only asserts equality of storageMap2, but compilation-model semantics for setMapping2/setStructMember2 update hashed slots in world.storage (via mapping-slot writes), not storageMap2. This means a function can modify a non-modifies double-mapping field while the generated _frame predicate still holds, making the effect guarantee unsound for those field types.

Useful? React with 👍 / 👎.

…heorems

The CI build was failing because four theorems in GenericInduction.lean
(stmtListCompileCore_of_scopeNamesIncluded, stmtListTerminalCore_of_scopeNamesIncluded,
stmtListGenericCore_of_stmtListCompileCore_of_scopeNamesIncluded,
stmtListGenericCore_of_stmtListTerminalCore_of_scopeNamesIncluded) were missing
match alternatives for the mstore and tstore constructors that were added to
StmtListCompileCore and StmtListTerminalCore in a previous commit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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: cd6181f463

ℹ️ 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 1675 to 1679
match f.ty with
| .scalar .uint256 => pure .uint256
| .scalar (.newtype ntName (.uint256)) => pure (.newtype ntName .uint256)
| .scalar (.newtype _ (.address)) => throwErrorAt rhs s!"field '{f.name}' is Address-based newtype; use getStorageAddr"
| .scalar .address => throwErrorAt rhs s!"field '{f.name}' is Address; use getStorageAddr"
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 Accept ADT storage fields in getStorage bind typing

inferBindSourceType rejects getStorage <field> unless the field is Uint256/newtype-Uint256, but ADT-typed storage fields are also lowered through Expr.storage in translateBindSource. As a result, valid code like let s ← getStorage myAdtField fails during translation/type inference with the mapping fallback error, even though ADT storage read support is otherwise present. Add a .scalar (.adt _ _) case here so ADT storage binds can type-check consistently.

Useful? React with 👍 / 👎.

Comment thread Compiler/CompilationModel/AbiEncoding.lean
…rs/adtTypes

The theorem was hardcoded to work only with empty events, errors, and
adtTypes lists, causing type mismatch errors in GenericInduction.lean
callers that pass general values. Generalizing the implicit parameters
fixes the CI build failure while remaining backwards-compatible with
all existing callers (which unify [] with the new implicit params).

Also cleans up several linter warnings: unnecessary simpa → simp/exact,
and simpa using → simp at for contradiction-based proofs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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: bfc3b59b4e

ℹ️ 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 +613 to +615
match s with
| Stmt.internalCall _ _ | Stmt.internalCallAssign _ _ _ => true
| _ => stmtContainsExternalCall s
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 Recurse conservative internal-call detection for no_external_calls

no_external_calls validation can be bypassed when an internal call is nested inside a compound statement (if, forEach, unsafeBlock, matchAdt). validateFunctionSpec checks spec.body.any stmtMayContainExternalCall, but stmtMayContainExternalCall only treats top-level Stmt.internalCall/Stmt.internalCallAssign as conservative and otherwise falls back to stmtContainsExternalCall, which does not conservatively flag nested internal calls. As a result, a function annotated no_external_calls can still pass validation while calling an internal helper that performs external calls, making the generated no-calls guarantee unsound in those control-flow shapes.

Useful? React with 👍 / 👎.

… suite

CEIWriteInBranchAfterCall, CEICallBothBranchesWrite, and UnsafeCEIViolation
are negative test contracts that deliberately violate CEI ordering (tested
via #guard_msgs in Smoke.lean). They should not be in the round-trip fuzz
macroSpecs list, which calls compileChecked and fails on validation errors.

Fixes the macro-fuzz CI shard failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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: 6e0a8df267

ℹ️ 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".

| .scalar (.tuple _) => throwError "storage fields cannot be Tuple; use mapping encodings"
| .scalar .unit => throwError "storage fields cannot be Unit"
| .scalar (.newtype _ baseType) => modelFieldTypeTerm (.scalar baseType) -- Erased to base type
| .scalar (.adt _ _) => `(Compiler.CompilationModel.FieldType.uint256) -- ADT stored as tag (uint256 slot)
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 Model ADT storage fields with full slot footprint

ADT storage is lowered as a tagged union spanning multiple consecutive slots (tag at base, payload at base+1...), but this mapping records every ADT storage field as FieldType.uint256 (single-slot). Because downstream layout checks and slot assignment reason over that model, overlaps in ADT payload slots are not detected (including auto-assigned immutables via nextUserSlot), so contracts can compile while matchAdt reads/writes slots that are also used by other fields. The storage model should reserve the full ADT slot range (e.g., dedicated ADT field shape or populated alias slots) so conflict detection and emitted layout remain sound.

Useful? React with 👍 / 👎.

…ge writes

The CEI (Checks-Effects-Interactions) validator previously used
`stmtIsPersistentWrite` which returned false for internal calls, so
a pattern like `externalCallBind(...); internalCall(helper, [...])` where
the helper writes storage would silently pass CEI validation.

Add `stmtMayPersistentlyWrite` — a conservative variant that treats
`internalCall` and `internalCallAssign` as potential persistent writes
(since their callee bodies are not visible at single-function scope).
Use it in the CEI write-after-call check.

Note: internal calls do NOT set `seenCall` for CEI purposes — each callee
function has its own CEI validation, so treating internal calls as both
interactions and writes would produce false positives on contracts like
DirectHelperCallSmoke.

Also fix a guard_pending state leak in generate_macro_property_tests.py
where `#guard_msgs in` / `#check_contract Foo` would incorrectly skip
the next `verity_contract` declaration (affected RolesCEISmoke,
ModifiesRolesSmoke, UnsafeCEICompliant property test generation).

Addresses P1 review threads #23 and #5 on PR #1731.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9b9a3d5. Configure here.

-- In a loop, if the body has both an external call and a state write,
-- a second iteration would violate CEI even if the first doesn't
let bodyHasCall := body.any stmtContainsExternalCall
let bodyHasWrite := body.any stmtIsPersistentWrite
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Loop CEI pre-check misses internal-call writes across iterations

Medium Severity

The forEach branch in stmtInternalCEIViolation uses body.any stmtIsPersistentWrite for its cross-iteration pre-check, but stmtIsPersistentWrite returns false for internal calls. The sequential analysis in stmtListCEIViolation correctly uses stmtMayPersistentlyWrite (which conservatively treats internal calls as potential writes). This inconsistency means a loop body like [internalCall(helper_that_writes), externalCallBind(...)] passes validation — the pre-check misses the internal-call write, and the single-iteration sequential analysis sees the write before the call. But on the second iteration, the internal call's storage write follows the previous iteration's external call, which is a CEI violation.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9b9a3d5. Configure here.

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: 9b9a3d5d33

ℹ️ 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".

-- Rung 2: cei_safe (machine-checked proof obligation)
-- Rung 3: nonreentrant(field) (known-safe reentrancy guard)
-- Rung 4: allow_post_interaction_writes (explicit trust-surface opt-out)
let ceiExempt := spec.allowPostInteractionWrites || spec.nonReentrantLock.isSome || spec.ceiSafe
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 Enforce nonreentrant guard before skipping CEI checks

This exempts any function with nonReentrantLock from CEI, but the new pipeline does not synthesize a lock acquire/release around the function body (the lock name is only carried as metadata/theorem input), so nonreentrant(lock) currently disables CEI without adding runtime protection. In practice, a function can perform externalCall followed by storage writes and be accepted as “guarded” while still being reentrancy-prone.

Useful? React with 👍 / 👎.

`parseContractSyntax` to respect custom `storage_namespace "key"`.
(#1730, Axis 4 Step 4a) -/
def mkStorageNamespaceCommand (contractName : String) (resolvedNamespace : Option Nat := none) : CommandElabM Cmd := do
let ns := resolvedNamespace.getD (computeStorageNamespace contractName)
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 Emit inactive namespace value when namespacing is off

For contracts without a storage_namespace clause, parseContractSyntax leaves slots unshifted and stores storageNamespace = none, but this fallback emits keccak(contractName.storage.v0) anyway. That makes Contract.storageNamespace disagree with the actual slot base (0) for non-namespaced contracts, so downstream slot-derivation tooling/proofs that trust this constant can compute incorrect storage locations.

Useful? React with 👍 / 👎.

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.

2 participants