Repository navigation
Expand file tree
/
Copy pathSourceSemantics.lean
More file actions
6057 lines (5711 loc) · 274 KB
/
Copy pathSourceSemantics.lean
File metadata and controls
6057 lines (5711 loc) · 274 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
import Compiler.Proofs.IRGeneration.Returndata
import Verity.Core.Model.Denote
import Verity.Core.Model.PhysicalMemory
import Verity.Core.Model.DynamicAbi
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 :=
abstractKeccakMemorySlice (fun address => (memory address).val) 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)
| .uintN bits => word &&& (2 ^ bits - 1)
| .intN bits =>
(Verity.Core.Uint256.signextend
(Verity.Core.Uint256.ofNat (bits / 8 - 1))
(Verity.Core.Uint256.ofNat word)).val
| .bytesN bytes =>
word &&& ((2 ^ (8 * bytes) - 1) * 2 ^ (8 * (32 - bytes)))
| .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 physical scratch stores of the signature chunk words, starting
at word index `wordIdx`. Each store is a full 32-byte EVM `mstore`; write keys
wrap mod `2^256`, mirroring the compiled code's wrapping `add` offset
arithmetic. -/
def writeEventSignatureScratchFrom :
List Nat → Nat → Nat → Verity.ContractState → Verity.ContractState
| [], _, _, world => world
| word :: words, ptr, wordIdx, world =>
writeEventSignatureScratchFrom words ptr (wordIdx + 1)
(world.writePhysicalMemoryValue
((ptr + wordIdx * 32) % Compiler.Constants.evmModulus) (word : Verity.Core.Uint256))
def writeEventSignatureScratch (eventDef : EventDef)
(ptr : Nat) (world : Verity.ContractState) : Verity.ContractState :=
writeEventSignatureScratchFrom
((chunkBytes32 (bytesFromString (eventSignature eventDef))).map wordFromBytes)
ptr 0 world
def writeUnindexedEventScratchFrom :
List EventParam → List Nat → Nat → Nat → Verity.ContractState →
Option Verity.ContractState
| [], [], _, _, world => some world
| param :: params, value :: values, ptr, wordIdx, world =>
let normalized := normalizeEventValue param.ty value
let next :=
if param.kind == EventParamKind.unindexed then
world.writePhysicalMemoryValue
((ptr + wordIdx * 32) % Compiler.Constants.evmModulus)
(normalized : Verity.Core.Uint256)
else
world
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) (world : Verity.ContractState) : Option Verity.ContractState :=
writeUnindexedEventScratchFrom params values ptr 0 world
theorem exists_writeUnindexedEventScratch_of_length :
∀ {params : List EventParam} {values : List Nat}
{ptr wordIdx : Nat} {world : Verity.ContractState},
values.length = params.length →
∃ world',
writeUnindexedEventScratchFrom params values ptr wordIdx world = some world'
| [], [], _, _, world, _ => by
exact ⟨world, rfl⟩
| [], _ :: _, _, _, _, hlen => by
simp at hlen
| _ :: _, [], _, _, _, hlen => by
simp at hlen
| param :: params, value :: values, ptr, wordIdx, world, 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)
(world := world.writePhysicalMemoryValue
((ptr + wordIdx * 32) % Compiler.Constants.evmModulus)
(normalizeEventValue param.ty value : Verity.Core.Uint256))
htail with
⟨world', hworld⟩
refine ⟨world', ?_⟩
simpa [writeUnindexedEventScratchFrom, hkind] using hworld
· rcases exists_writeUnindexedEventScratch_of_length
(params := params) (values := values) (ptr := ptr)
(wordIdx := wordIdx)
(world := world)
htail with
⟨world', hworld⟩
refine ⟨world', ?_⟩
simpa [writeUnindexedEventScratchFrom, hkind] using hworld
theorem exists_writeUnindexedEventScratch_of_length_zero
{params : List EventParam} {values : List Nat}
{ptr : Nat} {world : Verity.ContractState}
(hlen : values.length = params.length) :
∃ world', writeUnindexedEventScratch params values ptr world = some world' := by
simpa [writeUnindexedEventScratch] using
(exists_writeUnindexedEventScratch_of_length
(params := params) (values := values) (ptr := ptr) (wordIdx := 0)
(world := world) hlen)
/-- The world after the compiled scalar `emit` scratch stores: the signature
chunk words, then the unindexed heads, written physically from the free memory
pointer. -/
def eventScratchWorldAfterEmit? (events : List EventDef)
(eventName : String) (values : List Nat)
(world : Verity.ContractState) : Option Verity.ContractState :=
match events.find? (·.name == eventName) with
| none => some world
| some eventDef =>
if eventDefScalarProofSupported eventDef then
let ptr := (world.memory Compiler.Constants.freeMemoryPointer).val
writeUnindexedEventScratch eventDef.params values ptr
(writeEventSignatureScratch eventDef ptr world)
else
some world
theorem exists_eventScratchWorldAfterEmit?_of_supported_length
{events : List EventDef}
{eventName : String}
{exprArgs : List Expr}
{values : List Nat}
{world : Verity.ContractState}
(hsupport : eventEmissionProofSupported events eventName exprArgs = true)
(hlen : values.length = exprArgs.length) :
∃ world',
eventScratchWorldAfterEmit? events eventName values world = some world' := by
rcases exists_eventDef_of_eventEmissionProofSupported hsupport with
⟨eventDef, hfind, hscalar, hargsLen⟩
have hvalueLen : values.length = eventDef.params.length := by
rw [hlen, hargsLen]
unfold eventScratchWorldAfterEmit?
simp [hfind, hscalar]
exact exists_writeUnindexedEventScratch_of_length_zero
(params := eventDef.params) (values := values)
(ptr := (world.memory Compiler.Constants.freeMemoryPointer).val)
(world := writeEventSignatureScratch eventDef
(world.memory Compiler.Constants.freeMemoryPointer).val world)
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 findResolvedFieldAtStorageSlot (fields : List Field) (isTransient : Bool)
(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 field.isTransient == isTransient &&
(wordNormalize resolvedSlot = wordNormalize slot ||
(field.aliasSlots.map wordNormalize).contains (wordNormalize slot)) then
some field
else
go rest (idx + 1)
go fields 0
def findResolvedFieldAtSlot (fields : List Field) (slot : Nat) : Option Field :=
findResolvedFieldAtStorageSlot fields false slot
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`. -/
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.writePhysicalSlots targets word
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.writeSlot target word).writeAddrSlot target addr
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.writePhysicalSlots targets word).writeAddrSlots targets addr
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.writeAddrSlots targets addr
def fieldIsTransient (fields : List Field) (name : String) : Bool :=
match findFieldWithResolvedSlot fields name with
| some (field, _) => field.isTransient
| none => false
def readFieldWord (world : Verity.ContractState) (field : Field) (slot : Nat) :
Verity.Core.Uint256 :=
if field.isTransient then
world.transientStorage (wordNormalize slot)
else
world.storage (wordNormalize slot)
def writeTransientTargets (world : Verity.ContractState) (targets : List Nat) (value : Nat) :
Verity.ContractState :=
let word : Verity.Core.Uint256 := value
let targets := targets.map wordNormalize
world.writeTransientSlots targets word
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 writeUintFieldSlots (fields : List Field) (fieldName : String)
(world : Verity.ContractState) (slots : List Nat) (value : Nat) :
Verity.ContractState :=
match findFieldWithResolvedSlot fields fieldName with
| some (field, _) =>
match field.packedBits with
| some packed =>
let targets := slots.map wordNormalize
if field.isTransient then
world.modifyTransientSlots targets
(fun current => packedWordWrite current.val value packed)
else
world.modifyPhysicalSlots targets
(fun current => packedWordWrite current.val value packed)
| none =>
if field.isTransient then writeTransientTargets world slots value
else writeUintSlots world slots value
| none => writeUintSlots world slots value
def writeStorageWordFieldSlots (fields : List Field) (fieldName : String)
(world : Verity.ContractState) (slots : List Nat) (wordOffset value : Nat) :
Verity.ContractState :=
if fieldIsTransient fields fieldName then
writeTransientTargets world (slots.map (fun slot => slot + wordOffset)) value
else
writeStorageWordSlots world slots wordOffset value
def writeAddressFieldSlots (fields : List Field) (fieldName : String)
(world : Verity.ContractState) (slots : List Nat) (value : Nat) :
Verity.ContractState :=
if fieldIsTransient fields fieldName then
writeTransientTargets world slots (Verity.wordToAddress (value : Verity.Core.Uint256)).val
else
writeAddressSlots world slots value
def writeMappingTargets (fields : List Field) (fieldName : String)
(world : Verity.ContractState) (targets : List Nat) (value : Nat) :
Verity.ContractState :=
if fieldIsTransient fields fieldName then
writeTransientTargets world targets value
else
world.writePhysicalSlots (targets.map wordNormalize) (value : Verity.Core.Uint256)
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.withStorageChannel (fun _ s =>
(Compiler.Proofs.IRGeneration.IRStorageWord.toNat
(storage (Compiler.Proofs.IRGeneration.IRStorageSlot.ofNat s)) : Verity.Core.Uint256))).writeMap
slot keyAddr word
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.writePhysicalSlots targets word
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.writePhysicalSlots targets word
def readFixedUint128ArrayElement
(world : Verity.ContractState) (slot size index : Nat) : Option Nat :=
if index < size then
let word := (world.storage (wordNormalize (slot + index / 2))).val
some (Verity.Core.Uint256.and
(Verity.Core.Uint256.shr ((index % 2) * 128) word)
((2 ^ 128 - 1 : Nat))).val
else
none
def writeFixedUint128ArrayElementSlots
(world : Verity.ContractState) (slots : List Nat) (size index value : Nat) :
Option Verity.ContractState :=
if index < size then
let offset := (index % 2) * 128
let packed : PackedBits := { offset := offset, width := 128 }
let targets := slots.map (fun slot => wordNormalize (slot + index / 2))
some (world.modifyPhysicalSlots targets (fun current => packedWordWrite current.val value packed))
else
none
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.modifyPhysicalSlots targets (fun current => packedWordWrite current.val value packed)
def writeAddressKeyedMappingPackedWordFieldSlots
(fields : List Field) (fieldName : String)
(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))
if fieldIsTransient fields fieldName then
world.modifyTransientSlots targets (fun current => packedWordWrite current.val value packed)
else
writeAddressKeyedMappingPackedWordSlots world slots key wordOffset packed value
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.modifyPhysicalSlots targets (fun current => packedWordWrite current.val value packed)
def writeAddressKeyedMapping2PackedWordFieldSlots
(fields : List Field) (fieldName : String)
(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))
if fieldIsTransient fields fieldName then
world.modifyTransientSlots targets (fun current => packedWordWrite current.val value packed)
else
writeAddressKeyedMapping2PackedWordSlots world slots key1 key2 wordOffset packed value
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.withStorageChannel (fun _ s =>
(Compiler.Proofs.IRGeneration.IRStorageWord.toNat
(storage (Compiler.Proofs.IRGeneration.IRStorageSlot.ofNat s)) : Verity.Core.Uint256))).writeMapUint
slot keyWord word
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.withStorageChannel (fun _ s =>
(Compiler.Proofs.IRGeneration.IRStorageWord.toNat
(storage (Compiler.Proofs.IRGeneration.IRStorageSlot.ofNat s)) : Verity.Core.Uint256))).writeMap2
slot key1Addr key2Addr word
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.writePhysicalSlots targets word
def writeAddressKeyedMappingWordFieldSlots
(fields : List Field) (fieldName : String)
(world : Verity.ContractState) (slots : List Nat) (key wordOffset value : Nat) :
Verity.ContractState :=
let targets :=
slots.map (fun slot =>
wordNormalize (Compiler.Proofs.abstractMappingSlot slot key + wordOffset))
writeMappingTargets fields fieldName world targets value
def writeAddressKeyedMappingFieldSlots
(fields : List Field) (fieldName : String)
(world : Verity.ContractState) (slots : List Nat) (key value : Nat) :
Verity.ContractState :=
if fieldIsTransient fields fieldName then
let targets :=
slots.map (fun slot =>
wordNormalize (Compiler.Proofs.abstractMappingSlot slot key))
writeTransientTargets world targets value
else
writeAddressKeyedMappingSlots world slots key value
def writeAddressKeyedMappingChainFieldSlots
(fields : List Field) (fieldName : String)
(world : Verity.ContractState) (slots keys : List Nat) (value : Nat) :
Verity.ContractState :=
if fieldIsTransient fields fieldName then
let targets :=
slots.map (fun slot =>
wordNormalize (keys.foldl Compiler.Proofs.abstractMappingSlot slot))
writeTransientTargets world targets value
else
writeAddressKeyedMappingChainSlots world slots keys value
def writeAddressKeyedMapping2WordFieldSlots
(fields : List Field) (fieldName : String)
(world : Verity.ContractState) (slots : List Nat) (key1 key2 wordOffset value : Nat) :
Verity.ContractState :=
let targets := slots.map (fun slot =>
wordNormalize
(Compiler.Proofs.abstractMappingSlot
(Compiler.Proofs.abstractMappingSlot slot key1)
key2 + wordOffset))
writeMappingTargets fields fieldName world targets value
def writeUintKeyedMappingFieldSlots
(fields : List Field) (fieldName : String)
(world : Verity.ContractState) (slots : List Nat) (key value : Nat) :
Verity.ContractState :=
if fieldIsTransient fields fieldName then
let targets :=
slots.map (fun slot =>
wordNormalize (Compiler.Proofs.abstractMappingSlot slot key))
writeTransientTargets world targets value
else
writeUintKeyedMappingSlots world slots key value
def writeAddressKeyedMapping2FieldSlots
(fields : List Field) (fieldName : String)
(world : Verity.ContractState) (slots : List Nat) (key1 key2 value : Nat) :
Verity.ContractState :=
if fieldIsTransient fields fieldName then
let targets := slots.map (fun slot =>
wordNormalize
(Compiler.Proofs.abstractMappingSlot
(Compiler.Proofs.abstractMappingSlot slot key1)
key2))
writeTransientTargets world targets value
else
writeAddressKeyedMapping2Slots world slots key1 key2 value
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))
| .uintN bits => some (word &&& (2 ^ bits - 1))
| .intN bits => some
(Verity.Core.Uint256.signextend
(Verity.Core.Uint256.ofNat (bits / 8 - 1))
(Verity.Core.Uint256.ofNat word)).val
| .bytesN bytes => some
(word &&& ((2 ^ (8 * bytes) - 1) * 2 ^ (8 * (32 - bytes))))
| .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 bindValues (bindings : List (String × Nat)) : List String → List Nat → List (String × Nat)
| [], _ => bindings
| _ :: _, [] => bindings
| name :: names, value :: values => bindValues (bindValue bindings name value) names values
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
private theorem findEntry_filter_ne_eq_findEntry
(entries : List (String × Nat))
(blockedName queryName : String)
(hNe : queryName ≠ blockedName) :
List.find? (fun entry => entry.1 == queryName)
(entries.filter (fun entry => entry.1 != blockedName)) =
List.find? (fun entry => entry.1 == queryName) entries := by
induction entries with
| nil =>
simp
| cons entry rest ih =>
by_cases hBlocked : entry.1 = blockedName
· subst hBlocked
have hHeadNe : entry.1 ≠ queryName := by
intro hHeadEq
apply hNe
simp [hHeadEq]
simp [hHeadNe, ih]
· by_cases hQuery : entry.1 = queryName
· subst hQuery
simp [hBlocked]
· simp [hBlocked, hQuery, ih]
theorem lookupBinding?_bindValue_ne
(bindings : List (String × Nat))
(boundName queryName : String)
(value : Nat)
(hNe : queryName ≠ boundName) :
lookupBinding?
(bindValue bindings boundName value)
queryName =
lookupBinding? bindings queryName := by
have hNe' : boundName ≠ queryName := by
intro hEq
apply hNe
simp [hEq]
calc
lookupBinding?
(bindValue bindings boundName value)
queryName
=
Option.map Prod.snd
(List.find? (fun entry => entry.1 == queryName)
((boundName, value) :: List.filter (fun entry => entry.1 != boundName) bindings)) := by
rfl
_ = Option.map Prod.snd
(List.find? (fun entry => entry.1 == queryName)
(List.filter (fun entry => entry.1 != boundName) bindings)) := by
simp [hNe']
_ = Option.map Prod.snd
(List.find? (fun entry => entry.1 == queryName) bindings) := by
rw [findEntry_filter_ne_eq_findEntry bindings boundName queryName hNe]
_ = lookupBinding? bindings queryName := by
rfl
theorem lookupBinding?_bindValue_exists
(bindings : List (String × Nat))
(boundName queryName : String)
(value : Nat)
(hexists : ∃ found, lookupBinding? bindings queryName = some found) :
∃ found, lookupBinding? (bindValue bindings boundName value) queryName = some found := by
by_cases hEq : queryName = boundName
· subst hEq
exact ⟨value, by simp [lookupBinding?, bindValue]⟩
· rcases hexists with ⟨found, hfound⟩
exact ⟨found, by rw [lookupBinding?_bindValue_ne bindings boundName queryName value hEq, hfound]⟩
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 ExternalCallOutcome where
succeeded : Bool
returnValues : List Nat := []
postCallWorld : Option Verity.ContractState := none
instance : Inhabited ExternalCallOutcome := ⟨⟨false, [], none⟩⟩
/-- Install the EIP-211 returndata buffer left by a call receipt. The callee's
return data replaces the buffer wholesale on both the success and the
failure path, which is what lets the compiled `returndatacopy(0, 0,
returndatasize())` idiom bubble a callee revert reason. -/
def returndataAfterCall (outcome : ExternalCallOutcome)
(world : Verity.ContractState) : Verity.ContractState :=
{ world with returndata := outcome.returnValues.map wordNormalize }
@[simp] theorem returndataAfterCall_returndata
(outcome : ExternalCallOutcome) (world : Verity.ContractState) :
(returndataAfterCall outcome world).returndata
= outcome.returnValues.map wordNormalize := rfl
structure RuntimeState where
world : Verity.ContractState
immutable : String → Verity.Core.Uint256 := fun _ => 0
bindings : List (String × Nat)
selector : Nat := 0
externalCallOracle : Nat → ExternalCallOutcome := fun _ => ⟨false, [], none⟩
externalCallIndex : Nat := 0
/-- Words produced by `Stmt.returnValues`, in source order.
Mirrors `DenoteState.observedReturnWords`. Proof semantics observes the
words, not a Solidity panic payload: `Stmt.panic` remains a revert. -/
observedReturnWords : Option (List Nat) := none
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
def msbIndex (bitmap : Nat) : Nat :=
if bitmap = 0 then 0 else Nat.log2 bitmap
def clearMsb (bitmap : Nat) : Nat :=
let idx := msbIndex bitmap
if bitmap = 0 then 0 else bitmap - 2 ^ idx
def execForEachSetBitLoop
(varName : String)
(runBody : RuntimeState → StmtResult) :
Nat → RuntimeState → Nat → StmtResult
| 0, state, _ => .continue state
| fuel + 1, state, bitmap =>
if bitmap = 0 then
.continue state
else
let idx := msbIndex bitmap
let loopState :=
{ state with bindings := bindValue state.bindings varName (wordNormalize idx) }
match runBody loopState with
| .continue next => execForEachSetBitLoop varName runBody fuel next (clearMsb bitmap)
| .stop next => .stop next
| .return value next => .return value next
| .revert => .revert
@[simp] theorem execForEachSetBitLoop_zero
(varName : String)
(runBody : RuntimeState → StmtResult)
(state : RuntimeState)
(bitmap : Nat) :
execForEachSetBitLoop varName runBody 0 state bitmap = .continue state := rfl
theorem execForEachSetBitLoop_succ
(varName : String)
(runBody : RuntimeState → StmtResult)
(fuel : Nat)
(state : RuntimeState)
(bitmap : Nat) :
execForEachSetBitLoop varName runBody (fuel + 1) state bitmap =
if bitmap = 0 then
.continue state
else
let idx := msbIndex bitmap
let loopState :=
{ state with bindings := bindValue state.bindings varName (wordNormalize idx) }
match runBody loopState with
| .continue next => execForEachSetBitLoop varName runBody fuel next (clearMsb bitmap)
| .stop next => .stop next
| .return value next => .return value next
| .revert => .revert := rfl
@[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) } =