{"content":{"title":"Foundry 开发框架 - Solidity开发教程连载","body":"Foundry 是一个Solidity框架，用于构建、测试、模糊、调试和部署Solidity智能合约， Foundry 的优势是以Solidity 作为第一公民，完全使用 Solidity 进行开发与测试，如果你不太熟悉 JavaScript ， 使用 Foundry 是一个非常好的选择，而且Foundry 构建、测试的执行速度非常快。\r\n\r\nFoundry 的测试功能非常强大，通过 [作弊码](https://learnblockchain.cn/docs/foundry/i18n/zh/forge/cheatcodes.html#作弊码cheatcodes) 来操纵区块链的状态， 可以方便我们模拟各种情况， 还支持基于属性的模糊测试。\r\n\r\nFoundry 有非常详细的文档，并且登链社区进行的详尽的翻译，见[Foundry 中文文档](https://learnblockchain.cn/docs/foundry/i18n/zh/)，对中文用户非常友好，\r\n\r\n在本文中，我们将介绍：\r\n\r\n1. Foundry 安装\r\n2. 初始化Foundry项目\r\n3. 编写、编译智能合约\r\n4. 编写自动化测试\r\n5. 使用 Foundry 部署合约\r\n6. 补充1： Anvil 使用\r\n7. 补充2：Cast 与合约交互使用\r\n8. 补充3： 第 3 方库的安装\r\n9. 补充4： 标准库\r\n\r\n本文对应的代码在：https://github.com/xilibi2003/training_camp_2/tree/main/w1_foundry\r\n\r\n## Foundry 安装\r\n\r\n终端并输入以下命令：\r\n\r\n```text\r\ncurl -L https://foundry.paradigm.xyz | bash\r\n```\r\n\r\n\r\n\r\n这会下载`foundryup`。 然后通过运行它安装 Foundry：\r\n\r\n```text\r\nfoundryup\r\n```\r\n\r\n\r\n\r\n安装安装后，有三个命令行工具 `forge`, `cast`, `anvil` 组成\r\n\r\n- **forge**: 用来执行初始化项目、管理依赖、测试、构建、部署智能合约 ;\r\n- **cast**: 执行以太坊 RPC 调用的命令行工具, 进行智能合约调用、发送交易或检索任何类型的链数据\r\n- **anvil**: 创建一个本地测试网节点, 也可以用来分叉其他与 EVM 兼容的网络。\r\n\r\n## 初始化Foundry项目\r\n\r\n通过 `forge` 的 `forge init` 初始化项目：\r\n\r\n```text\r\n> forge init hello_decert\r\nInstalling forge-std in \"/Users/emmett/course/hello_decert/lib/forge-std\" (url: Some(\"https://github.com/foundry-rs/forge-std\"), tag: None)\r\n    Installed forge-std v1.5.1\r\n    Initialized forge project.\r\n```\r\n\r\n\r\n\r\ninit 命令会创建一个项目目录，并安装好`forge-std` 库。\r\n\r\n如需手动安装依赖库使用： `forge install forge/forge-std`\r\n\r\n创建好的 Foundry 工程结构为：\r\n\r\n```text\r\n> tree -L 2\r\n.\r\n├── foundry.toml\r\n├── lib\r\n│   └── forge-std\r\n├── script\r\n│   └── Counter.s.sol\r\n├── src\r\n│   └── Counter.sol\r\n└── test\r\n    └── Counter.t.sol\r\n\r\n5 directories, 4 files\r\n```\r\n\r\n\r\n\r\n- `src`：智能合约目录\r\n- `script` ：部署脚本文件\r\n- `lib`: 依赖库目录\r\n- `test`：智能合约测试用例文件夹\r\n- `foundry.toml`：配置文件，配置连接的网络URL 及编译选项。\r\n\r\nFoundry 使用 Git submodule 来管理依赖库， `.gitmodules` 文件记录了目录与子库的关系:\r\n\r\n```text\r\n[submodule \"lib/forge-std\"]\r\n    path = lib/forge-std\r\n    url = https://github.com/foundry-rs/forge-std\r\n    branch = v1.5.0\r\n```\r\n\r\n\r\n\r\n<details class=\"details_lb9f isBrowser_bmU9 alert alert--info details_b_Ee\" data-collapsed=\"true\" style=\"box-sizing: border-box; color: var(--ifm-alert-foreground-color); --ifm-alert-background-color:var(--ifm-color-info-contrast-background); --ifm-alert-background-color-highlight:#54c7ec26; --ifm-alert-foreground-color:var(--ifm-color-info-contrast-foreground); --ifm-alert-border-color:var(--ifm-color-info-dark); --ifm-code-background:var(--ifm-alert-background-color-highlight); --ifm-link-color:var(--ifm-alert-foreground-color); --ifm-link-hover-color:var(--ifm-alert-foreground-color); --ifm-link-decoration:underline; --ifm-tabs-color:var(--ifm-alert-foreground-color); --ifm-tabs-color-active:var(--ifm-alert-foreground-color); --ifm-tabs-color-active-border:var(--ifm-alert-border-color); background-color: var(--ifm-alert-background-color); border: 1px solid var(--ifm-alert-border-color); border-radius: var(--ifm-alert-border-radius); box-shadow: var(--ifm-alert-shadow); padding: var(--ifm-alert-padding-vertical) var(--ifm-alert-padding-horizontal); --docusaurus-details-summary-arrow-size:0.38rem; --docusaurus-details-transition:transform var(--ifm-transition-fast) ease; --docusaurus-details-decoration-color:var(--ifm-alert-border-color); margin: 0 0 var(--ifm-spacing-vertical); font-family: system-ui, -apple-system, &quot;Segoe UI&quot;, Roboto, Ubuntu, Cantarell, &quot;Noto Sans&quot;, sans-serif, &quot;system-ui&quot;, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;\"><summary style=\"box-sizing: border-box; list-style: none; cursor: pointer; padding-left: 1rem; position: relative;\">了解 Git submodule</summary></details>\r\n\r\n## 合约开发及编译[](https://decert.me/tutorial/solidity/tools/foundry/#合约开发及编译)\r\n\r\n合约开发推荐使用 VSCode 编辑器 + [solidity 插件](https://marketplace.visualstudio.com/items?itemName=NomicFoundation.hardhat-solidity)，在`contracts` 下新建一个合约文件 `Counter.sol` (`*.sol` 是 Solidity 合约文件的后缀名), 复制如下代码：\r\n\r\n```text\r\n// SPDX-License-Identifier: UNLICENSED\r\npragma solidity ^0.8.13;\r\n\r\ncontract Counter {\r\n    uint256 public counter;\r\n\r\n    function setNumber(uint256 newNumber) public {\r\n        counter = newNumber;\r\n    }\r\n\r\n    function increment() public {\r\n        counter++;\r\n    }\r\n\r\n    function count() public {\r\n        counter = counter + 1;\r\n    }\r\n}\r\n```\r\n\r\n\r\n\r\n在`foundry.toml` 中使用`solc`配置编译器版本：\r\n\r\n```text\r\n[profile.default]\r\nsrc = 'src'\r\nout = 'out'\r\nlibs = ['lib']\r\n\r\nsolc = \"0.8.18\" \r\n```\r\n\r\n\r\n\r\n更多的配置项请参考 [`foundry.toml` 配置](https://learnblockchain.cn/docs/foundry/i18n/zh/reference/config/overview.html)\r\n\r\n之后就使用`forge build`编译合约了：\r\n\r\n```text\r\n> forge build\r\n[⠒] Compiling...\r\n[⠔] Compiling 1 files with 0.8.18\r\n[⠒] Solc 0.8.18 finished in 362.64ms\r\nCompiler run successful\r\n```\r\n\r\n\r\n\r\n## 编写自动化测试\r\n\r\n测试是用 Solidity 编写的。 如果测试功能 revert，则测试失败，否则通过。\r\n\r\n### 测试 Case 编写\r\n\r\n在测试目录下`test` 添加自己的测试用例，添加文件 `Counter.t.sol` ，foundry 测试用例使用 `.t.sol` 后缀，约定具有以`test`开头的函数的合约都被认为是一个测试， 以下是测试代码：\r\n\r\n```text\r\npragma solidity ^0.8.13;\r\n\r\nimport \"forge-std/Test.sol\";\r\nimport \"../src/Counter.sol\";\r\n\r\ncontract CounterTest is Test {\r\n    Counter public counter;\r\n\r\n    function setUp() public {\r\n        counter = new Counter();\r\n        counter.setNumber(0);\r\n    }\r\n\r\n    function testIncrement() public {\r\n        counter.increment();\r\n        assertEq(counter.counter(), 1);\r\n    }\r\n\r\n    function testSetNumber(uint256 x) public {\r\n        counter.setNumber(x);\r\n        assertEq(counter.counter(), x);\r\n    }\r\n}\r\n```\r\n\r\n\r\n\r\n我们来分析一下测试代码：\r\n\r\n```text\r\nimport \"forge-std/Test.sol\";\r\n```\r\n\r\n\r\n\r\n引入 [Forge 标准库](https://github.com/foundry-rs/forge-std) 的 `Test` 合约，并让测试合约继承 `Test` 合约， 这是使用 Forge 编写测试的首选方式。\r\n\r\n第 9 行 `setUp()` 函数用来进行一些初始化，它是每个测试用例运行之前调用的可选函数\r\n\r\n第 14、19 行是以 `test` 为前缀的函数的两个测试用例，测试用例中使用 `assertEq` 断言判断相等。\r\n\r\n`testSetNumber` 带有一个参数 `x`， 它使用了基于属性的模糊测试， [forge 模糊器](https://learnblockchain.cn/docs/foundry/i18n/zh/forge/fuzz-testing.html)默认会随机指定256 个值运行测试。\r\n\r\n### 运行测试\r\n\r\nForge 使用 [`forge test`](https://learnblockchain.cn/docs/foundry/i18n/zh/reference/forge/forge-test.html) 命令运行测试用例（请先启动`anvil`）：\r\n\r\n```text\r\n> forge test\r\n[⠒] Compiling...\r\nNo files changed, compilation skipped\r\n\r\nRunning 2 tests for test/Counter.t.sol:CounterTest\r\n[PASS] testIncrement() (gas: 28390)\r\n[PASS] testSetNumber(uint256) (runs: 256, μ: 28064, ~: 28453)\r\nTest result: ok. 2 passed; 0 failed; finished in 9.33ms\r\n```\r\n\r\n\r\n\r\n结果中的两个 `PASS` 表示测试通过了，并且列出了测试所消耗的 gas，\r\n\r\n在 `testSetNumber(uint256)` 模糊测试中的`(runs: 256, μ: 28064, ~: 28453)`，含义是：\r\n\r\n- \"runs\" 是指模糊器 fuzzer 测试的场景数量。 默认情况下，模糊器 fuzzer 将生成 256 个场景，但是，其可以使用 [`FOUNDRY_FUZZ_RUNS`](https://learnblockchain.cn/docs/foundry/i18n/zh/reference/config/testing.html#runs) 环境变量进行配置。\r\n- “μ”（希腊字母 mu）是所有模糊运行中使用的平均 Gas\r\n- “~”（波浪号）是所有模糊运行中使用的中值 Gas\r\n\r\n我们还可以在测试用例用 `console2.sol` 打印值的结果，修改一下 `testIncrement` 加入 console2.log， 修改后的代码为：\r\n\r\n```text\r\n    function testIncrement() public {\r\n        counter.increment();\r\n        uint x = counter.counter();\r\n        console2.log(\"x= %d\", x);\r\n        assertEq(x, 1);\r\n    }\r\n```\r\n\r\n\r\n\r\n> `console2.sol` 包含 `console.sol` 的补丁，允许Forge 解码对控制台的调用追踪\r\n\r\n`forge test` 的默认行为是只显示通过和失败测试的摘要。 可以使用`-vv`标志通过增加日志详细程度：\r\n\r\n```text\r\n> forge test -vv\r\n[⠒] Compiling...\r\nNo files changed, compilation skipped\r\n\r\nRunning 2 tests for test/Counter.t.sol:CounterTest\r\n[PASS] testIncrement() (gas: 31626)\r\nLogs:\r\n  x= 1\r\n\r\n[PASS] testSetNumber(uint256) (runs: 256, μ: 27597, ~: 28453)\r\nTest result: ok. 2 passed; 0 failed; finished in 9.94ms\r\n```\r\n\r\n\r\n\r\n可以看到 Logs 下显示了测试用例中的打印的日志。\r\n\r\n更多 Forge 测试使用参考[文档 - 测试](https://learnblockchain.cn/docs/foundry/i18n/zh/forge/tests.html)， [文档 - 高级测试](https://learnblockchain.cn/docs/foundry/i18n/zh/forge/advanced-testing.html)\r\n\r\n## 部署合约\r\n\r\n部署合约到区块链，需要先准备有币的账号及区块链节点的 RPC URL。\r\n\r\nForge 提供 create 命令部署合约， 如：\r\n\r\n```text\r\nforge create  src/Counter.sol:Counter  --rpc-url <RPC_URL>  --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80\r\n```\r\n\r\n\r\n\r\ncreate 命令需要输入的参数较多，使用部署脚本是更推荐的做法是使用 [solidity-scripting](https://learnblockchain.cn/docs/foundry/i18n/zh/tutorials/solidity-scripting.html) 部署。\r\n\r\n为此我们需要稍微配置 Foundry 。\r\n\r\n通常我们会创建一个 `.env` 保存私密信息（如：私钥），`.env` 文件应遵循以下格式：\r\n\r\n```text\r\nGOERLI_RPC_URL=\r\nMNEMONIC=\r\n```\r\n\r\n\r\n\r\n`.env` 中记录自己的助记词及RPC URL。\r\n\r\n编辑 `foundry.toml` 文件：\r\n\r\n```toml\r\n[rpc_endpoints]\r\ngoerli = \"${GOERLI_RPC_URL}\"\r\nlocal = \"http://127.0.0.1:8545\"\r\n```\r\n\r\n\r\n\r\n然后在 script 目录下创建一个脚本，`Counter.s.sol`：\r\n\r\n```text\r\npragma solidity ^0.8.13;\r\n\r\nimport \"forge-std/Script.sol\";\r\nimport \"../src/Counter.sol\";\r\n\r\ncontract CounterScript is Script {\r\n        \r\n    function run() external {\r\n        string memory mnemonic = vm.envString(\"MNEMONIC\");\r\n                (address deployer, ) = deriveRememberKey(mnemonic, 0);\r\n                \r\n        vm.startBroadcast(deployer);\r\n        Counter c = new Counter();\r\n        console2.log(\"Counter deployed on %s\", address(c));\r\n        vm.stopBroadcast();\r\n    }\r\n}\r\n```\r\n\r\n\r\n\r\n我们来分析一下脚本代码：\r\n\r\n```text\r\ncontract CounterScript is Script {\r\n```\r\n\r\n\r\n\r\n创建一个名为 `CounterScript` 的合约，它从 Forge Std 继承了 `Script`。\r\n\r\n```text\r\nfunction run() external {\r\n```\r\n\r\n\r\n\r\n默认情况下，脚本是通过调用名为 `run` 的函数（入口点）来执行的部署。\r\n\r\n```text\r\nstring memory mnemonic = vm.envString(\"MNEMONIC\");\r\n(address deployer, ) = deriveRememberKey(mnemonic, 0);\r\n```\r\n\r\n\r\n\r\n从 .env 文件中加载助记词，并推导出部署账号，如果 `.env` 配置的是私钥，这使用`uint256 deployer = vm.envUint(\"PRIVATE_KEY\");` 加载账号\r\n\r\n```text\r\nvm.startBroadcast(deployerPrivateKey);\r\n```\r\n\r\n\r\n\r\n这是一个作弊码，表示使用该密钥来签署交易并广播。\r\n\r\n```text\r\nCounter c = new Counter();\r\n```\r\n\r\n\r\n\r\n创建Counter 合约。\r\n\r\n脚本代码编写好了， 让我们运行它， 在项目的根目录运行：\r\n\r\n```text\r\n> source .env\r\n\r\n> forge script script/Counter.s.sol --rpc-url goerli --broadcast \r\n[⠒] Compiling...\r\n[⠊] Compiling 1 files with 0.8.18\r\n[⠒] Solc 0.8.18 finished in 738.87ms\r\nCompiler run successful\r\nScript ran successfully.\r\nGas used: 127361\r\n\r\n== Logs ==\r\n  Counter deployed on 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0\r\n...\r\n```\r\n\r\n\r\n\r\n部署成功打印出合约的地址。\r\n\r\ngoerli 是我们之前在`foundry.toml` 文件中配置的端点。 如果我们不想在命令中输入`--rpc-url`， 可以在`foundry.toml`配置一个默认的 URL：\r\n\r\n```text\r\neth-rpc-url = \"${GOERLI_RPC_URL}\"  // 本地 RPC 为 http://127.0.0.1:8545\r\n```\r\n\r\n\r\n\r\nforge script 支持在部署时进行代码验证，在 `foundry.toml` 文件中配置了 etherscan的 API KEY：\r\n\r\n```text\r\n[etherscan]\r\ngoerli = { key = \"${ETHERSCAN_API_KEY}\" }\r\n```\r\n\r\n\r\n\r\n然后需要在 script 命令中加入 `--verify` 就可以执行代码开源验证。\r\n\r\n至此，我们已经知道了如何使用 Foundry 进行合约开发，下面继续补充一些常用知识点。\r\n\r\n## 补充1： Anvil 使用\r\n\r\n`anvil` 命令创建一个本地开发网节点（好像是对 hardhat node的封装 ），用于部署和测试智能合约。它也可以用来分叉其他与 EVM 兼容的网络。\r\n\r\n运行 `anvil` 效果如下\r\n\r\n```text\r\n> anvil\r\n\r\n\r\n                             _   _\r\n                            (_) | |\r\n      __ _   _ __   __   __  _  | |\r\n     / _` | | '_ \\  \\ \\ / / | | | |\r\n    | (_| | | | | |  \\ V /  | | | |\r\n     \\__,_| |_| |_|   \\_/   |_| |_|\r\n\r\n    0.1.0 (1d9a34e 2023-03-07T00:07:41.730822Z)\r\n    https://github.com/foundry-rs/foundry\r\n\r\nAvailable Accounts\r\n==================\r\n\r\n(0) \"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266\" (10000 ETH)\r\n(1) \"0x70997970C51812dc3A010C7d01b50e0d17dc79C8\" (10000 ETH)\r\n....\r\n\r\nPrivate Keys\r\n==================\r\n\r\n(0) 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80\r\n(1) 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d\r\n....\r\n\r\nWallet\r\n==================\r\nMnemonic:          test test test test test test test test test test test junk\r\nDerivation path:   m/44'/60'/0'/0/\r\n\r\n\r\nBase Fee\r\n==================\r\n\r\n1000000000\r\n\r\nGas Limit\r\n==================\r\n\r\n30000000\r\n\r\nGenesis Timestamp\r\n==================\r\n\r\n1678704146\r\n\r\nListening on 127.0.0.1:8545\r\n```\r\n\r\n\r\n\r\nanvil 命令常用到的功能选项有：\r\n\r\n```text\r\nanvil --port <PORT>\r\n```\r\n\r\n\r\n\r\n设置节点端口\r\n\r\n```text\r\nanvil --mnemonic=<MNEMONIC> \r\n```\r\n\r\n\r\n\r\n使用自定义助记词\r\n\r\n```text\r\nanvil --fork-url=$RPC --fork-block-number=<BLOCK>\r\n```\r\n\r\n\r\n\r\n从节点URL（需要是存档节点）fork 区块链状态，可以指定某个区块时的状态。\r\n\r\nanvil完整的功能选项可参考[文档](https://learnblockchain.cn/docs/foundry/i18n/zh/reference/anvil/index.html#选项)\r\n\r\n## 补充2：Cast 与合约交互使用\r\n\r\n`cast` 命令可以用来和区块链交互，因此可以直接使用 `cast` 在命令行中调用合约。\r\n\r\n例如 `cast call` 来调用`counter()` 方法：\r\n\r\n```text\r\n> cast call 0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0 \"counter()\" --rpc-url local\r\n0x0000000000000000000000000000000000000000000000000000000000000000\r\n```\r\n\r\n\r\n\r\n`0x9fe467...` 是被调用合约的地址，命令返回了结果 0。\r\n\r\n使用 `cast send` 调用 `setNumber(uint256)` 方法，发起一个交易:\r\n\r\n```bash\r\n> cast send 0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0 \"setNumber(uint256)\" 1 --rpc-url local --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80\r\n\r\nblockHash               0x9311823387753f28f47a5c87357e6207b13b223bd3afca5c1f1b31a5e4f8e400\r\nblockNumber             1\r\ncontractAddress\r\ncumulativeGasUsed       21204\r\neffectiveGasPrice       4000000000\r\ngasUsed                 21204\r\nlogs                    []\r\nlogsBloom               0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\r\nroot\r\nstatus                  1\r\ntransactionHash         0x5c74da477ce3922337037d0e153fb99f9b325b49f2bf199a487ddb965f6d1727\r\ntransactionIndex        0\r\ntype                    2\r\n```\r\n\r\n\r\n\r\n调用的函数有参数，则直接写在函数的后面。\r\n\r\n获取账号的余额（返回 Wei 为单位）：\r\n\r\n```text\r\ncast balance 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266\r\n9999999222505911124404\r\n```\r\n\r\n\r\n\r\n`cast` 命令功能非常多，更多参考[文档](https://learnblockchain.cn/docs/foundry/i18n/zh/reference/cast/cast.html)\r\n\r\n## 补充3：安装第 3 方库\r\n\r\n使用 `forge install` 可以安装第三方的库，不同于 npm，forge 会把整个第三方的库的 Git 仓库作为子模块放在lib目录下。 使用命令如下：\r\n\r\n```text\r\nforge install [OPTIONS] <github username>/<github project>@<tag>\r\n```\r\n\r\n\r\n\r\n例如，安装`openzepplin`使用命令：\r\n\r\n```text\r\n> forge install OpenZeppelin/openzeppelin-contracts\r\nInstalling openzeppelin-contracts in \"/Users/emmett/course/hello_decert/lib/openzeppelin-contracts\" (url: Some(\"https://github.com/OpenZeppelin/openzeppelin-contracts\"), tag: None)\r\n    Installed openzeppelin-contracts v4.8.2\r\n```\r\n\r\n\r\n\r\n安装之后，`.gitmodules` 会添加新记录：\r\n\r\n```text\r\n[submodule \"lib/openzeppelin-contracts\"]\r\n    path = lib/openzeppelin-contracts\r\n    url = https://github.com/OpenZeppelin/openzeppelin-contracts\r\n    branch = v4.8.2\r\n```\r\n\r\n\r\n\r\nlib 下也会多一个openzeppelin文件夹:\r\n\r\n```text\r\n> tree lib -L 1\r\nlib\r\n├── forge-std\r\n└── openzeppelin-contracts\r\n```\r\n\r\n\r\n\r\n然后，就可以在代码中引用 openzeppelin 库代码了， 让我们给 `setNumber` 加一个限制：仅所有者才可以调用，代码如下：\r\n\r\n```text\r\nimport \"openzeppelin-contracts/contracts/access/Ownable.sol\";\r\n\r\ncontract Counter is Ownable {\r\n    uint256 public number;\r\n\r\n    function setNumber(uint256 newNumber) public onlyOwner {\r\n        number = newNumber;\r\n    }\r\n    // ....\r\n}\r\n```\r\n\r\n\r\n\r\n### 使用 npm 安装库\r\n\r\n如果你使用NPM来安装库，也同样可以支持，在项目根目录下初始化项目，并安装库：\r\n\r\n```text\r\nnpm init -y\r\nnpm install @openzeppelin/contracts \r\n```\r\n\r\n\r\n\r\n安装完成之后，把node_modules文件夹 配置在 foundry.toml 的 libs中：\r\n\r\n```text\r\n[profile.default]\r\nsrc = 'src'\r\nout = 'out'\r\nlibs = ['lib','node_modules']\r\n```\r\n\r\n\r\n\r\n## 补充4： 标准库\r\n\r\n标准库封装了很多好好的方法可以直接使用，分为 4 个部分：\r\n\r\n- `Vm.sol`：提供作弊码（Cheatcodes）\r\n- `console.sol` 和 `console2.sol`：Hardhat 风格的日志记录功能， `console2.sol` 包含 `console.sol` 的补丁，允许Forge 解码对控制台的调用追踪，但它与 Hardhat 不兼容。\r\n- `Script.sol`：[Solidity 脚本](https://learnblockchain.cn/docs/foundry/i18n/zh/tutorials/solidity-scripting.html) 的基本实用程序\r\n- `Test.sol`：DSTest 的超集，包含标准库、作弊码实例 (`vm`) 和 Foundry 控制台\r\n\r\n介绍几个常用的作弊码：\r\n\r\n1. `vm.startPrank(address)` 来模拟用户， 在`startPrank`之后的调用使用设置的地址作为`msg.sender` 直到`stopPrank` 被调用。\r\n\r\n举例：\r\n\r\n```text\r\naddress owner = address(0x123);\r\n// 模拟owner\r\nvm.startPrank(owner);\r\n\r\nerc20.transfer(0x...., 1);  //  从bob 账号转出\r\nerc20.mint(100);\r\n....\r\n\r\n// 结束模拟\r\nvm.stopPrank();\r\n```\r\n\r\n\r\n\r\n如果只有一个调用需要模拟可以使用 `prank(address)`\r\n\r\n1. `warp(uint256)` 设置区块时间，可以用来测试时间的流逝。\r\n\r\n举例：\r\n\r\n```text\r\nvm.warp(1641070800);\r\nemit log_uint(block.timestamp); // 1641070800\r\n```\r\n\r\n\r\n\r\n1. `roll(uint256)` 设置区块\r\n\r\n举例：\r\n\r\n```text\r\nvm.roll(100);\r\nemit log_uint(block.number); // 100\r\n```\r\n\r\n\r\n\r\n更多用法可参考。\r\n\r\n## 小结\r\n\r\nFoundry 以Solidity为中心进行开发，减少了用户使用的心智负担。 Foundry 发布以来，使用率一直的攀升，非常推荐大家使用。\r\n\r\n\r\n\r\n原文首发于 Decert.me ： https://decert.me/tutorial/solidity/tools/foundry/\r\n\r\n\r\nDecert.me 是一个帮助个人构建自己的链上信用履历的开源平台。\r\n这是一个非盈利的公益产品，因为我们相信只有开放、可信的数据才能促进形成一个更自由、更可信、更高效的世界。 Decert.me 愿景是成为 Web3.0 时代的永久存储人们可信履历的基础数据。\r\n\r\n你可以自由转载文章，但请注明原文链接及 Decert.me 介绍。"},"author":{"user":"https://learnblockchain.cn/people/13917","address":null},"history":"QmQ2JW2P9wgxWVRrXhNP1dm4SqwWXbybLMHjbFg8XAyEFa","timestamp":1683596038,"version":1}