Michael.W基于Foundry精读Openzeppelin

2024年08月13日更新 120 人订阅
专栏简介 Michael.W基于Foundry精读Openzeppelin第8期——Context.sol Michael.W基于Foundry精读Openzeppelin第1期——Address.sol Michael.W基于Foundry精读Openzeppelin第2期——StorageSlot.sol Michael.W基于Foundry精读Openzeppelin第3期——Arrays.sol Michael.W基于Foundry精读Openzeppelin第4期——Base64.sol Michael.W基于Foundry精读Openzeppelin第5期——Counters.sol Michael.W基于Foundry精读Openzeppelin第6期——Strings.sol Michael.W基于Foundry精读Openzeppelin第7期——Timers.sol Michael.W基于Foundry精读Openzeppelin第9期——Multicall.sol Michael.W基于Foundry精读Openzeppelin第10期——Create2.sol Michael.W基于Foundry精读Openzeppelin第11期——Math.sol Michael.W基于Foundry精读Openzeppelin第12期——SafeCast.sol Michael.W基于Foundry精读Openzeppelin第13期——Checkpoints.sol Michael.W基于Foundry精读Openzeppelin第14期——SafeMath.sol Michael.W基于Foundry精读Openzeppelin第15期——SignedMath.sol Michael.W基于Foundry精读Openzeppelin第16期——SignedSafeMath.sol Michael.W基于Foundry精读Openzeppelin第17期——BitMaps.sol Michael.W基于Foundry精读Openzeppelin第18期——DoubleEndedQueue.sol Michael.W基于Foundry精读Openzeppelin第19期——EnumerableSet.sol Michael.W基于Foundry精读Openzeppelin第20期——EnumerableMap.sol Michael.W基于Foundry精读Openzeppelin第21期——ERC165.sol (番外篇)Michael.W基于Foundry精读Openzeppelin第22期——内联汇编staticcall Michael.W基于Foundry精读Openzeppelin第23期——ERC165Checker.sol Michael.W基于Foundry精读Openzeppelin第24期——ERC165Storage.sol Michael.W基于Foundry精读Openzeppelin第25期——IERC1820Registry.sol Michael.W基于Foundry精读Openzeppelin第26期——ERC1820Implementer.sol Michael.W基于Foundry精读Openzeppelin第27期——Escrow.sol Michael.W基于Foundry精读Openzeppelin第28期——ConditionalEscrow.sol Michael.W基于Foundry精读Openzeppelin第29期——RefundEscrow.sol Michael.W基于Foundry精读Openzeppelin第30期——ECDSA.sol Michael.W基于Foundry精读Openzeppelin第31期——IERC1271.sol Michael.W基于Foundry精读Openzeppelin第32期——SignatureChecker.sol Michael.W基于Foundry精读Openzeppelin第33期——EIP712.sol Michael.W基于Foundry精读Openzeppelin第34期——MerkleProof.sol Michael.W基于Foundry精读Openzeppelin第35期——Ownable.sol Michael.W基于Foundry精读Openzeppelin第36期——Ownable2Step.sol Michael.W基于Foundry精读Openzeppelin第37期——AccessControl.sol Michael.W基于Foundry精读Openzeppelin第38期——AccessControlEnumerable.sol Michael.W基于Foundry精读Openzeppelin第39期——ERC20.sol Michael.W基于Foundry精读Openzeppelin第40期——ERC20Burnable.sol Michael.W基于Foundry精读Openzeppelin第41期——ERC20Capped.sol Michael.W基于Foundry精读Openzeppelin第42期——draft-ERC20Permit.sol Michael.W基于Foundry精读Openzeppelin第43期——Pausable.sol Michael.W基于Foundry精读Openzeppelin第44期——ERC20Pausable.sol Michael.W基于Foundry精读Openzeppelin第45期——ERC20FlashMint.sol Michael.W基于Foundry精读Openzeppelin第46期——ERC20Snapshot.sol Michael.W基于Foundry精读Openzeppelin第47期——SafeERC20.sol Michael.W基于Foundry精读Openzeppelin第48期——TokenTimelock.sol Michael.W基于Foundry精读Openzeppelin第49期——ERC20Wrapper.sol Michael.W基于Foundry精读Openzeppelin第50期——ERC20Votes.sol Michael.W基于Foundry精读Openzeppelin第51期——ERC20VotesComp.sol Michael.W基于Foundry精读Openzeppelin第52期——ERC4626.sol Michael.W基于Foundry精读Openzeppelin第53期——ERC20PresetFixedSupply.sol Michael.W基于Foundry精读Openzeppelin第54期——ERC20PresetMinterPauser.sol Michael.W基于Foundry精读Openzeppelin第55期——PaymentSplitter.sol Michael.W基于Foundry精读Openzeppelin第56期——VestingWallet.sol Michael.W基于Foundry精读Openzeppelin第57期——ReentrancyGuard.sol Michael.W基于Foundry精读Openzeppelin第58期——PullPayment.sol Michael.W基于Foundry精读Openzeppelin第59期——Proxy.sol Michael.W基于Foundry精读Openzeppelin第60期——Clones.sol Michael.W基于Foundry精读Openzeppelin第61期——ERC1967Upgrade.sol Michael.W基于Foundry精读Openzeppelin第62期——ERC1967Proxy.sol Michael.W基于Foundry精读Openzeppelin第63期——Initializable.sol Michael.W基于Foundry精读Openzeppelin第64期——UUPSUpgradeable.sol Michael.W基于Foundry精读Openzeppelin第65期——TransparentUpgradeableProxy.sol Michael.W基于Foundry精读Openzeppelin第66期——ProxyAdmin.sol Michael.W基于Foundry精读Openzeppelin第67期——BeaconProxy.sol Michael.W基于Foundry精读Openzeppelin第68期——UpgradeableBeacon.sol

