-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathEndToEnd.lean
More file actions
388 lines (360 loc) · 17.2 KB
/
EndToEnd.lean
File metadata and controls
388 lines (360 loc) · 17.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
/-
Compiler.Proofs.EndToEnd: Composed Layers 2+3 End-to-End Theorem
Composes the existing Layer 2 (CompilationModel → IR) and Layer 3 (IR → Yul)
preservation theorems into a single end-to-end result:
For any CompilationModel, evaluating the compiled Yul via the proof semantics
produces the same result as the IR semantics.
This is the first step toward eliminating `interpretSpec` from the TCB
(Issue #998). Once primitive-level EDSL ≡ compiled-Yul lemmas are proven,
this end-to-end theorem provides the composition spine.
**Architecture**:
- Layer 2: `compile spec selectors = .ok irContract`
`interpretIR irContract tx irState ≡ interpretSpec spec ...`
(proven per-contract in Compiler/Proofs/IRGeneration/Expr.lean)
- Layer 3: `interpretIR irContract tx irState ≡ interpretYulFromIR irContract tx irState`
(proven generically in Compiler/Proofs/YulGeneration/Preservation.lean)
- This file: compose them into a single theorem statement.
**EVMYulLean note**: This file does NOT import EVMYulLean directly. The Yul
execution semantics used here (`interpretYulFromIR`, `interpretYulRuntime`)
are defined in terms of `evalBuiltinCallWithBackend` which defaults to the
Verity backend. The EVMYulLean bridge is established separately in
`Compiler/Proofs/YulGeneration/Backends/EvmYulLeanAdapter.lean` and
`Compiler/Proofs/ArithmeticProfile.lean`, proving that for pure builtins,
the Verity backend agrees with EVMYulLean.
Run: lake build Compiler.Proofs.EndToEnd
-/
import Compiler.Proofs.YulGeneration.Preservation
import Compiler.Proofs.IRGeneration.Expr
namespace Compiler.Proofs.EndToEnd
open Compiler
open Compiler.Proofs.IRGeneration
open Compiler.Proofs.YulGeneration
/-! ## Layer 3: IR → Yul (Generic) -/
/-- Layer 3 function-level preservation: any IR function body produces equivalent
results under IR execution and fuel-based Yul execution. -/
theorem layer3_function_preserves_semantics
(fn : IRFunction) (selector : Nat) (args : List Nat) (initialState : IRState) :
Compiler.Proofs.YulGeneration.resultsMatch
(execIRFunction fn args initialState)
(interpretYulBodyFromState fn selector
(fn.params.zip args |>.foldl (fun s (p, v) => s.setVar p.name v) initialState)
initialState) :=
ir_function_body_equiv fn selector args initialState
/-! ## Bridging Helpers -/
/-- Explicit bridge hypothesis for the param-load erasure step. -/
private def paramLoadErasure (fn : IRFunction) (tx : IRTransaction) (state : IRState) : Prop :=
let paramState := fn.params.zip tx.args |>.foldl
(fun s (p, v) => s.setVar p.name v) state
let yulTx : YulTransaction := {
sender := tx.sender
msgValue := tx.msgValue
thisAddress := tx.thisAddress
blockTimestamp := tx.blockTimestamp
blockNumber := tx.blockNumber
chainId := tx.chainId
blobBaseFee := tx.blobBaseFee
functionSelector := tx.functionSelector
args := tx.args
}
execYulStmts (yulStateOfIR 0 paramState) fn.body =
execYulStmts (YulState.initial yulTx state.storage state.events) fn.body
/-- Result wrapping equivalence: `interpretYulRuntime` produces the same `YulResult`
as `yulResultOfExecWithRollback` when the rollback storage matches. -/
theorem interpretYulRuntime_eq_yulResultOfExec
(stmts : List Yul.YulStmt) (tx : YulTransaction) (stor : Nat → Nat)
(events : List (List Nat)) :
interpretYulRuntime stmts tx stor events =
yulResultOfExecWithRollback (YulState.initial tx stor events)
(execYulStmts (YulState.initial tx stor events) stmts) := by
simp [interpretYulRuntime]
cases execYulStmts (YulState.initial tx stor events) stmts with
| «continue» s => simp [yulResultOfExecWithRollback]
| «return» v s => simp [yulResultOfExecWithRollback]
| «stop» s => simp [yulResultOfExecWithRollback]
| «revert» s => simp [yulResultOfExecWithRollback, YulState.initial]
/-- State equivalence: under the entry-point hypotheses, `yulStateOfIR` produces
the same YulState as `YulState.initial`. -/
theorem yulStateOfIR_eq_initial
(sel : Nat) (state : IRState) (tx : IRTransaction)
(hcalldata : state.calldata = tx.args)
(hsender : state.sender = tx.sender)
(hmsgValue : state.msgValue = tx.msgValue)
(hthis : state.thisAddress = tx.thisAddress)
(htimestamp : state.blockTimestamp = tx.blockTimestamp)
(hnumber : state.blockNumber = tx.blockNumber)
(hchain : state.chainId = tx.chainId)
(hblobBaseFee : state.blobBaseFee = tx.blobBaseFee)
(hselector : state.selector = tx.functionSelector)
(hreturn : state.returnValue = none)
(hmemory : state.memory = fun _ => 0)
(htransient : state.transientStorage = fun _ => 0)
(hvars : state.vars = []) :
yulStateOfIR sel state =
YulState.initial
{ sender := tx.sender
msgValue := tx.msgValue
thisAddress := tx.thisAddress
blockTimestamp := tx.blockTimestamp
blockNumber := tx.blockNumber
chainId := tx.chainId
blobBaseFee := tx.blobBaseFee
functionSelector := tx.functionSelector
args := tx.args }
state.storage state.events := by
simp [yulStateOfIR, YulState.initial, hvars, hmemory, htransient, hcalldata, hsender, hmsgValue,
hthis, htimestamp, hnumber, hchain, hblobBaseFee, hselector, hreturn]
/-- Hypothesis-driven param-load erasure. -/
theorem execYulStmts_paramState_eq_emptyVars
(fn : IRFunction) (tx : IRTransaction) (state : IRState)
(_hvars : state.vars = [])
(_hmemory : state.memory = fun _ => 0)
(_hcalldata : state.calldata = tx.args)
(_hsender : state.sender = tx.sender)
(_hmsgValue : state.msgValue = tx.msgValue)
(_hthis : state.thisAddress = tx.thisAddress)
(_htimestamp : state.blockTimestamp = tx.blockTimestamp)
(_hnumber : state.blockNumber = tx.blockNumber)
(_hchain : state.chainId = tx.chainId)
(_hselector : state.selector = tx.functionSelector)
(_hreturn : state.returnValue = none)
(hparamErase : paramLoadErasure fn tx state) :
paramLoadErasure fn tx state :=
hparamErase
/-- Bridging: the two Yul execution entry points produce the same result
when the IR state has empty vars and zero memory. -/
theorem yulBody_from_state_eq_yulBody
(fn : IRFunction) (tx : IRTransaction) (state : IRState)
(hcalldata : state.calldata = tx.args)
(hsender : state.sender = tx.sender)
(hmsgValue : state.msgValue = tx.msgValue)
(hthis : state.thisAddress = tx.thisAddress)
(htimestamp : state.blockTimestamp = tx.blockTimestamp)
(hnumber : state.blockNumber = tx.blockNumber)
(hchain : state.chainId = tx.chainId)
(hblobBaseFee : state.blobBaseFee = tx.blobBaseFee)
(hselector : state.selector = tx.functionSelector)
(hreturn : state.returnValue = none)
(hmemory : state.memory = fun _ => 0)
(htransient : state.transientStorage = fun _ => 0)
(hvars : state.vars = [])
(hparamErase : paramLoadErasure fn tx state) :
Compiler.Proofs.YulGeneration.resultsMatch
(execIRFunction fn tx.args state)
(interpretYulBody fn tx state) := by
have h_ir_from := ir_function_body_equiv fn 0 tx.args state
suffices h_eq : interpretYulBodyFromState fn 0
(fn.params.zip tx.args |>.foldl (fun s (p, v) => s.setVar p.name v) state)
state = interpretYulBody fn tx state by
rwa [h_eq] at h_ir_from
simp only [interpretYulBodyFromState, interpretYulBody]
have h_rollback := yulStateOfIR_eq_initial 0 state tx hcalldata hsender hmsgValue hthis htimestamp hnumber hchain hblobBaseFee hselector hreturn hmemory htransient hvars
have h_exec := execYulStmts_paramState_eq_emptyVars fn tx state hvars hmemory hcalldata hsender hmsgValue hthis htimestamp hnumber hchain hselector hreturn hparamErase
rw [h_rollback]
simp only at h_exec
rw [h_exec]
exact (interpretYulRuntime_eq_yulResultOfExec fn.body
{ sender := tx.sender
msgValue := tx.msgValue
thisAddress := tx.thisAddress
blockTimestamp := tx.blockTimestamp
blockNumber := tx.blockNumber
chainId := tx.chainId
blobBaseFee := tx.blobBaseFee
functionSelector := tx.functionSelector
args := tx.args }
state.storage state.events).symm
/-! ## Layer 3 Contract-Level: IR → Yul (via runtime dispatch) -/
/-- Layer 3 contract-level preservation: an IR contract execution produces
equivalent results under the Yul runtime dispatch. -/
theorem layer3_contract_preserves_semantics
(contract : IRContract) (tx : IRTransaction) (initialState : IRState)
(hselector : tx.functionSelector < selectorModulus)
(hNoWrap : 4 + tx.args.length * 32 < evmModulus)
(hvars : initialState.vars = [])
(hmemory : initialState.memory = fun _ => 0)
(htransient : initialState.transientStorage = fun _ => 0)
(hreturn : initialState.returnValue = none)
(hparamErase : ∀ fn, fn ∈ contract.functions →
paramLoadErasure fn tx
{ initialState with
sender := tx.sender
msgValue := tx.msgValue
thisAddress := tx.thisAddress
blockTimestamp := tx.blockTimestamp
blockNumber := tx.blockNumber
chainId := tx.chainId
blobBaseFee := tx.blobBaseFee
calldata := tx.args
selector := tx.functionSelector })
(hdispatchGuardSafe : ∀ fn, fn ∈ contract.functions →
DispatchGuardsSafe fn tx)
(hNoHasSelector : ∀ fn, fn ∈ contract.functions →
yulStmtsNoRef "__has_selector" fn.body)
(hHasSelectorDead : ∀ fn, fn ∈ contract.functions →
HasSelectorDeadBridge fn.body)
(hLoopFree : ∀ fn, fn ∈ contract.functions →
yulStmtsLoopFree fn.body = true)
(hWF : ContractWF contract)
(hNoFallback : contract.fallbackEntrypoint = none)
(hNoReceive : contract.receiveEntrypoint = none) :
Compiler.Proofs.YulGeneration.resultsMatch
(interpretIR contract tx initialState)
(interpretYulFromIR contract tx initialState) := by
apply yulCodegen_preserves_semantics contract tx initialState
hselector hNoWrap hWF hNoFallback hNoReceive hdispatchGuardSafe hNoHasSelector hHasSelectorDead
hLoopFree
· intro fn hmem
exact (yulBody_from_state_eq_yulBody fn tx
{ initialState with
sender := tx.sender
msgValue := tx.msgValue
thisAddress := tx.thisAddress
blockTimestamp := tx.blockTimestamp
blockNumber := tx.blockNumber
chainId := tx.chainId
blobBaseFee := tx.blobBaseFee
calldata := tx.args
selector := tx.functionSelector }
rfl rfl rfl rfl rfl rfl rfl rfl rfl
(by simpa using hreturn)
(by simpa using hmemory)
(by simpa using htransient)
(by simpa using hvars)
(hparamErase fn hmem))
/-- Unconditioned version: delegates directly to `yulCodegen_preserves_semantics`. -/
theorem layer3_contract_preserves_semantics_general
(contract : IRContract) (tx : IRTransaction) (initialState : IRState)
(hselector : tx.functionSelector < selectorModulus)
(hNoWrap : 4 + tx.args.length * 32 < evmModulus)
(hWF : ContractWF contract)
(hNoFallback : contract.fallbackEntrypoint = none)
(hNoReceive : contract.receiveEntrypoint = none)
(hdispatchGuardSafe : ∀ fn, fn ∈ contract.functions →
DispatchGuardsSafe fn tx)
(hNoHasSelector : ∀ fn, fn ∈ contract.functions →
yulStmtsNoRef "__has_selector" fn.body)
(hHasSelectorDead : ∀ fn, fn ∈ contract.functions →
HasSelectorDeadBridge fn.body)
(hLoopFree : ∀ fn, fn ∈ contract.functions →
yulStmtsLoopFree fn.body = true)
(hbody : ∀ fn, fn ∈ contract.functions →
Compiler.Proofs.YulGeneration.resultsMatch
(execIRFunction fn tx.args
{ initialState with
sender := tx.sender
msgValue := tx.msgValue
thisAddress := tx.thisAddress
blockTimestamp := tx.blockTimestamp
blockNumber := tx.blockNumber
chainId := tx.chainId
blobBaseFee := tx.blobBaseFee
calldata := tx.args
selector := tx.functionSelector })
(interpretYulBody fn tx
{ initialState with
sender := tx.sender
msgValue := tx.msgValue
thisAddress := tx.thisAddress
blockTimestamp := tx.blockTimestamp
blockNumber := tx.blockNumber
chainId := tx.chainId
blobBaseFee := tx.blobBaseFee
calldata := tx.args
selector := tx.functionSelector })) :
Compiler.Proofs.YulGeneration.resultsMatch
(interpretIR contract tx initialState)
(interpretYulFromIR contract tx initialState) :=
yulCodegen_preserves_semantics contract tx initialState
hselector hNoWrap hWF hNoFallback hNoReceive hdispatchGuardSafe hNoHasSelector hHasSelectorDead
hLoopFree hbody
/-! ## Layers 2+3 Composition -/
/-- End-to-end: given a successfully compiled contract, IR execution matches
Yul execution. -/
theorem layers2_3_ir_matches_yul
(spec : CompilationModel.CompilationModel) (selectors : List Nat)
(irContract : IRContract) (tx : IRTransaction) (initialState : IRState)
(_hCompile : CompilationModel.compile spec selectors = .ok irContract)
(hselector : tx.functionSelector < selectorModulus)
(hNoWrap : 4 + tx.args.length * 32 < evmModulus)
(hvars : initialState.vars = [])
(hmemory : initialState.memory = fun _ => 0)
(htransient : initialState.transientStorage = fun _ => 0)
(hreturn : initialState.returnValue = none)
(hparamErase : ∀ fn, fn ∈ irContract.functions →
paramLoadErasure fn tx
{ initialState with
sender := tx.sender
msgValue := tx.msgValue
thisAddress := tx.thisAddress
blockTimestamp := tx.blockTimestamp
blockNumber := tx.blockNumber
chainId := tx.chainId
blobBaseFee := tx.blobBaseFee
calldata := tx.args
selector := tx.functionSelector })
(hdispatchGuardSafe : ∀ fn, fn ∈ irContract.functions →
DispatchGuardsSafe fn tx)
(hNoHasSelector : ∀ fn, fn ∈ irContract.functions →
yulStmtsNoRef "__has_selector" fn.body)
(hHasSelectorDead : ∀ fn, fn ∈ irContract.functions →
HasSelectorDeadBridge fn.body)
(hLoopFree : ∀ fn, fn ∈ irContract.functions →
yulStmtsLoopFree fn.body = true)
(hWF : ContractWF irContract)
(hNoFallback : irContract.fallbackEntrypoint = none)
(hNoReceive : irContract.receiveEntrypoint = none) :
Compiler.Proofs.YulGeneration.resultsMatch
(interpretIR irContract tx initialState)
(interpretYulFromIR irContract tx initialState) :=
layer3_contract_preserves_semantics irContract tx initialState
hselector hNoWrap hvars hmemory htransient hreturn hparamErase hdispatchGuardSafe hNoHasSelector
hHasSelectorDead hLoopFree hWF hNoFallback hNoReceive
/-! ## Concrete Instantiation: SimpleStorage -/
/-- SimpleStorage end-to-end: compile → IR → Yul preserves semantics. -/
theorem simpleStorage_endToEnd
(tx : IRTransaction) (initialState : IRState)
(hselector : tx.functionSelector < selectorModulus)
(hNoWrap : 4 + tx.args.length * 32 < evmModulus)
(hvars : initialState.vars = [])
(hmemory : initialState.memory = fun _ => 0)
(htransient : initialState.transientStorage = fun _ => 0)
(hreturn : initialState.returnValue = none)
(hdispatchGuardSafe : ∀ fn, fn ∈ simpleStorageIRContract.functions →
DispatchGuardsSafe fn tx)
(hNoHasSelector : ∀ fn, fn ∈ simpleStorageIRContract.functions →
yulStmtsNoRef "__has_selector" fn.body)
(hHasSelectorDead : ∀ fn, fn ∈ simpleStorageIRContract.functions →
HasSelectorDeadBridge fn.body)
(hparamErase : ∀ fn, fn ∈ simpleStorageIRContract.functions →
paramLoadErasure fn tx
{ initialState with
sender := tx.sender
msgValue := tx.msgValue
thisAddress := tx.thisAddress
blockTimestamp := tx.blockTimestamp
blockNumber := tx.blockNumber
chainId := tx.chainId
blobBaseFee := tx.blobBaseFee
calldata := tx.args
selector := tx.functionSelector }) :
Compiler.Proofs.YulGeneration.resultsMatch
(interpretIR simpleStorageIRContract tx initialState)
(interpretYulFromIR simpleStorageIRContract tx initialState) :=
layer3_contract_preserves_semantics simpleStorageIRContract tx initialState
hselector hNoWrap hvars hmemory htransient hreturn hparamErase hdispatchGuardSafe hNoHasSelector
hHasSelectorDead
(by intro fn hmem; simp [simpleStorageIRContract] at hmem ⊢; rcases hmem with rfl | rfl <;> rfl)
(by intro s hs; simp [simpleStorageIRContract] at hs) rfl rfl
/-! ## Universal Pure Arithmetic Bridge
The pure arithmetic bridge proofs (`pure_add_bridge`, etc.) were removed
after the `evalBuiltinCall` refactor (commit e5da6c7f) which added
`callvalue`/`calldatasize` support, making `evalBuiltinCall` too large
for the default heartbeat limit during type-checking. The proofs were
mathematically correct but need `evalBuiltinCall` to be factored into
smaller pieces before they can be re-stated without timeout.
See: `ArithmeticProfile.lean` and
`YulGeneration/Backends/EvmYulLeanBridgeLemmas.lean` for the current
replacement coverage: universal bridge lemmas for all pure bridged
builtins.
-/
end Compiler.Proofs.EndToEnd