Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
f5cac28
docs: add language design axes implementation plan
claude Apr 13, 2026
b4a13f8
feat(axis3): step 1a — auto-generate _is_view theorem for @[view] fun…
claude Apr 13, 2026
405f491
feat(axis3): step 1b — add modifies(...) annotation with _frame theorem
claude Apr 13, 2026
deca050
feat(axis3): step 1c — add no_external_calls annotation with _no_call…
claude Apr 13, 2026
d639daa
feat(axis3): step 1d — annotation composition with _effects conjuncti…
claude Apr 13, 2026
9fff4b4
feat(axis2): step 2a — CEI static analysis with allow_post_interactio…
claude Apr 13, 2026
b1fd6e3
feat(axis2): step 2b — CEI escalation ladder with nonreentrant and ce…
claude Apr 13, 2026
d3a5e48
feat(axis2): step 2c — requires(role) access control with _requires_r…
claude Apr 13, 2026
dc4b40d
feat(axis1): step 3a — types section for semantic newtypes
claude Apr 13, 2026
5581d65
feat(axis4): step 4a — storage namespace computation via kernel Keccak
claude Apr 13, 2026
6eb3ac1
feat(axis4): step 4b — opt-in storage_namespace slot offsetting
claude Apr 13, 2026
708472b
feat(axis4): step 4d — ABI + tooling integration for storage namespace
claude Apr 13, 2026
50e054d
feat(axis6): step 6a — unsafe "reason" do block syntax
claude Apr 13, 2026
e03fabe
feat(axis6): step 6b — restricted operation gating outside unsafe blocks
claude Apr 13, 2026
dc5aa48
feat(axis6): step 6c — trust report unsafe block entries + --deny-uns…
claude Apr 13, 2026
2f00daf
feat(axis1): step 5a — inductive section in grammar for ADT declarations
claude Apr 13, 2026
81ea925
feat(axis1): step 5b — ADT IR structures in compilation model
claude Apr 13, 2026
d2bd23b
feat(axis1): steps 5c+5d — ADT storage encoding + Yul lowering
claude Apr 13, 2026
8b0ffde
feat(axis1): step 5e — ADT ABI encoding as (uint8, uint256...) tuple
claude Apr 13, 2026
6024326
feat(axis4): step 4c — custom namespace override via storage_namespac…
claude Apr 13, 2026
f221af1
feat(axis1): step 5f — tryExternalCall for non-reverting external calls
claude Apr 13, 2026
fd384e5
feat(axis3): steps 3b+3c — ParamType.newtypeOf + ValueType.newtype wi…
claude Apr 13, 2026
a7cd308
fix: resolve pre-existing build failures in Smoke.lean + IRInterprete…
claude Apr 13, 2026
23f3fbf
feat(axis1): wire ADT type definitions from syntax to ContractSpec (#…
claude Apr 13, 2026
e47cfbd
fix: address PR review bugs from Codex and Bugbot (#1731)
claude Apr 14, 2026
940ff7a
fix: address round 2 review bugs (ADT dispatch, match validation, nam…
claude Apr 14, 2026
14a9375
fix: address round 3 review bugs (CEI false positive, ABI components,…
claude Apr 14, 2026
d254c20
test: add 14 stress-test contracts + fix ADT storage bug
claude Apr 14, 2026
933b6de
fix: address round 4 review bugs (IRInterpreter proof, ABI newtypes, …
claude Apr 14, 2026
01507da
fix: add ADT expr cases to exprBoundNames (ExprCore.lean)
claude Apr 14, 2026
d3ec7a5
fix: extend SupportedSpec proof infrastructure for ADT/unsafe/tryExte…
claude Apr 15, 2026
de34a5b
fix: address remaining Bugbot review issues (round 5)
claude Apr 15, 2026
962d000
fix: update proof infrastructure for adtTypes parameter in compilatio…
claude Apr 15, 2026
a677c37
feat: widen compile-core fragment with mstore/tstore statement support
claude Apr 15, 2026
7ace9cc
fix: close soundness gaps in modifies() and no_external_calls validation
claude Apr 15, 2026
1dc7cc8
feat: generalize GenericInduction proofs from empty events/errors to …
claude Apr 15, 2026
f3514a8
fix: close newtype storage write gaps and harden CEI validation
claude Apr 15, 2026
d1058a1
feat: complete mstore/tstore proof cases across 4 FunctionBody theorems
claude Apr 15, 2026
107d2c5
fix: address round 6 review bugs (CEI matchAdt, ADT errors/externals,…
claude Apr 15, 2026
cd6181f
fix: add missing mstore/tstore alternatives in scope-widening proof t…
claude Apr 15, 2026
bfc3b59
fix: generalize compileStmtList_cons_ok_inv for arbitrary events/erro…
claude Apr 15, 2026
6e0a8df
fix: exclude intentionally-invalid CEI contracts from round-trip fuzz…
claude Apr 15, 2026
9b9a3d5
fix: harden CEI validation to treat internal calls as potential stora…
claude Apr 15, 2026
11817c3
Merge main into language design axes
claude Apr 21, 2026
ef8d0e0
chore: auto-refresh derived artifacts
github-actions[bot] Apr 21, 2026
a64fac3
fix: close remaining local PR1731 validation gaps
claude Apr 21, 2026
50431a0
fix: harden language axes validation gaps
claude Apr 21, 2026
c8da981
fix: cover ADT fields in source semantics array scans
claude Apr 21, 2026
56daaba
fix: reject unsupported ADT expression construction
claude Apr 21, 2026
db0ad8d
fix: close latest language axes review findings
claude Apr 21, 2026
f96dee2
refactor: use shared ADT storage helpers
claude Apr 21, 2026
f04ed78
fix: bound ADT variant tags
claude Apr 21, 2026
14230a2
chore: refresh macro property artifact
claude Apr 21, 2026
7ffa44d
feat: add native evmyullean lowering scaffold
claude Apr 21, 2026
33bf07f
fix: encode ADT event payloads
claude Apr 21, 2026
4436753
fix: close ADT storage and CEI review gaps
claude Apr 22, 2026
f8a2ffd
chore: auto-refresh derived artifacts
github-actions[bot] Apr 22, 2026
c5f815d
fix: repair native harness proof integration
claude Apr 22, 2026
1c9ab6a
chore: auto-refresh derived artifacts
github-actions[bot] Apr 22, 2026
0db2ac9
fix: close ADT alias and payload-local review gaps
claude Apr 22, 2026
586db31
fix: validate namespace and nonreentrant annotations
claude Apr 22, 2026
b7e90f8
fix: project native evmyullean runtime observables
claude Apr 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions Compiler/ABI.lean
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ private def abiTypeString : ParamType → String
| .tuple _ => "tuple"
| .array t => abiTypeString t ++ "[]"
| .fixedArray t n => abiTypeString t ++ "[" ++ toString n ++ "]"
| .adt _ _ => "tuple" -- ADTs are ABI-encoded as static tuples
Comment thread
Th0rgal marked this conversation as resolved.
| .newtypeOf _ baseType => abiTypeString baseType -- Erased to base type

-- Uses `fieldTypeToParamType` from CompilationModel (shared, not duplicated).
-- Uses `isInteropEntrypointName` from CompilationModel for consistent filtering.
Expand All @@ -39,6 +41,13 @@ mutual
| .tuple elems =>
let rendered := elems.map (fun ty => renderParam "" ty none)
some ("[" ++ String.intercalate ", " rendered ++ "]")
| .adt _ maxFields =>
-- ADTs encode as (uint8 tag, uint256 field₀, ..., uint256 fieldₙ)
let tagComponent := renderParam "tag" .uint8 none
let fieldComponents := List.range maxFields |>.map fun i =>
renderParam s!"field{i}" .uint256 none
some ("[" ++ String.intercalate ", " (tagComponent :: fieldComponents) ++ "]")
Comment thread
cursor[bot] marked this conversation as resolved.
| .newtypeOf _ baseType => abiComponents? baseType
| .array t => abiComponents? t
| .fixedArray t _ => abiComponents? t
| _ => none
Expand Down Expand Up @@ -126,4 +135,50 @@ def writeContractABIFile (outDir : String) (spec : CompilationModel) : IO Unit :
let path := s!"{outDir}/{spec.name}.abi.json"
IO.FS.writeFile path (emitContractABIJson spec)

/-- Render the storage layout for a contract as a JSON object.
Includes EIP-7201 namespace when present (#1730, Axis 4 Step 4d).
The output is a JSON object with `"contract"`, `"storageNamespace"`,
and `"fields"` keys. -/
def emitContractStorageLayoutJson (spec : CompilationModel) : String :=
let nsTerm := match spec.storageNamespace with
| some ns => jsonString (toString ns)
| none => "null"
let fieldEntries := renderFields spec.fields 0
"{" ++ joinJsonFields [
s!"\"contract\": {jsonString spec.name}",
s!"\"storageNamespace\": {nsTerm}",
s!"\"fields\": [{String.intercalate ", " fieldEntries}]"
] ++ "}\n"
where
renderFieldType : FieldType → String
| .uint256 => "uint256"
| .address => "address"
| .adt name maxFields => s!"adt({name},{maxFields})"
| .dynamicArray elemType => renderStorageArrayElemType elemType ++ "[]"
| .mappingTyped _ => "mapping"
| .mappingStruct _ _ => "mapping"
| .mappingStruct2 _ _ _ => "mapping"
Comment thread
Th0rgal marked this conversation as resolved.
Comment thread
cursor[bot] marked this conversation as resolved.
renderStorageArrayElemType : StorageArrayElemType → String
| .uint256 => "uint256"
| .address => "address"
| .bool => "bool"
| .uint8 => "uint8"
| .bytes32 => "bytes32"
renderFields (fields : List Field) (idx : Nat) : List String :=
match fields with
| [] => []
| f :: rest =>
let slot := f.slot.getD idx
let entry := "{" ++ joinJsonFields [
s!"\"name\": {jsonString f.name}",
s!"\"slot\": {jsonString (toString slot)}",
s!"\"type\": {jsonString (renderFieldType f.ty)}"
] ++ "}"
entry :: renderFields rest (idx + 1)

def writeContractStorageLayoutFile (outDir : String) (spec : CompilationModel) : IO Unit := do
IO.FS.createDirAll outDir
let path := s!"{outDir}/{spec.name}.storage.json"
IO.FS.writeFile path (emitContractStorageLayoutJson spec)

end Compiler.ABI
1 change: 1 addition & 0 deletions Compiler/CompilationModel.lean
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import Compiler.CompilationModel.Types
import Compiler.CompilationModel.AbiHelpers
import Compiler.CompilationModel.AbiTypeLayout
import Compiler.CompilationModel.AdtStorageLayout
import Compiler.CompilationModel.AbiEncoding
import Compiler.CompilationModel.DynamicData
import Compiler.CompilationModel.EcmAxiomCollection
Expand Down
38 changes: 38 additions & 0 deletions Compiler/CompilationModel/AbiEncoding.lean
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,25 @@ partial def compileUnindexedAbiEncode
])
], YulExpr.call "add" [YulExpr.lit 32, YulExpr.ident paddedName])

| ParamType.adt _ maxFields =>
-- ADTs are ABI-encoded as (uint8 tag, uint256 field0, ..., uint256 fieldN)
-- Tag word: load and mask to uint8
let tagLoaded := dynamicWordLoad dynamicSource srcBase
let tagStore := YulStmt.expr (YulExpr.call "mstore" [
dstBase, YulExpr.call "and" [tagLoaded, YulExpr.lit 0xFF]
])
-- Field words: load consecutive words from source
let fieldStores := (List.range maxFields).map fun i =>
let srcOff := YulExpr.call "add" [srcBase, YulExpr.lit ((i + 1) * 32)]
let dstOff := YulExpr.call "add" [dstBase, YulExpr.lit ((i + 1) * 32)]
YulStmt.expr (YulExpr.call "mstore" [dstOff, dynamicWordLoad dynamicSource srcOff])
let totalBytes := 32 * (1 + maxFields)
pure (tagStore :: fieldStores, YulExpr.lit totalBytes)

| ParamType.newtypeOf _ baseType =>
-- Newtypes erased to base type (#1727 Step 3b)
compileUnindexedAbiEncode dynamicSource baseType srcBase dstBase stem

def revertWithCustomError (dynamicSource : DynamicDataSource)
(errorDef : ErrorDef) (sourceArgs : List Expr) (args : List YulExpr) :
Except String (List YulStmt) := do
Expand Down Expand Up @@ -282,6 +301,25 @@ def revertWithCustomError (dynamicSource : DynamicDataSource)
| ParamType.uint256 | ParamType.int256 | ParamType.uint8 | ParamType.address | ParamType.bool | ParamType.bytes32 =>
let encoded ← encodeStaticCustomErrorArg errorDef.name ty argExpr
pure [YulStmt.expr (YulExpr.call "mstore" [YulExpr.lit headOffset, encoded])]
| ParamType.adt _ maxFields =>
match srcExpr with
| Expr.param name =>
let tagStore := YulStmt.expr (YulExpr.call "mstore" [
YulExpr.lit headOffset,
YulExpr.call "and" [argExpr, YulExpr.lit 0xFF]
])
let fieldStores := (List.range maxFields).map fun fieldIdx =>
YulStmt.expr (YulExpr.call "mstore" [
YulExpr.lit (headOffset + (fieldIdx + 1) * 32),
YulExpr.ident s!"{name}_f{fieldIdx}"
])
pure (tagStore :: fieldStores)
| _ =>
throw s!"Compilation error: custom error '{errorDef.name}' parameter of type {repr ty} currently requires direct parameter reference ({issue586Ref})."
| ParamType.newtypeOf _ baseType =>
-- Newtypes erased to base type (#1727 Step 3b)
let encoded ← encodeStaticCustomErrorArg errorDef.name baseType argExpr
pure [YulStmt.expr (YulExpr.call "mstore" [YulExpr.lit headOffset, encoded])]
| ParamType.tuple _ | ParamType.fixedArray _ _ =>
match srcExpr with
| Expr.param name =>
Expand Down
5 changes: 5 additions & 0 deletions Compiler/CompilationModel/AbiHelpers.lean
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ mutual
| ParamType.array t => paramTypeToSolidityString t ++ "[]"
| ParamType.fixedArray t n => paramTypeToSolidityString t ++ "[" ++ toString n ++ "]"
| ParamType.bytes => "bytes"
| ParamType.adt _name maxFields =>
-- ABI-encoded as static tuple: (uint8, uint256, ..., uint256)
"(" ++ String.intercalate "," ("uint8" :: List.replicate maxFields "uint256") ++ ")"
| ParamType.newtypeOf _ baseType => paramTypeToSolidityString baseType -- Erased to base type

private def paramTypeListToSolidityStrings : List ParamType → List String
| [] => []
Expand All @@ -79,6 +83,7 @@ def storageArrayElemTypeToParamType : StorageArrayElemType → ParamType
def fieldTypeToParamType : FieldType → ParamType
| FieldType.uint256 => ParamType.uint256
| FieldType.address => ParamType.address
| FieldType.adt name maxFields => ParamType.adt name maxFields
| FieldType.dynamicArray elemType => ParamType.array (storageArrayElemTypeToParamType elemType)
| FieldType.mappingTyped _ => ParamType.uint256
| FieldType.mappingStruct _ _ => ParamType.uint256
Expand Down
4 changes: 4 additions & 0 deletions Compiler/CompilationModel/AbiTypeLayout.lean
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ mutual
| ParamType.bytes => true
| ParamType.fixedArray elemTy _ => isDynamicParamType elemTy
| ParamType.tuple elemTys => isDynamicParamTypeList elemTys
| ParamType.adt _ _ => false -- ADTs are statically-sized tagged unions
| ParamType.newtypeOf _ baseType => isDynamicParamType baseType -- Erased to base type
termination_by ty => sizeOf ty

def isDynamicParamTypeList : List ParamType → Bool
Expand All @@ -43,6 +45,8 @@ mutual
if isDynamicParamType elemTy then 32 else n * paramHeadSize elemTy
| ParamType.tuple elemTys =>
if isDynamicParamTypeList elemTys then 32 else paramHeadSizeList elemTys
| ParamType.adt _ maxFields => 32 * (1 + maxFields) -- ADTs: uint8 tag word + maxFields field words (#1727 Step 5e)
| ParamType.newtypeOf _ baseType => paramHeadSize baseType -- Erased to base type
termination_by ty => sizeOf ty

def paramHeadSizeList : List ParamType → Nat
Expand Down
75 changes: 75 additions & 0 deletions Compiler/CompilationModel/AdtStorageLayout.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/-
Compiler.CompilationModel.AdtStorageLayout: Storage layout helpers for ADTs

ADT storage encoding uses a tagged-union layout:
- Slot 0 (relative): tag byte (uint8, identifies the variant)
- Slots 1..N (relative): max-width fields in consecutive slots

The total storage footprint is 1 + max(variant field counts).
All variants share the same field slots — unused trailing slots
are simply not read/written for shorter variants.

(#1727, Phase 5 Steps 5c+5d)
-/
import Compiler.CompilationModel.Types

namespace Compiler.CompilationModel

open Compiler.Yul

/-- Look up an ADT type definition by name. -/
def lookupAdtTypeDef (adtTypes : List AdtTypeDef) (name : String) :
Except String AdtTypeDef :=
match adtTypes.find? (·.name == name) with
| some def_ => pure def_
| none => throw s!"Compilation error: unknown ADT type '{name}'"

/-- Look up a variant within an ADT type definition by name. -/
def lookupAdtVariant (def_ : AdtTypeDef) (variantName : String) :
Except String AdtVariant :=
match def_.variants.find? (·.name == variantName) with
| some v => pure v
| none => throw s!"Compilation error: unknown variant '{variantName}' in ADT '{def_.name}'"

/-- Maximum number of field slots across all variants. -/
def adtMaxFieldSlots (def_ : AdtTypeDef) : Nat :=
def_.variants.foldl (fun acc v => max acc v.fields.length) 0

/-! ### Yul compilation helpers for ADT storage operations

These generate Yul AST fragments for reading/writing ADT values
stored in contract storage. The caller provides the base slot
(resolved from the contract's field list) and the ADT type info.
-/

/-- Read the tag byte from an ADT's storage slot.
`baseSlot` is the Yul expression for the ADT field's first slot. -/
def compileAdtTagRead (baseSlot : YulExpr) : YulExpr :=
-- Tag is stored as a full word in the base slot (mask to uint8)
YulExpr.call "and" [
YulExpr.call "sload" [baseSlot],
YulExpr.lit 0xFF
]

/-- Write the tag byte to an ADT's storage base slot. -/
def compileAdtTagWrite (baseSlot : YulExpr) (tagValue : Nat) : YulStmt :=
YulStmt.expr (YulExpr.call "sstore" [baseSlot, YulExpr.lit tagValue])

/-- Read a field from an ADT variant in storage.
`baseSlot` is the ADT's first slot, `fieldIndex` is 0-based within the variant.
Fields occupy slots base+1, base+2, ... -/
def compileAdtFieldRead (baseSlot : YulExpr) (fieldIndex : Nat) : YulExpr :=
YulExpr.call "sload" [
YulExpr.call "add" [baseSlot, YulExpr.lit (fieldIndex + 1)]
]

/-- Write a field value into an ADT variant's storage slot.
`baseSlot` is the ADT's first slot, `fieldIndex` is 0-based. -/
def compileAdtFieldWrite (baseSlot : YulExpr) (fieldIndex : Nat)
(valueExpr : YulExpr) : YulStmt :=
YulStmt.expr (YulExpr.call "sstore" [
YulExpr.call "add" [baseSlot, YulExpr.lit (fieldIndex + 1)],
valueExpr
])
Comment thread
cursor[bot] marked this conversation as resolved.
Comment thread
cursor[bot] marked this conversation as resolved.

end Compiler.CompilationModel
Loading
Loading