Skip to content

Commit e412bd9

Browse files
committed
Add EnumerableSetExtended and EnumerableMapExtended
1 parent 58c794e commit e412bd9

15 files changed

+1417
-42
lines changed

contracts/utils/README.adoc

+5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t
2323
* {BitMaps}: A simple library to manage boolean value mapped to a numerical index in an efficient way.
2424
* {EnumerableMap}: A type like Solidity's https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`], but with key-value _enumeration_: this will let you know how many entries a mapping has, and iterate over them (which is not possible with `mapping`).
2525
* {EnumerableSet}: Like {EnumerableMap}, but for https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets]. Can be used to store privileged accounts, issued IDs, etc.
26+
* {EnumerableSetExtended} and {EnumerableMapExtended}: Extensions of the `EnumerableSet` and `EnumerableMap` libraries with more types, including non-value types.
2627
* {DoubleEndedQueue}: An implementation of a https://en.wikipedia.org/wiki/Double-ended_queue[double ended queue] whose values can be added or removed from both sides. Useful for FIFO and LIFO structures.
2728
* {CircularBuffer}: A data structure to store the last N values pushed to it.
2829
* {Checkpoints}: A data structure to store values mapped to a strictly increasing key. Can be used for storing and accessing values over time.
@@ -108,8 +109,12 @@ Ethereum contracts have no native concept of an interface, so applications must
108109

109110
{{EnumerableMap}}
110111

112+
{{EnumerableMapExtended}}
113+
111114
{{EnumerableSet}}
112115

116+
{{EnumerableSetExtended}}
117+
113118
{{DoubleEndedQueue}}
114119

115120
{{CircularBuffer}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
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 "./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 {EnumerableMap}
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(
124+
BytesToUintMap storage map,
125+
uint256 index
126+
) internal view returns (bytes memory key, uint256 value) {
127+
key = map._keys.at(index);
128+
value = map._values[key];
129+
}
130+
131+
/**
132+
* @dev Tries to returns the value associated with `key`. O(1).
133+
* Does not revert if `key` is not in the map.
134+
*/
135+
function tryGet(
136+
BytesToUintMap storage map,
137+
bytes memory key
138+
) internal view returns (bool exists, uint256 value) {
139+
value = map._values[key];
140+
exists = value != uint256(0) || contains(map, key);
141+
}
142+
143+
/**
144+
* @dev Returns the value associated with `key`. O(1).
145+
*
146+
* Requirements:
147+
*
148+
* - `key` must be in the map.
149+
*/
150+
function get(BytesToUintMap storage map, bytes memory key) internal view returns (uint256 value) {
151+
bool exists;
152+
(exists, value) = tryGet(map, key);
153+
if (!exists) {
154+
revert EnumerableMapNonexistentBytesKey(key);
155+
}
156+
}
157+
158+
/**
159+
* @dev Return the an array containing all the keys
160+
*
161+
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
162+
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
163+
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
164+
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
165+
*/
166+
function keys(BytesToUintMap storage map) internal view returns (bytes[] memory) {
167+
return map._keys.values();
168+
}
169+
170+
/**
171+
* @dev Query for a nonexistent map key.
172+
*/
173+
error EnumerableMapNonexistentStringKey(string key);
174+
175+
struct StringToStringMap {
176+
// Storage of keys
177+
EnumerableSetExtended.StringSet _keys;
178+
mapping(string key => string) _values;
179+
}
180+
181+
/**
182+
* @dev Adds a key-value pair to a map, or updates the value for an existing
183+
* key. O(1).
184+
*
185+
* Returns true if the key was added to the map, that is if it was not
186+
* already present.
187+
*/
188+
function set(StringToStringMap storage map, string memory key, string memory value) internal returns (bool) {
189+
map._values[key] = value;
190+
return map._keys.add(key);
191+
}
192+
193+
/**
194+
* @dev Removes a key-value pair from a map. O(1).
195+
*
196+
* Returns true if the key was removed from the map, that is if it was present.
197+
*/
198+
function remove(StringToStringMap storage map, string memory key) internal returns (bool) {
199+
delete map._values[key];
200+
return map._keys.remove(key);
201+
}
202+
203+
/**
204+
* @dev Removes all the entries from a map. O(n).
205+
*
206+
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
207+
* function uncallable if the map grows to the point where clearing it consumes too much gas to fit in a block.
208+
*/
209+
function clear(StringToStringMap storage map) internal {
210+
uint256 len = length(map);
211+
for (uint256 i = 0; i < len; ++i) {
212+
delete map._values[map._keys.at(i)];
213+
}
214+
map._keys.clear();
215+
}
216+
217+
/**
218+
* @dev Returns true if the key is in the map. O(1).
219+
*/
220+
function contains(StringToStringMap storage map, string memory key) internal view returns (bool) {
221+
return map._keys.contains(key);
222+
}
223+
224+
/**
225+
* @dev Returns the number of key-value pairs in the map. O(1).
226+
*/
227+
function length(StringToStringMap storage map) internal view returns (uint256) {
228+
return map._keys.length();
229+
}
230+
231+
/**
232+
* @dev Returns the key-value pair stored at position `index` in the map. O(1).
233+
*
234+
* Note that there are no guarantees on the ordering of entries inside the
235+
* array, and it may change when more entries are added or removed.
236+
*
237+
* Requirements:
238+
*
239+
* - `index` must be strictly less than {length}.
240+
*/
241+
function at(
242+
StringToStringMap storage map,
243+
uint256 index
244+
) internal view returns (string memory key, string memory value) {
245+
key = map._keys.at(index);
246+
value = map._values[key];
247+
}
248+
249+
/**
250+
* @dev Tries to returns the value associated with `key`. O(1).
251+
* Does not revert if `key` is not in the map.
252+
*/
253+
function tryGet(
254+
StringToStringMap storage map,
255+
string memory key
256+
) internal view returns (bool exists, string memory value) {
257+
value = map._values[key];
258+
exists = bytes(value).length != 0 || contains(map, key);
259+
}
260+
261+
/**
262+
* @dev Returns the value associated with `key`. O(1).
263+
*
264+
* Requirements:
265+
*
266+
* - `key` must be in the map.
267+
*/
268+
function get(StringToStringMap storage map, string memory key) internal view returns (string memory value) {
269+
bool exists;
270+
(exists, value) = tryGet(map, key);
271+
if (!exists) {
272+
revert EnumerableMapNonexistentStringKey(key);
273+
}
274+
}
275+
276+
/**
277+
* @dev Return the an array containing all the keys
278+
*
279+
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
280+
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
281+
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
282+
* uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block.
283+
*/
284+
function keys(StringToStringMap storage map) internal view returns (string[] memory) {
285+
return map._keys.values();
286+
}
287+
}

0 commit comments

Comments
 (0)