Skip to content

Commit 66633c5

Browse files
committed
Fix undisclosed Yul semantics mismatches
1 parent 40587c9 commit 66633c5

7 files changed

Lines changed: 279 additions & 49 deletions

File tree

EvmYul/Semantics.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ def step {τ : OperationType} (op : Operation τ) (arg : Option (UInt256 × Nat)
391391
| .Yul, .REVERT => λ yulState lits ↦
392392
match (dispatchBinaryMachineStateOp .Yul MachineState.evmRevert) yulState lits with
393393
| .error e => .error e
394-
| .ok (_, _) => .error (Yul.Exception.Revert)
394+
| .ok (s, _) => .error (Yul.Exception.Revert s)
395395
| .EVM, .SELFDESTRUCT =>
396396
λ evmState ↦
397397
match evmState.stack.pop with

EvmYul/Yul/Ast.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ mutual
4747
inductive Stmt where
4848
| Block : List Stmt → Stmt
4949
| Let : List Identifier → Option Expr → Stmt
50+
| Assign : List Identifier → Expr → Stmt
5051
| ExprStmtCall : Expr → Stmt
5152
| Switch : Expr → List (Literal × List Stmt) → List Stmt → Stmt
5253
| For : Expr → List Stmt → List Stmt → Stmt

EvmYul/Yul/Exception.lean

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ inductive Exception where
1313
| MissingContract (s : String) : Exception
1414
| MissingContractFunction (s : String) : Exception
1515
| InvalidExpression : Exception
16+
| UnknownIdentifier (s : String) : Exception
17+
| DuplicateDeclaration (s : String) : Exception
1618
| YulEXTCODESIZENotImplemented : Exception
17-
| Revert : Exception
19+
| Revert (state : Yul.State) : Exception
1820
| YulHalt (state : Yul.State) (value : UInt256) : Exception
1921
-- | StopInvoked : Exception
2022

@@ -29,8 +31,10 @@ instance : Repr Exception where
2931
| .MissingContract s => "MissingContract: " ++ s
3032
| .MissingContractFunction f => "MissingContractFunction: " ++ f
3133
| .InvalidExpression => "InvalidExpression"
34+
| .UnknownIdentifier s => "UnknownIdentifier: " ++ s
35+
| .DuplicateDeclaration s => "DuplicateDeclaration: " ++ s
3236
| .YulEXTCODESIZENotImplemented => "YulEXTCODESIZENotImplemented"
33-
| .Revert => "Revert"
37+
| .Revert _ => "Revert"
3438
| .YulHalt _ _ => "YulHalt: (holds a state and a value)"
3539

3640

EvmYul/Yul/Interpreter.lean

Lines changed: 104 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,51 @@ def multifill' (vars : List Identifier) : Except Yul.Exception (State × List Li
3737
| .ok (s, rets) => .ok (s.multifill vars rets)
3838
| .error e => .error e
3939

40+
def firstDuplicate? : List Identifier → Option Identifier
41+
| [] => .none
42+
| var :: vars =>
43+
if vars.contains var then .some var else firstDuplicate? vars
44+
45+
def firstDeclared? (s : State) (vars : List Identifier) : Option Identifier :=
46+
vars.find? (fun var => (s.lookup? var).isSome)
47+
48+
def firstUndeclared? (s : State) (vars : List Identifier) : Option Identifier :=
49+
vars.find? (fun var => (s.lookup? var).isNone)
50+
51+
def checkDeclaration (s : State) (vars : List Identifier) : Except Yul.Exception Unit :=
52+
match firstDuplicate? vars with
53+
| .some var => .error (.DuplicateDeclaration var)
54+
| .none =>
55+
match firstDeclared? s vars with
56+
| .some var => .error (.DuplicateDeclaration var)
57+
| .none => .ok ()
58+
59+
def checkAssignment (s : State) (vars : List Identifier) : Except Yul.Exception Unit :=
60+
match firstDuplicate? vars with
61+
| .some _ => .error .InvalidArguments
62+
| .none =>
63+
match firstUndeclared? s vars with
64+
| .some var => .error (.UnknownIdentifier var)
65+
| .none => .ok ()
66+
67+
def restoreRevertedContractCallState (s₀ s₂ : State) (outOffset outSize : Literal) :
68+
Except Yul.Exception (State × List Literal) :=
69+
match s₀ with
70+
| .OutOfFuel => .error .OutOfFuel
71+
| .Checkpoint j => .ok (.Checkpoint j, [⟨0⟩])
72+
| .Ok sharedState₀ varstore =>
73+
let returnData := s₂.toMachineState.H_return
74+
let memory₃ :=
75+
returnData.copySlice 0 s₀.toMachineState.memory outOffset.toNat
76+
(min outSize.toNat returnData.size)
77+
let sharedState₃ :=
78+
{ sharedState₀ with
79+
memory := memory₃
80+
returnData := returnData
81+
H_return := ByteArray.empty
82+
}
83+
.ok (.Ok sharedState₃ varstore, [⟨0⟩])
84+
4085
def setStatic (s : State) (p : Bool) : State :=
4186
match s with
4287
| .OutOfFuel => .OutOfFuel
@@ -143,6 +188,8 @@ def primCall (fuel : ℕ) (s₀ : State) (prim : Operation .Yul) (args : List Li
143188
H_return := ByteArray.empty
144189
}
145190
.ok (.Ok sharedState₃ varstore, [⟨1⟩])
191+
| .error (.Revert s₂) =>
192+
restoreRevertedContractCallState s₀ s₂ outOffset outSize
146193
| .error e => .error e
147194
| .ok (s₂, _) =>
148195

@@ -235,6 +282,8 @@ def primCall (fuel : ℕ) (s₀ : State) (prim : Operation .Yul) (args : List Li
235282
executionEnv := executionEnv₃
236283
}
237284
.ok (setStatic (.Ok sharedState₃ varstore) s₀.executionEnv.perm, [⟨1⟩])
285+
| .error (.Revert s₂) =>
286+
restoreRevertedContractCallState s₀ s₂ outOffset outSize
238287
| .error e => .error e
239288
| .ok (s₂, _) =>
240289

@@ -320,6 +369,8 @@ def primCall (fuel : ℕ) (s₀ : State) (prim : Operation .Yul) (args : List Li
320369
}
321370
.ok (.Ok sharedState₃ varstore, [⟨1⟩])
322371

372+
| .error (.Revert s₂) =>
373+
restoreRevertedContractCallState s₀ s₂ outOffset outSize
323374
| .error e => .error e
324375
| .ok (s₂, _) =>
325376
let memory₃ := s₂.toMachineState.H_return.copySlice 0 s₀.toMachineState.memory outOffset.toNat (min outSize.toNat s₂.toMachineState.H_return.size)
@@ -394,6 +445,8 @@ def primCall (fuel : ℕ) (s₀ : State) (prim : Operation .Yul) (args : List Li
394445
executionEnv := executionEnv₃
395446
}
396447
.ok (.Ok sharedState₃ varstore, [⟨1⟩])
448+
| .error (.Revert s₂) =>
449+
restoreRevertedContractCallState s₀ s₂ outOffset outSize
397450
| .error e => .error e
398451
| .ok (s₂, _) =>
399452
let memory₃ := s₂.toMachineState.H_return.copySlice 0 s₀.toMachineState.memory outOffset.toNat (min outSize.toNat s₂.toMachineState.H_return.size)
@@ -462,7 +515,7 @@ def primCall (fuel : ℕ) (s₀ : State) (prim : Operation .Yul) (args : List Li
462515
match fOpt with
463516
| .none => .error (.MissingContractFunction (yulFunctionNameOption.getD ".none"))
464517
| .some f =>
465-
let s₁ := 👌 s.initcall f.params args
518+
let s₁ := 👌 s.initcall f.params f.rets args
466519
match exec fuel' (.Block f.body) codeOverride s₁ with
467520
| .error e => .error e
468521
| .ok s₂ =>
@@ -479,7 +532,7 @@ def primCall (fuel : ℕ) (s₀ : State) (prim : Operation .Yul) (args : List Li
479532
| 0 => .error .OutOfFuel
480533
| .succ fuel' =>
481534
let f := FunctionDefinition.Def [] [] [s.executionEnv.code.dispatcher]
482-
let s₁ := 👌 s.initcall f.params []
535+
let s₁ := 👌 s.initcall f.params f.rets []
483536
match exec fuel' (.Block f.body) codeOverride s₁ with
484537
| .error e => .error e
485538
| .ok s₂ =>
@@ -512,30 +565,47 @@ def primCall (fuel : ℕ) (s₀ : State) (prim : Operation .Yul) (args : List Li
512565
| .succ fuel' => multifill' vars (call fuel' args yulFunctionName codeOverride s)
513566
| .error e => .error e
514567

568+
def evalValues (fuel : Nat) (expr : Expr) (codeOverride : Option YulContract) (s : State) : Except Yul.Exception (State × List Literal) :=
569+
match fuel with
570+
| 0 => .error .OutOfFuel
571+
| .succ fuel' =>
572+
match expr with
573+
| .Call (Sum.inl prim) args =>
574+
match reverse' (evalArgs fuel' args.reverse codeOverride s) with
575+
| .ok (s, args) => primCall fuel' s prim args
576+
| .error e => .error e
577+
| .Call (Sum.inr yulFunctionName) args =>
578+
match reverse' (evalArgs fuel' args.reverse codeOverride s) with
579+
| .ok (s, args) => call fuel' args yulFunctionName codeOverride s
580+
| .error e => .error e
581+
| .Var id =>
582+
match s.lookup? id with
583+
| .some val => .ok (s, [val])
584+
| .none => .error (.UnknownIdentifier id)
585+
| .Lit val => .ok (s, [val])
586+
515587
/--
516588
`eval` evaluates an expression.
517589
518590
- calls evaluated here are assumed to have coarity 1
519591
-/
520592
def eval (fuel : Nat) (expr : Expr) (codeOverride : Option YulContract) (s : State) : Except Yul.Exception (State × Literal) :=
593+
head' (evalValues fuel expr codeOverride s)
594+
595+
def execSeq (fuel : Nat) (stmts : List Stmt) (codeOverride : Option YulContract) (s : State) : Except Yul.Exception State :=
521596
match fuel with
522597
| 0 => .error .OutOfFuel
523598
| .succ fuel' =>
524-
match expr with
525-
526-
-- We hit these two cases (`PrimCall` and `Call`) when evaluating:
527-
--
528-
-- 1. f() (expression statements)
529-
-- 2. g(f()) (calls in function arguments)
530-
-- 3. if f() {...} (if conditions)
531-
-- 4. for {...} f() ... (for conditions)
532-
-- 5. switch f() ... (switch conditions)
533-
534-
| .Call (Sum.inl prim) args => evalPrimCall fuel' prim (reverse' (evalArgs fuel' args.reverse codeOverride s))
535-
| .Call (Sum.inr yulFunctionName) args =>
536-
evalCall fuel' yulFunctionName codeOverride (reverse' (evalArgs fuel' args.reverse codeOverride s))
537-
| .Var id => .ok (s, s[id]!)
538-
| .Lit val => .ok (s, val)
599+
match stmts with
600+
| [] => .ok s
601+
| stmt :: stmts =>
602+
match exec fuel' stmt codeOverride s with
603+
| .error e => .error e
604+
| .ok s₁ =>
605+
match s₁ with
606+
| .Ok _ _ => execSeq fuel' stmts codeOverride s₁
607+
| .OutOfFuel => .ok s₁
608+
| .Checkpoint _ => .ok s₁
539609

540610
/--
541611
`exec` executs a single statement.
@@ -545,24 +615,23 @@ def primCall (fuel : ℕ) (s₀ : State) (prim : Operation .Yul) (args : List Li
545615
| 0 => .error .OutOfFuel
546616
| .succ fuel' =>
547617
match stmt with
548-
| .Block [] => .ok s
549-
| .Block (stmt :: stmts) =>
550-
let s₁ := exec fuel' stmt codeOverride s
551-
match s₁ with
552-
| .error e => .error e
553-
| .ok s₁ => exec fuel' (.Block stmts) codeOverride s₁
618+
| .Block stmts =>
619+
match execSeq fuel' stmts codeOverride s with
620+
| .error e => .error e
621+
| .ok s₁ => .ok (s₁.restrictStoreTo s.store)
554622

555623
| .Let vars exprOption =>
624+
match checkDeclaration s vars with
625+
| .error e => .error e
626+
| .ok () =>
556627
match exprOption with
557-
| .none => .ok (List.foldr (λ var s ↦ s.insert var ⟨0⟩) s vars)
558-
| .some expr =>
559-
match expr with
560-
| .Call (Sum.inl prim) args =>
561-
execPrimCall fuel' prim vars (reverse' (evalArgs fuel' args.reverse codeOverride s))
562-
| .Call (Sum.inr yulFunctionName) args =>
563-
execCall fuel' yulFunctionName vars codeOverride (reverse' (evalArgs fuel' args.reverse codeOverride s))
564-
| .Var identifier => .ok (s.insert vars.head! s[identifier]!) -- It should be safe to call head! here if the Yul code is parsed correctly.
565-
| .Lit literal => .ok (s.insert vars.head! literal) -- It should be safe to call head! here if the Yul code is parsed correctly.
628+
| .none => .ok (s.zeroFill vars)
629+
| .some expr => multifill' vars (evalValues fuel' expr codeOverride s)
630+
631+
| .Assign vars expr =>
632+
match checkAssignment s vars with
633+
| .error e => .error e
634+
| .ok () => multifill' vars (evalValues fuel' expr codeOverride s)
566635

567636
| .If cond body =>
568637
match eval fuel' cond codeOverride s with
@@ -640,8 +709,10 @@ def execTopLevel (fuel : Nat) (stmt : Stmt) (s : State) : State :=
640709
| .error (.MissingContract _) => default
641710
| .error (.MissingContractFunction _) => default -- We do not model fallback functions
642711
| .error .InvalidExpression => default
712+
| .error (.UnknownIdentifier _) => default
713+
| .error (.DuplicateDeclaration _) => default
643714
| .error .YulEXTCODESIZENotImplemented => default
644-
| .error .Revert => s
715+
| .error (.Revert _) => s
645716
| .error (.YulHalt s _) => s
646717
| .ok s => s
647718

EvmYul/Yul/StateOps.lean

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,19 @@ def multifill (vars : List Identifier) (vals : List Literal) : Yul.State → Yul
1616
| s@(Ok _ _) => (List.zip vars vals).foldr (λ (var, val) s ↦ s.insert var val) s
1717
| s => s
1818

19+
def zeroFill (vars : List Identifier) : Yul.State → Yul.State :=
20+
vars.foldr (λ var s ↦ s.insert var ⟨0⟩)
21+
22+
def restrictVarStore (store scope : VarStore) : VarStore :=
23+
store.sdiff (store.sdiff scope)
24+
25+
def restrictStoreTo (scope : VarStore) : Yul.State → Yul.State
26+
| Ok sharedState store => Ok sharedState (restrictVarStore store scope)
27+
| Checkpoint (.Continue sharedState store) => Checkpoint (.Continue sharedState (restrictVarStore store scope))
28+
| Checkpoint (.Break sharedState store) => Checkpoint (.Break sharedState (restrictVarStore store scope))
29+
| Checkpoint (.Leave sharedState store) => Checkpoint (.Leave sharedState (restrictVarStore store scope))
30+
| s => s
31+
1932
-- | Overwrite the EvmYul.Yul.State state of some state.
2033
def setSharedState (sharedState : EvmYul.SharedState .Yul) : Yul.State → Yul.State
2134
| Ok _ store => Ok sharedState store
@@ -53,10 +66,11 @@ def diverge : Yul.State → Yul.State
5366
| s => s
5467

5568
-- | Initialize function parameters and return values in varstore.
56-
def initcall (params : List Identifier) (args : List Literal) : Yul.State → Yul.State
69+
def initcall (params rets : List Identifier) (args : List Literal) : Yul.State → Yul.State
5770
| s@(Ok _ _) =>
5871
let s₁ := s.setStore default
59-
s₁.multifill params args
72+
let s₂ := s₁.zeroFill rets
73+
s₂.multifill params args
6074
| s => s
6175

6276
-- | Since it literally does not matter what happens if the state is non-Ok, we just use the default.
@@ -93,13 +107,16 @@ def overwrite? (s s' : Yul.State) : Yul.State :=
93107
-- STATE QUERIES
94108
-- ============================================================================
95109

96-
-- | Lookup the literal associated with an variable in the varstore, returning 0 if not found.
110+
def lookup? (var : Identifier) : Yul.State → Option Literal
111+
| Ok _ store => store.lookup var
112+
| Checkpoint (.Continue _ store) => store.lookup var
113+
| Checkpoint (.Break _ store) => store.lookup var
114+
| Checkpoint (.Leave _ store) => store.lookup var
115+
| _ => .none
116+
117+
-- | Lookup the literal associated with a variable in the varstore, returning 0 if not found.
97118
def lookup! (var : Identifier) : Yul.State → Literal
98-
| Ok _ store => (store.lookup var).get!
99-
| Checkpoint (.Continue _ store) => (store.lookup var).get!
100-
| Checkpoint (.Break _ store) => (store.lookup var).get!
101-
| Checkpoint (.Leave _ store) => (store.lookup var).get!
102-
| _ => ⟨0
119+
| s => (s.lookup? var).getD ⟨0
103120

104121
-- ============================================================================
105122
-- STATE NOTATION
@@ -150,7 +167,7 @@ notation:65 s:64 "🏪⟦" s' "⟧" => Yul.State.setStore s s'
150167
notation:65 s:64 "🇪⟦" sharedState "⟧" => Yul.State.setSharedState sharedState s
151168
notation:65 "🪫" s:64 => Yul.State.diverge s
152169
notation:65 "👌" s:64 => Yul.State.mkOk s
153-
notation:65 s:64 "☎️⟦" params "," args "⟧" => Yul.State.initcall params args s
170+
notation:65 s:64 "☎️⟦" params "," rets "," args "⟧" => Yul.State.initcall params rets args s
154171
notation:65 s:64 "✏️⟦" s' "⟧?" => Yul.State.overwrite? s s'
155172
notation:64 (priority := high) "🧟" s:max => Yul.State.reviveJump s
156173

EvmYul/Yul/YulNotation.lean

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ partial def translateStmt (stmt : TSyntax `stmt) : TermElabM Term :=
309309
| `(stmt| $ids:ident,* := $expr:expr) => do
310310
let ids' := (ids : TSyntaxArray _).map translateIdent
311311
let expr ← translateExpr expr
312-
`(Stmt.Let [$ids',*] (.some $expr))
312+
`(Stmt.Assign [$ids',*] $expr)
313313

314314
-- ExprStmt
315315
| `(stmt| $expr:expr) => do
@@ -373,10 +373,10 @@ example : <s break > = Stmt.Break := rfl
373373
example : <s let a, b := f(42) > = Stmt.Let ["a", "b"] (.some (.Call (Sum.inr "f") [Expr.Lit ⟨42⟩])) := rfl
374374
example : <s let a > = Stmt.Let ["a"] .none := rfl
375375
example : <s let a := 5 > = Stmt.Let ["a"] (.some (.Lit ⟨5⟩)) := rfl
376-
example : <s a, b := f(42) > = Stmt.Let ["a", "b"] (.some (.Call (Sum.inr "f") [Expr.Lit ⟨42⟩])) := rfl
377-
example : <s a := 42 > = Stmt.Let ["a"] (.some (.Lit ⟨42)) := rfl
376+
example : <s a, b := f(42) > = Stmt.Assign ["a", "b"] (.Call (Sum.inr "f") [Expr.Lit ⟨42⟩]) := rfl
377+
example : <s a := 42 > = Stmt.Assign ["a"] (.Lit ⟨42⟩) := rfl
378378

379-
example : <s c := add(a, b) > = Stmt.Let ["c"] (.some (Expr.Call (Sum.inl (Operation.StopArith Operation.SAOp.ADD)) [Expr.Var "a", Expr.Var "b"])) := rfl
379+
example : <s c := add(a, b) > = Stmt.Assign ["c"] (Expr.Call (Sum.inl (Operation.StopArith Operation.SAOp.ADD)) [Expr.Var "a", Expr.Var "b"]) := rfl
380380
example : <s let c := sub(a, b) > = Stmt.Let ["c"] (.some (Expr.Call (Sum.inl (Operation.StopArith Operation.SAOp.SUB)) [Expr.Var "a", Expr.Var "b"])) := rfl
381381
example : <s let a := 5 > = Stmt.Let ["a"] (.some (.Lit ⟨5⟩)) := rfl
382382
example : <s {} >

0 commit comments

Comments
 (0)