Skip to content

Commit 7dbdf0d

Browse files
committed
Add revertIf helper and setIfLess example
1 parent c3bf8bf commit 7dbdf0d

9 files changed

Lines changed: 145 additions & 8 deletions

File tree

STATUS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ Last updated: 2026-02-10
4848
- Added `requireEq`/`requireNeq` helpers plus a `compareAndSwap` example + Foundry test.
4949
- Added `requireGt` helper plus a `setIfGreater` example + Foundry test (and refactored guarded add to use it).
5050
- Added `sstoreAdd` helper plus a `bumpSlot` example + Foundry test (and refactored add-slot examples to use it).
51+
- Added a `revertIf` helper, refactored `checkHealth`, and added a `setIfLess` example + Foundry test.
52+
- Updated compiler entries + direct EVM asm for `setIfLess`.
5153
- Minimal docs frontend and compressed docs.
5254
- Further docs tightening (shorter guide + text).
5355
- External landscape scan (spec languages, model checkers, prover stacks).

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 `revertIf` stdlib helper for guard-style reverts.
5+
- Refactored `checkHealth` to use `revertIf`.
6+
- Added `setIfLess` example (guarded store on `value < max`) with SpecR + proofs.
7+
- Added Foundry test for `setIfLess`.
8+
- Updated compiler entries + direct EVM asm for `setIfLess`.
49
- Added `sstoreAdd` stdlib helper to make slot increments less noisy.
510
- Added `bumpSlot` example (increment slot by one) with a basic Spec + proof.
611
- Refactored `addSlot` and `guardedAddSlot` to use `sstoreAdd`.

research/lean_only_proto/DumbContracts/Compiler.lean

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,9 +178,21 @@ def exampleEntry9 : EntryPoint :=
178178
selector := 0xb8df0e12
179179
returns := false }
180180

181+
def exampleEntry10 : EntryPoint :=
182+
{ name := "setIfLess"
183+
args := ["slot", "value", "max"]
184+
body :=
185+
Lang.Stmt.if_
186+
(Lang.Expr.lt (Lang.Expr.var "value") (Lang.Expr.var "max"))
187+
(Lang.Stmt.sstore (Lang.Expr.var "slot") (Lang.Expr.var "value"))
188+
Lang.Stmt.revert
189+
-- setIfLess(uint256,uint256,uint256) -> 0x7e5acdb6
190+
selector := 0x7e5acdb6
191+
returns := false }
192+
181193
def exampleEntries : List EntryPoint :=
182194
[exampleEntry, exampleEntry2, exampleEntry3, exampleEntry4, exampleEntry5, exampleEntry6, exampleEntry7,
183-
exampleEntry8, exampleEntry9]
195+
exampleEntry8, exampleEntry9, exampleEntry10]
184196

185197
def healthEntrySet : EntryPoint :=
186198
{ name := "setRisk"

research/lean_only_proto/DumbContracts/EvmAsm.lean

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,30 @@ def directProgramAsm : List String :=
4848
, "EQ"
4949
, "PUSH2 tag_setIfGreater"
5050
, "JUMPI"
51+
, "DUP1"
5152
, "PUSH4 0xb8df0e12"
5253
, "EQ"
5354
, "PUSH2 tag_bumpSlot"
5455
, "JUMPI"
56+
, "PUSH4 0x7e5acdb6"
57+
, "EQ"
58+
, "PUSH2 tag_setIfLess"
59+
, "JUMPI"
5560
, "PUSH0"
5661
, "DUP1"
5762
, "REVERT"
63+
, "tag_setIfLess:"
64+
, "PUSH2 ret_setIfLess"
65+
, "PUSH1 0x44"
66+
, "CALLDATALOAD"
67+
, "PUSH1 0x24"
68+
, "CALLDATALOAD"
69+
, "PUSH1 0x04"
70+
, "CALLDATALOAD"
71+
, "PUSH2 fn_setIfLess"
72+
, "JUMP"
73+
, "ret_setIfLess:"
74+
, "STOP"
5875
, "tag_bumpSlot:"
5976
, "PUSH2 ret_bumpSlot"
6077
, "PUSH1 0x04"
@@ -308,6 +325,32 @@ def directProgramAsm : List String :=
308325
, "SWAP1"
309326
, "SSTORE"
310327
, "JUMP"
328+
, "fn_setIfLess:"
329+
, "DUP2"
330+
, "SWAP1"
331+
, "DUP4"
332+
, "DUP3"
333+
, "LT"
334+
, "PUSH2 setIfLess_ok"
335+
, "JUMPI"
336+
, "setIfLess_check:"
337+
, "POP"
338+
, "POP"
339+
, "LT"
340+
, "ISZERO"
341+
, "PUSH2 setIfLess_revert"
342+
, "JUMPI"
343+
, "JUMP"
344+
, "setIfLess_revert:"
345+
, "PUSH0"
346+
, "DUP1"
347+
, "REVERT"
348+
, "setIfLess_ok:"
349+
, "SSTORE"
350+
, "DUP1"
351+
, "PUSH0"
352+
, "PUSH2 setIfLess_check"
353+
, "JUMP"
311354
]
312355

