Foundry使用备忘录

说明

有关Foundry的基本使用,已经在Foundry入门以及实战部署中做过简单的介绍和演示。

cast

cast主要是和链交互的工具。官方文档

RPC请求

cast rpc eth_blockNumber --rpc-url=$ETH_RPC_URL,表示请求以太坊的区块高度。

区块查询

  • cast block-number --rpc-url=$ETH_RPC_URL 表示查询以太坊的区块高度,等价于cast rpc eth_blockNumber --rpc-url=$ETH_RPC_URL
  • cast block block-number --rpc-url=$ETH_RPC_URL 表示查询以太坊的区块高度对应的区块信息。
  • cast block latest 表示查询最新的区块信息。

交易查询

  • cast tx tx-hash --rpc-url=$ETH_RPC_URL 表示查询以太坊的交易信息。
  • cast receipt tx-hash --rpc-url=$ETH_RPC_URL 表示查询以太坊的交易回执信息。
  • cast tx tx-hash gasPrice --rpc-url=$ETH_RPC_URL 表示查询以太坊的交易的gasPrice。其他的字段也可以查询。
  • cast tx tx-hash --rpc-url=$ETH_RPC_URL --jsonjson格式输出交易信息。
  • cast pretty-calldata calldata, 解析交易的calldata
    1
    2
    3
    4
    5
    6
    7
    $ cast pretty-calldata 0xa9059cbb000000000000000000000000e78388b4ce79068e89bf8aa7f218ef6b9ab0e9d00000000000000000000000000000000000000000000000000174b37380cea000

    Possible methods:
    - transfer(address,uint256)
    ------------
    [0]: 000000000000000000000000e78388b4ce79068e89bf8aa7f218ef6b9ab0e9d0
    [1]: 0000000000000000000000000000000000000000000000000174b37380cea000
  • cast 4byte selector,通过selector查询对应的方法。
    1
    2
    3
    4
    $ cast 4byte 0xa9059cbb

    Possible methods:
    - transfer(address,uint256)
  • cast keccak function-sig 通过function-sig计算selector
    1
    2
    3
    $ cast keccak "transfer(address,uint256)"

    0xa9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b
  • cast sig function-sig, 通过function-sig计算selector
    1
    2
    3
    $ cast sig "transfer(address,uint256)"

    0xa9059cbb
  • cast 4buye-event event_hash, 根据event hash获得event的信息。
    1
    2
    3
    4
    $ cast 4byte-event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925

    Possible events:
    - Approval(address,address,uint256)
  • cast run tx-hash, 模拟运行,运行结果会显示调用栈信息,速度可能会比较慢,因为要下载访问链上的数据。
    1
    2
    3
    4
    5
    6
    7
    $ cast run 0x0e0b0c0d0f0e0b0c0d0f0e0b0c0d0f0e0b0c0d0f0e0b0c0d0f0e0b0c0d0f0e0b
    Traces:
    [29962] 0xc02a…6cc2::transfer(0x40950267d12e979ad42974be5ac9a7e452f9505e, 105667789681831058)
    ├─ emit Transfer(param0: 0xc564ee9f21ed8a2d8e7e76c085740d5e4c5fafbe, param1: 0x40950267d12e979ad42974be5ac9a7e452f9505e, param2: 105667789681831058)
    └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001
    Transaction successfully executed.
    Gas used: 51618
  • cast run tx-hash --debug调试链上合约的情况。

合约交互

  • cast etherscan-source contract_address --etherscan-api-key=$ETHERSCAN_API_KEY -d /path/to/dir,通过etherscanapi获取合约的源码, 保存到/path/to/dir目录下。
    1
    $ cast etherscan-source 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 --etherscan-api-key=$ETHERSCAN_API_KEY
  • cast storage address contract_address stroage_index, 查询合约的stroage_index的值。
    1
    2
    $ cast storage 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 0
    0x577261707065642045746865720000000000000000000000000000000000001a
  • cast interface /path/to/abifile 通过abi文件获取合约的接口