Michael.W基于Foundry精读Openzeppelin第38期——AccessControlEnumerable.sol

  • Michael.W
  • 发布于 2023-11-17 17:34
  • 阅读 3055

AccessControlEnumerable库用于管理函数的调用权限,是AccessControl库的拓展版。与AccessControl库相比,AccessControlEnumerable支持在编成员的迭代导出,这大大方便了各个角色权限的统计查询。

0. 版本

[openzeppelin]:v4.8.3,[forge-std]:v1.5.6

0.1 AccessControlEnumerable.sol

Github: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.3/contracts/access/AccessControlEnumerable.sol

AccessControlEnumerable库用于管理函数的调用权限,是AccessControl库的拓展版。与AccessControl库相比,AccessControlEnumerable支持在编成员的迭代导出,这大大方便了各个角色权限的统计查询(不用通过扫块追溯events来统计目前各角色的在编权限人员的地址)。

1. 目标合约

继承AccessControlEnumerable成为一个可调用合约:

Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/src/access/MockAccessControlEnumerable.sol

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "openzeppelin-contracts/contracts/access/AccessControlEnumerable.sol";

contract MockAccessControlEnumerable is AccessControlEnumerable {
    constructor(){
        // set msg.sender into admin role
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
    }

    function doSomethingWithAccessControl(bytes32 role) onlyRole(role) external {}
}

全部foundry测试合约:

Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/test/access/AccessControlEnumberable.t.sol

2. 代码精读

2.1 supportsInterface(bytes4 interfaceId)

对外提供本合约是否实现了输入interfaceId标识的interface的查询功能。

注:此处重写了AccessControl.supportsInterface(),即在全部支持的interface ids中加入IAccessControlEnumerable的interface id。AccessControl.supportsInterface()的细节参见:https://learnblockchain.cn/article/6632

    using EnumerableSet for EnumerableSet.AddressSet;

    // 用于迭代各role的在编成员地址的set。这里借用了openzeppelin的EnumerableSet库中的AddressSet结构体
    mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers;

    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        // 如果输入的interfaceId为IAccessControlEnumerable或IAccessControl或IERC165的interface id,返回true。否则返回false
        return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);
    }

foundry代码验证

