使用 Foundry 开发环境

  • Tiny熊
  • 更新于 2023-01-11 10:44
  • 阅读 6918

Foundry是一个全新的EVM开发环境。有原生的Solidity编写测试能力及强大的命令行工具支持。

Foundry是一个全新的EVM开发环境。有了Solidity-native测试能力(使用原生的Solidity编写测试),强大的命令行工具和高性能的Rust工具,Foundry更值得大家学习,翻译一篇 Foundry 的使用指南文章。

登链社区也组织翻译了 Foundry 文档: https://learnblockchain.cn/docs/foundry/i18n/zh/

img

安装

官方安装指南可以在这里找到。不过我自己在用foundryup获得anvil时遇到了麻烦,所以如果你想在bash/zsh shell上从源码构建,请使用以下方法。注意你需要先安装有gitcargo才能进行源代码构建。

git clone https://github.com/foundry-rs/foundry && \
    cd foundry && \
    cargo install --path ./cli --bins --locked --force && \
    cargo install --path ./anvil --locked --force

如果你选择了从源码构建,那么就需要花费给你一些精力,编译器有很多事情要做。

Foundry 包含的组件

Foundry由三个不同的命令行工具(CLI)组成,包括forgecast,和anvil。首先,我们先了解下这些工具,然后用他们来构建和测试一个智能合约。

Cast

Cast是一个CLI工具,用于对兼容以太坊虚拟机(EVM)的区块链进行RPC调用。使用cast,我们可以进行合约调用,查询数据,并处理编码和解码。cast有很多的子命令,所以要想获得完整的参考,请看Foundry书中的cast一节!

要执行合约调用而不发布交易,我们可以使用call子命令:

cast call 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 \
    "balanceOf(address)(uint256)" \
    0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 \
    --rpc-url https://rpc.flashbots.net

这对0xC0...c2(WETH)地址执行balanceOf(address)查询,传递0xf3...66地址作为参数,并将返回数据解码为uint256类型的值。在这个和下面的cast子命令中,我们明确地使用了Flashbots RPC,因为默认是http://localhost:8545

要查询一个地址的以太余额,我们可以使用balance子命令:

cast balance 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 \
    --rpc-url https://rpc.flashbots.net

我们也可以用ENS名称查询余额。

cast balance yourmom.eth --rpc-url https://rpc.flashbots.net

要提取合约上的合约状态槽内容,我们可以使用storage子命令。

cast storage 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 0 \
    --rpc-url https://rpc.flashbots.net

这将从0xC0...c2(WETH)地址中获得存储槽0内容。

Forge

Forge是一个CLI工具,用于构建、测试、模糊测试、部署和验证Solidity合约。Forge同样有很多的子命令,所以这里是参考文档! 顺便说一下,如果你是来学习教程的,不需要运行这些命令,所有东西都包含在后面 创建 Foundry 仓库部分。

一个常用的子命令是 init,它可以初始化一个新的版本库:

forge init my_gigabrain_protocol

install子命令允许你安装指定版本的依赖项:

forge install Rari-Capital/solmate@v6

这将安装由Rari-Capital拥有的solmate软件库,特别是v6。注意,@v6是可选的。

为了运行测试,我们可以使用以下方法:

forge test

Anvil

Anvil是一个CLI工具,用于运行本地EVM区块链。它可以与ganache和hardhat节点相媲美,但好像更快。

要启动Anvil,只需使用avil命令:

anvil

你也可以指定一些参数, 用-v来显示详细日志,如果用-fork-url <FORK_URL>指定URL来分叉一个公共网络,等等。要查看 anvil选项的完整列表,可以使用以下命令:

anvil -h

创建 Foundry 仓库

初始化

为了开始工作,如上所述,我们将使用以下命令:

forge init my_token && cd my_token

这将创建一个my_token目录,初始化一个git仓库,添加一个GitHub工作流目录,安装forge-std包,生成一个foundry.toml文件,一个test目录,一个src目录,最后进入到my_token目录。

