Three example configurations for the new OpenZeppelin access control contract. Access Control in Solidity. The new library from OpenZeppelin makes me happy Photo by from Scott Webb Pexels Restricting access to authorized users is really important in smart contracts. With $1 Billion locked in DeFi applications, you would really hope everyone knows what they are doing. Recently, I collaborated with OpenZeppelin to refactor the access control in their widely used repository. If you have ever coded a smart contract, most likely you have inherited from their code. openzeppelin-contracts In this article, I’m going to give you a walkthrough of how to use the revamped to restrict access to your smart contracts. I’ll do this through three examples. AccessControl.sol But first, let me tell you the story of how we got here. Please feel free to skip the next section if you are not interested in how this contract came to be and only want to learn how to use it. How we got here Getting here for me has been a process that took about a year. I first had the idea for a hierarchical role-based control contract in May 2019. I , along with some references to what OpenZeppelin was doing at the time. published the code in Medium I reused that code for and , becoming convinced that the implementation offered something both unique and powerful. From the lessons learned I wrote showing when to use , and . AllianceBlock Insig a second article Ownable.sol Whitelist.sol RBAC.sol To write that article I needed to research deeply into what OpenZeppelin had been doing in terms of access control. You don’t challenge the most-forked solidity repository without doing your homework. The OpenZeppelin team are really great guys, and shortly after they in the refactor of their access control contracts. Out of that collaboration we created an access control contract that is much more flexible than their previous approach, and much more robust than my previous approach. invited me to participate is available from the repository and I don’t keep a different codebase anymore. AccessControl.sol openzeppelin-contracts As an interesting aside, and as further proof of how important is access control, a version of existed back in 2016. It was Manuel Araoz’s towards what would become . Ownable.sol 9th commit openzeppelin-contracts In solidity years that is like digging dinosaur bones, and proves that access control is one of the very first things you should think about when coding smart contracts. Especially if you intend to code robust ones. Story time over, let’s dive into the code. How does AccessControl.sol work A role in is a struct that contains a set of addresses, representing the accounts bearing that role. All roles are stored in a mapping indexed by a bytes32 identifier, unique for each role. AccessControl.sol Each role also contains the bytes32 identifier of another role, which we call its admin role. There are internal functions to grant and revoke roles, and to set a role as the admin of another. These internal functions have no restrictions and you can use them if you are extending AccessControl.sol, as we will do later in this article. There are also external functions that restrict granting and revoking roles, as well as setting roles as admins of others. These functions can only be called externally by accounts that have been granted the admin role of the role being modified. As an example, imagine that we have two roles, DEFAULT_ADMIN_ROLE and USER_ROLE. DEFAULT_ADMIN_ROLE is the admin role for USER_ROLE. Only accounts that have been granted the DEFAULT_ADMIN_ROLE can grant or revoke USER_ROLE for an account. Likewise, only accounts that have been granted the DEFAULT_ADMIN_ROLE can redefine the admin role for USER_ROLE. A bit of magic worked here by was to define DEFAULT_ADMIN_ROLE as the uninitialized bytes32 variable. That means that all roles, by default, have DEFAULT_ADMIN_ROLE as the admin role. nventuro That’s it, let me show you in more detail with examples. Community replicates the functionality that existed in OpenZeppelin before. This contract Roles.sol There is a single role (DEFAULT_ADMIN_ROLE). Anyone that has that role can grant it to others. In roles can’t be revoked from a different account, but accounts can renounce to a role they hold. Community.sol The address passed on to the constructor is the first account being granted the role. pragma solidity ^ ; ; contract Community is AccessControl { (address root) public { _setupRole(DEFAULT_ADMIN_ROLE, root); } modifier onlyMember() { (isMember(msg.sender), ); _; } { hasRole(DEFAULT_ADMIN_ROLE, account); } { grantRole(DEFAULT_ADMIN_ROLE, account); } { renounceRole(DEFAULT_ADMIN_ROLE, msg.sender); } } 0.6 .0 import "@openzeppelin/contracts/access/AccessControl.sol" /// @dev Implements a single role access control contract. /// @dev Create the community role, with `root` as a member. constructor /// @dev Restricted to members of the community. require "Restricted to members." /// @dev Return `true` if the `account` belongs to the community. ( ) ( ) function isMember address account public virtual view returns bool return /// @dev Add a member of the community. ( ) function addMember address account public virtual onlyMember /// @dev Remove oneself as a member of the community. ( ) function leaveCommunity public virtual Administered implements a traditional setup with administrators and users. This smart contract DEFAULT_ADMIN_ROLE is the admin role of USER (by default). The address passed on to the constructor is the initial administrator. Admins can add other admins. Admins can grant and revoke user permissions to any accounts. The only way for an admin to lose its admin role is to renounce from it. pragma solidity ^ ; ; contract Administered is AccessControl { bytes32 public constant USER_ROLE = keccak256( ); (address root) public { _setupRole(DEFAULT_ADMIN_ROLE, root); _setRoleAdmin(USER_ROLE, DEFAULT_ADMIN_ROLE); } modifier onlyAdmin() { (isAdmin(msg.sender), ); _; } modifier onlyUser() { (isUser(msg.sender), ); _; } { hasRole(DEFAULT_ADMIN_ROLE, account); } { hasRole(USER_ROLE, account); } { grantRole(USER_ROLE, account); } { grantRole(DEFAULT_ADMIN_ROLE, account); } { revokeRole(USER_ROLE, account); } { renounceRole(DEFAULT_ADMIN_ROLE, msg.sender); } } 0.6 .0 import "@openzeppelin/contracts/access/AccessControl.sol" /** * @title Administered * @author Alberto Cuesta Canada * @notice Implements Admin and User roles. */ "USER" /// @dev Add `root` to the admin role as a member. constructor /// @dev Restricted to members of the admin role. require "Restricted to admins." /// @dev Restricted to members of the user role. require "Restricted to users." /// @dev Return `true` if the account belongs to the admin role. ( ) ( ) function isAdmin address account public virtual view returns bool return /// @dev Return `true` if the account belongs to the user role. ( ) ( ) function isUser address account public virtual view returns bool return /// @dev Add an account to the user role. Restricted to admins. ( ) function addUser address account public virtual onlyAdmin /// @dev Add an account to the admin role. Restricted to admins. ( ) function addAdmin address account public virtual onlyAdmin /// @dev Remove an account from the user role. Restricted to admins. ( ) function removeUser address account public virtual onlyAdmin /// @dev Remove oneself from the admin role. ( ) function renounceAdmin public virtual Hierarchy This is the structure implemented in , with a minimal modification to build a hierarchy of roles safely. AccessControl.sol The address passed on to the constructor has the DEFAULT_ADMIN_ROLE, and is by default admin of all roles. I’ll call that address root. Root can grant the DEFAULT_ADMIN_ROLE to any account. Any root account can revoke the DEFAULT_ADMIN_ROLE role from any other account, so better not to grant it in the first place unless you know what you are doing. All roles exist from the beginning, as keys in a mapping. Any role can be granted or revoked by an account in the DEFAULT_ADMIN_ROLE. In each role has an admin role (DEFAULT_ADMIN_ROLE by default). In we allow for any account with that admin role can change the relationship and choose a new admin role. This can be used to build a hierarchy of roles, for example: DEFAULT_ADMIN_ROLE -> USER_ADMIN -> USER. AccessControl.sol Hierarchy.sol contract Hierarchy is AccessControl { event AdminRoleSet(bytes32 roleId, bytes32 adminRoleId); (address root) public { _setupRole(DEFAULT_ADMIN_ROLE, root); } modifier onlyMember(bytes32 roleId) { (hasRole(roleId, msg.sender), ); _; } { _setRoleAdmin(roleId, adminRoleId); } } /// @dev Add `root` as a member of the root role. constructor /// @dev Restricted to members of the role passed as a parameter. require "Restricted to members." /// @dev Create a new role with the specified admin role. ( ) ( ) function addRole bytes32 roleId, bytes32 adminRoleId public onlyMember adminRoleId It’s useful to have AccessControl.sol relevant code handy to understand what’s going on. abstract contract AccessControl is Context { … { ( hasRole(_roles[role].adminRole, _msgSender()), ); _grantRole(role, account); } { ( hasRole(_roles[role].adminRole, _msgSender()), ); _revokeRole(role, account); } { ( account == _msgSender(), ); _revokeRole(role, account); } … } /// @dev Grants `role` to `account`. ( ) function grantRole bytes32 role, address account external virtual require "AccessControl: sender must be an admin to grant" /// @dev Revokes `role` from `account`. ( ) function revokeRole bytes32 role, address account external virtual require "AccessControl: sender must be an admin to revoke" /// @dev Revokes `role` from the calling account. ( ) function renounceRole bytes32 role, address account external virtual require "AccessControl: can only renounce roles for self" Conclusion I’m really happy and really proud of having got to this point. Really proud because contributing code and ideas to is the strongest validation possible as a smart contracts developer. openzeppelin-contracts Really happy because finally the access control contracts that I’ve been using have now gone through a thorough audit and have been refined by masters in their field. I can now use them with complete confidence that they are right. And really proud and happy to contribute to the community. Giving back is the best present. Now it is up to you to continue. Please , , . BUIDL! use these contracts give feedback propose improvements