contract AccessControlEnumerableTest is Test {
    MockAccessControlEnumerable private _testing = new MockAccessControlEnumerable();

    function test_SupportsInterface() external {
        // support IERC165 && IAccessControl && IAccessControlEnumerable
        assertTrue(_testing.supportsInterface(type(IERC165).interfaceId));
        assertTrue(_testing.supportsInterface(type(IAccessControl).interfaceId));
        assertTrue(_testing.supportsInterface(type(IAccessControlEnumerable).interfaceId));
    }
}

2.2 _grantRole(bytes32 role, address account)

授予地址account关于输入role的权限。只有具有输入role的adminRole权限的地址才可调用该方法,否则revert。

注:该方法重写了父类AccessControl的同名方法,在AccessControl._grantRole()的基础上增加了在EnumerableSet.AddressSet中注册account的逻辑。同时,父类AccessControl.grantRole()方法的内在逻辑也会改变。

    function _grantRole(bytes32 role, address account) internal virtual override {
        // 调用父类AccessControl._grantRole()
        super._grantRole(role, account);
        // 在输入role对应的EnumerableSet.AddressSet中注册account地址
        _roleMembers[role].add(account);
    }

foundry代码验证

contract AccessControlEnumerableTest is Test {
    MockAccessControlEnumerable private _testing = new MockAccessControlEnumerable();

    bytes32 immutable private ROLE_DEFAULT = 0;
    bytes32 immutable private ROLE_1 = keccak256("ROLE_1");

    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);

    function test_GrantRole() external {
        // case 1: grant role for ROLE_DEFAULT
        address account = address(1024);
        assertFalse(_testing.hasRole(ROLE_DEFAULT, account));
        // deployer (address of AccessControlEnumerableTest) is already in
        assertEq(_testing.getRoleMemberCount(ROLE_DEFAULT), 1);

        vm.expectEmit(true, true, true, false, address(_testing));
        emit RoleGranted(ROLE_DEFAULT, account, address(this));
        _testing.grantRole(ROLE_DEFAULT, account);
        assertEq(_testing.getRoleMemberCount(ROLE_DEFAULT), 2);
        assertTrue(_testing.hasRole(ROLE_DEFAULT, account));

        // grant more accounts for ROLE_DEFAULT
        _testing.grantRole(ROLE_DEFAULT, address(2048));
        _testing.grantRole(ROLE_DEFAULT, address(4096));
        assertEq(_testing.getRoleMemberCount(ROLE_DEFAULT), 4);

        // revert if msg.sender is not the admin of the role
        vm.prank(address(0));
        vm.expectRevert("AccessControl: account 0x0000000000000000000000000000000000000000 is missing role 0x0000000000000000000000000000000000000000000000000000000000000000");
        _testing.grantRole(ROLE_DEFAULT, account);

        // case 2: grant role for ROLE_1
        assertEq(_testing.getRoleMemberCount(ROLE_1), 0);
        assertFalse(_testing.hasRole(ROLE_1, account));
        vm.expectEmit(true, true, true, false, address(_testing));
        emit RoleGranted(ROLE_1, account, address(this));
        _testing.grantRole(ROLE_1, account);
        assertTrue(_testing.hasRole(ROLE_1, account));
        assertEq(_testing.getRoleMemberCount(ROLE_1), 1);

        // grant more accounts for ROLE_1
        _testing.grantRole(ROLE_1, address(2048));
        _testing.grantRole(ROLE_1, address(4096));
        assertEq(_testing.getRoleMemberCount(ROLE_1), 3);

        // revert if msg.sender is not the admin of the role
        vm.prank(address(0));
        vm.expectRevert("AccessControl: account 0x0000000000000000000000000000000000000000 is missing role 0x0000000000000000000000000000000000000000000000000000000000000000");
        _testing.grantRole(ROLE_1, account);
    }
}

2.3 _revokeRole(bytes32 role, address account)

撤销地址account关于输入role的权限。只有具有输入role的adminRole权限的地址才可调用该方法,否则revert。

注:该方法重写了父类AccessControl的同名方法,在AccessControl._revokeRole()的基础上增加了在EnumerableSet.AddressSet中删除account的逻辑。同时,父类AccessControl.revokeRole()方法的内在逻辑也会改变。

    function _revokeRole(bytes32 role, address account) internal virtual override {
        // 调用父类AccessControl._revokeRole()
        super._revokeRole(role, account);
        // 在输入role对应的EnumerableSet.AddressSet中删除account地址
        _roleMembers[role].remove(account);
    }

