Skip to content

Commit 748b0da

Browse files
committed
Add requireZero helper and initToOne example
1 parent 1670842 commit 748b0da

9 files changed

Lines changed: 141 additions & 7 deletions

File tree

STATUS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ 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 `requireGte` helper and a tiny `setIfAtLeast` example + Foundry test.
22+
- Iteration: add `requireZero` helper and a tiny `initToOne` example + Foundry test.
2323

2424
## Recently Done
25+
- Added `requireZero` helper and an `initToOne` example + Foundry test.
2526
- Lean -> Yul pipeline with runtime + creation bytecode artifacts.
2627
- Selector map (fixed + ABI keccak) and Foundry tests.
2728
- Spec-style wrappers for storage and Euler-style risk examples.

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 `requireZero` stdlib helper for zero guards.
5+
- Refactored `initOnce` to use `requireZero`.
6+
- Added `initToOne` example (initialize slot to `1` when empty) with SpecR + proofs.
7+
- Added Foundry test for `initToOne`.
8+
- Updated compiler entries + direct EVM asm for `initToOne`.
49
- Added `requireGte` stdlib helper for `>=` guards (via `not lt`).
510
- Refactored `subIfEnough` to use `requireGte`.
611
- Added `setIfAtLeast` example (guarded store on `value >= min`) with SpecR + proofs.

research/lean_only_proto/DumbContracts/Compiler.lean

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,19 @@ def exampleEntry12 : EntryPoint :=
194194
selector := 0xd3b9b05a
195195
returns := false }
196196

197+
def exampleEntry20 : EntryPoint :=
198+
{ name := "initToOne"
199+
args := ["slot"]
200+
body :=
201+
Lang.Stmt.let_ "current" (Lang.Expr.sload (Lang.Expr.var "slot"))
202+
(Lang.Stmt.if_
203+
(Lang.Expr.eq (Lang.Expr.var "current") (Lang.Expr.lit 0))
204+
(Lang.Stmt.sstore (Lang.Expr.var "slot") (Lang.Expr.lit 1))
205+
Lang.Stmt.revert)
206+
-- initToOne(uint256) -> 0x1ebe7f68
207+
selector := 0x1ebe7f68
208+
returns := false }
209+
197210
def exampleEntry8 : EntryPoint :=
198211
{ name := "setIfGreater"
199212
args := ["slot", "value", "min"]
@@ -320,9 +333,9 @@ def exampleEntry14 : EntryPoint :=
320333

321334
def exampleEntries : List EntryPoint :=
322335
[exampleEntry, exampleEntry2, exampleEntry3, exampleEntry4, exampleEntry5, exampleEntry15,
323-
exampleEntry16, exampleEntry6, exampleEntry7, exampleEntry12, exampleEntry8, exampleEntry9,
324-
exampleEntry19, exampleEntry10, exampleEntry11, exampleEntry17, exampleEntry18, exampleEntry13,
325-
exampleEntry14]
336+
exampleEntry16, exampleEntry6, exampleEntry7, exampleEntry12, exampleEntry20, exampleEntry8,
337+
exampleEntry9, exampleEntry19, exampleEntry10, exampleEntry11, exampleEntry17, exampleEntry18,
338+
exampleEntry13, exampleEntry14]
326339

327340
def healthEntrySet : EntryPoint :=
328341
{ name := "setRisk"

research/lean_only_proto/DumbContracts/EvmAsm.lean

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ def directProgramAsm : List String :=
5959
, "PUSH2 tag_initOnce"
6060
, "JUMPI"
6161
, "DUP1"
62+
, "PUSH4 0x1ebe7f68"
63+
, "EQ"
64+
, "PUSH2 tag_initToOne"
65+
, "JUMPI"
66+
, "DUP1"
6267
, "PUSH4 0x2dbeb1ba"
6368
, "EQ"
6469
, "PUSH2 tag_setIfGreater"
@@ -207,6 +212,14 @@ def directProgramAsm : List String :=
207212
, "JUMP"
208213
, "ret_setIfGreater:"
209214
, "STOP"
215+
, "tag_initToOne:"
216+
, "PUSH2 ret_initToOne"
217+
, "PUSH1 0x04"
218+
, "CALLDATALOAD"
219+
, "PUSH2 fn_initToOne"
220+
, "JUMP"
221+
, "ret_initToOne:"
222+
, "STOP"
210223
, "tag_initOnce:"
211224
, "PUSH2 ret_initOnce"
212225
, "PUSH1 0x24"
@@ -524,6 +537,34 @@ def directProgramAsm : List String :=
524537
, "DUP1"
525538
, "PUSH2 initOnce_check"
526539
, "JUMP"
540+
, "fn_initToOne:"
541+
, "PUSH0"
542+
, "SWAP1"
543+
, "DUP1"
544+
, "SLOAD"
545+
, "SWAP1"
546+
, "DUP3"
547+
, "DUP3"
548+
, "EQ"
549+
, "PUSH2 initToOne_ok"
550+
, "JUMPI"
551+
, "initToOne_check:"
552+
, "POP"
553+
, "SUB"
554+
, "PUSH2 initToOne_revert"
555+
, "JUMPI"
556+
, "JUMP"
557+
, "initToOne_revert:"
558+
, "PUSH0"
559+
, "DUP1"
560+
, "REVERT"
561+
, "initToOne_ok:"
562+
, "PUSH1 0x01"
563+
, "SWAP1"
564+
, "SSTORE"
565+
, "PUSH0"
566+
, "PUSH2 initToOne_check"
567+
, "JUMP"
527568
, "fn_setIfGreater:"
528569
, "DUP2"
529570
, "SWAP1"

research/lean_only_proto/DumbContracts/Examples.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import DumbContracts.Examples.UpdateMin
99
import DumbContracts.Examples.NonZeroStore
1010
import DumbContracts.Examples.CompareAndSwap
1111
import DumbContracts.Examples.InitOnce
12+
import DumbContracts.Examples.InitToOne
1213
import DumbContracts.Examples.SetIfGreater
1314
import DumbContracts.Examples.SetIfAtLeast
1415
import DumbContracts.Examples.SetIfLess

research/lean_only_proto/DumbContracts/Examples/InitOnce.lean

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def initOnceFun : Fun :=
1616
args := ["slot", "value"]
1717
body :=
1818
letSload "current" (v "slot")
19-
(requireEq (v "current") (n 0) (sstoreVar "slot" (v "value")))
19+
(requireZero (v "current") (sstoreVar "slot" (v "value")))
2020
ret := none }
2121

