Add non-value types in EnumerableSet and EnumerableMap (#5658)
Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>
This commit is contained in:
@ -39,6 +39,7 @@ 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 -> bytes` (`BytesToBytesMap`) since v5.4.0
|
||||
*
|
||||
* [WARNING]
|
||||
* ====
|
||||
@ -51,7 +52,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,
|
||||
@ -997,4 +998,122 @@ library EnumerableMap {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Query for a nonexistent map key.
|
||||
*/
|
||||
error EnumerableMapNonexistentBytesKey(bytes key);
|
||||
|
||||
struct BytesToBytesMap {
|
||||
// Storage of keys
|
||||
EnumerableSet.BytesSet _keys;
|
||||
mapping(bytes key => bytes) _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(BytesToBytesMap storage map, bytes memory key, bytes 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(BytesToBytesMap 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(BytesToBytesMap 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(BytesToBytesMap 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(BytesToBytesMap 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(
|
||||
BytesToBytesMap storage map,
|
||||
uint256 index
|
||||
) internal view returns (bytes memory key, bytes 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(
|
||||
BytesToBytesMap storage map,
|
||||
bytes memory key
|
||||
) internal view returns (bool exists, bytes 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(BytesToBytesMap storage map, bytes memory key) internal view returns (bytes memory 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(BytesToBytesMap storage map) internal view returns (bytes[] memory) {
|
||||
return map._keys.values();
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,8 +28,13 @@ import {Arrays} from "../Arrays.sol";
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
|
||||
* and `uint256` (`UintSet`) are supported.
|
||||
* The following types are supported:
|
||||
*
|
||||
* - `bytes32` (`Bytes32Set`) since v3.3.0
|
||||
* - `address` (`AddressSet`) since v3.3.0
|
||||
* - `uint256` (`UintSet`) since v3.3.0
|
||||
* - `string` (`StringSet`) since v5.4.0
|
||||
* - `bytes` (`BytesSet`) since v5.4.0
|
||||
*
|
||||
* [WARNING]
|
||||
* ====
|
||||
@ -419,4 +424,244 @@ library EnumerableSet {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
struct StringSet {
|
||||
// Storage of set values
|
||||
string[] _values;
|
||||
// Position is the index of the value in the `values` array plus 1.
|
||||
// Position 0 is used to mean a value is not in the set.
|
||||
mapping(string value => uint256) _positions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Add a value to a set. O(1).
|
||||
*
|
||||
* Returns true if the value was added to the set, that is if it was not
|
||||
* already present.
|
||||
*/
|
||||
function add(StringSet storage self, string memory value) internal returns (bool) {
|
||||
if (!contains(self, value)) {
|
||||
self._values.push(value);
|
||||
// The value is stored at length-1, but we add 1 to all indexes
|
||||
// and use 0 as a sentinel value
|
||||
self._positions[value] = self._values.length;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Removes a value from a set. O(1).
|
||||
*
|
||||
* Returns true if the value was removed from the set, that is if it was
|
||||
* present.
|
||||
*/
|
||||
function remove(StringSet storage self, string memory value) internal returns (bool) {
|
||||
// We cache the value's position to prevent multiple reads from the same storage slot
|
||||
uint256 position = self._positions[value];
|
||||
|
||||
if (position != 0) {
|
||||
// Equivalent to contains(self, value)
|
||||
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
|
||||
// the array, and then remove the last element (sometimes called as 'swap and pop').
|
||||
// This modifies the order of the array, as noted in {at}.
|
||||
|
||||
uint256 valueIndex = position - 1;
|
||||
uint256 lastIndex = self._values.length - 1;
|
||||
|
||||
if (valueIndex != lastIndex) {
|
||||
string memory lastValue = self._values[lastIndex];
|
||||
|
||||
// Move the lastValue to the index where the value to delete is
|
||||
self._values[valueIndex] = lastValue;
|
||||
// Update the tracked position of the lastValue (that was just moved)
|
||||
self._positions[lastValue] = position;
|
||||
}
|
||||
|
||||
// Delete the slot where the moved value was stored
|
||||
self._values.pop();
|
||||
|
||||
// Delete the tracked position for the deleted slot
|
||||
delete self._positions[value];
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Removes all the values from a set. 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 set grows to the point where clearing it consumes too much gas to fit in a block.
|
||||
*/
|
||||
function clear(StringSet storage set) internal {
|
||||
uint256 len = length(set);
|
||||
for (uint256 i = 0; i < len; ++i) {
|
||||
delete set._positions[set._values[i]];
|
||||
}
|
||||
Arrays.unsafeSetLength(set._values, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns true if the value is in the set. O(1).
|
||||
*/
|
||||
function contains(StringSet storage self, string memory value) internal view returns (bool) {
|
||||
return self._positions[value] != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the number of values on the set. O(1).
|
||||
*/
|
||||
function length(StringSet storage self) internal view returns (uint256) {
|
||||
return self._values.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the value stored at position `index` in the set. O(1).
|
||||
*
|
||||
* Note that there are no guarantees on the ordering of values inside the
|
||||
* array, and it may change when more values are added or removed.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - `index` must be strictly less than {length}.
|
||||
*/
|
||||
function at(StringSet storage self, uint256 index) internal view returns (string memory) {
|
||||
return self._values[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Return the entire 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 self) internal view returns (string[] memory) {
|
||||
return self._values;
|
||||
}
|
||||
|
||||
struct BytesSet {
|
||||
// Storage of set values
|
||||
bytes[] _values;
|
||||
// Position is the index of the value in the `values` array plus 1.
|
||||
// Position 0 is used to mean a value is not in the set.
|
||||
mapping(bytes value => uint256) _positions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Add a value to a set. O(1).
|
||||
*
|
||||
* Returns true if the value was added to the set, that is if it was not
|
||||
* already present.
|
||||
*/
|
||||
function add(BytesSet storage self, bytes memory value) internal returns (bool) {
|
||||
if (!contains(self, value)) {
|
||||
self._values.push(value);
|
||||
// The value is stored at length-1, but we add 1 to all indexes
|
||||
// and use 0 as a sentinel value
|
||||
self._positions[value] = self._values.length;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Removes a value from a set. O(1).
|
||||
*
|
||||
* Returns true if the value was removed from the set, that is if it was
|
||||
* present.
|
||||
*/
|
||||
function remove(BytesSet storage self, bytes memory value) internal returns (bool) {
|
||||
// We cache the value's position to prevent multiple reads from the same storage slot
|
||||
uint256 position = self._positions[value];
|
||||
|
||||
if (position != 0) {
|
||||
// Equivalent to contains(self, value)
|
||||
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
|
||||
// the array, and then remove the last element (sometimes called as 'swap and pop').
|
||||
// This modifies the order of the array, as noted in {at}.
|
||||
|
||||
uint256 valueIndex = position - 1;
|
||||
uint256 lastIndex = self._values.length - 1;
|
||||
|
||||
if (valueIndex != lastIndex) {
|
||||
bytes memory lastValue = self._values[lastIndex];
|
||||
|
||||
// Move the lastValue to the index where the value to delete is
|
||||
self._values[valueIndex] = lastValue;
|
||||
// Update the tracked position of the lastValue (that was just moved)
|
||||
self._positions[lastValue] = position;
|
||||
}
|
||||
|
||||
// Delete the slot where the moved value was stored
|
||||
self._values.pop();
|
||||
|
||||
// Delete the tracked position for the deleted slot
|
||||
delete self._positions[value];
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Removes all the values from a set. 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 set grows to the point where clearing it consumes too much gas to fit in a block.
|
||||
*/
|
||||
function clear(BytesSet storage set) internal {
|
||||
uint256 len = length(set);
|
||||
for (uint256 i = 0; i < len; ++i) {
|
||||
delete set._positions[set._values[i]];
|
||||
}
|
||||
Arrays.unsafeSetLength(set._values, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns true if the value is in the set. O(1).
|
||||
*/
|
||||
function contains(BytesSet storage self, bytes memory value) internal view returns (bool) {
|
||||
return self._positions[value] != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the number of values on the set. O(1).
|
||||
*/
|
||||
function length(BytesSet storage self) internal view returns (uint256) {
|
||||
return self._values.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the value stored at position `index` in the set. O(1).
|
||||
*
|
||||
* Note that there are no guarantees on the ordering of values inside the
|
||||
* array, and it may change when more values are added or removed.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - `index` must be strictly less than {length}.
|
||||
*/
|
||||
function at(BytesSet storage self, uint256 index) internal view returns (bytes memory) {
|
||||
return self._values[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Return the entire 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 self) internal view returns (bytes[] memory) {
|
||||
return self._values;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user