Skip to content

Commit 1670842

Browse files
committed
Add requireGte helper and setIfAtLeast example
1 parent d77d6fe commit 1670842

9 files changed

Lines changed: 148 additions & 8 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 `sstoreSub` helper and a tiny `subIfEnough` example + Foundry test.
22+
- Iteration: add `requireGte` helper and a tiny `setIfAtLeast` 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 `requireGte` stdlib helper for `>=` guards (via `not lt`).
5+
- Refactored `subIfEnough` to use `requireGte`.
6+
- Added `setIfAtLeast` example (guarded store on `value >= min`) with SpecR + proofs.
7+
- Added Foundry test for `setIfAtLeast`.
8+
- Updated compiler entries + direct EVM asm for `setIfAtLeast`.
49
- Added `sstoreSub` stdlib helper for slot decrements.
510
- Refactored `transfer` to use `sstoreSub` for balance subtraction.
611
- Added `subIfEnough` example (guarded decrement when slot >= delta) with SpecR + proofs.

research/lean_only_proto/DumbContracts/Compiler.lean

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,18 @@ def exampleEntry8 : EntryPoint :=
206206
selector := 0x2dbeb1ba
207207
returns := false }
208208

209+
def exampleEntry19 : EntryPoint :=
210+
{ name := "setIfAtLeast"
211+
args := ["slot", "value", "min"]
212+
body :=
213+
Lang.Stmt.if_
214+
(Lang.Expr.not (Lang.Expr.lt (Lang.Expr.var "value") (Lang.Expr.var "min")))
215+
(Lang.Stmt.sstore (Lang.Expr.var "slot") (Lang.Expr.var "value"))
216+
Lang.Stmt.revert
217+
-- setIfAtLeast(uint256,uint256,uint256) -> 0x407cdd03
218+
selector := 0x407cdd03
219+
returns := false }
220+
209221
def exampleEntry9 : EntryPoint :=
210222
{ name := "bumpSlot"
211223
args := ["slot"]
@@ -309,7 +321,8 @@ def exampleEntry14 : EntryPoint :=
309321
def exampleEntries : List EntryPoint :=
310322
[exampleEntry, exampleEntry2, exampleEntry3, exampleEntry4, exampleEntry5, exampleEntry15,
311323
exampleEntry16, exampleEntry6, exampleEntry7, exampleEntry12, exampleEntry8, exampleEntry9,
312-
exampleEntry10, exampleEntry11, exampleEntry17, exampleEntry18, exampleEntry13, exampleEntry14]
324+
exampleEntry19, exampleEntry10, exampleEntry11, exampleEntry17, exampleEntry18, exampleEntry13,
325+
exampleEntry14]
313326

