Skip to content

Commit 9eaf642

Browse files
authored
Document and test requireSomeUintError (#1941)
1 parent d7be2ef commit 9eaf642

4 files changed

Lines changed: 115 additions & 99 deletions

File tree

Compiler/MacroCustomErrorFeatureTest.lean

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,4 +170,43 @@ example : checkAddExecutableRevertsOnOverflow = true := by native_decide
170170

171171
end RequireSomeUintErrorSmoke
172172

173+
/--
174+
error: unknown custom error 'MissingOverflow'
175+
-/
176+
#guard_msgs in
177+
verity_contract RequireSomeUintErrorUnknownErrorRejected where
178+
storage
179+
errors
180+
error AddOverflow ()
181+
182+
function bad (a : Uint256, b : Uint256) : Uint256 := do
183+
let result ← requireSomeUintError (safeAdd a b) MissingOverflow()
184+
return result
185+
186+
/--
187+
error: custom error 'MulOverflow' expects 2 args, got 1
188+
-/
189+
#guard_msgs in
190+
verity_contract RequireSomeUintErrorWrongArityRejected where
191+
storage
192+
errors
193+
error MulOverflow (Uint256, Uint256)
194+
195+
function bad (a : Uint256, b : Uint256) : Uint256 := do
196+
let result ← requireSomeUintError (safeMul a b) MulOverflow(a)
197+
return result
198+
199+
/--
200+
error: unsupported requireSomeUintError source; expected safeAdd, safeSub, safeMul, or safeDiv
201+
-/
202+
#guard_msgs in
203+
verity_contract RequireSomeUintErrorInvalidSourceRejected where
204+
storage
205+
errors
206+
error AddOverflow ()
207+
208+
function bad (a : Uint256) : Uint256 := do
209+
let result ← requireSomeUintError a AddOverflow()
210+
return result
211+
173212
end Compiler.MacroCustomErrorFeatureTest

Verity/Macro/Translate.lean

Lines changed: 46 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -5642,111 +5642,63 @@ private def translateSafeRequireBind
56425642
(locals : Array TypedLocal)
56435643
(varName : String)
56445644
(rhs : Term) : CommandElabM (Option (Array Term)) := do
5645+
let translateSafeUintGuardAndValue (optExpr : Term) (label : String) :
5646+
CommandElabM (Term × Term) := do
5647+
match stripParens optExpr with
5648+
| `(term| safeAdd $a:term $b:term) =>
5649+
let aExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals a
5650+
let bExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals b
5651+
let valueExpr : Term ← `(Compiler.CompilationModel.Expr.add $aExpr $bExpr)
5652+
let guardExpr : Term ← `(Compiler.CompilationModel.Expr.ge $valueExpr $aExpr)
5653+
pure (guardExpr, valueExpr)
5654+
| `(term| safeSub $a:term $b:term) =>
5655+
let aExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals a
5656+
let bExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals b
5657+
let valueExpr : Term ← `(Compiler.CompilationModel.Expr.sub $aExpr $bExpr)
5658+
let guardExpr : Term ← `(Compiler.CompilationModel.Expr.ge $aExpr $bExpr)
5659+
pure (guardExpr, valueExpr)
5660+
| `(term| safeMul $a:term $b:term) =>
5661+
let aExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals a
5662+
let bExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals b
5663+
let valueExpr : Term ← `(Compiler.CompilationModel.Expr.mul $aExpr $bExpr)
5664+
let zeroExpr : Term ← `(Compiler.CompilationModel.Expr.literal 0)
5665+
let divisorZeroExpr : Term ← `(Compiler.CompilationModel.Expr.eq $bExpr $zeroExpr)
5666+
let quotientExpr : Term ← `(Compiler.CompilationModel.Expr.div $valueExpr $bExpr)
5667+
let noOverflowExpr : Term ← `(Compiler.CompilationModel.Expr.eq $quotientExpr $aExpr)
5668+
let guardExpr : Term ← `(Compiler.CompilationModel.Expr.logicalOr $divisorZeroExpr $noOverflowExpr)
5669+
pure (guardExpr, valueExpr)
5670+
| `(term| safeDiv $a:term $b:term) =>
5671+
let aExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals a
5672+
let bExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals b
5673+
let valueExpr : Term ← `(Compiler.CompilationModel.Expr.div $aExpr $bExpr)
5674+
let zeroExpr : Term ← `(Compiler.CompilationModel.Expr.literal 0)
5675+
let guardExpr : Term ←
5676+
`(Compiler.CompilationModel.Expr.logicalNot
5677+
(Compiler.CompilationModel.Expr.eq $bExpr $zeroExpr))
5678+
pure (guardExpr, valueExpr)
5679+
| _ =>
5680+
throwErrorAt rhs s!"unsupported {label} source; expected safeAdd, safeSub, safeMul, or safeDiv"
56455681
let rhs := stripParens rhs
56465682
match rhs with
56475683
| `(term| requireSomeUint $optExpr:term $msg:term) =>
56485684
let msgLit ← strTerm <$> expectStringLiteral msg
5649-
let optExpr := stripParens optExpr
5650-
match optExpr with
5651-
| `(term| safeAdd $a:term $b:term) =>
5652-
let aExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals a
5653-
let bExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals b
5654-
let valueExpr : Term ← `(Compiler.CompilationModel.Expr.add $aExpr $bExpr)
5655-
let guardExpr : Term ← `(Compiler.CompilationModel.Expr.ge $valueExpr $aExpr)
5656-
pure (some #[
5657-
(← `(Compiler.CompilationModel.Stmt.require $guardExpr $msgLit)),
5658-
(← `(Compiler.CompilationModel.Stmt.letVar $(strTerm varName) $valueExpr))
5659-
])
5660-
| `(term| safeSub $a:term $b:term) =>
5661-
let aExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals a
5662-
let bExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals b
5663-
let valueExpr : Term ← `(Compiler.CompilationModel.Expr.sub $aExpr $bExpr)
5664-
let guardExpr : Term ← `(Compiler.CompilationModel.Expr.ge $aExpr $bExpr)
5665-
pure (some #[
5666-
(← `(Compiler.CompilationModel.Stmt.require $guardExpr $msgLit)),
5667-
(← `(Compiler.CompilationModel.Stmt.letVar $(strTerm varName) $valueExpr))
5668-
])
5669-
| `(term| safeMul $a:term $b:term) =>
5670-
let aExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals a
5671-
let bExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals b
5672-
let valueExpr : Term ← `(Compiler.CompilationModel.Expr.mul $aExpr $bExpr)
5673-
let zeroExpr : Term ← `(Compiler.CompilationModel.Expr.literal 0)
5674-
let divisorZeroExpr : Term ← `(Compiler.CompilationModel.Expr.eq $bExpr $zeroExpr)
5675-
let quotientExpr : Term ← `(Compiler.CompilationModel.Expr.div $valueExpr $bExpr)
5676-
let noOverflowExpr : Term ← `(Compiler.CompilationModel.Expr.eq $quotientExpr $aExpr)
5677-
let guardExpr : Term ← `(Compiler.CompilationModel.Expr.logicalOr $divisorZeroExpr $noOverflowExpr)
5678-
pure (some #[
5679-
(← `(Compiler.CompilationModel.Stmt.require $guardExpr $msgLit)),
5680-
(← `(Compiler.CompilationModel.Stmt.letVar $(strTerm varName) $valueExpr))
5681-
])
5682-
| `(term| safeDiv $a:term $b:term) =>
5683-
let aExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals a
5684-
let bExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals b
5685-
let valueExpr : Term ← `(Compiler.CompilationModel.Expr.div $aExpr $bExpr)
5686-
let zeroExpr : Term ← `(Compiler.CompilationModel.Expr.literal 0)
5687-
let guardExpr : Term ←
5688-
`(Compiler.CompilationModel.Expr.logicalNot
5689-
(Compiler.CompilationModel.Expr.eq $bExpr $zeroExpr))
5690-
pure (some #[
5691-
(← `(Compiler.CompilationModel.Stmt.require $guardExpr $msgLit)),
5692-
(← `(Compiler.CompilationModel.Stmt.letVar $(strTerm varName) $valueExpr))
5693-
])
5694-
| _ =>
5695-
throwErrorAt rhs "unsupported requireSomeUint source; expected safeAdd, safeSub, safeMul, or safeDiv"
5685+
let (guardExpr, valueExpr) ← translateSafeUintGuardAndValue optExpr "requireSomeUint"
5686+
pure (some #[
5687+
(← `(Compiler.CompilationModel.Stmt.require $guardExpr $msgLit)),
5688+
(← `(Compiler.CompilationModel.Stmt.letVar $(strTerm varName) $valueExpr))
5689+
])
56965690
-- Typed-error counterpart to `requireSomeUint`. The lowering mirrors the
56975691
-- string-message variant exactly, except the guard becomes a
56985692
-- `Stmt.requireError` emitting a 4-byte selector revert with the supplied
56995693
-- error name and argument list.
57005694
| `(term| requireSomeUintError $optExpr:term $errorName:ident($args,*)) =>
57015695
let errorNameLit := strTerm (toString errorName.getId)
57025696
let argExprs ← args.getElems.mapM (translatePureExprWithTypes fields constDecls immutableDecls params locals)
5703-
let optExpr := stripParens optExpr
5704-
match optExpr with
5705-
| `(term| safeAdd $a:term $b:term) =>
5706-
let aExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals a
5707-
let bExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals b
5708-
let valueExpr : Term ← `(Compiler.CompilationModel.Expr.add $aExpr $bExpr)
5709-
let guardExpr : Term ← `(Compiler.CompilationModel.Expr.ge $valueExpr $aExpr)
5710-
pure (some #[
5711-
(← `(Compiler.CompilationModel.Stmt.requireError $guardExpr $errorNameLit [ $[$argExprs],* ])),
5712-
(← `(Compiler.CompilationModel.Stmt.letVar $(strTerm varName) $valueExpr))
5713-
])
5714-
| `(term| safeSub $a:term $b:term) =>
5715-
let aExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals a
5716-
let bExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals b
5717-
let valueExpr : Term ← `(Compiler.CompilationModel.Expr.sub $aExpr $bExpr)
5718-
let guardExpr : Term ← `(Compiler.CompilationModel.Expr.ge $aExpr $bExpr)
5719-
pure (some #[
5720-
(← `(Compiler.CompilationModel.Stmt.requireError $guardExpr $errorNameLit [ $[$argExprs],* ])),
5721-
(← `(Compiler.CompilationModel.Stmt.letVar $(strTerm varName) $valueExpr))
5722-
])
5723-
| `(term| safeMul $a:term $b:term) =>
5724-
let aExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals a
5725-
let bExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals b
5726-
let valueExpr : Term ← `(Compiler.CompilationModel.Expr.mul $aExpr $bExpr)
5727-
let zeroExpr : Term ← `(Compiler.CompilationModel.Expr.literal 0)
5728-
let divisorZeroExpr : Term ← `(Compiler.CompilationModel.Expr.eq $bExpr $zeroExpr)
5729-
let quotientExpr : Term ← `(Compiler.CompilationModel.Expr.div $valueExpr $bExpr)
5730-
let noOverflowExpr : Term ← `(Compiler.CompilationModel.Expr.eq $quotientExpr $aExpr)
5731-
let guardExpr : Term ← `(Compiler.CompilationModel.Expr.logicalOr $divisorZeroExpr $noOverflowExpr)
5732-
pure (some #[
5733-
(← `(Compiler.CompilationModel.Stmt.requireError $guardExpr $errorNameLit [ $[$argExprs],* ])),
5734-
(← `(Compiler.CompilationModel.Stmt.letVar $(strTerm varName) $valueExpr))
5735-
])
5736-
| `(term| safeDiv $a:term $b:term) =>
5737-
let aExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals a
5738-
let bExpr ← translatePureExprWithTypes fields constDecls immutableDecls params locals b
5739-
let valueExpr : Term ← `(Compiler.CompilationModel.Expr.div $aExpr $bExpr)
5740-
let zeroExpr : Term ← `(Compiler.CompilationModel.Expr.literal 0)
5741-
let guardExpr : Term ←
5742-
`(Compiler.CompilationModel.Expr.logicalNot
5743-
(Compiler.CompilationModel.Expr.eq $bExpr $zeroExpr))
5744-
pure (some #[
5745-
(← `(Compiler.CompilationModel.Stmt.requireError $guardExpr $errorNameLit [ $[$argExprs],* ])),
5746-
(← `(Compiler.CompilationModel.Stmt.letVar $(strTerm varName) $valueExpr))
5747-
])
5748-
| _ =>
5749-
throwErrorAt rhs "unsupported requireSomeUintError source; expected safeAdd, safeSub, safeMul, or safeDiv"
5697+
let (guardExpr, valueExpr) ← translateSafeUintGuardAndValue optExpr "requireSomeUintError"
5698+
pure (some #[
5699+
(← `(Compiler.CompilationModel.Stmt.requireError $guardExpr $errorNameLit [ $[$argExprs],* ])),
5700+
(← `(Compiler.CompilationModel.Stmt.letVar $(strTerm varName) $valueExpr))
5701+
])
57505702
-- Solidity-0.8 default-revert arithmetic (verity#1752): `let x ← addPanic a b`
57515703
-- lowers to the same IR as
57525704
-- `let x ← requireSomeUint (safeAdd a b) "Panic(0x11): arithmetic overflow"`,

docs-site/content/edsl-api-reference.mdx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ open Verity.EVM.Uint256
1414
open Verity.Stdlib.Math
1515
```
1616

17-
Every primitive is a `Contract α = ContractState → ContractResult α`. Read-only primitives never revert; write primitives return `success ()`; only `require` and `requireSomeUint` (and `revertError`) may emit `ContractResult.revert`. Proof lemmas for every primitive live in `Verity/Core.lean` (one `_run` lemma per primitive) and `Verity/Proofs/Stdlib/Automation.lean` / `Verity/Proofs/Stdlib/MappingAutomation.lean` (`_runState` / `_runValue` mirrors). See [Core Architecture](/core) for the model.
17+
Every primitive is a `Contract α = ContractState → ContractResult α`. Read-only primitives never revert; write primitives return `success ()`; only `require`, `requireSomeUint`, `requireSomeUintError`, and explicit error reverts such as `revertError` may emit `ContractResult.revert`. Proof lemmas for every primitive live in `Verity/Core.lean` (one `_run` lemma per primitive) and `Verity/Proofs/Stdlib/Automation.lean` / `Verity/Proofs/Stdlib/MappingAutomation.lean` (`_runState` / `_runValue` mirrors). See [Core Architecture](/core) for the model.
1818

1919
## Storage
2020

@@ -48,7 +48,7 @@ def require (condition : Bool) (message : String) : Contract Unit
4848
def bind {α β : Type} (ma : Contract α) (f : α → Contract β) : Contract β
4949
```
5050

51-
`require false msg` is the only revert primitive. Use it in place of an explicit `revert`. `bind` short-circuits on revert and powers do-notation.
51+
`require false msg` emits a string-message revert. Use `revertError` or helpers such as `requireSomeUintError` when a typed custom-error revert is required. `bind` short-circuits on revert and powers do-notation.
5252

5353
```verity
5454
require (amount > 0) "amount must be nonzero"
@@ -104,13 +104,25 @@ def safeMul (a b : Uint256) : Option Uint256
104104
def safeDiv (a b : Uint256) : Option Uint256
105105
106106
def requireSomeUint (opt : Option Uint256) (message : String) : Contract Uint256
107+
def requireSomeUintError (opt : Option Uint256) ErrorName(args...) : Contract Uint256
107108
108109
let sum <- requireSomeUint (safeAdd a b) "overflow"
109110
let product <- requireSomeUint (safeMul a b) "overflow"
110111
let quot <- requireSomeUint (safeDiv a b) "division by zero"
111112
```
112113

113-
`verity_contract` lowers `requireSomeUint` over `safeAdd`/`Sub`/`Mul`/`Div` to explicit compiled `require` guards. Bare `add`/`sub`/`mul`/`div` stay wrapping.
114+
`verity_contract` lowers `requireSomeUint` over `safeAdd`/`Sub`/`Mul`/`Div` to explicit compiled `require` guards. Use `requireSomeUintError` with errors declared in the contract's `errors` block when checked arithmetic should revert with a typed custom error instead of a string message.
115+
116+
```verity
117+
errors
118+
error AddOverflow()
119+
error MulOverflow(Uint256, Uint256)
120+
121+
let sum <- requireSomeUintError (safeAdd a b) AddOverflow()
122+
let product <- requireSomeUintError (safeMul sum b) MulOverflow(sum, b)
123+
```
124+
125+
Bare `add`/`sub`/`mul`/`div` stay wrapping.
114126

115127
### Fixed-point helpers
116128

docs/ARITHMETIC_PROFILE.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,20 @@ For contracts that require overflow protection, the EDSL provides checked operat
8585
| `mulDiv512Down? a b c` | `Option Uint256` | `none` if `c = 0` or `floor(a * b / c) > 2^256 - 1`; product is unbounded |
8686
| `mulDiv512Up? a b c` | `Option Uint256` | `none` if `c = 0` or `ceil(a * b / c) > 2^256 - 1`; product is unbounded |
8787

88-
Checked operations are **explicit EDSL-level constructs**. The compiler does not insert overflow checks for bare `add`/`sub`/`mul`, and bare `div` keeps EVM division-by-zero semantics. Contracts that need checked behavior must explicitly use `safeAdd`/`safeSub`/`safeMul`/`safeDiv` and handle the `Option` result. In `verity_contract`, `requireSomeUint (safeAdd ...)`, `requireSomeUint (safeSub ...)`, `requireSomeUint (safeMul ...)`, and `requireSomeUint (safeDiv ...)` lower to concrete `require` guards followed by the corresponding arithmetic result binding. The `mulDiv512...?` helpers are proof/modeling helpers for full-precision Solidity `Math.mulDiv` semantics; compiled Yul lowering for a first-class 512-bit division primitive is still tracked by #1761.
88+
Checked operations are **explicit EDSL-level constructs**. The compiler does not insert overflow checks for bare `add`/`sub`/`mul`, and bare `div` keeps EVM division-by-zero semantics. Contracts that need checked behavior must explicitly use `safeAdd`/`safeSub`/`safeMul`/`safeDiv` and handle the `Option` result. In `verity_contract`, `requireSomeUint (safeAdd ...)`, `requireSomeUint (safeSub ...)`, `requireSomeUint (safeMul ...)`, and `requireSomeUint (safeDiv ...)` lower to concrete `require` guards followed by the corresponding arithmetic result binding. `requireSomeUintError (safeAdd ...) ErrorName(args)` and the corresponding `safeSub`/`safeMul`/`safeDiv` forms lower to the same guards but emit typed custom errors through `requireError`. The `mulDiv512...?` helpers are proof/modeling helpers for full-precision Solidity `Math.mulDiv` semantics; compiled Yul lowering for a first-class 512-bit division primitive is still tracked by #1761.
89+
90+
Example checked arithmetic with a typed custom error:
91+
92+
```lean
93+
errors
94+
error AddOverflow ()
95+
error MulOverflow (Uint256, Uint256)
96+
97+
function checked (a : Uint256, b : Uint256) : Uint256 := do
98+
let sum ← requireSomeUintError (safeAdd a b) AddOverflow()
99+
let product ← requireSomeUintError (safeMul sum b) MulOverflow(sum, b)
100+
return product
101+
```
89102

90103
For Solidity-0.8-style source models, prefer the panic wrappers
91104
`addPanic`/`subPanic`/`mulPanic`/`divPanic`. They are thin `Contract` wrappers
@@ -175,7 +188,7 @@ The arithmetic model is invariant across profiles. See [`docs/SOLIDITY_PARITY_PR
175188

176189
1. Confirm that the contract's arithmetic assumptions match wrapping semantics.
177190
2. If overflow or division-by-zero protection is required, verify the contract uses `safeAdd`/`safeSub`/`safeMul`/`safeDiv`.
178-
3. Check that `requireSomeUint` is used to revert on overflow/underflow or zero divisors.
191+
3. Check that `requireSomeUint` or `requireSomeUintError` is used to revert on overflow/underflow or zero divisors.
179192
4. Review `Compiler/Proofs/ArithmeticProfile.lean` for the formal wrapping proofs.
180193
5. Confirm the backend profile does not affect arithmetic behavior (it doesn't).
181194

0 commit comments

Comments
 (0)