Skip to content

Commit 09ea02d

Browse files
committed
Add sstoreMin helper and updateMin example
1 parent d1730da commit 09ea02d

8 files changed

Lines changed: 142 additions & 2 deletions

File tree

STATUS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +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.
2223

2324
## Recently Done
2425
- Lean -> Yul pipeline with runtime + creation bytecode artifacts.

docs/research-log.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Research Log
22

33
## 2026-02-10
4+
- Added `sstoreMin` stdlib helper to mirror `sstoreMax` for min updates.
5+
- Added `updateMin` example (monotonic min update against a stored slot) with Spec + proof.
6+
- Added Foundry test for `updateMin`.
7+
- Updated compiler entries + direct EVM asm for `updateMin`.
48
- Added `sstoreMax` stdlib helper to reduce max-store boilerplate.
59
- Refactored `maxStore` to use `sstoreMax`.
610
- Added `updateMax` example (monotonic max update against a stored slot) with Spec + proof.

research/lean_only_proto/DumbContracts/Compiler.lean

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,19 @@ def exampleEntry15 : EntryPoint :=
144144
selector := 0xe9cc4edd
145145
returns := false }
146146

147+
def exampleEntry16 : EntryPoint :=
148+
{ name := "updateMin"
149+
args := ["slot", "value"]
150+
body :=
151+
Lang.Stmt.let_ "current" (Lang.Expr.sload (Lang.Expr.var "slot"))
152+
(Lang.Stmt.if_
153+
(Lang.Expr.lt (Lang.Expr.var "current") (Lang.Expr.var "value"))
154+
(Lang.Stmt.sstore (Lang.Expr.var "slot") (Lang.Expr.var "current"))
155+
(Lang.Stmt.sstore (Lang.Expr.var "slot") (Lang.Expr.var "value")))
156+
-- updateMin(uint256,uint256) -> 0x5c34a245
157+
selector := 0x5c34a245
158+
returns := false }
159+
147160
def exampleEntry6 : EntryPoint :=
148161
{ name := "setNonZero"
149162
args := ["slot", "value"]
@@ -262,8 +275,8 @@ def exampleEntry14 : EntryPoint :=
262275

263276
def exampleEntries : List EntryPoint :=
264277
[exampleEntry, exampleEntry2, exampleEntry3, exampleEntry4, exampleEntry5, exampleEntry15,
265-
exampleEntry6, exampleEntry7, exampleEntry12, exampleEntry8, exampleEntry9, exampleEntry10,
266-
exampleEntry11, exampleEntry13, exampleEntry14]
278+
exampleEntry16, exampleEntry6, exampleEntry7, exampleEntry12, exampleEntry8, exampleEntry9,
279+
exampleEntry10, exampleEntry11, exampleEntry13, exampleEntry14]
267280

