测试不是“跑过一次”。它把需求变成可重复证据,并主动寻找错误路径、权限缺口与边界条件。
Ethereum / DApp delivery / Lesson 22.02
一份代码,怎样变成真正可用的 DApp?
源码写完,只是起点。它还要变成可执行的字节码,在可重复环境里经受测试, 通过一笔部署交易获得网络身份与合约地址,最后由前端把用户意图编码成 可读、可签、可追踪的链上交互。
SOURCE ≠ DEPLOYMENT ≠ PRODUCT
地址不能脱离网络。一个实例至少由 chainId + address 唯一定位,ABI 才告诉前端如何与它说话。
拿到交易哈希不等于执行成功。前端必须处理签名、pending、receipt、revert、替换与网络变化。
01 / ORIENTATION
先看终点:你交付的不是源码,而是一条可验证链路
DApp 的“后端”公开运行在网络上,前端只是把人与协议接起来
DApp 不是“网页里加一个连接钱包按钮”。它由链上合约与用户界面共同组成; 合约像公开 API,但每次写入都要经过签名、网络传播、区块执行与状态确认。 [1]
一条从意图到可观察结果的交付链
8 boundaries行为证据
需求、权限、错误路径与不变量。测试必须对应它们,而不是只追求覆盖率数字。
构建证据
源码、依赖锁、编译器版本、优化设置、ABI、字节码与 metadata。
部署证据
网络、部署账户、交易哈希、收据、合约地址、构造参数和部署记录。
运行证据
前端网络配置、源码验证、监控、权限归属、故障告警与事件响应。
02 / ARTIFACT MAP
先认清四件套:字节码、ABI、地址与 chainId
“我有合约地址”还不足以让前端安全交互
Solidity 源码是给人读的;EVM 执行字节码;前端依靠 ABI 编码函数调用; 地址只在某一条链的状态空间中定位实例。编译就是把一份人类描述拆成不同消费者需要的产物。 [2]
同一次编译,服务三个不同读者
human / EVM / frontendEVM 执行什么
creation bytecode 负责构造实例;runtime bytecode 是部署后地址真正执行的代码。
0x60806040…
调用怎样编码
函数、参数、返回值、事件与自定义错误的机器可读接口说明。
incrementBy(uint256)
实例在哪里
20 字节账户地址。相同地址在另一条链上可能没有代码,或是完全不同的代码。
0x7A…42F1
它属于哪条链
网络身份,也是签名交易防跨链重放的一部分。主网为 1,Sepolia 为 11155111。
11155111
可复现部署 = 上述信息 + source + compiler version + build settings + constructor arguments
为什么同一个 0x… 不能脱离网络理解?
每条链维护独立世界状态。一个私钥可以在多条 EVM 链上导出同一个 EOA 地址,但该地址的余额、 nonce、合约代码和存储互不相通。前端若只保存地址、不绑定 chainId,就可能把 Sepolia 的 ABI 配到主网地址,或者让用户在错误网络上签名。EIP-155 把 chainId 纳入交易签名,降低跨链重放风险, 但不会替前端检查地址配置。[3]
03 / EXAMPLE CONTRACT
用一个小合约,贯穿测试、部署与前端
功能越小,越容易看清每一道工程边界
LearningCounter 不收 ETH、不调用外部合约,只保留状态、权限、边界、事件与错误。
这让我们能把注意力放在交付链路上,而不是把“能部署”误当成“适合管理真实资产”。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract LearningCounter {
uint256 public constant MAX_COUNT = 1_000_000;
uint256 public count;
address public immutable owner;
event CountChanged(
address indexed actor,
uint256 previous,
uint256 current
);
error ZeroAmount();
error CountLimitExceeded(
uint256 current,
uint256 requested,
uint256 maximum
);
error NotOwner();
constructor(uint256 initial) {
if (initial > MAX_COUNT) {
revert CountLimitExceeded(0, initial, MAX_COUNT);
}
owner = msg.sender;
count = initial;
}
function increment() external {
incrementBy(1);
}
function incrementBy(uint256 amount) public {
if (amount == 0) revert ZeroAmount();
if (amount > MAX_COUNT - count) {
revert CountLimitExceeded(count, amount, MAX_COUNT);
}
uint256 previous = count;
count = previous + amount;
emit CountChanged(msg.sender, previous, count);
}
function reset(uint256 next) external {
if (msg.sender != owner) revert NotOwner();
if (next > MAX_COUNT) {
revert CountLimitExceeded(count, next, MAX_COUNT);
}
uint256 previous = count;
count = next;
emit CountChanged(msg.sender, previous, next);
}
}
教学合约不承载资金。实际生产合约还需要与资产规模、依赖和升级模型相称的设计与独立审查。
- 01 / STATE
count是链上持久状态;公开 getter 是只读入口。 - 02 / AUTHORITY
owner在部署时固定;只有它能降低或重设计数。 - 03 / BOUNDARY
MAX_COUNT是业务不变量,不是 EVM 自带规则。 - 04 / ERROR自定义错误让测试和前端区分“零输入、越界、无权限”。
- 05 / EVENT事件记录 actor、旧值与新值,便于界面和索引器观察变化。
- 06 / OVERFLOW先检查
amount > MAX - count,避免在错误信息构造前先触发算术 Panic。
从“函数能跑”升级到“不变量可陈述”
范围不越界
任意成功交易后,始终满足 0 ≤ count ≤ MAX_COUNT。
递增不下降
increment* 成功后,新值严格大于旧值。
权限不旁路
非 owner 不能通过 reset 改变状态。
日志与状态一致
每次成功写入都发出与实际 previous/current 对应的事件。
04 / LOCAL TESTING
本地测试不是证明“我会调用函数”,而是持续挑战承诺
正常路径只回答“能不能”,失败路径才开始回答“守不守得住”
智能合约部署后难以随意替换,测试至少是安全底线。单元、集成、性质测试、静态分析、 人工复核各自看见不同问题;任何一个绿色勾都不能单独证明“没有漏洞”。 [4]
编译与静态检查
类型、可见性、警告、已知编译器问题与明显代码模式。
单元测试
初始值、正常递增、事件、错误类型、权限与边界。
Fuzz / invariant
让工具生成大量输入和调用序列,寻找破坏不变量的反例。
本地集成
多账户、多交易、事件解析、前端、持久节点与错误反馈。
Sepolia 端到端
真实 RPC、钱包、公开区块、等待时间、浏览器与部署配置。
最小测试矩阵:每项需求都要有成功与失败证据
| 被验证的承诺 | 正向样例 | 反向 / 边界样例 | 观察证据 |
|---|---|---|---|
| 构造器建立正确状态 | initial = 7 | initial = MAX + 1 | count / owner / custom error |
| 递增严格增加 | 7 + 3 = 10 | amount = 0;超出剩余空间 | storage + CountChanged |
| 只有 owner 可重设 | owner reset(2) | 其他账户 reset(2) | NotOwner + 状态不变 |
| 范围永不越界 | 随机合法增量序列 | 接近上限后继续随机调用 | 每步 count ≤ MAX |
# 初始化当前 Hardhat 3 项目并查看模板
npx hardhat --init
# 每次从干净模拟网络运行测试
npx hardhat test
# 启动一个可供脚本和前端共同连接的持久本地节点
npx hardhat node
# 部署到正在运行的本地节点;状态持续到节点停止或重置
npx hardhat ignition deploy ignition/modules/LearningCounter.ts --network localhost
Hardhat 3 同时支持 Solidity 与 TypeScript 测试;具体插件 API 会演进,先固定项目版本并阅读与锁文件一致的文档。
LAB 01 / ARRANGE → ACT → ASSERT
同一份合约,四种测试究竟证明什么?
选择情景。实验只在页面内模拟预期状态,不执行 Solidity。
PASS:这个样例证明给定前置状态和输入时,状态与事件符合预期;它没有证明其他输入或调用序列都安全。
无 JavaScript 结论:测试应同时覆盖成功、回滚、权限、边界与长期性质;覆盖率高不等于断言质量高。
05 / ENVIRONMENTS
本地链、Sepolia 与主网,不是三个速度档位
它们分别回答“逻辑可重复吗”“产品能端到端工作吗”“是否值得承担真实后果”
Sepolia 是当前普通合约和应用开发的默认测试网;Hoodi 主要服务协议升级、验证者与质押测试。 测试网 ETH 按设计没有现实价值,不应购买。[5]
开发链
只在你的开发环境存在,适合快速、确定性、可重置的测试。
- 预置账户与测试余额
- 可操纵时间、区块与状态
- 节点停止后状态可消失
- 默认私钥公开,只能用于本地
公开测试网
让真实钱包、RPC、浏览器和多人前端在公开环境完成端到端演练。
- 公开传播、真实等待与费用机制
- 测试 ETH 无现实价值
- 合约与交易对所有人可见
- 错误不能像本地快照一样撤销
生产网络
真实资产、真实对手、永久记录和组织责任都进入系统。
- 部署与调用消耗真实 ETH
- 任意用户和机器人都可交互
- 升级与应急都有信任成本
- 必须经过独立发布闸门
同一个地址,在三条链上是三份独立状态
address ≠ global identity同一公钥导出的 EOA
0xA11c…09F2永远不进入前端三份世界状态
10,000 test ETH · nonce 120.18 test ETH · nonce 30 ETH · nonce 0什么时候从本地进入 Sepolia?
当单元、边界与不变量测试稳定,本地前端已能重现完整读写流程,并且你已经能解释每个签名请求时, 才把同一构建产物送入 Sepolia。测试网的目标不是继续“试着改代码”,而是验证真实整合: 网络配置、部署密钥、RPC、钱包、地址、ABI、浏览器兼容、区块等待和错误反馈是否共同工作。
06 / DEPLOYMENT
部署不是上传文件,而是一笔创建合约的交易
交易被打包并成功执行后,网络才产生那个地址上的 runtime code
普通转账的 to 指向接收账户;合约创建交易没有普通接收方,输入数据包含 creation bytecode
与构造参数。成功收据会给出新合约地址。[6]
创建交易进入区块前后,产物怎样变化
transaction → receipt → code发送前:creation transaction
11155111null / contract creationcreation bytecode + ABI-encoded constructor argsdeployer signature + noncegas limit + fee parameters成功后:deployed instance
0x96f1…7bd01 / success0x7A93…42F1runtime bytecode stored at addresscount = 7 · owner = deployer部署前的四项输入
可信 RPC 与 chainId
RPC 是访问节点的通道,不是网络本身。连接后仍要读取并校验实际 chainId。
独立部署账户
只放足够的测试 ETH;私钥存入受控密钥系统或配置变量,不写入仓库。
固定构建产物
锁定源码、依赖、编译器和优化设置,避免测试与部署不是同一份代码。
可恢复部署记录
保存参数、交易哈希、地址与部署状态,让重试不会误发第二份实例。
import { buildModule } from
"@nomicfoundation/hardhat-ignition/modules";
export default buildModule(
"LearningCounterModule",
(m) => {
const initial = m.getParameter("initial", 7n);
const counter = m.contract(
"LearningCounter",
[initial],
);
return { counter };
},
);
声明式部署会记录已完成步骤并支持恢复;部署记录仍应纳入版本化发布证据,而不是只抄终端里最后一行地址。
# 先在 Hardhat 模拟网络运行部署计划
npx hardhat ignition deploy ignition/modules/LearningCounter.ts
# 再对正在运行的 localhost 节点部署,供本地前端访问
npx hardhat ignition deploy ignition/modules/LearningCounter.ts --network localhost
# 本地闭环通过后,使用独立测试账户部署到 Sepolia
npx hardhat ignition deploy ignition/modules/LearningCounter.ts --network sepolia
网络 URL、API key 与部署私钥应通过 Hardhat 配置变量或外部密钥设施提供;不要把它们硬编码进模块。
LAB 02 / SAME CODE, DIFFERENT CONSEQUENCES
你准备把这一份构建部署到哪里?
切换环境,观察同一操作的身份、公开性与风险含义。
LOCAL:快、私有、可重置。适合反复验证部署模块与前端,但本地预置 ETH、即时出块和公开测试密钥都不能代表生产环境。
无 JavaScript 结论:先本地、再 Sepolia;只有经过与真实价值相称的独立安全闸门,才讨论主网。
源码验证究竟证明什么?
验证服务会用你提交的源码、编译器、设置、库地址与构造参数重新编译,再把结果与该地址的部署字节码比对。 匹配说明“这份可读源码能够重现链上代码”,方便独立检查;它不是安全审计,更不是业务逻辑正确证明。 [7]
Security review: requirements ↔ source behavior ↔ adversarial conditions
07 / FRONTEND INTERACTION
前端没有“调用合约”这一个动作,只有读与写两条完全不同的路
Provider 连接状态;Signer 代表用户授权状态改变
只读调用通常通过 eth_call 在节点上模拟执行,不写入区块,也无需用户签名;
写调用必须构造交易、估算 Gas、请求签名、广播并等待收据。
[8]
同一个 Contract 对象,连接 Provider 或 Signer 会开放不同能力
eth_call ≠ transactioncount()
读取公开状态;通常不需要连接钱包。
- 用 ABI 编码函数选择器与参数
- Provider 向节点发送
eth_call - 节点在指定区块状态上模拟执行
- 返回 ABI 编码结果,前端解码为
bigint
incrementBy(3)
改变世界状态;用户必须看见并授权。
- 检查 chainId、账户、参数与目标地址
- 估算 Gas,钱包展示并签署交易
- 广播后取得 hash,交易进入 pending
- 等待 receipt,检查 status,再刷新状态
view 函数,该执行仍计入交易 Gas。
import { BrowserProvider, Contract } from "ethers";
import abi from "./LearningCounter.abi.json";
const SEPOLIA = 11155111n;
const address = deployments[SEPOLIA].LearningCounter;
const provider = new BrowserProvider(window.ethereum);
const network = await provider.getNetwork();
if (network.chainId !== SEPOLIA) {
throw new Error("请切换到 Sepolia");
}
// READ:Provider 足够,不需要签名。
const readContract = new Contract(address, abi, provider);
const current = await readContract.count();
// WRITE:先显式请求账户,再取得 Signer。
await provider.send("eth_requestAccounts", []);
const signer = await provider.getSigner();
const writeContract = readContract.connect(signer);
const tx = await writeContract.incrementBy(3n);
showPending(tx.hash);
const receipt = await tx.wait();
if (!receipt || receipt.status !== 1) {
throw new Error("交易已打包,但执行失败");
}
renderCount(await readContract.count());
示例省略 UI 状态管理。生产前端还要处理用户拒绝、错误网络、账户变化、交易替换、RPC 中断、重组与组件卸载后的监听清理。
前端配置必须按网络组织
export const deployments = {
31337n: {
name: "Localhost",
LearningCounter: "0x5FbD…0aa3",
},
11155111n: {
name: "Sepolia",
LearningCounter: "0x7A93…42F1",
},
} as const;
合约地址、chainId 和公共 RPC URL 不是秘密;部署私钥、助记词和服务端特权凭证才是。任何进入浏览器包的“环境变量”都应按公开数据对待。
LAB 03 / RPC ROUTER
前端此刻应该读、签、阻断,还是解释错误?
选择情景,观察请求类型、权限与返回对象。
READ:直接显示当前区块状态,并标明数据来自哪条链;不要为了读取公共状态强迫用户先连接钱包。
无 JavaScript 结论:读返回值,写返回交易响应;错误网络应在签名前阻断,用户拒绝是正常交互分支而不是系统故障。
事件不是装饰,也不是所有数据的永久数据库
外部账户发送写交易时,Solidity 函数的返回值不会像普通 HTTP 响应那样直接送回前端。 前端通常通过收据中的日志、随后读取的状态或索引服务确认结果。事件适合描述可观察变化, 但合约自身不能像读取 storage 那样读取历史日志;事件字段与索引设计要服务真实查询需求。 [9]
08 / TRANSACTION STATE
交易哈希只是“网络听见了”,不是“世界状态已经改变”
前端应当是一台状态机,而不是一个长时间转圈的按钮
一笔写交易可能在钱包阶段被拒绝,在 RPC 阶段广播失败,在 pending 阶段被替换, 被打包后因合约回滚得到失败收据,或成功后继续等待更强确定性。每一步都需要不同文案与恢复动作。
从点击按钮到可接受确定性的八个状态
UI state ≠ chain state| 前端状态 | 用户看到什么 | 系统知道什么 | 可恢复动作 |
|---|---|---|---|
| Awaiting signature | “请在钱包核对并确认” | 尚无链上交易,也不应伪造 hash | 允许取消;不要反复弹窗 |
| Pending | hash、网络、提交时间 | 已广播,但没有收据 | 持续查询;识别替换或超时 |
| Included / failed | “已打包,但执行回滚” | receipt.status = 0 | 解释错误;刷新链上真实状态 |
| Included / success | 区块、Gas、更新结果 | status = 1,日志和状态可读取 | 刷新 UI;按风险继续等待确认 |
| Replaced | “原交易被取消或加速” | 同账户同 nonce 出现另一交易 | 跟踪替代 hash,不把原 hash 永久挂起 |
钱包事件也是状态变化
EIP-1193 规定 Provider 的请求接口及 chainChanged、accountsChanged 等事件。
前端不能在首次连接后永远假定网络和账户不变;变化发生时,应重建 Provider/Contract 状态或明确刷新,
并移除旧监听器。用户拒绝请求的标准错误码是 4001,属于预期分支。
[11]
09 / SECURITY BOUNDARIES
安全检查不只看 Solidity:部署、前端与运维同样能把系统带偏
合约负责最终约束;前端负责不误导;发布流程负责不把错误版本送上链
链上代码不可轻易更改,因此开发阶段需要更高质量评估;但测试、静态分析、独立审查和形式化方法 都有边界。真正的纵深防御,是让同一错误必须穿过多层独立检查才能造成损失。 [12]
链上规则
权限、边界、不变量、外部调用、重入、事件与错误。前端校验不能替代链上校验。
可复现构建
锁定编译器与依赖,处理警告和已知缺陷,确保测试产物就是部署产物。
部署权限
独立账户、最小余额、硬件签名或受控密钥系统;记录谁在何时部署了什么。
网络与地址
按 chainId 映射地址和 ABI;启动时检查链上确有预期代码,不接受静默回退。
意图呈现
在签名前显示目标、方法、参数、金额与网络;把拒签、回滚和 pending 解释清楚。
监控与响应
观察事件、失败率、权限变化与前端版本;提前定义暂停、升级或迁移的责任边界。
最危险的秘密,往往泄露在“只是配置”里
可以进入前端
chainId、合约地址、ABI、区块浏览器 URL、公开 RPC URL 与功能开关。
- 仍需验证完整性与版本
- 不要把“环境变量”误叫秘密
只能留在受控服务端
有配额或计费权的 RPC key、监控凭证、后台服务权限与签名服务认证。
- 限制来源、速率与能力
- 泄露后可轮换并追踪
绝不进入代码或浏览器
部署私钥、助记词、多签签名材料、升级权与管理员控制根。
- 使用硬件或专用密钥系统
- 按职责、网络与资产隔离
LAB 04 / RELEASE GATE
你拥有的是“能部署”,还是“有资格讨论主网”?
逐项勾选。完成度只是教学提示,不能代替真实安全评审。
- 01 CORE
- 02 BUILD
- 03 E2E
- 04 VERIFY
- 05 CRITICAL
- 06 CRITICAL
主网闸门关闭:请先建立证据。清单全勾也只表示“可以进入正式发布评审”,不自动构成安全许可。
无 JavaScript 结论:验证源码与独立安全评审是两项不同工作;任何处理真实资产的系统都不应由一张通用清单自动放行。
10 / RELEASE GATES
Sepolia 全通过,也只说明你完成了彩排
主网不是下一个命令,而是风险、责任与可逆性都改变的决策
测试网能暴露整合问题,却没有同等资产诱因、流量、对手、费用压力和治理后果。 是否进入主网,应由资产规模、权限强度、升级模型、依赖关系和事件响应能力共同决定。
一份可交接的部署记录至少包含什么?
网络与实例
chainId、合约地址、部署交易、区块、deployer、构造参数。
构建与源码
Git 提交、依赖锁、编译器、优化设置、ABI、metadata 与字节码哈希。
权限与升级
owner、角色、多签阈值、时间锁、暂停权、升级权和密钥轮换流程。
观察与响应
关键事件、监控指标、前端版本、联系人、升级/迁移与事故流程。
11 / DEBUGGING
先定位失败在哪一层,再去读错误字符串
很多“合约坏了”,其实是网络、地址、ABI、账户或交易状态错位
调试最有效的第一问不是“为什么报错”,而是“请求有没有到达正确链上的正确地址, 使用正确 ABI,以正确账户和参数执行?”沿边界逐层缩小范围,比反复重试更可靠。
BAD_DATA / empty result
地址没有预期代码
先读 chainId,再用 eth_getCode 检查地址;常见原因是网络或部署地址错。
function selector mismatch
ABI 与 runtime code 不匹配
核对部署提交与前端构建;不要用“最新版 ABI”调用旧实例。
CALL_EXCEPTION
模拟或执行发生回滚
解析自定义错误,核对调用者、参数和前置状态;不要只显示“未知错误”。
4001
用户拒绝请求
这是正常产品分支;恢复按钮状态,保留输入,不自动再次弹出钱包。
4901 / wrong chain
Provider 未连接目标网络
明确展示期望和实际 chainId;切换后重建合约对象与读取状态。
insufficient funds
发送账户无法覆盖费用
核对的是当前网络余额。Sepolia 测试 ETH 与主网 ETH 互不相通。
nonce too low
账户已有同 nonce 交易
查询 pending nonce 和替代交易;不要盲目加一或无限重发。
hash forever pending
交易被延迟、丢弃或替换
检查费用、交易池与同 nonce hash;超时不是链上失败收据。
为什么“错误信息为空”不等于合约没有回滚原因?
回滚数据可能在 RPC、钱包或库的不同层级被包装、裁剪或无法解码。若前端 ABI 缺少对应 custom error, 即使合约返回了结构化错误,库也可能只能显示原始数据。因此发布时应让 ABI 与部署字节码同源, 并在本地测试里覆盖每种预期错误,确认前端能把它转换成对用户有意义的解释。
12 / RECAP
把整课压成六个动词:定义、编译、挑战、部署、交互、观察
每一步都有独立输入、输出和失败条件
= reproducible build × adversarial tests × correct deployment identity × honest transaction UX × operational readiness
ABI 不是代码
ABI 描述接口;真正执行的是该地址上的 runtime bytecode。
地址不是全局坐标
至少要与 chainId 配对,才能定位网络中的实例。
hash 不是成功
等收据、查 status、处理替换,再刷新链上真实状态。
验证不是审计
字节码匹配提高可检查性,不证明需求正确或没有漏洞。
13 / SELF CHECK
六题,检验你是否真的走通了闭环
先作答,再对照解释;每题都在检查一个边界
01ABI 的主要职责是什么?
答案 A。ABI 是接口说明;EVM 执行字节码,实例还必须由 chainId 与 address 定位。
02await contract.incrementBy(3) 返回后,最准确的判断是什么?
答案 B。广播成功不等于入块,更不等于执行成功;应等待 receipt、检查 status,再刷新状态。
03为什么合约地址不能脱离网络唯一定位实例?
答案 C。网络身份与地址必须配对;前端配置至少以 (chainId, address) 为键。
04外部直接读取 view 函数为什么通常不支付 Gas?
答案 A。“调用者不付 Gas”不等于“没有计算”;关键是执行是否成为链上交易的一部分。
05区块浏览器显示“源码已验证”意味着什么?
答案 B。源码验证解决“公开源码是否对应链上代码”,不解决“这段代码是否符合正确需求并足够安全”。
06哪条发布顺序最合理?
答案 C。每一层都先建立证据,再扩大公开性、不可逆性和真实价值风险。
14 / GLOSSARY
把经常混在一起的词拆开
术语清楚,调试路径才会清楚
- ABI
- Application Binary Interface;描述函数、参数、返回值、事件与错误如何编码和解码。
- creation bytecode
- 创建交易中执行的初始化代码,结合构造参数生成最终 runtime bytecode 与初始存储。
- runtime bytecode
- 部署成功后保存在合约地址、由后续调用实际执行的 EVM 字节码。
- artifact
- 编译器或框架生成的构建产物,通常包含 ABI、字节码、metadata 与调试信息。
- chainId
- 链的身份编号;用于网络配置,并进入现代签名交易的重放保护。
- Provider
- 读取区块链状态、发送 RPC 请求、广播交易并观察网络的连接抽象;不必拥有私钥。
- Signer
- 代表某账户授权并签署消息或交易的抽象;浏览器中通常由钱包控制。
- eth_call
- 在指定区块状态上模拟消息调用,不创建链上交易,常用于读取和写交易预检。
- receipt
- 交易被区块包含后的执行收据,包含 status、Gas 使用、日志与创建合约地址等信息。
- confirmation
- 包含交易的区块之后又出现的区块数量;是应用等待策略,不等同于 PoS 协议 finality。
- source verification
- 用给定源码和设置重编译并与部署字节码比对,不等于安全审计或形式化验证。
- invariant
- 在所有允许状态与调用序列中都应成立的性质,例如 count 永不超过 MAX_COUNT。
15 / PRIMARY SOURCES
继续深入的一手资料
课程按 2026-07-26 的官方资料核对;工具细节更新时,以这些文档为准
Technical introduction to dapps
DApp 的合约与前端结构,以及链上后端的基本边界。
Compiling smart contracts
源码、EVM 字节码与前端 ABI 的关系。
EIP-155: Simple replay attack protection
chainId 如何进入签名交易并降低跨链重放风险。
Testing smart contracts
单元、集成、性质测试、人工测试与形式化验证的边界。
Ethereum networks
Sepolia 与 Hoodi 的当前用途,以及应用测试网选择。
Development networks
本地开发网络与公开测试网的能力和使用场景。
Deploying smart contracts
合约创建交易、字节码、Gas、节点访问与部署工具。
Verifying smart contracts
源码验证、部分匹配、完整匹配与安全评审的区别。
JSON-RPC API
eth_call、Gas 估算、发送交易与收据等节点接口。
Transactions
签名交易、费用、nonce、输入数据与执行流程。
EIP-658: Embedding transaction status code in receipts
收据 status 字段如何表达执行成功或失败。
EIP-1193: Ethereum Provider JavaScript API
Provider 请求接口、错误码、网络与账户变化事件。
Contract ABI Specification
函数选择器、参数、返回值、事件与错误的编码规范。
Contract Metadata
编译 metadata、源码哈希、接口生成与源码验证。
Security Considerations
Solidity 安全陷阱、建议、限制与编译器警告。
Smart contract security
安全开发周期、分析方法、独立审查与运行期安全。
Getting started with Hardhat 3
当前项目初始化、目录结构、Node 要求与测试入口。
Testing overview
Solidity / TypeScript 测试、覆盖率、Gas 统计与测试策略。
Deploying with Hardhat Ignition
声明式部署模块、网络部署、参数与部署恢复。
Managing config values and secrets
配置变量与部署秘密的安全管理方式。
Running a local development node
持久本地节点以及脚本、钱包和前端的共享环境。
Providers
BrowserProvider、网络、交易等待、Signer 与 EIP-1193 包装。
Contracts
Contract、ABI、read/write runner、事件与交易收据。
Anvil
另一种快速本地 EVM 节点路径,以及分叉、时间与状态控制能力。