Skip to content

Commit 61e2376

Browse files
committed
Add requireBetween helper and addIfBetween example
1 parent 09ea02d commit 61e2376

9 files changed

Lines changed: 200 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 a minimal `sstoreMin` helper and a tiny `updateMin` example + Foundry test.
22+
- Iteration: add a minimal `requireBetween` helper and a tiny `addIfBetween` example + Foundry test.
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 `requireBetween` stdlib helper for strict range guards.
5+
- Refactored `setIfBetween` to use `requireBetween`.
6+
- Added `addIfBetween` example (guarded add on `min < delta < max`) with SpecR + proofs.
7+
- Added Foundry test for `addIfBetween`.
8+
- Updated compiler entries + direct EVM asm for `addIfBetween`.
49
- Added `sstoreMin` stdlib helper to mirror `sstoreMax` for min updates.
510
- Added `updateMin` example (monotonic min update against a stored slot) with Spec + proof.
611
- Added Foundry test for `updateMin`.

research/lean_only_proto/DumbContracts/Compiler.lean

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,23 @@ def exampleEntry11 : EntryPoint :=
244244
selector := 0x5ebc58db
245245
returns := false }
246246

247+
def exampleEntry17 : EntryPoint :=
248+
{ name := "addIfBetween"
249+
args := ["slot", "delta", "min", "max"]
250+
body :=
251+
Lang.Stmt.if_
252+
(Lang.Expr.gt (Lang.Expr.var "delta") (Lang.Expr.var "min"))
253+
(Lang.Stmt.if_
254+
(Lang.Expr.lt (Lang.Expr.var "delta") (Lang.Expr.var "max"))
255+
(Lang.Stmt.sstore
256+
(Lang.Expr.var "slot")
257+
(Lang.Expr.add (Lang.Expr.sload (Lang.Expr.var "slot")) (Lang.Expr.var "delta")))
258+
Lang.Stmt.revert)
259+
Lang.Stmt.revert
260+
-- addIfBetween(uint256,uint256,uint256,uint256) -> 0xbc0fcb79
261+
selector := 0xbc0fcb79
262+
returns := false }
263+
247264
def exampleEntry13 : EntryPoint :=
248265
{ name := "setIfNonZeroAndLess"
249266
args := ["slot", "value", "max"]
@@ -276,7 +293,7 @@ def exampleEntry14 : EntryPoint :=
276293
def exampleEntries : List EntryPoint :=
277294
[exampleEntry, exampleEntry2, exampleEntry3, exampleEntry4, exampleEntry5, exampleEntry15,
278295
exampleEntry16, exampleEntry6, exampleEntry7, exampleEntry12, exampleEntry8, exampleEntry9,
279-
exampleEntry10, exampleEntry11, exampleEntry13, exampleEntry14]
296+
exampleEntry10, exampleEntry11, exampleEntry17, exampleEntry13, exampleEntry14]
280297