foundry代码验证

contract AccessControlEnumerableTest is Test {
    MockAccessControlEnumerable private _testing = new MockAccessControlEnumerable();

    bytes32 immutable private ROLE_DEFAULT = 0;
    bytes32 immutable private ROLE_1 = keccak256("ROLE_1");

    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    function test_RevokeRole() external {
        // case 1: revoke role for ROLE_DEFAULT
        address account = address(1024);
        _testing.grantRole(ROLE_DEFAULT, account);
        _testing.grantRole(ROLE_DEFAULT, address(2048));
        _testing.grantRole(ROLE_DEFAULT, address(4096));
        assertEq(_testing.getRoleMemberCount(ROLE_DEFAULT), 4);

        vm.expectEmit(true, true, true, false, address(_testing));
        emit RoleRevoked(ROLE_DEFAULT, account, address(this));
        _testing.revokeRole(ROLE_DEFAULT, account);
        assertFalse(_testing.hasRole(ROLE_DEFAULT, account));
        assertEq(_testing.getRoleMemberCount(ROLE_DEFAULT), 3);

        _testing.revokeRole(ROLE_DEFAULT, address(2048));
        _testing.revokeRole(ROLE_DEFAULT, address(4096));
        assertEq(_testing.getRoleMemberCount(ROLE_DEFAULT), 1);

        // revert if msg.sender is not the admin of the role
        vm.prank(address(1));
        vm.expectRevert("AccessControl: account 0x0000000000000000000000000000000000000001 is missing role 0x0000000000000000000000000000000000000000000000000000000000000000");
        _testing.revokeRole(ROLE_DEFAULT, address(this));

        // case 2: revoke role for ROLE_1
        _testing.grantRole(ROLE_1, account);
        _testing.grantRole(ROLE_1, address(2048));
        _testing.grantRole(ROLE_1, address(4096));
        assertEq(_testing.getRoleMemberCount(ROLE_1), 3);

        vm.expectEmit(true, true, true, false, address(_testing));
        emit RoleRevoked(ROLE_1, account, address(this));
        _testing.revokeRole(ROLE_1, account);
        assertFalse(_testing.hasRole(ROLE_1, account));
        assertEq(_testing.getRoleMemberCount(ROLE_1), 2);

        _testing.revokeRole(ROLE_1, address(2048));
        _testing.revokeRole(ROLE_1, address(4096));
        assertEq(_testing.getRoleMemberCount(ROLE_1), 0);

        // revert if msg.sender is not the admin of the role
        vm.prank(address(1));
        vm.expectRevert("AccessControl: account 0x0000000000000000000000000000000000000001 is missing role 0x0000000000000000000000000000000000000000000000000000000000000000");
        _testing.revokeRole(ROLE_1, address(this));
    }
}

2.4 getRoleMember(bytes32 role, uint256 index) && getRoleMemberCount(bytes32 role)

  • getRoleMember(bytes32 role, uint256 index):获得输入role中索引为index的在编权限地址。输入的index应该介于[0, getRoleMemberCount(role))之内。注:1. 返回的在编权限地址的index顺序与其被添加的顺序无关;2. 严格的讲,该方法与getRoleMemberCount(role)应该保证在同一个区块高度被调用,这样才能保证数据状态的一致性;
  • getRoleMemberCount(bytes32 role):返回输入role的在编权限地址的个数。注:该函数与getRoleMember()配合使用可以迭代出该role的全部在编权限地址。

注:openzeppelin中EnumerableSet库的相关细节参见:https://learnblockchain.cn/article/6272

    function getRoleMember(bytes32 role, uint256 index) public view virtual override returns (address) {
        // 直接调用role对应的EnumerableSet.AddressSet的at()方法,获取role中索引为index的在编权限地址
        return _roleMembers[role].at(index);
    }

    function getRoleMemberCount(bytes32 role) public view virtual override returns (uint256) {
        // 直接调用role对应的EnumerableSet.AddressSet的length()方法,获取该role的在编权限地址的个数
        return _roleMembers[role].length();
    }

