0


Foundry 使用教程和单元测试示例

Foundry 是一个用 Rust 编写的以太坊应用开发工具包,具有极速、可移植和模块化的特点。

Foundry 包括以下组件:

  • Forge:以太坊测试框架(类似于 Truffle、Hardhat 和 DappTools)。
  • Cast:用于与 EVM 智能合约交互、发送交易和获取链上数据的瑞士军刀工具。
  • Anvil:本地以太坊节点,类似于 Ganache 和 Hardhat Network。
  • Chisel:快速、实用且详细的 Solidity REPL。

我们这篇文章不会单纯重复文档的内容,而是关注最常使用的部分。

Foundry 安装和基本使用

Foundry 安装指南

  1. curl -L https://foundry.paradigm.xyz | bash
  2. foundryup

使用

  1. forge -h

或 手册
查看相关命令:

新建项目

  1. forge init

里面的 foundry.toml 是 Forge 项目中的配置文件。在 foundry.toml 中使用 solc 配置编译器版本:

  1. [profile.default]
  2. src = 'src'
  3. out = 'out'
  4. libs = ['lib']
  5. solc = "0.8.x"

编译

  1. forge build

测试

  1. # 运行所有测试
  2. forge test
  3. # 单独运行匹配前缀为 `CounterTest` 的单元测试
  4. forge test --match-contract CounterTest
  5. # 单独运行 `CounterTest` 的单元测试中的测试用例 `test_Increment` (match 同样是匹配前缀)
  6. forge test --match-contract CounterTest --match-test test_Increment
  7. # 当合约内容有变动时,就会重新运行所有的单元测试
  8. forge test --watch

依赖包
比如 transmissions11/solmate 依赖包:

  1. # 安装
  2. forge install transmissions11/solmate
  3. # 移除
  4. forge remove lib/solmate
  5. # 更新
  6. forge update lib/solmate

如何让 VSCode 能识别到依赖包里的合约,使用:

  1. forge remappings > remappings.txt

如果还是不能识别,尝试重启 VSCode。

覆盖测试

如果你在 Foundry 项目中运行

  1. forge coverage

,会看到一张表,显示代码的行覆盖率和分支覆盖率。

想更直观的显示对应代码行,可以使用以下方法:

安装

  1. lcov

:

  1. brew install lcov

在 Foundry 项目中创建 coverage 目录:

  1. mkdir coverage

运行以下命令:

  1. forge coverage --report lcov
  2. genhtml lcov.info --branch-coverage --output-dir coverage

如果有错误,可尝试:

  1. genhtml --ignore-errors inconsistent,corrupt lcov.info --branch-coverage --output-dir coverage

最终打开

  1. coverage/index.html

中的网页:

另外,也可以安装 VSCode 插件

  1. Coverage Gutters

使用命令生成覆盖测试报告:

  1. forge coverage --report lcov

然后在 VSCode 右键选择

  1. Coverage Gutters

中的

  1. Display Coverage

单元测试

Assert 断言

在单元测试中,往往需要使用断言。以下是调用

  1. forge init

后提供的默认测试文件。

  1. // SPDX-License-Identifier: UNLICENSED
  2. pragma solidity ^0.8.13;
  3. import {Test, console} from "forge-std/Test.sol";
  4. import {Counter} from "../src/Counter.sol";
  5. contract CounterTest is Test {
  6. Counter public counter;
  7. function setUp() public {
  8. counter = new Counter();
  9. counter.setNumber(0);
  10. }
  11. function test_Increment() public {
  12. counter.increment();
  13. assertEq(counter.number(), 1);
  14. }
  15. function testFuzz_SetNumber(uint256 x) public {
  16. counter.setNumber(x);
  17. assertEq(counter.number(), x);
  18. }
  19. }
  1. setUp()

函数部署了正在测试的合约,每次测试用例的执行会默认执行

  1. setUp()

。任何以

  1. test

开头的函数都会被执行作为单元测试。

以下是可以使用的断言:

  • assertEq:断言相等
  • assertLt:断言小于
  • assertLe:断言小于或等于
  • assertGt:断言大于
  • assertGe:断言大于或等于
  • assertTrue:断言为真

断言的前两个参数是要进行比较的值,但你也可以添加一个错误消息作为第三个参数,建议总是这么做。以下是编写断言的推荐方式:

  1. function test_Increment() public {
  2. counter.increment();
  3. assertEq(counter.number(), 1, "expect x to equal to 1");
  4. }

