Skip to content

Commit 47cbc63

Browse files
Amxxernestognw
andauthored
Add EnumerableSetExtended and EnumerableMapExtended (#89)
Co-authored-by: ernestognw <[email protected]>
1 parent 30ed769 commit 47cbc63

16 files changed

+1427
-3
lines changed

.github/workflows/checks.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ jobs:
4545
run: npm run test:inheritance
4646
- name: Check pragma consistency between files
4747
run: npm run test:pragma
48+
- name: Check procedurally generated contracts are up-to-date
49+
run: npm run test:generation
4850

4951
coverage:
5052
runs-on: ubuntu-latest

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## XX-XX-XXXX
2+
3+
- `EnumerableSetExtended` and `EnumerableMapExtended`: Extensions of the `EnumerableSet` and `EnumerableMap` libraries with more types, including non-value types.
4+
15
## 03-04-2025
26

37
- `PaymasterERC20`: Extension of `PaymasterCore` that sponsors user operations against payment in ERC-20 tokens.

contracts/utils/README.adoc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t
99
* {ERC7739}: An abstract contract to validate signatures following the rehashing scheme from `ERC7739Utils`.
1010
* {ERC7739Utils}: Utilities library that implements a defensive rehashing mechanism to prevent replayability of smart contract signatures based on ERC-7739.
1111
* {SignerECDSA}, {SignerP256}, {SignerRSA}: Implementations of an {AbstractSigner} with specific signature validation algorithms.
12+
* {EnumerableSetExtended} and {EnumerableMapExtended}: Extensions of the `EnumerableSet` and `EnumerableMap` libraries with more types, including non-value types.
1213
* {Masks}: Library to handle `bytes32` masks.
1314

1415
== Cryptography
@@ -27,6 +28,12 @@ Miscellaneous contracts and libraries containing utility functions you can use t
2728

2829
{{SignerRSA}}
2930

31+
== Structs
32+
33+
{{EnumerableSetExtended}}
34+
35+
{{EnumerableMapExtended}}
36+
3037
== Libraries
3138

3239
{{Masks}}
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
// SPDX-License-Identifier: MIT
2+
// This file was procedurally generated from scripts/generate/templates/EnumerableMapExtended.js.
3+
4+
pragma solidity ^0.8.20;
5+
6+
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
7+
import {EnumerableSetExtended} from "./EnumerableSetExtended.sol";
8+
9+
/**
10+
* @dev Library for managing an enumerable variant of Solidity's
11+
* https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`]
12+
* type for non-value types as keys.
13+
*
14+
* Maps have the following properties:
15+
*
16+
* - Entries are added, removed, and checked for existence in constant time
17+
* (O(1)).
18+
* - Entries are enumerated in O(n). No guarantees are made on the ordering.
19+
* - Map can be cleared (all entries removed) in O(n).
20+
*
21+
* ```solidity
22+
* contract Example {
23+
* // Add the library methods
24+
* using EnumerableMapExtended for EnumerableMapExtended.BytesToUintMap;
25+
*
26+
* // Declare a set state variable
27+
* EnumerableMapExtended.BytesToUintMap private myMap;
28+
* }
29+
* ```
30+
*
31+
* The following map types are supported:
32+
*
33+
* - `bytes -> uint256` (`BytesToUintMap`)
34+
* - `string -> string` (`StringToStringMap`)
35+
*
36+
* [WARNING]
37+
* ====
38+
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
39+
* unusable.
40+
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
41+
*
42+
* In order to clean an EnumerableMap, you can either remove all elements one by one or create a fresh instance using an
43+
* array of EnumerableMap.
44+
* ====
45+
*
46+
* NOTE: Extensions of openzeppelin/contracts/utils/struct/EnumerableMap.sol.
47+
*/
48+
library EnumerableMapExtended {
49+
using EnumerableSet for *;
50+
using EnumerableSetExtended for *;
51+
52+
/**
53+
* @dev Query for a nonexistent map key.
54+
*/
55+
error EnumerableMapNonexistentBytesKey(bytes key);
56+
57+
struct BytesToUintMap {
58+
// Storage of keys
59+
EnumerableSetExtended.BytesSet _keys;
60+
mapping(bytes key => uint256) _values;
61+
}
62+
63+
/**
64+
* @dev Adds a key-value pair to a map, or updates the value for an existing
65+
* key. O(1).
66+
*
67+
* Returns true if the key was added to the map, that is if it was not
68+
* already present.
69+
*/
70+
function set(BytesToUintMap storage map, bytes memory key, uint256 value) internal returns (bool) {
71+
map._values[key] = value;
72+
return map._keys.add(key);
73+
}
74+
75+
/**
76+
* @dev Removes a key-value pair from a map. O(1).
77+
*
78+
* Returns true if the key was removed from the map, that is if it was present.
79+
*/
80+
function remove(BytesToUintMap storage map, bytes memory key) internal returns (bool) {
81+
delete map._values[key];
82+
return map._keys.remove(key);
83+
}
84+
85+
/**
86+
* @dev Removes all the entries from a map. O(n).
87+
*
88+
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
89+
* function uncallable if the map grows to the point where clearing it consumes too much gas to fit in a block.
90+
*/
91+
function clear(BytesToUintMap storage map) internal {
92+
uint256 len = length(map);
93+
for (uint256 i = 0; i < len; ++i) {
94+
delete map._values[map._keys.at(i)];
95+
}
96+
map._keys.clear();
97+
}
98+
99+
/**
100+
* @dev Returns true if the key is in the map. O(1).
101+
*/
102+
function contains(BytesToUintMap storage map, bytes memory key) internal view returns (bool) {
103+
return map._keys.contains(key);
104+
}
105+
106+
/**
107+
* @dev Returns the number of key-value pairs in the map. O(1).
108+
*/
109+
function length(BytesToUintMap storage map) internal view returns (uint256) {
110+
return map._keys.length();
111+
}
112+
113+
/**
114+
* @dev Returns the key-value pair stored at position `index` in the map. O(1).
115+
*
116+
* Note that there are no guarantees on the ordering of entries inside the
117+
* array, and it may change when more entries are added or removed.
118+
*
119+
* Requirements:
120+
*
121+
* - `index` must be strictly less than {length}.
122+
*/
123+
function at(BytesToUintMap storage map, uint256 index) internal view returns (bytes memory key, uint256 value) {
124+
key = map._keys.at(index);
125+
value = map._values[key];
126+
}
127+
128+
/**
129+
* @dev Tries to returns the value associated with `key`. O(1).
130+
* Does not revert if `key` is not in the map.
131+
*/
132+
function tryGet(BytesToUintMap storage map, bytes memory key) internal view returns (bool exists, uint256 value) {
133+
value = map._values[key];
134+
exists = value != uint256(0) || contains(map, key);
135+
}
136+
137+
/**
138+
* @dev Returns the value associated with `key`. O(1).
139+
*
140+
* Requirements:
141+
*
142+
* - `key` must be in the map.
143+
*/
144+
function get(BytesToUintMap storage map, bytes memory key) internal view returns (uint256 value) {
145+
bool exists;
146+
(exists, value) = tryGet(map, key);
147+
if (!exists) {
148+
revert EnumerableMapNonexistentBytesKey(key);
149+
}
150+
}
151+
152+
/**
153+
* @dev Return the an array containing all the keys
154+
*
155+
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
156+
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
157+
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
158+
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
159+
*/
160+
function keys(BytesToUintMap storage map) internal view returns (bytes[] memory) {
161+
return map._keys.values();
162+
}
163+
164+
/**
165+
* @dev Query for a nonexistent map key.
166+
*/
167+
error EnumerableMapNonexistentStringKey(string key);
168+
169+
struct StringToStringMap {
170+
// Storage of keys
171+
EnumerableSetExtended.StringSet _keys;
172+
mapping(string key => string) _values;
173+
}
174+
175+
/**
176+
* @dev Adds a key-value pair to a map, or updates the value for an existing
177+
* key. O(1).
178+
*
179+
* Returns true if the key was added to the map, that is if it was not
180+
* already present.
181+
*/
182+
function set(StringToStringMap storage map, string memory key, string memory value) internal returns (bool) {
183+
map._values[key] = value;
184+
return map._keys.add(key);
185+
}
186+
187+
/**
188+
* @dev Removes a key-value pair from a map. O(1).
189+
*
190+
* Returns true if the key was removed from the map, that is if it was present.
191+
*/
192+
function remove(StringToStringMap storage map, string memory key) internal returns (bool) {
193+
delete map._values[key];
194+
return map._keys.remove(key);
195+
}
196+
197+
/**
198+
* @dev Removes all the entries from a map. O(n).
199+
*
200+
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
201+
* function uncallable if the map grows to the point where clearing it consumes too much gas to fit in a block.
202+
*/
203+
function clear(StringToStringMap storage map) internal {
204+
uint256 len = length(map);
205+
for (uint256 i = 0; i < len; ++i) {
206+
delete map._values[map._keys.at(i)];
207+
}
208+
map._keys.clear();
209+
}
210+
211+
/**
212+
* @dev Returns true if the key is in the map. O(1).
213+
*/
214+
function contains(StringToStringMap storage map, string memory key) internal view returns (bool) {
215+
return map._keys.contains(key);
216+
}
217+
218+
/**
219+
* @dev Returns the number of key-value pairs in the map. O(1).
220+
*/
221+
function length(StringToStringMap storage map) internal view returns (uint256) {
222+
return map._keys.length();
223+
}
224+
225+
/**
226+
* @dev Returns the key-value pair stored at position `index` in the map. O(1).
227+
*
228+
* Note that there are no guarantees on the ordering of entries inside the
229+
* array, and it may change when more entries are added or removed.
230+
*
231+
* Requirements:
232+
*
233+
* - `index` must be strictly less than {length}.
234+
*/
235+
function at(
236+
StringToStringMap storage map,
237+
uint256 index
238+
) internal view returns (string memory key, string memory value) {
239+
key = map._keys.at(index);
240+
value = map._values[key];
241+
}
242+
243+
/**
244+
* @dev Tries to returns the value associated with `key`. O(1).
245+
* Does not revert if `key` is not in the map.
246+
*/
247+
function tryGet(
248+
StringToStringMap storage map,
249+
string memory key
250+
) internal view returns (bool exists, string memory value) {
251+
value = map._values[key];
252+
exists = bytes(value).length != 0 || contains(map, key);
253+
}
254+
255+
/**
256+
* @dev Returns the value associated with `key`. O(1).
257+
*
258+
* Requirements:
259+
*
260+
* - `key` must be in the map.
261+
*/
262+
function get(StringToStringMap storage map, string memory key) internal view returns (string memory value) {
263+
bool exists;
264+
(exists, value) = tryGet(map, key);
265+
if (!exists) {
266+
revert EnumerableMapNonexistentStringKey(key);
267+
}
268+
}
269+
270+
/**
271+
* @dev Return the an array containing all the keys
272+
*
273+
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
274+
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
275+
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
276+
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
277+
*/
278+
function keys(StringToStringMap storage map) internal view returns (string[] memory) {
279+
return map._keys.values();
280+
}
281+
}

0 commit comments

Comments
 (0)