Skip to content

Commit 695a960

Browse files
authored
Merge branch 'master' into feat/governor-keyed-nonces
2 parents cc0d64e + 6015d7e commit 695a960

File tree

9 files changed

+210
-70
lines changed

9 files changed

+210
-70
lines changed

.changeset/rare-shirts-unite.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': minor
3+
---
4+
5+
`Arrays`: Add `unsafeAccess`, `unsafeMemoryAccess` and `unsafeSetLength` for `bytes[]` and `string[]`.

.github/workflows/checks.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ jobs:
118118
- uses: actions/checkout@v4
119119
- name: Set up environment
120120
uses: ./.github/actions/setup
121-
- uses: crytic/slither-action@v0.4.0
121+
- uses: crytic/slither-action@d86660fe7e45835a0ec7b7aeb768d271fb421ea0 # Temporary use tagged commit until PR #93 merged
122122

123123
codespell:
124124
runs-on: ubuntu-latest

contracts/mocks/ArraysMock.sol

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,47 @@ contract Bytes32ArraysMock {
125125
return _array.length;
126126
}
127127
}
128+
129+
contract BytesArraysMock {
130+
using Arrays for bytes[];
131+
132+
bytes[] private _array;
133+
134+
constructor(bytes[] memory array) {
135+
_array = array;
136+
}
137+
138+
function unsafeAccess(uint256 pos) external view returns (bytes memory) {
139+
return _array.unsafeAccess(pos).value;
140+
}
141+
142+
function unsafeSetLength(uint256 newLength) external {
143+
_array.unsafeSetLength(newLength);
144+
}
145+
146+
function length() external view returns (uint256) {
147+
return _array.length;
148+
}
149+
}
150+
151+
contract StringArraysMock {
152+
using Arrays for string[];
153+
154+
string[] private _array;
155+
156+
constructor(string[] memory array) {
157+
_array = array;
158+
}
159+
160+
function unsafeAccess(uint256 pos) external view returns (string memory) {
161+
return _array.unsafeAccess(pos).value;
162+
}
163+
164+
function unsafeSetLength(uint256 newLength) external {
165+
_array.unsafeSetLength(newLength);
166+
}
167+
168+
function length() external view returns (uint256) {
169+
return _array.length;
170+
}
171+
}

contracts/utils/Arrays.sol

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,32 @@ library Arrays {
414414
return slot.deriveArray().offset(pos).getUint256Slot();
415415
}
416416

417+
/**
418+
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
419+
*
420+
* WARNING: Only use if you are certain `pos` is lower than the array length.
421+
*/
422+
function unsafeAccess(bytes[] storage arr, uint256 pos) internal pure returns (StorageSlot.BytesSlot storage) {
423+
bytes32 slot;
424+
assembly ("memory-safe") {
425+
slot := arr.slot
426+
}
427+
return slot.deriveArray().offset(pos).getBytesSlot();
428+
}
429+
430+
/**
431+
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
432+
*
433+
* WARNING: Only use if you are certain `pos` is lower than the array length.
434+
*/
435+
function unsafeAccess(string[] storage arr, uint256 pos) internal pure returns (StorageSlot.StringSlot storage) {
436+
bytes32 slot;
437+
assembly ("memory-safe") {
438+
slot := arr.slot
439+
}
440+
return slot.deriveArray().offset(pos).getStringSlot();
441+
}
442+
417443
/**
418444
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
419445
*
@@ -447,6 +473,28 @@ library Arrays {
447473
}
448474
}
449475

476+
/**
477+
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
478+
*
479+
* WARNING: Only use if you are certain `pos` is lower than the array length.
480+
*/
481+
function unsafeMemoryAccess(bytes[] memory arr, uint256 pos) internal pure returns (bytes memory res) {
482+
assembly {
483+
res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
484+
}
485+
}
486+
487+
/**
488+
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
489+
*
490+
* WARNING: Only use if you are certain `pos` is lower than the array length.
491+
*/
492+
function unsafeMemoryAccess(string[] memory arr, uint256 pos) internal pure returns (string memory res) {
493+
assembly {
494+
res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
495+
}
496+
}
497+
450498
/**
451499
* @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden.
452500
*
@@ -479,4 +527,26 @@ library Arrays {
479527
sstore(array.slot, len)
480528
}
481529
}
530+
531+
/**
532+
* @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden.
533+
*
534+
* WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
535+
*/
536+
function unsafeSetLength(bytes[] storage array, uint256 len) internal {
537+
assembly ("memory-safe") {
538+
sstore(array.slot, len)
539+
}
540+
}
541+
542+
/**
543+
* @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden.
544+
*
545+
* WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
546+
*/
547+
function unsafeSetLength(string[] storage array, uint256 len) internal {
548+
assembly ("memory-safe") {
549+
sstore(array.slot, len)
550+
}
551+
}
482552
}