让我们继续,通过运行下面的程序删除现有的合约:

rm src/Contract.sol

现在,首先让我们看看foundry.toml文件,自动生成的文件应该是这样的:

[default]
src = 'src'
out = 'out'
libs = ['lib']

这将合约源代码目录设置为src,编译器输出目录设置为out,库的重新映射设置为lib

安装依赖

现在我们将安装一个依赖关系。我们可以使用<所有者>/<存储库>模式来安装Rari-Capitalsolmate

forge install Rari-Capital/solmate

这样就把solmate安装到了lib目录。现在让我们在src/MyToken.sol中创建一个ERC20合约。

我们可以导入并使用solmate的ERC20实现,如下:

// SPDX-License-Identifier: AGPLv3
pragma solidity ^0.8.13;

import {ERC20} from "solmate/tokens/ERC20.sol";

error NotMinter();

contract MyToken is ERC20 {
    address public immutable minter;

    // ERC20(name, symbol, decimals)
    contructor() ERC20("MyToken", "MyT", 18) {
        minter == msg.sender;
    }

    function mint(uint256 amount) external {
        if (msg.sender != minter) revert NotMinter();
        _mint(minter, amount);
    }
}

所以这是一个符合ERC20标准的代币,有一个简单的访问控制功能,用于铸币控制。

注意solmate/tokens/ERC20.sol导入是根据foundry.toml文件中指定的libs/目录重新映射的。

如果你使用VSCode并得到错误信息,可以尝试在项目根目录下创建一个remappings.txt文件并添加以下内容:

solmate/=lib/solmate/src/
forge-std/=lib/forge-std/src

编译

让我们继续编译合约:

foundry build

测试

现在我们需要写一些测试,因为一个掌管着不可忽视的价值的智能合约实际上是一个非自愿的bug赏金。不要让自己成为一个教训。:)

为了写测试,在test中创建一个文件。让我们创建一个名为test/MyToken.t.sol的文件。.t.sol表示这将是一个测试文件:

// SPDX-License-Identifier: AGPLv3
pragma solidity ^0.8.13;

import {Test} from "forge-std/Test.sol";
import {MyToken} from "../MyToken.sol";

contract MyTokenTest {
    MyToken internal myToken;
    address internal constant alice = address(1);
    address internal constant bob = address(2);

    function setUp() external {
        vm.prank(alice);
        myToken = new MyToken();
    }

    function testMint() external {
        vm.prank(alice);

        myToken.mint(1);

        assertEq(myToken.balanceOf(alice), 1);
    }

    function testFailMint() external {
        vm.prank(bob);

        myToken.mint(1);
    }
}

这里有一些代码需要解释:

forge-std导入的 Test默认是在 forge创建 repo 时一起的生成的。它包括一个内部的vm变量,这是一个 cheat-code运行器。

vm包括一系列强大的“作弊代码”,例如:时间戳操纵 、将字节码刻在地址上、存储槽读写等。要查看所有这些代码,请看参考这里的介绍。在这个例子中,我们使用vm.prank(address),其中的address参数将是下一个外部合约调用的msg.sender

要测试一个函数,在运行测试的函数的名称前加上test。在本例中,我们正在测试mint,所以我们可以叫它testMint。如果它不是以test开头,它将不会在forge test上运行。assertEq函数用来断言两个值是相等的。在Test合约中还声明了其他断言,可以在这里找到参考。

要测试还原(revert),在函数名前加上testFail。在本例中,我们期望vm.prank(bob)失败,因为在setUp中,MyTokenmintervm.prank(alice)设置成了alice

现在我们来运行测试:

forge test

一切都应该通过。如果你需要调试函数调用,请在测试命令中加入 -vvvv (verbosity 4)。

forge test -vvvv

如果你需要从测试内部记录变量,你可以在Test中声明的事件。使用emit log_uint(uint)记录数字,你可以用emit log_named_uint(string,uint)来进行标记。要显示事件日志,在运行forge test时使用-vv(verbosity 2)。

本地部署