账户管理

  • cast account new,创建一个新的账户。
  • cast account new path/to/file, 创建一个新的账户,并保存到指定的路径。
  • cast wallet sign "abc" -i, 通过私钥签名交易, -i表示通过交互式输入私钥。
  • cast wallet verify

编码解码

  • cast --to-dec 0x1234,表示将十六进制转换为十进制
  • cast --to-hex , 将十进制换转为十六进制
  • cast --to-unit,
  • cast --to-wei
  • cast --to-rip, 序列化
  • cast --from-rip , 反序列化

anvil

官方文档

  • anvil 在本地模拟节点,同时会在本地生成10个账户。
  • anvil --fork-url=$ETH_RPC_URL, fork其他网络节点。

forge

官方文档

  • forge init hello-foundry,初始化一个名为hello-foudry的项目。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    $forge init hello-foundry

    └── hello-foundry
    ├── foundry.toml
    ├── lib
    │   └── forge-std
    ├── script
    │   └── Counter.s.sol
    ├── src
    │   └── Counter.sol
    └── test
    └── Counter.t.sol

  • forge build,编译合约,编译结果会保存到out目录下。
  • forge build -w ,watch模式,如果sol文件发生变化,会自动编译。

依赖管理

  • forge install <gh_user/gh_repo>, 安装依赖。比如forge install openzeppelin/openzeppelin-contracts --no-commit

forge test

在开发过程中使用forget test对合约进行测试,这也是foundry最为强大的功能之一。
Counter合约为例,Counter.t.sol文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "../src/Counter.sol";

contract CounterTest is Test {
Counter public counter;
function setUp() public {
counter = new Counter();
counter.setNumber(0);
}

function testIncrement() public {
counter.increment();
assertEq(counter.number(), 1);
}

function testSetNumber(uint256 x) public {
counter.setNumber(x);
assertEq(counter.number(), x);
}
}

测试代码需要注意两点:

  • 测试合约都会继承Test合约,测试一般都会有setUp(),每个测试方法运行之前会调用setUp()方法
  • 所有的测试函数都是以test开头的,测试函数的参数是public,测试函数的返回值是public
  • 每个测试方法是相互独立的,互不影响
  • 对于存在参数的测试方法,是一个模糊测试,会随机选择参数调用测试

forge test --vvv,显示test函数的详细信息;

普通测试

普通测试是指测试合约的方法,不涉及交易,不涉及账户,不涉及gas,也不需要参数