foundry代码验证

contract AccessControlEnumerableTest is Test {
    MockAccessControlEnumerable private _testing = new MockAccessControlEnumerable();

    bytes32 immutable private ROLE_DEFAULT = 0;
    bytes32 immutable private ROLE_1 = keccak256("ROLE_1");

    function test_GetRoleMemberAndGetRoleMemberCount() external {
        // case 1: for ROLE_DEFAULT
        _testing.grantRole(ROLE_DEFAULT, address(1024));
        _testing.grantRole(ROLE_DEFAULT, address(2048));
        _testing.grantRole(ROLE_DEFAULT, address(4096));

        assertEq(_testing.getRoleMemberCount(ROLE_DEFAULT), 4);
        assertEq(_testing.getRoleMember(ROLE_DEFAULT, 0), address(this));
        assertEq(_testing.getRoleMember(ROLE_DEFAULT, 1), address(1024));
        assertEq(_testing.getRoleMember(ROLE_DEFAULT, 2), address(2048));
        assertEq(_testing.getRoleMember(ROLE_DEFAULT, 3), address(4096));

        // revoke
        _testing.revokeRole(ROLE_DEFAULT, address(1024));

        // index of account are not sorted when #revoke()
        assertEq(_testing.getRoleMemberCount(ROLE_DEFAULT), 3);
        assertEq(_testing.getRoleMember(ROLE_DEFAULT, 0), address(this));
        assertEq(_testing.getRoleMember(ROLE_DEFAULT, 1), address(4096));
        assertEq(_testing.getRoleMember(ROLE_DEFAULT, 2), address(2048));

        // case 2: for ROLE_1
        _testing.grantRole(ROLE_1, address(1024));
        _testing.grantRole(ROLE_1, address(2048));
        _testing.grantRole(ROLE_1, address(4096));

        assertEq(_testing.getRoleMemberCount(ROLE_1), 3);
        assertEq(_testing.getRoleMember(ROLE_1, 0), address(1024));
        assertEq(_testing.getRoleMember(ROLE_1, 1), address(2048));
        assertEq(_testing.getRoleMember(ROLE_1, 2), address(4096));

        // revoke
        _testing.revokeRole(ROLE_1, address(1024));

        // index of account are not sorted when #revoke()
        assertEq(_testing.getRoleMemberCount(ROLE_1), 2);
        assertEq(_testing.getRoleMember(ROLE_1, 0), address(4096));
        assertEq(_testing.getRoleMember(ROLE_1, 1), address(2048));
    }

    function test_onlyRole() external {
        // test for modifier onlyRole
        address account = address(1024);
        // test for ROLE_DEFAULT
        // pass
        assertTrue(_testing.hasRole(ROLE_DEFAULT, address(this)));
        _testing.doSomethingWithAccessControl(ROLE_DEFAULT);
        // case 1: revert
        assertFalse(_testing.hasRole(ROLE_DEFAULT, account));
        vm.expectRevert("AccessControl: account 0x0000000000000000000000000000000000000400 is missing role 0x0000000000000000000000000000000000000000000000000000000000000000");
        vm.prank(account);
        _testing.doSomethingWithAccessControl(ROLE_DEFAULT);

        // test for ROLE_1
        // case 2: revert
        assertFalse(_testing.hasRole(ROLE_1, account));
        vm.expectRevert("AccessControl: account 0x0000000000000000000000000000000000000400 is missing role 0x00e1b9dbbc5c12d9bbd9ed29cbfd10bab1e01c5e67a7fc74a02f9d3edc5ad0a8");
        vm.prank(account);
        _testing.doSomethingWithAccessControl(ROLE_1);
        // grant ROLE_1 to account
        _testing.grantRole(ROLE_1, account);
        vm.prank(account);
        _testing.doSomethingWithAccessControl(ROLE_1);
    }
}

ps: 本人热爱图灵,热爱中本聪,热爱V神。 以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。 同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下! 如果需要转发,麻烦注明作者。十分感谢!

1.jpeg

公众号名称:后现代泼痞浪漫主义奠基人

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论