使用

  1. vm.prank

改变

  1. msg.sender

Foundry 通过一种“欺骗码”

  1. vm.prank

提供了一种方法来更改 sender。

下面是一个简单示例:

  1. function test_ChangeOwner() public {
  2. vm.prank(owner);
  3. contractToTest.changeOwner(newOwner);
  4. assertEq(contractToTest.owner(), newOwner);
  5. }
  1. vm.prank

仅对其后立即发生的交易有效。如果你希望之后的一系列交易都使用相同的地址,可以使用

  1. vm.startPrank

开始,并用

  1. vm.stopPrank

结束。

  1. function testMultipleTransactions() public {
  2. vm.startPrank(owner);
  3. // 作为 owner 地址发送的交易
  4. vm.stopPrank();
  5. }
在 Foundry 中定义账户和地址

上面的

  1. owner

变量可以通过几种方式定义:

  1. address owner = address(1234);
  2. address owner = 0x0d8dA6BF26964aF9D7eEd9e03E53415D37aA96045;
  3. address owner = vm.addr(privateKey);
更改
  1. msg.sender

  1. tx.origin

如果不只需要更改

  1. msg.sender

还需要更改

  1. tx.origin

,那么

  1. vm.prank

  1. vm.startPrank

可以选择性地接受两个参数,其中第二个参数是

  1. tx.origin

  1. vm.prank(msgSender, txOrigin);

检查余额

Foundry 中检查余额非常简单,因为它是用 Solidity 编写的。

比如以下合约:

  1. contract Deposit {
  2. event Deposited(address indexed);
  3. function buyerDeposit() external payable {
  4. require(msg.value == 1 ether, "incorrect amount");
  5. emit Deposited(msg.sender);
  6. }
  7. }

测试函数如下:

  1. function test_BuyerDeposit() public {
  2. uint256 balanceBefore = address(depositContract).balance;
  3. depositContract.buyerDeposit{value: 1 ether}();
  4. uint256 balanceAfter = address(depositContract).balance;
  5. assertEq(balanceAfter - balanceBefore, 1 ether, "expect increase of 1 ether");
  6. }

使用

  1. vm.expectRevert

预期 revert

上面的测试在当前形式下存在的问题是,如果您删除

  1. require

语句,测试仍会通过。让我们改进测试,使得删除

  1. require

语句会导致测试失败。

还是使用以上的例子,需要测试

  1. buyerDeposit()

函数中的

  1. require

导致 revert 的情况。

  1. vm.expectRevert

在期望发生 revert 之前调用。

  1. function test_BuyerDepositWrongPrice() public {
  2. vm.expectRevert("incorrect amount");
  3. depositContract.buyerDeposit{value: 1 ether + 1 wei}();
  4. vm.expectRevert("incorrect amount");
  5. depositContract.buyerDeposit{value: 1 ether - 1 wei}();
  6. }
测试自定义 error

以下是使用了自定义 error 的合约。

  1. contract CustomErrorContract {
  2. error SomeError(uint256);
  3. function revertError(uint256 x) public pure {
  4. revert SomeError(x);
  5. }
  6. }

测试函数如下:

  1. error SomeError(uint256);
  2. function test_Revert() public {
  3. vm.expectRevert(abi.encodeWithSelector(SomeError.selector, 6));
  4. customErrorContract.revertError(6);
  5. }

使用

  1. vm.expectEmit

测试事件

还是使用以上的例子,需要测试

  1. buyerDeposit()

函数中的

  1. Deposited

事件。

  1. vm.expectEmit

的使用有些反直觉,你需要在测试文件中写一个同样的事件,并且按照以下例子中的顺序编写。

  1. event Deposited(address indexed);
  2. function test_BuyerDepositEvent() public {
  3. vm.expectEmit();
  4. emit Deposited(buyer);
  5. depositContract.buyerDeposit{value: 1 ether}();
  6. }

使用

  1. vm.warp

调整

  1. block.timestamp