281298
def healthEntrySet : EntryPoint :=
282299
{ name := "setRisk"

research/lean_only_proto/DumbContracts/EvmAsm.lean

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ def directProgramAsm : List String :=
7979
, "PUSH2 tag_setIfBetween"
8080
, "JUMPI"
8181
, "DUP1"
82+
, "PUSH4 0xbc0fcb79"
83+
, "EQ"
84+
, "PUSH2 tag_addIfBetween"
85+
, "JUMPI"
86+
, "DUP1"
8287
, "PUSH4 0x8ba43d8f"
8388
, "EQ"
8489
, "PUSH2 tag_setIfNonZeroAndLess"
@@ -110,6 +115,20 @@ def directProgramAsm : List String :=
110115
, "JUMP"
111116
, "ret_setIfNonZeroAndLess:"
112117
, "STOP"
118+
, "tag_addIfBetween:"
119+
, "PUSH2 ret_addIfBetween"
120+
, "PUSH1 0x64"
121+
, "CALLDATALOAD"
122+
, "PUSH1 0x44"
123+
, "CALLDATALOAD"
124+
, "PUSH1 0x24"
125+
, "CALLDATALOAD"
126+
, "PUSH1 0x04"
127+
, "CALLDATALOAD"
128+
, "PUSH2 fn_addIfBetween"
129+
, "JUMP"
130+
, "ret_addIfBetween:"
131+
, "STOP"
113132
, "tag_setIfBetween:"
114133
, "PUSH2 ret_setIfBetween"
115134
, "PUSH1 0x64"
@@ -584,6 +603,59 @@ def directProgramAsm : List String :=
584603
, "PUSH0"
585604
, "PUSH2 setIfBetween_inner_check"
586605
, "JUMP"
606+
, "fn_addIfBetween:"
607+
, "SWAP3"
608+
, "SWAP1"
609+
, "SWAP2"
610+
, "SWAP3"
611+
, "DUP4"
612+
, "DUP4"
613+
, "GT"
614+
, "PUSH2 addIfBetween_outer_ok"
615+
, "JUMPI"
616+
, "addIfBetween_outer_check:"
617+
, "POP"
618+
, "POP"
619+
, "GT"
620+
, "ISZERO"
621+
, "PUSH2 addIfBetween_revert_outer"
622+
, "JUMPI"
623+
, "JUMP"
624+
, "addIfBetween_revert_outer:"
625+
, "PUSH0"
626+
, "DUP1"
627+
, "REVERT"
628+
, "addIfBetween_outer_ok:"
629+
, "DUP2"
630+
, "DUP4"
631+
, "LT"
632+
, "PUSH2 addIfBetween_inner_ok"
633+
, "JUMPI"
634+
, "addIfBetween_inner_check:"
635+
, "POP"
636+
, "DUP2"
637+
, "LT"
638+
, "ISZERO"
639+
, "PUSH2 addIfBetween_revert_inner"
640+
, "JUMPI"
641+
, "PUSH0"
642+
, "DUP1"
643+
, "PUSH2 addIfBetween_outer_check"
644+
, "JUMP"
645+
, "addIfBetween_revert_inner:"
646+
, "PUSH0"
647+
, "DUP1"
648+
, "REVERT"
649+
, "addIfBetween_inner_ok:"
650+
, "DUP3"
651+
, "DUP2"
652+
, "SLOAD"
653+
, "ADD"
654+
, "SWAP1"
655+
, "SSTORE"
656+
, "PUSH0"
657+
, "PUSH2 addIfBetween_inner_check"
658+
, "JUMP"
587659
, "fn_setIfNonZeroAndLess:"
588660
, "SWAP2"
589661
, "DUP2"

research/lean_only_proto/DumbContracts/Examples.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import DumbContracts.Examples.InitOnce
1212
import DumbContracts.Examples.SetIfGreater
1313
import DumbContracts.Examples.SetIfLess
1414
import DumbContracts.Examples.SetIfBetween
15+
import DumbContracts.Examples.AddIfBetween
1516
import DumbContracts.Examples.SetIfNonZeroAndLess
1617
import DumbContracts.Examples.BumpSlot
1718
import DumbContracts.Examples.BumpIfNonZero
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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+
/-- Add a delta to a slot only if it is strictly between min and max. -/
13+
14+
def addIfBetweenFun : Fun :=
15+
{ name := "addIfBetween"
16+
args := ["slot", "delta", "min", "max"]
17+
body :=
18+
requireBetween
19+
(v "delta")
20+
(v "min")
21+
(v "max")
22+
(sstoreAdd (v "slot") (v "delta"))
23+
ret := none }
24+
25+
def addIfBetweenSpecR (slot delta min max : Nat) : SpecR Store :=
26+
{ requires := fun _ => delta > min ∧ delta < max
27+
ensures := fun s s' => s' = updateStore s slot (s slot + delta)
28+
reverts := fun _ => delta <= min ∨ delta >= max }
29+
30+
theorem addIfBetween_meets_specR_ok (s : Store) (slot delta min max : Nat) :
31+
(addIfBetweenSpecR slot delta min max).requires s ->
32+
(match execFun addIfBetweenFun [slot, delta, min, max] s [] with
33+
| ExecResult.ok _ s' => (addIfBetweenSpecR slot delta min max).ensures s s'
34+
| _ => False) := by
35+
intro hreq
36+
rcases hreq with ⟨hgt, hlt⟩
37+
simp [addIfBetweenSpecR, addIfBetweenFun, requireBetween, requireAnd, require, sstoreAdd, v, execFun,
38+
execStmt, evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hgt, hlt]
39+
40+
theorem addIfBetween_meets_specR_reverts (s : Store) (slot delta min max : Nat) :
41+
(addIfBetweenSpecR slot delta min max).reverts s ->
42+
execFun addIfBetweenFun [slot, delta, min, max] s [] = ExecResult.reverted := by
43+
intro hrev
44+
rcases hrev with hle | hge
45+
· have hnot : ¬ delta > min := by
46+
exact Nat.not_lt.mpr hle
47+
simp [addIfBetweenSpecR, addIfBetweenFun, requireBetween, requireAnd, require, sstoreAdd, v, execFun,
48+
execStmt, evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hnot]
49+
· by_cases hgt : delta > min
50+
· have hnotlt : ¬ delta < max := by
51+
exact Nat.not_lt.mpr hge
52+
simp [addIfBetweenSpecR, addIfBetweenFun, requireBetween, requireAnd, require, sstoreAdd, v,
53+
execFun, execStmt, evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hgt, hnotlt]
54+
· have hnot : ¬ delta > min := by
55+
exact hgt
56+
simp [addIfBetweenSpecR, addIfBetweenFun, requireBetween, requireAnd, require, sstoreAdd, v,
57+
execFun, execStmt, evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hnot]
58+
59+
end DumbContracts.Examples

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-
requireAnd
19-
(Expr.gt (v "value") (v "min"))
20-
(Expr.lt (v "value") (v "max"))
18+
requireBetween
19+
(v "value")
20+
(v "min")
21+
(v "max")
2122
(sstoreVar "slot" (v "value"))
2223
ret := none }
2324

