Add paginated version of EnumerableSet.values() and EnumerableMap.keys() (#5713)

Co-authored-by: ernestognw <ernestognw@gmail.com>
This commit is contained in:
Hadrien Croubois
2025-06-04 09:33:00 +02:00
committed by GitHub
parent f45e9158b7
commit d20b9e30bd
10 changed files with 472 additions and 4 deletions

View File

@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---
`EnumerableMap`: Add `keys(uint256,uint256)` that returns a subset (slice) of the keys in the map.

View File

@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---
`EnumerableSet`: Add `values(uint256,uint256)` that returns a subset (slice) of the values in the set.

View File

@ -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 * 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 * 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(); 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 // UintToUintMap
struct UintToUintMap { struct UintToUintMap {
@ -278,6 +294,25 @@ library EnumerableMap {
return result; 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 // UintToAddressMap
struct UintToAddressMap { struct UintToAddressMap {
@ -381,6 +416,25 @@ library EnumerableMap {
return result; 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 // UintToBytes32Map
struct UintToBytes32Map { struct UintToBytes32Map {
@ -484,6 +538,25 @@ library EnumerableMap {
return result; 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 // AddressToUintMap
struct AddressToUintMap { struct AddressToUintMap {
@ -587,6 +660,25 @@ library EnumerableMap {
return result; 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 // AddressToAddressMap
struct AddressToAddressMap { struct AddressToAddressMap {
@ -690,6 +782,29 @@ library EnumerableMap {
return result; 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 // AddressToBytes32Map
struct AddressToBytes32Map { struct AddressToBytes32Map {
@ -793,6 +908,29 @@ library EnumerableMap {
return result; 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 // Bytes32ToUintMap
struct Bytes32ToUintMap { struct Bytes32ToUintMap {
@ -896,6 +1034,25 @@ library EnumerableMap {
return result; 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 // Bytes32ToAddressMap
struct Bytes32ToAddressMap { struct Bytes32ToAddressMap {
@ -999,6 +1156,29 @@ library EnumerableMap {
return result; 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. * @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 * 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 * 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) { function keys(BytesToBytesMap storage map) internal view returns (bytes[] memory) {
return map._keys.values(); 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);
}
} }

View File

@ -5,6 +5,7 @@
pragma solidity ^0.8.20; pragma solidity ^0.8.20;
import {Arrays} from "../Arrays.sol"; import {Arrays} from "../Arrays.sol";
import {Math} from "../math/Math.sol";
/** /**
* @dev Library for managing * @dev Library for managing
@ -176,6 +177,28 @@ library EnumerableSet {
return set._values; 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 // Bytes32Set
struct Bytes32Set { struct Bytes32Set {
@ -259,6 +282,25 @@ library EnumerableSet {
return result; 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 // AddressSet
struct AddressSet { struct AddressSet {
@ -342,6 +384,25 @@ library EnumerableSet {
return result; 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 // UintSet
struct UintSet { struct UintSet {
@ -425,6 +486,25 @@ library EnumerableSet {
return result; 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 { struct StringSet {
// Storage of set values // Storage of set values
string[] _values; string[] _values;
@ -545,6 +625,28 @@ library EnumerableSet {
return self._values; 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 { struct BytesSet {
// Storage of set values // Storage of set values
bytes[] _values; bytes[] _values;
@ -664,4 +766,26 @@ library EnumerableSet {
function values(BytesSet storage self) internal view returns (bytes[] memory) { function values(BytesSet storage self) internal view returns (bytes[] memory) {
return self._values; 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;
}
}
} }

View File

@ -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 * 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 * 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) { function keys(Bytes32ToBytes32Map storage map) internal view returns (bytes32[] memory) {
return map._keys.values(); 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 }) => `\ const customMap = ({ name, key, value }) => `\
@ -280,6 +292,25 @@ function keys(${name} storage map) internal view returns (${key.type}[] memory)
return result; 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 }) => `\ 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 * 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 * 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) { function keys(${name} storage map) internal view returns (${key.type}[] memory) {
return map._keys.values(); 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 // GENERATE

View File

@ -6,6 +6,7 @@ const header = `\
pragma solidity ^0.8.20; pragma solidity ^0.8.20;
import {Arrays} from "../Arrays.sol"; import {Arrays} from "../Arrays.sol";
import {Math} from "../math/Math.sol";
/** /**
* @dev Library for managing * @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) { function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values; 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 // 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; 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 }) => `\ 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) { function values(${name} storage self) internal view returns (${value.type}[] memory) {
return self._values; 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 // GENERATE

View File

@ -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 = { module.exports = {

View File

@ -34,6 +34,7 @@ async function fixture() {
length: `$length_EnumerableMap_${name}(uint256)`, length: `$length_EnumerableMap_${name}(uint256)`,
at: `$at_EnumerableMap_${name}(uint256,uint256)`, at: `$at_EnumerableMap_${name}(uint256,uint256)`,
keys: `$keys_EnumerableMap_${name}(uint256)`, keys: `$keys_EnumerableMap_${name}(uint256)`,
keysPage: `$keys_EnumerableMap_${name}(uint256,uint256,uint256)`,
} }
: { : {
set: `$set(uint256,${key.type},${value.type})`, set: `$set(uint256,${key.type},${value.type})`,
@ -45,6 +46,7 @@ async function fixture() {
length: `$length_EnumerableMap_${name}(uint256)`, length: `$length_EnumerableMap_${name}(uint256)`,
at: `$at_EnumerableMap_${name}(uint256,uint256)`, at: `$at_EnumerableMap_${name}(uint256,uint256)`,
keys: `$keys_EnumerableMap_${name}(uint256)`, keys: `$keys_EnumerableMap_${name}(uint256)`,
keysPage: `$keys_EnumerableMap_${name}(uint256,uint256,uint256)`,
}, },
fnSig => fnSig =>
(...args) => (...args) =>

View File

@ -152,6 +152,22 @@ function shouldBehaveLikeSet() {
await expectMembersMatch(this.methods, [this.valueA]); 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 = { module.exports = {

View File

@ -35,6 +35,7 @@ async function fixture() {
length: `$length_EnumerableSet_${name}(uint256)`, length: `$length_EnumerableSet_${name}(uint256)`,
at: `$at_EnumerableSet_${name}(uint256,uint256)`, at: `$at_EnumerableSet_${name}(uint256,uint256)`,
values: `$values_EnumerableSet_${name}(uint256)`, values: `$values_EnumerableSet_${name}(uint256)`,
valuesPage: `$values_EnumerableSet_${name}(uint256,uint256,uint256)`,
}), }),
events: { events: {
addReturn: `return$add_EnumerableSet_${name}_${value.type.replace(/[[\]]/g, '_')}`, addReturn: `return$add_EnumerableSet_${name}_${value.type.replace(/[[\]]/g, '_')}`,