You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
109 lines
7.1 KiB
109 lines
7.1 KiB
6 years ago
|
= Access Control
|
||
6 years ago
|
|
||
6 years ago
|
Access control—that is, "who is allowed to do this thing"—is incredibly important in the world of smart contracts. The access control of your contract may govern who can mint tokens, vote on proposals, freeze transfers, and many others. It is therefore critical to understand how you implement it, lest someone else https://blog.zeppelin.solutions/on-the-parity-wallet-multisig-hack-405a8c12e8f7[steals your whole system].
|
||
|
|
||
|
[[ownership-and-ownable]]
|
||
|
== Ownership and `Ownable`
|
||
6 years ago
|
|
||
6 years ago
|
The most common and basic form of access control is the concept of _ownership_: there's an account that is the `owner` of a contract and can do administrative tasks on it. This approach is perfectly reasonable for contracts that have a single administrative user.
|
||
6 years ago
|
|
||
6 years ago
|
OpenZeppelin provides link:api/ownership#ownable[`Ownable`] for implementing ownership in your contracts.
|
||
6 years ago
|
|
||
6 years ago
|
[source,solidity]
|
||
|
----
|
||
6 years ago
|
pragma solidity ^0.5.0;
|
||
|
|
||
6 years ago
|
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
|
||
|
|
||
|
contract MyContract is Ownable {
|
||
6 years ago
|
function normalThing() public {
|
||
6 years ago
|
// anyone can call this normalThing()
|
||
|
}
|
||
|
|
||
6 years ago
|
function specialThing() public onlyOwner {
|
||
6 years ago
|
// only the owner can call specialThing()!
|
||
|
}
|
||
|
}
|
||
6 years ago
|
----
|
||
6 years ago
|
|
||
6 years ago
|
By default, the link:api/ownership#Ownable.owner()[`owner`] of an `Ownable` contract is the account that deployed it, which is usually exactly what you want.
|
||
6 years ago
|
|
||
6 years ago
|
Ownable also lets you: - link:api/ownership#Ownable.transferOwnership(address)[`transferOwnership`] from the owner account to a new one - link:api/ownership#Ownable.renounceOwnership()[`renounceOwnership`] for the owner to lose this administrative privilege, a common pattern after an initial stage with centralized administration is over - *⚠ Warning! ⚠* Removing the owner altogether will mean that administrative tasks that are protected by `onlyOwner` will no longer be callable!
|
||
6 years ago
|
|
||
6 years ago
|
Note that *a contract can also be the owner of another one*! This opens the door to using, for example, a https://github.com/gnosis/MultiSigWallet[Gnosis Multisig] or https://safe.gnosis.io[Gnosis Safe], an https://aragon.org[Aragon DAO], an https://www.uport.me[ERC725/uPort] identity contract, or a totally custom contract that _you_ create.
|
||
6 years ago
|
|
||
6 years ago
|
In this way you can use _composability_ to add additional layers of access control complexity to your contracts. Instead of having a single regular Ethereum account (Externally Owned Account, or EOA) as the owner, you could use a 2-of-3 multisig run by your project leads, for example. Prominent projects in the space, such as https://makerdao.com[MakerDAO], use systems similar to this one.
|
||
6 years ago
|
|
||
6 years ago
|
[[role-based-access-control]]
|
||
|
== Role-Based Access Control
|
||
6 years ago
|
|
||
6 years ago
|
While the simplicity of _ownership_ can be useful for simple systems or quick prototyping, different levels of authorization are often needed. An account may be able to ban users from a system, but not create new tokens. _Role-Based Access Control (RBAC)_ offers flexibility in this regard.
|
||
6 years ago
|
|
||
6 years ago
|
In essence, we will be defining multiple _roles_, each allowed to perform different sets of actions. Instead of `onlyOwner` everywhere you will use, for example, `onlyAdminRole` in some places, and `onlyModeratorRole` in others. Separately you will be able to define rules for how accounts can be assignned a role, transfer it, and more.
|
||
6 years ago
|
|
||
6 years ago
|
Most of software development uses access control systems that are role-based: some users are regular users, some may be supervisors or managers, and a few will often have administrative privileges.
|
||
6 years ago
|
|
||
6 years ago
|
[[using-roles]]
|
||
|
=== Using `Roles`
|
||
6 years ago
|
|
||
6 years ago
|
OpenZeppelin provides link:api/access#roles[`Roles`] for implementing role-based access control. Its usage is straightforward: for each role that you want to define, you'll store a variable of type `Role`, which will hold the list of accounts with that role.
|
||
6 years ago
|
|
||
6 years ago
|
Here's an simple example of using `Roles` in an link:tokens#erc20[`ERC20` token]: we'll define two roles, `namers` and `minters`, that will be able to change the name of the token contract, and mint new tokens, respectively.
|
||
6 years ago
|
|
||
6 years ago
|
[source,solidity]
|
||
|
----
|
||
6 years ago
|
pragma solidity ^0.5.0;
|
||
|
|
||
6 years ago
|
import "openzeppelin-solidity/contracts/access/Roles.sol";
|
||
6 years ago
|
import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol";
|
||
|
import "openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol";
|
||
6 years ago
|
|
||
6 years ago
|
contract MyToken is ERC20, ERC20Detailed {
|
||
6 years ago
|
using Roles for Roles.Role;
|
||
|
|
||
6 years ago
|
Roles.Role private _minters;
|
||
|
Roles.Role private _namers;
|
||
|
|
||
|
constructor(address[] memory minters, address[] memory namers)
|
||
|
DetailedERC20("MyToken", "MTKN", 18)
|
||
6 years ago
|
public
|
||
|
{
|
||
6 years ago
|
for (uint256 i = 0; i < minters.length; ++i) {
|
||
|
_minters.add(minters[i]);
|
||
|
}
|
||
|
|
||
|
for (uint256 i = 0; i < namers.length; ++i) {
|
||
|
_namers.add(namers[i]);
|
||
|
}
|
||
6 years ago
|
}
|
||
|
|
||
6 years ago
|
function mint(address to, uint256 amount) public {
|
||
|
// Only minters can mint
|
||
6 years ago
|
require(minters.has(msg.sender), "DOES_NOT_HAVE_MINTER_ROLE");
|
||
6 years ago
|
|
||
6 years ago
|
_mint(to, amount);
|
||
|
}
|
||
|
|
||
6 years ago
|
function rename(string memory name, string memory symbol) public {
|
||
|
// Only namers can change the name and symbol
|
||
6 years ago
|
require(namers.has(msg.sender), "DOES_NOT_HAVE_NAMER_ROLE");
|
||
6 years ago
|
|
||
6 years ago
|
name = name;
|
||
|
symbol = symbol;
|
||
|
}
|
||
|
}
|
||
6 years ago
|
----
|
||
6 years ago
|
|
||
6 years ago
|
So clean! By splitting concerns this way, we can define more granular levels of permission, which was lacking in the _ownership_ approach to access control. Note that an account may have more than one role, if desired.
|
||
|
|
||
6 years ago
|
OpenZeppelin uses `Roles` extensively with predefined contracts that encode rules for each specific role. A few examples are: link:api/token/ERC20#erc20mintable[`ERC20Mintable`] which uses the link:api/access#minterrole[`MinterRole`] to determine who can mint tokens, and link:api/crowdsale#whitelistcrowdsale[`WhitelistCrowdsale`] which uses both link:api/access#whitelistadminrole[`WhitelistAdminRole`] and link:api/access#whitelistedrole[`WhitelistedRole`] to create a set of accounts that can purchase tokens.
|
||
6 years ago
|
|
||
6 years ago
|
This flexibility allows for interesting setups: for example, a link:api/crowdsale#mintedcrowdsale[`MintedCrowdsale`] expects to be given the `MinterRole` of an `ERC20Mintable` in order to work, but the token contract could also extend link:api/token/ERC20#erc20pausable[`ERC20Pausable`] and assign the link:api/access#pauserrole[`PauserRole`] to a DAO that serves as a contingency mechanism in case a vulnerability is discovered in the contract code. Limiting what each component of a system is able to do is known as the https://en.wikipedia.org/wiki/Principle_of_least_privilege[principle of least privilege], and is a good security practice.
|
||
6 years ago
|
|
||
6 years ago
|
[[usage-in-openzeppelin]]
|
||
|
== Usage in OpenZeppelin
|
||
6 years ago
|
|
||
|
You'll notice that none of the OpenZeppelin contracts use `Ownable`. `Roles` is a prefferred solution, because it provides the user of the library with enough flexibility to adapt the provided contracts to their needs.
|
||
|
|
||
6 years ago
|
There are some cases, though, where there's a direct relationship between contracts. For example, link:api/crowdsale#refundablecrowdsale[`RefundableCrowdsale`] deploys a link:api/payment#refundescrow[`RefundEscrow`] on construction, to hold its funds. For those cases, we'll use link:api/ownership#secondary[`Secondary`] to create a _secondary_ contract that allows a _primary_ contract to manage it. You could also think of these as _auxiliary_ contracts.
|