LOADING

加载过慢请开启缓存 浏览器默认开启

Code review: Openzeppelin-AccessControlEnumerable

2023/6/26 Solidity Coding

概述:

合约的作用是扩展了 OpenZeppelin Contracts 中的访问控制合约(AccessControl),使其具备了角色成员的枚举功能。具体而言,该合约提供了以下功能:

  1. 支持枚举角色成员:可以根据角色获取具有该角色的账户列表,以及获取角色成员的数量。
  2. 重写了 supportsInterface 函数,使合约能够判断是否支持 IAccessControlEnumerable 接口。
  3. 重写了 _grantRole_revokeRole 函数,用于在授予和撤销角色时更新角色成员的地址集合。

合约的内容如下:

  • 导入了 IAccessControlEnumerable.solAccessControl.solEnumerableSet.sol 这三个合约。
  • 声明了一个抽象合约 AccessControlEnumerable,继承自 IAccessControlEnumerableAccessControl 合约。
  • 使用 using 语句导入了 EnumerableSet 库,以方便在合约中使用地址集合的操作。
  • 声明了一个私有映射 _roleMembers,用于将角色映射到对应的地址集合。
  • 重写了 supportsInterface 函数,用于判断是否支持特定接口。
  • 实现了 getRoleMember 函数,用于返回具有指定角色的账户中的一个成员。
  • 实现了 getRoleMemberCount 函数,用于返回具有指定角色的账户数量。
  • 重写了 _grantRole 函数,在授予角色时同时更新了地址集合。
  • 重写了 _revokeRole 函数,在撤销角色时同时更新了地址集合。

总体而言,该合约提供了一种方便的方式来管理和枚举角色成员,对于需要在智能合约中实现访问控制的场景非常有用。

完整代码:

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControlEnumerable.sol)

pragma solidity ^0.8.0;

import "./IAccessControlEnumerable.sol";
import "./AccessControl.sol";
import "../utils/structs/EnumerableSet.sol";

/**
 * @dev Extension of {AccessControl} that allows enumerating the members of each role.
 */
abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {
    using EnumerableSet for EnumerableSet.AddressSet;

    mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers;

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * @dev Returns one of the accounts that have `role`. `index` must be a
     * value between 0 and {getRoleMemberCount}, non-inclusive.
     *
     * Role bearers are not sorted in any particular way, and their ordering may
     * change at any point.
     *
     * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
     * you perform all queries on the same block. See the following
     * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
     * for more information.
     */
    function getRoleMember(bytes32 role, uint256 index) public view virtual override returns (address) {
        return _roleMembers[role].at(index);
    }

    /**
     * @dev Returns the number of accounts that have `role`. Can be used
     * together with {getRoleMember} to enumerate all bearers of a role.
     */
    function getRoleMemberCount(bytes32 role) public view virtual override returns (uint256) {
        return _roleMembers[role].length();
    }

    /**
     * @dev Overload {_grantRole} to track enumerable memberships
     */
    function _grantRole(bytes32 role, address account) internal virtual override {
        super._grantRole(role, account);
        _roleMembers[role].add(account);
    }

    /**
     * @dev Overload {_revokeRole} to track enumerable memberships
     */
    function _revokeRole(bytes32 role, address account) internal virtual override {
        super._revokeRole(role, account);
        _roleMembers[role].remove(account);
    }
}

代码分析:

pragma solidity ^0.8.0;

这段代码是 Solidity 合约的声明部分,它指定了 Solidity 的版本要求。具体来说,pragma solidity ^0.8.0; 表示该合约需要使用 Solidity 0.8.0 或更高版本进行编译。

pragma 是 Solidity 的编译指示符,用于指定编译器的行为和版本要求。在这里,^0.8.0 中的 ^ 符号表示向后兼容性。它告诉编译器可以使用 0.8.0 及更高版本的 Solidity,但不包括 0.9.0 或更高版本的主要升级。这是为了确保合约能够在 0.8.0 或更新的版本中正常编译和运行,但不允许在具有不同主要版本的编译器中编译。

在 Solidity 中,pragma 语句通常用于指定编译器版本、优化选项和代码验证等指令。该指示符对于确保合约在特定 Solidity 版本下具有正确的行为非常重要。

import "./IAccessControlEnumerable.sol";
import "./AccessControl.sol";
import "../utils/structs/EnumerableSet.sol";

这段代码主要是导入了三个合约,分别是 IAccessControlEnumerable.solAccessControl.solEnumerableSet.sol:

  1. IAccessControlEnumerable.sol:这是一个接口合约,定义了访问控制中的角色枚举功能。它可能包含了枚举角色成员的函数声明,例如 getRoleMembergetRoleMemberCount

  2. AccessControl.sol:这是一个访问控制合约,实现了基本的角色授权和访问控制功能。它包含了授予角色、撤销角色、检查角色授权等功能。AccessControl 合约可能是 IAccessControlEnumerable 接口的实现合约。

  3. EnumerableSet.sol:这是一个通用的集合库合约,用于存储和操作元素的集合。它提供了添加、删除、查询元素等操作,并支持对集合中的元素进行迭代和枚举。在本代码中,EnumerableSet 库可能被用于管理角色成员的地址集合。

综合来看,这段代码的作用是导入了一些必要的合约,为后续的智能合约开发提供了所需的功能和工具。通过导入 IAccessControlEnumerable.solAccessControl.sol,可以使用访问控制的角色授权功能,并通过导入 EnumerableSet.sol,可以方便地处理地址集合相关的操作。

abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {
    using EnumerableSet for EnumerableSet.AddressSet;

    mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers;

这段代码定义了一个抽象合约 AccessControlEnumerable,该合约是 IAccessControlEnumerableAccessControl 合约的子合约。它引入了 EnumerableSet 库,并声明了一个名为 _roleMembers 的私有映射。

作用意义:

  • 扩展了 AccessControl 合约,为其添加了角色成员的枚举功能。
  • 通过 _roleMembers 映射,记录了每个角色对应的地址集合。

算法实现:

  • 使用 using 语句导入了 EnumerableSet 库,使得在合约中可以直接使用地址集合的操作函数。
  • _roleMembers 是一个私有映射,使用 bytes32 类型的角色哈希值作为键,将其映射到对应的地址集合 EnumerableSet.AddressSet
  • _roleMembers 的访问权限是私有的,意味着只有合约内部可以访问和修改该映射。
  • 这个映射的作用是存储每个角色对应的地址集合,用于记录具有特定角色的账户列表。

该代码段的作用是在访问控制合约的基础上扩展了角色成员的枚举功能。通过使用 _roleMembers 映射来记录每个角色的成员,合约可以方便地获取具有特定角色的账户列表,并进行相关操作。这种实现方式使用了映射和地址集合的组合,提供了高效和灵活的角色管理机制。

function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);
    }

这段代码是一个函数 supportsInterface 的实现,它是一个公共视图函数(public view),用于判断合约是否支持特定的接口。

函数的作用和意义如下:

  1. 参数 interfaceId:接受一个 bytes4 类型的参数 interfaceId,表示待检查的接口ID。

  2. 返回值:返回一个布尔值,表示合约是否支持给定的接口。

  3. type(IAccessControlEnumerable).interfaceId:使用 type 关键字获取 IAccessControlEnumerable 接口的标识符(ID)。

  4. super.supportsInterface(interfaceId):调用基础合约的 supportsInterface 函数,检查合约是否支持给定的接口。

  5. interfaceId == type(IAccessControlEnumerable).interfaceId:将 interfaceIdIAccessControlEnumerable 接口的标识符进行比较。

  6. 返回值计算:如果 interfaceId 等于 IAccessControlEnumerable 接口的标识符,或者基础合约的 supportsInterface 函数返回 true,则返回 true;否则,返回 false

这段代码实现了以下算法:

  1. 首先,将给定的接口ID interfaceIdIAccessControlEnumerable 接口的标识符进行比较。如果它们相等,表示当前合约支持 IAccessControlEnumerable 接口。

  2. 如果比较结果为 true,则直接返回 true,表示合约支持该接口。

  3. 如果比较结果为 false,则调用基础合约的 supportsInterface 函数,检查基础合约是否支持给定的接口。

  4. 如果基础合约的 supportsInterface 函数返回 true,则说明基础合约支持该接口,因此当前合约也支持该接口,返回 true

  5. 如果以上两个条件都不满足,则表示当前合约不支持该接口,返回 false

总结起来,该函数的作用是检查合约是否支持特定的接口,它首先检查当前合约是否支持 IAccessControlEnumerable 接口,如果不支持,则通过调用基础合约的 supportsInterface 函数来判断是否支持。这样可以实现接口的递归检查,确保正确判断合约的接口支持情况。

function getRoleMember(bytes32 role, uint256 index) public view virtual override returns (address) {
        return _roleMembers[role].at(index);
    }

这段代码是合约中的一个函数,函数名为 getRoleMember,用于获取具有指定角色的成员列表中的一个成员。以下是对代码作用和实现算法的详细分析:

作用意义:
该函数的作用是返回指定角色的成员列表中的一个成员。开发人员可以通过传入角色和索引参数来访问角色成员列表中的具体成员。

