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

news2024/11/23 23:53:37

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.coloradmin.cn/o/1220112.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

系列一、请谈谈你对JVM的理解?Java8的虚拟机有什么更新?

一、请谈谈你对JVM的理解?Java8的虚拟机有什么更新? JVM是Java虚拟机的意思。它是建立在操作系统之上的,由类加载器子系统、本地方法栈、Java栈、程序计数器、方法区、堆、本地方法库、本地方法接口、执行引擎组成。 (1&#xff0…

UniPro提高集成能力 让客户专注于交付价值

一千个哈姆莱特就有一千个读者,一千个开发团队,也会有各不相同的软件工具和工作流程。工具与工具之间,功能上的割裂亦或重叠,都会给企业和团队的协作带来阻塞,结果就会导致团队之间各自为战、信息孤岛的形成以及资源的…

TikTok与未来城市:数字社交如何影响城市发展

在数字化时代的浪潮中,社交媒体的兴起不仅仅改变了我们的个人生活,还深刻地影响了城市的发展和演变。其中,TikTok作为一款备受欢迎的短视频平台,正悄然改变着城市文化、社交互动和城市规划。本文将深入探讨TikTok对未来城市的影响…

Web server failed to start. Port 8080 was already in use.

Windows 服务端口被占用,杀死进程命令: netstat -ano | findstr 8080taskkill -PID [xxx] -F

电子科技大学 分布式系统 期末复习笔记

第一章 为什么需要分布式系统:功能分离,固有的分布性,负载均衡,可靠性,经济性。 定义:分布式系统是这样一种系统,其中位于联网计算机上的组件仅通过传递消息来通信和协调它们的操作。 特点&am…

Smart Tomcat的使用

文章目录 Smart Tomcat的作用Smart Tomcat的安装Smart Tomcat的配置Smart Tomcat的启动 Smart Tomcat的作用 我们知道使用Servlet来完成一个项目一共需要七个步骤,即创建maven项目、添加依赖、创建目录结构、编写代码、打包程序、部署程序、验证程序。这样的确是完…

Mysql之多表查询下篇

Mysql之多表查询下篇 满外连接的实现UNION关键字UNIONUNION ALL操作符 7种SQL JOINS的实现语法格式小结自然连接USING连接表连接的约束条件 满外连接的实现 在上篇博客中,我们可以了解到在Mysql中是不支持FULL JOIN来实现 满外连接的,那么我们在Mysql采用…

Leetcode—2760.最长奇偶子数组【简单】

2023每日刷题&#xff08;三十一&#xff09; Leetcode—2760.最长奇偶子数组 实现代码 #define MAX(a, b) ((a > b) ? (a): (b)) int longestAlternatingSubarray(int* nums, int numsSize, int threshold){int ans 0;int i 0;while(i < numsSize) {if(nums[i] >…

Wi-Fi 网络管理的最佳实践

无论是机场、银行、医院、IT 组织还是 MSP&#xff0c;如今 Wi-Fi 网络都在随处部署和使用。由于其成本效益、灵活性和简化的可扩展性&#xff0c;无线网络现已作为其 IT 基础设施的一部分被整合到许多网络中。但是&#xff0c;设置和管理 Wi-Fi 网络的任务并不像最终用户访问它…

【EI会议征稿】第五届人工智能与机电自动化国际学术会议(AIEA 2024)

第五届人工智能与机电自动化国际学术会议&#xff08;AIEA 2024&#xff09; 2024 5th International Conference on Artificial Intelligence and Electromechanical Automation 第五届人工智能与机电自动化国际学术会议&#xff08;AIEA 2024&#xff09;将于2024年3月8-10…

如何使用Matplotlib模块的text()函数给柱形图添加美丽的标签数据?

如何使用Matplotlib模块的text函数给柱形图添加美丽的标签数据&#xff1f; 1 简单引入2 关于text()函数2.1 Matplotlib安装2.2 text()引入2.3 text()源码2.4 text()参数说明2.5 text()两个简单示例 3 柱形图绘制并添加标签3.1 目标数据3.2 读取excel数据3.3 设置窗口大小和xy轴…

编译安装redis及配置多实例

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

配置环境-insightface-torch

1. 创建环境&#xff1a;conda create -n insightface2 python3.8 2.安装pytorch: 我的cuda 是 11.3 然后进入 pytorch 官网查找对应cuda 版本 pytorch 安装 建议使用 pip # CUDA 11.3 conda install pytorch1.12.1 torchvision0.13.1 torchaudio0.12.1 cudatoolkit11.3 -…

抖音订单列表查询api接口

怎么获取订单列表接口 请求地址&#xff1a;响应示例及参数

asp.net健身会所管理系统sqlserver

asp.net健身会所管理系统sqlserver说明文档 运行前附加数据库.mdf&#xff08;或sql生成数据库&#xff09; 主要技术&#xff1a; 基于asp.net架构和sql server数据库 功能模块&#xff1a; 首页 会员注册 教练预约 系统公告 健身课程 在线办卡 用户中心[修改个人信息 修…

数据结构--栈与队列

目录 前言 1.栈 1.1栈的概念及结构 1.2接口函数 1.3函数实现 1.4如何使用 2.队列 2.1队列的概念及结构 2.2接口函数 2.3函数实现 2.4如何使用 前言 前面我们已经学习了顺序表和链表&#xff0c;今天我们来学习栈与队列&#xff0c;这两种结构也属于线性表&#xff0c;实…

【数据结构初阶】单链表(附全部码源)

单链表 1&#xff0c;单链表的概念及结构2&#xff0c;单链表的实现2.1初始化内容&#xff08;所需文件&#xff0c;接口&#xff09;2.2申请结点2.3打印单链表2.4尾插2.5头插2.6尾删2.7头删2.8查找2.9在pos位置之后插入2.10在pos位置前面插入2.11删除pos之后的值2.12删除pos位…

2023亚太杯数学建模思路 - 案例:ID3-决策树分类算法

文章目录 0 赛题思路1 算法介绍2 FP树表示法3 构建FP树4 实现代码 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 算法介绍 FP-Tree算法全称是FrequentPattern Tree算法&#xff0c;就是频繁模…

接口测试系列之 —— 接口安全测试

“开源 Web 应用安全项目”(OWASP)在 2019 年发布了 API 十大安全风险 《OWASP API 安全 Top10》&#xff1a;失效的对象级别授权、失效的用户身份验证、过 度的数据暴露、资源缺乏和速率限制、失效的功能级授权、批量分配、安全配置 错误、注入、资产管理不当、日志和监视不足…

百家网约车平台发布“阳光五条” 多举措加强司机保障

11月17日&#xff0c;免佣联盟百家网约车平台发布“阳光五条”&#xff0c;通过加大免佣力度、实行车费保镖司机版、72小时保护期等措施&#xff0c;加强对网约车司机的权益保障。 近年&#xff0c;交通运输部推动交通运输新业态平台企业落实“阳光行动”等工作&#xff0c;加…