268281
def healthEntrySet : EntryPoint :=
269282
{ name := "setRisk"

research/lean_only_proto/DumbContracts/EvmAsm.lean

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ def directProgramAsm : List String :=
3939
, "PUSH2 tag_updateMax"
4040
, "JUMPI"
4141
, "DUP1"
42+
, "PUSH4 0x5c34a245"
43+
, "EQ"
44+
, "PUSH2 tag_updateMin"
45+
, "JUMPI"
46+
, "DUP1"
4247
, "PUSH4 0xac1f1f67"
4348
, "EQ"
4449
, "PUSH2 tag_setNonZero"
@@ -183,6 +188,16 @@ def directProgramAsm : List String :=
183188
, "JUMP"
184189
, "ret_setNonZero:"
185190
, "STOP"
191+
, "tag_updateMin:"
192+
, "PUSH2 ret_updateMin"
193+
, "PUSH1 0x24"
194+
, "CALLDATALOAD"
195+
, "PUSH1 0x04"
196+
, "CALLDATALOAD"
197+
, "PUSH2 fn_updateMin"
198+
, "JUMP"
199+
, "ret_updateMin:"
200+
, "STOP"
186201
, "tag_updateMax:"
187202
, "PUSH2 ret_updateMax"
188203
, "PUSH1 0x24"
@@ -345,6 +360,36 @@ def directProgramAsm : List String :=
345360
, "SSTORE"
346361
, "PUSH2 updateMax_check"
347362
, "JUMP"
363+
, "fn_updateMin:"
364+
, "DUP2"
365+
, "DUP2"
366+
, "SLOAD"
367+
, "DUP2"
368+
, "DUP2"
369+
, "LT"
370+
, "PUSH2 updateMin_then"
371+
, "JUMPI"
372+
, "updateMin_check:"
373+
, "LT"
374+
, "ISZERO"
375+
, "PUSH2 updateMin_else"
376+
, "JUMPI"
377+
, "updateMin_join:"
378+
, "POP"
379+
, "POP"
380+
, "JUMP"
381+
, "updateMin_else:"
382+
, "SSTORE"
383+
, "PUSH0"
384+
, "DUP1"
385+
, "PUSH2 updateMin_join"
386+
, "JUMP"
387+
, "updateMin_then:"
388+
, "DUP1"
389+
, "DUP4"
390+
, "SSTORE"
391+
, "PUSH2 updateMin_check"
392+
, "JUMP"
348393
, "fn_setNonZero:"
349394
, "SWAP1"
350395
, "DUP1"

research/lean_only_proto/DumbContracts/Examples.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import DumbContracts.Examples.Supply
55
import DumbContracts.Examples.TokenTransfer
66
import DumbContracts.Examples.MaxStore
77
import DumbContracts.Examples.UpdateMax
8+
import DumbContracts.Examples.UpdateMin
89
import DumbContracts.Examples.NonZeroStore
910
import DumbContracts.Examples.CompareAndSwap
1011
import DumbContracts.Examples.InitOnce
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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+
/-- Update a slot to the min of its current value and a new input. -/
13+
14+
def updateMinFun : Fun :=
15+
{ name := "updateMin"
16+
args := ["slot", "value"]
17+
body :=
18+
letSload "current" (v "slot")
19+
(sstoreMin (v "slot") (v "current") (v "value"))
20+
ret := none }
21+
22+
def updateMinSpec (slot value : Nat) : Spec Store :=
23+
{ requires := fun _ => True
24+
ensures := fun s s' => s' = updateStore s slot (if s slot < value then s slot else value) }
25+
26+
theorem updateMin_meets_spec (s : Store) (slot value : Nat) :
27+
(updateMinSpec slot value).requires s ->
28+
(match execFun updateMinFun [slot, value] s [] with
29+
| ExecResult.ok _ s' => (updateMinSpec slot value).ensures s s'
30+
| _ => False) := by
31+
intro _hreq
32+
by_cases h : s slot < value
33+
· simp [updateMinFun, updateMinSpec, letSload, sstoreMin, v, execFun, execStmt, evalExpr,
34+
bindArgs, emptyEnv, updateEnv, updateStore, h]
35+
· simp [updateMinFun, updateMinSpec, letSload, sstoreMin, v, execFun, execStmt, evalExpr,
36+
bindArgs, emptyEnv, updateEnv, updateStore, h]
37+
38+
end DumbContracts.Examples

research/lean_only_proto/DumbContracts/Stdlib.lean

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ def sstoreInc (slot : Expr) : Stmt :=
6060
def sstoreMax (slot a b : Expr) : Stmt :=
6161
Stmt.if_ (Expr.gt a b) (Stmt.sstore slot a) (Stmt.sstore slot b)
6262

63+
def sstoreMin (slot a b : Expr) : Stmt :=
64+
Stmt.if_ (Expr.lt a b) (Stmt.sstore slot a) (Stmt.sstore slot b)
65+
6366
def sloadSlot (slot : Nat) : Expr :=
6467
Expr.sload (Expr.lit slot)
6568

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import "./GeneratedBase.t.sol";
5+
6+
contract GeneratedUpdateMinTest is GeneratedBase {
7+
function testUpdateMin() public {
8+
bytes memory creation = _readHexFile("out/example.creation.bin");
9+
address deployed = _deploy(creation);
10+
11+
bytes4 selUpdateMin = 0x5c34a245;
12+
bytes4 selSet = 0xf2c298be;
13+
bytes4 selGet = 0x7eba7ba6;
14+
15+
(bool ok,) = deployed.call(abi.encodeWithSelector(selSet, 4, 10));
16+
require(ok, "setSlot failed (slot 4)");
17+
18+
(ok,) = deployed.call(abi.encodeWithSelector(selUpdateMin, 4, 12));
19+
require(ok, "updateMin failed on larger value");
20+
21+
bytes memory data;
22+
(ok, data) = deployed.call(abi.encodeWithSelector(selGet, 4));
23+
require(ok, "getSlot failed (slot 4)");
24+
uint256 val = abi.decode(data, (uint256));
25+
require(val == 10, "unexpected updateMin value after larger input");
26+
27+
(ok,) = deployed.call(abi.encodeWithSelector(selUpdateMin, 4, 7));
28+
require(ok, "updateMin failed on smaller value");
29+
30+
(ok, data) = deployed.call(abi.encodeWithSelector(selGet, 4));
31+
require(ok, "getSlot failed (slot 4) after update");
32+
val = abi.decode(data, (uint256));
33+
require(val == 7, "unexpected updateMin value after smaller input");
34+
}
35+
}

0 commit comments

Comments
 (0)