Skip to content

Commit 8bec91b

Browse files
committed
Add sstoreIfZero helper and initDouble example
1 parent 99c0cd1 commit 8bec91b

10 files changed

Lines changed: 156 additions & 17 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 `requireNonZeroAndLt` helper and a tiny `addIfNonZeroAndLess` example + Foundry test.
22+
2323

2424
## Recently Done
25+
- Added `sstoreIfZero` helper and an `initDouble` example + Foundry test.
2526
- Added `requireZero` helper and an `initToOne` example + Foundry test.
2627
- Lean -> Yul pipeline with runtime + creation bytecode artifacts.
2728
- Selector map (fixed + ABI keccak) and Foundry tests.

docs/research-log.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,11 @@
110110
- Added Foundry tests for generated contracts.
111111
- Compressed docs + tightened minimal frontend.
112112
- Scanned external tooling landscape (Act, Scribble, Certora, SMTChecker, KEVM/Kontrol, VerX, Move Prover).
113+
# 2026-02-10
114+
- Added `sstoreIfZero` stdlib helper to package the common "load + require zero + store" pattern.
115+
- Refactored `initOnce` and `initToOne` to use `sstoreIfZero`.
116+
- Added `initDouble` example (initialize to `value * 2` on zero) with SpecR + proofs.
117+
- Added Foundry test for `initDouble`.
118+
- Updated compiler entries + direct EVM asm wiring for `initDouble`.
119+
- `end_to_end` initially failed due to direct EVM asm mismatch in `initDouble`; fixed by aligning the stack order (`swap1` prelude + `swap1` before `sstore`) with solc output.
120+
- Ran `PATH=/opt/lean-4.27.0/bin:$PATH lake build`, `./scripts/end_to_end.sh`, and `./scripts/foundry_test_generated.sh` (all green).

research/lean_only_proto/DumbContracts/Compiler.lean

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,21 @@ def exampleEntry20 : EntryPoint :=
220220
selector := 0x1ebe7f68
221221
returns := false }
222222

223+
def exampleEntry23 : EntryPoint :=
224+
{ name := "initDouble"
225+
args := ["slot", "value"]
226+
body :=
227+
Lang.Stmt.let_ "current" (Lang.Expr.sload (Lang.Expr.var "slot"))
228+
(Lang.Stmt.if_
229+
(Lang.Expr.eq (Lang.Expr.var "current") (Lang.Expr.lit 0))
230+
(Lang.Stmt.sstore
231+
(Lang.Expr.var "slot")
232+
(Lang.Expr.mul (Lang.Expr.var "value") (Lang.Expr.lit 2)))
233+
Lang.Stmt.revert)
234+
-- initDouble(uint256,uint256) -> 0xa756818e
235+
selector := 0xa756818e
236+
returns := false }
237+
223238
def exampleEntry8 : EntryPoint :=
224239
{ name := "setIfGreater"
225240
args := ["slot", "value", "min"]
@@ -363,8 +378,8 @@ def exampleEntry14 : EntryPoint :=
363378
def exampleEntries : List EntryPoint :=
364379
[exampleEntry, exampleEntry2, exampleEntry3, exampleEntry4, exampleEntry5, exampleEntry15,
365380
exampleEntry16, exampleEntry6, exampleEntry7, exampleEntry21, exampleEntry12, exampleEntry20,
366-
exampleEntry8, exampleEntry9, exampleEntry19, exampleEntry10, exampleEntry11, exampleEntry17,
367-
exampleEntry18, exampleEntry13, exampleEntry22, exampleEntry14]
381+
exampleEntry23, exampleEntry8, exampleEntry9, exampleEntry19, exampleEntry10, exampleEntry11,
382+
exampleEntry17, exampleEntry18, exampleEntry13, exampleEntry22, exampleEntry14]
368383