2222
def initOnceSpecR (slot value : Nat) : SpecR Store :=
@@ -31,14 +31,14 @@ theorem initOnce_meets_specR_ok (s : Store) (slot value : Nat) :
3131
| _ => False) := by
3232
intro hreq
3333
have hzero : s slot = 0 := by exact hreq
34-
simp [initOnceSpecR, initOnceFun, letSload, requireEq, eq, require, execFun, execStmt,
34+
simp [initOnceSpecR, initOnceFun, letSload, requireZero, requireEq, eq, require, execFun, execStmt,
3535
evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hzero]
3636

3737
theorem initOnce_meets_specR_reverts (s : Store) (slot value : Nat) :
3838
(initOnceSpecR slot value).reverts s ->
3939
execFun initOnceFun [slot, value] s [] = ExecResult.reverted := by
4040
intro hrev
41-
simp [initOnceSpecR, initOnceFun, letSload, requireEq, eq, require, execFun, execStmt,
41+
simp [initOnceSpecR, initOnceFun, letSload, requireZero, requireEq, eq, require, execFun, execStmt,
4242
evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hrev]
4343

4444
end DumbContracts.Examples
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+
/-- Initialize a slot to 1 if it is currently zero. -/
13+
14+
def initToOneFun : Fun :=
15+
{ name := "initToOne"
16+
args := ["slot"]
17+
body :=
18+
letSload "current" (v "slot")
19+
(requireZero (v "current") (sstoreVar "slot" (n 1)))
20+
ret := none }
21+
22+
def initToOneSpecR (slot : Nat) : SpecR Store :=
23+
{ requires := fun s => s slot = 0
24+
ensures := fun s s' => s' = updateStore s slot 1
25+
reverts := fun s => s slot != 0 }
26+
27+
theorem initToOne_meets_specR_ok (s : Store) (slot : Nat) :
28+
(initToOneSpecR slot).requires s ->
29+
(match execFun initToOneFun [slot] s [] with
30+
| ExecResult.ok _ s' => (initToOneSpecR slot).ensures s s'
31+
| _ => False) := by
32+
intro hreq
33+
have hzero : s slot = 0 := by exact hreq
34+
simp [initToOneSpecR, initToOneFun, letSload, requireZero, requireEq, eq, require, execFun,
35+
execStmt, evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hzero]
36+
37+
theorem initToOne_meets_specR_reverts (s : Store) (slot : Nat) :
38+
(initToOneSpecR slot).reverts s ->
39+
execFun initToOneFun [slot] s [] = ExecResult.reverted := by
40+
intro hrev
41+
simp [initToOneSpecR, initToOneFun, letSload, requireZero, requireEq, eq, require, execFun,
42+
execStmt, evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hrev]
43+
44+
end DumbContracts.Examples

research/lean_only_proto/DumbContracts/Stdlib.lean

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ def requireBetween (value lo hi : Expr) (body : Stmt) : Stmt :=
5252
def requireNonZero (value : Expr) (body : Stmt) : Stmt :=
5353
requireNeq value (Expr.lit 0) body
5454

55+
def requireZero (value : Expr) (body : Stmt) : Stmt :=
56+
requireEq value (Expr.lit 0) body
57+
5558
/-- Shorthand for loading/storing fixed slots. -/
5659

5760
def letSload (name : String) (slot : Expr) (body : Stmt) : Stmt :=
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 GeneratedInitToOneTest is GeneratedBase {
7+
function testInitToOne() public {
8+
bytes memory creation = _readHexFile("out/example.creation.bin");
9+
address deployed = _deploy(creation);
10+
11+
bytes4 selInitToOne = 0x1ebe7f68;
12+
bytes4 selGet = 0x7eba7ba6;
13+
14+
(bool ok,) = deployed.call(abi.encodeWithSelector(selInitToOne, 20));
15+
require(ok, "initToOne failed (first init)");
16+
17+
bytes memory data;
18+
(ok, data) = deployed.call(abi.encodeWithSelector(selGet, 20));
19+
require(ok, "getSlot failed (slot 20)");
20+
uint256 val = abi.decode(data, (uint256));
21+
require(val == 1, "unexpected initToOne value");
22+
23+
(ok,) = deployed.call(abi.encodeWithSelector(selInitToOne, 20));
24+
require(!ok, "initToOne should revert when slot already set");
25+
}
26+
}

0 commit comments

Comments
 (0)