diff --git a/.changeset/fine-frogs-bake.md b/.changeset/fine-frogs-bake.md new file mode 100644 index 000000000..30ee756a4 --- /dev/null +++ b/.changeset/fine-frogs-bake.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`EnumerableMap`: Add `keys(uint256,uint256)` that returns a subset (slice) of the keys in the map. diff --git a/.changeset/hot-grapes-lie.md b/.changeset/hot-grapes-lie.md new file mode 100644 index 000000000..3d64dda7b --- /dev/null +++ b/.changeset/hot-grapes-lie.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`EnumerableSet`: Add `values(uint256,uint256)` that returns a subset (slice) of the values in the set. diff --git a/contracts/utils/structs/EnumerableMap.sol b/contracts/utils/structs/EnumerableMap.sol index 1c67aacaf..69cb57781 100644 --- a/contracts/utils/structs/EnumerableMap.sol +++ b/contracts/utils/structs/EnumerableMap.sol @@ -164,7 +164,7 @@ library EnumerableMap { } /** - * @dev Return the an array containing all the keys + * @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 @@ -175,6 +175,22 @@ library EnumerableMap { return map._keys.values(); } + /** + * @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( + Bytes32ToBytes32Map storage map, + uint256 start, + uint256 end + ) internal view returns (bytes32[] memory) { + return map._keys.values(start, end); + } + // UintToUintMap struct UintToUintMap { @@ -278,6 +294,25 @@ library EnumerableMap { return result; } + /** + * @dev Return the 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(UintToUintMap 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; + } + // UintToAddressMap struct UintToAddressMap { @@ -381,6 +416,25 @@ library EnumerableMap { return result; } + /** + * @dev Return the 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(UintToAddressMap 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 { @@ -484,6 +538,25 @@ library EnumerableMap { return result; } + /** + * @dev Return the 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(UintToBytes32Map 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; + } + // AddressToUintMap struct AddressToUintMap { @@ -587,6 +660,25 @@ library EnumerableMap { return result; } + /** + * @dev Return the 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(AddressToUintMap storage map, uint256 start, uint256 end) internal view returns (address[] memory) { + bytes32[] memory store = keys(map._inner, start, end); + address[] memory result; + + assembly ("memory-safe") { + result := store + } + + return result; + } + // AddressToAddressMap struct AddressToAddressMap { @@ -690,6 +782,29 @@ library EnumerableMap { return result; } + /** + * @dev Return the 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( + AddressToAddressMap storage map, + uint256 start, + uint256 end + ) internal view returns (address[] memory) { + bytes32[] memory store = keys(map._inner, start, end); + address[] memory result; + + assembly ("memory-safe") { + result := store + } + + return result; + } + // AddressToBytes32Map struct AddressToBytes32Map { @@ -793,6 +908,29 @@ library EnumerableMap { return result; } + /** + * @dev Return the 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( + AddressToBytes32Map storage map, + uint256 start, + uint256 end + ) internal view returns (address[] memory) { + bytes32[] memory store = keys(map._inner, start, end); + address[] memory result; + + assembly ("memory-safe") { + result := store + } + + return result; + } + // Bytes32ToUintMap struct Bytes32ToUintMap { @@ -896,6 +1034,25 @@ library EnumerableMap { return result; } + /** + * @dev Return the 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 { @@ -999,6 +1156,29 @@ library EnumerableMap { return result; } + /** + * @dev Return the 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; + } + /** * @dev Query for a nonexistent map key. */ @@ -1106,7 +1286,7 @@ library EnumerableMap { } /** - * @dev Return the an array containing all the keys + * @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 @@ -1116,4 +1296,16 @@ library EnumerableMap { function keys(BytesToBytesMap storage map) internal view returns (bytes[] memory) { return map._keys.values(); } + + /** + * @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(BytesToBytesMap storage map, uint256 start, uint256 end) internal view returns (bytes[] memory) { + return map._keys.values(start, end); + } } diff --git a/contracts/utils/structs/EnumerableSet.sol b/contracts/utils/structs/EnumerableSet.sol index 1c037241b..896e484c7 100644 --- a/contracts/utils/structs/EnumerableSet.sol +++ b/contracts/utils/structs/EnumerableSet.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.20; import {Arrays} from "../Arrays.sol"; +import {Math} from "../math/Math.sol"; /** * @dev Library for managing @@ -176,6 +177,28 @@ library EnumerableSet { return set._values; } + /** + * @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(Set storage set, uint256 start, uint256 end) private view returns (bytes32[] memory) { + unchecked { + end = Math.min(end, _length(set)); + start = Math.min(start, end); + + uint256 len = end - start; + bytes32[] memory result = new bytes32[](len); + for (uint256 i = 0; i < len; ++i) { + result[i] = Arrays.unsafeAccess(set._values, start + i).value; + } + return result; + } + } + // Bytes32Set struct Bytes32Set { @@ -259,6 +282,25 @@ library EnumerableSet { 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(Bytes32Set storage set, uint256 start, uint256 end) internal view returns (bytes32[] memory) { + bytes32[] memory store = _values(set._inner, start, end); + bytes32[] memory result; + + assembly ("memory-safe") { + result := store + } + + return result; + } + // AddressSet struct AddressSet { @@ -342,6 +384,25 @@ library EnumerableSet { 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(AddressSet storage set, uint256 start, uint256 end) internal view returns (address[] memory) { + bytes32[] memory store = _values(set._inner, start, end); + address[] memory result; + + assembly ("memory-safe") { + result := store + } + + return result; + } + // UintSet struct UintSet { @@ -425,6 +486,25 @@ library EnumerableSet { 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(UintSet storage set, uint256 start, uint256 end) internal view returns (uint256[] memory) { + bytes32[] memory store = _values(set._inner, start, end); + uint256[] memory result; + + assembly ("memory-safe") { + result := store + } + + return result; + } + struct StringSet { // Storage of set values string[] _values; @@ -545,6 +625,28 @@ library EnumerableSet { return self._values; } + /** + * @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(StringSet storage set, uint256 start, uint256 end) internal view returns (string[] memory) { + unchecked { + end = Math.min(end, length(set)); + start = Math.min(start, end); + + uint256 len = end - start; + string[] memory result = new string[](len); + for (uint256 i = 0; i < len; ++i) { + result[i] = Arrays.unsafeAccess(set._values, start + i).value; + } + return result; + } + } + struct BytesSet { // Storage of set values bytes[] _values; @@ -664,4 +766,26 @@ library EnumerableSet { function values(BytesSet storage self) internal view returns (bytes[] memory) { return self._values; } + + /** + * @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(BytesSet storage set, uint256 start, uint256 end) internal view returns (bytes[] memory) { + unchecked { + end = Math.min(end, length(set)); + start = Math.min(start, end); + + uint256 len = end - start; + bytes[] memory result = new bytes[](len); + for (uint256 i = 0; i < len; ++i) { + result[i] = Arrays.unsafeAccess(set._values, start + i).value; + } + return result; + } + } } diff --git a/scripts/generate/templates/EnumerableMap.js b/scripts/generate/templates/EnumerableMap.js index 557421888..5b0d1141d 100644 --- a/scripts/generate/templates/EnumerableMap.js +++ b/scripts/generate/templates/EnumerableMap.js @@ -165,7 +165,7 @@ function get(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns } /** - * @dev Return the an array containing all the keys + * @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 @@ -175,6 +175,18 @@ function get(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns function keys(Bytes32ToBytes32Map storage map) internal view returns (bytes32[] memory) { return map._keys.values(); } + +/** + * @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(Bytes32ToBytes32Map storage map, uint256 start, uint256 end) internal view returns (bytes32[] memory) { + return map._keys.values(start, end); +} `; const customMap = ({ name, key, value }) => `\ @@ -280,6 +292,25 @@ function keys(${name} storage map) internal view returns (${key.type}[] memory) return result; } + +/** + * @dev Return the 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(${name} storage map, uint256 start, uint256 end) internal view returns (${key.type}[] memory) { + bytes32[] memory store = keys(map._inner, start, end); + ${key.type}[] memory result; + + assembly ("memory-safe") { + result := store + } + + return result; +} `; const memoryMap = ({ name, keySet, key, value }) => `\ @@ -390,7 +421,7 @@ function get(${name} storage map, ${key.typeLoc} key) internal view returns (${v } /** - * @dev Return the an array containing all the keys + * @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 @@ -400,6 +431,18 @@ function get(${name} storage map, ${key.typeLoc} key) internal view returns (${v function keys(${name} storage map) internal view returns (${key.type}[] memory) { return map._keys.values(); } + +/** + * @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(${name} storage map, uint256 start, uint256 end) internal view returns (${key.type}[] memory) { + return map._keys.values(start, end); +} `; // GENERATE diff --git a/scripts/generate/templates/EnumerableSet.js b/scripts/generate/templates/EnumerableSet.js index ac620b88a..abd9b1b2a 100644 --- a/scripts/generate/templates/EnumerableSet.js +++ b/scripts/generate/templates/EnumerableSet.js @@ -6,6 +6,7 @@ const header = `\ pragma solidity ^0.8.20; import {Arrays} from "../Arrays.sol"; +import {Math} from "../math/Math.sol"; /** * @dev Library for managing @@ -179,6 +180,28 @@ function _at(Set storage set, uint256 index) private view returns (bytes32) { function _values(Set storage set) private view returns (bytes32[] memory) { return set._values; } + +/** + * @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(Set storage set, uint256 start, uint256 end) private view returns (bytes32[] memory) { + unchecked { + end = Math.min(end, _length(set)); + start = Math.min(start, end); + + uint256 len = end - start; + bytes32[] memory result = new bytes32[](len); + for (uint256 i = 0; i < len; ++i) { + result[i] = Arrays.unsafeAccess(set._values, start + i).value; + } + return result; + } +} `; // NOTE: this should be deprecated in favor of a more native construction in v6.0 @@ -265,6 +288,25 @@ function values(${name} storage set) internal view returns (${type}[] memory) { 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(${name} storage set, uint256 start, uint256 end) internal view returns (${type}[] memory) { + bytes32[] memory store = _values(set._inner, start, end); + ${type}[] memory result; + + assembly ("memory-safe") { + result := store + } + + return result; +} `; const memorySet = ({ name, value }) => `\ @@ -387,6 +429,28 @@ function at(${name} storage self, uint256 index) internal view returns (${value. function values(${name} storage self) internal view returns (${value.type}[] memory) { return self._values; } + +/** + * @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(${name} storage set, uint256 start, uint256 end) internal view returns (${value.type}[] memory) { + unchecked { + end = Math.min(end, length(set)); + start = Math.min(start, end); + + uint256 len = end - start; + ${value.type}[] memory result = new ${value.type}[](len); + for (uint256 i = 0; i < len; ++i) { + result[i] = Arrays.unsafeAccess(set._values, start + i).value; + } + return result; + } +} `; // GENERATE diff --git a/test/utils/structs/EnumerableMap.behavior.js b/test/utils/structs/EnumerableMap.behavior.js index 11806dae6..4074b0779 100644 --- a/test/utils/structs/EnumerableMap.behavior.js +++ b/test/utils/structs/EnumerableMap.behavior.js @@ -191,6 +191,22 @@ function shouldBehaveLikeMap() { }); }); }); + + it('keys (full & paginated)', async function () { + const keys = [this.keyA, this.keyB, this.keyC]; + await this.methods.set(this.keyA, this.valueA); + await this.methods.set(this.keyB, this.valueB); + await this.methods.set(this.keyC, this.valueC); + + // get all values + expect([...(await this.methods.keys())]).to.deep.equal(keys); + + // try pagination + for (const begin of [0, 1, 2, 3, 4]) + for (const end of [0, 1, 2, 3, 4]) { + expect([...(await this.methods.keysPage(begin, end))]).to.deep.equal(keys.slice(begin, end)); + } + }); } module.exports = { diff --git a/test/utils/structs/EnumerableMap.test.js b/test/utils/structs/EnumerableMap.test.js index 7319ba38d..567c82dec 100644 --- a/test/utils/structs/EnumerableMap.test.js +++ b/test/utils/structs/EnumerableMap.test.js @@ -34,6 +34,7 @@ async function fixture() { length: `$length_EnumerableMap_${name}(uint256)`, at: `$at_EnumerableMap_${name}(uint256,uint256)`, keys: `$keys_EnumerableMap_${name}(uint256)`, + keysPage: `$keys_EnumerableMap_${name}(uint256,uint256,uint256)`, } : { set: `$set(uint256,${key.type},${value.type})`, @@ -45,6 +46,7 @@ async function fixture() { length: `$length_EnumerableMap_${name}(uint256)`, at: `$at_EnumerableMap_${name}(uint256,uint256)`, keys: `$keys_EnumerableMap_${name}(uint256)`, + keysPage: `$keys_EnumerableMap_${name}(uint256,uint256,uint256)`, }, fnSig => (...args) => diff --git a/test/utils/structs/EnumerableSet.behavior.js b/test/utils/structs/EnumerableSet.behavior.js index fb932680c..286563b22 100644 --- a/test/utils/structs/EnumerableSet.behavior.js +++ b/test/utils/structs/EnumerableSet.behavior.js @@ -152,6 +152,22 @@ function shouldBehaveLikeSet() { await expectMembersMatch(this.methods, [this.valueA]); }); }); + + it('values (full & paginated)', async function () { + const values = [this.valueA, this.valueB, this.valueC]; + await this.methods.add(this.valueA); + await this.methods.add(this.valueB); + await this.methods.add(this.valueC); + + // get all values + expect([...(await this.methods.values())]).to.deep.equal(values); + + // try pagination + for (const begin of [0, 1, 2, 3, 4]) + for (const end of [0, 1, 2, 3, 4]) { + expect([...(await this.methods.valuesPage(begin, end))]).to.deep.equal(values.slice(begin, end)); + } + }); } module.exports = { diff --git a/test/utils/structs/EnumerableSet.test.js b/test/utils/structs/EnumerableSet.test.js index e111d2197..135bdf508 100644 --- a/test/utils/structs/EnumerableSet.test.js +++ b/test/utils/structs/EnumerableSet.test.js @@ -35,6 +35,7 @@ async function fixture() { length: `$length_EnumerableSet_${name}(uint256)`, at: `$at_EnumerableSet_${name}(uint256,uint256)`, values: `$values_EnumerableSet_${name}(uint256)`, + valuesPage: `$values_EnumerableSet_${name}(uint256,uint256,uint256)`, }), events: { addReturn: `return$add_EnumerableSet_${name}_${value.type.replace(/[[\]]/g, '_')}`,