现在我们考虑一个带有时间锁的提现场景:买家存款,卖家可以在 3 天后提取付款。

  1. contract Deposit {
  2. address public seller;
  3. mapping(address => uint256) public depositTime;
  4. event Deposited(address indexed);
  5. event SellerWithdraw(address indexed, uint256 indexed);
  6. constructor(address _seller) {
  7. seller = _seller;
  8. }
  9. function buyerDeposit() external payable {
  10. require(msg.value == 1 ether, "incorrect amount");
  11. uint256 _depositTime = depositTime[msg.sender];
  12. require(_depositTime == 0, "already deposited");
  13. depositTime[msg.sender] = block.timestamp;
  14. emit Deposited(msg.sender);
  15. }
  16. function sellerWithdraw(address buyer) external {
  17. require(msg.sender == seller, "not the seller");
  18. uint256 _depositTime = depositTime[buyer];
  19. require(_depositTime != 0, "buyer did not deposit");
  20. require(block.timestamp - _depositTime > 3 days, "refund period not passed");
  21. delete depositTime[buyer];
  22. emit SellerWithdraw(buyer, block.timestamp);
  23. (bool ok, ) = msg.sender.call{value: 1 ether}("");
  24. require(ok, "seller did not withdraw");
  25. }
  26. }

我们想要测试卖家不能在存款后的 3 天内提取资金。

注意,

  1. block.timestamp

默认从 1 开始。所以我们应该首先使用

  1. vm.warp(x)

调整到当前时间。

这是使用

  1. vm.warp

调整时间的方式,但因为每个测试用例一般都需要修改时间,我们可以使用修饰符:

  1. modifier startAtPresentDay() {
  2. vm.warp(1729072888);
  3. _;
  4. }
测试文件示例
  1. // SPDX-License-Identifier: UNLICENSED
  2. pragma solidity ^0.8.13;
  3. import {Test, console} from "forge-std/Test.sol";
  4. import {Deposit} from "../src/Deposit.sol";
  5. contract DepositTest is Test {
  6. Deposit public deposit;
  7. Deposit public faildeposit;
  8. address constant SELLER = address(0x5E11E7);
  9. RejectTransaction private rejector;
  10. event Deposited(address indexed);
  11. event SellerWithdraw(address indexed, uint256 indexed);
  12. function setUp() public {
  13. deposit = new Deposit(SELLER);
  14. rejector = new RejectTransaction();
  15. faildeposit = new Deposit(address(rejector));
  16. }
  17. modifier startAtPresentDay() {
  18. vm.warp(1729072888);
  19. _;
  20. }
  21. address public buyer = address(this);
  22. // 测试卖家不能在买家存款后的 3 天之内提款。
  23. function testBuyerDepositSellerWithdrawBefore3days() public startAtPresentDay {
  24. vm.startPrank(buyer);
  25. deposit.buyerDeposit{value: 1 ether}();
  26. assertEq(address(deposit).balance, 1 ether, "Contract balance did not increase");
  27. vm.stopPrank();
  28. vm.startPrank(SELLER);
  29. vm.warp(1729072888 + 2 days);
  30. vm.expectRevert();
  31. deposit.sellerWithdraw(address(this));
  32. }
  33. }

使用

  1. vm.roll

调整

  1. block.number

如果你想要调整

  1. block.number

,可以使用:

  1. vm.roll(blockNumber)

要向前推进一定数量的区块,可以这样做:

  1. vm.roll(block.number() + numberOfBlocks)

测试失败的 ETH 转账

在之前的例子中,卖家在 3 天后可以提取 ETH,如果我们要测试合约转 ETH 转账失败的情况,需要一定的技巧来达到完整的代码覆盖率。

我们可以编写一个

  1. RejectTransaction

合约,使得不能接收 ETH 的转入。

  1. contract RejectTransaction {
  2. receive() external payable {
  3. revert("Revert");
  4. }
  5. }

以下是测试

  1. require(ok...)

失败的测试用例:

  1. function testRejectedWithdrawl() public startAtPresentDay {
  2. vm.startPrank(buyer); // msg.sender == buyer
  3. faildeposit.buyerDeposit{value: 1 ether}();
  4. vm.stopPrank();
  5. assertEq(address(faildeposit).balance, 1 ether, "assertion failed");
  6. vm.warp(1729072888 + 3 days + 1 seconds); // 3 days and 1 second later...
  7. vm.startPrank(address(rejector)); // msg.sender == rejector
  8. vm.expectRevert();
  9. faildeposit.sellerWithdraw(buyer);
  10. vm.stopPrank();
  11. }

模糊测试

虽然我们可以使用错误的卖家地址来调用

  1. sellerWithdraw()