算法实现:

  1. 函数接收两个参数:role(角色)和 index(索引)。
  2. 函数声明为 public,表示任何人都可以调用该函数。
  3. 函数声明为 view,表示它不会修改合约状态。
  4. 函数使用 virtual 关键字进行声明,表示它可以被派生合约重写。
  5. 函数覆盖了父合约中的同名函数 getRoleMember,使用 override 关键字标识。
  6. 函数内部通过访问私有映射 _roleMembers 来获取指定角色的成员列表。
  7. 使用角色作为键,从 _roleMembers 映射中取出对应的地址集合。
  8. 调用地址集合的 at 函数,并传入索引参数 index,以获取具体的成员地址。
  9. 函数返回获取到的成员地址。

算法的具体实现依赖于 _roleMembers 映射和 EnumerableSet 库中的 at 函数。通过使用角色作为键访问 _roleMembers 映射,可以获取到与该角色对应的地址集合。然后,使用 at 函数和传入的索引参数,从地址集合中获取到具体的成员地址。最后,将该地址作为函数的返回值返回。

总结:
这段代码实现了一个函数,用于获取具有指定角色的成员列表中的一个成员。函数通过访问 _roleMembers 映射和地址集合的 at 函数,提供了一种便捷的方式来获取角色成员列表中的具体成员。这对于需要对角色成员进行访问和操作的场景非常有用。

function getRoleMemberCount(bytes32 role) public view virtual override returns (uint256) {
        return _roleMembers[role].length();
    }

这段代码实现了一个名为getRoleMemberCount的函数,用于返回具有指定角色的账户数量。

函数的作用意义是帮助用户获取指定角色的账户数量,以便于在访问控制或权限管理的场景中进行统计或判断。它可以与其他函数结合使用,例如getRoleMember函数,用于枚举指定角色的所有成员。

算法实现:

  1. 函数使用 publicview 关键字修饰,表示它是一个公开的只读函数,不会修改合约状态,仅用于查询。
  2. 函数接受一个 bytes32 类型的参数 role,用于指定要查询的角色。
  3. 函数返回一个 uint256 类型的值,表示具有指定角色的账户数量。
  4. 在函数体内,通过访问私有映射 _roleMembers,使用指定的 role 作为键来获取对应的地址集合。
  5. 使用地址集合的 length 函数,返回地址集合的长度,即具有指定角色的账户数量。
  6. 最后,函数将具有指定角色的账户数量作为结果返回。

该函数的实现非常简单,它利用了私有映射 _roleMembers 中存储的地址集合,通过调用集合的 length 函数来获取账户数量。通过这个函数,用户可以方便地获取指定角色的账户数量,以便于后续的访问控制或权限管理操作。

function _grantRole(bytes32 role, address account) internal virtual override {
        super._grantRole(role, account);
        _roleMembers[role].add(account);
    }

这段代码是一个内部函数 _grantRole,它被定义为 internal(内部可见性)和 virtual(可以被派生合约重写)。以下是代码的作用和实现的详细分析:

作用:

  • _grantRole 函数用于授予角色给指定的账户。
  • 它在授予角色的同时更新了角色成员的地址集合 _roleMembers

实现:

  1. super._grantRole(role, account):调用基础合约(即 AccessControl 合约)中的 _grantRole 函数,授予角色给指定的账户。这个调用确保了在子合约中重写的 _grantRole 函数能够保留基础合约中的行为。

  2. _roleMembers[role].add(account):将指定账户 account 添加到对应角色 role 的地址集合 _roleMembers[role] 中。这里使用了 EnumerableSet 库中的 add 函数来实现。

整体而言,该函数的目的是在授予角色给指定账户时,更新 _roleMembers 中对应角色的地址集合,以便能够方便地查询和管理角色成员。通过调用基础合约的 _grantRole 函数,确保了基础合约中定义的行为也会被执行。

function _revokeRole(bytes32 role, address account) internal virtual override {
        super._revokeRole(role, account);
        _roleMembers[role].remove(account);
    }

这段代码是 _revokeRole 函数的实现,它是在 AccessControlEnumerable 合约中重写的一个内部函数。

作用意义: _revokeRole 函数的作用是撤销给定角色(role)的特定账户(account)的角色授权。具体而言,该函数执行以下两个操作:

  1. 调用父合约的 _revokeRole 函数,撤销账户的角色授权。
  2. _roleMembers 映射中将账户从相应角色的地址集合中移除。

算法实现:

  1. 首先,使用 super 关键字调用父合约的 _revokeRole 函数,将角色授权撤销。
  2. 接下来,使用 _roleMembers[role].remove(account) 语句从 _roleMembers 映射中移除指定账户。
    • _roleMembers[role] 表达式获取指定角色的地址集合。
    • remove(account) 调用 EnumerableSet 库的 remove 函数,从地址集合中移除指定账户。

这段代码的目的是在撤销角色授权时,同时更新 _roleMembers 映射中的地址集合,确保角色成员的数据一致性。通过调用父合约的 _revokeRole 函数和移除地址集合中的账户,该函数实现了撤销角色授权的完整逻辑。