How to Use OpenZeppelin’s New AccessControl Contract
source link: https://medium.com/better-programming/how-to-use-openzeppelins-new-accesscontrol-contract-5b49a4bcd160
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
How to Use OpenZeppelin’s New AccessControl Contract
Using the latest version of the gold standard smart contract library
Version 3 of OpenZeppelin’s Smart Contracts library is out! In the latest release, they introduce a brand new method of controlling access to functions.
What We Know
Controlling access to certain functions is paramount to ensuring the security of smart contracts, and has been the case in Solidity since the introduction of the Ethereum Virtual Machine.
Developers familiar with OpenZeppelin’s Smart Contract repository know that it already provides options to restrict functionality depending on access level.
The most common is the onlyOwner
pattern, administered by the Ownable
contract. Another is Openzeppelin’s Roles
contract, which enables contracts to define multiple roles before deployment and set rules in each function, ensuring that msg.sender
holds the correct role.
Ownable
The onlyOwner
pattern is the most commonly used and easily implemented access control method. It’s primitive but highly effective.
It assumes that there is a single administrator of the smart contract, and enables the administrator to transfer ownership to another address.
Extending the Ownable
contract allows child contracts to define functions with the onlyOwner
custom modifier. These functions require that the sender of the transaction be the single administrator.
function normalFunction() public {
// anyone can call this
}function restrictedFunction() public onlyOwner {
// only the owner can call this
}
This is a simple example of how to utilize the custom modifier provided by the Ownable
contract to restrict function access.
Roles
Despite the popularity and ease of use of the Ownable
contract, other OpenZeppelin contracts in the repository only ever used the Roles
library for access control. This is because of the flexibility that the Roles
library provides over the rigidity of the Ownable
contract.
Being a library, it is not extended by child contracts but is used as a tool for adding functionality to data types, with the using
statement. The Roles
library has three functions that it provides for the Role
data type, which it defines itself.
Figure 1 shows the definition of Roles
.
At the top, you can see the Role
struct. This is used by contracts to define multiple roles and their members. The functions add()
, remove()
, and has()
are functions that the library uses to interact with a Role
struct.
For example, figure 2 shows how a token might use two separate roles, _minters
and _burners
, to apply access restrictions to certain functions.
Notice how in the mint()
function the require statement ensures that the sender of the message is a minter by using the _minters.has(msg.sender)
function.
What’s New
Given that this has been the standard for a while, the big news for developers is that the Roles
contract has been removed in the upgrade from version 2.5.x to 3.x.
Principles
The Roles
library was somewhat restrictive in the functionality it provided.
Being a library, data storage must be controlled by the importing contract. Ideally, access control should be abstracted away to some degree, where the importing contract needs only worry about the restrictions on each function.
The new AccessControl
contract is touted as:
“A one-stop-shop for all authorization needs. It lets you easily define multiple roles with different permissions, as well as which accounts are allowed to grant and revoke each role. It also boosts transparency by enabling enumeration of all privileged accounts in a system.”
The last two points in that statement were not possible with the Roles
library.
OpenZeppelin looks to be moving towards a system that is more reminiscent of role-based access control (RBAC) and attribute-based access control (ABAC) standards, prominent in traditional computing security.
Dissecting the Code
Figure 3 shows the AccessControl
contract code.
The RoleData
struct on line 42 uses EnumerableSet
(also new to version 3) as the data structure to store members. This allows easy iteration on privileged users.
The struct also stores adminRole
as a bytes32
variable. This defines which role acts as administrator over a particular role (i.e. the role that has the ability to act as administrator over this role, granting and revoking this role to users).
Events are now emitted when a role is granted or revoked, defined on lines 57 and 66.
The Roles
contract provided just three functions: has()
, add()
, and remove()
. Forms of these exist in AccessControl
, as well as extra functionality like getting role counts, getting specific members of a role by ID, and the ability to renounce a role.
How to Use It
Figure 2 gave the example of a token contract needing two separate roles, _minters
and _burners
, using the Roles
library. For continuity, we’ll use the same concept and apply the AccessControl
contract to do it.
Figure 4 shows the implementation of this.
Figure 4: Implementing AccessControlSo, what has changed? For a start, each role is no longer defined in the child contract, as they are stored in the parent. Only the bytes32 IDs exist as constant state variables in the child contract (MINTER_ROLE
and BURNER_ROLE
in this example).
The _setupRole()
is used in the constructor to set the initial administrator of roles, bypassing checks performed by grantRole()
in AccessControl
(since at the point of construction there is no administrator yet).
Also, instead of calling library functions as extensions on data types (i.e. _minters.has(msg.sender)
), the functions are internal in their own right (hasRole(MINTER_ROLE, msg.sender)
). This makes the code in the child contract generally more clean and readable.
Abstracting away more of the functionality allows child contracts to build on top of the AccessControl
contract easier than was possible with the Roles
library. In a previous article, I proposed future implementations of RBAC and ABAC systems built using AccessControl
.
If you’re interested in further exploring the possibilities, Alberto Cuesta Canada has produced examples of contracts expanding on the basic functionality provided by AccessControl
.
Conclusion
The introduction of AccessControl
is a big step forward in bringing the Ethereum ecosystem closer to industry standards with regards to system security.
The contract received heavy input from industry experts. I imagine some interesting and complex systems will arise from this contract soon, pushing the mantle even further.
I highly recommend importing OpenZeppelin contracts into your project and expanding on them yourself. You never know what you might learn or stumble upon!
Thanks for reading.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK