Skip to content
On this page

合约继承

大多数OpenZeppelin的合约希望使用者通过继承的方式来使用,你可以在自己的合约中直接继承他们。

继承通过 is 关键字来实现,比如contract MyToken is ERC20

注意

和合约contract不同的是,Solidity中的Library不是通过继承的方式来使用的,而是通过using for来使用。
OpenZeppelin合约库中有很多的Library,你可以在Utils目录中找到它们。

优势

继承可以将父合约中的功能加入到你自己的合约中,但并不只是这样。你还可以通过overrides来改变父合约中的功能。
例如:你希望改变AccessControl中的revokeRole方法,使其不再被调用:

solidity
// contracts/ModifiedAccessControl.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.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");
    }
}

旧的revokeRole方法被重写override所覆盖,所有对它的调用都会被直接拒绝。我们无法删除remove合约中的方法,但是通过重写方法的方式使其被拒绝调用已经足够了。

调用super

有时你会想继承一部分父合约中的行为方法,并不是完全的改变它们,这时需要用到super关键字。 super关键字使你可以调用父合约中的方法,尽管你正在对其进行重写。这种机制可以用来给父方法中添加检查功能模块,触发事件,或者根据你自己的想法添加其他功能。

提示

更多关于如何使用重写override可以参考official Solidity documentation

以下是访问控制的修改版本,我们将让revokeRole方法不能修改DEFAULT_ADMIN_ROLE角色。

solidity
// contracts/ModifiedAccessControl.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.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);
    }
}

super.revokeRole语句将调用AccessControll的原始版本的revokeRole方法。

注意

在3.0.0版本中,view方法在OpenZeppelin中并不是virtual的,因此无法被重写。我们会在将来的发布中解除这个限制。如果这是你关心的事情,请让我们知道。

使用Hooks

有时候,为了继承父合约你需要重写多个相关的方法,这会导致重复工作,也增加了产生BUG的可能。
例如,我们使用IERC721Receiver的方式来实现ERC20接口的安全转账。如果你以为只需要重写transfertransferFrom就足够了,那么_transfer_mint方法呢?为了避免出现这种情况,我们可以使用hooks

Hooks是一个在某些操作被执行前后调用的简单方法。它们提供了一个点,让hooks注入,然后再执行原来的操作。 下面展现了如何使用IERC721Receiver的模式来实现ERC20,这里用到了_beforeTokenTransferhook

solidity
pragma solidity ^0.8.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) {
        ...
    }

    ...
}

使用hooks可以获得更加干净,更加安全的代码,也不需要对父合约内部进行深入的理解。

注意

Hooks是OpenZeppelin 合约 V3.0.0版本出现的新特性,我们很希望看到你是如何使用它的。 目前为止,唯一可用的hook是_beforeTransferHook,在ERC20,ERC721,ERC777ERC1155里面都有这个hook。如果觉得应该有哪些新的hook,请联系我们。

Hooks的规则

在使用hooks来进行编码时,为了避免产生问题,我们提供了一些使用原则。它们非常简单,但请一定要遵循它们。

1.当你重写父合约中的hook时,请为其加上virtual属性。这样会允许其子合约也可以为hook增加更多的功能。

2.一定要在你重写的方法中使用super来调用父合约的hook。这会保证所有继承树中的hooks都被调用。比如ERC20Pausable合约就需要采用这种方式。

solidity
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
        ...
    }
}

就这么多了,请尽情的使用hook来让编码变得更加简单吧!

Released under the MIT License.