-
Notifications
You must be signed in to change notification settings - Fork 4
/
main.eas
387 lines (326 loc) · 16.3 KB
/
main.eas
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
;; ███████╗ ██████╗ ██████╗ ██████╗ █████╗ ███████╗███╗ ███╗
;; ╚════██║██╔═████╗██╔═████╗╚════██╗ ██╔══██╗██╔════╝████╗ ████║
;; ██╔╝██║██╔██║██║██╔██║ █████╔╝ ███████║███████╗██╔████╔██║
;; ██╔╝ ████╔╝██║████╔╝██║██╔═══╝ ██╔══██║╚════██║██║╚██╔╝██║
;; ██║ ╚██████╔╝╚██████╔╝███████╗ ██║ ██║███████║██║ ╚═╝ ██║
;; ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
;;
;; This is an implementation of EIP-7002's pre-deploy contract. It implements an
;; unvalidated withdrawal requests queue for beacon chain validators. The queue
;; is tracked using head and tail index pointers. After the queue is emptied,
;; the pointers are reset to zero.
;;
;; Entrance to the queue is determined only by a call's ability to pay the
;; exponentially increasing fee. This fee is computed using a simple function
;; which approximates true exponential value. No verification of ownership is
;; done by the pre-deploy or the execution layer. Only once the requests are
;; being processed by the beacon chain is the validity verified. The fee is used
;; to avoid spamming of the withdrawal requests queue.
;; -----------------------------------------------------------------------------
;; CONSTANTS -------------------------------------------------------------------
;; -----------------------------------------------------------------------------
#define SYSTEM_ADDR 0xfffffffffffffffffffffffffffffffffffffffe
#define SLOT_EXCESS 0
#define SLOT_COUNT 1
#define QUEUE_HEAD 2
#define QUEUE_TAIL 3
#define QUEUE_OFFSET 4
#define MIN_FEE 1
#define TARGET_PER_BLOCK 2
#define MAX_PER_BLOCK 16
#define FEE_UPDATE_FRACTION 17
#define EXCESS_INHIBITOR 1181
#define INPUT_SIZE 56 ;; the size of (pubkey ++ amount)
#define RECORD_SIZE 76 ;; the size of (address ++ pubkey ++ amount)
;; -----------------------------------------------------------------------------
;; PROGRAM START ---------------------------------------------------------------
;; -----------------------------------------------------------------------------
.start:
;; Protect the system subroutine by checking if the caller is the system
;; address.
caller ;; [caller]
push20 SYSTEM_ADDR ;; [sysaddr, caller]
eq ;; [sysaddr == caller]
push1 @read_requests ;; [read_lbl, sysaddr == caller]
jumpi ;; []
;; ---------------------------------------------------------------------------
;; USER SUBROUTINE -----------------------------------------------------------
;; ---------------------------------------------------------------------------
;;
;; Record new withdrawal request ~~
;; This is the default code path. It will attempt to record a user's request
;; so long as they pay the required fee.
;; If calldatasize == 0, return the current excess withdrawal requests.
calldatasize ;; [calldatasize]
iszero ;; [calldatasize == 0]
iszero ;; [calldatasize != 0]
jumpi @check_input
;; Load excess withdrawal requests and return the value.
push SLOT_EXCESS ;; [excess_reqs_slot]
sload ;; [excess_reqs]
push0 ;; [0, excess_reqs]
mstore ;; []
push 32 ;; [32]
push 0 ;; [0, 32]
return ;; []
check_input:
;; Input data has the following layout:
;;
;; +--------+--------+
;; | pubkey | amount |
;; +--------+--------+
;; 48 8
;; Verify the input is exactly 56 bytes.
calldatasize ;; [calldatasize]
push1 INPUT_SIZE ;; [INPUT_SIZE, calldatasize]
eq ;; [INPUT_SIZE == calldatasize]
iszero ;; [INPUT_SIZE != calldatasize]
jumpi @revert ;; []
;; Compute the fee using fake expo and the current excess withdrawal requests.
push FEE_UPDATE_FRACTION
push SLOT_EXCESS ;; [excess_slot, update_fraction]
sload ;; [excess, update_fraction]
push MIN_FEE ;; [min_fee, excess, update_fraction]
#include "fake_expo.eas"
;; Determine if the fee provided is enough to cover the withdrawal request fee.
callvalue ;; [callvalue, req_fee]
lt ;; [callvalue < req_fee]
jumpi @revert ;; []
;; The request can pay, increment withdrawal request count.
push SLOT_COUNT
sload ;; [req_count]
push1 1 ;; [1, req_count]
add ;; [req_count+1]
push SLOT_COUNT
sstore ;; []
;; Now insert request into queue. First, compute the base storage slot
push QUEUE_TAIL ;; [tail_idx_slot]
sload ;; [tail_idx]
dup1 ;; [tail_idx, tail_idx]
push1 3 ;; [3, tail_idx, tail_idx]
mul ;; [3*tail_idx, tail_idx]
push QUEUE_OFFSET
add ;; [slot, tail_idx]
;; Write address to queue.
caller ;; [caller, slot, ..]
dup2 ;; [slot, caller, slot, ..]
sstore ;; [slot, ..]
push1 1 ;; [1, slot, ..]
add ;; [slot, ..]
;; Store pk[0:32] to queue.
push0 ;; [0, slot, ..]
calldataload ;; [pk[0:32], slot, ..]
dup2 ;; [slot, pk[0:32], slot, ..]
sstore ;; [slot, ..]
push1 1 ;; [1, slot, ..]
add ;; [slot, ..]
;; Store pk2_am to queue.
push1 32 ;; [32, slot, ..]
calldataload ;; [pk2_am, slot, ..]
swap1 ;; [slot, pk2_am, ..]
sstore ;; [..]
;; Increment queue tail over last and write to storage.
push1 1 ;; [1, tail_idx]
add ;; [tail_idx+1]
push QUEUE_TAIL ;; [tail_idx_slot]
sstore ;; []
stop
;; -----------------------------------------------------------------------------
;; SYSTEM SUBROUTINE -----------------------------------------------------------
;; -----------------------------------------------------------------------------
;;
;; Pop withdrawal request from queue, update fee accumulator ~~
;; This is the logic executed by the protocol each block. It reads as many
;; requests as available from the queue, until the max withdrawal request per
;; block is reached. The requests are returned as a contiguous array of bytes
;; with each record being exactly 76 bytes.
;;
;; Withdrawal request record:
;;
;; +------+--------+--------+
;; | addr | pubkey | amount |
;; +------+--------+--------+
;; 20 48 8
;;
;; Because the requests are stored across three storage slots, there is some
;; shuffling to align the data.
;;
;; After reading the withdrawal requests, they are removed from the queue by
;; modifying the queue's head index. The excess requests accumulator is updated
;; so that the new cost of requesting a withdrawal is reflected. Finally, the
;; request count is reset.
read_requests:
;; Determine the size of the queue by calculating tail - head.
push QUEUE_TAIL ;; [tail_idx_slot, head_idx, head_idx]
sload ;; [tail_idx]
push QUEUE_HEAD ;; [head_idx_slot, tail_idx]
sload ;; [head_idx, tail_idx]
;; Now compute the count.
dup1 ;; [head_idx, head_idx, tail_idx]
dup3 ;; [tail_idx, head_idx, head_idx, tail_idx]
sub ;; [count, head_idx, tail_idx]
;; Determine if count is greater than the max withdrawal requests.
dup1 ;; [count, count, head_idx, tail_idx]
push MAX_PER_BLOCK ;; [reqs_per_block, count, count, head_idx, tail_idx]
gt ;; [reqs_per_block > count, count, head_idx, tail_idx]
jumpi @begin_loop ;; [count, head_idx, tail_idx]
;; Discard count, use the max withdrawal requests per block.
pop ;; [head_idx, tail_idx]
push MAX_PER_BLOCK ;; [count, head_idx, tail_idx]
begin_loop:
push0 ;; [i, count, head_idx, tail_idx]
accum_loop:
;; This loop will read each request and byte bang it into a 76 byte chunk.
;; Bounds check, ensure i < count.
dup2 ;; [count, i, count, head_idx, tail_idx]
dup2 ;; [i, count, i, count, head_idx, tail_idx]
eq ;; [i == count, i, count, head_idx, tail_idx]
jumpi @update_head ;; [i, count, head_idx, tail_idx]
;; Precompute record_offset = i*RECORD_SIZE.
dup1 ;; [i, i, count, head_idx, tail_idx]
push RECORD_SIZE ;; [size, i, i, count, head_idx, tail_idx]
mul ;; [record_offset, i, count, head_idx, tail_idx]
;; Determine the storage slot of the address for this iteration. This value is
;; also the base for the other two storage slots containing the public key and
;; amount. The base slot will be (queue_offset + queue_head*3 + i*3).
dup4 ;; [head_idx, record_offset, i, ..]
dup3 ;; [i, head_idx, record_offset, i, ..]
add ;; [i+head_idx, record_offset, i, ..]
push 3 ;; [3, i+head_idx, record_offset, i, ..]
mul ;; [3*(i+head_idx), record_offset, i, ..]
push QUEUE_OFFSET ;; [offset, 3*(i+head_idx), record_offset, i, ..]
add ;; [addr_offset, record_offset, i, ..]
;; Read address.
dup1 ;; [addr_offset, addr_offset, record_offset, i, ..]
sload ;; [addr, addr_offset, record_offset, i, ..]
;; Compute pk[0:32] offset and read it.
swap1 ;; [addr_offset, addr, record_offset, i, ..]
push 1 ;; [1, addr_offset, addr, record_offset, i, ..]
add ;; [pk1_offset, addr, record_offset, i, ..]
dup1 ;; [pk1_offset, pk1_offset, addr, record_offset, i, ..]
sload ;; [pk[0:32], pk1_offset, addr, record_offset, i, ..]
;; Compute pk2_am offset and read it.
swap1 ;; [pk1_offset, pk[0:32], addr, record_offset, i, ..]
push 1 ;; [1, pk1_offset, pk[0:32], addr, record_offset, i, ..]
add ;; [pk2_am_offset, pk[0:32], addr, record_offset, i, ..]
sload ;; [pk2_am, pk[0:32], addr, record_offset, i, ..]
;; Write values to memory flat and contiguously. This require combining the
;; three storage elements (addr, pk[0:32], pk2_am) so there is no padding.
;;
;; Each stack element has the following layout:
;;
;; A: addr
;; 0x00 | 00 00 00 00 00 00 00 00 00 00 00 00 aa aa aa aa
;; 0x10 | aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa
;;
;; B: pk[0:32]
;; 0x00 | bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb
;; 0x10 | bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb
;;
;; C: pk[32:48] ++ am[0:8] -> pk2_am
;; 0x00 | cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc
;; 0x10 | dd dd dd dd dd dd dd dd 00 00 00 00 00 00 00 00
;;
;; To get these three stack elements into the correct contiguous format, it is
;; neccessary to combine them in the follow form:
;;
;; (A[12:32] ++ B[0:12], B[12:32] ++ C[0:12], C[12:24])
;; Shift addr bytes.
swap2 ;; [addr, pk[0:32], pk2_am, record_offset, i, ..]
push 12*8 ;; [96, addr, pk0:32], pk2_am, record_offset, i, ..]
shl ;; [addr<<96, pk[0:32], pk2_am, record_offset, i, ..]
;; Store addr at offset = i*RECORD_SIZE.
dup4 ;; [record_offset, addr<<96, pk[0:32], pk2_am, record_offset, i, ..]
mstore ;; [pk[0:32], pk2_am, record_offset, i, ..]
;; Store pk[0:32] at offset = i*RECORD_SIZE + 20.
dup3 ;; [record_offset, pk[0:32], pk2_am, record_offset, i, ..]
push 20 ;; [20, record_offset, pk[0:32], pk2_am, record_offset, i, ..]
add ;; [record_offset+20, pk[0:32], pk2_am, record_offset, i, ..]
mstore ;; [pk2_am, record_offset, i, ..]
;; Store pk2_am at offset = i*RECORD_SIZE + 52.
swap1 ;; [record_offset, pk2_am, i, ..]
push 52 ;; [52, record_offset, pk2_am, i, ..]
add ;; [record_offset+52, pk2_am, i, ..]
mstore ;; [i, ..]
;; Increment i.
push 1 ;; [1, i, ..]
add ;; [i+1, ..]
jump @accum_loop ;; [i, count, head_idx, tail_idx]
update_head:
;; All requests have been read, update queue by adding the count read.
;; to the current head index.
swap2 ;; [head_idx, count, count, tail_idx]
add ;; [new_head_idx, count, tail_idx]
;; If the new head is equal to the tail, reset the queue by zeroing them both.
dup1 ;; [new_head_idx, new_head_idx, count, tail_idx]
swap3 ;; [tail_idx, new_head_idx, count, new_head_idx]
eq ;; [new_head_idx == tail_idx, count, new_head_idx]
jumpi @reset_queue ;; [count, new_head_idx]
;; Otherwise, write the new head to storage.
swap1 ;; [new_head_idx, count]
push QUEUE_HEAD ;; [head_idx_slot, new_head_idx, count]
sstore ;; [count]
jump @update_excess ;; [count]
reset_queue:
;; Since the queue is empty, both the head and tail indexes can be zeroed.
swap1 ;; [new_head_idx, count]
pop ;; [count]
push0 ;; [0, count]
push QUEUE_HEAD ;; [head_slot, 0, count]
sstore ;; [count]
push0 ;; [0, count]
push QUEUE_TAIL ;; [tail_slot, 0, count]
sstore ;; [count]
update_excess:
;; Update the new excess withdrawal requests.
push SLOT_EXCESS ;; [excess_slot, count]
sload ;; [excess, count]
;; Check if excess needs to be reset to 0 for first iteration after
;; activation.
dup1 ;; [excess, excess, count, count]
push EXCESS_INHIBITOR ;; [inhibitor, excess, excess, count, count]
eq ;; [inhibitor == excess, excess, count, count]
iszero ;; [inhibitor != excess, excess, count, count]
jumpi @skip_reset ;; [excess, count, count]
;; Drop the excess from storage and use 0.
pop ;; [count, count]
push0 ;; [reset_excess, count]
skip_reset:
push SLOT_COUNT ;; [count_slot, excess, count]
sload ;; [count, excess, count]
;; If the sum of the previous excess requests and requests added in the
;; current block is greater than the target, subtract the target from the sum
;; and set it as the new excess withdrawal requests value.
push TARGET_PER_BLOCK ;; [target, count, excess, count]
dup3 ;; [excess, target, count, excess]
dup3 ;; [count, excess, target, count, excess, count]
add ;; [count+excess, target, count, excess, count]
gt ;; [count+excess > target, count, excess, count]
jumpi @compute_excess ;; [count, excess, count]
;; Zero out excess.
pop ;; [excess, count]
pop ;; [count]
push0
jump @store_excess
compute_excess:
add ;; [count+excess, count]
push TARGET_PER_BLOCK ;; [target, count+excess, count]
swap1 ;; [count+excess, target, count]
sub ;; [new_excess, count]
store_excess:
push SLOT_EXCESS ;; [excess_slot, new_excess, count]
sstore ;; [count]
;; Reset withdrawal request count.
push0 ;; [0, count]
push SLOT_COUNT ;; [count_slot, 0, count]
sstore ;; [count]
;; Return the withdrawal requests.
push RECORD_SIZE ;; [record_size, count]
mul ;; [size]
push0 ;; [0, size]
return ;; []
;; Revert subroutine.
revert:
push0
push0
revert