{"content":{"title":"用 OpenZeppelin 和 Foundry 创建和部署可升级的 ERC20 代币","body":">- 原文链接：[Deploy an Upgradeable ERC20 Token](https://www.quicknode.com/guides/ethereum-development/smart-contracts/how-to-create-and-deploy-an-upgradeable-erc20-token#create-the-erc-20-upgradeable-token-smart-contract)\r\n>- 译文出自：[登链翻译计划](https://github.com/lbc-team/Pioneer)\r\n>- 译者：[翻译小组](https://learnblockchain.cn/people/412) ，校对：[Tiny 熊](https://learnblockchain.cn/people/15)\r\n>- 本文永久链接：[learnblockchain.cn/article…](https://learnblockchain.cn/article/7328)\r\n\r\n\r\n## 概述\r\n\r\n在部署后能够调整和改进智能合约的能力变得至关重要。随着项目的发展，可能会出现意想不到的需求、潜在的优化或不同的产品。对许多人来说，传统的部署全新合约并迁移用户的方法不仅不切实际，而且可能导致用户的沮丧和信任流失。这就是可升级合约的威力所在，它允许你在保留其地址和用户数据的同时改进代币的逻辑。在本教程中，我们将向你展示如何利用 OpenZeppelin 的经过审计的合约套件和 Foundry 的部署功能来创建一个可升级的 ERC-20 代币。\r\n\r\n## 将要做的事情\r\n\r\n- 巩固你对 ERC-20 代币的理解\r\n- 了解可升级性如何为你的 ERC-20 代币增加更多功能\r\n- 使用 [QuickNode](https://www.quicknode.com/signup?utm_source=internal&utm_campaign=guides&utm_content=how-to-create-and-deploy-an-upgradeable-erc20-token) 连接到区块链 （译者备注：本文原作者为 QuickNode ）\r\n- 使用 [OpenZeppelin](https://www.openzeppelin.com/) 和[Foundry](https://book.getfoundry.sh/getting-started/installation)创建和部署可升级的 ERC-20 代币\r\n\r\n## 你需要准备的东西\r\n\r\n- [对以太坊和智能合约的基本理解](https://decert.me/tutorials)\r\n- [了解 ERC-20 代币标准](https://learnblockchain.cn/tags/ERC20)\r\n- 已[安装](https://nodejs.org/en) Node.js\r\n- 一个 Web3 钱包\r\n\r\n## 什么是 ERC-20 代币？\r\n\r\n[ERC-20 代币标准](https://learnblockchain.cn/docs/eips/eip-20.html)是在以太坊和基于 EVM 的区块链上创建可互换代币的蓝图。ERC-20 引入了一套标准化的规则，包括了强制性的`transfer`、`balanceOf`和`totalSupply`等函数，确保了代币之间的一致行为。类似于面向对象编程中的接口，开发人员可以欣赏它在强制采用这些关键函数方面的作用。这种统一性已经彻底改变了代币的交互方式，使它们变得无缝和高效，现在代币可以轻松交易、在 dApps 中使用或存储在钱包中，而所有这些都是在以太坊的区块链上运行的。\r\n\r\n## 什么是可升级的 ERC-20 代币？\r\n\r\n在其核心，可升级的代币拥抱在部署后增强或修改其功能的灵活性。这是通过分层架构实现的：**代理**充当与用户交互的不可变智能合约，而**逻辑**智能合约（有时也称为*实现*合约）包含业务逻辑。升级是通过更改代理对较新逻辑合约的引用来实现的，确保代币余额和其他状态变量保持不变。在这种设计中，**管理员**角色通常管理升级过程，决定逻辑合约何时以及如何更改，从而在代币的生命周期中引入了一种治理元素。\r\n\r\n有不同类型的可升级智能合约，让我们来介绍最常见的并进行比较。\r\n\r\n## 升级方式\r\n\r\n### 透明代理\r\n\r\n透明代理模式旨在区分管理员和普通用户。它通过使用两个不同的地址来工作：一个用于管理员（可以升级合约），另一个用于普通用户（可以与合约的函数交互）。代理合约包括了区分管理员调用和普通用户调用的逻辑，防止在常规使用过程中意外执行管理功能。\r\n\r\n### UUPS 代理\r\n\r\nUUPS（通用可升级代理标准）代理是一种更简化和更节省 gas 的方法。在这种模式中，升级功能嵌入在逻辑合约本身中。这种设计减少了对额外'管理员'合约的需求，简化了结构。但是，它也要求逻辑合约在设计时考虑到可升级性，在其中嵌入必要的升级功能。\r\n\r\n### Beacon 代理\r\n\r\nBeacon 代理模式引入了一个中央的“信标（Beacon）”合约，所有代理实例都引用该合约以获取当前逻辑合约的地址。这种设计允许更高效的升级过程，因为在信标中更新逻辑合约地址会自动更新所有关联的代理。在需要保持多个代理合约与同一逻辑合约同步的情况下，这是特别有用的。\r\n\r\n要了解更多关于代理的信息，请查看这个 [QuickNode 指南](https://www.quicknode.com/guides/ethereum-development/smart-contracts/an-introduction-to-upgradeable-smart-contracts)和[OpenZeppelin 代理](https://docs.openzeppelin.com/contracts/5.x/api/proxy)。\r\n\r\n在本指南的技术演示中，我们将介绍 UUPS 代理方法。\r\n\r\n## 为什么选择 OpenZeppelin？\r\n\r\n[OpenZeppelin](https://www.openzeppelin.com/)提供了一系列可重用的智能合约，这些合约是安全的并且经过了审计，确保了你的智能合约的基本构建块是安全的。对于可升级性，OpenZeppelin 提供了代理合约，将调用委托给实现合约。这种方法允许开发人员替换实现合约，同时保留代理的存储、地址和余额。\r\n\r\n要了解更多关于 OpenZeppelin 及其可升级性插件的信息，请查看这个[资源](https://docs.openzeppelin.com/upgrades-plugins/) 。\r\n\r\n## 项目条件：创建  RPC 端点\r\n\r\n要将智能合约部署到区块链，你需要一个 API 端点来与网络通信。你可以使用公共节点或部署和管理自己的基础设施；但是，如果你希望获得 8 倍更快的响应时间，你可以把繁重的工作交给我们（QuickNode）。在[这里](https://www.quicknode.com/signup?utm_source=internal&utm_campaign=guides&utm_content=how-to-create-and-deploy-an-upgradeable-erc20-token)注册一个免费账户。\r\n\r\n登录后，点击**创建端点**按钮，然后选择你要部署的区块链和网络。在本指南中，我们将选择**以太坊 Sepolia**链。\r\n\r\n创建端点后，复制 HTTP 提供程序链接并保持方便，因为你将在接下来的部分中需要它。\r\n\r\n![Sepolia QuickNode 端点](https://img.learnblockchain.cn/attachments/migrate/1706434564063)\r\n\r\n## 项目先决条件：为钱包充值\r\n\r\n如果你需要在 Sepolia 测试网上获得 ETH，[Multi-Chain QuickNode Faucet](https://faucet.quicknode.com/?utm_source=internal&utm_campaign=guides) 可以帮助你轻松获取测试 ETH！\r\n\r\n转到 Multi-Chain QuickNode Faucet 并连接你的钱包（例如 MetaMask、Coinbase 钱包）或粘贴你的钱包地址以获取测试 ETH。请注意，以太坊主网需要 0.001 ETH 的余额要求才能使用 EVM 水龙头。你还可以在推特上发布你的请求以获得奖励！\r\n\r\n![Multi-Chain QuickNode Faucet](https://img.learnblockchain.cn/attachments/migrate/1706434564089)\r\n在下一节中，我们将转向创建项目目录并配置项目文件和依赖项。\r\n\r\n## 创建可升级的 ERC-20 代币项目\r\n\r\n现在我们对 ERC-20 代币以及以太坊上的可升级性有了很好的基本理解，让我们开始编写一个实际的示例。首先，让我们安装并初始化一个 Foundry 项目。\r\n\r\n### 安装 Foundry\r\n\r\n如果你尚未安装 Foundry，请打开你的终端并运行以下命令：\r\n\r\n```sh\r\ncurl -L https://foundry.paradigm.xyz | bash\r\n```\r\n\r\n上面的命令将安装`Foundryup`。然后，根据屏幕上的指示继续操作，这将使你能够在 CLI 中使用`foundryup`命令。安装完成后，你可以在终端中运行`foundryup -v`命令来检查版本并安装最新的（夜间）预编译二进制文件。\r\n\r\n提示\r\n\r\n> 如果你使用的是 Windows，你需要安装并使用 Git BASH 或 WSL 作为你的终端，因为 Foundryup 目前不支持 Powershell 或 Cmd。请按照[此处](https://book.getfoundry.sh/getting-started/installation)的说明进行操作。\r\n\r\n> 或者，如果你使用的是 M1 Mac，并且出现错误：`dyld[32719]: Library not loaded: /usr/local/opt/libusb/lib/libusb-1.0.0.dylib`；请尝试通过 brew 安装该库：`brew install libusb`。\r\n\r\n配置完成后，使用以下命令初始化一个 Foundry 项目并进入该目录：\r\n\r\n```sh\r\nforge init erc20_upgradeable && cd erc20_upgradeable\r\n```\r\n\r\n然后，进入**erc20_upgradeable**目录，你的项目结构应如下所示：\r\n\r\n```sh\r\n.\r\n├── lib\r\n├── script\r\n├── src\r\n└── test\r\nfoundry.toml\r\n```\r\n\r\n> 你可能会在项目中看到一些现有的示例文件，但你可以忽略它们。\r\n\r\n让我们回顾一下这个结构。\r\n\r\n- **lib**：存储依赖项的目录\r\n- **script**：部署合约或与现有智能合约交互的目录\r\n- **src**：智能合约的默认目录\r\n- **test**：运行测试的默认目录\r\n- **foundry.toml**：可以修改版本、优化、RPC 网络、合约验证等设置的配置文件。\r\n\r\n我们还需要创建项目所需的文件。运行以下命令以创建智能合约文件、测试文件、部署文件和 remappings.txt（我们将使用它来正确映射我们的库依赖项）。\r\n\r\n```sh\r\necho > src/MyToken.sol && echo > src/MyTokenV2.sol && echo > test/MyTokenTest.t.sol && echo > script/deployToken.s.sol && echo > script/deployProxy.s.sol && echo > remappings.txt\r\n```\r\n\r\n在下一节中，我们将安装所需的库并设置我们的配置。\r\n\r\n### 配置项目\r\n\r\n通过初始化我们的项目，让我们安装本指南中将要使用的 OpenZeppelin 库。在项目的根目录中，在你的终端中运行以下命令：\r\n\r\n```sh\r\nforge install OpenZeppelin/openzeppelin-contracts --no-commit\r\nforge install OpenZeppelin/openzeppelin-foundry-upgrades --no-commit\r\nforge install OpenZeppelin/openzeppelin-contracts-upgradeable --no-commit\r\n```\r\n\r\n注意末尾的`--no-commit`标志。这是因为你的项目文件夹已经与 git 存储库关联，所以我们必须指定不提交任何内容。\r\n\r\n现在，让我们通过填写我们之前创建的**remappings.txt**文件来将导入配置到正确的路径。\r\n\r\n向文件中添加以下配置：\r\n\r\n```sh\r\n@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/\r\n@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/\r\n```\r\n\r\n保存文件后，让我们打开**foundry.toml**文件，并向你的文件添加以下代码：\r\n\r\n```sh\r\nbuild_info = true\r\nextra_output = [\"storageLayout\"]\r\n[rpc_endpoints]\r\nsepolia = \"QUICKNODE_ENDPOINT_URL\"\r\n```\r\n\r\n前两行（例如，`build_info`和`extra_output`）是在使用 OpenZeppelin Foundry Upgrades [library](https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades)（感谢 ericglau！）时所需的配置。此外，由于我们在本指南中在 Sepolia 测试网上部署，我们将把这个端点命名为`sepolia`。如果你在其他网络上部署，可以更改名称。**注意**，请记住将**QUICKNODE_ENDPOINT_URL**占位符替换为你之前创建的实际 QuickNode HTTP 提供程序 URL。\r\n\r\n最后，让我们在环境中设置我们的私钥，使用以下变量名和你的私钥。在你的终端中运行以下命令，并将**YOUR_PRVATE_KEY**占位符更新为你的实际私钥。\r\n\r\n```sh\r\nexport PRIVATE_KEY=YOUR_PRIVATE_KEY\r\n```\r\n\r\n配置设置完成后，让我们继续创建可升级的 ERC-20 代币。\r\n\r\n## 创建可升级的 ERC-20 代币智能合约\r\n\r\n是时候为可升级的 ERC-20 代币合约构建逻辑了。在我们开始编写代码之前，让我们先了解一下我们的 ERC-20 代币将具有的确切功能。\r\n\r\n我们将在 ERC-20 代币中继承不同的智能合约：\r\n\r\n- `ERC20Upgradeable` - 包含可升级功能的 ERC-20 代币\r\n- `OwnableUpgradeable` - 仅允许所有者执行某些功能（所有者可以被转移）\r\n- `ERC20PermitUpgradeable` - 添加了一个许可功能，用户可以使用它来节省离线批准的成本\r\n- `Initializable` - 类似于构造函数，我们将使用它来设置代币的初始参数\r\n- `UUPSUpgradeable` - 我们的 ERC-20 代币将继承的通用可升级代理标准模式逻辑\r\n\r\n现在，进入你的**src**文件夹，并打开**MyToken.sol**文件。更新文件内容以包括：\r\n\r\n```javascript\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.19;\r\n\r\nimport \"openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol\";\r\nimport \"openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol\";\r\nimport \"openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC20PermitUpgradeable.sol\";\r\nimport \"openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol\";\r\nimport \"openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol\";\r\n\r\ncontract MyToken is Initializable, ERC20Upgradeable, OwnableUpgradeable, ERC20PermitUpgradeable, UUPSUpgradeable {\r\n    /// @custom:oz-upgrades-unsafe-allow constructor\r\n    constructor() {\r\n        _disableInitializers();\r\n    }\r\n\r\n    function initialize(address initialOwner) initializer public {\r\n        __ERC20_init(\"MyToken\", \"MTK\");\r\n        __Ownable_init(initialOwner);\r\n        __ERC20Permit_init(\"MyToken\");\r\n        __UUPSUpgradeable_init();\r\n\r\n        _mint(msg.sender, 1000000 * 10 ** decimals());\r\n    }\r\n\r\n    function mint(address to, uint256 amount) public onlyOwner {\r\n        _mint(to, amount);\r\n    }\r\n\r\n    function _authorizeUpgrade(address newImplementation)\r\n        internal\r\n        onlyOwner\r\n        override\r\n    {}\r\n}\r\n```\r\n\r\n> 为了下次轻松部署，请查看[Remix.IDE](https://remix.ethereum.org/?#code=Ly8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IE1JVApwcmFnbWEgc29saWRpdHkgXjAuOC4yMDsKCmltcG9ydCAiQG9wZW56ZXBwZWxpbi9jb250cmFjdHMtdXBncmFkZWFibGVANS4wLjAvdG9rZW4vRVJDMjAvRVJDMjBVcGdyYWRlYWJsZS5zb2wiOwppbXBvcnQgIkBvcGVuemVwcGVsaW4vY29udHJhY3RzLXVwZ3JhZGVhYmxlQDUuMC4wL2FjY2Vzcy9Pd25hYmxlVXBncmFkZWFibGUuc29sIjsKaW1wb3J0ICJAb3BlbnplcHBlbGluL2NvbnRyYWN0cy11cGdyYWRlYWJsZUA1LjAuMC9wcm94eS91dGlscy9Jbml0aWFsaXphYmxlLnNvbCI7CmltcG9ydCAiQG9wZW56ZXBwZWxpbi9jb250cmFjdHMtdXBncmFkZWFibGVANS4wLjAvcHJveHkvdXRpbHMvVVVQU1VwZ3JhZGVhYmxlLnNvbCI7Cgpjb250cmFjdCBNeVRva2VuIGlzIEluaXRpYWxpemFibGUsIEVSQzIwVXBncmFkZWFibGUsIE93bmFibGVVcGdyYWRlYWJsZSwgVVVQU1VwZ3JhZGVhYmxlIHsKICAgIC8vLyBAY3VzdG9tOm96LXVwZ3JhZGVzLXVuc2FmZS1hbGxvdyBjb25zdHJ1Y3RvcgogICAgY29uc3RydWN0b3IoKSB7CiAgICAgICAgX2Rpc2FibGVJbml0aWFsaXplcnMoKTsKICAgIH0KCiAgICBmdW5jdGlvbiBpbml0aWFsaXplKGFkZHJlc3MgaW5pdGlhbE93bmVyKSBpbml0aWFsaXplciBwdWJsaWMgewogICAgICAgIF9fRVJDMjBfaW5pdCgiTXlUb2tlbiIsICJNVEsiKTsKICAgICAgICBfX093bmFibGVfaW5pdChpbml0aWFsT3duZXIpOwogICAgICAgIF9fVVVQU1VwZ3JhZGVhYmxlX2luaXQoKTsKICAgIH0KCiAgICBmdW5jdGlvbiBtaW50KGFkZHJlc3MgdG8sIHVpbnQyNTYgYW1vdW50KSBwdWJsaWMgb25seU93bmVyIHsKICAgICAgICBfbWludCh0bywgYW1vdW50KTsKICAgIH0KCiAgICBmdW5jdGlvbiBfYXV0aG9yaXplVXBncmFkZShhZGRyZXNzIG5ld0ltcGxlbWVudGF0aW9uKQogICAgICAgIGludGVybmFsCiAgICAgICAgb25seU93bmVyCiAgICAgICAgb3ZlcnJpZGUKICAgIHt9Cn0K&deployProxy=true)\r\n\r\n记得保存文件。让我们回顾一下代码。\r\n\r\n如上所述，在我们的 ERC-20 代币中继承了不同的智能合约。然后，在合约的`constructor`中，我们通过调用`_disableInitializers()`确保初始化方法只运行一次，以防止意外重新初始化。中间的`initialize`函数设置了代币的名称为\"MyToken\"和符号为\"MTK\"，将所有权分配给提供的`initialOwner`，并激活了增强的授权机制和可升级功能。还向调用此函数的用户铸造了初始代币供应。此外，合约提供了一个仅限所有者的`mint`函数，允许创建新代币。通过内部的`_authorizeUpgrade`方法，确保了安全的合约升级，只允许所有者授权新的合约版本。\r\n\r\n```javascript\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.19;\r\n\r\nimport \"openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol\";\r\nimport \"openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol\";\r\nimport \"openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC20PermitUpgradeable.sol\";\r\nimport \"openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol\";\r\nimport \"openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol\";\r\n\r\n/// @custom:oz-upgrades-from MyToken\r\ncontract MyTokenV2 is Initializable, ERC20Upgradeable, OwnableUpgradeable, ERC20PermitUpgradeable, UUPSUpgradeable {\r\n    /// @custom:oz-upgrades-unsafe-allow constructor\r\n    constructor() {\r\n        _disableInitializers();\r\n    }\r\n\r\n    function initialize(address initialOwner) initializer public {\r\n        __ERC20_init(\"MyTokenV2\", \"MTKV2\");\r\n        __Ownable_init(initialOwner);\r\n        __ERC20Permit_init(\"MyTokenV2\");\r\n        __UUPSUpgradeable_init();\r\n\r\n        _mint(msg.sender, 1000000 * 10 ** decimals());\r\n    }\r\n\r\n    function mint(address to, uint256 amount) public onlyOwner {\r\n        _mint(to, amount);\r\n    }\r\n\r\n    function _authorizeUpgrade(address newImplementation)\r\n        internal\r\n        onlyOwner\r\n        override\r\n    {}\r\n}\r\n```\r\n\r\n总的来说，上面的 ERC-20 智能合约与*MyToken.sol*非常相似，但存在差异，例如不同的合约名称、符号，并包含升级我们的合约所需的 Foundry/OpenZepplin 注释（例如，`/// @custom:oz-upgrades-from MyToken`）。\r\n\r\n现在，让我们进行编译和测试。\r\n\r\n## 编译和测试可升级的 ERC-20 代币\r\n\r\n有了我们编写的智能合约，让我们尝试编译合约并测试可升级的 ERC-20 代币逻辑的行为。\r\n\r\n我们将测试以下功能：\r\n\r\n- 检查我们合约的`铸造`功能（请注意，这仅限于`Owner`）\r\n- 测试并验证我们的 ERC-20 代币的可升级性\r\n\r\n现在，转到**test**文件夹并打开**MyTokenTest.t.sol**文件。正如你可能已经猜到的那样，Foundry 中的测试是用 Solidity 编写的。更新文件的内容以包含以下内容：\r\n\r\n```javascript\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.19;\r\n\r\nimport \"forge-std/Test.sol\";\r\nimport \"../src/MyToken.sol\";\r\nimport \"forge-std/console.sol\";\r\nimport \"@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol\";\r\nimport \"@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol\";\r\nimport { Upgrades } from \"openzeppelin-foundry-upgrades/Upgrades.sol\";\r\n\r\ncontract MyTokenTest is Test {\r\n    MyToken myToken;\r\n    ERC1967Proxy proxy;\r\n    address owner;\r\n    address newOwner;\r\n\r\n    // Set up the test environment before running tests\r\n    function setUp() public {\r\n        // 部署实现\r\n        MyToken implementation = new MyToken();\r\n        // Define the owner address\r\n        owner = vm.addr(1);\r\n        // Deploy the proxy and initialize the contract through the proxy\r\n        proxy = new ERC1967Proxy(address(implementation), abi.encodeCall(implementation.initialize, owner));\r\n        // 用代理关联 MyToken 接口\r\n        myToken = MyToken(address(proxy));\r\n        // Define a new owner address for upgrade tests\r\n        newOwner = address(1);\r\n        // Emit the owner address for debugging purposes\r\n        emit log_address(owner);\r\n    }\r\n\r\n    // Test the basic ERC20 functionality of the MyToken contract\r\n    function testERC20Functionality() public {\r\n        // Impersonate the owner to call mint function\r\n        vm.prank(owner);\r\n        // Mint tokens to address(2) and assert the balance\r\n        myToken.mint(address(2), 1000);\r\n        assertEq(myToken.balanceOf(address(2)), 1000);\r\n    }\r\n\r\n    // 测试升级\r\n    function testUpgradeability() public {\r\n        // Upgrade the proxy to a new version; MyTokenV2\r\n        Upgrades.upgradeProxy(address(proxy), \"MyTokenV2.sol:MyTokenV2\", \"\", owner);\r\n    }\r\n}\r\n```\r\n\r\n测试代码相当长，但我们已添加了注释，以便你更好地理解每个测试用例的发生情况。\r\n\r\n现在，要编译我们的合约并执行测试，请在终端中运行以下命令：\r\n\r\n```sh\r\nforge build && forge test --ffi\r\n```\r\n\r\n> 包括`--ffi`标志是为了运行我们的代码需要访问的外部脚本。\r\n\r\n你可能会收到一些警告，例如“源文件未指定所需的编译器版本！”但这可以忽略。\r\n\r\n你将看到类似以下的输出：\r\n\r\n```sh\r\n[⠢] Compiling...\r\n[⠃] Compiling 62 files with 0.8.22\r\n[⠰] Solc 0.8.22 finished in 3.79s\r\nCompiler run successful with warnings:\r\nWarning (3420): Source file does not specify required compiler version! Consider adding \"pragma solidity ^0.8.22;\"\r\n--> script/deployProxy.s.sol\r\n\r\nWarning (3420): Source file does not specify required compiler version! Consider adding \"pragma solidity ^0.8.22;\"\r\n--> script/deployToken.s.sol\r\n\r\n[⠢] Compiling...\r\nNo files changed, compilation skipped\r\n\r\nRunning 2 tests for test/MyTokenTest.t.sol:MyTokenTest\r\n[PASS] testERC20Functionality() (gas: 48673)\r\n[PASS] testUpgradeability() (gas: 1642043)\r\nTest result: ok. 2 passed; 0 failed; 0 skipped; finished in 2.43s\r\n\r\nRunning 2 tests for test/Counter.t.sol:CounterTest\r\n[PASS] testFuzz_SetNumber(uint256) (runs: 256, μ: 27709, ~: 28409)\r\n[PASS] test_Increment() (gas: 28379)\r\nTest result: ok. 2 passed; 0 failed; 0 skipped; finished in 2.43s\r\n \r\nRan 2 test suites: 4 tests passed, 0 failed, 0 skipped (4 total tests)\r\n```\r\n\r\n> **注意**：如果你在测试中遇到错误，你可能需要运行命令 - `forge clean && forge build && forge test --ffi` 以删除构建工件和缓存目录，然后重新编译并运行测试。\r\n\r\n此外，你将注意到两个新目录；`out`目录包含合约工件，例如 ABI，而`cache`文件夹被 forge 用于重新编译必要的内容。\r\n\r\n唯一剩下的就是部署可升级的 ERC-20 代币。让我们开始吧！\r\n\r\n## 部署可升级的 ERC-20 代币\r\n\r\nFoundry 通过使用`forge create`命令使通过 CLI 轻松部署智能合约；但是，你也可以使用脚本进行部署。请注意，Foundry 一次只能部署一个合约，但这对我们目前来说并不构成障碍。\r\n\r\n要部署 ERC-20 代币，让我们使用一个脚本。打开我们之前创建的**scripts/deployToken.s.sol**文件，并更新文件以使用以下代码：\r\n\r\n```javascript\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.19;\r\n\r\nimport \"../src/MyToken.sol\";\r\nimport \"forge-std/Script.sol\";\r\n\r\ncontract DeployTokenImplementation is Script {\r\n    function run() public {\r\n        // Use address provided in config to broadcast transactions\r\n        vm.startBroadcast();\r\n        // Deploy the ERC-20 token\r\n        MyToken implementation = new MyToken();\r\n        // Stop broadcasting calls from our address\r\n        vm.stopBroadcast();\r\n        // Log the token address\r\n        console.log(\"Token Implementation Address:\", address(implementation));\r\n    }\r\n}\r\n```\r\n\r\n记得保存文件！剩下的就是执行脚本。\r\n\r\n```sh\r\nforge script script/DeployToken.s.sol --rpc-url sepolia --private-key $PRIVATE_KEY --broadcast \r\n```\r\n\r\n上述命令使用我们在`foundry.toml`文件中配置的`sepolia` RPC URL 执行脚本。`--private-key`标志设置我们用于交易的账户，`--broadcast`命令用于将它们广播到网络中。\r\n\r\n>  提示\r\n\r\n> 如果你想要验证你的合约在 Etherscan 上，以便让你和其他人能够从区块浏览器读取和写入你的智能合约，你将需要在上述命令的末尾添加`--etherscan-api-key YOUR_ETHERSCAN_API_KEY --verify`标志。在本指南的结尾，我们将演示如何从代理中读取，因此如果你想执行该步骤，这将是**必需的**。\r\n\r\n\r\n\r\n成功后，你将看到如下输出：\r\n\r\n```sh\r\n== Logs ==\r\nToken Implementation Address: 0x195136BA4F105dAe042F96a59E4dbeF9DCAdE773\r\n...\r\n...\r\n✅  [Success]Hash: 0xe097b9397cd7d36bcf8dc379c95b511746b8d6802e4794e4b0b1125e36bf75bf\r\nContract Address: 0x5FbDB2315678afecb367f032d93F642f64180aa3\r\nBlock: 1\r\nPaid: 0.00673602 ETH (1684005 gas * 4 gwei)\r\n```\r\n\r\n接下来，让我们部署代理合约。\r\n\r\n打开**scripts/deployProxy.s.sol**文件并输入以下代码：\r\n\r\n```javascript\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.19;\r\n\r\nimport \"../src/MyToken.sol\";\r\nimport \"@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol\";\r\nimport \"forge-std/Script.sol\";\r\n\r\ncontract DeployUUPSProxy is Script {\r\n    function run() public {\r\n\r\n        address _implementation = YOUR_DEPLOYED_SMART_CONTRACT_ADDRESS; // Replace with your token address\r\n        vm.startBroadcast();\r\n\r\n        // Encode the initializer function call\r\n        bytes memory data = abi.encodeWithSelector(\r\n            MyToken(_implementation).initialize.selector,\r\n            msg.sender // Initial owner/admin of the contract\r\n        );\r\n\r\n        // Deploy the proxy contract with the implementation address and initializer\r\n        ERC1967Proxy proxy = new ERC1967Proxy(_implementation, data);\r\n\r\n        vm.stopBroadcast();\r\n        // Log the proxy address\r\n        console.log(\"UUPS Proxy Address:\", address(proxy));\r\n    }\r\n}\r\n```\r\n\r\n**重要提示**：现在，在运行下一个命令之前，你需要使用你从上一步骤部署的智能合约地址（例如，代币地址）更新`_implementation`变量。记得保存文件。\r\n\r\n然后，要部署代理，请运行以下命令：\r\n\r\n```sh\r\nforge script script/deployProxy.s.sol:DeployUUPSProxy --rpc-url sepolia --private-key $PRIVATE_KEY --broadcast\r\n```\r\n\r\n请记住，可选地，你还可以在上述命令的末尾添加`--etherscan-api-key YOUR_ETHERSCAN_API_KEY --verify`标志以验证你的合约。如果你想要在接下来的步骤中与你的智能合约交互，这将是必需的。\r\n\r\n花点时间通过查看 Etherscan 来验证你的智能合约是否已部署。你可以通过 Etherscan 验证合约是否是代理，方法是导航到**Code**选项卡，单击**More Options**下拉菜单，然后选择**Is this a proxy?**。你将被提示验证地址（单击继续），然后导航回 Code 选项卡，你应该会看到两个新选项卡，**Read as Proxy**和**Write as Proxy**。如果需要，你可以通过 Etherscan 在这些选项卡上与你的 ERC-20 代币合约进行交互。\r\n\r\n例如，让我们使用**Read as Proxy**选项卡查看地址的 ERC-20 代币余额：\r\n\r\n![代理的 Etherscan 代码选项卡](https://img.learnblockchain.cn/attachments/migrate/1706434564118)\r\n\r\n在上面的图像中，我们正在检查所有者的余额，因为我们在部署时向其铸造了代币。\r\n\r\n你现在可以采取的下一步是与你的智能合约进行交互。由于我们在测试部分已经涵盖了交互，所以我们将暂时跳过这部分。\r\n\r\n## 使用 QuickNode 的 Token API 获取代币数据\r\n\r\n在结束本教程之前，让我们看看如何可以使用 [QuickNode 的 Token API](https://www.quicknode.com/token-api) 轻松获取 ERC-20 代币元数据和交易。\r\n\r\n要使用 Token API，你可以使用 [QuickNode SDK](https://www.quicknode.com/docs/quicknode-sdk/getting-started) 或在你喜欢的 web3 SDK（如 ethers.js 和 Eth.go）中实现它（在此处查看[文档](https://www.quicknode.com/docs/ethereum/qn_getTokenMetadataByContractAddress_v2) ）。\r\n\r\n以下是一个快速示例，演示如何使用 cURL 获取你的 ERC-20 代币的元数据：\r\n\r\n```sh\r\ncurl QUICKNODE_ENDPOINT_URL \\\r\n  -X POST \\\r\n  -H \"Content-Type: application/json\" \\\r\n  --data '{\r\n    \"id\":67,\r\n    \"jsonrpc\":\"2.0\",\r\n    \"method\":\"qn_getTokenMetadataByContractAddress\",\r\n    \"params\": [{\r\n      \"contract\": \"YOUR_TOKEN_ADDRESS\"\r\n    }]\r\n  }'\r\n```\r\n\r\n只需将代码粘贴到你的终端，并记得用你的实际 HTTP 提供程序 URL 和代币地址替换`QUICKNODE_ENDPOINT_URL`和`YOUR_TOKEN_ADDRESS`。\r\n\r\n通过在我们的 ERC-20 代币上调用**qn_getTokenMetadataByContractAddress** RPC 方法，我们可以返回诸如其元数据和交易信息（例如创建代币的创世块）之类的代币详细信息。\r\n\r\n示例响应：\r\n\r\n```json\r\n{\r\n    \"jsonrpc\": \"2.0\",\r\n    \"id\": 67,\r\n    \"result\": {\r\n        \"name\": \"MyToken\",\r\n        \"symbol\": \"MTK\",\r\n        \"contractAddress\": \"0xc731bc16e15e97687130f4c9a7232781ea060040\",\r\n        \"decimals\": \"18\",\r\n        \"genesisBlock\": \"4701990\",\r\n        \"genesisTransaction\": \"0xa8c93e0c5108f73a039e1537b02f94e871398b6b3fe3f4efafc97c8782965b8a\"\r\n    }\r\n}\r\n```\r\n\r\nQuickNode 还提供 [NFT API](https://www.quicknode.com/nft-api?utm_source=internal&utm_campaign=guides)，允许你检索聚合的 NFT 数据，例如集合详细信息、转移历史、元数据等。这两个 API 都受 [QuickNode 的 Graph API](https://www.quicknode.com/graph-api?utm_source=internal&utm_campaign=guides) 支持，该 API 允许你查询相同的数据，但以灵活的响应形式，并支持其他聚合数据，例如历史交易数据（例如 OHLC）和频繁更改的数据的实时订阅。\r\n\r\n\r\n\r\n## 结语 \r\n\r\n给自己一个鼓励吧！你已经完成了这篇关于创建和部署可升级 ERC-20 代币的技术指南。在这个过程中，你回顾了可升级智能合约的概念以及它们的不同类型，然后设置了一个智能合约环境来创建、测试和部署你的可升级 ERC-20 代币。\r\n\r\n---\r\n\r\n\r\n\r\n本翻译由 [DeCert.me](https://decert.me/) 协助支持， 在 DeCert 构建可信履历，为自己码一个未来。"},"author":{"user":"https://learnblockchain.cn/people/412","address":"0x9e64a306aB319811C5a1270F2CA9f6E1e4857c84"},"history":null,"timestamp":1706452753,"version":1}