Skip to content

Commit de05f3f

Browse files
committed
Add requireGt helper and setIfGreater example
1 parent 768fadf commit de05f3f

9 files changed

Lines changed: 145 additions & 9 deletions

File tree

STATUS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Last updated: 2026-02-10
4646
- Added a `requireNonZero` helper and a tiny `setNonZero` example + Foundry test.
4747
- Aligned direct EVM asm with solc for `setNonZero` (label pushes use `PUSH2`).
4848
- Added `requireEq`/`requireNeq` helpers plus a `compareAndSwap` example + Foundry test.
49+
- Added `requireGt` helper plus a `setIfGreater` example + Foundry test (and refactored guarded add to use it).
4950
- Minimal docs frontend and compressed docs.
5051
- Further docs tightening (shorter guide + text).
5152
- External landscape scan (spec languages, model checkers, prover stacks).

docs/research-log.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Research Log
22

33
## 2026-02-10
4+
- Added `requireGt` stdlib helper to reduce guard boilerplate.
5+
- Added `setIfGreater` example (guarded store on `value > min`) with SpecR + proofs.
6+
- Refactored `guardedAddSlot` to use `requireGt`.
7+
- Added Foundry test for `setIfGreater`.
48
- Added `eq`/`neq` plus `requireEq`/`requireNeq` stdlib helpers.
59
- Added `compareAndSwap` example (guarded store on expected value match) with SpecR and proofs.
610
- Avoided double `sload` in `compareAndSwap` by caching the slot value with `let_` (prevents false reverts).

research/lean_only_proto/DumbContracts/Compiler.lean

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,21 @@ def exampleEntry7 : EntryPoint :=
155155
selector := 0xc74962fa
156156
returns := false }
157157

158+
def exampleEntry8 : EntryPoint :=
159+
{ name := "setIfGreater"
160+
args := ["slot", "value", "min"]
161+
body :=
162+
Lang.Stmt.if_
163+
(Lang.Expr.gt (Lang.Expr.var "value") (Lang.Expr.var "min"))
164+
(Lang.Stmt.sstore (Lang.Expr.var "slot") (Lang.Expr.var "value"))
165+
Lang.Stmt.revert
166+
-- setIfGreater(uint256,uint256,uint256) -> 0x2dbeb1ba
167+
selector := 0x2dbeb1ba
168+
returns := false }
169+
158170
def exampleEntries : List EntryPoint :=
159-
[exampleEntry, exampleEntry2, exampleEntry3, exampleEntry4, exampleEntry5, exampleEntry6, exampleEntry7]
171+
[exampleEntry, exampleEntry2, exampleEntry3, exampleEntry4, exampleEntry5, exampleEntry6, exampleEntry7,
172+
exampleEntry8]
160173

161174
def healthEntrySet : EntryPoint :=
162175
{ name := "setRisk"

research/lean_only_proto/DumbContracts/EvmAsm.lean

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,30 @@ def directProgramAsm : List String :=
3838
, "EQ"
3939
, "PUSH2 tag_setNonZero"
4040
, "JUMPI"
41+
, "DUP1"
4142
, "PUSH4 0xc74962fa"
4243
, "EQ"
4344
, "PUSH2 tag_compareAndSwap"
4445
, "JUMPI"
46+
, "PUSH4 0x2dbeb1ba"
47+
, "EQ"
48+
, "PUSH2 tag_setIfGreater"
49+
, "JUMPI"
4550
, "PUSH0"
4651
, "DUP1"
4752
, "REVERT"
53+
, "tag_setIfGreater:"
54+
, "PUSH2 ret_setIfGreater"
55+
, "PUSH1 0x44"
56+
, "CALLDATALOAD"
57+
, "PUSH1 0x24"
58+
, "CALLDATALOAD"
59+
, "PUSH1 0x04"
60+
, "CALLDATALOAD"
61+
, "PUSH2 fn_setIfGreater"
62+
, "JUMP"
63+
, "ret_setIfGreater:"
64+
, "STOP"
4865
, "tag_compareAndSwap:"
4966
, "PUSH2 ret_compareAndSwap"
5067
, "PUSH1 0x44"
@@ -244,6 +261,32 @@ def directProgramAsm : List String :=
244261
, "DUP1"
245262
, "PUSH2 compareAndSwap_check"
246263
, "JUMP"
264+
, "fn_setIfGreater:"
265+
, "DUP2"
266+
, "SWAP1"
267+
, "DUP4"
268+
, "DUP3"
269+
, "GT"
270+
, "PUSH2 setIfGreater_ok"
271+
, "JUMPI"
272+
, "setIfGreater_check:"
273+
, "POP"
274+
, "POP"
275+
, "GT"
276+
, "ISZERO"
277+
, "PUSH2 setIfGreater_revert"
278+
, "JUMPI"
279+
, "JUMP"
280+
, "setIfGreater_revert:"
281+
, "PUSH0"
282+
, "DUP1"
283+
, "REVERT"
284+
, "setIfGreater_ok:"
285+
, "SSTORE"
286+
, "DUP1"
287+
, "PUSH0"
288+
, "PUSH2 setIfGreater_check"
289+
, "JUMP"
247290
]
248291

