{"content":{"title":"Foundry的基本使用总结","body":"![Untitled.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/a2b2gA9X63218bb03c553.png!\/scale\/40)\r\n本文列举了foundry中常用的命令，方便以后查询使用。\r\n\r\n# 一. 为什么要用foundry\r\n\r\n- 全面支持solidity，可有效减少上下文切换\r\n    \r\n    与hardhat+ethers组合工具相比，hardhat+ethers合约使用solidity，而部署测试等使用 js或者ts。而对于foundry工具，合约、部署、测试等都使用solidity，不需要在多种编程语言之间进行切换。\r\n    \r\n- 功能更齐全。如cast命令可以直接从etherscan下载源代码，可以直接从abi 生成interface等功能。\r\n- 运行速度更快。\r\n\r\n# 二. 软件安装方法\r\n\r\n官方网站：[getfoundry.sh](http:\/\/getfoundry.sh)\r\n\r\n在mac环境下，使用下面命令进行安装\r\n\r\n```solidity\r\ncurl -L https:\/\/foundry.paradigm.xyz | bash\r\nsource ~\/.zshrc\r\n# 每次执行foundryup时，都会下载最新的cast,anvil,forge程序\r\nfoundryup\r\n```\r\n\r\nfoundry系列的工具，主要包含三大组件，分别对应不同的功能，下面会每个组件依次试用。\r\n\r\n- **forge**：主要用来开发、编译、部署合约。\r\n- **cast**：执行以太坊 RPC 调用的命令行工具\r\n- **anvil**：本地模拟节点环境，类似于ganache-cli的功能。\r\n\r\n# 三. cast使用\r\n\r\n<aside>\r\n😀 我的 ETH alchemy的RPC接点\r\nhttps:\/\/eth-mainnet.g.alchemy.com\/v2\/*****\r\n\r\nexport ETH_RPC_URL=https:\/\/eth-mainnet.g.alchemy.com\/v2\/******\r\n\r\n<\/aside>\r\n\r\ncast 是 Foundry 用于执行以太坊 RPC 调用的命令行工具。您可以进`行智能合约调用`、`发送交易或检索任何类型的链数据`！\r\n\r\ncast与web3交互的小工具，即使不是代码开发的人员也会经常使用该工具与链上数据进行查询等交互。\r\n\r\ncast rpc eth_blockNumber --rpc-url=$ETH_RPC_URL\r\n\r\n<aside>\r\n😀 cast支持环境变量ETH_RPC_URL，将RPC节点设置到环境变量ETH_RPC_URL中。\r\n对带有--rpc-url的参数的cast命令中，可直接从环境变量中直接读取，不需要在命令中体现。\r\n\r\n<\/aside>\r\n\r\n## 3.1 查询功能\r\n\r\n### 查询区块高度-cast rpc eth_blockNumber\r\n\r\n```solidity\r\ncast rpc eth_blockNumber --rpc-url=$ETH_RPC_URL\r\n\"0xebc18f\"\r\n(base) ➜  ~ cast --to-dec \"0xebc18f\"\r\n15450511\r\n(base) ➜  ~ cast --to-dec 0xebc18f\r\n15450511\r\n```\r\n\r\n\r\n![Untitled 1.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/qU3t0xT663218bd8de330.png)\r\n\r\n### 查询区块信息-cast block <blockNumer>\r\n\r\n```solidity\r\n\r\n(base) ➜  ~ cast block 15450511 --rpc-url=$ETH_RPC_URL\r\n\r\nbaseFeePerGas        18648783904\r\ndifficulty           12266510444604275\r\nextraData            0x706f6f6c696e2e636f6d21bb45000ef0fc7e9d\r\ngasLimit             29941438\r\ngasUsed              28701300\r\n\r\ntransactions:        [\r\n\t0x1ac18cdb12a6cb7022823fef4e2bc64fa959352af58507e057fc27f62d1e23a7\r\n\t0x1b0032cb42ade1add87a25f367b4142ebe627771abc936f2a6f403bcd50e6dc5\r\n\t0x28afc8b0659d88ffb03b803b01eb573690b6e3b70a0c1cf941d7f6fafc146465\r\n\r\n```\r\n\r\n### 查询交易信息-cast tx <交易hash>\r\n\r\n```solidity\r\n(base) ➜  ~ cast tx 0xd38950f391b91fef3daaf516d86470a1552461539bdba5ace230b942d5237974 --rpc-url=$ETH_RPC_URL\r\n\r\nblockHash            0xd73fb0230f3ab6e8a8c9ba5698c1ec7beb5aa23175e1231560b5d507b748a7ea\r\nblockNumber          15450511\r\nfrom                 0x796ed889d874dEeE8fE495F6c245765cf7db193B\r\ngas                  96677\r\ngasPrice             19654542987\r\nhash                 0xd38950f391b91fef3daaf516d86470a1552461539bdba5ace230b942d5237974\r\ninput                0xa0712d680000000000000000000000000000000000000000000000000000000000000002\r\nnonce                0\r\nr                    0x31c9c3e6d7cd7058025a4b7cf2c17355ac856902c20fdff6b83d2c134d66ea2f\r\ns                    0x1775d3a1002467e05cbabc23219e18b27e9ac29dd0dbdafc165bbab052f7ba23\r\nto                   0xc93f78f08c7E9526C78Da56Cba1DEE8287baCb27\r\ntransactionIndex     4\r\nv                    1\r\nvalue                0\r\n```\r\n\r\n### 交易回执查询-cast receipt <receipt_hash>\r\n\r\n```solidity\r\nbase) ➜  ~ cast receipt 0xd38950f391b91fef3daaf516d86470a1552461539bdba5ace230b942d5237974 --rpc-url=$ETH_RPC_URL\r\n\r\nblockHash               0xd73fb0230f3ab6e8a8c9ba5698c1ec7beb5aa23175e1231560b5d507b748a7ea\r\nblockNumber             15450511\r\ncontractAddress\r\ncumulativeGasUsed       564786\r\neffectiveGasPrice       19654542987\r\ngasUsed                 86319\r\nlogs                    [{\"address\":\"0xc93f78f08c7e9526c78da56cba1dee8287bacb27\",\"topics\":[\"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef\",\"0x000000000000000000000000000000000000000000000000\r\n\r\n使用 --json 以json格式返回数据，使用管道输入给jq进行处\r\ncast receipt 0xd38950f391b91fef3daaf516d86470a1552461539bdba5ace230b942d5237974 --rpc-url=$ETH_RPC_URL --json | jq\r\n```\r\n\r\n\r\n> 🤣 jq工具的使用\r\n> \r\n> jq 一个灵活的轻量级命令行JSON处理器,jq 用于处理JSON输入，将给定过滤器应用于其JSON文本输入并在标准输出上将过滤器的结果生成为JSON。\r\n> \r\n> 1. 下载https:\/\/github.com\/stedolan\/jq\/releases\r\n> 2. 移动到 \/usr\/local\/bin，并命名成jq,设置成可执行属性。\r\n\r\n\r\n\r\n### 查询calldata数据-cast pretty-calldata <十六进制数据>\r\n\r\npretty-calldata 命令会取出 <十六进制数据>中的前4个字节，从在线网站的数据库([https:\/\/sig.eth.samczsun.com\/](https:\/\/sig.eth.samczsun.com\/))中比对4字节的selector对应的函数原型，并将 <十六进制数据>中的后面部分的数据按照函数原型进行格式化输出。\r\n\r\n```solidity\r\n\r\n查看input数据\r\n(base) ➜  ~ cast tx 0x3574c7c9b34df46d7476c5a8e9fb48b2bf007df7d5d021ef9aa79983f4b13f92 --rpc-url=$ETH_RPC_URL input\r\n0xa9059cbb0000000000000000000000007f1949e62203a83ad6e6be0a819f93e580054f9d000000000000000000000000000000000000000000038e8f7792d79767800000\r\n\r\n(base) ➜  ~ cast pretty-calldata 0xa9059cbb0000000000000000000000007f1949e62203a83ad6e6be0a819f93e580054f9d000000000000000000000000000000000000000000038e8f7792d79767800000\r\n\r\n Possible methods:\r\n - transfer(address,uint256)\r\n ------------\r\n [0]:  0000000000000000000000007f1949e62203a83ad6e6be0a819f93e580054f9d\r\n [1]:  000000000000000000000000000000000000000000038e8f7792d79767800000\r\n\r\n```\r\n\r\n可以通过cast 4byte <十六进制数据> 在查询函数selector对应的函数原型。\r\n\r\n```solidity\r\n\r\n# 查询0xa9059cbb selector对应的函数原型\r\n(base) ➜  ~ cast 4byte 0xa9059cbb\r\ntransfer(address,uint256)\r\n\r\n# 使用keccak计算函数原型对应的hash,可以发现hash的前4个字节就是selector\r\n(base) ➜  ~ cast keccak \"transfer(address,uint256)\"\r\n0xa9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b\r\n\r\n(base) ➜  ~ cast sig \"transfer(address,uint256)\"\r\n0xa9059cbb\r\n```\r\n\r\n### 查询topic日志对应的函数原型-cast 4byte-event <topic>\r\n\r\n    \r\n![Untitled 2.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/hskJaeEb63218c15d851e.png)\r\n\r\n```solidity\r\n\r\n(base) ➜  ~ cast 4byte-event 0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c\r\nDeposit(address,uint256)\r\n```\r\n\r\n## 3.2 交易模拟-cast run\r\n\r\ncast run命令\r\n\r\n以****defi直接价格操纵经典案例-tcrToken被黑事件中的****交易为例，可参考[https:\/\/learnblockchain.cn\/article\/4491](https:\/\/learnblockchain.cn\/article\/4491)\r\n\r\n对应的交易为[0x81e9918e248d14d78ff7b697355fd9f456c6d7881486ed14fdfb69db16631154](https:\/\/cn.etherscan.com\/tx\/0x81e9918e248d14d78ff7b697355fd9f456c6d7881486ed14fdfb69db16631154)\r\n\r\n```solidity\r\n(base) ➜ ~ cast run 0x81e9918e248d14d78ff7b697355fd9f456c6d7881486ed14fdfb69db16631154\r\n```\r\n\r\n\r\n\r\n## 3.3 钱包相关功能-cast wallet\r\n\r\n使用帮助\r\n\r\n```solidity\r\n(base) ➜  ~ cast wallet -h\r\ncast-wallet\r\nWallet management utilities.\r\n\r\nUSAGE:\r\n    cast wallet <SUBCOMMAND>\r\n\r\nOPTIONS:\r\n    -h, --help    Print help information\r\n\r\nSUBCOMMANDS:\r\n    address    Convert a private key to an address. [aliases: a, addr]\r\n    help       Print this message or the help of the given subcommand(s)\r\n    new        Create a new random keypair. [aliases: n]\r\n    sign       Sign a message. [aliases: s]\r\n    vanity     Generate a vanity address. [aliases: va]\r\n    verify     Verify the signature of a message. [aliases: v]\r\n\r\n```\r\n\r\n**创建钱包**\r\n\r\n通过cast wallet new 创建新的钱包\r\n\r\n```solidity\r\n(base) ➜  ~ cast wallet new\r\nSuccessfully created new keypair.\r\nAddress: 0x382B0Db462165Bc1b78B355eBB747E2F378bC711\r\n```\r\n\r\n直接跟目录名，将钱包保存到keystore目录中\r\n\r\n```solidity\r\n(base) ➜  cast_basic cast wallet new keystore\r\nInsert secret:\r\n\r\nCreated new encrypted keystore file: `\/Users\/mamaogang\/Nextcloud\/code\/eth_test\/foundry\/cast_basic\/keystore\/8c0cb584-95aa-4f63-924d-d8c5ab92f1bf`\\nPublic Address of the key: 0xb18A7BC0c376CB3be07CCC883900b61d8e33ce8B\r\n```\r\n\r\n\r\n\r\n![Untitled 4.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/mbKgfZP063218c528e3fe.png)\r\n\r\n **签名-cast wallet sign**\r\n\r\n\r\n![Untitled 5.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/puY6pDo263218c5fe4f13.png)\r\n**ENS功能-cast resolve-name和cast lookup-address**\r\n\r\n```solidity\r\n(base) ➜  cast_basic cast resolve-name vatalik.eth\r\n0x7d66bD3dA15e079495989dc8139379784146afeD\r\n(base) ➜  cast_basic cast lookup-address 0x7d66bD3dA15e079495989dc8139379784146afeD\r\nError:\r\nens name not found: 7d66bd3da15e079495989dc8139379784146afed.addr.reverse\r\n```\r\n\r\n\r\n![Untitled 6.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/OCKqMtGu63218c6a06f6a.png)\r\n\r\n## 3.4 合约相关功能\r\n\r\n在使用查看源代码功能之前，需要设置ETHERSCAN_API_KEY的环境变量\r\n\r\n```solidity\r\nexport ETHERSCAN_API_KEY=NZMQ7KC5CD5BND19KMBQFA3BI3QJUTG53V\r\n```\r\n\r\nWETH 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2et\r\n\r\n```solidity\r\nexport WETH=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\r\n```\r\n\r\n**查看源代码-cast etherscan-source**\r\n\r\ncast etherscan-source $WETH 查看$WETH的源代码。\r\n\r\n使用-d参数，将结果保存到指定目录下。\r\n\r\n```solidity\r\n(base) ➜  cast_basic cast etherscan-source $WETH -d weth_source\r\n(base) ➜  cast_basic vi weth_source\/WETH9\/WETH9.sol\r\n```\r\n\r\n\r\n![Untitled 7.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/4klQR0W063218c7858847.png)\r\n\r\n**调用合约函数-cast call**\r\n\r\n```solidity\r\ncast call $WETH \"balanceOf(address)\" 0xf04a5cc80b1e94c69b48f5ee68a08cd2f09a7c3e\r\n\r\n(base) ➜  cast_basic cast --to-dec 0x00000000000000000000000000000000000000000000bdb51a04b5aa8eb6431e\r\n\r\n895868000762793410577182\r\n```\r\n\r\n\r\n![Untitled 8.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/bYBJXRvn63218c8fd6297.png)\r\n\r\n### 查询合约的slot的存储位置-cast index\r\n\r\ncast index 根据KEY_TYPE的类型和KEY,及SLOT_NUMBER计算出`存储位置`。\r\n\r\n帮助说明\r\n\r\n\r\n![Untitled 9.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/c9jXLIDQ63218c9ae56db.png)\r\n\r\n问题：计算`0xf04a5cc80b1e94c69b48f5ee68a08cd2f09a7c3e`账户在$WETH token中的余额，可以使用两种方式取得。\r\n\r\n- 常规函数调用方式\r\n- 读取合约slot存储方式\r\n1. 常规函数调用方式\r\n    \r\n    采用合约函数调用的方式，可以看到该账户下有bdb51a04b5aa8eb6431e 个WETH.\r\n    \r\n    ```solidity\r\n    (base) ➜  cast_basic cast call $WETH  \"balanceOf(address)\" 0xf04a5cc80b1e94c69b48f5ee68a08cd2f09a7c3e\r\n    0x00000000000000000000000000000000000000000000bdb51a04b5aa8eb6431e\r\n    ```\r\n    \r\n2. 读取合约slot存储方式\r\n    \r\n    先根据WETH的源代码，分析得到balanceOf状态变量位于第3个slot，如何获得源代码？可以通过`cast etherscan-source $WETH -d 目录` 命令来获得。源代码如下：\r\n    \r\n    ```solidity\r\n    pragma solidity ^0.4.18;\r\n     \r\n      contract WETH9 {\r\n          string public name     = \"Wrapped Ether\";\r\n          string public symbol   = \"WETH\";\r\n          uint8  public decimals = 18;\r\n     \r\n          event  Approval(address indexed src, address indexed guy, uint wad);\r\n          event  Transfer(address indexed src, address indexed dst, uint wad);\r\n          event  Deposit(address indexed dst, uint wad);\r\n          event  Withdrawal(address indexed src, uint wad);\r\n     \r\n          mapping (address => uint)                       public  balanceOf;\r\n          mapping (address => mapping (address => uint))  public  allowance;\r\n     \r\n          function() public payable {\r\n              deposit();\r\n          }\r\n    ```\r\n    \r\n    通过slot来读取\r\n    \r\n    ```solidity\r\n    ＃ 先计算出KEY_TYPE为address,KEY为0xf04a5cc80b1e94c69b48f5ee68a08cd2f09a7c3e，slot为3，所对应的存储位置。\r\n    (base) ➜  cast_basic cast index address  0xf04a5cc80b1e94c69b48f5ee68a08cd2f09a7c3e 3\r\n    0x1f8193c3f94e8840dc3a6dfc0bc012432d338ef33c4f3e4b3aca0d6d3c5a09b6\r\n    \r\n    ＃　取出对应存储位置的原始数据，因为为address=>int,所以取出来就没int\r\n    (base) ➜  cast_basic cast storage $WETH  0x1f8193c3f94e8840dc3a6dfc0bc012432d338ef33c4f3e4b3aca0d6d3c5a09b6\r\n    0x00000000000000000000000000000000000000000000bdb51a04b5aa8eb6431e\r\n    ```\r\n    \r\n\r\n### **查询合约的存储slot的原始数据-cast storage**\r\n\r\n查询合约的存储slot中的原始数据。\r\n\r\n帮助文档\r\n\r\n![Untitled 10.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/sTMYBNqJ63218cb6d7e32.png)\r\n\r\n### 从abi生成interface-cast interface <abi文件或者合约地址>\r\n\r\n使用帮助\r\n\r\n\r\n![Untitled 11.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/PHhRBN6863218cc1bf1bb.png)\r\n\r\n以WBNB为例\r\n\r\n在[https:\/\/bscscan.com\/address\/0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c#code](https:\/\/bscscan.com\/address\/0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c#code)中复制abi并保存到wbnb.abi文件中，使用下列命令生成接口。\r\n\r\n```solidity\r\ncast interface wbnb.abi\r\n```\r\n\r\n\r\n![Untitled 12.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/AAlunIbX63218ccc6eb98.png)\r\n\r\n也可以直接跟某个地址\r\n\r\n```solidity\r\n(base) ➜  cast_basic echo $WETH\r\n0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\r\n\r\ncast interface $WETH\r\n```\r\n\r\n\r\n![Untitled 13.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/PtUrkX3r63218cd932a64.png)\r\n\r\n### 编码解码-cast —to-xxx系统函数\r\n\r\n```solidity\r\ncast --to-hex\r\ncast --to-dec\r\n\r\ncast --to-wei\r\ncast --to-uint  如cast --to-uint 100000 ether　将10000转成ether的单位。\r\n\r\ncast --to-bytes32\r\ncast --to-ascii\r\n\r\ncast --from-wei\r\ncast --format-bytes32-string\r\n```\r\n\r\n# 四. anvil使用\r\n\r\n直接运行效果\r\n\r\n\r\n![Untitled 14.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/MmKyzKzy63218ce6ba32c.png!\/scale\/50)\r\n\r\n**模拟从主网fork-casat —fork-url=$ETH_RPC_URL**\r\n\r\n使用**fork-casat —fork-url=$ETH_RPC_URL**可以模拟主网\r\n\r\n\r\n![Untitled 15.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/2pan14MG63218cf0c6389.png)\r\n\r\nanvil 常用的命令参数\r\n\r\n—accounts=账户的数量\r\n\r\n—balance=每个账户的余额\r\n\r\n—fork-block-number=区块高度\r\n\r\n**特殊的RPC方法－anvil_*等同于hardhat_***\r\n\r\n`anvil_impersonateAccount`\r\n\r\nanvil_setStorageAt\r\n\r\n# 五. forge-智能合约开发框架\r\n\r\n## 5.1 **初始化项目-forge init**\r\n\r\nforge init <dir_name>\r\n\r\nforge init —template <template_path> <dir_name>\r\n\r\n\r\n![Untitled 16.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/4JsR6BvH63218cfdce445.png)\r\n\r\n看下当前目录的结构\r\n\r\n```solidity\r\n(base) ➜  forge_basic tree -L 2\r\n.\r\n└── hello-foundry\r\n    ├── foundry.toml\r\n    ├── lib\r\n    ├── script\r\n    ├── src\r\n    └── test\r\n```\r\n\r\n**配置设置**\r\n\r\n```solidity\r\n＃ 打印所有的配置\r\nforge config \r\n\r\n# 打印基础的配置\r\nforge config --basic\r\n\r\n# 生成新的基础配置\r\nforge config > foundry.toml\r\n```\r\n\r\n![Untitled 17.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/KMrvrRCk63218d0ae23f4.png)\r\n\r\n## 5.2 **编译-forge build**\r\n\r\n对应的编译命令为\r\n\r\n```solidity\r\nforge build\r\nforge build -w 实时写代码，实时编译\r\n```\r\n\r\n<aside>\r\n😀 通常会在tmux中开两个pane。\r\n第一个pane用于查看实时编码情况，使用-w实时监控;\r\n第二个pane中编写代码，每次修改完代码后，保存后，第一个panel就会实时显示编译是否通过。\r\n\r\n<\/aside>\r\n\r\n## 5.3 **自动化测试-forge test**\r\n\r\n```solidity\r\n# 可以使用使用-v级别、－vv级别、-vvv级别进行日志的打印\r\nforge test -v \/-vv \/ -vvv\r\n\r\n＃ 使用-w进行监视模式\r\nforge test -v \/-vv \/ -vvv　-w 使用监视模式\r\n```\r\n\r\n测试分类\r\n\r\n- 简单测试\r\n- fuzz\r\n- 不变量测试\r\n\r\n有个牛逼的功能。标准库里有个vm实例，可以通过vm改变虚拟机的状态。\r\n\r\n## 5.4 日志打印\r\n\r\n日志打印通常有两种方法：\r\n\r\n1. console模块。如`console2.log(”hello world”)`。\r\n2. emit log方法。如emit log(”hello world”);\r\n\r\n注意，使用打日志的方法的方法时，如果使用`forge test`无法展示打印的日志，记得要`—vvv`以上才能打印出来，一个v时显示不出来\r\n\r\nemit log(”hello world”);\r\n\r\n使用`console2.log(”hello world”);`也是同样的效果。\r\n\r\n\r\n![Untitled 18.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/ed6NlIox63218d216b2fe.png)\r\n\r\n## 5.5 **cheatcode修改vm状态**\r\n\r\ncheatcode,可以在test合约中使用vm变量修改vm的状态。\r\n\r\n- vm.warp() 修改vm中的`block.timestamp`变量\r\n- vm.roll() 修改vm中的`block.number`变量\r\n- vm.prank(address)　改变`下一次`调用的msg.sender,只改变下一次调用，其他的调用会恢复回来。\r\n    \r\n    如果后面的调用也一直保持修改，使用`vm.startPrank(alice);  vm.stopPrank();`\r\n    \r\n- deal(address who, uint256 newBalance) 改变who地址的余额。\r\n\r\n### **vm.warp-修改timestamp示例**\r\n\r\n```solidity\r\nvm.warp(1641070800);\r\nemit log_uint(block.timestamp);\r\n```\r\n\r\n\r\n![Untitled 19.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/Grx2hgnD63218d2f91221.png)\r\n\r\n### **vm.startPrank-修改msg.sender示例**\r\n\r\nvm.startPrank(alice);\r\n\r\nvm.stopPrank();\r\n\r\n```solidity\r\n\/\/ SPDX-License-Identifier: UNLICENSED\r\npragma solidity ^0.8.10;\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    address public alice;\r\n    Helper public h;\r\n    function setUp() public {\r\n       counter = new Counter();\r\n       alice = address(1);\r\n       h = new Helper();\r\n       counter.setNumber(0);\r\n    }\r\n    function testVm() public {\r\n       console2.log(\"before cheatcode:\", h.whoCalled());\r\n       vm.startPrank(alice);\r\n       console2.log(\"after cheatcode:\", h.whoCalled());\r\n       vm.stopPrank();\r\n    }\r\n\r\n    function testIncrement() public {\r\n        counter.increment();\r\n        assertEq(counter.number(), 1);\r\n    }\r\n\tfunction testSetNumberOne() public {\r\n\t\tcounter.setNumber(1);\r\n\t\tassertEq(counter.number(), 1);\r\n\t}\r\n    function testSetNumber(uint256 x) public {\r\n        counter.setNumber(x);\r\n        assertEq(counter.number(), x);\r\n    }\r\n}\r\n\r\ncontract Helper {\r\n    function whoCalled() public view returns(address) {\r\n        return msg.sender;\r\n    }\r\n}\r\n```\r\n\r\n\r\n![Untitled 20.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/Mc1aaK3Y63218d3d21b2a.png)\r\n\r\n### **vm.deal修改balance示例**\r\n\r\nvm.deal(alice, 1 ether) \/\/改变alice地址的原生代币的余额为1 ether\r\n\r\n\r\n![Untitled 21.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/CBzUMnyL63218d4cef05f.png)\r\n\r\n### vm.rollFork() 到指定的区块高度。\r\n\r\n```solidity\r\nfunction testVmFork() public {\r\n        string memory MAINNET_RPC_URL = \"https:\/\/eth-mainnet.g.alchemy.com\/v2\/*******l\";\r\n        uint256 forkId = vm.createFork(MAINNET_RPC_URL);\r\n        vm.selectFork(forkId);\r\n        console2.log(\"cur blocknum:\", block.number);\r\n        vm.rollFork(15531500);\r\n        console2.log(\"after blocknum:\", block.number);\r\n    }\r\n```\r\n\r\n\r\n![Untitled 22.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/7NuoiGzQ63218d5c9afac.png)\r\n### vm.ffi 调用外部命令\r\n\r\n使用vm.ffi时，在启动forge test时，需要添加 —ffi 参数。如\r\n\r\n```solidity\r\nforge test -vvv -w --fork-url=$ETH_RPC_URL --ffi\r\n```\r\n\r\n测试代码\r\n\r\n```solidity\r\nfunction testffi() public {\r\n        \/\/ 使用keccak256函数计算出hash1\r\n        string memory aMessage = \"abc\";\r\n        bytes32 hash1 = keccak256(abi.encodePacked(aMessage));\r\n        console2.logBytes32(hash1);\r\n\r\n        \/\/ 使用vm.ffi计算出hash2\r\n        string[] memory cmds = new string[](3);\r\n        cmds[0] = \"cast\";\r\n        cmds[1] = \"keccak\";\r\n        cmds[2] = aMessage;\r\n\r\n        bytes memory ffiResult = vm.ffi(cmds);\r\n        bytes32 hash2 = abi.decode(ffiResult, (bytes32));\r\n        console2.logBytes32(hash2);\r\n\r\n        \/\/ 比较hash1和hash2是相同的。\r\n        assertEq(hash1, hash2);\r\n    }\r\n```\r\n\r\n\r\n![Untitled 23.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/GMpE5bmI63218d668e7ce.png)\r\n\r\n## 5.6 forge snapshot-快照功能\r\n\r\n为每个测试用例的gas使用创建快照。主要用于在开发过程中对gas费的优化。\r\n\r\n常与`forge snapshot —diff` 一起使用，-diff 参数会与上次的快照对比gas费的对比。\r\n\r\n\r\n# 六.代码示例\r\n\r\n### 6.1 如何修改ERC20代币的余额呢？\r\n\r\n在5.5中，可以通过vm.deal来修改原生代币的余额，那么在编写测试用例时，怎样才能修改ERC20代币的余额呢？可以一起通过编写一个ERC20的代币，并使用foundry来修改ERC20代币的余额的测试用例。\r\n\r\n**yarn 安装@openzeppelin\/contracts**\r\n\r\n```solidity\r\nyarn add @openzeppelin\/contracts\r\n```\r\n\r\n配置config，foundry.toml文件,将 lib中加入`node_modules`\r\n\r\n```solidity\r\nlibs = ['lib','node_modules']\r\n```\r\n\r\n使用forge remappings 查看当前的remappings\r\n\r\n```solidity\r\nforge remappings >remappings.txt\r\n```\r\n\r\n将当前remappings保存到remappings.txt文件中，\r\n\r\n```python\r\n@openzeppelin\/=node_modules\/@openzeppelin\/\r\nds-test\/=lib\/forge-std\/lib\/ds-test\/src\/\r\nforge-std\/=lib\/forge-std\/src\r\n```\r\n\r\n<aside>\r\n🤣 如果foundry.toml文件中的libs=[’lib’] 没有包含node_modules的话，使用forge remappings 产生的remappings.txt就不会包含@openzeppelin这一行了。\r\n\r\n<\/aside>\r\n\r\n使用标准的cheatcode函数deal\r\n\r\n```solidity\r\ndeal(address(dai), alice, 10000e18);\r\nassertEq(dai.balanceOf(alice), 10000e18);\r\n\r\n```\r\n\r\n完整的演示代码\r\n\r\n```solidity\r\ncontract CounterTest is Test {\r\n    Counter public counter;\r\n    address public alice;\r\n    Helper public h;\r\n    IERC20 public dai;\r\n\r\n    function setUp() public {\r\n       counter = new Counter();\r\n       alice = address(1);\r\n       h = new Helper();\r\n       counter.setNumber(0);\r\n        dai = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);\r\n    }\r\n    \r\n    function testDaiDeal() public {\r\n        console2.log(\"before deal, Alice Dai balance is\",2);\r\n        deal(address(dai), alice, 1001 ether); \r\n        console2.log(\"after deal, Alice Dai balance is\", alice.balance);\r\n}\r\n```\r\n\r\n如果使用forge test -vvv -w 时，可以看到测试不会通过，测试会失败，出错内容为\"EvmError: Revert”，如下所示\r\n\r\n\r\n![Untitled 25.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/PQa4Gbzl63218d866b633.png)\r\n\r\n出错的原因是，因为`dai合约没有在测试环境中部署`。如果不想部署dai合约，我们可以通过`fork-url`的方式直接使用主网的 dai合约。\r\n\r\n使用主网的dai合约测试的话，使用`forge test -vvv -w -fork-url=$ETH_RPC_URL` ,fork主网到本地进行测试。使用该命令就可以`测试成功`。\r\n\r\n\r\n![Untitled 26.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/Iov1wZ8563218d900d3a0.png)\r\n### 6.2. 如何在代码中进行fork-url\r\n\r\n上面fork-url时，是直接通过forge调用的参数传递进去的，有没有办法在代码直接进行fork-url？\r\n\r\n如果在代码中可以实现fork-url的话，我们就可以直接在代码针对不同的测试网络编写不同的测试用例，在测试用例中就可以覆盖全网络。\r\n\r\n \r\n\r\n通过vm.envAddress函数可以从 vm中读取环境变量\r\n\r\nvm.envAddress(string calldata, string calldata) 取得vm中的地址。\r\n\r\n\r\n在代码中进行fork的主要代码\r\n\r\n```solidity\r\nstring memory rpc = vm.envString(\"ETH_RPC_URL\");\r\nuint256 mainnet = vm.createFork(rpc);\r\nvm.selectFork(mainnet);\r\n\r\n```\r\n\r\n```solidity\r\nIERC20 public dai;\r\n\r\n    function setUp() public {\r\n       counter = new Counter();\r\n       alice = address(1);\r\n       h = new Helper();\r\n       counter.setNumber(0);\r\n       \/\/ dai = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);\r\n       dai = IERC20(vm.envAddress(\"DAI\"));\r\n       console2.log(\"DAI address:\", address(dai));\r\n    }\r\n    \r\n    function testDaiDeal() public {\r\n        string memory rpc = vm.envString(\"ETH_RPC_URL\");\r\n        uint256 mainnet = vm.createFork(rpc);\r\n        vm.selectFork(mainnet);\r\n        console2.log(\"before deal, Alice Dai balance is\", alice.balance);\r\n        deal(address(dai), alice, 1001 ether); \r\n        console2.log(\"after deal, Alice Dai balance is\", alice.balance);\r\n}\r\n```\r\n\r\n\r\n\r\n\r\n# 参考\r\nhttps:\/\/book.getfoundry.sh\/\r\nhttps:\/\/www.youtube.com\/watch?v=EXYeltwvftw&t=6s\r\nhttps:\/\/sig.eth.samczsun.com\/"},"author":{"user":"https:\/\/learnblockchain.cn\/people\/9625","address":null},"history":"QmV7w1qToYQZHCja4nyj37yeAZZwZhXZQhXRhhuFpoRnvA","timestamp":1663145905,"version":1}