,但是如果系统可以自动尝试不同的值会更好。当测试用例有传参时,Foundry 会自动测试多个不同的值。为了防止随机到不合适的值,可以使用

  1. vm.assume

。使用

  1. testFuzz

作为名称开头。

  1. function testFuzzInvalidSellerAddress(address notSeller) public {
  2. vm.assume(notSeller != SELLER);
  3. vm.startPrank(notSeller);
  4. vm.expectRevert("not the seller");
  5. deposit.sellerWithdraw(buyer);
  6. vm.stopPrank();
  7. }

使用 Foundry 进行

  1. console.log

调试

需要确保导入了

  1. console

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

然后用以下命令运行测试:

  1. forge test -vv

测试签名

以下是使用 OZ 库,创建和验证 ECDSA 签名的例子。

Verifier.sol

  1. // SPDX-License-Identifier: MIT
  2. pragma solidity ^0.8.0;
  3. import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
  4. contract Verifier {
  5. using ECDSA for bytes32;
  6. address public verifyingAddress;
  7. constructor(address _verifyingAddress) {
  8. verifyingAddress = _verifyingAddress;
  9. }
  10. function verifyV1(
  11. string calldata message,
  12. bytes32 r,
  13. bytes32 s,
  14. uint8 v
  15. ) public view {
  16. bytes32 signedMessageHash = keccak256(abi.encode(message))
  17. .toEthSignedMessageHash();
  18. require(
  19. signedMessageHash.recover(v, r, s) == verifyingAddress,
  20. "signature not valid v1"
  21. );
  22. }
  23. function verifyV2(
  24. string calldata message,
  25. bytes calldata signature
  26. ) public view {
  27. bytes32 signedMessageHash = keccak256(abi.encode(message))
  28. .toEthSignedMessageHash();
  29. require(
  30. signedMessageHash.recover(signature) == verifyingAddress,
  31. "signature not valid v2"
  32. );
  33. }
  34. }

Verifier.t.sol

  1. // SPDX-License-Identifier: MIT
  2. pragma solidity ^0.8.0;
  3. import {Test, console} from "forge-std/Test.sol";
  4. import {Verifier} from "../src/Verifier.sol";
  5. import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
  6. contract TestSigs1 is Test {
  7. using ECDSA for bytes32;
  8. Verifier verifier;
  9. address owner;
  10. uint256 privateKey =
  11. 0x1010101010101010101010101010101010101010101010101010101010101010;
  12. function setUp() public {
  13. owner = vm.addr(privateKey);
  14. verifier = new Verifier(owner);
  15. }
  16. function testVerifyV1andV2() public {
  17. string memory message = "attack at dawn";
  18. bytes32 msgHash = keccak256(abi.encode(message))
  19. .toEthSignedMessageHash();
  20. (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, msgHash);
  21. bytes memory signature = abi.encodePacked(r, s, v);
  22. assertEq(signature.length, 65);
  23. console.logBytes(signature);
  24. verifier.verifyV1(message, r, s, v);
  25. verifier.verifyV2(message, signature);
  26. }
  27. }

测试 Solidity 的 internal 函数

要测试

  1. internal

函数,只能再写一个合约继承原来的合约,使用

  1. external

函数调用

  1. internal

函数。比如,以下的例子:

  1. contract InternalFunction {
  2. uint256 private constant REWARD_RATE_PER_SECOND = 1e18;
  3. function calculateReward(uint256 depositTime) internal view returns (uint256 reward) {
  4. reward = (block.timestamp - depositTime) * REWARD_RATE_PER_SECOND;
  5. }
  6. }
  7. contract InternalFunctionHarness is InternalFunction {
  8. function calculateReward_HARNESS(uint256 depositTime) external view returns (uint256 reward) {
  9. reward = calculateReward(depositTime);
  10. }
  11. }

如果想要测试

  1. private

函数,可以考虑修改成

  1. internal

函数,这对 gas 并无影响。

使用

  1. vm.deal

  1. vm.hoax

设置地址余额

  1. vm.hoax

允许你同时设置地址余额并伪造调用者身份。

  1. vm.hoax(addressToPrank, balanceToGive);
  2. // next call is a prank for addressToPrank
  3. vm.deal(alice, balanceToGive);

Fork 网络后测试

有时我们需要主网上的数据,我们可以 fork 指定的链,再跑测试。

