原链接:https://www.learnblockchain.cn/article/9606#ABI
根据上面的blog,亲自实践操作下Foundry,并做了些详细的注释和更正了原博主的一些错误;以此记录; 基于foundry默认初始化项目Counter为例;项目名test;
# 运行所有测试
forge test
# 单独运行匹配前缀为 `CounterTest` 的单元测试
forge test --match-contract CounterTest
# 单独运行 `CounterTest` 的单元测试中的测试用例 `test_Increment` (match 同样是匹配前缀)
forge test --match-contract CounterTest --match-test test_Increment
forge test --match-test test_Increment
# 当合约内容有变动时,保存后就会重新运行所有的单元测试
forge test --watch
forge test -vv 是 Foundry 测试命令的一部分,v 的数量决定了输出的详细程度,具体如下:
执行 forge coverage ,<font style="color:rgb(51, 51, 51);">显示代码的行覆盖率和分支覆盖率的表格;</font>
wushu@whd-asr MINGW64 /d/dapp/test (master)
$ forge coverage
Warning: optimizer settings have been disabled for accurate coverage reports, if you encounter "stack too deep" errors, consider using `--ir-minimum` which enables viaIR with minimum optimization resolving most of the errors
[⠊] Compiling...
[⠢] Compiling 23 files with Solc 0.8.28
[⠰] Solc 0.8.28 finished in 4.10s
Compiler run successful!
Analysing contracts...
Running tests...
Ran 3 tests for test/Counter.t.sol:CounterTest
[PASS] testFuzz_SetNumber(uint256) (runs: 256, μ: 32043, ~: 32354)
[PASS] testFuzz_SetNumber2(uint256) (runs: 256, μ: 32064, ~: 32375)
[PASS] test_Increment() (gas: 31851)
Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 33.70ms (64.64ms CPU time)
Ran 1 test suite in 35.91ms (33.70ms CPU time): 3 tests passed, 0 failed, 0 skipped (3 total tests)
╭----------------------+---------------+---------------+---------------+---------------╮
| File | % Lines | % Statements | % Branches | % Funcs |
+======================================================================================+
| script/Counter.s.sol | 0.00% (0/5) | 0.00% (0/3) | 100.00% (0/0) | 0.00% (0/2) |
|----------------------+---------------+---------------+---------------+---------------|
| src/Counter.sol | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) |
|----------------------+---------------+---------------+---------------+---------------|
| Total | 44.44% (4/9) | 40.00% (2/5) | 100.00% (0/0) | 50.00% (2/4) |
╰----------------------+---------------+---------------+---------------+---------------╯
<font style="color:rgb(51, 51, 51);">更直观的显示对应代码行:可以使用 forge coverage --report lcov</font>
wushu@whd-asr MINGW64 /d/dapp/test (master)
$ forge coverage --report lcov
Warning: optimizer settings have been disabled for accurate coverage reports, if you encounter "stack too deep" errors, consider using `--ir-minimum` which enables viaIR with minimum optimization resolving most of the errors
[⠊] Compiling...
[⠢] Compiling 23 files with Solc 0.8.28
[⠔] Solc 0.8.28 finished in 2.19s
Compiler run successful!
Analysing contracts...
Running tests...
Ran 3 tests for test/Counter.t.sol:CounterTest
[PASS] testFuzz_SetNumber(uint256) (runs: 256, μ: 32043, ~: 32354)
[PASS] testFuzz_SetNumber2(uint256) (runs: 256, μ: 32064, ~: 32375)
[PASS] test_Increment() (gas: 31851)
Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 19.11ms (31.26ms CPU time)
Ran 1 test suite in 21.03ms (19.11ms CPU time): 3 tests passed, 0 failed, 0 skipped (3 total tests)
Wrote LCOV report.
会在根目录下生成 lcov.info 文件
TN:
SF:script/Counter.s.sol
DA:12,0
FN:12,CounterScript.setUp
FNDA:0,CounterScript.setUp
DA:14,0
FN:14,CounterScript.run
FNDA:0,CounterScript.run
DA:16,0
DA:18,0
DA:20,0
FNF:2
FNH:0
LF:5
LH:0
BRF:0
BRH:0
end_of_record
TN:
SF:src/Counter.sol
DA:7,515
FN:7,Counter.setNumber
FNDA:515,Counter.setNumber
DA:8,515
DA:11,1
FN:11,Counter.increment
FNDA:1,Counter.increment
DA:12,1
FNF:2
FNH:2
LF:4
LH:4
BRF:0
BRH:0
end_of_record
vm.prank 是 Foundry 提供的一个测试工具,用于模拟某个地址调用合约函数。它可以改变 msg.sender,从而模拟不同的调用者行为,常用于权限管理相关的测试。
假设有个修改拥有者的函数需要测试;
// 改变拥有者,仅有拥有者才能替换
function changeOwner(address newOwner) public {
require(msg.sender == owner, "Not the owner");
owner = newOwner;
}
可以通过模拟拥有者来对其进行测试;
function test_ChangeOwner() public {
address newOwner = address(0x123);
// 模拟非 owner 调用,应该失败
vm.prank(newOwner);
// vm.expectRevert(string reason):预期调用会失败,并指定失败原因
vm.expectRevert("Not the owner");
counter.changeOwner(newOwner);
assertEq(counter.owner(), newOwner);
}
vm.prank(msgSender, txOrigin);
vm.startPrank
开始,并用 vm.stopPrank
结束;function test_ChangeOwner() public {
address newOwner = address(0x123);
vm.startPrank(newOwner);
vm.expectRevert("Not the owner");
counter.changeOwner(newOwner);
// 还要其他交易都要验证
vm.stopPrank();
assertEq(counter.owner(), newOwner);
}
contract Deposit {
event Deposited(address indexed);
function buyerDeposit() external payable {
require(msg.value == 1 ether, "incorrect amount");
emit Deposited(msg.sender);
}
}
测试函数
function test_BuyerDeposit() public {
uint256 balanceBefore = address(depositContract).balance;
depositContract.buyerDeposit{value: 1 ether}();
uint256 balanceAfter = address(depositContract).balance;
assertEq(balanceAfter - balanceBefore, 1 ether, "expect increase of 1 ether");
}
vm.deal 和 vm.hoax 是 Foundry 提供的虚拟机工具,用于在测试中模拟账户余额和交易行为。它们的主要作用和使用场景如下:
vm.deal
vm.hoax
function testDeal() public {
address user = address(0x123);
uint256 initialBalance = 10 ether;
// 设置 user 的余额为 10 ether
vm.deal(user, initialBalance);
// 验证余额是否正确
assertEq(user.balance, initialBalance);
}
function testHoax() public {
address user = address(0x123);
uint256 initialBalance = 5 ether;
// 设置 user 的余额为 5 ether,并伪装后续调用由 user 发起
vm.hoax(user, initialBalance);
// 模拟 user 调用合约的函数
payable(address(this)).transfer(1 ether);
// 验证余额变化
assertEq(user.balance, initialBalance - 1 ether);
assertEq(address(this).balance, 1 ether);
}
vm.expectRevert 在测试中用于验证合约的错误处理逻辑是否符合预期,例如权限检查或输入验证。
function changeOwner(address newOwner) public {
require(msg.sender == owner, "Not the owner");
owner = newOwner;
}
测试函数
function test_ChangeOwner() public {
address newOwner = address(0x123);
// 模拟非 owner 调用,应该失败
vm.prank(newOwner);
vm.expectRevert("Not the owner");
counter.changeOwner(newOwner);
assertEq(counter.owner(), newOwner);
}
// 测试结果
Ran 1 test for test/Counter.t.sol:CounterTest
[FAIL: revert: Not the owner] test_ChangeOwner() (gas: 15679) 错误结果和预期错误结果一致
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 507.80µs (109.50µs CPU time)
//=================================================================
function test_ChangeOwner() public {
address newOwner = address(0x123);
// 模拟非 owner 调用,应该失败
vm.prank(newOwner);
vm.expectRevert("Not the owner+++");
counter.changeOwner(newOwner);
assertEq(counter.owner(), newOwner);
}
//测试结果
Ran 1 test for test/Counter.t.sol:CounterTest
[FAIL: Error != expected error: Not the owner != Not the owner+++] test_ChangeOwner() (gas: 12024) 错误结果和预期错误结果不一致
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 674.50µs (191.80µs CPU time)
contract CustomErrorContract {
error SomeError(uint256);
function revertError(uint256 x) public pure {
// 自定义错误如何测试
revert SomeError(x);
}
}
测试函数
// 测试合约中,同样自定义错误
error SomeError(uint256);
function test_Revert() public {
// revert返回的错误 和 这里预期错误进行比较
vm.expectRevert(abi.encodeWithSelector(SomeError.selector, 7));
customErrorContract.revertError(6);
}
// 结果
Ran 1 test for test/Counter.t.sol:CounterTest
[FAIL: Error != expected error: SomeError(6) != SomeError(7)] test_Revert() (gas: 9850)
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 516.80µs (142.00µs CPU time)
vm.expectEmit() 的作用是验证合约在执行过程中是否正确触发了指定的事件,并且可以选择性地验证事件的参数。
event Deposited(address indexed);
function buyerDeposit() external payable {
require(msg.value == 1 ether, "incorrect amount");
emit Deposited(msg.sender);
}
测试函数
vm.expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData):
function test_BuyerDepositEvent() public {
counter.buyerDeposit{value: 1 ether}();
}
// 测试结果;未测试事件
Ran 1 test for test/Counter.t.sol:CounterTest
[PASS] test_BuyerDepositEvent() (gas: 13217)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 763.60µs (111.80µs CPU time)
// =====================================================
function test_BuyerDepositEvent() public {
vm.expectEmit(true,false,false,false);
emit Deposited(msg.sender);
counter.buyerDeposit{value: 1 ether}();
}
// 测试结果 forge test --match-test test_BuyerDepositEvent
Ran 1 test for test/Counter.t.sol:CounterTest
[FAIL: log != expected log] test_BuyerDepositEvent() (gas: 17691)
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 1.14ms (224.30µs CPU time)
vm.warp 用于在测试中修改测试环境中区块的时间戳(block.timestamp)。它可以模拟时间的流逝,方便测试与时间相关的合约逻辑
假设有一个简单的时间锁合约,只有在特定时间之后才能提取资金:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
contract Timelock {
uint256 public unlockTime;
address public owner;
constructor(uint256 _unlockTime) {
unlockTime = _unlockTime;
owner = msg.sender;
}
function withdraw() external {
require(block.timestamp >= unlockTime, "Funds are locked");
require(msg.sender == owner, "Not the owner");
// 提取逻辑
}
}
测试代码
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Test} from "forge-std/Test.sol";
import {Timelock} from "../src/Timelock.sol";
contract TimelockTest is Test {
Timelock public timelock;
function setUp() public {
timelock = new Timelock(block.timestamp + 1 days);
}
function test_WithdrawAfterUnlock() public {
// 模拟时间流逝
vm.warp(block.timestamp + 1 days + 1 seconds);
// 测试提取逻辑
timelock.withdraw();
}
function test_WithdrawBeforeUnlock() public {
// 测试在解锁时间之前提取,应该失败
vm.expectRevert("Funds are locked");
timelock.withdraw();
}
}
vm.roll 用于在测试中修改当前区块高度(block.number)。它可以模拟区块的推进,方便测试与区块高度相关的合约逻辑;
假设有一个简单的合约,只有在特定区块高度之后才能执行某些操作:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
contract BlockLock {
uint256 public unlockBlock;
address public owner;
constructor(uint256 _unlockBlock) {
unlockBlock = _unlockBlock;
owner = msg.sender;
}
function execute() external {
require(block.number >= unlockBlock, "Block not reached");
require(msg.sender == owner, "Not the owner");
// 执行逻辑
}
}
测试代码
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Test} from "forge-std/Test.sol";
import {BlockLock} from "../src/BlockLock.sol";
contract BlockLockTest is Test {
BlockLock public blockLock;
function setUp() public {
blockLock = new BlockLock(block.number + 10);
}
function test_ExecuteAfterUnlockBlock() public {
// 模拟区块高度推进
vm.roll(block.number + 10);
// 测试执行逻辑
blockLock.execute();
}
function test_ExecuteBeforeUnlockBlock() public {
// 测试在解锁区块之前执行,应该失败
vm.expectRevert("Block not reached");
blockLock.execute();
}
}
vm.assume 用于在模糊测试(fuzz testing)中设置输入的约束条件。它可以限制模糊测试生成的输入数据范围,确保测试只在满足特定条件的情况下运行;
注意:如果 vm.assume 的条件过于严格,可能导致测试无法生成有效输入,从而跳过所有测试。
vm.assume(bool condition)
待测试函数
function isLargeNumber(uint256 x) public pure returns (bool) {
return x > 100;
}
测试代码
function testFuzz_IsLargeNumber(uint256 x) public view{
// 限制输入范围,小于100的会跳过
vm.assume(x > 100);
// 测试逻辑
assertTrue(example.isLargeNumber(x));
}
// 执行结果
Ran 1 test for test/Counter.t.sol:CounterTest
[PASS] test_IsLargeNumber(uint256) (runs: 256, μ: 9921, ~: 9821) 运行了256次,平均执行时间为9921,中位数执行时间为9821
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 13.27ms (12.74ms CPU time)
方式一:创建.env配置文件,并修改测试合约
// .env文件
MAINNET_RPC=
// 测试合约
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Test, console} from "forge-std/Test.sol";
import {Counter} from "../src/Counter.sol";
contract CounterTest is Test {
uint256 mainnetFork;
string mainnetRPC = vm.envString("MAINNET_RPC");
Counter public counter;
function setUp() public {
mainnetFork = vm.createFork(mainnetRPC); // 创建主网分叉
vm.selectFork(mainnetFork); // 切换到分叉环境
counter = new Counter();
counter.setNumber(0);
}
function testActiveFort() public {
assertEq(vm.activeFork(), mainnetFork);
}
}
方式二:也可以使用命令指定fork的网络和指定区块号;
<font style="color:rgb(51, 51, 51);">对应的区块会缓存到: /用户/.foundry/cache/rpc/mainnet中
forge test --match-contract CounterTest --fork-url <RPC_URL> --fork-block-number <BLOCK_NUMBER>
wushu@whd-asr MINGW64 /d/dapp/test (master)
$ forge inspect Counter abi > Counter.abi
打开Counter.abi文件
╭----------+---------------------------------+------------╮
| Type | Signature | Selector |
+=========================================================+
| function | increment() nonpayable | 0xd09de08a |
|----------+---------------------------------+------------|
| function | number() view returns (uint256) | 0x8381f58a |
|----------+---------------------------------+------------|
| function | setNumber(uint256) nonpayable | 0x3fb5c1cb |
╰----------+---------------------------------+------------╯
启动本地节点
wushu@whd-asr MINGW64 /d/dapp/test (master)
$ anvil
_ _
(_) | |
__ _ _ __ __ __ _ | |
/ _` | | '_ \ \ \ / / | | | |
| (_| | | | | | \ V / | | | |
\__,_| |_| |_| \_/ |_| |_|
1.0.0-stable (e144b82070 2025-02-13T20:02:16.393821500Z)
https://github.com/foundry-rs/foundry
默认账户
Available Accounts
==================
(0) 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 (10000.000000000000000000 ETH)
(1) 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 (10000.000000000000000000 ETH)
(2) 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC (10000.000000000000000000 ETH)
(3) 0x90F79bf6EB2c4f870365E785982E1f101E93b906 (10000.000000000000000000 ETH)
(4) 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 (10000.000000000000000000 ETH)
(5) 0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc (10000.000000000000000000 ETH)
(6) 0x976EA74026E726554dB657fA54763abd0C3a0aa9 (10000.000000000000000000 ETH)
(7) 0x14dC79964da2C08b23698B3D3cc7Ca32193d9955 (10000.000000000000000000 ETH)
(8) 0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f (10000.000000000000000000 ETH)
(9) 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720 (10000.000000000000000000 ETH)
默认私钥,和上面的账户一一对应
Private Keys
==================
(0) 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
(1) 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
(2) 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a
(3) 0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
(4) 0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a
(5) 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba
(6) 0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e
(7) 0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356
(8) 0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97
(9) 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6
默认钱包,助记词,
Wallet
==================
Mnemonic: test test test test test test test test test test test junk
Derivation path: m/44'/60'/0'/0/
链id
Chain ID
==================
31337
Base Fee
==================
1000000000
Gas Limit
==================
30000000
Genesis Timestamp
==================
1745993379
# 本地节点的url
Listening on 127.0.0.1:8545
新建终端,在部署合约
wushu@whd-asr MINGW64 /d/dapp/test (master)
$ forge script script/Counter.s.sol --rpc-url 127.0.0.1:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --broadcast
[⠊] Compiling...
[⠑] Compiling 1 files with Solc 0.8.28
[⠃] Solc 0.8.28 finished in 663.09ms
Compiler run successful!
Script ran successfully.
## Setting up 1 EVM.
==========================
Chain 31337
Estimated gas price: 1.534175043 gwei
Estimated total gas used for script: 203856
Estimated amount required: 0.000312750787565808 ETH
==========================
##### anvil-hardhat
✅ [Success] Hash: 0x10b51baa3497c2428240b1501d74d8d33a4ff87fd7366cc9c0b209d3b71b19e6
Contract Address: 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 合约地址
Block: 4
Paid: 0.000105279579406551 ETH (156813 gas * 0.671370227 gwei)
✅ Sequence #1 on anvil-hardhat | Total Paid: 0.000105279579406551 ETH (156813 gas * avg 0.671370227 gwei)
==========================
ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.
Transactions saved to: D:/dapp/test/broadcast\Counter.s.sol\31337\run-latest.json
Sensitive values saved to: D:/dapp/test/cache\Counter.s.sol\31337\run-latest.json
# 获取 chain ID
cast chain-id --rpc-url http://127.0.0.1:8545
31337
# 获取该节点使用的客户端软件的类型和版本信息
cast client --rpc-url http://127.0.0.1:8545
anvil/v1.0.0
https://developer.metamask.io/key/active-endpoints
// 从infura获取RPC节点的URL,这里以ETH的主网为例
export mainnet=https://mainnet.infura.io/v3/你的key
# 获取最新区块号
cast block-number --rpc-url $mainnet
22381664
# 获取区块出块时间,默认最新,也可指定区块
cast age --rpc-url $mainnet
Wed Apr 30 11:49:35 2025
cast age --rpc-url $mainnet 1
Thu Jul 30 15:26:28 2015
# 根据时间戳获取区块
cast find-block --rpc-url $mainnet 1729072888
20977392
# 获取区块内容
cast block --rpc-url $mainnet
cast block --rpc-url $mainnet 20990795
cast block --rpc-url $mainnet --json
cast block --rpc-url $mainnet --field number
cast block --rpc-url $mainnet --field hash
cast block --rpc-url $mainnet pending
cast block --rpc-url $mainnet --full
# 获取当前 gas 价格
cast gas-price --rpc-url $mainnet
826505153
# 获取 basefee
cast base-fee --rpc-url $mainnet
932153978
cast base-fee --rpc-url $mainnet 22381664
686242841
# 根据域名获取余额
cast balance --rpc-url $mainnet vitalik.eth
237570648344516640858
# 根据地址获取余额,并指定余额单位为 Ether(以太币)
cast balance --rpc-url $mainnet 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --ether
237.570648344516640858
# 获取 nonce 值
cast nonce --rpc-url $mainnet 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
1549
# 根据地址获取 ENS
cast lookup-address --rpc-url $mainnet 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
vitalik.eth
# 获取 ENS 对应地址
cast resolve-name --rpc-url $mainnet vitalik.eth
0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
# 获取存储槽 slot 数据
cast storage --rpc-url $mainnet 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 0
0x577261707065642045746865720000000000000000000000000000000000001a
# 获取合约的 bytescode
cast code --rpc-url $mainnet 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2
0x6060604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b9578063095ea7b31461014757806318160ddd146101a157806323b872dd146101ca5780632e1a7d4d14610243578063313c
e5671461026657806370a082311461029557806395d89b41146102e2578063a9059cbb14610370578063d0e30db0146103ca578063dd62ed3e146103d4575b6100b7610440565b005b34156100c457600080fd5b6100cc6104dd565b60405180806020018281038252838181518152602001
91508051906020019080838360005b8381101561010c5780820151818401526020810190506100f1565b50505050905090810190601f1680156101395780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561015257600080fd5b6101
87600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061057b565b604051808215151515815260200191505060405180910390f35b34156101ac57600080fd5b6101b461066d565b604051808281526020019150506040518091
0390f35b34156101d557600080fd5b610229600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061068c565b604051808215151515815260200191
505060405180910390f35b341561024e57600080fd5b61026460048080359060200190919050506109d9565b005b341561027157600080fd5b610279610b05565b604051808260ff1660ff16815260200191505060405180910390f35b34156102a057600080fd5b6102cc600480803573ff
ffffffffffffffffffffffffffffffffffffff16906020019091905050610b18565b6040518082815260200191505060405180910390f35b34156102ed57600080fd5b6102f5610b30565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381
101561033557808201518184015260208101905061031a565b50505050905090810190601f1680156103625780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561037b57600080fd5b6103b0600480803573ffffffffffffffffffff
ffffffffffffffffffff16906020019091908035906020019091905050610bce565b604051808215151515815260200191505060405180910390f35b6103d2610440565b005b34156103df57600080fd5b61042a600480803573ffffffffffffffffffffffffffffffffffffffff16906020
01909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610be3565b6040518082815260200191505060405180910390f35b34600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16
8152602001908152602001600020600082825401925050819055503373ffffffffffffffffffffffffffffffffffffffff167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c346040518082815260200191505060405180910390a2565b60008054600181
600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156105735780601f1061054857610100808354040283529160200191610573565b820191906000526020600020905b
81548152906001019060200180831161055657829003601f168201915b505050505081565b600081600460003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffff
ffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d
1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60003073ffffffffffffffffffffffffffffffffffffffff1631905090565b600081600360008673ffffffffffffffffffffffffffffffffffffffff1673ff
ffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101515156106dc57600080fd5b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16141580156107b457507fffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffff600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffff
ffffffffffffffffffffffffffffffffff1681526020019081526020016000205414155b156108cf5781600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffff
ffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561084457600080fd5b81600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffff
ff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b81600360008673ffffffffffffffffffffffffffffffff
ffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020
01600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180
910390a3600190509392505050565b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610a2757600080fd5b80600360003373ffffffffffffffffffffffffffff
ffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f193505050501515610ab4
57600080fd5b3373ffffffffffffffffffffffffffffffffffffffff167f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65826040518082815260200191505060405180910390a250565b600260009054906101000a900460ff1681565b6003602052806000
5260406000206000915090505481565b60018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610bc65780601f10610b9b576101008083540402835291
60200191610bc6565b820191906000526020600020905b815481529060010190602001808311610ba957829003601f168201915b505050505081565b6000610bdb33848461068c565b905092915050565b60046020528160005260406000206020528060005260406000206000915091505054815600a165627a7a72305820deb4c2ccab3c2fdca32ab3f46728389c2fe2c165d5fafa07661e4e004f6c344a0029
# 本地网络提供的私钥
# 设置为当前终端的变量,方便后续$privateKey引用
export privateKey=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
cast send --private-key <私钥> <目标地址> --value <金额>
# 转账
cast send --private-key $privateKey 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 --value 10ether
blockHash 0x1790cc4aea79b93becc08d70f32b2bbead272c592f41e6f6ed88d92b1516121b
blockNumber 2
contractAddress
cumulativeGasUsed 21000
effectiveGasPrice 878457601
from 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
gasUsed 21000
logs []
logsBloom 0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
root
status 1 (success)
transactionHash 0xc2ea17a11e972682631bef60dc64b2f7444345668198b8f18836221477a5e88f
transactionIndex 0
type 2
blobGasPrice 1
blobGasUsed
authorizationList
to 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
# 获取账号余额
cast balance 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 --ether
10010.000000000000000000
# 创建合约
forge create --private-key $privateKey src/Counter.sol:Counter --broadcast
[⠊] Compiling...
No files changed, compilation skipped
Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Deployed to: 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
Transaction hash: 0xb4ba08ea399d47511396386f2b5368029ba3d998c26d108b3e46b7f58413dada
# 调用合约方法
cast send --private-key $privateKey 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 "setNumber(uint256)" 100
blockHash 0x434e52ca0e2746687aa0394a22132d9361d1fa694cd30f60e0658ccf33f12430
blockNumber 4
contractAddress
cumulativeGasUsed 21204
effectiveGasPrice 675361833
from 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
gasUsed 21204
logs []
logsBloom 0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
root
status 1 (success)
transactionHash 0xe7a1e5b7ae1ac5ede1975f5c4ad3648da48be4f8cb08f1238c62501c0d58bb33
transactionIndex 0
type 2
blobGasPrice 1
blobGasUsed
authorizationList
to 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9
合约中的状态变量:uint256 public number; solidity会默认提供get方法
# 调用 static 合约方法
cast call --private-key $privateKey 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 "number()(uint256)"
100
cast tx <交易哈希>
# 获取 Transation 信息
cast tx 0xe7a1e5b7ae1ac5ede1975f5c4ad3648da48be4f8cb08f1238c62501c0d58bb33
blockHash 0x434e52ca0e2746687aa0394a22132d9361d1fa694cd30f60e0658ccf33f12430
blockNumber 4
from 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
transactionIndex 0
effectiveGasPrice 675361833
accessList []
chainId 31337
gasLimit 21204
hash 0xe7a1e5b7ae1ac5ede1975f5c4ad3648da48be4f8cb08f1238c62501c0d58bb33
input 0x3fb5c1cb0000000000000000000000000000000000000000000000000000000000000064
maxFeePerGas 1537608263
maxPriorityFeePerGas 1
nonce 3
r 0xefd2a5a1b798bc9ca9cbe688c7e3587b3bd9d78edac9484c40839b8c42914d3d
s 0x2bb9843f1acec7630fdbe02f4c08a86b63482542bfb3edab2c014e01618d7ddc
to 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9
type 2
value 0
yParity 0
# 创建钱包方式一
cast wallet new
Successfully created new keypair.
Address: 0xB930c4F004216640a2f22797bA9A86c6664c0496
Private key: 0xc9f83fffd1ba54b57d09b8c31bb8b26f966a8420516eb753347654df984a9b86
# 创建钱包方式二
# 在项目的根目录下创建密钥库文件夹,并将其加密后的私钥存储在 keystore 文件夹中,成功后会在文件夹中创建私钥文件名为UUID
mkdir keystore
cast wallet new keystore
Enter secret: 123456 请输入密码
Created new encrypted keystore file: D:\dapp\test\keystore\c6976752-603f-4e4b-80f8-316caad70a91
Address: 0xE7ca4cbcE29034807EB777DdE0D2e81F1aFC3d2a
文件内容:
{"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"5387124bb1795c41a7459997196f8258"},"ciphertext":"4a650924f84b27de101b4cdb77117131ab9295cca2199abaf43ef38edfd0d340","kdf":"scrypt","kdfparams":{"dklen":32,"n":8192,"p":1,"r":8,"salt":"bd4506b01411ba0f1659486d5ac9d4fbf57cef0f898d0ca1c0b05246f6f875c4"},"mac":"5f542b512eb7c64e8f5fdff8f8b441b9c963b91e9bfdac73d605da7a479216a7"},"id":"c6976752-603f-4e4b-80f8-316caad70a91","version":3}
# 根据 json 钱包获取地址; 绝对路径;注意windows是反斜杠
cast wallet addr --keystore D://dapp//test//keystore//c6976752-603f-4e4b-80f8-316caad70a91 --password 123456
结果
0xE7ca4cbcE29034807EB777DdE0D2e81F1aFC3d2a
验签:cast wallet verify --address <地址> <消息> <签名>
# 签名
export privateKey=0x113d463b15d61eb6df9182a7c45c8b952a9768b7a84e1d4e471731c271360ca6
cast wallet sign --private-key $privateKey "hello"
结果
0x8b3c393dcea9794ad7aebe10436b9cdaea6efef3841cee59aae38e5ccaf1fd33105aa97a838b92e50d28e65828e7483f14f857bdb1e10d51e05a8769bc50f20b1c
# 验签
cast wallet verify --address 0xEF9D0359bD4Ade81386C49e91D3dB75c2b75A1C8 "hello" 0x8b3c393dcea9794ad7aebe10436b9cdaea6efef3841cee59aae38e5ccaf1fd33105aa97a838b92e50d28e65828e7483f14f857bdb1e10d51e05a8769bc50f20b1c
结果
Validation succeeded. Address 0xEF9D0359bD4Ade81386C49e91D3dB75c2b75A1C8 signed this message.
cast wallet vanity --starts-with <前缀> --ends-with <后缀>
# 生成靓号
cast wallet vanity --starts-with 00 --ends-with 00
Starting to generate vanity address...
Successfully found vanity address in 0.841 seconds.
Address: 0x0023c76EB1abF0731F0db3e3659fbC4c376B8A00
Private Key: 0xb3afaf62c4623e63093cf36895f283da65278ce0a96f9364c13ffd22e2b85772
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!