{"content":{"title":"【教程】使用 OpenZeppelin  Deploy 部署和升级智能合约","body":"Defender 允许你轻松地跨链部署和升级智能合约，同时保持最佳的安全实践。本教程展示了如何使用一个 [Relayer](https://docs.openzeppelin.com/defender/manage#relayers) 来部署一个名为 Box 的合约，并通过一个 [Safe wallet](https://safe.global/)（多签钱包）使用 UUPS 代理模式进行升级。\r\n\r\n## 前提条件\r\n\r\n*   OpenZeppelin Defender 账户。你可以在[这里](https://defender.openzeppelin.com/v2/?utm_campaign=Defender_2.0_2023&utm_source=Docs#/auth/sign-up) 注册 Defender。\r\n    \r\n*   [NodeJS 和 NPM](https://nodejs.org/en)\r\n    \r\n*   任何 IDE 或文本编辑器\r\n    \r\n*   带有 Metamask（或任何其他兼容钱包）的网络浏览器，并且有 Sepolia ETH 资金。\r\n    \r\n\r\n## 1. 配置\r\n\r\n###  Safe wallet\r\n\r\n首先，你需要创建 Safe wallet 来管理升级过程。请按照以下步骤操作：\r\n\r\n1.  在网络浏览器中打开 [Safe app](https://app.safe.global/welcome) 并连接你的钱包（确保你已连接到 [Sepolia 测试网](https://sepolia.etherscan.io/) ）。\r\n    \r\n2.  点击 **Create new Account** 并按照步骤操作。\r\n    \r\n3.  记下你创建的 Safe wallet 地址，稍后你将需要它。\r\n    \r\n    ![Deploy safe copy address](https://img.learnblockchain.cn/attachments/migrate/1723643108959)\r\n    \r\n\r\n###  环境设置\r\n\r\n现在，你将创建一个带有 Sepolia 测试网的 Defender 测试环境，在这里你将部署和升级智能合约。请按照以下步骤操作：\r\n\r\n1.  打开 [Defender Deploy](https://defender.openzeppelin.com/v2/#/deploy)。\r\n    \r\n2.  点击 **Setup**。\r\n    \r\n    ![Deploy environments page](https://img.learnblockchain.cn/attachments/migrate/1723643109141)\r\n    \r\n3.  从下拉菜单中选择 **Sepolia**。\r\n    \r\n    ![Deploy networks wizard](https://img.learnblockchain.cn/attachments/migrate/1723643109691)\r\n    \r\n4.  选择与你的资金 relayer 相关联的审批流程，该 relayer 将为测试环境执行部署。如果你还没有审批流程，Defender 将允许你在向导流程中创建一个。Relayers 自动支付 gas 费用，并负责私钥安全存储、交易签名、nonce 管理、gas 价格估算和重新提交。不过，你也可以选择使用 EOA（“外部拥有账户”）或 Safe wallet 进行部署。\r\n    \r\n    阅读更多关于 relayers 及其管理的信息[这里](https://docs.openzeppelin.com/defender/manage#relayers) 。\r\n    \r\n    ![Deploy block deploy wizard](https://img.learnblockchain.cn/attachments/migrate/1723643109997)\r\n    \r\n5.  点击审批流程字段以展开下拉菜单，然后点击 **Create an Approval Process**。输入“Safe Wallet Approval Process”作为名称，展开合约字段并点击 **Add contract**。输入“Safe Wallet”作为名称，粘贴你之前复制的 Safe wallet 地址并点击 **Create**。在合约下拉菜单中选择“Safe Wallet”并点击 **Continue**。\r\n    \r\n    ![Deploy block upgrade wizard](https://img.learnblockchain.cn/attachments/migrate/1723643110384)\r\n    \r\n6.  Defender 将为此环境生成一个 API Key 和 Secret，请复制并安全存储它们。点击 **Let’s Deploy** 访问环境页面。\r\n    \r\n    ![Deploy block end wizard](https://img.learnblockchain.cn/attachments/migrate/1723643110594)\r\n    \r\n\r\n你已配置测试环境，以便在没有实际资金损失风险的情况下学习。设置生产环境的步骤是相同的。\r\n\r\nDefender 支持 Hardhat 和 Foundry 集成。选择适合你项目的一个！\r\n\r\n###  Foundry 设置\r\n\r\n首先，确保你已安装 [Foundry](https://book.getfoundry.sh/getting-started/installation)。按照以下步骤创建一个新目录和项目：\r\n\r\n1.  在终端中运行以下命令：\r\n    \r\n```\r\n        forge init deploy-tutorial && cd deploy-tutorial && forge install foundry-rs/forge-std && forge install OpenZeppelin/openzeppelin-foundry-upgrades && forge install OpenZeppelin/openzeppelin-contracts-upgradeable\r\n```\r\n    \r\n2.  现在，配置 `foundry.toml` 文件以启用 ffi、ast、build info 和 storage layout：\r\n```  \r\n        [profile.default]\r\n        ffi = true\r\n        ast = true\r\n        build_info = true\r\n        extra_output = [\"storageLayout\"]\r\n```    \r\n3.  在项目根目录中创建一个名为 `.env` 的新文件，并添加以下内容，其中包含你在创建 Defender 环境后收到的密钥：\r\n```    \r\n        DEFENDER_KEY = \"<<YOUR_KEY>>\"\r\n        DEFENDER_SECRET = \"<<YOUR_SECRET>>\"\r\n```\r\n\r\n###  Hardhat 设置\r\n\r\n首先，确保你已安装 [Hardhat](https://hardhat.org/hardhat-runner/docs/getting-started#installation) 和 ethers v6。按照以下步骤创建一个新目录和项目：\r\n\r\n1.  在终端中运行以下命令：\r\n    \r\n        mkdir deploy-tutorial && cd deploy-tutorial && npx hardhat init\r\n    \r\n2.  Hardhat 将询问一些问题以设置配置，请回答以下问题：\r\n    \r\n    *   你想做什么：创建一个 **Typescript** 项目\r\n        \r\n    *   Hardhat 项目根目录：_保持默认_\r\n        \r\n    *   你想使用 .gitignore 吗：是\r\n        \r\n    *   你想使用 npm 安装此示例项目的依赖项吗：是\r\n        \r\n    \r\n3.  Hardhat 现在将安装工具库，并为你创建项目文件。之后，使用以下命令安装 OpenZeppelin 包：\r\n\r\n```    \r\n        npm i @openzeppelin/hardhat-upgrades @openzeppelin/contracts-upgradeable dotenv --save-dev\r\n```\r\n\r\n 一旦所有内容都已安装，你的初始目录结构应如下所示：\r\n    \r\n    ![Deploy directory structure](https://img.learnblockchain.cn/attachments/migrate/1723643110999)\r\n    \r\n4.  现在，你需要编辑 Hardhat 配置以添加 Defender 密钥和 Sepolia 网络。打开 `hardhat.config.ts` 文件，并将其内容替换为以下代码：\r\n\r\n```\r\n        import { HardhatUserConfig } from \"hardhat/config\";\r\n        import \"@nomicfoundation/hardhat-toolbox\";\r\n        import \"@openzeppelin/hardhat-upgrades\";\r\n        \r\n        require(\"dotenv\").config();\r\n        \r\n        const config: HardhatUserConfig = {\r\n          solidity: \"0.8.20\",\r\n          defender: {\r\n            apiKey: process.env.DEFENDER_KEY as string,\r\n            apiSecret: process.env.DEFENDER_SECRET as string,\r\n          },\r\n          networks: {\r\n            sepolia: {\r\n              url: \"https://ethereum-sepolia.publicnode.com\",\r\n              chainId: 11155111\r\n            },\r\n          },\r\n        };\r\n        \r\n        export default config;\r\n```\r\n\r\n5.  在项目根目录中创建一个名为 `.env` 的新文件，并添加以下内容，其中包含你在创建 Defender 环境后收到的密钥：\r\n\r\n```    \r\n        DEFENDER_KEY = \"<<YOUR_KEY>>\"\r\n        DEFENDER_SECRET = \"<<YOUR_SECRET>>\"\r\n``` \r\n\r\n## 2. 部署\r\n\r\n1.  在 `contracts` 或 `src` 目录中创建一个名为 `Box.sol` 的新文件，并添加以下代码：\r\n\r\n```\r\n        // SPDX-License-Identifier: Unlicense\r\n        pragma solidity ^0.8.20;\r\n        \r\n        import {Initializable} from  \"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\";\r\n        import {UUPSUpgradeable} from \"@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol\";\r\n        import {OwnableUpgradeable} from \"@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol\";\r\n        \r\n        /// @title Box\r\n        /// @notice A box with objects inside.\r\n        contract Box is Initializable, UUPSUpgradeable, OwnableUpgradeable {\r\n            /*//////////////////////////////////////////////////////////////\r\n                                        VARIABLES\r\n            //////////////////////////////////////////////////////////////*/\r\n        \r\n            /// @notice Number of objects inside the box.\r\n            uint256 public numberOfObjects;\r\n        \r\n            /*//////////////////////////////////////////////////////////////\r\n                                        FUNCTIONS\r\n            //////////////////////////////////////////////////////////////*/\r\n        \r\n            /// @notice No constructor in upgradable contracts, so initialized with this function.\r\n            function initialize(uint256 objects, address multisig) public initializer {\r\n                __UUPSUpgradeable_init();\r\n                __Ownable_init(multisig);\r\n        \r\n                numberOfObjects = objects;\r\n            }\r\n        \r\n            /// @notice Remove an object from the box.\r\n            function removeObject() external {\r\n                require(numberOfObjects > 1, \"Nothing inside\");\r\n                numberOfObjects -= 1;\r\n            }\r\n        \r\n            /// @dev Upgrades the implementation of the proxy to new address.\r\n            function _authorizeUpgrade(address) internal override onlyOwner {}\r\n        }\r\n    \r\n    这是一个模拟盒子的合约，具有三个功能：\r\n    \r\n    *   `initialize()`: 使用其初始实现初始化可升级代理，并将 multisig 设置为所有者。\r\n        \r\n    *   `removeObject()`: 通过移除一个对象来减少盒子中的对象数量。\r\n        \r\n    *   `_authorizeUpgrade()`: 将代理指向新的实现地址。\r\n        \r\n```\r\n\r\n### Foundry\r\n\r\n1.  在 `script` 目录下创建一个名为 `Deploy.s.sol` 的文件。此脚本将通过 Defender 部署可升级的 Box 合约，初始包含 5 个对象，所有者为环境设置中配置的 multisig 地址。`initializer` 选项用于在合约部署后调用 `initialize()` 函数。将以下代码复制并粘贴到 `Deploy.s.sol` 中：\r\n\r\n```\r\n        // SPDX-License-Identifier: Unlicense\r\n        pragma solidity ^0.8.20;\r\n        \r\n        import {Script} from \"forge-std/Script.sol\";\r\n        import {console} from \"forge-std/console.sol\";\r\n        \r\n        import {Defender, ApprovalProcessResponse} from \"openzeppelin-foundry-upgrades/Defender.sol\";\r\n        import {Upgrades, Options} from \"openzeppelin-foundry-upgrades/Upgrades.sol\";\r\n        \r\n        import {Box} from \"src/Box.sol\";\r\n        \r\n        contract DefenderScript is Script {\r\n            function setUp() public {}\r\n        \r\n            function run() public {\r\n                ApprovalProcessResponse memory upgradeApprovalProcess = Defender.getUpgradeApprovalProcess();\r\n        \r\n                if (upgradeApprovalProcess.via == address(0)) {\r\n                    revert(\r\n                        string.concat(\r\n                            \"Upgrade approval process with id \",\r\n                            upgradeApprovalProcess.approvalProcessId,\r\n                            \" has no assigned address\"\r\n                        )\r\n                    );\r\n                }\r\n        \r\n                Options memory opts;\r\n                opts.defender.useDefenderDeploy = true;\r\n        \r\n                address proxy =\r\n                    Upgrades.deployUUPSProxy(\"Box.sol\", abi.encodeCall(Box.initialize, (5, upgradeApprovalProcess.via)), opts);\r\n        \r\n                console.log(\"Deployed proxy to address\", proxy);\r\n            }\r\n        }\r\n```\r\n\r\n2.  通过运行以下命令来部署，它将执行你的部署脚本：\r\n\r\n```\r\n        forge script script/Deploy.s.sol --force --rpc-url https://ethereum-sepolia.publicnode.com\r\n```    \r\n\r\n### Hardhat\r\n\r\n1.  打开 `scripts` 目录下的 `deploy.ts` 文件。此脚本将通过 Defender 部署可升级的 Box 合约，初始包含 5 个对象，所有者为环境设置中配置的 multisig 地址。`initializer` 选项用于在合约部署后调用 `initialize()` 函数。将以下代码复制并粘贴到 `deploy.ts` 中：\r\n\r\n```\r\n        import { ethers, defender } from \"hardhat\";\r\n        \r\n        async function main() {\r\n          const Box = await ethers.getContractFactory(\"Box\");\r\n        \r\n          const upgradeApprovalProcess = await defender.getUpgradeApprovalProcess();\r\n        \r\n          if (upgradeApprovalProcess.address === undefined) {\r\n            throw new Error(`Upgrade approval process with id ${upgradeApprovalProcess.approvalProcessId} has no assigned address`);\r\n          }\r\n        \r\n          const deployment = await defender.deployProxy(Box, [5, upgradeApprovalProcess.address], { initializer: \"initialize\" });\r\n        \r\n          await deployment.waitForDeployment();\r\n        \r\n          console.log(`Contract deployed to ${await deployment.getAddress()}`);\r\n        }\r\n        \r\n        // We recommend this pattern to be able to use async/await everywhere\r\n        // and properly handle errors.\r\n        main().catch((error) => {\r\n          console.error(error);\r\n          process.exitCode = 1;\r\n        });\r\n```\r\n\r\n    你应该使用 `deployProxy()`、`deployBeacon()` 和 `deployImplementation()` 来部署可升级合约，对于不可升级的合约使用 `deployContract()`。要强制使用 `deployContract()`，请将 `unsafeAllowDeployContract` 选项设置为 `true`。更多信息请参见[这里](https://github.com/OpenZeppelin/openzeppelin-upgrades/blob/master/docs/modules/ROOT/pages/defender-deploy.adoc) 。\r\n    \r\n2.  通过运行以下命令来部署你的 box，它将执行你的部署脚本：\r\n\r\n```    \r\n        npx hardhat run --network sepolia scripts/deploy.ts\r\n``` \r\n\r\n成功！你的合约应该已经部署在 Sepolia 测试网上。导航到 Defender 的 Deploy 页面，检查代理和实现是否已在测试环境中部署。所有 Box 交易都应发送到代理地址，因为它将存储状态并指向给定的实现。复制代理地址以便下一步升级。\r\n\r\n![Deployed contract](https://img.learnblockchain.cn/attachments/migrate/1723643111136)\r\n\r\n###  注意事项\r\n\r\n默认情况下，Defender 使用 `CREATE` 操作码来部署合约。此方法创建一个新的合约实例并为其分配一个唯一地址。此地址由交易的 nonce 和发送者的地址决定。\r\n\r\nDefender 还提供了使用 `CREATE2` 操作码的高级部署选项。当部署请求包含 `salt` 时，Defender 切换为使用 `CREATE2` 操作码。此操作码允许你根据发送者的 `address`、`salt` 和合约 `bytecode` 的组合来部署到一个确定的地址。\r\n\r\n虽然 `CREATE2` 提供了确定性的合约地址，但它改变了 `msg.sender` 的行为。在 `CREATE2` 部署中，构造函数或初始化代码中的 `msg.sender` 指的是工厂地址，而不是标准 `CREATE` 部署中的部署地址。这一区别可能会影响合约逻辑，因此在选择 `CREATE2` 时建议进行仔细测试和考虑。\r\n\r\n##  3. 升级\r\n\r\n升级智能合约允许在保持相同地址和存储的情况下更改其逻辑。\r\n\r\n1.  在 `contracts` 或 `src` 目录下创建一个名为 `BoxV2.sol` 的文件，并添加以下代码：\r\n```    \r\n        // SPDX-License-Identifier: Unlicense\r\n        pragma solidity ^0.8.20;\r\n        \r\n        import {Box} from \"./Box.sol\";\r\n        \r\n        /// @title BoxV2\r\n        /// @notice An improved box with objects inside.\r\n        /// @custom:oz-upgrades-from Box\r\n        contract BoxV2 is Box {\r\n            /*//////////////////////////////////////////////////////////////\r\n                                        FUNCTIONS\r\n            //////////////////////////////////////////////////////////////*/\r\n        \r\n            /// @notice Add an object to the box.\r\n            function addObject() external {\r\n                numberOfObjects += 1;\r\n            }\r\n        \r\n            /// @notice Returns the box version.\r\n            function boxVersion() external pure returns (uint256) {\r\n                return 2;\r\n            }\r\n        }\r\n```    \r\n    这是一个为你的 box 添加了两个新函数的合约：\r\n    \r\n    *   `addObject()`: 通过添加一个对象来增加 box 中的对象数量。\r\n        \r\n    *   `boxVersion()`: 返回 box 实现的版本。\r\n        \r\n    \r\n\r\n### Foundry\r\n\r\n1.  在 `script` 目录下创建一个名为 `Upgrade.s.sol` 的文件，并粘贴以下代码。确保将 `<PROXY ADDRESS>` 替换为你之前复制的代理地址。\r\n  \r\n```\r\n        // SPDX-License-Identifier: Unlicense\r\n        pragma solidity ^0.8.20;\r\n        \r\n        import {Script} from \"forge-std/Script.sol\";\r\n        import {console} from \"forge-std/console.sol\";\r\n        \r\n        import {ProposeUpgradeResponse, Defender, Options} from \"openzeppelin-foundry-upgrades/Defender.sol\";\r\n        \r\n        contract DefenderScript is Script {\r\n            function setUp() public {}        \r\n            function run() public {\r\n                Options memory opts;\r\n                ProposeUpgradeResponse memory response = Defender.proposeUpgrade(\r\n                    <PROXY ADDRESS>,\r\n                    \"BoxV2.sol\",\r\n                    opts\r\n                );\r\n                console.log(\"Proposal id\", response.proposalId);\r\n                console.log(\"Url\", response.url);\r\n            }\r\n        }\r\n```\r\n\r\n2.  使用升级脚本创建升级提案，使用以下命令：\r\n```\r\n        forge script script/Upgrade.s.sol --force --rpc-url https://ethereum-sepolia.publicnode.com\r\n```\r\n\r\n###  Hardhat\r\n\r\n1.  在 `scripts` 目录中创建一个名为 `upgrade.ts` 的文件，并粘贴以下代码。确保将 `<PROXY ADDRESS>` 替换为之前复制的代理地址。\r\n\r\n```\r\n        import { ethers, defender } from \"hardhat\";\r\n        \r\n        async function main() {\r\n          const BoxV2 = await ethers.getContractFactory(\"BoxV2\");\r\n        \r\n          const proposal = await defender.proposeUpgradeWithApproval('<PROXY ADDRESS>', BoxV2);\r\n        \r\n          console.log(`Upgrade proposed with URL: ${proposal.url}`);\r\n        }\r\n        \r\n        // 我们推荐这种模式，以便能够在任何地方使用 async/await\r\n        // 并正确处理错误。\r\n        main().catch((error) => {\r\n          console.error(error);\r\n          process.exitCode = 1;\r\n        });\r\n```   \r\n2.  使用升级脚本创建升级提案，使用以下命令：\r\n```    \r\n        npx hardhat run --network sepolia scripts/upgrade.ts\r\n```  \r\n\r\n###  Approve\r\n\r\n1.  导航到 [Defender 测试环境](https://defender.openzeppelin.com/v2/#/deploy/environment/test) 并点击升级提案，这将在屏幕右侧展开一个模态窗口。\r\n    \r\n2.  点击 **View Transaction Proposal** 并点击页面右上角的 **Approve and Execute**。使用你创建 Safe Wallet 时使用的钱包签名并执行交易。\r\n    \r\n\r\n你的 box 现在应该已经升级到新版本！测试环境页面中的升级提案现在应该标记为 **Executed**。\r\n\r\n![Uprade proposal executed](https://img.learnblockchain.cn/attachments/migrate/1723643111158)\r\n\r\n##  下一步\r\n\r\n恭喜！你现在可以使用相同的环境部署和升级其他合约。如果你对高级用例感兴趣，我们正在编写与部署相关的指南。\r\n\r\n在部署合约后，我们建议使用 Defender 监控其状态和交易。 在 [这里](https://learnblockchain.cn/article/9027)了解如何使用 Monitor。\r\n\r\n##  参考资料\r\n\r\n* [Deploy Documentation](https://docs.openzeppelin.com/defender/module/deploy)\r\n* [Foundry Upgrades Package](https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades)\r\n* [Hardhat Upgrades Package](https://www.npmjs.com/package/@openzeppelin/hardhat-upgrades)\r\n* [Upgrades Core Package](https://www.npmjs.com/package/@openzeppelin/upgrades-core)"},"author":{"user":"https://learnblockchain.cn/people/22207","address":null},"history":"bafkreibmbpxwqbuliwyrm65vcdv4on3ublw3bsrr6xezcckpju5xhkcsqa","timestamp":1723643744,"version":1}