-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathTypes.lean
More file actions
1546 lines (1401 loc) · 66.8 KB
/
Types.lean
File metadata and controls
1546 lines (1401 loc) · 66.8 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
/-
Compiler.CompilationModel: Declarative Compilation Model DSL
This module defines a declarative way to model contracts for compilation,
eliminating manual IR writing while keeping the system simple and maintainable.
Philosophy:
- Contracts specify their structure declaratively
- Compiler generates IR automatically from the spec
- Reduces boilerplate and eliminates manual slot/selector management
Features:
- Storage fields with automatic slot assignment (uint256, address, mapping)
- Flexible mapping types: Address→Uint256, Uint256→Uint256, nested mappings (#154)
- Functions with automatic selector computation
- Guards and access control patterns
- If/else branching and bounded loops (#179)
- Array/bytes parameter types and dynamic calldata (#180)
- Internal function composition (#181)
- Event emission (#153)
- Verified external library integration (#184)
-/
import Compiler.Constants
import Compiler.ECM
import Compiler.IR
import Compiler.ProofStatus
import Compiler.Yul.Ast
import Compiler.Identifier
import Verity.Core.Intrinsics
namespace Compiler.CompilationModel
export Compiler.Constants (errorStringSelectorWord addressMask selectorShift freeMemoryPointer)
open Compiler
open Compiler.Yul
def builtinExpName : String := "__verity_builtin_exp"
/-!
## Compilation Model DSL
Instead of manually writing IR, contracts provide a high-level model:
- Storage fields with automatic slot assignment
- Functions with automatic selector computation
- Guards and access control patterns
- Control flow: if/else branching, bounded loops
- Array parameters and dynamic calldata
- Internal function calls for modular composition
- Event emission for standards compliance
-/
/-!
### Mapping Key Types (#154)
Support flexible mapping types: single-key, double-key (nested), and uint256 keys.
-/
inductive MappingKeyType
| address -- mapping(address => ...)
| uint256 -- mapping(uint256 => ...)
| bytes32 -- mapping(bytes32 => ...)
deriving Repr, BEq
inductive MappingType
| simple (keyType : MappingKeyType) -- mapping(K => uint256)
| nested (outerKey : MappingKeyType) (innerKey : MappingKeyType) -- mapping(K1 => mapping(K2 => uint256))
| chain (keyTypes : List MappingKeyType) -- mapping(K1 => ... => mapping(Kn => uint256))
deriving Repr, BEq
structure PackedBits where
/-- Least-significant bit offset within the 256-bit storage word. -/
offset : Nat
/-- Bit width of this packed subfield. -/
width : Nat
deriving Repr, BEq
inductive StructMemberType
| uint256
| uint16
| address
| bool
| bytes32
deriving Repr, BEq
/-- A named member within a struct-valued mapping.
Each member occupies a specific word within the struct's storage region,
and may optionally be packed into a subregion of that word. -/
structure StructMember where
/-- The member name (used in `Expr.structMember` / `Stmt.setStructMember`). -/
name : String
/-- Solidity surface type of the packed/full-word member. -/
ty : StructMemberType := StructMemberType.uint256
/-- Zero-based word offset from the struct's base slot. -/
wordOffset : Nat
/-- Optional packed subfield within the word. When `none`, the member occupies
the full 256-bit word. -/
packed : Option PackedBits := none
deriving Repr, BEq
inductive StorageArrayElemType
| uint256
| address
| bool
| uint8
| bytes32
deriving Repr, BEq
def storageArrayElemUsesOneStorageWord : StorageArrayElemType → Bool
| .uint256 | .address | .bool | .bytes32 => true
| .uint8 => false
inductive FieldType
| uint256
| address
/-- Storage-backed tagged union: tag at the canonical slot followed by
`maxFields` payload slots. -/
| adt (name : String) (maxFields : Nat)
| dynamicArray (elemType : StorageArrayElemType)
| mappingTyped (mt : MappingType) -- Flexible mapping types (#154)
/-- A mapping whose value is a multi-word struct with named members.
`mappingStruct keyType members` defines `mapping(K => Struct)` where
`Struct` spans `members.map (·.wordOffset) |>.maximum + 1` words.
Access members via `Expr.structMember` / `Stmt.setStructMember`. -/
| mappingStruct (keyType : MappingKeyType) (members : List StructMember)
/-- A nested mapping whose inner value is a multi-word struct with named members.
`mappingStruct2 outerKey innerKey members` defines
`mapping(K1 => mapping(K2 => Struct))`.
Access members via `Expr.structMember2` / `Stmt.setStructMember2`. -/
| mappingStruct2 (outerKey : MappingKeyType) (innerKey : MappingKeyType) (members : List StructMember)
deriving Repr, BEq
structure Field where
name : String
ty : FieldType
/-- Optional explicit storage slot override.
When omitted, the slot defaults to declaration order (legacy behavior). -/
slot : Option Nat := none
/-- Optional packed subfield placement within the storage word.
When present, reads/writes are masked and shifted to this bit range. -/
packedBits : Option PackedBits := none
/-- Optional compatibility mirror-write slots.
Writes to this field also write the same value to each alias slot. -/
aliasSlots : List Nat := []
deriving Repr
structure ReservedSlotRange where
/-- Inclusive start slot of a reserved storage interval. -/
start : Nat
/-- Inclusive end slot of a reserved storage interval. -/
end_ : Nat
deriving Repr, BEq
structure SlotAliasRange where
/-- Inclusive start slot for canonical source slots. -/
sourceStart : Nat
/-- Inclusive end slot for canonical source slots. -/
sourceEnd : Nat
/-- Alias slot corresponding to sourceStart (sourceStart + i maps to targetStart + i). -/
targetStart : Nat
deriving Repr, BEq
/-!
### Parameter Types (#180)
Extended parameter types supporting arrays, bytes, and bytes32.
-/
inductive ParamType
| uint256
| int256
| uint8
| uint16
| address
| bool -- Solidity bool (ABI-encoded as 32-byte 0/1)
| bytes32 -- Fixed 32-byte value
| string -- Dynamic UTF-8 string (ABI-compatible with bytes)
| tuple (elemTypes : List ParamType) -- ABI tuple
| array (elemType : ParamType) -- Dynamic array: uint256[], address[]
| fixedArray (elemType : ParamType) (size : Nat) -- Fixed array: uint256[3]
| bytes -- Dynamic bytes
| adt (name : String) (maxFields : Nat) -- User-defined ADT; maxFields = max variant field count (#1727 Steps 5b/5e)
| newtypeOf (name : String) (baseType : ParamType) -- Semantic newtype; erased to baseType at Yul level (zero overhead) (#1727 Steps 3b/3c)
deriving Repr, BEq
structure Param where
name : String
ty : ParamType
deriving Repr, BEq
-- Convert to IR types
def ParamType.toIRType : ParamType → IRType
| uint256 => IRType.uint256
| int256 => IRType.uint256
| uint8 => IRType.uint256
| uint16 => IRType.uint256
| address => IRType.address
| bool => IRType.uint256
| bytes32 => IRType.uint256 -- bytes32 is a 256-bit value
| string => IRType.uint256
| tuple _ => IRType.uint256 -- Tuples are represented as ABI offsets for now
| array _ => IRType.uint256 -- Arrays are represented as calldata offsets
| fixedArray _ _ => IRType.uint256
| bytes => IRType.uint256
| adt _ _ => IRType.uint256 -- ADTs are represented as storage offsets
| newtypeOf _ baseType => baseType.toIRType -- Erased to base type
def Param.toIRParam (p : Param) : IRParam :=
{ name := p.name, ty := p.ty.toIRType }
/-!
### Event Definitions (#153)
Events for ERC20/ERC721 compliance and general logging.
-/
inductive EventParamKind
| indexed -- Goes into LOG topic (max 3 indexed params per event)
| unindexed -- Goes into LOG data
deriving Repr, BEq
structure EventParam where
name : String
ty : ParamType
kind : EventParamKind
deriving Repr
structure EventDef where
name : String
params : List EventParam
deriving Repr
structure ErrorDef where
name : String
params : List ParamType
deriving Repr
/-!
### External Function Declarations (#184)
Verified external library integration with axiom documentation.
-/
inductive ForeignLinkMode where
/-- The dependency remains an ABI boundary and is called through an adapter
that preserves Solidity-compatible returndata and revert behavior. -/
| external
/-- The dependency is provided as Yul/EVM object code and linked into the
generated artifact at compile time. -/
| objectLinked
/-- The dependency is a small pure helper whose body may be inlined by the
frontend/backend. The compiler still treats it as a declared foreign
surface for audit visibility. -/
| inline
/-- The dependency is owned by the compiler runtime rather than by a protocol
deployment boundary. -/
| compilerRuntime
deriving Repr, BEq
def ForeignLinkMode.toJsonString : ForeignLinkMode → String
| .external => "external"
| .objectLinked => "objectLinked"
| .inline => "inline"
| .compilerRuntime => "compilerRuntime"
def ForeignLinkMode.humanName : ForeignLinkMode → String
| .external => "external ABI boundary"
| .objectLinked => "object-linked Yul"
| .inline => "inline helper"
| .compilerRuntime => "compiler runtime"
structure ExternalFunction where
name : String
params : List ParamType
returnType : Option ParamType := none -- backward compatibility
returns : List ParamType := [] -- empty for void functions
/-- Proof-accounting status for this foreign surface.
`proved` means there is an end-to-end refinement theorem,
`assumed` means downstream proofs must quantify over the spec explicitly,
and `unchecked` means the function is available for compilation/testing only. -/
proofStatus : Compiler.ProofStatus := .assumed
/-- Names of axioms assumed about this function.
The actual Lean propositions are stated separately;
these names are for documentation and audit purposes. -/
axiomNames : List String
/-- How the foreign surface is linked into the generated artifact. Historical
`linked_externals` declarations default to object-linked Yul because the
compiler emits a Yul function call and the driver injects matching helper
definitions from `--link` libraries. -/
linkMode : ForeignLinkMode := .objectLinked
deriving Repr
structure YulState where
vars : List (String × Nat) := []
memory : Nat → Nat := fun _ => 0
storage : Nat → Nat := fun _ => 0
transientStorage : Nat → Nat := fun _ => 0
returndata : List Nat := []
reverted : Bool := false
structure FrameSpec where
localReads : List String := []
localWrites : List String := []
memoryReads : List String := []
memoryWrites : List String := []
storageReads : List String := []
storageWrites : List String := []
transientReads : List String := []
transientWrites : List String := []
deriving Repr, BEq, Inhabited
/-- Structured refinement contract for localized unsafe Yul boundaries.
The predicates give proof code a real target while `summary` remains the
stable human-readable report text. -/
structure UnsafeYulContract where
name : String
summary : String
pre : YulState → Prop
post : YulState → YulState → Prop
frame : FrameSpec := {}
instance : Repr UnsafeYulContract where
reprPrec contract prec :=
reprPrec (contract.name, contract.summary, contract.frame) prec
namespace UnsafeYulContract
def rawRevert (name summary : String) : UnsafeYulContract :=
{ name := name
summary := summary
pre := fun _ => True
post := fun _ after => after.reverted = true
frame := { memoryReads := ["revertPayload"] } }
end UnsafeYulContract
structure LocalObligation where
name : String
/-- User-supplied summary of the local refinement contract that must hold
for this localized unsafe/assembly boundary. -/
obligation : String
/-- Proof-accounting status for this local boundary. -/
proofStatus : Compiler.ProofStatus := .assumed
deriving Repr
/-- Coarse statement termination classification used by generic statement
metadata. `mayTerminate` covers handwritten/raw Yul fragments unless a
caller supplies a more precise contract. -/
inductive StmtTermination where
| fallsThrough
| alwaysTerminates
| mayTerminate
deriving Repr, BEq
/-- Finer control-flow summary for statements and statement lists.
This intentionally coexists with `StmtTermination` while callers migrate:
the old field answers the coarse "can execution continue?" question, while
this summary records which terminal behaviors may occur. -/
structure ControlFlowSummary where
mayFallThrough : Bool := true
mayRevert : Bool := false
mayReturn : Bool := false
mayStop : Bool := false
deriving Repr, BEq, Inhabited
namespace ControlFlowSummary
def fallsThrough : ControlFlowSummary := {}
/-- Empty control-flow set, used as the identity when unioning alternatives. -/
def noPaths : ControlFlowSummary :=
{ mayFallThrough := false, mayRevert := false, mayReturn := false, mayStop := false }
def mayReverting : ControlFlowSummary :=
{ mayFallThrough := true, mayRevert := true }
def reverts : ControlFlowSummary :=
{ mayFallThrough := false, mayRevert := true }
def returns : ControlFlowSummary :=
{ mayFallThrough := false, mayReturn := true }
def stops : ControlFlowSummary :=
{ mayFallThrough := false, mayStop := true }
def unknown : ControlFlowSummary :=
{ mayFallThrough := true, mayRevert := true, mayReturn := true, mayStop := true }
def union (a b : ControlFlowSummary) : ControlFlowSummary :=
{ mayFallThrough := a.mayFallThrough || b.mayFallThrough
mayRevert := a.mayRevert || b.mayRevert
mayReturn := a.mayReturn || b.mayReturn
mayStop := a.mayStop || b.mayStop }
/-- Sequential composition: `b` is reachable only along fall-through paths of `a`. -/
def seq (a b : ControlFlowSummary) : ControlFlowSummary :=
{ mayFallThrough := a.mayFallThrough && b.mayFallThrough
mayRevert := a.mayRevert || (a.mayFallThrough && b.mayRevert)
mayReturn := a.mayReturn || (a.mayFallThrough && b.mayReturn)
mayStop := a.mayStop || (a.mayFallThrough && b.mayStop) }
/-- True when every path terminates specifically through a Solidity-style
return or revert. A raw `stop` also halts execution, but it does not produce the
return data required by functions that declare return values. -/
def alwaysReturnsOrReverts (cf : ControlFlowSummary) : Bool :=
!cf.mayFallThrough && !cf.mayStop && (cf.mayReturn || cf.mayRevert)
def fromTermination : StmtTermination → ControlFlowSummary
| .fallsThrough => fallsThrough
| .alwaysTerminates => unknown
| .mayTerminate => unknown
end ControlFlowSummary
/-- Scope effects exposed by the generic statement metadata layer. -/
structure StmtScopeEffects where
bindNames : List String := []
assignNames : List String := []
storageWrites : List String := []
deriving Repr, Inhabited
namespace StmtScopeEffects
def merge (a b : StmtScopeEffects) : StmtScopeEffects :=
{ bindNames := a.bindNames ++ b.bindNames
assignNames := a.assignNames ++ b.assignNames
storageWrites := a.storageWrites ++ b.storageWrites }
end StmtScopeEffects
mutual
/-- Conservative scan for storage-like writes inside raw Yul expressions.
Transient `tstore` is intentionally classified as a storage write for
effect validation because it mutates EVM transaction-local state. -/
partial def yulExprWritesStorage : YulExpr → Bool
| .call func args =>
func == "sstore" || func == "tstore" || yulExprListWritesStorage args
| _ => false
partial def yulExprListWritesStorage : List YulExpr → Bool
| [] => false
| expr :: rest =>
yulExprWritesStorage expr || yulExprListWritesStorage rest
end
mutual
/-- Conservative scope/effect derivation for embedded Yul AST fragments.
This is the single source of truth used by both imported-Yul construction
and unsafe-Yul validation, so generated declarations and validation checks
cannot drift apart. -/
partial def yulStmtScopeEffects : YulStmt → StmtScopeEffects
| .comment _ | .leave =>
{}
| .let_ name value =>
{ bindNames := [name]
storageWrites := if yulExprWritesStorage value then ["<raw-yul-storage-write>"] else [] }
| .letMany names value =>
{ bindNames := names
storageWrites := if yulExprWritesStorage value then ["<raw-yul-storage-write>"] else [] }
| .assign name value =>
{ assignNames := [name]
storageWrites := if yulExprWritesStorage value then ["<raw-yul-storage-write>"] else [] }
| .expr expr =>
{ storageWrites := if yulExprWritesStorage expr then ["<raw-yul-storage-write>"] else [] }
| .if_ cond body =>
let bodyEffects := yulStmtListScopeEffects body
{ bodyEffects with
storageWrites :=
(if yulExprWritesStorage cond then ["<raw-yul-storage-write>"] else []) ++
bodyEffects.storageWrites }
| .for_ init cond post body =>
let initEffects := yulStmtListScopeEffects init
let postEffects := yulStmtListScopeEffects post
let bodyEffects := yulStmtListScopeEffects body
{ bindNames := initEffects.bindNames ++ postEffects.bindNames ++ bodyEffects.bindNames
assignNames := initEffects.assignNames ++ postEffects.assignNames ++ bodyEffects.assignNames
storageWrites :=
initEffects.storageWrites ++
(if yulExprWritesStorage cond then ["<raw-yul-storage-write>"] else []) ++
postEffects.storageWrites ++ bodyEffects.storageWrites }
| .switch expr cases default =>
let casesEffects :=
cases.foldl
(fun acc (_, body) => StmtScopeEffects.merge acc (yulStmtListScopeEffects body))
({} : StmtScopeEffects)
let defaultEffects :=
match default with
| none => ({} : StmtScopeEffects)
| some body => yulStmtListScopeEffects body
{ bindNames := casesEffects.bindNames ++ defaultEffects.bindNames
assignNames := casesEffects.assignNames ++ defaultEffects.assignNames
storageWrites :=
(if yulExprWritesStorage expr then ["<raw-yul-storage-write>"] else []) ++
casesEffects.storageWrites ++ defaultEffects.storageWrites }
| .block stmts =>
yulStmtListScopeEffects stmts
| .funcDef name _params _rets body =>
let bodyEffects := yulStmtListScopeEffects body
{ bindNames := [name]
assignNames := []
storageWrites := bodyEffects.storageWrites }
partial def yulStmtListScopeEffects : List YulStmt → StmtScopeEffects
| [] => {}
| stmt :: rest =>
StmtScopeEffects.merge (yulStmtScopeEffects stmt) (yulStmtListScopeEffects rest)
end
mutual
/-- Conservative scan for EVM external-call builtins inside raw Yul ASTs.
Unsafe-Yul fragments also declare mechanics metadata, but validation should
not miss a handwritten `call`/`staticcall`/`delegatecall` if the metadata is
under-declared. -/
partial def yulExprContainsExternalCall : YulExpr → Bool
| .call func args =>
func == "call" ||
func == "staticcall" ||
func == "delegatecall" ||
yulExprListContainsExternalCall args
| _ => false
partial def yulExprListContainsExternalCall : List YulExpr → Bool
| [] => false
| expr :: rest =>
yulExprContainsExternalCall expr || yulExprListContainsExternalCall rest
partial def yulStmtContainsExternalCall : YulStmt → Bool
| .comment _ | .leave =>
false
| .let_ _ value | .letMany _ value | .assign _ value | .expr value =>
yulExprContainsExternalCall value
| .if_ cond body =>
yulExprContainsExternalCall cond || yulStmtListContainsExternalCall body
| .for_ init cond post body =>
yulStmtListContainsExternalCall init ||
yulExprContainsExternalCall cond ||
yulStmtListContainsExternalCall post ||
yulStmtListContainsExternalCall body
| .switch discr cases default =>
yulExprContainsExternalCall discr ||
cases.any (fun (_, body) => yulStmtListContainsExternalCall body) ||
match default with
| none => false
| some body => yulStmtListContainsExternalCall body
| .block stmts =>
yulStmtListContainsExternalCall stmts
| .funcDef _ _ _ body =>
yulStmtListContainsExternalCall body
partial def yulStmtListContainsExternalCall : List YulStmt → Bool
| [] => false
| stmt :: rest =>
yulStmtContainsExternalCall stmt || yulStmtListContainsExternalCall rest
end
/-- Typed trust-report mechanics emitted by low-level statements and raw Yul fragments.
JSON and human-readable reports still render these through `toReportString`,
preserving the existing public report format while keeping the model boundary
from depending on ad-hoc string literals. -/
inductive LowLevelMechanic where
| call
| staticcall
| delegatecall
| returndataSize
| returndataCopy
| revertReturndata
| rawRevert
| returndataOptionalBoolAt
| blobbasefee
| mload
| mstore
| calldataload
| calldatacopy
| codecopy
| extcodesize
| extcodecopy
| create2
| tload
| tstore
| rawLog
| contractAddress
| chainid
| selfBalance
| blockNumber
| storageWrite
deriving Repr, BEq, Inhabited
namespace LowLevelMechanic
def toReportString : LowLevelMechanic → String
| .call => "call"
| .staticcall => "staticcall"
| .delegatecall => "delegatecall"
| .returndataSize => "returndataSize"
| .returndataCopy => "returndataCopy"
| .revertReturndata => "revertReturndata"
| .rawRevert => "rawRevert"
| .returndataOptionalBoolAt => "returndataOptionalBoolAt"
| .blobbasefee => "blobbasefee"
| .mload => "mload"
| .mstore => "mstore"
| .calldataload => "calldataload"
| .calldatacopy => "calldatacopy"
| .codecopy => "codecopy"
| .extcodesize => "extcodesize"
| .extcodecopy => "extcodecopy"
| .create2 => "create2"
| .tload => "tload"
| .tstore => "tstore"
| .rawLog => "rawLog"
| .contractAddress => "contractAddress"
| .chainid => "chainid"
| .selfBalance => "selfBalance"
| .blockNumber => "blockNumber"
| .storageWrite => "storageWrite"
instance : ToString LowLevelMechanic where
toString := toReportString
end LowLevelMechanic
/-- Typed handwritten Yul fragment. This is intentionally not just a string:
callers provide an EVMYul AST payload plus explicit proof obligations and
trust-surface metadata at the same boundary where the raw fragment enters
the compilation model. -/
structure UnsafeYulFragment where
label : String
stmts : List YulStmt
obligations : List LocalObligation
contracts : List UnsafeYulContract := []
mechanics : List LowLevelMechanic := []
scopeEffects : StmtScopeEffects := {}
termination : StmtTermination := .mayTerminate
controlFlow : ControlFlowSummary := .unknown
deriving Repr
/-- Backwards-friendly name for explicitly trusted raw Yul fragments. -/
abbrev RawYul := UnsafeYulFragment
namespace UnsafeYulFragment
/-- Helper constructor for the single Yul `revert(offset, size)` instruction.
Prefer this through `Stmt.unsafeYul` for one-off raw instruction escapes.
Stable typed primitives such as `Stmt.mstore` and `Stmt.calldatacopy`
remain first-class statements because Verity has explicit semantics and
proof/audit surfaces for them; ad-hoc raw instructions should carry their
trust metadata at the `UnsafeYulFragment` boundary instead of growing `Stmt`.
Raw memory reverts are intentionally modeled as unsafe Yul fragments rather
than first-class `Stmt` constructors. -/
def rawRevert (offset size : YulExpr) (obligation : LocalObligation)
(label : String := obligation.name) : UnsafeYulFragment := {
label := label
stmts := [YulStmt.expr (YulExpr.call "revert" [offset, size])]
obligations := [obligation]
contracts := [UnsafeYulContract.rawRevert obligation.name obligation.obligation]
mechanics := [.rawRevert]
termination := .alwaysTerminates
controlFlow := .reverts
}
end UnsafeYulFragment
/-!
### ADT Type Definitions (#1727, Phase 5 Step 5b)
IR-level representation of user-defined algebraic data types (tagged unions).
Each variant carries a tag byte and typed fields.
-/
/-- A single variant of an ADT at the IR level.
`tag` is the 0-based index used for storage encoding. -/
structure AdtVariant where
name : String
tag : Nat
fields : List Param
deriving Repr, BEq
/-- A user-defined algebraic data type at the IR level.
Storage layout: tag byte (1 slot) + max-width fields in consecutive slots. -/
structure AdtTypeDef where
name : String
variants : List AdtVariant
deriving Repr, BEq
/-!
## Function Body DSL
DSL for expressing contract operations including control flow,
internal calls, and event emission.
-/
inductive Expr
| literal (n : Nat)
| param (name : String)
| constructorArg (index : Nat) -- Access constructor argument (loaded from bytecode)
| storage (field : String)
| storageAddr (field : String)
| mapping (field : String) (key : Expr)
| mappingWord (field : String) (key : Expr) (wordOffset : Nat) -- mappingSlot(base,key) + wordOffset
| mappingPackedWord (field : String) (key : Expr) (wordOffset : Nat) (packed : PackedBits)
| mapping2 (field : String) (key1 key2 : Expr) -- Double mapping (#154)
| mapping2Word (field : String) (key1 key2 : Expr) (wordOffset : Nat) -- Double mapping + word offset
| mappingUint (field : String) (key : Expr) -- Uint256-keyed mapping (#154)
| mappingChain (field : String) (keys : List Expr) -- Arbitrary-depth mapping read (#1570)
/-- Read a named member of a struct-valued mapping.
Resolves the member's word offset and optional packed bits at compile time.
`structMember field key memberName` compiles to the same Yul as
`mappingPackedWord field key member.wordOffset member.packed` (or
`mappingWord` when unpacked). -/
| structMember (field : String) (key : Expr) (memberName : String)
/-- Read a named member of a struct-valued double mapping.
`structMember2 field key1 key2 memberName` resolves the member's word offset
and packed bits from the field's struct definition. -/
| structMember2 (field : String) (key1 key2 : Expr) (memberName : String)
| caller
| contractAddress
| chainid
| msgValue
| selfBalance
| blockTimestamp
| blockNumber
| blobbasefee
| mload (offset : Expr)
| tload (offset : Expr)
| keccak256 (offset size : Expr)
/-- First-class low-level `call(gas, target, value, inOffset, inSize, outOffset, outSize)`.
Returns the EVM success bit (0/1). -/
| call (gas target value inOffset inSize outOffset outSize : Expr)
/-- First-class low-level `staticcall(gas, target, inOffset, inSize, outOffset, outSize)`.
Returns the EVM success bit (0/1). -/
| staticcall (gas target inOffset inSize outOffset outSize : Expr)
/-- First-class low-level `delegatecall(gas, target, inOffset, inSize, outOffset, outSize)`.
Returns the EVM success bit (0/1). -/
| delegatecall (gas target inOffset inSize outOffset outSize : Expr)
/-- Size in bytes of the current call's calldata (`calldatasize()`). -/
| calldatasize
/-- Load a 32-byte word from calldata at the given byte offset (`calldataload(offset)`). -/
| calldataload (offset : Expr)
/-- Size in bytes of returndata from the most recent external call frame. -/
| returndataSize
/-- Size in bytes of code deployed at the given address (0 for EOAs). -/
| extcodesize (addr : Expr)
/-- ERC20-style optional bool return helper:
true iff `returndatasize() == 0 || (returndatasize() == 32 && mload(outOffset) == 1)`. -/
| returndataOptionalBoolAt (outOffset : Expr)
| localVar (name : String) -- Reference to local variable
| externalCall (name : String) (args : List Expr) -- External function call (linked at compile time)
| internalCall (functionName : String) (args : List Expr) -- Internal function call (#181)
| arrayLength (name : String) -- Length of a dynamic array parameter (#180)
| arrayElement (name : String) (index : Expr) -- Checked element access of a dynamic array parameter (revert on out-of-range) (#180)
/-- Length of a memory-backed dynamic array binding, e.g. an internal helper
returning `(data_offset, length)`. -/
| memoryArrayLength (name : String)
/-- Checked element access for a memory-backed dynamic array binding with
single-word static elements. -/
| memoryArrayElement (name : String) (index : Expr)
/-- Checked word access inside a dynamic array element. `elementWords` is the
static ABI word width of one element and `wordOffset` is the word inside
that element. This supports arrays of static tuple/struct-like values. -/
| arrayElementWord (name : String) (index : Expr) (elementWords wordOffset : Nat)
/-- Checked word access inside the head of a dynamically-sized array element.
This supports arrays of tuple/struct-like values whose element contains
nested dynamic members; `wordOffset` indexes the element head after the
ABI element offset has been resolved. -/
| arrayElementDynamicWord (name : String) (index : Expr) (wordOffset : Nat)
/-- Data offset of a dynamically-sized array element. Given an array
parameter whose elements are dynamically encoded tuples/structs,
bounds-checks `index`, resolves the element offset table entry, and
returns the element head offset. This is the dynamic-tuple analogue of
forwarding `(arrayElement <param> <i>)` into an internal helper. -/
| arrayElementDynamicDataOffset (name : String) (index : Expr)
/-- Checked word access inside the head of a directly-passed struct/tuple
parameter whose ABI encoding is dynamic. `wordOffset` indexes the
parameter's head section after the outer offset pointer has been
resolved by the parameter loader. Used for `param.field` projections
where `param` is a struct that carries nested dynamic members and the
projected field is a single-word static leaf at a fixed head offset.
(verity#1832) -/
| paramDynamicHeadWord (name : String) (wordOffset : Nat)
/-- Length of a dynamic member inside a directly-passed dynamic tuple
parameter. `wordOffset` points at the member's ABI head word relative to
`{name}_data_offset`. -/
| paramDynamicMemberLength (name : String) (wordOffset : Nat)
/-- Data offset of a dynamic member inside a directly-passed dynamic tuple
parameter, returning the first element word after the member length. -/
| paramDynamicMemberDataOffset (name : String) (wordOffset : Nat)
/-- Element access into an `Array<wordLike>` dynamic member inside a
directly-passed dynamic tuple parameter. -/
| paramDynamicMemberElement (name : String) (wordOffset : Nat) (innerIndex : Expr)
/-- Base pointer for a static composite member inside a directly-passed
dynamic tuple parameter. Event lowering uses this as the start of the
projected tuple's ABI words and then encodes each static leaf. -/
| paramDynamicStaticComposite (name : String) (wordOffset : Nat)
/-- Length of a dynamic member inside a struct-array element. Given a
struct-array parameter `name` indexed at `index`, dereferences the
head pointer at `wordOffset` (relative to the element's head
section), then reads the length word at that pointer. Used for
`arrayLength (arrayElement <param> <i>).<dynamicField>` projections,
e.g. `txn.nullifierHashes.length` where `txn` is an element of a
struct-array parameter. (verity#1849, G1) -/
| arrayElementDynamicMemberLength (name : String) (index : Expr) (wordOffset : Nat)
/-- Data offset of a dynamic word-array member nested inside a
struct-array element. The returned word points at the first element
word (immediately after the member's ABI length word), matching the
`_data_offset` convention used for direct dynamic-array parameters. -/
| arrayElementDynamicMemberDataOffset (name : String) (index : Expr) (wordOffset : Nat)
/-- Element access into a dynamic word-array member nested inside a
struct-array element. Given a struct-array parameter `name` indexed
at `index`, dereferences the head pointer at `wordOffset` (relative
to the element's head section), then reads the word at offset
`32 + innerIndex*32` from that pointer after bounds-checking
`innerIndex` against the member's stored length. Used for
`arrayElement (arrayElement <param> <i>).<dynamicField> <k>`
projections, e.g. `txn.nullifierHashes[k]` where `txn` is an
element of a struct-array parameter and `nullifierHashes` is an
`Array Uint256` member. (verity#1849, G2) -/
| arrayElementDynamicMemberElement (name : String) (index : Expr) (wordOffset : Nat) (innerIndex : Expr)
| storageArrayLength (field : String) -- Read the length word of a storage dynamic array (#1571)
| storageArrayElement (field : String) (index : Expr) -- Checked element access of a storage dynamic array (#1571)
/-- Equality on direct `bytes` / `string` parameters loaded from calldata or memory.
The names refer to the dynamic parameter base names (`foo`, not `foo_offset`). -/
| dynamicBytesEq (lhsName rhsName : String)
| add (a b : Expr)
| sub (a b : Expr)
| mul (a b : Expr)
| div (a b : Expr)
| sdiv (a b : Expr)
| mod (a b : Expr)
| smod (a b : Expr)
| bitAnd (a b : Expr)
| bitOr (a b : Expr)
| bitXor (a b : Expr)
| bitNot (a : Expr)
| shl (shift value : Expr)
| shr (shift value : Expr)
| sar (shift value : Expr)
| byte (index value : Expr)
| signextend (byteIndex value : Expr)
/-- Consumer-owned opcode intrinsic. Verity lowers using the supplied generic
Yul descriptor and does not attach opcode-specific semantics. -/
| intrinsic
(name : String)
(lowering : Verity.Core.Intrinsics.YulLowering)
(minFork : Verity.Core.Intrinsics.HardFork)
(args : List Expr)
/-- Compile-time fork selection. `thenExpr` is selected when the compiler
target fork is at least `required`; otherwise `elseExpr` is selected.
The unselected branch is removed before intrinsic fork gates and Yul
lowering run, so this is suitable for explicit opcode/emulation fallback
pairs without silently weakening `min_fork` on the intrinsic itself. -/
| forkIfAtLeast
(required : Verity.Core.Intrinsics.HardFork)
(thenExpr elseExpr : Expr)
| eq (a b : Expr)
| ge (a b : Expr)
| gt (a b : Expr) -- Greater than (strict)
| sgt (a b : Expr)
| lt (a b : Expr)
| slt (a b : Expr)
| le (a b : Expr) -- Less than or equal
| logicalAnd (a b : Expr) -- Logical AND (both operands always evaluated)
| logicalOr (a b : Expr) -- Logical OR (both operands always evaluated)
| logicalNot (a : Expr) -- Logical NOT
/-- `ceilDiv(a, b)` = `a == 0 ? 0 : (a - 1) / b + 1` (overflow-safe ceiling division).
Matches Solidity's Math256.ceilDiv / OpenZeppelin.
Compiles to `mul(iszero(iszero(a)), add(div(sub(a, 1), b), 1))`. -/
| ceilDiv (a b : Expr)
/-- `mulDivDown(a, b, c)` = `a * b / c` (round toward zero).
Compiles to `div(mul(a, b), c)`. (#928) -/
| mulDivDown (a b c : Expr)
/-- `mulDivUp(a, b, c)` = `(a * b + c - 1) / c` (round away from zero).
Compiles to `div(add(mul(a, b), sub(c, 1)), c)`. (#928) -/
| mulDivUp (a b c : Expr)
/-- `mulDiv512Down(a, b, c)` = full-precision `(a * b) / c` (round toward
zero). Unlike `mulDivDown`, the intermediate product is handled at
full 512-bit precision and may exceed `2^256` as long as the final
quotient fits in `uint256`. Reverts on zero divisor or when the
quotient does not fit. Matches OpenZeppelin `Math.mulDiv` / Solmate
`FullMath.mulDiv` semantics (verity#1761). -/
| mulDiv512Down (a b c : Expr)
/-- `mulDiv512Up(a, b, c)` = full-precision `ceil(a * b / c)`. Same
semantics as `mulDiv512Down`, rounded up by 1 when the division is
not exact. Matches OpenZeppelin `Math.mulDiv(..., Rounding.Ceil)`
semantics (verity#1761). -/
| mulDiv512Up (a b c : Expr)
/-- `wMulDown(a, b)` = `a * b / WAD` (WAD = 1e18, round down).
Sugar for `mulDivDown a b (literal WAD)`. (#928) -/
| wMulDown (a b : Expr)
/-- `wDivUp(a, b)` = `(a * WAD + b - 1) / b` (WAD = 1e18, round up).
Compiles to `div(add(mul(a, WAD), sub(b, 1)), b)`. (#928) -/
| wDivUp (a b : Expr)
/-- `min(a, b)` — unsigned minimum.
Compiles to `sub(a, mul(sub(a, b), gt(a, b)))`. (#928) -/
| min (a b : Expr)
/-- `max(a, b)` — unsigned maximum.
Compiles to `add(a, mul(sub(b, a), gt(b, a)))`. (#928) -/
| max (a b : Expr)
/-- Expression-level conditional: `ite cond thenVal elseVal`.
Compiles to branchless `add(mul(iszero(iszero(cond)), thenVal), mul(iszero(cond), elseVal))`.
Both branches are eagerly evaluated; `cond` is evaluated twice.
For complex conditions with side effects, bind to a local variable first. -/
| ite (cond thenVal elseVal : Expr)
/-- Construct an ADT value: `adtConstruct adtName variantName args`.
Produces the tagged-union encoding for the given variant. (#1727 Step 5b) -/
| adtConstruct (adtName variantName : String) (args : List Expr)
/-- Read the tag byte of an ADT value: `adtTag adtName field`.
Returns the 0-based tag index. (#1727 Step 5b) -/
| adtTag (adtName field : String)
/-- Read a field from an ADT value stored in contract storage.
`storageField` names the contract storage field holding the ADT.
`fieldIndex` is the 0-based index within the variant's field list,
pre-resolved at IR construction time. (#1727 Steps 5b/5c) -/
| adtField (adtName variantName fieldName : String) (fieldIndex : Nat) (storageField : String)
deriving Repr
inductive Stmt
| letVar (name : String) (value : Expr) -- Declare local variable
| assignVar (name : String) (value : Expr) -- Reassign existing variable
| setStorage (field : String) (value : Expr)
| setStorageAddr (field : String) (value : Expr)
/-- Write a full storage word at `field.slot + wordOffset`. Intended for
migration-faithful manual packed-word writes where the source constructs
the packed word explicitly. -/
| setStorageWord (field : String) (wordOffset : Nat) (value : Expr)
| storageArrayPush (field : String) (value : Expr) -- Append to a storage dynamic array (#1571)
| storageArrayPop (field : String) -- Pop from a storage dynamic array (#1571)
| setStorageArrayElement (field : String) (index : Expr) (value : Expr) -- Indexed write (#1571)
| setMapping (field : String) (key : Expr) (value : Expr)
| setMappingWord (field : String) (key : Expr) (wordOffset : Nat) (value : Expr) -- mappingSlot(base,key)+wordOffset write
| setMappingPackedWord (field : String) (key : Expr) (wordOffset : Nat) (packed : PackedBits) (value : Expr)
| setMapping2 (field : String) (key1 key2 : Expr) (value : Expr) -- Double mapping write (#154)
| setMapping2Word (field : String) (key1 key2 : Expr) (wordOffset : Nat) (value : Expr) -- Double mapping + word offset write
| setMappingUint (field : String) (key : Expr) (value : Expr) -- Uint256-keyed mapping write (#154)
| setMappingChain (field : String) (keys : List Expr) (value : Expr) -- Arbitrary-depth mapping write (#1570)
/-- Write to a named member of a struct-valued mapping.
Resolves the member's word offset and optional packed bits at compile time.
Generates the same Yul as `setMappingPackedWord` (or `setMappingWord` when
unpacked), including alias slot mirror writes. -/
| setStructMember (field : String) (key : Expr) (memberName : String) (value : Expr)
/-- Write to a named member of a struct-valued double mapping.
`setStructMember2 field key1 key2 memberName value` resolves the member's
word offset and packed bits from the field's struct definition. -/
| setStructMember2 (field : String) (key1 key2 : Expr) (memberName : String) (value : Expr)
| require (cond : Expr) (message : String)
| requireError (cond : Expr) (errorName : String) (args : List Expr)
| revertError (errorName : String) (args : List Expr)
| return (value : Expr)
| returnValues (values : List Expr) -- ABI-encode multiple static return words
| returnArray (name : String) -- ABI-encode dynamic uint256[] parameter loaded from calldata
| returnBytes (name : String) -- ABI-encode dynamic bytes parameter loaded from calldata
| returnStorageWords (name : String) -- ABI-encode dynamic uint256[] from sload over a dynamic word-array parameter
| mstore (offset value : Expr)
| tstore (offset value : Expr)
/-- First-class `calldatacopy(destOffset, sourceOffset, size)` statement. -/
| calldatacopy (destOffset sourceOffset size : Expr)
/-- First-class `returndatacopy(destOffset, sourceOffset, size)` statement. -/
| returndataCopy (destOffset sourceOffset size : Expr)
/-- Forward current returndata as revert payload (`returndatacopy` + `revert`). -/
| revertReturndata
| stop
| ite (cond : Expr) (thenBranch : List Stmt) (elseBranch : List Stmt) -- If/else (#179)
| forEach (varName : String) (count : Expr) (body : List Stmt) -- Bounded loop (#179)
| emit (eventName : String) (args : List Expr) -- Emit event (#153)
| internalCall (functionName : String) (args : List Expr) -- Internal call as statement (#181)
| internalCallAssign (names : List String) (functionName : String) (args : List Expr)
/-- Low-level log emission: `logN(dataOffset, dataSize, topic0, …, topicN-1)`.
`topics` must contain 0–4 expressions (selects log0–log4).
The caller is responsible for prior `mstore` calls that populate the data region. (#930) -/
| rawLog (topics : List Expr) (dataOffset dataSize : Expr)
/-- Perform an external call and bind ABI-decoded return values to local variables.
Reverts with forwarded returndata on call failure or insufficient return data. -/
| externalCallBind
(resultVars : List String) -- local vars to bind return values to
(externalName : String) -- name of the external function declaration
(args : List Expr) -- call arguments
/-- Perform an external call without auto-reverting on failure.
Binds a success flag (0/1) to `successVar` and return values to `resultVars`.
The caller is responsible for checking the success flag and handling failures.
(#1727, Axis 1 Step 5f) -/
| tryExternalCallBind
(successVar : String) -- local var bound to success flag (0 = failure, 1 = success)
(resultVars : List String) -- local vars to bind return values to (only valid if success == 1)
(externalName : String) -- name of the external function declaration
(args : List Expr) -- call arguments
/-- Invoke an External Call Module with the given arguments.
This generic variant delegates validation, compilation, and state analysis
to the module's metadata and compile function. See Compiler.ECM (#964). -/
| ecm (mod : ECM.ExternalCallModule) (args : List Expr)
/-- Unsafe block: `unsafe "reason" do body`.
Marks a region where restricted operations (Step 6b) are permitted.
The reason string is preserved for trust reporting (Step 6c). -/
| unsafeBlock (reason : String) (body : List Stmt)
/-- Typed handwritten Yul fragment with localized proof obligations and
trust-surface metadata. Lowering to EVMYul AST is centralized in
`CompilationModel.unsafeYulToEVMYul`. -/
| unsafeYul (fragment : UnsafeYulFragment)
/-- Pattern match on an ADT value: `matchAdt adtName scrutinee branches`.
Each branch is `(variantName, boundVarNames, body)`.