在 .env 中保存主网 RPC。

  1. MAINNET_RPC=
  1. // SPDX-License-Identifier: UNLICENSED
  2. pragma solidity ^0.8.13;
  3. import {Test, console} from "forge-std/Test.sol";
  4. import {Counter} from "../src/Counter.sol";
  5. contract CounterTest is Test {
  6. uint256 mainnetFork;
  7. string mainnetRPC = vm.envString("MAINNET_RPC");
  8. Counter public counter;
  9. function setUp() public {
  10. mainnetFork = vm.createFork(mainnetRPC);
  11. vm.selectFork(mainnetFork);
  12. counter = new Counter();
  13. counter.setNumber(0);
  14. }
  15. function testActiveFort() public {
  16. assertEq(vm.activeFork(), mainnetFork);
  17. }
  18. }

也可以使用命令,指定 fork 的网络和指定区块号

  1. forge test --match-contract CounterTest --fork-url <RPC_URL> --fork-block-number <BLOCK_NUMBER>

部署 与 Verify 合约

部署到本地

启动本地节点:

  1. anvil
  2. // 获取 rpc http://127.0.0.1:8545

新建终端,再部署合约:

  1. forge create --rpc-url http://127.0.0.1:8545 --constructor-args 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 src/Deposit.sol:Deposit
  2. // --constructor-args 为构造函数参数

部署到链上

命令行部署到链上并且验证合约
  1. forge create
  2. --rpc-url <RPC_URL>
  3. --constructor-args <CONSTRUCTOR_ARGS>
  4. --private-key <PK>
  5. --etherscan-api-key <ETHERSCAN_API_KEY>
  6. --verify src/Deposit.sol:Deposit

或者在 foundry.toml 文件中配置 etherscan 的 API KEY:

  1. [etherscan]
  2. sepolia = { key = "${ETHERSCAN_API_KEY}" }

并且在 .env 中存放 ETHERSCAN_API_KEY:

  1. ETHERSCAN_API_KEY=

申请 etherscan API

脚本部署

在 .env 中保存 RPC 和 私钥:

  1. SEPOLIA_RPC_URL=
  2. PRIVATE_KEY=0x...

编辑 foundry.toml 文件:

  1. [rpc_endpoints]
  2. sepolia = "${SEPOLIA_RPC_URL}"
  3. local = "http://127.0.0.1:8545"

在 script 目录下创建脚本 Deposit.s.sol:

  1. // SPDX-License-Identifier: UNLICENSED
  2. pragma solidity ^0.8.13;
  3. import {Script, console} from "forge-std/Script.sol";
  4. import {Deposit} from "../src/Deposit.sol";
  5. contract DepositScript is Script {
  6. Deposit public deposit;
  7. function setUp() public {}
  8. function run() public {
  9. uint256 privateKey = vm.envUint("PRIVATE_KEY");
  10. address deployerAddress = vm.addr(privateKey);
  11. vm.startBroadcast(privateKey);
  12. deposit = new Deposit(deployerAddress);
  13. console.log("Deposit deployed on %s", address(deposit));
  14. vm.stopBroadcast();
  15. }
  16. }

Cast 命令行工具

Chain

  1. # 获取余额
  2. cast balance --rpc-url <RPC_URL> <address>
  3. # 获取 chain ID
  4. cast chain-id --rpc-url <RPC_URL>
  5. # 获取该节点使用的客户端软件的类型和版本信息
  6. cast client --rpc-url <RPC_URL>

Block

  1. export mainnet=https://eth.llamarpc.com
  2. # 获取最新区块号
  3. cast block-number --rpc-url $mainnet
  4. # 获取区块出块时间,默认最新,也可指定区块
  5. cast age --rpc-url $mainnet
  6. cast age --rpc-url $mainnet 1
  7. # 根据时间戳获取区块
  8. cast find-block --rpc-url $mainnet 1729072888
  9. # 获取区块内容
  10. cast block --rpc-url $mainnet
  11. cast block --rpc-url $mainnet 20990795
  12. cast block --rpc-url $mainnet --json
  13. cast block --rpc-url $mainnet --field number
  14. cast block --rpc-url $mainnet --field hash
  15. cast block --rpc-url $mainnet pending
  16. cast block --rpc-url $mainnet --full
  17. # 获取当前 gas 价格
  18. cast gas-price --rpc-url $mainnet
  19. # 获取 basefee
  20. cast base-fee --rpc-url $mainnet
  21. cast base-fee --rpc-url $mainnet 20990795

