From the simple to the complex, with the code to reuse. Introduction When writing smart contracts . Even if they are intended for a production environment I make them as easy to understand as possible. I write to be reusable, but usually they get rewritten for each specific business case. I tend to take an educational approach contracts In this article I’m going to discuss three approaches to permissioning in solidity smart contracts. These approaches are discussed in increasing order of complexity, which is the order in which you should consider them for your project. I include code that you can reuse for each approach. This article assumes that you are comfortable coding smart contracts in solidity, and using features like inheritance and passing contracts addresses as a parameter. If you are looking for an easier article on smart contract development you can try . this one Simple Approach - Ownable.sol The contract from OpenZeppelin must be one of the most reused contracts out there. In 77 lines it implements: Ownable.sol The logic to assert that someone is an owner of a contract. The logic to restrict function calling to the contract owner for inheriting contracts. The logic to transfer ownership to a different address. When coding smart contracts you will inherit from very often. Let’s see how to use with an example. Imagine that you want to keep a list of addresses in a contract but you want to be the only one that can add more. Think of it like some kind of registry of people that you trust. You could do something like: Ownable Ownable contract Whitelist is Ownable { mapping ( bool) members; () public Ownable() { } { members[_member] = ; } } => address constructor ( ) function addMember address _member public onlyOwner true Inheriting from and calling its constructor on yours ensures that the address deploying your contract is registered as the owner. The modifier makes a function revert if not called by the address registered as owner. Ownable onlyOwner Once you deploy this contract only you or someone that you designate can add new members to the list within. That's it, in a nutshell. There are a couple more functions but you'll get it if you check the . Try to , it is very easy to understand. source code implement something simple with it Despite its usefulness, there will be many times when is not enough. Only one address can be the owner at a given time, only the owner gets to decide who can be the new owner, you can only check if you are the owner, not is someone else is. Ownable Middle Approach - Whitelist.sol keeps a list of addresses which then can be used to restrict functionality or any other purpose. It is very similar in functionality to OpenZeppelin’s , although with some critical differences (check our ). Whitelist.sol Roles.sol README only has three functions: Whitelist.sol ( ) ( ); ( ) ; ( ) ; function isMember address _member public view returns bool function addMember address _member public onlyOwner function removeMember address _member public onlyOwner With this contract you could, for example, keep a list of approved stakeholders who can be the only recipients for token transfers. You could do something like this: pragma solidity ^ ; ; ; contract ERC20Whitelisted is ERC20 { Whitelist whitelist; (address _whitelistAddress) public { whitelist = Whitelist(_whitelistAddress); } { (whitelist.isMember(account), ); ._transfer(account, amount); } } 0.5 .0 import "@openzeppelin/contracts/token/ERC20/ERC20.sol" import "../access/Whitelist.sol" constructor ( ) function transfer address account, uint256 amount public require "Account not whitelisted." super In the example above, you could also make inherit from both and . There are some trade offs that . ERC20Whitelisted ERC20 Whitelist I would be happy to discuss Simple whitelists can be quite powerful. OpenZeppelin implemented many and variants using them and managed to provide more functionality than most of us will need. At we implemented using only whitelists as well. ERC20 ERC721 TechHQ CementDAO Sometimes, however, whitelists will also fall short. You might need to have more than one owner for a whitelist. Or you might need to manage many overlapping whitelists. For those cases we have a hierarchical role contract. Complex - RBAC.sol We developed aiming to give multi user functionality like you have in modern shared systems. RBAC.sol There are roles that are nothing more than groups of addresses. Group membership can only be modified by members of some administrator role. New roles can be created at runtime. Role membership can be verified. At a low level we identify the roles using a argument chosen by the user. Commonly these are identifiable short strings, but you can also use an encrypted value or an address. bytes32 The roles themselves are a group of member addresses and the identifier of the admin role. Funnily enough we don’t need to store the identifier of the role inside its own struct. struct Role { bytes32 adminRoleId; mapping ( bool) members; } => address There are now two methods to add a new role and verify if a role exists: ( ) ( ); ( ) ; function roleExists bytes32 _roleId public view returns bool function addRole bytes32 _roleId, bytes32 _adminRoleId public And the functions for managing members are the same, only that now the relevant role must be specified: ( ) ( ); ( ) ; ( ) ; function isMember address _member, bytes32 _roleId public view returns bool function addMember address _member, bytes32 _roleId public function removeMember address _member, bytes32 _roleId public and will only succeed if the caller belongs to the administrator role of the role that we are adding members to. addMember removeMember will only succeed if the caller belongs to the role that will administer the role being created. addRole These simple rules will allow to create a hierarchy of roles, which can then be used to implement complex multi user platforms with different permissioning levels or areas. Further Learning To dive even deeper into the rabbit hole I suggest starting with . Their codebase and ours is not that different, and you will find there a thorough reasoning for most design decisions even in the cases where we have chosen to go the other way. Their use of for contracts like is a great example of an alternative to . this issue from OpenZeppelin Roles ERC20Mintable Whitelist Another resource for the brave is the . Just a glance to the interface shows that they have decided to go farther than anyone else: AragonOS ACL contract ( ) ( ); function hasPermission address who, address where, bytes32 what, bytes how public view returns bool We use the three levels of access control described in this article for the examples in our own package, so you should also keep an eye there as well. @hq20/contracts Conclusion When it comes to smart contract implementation it is a good idea to implement only the complexity that is required, and no more. In terms of permissioning there are three distinct levels of complexity: Single user Group of users Hierarchy of user groups You can use for systems that are permissioned for a single user. You can use or for systems that require permissioning users in a group. For systems that require a group hierarchy we have successfully used in the past. Ownable.sol @openzeppelin/Roles.sol @hq20/Whitelist.sol @hq20/RBAC.sol You will have your own requirements and will need to take your own decisions on trade offs. Knowing the design decisions behind each implementation will allow you to either use an existing contract or to modify one for your own use. Please make sure of of any feedback. We are developing the package to support the coding of real world blockchain applications. We are aiming for our code to be reused and abused, and would be very happy to know how you do that. letting us know @hq20/contracts