Repository navigation
Expand file tree
/
Copy pathStorageGeneric.sol
More file actions
260 lines (237 loc) Β· 10.9 KB
/
Copy pathStorageGeneric.sol
File metadata and controls
260 lines (237 loc) Β· 10.9 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
pragma solcore;
pragma no-patterson-condition StorageType;
pragma no-bounded-variable-condition StorageType;
export {
StorageDeriving,
loadGeneric,
storeGeneric
};
import * from std;
import { sload, sstore } from std.opcodes;
import * from std.Generic;
// Marker class. Importing this module brings StorageDeriving into scope, which
// is the signal DeriveGeneric looks for to auto-derive StorageSize / CanStore
// instances for local data types (alongside their Generic instance). It carries
// no methods β its mere visibility enables storage derivation.
trait StorageDeriving<self> {}
// βββ Storage layout for algebraic data types βββββββββββββββββββββββββββββ
//
// This module is the storage analogue of std.ABIGeneric: it teaches the
// StorageSize / StorageType / CanStore classes how to deal with the
// primitive SOP types that `Generic` maps user data types onto
// sum(f, g) with constructors inl / inr (choice / tagged union)
// (f, g) pair (product)
// () unit
// and then bridges every type with a `Generic(rep)` instance to those
// layouts. `Generic` instances are auto-derived for local data types, so
// no per-type boilerplate is needed at the use site.
// βββ StorageSize for the primitive sum(f, g) type ββββββββββββββββββββββββ
// A tagged union occupies one slot for the tag plus enough slots for the
// largest branch: size = 1 + max(size(f), size(g)).
// (StorageSize for () and (a, b) is already provided by std.)
impl<f, g> StorageSize<sum<f, g>> where f: StorageSize, g: StorageSize {
function size(x: Proxy<sum<f, g>>) returns (word) {
let f_sz: word = StorageSize.size(@f);
let g_sz: word = StorageSize.size(@g);
return 1 + maxWord(f_sz, g_sz);
}
}
// βββ StorageType for () ββββββββββββββββββββββββββββββββββββββββββββββββββ
// The unit type occupies no slots, so load/store are no-ops.
impl StorageType<()> {
function load(ptr: word) returns (()) {
return;
}
function store(ptr: word, value: ()) returns (()) {
return;
}
}
// βββ StorageType for the primitive product (a, b) ββββββββββββββββββββββββ
// Layout: [ptr .. ptr + size(a) - 1] : a
// [ptr + size(a) .. ] : b
impl<a, b> StorageType<(a, b)> where a: StorageType, a: StorageSize, b: StorageType {
function load(ptr: word) returns ((a, b)) {
let a_sz: word = StorageSize.size(@a);
let x: a = StorageType.load(ptr);
let y: b = StorageType.load(ptr + a_sz);
return (x, y);
}
function store(ptr: word, value: (a, b)) returns (()) {
match (value) {
case (x, y) {
let a_sz: word = StorageSize.size(@a);
StorageType.store(ptr, x);
StorageType.store(ptr + a_sz, y);
}
}
}
}
// βββ StorageType for the primitive sum(f, g) βββββββββββββββββββββββββββββ
// Slot layout (static sums):
// [ptr] : tag word (0 = inl, 1 = inr)
// [ptr + 1 .. ] : encoded branch payload
impl<f, g> StorageType<sum<f, g>> where f: StorageType, g: StorageType {
function load(ptr: word) returns (sum<f, g>) {
let tag: word = sload(ptr);
match (tag) {
case 0 {
let v: f = StorageType.load(ptr + 1);
return inl(v);
}
default {
let v: g = StorageType.load(ptr + 1);
return inr(v);
}
}
}
function store(ptr: word, value: sum<f, g>) returns (()) {
match (value) {
case inl(v) {
sstore(ptr, 0);
StorageType.store(ptr + 1, v);
}
case inr(v) {
sstore(ptr, 1);
StorageType.store(ptr + 1, v);
}
}
}
}
// βββ Storage layout via CanStore βββββββββββββββββββββββββββββββββββββββββ
//
// The structural instances above teach StorageType the fixed-slot encoding of
// the SOP primitives. But StorageType can only describe word-packed types: a
// dynamically-sized field such as memory(bytes) has a StorageSize (one slot,
// Solidity-style) and a CanStore instance (storage(bytes):CanStore(memory(bytes)))
// but NO StorageType instance. Routing an ADT's storage through StorageType
// therefore rejects any data type carrying such a field, even though the field
// is perfectly storable.
//
// So we give CanStore the same structural treatment, decomposing the SOP
// representation and storing each leaf through the leaf's OWN CanStore instance.
// Fixed leaves resolve to storage(word)/storage(uint256)/β¦ (which delegate to
// StorageType); dynamic leaves resolve to storage(bytes)/storage(string). Each
// field occupies StorageSize-many slots, so offsets are computed exactly as in
// the StorageType layout. The slot handle for a value of type `t` is uniformly
// `storage(t)`, which is why the dynamic leaves below are mirrored at that
// handle.
// The unit type occupies no slots.
impl CanStore<storage<()>, ()> {
function store(r: storage<()>, v: ()) returns (()) {
return;
}
function load(r: storage<()>) returns (()) {
return;
}
}
// Product: store `a` at the base slot, `b` size(a) slots later.
impl<a, b> CanStore<storage<(a, b)>, (a, b)> where storage<a>: CanStore<a>, a: StorageSize, storage<b>: CanStore<b> {
function store(r: storage<(a, b)>, v: (a, b)) returns (()) {
match (v) {
case (x, y) {
let base: word = Typedef.rep(r);
let a_sz: word = StorageSize.size(@a);
let syntaxValue1: storage<a> = storage(base);
CanStore.store(syntaxValue1, x);
let syntaxValue2: storage<b> = storage(base + a_sz);
CanStore.store(syntaxValue2, y);
}
}
}
function load(r: storage<(a, b)>) returns ((a, b)) {
let base: word = Typedef.rep(r);
let a_sz: word = StorageSize.size(@a);
let syntaxValue3: storage<a> = storage(base);
let x: a = CanStore.load(syntaxValue3);
let syntaxValue4: storage<b> = storage(base + a_sz);
let y: b = CanStore.load(syntaxValue4);
return (x, y);
}
}
// Tagged union: slot 0 holds the tag, the branch payload follows.
impl<f, g> CanStore<storage<sum<f, g>>, sum<f, g>> where storage<f>: CanStore<f>, storage<g>: CanStore<g> {
function store(r: storage<sum<f, g>>, v: sum<f, g>) returns (()) {
let base: word = Typedef.rep(r);
match (v) {
case inl(x) {
sstore(base, 0);
let syntaxValue5: storage<f> = storage(base + 1);
CanStore.store(syntaxValue5, x);
}
case inr(y) {
sstore(base, 1);
let syntaxValue6: storage<g> = storage(base + 1);
CanStore.store(syntaxValue6, y);
}
}
}
function load(r: storage<sum<f, g>>) returns (sum<f, g>) {
let base: word = Typedef.rep(r);
let tag: word = sload(base);
// NOTE: the loaded payload is inlined directly into inl(...) / inr(...)
// rather than bound to a `let x : f` / `let y : g` first. Binding the
// payload to an intermediate of the branch type (f or g) makes the
// compiler infer the *branch* type for the inl/inr application instead
// of the full sum(f, g), so it emits e.g. `inr<g>(y)` and Yul codegen
// rejects it (sum nesting off by one). Inlining matches the working
// ABIGeneric.decode pattern, so inl/inr pick up the full sum(f, g).
match (tag) {
case 0 {
let syntaxValue7: storage<f> = storage(base + 1);
return inl(CanStore.load(syntaxValue7));
}
default {
let syntaxValue8: storage<g> = storage(base + 1);
return inr(CanStore.load(syntaxValue8));
}
}
}
}
// Dynamic leaves at the uniform storage(t) handle. std provides the storage(bytes)
// / storage(string) instances (data lives at keccak(slot)); these mirror them at
// the storage(memory(bytes)) / storage(memory(string)) handle the structural
// decomposition asks for, so a memory(bytes) field inside an ADT is storable.
impl CanStore<storage<memory<bytes>>, memory<bytes>> {
function store(r: storage<memory<bytes>>, v: memory<bytes>) returns (()) {
let syntaxValue9: storage<bytes> = storage(Typedef.rep(r));
CanStore.store(syntaxValue9, v);
}
function load(r: storage<memory<bytes>>) returns (memory<bytes>) {
let syntaxValue10: storage<bytes> = storage(Typedef.rep(r));
return CanStore.load(syntaxValue10);
}
}
impl CanStore<storage<memory<string>>, memory<string>> {
function store(r: storage<memory<string>>, v: memory<string>) returns (()) {
let syntaxValue11: storage<string> = storage(Typedef.rep(r));
CanStore.store(syntaxValue11, v);
}
function load(r: storage<memory<string>>) returns (memory<string>) {
let syntaxValue12: storage<string> = storage(Typedef.rep(r));
return CanStore.load(syntaxValue12);
}
}
// StorageType / CanStore for an ADT are NOT provided here as blanket bridges.
//
// A `default instance a:StorageType` would have its `load` return the head
// variable `a` via Generic.to β but the specializer cannot monomorphize a
// result-position type variable of a default instance (it is not pinned by the
// arguments), so loads panic. Likewise a tyvar-headed `default a:CanStore(b)`
// is non-functional (accepts any storable b), so contract field access cannot
// infer the stored type from the slot type.
//
// Instead, DeriveGeneric emits a concrete, per-type storage(T):CanStore(T)
// instance (see `derived_storage.rs` in hir-ty) where the data type is fixed
// in the instance head; it delegates to the structural CanStore instances above
// via the type's Generic representation. StorageSize is likewise derived
// per-type for the field layout.
// βββ Top-level helpers βββββββββββββββββββββββββββββββββββββββββββββββββββ
// Convenience wrappers mirroring std.ABIGeneric's encode / decode: persist or
// read back any 'a' that has a Generic(rep) instance at a raw storage slot.
function storeGeneric<a, rep>(slot: word, value: a) returns (()) where a: Generic<rep>, rep: StorageType {
StorageType.store(slot, Generic.from(value));
}
function loadGeneric<a, rep>(slot: word) returns (a) where a: Generic<rep>, rep: StorageType {
let r: rep = StorageType.load(slot);
return Generic.to(r);
}