249292
def pretty (lines : List String) : String :=

research/lean_only_proto/DumbContracts/Examples.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ import DumbContracts.Examples.TokenTransfer
66
import DumbContracts.Examples.MaxStore
77
import DumbContracts.Examples.NonZeroStore
88
import DumbContracts.Examples.CompareAndSwap
9+
import DumbContracts.Examples.SetIfGreater
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import DumbContracts.Lang
2+
import DumbContracts.Semantics
3+
import DumbContracts.Stdlib
4+
5+
namespace DumbContracts.Examples
6+
7+
open DumbContracts.Lang
8+
open DumbContracts.Semantics
9+
open DumbContracts
10+
open DumbContracts.Std
11+
12+
/-- Store a value into a slot only if it exceeds a minimum. -/
13+
14+
def setIfGreaterFun : Fun :=
15+
{ name := "setIfGreater"
16+
args := ["slot", "value", "min"]
17+
body := requireGt (v "value") (v "min") (sstoreVar "slot" (v "value"))
18+
ret := none }
19+
20+
def setIfGreaterSpecR (slot value min : Nat) : SpecR Store :=
21+
{ requires := fun _ => value > min
22+
ensures := fun s s' => s' = updateStore s slot value
23+
reverts := fun _ => value <= min }
24+
25+
theorem setIfGreater_meets_specR_ok (s : Store) (slot value min : Nat) :
26+
(setIfGreaterSpecR slot value min).requires s ->
27+
(match execFun setIfGreaterFun [slot, value, min] s [] with
28+
| ExecResult.ok _ s' => (setIfGreaterSpecR slot value min).ensures s s'
29+
| _ => False) := by
30+
intro hreq
31+
have hgt : value > min := by exact hreq
32+
simp [setIfGreaterSpecR, setIfGreaterFun, requireGt, require, execFun, execStmt, evalExpr,
33+
bindArgs, emptyEnv, updateEnv, updateStore, hgt]
34+
35+
theorem setIfGreater_meets_specR_reverts (s : Store) (slot value min : Nat) :
36+
(setIfGreaterSpecR slot value min).reverts s ->
37+
execFun setIfGreaterFun [slot, value, min] s [] = ExecResult.reverted := by
38+
intro hrev
39+
have hnot : ¬ value > min := by
40+
exact Nat.not_lt.mpr hrev
41+
simp [setIfGreaterSpecR, setIfGreaterFun, requireGt, require, execFun, execStmt, evalExpr,
42+
bindArgs, emptyEnv, updateEnv, updateStore, hnot]
43+
44+
end DumbContracts.Examples

research/lean_only_proto/DumbContracts/Examples/StoreOps.lean

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ def addSlotFun : Fun :=
3232
def guardedAddSlotFun : Fun :=
3333
{ name := "guardedAddSlot"
3434
args := ["slot", "delta"]
35-
body := require
36-
(Expr.gt (Expr.var "delta") (Expr.lit 0))
35+
body := requireGt
36+
(v "delta")
37+
(n 0)
3738
(Stmt.sstore (Expr.var "slot")
3839
(Expr.add (Expr.sload (Expr.var "slot")) (Expr.var "delta")))
3940
ret := none }
@@ -80,13 +81,13 @@ theorem guarded_add_updates (slot base delta : Nat) (h : delta > 0) :
8081
execFun guardedAddSlotFun [slot, delta] (storeOf slot base) [] =
8182
ExecResult.ok (bindArgs emptyEnv ["slot", "delta"] [slot, delta]) (storeOf slot (base + delta)) := by
8283
simp [guardedAddSlotFun, require, execFun, execStmt, evalExpr, storeOf, bindArgs, emptyEnv,
83-
updateEnv, updateStore, h]
84+
updateEnv, updateStore, h, requireGt, v, n]
8485