为了在本地部署合约,我们需要首先启动一个anvil实例。

anvil

当本地devnet开始运行时,这应该会打印出一些账户。让anvil运行实例单独使用一个终端窗口。

现在让我们从 anvil的输出中获取第一个账户的私钥,并将其设置为$PRIV_KEY环境变量。这不是必须的,它只是保持事情清晰。

export \
PRIV_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80

注意这些私钥是确定的,并且是公开的,所以在公共网络上投入资金之前要三思而后行。

现在验证私钥是否可以访问:

echo $PRIV_KEY

如果它打印出私钥,你就可以了。

现在把合约部署到本地 devnet 上:

forge create src/MyToken.sol:MyToken --private-key=$PRIV_KEY

这将加载到环境中的私钥,使用src/MyToken.sol文件中MyToken创建合约。请注意语法使用了明确指定的文件名称和合约名称:

forge create <filename>:<contractname> ...

在这种情况下,我们不需要构造器参数,但如果需要,则在最后传递--constructor-args标志,并写出构造器参数,用空格隔开:

forge create Filename.sol:Contractname \
    --private-key $PRIV \
    --constructor-args arg0 arg1 arg2

运行后,你应该在终端上打印出类似这样的东西。注意,你的合约地址和交易哈希值可能不同。

Deployer: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
Deployed to: 0x5fbdb2315678afecb367f032d93f642f64180aa3

Transaction hash: 0x3f849ddc766b4851fed8798a26aa6fe527c74cd9d6f2639ec289f5e11ceaa3b5

很好,现在我们可以使用cast来试用我们新创建的代币了。

同样,为了保持整洁,在终端环境中把你的合约地址导出为$CON_ADDRESS。如果你的地址不同,只需在下面的命令中替换它。

export CON_ADDRESS=0x5fbdb2315678afecb367f032d93f642f64180aa3

现在我们来查询token的.name()

cast call $CON_ADDRESS "name():(string)"

注意我们在函数签名中包括:(string)。这是为了帮助cast解码返回的数据,否则我们会得到一个巨大的十六进制字符串。

现在让我们使用部署该函数的同一私钥来调用mint函数。如果你使用任何其他的私钥则会失败,因为这是mint函数中的逻辑。

cast send --private-key $PRIV_KEY $CON_ADDRESS "mint(uint256)" 1

这会给我们的账户铸造1个代币,你也会注意到交易数据已经打印到屏幕上。真不错。

作为本地部署的最后验证,检查你账户的余额。如果你使用的私钥不是由 anvil 提供的,你可以随时使用以下方法:

cast wallet address --private-key $PRIV_KEY

再一次,为了方便,只需将钱包地址添加到环境中:

export WALLET=0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266

现在进行余额查询。

cast call $CON_ADDRESS "balanceOf(address):uint256" $WALLET

这应该返回1。

公共网络部署

部署在公共网络的工作原理与上述相同,当然是使用你自己的私钥、RPC端点、链ID。

合约代码验证

要用Etherscan验证你公开部署的合约,请使用以下命令:

forge verify-contract \
    --chain $CHAIN_ID \
    --compiler-version $COMPILER_VERSION \
    $CON_ADDRESS src/MyToken.sol:MyToken $ETHERSCAN_API_KEY

总结

Foundry 很强大,而且我们还只是从表面上看到了代工厂的兔子洞有多深。我希望这篇文章对你的编程和智能合约之旅有所帮助,并一如既往地做好黑客工作🤘。


原文:https://medium.com/@jtriley15/the-foundry-evm-development-environment-f198f2e4c372

本翻译由 Decert.me 赞助支持。

Foundry 框架合约开发测试 测试对 Foundry 开发框架的了解,主要涉及的内容有编译、测试、部署、以及如何进行代码开源验证。
开始挑战
点赞 2
收藏 5
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

2 条评论

请先 登录 后评论
Tiny熊
Tiny熊
0xD682...E8AB
登链社区发起人 通过区块链技术让世界变得更好而尽一份力。