314327
def healthEntrySet : EntryPoint :=
315328
{ name := "setRisk"

research/lean_only_proto/DumbContracts/EvmAsm.lean

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ def directProgramAsm : List String :=
6969
, "PUSH2 tag_bumpSlot"
7070
, "JUMPI"
7171
, "DUP1"
72+
, "PUSH4 0x407cdd03"
73+
, "EQ"
74+
, "PUSH2 tag_setIfAtLeast"
75+
, "JUMPI"
76+
, "DUP1"
7277
, "PUSH4 0x7e5acdb6"
7378
, "EQ"
7479
, "PUSH2 tag_setIfLess"
@@ -170,6 +175,18 @@ def directProgramAsm : List String :=
170175
, "JUMP"
171176
, "ret_setIfLess:"
172177
, "STOP"
178+
, "tag_setIfAtLeast:"
179+
, "PUSH2 ret_setIfAtLeast"
180+
, "PUSH1 0x44"
181+
, "CALLDATALOAD"
182+
, "PUSH1 0x24"
183+
, "CALLDATALOAD"
184+
, "PUSH1 0x04"
185+
, "CALLDATALOAD"
186+
, "PUSH2 fn_setIfAtLeast"
187+
, "JUMP"
188+
, "ret_setIfAtLeast:"
189+
, "STOP"
173190
, "tag_bumpSlot:"
174191
, "PUSH2 ret_bumpSlot"
175192
, "PUSH1 0x04"
@@ -541,6 +558,32 @@ def directProgramAsm : List String :=
541558
, "SWAP1"
542559
, "SSTORE"
543560
, "JUMP"
561+
, "fn_setIfAtLeast:"
562+
, "DUP2"
563+
, "SWAP1"
564+
, "DUP4"
565+
, "DUP3"
566+
, "LT"
567+
, "ISZERO"
568+
, "PUSH2 setIfAtLeast_ok"
569+
, "JUMPI"
570+
, "setIfAtLeast_check:"
571+
, "POP"
572+
, "POP"
573+
, "LT"
574+
, "PUSH2 setIfAtLeast_revert"
575+
, "JUMPI"
576+
, "JUMP"
577+
, "setIfAtLeast_revert:"
578+
, "PUSH0"
579+
, "DUP1"
580+
, "REVERT"
581+
, "setIfAtLeast_ok:"
582+
, "SSTORE"
583+
, "DUP1"
584+
, "PUSH0"
585+
, "PUSH2 setIfAtLeast_check"
586+
, "JUMP"
544587
, "fn_setIfLess:"
545588
, "DUP2"
546589
, "SWAP1"

research/lean_only_proto/DumbContracts/Examples.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import DumbContracts.Examples.NonZeroStore
1010
import DumbContracts.Examples.CompareAndSwap
1111
import DumbContracts.Examples.InitOnce
1212
import DumbContracts.Examples.SetIfGreater
13+
import DumbContracts.Examples.SetIfAtLeast
1314
import DumbContracts.Examples.SetIfLess
1415
import DumbContracts.Examples.SetIfBetween
1516
import DumbContracts.Examples.AddIfBetween
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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 meets a minimum (>=). -/
13+
14+
def setIfAtLeastFun : Fun :=
15+
{ name := "setIfAtLeast"
16+
args := ["slot", "value", "min"]
17+
body := requireGte (v "value") (v "min") (sstoreVar "slot" (v "value"))
18+
ret := none }
19+
20+
def setIfAtLeastSpecR (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 setIfAtLeast_meets_specR_ok (s : Store) (slot value min : Nat) :
26+
(setIfAtLeastSpecR slot value min).requires s ->
27+
(match execFun setIfAtLeastFun [slot, value, min] s [] with
28+
| ExecResult.ok _ s' => (setIfAtLeastSpecR slot value min).ensures s s'
29+
| _ => False) := by
30+
intro hreq
31+
have hge : value >= min := by exact hreq
32+
have hnot : ¬ value < min := by
33+
exact not_lt_of_ge hge
34+
simp [setIfAtLeastSpecR, setIfAtLeastFun, requireGte, require, sstoreVar, v, execFun, execStmt,
35+
evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hnot]
36+
37+
theorem setIfAtLeast_meets_specR_reverts (s : Store) (slot value min : Nat) :
38+
(setIfAtLeastSpecR slot value min).reverts s ->
39+
execFun setIfAtLeastFun [slot, value, min] s [] = ExecResult.reverted := by
40+
intro hrev
41+
have hlt : value < min := by exact hrev
42+
simp [setIfAtLeastSpecR, setIfAtLeastFun, requireGte, require, sstoreVar, v, execFun, execStmt,
43+
evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hlt]
44+
45+
end DumbContracts.Examples

research/lean_only_proto/DumbContracts/Examples/SubIfEnough.lean

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ def subIfEnoughFun : Fun :=
1515
{ name := "subIfEnough"
1616
args := ["slot", "delta"]
1717
body := letSload "current" (v "slot")
18-
(require
19-
(Expr.not (Expr.lt (v "current") (v "delta")))
18+
(requireGte
19+
(v "current")
20+
(v "delta")
2021
(Stmt.sstore (v "slot") (Expr.sub (v "current") (v "delta"))))
2122
ret := none }
2223

@@ -34,15 +35,15 @@ theorem subIfEnough_meets_specR_ok (s : Store) (slot delta : Nat) :
3435
have hge : s slot >= delta := by exact hreq
3536
have hnot : ¬ s slot < delta := by
3637
exact not_lt_of_ge hge
37-
simp [subIfEnoughSpecR, subIfEnoughFun, letSload, require, v, execFun, execStmt, evalExpr,
38-
bindArgs, emptyEnv, updateEnv, updateStore, hnot]
38+
simp [subIfEnoughSpecR, subIfEnoughFun, letSload, requireGte, require, v, execFun, execStmt,
39+
evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hnot]
3940

4041
theorem subIfEnough_meets_specR_reverts (s : Store) (slot delta : Nat) :
4142
(subIfEnoughSpecR slot delta).reverts s ->
4243
execFun subIfEnoughFun [slot, delta] s [] = ExecResult.reverted := by
4344
intro hrev
4445
have hlt : s slot < delta := by exact hrev
45-
simp [subIfEnoughSpecR, subIfEnoughFun, letSload, require, v, execFun, execStmt, evalExpr,
46-
bindArgs, emptyEnv, updateEnv, updateStore, hlt]
46+
simp [subIfEnoughSpecR, subIfEnoughFun, letSload, requireGte, require, v, execFun, execStmt,
47+
evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hlt]
4748

4849
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 requireGte (lhs rhs : Expr) (body : Stmt) : Stmt :=
47+
require (Expr.not (Expr.lt lhs rhs)) body
48+
4649
def requireBetween (value lo hi : Expr) (body : Stmt) : Stmt :=
4750
requireAnd (Expr.gt value lo) (Expr.lt value hi) body
4851

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 GeneratedSetIfAtLeastTest is GeneratedBase {
7+
function testSetIfAtLeast() public {
8+
bytes memory creation = _readHexFile("out/example.creation.bin");
9+
address deployed = _deploy(creation);
10+
11+
bytes4 selSet = 0x407cdd03;
12+
bytes4 selGet = 0x7eba7ba6;
13+
14+
(bool ok,) = deployed.call(abi.encodeWithSelector(selSet, 13, 9, 9));
15+
require(ok, "setIfAtLeast failed (value == min)");
16+
17+
bytes memory data;
18+
(ok, data) = deployed.call(abi.encodeWithSelector(selGet, 13));
19+
require(ok, "getSlot failed (slot 13)");
20+
uint256 val = abi.decode(data, (uint256));
21+
require(val == 9, "unexpected setIfAtLeast value");
22+
23+
(ok,) = deployed.call(abi.encodeWithSelector(selSet, 14, 12, 10));
24+
require(ok, "setIfAtLeast failed (value > min)");
25+
26+
(ok,) = deployed.call(abi.encodeWithSelector(selSet, 15, 4, 9));
27+
require(!ok, "setIfAtLeast should revert when value < min");
28+
}
29+
}

0 commit comments

Comments
 (0)