EnumerableSet
是 OpenZeppelin 提供的 Solidity 库中的一个合约,用于管理集合(Set)类型的数据结构。
核心功能介绍
EnumerableSet结构
:
- _values:用于存储集合元素值,
- _indexes:用于记录元素值与索引位置映射关系
struct Set {
// Storage of set values
bytes32[] _values;
// Position of the value in the `values` array, plus 1 because index 0
// means a value is not in the set.
mapping(bytes32 => uint256) _indexes;
}
以下是常用函数和内部实现代码解析:
add(Bytes32Set storage set, bytes32 value) internal returns (bool);
向集合中提及添加元素,如果集合中不存在该元素则添加成功,返回true,否则返回false。
/**
* @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, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._positions[value] = set._values.length;
return true;
} else {
return false;
}
}
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool);
从集合中移除某个元素,如存在移除的值删除后返回true,不存在则返回false。实现步骤为:第一步从_indexes映射中获取要移除的元素(valueA)和最后一个元素(valueB)的索引(indexA、indexB),第二步用_values数组中最后一个元素(valueB)去覆盖要移除的元素(valueA)完成值替换,在_indexes映射中修改元素(indexB)的索引为(indexA)完成索引替换,第三步从_values数组中删掉元素(valueA),_indexes映射中删掉valueA的映射关系(valueA——>indexA)。
/**
* @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, bytes32 value) private 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 _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 toDeleteIndex = valueIndex - 1;
uint256 lastIndex = set._values.length - 1;
if (lastIndex != toDeleteIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the last value to the index where the value to delete is
set._values[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._values.pop();
// Delete the index for the deleted slot
delete set._indexes[value];
return true;
} else {
return false;
}
}
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool);
查询集合中是否包含某个元素, 如有返回true,否则返回false。
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._indexes[value] != 0;
}
function length(Bytes32Set storage set) internal view returns (uint256);
查询集合长度大小值。
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32);
查询集合中某个索引对应的元素值。
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
function values(Bytes32Set storage set) internal view returns (bytes32[] memory);
查询集合存储的所有元素,返回数组。
/**
* @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(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
EnumerableSet
支持Bytes32、Address、Uint256三种数据类型。Address、Uint256类型数据在会转化为Bytes32类型进行存储。
// sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
// and `uint256` (`UintSet`) are supported.
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
// AddressSet
struct AddressSet {
Set _inner;
}
// UintSet
struct UintSet {
Set _inner;
}
EnumerableSet优点介绍
**高效查找:** `EnumerableSet` 提供了对集合元素的高效查找。在集合中查找某个元素的时间复杂度是 O(1)。这使得在集合中快速检查某个元素是否存在,而不需要遍历整个集合。
**可迭代性:** 与普通的 Set 不同,`EnumerableSet` 具有可迭代的特性。你可以方便地遍历集合中的所有元素,而不需要知道元素的具体值。这对于某些应用场景,比如合约状态的查询和遍历,非常有用。
**可操作性:** 提供了丰富的集合操作,如添加、删除、清空等,这些操作都是高效的。这样,开发者可以方便地对集合进行操作而无需担心性能问题。
**与 Solidity 标准兼容:** `EnumerableSet` 是为了与 Solidity 标准兼容而设计的,特别是 ERC 标准。例如,ERC-20 标准要求实现 `balanceOf` 函数时返回的是一个集合,而 `EnumerableSet` 可以方便地满足这个要求。
**类型安全:** `EnumerableSet` 使用泛型,这意味着你可以在编译时检查集合中存储的元素类型,从而提高代码的类型安全性。
**扩展性:** 由于 `EnumerableSet` 提供了可迭代性和高效的集合操作,因此它可以用作更复杂数据结构的基础。例如,可以用它来构建图、映射等更高层次的数据结构。
总体而言,`EnumerableSet` 提供了一种方便、高效且可迭代的集合管理方式,适用于各种 Solidity 合约的开发场景。
使用方式
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
contract GameDB {
using EnumerableSet for EnumerableSet.UintSet;
mapping(uint256 => EnumerableSet.UintSet) private _heroLearnSkills;
function learnSkill(uint256 tokenId, uint256 skillId) external {
uint256[] memory skills = _heroLearnSkills[tokenId].values();
if (_heroLearnSkills[tokenId].contains(skillId)) {
//todo
} else {
_heroLearnSkills[tokenId].add(skillId);
//todo
}
}
}
以上是关于EnumerableSet的介绍,欢迎批评指导!