本文档介绍了如何通过继承和重写OpenZeppelin合约来扩展其功能。
你当前阅读的不是此文档的最新版本。5.x 是当前版本。
大多数 OpenZeppelin 合约都期望通过继承来使用:在编写自己的合约时,你将会继承它们。
这是常见的 is
语法,例如 contract MyToken is ERC20
。
与 contract 不同,Solidity library 不会被继承,而是依赖于 using for 语法。<br>OpenZeppelin 合约有一些 library :大多数都在 Utils 目录中。 |
继承通常用于将父合约的功能添加到你自己的合约中,但这并不是它的全部功能。你还可以使用重写来更改父合约某些部分的行为。
例如,假设你想更改 AccessControl
,以便不能再调用 revokeRole
。这可以使用重写来实现:
// contracts/ModifiedAccessControl.sol
// SPDX-License-Identifier: MIT
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");
}
}
然后,旧的 revokeRole
将被我们的重写替换,并且任何对其的调用都将立即回退。我们不能从合约中删除该函数,但是在所有调用中回退就足够了。
super
有时你想扩展父级的行为,而不是完全将其更改为其他内容。这就是 super
的用武之地。
super
关键字将允许你调用在父合约中定义的函数,即使它们被重写了也是如此。此机制可用于向函数添加其他检查、发出事件或以你认为合适的方式添加功能。
有关重写如何工作的更多信息,请访问 Solidity 官方文档。 |
这是一个修改后的 AccessControl
版本,其中 revokeRole
不能用于撤销 DEFAULT_ADMIN_ROLE
:
// contracts/ModifiedAccessControl.sol
// SPDX-License-Identifier: MIT
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);
}
}
末尾的 super.revokeRole
语句将调用 AccessControl
原始版本的 revokeRole
,如果没有任何重写则会运行相同的代码。
从 v3.0.0 开始,view 函数在 OpenZeppelin 中不是 virtual 的,因此无法被重写。我们正在考虑在即将发布的版本中取消此限制。如果你对此感兴趣,请告诉我们! |
有时,为了扩展父合约,你需要重写多个相关函数,这会导致代码重复和增加错误的可能。
例如,考虑以 IERC721Receiver
的风格实现安全的 ERC20
transfer。你可能会认为重写 transfer
和 transferFrom
就足够了,但是 _transfer
和 _mint
呢?为了防止你不得不处理这些细节,我们引入了 hooks。
Hooks 只是在某些操作发生之前或之后调用的函数。它们提供了一个集中的点来hook into 并扩展原始行为。
以下是如何在 ERC20
中使用 _beforeTokenTransfer
hook 来实现 IERC721Receiver
模式:
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) {
...
}
...
}
以这种方式使用 hooks 可以生成更简洁、更安全的代码,而无需深入了解父级的内部结构。
Hooks 是 OpenZeppelin Contracts v3.0.0 的一项新功能,我们很想了解你打算如何使用它们!<br>到目前为止,唯一可用的 hook 是 _beforeTransferHook ,在所有的 ERC20 ,ERC721 ,ERC777 和 ERC1155 中。如果你有关于新 hooks 的想法,请告诉我们! |
在编写使用 hooks 的代码时,应遵循一些准则,以防止出现问题。它们非常简单,但是请确保你遵循它们:
每当你重写父级的 hook 时,请将 virtual
属性重新应用于该 hook。这将允许子合约向 hook 添加更多功能。
始终使用 super
在重写中调用父级的 hook。这将确保调用继承树中的所有 hooks:像 ERC20Pausable
这样的合约依赖于此行为。
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
...
}
}
就这样!享受使用 hooks 编写的更简单的代码!
- 原文链接: docs.openzeppelin.com/co...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!