Skip to content

Commit 30c2a2c

Browse files
committed
Add requireAnd helper and setIfNonZeroAndLess example
1 parent 083fa13 commit 30c2a2c

9 files changed

Lines changed: 172 additions & 9 deletions

File tree

STATUS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Last updated: 2026-02-10
1919
- Decide whether to guard old-state specs with `from ≠ to` or adopt sequential reads by default.
2020
- Supply accounting abstraction (list vs set/dedup semantics).
2121
- EDSL ergonomics: add helpers, notations, and a minimal stdlib for common patterns.
22-
- Iteration: add `letSload` helper and an `initOnce` example to make cached-slot guards less noisy.
22+
- Iteration: add `requireAnd` helper plus a `setIfNonZeroAndLess` example to reduce nested guard boilerplate.
2323

2424
## Recently Done
2525
- Lean -> Yul pipeline with runtime + creation bytecode artifacts.

docs/research-log.md

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

33
## 2026-02-10
4+
- Added `requireAnd` stdlib helper to combine guard conditions cleanly.
5+
- Refactored `setIfBetween` to use `requireAnd` instead of nested guards.
6+
- Added `setIfNonZeroAndLess` example (nonzero + max guard) with SpecR + proofs.
7+
- Added Foundry test for `setIfNonZeroAndLess`.
8+
- Updated compiler entries + direct EVM asm for `setIfNonZeroAndLess`.
49
- Added `letSload` stdlib helper for caching a slot read via `let_`.
510
- Refactored `compareAndSwap` to use `letSload`.
611
- Added `initOnce` example (only initialize a zero slot) with SpecR + proofs.

research/lean_only_proto/DumbContracts/Compiler.lean

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,9 +218,23 @@ def exampleEntry11 : EntryPoint :=
218218
selector := 0x5ebc58db
219219
returns := false }
220220

221+
def exampleEntry13 : EntryPoint :=
222+
{ name := "setIfNonZeroAndLess"
223+
args := ["slot", "value", "max"]
224+
body :=
225+
Lang.Stmt.if_
226+
(Lang.Expr.and
227+
(Lang.Expr.not (Lang.Expr.eq (Lang.Expr.var "value") (Lang.Expr.lit 0)))
228+
(Lang.Expr.lt (Lang.Expr.var "value") (Lang.Expr.var "max")))
229+
(Lang.Stmt.sstore (Lang.Expr.var "slot") (Lang.Expr.var "value"))
230+
Lang.Stmt.revert
231+
-- setIfNonZeroAndLess(uint256,uint256,uint256) -> 0x8ba43d8f
232+
selector := 0x8ba43d8f
233+
returns := false }
234+
221235
def exampleEntries : List EntryPoint :=
222236
[exampleEntry, exampleEntry2, exampleEntry3, exampleEntry4, exampleEntry5, exampleEntry6, exampleEntry7,
223-
exampleEntry12, exampleEntry8, exampleEntry9, exampleEntry10, exampleEntry11]
237+
exampleEntry12, exampleEntry8, exampleEntry9, exampleEntry10, exampleEntry11, exampleEntry13]
224238

