// 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; } }