369384
def healthEntrySet : EntryPoint :=
370385
{ name := "setRisk"

research/lean_only_proto/DumbContracts/EvmAsm.lean

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ def directProgramAsm : List String :=
6969
, "PUSH2 tag_initToOne"
7070
, "JUMPI"
7171
, "DUP1"
72+
, "PUSH4 0xa756818e"
73+
, "EQ"
74+
, "PUSH2 tag_initDouble"
75+
, "JUMPI"
76+
, "DUP1"
7277
, "PUSH4 0x2dbeb1ba"
7378
, "EQ"
7479
, "PUSH2 tag_setIfGreater"
@@ -234,6 +239,16 @@ def directProgramAsm : List String :=
234239
, "JUMP"
235240
, "ret_setIfGreater:"
236241
, "STOP"
242+
, "tag_initDouble:"
243+
, "PUSH2 ret_initDouble"
244+
, "PUSH1 0x24"
245+
, "CALLDATALOAD"
246+
, "PUSH1 0x04"
247+
, "CALLDATALOAD"
248+
, "PUSH2 fn_initDouble"
249+
, "JUMP"
250+
, "ret_initDouble:"
251+
, "STOP"
237252
, "tag_initToOne:"
238253
, "PUSH2 ret_initToOne"
239254
, "PUSH1 0x04"
@@ -623,6 +638,38 @@ def directProgramAsm : List String :=
623638
, "PUSH0"
624639
, "PUSH2 initToOne_check"
625640
, "JUMP"
641+
, "fn_initDouble:"
642+
, "SWAP1"
643+
, "PUSH0"
644+
, "SWAP2"
645+
, "DUP1"
646+
, "SLOAD"
647+
, "SWAP2"
648+
, "DUP4"
649+
, "DUP4"
650+
, "EQ"
651+
, "PUSH2 initDouble_ok"
652+
, "JUMPI"
653+
, "initDouble_check:"
654+
, "POP"
655+
, "POP"
656+
, "SUB"
657+
, "PUSH2 initDouble_revert"
658+
, "JUMPI"
659+
, "JUMP"
660+
, "initDouble_revert:"
661+
, "PUSH0"
662+
, "DUP1"
663+
, "REVERT"
664+
, "initDouble_ok:"
665+
, "PUSH1 0x02"
666+
, "MUL"
667+
, "SWAP1"
668+
, "SSTORE"
669+
, "PUSH0"
670+
, "DUP1"
671+
, "PUSH2 initDouble_check"
672+
, "JUMP"
626673
, "fn_setIfGreater:"
627674
, "DUP2"
628675
, "SWAP1"

research/lean_only_proto/DumbContracts/Examples.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import DumbContracts.Examples.CompareAndSwap
1111
import DumbContracts.Examples.ClearIfEq
1212
import DumbContracts.Examples.InitOnce
1313
import DumbContracts.Examples.InitToOne
14+
import DumbContracts.Examples.InitDouble
1415
import DumbContracts.Examples.SetIfGreater
1516
import DumbContracts.Examples.SetIfAtLeast
1617
import DumbContracts.Examples.SetIfLess
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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 double the input if it is currently zero. -/
13+
14+
def initDoubleFun : Fun :=
15+
{ name := "initDouble"
16+
args := ["slot", "value"]
17+
body := sstoreIfZero (v "slot") (Expr.mul (v "value") (n 2))
18+
ret := none }
19+
20+
def initDoubleSpecR (slot value : Nat) : SpecR Store :=
21+
{ requires := fun s => s slot = 0
22+
ensures := fun s s' => s' = updateStore s slot (value * 2)
23+
reverts := fun s => s slot != 0 }
24+
25+
theorem initDouble_meets_specR_ok (s : Store) (slot value : Nat) :
26+
(initDoubleSpecR slot value).requires s ->
27+
(match execFun initDoubleFun [slot, value] s [] with
28+
| ExecResult.ok _ s' => (initDoubleSpecR slot value).ensures s s'
29+
| _ => False) := by
30+
intro hreq
31+
have hzero : s slot = 0 := by exact hreq
32+
simp [initDoubleSpecR, initDoubleFun, sstoreIfZero, letSload, requireZero, requireEq, eq, require,
33+
execFun, execStmt, evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hzero]
34+
35+
theorem initDouble_meets_specR_reverts (s : Store) (slot value : Nat) :
36+
(initDoubleSpecR slot value).reverts s ->
37+
execFun initDoubleFun [slot, value] s [] = ExecResult.reverted := by
38+
intro hrev
39+
simp [initDoubleSpecR, initDoubleFun, sstoreIfZero, letSload, requireZero, requireEq, eq, require,
40+
execFun, execStmt, evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hrev]
41+
42+
end DumbContracts.Examples

research/lean_only_proto/DumbContracts/Examples/InitOnce.lean

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ open DumbContracts.Std
1414
def initOnceFun : Fun :=
1515
{ name := "initOnce"
1616
args := ["slot", "value"]
17-
body :=
18-
letSload "current" (v "slot")
19-
(requireZero (v "current") (sstoreVar "slot" (v "value")))
17+
body := sstoreIfZero (v "slot") (v "value")
2018
ret := none }
2119

2220
def initOnceSpecR (slot value : Nat) : SpecR Store :=
@@ -31,14 +29,14 @@ theorem initOnce_meets_specR_ok (s : Store) (slot value : Nat) :
3129
| _ => False) := by
3230
intro hreq
3331
have hzero : s slot = 0 := by exact hreq
34-
simp [initOnceSpecR, initOnceFun, letSload, requireZero, requireEq, eq, require, execFun, execStmt,
35-
evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hzero]
32+
simp [initOnceSpecR, initOnceFun, sstoreIfZero, letSload, requireZero, requireEq, eq, require,
33+
execFun, execStmt, evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hzero]
3634

