Speedrun Ethereum Tokenization Checkpoint 2 - 账户生成与 Sepolia 部署排错
Checkpoint 2:账户生成与 Sepolia 部署排错
挑战地址:https://speedrunethereum.com/challenge/tokenization
本文是 Speedrun Ethereum Tokenization 系列第一篇,记录 Checkpoint 2 中账户生成和 Sepolia 部署的两个问题。
隐私说明:本文不包含真实钱包地址、交易哈希、部署 URL、IPFS 哈希或本地绝对路径。
系列目录
- 索引:完整排错路线
- 当前:Checkpoint 2 - Deploy to a Testnet
- 下一篇:Checkpoint 3 - Mint NFTs
- 最后篇:Checkpoint 4 - Ship
背景
这个挑战要求使用 Scaffold-ETH 2 和 Foundry 完成一个 ERC-721 NFT 项目 YourCollectible,流程包括:
- 编写和编译 ERC-721 合约
- 生成部署账户
- 部署到本地链
- 部署到 Sepolia
- 更新前端合约地址和 ABI
Checkpoint 2 中遇到了两个问题:
yarn generate无法生成部署账户- 部署到 Sepolia 的交易被打包,但执行失败
两个问题都需要结合工具版本、输出流和链上 gas 数据来分析。
环境信息
本次排查环境如下:
1 | 操作系统: macOS |
项目目录:
1 | /path/to/challenge-tokenization |
问题一(Checkpoint 2:部署到测试网前):yarn generate 报 Could not extract private key from output
当前 Challenge 步骤:Checkpoint 2 - Deploy to a Testnet
此时已经完成 ERC-721 合约,准备生成部署账户、连接测试网并部署到 Sepolia。
1. 复现命令
进入正确项目目录:
1 | cd "/path/to/challenge-tokenization" |
执行:
1 | yarn generate |
终端输出:
1 | 🔑 Generating new wallet... |
一开始很容易误以为是“新终端没有加载环境”,或者 node、yarn、cast 没有安装。
但检查后确认:
1 | node -> /opt/homebrew/bin/node |
所有命令都能正常找到,所以问题不在终端 PATH。
2. 定位问题脚本
yarn generate 最终执行的是:
1 | packages/foundry/scripts-js/generateKeystore.js |
关键代码如下:
1 | const newWalletResult = spawnSync("cast", ["wallet", "new"], { |
这段代码做了一件很常见但不够稳定的操作:
从命令的人类可读文本输出中提取私钥。
它假设 cast wallet new 会把下面这行写到 stdout:
1 | Private key: 0x... |
然而在 Foundry 1.8.5 中,输出流发生了变化。
3. 检查 cast wallet new 的真实输出
在当前 Foundry 版本中,执行:
1 | cast wallet new |
实际输出可以分成两个部分。
stdout:
1 | 0x<address> 0x<private-key> |
stderr:
1 | Successfully created new keypair. |
使用 Node.js 的 spawnSync() 时,stdout 和 stderr 会被分别保存:
newWalletResult.stdout只有地址和私钥的制表符分隔行newWalletResult.stderr才包含Private key:标签
但项目脚本只搜索:
1 | newWalletResult.stdout |
所以它永远找不到 Private key:,最终进入:
1 | if (!privateKey) { |
4. 根因总结
这不是钱包生成失败,而是:
1 | 钱包已经成功生成 |
同样的问题还存在于:
1 | packages/foundry/scripts-js/selectOrCreateKeystore.js |
也就是说,即使绕过 yarn generate,之后在部署流程中选择“创建新 keystore”,也可能再次遇到同样的错误。
5. 快速解决方案:直接创建 keystore
如果目标只是继续完成挑战,可以不修改项目,直接使用 Foundry 的 keystore 命令:
1 | cd "/path/to/challenge-tokenization" |
终端会要求输入并确认 keystore 密码。密码不会显示,这是正常行为。
生成的文件位于:
1 | ~/.foundry/keystores/sepolia-deployer |
查看账户:
1 | yarn account |
在列表中选择 sepolia-deployer,然后复制地址去 Sepolia faucet 领取测试 ETH。
这种方式完全绕过了有问题的私钥文本解析代码。
6. 永久修复方案:读取 JSON 输出
更稳妥的修复方式是使用:
1 | cast wallet new --json |
Foundry 返回的结构类似:
1 | { |
把原来的调用:
1 | const newWalletResult = spawnSync("cast", ["wallet", "new"], { |
改为:
1 | const newWalletResult = spawnSync("cast", ["wallet", "new", "--json"], { |
把原来的文本搜索:
1 | const privateKey = newWalletResult.stdout |
替换为:
1 | let privateKey; |
永久修复时需要同时修改:
1 | packages/foundry/scripts-js/generateKeystore.js |
因为两个入口都使用了相同的旧解析逻辑。
7. 问题一验证结果
使用临时 HOME 验证了两个方案:
1 | cast wallet new sepolia-deployer |
这说明问题确实出在输出解析,而不是钱包生成本身。
问题二(Checkpoint 2:部署到测试网):Sepolia 部署交易被打包,但执行失败
当前 Challenge 步骤:Checkpoint 2 - Deploy to a Testnet
账户已经准备好并领取了 Sepolia 测试 ETH,现在正在真正执行
forge script --broadcast部署合约。
1. 部署命令
账户创建完成后,我执行了:
1 | yarn deploy --network sepolia --account sepolia-deployer |
脚本显示 keystore 列表,手动选择:
1 | 1. sepolia-deployer |
随后合约编译和模拟都成功:
1 | Compiler run successful! |
但真正广播后,交易失败:
1 | ❌ [Failed] |
2. 先确认 --account 是否是问题
检查发现,项目的 yarn deploy 包装脚本只识别:
1 | --network |
它不识别 --account。
所以在命令:
1 | yarn deploy --network sepolia --account sepolia-deployer |
中,--account sepolia-deployer 被忽略了。
不过因为之后脚本弹出了 keystore 列表,并手动选择了:
1 | 1. sepolia-deployer |
所以这次实际使用的账户是正确的。
结论:
1 | --account 参数确实不该用在这里 |
以后使用项目的 Yarn 包装命令时,应写:
1 | yarn deploy --network sepolia --keystore sepolia-deployer |
3. 查询链上交易回执
执行:
1 | cast receipt \ |
关键字段:
1 | status: 0 (failed) |
而广播交易中的 gas limit 也是:
1 | gasLimit: 1894991 |
也就是:
1 | gasUsed == gasLimit |
这是非常典型的交易耗尽 gas 后被回滚的表现。
4. 为什么不是合约业务逻辑 revert
如果构造函数正常执行,但其中某个 require 或条件失败,通常会看到明确的 revert 原因,而不是刚好消耗完所有 gas。
这次回执里:
status失败logs为空gasUsed正好等于发送时的gasLimit
因此更像是 EVM 在执行过程中用完了 gas,而不是某个 Solidity 条件主动 revert。
5. Foundry 的模拟结果
Foundry 模拟时显示:
1 | new YourCollectible gas: 约 1,296,570 |
Foundry 因此发出:
1 | gasLimit: 1,894,991 |
从本地模拟结果看,这个 gas 似乎足够。
但链上执行给出了不同的结果。
6. 使用链上 gas 估算验证
对同一部署字节码使用 Sepolia 进行只读调用,得到以下结果:
1 | 1,894,991 gas -> out of gas |
这说明当前 Sepolia 实际执行这个创建交易时,需要大约:
1 | 10.11M gas |
而 Foundry 只发送了:
1 | 1.895M gas |
两者差距约为五倍以上。
7. 问题二根因总结
本次失败的核心原因是:
1 | Foundry 1.8.5 在当前 Sepolia 环境下对部署 gas 的估算偏低 |
账户余额没有问题。链上查询时余额约为:
1 | 0.098105009 ETH |
失败交易消耗了 nonce 0,所以下一次部署会使用 nonce 1,并生成一个新的合约地址。
8. 解决方案:提高 gas 估算倍数
不再使用当前不带 gas 倍数的 yarn deploy 包装命令,而是直接运行 forge script。
进入 Foundry 包目录:
1 | cd "/path/to/challenge-tokenization/packages/foundry" |
执行:
1 | forge script script/Deploy.s.sol \ |
参数说明:
1 | forge script script/Deploy.s.sol |
验证过的结果:
1 | --gas-estimate-multiplier 700 |
执行后会提示输入 keystore 密码:
1 | Enter keystore password: |
输入密码时终端不会显示字符。
9. 部署成功后更新前端 ABI
看到类似下面输出时,表示链上部署成功:
1 | ONCHAIN EXECUTION COMPLETE & SUCCESS |
然后仍然在:
1 | packages/foundry |
目录执行:
1 | node scripts-js/generateTsAbis.js |
或者:
1 | make generate-abis |
这一步只更新:
1 | packages/nextjs/contracts/deployedContracts.ts |
它不会再次部署合约,也不需要重新执行 yarn deploy。
下一篇
Checkpoint 2 完成后,继续阅读:
返回:完整排错路线
