前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Foundry教程|如何调试和部署Solidity智能合约

Foundry教程|如何调试和部署Solidity智能合约

作者头像
Tiny熊
发布2022-11-07 10:45:40
1.9K0
发布2022-11-07 10:45:40
举报

译文出自:登链翻译计划[1] 译者:翻译小组[2] 校对:Tiny 熊[3]

Foundry 是一个 Solidity 框架,用于构建、测试、模糊、调试和部署 Solidity 智能合约。在这个 Foundry 教程中,我们将介绍以下内容。

  1. Foundry 简介 [视频]
  2. Foundry 入门
  3. 使用 Foundry 的 Hardhat
  4. 使用 Solidity 测试
  5. Foundry 作弊代码
  6. 部署和使用合约

Foundry 简介

我制作了一个[视频],你可以在 YouTube 上观看:https://youtu.be/VhaP9kYvlOA

智能合约通常是用 Solidity 编写的,但使用 Javascript 框架(如 Truffle 或 Hardhat)进行测试和部署。Foundry 提供了一套在 Rust 中构建的工具,允许区块链开发者在 Solidity 中编写测试,并通过命令行部署和与合约交互。

为什么用 Foundry?

  • 在 solidity 中编写单元测试,而不是 Javascript
  • 更快的编译和测试
  • 内置的模糊测试
  • Gas 优化工具
  • 支持主网分叉
  • Etherscan 代码验证
  • 硬件钱包兼容
  • Solidity 脚本
  • 通过作弊代码操纵区块链状态

开始使用 Foundry

安装说明

为了开始使用,我们需要安装 foundry 包,它需要 rust。以下是 linux、mac、windows 和 docker 的命令。

Linux/Mac:

代码语言:javascript
复制
curl -L https://foundry.paradigm.xyz | bash;
foundryup

