diff --git a/.changeset/bright-webs-create.md b/.changeset/bright-webs-create.md new file mode 100644 index 00000000000..f23b2969297 --- /dev/null +++ b/.changeset/bright-webs-create.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`EnumerableMap`: Add support for `UintToBytes4Map`, `AddressToBytes4Map`, `Bytes4ToUintMap`, `Bytes4ToAddressMap`, `Bytes4ToBytes4Map`, `Bytes4ToBytes32Map`, and `Bytes32ToBytes4Map` types. diff --git a/.changeset/thick-banks-relate.md b/.changeset/thick-banks-relate.md new file mode 100644 index 00000000000..3fcbf07cfad --- /dev/null +++ b/.changeset/thick-banks-relate.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`EnumerableSet`: Add support for `Bytes4Set` type. diff --git a/contracts/utils/structs/EnumerableMap.sol b/contracts/utils/structs/EnumerableMap.sol index 3173623b92f..181d2f2df09 100644 --- a/contracts/utils/structs/EnumerableMap.sol +++ b/contracts/utils/structs/EnumerableMap.sol @@ -40,6 +40,13 @@ import {EnumerableSet} from "./EnumerableSet.sol"; * - `address -> bytes32` (`AddressToBytes32Map`) since v5.1.0 * - `bytes32 -> address` (`Bytes32ToAddressMap`) since v5.1.0 * - `bytes -> bytes` (`BytesToBytesMap`) since v5.4.0 + * - `uint256 -> bytes4` (`UintToBytes4Map`) since v5.6.0 + * - `address -> bytes4` (`AddressToBytes4Map`) since v5.6.0 + * - `bytes4 -> uint256` (`Bytes4ToUintMap`) since v5.6.0 + * - `bytes4 -> address` (`Bytes4ToAddressMap`) since v5.6.0 + * - `bytes4 -> bytes4` (`Bytes4ToBytes4Map`) since v5.6.0 + * - `bytes4 -> bytes32` (`Bytes4ToBytes32Map`) since v5.6.0 + * - `bytes32 -> bytes4` (`Bytes32ToBytes4Map`) since v5.6.0 * * [WARNING] * ==== @@ -437,6 +444,129 @@ library EnumerableMap { return result; } + // UintToBytes4Map + + struct UintToBytes4Map { + Bytes32ToBytes32Map _inner; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function set(UintToBytes4Map storage map, uint256 key, bytes4 value) internal returns (bool) { + return set(map._inner, bytes32(key), bytes32(value)); + } + + /** + * @dev Removes a value from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(UintToBytes4Map storage map, uint256 key) internal returns (bool) { + return remove(map._inner, bytes32(key)); + } + + /** + * @dev Removes all the entries from a map. O(n). + * + * WARNING: This function has an unbounded cost that scales with map size. Developers should keep in mind that + * using it may render the function uncallable if the map grows to the point where clearing it consumes too much + * gas to fit in a block. + */ + function clear(UintToBytes4Map storage map) internal { + clear(map._inner); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(UintToBytes4Map storage map, uint256 key) internal view returns (bool) { + return contains(map._inner, bytes32(key)); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(UintToBytes4Map storage map) internal view returns (uint256) { + return length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the map. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(UintToBytes4Map storage map, uint256 index) internal view returns (uint256 key, bytes4 value) { + (bytes32 atKey, bytes32 val) = at(map._inner, index); + return (uint256(atKey), bytes4(val)); + } + + /** + * @dev Tries to return the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(UintToBytes4Map storage map, uint256 key) internal view returns (bool exists, bytes4 value) { + (bool success, bytes32 val) = tryGet(map._inner, bytes32(key)); + return (success, bytes4(val)); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(UintToBytes4Map storage map, uint256 key) internal view returns (bytes4) { + return bytes4(get(map._inner, bytes32(key))); + } + + /** + * @dev Returns an array containing all the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(UintToBytes4Map storage map) internal view returns (uint256[] memory) { + bytes32[] memory store = keys(map._inner); + uint256[] memory result; + + assembly ("memory-safe") { + result := store + } + + return result; + } + + /** + * @dev Returns an array containing a slice of the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(UintToBytes4Map storage map, uint256 start, uint256 end) internal view returns (uint256[] memory) { + bytes32[] memory store = keys(map._inner, start, end); + uint256[] memory result; + + assembly ("memory-safe") { + result := store + } + + return result; + } + // UintToBytes32Map struct UintToBytes32Map { @@ -810,9 +940,9 @@ library EnumerableMap { return result; } - // AddressToBytes32Map + // AddressToBytes4Map - struct AddressToBytes32Map { + struct AddressToBytes4Map { Bytes32ToBytes32Map _inner; } @@ -823,8 +953,8 @@ library EnumerableMap { * Returns true if the key was added to the map, that is if it was not * already present. */ - function set(AddressToBytes32Map storage map, address key, bytes32 value) internal returns (bool) { - return set(map._inner, bytes32(uint256(uint160(key))), value); + function set(AddressToBytes4Map storage map, address key, bytes4 value) internal returns (bool) { + return set(map._inner, bytes32(uint256(uint160(key))), bytes32(value)); } /** @@ -832,7 +962,7 @@ library EnumerableMap { * * Returns true if the key was removed from the map, that is if it was present. */ - function remove(AddressToBytes32Map storage map, address key) internal returns (bool) { + function remove(AddressToBytes4Map storage map, address key) internal returns (bool) { return remove(map._inner, bytes32(uint256(uint160(key)))); } @@ -843,21 +973,21 @@ library EnumerableMap { * using it may render the function uncallable if the map grows to the point where clearing it consumes too much * gas to fit in a block. */ - function clear(AddressToBytes32Map storage map) internal { + function clear(AddressToBytes4Map storage map) internal { clear(map._inner); } /** * @dev Returns true if the key is in the map. O(1). */ - function contains(AddressToBytes32Map storage map, address key) internal view returns (bool) { + function contains(AddressToBytes4Map storage map, address key) internal view returns (bool) { return contains(map._inner, bytes32(uint256(uint160(key)))); } /** * @dev Returns the number of elements in the map. O(1). */ - function length(AddressToBytes32Map storage map) internal view returns (uint256) { + function length(AddressToBytes4Map storage map) internal view returns (uint256) { return length(map._inner); } @@ -870,18 +1000,18 @@ library EnumerableMap { * * - `index` must be strictly less than {length}. */ - function at(AddressToBytes32Map storage map, uint256 index) internal view returns (address key, bytes32 value) { + function at(AddressToBytes4Map storage map, uint256 index) internal view returns (address key, bytes4 value) { (bytes32 atKey, bytes32 val) = at(map._inner, index); - return (address(uint160(uint256(atKey))), val); + return (address(uint160(uint256(atKey))), bytes4(val)); } /** * @dev Tries to return the value associated with `key`. O(1). * Does not revert if `key` is not in the map. */ - function tryGet(AddressToBytes32Map storage map, address key) internal view returns (bool exists, bytes32 value) { + function tryGet(AddressToBytes4Map storage map, address key) internal view returns (bool exists, bytes4 value) { (bool success, bytes32 val) = tryGet(map._inner, bytes32(uint256(uint160(key)))); - return (success, val); + return (success, bytes4(val)); } /** @@ -891,8 +1021,8 @@ library EnumerableMap { * * - `key` must be in the map. */ - function get(AddressToBytes32Map storage map, address key) internal view returns (bytes32) { - return get(map._inner, bytes32(uint256(uint160(key)))); + function get(AddressToBytes4Map storage map, address key) internal view returns (bytes4) { + return bytes4(get(map._inner, bytes32(uint256(uint160(key))))); } /** @@ -903,7 +1033,7 @@ library EnumerableMap { * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. */ - function keys(AddressToBytes32Map storage map) internal view returns (address[] memory) { + function keys(AddressToBytes4Map storage map) internal view returns (address[] memory) { bytes32[] memory store = keys(map._inner); address[] memory result; @@ -922,11 +1052,7 @@ library EnumerableMap { * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. */ - function keys( - AddressToBytes32Map storage map, - uint256 start, - uint256 end - ) internal view returns (address[] memory) { + function keys(AddressToBytes4Map storage map, uint256 start, uint256 end) internal view returns (address[] memory) { bytes32[] memory store = keys(map._inner, start, end); address[] memory result; @@ -937,9 +1063,9 @@ library EnumerableMap { return result; } - // Bytes32ToUintMap + // AddressToBytes32Map - struct Bytes32ToUintMap { + struct AddressToBytes32Map { Bytes32ToBytes32Map _inner; } @@ -950,8 +1076,8 @@ library EnumerableMap { * Returns true if the key was added to the map, that is if it was not * already present. */ - function set(Bytes32ToUintMap storage map, bytes32 key, uint256 value) internal returns (bool) { - return set(map._inner, key, bytes32(value)); + function set(AddressToBytes32Map storage map, address key, bytes32 value) internal returns (bool) { + return set(map._inner, bytes32(uint256(uint160(key))), value); } /** @@ -959,8 +1085,8 @@ library EnumerableMap { * * Returns true if the key was removed from the map, that is if it was present. */ - function remove(Bytes32ToUintMap storage map, bytes32 key) internal returns (bool) { - return remove(map._inner, key); + function remove(AddressToBytes32Map storage map, address key) internal returns (bool) { + return remove(map._inner, bytes32(uint256(uint160(key)))); } /** @@ -970,21 +1096,21 @@ library EnumerableMap { * using it may render the function uncallable if the map grows to the point where clearing it consumes too much * gas to fit in a block. */ - function clear(Bytes32ToUintMap storage map) internal { + function clear(AddressToBytes32Map storage map) internal { clear(map._inner); } /** * @dev Returns true if the key is in the map. O(1). */ - function contains(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool) { - return contains(map._inner, key); + function contains(AddressToBytes32Map storage map, address key) internal view returns (bool) { + return contains(map._inner, bytes32(uint256(uint160(key)))); } /** * @dev Returns the number of elements in the map. O(1). */ - function length(Bytes32ToUintMap storage map) internal view returns (uint256) { + function length(AddressToBytes32Map storage map) internal view returns (uint256) { return length(map._inner); } @@ -997,18 +1123,18 @@ library EnumerableMap { * * - `index` must be strictly less than {length}. */ - function at(Bytes32ToUintMap storage map, uint256 index) internal view returns (bytes32 key, uint256 value) { + function at(AddressToBytes32Map storage map, uint256 index) internal view returns (address key, bytes32 value) { (bytes32 atKey, bytes32 val) = at(map._inner, index); - return (atKey, uint256(val)); + return (address(uint160(uint256(atKey))), val); } /** * @dev Tries to return the value associated with `key`. O(1). * Does not revert if `key` is not in the map. */ - function tryGet(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool exists, uint256 value) { - (bool success, bytes32 val) = tryGet(map._inner, key); - return (success, uint256(val)); + function tryGet(AddressToBytes32Map storage map, address key) internal view returns (bool exists, bytes32 value) { + (bool success, bytes32 val) = tryGet(map._inner, bytes32(uint256(uint160(key)))); + return (success, val); } /** @@ -1018,8 +1144,8 @@ library EnumerableMap { * * - `key` must be in the map. */ - function get(Bytes32ToUintMap storage map, bytes32 key) internal view returns (uint256) { - return uint256(get(map._inner, key)); + function get(AddressToBytes32Map storage map, address key) internal view returns (bytes32) { + return get(map._inner, bytes32(uint256(uint160(key)))); } /** @@ -1030,9 +1156,9 @@ library EnumerableMap { * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. */ - function keys(Bytes32ToUintMap storage map) internal view returns (bytes32[] memory) { + function keys(AddressToBytes32Map storage map) internal view returns (address[] memory) { bytes32[] memory store = keys(map._inner); - bytes32[] memory result; + address[] memory result; assembly ("memory-safe") { result := store @@ -1049,9 +1175,13 @@ library EnumerableMap { * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. */ - function keys(Bytes32ToUintMap storage map, uint256 start, uint256 end) internal view returns (bytes32[] memory) { + function keys( + AddressToBytes32Map storage map, + uint256 start, + uint256 end + ) internal view returns (address[] memory) { bytes32[] memory store = keys(map._inner, start, end); - bytes32[] memory result; + address[] memory result; assembly ("memory-safe") { result := store @@ -1060,9 +1190,9 @@ library EnumerableMap { return result; } - // Bytes32ToAddressMap + // Bytes4ToUintMap - struct Bytes32ToAddressMap { + struct Bytes4ToUintMap { Bytes32ToBytes32Map _inner; } @@ -1073,8 +1203,8 @@ library EnumerableMap { * Returns true if the key was added to the map, that is if it was not * already present. */ - function set(Bytes32ToAddressMap storage map, bytes32 key, address value) internal returns (bool) { - return set(map._inner, key, bytes32(uint256(uint160(value)))); + function set(Bytes4ToUintMap storage map, bytes4 key, uint256 value) internal returns (bool) { + return set(map._inner, bytes32(key), bytes32(value)); } /** @@ -1082,8 +1212,8 @@ library EnumerableMap { * * Returns true if the key was removed from the map, that is if it was present. */ - function remove(Bytes32ToAddressMap storage map, bytes32 key) internal returns (bool) { - return remove(map._inner, key); + function remove(Bytes4ToUintMap storage map, bytes4 key) internal returns (bool) { + return remove(map._inner, bytes32(key)); } /** @@ -1093,21 +1223,21 @@ library EnumerableMap { * using it may render the function uncallable if the map grows to the point where clearing it consumes too much * gas to fit in a block. */ - function clear(Bytes32ToAddressMap storage map) internal { + function clear(Bytes4ToUintMap storage map) internal { clear(map._inner); } /** * @dev Returns true if the key is in the map. O(1). */ - function contains(Bytes32ToAddressMap storage map, bytes32 key) internal view returns (bool) { - return contains(map._inner, key); + function contains(Bytes4ToUintMap storage map, bytes4 key) internal view returns (bool) { + return contains(map._inner, bytes32(key)); } /** * @dev Returns the number of elements in the map. O(1). */ - function length(Bytes32ToAddressMap storage map) internal view returns (uint256) { + function length(Bytes4ToUintMap storage map) internal view returns (uint256) { return length(map._inner); } @@ -1120,18 +1250,18 @@ library EnumerableMap { * * - `index` must be strictly less than {length}. */ - function at(Bytes32ToAddressMap storage map, uint256 index) internal view returns (bytes32 key, address value) { + function at(Bytes4ToUintMap storage map, uint256 index) internal view returns (bytes4 key, uint256 value) { (bytes32 atKey, bytes32 val) = at(map._inner, index); - return (atKey, address(uint160(uint256(val)))); + return (bytes4(atKey), uint256(val)); } /** * @dev Tries to return the value associated with `key`. O(1). * Does not revert if `key` is not in the map. */ - function tryGet(Bytes32ToAddressMap storage map, bytes32 key) internal view returns (bool exists, address value) { - (bool success, bytes32 val) = tryGet(map._inner, key); - return (success, address(uint160(uint256(val)))); + function tryGet(Bytes4ToUintMap storage map, bytes4 key) internal view returns (bool exists, uint256 value) { + (bool success, bytes32 val) = tryGet(map._inner, bytes32(key)); + return (success, uint256(val)); } /** @@ -1141,8 +1271,8 @@ library EnumerableMap { * * - `key` must be in the map. */ - function get(Bytes32ToAddressMap storage map, bytes32 key) internal view returns (address) { - return address(uint160(uint256(get(map._inner, key)))); + function get(Bytes4ToUintMap storage map, bytes4 key) internal view returns (uint256) { + return uint256(get(map._inner, bytes32(key))); } /** @@ -1153,9 +1283,9 @@ library EnumerableMap { * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. */ - function keys(Bytes32ToAddressMap storage map) internal view returns (bytes32[] memory) { + function keys(Bytes4ToUintMap storage map) internal view returns (bytes4[] memory) { bytes32[] memory store = keys(map._inner); - bytes32[] memory result; + bytes4[] memory result; assembly ("memory-safe") { result := store @@ -1172,11 +1302,749 @@ library EnumerableMap { * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. */ - function keys( - Bytes32ToAddressMap storage map, - uint256 start, - uint256 end - ) internal view returns (bytes32[] memory) { + function keys(Bytes4ToUintMap storage map, uint256 start, uint256 end) internal view returns (bytes4[] memory) { + bytes32[] memory store = keys(map._inner, start, end); + bytes4[] memory result; + + assembly ("memory-safe") { + result := store + } + + return result; + } + + // Bytes4ToAddressMap + + struct Bytes4ToAddressMap { + Bytes32ToBytes32Map _inner; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function set(Bytes4ToAddressMap storage map, bytes4 key, address value) internal returns (bool) { + return set(map._inner, bytes32(key), bytes32(uint256(uint160(value)))); + } + + /** + * @dev Removes a value from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(Bytes4ToAddressMap storage map, bytes4 key) internal returns (bool) { + return remove(map._inner, bytes32(key)); + } + + /** + * @dev Removes all the entries from a map. O(n). + * + * WARNING: This function has an unbounded cost that scales with map size. Developers should keep in mind that + * using it may render the function uncallable if the map grows to the point where clearing it consumes too much + * gas to fit in a block. + */ + function clear(Bytes4ToAddressMap storage map) internal { + clear(map._inner); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(Bytes4ToAddressMap storage map, bytes4 key) internal view returns (bool) { + return contains(map._inner, bytes32(key)); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(Bytes4ToAddressMap storage map) internal view returns (uint256) { + return length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the map. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(Bytes4ToAddressMap storage map, uint256 index) internal view returns (bytes4 key, address value) { + (bytes32 atKey, bytes32 val) = at(map._inner, index); + return (bytes4(atKey), address(uint160(uint256(val)))); + } + + /** + * @dev Tries to return the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(Bytes4ToAddressMap storage map, bytes4 key) internal view returns (bool exists, address value) { + (bool success, bytes32 val) = tryGet(map._inner, bytes32(key)); + return (success, address(uint160(uint256(val)))); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(Bytes4ToAddressMap storage map, bytes4 key) internal view returns (address) { + return address(uint160(uint256(get(map._inner, bytes32(key))))); + } + + /** + * @dev Returns an array containing all the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(Bytes4ToAddressMap storage map) internal view returns (bytes4[] memory) { + bytes32[] memory store = keys(map._inner); + bytes4[] memory result; + + assembly ("memory-safe") { + result := store + } + + return result; + } + + /** + * @dev Returns an array containing a slice of the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(Bytes4ToAddressMap storage map, uint256 start, uint256 end) internal view returns (bytes4[] memory) { + bytes32[] memory store = keys(map._inner, start, end); + bytes4[] memory result; + + assembly ("memory-safe") { + result := store + } + + return result; + } + + // Bytes4ToBytes4Map + + struct Bytes4ToBytes4Map { + Bytes32ToBytes32Map _inner; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function set(Bytes4ToBytes4Map storage map, bytes4 key, bytes4 value) internal returns (bool) { + return set(map._inner, bytes32(key), bytes32(value)); + } + + /** + * @dev Removes a value from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(Bytes4ToBytes4Map storage map, bytes4 key) internal returns (bool) { + return remove(map._inner, bytes32(key)); + } + + /** + * @dev Removes all the entries from a map. O(n). + * + * WARNING: This function has an unbounded cost that scales with map size. Developers should keep in mind that + * using it may render the function uncallable if the map grows to the point where clearing it consumes too much + * gas to fit in a block. + */ + function clear(Bytes4ToBytes4Map storage map) internal { + clear(map._inner); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(Bytes4ToBytes4Map storage map, bytes4 key) internal view returns (bool) { + return contains(map._inner, bytes32(key)); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(Bytes4ToBytes4Map storage map) internal view returns (uint256) { + return length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the map. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(Bytes4ToBytes4Map storage map, uint256 index) internal view returns (bytes4 key, bytes4 value) { + (bytes32 atKey, bytes32 val) = at(map._inner, index); + return (bytes4(atKey), bytes4(val)); + } + + /** + * @dev Tries to return the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(Bytes4ToBytes4Map storage map, bytes4 key) internal view returns (bool exists, bytes4 value) { + (bool success, bytes32 val) = tryGet(map._inner, bytes32(key)); + return (success, bytes4(val)); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(Bytes4ToBytes4Map storage map, bytes4 key) internal view returns (bytes4) { + return bytes4(get(map._inner, bytes32(key))); + } + + /** + * @dev Returns an array containing all the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(Bytes4ToBytes4Map storage map) internal view returns (bytes4[] memory) { + bytes32[] memory store = keys(map._inner); + bytes4[] memory result; + + assembly ("memory-safe") { + result := store + } + + return result; + } + + /** + * @dev Returns an array containing a slice of the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(Bytes4ToBytes4Map storage map, uint256 start, uint256 end) internal view returns (bytes4[] memory) { + bytes32[] memory store = keys(map._inner, start, end); + bytes4[] memory result; + + assembly ("memory-safe") { + result := store + } + + return result; + } + + // Bytes4ToBytes32Map + + struct Bytes4ToBytes32Map { + Bytes32ToBytes32Map _inner; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function set(Bytes4ToBytes32Map storage map, bytes4 key, bytes32 value) internal returns (bool) { + return set(map._inner, bytes32(key), value); + } + + /** + * @dev Removes a value from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(Bytes4ToBytes32Map storage map, bytes4 key) internal returns (bool) { + return remove(map._inner, bytes32(key)); + } + + /** + * @dev Removes all the entries from a map. O(n). + * + * WARNING: This function has an unbounded cost that scales with map size. Developers should keep in mind that + * using it may render the function uncallable if the map grows to the point where clearing it consumes too much + * gas to fit in a block. + */ + function clear(Bytes4ToBytes32Map storage map) internal { + clear(map._inner); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(Bytes4ToBytes32Map storage map, bytes4 key) internal view returns (bool) { + return contains(map._inner, bytes32(key)); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(Bytes4ToBytes32Map storage map) internal view returns (uint256) { + return length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the map. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(Bytes4ToBytes32Map storage map, uint256 index) internal view returns (bytes4 key, bytes32 value) { + (bytes32 atKey, bytes32 val) = at(map._inner, index); + return (bytes4(atKey), val); + } + + /** + * @dev Tries to return the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(Bytes4ToBytes32Map storage map, bytes4 key) internal view returns (bool exists, bytes32 value) { + (bool success, bytes32 val) = tryGet(map._inner, bytes32(key)); + return (success, val); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(Bytes4ToBytes32Map storage map, bytes4 key) internal view returns (bytes32) { + return get(map._inner, bytes32(key)); + } + + /** + * @dev Returns an array containing all the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(Bytes4ToBytes32Map storage map) internal view returns (bytes4[] memory) { + bytes32[] memory store = keys(map._inner); + bytes4[] memory result; + + assembly ("memory-safe") { + result := store + } + + return result; + } + + /** + * @dev Returns an array containing a slice of the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(Bytes4ToBytes32Map storage map, uint256 start, uint256 end) internal view returns (bytes4[] memory) { + bytes32[] memory store = keys(map._inner, start, end); + bytes4[] memory result; + + assembly ("memory-safe") { + result := store + } + + return result; + } + + // Bytes32ToUintMap + + struct Bytes32ToUintMap { + Bytes32ToBytes32Map _inner; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function set(Bytes32ToUintMap storage map, bytes32 key, uint256 value) internal returns (bool) { + return set(map._inner, key, bytes32(value)); + } + + /** + * @dev Removes a value from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(Bytes32ToUintMap storage map, bytes32 key) internal returns (bool) { + return remove(map._inner, key); + } + + /** + * @dev Removes all the entries from a map. O(n). + * + * WARNING: This function has an unbounded cost that scales with map size. Developers should keep in mind that + * using it may render the function uncallable if the map grows to the point where clearing it consumes too much + * gas to fit in a block. + */ + function clear(Bytes32ToUintMap storage map) internal { + clear(map._inner); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool) { + return contains(map._inner, key); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(Bytes32ToUintMap storage map) internal view returns (uint256) { + return length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the map. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(Bytes32ToUintMap storage map, uint256 index) internal view returns (bytes32 key, uint256 value) { + (bytes32 atKey, bytes32 val) = at(map._inner, index); + return (atKey, uint256(val)); + } + + /** + * @dev Tries to return the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool exists, uint256 value) { + (bool success, bytes32 val) = tryGet(map._inner, key); + return (success, uint256(val)); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(Bytes32ToUintMap storage map, bytes32 key) internal view returns (uint256) { + return uint256(get(map._inner, key)); + } + + /** + * @dev Returns an array containing all the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(Bytes32ToUintMap storage map) internal view returns (bytes32[] memory) { + bytes32[] memory store = keys(map._inner); + bytes32[] memory result; + + assembly ("memory-safe") { + result := store + } + + return result; + } + + /** + * @dev Returns an array containing a slice of the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(Bytes32ToUintMap storage map, uint256 start, uint256 end) internal view returns (bytes32[] memory) { + bytes32[] memory store = keys(map._inner, start, end); + bytes32[] memory result; + + assembly ("memory-safe") { + result := store + } + + return result; + } + + // Bytes32ToAddressMap + + struct Bytes32ToAddressMap { + Bytes32ToBytes32Map _inner; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function set(Bytes32ToAddressMap storage map, bytes32 key, address value) internal returns (bool) { + return set(map._inner, key, bytes32(uint256(uint160(value)))); + } + + /** + * @dev Removes a value from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(Bytes32ToAddressMap storage map, bytes32 key) internal returns (bool) { + return remove(map._inner, key); + } + + /** + * @dev Removes all the entries from a map. O(n). + * + * WARNING: This function has an unbounded cost that scales with map size. Developers should keep in mind that + * using it may render the function uncallable if the map grows to the point where clearing it consumes too much + * gas to fit in a block. + */ + function clear(Bytes32ToAddressMap storage map) internal { + clear(map._inner); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(Bytes32ToAddressMap storage map, bytes32 key) internal view returns (bool) { + return contains(map._inner, key); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(Bytes32ToAddressMap storage map) internal view returns (uint256) { + return length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the map. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(Bytes32ToAddressMap storage map, uint256 index) internal view returns (bytes32 key, address value) { + (bytes32 atKey, bytes32 val) = at(map._inner, index); + return (atKey, address(uint160(uint256(val)))); + } + + /** + * @dev Tries to return the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(Bytes32ToAddressMap storage map, bytes32 key) internal view returns (bool exists, address value) { + (bool success, bytes32 val) = tryGet(map._inner, key); + return (success, address(uint160(uint256(val)))); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(Bytes32ToAddressMap storage map, bytes32 key) internal view returns (address) { + return address(uint160(uint256(get(map._inner, key)))); + } + + /** + * @dev Returns an array containing all the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(Bytes32ToAddressMap storage map) internal view returns (bytes32[] memory) { + bytes32[] memory store = keys(map._inner); + bytes32[] memory result; + + assembly ("memory-safe") { + result := store + } + + return result; + } + + /** + * @dev Returns an array containing a slice of the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys( + Bytes32ToAddressMap storage map, + uint256 start, + uint256 end + ) internal view returns (bytes32[] memory) { + bytes32[] memory store = keys(map._inner, start, end); + bytes32[] memory result; + + assembly ("memory-safe") { + result := store + } + + return result; + } + + // Bytes32ToBytes4Map + + struct Bytes32ToBytes4Map { + Bytes32ToBytes32Map _inner; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function set(Bytes32ToBytes4Map storage map, bytes32 key, bytes4 value) internal returns (bool) { + return set(map._inner, key, bytes32(value)); + } + + /** + * @dev Removes a value from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(Bytes32ToBytes4Map storage map, bytes32 key) internal returns (bool) { + return remove(map._inner, key); + } + + /** + * @dev Removes all the entries from a map. O(n). + * + * WARNING: This function has an unbounded cost that scales with map size. Developers should keep in mind that + * using it may render the function uncallable if the map grows to the point where clearing it consumes too much + * gas to fit in a block. + */ + function clear(Bytes32ToBytes4Map storage map) internal { + clear(map._inner); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(Bytes32ToBytes4Map storage map, bytes32 key) internal view returns (bool) { + return contains(map._inner, key); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(Bytes32ToBytes4Map storage map) internal view returns (uint256) { + return length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the map. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(Bytes32ToBytes4Map storage map, uint256 index) internal view returns (bytes32 key, bytes4 value) { + (bytes32 atKey, bytes32 val) = at(map._inner, index); + return (atKey, bytes4(val)); + } + + /** + * @dev Tries to return the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(Bytes32ToBytes4Map storage map, bytes32 key) internal view returns (bool exists, bytes4 value) { + (bool success, bytes32 val) = tryGet(map._inner, key); + return (success, bytes4(val)); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(Bytes32ToBytes4Map storage map, bytes32 key) internal view returns (bytes4) { + return bytes4(get(map._inner, key)); + } + + /** + * @dev Returns an array containing all the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(Bytes32ToBytes4Map storage map) internal view returns (bytes32[] memory) { + bytes32[] memory store = keys(map._inner); + bytes32[] memory result; + + assembly ("memory-safe") { + result := store + } + + return result; + } + + /** + * @dev Returns an array containing a slice of the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(Bytes32ToBytes4Map storage map, uint256 start, uint256 end) internal view returns (bytes32[] memory) { bytes32[] memory store = keys(map._inner, start, end); bytes32[] memory result; diff --git a/contracts/utils/structs/EnumerableSet.sol b/contracts/utils/structs/EnumerableSet.sol index 12479caf0b7..8863e9d3fc2 100644 --- a/contracts/utils/structs/EnumerableSet.sol +++ b/contracts/utils/structs/EnumerableSet.sol @@ -36,6 +36,7 @@ import {Math} from "../math/Math.sol"; * - `uint256` (`UintSet`) since v3.3.0 * - `string` (`StringSet`) since v5.4.0 * - `bytes` (`BytesSet`) since v5.4.0 + * - `bytes4` (`Bytes4Set`) since v5.6.0 * * [WARNING] * ==== @@ -302,6 +303,108 @@ library EnumerableSet { return result; } + // Bytes4Set + + struct Bytes4Set { + Set _inner; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function add(Bytes4Set storage set, bytes4 value) internal returns (bool) { + return _add(set._inner, bytes32(value)); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(Bytes4Set storage set, bytes4 value) internal returns (bool) { + return _remove(set._inner, bytes32(value)); + } + + /** + * @dev Removes all the values from a set. O(n). + * + * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the + * function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block. + */ + function clear(Bytes4Set storage set) internal { + _clear(set._inner); + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(Bytes4Set storage set, bytes4 value) internal view returns (bool) { + return _contains(set._inner, bytes32(value)); + } + + /** + * @dev Returns the number of values in the set. O(1). + */ + function length(Bytes4Set storage set) internal view returns (uint256) { + return _length(set._inner); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(Bytes4Set storage set, uint256 index) internal view returns (bytes4) { + return bytes4(_at(set._inner, index)); + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function values(Bytes4Set storage set) internal view returns (bytes4[] memory) { + bytes32[] memory store = _values(set._inner); + bytes4[] memory result; + + assembly ("memory-safe") { + result := store + } + + return result; + } + + /** + * @dev Return a slice of the set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function values(Bytes4Set storage set, uint256 start, uint256 end) internal view returns (bytes4[] memory) { + bytes32[] memory store = _values(set._inner, start, end); + bytes4[] memory result; + + assembly ("memory-safe") { + result := store + } + + return result; + } + // AddressSet struct AddressSet { diff --git a/scripts/generate/templates/Enumerable.opts.js b/scripts/generate/templates/Enumerable.opts.js index 50c7349b352..57404a60103 100644 --- a/scripts/generate/templates/Enumerable.opts.js +++ b/scripts/generate/templates/Enumerable.opts.js @@ -24,6 +24,7 @@ const toMapTypeDescr = ({ key, value }) => ({ const SET_TYPES = [ { type: 'bytes32' }, + { type: 'bytes4' }, { type: 'address' }, { type: 'uint256' }, { type: 'string', memory: true }, @@ -35,7 +36,7 @@ const SET_TYPES = [ const MAP_TYPES = [] .concat( // value type maps - ['uint256', 'address', 'bytes32'] + ['uint256', 'address', 'bytes4', 'bytes32'] .flatMap((keyType, _, array) => array.map(valueType => ({ key: { type: keyType }, value: { type: valueType } }))) .slice(0, -1), // remove bytes32 → bytes32 (last one) that is already defined // non-value type maps diff --git a/scripts/generate/templates/EnumerableMap.js b/scripts/generate/templates/EnumerableMap.js index a7d061d771a..9f81426cdc6 100644 --- a/scripts/generate/templates/EnumerableMap.js +++ b/scripts/generate/templates/EnumerableMap.js @@ -41,6 +41,13 @@ import {EnumerableSet} from "./EnumerableSet.sol"; * - \`address -> bytes32\` (\`AddressToBytes32Map\`) since v5.1.0 * - \`bytes32 -> address\` (\`Bytes32ToAddressMap\`) since v5.1.0 * - \`bytes -> bytes\` (\`BytesToBytesMap\`) since v5.4.0 + * - \`uint256 -> bytes4\` (\`UintToBytes4Map\`) since v5.6.0 + * - \`address -> bytes4\` (\`AddressToBytes4Map\`) since v5.6.0 + * - \`bytes4 -> uint256\` (\`Bytes4ToUintMap\`) since v5.6.0 + * - \`bytes4 -> address\` (\`Bytes4ToAddressMap\`) since v5.6.0 + * - \`bytes4 -> bytes4\` (\`Bytes4ToBytes4Map\`) since v5.6.0 + * - \`bytes4 -> bytes32\` (\`Bytes4ToBytes32Map\`) since v5.6.0 + * - \`bytes32 -> bytes4\` (\`Bytes32ToBytes4Map\`) since v5.6.0 * * [WARNING] * ==== diff --git a/scripts/generate/templates/EnumerableSet.js b/scripts/generate/templates/EnumerableSet.js index f69faea8566..2c5a54227a8 100644 --- a/scripts/generate/templates/EnumerableSet.js +++ b/scripts/generate/templates/EnumerableSet.js @@ -37,6 +37,7 @@ import {Math} from "../math/Math.sol"; * - \`uint256\` (\`UintSet\`) since v3.3.0 * - \`string\` (\`StringSet\`) since v5.4.0 * - \`bytes\` (\`BytesSet\`) since v5.4.0 + * - \`bytes4\` (\`Bytes4Set\`) since v5.6.0 * * [WARNING] * ==== diff --git a/scripts/generate/templates/conversion.js b/scripts/generate/templates/conversion.js index 9221f7c21c9..c0f55a982e5 100644 --- a/scripts/generate/templates/conversion.js +++ b/scripts/generate/templates/conversion.js @@ -2,6 +2,8 @@ function toBytes32(type, value) { switch (type) { case 'bytes32': return value; + case 'bytes4': + return `bytes32(${value})`; case 'uint256': return `bytes32(${value})`; case 'address': @@ -15,6 +17,8 @@ function fromBytes32(type, value) { switch (type) { case 'bytes32': return value; + case 'bytes4': + return `bytes4(${value})`; case 'uint256': return `uint256(${value})`; case 'address': diff --git a/test/helpers/random.js b/test/helpers/random.js index 6d782675081..15d00c15bbc 100644 --- a/test/helpers/random.js +++ b/test/helpers/random.js @@ -3,6 +3,7 @@ const { ethers } = require('hardhat'); const generators = { address: () => ethers.Wallet.createRandom().address, bytes32: () => ethers.hexlify(ethers.randomBytes(32)), + bytes4: () => ethers.hexlify(ethers.randomBytes(4)), uint256: () => ethers.toBigInt(ethers.randomBytes(32)), int256: () => ethers.toBigInt(ethers.randomBytes(32)) + ethers.MinInt256, bytes: (length = 32) => ethers.hexlify(ethers.randomBytes(length)), @@ -11,6 +12,7 @@ const generators = { generators.address.zero = ethers.ZeroAddress; generators.bytes32.zero = ethers.ZeroHash; +generators.bytes4.zero = ethers.zeroPadBytes('0x', 4); generators.uint256.zero = 0n; generators.int256.zero = 0n; generators.bytes.zero = '0x';