1
2
3
4
5
```solidity
function testIncrement() public {
counter.increment();
assertEq(counter.number(), 1);
}

模糊测试

模糊测试是指测试合约的方法,不涉及交易,不涉及账户,不涉及gas,但是需要参数,参数是由foundry框架自动输入。

1
2
3
4
function testSetNumber(uint256 x) public {
counter.setNumber(x);
assertEq(counter.number(), x);
}

invariant测试

invariant测试,顾名思义就是不变量测试。invariant测试的方法需要以invariant。测试框架会随意调用合约中的方法,看看是否是有不满足不变量的情况。

1
2
3
function invariant_sometest() public {
assertTrue(counter.number() <= type(uint128).max);
}

在上述例子中,我们测试了counter.number()是否小于uint128的最大值。实际测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Failing tests:
Encountered 1 failing test in test/Counter.t.sol:CounterTest
[FAIL. Reason: Assertion failed.]
[Sequence]
sender=0x6c721e4a80a47202fecdcfd48b87d8eb60e9fa0f addr=[src/Counter.sol:Counter]0xce71065d4017f316ec606fe4422e11eb2c47c246 calldata=increment(), args=[]
sender=0x0000000000000000000000000000000000000039 addr=[src/Counter.sol:Counter]0xce71065d4017f316ec606fe4422e11eb2c47c246 calldata=setNumber(uint256), args=[2]
sender=0x0000000000000000000000000000000000000103 addr=[src/Counter.sol:Counter]0xce71065d4017f316ec606fe4422e11eb2c47c246 calldata=increment(), args=[]
sender=0xefbe7959b7f6b42d3e185a91aa63c8fd710b30cd addr=[src/Counter.sol:Counter]0xce71065d4017f316ec606fe4422e11eb2c47c246 calldata=setNumber(uint256), args=[811]
sender=0x8a0a7cb7aaf1f2a3a1069eba99ae0e7204380276 addr=[src/Counter.sol:Counter]0xce71065d4017f316ec606fe4422e11eb2c47c246 calldata=setNumber(uint256), args=[1]
sender=0x00000000000000000000000000000000000001e1 addr=[src/Counter.sol:Counter]0xce71065d4017f316ec606fe4422e11eb2c47c246 calldata=increment(), args=[]
sender=0x0000000000000000000000000000000000000060 addr=[src/Counter.sol:Counter]0xce71065d4017f316ec606fe4422e11eb2c47c246 calldata=increment(), args=[]
sender=0x3fab184622dc19b6109349b94811493bf2a45362 addr=[src/Counter.sol:Counter]0xce71065d4017f316ec606fe4422e11eb2c47c246 calldata=increment(), args=[]
sender=0x00000000000000000000000000000000000005d0 addr=[src/Counter.sol:Counter]0xce71065d4017f316ec606fe4422e11eb2c47c246 calldata=setNumber(uint256), args=[2054]
sender=0x00000000000000000000000000000000000000d2 addr=[src/Counter.sol:Counter]0xce71065d4017f316ec606fe4422e11eb2c47c246 calldata=increment(), args=[]
sender=0x0000000000000000000000000000000000000807 addr=[src/Counter.sol:Counter]0xce71065d4017f316ec606fe4422e11eb2c47c246 calldata=setNumber(uint256), args=[115792089237316195423570985008687907853269984665640564039457584007913129639934]

会发现当counter.number()大于uint128的最大值时,就会出现不满足不变量的情况,测试用例不通过。如果修改为如下:

1
2
3
function invariant_sometest() public {
assertTrue(counter.number() <= type(uint256).max);
}

合约中的number最大值必然不会超过uint256的最大值,测试用例就会通过。

ffi测试

主要是用于本地合约算法算出来的内容和外部工具计算出来的内容是否一致,从而验证算法的有效性。ffi测试需要注意两点:

  • ffi测试需要借助与vm.ffi(cmd),用于执行外部命令,返回结果。
  • 测试时,需要加上--ffi参数。例如forge test -vvv --ffi
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    function testCheatcode() public {
    string memory message = "abc";
    bytes32 hash = keccak256(abi.encodePacked(message));
    console2.logBytes32(hash);

    string[] memory cmd = new string[](3);
    // 传入外部命令
    cmd[0] = "cast";
    cmd[1] = "keccak";
    cmd[2] = message;
    // 执行外部命令
    bytes memory result = vm.ffi(cmd);
    bytes32 hash1 = abi.decode(result, (bytes32));

    assertEq(hash, hash1);
    }
    ---------------------output----------
    [PASS] testCheatcode() (gas: 8555)
    Logs:
    0x4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45

    Test result: ok. 1 passed; 0 failed; finished in 16.28ms
    可以看到测试通过,表示外部工具计算出来的结果和合约中的结果一致。

Cheatcode

官方参考文档
foundry在测试用例中为我们提供了一些内置的合约,可以方便我们打印一些信息。常用的是console2emit log。比如:

1
2
3
4
function test_log() public {
console2.log("use console2");
emit log("use emit log");
}

输出结果如下:

1
2
3
4
[PASS] test_log() (gas: 4939)
Logs:
use console2
use emit log

foundry在测试用例中,可以利用vm变量,修改合约中的状态。vm中存在很多方法:

  • vm.warp(int).改变block timestamp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function testCheatcode() public {
    console2.log("before",block.timestamp);
    vm.warp(1000); // 改变block timestamp
    console2.log("after",block.timestamp);
    }
    ---------------------output----------
    [PASS] testCheatcode() (gas: 6887)
    Logs:
    before 1
    after 1000
  • vm.roll(int) 改变 block number

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function testCheatcode() public {
    console2.log("before",block.number);
    vm.roll(1000); // 改变block number
    console2.log("after",block.number);
    }
    ---------------------output----------
    [PASS] testCheatcode() (gas: 6887)
    Logs:
    before 1
    after 1000
  • vm.prank() 改变下一次合约调用的msg.sender

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    contract CounterTest is Test {
    Counter public counter;
    Helper public h;
    address public alice;
    function setUp() public {
    counter = new Counter();
    counter.setNumber(0);

    h = new Helper();
    alice = address(1);
    }

    function testCheatcode() public {
    vm.prank(alice);
    address caller = h.whocalled();
    console2.log("caller", caller);

    address caller2 = h.whocalled();
    console2.log("caller2", caller2);
    }

    }

    contract Helper {
    function whocalled() public returns (address) {
    return msg.sender;
    }
    }
    ---------------------output----------
    Running 1 test for test/Counter.t.sol:CounterTest
    [PASS] testCheatcode() (gas: 14908)
    Logs:
    caller 0x0000000000000000000000000000000000000001
    caller2 0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84
  • startPrank(address),stopPrank() 包含在这两个语句之间的合约调用的msg.sender都会被改变

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function testCheatcode() public {
    vm.startPrank(alice);
    address caller = h.whocalled();
    console2.log("caller", caller);

    address caller2 = h.whocalled();
    console2.log("caller2", caller2);

    vm.stopPrank();
    address caller3 = h.whocalled();
    console2.log("caller3", caller3);
    }
    ---------------------output----------
    [PASS] testCheatcode() (gas: 14908)
    Logs:
    caller 0x0000000000000000000000000000000000000001
    caller2 0x0000000000000000000000000000000000000001
    caller3 0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84
  • vm.deal(address,1ether),修改address余额为1ether

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function testCheatcode() public {
    console2.log("before:",alice.balance);
    vm.deal(alice,1 ether);
    console2.log("after:",alice.balance);
    }
    ---------------------output----------
    Running 1 test for test/Counter.t.sol:CounterTest
    [PASS] testCheatcode() (gas: 9481)
    Logs:
    before: 0
    after: 1000000000000000000
  • deal(address token,address to,uint256 give), 给用户发送某个erc20的token.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.13;

    import "forge-std/Test.sol";
    import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
    import "../src/Counter.sol";

    contract CounterTest is Test {
    Counter public counter;
    Helper public h;
    address public alice;
    IERC20 public dai;

    function setUp() public {
    counter = new Counter();
    counter.setNumber(1);

    h = new Helper();
    alice = address(10086);
    dai = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
    }

    function testCheatcode() public {
    console2.log("before:",dai.balanceOf(alice));
    deal(address(dai),alice,1 ether);
    console2.log("after:",dai.balanceOf(alice));
    }

    }

    contract Helper {
    function whocalled() public returns (address) {
    return msg.sender;
    }
    }

    因为我们没有在本地部署dai合约,所以我们在进行forge test时,需要使用链上的rpc。

    1
    2
    3
    4
    5
    6
    7
    forge test -vvv --fork-url=$ETH_RPC_URL
    ---------------------output----------
    Running 1 test for test/Counter.t.sol:CounterTest
    [PASS] testCheatcode() (gas: 153636)
    Logs:
    before: 0
    after: 1000000000000000000
  • vm.envString(env_name) 从环境变量中获取内容

    1
    2
    3
    4
    5
    6
    7
    8
    function testCheatcode() public {
    string memory rpc = vm.envString("ETH_RPC_URL");
    console2.log("rpc from env",rpc);
    }
    ---------------------output----------
    [PASS] testCheatcode() (gas: 7311)
    Logs:
    rpc from env your_rpc_url
  • vm.selectFork(forkId) 在这条语句后面都是多会处在forkId所在的rpc的环境里。使用selectFork好处就是可以在一份测试代码里对不同的rpc测试。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
        function testCheatcode() public {
    string memory rpc = vm.envString("ETH_RPC_URL");
    console2.log("rpc from env",rpc);
    uint256 mainnet = vm.createFork(rpc);
    vm.selectFork(mainnet);

    console2.log("before:",dai.balanceOf(alice));
    deal(address(dai),alice,1 ether);
    console2.log("after:",dai.balanceOf(alice));

    }
    ---------------------output----------
    forge test -vvv # 由于我们已经在代码中通过selectFork指定了rpc,所以测试时就不需要再指定rpc了
    [PASS] testCheatcode() (gas: 156409)
    Logs:
    rpc from env your_rpc_url
    before: 0
    after: 1000000000000000000
  • vm.rollFork(block_number) 指定需要fork的区块高度

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function testCheatcode() public {
    string memory rpc = vm.envString("ETH_RPC_URL");
    uint256 mainnet = vm.createFork(rpc);
    vm.selectFork(mainnet);
    vm.rollFork(150000);
    console2.log(block.number);
    }
    ---------------------output----------
    [PASS] testCheatcode() (gas: 7976)
    Logs:
    150000
  • vm.expectEmit(bool,bool,bool,bool)表示对event中的index0,index1,index2,data进行测试。如果不需要测试,就将bool设置为false.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    pragma solidity 0.8.10;

    import "forge-std/Test.sol";

    contract EmitContractTest is Test {
    event Transfer(address indexed from, address indexed to, uint256 amount);

    function testExpectEmit() public {
    ExpectEmit emitter = new ExpectEmit();
    // Check that topic 1, topic 2, and data are the same as the following emitted event.
    // Checking topic 3 here doesn't matter, because `Transfer` only has 2 indexed topics.
    vm.expectEmit(true, true, false, true);
    // The event we expect
    emit Transfer(address(this), address(1337), 1337);
    // The event we get
    emitter.t();
    }

    function testExpectEmitDoNotCheckData() public {
    ExpectEmit emitter = new ExpectEmit();
    // Check topic 1 and topic 2, but do not check data
    vm.expectEmit(true, true, false, false);
    // The event we expect
    emit Transfer(address(this), address(1337), 1338);
    // The event we get
    emitter.t();
    }
    }

    contract ExpectEmit {
    event Transfer(address indexed from, address indexed to, uint256 amount);

    function t() public {
    emit Transfer(msg.sender, address(1337), 1337);
    }
    }

    ---------------------output----------
    [PASS] testEmit() (gas: 72582)
    Test result: ok. 1 passed; 0 failed; finished in 324.91µs

    测试通过。

  • vm.expectRevert(message) ,用于判断revert的错误是否和message一致,这种主要是用于测试revert报错的内容。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    contract CounterTest is Test {
    Helper public h;
    function setUp() public {
    h = new Helper();
    }
    function testRevert() public {
    vm.expectRevert("some reason");
    h.revertIt();
    }
    }

    contract Helper {
    function revertIt() public {
    revert("some reason");
    }
    }
    ---------------------output----------
    [PASS] testRevert() (gas: 8321)
    Test result: ok. 1 passed; 0 failed; finished in 295.85µs

    测试通过,表示revert的原因与预期的一致。

forget script

常规使用

社区的规范,是将script的脚本宣布全部放在scripts目录下,以.s.sol结尾。

  • 和测试脚本一样,也会存在一个setUp()方法。函数运行前会调用setUp()函数。
  • 默认创建的项目中的scipts中会存在一个Counter.s.sol的脚本,我们可以直接使用。
  • 运行方式 forge script script/xxx.s.sol
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.13;

    import "forge-std/Script.sol";

    contract CounterScript is Script {
    function setUp() public {}

    function run() public {
    vm.broadcast();
    }
    }
    例如,我们修改为:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    contract CounterScript is Script {
    function setUp() public {
    console2.log("in setUp");
    }

    function run() public {
    console2.log("in run");
    }
    }
    ---------------------output----------
    $ forge script scripts/Counter.s.sol
    Script ran successfully.
    Gas used: 25870

    == Logs ==
    in setUp
    in run

指定运行函数

除此之外,forge script还可以通过--sig指定运行函数。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
contract CounterScript is Script {
function setUp() public {
console2.log("in setUp");
}

function run() public {
console2.log("in run");
}

function someFunction() public {
console2.log("in some function");
}
}
---------------------output----------
$ forge script script/Counter.s.sol --sig="someFunction()"
Script ran successfully.
Gas used: 27380

== Logs ==
in setUp
in some function

传入参数

如果我们需要运行的方法需要参数,我们也可以手动传入参数。这一点与测试函数不一样,测试函数是采用fuzzing测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
contract CounterScript is Script {
function setUp() public {
console2.log("in setUp");
}

function run() public {
console2.log("in run");
}

function someFunction(uint256 x) public {
console2.log("in some function");
console2.log(x);
}
}
---------------------output----------
forge script script/Counter.s.sol --sig="someFunction(uint256)" 10
Script ran successfully.
Gas used: 26466

== Logs ==
in setUp
in some function
10

部署合约

目前forge script 最常用的功能是用于部署合约。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
contract CounterScript is Script {
function setUp() public {
console2.log("in setUp");
}

function run() public {
vm.startBroadcast();

Counter c = new Counter();

vm.stopBroadcast();
}

}

vm.startBroadcast()vm.stopBroadcast();中的调用信息会被记录下来。根据这些调用信息,将合约部署上链。
forge script script/Counter.s.sol -vvvv --rpc-url=http://127.0.0.1:8545 在本地部合约。查看调用详情:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Traces: 
# 这些表示是案子本地模拟的结果
[119377] CounterScript::run()
├─ [0] VM::startBroadcast()
│ └─ ← ()
├─ [49499] → new Counter@"0x731a…a4b1"
│ └─ ← 247 bytes of code
├─ [0] VM::stopBroadcast()
│ └─ ← ()
└─ ← ()


Script ran successfully.

== Logs ==
in setUp
==========================
# 这些是在链上模拟的结果
Simulated On-chain Traces:

[106719] → new Counter@"0x731a…a4b1"
└─ ← 247 bytes of code


==========================

Estimated total gas used for script: 138734

Estimated amount required: 0.00069367 ETH

==========================

SIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more.

Transactions saved to: /path/to/broadcast/Counter.s.sol/31337/run-latest.json

如果确认没有问题,需要实际部署合约时,需要加上--broadcast参数。如下所示:

1
forge script script/Counter.s.sol -vvvv --rpc-url=rpc-url --broadcast --private-key=private-key

如果在本地成功部署之后。forge script运行的结果是:

1
2
3
4
5
6
7
8
Waiting for receipts.
⠉ [00:00:00] [#################################################################################] 1/1 receipts (0.0s)
#####
✅ Hash: 0x98f1137d22797fa42197038cde25c43b3038c66cb3adbe0ebb88c1ab7a69b186
Contract Address: 0x5fbdb2315678afecb367f032d93f642f64180aa3
Block: 1
Paid: 0.000413631032874675 ETH (106719 gas * 3.875889325 gwei)

anvil的结果会输出:

1
2
3
4
5
6
7
Transaction: 0x98f1137d22797fa42197038cde25c43b3038c66cb3adbe0ebb88c1ab7a69b186
Contract created: 0x5fbdb2315678afecb367f032d93f642f64180aa3
Gas used: 106719

Block Number: 1
Block Hash: 0xcf242b03f49996e42fa29bd13666da6c4fee111d8ff5fbab8c51deb329f3764c
Block Time: "Mon, 12 Sep 2022 07:49:26 +0000"

forge script结果输出和anvil的输出是一致的。所有的部署信息会保存在broadcast目录下。最终以json的格式保存,比如本次部署的结果就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
{
"transactions": [
{
"hash": "0x98f1137d22797fa42197038cde25c43b3038c66cb3adbe0ebb88c1ab7a69b186",
"transactionType": "CREATE",
"contractName": "Counter",
"contractAddress": "0x5fbdb2315678afecb367f032d93f642f64180aa3",
"function": null,
"arguments": null,
"transaction": {
"type": "0x02",
"from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"gas": "0x21dee",
"value": "0x0",
"data": "0x608060405234801561001057600080fd5b5060f78061001f6000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c80633fb5c1cb1460415780638381f58a146053578063d09de08a14606d575b600080fd5b6051604c3660046083565b600055565b005b605b60005481565b60405190815260200160405180910390f35b6051600080549080607c83609b565b9190505550565b600060208284031215609457600080fd5b5035919050565b60006001820160ba57634e487b7160e01b600052601160045260246000fd5b506001019056fea2646970667358221220f4a9b22e7a2d64c24355b4e7a6f8c62115ca728f26fc2a1e98e364ee91f794fa64736f6c63430008100033",
"nonce": "0x0",
"accessList": []
},
"additionalContracts": []
}
],
"receipts": [
{
"transactionHash": "0x98f1137d22797fa42197038cde25c43b3038c66cb3adbe0ebb88c1ab7a69b186",
"transactionIndex": "0x0",
"blockHash": "0xcf242b03f49996e42fa29bd13666da6c4fee111d8ff5fbab8c51deb329f3764c",
"blockNumber": "0x1",
"from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"to": null,
"cumulativeGasUsed": "0x1a0df",
"gasUsed": "0x1a0df",
"contractAddress": "0x5fbdb2315678afecb367f032d93f642f64180aa3",
"logs": [],
"status": "0x1",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"effectiveGasPrice": "0xe70560ad"
}
],
"libraries": [],
"pending": [],
"returns": {},
"timestamp": 1662968966,
"commit": "b56b6b2"
}
调试合约

在部署合约之前,我们需要先调试合约。调试合约的命令是forge script script/Counter.s.sol --debug。调用完成之后就会出来一个字符界面的调试窗口。从上到下以此是:

  • 最上面的是evm字节码PC寄存器,每一步消耗的gas
  • 其次是evm stack保存的数据
  • 然后是evm memory保存的数据
  • 最后对应的就是sol代码

通过上下键运行选择字节码运行。通过这种方式学习evm字节码以及字节码的优化是非常有帮助的。这条命令和cast run tx-hash --debug调试界面是一样的。只不过通过forge script 调试的是本地合约。cast调试的是链上合约。

forge inspect

默认情况下,forge build的输出结果都会保存在output目录下。如果需要查看output目录下的合约的信息,可以使用forge inspect命令。如下所示:

  • forge inspect Counter mi,查看合约的方法标识符
    1
    2
    3
    4
    5
    6
    $ forge inspect Counter mi
    {
    "increment()": "d09de08a",
    "number()": "8381f58a",
    "setNumber(uint256)": "3fb5c1cb"
    }
  • forge inspect Counter storage 查看storage
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    $ forge inspect Counter storage
    {
    "storage": [
    {
    "astId": 23201,
    "contract": "src/Counter.sol:Counter",
    "label": "number",
    "offset": 0,
    "slot": "0",
    "type": "t_uint256"
    }
    ],
    "types": {
    "t_uint256": {
    "encoding": "inplace",
    "label": "uint256",
    "numberOfBytes": "32"
    }
    }
    }
  • forge inspect Counter abi, 查看合约的abi。

其他

  • forge test --gas-report 查看每个方法消耗的gas情况;
  • forge install <gh_user/gh_repo> 安装依赖
  • forge update <dep> 更新依赖
  • forge remove <dep> 删除依赖
  • forge coverage 查看测试覆盖率
  • forge fmt 格式化代码

参考

  1. https://www.youtube.com/watch?v=EXYeltwvftw
  2. https://www.youtube.com/watch?v=tIsRR-ekmgU
文章作者: Oooverflow
文章链接: https://www.oooverflow.com/2022/09/12/foundry-momo/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Oooverflow