Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/pink-dolls-shop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`EnumerableSetExtended` and `EnumerableMapExtended`: Extensions of the `EnumerableSet` and `EnumerableMap` libraries with more types, including non-value types.
234 changes: 233 additions & 1 deletion contracts/utils/structs/EnumerableMap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import {EnumerableSet} from "./EnumerableSet.sol";
* - `address -> address` (`AddressToAddressMap`) since v5.1.0
* - `address -> bytes32` (`AddressToBytes32Map`) since v5.1.0
* - `bytes32 -> address` (`Bytes32ToAddressMap`) since v5.1.0
* - `bytes -> uint256` (`BytesToUintMap`) since v5.4.0
* - `string -> string` (`StringToStringMap`) since v5.4.0
*
* [WARNING]
* ====
Expand All @@ -51,7 +53,7 @@ import {EnumerableSet} from "./EnumerableSet.sol";
* ====
*/
library EnumerableMap {
using EnumerableSet for EnumerableSet.Bytes32Set;
using EnumerableSet for *;

// To implement this library for multiple types with as little code repetition as possible, we write it in
// terms of a generic Map type with bytes32 keys and values. The Map implementation uses private functions,
Expand Down Expand Up @@ -997,4 +999,234 @@ library EnumerableMap {

return result;
}

/**
* @dev Query for a nonexistent map key.
*/
error EnumerableMapNonexistentBytesKey(bytes key);

struct BytesToUintMap {
// Storage of keys
EnumerableSet.BytesSet _keys;
mapping(bytes key => uint256) _values;
}

/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(BytesToUintMap storage map, bytes memory key, uint256 value) internal returns (bool) {
map._values[key] = value;
return map._keys.add(key);
}

/**
* @dev Removes a key-value pair from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(BytesToUintMap storage map, bytes memory key) internal returns (bool) {
delete map._values[key];
return map._keys.remove(key);
}

/**
* @dev Removes all the entries from a map. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the map grows to the point where clearing it consumes too much gas to fit in a block.
*/
function clear(BytesToUintMap storage map) internal {
uint256 len = length(map);
for (uint256 i = 0; i < len; ++i) {
delete map._values[map._keys.at(i)];
}
map._keys.clear();
}

/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(BytesToUintMap storage map, bytes memory key) internal view returns (bool) {
return map._keys.contains(key);
}

/**
* @dev Returns the number of key-value pairs in the map. O(1).
*/
function length(BytesToUintMap storage map) internal view returns (uint256) {
return map._keys.length();
}

/**
* @dev Returns the key-value pair stored at position `index` in the map. O(1).
*
* Note that there are no guarantees on the ordering of entries inside the
* array, and it may change when more entries are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(BytesToUintMap storage map, uint256 index) internal view returns (bytes memory key, uint256 value) {
key = map._keys.at(index);
value = map._values[key];
}

/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(BytesToUintMap storage map, bytes memory key) internal view returns (bool exists, uint256 value) {
value = map._values[key];
exists = value != uint256(0) || contains(map, key);
}

/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(BytesToUintMap storage map, bytes memory key) internal view returns (uint256 value) {
bool exists;
(exists, value) = tryGet(map, key);
if (!exists) {
revert EnumerableMapNonexistentBytesKey(key);
}
}

/**
* @dev Return the 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
* 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(BytesToUintMap storage map) internal view returns (bytes[] memory) {
return map._keys.values();
}

/**
* @dev Query for a nonexistent map key.
*/
error EnumerableMapNonexistentStringKey(string key);

struct StringToStringMap {
// Storage of keys
EnumerableSet.StringSet _keys;
mapping(string key => string) _values;
}

/**
* @dev Adds a key-value pair to a map, or updates the value for an existing
* key. O(1).
*
* Returns true if the key was added to the map, that is if it was not
* already present.
*/
function set(StringToStringMap storage map, string memory key, string memory value) internal returns (bool) {
map._values[key] = value;
return map._keys.add(key);
}

/**
* @dev Removes a key-value pair from a map. O(1).
*
* Returns true if the key was removed from the map, that is if it was present.
*/
function remove(StringToStringMap storage map, string memory key) internal returns (bool) {
delete map._values[key];
return map._keys.remove(key);
}

/**
* @dev Removes all the entries from a map. O(n).
*
* WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
* function uncallable if the map grows to the point where clearing it consumes too much gas to fit in a block.
*/
function clear(StringToStringMap storage map) internal {
uint256 len = length(map);
for (uint256 i = 0; i < len; ++i) {
delete map._values[map._keys.at(i)];
}
map._keys.clear();
}

/**
* @dev Returns true if the key is in the map. O(1).
*/
function contains(StringToStringMap storage map, string memory key) internal view returns (bool) {
return map._keys.contains(key);
}

/**
* @dev Returns the number of key-value pairs in the map. O(1).
*/
function length(StringToStringMap storage map) internal view returns (uint256) {
return map._keys.length();
}

/**
* @dev Returns the key-value pair stored at position `index` in the map. O(1).
*
* Note that there are no guarantees on the ordering of entries inside the
* array, and it may change when more entries are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(
StringToStringMap storage map,
uint256 index
) internal view returns (string memory key, string memory value) {
key = map._keys.at(index);
value = map._values[key];
}

/**
* @dev Tries to returns the value associated with `key`. O(1).
* Does not revert if `key` is not in the map.
*/
function tryGet(
StringToStringMap storage map,
string memory key
) internal view returns (bool exists, string memory value) {
value = map._values[key];
exists = bytes(value).length != 0 || contains(map, key);
}

/**
* @dev Returns the value associated with `key`. O(1).
*
* Requirements:
*
* - `key` must be in the map.
*/
function get(StringToStringMap storage map, string memory key) internal view returns (string memory value) {
bool exists;
(exists, value) = tryGet(map, key);
if (!exists) {
revert EnumerableMapNonexistentStringKey(key);
}
}

/**
* @dev Return the 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
* 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(StringToStringMap storage map) internal view returns (string[] memory) {
return map._keys.values();
}
}
Loading