-
Notifications
You must be signed in to change notification settings - Fork 12.4k
Expand file tree
/
Copy pathArrays.js
More file actions
513 lines (457 loc) · 17.1 KB
/
Arrays.js
File metadata and controls
513 lines (457 loc) · 17.1 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
const format = require('../format-lines');
const { capitalize } = require('../../helpers');
const { TYPES } = require('./Arrays.opts');
const header = `\
pragma solidity ^0.8.24;
import {Comparators} from "./Comparators.sol";
import {SlotDerivation} from "./SlotDerivation.sol";
import {StorageSlot} from "./StorageSlot.sol";
import {Math} from "./math/Math.sol";
/**
* @dev Collection of functions related to array types.
*/
`;
const sort = type => `\
/**
* @dev Sort an array of ${type.name} (in memory) following the provided comparator function.
*
* This function does the sorting "in place", meaning that it overrides the input. The object is returned for
* convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array.
*
* NOTE: this function's cost is \`O(n · log(n))\` in average and \`O(n²)\` in the worst case, with n the length of the
* array. Using it in view functions that are executed through \`eth_call\` is safe, but one should be very careful
* when executing this as part of a transaction. If the array being sorted is too large, the sort operation may
* consume more gas than is available in a block, leading to potential DoS.
*
* IMPORTANT: Consider memory side-effects when using custom comparator functions that access memory in an unsafe way.
*/
function sort(
${type.name}[] memory array,
function(${type.name}, ${type.name}) pure returns (bool) comp
) internal pure returns (${type.name}[] memory) {
${
type.name === 'uint256'
? '_quickSort(_begin(array), _end(array), comp);'
: 'sort(_castToUint256Array(array), _castToUint256Comp(comp));'
}
return array;
}
/**
* @dev Variant of {sort} that sorts an array of ${type.name} in increasing order.
*/
function sort(${type.name}[] memory array) internal pure returns (${type.name}[] memory) {
${type.name === 'uint256' ? 'sort(array, Comparators.lt);' : 'sort(_castToUint256Array(array), Comparators.lt);'}
return array;
}
`;
const quickSort = `\
/**
* @dev Performs a quick sort of a segment of memory. The segment sorted starts at \`begin\` (inclusive), and stops
* at end (exclusive). Sorting follows the \`comp\` comparator.
*
* Invariant: \`begin <= end\`. This is the case when initially called by {sort} and is preserved in subcalls.
*
* IMPORTANT: Memory locations between \`begin\` and \`end\` are not validated/zeroed. This function should
* be used only if the limits are within a memory array.
*/
function _quickSort(uint256 begin, uint256 end, function(uint256, uint256) pure returns (bool) comp) private pure {
unchecked {
while (end - begin > 0x20) {
// Use first element as pivot
uint256 pivot = _mload(begin);
// Position where the pivot should be at the end of the loop
uint256 pos = begin;
for (uint256 it = begin + 0x20; it < end; it += 0x20) {
if (comp(_mload(it), pivot)) {
// If the value stored at the iterator's position comes before the pivot, we increment the
// position of the pivot and move the value there.
pos += 0x20;
_swap(pos, it);
}
}
_swap(begin, pos); // Swap pivot into place
// Recurse on the smaller partition, iterate on the larger one.
uint256 middle = pos + 0x20;
if (pos - begin < end - middle) {
_quickSort(begin, pos, comp);
begin = middle;
} else {
_quickSort(middle, end, comp);
end = pos;
}
}
}
}
/**
* @dev Pointer to the memory location of the first element of \`array\`.
*/
function _begin(uint256[] memory array) private pure returns (uint256 ptr) {
assembly ("memory-safe") {
ptr := add(array, 0x20)
}
}
/**
* @dev Pointer to the memory location of the first memory word (32bytes) after \`array\`. This is the memory word
* that comes just after the last element of the array.
*/
function _end(uint256[] memory array) private pure returns (uint256 ptr) {
unchecked {
return _begin(array) + array.length * 0x20;
}
}
/**
* @dev Load memory word (as a uint256) at location \`ptr\`.
*/
function _mload(uint256 ptr) private pure returns (uint256 value) {
assembly {
value := mload(ptr)
}
}
/**
* @dev Swaps the elements memory location \`ptr1\` and \`ptr2\`.
*/
function _swap(uint256 ptr1, uint256 ptr2) private pure {
assembly {
let value1 := mload(ptr1)
let value2 := mload(ptr2)
mstore(ptr1, value2)
mstore(ptr2, value1)
}
}
`;
const castArray = type => `\
/// @dev Helper: low level cast ${type.name} memory array to uint256 memory array
function _castToUint256Array(${type.name}[] memory input) private pure returns (uint256[] memory output) {
assembly {
output := input
}
}
`;
const castComparator = type => `\
/// @dev Helper: low level cast ${type.name} comp function to uint256 comp function
function _castToUint256Comp(
function(${type.name}, ${type.name}) pure returns (bool) input
) private pure returns (function(uint256, uint256) pure returns (bool) output) {
assembly {
output := input
}
}
`;
const search = `\
/**
* @dev Searches a sorted \`array\` and returns the first index that contains
* a value greater or equal to \`element\`. If no such index exists (i.e. all
* values in the array are strictly less than \`element\`), the array length is
* returned. Time complexity O(log n).
*
* NOTE: The \`array\` is expected to be sorted in ascending order, and to
* contain no repeated elements.
*
* IMPORTANT: Deprecated. This implementation behaves as {lowerBound} but lacks
* support for repeated elements in the array. The {lowerBound} function should
* be used instead.
*/
function findUpperBound(uint256[] storage array, uint256 element) internal view returns (uint256) {
uint256 low = 0;
uint256 high = array.length;
if (high == 0) {
return 0;
}
while (low < high) {
uint256 mid = Math.average(low, high);
// Note that mid will always be strictly less than high (i.e. it will be a valid array index)
// because Math.average rounds towards zero (it does integer division with truncation).
if (unsafeAccess(array, mid).value > element) {
high = mid;
} else {
low = mid + 1;
}
}
// At this point \`low\` is the exclusive upper bound. We will return the inclusive upper bound.
if (low > 0 && unsafeAccess(array, low - 1).value == element) {
return low - 1;
} else {
return low;
}
}
/**
* @dev Searches an \`array\` sorted in ascending order and returns the first
* index that contains a value greater or equal than \`element\`. If no such index
* exists (i.e. all values in the array are strictly less than \`element\`), the array
* length is returned. Time complexity O(log n).
*
* See C++'s https://en.cppreference.com/w/cpp/algorithm/lower_bound[lower_bound].
*/
function lowerBound(uint256[] storage array, uint256 element) internal view returns (uint256) {
uint256 low = 0;
uint256 high = array.length;
if (high == 0) {
return 0;
}
while (low < high) {
uint256 mid = Math.average(low, high);
// Note that mid will always be strictly less than high (i.e. it will be a valid array index)
// because Math.average rounds towards zero (it does integer division with truncation).
if (unsafeAccess(array, mid).value < element) {
// this cannot overflow because mid < high
unchecked {
low = mid + 1;
}
} else {
high = mid;
}
}
return low;
}
/**
* @dev Searches an \`array\` sorted in ascending order and returns the first
* index that contains a value strictly greater than \`element\`. If no such index
* exists (i.e. all values in the array are strictly less than \`element\`), the array
* length is returned. Time complexity O(log n).
*
* See C++'s https://en.cppreference.com/w/cpp/algorithm/upper_bound[upper_bound].
*/
function upperBound(uint256[] storage array, uint256 element) internal view returns (uint256) {
uint256 low = 0;
uint256 high = array.length;
if (high == 0) {
return 0;
}
while (low < high) {
uint256 mid = Math.average(low, high);
// Note that mid will always be strictly less than high (i.e. it will be a valid array index)
// because Math.average rounds towards zero (it does integer division with truncation).
if (unsafeAccess(array, mid).value > element) {
high = mid;
} else {
// this cannot overflow because mid < high
unchecked {
low = mid + 1;
}
}
}
return low;
}
/**
* @dev Same as {lowerBound}, but with an array in memory.
*/
function lowerBoundMemory(uint256[] memory array, uint256 element) internal pure returns (uint256) {
uint256 low = 0;
uint256 high = array.length;
if (high == 0) {
return 0;
}
while (low < high) {
uint256 mid = Math.average(low, high);
// Note that mid will always be strictly less than high (i.e. it will be a valid array index)
// because Math.average rounds towards zero (it does integer division with truncation).
if (unsafeMemoryAccess(array, mid) < element) {
// this cannot overflow because mid < high
unchecked {
low = mid + 1;
}
} else {
high = mid;
}
}
return low;
}
/**
* @dev Same as {upperBound}, but with an array in memory.
*/
function upperBoundMemory(uint256[] memory array, uint256 element) internal pure returns (uint256) {
uint256 low = 0;
uint256 high = array.length;
if (high == 0) {
return 0;
}
while (low < high) {
uint256 mid = Math.average(low, high);
// Note that mid will always be strictly less than high (i.e. it will be a valid array index)
// because Math.average rounds towards zero (it does integer division with truncation).
if (unsafeMemoryAccess(array, mid) > element) {
high = mid;
} else {
// this cannot overflow because mid < high
unchecked {
low = mid + 1;
}
}
}
return low;
}
`;
const unsafeAccessStorage = type => `\
/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
* WARNING: Only use if you are certain \`pos\` is lower than the array length.
*/
function unsafeAccess(${type.name}[] storage arr, uint256 pos) internal pure returns (StorageSlot.${capitalize(
type.name,
)}Slot storage) {
bytes32 slot;
assembly ("memory-safe") {
slot := arr.slot
}
return slot.deriveArray().offset(pos).get${capitalize(type.name)}Slot();
}
`;
const unsafeAccessMemory = type => `\
/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
* WARNING: Only use if you are certain \`pos\` is lower than the array length.
*/
function unsafeMemoryAccess(${type.name}[] memory arr, uint256 pos) internal pure returns (${type.name}${
type.isValueType ? '' : ' memory'
} res) {
assembly {
res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
}
}
`;
const unsafeSetLength = type => `\
/**
* @dev Helper to set the length of a dynamic array. Directly writing to \`.length\` is forbidden.
*
* WARNING: this does not clear elements if length is reduced, or initialize elements if length is increased.
*/
function unsafeSetLength(${type.name}[] storage array, uint256 len) internal {
assembly ("memory-safe") {
sstore(array.slot, len)
}
}
`;
const slice = type => `\
/**
* @dev Copies the content of \`array\`, from \`start\` (included) to the end of \`array\` into a new ${type.name} array in
* memory.
*
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice[Javascript's \`Array.slice\`]
*/
function slice(${type.name}[] memory array, uint256 start) internal pure returns (${type.name}[] memory) {
return slice(array, start, array.length);
}
/**
* @dev Copies the content of \`array\`, from \`start\` (included) to \`end\` (excluded) into a new ${type.name} array in
* memory. The \`end\` argument is truncated to the length of the \`array\`.
*
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice[Javascript's \`Array.slice\`]
*/
function slice(${type.name}[] memory array, uint256 start, uint256 end) internal pure returns (${type.name}[] memory) {
// sanitize
end = Math.min(end, array.length);
start = Math.min(start, end);
// allocate and copy
${type.name}[] memory result = new ${type.name}[](end - start);
assembly ("memory-safe") {
mcopy(add(result, 0x20), add(add(array, 0x20), mul(start, 0x20)), mul(sub(end, start), 0x20))
}
return result;
}
`;
const splice = type => `\
/**
* @dev Moves the content of \`array\`, from \`start\` (included) to the end of \`array\` to the start of that array,
* and shrinks the array length accordingly, effectively overwriting the array with array[start:].
*
* NOTE: This function modifies the provided array in place. If you need to preserve the original array, use {slice} instead.
*/
function splice(${type.name}[] memory array, uint256 start) internal pure returns (${type.name}[] memory) {
return splice(array, start, array.length);
}
/**
* @dev Moves the content of \`array\`, from \`start\` (included) to \`end\` (excluded) to the start of that array,
* and shrinks the array length accordingly, effectively overwriting the array with array[start:end]. The
* \`end\` argument is truncated to the length of the \`array\`.
*
* NOTE: This function modifies the provided array in place. If you need to preserve the original array, use {slice} instead.
*/
function splice(${type.name}[] memory array, uint256 start, uint256 end) internal pure returns (${type.name}[] memory) {
// sanitize
end = Math.min(end, array.length);
start = Math.min(start, end);
// move and resize
assembly ("memory-safe") {
mcopy(add(array, 0x20), add(add(array, 0x20), mul(start, 0x20)), mul(sub(end, start), 0x20))
mstore(array, sub(end, start))
}
return array;
}
/**
* @dev Replaces elements in \`array\` starting at \`pos\` with all elements from \`replacement\`.
*
* Parameters are clamped to valid ranges (e.g. \`pos\` is clamped to \`[0, array.length]\`).
* If \`pos >= array.length\`, no replacement occurs and the array is returned unchanged.
*
* NOTE: This function modifies the provided array in place.
*/
function replace(
${type.name}[] memory array,
uint256 pos,
${type.name}[] memory replacement
) internal pure returns (${type.name}[] memory) {
return replace(array, pos, replacement, 0, replacement.length);
}
/**
* @dev Replaces elements in \`array\` starting at \`pos\` with elements from \`replacement\` starting at \`offset\`.
* Copies at most \`length\` elements from \`replacement\` to \`array\`.
*
* Parameters are clamped to valid ranges (i.e. \`pos\` is clamped to \`[0, array.length]\`, \`offset\` is
* clamped to \`[0, replacement.length]\`, and \`length\` is clamped to \`min(length, replacement.length - offset,
* array.length - pos)\`). If \`pos >= array.length\` or \`offset >= replacement.length\`, no replacement occurs
* and the array is returned unchanged.
*
* NOTE: This function modifies the provided array in place.
*/
function replace(
${type.name}[] memory array,
uint256 pos,
${type.name}[] memory replacement,
uint256 offset,
uint256 length
) internal pure returns (${type.name}[] memory) {
// sanitize
pos = Math.min(pos, array.length);
offset = Math.min(offset, replacement.length);
length = Math.min(length, Math.min(replacement.length - offset, array.length - pos));
// replace
assembly ("memory-safe") {
mcopy(
add(add(array, 0x20), mul(pos, 0x20)),
add(add(replacement, 0x20), mul(offset, 0x20)),
mul(length, 0x20)
)
}
return array;
}
`;
// GENERATE
module.exports = format(
header.trimEnd(),
'library Arrays {',
format(
[].concat(
'using SlotDerivation for bytes32;',
'using StorageSlot for bytes32;',
'',
// sorting, comparator, helpers and internal
sort({ name: 'uint256' }),
TYPES.filter(type => type.isValueType && type.name !== 'uint256').map(sort),
quickSort,
TYPES.filter(type => type.isValueType && type.name !== 'uint256').map(castArray),
TYPES.filter(type => type.isValueType && type.name !== 'uint256').map(castComparator),
// lookup
search,
// slice and splice for value types only
TYPES.filter(type => type.isValueType).map(slice),
TYPES.filter(type => type.isValueType).map(splice),
// unsafe (direct) storage and memory access
TYPES.map(unsafeAccessStorage),
TYPES.map(unsafeAccessMemory),
TYPES.map(unsafeSetLength),
),
).trimEnd(),
'}',
);