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

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

      • 0. 版本
        • 0.1 AccessControlEnumerable.sol
      • 1. 目标合约
      • 2. 代码精读
        • 2.1 supportsInterface(bytes4 interfaceId)
        • 2.2 _grantRole(bytes32 role, address account)
        • 2.3 _revokeRole(bytes32 role, address account)
        • 2.4 getRoleMember(bytes32 role, uint256 index) && getRoleMemberCount(bytes32 role)

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()的细节参见:

    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神。
以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。
同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下!
如果需要转发,麻烦注明作者。十分感谢!

在这里插入图片描述

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/157609.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ERP智能管理系统:智能化的未来之路

ERP智能管理系统:智能化的未来之路 科技飞速发展,人工智能(AI)和大数据等先进技术的应用正在改变着企业的运营模式。其中,ERP智能管理系统在帮助企业实现智能化运营、提高效率、降低成本等方面发挥着越来越重要的作用。本文将为您详细介绍ERP…

有什么好用的后勤管理软件?学校后勤服务要怎么提升满意度?

后勤服务是院校管理中的重要一环,直接影响到师生的工作、学习和生活质量。师生作为学校的核心用户,对后勤服务的质量和满意度有着深刻的体验和感受。因此,他们的评价对于提升学校品牌形象、提高服务质量以及改进学校管理具有至关重要的作用。…

《rPPG》——(1)PyTorch——Windows环境配置

《rPPG》——(1)PyTorch——Windows环境配置 如何查看电脑是否已安装Python环境以及Python版本 anaconda对应python3.8的版本号是多少? 截止到我的知识截止日期(2022年1月),Anaconda支持Python 3.8的版本号是Anacond…

易点易动固定资产管理系统场景应用二:集成本地OA/BPM系统

在企业的日常运营中,固定资产管理是一项重要的任务。为了实现高效的工作流程和准确的审批流程,易点易动固定资产管理系统提供了与本地OA/BPM系统的集成功能。本文将重点介绍易点易动固定资产管理系统在集成本地OA/BPM系统方面的应用场景,以帮…

html实现图片裁剪处理(附源码)

文章目录 1.设计来源1.1 主界面1.2 裁剪界面 2.效果和源码2.1 动态效果2.2 源代码 源码下载 作者:xcLeigh 文章地址:https://blog.csdn.net/weixin_43151418/article/details/134455169 html实现图片裁剪处理(附源码),支持图片放大缩小&#…

Snipaste 截图悬浮工具【实用教程】

下载安装 Snipaste windows64位版本下载链接:https://pan.baidu.com/s/1i2L3JHxOGqkmX3lj2fUVHA?pwdeut6 无需安装,解压后即可使用 其他版本见官网 https://zh.snipaste.com/download.html 启动 Snipaste 双击解压后文件夹 Snipaste-2.8.8-Beta-x64 中…

亲测一款超实用的在线制作产品册工具,一看就会

最近,我一直在寻找一款简单易用的在线制作产品册工具,终于让我找到了一个超实用的神器!这款工具不仅功能强大,而且操作简单,一看就会。 首先,这款工具提供了丰富的模板和素材,用户可以根据自己的…

linux网络编程之TCP协议编程

Linux网络编程之TCP协议编程 tcp协议编程模型socket函数sockaddr_inbindlistenconnect 应用服务端代码客服端代码 TCP协议编程) tcp协议编程模型 Server 1.创建socket (socket函数) 2.确定服务器协议地址簇 (struct sockaddr) 3.绑定 (bind) 4.监听 ( listen) 5.接受客户端连接…

编译安装redis及配置多实例

yum安装是这种十分简单的方法我们就不在提及了,今天我们来做一下redis的编译安装 Redis源码包官方下载链接:http://download.redis.io/releases/ 一、编译安装: 安装依赖包 dnf -y install make gcc jemalloc-devel systemd-devel如果是…

Appium移动自动化测试--安装Appium

Appium 自动化测试是很早之前就想学习和研究的技术了,可是一直抽不出一块完整的时间来做这件事儿。现在终于有了。 反观各种互联网的招聘移动测试成了主流,如果再不去学习移动自动化测试技术将会被淘汰。 web自动化测试的路线是这样的:编程语…

java:IDEA中的Scratches and Consoles

背景 IntelliJ IDEA中的Scratches and Consoles是一种临时的文件编辑环境,用于写一些文本内容或者代码片段。 其中,Scratch files拥有完整的运行和debug功能,这些文件需要指定编程语言类型并且指定后缀。 举例:调接口 可以看到…

干扰项目成本估算精准度的5大因素

干扰项目成本估算精准度的因素有很多,这些因素可能导致成本估算的不准确性,增加成本偏差和额外的成本投入,从而对项目的进度和预算产生影响。因此,在进行项目成本估算时,需要充分考虑这些因素,并采取相应的…

API接口怎么对接电商平台获取商品详情数据

对于api接口的对接,你可以按照以下步骤进行操作: 1. 确定需求:首先要明确你的对接需求,即想要通过对接api接口实现什么功能,例如获取数据、实现支付等。 2. 寻找文档:在对接之前,要找到相关ap…

虚拟机配置网络ip,主打一个详细

文章目录 一、前言二、安装vim编辑器三、检查联网状态1. 使用ping命令 四、查看ip五、ens33网卡六、开机启动ens33网卡七、获取子网地址和子网掩码八、配置网关与子网掩码1. 编辑虚拟网络信息2. 配置网关3. 配置ens33网卡信息 九、动态ip配置十、静态ip配置 一、前言 本文主要…

面试题 Android 如何实现自定义View 固定帧率绘制

曾经遇到的面试题, 如何实现自定义View 1s内固定帧率的绘制. 当时对Android理解不深, 考虑的不全面, 直接回答了在onDraw结束时通过postDelay发送一个(1000 / 帧数)ms的延时消息触发invalidate进行下一次绘制. 但实际上这样做存在明显的问题 实际上1s绘制的帧数是不符合期望帧…

main函数的数组参数是干嘛用的

今天在看项目代码的时候,突然看到项目中用到了main函数的参数args,在这之前我还没怎么注意过这个参数,一时间居然不知道这个参数是干嘛的! 虽然也写过一些java和scala,但是确实没遇到过会用这个参数的情况。 网上就查…

国鑫受邀出席2023松山湖软件和信息服务业高质量发展大会

为推动粤港澳大湾区的软件和先进制造产业的融合发展,“2023松山湖软件和信息服务业高质量发展大会”于今日在松山湖畔隆重举办,会议以“推动软件和制造业深度融合发展,打造软件和信息服务业集聚高地”为主题,聚焦工业软件应用、智…

springboot引入redisson分布式锁及原理

1.引入依赖 <dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.13.6</version> </dependency>2.配置类创建bean /*** author qujingye* Classname RedissonConfig* Description TOD…

数据结构与集合源码

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 本…

java调用c函数

一、关于JNI JNI是Java Native Interface的缩写&#xff0c;JNI是JAVA平台专门用于和本地C代码进行相互操作的API&#xff0c;称为JAVA本地接口。 二、JNI开发流程 1.在JAVA中先声明一个native方法。2.通过javac -h或javah -jni命令导出JNI使用的C头头文件。3.使用C实现本地方…