diff --git a/.github/workflows/evmyullean-fork-conformance.yml b/.github/workflows/evmyullean-fork-conformance.yml index e93f4d6f6..bc762f3e7 100644 --- a/.github/workflows/evmyullean-fork-conformance.yml +++ b/.github/workflows/evmyullean-fork-conformance.yml @@ -3,9 +3,10 @@ name: EVMYulLean fork conformance # Weekly probe that the pinned lfglabs-dev/EVMYulLean fork still # satisfies Verity's bridge assumptions: the fork audit and adapter # report artifacts are in sync, adapter correctness still builds, -# all universal bridge lemmas still build, the public EndToEnd -# EVMYulLean target still builds, and all 123 concrete `native_decide` -# bridge-equivalence tests still pass. +# all universal bridge lemmas still build, the native transition harness still +# builds, emitted dispatcher native/reference oracle coverage still passes, the +# public EndToEnd EVMYulLean target still builds, and all 123 concrete +# `native_decide` bridge-equivalence tests still pass. # # Burn-in: the probe step is captured with `continue-on-error: true` # during the two-week window starting 2026-04-20 (see issue #1722 plan @@ -36,6 +37,9 @@ on: - 'Compiler/Proofs/YulGeneration/Backends/EvmYulLeanBodyClosure.lean' - 'Compiler/Proofs/YulGeneration/Backends/EvmYulLeanBridgeLemmas.lean' - 'Compiler/Proofs/YulGeneration/Backends/EvmYulLeanBridgeTest.lean' + - 'Compiler/Proofs/YulGeneration/Backends/EvmYulLeanNativeHarness.lean' + - 'Compiler/Proofs/YulGeneration/Backends/EvmYulLeanNativeDispatchOracleTest.lean' + - 'Compiler/Proofs/YulGeneration/Backends/EvmYulLeanNativeSmokeTest.lean' - 'Compiler/Proofs/YulGeneration/Backends/EvmYulLeanRetarget.lean' - 'Compiler/Proofs/YulGeneration/Backends/EvmYulLeanSignedArithSpec.lean' - 'Compiler/Proofs/YulGeneration/Backends/EvmYulLeanSourceExprClosure.lean' @@ -58,6 +62,9 @@ on: - 'Compiler/Proofs/YulGeneration/Backends/EvmYulLeanBodyClosure.lean' - 'Compiler/Proofs/YulGeneration/Backends/EvmYulLeanBridgeLemmas.lean' - 'Compiler/Proofs/YulGeneration/Backends/EvmYulLeanBridgeTest.lean' + - 'Compiler/Proofs/YulGeneration/Backends/EvmYulLeanNativeHarness.lean' + - 'Compiler/Proofs/YulGeneration/Backends/EvmYulLeanNativeDispatchOracleTest.lean' + - 'Compiler/Proofs/YulGeneration/Backends/EvmYulLeanNativeSmokeTest.lean' - 'Compiler/Proofs/YulGeneration/Backends/EvmYulLeanRetarget.lean' - 'Compiler/Proofs/YulGeneration/Backends/EvmYulLeanSignedArithSpec.lean' - 'Compiler/Proofs/YulGeneration/Backends/EvmYulLeanSourceExprClosure.lean' @@ -160,7 +167,7 @@ jobs: "make test-evmyullean-fork", "```", "", - "This guard checks the pinned EVMYulLean fork audit, checks the adapter report, rebuilds adapter correctness and the public EndToEnd target, and runs the concrete bridge-equivalence tests." + "This guard checks the pinned EVMYulLean fork audit, checks the adapter report, rebuilds adapter correctness, the native transition harness, and the public EndToEnd target, and runs the concrete bridge-equivalence tests." ]; const body = bodyLines.join("\n"); diff --git a/AXIOMS.md b/AXIOMS.md index 765edc874..9ca1a057d 100644 --- a/AXIOMS.md +++ b/AXIOMS.md @@ -117,8 +117,9 @@ the EVM. **Soundness controls**: - `make check` validates the fork-audit artifact against `lake-manifest.json`. - `make test-evmyullean-fork` rechecks the fork audit, checks the adapter - report, rebuilds adapter correctness and the public EndToEnd EVMYulLean - target, and runs the concrete `native_decide` bridge-equivalence tests. + report, rebuilds adapter correctness, the native transition harness, the + public EndToEnd EVMYulLean target, and the concrete `native_decide` + bridge-equivalence tests. - `.github/workflows/evmyullean-fork-conformance.yml` runs the conformance probe weekly; after the burn-in ending 2026-05-04, scheduled/manual failures fail the workflow and open or update a GitHub issue for drift triage. diff --git a/Compiler/CompilationModel/AbiHelpers.lean b/Compiler/CompilationModel/AbiHelpers.lean index bcd8743d6..6ddd01903 100644 --- a/Compiler/CompilationModel/AbiHelpers.lean +++ b/Compiler/CompilationModel/AbiHelpers.lean @@ -73,6 +73,10 @@ def eventSignature (eventDef : EventDef) : String := def errorSignature (errorDef : ErrorDef) : String := s!"{errorDef.name}(" ++ String.intercalate "," (errorDef.params.map paramTypeToSolidityString) ++ ")" +def functionSignature (fn : FunctionSpec) : String := + let params := fn.params.map (fun p => paramTypeToSolidityString p.ty) + s!"{fn.name}(" ++ String.intercalate "," params ++ ")" + def storageArrayElemTypeToParamType : StorageArrayElemType → ParamType | .uint256 => .uint256 | .address => .address diff --git a/Compiler/CompilationModel/Compile.lean b/Compiler/CompilationModel/Compile.lean index 9d20ed588..ee77d2c70 100644 --- a/Compiler/CompilationModel/Compile.lean +++ b/Compiler/CompilationModel/Compile.lean @@ -126,6 +126,16 @@ def compileStmt (fields : List Field) (events : List EventDef := []) compileSetStorage fields dynamicSource field value | Stmt.setStorageAddr field value => compileSetStorage fields dynamicSource field value true + | Stmt.setStorageWord field wordOffset value => + match findFieldWithResolvedSlot fields field with + | some (_f, slot) => do + let valueExpr ← compileExpr fields dynamicSource value + let slotExpr := + if wordOffset == 0 then YulExpr.lit slot + else YulExpr.call "add" [YulExpr.lit slot, YulExpr.lit wordOffset] + pure [YulStmt.expr (YulExpr.call "sstore" [slotExpr, valueExpr])] + | none => + throw s!"Compilation error: unknown storage field '{field}' in setStorageWord" | Stmt.storageArrayPush field value => compileStorageArrayPush fields dynamicSource field value | Stmt.storageArrayPop field => diff --git a/Compiler/CompilationModel/Dispatch.lean b/Compiler/CompilationModel/Dispatch.lean index 33413d73c..a29f7ce52 100644 --- a/Compiler/CompilationModel/Dispatch.lean +++ b/Compiler/CompilationModel/Dispatch.lean @@ -278,9 +278,14 @@ private def validateCompileInputsBeforeFieldWriteConflict for ext in spec.externals do let _ ← externalFunctionReturns ext validateInteropExternalSpec ext - match firstDuplicateName (spec.functions.map (·.name)) with + match firstDuplicateName ((spec.functions.filter (fun fn => !fn.isInternal)).map functionSignature) with | some dup => - throw s!"Compilation error: duplicate function name '{dup}' in {spec.name}" + throw s!"Compilation error: duplicate function signature '{dup}' in {spec.name}" + | none => + pure () + match firstDuplicateName ((spec.functions.filter (·.isInternal)).map (·.name)) with + | some dup => + throw s!"Compilation error: duplicate internal function name '{dup}' in {spec.name}; internal function Yul definitions are keyed by name" | none => pure () match firstDuplicateName (spec.errors.map (·.name)) with @@ -352,11 +357,13 @@ def validateCompileInputs (spec : CompilationModel) (selectors : List Nat) : Exc | none => pure () let mappingHelpersRequired := usesMapping fields - let arrayHelpersRequired := contractUsesArrayElement spec + let arrayHelpersRequired := contractUsesPlainArrayElement spec + let arrayElementWordHelpersRequired := contractUsesArrayElementWord spec let storageArrayHelpersRequired := contractUsesStorageArrayElement spec let dynamicBytesEqHelpersRequired := contractUsesDynamicBytesEq spec match firstReservedExternalCollision - spec mappingHelpersRequired arrayHelpersRequired storageArrayHelpersRequired dynamicBytesEqHelpersRequired with + spec mappingHelpersRequired arrayHelpersRequired arrayElementWordHelpersRequired + storageArrayHelpersRequired dynamicBytesEqHelpersRequired with | some name => if name.startsWith internalFunctionPrefix then throw s!"Compilation error: external declaration '{name}' uses reserved prefix '{internalFunctionPrefix}' ({issue756Ref})." @@ -380,7 +387,8 @@ def compileValidatedCore (spec : CompilationModel) (selectors : List Nat) : Exce let externalFns := spec.functions.filter (fun fn => !fn.isInternal && !isInteropEntrypointName fn.name) let internalFns := spec.functions.filter (·.isInternal) let mappingHelpersRequired := usesMapping fields - let arrayHelpersRequired := contractUsesArrayElement spec + let arrayHelpersRequired := contractUsesPlainArrayElement spec + let arrayElementWordHelpersRequired := contractUsesArrayElementWord spec let storageArrayHelpersRequired := contractUsesStorageArrayElement spec let dynamicBytesEqHelpersRequired := contractUsesDynamicBytesEq spec let fallbackSpec ← pickUniqueFunctionByName "fallback" spec.functions @@ -389,10 +397,18 @@ def compileValidatedCore (spec : CompilationModel) (selectors : List Nat) : Exce compileFunctionSpec fields spec.events spec.errors spec.adtTypes sel fnSpec let internalFuncDefs ← internalFns.mapM (compileInternalFunction fields spec.events spec.errors spec.adtTypes) let arrayElementHelpers := - if arrayHelpersRequired then - [checkedArrayElementCalldataHelper, checkedArrayElementMemoryHelper] + (if arrayHelpersRequired then + [ checkedArrayElementCalldataHelper + , checkedArrayElementMemoryHelper + ] else - [] + []) ++ + (if arrayElementWordHelpersRequired then + [ checkedArrayElementWordCalldataHelper + , checkedArrayElementWordMemoryHelper + ] + else + []) let storageArrayElementHelpers := if storageArrayHelpersRequired then [checkedStorageArrayElementHelper] diff --git a/Compiler/CompilationModel/DynamicData.lean b/Compiler/CompilationModel/DynamicData.lean index 5f3716b41..a3864729d 100644 --- a/Compiler/CompilationModel/DynamicData.lean +++ b/Compiler/CompilationModel/DynamicData.lean @@ -20,6 +20,12 @@ def checkedArrayElementCalldataHelperName : String := def checkedArrayElementMemoryHelperName : String := "__verity_array_element_memory_checked" +def checkedArrayElementWordCalldataHelperName : String := + "__verity_array_element_word_calldata_checked" + +def checkedArrayElementWordMemoryHelperName : String := + "__verity_array_element_word_memory_checked" + def checkedStorageArrayElementHelperName : String := "__verity_storage_array_element_checked" @@ -50,6 +56,33 @@ def checkedArrayElementCalldataHelper : YulStmt := def checkedArrayElementMemoryHelper : YulStmt := checkedArrayElementHelper checkedArrayElementMemoryHelperName "mload" +private def checkedArrayElementWordHelper (helperName loadOp : String) : YulStmt := + let elementWordIndex := + YulExpr.call "add" [ + YulExpr.call "mul" [YulExpr.ident "index", YulExpr.ident "element_words"], + YulExpr.ident "word_offset" + ] + let byteOffset := YulExpr.call "mul" [elementWordIndex, YulExpr.lit 32] + YulStmt.funcDef helperName ["data_offset", "length", "index", "element_words", "word_offset"] ["word"] [ + YulStmt.if_ (YulExpr.call "iszero" [ + YulExpr.call "lt" [YulExpr.ident "index", YulExpr.ident "length"] + ]) [ + YulStmt.expr (YulExpr.call "revert" [YulExpr.lit 0, YulExpr.lit 0]) + ], + YulStmt.assign "word" (YulExpr.call loadOp [ + YulExpr.call "add" [ + YulExpr.ident "data_offset", + byteOffset + ] + ]) + ] + +def checkedArrayElementWordCalldataHelper : YulStmt := + checkedArrayElementWordHelper checkedArrayElementWordCalldataHelperName "calldataload" + +def checkedArrayElementWordMemoryHelper : YulStmt := + checkedArrayElementWordHelper checkedArrayElementWordMemoryHelperName "mload" + def checkedStorageArrayElementHelper : YulStmt := YulStmt.funcDef checkedStorageArrayElementHelperName ["slot", "index"] ["word"] [ YulStmt.let_ "__array_len" (YulExpr.call "sload" [YulExpr.ident "slot"]), diff --git a/Compiler/CompilationModel/ExpressionCompile.lean b/Compiler/CompilationModel/ExpressionCompile.lean index 7dfdfbc46..e9364c0c3 100644 --- a/Compiler/CompilationModel/ExpressionCompile.lean +++ b/Compiler/CompilationModel/ExpressionCompile.lean @@ -257,6 +257,23 @@ def compileExpr (fields : List Field) YulExpr.ident s!"{name}_length", indexExpr ]) + | Expr.arrayElementWord name index elementWords wordOffset => do + if elementWords == 0 then + throw s!"Compilation error: Expr.arrayElementWord '{name}' requires elementWords > 0" + else if wordOffset >= elementWords then + throw s!"Compilation error: Expr.arrayElementWord '{name}' wordOffset {wordOffset} is outside element width {elementWords}" + else + let indexExpr ← compileExpr fields dynamicSource index + let helperName := match dynamicSource with + | .calldata => checkedArrayElementWordCalldataHelperName + | .memory => checkedArrayElementWordMemoryHelperName + pure (YulExpr.call helperName [ + YulExpr.ident s!"{name}_data_offset", + YulExpr.ident s!"{name}_length", + indexExpr, + YulExpr.lit elementWords, + YulExpr.lit wordOffset + ]) | Expr.storageArrayLength field => match findFieldWithResolvedSlot fields field with | some (f, slot) => diff --git a/Compiler/CompilationModel/LogicalPurity.lean b/Compiler/CompilationModel/LogicalPurity.lean index ffa07dfdb..73c3fe801 100644 --- a/Compiler/CompilationModel/LogicalPurity.lean +++ b/Compiler/CompilationModel/LogicalPurity.lean @@ -19,7 +19,8 @@ partial def exprContainsCallLike (expr : Expr) : Bool := | Expr.structMember2 _ key1 key2 _ => exprContainsCallLike key1 || exprContainsCallLike key2 | Expr.storageArrayElement _ index - | Expr.arrayElement _ index => + | Expr.arrayElement _ index + | Expr.arrayElementWord _ index _ _ => exprContainsCallLike index | Expr.dynamicBytesEq _ _ => false @@ -110,7 +111,8 @@ def exprContainsUnsafeLogicalCallLike (expr : Expr) : Bool := | Expr.mapping2 _ key1 key2 | Expr.mapping2Word _ key1 key2 _ | Expr.structMember2 _ key1 key2 _ => exprContainsUnsafeLogicalCallLike key1 || exprContainsUnsafeLogicalCallLike key2 - | Expr.storageArrayElement _ index | Expr.arrayElement _ index | Expr.returndataOptionalBoolAt index => + | Expr.storageArrayElement _ index | Expr.arrayElement _ index + | Expr.arrayElementWord _ index _ _ | Expr.returndataOptionalBoolAt index => exprContainsUnsafeLogicalCallLike index | Expr.dynamicBytesEq _ _ => false @@ -152,7 +154,8 @@ termination_by es => sizeOf es decreasing_by all_goals simp_wf; all_goals omega def stmtContainsUnsafeLogicalCallLike : Stmt → Bool - | Stmt.letVar _ value | Stmt.assignVar _ value | Stmt.setStorage _ value | Stmt.setStorageAddr _ value | + | Stmt.letVar _ value | Stmt.assignVar _ value | Stmt.setStorage _ value | Stmt.setStorageAddr _ value + | Stmt.setStorageWord _ _ value | Stmt.storageArrayPush _ value | Stmt.return value | Stmt.require value _ => exprContainsUnsafeLogicalCallLike value diff --git a/Compiler/CompilationModel/ParamLoading.lean b/Compiler/CompilationModel/ParamLoading.lean index 6a00ef216..521b3ddbc 100644 --- a/Compiler/CompilationModel/ParamLoading.lean +++ b/Compiler/CompilationModel/ParamLoading.lean @@ -15,6 +15,12 @@ def isScalarParamType : ParamType → Bool | ParamType.uint256 | ParamType.int256 | ParamType.uint8 | ParamType.address | ParamType.bool | ParamType.bytes32 => true | _ => false +private def dynamicArrayElementStrideWords (elemTy : ParamType) : Nat := + if isDynamicParamType elemTy then + 1 + else + max 1 (paramHeadSize elemTy / 32) + private def genDynamicParamLoads (loadWord : YulExpr → YulExpr) (sizeExpr : YulExpr) (headSize : Nat) (baseOffset : Nat) (name : String) (ty : ParamType) (headOffset : Nat) : @@ -56,10 +62,11 @@ private def genDynamicParamLoads ]) [ YulStmt.expr (YulExpr.call "revert" [YulExpr.lit 0, YulExpr.lit 0]) ]] - | ParamType.array _ => + | ParamType.array elemTy => + let elemWords := dynamicArrayElementStrideWords elemTy [YulStmt.if_ (YulExpr.call "gt" [ YulExpr.ident s!"{name}_length", - YulExpr.call "div" [YulExpr.ident tailRemainingName, YulExpr.lit 32] + YulExpr.call "div" [YulExpr.ident tailRemainingName, YulExpr.lit (32 * elemWords)] ]) [ YulStmt.expr (YulExpr.call "revert" [YulExpr.lit 0, YulExpr.lit 0]) ]] diff --git a/Compiler/CompilationModel/ScopeValidation.lean b/Compiler/CompilationModel/ScopeValidation.lean index b39497178..9e452798d 100644 --- a/Compiler/CompilationModel/ScopeValidation.lean +++ b/Compiler/CompilationModel/ScopeValidation.lean @@ -131,6 +131,26 @@ def validateScopedExprIdentifiers | none => throw s!"Compilation error: {context} references unknown parameter '{name}' in Expr.arrayElement" validateScopedExprIdentifiers context params paramScope dynamicParams localScope constructorArgCount index + | Expr.arrayElementWord name index elementWords wordOffset => do + if elementWords == 0 then + throw s!"Compilation error: {context} Expr.arrayElementWord '{name}' requires elementWords > 0" + else if wordOffset >= elementWords then + throw s!"Compilation error: {context} Expr.arrayElementWord '{name}' wordOffset {wordOffset} is outside element width {elementWords}" + match findParamType params name with + | some ty@(ParamType.array elemTy) => + if isDynamicParamType elemTy then + throw s!"Compilation error: {context} Expr.arrayElementWord '{name}' requires an array parameter with static ABI-word elements, got {repr ty}" + else + let expectedWords := paramHeadSize elemTy / 32 + if elementWords == expectedWords then + pure () + else + throw s!"Compilation error: {context} Expr.arrayElementWord '{name}' element width {elementWords} does not match ABI width {expectedWords} for {repr ty}" + | some ty => + throw s!"Compilation error: {context} Expr.arrayElementWord '{name}' requires array parameter, got {repr ty}" + | none => + throw s!"Compilation error: {context} references unknown parameter '{name}' in Expr.arrayElementWord" + validateScopedExprIdentifiers context params paramScope dynamicParams localScope constructorArgCount index | Expr.mapping _ key | Expr.mappingWord _ key _ | Expr.mappingPackedWord _ key _ _ | Expr.mappingUint _ key | Expr.structMember _ key _ => validateScopedExprIdentifiers context params paramScope dynamicParams localScope constructorArgCount key @@ -263,7 +283,8 @@ def validateScopedStmtIdentifiers throw s!"Compilation error: {context} assigns to undeclared local variable '{name}'" validateScopedExprIdentifiers context params paramScope dynamicParams localScope constructorArgCount value pure localScope - | Stmt.setStorage _ value | Stmt.setStorageAddr _ value | Stmt.return value | Stmt.require value _ => do + | Stmt.setStorage _ value | Stmt.setStorageAddr _ value | Stmt.setStorageWord _ _ value + | Stmt.return value | Stmt.require value _ => do validateScopedExprIdentifiers context params paramScope dynamicParams localScope constructorArgCount value pure localScope | Stmt.storageArrayPush _ value => do diff --git a/Compiler/CompilationModel/TrustSurface.lean b/Compiler/CompilationModel/TrustSurface.lean index 9d80836e2..1c3c984bb 100644 --- a/Compiler/CompilationModel/TrustSurface.lean +++ b/Compiler/CompilationModel/TrustSurface.lean @@ -58,7 +58,8 @@ private partial def collectLowLevelExprMechanics : Expr → List String collectLowLevelExprMechanics key1 ++ collectLowLevelExprMechanics key2 | .mappingUint _ key | .storageArrayElement _ key - | .arrayElement _ key => + | .arrayElement _ key + | .arrayElementWord _ key _ _ => collectLowLevelExprMechanics key | .mload key => ["mload"] ++ collectLowLevelExprMechanics key @@ -121,7 +122,8 @@ private partial def collectAxiomatizedExprPrimitives : Expr → List String collectAxiomatizedExprPrimitives key1 ++ collectAxiomatizedExprPrimitives key2 | .mappingUint _ key | .storageArrayElement _ key - | .arrayElement _ key => + | .arrayElement _ key + | .arrayElementWord _ key _ _ => collectAxiomatizedExprPrimitives key | .externalCall _ args | .internalCall _ args => @@ -147,6 +149,7 @@ private partial def collectLowLevelStmtMechanics : Stmt → List String | .assignVar _ value | .setStorage _ value | .setStorageAddr _ value + | .setStorageWord _ _ value | .storageArrayPush _ value | .return value | .require value _ => @@ -211,6 +214,7 @@ private partial def collectAxiomatizedStmtPrimitives : Stmt → List String | .assignVar _ value | .setStorage _ value | .setStorageAddr _ value + | .setStorageWord _ _ value | .storageArrayPush _ value | .return value | .require value _ => @@ -296,6 +300,7 @@ private partial def collectUnguardedLowLevelStmtMechanics : Stmt → List String | .assignVar _ value | .setStorage _ value | .setStorageAddr _ value + | .setStorageWord _ _ value | .storageArrayPush _ value | .return value | .require value _ => @@ -446,7 +451,8 @@ private partial def collectEventEmissionExprMechanics : Expr → List String | .mappingChain _ keys => keys.flatMap collectEventEmissionExprMechanics | .mappingUint _ key - | .arrayElement _ key => + | .arrayElement _ key + | .arrayElementWord _ key _ _ => collectEventEmissionExprMechanics key | .add a b | .sub a b | .mul a b | .div a b | .sdiv a b | .mod a b | .smod a b | .bitAnd a b | .bitOr a b | .bitXor a b | .shl a b | .shr a b | .sar a b | .signextend a b @@ -470,6 +476,7 @@ private partial def collectEventEmissionStmtMechanics : Stmt → List String | .assignVar _ value | .setStorage _ value | .setStorageAddr _ value + | .setStorageWord _ _ value | .storageArrayPush _ value | .return value | .require value _ => @@ -605,7 +612,8 @@ private partial def collectRuntimeIntrospectionExprMechanics : Expr → List Str | .mappingChain _ keys => keys.flatMap collectRuntimeIntrospectionExprMechanics | .mappingUint _ key - | .arrayElement _ key => + | .arrayElement _ key + | .arrayElementWord _ key _ _ => collectRuntimeIntrospectionExprMechanics key | .add a b | .sub a b | .mul a b | .div a b | .sdiv a b | .mod a b | .smod a b | .bitAnd a b | .bitOr a b | .bitXor a b | .shl a b | .shr a b | .sar a b | .signextend a b @@ -629,6 +637,7 @@ private partial def collectRuntimeIntrospectionStmtMechanics : Stmt → List Str | .assignVar _ value | .setStorage _ value | .setStorageAddr _ value + | .setStorageWord _ _ value | .storageArrayPush _ value | .return value | .require value _ => @@ -752,7 +761,8 @@ private partial def collectExternalExprNames : Expr → List String | .mappingChain _ keys => keys.flatMap collectExternalExprNames | .mappingUint _ key - | .arrayElement _ key => + | .arrayElement _ key + | .arrayElementWord _ key _ _ => collectExternalExprNames key | .internalCall _ args => args.flatMap collectExternalExprNames @@ -771,11 +781,39 @@ private partial def collectExternalExprNames : Expr → List String | _ => [] +private def arrayElementWordLowLevelIndexSmoke : Expr := + Expr.arrayElementWord "cuts" + (Expr.call (Expr.literal 5000) (Expr.literal 1) (Expr.literal 0) + (Expr.literal 0) (Expr.literal 0) (Expr.literal 0) (Expr.literal 32)) + 3 1 + +private def arrayElementWordAxiomatizedIndexSmoke : Expr := + Expr.arrayElementWord "cuts" (Expr.keccak256 (Expr.literal 0) (Expr.literal 64)) 3 1 + +private def arrayElementWordRuntimeIndexSmoke : Expr := + Expr.arrayElementWord "cuts" Expr.blockNumber 3 1 + +private def arrayElementWordExternalIndexSmoke : Expr := + Expr.arrayElementWord "cuts" (Expr.externalCall "oracle" []) 3 1 + +example : collectLowLevelExprMechanics arrayElementWordLowLevelIndexSmoke = ["call"] := by + native_decide + +example : collectAxiomatizedExprPrimitives arrayElementWordAxiomatizedIndexSmoke = ["keccak256"] := by + native_decide + +example : collectRuntimeIntrospectionExprMechanics arrayElementWordRuntimeIndexSmoke = ["blockNumber"] := by + native_decide + +example : collectExternalExprNames arrayElementWordExternalIndexSmoke = ["oracle"] := by + native_decide + private partial def collectExternalStmtNames : Stmt → List String | .letVar _ value | .assignVar _ value | .setStorage _ value | .setStorageAddr _ value + | .setStorageWord _ _ value | .storageArrayPush _ value | .return value | .require value _ => diff --git a/Compiler/CompilationModel/Types.lean b/Compiler/CompilationModel/Types.lean index 49a3cf3ad..91e7d4947 100644 --- a/Compiler/CompilationModel/Types.lean +++ b/Compiler/CompilationModel/Types.lean @@ -338,6 +338,10 @@ inductive Expr | internalCall (functionName : String) (args : List Expr) -- Internal function call (#181) | arrayLength (name : String) -- Length of a dynamic array parameter (#180) | arrayElement (name : String) (index : Expr) -- Checked element access of a dynamic array parameter (revert on out-of-range) (#180) + /-- Checked word access inside a dynamic array element. `elementWords` is the + static ABI word width of one element and `wordOffset` is the word inside + that element. This supports arrays of static tuple/struct-like values. -/ + | arrayElementWord (name : String) (index : Expr) (elementWords wordOffset : Nat) | storageArrayLength (field : String) -- Read the length word of a storage dynamic array (#1571) | storageArrayElement (field : String) (index : Expr) -- Checked element access of a storage dynamic array (#1571) /-- Equality on direct `bytes` / `string` parameters loaded from calldata or memory. @@ -413,6 +417,10 @@ inductive Stmt | assignVar (name : String) (value : Expr) -- Reassign existing variable | setStorage (field : String) (value : Expr) | setStorageAddr (field : String) (value : Expr) + /-- Write a full storage word at `field.slot + wordOffset`. Intended for + migration-faithful manual packed-word writes where the source constructs + the packed word explicitly. -/ + | setStorageWord (field : String) (wordOffset : Nat) (value : Expr) | storageArrayPush (field : String) (value : Expr) -- Append to a storage dynamic array (#1571) | storageArrayPop (field : String) -- Pop from a storage dynamic array (#1571) | setStorageArrayElement (field : String) (index : Expr) (value : Expr) -- Indexed write (#1571) diff --git a/Compiler/CompilationModel/UsageAnalysis.lean b/Compiler/CompilationModel/UsageAnalysis.lean index 4e93919ee..3e0ff21bf 100644 --- a/Compiler/CompilationModel/UsageAnalysis.lean +++ b/Compiler/CompilationModel/UsageAnalysis.lean @@ -18,7 +18,7 @@ def collectStmtBindNames : Stmt → List String | Stmt.tryExternalCallBind successVar resultVars _ _ => successVar :: resultVars | Stmt.ecm mod _ => mod.resultVars -- Statements that never bind new names. - | Stmt.assignVar _ _ | Stmt.setStorage _ _ | Stmt.setStorageAddr _ _ + | Stmt.assignVar _ _ | Stmt.setStorage _ _ | Stmt.setStorageAddr _ _ | Stmt.setStorageWord _ _ _ | Stmt.storageArrayPush _ _ | Stmt.storageArrayPop _ | Stmt.setStorageArrayElement _ _ _ | Stmt.return _ | Stmt.setMapping _ _ _ | Stmt.setMappingWord _ _ _ _ | Stmt.setMappingPackedWord _ _ _ _ _ | Stmt.setMappingUint _ _ _ @@ -59,7 +59,7 @@ def collectStmtAssignedNames : Stmt → List String collectStmtListAssignedNames body | Stmt.matchAdt _ _ branches => collectMatchBranchAssignedNames branches - | Stmt.letVar _ _ | Stmt.setStorage _ _ | Stmt.setStorageAddr _ _ + | Stmt.letVar _ _ | Stmt.setStorage _ _ | Stmt.setStorageAddr _ _ | Stmt.setStorageWord _ _ _ | Stmt.storageArrayPush _ _ | Stmt.storageArrayPop _ | Stmt.setStorageArrayElement _ _ _ | Stmt.return _ | Stmt.setMapping _ _ _ | Stmt.setMappingWord _ _ _ _ | Stmt.setMappingPackedWord _ _ _ _ _ | Stmt.setMappingUint _ _ _ @@ -90,44 +90,231 @@ termination_by bs => sizeOf bs decreasing_by all_goals simp_wf; all_goals omega end +mutual +def exprUsesArrayElementKind (includePlain includeWord : Bool) : Expr → Bool + | Expr.arrayElement _ index => + let nested := exprUsesArrayElementKind includePlain includeWord index + if nested then true else includePlain + | Expr.arrayElementWord _ index _ _ => + let nested := exprUsesArrayElementKind includePlain includeWord index + if nested then true else includeWord + | Expr.mapping _ key => exprUsesArrayElementKind includePlain includeWord key + | Expr.mappingWord _ key _ => exprUsesArrayElementKind includePlain includeWord key + | Expr.mappingPackedWord _ key _ _ => exprUsesArrayElementKind includePlain includeWord key + | Expr.mappingChain _ keys => exprListUsesArrayElementKind includePlain includeWord keys + | Expr.structMember _ key _ => exprUsesArrayElementKind includePlain includeWord key + | Expr.mapping2 _ key1 key2 | Expr.mapping2Word _ key1 key2 _ + | Expr.structMember2 _ key1 key2 _ => + exprUsesArrayElementKind includePlain includeWord key1 || + exprUsesArrayElementKind includePlain includeWord key2 + | Expr.mappingUint _ key => exprUsesArrayElementKind includePlain includeWord key + | Expr.call gas target value inOffset inSize outOffset outSize => + exprUsesArrayElementKind includePlain includeWord gas || + exprUsesArrayElementKind includePlain includeWord target || + exprUsesArrayElementKind includePlain includeWord value || + exprUsesArrayElementKind includePlain includeWord inOffset || + exprUsesArrayElementKind includePlain includeWord inSize || + exprUsesArrayElementKind includePlain includeWord outOffset || + exprUsesArrayElementKind includePlain includeWord outSize + | Expr.staticcall gas target inOffset inSize outOffset outSize => + exprUsesArrayElementKind includePlain includeWord gas || + exprUsesArrayElementKind includePlain includeWord target || + exprUsesArrayElementKind includePlain includeWord inOffset || + exprUsesArrayElementKind includePlain includeWord inSize || + exprUsesArrayElementKind includePlain includeWord outOffset || + exprUsesArrayElementKind includePlain includeWord outSize + | Expr.delegatecall gas target inOffset inSize outOffset outSize => + exprUsesArrayElementKind includePlain includeWord gas || + exprUsesArrayElementKind includePlain includeWord target || + exprUsesArrayElementKind includePlain includeWord inOffset || + exprUsesArrayElementKind includePlain includeWord inSize || + exprUsesArrayElementKind includePlain includeWord outOffset || + exprUsesArrayElementKind includePlain includeWord outSize + | Expr.extcodesize addr => exprUsesArrayElementKind includePlain includeWord addr + | Expr.mload offset => exprUsesArrayElementKind includePlain includeWord offset + | Expr.tload offset => exprUsesArrayElementKind includePlain includeWord offset + | Expr.calldataload offset => exprUsesArrayElementKind includePlain includeWord offset + | Expr.keccak256 offset size => + exprUsesArrayElementKind includePlain includeWord offset || + exprUsesArrayElementKind includePlain includeWord size + | Expr.returndataOptionalBoolAt outOffset => + exprUsesArrayElementKind includePlain includeWord outOffset + | Expr.externalCall _ args | Expr.internalCall _ args => + exprListUsesArrayElementKind includePlain includeWord args + | Expr.storageArrayElement _ index => + exprUsesArrayElementKind includePlain includeWord index + | Expr.dynamicBytesEq _ _ => + false + | Expr.add a b | Expr.sub a b | Expr.mul a b | Expr.div a b | Expr.sdiv a b + | Expr.mod a b | Expr.smod a b | + Expr.bitAnd a b | Expr.bitOr a b | Expr.bitXor a b | Expr.shl a b | Expr.shr a b + | Expr.sar a b | Expr.signextend a b | + Expr.eq a b | Expr.ge a b | Expr.gt a b | Expr.sgt a b | Expr.lt a b | Expr.slt a b | Expr.le a b | + Expr.logicalAnd a b | Expr.logicalOr a b | + Expr.wMulDown a b | Expr.wDivUp a b | Expr.min a b | Expr.max a b | + Expr.ceilDiv a b => + exprUsesArrayElementKind includePlain includeWord a || + exprUsesArrayElementKind includePlain includeWord b + | Expr.mulDivDown a b c | Expr.mulDivUp a b c => + exprUsesArrayElementKind includePlain includeWord a || + exprUsesArrayElementKind includePlain includeWord b || + exprUsesArrayElementKind includePlain includeWord c + | Expr.bitNot a | Expr.logicalNot a => + exprUsesArrayElementKind includePlain includeWord a + | Expr.ite cond thenVal elseVal => + exprUsesArrayElementKind includePlain includeWord cond || + exprUsesArrayElementKind includePlain includeWord thenVal || + exprUsesArrayElementKind includePlain includeWord elseVal + | Expr.adtConstruct _ _ args => exprListUsesArrayElementKind includePlain includeWord args + | Expr.adtField _ _ _ _ _ => false + | Expr.literal _ | Expr.param _ | Expr.constructorArg _ | Expr.storage _ | Expr.storageAddr _ + | Expr.caller | Expr.contractAddress | Expr.chainid | Expr.msgValue | Expr.blockTimestamp + | Expr.blockNumber | Expr.blobbasefee + | Expr.calldatasize | Expr.returndataSize | Expr.localVar _ | Expr.arrayLength _ + | Expr.storageArrayLength _ + | Expr.adtTag _ _ => + false +termination_by e => sizeOf e +decreasing_by all_goals simp_wf; all_goals omega + +def exprListUsesArrayElementKind (includePlain includeWord : Bool) : List Expr → Bool + | [] => false + | e :: es => + exprUsesArrayElementKind includePlain includeWord e || + exprListUsesArrayElementKind includePlain includeWord es +termination_by es => sizeOf es +decreasing_by all_goals simp_wf; all_goals omega + +def stmtUsesArrayElementKind (includePlain includeWord : Bool) : Stmt → Bool + | Stmt.letVar _ value | Stmt.assignVar _ value | Stmt.setStorage _ value | Stmt.setStorageAddr _ value + | Stmt.setStorageWord _ _ value | + Stmt.storageArrayPush _ value | + Stmt.return value | Stmt.require value _ => + exprUsesArrayElementKind includePlain includeWord value + | Stmt.setStorageArrayElement _ index value => + exprUsesArrayElementKind includePlain includeWord index || + exprUsesArrayElementKind includePlain includeWord value + | Stmt.storageArrayPop _ => + false + | Stmt.requireError cond _ args => + exprUsesArrayElementKind includePlain includeWord cond || + exprListUsesArrayElementKind includePlain includeWord args + | Stmt.revertError _ args | Stmt.emit _ args | Stmt.returnValues args => + exprListUsesArrayElementKind includePlain includeWord args + | Stmt.mstore offset value => + exprUsesArrayElementKind includePlain includeWord offset || + exprUsesArrayElementKind includePlain includeWord value + | Stmt.tstore offset value => + exprUsesArrayElementKind includePlain includeWord offset || + exprUsesArrayElementKind includePlain includeWord value + | Stmt.calldatacopy destOffset sourceOffset size => + exprUsesArrayElementKind includePlain includeWord destOffset || + exprUsesArrayElementKind includePlain includeWord sourceOffset || + exprUsesArrayElementKind includePlain includeWord size + | Stmt.returndataCopy destOffset sourceOffset size => + exprUsesArrayElementKind includePlain includeWord destOffset || + exprUsesArrayElementKind includePlain includeWord sourceOffset || + exprUsesArrayElementKind includePlain includeWord size + | Stmt.setMapping _ key value | Stmt.setMappingWord _ key _ value | Stmt.setMappingPackedWord _ key _ _ value | Stmt.setMappingUint _ key value + | Stmt.setStructMember _ key _ value => + exprUsesArrayElementKind includePlain includeWord key || + exprUsesArrayElementKind includePlain includeWord value + | Stmt.setMappingChain _ keys value => + exprListUsesArrayElementKind includePlain includeWord keys || + exprUsesArrayElementKind includePlain includeWord value + | Stmt.setMapping2 _ key1 key2 value | Stmt.setMapping2Word _ key1 key2 _ value + | Stmt.setStructMember2 _ key1 key2 _ value => + exprUsesArrayElementKind includePlain includeWord key1 || + exprUsesArrayElementKind includePlain includeWord key2 || + exprUsesArrayElementKind includePlain includeWord value + | Stmt.ite cond thenBranch elseBranch => + exprUsesArrayElementKind includePlain includeWord cond || + stmtListUsesArrayElementKind includePlain includeWord thenBranch || + stmtListUsesArrayElementKind includePlain includeWord elseBranch + | Stmt.forEach _ count body => + exprUsesArrayElementKind includePlain includeWord count || + stmtListUsesArrayElementKind includePlain includeWord body + | Stmt.unsafeBlock _ body => + stmtListUsesArrayElementKind includePlain includeWord body + | Stmt.matchAdt _ scrutinee branches => + exprUsesArrayElementKind includePlain includeWord scrutinee || + matchBranchesUseArrayElementKind includePlain includeWord branches + | Stmt.internalCall _ args | Stmt.internalCallAssign _ _ args => + exprListUsesArrayElementKind includePlain includeWord args + | Stmt.rawLog topics dataOffset dataSize => + exprListUsesArrayElementKind includePlain includeWord topics || + exprUsesArrayElementKind includePlain includeWord dataOffset || + exprUsesArrayElementKind includePlain includeWord dataSize + | Stmt.externalCallBind _ _ args | Stmt.tryExternalCallBind _ _ _ args => + exprListUsesArrayElementKind includePlain includeWord args + | Stmt.ecm _ args => + exprListUsesArrayElementKind includePlain includeWord args + | Stmt.returnArray _ | Stmt.returnBytes _ | Stmt.returnStorageWords _ + | Stmt.revertReturndata | Stmt.stop => + false +termination_by s => sizeOf s +decreasing_by all_goals simp_wf; all_goals omega + +def stmtListUsesArrayElementKind (includePlain includeWord : Bool) : List Stmt → Bool + | [] => false + | s :: ss => + stmtUsesArrayElementKind includePlain includeWord s || + stmtListUsesArrayElementKind includePlain includeWord ss +termination_by ss => sizeOf ss +decreasing_by all_goals simp_wf; all_goals omega + +def matchBranchesUseArrayElementKind + (includePlain includeWord : Bool) : List (String × List String × List Stmt) → Bool + | [] => false + | (_, _, body) :: rest => + stmtListUsesArrayElementKind includePlain includeWord body || + matchBranchesUseArrayElementKind includePlain includeWord rest +termination_by bs => sizeOf bs +decreasing_by all_goals simp_wf; all_goals omega +end + +attribute [simp] exprUsesArrayElementKind exprListUsesArrayElementKind + stmtUsesArrayElementKind stmtListUsesArrayElementKind matchBranchesUseArrayElementKind + +@[simp] theorem stmtListUsesArrayElementKind_nil (includePlain includeWord : Bool) : + stmtListUsesArrayElementKind includePlain includeWord [] = false := by + simp [stmtListUsesArrayElementKind] + +@[simp] theorem stmtListUsesArrayElementKind_cons + (includePlain includeWord : Bool) (s : Stmt) (ss : List Stmt) : + stmtListUsesArrayElementKind includePlain includeWord (s :: ss) = + (stmtUsesArrayElementKind includePlain includeWord s || + stmtListUsesArrayElementKind includePlain includeWord ss) := by + simp [stmtListUsesArrayElementKind] + mutual def exprUsesArrayElement : Expr → Bool - | Expr.arrayElement _ _ => true - | Expr.mapping _ key => exprUsesArrayElement key - | Expr.mappingWord _ key _ => exprUsesArrayElement key - | Expr.mappingPackedWord _ key _ _ => exprUsesArrayElement key + | Expr.arrayElement _ _ | Expr.arrayElementWord _ _ _ _ => + true + | Expr.mapping _ key | Expr.mappingWord _ key _ | Expr.mappingPackedWord _ key _ _ + | Expr.mappingUint _ key | Expr.structMember _ key _ => + exprUsesArrayElement key | Expr.mappingChain _ keys => exprListUsesArrayElement keys - | Expr.structMember _ key _ => exprUsesArrayElement key | Expr.mapping2 _ key1 key2 | Expr.mapping2Word _ key1 key2 _ - | Expr.structMember2 _ key1 key2 _ => exprUsesArrayElement key1 || exprUsesArrayElement key2 - | Expr.mappingUint _ key => exprUsesArrayElement key + | Expr.structMember2 _ key1 key2 _ => + exprUsesArrayElement key1 || exprUsesArrayElement key2 | Expr.call gas target value inOffset inSize outOffset outSize => exprUsesArrayElement gas || exprUsesArrayElement target || exprUsesArrayElement value || exprUsesArrayElement inOffset || exprUsesArrayElement inSize || exprUsesArrayElement outOffset || exprUsesArrayElement outSize - | Expr.staticcall gas target inOffset inSize outOffset outSize => - exprUsesArrayElement gas || exprUsesArrayElement target || - exprUsesArrayElement inOffset || exprUsesArrayElement inSize || - exprUsesArrayElement outOffset || exprUsesArrayElement outSize + | Expr.staticcall gas target inOffset inSize outOffset outSize | Expr.delegatecall gas target inOffset inSize outOffset outSize => exprUsesArrayElement gas || exprUsesArrayElement target || exprUsesArrayElement inOffset || exprUsesArrayElement inSize || exprUsesArrayElement outOffset || exprUsesArrayElement outSize - | Expr.extcodesize addr => exprUsesArrayElement addr - | Expr.mload offset => - exprUsesArrayElement offset - | Expr.tload offset => - exprUsesArrayElement offset - | Expr.calldataload offset => - exprUsesArrayElement offset + | Expr.extcodesize addr | Expr.mload addr | Expr.tload addr | Expr.calldataload addr + | Expr.returndataOptionalBoolAt addr | Expr.storageArrayElement _ addr => + exprUsesArrayElement addr | Expr.keccak256 offset size => exprUsesArrayElement offset || exprUsesArrayElement size - | Expr.returndataOptionalBoolAt outOffset => exprUsesArrayElement outOffset - | Expr.externalCall _ args | Expr.internalCall _ args => + | Expr.externalCall _ args | Expr.internalCall _ args | Expr.adtConstruct _ _ args => exprListUsesArrayElement args - | Expr.storageArrayElement _ index => - exprUsesArrayElement index - | Expr.dynamicBytesEq _ _ => + | Expr.dynamicBytesEq _ _ | Expr.adtField _ _ _ _ _ => false | Expr.add a b | Expr.sub a b | Expr.mul a b | Expr.div a b | Expr.sdiv a b | Expr.mod a b | Expr.smod a b | @@ -144,13 +331,11 @@ def exprUsesArrayElement : Expr → Bool exprUsesArrayElement a | Expr.ite cond thenVal elseVal => exprUsesArrayElement cond || exprUsesArrayElement thenVal || exprUsesArrayElement elseVal - | Expr.adtConstruct _ _ args => exprListUsesArrayElement args - | Expr.adtField _ _ _ _ _ => false - -- Leaf expressions: no sub-expressions that could contain arrayElement. | Expr.literal _ | Expr.param _ | Expr.constructorArg _ | Expr.storage _ | Expr.storageAddr _ | Expr.caller | Expr.contractAddress | Expr.chainid | Expr.msgValue | Expr.blockTimestamp | Expr.blockNumber | Expr.blobbasefee - | Expr.calldatasize | Expr.returndataSize | Expr.localVar _ | Expr.arrayLength _ | Expr.storageArrayLength _ + | Expr.calldatasize | Expr.returndataSize | Expr.localVar _ | Expr.arrayLength _ + | Expr.storageArrayLength _ | Expr.adtTag _ _ => false termination_by e => sizeOf e @@ -163,9 +348,9 @@ termination_by es => sizeOf es decreasing_by all_goals simp_wf; all_goals omega def stmtUsesArrayElement : Stmt → Bool - | Stmt.letVar _ value | Stmt.assignVar _ value | Stmt.setStorage _ value | Stmt.setStorageAddr _ value | - Stmt.storageArrayPush _ value | - Stmt.return value | Stmt.require value _ => + | Stmt.letVar _ value | Stmt.assignVar _ value | Stmt.setStorage _ value | Stmt.setStorageAddr _ value + | Stmt.setStorageWord _ _ value | Stmt.storageArrayPush _ value + | Stmt.return value | Stmt.require value _ => exprUsesArrayElement value | Stmt.setStorageArrayElement _ index value => exprUsesArrayElement index || exprUsesArrayElement value @@ -175,15 +360,13 @@ def stmtUsesArrayElement : Stmt → Bool exprUsesArrayElement cond || exprListUsesArrayElement args | Stmt.revertError _ args | Stmt.emit _ args | Stmt.returnValues args => exprListUsesArrayElement args - | Stmt.mstore offset value => - exprUsesArrayElement offset || exprUsesArrayElement value - | Stmt.tstore offset value => + | Stmt.mstore offset value | Stmt.tstore offset value => exprUsesArrayElement offset || exprUsesArrayElement value - | Stmt.calldatacopy destOffset sourceOffset size => - exprUsesArrayElement destOffset || exprUsesArrayElement sourceOffset || exprUsesArrayElement size + | Stmt.calldatacopy destOffset sourceOffset size | Stmt.returndataCopy destOffset sourceOffset size => exprUsesArrayElement destOffset || exprUsesArrayElement sourceOffset || exprUsesArrayElement size - | Stmt.setMapping _ key value | Stmt.setMappingWord _ key _ value | Stmt.setMappingPackedWord _ key _ _ value | Stmt.setMappingUint _ key value + | Stmt.setMapping _ key value | Stmt.setMappingWord _ key _ value + | Stmt.setMappingPackedWord _ key _ _ value | Stmt.setMappingUint _ key value | Stmt.setStructMember _ key _ value => exprUsesArrayElement key || exprUsesArrayElement value | Stmt.setMappingChain _ keys value => @@ -192,23 +375,20 @@ def stmtUsesArrayElement : Stmt → Bool | Stmt.setStructMember2 _ key1 key2 _ value => exprUsesArrayElement key1 || exprUsesArrayElement key2 || exprUsesArrayElement value | Stmt.ite cond thenBranch elseBranch => - exprUsesArrayElement cond || stmtListUsesArrayElement thenBranch || stmtListUsesArrayElement elseBranch + exprUsesArrayElement cond || stmtListUsesArrayElement thenBranch || + stmtListUsesArrayElement elseBranch | Stmt.forEach _ count body => exprUsesArrayElement count || stmtListUsesArrayElement body | Stmt.unsafeBlock _ body => stmtListUsesArrayElement body | Stmt.matchAdt _ scrutinee branches => - exprUsesArrayElement scrutinee || - matchBranchesUseArrayElement branches + exprUsesArrayElement scrutinee || matchBranchesUseArrayElement branches | Stmt.internalCall _ args | Stmt.internalCallAssign _ _ args => exprListUsesArrayElement args | Stmt.rawLog topics dataOffset dataSize => exprListUsesArrayElement topics || exprUsesArrayElement dataOffset || exprUsesArrayElement dataSize - | Stmt.externalCallBind _ _ args | Stmt.tryExternalCallBind _ _ _ args => + | Stmt.externalCallBind _ _ args | Stmt.tryExternalCallBind _ _ _ args | Stmt.ecm _ args => exprListUsesArrayElement args - | Stmt.ecm _ args => - exprListUsesArrayElement args - -- Leaf statements: no sub-expressions that could contain arrayElement. | Stmt.returnArray _ | Stmt.returnBytes _ | Stmt.returnStorageWords _ | Stmt.revertReturndata | Stmt.stop => false @@ -223,8 +403,7 @@ decreasing_by all_goals simp_wf; all_goals omega def matchBranchesUseArrayElement : List (String × List String × List Stmt) → Bool | [] => false - | (_, _, body) :: rest => - stmtListUsesArrayElement body || matchBranchesUseArrayElement rest + | (_, _, body) :: rest => stmtListUsesArrayElement body || matchBranchesUseArrayElement rest termination_by bs => sizeOf bs decreasing_by all_goals simp_wf; all_goals omega end @@ -239,6 +418,76 @@ def constructorUsesArrayElement : Option ConstructorSpec → Bool def contractUsesArrayElement (spec : CompilationModel) : Bool := constructorUsesArrayElement spec.constructor || spec.functions.any functionUsesArrayElement +abbrev exprUsesPlainArrayElement : Expr → Bool := + exprUsesArrayElementKind true false + +abbrev exprListUsesPlainArrayElement : List Expr → Bool := + exprListUsesArrayElementKind true false + +abbrev stmtUsesPlainArrayElement : Stmt → Bool := + stmtUsesArrayElementKind true false + +abbrev stmtListUsesPlainArrayElement : List Stmt → Bool := + stmtListUsesArrayElementKind true false + +abbrev matchBranchesUsePlainArrayElement : List (String × List String × List Stmt) → Bool := + matchBranchesUseArrayElementKind true false + +def functionUsesPlainArrayElement (fn : FunctionSpec) : Bool := + fn.body.any stmtUsesPlainArrayElement + +def constructorUsesPlainArrayElement : Option ConstructorSpec → Bool + | none => false + | some ctor => ctor.body.any stmtUsesPlainArrayElement + +def contractUsesPlainArrayElement (spec : CompilationModel) : Bool := + contractUsesArrayElement spec && + (constructorUsesPlainArrayElement spec.constructor || spec.functions.any functionUsesPlainArrayElement) + +abbrev exprUsesArrayElementWord : Expr → Bool := + exprUsesArrayElementKind false true + +abbrev exprListUsesArrayElementWord : List Expr → Bool := + exprListUsesArrayElementKind false true + +abbrev stmtUsesArrayElementWord : Stmt → Bool := + stmtUsesArrayElementKind false true + +abbrev stmtListUsesArrayElementWord : List Stmt → Bool := + stmtListUsesArrayElementKind false true + +abbrev matchBranchesUseArrayElementWord : List (String × List String × List Stmt) → Bool := + matchBranchesUseArrayElementKind false true + +def functionUsesArrayElementWord (fn : FunctionSpec) : Bool := + fn.body.any stmtUsesArrayElementWord + +def constructorUsesArrayElementWord : Option ConstructorSpec → Bool + | none => false + | some ctor => ctor.body.any stmtUsesArrayElementWord + +def contractUsesArrayElementWord (spec : CompilationModel) : Bool := + contractUsesArrayElement spec && + (constructorUsesArrayElementWord spec.constructor || spec.functions.any functionUsesArrayElementWord) + +private def nestedPlainWithWordIndex : Expr := + Expr.arrayElement "plain" (Expr.arrayElementWord "word" (Expr.literal 0) 1 0) + +private def nestedWordWithPlainIndex : Expr := + Expr.arrayElementWord "word" (Expr.arrayElement "plain" (Expr.literal 0)) 1 0 + +example : exprUsesPlainArrayElement nestedPlainWithWordIndex = true := by + native_decide + +example : exprUsesArrayElementWord nestedPlainWithWordIndex = true := by + native_decide + +example : exprUsesPlainArrayElement nestedWordWithPlainIndex = true := by + native_decide + +example : exprUsesArrayElementWord nestedWordWithPlainIndex = true := by + native_decide + mutual def exprUsesStorageArrayElement : Expr → Bool | Expr.storageArrayElement _ _ => true @@ -293,7 +542,7 @@ def exprUsesStorageArrayElement : Expr → Bool | Expr.caller | Expr.contractAddress | Expr.chainid | Expr.msgValue | Expr.blockTimestamp | Expr.blockNumber | Expr.blobbasefee | Expr.calldatasize | Expr.returndataSize | Expr.localVar _ | Expr.arrayLength _ | Expr.storageArrayLength _ - | Expr.arrayElement _ _ | Expr.adtTag _ _ => + | Expr.arrayElement _ _ | Expr.arrayElementWord _ _ _ _ | Expr.adtTag _ _ => false termination_by e => sizeOf e decreasing_by all_goals simp_wf; all_goals omega @@ -305,7 +554,8 @@ termination_by es => sizeOf es decreasing_by all_goals simp_wf; all_goals omega def stmtUsesStorageArrayElement : Stmt → Bool - | Stmt.letVar _ value | Stmt.assignVar _ value | Stmt.setStorage _ value | Stmt.setStorageAddr _ value | + | Stmt.letVar _ value | Stmt.assignVar _ value | Stmt.setStorage _ value | Stmt.setStorageAddr _ value + | Stmt.setStorageWord _ _ value | Stmt.storageArrayPush _ value | Stmt.return value | Stmt.require value _ => exprUsesStorageArrayElement value @@ -410,7 +660,8 @@ def exprUsesDynamicBytesEq : Expr → Bool | Expr.returndataOptionalBoolAt outOffset => exprUsesDynamicBytesEq outOffset | Expr.externalCall _ args | Expr.internalCall _ args => exprListUsesDynamicBytesEq args - | Expr.storageArrayElement _ index | Expr.arrayElement _ index => exprUsesDynamicBytesEq index + | Expr.storageArrayElement _ index | Expr.arrayElement _ index + | Expr.arrayElementWord _ index _ _ => exprUsesDynamicBytesEq index | Expr.add a b | Expr.sub a b | Expr.mul a b | Expr.div a b | Expr.sdiv a b | Expr.mod a b | Expr.smod a b | Expr.bitAnd a b | Expr.bitOr a b | Expr.bitXor a b | Expr.shl a b | Expr.shr a b @@ -445,6 +696,7 @@ decreasing_by all_goals simp_wf; all_goals omega def stmtUsesDynamicBytesEq : Stmt → Bool | Stmt.letVar _ value | Stmt.assignVar _ value | Stmt.setStorage _ value | Stmt.setStorageAddr _ value + | Stmt.setStorageWord _ _ value | Stmt.storageArrayPush _ value | Stmt.return value | Stmt.require value _ => exprUsesDynamicBytesEq value diff --git a/Compiler/CompilationModel/Validation.lean b/Compiler/CompilationModel/Validation.lean index 7ab6b84c0..ce2777868 100644 --- a/Compiler/CompilationModel/Validation.lean +++ b/Compiler/CompilationModel/Validation.lean @@ -273,7 +273,7 @@ def exprReadsStateOrEnv : Expr → Bool | Expr.arrayLength _ => false | Expr.storageArrayLength _ => true | Expr.storageArrayElement _ index => true || exprReadsStateOrEnv index - | Expr.arrayElement _ index => exprReadsStateOrEnv index + | Expr.arrayElement _ index | Expr.arrayElementWord _ index _ _ => exprReadsStateOrEnv index | Expr.add a b | Expr.sub a b | Expr.mul a b | Expr.div a b | Expr.sdiv a b | Expr.mod a b | Expr.smod a b | Expr.bitAnd a b | Expr.bitOr a b | Expr.bitXor a b | Expr.shl a b | Expr.shr a b @@ -345,7 +345,7 @@ def exprWritesState : Expr → Bool false | Expr.storageArrayElement _ index => exprWritesState index - | Expr.arrayElement _ index => + | Expr.arrayElement _ index | Expr.arrayElementWord _ index _ _ => exprWritesState index | _ => false @@ -361,7 +361,7 @@ decreasing_by all_goals simp_wf; all_goals omega def stmtWritesState : Stmt → Bool | Stmt.letVar _ value | Stmt.assignVar _ value => exprWritesState value - | Stmt.setStorage _ _ | Stmt.setStorageAddr _ _ + | Stmt.setStorage _ _ | Stmt.setStorageAddr _ _ | Stmt.setStorageWord _ _ _ | Stmt.storageArrayPush _ _ | Stmt.storageArrayPop _ | Stmt.setStorageArrayElement _ _ _ | Stmt.setMapping _ _ _ | Stmt.setMappingWord _ _ _ _ | Stmt.setMappingPackedWord _ _ _ _ _ | Stmt.setMappingUint _ _ _ | Stmt.setMappingChain _ _ _ @@ -432,7 +432,7 @@ mutual `setMapping*`, `storageArray*`, and `setStructMember*` constructors. Used by `modifies(...)` validation (#1729, Axis 3 Step 1b). -/ def stmtWrittenFields : Stmt → List String - | Stmt.setStorage field _ | Stmt.setStorageAddr field _ + | Stmt.setStorage field _ | Stmt.setStorageAddr field _ | Stmt.setStorageWord field _ _ | Stmt.storageArrayPush field _ | Stmt.storageArrayPop field | Stmt.setStorageArrayElement field _ _ | Stmt.setMapping field _ _ | Stmt.setMappingWord field _ _ _ | Stmt.setMappingPackedWord field _ _ _ _ | Stmt.setMappingUint field _ _ @@ -486,7 +486,8 @@ def exprHasUntrackableWrites : Expr → Bool | Expr.ite cond thenVal elseVal => exprHasUntrackableWrites cond || exprHasUntrackableWrites thenVal || exprHasUntrackableWrites elseVal | Expr.mapping _ key | Expr.mappingWord _ key _ | Expr.mappingPackedWord _ key _ _ | Expr.mappingUint _ key - | Expr.structMember _ key _ | Expr.arrayElement _ key | Expr.storageArrayElement _ key => + | Expr.structMember _ key _ | Expr.arrayElement _ key | Expr.arrayElementWord _ key _ _ + | Expr.storageArrayElement _ key => exprHasUntrackableWrites key | Expr.mappingChain _ keys => exprListHasUntrackableWrites keys @@ -523,7 +524,8 @@ def stmtHasUntrackableWrites : Stmt → Bool | Stmt.internalCall _ _ | Stmt.internalCallAssign _ _ _ => true | Stmt.letVar _ value | Stmt.assignVar _ value => exprHasUntrackableWrites value - | Stmt.setStorage _ value | Stmt.setStorageAddr _ value | Stmt.require value _ => + | Stmt.setStorage _ value | Stmt.setStorageAddr _ value | Stmt.setStorageWord _ _ value + | Stmt.require value _ => exprHasUntrackableWrites value | Stmt.requireError cond _ args => exprHasUntrackableWrites cond || args.any exprHasUntrackableWrites @@ -600,7 +602,8 @@ def exprContainsExternalCall : Expr → Bool | Expr.ite cond thenVal elseVal => exprContainsExternalCall cond || exprContainsExternalCall thenVal || exprContainsExternalCall elseVal | Expr.mapping _ key | Expr.mappingWord _ key _ | Expr.mappingPackedWord _ key _ _ | Expr.mappingUint _ key - | Expr.structMember _ key _ | Expr.arrayElement _ key | Expr.storageArrayElement _ key => + | Expr.structMember _ key _ | Expr.arrayElement _ key | Expr.arrayElementWord _ key _ _ + | Expr.storageArrayElement _ key => exprContainsExternalCall key | Expr.mappingChain _ keys => exprListContainsExternalCall keys @@ -653,7 +656,8 @@ def exprMayContainExternalCall : Expr → Bool | Expr.ite cond thenVal elseVal => exprMayContainExternalCall cond || exprMayContainExternalCall thenVal || exprMayContainExternalCall elseVal | Expr.mapping _ key | Expr.mappingWord _ key _ | Expr.mappingPackedWord _ key _ _ | Expr.mappingUint _ key - | Expr.structMember _ key _ | Expr.arrayElement _ key | Expr.storageArrayElement _ key => + | Expr.structMember _ key _ | Expr.arrayElement _ key | Expr.arrayElementWord _ key _ _ + | Expr.storageArrayElement _ key => exprMayContainExternalCall key | Expr.mappingChain _ keys => exprListMayContainExternalCall keys @@ -688,7 +692,8 @@ def stmtContainsExternalCall : Stmt → Bool | Stmt.ecm _ _ => true | Stmt.letVar _ value | Stmt.assignVar _ value => exprContainsExternalCall value - | Stmt.setStorage _ value | Stmt.setStorageAddr _ value | Stmt.require value _ => + | Stmt.setStorage _ value | Stmt.setStorageAddr _ value | Stmt.setStorageWord _ _ value + | Stmt.require value _ => exprContainsExternalCall value | Stmt.requireError cond _ args => exprContainsExternalCall cond || args.any exprContainsExternalCall @@ -776,7 +781,8 @@ def stmtMayContainExternalCall : Stmt → Bool exprMayContainExternalCall scrutinee || matchBranchesMayContainExternalCall branches | Stmt.letVar _ value | Stmt.assignVar _ value => exprMayContainExternalCall value - | Stmt.setStorage _ value | Stmt.setStorageAddr _ value | Stmt.require value _ => + | Stmt.setStorage _ value | Stmt.setStorageAddr _ value | Stmt.setStorageWord _ _ value + | Stmt.require value _ => exprMayContainExternalCall value | Stmt.requireError cond _ args => exprMayContainExternalCall cond || args.any exprMayContainExternalCall @@ -836,7 +842,8 @@ end mutual def stmtReadsStateOrEnv : Stmt → Bool - | Stmt.letVar _ value | Stmt.assignVar _ value | Stmt.setStorage _ value | Stmt.setStorageAddr _ value | + | Stmt.letVar _ value | Stmt.assignVar _ value | Stmt.setStorage _ value | Stmt.setStorageAddr _ value + | Stmt.setStorageWord _ _ value | Stmt.return value | Stmt.require value _ => exprReadsStateOrEnv value | Stmt.storageArrayPush _ value => @@ -905,7 +912,7 @@ mutual NOT considered persistent state writes for CEI purposes. (#1728, Axis 2 Step 2a) -/ def stmtIsPersistentWrite : Stmt → Bool - | Stmt.setStorage _ _ | Stmt.setStorageAddr _ _ + | Stmt.setStorage _ _ | Stmt.setStorageAddr _ _ | Stmt.setStorageWord _ _ _ | Stmt.storageArrayPush _ _ | Stmt.storageArrayPop _ | Stmt.setStorageArrayElement _ _ _ | Stmt.setMapping _ _ _ | Stmt.setMappingWord _ _ _ _ | Stmt.setMappingPackedWord _ _ _ _ _ | Stmt.setMappingUint _ _ _ | Stmt.setMappingChain _ _ _ @@ -1084,7 +1091,7 @@ def exprContainsAdtConstruct : Expr → Bool | Expr.bitNot a | Expr.logicalNot a | Expr.extcodesize a | Expr.mload a | Expr.tload a | Expr.calldataload a | Expr.returndataOptionalBoolAt a - | Expr.storageArrayElement _ a | Expr.arrayElement _ a => + | Expr.storageArrayElement _ a | Expr.arrayElement _ a | Expr.arrayElementWord _ a _ _ => exprContainsAdtConstruct a | Expr.ite cond thenVal elseVal => exprContainsAdtConstruct cond || exprContainsAdtConstruct thenVal || @@ -1140,7 +1147,7 @@ def validateNoUnsupportedAdtConstructInStmt : Stmt → Except String Unit else pure () | Stmt.letVar _ value | Stmt.assignVar _ value | Stmt.setStorage _ value - | Stmt.setStorageAddr _ value | Stmt.storageArrayPush _ value + | Stmt.setStorageAddr _ value | Stmt.setStorageWord _ _ value | Stmt.storageArrayPush _ value | Stmt.setStorageArrayElement _ _ value | Stmt.setMapping _ _ value | Stmt.setMappingUint _ _ value | Stmt.setMappingWord _ _ _ value | Stmt.setMapping2 _ _ _ value | Stmt.setMapping2Word _ _ _ _ value diff --git a/Compiler/CompilationModel/ValidationCalls.lean b/Compiler/CompilationModel/ValidationCalls.lean index bba70e368..2e3ff2ed9 100644 --- a/Compiler/CompilationModel/ValidationCalls.lean +++ b/Compiler/CompilationModel/ValidationCalls.lean @@ -12,11 +12,21 @@ import Compiler.CompilationModel.UsageAnalysis namespace Compiler.CompilationModel def reservedExternalNames - (mappingHelpersRequired arrayHelpersRequired storageArrayHelpersRequired dynamicBytesEqHelpersRequired : Bool) : List String := + (mappingHelpersRequired arrayHelpersRequired arrayElementWordHelpersRequired + storageArrayHelpersRequired dynamicBytesEqHelpersRequired : Bool) : List String := let mappingHelpers := if mappingHelpersRequired then ["mappingSlot"] else [] let arrayHelpers := if arrayHelpersRequired then - [checkedArrayElementCalldataHelperName, checkedArrayElementMemoryHelperName] + [ checkedArrayElementCalldataHelperName + , checkedArrayElementMemoryHelperName + ] + else + [] + let arrayElementWordHelpers := + if arrayElementWordHelpersRequired then + [ checkedArrayElementWordCalldataHelperName + , checkedArrayElementWordMemoryHelperName + ] else [] let storageArrayHelpers := @@ -30,14 +40,20 @@ def reservedExternalNames else [] let entrypoints := ["fallback", "receive"] - (mappingHelpers ++ arrayHelpers ++ storageArrayHelpers ++ dynamicBytesEqHelpers ++ entrypoints).eraseDups + (mappingHelpers ++ arrayHelpers ++ arrayElementWordHelpers ++ storageArrayHelpers ++ dynamicBytesEqHelpers ++ entrypoints).eraseDups def firstReservedExternalCollision (spec : CompilationModel) - (mappingHelpersRequired arrayHelpersRequired storageArrayHelpersRequired dynamicBytesEqHelpersRequired : Bool) : Option String := + (mappingHelpersRequired arrayHelpersRequired arrayElementWordHelpersRequired + storageArrayHelpersRequired dynamicBytesEqHelpersRequired : Bool) : Option String := (spec.externals.map (·.name)).find? (fun name => name.startsWith internalFunctionPrefix || - (reservedExternalNames mappingHelpersRequired arrayHelpersRequired storageArrayHelpersRequired dynamicBytesEqHelpersRequired).contains name) + (reservedExternalNames + mappingHelpersRequired + arrayHelpersRequired + arrayElementWordHelpersRequired + storageArrayHelpersRequired + dynamicBytesEqHelpersRequired).contains name) def firstInternalDynamicParam (fns : List FunctionSpec) : Option (String × String × ParamType) := @@ -123,7 +139,8 @@ def validateInternalCallShapesInExpr | Expr.mappingUint _ key => validateInternalCallShapesInExpr functions callerName key | Expr.storageArrayElement _ index - | Expr.arrayElement _ index => + | Expr.arrayElement _ index + | Expr.arrayElementWord _ index _ _ => validateInternalCallShapesInExpr functions callerName index | Expr.add a b | Expr.sub a b | Expr.mul a b | Expr.div a b | Expr.sdiv a b | Expr.mod a b | Expr.smod a b | Expr.bitAnd a b | Expr.bitOr a b | Expr.bitXor a b | Expr.shl a b | Expr.shr a b | @@ -160,7 +177,8 @@ decreasing_by all_goals simp_wf; all_goals omega def validateInternalCallShapesInStmt (functions : List FunctionSpec) (callerName : String) : Stmt → Except String Unit - | Stmt.letVar _ value | Stmt.assignVar _ value | Stmt.setStorage _ value | Stmt.setStorageAddr _ value | + | Stmt.letVar _ value | Stmt.assignVar _ value | Stmt.setStorage _ value | Stmt.setStorageAddr _ value + | Stmt.setStorageWord _ _ value | Stmt.storageArrayPush _ value | Stmt.return value | Stmt.require value _ => validateInternalCallShapesInExpr functions callerName value @@ -353,7 +371,8 @@ def validateExternalCallTargetsInExpr | Expr.internalCall _ args => validateExternalCallTargetsInExprList externals context args | Expr.storageArrayElement _ index - | Expr.arrayElement _ index => + | Expr.arrayElement _ index + | Expr.arrayElementWord _ index _ _ => validateExternalCallTargetsInExpr externals context index | Expr.add a b | Expr.sub a b | Expr.mul a b | Expr.div a b | Expr.sdiv a b | Expr.mod a b | Expr.smod a b | Expr.bitAnd a b | Expr.bitOr a b | Expr.bitXor a b | Expr.shl a b | Expr.shr a b | @@ -390,7 +409,8 @@ decreasing_by all_goals simp_wf; all_goals omega def validateExternalCallTargetsInStmt (externals : List ExternalFunction) (context : String) : Stmt → Except String Unit - | Stmt.letVar _ value | Stmt.assignVar _ value | Stmt.setStorage _ value | Stmt.setStorageAddr _ value | + | Stmt.letVar _ value | Stmt.assignVar _ value | Stmt.setStorage _ value | Stmt.setStorageAddr _ value + | Stmt.setStorageWord _ _ value | Stmt.storageArrayPush _ value | Stmt.return value | Stmt.require value _ => validateExternalCallTargetsInExpr externals context value diff --git a/Compiler/CompilationModel/ValidationHelpers.lean b/Compiler/CompilationModel/ValidationHelpers.lean index b2c936a18..ef04f9bf3 100644 --- a/Compiler/CompilationModel/ValidationHelpers.lean +++ b/Compiler/CompilationModel/ValidationHelpers.lean @@ -77,7 +77,8 @@ def collectExprNames : Expr → List String | Expr.externalCall name args => name :: collectExprListNames args | Expr.internalCall name args => name :: collectExprListNames args | Expr.arrayLength name => [name] - | Expr.arrayElement name index => name :: collectExprNames index + | Expr.arrayElement name index | Expr.arrayElementWord name index _ _ => + name :: collectExprNames index | Expr.storageArrayLength field => [field] | Expr.storageArrayElement field index => field :: collectExprNames index | Expr.dynamicBytesEq lhsName rhsName => [lhsName, rhsName] @@ -135,7 +136,8 @@ mutual def collectStmtNames : Stmt → List String | Stmt.letVar name value => name :: collectExprNames value | Stmt.assignVar name value => name :: collectExprNames value - | Stmt.setStorage _ value | Stmt.setStorageAddr _ value => collectExprNames value + | Stmt.setStorage _ value | Stmt.setStorageAddr _ value | Stmt.setStorageWord _ _ value => + collectExprNames value | Stmt.storageArrayPush _ value => collectExprNames value | Stmt.storageArrayPop _ => [] | Stmt.setStorageArrayElement _ index value => diff --git a/Compiler/CompilationModel/ValidationInterop.lean b/Compiler/CompilationModel/ValidationInterop.lean index 6a8c1cd9e..7b4742375 100644 --- a/Compiler/CompilationModel/ValidationInterop.lean +++ b/Compiler/CompilationModel/ValidationInterop.lean @@ -83,7 +83,7 @@ def validateInteropExpr (context : String) : Expr → Except String Unit validateInteropExprList context args | Expr.storageArrayElement _ index => validateInteropExpr context index - | Expr.arrayElement _ index => + | Expr.arrayElement _ index | Expr.arrayElementWord _ index _ _ => validateInteropExpr context index | Expr.add a b | Expr.sub a b | Expr.mul a b | Expr.div a b | Expr.sdiv a b | Expr.mod a b | Expr.smod a b | Expr.bitAnd a b | Expr.bitOr a b | Expr.bitXor a b | Expr.shl a b | Expr.shr a b | @@ -118,7 +118,8 @@ termination_by es => sizeOf es decreasing_by all_goals simp_wf; all_goals omega def validateInteropStmt (context : String) : Stmt → Except String Unit - | Stmt.letVar _ value | Stmt.assignVar _ value | Stmt.setStorage _ value | Stmt.setStorageAddr _ value | + | Stmt.letVar _ value | Stmt.assignVar _ value | Stmt.setStorage _ value | Stmt.setStorageAddr _ value + | Stmt.setStorageWord _ _ value | Stmt.storageArrayPush _ value | Stmt.return value | Stmt.require value _ => validateInteropExpr context value diff --git a/Compiler/CompilationModelFeatureTest.lean b/Compiler/CompilationModelFeatureTest.lean index 7fe8abc4a..4ba9ec20e 100644 --- a/Compiler/CompilationModelFeatureTest.lean +++ b/Compiler/CompilationModelFeatureTest.lean @@ -289,11 +289,11 @@ verity_contract MacroERC20 where lastAllowance : Uint256 := slot 1 lastSupply : Uint256 := slot 2 - function pushTokens (token : Address, to : Address, amount : Uint256) : Unit := do - safeTransfer token to amount + function pushTokens (token : Address, toAddr : Address, amount : Uint256) : Unit := do + safeTransfer token toAddr amount - function pullTokens (token : Address, fromAddr : Address, to : Address, amount : Uint256) : Unit := do - safeTransferFrom token fromAddr to amount + function pullTokens (token : Address, fromAddr : Address, toAddr : Address, amount : Uint256) : Unit := do + safeTransferFrom token fromAddr toAddr amount function approveTokens (token : Address, spender : Address, amount : Uint256) : Unit := do safeApprove token spender amount @@ -315,7 +315,7 @@ verity_contract MacroERC20 where def pushTokensModelUsesSafeTransfer : Bool := match MacroERC20.pushTokens_modelBody with - | [Stmt.ecm mod [Expr.param "token", Expr.param "to", Expr.param "amount"], Stmt.stop] => + | [Stmt.ecm mod [Expr.param "token", Expr.param "toAddr", Expr.param "amount"], Stmt.stop] => mod.name == "safeTransfer" && mod.resultVars.isEmpty && mod.axioms == ["erc20_transfer_interface"] @@ -325,7 +325,7 @@ example : pushTokensModelUsesSafeTransfer = true := by native_decide def pullTokensModelUsesSafeTransferFrom : Bool := match MacroERC20.pullTokens_modelBody with - | [Stmt.ecm mod [Expr.param "token", Expr.param "fromAddr", Expr.param "to", Expr.param "amount"], Stmt.stop] => + | [Stmt.ecm mod [Expr.param "token", Expr.param "fromAddr", Expr.param "toAddr", Expr.param "amount"], Stmt.stop] => mod.name == "safeTransferFrom" && mod.resultVars.isEmpty && mod.axioms == ["erc20_transferFrom_interface"] @@ -517,22 +517,34 @@ verity_contract MacroBlobbasefee where storage function currentBlobBaseFee () : Uint256 := do - return blobbasefee + let fee ← blobbasefee + return fee + + function qualifiedCurrentBlobBaseFee () : Uint256 := do + let fee ← Verity.blobbasefee + return fee def modelReturnsBlobbasefeeBuiltin : Bool := match MacroBlobbasefee.currentBlobBaseFee_modelBody with - | [Stmt.return Expr.blobbasefee] => true + | [Stmt.letVar "fee" Expr.blobbasefee, Stmt.return (Expr.localVar "fee")] => true | _ => false example : modelReturnsBlobbasefeeBuiltin = true := by native_decide -def executableUsesRuntimeStub : Bool := - match MacroBlobbasefee.currentBlobBaseFee Verity.defaultState with +def qualifiedModelReturnsBlobbasefeeBuiltin : Bool := + match MacroBlobbasefee.qualifiedCurrentBlobBaseFee_modelBody with + | [Stmt.letVar "fee" Expr.blobbasefee, Stmt.return (Expr.localVar "fee")] => true + | _ => false + +example : qualifiedModelReturnsBlobbasefeeBuiltin = true := by native_decide + +def executableUsesContractState : Bool := + match MacroBlobbasefee.currentBlobBaseFee { Verity.defaultState with blobBaseFee := 19 } with | .success fee state => - fee == 0 && state.sender == Verity.defaultState.sender + fee == 19 && state.sender == Verity.defaultState.sender | .revert _ _ => false -example : executableUsesRuntimeStub = true := by native_decide +example : executableUsesContractState = true := by native_decide end MacroBlobbasefeeSmoke @@ -1730,6 +1742,45 @@ private def reservedParamSpec : CompilationModel := { ] } +private def duplicateInternalNameSpec : CompilationModel := { + name := "DuplicateInternalName" + fields := [] + «constructor» := none + functions := [ + { name := "helper" + params := [{ name := "amount", ty := ParamType.uint256 }] + returnType := some FieldType.uint256 + body := [Stmt.return (Expr.param "amount")] + isInternal := true + }, + { name := "helper" + params := [{ name := "target", ty := ParamType.uint256 }] + returnType := some FieldType.uint256 + body := [Stmt.return (Expr.literal 1)] + isInternal := true + } + ] +} + +private def internalExternalNameCollisionSpec : CompilationModel := { + name := "InternalExternalNameCollision" + fields := [] + «constructor» := none + functions := [ + { name := "helper" + params := [{ name := "amount", ty := ParamType.uint256 }] + returnType := some FieldType.uint256 + body := [Stmt.return (Expr.param "amount")] + }, + { name := "helper" + params := [{ name := "target", ty := ParamType.uint256 }] + returnType := some FieldType.uint256 + body := [Stmt.return (Expr.literal 1)] + isInternal := true + } + ] +} + private def reservedFieldSpec : CompilationModel := { name := "ReservedField" fields := [{ name := "__compat_value", ty := FieldType.uint256 }] @@ -2299,6 +2350,45 @@ private def bytesArrayElementSpec : CompilationModel := { ] } +private def bytesArrayElementWordSpec : CompilationModel := { + name := "BytesArrayElementWord" + fields := [] + «constructor» := none + functions := [ + { name := "headWord" + params := [{ name := "calls", ty := ParamType.array ParamType.bytes }] + returnType := some FieldType.uint256 + body := [Stmt.return (Expr.arrayElementWord "calls" (Expr.literal 0) 1 0)] + } + ] +} + +private def uintArrayElementOnlySpec : CompilationModel := { + name := "UintArrayElementOnly" + fields := [] + «constructor» := none + functions := [ + { name := "head" + params := [{ name := "values", ty := ParamType.array ParamType.uint256 }] + returnType := some FieldType.uint256 + body := [Stmt.return (Expr.arrayElement "values" (Expr.literal 0))] + } + ] +} + +private def tupleArrayElementWordOnlySpec : CompilationModel := { + name := "TupleArrayElementWordOnly" + fields := [] + «constructor» := none + functions := [ + { name := "second" + params := [{ name := "values", ty := ParamType.array (ParamType.tuple [ParamType.uint256, ParamType.uint256]) }] + returnType := some FieldType.uint256 + body := [Stmt.return (Expr.arrayElementWord "values" (Expr.literal 0) 2 1)] + } + ] +} + private def storageArrayUint256SmokeSpec : CompilationModel := { name := "StorageArrayUint256Smoke" fields := [{ name := "queue", ty := FieldType.dynamicArray .uint256, «slot» := some 7 }] @@ -2785,6 +2875,16 @@ set_option maxRecDepth 4096 in "reserved compiler prefix is rejected in function parameters" reservedParamSpec "function parameter '__has_selector' uses reserved compiler prefix '__'" + expectCompileErrorContains + "same-name internal helpers are rejected before Yul lowering" + duplicateInternalNameSpec + "duplicate internal function name 'helper'" + expectTrue + "internal helper source names may match external dispatch names" + (match Compiler.CompilationModel.compile internalExternalNameCollisionSpec + (selectorsFor internalExternalNameCollisionSpec) with + | .ok _ => true + | .error _ => false) let reservedFieldRejected := match validateCompileInputs reservedFieldSpec (selectorsFor reservedFieldSpec) with | .ok _ => false @@ -3033,6 +3133,31 @@ set_option maxRecDepth 4096 in "arrayElement rejects bytes[] params until dynamic-element indexing lands" bytesArrayElementSpec "Expr.arrayElement 'calls' requires an array with single-word static elements" + expectCompileErrorContains + "arrayElementWord rejects bytes[] params until dynamic-element word indexing lands" + bytesArrayElementWordSpec + "Expr.arrayElementWord 'calls' requires an array parameter with static ABI-word elements" + let uintArrayElementOnlyYul ← + expectCompileToYul "uint256[] arrayElement-only smoke spec" uintArrayElementOnlySpec + expectTrue "arrayElement-only specs emit the tuple-array helpers" + ((contains uintArrayElementOnlyYul checkedArrayElementCalldataHelperName) && + (contains uintArrayElementOnlyYul checkedArrayElementMemoryHelperName)) + expectTrue "arrayElement-only specs do not emit word-array helpers" + (!(contains uintArrayElementOnlyYul checkedArrayElementWordCalldataHelperName) && + !(contains uintArrayElementOnlyYul checkedArrayElementWordMemoryHelperName)) + let tupleArrayElementWordOnlyYul ← + expectCompileToYul "tuple[] arrayElementWord-only smoke spec" tupleArrayElementWordOnlySpec + expectTrue "arrayElementWord-only specs emit the word-array helpers" + ((contains tupleArrayElementWordOnlyYul checkedArrayElementWordCalldataHelperName) && + (contains tupleArrayElementWordOnlyYul checkedArrayElementWordMemoryHelperName)) + expectTrue "arrayElementWord helper scales only element word offset" + (contains tupleArrayElementWordOnlyYul + "calldataload(add(data_offset, mul(add(mul(index, element_words), word_offset), 32)))" && + !(contains tupleArrayElementWordOnlyYul + "calldataload(add(data_offset, mul(add(add(mul(index, element_words), word_offset), 32), 32)))")) + expectTrue "arrayElementWord-only specs do not emit tuple-array helpers" + (!(contains tupleArrayElementWordOnlyYul checkedArrayElementCalldataHelperName) && + !(contains tupleArrayElementWordOnlyYul checkedArrayElementMemoryHelperName)) let storageArrayUint256Yul ← expectCompileToYul "storage uint256[] smoke spec" storageArrayUint256SmokeSpec expectTrue "storage uint256[] length lowers to sload(slot)" @@ -3289,8 +3414,8 @@ set_option maxRecDepth 4096 in expectCompileToYul "macro blobbasefee smoke spec" MacroBlobbasefeeSmoke.MacroBlobbasefee.spec expectTrue "macro blobbasefee lowers to the Yul blobbasefee builtin" (contains macroBlobbasefeeYul "blobbasefee()") - expectTrue "macro blobbasefee executable path uses the runtime stub" - MacroBlobbasefeeSmoke.executableUsesRuntimeStub + expectTrue "macro blobbasefee executable path uses ContractState" + MacroBlobbasefeeSmoke.executableUsesContractState let macroBlobbasefeeTrustReport := emitTrustReportJson [MacroBlobbasefeeSmoke.MacroBlobbasefee.spec] expectTrue "macro blobbasefee trust report surfaces the post-core builtin" (contains macroBlobbasefeeTrustReport "\"modeledLowLevelMechanics\"" && diff --git a/Compiler/ModuleInput.lean b/Compiler/ModuleInput.lean index 6f5a458e7..d51465769 100644 --- a/Compiler/ModuleInput.lean +++ b/Compiler/ModuleInput.lean @@ -72,19 +72,31 @@ private unsafe def evalSpecConst | .error _ => throw s!"Unable to evaluate '{specName}' as Compiler.CompilationModel.CompilationModel" -private def splitPackageSearchRoots : List System.FilePath := +private def workspaceSearchRoots : List System.FilePath := [ ".lake/build/lib/lean" , "packages/verity-edsl/.lake/build/lib/lean" , "packages/verity-compiler/.lake/build/lib/lean" , "packages/verity-examples/.lake/build/lib/lean" ] +private def packageSearchRoots : IO SearchPath := do + let packagesRoot : System.FilePath := ".lake/packages" + if !(← packagesRoot.isDir) then + pure [] + else + let mut roots : SearchPath := [] + for entry in (← packagesRoot.readDir) do + let root := entry.path / ".lake" / "build" / "lib" / "lean" + if ← root.isDir then + roots := roots.concat root + pure roots + private def existingSplitPackageSearchRoots : IO SearchPath := do let mut roots : SearchPath := [] - for path in splitPackageSearchRoots do + for path in workspaceSearchRoots do if ← path.isDir then roots := roots.concat path - pure roots + pure (roots ++ (← packageSearchRoots)) /-- Import modules and evaluate their canonical `.spec` constants. -/ unsafe def loadSpecsFromModules (moduleNames : List Name) : IO (Except String (List CompilationModel)) := do diff --git a/Compiler/Proofs/EndToEnd.lean b/Compiler/Proofs/EndToEnd.lean index 7bdcfa684..8bfe4c9ad 100644 --- a/Compiler/Proofs/EndToEnd.lean +++ b/Compiler/Proofs/EndToEnd.lean @@ -45,6 +45,7 @@ import Compiler.Proofs.YulGeneration.Preservation import Compiler.Proofs.YulGeneration.Backends.EvmYulLeanRetarget import Compiler.Proofs.YulGeneration.Backends.EvmYulLeanBodyClosure +import Compiler.Proofs.YulGeneration.Backends.EvmYulLeanNativeHarness import Compiler.Proofs.IRGeneration.Contract import Compiler.Proofs.IRGeneration.Function import Compiler.Proofs.IRGeneration.Expr @@ -55,6 +56,371 @@ open Compiler open Compiler.Proofs.IRGeneration open Compiler.Proofs.YulGeneration +/-! ## Native Runtime Result Surface -/ + +/-- Result comparison surface for the native EVMYulLean harness. + +The native harness can still fail closed during Verity-Yul-to-EVMYulLean +lowering, so the native-facing public theorem states both that native execution +returns a `YulResult` and that this result matches IR execution. -/ +def nativeResultsMatch + (ir : IRResult) + (native : Except Compiler.Proofs.YulGeneration.Backends.AdapterError YulResult) : + Prop := + match native with + | .ok yul => Compiler.Proofs.YulGeneration.resultsMatch ir yul + | .error _ => False + +/-- Observable native result comparison for the current native bridge. + +The native EVMYulLean state bridge materializes only the storage slots supplied +by the caller. Until the generated-fragment bridge proves a complete storage +projection, native-facing theorems compare success, return value, events, and +the explicitly observable final-storage slots. -/ +def yulResultsAgreeOn + (observableSlots : List Nat) (left right : YulResult) : Prop := + left.success = right.success ∧ + left.returnValue = right.returnValue ∧ + (∀ slot, slot ∈ observableSlots → left.finalStorage slot = right.finalStorage slot) ∧ + left.events = right.events + +/-- Observable result comparison surface for native EVMYulLean execution. -/ +def nativeResultsMatchOn + (observableSlots : List Nat) + (ir : IRResult) + (native : Except Compiler.Proofs.YulGeneration.Backends.AdapterError YulResult) : + Prop := + match native with + | .ok yul => + ir.success = yul.success ∧ + ir.returnValue = yul.returnValue ∧ + (∀ slot, slot ∈ observableSlots → ir.finalStorage slot = yul.finalStorage slot) ∧ + ir.events = yul.events + | .error _ => False + +theorem nativeResultsMatchOn_ok_of_resultsMatch_of_yulResultsAgreeOn + {observableSlots : List Nat} {ir : IRResult} {native oracle : YulResult} + (hLayer : Compiler.Proofs.YulGeneration.resultsMatch ir oracle) + (hNative : yulResultsAgreeOn observableSlots native oracle) : + nativeResultsMatchOn observableSlots ir (.ok native) := by + rcases hLayer with ⟨hsuccess, hreturnValue, hstorage, _hmappings, hevents⟩ + rcases hNative with ⟨hnativeSuccess, hnativeReturnValue, hnativeStorage, hnativeEvents⟩ + exact ⟨ + hsuccess.trans hnativeSuccess.symm, + hreturnValue.trans hnativeReturnValue.symm, + (by + intro slot hslot + exact (hstorage slot).trans (hnativeStorage slot hslot).symm), + hevents.trans hnativeEvents.symm + ⟩ + +/-- The exact semantic bridge still needed before the public theorem can be +retargeted unconditionally to native EVMYulLean. + +This predicate is intentionally concrete: it compares the current +fuel-aligned EVMYulLean-backed interpreter oracle with +`Native.interpretIRRuntimeNative` on the actual `Compiler.emitYul` runtime code +for an `IRContract`, under the same explicit fuel bound and observable +storage-slot set. -/ +def nativeIRRuntimeAgreesWithInterpreter + (fuel : Nat) + (contract : IRContract) + (tx : IRTransaction) + (state : IRState) + (observableSlots : List Nat) : + Prop := + match Compiler.Proofs.YulGeneration.Backends.Native.interpretIRRuntimeNative + fuel contract tx state observableSlots with + | .ok native => + yulResultsAgreeOn observableSlots native + (Compiler.Proofs.YulGeneration.Backends.interpretYulRuntimeWithBackendFuel + .evmYulLean fuel (Compiler.emitYul contract).runtimeCode + (YulTransaction.ofIR tx) state.storage state.events) + | .error _ => False + +/-- Intro form for the native/interpreter bridge obligation. + +Native-lowering proofs can stay at the harness level: prove that +`interpretIRRuntimeNative` succeeds and that its projected result agrees with +the current interpreter oracle on the requested observable slots. This packages +those two facts as the public `nativeIRRuntimeAgreesWithInterpreter` +obligation consumed by the native EndToEnd seam. -/ +theorem nativeIRRuntimeAgreesWithInterpreter_of_ok_agree + {fuel : Nat} {contract : IRContract} {tx : IRTransaction} + {state : IRState} {observableSlots : List Nat} {native : YulResult} + (hNative : + Compiler.Proofs.YulGeneration.Backends.Native.interpretIRRuntimeNative + fuel contract tx state observableSlots = .ok native) + (hAgree : + yulResultsAgreeOn observableSlots native + (Compiler.Proofs.YulGeneration.Backends.interpretYulRuntimeWithBackendFuel + .evmYulLean fuel (Compiler.emitYul contract).runtimeCode + (YulTransaction.ofIR tx) state.storage state.events)) : + nativeIRRuntimeAgreesWithInterpreter fuel contract tx state observableSlots := by + unfold nativeIRRuntimeAgreesWithInterpreter + rw [hNative] + exact hAgree + +/-- Concrete native execution agreement target after Verity runtime Yul has +successfully lowered to an EVMYulLean contract. + +This is the next proof obligation under the opaque +`nativeIRRuntimeAgreesWithInterpreter` seam: compare the projected native +`callDispatcher` result with the fuel-aligned interpreter oracle on the same +emitted runtime code and observable storage slots. -/ +def nativeCallDispatcherAgreesWithInterpreter + (fuel : Nat) + (contract : IRContract) + (tx : IRTransaction) + (state : IRState) + (observableSlots : List Nat) + (nativeContract : EvmYul.Yul.Ast.YulContract) : + Prop := + yulResultsAgreeOn observableSlots + (Compiler.Proofs.YulGeneration.Backends.Native.projectResult + (YulTransaction.ofIR tx) state.storage state.events + (EvmYul.Yul.callDispatcher fuel (some nativeContract) + (Compiler.Proofs.YulGeneration.Backends.Native.initialState + nativeContract (YulTransaction.ofIR tx) state.storage observableSlots))) + (Compiler.Proofs.YulGeneration.Backends.interpretYulRuntimeWithBackendFuel + .evmYulLean fuel (Compiler.emitYul contract).runtimeCode + (YulTransaction.ofIR tx) state.storage state.events) + +/-- Lower-level native dispatcher agreement target. + +For positive fuel this compares the interpreter oracle with direct +`EvmYul.Yul.exec` execution of the lowered contract's dispatcher block, after +the same empty call-frame setup and result projection used by `callDispatcher`. +This is the statement-execution preservation obligation needed next for the +generated fragment. The zero-fuel case is kept explicit so the theorem below is +total in `fuel`. -/ +def nativeDispatcherBlockAgreesWithInterpreter + (fuel : Nat) + (contract : IRContract) + (tx : IRTransaction) + (state : IRState) + (observableSlots : List Nat) + (nativeContract : EvmYul.Yul.Ast.YulContract) : + Prop := + let initial := + Compiler.Proofs.YulGeneration.Backends.Native.initialState nativeContract + (YulTransaction.ofIR tx) state.storage observableSlots + let nativeResult := + match fuel with + | 0 => + .error EvmYul.Yul.Exception.OutOfFuel + | Nat.succ fuel' => + Compiler.Proofs.YulGeneration.Backends.Native.contractDispatcherBlockResult + fuel' nativeContract initial + yulResultsAgreeOn observableSlots + (Compiler.Proofs.YulGeneration.Backends.Native.projectResult + (YulTransaction.ofIR tx) state.storage state.events nativeResult) + (Compiler.Proofs.YulGeneration.Backends.interpretYulRuntimeWithBackendFuel + .evmYulLean fuel (Compiler.emitYul contract).runtimeCode + (YulTransaction.ofIR tx) state.storage state.events) + +/-- Raw native dispatcher-exec agreement target. + +This peels off the `contractDispatcherBlockResult` wrapper too: the remaining +native preservation proof can target the exact `EvmYul.Yul.exec` result for the +lowered dispatcher block, with only the final `callDispatcher` restoration and +return-list projection left around that raw result. -/ +def nativeDispatcherExecAgreesWithInterpreter + (fuel : Nat) + (contract : IRContract) + (tx : IRTransaction) + (state : IRState) + (observableSlots : List Nat) + (nativeContract : EvmYul.Yul.Ast.YulContract) : + Prop := + let initial := + Compiler.Proofs.YulGeneration.Backends.Native.initialState nativeContract + (YulTransaction.ofIR tx) state.storage observableSlots + let nativeResult := + match fuel with + | 0 => + .error EvmYul.Yul.Exception.OutOfFuel + | Nat.succ fuel' => + match + Compiler.Proofs.YulGeneration.Backends.Native.contractDispatcherExecResult + fuel' nativeContract initial with + | .error err => .error err + | .ok finalState => + let restored := finalState.reviveJump.overwrite? initial |>.setStore initial + .ok (restored, []) + yulResultsAgreeOn observableSlots + (Compiler.Proofs.YulGeneration.Backends.Native.projectResult + (YulTransaction.ofIR tx) state.storage state.events nativeResult) + (Compiler.Proofs.YulGeneration.Backends.interpretYulRuntimeWithBackendFuel + .evmYulLean fuel (Compiler.emitYul contract).runtimeCode + (YulTransaction.ofIR tx) state.storage state.events) + +/-- Intro form for the positive-fuel raw dispatcher-exec bridge when native +execution finishes normally. + +The remaining generated-fragment simulation can prove a concrete +`contractDispatcherExecResult = .ok finalState` fact, then prove observable +agreement for the restored/projected call-dispatcher result. This theorem +packages that pair as the public raw-dispatcher agreement obligation. -/ +theorem nativeDispatcherExecAgreesWithInterpreter_of_exec_ok_agree + {fuel' : Nat} {contract : IRContract} {tx : IRTransaction} + {state : IRState} {observableSlots : List Nat} + {nativeContract : EvmYul.Yul.Ast.YulContract} + {finalState : EvmYul.Yul.State} + (hExec : + Compiler.Proofs.YulGeneration.Backends.Native.contractDispatcherExecResult + fuel' nativeContract + (Compiler.Proofs.YulGeneration.Backends.Native.initialState + nativeContract (YulTransaction.ofIR tx) state.storage observableSlots) = + .ok finalState) + (hAgree : + let initial := + Compiler.Proofs.YulGeneration.Backends.Native.initialState nativeContract + (YulTransaction.ofIR tx) state.storage observableSlots + let dispatcherDef := + EvmYul.Yul.Ast.FunctionDefinition.Def [] [] [nativeContract.dispatcher] + yulResultsAgreeOn observableSlots + (Compiler.Proofs.YulGeneration.Backends.Native.projectResult + (YulTransaction.ofIR tx) state.storage state.events + (.ok + (finalState.reviveJump.overwrite? initial |>.setStore initial, + List.map finalState.lookup! dispatcherDef.rets))) + (Compiler.Proofs.YulGeneration.Backends.interpretYulRuntimeWithBackendFuel + .evmYulLean (Nat.succ fuel') (Compiler.emitYul contract).runtimeCode + (YulTransaction.ofIR tx) state.storage state.events)) : + nativeDispatcherExecAgreesWithInterpreter (Nat.succ fuel') contract tx state + observableSlots nativeContract := by + unfold nativeDispatcherExecAgreesWithInterpreter + simp [hExec] + exact hAgree + +/-- Intro form for the positive-fuel raw dispatcher-exec bridge when native +execution halts through EVMYulLean's Yul halt channel (`stop`/`return`). -/ +theorem nativeDispatcherExecAgreesWithInterpreter_of_exec_yulHalt_agree + {fuel' : Nat} {contract : IRContract} {tx : IRTransaction} + {state : IRState} {observableSlots : List Nat} + {nativeContract : EvmYul.Yul.Ast.YulContract} + {haltState : EvmYul.Yul.State} {haltValue : EvmYul.Yul.Ast.Literal} + (hExec : + Compiler.Proofs.YulGeneration.Backends.Native.contractDispatcherExecResult + fuel' nativeContract + (Compiler.Proofs.YulGeneration.Backends.Native.initialState + nativeContract (YulTransaction.ofIR tx) state.storage observableSlots) = + .error (.YulHalt haltState haltValue)) + (hAgree : + yulResultsAgreeOn observableSlots + (Compiler.Proofs.YulGeneration.Backends.Native.projectResult + (YulTransaction.ofIR tx) state.storage state.events + (.error (.YulHalt haltState haltValue))) + (Compiler.Proofs.YulGeneration.Backends.interpretYulRuntimeWithBackendFuel + .evmYulLean (Nat.succ fuel') (Compiler.emitYul contract).runtimeCode + (YulTransaction.ofIR tx) state.storage state.events)) : + nativeDispatcherExecAgreesWithInterpreter (Nat.succ fuel') contract tx state + observableSlots nativeContract := by + unfold nativeDispatcherExecAgreesWithInterpreter + simp [hExec] + exact hAgree + +/-- Intro form for the positive-fuel raw dispatcher-exec bridge when native +execution fails through a non-halt EVMYulLean exception. + +This is useful for revert/fail-closed generated-fragment cases: after proving +the raw native exception and the projected oracle agreement, callers can close +the same raw-dispatcher obligation consumed by the public native theorem seam. -/ +theorem nativeDispatcherExecAgreesWithInterpreter_of_exec_error_agree + {fuel' : Nat} {contract : IRContract} {tx : IRTransaction} + {state : IRState} {observableSlots : List Nat} + {nativeContract : EvmYul.Yul.Ast.YulContract} + {err : EvmYul.Yul.Exception} + (hExec : + Compiler.Proofs.YulGeneration.Backends.Native.contractDispatcherExecResult + fuel' nativeContract + (Compiler.Proofs.YulGeneration.Backends.Native.initialState + nativeContract (YulTransaction.ofIR tx) state.storage observableSlots) = + .error err) + (hAgree : + yulResultsAgreeOn observableSlots + (Compiler.Proofs.YulGeneration.Backends.Native.projectResult + (YulTransaction.ofIR tx) state.storage state.events (.error err)) + (Compiler.Proofs.YulGeneration.Backends.interpretYulRuntimeWithBackendFuel + .evmYulLean (Nat.succ fuel') (Compiler.emitYul contract).runtimeCode + (YulTransaction.ofIR tx) state.storage state.events)) : + nativeDispatcherExecAgreesWithInterpreter (Nat.succ fuel') contract tx state + observableSlots nativeContract := by + unfold nativeDispatcherExecAgreesWithInterpreter + simp [hExec] + exact hAgree + +/-- Lift raw lowered-dispatcher `EvmYul.Yul.exec` agreement to the +dispatcher-block bridge obligation. -/ +theorem nativeDispatcherBlockAgreesWithInterpreter_of_exec_agree + {fuel : Nat} {contract : IRContract} {tx : IRTransaction} + {state : IRState} {observableSlots : List Nat} + {nativeContract : EvmYul.Yul.Ast.YulContract} + (hAgree : + nativeDispatcherExecAgreesWithInterpreter fuel contract tx state + observableSlots nativeContract) : + nativeDispatcherBlockAgreesWithInterpreter fuel contract tx state + observableSlots nativeContract := by + unfold nativeDispatcherExecAgreesWithInterpreter at hAgree + unfold nativeDispatcherBlockAgreesWithInterpreter + cases fuel with + | zero => + simpa using hAgree + | succ fuel' => + simpa [Compiler.Proofs.YulGeneration.Backends.Native.contractDispatcherBlockResult_eq_execResult] + using hAgree + +/-- Lift dispatcher-block execution agreement to the existing +`callDispatcher`-level bridge obligation. -/ +theorem nativeCallDispatcherAgreesWithInterpreter_of_dispatcherBlock_agree + {fuel : Nat} {contract : IRContract} {tx : IRTransaction} + {state : IRState} {observableSlots : List Nat} + {nativeContract : EvmYul.Yul.Ast.YulContract} + (hAgree : + nativeDispatcherBlockAgreesWithInterpreter fuel contract tx state + observableSlots nativeContract) : + nativeCallDispatcherAgreesWithInterpreter fuel contract tx state + observableSlots nativeContract := by + unfold nativeDispatcherBlockAgreesWithInterpreter at hAgree + unfold nativeCallDispatcherAgreesWithInterpreter + cases fuel with + | zero => + simpa [Compiler.Proofs.YulGeneration.Backends.Native.callDispatcher_zero] + using hAgree + | succ fuel' => + rw [Compiler.Proofs.YulGeneration.Backends.Native.callDispatcher_succ_eq_callDispatcherBlockResult] + rw [Compiler.Proofs.YulGeneration.Backends.Native.callDispatcherBlockResult_initialState_eq_contractDispatcherBlockResult] + simpa using hAgree + +/-- Discharge the public native/interpreter bridge from concrete native +lowering, selected-path environment validation, and projected +`callDispatcher` agreement. + +After this theorem, generated-fragment work can focus on facts about +`lowerRuntimeContractNative`, `validateNativeRuntimeEnvironment`, and +`EvmYul.Yul.callDispatcher` rather than re-opening the public native harness +wrapper. -/ +theorem nativeIRRuntimeAgreesWithInterpreter_of_lowered_callDispatcher_agree + {fuel : Nat} {contract : IRContract} {tx : IRTransaction} + {state : IRState} {observableSlots : List Nat} + {nativeContract : EvmYul.Yul.Ast.YulContract} + (hLower : + Compiler.Proofs.YulGeneration.Backends.lowerRuntimeContractNative + (Compiler.emitYul contract).runtimeCode = .ok nativeContract) + (hEnv : + Compiler.Proofs.YulGeneration.Backends.Native.validateNativeRuntimeEnvironment + (Compiler.emitYul contract).runtimeCode (YulTransaction.ofIR tx) = .ok ()) + (hAgree : + nativeCallDispatcherAgreesWithInterpreter fuel contract tx state + observableSlots nativeContract) : + nativeIRRuntimeAgreesWithInterpreter fuel contract tx state observableSlots := by + apply nativeIRRuntimeAgreesWithInterpreter_of_ok_agree + · exact + (Compiler.Proofs.YulGeneration.Backends.Native.interpretIRRuntimeNative_eq_callDispatcher_of_lowerRuntimeContractNative + fuel contract tx state observableSlots nativeContract hLower hEnv) + · exact hAgree + /-! ## Layer 3: IR → Yul (Generic) -/ /-- Layer 3 function-level preservation: any IR function body produces equivalent @@ -417,13 +783,102 @@ theorem layer3_contract_preserves_semantics_evmYulLean · intro fn hmem exact (yulBody_from_state_eq_yulBody fn tx (initialState.withTx tx) rfl rfl rfl rfl rfl rfl rfl rfl rfl - (by simpa using hreturn) - (by simpa using hmemory) - (by simpa using htransient) - (by simpa using hvars) - (hparamErase fn hmem)) + (by simpa using hreturn) + (by simpa using hmemory) + (by simpa using htransient) + (by simpa using hvars) + (hparamErase fn hmem)) · exact hFunctions +/-- Native Layer 3 bridge theorem under the named generated-fragment bridge obligation. -/ +theorem layer3_contract_preserves_semantics_native_of_interpreter_bridge + (fuel : Nat) (contract : IRContract) (tx : IRTransaction) + (initialState : IRState) (observableSlots : List Nat) + (hselector : tx.functionSelector < selectorModulus) (hNoWrap : 4 + tx.args.length * 32 < evmModulus) + (hvars : initialState.vars = []) (hmemory : initialState.memory = fun _ => 0) + (htransient : initialState.transientStorage = fun _ => 0) (hreturn : initialState.returnValue = none) + (hparamErase : ∀ fn, fn ∈ contract.functions → paramLoadErasure fn tx (initialState.withTx tx)) + (hdispatchGuardSafe : ∀ fn, fn ∈ contract.functions → DispatchGuardsSafe fn tx) + (hNoHasSelector : ∀ fn, fn ∈ contract.functions → yulStmtsNoRef "__has_selector" fn.body) + (hHasSelectorDead : ∀ fn, fn ∈ contract.functions → HasSelectorDeadBridge fn.body) + (hLoopFree : ∀ fn, fn ∈ contract.functions → yulStmtsLoopFree fn.body = true) + (hWF : ContractWF contract) (hNoFallback : contract.fallbackEntrypoint = none) + (hNoReceive : contract.receiveEntrypoint = none) + (hFunctions : ∀ fn, fn ∈ contract.functions → Compiler.Proofs.YulGeneration.Backends.BridgedStmts fn.body) + (hFuel : fuel = sizeOf (Compiler.emitYul contract).runtimeCode + 1) + (hNativeBridge : nativeIRRuntimeAgreesWithInterpreter fuel contract tx initialState observableSlots) : + nativeResultsMatchOn observableSlots (interpretIR contract tx initialState) + (Compiler.Proofs.YulGeneration.Backends.Native.interpretIRRuntimeNative + fuel contract tx initialState observableSlots) := by + subst fuel + have hLayer := + layer3_contract_preserves_semantics_evmYulLean contract tx initialState + hselector hNoWrap hvars hmemory htransient hreturn hparamErase + hdispatchGuardSafe hNoHasSelector hHasSelectorDead hLoopFree hWF hNoFallback + hNoReceive hFunctions + unfold nativeIRRuntimeAgreesWithInterpreter at hNativeBridge + cases hNative : + Compiler.Proofs.YulGeneration.Backends.Native.interpretIRRuntimeNative + (sizeOf (Compiler.emitYul contract).runtimeCode + 1) + contract tx initialState observableSlots with + | error err => + rw [hNative] at hNativeBridge + exact False.elim hNativeBridge + | ok native => + rw [hNative] at hNativeBridge + exact nativeResultsMatchOn_ok_of_resultsMatch_of_yulResultsAgreeOn hLayer + hNativeBridge + +/-- Native Layer 3 bridge theorem with the remaining obligation stated at the +concrete lowered EVMYulLean contract boundary. + +This variant removes the opaque `nativeIRRuntimeAgreesWithInterpreter` +hypothesis from callers. They instead prove native lowering succeeds, the +selected native runtime path has representable environment reads, and projected +`callDispatcher` execution agrees with the interpreter oracle. -/ +theorem layer3_contract_preserves_semantics_native_of_lowered_callDispatcher_bridge + (fuel : Nat) (contract : IRContract) (tx : IRTransaction) + (initialState : IRState) (observableSlots : List Nat) + (nativeContract : EvmYul.Yul.Ast.YulContract) + (hselector : tx.functionSelector < selectorModulus) + (hNoWrap : 4 + tx.args.length * 32 < evmModulus) + (hvars : initialState.vars = []) (hmemory : initialState.memory = fun _ => 0) + (htransient : initialState.transientStorage = fun _ => 0) + (hreturn : initialState.returnValue = none) + (hparamErase : ∀ fn, fn ∈ contract.functions → + paramLoadErasure fn tx (initialState.withTx tx)) + (hdispatchGuardSafe : ∀ fn, fn ∈ contract.functions → + DispatchGuardsSafe fn tx) + (hNoHasSelector : ∀ fn, fn ∈ contract.functions → + yulStmtsNoRef "__has_selector" fn.body) + (hHasSelectorDead : ∀ fn, fn ∈ contract.functions → + HasSelectorDeadBridge fn.body) + (hLoopFree : ∀ fn, fn ∈ contract.functions → yulStmtsLoopFree fn.body = true) + (hWF : ContractWF contract) (hNoFallback : contract.fallbackEntrypoint = none) + (hNoReceive : contract.receiveEntrypoint = none) + (hFunctions : ∀ fn, fn ∈ contract.functions → + Compiler.Proofs.YulGeneration.Backends.BridgedStmts fn.body) + (hFuel : fuel = sizeOf (Compiler.emitYul contract).runtimeCode + 1) + (hLower : + Compiler.Proofs.YulGeneration.Backends.lowerRuntimeContractNative + (Compiler.emitYul contract).runtimeCode = .ok nativeContract) + (hEnv : + Compiler.Proofs.YulGeneration.Backends.Native.validateNativeRuntimeEnvironment + (Compiler.emitYul contract).runtimeCode (YulTransaction.ofIR tx) = .ok ()) + (hNativeCallDispatcher : + nativeCallDispatcherAgreesWithInterpreter fuel contract tx initialState + observableSlots nativeContract) : + nativeResultsMatchOn observableSlots (interpretIR contract tx initialState) + (Compiler.Proofs.YulGeneration.Backends.Native.interpretIRRuntimeNative + fuel contract tx initialState observableSlots) := + layer3_contract_preserves_semantics_native_of_interpreter_bridge + fuel contract tx initialState observableSlots + hselector hNoWrap hvars hmemory htransient hreturn hparamErase + hdispatchGuardSafe hNoHasSelector hHasSelectorDead hLoopFree hWF hNoFallback + hNoReceive hFunctions hFuel + (nativeIRRuntimeAgreesWithInterpreter_of_lowered_callDispatcher_agree + hLower hEnv hNativeCallDispatcher) + /-! ## Layers 2+3 Composition -/ /-- Reference-oracle end-to-end wrapper: given a successfully compiled @@ -553,6 +1008,106 @@ theorem layers2_3_ir_matches_yul_evmYulLean spec selectors hSupported irContract hCompile) hStaticParams hSafeBodies) +/-- Native end-to-end theorem seam for supported compiler-produced contracts. + +The conclusion targets `Native.interpretIRRuntimeNative` directly. The remaining +generated-fragment proof obligation is exactly +`nativeIRRuntimeAgreesWithInterpreter`: native EVMYulLean execution of the +emitted runtime code must agree with the current EVMYulLean-backed interpreter +oracle for the same fuel, transaction, initial storage/events, and observable +storage-slot materialization. -/ +theorem layers2_3_ir_matches_native_evmYulLean_of_interpreter_bridge + (fuel : Nat) + (spec : CompilationModel.CompilationModel) (selectors : List Nat) + (irContract : IRContract) (tx : IRTransaction) + (initialState : IRState) (observableSlots : List Nat) + (hCompile : CompilationModel.compile spec selectors = .ok irContract) + (hSupported : SupportedSpec spec selectors) + (hStaticParams : ∀ entry, entry ∈ SourceSemantics.selectorFunctionPairs spec selectors → + Compiler.Proofs.YulGeneration.Backends.AllStaticScalarParams entry.1.params) + (hSafeBodies : + ∀ entry, entry ∈ SourceSemantics.selectorFunctionPairs spec selectors → + Compiler.Proofs.YulGeneration.Backends.BridgedSafeStmts spec.fields + spec.errors .calldata [] false entry.1.body) + (hselector : tx.functionSelector < selectorModulus) + (hNoWrap : 4 + tx.args.length * 32 < evmModulus) + (hvars : initialState.vars = []) + (hmemory : initialState.memory = fun _ => 0) + (htransient : initialState.transientStorage = fun _ => 0) + (hreturn : initialState.returnValue = none) + (hparamErase : ∀ fn, fn ∈ irContract.functions → + paramLoadErasure fn tx (initialState.withTx tx)) + (hdispatchGuardSafe : ∀ fn, fn ∈ irContract.functions → DispatchGuardsSafe fn tx) + (hNoHasSelector : ∀ fn, fn ∈ irContract.functions → yulStmtsNoRef "__has_selector" fn.body) + (hHasSelectorDead : ∀ fn, fn ∈ irContract.functions → HasSelectorDeadBridge fn.body) + (hLoopFree : ∀ fn, fn ∈ irContract.functions → yulStmtsLoopFree fn.body = true) + (hWF : ContractWF irContract) (hNoFallback : irContract.fallbackEntrypoint = none) + (hNoReceive : irContract.receiveEntrypoint = none) + (hFuel : fuel = sizeOf (Compiler.emitYul irContract).runtimeCode + 1) + (hNativeBridge : nativeIRRuntimeAgreesWithInterpreter fuel irContract tx + initialState observableSlots) : + nativeResultsMatchOn observableSlots + (interpretIR irContract tx initialState) + (Compiler.Proofs.YulGeneration.Backends.Native.interpretIRRuntimeNative + fuel irContract tx initialState observableSlots) := + layer3_contract_preserves_semantics_native_of_interpreter_bridge + fuel irContract tx initialState observableSlots + hselector hNoWrap hvars hmemory htransient hreturn hparamErase + hdispatchGuardSafe hNoHasSelector hHasSelectorDead hLoopFree hWF hNoFallback + hNoReceive + (compiledExternalFunctions_bridged_of_safe_static + spec.fields spec.events spec.errors + (Compiler.Proofs.IRGeneration.Contract.compile_ok_yields_compiled_functions + spec selectors hSupported irContract hCompile) + hStaticParams hSafeBodies) + hFuel + hNativeBridge + +/-- Supported compiler-produced native theorem seam with the remaining native +obligation exposed at the concrete lowered `callDispatcher` boundary. -/ +theorem layers2_3_ir_matches_native_evmYulLean_of_lowered_callDispatcher_bridge + (fuel : Nat) (spec : CompilationModel.CompilationModel) (selectors : List Nat) + (irContract : IRContract) (tx : IRTransaction) (initialState : IRState) + (observableSlots : List Nat) (nativeContract : EvmYul.Yul.Ast.YulContract) + (hCompile : CompilationModel.compile spec selectors = .ok irContract) + (hSupported : SupportedSpec spec selectors) + (hStaticParams : ∀ entry, entry ∈ SourceSemantics.selectorFunctionPairs spec selectors → Compiler.Proofs.YulGeneration.Backends.AllStaticScalarParams entry.1.params) + (hSafeBodies : ∀ entry, entry ∈ SourceSemantics.selectorFunctionPairs spec selectors → + Compiler.Proofs.YulGeneration.Backends.BridgedSafeStmts spec.fields spec.errors .calldata [] false entry.1.body) + (hselector : tx.functionSelector < selectorModulus) + (hNoWrap : 4 + tx.args.length * 32 < evmModulus) + (hvars : initialState.vars = []) + (hmemory : initialState.memory = fun _ => 0) + (htransient : initialState.transientStorage = fun _ => 0) + (hreturn : initialState.returnValue = none) + (hparamErase : ∀ fn, fn ∈ irContract.functions → paramLoadErasure fn tx (initialState.withTx tx)) + (hdispatchGuardSafe : ∀ fn, fn ∈ irContract.functions → DispatchGuardsSafe fn tx) + (hNoHasSelector : ∀ fn, fn ∈ irContract.functions → yulStmtsNoRef "__has_selector" fn.body) + (hHasSelectorDead : ∀ fn, fn ∈ irContract.functions → HasSelectorDeadBridge fn.body) + (hLoopFree : ∀ fn, fn ∈ irContract.functions → yulStmtsLoopFree fn.body = true) + (hWF : ContractWF irContract) (hNoFallback : irContract.fallbackEntrypoint = none) + (hNoReceive : irContract.receiveEntrypoint = none) + (hFuel : fuel = sizeOf (Compiler.emitYul irContract).runtimeCode + 1) + (hLower : Compiler.Proofs.YulGeneration.Backends.lowerRuntimeContractNative + (Compiler.emitYul irContract).runtimeCode = .ok nativeContract) + (hEnv : Compiler.Proofs.YulGeneration.Backends.Native.validateNativeRuntimeEnvironment + (Compiler.emitYul irContract).runtimeCode (YulTransaction.ofIR tx) = .ok ()) + (hNativeCallDispatcher : nativeCallDispatcherAgreesWithInterpreter fuel irContract tx initialState observableSlots nativeContract) : + nativeResultsMatchOn observableSlots (interpretIR irContract tx initialState) + (Compiler.Proofs.YulGeneration.Backends.Native.interpretIRRuntimeNative + fuel irContract tx initialState observableSlots) := + layer3_contract_preserves_semantics_native_of_lowered_callDispatcher_bridge + fuel irContract tx initialState observableSlots nativeContract + hselector hNoWrap hvars hmemory htransient hreturn hparamErase + hdispatchGuardSafe hNoHasSelector hHasSelectorDead hLoopFree hWF hNoFallback + hNoReceive + (compiledExternalFunctions_bridged_of_safe_static + spec.fields spec.events spec.errors + (Compiler.Proofs.IRGeneration.Contract.compile_ok_yields_compiled_functions + spec selectors hSupported irContract hCompile) + hStaticParams hSafeBodies) + hFuel hLower hEnv hNativeCallDispatcher + /-! ## Concrete Instantiation: SimpleStorage -/ /-- SimpleStorage end-to-end: compile → IR → Yul preserves semantics. -/ diff --git a/Compiler/Proofs/IRGeneration/Contract.lean b/Compiler/Proofs/IRGeneration/Contract.lean index cd25955c0..6cdb1f10a 100644 --- a/Compiler/Proofs/IRGeneration/Contract.lean +++ b/Compiler/Proofs/IRGeneration/Contract.lean @@ -538,8 +538,9 @@ private theorem compileValidatedCore_ok_yields_internalFunctions_nil have hdynamicBytesEq : contractUsesDynamicBytesEq model = false := hSupported.contractUsesDynamicBytesEq_eq_false unfold compileValidatedCore at hcore - rw [hSupported.normalizedFields, hfallback, hreceive, harray, hstorageArray, - hdynamicBytesEq, hnoInternalFns, hSupported.noAdtTypes] at hcore + rw [hSupported.normalizedFields, hfallback, hreceive, + contractUsesPlainArrayElement, contractUsesArrayElementWord, harray, + hstorageArray, hdynamicBytesEq, hnoInternalFns, hSupported.noAdtTypes] at hcore simp only [bind, Except.bind, pure, Except.pure, List.mapM_nil] at hcore rcases hmap : ((model.functions.filter @@ -766,8 +767,9 @@ theorem compile_ok_yields_internalFunctions_nil_except_mapping_writes · simp [hvalidate] at hcompile · simp [hvalidate] at hcompile unfold compileValidatedCore at hcompile - rw [hSupported.normalizedFields, hfallback, hreceive, harray, hstorageArray, - hdynamicBytesEq, hnoInternalFns, hSupported.noAdtTypes] at hcompile + rw [hSupported.normalizedFields, hfallback, hreceive, + contractUsesPlainArrayElement, contractUsesArrayElementWord, harray, + hstorageArray, hdynamicBytesEq, hnoInternalFns, hSupported.noAdtTypes] at hcompile simp only [bind, Except.bind, pure, Except.pure, List.mapM_nil] at hcompile rcases hmap : ((model.functions.filter diff --git a/Compiler/Proofs/IRGeneration/ExprCore.lean b/Compiler/Proofs/IRGeneration/ExprCore.lean index 1fb1e1df4..5ab223789 100644 --- a/Compiler/Proofs/IRGeneration/ExprCore.lean +++ b/Compiler/Proofs/IRGeneration/ExprCore.lean @@ -37,7 +37,7 @@ def exprBoundNames : Expr → List String | .externalCall _ args | .internalCall _ args | .adtConstruct _ _ args => exprListBoundNames args | .adtTag _ field => [field] | .adtField _ _ _ _ storageField => [storageField] - | .arrayElement name index => name :: exprBoundNames index + | .arrayElement name index | .arrayElementWord name index _ _ => name :: exprBoundNames index | .arrayLength name => [name] | .storageArrayLength name => [name] | .storageArrayElement name index => name :: exprBoundNames index diff --git a/Compiler/Proofs/IRGeneration/FunctionBody.lean b/Compiler/Proofs/IRGeneration/FunctionBody.lean index ab19651c1..158e25dc8 100644 --- a/Compiler/Proofs/IRGeneration/FunctionBody.lean +++ b/Compiler/Proofs/IRGeneration/FunctionBody.lean @@ -7949,7 +7949,7 @@ private theorem compileStmt_ok_any_scope_aux rcases hok with ⟨ir, hir⟩ simp [CompilationModel.compileStmt, lookupAdtTypeDef, Except.bind, bind] at hir -- All remaining cases: inScopeNames is unused, so the result is identical - | letVar | assignVar | setStorage | setStorageAddr | storageArrayPush + | letVar | assignVar | setStorage | setStorageAddr | setStorageWord | storageArrayPush | storageArrayPop | setStorageArrayElement | setMapping | setMappingWord | setMappingPackedWord | setMapping2 | setMapping2Word | setMappingUint | setMappingChain | setStructMember | setStructMember2 | require @@ -8066,7 +8066,7 @@ private theorem compileStmt_ok_any_scope_with_surface_aux | matchAdt adtName scrutinee branches => rcases hok with ⟨ir, hir⟩ simp [CompilationModel.compileStmt, lookupAdtTypeDef, Except.bind, bind] at hir - | letVar | assignVar | setStorage | setStorageAddr | storageArrayPush + | letVar | assignVar | setStorage | setStorageAddr | setStorageWord | storageArrayPush | storageArrayPop | setStorageArrayElement | setMapping | setMappingWord | setMappingPackedWord | setMapping2 | setMapping2Word | setMappingUint | setMappingChain | setStructMember | setStructMember2 | require diff --git a/Compiler/Proofs/IRGeneration/GenericInduction.lean b/Compiler/Proofs/IRGeneration/GenericInduction.lean index ca4abbc67..1e2936f1e 100644 --- a/Compiler/Proofs/IRGeneration/GenericInduction.lean +++ b/Compiler/Proofs/IRGeneration/GenericInduction.lean @@ -835,6 +835,35 @@ private theorem legacyCompatibleExternalStmtList_of_compileStmt_ok_tstore [] LegacyCompatibleExternalStmtList.nil +private theorem legacyCompatibleExternalStmtList_of_compileStmt_ok_setStorageWord + {fields : List Field} + {events : List EventDef} + {errors : List ErrorDef} + {inScopeNames : List String} + {field : String} + {wordOffset : Nat} + {value : Expr} + {bodyIR : List YulStmt} + (hcompile : + CompilationModel.compileStmt + fields events errors .calldata [] false inScopeNames [] (.setStorageWord field wordOffset value) = + Except.ok bodyIR) : + LegacyCompatibleExternalStmtList bodyIR := by + unfold CompilationModel.compileStmt at hcompile + rcases hfind : findFieldWithResolvedSlot fields field with _ | ⟨f, slot⟩ + · simp [hfind] at hcompile + · rcases hvalue : CompilationModel.compileExpr fields .calldata value with _ | valueIR + · simp [hfind, hvalue] at hcompile + · simp [hfind, hvalue] at hcompile + cases hcompile + exact LegacyCompatibleExternalStmtList.expr + (YulExpr.call "sstore" + [if wordOffset = 0 then YulExpr.lit slot + else YulExpr.call "add" [YulExpr.lit slot, YulExpr.lit wordOffset], + valueIR]) + [] + LegacyCompatibleExternalStmtList.nil + mutual /-- On the current supported contract surface, successful single-statement compilation stays inside the legacy helper-free external Yul subset. This is @@ -871,6 +900,9 @@ theorem legacyCompatibleExternalStmtList_of_compileStmt_ok_on_supportedContractS (requireAddressField := true) hnoPacked hcompile + | setStorageWord field wordOffset value => + simp [stmtTouchesUnsupportedContractSurface] at hsurface + exact legacyCompatibleExternalStmtList_of_compileStmt_ok_setStorageWord hcompile | require cond message => simp [stmtTouchesUnsupportedContractSurface] at hsurface exact legacyCompatibleExternalStmtList_of_compileStmt_ok_require hcompile @@ -1618,7 +1650,7 @@ theorem legacyCompatibleExternalStmtList_of_compileStmt_ok_on_supportedContractS | setStructMember2 field key1 key2 memberName value => unfold CompilationModel.compileStmt at hcompile exact legacyCompatibleExternalStmtList_of_compileSetStructMember2_ok hcompile - | letVar _ _ | assignVar _ _ | setStorage _ _ | setStorageAddr _ _ + | letVar _ _ | assignVar _ _ | setStorage _ _ | setStorageAddr _ _ | setStorageWord _ _ _ | storageArrayPush _ _ | storageArrayPop _ | setStorageArrayElement _ _ _ | require _ | requireError _ _ | revertError _ _ | «return» _ | returnValues _ | returnArray _ | returnBytes _ @@ -3005,6 +3037,14 @@ inductive StmtListScopeDiscipline (fieldNames : List String) : List String → L FunctionBody.exprBoundNamesInScope value scope → StmtListScopeDiscipline fieldNames (stmtNextScope scope (.setStorageAddr fieldName value)) rest → StmtListScopeDiscipline fieldNames scope (.setStorageAddr fieldName value :: rest) + | setStorageWord {scope : List String} {fieldName : String} {wordOffset : Nat} {value : Expr} + {rest : List Stmt} : + fieldName ∈ fieldNames → + FunctionBody.ExprCompileCore value → + FunctionBody.exprBoundNamesInScope value scope → + StmtListScopeDiscipline fieldNames + (stmtNextScope scope (.setStorageWord fieldName wordOffset value)) rest → + StmtListScopeDiscipline fieldNames scope (.setStorageWord fieldName wordOffset value :: rest) | mstore {scope : List String} {offset value : Expr} {rest : List Stmt} : FunctionBody.ExprCompileCore offset → FunctionBody.exprBoundNamesInScope offset scope → @@ -3061,6 +3101,11 @@ inductive StmtListScopeCore (fieldNames : List String) : List Stmt → Prop wher FunctionBody.ExprCompileCore value → StmtListScopeCore fieldNames rest → StmtListScopeCore fieldNames (.setStorageAddr fieldName value :: rest) + | setStorageWord {fieldName : String} {wordOffset : Nat} {value : Expr} {rest : List Stmt} : + fieldName ∈ fieldNames → + FunctionBody.ExprCompileCore value → + StmtListScopeCore fieldNames rest → + StmtListScopeCore fieldNames (.setStorageWord fieldName wordOffset value :: rest) | mstore {offset value : Expr} {rest : List Stmt} : FunctionBody.ExprCompileCore offset → FunctionBody.ExprCompileCore value → @@ -3315,6 +3360,15 @@ private theorem stmtListScopeCore_of_unsupportedContractSurface_eq_false exact fieldName_mem_fields_of_compileSetStorage_ok hhead) (exprCompileCore_of_exprTouchesUnsupportedContractSurface_eq_false (by simpa [stmtTouchesUnsupportedContractSurface] using hstmtSurface)) ihRest + | setStorageWord fieldName wordOffset value => + exact .setStorageWord + (by + simp only [CompilationModel.compileStmt, bind, Except.bind] at hhead + rcases hfind : findFieldWithResolvedSlot fields fieldName with _ | ⟨f, slot⟩ + · simp [hfind] at hhead + · exact fieldName_mem_fields_of_findFieldWithResolvedSlot_some hfind) + (exprCompileCore_of_exprTouchesUnsupportedContractSurface_eq_false + (by simpa [stmtTouchesUnsupportedContractSurface] using hstmtSurface)) ihRest | require cond message => exact .require (exprCompileCore_of_exprTouchesUnsupportedContractSurface_eq_false (by simpa [stmtTouchesUnsupportedContractSurface] using hstmtSurface)) ihRest @@ -3398,6 +3452,16 @@ theorem stmtListScopeCore_prefix_of_compileStmtList_ok_of_stmtListTouchesUnsuppo (exprCompileCore_of_exprTouchesUnsupportedContractSurface_eq_false (by simpa [stmtTouchesUnsupportedContractSurface] using hstmtSurface)) (ih hrestSurface htail) + | setStorageWord fieldName wordOffset value => + exact StmtListScopeCore.setStorageWord + (by + simp only [CompilationModel.compileStmt, bind, Except.bind] at hhead + rcases hfind : findFieldWithResolvedSlot fields fieldName with _ | ⟨f, slot⟩ + · simp [hfind] at hhead + · exact fieldName_mem_fields_of_findFieldWithResolvedSlot_some hfind) + (exprCompileCore_of_exprTouchesUnsupportedContractSurface_eq_false + (by simpa [stmtTouchesUnsupportedContractSurface] using hstmtSurface)) + (ih hrestSurface htail) | require cond message => exact StmtListScopeCore.require (exprCompileCore_of_exprTouchesUnsupportedContractSurface_eq_false @@ -4073,6 +4137,28 @@ private theorem stmtListScopeDiscipline_of_validateScopedStmtListIdentifiers (by intro other hmem exact mem_stmtNextScope_of_mem_scope (hlocalsInScope other hmem))) + | setStorageWord hfield hvalueCore hrest ih => + rcases validateScopedStmtListIdentifiers_cons_ok_inv hvalidate with + ⟨nextLocalScope, hstmt, hrestValidate⟩ + have hstmt' := hstmt + unfold validateScopedStmtIdentifiers at hstmt' + revert hstmt' + rcases hExprVal : validateScopedExprIdentifiers context params paramScope dynamicParams localScope constructorArgCount _ with _ | _ + · intro h; simp [bind, Except.bind] at h + · simp only [bind, Except.bind, pure, Except.pure] + intro h; cases h + exact StmtListScopeDiscipline.setStorageWord + hfield + hvalueCore + (exprBoundNamesInScope_of_validateScopedExprIdentifiers_core + hvalueCore hExprVal hparamsInScope hlocalsInScope) + (ih hrestValidate + (by + intro other hmem + exact mem_stmtNextScope_of_mem_scope (hparamsInScope other hmem)) + (by + intro other hmem + exact mem_stmtNextScope_of_mem_scope (hlocalsInScope other hmem))) | mstore hcoreOffset hcoreValue hrest ih => rcases validateScopedStmtListIdentifiers_cons_ok_inv hvalidate with ⟨nextLocalScope, hstmt, hrestValidate⟩ @@ -4336,6 +4422,25 @@ private theorem scopeNamesPresent_foldl_stmtNextScope_of_validateScopedStmtListI intro name hname exact mem_stmtNextScope_of_mem_scope (hlocalsInScope name hname)) other hmem + | setStorageWord hfield hvalueCore hrest ih => + rcases validateScopedStmtListIdentifiers_cons_ok_inv hvalidate with + ⟨nextLocalScope, hstmt, hrestValidate⟩ + have hstmt' := hstmt + unfold validateScopedStmtIdentifiers at hstmt' + revert hstmt' + rcases hExprVal : validateScopedExprIdentifiers context params paramScope dynamicParams localScope constructorArgCount _ with _ | _ + · intro h; simp [bind, Except.bind] at h + · simp only [bind, Except.bind, pure, Except.pure] + intro h; cases h + intro other hmem + exact ih hrestValidate + (by + intro name hname + exact mem_stmtNextScope_of_mem_scope (hparamsInScope name hname)) + (by + intro name hname + exact mem_stmtNextScope_of_mem_scope (hlocalsInScope name hname)) + other hmem | mstore hcoreOffset hcoreValue hrest ih => rcases validateScopedStmtListIdentifiers_cons_ok_inv hvalidate with ⟨nextLocalScope, hstmt, hrestValidate⟩ @@ -4611,6 +4716,18 @@ private theorem stmtListScopeDiscipline_scope_names · right; left; exact hbind · right; right; left; exact hassign · right; right; right; exact hfld + | setStorageWord _hfield hcore hinScope _ ih => + intro other hmem + simp only [List.foldl] at hmem + have htail := ih other hmem + simp [stmtNextScope, collectStmtNames, collectStmtListBindNames, collectStmtBindNames, + collectStmtListAssignedNames, collectStmtAssignedNames] at htail ⊢ + rcases htail with hvalue | hscope | hbind | hassign | hfld + · left; exact hinScope _ (collectExprNames_mem_exprBoundNames_of_core hcore _ hvalue) + · left; exact hscope + · right; left; exact hbind + · right; right; left; exact hassign + · right; right; right; exact hfld | mstore hcoreOffset hinScopeOffset hcoreValue hinScopeValue _ ih => intro other hmem simp only [List.foldl] at hmem diff --git a/Compiler/Proofs/IRGeneration/SourceSemantics.lean b/Compiler/Proofs/IRGeneration/SourceSemantics.lean index e1e94e5b6..754c45fd0 100644 --- a/Compiler/Proofs/IRGeneration/SourceSemantics.lean +++ b/Compiler/Proofs/IRGeneration/SourceSemantics.lean @@ -313,6 +313,17 @@ def writeUintSlots (world : Verity.ContractState) (slots : List Nat) (value : Na storage := fun slot => if slots.contains slot then word else world.storage slot } +def writeStorageWordSlot (world : Verity.ContractState) (slot wordOffset value : Nat) : + Verity.ContractState := + let word : Verity.Core.Uint256 := value + let addr := Verity.wordToAddress word + let target := wordNormalize (slot + wordOffset) + { world with + storage := fun current => + if current = target then word else world.storage current, + storageAddr := fun current => + if current = target then addr else world.storageAddr current } + def writeAddressSlots (world : Verity.ContractState) (slots : List Nat) (value : Nat) : Verity.ContractState := let addr := Verity.wordToAddress (value : Verity.Core.Uint256) @@ -1282,6 +1293,14 @@ private theorem evalExpr_arrayElement (index : Expr) : evalExpr fields state (.arrayElement name index) = none := rfl +private theorem evalExpr_arrayElementWord + (fields : List Field) + (state : RuntimeState) + (name : String) + (index : Expr) + (elementWords wordOffset : Nat) : + evalExpr fields state (.arrayElementWord name index elementWords wordOffset) = none := rfl + private theorem evalExpr_mappingWord (fields : List Field) (state : RuntimeState) @@ -1467,6 +1486,13 @@ mutual | some slots, some resolved => .continue { state with world := writeUintSlots state.world slots resolved } | _, _ => .revert + | state, .setStorageWord fieldName wordOffset value => + match findFieldWithResolvedSlot fields fieldName, evalExpr fields state value with + | some (_, slot), some resolved => + .continue + { state with + world := writeStorageWordSlot state.world slot wordOffset resolved } + | _, _ => .revert | state, .setMapping fieldName key value => match findFieldWriteSlots fields fieldName, evalExpr fields state key, @@ -1695,6 +1721,13 @@ mutual | some slots, some resolved => .continue { state with world := writeUintSlots state.world slots resolved } | _, _ => .revert + | state, .setStorageWord fieldName wordOffset value => + match findFieldWithResolvedSlot fields fieldName, evalExpr fields state value with + | some (_, slot), some resolved => + .continue + { state with + world := writeStorageWordSlot state.world slot wordOffset resolved } + | _, _ => .revert | state, .setMapping fieldName key value => match findFieldWriteSlots fields fieldName, evalExpr fields state key, @@ -2626,6 +2659,14 @@ mutual | some slots, some resolved => .continue { state with world := writeUintSlots state.world slots resolved } | _, _ => .revert + | .setStorageWord fieldName wordOffset value => + match findFieldWithResolvedSlot fields fieldName, + evalExprWithHelpers spec fields fuel state value with + | some (_, slot), some resolved => + .continue + { state with + world := writeStorageWordSlot state.world slot wordOffset resolved } + | _, _ => .revert | .setMapping fieldName key value => match findFieldWriteSlots fields fieldName, evalExprWithHelpers spec fields fuel state key, @@ -3583,6 +3624,8 @@ mutual simpa [evalExprWithHelpers, evalExpr_mapping, evalExpr_mappingUint, hb] | arrayElement _ b => simp [evalExprWithHelpers, evalExpr_arrayElement] + | arrayElementWord _ b _ _ => + simp [evalExprWithHelpers, evalExpr_arrayElementWord] | mappingWord _ b _ | mappingPackedWord _ b _ _ | structMember _ b _ => simp only [exprTouchesUnsupportedHelperSurface] at hsurface have hb := @@ -3935,6 +3978,10 @@ private theorem execStmtWithHelpers_eq_execStmt_of_helperSurfaceClosed_aux simp only [stmtTouchesUnsupportedHelperSurface] at hsurface simp [execStmtWithHelpers, execStmtWithEvents, evalExprWithHelpers_eq_evalExpr_of_helperSurfaceClosed spec fields fuel state value hsurface] + | .setStorageWord _ _ value => + simp only [stmtTouchesUnsupportedHelperSurface] at hsurface + simp [execStmtWithHelpers, execStmtWithEvents, + evalExprWithHelpers_eq_evalExpr_of_helperSurfaceClosed spec fields fuel state value hsurface] | .setStorageAddr _ value => simp only [stmtTouchesUnsupportedHelperSurface] at hsurface simp [execStmtWithHelpers, execStmtWithEvents, diff --git a/Compiler/Proofs/IRGeneration/SourceSemanticsFeatureTest.lean b/Compiler/Proofs/IRGeneration/SourceSemanticsFeatureTest.lean index 493bf377a..28274fc09 100644 --- a/Compiler/Proofs/IRGeneration/SourceSemanticsFeatureTest.lean +++ b/Compiler/Proofs/IRGeneration/SourceSemanticsFeatureTest.lean @@ -77,6 +77,30 @@ private def indexedEmitSourceSpec : CompilationModel := returnType := none body := [Stmt.emit "Ping" [Expr.param "topic", Expr.param "value"], .stop] } ] } +private def storageWordFields : List Field := + [{ name := "root", ty := .uint256, «slot» := some 10 }] + +private def storageWordSpec : CompilationModel := + { name := "StorageWordSource" + fields := storageWordFields + constructor := none + functions := [] } + +private def storageWordState : SourceSemantics.RuntimeState := + { world := Verity.defaultState, bindings := [] } + +private def resultStorageAt? (slot : Nat) : SourceSemantics.StmtResult → Option Nat + | .continue st => some (st.world.storage slot).val + | .stop st => some (st.world.storage slot).val + | .return _ st => some (st.world.storage slot).val + | .revert => none + +private def resultStorageAddrAt? (slot : Nat) : SourceSemantics.StmtResult → Option Verity.Address + | .continue st => some (st.world.storageAddr slot) + | .stop st => some (st.world.storageAddr slot) + | .return _ st => some (st.world.storageAddr slot) + | .revert => none + example : (sourceContractSemantics simpleStorageSupportedSpecModel [0x2e64cec1] { sender := 7, functionSelector := 0x2e64cec1, args := [] } @@ -165,7 +189,45 @@ example : SourceSemantics.encodeEvents [{ name := "Ping" args := [Verity.Core.Uint256.ofNat (22 % Compiler.Constants.evmModulus)] - indexedArgs := [Verity.Core.Uint256.ofNat (11 % Compiler.Constants.evmModulus)] }] := by + indexedArgs := [ + SourceSemantics.eventSignatureTopic + { name := "Ping" + params := [ + { name := "topic", ty := .uint256, kind := .indexed }, + { name := "value", ty := .uint256, kind := .unindexed } + ] }, + Verity.Core.Uint256.ofNat (11 % Compiler.Constants.evmModulus)] }] := by + native_decide + +example : + resultStorageAt? 12 + (SourceSemantics.execStmtWithEvents storageWordFields [] storageWordState + (Stmt.setStorageWord "root" 2 (.literal 99))) = some 99 := by + native_decide + +example : + resultStorageAt? 12 + (SourceSemantics.execStmt storageWordFields storageWordState + (Stmt.setStorageWord "root" 2 (.literal 99))) = some 99 := by + native_decide + +example : + resultStorageAddrAt? 12 + (SourceSemantics.execStmt storageWordFields storageWordState + (Stmt.setStorageWord "root" 2 (.literal 99))) = + some (Verity.wordToAddress (99 : Verity.Uint256)) := by + native_decide + +example : + resultStorageAt? 12 + (SourceSemantics.execStmtWithHelpers storageWordSpec storageWordFields 1 storageWordState + (Stmt.setStorageWord "root" 2 (.literal 99))) = some 99 := by + native_decide + +example : + resultStorageAt? 10 + (SourceSemantics.execStmt storageWordFields storageWordState + (Stmt.setStorageWord "root" 2 (.literal 99))) = some 0 := by native_decide end Compiler.Proofs.IRGeneration.SourceSemanticsFeatureTest diff --git a/Compiler/Proofs/IRGeneration/SupportedSpec.lean b/Compiler/Proofs/IRGeneration/SupportedSpec.lean index 2a1d67044..09313d046 100644 --- a/Compiler/Proofs/IRGeneration/SupportedSpec.lean +++ b/Compiler/Proofs/IRGeneration/SupportedSpec.lean @@ -558,6 +558,7 @@ def exprTouchesUnsupportedConstructorRawCalldataSurface : Expr → Bool true | .mapping _ a | .mappingWord _ a _ | .mappingPackedWord _ a _ _ | .mappingUint _ a | .structMember _ a _ | .arrayElement _ a + | .arrayElementWord _ a _ _ | .storageArrayElement _ a => exprTouchesUnsupportedConstructorRawCalldataSurface a | .add a b | .sub a b | .mul a b | .div a b | .mod a b @@ -608,7 +609,8 @@ def exprListTouchesUnsupportedConstructorRawCalldataSurface : List Expr → Bool def stmtTouchesUnsupportedConstructorRawCalldataSurface : Stmt → Bool | .letVar _ value | .assignVar _ value | .setStorage _ value - | .setStorageAddr _ value | .require value _ | .return value + | .setStorageAddr _ value | .setStorageWord _ _ value + | .require value _ | .return value | .storageArrayPush _ value => exprTouchesUnsupportedConstructorRawCalldataSurface value | .setMapping _ key value | .setMappingWord _ key _ value @@ -701,7 +703,8 @@ def exprTouchesUnsupportedCoreSurface : Expr → Bool | .call _ _ _ _ _ _ _ | .staticcall _ _ _ _ _ _ | .delegatecall _ _ _ _ _ _ | .returndataSize | .extcodesize _ | .returndataOptionalBoolAt _ | .externalCall _ _ | .internalCall _ _ - | .arrayLength _ | .arrayElement _ _ | .storageArrayLength _ | .storageArrayElement _ _ + | .arrayLength _ | .arrayElement _ _ | .arrayElementWord _ _ _ _ + | .storageArrayLength _ | .storageArrayElement _ _ | .dynamicBytesEq _ _ | .adtConstruct _ _ _ | .adtTag _ _ | .adtField _ _ _ _ _ => true @@ -737,7 +740,7 @@ def exprTouchesUnsupportedStateSurface : Expr → Bool | .call _ _ _ _ _ _ _ | .staticcall _ _ _ _ _ _ | .delegatecall _ _ _ _ _ _ | .calldatasize | .returndataSize | .extcodesize _ | .returndataOptionalBoolAt _ | .externalCall _ _ | .internalCall _ _ - | .arrayLength _ | .arrayElement _ _ + | .arrayLength _ | .arrayElement _ _ | .arrayElementWord _ _ _ _ | .dynamicBytesEq _ _ => false | .mload a | .tload a | .calldataload a => exprTouchesUnsupportedStateSurface a | .adtConstruct _ _ _ | .adtTag _ _ | .adtField _ _ _ _ _ => true @@ -762,7 +765,8 @@ def exprTouchesUnsupportedCallSurface : Expr → Bool exprTouchesUnsupportedCallSurface a || exprTouchesUnsupportedCallSurface b | .min a b | .max a b | .wMulDown a b | .wDivUp a b | .ceilDiv a b => exprTouchesUnsupportedCallSurface a || exprTouchesUnsupportedCallSurface b - | .mapping _ b | .mappingUint _ b | .arrayElement _ b | .storageArrayElement _ b => + | .mapping _ b | .mappingUint _ b | .arrayElement _ b | .arrayElementWord _ b _ _ + | .storageArrayElement _ b => exprTouchesUnsupportedCallSurface b | .mappingChain _ _ => true | .bitNot a | .logicalNot a | .mappingWord _ a _ | .mappingPackedWord _ a _ _ @@ -802,7 +806,8 @@ def exprTouchesUnsupportedHelperSurface : Expr → Bool exprTouchesUnsupportedHelperSurface a || exprTouchesUnsupportedHelperSurface b | .min a b | .max a b | .wMulDown a b | .wDivUp a b | .ceilDiv a b => exprTouchesUnsupportedHelperSurface a || exprTouchesUnsupportedHelperSurface b - | .mapping _ b | .mappingUint _ b | .arrayElement _ b | .storageArrayElement _ b => + | .mapping _ b | .mappingUint _ b | .arrayElement _ b | .arrayElementWord _ b _ _ + | .storageArrayElement _ b => exprTouchesUnsupportedHelperSurface b | .mappingChain _ _ => true | .bitNot a | .logicalNot a | .mappingWord _ a _ | .mappingPackedWord _ a _ _ @@ -850,7 +855,8 @@ def exprTouchesInternalHelperSurface : Expr → Bool exprTouchesInternalHelperSurface a || exprTouchesInternalHelperSurface b | .min a b | .max a b | .wMulDown a b | .wDivUp a b | .ceilDiv a b => exprTouchesInternalHelperSurface a || exprTouchesInternalHelperSurface b - | .mapping _ b | .mappingUint _ b | .arrayElement _ b | .storageArrayElement _ b => + | .mapping _ b | .mappingUint _ b | .arrayElement _ b | .arrayElementWord _ b _ _ + | .storageArrayElement _ b => exprTouchesInternalHelperSurface b | .mappingChain _ [] => false | .mappingChain field (k :: ks) => @@ -892,7 +898,8 @@ def exprTouchesUnsupportedForeignSurface : Expr → Bool exprTouchesUnsupportedForeignSurface a || exprTouchesUnsupportedForeignSurface b | .min a b | .max a b | .wMulDown a b | .wDivUp a b | .ceilDiv a b => exprTouchesUnsupportedForeignSurface a || exprTouchesUnsupportedForeignSurface b - | .mapping _ b | .mappingUint _ b | .arrayElement _ b | .storageArrayElement _ b => + | .mapping _ b | .mappingUint _ b | .arrayElement _ b | .arrayElementWord _ b _ _ + | .storageArrayElement _ b => exprTouchesUnsupportedForeignSurface b | .mappingChain _ _ => true | .bitNot a | .logicalNot a | .mappingWord _ a _ | .mappingPackedWord _ a _ _ @@ -931,7 +938,8 @@ def exprTouchesUnsupportedLowLevelSurface : Expr → Bool exprTouchesUnsupportedLowLevelSurface a || exprTouchesUnsupportedLowLevelSurface b | .min a b | .max a b | .wMulDown a b | .wDivUp a b | .ceilDiv a b => exprTouchesUnsupportedLowLevelSurface a || exprTouchesUnsupportedLowLevelSurface b - | .mapping _ b | .mappingUint _ b | .arrayElement _ b | .storageArrayElement _ b => + | .mapping _ b | .mappingUint _ b | .arrayElement _ b | .arrayElementWord _ b _ _ + | .storageArrayElement _ b => exprTouchesUnsupportedLowLevelSurface b | .mappingChain _ _ => true | .bitNot a | .logicalNot a | .mappingWord _ a _ | .mappingPackedWord _ a _ _ @@ -987,7 +995,8 @@ def exprTouchesUnsupportedContractSurface (expr : Expr) : Bool := | .call _ _ _ _ _ _ _ | .staticcall _ _ _ _ _ _ | .delegatecall _ _ _ _ _ _ | .returndataSize | .extcodesize _ | .returndataOptionalBoolAt _ | .externalCall _ _ | .internalCall _ _ - | .arrayLength _ | .arrayElement _ _ | .storageArrayLength _ | .storageArrayElement _ _ + | .arrayLength _ | .arrayElement _ _ | .arrayElementWord _ _ _ _ + | .storageArrayLength _ | .storageArrayElement _ _ | .dynamicBytesEq _ _ | .adtConstruct _ _ _ | .adtTag _ _ | .adtField _ _ _ _ _ => true @@ -999,6 +1008,7 @@ def stmtTouchesUnsupportedEffectSurface : Stmt → Bool | .returnBytes _ | .returnStorageWords _ | .emit _ _ | .rawLog _ _ _ | .externalCallBind _ _ _ | .tryExternalCallBind _ _ _ _ | .ecm _ _ => true | .letVar _ _ | .assignVar _ _ | .setStorage _ _ | .setStorageAddr _ _ + | .setStorageWord _ _ _ | .require _ _ | .return _ | .mstore _ _ | .tstore _ _ | .stop | .setMapping _ _ _ | .setMappingWord _ _ _ _ | .setMappingPackedWord _ _ _ _ _ | .setMapping2 _ _ _ _ @@ -1022,6 +1032,8 @@ def stmtTouchesUnsupportedCoreSurface : Stmt → Bool exprTouchesUnsupportedCoreSurface value | .setStorageAddr _ value => exprTouchesUnsupportedCoreSurface value + | .setStorageWord _ _ value => + exprTouchesUnsupportedCoreSurface value | .setMapping _ key value | .setMappingWord _ key _ value | .setMappingPackedWord _ key _ _ value | .setMappingUint _ key value | .setStructMember _ key _ value => @@ -1068,7 +1080,7 @@ def stmtTouchesUnsupportedStateSurface : Stmt → Bool exprTouchesUnsupportedStateSurface cond | .setStorageAddr _ value => exprTouchesUnsupportedStateSurface value - | .setMapping _ _ _ | .setMappingWord _ _ _ _ | .setMappingPackedWord _ _ _ _ _ + | .setStorageWord _ _ _ | .setMapping _ _ _ | .setMappingWord _ _ _ _ | .setMappingPackedWord _ _ _ _ _ | .setMapping2 _ _ _ _ | .setMapping2Word _ _ _ _ _ | .setMappingUint _ _ _ | .setMappingChain _ _ _ | .setStructMember _ _ _ _ | .setStructMember2 _ _ _ _ _ @@ -1105,7 +1117,7 @@ def stmtTouchesUnsupportedStateSurfaceExceptMappingWrites : Stmt → Bool generic theorem. -/ def stmtTouchesUnsupportedCallSurface : Stmt → Bool | .letVar _ value | .assignVar _ value | .setStorage _ value - | .setStorageAddr _ value | .storageArrayPush _ value => + | .setStorageAddr _ value | .setStorageWord _ _ value | .storageArrayPush _ value => exprTouchesUnsupportedCallSurface value | .setMapping _ key value | .setMappingWord _ key _ value | .setMappingPackedWord _ key _ _ value | .setMappingUint _ key value @@ -1145,7 +1157,7 @@ def stmtTouchesUnsupportedCallSurface : Stmt → Bool def stmtTouchesUnsupportedHelperSurface : Stmt → Bool | .letVar _ value | .assignVar _ value | .setStorage _ value - | .setStorageAddr _ value | .storageArrayPush _ value => + | .setStorageAddr _ value | .setStorageWord _ _ value | .storageArrayPush _ value => exprTouchesUnsupportedHelperSurface value | .setMapping _ key value | .setMappingWord _ key _ value | .setMappingPackedWord _ key _ _ value | .setMappingUint _ key value @@ -1187,7 +1199,7 @@ this isolates heads that genuinely execute internal helpers, leaving residual non-helper unsupported cases to be tracked separately. -/ def stmtTouchesInternalHelperSurface : Stmt → Bool | .letVar _ value | .assignVar _ value | .setStorage _ value - | .setStorageAddr _ value | .storageArrayPush _ value => + | .setStorageAddr _ value | .setStorageWord _ _ value | .storageArrayPush _ value => exprTouchesInternalHelperSurface value | .setMapping _ key value | .setMappingWord _ key _ value | .setMappingPackedWord _ key _ _ value | .setMappingUint _ key value @@ -1252,7 +1264,7 @@ soundness and world-preservation lemmas directly, rather than bundling them with direct helper statements or recursive structural transport. -/ def stmtTouchesExprInternalHelperSurface : Stmt → Bool | .letVar _ value | .assignVar _ value | .setStorage _ value - | .setStorageAddr _ value | .storageArrayPush _ value => + | .setStorageAddr _ value | .setStorageWord _ _ value | .storageArrayPush _ value => exprTouchesInternalHelperSurface value | .setMapping _ key value | .setMappingWord _ key _ value | .setMappingPackedWord _ key _ _ value | .setMappingUint _ key value @@ -1297,7 +1309,7 @@ def stmtTouchesStructuralInternalHelperSurface : Stmt → Bool stmtListTouchesInternalHelperSurface body | .letVar _ _ | .assignVar _ _ | .setStorage _ _ | .require _ _ | .return _ | .internalCall _ _ | .internalCallAssign _ _ _ - | .stop | .setStorageAddr _ _ | .mstore _ _ | .tstore _ _ + | .stop | .setStorageAddr _ _ | .setStorageWord _ _ _ | .mstore _ _ | .tstore _ _ | .calldatacopy _ _ _ | .returndataCopy _ _ _ | .revertReturndata | .externalCallBind _ _ _ | .tryExternalCallBind _ _ _ _ | .ecm _ _ | .setMapping _ _ _ | .setMappingWord _ _ _ _ @@ -1314,7 +1326,7 @@ def stmtTouchesStructuralInternalHelperSurface : Stmt → Bool def stmtTouchesUnsupportedForeignSurface : Stmt → Bool | .letVar _ value | .assignVar _ value | .setStorage _ value - | .setStorageAddr _ value | .storageArrayPush _ value => + | .setStorageAddr _ value | .setStorageWord _ _ value | .storageArrayPush _ value => exprTouchesUnsupportedForeignSurface value | .setMapping _ key value | .setMappingWord _ key _ value | .setMappingPackedWord _ key _ _ value | .setMappingUint _ key value @@ -1354,7 +1366,7 @@ def stmtTouchesUnsupportedForeignSurface : Stmt → Bool def stmtTouchesUnsupportedLowLevelSurface : Stmt → Bool | .letVar _ value | .assignVar _ value | .setStorage _ value - | .setStorageAddr _ value | .storageArrayPush _ value => + | .setStorageAddr _ value | .setStorageWord _ _ value | .storageArrayPush _ value => exprTouchesUnsupportedLowLevelSurface value | .setMapping _ key value | .setMappingWord _ key _ value | .setMappingPackedWord _ key _ _ value | .setMappingUint _ key value @@ -1398,6 +1410,8 @@ def stmtTouchesUnsupportedContractSurface (stmt : Stmt) : Bool := exprTouchesUnsupportedContractSurface value | .setStorageAddr _ value => exprTouchesUnsupportedContractSurface value + | .setStorageWord _ _ value => + exprTouchesUnsupportedContractSurface value | .require cond _ | .return cond => exprTouchesUnsupportedContractSurface cond | .mstore offset value | .tstore offset value => @@ -1583,6 +1597,7 @@ mutual calleeName :: exprListInternalHelperCallNames args | .mapping _ key | .mappingWord _ key _ | .mappingPackedWord _ key _ _ | .mappingUint _ key | .structMember _ key _ | .arrayElement _ key + | .arrayElementWord _ key _ _ | .storageArrayElement _ key | .mload key | .tload key | .calldataload key | .extcodesize key | .returndataOptionalBoolAt key => exprInternalHelperCallNames key @@ -1641,6 +1656,7 @@ mutual helper-aware expression semantics returns only a value. -/ def stmtExprHelperCallNames : Stmt → List String | .letVar _ value | .assignVar _ value | .setStorage _ value | .setStorageAddr _ value + | .setStorageWord _ _ value | .storageArrayPush _ value | .return value | .require value _ => exprInternalHelperCallNames value | .setStorageArrayElement _ index value => @@ -1704,6 +1720,7 @@ mutual /-- Collect direct internal-helper callee names mentioned by a statement list. -/ def stmtInternalHelperCallNames : Stmt → List String | .letVar _ value | .assignVar _ value | .setStorage _ value | .setStorageAddr _ value + | .setStorageWord _ _ value | .storageArrayPush _ value | .return value | .require value _ => exprInternalHelperCallNames value | .setStorageArrayElement _ index value => @@ -3105,7 +3122,8 @@ mutual simp only [exprTouchesUnsupportedHelperSurface] at hsurface simp [exprTouchesInternalHelperSurface, exprTouchesInternalHelperSurface_eq_false_of_helperSurfaceClosed hsurface] - | mapping _ b | mappingUint _ b | arrayElement _ b | storageArrayElement _ b + | mapping _ b | mappingUint _ b | arrayElement _ b | arrayElementWord _ b _ _ + | storageArrayElement _ b | mappingWord _ b _ | mappingPackedWord _ b _ _ | structMember _ b _ => simp only [exprTouchesUnsupportedHelperSurface] at hsurface simp [exprTouchesInternalHelperSurface, @@ -3155,7 +3173,7 @@ mutual stmtTouchesInternalHelperSurface stmt = false := by cases stmt with | letVar _ value | assignVar _ value | setStorage _ value - | setStorageAddr _ value | storageArrayPush _ value => + | setStorageAddr _ value | setStorageWord _ _ value | storageArrayPush _ value => simp only [stmtTouchesUnsupportedHelperSurface] at hsurface simp [stmtTouchesInternalHelperSurface, exprTouchesInternalHelperSurface_eq_false_of_helperSurfaceClosed hsurface] @@ -3487,7 +3505,7 @@ private theorem exprTouchesUnsupportedCallSurface_eq_featureOr simp only [exprTouchesUnsupportedCallSurface, exprTouchesUnsupportedHelperSurface, exprTouchesUnsupportedForeignSurface, exprTouchesUnsupportedLowLevelSurface] exact exprTouchesUnsupportedCallSurface_eq_featureOr a - | mapping _ b | mappingUint _ b | arrayElement _ b + | mapping _ b | mappingUint _ b | arrayElement _ b | arrayElementWord _ b _ _ | storageArrayElement _ b => simp only [exprTouchesUnsupportedCallSurface, exprTouchesUnsupportedHelperSurface, exprTouchesUnsupportedForeignSurface, exprTouchesUnsupportedLowLevelSurface] @@ -3724,7 +3742,7 @@ private theorem exprTouchesUnsupportedContractSurface_eq_false_of_featureClosed | mapping _ _ | mappingWord _ _ _ | mappingPackedWord _ _ _ _ | mapping2 _ _ _ | mapping2Word _ _ _ _ | mappingUint _ _ | mappingChain _ _ | structMember _ _ _ | structMember2 _ _ _ _ - | arrayElement _ _ | storageArrayElement _ _ + | arrayElement _ _ | arrayElementWord _ _ _ _ | storageArrayElement _ _ | call _ _ _ _ _ _ _ | staticcall _ _ _ _ _ _ | delegatecall _ _ _ _ _ _ | externalCall _ _ | internalCall _ _ => cases hcore @@ -3921,6 +3939,7 @@ private theorem stmtTouchesUnsupportedContractSurface_eq_false_of_featureClosed stmtListTouchesUnsupportedContractSurface_eq_false_of_featureClosed elseBranch hcore.2 hstate.2 hcalls.2 heffects.2⟩ | forEach _ _ _ => cases hcore + | setStorageWord _ _ _ => cases hstate | _ => all_goals (simp only [stmtTouchesUnsupportedContractSurface]; first | assumption | cases hcore | cases heffects | cases hcalls) termination_by sizeOf stmt @@ -4021,7 +4040,7 @@ theorem exprTouchesUnsupportedHelperSurface_eq_false_of_contractSurfaceClosed | mapping _ _ | mappingWord _ _ _ | mappingPackedWord _ _ _ _ | mapping2 _ _ _ | mapping2Word _ _ _ _ | mappingUint _ _ | structMember _ _ _ | structMember2 _ _ _ _ - | arrayElement _ _ | storageArrayElement _ _ + | arrayElement _ _ | arrayElementWord _ _ _ _ | storageArrayElement _ _ | mappingChain _ _ => simp [exprTouchesUnsupportedContractSurface] at hsurface | tload a | calldataload a | mload a => @@ -4090,6 +4109,7 @@ theorem stmtTouchesUnsupportedHelperSurface_eq_false_of_contractSurfaceClosed stmtTouchesUnsupportedHelperSurface stmt = false := by cases stmt with | letVar _ value | assignVar _ value | setStorage _ value | setStorageAddr _ value + | setStorageWord _ _ value | storageArrayPush _ value | require value _ | «return» value => simp [stmtTouchesUnsupportedHelperSurface, stmtTouchesUnsupportedContractSurface] at hsurface ⊢ all_goals exact exprTouchesUnsupportedHelperSurface_eq_false_of_contractSurfaceClosed hsurface diff --git a/Compiler/Proofs/YulGeneration/Backends/EvmYulLeanAdapter.lean b/Compiler/Proofs/YulGeneration/Backends/EvmYulLeanAdapter.lean index 1bc7fea12..2ba3a85b6 100644 --- a/Compiler/Proofs/YulGeneration/Backends/EvmYulLeanAdapter.lean +++ b/Compiler/Proofs/YulGeneration/Backends/EvmYulLeanAdapter.lean @@ -180,56 +180,602 @@ def lowerExprNative : YulExpr → EvmYul.Yul.Ast.Expr | some prim => .Call (.inl prim) loweredArgs | none => .Call (.inr func) loweredArgs +@[simp] theorem lowerExprNative_call_runtimePrimOp + (func : String) + (args : List YulExpr) + (op : EvmYul.Operation .Yul) + (hOp : lookupRuntimePrimOp func = some op) : + lowerExprNative (.call func args) = + .Call (.inl op) (args.map lowerExprNative) := by + simp [lowerExprNative, hOp] + +@[simp] theorem lowerExprNative_call_userFunction + (func : String) + (args : List YulExpr) + (hOp : lookupRuntimePrimOp func = none) : + lowerExprNative (.call func args) = + .Call (.inr func) (args.map lowerExprNative) := by + simp [lowerExprNative, hOp] + +/-- EVMYulLean's Yul AST represents both declaration-style bindings and + reassignment-style bindings with `Stmt.Let`; its interpreter updates the + `VarStore` by inserting the target name, replacing any previous binding. + Keep this helper named so the native assignment boundary is explicit. -/ +def lowerAssignNative (name : String) (value : YulExpr) : EvmYul.Yul.Ast.Stmt := + .Let [name] (some (lowerExprNative value)) + +/-- Build a native EVMYulLean primitive call expression. + +This is public because the native dispatcher proof reasons directly about the +guard expressions introduced by `lowerNativeSwitchBlock`. -/ +def nativePrimCall + (op : EvmYul.Operation .Yul) (args : List EvmYul.Yul.Ast.Expr) : + EvmYul.Yul.Ast.Expr := + .Call (.inl op) args + +/-! ### Native switch temporary freshness -/ + +partial def yulExprIdentifierNames : YulExpr → List String + | .lit _ | .hex _ => [] + | .str name | .ident name => [name] + | .call _ args => args.foldl (fun acc arg => acc ++ yulExprIdentifierNames arg) [] + mutual -partial def lowerStmtsNative : - List YulStmt → Except AdapterError (List EvmYul.Yul.Ast.Stmt) - | [] => pure [] + partial def yulStmtIdentifierNames : YulStmt → List String + | .comment _ | .leave => [] + | .let_ name value => name :: yulExprIdentifierNames value + | .letMany names value => names ++ yulExprIdentifierNames value + | .assign name value => name :: yulExprIdentifierNames value + | .expr e => yulExprIdentifierNames e + | .if_ cond body => yulExprIdentifierNames cond ++ yulStmtsIdentifierNames body + | .for_ init cond post body => + yulStmtsIdentifierNames init ++ + yulExprIdentifierNames cond ++ + yulStmtsIdentifierNames post ++ + yulStmtsIdentifierNames body + | .switch discr cases defaultBody => + yulExprIdentifierNames discr ++ + cases.foldl (fun acc (_, body) => acc ++ yulStmtsIdentifierNames body) [] ++ + yulStmtsIdentifierNames (defaultBody.getD []) + | .block stmts => yulStmtsIdentifierNames stmts + | .funcDef name params rets body => name :: params ++ rets ++ yulStmtsIdentifierNames body + + partial def yulStmtsIdentifierNames (stmts : List YulStmt) : List String := + stmts.foldl (fun acc stmt => acc ++ yulStmtIdentifierNames stmt) [] +end + +mutual + partial def yulStmtWriteNames : YulStmt → List String + | .comment _ | .expr _ | .leave => [] + | .let_ name _ => [name] + | .letMany names _ => names + | .assign name _ => [name] + | .if_ _ body => yulStmtsWriteNames body + | .for_ init _ post body => + yulStmtsWriteNames init ++ + yulStmtsWriteNames post ++ + yulStmtsWriteNames body + | .switch _ cases defaultBody => + cases.foldl (fun acc (_, body) => acc ++ yulStmtsWriteNames body) [] ++ + yulStmtsWriteNames (defaultBody.getD []) + | .block stmts => yulStmtsWriteNames stmts + | .funcDef _ params rets body => params ++ rets ++ yulStmtsWriteNames body + + partial def yulStmtsWriteNames (stmts : List YulStmt) : List String := + stmts.foldl (fun acc stmt => acc ++ yulStmtWriteNames stmt) [] +end + +mutual + partial def nativeStmtWriteNames : EvmYul.Yul.Ast.Stmt → List String + | .Block stmts => nativeStmtsWriteNames stmts + | .Let names _ => names + | .ExprStmtCall _ | .Continue | .Break | .Leave => [] + | .Switch _ cases defaultBody => + cases.foldl (fun acc (_, body) => acc ++ nativeStmtsWriteNames body) [] ++ + nativeStmtsWriteNames defaultBody + | .For _ post body => nativeStmtsWriteNames post ++ nativeStmtsWriteNames body + | .If _ body => nativeStmtsWriteNames body + + partial def nativeStmtsWriteNames (stmts : List EvmYul.Yul.Ast.Stmt) : List String := + stmts.foldl (fun acc stmt => acc ++ nativeStmtWriteNames stmt) [] +end + +def nativeSwitchDiscrTempName (switchId : Nat) : String := + s!"__verity_native_switch_discr_{switchId}" + +def nativeSwitchMatchedTempName (switchId : Nat) : String := + s!"__verity_native_switch_matched_{switchId}" + +def nativeSwitchTempsFreshForWrites + (switchId : Nat) + (writeNames : List String) : Prop := + nativeSwitchDiscrTempName switchId ∉ writeNames ∧ + nativeSwitchMatchedTempName switchId ∉ writeNames + +def nativeSwitchTempsFreshForSourceBodies + (switchId : Nat) + (cases' : List (Nat × List YulStmt)) + (defaultBody : Option (List YulStmt)) : Prop := + (∀ tag body, + (tag, body) ∈ cases' → + nativeSwitchTempsFreshForWrites switchId (yulStmtsWriteNames body)) ∧ + (∀ body, + defaultBody = some body → + nativeSwitchTempsFreshForWrites switchId (yulStmtsWriteNames body)) + +def nativeSwitchTempsFreshForNativeBodies + (switchId : Nat) + (cases' : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (defaultBody : List EvmYul.Yul.Ast.Stmt) : Prop := + (∀ tag body, + (tag, body) ∈ cases' → + nativeSwitchTempsFreshForWrites switchId (nativeStmtsWriteNames body)) ∧ + nativeSwitchTempsFreshForWrites switchId (nativeStmtsWriteNames defaultBody) + +partial def freshNativeSwitchId (reservedNames : List String) (candidate : Nat) : Nat := + let discrName := nativeSwitchDiscrTempName candidate + let matchedName := nativeSwitchMatchedTempName candidate + if reservedNames.contains discrName || reservedNames.contains matchedName then + freshNativeSwitchId reservedNames (candidate + 1) + else + candidate + +def lowerNativeSwitchBlock + (expr : YulExpr) + (switchId : Nat) + (cases' : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (default' : List EvmYul.Yul.Ast.Stmt) : + EvmYul.Yul.Ast.Stmt := + let discrName := nativeSwitchDiscrTempName switchId + let matchedName := nativeSwitchMatchedTempName switchId + let discr := EvmYul.Yul.Ast.Expr.Var discrName + let matched := EvmYul.Yul.Ast.Expr.Var matchedName + let matchExpr (tag : Nat) : EvmYul.Yul.Ast.Expr := + nativePrimCall (EvmYul.Operation.EQ : EvmYul.Operation .Yul) + [discr, .Lit (EvmYul.UInt256.ofNat tag)] + let unmatched : EvmYul.Yul.Ast.Expr := + nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) [matched] + let guardedMatch (tag : Nat) : EvmYul.Yul.Ast.Expr := + nativePrimCall (EvmYul.Operation.AND : EvmYul.Operation .Yul) + [unmatched, matchExpr tag] + let defaultGuard := + nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) [matched] + let markMatched := lowerAssignNative matchedName (.lit 1) + let caseIfs := cases'.map (fun (tag, body) => .If (guardedMatch tag) (markMatched :: body)) + let defaultIf := if default'.isEmpty then [] else [.If defaultGuard default'] + .Block ([ .Let [discrName] (some (lowerExprNative expr)) + , .Let [matchedName] (some (.Lit (EvmYul.UInt256.ofNat 0))) ] ++ + caseIfs ++ defaultIf) + +/-- Native switch lowering expands to a lazy guarded block instead of using + EVMYulLean's eager `.Switch` form. The native/interpreter dispatcher proof + should consume this shape directly: the first statement evaluates the + discriminator once, each case is guarded by `iszero(matched) && discr == tag`, + and the optional default runs only if no case marked the switch matched. -/ +theorem lowerNativeSwitchBlock_eq + (expr : YulExpr) + (switchId : Nat) + (cases' : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (default' : List EvmYul.Yul.Ast.Stmt) : + lowerNativeSwitchBlock expr switchId cases' default' = + (let discrName := nativeSwitchDiscrTempName switchId + let matchedName := nativeSwitchMatchedTempName switchId + let discr := EvmYul.Yul.Ast.Expr.Var discrName + let matched := EvmYul.Yul.Ast.Expr.Var matchedName + let matchExpr := fun tag => + nativePrimCall (EvmYul.Operation.EQ : EvmYul.Operation .Yul) + [discr, .Lit (EvmYul.UInt256.ofNat tag)] + let unmatched := + nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) [matched] + let guardedMatch := fun tag => + nativePrimCall (EvmYul.Operation.AND : EvmYul.Operation .Yul) + [unmatched, matchExpr tag] + let defaultGuard := + nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) [matched] + let markMatched := lowerAssignNative matchedName (.lit 1) + let caseIfs := cases'.map (fun (tag, body) => .If (guardedMatch tag) (markMatched :: body)) + let defaultIf := if default'.isEmpty then [] else [.If defaultGuard default'] + .Block ([ .Let [discrName] (some (lowerExprNative expr)) + , .Let [matchedName] (some (.Lit (EvmYul.UInt256.ofNat 0))) ] ++ + caseIfs ++ defaultIf)) := by + rfl + +mutual +def lowerStmtsNativeWithSwitchIds + (reservedNames : List String) + (nextSwitchId : Nat) : + List YulStmt → Except AdapterError (List EvmYul.Yul.Ast.Stmt × Nat) + | [] => pure ([], nextSwitchId) | stmt :: rest => do - let stmts' ← lowerStmtGroupNative stmt - let rest' ← lowerStmtsNative rest - pure (stmts' ++ rest') + let (stmts', nextSwitchId) ← + lowerStmtGroupNativeWithSwitchIds reservedNames nextSwitchId stmt + let (rest', nextSwitchId) ← + lowerStmtsNativeWithSwitchIds reservedNames nextSwitchId rest + pure (stmts' ++ rest', nextSwitchId) +termination_by stmts => sizeOf stmts +decreasing_by all_goals simp_wf; all_goals omega -/-- Lower a statement for native `EvmYul.Yul.exec`. +def lowerSwitchCasesNativeWithSwitchIds + (reservedNames : List String) + (nextSwitchId : Nat) : + List (Nat × List YulStmt) → + Except AdapterError (List (Nat × List EvmYul.Yul.Ast.Stmt) × Nat) + | [] => pure ([], nextSwitchId) + | (tag, block) :: rest => do + let (block', nextSwitchId) ← + lowerStmtsNativeWithSwitchIds reservedNames nextSwitchId block + let (rest', nextSwitchId) ← + lowerSwitchCasesNativeWithSwitchIds reservedNames nextSwitchId rest + pure ((tag, block') :: rest', nextSwitchId) +termination_by cases => sizeOf cases +decreasing_by all_goals simp_wf; all_goals omega -Top-level function definitions are intentionally rejected here; callers that -need executable contracts should use `lowerRuntimeContractNative`, which places -them in `YulContract.functions`. --/ -partial def lowerStmtGroupNative : - YulStmt → Except AdapterError (List EvmYul.Yul.Ast.Stmt) - | .comment _ => pure [.Block []] - | .let_ name value => pure [.Let [name] (some (lowerExprNative value))] - | .letMany names value => pure [.Let names (some (lowerExprNative value))] - | .assign name value => pure [.Let [name] (some (lowerExprNative value))] - | .expr e => pure [.ExprStmtCall (lowerExprNative e)] - | .leave => pure [.Leave] +def lowerStmtGroupNativeWithSwitchIds + (reservedNames : List String) + (nextSwitchId : Nat) : + YulStmt → Except AdapterError (List EvmYul.Yul.Ast.Stmt × Nat) + | .comment _ => pure ([.Block []], nextSwitchId) + | .let_ name value => pure ([.Let [name] (some (lowerExprNative value))], nextSwitchId) + | .letMany names value => pure ([.Let names (some (lowerExprNative value))], nextSwitchId) + | .assign name value => pure ([lowerAssignNative name value], nextSwitchId) + | .expr e => pure ([.ExprStmtCall (lowerExprNative e)], nextSwitchId) + | .leave => pure ([.Leave], nextSwitchId) | .if_ cond body => do - let body' ← lowerStmtsNative body - pure [.If (lowerExprNative cond) body'] + let (body', nextSwitchId) ← lowerStmtsNativeWithSwitchIds reservedNames nextSwitchId body + pure ([.If (lowerExprNative cond) body'], nextSwitchId) | .for_ init cond post body => do - let init' ← lowerStmtsNative init - let post' ← lowerStmtsNative post - let body' ← lowerStmtsNative body - pure (init' ++ [.For (lowerExprNative cond) post' body']) + let (init', nextSwitchId) ← lowerStmtsNativeWithSwitchIds reservedNames nextSwitchId init + let (post', nextSwitchId) ← lowerStmtsNativeWithSwitchIds reservedNames nextSwitchId post + let (body', nextSwitchId) ← lowerStmtsNativeWithSwitchIds reservedNames nextSwitchId body + pure (init' ++ [.For (lowerExprNative cond) post' body'], nextSwitchId) | .switch expr cases defaultCase => do - let lowerCase := fun ((tag, block) : Nat × List YulStmt) => do - let block' ← lowerStmtsNative block - pure (EvmYul.UInt256.ofNat tag, block') - let cases' ← cases.mapM lowerCase - let default' ← lowerStmtsNative (defaultCase.getD []) - pure [.Switch (lowerExprNative expr) cases' default'] + -- EVMYulLean's `Switch` executor currently evaluates the default branch + -- before selecting a case. Lower to lazy `if` guards with an explicit + -- match flag so exactly one non-halting branch can execute. Generate + -- temporary names outside the source identifier surface to avoid + -- shadowing user-visible Yul variables. + let switchId := freshNativeSwitchId reservedNames nextSwitchId + let nextSwitchId := switchId + 1 + let (cases', nextSwitchId) ← + lowerSwitchCasesNativeWithSwitchIds reservedNames nextSwitchId cases + let (default', nextSwitchId) ← + match defaultCase with + | some defaultBody => + lowerStmtsNativeWithSwitchIds reservedNames nextSwitchId defaultBody + | none => pure ([], nextSwitchId) + pure ([lowerNativeSwitchBlock expr switchId cases' default'], nextSwitchId) | .block stmts => do - let stmts' ← lowerStmtsNative stmts - pure [.Block stmts'] + let (stmts', nextSwitchId) ← lowerStmtsNativeWithSwitchIds reservedNames nextSwitchId stmts + pure ([.Block stmts'], nextSwitchId) | .funcDef name _ _ _ => throw s!"native EVMYulLean statement lowering cannot inline function definition '{name}'; use lowerRuntimeContractNative" +termination_by stmt => sizeOf stmt +decreasing_by all_goals simp_wf; all_goals omega end -def lowerFunctionDefinitionNative (params rets : List String) (body : List YulStmt) : +def lowerStmtsNative : + List YulStmt → Except AdapterError (List EvmYul.Yul.Ast.Stmt) + | stmts => do + let (lowered, _) ← + lowerStmtsNativeWithSwitchIds (yulStmtsIdentifierNames stmts) 0 stmts + pure lowered + +@[simp] theorem lowerStmtsNativeWithSwitchIds_nil + (reservedNames : List String) + (nextSwitchId : Nat) : + lowerStmtsNativeWithSwitchIds reservedNames nextSwitchId [] = + .ok ([], nextSwitchId) := + lowerStmtsNativeWithSwitchIds.eq_1 reservedNames nextSwitchId + +/-- Statement-list native lowering exposes a stable head/tail equation. + +This is the statement-level analogue of the top-level runtime partition +equations below: future native/interpreter preservation proofs can reason by +the source statement list instead of treating the native lowerer as an opaque +executable. -/ +theorem lowerStmtsNativeWithSwitchIds_cons + (reservedNames : List String) + (nextSwitchId : Nat) + (stmt : YulStmt) + (rest : List YulStmt) : + lowerStmtsNativeWithSwitchIds reservedNames nextSwitchId (stmt :: rest) = + (do + let loweredAndNext ← + lowerStmtGroupNativeWithSwitchIds reservedNames nextSwitchId stmt + match loweredAndNext with + | (stmts', nextSwitchId) => + let restAndNext ← + lowerStmtsNativeWithSwitchIds reservedNames nextSwitchId rest + match restAndNext with + | (rest', nextSwitchId) => pure (stmts' ++ rest', nextSwitchId)) := + lowerStmtsNativeWithSwitchIds.eq_2 reservedNames nextSwitchId stmt rest + +@[simp] theorem lowerSwitchCasesNativeWithSwitchIds_nil + (reservedNames : List String) + (nextSwitchId : Nat) : + lowerSwitchCasesNativeWithSwitchIds reservedNames nextSwitchId [] = + .ok ([], nextSwitchId) := + lowerSwitchCasesNativeWithSwitchIds.eq_1 reservedNames nextSwitchId + +/-- Native switch case lowering preserves the case spine and threads the switch +temp counter through each lowered case body. + +The generated dispatcher proof needs this before it can relate selected source +`switch` cases to the lazy guarded native lowering used to avoid EVMYulLean's +eager default execution. -/ +theorem lowerSwitchCasesNativeWithSwitchIds_cons + (reservedNames : List String) + (nextSwitchId tag : Nat) + (block : List YulStmt) + (rest : List (Nat × List YulStmt)) : + lowerSwitchCasesNativeWithSwitchIds reservedNames nextSwitchId + ((tag, block) :: rest) = + (do + let blockAndNext ← + lowerStmtsNativeWithSwitchIds reservedNames nextSwitchId block + match blockAndNext with + | (block', nextSwitchId) => + let restAndNext ← + lowerSwitchCasesNativeWithSwitchIds reservedNames nextSwitchId rest + match restAndNext with + | (rest', nextSwitchId) => pure ((tag, block') :: rest', nextSwitchId)) := + lowerSwitchCasesNativeWithSwitchIds.eq_2 reservedNames nextSwitchId tag block rest + +/-- Lowering native switch cases preserves selector misses through the lowered +case spine. + +The native dispatcher proof consumes this to move from source-level selector +lookup to the generated guarded case list before considering an optional +default branch. -/ +theorem lowerSwitchCasesNativeWithSwitchIds_find?_none + (reservedNames : List String) (nextSwitchId final selector : Nat) + (cases : List (Nat × List YulStmt)) + (cases' : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (hLower : lowerSwitchCasesNativeWithSwitchIds reservedNames nextSwitchId cases = + .ok (cases', final)) + (hFind : cases.find? (fun entry => entry.1 == selector) = none) : + cases'.find? (fun entry => entry.1 == selector) = none := by + induction cases generalizing nextSwitchId cases' final with + | nil => + simp at hLower + rcases hLower with ⟨rfl, rfl⟩ + simp + | cons head rest ih => + rcases head with ⟨tag, block⟩ + cases hBlock : lowerStmtsNativeWithSwitchIds reservedNames nextSwitchId block with + | error err => + simp [lowerSwitchCasesNativeWithSwitchIds.eq_2, hBlock] at hLower + cases hLower + | ok blockAndNext => + rcases blockAndNext with ⟨block', nextSwitchId'⟩ + rw [lowerSwitchCasesNativeWithSwitchIds.eq_2, hBlock] at hLower + change + ((fun a => ((tag, block') :: a.1, a.2)) <$> + lowerSwitchCasesNativeWithSwitchIds reservedNames nextSwitchId' rest) = + Except.ok (cases', final) at hLower + cases hRest : + lowerSwitchCasesNativeWithSwitchIds reservedNames nextSwitchId' rest with + | error err => + rw [hRest] at hLower + simp at hLower + | ok restAndNext => + rcases restAndNext with ⟨rest', final'⟩ + rw [hRest] at hLower + simp at hLower + rcases hLower with ⟨rfl, rfl⟩ + by_cases hTag : tag == selector + · simp [List.find?, hTag] at hFind + · have hRestFind : + rest.find? (fun entry => entry.1 == selector) = none := by + simpa [List.find?, hTag] using hFind + have hLowerRest : + rest'.find? (fun entry => entry.1 == selector) = none := + ih nextSwitchId' final' rest' hRest hRestFind + simp [List.find?, hTag, hLowerRest] + +/-- Lowering native switch cases preserves selector hits through the lowered +case spine, while exposing the switch-temp counter range used to lower the +selected body. -/ +theorem lowerSwitchCasesNativeWithSwitchIds_find?_some + (reservedNames : List String) (nextSwitchId final selector tag : Nat) + (body : List YulStmt) (cases : List (Nat × List YulStmt)) + (cases' : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (hLower : lowerSwitchCasesNativeWithSwitchIds reservedNames nextSwitchId cases = + .ok (cases', final)) + (hFind : cases.find? (fun entry => entry.1 == selector) = some (tag, body)) : + ∃ body' bodyStart bodyEnd, + cases'.find? (fun entry => entry.1 == selector) = some (tag, body') ∧ + lowerStmtsNativeWithSwitchIds reservedNames bodyStart body = + .ok (body', bodyEnd) := by + induction cases generalizing nextSwitchId cases' final with + | nil => + simp [List.find?] at hFind + | cons head rest ih => + rcases head with ⟨headTag, headBody⟩ + cases hBlock : lowerStmtsNativeWithSwitchIds reservedNames nextSwitchId headBody with + | error err => + simp [lowerSwitchCasesNativeWithSwitchIds.eq_2, hBlock] at hLower + cases hLower + | ok blockAndNext => + rcases blockAndNext with ⟨headBody', nextSwitchId'⟩ + rw [lowerSwitchCasesNativeWithSwitchIds.eq_2, hBlock] at hLower + change ((fun a => ((headTag, headBody') :: a.1, a.2)) <$> + lowerSwitchCasesNativeWithSwitchIds reservedNames nextSwitchId' rest) = + Except.ok (cases', final) at hLower + cases hRest : lowerSwitchCasesNativeWithSwitchIds reservedNames nextSwitchId' rest with + | error err => + rw [hRest] at hLower + simp at hLower + | ok restAndNext => + rcases restAndNext with ⟨rest', final'⟩ + rw [hRest] at hLower + simp at hLower + rcases hLower with ⟨rfl, rfl⟩ + by_cases hTag : headTag == selector + · have hHead : (headTag, headBody) = (tag, body) := by + simpa [List.find?, hTag] using hFind + cases hHead + exact ⟨headBody', nextSwitchId, nextSwitchId', + by simp [List.find?, hTag], hBlock⟩ + · have hRestFind : rest.find? (fun entry => entry.1 == selector) = + some (tag, body) := by + simpa [List.find?, hTag] using hFind + rcases ih nextSwitchId' final' rest' hRest hRestFind with + ⟨body', bodyStart, bodyEnd, hLowerFind, hLowerBody⟩ + exact ⟨body', bodyStart, bodyEnd, + by simp [List.find?, hTag, hLowerFind], hLowerBody⟩ + +@[simp] theorem lowerStmtGroupNativeWithSwitchIds_comment + (reservedNames : List String) + (nextSwitchId : Nat) + (text : String) : + lowerStmtGroupNativeWithSwitchIds reservedNames nextSwitchId (.comment text) = + .ok ([.Block []], nextSwitchId) := + lowerStmtGroupNativeWithSwitchIds.eq_1 reservedNames nextSwitchId text + +@[simp] theorem lowerStmtGroupNativeWithSwitchIds_let + (reservedNames : List String) + (nextSwitchId : Nat) + (name : String) + (value : YulExpr) : + lowerStmtGroupNativeWithSwitchIds reservedNames nextSwitchId (.let_ name value) = + .ok ([.Let [name] (some (lowerExprNative value))], nextSwitchId) := + lowerStmtGroupNativeWithSwitchIds.eq_2 reservedNames nextSwitchId name value + +@[simp] theorem lowerStmtGroupNativeWithSwitchIds_letMany + (reservedNames : List String) + (nextSwitchId : Nat) + (names : List String) + (value : YulExpr) : + lowerStmtGroupNativeWithSwitchIds reservedNames nextSwitchId (.letMany names value) = + .ok ([.Let names (some (lowerExprNative value))], nextSwitchId) := + lowerStmtGroupNativeWithSwitchIds.eq_3 reservedNames nextSwitchId names value + +@[simp] theorem lowerStmtGroupNativeWithSwitchIds_assign + (reservedNames : List String) + (nextSwitchId : Nat) + (name : String) + (value : YulExpr) : + lowerStmtGroupNativeWithSwitchIds reservedNames nextSwitchId (.assign name value) = + .ok ([lowerAssignNative name value], nextSwitchId) := + lowerStmtGroupNativeWithSwitchIds.eq_4 reservedNames nextSwitchId name value + +@[simp] theorem lowerStmtGroupNativeWithSwitchIds_expr + (reservedNames : List String) + (nextSwitchId : Nat) + (e : YulExpr) : + lowerStmtGroupNativeWithSwitchIds reservedNames nextSwitchId (.expr e) = + .ok ([.ExprStmtCall (lowerExprNative e)], nextSwitchId) := + lowerStmtGroupNativeWithSwitchIds.eq_5 reservedNames nextSwitchId e + +@[simp] theorem lowerStmtGroupNativeWithSwitchIds_leave + (reservedNames : List String) + (nextSwitchId : Nat) : + lowerStmtGroupNativeWithSwitchIds reservedNames nextSwitchId .leave = + .ok ([.Leave], nextSwitchId) := + lowerStmtGroupNativeWithSwitchIds.eq_6 reservedNames nextSwitchId + +theorem lowerStmtGroupNativeWithSwitchIds_if + (reservedNames : List String) + (nextSwitchId : Nat) + (cond : YulExpr) + (body : List YulStmt) : + lowerStmtGroupNativeWithSwitchIds reservedNames nextSwitchId (.if_ cond body) = + (do + let bodyAndNext ← + lowerStmtsNativeWithSwitchIds reservedNames nextSwitchId body + match bodyAndNext with + | (body', nextSwitchId) => + pure ([.If (lowerExprNative cond) body'], nextSwitchId)) := + lowerStmtGroupNativeWithSwitchIds.eq_7 reservedNames nextSwitchId cond body + +theorem lowerStmtGroupNativeWithSwitchIds_for + (reservedNames : List String) + (nextSwitchId : Nat) + (init : List YulStmt) + (cond : YulExpr) + (post body : List YulStmt) : + lowerStmtGroupNativeWithSwitchIds reservedNames nextSwitchId + (.for_ init cond post body) = + (do + let initAndNext ← + lowerStmtsNativeWithSwitchIds reservedNames nextSwitchId init + match initAndNext with + | (init', nextSwitchId) => + let postAndNext ← + lowerStmtsNativeWithSwitchIds reservedNames nextSwitchId post + match postAndNext with + | (post', nextSwitchId) => + let bodyAndNext ← + lowerStmtsNativeWithSwitchIds reservedNames nextSwitchId body + match bodyAndNext with + | (body', nextSwitchId) => + pure (init' ++ [.For (lowerExprNative cond) post' body'], + nextSwitchId)) := + lowerStmtGroupNativeWithSwitchIds.eq_8 reservedNames nextSwitchId init cond post body + +theorem lowerStmtGroupNativeWithSwitchIds_switch + (reservedNames : List String) + (nextSwitchId : Nat) + (expr : YulExpr) + (cases : List (Nat × List YulStmt)) + (defaultCase : Option (List YulStmt)) : + lowerStmtGroupNativeWithSwitchIds reservedNames nextSwitchId + (.switch expr cases defaultCase) = + (let switchId := freshNativeSwitchId reservedNames nextSwitchId + do + let casesAndNext ← + lowerSwitchCasesNativeWithSwitchIds reservedNames (switchId + 1) cases + match casesAndNext with + | (cases', nextSwitchId) => + let defaultAndNext ← + match defaultCase with + | some defaultBody => + lowerStmtsNativeWithSwitchIds reservedNames nextSwitchId + defaultBody + | none => pure ([], nextSwitchId) + match defaultAndNext with + | (default', nextSwitchId) => + pure ([lowerNativeSwitchBlock expr switchId cases' default'], + nextSwitchId)) := + lowerStmtGroupNativeWithSwitchIds.eq_9 reservedNames nextSwitchId expr cases defaultCase + +theorem lowerStmtGroupNativeWithSwitchIds_block + (reservedNames : List String) + (nextSwitchId : Nat) + (stmts : List YulStmt) : + lowerStmtGroupNativeWithSwitchIds reservedNames nextSwitchId (.block stmts) = + (do + let stmtsAndNext ← + lowerStmtsNativeWithSwitchIds reservedNames nextSwitchId stmts + match stmtsAndNext with + | (stmts', nextSwitchId) => pure ([.Block stmts'], nextSwitchId)) := + lowerStmtGroupNativeWithSwitchIds.eq_10 reservedNames nextSwitchId stmts + +@[simp] theorem lowerStmtGroupNativeWithSwitchIds_funcDef + (reservedNames : List String) + (nextSwitchId : Nat) + (name : String) + (params rets : List String) + (body : List YulStmt) : + lowerStmtGroupNativeWithSwitchIds reservedNames nextSwitchId + (.funcDef name params rets body) = + .error s!"native EVMYulLean statement lowering cannot inline function definition '{name}'; use lowerRuntimeContractNative" := + lowerStmtGroupNativeWithSwitchIds.eq_11 reservedNames nextSwitchId name params rets body + +def lowerFunctionDefinitionNativeWithReserved + (globalReservedNames : List String) + (params rets : List String) + (body : List YulStmt) : Except AdapterError EvmYul.Yul.Ast.FunctionDefinition := do - let body' ← lowerStmtsNative body + let reservedNames := globalReservedNames ++ params ++ rets ++ yulStmtsIdentifierNames body + let (body', _) ← lowerStmtsNativeWithSwitchIds reservedNames 0 body pure (.Def params rets body') +def lowerFunctionDefinitionNative (params rets : List String) (body : List YulStmt) : + Except AdapterError EvmYul.Yul.Ast.FunctionDefinition := do + lowerFunctionDefinitionNativeWithReserved (yulStmtsIdentifierNames body) params rets body + abbrev NativeFunctionMap := Finmap (fun (_ : EvmYul.Yul.Ast.YulFunctionName) => EvmYul.Yul.Ast.FunctionDefinition) @@ -243,32 +789,104 @@ private def insertNativeFunction else pure (functions.insert name definition) -partial def lowerRuntimeContractNativeAux +def lowerRuntimeContractNativeAux + (reservedNames : List String) (stmts : List YulStmt) (dispatcherAcc : List EvmYul.Yul.Ast.Stmt) - (functionsAcc : NativeFunctionMap) : - Except AdapterError (List EvmYul.Yul.Ast.Stmt × NativeFunctionMap) := do + (functionsAcc : NativeFunctionMap) + (nextSwitchId : Nat) : + Except AdapterError (List EvmYul.Yul.Ast.Stmt × NativeFunctionMap × Nat) := do match stmts with - | [] => pure (dispatcherAcc.reverse, functionsAcc) + | [] => pure (dispatcherAcc.reverse, functionsAcc, nextSwitchId) | .funcDef name params rets body :: rest => - let definition ← lowerFunctionDefinitionNative params rets body + let definition ← lowerFunctionDefinitionNativeWithReserved reservedNames params rets body let functionsAcc ← insertNativeFunction functionsAcc name definition - lowerRuntimeContractNativeAux rest dispatcherAcc functionsAcc + lowerRuntimeContractNativeAux reservedNames rest dispatcherAcc functionsAcc nextSwitchId | stmt :: rest => - let lowered ← lowerStmtGroupNative stmt - lowerRuntimeContractNativeAux rest (lowered.reverse ++ dispatcherAcc) functionsAcc + let (lowered, nextSwitchId) ← + lowerStmtGroupNativeWithSwitchIds reservedNames nextSwitchId stmt + lowerRuntimeContractNativeAux reservedNames rest (lowered.reverse ++ dispatcherAcc) functionsAcc + nextSwitchId + +@[simp] theorem lowerRuntimeContractNativeAux_nil + (reservedNames : List String) + (dispatcherAcc : List EvmYul.Yul.Ast.Stmt) + (functionsAcc : NativeFunctionMap) + (nextSwitchId : Nat) : + lowerRuntimeContractNativeAux reservedNames [] dispatcherAcc functionsAcc nextSwitchId = + .ok (dispatcherAcc.reverse, functionsAcc, nextSwitchId) := by + rw [lowerRuntimeContractNativeAux.eq_1] + rfl + +/-- Top-level native runtime lowering partitions a `funcDef` into the native +function map rather than appending it to dispatcher code. + +Keeping this equation named is important for the native migration proof: it is +the first generated-fragment shape fact needed before proving that +`callDispatcher` runs the same selected body as the interpreter oracle. -/ +theorem lowerRuntimeContractNativeAux_funcDef_cons + (reservedNames : List String) + (dispatcherAcc : List EvmYul.Yul.Ast.Stmt) + (functionsAcc : NativeFunctionMap) + (nextSwitchId : Nat) + (name : String) + (params rets : List String) + (body rest : List YulStmt) : + lowerRuntimeContractNativeAux reservedNames + (YulStmt.funcDef name params rets body :: rest) + dispatcherAcc functionsAcc nextSwitchId = + (do + let definition ← lowerFunctionDefinitionNativeWithReserved reservedNames params rets body + let functionsAcc ← insertNativeFunction functionsAcc name definition + lowerRuntimeContractNativeAux reservedNames rest dispatcherAcc functionsAcc nextSwitchId) := by + rw [lowerRuntimeContractNativeAux.eq_2] + +/-- Top-level native runtime lowering appends non-`funcDef` statements to the +dispatcher accumulator after statement lowering. + +Together with `lowerRuntimeContractNativeAux_funcDef_cons`, this gives proofs a +transparent partition boundary for generated runtime code: helper definitions +flow to the function map, while the switch dispatcher remains dispatcher code. -/ +theorem lowerRuntimeContractNativeAux_stmt_cons + (reservedNames : List String) + (dispatcherAcc : List EvmYul.Yul.Ast.Stmt) + (functionsAcc : NativeFunctionMap) + (nextSwitchId : Nat) + (stmt : YulStmt) + (rest : List YulStmt) + (hNoFunc : ∀ name params rets body, + stmt ≠ YulStmt.funcDef name params rets body) : + lowerRuntimeContractNativeAux reservedNames (stmt :: rest) + dispatcherAcc functionsAcc nextSwitchId = + (do + let loweredAndNext ← + lowerStmtGroupNativeWithSwitchIds reservedNames nextSwitchId stmt + match loweredAndNext with + | (lowered, nextSwitchId) => + lowerRuntimeContractNativeAux reservedNames rest + (lowered.reverse ++ dispatcherAcc) functionsAcc nextSwitchId) := by + rw [lowerRuntimeContractNativeAux.eq_3] + intro name params rets body h + exact hNoFunc name params rets body h /-- Lower generated runtime Yul into an executable EVMYulLean contract shape. -/ def lowerRuntimeContractNative (stmts : List YulStmt) : Except AdapterError EvmYul.Yul.Ast.YulContract := do let emptyFunctions : NativeFunctionMap := ∅ - let (dispatcher, functions) ← - lowerRuntimeContractNativeAux stmts [] emptyFunctions + let reservedNames := yulStmtsIdentifierNames stmts + let (dispatcher, functions, _) ← + lowerRuntimeContractNativeAux reservedNames stmts [] emptyFunctions 0 pure { dispatcher := .Block dispatcher, functions := functions } +@[simp] theorem lowerRuntimeContractNative_empty : + lowerRuntimeContractNative [] = + .ok { dispatcher := EvmYul.Yul.Ast.Stmt.Block [] + functions := (∅ : NativeFunctionMap) } := by + simp [lowerRuntimeContractNative] + /-- Map a Verity builtin name to the corresponding EVMYulLean PrimOp. Returns `none` for Verity-specific helpers (e.g. `mappingSlot`) that have no direct EVMYulLean counterpart. -/ diff --git a/Compiler/Proofs/YulGeneration/Backends/EvmYulLeanNativeDispatchOracleTest.lean b/Compiler/Proofs/YulGeneration/Backends/EvmYulLeanNativeDispatchOracleTest.lean new file mode 100644 index 000000000..5bda597bc --- /dev/null +++ b/Compiler/Proofs/YulGeneration/Backends/EvmYulLeanNativeDispatchOracleTest.lean @@ -0,0 +1,454 @@ +import Compiler.CompilationModel +import Compiler.Proofs.YulGeneration.Backends.EvmYulLeanNativeHarness +import Compiler.Proofs.YulGeneration.Backends.EvmYulLeanRetarget + +namespace Compiler.Proofs.YulGeneration.Backends.NativeDispatchOracleTest + +open Compiler +open Compiler.Proofs.IRGeneration +open Compiler.Proofs.YulGeneration +open Compiler.Proofs.YulGeneration.Backends + +private def mappingReadSlot : Nat := + Compiler.Proofs.abstractMappingSlot 21 5 + +private def mappingWriteSlot : Nat := + Compiler.Proofs.abstractMappingSlot 21 5 + +private def nestedMappingWriteSlot : Nat := + Compiler.Proofs.abstractMappingSlot (Compiler.Proofs.abstractMappingSlot 22 3) 4 + +private def packedMappingSlot : Nat := + Compiler.Proofs.abstractMappingSlot 23 6 + +private def multiWordMappingBaseSlot : Nat := + Compiler.Proofs.abstractMappingSlot 24 7 + +private def multiWordMappingMemberSlot : Nat := + multiWordMappingBaseSlot + 1 + +private def nestedMultiWordMappingBaseSlot : Nat := + Compiler.Proofs.abstractMappingSlot (Compiler.Proofs.abstractMappingSlot 25 3) 4 + +private def nestedMultiWordMappingMemberSlot : Nat := + nestedMultiWordMappingBaseSlot + 1 + +private def seededStorage : Nat -> Nat := fun slot => + if slot = 7 then 77 else + if slot = mappingReadSlot then 515 else + if slot = packedMappingSlot then 0x123456 else + if slot = multiWordMappingBaseSlot then 0xAAAA else + if slot = multiWordMappingMemberSlot then 0xBBBB else + if slot = nestedMultiWordMappingBaseSlot then 0xCCCC else + if slot = nestedMultiWordMappingMemberSlot then 0xDDDD else 0 + +private def sampleIRTx (selector : Nat) (args : List Nat := []) : IRTransaction := + { sender := 0xCAFE + msgValue := 0 + thisAddress := 0x1234 + blockTimestamp := 12345 + blockNumber := 678 + chainId := 31337 + blobBaseFee := 19 + functionSelector := selector + args := args } + +private def sampleIRState : IRState := + { vars := [] + storage := seededStorage + transientStorage := fun _ => 0 + memory := fun _ => 0 + calldata := [] + returnValue := none + sender := 0 + msgValue := 0 + thisAddress := 0 + blockTimestamp := 0 + blockNumber := 0 + chainId := 0 + blobBaseFee := 0 + selector := 0 + events := [[9, 9]] } + +private def dispatchSmokeContract : IRContract := + { name := "NativeDispatchOracleSmoke" + deploy := [] + functions := [ + { name := "left" + selector := 0x11111111 + params := [] + ret := .unit + body := [ + .expr (.call "sstore" [.lit 11, .lit 101]) + ] }, + { name := "right" + selector := 0x22222222 + params := [] + ret := .unit + body := [ + .expr (.call "sstore" [.lit 11, .lit 202]) + ] } + ] + usesMapping := false } + +private def storageReadIRContract : IRContract := + { name := "NativeStorageReadOracleSmoke" + deploy := [] + functions := [ + { name := "copySlot" + selector := 0x44444444 + params := [] + ret := .unit + body := [ + .expr (.call "sstore" [.lit 8, .call "sload" [.lit 7]]) + ] } + ] + usesMapping := false } + +private def mappingHelperDispatchSmokeContract : IRContract := + { name := "NativeMappingHelperDispatchOracleSmoke" + deploy := [] + functions := [ + { name := "storeMapped" + selector := 0x88888888 + params := [{ name := "key", ty := .uint256 }] + ret := .unit + body := [ + .expr (.call "sstore" [ + .call "mappingSlot" [.lit 21, .call "calldataload" [.lit 4]], + .lit 909]) + ] } + ] + usesMapping := true } + +private def mappingHelperReadDispatchSmokeContract : IRContract := + { name := "NativeMappingHelperReadDispatchOracleSmoke" + deploy := [] + functions := [ + { name := "loadMapped" + selector := 0x99999999 + params := [{ name := "key", ty := .uint256 }] + ret := .unit + body := [ + .expr (.call "sstore" [ + .lit 15, + .call "sload" [ + .call "mappingSlot" [.lit 21, .call "calldataload" [.lit 4]] + ]]) + ] } + ] + usesMapping := true } + +private def nestedMappingHelperDispatchSmokeContract : IRContract := + { name := "NativeNestedMappingHelperDispatchOracleSmoke" + deploy := [] + functions := [ + { name := "storeNestedMapped" + selector := 0xAAAAAAAA + params := [ + { name := "outerKey", ty := .uint256 }, + { name := "innerKey", ty := .uint256 } + ] + ret := .unit + body := [ + .expr (.call "sstore" [ + .call "mappingSlot" [ + .call "mappingSlot" [.lit 22, .call "calldataload" [.lit 4]], + .call "calldataload" [.lit 36] + ], + .lit 808]) + ] } + ] + usesMapping := true } + +private def packedMember : Compiler.CompilationModel.StructMember := + { name := "flags" + wordOffset := 0 + packed := some { offset := 8, width := 8 } } + +private def packedMappingModel : Compiler.CompilationModel.CompilationModel := + { name := "NativePackedMappingOracleSmoke" + fields := [ + { name := "positions" + ty := .mappingStruct .uint256 [packedMember] + slot := some 23 }, + { name := "scratch" + ty := .uint256 + slot := some 16 } + ] + constructor := none + functions := [ + { name := "readFlags" + params := [{ name := "key", ty := .uint256 }] + returnType := none + body := [ + .setStorage "scratch" (.structMember "positions" (.param "key") "flags") + ] }, + { name := "writeFlags" + params := [ + { name := "key", ty := .uint256 }, + { name := "value", ty := .uint256 } + ] + returnType := none + body := [ + .setStructMember "positions" (.param "key") "flags" (.param "value") + ] } + ] } + +private def packedMappingDispatchSmokeContract : Except String IRContract := + Compiler.CompilationModel.compile packedMappingModel [0xBBBBBBBB, 0xCCCCCCCC] + +private def multiWordMember : Compiler.CompilationModel.StructMember := + { name := "balance" + wordOffset := 1 + packed := none } + +private def multiWordMappingModel : Compiler.CompilationModel.CompilationModel := + { name := "NativeMultiWordMappingOracleSmoke" + fields := [ + { name := "accounts" + ty := .mappingStruct .uint256 [multiWordMember] + slot := some 24 }, + { name := "nestedAccounts" + ty := .mappingStruct2 .uint256 .uint256 [multiWordMember] + slot := some 25 }, + { name := "scratch" + ty := .uint256 + slot := some 17 } + ] + constructor := none + functions := [ + { name := "readBalance" + params := [{ name := "key", ty := .uint256 }] + returnType := none + body := [ + .setStorage "scratch" (.structMember "accounts" (.param "key") "balance") + ] }, + { name := "writeBalance" + params := [ + { name := "key", ty := .uint256 }, + { name := "value", ty := .uint256 } + ] + returnType := none + body := [ + .setStructMember "accounts" (.param "key") "balance" (.param "value") + ] }, + { name := "readNestedBalance" + params := [ + { name := "outerKey", ty := .uint256 }, + { name := "innerKey", ty := .uint256 } + ] + returnType := none + body := [ + .setStorage "scratch" + (.structMember2 "nestedAccounts" (.param "outerKey") (.param "innerKey") "balance") + ] }, + { name := "writeNestedBalance" + params := [ + { name := "outerKey", ty := .uint256 }, + { name := "innerKey", ty := .uint256 }, + { name := "value", ty := .uint256 } + ] + returnType := none + body := [ + .setStructMember2 "nestedAccounts" (.param "outerKey") (.param "innerKey") + "balance" (.param "value") + ] } + ] } + +private def multiWordMappingDispatchSmokeContract : Except String IRContract := + Compiler.CompilationModel.compile multiWordMappingModel + [0xDDDDDDDD, 0xEEEEEEEE, 0xDADADADA, 0xEFEFEFEF] + +private def calldataArgDispatchSmokeContract : IRContract := + { name := "NativeCalldataArgDispatchOracleSmoke" + deploy := [] + functions := [ + { name := "storeArg" + selector := 0x77777777 + params := [{ name := "x", ty := .uint256 }] + ret := .unit + body := [ + .let_ "x" (.call "calldataload" [.lit 4]), + .expr (.call "sstore" [.lit 12, .ident "x"]) + ] } + ] + usesMapping := false } + +private def returnDispatchSmokeContract : IRContract := + { name := "NativeReturnDispatchOracleSmoke" + deploy := [] + functions := [ + { name := "answer" + selector := 0x33333333 + params := [] + ret := .uint256 + body := [ + .expr (.call "mstore" [.lit 0, .lit 42]), + .expr (.call "return" [.lit 0, .lit 32]) + ] } + ] + usesMapping := false } + +private def multiWordReturnDispatchSmokeContract : IRContract := + { name := "NativeMultiWordReturnDispatchOracleSmoke" + deploy := [] + functions := [ + { name := "pair" + selector := 0x55555555 + params := [] + ret := .unit + body := [ + .expr (.call "mstore" [.lit 0, .lit 41]), + .expr (.call "mstore" [.lit 32, .lit 42]), + .expr (.call "return" [.lit 0, .lit 64]) + ] } + ] + usesMapping := false } + +private def memoryRevertDispatchSmokeContract : IRContract := + { name := "NativeMemoryRevertDispatchOracleSmoke" + deploy := [] + functions := [ + { name := "fail" + selector := 0x66666666 + params := [] + ret := .unit + body := [ + .expr (.call "sstore" [.lit 7, .lit 99]), + .expr (.call "mstore" [.lit 0, .lit 0xDEAD]), + .expr (.call "revert" [.lit 0, .lit 32]) + ] } + ] + usesMapping := false } + +private def referenceRuntimeWithBackendFuel + (fuel : Nat) (runtimeCode : List Compiler.Yul.YulStmt) + (tx : YulTransaction) (storage : Nat -> Nat) (events : List (List Nat)) : + YulResult := + let initialState := YulState.initial tx storage events + yulResultOfExecWithRollback initialState + (execYulFuelWithBackend .evmYulLean fuel initialState (.stmts runtimeCode)) + +private def resultsMatchOnSlots + (slots : List Nat) (nativeResult referenceResult : YulResult) : Bool := + nativeResult.success == referenceResult.success && + nativeResult.returnValue == referenceResult.returnValue && + nativeResult.events == referenceResult.events && + slots.all (fun slot => + nativeResult.finalStorage slot == referenceResult.finalStorage slot) + +private def emittedDispatchMatchesReferenceWithExpected + (contract : IRContract) (tx : IRTransaction) + (observableSlots compareSlots : List Nat) + (expectedSuccess : Bool) (expectedReturn : Option Nat) + (expectedSlots : List (Nat × Nat)) : Except String Bool := do + let yulTx := YulTransaction.ofIR tx + let reference := + referenceRuntimeWithBackendFuel 256 (Compiler.emitYul contract).runtimeCode + yulTx sampleIRState.storage sampleIRState.events + let nativeResult ← + match Native.interpretIRRuntimeNative 256 contract tx sampleIRState observableSlots with + | .ok result => .ok result + | .error _ => .error "native runtime lowering failed" + pure ( + resultsMatchOnSlots compareSlots nativeResult reference && + nativeResult.success == expectedSuccess && + reference.success == expectedSuccess && + nativeResult.returnValue == expectedReturn && + reference.returnValue == expectedReturn && + expectedSlots.all (fun (slot, value) => + nativeResult.finalStorage slot == value && + reference.finalStorage slot == value)) + +private def emittedCompiledDispatchMatchesReferenceWithExpected + (contract : Except String IRContract) (tx : IRTransaction) + (observableSlots compareSlots : List Nat) + (expectedSuccess : Bool) (expectedReturn : Option Nat) + (expectedSlots : List (Nat × Nat)) : Except String Bool := do + let irContract ← contract + emittedDispatchMatchesReferenceWithExpected irContract tx observableSlots compareSlots + expectedSuccess expectedReturn expectedSlots + +private def check (label : String) (actual : Except String Bool) : IO Unit := do + match actual with + | .ok true => IO.println s!"ok: {label}" + | .ok false => throw (IO.userError s!"native/reference mismatch: {label}") + | .error err => throw (IO.userError s!"{label}: {err}") + +def main : IO Unit := do + check "emitted dispatcher selects first storage-writing case" + (emittedDispatchMatchesReferenceWithExpected dispatchSmokeContract + (sampleIRTx 0x11111111) [11] [11] true none [(11, 101)]) + check "emitted dispatcher selects second storage-writing case" + (emittedDispatchMatchesReferenceWithExpected dispatchSmokeContract + (sampleIRTx 0x22222222) [11] [11] true none [(11, 202)]) + check "emitted dispatcher forwards observable storage reads" + (emittedDispatchMatchesReferenceWithExpected storageReadIRContract + (sampleIRTx 0x44444444) [7, 8] [7, 8] true none [(7, 77), (8, 77)]) + check "emitted dispatcher decodes ABI argument words" + (emittedDispatchMatchesReferenceWithExpected calldataArgDispatchSmokeContract + (sampleIRTx 0x77777777 [0xABCD]) [12] [12] true none [(12, 0xABCD)]) + check "emitted dispatcher executes mappingSlot helper writes" + (emittedDispatchMatchesReferenceWithExpected mappingHelperDispatchSmokeContract + (sampleIRTx 0x88888888 [5]) [mappingWriteSlot] [mappingWriteSlot] true none + [(mappingWriteSlot, 909)]) + check "emitted dispatcher executes mappingSlot helper reads" + (emittedDispatchMatchesReferenceWithExpected mappingHelperReadDispatchSmokeContract + (sampleIRTx 0x99999999 [5]) [mappingReadSlot, 15] [mappingReadSlot, 15] true none + [(mappingReadSlot, 515), (15, 515)]) + check "emitted dispatcher executes nested mappingSlot helper writes" + (emittedDispatchMatchesReferenceWithExpected nestedMappingHelperDispatchSmokeContract + (sampleIRTx 0xAAAAAAAA [3, 4]) [nestedMappingWriteSlot] [nestedMappingWriteSlot] + true none [(nestedMappingWriteSlot, 808)]) + check "compiled dispatcher reads packed mapping struct members" + (emittedCompiledDispatchMatchesReferenceWithExpected packedMappingDispatchSmokeContract + (sampleIRTx 0xBBBBBBBB [6]) [packedMappingSlot, 16] [packedMappingSlot, 16] + true none [(packedMappingSlot, 0x123456), (16, 0x34)]) + check "compiled dispatcher writes packed mapping struct members" + (emittedCompiledDispatchMatchesReferenceWithExpected packedMappingDispatchSmokeContract + (sampleIRTx 0xCCCCCCCC [6, 0xABCD]) [packedMappingSlot] [packedMappingSlot] + true none [(packedMappingSlot, 0x12CD56)]) + check "compiled dispatcher reads multi-word mapping struct members" + (emittedCompiledDispatchMatchesReferenceWithExpected multiWordMappingDispatchSmokeContract + (sampleIRTx 0xDDDDDDDD [7]) + [multiWordMappingBaseSlot, multiWordMappingMemberSlot, 17] + [multiWordMappingBaseSlot, multiWordMappingMemberSlot, 17] + true none + [(multiWordMappingBaseSlot, 0xAAAA), (multiWordMappingMemberSlot, 0xBBBB), (17, 0xBBBB)]) + check "compiled dispatcher writes multi-word mapping struct members" + (emittedCompiledDispatchMatchesReferenceWithExpected multiWordMappingDispatchSmokeContract + (sampleIRTx 0xEEEEEEEE [7, 0x1234]) + [multiWordMappingBaseSlot, multiWordMappingMemberSlot] + [multiWordMappingBaseSlot, multiWordMappingMemberSlot] + true none + [(multiWordMappingBaseSlot, 0xAAAA), (multiWordMappingMemberSlot, 0x1234)]) + check "compiled dispatcher reads nested multi-word mapping struct members" + (emittedCompiledDispatchMatchesReferenceWithExpected multiWordMappingDispatchSmokeContract + (sampleIRTx 0xDADADADA [3, 4]) + [nestedMultiWordMappingBaseSlot, nestedMultiWordMappingMemberSlot, 17] + [nestedMultiWordMappingBaseSlot, nestedMultiWordMappingMemberSlot, 17] + true none + [(nestedMultiWordMappingBaseSlot, 0xCCCC), (nestedMultiWordMappingMemberSlot, 0xDDDD), + (17, 0xDDDD)]) + check "compiled dispatcher writes nested multi-word mapping struct members" + (emittedCompiledDispatchMatchesReferenceWithExpected multiWordMappingDispatchSmokeContract + (sampleIRTx 0xEFEFEFEF [3, 4, 0x5678]) + [nestedMultiWordMappingBaseSlot, nestedMultiWordMappingMemberSlot] + [nestedMultiWordMappingBaseSlot, nestedMultiWordMappingMemberSlot] + true none + [(nestedMultiWordMappingBaseSlot, 0xCCCC), (nestedMultiWordMappingMemberSlot, 0x5678)]) + check "emitted dispatcher projects 32-byte return halts" + (emittedDispatchMatchesReferenceWithExpected returnDispatchSmokeContract + (sampleIRTx 0x33333333) [] [] true (some 42) []) + check "emitted dispatcher projects multi-word return fallback" + (emittedDispatchMatchesReferenceWithExpected multiWordReturnDispatchSmokeContract + (sampleIRTx 0x55555555) [] [] true (some 0) []) + check "emitted dispatcher rolls back memory-backed revert" + (emittedDispatchMatchesReferenceWithExpected memoryRevertDispatchSmokeContract + (sampleIRTx 0x66666666) [7] [7] false none [(7, 77)]) + +end Compiler.Proofs.YulGeneration.Backends.NativeDispatchOracleTest + +def main : IO Unit := + Compiler.Proofs.YulGeneration.Backends.NativeDispatchOracleTest.main diff --git a/Compiler/Proofs/YulGeneration/Backends/EvmYulLeanNativeHarness.lean b/Compiler/Proofs/YulGeneration/Backends/EvmYulLeanNativeHarness.lean index 04aef565a..93a1696e2 100644 --- a/Compiler/Proofs/YulGeneration/Backends/EvmYulLeanNativeHarness.lean +++ b/Compiler/Proofs/YulGeneration/Backends/EvmYulLeanNativeHarness.lean @@ -1,6 +1,7 @@ import Compiler.Proofs.YulGeneration.Backends.EvmYulLeanAdapter import Compiler.Proofs.YulGeneration.Backends.EvmYulLeanStateBridge import Compiler.Proofs.YulGeneration.ReferenceOracle.Semantics +import Compiler.Codegen import EvmYul.Yul.Interpreter namespace Compiler.Proofs.YulGeneration.Backends.Native @@ -54,12 +55,3462 @@ def initialState perm := true } } .Ok shared' ∅ +/-! ## Native Environment Support Boundary -/ + +partial def yulExprUsesBuiltinExceptFunctions + (builtin : String) + (functionNames : List String) : + YulExpr → Bool + | .call func args => + ((func == builtin) && !functionNames.contains func) || + args.any (yulExprUsesBuiltinExceptFunctions builtin functionNames) + | _ => false + +partial def yulExprCalledFunctions : YulExpr → List String + | .call func args => func :: args.flatMap yulExprCalledFunctions + | _ => [] + +mutual + partial def yulStmtUsesBuiltinExceptFunctions + (builtin : String) + (functionNames : List String) : + YulStmt → Bool + | .let_ _ value => yulExprUsesBuiltinExceptFunctions builtin functionNames value + | .letMany _ value => yulExprUsesBuiltinExceptFunctions builtin functionNames value + | .assign _ value => yulExprUsesBuiltinExceptFunctions builtin functionNames value + | .expr e => yulExprUsesBuiltinExceptFunctions builtin functionNames e + | .if_ cond body => + yulExprUsesBuiltinExceptFunctions builtin functionNames cond || + yulStmtsUseBuiltinExceptFunctions builtin functionNames body + | .for_ init cond post body => + yulStmtsUseBuiltinExceptFunctions builtin functionNames init || + yulExprUsesBuiltinExceptFunctions builtin functionNames cond || + yulStmtsUseBuiltinExceptFunctions builtin functionNames post || + yulStmtsUseBuiltinExceptFunctions builtin functionNames body + | .switch discr cases defaultBody => + yulExprUsesBuiltinExceptFunctions builtin functionNames discr || + cases.any (fun (_, body) => + yulStmtsUseBuiltinExceptFunctions builtin functionNames body) || + defaultBody.any (yulStmtsUseBuiltinExceptFunctions builtin functionNames) + | .block stmts => yulStmtsUseBuiltinExceptFunctions builtin functionNames stmts + | .funcDef _ _ _ body => + yulStmtsUseBuiltinExceptFunctions builtin functionNames body + | .comment _ | .leave => false + + partial def yulStmtsUseBuiltinExceptFunctions + (builtin : String) + (functionNames : List String) + (stmts : List YulStmt) : + Bool := + stmts.any (yulStmtUsesBuiltinExceptFunctions builtin functionNames) +end + +mutual + partial def yulStmtCalledFunctions : YulStmt → List String + | .let_ _ value => yulExprCalledFunctions value + | .letMany _ value => yulExprCalledFunctions value + | .assign _ value => yulExprCalledFunctions value + | .expr e => yulExprCalledFunctions e + | .if_ cond body => yulExprCalledFunctions cond ++ yulStmtsCalledFunctions body + | .for_ init cond post body => + yulStmtsCalledFunctions init ++ + yulExprCalledFunctions cond ++ + yulStmtsCalledFunctions post ++ + yulStmtsCalledFunctions body + | .switch discr cases defaultBody => + yulExprCalledFunctions discr ++ + cases.flatMap (fun entry => yulStmtsCalledFunctions entry.2) ++ + (defaultBody.map yulStmtsCalledFunctions).getD [] + | .block stmts => yulStmtsCalledFunctions stmts + | .funcDef _ _ _ body => yulStmtsCalledFunctions body + | .comment _ | .leave => [] + + partial def yulStmtsCalledFunctions (stmts : List YulStmt) : List String := + stmts.flatMap yulStmtCalledFunctions +end + +def yulFunctionBodies (runtimeCode : List YulStmt) : List (String × List YulStmt) := + runtimeCode.filterMap fun + | .funcDef name _ _ body => some (name, body) + | _ => none + +def selectorExprMatchesGeneratedDispatcher : YulExpr → Bool + | .call "shr" [.lit shift, .call "calldataload" [.lit 0]] => + shift == Compiler.Constants.selectorShift + | _ => false + +@[simp] theorem selectorExprMatchesGeneratedDispatcher_selectorExpr : + selectorExprMatchesGeneratedDispatcher + Compiler.Proofs.YulGeneration.selectorExpr = true := by + simp [selectorExprMatchesGeneratedDispatcher, + Compiler.Proofs.YulGeneration.selectorExpr] + +def selectedSwitchBody + (selector : Nat) + (cases : List (Nat × List YulStmt)) + (defaultBody : Option (List YulStmt)) : + List YulStmt := + match cases.find? (fun entry => entry.1 == selector) with + | some (_, body) => body + | none => defaultBody.getD [] + +@[simp] theorem selectedSwitchBody_hit + (selector : Nat) + (cases : List (Nat × List YulStmt)) + (defaultBody : Option (List YulStmt)) + (body : List YulStmt) + (hFind : cases.find? (fun entry => entry.1 == selector) = + some (selector, body)) : + selectedSwitchBody selector cases defaultBody = body := by + unfold selectedSwitchBody + rw [hFind] + +@[simp] theorem selectedSwitchBody_miss + (selector : Nat) + (cases : List (Nat × List YulStmt)) + (defaultBody : Option (List YulStmt)) + (hFind : cases.find? (fun entry => entry.1 == selector) = none) : + selectedSwitchBody selector cases defaultBody = defaultBody.getD [] := by + unfold selectedSwitchBody + rw [hFind] + +def nativeDispatchSelector (tx : YulTransaction) : Nat := + tx.functionSelector % Compiler.Constants.selectorModulus + +@[simp] theorem nativeDispatchSelector_of_selector_lt + (tx : YulTransaction) + (hSelector : tx.functionSelector < Compiler.Constants.selectorModulus) : + nativeDispatchSelector tx = tx.functionSelector := by + simp [nativeDispatchSelector, Nat.mod_eq_of_lt hSelector] + +partial def yulStmtsUseBuiltinWithCalledFunctions + (fuel : Nat) + (builtin : String) + (functionBodies : List (String × List YulStmt)) + (stmts : List YulStmt) : + Bool := + yulStmtsUseBuiltinExceptFunctions builtin (functionBodies.map Prod.fst) stmts || + match fuel with + | 0 => false + | fuel' + 1 => + (yulStmtsCalledFunctions stmts).any fun name => + match functionBodies.find? (fun entry => entry.1 == name) with + | some (_, body) => + yulStmtsUseBuiltinWithCalledFunctions fuel' builtin functionBodies body + | none => false + +mutual + partial def yulStmtUsesBuiltinOnNativeRuntimePath + (builtin : String) + (selector : Nat) + (functionBodies : List (String × List YulStmt)) : + YulStmt → Bool + | .funcDef _ _ _ _ => false + | .block [ + .let_ "__has_selector" _, + .if_ (YulExpr.call "iszero" [YulExpr.ident "__has_selector"]) _, + .if_ (YulExpr.ident "__has_selector") [ + .switch discr cases defaultBody + ] + ] => + if selectorExprMatchesGeneratedDispatcher discr then + yulExprUsesBuiltinExceptFunctions builtin (functionBodies.map Prod.fst) discr || + yulStmtsUseBuiltinWithCalledFunctions (functionBodies.length + 1) + builtin functionBodies (selectedSwitchBody selector cases defaultBody) + else + yulStmtsUseBuiltinWithCalledFunctions (functionBodies.length + 1) + builtin functionBodies [ + .block [ + .let_ "__has_selector" (.lit 0), + .if_ (YulExpr.call "iszero" [YulExpr.ident "__has_selector"]) [], + .if_ (YulExpr.ident "__has_selector") [ + .switch discr cases defaultBody + ] + ] + ] + | .block stmts => + yulStmtsUseBuiltinOnNativeRuntimePath builtin selector functionBodies stmts + | stmt => + yulStmtsUseBuiltinWithCalledFunctions (functionBodies.length + 1) + builtin functionBodies [stmt] + + partial def yulStmtsUseBuiltinOnNativeRuntimePath + (builtin : String) + (selector : Nat) + (functionBodies : List (String × List YulStmt)) + (stmts : List YulStmt) : + Bool := + stmts.any (yulStmtUsesBuiltinOnNativeRuntimePath builtin selector functionBodies) +end + +def nativeRuntimePathUsesBuiltin + (builtin : String) + (runtimeCode : List YulStmt) + (tx : YulTransaction) : + Bool := + yulStmtsUseBuiltinOnNativeRuntimePath builtin (nativeDispatchSelector tx) + (yulFunctionBodies runtimeCode) runtimeCode + +def unsupportedNativeHeaderBuiltinNames : List String := + ["coinbase", "difficulty", "prevrandao", "gaslimit", "basefee", "gasprice"] + +def nativeRuntimePathUsesAnyBuiltin + (builtins : List String) + (runtimeCode : List YulStmt) + (tx : YulTransaction) : + Bool := + builtins.any fun builtin => nativeRuntimePathUsesBuiltin builtin runtimeCode tx + +def nativeRuntimePathUsesUnsupportedHeaderBuiltin + (runtimeCode : List YulStmt) + (tx : YulTransaction) : + Bool := + nativeRuntimePathUsesAnyBuiltin unsupportedNativeHeaderBuiltinNames runtimeCode tx + +def nativeBlobBaseFeeRepresentable (fee : Nat) : Bool := + fee == EvmYul.MIN_BASE_FEE_PER_BLOB_GAS + +def nativeChainIdRepresentable (chainId : Nat) : Bool := + chainId == EvmYul.chainId + +def unsupportedNativeBlobBaseFeeError : AdapterError := + "native EVMYulLean blobbasefee requires representable blobBaseFee; \ + current bridge supports only EVMYulLean minimum blob gas price" + +def unsupportedNativeChainIdError : AdapterError := + "native EVMYulLean chainid requires representable chainId; \ + current bridge supports only EVMYulLean global chain id" + +def unsupportedNativeHeaderBuiltinError : AdapterError := + "native EVMYulLean selected runtime path uses a header builtin that is not \ + represented in Verity's YulTransaction bridge" + +def validateNativeRuntimeEnvironment + (runtimeCode : List YulStmt) + (tx : YulTransaction) : + Except AdapterError Unit := + if nativeRuntimePathUsesBuiltin "chainid" runtimeCode tx && + !nativeChainIdRepresentable tx.chainId then + .error unsupportedNativeChainIdError + else if nativeRuntimePathUsesBuiltin "blobbasefee" runtimeCode tx && + !nativeBlobBaseFeeRepresentable tx.blobBaseFee then + .error unsupportedNativeBlobBaseFeeError + else if nativeRuntimePathUsesUnsupportedHeaderBuiltin runtimeCode tx then + .error unsupportedNativeHeaderBuiltinError + else + .ok () + +@[simp] theorem nativeChainIdRepresentable_global : + nativeChainIdRepresentable EvmYul.chainId = true := by + simp [nativeChainIdRepresentable] + +@[simp] theorem nativeBlobBaseFeeRepresentable_minimum : + nativeBlobBaseFeeRepresentable EvmYul.MIN_BASE_FEE_PER_BLOB_GAS = true := by + simp [nativeBlobBaseFeeRepresentable] + +@[simp] theorem validateNativeRuntimeEnvironment_noChainId_noBlobBaseFee + (runtimeCode : List YulStmt) + (tx : YulTransaction) + (hNoChainId : nativeRuntimePathUsesBuiltin "chainid" runtimeCode tx = false) + (hNoBlobBaseFee : nativeRuntimePathUsesBuiltin "blobbasefee" runtimeCode tx = false) + (hNoHeader : + nativeRuntimePathUsesUnsupportedHeaderBuiltin runtimeCode tx = false) : + validateNativeRuntimeEnvironment runtimeCode tx = .ok () := by + simp [validateNativeRuntimeEnvironment, hNoChainId, hNoBlobBaseFee, hNoHeader] + +@[simp] theorem validateNativeRuntimeEnvironment_representableBlobBaseFee + (runtimeCode : List YulStmt) + (tx : YulTransaction) + (hNoChainId : nativeRuntimePathUsesBuiltin "chainid" runtimeCode tx = false) + (hBlobBaseFee : + nativeBlobBaseFeeRepresentable tx.blobBaseFee = true) + (hNoHeader : + nativeRuntimePathUsesUnsupportedHeaderBuiltin runtimeCode tx = false) : + validateNativeRuntimeEnvironment runtimeCode tx = .ok () := by + simp [validateNativeRuntimeEnvironment, hNoChainId, hBlobBaseFee, hNoHeader] + +@[simp] theorem validateNativeRuntimeEnvironment_representableEnvironment + (runtimeCode : List YulStmt) + (tx : YulTransaction) + (hChainId : nativeChainIdRepresentable tx.chainId = true) + (hBlobBaseFee : + nativeBlobBaseFeeRepresentable tx.blobBaseFee = true) + (hNoHeader : + nativeRuntimePathUsesUnsupportedHeaderBuiltin runtimeCode tx = false) : + validateNativeRuntimeEnvironment runtimeCode tx = .ok () := by + simp [validateNativeRuntimeEnvironment, hChainId, hBlobBaseFee, hNoHeader] + +@[simp] theorem validateNativeRuntimeEnvironment_unsupportedChainId + (runtimeCode : List YulStmt) + (tx : YulTransaction) + (hUsesChainId : nativeRuntimePathUsesBuiltin "chainid" runtimeCode tx = true) + (hChainId : nativeChainIdRepresentable tx.chainId = false) : + validateNativeRuntimeEnvironment runtimeCode tx = + .error unsupportedNativeChainIdError := by + simp [validateNativeRuntimeEnvironment, hUsesChainId, hChainId] + +@[simp] theorem validateNativeRuntimeEnvironment_unsupportedBlobBaseFee + (runtimeCode : List YulStmt) + (tx : YulTransaction) + (hNoChainId : nativeRuntimePathUsesBuiltin "chainid" runtimeCode tx = false) + (hUsesBlobBaseFee : nativeRuntimePathUsesBuiltin "blobbasefee" runtimeCode tx = true) + (hBlobBaseFee : + nativeBlobBaseFeeRepresentable tx.blobBaseFee = false) : + validateNativeRuntimeEnvironment runtimeCode tx = + .error unsupportedNativeBlobBaseFeeError := by + simp [validateNativeRuntimeEnvironment, hNoChainId, hUsesBlobBaseFee, hBlobBaseFee] + +@[simp] theorem validateNativeRuntimeEnvironment_unsupportedHeaderBuiltin + (runtimeCode : List YulStmt) + (tx : YulTransaction) + (hNoChainId : nativeRuntimePathUsesBuiltin "chainid" runtimeCode tx = false) + (hNoBlobBaseFee : nativeRuntimePathUsesBuiltin "blobbasefee" runtimeCode tx = false) + (hUsesHeader : + nativeRuntimePathUsesUnsupportedHeaderBuiltin runtimeCode tx = true) : + validateNativeRuntimeEnvironment runtimeCode tx = + .error unsupportedNativeHeaderBuiltinError := by + simp [validateNativeRuntimeEnvironment, hNoChainId, hNoBlobBaseFee, hUsesHeader] + +@[simp] theorem initialState_installsExecutionContract + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) : + (initialState contract tx storage observableSlots).sharedState.executionEnv.code = + contract ∧ + (initialState contract tx storage observableSlots).sharedState.executionEnv.perm = + true := by + simp [initialState, EvmYul.Yul.State.sharedState] + +@[simp] theorem initialState_installsCurrentAccountContract + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) : + ((initialState contract tx storage observableSlots).sharedState.accountMap.find? + (natToAddress tx.thisAddress)).map (fun account => account.code) = + some contract := by + simp only [initialState, EvmYul.Yul.State.sharedState] + rw [Batteries.RBMap.find?_insert_of_eq _ Std.ReflCmp.compare_self] + split <;> simp + +@[simp] theorem initialState_transactionEnvironment + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) : + (initialState contract tx storage observableSlots).sharedState.executionEnv.source = + natToAddress tx.sender ∧ + (initialState contract tx storage observableSlots).sharedState.executionEnv.sender = + natToAddress tx.sender ∧ + (initialState contract tx storage observableSlots).sharedState.executionEnv.codeOwner = + natToAddress tx.thisAddress ∧ + (initialState contract tx storage observableSlots).sharedState.executionEnv.weiValue = + natToUInt256 tx.msgValue ∧ + (initialState contract tx storage observableSlots).sharedState.executionEnv.header.timestamp = + tx.blockTimestamp ∧ + (initialState contract tx storage observableSlots).sharedState.executionEnv.header.number = + tx.blockNumber ∧ + (initialState contract tx storage observableSlots).sharedState.executionEnv.calldata = + calldataToByteArray tx.functionSelector tx.args := by + simp [initialState, EvmYul.Yul.State.sharedState, YulState.initial, toSharedState, + mkBlockHeader] + +@[simp] theorem initialState_source + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) : + (initialState contract tx storage observableSlots).sharedState.executionEnv.source = + natToAddress tx.sender := by + simp [initialState, EvmYul.Yul.State.sharedState, YulState.initial, toSharedState, + mkBlockHeader] + +@[simp] theorem initialState_sender + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) : + (initialState contract tx storage observableSlots).sharedState.executionEnv.sender = + natToAddress tx.sender := by + simp [initialState, EvmYul.Yul.State.sharedState, YulState.initial, toSharedState, + mkBlockHeader] + +@[simp] theorem initialState_codeOwner + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) : + (initialState contract tx storage observableSlots).sharedState.executionEnv.codeOwner = + natToAddress tx.thisAddress := by + simp [initialState, EvmYul.Yul.State.sharedState, YulState.initial, toSharedState, + mkBlockHeader] + +@[simp] theorem initialState_weiValue + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) : + (initialState contract tx storage observableSlots).sharedState.executionEnv.weiValue = + natToUInt256 tx.msgValue := by + simp [initialState, EvmYul.Yul.State.sharedState, YulState.initial, toSharedState, + mkBlockHeader] + +@[simp] theorem initialState_blockTimestamp + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) : + (initialState contract tx storage observableSlots).sharedState.executionEnv.header.timestamp = + tx.blockTimestamp := by + simp [initialState, EvmYul.Yul.State.sharedState, YulState.initial, toSharedState, + mkBlockHeader] + +@[simp] theorem initialState_blockNumber + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) : + (initialState contract tx storage observableSlots).sharedState.executionEnv.header.number = + tx.blockNumber := by + simp [initialState, EvmYul.Yul.State.sharedState, YulState.initial, toSharedState, + mkBlockHeader] + +@[simp] theorem initialState_calldata + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) : + (initialState contract tx storage observableSlots).sharedState.executionEnv.calldata = + calldataToByteArray tx.functionSelector tx.args := by + simp [initialState, EvmYul.Yul.State.sharedState, YulState.initial, toSharedState, + mkBlockHeader] + +@[simp] theorem initialState_calldataSize + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) : + (initialState contract tx storage observableSlots).sharedState.executionEnv.calldata.size = + 4 + tx.args.length * 32 := by + simp [initialState, EvmYul.Yul.State.sharedState, YulState.initial, toSharedState, + calldataToByteArray_size] + +private theorem byteArray_get?_append_left + {a b : ByteArray} {i : Nat} (h : i < a.size) : + (a ++ b).get? i = a.get? i := by + unfold ByteArray.get? + split + · apply congrArg some + have hEq : (a ++ b)[i] = a[i] := ByteArray.get_append_left h + convert hEq using 1 + · exact False.elim (by + rename_i hAppend + exact hAppend (by + rw [ByteArray.size_append] + exact Nat.lt_of_lt_of_le h (Nat.le_add_right a.size b.size))) + +/-- Reading the first ABI word from offset zero preserves every source byte + already present in the first 32-byte window. This isolates the non-opaque + part of EVMYulLean's `ByteArray.readBytes`; padding may still come from + `ffi.ByteArray.zeroes`, but the dispatcher selector only depends on bytes + 0 through 3. -/ +theorem readBytes_zero_get?_of_lt_source + (source : ByteArray) + (i : Nat) + (hi : i < source.size) + (hi32 : i < 32) : + (ByteArray.readBytes source 0 32).get? i = source.get? i := by + unfold ByteArray.readBytes + have hsmall : (decide (0 < 2 ^ 64) && decide (32 < 2 ^ 64)) = true := by + norm_num + simp only [hsmall, ↓reduceIte] + have hiData : i < source.data.size := by + simpa using hi + have hCopySize : i < (source.copySlice 0 ByteArray.empty 0 32).size := by + simp [ByteArray.size, ByteArray.data_copySlice] + exact ⟨hi32, hiData⟩ + calc + (source.copySlice 0 ByteArray.empty 0 32 ++ + ffi.ByteArray.zeroes + { toBitVec := ↑32 - + ↑(source.copySlice 0 ByteArray.empty 0 32).size }).get? i + = (source.copySlice 0 ByteArray.empty 0 32).get? i := + byteArray_get?_append_left hCopySize + _ = source.get? i := by + unfold ByteArray.get? + split + · simp [ByteArray.get] + · contradiction + +@[simp] theorem initialState_calldataReadWord_selectorByte0 + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) : + (ByteArray.readBytes + (initialState contract tx storage observableSlots).toState.executionEnv.calldata + 0 32).get? 0 = + some (UInt8.ofNat (tx.functionSelector / 2^24 % 256)) := by + rw [readBytes_zero_get?_of_lt_source] + · rw [show + (initialState contract tx storage observableSlots).toState.executionEnv.calldata = + calldataToByteArray tx.functionSelector tx.args by + simp [initialState, EvmYul.Yul.State.toState, YulState.initial, + toSharedState, mkBlockHeader]] + simp + · rw [show + (initialState contract tx storage observableSlots).toState.executionEnv.calldata = + calldataToByteArray tx.functionSelector tx.args by + simp [initialState, EvmYul.Yul.State.toState, YulState.initial, + toSharedState, mkBlockHeader]] + simp [calldataToByteArray_size] + · norm_num + +@[simp] theorem initialState_calldataReadWord_selectorByte1 + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) : + (ByteArray.readBytes + (initialState contract tx storage observableSlots).toState.executionEnv.calldata + 0 32).get? 1 = + some (UInt8.ofNat (tx.functionSelector / 2^16 % 256)) := by + rw [readBytes_zero_get?_of_lt_source] + · rw [show + (initialState contract tx storage observableSlots).toState.executionEnv.calldata = + calldataToByteArray tx.functionSelector tx.args by + simp [initialState, EvmYul.Yul.State.toState, YulState.initial, + toSharedState, mkBlockHeader]] + simp + · rw [show + (initialState contract tx storage observableSlots).toState.executionEnv.calldata = + calldataToByteArray tx.functionSelector tx.args by + simp [initialState, EvmYul.Yul.State.toState, YulState.initial, + toSharedState, mkBlockHeader]] + simp [calldataToByteArray_size] + omega + · norm_num + +@[simp] theorem initialState_calldataReadWord_selectorByte2 + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) : + (ByteArray.readBytes + (initialState contract tx storage observableSlots).toState.executionEnv.calldata + 0 32).get? 2 = + some (UInt8.ofNat (tx.functionSelector / 2^8 % 256)) := by + rw [readBytes_zero_get?_of_lt_source] + · rw [show + (initialState contract tx storage observableSlots).toState.executionEnv.calldata = + calldataToByteArray tx.functionSelector tx.args by + simp [initialState, EvmYul.Yul.State.toState, YulState.initial, + toSharedState, mkBlockHeader]] + simp + · rw [show + (initialState contract tx storage observableSlots).toState.executionEnv.calldata = + calldataToByteArray tx.functionSelector tx.args by + simp [initialState, EvmYul.Yul.State.toState, YulState.initial, + toSharedState, mkBlockHeader]] + simp [calldataToByteArray_size] + omega + · norm_num + +@[simp] theorem initialState_calldataReadWord_selectorByte3 + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) : + (ByteArray.readBytes + (initialState contract tx storage observableSlots).toState.executionEnv.calldata + 0 32).get? 3 = + some (UInt8.ofNat (tx.functionSelector % 256)) := by + rw [readBytes_zero_get?_of_lt_source] + · rw [show + (initialState contract tx storage observableSlots).toState.executionEnv.calldata = + calldataToByteArray tx.functionSelector tx.args by + simp [initialState, EvmYul.Yul.State.toState, YulState.initial, + toSharedState, mkBlockHeader]] + simp + · rw [show + (initialState contract tx storage observableSlots).toState.executionEnv.calldata = + calldataToByteArray tx.functionSelector tx.args by + simp [initialState, EvmYul.Yul.State.toState, YulState.initial, + toSharedState, mkBlockHeader]] + simp [calldataToByteArray_size] + omega + · norm_num + +private theorem byteArray_data_toList_get?_of_get? + (ba : ByteArray) + (i : Nat) + (b : UInt8) + (h : ba.get? i = some b) : + ba.data.toList[i]? = some b := by + unfold ByteArray.get? at h + split at h + · cases h + rw [Array.getElem?_toList] + simp [ByteArray.get] + · contradiction + +private theorem list_reverse_eq_drop4_reverse_append_four + {α : Type} + (xs : List α) + (b0 b1 b2 b3 : α) + (h0 : xs[0]? = some b0) + (h1 : xs[1]? = some b1) + (h2 : xs[2]? = some b2) + (h3 : xs[3]? = some b3) : + xs.reverse = (xs.drop 4).reverse ++ [b3, b2, b1, b0] := by + cases xs with + | nil => simp at h0 + | cons x0 xs => + simp at h0 + subst x0 + cases xs with + | nil => simp at h1 + | cons x1 xs => + simp at h1 + subst x1 + cases xs with + | nil => simp at h2 + | cons x2 xs => + simp at h2 + subst x2 + cases xs with + | nil => simp at h3 + | cons x3 xs => + simp at h3 + subst x3 + simp + +/-- The decoded native calldata word has the four ABI selector bytes at the + high end of EVMYulLean's little-endian `fromBytes'` input. Proving the + selector value itself still needs the opaque `readBytes` result length. -/ +theorem initialState_calldataReadWord_selectorPrefix + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) : + let bytes := ByteArray.readBytes + (initialState contract tx storage observableSlots).toState.executionEnv.calldata 0 32 + bytes.data.toList.reverse = + (bytes.data.toList.drop 4).reverse ++ + [UInt8.ofNat (tx.functionSelector % 256), + UInt8.ofNat (tx.functionSelector / 2^8 % 256), + UInt8.ofNat (tx.functionSelector / 2^16 % 256), + UInt8.ofNat (tx.functionSelector / 2^24 % 256)] := by + intro bytes + apply list_reverse_eq_drop4_reverse_append_four + · exact byteArray_data_toList_get?_of_get? bytes 0 _ + (initialState_calldataReadWord_selectorByte0 contract tx storage observableSlots) + · exact byteArray_data_toList_get?_of_get? bytes 1 _ + (initialState_calldataReadWord_selectorByte1 contract tx storage observableSlots) + · exact byteArray_data_toList_get?_of_get? bytes 2 _ + (initialState_calldataReadWord_selectorByte2 contract tx storage observableSlots) + · exact byteArray_data_toList_get?_of_get? bytes 3 _ + (initialState_calldataReadWord_selectorByte3 contract tx storage observableSlots) + +/-- Recompose the four ABI selector bytes into the normalized 32-bit + dispatcher selector. This isolates the remaining native byte-decoding proof: + once `calldataload(0) >>> 224` is reduced to the four high calldata bytes, + this theorem closes the arithmetic side against the interpreter oracle. -/ +theorem selectorBytesAsNat (selector : Nat) : + (selector / 2^24 % 256) * 2^24 + + (selector / 2^16 % 256) * 2^16 + + (selector / 2^8 % 256) * 2^8 + + (selector % 256) = + selector % Compiler.Constants.selectorModulus := by + have hb0 : selector / 2^24 % 256 = + (selector % 2^32) / 2^24 := by + omega + have hb1 : selector / 2^16 % 256 = + ((selector % 2^32) % 2^24) / 2^16 := by + omega + have hb2 : selector / 2^8 % 256 = + (((selector % 2^32) % 2^24) % 2^16) / 2^8 := by + omega + have hb3 : selector % 256 = + ((((selector % 2^32) % 2^24) % 2^16) % 2^8) := by + omega + rw [hb0, hb1, hb2, hb3] + have h1 := Nat.div_add_mod (selector % 2^32) (2^24) + have h2 := Nat.div_add_mod ((selector % 2^32) % 2^24) (2^16) + have h3 := Nat.div_add_mod (((selector % 2^32) % 2^24) % 2^16) (2^8) + simp [Compiler.Constants.selectorModulus] + omega + +private theorem fromBytes'_append (xs ys : List UInt8) : + EvmYul.fromBytes' (xs ++ ys) = + EvmYul.fromBytes' xs + 2^(8 * xs.length) * EvmYul.fromBytes' ys := by + induction xs with + | nil => + simp [EvmYul.fromBytes'] + | cons x xs ih => + simp only [List.cons_append, EvmYul.fromBytes'] + rw [ih] + rw [show 8 * (x :: xs).length = 8 + 8 * xs.length by + simp [Nat.mul_add, Nat.add_comm]] + rw [Nat.pow_add] + ring + +private theorem fromBytes'_lt (xs : List UInt8) : + EvmYul.fromBytes' xs < 2^(8 * xs.length) := by + induction xs with + | nil => + simp [EvmYul.fromBytes'] + | cons x xs ih => + unfold EvmYul.fromBytes' + have hx : x.toFin.val < 2^8 := by + have := x.toFin.isLt + norm_num at this ⊢ + exact this + simp only [List.length_cons, Nat.mul_succ, Nat.add_comm, Nat.pow_add] + have _ := + Nat.add_le_of_le_sub + (Nat.one_le_pow _ _ (by decide)) + (Nat.le_sub_one_of_lt ih) + linarith + +private theorem uint256_ofNat_eq_mk + (value : Nat) + (hvalue : value < EvmYul.UInt256.size) : + EvmYul.UInt256.ofNat value = ⟨⟨value, hvalue⟩⟩ := by + apply congrArg EvmYul.UInt256.mk + apply Fin.ext + simp [Nat.mod_eq_of_lt hvalue] + +private theorem uint256_eq_of_toNat_eq + (a b : EvmYul.UInt256) + (h : a.toNat = b.toNat) : + a = b := by + cases a with + | mk av => + cases b with + | mk bv => + apply congrArg EvmYul.UInt256.mk + apply Fin.ext + simpa [EvmYul.UInt256.toNat] using h + +private theorem uint256_ofNat_toNat_of_lt + (value : Nat) + (hvalue : value < EvmYul.UInt256.size) : + (EvmYul.UInt256.ofNat value).toNat = value := by + change (Fin.ofNat EvmYul.UInt256.size value).val = value + simp [Fin.ofNat] + exact Nat.mod_eq_of_lt hvalue + +private theorem uint256_shiftRight_224_mk_toNat + (value : Nat) + (hvalue : value < EvmYul.UInt256.size) : + EvmYul.UInt256.toNat + (EvmYul.UInt256.shiftRight ⟨⟨value, hvalue⟩⟩ + ⟨⟨Compiler.Constants.selectorShift, + by norm_num [Compiler.Constants.selectorShift, EvmYul.UInt256.size]⟩⟩) = + value / 2^Compiler.Constants.selectorShift := by + have hshift : Compiler.Constants.selectorShift < 256 := by + norm_num [Compiler.Constants.selectorShift] + have hg : ¬ 256 ≤ + (⟨⟨Compiler.Constants.selectorShift, + by norm_num [Compiler.Constants.selectorShift, EvmYul.UInt256.size]⟩⟩ : + EvmYul.UInt256).val := by + change ¬ 256 ≤ Compiler.Constants.selectorShift + exact Nat.not_le_of_lt hshift + simp [EvmYul.UInt256.shiftRight, EvmYul.UInt256.toNat, hg, + Nat.shiftRight_eq_div_pow] + +theorem uint256_shiftRight_224_ofNat_toNat + (value : Nat) + (hvalue : value < EvmYul.UInt256.size) : + EvmYul.UInt256.toNat + (EvmYul.UInt256.shiftRight + (EvmYul.UInt256.ofNat value) + (EvmYul.UInt256.ofNat Compiler.Constants.selectorShift)) = + value / 2^Compiler.Constants.selectorShift := by + rw [uint256_ofNat_eq_mk value hvalue] + rw [uint256_ofNat_eq_mk Compiler.Constants.selectorShift + (by norm_num [Compiler.Constants.selectorShift, EvmYul.UInt256.size])] + exact uint256_shiftRight_224_mk_toNat value hvalue + +private theorem fromBytes'_four + (b0 b1 b2 b3 : UInt8) : + EvmYul.fromBytes' [b3, b2, b1, b0] = + b3.toFin.val + 2^8 * b2.toFin.val + + 2^16 * b1.toFin.val + 2^24 * b0.toFin.val := by + simp [EvmYul.fromBytes'] + omega + +private theorem fromBytes'_tail4_shift + (b0 b1 b2 b3 : UInt8) + (tail : List UInt8) + (hlen : tail.length = 28) : + EvmYul.fromBytes' (tail.reverse ++ [b3, b2, b1, b0]) / 2^224 = + b0.toFin.val * 2^24 + + b1.toFin.val * 2^16 + + b2.toFin.val * 2^8 + + b3.toFin.val := by + rw [fromBytes'_append] + have htailLen : tail.reverse.length = 28 := by + simp [hlen] + have htailBound : EvmYul.fromBytes' tail.reverse < 2^224 := by + have h := fromBytes'_lt tail.reverse + simpa [htailLen] using h + rw [fromBytes'_four] + rw [htailLen] + norm_num + omega + +/-- Once the EVMYulLean calldata word has been reduced to a 32-byte big-endian + list whose first four bytes are the ABI selector, shifting the corresponding + `fromBytes'` value right by 224 yields the normalized dispatcher selector. + The remaining native selector proof only has to connect + `ByteArray.readBytes`/`State.calldataload` to this list shape. -/ +theorem fromBytes'_selectorPrefix_shift + (selector : Nat) + (tail : List UInt8) + (hlen : tail.length = 28) : + EvmYul.fromBytes' + (tail.reverse ++ + [UInt8.ofNat (selector % 256), + UInt8.ofNat (selector / 2^8 % 256), + UInt8.ofNat (selector / 2^16 % 256), + UInt8.ofNat (selector / 2^24 % 256)]) / 2^224 = + selector % Compiler.Constants.selectorModulus := by + rw [fromBytes'_tail4_shift + (UInt8.ofNat (selector / 2^24 % 256)) + (UInt8.ofNat (selector / 2^16 % 256)) + (UInt8.ofNat (selector / 2^8 % 256)) + (UInt8.ofNat (selector % 256)) + tail hlen] + norm_num [UInt8.ofNat, UInt8.size] + have h0 : OfNat.ofNat (selector / 16777216 % 256) % 256 = + selector / 16777216 % 256 := by + change (selector / 16777216 % 256) % 256 = selector / 16777216 % 256 + exact Nat.mod_eq_of_lt (Nat.mod_lt _ (by norm_num)) + have h1 : OfNat.ofNat (selector / 65536 % 256) % 256 = + selector / 65536 % 256 := by + change (selector / 65536 % 256) % 256 = selector / 65536 % 256 + exact Nat.mod_eq_of_lt (Nat.mod_lt _ (by norm_num)) + have h2 : OfNat.ofNat (selector / 256 % 256) % 256 = + selector / 256 % 256 := by + change (selector / 256 % 256) % 256 = selector / 256 % 256 + exact Nat.mod_eq_of_lt (Nat.mod_lt _ (by norm_num)) + have h3 : OfNat.ofNat (selector % 256) % 256 = + selector % 256 := by + change (selector % 256) % 256 = selector % 256 + exact Nat.mod_eq_of_lt (Nat.mod_lt _ (by norm_num)) + rw [h0, h1, h2, h3] + simpa [Compiler.Constants.selectorModulus] using selectorBytesAsNat selector + +private theorem usize_sub_toNat_of_le_32 (n : Nat) (hn : n ≤ 32) : + ((OfNat.ofNat 32 : USize) - (OfNat.ofNat n : USize)).toNat = 32 - n := by + rw [USize.toNat_sub] + rw [USize.toNat_ofNat, USize.toNat_ofNat] + rcases System.Platform.numBits_eq with hbits | hbits + · rw [hbits] + have hnMod : n % 4294967296 = n := by + apply Nat.mod_eq_of_lt + omega + rw [hnMod] + omega + · rw [hbits] + have hnMod : n % 18446744073709551616 = n := by + apply Nat.mod_eq_of_lt + omega + rw [hnMod] + omega + +theorem readBytes_zero_32_size (source : ByteArray) : + (ByteArray.readBytes source 0 32).size = 32 := by + unfold ByteArray.readBytes + have hsmall : (decide (0 < 2 ^ 64) && decide (32 < 2 ^ 64)) = true := by + norm_num + simp only [hsmall, ↓reduceIte] + rw [ByteArray.size_append] + simp [ffi.ByteArray.zeroes, ByteArray.size] + rw [usize_sub_toNat_of_le_32] + · omega + · omega + +/-- Native selector decoding agrees with the interpreter selector by reducing + EVMYulLean `calldataload(0) >>> 224` to the four ABI selector bytes in + the initial bridged calldata. -/ +theorem initialState_selectorExpr_native_value + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) : + EvmYul.UInt256.toNat + (EvmYul.UInt256.shiftRight + (EvmYul.State.calldataload + (initialState contract tx storage observableSlots).toState + (EvmYul.UInt256.ofNat 0)) + (EvmYul.UInt256.ofNat Compiler.Constants.selectorShift)) = + tx.functionSelector % Compiler.Constants.selectorModulus := by + let bytes := ByteArray.readBytes + (initialState contract tx storage observableSlots).toState.executionEnv.calldata 0 32 + have hprefix := + initialState_calldataReadWord_selectorPrefix contract tx storage observableSlots + have hlen : bytes.data.toList.length = 32 := by + have hsize := readBytes_zero_32_size + (initialState contract tx storage observableSlots).toState.executionEnv.calldata + simpa [bytes, ByteArray.size] using hsize + have htailLen : (bytes.data.toList.drop 4).length = 28 := by + simp [hlen] + unfold EvmYul.State.calldataload EvmYul.uInt256OfByteArray + rw [show (EvmYul.UInt256.ofNat 0).toNat = 0 by + change (Fin.ofNat EvmYul.UInt256.size 0).val = 0 + simp] + rw [uint256_shiftRight_224_ofNat_toNat] + · rw [show (ByteArray.readBytes + (initialState contract tx storage observableSlots).toState.executionEnv.calldata + 0 32).data.toList.reverse = + bytes.data.toList.reverse by rfl] + rw [hprefix] + exact fromBytes'_selectorPrefix_shift tx.functionSelector + (bytes.data.toList.drop 4) htailLen + · have hlt := fromBytes'_lt bytes.data.toList.reverse + have hrevLen : bytes.data.toList.reverse.length = 32 := by + simp [hlen] + simpa [hrevLen, EvmYul.UInt256.size] using hlt + +theorem initialState_selectorExpr_native_value_of_readBytes_size + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) + (_hReadBytesSize : + ∀ source : ByteArray, (ByteArray.readBytes source 0 32).size = 32) : + EvmYul.UInt256.toNat + (EvmYul.UInt256.shiftRight + (EvmYul.State.calldataload + (initialState contract tx storage observableSlots).toState + (EvmYul.UInt256.ofNat 0)) + (EvmYul.UInt256.ofNat Compiler.Constants.selectorShift)) = + tx.functionSelector % Compiler.Constants.selectorModulus := + initialState_selectorExpr_native_value contract tx storage observableSlots + +theorem initialState_selectorExpr_native_uint256 + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) : + EvmYul.UInt256.shiftRight + (EvmYul.State.calldataload + (EvmYul.SharedState.toState + (initialState contract tx storage observableSlots).sharedState) + (EvmYul.UInt256.ofNat 0)) + (EvmYul.UInt256.ofNat Compiler.Constants.selectorShift) = + EvmYul.UInt256.ofNat + (tx.functionSelector % Compiler.Constants.selectorModulus) := by + apply uint256_eq_of_toNat_eq + rw [show EvmYul.UInt256.toNat + (EvmYul.UInt256.shiftRight + (EvmYul.State.calldataload + (EvmYul.SharedState.toState + (initialState contract tx storage observableSlots).sharedState) + (EvmYul.UInt256.ofNat 0)) + (EvmYul.UInt256.ofNat Compiler.Constants.selectorShift)) = + tx.functionSelector % Compiler.Constants.selectorModulus by + simpa [EvmYul.Yul.State.toState] using + initialState_selectorExpr_native_value contract tx storage observableSlots] + rw [uint256_ofNat_toNat_of_lt] + have hmod : + tx.functionSelector % Compiler.Constants.selectorModulus < + Compiler.Constants.selectorModulus := by + exact Nat.mod_lt _ (by norm_num [Compiler.Constants.selectorModulus]) + have hsel : + Compiler.Constants.selectorModulus < EvmYul.UInt256.size := by + norm_num [Compiler.Constants.selectorModulus, EvmYul.UInt256.size] + exact Nat.lt_trans hmod hsel + +/-- The native lowerer maps the generated dispatcher selector expression to + EVMYulLean's primitive `SHR(CALLDATALOAD(0), 224)` shape. -/ +theorem lowerExprNative_selectorExpr : + Backends.lowerExprNative Compiler.Proofs.YulGeneration.selectorExpr = + .Call (.inl (EvmYul.Operation.SHR : EvmYul.Operation .Yul)) + [.Lit (EvmYul.UInt256.ofNat Compiler.Constants.selectorShift), + .Call (.inl (EvmYul.Operation.CALLDATALOAD : EvmYul.Operation .Yul)) + [.Lit (EvmYul.UInt256.ofNat 0)]] := by + rw [show Compiler.Proofs.YulGeneration.selectorExpr = + YulExpr.call "shr" + [YulExpr.lit Compiler.Constants.selectorShift, + YulExpr.call "calldataload" [YulExpr.lit 0]] by rfl] + rw [Backends.lowerExprNative_call_runtimePrimOp "shr" _ + (EvmYul.Operation.SHR : EvmYul.Operation .Yul) (by rfl)] + change EvmYul.Yul.Ast.Expr.Call (Sum.inl EvmYul.Operation.SHR) + [Backends.lowerExprNative (YulExpr.lit Compiler.Constants.selectorShift), + Backends.lowerExprNative (YulExpr.call "calldataload" [YulExpr.lit 0])] = _ + rw [Backends.lowerExprNative_call_runtimePrimOp "calldataload" _ + (EvmYul.Operation.CALLDATALOAD : EvmYul.Operation .Yul) (by rfl)] + simp [Backends.lowerExprNative] + +@[simp] theorem step_calldataload_ok + (shared : EvmYul.SharedState .Yul) + (store : EvmYul.Yul.VarStore) + (offset : EvmYul.UInt256) : + EvmYul.step (τ := .Yul) EvmYul.Operation.CALLDATALOAD none + (.Ok shared store) [offset] = + .ok (.Ok shared store, + some (EvmYul.State.calldataload shared.toState offset)) := by + rfl + +@[simp] theorem step_shr_ok + (state : EvmYul.Yul.State) + (shift value : EvmYul.UInt256) : + EvmYul.step (τ := .Yul) EvmYul.Operation.SHR none state [shift, value] = + .ok (state, some (EvmYul.UInt256.shiftRight value shift)) := by + rfl + +@[simp] theorem step_eq_ok + (state : EvmYul.Yul.State) + (left right : EvmYul.UInt256) : + EvmYul.step (τ := .Yul) EvmYul.Operation.EQ none state [left, right] = + .ok (state, some (EvmYul.UInt256.eq left right)) := by + rfl + +@[simp] theorem step_iszero_ok + (state : EvmYul.Yul.State) + (value : EvmYul.UInt256) : + EvmYul.step (τ := .Yul) EvmYul.Operation.ISZERO none state [value] = + .ok (state, some (EvmYul.UInt256.isZero value)) := by + rfl + +@[simp] theorem step_and_ok + (state : EvmYul.Yul.State) + (left right : EvmYul.UInt256) : + EvmYul.step (τ := .Yul) EvmYul.Operation.AND none state [left, right] = + .ok (state, some (EvmYul.UInt256.land left right)) := by + rfl + +@[simp] theorem primCall_calldataload_ok + (fuel : Nat) + (shared : EvmYul.SharedState .Yul) + (store : EvmYul.Yul.VarStore) + (offset : EvmYul.UInt256) : + EvmYul.Yul.primCall (fuel + 1) (.Ok shared store) + EvmYul.Operation.CALLDATALOAD [offset] = + .ok (.Ok shared store, + [EvmYul.State.calldataload shared.toState offset]) := by + cases fuel <;> simp [EvmYul.Yul.primCall] + +@[simp] theorem primCall_shr_ok + (fuel : Nat) + (state : EvmYul.Yul.State) + (shift value : EvmYul.UInt256) : + EvmYul.Yul.primCall (fuel + 1) state + EvmYul.Operation.SHR [shift, value] = + .ok (state, [EvmYul.UInt256.shiftRight value shift]) := by + cases fuel <;> simp [EvmYul.Yul.primCall] + +@[simp] theorem primCall_eq_ok + (fuel : Nat) + (state : EvmYul.Yul.State) + (left right : EvmYul.UInt256) : + EvmYul.Yul.primCall (fuel + 1) state + EvmYul.Operation.EQ [left, right] = + .ok (state, [EvmYul.UInt256.eq left right]) := by + cases fuel <;> simp [EvmYul.Yul.primCall] + +@[simp] theorem primCall_iszero_ok + (fuel : Nat) + (state : EvmYul.Yul.State) + (value : EvmYul.UInt256) : + EvmYul.Yul.primCall (fuel + 1) state + EvmYul.Operation.ISZERO [value] = + .ok (state, [EvmYul.UInt256.isZero value]) := by + cases fuel <;> simp [EvmYul.Yul.primCall] + +@[simp] theorem primCall_and_ok + (fuel : Nat) + (state : EvmYul.Yul.State) + (left right : EvmYul.UInt256) : + EvmYul.Yul.primCall (fuel + 1) state + EvmYul.Operation.AND [left, right] = + .ok (state, [EvmYul.UInt256.land left right]) := by + cases fuel <;> simp [EvmYul.Yul.primCall] + +/-- Native evaluation of the lowered generated selector expression peels to + exactly EVMYulLean `calldataload(0)` followed by `shr(224, ...)`. -/ +theorem eval_lowerExprNative_selectorExpr_ok + (shared : EvmYul.SharedState .Yul) + (store : EvmYul.Yul.VarStore) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) : + EvmYul.Yul.eval 10 + (Backends.lowerExprNative Compiler.Proofs.YulGeneration.selectorExpr) + codeOverride (.Ok shared store) = + .ok (.Ok shared store, + EvmYul.UInt256.shiftRight + (EvmYul.State.calldataload shared.toState (EvmYul.UInt256.ofNat 0)) + (EvmYul.UInt256.ofNat Compiler.Constants.selectorShift)) := by + rw [lowerExprNative_selectorExpr] + simp [EvmYul.Yul.eval, EvmYul.Yul.evalArgs, EvmYul.Yul.evalTail, + EvmYul.Yul.evalPrimCall, EvmYul.Yul.reverse', EvmYul.Yul.cons', + EvmYul.Yul.head', Compiler.Constants.selectorShift] + +theorem eval_lowerExprNative_selectorExpr_initialState_ok + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) : + EvmYul.Yul.eval 10 + (Backends.lowerExprNative Compiler.Proofs.YulGeneration.selectorExpr) + (some contract) (.Ok (initialState contract tx storage observableSlots).sharedState ∅) = + .ok (.Ok (initialState contract tx storage observableSlots).sharedState ∅, + EvmYul.UInt256.ofNat + (tx.functionSelector % Compiler.Constants.selectorModulus)) := by + rw [eval_lowerExprNative_selectorExpr_ok] + have hv := + initialState_selectorExpr_native_uint256 contract tx storage observableSlots + rw [hv] + +theorem exec_let_lowerExprNative_selectorExpr_initialState_ok + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) + (discrName : EvmYul.Identifier) : + EvmYul.Yul.exec 11 + (.Let [discrName] + (some (Backends.lowerExprNative Compiler.Proofs.YulGeneration.selectorExpr))) + (some contract) (.Ok (initialState contract tx storage observableSlots).sharedState ∅) = + .ok ((.Ok (initialState contract tx storage observableSlots).sharedState ∅ : + EvmYul.Yul.State).insert discrName + (EvmYul.UInt256.ofNat + (tx.functionSelector % Compiler.Constants.selectorModulus))) := by + have hv := + initialState_selectorExpr_native_uint256 contract tx storage observableSlots + have hv224 : + EvmYul.UInt256.shiftRight + (EvmYul.State.calldataload + (EvmYul.SharedState.toState + (initialState contract tx storage observableSlots).sharedState) + (EvmYul.UInt256.ofNat 0)) + (EvmYul.UInt256.ofNat 224) = + EvmYul.UInt256.ofNat + (tx.functionSelector % Compiler.Constants.selectorModulus) := by + simpa [Compiler.Constants.selectorShift] using hv + rw [lowerExprNative_selectorExpr] + simp [EvmYul.Yul.exec, EvmYul.Yul.eval, EvmYul.Yul.evalArgs, + EvmYul.Yul.evalTail, EvmYul.Yul.evalPrimCall, EvmYul.Yul.execPrimCall, + EvmYul.Yul.reverse', EvmYul.Yul.cons', EvmYul.Yul.head', + EvmYul.Yul.multifill', EvmYul.Yul.State.multifill, + Compiler.Constants.selectorShift] + rw [hv224] + +theorem exec_let_lowerExprNative_selectorExpr_initialState_ok_fuel + (fuel : Nat) + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) + (discrName : EvmYul.Identifier) : + EvmYul.Yul.exec (fuel + 11) + (.Let [discrName] + (some (Backends.lowerExprNative Compiler.Proofs.YulGeneration.selectorExpr))) + (some contract) (.Ok (initialState contract tx storage observableSlots).sharedState ∅) = + .ok ((.Ok (initialState contract tx storage observableSlots).sharedState ∅ : + EvmYul.Yul.State).insert discrName + (EvmYul.UInt256.ofNat + (tx.functionSelector % Compiler.Constants.selectorModulus))) := by + have hv := + initialState_selectorExpr_native_uint256 contract tx storage observableSlots + have hv224 : + EvmYul.UInt256.shiftRight + (EvmYul.State.calldataload + (EvmYul.SharedState.toState + (initialState contract tx storage observableSlots).sharedState) + (EvmYul.UInt256.ofNat 0)) + (EvmYul.UInt256.ofNat 224) = + EvmYul.UInt256.ofNat + (tx.functionSelector % Compiler.Constants.selectorModulus) := by + simpa [Compiler.Constants.selectorShift] using hv + rw [lowerExprNative_selectorExpr] + simp [EvmYul.Yul.exec, EvmYul.Yul.eval, EvmYul.Yul.evalArgs, + EvmYul.Yul.evalTail, EvmYul.Yul.evalPrimCall, EvmYul.Yul.execPrimCall, + EvmYul.Yul.reverse', EvmYul.Yul.cons', EvmYul.Yul.head', + EvmYul.Yul.multifill', EvmYul.Yul.State.multifill, + Compiler.Constants.selectorShift] + rw [hv224] + +@[simp] theorem exec_let_lit_ok + (fuel' : Nat) + (name : EvmYul.Identifier) + (value : EvmYul.Literal) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state : EvmYul.Yul.State) : + EvmYul.Yul.exec (Nat.succ fuel') + (.Let [name] (some (.Lit value))) codeOverride state = + .ok (state.insert name value) := by + simp [EvmYul.Yul.exec] + +/-- If the head statement of a native block finishes normally, execution + continues with the remaining block statements at the same decremented fuel. -/ +theorem exec_block_cons_ok + (fuel' : Nat) + (stmt : EvmYul.Yul.Ast.Stmt) + (stmts : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state next final : EvmYul.Yul.State) + (hHead : EvmYul.Yul.exec fuel' stmt codeOverride state = .ok next) + (hTail : EvmYul.Yul.exec fuel' (.Block stmts) codeOverride next = .ok final) : + EvmYul.Yul.exec (Nat.succ fuel') (.Block (stmt :: stmts)) codeOverride state = + .ok final := by + simp [EvmYul.Yul.exec, hHead, hTail] + +/-- Execute an appended native block when the left block consumes exactly its + statement-count fuel prefix and the right block runs at the remaining fuel. + +This matches EVMYulLean's block interpreter: every cons step decrements the fuel +available to both the head statement and the tail block. -/ +theorem exec_block_append_ok + (fuel k : Nat) (left right : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) (state middle final : EvmYul.Yul.State) + (hLeft : EvmYul.Yul.exec (fuel + left.length + k) (.Block left) codeOverride state = .ok middle) + (hRight : EvmYul.Yul.exec (fuel + k) (.Block right) codeOverride middle = .ok final) : + EvmYul.Yul.exec (fuel + left.length + k) (.Block (left ++ right)) codeOverride state = .ok final := by + induction left generalizing fuel state with + | nil => + generalize hFuel : fuel + k = remaining at hLeft hRight ⊢ + cases remaining with + | zero => + have hLeft0 : EvmYul.Yul.exec 0 (.Block []) codeOverride state = .ok middle := by + simpa [hFuel] using hLeft + simp [EvmYul.Yul.exec] at hLeft0 + | succ remaining' => + have hLeftS : EvmYul.Yul.exec (Nat.succ remaining') (.Block []) + codeOverride state = .ok middle := by + simpa [hFuel] using hLeft + simp [EvmYul.Yul.exec] at hLeftS + cases hLeftS + simpa [hFuel] using hRight + | cons stmt rest ih => + have hFuel : fuel + (stmt :: rest).length + k = + fuel + rest.length + k + 1 := by + simp only [List.length_cons]; omega + have hLeft' : EvmYul.Yul.exec (Nat.succ (fuel + rest.length + k)) + (.Block (stmt :: rest)) codeOverride state = .ok middle := by + simpa [hFuel, Nat.add_assoc, Nat.add_comm, Nat.add_left_comm] using hLeft + simp only [EvmYul.Yul.exec] at hLeft' + generalize hHead : EvmYul.Yul.exec (fuel + rest.length + k) stmt codeOverride state = + head at hLeft' + cases head with + | error err => simp at hLeft' + | ok next => + simp at hLeft' + have hTail : EvmYul.Yul.exec (fuel + rest.length + k) (.Block rest) + codeOverride next = .ok middle := hLeft' + have hRest : EvmYul.Yul.exec (fuel + rest.length + k) + (.Block (rest ++ right)) codeOverride next = .ok final := + ih fuel next hTail hRight + have hBlock := exec_block_cons_ok (fuel + rest.length + k) + stmt (rest ++ right) codeOverride state next final hHead hRest + have hGoalFuel : fuel + rest.length + k + 1 = + fuel + (stmt :: rest).length + k := by + simp only [List.length_cons]; omega + simpa [hGoalFuel, Nat.add_assoc, Nat.add_comm, Nat.add_left_comm] + using hBlock + +theorem exec_block_nil_ok + (fuel' : Nat) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state : EvmYul.Yul.State) : + EvmYul.Yul.exec (Nat.succ fuel') (.Block []) codeOverride state = + .ok state := by + simp [EvmYul.Yul.exec] + +def nativeSwitchPrefixStmts + (discrName matchedName : EvmYul.Identifier) : + List EvmYul.Yul.Ast.Stmt := + [.Let [discrName] + (some (Backends.lowerExprNative Compiler.Proofs.YulGeneration.selectorExpr)), + .Let [matchedName] (some (.Lit (EvmYul.UInt256.ofNat 0)))] + +def nativeSwitchInitialOkState + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) : + EvmYul.Yul.State := + .Ok (initialState contract tx storage observableSlots).sharedState ∅ + +def nativeSwitchPrefixFinalState + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) + (discrName matchedName : EvmYul.Identifier) : + EvmYul.Yul.State := + ((nativeSwitchInitialOkState contract tx storage observableSlots).insert discrName + (EvmYul.UInt256.ofNat + (tx.functionSelector % Compiler.Constants.selectorModulus))).insert + matchedName (EvmYul.UInt256.ofNat 0) + +/-- The generated dispatcher switch prefix initializes the discriminator temp + from native calldata selector evaluation, then clears the lazy matched flag. + +This packages the first two statements emitted by `lowerNativeSwitchBlock` for +the generated dispatcher case and leaves the remaining case-chain proof with a +state whose native switch temporaries are aligned to the interpreter oracle. -/ +theorem exec_nativeSwitchPrefix_selector_initialState_ok + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) + (discrName matchedName : EvmYul.Identifier) : + EvmYul.Yul.exec 12 + (.Block (nativeSwitchPrefixStmts discrName matchedName)) + (some contract) (nativeSwitchInitialOkState contract tx storage observableSlots) = + .ok (nativeSwitchPrefixFinalState contract tx storage observableSlots + discrName matchedName) := by + let initState : EvmYul.Yul.State := + nativeSwitchInitialOkState contract tx storage observableSlots + let discrState : EvmYul.Yul.State := + initState.insert discrName + (EvmYul.UInt256.ofNat + (tx.functionSelector % Compiler.Constants.selectorModulus)) + let matchedState : EvmYul.Yul.State := + discrState.insert matchedName (EvmYul.UInt256.ofNat 0) + change EvmYul.Yul.exec 12 + (.Block (nativeSwitchPrefixStmts discrName matchedName)) + (some contract) initState = .ok matchedState + rw [nativeSwitchPrefixStmts] + apply exec_block_cons_ok 11 + (.Let [discrName] + (some (Backends.lowerExprNative Compiler.Proofs.YulGeneration.selectorExpr))) + [.Let [matchedName] (some (.Lit (EvmYul.UInt256.ofNat 0)))] + (some contract) initState discrState matchedState + · exact exec_let_lowerExprNative_selectorExpr_initialState_ok + contract tx storage observableSlots discrName + · apply exec_block_cons_ok 10 + (.Let [matchedName] (some (.Lit (EvmYul.UInt256.ofNat 0)))) + [] (some contract) discrState matchedState matchedState + · simp + simp [matchedState] + · simp [EvmYul.Yul.exec] + +theorem exec_nativeSwitchPrefix_selector_initialState_ok_fuel + (fuel : Nat) (contract : EvmYul.Yul.Ast.YulContract) (tx : YulTransaction) + (storage : Nat → Nat) (observableSlots : List Nat) + (discrName matchedName : EvmYul.Identifier) : + EvmYul.Yul.exec (fuel + 12) + (.Block (nativeSwitchPrefixStmts discrName matchedName)) + (some contract) (nativeSwitchInitialOkState contract tx storage observableSlots) = + .ok (nativeSwitchPrefixFinalState contract tx storage observableSlots + discrName matchedName) := by + let initState : EvmYul.Yul.State := + nativeSwitchInitialOkState contract tx storage observableSlots + let discrState : EvmYul.Yul.State := + initState.insert discrName + (EvmYul.UInt256.ofNat + (tx.functionSelector % Compiler.Constants.selectorModulus)) + let matchedState : EvmYul.Yul.State := + discrState.insert matchedName (EvmYul.UInt256.ofNat 0) + change EvmYul.Yul.exec (fuel + 12) + (.Block (nativeSwitchPrefixStmts discrName matchedName)) + (some contract) initState = .ok matchedState + have hFuel : fuel + 12 = Nat.succ (fuel + 11) := by omega + rw [hFuel] + rw [nativeSwitchPrefixStmts] + apply exec_block_cons_ok (fuel + 11) + (.Let [discrName] + (some (Backends.lowerExprNative Compiler.Proofs.YulGeneration.selectorExpr))) + [.Let [matchedName] (some (.Lit (EvmYul.UInt256.ofNat 0)))] + (some contract) initState discrState matchedState + · exact exec_let_lowerExprNative_selectorExpr_initialState_ok_fuel + fuel contract tx storage observableSlots discrName + · have hFuelTail : fuel + 11 = Nat.succ (fuel + 10) := by omega + rw [hFuelTail] + apply exec_block_cons_ok (fuel + 10) + (.Let [matchedName] (some (.Lit (EvmYul.UInt256.ofNat 0)))) + [] (some contract) discrState matchedState matchedState + · simp [matchedState] + · simp [EvmYul.Yul.exec] + +/-- Native `if` reduction for a zero guard. -/ +theorem exec_if_eval_zero + (fuel' : Nat) + (cond : EvmYul.Yul.Ast.Expr) + (body : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state next : EvmYul.Yul.State) + (hEval : + EvmYul.Yul.eval fuel' cond codeOverride state = + .ok (next, (⟨0⟩ : EvmYul.Literal))) : + EvmYul.Yul.exec (Nat.succ fuel') (.If cond body) codeOverride state = + .ok next := by + simp [EvmYul.Yul.exec, hEval] + +/-- Native `if` reduction for a nonzero guard. -/ +theorem exec_if_eval_nonzero + (fuel' : Nat) + (cond : EvmYul.Yul.Ast.Expr) + (body : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state next final : EvmYul.Yul.State) + (value : EvmYul.Literal) + (hEval : EvmYul.Yul.eval fuel' cond codeOverride state = .ok (next, value)) + (hNe : value ≠ (⟨0⟩ : EvmYul.Literal)) + (hBody : EvmYul.Yul.exec fuel' (.Block body) codeOverride next = .ok final) : + EvmYul.Yul.exec (Nat.succ fuel') (.If cond body) codeOverride state = + .ok final := by + simp [EvmYul.Yul.exec, hEval, hNe, hBody] + +/-- Native evaluation of the lazy lowered switch guard peels to the exact + EVMYulLean `AND(ISZERO(matched), EQ(discr, tag))` value. + +This is the next bridge after selector evaluation: selected-case execution can +now reason from concrete discriminator and matched-temporary bindings instead +of re-opening nested primitive-call evaluation. -/ +theorem eval_nativeSwitchGuardedMatch_ok + (state : EvmYul.Yul.State) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (discrName matchedName : EvmYul.Identifier) + (tag : Nat) : + EvmYul.Yul.eval 8 + (Backends.nativePrimCall (EvmYul.Operation.AND : EvmYul.Operation .Yul) + [Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName], + Backends.nativePrimCall (EvmYul.Operation.EQ : EvmYul.Operation .Yul) + [.Var discrName, .Lit (EvmYul.UInt256.ofNat tag)]]) + codeOverride state = + .ok (state, + EvmYul.UInt256.land + (EvmYul.UInt256.isZero state[matchedName]!) + (EvmYul.UInt256.eq state[discrName]! (EvmYul.UInt256.ofNat tag))) := by + simp [Backends.nativePrimCall, EvmYul.Yul.eval, EvmYul.Yul.evalArgs, + EvmYul.Yul.evalTail, EvmYul.Yul.evalPrimCall, EvmYul.Yul.reverse', + EvmYul.Yul.cons', EvmYul.Yul.head'] + +/-- Fuel-parametric form of `eval_nativeSwitchGuardedMatch_ok`, for use under + recursively executed generated switch blocks. -/ +theorem eval_nativeSwitchGuardedMatch_ok_fuel + (fuel : Nat) + (state : EvmYul.Yul.State) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (discrName matchedName : EvmYul.Identifier) + (tag : Nat) : + EvmYul.Yul.eval (fuel + 8) + (Backends.nativePrimCall (EvmYul.Operation.AND : EvmYul.Operation .Yul) + [Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName], + Backends.nativePrimCall (EvmYul.Operation.EQ : EvmYul.Operation .Yul) + [.Var discrName, .Lit (EvmYul.UInt256.ofNat tag)]]) + codeOverride state = + .ok (state, + EvmYul.UInt256.land + (EvmYul.UInt256.isZero state[matchedName]!) + (EvmYul.UInt256.eq state[discrName]! (EvmYul.UInt256.ofNat tag))) := by + cases fuel <;> + simp [Backends.nativePrimCall, EvmYul.Yul.eval, EvmYul.Yul.evalArgs, + EvmYul.Yul.evalTail, EvmYul.Yul.evalPrimCall, EvmYul.Yul.reverse', + EvmYul.Yul.cons', EvmYul.Yul.head'] + +/-- The selected lowered switch case has a nonzero guard while no previous case + has marked the switch matched and the discriminator equals the case tag. -/ +theorem eval_nativeSwitchGuardedMatch_hit_ok + (state : EvmYul.Yul.State) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (discrName matchedName : EvmYul.Identifier) + (tag : Nat) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 0) + (hDiscr : state[discrName]! = EvmYul.UInt256.ofNat tag) : + EvmYul.Yul.eval 8 + (Backends.nativePrimCall (EvmYul.Operation.AND : EvmYul.Operation .Yul) + [Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName], + Backends.nativePrimCall (EvmYul.Operation.EQ : EvmYul.Operation .Yul) + [.Var discrName, .Lit (EvmYul.UInt256.ofNat tag)]]) + codeOverride state = + .ok (state, EvmYul.UInt256.ofNat 1) := by + rw [eval_nativeSwitchGuardedMatch_ok, hMatched, hDiscr] + simp [EvmYul.UInt256.eq, EvmYul.UInt256.isZero] + decide + +/-- An unmatched lowered switch case has a zero guard while no previous case has + matched and the discriminator differs from the case tag. -/ +theorem eval_nativeSwitchGuardedMatch_miss_ok + (state : EvmYul.Yul.State) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (discrName matchedName : EvmYul.Identifier) + (tag : Nat) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 0) + (hDiscr : state[discrName]! ≠ EvmYul.UInt256.ofNat tag) : + EvmYul.Yul.eval 8 + (Backends.nativePrimCall (EvmYul.Operation.AND : EvmYul.Operation .Yul) + [Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName], + Backends.nativePrimCall (EvmYul.Operation.EQ : EvmYul.Operation .Yul) + [.Var discrName, .Lit (EvmYul.UInt256.ofNat tag)]]) + codeOverride state = + .ok (state, EvmYul.UInt256.ofNat 0) := by + rw [eval_nativeSwitchGuardedMatch_ok, hMatched] + simp [EvmYul.UInt256.eq, EvmYul.UInt256.isZero, hDiscr] + decide + +/-- Fuel-parametric non-selected lowered switch case guard reduction. -/ +theorem eval_nativeSwitchGuardedMatch_miss_ok_fuel + (fuel : Nat) + (state : EvmYul.Yul.State) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (discrName matchedName : EvmYul.Identifier) + (tag : Nat) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 0) + (hDiscr : state[discrName]! ≠ EvmYul.UInt256.ofNat tag) : + EvmYul.Yul.eval (fuel + 8) + (Backends.nativePrimCall (EvmYul.Operation.AND : EvmYul.Operation .Yul) + [Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName], + Backends.nativePrimCall (EvmYul.Operation.EQ : EvmYul.Operation .Yul) + [.Var discrName, .Lit (EvmYul.UInt256.ofNat tag)]]) + codeOverride state = + .ok (state, EvmYul.UInt256.ofNat 0) := by + rw [eval_nativeSwitchGuardedMatch_ok_fuel, hMatched] + simp [EvmYul.UInt256.eq, EvmYul.UInt256.isZero, hDiscr] + decide + +/-- Bitwise `and` with a zero left operand stays zero for native UInt256 words. -/ +private theorem uint256_land_zero_left (value : EvmYul.UInt256) : + EvmYul.UInt256.land (EvmYul.UInt256.ofNat 0) value = + EvmYul.UInt256.ofNat 0 := by + cases value with + | mk raw => + apply congrArg EvmYul.UInt256.mk + apply Fin.ext + change (0 &&& (raw : Nat)) % EvmYul.UInt256.size = 0 + simp [Nat.zero_and] + +/-- Once the lazy lowered switch matched flag is set, later case guards evaluate + to zero independently of the discriminator value. -/ +theorem eval_nativeSwitchGuardedMatch_matched_ok + (state : EvmYul.Yul.State) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (discrName matchedName : EvmYul.Identifier) + (tag : Nat) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 1) : + EvmYul.Yul.eval 8 + (Backends.nativePrimCall (EvmYul.Operation.AND : EvmYul.Operation .Yul) + [Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName], + Backends.nativePrimCall (EvmYul.Operation.EQ : EvmYul.Operation .Yul) + [.Var discrName, .Lit (EvmYul.UInt256.ofNat tag)]]) + codeOverride state = + .ok (state, EvmYul.UInt256.ofNat 0) := by + rw [eval_nativeSwitchGuardedMatch_ok, hMatched] + rw [show EvmYul.UInt256.isZero (EvmYul.UInt256.ofNat 1) = + EvmYul.UInt256.ofNat 0 by decide] + rw [uint256_land_zero_left] + +/-- Fuel-parametric form of `eval_nativeSwitchGuardedMatch_matched_ok`. -/ +theorem eval_nativeSwitchGuardedMatch_matched_ok_fuel + (fuel : Nat) + (state : EvmYul.Yul.State) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (discrName matchedName : EvmYul.Identifier) + (tag : Nat) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 1) : + EvmYul.Yul.eval (fuel + 8) + (Backends.nativePrimCall (EvmYul.Operation.AND : EvmYul.Operation .Yul) + [Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName], + Backends.nativePrimCall (EvmYul.Operation.EQ : EvmYul.Operation .Yul) + [.Var discrName, .Lit (EvmYul.UInt256.ofNat tag)]]) + codeOverride state = + .ok (state, EvmYul.UInt256.ofNat 0) := by + rw [eval_nativeSwitchGuardedMatch_ok_fuel, hMatched] + rw [show EvmYul.UInt256.isZero (EvmYul.UInt256.ofNat 1) = + EvmYul.UInt256.ofNat 0 by decide] + rw [uint256_land_zero_left] + +/-- Native `if` execution for the selected lowered switch case. This packages + guard evaluation with the existing nonzero-`if` reduction so the remaining + case-chain proof can focus on matching/freshness invariants. -/ +theorem exec_if_nativeSwitchGuardedMatch_hit + (body : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state final : EvmYul.Yul.State) + (discrName matchedName : EvmYul.Identifier) + (tag : Nat) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 0) + (hDiscr : state[discrName]! = EvmYul.UInt256.ofNat tag) + (hBody : EvmYul.Yul.exec 8 (.Block body) codeOverride state = .ok final) : + EvmYul.Yul.exec 9 + (.If + (Backends.nativePrimCall (EvmYul.Operation.AND : EvmYul.Operation .Yul) + [Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName], + Backends.nativePrimCall (EvmYul.Operation.EQ : EvmYul.Operation .Yul) + [.Var discrName, .Lit (EvmYul.UInt256.ofNat tag)]]) + body) + codeOverride state = .ok final := by + exact exec_if_eval_nonzero 8 _ body codeOverride state state final + (EvmYul.UInt256.ofNat 1) + (eval_nativeSwitchGuardedMatch_hit_ok state codeOverride discrName matchedName tag + hMatched hDiscr) + (by decide) + hBody + +/-- Native `if` execution skips a non-selected lowered switch case while no + previous case has matched. -/ +theorem exec_if_nativeSwitchGuardedMatch_miss + (body : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state : EvmYul.Yul.State) + (discrName matchedName : EvmYul.Identifier) + (tag : Nat) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 0) + (hDiscr : state[discrName]! ≠ EvmYul.UInt256.ofNat tag) : + EvmYul.Yul.exec 9 + (.If + (Backends.nativePrimCall (EvmYul.Operation.AND : EvmYul.Operation .Yul) + [Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName], + Backends.nativePrimCall (EvmYul.Operation.EQ : EvmYul.Operation .Yul) + [.Var discrName, .Lit (EvmYul.UInt256.ofNat tag)]]) + body) + codeOverride state = .ok state := by + exact exec_if_eval_zero 8 _ body codeOverride state state + (eval_nativeSwitchGuardedMatch_miss_ok state codeOverride discrName matchedName tag + hMatched hDiscr) + +/-- Fuel-parametric native `if` skip for a non-selected lowered switch case. -/ +theorem exec_if_nativeSwitchGuardedMatch_miss_fuel + (fuel : Nat) + (body : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state : EvmYul.Yul.State) + (discrName matchedName : EvmYul.Identifier) + (tag : Nat) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 0) + (hDiscr : state[discrName]! ≠ EvmYul.UInt256.ofNat tag) : + EvmYul.Yul.exec (fuel + 9) + (.If + (Backends.nativePrimCall (EvmYul.Operation.AND : EvmYul.Operation .Yul) + [Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName], + Backends.nativePrimCall (EvmYul.Operation.EQ : EvmYul.Operation .Yul) + [.Var discrName, .Lit (EvmYul.UInt256.ofNat tag)]]) + body) + codeOverride state = .ok state := by + simpa using + (exec_if_eval_zero (fuel + 8) _ body codeOverride state state + (eval_nativeSwitchGuardedMatch_miss_ok_fuel fuel state codeOverride + discrName matchedName tag hMatched hDiscr)) + +@[simp] theorem exec_lowerAssignNative_lit_ok + (fuel' : Nat) + (name : EvmYul.Identifier) + (value : Nat) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state : EvmYul.Yul.State) : + EvmYul.Yul.exec (Nat.succ fuel') + (Backends.lowerAssignNative name (.lit value)) codeOverride state = + .ok (state.insert name (EvmYul.UInt256.ofNat value)) := by + simp [Backends.lowerAssignNative, Backends.lowerExprNative] + +/-- Native `if` execution for the selected lowered switch case, including the + leading matched-flag assignment inserted by `lowerNativeSwitchBlock`. + +This is the selected-case execution boundary the full lowered-switch proof can +reuse: after the guard hits, the selected body runs in the same state except +for `matchedName := 1`. -/ +theorem exec_if_nativeSwitchGuardedMatch_hit_marked + (body : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state final : EvmYul.Yul.State) + (discrName matchedName : EvmYul.Identifier) + (tag : Nat) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 0) + (hDiscr : state[discrName]! = EvmYul.UInt256.ofNat tag) + (hBody : + EvmYul.Yul.exec 7 (.Block body) codeOverride + (state.insert matchedName (EvmYul.UInt256.ofNat 1)) = .ok final) : + EvmYul.Yul.exec 9 + (.If + (Backends.nativePrimCall (EvmYul.Operation.AND : EvmYul.Operation .Yul) + [Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName], + Backends.nativePrimCall (EvmYul.Operation.EQ : EvmYul.Operation .Yul) + [.Var discrName, .Lit (EvmYul.UInt256.ofNat tag)]]) + (Backends.lowerAssignNative matchedName (.lit 1) :: body)) + codeOverride state = .ok final := by + apply exec_if_nativeSwitchGuardedMatch_hit + (body := Backends.lowerAssignNative matchedName (.lit 1) :: body) + (codeOverride := codeOverride) (state := state) (final := final) + (discrName := discrName) (matchedName := matchedName) (tag := tag) + hMatched hDiscr + exact exec_block_cons_ok 7 (Backends.lowerAssignNative matchedName (.lit 1)) + body codeOverride state (state.insert matchedName (EvmYul.UInt256.ofNat 1)) + final (by simp) hBody + +/-- Fuel-parametric selected-case guard reduction. -/ +theorem eval_nativeSwitchGuardedMatch_hit_ok_fuel + (fuel : Nat) + (state : EvmYul.Yul.State) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (discrName matchedName : EvmYul.Identifier) + (tag : Nat) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 0) + (hDiscr : state[discrName]! = EvmYul.UInt256.ofNat tag) : + EvmYul.Yul.eval (fuel + 8) + (Backends.nativePrimCall (EvmYul.Operation.AND : EvmYul.Operation .Yul) + [Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName], + Backends.nativePrimCall (EvmYul.Operation.EQ : EvmYul.Operation .Yul) + [.Var discrName, .Lit (EvmYul.UInt256.ofNat tag)]]) + codeOverride state = + .ok (state, EvmYul.UInt256.ofNat 1) := by + rw [eval_nativeSwitchGuardedMatch_ok_fuel, hMatched, hDiscr] + simp [EvmYul.UInt256.eq, EvmYul.UInt256.isZero] + decide + +/-- Fuel-parametric selected-case execution, including the matched-flag + assignment inserted at the start of each lowered native switch case. -/ +theorem exec_if_nativeSwitchGuardedMatch_hit_marked_fuel + (fuel : Nat) + (body : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state final : EvmYul.Yul.State) + (discrName matchedName : EvmYul.Identifier) + (tag : Nat) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 0) + (hDiscr : state[discrName]! = EvmYul.UInt256.ofNat tag) + (hBody : + EvmYul.Yul.exec (fuel + 7) (.Block body) codeOverride + (state.insert matchedName (EvmYul.UInt256.ofNat 1)) = .ok final) : + EvmYul.Yul.exec (fuel + 9) + (.If + (Backends.nativePrimCall (EvmYul.Operation.AND : EvmYul.Operation .Yul) + [Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName], + Backends.nativePrimCall (EvmYul.Operation.EQ : EvmYul.Operation .Yul) + [.Var discrName, .Lit (EvmYul.UInt256.ofNat tag)]]) + (Backends.lowerAssignNative matchedName (.lit 1) :: body)) + codeOverride state = .ok final := by + apply exec_if_eval_nonzero (fuel + 8) _ _ codeOverride state state final + (EvmYul.UInt256.ofNat 1) + · exact eval_nativeSwitchGuardedMatch_hit_ok_fuel fuel state codeOverride discrName matchedName tag + hMatched hDiscr + · decide + · exact exec_block_cons_ok (fuel + 7) (Backends.lowerAssignNative matchedName (.lit 1)) + body codeOverride state (state.insert matchedName (EvmYul.UInt256.ofNat 1)) + final (by simp) hBody + +/-- Native `if` execution skips a later lowered switch case after an earlier case + has set the matched flag. -/ +theorem exec_if_nativeSwitchGuardedMatch_matched + (body : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state : EvmYul.Yul.State) + (discrName matchedName : EvmYul.Identifier) + (tag : Nat) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 1) : + EvmYul.Yul.exec 9 + (.If + (Backends.nativePrimCall (EvmYul.Operation.AND : EvmYul.Operation .Yul) + [Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName], + Backends.nativePrimCall (EvmYul.Operation.EQ : EvmYul.Operation .Yul) + [.Var discrName, .Lit (EvmYul.UInt256.ofNat tag)]]) + body) + codeOverride state = .ok state := by + exact exec_if_eval_zero 8 _ body codeOverride state state + (eval_nativeSwitchGuardedMatch_matched_ok state codeOverride discrName matchedName tag + hMatched) + +/-- Fuel-parametric native `if` skip for a later lowered switch case after an + earlier case has set the matched flag. -/ +theorem exec_if_nativeSwitchGuardedMatch_matched_fuel + (fuel : Nat) + (body : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state : EvmYul.Yul.State) + (discrName matchedName : EvmYul.Identifier) + (tag : Nat) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 1) : + EvmYul.Yul.exec (fuel + 9) + (.If + (Backends.nativePrimCall (EvmYul.Operation.AND : EvmYul.Operation .Yul) + [Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName], + Backends.nativePrimCall (EvmYul.Operation.EQ : EvmYul.Operation .Yul) + [.Var discrName, .Lit (EvmYul.UInt256.ofNat tag)]]) + body) + codeOverride state = .ok state := by + simpa using + (exec_if_eval_zero (fuel + 8) _ body codeOverride state state + (eval_nativeSwitchGuardedMatch_matched_ok_fuel fuel state codeOverride + discrName matchedName tag hMatched)) + +/-- Native evaluation of the lazy lowered switch default guard peels to + `ISZERO(matched)`. -/ +theorem eval_nativeSwitchDefaultGuard_ok + (state : EvmYul.Yul.State) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (matchedName : EvmYul.Identifier) : + EvmYul.Yul.eval 6 + (Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName]) + codeOverride state = + .ok (state, EvmYul.UInt256.isZero state[matchedName]!) := by + simp [Backends.nativePrimCall, EvmYul.Yul.eval, EvmYul.Yul.evalArgs, + EvmYul.Yul.evalTail, EvmYul.Yul.evalPrimCall, EvmYul.Yul.reverse', + EvmYul.Yul.cons', EvmYul.Yul.head'] + +/-- Fuel-parametric form of `eval_nativeSwitchDefaultGuard_ok`, for use under + recursively executed generated switch blocks. -/ +theorem eval_nativeSwitchDefaultGuard_ok_fuel + (fuel : Nat) + (state : EvmYul.Yul.State) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (matchedName : EvmYul.Identifier) : + EvmYul.Yul.eval (fuel + 6) + (Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName]) + codeOverride state = + .ok (state, EvmYul.UInt256.isZero state[matchedName]!) := by + cases fuel <;> + simp [Backends.nativePrimCall, EvmYul.Yul.eval, EvmYul.Yul.evalArgs, + EvmYul.Yul.evalTail, EvmYul.Yul.evalPrimCall, EvmYul.Yul.reverse', + EvmYul.Yul.cons', EvmYul.Yul.head'] + +/-- If no lowered switch case has matched, the default guard is nonzero. -/ +theorem eval_nativeSwitchDefaultGuard_unmatched_ok + (state : EvmYul.Yul.State) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (matchedName : EvmYul.Identifier) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 0) : + EvmYul.Yul.eval 6 + (Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName]) + codeOverride state = + .ok (state, EvmYul.UInt256.ofNat 1) := by + rw [eval_nativeSwitchDefaultGuard_ok, hMatched] + simp [EvmYul.UInt256.isZero] + decide + +/-- Fuel-parametric default guard reduction when no case has matched. -/ +theorem eval_nativeSwitchDefaultGuard_unmatched_ok_fuel + (fuel : Nat) + (state : EvmYul.Yul.State) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (matchedName : EvmYul.Identifier) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 0) : + EvmYul.Yul.eval (fuel + 6) + (Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName]) + codeOverride state = + .ok (state, EvmYul.UInt256.ofNat 1) := by + rw [eval_nativeSwitchDefaultGuard_ok_fuel, hMatched] + simp [EvmYul.UInt256.isZero] + decide + +/-- If a lowered switch case has matched, the default guard is zero. -/ +theorem eval_nativeSwitchDefaultGuard_matched_ok + (state : EvmYul.Yul.State) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (matchedName : EvmYul.Identifier) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 1) : + EvmYul.Yul.eval 6 + (Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName]) + codeOverride state = + .ok (state, EvmYul.UInt256.ofNat 0) := by + rw [eval_nativeSwitchDefaultGuard_ok, hMatched] + simp [EvmYul.UInt256.isZero] + decide + +/-- Fuel-parametric default guard reduction after a case has matched. -/ +theorem eval_nativeSwitchDefaultGuard_matched_ok_fuel + (fuel : Nat) + (state : EvmYul.Yul.State) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (matchedName : EvmYul.Identifier) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 1) : + EvmYul.Yul.eval (fuel + 6) + (Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName]) + codeOverride state = + .ok (state, EvmYul.UInt256.ofNat 0) := by + rw [eval_nativeSwitchDefaultGuard_ok_fuel, hMatched] + simp [EvmYul.UInt256.isZero] + decide + +/-- Native `if` execution for the lowered switch default when no case matched. -/ +theorem exec_if_nativeSwitchDefaultGuard_unmatched + (body : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state final : EvmYul.Yul.State) + (matchedName : EvmYul.Identifier) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 0) + (hBody : EvmYul.Yul.exec 6 (.Block body) codeOverride state = .ok final) : + EvmYul.Yul.exec 7 + (.If + (Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName]) + body) + codeOverride state = .ok final := by + exact exec_if_eval_nonzero 6 _ body codeOverride state state final + (EvmYul.UInt256.ofNat 1) + (eval_nativeSwitchDefaultGuard_unmatched_ok state codeOverride matchedName hMatched) + (by decide) + hBody + +/-- Fuel-parametric default execution when no lowered switch case matched. -/ +theorem exec_if_nativeSwitchDefaultGuard_unmatched_fuel + (fuel : Nat) + (body : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state final : EvmYul.Yul.State) + (matchedName : EvmYul.Identifier) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 0) + (hBody : EvmYul.Yul.exec (fuel + 6) (.Block body) codeOverride state = .ok final) : + EvmYul.Yul.exec (fuel + 7) + (.If + (Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName]) + body) + codeOverride state = .ok final := by + simpa using + (exec_if_eval_nonzero (fuel + 6) _ body codeOverride state state final + (EvmYul.UInt256.ofNat 1) + (eval_nativeSwitchDefaultGuard_unmatched_ok_fuel fuel state codeOverride + matchedName hMatched) + (by decide) + hBody) + +/-- Native `if` execution skips the lowered switch default after a case matched. -/ +theorem exec_if_nativeSwitchDefaultGuard_matched + (body : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state : EvmYul.Yul.State) + (matchedName : EvmYul.Identifier) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 1) : + EvmYul.Yul.exec 7 + (.If + (Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName]) + body) + codeOverride state = .ok state := by + exact exec_if_eval_zero 6 _ body codeOverride state state + (eval_nativeSwitchDefaultGuard_matched_ok state codeOverride matchedName hMatched) + +/-- Fuel-parametric default skip after a lowered switch case matched. -/ +theorem exec_if_nativeSwitchDefaultGuard_matched_fuel + (fuel : Nat) + (body : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state : EvmYul.Yul.State) + (matchedName : EvmYul.Identifier) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 1) : + EvmYul.Yul.exec (fuel + 7) + (.If + (Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName]) + body) + codeOverride state = .ok state := by + simpa using + (exec_if_eval_zero (fuel + 6) _ body codeOverride state state + (eval_nativeSwitchDefaultGuard_matched_ok_fuel fuel state codeOverride + matchedName hMatched)) + +def nativeSwitchGuardedMatchExpr + (discrName matchedName : EvmYul.Identifier) + (tag : Nat) : + EvmYul.Yul.Ast.Expr := + Backends.nativePrimCall (EvmYul.Operation.AND : EvmYul.Operation .Yul) + [Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName], + Backends.nativePrimCall (EvmYul.Operation.EQ : EvmYul.Operation .Yul) + [.Var discrName, .Lit (EvmYul.UInt256.ofNat tag)]] + +def nativeSwitchDefaultGuardExpr + (matchedName : EvmYul.Identifier) : + EvmYul.Yul.Ast.Expr := + Backends.nativePrimCall (EvmYul.Operation.ISZERO : EvmYul.Operation .Yul) + [.Var matchedName] + +def nativeSwitchCaseIf + (discrName matchedName : EvmYul.Identifier) + (entry : Nat × List EvmYul.Yul.Ast.Stmt) : + EvmYul.Yul.Ast.Stmt := + .If (nativeSwitchGuardedMatchExpr discrName matchedName entry.1) + (Backends.lowerAssignNative matchedName (.lit 1) :: entry.2) + +def nativeSwitchCaseIfs + (discrName matchedName : EvmYul.Identifier) + (cases : List (Nat × List EvmYul.Yul.Ast.Stmt)) : + List EvmYul.Yul.Ast.Stmt := + cases.map (nativeSwitchCaseIf discrName matchedName) + +def nativeSwitchDefaultIf + (matchedName : EvmYul.Identifier) + (defaultBody : List EvmYul.Yul.Ast.Stmt) : + List EvmYul.Yul.Ast.Stmt := + if defaultBody.isEmpty then [] + else [.If (nativeSwitchDefaultGuardExpr matchedName) defaultBody] + +def nativeSwitchTailStmts + (switchId : Nat) + (cases : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (defaultBody : List EvmYul.Yul.Ast.Stmt) : + List EvmYul.Yul.Ast.Stmt := + nativeSwitchCaseIfs (Backends.nativeSwitchDiscrTempName switchId) + (Backends.nativeSwitchMatchedTempName switchId) cases ++ + nativeSwitchDefaultIf (Backends.nativeSwitchMatchedTempName switchId) + defaultBody + +theorem lowerNativeSwitchBlock_selectorExpr_eq_nativeSwitchParts + (switchId : Nat) + (cases : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (defaultBody : List EvmYul.Yul.Ast.Stmt) : + Backends.lowerNativeSwitchBlock Compiler.Proofs.YulGeneration.selectorExpr + switchId cases defaultBody = + .Block + (nativeSwitchPrefixStmts (Backends.nativeSwitchDiscrTempName switchId) + (Backends.nativeSwitchMatchedTempName switchId) ++ + nativeSwitchTailStmts switchId cases defaultBody) := by + simp [Backends.lowerNativeSwitchBlock, nativeSwitchPrefixStmts, + nativeSwitchCaseIfs, nativeSwitchCaseIf, nativeSwitchGuardedMatchExpr, + nativeSwitchTailStmts, nativeSwitchDefaultIf, nativeSwitchDefaultGuardExpr] + +def NativeBlockPreservesWord + (name : EvmYul.Identifier) + (value : EvmYul.Literal) + (body : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) : Prop := + ∀ fuel state final, + state[name]! = value → + EvmYul.Yul.exec fuel (.Block body) codeOverride state = .ok final → + final[name]! = value + +def NativeStmtPreservesWord + (name : EvmYul.Identifier) + (value : EvmYul.Literal) + (stmt : EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) : Prop := + ∀ fuel state final, + state[name]! = value → + EvmYul.Yul.exec fuel stmt codeOverride state = .ok final → + final[name]! = value + +theorem state_lookup_insert_of_ne + (state : EvmYul.Yul.State) + (name other : EvmYul.Identifier) + (value : EvmYul.Literal) + (hne : name ≠ other) : + EvmYul.Yul.State.lookup! name (state.insert other value) = + EvmYul.Yul.State.lookup! name state := by + cases state with + | Ok shared store => + simp [EvmYul.Yul.State.insert, EvmYul.Yul.State.lookup!] + rw [Finmap.lookup_insert_of_ne store hne] + | OutOfFuel => simp [EvmYul.Yul.State.insert] + | Checkpoint jump => simp [EvmYul.Yul.State.insert] + +theorem state_getElem_insert_of_ne + (state : EvmYul.Yul.State) + (name other : EvmYul.Identifier) + (value : EvmYul.Literal) + (hne : name ≠ other) : + (state.insert other value)[name]! = state[name]! := by + cases state with + | Ok shared store => + simp [EvmYul.Yul.State.insert, EvmYul.Yul.State.lookup!, + EvmYul.Yul.State.store, GetElem?.getElem!, decidableGetElem?, + GetElem.getElem] + by_cases hmem : name ∈ store + · simp [hmem, hne, Finmap.lookup_insert_of_ne store hne] + · simp [hmem, hne] + | OutOfFuel => + simp [EvmYul.Yul.State.insert] + | Checkpoint jump => + simp [EvmYul.Yul.State.insert] + +theorem nativeSwitchDiscrTempName_ne_matchedTempName + (switchId : Nat) : + Backends.nativeSwitchDiscrTempName switchId ≠ + Backends.nativeSwitchMatchedTempName switchId := by + intro h + have hlen := congrArg String.length h + have hd : + (toString "__verity_native_switch_discr_").length = 29 := by + decide + have hm : + (toString "__verity_native_switch_matched_").length = 31 := by + decide + simp [Backends.nativeSwitchDiscrTempName, + Backends.nativeSwitchMatchedTempName, hd, hm] at hlen + +theorem nativeSwitchPrefixFinalState_matched + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) + (discrName matchedName : EvmYul.Identifier) : + (nativeSwitchPrefixFinalState contract tx storage observableSlots + discrName matchedName)[matchedName]! = EvmYul.UInt256.ofNat 0 := by + simp [nativeSwitchPrefixFinalState, nativeSwitchInitialOkState, + EvmYul.Yul.State.insert, GetElem?.getElem!, decidableGetElem?, + GetElem.getElem, EvmYul.Yul.State.store, EvmYul.Yul.State.lookup!] + +theorem nativeSwitchPrefixFinalState_discr + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) + (discrName matchedName : EvmYul.Identifier) + (selector : Nat) + (hne : discrName ≠ matchedName) + (hSelector : + selector = tx.functionSelector % Compiler.Constants.selectorModulus) : + (nativeSwitchPrefixFinalState contract tx storage observableSlots discrName matchedName)[discrName]! = + EvmYul.UInt256.ofNat selector := by + rw [hSelector] + simp [nativeSwitchPrefixFinalState, nativeSwitchInitialOkState, + EvmYul.Yul.State.insert, GetElem?.getElem!, decidableGetElem?, + GetElem.getElem, EvmYul.Yul.State.store, EvmYul.Yul.State.lookup!] + rw [Finmap.lookup_insert_of_ne] + · rw [Finmap.lookup_insert] + simp + · exact hne + +theorem nativeSwitchPrefixFinalState_marked + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) + (discrName matchedName : EvmYul.Identifier) : + ((nativeSwitchPrefixFinalState contract tx storage observableSlots discrName matchedName).insert matchedName (EvmYul.UInt256.ofNat 1))[matchedName]! = + EvmYul.UInt256.ofNat 1 := by + simp [nativeSwitchPrefixFinalState, nativeSwitchInitialOkState, + EvmYul.Yul.State.insert, GetElem?.getElem!, decidableGetElem?, + GetElem.getElem, EvmYul.Yul.State.store, EvmYul.Yul.State.lookup!] + +def nativeSwitchPrefixStateForId + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) + (switchId : Nat) : + EvmYul.Yul.State := + nativeSwitchPrefixFinalState contract tx storage observableSlots + (Backends.nativeSwitchDiscrTempName switchId) + (Backends.nativeSwitchMatchedTempName switchId) + +def nativeSwitchMarkedPrefixStateForId + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) + (switchId : Nat) : + EvmYul.Yul.State := + (nativeSwitchPrefixStateForId contract tx storage observableSlots switchId).insert + (Backends.nativeSwitchMatchedTempName switchId) (EvmYul.UInt256.ofNat 1) + +theorem NativeBlockPreservesWord_nil + (name : EvmYul.Identifier) + (value : EvmYul.Literal) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) : + NativeBlockPreservesWord name value [] codeOverride := by + intro fuel state final hLookup hExec + cases fuel with + | zero => + simp [EvmYul.Yul.exec] at hExec + | succ fuel' => + simp [EvmYul.Yul.exec] at hExec + cases hExec + exact hLookup + +theorem NativeBlockPreservesWord_cons + (name : EvmYul.Identifier) + (value : EvmYul.Literal) + (stmt : EvmYul.Yul.Ast.Stmt) + (rest : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (hHead : + ∀ fuel state next, + state[name]! = value → + EvmYul.Yul.exec fuel stmt codeOverride state = .ok next → + next[name]! = value) + (hRest : NativeBlockPreservesWord name value rest codeOverride) : + NativeBlockPreservesWord name value (stmt :: rest) codeOverride := by + intro fuel state final hLookup hExec + cases fuel with + | zero => + simp [EvmYul.Yul.exec] at hExec + | succ fuel' => + simp [EvmYul.Yul.exec] at hExec + cases hStmt : EvmYul.Yul.exec fuel' stmt codeOverride state with + | error err => + simp [hStmt] at hExec + | ok next => + simp [hStmt] at hExec + have hNext : next[name]! = value := + hHead fuel' state next hLookup hStmt + exact hRest fuel' next final hNext hExec + +theorem NativeBlockPreservesWord_cons_stmt + (name : EvmYul.Identifier) + (value : EvmYul.Literal) + (stmt : EvmYul.Yul.Ast.Stmt) + (rest : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (hHead : NativeStmtPreservesWord name value stmt codeOverride) + (hRest : NativeBlockPreservesWord name value rest codeOverride) : + NativeBlockPreservesWord name value (stmt :: rest) codeOverride := + NativeBlockPreservesWord_cons name value stmt rest codeOverride hHead hRest + +theorem NativeStmtPreservesWord_lowerAssignNative_lit_of_ne + (name target : EvmYul.Identifier) + (expected : EvmYul.Literal) + (assigned : Nat) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (hne : name ≠ target) : + NativeStmtPreservesWord name expected + (Backends.lowerAssignNative target (.lit assigned)) codeOverride := by + intro fuel state final hLookup hExec + cases fuel with + | zero => + simp [EvmYul.Yul.exec] at hExec + | succ fuel' => + simp [Backends.lowerAssignNative, Backends.lowerExprNative] at hExec + cases hExec + rw [state_getElem_insert_of_ne state name target + (EvmYul.UInt256.ofNat assigned) hne] + exact hLookup + +theorem nativeSwitchTempsFreshForNativeBodies_case_matched_not_mem + (switchId tag : Nat) + (body defaultBody : List EvmYul.Yul.Ast.Stmt) + (cases : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (hFresh : + Backends.nativeSwitchTempsFreshForNativeBodies switchId cases defaultBody) + (hMem : (tag, body) ∈ cases) : + Backends.nativeSwitchMatchedTempName switchId ∉ + Backends.nativeStmtsWriteNames body := + (hFresh.1 tag body hMem).2 + +theorem nativeSwitchTempsFreshForNativeBodies_find_hit_matched_not_mem + (switchId selector tag : Nat) + (body defaultBody : List EvmYul.Yul.Ast.Stmt) + (cases : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (hFresh : + Backends.nativeSwitchTempsFreshForNativeBodies switchId cases defaultBody) + (hFind : cases.find? (fun entry => entry.1 == selector) = + some (tag, body)) : + Backends.nativeSwitchMatchedTempName switchId ∉ + Backends.nativeStmtsWriteNames body := by + have hMem : (tag, body) ∈ cases := by + clear hFresh + induction cases with + | nil => + simp [List.find?] at hFind + | cons head rest ih => + cases hHead : (head.1 == selector) + · simp [List.find?, hHead] at hFind + exact List.mem_cons_of_mem head (ih hFind) + · simp [List.find?, hHead] at hFind + simp [hFind] + exact nativeSwitchTempsFreshForNativeBodies_case_matched_not_mem + switchId tag body defaultBody cases hFresh hMem + +theorem nativeSwitchTempsFreshForNativeBodies_default_matched_not_mem + (switchId : Nat) + (cases : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (defaultBody : List EvmYul.Yul.Ast.Stmt) + (hFresh : + Backends.nativeSwitchTempsFreshForNativeBodies switchId cases defaultBody) : + Backends.nativeSwitchMatchedTempName switchId ∉ + Backends.nativeStmtsWriteNames defaultBody := + hFresh.2.2 + +@[simp] theorem nativeSwitchCaseIfs_nil + (discrName matchedName : EvmYul.Identifier) : + nativeSwitchCaseIfs discrName matchedName [] = [] := by + rfl + +@[simp] theorem nativeSwitchCaseIfs_cons + (discrName matchedName : EvmYul.Identifier) + (entry : Nat × List EvmYul.Yul.Ast.Stmt) + (rest : List (Nat × List EvmYul.Yul.Ast.Stmt)) : + nativeSwitchCaseIfs discrName matchedName (entry :: rest) = + nativeSwitchCaseIf discrName matchedName entry :: + nativeSwitchCaseIfs discrName matchedName rest := by + rfl + +private theorem list_find?_eq_some_split_false + {α : Type} + (p : α → Bool) : + ∀ {xs : List α} {x : α}, + xs.find? p = some x → + ∃ pre suffix, + xs = pre ++ x :: suffix ∧ + ∀ y, y ∈ pre → p y = false + | [], _, hFind => by + simp [List.find?] at hFind + | y :: ys, x, hFind => by + by_cases hp : p y = true + · have hxy : x = y := by + simpa [List.find?, hp] using hFind.symm + subst x + exact ⟨[], ys, by simp, by simp⟩ + · have hFalse : p y = false := Bool.eq_false_iff.2 hp + have hRest : ys.find? p = some x := by + simpa [List.find?, hFalse] using hFind + rcases list_find?_eq_some_split_false p hRest with + ⟨pre, suffix, hSplit, hPre⟩ + refine ⟨y :: pre, suffix, ?_, ?_⟩ + · simp [hSplit] + · intro z hz + have hz' : z = y ∨ z ∈ pre := by + simpa [List.mem_cons] using hz + rcases hz' with hzy | hzPre + · cases hzy + exact hFalse + · exact hPre z hzPre + +private theorem list_find?_eq_none_all_false + {α : Type} + (p : α → Bool) : + ∀ {xs : List α}, + xs.find? p = none → + ∀ x, x ∈ xs → p x = false + | [], hFind, x, hx => by + simp at hx + | y :: ys, hFind, x, hx => by + by_cases hp : p y = true + · simp [List.find?, hp] at hFind + · have hFalse : p y = false := Bool.eq_false_iff.2 hp + have hRest : ys.find? p = none := by + simpa [List.find?, hFalse] using hFind + have hx' : x = y ∨ x ∈ ys := by + simpa [List.mem_cons] using hx + rcases hx' with hxy | hxTail + · cases hxy + exact hFalse + · exact list_find?_eq_none_all_false p hRest x hxTail + +private theorem uint256_ofNat_ne_of_ne_of_lt + {a b : Nat} + (ha : a < EvmYul.UInt256.size) + (hb : b < EvmYul.UInt256.size) + (hne : a ≠ b) : + EvmYul.UInt256.ofNat a ≠ EvmYul.UInt256.ofNat b := by + intro h + apply hne + have hToNat := congrArg EvmYul.UInt256.toNat h + rw [uint256_ofNat_toNat_of_lt a ha, + uint256_ofNat_toNat_of_lt b hb] at hToNat + exact hToNat + +private theorem nativeSwitch_prefix_miss_of_selector_find + (selector : Nat) + (cases pre suffix : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (tag : Nat) + (body : List EvmYul.Yul.Ast.Stmt) + (state : EvmYul.Yul.State) + (discrName : EvmYul.Identifier) + (hCases : cases = pre ++ (tag, body) :: suffix) + (hPrefix : + ∀ entry, entry ∈ pre → (fun entry : Nat × List EvmYul.Yul.Ast.Stmt => + entry.1 == selector) entry = false) + (hDiscrSelector : state[discrName]! = EvmYul.UInt256.ofNat selector) + (hSelectorRange : selector < EvmYul.UInt256.size) + (hTagsRange : + ∀ tag' body', (tag', body') ∈ cases → tag' < EvmYul.UInt256.size) : + ∀ tag' body', (tag', body') ∈ pre → + state[discrName]! ≠ EvmYul.UInt256.ofNat tag' := by + intro tag' body' hmem hDiscrTag + have hPrefixFalse := hPrefix (tag', body') hmem + have hTagNe : tag' ≠ selector := by + intro hEq + have hTrue : (tag' == selector) = true := beq_iff_eq.mpr hEq + have hFalse : (tag' == selector) = false := by + simpa using hPrefixFalse + rw [hTrue] at hFalse + contradiction + have hCaseMem : (tag', body') ∈ cases := by + rw [hCases] + simp [hmem] + have hWordNe : + EvmYul.UInt256.ofNat selector ≠ EvmYul.UInt256.ofNat tag' := + uint256_ofNat_ne_of_ne_of_lt hSelectorRange + (hTagsRange tag' body' hCaseMem) (Ne.symm hTagNe) + exact hWordNe (hDiscrSelector.symm.trans hDiscrTag) + +/-- A selector lookup hit exposes the generated case list as a miss prefix, + selected case, and suffix. This is the list-shape bridge consumed by the + native lazy-switch execution lemmas. -/ +theorem nativeSwitch_find_hit_split + (selector : Nat) + (cases : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (tag : Nat) + (body : List EvmYul.Yul.Ast.Stmt) + (hFind : + cases.find? (fun entry => entry.1 == selector) = some (tag, body)) : + ∃ pre suffix, + cases = pre ++ (tag, body) :: suffix ∧ + tag = selector ∧ + ∀ entry, entry ∈ pre → + (fun entry : Nat × List EvmYul.Yul.Ast.Stmt => + entry.1 == selector) entry = false := by + rcases list_find?_eq_some_split_false + (fun entry : Nat × List EvmYul.Yul.Ast.Stmt => entry.1 == selector) + hFind with + ⟨pre, suffix, hSplit, hPrefix⟩ + have hSelected : + (fun entry : Nat × List EvmYul.Yul.Ast.Stmt => + entry.1 == selector) (tag, body) = true := + List.find?_some + (p := fun entry : Nat × List EvmYul.Yul.Ast.Stmt => + entry.1 == selector) hFind + have hTag : tag = selector := by + exact beq_iff_eq.mp hSelected + exact ⟨pre, suffix, hSplit, hTag, hPrefix⟩ + +/-- A selector lookup miss proves every generated case tag misses the native + dispatcher discriminator when the discriminator contains that selector and + all case tags are in the `UInt256` range. -/ +theorem nativeSwitch_find_none_all_miss + (selector : Nat) + (cases : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (state : EvmYul.Yul.State) + (discrName : EvmYul.Identifier) + (hFind : + cases.find? (fun entry => entry.1 == selector) = none) + (hDiscrSelector : state[discrName]! = EvmYul.UInt256.ofNat selector) + (hSelectorRange : selector < EvmYul.UInt256.size) + (hTagsRange : + ∀ tag body, (tag, body) ∈ cases → tag < EvmYul.UInt256.size) : + ∀ tag body, (tag, body) ∈ cases → + state[discrName]! ≠ EvmYul.UInt256.ofNat tag := by + intro tag body hmem hDiscrTag + have hFalse := + list_find?_eq_none_all_false + (fun entry : Nat × List EvmYul.Yul.Ast.Stmt => entry.1 == selector) + hFind (tag, body) hmem + have hTagNe : tag ≠ selector := by + intro hEq + have hTrue : (tag == selector) = true := beq_iff_eq.mpr hEq + have hFalse' : (tag == selector) = false := by + simpa using hFalse + rw [hTrue] at hFalse' + contradiction + have hWordNe : + EvmYul.UInt256.ofNat selector ≠ EvmYul.UInt256.ofNat tag := + uint256_ofNat_ne_of_ne_of_lt hSelectorRange + (hTagsRange tag body hmem) (Ne.symm hTagNe) + exact hWordNe (hDiscrSelector.symm.trans hDiscrTag) + +/-- If no case tag matches and the matched flag is still clear, the generated + native switch case chain skips every case body and leaves the state + unchanged. -/ +theorem exec_nativeSwitchCaseIfs_all_miss_fuel + (fuel : Nat) + (cases : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state : EvmYul.Yul.State) + (discrName matchedName : EvmYul.Identifier) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 0) + (hMiss : + ∀ tag body, (tag, body) ∈ cases → + state[discrName]! ≠ EvmYul.UInt256.ofNat tag) : + EvmYul.Yul.exec (fuel + cases.length + 9) + (.Block (nativeSwitchCaseIfs discrName matchedName cases)) + codeOverride state = .ok state := by + induction cases generalizing fuel codeOverride state discrName matchedName with + | nil => + simp [nativeSwitchCaseIfs, EvmYul.Yul.exec] + | cons entry rest ih => + rcases entry with ⟨tag, body⟩ + have hHeadMiss : state[discrName]! ≠ EvmYul.UInt256.ofNat tag := by + exact hMiss tag body (by simp) + have hRestMiss : + ∀ tag' body', (tag', body') ∈ rest → + state[discrName]! ≠ EvmYul.UInt256.ofNat tag' := by + intro tag' body' hmem + exact hMiss tag' body' (by simp [hmem]) + have hHead : + EvmYul.Yul.exec (fuel + rest.length + 9) + (nativeSwitchCaseIf discrName matchedName (tag, body)) + codeOverride state = .ok state := by + simpa [nativeSwitchCaseIf, nativeSwitchGuardedMatchExpr] using + (exec_if_nativeSwitchGuardedMatch_miss_fuel + (fuel + rest.length) (Backends.lowerAssignNative matchedName (.lit 1) :: body) + codeOverride state discrName matchedName tag hMatched hHeadMiss) + have hTail : + EvmYul.Yul.exec (fuel + rest.length + 9) + (.Block (nativeSwitchCaseIfs discrName matchedName rest)) + codeOverride state = .ok state := + ih fuel codeOverride state discrName matchedName hMatched hRestMiss + have hBlock := exec_block_cons_ok (fuel + rest.length + 9) + (nativeSwitchCaseIf discrName matchedName (tag, body)) + (nativeSwitchCaseIfs discrName matchedName rest) + codeOverride state state state hHead hTail + simpa [nativeSwitchCaseIfs, Nat.add_assoc, Nat.add_comm, Nat.add_left_comm] + using hBlock + +/-- Once a selected lowered switch body preserves the matched flag at one, every + later generated case guard skips. -/ +theorem exec_nativeSwitchCaseIfs_matched_fuel + (fuel : Nat) + (cases : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state : EvmYul.Yul.State) + (discrName matchedName : EvmYul.Identifier) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 1) : + EvmYul.Yul.exec (fuel + cases.length + 9) + (.Block (nativeSwitchCaseIfs discrName matchedName cases)) + codeOverride state = .ok state := by + induction cases generalizing fuel codeOverride state discrName matchedName with + | nil => + simp [nativeSwitchCaseIfs, EvmYul.Yul.exec] + | cons entry rest ih => + rcases entry with ⟨tag, body⟩ + have hHead : + EvmYul.Yul.exec (fuel + rest.length + 9) + (nativeSwitchCaseIf discrName matchedName (tag, body)) + codeOverride state = .ok state := by + simpa [nativeSwitchCaseIf, nativeSwitchGuardedMatchExpr] using + (exec_if_nativeSwitchGuardedMatch_matched_fuel + (fuel + rest.length) (Backends.lowerAssignNative matchedName (.lit 1) :: body) + codeOverride state discrName matchedName tag hMatched) + have hTail : + EvmYul.Yul.exec (fuel + rest.length + 9) + (.Block (nativeSwitchCaseIfs discrName matchedName rest)) + codeOverride state = .ok state := + ih fuel codeOverride state discrName matchedName hMatched + have hBlock := exec_block_cons_ok (fuel + rest.length + 9) + (nativeSwitchCaseIf discrName matchedName (tag, body)) + (nativeSwitchCaseIfs discrName matchedName rest) + codeOverride state state state hHead hTail + simpa [nativeSwitchCaseIfs, Nat.add_assoc, Nat.add_comm, Nat.add_left_comm] + using hBlock + +/-- Whole generated case-chain execution when the first remaining case is the + selected case and suffix cases must skip after the selected body preserves + the matched flag. -/ +theorem exec_nativeSwitchCaseIfs_head_hit_fuel + (fuel : Nat) + (suffix : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (tag : Nat) + (body : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state final : EvmYul.Yul.State) + (discrName matchedName : EvmYul.Identifier) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 0) + (hDiscr : state[discrName]! = EvmYul.UInt256.ofNat tag) + (hBody : + EvmYul.Yul.exec (fuel + suffix.length + 7) (.Block body) codeOverride + (state.insert matchedName (EvmYul.UInt256.ofNat 1)) = .ok final) + (hFinalMatched : final[matchedName]! = EvmYul.UInt256.ofNat 1) : + EvmYul.Yul.exec (fuel + suffix.length + 10) + (.Block + (nativeSwitchCaseIfs discrName matchedName ((tag, body) :: suffix))) + codeOverride state = .ok final := by + have hHead : + EvmYul.Yul.exec (fuel + suffix.length + 9) + (nativeSwitchCaseIf discrName matchedName (tag, body)) + codeOverride state = .ok final := by + simpa [nativeSwitchCaseIf, nativeSwitchGuardedMatchExpr] using + (exec_if_nativeSwitchGuardedMatch_hit_marked_fuel + (fuel + suffix.length) body codeOverride state final discrName + matchedName tag hMatched hDiscr hBody) + have hTail : + EvmYul.Yul.exec (fuel + suffix.length + 9) + (.Block (nativeSwitchCaseIfs discrName matchedName suffix)) + codeOverride final = .ok final := + exec_nativeSwitchCaseIfs_matched_fuel fuel suffix codeOverride final + discrName matchedName hFinalMatched + have hBlock := exec_block_cons_ok (fuel + suffix.length + 9) + (nativeSwitchCaseIf discrName matchedName (tag, body)) + (nativeSwitchCaseIfs discrName matchedName suffix) + codeOverride state final final hHead hTail + simpa [nativeSwitchCaseIfs, Nat.add_assoc, Nat.add_comm, Nat.add_left_comm] + using hBlock + +/-- Cons a non-selected generated switch case onto an already-proved generated + case-chain execution. -/ +theorem exec_nativeSwitchCaseIfs_cons_miss_fuel + (fuel : Nat) + (rest : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (missTag : Nat) + (missBody : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state final : EvmYul.Yul.State) + (discrName matchedName : EvmYul.Identifier) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 0) + (hHeadMiss : state[discrName]! ≠ EvmYul.UInt256.ofNat missTag) + (hTail : + EvmYul.Yul.exec (fuel + rest.length + 9) + (.Block (nativeSwitchCaseIfs discrName matchedName rest)) + codeOverride state = .ok final) : + EvmYul.Yul.exec (fuel + rest.length + 10) + (.Block + (nativeSwitchCaseIfs discrName matchedName + ((missTag, missBody) :: rest))) + codeOverride state = .ok final := by + have hHead : + EvmYul.Yul.exec (fuel + rest.length + 9) + (nativeSwitchCaseIf discrName matchedName (missTag, missBody)) + codeOverride state = .ok state := by + simpa [nativeSwitchCaseIf, nativeSwitchGuardedMatchExpr] using + (exec_if_nativeSwitchGuardedMatch_miss_fuel + (fuel + rest.length) + (Backends.lowerAssignNative matchedName (.lit 1) :: missBody) + codeOverride state discrName matchedName missTag hMatched hHeadMiss) + have hBlock := exec_block_cons_ok (fuel + rest.length + 9) + (nativeSwitchCaseIf discrName matchedName (missTag, missBody)) + (nativeSwitchCaseIfs discrName matchedName rest) + codeOverride state state final hHead hTail + simpa [nativeSwitchCaseIfs, Nat.add_assoc, Nat.add_comm, Nat.add_left_comm] + using hBlock + +/-- Whole generated case-chain execution for a miss prefix, selected case, and suffix. -/ +theorem exec_nativeSwitchCaseIfs_prefix_hit_fuel + (fuel : Nat) + (pre suffix : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (tag : Nat) (body : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state final : EvmYul.Yul.State) + (discrName matchedName : EvmYul.Identifier) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 0) + (hMissPrefix : ∀ tag' body', (tag', body') ∈ pre → + state[discrName]! ≠ EvmYul.UInt256.ofNat tag') + (hDiscr : state[discrName]! = EvmYul.UInt256.ofNat tag) + (hBody : + EvmYul.Yul.exec (fuel + suffix.length + 7) (.Block body) codeOverride + (state.insert matchedName (EvmYul.UInt256.ofNat 1)) = .ok final) + (hFinalMatched : final[matchedName]! = EvmYul.UInt256.ofNat 1) : + EvmYul.Yul.exec (fuel + (pre ++ (tag, body) :: suffix).length + 9) + (.Block (nativeSwitchCaseIfs discrName matchedName + (pre ++ (tag, body) :: suffix))) + codeOverride state = .ok final := by + induction pre generalizing fuel with + | nil => + simpa [Nat.add_assoc, Nat.add_comm, Nat.add_left_comm] using + (exec_nativeSwitchCaseIfs_head_hit_fuel fuel suffix tag body codeOverride + state final discrName matchedName hMatched hDiscr hBody hFinalMatched) + | cons entry rest ih => + rcases entry with ⟨missTag, missBody⟩ + have hHeadMiss : + state[discrName]! ≠ EvmYul.UInt256.ofNat missTag := by + exact hMissPrefix missTag missBody (by simp) + have hRestMiss : + ∀ tag' body', (tag', body') ∈ rest → + state[discrName]! ≠ EvmYul.UInt256.ofNat tag' := by + intro tag' body' hmem + exact hMissPrefix tag' body' (by simp [hmem]) + have hTail : + EvmYul.Yul.exec + (fuel + (rest ++ (tag, body) :: suffix).length + 9) + (.Block (nativeSwitchCaseIfs discrName matchedName + (rest ++ (tag, body) :: suffix))) + codeOverride state = .ok final := + ih fuel hRestMiss hBody + simpa [Nat.add_assoc, Nat.add_comm, Nat.add_left_comm] using + (exec_nativeSwitchCaseIfs_cons_miss_fuel fuel + (rest ++ (tag, body) :: suffix) missTag missBody codeOverride state + final discrName matchedName hMatched hHeadMiss hTail) + +/-- Whole generated case-chain execution for a selector lookup hit. This wraps + `exec_nativeSwitchCaseIfs_prefix_hit_fuel` with the generated dispatcher + lookup split, so callers only need the `find?` result and the selected body + execution premise for the discovered suffix. -/ +theorem exec_nativeSwitchCaseIfs_find_hit_fuel + (fuel selector : Nat) + (cases : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (tag : Nat) + (body : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state final : EvmYul.Yul.State) + (discrName matchedName : EvmYul.Identifier) + (hFind : cases.find? (fun entry => entry.1 == selector) = some (tag, body)) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 0) + (hDiscrSelector : state[discrName]! = EvmYul.UInt256.ofNat selector) + (hSelectorRange : selector < EvmYul.UInt256.size) + (hTagsRange : ∀ tag' body', (tag', body') ∈ cases → tag' < EvmYul.UInt256.size) + (hBody : + ∀ pre suffix, + cases = pre ++ (tag, body) :: suffix → + EvmYul.Yul.exec (fuel + suffix.length + 7) (.Block body) + codeOverride (state.insert matchedName (EvmYul.UInt256.ofNat 1)) = + .ok final) + (hFinalMatched : final[matchedName]! = EvmYul.UInt256.ofNat 1) : + EvmYul.Yul.exec (fuel + cases.length + 9) + (.Block (nativeSwitchCaseIfs discrName matchedName cases)) + codeOverride state = .ok final := by + rcases nativeSwitch_find_hit_split selector cases tag body hFind with + ⟨pre, suffix, hCases, hTag, hPrefix⟩ + subst tag + have hMissPrefix : + ∀ tag' body', (tag', body') ∈ pre → + state[discrName]! ≠ EvmYul.UInt256.ofNat tag' := + nativeSwitch_prefix_miss_of_selector_find selector cases pre suffix selector body + state discrName hCases hPrefix hDiscrSelector hSelectorRange hTagsRange + have hSelectedBody : + EvmYul.Yul.exec (fuel + suffix.length + 7) (.Block body) + codeOverride (state.insert matchedName (EvmYul.UInt256.ofNat 1)) = + .ok final := + hBody pre suffix hCases + have hExec := + exec_nativeSwitchCaseIfs_prefix_hit_fuel fuel pre suffix selector body + codeOverride state final discrName matchedName hMatched hMissPrefix + hDiscrSelector hSelectedBody hFinalMatched + simpa [hCases, Nat.add_assoc, Nat.add_comm, Nat.add_left_comm] using hExec + +/-- Selector-hit case-chain execution with the selected-body matched-flag + preservation obligation factored into a reusable predicate. + +This is the proof boundary needed by the full native dispatcher bridge: the +lowered case body may update storage, memory, and user variables, but it must +not clobber the generated lazy-switch matched flag after the lowering has set +it to one. -/ +theorem exec_nativeSwitchCaseIfs_find_hit_preserved_fuel + (fuel selector : Nat) + (cases : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (tag : Nat) + (body : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state final : EvmYul.Yul.State) + (discrName matchedName : EvmYul.Identifier) + (hFind : + cases.find? (fun entry => entry.1 == selector) = some (tag, body)) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 0) + (hDiscrSelector : state[discrName]! = EvmYul.UInt256.ofNat selector) + (hSelectorRange : selector < EvmYul.UInt256.size) + (hTagsRange : + ∀ tag' body', (tag', body') ∈ cases → tag' < EvmYul.UInt256.size) + (hMarked : + (state.insert matchedName (EvmYul.UInt256.ofNat 1))[matchedName]! = + EvmYul.UInt256.ofNat 1) + (hBody : + ∀ pre suffix, + cases = pre ++ (tag, body) :: suffix → + EvmYul.Yul.exec (fuel + suffix.length + 7) (.Block body) + codeOverride (state.insert matchedName (EvmYul.UInt256.ofNat 1)) = + .ok final) + (hPreservesMatched : + ∀ pre suffix, + cases = pre ++ (tag, body) :: suffix → + NativeBlockPreservesWord matchedName (EvmYul.UInt256.ofNat 1) + body codeOverride) : + EvmYul.Yul.exec (fuel + cases.length + 9) + (.Block (nativeSwitchCaseIfs discrName matchedName cases)) + codeOverride state = .ok final := by + apply exec_nativeSwitchCaseIfs_find_hit_fuel + (fuel := fuel) (selector := selector) (cases := cases) (tag := tag) + (body := body) (codeOverride := codeOverride) (state := state) + (final := final) (discrName := discrName) (matchedName := matchedName) + hFind hMatched hDiscrSelector hSelectorRange hTagsRange hBody + rcases nativeSwitch_find_hit_split selector cases tag body hFind with + ⟨pre, suffix, hCases, _hTag, _hPrefix⟩ + exact hPreservesMatched pre suffix hCases (fuel + suffix.length + 7) + (state.insert matchedName (EvmYul.UInt256.ofNat 1)) final hMarked + (hBody pre suffix hCases) + +/-- Whole generated case-chain skip for a selector lookup miss. This packages + the `find? = none` selector fact into the all-cases-miss premise expected by + the lazy native switch executor. -/ +theorem exec_nativeSwitchCaseIfs_find_none_fuel + (fuel selector : Nat) + (cases : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state : EvmYul.Yul.State) + (discrName matchedName : EvmYul.Identifier) + (hFind : + cases.find? (fun entry => entry.1 == selector) = none) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 0) + (hDiscrSelector : state[discrName]! = EvmYul.UInt256.ofNat selector) + (hSelectorRange : selector < EvmYul.UInt256.size) + (hTagsRange : + ∀ tag body, (tag, body) ∈ cases → tag < EvmYul.UInt256.size) : + EvmYul.Yul.exec (fuel + cases.length + 9) + (.Block (nativeSwitchCaseIfs discrName matchedName cases)) + codeOverride state = .ok state := by + have hMiss : + ∀ tag body, (tag, body) ∈ cases → + state[discrName]! ≠ EvmYul.UInt256.ofNat tag := + nativeSwitch_find_none_all_miss selector cases state discrName hFind + hDiscrSelector hSelectorRange hTagsRange + exact exec_nativeSwitchCaseIfs_all_miss_fuel fuel cases codeOverride state + discrName matchedName hMatched hMiss +/-- Non-empty generated default block execution when no case matched. -/ +theorem exec_nativeSwitchDefaultIf_unmatched_nonempty_fuel + (fuel : Nat) + (defaultBody : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state final : EvmYul.Yul.State) + (matchedName : EvmYul.Identifier) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 0) + (hBody : + EvmYul.Yul.exec (fuel + 6) (.Block defaultBody) codeOverride state = + .ok final) + (hNonempty : defaultBody ≠ []) : + EvmYul.Yul.exec (fuel + 8) + (.Block (nativeSwitchDefaultIf matchedName defaultBody)) + codeOverride state = .ok final := by + cases defaultBody with + | nil => contradiction + | cons stmt rest => + have hHead : + EvmYul.Yul.exec (fuel + 7) + (.If (nativeSwitchDefaultGuardExpr matchedName) (stmt :: rest)) + codeOverride state = .ok final := by + simpa [nativeSwitchDefaultGuardExpr] using + (exec_if_nativeSwitchDefaultGuard_unmatched_fuel fuel + (stmt :: rest) codeOverride state final matchedName hMatched hBody) + have hTail : + EvmYul.Yul.exec (fuel + 7) (.Block []) + codeOverride final = .ok final := by + simp [EvmYul.Yul.exec] + exact exec_block_cons_ok (fuel + 7) + (.If (nativeSwitchDefaultGuardExpr matchedName) (stmt :: rest)) + [] codeOverride state final final hHead hTail + +/-- After a selected case preserves the matched flag at one, the optional + generated default block skips. Empty defaults also skip because no default + statement is emitted. -/ +theorem exec_nativeSwitchDefaultIf_matched_fuel + (fuel : Nat) + (defaultBody : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state : EvmYul.Yul.State) + (matchedName : EvmYul.Identifier) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 1) : + EvmYul.Yul.exec + (fuel + (nativeSwitchDefaultIf matchedName defaultBody).length + 7) + (.Block (nativeSwitchDefaultIf matchedName defaultBody)) + codeOverride state = .ok state := by + cases defaultBody with + | nil => + simp [nativeSwitchDefaultIf, EvmYul.Yul.exec] + | cons stmt rest => + have hHead : + EvmYul.Yul.exec (fuel + 7) + (.If (nativeSwitchDefaultGuardExpr matchedName) (stmt :: rest)) + codeOverride state = .ok state := by + simpa [nativeSwitchDefaultGuardExpr] using + (exec_if_nativeSwitchDefaultGuard_matched_fuel fuel + (stmt :: rest) codeOverride state matchedName hMatched) + have hTail : + EvmYul.Yul.exec (fuel + 7) (.Block []) + codeOverride state = .ok state := by + simp [EvmYul.Yul.exec] + simpa [nativeSwitchDefaultIf] using + (exec_block_cons_ok (fuel + 7) + (.If (nativeSwitchDefaultGuardExpr matchedName) (stmt :: rest)) + [] codeOverride state state state hHead hTail) + +/-- Default-tail skip at the fuel level left after a generated case chain. -/ +theorem exec_nativeSwitchDefaultIf_matched_caseTail_fuel + (fuel : Nat) + (defaultBody : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state : EvmYul.Yul.State) + (matchedName : EvmYul.Identifier) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 1) : + EvmYul.Yul.exec (fuel + 9) + (.Block (nativeSwitchDefaultIf matchedName defaultBody)) + codeOverride state = .ok state := by + cases defaultBody with + | nil => + simp [nativeSwitchDefaultIf, EvmYul.Yul.exec] + | cons stmt rest => + simpa [nativeSwitchDefaultIf, Nat.add_assoc, Nat.add_comm, + Nat.add_left_comm] using + (exec_nativeSwitchDefaultIf_matched_fuel (fuel + 1) + (stmt :: rest) codeOverride state matchedName hMatched) + +/-- Non-empty default-tail execution at the fuel level left after all generated + cases miss. -/ +theorem exec_nativeSwitchDefaultIf_unmatched_caseTail_nonempty_fuel + (fuel : Nat) + (defaultBody : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state final : EvmYul.Yul.State) + (matchedName : EvmYul.Identifier) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 0) + (hBody : + EvmYul.Yul.exec (fuel + 7) (.Block defaultBody) codeOverride state = + .ok final) + (hNonempty : defaultBody ≠ []) : + EvmYul.Yul.exec (fuel + 9) + (.Block (nativeSwitchDefaultIf matchedName defaultBody)) + codeOverride state = .ok final := by + simpa [Nat.add_assoc, Nat.add_comm, Nat.add_left_comm] using + (exec_nativeSwitchDefaultIf_unmatched_nonempty_fuel (fuel + 1) + defaultBody codeOverride state final matchedName hMatched hBody hNonempty) + +/-- Compose a generated case chain with its optional default when the case chain + has already set and preserved the matched flag. -/ +theorem exec_nativeSwitchCaseIfs_with_default_matched_fuel + (fuel : Nat) + (cases : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (defaultBody : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state final : EvmYul.Yul.State) + (discrName matchedName : EvmYul.Identifier) + (hCases : + EvmYul.Yul.exec (fuel + cases.length + 9) + (.Block (nativeSwitchCaseIfs discrName matchedName cases)) + codeOverride state = .ok final) + (hFinalMatched : final[matchedName]! = EvmYul.UInt256.ofNat 1) : + EvmYul.Yul.exec (fuel + cases.length + 9) + (.Block + (nativeSwitchCaseIfs discrName matchedName cases ++ + nativeSwitchDefaultIf matchedName defaultBody)) + codeOverride state = .ok final := by + have hDefault : + EvmYul.Yul.exec (fuel + 9) + (.Block (nativeSwitchDefaultIf matchedName defaultBody)) + codeOverride final = .ok final := + exec_nativeSwitchDefaultIf_matched_caseTail_fuel fuel defaultBody + codeOverride final matchedName hFinalMatched + simpa [nativeSwitchCaseIfs, Nat.add_assoc, Nat.add_comm, Nat.add_left_comm] + using exec_block_append_ok fuel 9 + (nativeSwitchCaseIfs discrName matchedName cases) + (nativeSwitchDefaultIf matchedName defaultBody) + codeOverride state final final + (by simpa [nativeSwitchCaseIfs, Nat.add_assoc, Nat.add_comm, + Nat.add_left_comm] using hCases) + hDefault + +/-- Selector-hit execution for the generated case chain followed by the + generated optional default statement list. The selected body must preserve + the matched flag so the default guard and suffix cases skip. -/ +theorem exec_nativeSwitchCaseIfs_find_hit_with_default_preserved_fuel + (fuel selector : Nat) + (cases : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (defaultBody : List EvmYul.Yul.Ast.Stmt) (tag : Nat) + (body : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state final : EvmYul.Yul.State) + (discrName matchedName : EvmYul.Identifier) + (hFind : cases.find? (fun entry => entry.1 == selector) = some (tag, body)) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 0) + (hDiscrSelector : state[discrName]! = EvmYul.UInt256.ofNat selector) + (hSelectorRange : selector < EvmYul.UInt256.size) + (hTagsRange : ∀ tag' body', (tag', body') ∈ cases → tag' < EvmYul.UInt256.size) + (hMarked : (state.insert matchedName (EvmYul.UInt256.ofNat 1))[matchedName]! = + EvmYul.UInt256.ofNat 1) + (hBody : ∀ pre suffix, cases = pre ++ (tag, body) :: suffix → + EvmYul.Yul.exec (fuel + suffix.length + 7) (.Block body) codeOverride + (state.insert matchedName (EvmYul.UInt256.ofNat 1)) = .ok final) + (hPreservesMatched : ∀ pre suffix, cases = pre ++ (tag, body) :: suffix → + NativeBlockPreservesWord matchedName (EvmYul.UInt256.ofNat 1) body codeOverride) : + EvmYul.Yul.exec (fuel + cases.length + 9) (.Block + (nativeSwitchCaseIfs discrName matchedName cases ++ nativeSwitchDefaultIf matchedName defaultBody)) + codeOverride state = .ok final := by + have hCases : + EvmYul.Yul.exec (fuel + cases.length + 9) + (.Block (nativeSwitchCaseIfs discrName matchedName cases)) + codeOverride state = .ok final := + exec_nativeSwitchCaseIfs_find_hit_preserved_fuel fuel selector cases tag + body codeOverride state final discrName matchedName hFind hMatched + hDiscrSelector hSelectorRange hTagsRange hMarked hBody hPreservesMatched + have hFinalMatched : final[matchedName]! = EvmYul.UInt256.ofNat 1 := by + rcases nativeSwitch_find_hit_split selector cases tag body hFind with + ⟨pre, suffix, hCasesEq, _hTag, _hPrefix⟩ + exact hPreservesMatched pre suffix hCasesEq + (fuel + suffix.length + 7) + (state.insert matchedName (EvmYul.UInt256.ofNat 1)) final hMarked + (hBody pre suffix hCasesEq) + exact exec_nativeSwitchCaseIfs_with_default_matched_fuel fuel cases + defaultBody codeOverride state final discrName matchedName hCases + hFinalMatched + +/-- Selector-miss execution for the generated case chain followed by a + non-empty generated default block. -/ +theorem exec_nativeSwitchCaseIfs_find_none_with_default_nonempty_fuel + (fuel selector : Nat) + (cases : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (defaultBody : List EvmYul.Yul.Ast.Stmt) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state final : EvmYul.Yul.State) + (discrName matchedName : EvmYul.Identifier) + (hFind : + cases.find? (fun entry => entry.1 == selector) = none) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 0) + (hDiscrSelector : state[discrName]! = EvmYul.UInt256.ofNat selector) + (hSelectorRange : selector < EvmYul.UInt256.size) + (hTagsRange : + ∀ tag body, (tag, body) ∈ cases → tag < EvmYul.UInt256.size) + (hDefaultBody : + EvmYul.Yul.exec (fuel + 7) (.Block defaultBody) codeOverride state = + .ok final) + (hNonempty : defaultBody ≠ []) : + EvmYul.Yul.exec (fuel + cases.length + 9) + (.Block + (nativeSwitchCaseIfs discrName matchedName cases ++ + nativeSwitchDefaultIf matchedName defaultBody)) + codeOverride state = .ok final := by + have hCases : + EvmYul.Yul.exec (fuel + cases.length + 9) + (.Block (nativeSwitchCaseIfs discrName matchedName cases)) + codeOverride state = .ok state := + exec_nativeSwitchCaseIfs_find_none_fuel fuel selector cases codeOverride + state discrName matchedName hFind hMatched hDiscrSelector hSelectorRange + hTagsRange + have hDefault : + EvmYul.Yul.exec (fuel + 9) + (.Block (nativeSwitchDefaultIf matchedName defaultBody)) + codeOverride state = .ok final := + exec_nativeSwitchDefaultIf_unmatched_caseTail_nonempty_fuel fuel + defaultBody codeOverride state final matchedName hMatched hDefaultBody + hNonempty + simpa [nativeSwitchCaseIfs, Nat.add_assoc, Nat.add_comm, Nat.add_left_comm] + using + (exec_block_append_ok fuel 9 + (nativeSwitchCaseIfs discrName matchedName cases) + (nativeSwitchDefaultIf matchedName defaultBody) + codeOverride state state final + (by simpa [nativeSwitchCaseIfs, Nat.add_assoc, Nat.add_comm, + Nat.add_left_comm] using hCases) + hDefault) + +/-- Selector-miss execution for the generated case chain when no default is + emitted. -/ +theorem exec_nativeSwitchCaseIfs_find_none_without_default_fuel + (fuel selector : Nat) + (cases : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (codeOverride : Option EvmYul.Yul.Ast.YulContract) + (state : EvmYul.Yul.State) + (discrName matchedName : EvmYul.Identifier) + (hFind : + cases.find? (fun entry => entry.1 == selector) = none) + (hMatched : state[matchedName]! = EvmYul.UInt256.ofNat 0) + (hDiscrSelector : state[discrName]! = EvmYul.UInt256.ofNat selector) + (hSelectorRange : selector < EvmYul.UInt256.size) + (hTagsRange : + ∀ tag body, (tag, body) ∈ cases → tag < EvmYul.UInt256.size) : + EvmYul.Yul.exec (fuel + cases.length + 9) + (.Block + (nativeSwitchCaseIfs discrName matchedName cases ++ + nativeSwitchDefaultIf matchedName [])) + codeOverride state = .ok state := by + have hCases : + EvmYul.Yul.exec (fuel + cases.length + 9) + (.Block (nativeSwitchCaseIfs discrName matchedName cases)) + codeOverride state = .ok state := + exec_nativeSwitchCaseIfs_find_none_fuel fuel selector cases codeOverride + state discrName matchedName hFind hMatched hDiscrSelector hSelectorRange + hTagsRange + have hDefault : + EvmYul.Yul.exec (fuel + 9) + (.Block (nativeSwitchDefaultIf matchedName [])) + codeOverride state = .ok state := by + simp [nativeSwitchDefaultIf, EvmYul.Yul.exec] + simpa [nativeSwitchCaseIfs, nativeSwitchDefaultIf, Nat.add_assoc, + Nat.add_comm, Nat.add_left_comm] using + (exec_block_append_ok fuel 9 + (nativeSwitchCaseIfs discrName matchedName cases) + (nativeSwitchDefaultIf matchedName []) + codeOverride state state state + (by simpa [nativeSwitchCaseIfs, Nat.add_assoc, Nat.add_comm, + Nat.add_left_comm] using hCases) + hDefault) + +theorem exec_nativeSwitchPrefix_then_tail_fuel + (fuel : Nat) + (tail : List EvmYul.Yul.Ast.Stmt) + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) + (discrName matchedName : EvmYul.Identifier) + (final : EvmYul.Yul.State) + (hTail : + EvmYul.Yul.exec (fuel + 10) (.Block tail) (some contract) + (nativeSwitchPrefixFinalState contract tx storage observableSlots + discrName matchedName) = + .ok final) : + EvmYul.Yul.exec (fuel + 12) + (.Block (nativeSwitchPrefixStmts discrName matchedName ++ tail)) + (some contract) + (nativeSwitchInitialOkState contract tx storage observableSlots) = + .ok final := by + let prefixState := + nativeSwitchPrefixFinalState contract tx storage observableSlots + discrName matchedName + have hPrefix : + EvmYul.Yul.exec (fuel + 12) + (.Block (nativeSwitchPrefixStmts discrName matchedName)) + (some contract) + (nativeSwitchInitialOkState contract tx storage observableSlots) = + .ok prefixState := by + simpa [prefixState, Nat.add_assoc, Nat.add_comm, Nat.add_left_comm] using + (exec_nativeSwitchPrefix_selector_initialState_ok_fuel fuel + contract tx storage observableSlots discrName matchedName) + exact exec_block_append_ok (fuel + 10) 0 + (nativeSwitchPrefixStmts discrName matchedName) tail + (some contract) + (nativeSwitchInitialOkState contract tx storage observableSlots) + prefixState final + (by simpa [nativeSwitchPrefixStmts, Nat.add_assoc, Nat.add_comm, + Nat.add_left_comm] using hPrefix) + (by simpa [prefixState] using hTail) + +theorem exec_nativeSwitchTail_find_hit_preserved_fuel + (fuel selector switchId tag : Nat) + (cases : List (Nat × List EvmYul.Yul.Ast.Stmt)) (defaultBody body : List EvmYul.Yul.Ast.Stmt) + (contract : EvmYul.Yul.Ast.YulContract) (tx : YulTransaction) (storage : Nat → Nat) + (observableSlots : List Nat) (final : EvmYul.Yul.State) + (hSelector : selector = tx.functionSelector % Compiler.Constants.selectorModulus) + (hFind : cases.find? (fun entry => entry.1 == selector) = some (tag, body)) + (hSelectorRange : selector < EvmYul.UInt256.size) + (hTagsRange : ∀ tag' body', (tag', body') ∈ cases → tag' < EvmYul.UInt256.size) + (hBody : ∀ pre suffix, cases = pre ++ (tag, body) :: suffix → + EvmYul.Yul.exec ((fuel + 1) + suffix.length + 7) (.Block body) + (some contract) (nativeSwitchMarkedPrefixStateForId contract tx storage observableSlots switchId) = .ok final) + (hPreservesMatched : ∀ pre suffix, cases = pre ++ (tag, body) :: suffix → + NativeBlockPreservesWord (Backends.nativeSwitchMatchedTempName switchId) + (EvmYul.UInt256.ofNat 1) body (some contract)) : + EvmYul.Yul.exec (fuel + cases.length + 10) + (.Block (nativeSwitchTailStmts switchId cases defaultBody)) + (some contract) (nativeSwitchPrefixStateForId contract tx storage observableSlots switchId) = + .ok final := by + let discrName : EvmYul.Identifier := Backends.nativeSwitchDiscrTempName switchId + let matchedName : EvmYul.Identifier := Backends.nativeSwitchMatchedTempName switchId + let prefixState := + nativeSwitchPrefixFinalState contract tx storage observableSlots + discrName matchedName + have hCasesDefault := + exec_nativeSwitchCaseIfs_find_hit_with_default_preserved_fuel + (fuel + 1) selector cases defaultBody tag body (some contract) + prefixState final discrName matchedName hFind + (by simpa [prefixState, discrName, matchedName] using + (nativeSwitchPrefixFinalState_matched contract tx storage + observableSlots discrName matchedName)) + (by simpa [prefixState, discrName, matchedName] using + (nativeSwitchPrefixFinalState_discr contract tx storage observableSlots + discrName matchedName selector + (nativeSwitchDiscrTempName_ne_matchedTempName switchId) hSelector)) + hSelectorRange hTagsRange + (by simpa [prefixState, discrName, matchedName] using + (nativeSwitchPrefixFinalState_marked contract tx storage + observableSlots discrName matchedName)) + (by intro pre suffix hCases; simpa [nativeSwitchMarkedPrefixStateForId, + nativeSwitchPrefixStateForId, prefixState, discrName, matchedName] + using hBody pre suffix hCases) + (by intro pre suffix hCases; simpa [matchedName] using + hPreservesMatched pre suffix hCases) + simpa [nativeSwitchTailStmts, nativeSwitchPrefixStateForId, prefixState, + discrName, matchedName, Nat.add_assoc, Nat.add_comm, Nat.add_left_comm] + using hCasesDefault + +theorem exec_nativeSwitchTail_find_none_with_default_nonempty_fuel + (fuel selector switchId : Nat) + (cases : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (defaultBody : List EvmYul.Yul.Ast.Stmt) + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) + (final : EvmYul.Yul.State) + (hSelector : + selector = tx.functionSelector % Compiler.Constants.selectorModulus) + (hFind : cases.find? (fun entry => entry.1 == selector) = none) + (hSelectorRange : selector < EvmYul.UInt256.size) + (hTagsRange : + ∀ tag body, (tag, body) ∈ cases → tag < EvmYul.UInt256.size) + (hDefaultBody : + EvmYul.Yul.exec ((fuel + 1) + 7) (.Block defaultBody) (some contract) + (nativeSwitchPrefixStateForId contract tx storage observableSlots + switchId) = .ok final) + (hNonempty : defaultBody ≠ []) : + EvmYul.Yul.exec (fuel + cases.length + 10) + (.Block (nativeSwitchTailStmts switchId cases defaultBody)) + (some contract) (nativeSwitchPrefixStateForId contract tx storage + observableSlots switchId) = + .ok final := by + let discrName : EvmYul.Identifier := Backends.nativeSwitchDiscrTempName switchId + let matchedName : EvmYul.Identifier := Backends.nativeSwitchMatchedTempName switchId + let prefixState := + nativeSwitchPrefixFinalState contract tx storage observableSlots + discrName matchedName + have hCasesDefault := + exec_nativeSwitchCaseIfs_find_none_with_default_nonempty_fuel + (fuel + 1) selector cases defaultBody (some contract) + prefixState final discrName matchedName hFind + (by simpa [prefixState, discrName, matchedName] using + (nativeSwitchPrefixFinalState_matched contract tx storage + observableSlots discrName matchedName)) + (by simpa [prefixState, discrName, matchedName] using + (nativeSwitchPrefixFinalState_discr contract tx storage observableSlots + discrName matchedName selector + (nativeSwitchDiscrTempName_ne_matchedTempName switchId) hSelector)) + hSelectorRange hTagsRange + (by simpa [nativeSwitchPrefixStateForId, prefixState, discrName, + matchedName] using hDefaultBody) + hNonempty + simpa [nativeSwitchTailStmts, nativeSwitchPrefixStateForId, prefixState, + discrName, matchedName, Nat.add_assoc, Nat.add_comm, Nat.add_left_comm] + using hCasesDefault + +theorem exec_nativeSwitchTail_find_none_without_default_fuel + (fuel selector switchId : Nat) + (cases : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) + (hSelector : + selector = tx.functionSelector % Compiler.Constants.selectorModulus) + (hFind : cases.find? (fun entry => entry.1 == selector) = none) + (hSelectorRange : selector < EvmYul.UInt256.size) + (hTagsRange : + ∀ tag body, (tag, body) ∈ cases → tag < EvmYul.UInt256.size) : + EvmYul.Yul.exec (fuel + cases.length + 10) + (.Block (nativeSwitchTailStmts switchId cases [])) + (some contract) (nativeSwitchPrefixStateForId contract tx storage + observableSlots switchId) = + .ok (nativeSwitchPrefixStateForId contract tx storage observableSlots + switchId) := by + let discrName : EvmYul.Identifier := Backends.nativeSwitchDiscrTempName switchId + let matchedName : EvmYul.Identifier := Backends.nativeSwitchMatchedTempName switchId + let prefixState := + nativeSwitchPrefixFinalState contract tx storage observableSlots + discrName matchedName + have hCasesDefault := + exec_nativeSwitchCaseIfs_find_none_without_default_fuel + (fuel + 1) selector cases (some contract) + prefixState discrName matchedName hFind + (by simpa [prefixState, discrName, matchedName] using + (nativeSwitchPrefixFinalState_matched contract tx storage + observableSlots discrName matchedName)) + (by simpa [prefixState, discrName, matchedName] using + (nativeSwitchPrefixFinalState_discr contract tx storage observableSlots + discrName matchedName selector + (nativeSwitchDiscrTempName_ne_matchedTempName switchId) hSelector)) + hSelectorRange hTagsRange + simpa [nativeSwitchTailStmts, nativeSwitchPrefixStateForId, prefixState, + discrName, matchedName, Nat.add_assoc, Nat.add_comm, Nat.add_left_comm] + using hCasesDefault + +theorem exec_lowerNativeSwitchBlock_selector_find_hit_preserved_fuel + (fuel selector switchId : Nat) + (cases : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (defaultBody : List EvmYul.Yul.Ast.Stmt) + (tag : Nat) + (body : List EvmYul.Yul.Ast.Stmt) + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) + (final : EvmYul.Yul.State) + (hSelector : + selector = tx.functionSelector % Compiler.Constants.selectorModulus) + (hFind : cases.find? (fun entry => entry.1 == selector) = some (tag, body)) + (hSelectorRange : selector < EvmYul.UInt256.size) + (hTagsRange : + ∀ tag' body', (tag', body') ∈ cases → tag' < EvmYul.UInt256.size) + (hBody : ∀ pre suffix, cases = pre ++ (tag, body) :: suffix → + EvmYul.Yul.exec ((fuel + 1) + suffix.length + 7) (.Block body) + (some contract) + ((nativeSwitchPrefixFinalState contract tx storage observableSlots + (Backends.nativeSwitchDiscrTempName switchId) + (Backends.nativeSwitchMatchedTempName switchId)).insert + (Backends.nativeSwitchMatchedTempName switchId) + (EvmYul.UInt256.ofNat 1)) = .ok final) + (hPreservesMatched : ∀ pre suffix, + cases = pre ++ (tag, body) :: suffix → + NativeBlockPreservesWord (Backends.nativeSwitchMatchedTempName switchId) + (EvmYul.UInt256.ofNat 1) body (some contract)) : + EvmYul.Yul.exec (fuel + cases.length + 12) + (Backends.lowerNativeSwitchBlock + Compiler.Proofs.YulGeneration.selectorExpr switchId cases defaultBody) + (some contract) + (nativeSwitchInitialOkState contract tx storage observableSlots) = + .ok final := by + rw [lowerNativeSwitchBlock_selectorExpr_eq_nativeSwitchParts] + apply exec_nativeSwitchPrefix_then_tail_fuel + exact exec_nativeSwitchTail_find_hit_preserved_fuel fuel selector switchId tag + cases defaultBody body contract tx storage observableSlots final + hSelector hFind hSelectorRange hTagsRange hBody hPreservesMatched + +theorem exec_lowerNativeSwitchBlock_selector_find_none_with_default_nonempty_fuel + (fuel selector switchId : Nat) + (cases : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (defaultBody : List EvmYul.Yul.Ast.Stmt) + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) + (final : EvmYul.Yul.State) + (hSelector : + selector = tx.functionSelector % Compiler.Constants.selectorModulus) + (hFind : cases.find? (fun entry => entry.1 == selector) = none) + (hSelectorRange : selector < EvmYul.UInt256.size) + (hTagsRange : + ∀ tag body, (tag, body) ∈ cases → tag < EvmYul.UInt256.size) + (hDefaultBody : + EvmYul.Yul.exec ((fuel + 1) + 7) (.Block defaultBody) (some contract) + (nativeSwitchPrefixFinalState contract tx storage observableSlots + (Backends.nativeSwitchDiscrTempName switchId) + (Backends.nativeSwitchMatchedTempName switchId)) = + .ok final) + (hNonempty : defaultBody ≠ []) : + EvmYul.Yul.exec (fuel + cases.length + 12) + (Backends.lowerNativeSwitchBlock + Compiler.Proofs.YulGeneration.selectorExpr switchId cases defaultBody) + (some contract) + (nativeSwitchInitialOkState contract tx storage observableSlots) = + .ok final := by + rw [lowerNativeSwitchBlock_selectorExpr_eq_nativeSwitchParts] + apply exec_nativeSwitchPrefix_then_tail_fuel + exact exec_nativeSwitchTail_find_none_with_default_nonempty_fuel fuel + selector switchId cases defaultBody contract tx storage observableSlots + final hSelector hFind hSelectorRange hTagsRange hDefaultBody hNonempty + +theorem exec_lowerNativeSwitchBlock_selector_find_none_without_default_fuel + (fuel selector switchId : Nat) + (cases : List (Nat × List EvmYul.Yul.Ast.Stmt)) + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) + (hSelector : + selector = tx.functionSelector % Compiler.Constants.selectorModulus) + (hFind : cases.find? (fun entry => entry.1 == selector) = none) + (hSelectorRange : selector < EvmYul.UInt256.size) + (hTagsRange : + ∀ tag body, (tag, body) ∈ cases → tag < EvmYul.UInt256.size) : + EvmYul.Yul.exec (fuel + cases.length + 12) + (Backends.lowerNativeSwitchBlock + Compiler.Proofs.YulGeneration.selectorExpr switchId cases []) + (some contract) + (nativeSwitchInitialOkState contract tx storage observableSlots) = + .ok (nativeSwitchPrefixFinalState contract tx storage observableSlots + (Backends.nativeSwitchDiscrTempName switchId) + (Backends.nativeSwitchMatchedTempName switchId)) := by + rw [lowerNativeSwitchBlock_selectorExpr_eq_nativeSwitchParts] + apply exec_nativeSwitchPrefix_then_tail_fuel + exact exec_nativeSwitchTail_find_none_without_default_fuel fuel selector + switchId cases contract tx storage observableSlots hSelector hFind + hSelectorRange hTagsRange + +@[simp] theorem initialState_unbridgedEnvironmentDefaults + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) : + (initialState contract tx storage observableSlots).sharedState.executionEnv.header.baseFeePerGas = + 0 ∧ + (initialState contract tx storage observableSlots).sharedState.executionEnv.header.blobGasUsed = + (0 : UInt64) ∧ + (initialState contract tx storage observableSlots).sharedState.executionEnv.header.excessBlobGas = + (0 : UInt64) ∧ + (initialState contract tx storage observableSlots).sharedState.executionEnv.blobVersionedHashes = + [] ∧ + EvmYul.State.chainId + (initialState contract tx storage observableSlots).sharedState.toState = + EvmYul.UInt256.ofNat EvmYul.chainId := by + simp [initialState, EvmYul.Yul.State.sharedState, YulState.initial, toSharedState, + mkBlockHeader, EvmYul.State.chainId] + /-- Project the account storage for the current contract back to Verity's `Nat → Nat` storage view. -/ def projectStorageFromState (tx : YulTransaction) (state : EvmYul.Yul.State) : Nat → Nat := extractStorage state.sharedState (natToAddress tx.thisAddress) +/-- Projecting final native storage reads the current contract account storage + entry for the requested slot. -/ +@[simp] theorem projectStorageFromState_accountStorageSlot + (tx : YulTransaction) + (state : EvmYul.Yul.State) + (slot : Nat) + (account : EvmYul.Account .Yul) + (value : EvmYul.UInt256) + (hAccount : + state.sharedState.accountMap.find? (natToAddress tx.thisAddress) = + some account) + (hSlot : account.storage.find? (natToUInt256 slot) = some value) : + projectStorageFromState tx state slot = uint256ToNat value := by + simp [projectStorageFromState, extractStorage, hAccount, hSlot] + +/-- Projecting final native storage defaults to zero when the current contract + account has no native storage entry for the requested slot. -/ +@[simp] theorem projectStorageFromState_missingAccountStorageSlot + (tx : YulTransaction) + (state : EvmYul.Yul.State) + (slot : Nat) + (account : EvmYul.Account .Yul) + (hAccount : + state.sharedState.accountMap.find? (natToAddress tx.thisAddress) = + some account) + (hSlot : account.storage.find? (natToUInt256 slot) = none) : + projectStorageFromState tx state slot = 0 := by + simp [projectStorageFromState, extractStorage, hAccount, hSlot] + +/-- Projecting final native storage defaults to zero when the current contract + account is absent from the native account map. -/ +@[simp] theorem projectStorageFromState_missingAccount + (tx : YulTransaction) + (state : EvmYul.Yul.State) + (slot : Nat) + (hAccount : + state.sharedState.accountMap.find? (natToAddress tx.thisAddress) = + none) : + projectStorageFromState tx state slot = 0 := by + simp [projectStorageFromState, extractStorage, hAccount] + +/-- Native initial-state storage materialization agrees with Verity storage on + every explicit observable slot. Slots and values are interpreted in the + EVM word domain, so the result is modulo `UInt256.size`. -/ +theorem initialState_observableStorageSlot + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) + (slot : Nat) + (hSlot : slot ∈ observableSlots) + (hRange : ∀ s ∈ observableSlots, s < EvmYul.UInt256.size) : + projectStorageFromState tx + (initialState contract tx storage observableSlots) slot = + storage slot % EvmYul.UInt256.size := by + simp only [projectStorageFromState, extractStorage, initialState, + EvmYul.Yul.State.sharedState, YulState.initial, toSharedState] + rw [Batteries.RBMap.find?_insert_of_eq _ Std.ReflCmp.compare_self] + rw [Batteries.RBMap.find?_insert_of_eq _ Std.ReflCmp.compare_self] + simp only at * + have h := storageLookup_projectStorage storage observableSlots slot hSlot hRange + unfold storageLookup at h + generalize hfind : Batteries.RBMap.find? (projectStorage storage observableSlots) + (natToUInt256 slot) = found at h ⊢ + cases found <;> + simpa [uint256ToNat, EvmYul.UInt256.toNat] using h + +/-- Native initial-state storage materialization defaults omitted observable + pre-state slots to zero. The in-range hypotheses rule out modular aliasing + through the EVM word key used by the finite native storage map. -/ +theorem initialState_omittedStorageSlot + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) + (slot : Nat) + (hNotSlot : slot ∉ observableSlots) + (hRange : ∀ s ∈ observableSlots, s < EvmYul.UInt256.size) + (hSlotRange : slot < EvmYul.UInt256.size) : + projectStorageFromState tx + (initialState contract tx storage observableSlots) slot = 0 := by + simp only [projectStorageFromState, extractStorage, initialState, + EvmYul.Yul.State.sharedState, YulState.initial, toSharedState] + rw [Batteries.RBMap.find?_insert_of_eq _ Std.ReflCmp.compare_self] + rw [Batteries.RBMap.find?_insert_of_eq _ Std.ReflCmp.compare_self] + simp only + have h := foldl_insert_find_not_mem storage observableSlots slot hNotSlot + hRange hSlotRange (Batteries.RBMap.empty : EvmYul.Storage) + have hNone : + (projectStorage storage observableSlots).find? (natToUInt256 slot) = none := by + simpa [projectStorage] using h + rw [hNone] + /-- Decode one 32-byte big-endian word from an EVMYulLean byte array. -/ def byteArrayWord (bytes : ByteArray) (offset : Nat) : Nat := (List.range 32).foldl @@ -79,6 +3530,18 @@ def projectLogEntry (entry : EvmYul.LogEntry) : List Nat := def projectLogsFromState (state : EvmYul.Yul.State) : List (List Nat) := state.sharedState.substate.logSeries.toList.map projectLogEntry +@[simp] theorem projectLogEntry_topicsAndWordData + (entry : EvmYul.LogEntry) : + projectLogEntry entry = + entry.topics.toList.map uint256ToNat ++ byteArrayLogWords entry.data := by + rfl + +@[simp] theorem projectLogsFromState_logSeries + (state : EvmYul.Yul.State) : + projectLogsFromState state = + state.sharedState.substate.logSeries.toList.map projectLogEntry := by + rfl + /-- Project a native Yul halt produced by `return`/`stop` to Verity's single-word return observable. EVMYulLean represents `stop` as `YulHalt _ 0`; `return` goes through `H_return`, matching the proof oracle's 32-byte return case. -/ @@ -91,12 +3554,156 @@ def projectHaltReturn (state : EvmYul.Yul.State) (haltValue : EvmYul.Yul.Ast.Lit else some 0 +@[simp] theorem projectHaltReturn_stop + (state : EvmYul.Yul.State) : + projectHaltReturn state ⟨0⟩ = none := by + simp [projectHaltReturn] + +@[simp] theorem projectHaltReturn_32ByteReturn + (state : EvmYul.Yul.State) + (haltValue : EvmYul.Yul.Ast.Literal) + (hHalt : haltValue ≠ ⟨0⟩) + (hSize : state.sharedState.H_return.size = 32) : + projectHaltReturn state haltValue = + some (byteArrayWord state.sharedState.H_return 0) := by + simp [projectHaltReturn, hHalt, hSize] + +/-- Until wider returndata support lands, a non-`stop` halt with a native return + buffer whose size is not exactly one ABI word projects to the conservative + single-word fallback used by the current proof-side observable model. -/ +@[simp] theorem projectHaltReturn_non32ByteReturn + (state : EvmYul.Yul.State) + (haltValue : EvmYul.Yul.Ast.Literal) + (hHalt : haltValue ≠ ⟨0⟩) + (hSize : state.sharedState.H_return.size ≠ 32) : + projectHaltReturn state haltValue = some 0 := by + simp [projectHaltReturn, hHalt, hSize] + +/-- The dispatcher-block execution that `EvmYul.Yul.callDispatcher` performs + after its initial fuel check and empty-argument call-frame setup. + +Keeping this expression named lets the native/interpreter bridge target +statement execution of the lowered dispatcher body directly, instead of +re-opening the full `callDispatcher` wrapper at each EndToEnd proof site. -/ +def callDispatcherBlockResult + (fuel' : Nat) + (contract : EvmYul.Yul.Ast.YulContract) + (initial : EvmYul.Yul.State) : + Except EvmYul.Yul.Exception + (EvmYul.Yul.State × List EvmYul.Yul.Ast.Literal) := + let dispatcherDef := + EvmYul.Yul.Ast.FunctionDefinition.Def [] [] + [initial.executionEnv.code.dispatcher] + let callState := EvmYul.Yul.State.mkOk (initial.initcall dispatcherDef.params []) + match EvmYul.Yul.exec fuel' (.Block dispatcherDef.body) (some contract) callState with + | .error err => .error err + | .ok finalState => + let restored := finalState.reviveJump.overwrite? initial |>.setStore initial + .ok (restored, List.map finalState.lookup! dispatcherDef.rets) + +/-- Dispatcher-block execution specialized to the lowered contract dispatcher + rather than the state-installed dispatcher lookup. + +For states built by `initialState`, this is definitionally the next proof +target after `callDispatcherBlockResult`: native execution of the lowered +contract's dispatcher statement. -/ +def contractDispatcherBlockResult + (fuel' : Nat) + (contract : EvmYul.Yul.Ast.YulContract) + (initial : EvmYul.Yul.State) : + Except EvmYul.Yul.Exception + (EvmYul.Yul.State × List EvmYul.Yul.Ast.Literal) := + let dispatcherDef := + EvmYul.Yul.Ast.FunctionDefinition.Def [] [] [contract.dispatcher] + let callState := EvmYul.Yul.State.mkOk (initial.initcall dispatcherDef.params []) + match EvmYul.Yul.exec fuel' (.Block dispatcherDef.body) (some contract) callState with + | .error err => .error err + | .ok finalState => + let restored := finalState.reviveJump.overwrite? initial |>.setStore initial + .ok (restored, List.map finalState.lookup! dispatcherDef.rets) + +/-- Raw native execution of the lowered contract dispatcher block, before the + `callDispatcher`-style state restoration and return-list projection. -/ +def contractDispatcherExecResult + (fuel' : Nat) + (contract : EvmYul.Yul.Ast.YulContract) + (initial : EvmYul.Yul.State) : + Except EvmYul.Yul.Exception EvmYul.Yul.State := + let dispatcherDef := + EvmYul.Yul.Ast.FunctionDefinition.Def [] [] [contract.dispatcher] + let callState := EvmYul.Yul.State.mkOk (initial.initcall dispatcherDef.params []) + EvmYul.Yul.exec fuel' (.Block dispatcherDef.body) (some contract) callState + +/-- The projected dispatcher-block result is just raw lowered-dispatcher + execution followed by the same restoration/projection used by + `callDispatcher`. -/ +theorem contractDispatcherBlockResult_eq_execResult + (fuel' : Nat) + (contract : EvmYul.Yul.Ast.YulContract) + (initial : EvmYul.Yul.State) : + contractDispatcherBlockResult fuel' contract initial = + let dispatcherDef := + EvmYul.Yul.Ast.FunctionDefinition.Def [] [] [contract.dispatcher] + match contractDispatcherExecResult fuel' contract initial with + | .error err => .error err + | .ok finalState => + let restored := finalState.reviveJump.overwrite? initial |>.setStore initial + .ok (restored, List.map finalState.lookup! dispatcherDef.rets) := by + simp [contractDispatcherBlockResult, contractDispatcherExecResult] + +/-- `initialState` installs the lowered contract as the execution contract, so + the dispatcher-block target can be rewritten to the lowered contract's own + dispatcher. -/ +theorem callDispatcherBlockResult_initialState_eq_contractDispatcherBlockResult + (fuel' : Nat) + (contract : EvmYul.Yul.Ast.YulContract) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) : + callDispatcherBlockResult fuel' contract + (initialState contract tx storage observableSlots) = + contractDispatcherBlockResult fuel' contract + (initialState contract tx storage observableSlots) := by + have hcode : + (initialState contract tx storage observableSlots).executionEnv.code = + contract := by + simp [initialState, EvmYul.Yul.State.executionEnv] + simp [callDispatcherBlockResult, contractDispatcherBlockResult, hcode] + +@[simp] theorem callDispatcher_zero + (contract : EvmYul.Yul.Ast.YulContract) + (initial : EvmYul.Yul.State) : + EvmYul.Yul.callDispatcher 0 (some contract) initial = + .error EvmYul.Yul.Exception.OutOfFuel := by + simp [EvmYul.Yul.callDispatcher] + +/-- `callDispatcher` is exactly execution of the installed dispatcher block + once fuel and call-frame setup have been peeled away. -/ +theorem callDispatcher_succ_eq_callDispatcherBlockResult + (fuel' : Nat) + (contract : EvmYul.Yul.Ast.YulContract) + (initial : EvmYul.Yul.State) : + EvmYul.Yul.callDispatcher (Nat.succ fuel') (some contract) initial = + callDispatcherBlockResult fuel' contract initial := by + simp [EvmYul.Yul.callDispatcher, callDispatcherBlockResult] + cases + EvmYul.Yul.exec fuel' + (.Block + (EvmYul.Yul.Ast.FunctionDefinition.Def [] [] + [initial.executionEnv.code.dispatcher]).body) + (some contract) + (EvmYul.Yul.State.mkOk + (initial.initcall + (EvmYul.Yul.Ast.FunctionDefinition.Def [] [] + [initial.executionEnv.code.dispatcher]).params [])) <;> rfl + /-- Convert a native `callDispatcher` result to the current Verity observable result shape. Reverts and hard native errors conservatively roll storage back to the supplied initial storage function. -/ def projectResult (tx : YulTransaction) (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) (result : Except EvmYul.Yul.Exception (EvmYul.Yul.State × List EvmYul.Yul.Ast.Literal)) : @@ -108,20 +3715,495 @@ def projectResult returnValue := values.head?.map uint256ToNat finalStorage := finalStorage finalMappings := Compiler.Proofs.storageAsMappings finalStorage - events := projectLogsFromState state } + events := initialEvents ++ projectLogsFromState state } | .error (.YulHalt state value) => let finalStorage := projectStorageFromState tx state { success := true returnValue := projectHaltReturn state value finalStorage := finalStorage finalMappings := Compiler.Proofs.storageAsMappings finalStorage - events := projectLogsFromState state } + events := initialEvents ++ projectLogsFromState state } | .error _ => { success := false returnValue := none finalStorage := initialStorage finalMappings := Compiler.Proofs.storageAsMappings initialStorage - events := [] } + events := initialEvents } + +@[simp] theorem projectResult_ok + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (state : EvmYul.Yul.State) + (values : List EvmYul.Yul.Ast.Literal) : + projectResult tx initialStorage initialEvents (.ok (state, values)) = + { success := true + returnValue := values.head?.map uint256ToNat + finalStorage := projectStorageFromState tx state + finalMappings := + Compiler.Proofs.storageAsMappings (projectStorageFromState tx state) + events := initialEvents ++ projectLogsFromState state } := by + rfl + +@[simp] theorem projectResult_yulHalt + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (state : EvmYul.Yul.State) + (value : EvmYul.Yul.Ast.Literal) : + projectResult tx initialStorage initialEvents + (.error (.YulHalt state value)) = + { success := true + returnValue := projectHaltReturn state value + finalStorage := projectStorageFromState tx state + finalMappings := + Compiler.Proofs.storageAsMappings (projectStorageFromState tx state) + events := initialEvents ++ projectLogsFromState state } := by + rfl + +@[simp] theorem projectResult_ok_events + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (state : EvmYul.Yul.State) + (values : List EvmYul.Yul.Ast.Literal) : + (projectResult tx initialStorage initialEvents (.ok (state, values))).events = + initialEvents ++ projectLogsFromState state := by + rfl + +@[simp] theorem projectResult_ok_success + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (state : EvmYul.Yul.State) + (values : List EvmYul.Yul.Ast.Literal) : + (projectResult tx initialStorage initialEvents + (.ok (state, values))).success = true := by + rfl + +@[simp] theorem projectResult_ok_returnValue + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (state : EvmYul.Yul.State) + (values : List EvmYul.Yul.Ast.Literal) : + (projectResult tx initialStorage initialEvents + (.ok (state, values))).returnValue = + values.head?.map uint256ToNat := by + rfl + +@[simp] theorem projectResult_ok_finalMappings + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (state : EvmYul.Yul.State) + (values : List EvmYul.Yul.Ast.Literal) : + (projectResult tx initialStorage initialEvents + (.ok (state, values))).finalMappings = + Compiler.Proofs.storageAsMappings (projectStorageFromState tx state) := by + rfl + +@[simp] theorem projectResult_ok_finalStorageSlot + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (state : EvmYul.Yul.State) + (values : List EvmYul.Yul.Ast.Literal) + (slot : Nat) + (account : EvmYul.Account .Yul) + (value : EvmYul.UInt256) + (hAccount : + state.sharedState.accountMap.find? (natToAddress tx.thisAddress) = + some account) + (hSlot : account.storage.find? (natToUInt256 slot) = some value) : + (projectResult tx initialStorage initialEvents + (.ok (state, values))).finalStorage slot = uint256ToNat value := by + simp [projectResult, projectStorageFromState_accountStorageSlot, + hAccount, hSlot] + +@[simp] theorem projectResult_ok_missingFinalStorageAccountSlot + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (state : EvmYul.Yul.State) + (values : List EvmYul.Yul.Ast.Literal) + (slot : Nat) + (hAccount : + state.sharedState.accountMap.find? (natToAddress tx.thisAddress) = + none) : + (projectResult tx initialStorage initialEvents + (.ok (state, values))).finalStorage slot = 0 := by + simp [projectResult, projectStorageFromState_missingAccount, hAccount] + +@[simp] theorem projectResult_ok_missingFinalStorageSlot + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (state : EvmYul.Yul.State) + (values : List EvmYul.Yul.Ast.Literal) + (slot : Nat) + (account : EvmYul.Account .Yul) + (hAccount : + state.sharedState.accountMap.find? (natToAddress tx.thisAddress) = + some account) + (hSlot : account.storage.find? (natToUInt256 slot) = none) : + (projectResult tx initialStorage initialEvents + (.ok (state, values))).finalStorage slot = 0 := by + simp [projectResult, projectStorageFromState_missingAccountStorageSlot, + hAccount, hSlot] + +@[simp] theorem projectResult_yulHalt_events + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (state : EvmYul.Yul.State) + (value : EvmYul.Yul.Ast.Literal) : + (projectResult tx initialStorage initialEvents + (.error (.YulHalt state value))).events = + initialEvents ++ projectLogsFromState state := by + rfl + +@[simp] theorem projectResult_yulHalt_success + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (state : EvmYul.Yul.State) + (value : EvmYul.Yul.Ast.Literal) : + (projectResult tx initialStorage initialEvents + (.error (.YulHalt state value))).success = true := by + rfl + +@[simp] theorem projectResult_yulHalt_returnValue + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (state : EvmYul.Yul.State) + (value : EvmYul.Yul.Ast.Literal) : + (projectResult tx initialStorage initialEvents + (.error (.YulHalt state value))).returnValue = + projectHaltReturn state value := by + rfl + +@[simp] theorem projectResult_yulHalt_finalMappings + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (state : EvmYul.Yul.State) + (value : EvmYul.Yul.Ast.Literal) : + (projectResult tx initialStorage initialEvents + (.error (.YulHalt state value))).finalMappings = + Compiler.Proofs.storageAsMappings (projectStorageFromState tx state) := by + rfl + +@[simp] theorem projectResult_yulHalt_finalStorageSlot + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (state : EvmYul.Yul.State) + (value : EvmYul.Yul.Ast.Literal) + (slot : Nat) + (account : EvmYul.Account .Yul) + (slotValue : EvmYul.UInt256) + (hAccount : + state.sharedState.accountMap.find? (natToAddress tx.thisAddress) = + some account) + (hSlot : account.storage.find? (natToUInt256 slot) = some slotValue) : + (projectResult tx initialStorage initialEvents + (.error (.YulHalt state value))).finalStorage slot = + uint256ToNat slotValue := by + simp [projectResult, projectStorageFromState_accountStorageSlot, + hAccount, hSlot] + +@[simp] theorem projectResult_yulHalt_missingFinalStorageAccountSlot + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (state : EvmYul.Yul.State) + (value : EvmYul.Yul.Ast.Literal) + (slot : Nat) + (hAccount : + state.sharedState.accountMap.find? (natToAddress tx.thisAddress) = + none) : + (projectResult tx initialStorage initialEvents + (.error (.YulHalt state value))).finalStorage slot = 0 := by + simp [projectResult, projectStorageFromState_missingAccount, hAccount] + +@[simp] theorem projectResult_yulHalt_missingFinalStorageSlot + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (state : EvmYul.Yul.State) + (value : EvmYul.Yul.Ast.Literal) + (slot : Nat) + (account : EvmYul.Account .Yul) + (hAccount : + state.sharedState.accountMap.find? (natToAddress tx.thisAddress) = + some account) + (hSlot : account.storage.find? (natToUInt256 slot) = none) : + (projectResult tx initialStorage initialEvents + (.error (.YulHalt state value))).finalStorage slot = 0 := by + simp [projectResult, projectStorageFromState_missingAccountStorageSlot, + hAccount, hSlot] + +@[simp] theorem projectResult_stop + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (state : EvmYul.Yul.State) : + projectResult tx initialStorage initialEvents + (.error (.YulHalt state ⟨0⟩)) = + { success := true + returnValue := none + finalStorage := projectStorageFromState tx state + finalMappings := + Compiler.Proofs.storageAsMappings (projectStorageFromState tx state) + events := initialEvents ++ projectLogsFromState state } := by + simp [projectResult] + +@[simp] theorem projectResult_32ByteReturn + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (state : EvmYul.Yul.State) + (value : EvmYul.Yul.Ast.Literal) + (hHalt : value ≠ ⟨0⟩) + (hSize : state.sharedState.H_return.size = 32) : + projectResult tx initialStorage initialEvents + (.error (.YulHalt state value)) = + { success := true + returnValue := some (byteArrayWord state.sharedState.H_return 0) + finalStorage := projectStorageFromState tx state + finalMappings := + Compiler.Proofs.storageAsMappings (projectStorageFromState tx state) + events := initialEvents ++ projectLogsFromState state } := by + simp [projectResult, hHalt, hSize] + +@[simp] theorem projectResult_non32ByteReturn + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (state : EvmYul.Yul.State) + (value : EvmYul.Yul.Ast.Literal) + (hHalt : value ≠ ⟨0⟩) + (hSize : state.sharedState.H_return.size ≠ 32) : + projectResult tx initialStorage initialEvents + (.error (.YulHalt state value)) = + { success := true + returnValue := some 0 + finalStorage := projectStorageFromState tx state + finalMappings := + Compiler.Proofs.storageAsMappings (projectStorageFromState tx state) + events := initialEvents ++ projectLogsFromState state } := by + simp [projectResult, hHalt, hSize] + +@[simp] theorem projectResult_revert + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) : + projectResult tx initialStorage initialEvents + (.error EvmYul.Yul.Exception.Revert) = + { success := false + returnValue := none + finalStorage := initialStorage + finalMappings := Compiler.Proofs.storageAsMappings initialStorage + events := initialEvents } := by + rfl + +@[simp] theorem projectResult_revert_events + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) : + (projectResult tx initialStorage initialEvents + (.error EvmYul.Yul.Exception.Revert)).events = + initialEvents := by + rfl + +@[simp] theorem projectResult_revert_success + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) : + (projectResult tx initialStorage initialEvents + (.error EvmYul.Yul.Exception.Revert)).success = false := by + rfl + +@[simp] theorem projectResult_revert_returnValue + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) : + (projectResult tx initialStorage initialEvents + (.error EvmYul.Yul.Exception.Revert)).returnValue = none := by + rfl + +@[simp] theorem projectResult_revert_finalMappings + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) : + (projectResult tx initialStorage initialEvents + (.error EvmYul.Yul.Exception.Revert)).finalMappings = + Compiler.Proofs.storageAsMappings initialStorage := by + rfl + +@[simp] theorem projectResult_revert_finalStorageSlot + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (slot : Nat) : + (projectResult tx initialStorage initialEvents + (.error EvmYul.Yul.Exception.Revert)).finalStorage slot = + initialStorage slot := by + rfl + +@[simp] theorem projectResult_hardError + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (err : EvmYul.Yul.Exception) + (hNotHalt : ∀ state value, err ≠ EvmYul.Yul.Exception.YulHalt state value) : + projectResult tx initialStorage initialEvents (.error err) = + { success := false + returnValue := none + finalStorage := initialStorage + finalMappings := Compiler.Proofs.storageAsMappings initialStorage + events := initialEvents } := by + cases err with + | YulHalt state value => + exact False.elim (hNotHalt state value rfl) + | InvalidArguments => rfl + | NotEncodableRLP => rfl + | InvalidInstruction => rfl + | OutOfFuel => rfl + | StaticModeViolation => rfl + | MissingContract s => rfl + | MissingContractFunction s => rfl + | InvalidExpression => rfl + | YulEXTCODESIZENotImplemented => rfl + | Revert => rfl + +@[simp] theorem projectResult_hardError_success + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (err : EvmYul.Yul.Exception) + (hNotHalt : ∀ state value, err ≠ EvmYul.Yul.Exception.YulHalt state value) : + (projectResult tx initialStorage initialEvents (.error err)).success = false := by + cases err with + | YulHalt state value => + exact False.elim (hNotHalt state value rfl) + | InvalidArguments => rfl + | NotEncodableRLP => rfl + | InvalidInstruction => rfl + | OutOfFuel => rfl + | StaticModeViolation => rfl + | MissingContract s => rfl + | MissingContractFunction s => rfl + | InvalidExpression => rfl + | YulEXTCODESIZENotImplemented => rfl + | Revert => rfl + +@[simp] theorem projectResult_hardError_returnValue + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (err : EvmYul.Yul.Exception) + (hNotHalt : ∀ state value, err ≠ EvmYul.Yul.Exception.YulHalt state value) : + (projectResult tx initialStorage initialEvents (.error err)).returnValue = none := by + cases err with + | YulHalt state value => + exact False.elim (hNotHalt state value rfl) + | InvalidArguments => rfl + | NotEncodableRLP => rfl + | InvalidInstruction => rfl + | OutOfFuel => rfl + | StaticModeViolation => rfl + | MissingContract s => rfl + | MissingContractFunction s => rfl + | InvalidExpression => rfl + | YulEXTCODESIZENotImplemented => rfl + | Revert => rfl + +@[simp] theorem projectResult_hardError_finalStorageSlot + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (err : EvmYul.Yul.Exception) + (slot : Nat) + (hNotHalt : ∀ state value, err ≠ EvmYul.Yul.Exception.YulHalt state value) : + (projectResult tx initialStorage initialEvents (.error err)).finalStorage slot = + initialStorage slot := by + cases err with + | YulHalt state value => + exact False.elim (hNotHalt state value rfl) + | InvalidArguments => rfl + | NotEncodableRLP => rfl + | InvalidInstruction => rfl + | OutOfFuel => rfl + | StaticModeViolation => rfl + | MissingContract s => rfl + | MissingContractFunction s => rfl + | InvalidExpression => rfl + | YulEXTCODESIZENotImplemented => rfl + | Revert => rfl + +@[simp] theorem projectResult_hardError_finalMappings + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (err : EvmYul.Yul.Exception) + (hNotHalt : ∀ state value, err ≠ EvmYul.Yul.Exception.YulHalt state value) : + (projectResult tx initialStorage initialEvents (.error err)).finalMappings = + Compiler.Proofs.storageAsMappings initialStorage := by + cases err with + | YulHalt state value => + exact False.elim (hNotHalt state value rfl) + | InvalidArguments => rfl + | NotEncodableRLP => rfl + | InvalidInstruction => rfl + | OutOfFuel => rfl + | StaticModeViolation => rfl + | MissingContract s => rfl + | MissingContractFunction s => rfl + | InvalidExpression => rfl + | YulEXTCODESIZENotImplemented => rfl + | Revert => rfl + +@[simp] theorem projectResult_hardError_events + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (err : EvmYul.Yul.Exception) + (hNotHalt : ∀ state value, err ≠ EvmYul.Yul.Exception.YulHalt state value) : + (projectResult tx initialStorage initialEvents (.error err)).events = + initialEvents := by + cases err with + | YulHalt state value => + exact False.elim (hNotHalt state value rfl) + | InvalidArguments => rfl + | NotEncodableRLP => rfl + | InvalidInstruction => rfl + | OutOfFuel => rfl + | StaticModeViolation => rfl + | MissingContract s => rfl + | MissingContractFunction s => rfl + | InvalidExpression => rfl + | YulEXTCODESIZENotImplemented => rfl + | Revert => rfl + +@[simp] theorem projectResult_finalMappings + (tx : YulTransaction) + (initialStorage : Nat → Nat) + (initialEvents : List (List Nat)) + (result : + Except EvmYul.Yul.Exception + (EvmYul.Yul.State × List EvmYul.Yul.Ast.Literal)) : + (projectResult tx initialStorage initialEvents result).finalMappings = + Compiler.Proofs.storageAsMappings + (projectResult tx initialStorage initialEvents result).finalStorage := by + cases result with + | ok value => + cases value with + | mk state values => rfl + | error err => + cases err <;> rfl /-- Lower and execute Verity runtime Yul through EVMYulLean's native dispatcher. -/ @@ -130,11 +4212,126 @@ def interpretRuntimeNative (runtimeCode : List YulStmt) (tx : YulTransaction) (storage : Nat → Nat) - (observableSlots : List Nat := []) : + (observableSlots : List Nat) + (events : List (List Nat) := []) : Except AdapterError YulResult := do let contract ← lowerRuntimeContractNative runtimeCode + validateNativeRuntimeEnvironment runtimeCode tx let initial := initialState contract tx storage observableSlots let result := EvmYul.Yul.callDispatcher fuel (some contract) initial - pure (projectResult tx storage result) + pure (projectResult tx storage events result) + +@[simp] theorem interpretRuntimeNative_loweringError + (fuel : Nat) + (runtimeCode : List YulStmt) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) + (events : List (List Nat)) + (err : AdapterError) + (hLower : lowerRuntimeContractNative runtimeCode = .error err) : + interpretRuntimeNative fuel runtimeCode tx storage observableSlots events = + .error err := by + rw [interpretRuntimeNative, hLower] + rfl + +@[simp] theorem interpretRuntimeNative_eq_callDispatcher_of_lowerRuntimeContractNative + (fuel : Nat) + (runtimeCode : List YulStmt) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) + (events : List (List Nat)) + (contract : EvmYul.Yul.Ast.YulContract) + (hLower : lowerRuntimeContractNative runtimeCode = .ok contract) + (hEnv : validateNativeRuntimeEnvironment runtimeCode tx = .ok ()) : + interpretRuntimeNative fuel runtimeCode tx storage observableSlots events = + .ok (projectResult tx storage events + (EvmYul.Yul.callDispatcher fuel (some contract) + (initialState contract tx storage observableSlots))) := by + rw [interpretRuntimeNative, hLower, hEnv] + rfl + +@[simp] theorem interpretRuntimeNative_environmentError + (fuel : Nat) + (runtimeCode : List YulStmt) + (tx : YulTransaction) + (storage : Nat → Nat) + (observableSlots : List Nat) + (events : List (List Nat)) + (contract : EvmYul.Yul.Ast.YulContract) + (err : AdapterError) + (hLower : lowerRuntimeContractNative runtimeCode = .ok contract) + (hEnv : validateNativeRuntimeEnvironment runtimeCode tx = .error err) : + interpretRuntimeNative fuel runtimeCode tx storage observableSlots events = + .error err := by + rw [interpretRuntimeNative, hLower, hEnv] + rfl + +/-- Native EVMYulLean execution target for emitted IR-contract runtime Yul. + +This is the executable target that #1737 will promote into the public theorem +path once the state/result bridge lemmas are proved. It intentionally returns +`Except AdapterError YulResult` today because native lowering can still fail +closed for duplicate helper definitions or unsupported runtime shapes. + +The observable slot set is explicit because the native state bridge only +materializes pre-state storage for the listed slots. Passing `[]` is valid for +storage-free smoke tests, but storage-reading callers must provide every slot +they intend the native EVMYulLean state to observe. +-/ +def interpretIRRuntimeNative + (fuel : Nat) + (contract : Compiler.IRContract) + (tx : Compiler.Proofs.IRGeneration.IRTransaction) + (state : Compiler.Proofs.IRGeneration.IRState) + (observableSlots : List Nat) : + Except AdapterError YulResult := + interpretRuntimeNative fuel (Compiler.emitYul contract).runtimeCode + (YulTransaction.ofIR tx) state.storage observableSlots state.events + +@[simp] theorem interpretIRRuntimeNative_eq_interpretRuntimeNative + (fuel : Nat) + (contract : Compiler.IRContract) + (tx : Compiler.Proofs.IRGeneration.IRTransaction) + (state : Compiler.Proofs.IRGeneration.IRState) + (observableSlots : List Nat) : + interpretIRRuntimeNative fuel contract tx state observableSlots = + interpretRuntimeNative fuel (Compiler.emitYul contract).runtimeCode + (YulTransaction.ofIR tx) state.storage observableSlots state.events := by + rfl + +@[simp] theorem interpretIRRuntimeNative_loweringError + (fuel : Nat) + (contract : Compiler.IRContract) + (tx : Compiler.Proofs.IRGeneration.IRTransaction) + (state : Compiler.Proofs.IRGeneration.IRState) + (observableSlots : List Nat) + (err : AdapterError) + (hLower : lowerRuntimeContractNative (Compiler.emitYul contract).runtimeCode = + .error err) : + interpretIRRuntimeNative fuel contract tx state observableSlots = .error err := by + rw [interpretIRRuntimeNative, interpretRuntimeNative, hLower] + rfl + +@[simp] theorem interpretIRRuntimeNative_eq_callDispatcher_of_lowerRuntimeContractNative + (fuel : Nat) + (irContract : Compiler.IRContract) + (tx : Compiler.Proofs.IRGeneration.IRTransaction) + (state : Compiler.Proofs.IRGeneration.IRState) + (observableSlots : List Nat) + (nativeContract : EvmYul.Yul.Ast.YulContract) + (hLower : lowerRuntimeContractNative (Compiler.emitYul irContract).runtimeCode = + .ok nativeContract) + (hEnv : + validateNativeRuntimeEnvironment (Compiler.emitYul irContract).runtimeCode + (YulTransaction.ofIR tx) = .ok ()) : + interpretIRRuntimeNative fuel irContract tx state observableSlots = + .ok (projectResult (YulTransaction.ofIR tx) state.storage state.events + (EvmYul.Yul.callDispatcher fuel (some nativeContract) + (initialState nativeContract (YulTransaction.ofIR tx) state.storage + observableSlots))) := by + rw [interpretIRRuntimeNative, interpretRuntimeNative, hLower, hEnv] + rfl end Compiler.Proofs.YulGeneration.Backends.Native diff --git a/Compiler/Proofs/YulGeneration/Backends/EvmYulLeanNativeSmokeTest.lean b/Compiler/Proofs/YulGeneration/Backends/EvmYulLeanNativeSmokeTest.lean index a69483b31..e4b929939 100644 --- a/Compiler/Proofs/YulGeneration/Backends/EvmYulLeanNativeSmokeTest.lean +++ b/Compiler/Proofs/YulGeneration/Backends/EvmYulLeanNativeSmokeTest.lean @@ -26,11 +26,750 @@ private def sampleTx : Compiler.Proofs.YulGeneration.YulTransaction := { sender := 0xCAFE msgValue := 7 thisAddress := 0x1234 + blockTimestamp := 12345 + blockNumber := 678 + chainId := 31337 + blobBaseFee := 19 functionSelector := 0x01020304 args := [41] } private def zeroStorage : Nat → Nat := fun _ => 0 +private def seededStorage : Nat → Nat := fun slot => + if slot = 7 then 77 else 0 + +private def wordByteArray (value : Nat) : ByteArray := + ByteArray.ofFn fun i : Fin 32 => + if i.1 = 31 then UInt8.ofNat value else UInt8.ofNat 0 + +private def sampleLogEntry (topics : List Nat) (dataWord : Nat) : EvmYul.LogEntry := + { address := StateBridge.natToAddress sampleTx.thisAddress + topics := topics.toArray.map EvmYul.UInt256.ofNat + data := wordByteArray dataWord } + +private def stateWithLogEntries (entries : List EvmYul.LogEntry) : EvmYul.Yul.State := + let shared := StateBridge.toSharedState + (Compiler.Proofs.YulGeneration.YulState.initial sampleTx zeroStorage) [] + .Ok { shared with substate := { shared.substate with logSeries := entries.toArray } } ∅ + +private def stateWithStorageLogReturn + (storage : Nat → Nat) (observableSlots : List Nat) + (entries : List EvmYul.LogEntry) (returnWord : Nat) : EvmYul.Yul.State := + let shared := StateBridge.toSharedState + (Compiler.Proofs.YulGeneration.YulState.initial sampleTx storage) observableSlots + .Ok + { shared with + substate := { shared.substate with logSeries := entries.toArray } + H_return := wordByteArray returnWord } + ∅ + +private def nativeStoresBuiltinWithTx + (builtin : String) + (slot expected : Nat) + (tx : Compiler.Proofs.YulGeneration.YulTransaction) : + Bool := + match Native.interpretRuntimeNative 128 [ + .let_ "v" (.call builtin []), + .expr (.call "sstore" [.lit slot, .ident "v"]) + ] tx zeroStorage [slot] with + | .ok result => result.success && result.finalStorage slot == expected + | .error _ => false + +private def nativeStoresBuiltin (builtin : String) (slot expected : Nat) : Bool := + nativeStoresBuiltinWithTx builtin slot expected sampleTx + +private def nativeRejectsUnsupportedBlobBaseFee : Bool := + match Native.interpretRuntimeNative 128 [ + .let_ "v" (.call "blobbasefee" []), + .expr (.call "sstore" [.lit 16, .ident "v"]) + ] sampleTx zeroStorage [16] with + | .error _ => true + | .ok _ => false + +private def nativeRejectsUnsupportedChainId : Bool := + match Native.interpretRuntimeNative 128 [ + .let_ "v" (.call "chainid" []), + .expr (.call "sstore" [.lit 15, .ident "v"]) + ] sampleTx zeroStorage [15] with + | .error _ => true + | .ok _ => false + +private def nativeRejectsUnsupportedHeaderBuiltin (builtin : String) : Bool := + match Native.interpretRuntimeNative 128 [ + .let_ "v" (.call builtin []), + .expr (.call "sstore" [.lit 17, .ident "v"]) + ] sampleTx zeroStorage [17] with + | .error _ => true + | .ok _ => false + +private def runtimeWithUnselectedUnsupportedEnvironmentBuiltin : List YulStmt := + let selectorExpr := + YulExpr.call "shr" [ + YulExpr.lit Compiler.Constants.selectorShift, + YulExpr.call "calldataload" [YulExpr.lit 0] + ] + let runtimeCode := [ + YulStmt.funcDef "unusedEnvHelper" [] [] [ + .let_ "v" (.call "chainid" []), + .expr (.call "sstore" [.lit 15, .ident "v"]) + ], + YulStmt.block [ + .let_ "__has_selector" + (.call "iszero" [.call "lt" [.call "calldatasize" [], .lit 4]]), + .if_ (.call "iszero" [.ident "__has_selector"]) [], + .if_ (.ident "__has_selector") [ + .switch selectorExpr [ + (sampleTx.functionSelector, [ + .expr (.call "sstore" [.lit 1, .lit 7]) + ]) + ] (some []) + ] + ] + ] + runtimeCode + +private def nativeAllowsUnselectedUnsupportedEnvironmentBuiltin : Bool := + match Native.validateNativeRuntimeEnvironment + runtimeWithUnselectedUnsupportedEnvironmentBuiltin sampleTx with + | .ok () => true + | .error _ => false + +private def runtimeWithUserFunctionNamedChainid : List YulStmt := [ + .funcDef "chainid" [] ["ret"] [ + .assign "ret" (.lit 7) + ], + .let_ "v" (.call "chainid" []), + .expr (.call "sstore" [.lit 15, .ident "v"]) +] + +private def nativeAllowsUserFunctionNamedChainid : Bool := + match Native.validateNativeRuntimeEnvironment + runtimeWithUserFunctionNamedChainid sampleTx with + | .ok () => true + | .error _ => false + +private def referenceRuntimeWithFuel + (fuel : Nat) (stmts : List YulStmt) (tx : Compiler.Proofs.YulGeneration.YulTransaction) + (storage : Nat → Nat) (events : List (List Nat)) : + Compiler.Proofs.YulGeneration.YulResult := + let initialState := Compiler.Proofs.YulGeneration.YulState.initial tx storage events + match Compiler.Proofs.YulGeneration.execYulFuel fuel initialState (.stmts stmts) with + | .continue s => + { success := true + returnValue := s.returnValue + finalStorage := s.storage + finalMappings := Compiler.Proofs.storageAsMappings s.storage + events := s.events } + | .return v s => + { success := true + returnValue := some v + finalStorage := s.storage + finalMappings := Compiler.Proofs.storageAsMappings s.storage + events := s.events } + | .stop s => + { success := true + returnValue := none + finalStorage := s.storage + finalMappings := Compiler.Proofs.storageAsMappings s.storage + events := s.events } + | .revert _ => + { success := false + returnValue := none + finalStorage := storage + finalMappings := Compiler.Proofs.storageAsMappings storage + events := events } + +private def resultsMatchOnSlots + (slots : List Nat) + (nativeResult referenceResult : Compiler.Proofs.YulGeneration.YulResult) : Bool := + nativeResult.success == referenceResult.success && + nativeResult.returnValue == referenceResult.returnValue && + nativeResult.events == referenceResult.events && + slots.all (fun slot => nativeResult.finalStorage slot == referenceResult.finalStorage slot) + +private def nativeMatchesReferenceRuntime + (stmts : List YulStmt) (observableSlots compareSlots : List Nat) : Bool := + match Native.interpretRuntimeNative 128 stmts sampleTx zeroStorage observableSlots with + | .ok nativeResult => + resultsMatchOnSlots compareSlots nativeResult + (referenceRuntimeWithFuel 128 stmts sampleTx zeroStorage []) + | .error _ => false + +private def nativeEnvironmentMatchesReferenceRuntime : Bool := + nativeMatchesReferenceRuntime [ + .expr (.call "sstore" [.lit 8, .call "callvalue" []]), + .expr (.call "sstore" [.lit 9, .call "timestamp" []]), + .expr (.call "sstore" [.lit 10, .call "number" []]), + .expr (.call "sstore" [.lit 12, .call "caller" []]), + .expr (.call "sstore" [.lit 13, .call "address" []]), + .expr (.call "sstore" [.lit 14, .call "calldatasize" []]) + ] [8, 9, 10, 12, 13, 14] [8, 9, 10, 12, 13, 14] + +private def nativeCopiesSloadToSlot + (observableSlots : List Nat) (expected : Nat) : Bool := + match Native.interpretRuntimeNative 128 [ + .expr (.call "sstore" [.lit 8, .call "sload" [.lit 7]]) + ] sampleTx seededStorage observableSlots with + | .ok result => result.success && result.finalStorage 8 == expected + | .error _ => false + +private def nativeCopiesTransientLoadToStorage : Bool := + match Native.interpretRuntimeNative 128 [ + .expr (.call "tstore" [.lit 3, .lit 88]), + .expr (.call "sstore" [.lit 9, .call "tload" [.lit 3]]) + ] sampleTx zeroStorage [9] with + | .ok result => result.success && result.finalStorage 9 == 88 + | .error _ => false + +private def nativeInitialStateInstallsContractAndStorage : Bool := + let contract : EvmYul.Yul.Ast.YulContract := + { dispatcher := .Block [] + functions := ∅ } + let addr := StateBridge.natToAddress sampleTx.thisAddress + match Native.initialState contract sampleTx seededStorage [7] with + | .Ok shared _ => + shared.executionEnv.code == contract && + shared.executionEnv.codeOwner == addr && + shared.executionEnv.perm && + (match shared.accountMap.find? addr with + | some account => + account.code == contract && + StateBridge.storageLookup account.storage + (StateBridge.natToUInt256 7) == EvmYul.UInt256.ofNat 77 + | none => false) + | _ => false + +private def nativeInitialStateOmittedSlotDefaultsToZero : Bool := + let contract : EvmYul.Yul.Ast.YulContract := + { dispatcher := .Block [] + functions := ∅ } + let addr := StateBridge.natToAddress sampleTx.thisAddress + match Native.initialState contract sampleTx seededStorage [8] with + | .Ok shared _ => + match shared.accountMap.find? addr with + | some account => + StateBridge.storageLookup account.storage + (StateBridge.natToUInt256 7) == EvmYul.UInt256.ofNat 0 + | none => false + | _ => false + +private def nativeInitialStatePinsTransactionEnvironment : Bool := + let contract : EvmYul.Yul.Ast.YulContract := + { dispatcher := .Block [] + functions := ∅ } + match Native.initialState contract sampleTx zeroStorage [] with + | .Ok shared _ => + shared.executionEnv.source == StateBridge.natToAddress sampleTx.sender && + shared.executionEnv.sender == StateBridge.natToAddress sampleTx.sender && + shared.executionEnv.codeOwner == StateBridge.natToAddress sampleTx.thisAddress && + shared.executionEnv.weiValue == EvmYul.UInt256.ofNat sampleTx.msgValue && + shared.executionEnv.header.timestamp == sampleTx.blockTimestamp && + shared.executionEnv.header.number == sampleTx.blockNumber && + shared.executionEnv.calldata.size == 36 && + shared.executionEnv.calldata.get? 0 == some (UInt8.ofNat 0x01) && + shared.executionEnv.calldata.get? 1 == some (UInt8.ofNat 0x02) && + shared.executionEnv.calldata.get? 2 == some (UInt8.ofNat 0x03) && + shared.executionEnv.calldata.get? 3 == some (UInt8.ofNat 0x04) && + Native.byteArrayWord shared.executionEnv.calldata 4 == 41 + | _ => false + +private def nativeStopCommitsStorageAndPreservesEvents : Bool := + match Native.interpretRuntimeNative 128 [ + .expr (.call "sstore" [.lit 7, .lit 99]), + .expr (.call "stop" []) + ] sampleTx seededStorage [7] [[1, 2, 3]] with + | .ok result => + result.success && + result.returnValue.isNone && + result.finalStorage 7 == 99 && + result.events == [[1, 2, 3]] + | .error _ => false + +private def nativeReturnHaltProjectsStorageReturnAndEvents : Bool := + let finalStorage : Nat → Nat := fun slot => if slot = 7 then 99 else 0 + let result := + Native.projectResult sampleTx seededStorage [[1, 2, 3]] + (.error (.YulHalt + (stateWithStorageLogReturn finalStorage [7] [sampleLogEntry [5] 88] 44) + (EvmYul.UInt256.ofNat 1))) + result.success && + result.returnValue == some 44 && + result.finalStorage 7 == 99 && + result.events == [[1, 2, 3], [5, 88]] + +private def nativeValueResultProjectsStorageReturnAndEvents : Bool := + let finalStorage : Nat → Nat := fun slot => if slot = 7 then 99 else 0 + let result := + Native.projectResult sampleTx seededStorage [[1, 2, 3]] + (.ok + (stateWithStorageLogReturn finalStorage [7] [sampleLogEntry [5] 88] 0, + [EvmYul.UInt256.ofNat 44])) + result.success && + result.returnValue == some 44 && + result.finalStorage 7 == 99 && + result.events == [[1, 2, 3], [5, 88]] + +private def nativeHardErrorRollsBackStorageAndEvents : Bool := + let result := + Native.projectResult sampleTx + (fun slot => if slot = 7 then 5 else 0) + [[1, 2, 3]] + (.error EvmYul.Yul.Exception.OutOfFuel) + !result.success && + result.returnValue.isNone && + result.finalStorage 7 == 5 && + result.events == [[1, 2, 3]] + +private def nativeRevertErrorRollsBackStorageAndEvents : Bool := + let result := + Native.projectResult sampleTx + (fun slot => if slot = 7 then 5 else 0) + [[1, 2, 3]] + (.error EvmYul.Yul.Exception.Revert) + !result.success && + result.returnValue.isNone && + result.finalStorage 7 == 5 && + result.events == [[1, 2, 3]] + +private def dispatchSmokeContract : Compiler.IRContract := + { name := "NativeDispatchSmoke" + deploy := [] + functions := [ + { name := "left" + selector := 0x11111111 + params := [] + ret := .unit + body := [ + .expr (.call "sstore" [.lit 11, .lit 101]) + ] }, + { name := "right" + selector := 0x22222222 + params := [] + ret := .unit + body := [ + .expr (.call "sstore" [.lit 11, .lit 202]) + ] } + ] + usesMapping := false } + +private def returnDispatchSmokeContract : Compiler.IRContract := + { name := "NativeReturnDispatchSmoke" + deploy := [] + functions := [ + { name := "answer" + selector := 0x33333333 + params := [] + ret := .uint256 + body := [ + .expr (.call "mstore" [.lit 0, .lit 42]), + .expr (.call "return" [.lit 0, .lit 32]) + ] } + ] + usesMapping := false } + +private def sampleIRTx : Compiler.Proofs.IRGeneration.IRTransaction := + { sender := sampleTx.sender + msgValue := sampleTx.msgValue + thisAddress := sampleTx.thisAddress + blockTimestamp := sampleTx.blockTimestamp + blockNumber := sampleTx.blockNumber + chainId := sampleTx.chainId + blobBaseFee := sampleTx.blobBaseFee + functionSelector := 0x11111111 + args := [] } + +private def sampleIRState : Compiler.Proofs.IRGeneration.IRState := + { vars := [] + storage := seededStorage + transientStorage := fun _ => 0 + memory := fun _ => 0 + calldata := [] + returnValue := none + sender := 0 + msgValue := 0 + thisAddress := 0 + blockTimestamp := 0 + blockNumber := 0 + chainId := 0 + blobBaseFee := 0 + selector := 0 + events := [[9, 9]] } + +private def duplicateHelperIRContract : Compiler.IRContract := + { name := "DuplicateHelperIR" + deploy := [] + functions := [] + internalFunctions := [ + .funcDef "dup" [] [] [], + .funcDef "dup" [] [] [] + ] + usesMapping := false } + +private def storageReadIRContract : Compiler.IRContract := + { name := "NativeStorageReadSmoke" + deploy := [] + functions := [ + { name := "copySlot" + selector := 0x44444444 + params := [] + ret := .unit + body := [ + .expr (.call "sstore" [.lit 8, .call "sload" [.lit 7]]) + ] } + ] + usesMapping := false } + +private def storageReadIRTx : Compiler.Proofs.IRGeneration.IRTransaction := + { sampleIRTx with functionSelector := 0x44444444 } + +private def calldataBridgePinsSelectorAndFirstArg : Bool := + let bytes := StateBridge.calldataToByteArray 0x11223344 [42] + bytes.get? 0 == some (UInt8.ofNat 0x11) && + bytes.get? 1 == some (UInt8.ofNat 0x22) && + bytes.get? 2 == some (UInt8.ofNat 0x33) && + bytes.get? 3 == some (UInt8.ofNat 0x44) && + Native.byteArrayWord bytes 4 == 42 + +private def nativeAssignRebindsExistingLocal : Bool := + match runNativeProgram [ + .let_ "x" (.lit 1), + .assign "x" (.lit 2), + .let_ "y" (.ident "x") + ] with + | some state => varIs "x" 2 state && varIs "y" 2 state + | none => false + +private partial def nativeStmtContainsSwitch : EvmYul.Yul.Ast.Stmt → Bool + | .Switch _ _ _ => true + | .Block stmts => stmts.any nativeStmtContainsSwitch + | .If _ body => body.any nativeStmtContainsSwitch + | .For _ post body => + post.any nativeStmtContainsSwitch || body.any nativeStmtContainsSwitch + | _ => false + +private def nativeExprIsSelectorLoad : EvmYul.Yul.Ast.Expr → Bool + | .Call (.inl op) + [.Lit shift, .Call (.inl loadOp) [.Lit offset]] => + op == (EvmYul.Operation.SHR : EvmYul.Operation .Yul) && + StateBridge.uint256ToNat shift == Compiler.Constants.selectorShift && + loadOp == (EvmYul.Operation.CALLDATALOAD : EvmYul.Operation .Yul) && + StateBridge.uint256ToNat offset == 0 + | _ => false + +private def nativeSwitchDiscrName : String := + "__verity_native_switch_discr_0" + +private def nativeExprSwitchCaseLabel? : EvmYul.Yul.Ast.Expr → Option Nat + | .Call (.inl op) [.Var name, .Lit label] => + if op == (EvmYul.Operation.EQ : EvmYul.Operation .Yul) && + name == nativeSwitchDiscrName then + some (StateBridge.uint256ToNat label) + else + none + | .Call (.inl op) [.Lit label, .Var name] => + if op == (EvmYul.Operation.EQ : EvmYul.Operation .Yul) && + name == nativeSwitchDiscrName then + some (StateBridge.uint256ToNat label) + else + none + | .Call (.inl op) [left, right] => + if op == (EvmYul.Operation.AND : EvmYul.Operation .Yul) || + op == (EvmYul.Operation.OR : EvmYul.Operation .Yul) then + match nativeExprSwitchCaseLabel? left with + | some label => some label + | none => nativeExprSwitchCaseLabel? right + else + none + | _ => none + +private partial def nativeStmtContainsSelectorSwitch : EvmYul.Yul.Ast.Stmt → Bool + | .Switch selector _ _ => nativeExprIsSelectorLoad selector + | .Let [name] (some selector) => + name == nativeSwitchDiscrName && nativeExprIsSelectorLoad selector + | .Block stmts => stmts.any nativeStmtContainsSelectorSwitch + | .If _ body => body.any nativeStmtContainsSelectorSwitch + | .For _ post body => + post.any nativeStmtContainsSelectorSwitch || body.any nativeStmtContainsSelectorSwitch + | _ => false + +private partial def nativeStmtContainsScopedSelectorSwitch : EvmYul.Yul.Ast.Stmt → Bool + | .Block stmts => + stmts.any (fun stmt => + match stmt with + | .Block inner => inner.any nativeStmtContainsSelectorSwitch + | _ => nativeStmtContainsScopedSelectorSwitch stmt) + | .If _ body => body.any nativeStmtContainsScopedSelectorSwitch + | .For _ post body => + post.any nativeStmtContainsScopedSelectorSwitch || + body.any nativeStmtContainsScopedSelectorSwitch + | _ => false + +private partial def nativeStmtSwitchCaseLabels : EvmYul.Yul.Ast.Stmt → List Nat + | .Switch _ cases defaultBody => + cases.map (fun (label, _) => StateBridge.uint256ToNat label) ++ + defaultBody.foldl (fun acc stmt => acc ++ nativeStmtSwitchCaseLabels stmt) [] + | .Block stmts => + stmts.foldl (fun acc stmt => acc ++ nativeStmtSwitchCaseLabels stmt) [] + | .If cond body => + let nested := body.foldl (fun acc stmt => acc ++ nativeStmtSwitchCaseLabels stmt) [] + match nativeExprSwitchCaseLabel? cond with + | some label => label :: nested + | none => nested + | .For _ post body => + post.foldl (fun acc stmt => acc ++ nativeStmtSwitchCaseLabels stmt) [] ++ + body.foldl (fun acc stmt => acc ++ nativeStmtSwitchCaseLabels stmt) [] + | _ => [] + +private partial def nativeStmtContainsSstore (slot value : Nat) : EvmYul.Yul.Ast.Stmt → Bool + | .ExprStmtCall (.Call (.inl op) [.Lit gotSlot, .Lit gotValue]) => + op == (EvmYul.Operation.SSTORE : EvmYul.Operation .Yul) && + StateBridge.uint256ToNat gotSlot == slot && + StateBridge.uint256ToNat gotValue == value + | .Block stmts => + stmts.any (nativeStmtContainsSstore slot value) + | .If _ body => + body.any (nativeStmtContainsSstore slot value) + | .Switch _ cases defaultBody => + cases.any (fun (_, body) => body.any (nativeStmtContainsSstore slot value)) || + defaultBody.any (nativeStmtContainsSstore slot value) + | .For _ post body => + post.any (nativeStmtContainsSstore slot value) || + body.any (nativeStmtContainsSstore slot value) + | _ => false + +private def nativeExprMatchesLit (expected : Nat) : EvmYul.Yul.Ast.Expr → Bool + | .Lit got => StateBridge.uint256ToNat got == expected + | _ => false + +private def nativeExprsMatchLits + (args : List EvmYul.Yul.Ast.Expr) (expected : List Nat) : Bool := + args.length == expected.length && + (args.zip expected).all (fun (arg, value) => nativeExprMatchesLit value arg) + +private partial def nativeExprContainsPrimCall + (op : EvmYul.Operation .Yul) (args : List Nat) : + EvmYul.Yul.Ast.Expr → Bool + | .Call (.inl got) gotArgs => + (got == op && nativeExprsMatchLits gotArgs args) || + gotArgs.any (nativeExprContainsPrimCall op args) + | .Call (.inr _) gotArgs => + gotArgs.any (nativeExprContainsPrimCall op args) + | _ => false + +private partial def nativeStmtContainsPrimCall + (op : EvmYul.Operation .Yul) (args : List Nat) : + EvmYul.Yul.Ast.Stmt → Bool + | .Let _ (some expr) => + nativeExprContainsPrimCall op args expr + | .ExprStmtCall expr => + nativeExprContainsPrimCall op args expr + | .Block stmts => + stmts.any (nativeStmtContainsPrimCall op args) + | .If _ body => + body.any (nativeStmtContainsPrimCall op args) + | .Switch _ cases defaultBody => + cases.any (fun (_, body) => body.any (nativeStmtContainsPrimCall op args)) || + defaultBody.any (nativeStmtContainsPrimCall op args) + | .For _ post body => + post.any (nativeStmtContainsPrimCall op args) || + body.any (nativeStmtContainsPrimCall op args) + | _ => false + +private partial def nativeStmtSwitchCaseStores + (label slot value : Nat) : EvmYul.Yul.Ast.Stmt → Bool + | .Switch _ cases defaultBody => + cases.any (fun (gotLabel, body) => + StateBridge.uint256ToNat gotLabel == label && + body.any (nativeStmtContainsSstore slot value)) || + defaultBody.any (nativeStmtSwitchCaseStores label slot value) + | .Block stmts => + stmts.any (nativeStmtSwitchCaseStores label slot value) + | .If cond body => + (nativeExprSwitchCaseLabel? cond == some label && + body.any (nativeStmtContainsSstore slot value)) || + body.any (nativeStmtSwitchCaseStores label slot value) + | .For _ post body => + post.any (nativeStmtSwitchCaseStores label slot value) || + body.any (nativeStmtSwitchCaseStores label slot value) + | _ => false + +private partial def nativeStmtSwitchCaseContainsPrimCall + (label : Nat) (op : EvmYul.Operation .Yul) (args : List Nat) : + EvmYul.Yul.Ast.Stmt → Bool + | .Switch _ cases defaultBody => + cases.any (fun (gotLabel, body) => + StateBridge.uint256ToNat gotLabel == label && + body.any (nativeStmtContainsPrimCall op args)) || + defaultBody.any (nativeStmtSwitchCaseContainsPrimCall label op args) + | .Block stmts => + stmts.any (nativeStmtSwitchCaseContainsPrimCall label op args) + | .If cond body => + (nativeExprSwitchCaseLabel? cond == some label && + body.any (nativeStmtContainsPrimCall op args)) || + body.any (nativeStmtSwitchCaseContainsPrimCall label op args) + | .For _ post body => + post.any (nativeStmtSwitchCaseContainsPrimCall label op args) || + body.any (nativeStmtSwitchCaseContainsPrimCall label op args) + | _ => false + +private def emittedDispatchLowersToLazyNativeDispatcher : Bool := + match lowerRuntimeContractNative (Compiler.emitYul dispatchSmokeContract).runtimeCode with + | .ok contract => + !nativeStmtContainsSwitch contract.dispatcher && + nativeStmtContainsSelectorSwitch contract.dispatcher && + (contract.functions.lookup "left").isNone && + (contract.functions.lookup "right").isNone + | .error _ => false + +private def emittedDispatchScopesLazyNativeDispatcher : Bool := + match lowerRuntimeContractNative (Compiler.emitYul dispatchSmokeContract).runtimeCode with + | .ok contract => nativeStmtContainsScopedSelectorSwitch contract.dispatcher + | .error _ => false + +private def topLevelNativeSwitchIdsAreThreaded : Bool := + match lowerRuntimeContractNative [ + .switch (.lit 1) [(1, [.expr (.call "sstore" [.lit 1, .lit 11])])] none, + .switch (.lit 2) [(2, [.expr (.call "sstore" [.lit 2, .lit 22])])] none + ] with + | .ok { dispatcher := .Block [ + .Block (.Let [first] (some _) :: _), + .Block (.Let [second] (some _) :: _) + ], .. } => + first == "__verity_native_switch_discr_0" && + second == "__verity_native_switch_discr_1" + | _ => false + +private def nativeSwitchTempNamesAvoidUserNames : Bool := + nativeMatchesReferenceRuntime [ + .let_ "__verity_native_switch_discr_0" (.lit 99), + .let_ "__verity_native_switch_matched_0" (.lit 77), + .switch (.lit 1) + [(1, [.expr (.call "sstore" [.lit 7, .ident "__verity_native_switch_discr_0"])])] + none, + .expr (.call "sstore" [.lit 8, .ident "__verity_native_switch_matched_0"]) + ] [7, 8] [7, 8] + +private def nativeFunctionSwitchTempNamesAvoidLocalUserNames : Bool := + match lowerFunctionDefinitionNativeWithReserved [] [] [] [ + .let_ "__verity_native_switch_discr_0" (.lit 99), + .let_ "__verity_native_switch_matched_0" (.lit 77), + .switch (.lit 1) + [(1, [.let_ "after" (.ident "__verity_native_switch_discr_0")])] + none + ] with + | .ok (.Def _ _ [ + .Let ["__verity_native_switch_discr_0"] (some _), + .Let ["__verity_native_switch_matched_0"] (some _), + .Block (.Let [freshDiscr] (some _) :: .Let [freshMatched] (some _) :: _) + ]) => + freshDiscr != "__verity_native_switch_discr_0" && + freshMatched != "__verity_native_switch_matched_0" + | _ => false + +private def nativeSwitchExecutesOnlyFirstMatchingNonHaltingCase : Bool := + nativeMatchesReferenceRuntime [ + .switch (.lit 1) + [ (1, [.expr (.call "sstore" [.lit 7, .lit 11])]) + , (1, [.expr (.call "sstore" [.lit 7, .lit 22])]) ] + (some [.expr (.call "sstore" [.lit 7, .lit 33])]), + .expr (.call "sstore" [.lit 8, .lit 44]) + ] [7, 8] [7, 8] + +private def duplicateNativeHelperFailsClosed : Bool := + match lowerRuntimeContractNative [ + .funcDef "dup" [] [] [], + .funcDef "dup" [] [] [] + ] with + | .ok _ => false + | .error _ => true + +private def nestedNativeFunctionDefinitionsFailClosed : Bool := + (match lowerRuntimeContractNative [ + .block [ + .funcDef "nested_dispatcher" [] [] [] + ] + ] with + | .ok _ => false + | .error _ => true) && + (match lowerRuntimeContractNative [ + .funcDef "outer" [] [] [ + .funcDef "nested_helper" [] [] [] + ] + ] with + | .ok _ => false + | .error _ => true) + +private def emittedDispatchLowersNativeSelectorCases : Bool := + match lowerRuntimeContractNative (Compiler.emitYul dispatchSmokeContract).runtimeCode with + | .ok contract => + let labels := nativeStmtSwitchCaseLabels contract.dispatcher + labels.contains 0x11111111 && labels.contains 0x22222222 + | .error _ => false + +private def emittedDispatchLowersNativeSelectorExpr : Bool := + match lowerRuntimeContractNative (Compiler.emitYul dispatchSmokeContract).runtimeCode with + | .ok contract => nativeStmtContainsSelectorSwitch contract.dispatcher + | .error _ => false + +private def emittedDispatchNativeSelectorCaseBodies : Bool := + match lowerRuntimeContractNative (Compiler.emitYul dispatchSmokeContract).runtimeCode with + | .ok contract => + nativeStmtSwitchCaseStores 0x11111111 11 101 contract.dispatcher && + nativeStmtSwitchCaseStores 0x22222222 11 202 contract.dispatcher + | .error _ => false + +private def emittedReturnDispatchLowersNativeMemoryReturn : Bool := + match lowerRuntimeContractNative (Compiler.emitYul returnDispatchSmokeContract).runtimeCode with + | .ok contract => + nativeStmtSwitchCaseContainsPrimCall 0x33333333 .MSTORE [0, 42] + contract.dispatcher && + nativeStmtSwitchCaseContainsPrimCall 0x33333333 .RETURN [0, 32] + contract.dispatcher + | .error _ => false + +private def emittedStorageReadDispatchLowersNativeSloadBody : Bool := + match lowerRuntimeContractNative (Compiler.emitYul storageReadIRContract).runtimeCode with + | .ok contract => + nativeStmtSwitchCaseContainsPrimCall 0x44444444 .SLOAD [7] + contract.dispatcher + | .error _ => false + +private partial def nativeExprContainsUserCall (name : String) : EvmYul.Yul.Ast.Expr → Bool + | .Call (.inr got) args => + got == name || args.any (nativeExprContainsUserCall name) + | .Call (.inl _) args => + args.any (nativeExprContainsUserCall name) + | _ => false + +private partial def nativeStmtContainsUserCall (name : String) : EvmYul.Yul.Ast.Stmt → Bool + | .Let _ (some expr) => nativeExprContainsUserCall name expr + | .ExprStmtCall expr => nativeExprContainsUserCall name expr + | .Switch discr cases defaultBody => + nativeExprContainsUserCall name discr || + cases.any (fun (_, body) => body.any (nativeStmtContainsUserCall name)) || + defaultBody.any (nativeStmtContainsUserCall name) + | .For cond post body => + nativeExprContainsUserCall name cond || + post.any (nativeStmtContainsUserCall name) || + body.any (nativeStmtContainsUserCall name) + | .Block stmts => stmts.any (nativeStmtContainsUserCall name) + | .If cond body => + nativeExprContainsUserCall name cond || + body.any (nativeStmtContainsUserCall name) + | _ => false + +private def helperFuncDefMovesToMapAndCallStaysUserCall : Bool := + match lowerRuntimeContractNative [ + .funcDef "inc" ["x"] ["r"] [ + .let_ "r" (.call "add" [.ident "x", .lit 1]) + ], + .letMany ["y"] (.call "inc" [.lit 41]) + ] with + | .ok contract => + (contract.functions.lookup "inc").isSome && + nativeStmtContainsUserCall "inc" contract.dispatcher + | .error _ => false + private def lowersAddAsPrim : Bool := match lowerExprNative (.call "add" [.lit 1, .lit 2]) with | .Call (.inl op) args => @@ -42,12 +781,31 @@ private def lowersHelperAsUserFunction : Bool := | .Call (.inr name) args => name == "helper" && args.length == 1 | _ => false +private def lowerCallIsPrim + (name : String) (args : List YulExpr) (op : EvmYul.Operation .Yul) : Bool := + match lowerExprNative (.call name args) with + | .Call (.inl got) lowered => got == op && lowered.length == args.length + | _ => false + +private def lowersNativeHaltAndLogBuiltinsAsPrims : Bool := + lowerCallIsPrim "stop" [] .STOP && + lowerCallIsPrim "return" [.lit 0, .lit 32] .RETURN && + lowerCallIsPrim "revert" [.lit 0, .lit 0] .REVERT && + lowerCallIsPrim "log0" [.lit 0, .lit 32] .LOG0 && + lowerCallIsPrim "log1" [.lit 0, .lit 32, .lit 1] .LOG1 && + lowerCallIsPrim "log2" [.lit 0, .lit 32, .lit 1, .lit 2] .LOG2 && + lowerCallIsPrim "log3" [.lit 0, .lit 32, .lit 1, .lit 2, .lit 3] .LOG3 && + lowerCallIsPrim "log4" [.lit 0, .lit 32, .lit 1, .lit 2, .lit 3, .lit 4] .LOG4 + example : lowersAddAsPrim = true := by native_decide example : lowersHelperAsUserFunction = true := by native_decide +example : lowersNativeHaltAndLogBuiltinsAsPrims = true := by + native_decide + example : (match runNativeProgram [ .let_ "x" (.call "add" [.lit 40, .lit 2]) @@ -87,6 +845,88 @@ example : | .error _ => false) = true := by native_decide +example : + nativeCopiesSloadToSlot [7, 8] 77 = true := by + native_decide + +example : + nativeCopiesSloadToSlot [8] 0 = true := by + native_decide + +example : + nativeCopiesTransientLoadToStorage = true := by + native_decide + +example : + nativeInitialStateInstallsContractAndStorage = true := by + native_decide + +example : + nativeInitialStateOmittedSlotDefaultsToZero = true := by + native_decide + +example : + nativeInitialStatePinsTransactionEnvironment = true := by + native_decide + +example : + nativeEnvironmentMatchesReferenceRuntime = true := by + native_decide + +example : + nativeStopCommitsStorageAndPreservesEvents = true := by + native_decide + +example : + nativeReturnHaltProjectsStorageReturnAndEvents = true := by + native_decide + +example : + nativeValueResultProjectsStorageReturnAndEvents = true := by + native_decide + +example : + nativeHardErrorRollsBackStorageAndEvents = true := by + native_decide + +example : + nativeRevertErrorRollsBackStorageAndEvents = true := by + native_decide + +example : + (let finalStorage : Nat → Nat := fun slot => if slot = 7 then 99 else 0 + let result := + Native.projectResult sampleTx seededStorage [[1, 2, 3]] + (.ok + (stateWithStorageLogReturn finalStorage [7] [sampleLogEntry [5] 88] 0, + [EvmYul.UInt256.ofNat 44])) + result.finalMappings) = + (let finalStorage : Nat → Nat := fun slot => if slot = 7 then 99 else 0 + let result := + Native.projectResult sampleTx seededStorage [[1, 2, 3]] + (.ok + (stateWithStorageLogReturn finalStorage [7] [sampleLogEntry [5] 88] 0, + [EvmYul.UInt256.ofNat 44])) + Compiler.Proofs.storageAsMappings result.finalStorage) := by + rfl + +example : + (let finalStorage : Nat → Nat := fun slot => if slot = 7 then 99 else 0 + let result := + Native.projectResult sampleTx seededStorage [[1, 2, 3]] + (.error (.YulHalt + (stateWithStorageLogReturn finalStorage [7] [sampleLogEntry [5] 88] 44) + (EvmYul.UInt256.ofNat 1))) + result.finalMappings) = + (let finalStorage : Nat → Nat := fun slot => if slot = 7 then 99 else 0 + let result := + Native.projectResult sampleTx seededStorage [[1, 2, 3]] + (.error (.YulHalt + (stateWithStorageLogReturn finalStorage [7] [sampleLogEntry [5] 88] 44) + (EvmYul.UInt256.ofNat 1))) + Compiler.Proofs.storageAsMappings result.finalStorage) := by + rfl + example : Native.byteArrayWord (ByteArray.ofFn fun i : Fin 32 => @@ -98,12 +938,50 @@ example : Native.projectLogEntry { address := StateBridge.natToAddress sampleTx.thisAddress topics := #[EvmYul.UInt256.ofNat 7] - data := - ByteArray.ofFn fun i : Fin 32 => - if i.1 = 31 then UInt8.ofNat 55 else UInt8.ofNat 0 } = + data := wordByteArray 55 } = [7, 55] := by native_decide +example : + Native.projectLogsFromState + (stateWithLogEntries [ + sampleLogEntry [] 50, + sampleLogEntry [1] 51, + sampleLogEntry [1, 2] 52, + sampleLogEntry [1, 2, 3] 53, + sampleLogEntry [1, 2, 3, 4] 54 + ]) = + [[50], [1, 51], [1, 2, 52], [1, 2, 3, 53], [1, 2, 3, 4, 54]] := by + native_decide + +example : + (let result := + Native.projectResult sampleTx zeroStorage [[9]] + (.error (.YulHalt + (stateWithLogEntries [ + sampleLogEntry [] 50, + sampleLogEntry [1] 51, + sampleLogEntry [1, 2] 52, + sampleLogEntry [1, 2, 3] 53, + sampleLogEntry [1, 2, 3, 4] 54 + ]) + (EvmYul.UInt256.ofNat 0))) + result.success && + result.events == + [[9], [50], [1, 51], [1, 2, 52], [1, 2, 3, 53], [1, 2, 3, 4, 54]]) = true := by + native_decide + +example : + (let result := + Native.projectResult sampleTx zeroStorage [[9]] + (.ok + (stateWithLogEntries [sampleLogEntry [7] 70], + [EvmYul.UInt256.ofNat 44])) + result.success && + result.returnValue == some 44 && + result.events == [[9], [7, 70]]) = true := by + native_decide + example : (let state : EvmYul.Yul.State := .Ok @@ -117,12 +995,192 @@ example : native_decide example : - (match Native.interpretRuntimeNative 128 [ - .let_ "v" (.call "callvalue" []), - .expr (.call "sstore" [.lit 8, .ident "v"]) - ] sampleTx zeroStorage [8] with - | .ok result => result.success && result.finalStorage 8 == sampleTx.msgValue + nativeStoresBuiltin "callvalue" 8 sampleTx.msgValue = true := by + native_decide + +example : + nativeStoresBuiltin "timestamp" 9 sampleTx.blockTimestamp = true := by + native_decide + +example : + nativeStoresBuiltin "number" 10 sampleTx.blockNumber = true := by + native_decide + +example : + nativeStoresBuiltinWithTx "chainid" 15 EvmYul.chainId + { sampleTx with chainId := EvmYul.chainId } = true := by + native_decide + +example : + nativeRejectsUnsupportedChainId = true := by + native_decide + +example : + nativeAllowsUnselectedUnsupportedEnvironmentBuiltin = true := by + native_decide + +example : + nativeAllowsUserFunctionNamedChainid = true := by + native_decide + +example : + nativeStoresBuiltinWithTx "blobbasefee" 16 EvmYul.MIN_BASE_FEE_PER_BLOB_GAS + { sampleTx with blobBaseFee := EvmYul.MIN_BASE_FEE_PER_BLOB_GAS } = true := by + native_decide + +example : + nativeRejectsUnsupportedBlobBaseFee = true := by + native_decide + +example : + nativeRejectsUnsupportedHeaderBuiltin "coinbase" = true := by + native_decide + +example : + nativeRejectsUnsupportedHeaderBuiltin "gaslimit" = true := by + native_decide + +example : + nativeStoresBuiltin "caller" 12 sampleTx.sender = true := by + native_decide + +example : + nativeStoresBuiltin "address" 13 sampleTx.thisAddress = true := by + native_decide + +example : + nativeStoresBuiltin "calldatasize" 14 36 = true := by + native_decide + +example : + emittedDispatchLowersToLazyNativeDispatcher = true := by + native_decide + +example : + emittedDispatchLowersNativeSelectorCases = true := by + native_decide + +example : + emittedDispatchLowersNativeSelectorExpr = true := by + native_decide + +example : + emittedDispatchNativeSelectorCaseBodies = true := by + native_decide + +example : + emittedReturnDispatchLowersNativeMemoryReturn = true := by + native_decide + +example : + emittedStorageReadDispatchLowersNativeSloadBody = true := by + native_decide + +example : + calldataBridgePinsSelectorAndFirstArg = true := by + native_decide + +example : + nativeAssignRebindsExistingLocal = true := by + native_decide + +example : + emittedDispatchScopesLazyNativeDispatcher = true := by + native_decide + +example : + topLevelNativeSwitchIdsAreThreaded = true := by + native_decide + +example : + nativeSwitchTempNamesAvoidUserNames = true := by + native_decide + +example : + nativeFunctionSwitchTempNamesAvoidLocalUserNames = true := by + native_decide + +example : + nativeSwitchExecutesOnlyFirstMatchingNonHaltingCase = true := by + native_decide + +example : + duplicateNativeHelperFailsClosed = true := by + native_decide + +example : + nestedNativeFunctionDefinitionsFailClosed = true := by + native_decide + +example : + helperFuncDefMovesToMapAndCallStaysUserCall = true := by + native_decide + +example : + Native.interpretIRRuntimeNative 128 dispatchSmokeContract sampleIRTx + sampleIRState [11] = + Native.interpretRuntimeNative 128 + (Compiler.emitYul dispatchSmokeContract).runtimeCode + (Compiler.Proofs.YulGeneration.YulTransaction.ofIR sampleIRTx) + sampleIRState.storage [11] sampleIRState.events := by + rfl + +example : + Native.interpretIRRuntimeNative 128 storageReadIRContract storageReadIRTx + sampleIRState [7, 8] = + Native.interpretRuntimeNative 128 + (Compiler.emitYul storageReadIRContract).runtimeCode + (Compiler.Proofs.YulGeneration.YulTransaction.ofIR storageReadIRTx) + sampleIRState.storage [7, 8] sampleIRState.events := by + rfl + +example : + Native.interpretIRRuntimeNative 128 storageReadIRContract storageReadIRTx + sampleIRState [8] = + Native.interpretRuntimeNative 128 + (Compiler.emitYul storageReadIRContract).runtimeCode + (Compiler.Proofs.YulGeneration.YulTransaction.ofIR storageReadIRTx) + sampleIRState.storage [8] sampleIRState.events := by + rfl + +example : + (match Native.interpretIRRuntimeNative 128 duplicateHelperIRContract + sampleIRTx sampleIRState [] with + | .ok _ => false + | .error _ => true) = true := by + native_decide + +example : + (match Native.interpretRuntimeNative 128 [] + sampleTx zeroStorage [] [[1, 2, 3]] with + | .ok result => result.success && result.events == [[1, 2, 3]] | .error _ => false) = true := by native_decide +example : + (let result := + Native.projectResult sampleTx + (fun slot => if slot = 7 then 5 else 0) + [[1, 2, 3]] + (.error EvmYul.Yul.Exception.Revert) + !result.success && + result.finalStorage 7 == 5 && + result.events == [[1, 2, 3]]) = true := by + native_decide + +example : + (let result := + Native.projectResult sampleTx + (fun slot => if slot = 7 then 5 else 0) + [[1, 2, 3]] + (.error EvmYul.Yul.Exception.Revert) + result.finalMappings) = + (let result := + Native.projectResult sampleTx + (fun slot => if slot = 7 then 5 else 0) + [[1, 2, 3]] + (.error EvmYul.Yul.Exception.Revert) + Compiler.Proofs.storageAsMappings result.finalStorage) := by + rfl + end Compiler.Proofs.YulGeneration.Backends diff --git a/Compiler/Proofs/YulGeneration/Backends/EvmYulLeanRetarget.lean b/Compiler/Proofs/YulGeneration/Backends/EvmYulLeanRetarget.lean index 068e0dc41..4a7eb8f7e 100644 --- a/Compiler/Proofs/YulGeneration/Backends/EvmYulLeanRetarget.lean +++ b/Compiler/Proofs/YulGeneration/Backends/EvmYulLeanRetarget.lean @@ -1353,6 +1353,28 @@ private theorem bridgedExpr_selector : subst hNested exact BridgedExpr.lit 0 +/-- The generated dispatcher selector expression is in the bridged expression +fragment, so the EVMYulLean-backed interpreter oracle evaluates it exactly like +the historical Verity interpreter. -/ +theorem bridgedExpr_selectorExpr : + BridgedExpr Compiler.Proofs.YulGeneration.selectorExpr := by + simpa [Compiler.Proofs.YulGeneration.selectorExpr] using bridgedExpr_selector + +/-- The EVMYulLean-backed interpreter oracle selects the same 4-byte dispatcher +selector as the generated Verity selector expression. + +This is the first generated-dispatcher semantic slice needed by the native +migration: every native/interpreter dispatcher simulation branches on this +expression before it reaches storage, memory, or halt behavior. -/ +@[simp] theorem evalYulExprWithBackend_evmYulLean_selectorExpr_semantics + (state : YulState) : + evalYulExprWithBackend .evmYulLean state + Compiler.Proofs.YulGeneration.selectorExpr = + some (state.selector % selectorModulus) := by + rw [← evalYulExpr_evmYulLean_eq_on_bridged state + Compiler.Proofs.YulGeneration.selectorExpr bridgedExpr_selectorExpr] + exact evalYulExpr_selectorExpr_semantics state + private theorem bridgedExpr_calldatasize_lt (n : Nat) : BridgedExpr (Compiler.Yul.YulExpr.call "lt" @@ -2630,20 +2652,37 @@ noncomputable def execYulStmtsWithBackend YulExecResult := execYulFuelWithBackend backend (sizeOf stmts + 1) state (.stmts stmts) -noncomputable def interpretYulRuntimeWithBackend - (backend : BuiltinBackend) (runtimeCode : List Compiler.Yul.YulStmt) +noncomputable def interpretYulRuntimeWithBackendFuel + (backend : BuiltinBackend) (fuel : Nat) + (runtimeCode : List Compiler.Yul.YulStmt) (tx : YulTransaction) (storage : Nat → Nat) (events : List (List Nat) := []) : YulResult := let initialState := YulState.initial tx storage events yulResultOfExecWithRollback initialState - (execYulStmtsWithBackend backend initialState runtimeCode) + (execYulFuelWithBackend backend fuel initialState (.stmts runtimeCode)) + +noncomputable def interpretYulRuntimeWithBackend + (backend : BuiltinBackend) (runtimeCode : List Compiler.Yul.YulStmt) + (tx : YulTransaction) (storage : Nat → Nat) (events : List (List Nat) := []) : + YulResult := + interpretYulRuntimeWithBackendFuel backend (sizeOf runtimeCode + 1) + runtimeCode tx storage events + +@[simp] theorem interpretYulRuntimeWithBackend_eq_fuel + (backend : BuiltinBackend) (runtimeCode : List Compiler.Yul.YulStmt) + (tx : YulTransaction) (storage : Nat → Nat) (events : List (List Nat) := []) : + interpretYulRuntimeWithBackend backend runtimeCode tx storage events = + interpretYulRuntimeWithBackendFuel backend (sizeOf runtimeCode + 1) + runtimeCode tx storage events := by + rfl theorem interpretYulRuntimeWithBackend_verity_eq (runtimeCode : List Compiler.Yul.YulStmt) (tx : YulTransaction) (storage : Nat → Nat) (events : List (List Nat) := []) : interpretYulRuntimeWithBackend .verity runtimeCode tx storage events = interpretYulRuntime runtimeCode tx storage events := by - unfold interpretYulRuntimeWithBackend execYulStmtsWithBackend interpretYulRuntime + unfold interpretYulRuntimeWithBackend interpretYulRuntimeWithBackendFuel + interpretYulRuntime unfold execYulStmts execYulStmtsFuel change yulResultOfExecWithRollback (YulState.initial tx storage events) @@ -2691,7 +2730,7 @@ theorem interpretYulFromIR_evmYulLean_eq_on_bridged_bodies _ = interpretYulRuntimeWithBackend .evmYulLean (Compiler.emitYul contract).runtimeCode (YulTransaction.ofIR tx) state.storage state.events := by - unfold interpretYulRuntimeWithBackend execYulStmtsWithBackend + unfold interpretYulRuntimeWithBackend interpretYulRuntimeWithBackendFuel change yulResultOfExecWithRollback (YulState.initial (YulTransaction.ofIR tx) state.storage state.events) diff --git a/Compiler/Proofs/YulGeneration/Backends/EvmYulLeanStateBridge.lean b/Compiler/Proofs/YulGeneration/Backends/EvmYulLeanStateBridge.lean index 75fd45d82..1b83dbf03 100644 --- a/Compiler/Proofs/YulGeneration/Backends/EvmYulLeanStateBridge.lean +++ b/Compiler/Proofs/YulGeneration/Backends/EvmYulLeanStateBridge.lean @@ -195,6 +195,42 @@ private theorem calldataToByteArray_fold_size simp [List.foldl_cons, ByteArray.size_append, hWord, ih, Nat.add_assoc] omega +private theorem byteArray_get?_append_left + {a b : ByteArray} {i : Nat} (h : i < a.size) : + (a ++ b).get? i = a.get? i := by + unfold ByteArray.get? + split + · apply congrArg some + have hEq : (a ++ b)[i] = a[i] := ByteArray.get_append_left h + convert hEq using 1 + · exact False.elim (by + rename_i hAppend + exact hAppend (by + rw [ByteArray.size_append] + exact Nat.lt_of_lt_of_le h (Nat.le_add_right a.size b.size))) + +private theorem calldataToByteArray_fold_get?_left + (wordBytes : Nat → ByteArray) + (hWord : ∀ w, (wordBytes w).size = 32) : + ∀ (acc : ByteArray) (calldata : List Nat) (i : Nat), i < acc.size → + (calldata.foldl (init := acc) fun acc w => acc ++ wordBytes w).get? i = + acc.get? i := by + intro acc calldata + induction calldata generalizing acc with + | nil => + intro i h + rfl + | cons w ws ih => + intro i h + simp only [List.foldl_cons] + calc + (ws.foldl (init := acc ++ wordBytes w) fun acc w => acc ++ wordBytes w).get? i + = (acc ++ wordBytes w).get? i := by + apply ih + rw [ByteArray.size_append, hWord] + omega + _ = acc.get? i := byteArray_get?_append_left h + /-- The bridged calldata byte array has the same observable length as Verity's `calldatasize`: 4 selector bytes plus 32 bytes per calldata word. -/ theorem calldataToByteArray_size (selector : Nat) (calldata : List Nat) : @@ -218,6 +254,98 @@ theorem calldataToByteArray_size (selector : Nat) (calldata : List Nat) : have hFold := calldataToByteArray_fold_size wordBytes hWord selectorBytes calldata simpa [selectorBytes, wordBytes, hSel] using hFold +@[simp] theorem calldataToByteArray_selectorByte0 (selector : Nat) (calldata : List Nat) : + (calldataToByteArray selector calldata).get? 0 = + some (UInt8.ofNat (selector / 2^24 % 256)) := by + unfold calldataToByteArray + let selectorBytes : ByteArray := + ByteArray.ofFn fun i : Fin 4 => + match i.1 with + | 0 => UInt8.ofNat (selector / 2^24 % 256) + | 1 => UInt8.ofNat (selector / 2^16 % 256) + | 2 => UInt8.ofNat (selector / 2^8 % 256) + | _ => UInt8.ofNat (selector % 256) + let wordBytes : Nat → ByteArray := fun w => + ByteArray.ofFn fun i : Fin 32 => + UInt8.ofNat (w / 2^((31 - i.1) * 8) % 256) + have hWord : ∀ w, (wordBytes w).size = 32 := by intro w; simp [wordBytes] + rw [calldataToByteArray_fold_get?_left wordBytes hWord] + · unfold ByteArray.get? + split + · apply congrArg some + simp [ByteArray.get] + · simp at * + · simp + +@[simp] theorem calldataToByteArray_selectorByte1 (selector : Nat) (calldata : List Nat) : + (calldataToByteArray selector calldata).get? 1 = + some (UInt8.ofNat (selector / 2^16 % 256)) := by + unfold calldataToByteArray + let selectorBytes : ByteArray := + ByteArray.ofFn fun i : Fin 4 => + match i.1 with + | 0 => UInt8.ofNat (selector / 2^24 % 256) + | 1 => UInt8.ofNat (selector / 2^16 % 256) + | 2 => UInt8.ofNat (selector / 2^8 % 256) + | _ => UInt8.ofNat (selector % 256) + let wordBytes : Nat → ByteArray := fun w => + ByteArray.ofFn fun i : Fin 32 => + UInt8.ofNat (w / 2^((31 - i.1) * 8) % 256) + have hWord : ∀ w, (wordBytes w).size = 32 := by intro w; simp [wordBytes] + rw [calldataToByteArray_fold_get?_left wordBytes hWord] + · unfold ByteArray.get? + split + · apply congrArg some + simp [ByteArray.get] + · simp at * + · simp + +@[simp] theorem calldataToByteArray_selectorByte2 (selector : Nat) (calldata : List Nat) : + (calldataToByteArray selector calldata).get? 2 = + some (UInt8.ofNat (selector / 2^8 % 256)) := by + unfold calldataToByteArray + let selectorBytes : ByteArray := + ByteArray.ofFn fun i : Fin 4 => + match i.1 with + | 0 => UInt8.ofNat (selector / 2^24 % 256) + | 1 => UInt8.ofNat (selector / 2^16 % 256) + | 2 => UInt8.ofNat (selector / 2^8 % 256) + | _ => UInt8.ofNat (selector % 256) + let wordBytes : Nat → ByteArray := fun w => + ByteArray.ofFn fun i : Fin 32 => + UInt8.ofNat (w / 2^((31 - i.1) * 8) % 256) + have hWord : ∀ w, (wordBytes w).size = 32 := by intro w; simp [wordBytes] + rw [calldataToByteArray_fold_get?_left wordBytes hWord] + · unfold ByteArray.get? + split + · apply congrArg some + simp [ByteArray.get] + · simp at * + · simp + +@[simp] theorem calldataToByteArray_selectorByte3 (selector : Nat) (calldata : List Nat) : + (calldataToByteArray selector calldata).get? 3 = + some (UInt8.ofNat (selector % 256)) := by + unfold calldataToByteArray + let selectorBytes : ByteArray := + ByteArray.ofFn fun i : Fin 4 => + match i.1 with + | 0 => UInt8.ofNat (selector / 2^24 % 256) + | 1 => UInt8.ofNat (selector / 2^16 % 256) + | 2 => UInt8.ofNat (selector / 2^8 % 256) + | _ => UInt8.ofNat (selector % 256) + let wordBytes : Nat → ByteArray := fun w => + ByteArray.ofFn fun i : Fin 32 => + UInt8.ofNat (w / 2^((31 - i.1) * 8) % 256) + have hWord : ∀ w, (wordBytes w).size = 32 := by intro w; simp [wordBytes] + rw [calldataToByteArray_fold_get?_left wordBytes hWord] + · unfold ByteArray.get? + split + · apply congrArg some + simp [ByteArray.get] + · simp at * + · simp + /-! ## Full State Conversion The main bridge functions that convert between Verity's YulState and diff --git a/Compiler/Selector.lean b/Compiler/Selector.lean index 75e1cb1b9..88ee02ecf 100644 --- a/Compiler/Selector.lean +++ b/Compiler/Selector.lean @@ -8,11 +8,6 @@ namespace Compiler.Selector open Compiler.CompilationModel open Compiler.Hex -private def functionSignature (fn : FunctionSpec) : String := - let params := fn.params.map (fun p => paramTypeToSolidityString p.ty) - let paramStr := String.intercalate "," params - s!"{fn.name}({paramStr})" - private def externalFunctions (spec : CompilationModel) : List FunctionSpec := spec.functions.filter (fun fn => !fn.isInternal && !isInteropEntrypointName fn.name) diff --git a/Compiler/TypedIRCompiler.lean b/Compiler/TypedIRCompiler.lean index 59afa867e..e8a880d2e 100644 --- a/Compiler/TypedIRCompiler.lean +++ b/Compiler/TypedIRCompiler.lean @@ -45,6 +45,9 @@ private def pushLocal (v : TVar) : CompileM Unit := private def emit (stmt : TStmt) : CompileM Unit := modify fun st => { st with body := st.body.push stmt } +private def storageWordSlot (slot wordOffset : Nat) : Nat := + (slot + wordOffset) % Compiler.Constants.evmModulus + private def paramTypeToTy : ParamType → Except String Ty | .uint256 => Except.ok Ty.uint256 | .int256 => Except.ok Ty.uint256 @@ -349,6 +352,13 @@ private def compileStmt (fields : List Field) : Stmt → CompileM Unit throw s!"Typed IR compile error: setStorageAddr requires an address-typed field '{fieldName}'" | expectedTy, ⟨actualTy, _⟩ => throw s!"Typed IR compile error: setStorageAddr type mismatch for '{fieldName}' (expected {repr expectedTy}, got {repr actualTy})" + | .setStorageWord fieldName wordOffset value => do + let rhs ← liftExcept <| asUInt256 (← compileExpr fields value) + match findFieldWithResolvedSlot fields fieldName with + | some (_, slot) => + emit (.setStorageWord (storageWordSlot slot wordOffset) rhs) + | none => + throw s!"Typed IR compile error: unknown storage field '{fieldName}' in setStorageWord" | .setMapping fieldName key value => do let keyExpr ← liftExcept <| asAddress (← compileExpr fields key) let valueExpr ← liftExcept <| asUInt256 (← compileExpr fields value) @@ -574,11 +584,27 @@ def compileFunctionToTBlock (spec : CompilationModel) (fn : FunctionSpec) : Exce locals := st.locals.toList body := st.body.toList } -/-- Compile a named function from a `CompilationModel` to typed IR. -/ +/-- Compile a function by full ABI signature from a `CompilationModel` to typed IR. -/ +def compileFunctionWithSignature (spec : CompilationModel) (signature : String) : + Except String TBlock := do + match spec.functions.filter (fun fn => functionSignature fn == signature) with + | [] => throw s!"Typed IR compile error: function signature '{signature}' not found in spec '{spec.name}'" + | [fn] => compileFunctionToTBlock spec fn + | _ => + throw s!"Typed IR compile error: function signature '{signature}' is ambiguous in spec '{spec.name}'" + +/-- Compile a named function from a `CompilationModel` to typed IR. + +Name-only lookup is accepted only when the source name is unambiguous. Specs with +overloads must use `compileFunctionWithSignature` so typed-IR callers cannot +silently select the first same-name declaration. +-/ def compileFunctionNamed (spec : CompilationModel) (functionName : String) : Except String TBlock := do - match spec.functions.find? (fun fn => fn.name == functionName) with - | some fn => compileFunctionToTBlock spec fn - | none => throw s!"Typed IR compile error: function '{functionName}' not found in spec '{spec.name}'" + match spec.functions.filter (fun fn => fn.name == functionName) with + | [] => throw s!"Typed IR compile error: function '{functionName}' not found in spec '{spec.name}'" + | [fn] => compileFunctionToTBlock spec fn + | _ => + throw s!"Typed IR compile error: function '{functionName}' is overloaded in spec '{spec.name}'; use compileFunctionWithSignature" private def abiHeadParamSmokeFn : FunctionSpec := { name := "acceptHeads" @@ -618,6 +644,79 @@ private def abiHeadExpectedParamTys : List Ty := [ Ty.uint256 ] +private def typedIROverloadSmokeSpec : CompilationModel := { + name := "TypedIROverloadSmoke" + fields := [] + «constructor» := none + functions := [ + { name := "echo" + params := [{ name := "amount", ty := ParamType.uint256 }] + returnType := some FieldType.uint256 + body := [Stmt.return (Expr.param "amount")] + }, + { name := "echo" + params := [{ name := "recipient", ty := ParamType.address }] + returnType := some FieldType.uint256 + body := [Stmt.return (Expr.literal 2)] + } + ] +} + +private def typedIRAmbiguousSignatureSmokeSpec : CompilationModel := { + name := "TypedIRAmbiguousSignatureSmoke" + fields := [] + «constructor» := none + functions := [ + { name := "echo" + params := [{ name := "amount", ty := ParamType.uint256 }] + returnType := some FieldType.uint256 + body := [Stmt.return (Expr.param "amount")] + }, + { name := "echo" + params := [{ name := "amount", ty := ParamType.uint256 }] + returnType := some FieldType.uint256 + isInternal := true + body := [Stmt.return (Expr.literal 2)] + } + ] +} + +private def stringContains (haystack needle : String) : Bool := + let h := haystack.toList + let n := needle.toList + if n.isEmpty then true + else + let rec go : List Char → Bool + | [] => false + | c :: cs => + if (c :: cs).take n.length == n then true + else go cs + go h + +example : + (match compileFunctionNamed typedIROverloadSmokeSpec "echo" with + | Except.ok _ => false + | Except.error msg => stringContains msg "function 'echo' is overloaded") = true := by + native_decide + +example : + (match compileFunctionWithSignature typedIROverloadSmokeSpec "echo(uint256)" with + | Except.ok block => decide (block.params.map TVar.ty = [Ty.uint256]) + | Except.error _ => false) = true := by + native_decide + +example : + (match compileFunctionWithSignature typedIROverloadSmokeSpec "echo(address)" with + | Except.ok block => decide (block.params.map TVar.ty = [Ty.address]) + | Except.error _ => false) = true := by + native_decide + +example : + (match compileFunctionWithSignature typedIRAmbiguousSignatureSmokeSpec "echo(uint256)" with + | Except.ok _ => false + | Except.error msg => stringContains msg "signature 'echo(uint256)' is ambiguous") = true := by + native_decide + example : (match compileFunctionNamed abiHeadParamSmokeSpec "acceptHeads" with | Except.ok block => decide (block.params.map TVar.ty = abiHeadExpectedParamTys) @@ -656,6 +755,43 @@ theorem compileStmts_single_setStorage_literal_run simp only [compileStmts, compileStmt, fieldTypeToTy, hfind, emit] rfl +/-- `setStorageWord fieldName wordOffset (literal n)` lowers to a raw uint256 +write at the resolved root slot plus the requested ABI word offset, wrapping +the slot arithmetic at the EVM word modulus. -/ +theorem compileStmts_single_setStorageWord_literal_run + (fields : List Field) (fieldName : String) (slot wordOffset : Nat) + (n : Nat) (st : CompileState) + (hfind : findFieldWithResolvedSlot fields fieldName = + some ({ name := fieldName, ty := FieldType.uint256 }, slot)) : + (compileStmts fields [Stmt.setStorageWord fieldName wordOffset (Expr.literal n)]).run st = + Except.ok ((), { st with body := st.body.push (TStmt.setStorageWord (storageWordSlot slot wordOffset) (TExpr.uintLit n)) }) := by + simp only [compileStmts, compileStmt, hfind, emit] + rfl + +/-- `setStorageWord` is raw word storage: address roots also lower to the +projection-mirroring typed raw write rather than a uint-only `setStorage`. -/ +theorem compileStmts_single_setStorageWord_address_literal_run + (fields : List Field) (fieldName : String) (slot wordOffset : Nat) + (n : Nat) (st : CompileState) + (hfind : findFieldWithResolvedSlot fields fieldName = + some ({ name := fieldName, ty := FieldType.address }, slot)) : + (compileStmts fields [Stmt.setStorageWord fieldName wordOffset (Expr.literal n)]).run st = + Except.ok ((), { st with body := st.body.push (TStmt.setStorageWord (storageWordSlot slot wordOffset) (TExpr.uintLit n)) }) := by + simp only [compileStmts, compileStmt, hfind, emit] + rfl + +theorem compileStmts_single_setStorageWord_wraps_slot_run + (fields : List Field) (fieldName : String) (slot : Nat) + (n : Nat) (st : CompileState) + (hfind : findFieldWithResolvedSlot fields fieldName = + some ({ name := fieldName, ty := FieldType.uint256 }, slot)) : + (compileStmts fields + [Stmt.setStorageWord fieldName Compiler.Constants.evmModulus (Expr.literal n)]).run st = + Except.ok ((), { st with body := st.body.push (TStmt.setStorageWord (slot % Compiler.Constants.evmModulus) (TExpr.uintLit n)) }) := by + simp [compileStmts, compileStmt, hfind, asUInt256, liftExcept, emit, + storageWordSlot, Nat.add_mod_right] + rfl + /-- Two-statement compilation shape for a broader supported subset: `letVar tmp (literal n); setStorage fieldName (localVar tmp)` lowers to one SSA `let_` followed by one typed `setStorage`, from an empty compile state. -/ diff --git a/Compiler/TypedIRLowering.lean b/Compiler/TypedIRLowering.lean index 79e34ff88..16c59291d 100644 --- a/Compiler/TypedIRLowering.lean +++ b/Compiler/TypedIRLowering.lean @@ -61,6 +61,7 @@ where | .assign dst rhs => [.assign (tVarName dst) (lowerTExpr rhs)] | .setStorage slot value => [.expr (.call "sstore" [.lit slot, lowerTExpr value])] | .setStorageAddr slot value => [.expr (.call "sstore" [.lit slot, lowerTExpr value])] + | .setStorageWord slot value => [.expr (.call "sstore" [.lit slot, lowerTExpr value])] | .setMapping slot key value => [ .expr (.call "sstore" [.call "mappingSlot" [.lit slot, lowerTExpr key], lowerTExpr value]) diff --git a/Contracts/Common.lean b/Contracts/Common.lean index d8541a578..e1351baf5 100644 --- a/Contracts/Common.lean +++ b/Contracts/Common.lean @@ -1,4 +1,5 @@ import Compiler.CompilationModel +import Compiler.Proofs.MappingSlot import Verity.Core import Verity.Core.Semantics import Verity.EVM.Uint256 @@ -55,6 +56,11 @@ macro_rules | `(doElem| let $name:ident := arrayElement $values:term $index:term) => do let checked := Lean.mkIdentFrom name `_root_.Contracts.arrayElementChecked `(doElem| let $name ← $checked:ident $values $index) + | `(doElem| let $pat:term := arrayElement $values:term $index:term) => do + if pat.raw.getKind != `Lean.Parser.Term.tuple then + Lean.Macro.throwUnsupported + let checked := Lean.mkIdentFrom values `_root_.Contracts.arrayElementChecked + `(doElem| let $pat:term ← $checked:ident $values $index) | `(doElem| let $name:ident := tload $offset:term) => do let load := Lean.mkIdentFrom name `_root_.Contracts.tload `(doElem| let $name ← $load:ident $offset) @@ -204,7 +210,7 @@ def returndataCopy (_destOffset _sourceOffset _size : Uint256) : Contract Unit : def revertReturndata : Contract Unit := pure () def arrayLength {α : Type} (values : Array α) : Uint256 := values.size def arrayElement {α : Type} [Inhabited α] (values : Array α) (index : Uint256) : α := - values.getD (index : Nat) default + values.getD (index : Nat) (Inhabited.default : α) def arrayElementChecked {α : Type} (values : Array α) (index : Uint256) : Contract α := fun state => if h : (index : Nat) < values.size then ContractResult.success (values[(index : Nat)]'h) state @@ -215,6 +221,14 @@ def returnValues (_values : List Uint256) : Contract Unit := pure () def returnBytes {α : Type} (value : α) : Contract α := pure value def returnStorageWords {α : Type} (_slots : Array α) : Contract (Array Uint256) := pure #[] def emit (name : String) (args : List Uint256) : Contract Unit := emitEvent name args +def setPackedStorage {α : Type} (rootSlot : StorageSlot α) (wordOffset : Nat) + (word : Uint256) : Contract Unit := fun state => + let targetSlot := (rootSlot.slot + wordOffset) % Compiler.Constants.evmModulus + ContractResult.success () { state with + «storage» := fun target => if target == targetSlot then word else state.storage target, + storageAddr := fun target => + if target == targetSlot then wordToAddress word else state.storageAddr target + } def rawLog (topics : List Uint256) (dataOffset dataSize : Uint256) : Contract Unit := fun state => if topics.length > 4 then ContractResult.revert s!"rawLog supports at most 4 topics, got {topics.length}" state @@ -255,7 +269,7 @@ private def externalCallStubWord (name : String) (args : List Uint256) : Uint256 def externalCallWords {α : Type} [ExternalResult α] (name : String) (args : List Uint256) : α := ExternalResult.fromWord (externalCallStubWord name args) def tryExternalCallWords {α : Type} [Inhabited α] (_name : String) (_args : List Uint256) : Contract (Bool × α) := - pure (false, default) + pure (false, (Inhabited.default : α)) private def erc20ReadStubWord (name : String) (args : List Uint256) : Uint256 := externalCallStubWord name args macro_rules @@ -277,13 +291,15 @@ def setMappingN {κ α : Type} (_slot : StorageSlot α) (_keys : List κ) (_valu Contract Unit := pure () def structMember {κ α : Type} [Inhabited α] (_field : String) (_key : κ) (_member : String) : - Contract α := pure default + Contract α := pure (Inhabited.default : α) def structMember2 {κ₁ κ₂ α : Type} [Inhabited α] - (_field : String) (_key1 : κ₁) (_key2 : κ₂) (_member : String) : Contract α := pure default + (_field : String) (_key1 : κ₁) (_key2 : κ₂) (_member : String) : Contract α := + pure (Inhabited.default : α) def structMembers {κ α : Type} [Inhabited α] - (_field : String) (_key : κ) (_members : List String) : α := default + (_field : String) (_key : κ) (_members : List String) : α := (Inhabited.default : α) def structMembers2 {κ₁ κ₂ α : Type} [Inhabited α] - (_field : String) (_key1 : κ₁) (_key2 : κ₂) (_members : List String) : α := default + (_field : String) (_key1 : κ₁) (_key2 : κ₂) (_members : List String) : α := + (Inhabited.default : α) def setStructMember {κ α : Type} (_field : String) (_key : κ) (_member : String) (_value : α) : Contract Unit := pure () def setStructMember2 {κ₁ κ₂ α : Type} @@ -295,11 +311,104 @@ def balanceOf (token owner : Address) : Contract Uint256 := pure <| erc20ReadStu def allowance (token owner spender : Address) : Contract Uint256 := pure <| erc20ReadStubWord "allowance" [token.toNat, owner.toNat, spender.toNat] def totalSupply (token : Address) : Contract Uint256 := pure <| erc20ReadStubWord "totalSupply" [token.toNat] def forEach (_name : String) (_count : Uint256) (body : Contract Unit) : Contract Unit := body -def blockTimestamp : Uint256 := 0 -def blockNumber : Uint256 := 0 -def blobbasefee : Uint256 := 0 -def contractAddress : Uint256 := 0 -def chainid : Uint256 := 0 +def blockTimestamp : Contract Uint256 := Verity.blockTimestamp +def blockNumber : Contract Uint256 := Verity.blockNumber +def blobbasefee : Contract Uint256 := Verity.blobbasefee +def contractAddress : Contract Address := Verity.contractAddress +def chainid : Contract Uint256 := Verity.chainid + +class StorageKey (α : Type) where + toWord : α → Nat + +instance : StorageKey Uint256 where + toWord key := key.val + +instance : StorageKey Address where + toWord key := key.toNat + +class StorageWord (α : Type) where + fromWord : Uint256 → α + toWord : α → Uint256 + +instance : StorageWord Uint256 where + fromWord word := word + toWord word := word + +instance : StorageWord Address where + fromWord word := Verity.wordToAddress word + toWord addr := Verity.addressToWord addr + +def structSlot (baseSlot : Nat) (key : Nat) (wordOffset : Nat) : Nat := + (Compiler.Proofs.abstractMappingSlot baseSlot key + wordOffset) % Compiler.Constants.evmModulus + +def structSlot2 (baseSlot : Nat) (key1 key2 : Nat) (wordOffset : Nat) : Nat := + (Compiler.Proofs.abstractMappingSlot (Compiler.Proofs.abstractMappingSlot baseSlot key1) key2 + wordOffset) % + Compiler.Constants.evmModulus + +private def packedMask (width : Nat) : Nat := + 2 ^ width - 1 + +private def decodePackedWord (word : Uint256) (offset width : Nat) : Uint256 := + Verity.Core.Uint256.and (Verity.Core.Uint256.shr offset word) (packedMask width) + +private def encodePackedWord (current value : Uint256) (offset width : Nat) : Uint256 := + let mask := packedMask width + let shiftedMask := Verity.Core.Uint256.shl offset mask + let packedValue := Verity.Core.Uint256.and value mask + let cleared := Verity.Core.Uint256.and current (Verity.Core.Uint256.not shiftedMask) + Verity.Core.Uint256.or cleared (Verity.Core.Uint256.shl offset packedValue) + +def structMemberAt {κ α : Type} [StorageKey κ] [StorageWord α] + (baseSlot : Nat) (wordOffset : Nat) (packed : Option (Nat × Nat)) (key : κ) : + Contract α := + fun state => + let targetSlot := structSlot baseSlot (StorageKey.toWord key) wordOffset + let raw := state.storage targetSlot + let word := match packed with + | none => raw + | some (offset, width) => decodePackedWord raw offset width + ContractResult.success (StorageWord.fromWord word) state + +def structMember2At {κ₁ κ₂ α : Type} [StorageKey κ₁] [StorageKey κ₂] [StorageWord α] + (baseSlot : Nat) (wordOffset : Nat) (packed : Option (Nat × Nat)) (key1 : κ₁) (key2 : κ₂) : + Contract α := + fun state => + let targetSlot := structSlot2 baseSlot (StorageKey.toWord key1) (StorageKey.toWord key2) wordOffset + let raw := state.storage targetSlot + let word := match packed with + | none => raw + | some (offset, width) => decodePackedWord raw offset width + ContractResult.success (StorageWord.fromWord word) state + +def setStructMemberAt {κ α : Type} [StorageKey κ] [StorageWord α] + (baseSlot : Nat) (wordOffset : Nat) (packed : Option (Nat × Nat)) (key : κ) (value : α) : + Contract Unit := + fun state => + let targetSlot := structSlot baseSlot (StorageKey.toWord key) wordOffset + let word := StorageWord.toWord value + let stored := + match packed with + | none => word + | some (offset, width) => encodePackedWord (state.storage targetSlot) word offset width + let updatedStorage := fun target => if target == targetSlot then stored else state.storage target + ContractResult.success () { state with + «storage» := updatedStorage + } + +def setStructMember2At {κ₁ κ₂ α : Type} [StorageKey κ₁] [StorageKey κ₂] [StorageWord α] + (baseSlot : Nat) (wordOffset : Nat) (packed : Option (Nat × Nat)) (key1 : κ₁) (key2 : κ₂) + (value : α) : Contract Unit := + fun state => + let targetSlot := structSlot2 baseSlot (StorageKey.toWord key1) (StorageKey.toWord key2) wordOffset + let word := StorageWord.toWord value + let stored := + match packed with + | none => word + | some (offset, width) => encodePackedWord (state.storage targetSlot) word offset width + let updatedStorage := fun target => if target == targetSlot then stored else state.storage target + ContractResult.success () { state with + «storage» := updatedStorage + } end Contracts diff --git a/Contracts/Counter/Counter.lean b/Contracts/Counter/Counter.lean index aa43206b4..b0dcadc2c 100644 --- a/Contracts/Counter/Counter.lean +++ b/Contracts/Counter/Counter.lean @@ -48,10 +48,10 @@ verity_contract Counter where function previewEnvOps (x : Uint256, y : Uint256) local_obligations [env_memory_refinement := assumed "Caller must separately prove the direct mload-based environment digest path respects the intended memory/refinement boundary."] : Uint256 := do - let ts := blockTimestamp - let bn := blockNumber - let self := contractAddress - let cid := chainid + let ts ← blockTimestamp + let bn ← blockNumber + let self ← contractAddress + let cid ← chainid let flagAnd := logicalAnd x y let flagOr := logicalOr x y let flagNot := logicalNot x diff --git a/Contracts/ERC20/ERC20.lean b/Contracts/ERC20/ERC20.lean index 4e65bafc1..50132ab01 100644 --- a/Contracts/ERC20/ERC20.lean +++ b/Contracts/ERC20/ERC20.lean @@ -17,35 +17,35 @@ verity_contract ERC20 where setStorageAddr ownerSlot initialOwner setStorage totalSupplySlot 0 - function mint (to : Address, amount : Uint256) : Unit := do + function mint (toAddr : Address, amount : Uint256) : Unit := do let sender ← msgSender let currentOwner ← getStorageAddr ownerSlot require (sender == currentOwner) "Caller is not the owner" - let currentBalance ← getMapping balancesSlot to + let currentBalance ← getMapping balancesSlot toAddr let newBalance ← requireSomeUint (safeAdd currentBalance amount) "Balance overflow" let currentSupply ← getStorage totalSupplySlot let newSupply ← requireSomeUint (safeAdd currentSupply amount) "Supply overflow" - setMapping balancesSlot to newBalance + setMapping balancesSlot toAddr newBalance setStorage totalSupplySlot newSupply - function transfer (to : Address, amount : Uint256) : Unit := do + function transfer (toAddr : Address, amount : Uint256) : Unit := do let sender ← msgSender let senderBalance ← getMapping balancesSlot sender require (senderBalance >= amount) "Insufficient balance" - if sender == to then + if sender == toAddr then pure () else - let recipientBalance ← getMapping balancesSlot to + let recipientBalance ← getMapping balancesSlot toAddr let newRecipientBalance ← requireSomeUint (safeAdd recipientBalance amount) "Recipient balance overflow" setMapping balancesSlot sender (sub senderBalance amount) - setMapping balancesSlot to newRecipientBalance + setMapping balancesSlot toAddr newRecipientBalance function approve (spender : Address, amount : Uint256) : Unit := do let sender ← msgSender setMapping2 allowancesSlot sender spender amount - function transferFrom (fromAddr : Address, to : Address, amount : Uint256) : Unit := do + function transferFrom (fromAddr : Address, toAddr : Address, amount : Uint256) : Unit := do let spender ← msgSender let currentAllowance ← getMapping2 allowancesSlot fromAddr spender require (currentAllowance >= amount) "Insufficient allowance" @@ -53,13 +53,13 @@ verity_contract ERC20 where let fromBalance ← getMapping balancesSlot fromAddr require (fromBalance >= amount) "Insufficient balance" - if fromAddr == to then + if fromAddr == toAddr then pure () else - let toBalance ← getMapping balancesSlot to + let toBalance ← getMapping balancesSlot toAddr let newToBalance ← requireSomeUint (safeAdd toBalance amount) "Recipient balance overflow" setMapping balancesSlot fromAddr (sub fromBalance amount) - setMapping balancesSlot to newToBalance + setMapping balancesSlot toAddr newToBalance if currentAllowance == 115792089237316195423570985008687907853269984665640564039457584007913129639935 then pure () diff --git a/Contracts/ERC20/Proofs/Basic.lean b/Contracts/ERC20/Proofs/Basic.lean index b3be79afc..e3961804d 100644 --- a/Contracts/ERC20/Proofs/Basic.lean +++ b/Contracts/ERC20/Proofs/Basic.lean @@ -87,17 +87,17 @@ theorem getOwner_meets_spec (s : ContractState) : Verity.pure, Pure.pure, ownerSlot] /-- Helper: unfold `mint` on the successful owner/non-overflow path. -/ -private theorem mint_unfold (s : ContractState) (to : Address) (amount : Uint256) +private theorem mint_unfold (s : ContractState) (toAddr : Address) (amount : Uint256) (h_owner : s.sender = s.storageAddr 0) - (h_no_bal_overflow : (s.storageMap 2 to : Nat) + (amount : Nat) ≤ MAX_UINT256) + (h_no_bal_overflow : (s.storageMap 2 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) (h_no_sup_overflow : (s.storage 1 : Nat) + (amount : Nat) ≤ MAX_UINT256) : - (mint to amount).run s = ContractResult.success () + (mint toAddr amount).run s = ContractResult.success () { «storage» := fun slotIdx => if slotIdx == 1 then EVM.Uint256.add (s.storage 1) amount else s.storage slotIdx, transientStorage := s.transientStorage, storageAddr := s.storageAddr, storageMap := fun slotIdx addr => - if (slotIdx == 2 && addr == to) = true then EVM.Uint256.add (s.storageMap 2 to) amount + if (slotIdx == 2 && addr == toAddr) = true then EVM.Uint256.add (s.storageMap 2 toAddr) amount else s.storageMap slotIdx addr, storageMapUint := s.storageMapUint, storageMap2 := s.storageMap2, @@ -113,9 +113,9 @@ private theorem mint_unfold (s : ContractState) (to : Address) (amount : Uint256 calldata := s.calldata, memory := s.memory, knownAddresses := fun slotIdx => - if slotIdx == 2 then (s.knownAddresses slotIdx).insert to else s.knownAddresses slotIdx, + if slotIdx == 2 then (s.knownAddresses slotIdx).insert toAddr else s.knownAddresses slotIdx, events := s.events } := by - have h_safe_bal := safeAdd_some (s.storageMap 2 to) amount h_no_bal_overflow + have h_safe_bal := safeAdd_some (s.storageMap 2 toAddr) amount h_no_bal_overflow have h_safe_sup := safeAdd_some (s.storage 1) amount h_no_sup_overflow verity_unfold mint simp only [ownerSlot, balancesSlot, totalSupplySlot, @@ -128,12 +128,12 @@ private theorem mint_unfold (s : ContractState) (to : Address) (amount : Uint256 simp only [HAdd.hAdd, h_owner] /-- `mint` satisfies `mint_spec` under owner and no-overflow preconditions. -/ -theorem mint_meets_spec_when_owner (s : ContractState) (to : Address) (amount : Uint256) +theorem mint_meets_spec_when_owner (s : ContractState) (toAddr : Address) (amount : Uint256) (h_owner : s.sender = s.storageAddr 0) - (h_no_bal_overflow : (s.storageMap 2 to : Nat) + (amount : Nat) ≤ MAX_UINT256) + (h_no_bal_overflow : (s.storageMap 2 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) (h_no_sup_overflow : (s.storage 1 : Nat) + (amount : Nat) ≤ MAX_UINT256) : - mint_spec to amount s ((mint to amount).runState s) := by - have h_unfold := mint_unfold s to amount h_owner h_no_bal_overflow h_no_sup_overflow + mint_spec toAddr amount s ((mint toAddr amount).runState s) := by + have h_unfold := mint_unfold s toAddr amount h_owner h_no_bal_overflow h_no_sup_overflow have h_unfold_apply := Contract.eq_of_run_success h_unfold simp only [Contract.runState, mint_spec] rw [h_unfold_apply] @@ -153,43 +153,43 @@ theorem mint_meets_spec_when_owner (s : ContractState) (to : Address) (amount : · exact Specs.sameContext_rfl _ /-- Under successful-owner assumptions, `mint` increases recipient balance by `amount`. -/ -theorem mint_increases_balance_when_owner (s : ContractState) (to : Address) (amount : Uint256) +theorem mint_increases_balance_when_owner (s : ContractState) (toAddr : Address) (amount : Uint256) (h_owner : s.sender = s.storageAddr 0) - (h_no_bal_overflow : (s.storageMap 2 to : Nat) + (amount : Nat) ≤ MAX_UINT256) + (h_no_bal_overflow : (s.storageMap 2 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) (h_no_sup_overflow : (s.storage 1 : Nat) + (amount : Nat) ≤ MAX_UINT256) : - ((mint to amount).runState s).storageMap 2 to = EVM.Uint256.add (s.storageMap 2 to) amount := by - have h := mint_meets_spec_when_owner s to amount h_owner h_no_bal_overflow h_no_sup_overflow + ((mint toAddr amount).runState s).storageMap 2 toAddr = EVM.Uint256.add (s.storageMap 2 toAddr) amount := by + have h := mint_meets_spec_when_owner s toAddr amount h_owner h_no_bal_overflow h_no_sup_overflow exact h.1 /-- Under successful-owner assumptions, `mint` increases total supply by `amount`. -/ -theorem mint_increases_supply_when_owner (s : ContractState) (to : Address) (amount : Uint256) +theorem mint_increases_supply_when_owner (s : ContractState) (toAddr : Address) (amount : Uint256) (h_owner : s.sender = s.storageAddr 0) - (h_no_bal_overflow : (s.storageMap 2 to : Nat) + (amount : Nat) ≤ MAX_UINT256) + (h_no_bal_overflow : (s.storageMap 2 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) (h_no_sup_overflow : (s.storage 1 : Nat) + (amount : Nat) ≤ MAX_UINT256) : - ((mint to amount).runState s).storage 1 = EVM.Uint256.add (s.storage 1) amount := by - have h := mint_meets_spec_when_owner s to amount h_owner h_no_bal_overflow h_no_sup_overflow + ((mint toAddr amount).runState s).storage 1 = EVM.Uint256.add (s.storage 1) amount := by + have h := mint_meets_spec_when_owner s toAddr amount h_owner h_no_bal_overflow h_no_sup_overflow exact h.2.1 /-- Helper: unfold `transfer` on the successful self-transfer path. -/ -private theorem transfer_unfold_self (s : ContractState) (to : Address) (amount : Uint256) +private theorem transfer_unfold_self (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 2 s.sender ≥ amount) - (h_eq : s.sender = to) : - (transfer to amount).run s = ContractResult.success () s := by + (h_eq : s.sender = toAddr) : + (transfer toAddr amount).run s = ContractResult.success () s := by have h_balance' := uint256_ge_val_le (h_eq ▸ h_balance) verity_unfold transfer simp [balancesSlot, Verity.pure, h_balance', h_eq] /-- Helper: unfold `transfer` on the successful non-self path with no overflow. -/ -private theorem transfer_unfold_other (s : ContractState) (to : Address) (amount : Uint256) +private theorem transfer_unfold_other (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 2 s.sender ≥ amount) - (h_ne : s.sender ≠ to) - (h_no_overflow : (s.storageMap 2 to : Nat) + (amount : Nat) ≤ MAX_UINT256) : - (transfer to amount).run s = ContractResult.success () + (h_ne : s.sender ≠ toAddr) + (h_no_overflow : (s.storageMap 2 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) : + (transfer toAddr amount).run s = ContractResult.success () { «storage» := s.storage, transientStorage := s.transientStorage, storageAddr := s.storageAddr, storageMap := fun slotIdx addr => - if (slotIdx == 2 && addr == to) = true then EVM.Uint256.add (s.storageMap 2 to) amount + if (slotIdx == 2 && addr == toAddr) = true then EVM.Uint256.add (s.storageMap 2 toAddr) amount else if (slotIdx == 2 && addr == s.sender) = true then EVM.Uint256.sub (s.storageMap 2 s.sender) amount else s.storageMap slotIdx addr, storageMapUint := s.storageMapUint, @@ -206,11 +206,11 @@ private theorem transfer_unfold_other (s : ContractState) (to : Address) (amount memory := s.memory, calldata := s.calldata, knownAddresses := fun slotIdx => - if slotIdx == 2 then ((s.knownAddresses slotIdx).insert s.sender).insert to + if slotIdx == 2 then ((s.knownAddresses slotIdx).insert s.sender).insert toAddr else s.knownAddresses slotIdx, events := s.events } := by have h_balance' := uint256_ge_val_le h_balance - have h_safe := safeAdd_some (s.storageMap 2 to) amount h_no_overflow + have h_safe := safeAdd_some (s.storageMap 2 toAddr) amount h_no_overflow simp only [transfer, balancesSlot, msgSender, getMapping, setMapping, requireSomeUint, Verity.require, Verity.pure, Verity.bind, Bind.bind, Pure.pure, Contract.run, h_balance, h_ne, beq_iff_eq, h_safe, decide_eq_true_eq, @@ -221,12 +221,12 @@ private theorem transfer_unfold_other (s : ContractState) (to : Address) (amount split <;> simp [*] /-- `transfer` satisfies `transfer_spec` under balance/overflow preconditions. -/ -theorem transfer_meets_spec_when_sufficient (s : ContractState) (to : Address) (amount : Uint256) +theorem transfer_meets_spec_when_sufficient (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 2 s.sender ≥ amount) - (h_no_overflow : s.sender ≠ to → (s.storageMap 2 to : Nat) + (amount : Nat) ≤ MAX_UINT256) : - transfer_spec s.sender to amount s ((transfer to amount).runState s) := by - by_cases h_eq : s.sender = to - · have h_unfold := transfer_unfold_self s to amount h_balance h_eq + (h_no_overflow : s.sender ≠ toAddr → (s.storageMap 2 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) : + transfer_spec s.sender toAddr amount s ((transfer toAddr amount).runState s) := by + by_cases h_eq : s.sender = toAddr + · have h_unfold := transfer_unfold_self s toAddr amount h_balance h_eq have h_unfold_apply := Contract.eq_of_run_success h_unfold simp only [Contract.runState, transfer_spec] rw [h_unfold_apply] @@ -240,11 +240,11 @@ theorem transfer_meets_spec_when_sufficient (s : ContractState) (to : Address) ( · rfl · rfl · exact Specs.sameContext_rfl _ - · have h_unfold := transfer_unfold_other s to amount h_balance h_eq (h_no_overflow h_eq) + · have h_unfold := transfer_unfold_other s toAddr amount h_balance h_eq (h_no_overflow h_eq) have h_unfold_apply := Contract.eq_of_run_success h_unfold simp only [Contract.runState, transfer_spec] rw [h_unfold_apply] - have h_ne' := address_beq_false_of_ne s.sender to h_eq + have h_ne' := address_beq_false_of_ne s.sender toAddr h_eq refine ⟨h_balance, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ · simp [h_ne'] · simp [h_ne'] @@ -262,25 +262,25 @@ theorem transfer_meets_spec_when_sufficient (s : ContractState) (to : Address) ( /-- On successful non-self transfer, sender balance decreases by `amount`. -/ theorem transfer_decreases_sender_balance_when_sufficient - (s : ContractState) (to : Address) (amount : Uint256) + (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 2 s.sender ≥ amount) - (h_ne : s.sender ≠ to) - (h_no_overflow : (s.storageMap 2 to : Nat) + (amount : Nat) ≤ MAX_UINT256) : - ((transfer to amount).runState s).storageMap 2 s.sender = + (h_ne : s.sender ≠ toAddr) + (h_no_overflow : (s.storageMap 2 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) : + ((transfer toAddr amount).runState s).storageMap 2 s.sender = EVM.Uint256.sub (s.storageMap 2 s.sender) amount := by - have h := transfer_meets_spec_when_sufficient s to amount h_balance (fun _ => h_no_overflow) + have h := transfer_meets_spec_when_sufficient s toAddr amount h_balance (fun _ => h_no_overflow) simp [transfer_spec, h_ne, beq_iff_eq] at h simpa [Contract.runState_eq_snd_run] using h.2.1 /-- On successful non-self transfer, recipient balance increases by `amount`. -/ theorem transfer_increases_recipient_balance_when_sufficient - (s : ContractState) (to : Address) (amount : Uint256) + (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 2 s.sender ≥ amount) - (h_ne : s.sender ≠ to) - (h_no_overflow : (s.storageMap 2 to : Nat) + (amount : Nat) ≤ MAX_UINT256) : - ((transfer to amount).runState s).storageMap 2 to = - EVM.Uint256.add (s.storageMap 2 to) amount := by - have h := transfer_meets_spec_when_sufficient s to amount h_balance (fun _ => h_no_overflow) + (h_ne : s.sender ≠ toAddr) + (h_no_overflow : (s.storageMap 2 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) : + ((transfer toAddr amount).runState s).storageMap 2 toAddr = + EVM.Uint256.add (s.storageMap 2 toAddr) amount := by + have h := transfer_meets_spec_when_sufficient s toAddr amount h_balance (fun _ => h_no_overflow) simp [transfer_spec, h_ne, beq_iff_eq] at h simpa [Contract.runState_eq_snd_run] using h.2.2.1 diff --git a/Contracts/ERC20/Proofs/Correctness.lean b/Contracts/ERC20/Proofs/Correctness.lean index 80ccc18ff..58ce97e4a 100644 --- a/Contracts/ERC20/Proofs/Correctness.lean +++ b/Contracts/ERC20/Proofs/Correctness.lean @@ -45,22 +45,22 @@ theorem approve_is_balance_neutral_holds (s : ContractState) (spender : Address) /-- `transfer` preserves total supply under successful-path preconditions. -/ theorem transfer_preserves_totalSupply_when_sufficient - (s : ContractState) (to : Address) (amount : Uint256) + (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 2 s.sender ≥ amount) - (h_no_overflow : s.sender ≠ to → - (s.storageMap 2 to : Nat) + (amount : Nat) ≤ Verity.Stdlib.Math.MAX_UINT256) : - ((Contracts.ERC20.transfer to amount).runState s).storage 1 = s.storage 1 := by - have h := transfer_meets_spec_when_sufficient s to amount h_balance h_no_overflow + (h_no_overflow : s.sender ≠ toAddr → + (s.storageMap 2 toAddr : Nat) + (amount : Nat) ≤ Verity.Stdlib.Math.MAX_UINT256) : + ((Contracts.ERC20.transfer toAddr amount).runState s).storage 1 = s.storage 1 := by + have h := transfer_meets_spec_when_sufficient s toAddr amount h_balance h_no_overflow exact h.2.2.2.2.1 /-- `transfer` preserves owner storage under successful-path preconditions. -/ theorem transfer_preserves_owner_when_sufficient - (s : ContractState) (to : Address) (amount : Uint256) + (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 2 s.sender ≥ amount) - (h_no_overflow : s.sender ≠ to → - (s.storageMap 2 to : Nat) + (amount : Nat) ≤ Verity.Stdlib.Math.MAX_UINT256) : - ((Contracts.ERC20.transfer to amount).runState s).storageAddr 0 = s.storageAddr 0 := by - have h := transfer_meets_spec_when_sufficient s to amount h_balance h_no_overflow + (h_no_overflow : s.sender ≠ toAddr → + (s.storageMap 2 toAddr : Nat) + (amount : Nat) ≤ Verity.Stdlib.Math.MAX_UINT256) : + ((Contracts.ERC20.transfer toAddr amount).runState s).storageAddr 0 = s.storageAddr 0 := by + have h := transfer_meets_spec_when_sufficient s toAddr amount h_balance h_no_overflow exact h.2.2.2.2.2.1 end Contracts.ERC20.Proofs diff --git a/Contracts/ERC20/Spec.lean b/Contracts/ERC20/Spec.lean index 0717cda99..22176eaad 100644 --- a/Contracts/ERC20/Spec.lean +++ b/Contracts/ERC20/Spec.lean @@ -14,16 +14,16 @@ open Verity.Specs /-! ## Operation Specifications -/ --- constructor: sets owner and initializes total supply to zero +-- constructor: sets owner and initializes total supply toAddr zero #gen_spec_addr_storage constructor_spec for (initialOwner : Address) (0, 1, (fun _ => initialOwner), (fun _ => 0), sameStorageMap2Context) -- mint: increases recipient balance and total supply by amount -#gen_spec_map_storage mint_spec for (to : Address) (amount : Uint256) - (2, to, (fun st => add (st.storageMap 2 to) amount), 1, (fun st => add (st.storage 1) amount), sameStorageAddrSlotMap2Context 0) +#gen_spec_map_storage mint_spec for (toAddr : Address) (amount : Uint256) + (2, toAddr, (fun st => add (st.storageMap 2 toAddr) amount), 1, (fun st => add (st.storage 1) amount), sameStorageAddrSlotMap2Context 0) #gen_spec_transfer transfer_spec for3 - (sender : Address) (to : Address) (amount : Uint256) + (sender : Address) (toAddr : Address) (amount : Uint256) (2, sameStorageSlotAddrSlotMap2Context 1 0) -- approve: updates the owner-spender allowance mapping entry @@ -31,7 +31,7 @@ open Verity.Specs (3, ownerAddr, spender, (fun _ => amount), sameStorageAddrMapContext) #gen_spec_transfer_from transferFrom_spec for4 - (spender : Address) (fromAddr : Address) (to : Address) (amount : Uint256) + (spender : Address) (fromAddr : Address) (toAddr : Address) (amount : Uint256) (2, 3, sameStorageAddrContext) /-- balanceOf: returns current balance of `addr` -/ diff --git a/Contracts/ERC721/ERC721.lean b/Contracts/ERC721/ERC721.lean index e534b64b8..67779eacd 100644 --- a/Contracts/ERC721/ERC721.lean +++ b/Contracts/ERC721/ERC721.lean @@ -55,31 +55,31 @@ verity_contract ERC721 where let sender ← msgSender setMapping2 operatorApprovals sender operator (boolToWord approved) - function mint (to : Address) : Uint256 := do + function mint (toAddr : Address) : Uint256 := do let sender ← msgSender let currentOwner ← getStorageAddr owner require (sender == currentOwner) "Caller is not the owner" - require (to != zeroAddress) "Invalid recipient" + require (toAddr != zeroAddress) "Invalid recipient" let tokenId ← getStorage nextTokenId let currentOwnerWord ← getMappingUint owners tokenId require (currentOwnerWord == 0) "Token already minted" - let recipientBalance ← getMapping balances to + let recipientBalance ← getMapping balances toAddr let newRecipientBalance ← requireSomeUint (safeAdd recipientBalance 1) "Balance overflow" let currentSupply ← getStorage totalSupply let newSupply ← requireSomeUint (safeAdd currentSupply 1) "Supply overflow" - setMappingUintAddr owners tokenId to - setMapping balances to newRecipientBalance + setMappingUintAddr owners tokenId toAddr + setMapping balances toAddr newRecipientBalance setStorage totalSupply newSupply setStorage nextTokenId (add tokenId 1) return tokenId - function transferFrom (fromAddr : Address, to : Address, tokenId : Uint256) : Unit := do + function transferFrom (fromAddr : Address, toAddr : Address, tokenId : Uint256) : Unit := do let sender ← msgSender - require (to != zeroAddress) "Invalid recipient" + require (toAddr != zeroAddress) "Invalid recipient" let ownerWord ← getMappingUint owners tokenId require (ownerWord != 0) "Token does not exist" @@ -93,17 +93,17 @@ verity_contract ERC721 where let authorized := (sender == fromAddr) || (approvedWord == senderWord) || (operatorWord != 0) require authorized "Not authorized" - if fromAddr == to then + if fromAddr == toAddr then pure () else let fromBalance ← getMapping balances fromAddr require (fromBalance >= 1) "Insufficient balance" - let toBalance ← getMapping balances to + let toBalance ← getMapping balances toAddr let newToBalance ← requireSomeUint (safeAdd toBalance 1) "Balance overflow" setMapping balances fromAddr (sub fromBalance 1) - setMapping balances to newToBalance + setMapping balances toAddr newToBalance - setMappingUintAddr owners tokenId to + setMappingUintAddr owners tokenId toAddr setMappingUintAddr tokenApprovals tokenId zeroAddress namespace ERC721 diff --git a/Contracts/ERC721/Spec.lean b/Contracts/ERC721/Spec.lean index 9829c8a11..c864725b6 100644 --- a/Contracts/ERC721/Spec.lean +++ b/Contracts/ERC721/Spec.lean @@ -14,7 +14,7 @@ open Verity.Specs def boolToWord (b : Bool) : Uint256 := if b then 1 else 0 -/-- constructor: sets owner and initializes counters to zero -/ +/-- constructor: sets owner and initializes counters toAddr zero -/ def constructor_spec (initialOwner : Address) (s s' : ContractState) : Prop := storageAddrStorage2UpdateSpec 0 1 2 diff --git a/Contracts/Interpreter.lean b/Contracts/Interpreter.lean index 8ecd35acc..42257aa36 100644 --- a/Contracts/Interpreter.lean +++ b/Contracts/Interpreter.lean @@ -679,7 +679,7 @@ private def parseArgNat? (s : String) : Option Nat := -- Generic parser for "slot:value,slot:value,..." storage formats. -- Takes a value parser and default, returns a slot→value lookup function. -private def parseSlotPairs (storageStr : String) (parseVal : String → Option α) (default : α) : Nat → α := +private def parseSlotPairs (storageStr : String) (parseVal : String → Option α) (defaultValue : α) : Nat → α := let pairs := storageStr.splitOn "," let entries := pairs.foldl (fun acc pair => if pair.isEmpty then acc @@ -690,7 +690,7 @@ private def parseSlotPairs (storageStr : String) (parseVal : String → Option | _, _ => acc | _ => acc ) [] - fun slotIdx => (entries.find? (fun (s, _) => s == slotIdx)).map Prod.snd |>.getD default + fun slotIdx => (entries.find? (fun (s, _) => s == slotIdx)).map Prod.snd |>.getD defaultValue -- Parse storage state: "slot0:value0,slot1:value1,..." def parseStorage (storageStr : String) : Nat → Uint256 := diff --git a/Contracts/Ledger/Ledger.lean b/Contracts/Ledger/Ledger.lean index 21f9d7399..731177d87 100644 --- a/Contracts/Ledger/Ledger.lean +++ b/Contracts/Ledger/Ledger.lean @@ -21,17 +21,17 @@ verity_contract Ledger where require (currentBalance >= amount) "Insufficient balance" setMapping balances sender (sub currentBalance amount) - function transfer (to : Address, amount : Uint256) : Unit := do + function transfer (toAddr : Address, amount : Uint256) : Unit := do let sender ← msgSender let senderBalance ← getMapping balances sender require (senderBalance >= amount) "Insufficient balance" - if sender == to then + if sender == toAddr then pure () else - let recipientBalance ← getMapping balances to + let recipientBalance ← getMapping balances toAddr setMapping balances sender (sub senderBalance amount) - setMapping balances to (add recipientBalance amount) + setMapping balances toAddr (add recipientBalance amount) function getBalance (addr : Address) : Uint256 := do let currentBalance ← getMapping balances addr diff --git a/Contracts/Ledger/Proofs/Basic.lean b/Contracts/Ledger/Proofs/Basic.lean index 0728a8c1e..6d13d8407 100644 --- a/Contracts/Ledger/Proofs/Basic.lean +++ b/Contracts/Ledger/Proofs/Basic.lean @@ -4,7 +4,7 @@ Proves mapping-based balance operations: - deposit increases sender balance - withdraw decreases sender balance (guarded) - - transfer moves between accounts (guarded, sender ≠ to, no overflow) + - transfer moves between accounts (guarded, sender ≠ toAddr, no overflow) - getBalance returns correct value - All operations preserve non-mapping storage -/ @@ -161,24 +161,24 @@ theorem withdraw_reverts_insufficient (s : ContractState) (amount : Uint256) /-! ## Transfer Correctness -/ -/-- Helper: unfold transfer when balance sufficient and sender == to (no-op). -/ -private theorem transfer_unfold_self (s : ContractState) (to : Address) (amount : Uint256) +/-- Helper: unfold transfer when balance sufficient and sender == toAddr (no-op). -/ +private theorem transfer_unfold_self (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 0 s.sender >= amount) - (h_eq : s.sender = to) : - (transfer to amount).run s = ContractResult.success () s := by + (h_eq : s.sender = toAddr) : + (transfer toAddr amount).run s = ContractResult.success () s := by verity_unfold transfer simp [balances, Verity.pure, uint256_ge_val_le (h_eq ▸ h_balance), h_eq] -/-- Helper: unfold transfer when balance sufficient and sender ≠ to. -/ -private theorem transfer_unfold_other (s : ContractState) (to : Address) (amount : Uint256) +/-- Helper: unfold transfer when balance sufficient and sender ≠ toAddr. -/ +private theorem transfer_unfold_other (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 0 s.sender >= amount) - (h_ne : s.sender ≠ to) : - (transfer to amount).run s = ContractResult.success () + (h_ne : s.sender ≠ toAddr) : + (transfer toAddr amount).run s = ContractResult.success () { «storage» := s.storage, transientStorage := s.transientStorage, storageAddr := s.storageAddr, storageMap := fun slotIdx addr => - if (slotIdx == 0 && addr == to) = true then EVM.Uint256.add (s.storageMap 0 to) amount + if (slotIdx == 0 && addr == toAddr) = true then EVM.Uint256.add (s.storageMap 0 toAddr) amount else if (slotIdx == 0 && addr == s.sender) = true then EVM.Uint256.sub (s.storageMap 0 s.sender) amount else s.storageMap slotIdx addr, storageMapUint := s.storageMapUint, @@ -195,7 +195,7 @@ private theorem transfer_unfold_other (s : ContractState) (to : Address) (amount calldata := s.calldata, memory := s.memory, knownAddresses := fun slotIdx => - if slotIdx == 0 then ((s.knownAddresses slotIdx).insert s.sender).insert to + if slotIdx == 0 then ((s.knownAddresses slotIdx).insert s.sender).insert toAddr else s.knownAddresses slotIdx, events := s.events } := by simp only [transfer, Contracts.Ledger.balances, @@ -208,12 +208,12 @@ private theorem transfer_unfold_other (s : ContractState) (to : Address) (amount funext slotIdx split <;> simp [*] -theorem transfer_meets_spec (s : ContractState) (to : Address) (amount : Uint256) +theorem transfer_meets_spec (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 0 s.sender >= amount) : - let s' := ((transfer to amount).run s).snd - transfer_spec to amount s s' := by - by_cases h_eq : s.sender = to - · rw [transfer_unfold_self s to amount h_balance h_eq] + let s' := ((transfer toAddr amount).run s).snd + transfer_spec toAddr amount s s' := by + by_cases h_eq : s.sender = toAddr + · rw [transfer_unfold_self s toAddr amount h_balance h_eq] simp only [ContractResult.snd, transfer_spec] refine ⟨?_, ?_, ?_, ?_⟩ · simp [h_eq] @@ -221,9 +221,9 @@ theorem transfer_meets_spec (s : ContractState) (to : Address) (amount : Uint256 · simp [h_eq, Specs.storageMapUnchangedExceptKeyAtSlot, Specs.storageMapUnchangedExceptKey, Specs.storageMapUnchangedExceptSlot] · simp [Specs.sameStorageAddrContext, Specs.sameStorage, Specs.sameStorageAddr, Specs.sameStorageArray, Specs.sameContext] - · rw [transfer_unfold_other s to amount h_balance h_eq] + · rw [transfer_unfold_other s toAddr amount h_balance h_eq] simp only [ContractResult.snd, transfer_spec] - have h_ne' := address_beq_false_of_ne s.sender to h_eq + have h_ne' := address_beq_false_of_ne s.sender toAddr h_eq refine ⟨?_, ?_, ?_, ?_⟩ · simp [h_ne'] · simp [h_ne'] @@ -243,44 +243,44 @@ theorem transfer_self_preserves_balance (s : ContractState) (amount : Uint256) simp [transfer_spec] at h exact h.1 -theorem transfer_decreases_sender (s : ContractState) (to : Address) (amount : Uint256) +theorem transfer_decreases_sender (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 0 s.sender >= amount) - (h_ne : s.sender ≠ to) : - let s' := ((transfer to amount).run s).snd + (h_ne : s.sender ≠ toAddr) : + let s' := ((transfer toAddr amount).run s).snd s'.storageMap 0 s.sender = EVM.Uint256.sub (s.storageMap 0 s.sender) amount := by - have h := transfer_meets_spec s to amount h_balance + have h := transfer_meets_spec s toAddr amount h_balance simp [transfer_spec, h_ne, beq_iff_eq] at h exact h.1 -theorem transfer_increases_recipient (s : ContractState) (to : Address) (amount : Uint256) +theorem transfer_increases_recipient (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 0 s.sender >= amount) - (h_ne : s.sender ≠ to) : - let s' := ((transfer to amount).run s).snd - s'.storageMap 0 to = EVM.Uint256.add (s.storageMap 0 to) amount := by - have h := transfer_meets_spec s to amount h_balance + (h_ne : s.sender ≠ toAddr) : + let s' := ((transfer toAddr amount).run s).snd + s'.storageMap 0 toAddr = EVM.Uint256.add (s.storageMap 0 toAddr) amount := by + have h := transfer_meets_spec s toAddr amount h_balance simp [transfer_spec, h_ne, beq_iff_eq] at h exact h.2.1 -theorem transfer_reverts_insufficient (s : ContractState) (to : Address) (amount : Uint256) +theorem transfer_reverts_insufficient (s : ContractState) (toAddr : Address) (amount : Uint256) (h_insufficient : ¬(s.storageMap 0 s.sender >= amount)) : - ∃ msg, (transfer to amount).run s = ContractResult.revert msg s := by + ∃ msg, (transfer toAddr amount).run s = ContractResult.revert msg s := by simp [transfer, msgSender, getMapping, balances, Verity.require, Verity.bind, Bind.bind, Contract.run, show (s.storageMap 0 s.sender >= amount) = false from by simp [ge_iff_le] at h_insufficient ⊢; omega] -- Transfer does not revert on recipient overflow; Uint256 addition wraps. -theorem transfer_succeeds_recipient_overflow (s : ContractState) (to : Address) (amount : Uint256) +theorem transfer_succeeds_recipient_overflow (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 0 s.sender >= amount) - (h_ne : s.sender ≠ to) - (_h_overflow : (s.storageMap 0 to : Nat) + (amount : Nat) > MAX_UINT256) : - ∃ s', (transfer to amount).run s = ContractResult.success () s' := by + (h_ne : s.sender ≠ toAddr) + (_h_overflow : (s.storageMap 0 toAddr : Nat) + (amount : Nat) > MAX_UINT256) : + ∃ s', (transfer toAddr amount).run s = ContractResult.success () s' := by let s' : ContractState := { «storage» := s.storage, transientStorage := s.transientStorage, storageAddr := s.storageAddr, storageMap := fun slotIdx addr => - if (slotIdx == 0 && addr == to) = true then EVM.Uint256.add (s.storageMap 0 to) amount + if (slotIdx == 0 && addr == toAddr) = true then EVM.Uint256.add (s.storageMap 0 toAddr) amount else if (slotIdx == 0 && addr == s.sender) = true then EVM.Uint256.sub (s.storageMap 0 s.sender) amount else s.storageMap slotIdx addr, storageMapUint := s.storageMapUint, @@ -297,11 +297,11 @@ theorem transfer_succeeds_recipient_overflow (s : ContractState) (to : Address) calldata := s.calldata, memory := s.memory, knownAddresses := fun slotIdx => - if slotIdx == 0 then ((s.knownAddresses slotIdx).insert s.sender).insert to + if slotIdx == 0 then ((s.knownAddresses slotIdx).insert s.sender).insert toAddr else s.knownAddresses slotIdx, events := s.events } refine ⟨s', ?_⟩ - simpa [s'] using transfer_unfold_other s to amount h_balance h_ne + simpa [s'] using transfer_unfold_other s toAddr amount h_balance h_ne /-! ## State Preservation -/ @@ -363,7 +363,7 @@ Withdraw (guarded): 8. withdraw_decreases_balance 9. withdraw_reverts_insufficient -Transfer (guarded, sender ≠ to): +Transfer (guarded, sender ≠ toAddr): 10. transfer_meets_spec 11. transfer_decreases_sender 12. transfer_increases_recipient diff --git a/Contracts/Ledger/Proofs/Conservation.lean b/Contracts/Ledger/Proofs/Conservation.lean index 5ecbcf49b..42bb5de11 100644 --- a/Contracts/Ledger/Proofs/Conservation.lean +++ b/Contracts/Ledger/Proofs/Conservation.lean @@ -5,7 +5,7 @@ across any list of addresses: 1. Deposit: new_sum = old_sum + countOcc(sender, addrs) * amount 2. Withdraw: new_sum + countOcc(sender, addrs) * amount = old_sum (when guarded) - 3. Transfer: new_sum + countOcc(sender, addrs) * amount = old_sum + countOcc(to, addrs) * amount + 3. Transfer: new_sum + countOcc(sender, addrs) * amount = old_sum + countOcc(toAddr, addrs) * amount These are the strongest correct statements about sum changes. The countOcc factor accounts for addresses appearing multiple times in the list. @@ -28,7 +28,7 @@ open Verity.Proofs.Stdlib.Automation (evm_add_eq_hadd) /-- Exact sum relationship after deposit: the new sum equals the old sum plus count(sender, addrs) * amount. Each occurrence of the sender in the list - contributes an additional `amount` to the sum. -/ + contributes an additional `amount` toAddr the sum. -/ theorem deposit_sum_equation (s : ContractState) (amount : Uint256) : ∀ addrs : List Address, @@ -56,7 +56,7 @@ theorem deposit_sum_singleton_sender (s : ContractState) (amount : Uint256) /-- Exact sum relationship after withdraw (when balance sufficient): new_sum + count(sender, addrs) * amount = old_sum. - Each occurrence of the sender in the list contributes `amount` less to the new sum. -/ + Each occurrence of the sender in the list contributes `amount` less toAddr the new sum. -/ theorem withdraw_sum_equation (s : ContractState) (amount : Uint256) (h_balance : s.storageMap 0 s.sender >= amount) : ∀ addrs : List Address, @@ -88,45 +88,45 @@ theorem withdraw_sum_singleton_sender (s : ContractState) (amount : Uint256) /-! ## Transfer: Exact Sum Conservation Equation -/ /-- Exact sum conservation equation for transfer: - new_sum + count(sender, addrs) * amount = old_sum + count(to, addrs) * amount. + new_sum + count(sender, addrs) * amount = old_sum + count(toAddr, addrs) * amount. This is the fundamental conservation law for Ledger transfer: each occurrence of the sender in the list loses `amount`, and each occurrence of the recipient gains `amount`. The equation holds exactly (not just as an inequality). -/ -theorem transfer_sum_equation (s : ContractState) (to : Address) (amount : Uint256) +theorem transfer_sum_equation (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 0 s.sender >= amount) - (h_ne : s.sender ≠ to) : + (h_ne : s.sender ≠ toAddr) : ∀ addrs : List Address, - (addrs.map (fun addr => ((transfer to amount).run s).snd.storageMap 0 addr)).sum + (addrs.map (fun addr => ((transfer toAddr amount).run s).snd.storageMap 0 addr)).sum + countOccU s.sender addrs * amount = (addrs.map (fun addr => s.storageMap 0 addr)).sum - + countOccU to addrs * amount := by - have h_spec := transfer_meets_spec s to amount h_balance + + countOccU toAddr addrs * amount := by + have h_spec := transfer_meets_spec s toAddr amount h_balance simp [transfer_spec, h_ne, beq_iff_eq] at h_spec obtain ⟨h_sender_bal, h_recip_bal, h_other_bal, _, _, _⟩ := h_spec have h_recip_bal' : - ((transfer to amount).run s).snd.storageMap 0 to = - s.storageMap 0 to + amount := by + ((transfer toAddr amount).run s).snd.storageMap 0 toAddr = + s.storageMap 0 toAddr + amount := by simpa [evm_add_eq_hadd] using h_recip_bal exact map_sum_transfer_eq (fun addr => s.storageMap 0 addr) - (fun addr => ((transfer to amount).run s).snd.storageMap 0 addr) - s.sender to amount h_ne h_sender_bal h_recip_bal' + (fun addr => ((transfer toAddr amount).run s).snd.storageMap 0 addr) + s.sender toAddr amount h_ne h_sender_bal h_recip_bal' h_other_bal.1 -/-- Corollary: for NoDup lists where sender and to each appear once, +/-- Corollary: for NoDup lists where sender and toAddr each appear once, the total sum is exactly preserved by transfer. -/ -theorem transfer_sum_preserved_unique (s : ContractState) (to : Address) (amount : Uint256) +theorem transfer_sum_preserved_unique (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 0 s.sender >= amount) - (h_ne : s.sender ≠ to) + (h_ne : s.sender ≠ toAddr) (addrs : List Address) (h_sender_once : countOcc s.sender addrs = 1) - (h_to_once : countOcc to addrs = 1) : - (addrs.map (fun addr => ((transfer to amount).run s).snd.storageMap 0 addr)).sum + (h_to_once : countOcc toAddr addrs = 1) : + (addrs.map (fun addr => ((transfer toAddr amount).run s).snd.storageMap 0 addr)).sum = (addrs.map (fun addr => s.storageMap 0 addr)).sum := by - have h := transfer_sum_equation s to amount h_balance h_ne addrs + have h := transfer_sum_equation s toAddr amount h_balance h_ne addrs simp [countOccU, h_sender_once, h_to_once] at h - have h' : (addrs.map (fun addr => ((transfer to amount).run s).snd.storageMap 0 addr)).sum + amount = + have h' : (addrs.map (fun addr => ((transfer toAddr amount).run s).snd.storageMap 0 addr)).sum + amount = (addrs.map (fun addr => s.storageMap 0 addr)).sum + amount := by simpa [Verity.Core.Uint256.add_comm] using h exact Verity.Core.Uint256.add_right_cancel h' @@ -152,7 +152,7 @@ theorem deposit_withdraw_sum_cancel (s : ContractState) (amount : Uint256) have h_inc_val : (s1.storageMap 0 s.sender : Nat) = (s.storageMap 0 s.sender : Nat) + (amount : Nat) := (congrArg (fun x => x.val) h_inc).trans (Verity.Core.Uint256.add_eq_of_lt h_no_overflow) - -- Convert to Uint256 order + -- Convert toAddr Uint256 order simp [Verity.Core.Uint256.le_def, h_inc_val, h_le] have h_dep := deposit_sum_equation s amount addrs have h_wd := withdraw_sum_equation (s := s1) amount h_balance addrs @@ -173,8 +173,8 @@ Withdraw conservation: 4. withdraw_sum_singleton_sender — for unique sender: new_sum + amount = old_sum Transfer conservation: -5. transfer_sum_equation — new_sum + count(sender)*amt = old_sum + count(to)*amt -6. transfer_sum_preserved_unique — for unique sender & to: new_sum = old_sum +5. transfer_sum_equation — new_sum + count(sender)*amt = old_sum + count(toAddr)*amt +6. transfer_sum_preserved_unique — for unique sender & toAddr: new_sum = old_sum Composition: 7. deposit_withdraw_sum_cancel — deposit then withdraw preserves sum diff --git a/Contracts/Ledger/Proofs/Correctness.lean b/Contracts/Ledger/Proofs/Correctness.lean index d71b9d34a..d91b9bf65 100644 --- a/Contracts/Ledger/Proofs/Correctness.lean +++ b/Contracts/Ledger/Proofs/Correctness.lean @@ -3,8 +3,8 @@ Proves deeper properties beyond Basic.lean: - Invariant preservation: transfer preserves WellFormedState - - End-to-end composition: withdraw→getBalance, transfer→getBalance - - Deposit-withdraw cancellation: deposit then withdraw returns to original balance + - End-toAddr-end composition: withdraw→getBalance, transfer→getBalance + - Deposit-withdraw cancellation: deposit then withdraw returns toAddr original balance -/ import Contracts.Ledger.Proofs.Basic @@ -20,13 +20,13 @@ open Contracts.Ledger.Invariants /-! ## Invariant Preservation -/ /-- Transfer preserves WellFormedState (sender ≠ recipient). -/ -theorem transfer_preserves_wellformedness (s : ContractState) (to : Address) (amount : Uint256) +theorem transfer_preserves_wellformedness (s : ContractState) (toAddr : Address) (amount : Uint256) (h : WellFormedState s) (h_balance : s.storageMap 0 s.sender >= amount) - (h_ne : s.sender ≠ to) : - let s' := ((transfer to amount).run s).snd + (h_ne : s.sender ≠ toAddr) : + let s' := ((transfer toAddr amount).run s).snd WellFormedState s' := by - have h_spec := transfer_meets_spec s to amount h_balance + have h_spec := transfer_meets_spec s toAddr amount h_balance simp [transfer_spec, h_ne, beq_iff_eq] at h_spec have h_frame := h_spec.2.2.2 constructor @@ -34,17 +34,17 @@ theorem transfer_preserves_wellformedness (s : ContractState) (to : Address) (am · exact h_frame.2.2.2.2.1 ▸ h.contract_nonzero /-- Transfer preserves non-mapping storage. -/ -theorem transfer_preserves_non_mapping (s : ContractState) (to : Address) (amount : Uint256) - (h_balance : s.storageMap 0 s.sender >= amount) (h_ne : s.sender ≠ to) : - let s' := ((transfer to amount).run s).snd +theorem transfer_preserves_non_mapping (s : ContractState) (toAddr : Address) (amount : Uint256) + (h_balance : s.storageMap 0 s.sender >= amount) (h_ne : s.sender ≠ toAddr) : + let s' := ((transfer toAddr amount).run s).snd non_mapping_storage_unchanged s s' := by - have h_spec := transfer_meets_spec s to amount h_balance + have h_spec := transfer_meets_spec s toAddr amount h_balance simp [transfer_spec, h_ne, beq_iff_eq] at h_spec have h_frame := h_spec.2.2.2 simp [non_mapping_storage_unchanged] exact ⟨h_frame.1, h_frame.2.1, h_frame.2.2.1⟩ -/-! ## End-to-End Composition -/ +/-! ## End-toAddr-End Composition -/ /-- After withdraw, getBalance returns the decreased balance. -/ theorem withdraw_getBalance_correct (s : ContractState) (amount : Uint256) @@ -55,28 +55,28 @@ theorem withdraw_getBalance_correct (s : ContractState) (amount : Uint256) exact withdraw_decreases_balance s amount h_balance /-- After transfer, sender's balance is decreased. -/ -theorem transfer_getBalance_sender_correct (s : ContractState) (to : Address) (amount : Uint256) - (h_balance : s.storageMap 0 s.sender >= amount) (h_ne : s.sender ≠ to) : - let s' := ((transfer to amount).run s).snd +theorem transfer_getBalance_sender_correct (s : ContractState) (toAddr : Address) (amount : Uint256) + (h_balance : s.storageMap 0 s.sender >= amount) (h_ne : s.sender ≠ toAddr) : + let s' := ((transfer toAddr amount).run s).snd ((getBalance s.sender).run s').fst = EVM.Uint256.sub (s.storageMap 0 s.sender) amount := by simp only [getBalance_returns_balance] - exact transfer_decreases_sender s to amount h_balance h_ne + exact transfer_decreases_sender s toAddr amount h_balance h_ne /-- After transfer, recipient's balance is increased. -/ -theorem transfer_getBalance_recipient_correct (s : ContractState) (to : Address) (amount : Uint256) - (h_balance : s.storageMap 0 s.sender >= amount) (h_ne : s.sender ≠ to) : - let s' := ((transfer to amount).run s).snd - ((getBalance to).run s').fst = EVM.Uint256.add (s.storageMap 0 to) amount := by +theorem transfer_getBalance_recipient_correct (s : ContractState) (toAddr : Address) (amount : Uint256) + (h_balance : s.storageMap 0 s.sender >= amount) (h_ne : s.sender ≠ toAddr) : + let s' := ((transfer toAddr amount).run s).snd + ((getBalance toAddr).run s').fst = EVM.Uint256.add (s.storageMap 0 toAddr) amount := by simp only [getBalance_returns_balance] - exact transfer_increases_recipient s to amount h_balance h_ne + exact transfer_increases_recipient s toAddr amount h_balance h_ne /-! ## Deposit-Withdraw Cancellation A key property: depositing and then withdrawing the same amount -returns to the original balance. This proves operations are inverses. +returns toAddr the original balance. This proves operations are inverses. -/ -/-- Deposit then withdraw of the same amount returns to original balance. +/-- Deposit then withdraw of the same amount returns toAddr original balance. Requires that the intermediate balance (original + amount) is sufficient for withdrawal, which is trivially true. -/ theorem deposit_withdraw_cancel (s : ContractState) (amount : Uint256) @@ -104,7 +104,7 @@ Invariant preservation: 1. transfer_preserves_wellformedness 2. transfer_preserves_non_mapping -End-to-end composition: +End-toAddr-end composition: 3. withdraw_getBalance_correct 4. transfer_getBalance_sender_correct 5. transfer_getBalance_recipient_correct diff --git a/Contracts/Ledger/Spec.lean b/Contracts/Ledger/Spec.lean index de6ce1717..cfcb82941 100644 --- a/Contracts/Ledger/Spec.lean +++ b/Contracts/Ledger/Spec.lean @@ -26,9 +26,9 @@ open Verity.Specs.Common (sumBalances balancesFinite) #gen_spec_map withdraw_spec for (amount : Uint256) (0, s.sender, (fun st => sub (st.storageMap 0 st.sender) amount), sameStorageAddrContext) -/-- transfer: moves amount from sender to recipient -/ -def transfer_spec (to : Address) (amount : Uint256) (s s' : ContractState) : Prop := - storageMapTransferSpec 0 s.sender to amount sameStorageAddrContext s s' +/-- transfer: moves amount from sender toAddr recipient -/ +def transfer_spec (toAddr : Address) (amount : Uint256) (s s' : ContractState) : Prop := + storageMapTransferSpec 0 s.sender toAddr amount sameStorageAddrContext s s' /-- getBalance: returns balance at given address, no state change -/ def getBalance_spec (addr : Address) (result : Uint256) (s : ContractState) : Prop := diff --git a/Contracts/MacroTranslateInvariantTest.lean b/Contracts/MacroTranslateInvariantTest.lean index c9b5e18a7..e6000e36e 100644 --- a/Contracts/MacroTranslateInvariantTest.lean +++ b/Contracts/MacroTranslateInvariantTest.lean @@ -311,9 +311,16 @@ private def macroSpecs : List CompilationModel := , Contracts.StringEqSmoke.spec , Contracts.BytesEqSmoke.spec , Contracts.Smoke.TupleSmoke.spec + , Contracts.Smoke.CurveCutArraySmoke.spec + , Contracts.Smoke.PackedStorageWriteSmoke.spec + , Contracts.Smoke.PackedAddressStorageWriteSmoke.spec , Contracts.Smoke.Uint8Smoke.spec , Contracts.Smoke.AddressHelpersSmoke.spec , Contracts.Smoke.ZeroAddressShadowSmoke.spec + , Contracts.Smoke.ContextAccessorShadowSmoke.spec + , Contracts.Smoke.FunctionOverloadSmoke.spec + , Contracts.Smoke.HelperExternalArgumentSmoke.spec + , Contracts.Smoke.BlockTimestampSmoke.spec , Contracts.Smoke.StructMappingSmoke.spec , Contracts.Smoke.ExternalCallSmoke.spec , Contracts.Smoke.TryExternalCallSmoke.spec @@ -356,11 +363,6 @@ private def macroSpecs : List CompilationModel := , Contracts.Smoke.FullComboSmoke.spec ] -private def functionSignature (fn : FunctionSpec) : String := - let params := fn.params.map (fun p => paramTypeToSolidityString p.ty) - let paramStr := String.intercalate "," params - s!"{fn.name}({paramStr})" - private def expectedExternalSignatures : List (String × List String) := [ ("SimpleStorage", ["store(uint256)", "retrieve()"]) , ("LocalObligationMacroSmoke", ["unsafeEdge()", "dischargedEdge(uint256)"]) @@ -417,10 +419,20 @@ private def expectedExternalSignatures : List (String × List String) := , ("StringEqSmoke", ["same(string,string)", "different(string,string)", "choose(string,string)"]) , ("BytesEqSmoke", ["same(bytes,bytes)", "different(bytes,bytes)", "choose(bytes,bytes)"]) , ("TupleSmoke", ["setFromPair((uint256,uint256))", "getPair(uint256)", "processConfig((address,address,uint256))"]) + , ("CurveCutArraySmoke", ["firstCutXt((uint256,uint256,int256)[])", "returnCut((uint256,uint256,int256)[],uint256)", + "storeCut((uint256,uint256,int256)[],uint256)", "storeTwoCuts((uint256,uint256,int256)[],uint256,uint256)"]) + , ("PackedStorageWriteSmoke", ["writeSlot0(bool,uint256)", "writeSlot1(uint256,uint256)"]) + , ("PackedAddressStorageWriteSmoke", ["writeOwnerWord(uint256)"]) , ("Uint8Smoke", ["acceptSig((uint8,bytes32,bytes32))", "sigV()"]) , ("AddressHelpersSmoke", ["setDelegate(address,address)", "getDelegate(address)", "clearDelegate(address)", "hasDelegate(address)", "isDelegateZero(address)", "setOwnerForId(uint256,address)", "getOwnerForId(uint256)"]) , ("ZeroAddressShadowSmoke", ["shadowWrite(address)"]) + , ("ContextAccessorShadowSmoke", ["echoSenderName(address)", "constantNamedChainid()", + "immutableNamedBlockTimestamp()", "immutableNamedMsgSender()"]) + , ("FunctionOverloadSmoke", ["echo(uint256)", "echo(address)", "echo(uint256,uint256)"]) + , ("HelperExternalArgumentSmoke", ["idWord(uint256)", "pair(uint256)", "put(uint256)", + "bindExternalArg(uint256)", "tupleExternalArg(uint256)", "statementExternalArg(uint256)"]) + , ("BlockTimestampSmoke", ["nowish()", "timestampPlus(uint256)", "blobFeePlus(uint256)"]) , ("StructMappingSmoke", ["setPosition(address,uint256,uint256,address)", "totalPositionShares(address)", "delegateOf(address)", "setApproval(address,address,uint256,uint256)", "approvalOf(address,address)", "approvalNonce(address,address)"]) @@ -513,10 +525,18 @@ private def expectedExternalSelectors : List (String × List String) := , ("StringEqSmoke", ["0x6df1667c", "0x1ce8f655", "0xc9e9b0e3"]) , ("BytesEqSmoke", ["0xfc39552e", "0x2c16057d", "0x3eb6f0de"]) , ("TupleSmoke", ["0x712ea680", "0xbdf391cc", "0x01b427d2"]) + , ("CurveCutArraySmoke", ["0xefca8f0f", "0x6f413e6b", "0x0d7610a3", "0xbea7dfd2"]) + , ("PackedStorageWriteSmoke", ["0xa0522387", "0x233ab149"]) + , ("PackedAddressStorageWriteSmoke", ["0xd59c874d"]) , ("Uint8Smoke", ["0xc233eaa7", "0x62fc458b"]) , ("AddressHelpersSmoke", ["0x5c873849", "0x544d8564", "0xcc21cc2a", "0x480005cd", "0x67129177", "0x0b0126c5", "0x85a9cdd0"]) , ("ZeroAddressShadowSmoke", ["0xc0aab575"]) + , ("ContextAccessorShadowSmoke", ["0x40d06f02", "0xda15c6a0", "0x3f08a0dd", "0xe6d00bb0"]) + , ("FunctionOverloadSmoke", ["0x6279e43c", "0x2ffdbf1a", "0x3bb2bcd0"]) + , ("HelperExternalArgumentSmoke", ["0x2d29ad72", "0x645751af", "0x3f81a2c0", + "0xb503d0dd", "0xcdc18015", "0xe41657c6"]) + , ("BlockTimestampSmoke", ["0xa676760e", "0x8c041599", "0x7150df5e"]) , ("StructMappingSmoke", ["0x468c900e", "0xe7933b6a", "0x8d22ea2a", "0xf4536007", "0xcb01943e", "0x6c241120"]) , ("ExternalCallSmoke", ["0x32fdff86", "0x21209dbd"]) @@ -593,7 +613,7 @@ private def _findIdxFieldRegression4 := Contracts.SimpleToken.findIdx_balancesSl -- Regression: `verity_contract` elaboration emits parameter-level findIdx simp lemmas. private def _findIdxParamRegression1 := Contracts.OwnedCounter.findIdx_param_initialOwner_constructor_OwnedCounter private def _findIdxParamRegression2 := Contracts.OwnedCounter.findIdx_param_newOwner_transferOwnership_OwnedCounter -private def _findIdxParamRegression3 := Contracts.Ledger.findIdx_param_to_transfer_Ledger +private def _findIdxParamRegression3 := Contracts.Ledger.findIdx_param_toAddr_transfer_Ledger private def _findIdxParamRegression4 := Contracts.Ledger.findIdx_param_amount_transfer_Ledger_decide private def checkMutabilitySmoke : IO Unit := do @@ -844,10 +864,52 @@ private def checkDirectHelperCallSmoke : IO Unit := do (contains (reprStr runHelpers.body) "Stmt.internalCallAssign" && contains (reprStr runHelpers.body) "\"internal_pairWithTotal\"") +private def curveCutArrayStoreMemoizesIndex : Bool := + match Contracts.Smoke.CurveCutArraySmoke.storeCut_modelBody with + | Stmt.letVar "arrayElement_index" (Expr.param "idx") :: + Stmt.letVar "xtReserve" (Expr.arrayElementWord "cuts" (Expr.localVar "arrayElement_index") 3 0) :: + Stmt.letVar "liqSquare" (Expr.arrayElementWord "cuts" (Expr.localVar "arrayElement_index") 3 1) :: + Stmt.letVar "offset" (Expr.arrayElementWord "cuts" (Expr.localVar "arrayElement_index") 3 2) :: + _ => true + | _ => false + +private def curveCutArrayReturnMemoizesIndex : Bool := + match Contracts.Smoke.CurveCutArraySmoke.returnCut_modelBody with + | Stmt.letVar "arrayElement_index" (Expr.param "idx") :: + Stmt.returnValues [ + Expr.arrayElementWord "cuts" (Expr.localVar "arrayElement_index") 3 0, + Expr.arrayElementWord "cuts" (Expr.localVar "arrayElement_index") 3 1, + Expr.arrayElementWord "cuts" (Expr.localVar "arrayElement_index") 3 2 + ] :: + _ => true + | _ => false + +private def curveCutArrayRepeatedDestructuresUseFreshSyntheticIndexes : Bool := + match Contracts.Smoke.CurveCutArraySmoke.storeTwoCuts_modelBody with + | Stmt.letVar "arrayElement_index" (Expr.param "firstIdx") :: + Stmt.letVar "firstXt" (Expr.arrayElementWord "cuts" (Expr.localVar "arrayElement_index") 3 0) :: + Stmt.letVar "_firstLiq" (Expr.arrayElementWord "cuts" (Expr.localVar "arrayElement_index") 3 1) :: + Stmt.letVar "_firstOffset" (Expr.arrayElementWord "cuts" (Expr.localVar "arrayElement_index") 3 2) :: + Stmt.letVar "arrayElement_index_1" (Expr.param "secondIdx") :: + Stmt.letVar "secondXt" (Expr.arrayElementWord "cuts" (Expr.localVar "arrayElement_index_1") 3 0) :: + Stmt.letVar "_secondLiq" (Expr.arrayElementWord "cuts" (Expr.localVar "arrayElement_index_1") 3 1) :: + Stmt.letVar "_secondOffset" (Expr.arrayElementWord "cuts" (Expr.localVar "arrayElement_index_1") 3 2) :: + _ => true + | _ => false + +private def checkCurveCutArraySmoke : IO Unit := do + expectTrue "CurveCutArraySmoke: tuple arrayElement destructuring memoizes the index once" + curveCutArrayStoreMemoizesIndex + expectTrue "CurveCutArraySmoke: tuple arrayElement return memoizes the index once" + curveCutArrayReturnMemoizesIndex + expectTrue "CurveCutArraySmoke: repeated tuple arrayElement destructures use fresh synthetic indexes" + curveCutArrayRepeatedDestructuresUseFreshSyntheticIndexes + private def checkSpec (spec : CompilationModel) : IO Unit := do let extFns := externalFunctions spec let fnNames := extFns.map (·.name) - expectTrue s!"{spec.name}: external function names are unique" (allDistinct fnNames) + let fnSignatures := extFns.map functionSignature + expectTrue s!"{spec.name}: external function signatures are unique" (allDistinct fnSignatures) let fieldNames := spec.fields.map (·.name) expectTrue s!"{spec.name}: field names are unique" (allDistinct fieldNames) @@ -963,6 +1025,7 @@ private def checkSpec (spec : CompilationModel) : IO Unit := do checkLowLevelTryCatchSmoke checkSpecialEntrypointSmoke checkDirectHelperCallSmoke + checkCurveCutArraySmoke for spec in macroSpecs do checkSpec spec diff --git a/Contracts/MacroTranslateRoundTripFuzz.lean b/Contracts/MacroTranslateRoundTripFuzz.lean index 1da16cea0..53a39a1bb 100644 --- a/Contracts/MacroTranslateRoundTripFuzz.lean +++ b/Contracts/MacroTranslateRoundTripFuzz.lean @@ -67,10 +67,17 @@ private def macroSpecs : List CompilationModel := , Contracts.Smoke.ImmutableSmoke.spec , Contracts.Smoke.TypedImmutableSmoke.spec , Contracts.Smoke.TupleSmoke.spec + , Contracts.Smoke.CurveCutArraySmoke.spec + , Contracts.Smoke.PackedStorageWriteSmoke.spec + , Contracts.Smoke.PackedAddressStorageWriteSmoke.spec , Contracts.Smoke.Uint8Smoke.spec , Contracts.BytesEqSmoke.spec , Contracts.Smoke.AddressHelpersSmoke.spec , Contracts.Smoke.ZeroAddressShadowSmoke.spec + , Contracts.Smoke.ContextAccessorShadowSmoke.spec + , Contracts.Smoke.FunctionOverloadSmoke.spec + , Contracts.Smoke.HelperExternalArgumentSmoke.spec + , Contracts.Smoke.BlockTimestampSmoke.spec , Contracts.Smoke.StructMappingSmoke.spec , Contracts.Smoke.ExternalCallSmoke.spec , Contracts.Smoke.TryExternalCallSmoke.spec diff --git a/Contracts/SimpleToken/Proofs/Basic.lean b/Contracts/SimpleToken/Proofs/Basic.lean index 09adc88ad..e3995d8b5 100644 --- a/Contracts/SimpleToken/Proofs/Basic.lean +++ b/Contracts/SimpleToken/Proofs/Basic.lean @@ -143,17 +143,17 @@ theorem isOwner_true_when_owner (s : ContractState) (h : s.sender = s.storageAdd simp [Contracts.SimpleToken.ownerSlot, h] -- Helper: unfold mint when owner guard passes and no overflow -private theorem mint_unfold (s : ContractState) (to : Address) (amount : Uint256) +private theorem mint_unfold (s : ContractState) (toAddr : Address) (amount : Uint256) (h_owner : s.sender = s.storageAddr 0) - (h_no_bal_overflow : (s.storageMap 1 to : Nat) + (amount : Nat) ≤ MAX_UINT256) + (h_no_bal_overflow : (s.storageMap 1 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) (h_no_sup_overflow : (s.storage 2 : Nat) + (amount : Nat) ≤ MAX_UINT256) : - (mint to amount).run s = ContractResult.success () + (mint toAddr amount).run s = ContractResult.success () { «storage» := fun slotIdx => if (slotIdx == 2) = true then EVM.Uint256.add (s.storage 2) amount else s.storage slotIdx, transientStorage := s.transientStorage, storageAddr := s.storageAddr, storageMap := fun slotIdx addr => - if (slotIdx == 1 && addr == to) = true then EVM.Uint256.add (s.storageMap 1 to) amount + if (slotIdx == 1 && addr == toAddr) = true then EVM.Uint256.add (s.storageMap 1 toAddr) amount else s.storageMap slotIdx addr, storageMapUint := s.storageMapUint, storageMap2 := s.storageMap2, @@ -169,10 +169,10 @@ private theorem mint_unfold (s : ContractState) (to : Address) (amount : Uint256 calldata := s.calldata, memory := s.memory, knownAddresses := fun slotIdx => - if slotIdx == 1 then (s.knownAddresses slotIdx).insert to + if slotIdx == 1 then (s.knownAddresses slotIdx).insert toAddr else s.knownAddresses slotIdx, events := s.events } := by - have h_safe_bal := safeAdd_some (s.storageMap 1 to) amount h_no_bal_overflow + have h_safe_bal := safeAdd_some (s.storageMap 1 toAddr) amount h_no_bal_overflow have h_safe_sup := safeAdd_some (s.storage 2) amount h_no_sup_overflow -- Unfold mint (checks-before-effects ordering: both requireSomeUint before mutations) verity_unfold mint @@ -191,19 +191,19 @@ private theorem mint_unfold (s : ContractState) (to : Address) (amount : Uint256 simp only [HAdd.hAdd, Add.add, h_owner] -- Mint correctness when caller is owner and no overflow -theorem mint_meets_spec_when_owner (s : ContractState) (to : Address) (amount : Uint256) +theorem mint_meets_spec_when_owner (s : ContractState) (toAddr : Address) (amount : Uint256) (h_owner : s.sender = s.storageAddr 0) - (h_no_bal_overflow : (s.storageMap 1 to : Nat) + (amount : Nat) ≤ MAX_UINT256) + (h_no_bal_overflow : (s.storageMap 1 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) (h_no_sup_overflow : (s.storage 2 : Nat) + (amount : Nat) ≤ MAX_UINT256) : - let s' := ((mint to amount).run s).snd - mint_spec to amount s s' := by - have h_unfold := mint_unfold s to amount h_owner h_no_bal_overflow h_no_sup_overflow + let s' := ((mint toAddr amount).run s).snd + mint_spec toAddr amount s s' := by + have h_unfold := mint_unfold s toAddr amount h_owner h_no_bal_overflow h_no_sup_overflow have h_unfold_apply := Contract.eq_of_run_success h_unfold simp only [Contract.run, ContractResult.snd, mint_spec] rw [h_unfold_apply] simp only [ContractResult.snd] refine ⟨?_, ?_, ?_, ?_, ?_, ?_⟩ - · simp -- balance of 'to' updated + · simp -- balance of 'toAddr' updated · simp -- supply updated · refine ⟨?_, ?_⟩ · intro addr h_neq; simp [h_neq] -- other balances preserved @@ -212,30 +212,30 @@ theorem mint_meets_spec_when_owner (s : ContractState) (to : Address) (amount : · trivial -- owner preserved · exact Specs.sameContext_rfl _ -theorem mint_increases_balance (s : ContractState) (to : Address) (amount : Uint256) +theorem mint_increases_balance (s : ContractState) (toAddr : Address) (amount : Uint256) (h_owner : s.sender = s.storageAddr 0) - (h_no_bal_overflow : (s.storageMap 1 to : Nat) + (amount : Nat) ≤ MAX_UINT256) + (h_no_bal_overflow : (s.storageMap 1 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) (h_no_sup_overflow : (s.storage 2 : Nat) + (amount : Nat) ≤ MAX_UINT256) : - let s' := ((mint to amount).run s).snd - s'.storageMap 1 to = EVM.Uint256.add (s.storageMap 1 to) amount := by - have h := mint_meets_spec_when_owner s to amount h_owner h_no_bal_overflow h_no_sup_overflow + let s' := ((mint toAddr amount).run s).snd + s'.storageMap 1 toAddr = EVM.Uint256.add (s.storageMap 1 toAddr) amount := by + have h := mint_meets_spec_when_owner s toAddr amount h_owner h_no_bal_overflow h_no_sup_overflow simp [mint_spec] at h; exact h.1 -theorem mint_increases_supply (s : ContractState) (to : Address) (amount : Uint256) +theorem mint_increases_supply (s : ContractState) (toAddr : Address) (amount : Uint256) (h_owner : s.sender = s.storageAddr 0) - (h_no_bal_overflow : (s.storageMap 1 to : Nat) + (amount : Nat) ≤ MAX_UINT256) + (h_no_bal_overflow : (s.storageMap 1 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) (h_no_sup_overflow : (s.storage 2 : Nat) + (amount : Nat) ≤ MAX_UINT256) : - let s' := ((mint to amount).run s).snd + let s' := ((mint toAddr amount).run s).snd s'.storage 2 = EVM.Uint256.add (s.storage 2) amount := by - have h := mint_meets_spec_when_owner s to amount h_owner h_no_bal_overflow h_no_sup_overflow + have h := mint_meets_spec_when_owner s toAddr amount h_owner h_no_bal_overflow h_no_sup_overflow simp [mint_spec] at h; exact h.2.1 -- Mint reverts on balance overflow -theorem mint_reverts_balance_overflow (s : ContractState) (to : Address) (amount : Uint256) +theorem mint_reverts_balance_overflow (s : ContractState) (toAddr : Address) (amount : Uint256) (h_owner : s.sender = s.storageAddr 0) - (h_overflow : (s.storageMap 1 to : Nat) + (amount : Nat) > MAX_UINT256) : - ∃ msg, (mint to amount).run s = ContractResult.revert msg s := by - have h_none := safeAdd_none (s.storageMap 1 to) amount h_overflow + (h_overflow : (s.storageMap 1 toAddr : Nat) + (amount : Nat) > MAX_UINT256) : + ∃ msg, (mint toAddr amount).run s = ContractResult.revert msg s := by + have h_none := safeAdd_none (s.storageMap 1 toAddr) amount h_overflow simp [mint, Contracts.SimpleToken.onlyOwner, isOwner, requireSomeUint, Contracts.SimpleToken.ownerSlot, Contracts.SimpleToken.balancesSlot, Contracts.SimpleToken.totalSupplySlot, msgSender, getStorageAddr, setStorageAddr, getStorage, setStorage, getMapping, setMapping, @@ -246,12 +246,12 @@ theorem mint_reverts_balance_overflow (s : ContractState) (to : Address) (amount -- Mint reverts on supply overflow (even if balance doesn't overflow). -- With checks-before-effects ordering, the revert happens before any state -- mutations, so the revert state equals the original state `s`. -theorem mint_reverts_supply_overflow (s : ContractState) (to : Address) (amount : Uint256) +theorem mint_reverts_supply_overflow (s : ContractState) (toAddr : Address) (amount : Uint256) (h_owner : s.sender = s.storageAddr 0) - (h_no_bal_overflow : (s.storageMap 1 to : Nat) + (amount : Nat) ≤ MAX_UINT256) + (h_no_bal_overflow : (s.storageMap 1 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) (h_overflow : (s.storage 2 : Nat) + (amount : Nat) > MAX_UINT256) : - ∃ msg, (mint to amount).run s = ContractResult.revert msg s := by - have h_safe_bal := safeAdd_some (s.storageMap 1 to) amount h_no_bal_overflow + ∃ msg, (mint toAddr amount).run s = ContractResult.revert msg s := by + have h_safe_bal := safeAdd_some (s.storageMap 1 toAddr) amount h_no_bal_overflow have h_none := safeAdd_none (s.storage 2) amount h_overflow simp only [mint, Contracts.SimpleToken.onlyOwner, isOwner, requireSomeUint, Contracts.SimpleToken.ownerSlot, Contracts.SimpleToken.balancesSlot, Contracts.SimpleToken.totalSupplySlot, @@ -271,10 +271,10 @@ fully modeled, matching Solidity ^0.8 checked arithmetic semantics. -/ -- Helper lemma: after unfolding transfer with sufficient balance and self-transfer, state is unchanged -private theorem transfer_unfold_self (s : ContractState) (to : Address) (amount : Uint256) +private theorem transfer_unfold_self (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 1 s.sender ≥ amount) - (h_eq : s.sender = to) : - (transfer to amount).run s = ContractResult.success () s := by + (h_eq : s.sender = toAddr) : + (transfer toAddr amount).run s = ContractResult.success () s := by have h_balance' := uint256_ge_val_le (h_eq ▸ h_balance) verity_unfold transfer simp [Contracts.SimpleToken.balancesSlot, @@ -282,16 +282,16 @@ private theorem transfer_unfold_self (s : ContractState) (to : Address) (amount h_balance', h_eq, beq_iff_eq] -- Helper lemma: after unfolding transfer with sufficient balance, distinct recipient, and no overflow -private theorem transfer_unfold_other (s : ContractState) (to : Address) (amount : Uint256) +private theorem transfer_unfold_other (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 1 s.sender ≥ amount) - (h_ne : s.sender ≠ to) - (h_no_overflow : (s.storageMap 1 to : Nat) + (amount : Nat) ≤ MAX_UINT256) : - (transfer to amount).run s = ContractResult.success () + (h_ne : s.sender ≠ toAddr) + (h_no_overflow : (s.storageMap 1 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) : + (transfer toAddr amount).run s = ContractResult.success () { «storage» := s.storage, transientStorage := s.transientStorage, storageAddr := s.storageAddr, storageMap := fun slotIdx addr => - if (slotIdx == 1 && addr == to) = true then EVM.Uint256.add (s.storageMap 1 to) amount + if (slotIdx == 1 && addr == toAddr) = true then EVM.Uint256.add (s.storageMap 1 toAddr) amount else if (slotIdx == 1 && addr == s.sender) = true then EVM.Uint256.sub (s.storageMap 1 s.sender) amount else s.storageMap slotIdx addr, storageMapUint := s.storageMapUint, @@ -308,11 +308,11 @@ private theorem transfer_unfold_other (s : ContractState) (to : Address) (amount calldata := s.calldata, memory := s.memory, knownAddresses := fun slotIdx => - if slotIdx == 1 then ((s.knownAddresses slotIdx).insert s.sender).insert to + if slotIdx == 1 then ((s.knownAddresses slotIdx).insert s.sender).insert toAddr else s.knownAddresses slotIdx, events := s.events } := by have h_balance' := uint256_ge_val_le h_balance - have h_safe := safeAdd_some (s.storageMap 1 to) amount h_no_overflow + have h_safe := safeAdd_some (s.storageMap 1 toAddr) amount h_no_overflow simp only [transfer, Contracts.SimpleToken.balancesSlot, msgSender, getMapping, setMapping, requireSomeUint, @@ -327,13 +327,13 @@ private theorem transfer_unfold_other (s : ContractState) (to : Address) (amount funext slotIdx split <;> simp [*] -theorem transfer_meets_spec_when_sufficient (s : ContractState) (to : Address) (amount : Uint256) +theorem transfer_meets_spec_when_sufficient (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 1 s.sender ≥ amount) - (h_no_overflow : s.sender ≠ to → (s.storageMap 1 to : Nat) + (amount : Nat) ≤ MAX_UINT256) : - let s' := ((transfer to amount).run s).snd - transfer_spec s.sender to amount s s' := by - by_cases h_eq : s.sender = to - · have h_unfold := transfer_unfold_self s to amount h_balance h_eq + (h_no_overflow : s.sender ≠ toAddr → (s.storageMap 1 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) : + let s' := ((transfer toAddr amount).run s).snd + transfer_spec s.sender toAddr amount s s' := by + by_cases h_eq : s.sender = toAddr + · have h_unfold := transfer_unfold_self s toAddr amount h_balance h_eq have h_unfold_apply := Contract.eq_of_run_success h_unfold simp only [Contract.run, ContractResult.snd, transfer_spec] rw [h_unfold_apply] @@ -344,12 +344,12 @@ theorem transfer_meets_spec_when_sufficient (s : ContractState) (to : Address) ( Specs.storageMapUnchangedExceptKey, Specs.storageMapUnchangedExceptSlot] · rfl · simp [Specs.sameStorageAddrContext, Specs.sameStorage, Specs.sameStorageAddr, Specs.sameStorageArray, Specs.sameContext] - · have h_unfold := transfer_unfold_other s to amount h_balance h_eq (h_no_overflow h_eq) + · have h_unfold := transfer_unfold_other s toAddr amount h_balance h_eq (h_no_overflow h_eq) have h_unfold_apply := Contract.eq_of_run_success h_unfold simp only [Contract.run, ContractResult.snd, transfer_spec] rw [h_unfold_apply] simp only [ContractResult.snd] - have h_ne' := address_beq_false_of_ne s.sender to h_eq + have h_ne' := address_beq_false_of_ne s.sender toAddr h_eq refine ⟨h_balance, ?_, ?_, ?_, ?_, ?_⟩ · simp [h_ne'] -- sender balance decreased · simp [h_ne'] -- recipient balance increased @@ -361,31 +361,31 @@ theorem transfer_meets_spec_when_sufficient (s : ContractState) (to : Address) ( trivial · simp [Specs.sameStorageAddrContext, Specs.sameStorage, Specs.sameStorageAddr, Specs.sameStorageArray, Specs.sameContext] -theorem transfer_preserves_supply_when_sufficient (s : ContractState) (to : Address) (amount : Uint256) +theorem transfer_preserves_supply_when_sufficient (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 1 s.sender ≥ amount) - (h_no_overflow : s.sender ≠ to → (s.storageMap 1 to : Nat) + (amount : Nat) ≤ MAX_UINT256) : - let s' := ((transfer to amount).run s).snd + (h_no_overflow : s.sender ≠ toAddr → (s.storageMap 1 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) : + let s' := ((transfer toAddr amount).run s).snd s'.storage 2 = s.storage 2 := by - have h := transfer_meets_spec_when_sufficient s to amount h_balance h_no_overflow + have h := transfer_meets_spec_when_sufficient s toAddr amount h_balance h_no_overflow simp [transfer_spec, Specs.sameStorage] at h simpa using congrArg (fun f => f 2) h.2.2.2.2.2.1 -theorem transfer_decreases_sender_balance (s : ContractState) (to : Address) (amount : Uint256) +theorem transfer_decreases_sender_balance (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 1 s.sender ≥ amount) - (h_ne : s.sender ≠ to) - (h_no_overflow : (s.storageMap 1 to : Nat) + (amount : Nat) ≤ MAX_UINT256) : - let s' := ((transfer to amount).run s).snd + (h_ne : s.sender ≠ toAddr) + (h_no_overflow : (s.storageMap 1 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) : + let s' := ((transfer toAddr amount).run s).snd s'.storageMap 1 s.sender = EVM.Uint256.sub (s.storageMap 1 s.sender) amount := by - have h := transfer_meets_spec_when_sufficient s to amount h_balance (fun _ => h_no_overflow) + have h := transfer_meets_spec_when_sufficient s toAddr amount h_balance (fun _ => h_no_overflow) simp [transfer_spec, h_ne, beq_iff_eq] at h; exact h.2.1 -theorem transfer_increases_recipient_balance (s : ContractState) (to : Address) (amount : Uint256) +theorem transfer_increases_recipient_balance (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 1 s.sender ≥ amount) - (h_ne : s.sender ≠ to) - (h_no_overflow : (s.storageMap 1 to : Nat) + (amount : Nat) ≤ MAX_UINT256) : - let s' := ((transfer to amount).run s).snd - s'.storageMap 1 to = EVM.Uint256.add (s.storageMap 1 to) amount := by - have h := transfer_meets_spec_when_sufficient s to amount h_balance (fun _ => h_no_overflow) + (h_ne : s.sender ≠ toAddr) + (h_no_overflow : (s.storageMap 1 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) : + let s' := ((transfer toAddr amount).run s).snd + s'.storageMap 1 toAddr = EVM.Uint256.add (s.storageMap 1 toAddr) amount := by + have h := transfer_meets_spec_when_sufficient s toAddr amount h_balance (fun _ => h_no_overflow) simp [transfer_spec, h_ne, beq_iff_eq] at h; exact h.2.2.1 theorem transfer_self_preserves_balance (s : ContractState) (amount : Uint256) @@ -396,13 +396,13 @@ theorem transfer_self_preserves_balance (s : ContractState) (amount : Uint256) simp [transfer_spec, beq_iff_eq] at h; exact h.2.1 -- Transfer reverts on recipient balance overflow -theorem transfer_reverts_recipient_overflow (s : ContractState) (to : Address) (amount : Uint256) +theorem transfer_reverts_recipient_overflow (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 1 s.sender ≥ amount) - (h_ne : s.sender ≠ to) - (h_overflow : (s.storageMap 1 to : Nat) + (amount : Nat) > MAX_UINT256) : - ∃ msg, (transfer to amount).run s = ContractResult.revert msg s := by + (h_ne : s.sender ≠ toAddr) + (h_overflow : (s.storageMap 1 toAddr : Nat) + (amount : Nat) > MAX_UINT256) : + ∃ msg, (transfer toAddr amount).run s = ContractResult.revert msg s := by have h_balance' := uint256_ge_val_le h_balance - have h_none := safeAdd_none (s.storageMap 1 to) amount h_overflow + have h_none := safeAdd_none (s.storageMap 1 toAddr) amount h_overflow simp [transfer, requireSomeUint, Contracts.SimpleToken.balancesSlot, msgSender, getMapping, setMapping, Verity.require, Verity.pure, Verity.bind, Bind.bind, Pure.pure, @@ -534,9 +534,9 @@ Guard-dependent proofs (now complete): Proof technique: Full unfolding of do-notation chains through bind/pure/Contract.run/ContractResult.snd, with simp [h_owner] or -simp [h_balance] to resolve the guard condition, then refine for +simp [h_balance] toAddr resolve the guard condition, then refine for each conjunct of the spec. Overflow checks use safeAdd/requireSomeUint -with safeAdd_some/safeAdd_none helpers to resolve the Option matching. +with safeAdd_some/safeAdd_none helpers toAddr resolve the Option matching. -/ end Contracts.SimpleToken.Proofs diff --git a/Contracts/SimpleToken/Proofs/Correctness.lean b/Contracts/SimpleToken/Proofs/Correctness.lean index b1fe5b7c4..9ea4a0394 100644 --- a/Contracts/SimpleToken/Proofs/Correctness.lean +++ b/Contracts/SimpleToken/Proofs/Correctness.lean @@ -5,7 +5,7 @@ - Guard revert behavior (mint reverts when not owner, transfer reverts with insufficient balance) - Invariant preservation (mint/transfer preserve WellFormedState) - Owner stability (mint/transfer don't change owner) - - End-to-end composition (mint→balanceOf, transfer→balanceOf) + - End-toAddr-end composition (mint→balanceOf, transfer→balanceOf) These are Tier 2-3 properties: functional correctness and invariant preservation. -/ @@ -30,9 +30,9 @@ This is critical for safety: unauthorized or invalid operations must fail. /-- Mint reverts when caller is not the owner. Safety property: non-owners cannot create tokens. -/ -theorem mint_reverts_when_not_owner (s : ContractState) (to : Address) (amount : Uint256) +theorem mint_reverts_when_not_owner (s : ContractState) (toAddr : Address) (amount : Uint256) (h_not_owner : s.sender ≠ s.storageAddr 0) : - ∃ msg, (mint to amount).run s = ContractResult.revert msg s := by + ∃ msg, (mint toAddr amount).run s = ContractResult.revert msg s := by simp only [mint, Contracts.SimpleToken.ownerSlot, Contracts.SimpleToken.balancesSlot, Contracts.SimpleToken.totalSupplySlot, msgSender, getStorageAddr, getStorage, setStorage, getMapping, setMapping, @@ -42,9 +42,9 @@ theorem mint_reverts_when_not_owner (s : ContractState) (to : Address) (amount : /-- Transfer reverts when sender has insufficient balance. Safety property: no overdrafts possible. -/ -theorem transfer_reverts_insufficient_balance (s : ContractState) (to : Address) (amount : Uint256) +theorem transfer_reverts_insufficient_balance (s : ContractState) (toAddr : Address) (amount : Uint256) (h_insufficient : s.storageMap 1 s.sender < amount) : - ∃ msg, (transfer to amount).run s = ContractResult.revert msg s := by + ∃ msg, (transfer toAddr amount).run s = ContractResult.revert msg s := by simp only [transfer, Contracts.SimpleToken.balancesSlot, msgSender, getMapping, Verity.require, Verity.bind, Bind.bind, @@ -59,41 +59,41 @@ Combined with Basic.lean's proofs for simpleTokenConstructor/reads, this gives f /-- Mint preserves well-formedness when caller is owner and no overflow. The owner address stays non-empty, context is preserved. -/ -theorem mint_preserves_wellformedness (s : ContractState) (to : Address) (amount : Uint256) +theorem mint_preserves_wellformedness (s : ContractState) (toAddr : Address) (amount : Uint256) (h : WellFormedState s) (h_owner : s.sender = s.storageAddr 0) - (h_no_bal_overflow : (s.storageMap 1 to : Nat) + (amount : Nat) ≤ MAX_UINT256) + (h_no_bal_overflow : (s.storageMap 1 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) (h_no_sup_overflow : (s.storage 2 : Nat) + (amount : Nat) ≤ MAX_UINT256) : - let s' := ((mint to amount).run s).snd + let s' := ((mint toAddr amount).run s).snd WellFormedState s' := by - have h_spec := mint_meets_spec_when_owner s to amount h_owner h_no_bal_overflow h_no_sup_overflow + have h_spec := mint_meets_spec_when_owner s toAddr amount h_owner h_no_bal_overflow h_no_sup_overflow have h_owner_pres : - ((mint to amount).run s).snd.storageAddr 0 = s.storageAddr 0 := by + ((mint toAddr amount).run s).snd.storageAddr 0 = s.storageAddr 0 := by simpa [mint_spec] using h_spec.2.2.2.2.1 have h_ctx : - Specs.sameContext s ((mint to amount).run s).snd := by + Specs.sameContext s ((mint toAddr amount).run s).snd := by simpa [mint_spec, Specs.sameStorageAddrSlotContext] using h_spec.2.2.2.2.2 have h_wf_frame := - wf_preservation_of_frame s ((mint to amount).run s).snd + wf_preservation_of_frame s ((mint toAddr amount).run s).snd h.sender_nonzero h.contract_nonzero h.owner_nonzero h_ctx.1 h_ctx.2.1 h_owner_pres exact ⟨h_wf_frame.1, h_wf_frame.2.1, h_wf_frame.2.2⟩ /-- Transfer preserves well-formedness when balance is sufficient and no overflow. Owner, context all remain intact across transfers. -/ -theorem transfer_preserves_wellformedness (s : ContractState) (to : Address) (amount : Uint256) +theorem transfer_preserves_wellformedness (s : ContractState) (toAddr : Address) (amount : Uint256) (h : WellFormedState s) (h_balance : s.storageMap 1 s.sender ≥ amount) - (h_no_overflow : s.sender ≠ to → (s.storageMap 1 to : Nat) + (amount : Nat) ≤ MAX_UINT256) : - let s' := ((transfer to amount).run s).snd + (h_no_overflow : s.sender ≠ toAddr → (s.storageMap 1 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) : + let s' := ((transfer toAddr amount).run s).snd WellFormedState s' := by - have h_spec := transfer_meets_spec_when_sufficient s to amount h_balance h_no_overflow + have h_spec := transfer_meets_spec_when_sufficient s toAddr amount h_balance h_no_overflow have h_owner_pres : - ((transfer to amount).run s).snd.storageAddr 0 = s.storageAddr 0 := by + ((transfer toAddr amount).run s).snd.storageAddr 0 = s.storageAddr 0 := by simpa [transfer_spec] using h_spec.2.2.2.2.1 have h_addr_ctx : - Specs.sameStorageAddrContext s ((transfer to amount).run s).snd := by + Specs.sameStorageAddrContext s ((transfer toAddr amount).run s).snd := by simpa [transfer_spec] using h_spec.2.2.2.2.2 have h_wf_frame := - wf_preservation_of_frame s ((transfer to amount).run s).snd + wf_preservation_of_frame s ((transfer toAddr amount).run s).snd h.sender_nonzero h.contract_nonzero h.owner_nonzero h_addr_ctx.2.2.2.1 h_addr_ctx.2.2.2.2.1 h_owner_pres exact ⟨h_wf_frame.1, h_wf_frame.2.1, h_wf_frame.2.2⟩ @@ -105,67 +105,67 @@ never change it. This is a critical access control property. -/ /-- Mint does not change the owner address. -/ -theorem mint_preserves_owner (s : ContractState) (to : Address) (amount : Uint256) +theorem mint_preserves_owner (s : ContractState) (toAddr : Address) (amount : Uint256) (h_owner : s.sender = s.storageAddr 0) - (h_no_bal_overflow : (s.storageMap 1 to : Nat) + (amount : Nat) ≤ MAX_UINT256) + (h_no_bal_overflow : (s.storageMap 1 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) (h_no_sup_overflow : (s.storage 2 : Nat) + (amount : Nat) ≤ MAX_UINT256) : - let s' := ((mint to amount).run s).snd + let s' := ((mint toAddr amount).run s).snd s'.storageAddr 0 = s.storageAddr 0 := by simpa [mint_spec] using - (mint_meets_spec_when_owner s to amount h_owner h_no_bal_overflow h_no_sup_overflow).2.2.2.2.1 + (mint_meets_spec_when_owner s toAddr amount h_owner h_no_bal_overflow h_no_sup_overflow).2.2.2.2.1 /-- Transfer does not change the owner address. -/ -theorem transfer_preserves_owner (s : ContractState) (to : Address) (amount : Uint256) +theorem transfer_preserves_owner (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 1 s.sender ≥ amount) - (h_no_overflow : s.sender ≠ to → (s.storageMap 1 to : Nat) + (amount : Nat) ≤ MAX_UINT256) : - let s' := ((transfer to amount).run s).snd + (h_no_overflow : s.sender ≠ toAddr → (s.storageMap 1 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) : + let s' := ((transfer toAddr amount).run s).snd s'.storageAddr 0 = s.storageAddr 0 := by simpa [transfer_spec] using - (transfer_meets_spec_when_sufficient s to amount h_balance h_no_overflow).2.2.2.2.1 + (transfer_meets_spec_when_sufficient s toAddr amount h_balance h_no_overflow).2.2.2.2.1 -/-! ## End-to-End Composition +/-! ## End-toAddr-End Composition These prove that operation sequences produce the expected observable results. They combine state-modifying operations with read operations. -/ /-- After minting, balanceOf returns the increased balance. -/ -theorem mint_then_balanceOf_correct (s : ContractState) (to : Address) (amount : Uint256) +theorem mint_then_balanceOf_correct (s : ContractState) (toAddr : Address) (amount : Uint256) (h_owner : s.sender = s.storageAddr 0) - (h_no_bal_overflow : (s.storageMap 1 to : Nat) + (amount : Nat) ≤ MAX_UINT256) + (h_no_bal_overflow : (s.storageMap 1 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) (h_no_sup_overflow : (s.storage 2 : Nat) + (amount : Nat) ≤ MAX_UINT256) : - let s' := ((mint to amount).run s).snd - ((balanceOf to).run s').fst = EVM.Uint256.add (s.storageMap 1 to) amount := by - simp only [balanceOf_returns_balance, mint_increases_balance s to amount h_owner h_no_bal_overflow h_no_sup_overflow] + let s' := ((mint toAddr amount).run s).snd + ((balanceOf toAddr).run s').fst = EVM.Uint256.add (s.storageMap 1 toAddr) amount := by + simp only [balanceOf_returns_balance, mint_increases_balance s toAddr amount h_owner h_no_bal_overflow h_no_sup_overflow] /-- After minting, getTotalSupply returns the increased supply. -/ -theorem mint_then_getTotalSupply_correct (s : ContractState) (to : Address) (amount : Uint256) +theorem mint_then_getTotalSupply_correct (s : ContractState) (toAddr : Address) (amount : Uint256) (h_owner : s.sender = s.storageAddr 0) - (h_no_bal_overflow : (s.storageMap 1 to : Nat) + (amount : Nat) ≤ MAX_UINT256) + (h_no_bal_overflow : (s.storageMap 1 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) (h_no_sup_overflow : (s.storage 2 : Nat) + (amount : Nat) ≤ MAX_UINT256) : - let s' := ((mint to amount).run s).snd + let s' := ((mint toAddr amount).run s).snd ((getTotalSupply).run s').fst = EVM.Uint256.add (s.storage 2) amount := by - simp only [getTotalSupply_returns_supply, mint_increases_supply s to amount h_owner h_no_bal_overflow h_no_sup_overflow] + simp only [getTotalSupply_returns_supply, mint_increases_supply s toAddr amount h_owner h_no_bal_overflow h_no_sup_overflow] /-- After transfer, sender's balance is decreased by the transfer amount. -/ -theorem transfer_then_balanceOf_sender_correct (s : ContractState) (to : Address) (amount : Uint256) - (h_balance : s.storageMap 1 s.sender ≥ amount) (h_ne : s.sender ≠ to) - (h_no_overflow : (s.storageMap 1 to : Nat) + (amount : Nat) ≤ MAX_UINT256) : - let s' := ((transfer to amount).run s).snd +theorem transfer_then_balanceOf_sender_correct (s : ContractState) (toAddr : Address) (amount : Uint256) + (h_balance : s.storageMap 1 s.sender ≥ amount) (h_ne : s.sender ≠ toAddr) + (h_no_overflow : (s.storageMap 1 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) : + let s' := ((transfer toAddr amount).run s).snd ((balanceOf s.sender).run s').fst = EVM.Uint256.sub (s.storageMap 1 s.sender) amount := by simp only [balanceOf_returns_balance] - exact transfer_decreases_sender_balance s to amount h_balance h_ne h_no_overflow + exact transfer_decreases_sender_balance s toAddr amount h_balance h_ne h_no_overflow /-- After transfer, recipient's balance is increased by the transfer amount. -/ -theorem transfer_then_balanceOf_recipient_correct (s : ContractState) (to : Address) (amount : Uint256) - (h_balance : s.storageMap 1 s.sender ≥ amount) (h_ne : s.sender ≠ to) - (h_no_overflow : (s.storageMap 1 to : Nat) + (amount : Nat) ≤ MAX_UINT256) : - let s' := ((transfer to amount).run s).snd - ((balanceOf to).run s').fst = EVM.Uint256.add (s.storageMap 1 to) amount := by +theorem transfer_then_balanceOf_recipient_correct (s : ContractState) (toAddr : Address) (amount : Uint256) + (h_balance : s.storageMap 1 s.sender ≥ amount) (h_ne : s.sender ≠ toAddr) + (h_no_overflow : (s.storageMap 1 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) : + let s' := ((transfer toAddr amount).run s).snd + ((balanceOf toAddr).run s').fst = EVM.Uint256.add (s.storageMap 1 toAddr) amount := by simp only [balanceOf_returns_balance] - exact transfer_increases_recipient_balance s to amount h_balance h_ne h_no_overflow + exact transfer_increases_recipient_balance s toAddr amount h_balance h_ne h_no_overflow /-! ## Summary @@ -183,7 +183,7 @@ Owner stability: 5. mint_preserves_owner — mint doesn't change owner 6. transfer_preserves_owner — transfer doesn't change owner -End-to-end composition: +End-toAddr-end composition: 7. mint_then_balanceOf_correct — mint→read gives expected balance 8. mint_then_getTotalSupply_correct — mint→read gives expected supply 9. transfer_then_balanceOf_sender_correct — transfer→read sender balance diff --git a/Contracts/SimpleToken/Proofs/Isolation.lean b/Contracts/SimpleToken/Proofs/Isolation.lean index b817e6de8..0f34d6eb2 100644 --- a/Contracts/SimpleToken/Proofs/Isolation.lean +++ b/Contracts/SimpleToken/Proofs/Isolation.lean @@ -58,11 +58,11 @@ theorem constructor_owner_addr_isolated (s : ContractState) (initialOwner : Addr -- All three mint isolation properties share the same proof structure: -- unfold mint, case-split on both safeAdd calls, simp in each branch. -private theorem mint_isolation (s : ContractState) (to : Address) (amount : Uint256) +private theorem mint_isolation (s : ContractState) (toAddr : Address) (amount : Uint256) (h_owner : s.sender = s.storageAddr 0) (slotIdx : Nat) : - (slotIdx ≠ 2 → ((mint to amount).run s).snd.storage slotIdx = s.storage slotIdx) ∧ - (slotIdx ≠ 1 → ∀ addr, ((mint to amount).run s).snd.storageMap slotIdx addr = s.storageMap slotIdx addr) ∧ - (slotIdx ≠ 0 → ((mint to amount).run s).snd.storageAddr slotIdx = s.storageAddr slotIdx) := by + (slotIdx ≠ 2 → ((mint toAddr amount).run s).snd.storage slotIdx = s.storage slotIdx) ∧ + (slotIdx ≠ 1 → ∀ addr, ((mint toAddr amount).run s).snd.storageMap slotIdx addr = s.storageMap slotIdx addr) ∧ + (slotIdx ≠ 0 → ((mint toAddr amount).run s).snd.storageAddr slotIdx = s.storageAddr slotIdx) := by simp only [mint, Contracts.SimpleToken.ownerSlot, Contracts.SimpleToken.balancesSlot, Contracts.SimpleToken.totalSupplySlot, msgSender, getStorageAddr, getStorage, setStorage, getMapping, setMapping, @@ -70,39 +70,39 @@ private theorem mint_isolation (s : ContractState) (to : Address) (amount : Uint Contract.run, ContractResult.snd, h_owner, beq_self_eq_true, ite_true] unfold Stdlib.Math.requireSomeUint - cases safeAdd (s.storageMap 1 to) amount <;> + cases safeAdd (s.storageMap 1 toAddr) amount <;> simp_all [Verity.require, Verity.pure, Verity.bind, Bind.bind, Pure.pure, beq_iff_eq] cases safeAdd (s.storage 2) amount <;> simp_all [Verity.require, Verity.pure, Verity.bind] /-- Mint only writes Uint256 slotIdx 2. -/ -theorem mint_supply_storage_isolated (s : ContractState) (to : Address) (amount : Uint256) +theorem mint_supply_storage_isolated (s : ContractState) (toAddr : Address) (amount : Uint256) (h_owner : s.sender = s.storageAddr 0) (slotIdx : Nat) : - supply_storage_isolated s ((mint to amount).run s).snd slotIdx := by - unfold supply_storage_isolated; exact (mint_isolation s to amount h_owner slotIdx).1 + supply_storage_isolated s ((mint toAddr amount).run s).snd slotIdx := by + unfold supply_storage_isolated; exact (mint_isolation s toAddr amount h_owner slotIdx).1 /-- Mint only writes Mapping slotIdx 1. -/ -theorem mint_balance_mapping_isolated (s : ContractState) (to : Address) (amount : Uint256) +theorem mint_balance_mapping_isolated (s : ContractState) (toAddr : Address) (amount : Uint256) (h_owner : s.sender = s.storageAddr 0) (slotIdx : Nat) : - balance_mapping_isolated s ((mint to amount).run s).snd slotIdx := by - unfold balance_mapping_isolated; exact (mint_isolation s to amount h_owner slotIdx).2.1 + balance_mapping_isolated s ((mint toAddr amount).run s).snd slotIdx := by + unfold balance_mapping_isolated; exact (mint_isolation s toAddr amount h_owner slotIdx).2.1 /-- Mint doesn't write any Address slotIdx (owner unchanged). -/ -theorem mint_owner_addr_isolated (s : ContractState) (to : Address) (amount : Uint256) +theorem mint_owner_addr_isolated (s : ContractState) (toAddr : Address) (amount : Uint256) (h_owner : s.sender = s.storageAddr 0) (slotIdx : Nat) : - owner_addr_isolated s ((mint to amount).run s).snd slotIdx := by - unfold owner_addr_isolated; exact (mint_isolation s to amount h_owner slotIdx).2.2 + owner_addr_isolated s ((mint toAddr amount).run s).snd slotIdx := by + unfold owner_addr_isolated; exact (mint_isolation s toAddr amount h_owner slotIdx).2.2 /-! ## Transfer Isolation -/ -- All three transfer isolation properties share the same proof structure: --- case-split on sender = to, then simp in each branch. -private theorem transfer_isolation (s : ContractState) (to : Address) (amount : Uint256) +-- case-split on sender = toAddr, then simp in each branch. +private theorem transfer_isolation (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 1 s.sender ≥ amount) (slotIdx : Nat) : - (((transfer to amount).run s).snd.storage slotIdx = s.storage slotIdx) ∧ - (slotIdx ≠ 1 → ∀ addr, ((transfer to amount).run s).snd.storageMap slotIdx addr = s.storageMap slotIdx addr) ∧ - (((transfer to amount).run s).snd.storageAddr slotIdx = s.storageAddr slotIdx) := by - by_cases h_eq : s.sender = to + (((transfer toAddr amount).run s).snd.storage slotIdx = s.storage slotIdx) ∧ + (slotIdx ≠ 1 → ∀ addr, ((transfer toAddr amount).run s).snd.storageMap slotIdx addr = s.storageMap slotIdx addr) ∧ + (((transfer toAddr amount).run s).snd.storageAddr slotIdx = s.storageAddr slotIdx) := by + by_cases h_eq : s.sender = toAddr · have h_balance' := uint256_ge_val_le (h_eq ▸ h_balance) simp [transfer, Contracts.SimpleToken.balancesSlot, msgSender, getMapping, @@ -114,26 +114,26 @@ private theorem transfer_isolation (s : ContractState) (to : Address) (amount : Verity.require, Verity.bind, Bind.bind, Pure.pure, Contract.run, ContractResult.snd, h_balance, h_eq, beq_iff_eq] - all_goals cases safeAdd (s.storageMap 1 to) amount <;> + all_goals cases safeAdd (s.storageMap 1 toAddr) amount <;> simp_all [Verity.require, Verity.pure, Verity.bind] /-- Transfer doesn't write any Uint256 slotIdx (supply unchanged). -/ -theorem transfer_supply_storage_isolated (s : ContractState) (to : Address) (amount : Uint256) +theorem transfer_supply_storage_isolated (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 1 s.sender ≥ amount) (slotIdx : Nat) : - supply_storage_isolated s ((transfer to amount).run s).snd slotIdx := by - unfold supply_storage_isolated; intro _; exact (transfer_isolation s to amount h_balance slotIdx).1 + supply_storage_isolated s ((transfer toAddr amount).run s).snd slotIdx := by + unfold supply_storage_isolated; intro _; exact (transfer_isolation s toAddr amount h_balance slotIdx).1 /-- Transfer only writes Mapping slotIdx 1. -/ -theorem transfer_balance_mapping_isolated (s : ContractState) (to : Address) (amount : Uint256) +theorem transfer_balance_mapping_isolated (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 1 s.sender ≥ amount) (slotIdx : Nat) : - balance_mapping_isolated s ((transfer to amount).run s).snd slotIdx := by - unfold balance_mapping_isolated; exact (transfer_isolation s to amount h_balance slotIdx).2.1 + balance_mapping_isolated s ((transfer toAddr amount).run s).snd slotIdx := by + unfold balance_mapping_isolated; exact (transfer_isolation s toAddr amount h_balance slotIdx).2.1 /-- Transfer doesn't write any Address slotIdx (owner unchanged). -/ -theorem transfer_owner_addr_isolated (s : ContractState) (to : Address) (amount : Uint256) +theorem transfer_owner_addr_isolated (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 1 s.sender ≥ amount) (slotIdx : Nat) : - owner_addr_isolated s ((transfer to amount).run s).snd slotIdx := by - unfold owner_addr_isolated; intro _; exact (transfer_isolation s to amount h_balance slotIdx).2.2 + owner_addr_isolated s ((transfer toAddr amount).run s).snd slotIdx := by + unfold owner_addr_isolated; intro _; exact (transfer_isolation s toAddr amount h_balance slotIdx).2.2 /-! ## Summary diff --git a/Contracts/SimpleToken/Proofs/Supply.lean b/Contracts/SimpleToken/Proofs/Supply.lean index 2582ef121..d125b712f 100644 --- a/Contracts/SimpleToken/Proofs/Supply.lean +++ b/Contracts/SimpleToken/Proofs/Supply.lean @@ -7,8 +7,8 @@ 3. Transfer has an exact sum conservation equation (accounting for occurrences) Key insight: supply_bounds_balances (∀ lists, sum ≤ supply) quantifies over ALL - lists including duplicates. For a list containing address `to` twice, minting - `amount` to `to` increases the sum by 2*amount but supply by only amount. + lists including duplicates. For a list containing address `toAddr` twice, minting + `amount` toAddr `toAddr` increases the sum by 2*amount but supply by only amount. This means the invariant cannot be preserved by mint/transfer in general. We prove the exact conservation equation instead, which is the strongest provable statement about how sums change under state-modifying operations. @@ -31,7 +31,7 @@ open Contracts.SimpleToken.Invariants /-! ## Helper: All-Zero List Sum -/ -/-- If every element maps to 0, the list sum is 0. -/ +/-- If every element maps toAddr 0, the list sum is 0. -/ private theorem map_sum_zero_of_all_zero (f : Address → Uint256) (h_zero : ∀ addr, f addr = 0) : ∀ addrs : List Address, (addrs.map f).sum = 0 := by @@ -68,79 +68,79 @@ theorem constructor_establishes_supply_bounds (s : ContractState) (initialOwner /-! ## Mint: Exact Sum Equation -/ /-- Exact sum relationship after mint: the new sum equals the old sum plus - count(to, addrs) * amount. This captures that each occurrence of `to` in - the list contributes an additional `amount` to the sum. -/ -theorem mint_sum_equation (s : ContractState) (to : Address) (amount : Uint256) + count(toAddr, addrs) * amount. This captures that each occurrence of `toAddr` in + the list contributes an additional `amount` toAddr the sum. -/ +theorem mint_sum_equation (s : ContractState) (toAddr : Address) (amount : Uint256) (h_owner : s.sender = s.storageAddr 0) - (h_no_bal_overflow : (s.storageMap 1 to : Nat) + (amount : Nat) ≤ MAX_UINT256) + (h_no_bal_overflow : (s.storageMap 1 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) (h_no_sup_overflow : (s.storage 2 : Nat) + (amount : Nat) ≤ MAX_UINT256) : ∀ addrs : List Address, - (addrs.map (fun addr => ((mint to amount).run s).snd.storageMap 1 addr)).sum - = (addrs.map (fun addr => s.storageMap 1 addr)).sum + countOccU to addrs * amount := by - have h_spec := mint_meets_spec_when_owner s to amount h_owner h_no_bal_overflow h_no_sup_overflow + (addrs.map (fun addr => ((mint toAddr amount).run s).snd.storageMap 1 addr)).sum + = (addrs.map (fun addr => s.storageMap 1 addr)).sum + countOccU toAddr addrs * amount := by + have h_spec := mint_meets_spec_when_owner s toAddr amount h_owner h_no_bal_overflow h_no_sup_overflow simp [mint_spec] at h_spec obtain ⟨h_bal_raw, _, h_other, _, _, _⟩ := h_spec have h_bal : - ((mint to amount).run s).snd.storageMap 1 to = s.storageMap 1 to + amount := by + ((mint toAddr amount).run s).snd.storageMap 1 toAddr = s.storageMap 1 toAddr + amount := by simpa [evm_add_eq_hadd] using h_bal_raw exact map_sum_point_update (fun addr => s.storageMap 1 addr) - (fun addr => ((mint to amount).run s).snd.storageMap 1 addr) - to amount h_bal h_other.1 + (fun addr => ((mint toAddr amount).run s).snd.storageMap 1 addr) + toAddr amount h_bal h_other.1 -/-- Corollary: for a list where `to` appears exactly once, mint adds exactly `amount`. -/ -theorem mint_sum_singleton_to (s : ContractState) (to : Address) (amount : Uint256) +/-- Corollary: for a list where `toAddr` appears exactly once, mint adds exactly `amount`. -/ +theorem mint_sum_singleton_to (s : ContractState) (toAddr : Address) (amount : Uint256) (h_owner : s.sender = s.storageAddr 0) - (h_no_bal_overflow : (s.storageMap 1 to : Nat) + (amount : Nat) ≤ MAX_UINT256) + (h_no_bal_overflow : (s.storageMap 1 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) (h_no_sup_overflow : (s.storage 2 : Nat) + (amount : Nat) ≤ MAX_UINT256) - (addrs : List Address) (h_once : countOcc to addrs = 1) : - (addrs.map (fun addr => ((mint to amount).run s).snd.storageMap 1 addr)).sum + (addrs : List Address) (h_once : countOcc toAddr addrs = 1) : + (addrs.map (fun addr => ((mint toAddr amount).run s).snd.storageMap 1 addr)).sum = (addrs.map (fun addr => s.storageMap 1 addr)).sum + amount := by - have h := mint_sum_equation s to amount h_owner h_no_bal_overflow h_no_sup_overflow addrs + have h := mint_sum_equation s toAddr amount h_owner h_no_bal_overflow h_no_sup_overflow addrs simp [countOccU, h_once] at h simpa [Verity.Core.Uint256.add_comm] using h /-- Exact sum conservation equation for transfer: - new_sum + count(sender, addrs) * amount = old_sum + count(to, addrs) * amount. + new_sum + count(sender, addrs) * amount = old_sum + count(toAddr, addrs) * amount. This is the fundamental conservation law for transfer: each occurrence of the sender in the list loses `amount`, and each occurrence of the recipient gains `amount`. The equation holds exactly (not just as an inequality). -/ -theorem transfer_sum_equation (s : ContractState) (to : Address) (amount : Uint256) +theorem transfer_sum_equation (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 1 s.sender ≥ amount) - (h_ne : s.sender ≠ to) - (h_no_overflow : (s.storageMap 1 to : Nat) + (amount : Nat) ≤ MAX_UINT256) : + (h_ne : s.sender ≠ toAddr) + (h_no_overflow : (s.storageMap 1 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) : ∀ addrs : List Address, - (addrs.map (fun addr => ((transfer to amount).run s).snd.storageMap 1 addr)).sum + (addrs.map (fun addr => ((transfer toAddr amount).run s).snd.storageMap 1 addr)).sum + countOccU s.sender addrs * amount = (addrs.map (fun addr => s.storageMap 1 addr)).sum - + countOccU to addrs * amount := by - have h_spec := transfer_meets_spec_when_sufficient s to amount h_balance (fun _ => h_no_overflow) + + countOccU toAddr addrs * amount := by + have h_spec := transfer_meets_spec_when_sufficient s toAddr amount h_balance (fun _ => h_no_overflow) simp [transfer_spec, h_ne, beq_iff_eq] at h_spec obtain ⟨_, h_sender_bal, h_recip_bal, h_other_bal, _, _, _, _⟩ := h_spec have h_recip_bal' : - ((transfer to amount).run s).snd.storageMap 1 to = s.storageMap 1 to + amount := by + ((transfer toAddr amount).run s).snd.storageMap 1 toAddr = s.storageMap 1 toAddr + amount := by simpa [evm_add_eq_hadd] using h_recip_bal exact map_sum_transfer_eq (fun addr => s.storageMap 1 addr) - (fun addr => ((transfer to amount).run s).snd.storageMap 1 addr) - s.sender to amount h_ne h_sender_bal h_recip_bal' + (fun addr => ((transfer toAddr amount).run s).snd.storageMap 1 addr) + s.sender toAddr amount h_ne h_sender_bal h_recip_bal' h_other_bal.1 -/-- Corollary: for NoDup lists where sender and to each appear once, +/-- Corollary: for NoDup lists where sender and toAddr each appear once, the total balance sum is exactly preserved by transfer. -/ -theorem transfer_sum_preserved_unique (s : ContractState) (to : Address) (amount : Uint256) +theorem transfer_sum_preserved_unique (s : ContractState) (toAddr : Address) (amount : Uint256) (h_balance : s.storageMap 1 s.sender ≥ amount) - (h_ne : s.sender ≠ to) - (h_no_overflow : (s.storageMap 1 to : Nat) + (amount : Nat) ≤ MAX_UINT256) + (h_ne : s.sender ≠ toAddr) + (h_no_overflow : (s.storageMap 1 toAddr : Nat) + (amount : Nat) ≤ MAX_UINT256) (addrs : List Address) (h_sender_once : countOcc s.sender addrs = 1) - (h_to_once : countOcc to addrs = 1) : - (addrs.map (fun addr => ((transfer to amount).run s).snd.storageMap 1 addr)).sum + (h_to_once : countOcc toAddr addrs = 1) : + (addrs.map (fun addr => ((transfer toAddr amount).run s).snd.storageMap 1 addr)).sum = (addrs.map (fun addr => s.storageMap 1 addr)).sum := by - have h := transfer_sum_equation s to amount h_balance h_ne h_no_overflow addrs + have h := transfer_sum_equation s toAddr amount h_balance h_ne h_no_overflow addrs simp [countOccU, h_sender_once, h_to_once] at h - have h' : (addrs.map (fun addr => ((transfer to amount).run s).snd.storageMap 1 addr)).sum + amount = + have h' : (addrs.map (fun addr => ((transfer toAddr amount).run s).snd.storageMap 1 addr)).sum + amount = (addrs.map (fun addr => s.storageMap 1 addr)).sum + amount := by simpa [Verity.Core.Uint256.add_comm] using h exact Verity.Core.Uint256.add_right_cancel h' @@ -156,13 +156,13 @@ Helper lemma: Supply conservation: 2. constructor_establishes_supply_bounds — simpleTokenConstructor establishes invariant (all lists) -3. mint_sum_equation — exact sum change: new = old + count(to) * amount -4. mint_sum_singleton_to — for unique to: new_sum = old_sum + amount -5. transfer_sum_equation — exact conservation: new + count(sender)*amt = old + count(to)*amt -6. transfer_sum_preserved_unique — for unique sender & to: new_sum = old_sum +3. mint_sum_equation — exact sum change: new = old + count(toAddr) * amount +4. mint_sum_singleton_to — for unique toAddr: new_sum = old_sum + amount +5. transfer_sum_equation — exact conservation: new + count(sender)*amt = old + count(toAddr)*amt +6. transfer_sum_preserved_unique — for unique sender & toAddr: new_sum = old_sum NoDup corollaries (4 and 6) give the strongest practical conservation statements: -when each address appears at most once in the list, mint adds exactly `amount` to +when each address appears at most once in the list, mint adds exactly `amount` toAddr the balance sum, and transfer preserves it exactly. -/ diff --git a/Contracts/SimpleToken/SimpleToken.lean b/Contracts/SimpleToken/SimpleToken.lean index 597fc8886..ab0c6f83b 100644 --- a/Contracts/SimpleToken/SimpleToken.lean +++ b/Contracts/SimpleToken/SimpleToken.lean @@ -16,29 +16,29 @@ verity_contract SimpleToken where setStorageAddr ownerSlot initialOwner setStorage totalSupplySlot 0 - function mint (to : Address, amount : Uint256) : Unit := do + function mint (toAddr : Address, amount : Uint256) : Unit := do let sender ← msgSender let currentOwner ← getStorageAddr ownerSlot require (sender == currentOwner) "Caller is not the owner" - let currentBalance ← getMapping balancesSlot to + let currentBalance ← getMapping balancesSlot toAddr let newBalance ← requireSomeUint (safeAdd currentBalance amount) "Balance overflow" let currentSupply ← getStorage totalSupplySlot let newSupply ← requireSomeUint (safeAdd currentSupply amount) "Supply overflow" - setMapping balancesSlot to newBalance + setMapping balancesSlot toAddr newBalance setStorage totalSupplySlot newSupply - function transfer (to : Address, amount : Uint256) : Unit := do + function transfer (toAddr : Address, amount : Uint256) : Unit := do let sender ← msgSender let senderBalance ← getMapping balancesSlot sender require (senderBalance >= amount) "Insufficient balance" - if sender == to then + if sender == toAddr then pure () else - let recipientBalance ← getMapping balancesSlot to + let recipientBalance ← getMapping balancesSlot toAddr let newRecipientBalance ← requireSomeUint (safeAdd recipientBalance amount) "Recipient balance overflow" setMapping balancesSlot sender (sub senderBalance amount) - setMapping balancesSlot to newRecipientBalance + setMapping balancesSlot toAddr newRecipientBalance function balanceOf (addr : Address) : Uint256 := do let currentBalance ← getMapping balancesSlot addr diff --git a/Contracts/SimpleToken/Spec.lean b/Contracts/SimpleToken/Spec.lean index 11535311a..076551763 100644 --- a/Contracts/SimpleToken/Spec.lean +++ b/Contracts/SimpleToken/Spec.lean @@ -14,16 +14,16 @@ open Verity.Specs /-! ## Operation Specifications -/ --- Constructor: sets owner and initializes total supply to 0 +-- Constructor: sets owner and initializes total supply toAddr 0 #gen_spec_addr_storage constructor_spec for (initialOwner : Address) (0, 2, (fun _ => initialOwner), (fun _ => 0), sameMapContext) -- Mint: increases balance and total supply by amount (owner only) -#gen_spec_map_storage mint_spec for (to : Address) (amount : Uint256) - (1, to, (fun st => add (st.storageMap 1 to) amount), 2, (fun st => add (st.storage 2) amount), sameStorageAddrSlotContext 0) +#gen_spec_map_storage mint_spec for (toAddr : Address) (amount : Uint256) + (1, toAddr, (fun st => add (st.storageMap 1 toAddr) amount), 2, (fun st => add (st.storage 2) amount), sameStorageAddrSlotContext 0) #gen_spec_transfer transfer_spec for3 - (sender : Address) (to : Address) (amount : Uint256) + (sender : Address) (toAddr : Address) (amount : Uint256) (1, (fun st st' => sameStorageAddrSlot 0 st st' ∧ sameStorageAddrContext st st')) diff --git a/Contracts/Smoke.lean b/Contracts/Smoke.lean index de6321149..6ea5266f8 100644 --- a/Contracts/Smoke.lean +++ b/Contracts/Smoke.lean @@ -658,7 +658,7 @@ verity_contract FunctionConstantNameConflictRejected where return 0 /-- -error: duplicate function declaration 'echo' +error: duplicate function declaration 'echo()' -/ #guard_msgs in verity_contract DuplicateFunctionRejected where @@ -670,6 +670,118 @@ verity_contract DuplicateFunctionRejected where function echo () : Uint256 := do return 2 +verity_contract FunctionOverloadSmoke where + storage + + function echo (a : Uint256) : Uint256 := do + return a + + function echo (a : Address) : Uint256 := do + return (addressToWord a) + + function echo (a : Uint256, b : Uint256) : Uint256 := do + return (add a b) + +/-- +error: duplicate function ABI signature 'echo(scalar_uint256)' after ABI erasure +-/ +#guard_msgs in +verity_contract NewtypeErasedOverloadRejected where + types + Amount : Uint256 + Shares : Uint256 + storage + + function echo (a : Amount) : Uint256 := do + return a + + function echo (a : Shares) : Uint256 := do + return a + +/-- +error: duplicate function ABI signature 'echo(tuple2_scalar_uint8__scalar_uint256)' after ABI erasure +-/ +#guard_msgs in +verity_contract AdtErasedOverloadRejected where + inductive + LeftBox := | LeftValue(value : Uint256) + RightBox := | RightValue(value : Uint256) + storage + + function echo (a : LeftBox) : Uint256 := do + return 0 + + function echo (a : RightBox) : Uint256 := do + return 0 + +verity_contract HelperExternalArgumentSmoke where + storage + saved : Uint256 := slot 0 + + linked_externals + external echo(Uint256) -> (Uint256) + + function idWord (a : Uint256) : Uint256 := do + return a + + function pair (a : Uint256) : Tuple [Uint256, Uint256] := do + return (a, add a 1) + + function put (a : Uint256) : Unit := do + setStorage saved a + + function bindExternalArg (x : Uint256) : Uint256 := do + let y ← idWord (externalCall "echo" [x]) + return y + + function tupleExternalArg (x : Uint256) : Uint256 := do + let (a, b) ← pair (externalCall "echo" [x]) + return (add a b) + + function statementExternalArg (x : Uint256) : Unit := do + put (externalCall "echo" [x]) + +verity_contract BlockTimestampSmoke where + storage + + function nowish () : Uint256 := do + let t ← Verity.blockTimestamp + return t + + function timestampPlus (delta : Uint256) : Uint256 := do + let t ← blockTimestamp + return (add t delta) + + function blobFeePlus (delta : Uint256) : Uint256 := do + let fee ← blobbasefee + return (add fee delta) + +example : + (BlockTimestampSmoke.nowish.run { Verity.defaultState with blockTimestamp := 123 }).getValue? = + some 123 := by + decide + +example : + (BlockTimestampSmoke.timestampPlus 7 |>.run { Verity.defaultState with blockTimestamp := 123 }).getValue? = + some 130 := by + decide + +example : + (BlockTimestampSmoke.blobFeePlus 7 |>.run { Verity.defaultState with blobBaseFee := 123 }).getValue? = + some 130 := by + decide + +/-- +error: context accessor 'blockTimestamp' is monadic; use `let x ← blockTimestamp` before using it in a pure expression +-/ +#guard_msgs in +verity_contract PureBlockTimestampAccessorRejected where + storage + + function nowish () : Uint256 := do + let t := blockTimestamp + return t + /-- error: storage field 'spec' conflicts with reserved generated declaration 'spec' -/ @@ -707,6 +819,80 @@ verity_contract ConstantFunctionHelperCollisionRejected where function price () : Uint256 := do return 3 +/-- +error: function 'structMember' conflicts with reserved generated declaration 'structMember' +-/ +#guard_msgs in +verity_contract StructMappingGeneratedReadHelperCollisionRejected where + storage + positions : MappingStruct(Address,[delegate @word 0]) := slot 0 + + function structMember () : Uint256 := do + return 1 + +/-- +error: immutable 'setStructMember2' conflicts with reserved generated declaration 'setStructMember2' +-/ +#guard_msgs in +verity_contract StructMappingGeneratedWriteHelperImmutableCollisionRejected where + storage + approvals : MappingStruct2(Address,Address,[allowance @word 0]) := slot 0 + + immutables + setStructMember2 : Uint256 := 1 + + constructor () := do + pure () + + function allowanceOf (owner : Address, spender : Address) : Uint256 := do + let amount ← structMember2 "approvals" owner spender "allowance" + return amount + +/-- +error: function 'quote' generates internal declaration 'quote__scalar_uint256' that conflicts with a contract constant of the same name +-/ +#guard_msgs in +verity_contract ConstantOverloadGeneratedNameCollisionRejected where + storage + + constants + quote__scalar_uint256 : Uint256 := 7 + + function quote (amount : Uint256) : Uint256 := do + return amount + + function quote (recipient : Address) : Uint256 := do + return (addressToWord recipient) + +/-- +error: function 'quote' generates internal declaration 'quote__scalar_uint256' that conflicts with an immutable of the same name +-/ +#guard_msgs in +verity_contract ImmutableOverloadGeneratedNameCollisionRejected where + storage + + immutables + quote__scalar_uint256 : Uint256 := 7 + + function quote (amount : Uint256) : Uint256 := do + return amount + + function quote (recipient : Address) : Uint256 := do + return (addressToWord recipient) + +#guard_msgs in +verity_contract OverloadSignatureEncodingSmoke where + types + tuple_uint256 : Uint256 + storage + + function quote (amount : tuple_uint256) : Uint256 := do + return amount + + function quote (pair : Tuple [Uint256, Uint256]) : Uint256 := do + let _pairValue := pair + return pair_0 + verity_contract TupleSmoke where storage values : Uint256 → Uint256 := slot 0 @@ -728,6 +914,91 @@ verity_contract TupleSmoke where let flag := cfg_2 setMapping authorized owner flag +verity_contract CurveCutArraySmoke where + storage + lastXt : Uint256 := slot 0 + lastLiq : Uint256 := slot 1 + lastOffset : Uint256 := slot 2 + + function firstCutXt (cuts : Array (Tuple [Uint256, Uint256, Int256])) : Uint256 := do + let (xtReserve, _liqSquare, _offset) := arrayElement cuts 0 + return xtReserve + + function returnCut (cuts : Array (Tuple [Uint256, Uint256, Int256]), idx : Uint256) : Tuple [Uint256, Uint256, Int256] := do + return arrayElement cuts idx + + function storeCut (cuts : Array (Tuple [Uint256, Uint256, Int256]), idx : Uint256) : Unit := do + let (xtReserve, liqSquare, offset) := arrayElement cuts idx + setStorage lastXt xtReserve + setStorage lastLiq liqSquare + setStorage lastOffset (toUint256 offset) + + function storeTwoCuts (cuts : Array (Tuple [Uint256, Uint256, Int256]), firstIdx : Uint256, secondIdx : Uint256) : Unit := do + let (firstXt, _firstLiq, _firstOffset) := arrayElement cuts firstIdx + let (secondXt, _secondLiq, _secondOffset) := arrayElement cuts secondIdx + setStorage lastXt firstXt + setStorage lastLiq secondXt + +/-- +error: arrayElement currently supports only arrays with single-word static elements on the compilation-model path, got Verity.Macro.ValueType.array + (Verity.Macro.ValueType.tuple [Verity.Macro.ValueType.uint256, Verity.Macro.ValueType.uint256]) +-/ +#guard_msgs in +verity_contract CurveCutPlainTupleArrayElementRejected where + storage + + function badPlainRead (cuts : Array (Tuple [Uint256, Uint256])) : Uint256 := do + let cut := arrayElement cuts 0 + return 0 + +def curveCutArrayExecutableReadsTupleElement : Bool := + match CurveCutArraySmoke.firstCutXt #[(11, 13, toInt256 17)] Verity.defaultState with + | .success value _ => value == 11 + | _ => false + +example : curveCutArrayExecutableReadsTupleElement = true := by decide + +verity_contract PackedStorageWriteSmoke where + storage + stateRoot : Uint256 := slot 0 + + function writeSlot0 (isClosed : Bool, maxTotalSupply : Uint256) : Unit := do + let closedWord := boolToWord isClosed + let slot0 := bitOr closedWord (shl 8 maxTotalSupply) + setPackedStorage stateRoot 0 slot0 + + function writeSlot1 (accruedProtocolFees : Uint256, normalizedUnclaimedWithdrawals : Uint256) : Unit := do + let slot1 := bitOr accruedProtocolFees (shl 128 normalizedUnclaimedWithdrawals) + setPackedStorage stateRoot 1 slot1 + +def packedStorageExecutableWritesExplicitWordOffset : Bool := + match PackedStorageWriteSmoke.writeSlot1 7 9 Verity.defaultState with + | .success _ state => + state.storage 0 == 0 && + state.storage 1 == bitOr 7 (shl 128 9) + | _ => false + +example : packedStorageExecutableWritesExplicitWordOffset = true := by decide + +verity_contract PackedAddressStorageWriteSmoke where + storage + owner : Address := slot 0 + + function writeOwnerWord (word : Uint256) : Address := do + setPackedStorage owner 0 word + let current ← getStorageAddr owner + return current + +def packedStorageExecutableUpdatesAddressMirror : Bool := + match PackedAddressStorageWriteSmoke.writeOwnerWord 0xA11CE Verity.defaultState with + | .success value state => + value == Verity.wordToAddress 0xA11CE && + state.storage 0 == 0xA11CE && + state.storageAddr 0 == Verity.wordToAddress 0xA11CE + | _ => false + +example : packedStorageExecutableUpdatesAddressMirror = true := by decide + verity_contract DirectHelperCallSmoke where storage total : Uint256 := slot 0 @@ -853,6 +1124,28 @@ verity_contract ZeroAddressShadowSmoke where function shadowWrite (zeroAddress : Address) : Unit := do setMappingAddr delegates zeroAddress zeroAddress +verity_contract ContextAccessorShadowSmoke where + storage + + constants + chainid : Uint256 := 31337 + + immutables + blockTimestamp : Uint256 := 12345 + msgSender : Address := (wordToAddress 42) + + function echoSenderName (msgSender : Address) : Address := do + return msgSender + + function constantNamedChainid () : Uint256 := do + return chainid + + function immutableNamedBlockTimestamp () : Uint256 := do + return blockTimestamp + + function immutableNamedMsgSender () : Address := do + return msgSender + verity_contract StructMappingSmoke where storage positions : MappingStruct(Address,[ @@ -891,6 +1184,19 @@ verity_contract StructMappingSmoke where let nextNonce ← structMember2 "approvals" owner spender "nonce" return nextNonce +private def _structMemberExecutableHelper : + String → Address → String → Contract Uint256 := + StructMappingSmoke.structMember +private def _setStructMemberExecutableHelper : + String → Address → String → Uint256 → Contract Unit := + StructMappingSmoke.setStructMember +private def _structMember2ExecutableHelper : + String → Address → Address → String → Contract Uint256 := + StructMappingSmoke.structMember2 +private def _setStructMember2ExecutableHelper : + String → Address → Address → String → Uint256 → Contract Unit := + StructMappingSmoke.setStructMember2 + verity_contract ExternalCallSmoke where storage echoedValue : Uint256 := slot 0 @@ -929,11 +1235,11 @@ verity_contract ERC20HelperSmoke where lastAllowance : Uint256 := slot 1 lastSupply : Uint256 := slot 2 - function pushTokens (token : Address, to : Address, amount : Uint256) : Unit := do - safeTransfer token to amount + function pushTokens (token : Address, toAddr : Address, amount : Uint256) : Unit := do + safeTransfer token toAddr amount - function pullTokens (token : Address, fromAddr : Address, to : Address, amount : Uint256) : Unit := do - safeTransferFrom token fromAddr to amount + function pullTokens (token : Address, fromAddr : Address, toAddr : Address, amount : Uint256) : Unit := do + safeTransferFrom token fromAddr toAddr amount function approveTokens (token : Address, spender : Address, amount : Uint256) : Unit := do safeApprove token spender amount @@ -1041,8 +1347,8 @@ verity_contract ERC20HelperShadowWriteRejected where function safeTransfer (_token : Address, _to : Address, amount : Uint256) : Unit := do setStorage lastTransfer amount - function writeShadowedTransfer (token : Address, to : Address, amount : Uint256) : Unit := do - safeTransfer token to amount + function writeShadowedTransfer (token : Address, toAddr : Address, amount : Uint256) : Unit := do + safeTransfer token toAddr amount /-- error: linked external 'describe' uses unsupported parameter type; executable externalCall currently supports only Uint256, Int256, Uint8, Address, Bytes32, and Bool @@ -1294,10 +1600,16 @@ end SpecGenSmoke #check_contract StatelessSmoke #check_contract SpecialEntrypointSmoke #check_contract TupleSmoke +#check_contract CurveCutArraySmoke +#check_contract PackedStorageWriteSmoke #check_contract DirectHelperCallSmoke #check_contract Uint8Smoke #check_contract AddressHelpersSmoke #check_contract ZeroAddressShadowSmoke +#check_contract ContextAccessorShadowSmoke +#check_contract FunctionOverloadSmoke +#check_contract HelperExternalArgumentSmoke +#check_contract BlockTimestampSmoke #check_contract StructMappingSmoke #check_contract ExternalCallSmoke #check_contract TryExternalCallSmoke diff --git a/Contracts/StringSmoke.lean b/Contracts/StringSmoke.lean index 3ff231537..2d059dead 100644 --- a/Contracts/StringSmoke.lean +++ b/Contracts/StringSmoke.lean @@ -169,7 +169,7 @@ verity_contract StringLogicalUnsupported where return 0 /-- -error: local binding 'alias' currently cannot bind dynamic values (Verity.Macro.ValueType.string) to local variables on the compilation-model path; use the parameter directly +error: local binding 'localAlias' currently cannot bind dynamic values (Verity.Macro.ValueType.string) to local variables on the compilation-model path; use the parameter directly -/ #guard_msgs in verity_contract StringLocalAliasUnsupported where @@ -177,11 +177,11 @@ verity_contract StringLocalAliasUnsupported where sentinel : Uint256 := slot 0 function same (lhs : String, rhs : String) : Bool := do - let alias := lhs - return (alias == rhs) + let localAlias := lhs + return (localAlias == rhs) /-- -error: local binding 'alias' currently cannot bind dynamic values (Verity.Macro.ValueType.bytes) to local variables on the compilation-model path; use the parameter directly +error: local binding 'localAlias' currently cannot bind dynamic values (Verity.Macro.ValueType.bytes) to local variables on the compilation-model path; use the parameter directly -/ #guard_msgs in verity_contract BytesLocalAliasUnsupported where @@ -189,8 +189,8 @@ verity_contract BytesLocalAliasUnsupported where sentinel : Uint256 := slot 0 function same (lhs : Bytes, rhs : Bytes) : Bool := do - let alias := lhs - return (alias == rhs) + let localAlias := lhs + return (localAlias == rhs) /-- error: returnArray currently supports only arrays with single-word static elements on the compilation-model path, got Verity.Macro.ValueType.array (Verity.Macro.ValueType.string) diff --git a/Makefile b/Makefile index 5d0705919..29d2de75d 100644 --- a/Makefile +++ b/Makefile @@ -109,10 +109,13 @@ test-evmyullean-fork: ## Probe EVMYulLean fork conformance (audit + adapter repo python3 scripts/generate_evmyullean_fork_audit.py --check @echo "Checking EVMYulLean adapter report..." python3 scripts/generate_evmyullean_adapter_report.py --check - @echo "Building EVMYulLean adapter correctness, bridge lemmas, and 123 concrete bridge tests..." + @echo "Building EVMYulLean adapter correctness, bridge lemmas, native harness, and 123 concrete bridge tests..." lake build Compiler.Proofs.YulGeneration.Backends.EvmYulLeanAdapterCorrectness lake build Compiler.Proofs.YulGeneration.Backends.EvmYulLeanBridgeLemmas lake build Compiler.Proofs.YulGeneration.Backends.EvmYulLeanBridgeTest + lake build Compiler.Proofs.YulGeneration.Backends.EvmYulLeanNativeHarness + lake build Compiler.Proofs.YulGeneration.Backends.EvmYulLeanNativeSmokeTest + lake exe native-dispatch-oracle-test @echo "Building public EVMYulLean EndToEnd target..." lake build Compiler.Proofs.EndToEnd @echo "EVMYulLean fork conformance probe passed." @@ -155,6 +158,7 @@ check: ## Run local CI-equivalent checks job (no Lean build, no solc) python3 scripts/generate_evmyullean_capability_report.py --check python3 scripts/generate_evmyullean_adapter_report.py --check python3 scripts/generate_evmyullean_fork_audit.py --check + python3 scripts/check_native_transition_doc.py python3 scripts/generate_print_axioms.py --check python3 scripts/check_proof_length.py python3 scripts/check_issue_1060_integrity.py diff --git a/PrintAxioms.lean b/PrintAxioms.lean index 998976660..177b802ae 100644 --- a/PrintAxioms.lean +++ b/PrintAxioms.lean @@ -47,9 +47,11 @@ import Compiler.Proofs.IRGeneration.SupportedSpec import Compiler.Proofs.KeccakBound import Compiler.Proofs.MappingSlot import Compiler.Proofs.StorageBounds +import Compiler.Proofs.YulGeneration.Backends.EvmYulLeanAdapter import Compiler.Proofs.YulGeneration.Backends.EvmYulLeanAdapterCorrectness import Compiler.Proofs.YulGeneration.Backends.EvmYulLeanBodyClosure import Compiler.Proofs.YulGeneration.Backends.EvmYulLeanBridgeLemmas +import Compiler.Proofs.YulGeneration.Backends.EvmYulLeanNativeHarness import Compiler.Proofs.YulGeneration.Backends.EvmYulLeanRetarget import Compiler.Proofs.YulGeneration.Backends.EvmYulLeanSignedArithSpec import Compiler.Proofs.YulGeneration.Backends.EvmYulLeanSourceExprClosure @@ -671,6 +673,14 @@ import Compiler.Proofs.YulGeneration.ReferenceOracle.Semantics #print axioms Compiler.Proofs.ArithmeticProfile.shr_bridge -- Compiler/Proofs/EndToEnd.lean +#print axioms Compiler.Proofs.EndToEnd.nativeResultsMatchOn_ok_of_resultsMatch_of_yulResultsAgreeOn +#print axioms Compiler.Proofs.EndToEnd.nativeIRRuntimeAgreesWithInterpreter_of_ok_agree +#print axioms Compiler.Proofs.EndToEnd.nativeDispatcherExecAgreesWithInterpreter_of_exec_ok_agree +#print axioms Compiler.Proofs.EndToEnd.nativeDispatcherExecAgreesWithInterpreter_of_exec_yulHalt_agree +#print axioms Compiler.Proofs.EndToEnd.nativeDispatcherExecAgreesWithInterpreter_of_exec_error_agree +#print axioms Compiler.Proofs.EndToEnd.nativeDispatcherBlockAgreesWithInterpreter_of_exec_agree +#print axioms Compiler.Proofs.EndToEnd.nativeCallDispatcherAgreesWithInterpreter_of_dispatcherBlock_agree +#print axioms Compiler.Proofs.EndToEnd.nativeIRRuntimeAgreesWithInterpreter_of_lowered_callDispatcher_agree #print axioms Compiler.Proofs.EndToEnd.layer3_function_preserves_semantics #print axioms Compiler.Proofs.EndToEnd.interpretYulRuntime_eq_yulResultOfExec #print axioms Compiler.Proofs.EndToEnd.yulStateOfIR_eq_initial @@ -683,9 +693,13 @@ import Compiler.Proofs.YulGeneration.ReferenceOracle.Semantics #print axioms Compiler.Proofs.EndToEnd.layer3_contract_preserves_semantics_via_reference_oracle #print axioms Compiler.Proofs.EndToEnd.layer3_contract_preserves_semantics_evmYulLean_with_function_bridge #print axioms Compiler.Proofs.EndToEnd.layer3_contract_preserves_semantics_evmYulLean +#print axioms Compiler.Proofs.EndToEnd.layer3_contract_preserves_semantics_native_of_interpreter_bridge +#print axioms Compiler.Proofs.EndToEnd.layer3_contract_preserves_semantics_native_of_lowered_callDispatcher_bridge #print axioms Compiler.Proofs.EndToEnd.layers2_3_ir_matches_yul_via_reference_oracle #print axioms Compiler.Proofs.EndToEnd.layers2_3_ir_matches_yul_evmYulLean_with_function_bridge #print axioms Compiler.Proofs.EndToEnd.layers2_3_ir_matches_yul_evmYulLean +#print axioms Compiler.Proofs.EndToEnd.layers2_3_ir_matches_native_evmYulLean_of_interpreter_bridge +#print axioms Compiler.Proofs.EndToEnd.layers2_3_ir_matches_native_evmYulLean_of_lowered_callDispatcher_bridge #print axioms Compiler.Proofs.EndToEnd.simpleStorage_endToEnd -- #print axioms Compiler.Proofs.EndToEnd.simpleStorage_functions_bridged -- private #print axioms Compiler.Proofs.EndToEnd.simpleStorage_endToEnd_evmYulLean @@ -1269,6 +1283,7 @@ import Compiler.Proofs.YulGeneration.ReferenceOracle.Semantics -- #print axioms Compiler.Proofs.IRGeneration.legacyCompatibleExternalStmtList_of_compileStmt_ok_stop -- private -- #print axioms Compiler.Proofs.IRGeneration.legacyCompatibleExternalStmtList_of_compileStmt_ok_mstore -- private -- #print axioms Compiler.Proofs.IRGeneration.legacyCompatibleExternalStmtList_of_compileStmt_ok_tstore -- private +-- #print axioms Compiler.Proofs.IRGeneration.legacyCompatibleExternalStmtList_of_compileStmt_ok_setStorageWord -- private #print axioms Compiler.Proofs.IRGeneration.legacyCompatibleExternalStmtList_of_compileStmt_ok_on_supportedContractSurface #print axioms Compiler.Proofs.IRGeneration.legacyCompatibleExternalStmtList_of_compileStmtList_ok_on_supportedContractSurface #print axioms Compiler.Proofs.IRGeneration.stmtListCompiledLegacyCompatible_of_supportedContractSurface @@ -1883,6 +1898,7 @@ import Compiler.Proofs.YulGeneration.ReferenceOracle.Semantics -- #print axioms Compiler.Proofs.IRGeneration.SourceSemantics.evalExpr_mapping -- private -- #print axioms Compiler.Proofs.IRGeneration.SourceSemantics.evalExpr_mappingUint -- private -- #print axioms Compiler.Proofs.IRGeneration.SourceSemantics.evalExpr_arrayElement -- private +-- #print axioms Compiler.Proofs.IRGeneration.SourceSemantics.evalExpr_arrayElementWord -- private -- #print axioms Compiler.Proofs.IRGeneration.SourceSemantics.evalExpr_mappingWord -- private -- #print axioms Compiler.Proofs.IRGeneration.SourceSemantics.evalExpr_mappingPackedWord -- private -- #print axioms Compiler.Proofs.IRGeneration.SourceSemantics.evalExpr_structMember -- private @@ -2197,6 +2213,32 @@ import Compiler.Proofs.YulGeneration.ReferenceOracle.Semantics #print axioms Compiler.Proofs.StorageBounds.writeStorageArray_storage_unchanged #print axioms Compiler.Proofs.StorageBounds.writeStorageArray_events_unchanged +-- Compiler/Proofs/YulGeneration/Backends/EvmYulLeanAdapter.lean +#print axioms Compiler.Proofs.YulGeneration.Backends.lowerExprNative_call_runtimePrimOp +#print axioms Compiler.Proofs.YulGeneration.Backends.lowerExprNative_call_userFunction +#print axioms Compiler.Proofs.YulGeneration.Backends.lowerNativeSwitchBlock_eq +#print axioms Compiler.Proofs.YulGeneration.Backends.lowerStmtsNativeWithSwitchIds_nil +#print axioms Compiler.Proofs.YulGeneration.Backends.lowerStmtsNativeWithSwitchIds_cons +#print axioms Compiler.Proofs.YulGeneration.Backends.lowerSwitchCasesNativeWithSwitchIds_nil +#print axioms Compiler.Proofs.YulGeneration.Backends.lowerSwitchCasesNativeWithSwitchIds_cons +#print axioms Compiler.Proofs.YulGeneration.Backends.lowerSwitchCasesNativeWithSwitchIds_find?_none +#print axioms Compiler.Proofs.YulGeneration.Backends.lowerSwitchCasesNativeWithSwitchIds_find?_some +#print axioms Compiler.Proofs.YulGeneration.Backends.lowerStmtGroupNativeWithSwitchIds_comment +#print axioms Compiler.Proofs.YulGeneration.Backends.lowerStmtGroupNativeWithSwitchIds_let +#print axioms Compiler.Proofs.YulGeneration.Backends.lowerStmtGroupNativeWithSwitchIds_letMany +#print axioms Compiler.Proofs.YulGeneration.Backends.lowerStmtGroupNativeWithSwitchIds_assign +#print axioms Compiler.Proofs.YulGeneration.Backends.lowerStmtGroupNativeWithSwitchIds_expr +#print axioms Compiler.Proofs.YulGeneration.Backends.lowerStmtGroupNativeWithSwitchIds_leave +#print axioms Compiler.Proofs.YulGeneration.Backends.lowerStmtGroupNativeWithSwitchIds_if +#print axioms Compiler.Proofs.YulGeneration.Backends.lowerStmtGroupNativeWithSwitchIds_for +#print axioms Compiler.Proofs.YulGeneration.Backends.lowerStmtGroupNativeWithSwitchIds_switch +#print axioms Compiler.Proofs.YulGeneration.Backends.lowerStmtGroupNativeWithSwitchIds_block +#print axioms Compiler.Proofs.YulGeneration.Backends.lowerStmtGroupNativeWithSwitchIds_funcDef +#print axioms Compiler.Proofs.YulGeneration.Backends.lowerRuntimeContractNativeAux_nil +#print axioms Compiler.Proofs.YulGeneration.Backends.lowerRuntimeContractNativeAux_funcDef_cons +#print axioms Compiler.Proofs.YulGeneration.Backends.lowerRuntimeContractNativeAux_stmt_cons +#print axioms Compiler.Proofs.YulGeneration.Backends.lowerRuntimeContractNative_empty + -- Compiler/Proofs/YulGeneration/Backends/EvmYulLeanAdapterCorrectness.lean #print axioms Compiler.Proofs.YulGeneration.Backends.AdapterCorrectness.assign_equiv_let #print axioms Compiler.Proofs.YulGeneration.Backends.AdapterCorrectness.assign_equiv_let' @@ -2734,6 +2776,204 @@ import Compiler.Proofs.YulGeneration.ReferenceOracle.Semantics #print axioms Compiler.Proofs.YulGeneration.Backends.evalBuiltinCallWithBackendContext_evmYulLean_mappingSlot_bridge #print axioms Compiler.Proofs.YulGeneration.Backends.evalBuiltinCallWithBackendContext_evmYulLean_pure_bridge +-- Compiler/Proofs/YulGeneration/Backends/EvmYulLeanNativeHarness.lean +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.selectorExprMatchesGeneratedDispatcher_selectorExpr +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.selectedSwitchBody_hit +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.selectedSwitchBody_miss +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.nativeDispatchSelector_of_selector_lt +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.nativeChainIdRepresentable_global +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.nativeBlobBaseFeeRepresentable_minimum +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.validateNativeRuntimeEnvironment_noChainId_noBlobBaseFee +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.validateNativeRuntimeEnvironment_representableBlobBaseFee +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.validateNativeRuntimeEnvironment_representableEnvironment +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.validateNativeRuntimeEnvironment_unsupportedChainId +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.validateNativeRuntimeEnvironment_unsupportedBlobBaseFee +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.validateNativeRuntimeEnvironment_unsupportedHeaderBuiltin +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.initialState_installsExecutionContract +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.initialState_installsCurrentAccountContract +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.initialState_transactionEnvironment +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.initialState_source +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.initialState_sender +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.initialState_codeOwner +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.initialState_weiValue +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.initialState_blockTimestamp +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.initialState_blockNumber +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.initialState_calldata +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.initialState_calldataSize +-- #print axioms Compiler.Proofs.YulGeneration.Backends.Native.byteArray_get?_append_left -- private +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.readBytes_zero_get?_of_lt_source +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.initialState_calldataReadWord_selectorByte0 +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.initialState_calldataReadWord_selectorByte1 +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.initialState_calldataReadWord_selectorByte2 +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.initialState_calldataReadWord_selectorByte3 +-- #print axioms Compiler.Proofs.YulGeneration.Backends.Native.byteArray_data_toList_get?_of_get? -- private +-- #print axioms Compiler.Proofs.YulGeneration.Backends.Native.list_reverse_eq_drop4_reverse_append_four -- private +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.initialState_calldataReadWord_selectorPrefix +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.selectorBytesAsNat +-- #print axioms Compiler.Proofs.YulGeneration.Backends.Native.fromBytes'_append -- private +-- #print axioms Compiler.Proofs.YulGeneration.Backends.Native.fromBytes'_lt -- private +-- #print axioms Compiler.Proofs.YulGeneration.Backends.Native.uint256_ofNat_eq_mk -- private +-- #print axioms Compiler.Proofs.YulGeneration.Backends.Native.uint256_eq_of_toNat_eq -- private +-- #print axioms Compiler.Proofs.YulGeneration.Backends.Native.uint256_ofNat_toNat_of_lt -- private +-- #print axioms Compiler.Proofs.YulGeneration.Backends.Native.uint256_shiftRight_224_mk_toNat -- private +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.uint256_shiftRight_224_ofNat_toNat +-- #print axioms Compiler.Proofs.YulGeneration.Backends.Native.fromBytes'_four -- private +-- #print axioms Compiler.Proofs.YulGeneration.Backends.Native.fromBytes'_tail4_shift -- private +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.fromBytes'_selectorPrefix_shift +-- #print axioms Compiler.Proofs.YulGeneration.Backends.Native.usize_sub_toNat_of_le_32 -- private +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.readBytes_zero_32_size +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.initialState_selectorExpr_native_value +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.initialState_selectorExpr_native_value_of_readBytes_size +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.initialState_selectorExpr_native_uint256 +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.lowerExprNative_selectorExpr +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.step_calldataload_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.step_shr_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.step_eq_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.step_iszero_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.step_and_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.primCall_calldataload_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.primCall_shr_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.primCall_eq_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.primCall_iszero_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.primCall_and_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.eval_lowerExprNative_selectorExpr_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.eval_lowerExprNative_selectorExpr_initialState_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_let_lowerExprNative_selectorExpr_initialState_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_let_lowerExprNative_selectorExpr_initialState_ok_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_let_lit_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_block_cons_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_block_append_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_block_nil_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_nativeSwitchPrefix_selector_initialState_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_nativeSwitchPrefix_selector_initialState_ok_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_if_eval_zero +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_if_eval_nonzero +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.eval_nativeSwitchGuardedMatch_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.eval_nativeSwitchGuardedMatch_ok_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.eval_nativeSwitchGuardedMatch_hit_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.eval_nativeSwitchGuardedMatch_miss_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.eval_nativeSwitchGuardedMatch_miss_ok_fuel +-- #print axioms Compiler.Proofs.YulGeneration.Backends.Native.uint256_land_zero_left -- private +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.eval_nativeSwitchGuardedMatch_matched_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.eval_nativeSwitchGuardedMatch_matched_ok_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_if_nativeSwitchGuardedMatch_hit +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_if_nativeSwitchGuardedMatch_miss +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_if_nativeSwitchGuardedMatch_miss_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_lowerAssignNative_lit_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_if_nativeSwitchGuardedMatch_hit_marked +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.eval_nativeSwitchGuardedMatch_hit_ok_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_if_nativeSwitchGuardedMatch_hit_marked_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_if_nativeSwitchGuardedMatch_matched +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_if_nativeSwitchGuardedMatch_matched_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.eval_nativeSwitchDefaultGuard_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.eval_nativeSwitchDefaultGuard_ok_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.eval_nativeSwitchDefaultGuard_unmatched_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.eval_nativeSwitchDefaultGuard_unmatched_ok_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.eval_nativeSwitchDefaultGuard_matched_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.eval_nativeSwitchDefaultGuard_matched_ok_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_if_nativeSwitchDefaultGuard_unmatched +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_if_nativeSwitchDefaultGuard_unmatched_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_if_nativeSwitchDefaultGuard_matched +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_if_nativeSwitchDefaultGuard_matched_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.lowerNativeSwitchBlock_selectorExpr_eq_nativeSwitchParts +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.state_lookup_insert_of_ne +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.state_getElem_insert_of_ne +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.nativeSwitchDiscrTempName_ne_matchedTempName +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.nativeSwitchPrefixFinalState_matched +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.nativeSwitchPrefixFinalState_discr +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.nativeSwitchPrefixFinalState_marked +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.NativeBlockPreservesWord_nil +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.NativeBlockPreservesWord_cons +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.NativeBlockPreservesWord_cons_stmt +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.NativeStmtPreservesWord_lowerAssignNative_lit_of_ne +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.nativeSwitchTempsFreshForNativeBodies_case_matched_not_mem +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.nativeSwitchTempsFreshForNativeBodies_find_hit_matched_not_mem +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.nativeSwitchTempsFreshForNativeBodies_default_matched_not_mem +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.nativeSwitchCaseIfs_nil +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.nativeSwitchCaseIfs_cons +-- #print axioms Compiler.Proofs.YulGeneration.Backends.Native.list_find?_eq_some_split_false -- private +-- #print axioms Compiler.Proofs.YulGeneration.Backends.Native.list_find?_eq_none_all_false -- private +-- #print axioms Compiler.Proofs.YulGeneration.Backends.Native.uint256_ofNat_ne_of_ne_of_lt -- private +-- #print axioms Compiler.Proofs.YulGeneration.Backends.Native.nativeSwitch_prefix_miss_of_selector_find -- private +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.nativeSwitch_find_hit_split +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.nativeSwitch_find_none_all_miss +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_nativeSwitchCaseIfs_all_miss_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_nativeSwitchCaseIfs_matched_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_nativeSwitchCaseIfs_head_hit_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_nativeSwitchCaseIfs_cons_miss_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_nativeSwitchCaseIfs_prefix_hit_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_nativeSwitchCaseIfs_find_hit_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_nativeSwitchCaseIfs_find_hit_preserved_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_nativeSwitchCaseIfs_find_none_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_nativeSwitchDefaultIf_unmatched_nonempty_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_nativeSwitchDefaultIf_matched_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_nativeSwitchDefaultIf_matched_caseTail_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_nativeSwitchDefaultIf_unmatched_caseTail_nonempty_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_nativeSwitchCaseIfs_with_default_matched_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_nativeSwitchCaseIfs_find_hit_with_default_preserved_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_nativeSwitchCaseIfs_find_none_with_default_nonempty_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_nativeSwitchCaseIfs_find_none_without_default_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_nativeSwitchPrefix_then_tail_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_nativeSwitchTail_find_hit_preserved_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_nativeSwitchTail_find_none_with_default_nonempty_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_nativeSwitchTail_find_none_without_default_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_lowerNativeSwitchBlock_selector_find_hit_preserved_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_lowerNativeSwitchBlock_selector_find_none_with_default_nonempty_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.exec_lowerNativeSwitchBlock_selector_find_none_without_default_fuel +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.initialState_unbridgedEnvironmentDefaults +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectStorageFromState_accountStorageSlot +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectStorageFromState_missingAccountStorageSlot +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectStorageFromState_missingAccount +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.initialState_observableStorageSlot +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.initialState_omittedStorageSlot +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectLogEntry_topicsAndWordData +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectLogsFromState_logSeries +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectHaltReturn_stop +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectHaltReturn_32ByteReturn +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectHaltReturn_non32ByteReturn +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.contractDispatcherBlockResult_eq_execResult +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.callDispatcherBlockResult_initialState_eq_contractDispatcherBlockResult +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.callDispatcher_zero +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.callDispatcher_succ_eq_callDispatcherBlockResult +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_ok +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_yulHalt +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_ok_events +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_ok_success +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_ok_returnValue +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_ok_finalMappings +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_ok_finalStorageSlot +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_ok_missingFinalStorageAccountSlot +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_ok_missingFinalStorageSlot +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_yulHalt_events +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_yulHalt_success +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_yulHalt_returnValue +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_yulHalt_finalMappings +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_yulHalt_finalStorageSlot +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_yulHalt_missingFinalStorageAccountSlot +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_yulHalt_missingFinalStorageSlot +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_stop +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_32ByteReturn +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_non32ByteReturn +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_revert +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_revert_events +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_revert_success +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_revert_returnValue +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_revert_finalMappings +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_revert_finalStorageSlot +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_hardError +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_hardError_success +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_hardError_returnValue +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_hardError_finalStorageSlot +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_hardError_finalMappings +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_hardError_events +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.projectResult_finalMappings +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.interpretRuntimeNative_loweringError +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.interpretRuntimeNative_eq_callDispatcher_of_lowerRuntimeContractNative +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.interpretRuntimeNative_environmentError +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.interpretIRRuntimeNative_eq_interpretRuntimeNative +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.interpretIRRuntimeNative_loweringError +#print axioms Compiler.Proofs.YulGeneration.Backends.Native.interpretIRRuntimeNative_eq_callDispatcher_of_lowerRuntimeContractNative + -- Compiler/Proofs/YulGeneration/Backends/EvmYulLeanRetarget.lean -- #print axioms Compiler.Proofs.YulGeneration.Backends.backends_agree_add -- private -- #print axioms Compiler.Proofs.YulGeneration.Backends.backends_agree_sub -- private @@ -2802,6 +3042,8 @@ import Compiler.Proofs.YulGeneration.ReferenceOracle.Semantics -- #print axioms Compiler.Proofs.YulGeneration.Backends.bridgedExpr_callvalue -- private -- #print axioms Compiler.Proofs.YulGeneration.Backends.bridgedExpr_calldatasize -- private -- #print axioms Compiler.Proofs.YulGeneration.Backends.bridgedExpr_selector -- private +#print axioms Compiler.Proofs.YulGeneration.Backends.bridgedExpr_selectorExpr +#print axioms Compiler.Proofs.YulGeneration.Backends.evalYulExprWithBackend_evmYulLean_selectorExpr_semantics -- #print axioms Compiler.Proofs.YulGeneration.Backends.bridgedExpr_calldatasize_lt -- private -- #print axioms Compiler.Proofs.YulGeneration.Backends.bridgedExpr_has_selector -- private -- #print axioms Compiler.Proofs.YulGeneration.Backends.bridgedExpr_empty_calldata -- private @@ -2905,6 +3147,7 @@ import Compiler.Proofs.YulGeneration.ReferenceOracle.Semantics #print axioms Compiler.Proofs.YulGeneration.Backends.execYulFuelWithBackend_eq_on_bridged_stmt #print axioms Compiler.Proofs.YulGeneration.Backends.execYulFuelWithBackend_eq_on_bridged_stmts #print axioms Compiler.Proofs.YulGeneration.Backends.emitYul_runtimeCode_evmYulLean_eq_on_bridged_bodies +#print axioms Compiler.Proofs.YulGeneration.Backends.interpretYulRuntimeWithBackend_eq_fuel #print axioms Compiler.Proofs.YulGeneration.Backends.interpretYulRuntimeWithBackend_verity_eq #print axioms Compiler.Proofs.YulGeneration.Backends.interpretYulFromIR_evmYulLean_eq_on_bridged_bodies #print axioms Compiler.Proofs.YulGeneration.Backends.yulCodegen_preserves_semantics_evmYulLean @@ -2943,7 +3186,13 @@ import Compiler.Proofs.YulGeneration.ReferenceOracle.Semantics -- #print axioms Compiler.Proofs.YulGeneration.Backends.StateBridge.calldataToByteArray_selectorBytes_size -- private -- #print axioms Compiler.Proofs.YulGeneration.Backends.StateBridge.calldataToByteArray_wordBytes_size -- private -- #print axioms Compiler.Proofs.YulGeneration.Backends.StateBridge.calldataToByteArray_fold_size -- private +-- #print axioms Compiler.Proofs.YulGeneration.Backends.StateBridge.byteArray_get?_append_left -- private +-- #print axioms Compiler.Proofs.YulGeneration.Backends.StateBridge.calldataToByteArray_fold_get?_left -- private #print axioms Compiler.Proofs.YulGeneration.Backends.StateBridge.calldataToByteArray_size +#print axioms Compiler.Proofs.YulGeneration.Backends.StateBridge.calldataToByteArray_selectorByte0 +#print axioms Compiler.Proofs.YulGeneration.Backends.StateBridge.calldataToByteArray_selectorByte1 +#print axioms Compiler.Proofs.YulGeneration.Backends.StateBridge.calldataToByteArray_selectorByte2 +#print axioms Compiler.Proofs.YulGeneration.Backends.StateBridge.calldataToByteArray_selectorByte3 #print axioms Compiler.Proofs.YulGeneration.Backends.StateBridge.callvalue_bridge #print axioms Compiler.Proofs.YulGeneration.Backends.StateBridge.timestamp_bridge #print axioms Compiler.Proofs.YulGeneration.Backends.StateBridge.number_bridge @@ -2998,4 +3247,4 @@ import Compiler.Proofs.YulGeneration.ReferenceOracle.Semantics -- Compiler/Proofs/YulGeneration/ReferenceOracle/Semantics.lean #print axioms Compiler.Proofs.YulGeneration.YulTransaction.ofIR_sender #print axioms Compiler.Proofs.YulGeneration.YulTransaction.ofIR_args --- Total: 2831 theorems/lemmas (1948 public, 883 private, 0 sorry'd) +-- Total: 3074 theorems/lemmas (2170 public, 904 private, 0 sorry'd) diff --git a/TRUST_ASSUMPTIONS.md b/TRUST_ASSUMPTIONS.md index 7582efbc0..220639441 100644 --- a/TRUST_ASSUMPTIONS.md +++ b/TRUST_ASSUMPTIONS.md @@ -60,7 +60,7 @@ Current theorem totals, property-test coverage, and proof status live in [docs/V - **Role**: Runtime execution model. - **Status (Phase 4: full semantic integration for safe compiler-produced bodies)**: 25 universal pure bridge theorems (all fully proven) in `Compiler/Proofs/YulGeneration/Backends/EvmYulLeanBridgeLemmas.lean`. All pure bridge cases are now covered by universal symbolic lemmas. Additionally, 11 context/env/storage/helper builtin bridge theorems bring the total to 36 of 36 builtins bridged, and `smod`/`sar` no longer depend on admitted bridge lemmas. The retargeting module `EvmYulLeanRetarget.lean` proves the pointwise theorem `backends_agree_on_bridged_builtins`, the expression-level theorem `evalYulExpr_evmYulLean_eq_on_bridged`, statement-fragment helpers for straight-line, block, if, switch, and for cases, the recursive target theorem `execYulFuelWithBackend_eq_on_bridged_target`, emitted-runtime closure/equality theorems, and `yulCodegen_preserves_semantics_evmYulLean`, which retargets the existing Layer-3 IR→Yul preservation theorem to `interpretYulRuntimeWithBackend .evmYulLean` under bridged-body hypotheses. `EvmYulLeanBodyClosure.lean` proves `compileStmtList_always_bridged`, a universal aggregation theorem for `BridgedSafeStmts`; the external-call family (`internalCall`, `internalCallAssign`, `externalCallBind`, and `ecm`) remains carved out behind explicit function-table hypotheses. `EndToEnd.lean` now exposes `layers2_3_ir_matches_yul_evmYulLean` without raw `BridgedStmts` body hypotheses by deriving them from `SupportedSpec`, static-parameter witnesses, and `BridgedSafeStmts` source-body witnesses. Gas is not modeled. - **Trust boundary (EVMYulLean EndToEnd target)**: For `BridgedExpr` expressions, `BridgedStraightStmts` statement lists (including mapping-slot, literal-slot, and identifier-slot `sstore`), recursive `BridgedTarget` executions, source statement lists admitted by `BridgedSafeStmts`, emitted runtime wrappers whose embedded bodies satisfy `BridgedStmt`, Layer-3 contract executions whose body hypotheses are discharged, and the public EndToEnd wrapper that derives raw body bridges from source-level safe-body/static-param witnesses, the trust assumption moves from "Verity's custom builtin implementations are correct" to "EVMYulLean's execution model matches the EVM" (backed by upstream Ethereum conformance tests) with fully proven builtin bridge dependencies. -- **Fork dependency**: Verity pins [`lfglabs-dev/EVMYulLean`](https://github.com/lfglabs-dev/EVMYulLean), a fork of [`NethermindEth/EVMYulLean`](https://github.com/NethermindEth/EVMYulLean). The pinned commit is recorded in `lake-manifest.json` under the `evmyul` package. The exact divergence from upstream is enumerated in [`artifacts/evmyullean_fork_audit.json`](artifacts/evmyullean_fork_audit.json), regenerated by `scripts/generate_evmyullean_fork_audit.py` and validated by `make check`. As of the current pin, the fork is 2 commits ahead of `upstream/main` and 0 behind; both commits are non-semantic (one visibility change on an exponentiation accumulator, one Lean 4.22.0 deprecation fix), so upstream Ethereum conformance test coverage applies transitively. In addition to the `make check` validation, a weekly scheduled GitHub Actions workflow ([`.github/workflows/evmyullean-fork-conformance.yml`](.github/workflows/evmyullean-fork-conformance.yml)) runs `make test-evmyullean-fork`, which re-verifies the fork audit artifact against `lake-manifest.json`, checks the EVMYulLean adapter report, rebuilds adapter correctness, rebuilds the public EndToEnd EVMYulLean target, and rebuilds the universal bridge lemmas (25 proven) together with the 123 concrete `native_decide` bridge-equivalence tests (`EvmYulLeanBridgeTest`), surfacing any upstream drift as a run annotation during the two-week `continue-on-error` burn-in ending 2026-05-04 and then as a red workflow plus an automatically opened or updated GitHub issue for scheduled/manual failures. +- **Fork dependency**: Verity pins [`lfglabs-dev/EVMYulLean`](https://github.com/lfglabs-dev/EVMYulLean), a fork of [`NethermindEth/EVMYulLean`](https://github.com/NethermindEth/EVMYulLean). The pinned commit is recorded in `lake-manifest.json` under the `evmyul` package. The exact divergence from upstream is enumerated in [`artifacts/evmyullean_fork_audit.json`](artifacts/evmyullean_fork_audit.json), regenerated by `scripts/generate_evmyullean_fork_audit.py` and validated by `make check`. As of the current pin, the fork is 2 commits ahead of `upstream/main` and 0 behind; both commits are non-semantic (one visibility change on an exponentiation accumulator, one Lean 4.22.0 deprecation fix), so upstream Ethereum conformance test coverage applies transitively. In addition to the `make check` validation, a weekly scheduled GitHub Actions workflow ([`.github/workflows/evmyullean-fork-conformance.yml`](.github/workflows/evmyullean-fork-conformance.yml)) runs `make test-evmyullean-fork`, which re-verifies the fork audit artifact against `lake-manifest.json`, checks the EVMYulLean adapter report, rebuilds adapter correctness, rebuilds the native transition harness and smoke tests, rebuilds the public EndToEnd EVMYulLean target, and rebuilds the universal bridge lemmas (25 proven) together with the 123 concrete `native_decide` bridge-equivalence tests (`EvmYulLeanBridgeTest`), surfacing any upstream drift as a run annotation during the two-week `continue-on-error` burn-in ending 2026-05-04 and then as a red workflow plus an automatically opened or updated GitHub issue for scheduled/manual failures. - **Remaining gap for whole-program retargeting**: The report generator now records status `full_semantic_integration`: `compileStmtList_always_bridged` is proven for the safe source-body whitelist, and `EndToEnd.lean` discharges the public theorem's raw external-function `BridgedStmts` body hypotheses for supported compiler-produced contracts. The remaining scope limit is the external-call/function-table family, which stays carved out of `BridgedSafeStmts`. - **Implication**: Semantic correctness does not imply gas-safety. - **Proxy note**: `delegatecall`-based proxy / upgradeability flows still sit outside the current proof-interpreter model. Archive `--trust-report` and use `--deny-proxy-upgradeability` when proxy semantics must remain outside the selected verified subset (issue `#1420`). diff --git a/Verity/Core.lean b/Verity/Core.lean index 57676852c..9d0e99f9c 100644 --- a/Verity/Core.lean +++ b/Verity/Core.lean @@ -529,6 +529,9 @@ def blockTimestamp : Contract Uint256 := def blockNumber : Contract Uint256 := fun state => ContractResult.success state.blockNumber state +protected def blobbasefee : Contract Uint256 := + fun state => ContractResult.success state.blobBaseFee state + def chainid : Contract Uint256 := fun state => ContractResult.success state.chainId state @@ -547,6 +550,9 @@ def chainid : Contract Uint256 := @[simp] theorem blockNumber_run (state : ContractState) : blockNumber.run state = ContractResult.success state.blockNumber state := rfl +@[simp] theorem blobbasefee_run (state : ContractState) : + Verity.blobbasefee.run state = ContractResult.success state.blobBaseFee state := rfl + @[simp] theorem chainid_run (state : ContractState) : chainid.run state = ContractResult.success state.chainId state := rfl diff --git a/Verity/Core/Free/TypedIR.lean b/Verity/Core/Free/TypedIR.lean index 10a551cae..25215d4a2 100644 --- a/Verity/Core/Free/TypedIR.lean +++ b/Verity/Core/Free/TypedIR.lean @@ -57,6 +57,7 @@ inductive TStmt where | assign (dst : TVar) (rhs : TExpr dst.ty) | setStorage (slot : Nat) (value : TExpr .uint256) | setStorageAddr (slot : Nat) (value : TExpr .address) + | setStorageWord (slot : Nat) (value : TExpr .uint256) | setMapping (slot : Nat) (key : TExpr .address) (value : TExpr .uint256) | setMapping2 (slot : Nat) (key1 key2 : TExpr .address) (value : TExpr .uint256) | setMappingUint (slot : Nat) (key : TExpr .uint256) (value : TExpr .uint256) @@ -218,6 +219,11 @@ def evalTStmtFuel : Nat → TExecState → TStmt → TExecResult let v := evalTExpr s value .ok { s with world := { s.world with storageAddr := fun i => if i == slot then v else s.world.storageAddr i } } + | Nat.succ _, s, .setStorageWord slot value => + let v := evalTExpr s value + .ok { s with world := { s.world with + storage := fun i => if i == slot then v else s.world.storage i, + storageAddr := fun i => if i == slot then Verity.wordToAddress v else s.world.storageAddr i } } | Nat.succ _, s, .setMapping slot key value => let k := evalTExpr s key let v := evalTExpr s value diff --git a/Verity/Macro/Elaborate.lean b/Verity/Macro/Elaborate.lean index e49f70064..b71312852 100644 --- a/Verity/Macro/Elaborate.lean +++ b/Verity/Macro/Elaborate.lean @@ -15,7 +15,7 @@ set_option hygiene false def elabVerityContract : CommandElab := fun stx => do let (contractName, _newtypeDecls, adtDecls, fields, errorDecls, constDecls, immutableDecls, externalDecls, ctor, functions, storageNamespace) ← parseContractSyntax stx - validateGeneratedDefNamesPublic fields constDecls functions + validateGeneratedDefNamesPublic fields constDecls immutableDecls functions validateConstantDeclsPublic constDecls validateImmutableDeclsPublic fields constDecls immutableDecls ctor validateExternalDeclsPublic externalDecls @@ -32,6 +32,9 @@ def elabVerityContract : CommandElab := fun stx => do for imm in immutableDecls.zipIdx do elabCommand (← mkStorageDefCommandPublic (immutableStorageFieldDecl fields imm.1 imm.2)) + for cmd in (← mkExecutableStructMappingCommandsPublic fields) do + elabCommand cmd + -- Emit storageNamespace : Nat for the contract (#1730, Axis 4 Step 4a). -- Use the resolved namespace from parseContractSyntax to respect custom keys. elabCommand (← mkStorageNamespaceCommand (toString contractName.getId) storageNamespace) diff --git a/Verity/Macro/Translate.lean b/Verity/Macro/Translate.lean index cb2fdc83f..2c20a0fc9 100644 --- a/Verity/Macro/Translate.lean +++ b/Verity/Macro/Translate.lean @@ -162,6 +162,51 @@ structure FunctionDecl where localObligations : Array LocalObligationDecl := #[] body : Term +private partial def valueTypeSignatureComponent : ValueType → String + | .uint256 => "scalar_uint256" + | .int256 => "scalar_int256" + | .uint8 => "scalar_uint8" + | .address => "scalar_address" + | .bool => "scalar_bool" + | .bytes32 => "scalar_bytes32" + | .string => "scalar_string" + | .bytes => "scalar_bytes" + | .unit => "scalar_unit" + | .array ty => "array_" ++ valueTypeSignatureComponent ty + | .tuple tys => "tuple" ++ toString tys.length ++ "_" ++ String.intercalate "__" (tys.map valueTypeSignatureComponent) + | .newtype name baseType => "newtype_" ++ name ++ "_" ++ valueTypeSignatureComponent baseType + | .adt name _ => "adt_" ++ name + +private def functionSignatureKey (fn : FunctionDecl) : String := + fn.name ++ "(" ++ String.intercalate "," (fn.params.toList.map (fun p => valueTypeSignatureComponent p.ty)) ++ ")" + +private partial def valueTypeAbiSignatureComponent : ValueType → String + | .newtype _ baseType => valueTypeAbiSignatureComponent baseType + | .array ty => "array_" ++ valueTypeAbiSignatureComponent ty + | .tuple tys => "tuple" ++ toString tys.length ++ "_" ++ String.intercalate "__" (tys.map valueTypeAbiSignatureComponent) + | .adt _ maxFields => + "tuple" ++ toString (maxFields + 1) ++ "_" ++ + String.intercalate "__" ("scalar_uint8" :: List.replicate maxFields "scalar_uint256") + | ty => valueTypeSignatureComponent ty + +private def functionAbiSignatureKey (fn : FunctionDecl) : String := + fn.name ++ "(" ++ String.intercalate "," (fn.params.toList.map (fun p => valueTypeAbiSignatureComponent p.ty)) ++ ")" + +private def overloadedFunctionIdentName (fn : FunctionDecl) : String := + let suffix := + match fn.params.toList.map (fun p => valueTypeSignatureComponent p.ty) with + | [] => "0" + | parts => String.intercalate "_" parts + fn.name ++ "__" ++ suffix + +private def assignOverloadInternalIdents (functions : Array FunctionDecl) : + Array FunctionDecl := + functions.map fun fn => + if functions.any (fun other => other.name == fn.name && functionSignatureKey other != functionSignatureKey fn) then + { fn with ident := mkIdentFrom fn.ident (Name.mkSimple (overloadedFunctionIdentName fn)) } + else + fn + structure ConstructorDecl where params : Array ParamDecl isPayable : Bool := false @@ -206,7 +251,13 @@ private def natFromSyntax (stx : Syntax) : CommandElabM Nat := | some n => pure n | none => throwErrorAt stx "expected natural literal" +private partial def stripParens (stx : Term) : Term := + match stx with + | `(term| ($inner)) => stripParens inner + | _ => stx + private partial def valueTypeFromSyntax (newtypes : Array NewtypeDecl) (adtDecls : Array AdtDecl) (ty : Term) : CommandElabM ValueType := do + let ty := stripParens ty match ty with | `(term| Uint256) => pure .uint256 | `(term| Int256) => pure .int256 @@ -871,11 +922,6 @@ private def validateExternalExecutableType throwErrorAt extIdent s!"linked external '{extName}' uses unsupported {role} type; executable externalCall currently supports only Uint256, Int256, Uint8, Address, Bytes32, and Bool" -private partial def stripParens (stx : Term) : Term := - match stx with - | `(term| ($inner)) => stripParens inner - | _ => stx - private def tupleElemsFromTerm? (stx : Term) : Option (Array Term) := tupleElemsFromSyntax? (stripParens stx).raw |>.map (·.map (fun syn => ⟨syn⟩)) @@ -1179,6 +1225,28 @@ private abbrev TypedLocal := String × ValueType private def typedLocalNames (locals : Array TypedLocal) : Array String := locals.map Prod.fst +private def matchesBareName (actual bare : String) : Bool := + actual == bare || actual.endsWith s!".{bare}" + +private def declaredNameMatches (query declared : String) : Bool := + matchesBareName query declared || matchesBareName declared query + +private def contextAccessorBareName? (name : String) : Option String := + if matchesBareName name "msgSender" then some "msgSender" + else if matchesBareName name "msgValue" then some "msgValue" + else if matchesBareName name "blockTimestamp" then some "blockTimestamp" + else if matchesBareName name "blockNumber" then some "blockNumber" + else if matchesBareName name "blobbasefee" then some "blobbasefee" + else if matchesBareName name "contractAddress" then some "contractAddress" + else if matchesBareName name "chainid" then some "chainid" + else none + +private def findContextAccessorShadowName? + (params : Array ParamDecl) (locals : Array String) (name : String) : Option String := + match params.find? (fun p => matchesBareName p.name name) with + | some param => some param.name + | none => locals.find? (fun localName => matchesBareName localName name) + private def isSignedWordValueType : ValueType → Bool | .int256 => true | .newtype _ baseType => isSignedWordValueType baseType @@ -1194,6 +1262,18 @@ private def isSingleWordStaticValueType : ValueType → Bool | .newtype _ baseType => isSingleWordStaticValueType baseType | ty => isWordLikeValueType ty +private partial def staticAbiWordCount? : ValueType → Option Nat + | .uint256 | .int256 | .uint8 | .address | .bytes32 | .bool => some 1 + | .tuple elemTys => + elemTys.foldl + (fun acc ty => + match acc, staticAbiWordCount? ty with + | some n, some m => some (n + m) + | _, _ => none) + (some 0) + | .newtype _ baseType => staticAbiWordCount? baseType + | _ => none + private def classifyWordArithmeticResultType (stx : Syntax) (context : String) @@ -1283,11 +1363,24 @@ private def requireSupportedArrayElementSourceType (ty : ValueType) : CommandElabM ValueType := do match ty with | .array elemTy => - if isSingleWordStaticValueType elemTy then - pure elemTy - else + unless isSingleWordStaticValueType elemTy do throwErrorAt stx s!"{context} currently supports only arrays with single-word static elements on the compilation-model path, got {renderValueType ty}" + pure elemTy + | _ => + throwErrorAt stx s!"{context} requires an Array parameter, got {renderValueType ty}" + +private def requireSupportedArrayElementTupleSourceType + (stx : Syntax) + (context : String) + (ty : ValueType) : CommandElabM ValueType := do + match ty with + | .array elemTy => + match staticAbiWordCount? elemTy with + | some _ => pure elemTy + | none => + throwErrorAt stx + s!"{context} currently supports only arrays with static ABI-word elements on the tuple arrayElement path, got {renderValueType ty}" | _ => throwErrorAt stx s!"{context} requires an Array parameter, got {renderValueType ty}" @@ -1447,25 +1540,18 @@ private def validateCustomErrorCall if customErrorRequiresDirectParamRef expectedTy then validateDirectParamCustomErrorArg arg fnName errorName params expectedTy argIdx -private def matchLocalFunctionApp? - (functions : Array FunctionDecl) - (stx : Term) : Option (FunctionDecl × Array Term) := +private def localFunctionAppSyntax? + (stx : Term) : Option (String × Array Term) := let stx := stripParens stx - let findFn := fun (fnName : String) (arity : Nat) => - functions.find? fun fn => fn.name == fnName && fn.params.size == arity match stx.raw with | .node _ `Lean.Parser.Term.app args => match args.getD 0 Syntax.missing with | .ident _ raw _ _ => let argTerms := (args.getD 1 Syntax.missing).getArgs.map (fun syn => ⟨syn⟩) - match findFn raw.toString argTerms.size with - | some fn => some (fn, argTerms) - | none => none + some (raw.toString, argTerms) | _ => none | .ident _ raw _ _ => - match findFn raw.toString 0 with - | some fn => some (fn, #[]) - | none => none + some (raw.toString, #[]) | _ => none private def internalHelperSpecName @@ -1475,6 +1561,9 @@ private def internalHelperSpecName (Compiler.CompilationModel.internalFunctionPrefix ++ fnName) (functions.map (·.name)).toList +private def internalHelperSpecNameFor (fn : FunctionDecl) : String := + Compiler.CompilationModel.internalFunctionPrefix ++ toString fn.ident.getId + private partial def hasDynamicInternalHelperType (ty : ValueType) : Bool := match ty with | .string | .bytes | .array _ => true @@ -1494,6 +1583,10 @@ private def ensureSupportsInternalHelperSpec throwErrorAt stx s!"helper call '{fn.name}' uses a parameter or return type that direct macro helper lowering does not support yet; only static non-fallback/non-receive helpers can be lowered to internal specs" +private def throwPureContextAccessorError (stx : Syntax) (name : String) : CommandElabM α := + throwErrorAt stx + s!"context accessor '{name}' is monadic; use `let x ← {name}` before using it in a pure expression" + mutual private partial def inferPureExprType (fields : Array StorageFieldDecl) @@ -1505,20 +1598,57 @@ private partial def inferPureExprType (stx : Term) (visitingConstants : List String := []) : CommandElabM ValueType := do let stx := stripParens stx + let inferContextAccessorOrLocal (name : String) : CommandElabM ValueType := do + match locals.findSome? (fun localDecl => + if matchesBareName localDecl.fst name then some localDecl.snd else none) + <|> params.findSome? (fun p => + if matchesBareName p.name name then some p.ty else none) with + | some ty => pure ty + | none => throwPureContextAccessorError stx name match stx with | `(term| true) | `(term| false) => pure .bool | `(term| constructorArg $idx:num) => match params[(← natFromSyntax idx)]? with | some param => pure param.ty | none => throwErrorAt stx s!"constructorArg index {idx.raw.reprint.getD ""} is out of bounds" - | `(term| msgValue) | `(term| blockTimestamp) | `(term| blockNumber) | `(term| blobbasefee) - | `(term| chainid) | `(term| calldatasize) | `(term| returndataSize) => + | `(term| Verity.msgSender) => + throwPureContextAccessorError stx "msgSender" + | `(term| Verity.msgValue) => + throwPureContextAccessorError stx "msgValue" + | `(term| Verity.blockTimestamp) => + throwPureContextAccessorError stx "blockTimestamp" + | `(term| Verity.blockNumber) => + throwPureContextAccessorError stx "blockNumber" + | `(term| Verity.blobbasefee) => + throwPureContextAccessorError stx "blobbasefee" + | `(term| Verity.chainid) => + throwPureContextAccessorError stx "chainid" + | `(term| Verity.contractAddress) => + throwPureContextAccessorError stx "contractAddress" + | `(term| $id:ident) => + let name := toString id.getId + match params.findSome? (fun p => if p.name == name then some p.ty else none) + <|> tupleParamElemType? params name + <|> lookupTypedLocalType? locals name + <|> immutableDecls.findSome? (fun imm => + if declaredNameMatches name imm.name then some imm.ty else none) + <|> constDecls.findSome? (fun constant => + if declaredNameMatches name constant.name then some constant.ty else none) with + | some ty => pure ty + | none => + if matchesBareName name "calldatasize" || matchesBareName name "returndataSize" then + pure .uint256 + else if matchesBareName name "zeroAddress" then + pure .address + else + match contextAccessorBareName? name with + | some accessor => inferContextAccessorOrLocal accessor + | none => throwErrorAt stx s!"unknown variable '{name}'" + | `(term| calldatasize) | `(term| returndataSize) => pure .uint256 - | `(term| contractAddress) => - pure .address | `(term| zeroAddress) => match lookupTypedLocalType? locals "zeroAddress" <|> params.findSome? (fun p => - if p.name == "zeroAddress" then some p.ty else none) with + if p.name == "zeroAddress" then some p.ty else none) with | some ty => pure ty | none => pure .address | `(term| isZeroAddress $a:term) => @@ -1541,15 +1671,6 @@ private partial def inferPureExprType | `(term| boolToWord $a:term) => requireBoolType a "boolToWord" (← inferPureExprType fields constDecls immutableDecls externalDecls params locals a visitingConstants) pure .uint256 - | `(term| $id:ident) => - let name := toString id.getId - match params.findSome? (fun p => if p.name == name then some p.ty else none) - <|> tupleParamElemType? params name - <|> lookupTypedLocalType? locals name - <|> immutableDecls.findSome? (fun imm => if imm.name == name then some imm.ty else none) - <|> constDecls.findSome? (fun constant => if constant.name == name then some constant.ty else none) with - | some ty => pure ty - | none => throwErrorAt stx s!"unknown variable '{name}'" | `(term| $n:num) => pure .uint256 | `(term| add $a $b) | `(term| sub $a $b) | `(term| mul $a $b) => do @@ -1816,10 +1937,15 @@ private partial def inferBindSourceType requireWordLikeType key1 "structMember2 key" (← inferPureExprType fields constDecls immutableDecls externalDecls params locals key1) requireWordLikeType key2 "structMember2 key" (← inferPureExprType fields constDecls immutableDecls externalDecls params locals key2) pure .uint256 - | `(term| msgSender) => + | `(term| msgSender) | `(term| Verity.msgSender) => pure .address - | `(term| msgValue) => + | `(term| msgValue) | `(term| Verity.msgValue) | `(term| blockTimestamp) + | `(term| Verity.blockTimestamp) | `(term| blockNumber) | `(term| Verity.blockNumber) + | `(term| blobbasefee) | `(term| Verity.blobbasefee) | `(term| chainid) + | `(term| Verity.chainid) => pure .uint256 + | `(term| contractAddress) | `(term| Verity.contractAddress) => + pure .address | `(term| tload $offset:term) => do requireWordLikeType offset "tload offset" (← inferPureExprType fields constDecls immutableDecls externalDecls params locals offset) pure .uint256 @@ -1870,11 +1996,9 @@ private partial def inferBindSourceType pure .uint256 | _ => throwErrorAt rhs "unsupported requireSomeUint source; expected safeAdd or safeSub" | _ => - match matchLocalFunctionApp? functions rhs with - | some (fn, argTerms) => + match ← resolveLocalFunctionApp? fields constDecls immutableDecls externalDecls functions params locals rhs with + | some (fn, _argTerms) => ensureSupportsInternalHelperSpec rhs fn - for arg in argTerms do - let _ ← inferPureExprType fields constDecls immutableDecls externalDecls params locals arg match fn.returnTy with | .tuple _ => throwErrorAt rhs @@ -1924,6 +2048,15 @@ private partial def inferTupleSourceTypes? requireWordLikeType key1 "structMembers2 key" (← inferPureExprType fields constDecls immutableDecls externalDecls params locals key1) requireWordLikeType key2 "structMembers2 key" (← inferPureExprType fields constDecls immutableDecls externalDecls params locals key2) pure (some (Array.replicate memberNames.size .uint256)) + | `(term| arrayElement $name:term $index:term) => do + requireWordLikeType index "arrayElement index" + (← inferPureExprType fields constDecls immutableDecls externalDecls params locals index) + let sourceTy ← requireDirectParamRef name "arrayElement" params + match sourceTy with + | .array (.tuple elemTys) => + let _ ← requireSupportedArrayElementTupleSourceType name "arrayElement tuple destructuring" sourceTy + pure (some elemTys.toArray) + | _ => pure none | `(term| tryExternalCall $name:term $args:term) => let extName := ← expectStringOrIdent name match stripParens args with @@ -1942,15 +2075,45 @@ private partial def inferTupleSourceTypes? -- The validation path (with real externalDecls) catches actual errors. pure none | other => - match matchLocalFunctionApp? functions other with - | some (fn, argTerms) => + match ← resolveLocalFunctionApp? fields constDecls immutableDecls externalDecls functions params locals other with + | some (fn, _argTerms) => ensureSupportsInternalHelperSpec rhs fn - for arg in argTerms do - let _ ← inferPureExprType fields constDecls immutableDecls externalDecls params locals arg match fn.returnTy with | .tuple elemTys => pure (some elemTys.toArray) | _ => pure none | none => pure none + +private partial def resolveLocalFunctionApp? + (fields : Array StorageFieldDecl) + (constDecls : Array ConstantDecl) + (immutableDecls : Array ImmutableDecl) + (externalDecls : Array ExternalDecl) + (functions : Array FunctionDecl) + (params : Array ParamDecl) + (locals : Array TypedLocal) + (stx : Term) : CommandElabM (Option (FunctionDecl × Array Term)) := do + let some (fnName, argTerms) := localFunctionAppSyntax? stx + | pure none + let candidates := functions.filter (fun fn => fn.name == fnName && fn.params.size == argTerms.size) + if candidates.isEmpty then + pure none + else + let argTypes ← argTerms.mapM (inferPureExprType fields constDecls immutableDecls externalDecls params locals) + let matchedFns := candidates.filter (fun fn => + fn.params.map (fun param => param.ty) == argTypes) + match matchedFns.toList with + | [fn] => pure (some (fn, argTerms)) + | [] => + let expected := + String.intercalate ", " + (candidates.toList.map functionSignatureKey) + let actual := + fnName ++ "(" ++ String.intercalate "," (argTypes.toList.map renderValueType) ++ ")" + throwErrorAt stx + s!"no overload of '{fnName}' matches argument types {actual}; candidates: {expected}" + | _ => + throwErrorAt stx + s!"ambiguous overload resolution for '{fnName}'" end mutual @@ -1966,7 +2129,8 @@ private partial def validateConstantBody | `(term| msgValue) => throwNonCompileTimeConstantError stx "msgValue" | `(term| blockTimestamp) => throwNonCompileTimeConstantError stx "blockTimestamp" | `(term| blockNumber) => throwNonCompileTimeConstantError stx "blockNumber" - | `(term| blobbasefee) => throwNonCompileTimeConstantError stx "blobbasefee" + | `(term| blobbasefee) | `(term| Verity.blobbasefee) => + throwNonCompileTimeConstantError stx "blobbasefee" | `(term| contractAddress) => throwNonCompileTimeConstantError stx "contractAddress" | `(term| chainid) => throwNonCompileTimeConstantError stx "chainid" | `(term| calldatasize) => throwNonCompileTimeConstantError stx "calldatasize" @@ -2070,12 +2234,44 @@ partial def translatePureExprWithTypes | `(term| false) => `(Compiler.CompilationModel.Expr.literal 0) | `(term| constructorArg $idx:num) => `(Compiler.CompilationModel.Expr.constructorArg $idx) - | `(term| msgValue) => `(Compiler.CompilationModel.Expr.msgValue) - | `(term| blockTimestamp) => `(Compiler.CompilationModel.Expr.blockTimestamp) - | `(term| blockNumber) => `(Compiler.CompilationModel.Expr.blockNumber) - | `(term| blobbasefee) => `(Compiler.CompilationModel.Expr.blobbasefee) - | `(term| contractAddress) => `(Compiler.CompilationModel.Expr.contractAddress) - | `(term| chainid) => `(Compiler.CompilationModel.Expr.chainid) + | `(term| Verity.msgSender) => + throwPureContextAccessorError stx "msgSender" + | `(term| Verity.msgValue) => + throwPureContextAccessorError stx "msgValue" + | `(term| Verity.blockTimestamp) => + throwPureContextAccessorError stx "blockTimestamp" + | `(term| Verity.blockNumber) => + throwPureContextAccessorError stx "blockNumber" + | `(term| Verity.blobbasefee) => + throwPureContextAccessorError stx "blobbasefee" + | `(term| Verity.contractAddress) => + throwPureContextAccessorError stx "contractAddress" + | `(term| Verity.chainid) => + throwPureContextAccessorError stx "chainid" + | `(term| $id:ident) => + let name := toString id.getId + if params.any (fun p => p.name == name) || isTupleComponentRef params name || localNames.contains name then + lookupVarExpr params localNames name + else if let some actualName := findContextAccessorShadowName? params localNames name then + lookupVarExpr params localNames actualName + else if matchesBareName name "calldatasize" then + `(Compiler.CompilationModel.Expr.calldatasize) + else if matchesBareName name "returndataSize" then + `(Compiler.CompilationModel.Expr.returndataSize) + else if matchesBareName name "zeroAddress" then + `(Compiler.CompilationModel.Expr.literal 0) + else if let some imm := immutableDecls.find? (fun imm => declaredNameMatches name imm.name) then + match imm.ty with + | .uint256 | .int256 | .uint8 | .bytes32 | .bool => + `(Compiler.CompilationModel.Expr.storage $(strTerm (immutableHiddenName imm))) + | .address => `(Compiler.CompilationModel.Expr.storageAddr $(strTerm (immutableHiddenName imm))) + | _ => throwErrorAt stx s!"immutable '{name}' uses unsupported type" + else if let some constant := constDecls.find? (fun constant => declaredNameMatches name constant.name) then + translateConstantExpr fields constDecls immutableDecls visitingConstants constant.name + else if let some accessor := contextAccessorBareName? name then + throwPureContextAccessorError stx accessor + else + translateConstantExpr fields constDecls immutableDecls visitingConstants name | `(term| calldatasize) => `(Compiler.CompilationModel.Expr.calldatasize) | `(term| returndataSize) => `(Compiler.CompilationModel.Expr.returndataSize) | `(term| zeroAddress) => @@ -2096,18 +2292,6 @@ partial def translatePureExprWithTypes $(← translatePureExprWithTypes fields constDecls immutableDecls params locals a visitingConstants) (Compiler.CompilationModel.Expr.literal 1) (Compiler.CompilationModel.Expr.literal 0)) - | `(term| $id:ident) => - let name := toString id.getId - if params.any (fun p => p.name == name) || isTupleComponentRef params name || localNames.contains name then - lookupVarExpr params localNames name - else if let some imm := immutableDecls.find? (fun imm => imm.name == name) then - match imm.ty with - | .uint256 | .int256 | .uint8 | .bytes32 | .bool => - `(Compiler.CompilationModel.Expr.storage $(strTerm (immutableHiddenName imm))) - | .address => `(Compiler.CompilationModel.Expr.storageAddr $(strTerm (immutableHiddenName imm))) - | _ => throwErrorAt stx s!"immutable '{name}' uses unsupported type" - else - translateConstantExpr fields constDecls immutableDecls visitingConstants name | `(term| $n:num) => `(Compiler.CompilationModel.Expr.literal $n) | `(term| add $a $b) => `(Compiler.CompilationModel.Expr.add $(← translatePureExprWithTypes fields constDecls immutableDecls params locals a visitingConstants) $(← translatePureExprWithTypes fields constDecls immutableDecls params locals b visitingConstants)) | `(term| sub $a $b) => `(Compiler.CompilationModel.Expr.sub $(← translatePureExprWithTypes fields constDecls immutableDecls params locals a visitingConstants) $(← translatePureExprWithTypes fields constDecls immutableDecls params locals b visitingConstants)) @@ -2407,6 +2591,152 @@ private def validateSingleResultEcmModuleTerm throwErrorAt moduleTerm s!"ecmCall must elaborate to an ECM module binding exactly ['{boundVarName}'], but '{mod.name}' binds {repr mod.resultVars}" +private def arrayElementTupleElemExprs? + (fields : Array StorageFieldDecl) + (constDecls : Array ConstantDecl) + (immutableDecls : Array ImmutableDecl) + (params : Array ParamDecl) + (locals : Array TypedLocal) + (rhs : Term) : CommandElabM (Option (Array Term)) := do + match stripParens rhs with + | `(term| arrayElement $name:term $index:term) => + let paramName := ← expectStringOrIdent name + match params.find? (fun p => p.name == paramName) with + | some { ty := .array (.tuple elemTys), .. } => + let elementWords ← + match staticAbiWordCount? (.tuple elemTys) with + | some n => pure n + | none => + throwErrorAt rhs + "arrayElement tuple destructuring requires a static ABI-word tuple element type" + let indexExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals index + let mut offset := 0 + let mut exprs : Array Term := #[] + for elemTy in elemTys do + let elemWords ← + match staticAbiWordCount? elemTy with + | some n => pure n + | none => + throwErrorAt rhs + "arrayElement tuple destructuring requires static ABI-word tuple members" + if elemWords != 1 then + throwErrorAt rhs + "arrayElement tuple destructuring currently supports top-level single-word tuple members" + exprs := exprs.push (← `(Compiler.CompilationModel.Expr.arrayElementWord + $(strTerm paramName) + $indexExpr + $(natTerm elementWords) + $(natTerm offset))) + offset := offset + elemWords + pure (some exprs) + | _ => pure none + | _ => pure none + +private def arrayElementTupleDestructureStmts? + (fields : Array StorageFieldDecl) + (constDecls : Array ConstantDecl) + (immutableDecls : Array ImmutableDecl) + (params : Array ParamDecl) + (locals : Array TypedLocal) + (mutableLocals : Array String) + (rhs : Term) + (names : Array (Option String)) : CommandElabM (Option (Array Term × TypedLocal)) := do + match stripParens rhs with + | `(term| arrayElement $name:term $index:term) => + let paramName := ← expectStringOrIdent name + match params.find? (fun p => p.name == paramName) with + | some { ty := .array (.tuple elemTys), .. } => + let elementWords ← + match staticAbiWordCount? (.tuple elemTys) with + | some n => pure n + | none => + throwErrorAt rhs + "arrayElement tuple destructuring requires a static ABI-word tuple element type" + if names.size != elemTys.length then + throwErrorAt rhs + s!"tuple destructuring binds {names.size} names, but the source provides {elemTys.length} values" + let syntheticUsed := mutableLocals ++ names.filterMap id + let indexName := freshSyntheticLocalName "arrayElement_index" params locals syntheticUsed + let indexExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals index + let indexStmt ← + `(Compiler.CompilationModel.Stmt.letVar $(strTerm indexName) $indexExpr) + let indexLocal ← + `(Compiler.CompilationModel.Expr.localVar $(strTerm indexName)) + let mut offset := 0 + let mut valueExprs : Array Term := #[] + for elemTy in elemTys do + let elemWords ← + match staticAbiWordCount? elemTy with + | some n => pure n + | none => + throwErrorAt rhs + "arrayElement tuple destructuring requires static ABI-word tuple members" + if elemWords != 1 then + throwErrorAt rhs + "arrayElement tuple destructuring currently supports top-level single-word tuple members" + valueExprs := valueExprs.push (← `(Compiler.CompilationModel.Expr.arrayElementWord + $(strTerm paramName) + $indexLocal + $(natTerm elementWords) + $(natTerm offset))) + offset := offset + elemWords + let boundPairs := (names.zip valueExprs).filterMap fun (name?, valueExpr) => + name?.map (fun name => (name, valueExpr)) + let boundStmts ← boundPairs.mapM fun (name, valueExpr) => + `(Compiler.CompilationModel.Stmt.letVar $(strTerm name) $valueExpr) + pure (some (#[indexStmt] ++ boundStmts, (indexName, .uint256))) + | _ => pure none + | _ => pure none + +private def arrayElementTupleReturnStmts? + (fields : Array StorageFieldDecl) + (constDecls : Array ConstantDecl) + (immutableDecls : Array ImmutableDecl) + (params : Array ParamDecl) + (locals : Array TypedLocal) + (mutableLocals : Array String) + (rhs : Term) : CommandElabM (Option (Array Term × TypedLocal)) := do + match stripParens rhs with + | `(term| arrayElement $name:term $index:term) => + let paramName := ← expectStringOrIdent name + match params.find? (fun p => p.name == paramName) with + | some { ty := .array (.tuple elemTys), .. } => + let elementWords ← + match staticAbiWordCount? (.tuple elemTys) with + | some n => pure n + | none => + throwErrorAt rhs + "arrayElement tuple return requires a static ABI-word tuple element type" + let indexName := freshSyntheticLocalName "arrayElement_index" params locals mutableLocals + let indexExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals index + let indexStmt ← + `(Compiler.CompilationModel.Stmt.letVar $(strTerm indexName) $indexExpr) + let indexLocal ← + `(Compiler.CompilationModel.Expr.localVar $(strTerm indexName)) + let mut offset := 0 + let mut valueExprs : Array Term := #[] + for elemTy in elemTys do + let elemWords ← + match staticAbiWordCount? elemTy with + | some n => pure n + | none => + throwErrorAt rhs + "arrayElement tuple return requires static ABI-word tuple members" + if elemWords != 1 then + throwErrorAt rhs + "arrayElement tuple return currently supports top-level single-word tuple members" + valueExprs := valueExprs.push (← `(Compiler.CompilationModel.Expr.arrayElementWord + $(strTerm paramName) + $indexLocal + $(natTerm elementWords) + $(natTerm offset))) + offset := offset + elemWords + let returnStmt ← + `(Compiler.CompilationModel.Stmt.returnValues [ $[$valueExprs],* ]) + pure (some (#[indexStmt, returnStmt], (indexName, .uint256))) + | _ => pure none + | _ => pure none + private def tupleLiteralOrStructValueExprs? (fields : Array StorageFieldDecl) (constDecls : Array ConstantDecl) @@ -2442,7 +2772,9 @@ private def tupleLiteralOrStructValueExprs? | some elems => pure (some (← elems.mapM (translatePureExprWithTypes fields constDecls immutableDecls params locals))) | none => - structValueExprs? + match ← arrayElementTupleElemExprs? fields constDecls immutableDecls params locals rhs with + | some exprs => pure (some exprs) + | none => structValueExprs? private def tupleValueExprs (fields : Array StorageFieldDecl) @@ -2482,6 +2814,7 @@ private def tupleInternalCallAssignStmt? (fields : Array StorageFieldDecl) (constDecls : Array ConstantDecl) (immutableDecls : Array ImmutableDecl) + (externalDecls : Array ExternalDecl) (functions : Array FunctionDecl) (params : Array ParamDecl) (locals : Array TypedLocal) @@ -2499,14 +2832,14 @@ private def tupleInternalCallAssignStmt? (initialUsedNames, []) let targetNames := targetNamesRev.reverse let resultNameTerms := targetNames.toArray.map strTerm - match matchLocalFunctionApp? functions rhs with + match ← resolveLocalFunctionApp? fields constDecls immutableDecls externalDecls functions params locals rhs with | some (fn, argTerms) => ensureSupportsInternalHelperSpec rhs fn let argExprs ← argTerms.mapM (translatePureExprWithTypes fields constDecls immutableDecls params locals) pure (some (← `(Compiler.CompilationModel.Stmt.internalCallAssign [ $[$resultNameTerms],* ] - $(strTerm (internalHelperSpecName functions fn.name)) + $(strTerm (internalHelperSpecNameFor fn)) [ $[$argExprs],* ]))) | none => pure none @@ -2586,6 +2919,7 @@ private def translateBindSource (fields : Array StorageFieldDecl) (constDecls : Array ConstantDecl) (immutableDecls : Array ImmutableDecl) + (externalDecls : Array ExternalDecl) (functions : Array FunctionDecl) (params : Array ParamDecl) (locals : Array TypedLocal) @@ -2761,19 +3095,29 @@ private def translateBindSource $(← translatePureExprWithTypes fields constDecls immutableDecls params locals key1) $(← translatePureExprWithTypes fields constDecls immutableDecls params locals key2) $(strTerm memberName)) - | `(term| msgSender) => `(Compiler.CompilationModel.Expr.caller) - | `(term| msgValue) => `(Compiler.CompilationModel.Expr.msgValue) + | `(term| msgSender) | `(term| Verity.msgSender) => `(Compiler.CompilationModel.Expr.caller) + | `(term| msgValue) | `(term| Verity.msgValue) => `(Compiler.CompilationModel.Expr.msgValue) + | `(term| blockTimestamp) | `(term| Verity.blockTimestamp) => + `(Compiler.CompilationModel.Expr.blockTimestamp) + | `(term| blockNumber) | `(term| Verity.blockNumber) => + `(Compiler.CompilationModel.Expr.blockNumber) + | `(term| blobbasefee) | `(term| Verity.blobbasefee) => + `(Compiler.CompilationModel.Expr.blobbasefee) + | `(term| contractAddress) | `(term| Verity.contractAddress) => + `(Compiler.CompilationModel.Expr.contractAddress) + | `(term| chainid) | `(term| Verity.chainid) => + `(Compiler.CompilationModel.Expr.chainid) | `(term| tload $offset:term) => `(Compiler.CompilationModel.Expr.tload $(← translatePureExprWithTypes fields constDecls immutableDecls params locals offset)) | _ => - match matchLocalFunctionApp? functions rhs with + match ← resolveLocalFunctionApp? fields constDecls immutableDecls externalDecls functions params locals rhs with | some (fn, argTerms) => ensureSupportsInternalHelperSpec rhs fn let argExprs ← argTerms.mapM (translatePureExprWithTypes fields constDecls immutableDecls params locals) `(Compiler.CompilationModel.Expr.internalCall - $(strTerm (internalHelperSpecName functions fn.name)) + $(strTerm (internalHelperSpecNameFor fn)) [ $[$argExprs],* ]) | none => throwErrorAt rhs @@ -3010,6 +3354,9 @@ private partial def validateEffectStmtExprTypes | `(term| require $value:term $_msg) => let _ ← inferPureExprType fields constDecls immutableDecls externalDecls params locals value pure () + | `(term| setPackedStorage $_field:ident $_wordOffset:num $value:term) => + let _ ← inferPureExprType fields constDecls immutableDecls externalDecls params locals value + pure () | `(term| pushStorageArray $_field:ident $value:term) => do let _ ← inferPureExprType fields constDecls immutableDecls externalDecls params locals value pure () @@ -3070,9 +3417,7 @@ private partial def validateEffectStmtExprTypes | `(term| returnStorageWords $name:term) => do let ty ← requireDirectParamRef name "returnStorageWords" params requireSupportedReturnStorageWordsType name "returnStorageWords" ty - | `(term| internalCall $_fnName:term $args:term) - | `(term| internalCallAssign $_names:term $_fnName:term $args:term) - | `(term| externalCallBind $_names:term $_fnName:term $args:term) + | `(term| externalCallBind $_names:term $_fnName:term $args:term) | `(term| tryExternalCallBind $_successVar:term $_names:term $_fnName:term $args:term) => match stripParens args with | `(term| [ $[$xs],* ]) => @@ -3082,7 +3427,7 @@ private partial def validateEffectStmtExprTypes | `(term| revertReturndata) => pure () | _ => - match matchLocalFunctionApp? functions stx with + match ← resolveLocalFunctionApp? fields constDecls immutableDecls externalDecls functions params locals stx with | some (fn, argTerms) => ensureSupportsInternalHelperSpec stx fn if fn.returnTy != .unit then @@ -3240,6 +3585,7 @@ private def translateEffectStmt (fields : Array StorageFieldDecl) (constDecls : Array ConstantDecl) (immutableDecls : Array ImmutableDecl) + (externalDecls : Array ExternalDecl) (functions : Array FunctionDecl) (params : Array ParamDecl) (locals : Array TypedLocal) @@ -3321,6 +3667,19 @@ private def translateEffectStmt throwErrorAt stx s!"field '{f.name}' is a storage dynamic array; use pushStorageArray/popStorageArray/setStorageArrayElement" | _ => throwErrorAt stx s!"field '{f.name}' is not Address; use setStorage" + | `(term| setPackedStorage $field:ident $wordOffset:num $value:term) => + let f ← lookupStorageField fields (toString field.getId) + match f.ty with + | .scalar _ => + `(Compiler.CompilationModel.Stmt.setStorageWord + $(strTerm f.name) + $(natTerm (← natFromSyntax wordOffset)) + $(← translatePureExprWithTypes fields constDecls immutableDecls params locals value)) + | .dynamicArray _ => + throwErrorAt stx s!"field '{f.name}' is a storage dynamic array; setPackedStorage requires a scalar root slot" + | .mappingAddressToUint256 | .mappingUintToUint256 | .mapping2AddressToAddressToUint256 + | .mappingChain _ | .mappingStruct _ _ | .mappingStruct2 _ _ _ => + throwErrorAt stx s!"field '{f.name}' is a mapping; setPackedStorage requires a scalar root slot" | `(term| setMapping $field:ident $key:term $value:term) => let f ← lookupStorageField fields (toString field.getId) match f.ty with @@ -3517,19 +3876,6 @@ private def translateEffectStmt [ $[$topicExprs],* ] $(← translatePureExprWithTypes fields constDecls immutableDecls params locals dataOffset) $(← translatePureExprWithTypes fields constDecls immutableDecls params locals dataSize)) - | `(term| internalCall $fnName:term $args:term) => - let targetFn := ← expectStringOrIdent fnName - let argExprs ← expectExprList fields constDecls immutableDecls params locals args - `(Compiler.CompilationModel.Stmt.internalCall $(strTerm targetFn) [ $[$argExprs],* ]) - | `(term| internalCallAssign $names:term $fnName:term $args:term) => - let resultNames := ← expectStringList names - let resultNameTerms := resultNames.map strTerm - let targetFn := ← expectStringOrIdent fnName - let argExprs ← expectExprList fields constDecls immutableDecls params locals args - `(Compiler.CompilationModel.Stmt.internalCallAssign - [ $[$resultNameTerms],* ] - $(strTerm targetFn) - [ $[$argExprs],* ]) | `(term| externalCallBind $names:term $fnName:term $args:term) => let resultNames := ← expectStringList names let resultNameTerms := resultNames.map strTerm @@ -3559,7 +3905,7 @@ private def translateEffectStmt $(strTerm memberName) $(← translatePureExprWithTypes fields constDecls immutableDecls params locals value)) | _ => - match matchLocalFunctionApp? functions stx with + match ← resolveLocalFunctionApp? fields constDecls immutableDecls externalDecls functions params locals stx with | some (fn, argTerms) => ensureSupportsInternalHelperSpec stx fn if fn.returnTy != .unit then @@ -3568,7 +3914,7 @@ private def translateEffectStmt let argExprs ← argTerms.mapM (translatePureExprWithTypes fields constDecls immutableDecls params locals) `(Compiler.CompilationModel.Stmt.internalCall - $(strTerm (internalHelperSpecName functions fn.name)) + $(strTerm (internalHelperSpecNameFor fn)) [ $[$argExprs],* ]) | none => throwErrorAt stx "unsupported statement in do block" @@ -3648,7 +3994,7 @@ private partial def translateDoElem pure (some (stmts, locals ++ typedPairs, mutableLocals)) | none => throwErrorAt rhs "unable to infer tuple local types" | none => - match (← tupleInternalCallAssignStmt? fields constDecls immutableDecls functions params locals rhs names) with + match (← tupleInternalCallAssignStmt? fields constDecls immutableDecls externalDecls functions params locals rhs names) with | some stmt => let valueTys ← inferTupleSourceTypes? fields constDecls immutableDecls externalDecls functions params locals rhs match valueTys with @@ -3663,48 +4009,57 @@ private partial def translateDoElem pure (some (#[(stmt)], locals ++ typedPairs, mutableLocals)) | none => throwErrorAt rhs "tuple destructuring currently requires a tuple literal, tuple-typed parameter, structMembers/structMembers2 source, internal helper call, or tryExternalCall" | _ => - match (← tupleLiteralOrStructValueExprs? fields constDecls immutableDecls params locals rhs) with - | some valueExprs => - if names.size != valueExprs.size then - throwErrorAt patDecl s!"tuple destructuring binds {names.size} names, but the source provides {valueExprs.size} values" - let boundPairs := (names.zip valueExprs).filterMap fun (name?, valueExpr) => - name?.map (fun name => (name, valueExpr)) - let stmts ← boundPairs.mapM fun (name, valueExpr) => - `(Compiler.CompilationModel.Stmt.letVar $(strTerm name) $valueExpr) + match (← arrayElementTupleDestructureStmts? fields constDecls immutableDecls params locals mutableLocals rhs names) with + | some (stmts, syntheticLocal) => let valueTys ← inferTupleSourceTypes? fields constDecls immutableDecls externalDecls functions params locals rhs match valueTys with | some tys => let typedPairs := (names.zip tys).filterMap fun (name?, ty) => name?.map (fun name => (name, ty)) - pure (some (stmts, locals ++ typedPairs, mutableLocals)) + pure (some (stmts, locals.push syntheticLocal ++ typedPairs, mutableLocals)) | none => throwErrorAt rhs "unable to infer tuple local types" | none => - match (← tupleInternalCallAssignStmt? fields constDecls immutableDecls functions params locals rhs names) with - | some stmt => - let valueTys ← inferTupleSourceTypes? fields constDecls immutableDecls externalDecls functions params locals rhs - match valueTys with - | some tys => - let typedPairs := (names.zip tys).filterMap fun (name?, ty) => name?.map (fun name => (name, ty)) - pure (some (#[(stmt)], locals ++ typedPairs, mutableLocals)) - | none => throwErrorAt rhs "unable to infer tuple local types" - | none => - match (← tryExternalCallBindStmt? fields constDecls immutableDecls externalDecls params locals rhs names) with - | some (stmt, tys) => - let typedPairs := (names.zip tys).filterMap fun (name?, ty) => name?.map (fun name => (name, ty)) - pure (some (#[(stmt)], locals ++ typedPairs, mutableLocals)) + match (← tupleLiteralOrStructValueExprs? fields constDecls immutableDecls params locals rhs) with + | some valueExprs => + if names.size != valueExprs.size then + throwErrorAt patDecl s!"tuple destructuring binds {names.size} names, but the source provides {valueExprs.size} values" + let boundPairs := (names.zip valueExprs).filterMap fun (name?, valueExpr) => + name?.map (fun name => (name, valueExpr)) + let stmts ← boundPairs.mapM fun (name, valueExpr) => + `(Compiler.CompilationModel.Stmt.letVar $(strTerm name) $valueExpr) + let valueTys ← inferTupleSourceTypes? fields constDecls immutableDecls externalDecls functions params locals rhs + match valueTys with + | some tys => + let typedPairs := (names.zip tys).filterMap fun (name?, ty) => name?.map (fun name => (name, ty)) + pure (some (stmts, locals ++ typedPairs, mutableLocals)) + | none => throwErrorAt rhs "unable to infer tuple local types" | none => - let valueExprs ← tupleValueExprs fields constDecls immutableDecls params locals rhs - if names.size != valueExprs.size then - throwErrorAt patDecl s!"tuple destructuring binds {names.size} names, but the source provides {valueExprs.size} values" - let boundPairs := (names.zip valueExprs).filterMap fun (name?, valueExpr) => - name?.map (fun name => (name, valueExpr)) - let stmts ← boundPairs.mapM fun (name, valueExpr) => - `(Compiler.CompilationModel.Stmt.letVar $(strTerm name) $valueExpr) - let valueTys ← inferTupleSourceTypes? fields constDecls immutableDecls externalDecls functions params locals rhs - match valueTys with - | some tys => - let typedPairs := (names.zip tys).filterMap fun (name?, ty) => name?.map (fun name => (name, ty)) - pure (some (stmts, locals ++ typedPairs, mutableLocals)) - | none => throwErrorAt rhs "unable to infer tuple local types" + match (← tupleInternalCallAssignStmt? fields constDecls immutableDecls externalDecls functions params locals rhs names) with + | some stmt => + let valueTys ← inferTupleSourceTypes? fields constDecls immutableDecls externalDecls functions params locals rhs + match valueTys with + | some tys => + let typedPairs := (names.zip tys).filterMap fun (name?, ty) => name?.map (fun name => (name, ty)) + pure (some (#[(stmt)], locals ++ typedPairs, mutableLocals)) + | none => throwErrorAt rhs "unable to infer tuple local types" + | none => + match (← tryExternalCallBindStmt? fields constDecls immutableDecls externalDecls params locals rhs names) with + | some (stmt, tys) => + let typedPairs := (names.zip tys).filterMap fun (name?, ty) => name?.map (fun name => (name, ty)) + pure (some (#[(stmt)], locals ++ typedPairs, mutableLocals)) + | none => + let valueExprs ← tupleValueExprs fields constDecls immutableDecls params locals rhs + if names.size != valueExprs.size then + throwErrorAt patDecl s!"tuple destructuring binds {names.size} names, but the source provides {valueExprs.size} values" + let boundPairs := (names.zip valueExprs).filterMap fun (name?, valueExpr) => + name?.map (fun name => (name, valueExpr)) + let stmts ← boundPairs.mapM fun (name, valueExpr) => + `(Compiler.CompilationModel.Stmt.letVar $(strTerm name) $valueExpr) + let valueTys ← inferTupleSourceTypes? fields constDecls immutableDecls externalDecls functions params locals rhs + match valueTys with + | some tys => + let typedPairs := (names.zip tys).filterMap fun (name?, ty) => name?.map (fun name => (name, ty)) + pure (some (stmts, locals ++ typedPairs, mutableLocals)) + | none => throwErrorAt rhs "unable to infer tuple local types" | none => pure none else if stx.getKind == `Lean.Parser.Term.doLetArrow then let patDecl := stx[2] @@ -3712,7 +4067,7 @@ private partial def translateDoElem | some names => ensureFreshLocalNames localNames names stx let rhs : Term := ⟨patDecl[2][0]⟩ - match (← tupleInternalCallAssignStmt? fields constDecls immutableDecls functions params locals rhs names) with + match (← tupleInternalCallAssignStmt? fields constDecls immutableDecls externalDecls functions params locals rhs names) with | some stmt => let valueTys ← inferTupleSourceTypes? fields constDecls immutableDecls externalDecls functions params locals rhs match valueTys with @@ -3800,7 +4155,7 @@ private partial def translateDoElem | some stmt => pure (#[(stmt)], locals.push (varName, .uint256), mutableLocals) | none => - let rhsExpr ← translateBindSource fields constDecls immutableDecls functions params locals rhs + let rhsExpr ← translateBindSource fields constDecls immutableDecls externalDecls functions params locals rhs let ty ← inferBindSourceType fields constDecls immutableDecls externalDecls functions params locals rhs pure (#[(← `(Compiler.CompilationModel.Stmt.letVar $(strTerm varName) $rhsExpr))], @@ -3818,11 +4173,15 @@ private partial def translateDoElem locals, mutableLocals) | `(doElem| return $value:term) => - match (← tupleReturnValueExprs? fields constDecls immutableDecls params locals value) with - | some valueExprs => - pure (#[(← `(Compiler.CompilationModel.Stmt.returnValues [ $[$valueExprs],* ]))], locals, mutableLocals) + match (← arrayElementTupleReturnStmts? fields constDecls immutableDecls params locals mutableLocals value) with + | some (stmts, syntheticLocal) => + pure (stmts, locals.push syntheticLocal, mutableLocals) | none => - pure (#[(← `(Compiler.CompilationModel.Stmt.return $(← translatePureExprWithTypes fields constDecls immutableDecls params locals value)))], locals, mutableLocals) + match (← tupleReturnValueExprs? fields constDecls immutableDecls params locals value) with + | some valueExprs => + pure (#[(← `(Compiler.CompilationModel.Stmt.returnValues [ $[$valueExprs],* ]))], locals, mutableLocals) + | none => + pure (#[(← `(Compiler.CompilationModel.Stmt.return $(← translatePureExprWithTypes fields constDecls immutableDecls params locals value)))], locals, mutableLocals) | `(doElem| pure ()) => pure (#[], locals, mutableLocals) | `(doElem| if $cond:term then $thenBranch:doSeq else $elseBranch:doSeq) => @@ -3907,7 +4266,7 @@ private partial def translateDoElem locals, mutableLocals) | `(doElem| $stmt:term) => - pure (#[(← translateEffectStmt fields constDecls immutableDecls functions params locals stmt)], locals, mutableLocals) + pure (#[(← translateEffectStmt fields constDecls immutableDecls externalDecls functions params locals stmt)], locals, mutableLocals) | _ => throwErrorAt elem "unsupported do element" end @@ -4055,6 +4414,110 @@ private def mkStorageDefCommand (field : StorageFieldDecl) : CommandElabM Cmd := let fid := field.ident `(command| def $fid : Verity.StorageSlot $storageTy := ⟨$(natTerm field.slotNum)⟩) +private def packedOptionTerm (packed : Option (Nat × Nat)) : CommandElabM Term := do + match packed with + | none => `(none) + | some (offset, width) => `(some ($(natTerm offset), $(natTerm width))) + +private def mkStructMemberReadBranches + (fields : Array StorageFieldDecl) + (nested : Bool) + (fallbackTerm : Term) : CommandElabM Term := do + let mut acc := fallbackTerm + for field in fields.reverse do + match nested, field.ty with + | false, .mappingStruct _ members => + for member in members.reverse do + let packedTerm ← packedOptionTerm member.packed + acc ← `(if field == $(strTerm field.name) && member == $(strTerm member.name) then + _root_.Contracts.structMemberAt $(natTerm field.slotNum) $(natTerm member.wordOffset) + $packedTerm key + else + $acc) + | true, .mappingStruct2 _ _ members => + for member in members.reverse do + let packedTerm ← packedOptionTerm member.packed + acc ← `(if field == $(strTerm field.name) && member == $(strTerm member.name) then + _root_.Contracts.structMember2At $(natTerm field.slotNum) $(natTerm member.wordOffset) + $packedTerm key1 key2 + else + $acc) + | _, _ => pure () + pure acc + +private def mkStructMemberWriteBranches + (fields : Array StorageFieldDecl) + (nested : Bool) + (fallbackTerm : Term) : CommandElabM Term := do + let mut acc := fallbackTerm + for field in fields.reverse do + match nested, field.ty with + | false, .mappingStruct _ members => + for member in members.reverse do + let packedTerm ← packedOptionTerm member.packed + acc ← `(if field == $(strTerm field.name) && member == $(strTerm member.name) then + _root_.Contracts.setStructMemberAt $(natTerm field.slotNum) $(natTerm member.wordOffset) + $packedTerm key value + else + $acc) + | true, .mappingStruct2 _ _ members => + for member in members.reverse do + let packedTerm ← packedOptionTerm member.packed + acc ← `(if field == $(strTerm field.name) && member == $(strTerm member.name) then + _root_.Contracts.setStructMember2At $(natTerm field.slotNum) $(natTerm member.wordOffset) + $packedTerm key1 key2 value + else + $acc) + | _, _ => pure () + pure acc + +private def hasStructMapping (fields : Array StorageFieldDecl) : Bool := + fields.any fun field => + match field.ty with + | .mappingStruct _ _ => true + | _ => false + +private def hasStructMapping2 (fields : Array StorageFieldDecl) : Bool := + fields.any fun field => + match field.ty with + | .mappingStruct2 _ _ _ => true + | _ => false + +def mkExecutableStructMappingCommandsPublic (fields : Array StorageFieldDecl) : + CommandElabM (Array Cmd) := do + let mut cmds : Array Cmd := #[] + if hasStructMapping fields then + let readFallback : Term ← `(pure default) + let writeFallback : Term ← `(pure ()) + let readBranches ← mkStructMemberReadBranches fields false readFallback + let writeBranches ← mkStructMemberWriteBranches fields false writeFallback + cmds := cmds.push (← `(command| + def structMember {κ α : Type} [Inhabited α] [_root_.Contracts.StorageKey κ] + [_root_.Contracts.StorageWord α] (field : String) (key : κ) (member : String) : + Verity.Contract α := + $readBranches)) + cmds := cmds.push (← `(command| + def setStructMember {κ α : Type} [_root_.Contracts.StorageKey κ] + [_root_.Contracts.StorageWord α] (field : String) (key : κ) (member : String) + (value : α) : Verity.Contract Unit := + $writeBranches)) + if hasStructMapping2 fields then + let readFallback : Term ← `(pure default) + let writeFallback : Term ← `(pure ()) + let readBranches ← mkStructMemberReadBranches fields true readFallback + let writeBranches ← mkStructMemberWriteBranches fields true writeFallback + cmds := cmds.push (← `(command| + def structMember2 {κ₁ κ₂ α : Type} [Inhabited α] [_root_.Contracts.StorageKey κ₁] + [_root_.Contracts.StorageKey κ₂] [_root_.Contracts.StorageWord α] (field : String) + (key1 : κ₁) (key2 : κ₂) (member : String) : Verity.Contract α := + $readBranches)) + cmds := cmds.push (← `(command| + def setStructMember2 {κ₁ κ₂ α : Type} [_root_.Contracts.StorageKey κ₁] + [_root_.Contracts.StorageKey κ₂] [_root_.Contracts.StorageWord α] (field : String) + (key1 : κ₁) (key2 : κ₂) (member : String) (value : α) : Verity.Contract Unit := + $writeBranches)) + pure cmds + private def mkModelFieldTerm (field : StorageFieldDecl) : CommandElabM Term := do `(Compiler.CompilationModel.Field.mk $(strTerm field.name) @@ -4174,7 +4637,7 @@ private def mkSpecCommand let returnTypeTerm ← modelReturnTypeTerm fn.returnTy let returnsTerm ← modelReturnsTerm fn.returnTy pure <| some (← `( ({ - name := $(strTerm (internalHelperSpecName functions fn.name)) + name := $(strTerm (internalHelperSpecNameFor fn)) params := $modelParams returnType := $returnTypeTerm «returns» := $returnsTerm @@ -4281,7 +4744,7 @@ private def mkFindIdxParamSimpCommands cmds := cmds ++ ctorCmds | none => pure () for fn in functions do - let fnCmds ← mkFindIdxParamSimpCommandsForScope contractName fn.name fn.params + let fnCmds ← mkFindIdxParamSimpCommandsForScope contractName (toString fn.ident.getId) fn.params cmds := cmds ++ fnCmds pure cmds @@ -4391,9 +4854,11 @@ def parseContractSyntax , parsedImmutables , parsedExternals , (← ctor.mapM (parseConstructor parsedNewtypes parsedAdts)) - , (← entrypoints.mapM parseSpecialEntrypoint) ++ (← functions.mapM (parseFunction parsedNewtypes parsedAdts)) - , namespaceOpt - ) + , assignOverloadInternalIdents + ((← entrypoints.mapM parseSpecialEntrypoint) ++ + (← functions.mapM (parseFunction parsedNewtypes parsedAdts))) + , namespaceOpt + ) | _ => throwErrorAt stx "invalid verity_contract declaration" private def mkConstantDefCommand (constant : ConstantDecl) : CommandElabM Cmd := do @@ -4423,13 +4888,20 @@ def validateConstantDeclsPublic (constDecls : Array ConstantDecl) : CommandElabM def validateGeneratedDefNamesPublic (fields : Array StorageFieldDecl) (constDecls : Array ConstantDecl) + (immutableDecls : Array ImmutableDecl) (functions : Array FunctionDecl) : CommandElabM Unit := do let reservedGeneratedNames : Array String := #["spec", "storageNamespace"] let mut generatedHelperNames : Array String := reservedGeneratedNames + if hasStructMapping fields then + generatedHelperNames := generatedHelperNames.push "structMember" + generatedHelperNames := generatedHelperNames.push "setStructMember" + if hasStructMapping2 fields then + generatedHelperNames := generatedHelperNames.push "structMember2" + generatedHelperNames := generatedHelperNames.push "setStructMember2" let mut storageNames : Array String := #[] for field in fields do - if reservedGeneratedNames.contains field.name then + if generatedHelperNames.contains field.name then throwErrorAt field.ident s!"storage field '{field.name}' conflicts with reserved generated declaration '{field.name}'" if storageNames.contains field.name then @@ -4438,7 +4910,7 @@ def validateGeneratedDefNamesPublic let mut constantNames : Array String := #[] for constant in constDecls do - if reservedGeneratedNames.contains constant.name then + if generatedHelperNames.contains constant.name then throwErrorAt constant.ident s!"constant '{constant.name}' conflicts with reserved generated declaration '{constant.name}'" if storageNames.contains constant.name then @@ -4449,8 +4921,20 @@ def validateGeneratedDefNamesPublic s!"duplicate constant declaration '{constant.name}'" constantNames := constantNames.push constant.name + let mut immutableNames : Array String := #[] + for imm in immutableDecls do + if generatedHelperNames.contains imm.name then + throwErrorAt imm.ident + s!"immutable '{imm.name}' conflicts with reserved generated declaration '{imm.name}'" + immutableNames := immutableNames.push imm.name + let mut functionNames : Array String := #[] + let mut functionSignatures : Array String := #[] + let mut functionAbiSignatures : Array String := #[] for fn in functions do + let generatedFnName := toString fn.ident.getId + let signature := functionSignatureKey fn + let abiSignature := functionAbiSignatureKey fn if generatedHelperNames.contains fn.name then throwErrorAt fn.ident s!"function '{fn.name}' conflicts with reserved generated declaration '{fn.name}'" @@ -4460,26 +4944,49 @@ def validateGeneratedDefNamesPublic if constantNames.contains fn.name then throwErrorAt fn.ident s!"function '{fn.name}' conflicts with a contract constant of the same name" - if functionNames.contains fn.name then + if immutableNames.contains fn.name then + throwErrorAt fn.ident + s!"function '{fn.name}' conflicts with an immutable of the same name" + if storageNames.contains generatedFnName then + throwErrorAt fn.ident + s!"function '{fn.name}' generates internal declaration '{generatedFnName}' that conflicts with a storage field of the same name" + if constantNames.contains generatedFnName then throwErrorAt fn.ident - s!"duplicate function declaration '{fn.name}'" - functionNames := functionNames.push fn.name + s!"function '{fn.name}' generates internal declaration '{generatedFnName}' that conflicts with a contract constant of the same name" + if immutableNames.contains generatedFnName then + throwErrorAt fn.ident + s!"function '{fn.name}' generates internal declaration '{generatedFnName}' that conflicts with an immutable of the same name" + if generatedHelperNames.contains generatedFnName then + throwErrorAt fn.ident + s!"function '{fn.name}' generates internal declaration '{generatedFnName}' that conflicts with reserved generated declaration '{generatedFnName}'" + if functionSignatures.contains signature then + throwErrorAt fn.ident + s!"duplicate function declaration '{signature}'" + if functionAbiSignatures.contains abiSignature then + throwErrorAt fn.ident + s!"duplicate function ABI signature '{abiSignature}' after ABI erasure" + if functionNames.contains generatedFnName then + throwErrorAt fn.ident + s!"function '{fn.name}' generates duplicate internal declaration '{generatedFnName}'" + functionNames := functionNames.push generatedFnName + functionSignatures := functionSignatures.push signature + functionAbiSignatures := functionAbiSignatures.push abiSignature let helperNames := - #[ s!"{fn.name}_modelBody" - , s!"{fn.name}_model" - , s!"{fn.name}_bridge" - , s!"{fn.name}_semantic_preservation" - , s!"{fn.name}_is_view" - , s!"{fn.name}_no_calls" - , s!"{fn.name}_modifies" - , s!"{fn.name}_frame" - , s!"{fn.name}_frame_rfl" - , s!"{fn.name}_effects" - , s!"{fn.name}_cei_compliant" - , s!"{fn.name}_nonreentrant" - , s!"{fn.name}_cei_safe" - , s!"{fn.name}_requires_role" + #[ s!"{generatedFnName}_modelBody" + , s!"{generatedFnName}_model" + , s!"{generatedFnName}_bridge" + , s!"{generatedFnName}_semantic_preservation" + , s!"{generatedFnName}_is_view" + , s!"{generatedFnName}_no_calls" + , s!"{generatedFnName}_modifies" + , s!"{generatedFnName}_frame" + , s!"{generatedFnName}_frame_rfl" + , s!"{generatedFnName}_effects" + , s!"{generatedFnName}_cei_compliant" + , s!"{generatedFnName}_nonreentrant" + , s!"{generatedFnName}_cei_safe" + , s!"{generatedFnName}_requires_role" ] for helperName in helperNames do if storageNames.contains helperName then @@ -4488,6 +4995,9 @@ def validateGeneratedDefNamesPublic if constantNames.contains helperName then throwErrorAt fn.ident s!"function '{fn.name}' generates helper '{helperName}' that conflicts with a contract constant of the same name" + if immutableNames.contains helperName then + throwErrorAt fn.ident + s!"function '{fn.name}' generates helper '{helperName}' that conflicts with an immutable of the same name" if functionNames.contains helperName then throwErrorAt fn.ident s!"function '{fn.name}' generates helper '{helperName}' that conflicts with a function of the same name" diff --git a/artifacts/evmyullean_fork_audit.json b/artifacts/evmyullean_fork_audit.json index f319ff583..f788ffc35 100644 --- a/artifacts/evmyullean_fork_audit.json +++ b/artifacts/evmyullean_fork_audit.json @@ -27,13 +27,23 @@ "sha": "7b54b8f38bb68ee930d00d39c1b11dd60fb123c8", "title": "fix: replace deprecated nativeLibDir with staticLibDir", "trust_impact": "Zero. Lake build metadata only; compiled output is byte-identical up to path naming." + }, + { + "category": "visibility", + "diff_summary": "1 file changed, 1 insertion(+), 1 deletion(-): replace one opaque declaration with an equivalent Lean definition. No behavior change.", + "file": "EvmYul/FFI/ffi.lean", + "rationale": "Replace the opaque `ByteArray.zeroes` extern declaration with a kernel-visible Lean body using `Array.replicate`. The attached extern name remains the same, preserving the runtime FFI target while allowing downstream proofs to unfold the size and byte contents of zero-filled padding.", + "semantic_change": false, + "sha": "b353c7583ea36e49dbbffd57f5b25f4d01226e15", + "title": "ffi: expose zero byte array body", + "trust_impact": "Low. This makes the existing zero-fill behavior visible to Lean's kernel for proofs; it does not alter Yul/EVM execution semantics or the extern symbol used by compiled code." } ], - "divergence_summary": "Fork is exactly 2 commits ahead of upstream/main. Both commits are non-semantic: one visibility change (private -> default) on an internal exponentiation accumulator, and one Lean 4.22.0 deprecation fix (nativeLibDir -> staticLibDir) in the lakefile. Neither commit changes EVM/Yul execution semantics, so upstream Ethereum conformance test coverage continues to apply transitively.", - "fork_ahead_by": 2, + "divergence_summary": "Fork is exactly 3 commits ahead of upstream/main. All commits are non-semantic: one visibility change (private -> default) on an internal exponentiation accumulator, one Lean 4.22.0 deprecation fix (nativeLibDir -> staticLibDir) in the lakefile, and one FFI body exposure for ByteArray.zeroes that matches the extern zero-fill behavior. None of these commits changes EVM/Yul execution semantics, so upstream Ethereum conformance test coverage continues to apply transitively.", + "fork_ahead_by": 3, "fork_behind_by": 0, "fork_url": "https://github.com/lfglabs-dev/EVMYulLean", - "pinned_commit": "7b54b8f38bb68ee930d00d39c1b11dd60fb123c8", + "pinned_commit": "b353c7583ea36e49dbbffd57f5b25f4d01226e15", "reproduction": { "script": "scripts/generate_evmyullean_fork_audit.py", "steps": [ diff --git a/artifacts/macro_property_tests/PropertyBlockTimestampSmoke.t.sol b/artifacts/macro_property_tests/PropertyBlockTimestampSmoke.t.sol new file mode 100644 index 000000000..f4153e5bb --- /dev/null +++ b/artifacts/macro_property_tests/PropertyBlockTimestampSmoke.t.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.33; + +import "./yul/YulTestBase.sol"; + +/** + * @title PropertyBlockTimestampSmokeTest + * @notice Auto-generated baseline property stubs from `verity_contract` declarations. + * @dev Source: Contracts/Smoke.lean + */ +contract PropertyBlockTimestampSmokeTest is YulTestBase { + address target; + address alice = address(0x1111); + + function setUp() public { + target = deployYul("BlockTimestampSmoke"); + require(target != address(0), "Deploy failed"); + } + + // Property 1: TODO decode and assert `nowish` result + function testTODO_Nowish_DecodeAndAssert() public { + vm.prank(alice); + (bool ok, bytes memory ret) = target.call(abi.encodeWithSignature("nowish()")); + require(ok, "nowish reverted unexpectedly"); + assertEq(ret.length, 32, "nowish ABI return length mismatch (expected 32 bytes)"); + // TODO(#1011): decode `ret` and assert the concrete postcondition from Lean theorem. + ret; + } + // Property 2: TODO decode and assert `timestampPlus` result + function testTODO_TimestampPlus_DecodeAndAssert() public { + vm.prank(alice); + (bool ok, bytes memory ret) = target.call(abi.encodeWithSignature("timestampPlus(uint256)", uint256(1))); + require(ok, "timestampPlus reverted unexpectedly"); + assertEq(ret.length, 32, "timestampPlus ABI return length mismatch (expected 32 bytes)"); + // TODO(#1011): decode `ret` and assert the concrete postcondition from Lean theorem. + ret; + } + // Property 3: TODO decode and assert `blobFeePlus` result + function testTODO_BlobFeePlus_DecodeAndAssert() public { + vm.prank(alice); + (bool ok, bytes memory ret) = target.call(abi.encodeWithSignature("blobFeePlus(uint256)", uint256(1))); + require(ok, "blobFeePlus reverted unexpectedly"); + assertEq(ret.length, 32, "blobFeePlus ABI return length mismatch (expected 32 bytes)"); + // TODO(#1011): decode `ret` and assert the concrete postcondition from Lean theorem. + ret; + } +} diff --git a/artifacts/macro_property_tests/PropertyContextAccessorShadowSmoke.t.sol b/artifacts/macro_property_tests/PropertyContextAccessorShadowSmoke.t.sol new file mode 100644 index 000000000..3a44d9950 --- /dev/null +++ b/artifacts/macro_property_tests/PropertyContextAccessorShadowSmoke.t.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.33; + +import "./yul/YulTestBase.sol"; + +/** + * @title PropertyContextAccessorShadowSmokeTest + * @notice Auto-generated baseline property stubs from `verity_contract` declarations. + * @dev Source: Contracts/Smoke.lean + */ +contract PropertyContextAccessorShadowSmokeTest is YulTestBase { + address target; + address alice = address(0x1111); + + function setUp() public { + target = deployYul("ContextAccessorShadowSmoke"); + require(target != address(0), "Deploy failed"); + } + + // Property 1: echoSenderName returns the direct parameter value + function testAuto_EchoSenderName_ReturnsDirectParam() public { + vm.prank(alice); + (bool ok, bytes memory ret) = target.call(abi.encodeWithSignature("echoSenderName(address)", alice)); + require(ok, "echoSenderName reverted unexpectedly"); + assertEq(ret.length, 32, "echoSenderName ABI return length mismatch (expected 32 bytes)"); + address actual = abi.decode(ret, (address)); + assertEq(actual, alice, "echoSenderName should preserve the expected value"); + } + // Property 2: constantNamedChainid returns the declared constant or immutable value + function testAuto_ConstantNamedChainid_ReturnsDeclaredBinding() public { + vm.prank(alice); + (bool ok, bytes memory ret) = target.call(abi.encodeWithSignature("constantNamedChainid()")); + require(ok, "constantNamedChainid reverted unexpectedly"); + assertEq(ret.length, 32, "constantNamedChainid ABI return length mismatch (expected 32 bytes)"); + uint256 actual = abi.decode(ret, (uint256)); + assertEq(actual, 31337, "constantNamedChainid should preserve the expected value"); + } + // Property 3: immutableNamedBlockTimestamp returns the declared constant or immutable value + function testAuto_ImmutableNamedBlockTimestamp_ReturnsDeclaredBinding() public { + vm.prank(alice); + (bool ok, bytes memory ret) = target.call(abi.encodeWithSignature("immutableNamedBlockTimestamp()")); + require(ok, "immutableNamedBlockTimestamp reverted unexpectedly"); + assertEq(ret.length, 32, "immutableNamedBlockTimestamp ABI return length mismatch (expected 32 bytes)"); + uint256 actual = abi.decode(ret, (uint256)); + assertEq(actual, 12345, "immutableNamedBlockTimestamp should preserve the expected value"); + } + // Property 4: immutableNamedMsgSender returns the declared constant or immutable value + function testAuto_ImmutableNamedMsgSender_ReturnsDeclaredBinding() public { + vm.prank(alice); + (bool ok, bytes memory ret) = target.call(abi.encodeWithSignature("immutableNamedMsgSender()")); + require(ok, "immutableNamedMsgSender reverted unexpectedly"); + assertEq(ret.length, 32, "immutableNamedMsgSender ABI return length mismatch (expected 32 bytes)"); + address actual = abi.decode(ret, (address)); + assertEq(actual, address(uint160(42)), "immutableNamedMsgSender should preserve the expected value"); + } +} diff --git a/artifacts/macro_property_tests/PropertyCurveCutArraySmoke.t.sol b/artifacts/macro_property_tests/PropertyCurveCutArraySmoke.t.sol new file mode 100644 index 000000000..e2b60e7a8 --- /dev/null +++ b/artifacts/macro_property_tests/PropertyCurveCutArraySmoke.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.33; + +import "./yul/YulTestBase.sol"; + +/** + * @title PropertyCurveCutArraySmokeTest + * @notice Auto-generated baseline property stubs from `verity_contract` declarations. + * @dev Source: Contracts/Smoke.lean + */ +contract PropertyCurveCutArraySmokeTest is YulTestBase { + address target; + address alice = address(0x1111); + + function setUp() public { + target = deployYul("CurveCutArraySmoke"); + require(target != address(0), "Deploy failed"); + } + +} diff --git a/artifacts/macro_property_tests/PropertyERC721.t.sol b/artifacts/macro_property_tests/PropertyERC721.t.sol index 752f7b743..d24a9a1ec 100644 --- a/artifacts/macro_property_tests/PropertyERC721.t.sol +++ b/artifacts/macro_property_tests/PropertyERC721.t.sol @@ -76,7 +76,7 @@ contract PropertyERC721Test is YulTestBase { } // Property 7: mint returns the minted token id and persists the success-path writes function testAuto_Mint_ReturnsMintedTokenIdAndUpdatesState() public { - address to = alice; + address toAddr = alice; address expectedOwner = alice; uint256 mintedTokenId = uint256(1); uint256 currentSupply = uint256(2); @@ -84,7 +84,7 @@ contract PropertyERC721Test is YulTestBase { vm.store(target, bytes32(uint256(0)), bytes32(uint256(uint160(expectedOwner)))); vm.store(target, bytes32(uint256(1)), bytes32(uint256(currentSupply))); vm.store(target, bytes32(uint256(2)), bytes32(uint256(mintedTokenId))); - vm.store(target, _mappingSlot(bytes32(uint256(uint160(to))), 3), bytes32(uint256(recipientBalance))); + vm.store(target, _mappingSlot(bytes32(uint256(uint160(toAddr))), 3), bytes32(uint256(recipientBalance))); vm.prank(alice); (bool ok, bytes memory ret) = target.call(abi.encodeWithSignature("mint(address)", alice)); require(ok, "mint reverted unexpectedly"); @@ -93,11 +93,11 @@ contract PropertyERC721Test is YulTestBase { assertEq(actual, mintedTokenId, "mint should return the seeded next token id"); assertEq( vm.load(target, _mappingSlot(bytes32(uint256(actual)), 4)), - bytes32(uint256(uint160(to))), + bytes32(uint256(uint160(toAddr))), "mint should persist the new owner word" ); assertEq( - vm.load(target, _mappingSlot(bytes32(uint256(uint160(to))), 3)), + vm.load(target, _mappingSlot(bytes32(uint256(uint160(toAddr))), 3)), bytes32(uint256((uint256(3) + 1))), "mint should increment the recipient balance" ); diff --git a/artifacts/macro_property_tests/PropertyFunctionOverloadSmoke.t.sol b/artifacts/macro_property_tests/PropertyFunctionOverloadSmoke.t.sol new file mode 100644 index 000000000..e8f350956 --- /dev/null +++ b/artifacts/macro_property_tests/PropertyFunctionOverloadSmoke.t.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.33; + +import "./yul/YulTestBase.sol"; + +/** + * @title PropertyFunctionOverloadSmokeTest + * @notice Auto-generated baseline property stubs from `verity_contract` declarations. + * @dev Source: Contracts/Smoke.lean + */ +contract PropertyFunctionOverloadSmokeTest is YulTestBase { + address target; + address alice = address(0x1111); + + function setUp() public { + target = deployYul("FunctionOverloadSmoke"); + require(target != address(0), "Deploy failed"); + } + + // Property 1: echo returns the direct parameter value + function testAuto_Echo_ReturnsDirectParam() public { + vm.prank(alice); + (bool ok, bytes memory ret) = target.call(abi.encodeWithSignature("echo(uint256)", uint256(1))); + require(ok, "echo reverted unexpectedly"); + assertEq(ret.length, 32, "echo ABI return length mismatch (expected 32 bytes)"); + uint256 actual = abi.decode(ret, (uint256)); + assertEq(actual, uint256(1), "echo should preserve the expected value"); + } + // Property 2: TODO decode and assert `echo` result + function testTODO_Echo_DecodeAndAssert() public { + vm.prank(alice); + (bool ok, bytes memory ret) = target.call(abi.encodeWithSignature("echo(address)", alice)); + require(ok, "echo reverted unexpectedly"); + assertEq(ret.length, 32, "echo ABI return length mismatch (expected 32 bytes)"); + // TODO(#1011): decode `ret` and assert the concrete postcondition from Lean theorem. + ret; + } + // Property 3: echo returns the declared constant result + function testAuto_Echo_ReturnsDeclaredConstant() public { + vm.prank(alice); + (bool ok, bytes memory ret) = target.call(abi.encodeWithSignature("echo(uint256,uint256)", uint256(1), uint256(1))); + require(ok, "echo reverted unexpectedly"); + assertEq(ret.length, 32, "echo ABI return length mismatch (expected 32 bytes)"); + uint256 actual = abi.decode(ret, (uint256)); + assertEq(actual, 2, "echo should return the declared constant"); + } +} diff --git a/artifacts/macro_property_tests/PropertyHelperExternalArgumentSmoke.t.sol b/artifacts/macro_property_tests/PropertyHelperExternalArgumentSmoke.t.sol new file mode 100644 index 000000000..a1853a1ec --- /dev/null +++ b/artifacts/macro_property_tests/PropertyHelperExternalArgumentSmoke.t.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.33; + +import "./yul/YulTestBase.sol"; + +/** + * @title PropertyHelperExternalArgumentSmokeTest + * @notice Auto-generated baseline property stubs from `verity_contract` declarations. + * @dev Source: Contracts/Smoke.lean + */ +contract PropertyHelperExternalArgumentSmokeTest is YulTestBase { + address target; + address alice = address(0x1111); + + function setUp() public { + target = deployYul("HelperExternalArgumentSmoke"); + require(target != address(0), "Deploy failed"); + } + + // Property 1: idWord returns the direct parameter value + function testAuto_IdWord_ReturnsDirectParam() public { + vm.prank(alice); + (bool ok, bytes memory ret) = target.call(abi.encodeWithSignature("idWord(uint256)", uint256(1))); + require(ok, "idWord reverted unexpectedly"); + assertEq(ret.length, 32, "idWord ABI return length mismatch (expected 32 bytes)"); + uint256 actual = abi.decode(ret, (uint256)); + assertEq(actual, uint256(1), "idWord should preserve the expected value"); + } + // Property 2: pair decodes and matches the inferred tuple result + function testAuto_Pair_ReturnsInferredTupleResult() public { + vm.prank(alice); + (bool ok, bytes memory ret) = target.call(abi.encodeWithSignature("pair(uint256)", uint256(1))); + require(ok, "pair reverted unexpectedly"); + require(ret.length >= 64, "pair ABI tuple return payload unexpectedly short"); + (uint256 actual0, uint256 actual1) = abi.decode(ret, (uint256, uint256)); + assertEq(actual0, uint256(1), "pair tuple element 0 should preserve the inferred result"); + assertEq(actual1, 2, "pair tuple element 1 should preserve the inferred result"); + } + // Property 3: put has no unexpected revert + function testAuto_Put_NoUnexpectedRevert() public { + vm.prank(alice); + (bool ok,) = target.call(abi.encodeWithSignature("put(uint256)", uint256(1))); + require(ok, "put reverted unexpectedly"); + } + // Property 4: TODO decode and assert `bindExternalArg` result + function testTODO_BindExternalArg_DecodeAndAssert() public { + vm.prank(alice); + (bool ok, bytes memory ret) = target.call(abi.encodeWithSignature("bindExternalArg(uint256)", uint256(1))); + require(ok, "bindExternalArg reverted unexpectedly"); + assertEq(ret.length, 32, "bindExternalArg ABI return length mismatch (expected 32 bytes)"); + // TODO(#1011): decode `ret` and assert the concrete postcondition from Lean theorem. + ret; + } + // Property 5: TODO decode and assert `tupleExternalArg` result + function testTODO_TupleExternalArg_DecodeAndAssert() public { + vm.prank(alice); + (bool ok, bytes memory ret) = target.call(abi.encodeWithSignature("tupleExternalArg(uint256)", uint256(1))); + require(ok, "tupleExternalArg reverted unexpectedly"); + assertEq(ret.length, 32, "tupleExternalArg ABI return length mismatch (expected 32 bytes)"); + // TODO(#1011): decode `ret` and assert the concrete postcondition from Lean theorem. + ret; + } + // Property 6: statementExternalArg has no unexpected revert + function testAuto_StatementExternalArg_NoUnexpectedRevert() public { + vm.prank(alice); + (bool ok,) = target.call(abi.encodeWithSignature("statementExternalArg(uint256)", uint256(1))); + require(ok, "statementExternalArg reverted unexpectedly"); + } +} diff --git a/artifacts/macro_property_tests/PropertyPackedAddressStorageWriteSmoke.t.sol b/artifacts/macro_property_tests/PropertyPackedAddressStorageWriteSmoke.t.sol new file mode 100644 index 000000000..2381245f7 --- /dev/null +++ b/artifacts/macro_property_tests/PropertyPackedAddressStorageWriteSmoke.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.33; + +import "./yul/YulTestBase.sol"; + +/** + * @title PropertyPackedAddressStorageWriteSmokeTest + * @notice Auto-generated baseline property stubs from `verity_contract` declarations. + * @dev Source: Contracts/Smoke.lean + */ +contract PropertyPackedAddressStorageWriteSmokeTest is YulTestBase { + address target; + address alice = address(0x1111); + + function setUp() public { + target = deployYul("PackedAddressStorageWriteSmoke"); + require(target != address(0), "Deploy failed"); + } + + // Property 1: TODO decode and assert `writeOwnerWord` result + function testTODO_WriteOwnerWord_DecodeAndAssert() public { + vm.prank(alice); + (bool ok, bytes memory ret) = target.call(abi.encodeWithSignature("writeOwnerWord(uint256)", uint256(1))); + require(ok, "writeOwnerWord reverted unexpectedly"); + assertEq(ret.length, 32, "writeOwnerWord ABI return length mismatch (expected 32 bytes)"); + // TODO(#1011): decode `ret` and assert the concrete postcondition from Lean theorem. + ret; + } +} diff --git a/artifacts/macro_property_tests/PropertyPackedStorageWriteSmoke.t.sol b/artifacts/macro_property_tests/PropertyPackedStorageWriteSmoke.t.sol new file mode 100644 index 000000000..4e36e0b74 --- /dev/null +++ b/artifacts/macro_property_tests/PropertyPackedStorageWriteSmoke.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.33; + +import "./yul/YulTestBase.sol"; + +/** + * @title PropertyPackedStorageWriteSmokeTest + * @notice Auto-generated baseline property stubs from `verity_contract` declarations. + * @dev Source: Contracts/Smoke.lean + */ +contract PropertyPackedStorageWriteSmokeTest is YulTestBase { + address target; + address alice = address(0x1111); + + function setUp() public { + target = deployYul("PackedStorageWriteSmoke"); + require(target != address(0), "Deploy failed"); + } + + // Property 1: writeSlot0 has no unexpected revert + function testAuto_WriteSlot0_NoUnexpectedRevert() public { + vm.prank(alice); + (bool ok,) = target.call(abi.encodeWithSignature("writeSlot0(bool,uint256)", true, uint256(1))); + require(ok, "writeSlot0 reverted unexpectedly"); + } + // Property 2: writeSlot1 has no unexpected revert + function testAuto_WriteSlot1_NoUnexpectedRevert() public { + vm.prank(alice); + (bool ok,) = target.call(abi.encodeWithSignature("writeSlot1(uint256,uint256)", uint256(1), uint256(1))); + require(ok, "writeSlot1 reverted unexpectedly"); + } +} diff --git a/artifacts/verification_status.json b/artifacts/verification_status.json index e401404aa..75b4eee57 100644 --- a/artifacts/verification_status.json +++ b/artifacts/verification_status.json @@ -1,6 +1,6 @@ { "codebase": { - "core_lines": 635, + "core_lines": 641, "example_contracts": 14 }, "proofs": { diff --git a/docs-site/content/compiler.mdx b/docs-site/content/compiler.mdx index 8fe999028..4efa69898 100644 --- a/docs-site/content/compiler.mdx +++ b/docs-site/content/compiler.mdx @@ -490,6 +490,16 @@ for { let i := 0 } lt(i, recipients_length) { i := add(i, 1) } { Define reusable logic shared across multiple external functions. Internal functions compile to Yul `function` definitions and are **not** exposed via selector dispatch: +The examples in this section use the lower-level `CompilationModel` API directly. In `verity_contract` source, call same-contract helpers with ordinary function-call syntax instead: + +```lean +helper arg +let result ← helper arg +let (lhs, rhs) ← helperPair arg +``` + +Those source forms lower to `Stmt.internalCall`, `Stmt.internalCallAssign`, or `Expr.internalCall` in the compilation model. + ```lean def mySpec : CompilationModel := { name := "MyContract" diff --git a/docs-site/content/edsl-api-reference.mdx b/docs-site/content/edsl-api-reference.mdx index cda74eaa3..bac0b4265 100644 --- a/docs-site/content/edsl-api-reference.mdx +++ b/docs-site/content/edsl-api-reference.mdx @@ -326,6 +326,38 @@ def wDivUp (a b : Uint256) : Uint256 - Exact cancellation and wad identity lemmas: `mulDivDown_cancel_right/left`, `mulDivUp_cancel_right/left`, `wMulDown_one_left/right`, `wDivUp_by_wad` (`Verity/Proofs/Stdlib/Math.lean`) - Wad-specialized helper lemmas: `wMulDown_nat_eq`, `wDivUp_nat_eq`, `wDivUp_antitone_right` (`Verity/Proofs/Stdlib/Math.lean`) +## Same-Contract Helper Calls + +Inside `verity_contract`, call reusable same-contract helpers with ordinary function-call syntax. Do not write `internalCall` or `internalCallAssign` in source contracts; those names are compilation-model IR constructors used after macro lowering. + +```lean +verity_contract HelperExample where + storage + total : Uint256 := slot 0 + + function bump (x : Uint256) : Uint256 := do + return add x 1 + + function storeBump (x : Uint256) : Unit := do + let y ← bump x + setStorage total y + + function applyBump (x : Uint256) : Unit := do + storeBump x +``` + +Use the same direct-call form for helpers that return no value, one value, or a static tuple: + +```lean +storeBump x +let y ← bump x +let (assets, shares) ← preview seed +``` + +The macro lowers effect-only helper calls such as `storeBump x` to `Stmt.internalCall`, helper result bindings such as `let y ← bump x` to `Stmt.internalCallAssign`, and expression-position helper calls to `Expr.internalCall`. Those IR nodes are useful when writing `CompilationModel` values by hand, but direct helper-call syntax is the supported `verity_contract` authoring surface. + +Direct helper lowering currently supports static non-dynamic parameter and return types. Helpers whose parameters or return values contain dynamic data such as `String`, `Bytes`, or arrays are still outside this direct internal-helper path. + ## Tuple Surface `verity_contract` now accepts tuple-shaped local sugar for the static tuple cases already supported by the compilation model: diff --git a/docs-site/content/guides/solidity-to-verity.mdx b/docs-site/content/guides/solidity-to-verity.mdx index d03243988..46065ad5a 100644 --- a/docs-site/content/guides/solidity-to-verity.mdx +++ b/docs-site/content/guides/solidity-to-verity.mdx @@ -50,13 +50,21 @@ modifier onlyOwner() { Verity pattern: ```lean -private def onlyOwner : Contract Unit := do - let owner <- getStorage ownerSlot - let sender <- msgSender - require (sender == owner) "not owner" +verity_contract OwnedExample where + storage + owner : Address := slot 0 + + function onlyOwner () : Unit := do + let currentOwner ← getStorageAddr owner + let sender ← msgSender + require (sender == currentOwner) "not owner" + + function restricted () : Unit := do + onlyOwner + -- restricted body ``` -Then call `onlyOwner` at function start. +Call helpers directly by name. `internalCall` and `internalCallAssign` are the lowered compilation-model nodes for helper calls, not the source syntax to write in `verity_contract`. ### try/catch diff --git a/docs/INTERPRETER_FEATURE_MATRIX.md b/docs/INTERPRETER_FEATURE_MATRIX.md index cdd4ad45d..e307d4010 100644 --- a/docs/INTERPRETER_FEATURE_MATRIX.md +++ b/docs/INTERPRETER_FEATURE_MATRIX.md @@ -97,6 +97,7 @@ Legend: **ok** = supported, **0** = returns 0 (not modeled), **del** = delegated | Event emission | `Stmt.emit` | ok | ok | -- | proved | | Internal call (stmt) | `Stmt.internalCall` | **rev** | ok | -- | proved | | Internal call assign | `Stmt.internalCallAssign` | **rev** | ok | -- | proved | +| Manual full-word storage write | `Stmt.setStorageWord` | ok | ok | -- | n/m | | Memory store | `Stmt.mstore` | **rev** | **rev** | ok | partial | | Calldatacopy | `Stmt.calldatacopy` | nop | nop | -- | n/m | | Returndatacopy | `Stmt.returndataCopy` | **rev** | **rev** | -- | n/m | @@ -174,7 +175,7 @@ Proof-boundary features split across two buckets. Partially modeled features cur 2. **Low-level calls**: `call`/`staticcall`/`delegatecall` and `externalCallBind`/`ecm` are compiler-only features validated by Foundry testing, not modeled in proof interpreters. `delegatecall` additionally remains a dedicated proxy / upgradeability trust boundary; use `--trust-report` / `--deny-proxy-upgradeability` when those semantics must stay outside the selected verification envelope (issue [#1420](https://github.com/lfglabs-dev/verity/issues/1420)). -3. **Internal helper compositional proofs**: `Stmt.internalCall` / `Expr.internalCall` execute in the fuel-based interpreter path, but helper-level theorem reuse across callers is not yet surfaced as a first-class proof interface. The current proof-level gap is tracked under the Layer 2 completeness roadmap in [#1630](https://github.com/lfglabs-dev/verity/issues/1630), with the interface/boundary refactor in [#1633](https://github.com/lfglabs-dev/verity/pull/1633). +3. **Internal helper compositional proofs**: `Stmt.internalCall` / `Expr.internalCall` execute in the fuel-based interpreter path, but helper-level theorem reuse across callers is not yet surfaced as a first-class proof interface. The `verity_contract` user surface for these is ordinary direct function-name calls; `internalCall` / `internalCallAssign` remain lower-level compilation-model constructors. The current proof-level gap is tracked under the Layer 2 completeness roadmap in [#1630](https://github.com/lfglabs-dev/verity/issues/1630), with the interface/boundary refactor in [#1633](https://github.com/lfglabs-dev/verity/pull/1633). --- diff --git a/docs/NATIVE_EVMYULLEAN_TRANSITION.md b/docs/NATIVE_EVMYULLEAN_TRANSITION.md new file mode 100644 index 000000000..2014f750c --- /dev/null +++ b/docs/NATIVE_EVMYULLEAN_TRANSITION.md @@ -0,0 +1,604 @@ +# Native EVMYulLean Runtime Transition + +This document tracks the remaining work for issue #1737: make native +EVMYulLean execution the public Layer 3 semantic target for Verity-generated +runtime Yul. + +## Current State + +The current public proof path still targets: + +```lean +interpretYulRuntimeWithBackend .evmYulLean +``` + +That path executes Verity's custom fuel-based Yul statement interpreter and +routes bridged builtins through EVMYulLean-backed builtin evaluation. This is a +useful compatibility bridge, but it is not the final architecture requested by +#1722. + +The native path now exists beside it: + +```lean +Compiler.Proofs.YulGeneration.Backends.Native.interpretRuntimeNative +Compiler.Proofs.YulGeneration.Backends.Native.interpretIRRuntimeNative +``` + +Those entry points lower Verity runtime Yul into an EVMYulLean `YulContract`, +construct an EVMYulLean `SharedState .Yul`, run +`EvmYul.Yul.callDispatcher`, and project the observable result back to +Verity's `YulResult` shape. The native IR entry point requires callers to pass +the observable storage slot set explicitly, because the state bridge only +materializes pre-state storage for those slots. + +## What This PR Establishes + +- The native target has an IR-contract entry point: + `interpretIRRuntimeNative`. +- Native result projection preserves pre-existing event history and appends + native EVMYulLean logs, matching the observable shape expected by the current + proof-side `YulResult`. +- The EndToEnd layer now exposes the native-facing theorem seam + `layers2_3_ir_matches_native_evmYulLean_of_interpreter_bridge`. Its + conclusion targets `Native.interpretIRRuntimeNative` through + `nativeResultsMatchOn`, comparing success, return value, events, and the + explicitly observable final-storage slots, but it still requires the explicit + `nativeIRRuntimeAgreesWithInterpreter` obligation for the generated runtime. + That obligation is observable-slot and fuel-aligned with the native run through + `interpretYulRuntimeWithBackendFuel`, and the theorem seam currently requires + that fuel to equal the interpreter proof stack's default runtime fuel + `sizeOf (Compiler.emitYul contract).runtimeCode + 1`. This is the exact + remaining native-vs-interpreter equivalence theorem plus a named + full-storage-projection and fuel-parametric-preservation gap, not a completed + public flip. +- The same module also exposes + `nativeCallDispatcherAgreesWithInterpreter`, + `nativeDispatcherBlockAgreesWithInterpreter`, + `nativeCallDispatcherAgreesWithInterpreter_of_dispatcherBlock_agree`, + `nativeIRRuntimeAgreesWithInterpreter_of_lowered_callDispatcher_agree`, + `layer3_contract_preserves_semantics_native_of_lowered_callDispatcher_bridge`, + and + `layers2_3_ir_matches_native_evmYulLean_of_lowered_callDispatcher_bridge`. + These move the remaining proof obligation down to concrete native lowering, + selected-path environment validation, and projected native dispatcher-block + execution agreement with the fuel-aligned interpreter oracle. +- The native harness also names the dispatcher-block execution that + `EvmYul.Yul.callDispatcher` performs after fuel checking and empty call-frame + setup: `callDispatcherBlockResult`, with + `callDispatcher_succ_eq_callDispatcherBlockResult` proving the reduction. + It then rewrites initial-state execution to the lowered contract directly via + `contractDispatcherBlockResult` and + `callDispatcherBlockResult_initialState_eq_contractDispatcherBlockResult`. + The wrapper is also peeled to raw native execution through + `contractDispatcherExecResult` and + `contractDispatcherBlockResult_eq_execResult`. The next proof no longer has + to open dispatcher or projection wrappers before attacking `EvmYul.Yul.exec` + preservation for the lowered contract dispatcher body. +- Native runtime top-level partitioning is now transparent enough for proofs: + `lowerRuntimeContractNativeAux` is structurally recursive, and the named + equations `lowerRuntimeContractNativeAux_funcDef_cons`, + `lowerRuntimeContractNativeAux_stmt_cons`, and + `lowerRuntimeContractNative_empty` expose the helper-definition/function-map + split that future dispatcher-agreement proofs need. +- The selected generated-dispatch path has small named hooks: + `selectorExprMatchesGeneratedDispatcher_selectorExpr`, + `selectedSwitchBody_hit`, and `selectedSwitchBody_miss`. +- The native harness remains separate from the existing retargeting theorem, so + the proof tree does not claim a theorem that is not yet proved. + +## Clean Target Architecture + +The desired end state is: + +```text +CompilationModel + -> IRContract + -> emitted runtime Yul + -> EVMYulLean YulContract + -> EvmYul.Yul.callDispatcher + -> projected observable result +``` + +The Verity custom Yul interpreter should then be used only as a regression +oracle, not as the semantic target in the public theorem stack. + +## Remaining Work + +The following verified user reports should stay explicit in the transition +scope so the native path does not look more complete than it is: + +- [#1741](https://github.com/lfglabs-dev/verity/issues/1741): + `blockTimestamp` is bridged through native EVMYulLean execution, and native + smoke coverage now checks `timestamp()`/`number()` state reads. Native + `chainid()` and `blobbasefee()` now fail closed on the selected native runtime + path unless the corresponding `YulTransaction` field is representable by + EVMYulLean's current environment model. Today that means + `YulTransaction.chainId` must match the EVMYulLean global `EvmYul.chainId`, and + `YulTransaction.blobBaseFee` must match the minimum blob gas price + `EvmYul.MIN_BASE_FEE_PER_BLOB_GAS`. Header-derived native builtins that do + not yet have Verity `YulTransaction` fields, such as `coinbase`, `difficulty`, + `prevrandao`, `gaslimit`, `basefee`, and `gasprice`, also fail closed on the + selected native runtime path instead of reading EVMYulLean's zeroed header + defaults. The native harness names the remaining unbridged boundary with + `initialState_unbridgedEnvironmentDefaults`, pinning base-fee/blob fields and + native `chainid` to their current EVMYulLean default/global behavior until + the follow-up widens the state bridge. The + `verity_contract` surface now accepts monadic environment reads such as + `let t <- blockTimestamp`, `let t <- Verity.blockTimestamp`, `blockNumber`, + `chainid`, `blobbasefee`, `contractAddress`, `msgSender`, and `msgValue`, and the + executable `.run` helpers read those values from `ContractState` instead of + placeholder constants. +- [#1738](https://github.com/lfglabs-dev/verity/issues/1738): mapping-struct + storage now has contract-local executable `.run` helpers for struct member + reads/writes that use the same abstract Solidity mapping-slot formula as the + compiler/native storage projection, including packed member masking. Before + native execution becomes authoritative, this still needs proof-level source + semantics and preservation coverage for the packed struct-member cases. +- [#1742](https://github.com/lfglabs-dev/verity/issues/1742): overloaded + source functions now use a signature-based identity model for generated + declarations, duplicate validation, and direct internal-call lowering while + preserving the Solidity-facing source name for selectors/ABI dispatch. + Same-name/same-arity declarations are accepted when their parameter types + differ. The macro-generated compilation model still gives internal helpers + unique Yul-level names, and the lower-level `CompilationModel` rejects + duplicate same-name internal functions because native/Yul function + definitions are keyed by name. Native EVM dispatch is selector-based; the + remaining transition work is theorem coverage around the widened frontend + surface and any future executable `.run` overload-dispatch extensions. +- [#1740](https://github.com/lfglabs-dev/verity/issues/1740): the + `verity_contract` source surface intentionally models internal delegation with + ordinary direct function-name calls, not user-written `internalCall`/ + `internalCallAssign`. Statement-position calls lower to + `Stmt.internalCall`, single-result binds lower to `Expr.internalCall`, and + tuple-result binds lower to `Stmt.internalCallAssign`; the lower-level + constructors remain compilation-model IR, not the recommended executable + contract-body syntax. +- [#1744](https://github.com/lfglabs-dev/verity/issues/1744): near-literal + manual packed-slot writes now have a first-class `verity_contract` surface: + construct the packed word with ordinary word operations such as `bitOr` and + `shl`, then write it with `setPackedStorage root offset word`. This lowers to + a full-word `sstore(root.slot + offset, word)` via + `Stmt.setStorageWord`, keeping explicit slot boundaries visible without an + unsafe raw-Yul block. +- [#1745](https://github.com/lfglabs-dev/verity/issues/1745): dynamic array + parameters with static tuple elements are now accepted on the tuple + destructuring/tuple-return `arrayElement` path. Tuple destructuring such as + `let (xtReserve, liqSquare, offset) := arrayElement cuts idx` lowers to + checked word reads with the element ABI stride. Plain scalar `arrayElement` + remains limited to single-word static element arrays until the general + multi-word element read path lowers through `Expr.arrayElementWord` instead + of the 32-byte-stride helper. Dynamic element types such as `Array String` + and `Array Bytes` remain rejected. + +1. Prove lowering invariants for the native contract shape. + + Required facts: + - top-level `funcDef` nodes are partitioned into `YulContract.functions` + (now exposed by `lowerRuntimeContractNativeAux_funcDef_cons`), + - dispatcher code contains no function definitions, + - known runtime builtins lower to native `.inl` primops, now named by + `lowerExprNative_call_runtimePrimOp`, + - user/helper calls remain `.inr` function calls, now named by + `lowerExprNative_call_userFunction`, + - duplicate helper definitions fail closed. + + Progress: `EvmYul.Yul.callDispatcher` now unfolds through + `callDispatcher_succ_eq_callDispatcherBlockResult` to the named + `callDispatcherBlockResult`, then rewrites initial-state execution to + `contractDispatcherBlockResult`, then peels the block wrapper to + `contractDispatcherExecResult`. EndToEnd exposes + `nativeDispatcherBlockAgreesWithInterpreter` plus + `nativeDispatcherExecAgreesWithInterpreter`, + `nativeDispatcherBlockAgreesWithInterpreter_of_exec_agree`, and + `nativeCallDispatcherAgreesWithInterpreter_of_dispatcherBlock_agree`. The + remaining bridge is therefore direct native `EvmYul.Yul.exec` execution of + the lowered contract dispatcher block against the interpreter oracle. + + Statement-level native lowering through + `lowerStmtsNativeWithSwitchIds`/`lowerStmtGroupNativeWithSwitchIds` is now + structurally recursive, and named equations expose list cons, switch-case + cons, straight-line statement forms, blocks, loops, and the lazy native + switch block constructor `lowerNativeSwitchBlock`. The theorem + `lowerNativeSwitchBlock_eq` records the actual guarded-block shape that + native dispatcher proofs must consume: the discriminator is evaluated once, + each case is guarded by `iszero(matched) && discr == tag`, and the default + runs only when no case has marked the switch matched. The top-level partition + equation and statement-level lowering equations are proved, but full + dispatcher-block agreement still requires per-statement native execution + preservation lemmas against `execYulFuelWithBackend .evmYulLean`. + EndToEnd now provides raw-exec intro forms for the three concrete native + outcomes: + `nativeDispatcherExecAgreesWithInterpreter_of_exec_ok_agree`, + `nativeDispatcherExecAgreesWithInterpreter_of_exec_yulHalt_agree`, and + `nativeDispatcherExecAgreesWithInterpreter_of_exec_error_agree`. These let + each generated-statement simulation case finish from a proved + `contractDispatcherExecResult` equation plus the corresponding observable + projection agreement. + + The generated dispatcher selector expression is also pinned for the + EVMYulLean-backed interpreter oracle: + `bridgedExpr_selectorExpr` shows that `selectorExpr` is in the bridged + expression fragment, and + `evalYulExprWithBackend_evmYulLean_selectorExpr_semantics` proves that it + evaluates to `state.selector % selectorModulus`. This discharges the + interpreter-oracle side of the first selector branch condition. The native + side now exposes `lowerExprNative_selectorExpr`, + `step_calldataload_ok`, `step_shr_ok`, + `primCall_calldataload_ok`, `primCall_shr_ok`, and + `eval_lowerExprNative_selectorExpr_ok`, so native evaluation of the lowered + selector expression reduces to EVMYulLean `calldataload(0)` followed by + `shr(224, ...)`. The byte bridge also names + `readBytes_zero_get?_of_lt_source` plus + `initialState_calldataReadWord_selectorByte0` through + `initialState_calldataReadWord_selectorByte3`, proving that the native + word read sees the bridged selector bytes before any opaque zero-padding. + The arithmetic recomposition side is named by `selectorBytesAsNat`, + `initialState_calldataReadWord_selectorPrefix` exposes the corresponding + little-endian `fromBytes'` prefix, `uint256_shiftRight_224_ofNat_toNat` + exposes the native `UInt256.shiftRight` arithmetic at selector shift, and + `readBytes_zero_32_size` proves the EVMYulLean `readBytes` length fact now + that `ffi.ByteArray.zeroes` has a kernel-visible Lean body in the pinned + EVMYulLean revision. The named + `initialState_selectorExpr_native_value` and + `eval_lowerExprNative_selectorExpr_initialState_ok` theorems therefore prove + that native evaluation of the lowered selector expression over the bridged + initial state returns `tx.functionSelector % selectorModulus`. + `exec_let_lowerExprNative_selectorExpr_initialState_ok`, `exec_let_lit_ok`, + and `exec_nativeSwitchPrefix_selector_initialState_ok` then package the + first two statements emitted by the generated native switch block: + discriminator-temp initialization from the normalized selector followed by + matched-flag initialization to zero. The named + `step_eq_ok`, `step_iszero_ok`, `step_and_ok`, `primCall_eq_ok`, + `primCall_iszero_ok`, `primCall_and_ok`, `exec_block_cons_ok`, + `exec_if_eval_zero`, `exec_if_eval_nonzero`, and + `eval_nativeSwitchGuardedMatch_ok` theorems expose the next native + guarded-switch reduction layer for the lazy switch block emitted by + `lowerNativeSwitchBlock`; `eval_nativeSwitchGuardedMatch_hit_ok`, + `exec_if_nativeSwitchGuardedMatch_hit`, `exec_lowerAssignNative_lit_ok`, + and `exec_if_nativeSwitchGuardedMatch_hit_marked` package the selected-case + guard hit, matched-flag assignment, and native `if` execution step, while + `eval_nativeSwitchGuardedMatch_miss_ok` and + `exec_if_nativeSwitchGuardedMatch_miss` package the non-selected-case skip + while no previous case has matched, and + `eval_nativeSwitchGuardedMatch_matched_ok` and + `exec_if_nativeSwitchGuardedMatch_matched` package the later-case skip + once the matched flag is set. `eval_nativeSwitchDefaultGuard_ok`, + `eval_nativeSwitchDefaultGuard_unmatched_ok`, + `eval_nativeSwitchDefaultGuard_matched_ok`, + `exec_if_nativeSwitchDefaultGuard_unmatched`, and + `exec_if_nativeSwitchDefaultGuard_matched` package the generated default + guard for both no-case-matched and case-already-matched paths. The matching + `_fuel` variants remove the fixed-fuel limitation that blocked recursive + whole-case-chain execution over the generated block tail. The native harness + now also exposes `exec_nativeSwitchCaseIfs_all_miss_fuel`, + `exec_nativeSwitchCaseIfs_matched_fuel`, and + `exec_nativeSwitchCaseIfs_prefix_hit_fuel`, packaging whole guarded + case-chain execution for default misses, suffix skips after a match, and the + selected-case prefix-hit shape. `exec_nativeSwitchDefaultIf_unmatched_nonempty_fuel` + and `exec_nativeSwitchDefaultIf_matched_fuel` then package the optional + default statement emitted by the lazy lowering. The selector lookup bridge + now exposes `nativeSwitch_find_hit_split`, + `nativeSwitch_find_none_all_miss`, + `exec_nativeSwitchCaseIfs_find_hit_fuel`, and + `exec_nativeSwitchCaseIfs_find_none_fuel`, so generated dispatcher proofs can + consume a concrete `find?` hit or miss instead of manually supplying the + prefix/selected/suffix split. The hit side now has the preservation adapter + `NativeBlockPreservesWord` plus + `exec_nativeSwitchCaseIfs_find_hit_preserved_fuel`, which turns selected-body + preservation of the matched flag into the whole generated case-chain + postcondition. The native harness also exposes `exec_block_append_ok`, + `exec_nativeSwitchCaseIfs_with_default_matched_fuel`, + `exec_nativeSwitchCaseIfs_find_hit_with_default_preserved_fuel`, + `exec_nativeSwitchCaseIfs_find_none_with_default_nonempty_fuel`, and + `exec_nativeSwitchCaseIfs_find_none_without_default_fuel`, composing selector + lookup with the optional default arm emitted by `lowerNativeSwitchBlock`. The + adapter now also exposes + `lowerSwitchCasesNativeWithSwitchIds_find?_some` and + `lowerSwitchCasesNativeWithSwitchIds_find?_none`, proving that native case + lowering preserves the source switch selector lookup result while exposing + the switch-temp counter interval for the selected lowered body. The + remaining native dispatcher proof starts after that complete lazy-switch + bridge, at proving `NativeBlockPreservesWord` for selected/default lowered + bodies and threading the initialized prefix state plus the lowering lookup + facts into the raw lowered switch block. The harness now exposes that raw + switch shape as `lowerNativeSwitchBlock_selectorExpr_eq_nativeSwitchParts` + and packages the raw block execution bridge with + `exec_lowerNativeSwitchBlock_selector_find_hit_preserved_fuel`, + `exec_lowerNativeSwitchBlock_selector_find_none_with_default_nonempty_fuel`, + and `exec_lowerNativeSwitchBlock_selector_find_none_without_default_fuel`. + The body preservation algebra now includes `state_lookup_insert_of_ne`, + `nativeSwitchPrefixFinalState_matched`, + `nativeSwitchPrefixFinalState_discr`, + `nativeSwitchPrefixFinalState_marked`, `NativeBlockPreservesWord_nil`, + `NativeBlockPreservesWord_cons`, + `nativeSwitchTempsFreshForNativeBodies_find_hit_matched_not_mem`, and + `nativeSwitchTempsFreshForNativeBodies_default_matched_not_mem`; the next + proof step is the statement induction that derives those preservation + obligations from `nativeStmtsWriteNames` freshness. The adapter now names + the needed freshness surface with `yulStmtWriteNames`, + `yulStmtsWriteNames`, + `nativeStmtWriteNames`, `nativeStmtsWriteNames`, + `nativeSwitchTempsFreshForWrites`, + `nativeSwitchTempsFreshForSourceBodies`, and + `nativeSwitchTempsFreshForNativeBodies`; the key precondition to discharge + is that fresh native switch temporaries are not reassigned by lowered + case/default bodies while the selected body runs. + +2. Prove native state bridge lemmas. + + Required fields: + - selector and calldata byte layout, + - caller/source and current address, + - callvalue, + - block timestamp, block number, chain id, and blob base fee, + - storage lookup and storage write projection, + - transient storage where generated Yul uses `tload`/`tstore`, + - memory and returndata for ABI return/revert/log paths. + +3. Prove native result projection lemmas. + + Required cases: + - normal expression values returned by `callDispatcher`, + - `stop`, + - 32-byte `return`, + - `revert` with rollback, + - log projection with topics followed by word-aligned data, + - hard native errors mapping to conservative failure. + +4. Add wider executable coverage for the native path. + + The native theorem seam now compares native execution and the interpreter + oracle under the same explicit fuel, but the existing Layer 3 preservation + theorem is still only composed at the default runtime fuel. Before the + public target can accept arbitrary native fuel, either prove the Layer 3 + preservation theorem fuel-parametrically or keep the public native wrapper + total by choosing that default fuel internally. + + Current smoke coverage exercises primop lowering, including critical + halt/log builtins, helper function maps, duplicate-helper failure, emitted + dispatcher lowering shape and selector expression, lazy native dispatcher + guards used instead of native `Switch` so matched selectors do not execute + reverting default branches, block-scoped discriminator bindings for those + lazy dispatcher guards, threaded native switch-discriminator ids across + dispatcher statement lowering, per-switch matched flags that prevent + non-halting duplicate case bodies from falling through, native assignment + lowering through the named + `lowerAssignNative` helper plus executable rebinding smoke coverage, + dispatcher/helper + partitioning that keeps helper definitions in the function map while + dispatcher calls remain native user-function calls, fail-closed rejection + of nested native function definitions in dispatcher/helper bodies, the named + `lowerExprNative_call_runtimePrimOp` and + `lowerExprNative_call_userFunction` lemmas for native expression-call + lowering, + selector cases with their lowered storage-write and memory-return bodies, + selector/calldata byte layout, ABI argument-word decoding, storage writes, `sload` through explicit + observable pre-state slots, omitted-slot default reads, IR storage-read + dispatcher lowering to native `SLOAD`, `interpretIRRuntimeNative` storage + forwarding with explicit observable slots and prior events, the named + `interpretIRRuntimeNative_eq_interpretRuntimeNative` lemma for that + forwarding contract, `tstore`/`tload` + execution through copied observable storage, initial native state contract + installation, the named `initialState_installsExecutionContract` lemma for + installed dispatcher code and mutable execution permission, the named + `initialState_installsCurrentAccountContract` lemma for account-map runtime + code installation at the current contract address, observable + storage seeding, the named `initialState_observableStorageSlot` lemma for + explicit observable pre-state slots, the named + `initialState_omittedStorageSlot` lemma for omitted pre-state slot defaults, + the named `projectStorageFromState_accountStorageSlot` and + `projectStorageFromState_missingAccountStorageSlot` lemmas for final native + account storage projection, the named + `projectStorageFromState_missingAccount` lemma for absent-account default + projection, + and direct transaction-environment seeding for sender/source, value, current + address, calldata selector/argument bytes, timestamp, and block number, the + named `initialState_transactionEnvironment` lemma for those initial + environment fields, the named `initialState_source`, + `initialState_sender`, `initialState_codeOwner`, `initialState_weiValue`, + `initialState_blockTimestamp`, `initialState_blockNumber`, and + `initialState_calldata` lemmas for direct downstream rewriting of those + native state bridge fields, the named `initialState_calldataSize` lemma for the native + selector-plus-ABI-word calldata length, the named + `calldataToByteArray_selectorByte0`, + `calldataToByteArray_selectorByte1`, + `calldataToByteArray_selectorByte2`, and + `calldataToByteArray_selectorByte3` lemmas pinning the native calldata + selector byte layout needed by dispatcher-selection proofs, the named + `readBytes_zero_get?_of_lt_source`, + `initialState_calldataReadWord_selectorByte0`, + `initialState_calldataReadWord_selectorByte1`, + `initialState_calldataReadWord_selectorByte2`, and + `initialState_calldataReadWord_selectorByte3` lemmas showing that native + `calldataload(0)` reads those selector bytes in its first ABI word. It also + includes the named `initialState_calldataReadWord_selectorPrefix`, + `uint256_shiftRight_224_ofNat_toNat`, `readBytes_zero_32_size`, + `initialState_selectorExpr_native_value`, and + `eval_lowerExprNative_selectorExpr_initialState_ok` lemmas proving native + selector-value agreement for the bridged initial state, the named + `exec_let_lowerExprNative_selectorExpr_initialState_ok`, `exec_let_lit_ok`, + and `exec_nativeSwitchPrefix_selector_initialState_ok` lemmas for the + generated native switch discriminator/matched-prefix initialization, the named + `eval_nativeSwitchGuardedMatch_ok`, `eval_nativeSwitchGuardedMatch_hit_ok`, + `exec_if_nativeSwitchGuardedMatch_hit`, `exec_lowerAssignNative_lit_ok`, + `exec_if_nativeSwitchGuardedMatch_hit_marked`, + `eval_nativeSwitchGuardedMatch_miss_ok`, + `exec_if_nativeSwitchGuardedMatch_miss`, + `eval_nativeSwitchGuardedMatch_matched_ok`, + `exec_if_nativeSwitchGuardedMatch_matched`, + `eval_nativeSwitchDefaultGuard_ok`, + `eval_nativeSwitchDefaultGuard_unmatched_ok`, + `eval_nativeSwitchDefaultGuard_matched_ok`, + `exec_if_nativeSwitchDefaultGuard_unmatched`, + `exec_if_nativeSwitchDefaultGuard_matched`, the fuel-parametric + `_fuel` variants for generated case/default guards, + `exec_nativeSwitchCaseIfs_all_miss_fuel`, + `exec_nativeSwitchCaseIfs_matched_fuel`, and + `exec_nativeSwitchCaseIfs_prefix_hit_fuel` for whole generated case chains, + `nativeSwitch_find_hit_split`, `nativeSwitch_find_none_all_miss`, + `exec_nativeSwitchCaseIfs_find_hit_fuel`, and + `exec_nativeSwitchCaseIfs_find_none_fuel` for selector-lookup-driven case + chain execution, + `exec_nativeSwitchDefaultIf_unmatched_nonempty_fuel` and + `exec_nativeSwitchDefaultIf_matched_fuel` for optional generated defaults, + `exec_block_append_ok`, + `exec_nativeSwitchCaseIfs_with_default_matched_fuel`, + `exec_nativeSwitchCaseIfs_find_hit_with_default_preserved_fuel`, + `exec_nativeSwitchCaseIfs_find_none_with_default_nonempty_fuel`, and + `exec_nativeSwitchCaseIfs_find_none_without_default_fuel` for complete + generated case-chain plus optional-default execution, + `exec_lowerNativeSwitchBlock_selector_find_hit_preserved_fuel`, + `exec_lowerNativeSwitchBlock_selector_find_none_with_default_nonempty_fuel`, + and `exec_lowerNativeSwitchBlock_selector_find_none_without_default_fuel` + for raw lowered switch-block execution from the native initial state, + and companion native `exec`/primitive reduction lemmas for the lazy guarded switch case/default gates, + the native-switch write-target collectors and freshness predicates + `yulStmtsWriteNames`, `nativeStmtsWriteNames`, + `nativeSwitchTempsFreshForSourceBodies`, and + `nativeSwitchTempsFreshForNativeBodies`, plus freshness projection lemmas + for selected native bodies and optional defaults, + plus the named + `bridgedExpr_selectorExpr` and + `evalYulExprWithBackend_evmYulLean_selectorExpr_semantics` lemmas for the + generated dispatcher selector expression on the interpreter-oracle side, the named + `initialState_unbridgedEnvironmentDefaults` lemma for + base-fee/blob-field defaults and native-global `chainid` behavior, callvalue, + caller/address, calldatasize, timestamp/number, a native-vs-reference-oracle + runtime comparison for those bridged environment fields, selected-path + fail-closed `chainid`/`blobbasefee` validation for non-representable + `YulTransaction.chainId`/`YulTransaction.blobBaseFee`, executable `stop` halt + projection, the named `projectHaltReturn_stop` and `projectResult_stop` + lemmas for `stop`/zero-halt return projection, native log projection from + topics plus word-aligned data with the named + `projectLogEntry_topicsAndWordData` and `projectLogsFromState_logSeries` + lemmas, successful native value result projection with committed + storage/logs and matching `finalMappings`, the named + `projectResult_ok` and `projectResult_ok_events` lemmas for successful + native value results and event-history append behavior, native return + halt projection with committed storage/logs and matching `finalMappings`, + the named `projectResult_ok_success`, `projectResult_ok_returnValue`, and + `projectResult_ok_finalMappings` lemmas for successful value-result + projection, the named + `projectHaltReturn_32ByteReturn`, `projectResult_yulHalt`, + `projectResult_yulHalt_success`, `projectResult_yulHalt_returnValue`, + `projectResult_yulHalt_finalMappings`, and `projectResult_yulHalt_events` + lemmas for halt success, return-value, final-mapping, and event-history + append behavior, and the named `projectResult_32ByteReturn` lemma for + 32-byte `return` halt projection, the named + `projectHaltReturn_non32ByteReturn` and `projectResult_non32ByteReturn` + lemmas pinning the current conservative non-word-sized return-buffer + fallback until wider returndata support lands, log projection for `log0` + through `log4` topic arities, conservative rollback projection for native errors, + explicit hard-error rollback for `OutOfFuel`, explicit `Revert` rollback + projection with no return value, the named `projectResult_revert` rollback, + `projectResult_revert_success`, `projectResult_revert_returnValue`, and + `projectResult_revert_finalMappings` final-mapping rollback and + `projectResult_revert_events` event-history preservation lemmas, the named + `projectResult_hardError_success` and `projectResult_hardError_returnValue` + lemmas for hard native failure observables, the named + `projectResult_ok_finalStorageSlot` and + `projectResult_yulHalt_finalStorageSlot` lemmas for committed final-storage + slot projection, the named `projectResult_ok_missingFinalStorageSlot` and + `projectResult_yulHalt_missingFinalStorageSlot` lemmas for present-account + missing-slot default projection, the named + `projectResult_ok_missingFinalStorageAccountSlot` and + `projectResult_yulHalt_missingFinalStorageAccountSlot` lemmas for + absent-account final-storage default projection, the named + `projectResult_revert_finalStorageSlot` and + `projectResult_hardError_finalStorageSlot` lemmas for rollback final-storage + slot projection, + conservative `finalMappings` rollback on native errors with the named + `projectResult_hardError_finalMappings` lemma, the named + `projectResult_hardError` and `projectResult_hardError_events` lemmas for + every non-halt native error, the named `projectResult_finalMappings` lemma + showing every native projection keeps `finalMappings` synchronized with + `finalStorage`, named `interpretRuntimeNative_loweringError` and + `interpretRuntimeNative_eq_callDispatcher_of_lowerRuntimeContractNative` + lemmas for fail-closed lowering and the successful lower/build/call/project + native execution pipeline, `interpretIRRuntimeNative` + forwarding/fail-closed lowering behavior, executable native-vs-reference + oracle coverage for actual `Compiler.emitYul` dispatcher selection with + explicit expected success/return/storage outcomes, + ABI argument-word decoding, observable storage reads, generated + `mappingSlot` helper execution for singleton mapping writes, singleton + mapping reads, nested mapping writes, and `CompilationModel`-compiled + packed mapping-struct member reads/writes plus singleton and nested + multi-word mapping-struct member reads/writes, 32-byte return halt projection, + multi-word memory-return fallback projection, and memory-backed revert rollback, and named + `interpretIRRuntimeNative_loweringError` and + `interpretIRRuntimeNative_eq_callDispatcher_of_lowerRuntimeContractNative` + lemmas pinning the same fail-closed and exact native call-dispatcher + pipeline at the IR entry point. Next coverage should include: + - returndata and external-call outcomes, + - static-call permission behavior, + - proof-level preservation for the covered mapping member oracle cases. + +5. Introduce the public native preservation theorem. + + The EndToEnd module now has a named native theorem seam: + + ```lean + layers2_3_ir_matches_native_evmYulLean_of_interpreter_bridge + ``` + + It targets `Native.interpretIRRuntimeNative` directly, but only under: + + ```lean + nativeIRRuntimeAgreesWithInterpreter fuel irContract tx initialState + observableSlots + ``` + + The next theorem in that chain is: + + ```lean + layers2_3_ir_matches_native_evmYulLean_of_lowered_callDispatcher_bridge + ``` + + It replaces the opaque bridge hypothesis with successful + `lowerRuntimeContractNative`, successful + `validateNativeRuntimeEnvironment`, and + `nativeCallDispatcherAgreesWithInterpreter` for the lowered native contract. + That obligation can now be discharged from + `nativeDispatcherBlockAgreesWithInterpreter`, which compares projected + `contractDispatcherBlockResult` execution with the interpreter oracle. + The block obligation can in turn be discharged from + `nativeDispatcherExecAgreesWithInterpreter`, which targets raw + `contractDispatcherExecResult`. + + This makes the remaining proof obligation concrete: for the supported + generated fragment, native `lowerRuntimeContractNative` plus + `EvmYul.Yul.exec` of the lowered contract dispatcher block must produce the + same projected `YulResult` as the current + `interpretYulRuntimeWithBackend .evmYulLean` interpreter oracle. The + successor theorem should discharge that bridge, or target a total native + wrapper once the remaining closed-failure cases are ruled out by syntactic + invariants. + + A clean intermediate theorem is: + + ```lean + interpretYulRuntimeWithBackend .evmYulLean emittedRuntime + = + interpretRuntimeNative fuel emittedRuntime ... + ``` + + for the safe generated fragment. Once that bridge is proved, retarget the + Layer 3 and EndToEnd statements directly to the native execution target. + +6. Flip the trust boundary only after the theorem target moves. + + Documentation should say EVMYulLean is the authoritative semantic target + only after the public theorem no longer routes through + `execYulFuelWithBackend`. Until then, the accurate status is: + EVMYulLean-backed builtin bridge proven, native runtime harness executable, + native public theorem pending. + +## Cleanup After the Flip + +- Move `execYulFuel` and `execYulFuelWithBackend` to reference-oracle status. +- Remove bridge-only docs that describe the custom interpreter as the active + semantic target. +- Keep cross-check tests between the old oracle and native EVMYulLean for one + release cycle. +- Upstream any EVMYulLean fork changes needed for memory, returndata, logs, or + external-call semantics. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 3c470c893..e9897291f 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -76,6 +76,7 @@ Recent progress for storage layout controls (`#623`): - `CompilationModel` now supports declarative reserved storage slot ranges (`reservedSlotRanges := [{ start := a, end_ := b }, ...]`) with compile-time overlap checks and fail-fast diagnostics when field canonical/alias write slots intersect reserved intervals. - `CompilationModel.Field` now supports packed subfield placement (`packedBits := some { offset := o, width := w }`) so multiple fields can share a slot with disjoint bit ranges; codegen performs masked read-modify-write updates and masked reads directly from layout metadata. - `FieldType.mappingStruct` / `FieldType.mappingStruct2` plus `Expr.structMember` / `Stmt.setStructMember` now make struct-valued mappings and packed submembers first-class in the CompilationModel surface, and `verity_contract` now exposes matching `MappingStruct(...)` / `MappingStruct2(...)` storage declarations so Morpho-style layouts no longer require handwritten CompilationModel shims. +- Migration-faithful packed-slot writes can now be expressed without unsafe raw Yul: build the packed word with first-class word operations and call `setPackedStorage root offset word`, which lowers through `Stmt.setStorageWord` to an explicit full-word write at `root.slot + offset`. Recent progress for low-level calls + returndata handling (`#622`): - `CompilationModel.Expr` now supports first-class low-level call primitives (`call`, `staticcall`, `delegatecall`) with explicit gas/value/target/input/output operands and deterministic Yul lowering. @@ -83,6 +84,9 @@ Recent progress for low-level calls + returndata handling (`#622`): - `CompilationModel.Expr.returndataOptionalBoolAt(outOffset)` now provides a first-class ERC20 compatibility helper for optional return-data bool decoding (`returndatasize()==0 || (returndatasize()==32 && mload(outOffset)==1)`), so low-level token call acceptance paths can be expressed without raw Yul builtins. - `verity-compiler --trust-report ` now emits a machine-readable per-contract trust surface covering low-level mechanics usage, raw `rawLog` event emission, linked external assumptions, ECM axioms, explicit `proved` / `assumed` / `unchecked` proof-status buckets for foreign surfaces, constructor/function-level `usageSites`, site-localized `localObligations` for unsafe/refinement escape hatches, and dedicated `notModeledEventEmission` / `notModeledProxyUpgradeability` / `partiallyModeledLinearMemoryMechanics` / `partiallyModeledRuntimeIntrospection` slices for the current event, proxy/upgradeability, memory/ABI, and runtime-context proof gaps; `--verbose` now includes matching human-readable summaries, `--deny-unchecked-dependencies` upgrades unchecked foreign usage from a warning to a fail-closed verification gate with site-localized diagnostics, `--deny-assumed-dependencies` provides a proof-strict gate for any remaining assumed or unchecked foreign dependency, `--deny-axiomatized-primitives` fails closed on contracts that still rely on axiomatized primitives such as `keccak256`, `--deny-local-obligations` fails closed on any undischarged local unsafe/refinement obligation, `--deny-linear-memory-mechanics` fails closed on contracts that still rely on partially modeled linear-memory mechanics, `--deny-event-emission` fails closed on raw `rawLog` event emission, `--deny-low-level-mechanics` fails closed on first-class low-level call / returndata usage, `--deny-proxy-upgradeability` fails closed on `delegatecall`-based proxy / upgradeability mechanics tracked under issue `#1420`, and `--deny-runtime-introspection` does the same for partially modeled runtime-introspection primitives. - Raw `Expr.externalCall` interop names for low-level/builtin opcodes remain fail-fast rejected, preserving explicit migration diagnostics while the first-class surface continues to expand. + +Recent progress for dynamic ABI-shaped parameters: +- `verity_contract` now accepts dynamic array parameters whose element type is a static tuple of ABI words, e.g. `Array (Tuple [Uint256, Uint256, Int256])`, on tuple destructuring and tuple-return `arrayElement` paths. Those paths lower to checked word reads with the tuple element stride, which covers Solidity memory arrays of small fixed-size structs such as `CurveCut[]`; plain scalar `arrayElement` remains limited to single-word static element arrays. - ABI artifact emission now reflects explicit function mutability markers (`isView`, `isPure`) as `stateMutability: "view" | "pure"` in generated JSON. Recent progress for custom errors (`#586`): @@ -180,7 +184,7 @@ Execution priorities: | # | Component | Approach | Effort | Status | |---|-----------|----------|--------|--------| | 1 | ~~Function Selectors~~ | keccak256 axiom + CI | — | ✅ **DONE** (PR #43, #46) | -| 2 | Yul/EVM Semantics Bridge | EVMYulLean integration | 1-3m | 🟢 **PHASE 4 COMPLETE** | +| 2 | Yul/EVM Semantics Bridge | EVMYulLean integration | 1-3m | 🟡 **BRIDGE COMPLETE, NATIVE TARGET PENDING** | | 3 | EVM Semantics | Strong testing + documented assumption | Ongoing | ⚪ TODO | **Yul/EVM Semantics Bridge** (Issue [#1722](https://github.com/lfglabs-dev/verity/issues/1722)): EVMYulLean (NethermindEth) provides formally-defined Yul AST types and UInt256 operations. Current integration status: @@ -191,6 +195,7 @@ Execution priorities: - Unbridged: none; `mappingSlot` is bridged via the shared keccak-faithful `abstractMappingSlot` derivation - Phase 2 state bridge scaffolding: type conversions, storage round-trip, env field bridges (0 sorry) - **Phase 4 (safe-body EndToEnd retarget)**: `EvmYulLeanRetarget.lean` proves `backends_agree_on_bridged_builtins` and `evalYulExpr_evmYulLean_eq_on_bridged`, establishing that `.verity` and `.evmYulLean` agree for `BridgedExpr` expressions built from bridged builtin calls plus backend-independent `tload`/`mload`. It also defines `execYulFuelWithBackend`, proves `execYulFuelWithBackend_verity_eq`, proves straight-line/block/if/switch/for statement-fragment helpers, proves `execYulFuelWithBackend_eq_on_bridged_target` for recursive `BridgedTarget` values, proves `emitYul_runtimeCode_bridged`, proves `emitYul_runtimeCode_evmYulLean_eq_on_bridged_bodies`, and proves `yulCodegen_preserves_semantics_evmYulLean`: the existing Layer-3 IR→Yul preservation theorem retargeted to `interpretYulRuntimeWithBackend .evmYulLean` under bridged-body hypotheses. `EndToEnd.lean` now exposes `layers2_3_ir_matches_yul_evmYulLean` over that EVMYulLean-backed target without raw external function-body `BridgedStmts` hypotheses, deriving them from `SupportedSpec`, static-parameter witnesses, and `BridgedSafeStmts` source-body witnesses. `EvmYulLeanBodyClosure.lean` proves universal `compileStmtList_always_bridged` coverage for `BridgedSafeStmts`; the external-call family remains carved out behind explicit function-table simulation work. `EvmYulLeanSourceExprClosure.lean` proves scalar leaf closure (`compileExpr_bridgedSource_leaf`) and pure arithmetic/comparison/bit-operation expression closure (`compileExpr_bridgedSource`) for the `BridgedSourceExpr` fragment. Trust boundary shifts for bridged expressions, recursive bridged statement targets, Layer-3 contract executions, and the safe-body EndToEnd wrapper with fully proven builtin dependencies. +- **Native runtime follow-up (#1737)**: the public theorem target still routes through the backend-parameterized Verity Yul interpreter. `EvmYulLeanNativeHarness.lean` provides the executable native path through `EvmYul.Yul.callDispatcher`, and `docs/NATIVE_EVMYULLEAN_TRANSITION.md` tracks the remaining state/result bridge lemmas and theorem flip needed to remove the custom interpreter from the public semantic target. - **Remaining to make retargeting whole-program**: extend `BridgedSafeStmts` or add a separate function-table simulation for the external-call family (`internalCall`, `internalCallAssign`, `externalCallBind`, and `ecm`) **EVM Semantics**: Mitigated by differential testing against actual EVM execution (Foundry). Likely remains a documented fundamental assumption. diff --git a/docs/VERIFICATION_STATUS.md b/docs/VERIFICATION_STATUS.md index 4ad8d5c88..37cfc7b54 100644 --- a/docs/VERIFICATION_STATUS.md +++ b/docs/VERIFICATION_STATUS.md @@ -145,10 +145,13 @@ The retargeting module [`EvmYulLeanRetarget.lean`](../Compiler/Proofs/YulGenerat The backend-parameterized executor now has a proved `.verity = .evmYulLean` theorem for recursive statement targets constrained by `BridgedTarget`, the generated runtime wrapper is proved to preserve that predicate and to execute equivalently under explicit body-closure hypotheses, Layer 3 now has a contract-level theorem whose Yul side is `interpretYulRuntimeWithBackend .evmYulLean`, and the public EndToEnd module exposes a safe-body wrapper over that target. Body closure now has a universal safe-body aggregation theorem for `BridgedSafeStmts`, and the public EndToEnd theorem uses that closure to discharge raw `BridgedStmts` body hypotheses for supported compiler-produced contracts while keeping the external-call/function-table family carved out where needed. -Trust boundary (safe-body EndToEnd target): `BridgedExpr` expressions, `BridgedStraightStmts` statement lists, recursively nested `BridgedTarget` executions, source statement lists admitted by `BridgedSafeStmts`, emitted runtime wrappers whose embedded bodies are bridged, and public EndToEnd wrappers deriving those body bridges from source-level safe-body/static-param witnesses now inherit EVMYulLean semantics with fully proven builtin bridge dependencies — "EVMYulLean's execution model matches the EVM" (backed by upstream Ethereum conformance tests) — rather than "Verity's custom builtin implementations are correct." +Native-runtime transition status: the current theorem target is still the backend-parameterized Verity statement interpreter. The executable native EVMYulLean path lives in [`EvmYulLeanNativeHarness.lean`](../Compiler/Proofs/YulGeneration/Backends/EvmYulLeanNativeHarness.lean), and the remaining theorem-transition plan is tracked in [`NATIVE_EVMYULLEAN_TRANSITION.md`](NATIVE_EVMYULLEAN_TRANSITION.md). + +Trust boundary (safe-body EndToEnd target): `BridgedExpr` expressions, `BridgedStraightStmts` statement lists, recursively nested `BridgedTarget` executions, source statement lists admitted by `BridgedSafeStmts`, emitted runtime wrappers whose embedded bodies are bridged, and public EndToEnd wrappers deriving those body bridges from source-level safe-body/static-param witnesses now inherit EVMYulLean builtin semantics with fully proven bridge dependencies. The stronger claim that native `EvmYul.Yul.callDispatcher` is the public semantic target remains pending until the native theorem path replaces `execYulFuelWithBackend`. Not yet proven in this module: - external-call/function-table body closure beyond the current `BridgedSafeStmts` whitelist +- native `EvmYul.Yul.callDispatcher` preservation for emitted runtime Yul Remaining gaps for whole-program retargeting: - 0 sorry-backed core equivalences diff --git a/lake-manifest.json b/lake-manifest.json index adbc612b9..d547f09ed 100644 --- a/lake-manifest.json +++ b/lake-manifest.json @@ -5,10 +5,10 @@ "type": "git", "subDir": null, "scope": "", - "rev": "7b54b8f38bb68ee930d00d39c1b11dd60fb123c8", + "rev": "b353c7583ea36e49dbbffd57f5b25f4d01226e15", "name": "evmyul", "manifestFile": "lake-manifest.json", - "inputRev": "7b54b8f38bb68ee930d00d39c1b11dd60fb123c8", + "inputRev": "b353c7583ea36e49dbbffd57f5b25f4d01226e15", "inherited": false, "configFile": "lakefile.lean"}, {"url": "https://github.com/leanprover-community/mathlib4.git", diff --git a/lakefile.lean b/lakefile.lean index 6916c6f7d..4333ea68e 100644 --- a/lakefile.lean +++ b/lakefile.lean @@ -5,7 +5,7 @@ package «verity» where version := v!"1.0.0" require evmyul from git - "https://github.com/lfglabs-dev/EVMYulLean.git"@"7b54b8f38bb68ee930d00d39c1b11dd60fb123c8" + "https://github.com/lfglabs-dev/EVMYulLean.git"@"b353c7583ea36e49dbbffd57f5b25f4d01226e15" @[default_target] lean_lib «Verity» where @@ -70,3 +70,6 @@ lean_exe «macro-roundtrip-fuzz» where lean_exe «compiler-main-test» where root := `Compiler.MainTestRunner + +lean_exe «native-dispatch-oracle-test» where + root := `Compiler.Proofs.YulGeneration.Backends.EvmYulLeanNativeDispatchOracleTest diff --git a/packages/verity-compiler/lake-manifest.json b/packages/verity-compiler/lake-manifest.json index 504cd579a..8b85cfbf9 100644 --- a/packages/verity-compiler/lake-manifest.json +++ b/packages/verity-compiler/lake-manifest.json @@ -12,10 +12,10 @@ "type": "git", "subDir": null, "scope": "", - "rev": "7b54b8f38bb68ee930d00d39c1b11dd60fb123c8", + "rev": "b353c7583ea36e49dbbffd57f5b25f4d01226e15", "name": "evmyul", "manifestFile": "lake-manifest.json", - "inputRev": "7b54b8f38bb68ee930d00d39c1b11dd60fb123c8", + "inputRev": "b353c7583ea36e49dbbffd57f5b25f4d01226e15", "inherited": true, "configFile": "lakefile.lean"}, {"url": "https://github.com/leanprover-community/mathlib4.git", diff --git a/packages/verity-edsl/lake-manifest.json b/packages/verity-edsl/lake-manifest.json index ce247ec7f..088dd0e7e 100644 --- a/packages/verity-edsl/lake-manifest.json +++ b/packages/verity-edsl/lake-manifest.json @@ -5,10 +5,10 @@ "type": "git", "subDir": null, "scope": "", - "rev": "7b54b8f38bb68ee930d00d39c1b11dd60fb123c8", + "rev": "b353c7583ea36e49dbbffd57f5b25f4d01226e15", "name": "evmyul", "manifestFile": "lake-manifest.json", - "inputRev": "7b54b8f38bb68ee930d00d39c1b11dd60fb123c8", + "inputRev": "b353c7583ea36e49dbbffd57f5b25f4d01226e15", "inherited": false, "configFile": "lakefile.lean"}, {"url": "https://github.com/leanprover-community/mathlib4.git", diff --git a/packages/verity-edsl/lakefile.lean b/packages/verity-edsl/lakefile.lean index 87179e23f..ea69d8015 100644 --- a/packages/verity-edsl/lakefile.lean +++ b/packages/verity-edsl/lakefile.lean @@ -5,7 +5,7 @@ package «verity-edsl» where version := v!"1.0.0" require evmyul from git - "https://github.com/lfglabs-dev/EVMYulLean.git"@"7b54b8f38bb68ee930d00d39c1b11dd60fb123c8" + "https://github.com/lfglabs-dev/EVMYulLean.git"@"b353c7583ea36e49dbbffd57f5b25f4d01226e15" lean_lib «Verity» where srcDir := "../.." diff --git a/packages/verity-examples/lake-manifest.json b/packages/verity-examples/lake-manifest.json index 156272658..ddd4ba2b7 100644 --- a/packages/verity-examples/lake-manifest.json +++ b/packages/verity-examples/lake-manifest.json @@ -19,10 +19,10 @@ "type": "git", "subDir": null, "scope": "", - "rev": "7b54b8f38bb68ee930d00d39c1b11dd60fb123c8", + "rev": "b353c7583ea36e49dbbffd57f5b25f4d01226e15", "name": "evmyul", "manifestFile": "lake-manifest.json", - "inputRev": "7b54b8f38bb68ee930d00d39c1b11dd60fb123c8", + "inputRev": "b353c7583ea36e49dbbffd57f5b25f4d01226e15", "inherited": true, "configFile": "lakefile.lean"}, {"url": "https://github.com/leanprover-community/mathlib4.git", diff --git a/scripts/check_native_transition_doc.py b/scripts/check_native_transition_doc.py new file mode 100644 index 000000000..4b75e5b68 --- /dev/null +++ b/scripts/check_native_transition_doc.py @@ -0,0 +1,341 @@ +#!/usr/bin/env python3 +"""Keep the native EVMYulLean transition document honest. + +PR #1743 deliberately introduces an executable native harness without moving the +public theorem target. This checker prevents the transition note from losing the +explicit blocker list or overstating native EVMYulLean as the current public +semantic target. +""" + +from __future__ import annotations + +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +DOC = ROOT / "docs" / "NATIVE_EVMYULLEAN_TRANSITION.md" +END_TO_END = ROOT / "Compiler" / "Proofs" / "EndToEnd.lean" +NATIVE_HARNESS = ( + ROOT + / "Compiler" + / "Proofs" + / "YulGeneration" + / "Backends" + / "EvmYulLeanNativeHarness.lean" +) +RETARGET = ( + ROOT + / "Compiler" + / "Proofs" + / "YulGeneration" + / "Backends" + / "EvmYulLeanRetarget.lean" +) +NATIVE_ADAPTER = ( + ROOT + / "Compiler" + / "Proofs" + / "YulGeneration" + / "Backends" + / "EvmYulLeanAdapter.lean" +) +NATIVE_SMOKE_TEST = ( + ROOT + / "Compiler" + / "Proofs" + / "YulGeneration" + / "Backends" + / "EvmYulLeanNativeSmokeTest.lean" +) + +REQUIRED_SNIPPETS = ( + "interpretYulRuntimeWithBackend .evmYulLean", + "Verity's custom fuel-based Yul statement interpreter", + "not the final architecture", + "Native.interpretRuntimeNative", + "Native.interpretIRRuntimeNative", + "EvmYul.Yul.callDispatcher", + "observable storage slot set explicitly", + "only materializes pre-state storage for those slots", + "layers2_3_ir_matches_native_evmYulLean_of_interpreter_bridge", + "nativeIRRuntimeAgreesWithInterpreter", + "nativeResultsMatchOn", + "nativeCallDispatcherAgreesWithInterpreter", + "layers2_3_ir_matches_native_evmYulLean_of_lowered_callDispatcher_bridge", + "explicitly observable final-storage slots", + "full-storage-projection", + "same explicit fuel", + "default runtime fuel", + "native public theorem pending", + "not yet proved", + "#1741", + "#1738", + "#1742", + "`blockTimestamp`", + "mapping-struct", + "signature-based identity model", + "`YulTransaction.chainId` must match", + "EvmYul.chainId", + "`chainid()` and `blobbasefee()` now fail closed on the selected native runtime path", + "EvmYul.MIN_BASE_FEE_PER_BLOB_GAS", + "`initialState_unbridgedEnvironmentDefaults`", +) + +FORBIDDEN_SNIPPETS = ( + "native EVMYulLean is the authoritative semantic target today", + "native EVMYulLean is now the authoritative semantic target", + "public theorem targets `interpretIRRuntimeNative`", + "public theorem target is `interpretIRRuntimeNative`", + "custom Yul interpreter is only a regression oracle", +) + + +def normalize_ws(text: str) -> str: + return " ".join(text.split()) + + +def check_doc(text: str) -> list[str]: + normalized = normalize_ws(text) + errors: list[str] = [] + + for snippet in REQUIRED_SNIPPETS: + if normalize_ws(snippet) not in normalized: + errors.append( + f"{DOC.relative_to(ROOT)} is missing required native-transition status text: `{snippet}`" + ) + + normalized_lower = normalized.lower() + for snippet in FORBIDDEN_SNIPPETS: + if normalize_ws(snippet).lower() in normalized_lower: + errors.append( + f"{DOC.relative_to(ROOT)} overstates the current native-transition status: `{snippet}`" + ) + + if "#1741" in normalized: + issue_1741 = normalized.index("#1741") + issue_1738 = normalized.find("#1738", issue_1741) + issue_1742 = normalized.find("#1742", issue_1738 if issue_1738 >= 0 else issue_1741) + if issue_1738 < 0 or issue_1742 < 0: + errors.append( + f"{DOC.relative_to(ROOT)} must list blockers #1741, #1738, and #1742 together" + ) + + return errors + + +def check_public_theorem_target( + end_to_end_text: str, native_harness_text: str, retarget_text: str +) -> list[str]: + """Pin the current transition boundary until the native theorem flips. + + This guard should be updated in the same PR that proves the native + preservation theorem and retargets the public EndToEnd path. Until then, + the public theorem must still visibly target the backend-parameterized + interpreter, while the native harness remains an executable side path. + """ + + errors: list[str] = [] + normalized_end_to_end = normalize_ws(end_to_end_text) + normalized_native_harness = normalize_ws(native_harness_text) + normalized_retarget = normalize_ws(retarget_text) + + if "interpretYulRuntimeWithBackend .evmYulLean" not in normalized_end_to_end: + errors.append( + "Compiler/Proofs/EndToEnd.lean must still expose the current " + "`interpretYulRuntimeWithBackend .evmYulLean` public theorem target " + "until the native preservation theorem is proved and this guard is updated" + ) + + for required_native_seam in ( + "def nativeResultsMatch", + "def yulResultsAgreeOn", + "def nativeResultsMatchOn", + "def nativeIRRuntimeAgreesWithInterpreter", + "def nativeCallDispatcherAgreesWithInterpreter", + "theorem nativeIRRuntimeAgreesWithInterpreter_of_lowered_callDispatcher_agree", + "interpretYulRuntimeWithBackendFuel .evmYulLean fuel", + "hFuel : fuel = sizeOf (Compiler.emitYul contract).runtimeCode + 1", + "theorem layer3_contract_preserves_semantics_native_of_interpreter_bridge", + "theorem layer3_contract_preserves_semantics_native_of_lowered_callDispatcher_bridge", + "theorem layers2_3_ir_matches_native_evmYulLean_of_interpreter_bridge", + "theorem layers2_3_ir_matches_native_evmYulLean_of_lowered_callDispatcher_bridge", + ): + if required_native_seam not in normalized_end_to_end: + errors.append( + "Compiler/Proofs/EndToEnd.lean must keep the native theorem seam " + f"`{required_native_seam}` explicit until the generated-fragment " + "native bridge is discharged" + ) + + for required_fuel_surface in ( + "def interpretYulRuntimeWithBackendFuel", + "theorem interpretYulRuntimeWithBackend_eq_fuel", + ): + if required_fuel_surface not in normalized_retarget: + errors.append( + "Compiler/Proofs/YulGeneration/Backends/" + "EvmYulLeanRetarget.lean must keep the fuel-aligned " + f"interpreter oracle surface `{required_fuel_surface}` explicit" + ) + + forbidden_native_in_end_to_end = ( + "theorem target: interpretRuntimeNative", + "theorem target: EvmYul.Yul.callDispatcher", + ) + for native_target in forbidden_native_in_end_to_end: + if native_target in normalized_end_to_end: + errors.append( + "Compiler/Proofs/EndToEnd.lean should target the native IR " + "runtime seam, not the lower-level harness implementation " + f"`{native_target.removeprefix('theorem target: ')}`" + ) + + for required_native_entrypoint in ( + "def interpretRuntimeNative", + "def interpretIRRuntimeNative", + "EvmYul.Yul.callDispatcher", + ): + if required_native_entrypoint not in normalized_native_harness: + errors.append( + "Compiler/Proofs/YulGeneration/Backends/" + "EvmYulLeanNativeHarness.lean is missing native harness surface " + f"`{required_native_entrypoint}`" + ) + + return errors + + +def check_native_switch_lowering_boundary(native_adapter_text: str, native_smoke_text: str) -> list[str]: + """Keep native switch lowering fresh and regression-tested.""" + + errors: list[str] = [] + normalized_adapter = normalize_ws(native_adapter_text) + normalized_smoke = normalize_ws(native_smoke_text) + + for required_boundary in ( + "freshNativeSwitchId", + "nativeSwitchDiscrTempName", + "nativeSwitchMatchedTempName", + "yulStmtsIdentifierNames", + ): + if required_boundary not in normalized_adapter: + errors.append( + "Compiler/Proofs/YulGeneration/Backends/" + "EvmYulLeanAdapter.lean must keep native switch temporary " + f"freshness explicit with `{required_boundary}`" + ) + + for required_smoke in ( + "nativeSwitchTempNamesAvoidUserNames = true", + "nativeFunctionSwitchTempNamesAvoidLocalUserNames = true", + "nativeSwitchExecutesOnlyFirstMatchingNonHaltingCase = true", + ): + if required_smoke not in normalized_smoke: + errors.append( + "Compiler/Proofs/YulGeneration/Backends/" + "EvmYulLeanNativeSmokeTest.lean must pin native switch " + f"lowering behavior with `{required_smoke}`" + ) + + return errors + + +def check_unbridged_environment_boundary(native_harness_text: str, native_smoke_text: str) -> list[str]: + """Keep the native environment-read limitation explicit and tested. + + EVMYulLean currently evaluates `CHAINID` from its own global constant, and + `BLOBBASEFEE` from the block-header blob gas price formula. The native + harness does not yet derive those fields from Verity's `YulTransaction`. + Until that bridge is widened, the transition must keep both the named lemma + and executable smoke expectations for the current default behavior. + """ + + errors: list[str] = [] + normalized_native_harness = normalize_ws(native_harness_text) + normalized_native_smoke = normalize_ws(native_smoke_text) + + for required_boundary in ( + "validateNativeRuntimeEnvironment", + "nativeRuntimePathUsesBuiltin", + "yulStmtsUseBuiltinOnNativeRuntimePath", + "selectedSwitchBody", + "nativeChainIdRepresentable", + "nativeBlobBaseFeeRepresentable", + "unsupportedNativeHeaderBuiltinNames", + "nativeRuntimePathUsesUnsupportedHeaderBuiltin", + "unsupportedNativeHeaderBuiltinError", + "initialState_unbridgedEnvironmentDefaults", + "EvmYul.State.chainId", + "EvmYul.chainId", + "header.blobGasUsed", + "header.excessBlobGas", + ): + if required_boundary not in normalized_native_harness: + errors.append( + "Compiler/Proofs/YulGeneration/Backends/" + "EvmYulLeanNativeHarness.lean must keep the unbridged " + f"environment boundary explicit with `{required_boundary}`" + ) + + for pinned_default in ( + 'nativeRejectsUnsupportedChainId = true', + 'nativeStoresBuiltinWithTx "chainid" 15 EvmYul.chainId', + 'nativeRejectsUnsupportedBlobBaseFee = true', + 'nativeStoresBuiltinWithTx "blobbasefee" 16 EvmYul.MIN_BASE_FEE_PER_BLOB_GAS', + 'nativeRejectsUnsupportedHeaderBuiltin "coinbase" = true', + 'nativeRejectsUnsupportedHeaderBuiltin "gaslimit" = true', + 'nativeAllowsUnselectedUnsupportedEnvironmentBuiltin = true', + ): + if pinned_default not in normalized_native_smoke: + errors.append( + "Compiler/Proofs/YulGeneration/Backends/" + "EvmYulLeanNativeSmokeTest.lean must pin the current native " + f"environment behavior with `{pinned_default}` until " + "the blobbasefee bridge is widened" + ) + + return errors + + +def main() -> int: + if not DOC.exists(): + print(f"Missing: {DOC.relative_to(ROOT)}", file=sys.stderr) + return 1 + for path in (END_TO_END, NATIVE_HARNESS, RETARGET, NATIVE_ADAPTER, NATIVE_SMOKE_TEST): + if not path.exists(): + print(f"Missing: {path.relative_to(ROOT)}", file=sys.stderr) + return 1 + + native_harness_text = NATIVE_HARNESS.read_text(encoding="utf-8") + native_smoke_text = NATIVE_SMOKE_TEST.read_text(encoding="utf-8") + errors = check_doc(DOC.read_text(encoding="utf-8")) + errors.extend( + check_public_theorem_target( + END_TO_END.read_text(encoding="utf-8"), + native_harness_text, + RETARGET.read_text(encoding="utf-8"), + ) + ) + errors.extend( + check_unbridged_environment_boundary( + native_harness_text, + native_smoke_text, + ) + ) + errors.extend( + check_native_switch_lowering_boundary( + NATIVE_ADAPTER.read_text(encoding="utf-8"), + native_smoke_text, + ) + ) + if errors: + for error in errors: + print(error, file=sys.stderr) + return 1 + + print("native EVMYulLean transition doc check passed") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/check_proof_length.py b/scripts/check_proof_length.py index 56455090e..442f5557d 100644 --- a/scripts/check_proof_length.py +++ b/scripts/check_proof_length.py @@ -23,6 +23,7 @@ # Any new declaration keyword that ends the current proof span. _DECL_RE = re.compile( r"^\s*(?:@\[[^\]]*\]\s*)*(?:(?:private|protected|noncomputable|unsafe)\s+)*" + r"(?:(?:partial)\s+)?" r"(?:theorem|lemma|def|example|instance|inductive|structure|class|abbrev|opaque)\s+" ) _END_RE = re.compile(r"^\s*end\b") diff --git a/scripts/check_selectors.py b/scripts/check_selectors.py index 06a03e5bf..d1b5d38cd 100755 --- a/scripts/check_selectors.py +++ b/scripts/check_selectors.py @@ -11,7 +11,7 @@ 7) Selector shift constant sync between CompilationModel, Codegen, and Builtins. 8) Internal function prefix sync between CompilationModel and CI script. 9) Special entrypoint names sync between CompilationModel and CI script. -10) No duplicate function names per contract; compile has all five duplicate-name guards. +10) No duplicate function signatures per contract; compile has all five duplicate guards. 11) Free memory pointer constant matches Solidity convention (0x40). """ @@ -75,6 +75,7 @@ class SpecInfo: signatures: List[str] has_externals: bool = False all_function_names: List[str] = field(default_factory=list) + all_function_signatures: List[str] = field(default_factory=list) @dataclass @@ -133,7 +134,7 @@ def _extract_macro_spec(def_name: str, contract_name: str) -> SpecInfo: for fn_name, params, _ret in MACRO_FUNCTION_RE.findall(content): all_names.append(fn_name) signatures.append(f"{fn_name}({','.join(_split_macro_params(params))})") - return SpecInfo(def_name, contract_name, signatures, False, all_names) + return SpecInfo(def_name, contract_name, signatures, False, all_names, signatures) def _extract_filtered_macro_spec( @@ -157,6 +158,7 @@ def _extract_filtered_macro_spec( filtered_signatures, canonical.has_externals, filtered_names, + filtered_signatures, ) @@ -178,7 +180,8 @@ def extract_specs(text: str) -> List[SpecInfo]: signatures = extract_functions(block) has_externals = has_nonempty_externals(block) all_names = extract_all_function_names(block) - specs.append(SpecInfo(def_name, contract_name, signatures, has_externals, all_names)) + all_signatures = extract_all_function_signatures(block) + specs.append(SpecInfo(def_name, contract_name, signatures, has_externals, all_names, all_signatures)) seen_def_names.add(def_name) for def_name, contract_name in COMPILER_ALIAS_RE.findall(text): @@ -286,6 +289,11 @@ def extract_all_function_names(spec_block: str) -> List[str]: return [fn_name for fn_name, _ in _iter_function_blocks(spec_block)] +def extract_all_function_signatures(spec_block: str) -> List[str]: + """Extract ALL function signatures from a spec block (including internal/special).""" + return [_build_signature(fn_block, fn_name) for fn_name, fn_block in _iter_function_blocks(spec_block)] + + def _skip_ws(text: str, pos: int) -> int: while pos < len(text) and text[pos].isspace(): pos += 1 @@ -454,21 +462,22 @@ def check_reserved_prefix_collisions(specs: List[SpecInfo]) -> List[str]: return errors -def check_unique_function_names(specs: List[SpecInfo]) -> List[str]: - """Check that no contract has duplicate function names. +def check_unique_function_signatures(specs: List[SpecInfo]) -> List[str]: + """Check that no contract has duplicate external function signatures. - Mirrors the ``firstDuplicateName (spec.functions.map (·.name))`` check in - ``CompilationModel.compile``. + Mirrors the non-internal ``functionSignature`` check in + ``CompilationModel.compile``. Internal functions are validated separately by + source name because they compile into the internal Yul-function namespace. """ errors: List[str] = [] for spec in specs: seen: set[str] = set() - for name in spec.all_function_names: - if name in seen: + for signature in spec.signatures: + if signature in seen: errors.append( - f"{spec.contract_name}: duplicate function name '{name}'" + f"{spec.contract_name}: duplicate function signature '{signature}'" ) - seen.add(name) + seen.add(signature) return errors @@ -727,17 +736,36 @@ def check_free_memory_pointer_sync() -> List[str]: def check_compile_duplicate_name_guard() -> List[str]: - """Verify that validateCompileInputs checks duplicate names across all spec lists. + """Verify that validateCompileInputs checks duplicate names across spec lists. - Ensures the current dispatch validation calls ``firstDuplicateName`` on all five - spec collections: functions, errors, fields, events, and externals. + Function ABI signatures are only globally unique in the dispatch namespace, so + the guard must scan non-internal functions by ``functionSignature`` and scan + internal helpers separately by source name. The remaining declaration lists + keep direct name uniqueness checks. """ errors: List[str] = [] if not DISPATCH_FILE.exists(): errors.append(f"Missing {DISPATCH_FILE}") return errors text = DISPATCH_FILE.read_text(encoding="utf-8") - for collection in ("functions", "errors", "fields", "events", "externals"): + if not re.search( + r"firstDuplicateName\s*\(\(spec\.functions\.filter\s*" + r"\(fun\s+fn\s+=>\s*!fn\.isInternal\)\)\.map\s+functionSignature\)", + text, + ): + errors.append( + "Dispatch.validateCompileInputs: missing external duplicate function signature check " + "(expected firstDuplicateName ((spec.functions.filter (fun fn => !fn.isInternal)).map functionSignature))" + ) + if not re.search( + r"firstDuplicateName\s*\(\(spec\.functions\.filter\s*\(·\.isInternal\)\)\.map\s*\(·\.name\)\)", + text, + ): + errors.append( + "Dispatch.validateCompileInputs: missing internal duplicate function name check " + "(expected firstDuplicateName ((spec.functions.filter (·.isInternal)).map (·.name)))" + ) + for collection in ("errors", "fields", "events", "externals"): if not re.search( rf"firstDuplicateName\s*\(spec\.{collection}\.map", text ): @@ -785,7 +813,7 @@ def main(argv: list[str] | None = None) -> int: errors: List[str] = [] errors.extend(check_unique_selectors(specs)) - errors.extend(check_unique_function_names(specs)) + errors.extend(check_unique_function_signatures(specs)) errors.extend(check_reserved_prefix_collisions(specs)) errors.extend(check_compile_lists(specs, compile_lists)) @@ -812,7 +840,7 @@ def main(argv: list[str] | None = None) -> int: # Validate free memory pointer matches Solidity convention. errors.extend(check_free_memory_pointer_sync()) - # Validate compile function has all five duplicate-name guards. + # Validate compile function has all five duplicate guards. errors.extend(check_compile_duplicate_name_guard()) # Validate #check_contract computes selectors before invoking compile. diff --git a/scripts/generate_evmyullean_fork_audit.py b/scripts/generate_evmyullean_fork_audit.py index da959b40f..8827308c6 100644 --- a/scripts/generate_evmyullean_fork_audit.py +++ b/scripts/generate_evmyullean_fork_audit.py @@ -49,17 +49,19 @@ "schema_version": 1, "fork_url": "https://github.com/lfglabs-dev/EVMYulLean", "upstream_url": "https://github.com/NethermindEth/EVMYulLean", - "pinned_commit": "7b54b8f38bb68ee930d00d39c1b11dd60fb123c8", + "pinned_commit": "b353c7583ea36e49dbbffd57f5b25f4d01226e15", "upstream_base": "047f63070309f436b66c61e276ab3b6d1169265a", - "fork_ahead_by": 2, + "fork_ahead_by": 3, "fork_behind_by": 0, "divergence_summary": ( - "Fork is exactly 2 commits ahead of upstream/main. Both commits are " + "Fork is exactly 3 commits ahead of upstream/main. All commits are " "non-semantic: one visibility change (private -> default) on an " - "internal exponentiation accumulator, and one Lean 4.22.0 " - "deprecation fix (nativeLibDir -> staticLibDir) in the lakefile. " - "Neither commit changes EVM/Yul execution semantics, so upstream " - "Ethereum conformance test coverage continues to apply transitively." + "internal exponentiation accumulator, one Lean 4.22.0 deprecation " + "fix (nativeLibDir -> staticLibDir) in the lakefile, and one FFI " + "body exposure for ByteArray.zeroes that matches the extern zero-fill " + "behavior. None of these commits changes EVM/Yul execution semantics, " + "so upstream Ethereum conformance test coverage continues to apply " + "transitively." ), "commits": [ { @@ -112,6 +114,30 @@ "byte-identical up to path naming." ), }, + { + "sha": "b353c7583ea36e49dbbffd57f5b25f4d01226e15", + "title": "ffi: expose zero byte array body", + "file": "EvmYul/FFI/ffi.lean", + "category": "visibility", + "semantic_change": False, + "rationale": ( + "Replace the opaque `ByteArray.zeroes` extern declaration " + "with a kernel-visible Lean body using `Array.replicate`. " + "The attached extern name remains the same, preserving the " + "runtime FFI target while allowing downstream proofs to unfold " + "the size and byte contents of zero-filled padding." + ), + "diff_summary": ( + "1 file changed, 1 insertion(+), 1 deletion(-): replace one " + "opaque declaration with an equivalent Lean definition. No " + "behavior change." + ), + "trust_impact": ( + "Low. This makes the existing zero-fill behavior visible to " + "Lean's kernel for proofs; it does not alter Yul/EVM execution " + "semantics or the extern symbol used by compiled code." + ), + }, ], "audit_methodology": [ "1. Clone lfglabs-dev/EVMYulLean at pinned commit into a local worktree.", diff --git a/scripts/generate_macro_property_tests.py b/scripts/generate_macro_property_tests.py index 3a875bc72..54c2cba33 100644 --- a/scripts/generate_macro_property_tests.py +++ b/scripts/generate_macro_property_tests.py @@ -1916,21 +1916,22 @@ def _render_erc721_mint_assertion( if contract.storage_slots.get("owner") != 0: return None + to_name = fn.params[0].name body = list(fn.body) expected_body = [ "let sender ← msgSender", "let currentOwner ← getStorageAddr owner", 'require (sender == currentOwner) "Caller is not the owner"', - 'require (to != zeroAddress) "Invalid recipient"', + f'require ({to_name} != zeroAddress) "Invalid recipient"', "let tokenId ← getStorage nextTokenId", "let currentOwnerWord ← getMappingUint owners tokenId", 'require (currentOwnerWord == 0) "Token already minted"', - "let recipientBalance ← getMapping balances to", + f"let recipientBalance ← getMapping balances {to_name}", 'let newRecipientBalance ← requireSomeUint (safeAdd recipientBalance 1) "Balance overflow"', "let currentSupply ← getStorage totalSupply", 'let newSupply ← requireSomeUint (safeAdd currentSupply 1) "Supply overflow"', - "setMappingUintAddr owners tokenId to", - "setMapping balances to newRecipientBalance", + f"setMappingUintAddr owners tokenId {to_name}", + f"setMapping balances {to_name} newRecipientBalance", "setStorage totalSupply newSupply", "setStorage nextTokenId (add tokenId 1)", "return tokenId", @@ -1938,7 +1939,6 @@ def _render_erc721_mint_assertion( if body != expected_body: return None - to_name = fn.params[0].name owner_slot = contract.storage_slots.get("owner") total_supply_slot = contract.storage_slots.get("totalSupply") next_token_id_slot = contract.storage_slots.get("nextTokenId") diff --git a/scripts/test_check_native_transition_doc.py b/scripts/test_check_native_transition_doc.py new file mode 100644 index 000000000..f11cfe80f --- /dev/null +++ b/scripts/test_check_native_transition_doc.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import re +import unittest +from pathlib import Path +import sys + +sys.path.insert(0, str(Path(__file__).resolve().parent)) + +import check_native_transition_doc as check + + +class NativeTransitionDocCheckTests(unittest.TestCase): + def test_current_doc_passes(self) -> None: + text = check.DOC.read_text(encoding="utf-8") + self.assertEqual(check.check_doc(text), []) + + def test_rejects_missing_blocker_issue(self) -> None: + text = check.DOC.read_text(encoding="utf-8").replace("#1738", "#0000") + errors = check.check_doc(text) + self.assertTrue(any("#1738" in error for error in errors), errors) + + def test_rejects_missing_observable_slot_caveat(self) -> None: + text = check.DOC.read_text(encoding="utf-8").replace( + "observable storage slot set explicitly", + "observable storage slot set", + ) + errors = check.check_doc(text) + self.assertTrue(any("observable storage slot set explicitly" in error for error in errors), errors) + + def test_rejects_missing_chainid_validation_caveat(self) -> None: + text = check.DOC.read_text(encoding="utf-8").replace( + "`YulTransaction.chainId` must match", + "`YulTransaction.chainIdStatus`", + ) + errors = check.check_doc(text) + self.assertTrue( + any("YulTransaction.chainId" in error for error in errors), + errors, + ) + + def test_rejects_missing_blobbasefee_validation_caveat(self) -> None: + text = check.DOC.read_text(encoding="utf-8").replace( + "`chainid()` and `blobbasefee()` now fail closed on the selected native runtime", + "`YulTransaction.blobBaseFeeStatus`", + ) + errors = check.check_doc(text) + self.assertTrue( + any("blobbasefee" in error for error in errors), + errors, + ) + + def test_rejects_authoritative_native_claim(self) -> None: + text = ( + check.DOC.read_text(encoding="utf-8") + + "\nNative EVMYulLean is now the authoritative semantic target.\n" + ) + errors = check.check_doc(text) + self.assertTrue(any("overstates" in error for error in errors), errors) + + def test_public_theorem_target_guard_accepts_current_transition_shape(self) -> None: + errors = check.check_public_theorem_target( + check.END_TO_END.read_text(encoding="utf-8"), + check.NATIVE_HARNESS.read_text(encoding="utf-8"), + check.RETARGET.read_text(encoding="utf-8"), + ) + self.assertEqual(errors, []) + + def test_public_theorem_target_guard_rejects_missing_current_target(self) -> None: + end_to_end_text = re.sub( + r"interpretYulRuntimeWithBackend\s+\.evmYulLean", + "interpretYulRuntimeWithBackend .verity", + check.END_TO_END.read_text(encoding="utf-8"), + ) + errors = check.check_public_theorem_target( + end_to_end_text, + check.NATIVE_HARNESS.read_text(encoding="utf-8"), + check.RETARGET.read_text(encoding="utf-8"), + ) + self.assertTrue( + any("interpretYulRuntimeWithBackend .evmYulLean" in error for error in errors), + errors, + ) + + def test_public_theorem_target_guard_accepts_native_theorem_seam(self) -> None: + end_to_end_text = ( + check.END_TO_END.read_text(encoding="utf-8") + + "\n-- theorem target: interpretIRRuntimeNative\n" + ) + errors = check.check_public_theorem_target( + end_to_end_text, + check.NATIVE_HARNESS.read_text(encoding="utf-8"), + check.RETARGET.read_text(encoding="utf-8"), + ) + self.assertEqual(errors, []) + + def test_public_theorem_target_guard_rejects_missing_native_bridge_obligation(self) -> None: + end_to_end_text = check.END_TO_END.read_text(encoding="utf-8").replace( + "nativeIRRuntimeAgreesWithInterpreter", + "nativeRuntimeBridgeObligation", + ) + errors = check.check_public_theorem_target( + end_to_end_text, + check.NATIVE_HARNESS.read_text(encoding="utf-8"), + check.RETARGET.read_text(encoding="utf-8"), + ) + self.assertTrue( + any("nativeIRRuntimeAgreesWithInterpreter" in error for error in errors), + errors, + ) + + def test_public_theorem_target_guard_rejects_low_level_native_target(self) -> None: + end_to_end_text = ( + check.END_TO_END.read_text(encoding="utf-8") + + "\n-- theorem target: EvmYul.Yul.callDispatcher\n" + ) + errors = check.check_public_theorem_target( + end_to_end_text, + check.NATIVE_HARNESS.read_text(encoding="utf-8"), + check.RETARGET.read_text(encoding="utf-8"), + ) + self.assertTrue(any("callDispatcher" in error for error in errors), errors) + + def test_public_theorem_target_guard_rejects_missing_fuel_aligned_oracle(self) -> None: + retarget_text = check.RETARGET.read_text(encoding="utf-8").replace( + "interpretYulRuntimeWithBackendFuel", + "interpretYulRuntimeWithBackendDefaultFuel", + ) + errors = check.check_public_theorem_target( + check.END_TO_END.read_text(encoding="utf-8"), + check.NATIVE_HARNESS.read_text(encoding="utf-8"), + retarget_text, + ) + self.assertTrue( + any("interpretYulRuntimeWithBackendFuel" in error for error in errors), + errors, + ) + + def test_unbridged_environment_boundary_accepts_current_shape(self) -> None: + errors = check.check_unbridged_environment_boundary( + check.NATIVE_HARNESS.read_text(encoding="utf-8"), + check.NATIVE_SMOKE_TEST.read_text(encoding="utf-8"), + ) + self.assertEqual(errors, []) + + def test_unbridged_environment_boundary_rejects_missing_chainid_rejection_pin(self) -> None: + smoke_text = check.NATIVE_SMOKE_TEST.read_text(encoding="utf-8").replace( + 'nativeRejectsUnsupportedChainId = true', + 'nativeAcceptsUnsupportedChainId = true', + ) + errors = check.check_unbridged_environment_boundary( + check.NATIVE_HARNESS.read_text(encoding="utf-8"), + smoke_text, + ) + self.assertTrue(any("chainid" in error.lower() for error in errors), errors) + + def test_unbridged_environment_boundary_rejects_missing_blobbasefee_rejection_pin(self) -> None: + smoke_text = check.NATIVE_SMOKE_TEST.read_text(encoding="utf-8").replace( + 'nativeRejectsUnsupportedBlobBaseFee = true', + 'nativeAcceptsUnsupportedBlobBaseFee = true', + ) + errors = check.check_unbridged_environment_boundary( + check.NATIVE_HARNESS.read_text(encoding="utf-8"), + smoke_text, + ) + self.assertTrue(any("blobbasefee" in error for error in errors), errors) + + def test_native_switch_lowering_boundary_accepts_current_shape(self) -> None: + errors = check.check_native_switch_lowering_boundary( + check.NATIVE_ADAPTER.read_text(encoding="utf-8"), + check.NATIVE_SMOKE_TEST.read_text(encoding="utf-8"), + ) + self.assertEqual(errors, []) + + def test_native_switch_lowering_boundary_rejects_missing_freshness_helper(self) -> None: + adapter_text = check.NATIVE_ADAPTER.read_text(encoding="utf-8").replace( + "freshNativeSwitchId", + "staleNativeSwitchId", + ) + errors = check.check_native_switch_lowering_boundary( + adapter_text, + check.NATIVE_SMOKE_TEST.read_text(encoding="utf-8"), + ) + self.assertTrue(any("freshNativeSwitchId" in error for error in errors), errors) + + def test_native_switch_lowering_boundary_rejects_missing_collision_smoke(self) -> None: + smoke_text = check.NATIVE_SMOKE_TEST.read_text(encoding="utf-8").replace( + "nativeSwitchTempNamesAvoidUserNames = true", + "nativeSwitchTempNamesCollideWithUserNames = true", + ) + errors = check.check_native_switch_lowering_boundary( + check.NATIVE_ADAPTER.read_text(encoding="utf-8"), + smoke_text, + ) + self.assertTrue(any("nativeSwitchTempNamesAvoidUserNames" in error for error in errors), errors) + + def test_native_switch_lowering_boundary_rejects_missing_function_collision_smoke(self) -> None: + smoke_text = check.NATIVE_SMOKE_TEST.read_text(encoding="utf-8").replace( + "nativeFunctionSwitchTempNamesAvoidLocalUserNames = true", + "nativeFunctionSwitchTempNamesCollideWithLocalUserNames = true", + ) + errors = check.check_native_switch_lowering_boundary( + check.NATIVE_ADAPTER.read_text(encoding="utf-8"), + smoke_text, + ) + self.assertTrue( + any("nativeFunctionSwitchTempNamesAvoidLocalUserNames" in error for error in errors), + errors, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/scripts/test_check_proof_length.py b/scripts/test_check_proof_length.py index c63356990..5a3a2ec48 100644 --- a/scripts/test_check_proof_length.py +++ b/scripts/test_check_proof_length.py @@ -62,6 +62,23 @@ def test_proof_ends_at_next_theorem(self) -> None: self.assertEqual(results[0][0], "first") self.assertEqual(results[1][0], "second") + def test_proof_ends_at_partial_def(self) -> None: + path = self._write_lean( + """\ + theorem before_partial : True := by + trivial + + partial def recursiveHelper : Nat -> Nat + | 0 => 0 + | n + 1 => recursiveHelper n + """ + ) + results = check_proof_length.measure_proofs(path) + self.assertEqual(len(results), 1) + name, _, length = results[0] + self.assertEqual(name, "before_partial") + self.assertLessEqual(length, 5) + def test_lemma_detected(self) -> None: path = self._write_lean( """\ diff --git a/scripts/test_check_selectors.py b/scripts/test_check_selectors.py index 0881ad8ab..4b3cd7e65 100644 --- a/scripts/test_check_selectors.py +++ b/scripts/test_check_selectors.py @@ -14,6 +14,7 @@ CompileSelectors, SpecInfo, check_compile_lists, + check_unique_function_signatures, extract_compile_selectors, extract_specs, load_specs_text, @@ -59,6 +60,29 @@ def test_extract_specs_supports_filtered_macro_alias_defs(self) -> None: ) +class CheckSelectorsDuplicateSignatureTests(unittest.TestCase): + def test_unique_signature_check_ignores_internal_external_pairs(self) -> None: + spec = SpecInfo( + "helperSpec", + "Helper", + signatures=["helper(uint256)"], + all_function_signatures=["helper(uint256)", "helper(uint256)"], + ) + self.assertEqual(check_unique_function_signatures([spec]), []) + + def test_unique_signature_check_rejects_duplicate_externals(self) -> None: + spec = SpecInfo( + "helperSpec", + "Helper", + signatures=["helper(uint256)", "helper(uint256)"], + all_function_signatures=["helper(uint256)", "helper(uint256)"], + ) + self.assertEqual( + check_unique_function_signatures([spec]), + ["Helper: duplicate function signature 'helper(uint256)'"], + ) + + class CheckSelectorsCliTests(unittest.TestCase): def test_check_fixtures_flag_dispatches_fixture_check(self) -> None: calls: list[str] = [] diff --git a/scripts/test_evmyullean_fork_conformance_workflow.py b/scripts/test_evmyullean_fork_conformance_workflow.py index 5eff19610..9fa519980 100644 --- a/scripts/test_evmyullean_fork_conformance_workflow.py +++ b/scripts/test_evmyullean_fork_conformance_workflow.py @@ -52,6 +52,9 @@ def test_post_burn_in_failures_open_or_update_issue(self) -> None: "Compiler/Proofs/YulGeneration/Backends/EvmYulLeanBodyClosure.lean", "Compiler/Proofs/YulGeneration/Backends/EvmYulLeanBridgeLemmas.lean", "Compiler/Proofs/YulGeneration/Backends/EvmYulLeanBridgeTest.lean", + "Compiler/Proofs/YulGeneration/Backends/EvmYulLeanNativeHarness.lean", + "Compiler/Proofs/YulGeneration/Backends/EvmYulLeanNativeDispatchOracleTest.lean", + "Compiler/Proofs/YulGeneration/Backends/EvmYulLeanNativeSmokeTest.lean", "Compiler/Proofs/YulGeneration/Backends/EvmYulLeanRetarget.lean", "Compiler/Proofs/YulGeneration/Backends/EvmYulLeanSignedArithSpec.lean", "Compiler/Proofs/YulGeneration/Backends/EvmYulLeanSourceExprClosure.lean", @@ -95,6 +98,9 @@ def test_post_burn_in_failures_open_or_update_issue(self) -> None: makefile_text = MAKEFILE.read_text(encoding="utf-8") self.assertIn("python3 scripts/generate_evmyullean_adapter_report.py --check", makefile_text) self.assertIn("lake build Compiler.Proofs.YulGeneration.Backends.EvmYulLeanAdapterCorrectness", makefile_text) + self.assertIn("lake build Compiler.Proofs.YulGeneration.Backends.EvmYulLeanNativeHarness", makefile_text) + self.assertIn("lake build Compiler.Proofs.YulGeneration.Backends.EvmYulLeanNativeSmokeTest", makefile_text) + self.assertIn("lake exe native-dispatch-oracle-test", makefile_text) self.assertIn("lake build Compiler.Proofs.EndToEnd", makefile_text) issue_step = re.search( diff --git a/scripts/test_generate_macro_property_tests.py b/scripts/test_generate_macro_property_tests.py index ed53b6b7c..77a2842f5 100644 --- a/scripts/test_generate_macro_property_tests.py +++ b/scripts/test_generate_macro_property_tests.py @@ -1583,22 +1583,22 @@ def test_render_erc721_mint_infers_stateful_assertion(self) -> None: functions=( gen.FunctionDecl( "mint", - (gen.ParamDecl("to", "Address"),), + (gen.ParamDecl("toAddr", "Address"),), "Uint256", body=( "let sender ← msgSender", "let currentOwner ← getStorageAddr owner", 'require (sender == currentOwner) "Caller is not the owner"', - 'require (to != zeroAddress) "Invalid recipient"', + 'require (toAddr != zeroAddress) "Invalid recipient"', "let tokenId ← getStorage nextTokenId", "let currentOwnerWord ← getMappingUint owners tokenId", 'require (currentOwnerWord == 0) "Token already minted"', - "let recipientBalance ← getMapping balances to", + "let recipientBalance ← getMapping balances toAddr", 'let newRecipientBalance ← requireSomeUint (safeAdd recipientBalance 1) "Balance overflow"', "let currentSupply ← getStorage totalSupply", 'let newSupply ← requireSomeUint (safeAdd currentSupply 1) "Supply overflow"', - "setMappingUintAddr owners tokenId to", - "setMapping balances to newRecipientBalance", + "setMappingUintAddr owners tokenId toAddr", + "setMapping balances toAddr newRecipientBalance", "setStorage totalSupply newSupply", "setStorage nextTokenId (add tokenId 1)", "return tokenId", @@ -1615,7 +1615,7 @@ def test_render_erc721_mint_infers_stateful_assertion(self) -> None: rendered, ) self.assertIn( - "vm.store(target, _mappingSlot(bytes32(uint256(uint160(to))), 3), bytes32(uint256(recipientBalance)));", + "vm.store(target, _mappingSlot(bytes32(uint256(uint160(toAddr))), 3), bytes32(uint256(recipientBalance)));", rendered, ) self.assertIn( diff --git a/scripts/test_native_transition_api.py b/scripts/test_native_transition_api.py new file mode 100644 index 000000000..a795cee7b --- /dev/null +++ b/scripts/test_native_transition_api.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import re +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +HARNESS = ( + ROOT + / "Compiler" + / "Proofs" + / "YulGeneration" + / "Backends" + / "EvmYulLeanNativeHarness.lean" +) + + +def declaration_block(text: str, name: str) -> str: + match = re.search(rf"(?m)^def {re.escape(name)}\b", text) + if match is None: + raise AssertionError(f"missing definition: {name}") + + next_decl = re.search( + r"(?m)^(?:def|theorem|lemma|@[^\n]+\s+theorem)\b", + text[match.end() :], + ) + end = match.end() + next_decl.start() if next_decl is not None else len(text) + return text[match.start() : end] + + +class NativeTransitionApiTests(unittest.TestCase): + def test_native_runtime_entrypoints_require_explicit_observable_slots(self) -> None: + text = HARNESS.read_text(encoding="utf-8") + + for name in ["interpretRuntimeNative", "interpretIRRuntimeNative"]: + with self.subTest(name=name): + block = declaration_block(text, name) + self.assertIn("(observableSlots : List Nat)", block) + self.assertNotIn("(observableSlots : List Nat := []", block) + self.assertNotIn("observableSlots : List Nat := []", block) + + +if __name__ == "__main__": + unittest.main()