Extending Contracts

Most of the OpenZeppelin Contracts are expected to be used via inheritance: you will inherit from them when writing your own contracts.

This is the commonly found is syntax, like in contract MyToken is ERC20.

Unlike contracts, Solidity librarys are not inherited from and instead rely on the using for syntax.

OpenZeppelin Contracts has some librarys: most are in the Utils directory.

Overriding

Inheritance is often used to add the parent contract’s functionality to your own contract, but that’s not all it can do. You can also change how some parts of the parent behave using overrides.

For example, imagine you want to change AccessControl so that revokeRole can no longer be called. This can be achieved using overrides:

pragma solidity ^0.6.0;

import "@openzeppelin/contracts/access/AccessControl.sol";

contract ModifiedAccessControl is AccessControl {
    // Override the revokeRole function
    function revokeRole(bytes32, address) public override {
        revert("ModifiedAccessControl: cannot revoke roles");
    }
}

The old revokeRole is then replaced by our override, and any calls to it will immediately revert. We cannot remove the function from the contract, but reverting on all calls is good enough.

Calling super

Sometimes you want to extend a parent’s behavior, instead of outright changing it to something else. This is where super comes in.

The super keyword will let you call functions defined in a parent contract, even if they are overridden. This mechanism can be used to add additional checks to a function, emit events, or otherwise add functionality as you see fit.

For more information on how overrides work, head over to the official Solidity documentation.

Here is a modified version of AccessControl where revokeRole cannot be used to revoke the DEFAULT_ADMIN_ROLE:

pragma solidity ^0.6.0;

import "@openzeppelin/contracts/access/AccessControl.sol";

contract ModifiedAccessControl is AccessControl {
    function revokeRole(bytes32 role, address account) public override {
        require(
            role != DEFAULT_ADMIN_ROLE,
            "ModifiedAccessControl: cannot revoke default admin role"
        );

        super.revokeRole(role, account);
    }
}

The super.revokeRole statement at the end will invoke AccessControl's original version of revokeRole, the same code that would’ve run if there were no overrides in place.

As of v3.0.0, view functions are not virtual in OpenZeppelin, and therefore cannot be overriden. We’re considering lifting this restriction in an upcoming release. Let us know if this is something you care about!

Using Hooks

Sometimes, in order to extend a parent contract you will need to override multiple related functions, which leads to code duplication and increased likelihood of bugs.

For example, consider implementing safe ERC20 transfers in the style of IERC721Receiver. You may think overriding transfer and transferFrom would be enough, but what about _transfer and _mint? To prevent you from having to deal with these details, we introduced hooks.

Hooks are simply functions that are called before or after some action takes place. They provide a centralized point to hook into and extend the original behavior.

Here’s how you would implement the IERC721Receiver pattern in ERC20, using the _beforeTokenTransfer hook:

pragma solidity ^0.6.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract ERC20WithSafeTransfer is ERC20 {
    function _beforeTokenTransfer(address from, address to, uint256 amount)
        internal virtual override
    {
        super._beforeTokenTransfer(from, to, amount);

        require(_validRecipient(to), "ERC20WithSafeTransfer: invalid recipient");
    }

    function _validRecipient(address to) private view returns (bool) {
        ...
    }

Using hooks this way leads to cleaner and safer code, without having to rely on a deep understanding of the parent’s internals.

Hooks are a new feature of OpenZeppelin Contracts v3.0.0, and we’re eager to learn how you plan to use them!

So far, the only available hook is _beforeTransferHook, in all of ERC20, ERC721 and ERC777. If you have ideas for new hooks, let us know!

Rules of Hooks

There’s a few guidelines you should follow when writing code that uses hooks in order to prevent issues. They are very simple, but do make sure you follow them:

  1. Whenever you override a parent’s hook, re-apply the virtual attribute to the hook. That will allow child contracts to add more functionality to the hook.

  2. Always call the parent’s hook in your override using super. This will make sure all hooks in the inheritance tree are called: contracts like ERC20Pausable rely on this behavior.

contract MyToken is ERC20 {
    function _beforeTokenTransfer(address from, address to, uint256 amount)
        internal virtual override // Add virtual here!
    {
        super._beforeTokenTransfer(from, to, amount); // Call parent hook
        ...
    }
}

That’s it! Enjoy simpler code using hooks!