scripts/generate/templates/Arrays.js

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {Math} from "./math/Math.sol";
1717

1818
const sort = type => `\
1919
/**
20-
* @dev Sort an array of ${type} (in memory) following the provided comparator function.
20+
* @dev Sort an array of ${type.name} (in memory) following the provided comparator function.
2121
*
2222
* This function does the sorting "in place", meaning that it overrides the input. The object is returned for
2323
* convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array.
@@ -30,22 +30,22 @@ const sort = type => `\
3030
* IMPORTANT: Consider memory side-effects when using custom comparator functions that access memory in an unsafe way.
3131
*/
3232
function sort(
33-
${type}[] memory array,
34-
function(${type}, ${type}) pure returns (bool) comp
35-
) internal pure returns (${type}[] memory) {
33+
${type.name}[] memory array,
34+
function(${type.name}, ${type.name}) pure returns (bool) comp
35+
) internal pure returns (${type.name}[] memory) {
3636
${
37-
type === 'uint256'
37+
type.name === 'uint256'
3838
? '_quickSort(_begin(array), _end(array), comp);'
3939
: 'sort(_castToUint256Array(array), _castToUint256Comp(comp));'
4040
}
4141
return array;
4242
}
4343
4444
/**
45-
* @dev Variant of {sort} that sorts an array of ${type} in increasing order.
45+
* @dev Variant of {sort} that sorts an array of ${type.name} in increasing order.
4646
*/
47-
function sort(${type}[] memory array) internal pure returns (${type}[] memory) {
48-
${type === 'uint256' ? 'sort(array, Comparators.lt);' : 'sort(_castToUint256Array(array), Comparators.lt);'}
47+
function sort(${type.name}[] memory array) internal pure returns (${type.name}[] memory) {
48+
${type.name === 'uint256' ? 'sort(array, Comparators.lt);' : 'sort(_castToUint256Array(array), Comparators.lt);'}
4949
return array;
5050
}
5151
`;
@@ -126,18 +126,18 @@ function _swap(uint256 ptr1, uint256 ptr2) private pure {
126126
`;
127127

128128
const castArray = type => `\
129-
/// @dev Helper: low level cast ${type} memory array to uint256 memory array
130-
function _castToUint256Array(${type}[] memory input) private pure returns (uint256[] memory output) {
129+
/// @dev Helper: low level cast ${type.name} memory array to uint256 memory array
130+
function _castToUint256Array(${type.name}[] memory input) private pure returns (uint256[] memory output) {
131131
assembly {
132132
output := input
133133
}
134134
}
135135
`;
136136

137137
const castComparator = type => `\
138-
/// @dev Helper: low level cast ${type} comp function to uint256 comp function
138+
/// @dev Helper: low level cast ${type.name} comp function to uint256 comp function
139139
function _castToUint256Comp(
140-
function(${type}, ${type}) pure returns (bool) input
140+
function(${type.name}, ${type.name}) pure returns (bool) input
141141
) private pure returns (function(uint256, uint256) pure returns (bool) output) {
142142
assembly {
143143
output := input
@@ -320,14 +320,14 @@ const unsafeAccessStorage = type => `\
320320
*
321321
* WARNING: Only use if you are certain \`pos\` is lower than the array length.
322322
*/
323-
function unsafeAccess(${type}[] storage arr, uint256 pos) internal pure returns (StorageSlot.${capitalize(
324-
type,
323+
function unsafeAccess(${type.name}[] storage arr, uint256 pos) internal pure returns (StorageSlot.${capitalize(
324+
type.name,
325325
)}Slot storage) {
326326
bytes32 slot;
327327
assembly ("memory-safe") {
328328
slot := arr.slot
329329
}
330-
return slot.deriveArray().offset(pos).get${capitalize(type)}Slot();
330+
return slot.deriveArray().offset(pos).get${capitalize(type.name)}Slot();
331331
}
332332
`;
333333

@@ -337,7 +337,9 @@ const unsafeAccessMemory = type => `\
337337
*
338338
* WARNING: Only use if you are certain \`pos\` is lower than the array length.
339339
*/
340-
function unsafeMemoryAccess(${type}[] memory arr, uint256 pos) internal pure returns (${type} res) {
340+
function unsafeMemoryAccess(${type.name}[] memory arr, uint256 pos) internal pure returns (${type.name}${
341+
type.isValueType ? '' : ' memory'
342+
} res) {
341343
assembly {
342344
res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
343345
}
@@ -350,7 +352,7 @@ const unsafeSetLength = type => `\
350352
*
351353
* WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
352354
*/
353-
function unsafeSetLength(${type}[] storage array, uint256 len) internal {
355+
function unsafeSetLength(${type.name}[] storage array, uint256 len) internal {
354356
assembly ("memory-safe") {
355357
sstore(array.slot, len)
356358
}
@@ -367,11 +369,11 @@ module.exports = format(
367369
'using StorageSlot for bytes32;',
368370
'',
369371
// sorting, comparator, helpers and internal
370-
sort('uint256'),
371-
TYPES.filter(type => type !== 'uint256').map(sort),
372+
sort({ name: 'uint256' }),
373+
TYPES.filter(type => type.isValueType && type.name !== 'uint256').map(sort),
372374
quickSort,
373-
TYPES.filter(type => type !== 'uint256').map(castArray),
374-
TYPES.filter(type => type !== 'uint256').map(castComparator),
375+
TYPES.filter(type => type.isValueType && type.name !== 'uint256').map(castArray),
376+
TYPES.filter(type => type.isValueType && type.name !== 'uint256').map(castComparator),
375377
// lookup
376378
search,
377379
// unsafe (direct) storage and memory access
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1-
const TYPES = ['address', 'bytes32', 'uint256'];
1+
const TYPES = [
2+
{ name: 'address', isValueType: true },
3+
{ name: 'bytes32', isValueType: true },
4+
{ name: 'uint256', isValueType: true },
5+
{ name: 'bytes', isValueType: false },
6+
{ name: 'string', isValueType: false },
7+
];
28

39
module.exports = { TYPES };

test/helpers/random.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,19 @@ const generators = {
55
bytes32: () => ethers.hexlify(ethers.randomBytes(32)),
66
uint256: () => ethers.toBigInt(ethers.randomBytes(32)),
77
int256: () => ethers.toBigInt(ethers.randomBytes(32)) + ethers.MinInt256,
8-
hexBytes: length => ethers.hexlify(ethers.randomBytes(length)),
8+
bytes: (length = 32) => ethers.hexlify(ethers.randomBytes(length)),
9+
string: () => ethers.uuidV4(ethers.randomBytes(32)),
910
};
1011

1112
generators.address.zero = ethers.ZeroAddress;
1213
generators.bytes32.zero = ethers.ZeroHash;
1314
generators.uint256.zero = 0n;
1415
generators.int256.zero = 0n;
15-
generators.hexBytes.zero = '0x';
16+
generators.bytes.zero = '0x';
17+
generators.string.zero = '';
18+
19+
// alias hexBytes -> bytes
20+
generators.hexBytes = generators.bytes;
1621

1722
module.exports = {
1823
generators,

0 commit comments

Comments
 (0)