Appearance
合约继承
大多数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接口的安全转账。如果你以为只需要重写transfer和transferFrom就足够了,那么_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,ERC777和ERC1155里面都有这个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来让编码变得更加简单吧!