OpenZeppelin Contracts v3.0 release candidate

We’re excited to announce the first release candidate of OpenZeppelin Contracts v3.0 :sparkles:

This is release features the migration to Solidity v0.6, as well as a revamped access control system.

To install the release candidate, run:

npm install --save-dev @openzeppelin/contracts@next

What’s New

  • All contracts were migrated to Solidity v0.6.
  • AccessControl was designed with help from the community and has replaced Roles contracts (such as MinterRole and PauserRole), which were removed.
  • Crowdsales were removed: we’ll continue to provide support for security issues on the v2.5 release, but will not bring them over to v3.0.
  • We’ve added hooks, a new feature of the library that will make extending it easier than ever. Read more below!
  • Many, many breaking changes with small improvements. We’ve also moved some contracts around (e.g. Ownable is now found under the access directory) and deleted some that were not being used. Head to our changelog to see the full list.

Compiling v0.6 Contracts

You can use the OpenZeppelin CLI to compile any Solidity v0.6 contract: just update the pragma statement on your source code and you’ll be good to go!

pragma solidity ^0.6.0;

Note that you will need to use the recent v2.7 release of the CLI to have Solidity v0.6 support. For detailed information about using the CLI compiler, head to its documenation.

Revamped Access Control

One of our most widely-used contracts is Ownable, providing a simple authorization scheme. However, this fell short in complex systems with multiple permissions.

The v3.0 release introduces AccessControl, 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.

AccessControl was designed with a security-first mindset, receiving input from a wide array of users and incorporating best practices in the field. We’ll have detailed guides covering it out soon, but in the meantime you can refer to the documentation on the source file.

Migrating From OpenZeppelin Contracts v2.5

Other than the contract removals mentioned above, the library API is pretty much the same as in the v2.5 release, so the migration should be straightforward. For instructions on how to update your Solidity v0.5 contracts to v0.6, refer to the official documentation.

The exception to this is contracts that use the Gas Station Network (GSN): if you’re inheriting from GSNRecipient or one of the other GSN contracts, you’ll need to add the following snippet to your contracts:

function _msgSender() internal view override(Context, GSNRecipient) returns (address payable) {
    return GSNRecipient._msgSender();
}

function _msgData() internal view override(Context, GSNRecipient) returns (bytes memory) {
    return GSNRecipient._msgData();
}

Using Hooks

To improve library flexibility, we’re introducing hooks: functions that are called at specific moments during a contract’s operation that you can use to hook into the internals and extend as you wish.

For example, the _beforeTokenTransfer hook in ERC20, ERC721 and ERC777 makes it very easy to add additional checks or actions to execute whenever tokens are transferred, minted or burned, regardless of what prompted it.

// Tokens can only be transferred, minted or burned if the contract is not paused
contract ERC20Pausable is ERC20, Pausable {
    function _beforeTokenTransfer(address from, address to, uint256 amount) 
        internal virtual override 
    {
        super._beforeTokenTransfer(from, to, amount);

        require(!paused(), "ERC20Pausable: token transfer while paused");
    }
}

As an additional benefit, using hooks will allow you to side-step some of the edge-cases product of the new override keyword.

Next Steps

As with all release candidates, the final release will follow one or two weeks later. This is so that community members get a chance to share their thoughts on the upcoming changes before they are made final.

So give this release candidate a try, upgrade your project to Solidity v0.6, and tell us what you think!

4 Likes

Nice change to access control. I’d like to have a method to list a certain member role too. Also it could be useful to have another method to shutdown member’s access immediately and then to remove exact roles associations.

I’d suggest to replace error messages like AccessControl: sender must be an admin to revoke with error codes. For example oz/access-control/role-admin. It has a lot of benefits:

  1. It’s both human and computer friendly in the same time.
  2. It could be a part of URL to documentation website.
  3. It’s easy to make a structure (tuple) from a string using only one call in JS (and other languages):
    const [ns, contract, failure] = "oz/access-control/role-admin".split("/")
    
1 Like

Does this mean listing all roles an account has? This could be useful, though it would require additional storage (and therefore increase gas costs). I created an issue for this, thanks!

Interesting! I fear this may lead to too much complexity though, since we’d have to keep track of ‘banned’ accounts, and these would be in a strange intermediate state where they have a role, but are unable to use it. It also creates issues regarding permission, who is allowed to ban an account? It’d be equivalent to calling revoke on all roles, which only the role admins can do.

If the goal is to remove all roles from an account, I think this can be better achieved with the enumeration suggested above, and then calling revokeRole for each role.

Thanks! We’ve had some discussion on what revert reasons should look like, and consensus was the human-readable strings were important. Note you can still split on the : to separate contract and message, as shown on your snippet above!

The bit about linking to the documentation is very interesting though, I’ll think some more on it. Thanks!

1 Like

Does this mean listing all roles an account has?

Yes. It’s exactly what I meant.

I fear this may lead to too much complexity though, since we’d have to keep track of ‘banned’ accounts, and these would be in a strange intermediate state where they have a role, but are unable to use it.

In my opinion it’s an active/inactive boolean. It looks pretty straightforward to me, maybe for others too, probably it should be researched/need more feedback. It could (and probably should) be implemented as a separated contract for inheritance. And then be used in the final contract like this:

require(isActive(_msgSender()))
require(hasRole('admin', _msgSender()))

It’s helpful to prevent attacks in the periods when gas cost is too high and a bunch of rejection transactions just could stall. Also it allows to suspend account membership for some period of time and then restore it without a need of reassigning all the permissions.

human-readable strings were important

Agree with you about importance of human-readability. But also unstructured string error message usually leads developers just to ignore error instead of properly handling it, what is worse in my opinion. And now solidity has a try-catch statement so it would be way more easy to identify an error. And structure should help to remember error code. This is my thoughts on it, maybe it would change your mind.

1 Like

Is this information going to be needed by a smart contract? If it is only for UI or audit purposes you could retrieve role memberships from blockchain events with a listener.