@@ -33,7 +34,7 @@ theorem setIfBetween_meets_specR_ok (s : Store) (slot value min max : Nat) :
3334
| _ => False) := by
3435
intro hreq
3536
rcases hreq with ⟨hgt, hlt⟩
36-
simp [setIfBetweenSpecR, setIfBetweenFun, requireAnd, require, sstoreVar, v, execFun,
37+
simp [setIfBetweenSpecR, setIfBetweenFun, requireBetween, requireAnd, require, sstoreVar, v, execFun,
3738
execStmt, evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hgt, hlt]
3839

3940
theorem setIfBetween_meets_specR_reverts (s : Store) (slot value min max : Nat) :
@@ -43,16 +44,16 @@ theorem setIfBetween_meets_specR_reverts (s : Store) (slot value min max : Nat)
4344
rcases hrev with hle | hge
4445
· have hnot : ¬ value > min := by
4546
exact Nat.not_lt.mpr hle
46-
simp [setIfBetweenSpecR, setIfBetweenFun, requireAnd, require, sstoreVar, v, execFun,
47+
simp [setIfBetweenSpecR, setIfBetweenFun, requireBetween, requireAnd, require, sstoreVar, v, execFun,
4748
execStmt, evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hnot]
4849
· by_cases hgt : value > min
4950
· have hnotlt : ¬ value < max := by
5051
exact Nat.not_lt.mpr hge
51-
simp [setIfBetweenSpecR, setIfBetweenFun, requireAnd, require, sstoreVar, v,
52+
simp [setIfBetweenSpecR, setIfBetweenFun, requireBetween, requireAnd, require, sstoreVar, v,
5253
execFun, execStmt, evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hgt, hnotlt]
5354
· have hnot : ¬ value > min := by
5455
exact hgt
55-
simp [setIfBetweenSpecR, setIfBetweenFun, requireAnd, require, sstoreVar, v,
56+
simp [setIfBetweenSpecR, setIfBetweenFun, requireBetween, requireAnd, require, sstoreVar, v,
5657
execFun, execStmt, evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hnot]
5758

5859
end DumbContracts.Examples

research/lean_only_proto/DumbContracts/Stdlib.lean

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ def requireGt (lhs rhs : Expr) (body : Stmt) : Stmt :=
4343
def requireLt (lhs rhs : Expr) (body : Stmt) : Stmt :=
4444
require (Expr.lt lhs rhs) body
4545

46+
def requireBetween (value lo hi : Expr) (body : Stmt) : Stmt :=
47+
requireAnd (Expr.gt value lo) (Expr.lt value hi) body
48+
4649
def requireNonZero (value : Expr) (body : Stmt) : Stmt :=
4750
requireNeq value (Expr.lit 0) body
4851

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

0 commit comments

Comments
 (0)