225239
def healthEntrySet : EntryPoint :=
226240
{ name := "setRisk"

research/lean_only_proto/DumbContracts/EvmAsm.lean

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,30 @@ def directProgramAsm : List String :=
6363
, "EQ"
6464
, "PUSH2 tag_setIfLess"
6565
, "JUMPI"
66+
, "DUP1"
6667
, "PUSH4 0x5ebc58db"
6768
, "EQ"
6869
, "PUSH2 tag_setIfBetween"
6970
, "JUMPI"
71+
, "PUSH4 0x8ba43d8f"
72+
, "EQ"
73+
, "PUSH2 tag_setIfNonZeroAndLess"
74+
, "JUMPI"
7075
, "PUSH0"
7176
, "DUP1"
7277
, "REVERT"
78+
, "tag_setIfNonZeroAndLess:"
79+
, "PUSH2 ret_setIfNonZeroAndLess"
80+
, "PUSH1 0x44"
81+
, "CALLDATALOAD"
82+
, "PUSH1 0x24"
83+
, "CALLDATALOAD"
84+
, "PUSH1 0x04"
85+
, "CALLDATALOAD"
86+
, "PUSH2 fn_setIfNonZeroAndLess"
87+
, "JUMP"
88+
, "ret_setIfNonZeroAndLess:"
89+
, "STOP"
7390
, "tag_setIfBetween:"
7491
, "PUSH2 ret_setIfBetween"
7592
, "PUSH1 0x64"
@@ -464,6 +481,43 @@ def directProgramAsm : List String :=
464481
, "PUSH0"
465482
, "PUSH2 setIfBetween_inner_check"
466483
, "JUMP"
484+
, "fn_setIfNonZeroAndLess:"
485+
, "SWAP2"
486+
, "DUP2"
487+
, "PUSH0"
488+
, "SWAP3"
489+
, "SWAP4"
490+
, "DUP3"
491+
, "DUP3"
492+
, "LT"
493+
, "DUP5"
494+
, "DUP4"
495+
, "EQ"
496+
, "ISZERO"
497+
, "AND"
498+
, "PUSH2 setIfNonZeroAndLess_ok"
499+
, "JUMPI"
500+
, "setIfNonZeroAndLess_check:"
501+
, "POP"
502+
, "LT"
503+
, "SWAP2"
504+
, "EQ"
505+
, "ISZERO"
506+
, "AND"
507+
, "ISZERO"
508+
, "PUSH2 setIfNonZeroAndLess_revert"
509+
, "JUMPI"
510+
, "JUMP"
511+
, "setIfNonZeroAndLess_revert:"
512+
, "PUSH0"
513+
, "DUP1"
514+
, "REVERT"
515+
, "setIfNonZeroAndLess_ok:"
516+
, "SSTORE"
517+
, "DUP3"
518+
, "PUSH0"
519+
, "PUSH2 setIfNonZeroAndLess_check"
520+
, "JUMP"
467521
]
468522

469523
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
@@ -10,4 +10,5 @@ import DumbContracts.Examples.InitOnce
1010
import DumbContracts.Examples.SetIfGreater
1111
import DumbContracts.Examples.SetIfLess
1212
import DumbContracts.Examples.SetIfBetween
13+
import DumbContracts.Examples.SetIfNonZeroAndLess
1314
import DumbContracts.Examples.BumpSlot

research/lean_only_proto/DumbContracts/Examples/SetIfBetween.lean

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ def setIfBetweenFun : Fun :=
1515
{ name := "setIfBetween"
1616
args := ["slot", "value", "min", "max"]
1717
body :=
18-
requireGt (v "value") (v "min")
19-
(requireLt (v "value") (v "max")
20-
(sstoreVar "slot" (v "value")))
18+
requireAnd
19+
(Expr.gt (v "value") (v "min"))
20+
(Expr.lt (v "value") (v "max"))
21+
(sstoreVar "slot" (v "value"))
2122
ret := none }
2223

