一、创建项目 1 forge init hello_foundry
构建项目:
运行测试
out目录为合约工件 cache目录被forge用来记录
在现有项目工作:
1 2 3 4 $ git clone https://github.com/PaulRBerg/foundry-template $ cd foundry-template $ forge install $ bun install # 安装 Solhint, Prettier, 以及其他 Node.js 依赖项。
forge install 用来安装项目中的子模块依赖项。
二、项目结构
src:智能合约源代码(Counter.sol)
test:单元测试文件(Counter.t.sol)
script:部署和交互脚本(Deploty.s.sol)
lib:依赖库
foundry.toml:配置文件,用于自定义构建、测试、部署等行为。
三、如何跑起来一个代码 配置好vscode插件: Solidity(由 Juan Blanco 开发):提供 Solidity 语法高亮、自动补全和 linting。 Prettier - Code formatter:自动格式化代码,确保一致性。 Solidity Visual Developer:增强 Solidity 可视化,如函数调用图和审计工具。
本地测试 先用默认的测试代码运行
得到out(合约工件、如ABI)、cache(记录,防重复编译)目录
src/Counter.sol:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; contract Counter { // 一个很简单的 Counter 合约 uint256 public number; // 维护一个 public 的 uint256 数字 // 设置 number 变量的内容 function setNumber(uint256 newNumber) public { number = newNumber; } // 让 number 变量的内容自增 function increment() public { number++; } }
script/Counter.s.sol:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; // 许可 和 Solidity版本标识 import "forge-std/Script.sol"; // 引入foundry forge中的Script库 import "../src/Counter.sol"; // 引入要部署的Counter合约 // 部署脚本继承了Script合约 contract CounterScript is Script { // 可选函数,在每个函数运行之前被调用 function setUp() public {} // 部署合约时会调用run()函数 function run() public { vm.startBroadcast(); // 开始记录脚本中合约的调用和创建 new Counter(); // 创建合约 vm.stopBroadcast(); // 结束记录 } }
运行得到:
1 2 3 4 5 6 7 8 9 $ forge script script/Counter.s.sol:CounterScript [⠊] Compiling... [⠔] Compiling 2 files with Solc 0.8.30 [⠒] Solc 0.8.30 finished in 402.95ms Compiler run successful! Script ran successfully. Gas used: 152949 If you wish to simulate on-chain transactions pass a RPC URL.
表示脚本在 本地EVM 环境编译成功了,此时并没有上链
测试上链 rpc可在https://chainlist.org/?search=sepolia&testnets=true
使用测试网 Ethereum Sepolia 的链上状态,但是不发送交易(fork测试网)
1 2 forge script script/Counter.s.sol:CounterScript \ --rpc-url https://ethereum-sepolia-rpc.publicnode.com
合约和交易逻辑验证通过:
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 $ forge script script/Counter.s.sol:CounterScript \ --rpc-url https://ethereum-sepolia-rpc.publicnode.com [⠊] Compiling... No files changed, compilation skipped Script ran successfully. ## Setting up 1 EVM. ========================== Chain 11155111 Estimated gas price: 60.500178376 gwei Estimated total gas used for script: 203856 Estimated amount required: 0.012333324363017856 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: /home/vstral/Code/Web3/study/broadcast/Counter.s.sol/11155111/dry-run/run-latest.json Sensitive values saved to: /home/vstral/Code/Web3/study/cache/Counter.s.sol/11155111/dry-run/run-latest.json
生成了文件
第一个broadcast的:run-latest.json:交易记录
第二个cache的:run-lastest.json:私钥、签名等隐私数据
真正上链 要想上链需要配置自己的私钥,有以下方法:
可以直接指定私钥 添加参数 –private-key YOUR_PRIVATE_KEY
使用foundry.toml配置填入钱包地址和rpc,千万不要写入私钥,私钥需要放在项目根目录.env文件中(注意在.gitignore中屏蔽)
1 2 3 4 5 [profile.default] sender = "YOUR_ADDRESS" [rpc_endpoints] sepolia = "https://ethereum-sepolia-rpc.publicnode.com"
.env文件:
1 2 3 # .env PRIVATE_KEY=你的私钥 RPC_URL=https://ethereum-sepolia-rpc.publicnode.com
注意需要source .env引入环境变量
添加 –broadcast 和 –private-key:
1 2 3 4 $ forge script script/Counter.s.sol:CounterScript \ --rpc-url sepolia \ --broadcast \ --private-key $PRIVATE_KEY
(推荐)在合约里读取环境变量,避免私钥出现在命令历史,增大安全风险
1 vm.startBroadcast(vm.envUint("PRIVATE_KEY));
测试合约状态 调用函数:
读状态(view函数)假设有 function number() view returns (uint256) 这样一个函数:
1 cast call 0xdfD18Fc5A3416Fd677C8b238EAC14c282FdF0428 "number()" --rpc-url sepolia
调用修改状态的函数
1 2 3 4 cast send 0xdfD18Fc5A3416Fd677C8b238EAC14c282FdF0428 "increment()" \ --private-key $PRIVATE_KEY \ --rpc-url sepolia
cast命令文档:cast - Foundry 中文文档 | 登链社区 s cast call的本质:调用JSON-RPC的 eth_call:
在某个区块高度的EVM上假装执行
执行完后丢弃结果,不写storage
call send做了什么? 使用eth_sendRawTransaction
构造交易
用私钥签名
通过RPC发送到mempool
被矿工打包
写入区块