ABI

  1. export mainnet=https://eth.llamarpc.com
  2. # 对参数进行编码和解码
  3. cast abi-encode "transfer(address, uint256)" 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 10000
  4. cast --abi-decode "transfer()(address, uint256)" 0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000002710
  5. # 对调用方法和参数编码和解码
  6. cast calldata "transfer(address, uint256)" 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 10000
  7. cast --calldata-decode "transfer(address, uint256)" 0xa9059cbb000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000002710
  8. cast pretty-calldata 0xa9059cbb000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000002710
  9. cast 4byte-decode 0xa9059cbb000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000002710
  10. # 对调用方法解码
  11. cast 4byte 0xa9059cbb
  12. # 对事件 Topics 进行解码
  13. cast 4byte-event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
  14. # 将字符串转换成 bytes32
  15. cast --format-bytes32-string "hello world"
  16. # 将字节转换成字符串 string
  17. cast --parse-bytes32-string "0x68656c6c6f20776f726c64000000000000000000000000000000000000000000"
  18. # 将字符串转换成 utf8
  19. cast --from-utf8 "hello world"
  20. # 将 ascii 转换成字符串
  21. cast --to-ascii "0x68656c6c6f20776f726c64"
  22. # 将整数转换成 32 字节
  23. cast --to-uint256 10

Account

  1. # 获取余额
  2. cast balance --rpc-url $mainnet vitalik.eth
  3. cast balance --rpc-url $mainnet --ether 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
  4. # 获取 nonce 值
  5. cast nonce --rpc-url $mainnet 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
  6. # 获取 ENS
  7. cast lookup-address --rpc-url $mainnet 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
  8. # 获取 ENS 对应地址
  9. cast resolve-name --rpc-url $mainnet vitalik.eth
  10. # 获取存储槽 slot 数据
  11. cast storage --rpc-url $mainnet 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 0
  12. # 获取合约的 bytescode
  13. cast code --rpc-url $mainnet 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2
  14. # 获取合约源代码
  15. export ETHERSCAN_API_KEY=
  16. cast etherscan-source 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
  17. cast etherscan-source -d weth 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2

Transation

开启

  1. anvil

,以下命令是在本地节点运行。如果需要对测试网或者主网进行操作,需要增加 --rpc-url。

  1. export privateKey=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
  2. # 转账
  3. cast send --private-key $privateKey 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 --value 10ether
  4. # 获取账号余额
  5. cast balance 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 --ether
  6. # 创建合约
  7. forge create --private-key $privateKey src/Counter.sol:Counter
  8. # 调用合约方法
  9. cast send --private-key $privateKey 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 "setNumber(uint256)" 100
  10. # 调用 static 合约方法
  11. cast call 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 "number()(uint256)"
  12. # 获取 Transation 信息
  13. cast tx 0x914610aa6c9bb7659a9b5b8ae1c0575b4dfce51226a01ecd1180d7553977d0e1

Wallet

  1. # 创建钱包方式一
  2. cast wallet new
  3. # 创建钱包方式二
  4. mkdir keystore
  5. cast wallet new keystore
  6. # 根据 json 钱包获取地址
  7. cast wallet address --keystore 1c0ac11a-d844-4bf6-be36-81f2da400f19
  8. # 签名
  9. export privateKey=0x113d463b15d61eb6df9182a7c45c8b952a9768b7a84e1d4e471731c271360ca6
  10. cast wallet sign --private-key $privateKey "hello"
  11. # 验签
  12. cast wallet verify --address 0xEF9D0359bD4Ade81386C49e91D3dB75c2b75A1C8 "hello" 0x8b3c393dcea9794ad7aebe10436b9cdaea6efef3841cee59aae38e5ccaf1fd33105aa97a838b92e50d28e65828e7483f14f857bdb1e10d51e05a8769bc50f20b1c
  13. # 生成靓号
  14. cast wallet vanity --starts-with 00 --ends-with 00

到此结束,大家也可以,https://t.me/gtokentool

标签: 单元测试

本文转载自: https://blog.csdn.net/2408_87746709/article/details/143067318
版权归原作者 加密新世界 所有, 如有侵权,请联系我们删除。

“Foundry 使用教程和单元测试示例”的评论:

还没有评论