135 lines
4.8 KiB
Solidity
135 lines
4.8 KiB
Solidity
|
// SPDX-License-Identifier: CC0-1.0
|
||
|
// Based on OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol)
|
||
|
|
||
|
pragma solidity ^0.8.0;
|
||
|
|
||
|
library ActionsSet {
|
||
|
struct Set {
|
||
|
// Storage of action names
|
||
|
string[] _names;
|
||
|
// Storage of action selectors
|
||
|
bytes4[] _selectors;
|
||
|
// Position of the value in the `values` array, plus 1 because index 0
|
||
|
// means a value is not in the set.
|
||
|
mapping(bytes4 => uint256) _indexes;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @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(Set storage set, string memory name) internal returns (bool) {
|
||
|
bytes4 selector = bytes4(keccak256(bytes(name)));
|
||
|
if (!contains(set, selector)) {
|
||
|
set._selectors.push(selector);
|
||
|
set._names.push(name);
|
||
|
// The value is stored at length-1, but we add 1 to all indexes
|
||
|
// and use 0 as a sentinel value
|
||
|
set._indexes[selector] = set._selectors.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(Set storage set, bytes4 value) internal returns (bool) {
|
||
|
// We read and store the value's index to prevent multiple reads from the same storage slot
|
||
|
uint256 valueIndex = set._indexes[value];
|
||
|
|
||
|
if (valueIndex != 0) {
|
||
|
// Equivalent to contains(set, value)
|
||
|
// To delete an element from the _selectors 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 toDeleteIndex = valueIndex - 1;
|
||
|
uint256 lastIndex = set._selectors.length - 1;
|
||
|
|
||
|
if (lastIndex != toDeleteIndex) {
|
||
|
bytes4 lastValue = set._selectors[lastIndex];
|
||
|
|
||
|
// Move the last value to the index where the value to delete is
|
||
|
set._selectors[toDeleteIndex] = lastValue;
|
||
|
// Update the index for the moved value
|
||
|
set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
|
||
|
}
|
||
|
|
||
|
// Delete the slot where the moved value was stored
|
||
|
set._selectors.pop();
|
||
|
|
||
|
// Delete the index for the deleted slot
|
||
|
delete set._indexes[value];
|
||
|
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @dev Returns true if the value is in the set. O(1).
|
||
|
*/
|
||
|
function contains(Set storage set, bytes4 value)
|
||
|
internal
|
||
|
view
|
||
|
returns (bool)
|
||
|
{
|
||
|
return set._indexes[value] != 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @dev Returns the number of values on the set. O(1).
|
||
|
*/
|
||
|
function length(Set storage set) internal view returns (uint256) {
|
||
|
return set._selectors.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(Set storage set, uint256 index) internal view returns (bytes4) {
|
||
|
return set._selectors[index];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @dev Return the entire set of action names
|
||
|
*
|
||
|
* 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 names(Set storage set) internal view returns (string[] memory) {
|
||
|
return set._names;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @dev Return the entire set of action selectors
|
||
|
*
|
||
|
* 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 selectors(Set storage set) internal view returns (bytes4[] memory) {
|
||
|
return set._selectors;
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|