From 624f0ad3cb6a972e9357a2e168c17adc35b88091 Mon Sep 17 00:00:00 2001 From: "Thomas Marchand (agent)" Date: Sat, 2 May 2026 00:09:03 +0200 Subject: [PATCH 1/2] Add Uint256 pow macro lowering --- .../CompilationModel/ExpressionCompile.lean | 7 +- Compiler/CompilationModel/LogicalPurity.lean | 4 +- Compiler/CompilationModel/TrustSurface.lean | 5 +- Compiler/CompilationModel/Types.lean | 2 + Compiler/CompilationModel/Validation.lean | 16 +++-- .../CompilationModel/ValidationCalls.lean | 25 ++++--- Compiler/CompilationModelFeatureTest.lean | 65 +++++++++++++++++++ Contracts/MacroTranslateInvariantTest.lean | 3 + Contracts/MacroTranslateRoundTripFuzz.lean | 1 + Contracts/Smoke.lean | 12 ++++ Verity/Core/Uint256.lean | 10 +++ Verity/EVM/Uint256.lean | 1 + Verity/Macro/Translate.lean | 5 ++ .../PropertyUint256PowSmoke.t.sol | 38 +++++++++++ docs-site/content/edsl-api-reference.mdx | 4 +- docs/ARITHMETIC_PROFILE.md | 4 +- docs/INTERPRETER_FEATURE_MATRIX.md | 2 +- 17 files changed, 184 insertions(+), 20 deletions(-) create mode 100644 artifacts/macro_property_tests/PropertyUint256PowSmoke.t.sol diff --git a/Compiler/CompilationModel/ExpressionCompile.lean b/Compiler/CompilationModel/ExpressionCompile.lean index 3ad0e53d8..af4b60a4b 100644 --- a/Compiler/CompilationModel/ExpressionCompile.lean +++ b/Compiler/CompilationModel/ExpressionCompile.lean @@ -243,7 +243,12 @@ def compileExpr (fields : List Field) | Expr.localVar name => pure (YulExpr.ident name) | Expr.externalCall name args => do let argExprs ← compileExprList fields dynamicSource args - pure (YulExpr.call name argExprs) + if name == builtinExpName then + match argExprs with + | [base, exponent] => pure (YulExpr.call "exp" [base, exponent]) + | _ => throw s!"Compilation error: builtin exp expects 2 args, got {argExprs.length}" + else + pure (YulExpr.call name argExprs) | Expr.internalCall functionName args => do let argExprs ← compileExprList fields dynamicSource args pure (YulExpr.call (internalFunctionYulName functionName) argExprs) diff --git a/Compiler/CompilationModel/LogicalPurity.lean b/Compiler/CompilationModel/LogicalPurity.lean index b47c033c2..7d376cd21 100644 --- a/Compiler/CompilationModel/LogicalPurity.lean +++ b/Compiler/CompilationModel/LogicalPurity.lean @@ -9,7 +9,9 @@ partial def exprContainsCallLike (expr : Expr) : Bool := | Expr.call _ _ _ _ _ _ _ => true | Expr.staticcall _ _ _ _ _ _ => true | Expr.delegatecall _ _ _ _ _ _ => true - | Expr.externalCall _ _ | Expr.internalCall _ _ => true + | Expr.externalCall name args => + if name == builtinExpName then exprListContainsCallLike args else true + | Expr.internalCall _ _ => true | Expr.mapping _ key | Expr.mappingWord _ key _ | Expr.mappingPackedWord _ key _ _ | Expr.mappingUint _ key | Expr.structMember _ key _ => exprContainsCallLike key diff --git a/Compiler/CompilationModel/TrustSurface.lean b/Compiler/CompilationModel/TrustSurface.lean index 14c1cb3be..a7a7a589a 100644 --- a/Compiler/CompilationModel/TrustSurface.lean +++ b/Compiler/CompilationModel/TrustSurface.lean @@ -736,7 +736,10 @@ private def collectAxiomatizedPrimitivesByStatus private partial def collectExternalExprNames : Expr → List String | .externalCall name args => - name :: args.flatMap collectExternalExprNames + if name == builtinExpName then + args.flatMap collectExternalExprNames + else + name :: args.flatMap collectExternalExprNames | .call gas target value inOffset inSize outOffset outSize => collectExternalExprNames gas ++ collectExternalExprNames target ++ collectExternalExprNames value ++ collectExternalExprNames inOffset ++ collectExternalExprNames inSize ++ diff --git a/Compiler/CompilationModel/Types.lean b/Compiler/CompilationModel/Types.lean index e194fb219..32c17beee 100644 --- a/Compiler/CompilationModel/Types.lean +++ b/Compiler/CompilationModel/Types.lean @@ -35,6 +35,8 @@ export Compiler.Constants (errorStringSelectorWord addressMask selectorShift fre open Compiler open Compiler.Yul +def builtinExpName : String := "__verity_builtin_exp" + /-! ## Compilation Model DSL diff --git a/Compiler/CompilationModel/Validation.lean b/Compiler/CompilationModel/Validation.lean index cae6e64d8..f5f4099db 100644 --- a/Compiler/CompilationModel/Validation.lean +++ b/Compiler/CompilationModel/Validation.lean @@ -270,7 +270,9 @@ def exprReadsStateOrEnv : Expr → Bool | Expr.returndataOptionalBoolAt _ => true | Expr.dynamicBytesEq _ _ => false | Expr.localVar _ => false - | Expr.externalCall _ _ | Expr.internalCall _ _ => true + | Expr.externalCall name args => + if name == builtinExpName then exprListReadsStateOrEnv args else true + | Expr.internalCall _ _ => true | Expr.arrayLength _ => false | Expr.storageArrayLength _ => true | Expr.storageArrayElement _ index => true || exprReadsStateOrEnv index @@ -338,7 +340,9 @@ def exprWritesState : Expr → Bool exprWritesState outOffset | Expr.dynamicBytesEq _ _ => false - | Expr.externalCall _ _ | Expr.internalCall _ _ => true + | Expr.externalCall name args => + if name == builtinExpName then exprListWritesState args else true + | Expr.internalCall _ _ => true | Expr.adtConstruct _ _ args => exprListWritesState args | Expr.extcodesize addr => exprWritesState addr @@ -592,7 +596,9 @@ mutual or externalCall). Used by `no_external_calls` validation (#1729, Axis 3 Step 1c). -/ def exprContainsExternalCall : Expr → Bool | Expr.call _ _ _ _ _ _ _ | Expr.staticcall _ _ _ _ _ _ - | Expr.delegatecall _ _ _ _ _ _ | Expr.externalCall _ _ => true + | Expr.delegatecall _ _ _ _ _ _ => true + | Expr.externalCall name args => + if name == builtinExpName then exprListContainsExternalCall args else true | 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 @@ -647,7 +653,9 @@ mutual def exprMayContainExternalCall : Expr → Bool | Expr.internalCall _ _ => true | Expr.call _ _ _ _ _ _ _ | Expr.staticcall _ _ _ _ _ _ - | Expr.delegatecall _ _ _ _ _ _ | Expr.externalCall _ _ => true + | Expr.delegatecall _ _ _ _ _ _ => true + | Expr.externalCall name args => + if name == builtinExpName then exprListMayContainExternalCall args else true | 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 diff --git a/Compiler/CompilationModel/ValidationCalls.lean b/Compiler/CompilationModel/ValidationCalls.lean index 76925e073..21d066c2b 100644 --- a/Compiler/CompilationModel/ValidationCalls.lean +++ b/Compiler/CompilationModel/ValidationCalls.lean @@ -41,8 +41,9 @@ def reservedExternalNames [dynamicBytesEqCalldataHelperName, dynamicBytesEqMemoryHelperName] else [] + let builtins := [builtinExpName] let entrypoints := ["fallback", "receive"] - (mappingHelpers ++ arrayHelpers ++ arrayElementWordHelpers ++ storageArrayHelpers ++ dynamicBytesEqHelpers ++ entrypoints).eraseDups + (mappingHelpers ++ arrayHelpers ++ arrayElementWordHelpers ++ storageArrayHelpers ++ dynamicBytesEqHelpers ++ builtins ++ entrypoints).eraseDups def firstReservedExternalCollision (spec : CompilationModel) @@ -312,15 +313,19 @@ mutual def validateExternalCallTargetsInExpr (externals : List ExternalFunction) (context : String) : Expr → Except String Unit | Expr.externalCall name args => do - match externals.find? (fun ext => ext.name == name) with - | none => - throw s!"Compilation error: {context} references unknown external call target '{name}' ({issue732Ref}). Declare it in spec.externals." - | some ext => - if args.length != ext.params.length then - throw s!"Compilation error: {context} calls external '{name}' with {args.length} args, but spec.externals declares {ext.params.length} ({issue184Ref})." - let returns ← externalFunctionReturns ext - if returns.length != 1 then - throw s!"Compilation error: {context} uses Expr.externalCall '{name}' but spec.externals declares {returns.length} return values; Expr.externalCall requires exactly 1 ({issue184Ref})." + if name == builtinExpName then + if args.length != 2 then + throw s!"Compilation error: {context} calls builtin exp with {args.length} args, expected 2." + else + match externals.find? (fun ext => ext.name == name) with + | none => + throw s!"Compilation error: {context} references unknown external call target '{name}' ({issue732Ref}). Declare it in spec.externals." + | some ext => + if args.length != ext.params.length then + throw s!"Compilation error: {context} calls external '{name}' with {args.length} args, but spec.externals declares {ext.params.length} ({issue184Ref})." + let returns ← externalFunctionReturns ext + if returns.length != 1 then + throw s!"Compilation error: {context} uses Expr.externalCall '{name}' but spec.externals declares {returns.length} return values; Expr.externalCall requires exactly 1 ({issue184Ref})." validateExternalCallTargetsInExprList externals context args | Expr.call gas target value inOffset inSize outOffset outSize => do validateExternalCallTargetsInExpr externals context gas diff --git a/Compiler/CompilationModelFeatureTest.lean b/Compiler/CompilationModelFeatureTest.lean index 2f3624b65..2c3c45e4b 100644 --- a/Compiler/CompilationModelFeatureTest.lean +++ b/Compiler/CompilationModelFeatureTest.lean @@ -590,6 +590,43 @@ def feeOnModelInlinesContractConstants : Bool := example : feeOnModelInlinesContractConstants = true := by native_decide +def uint256PowSmokeLowersToBuiltinExp : Bool := + match Contracts.Smoke.Uint256PowSmoke.scale_modelBody with + | [Stmt.letVar "exponent" (Expr.sub (Expr.literal 18) (Expr.param "decimals")), + Stmt.return (Expr.externalCall name [Expr.literal 10, Expr.localVar "exponent"])] => + name == builtinExpName + | _ => false + +example : uint256PowSmokeLowersToBuiltinExp = true := by native_decide + +def uint256PowInfixLowersToBuiltinExp : Bool := + match Contracts.Smoke.Uint256PowSmoke.scaleInfix_modelBody with + | [Stmt.letVar "exponent" (Expr.sub (Expr.literal 18) (Expr.param "decimals")), + Stmt.return (Expr.externalCall name [Expr.literal 10, Expr.localVar "exponent"])] => + name == builtinExpName + | _ => false + +example : uint256PowInfixLowersToBuiltinExp = true := by native_decide + +def uint256PowBuiltinCompilesToYulExp : Bool := + match compileExpr [] .calldata + (Expr.externalCall builtinExpName [Expr.param "base", Expr.param "exponent"]) with + | .ok (Compiler.Yul.YulExpr.call "exp" + [Compiler.Yul.YulExpr.ident "base", Compiler.Yul.YulExpr.ident "exponent"]) => true + | _ => false + +example : uint256PowBuiltinCompilesToYulExp = true := by native_decide + +def uint256PowBuiltinIsNotAnExternalInteraction : Bool := + let expr := Expr.externalCall builtinExpName [Expr.literal 10, Expr.param "exponent"] + !exprReadsStateOrEnv expr && + !exprWritesState expr && + !exprContainsCallLike expr && + !exprContainsExternalCall expr && + !exprMayContainExternalCall expr + +example : uint256PowBuiltinIsNotAnExternalInteraction = true := by native_decide + def treasuryAddrModelInlinesAddressConstant : Bool := match MacroConstant.treasuryAddr_modelBody with | [Stmt.return (Expr.literal 42)] => @@ -2167,6 +2204,27 @@ private def effectOnlyExternalBindMismatchSpec : CompilationModel := { ] } +private def reservedBuiltinExpExternalSpec : CompilationModel := { + name := "ReservedBuiltinExpExternal" + fields := [] + «constructor» := none + functions := [ + { name := "scale" + params := [{ name := "exponent", ty := ParamType.uint256 }] + returnType := some FieldType.uint256 + body := [Stmt.return (Expr.externalCall builtinExpName [Expr.literal 10, Expr.param "exponent"])] + } + ] + externals := [ + { name := builtinExpName + params := [ParamType.uint256, ParamType.uint256] + returnType := some ParamType.uint256 + returns := [ParamType.uint256] + axiomNames := ["malicious_shadow_exp"] + } + ] +} + private def rawLogTraceSmokeSpec : CompilationModel := { name := "RawLogTraceSmoke" fields := [] @@ -3143,6 +3201,10 @@ set_option maxRecDepth 4096 in "effect-only external call bind still rejects non-void externals" effectOnlyExternalBindMismatchSpec "binds 0 values from external function 'echo', but it returns 1." + expectCompileErrorContains + "reserved builtin exp name cannot be shadowed by externals" + reservedBuiltinExpExternalSpec + s!"external declaration '{builtinExpName}' collides with compiler-generated/reserved symbol '{builtinExpName}'" expectCompileErrorContains "reserved compiler prefix is rejected in ECM result binders" reservedEcmResultVarSpec @@ -3656,6 +3718,9 @@ set_option maxRecDepth 4096 in expectTrue "macro externals surface in the trust report" (contains macroExternalTrustReport "\"linkedExternals\"" && contains macroExternalTrustReport "\"echo\"") + let macroPowTrustReport := emitTrustReportJson [Contracts.Smoke.Uint256PowSmoke.spec] + expectTrue "macro pow builtin does not surface as a linked external" + (!contains macroPowTrustReport builtinExpName) expectTrue "macro constant expressions inline into model bodies" MacroConstantSmoke.feeOnModelInlinesContractConstants expectTrue "macro address constants inline through the executable and model paths" diff --git a/Contracts/MacroTranslateInvariantTest.lean b/Contracts/MacroTranslateInvariantTest.lean index 25158e4cc..cdac71e3f 100644 --- a/Contracts/MacroTranslateInvariantTest.lean +++ b/Contracts/MacroTranslateInvariantTest.lean @@ -285,6 +285,7 @@ private def macroSpecs : List CompilationModel := , Contracts.SimpleToken.spec , Contracts.ERC20.spec , Contracts.ERC721.spec + , Contracts.Smoke.Uint256PowSmoke.spec , Contracts.Smoke.UintMapSmoke.spec , Contracts.Smoke.MappingChainSmoke.spec , Contracts.Smoke.MixedMappingChainSmoke.spec @@ -390,6 +391,7 @@ private def expectedExternalSignatures : List (String × List String) := , ("ERC721", ["balanceOf(address)", "ownerOf(uint256)", "getApproved(uint256)", "isApprovedForAll(address,address)", "approve(address,uint256)", "setApprovalForAll(address,bool)", "mint(address)", "transferFrom(address,address,uint256)"]) + , ("Uint256PowSmoke", ["scale(uint256)", "scaleInfix(uint256)"]) , ("UintMapSmoke", ["setValue(uint256,uint256)", "getValue(uint256)"]) , ("MappingChainSmoke", ["setApproval(address,address,address,uint256)", "getApproval(address,address,address)"]) , ("MixedMappingChainSmoke", ["setApproval(address,uint256,address,uint256)", "getApproval(address,uint256,address)"]) @@ -509,6 +511,7 @@ private def expectedExternalSelectors : List (String × List String) := "0x8da5cb5b"]) , ("ERC721", ["0x70a08231", "0x6352211e", "0x081812fc", "0xe985e9c5", "0x095ea7b3", "0xa22cb465", "0x6a627842", "0x23b872dd"]) + , ("Uint256PowSmoke", ["0x2bec1547", "0x09c39250"]) , ("UintMapSmoke", ["0x7b8d56e3", "0x0ff4c916"]) , ("MappingChainSmoke", ["0xd5264fdd", "0xf7531281"]) , ("MixedMappingChainSmoke", ["0xd3bf29a3", "0xa75ac7f0"]) diff --git a/Contracts/MacroTranslateRoundTripFuzz.lean b/Contracts/MacroTranslateRoundTripFuzz.lean index 00648d7e4..949c3e683 100644 --- a/Contracts/MacroTranslateRoundTripFuzz.lean +++ b/Contracts/MacroTranslateRoundTripFuzz.lean @@ -45,6 +45,7 @@ private def macroSpecs : List CompilationModel := , Contracts.Vault.spec , Contracts.ERC20.spec , Contracts.ERC721.spec + , Contracts.Smoke.Uint256PowSmoke.spec , Contracts.Smoke.UintMapSmoke.spec , Contracts.Smoke.MappingChainSmoke.spec , Contracts.Smoke.MixedMappingChainSmoke.spec diff --git a/Contracts/Smoke.lean b/Contracts/Smoke.lean index f489afa5c..ee6a28d13 100644 --- a/Contracts/Smoke.lean +++ b/Contracts/Smoke.lean @@ -35,6 +35,17 @@ private def genericECMEffectDemoModule : Compiler.ECM.ExternalCallModule where axioms := [] compile := fun _ctx _args => pure [] +verity_contract Uint256PowSmoke where + storage + + function scale (decimals : Uint256) : Uint256 := do + let exponent := sub 18 decimals + return (pow 10 exponent) + + function scaleInfix (decimals : Uint256) : Uint256 := do + let exponent := sub 18 decimals + return (10 ^ exponent) + verity_contract UintMapSmoke where storage values : Uint256 → Uint256 := slot 0 @@ -1773,6 +1784,7 @@ example (owner : Address) (value : Uint256) (s s' : ContractState) : end SpecGenSmoke #check_contract Contracts.Counter +#check_contract Uint256PowSmoke #check_contract UintMapSmoke #check_contract Bytes32Smoke #check_contract StorageAddressArraySmoke diff --git a/Verity/Core/Uint256.lean b/Verity/Core/Uint256.lean index c2873f52f..1679fbcba 100644 --- a/Verity/Core/Uint256.lean +++ b/Verity/Core/Uint256.lean @@ -114,6 +114,9 @@ def sub (a b : Uint256) : Uint256 := -- Modular multiplication (wraps at 2^256) def mul (a b : Uint256) : Uint256 := ofNat (a.val * b.val) +/-- EVM EXP semantics: exponentiation modulo `2^256`. -/ +def pow (base exponent : Uint256) : Uint256 := ofNat (base.val ^ exponent.val) + -- Division (returns 0 on division by zero, matching EVM) def div (a b : Uint256) : Uint256 := if b.val = 0 then ofNat 0 else ofNat (a.val / b.val) @@ -159,11 +162,13 @@ def willMulOverflow (a b : Uint256) : Bool := instance : HAdd Uint256 Uint256 Uint256 := ⟨add⟩ instance : HSub Uint256 Uint256 Uint256 := ⟨sub⟩ instance : HMul Uint256 Uint256 Uint256 := ⟨mul⟩ +instance : HPow Uint256 Uint256 Uint256 := ⟨pow⟩ instance : HDiv Uint256 Uint256 Uint256 := ⟨div⟩ instance : HMod Uint256 Uint256 Uint256 := ⟨mod⟩ instance : Add Uint256 := ⟨add⟩ instance : Sub Uint256 := ⟨sub⟩ instance : Mul Uint256 := ⟨mul⟩ +instance : Pow Uint256 Uint256 := ⟨pow⟩ instance : Div Uint256 := ⟨div⟩ instance : Mod Uint256 := ⟨mod⟩ @@ -179,6 +184,11 @@ theorem mul_eq_of_lt {a b : Uint256} (h : a.val * b.val < modulus) : simp [HMul.hMul, mul, ofNat] exact Nat.mod_eq_of_lt h +theorem pow_eq_of_lt {a b : Uint256} (h : a.val ^ b.val < modulus) : + ((a ^ b : Uint256) : Nat) = a.val ^ b.val := by + simp [HPow.hPow, Pow.pow, pow, ofNat] + exact Nat.mod_eq_of_lt h + theorem sub_eq_of_le {a b : Uint256} (h : b.val ≤ a.val) : ((a - b : Uint256) : Nat) = a.val - b.val := by have hlt : a.val - b.val < modulus := by diff --git a/Verity/EVM/Uint256.lean b/Verity/EVM/Uint256.lean index 667ebed81..37dd0b796 100644 --- a/Verity/EVM/Uint256.lean +++ b/Verity/EVM/Uint256.lean @@ -18,6 +18,7 @@ abbrev modulus := Verity.Core.Uint256.modulus abbrev add := Verity.Core.Uint256.add abbrev sub := Verity.Core.Uint256.sub abbrev mul := Verity.Core.Uint256.mul +abbrev pow := Verity.Core.Uint256.pow abbrev div := Verity.Core.Uint256.div abbrev mod := Verity.Core.Uint256.mod diff --git a/Verity/Macro/Translate.lean b/Verity/Macro/Translate.lean index 08801ff8c..2b5a81c6a 100644 --- a/Verity/Macro/Translate.lean +++ b/Verity/Macro/Translate.lean @@ -2237,6 +2237,7 @@ private partial def inferPureExprType let lhsTy ← inferPureExprType fields constDecls immutableDecls externalDecls params locals a visitingConstants let rhsTy ← inferPureExprType fields constDecls immutableDecls externalDecls params locals b visitingConstants classifyUnsignedWordArithmeticResultType stx "bitwise word arithmetic" lhsTy rhsTy + | `(term| pow $a $b) | `(term| $a ^ $b) | `(term| min $a $b) | `(term| max $a $b) | `(term| wMulDown $a $b) | `(term| wDivUp $a $b) | `(term| ceilDiv $a $b) => do let lhsTy ← inferPureExprType fields constDecls immutableDecls externalDecls params locals a visitingConstants @@ -3062,6 +3063,10 @@ partial def translatePureExprWithTypes | `(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)) | `(term| mul $a $b) => `(Compiler.CompilationModel.Expr.mul $(← translatePureExprWithTypes fields constDecls immutableDecls params locals a visitingConstants) $(← translatePureExprWithTypes fields constDecls immutableDecls params locals b visitingConstants)) + | `(term| pow $a $b) | `(term| $a ^ $b) => + `(Compiler.CompilationModel.Expr.externalCall Compiler.CompilationModel.builtinExpName + [$(← translatePureExprWithTypes fields constDecls immutableDecls params locals a visitingConstants), + $(← translatePureExprWithTypes fields constDecls immutableDecls params locals b visitingConstants)]) | `(term| div $a $b) | `(term| $a / $b) => do let lhsTy ← inferPureExprType fields constDecls immutableDecls #[] params locals a visitingConstants let rhsTy ← inferPureExprType fields constDecls immutableDecls #[] params locals b visitingConstants diff --git a/artifacts/macro_property_tests/PropertyUint256PowSmoke.t.sol b/artifacts/macro_property_tests/PropertyUint256PowSmoke.t.sol new file mode 100644 index 000000000..2d8b198a3 --- /dev/null +++ b/artifacts/macro_property_tests/PropertyUint256PowSmoke.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.33; + +import "./yul/YulTestBase.sol"; + +/** + * @title PropertyUint256PowSmokeTest + * @notice Auto-generated baseline property stubs from `verity_contract` declarations. + * @dev Source: Contracts/Smoke.lean + */ +contract PropertyUint256PowSmokeTest is YulTestBase { + address target; + address alice = address(0x1111); + + function setUp() public { + target = deployYul("Uint256PowSmoke"); + require(target != address(0), "Deploy failed"); + } + + // Property 1: TODO decode and assert `scale` result + function testTODO_Scale_DecodeAndAssert() public { + vm.prank(alice); + (bool ok, bytes memory ret) = target.call(abi.encodeWithSignature("scale(uint256)", uint256(1))); + require(ok, "scale reverted unexpectedly"); + assertEq(ret.length, 32, "scale 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 `scaleInfix` result + function testTODO_ScaleInfix_DecodeAndAssert() public { + vm.prank(alice); + (bool ok, bytes memory ret) = target.call(abi.encodeWithSignature("scaleInfix(uint256)", uint256(1))); + require(ok, "scaleInfix reverted unexpectedly"); + assertEq(ret.length, 32, "scaleInfix ABI return length mismatch (expected 32 bytes)"); + // TODO(#1011): decode `ret` and assert the concrete postcondition from Lean theorem. + ret; + } +} diff --git a/docs-site/content/edsl-api-reference.mdx b/docs-site/content/edsl-api-reference.mdx index bac0b4265..fb5d791b4 100644 --- a/docs-site/content/edsl-api-reference.mdx +++ b/docs-site/content/edsl-api-reference.mdx @@ -263,16 +263,18 @@ emitEvent "Transfer" [amount] [fromAddr, toAddr] Verity.EVM.Uint256.add : Uint256 -> Uint256 -> Uint256 Verity.EVM.Uint256.sub : Uint256 -> Uint256 -> Uint256 Verity.EVM.Uint256.mul : Uint256 -> Uint256 -> Uint256 +Verity.EVM.Uint256.pow : Uint256 -> Uint256 -> Uint256 Verity.EVM.Uint256.div : Uint256 -> Uint256 -> Uint256 Verity.EVM.Uint256.mod : Uint256 -> Uint256 -> Uint256 ``` -These model EVM modular arithmetic. +These model EVM modular arithmetic. `pow a b` and `a ^ b` compile to Yul/EVM `exp(a, b)`. **Example** ```lean let z : Uint256 := add x y +let scale : Uint256 := pow 10 decimals ``` **Proof lemmas** diff --git a/docs/ARITHMETIC_PROFILE.md b/docs/ARITHMETIC_PROFILE.md index 189eb1347..10620de7f 100644 --- a/docs/ARITHMETIC_PROFILE.md +++ b/docs/ARITHMETIC_PROFILE.md @@ -32,7 +32,7 @@ Wrapping semantics are **proven** (not assumed) across all three verification la | Layer | Proof location | What is proved | |-------|---------------|----------------| -| Layer 1 (EDSL) | `Verity/Core/Uint256.lean` | `Uint256.add`, `sub`, `mul`, `div`, `mod` are wrapping modular | +| Layer 1 (EDSL) | `Verity/Core/Uint256.lean` | `Uint256.add`, `sub`, `mul`, `pow`, `div`, `mod` are wrapping modular | | Layer 1 (EDSL) | `Verity/Proofs/Stdlib/Math.lean` | `safeAdd`, `safeSub`, `safeMul` correctness | | Compiler reference oracle | `Compiler/Proofs/YulGeneration/ReferenceOracle/Builtins.lean` | `evalBuiltinCall` implements wrapping for all 15 pure builtins | | Compiler | `Compiler/Proofs/ArithmeticProfile.lean` | `add_wraps`, `sub_wraps`, `mul_wraps`, `div_by_zero`, `mod_by_zero` | @@ -44,6 +44,8 @@ The EVMYulLean bridge validates that Verity's `Nat`-modular arithmetic agrees wi - context-lifted bridge theorems for all 25 pure builtins at the `evalBuiltinCallWithBackendContext` level (the Phase 4 backend-retargeting surface) - concrete bridge smoke tests are no longer needed for any pure builtin +The EDSL exposes `pow(a, b)` and `a ^ b` for EVM modular exponentiation. Macro lowering emits a reserved compiler builtin that compiles directly to Yul/EVM `exp(a, b)`. + ### Higher-Level Expression Operators Beyond the 15 low-level builtins, the `ExprCompileCore` proven fragment includes compilation-correctness proofs for 8 higher-level expression operators: diff --git a/docs/INTERPRETER_FEATURE_MATRIX.md b/docs/INTERPRETER_FEATURE_MATRIX.md index 96086f907..368357d6d 100644 --- a/docs/INTERPRETER_FEATURE_MATRIX.md +++ b/docs/INTERPRETER_FEATURE_MATRIX.md @@ -53,7 +53,7 @@ with the existing sync scripts and boundary checks. | `call` | `Expr.call` | **0** | **0** | -- | -- | n/m | | `staticcall` | `Expr.staticcall` | **0** | **0** | -- | -- | n/m | | `delegatecall` | `Expr.delegatecall` | **0** | **0** | -- | -- | n/m | -| Arithmetic | `add/sub/mul/div/mod` | ok | ok | ok | ok | proved | +| Arithmetic | `add/sub/mul/pow/div/mod` | ok | ok | ok | ok | proved | | Bitwise | `and/or/xor/shl/shr` | ok | ok | ok | ok | proved | | Bitwise | `not` | ok | ok | ok | ok | partial | | Comparison | `eq/lt/gt/le/ge` | ok | ok | ok | ok | proved | From 5d4688a42197aae984d1dcde5d554a8d10895bbd Mon Sep 17 00:00:00 2001 From: "Thomas Marchand (agent)" Date: Sun, 3 May 2026 10:39:15 +0200 Subject: [PATCH 2/2] Fix pow invariant field assertion --- Contracts/MacroTranslateInvariantTest.lean | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Contracts/MacroTranslateInvariantTest.lean b/Contracts/MacroTranslateInvariantTest.lean index b7a1d02f3..19f71a73f 100644 --- a/Contracts/MacroTranslateInvariantTest.lean +++ b/Contracts/MacroTranslateInvariantTest.lean @@ -802,7 +802,10 @@ private def checkSignedBuiltinSmoke : IO Unit := do let loadSigned := loadSigned?.getD { name := "", params := [], returnType := none, returns := [], body := [] } expectTrue "SignedBuiltinSmoke: Int256 storage is modeled as a word slot" (match Contracts.Smoke.SignedBuiltinSmoke.spec.fields with - | [{ name := "signedSlot", ty := FieldType.uint256, slot := some 0 }] => true + | [field] => + field.name == "signedSlot" && + field.ty == FieldType.uint256 && + field.slot == some 0 | _ => false) expectTrue "SignedBuiltinSmoke: signedDiv body uses Expr.sdiv" (bodyUsesSignedBuiltin signedDiv.body "Expr.sdiv")