8586
theorem guarded_add_reverts (slot base delta : Nat) (h : delta = 0) :
8687
execFun guardedAddSlotFun [slot, delta] (storeOf slot base) [] =
8788
ExecResult.reverted := by
8889
simp [guardedAddSlotFun, require, execFun, execStmt, evalExpr, storeOf, bindArgs, emptyEnv,
89-
updateEnv, updateStore, h]
90+
updateEnv, updateStore, h, requireGt, v, n]
9091

9192
-- Storage specs (Store-level).
9293

@@ -119,7 +120,7 @@ theorem guardedAddSlot_meets_spec (s : Store) (slot delta : Nat) :
119120
intro hreq
120121
have hpos : delta > 0 := by exact hreq
121122
simp [guardedAddSlotSpec, guardedAddSlotFun, require, execFun, execStmt, evalExpr,
122-
bindArgs, emptyEnv, updateEnv, updateStore, hpos]
123+
bindArgs, emptyEnv, updateEnv, updateStore, hpos, requireGt, v, n]
123124

124125
theorem guardedAddSlot_meets_specR_ok (s : Store) (slot delta : Nat) :
125126
(guardedAddSlotSpecR slot delta).requires s ->
@@ -129,21 +130,21 @@ theorem guardedAddSlot_meets_specR_ok (s : Store) (slot delta : Nat) :
129130
intro hreq
130131
have hpos : delta > 0 := by exact hreq
131132
simp [guardedAddSlotSpecR, guardedAddSlotFun, require, execFun, execStmt, evalExpr,
132-
bindArgs, emptyEnv, updateEnv, updateStore, hpos]
133+
bindArgs, emptyEnv, updateEnv, updateStore, hpos, requireGt, v, n]
133134

134135
theorem guardedAddSlot_meets_specR_reverts (s : Store) (slot delta : Nat) :
135136
(guardedAddSlotSpecR slot delta).reverts s ->
136137
execFun guardedAddSlotFun [slot, delta] s [] = ExecResult.reverted := by
137138
intro hrev
138139
simp [guardedAddSlotSpecR, guardedAddSlotFun, require, execFun, execStmt, evalExpr,
139-
bindArgs, emptyEnv, updateEnv, updateStore, hrev]
140+
bindArgs, emptyEnv, updateEnv, updateStore, hrev, requireGt, v, n]
140141

141142
theorem guardedAddSlot_reverts_when_not_requires (slot delta : Nat) (h : delta = 0) :
142143
(guardedAddSlotSpec slot delta).requires (storeOf slot 0) = False ∧
143144
execFun guardedAddSlotFun [slot, delta] (storeOf slot 0) [] = ExecResult.reverted := by
144145
constructor
145146
· simp [guardedAddSlotSpec, h]
146147
· simp [guardedAddSlotFun, require, execFun, execStmt, evalExpr, storeOf, bindArgs, emptyEnv,
147-
updateEnv, updateStore, h]
148+
updateEnv, updateStore, h, requireGt, v, n]
148149

149150
end DumbContracts.Examples

research/lean_only_proto/DumbContracts/Stdlib.lean

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ def requireEq (lhs rhs : Expr) (body : Stmt) : Stmt :=
3131
def requireNeq (lhs rhs : Expr) (body : Stmt) : Stmt :=
3232
require (neq lhs rhs) body
3333

34+
def requireGt (lhs rhs : Expr) (body : Stmt) : Stmt :=
35+
require (Expr.gt lhs rhs) body
36+
3437
def requireNonZero (value : Expr) (body : Stmt) : Stmt :=
3538
requireNeq value (Expr.lit 0) body
3639

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import "./GeneratedBase.t.sol";
5+
6+
contract GeneratedSetIfGreaterTest is GeneratedBase {
7+
function testSetIfGreater() public {
8+
bytes memory creation = _readHexFile("out/example.creation.bin");
9+
address deployed = _deploy(creation);
10+
11+
bytes4 selSet = 0x2dbeb1ba;
12+
bytes4 selGet = 0x7eba7ba6;
13+
14+
(bool ok,) = deployed.call(abi.encodeWithSelector(selSet, 11, 10, 5));
15+
require(ok, "setIfGreater failed (value > min)");
16+
17+
bytes memory data;
18+
(ok, data) = deployed.call(abi.encodeWithSelector(selGet, 11));
19+
require(ok, "getSlot failed (slot 11)");
20+
uint256 val = abi.decode(data, (uint256));
21+
require(val == 10, "unexpected setIfGreater value");
22+
23+
(ok,) = deployed.call(abi.encodeWithSelector(selSet, 12, 4, 9));
24+
require(!ok, "setIfGreater should revert when value <= min");
25+
}
26+
}

0 commit comments

Comments
 (0)