313356
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
@@ -7,4 +7,5 @@ import DumbContracts.Examples.MaxStore
77
import DumbContracts.Examples.NonZeroStore
88
import DumbContracts.Examples.CompareAndSwap
99
import DumbContracts.Examples.SetIfGreater
10+
import DumbContracts.Examples.SetIfLess
1011
import DumbContracts.Examples.BumpSlot

research/lean_only_proto/DumbContracts/Examples/Risk.lean

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,8 @@ def setRiskFun : Fun :=
3434
def checkHealthFun : Fun :=
3535
{ name := "checkHealth"
3636
args := []
37-
body := unless
37+
body := revertIf
3838
(Expr.lt (sloadSlot 0) (Expr.mul (sloadSlot 1) (sloadSlot 2)))
39-
Stmt.skip
4039
ret := none }
4140

4241
-- Execution facts.
@@ -52,13 +51,13 @@ theorem setRisk_updates (collateral debt minHF : Nat) :
5251
theorem checkHealth_ok (collateral debt minHF : Nat) (h : debt * minHF <= collateral) :
5352
execFun checkHealthFun [] (riskStore collateral debt minHF) [] =
5453
ExecResult.ok (bindArgs emptyEnv [] []) (riskStore collateral debt minHF) := by
55-
simp [checkHealthFun, unless, sloadSlot, execFun, execStmt, evalExpr, riskStore,
54+
simp [checkHealthFun, revertIf, sloadSlot, execFun, execStmt, evalExpr, riskStore,
5655
bindArgs, emptyEnv, updateEnv, updateStore, not_lt_of_ge h]
5756

5857
theorem checkHealth_reverts (collateral debt minHF : Nat) (h : collateral < debt * minHF) :
5958
execFun checkHealthFun [] (riskStore collateral debt minHF) [] =
6059
ExecResult.reverted := by
61-
simp [checkHealthFun, unless, sloadSlot, execFun, execStmt, evalExpr, riskStore,
60+
simp [checkHealthFun, revertIf, sloadSlot, execFun, execStmt, evalExpr, riskStore,
6261
bindArgs, emptyEnv, updateEnv, updateStore, h]
6362

6463
-- Risk specs (Store-level).
@@ -95,7 +94,7 @@ theorem checkHealth_meets_spec (s : Store) :
9594
| ExecResult.ok _ s' => checkHealthSpec.ensures s s'
9695
| _ => False) := by
9796
intro hreq
98-
simp [checkHealthSpec, riskOk, checkHealthFun, unless, sloadSlot, execFun, execStmt,
97+
simp [checkHealthSpec, riskOk, checkHealthFun, revertIf, sloadSlot, execFun, execStmt,
9998
evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, not_lt_of_ge hreq]
10099

101100
theorem checkHealth_meets_specR_ok (s : Store) :
@@ -104,14 +103,14 @@ theorem checkHealth_meets_specR_ok (s : Store) :
104103
| ExecResult.ok _ s' => checkHealthSpecR.ensures s s'
105104
| _ => False) := by
106105
intro hreq
107-
simp [checkHealthSpecR, riskOk, checkHealthFun, unless, sloadSlot, execFun, execStmt,
106+
simp [checkHealthSpecR, riskOk, checkHealthFun, revertIf, sloadSlot, execFun, execStmt,
108107
evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, not_lt_of_ge hreq]
109108

110109
theorem checkHealth_meets_specR_reverts (s : Store) :
111110
checkHealthSpecR.reverts s ->
112111
execFun checkHealthFun [] s [] = ExecResult.reverted := by
113112
intro hrev
114-
simp [checkHealthSpecR, checkHealthFun, unless, sloadSlot, execFun, execStmt, evalExpr,
113+
simp [checkHealthSpecR, checkHealthFun, revertIf, sloadSlot, execFun, execStmt, evalExpr,
115114
bindArgs, emptyEnv, updateEnv, updateStore, hrev]
116115

117116
end DumbContracts.Examples
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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 below a maximum. -/
13+
14+
def setIfLessFun : Fun :=
15+
{ name := "setIfLess"
16+
args := ["slot", "value", "max"]
17+
body :=
18+
revertIf (Expr.not (Expr.lt (v "value") (v "max"))) ;;
19+
sstoreVar "slot" (v "value")
20+
ret := none }
21+
22+
def setIfLessSpecR (slot value max : Nat) : SpecR Store :=
23+
{ requires := fun _ => value < max
24+
ensures := fun s s' => s' = updateStore s slot value
25+
reverts := fun _ => value >= max }
26+
27+
theorem setIfLess_meets_specR_ok (s : Store) (slot value max : Nat) :
28+
(setIfLessSpecR slot value max).requires s ->
29+
(match execFun setIfLessFun [slot, value, max] s [] with
30+
| ExecResult.ok _ s' => (setIfLessSpecR slot value max).ensures s s'
31+
| _ => False) := by
32+
intro hreq
33+
have hlt : value < max := by exact hreq
34+
simp [setIfLessSpecR, setIfLessFun, revertIf, sstoreVar, v, execFun, execStmt, evalExpr,
35+
bindArgs, emptyEnv, updateEnv, updateStore, hlt]
36+
37+
theorem setIfLess_meets_specR_reverts (s : Store) (slot value max : Nat) :
38+
(setIfLessSpecR slot value max).reverts s ->
39+
execFun setIfLessFun [slot, value, max] s [] = ExecResult.reverted := by
40+
intro hrev
41+
have hnot : ¬ value < max := by
42+
exact Nat.not_lt.mpr hrev
43+
simp [setIfLessSpecR, setIfLessFun, revertIf, sstoreVar, v, execFun, execStmt, evalExpr,
44+
bindArgs, emptyEnv, updateEnv, updateStore, hnot]
45+
46+
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 revertIf (cond : Expr) : Stmt :=
20+
Stmt.if_ cond Stmt.revert Stmt.skip
21+
1922
def assert (cond : Expr) : Stmt :=
2023
Stmt.if_ cond Stmt.skip Stmt.revert
2124

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 GeneratedSetIfLessTest is GeneratedBase {
7+
function testSetIfLess() public {
8+
bytes memory creation = _readHexFile("out/example.creation.bin");
9+
address deployed = _deploy(creation);
10+
11+
bytes4 selSet = 0x7e5acdb6;
12+
bytes4 selGet = 0x7eba7ba6;
13+
14+
(bool ok,) = deployed.call(abi.encodeWithSelector(selSet, 21, 7, 10));
15+
require(ok, "setIfLess failed (value < 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 setIfLess value");
22+
23+
(ok,) = deployed.call(abi.encodeWithSelector(selSet, 22, 9, 9));
24+
require(!ok, "setIfLess should revert when value >= max");
25+
}
26+
}

0 commit comments

Comments
 (0)