-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathSourceSemantics.lean
More file actions
5368 lines (5062 loc) · 235 KB
/
Copy pathSourceSemantics.lean
File metadata and controls
5368 lines (5062 loc) · 235 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
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import Compiler.Proofs.IRGeneration.SupportedSpec
import Compiler.Proofs.IRGeneration.IRInterpreter
import Compiler.Proofs.MappingSlot
import Compiler.CompilationModel.LayoutValidation
import Compiler.Keccak.Sponge
set_option linter.unnecessarySimpa false
set_option linter.unusedSimpArgs false
set_option linter.unusedTactic false
set_option linter.unusedVariables false
namespace Compiler.Proofs.IRGeneration
open Compiler
open Compiler.CompilationModel
namespace SourceSemantics
def wordNormalize (n : Nat) : Nat :=
((n : Verity.Core.Uint256) : Nat)
@[simp] theorem wordNormalize_eq_mod (n : Nat) :
wordNormalize n = n % Compiler.Constants.evmModulus := by
rfl
def uint8Modulus : Nat := 2 ^ 8
def addressModulus : Nat := 2 ^ 160
def boolWord (b : Bool) : Nat :=
if b then 1 else 0
/-- Big-endian 32-byte encoding of a 256-bit word (byte 0 most significant). -/
def wordToBytesBE (w : Nat) : ByteArray :=
⟨((List.range 32).map (fun i => UInt8.ofNat ((w / (256 ^ (31 - i))) % 256))).toArray⟩
/-- Concatenate the big-endian bytes of the word-aligned memory cells covering
`[offset, offset + size)` and keep the first `size` bytes.
This is faithful to EVM `keccak256(offset, size)` for the word-aligned access
pattern Verity's compiler emits: one 32-byte cell per slot at `offset + 32*i`,
matching how `mload`/`mstore` are modelled here (memory is keyed by byte offset
and returns the full word stored at that key). -/
def memorySliceBytesBE (memory : Nat → Verity.Core.Uint256) (offset size : Nat) :
ByteArray :=
let nWords := (size + 31) / 32
let full := (List.range nWords).foldl
(fun acc i => acc ++ wordToBytesBE (memory (offset + 32 * i)).val) ByteArray.empty
full.extract 0 size
/-- Source-semantics model of `keccak256(offset, size)`: the in-tree pure Keccak
engine applied to the word-aligned memory slice, returned as a big-endian word.
Uses the real `KeccakEngine.keccak256`, so the modelled digest is the genuine
Keccak-256 of the slice (no abstract placeholder). -/
def keccakMemorySlice (memory : Nat → Verity.Core.Uint256) (offset size : Nat) : Nat :=
wordNormalize (KeccakEngine.byteArrayToNatBE
(KeccakEngine.keccak256 (memorySliceBytesBE memory offset size)))
/-- Low-level proof encoding of an emitted event.
Source execution stores the same log payload shape as proof IR `log*`
execution: topics first, followed by word-aligned data words. For known events
`eventFromResolvedArgs?` places the event signature topic at the front of
`indexedArgs`, so this encoder intentionally ignores the high-level name. -/
def encodeEvent (ev : Verity.Event) : List Nat :=
ev.indexedArgs.map (fun arg => arg.val) ++ ev.args.map (fun arg => arg.val)
def encodeEvents (events : List Verity.Event) : List (List Nat) :=
events.map encodeEvent
def valuesAsEventArgs (values : List Nat) : List Verity.Core.Uint256 :=
values.map (fun value => (value : Verity.Core.Uint256))
def eventSignatureMemory (eventDef : EventDef) : Nat → Nat :=
let words :=
(chunkBytes32 (bytesFromString (eventSignature eventDef))).map wordFromBytes
fun offset =>
if offset % 32 = 0 then
(words[offset / 32]?).getD 0
else
0
def eventSignatureTopic (eventDef : EventDef) : Nat :=
abstractKeccakMemorySlice (eventSignatureMemory eventDef) 0
(bytesFromString (eventSignature eventDef)).length
def normalizeEventValue (ty : ParamType) (value : Nat) : Nat :=
let word := wordNormalize value
match ty with
| .uint8 => word &&& (uint8Modulus - 1)
| .uint16 => word &&& (2^16 - 1)
| .address => word &&& Compiler.Constants.addressMask
| .bool => if word = 0 then 0 else 1
-- Mirrors the compiler's newtype erasure in `normalizeEventWord`.
| .newtypeOf _ baseType => normalizeEventValue baseType value
| _ => word
def splitEventArgsByParams :
List EventParam → List Nat → Option (List Verity.Core.Uint256 × List Verity.Core.Uint256)
| [], [] => some ([], [])
| param :: params, value :: values => do
let (args, indexedArgs) ← splitEventArgsByParams params values
let normalized := normalizeEventValue param.ty value
if param.kind == EventParamKind.indexed then
some (args, (normalized : Verity.Core.Uint256) :: indexedArgs)
else
some ((normalized : Verity.Core.Uint256) :: args, indexedArgs)
| _, _ => none
theorem exists_splitEventArgsByParams_of_length :
∀ {params : List EventParam} {values : List Nat},
values.length = params.length →
∃ args indexedArgs,
splitEventArgsByParams params values = some (args, indexedArgs)
| [], [], _ => by
exact ⟨[], [], rfl⟩
| [], _ :: _, hlen => by
simp at hlen
| _ :: _, [], hlen => by
simp at hlen
| param :: params, value :: values, hlen => by
have htail : values.length = params.length := by
simpa using Nat.succ.inj hlen
rcases exists_splitEventArgsByParams_of_length htail with
⟨args, indexedArgs, hsplit⟩
by_cases hkind : param.kind == EventParamKind.indexed
· exact ⟨args, (normalizeEventValue param.ty value : Verity.Core.Uint256) :: indexedArgs,
by simp [splitEventArgsByParams, hsplit, hkind]⟩
· exact ⟨(normalizeEventValue param.ty value : Verity.Core.Uint256) :: args, indexedArgs,
by simp [splitEventArgsByParams, hsplit, hkind]⟩
def eventFromResolvedArgs? (events : List EventDef) (eventName : String)
(values : List Nat) : Option Verity.Event :=
match events.find? (·.name == eventName) with
| none =>
some { name := eventName, args := valuesAsEventArgs values, indexedArgs := [] }
| some eventDef => do
let (args, indexedArgs) ← splitEventArgsByParams eventDef.params values
some {
name := eventName
args := args
indexedArgs := (eventSignatureTopic eventDef : Verity.Core.Uint256) :: indexedArgs }
theorem exists_eventFromResolvedArgs?_of_supported_length
{events : List EventDef}
{eventName : String}
{exprArgs : List Expr}
{values : List Nat}
(hsupport : eventEmissionProofSupported events eventName exprArgs = true)
(hlen : values.length = exprArgs.length) :
∃ event, eventFromResolvedArgs? events eventName values = some event := by
rcases exists_eventDef_of_eventEmissionProofSupported hsupport with
⟨eventDef, hfind, _hscalar, hargsLen⟩
have hvalueLen : values.length = eventDef.params.length := by
rw [hlen, hargsLen]
rcases exists_splitEventArgsByParams_of_length hvalueLen with
⟨args, indexedArgs, hsplit⟩
refine ⟨{ name := eventName
args := args
indexedArgs :=
(eventSignatureTopic eventDef : Verity.Core.Uint256) :: indexedArgs }, ?_⟩
simp [eventFromResolvedArgs?, hfind, hsplit]
/-- Sequential scratch stores of the signature chunk words, starting at word
index `wordIdx`. Write keys wrap mod `2^256`, mirroring the compiled code's
wrapping `add` offset arithmetic. -/
def writeEventSignatureScratchFrom :
List Nat → Nat → Nat → (Nat → Verity.Core.Uint256) → (Nat → Verity.Core.Uint256)
| [], _, _, memory => memory
| word :: words, ptr, wordIdx, memory =>
writeEventSignatureScratchFrom words ptr (wordIdx + 1)
(fun offset =>
if offset = (ptr + wordIdx * 32) % Compiler.Constants.evmModulus then
(word : Verity.Core.Uint256)
else
memory offset)
def writeEventSignatureScratch (eventDef : EventDef)
(ptr : Nat) (memory : Nat → Verity.Core.Uint256) : Nat → Verity.Core.Uint256 :=
writeEventSignatureScratchFrom
((chunkBytes32 (bytesFromString (eventSignature eventDef))).map wordFromBytes)
ptr 0 memory
def writeUnindexedEventScratchFrom :
List EventParam → List Nat → Nat → Nat → (Nat → Verity.Core.Uint256) →
Option (Nat → Verity.Core.Uint256)
| [], [], _, _, memory => some memory
| param :: params, value :: values, ptr, wordIdx, memory =>
let normalized := normalizeEventValue param.ty value
let next :=
if param.kind == EventParamKind.unindexed then
fun offset =>
if offset = (ptr + wordIdx * 32) % Compiler.Constants.evmModulus then
(normalized : Verity.Core.Uint256)
else
memory offset
else
memory
writeUnindexedEventScratchFrom params values ptr
(if param.kind == EventParamKind.unindexed then wordIdx + 1 else wordIdx)
next
| _, _, _, _, _ => none
def writeUnindexedEventScratch
(params : List EventParam) (values : List Nat)
(ptr : Nat) (memory : Nat → Verity.Core.Uint256) : Option (Nat → Verity.Core.Uint256) :=
writeUnindexedEventScratchFrom params values ptr 0 memory
theorem exists_writeUnindexedEventScratch_of_length :
∀ {params : List EventParam} {values : List Nat}
{ptr wordIdx : Nat} {memory : Nat → Verity.Core.Uint256},
values.length = params.length →
∃ memory',
writeUnindexedEventScratchFrom params values ptr wordIdx memory = some memory'
| [], [], _, _, memory, _ => by
exact ⟨memory, rfl⟩
| [], _ :: _, _, _, _, hlen => by
simp at hlen
| _ :: _, [], _, _, _, hlen => by
simp at hlen
| param :: params, value :: values, ptr, wordIdx, memory, hlen => by
have htail : values.length = params.length := by
simpa using Nat.succ.inj hlen
by_cases hkind : param.kind == EventParamKind.unindexed
· rcases exists_writeUnindexedEventScratch_of_length
(params := params) (values := values) (ptr := ptr)
(wordIdx := wordIdx + 1)
(memory := fun offset =>
if offset = (ptr + wordIdx * 32) % Compiler.Constants.evmModulus then
(normalizeEventValue param.ty value : Verity.Core.Uint256)
else
memory offset)
htail with
⟨memory', hmem⟩
refine ⟨memory', ?_⟩
simpa [writeUnindexedEventScratchFrom, hkind] using hmem
· rcases exists_writeUnindexedEventScratch_of_length
(params := params) (values := values) (ptr := ptr)
(wordIdx := wordIdx)
(memory := memory)
htail with
⟨memory', hmem⟩
refine ⟨memory', ?_⟩
simpa [writeUnindexedEventScratchFrom, hkind] using hmem
theorem exists_writeUnindexedEventScratch_of_length_zero
{params : List EventParam} {values : List Nat}
{ptr : Nat} {memory : Nat → Verity.Core.Uint256}
(hlen : values.length = params.length) :
∃ memory', writeUnindexedEventScratch params values ptr memory = some memory' := by
simpa [writeUnindexedEventScratch] using
(exists_writeUnindexedEventScratch_of_length
(params := params) (values := values) (ptr := ptr) (wordIdx := 0)
(memory := memory) hlen)
def eventScratchMemoryAfterEmit? (events : List EventDef)
(eventName : String) (values : List Nat)
(memory : Nat → Verity.Core.Uint256) : Option (Nat → Verity.Core.Uint256) :=
match events.find? (·.name == eventName) with
| none => some memory
| some eventDef =>
if eventDefScalarProofSupported eventDef then
let ptr := (memory Compiler.Constants.freeMemoryPointer).val
writeUnindexedEventScratch eventDef.params values ptr
(writeEventSignatureScratch eventDef ptr memory)
else
some memory
theorem exists_eventScratchMemoryAfterEmit?_of_supported_length
{events : List EventDef}
{eventName : String}
{exprArgs : List Expr}
{values : List Nat}
{memory : Nat → Verity.Core.Uint256}
(hsupport : eventEmissionProofSupported events eventName exprArgs = true)
(hlen : values.length = exprArgs.length) :
∃ memory',
eventScratchMemoryAfterEmit? events eventName values memory = some memory' := by
rcases exists_eventDef_of_eventEmissionProofSupported hsupport with
⟨eventDef, hfind, hscalar, hargsLen⟩
have hvalueLen : values.length = eventDef.params.length := by
rw [hlen, hargsLen]
unfold eventScratchMemoryAfterEmit?
simp [hfind, hscalar]
exact exists_writeUnindexedEventScratch_of_length_zero
(params := eventDef.params) (values := values)
(ptr := (memory Compiler.Constants.freeMemoryPointer).val)
(memory := writeEventSignatureScratch eventDef
(memory Compiler.Constants.freeMemoryPointer).val memory)
hvalueLen
def effectiveFields (spec : CompilationModel) : List Field :=
applySlotAliasRanges spec.fields spec.slotAliasRanges
def fieldUsesAddressStorage (field : Field) : Bool :=
match field.ty with
| .address => true
| _ => false
def fieldUsesDynamicArrayStorage (field : Field) : Bool :=
match field.ty with
| .dynamicArray _ => true
| _ => false
def findResolvedFieldAtSlot (fields : List Field) (slot : Nat) : Option Field :=
let rec go (remaining : List Field) (idx : Nat) : Option Field :=
match remaining with
| [] => none
| field :: rest =>
let resolvedSlot := field.slot.getD idx
if wordNormalize resolvedSlot = wordNormalize slot ||
(field.aliasSlots.map wordNormalize).contains (wordNormalize slot) then
some field
else
go rest (idx + 1)
go fields 0
def findDynamicArrayElementAtSlot
(fields : List Field) (world : Verity.ContractState) (targetSlot : Nat) : Option Nat :=
let rec scanElements (baseSlot : Nat) : List Verity.Core.Uint256 → Nat → Option Nat
| [], _ => none
| value :: rest, idx =>
if Compiler.Proofs.solidityMappingSlot baseSlot idx = wordNormalize targetSlot then
some value.val
else
scanElements baseSlot rest (idx + 1)
let rec go (remaining : List Field) (idx : Nat) : Option Nat :=
match remaining with
| [] => none
| field :: rest =>
let resolvedSlot := field.slot.getD idx
match field.ty with
| .dynamicArray _ =>
match scanElements resolvedSlot (world.storageArray resolvedSlot) 0 with
| some value => some value
| none => go rest (idx + 1)
| _ => go rest (idx + 1)
go fields 0
/-- Bridge lemma: the EvmYulLean `UInt256.size` and the verity-core
`UINT256_MODULUS` literal are the same `Nat` (`2^256`). Used to discharge
`% UInt256.size` modulo wraps that arise from `IRStorageWord.toNat`
projection of values originally bounded by `Verity.Core.Uint256.isLt`. -/
@[simp] theorem UInt256_size_eq_UINT256_MODULUS :
EvmYul.UInt256.size = Verity.Core.UINT256_MODULUS := by
unfold EvmYul.UInt256.size Verity.Core.UINT256_MODULUS
rfl
def encodeStorageAt (fields : List Field) (world : Verity.ContractState) (slot : Nat) : Nat :=
match findResolvedFieldAtSlot fields slot with
| some field =>
if fieldUsesAddressStorage field then
(world.storageAddr slot).val
else if fieldUsesDynamicArrayStorage field then
(world.storageArray slot).length
else
(world.storage slot).val
| none =>
match findDynamicArrayElementAtSlot fields world slot with
| some value => value
| none => (world.storage slot).val
def encodeStorage (spec : CompilationModel) (world : Verity.ContractState) :
Nat → Nat :=
encodeStorageAt (effectiveFields spec) world
def writeUintSlots (world : Verity.ContractState) (slots : List Nat) (value : Nat) :
Verity.ContractState :=
let word : Verity.Core.Uint256 := value
let targets := slots.map wordNormalize
{ world with
storage := fun slot =>
if targets.contains slot then word else world.storage slot }
def writeStorageWordSlot (world : Verity.ContractState) (slot wordOffset value : Nat) :
Verity.ContractState :=
let word : Verity.Core.Uint256 := value
let addr := Verity.wordToAddress word
let target := wordNormalize (slot + wordOffset)
{ world with
storage := fun current =>
if current = target then word else world.storage current,
storageAddr := fun current =>
if current = target then addr else world.storageAddr current }
def writeStorageWordSlots (world : Verity.ContractState) (slots : List Nat) (wordOffset value : Nat) :
Verity.ContractState :=
let word : Verity.Core.Uint256 := value
let addr := Verity.wordToAddress word
let targets := slots.map (fun slot => wordNormalize (slot + wordOffset))
{ world with
storage := fun current =>
if targets.contains current then word else world.storage current,
storageAddr := fun current =>
if targets.contains current then addr else world.storageAddr current }
def writeAddressSlots (world : Verity.ContractState) (slots : List Nat) (value : Nat) :
Verity.ContractState :=
let addr := Verity.wordToAddress (value : Verity.Core.Uint256)
let targets := slots.map wordNormalize
{ world with
storageAddr := fun slot =>
if targets.contains slot then addr else world.storageAddr slot }
def writeAddressKeyedMappingSlots
(world : Verity.ContractState) (slots : List Nat) (key value : Nat) :
Verity.ContractState :=
match slots with
| [] => world
| slot :: _ =>
let keyAddr := Verity.wordToAddress (key : Verity.Core.Uint256)
let word : Verity.Core.Uint256 := value
let storageNat : Compiler.Proofs.IRGeneration.IRStorageSlot →
Compiler.Proofs.IRGeneration.IRStorageWord :=
fun s => Compiler.Proofs.IRGeneration.IRStorageWord.ofNat (world.storage s.toNat).val
let storage :=
slots.foldl
(fun current slot =>
Compiler.Proofs.abstractStoreMappingEntry current slot key value)
storageNat
{ world with
storage := fun s =>
(Compiler.Proofs.IRGeneration.IRStorageWord.toNat
(storage (Compiler.Proofs.IRGeneration.IRStorageSlot.ofNat s)) : Verity.Core.Uint256)
storageMap := fun baseSlot addr =>
if baseSlot == slot && addr == keyAddr then
word
else
world.storageMap baseSlot addr }
def mappingSlotChain (baseSlot : Nat) (keys : List Nat) : Nat :=
keys.foldl Compiler.Proofs.abstractMappingSlot baseSlot
def writeAddressKeyedMappingChainSlots
(world : Verity.ContractState) (slots keys : List Nat) (value : Nat) :
Verity.ContractState :=
let word : Verity.Core.Uint256 := value
let targets := slots.map (fun slot => wordNormalize (mappingSlotChain slot keys))
{ world with
storage := fun slot =>
if targets.contains slot then word else world.storage slot }
def writeAddressKeyedMappingWordSlots
(world : Verity.ContractState) (slots : List Nat) (key wordOffset value : Nat) :
Verity.ContractState :=
let word : Verity.Core.Uint256 := value
let targets :=
slots.map (fun slot =>
wordNormalize (Compiler.Proofs.abstractMappingSlot slot key + wordOffset))
{ world with
storage := fun slot =>
if targets.contains slot then word else world.storage slot }
def packedWordWrite (current value : Nat) (packed : PackedBits) : Nat :=
let maskNat := packedMaskNat packed
let shiftedMaskNat := packedShiftedMaskNat packed
let packedValue := Verity.Core.Uint256.and value maskNat
let cleared := Verity.Core.Uint256.and current (Verity.Core.Uint256.not shiftedMaskNat)
(Verity.Core.Uint256.or cleared (Verity.Core.Uint256.shl packed.offset packedValue)).val
def writeAddressKeyedMappingPackedWordSlots
(world : Verity.ContractState) (slots : List Nat) (key wordOffset : Nat)
(packed : PackedBits) (value : Nat) :
Verity.ContractState :=
let targets :=
slots.map (fun slot =>
wordNormalize (Compiler.Proofs.abstractMappingSlot slot key + wordOffset))
{ world with
storage := fun slot =>
if targets.contains slot then
packedWordWrite (world.storage slot).val value packed
else
world.storage slot }
def writeAddressKeyedMapping2PackedWordSlots
(world : Verity.ContractState) (slots : List Nat) (key1 key2 wordOffset : Nat)
(packed : PackedBits) (value : Nat) :
Verity.ContractState :=
let targets :=
slots.map (fun slot =>
wordNormalize
(Compiler.Proofs.abstractMappingSlot
(Compiler.Proofs.abstractMappingSlot slot key1) key2 + wordOffset))
{ world with
storage := fun slot =>
if targets.contains slot then
packedWordWrite (world.storage slot).val value packed
else
world.storage slot }
def writeUintKeyedMappingSlots
(world : Verity.ContractState) (slots : List Nat) (key value : Nat) :
Verity.ContractState :=
match slots with
| [] => world
| slot :: _ =>
let keyWord : Verity.Core.Uint256 := key
let word : Verity.Core.Uint256 := value
let storageNat : Compiler.Proofs.IRGeneration.IRStorageSlot →
Compiler.Proofs.IRGeneration.IRStorageWord :=
fun s => Compiler.Proofs.IRGeneration.IRStorageWord.ofNat (world.storage s.toNat).val
let storage :=
slots.foldl
(fun current slot =>
Compiler.Proofs.abstractStoreMappingEntry current slot key value)
storageNat
{ world with
storage := fun s =>
(Compiler.Proofs.IRGeneration.IRStorageWord.toNat
(storage (Compiler.Proofs.IRGeneration.IRStorageSlot.ofNat s)) : Verity.Core.Uint256)
storageMapUint := fun baseSlot key' =>
if baseSlot == slot && key' == keyWord then
word
else
world.storageMapUint baseSlot key' }
def writeAddressKeyedMapping2Slots
(world : Verity.ContractState) (slots : List Nat) (key1 key2 value : Nat) :
Verity.ContractState :=
match slots with
| [] => world
| slot :: _ =>
let key1Addr := Verity.wordToAddress (key1 : Verity.Core.Uint256)
let key2Addr := Verity.wordToAddress (key2 : Verity.Core.Uint256)
let word : Verity.Core.Uint256 := value
let storageNat : Compiler.Proofs.IRGeneration.IRStorageSlot →
Compiler.Proofs.IRGeneration.IRStorageWord :=
fun s => Compiler.Proofs.IRGeneration.IRStorageWord.ofNat (world.storage s.toNat).val
let storage :=
slots.foldl
(fun current slot =>
Compiler.Proofs.abstractStoreMappingEntry
current
(Compiler.Proofs.abstractMappingSlot slot key1)
key2
value)
storageNat
{ world with
storage := fun s =>
(Compiler.Proofs.IRGeneration.IRStorageWord.toNat
(storage (Compiler.Proofs.IRGeneration.IRStorageSlot.ofNat s)) : Verity.Core.Uint256)
storageMap2 := fun baseSlot addr1 addr2 =>
if baseSlot == slot && addr1 == key1Addr && addr2 == key2Addr then
word
else
world.storageMap2 baseSlot addr1 addr2 }
def writeAddressKeyedMapping2WordSlots
(world : Verity.ContractState) (slots : List Nat) (key1 key2 wordOffset value : Nat) :
Verity.ContractState :=
let word : Verity.Core.Uint256 := value
let targets := slots.map (fun slot =>
wordNormalize
(Compiler.Proofs.abstractMappingSlot
(Compiler.Proofs.abstractMappingSlot slot key1)
key2 + wordOffset))
{ world with
storage := fun slot =>
if targets.contains slot then word else world.storage slot }
def decodeSupportedParamWord (ty : ParamType) (word : Nat) : Option Nat :=
let word := wordNormalize word
match ty with
| .uint256 | .int256 | .bytes32 => some word
| .uint8 => some (word &&& (uint8Modulus - 1))
| .uint16 => some (word &&& (2^16 - 1))
| .address => some (word &&& Compiler.Constants.addressMask)
| .bool => some (if word = 0 then 0 else 1)
| _ => none
def bindValue (bindings : List (String × Nat)) (name : String) (value : Nat) :
List (String × Nat) :=
(name, value) :: bindings.filter (fun entry => entry.1 != name)
def lookupValue (bindings : List (String × Nat)) (name : String) : Nat :=
bindings.find? (fun entry => entry.1 == name) |>.map Prod.snd |>.getD 0
def lookupBinding? (bindings : List (String × Nat)) (name : String) : Option Nat :=
bindings.find? (fun entry => entry.1 == name) |>.map Prod.snd
def bindInternalArgs (params : List Param) (args : List Nat) :
Option (List (String × Nat)) :=
match params, args with
| [], [] => some []
| param :: restParams, arg :: restArgs => do
let bindings ← bindInternalArgs restParams restArgs
pure ((param.name, arg) :: bindings)
| _, _ => none
private def findUniqueInternalFunction? (spec : CompilationModel) (calleeName : String) :
Option FunctionSpec :=
match spec.functions.filter (fun fn => fn.isInternal && fn.name == calleeName) with
| [fn] => some fn
| _ => none
structure RuntimeState where
world : Verity.ContractState
bindings : List (String × Nat)
selector : Nat := 0
inductive StmtResult where
| continue (state : RuntimeState)
| stop (state : RuntimeState)
| return (value : Nat) (state : RuntimeState)
| revert
def execForEachLoop
(varName : String)
(runBody : RuntimeState → StmtResult) :
RuntimeState → Nat → Nat → StmtResult
| state, _, 0 => .continue state
| state, index, remaining + 1 =>
let loopState :=
{ state with bindings := bindValue state.bindings varName (wordNormalize index) }
match runBody loopState with
| .continue next => execForEachLoop varName runBody next (index + 1) remaining
| .stop next => .stop next
| .return value next => .return value next
| .revert => .revert
@[simp] theorem execForEachLoop_zero
(varName : String)
(runBody : RuntimeState → StmtResult)
(state : RuntimeState)
(index : Nat) :
execForEachLoop varName runBody state index 0 = .continue state := rfl
theorem execForEachLoop_succ
(varName : String)
(runBody : RuntimeState → StmtResult)
(state : RuntimeState)
(index remaining : Nat) :
execForEachLoop varName runBody state index (remaining + 1) =
let loopState :=
{ state with bindings := bindValue state.bindings varName (wordNormalize index) }
match runBody loopState with
| .continue next => execForEachLoop varName runBody next (index + 1) remaining
| .stop next => .stop next
| .return value next => .return value next
| .revert => .revert := rfl
@[simp] theorem lookupBinding?_bindValue_same
(bindings : List (String × Nat))
(name : String)
(value : Nat) :
lookupBinding? (bindValue bindings name value) name = some value := by
simp [lookupBinding?, bindValue]
@[simp] theorem lookupValue_bindValue_same
(bindings : List (String × Nat))
(name : String)
(value : Nat) :
lookupValue (bindValue bindings name value) name = value := by
simp [lookupValue, bindValue]
@[simp] theorem execForEachLoop_boundState_lookupBinding?
(varName : String)
(state : RuntimeState)
(index : Nat) :
lookupBinding?
(bindValue state.bindings varName (wordNormalize index))
varName =
some (wordNormalize index) := by
simp
@[simp] theorem execForEachLoop_boundState_lookupValue
(varName : String)
(state : RuntimeState)
(index : Nat) :
lookupValue
(bindValue state.bindings varName (wordNormalize index))
varName =
wordNormalize index := by
simp
theorem execForEachLoop_zero_continue_state
{varName : String}
{runBody : RuntimeState → StmtResult}
{state final : RuntimeState}
{index : Nat}
(hloop : execForEachLoop varName runBody state index 0 = .continue final) :
final = state := by
simpa [execForEachLoop] using hloop.symm
theorem execForEachLoop_succ_continue_iff
{varName : String}
{runBody : RuntimeState → StmtResult}
{state final : RuntimeState}
{index remaining : Nat} :
execForEachLoop varName runBody state index (remaining + 1) = .continue final ↔
∃ next,
runBody
{ state with
bindings := bindValue state.bindings varName (wordNormalize index) } =
.continue next ∧
execForEachLoop varName runBody next (index + 1) remaining =
.continue final := by
simp only [execForEachLoop]
cases hbody :
runBody
{ state with
bindings := bindValue state.bindings varName (wordNormalize index) } <;>
simp [hbody]
theorem execForEachLoop_succ_continue
{varName : String}
{runBody : RuntimeState → StmtResult}
{state next final : RuntimeState}
{index remaining : Nat}
(hbody :
runBody
{ state with
bindings := bindValue state.bindings varName (wordNormalize index) } =
.continue next)
(hloop :
execForEachLoop varName runBody next (index + 1) remaining =
.continue final) :
execForEachLoop varName runBody state index (remaining + 1) =
.continue final := by
rw [execForEachLoop_succ]
simpa only [hbody] using hloop
theorem execForEachLoop_congr
{varName : String}
{runBodyA runBodyB : RuntimeState → StmtResult}
(hbody : ∀ state, runBodyA state = runBodyB state) :
∀ (state : RuntimeState) (index remaining : Nat),
execForEachLoop varName runBodyA state index remaining =
execForEachLoop varName runBodyB state index remaining
| state, index, 0 => by
simp [execForEachLoop]
| state, index, remaining + 1 => by
simp only [execForEachLoop]
rw [hbody]
cases hrun : runBodyB
{ state with bindings := bindValue state.bindings varName (wordNormalize index) } <;>
simp [hrun, execForEachLoop_congr hbody]
def execForEachEmptyLoopFinal
(varName : String) : RuntimeState → Nat → Nat → RuntimeState
| state, _, 0 => state
| state, index, remaining + 1 =>
execForEachEmptyLoopFinal varName
{ state with bindings := bindValue state.bindings varName (wordNormalize index) }
(index + 1) remaining
theorem execForEachLoop_empty_body
(varName : String)
(state : RuntimeState)
(index remaining : Nat) :
execForEachLoop varName (fun loopState => .continue loopState)
state index remaining =
.continue (execForEachEmptyLoopFinal varName state index remaining) := by
induction remaining generalizing state index with
| zero =>
rfl
| succ remaining ih =>
simp [execForEachLoop, execForEachEmptyLoopFinal, ih]
theorem execForEachLoop_empty_body_zero_bound
(varName : String)
(state : RuntimeState)
(index : Nat) :
execForEachLoop varName (fun loopState => .continue loopState)
state index 0 =
.continue state := rfl
theorem execForEachLoop_empty_body_positive_bound
(varName : String)
(state : RuntimeState)
(index remaining : Nat) :
execForEachLoop varName (fun loopState => .continue loopState)
state index (remaining + 1) =
.continue
(execForEachEmptyLoopFinal varName
{ state with bindings := bindValue state.bindings varName (wordNormalize index) }
(index + 1) remaining) := by
simp [execForEachLoop_empty_body, execForEachEmptyLoopFinal]
def storageArraySetAt : List Verity.Core.Uint256 → Nat → Verity.Core.Uint256 → Option (List Verity.Core.Uint256)
| [], _, _ => none
| _ :: rest, 0, value => some (value :: rest)
| head :: rest, idx + 1, value => do
let updatedRest ← storageArraySetAt rest idx value
some (head :: updatedRest)
def storageArrayDropLast? : List Verity.Core.Uint256 → Option (List Verity.Core.Uint256)
| [] => none
| [_] => some []
| head :: rest => do
let updatedRest ← storageArrayDropLast? rest
some (head :: updatedRest)
def writeStorageArray (world : Verity.ContractState) (slot : Nat)
(values : List Verity.Core.Uint256) : Verity.ContractState :=
{ world with
storageArray := fun s => if s == slot then values else world.storageArray s }
/-- Ceiling-division helper matching Solidity's `Math256.ceilDiv`.
Factored out so the mutual block's equation-lemma derivation stays simple. -/
private def ceilDivVal (lhs rhs : Verity.Core.Uint256) : Nat :=
if lhs == 0 then 0 else ((lhs - 1) / rhs + 1).val
def evalExpr (fields : List Field) (state : RuntimeState) : Expr → Option Nat
| .memoryArrayLength _ => none
| .memoryArrayElement _ _ => none
| .arrayElementDynamicDataOffset _ _ => none
| .arrayElementDynamicMemberLength _ _ _ => none
| .arrayElementDynamicMemberDataOffset _ _ _ => none
| .arrayElementDynamicMemberElement _ _ _ _ => none
| .paramDynamicMemberLength _ _ => none
| .paramDynamicMemberDataOffset _ _ => none
| .paramDynamicMemberElement _ _ _ => none
| .paramDynamicStaticComposite _ _ => none
| .literal n => some (wordNormalize n)
| .param name => some (lookupValue state.bindings name)
| .storage fieldName =>
match findFieldWithResolvedSlot fields fieldName with
| some (_, slot) => some (state.world.storage (wordNormalize slot)).val
| none => none
| .storageAddr fieldName =>
match findFieldWithResolvedSlot fields fieldName with
| some (_, slot) => some (state.world.storageAddr (wordNormalize slot)).val
| none => none
| .storageArrayLength fieldName =>
match findFieldWithResolvedSlot fields fieldName with
| some ({ ty := .dynamicArray _, .. }, slot) => some (state.world.storageArray slot).length
| _ => none
| .storageArrayElement fieldName index => do
let idx ← evalExpr fields state index
match findFieldWithResolvedSlot fields fieldName with
| some ({ ty := .dynamicArray _, .. }, slot) =>
match (state.world.storageArray slot)[idx]? with
| some value => some value.val
| none => none
| _ => none
| .caller => some state.world.sender.val
| .contractAddress => some state.world.thisAddress.val
| .txOrigin => some state.world.txOrigin.val
| .chainid => some state.world.chainId.val
| .msgValue => some state.world.msgValue.val
| .selfBalance => some state.world.selfBalance.val
| .blockTimestamp => some state.world.blockTimestamp.val
| .blockNumber => some state.world.blockNumber.val
| .blobbasefee => some state.world.blobBaseFee.val
| .calldatasize => some state.world.calldataSize.val
| .localVar name => some (lookupValue state.bindings name)
| .add a b => do
let lhs : Verity.Core.Uint256 := ← evalExpr fields state a
let rhs : Verity.Core.Uint256 := ← evalExpr fields state b
pure (lhs + rhs).val
| .sub a b => do
let lhs : Verity.Core.Uint256 := ← evalExpr fields state a
let rhs : Verity.Core.Uint256 := ← evalExpr fields state b
pure (lhs - rhs).val
| .mul a b => do
let lhs : Verity.Core.Uint256 := ← evalExpr fields state a
let rhs : Verity.Core.Uint256 := ← evalExpr fields state b
pure (lhs * rhs).val
| .div a b => do
let lhs : Verity.Core.Uint256 := ← evalExpr fields state a
let rhs : Verity.Core.Uint256 := ← evalExpr fields state b
pure (lhs / rhs).val
| .mod a b => do
let lhs : Verity.Core.Uint256 := ← evalExpr fields state a
let rhs : Verity.Core.Uint256 := ← evalExpr fields state b
pure (lhs % rhs).val
| .bitAnd a b => do
let lhs ← evalExpr fields state a
let rhs ← evalExpr fields state b
pure (Verity.Core.Uint256.and lhs rhs).val
| .bitOr a b => do
let lhs ← evalExpr fields state a
let rhs ← evalExpr fields state b
pure (Verity.Core.Uint256.or lhs rhs).val
| .bitXor a b => do
let lhs ← evalExpr fields state a
let rhs ← evalExpr fields state b
pure (Verity.Core.Uint256.xor lhs rhs).val
| .bitNot a => do
let value ← evalExpr fields state a
pure (Verity.Core.Uint256.not value).val
| .eq a b => do
let lhs ← evalExpr fields state a
let rhs ← evalExpr fields state b
pure (boolWord (decide (lhs = rhs)))
| .ge a b => do
let lhs ← evalExpr fields state a
let rhs ← evalExpr fields state b
pure (boolWord (decide (rhs ≤ lhs)))
| .gt a b => do
let lhs ← evalExpr fields state a
let rhs ← evalExpr fields state b
pure (boolWord (decide (rhs < lhs)))
| .lt a b => do
let lhs ← evalExpr fields state a
let rhs ← evalExpr fields state b
pure (boolWord (decide (lhs < rhs)))
| .le a b => do
let lhs ← evalExpr fields state a
let rhs ← evalExpr fields state b
pure (boolWord (decide (lhs ≤ rhs)))
| .logicalAnd a b => do
let lhs ← evalExpr fields state a
let rhs ← evalExpr fields state b
pure (boolWord (decide (lhs != 0) && decide (rhs != 0)))
| .logicalOr a b => do
let lhs ← evalExpr fields state a
let rhs ← evalExpr fields state b
pure (boolWord (decide (lhs != 0) || decide (rhs != 0)))
| .logicalNot a => do
let value ← evalExpr fields state a
pure (boolWord (decide (value = 0)))
| .min a b => do
let lhs ← evalExpr fields state a
let rhs ← evalExpr fields state b
pure (if lhs ≤ rhs then lhs else rhs)
| .max a b => do
let lhs ← evalExpr fields state a
let rhs ← evalExpr fields state b
pure (if rhs ≤ lhs then lhs else rhs)
| .wMulDown a b => do
let lhs : Verity.Core.Uint256 := ← evalExpr fields state a
let rhs : Verity.Core.Uint256 := ← evalExpr fields state b
let wad : Verity.Core.Uint256 := 1000000000000000000
pure ((lhs * rhs) / wad).val
| .wDivUp a b => do
let lhs : Verity.Core.Uint256 := ← evalExpr fields state a
let rhs : Verity.Core.Uint256 := ← evalExpr fields state b
let wad : Verity.Core.Uint256 := 1000000000000000000
pure (((lhs * wad) + (rhs - 1)) / rhs).val
| .ceilDiv a b => do
let lhs : Verity.Core.Uint256 := ← evalExpr fields state a
let rhs : Verity.Core.Uint256 := ← evalExpr fields state b
pure (ceilDivVal lhs rhs)
| .mulDivDown a b c => do
let lhs : Verity.Core.Uint256 := ← evalExpr fields state a
let rhs : Verity.Core.Uint256 := ← evalExpr fields state b
let denom : Verity.Core.Uint256 := ← evalExpr fields state c
pure ((lhs * rhs) / denom).val
| .mulDivUp a b c => do
let lhs : Verity.Core.Uint256 := ← evalExpr fields state a
let rhs : Verity.Core.Uint256 := ← evalExpr fields state b
let denom : Verity.Core.Uint256 := ← evalExpr fields state c
pure (((lhs * rhs) + (denom - 1)) / denom).val
| .ite cond thenVal elseVal => do
let condVal ← evalExpr fields state cond
if condVal != 0 then
evalExpr fields state thenVal
else
evalExpr fields state elseVal
| .forkIfAtLeast _ _ _ => none
| .shl shift value => do
let shiftVal ← evalExpr fields state shift
let wordVal ← evalExpr fields state value
pure (Verity.Core.Uint256.shl shiftVal wordVal).val
| .shr shift value => do
let shiftVal ← evalExpr fields state shift
let wordVal ← evalExpr fields state value
pure (Verity.Core.Uint256.shr shiftVal wordVal).val
| .slt a b => do
let lhs ← evalExpr fields state a
let rhs ← evalExpr fields state b
pure (boolWord (decide (
(Verity.Core.Int256.ofUint256 (Verity.Core.Uint256.ofNat lhs) : Int) <
(Verity.Core.Int256.ofUint256 (Verity.Core.Uint256.ofNat rhs) : Int))))
| .sgt a b => do
let lhs ← evalExpr fields state a
let rhs ← evalExpr fields state b
pure (boolWord (decide (
(Verity.Core.Int256.ofUint256 (Verity.Core.Uint256.ofNat rhs) : Int) <
(Verity.Core.Int256.ofUint256 (Verity.Core.Uint256.ofNat lhs) : Int))))
| .sdiv a b => do
let lhs ← evalExpr fields state a
let rhs ← evalExpr fields state b
pure (Verity.Core.Int256.div
(Verity.Core.Int256.ofUint256 (Verity.Core.Uint256.ofNat lhs))
(Verity.Core.Int256.ofUint256 (Verity.Core.Uint256.ofNat rhs))).toUint256.val
| .smod a b => do
let lhs ← evalExpr fields state a
let rhs ← evalExpr fields state b
pure (Verity.Core.Int256.mod
(Verity.Core.Int256.ofUint256 (Verity.Core.Uint256.ofNat lhs))
(Verity.Core.Int256.ofUint256 (Verity.Core.Uint256.ofNat rhs))).toUint256.val
| .sar a b => do
let lhs ← evalExpr fields state a
let rhs ← evalExpr fields state b
pure (Verity.Core.Int256.sar
(Verity.Core.Int256.ofUint256 (Verity.Core.Uint256.ofNat lhs))
(Verity.Core.Int256.ofUint256 (Verity.Core.Uint256.ofNat rhs))).toUint256.val
| .byte a b => do
let index ← evalExpr fields state a
let value ← evalExpr fields state b
pure (Verity.Core.Uint256.byte
(Verity.Core.Uint256.ofNat index)
(Verity.Core.Uint256.ofNat value)).val
| .signextend a b => do
let byteIdx ← evalExpr fields state a
let value ← evalExpr fields state b
pure (Verity.Core.Uint256.signextend