Skip to content

Commit 88754d0

Browse files
Amxxfrangio
andauthored
Add keys() accessor to EnumerableMaps (OpenZeppelin#3920)
Co-authored-by: Francisco Giordano <[email protected]>
1 parent 2fc24fc commit 88754d0

File tree

5 files changed

+147
-10
lines changed

5 files changed

+147
-10
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
* `Strings`: add `equal` method. ([#3774](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3774))
1111
* `Strings`: add `toString` method for signed integers. ([#3773](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3773))
1212
* `MerkleProof`: optimize by using unchecked arithmetic. ([#3745](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3745))
13+
* `EnumerableMap`: add a `keys()` function that returns an array containing all the keys. ([#3920](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3920))
1314

1415
### Deprecations
1516

contracts/utils/structs/EnumerableMap.sol

Lines changed: 100 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,18 @@ library EnumerableMap {
156156
return value;
157157
}
158158

159+
/**
160+
* @dev Return the an array containing all the keys
161+
*
162+
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
163+
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
164+
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
165+
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
166+
*/
167+
function keys(Bytes32ToBytes32Map storage map) internal view returns (bytes32[] memory) {
168+
return map._keys.values();
169+
}
170+
159171
// UintToUintMap
160172

161173
struct UintToUintMap {
@@ -174,7 +186,7 @@ library EnumerableMap {
174186
}
175187

176188
/**
177-
* @dev Removes a value from a set. O(1).
189+
* @dev Removes a value from a map. O(1).
178190
*
179191
* Returns true if the key was removed from the map, that is if it was present.
180192
*/
@@ -197,7 +209,7 @@ library EnumerableMap {
197209
}
198210

199211
/**
200-
* @dev Returns the element stored at position `index` in the set. O(1).
212+
* @dev Returns the element stored at position `index` in the map. O(1).
201213
* Note that there are no guarantees on the ordering of values inside the
202214
* array, and it may change when more values are added or removed.
203215
*
@@ -240,6 +252,26 @@ library EnumerableMap {
240252
return uint256(get(map._inner, bytes32(key), errorMessage));
241253
}
242254

255+
/**
256+
* @dev Return the an array containing all the keys
257+
*
258+
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
259+
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
260+
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
261+
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
262+
*/
263+
function keys(UintToUintMap storage map) internal view returns (uint256[] memory) {
264+
bytes32[] memory store = keys(map._inner);
265+
uint256[] memory result;
266+
267+
/// @solidity memory-safe-assembly
268+
assembly {
269+
result := store
270+
}
271+
272+
return result;
273+
}
274+
243275
// UintToAddressMap
244276

245277
struct UintToAddressMap {
@@ -258,7 +290,7 @@ library EnumerableMap {
258290
}
259291

260292
/**
261-
* @dev Removes a value from a set. O(1).
293+
* @dev Removes a value from a map. O(1).
262294
*
263295
* Returns true if the key was removed from the map, that is if it was present.
264296
*/
@@ -281,7 +313,7 @@ library EnumerableMap {
281313
}
282314

283315
/**
284-
* @dev Returns the element stored at position `index` in the set. O(1).
316+
* @dev Returns the element stored at position `index` in the map. O(1).
285317
* Note that there are no guarantees on the ordering of values inside the
286318
* array, and it may change when more values are added or removed.
287319
*
@@ -328,6 +360,26 @@ library EnumerableMap {
328360
return address(uint160(uint256(get(map._inner, bytes32(key), errorMessage))));
329361
}
330362

363+
/**
364+
* @dev Return the an array containing all the keys
365+
*
366+
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
367+
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
368+
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
369+
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
370+
*/
371+
function keys(UintToAddressMap storage map) internal view returns (uint256[] memory) {
372+
bytes32[] memory store = keys(map._inner);
373+
uint256[] memory result;
374+
375+
/// @solidity memory-safe-assembly
376+
assembly {
377+
result := store
378+
}
379+
380+
return result;
381+
}
382+
331383
// AddressToUintMap
332384

333385
struct AddressToUintMap {
@@ -346,7 +398,7 @@ library EnumerableMap {
346398
}
347399

348400
/**
349-
* @dev Removes a value from a set. O(1).
401+
* @dev Removes a value from a map. O(1).
350402
*
351403
* Returns true if the key was removed from the map, that is if it was present.
352404
*/
@@ -369,7 +421,7 @@ library EnumerableMap {
369421
}
370422

371423
/**
372-
* @dev Returns the element stored at position `index` in the set. O(1).
424+
* @dev Returns the element stored at position `index` in the map. O(1).
373425
* Note that there are no guarantees on the ordering of values inside the
374426
* array, and it may change when more values are added or removed.
375427
*
@@ -416,6 +468,26 @@ library EnumerableMap {
416468
return uint256(get(map._inner, bytes32(uint256(uint160(key))), errorMessage));
417469
}
418470

471+
/**
472+
* @dev Return the an array containing all the keys
473+
*
474+
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
475+
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
476+
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
477+
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
478+
*/
479+
function keys(AddressToUintMap storage map) internal view returns (address[] memory) {
480+
bytes32[] memory store = keys(map._inner);
481+
address[] memory result;
482+
483+
/// @solidity memory-safe-assembly
484+
assembly {
485+
result := store
486+
}
487+
488+
return result;
489+
}
490+
419491
// Bytes32ToUintMap
420492

421493
struct Bytes32ToUintMap {
@@ -434,7 +506,7 @@ library EnumerableMap {
434506
}
435507

436508
/**
437-
* @dev Removes a value from a set. O(1).
509+
* @dev Removes a value from a map. O(1).
438510
*
439511
* Returns true if the key was removed from the map, that is if it was present.
440512
*/
@@ -457,7 +529,7 @@ library EnumerableMap {
457529
}
458530

459531
/**
460-
* @dev Returns the element stored at position `index` in the set. O(1).
532+
* @dev Returns the element stored at position `index` in the map. O(1).
461533
* Note that there are no guarantees on the ordering of values inside the
462534
* array, and it may change when more values are added or removed.
463535
*
@@ -503,4 +575,24 @@ library EnumerableMap {
503575
) internal view returns (uint256) {
504576
return uint256(get(map._inner, key, errorMessage));
505577
}
578+
579+
/**
580+
* @dev Return the an array containing all the keys
581+
*
582+
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
583+
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
584+
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
585+
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
586+
*/
587+
function keys(Bytes32ToUintMap storage map) internal view returns (bytes32[] memory) {
588+
bytes32[] memory store = keys(map._inner);
589+
bytes32[] memory result;
590+
591+
/// @solidity memory-safe-assembly
592+
assembly {
593+
result := store
594+
}
595+
596+
return result;
597+
}
506598
}

scripts/generate/templates/EnumerableMap.js

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,18 @@ function get(
168168
require(value != 0 || contains(map, key), errorMessage);
169169
return value;
170170
}
171+
172+
/**
173+
* @dev Return the an array containing all the keys
174+
*
175+
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
176+
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
177+
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
178+
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
179+
*/
180+
function keys(Bytes32ToBytes32Map storage map) internal view returns (bytes32[] memory) {
181+
return map._keys.values();
182+
}
171183
`;
172184

173185
const customMap = ({ name, keyType, valueType }) => `\
@@ -193,7 +205,7 @@ function set(
193205
}
194206
195207
/**
196-
* @dev Removes a value from a set. O(1).
208+
* @dev Removes a value from a map. O(1).
197209
*
198210
* Returns true if the key was removed from the map, that is if it was present.
199211
*/
@@ -216,7 +228,7 @@ function length(${name} storage map) internal view returns (uint256) {
216228
}
217229
218230
/**
219-
* @dev Returns the element stored at position \`index\` in the set. O(1).
231+
* @dev Returns the element stored at position \`index\` in the map. O(1).
220232
* Note that there are no guarantees on the ordering of values inside the
221233
* array, and it may change when more values are added or removed.
222234
*
@@ -262,6 +274,26 @@ function get(
262274
) internal view returns (${valueType}) {
263275
return ${fromBytes32(valueType, `get(map._inner, ${toBytes32(keyType, 'key')}, errorMessage)`)};
264276
}
277+
278+
/**
279+
* @dev Return the an array containing all the keys
280+
*
281+
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
282+
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
283+
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
284+
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
285+
*/
286+
function keys(${name} storage map) internal view returns (${keyType}[] memory) {
287+
bytes32[] memory store = keys(map._inner);
288+
${keyType}[] memory result;
289+
290+
/// @solidity memory-safe-assembly
291+
assembly {
292+
result := store
293+
}
294+
295+
return result;
296+
}
265297
`;
266298

267299
// GENERATE

test/utils/structs/EnumerableMap.behavior.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ function shouldBehaveLikeMap (
3636
}))).to.have.same.deep.members(
3737
zip(keys.map(k => k.toString()), values.map(v => v.toString())),
3838
);
39+
40+
// This also checks that both arrays have the same length
41+
expect(
42+
(await methods.keys(map)).map(k => k.toString()),
43+
).to.have.same.members(
44+
keys.map(key => key.toString()),
45+
);
3946
}
4047

4148
it('starts empty', async function () {

test/utils/structs/EnumerableMap.test.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ contract('EnumerableMap', function (accounts) {
3939
length: '$length_EnumerableMap_AddressToUintMap(uint256)',
4040
at: '$at_EnumerableMap_AddressToUintMap(uint256,uint256)',
4141
contains: '$contains(uint256,address)',
42+
keys: '$keys_EnumerableMap_AddressToUintMap(uint256)',
4243
}),
4344
{
4445
setReturn: 'return$set_EnumerableMap_AddressToUintMap_address_uint256',
@@ -62,6 +63,7 @@ contract('EnumerableMap', function (accounts) {
6263
length: '$length_EnumerableMap_UintToAddressMap(uint256)',
6364
at: '$at_EnumerableMap_UintToAddressMap(uint256,uint256)',
6465
contains: '$contains_EnumerableMap_UintToAddressMap(uint256,uint256)',
66+
keys: '$keys_EnumerableMap_UintToAddressMap(uint256)',
6567
}),
6668
{
6769
setReturn: 'return$set_EnumerableMap_UintToAddressMap_uint256_address',
@@ -85,6 +87,7 @@ contract('EnumerableMap', function (accounts) {
8587
length: '$length_EnumerableMap_Bytes32ToBytes32Map(uint256)',
8688
at: '$at_EnumerableMap_Bytes32ToBytes32Map(uint256,uint256)',
8789
contains: '$contains_EnumerableMap_Bytes32ToBytes32Map(uint256,bytes32)',
90+
keys: '$keys_EnumerableMap_Bytes32ToBytes32Map(uint256)',
8891
}),
8992
{
9093
setReturn: 'return$set_EnumerableMap_Bytes32ToBytes32Map_bytes32_bytes32',
@@ -108,6 +111,7 @@ contract('EnumerableMap', function (accounts) {
108111
length: '$length_EnumerableMap_UintToUintMap(uint256)',
109112
at: '$at_EnumerableMap_UintToUintMap(uint256,uint256)',
110113
contains: '$contains_EnumerableMap_UintToUintMap(uint256,uint256)',
114+
keys: '$keys_EnumerableMap_UintToUintMap(uint256)',
111115
}),
112116
{
113117
setReturn: 'return$set_EnumerableMap_UintToUintMap_uint256_uint256',
@@ -131,6 +135,7 @@ contract('EnumerableMap', function (accounts) {
131135
length: '$length_EnumerableMap_Bytes32ToUintMap(uint256)',
132136
at: '$at_EnumerableMap_Bytes32ToUintMap(uint256,uint256)',
133137
contains: '$contains_EnumerableMap_Bytes32ToUintMap(uint256,bytes32)',
138+
keys: '$keys_EnumerableMap_Bytes32ToUintMap(uint256)',
134139
}),
135140
{
136141
setReturn: 'return$set_EnumerableMap_Bytes32ToUintMap_bytes32_uint256',

0 commit comments

Comments
 (0)