3735
theorem initOnce_meets_specR_reverts (s : Store) (slot value : Nat) :
3836
(initOnceSpecR slot value).reverts s ->
3937
execFun initOnceFun [slot, value] s [] = ExecResult.reverted := by
4038
intro hrev
41-
simp [initOnceSpecR, initOnceFun, letSload, requireZero, requireEq, eq, require, execFun, execStmt,
42-
evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hrev]
39+
simp [initOnceSpecR, initOnceFun, sstoreIfZero, letSload, requireZero, requireEq, eq, require,
40+
execFun, execStmt, evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hrev]
4341

4442
end DumbContracts.Examples

research/lean_only_proto/DumbContracts/Examples/InitToOne.lean

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ open DumbContracts.Std
1414
def initToOneFun : Fun :=
1515
{ name := "initToOne"
1616
args := ["slot"]
17-
body :=
18-
letSload "current" (v "slot")
19-
(requireZero (v "current") (sstoreVar "slot" (n 1)))
17+
body := sstoreIfZero (v "slot") (n 1)
2018
ret := none }
2119

2220
def initToOneSpecR (slot : Nat) : SpecR Store :=
@@ -31,14 +29,14 @@ theorem initToOne_meets_specR_ok (s : Store) (slot : Nat) :
3129
| _ => False) := by
3230
intro hreq
3331
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]
32+
simp [initToOneSpecR, initToOneFun, sstoreIfZero, letSload, requireZero, requireEq, eq, require,
33+
execFun, execStmt, evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hzero]
3634

3735
theorem initToOne_meets_specR_reverts (s : Store) (slot : Nat) :
3836
(initToOneSpecR slot).reverts s ->
3937
execFun initToOneFun [slot] s [] = ExecResult.reverted := by
4038
intro hrev
41-
simp [initToOneSpecR, initToOneFun, letSload, requireZero, requireEq, eq, require, execFun,
42-
execStmt, evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hrev]
39+
simp [initToOneSpecR, initToOneFun, sstoreIfZero, letSload, requireZero, requireEq, eq, require,
40+
execFun, execStmt, evalExpr, bindArgs, emptyEnv, updateEnv, updateStore, hrev]
4341

4442
end DumbContracts.Examples

research/lean_only_proto/DumbContracts/Stdlib.lean

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ def letSload (name : String) (slot : Expr) (body : Stmt) : Stmt :=
6666
def sstoreIfEq (slot expected value : Expr) : Stmt :=
6767
letSload "current" slot (requireEq (Expr.var "current") expected (Stmt.sstore slot value))
6868

69+
def sstoreIfZero (slot value : Expr) : Stmt :=
70+
letSload "current" slot (requireZero (Expr.var "current") (Stmt.sstore slot value))
71+
6972
def sstoreAdd (slot delta : Expr) : Stmt :=
7073
Stmt.sstore slot (Expr.add (Expr.sload slot) delta)
7174

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 GeneratedInitDoubleTest is GeneratedBase {
7+
function testInitDouble() public {
8+
bytes memory creation = _readHexFile("out/example.creation.bin");
9+
address deployed = _deploy(creation);
10+
11+
bytes4 selInitDouble = 0xa756818e;
12+
bytes4 selGet = 0x7eba7ba6;
13+
14+
(bool ok,) = deployed.call(abi.encodeWithSelector(selInitDouble, 12, 7));
15+
require(ok, "initDouble failed (first init)");
16+
17+
bytes memory data;
18+
(ok, data) = deployed.call(abi.encodeWithSelector(selGet, 12));
19+
require(ok, "getSlot failed (slot 12)");
20+
uint256 val = abi.decode(data, (uint256));
21+
require(val == 14, "unexpected initDouble value");
22+
23+
(ok,) = deployed.call(abi.encodeWithSelector(selInitDouble, 12, 9));
24+
require(!ok, "initDouble should revert when slot already set");
25+
}
26+
}

0 commit comments

Comments
 (0)