Skip to content

Commit 9d96210

Browse files
ernestognwAmxx
andauthored
Add enumerable data structures for bytes4 (#6091)
Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>
1 parent 0e8e34a commit 9d96210

File tree

9 files changed

+248
-0
lines changed

9 files changed

+248
-0
lines changed

.changeset/bright-webs-create.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+
`EnumerableMap`: Add support for `Bytes4ToAddressMap` types.

.changeset/thick-banks-relate.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+
`EnumerableSet`: Add support for `Bytes4Set` type.

contracts/utils/structs/EnumerableMap.sol

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {EnumerableSet} from "./EnumerableSet.sol";
4040
* - `address -> bytes32` (`AddressToBytes32Map`) since v5.1.0
4141
* - `bytes32 -> address` (`Bytes32ToAddressMap`) since v5.1.0
4242
* - `bytes -> bytes` (`BytesToBytesMap`) since v5.4.0
43+
* - `bytes4 -> address` (`Bytes4ToAddressMap`) since v5.6.0
4344
*
4445
* [WARNING]
4546
* ====
@@ -1187,6 +1188,129 @@ library EnumerableMap {
11871188
return result;
11881189
}
11891190

1191+
// Bytes4ToAddressMap
1192+
1193+
struct Bytes4ToAddressMap {
1194+
Bytes32ToBytes32Map _inner;
1195+
}
1196+
1197+
/**
1198+
* @dev Adds a key-value pair to a map, or updates the value for an existing
1199+
* key. O(1).
1200+
*
1201+
* Returns true if the key was added to the map, that is if it was not
1202+
* already present.
1203+
*/
1204+
function set(Bytes4ToAddressMap storage map, bytes4 key, address value) internal returns (bool) {
1205+
return set(map._inner, bytes32(key), bytes32(uint256(uint160(value))));
1206+
}
1207+
1208+
/**
1209+
* @dev Removes a value from a map. O(1).
1210+
*
1211+
* Returns true if the key was removed from the map, that is if it was present.
1212+
*/
1213+
function remove(Bytes4ToAddressMap storage map, bytes4 key) internal returns (bool) {
1214+
return remove(map._inner, bytes32(key));
1215+
}
1216+
1217+
/**
1218+
* @dev Removes all the entries from a map. O(n).
1219+
*
1220+
* WARNING: This function has an unbounded cost that scales with map size. Developers should keep in mind that
1221+
* using it may render the function uncallable if the map grows to the point where clearing it consumes too much
1222+
* gas to fit in a block.
1223+
*/
1224+
function clear(Bytes4ToAddressMap storage map) internal {
1225+
clear(map._inner);
1226+
}
1227+
1228+
/**
1229+
* @dev Returns true if the key is in the map. O(1).
1230+
*/
1231+
function contains(Bytes4ToAddressMap storage map, bytes4 key) internal view returns (bool) {
1232+
return contains(map._inner, bytes32(key));
1233+
}
1234+
1235+
/**
1236+
* @dev Returns the number of elements in the map. O(1).
1237+
*/
1238+
function length(Bytes4ToAddressMap storage map) internal view returns (uint256) {
1239+
return length(map._inner);
1240+
}
1241+
1242+
/**
1243+
* @dev Returns the element stored at position `index` in the map. O(1).
1244+
* Note that there are no guarantees on the ordering of values inside the
1245+
* array, and it may change when more values are added or removed.
1246+
*
1247+
* Requirements:
1248+
*
1249+
* - `index` must be strictly less than {length}.
1250+
*/
1251+
function at(Bytes4ToAddressMap storage map, uint256 index) internal view returns (bytes4 key, address value) {
1252+
(bytes32 atKey, bytes32 val) = at(map._inner, index);
1253+
return (bytes4(atKey), address(uint160(uint256(val))));
1254+
}
1255+
1256+
/**
1257+
* @dev Tries to return the value associated with `key`. O(1).
1258+
* Does not revert if `key` is not in the map.
1259+
*/
1260+
function tryGet(Bytes4ToAddressMap storage map, bytes4 key) internal view returns (bool exists, address value) {
1261+
(bool success, bytes32 val) = tryGet(map._inner, bytes32(key));
1262+
return (success, address(uint160(uint256(val))));
1263+
}
1264+
1265+
/**
1266+
* @dev Returns the value associated with `key`. O(1).
1267+
*
1268+
* Requirements:
1269+
*
1270+
* - `key` must be in the map.
1271+
*/
1272+
function get(Bytes4ToAddressMap storage map, bytes4 key) internal view returns (address) {
1273+
return address(uint160(uint256(get(map._inner, bytes32(key)))));
1274+
}
1275+
1276+
/**
1277+
* @dev Returns an array containing all the keys
1278+
*
1279+
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
1280+
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
1281+
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
1282+
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
1283+
*/
1284+
function keys(Bytes4ToAddressMap storage map) internal view returns (bytes4[] memory) {
1285+
bytes32[] memory store = keys(map._inner);
1286+
bytes4[] memory result;
1287+
1288+
assembly ("memory-safe") {
1289+
result := store
1290+
}
1291+
1292+
return result;
1293+
}
1294+
1295+
/**
1296+
* @dev Returns an array containing a slice of the keys
1297+
*
1298+
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
1299+
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
1300+
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
1301+
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
1302+
*/
1303+
function keys(Bytes4ToAddressMap storage map, uint256 start, uint256 end) internal view returns (bytes4[] memory) {
1304+
bytes32[] memory store = keys(map._inner, start, end);
1305+
bytes4[] memory result;
1306+
1307+
assembly ("memory-safe") {
1308+
result := store
1309+
}
1310+
1311+
return result;
1312+
}
1313+
11901314
/**
11911315
* @dev Query for a nonexistent map key.
11921316
*/

contracts/utils/structs/EnumerableSet.sol

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {Math} from "../math/Math.sol";
3636
* - `uint256` (`UintSet`) since v3.3.0
3737
* - `string` (`StringSet`) since v5.4.0
3838
* - `bytes` (`BytesSet`) since v5.4.0
39+
* - `bytes4` (`Bytes4Set`) since v5.6.0
3940
*
4041
* [WARNING]
4142
* ====
@@ -302,6 +303,108 @@ library EnumerableSet {
302303
return result;
303304
}
304305

306+
// Bytes4Set
307+
308+
struct Bytes4Set {
309+
Set _inner;
310+
}
311+
312+
/**
313+
* @dev Add a value to a set. O(1).
314+
*
315+
* Returns true if the value was added to the set, that is if it was not
316+
* already present.
317+
*/
318+
function add(Bytes4Set storage set, bytes4 value) internal returns (bool) {
319+
return _add(set._inner, bytes32(value));
320+
}
321+
322+
/**
323+
* @dev Removes a value from a set. O(1).
324+
*
325+
* Returns true if the value was removed from the set, that is if it was
326+
* present.
327+
*/
328+
function remove(Bytes4Set storage set, bytes4 value) internal returns (bool) {
329+
return _remove(set._inner, bytes32(value));
330+
}
331+
332+
/**
333+
* @dev Removes all the values from a set. O(n).
334+
*
335+
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
336+
* function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
337+
*/
338+
function clear(Bytes4Set storage set) internal {
339+
_clear(set._inner);
340+
}
341+
342+
/**
343+
* @dev Returns true if the value is in the set. O(1).
344+
*/
345+
function contains(Bytes4Set storage set, bytes4 value) internal view returns (bool) {
346+
return _contains(set._inner, bytes32(value));
347+
}
348+
349+
/**
350+
* @dev Returns the number of values in the set. O(1).
351+
*/
352+
function length(Bytes4Set storage set) internal view returns (uint256) {
353+
return _length(set._inner);
354+
}
355+
356+
/**
357+
* @dev Returns the value stored at position `index` in the set. O(1).
358+
*
359+
* Note that there are no guarantees on the ordering of values inside the
360+
* array, and it may change when more values are added or removed.
361+
*
362+
* Requirements:
363+
*
364+
* - `index` must be strictly less than {length}.
365+
*/
366+
function at(Bytes4Set storage set, uint256 index) internal view returns (bytes4) {
367+
return bytes4(_at(set._inner, index));
368+
}
369+
370+
/**
371+
* @dev Return the entire set in an array
372+
*
373+
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
374+
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
375+
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
376+
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
377+
*/
378+
function values(Bytes4Set storage set) internal view returns (bytes4[] memory) {
379+
bytes32[] memory store = _values(set._inner);
380+
bytes4[] memory result;
381+
382+
assembly ("memory-safe") {
383+
result := store
384+
}
385+
386+
return result;
387+
}
388+
389+
/**
390+
* @dev Return a slice of the set in an array
391+
*
392+
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
393+
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
394+
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
395+
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
396+
*/
397+
function values(Bytes4Set storage set, uint256 start, uint256 end) internal view returns (bytes4[] memory) {
398+
bytes32[] memory store = _values(set._inner, start, end);
399+
bytes4[] memory result;
400+
401+
assembly ("memory-safe") {
402+
result := store
403+
}
404+
405+
return result;
406+
}
407+
305408
// AddressSet
306409

307410
struct AddressSet {

scripts/generate/templates/Enumerable.opts.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const toMapTypeDescr = ({ key, value }) => ({
2424

2525
const SET_TYPES = [
2626
{ type: 'bytes32' },
27+
{ type: 'bytes4' },
2728
{ type: 'address' },
2829
{ type: 'uint256' },
2930
{ type: 'string', memory: true },
@@ -38,6 +39,8 @@ const MAP_TYPES = []
3839
['uint256', 'address', 'bytes32']
3940
.flatMap((keyType, _, array) => array.map(valueType => ({ key: { type: keyType }, value: { type: valueType } })))
4041
.slice(0, -1), // remove bytes32 → bytes32 (last one) that is already defined
42+
// other value type maps
43+
{ key: { type: 'bytes4' }, value: { type: 'address' } },
4144
// non-value type maps
4245
{ key: { type: 'bytes', memory: true }, value: { type: 'bytes', memory: true } },
4346
)

scripts/generate/templates/EnumerableMap.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {EnumerableSet} from "./EnumerableSet.sol";
4141
* - \`address -> bytes32\` (\`AddressToBytes32Map\`) since v5.1.0
4242
* - \`bytes32 -> address\` (\`Bytes32ToAddressMap\`) since v5.1.0
4343
* - \`bytes -> bytes\` (\`BytesToBytesMap\`) since v5.4.0
44+
* - \`bytes4 -> address\` (\`Bytes4ToAddressMap\`) since v5.6.0
4445
*
4546
* [WARNING]
4647
* ====

scripts/generate/templates/EnumerableSet.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {Math} from "../math/Math.sol";
3737
* - \`uint256\` (\`UintSet\`) since v3.3.0
3838
* - \`string\` (\`StringSet\`) since v5.4.0
3939
* - \`bytes\` (\`BytesSet\`) since v5.4.0
40+
* - \`bytes4\` (\`Bytes4Set\`) since v5.6.0
4041
*
4142
* [WARNING]
4243
* ====

scripts/generate/templates/conversion.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ function toBytes32(type, value) {
22
switch (type) {
33
case 'bytes32':
44
return value;
5+
case 'bytes4':
6+
return `bytes32(${value})`;
57
case 'uint256':
68
return `bytes32(${value})`;
79
case 'address':
@@ -15,6 +17,8 @@ function fromBytes32(type, value) {
1517
switch (type) {
1618
case 'bytes32':
1719
return value;
20+
case 'bytes4':
21+
return `bytes4(${value})`;
1822
case 'uint256':
1923
return `uint256(${value})`;
2024
case 'address':

test/helpers/random.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const { ethers } = require('hardhat');
33
const generators = {
44
address: () => ethers.Wallet.createRandom().address,
55
bytes32: () => ethers.hexlify(ethers.randomBytes(32)),
6+
bytes4: () => ethers.hexlify(ethers.randomBytes(4)),
67
uint256: () => ethers.toBigInt(ethers.randomBytes(32)),
78
int256: () => ethers.toBigInt(ethers.randomBytes(32)) + ethers.MinInt256,
89
bytes: (length = 32) => ethers.hexlify(ethers.randomBytes(length)),
@@ -11,6 +12,7 @@ const generators = {
1112

1213
generators.address.zero = ethers.ZeroAddress;
1314
generators.bytes32.zero = ethers.ZeroHash;
15+
generators.bytes4.zero = ethers.zeroPadBytes('0x', 4);
1416
generators.uint256.zero = 0n;
1517
generators.int256.zero = 0n;
1618
generators.bytes.zero = '0x';

0 commit comments

Comments
 (0)