扩展合约 - OpenZeppelin 文档

本文档介绍了如何通过继承和重写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 的,因此无法被重写。我们正在考虑在即将发布的版本中取消此限制。如果你对此感兴趣,请告诉我们!

使用 Hooks

有时,为了扩展父合约,你需要重写多个相关函数,这会导致代码重复和增加错误的可能。

例如,考虑以 IERC721Receiver 的风格实现安全的 ERC20 transfer。你可能会认为重写 transfertransferFrom 就足够了,但是 _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,在所有的 ERC20ERC721ERC777ERC1155 中。如果你有关于新 hooks 的想法,请告诉我们!

Hooks 规则

在编写使用 hooks 的代码时,应遵循一些准则,以防止出现问题。它们非常简单,但是请确保你遵循它们:

  1. 每当你重写父级的 hook 时,请将 virtual 属性重新应用于该 hook。这将允许子合约向 hook 添加更多功能。

  2. 始终使用 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 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
OpenZeppelin
OpenZeppelin
江湖只有他的大名,没有他的介绍。