2324
def setIfBetweenSpecR (slot value min max : Nat) : SpecR Store :=
@@ -32,7 +33,7 @@ theorem setIfBetween_meets_specR_ok (s : Store) (slot value min max : Nat) :
3233
| _ => False) := by
3334
intro hreq
3435
rcases hreq with ⟨hgt, hlt⟩
35-
simp [setIfBetweenSpecR, setIfBetweenFun, requireGt, requireLt, require, sstoreVar, v, execFun,
36+
simp [setIfBetweenSpecR, setIfBetweenFun, requireAnd, require, sstoreVar, v, execFun,
3637
execStmt, evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hgt, hlt]
3738

3839
theorem setIfBetween_meets_specR_reverts (s : Store) (slot value min max : Nat) :
@@ -42,16 +43,16 @@ theorem setIfBetween_meets_specR_reverts (s : Store) (slot value min max : Nat)
4243
rcases hrev with hle | hge
4344
· have hnot : ¬ value > min := by
4445
exact Nat.not_lt.mpr hle
45-
simp [setIfBetweenSpecR, setIfBetweenFun, requireGt, requireLt, require, sstoreVar, v, execFun,
46+
simp [setIfBetweenSpecR, setIfBetweenFun, requireAnd, require, sstoreVar, v, execFun,
4647
execStmt, evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hnot]
4748
· by_cases hgt : value > min
4849
· have hnotlt : ¬ value < max := by
4950
exact Nat.not_lt.mpr hge
50-
simp [setIfBetweenSpecR, setIfBetweenFun, requireGt, requireLt, require, sstoreVar, v,
51+
simp [setIfBetweenSpecR, setIfBetweenFun, requireAnd, require, sstoreVar, v,
5152
execFun, execStmt, evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hgt, hnotlt]
5253
· have hnot : ¬ value > min := by
5354
exact hgt
54-
simp [setIfBetweenSpecR, setIfBetweenFun, requireGt, requireLt, require, sstoreVar, v,
55+
simp [setIfBetweenSpecR, setIfBetweenFun, requireAnd, require, sstoreVar, v,
5556
execFun, execStmt, evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hnot]
5657

5758
end DumbContracts.Examples
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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 is non-zero and below a max. -/
13+
14+
def setIfNonZeroAndLessFun : Fun :=
15+
{ name := "setIfNonZeroAndLess"
16+
args := ["slot", "value", "max"]
17+
body :=
18+
requireAnd
19+
(neq (v "value") (n 0))
20+
(Expr.lt (v "value") (v "max"))
21+
(sstoreVar "slot" (v "value"))
22+
ret := none }
23+
24+
def setIfNonZeroAndLessSpecR (slot value max : Nat) : SpecR Store :=
25+
{ requires := fun _ => value != 0 ∧ value < max
26+
ensures := fun s s' => s' = updateStore s slot value
27+
reverts := fun _ => value = 0 ∨ value >= max }
28+
29+
theorem setIfNonZeroAndLess_meets_specR_ok (s : Store) (slot value max : Nat) :
30+
(setIfNonZeroAndLessSpecR slot value max).requires s ->
31+
(match execFun setIfNonZeroAndLessFun [slot, value, max] s [] with
32+
| ExecResult.ok _ s' => (setIfNonZeroAndLessSpecR slot value max).ensures s s'
33+
| _ => False) := by
34+
intro hreq
35+
rcases hreq with ⟨hnonzero, hlt⟩
36+
simp [setIfNonZeroAndLessSpecR, setIfNonZeroAndLessFun, requireAnd, require, neq, eq, sstoreVar,
37+
v, n, execFun, execStmt, evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hnonzero, hlt]
38+
39+
theorem setIfNonZeroAndLess_meets_specR_reverts (s : Store) (slot value max : Nat) :
40+
(setIfNonZeroAndLessSpecR slot value max).reverts s ->
41+
execFun setIfNonZeroAndLessFun [slot, value, max] s [] = ExecResult.reverted := by
42+
intro hrev
43+
rcases hrev with hzero | hge
44+
· simp [setIfNonZeroAndLessSpecR, setIfNonZeroAndLessFun, requireAnd, require, neq, eq, sstoreVar,
45+
v, n, execFun, execStmt, evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hzero]
46+
· by_cases hnonzero : value != 0
47+
· have hnotlt : ¬ value < max := by
48+
exact Nat.not_lt.mpr hge
49+
simp [setIfNonZeroAndLessSpecR, setIfNonZeroAndLessFun, requireAnd, require, neq, eq, sstoreVar,
50+
v, n, execFun, execStmt, evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hnonzero, hnotlt]
51+
· have hnot : ¬ value != 0 := by
52+
exact hnonzero
53+
simp [setIfNonZeroAndLessSpecR, setIfNonZeroAndLessFun, requireAnd, require, neq, eq, sstoreVar,
54+
v, n, execFun, execStmt, evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hnot]
55+
56+
end DumbContracts.Examples

research/lean_only_proto/DumbContracts/Stdlib.lean

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ def require (cond : Expr) (body : Stmt) : Stmt :=
1616
def unless (cond : Expr) (body : Stmt) : Stmt :=
1717
Stmt.if_ cond Stmt.revert body
1818

19+
def requireAnd (lhs rhs : Expr) (body : Stmt) : Stmt :=
20+
require (Expr.and lhs rhs) body
21+
1922
def revertIf (cond : Expr) : Stmt :=
2023
Stmt.if_ cond Stmt.revert Stmt.skip
2124

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

0 commit comments

Comments
 (0)