{"content":{"title":"Foundry教程|如何调试和部署Solidity智能合约","body":"> * 原文：https://jamesbachini.com/foundry-tutorial/  作者：[james](https://jamesbachini.com/author/admin/) \r\n> * 译文出自：[登链翻译计划](https://github.com/lbc-team/Pioneer)\r\n> * 译者：[翻译小组](https://learnblockchain.cn/people/412)\r\n> * 校对：[Tiny 熊](https://learnblockchain.cn/people/15)\r\n> * 本文永久链接：[learnblockchain.cn/article…](https://learnblockchain.cn/article/4524)\r\n\r\n\r\n\r\n\r\n\r\n\r\n[Foundry](https://learnblockchain.cn/docs/foundry/i18n/zh/)是一个Solidity框架，用于构建、测试、模糊、调试和部署Solidity智能合约。在这个Foundry教程中，我们将介绍以下内容。\r\n\r\n1. Foundry简介 [视频]\r\n2. Foundry入门\r\n3. 使用Foundry的Hardhat\r\n4. 使用 Solidity 测试\r\n5. Foundry作弊代码\r\n6. 部署和使用合约\r\n\r\n## Foundry简介\r\n\r\n我制作了一个[视频]，你可以在YouTube上观看：https://youtu.be/VhaP9kYvlOA \r\n\r\n智能合约通常是用Solidity编写的，但使用Javascript框架（如Truffle或Hardhat）进行测试和部署。Foundry提供了一套在Rust中构建的工具，允许区块链开发者在Solidity中编写测试，并通过命令行部署和与合约交互。\r\n\r\n### 为什么用Foundry？\r\n\r\n- 在solidity中编写单元测试，而不是Javascript\r\n- 更快的编译和测试\r\n- 内置的模糊测试\r\n- Gas优化工具\r\n- 支持主网分叉\r\n- Etherscan 代码验证\r\n- 硬件钱包兼容\r\n- Solidity脚本\r\n- 通过作弊代码操纵区块链状态\r\n\r\n\r\n\r\n## 开始使用Foundry\r\n\r\n### 安装说明\r\n\r\n为了开始使用，我们需要安装foundry包，它需要rust。以下是linux、mac、windows和docker的命令。\r\n\r\n**Linux/Mac**:\r\n\r\n```\r\ncurl -L https://foundry.paradigm.xyz | bash;\r\nfoundryup\r\n```\r\n\r\n**Windows**: (需要 Rust，从 https://rustup.rs/ 安装)\r\n\r\n```\r\ncargo install --git https://github.com/foundry-rs/foundry --bins --locked\r\n```\r\n\r\n**Docker**:\r\n\r\n```\r\ndocker pull ghcr.io/foundry-rs/foundry:latest\r\n```\r\n\r\n### 使用foundry的第一步\r\n\r\nFoundry软件包带有两个主要的命令行功能：\r\n\r\n- **forge** - 建立编译测试本地智能合约\r\n- **cast** - 使用已部署的智能合约执行链上交易\r\n\r\n如果想从Github上克隆一个repo，我们可以使用forge命令。\r\n\r\n```\r\nforge install jamesbachini/myVault -hh\r\n```\r\n\r\n这里我们使用的是Github用户名和版本库名称，加上-hh 参数，用于迁移Hardhat版本库。\r\n\r\n我们也可以用 \"myrepo\" 初始化一个新的版本库。\r\n\r\n```\r\nforge init myrepo\r\n```\r\n\r\n然后就可以继续编译和测试智能合约了\r\n\r\n```\r\nforge build\r\nforge test\r\n```\r\n\r\n![forge tutorial solidity](https://img.learnblockchain.cn/pics/20220811101641.jpg)\r\n\r\n注意测试是如何通过的，还得到了测试交易的Gas成本\r\n\r\n------\r\n\r\n## 如何使用 Hardhat 设置 Foundry\r\n\r\n假设我们已经按照上面的说明安装了 Foundry，我们就可以与 [Hardhat](https://hardhat.org/) 一起使用它。\r\n\r\n注意，我们需要为当前工作目录设置一个 repo，所以如果还没有的话，请使用 git init。\r\n\r\n然后复制并粘贴以下内容到版本库根目录下的一个新的*foundry.toml*文件中：\r\n\r\n```toml\r\n[default]\r\nsrc = 'contracts'\r\ntest = 'test'\r\nout = 'artifacts/contracts'\r\nlibs = ['lib']\r\n```\r\n\r\n这将设置目录结构以便与 Hardhat 保持一致。Foundry 测试可以使用正常的 MyContract.t.sol 命名放在标准测试文件夹中。\r\n\r\n>  从这里我们可以在Hardhat 中使用Foundry进行测试和部署。就我个人而言，我喜欢 Hardhat 的脚本环境（特别是对于复杂的部署），但也认识到使用 Foundry 进行测试和模糊处理的好处。在同一个代码库中使用这两个应用程序，可以提供两个最佳选择。\r\n\r\n\r\n\r\n## 使用 Solidity 测试\r\n\r\n在我们开始编写单元测试之前，需要安装标准库\r\n\r\n```\r\nforge install foundry-rs/forge-std\r\n```\r\n\r\n然后我们可以将其导入测试文件中，该文件的名称将与我们的合约相同，后缀是.t.sol。\r\n\r\n```solidity\r\npragma solidity ^0.8.13;\r\n\r\nimport \"forge-std/Test.sol\";\r\n\r\ncontract MyContractTest is Test {\r\n\r\n  testWhatever(uint256 var1) public {\r\n    uint256 var2 = 1;\r\n    assertEq(var1,var2);\r\n  }\r\n\r\n}\r\n```\r\n\r\n请注意，在上面的例子中，我们给函数名加上了一个前缀test -> testWhatever()，此时出现任何revert都将失败。我们也可以把testFail -> testFailWhatever()作为前缀，这样就需要revert才能通过。\r\n\r\n另外请注意，在上面的例子中，我们在var1中传入了一个uint256变量。这个值将被**fuzzed（模糊）**，这意味着它将用各种不常用的值在函数上循环，试图创造一个边缘情况，导致回退（revert）。在这种情况下，任何不是1的值都会由于assertEq()函数而导致测试失败。\r\n\r\n也可以创建自定义断言，例如：\r\n\r\n```solidity\r\nfunction myAssertion(uint a, uint b) {\r\n  if (a != b) {\r\n    emit log_string(\"a != b\");\r\n    fail();\r\n  }\r\n}\r\n```\r\n\r\n然后，我们可以在整个合约中使用这个断言，或者建立一个自定义断言库，并类似于我们先前导入标准库的方式来导入它们。\r\n\r\n\r\n\r\n如果代码库包含许多不同的智能合约，可以使用` --match-contract`将单个合约和它的依赖关系分离出来，甚至可以使用`--match-test`命令行选项进行特定测试。\r\n\r\n```\r\nforge test --match-test optionalSpecificTest --match-contract optionalSpecificContract\r\n```\r\n\r\n另外，我们可以使用forge run来执行一个单一的solidity *\"脚本 \"*。\r\n\r\n```\r\nforge run src/Contract.sol \t\t\t\t//  执行单个脚本\r\nforge run src/Contract.sol --debug // open script in debugger\r\nforge run src/Contract.sol --sig \"foo(string)\" \"hi\" //  执行单一函数\r\n```\r\n\r\n一旦我们发现了一个bug，我们可以使用-v命令来提高提示程度并获得更多细节。\r\n\r\n```\r\ndebug with logs -vv\r\ndebug with traces for failing tests -vvv\r\ndebug with traces for all tests -vvvv\r\n```\r\n\r\n追踪（Traces）是一种非常强大的方式，可以仔细观察智能合约执行过程中发生的事情。这有点像在命令行上有[Tenderly.co](https://tenderly.co/)。\r\n\r\n还有一个交互式调试器（debugger），这让我想起了1990年的Linux调试器。我没有经常使用，但这里有更多的说明： https://book.getfoundry.sh/forge/debugger.html\r\n\r\n```\r\nforge test --debug \"testSomething\"\r\n```\r\n\r\n可以在本地分叉（fork）一个区块链，然后关联外部智能合约一起测试我们的合约。如果你想与其他defi协议交互，例如Uniswap，或者使用真实的市场数据执行压力测试，这很有用。\r\n\r\n```bash\r\nforge test --fork-url https://eth-mainnet.alchemyapi.io/v2/abc123alchmeyApiKey\r\n```\r\n\r\n### Gas优化\r\n\r\n编译时的合约Gas报告可以通过foundry.toml配置来设置\r\n\r\n```\r\ngas_reports = [\"MyContract\", \"MyContractFactory\"]\r\n```\r\n\r\n然后用*forge test -gas-report*选项执行命令。\r\n\r\n优化函数的一个方法是使用测试合约，并在修改前后进行快照对比：\r\n\r\n```\r\nforge snapshot --snap gas1.txt\r\n// make some changes\r\nforge snapshot --diff gas1.txt\r\n```\r\n\r\n这将提供之前的Gas报告和当前快照之间的差异。\r\n\r\n### 用Slither进行安全分析\r\n\r\n当涉及到智能合约安全时，Slither绝不是一个简单的解决方案，但它是有用的，并提供了一些自动检查，如检查重入错误。为了使用slither，我一般会切换到WSL（linux的Windows子系统），它可以用以下命令安装（注意0.8.13是目前foundry演示合约中使用的solc版本。你可以把它修改为Solidity文件中设置的任何版本）。\r\n\r\n```\r\napt install python3-pip\r\npip3 install slither-analyzer\r\npip3 install solc-select\r\nsolc-select install 0.8.13\r\nsolc-select use 0.8.13\r\n```\r\n\r\n然后把以下复制到我们工作的主目录下一个叫slither.config.json的文件里\r\n\r\n```\r\n{\r\n  \"filter_paths\": \"lib\",\r\n  \"solc_remaps\": [\r\n    \"ds-test/=lib/ds-test/src/\",\r\n    \"forge-std/=lib/forge-std/src/\"\r\n  ]\r\n}\r\n```\r\n\r\n然后运行一个测试，使用\r\n\r\n```\r\nslither src/Contract.sol\r\n```\r\n\r\n在官方readme中有更多关于slither的信息：https://github.com/crytic/slither\r\n\r\n## Foundry作弊代码\r\n\r\nFoundry有一套作弊代码，它可以对区块链的状态进行修改，以方便在测试时使用。这些代码可以直接执行合约：0x7109709ECfa91a80626fF3989D68f67F5b1DD12D 进行调用，但更多时候是通过标准库和vm对象执行。\r\n\r\n**重要的作弊代码有：**\r\n\r\n- **vm.warp(uint256) external;** 设置 block.timestamp\r\n- **vm.roll(uint256) external;** 设置 block.height.\r\n- **vm.prank(address) external;** 设置地址作为下一次调用的msg.sender\r\n- **vm.startPrank(address) external;** 设置地址作为所有后续调用的msg.sender\r\n- **vm.stopPrank() external;** 重置后续调用msg.sender为`address(this)`。\r\n- **vm.deal(address, uint256) external;** 设置一个地址的余额，参数：（who，newBalance）。\r\n- **vm.expectRevert(bytes calldata) external;** 期待下次调用时出现错误。\r\n- **vm.record() external;** 记录所有存储的读和写。\r\n- **vm.expectEmit(true, false, false, false); emit Transfer(address(this)); transfer();** 检查事件主题1在两个事件中是否相等\r\n- **vm.load(address,bytes32)外部返回(bytes32);** 从一个地址加载一个存储槽\r\n- **vm.store(address,bytes32,bytes32) external;** 将一个值存储到一个地址的存储槽中，参数（who, slot, value）。\r\n\r\n这些可以用来改变测试的过程，如在这个例子中，告诉测试套件在调用时期望一个标准的算术错误。\r\n\r\n```\r\nvm.expectRevert(stdError.arithmeticError);\r\n```\r\n\r\n完整的列表在这里：https://github.com/foundry-rs/forge-std/blob/master/src/Vm.sol\r\n\r\n## 部署和使用合约\r\n\r\nFoundry也可以用来部署并与智能合约交互。\r\n\r\n要部署一个合约，我们可以使用下面的命令：\r\n\r\n```\r\nforge create --rpc-url https://mainnet.infura.io --private-key abc123456789 src/MyContract.sol:MyContract --constructor-args \"Hello Foundry\" \"Arg2\"\r\n```\r\n\r\n注意我们在生产中部署时不应该使用硬编码的私钥。一个选择是使用-ledger或-trezor来通过硬件钱包执行。另外，也可以使用环境变量来存储私钥。\r\n\r\n另一种环境变量来存储私钥：\r\n**Linux/Mac**:\r\n--priva*teKey $privateKey*.\r\n\r\n```\r\nexport privateKey=abc123\r\n```\r\n\r\n**Windows Powershell**:\r\n\r\n--privateKey *$env:privateKey*\r\n\r\n```\r\n$env:privateKey = \"0x123abc\"\r\n```\r\n\r\n私钥不应该包含0x前缀，否则你会得到一个错误 \"Invalid character ‘x’ at position 1（无效字符'x'在位置1）\"\r\n\r\n我们还可以使用forge命令**在etherscan上验证合约**，以便我们能够使用Etherscan的UI和Metamask与之交互。\r\n\r\n```\r\nforge verify-contract --chain-id 1 --num-of-optimizations 200 --constructor-args (cast abi-encode \"constructor(string)\" \"Hello Foundry\" --compiler-version v0.8.10+commit.fc410830 0xContractAddressHere src/MyContract.sol:MyContract ABCetherscanApiKey123\r\n```\r\n\r\n也可以使用forge来**扁平化合约**，其中包括外部合约的依赖关系合并到一个文件中：\r\n\r\n```\r\nforge flatten --output src/MyContract.flattened.sol src/MyContract.sol\r\n```\r\n\r\n要生成ABI，可以使用以下命令：\r\n\r\n```\r\nforge inspect src/MyContract.sol abi\r\n```\r\n\r\n\r\n\r\n注意任何ABI都可以转换为接口并直接在solidity中使用： https://gnidan.github.io/abi-to-sol/\r\n\r\n如果合约已经被验证，我们也可以使用以下命令来生成一个接口。\r\n\r\n```\r\ncast interface 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984\r\n```\r\n\r\n### 用 Cast 交互\r\n\r\n我们可以call 方式调用合约请求链上数据。我们也可以提供凭证（私钥）来发送一个交易，就像我们在metamask中签署一个交易一样。\r\n\r\n```\r\ncast call 0xabc123 \"totalSupply()(uint256)\" --rpc-url https://eth-mainnet.alchemyapi.io\r\n\r\ncast send 0xabc123 \"mint(uint256)\" 3 --rpc-url https://eth-mainnet.alchemyapi.io --private-key=abc123\r\n```\r\n\r\n一旦区块被确认，也可以获取交易本身的信息：\r\n\r\n```\r\ncast tx 0xa1588a7c58a0ac632a9c7389b205f3999b7caee67ecb918d07b80f859aa605fd\r\n```\r\n\r\n也可以通过flashbots保护执行交易，cast使用`--flashbots` 参数发送交易。\r\n\r\n最后，你可能想估算一个Gas成本，这也可以用cast来完成：\r\n\r\n```\r\ncast estimate 0xabc123 \"mint(uint256)\" 3 --rpc-url https://eth-mainnet.alchemyapi.io --private-key=abc123\r\n```\r\n\r\nFoundry为测试和审计智能合约提供了一个快速、高效的框架。我希望这个教程是有用的，如果想进一步深入了解，别忘了查看官方文档。[https://book.getfoundry.sh](https://book.getfoundry.sh/)\r\n\r\nFoundry中文文档： https://learnblockchain.cn/docs/foundry/i18n/zh/ \r\n\r\n\r\n\r\n------\r\n\r\n本翻译由 [Decert.me](https://github.com/decert-me) 赞助支持。"},"author":{"user":"https://learnblockchain.cn/people/412","address":null},"history":null,"timestamp":1673404799,"version":1}