Windows: (需要 Rust,从 https://rustup.rs/ 安装)

代码语言:javascript
复制
cargo install --git https://github.com/foundry-rs/foundry --bins --locked

Docker:

代码语言:javascript
复制
docker pull ghcr.io/foundry-rs/foundry:latest

使用 foundry 的第一步

Foundry 软件包带有两个主要的命令行功能:

  • forge - 建立编译测试本地智能合约
  • cast - 使用已部署的智能合约执行链上交易

如果想从 Github 上克隆一个 repo,我们可以使用 forge 命令。

代码语言:javascript
复制
forge install jamesbachini/myVault -hh

这里我们使用的是 Github 用户名和版本库名称,加上-hh 参数,用于迁移 Hardhat 版本库。

我们也可以用 "myrepo" 初始化一个新的版本库。

代码语言:javascript
复制
forge init myrepo

然后就可以继续编译和测试智能合约了

代码语言:javascript
复制
forge build
forge test

注意测试是如何通过的,还得到了测试交易的 Gas 成本


如何使用 Hardhat 设置 Foundry

假设我们已经按照上面的说明安装了 Foundry,我们就可以与 Hardhat[4] 一起使用它。

注意,我们需要为当前工作目录设置一个 repo,所以如果还没有的话,请使用 git init。

然后复制并粘贴以下内容到版本库根目录下的一个新的foundry.toml文件中:

代码语言:javascript
复制
[default]
src = 'contracts'
test = 'test'
out = 'artifacts/contracts'
libs = ['lib']

这将设置目录结构以便与 Hardhat 保持一致。Foundry 测试可以使用正常的 MyContract.t.sol 命名放在标准测试文件夹中。

从这里我们可以在 Hardhat 中使用 Foundry 进行测试和部署。就我个人而言,我喜欢 Hardhat 的脚本环境(特别是对于复杂的部署),但也认识到使用 Foundry 进行测试和模糊处理的好处。在同一个代码库中使用这两个应用程序,可以提供两个最佳选择。

使用 Solidity 测试

在我们开始编写单元测试之前,需要安装标准库

代码语言:javascript
复制
forge install foundry-rs/forge-std

然后我们可以将其导入测试文件中,该文件的名称将与我们的合约相同,后缀是.t.sol。

代码语言:javascript
复制
pragma solidity ^0.8.13;

import "forge-std/Test.sol";

contract MyContractTest is Test {

  testWhatever(uint256 var1) public {
    uint256 var2 = 1;
    assertEq(var1,var2);
  }

}

请注意,在上面的例子中,我们给函数名加上了一个前缀 test -> testWhatever(),此时出现任何 revert 都将失败。我们也可以把 testFail -> testFailWhatever()作为前缀,这样就需要 revert 才能通过。

另外请注意,在上面的例子中,我们在 var1 中传入了一个 uint256 变量。这个值将被fuzzed(模糊),这意味着它将用各种不常用的值在函数上循环,试图创造一个边缘情况,导致回退(revert)。在这种情况下,任何不是 1 的值都会由于 assertEq()函数而导致测试失败。

也可以创建自定义断言,例如:

代码语言:javascript
复制
function myAssertion(uint a, uint b) {
  if (a != b) {
    emit log_string("a != b");
    fail();
  }
}

然后,我们可以在整个合约中使用这个断言,或者建立一个自定义断言库,并类似于我们先前导入标准库的方式来导入它们。

如果代码库包含许多不同的智能合约,可以使用--match-contract将单个合约和它的依赖关系分离出来,甚至可以使用--match-test命令行选项进行特定测试。

代码语言:javascript
复制
forge test --match-test optionalSpecificTest --match-contract optionalSpecificContract

另外,我们可以使用 forge run 来执行一个单一的 solidity _"脚本 "_。

代码语言:javascript
复制
forge run src/Contract.sol     //  执行单个脚本
forge run src/Contract.sol --debug // open script in debugger
forge run src/Contract.sol --sig "foo(string)" "hi" //  执行单一函数

一旦我们发现了一个 bug,我们可以使用-v 命令来提高提示程度并获得更多细节。

代码语言:javascript
复制
debug with logs -vv
debug with traces for failing tests -vvv
debug with traces for all tests -vvvv

追踪(Traces)是一种非常强大的方式,可以仔细观察智能合约执行过程中发生的事情。这有点像在命令行上有Tenderly.co[5]

还有一个交互式调试器(debugger),这让我想起了 1990 年的 Linux 调试器。我没有经常使用,但这里有更多的说明:https://book.getfoundry.sh/forge/debugger.html

代码语言:javascript
复制
forge test --debug "testSomething"

可以在本地分叉(fork)一个区块链,然后关联外部智能合约一起测试我们的合约。如果你想与其他 defi 协议交互,例如 Uniswap,或者使用真实的市场数据执行压力测试,这很有用。

代码语言:javascript
复制
forge test --fork-url https://eth-mainnet.alchemyapi.io/v2/abc123alchmeyApiKey

Gas 优化

编译时的合约 Gas 报告可以通过 foundry.toml 配置来设置

代码语言:javascript
复制
gas_reports = ["MyContract", "MyContractFactory"]

然后用forge test -gas-report选项执行命令。

优化函数的一个方法是使用测试合约,并在修改前后进行快照对比:

代码语言:javascript
复制
forge snapshot --snap gas1.txt
// make some changes
forge snapshot --diff gas1.txt

这将提供之前的 Gas 报告和当前快照之间的差异。

用 Slither 进行安全分析

当涉及到智能合约安全时,Slither 绝不是一个简单的解决方案,但它是有用的,并提供了一些自动检查,如检查重入错误。为了使用 slither,我一般会切换到 WSL(linux 的 Windows 子系统),它可以用以下命令安装(注意 0.8.13 是目前 foundry 演示合约中使用的 solc 版本。你可以把它修改为 Solidity 文件中设置的任何版本)。

代码语言:javascript
复制
apt install python3-pip
pip3 install slither-analyzer
pip3 install solc-select
solc-select install 0.8.13
solc-select use 0.8.13

然后把以下复制到我们工作的主目录下一个叫 slither.config.json 的文件里

代码语言:javascript
复制
{
  "filter_paths": "lib",
  "solc_remaps": [
    "ds-test/=lib/ds-test/src/",
    "forge-std/=lib/forge-std/src/"
  ]
}

然后运行一个测试,使用

代码语言:javascript
复制
slither src/Contract.sol

在官方 readme 中有更多关于 slither 的信息:https://github.com/crytic/slither

Foundry 作弊代码

Foundry 有一套作弊代码,它可以对区块链的状态进行修改,以方便在测试时使用。这些代码可以直接执行合约:0x7109709ECfa91a80626fF3989D68f67F5b1DD12D 进行调用,但更多时候是通过标准库和 vm 对象执行。

重要的作弊代码有:

  • vm.warp(uint256) external; 设置 block.timestamp
  • vm.roll(uint256) external; 设置 block.height.
  • vm.prank(address) external; 设置地址作为下一次调用的 msg.sender
  • vm.startPrank(address) external; 设置地址作为所有后续调用的 msg.sender
  • vm.stopPrank() external; 重置后续调用 msg.sender 为address(this)
  • vm.deal(address, uint256) external; 设置一个地址的余额,参数:(who,newBalance)。
  • vm.expectRevert(bytes calldata) external; 期待下次调用时出现错误。
  • vm.record() external; 记录所有存储的读和写。
  • vm.expectEmit(true, false, false, false); emit Transfer(address(this)); transfer(); 检查事件主题 1 在两个事件中是否相等
  • vm.load(address,bytes32)外部返回(bytes32); 从一个地址加载一个存储槽
  • vm.store(address,bytes32,bytes32) external; 将一个值存储到一个地址的存储槽中,参数(who, slot, value)。

这些可以用来改变测试的过程,如在这个例子中,告诉测试套件在调用时期望一个标准的算术错误。

代码语言:javascript
复制
vm.expectRevert(stdError.arithmeticError);

完整的列表在这里:https://github.com/foundry-rs/forge-std/blob/master/src/Vm.sol

部署和使用合约

Foundry 也可以用来部署并与智能合约交互。

要部署一个合约,我们可以使用下面的命令:

代码语言:javascript
复制
forge create --rpc-url https://mainnet.infura.io --private-key abc123456789 src/MyContract.sol:MyContract --constructor-args "Hello Foundry" "Arg2"

注意我们在生产中部署时不应该使用硬编码的私钥。一个选择是使用-ledger 或-trezor 来通过硬件钱包执行。另外,也可以使用环境变量来存储私钥。

另一种环境变量来存储私钥:Linux/Mac: --privateKey $privateKey.

代码语言:javascript
复制
export privateKey=abc123

Windows Powershell:

--privateKey $env:privateKey

代码语言:javascript
复制
$env:privateKey = "0x123abc"

私钥不应该包含 0x 前缀,否则你会得到一个错误 "Invalid character ‘x’ at position 1(无效字符'x'在位置 1)"

我们还可以使用 forge 命令在 etherscan 上验证合约,以便我们能够使用 Etherscan 的 UI 和 Metamask 与之交互。

代码语言:javascript
复制
forge 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

也可以使用 forge 来扁平化合约,其中包括外部合约的依赖关系合并到一个文件中:

代码语言:javascript
复制
forge flatten --output src/MyContract.flattened.sol src/MyContract.sol

要生成 ABI,可以使用以下命令:

代码语言:javascript
复制
forge inspect src/MyContract.sol abi

注意任何 ABI 都可以转换为接口并直接在 solidity 中使用:https://gnidan.github.io/abi-to-sol/

如果合约已经被验证,我们也可以使用以下命令来生成一个接口。

代码语言:javascript
复制
cast interface 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984

用 Cast 交互

我们可以 call 方式调用合约请求链上数据。我们也可以提供凭证(私钥)来发送一个交易,就像我们在 metamask 中签署一个交易一样。

代码语言:javascript
复制
cast call 0xabc123 "totalSupply()(uint256)" --rpc-url https://eth-mainnet.alchemyapi.io

cast send 0xabc123 "mint(uint256)" 3 --rpc-url https://eth-mainnet.alchemyapi.io --private-key=abc123

一旦区块被确认,也可以获取交易本身的信息:

代码语言:javascript
复制
cast tx 0xa1588a7c58a0ac632a9c7389b205f3999b7caee67ecb918d07b80f859aa605fd

也可以通过 flashbots 保护执行交易,cast 使用--flashbots 参数发送交易。

最后,你可能想估算一个 Gas 成本,这也可以用 cast 来完成:

代码语言:javascript
复制
cast estimate 0xabc123 "mint(uint256)" 3 --rpc-url https://eth-mainnet.alchemyapi.io --private-key=abc123

Foundry 为测试和审计智能合约提供了一个快速、高效的框架。我希望这个教程是有用的,如果想进一步深入了解,别忘了查看官方文档。https://book.getfoundry.sh[6]


本翻译由 Duet Protocol[7] 赞助支持。

原文:https://jamesbachini.com/foundry-tutorial/

参考资料

[1]

登链翻译计划: https://github.com/lbc-team/Pioneer

[2]

翻译小组: https://learnblockchain.cn/people/412

[3]

Tiny 熊: https://learnblockchain.cn/people/15

[4]

Hardhat: https://hardhat.org/

[5]

Tenderly.co: https://tenderly.co/

[6]

https://book.getfoundry.sh: https://book.getfoundry.sh/

[7]

Duet Protocol: https://duet.finance/?utm_souce=learnblockchain

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-08-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 深入浅出区块链技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Foundry 简介
    • 为什么用 Foundry?
    • 开始使用 Foundry
      • 安装说明
        • 使用 foundry 的第一步
        • 如何使用 Hardhat 设置 Foundry
        • 使用 Solidity 测试
          • Gas 优化
            • 用 Slither 进行安全分析
            • Foundry 作弊代码
            • 部署和使用合约
              • 用 Cast 交互
                • 参考资料
                相关产品与服务
                容器服务
                腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档