Ethereum / DApp delivery / Lesson 22.02

一份代码,怎样变成真正可用的 DApp?

源码写完,只是起点。它还要变成可执行的字节码,在可重复环境里经受测试, 通过一笔部署交易获得网络身份与合约地址,最后由前端把用户意图编码成 可读、可签、可追踪的链上交互。

  • 正式目录:DApp 开发
  • 约 75 分钟
  • 4 个离线实验
  • 6 题自检
01 / TESTS ARE EVIDENCE

测试不是“跑过一次”。它把需求变成可重复证据,并主动寻找错误路径、权限缺口与边界条件。

02 / DEPLOYMENT HAS IDENTITY

地址不能脱离网络。一个实例至少由 chainId + address 唯一定位,ABI 才告诉前端如何与它说话。

03 / HASH IS NOT SUCCESS

拿到交易哈希不等于执行成功。前端必须处理签名、pending、receipt、revert、替换与网络变化。

01 / ORIENTATION

先看终点:你交付的不是源码,而是一条可验证链路

DApp 的“后端”公开运行在网络上,前端只是把人与协议接起来

DApp 不是“网页里加一个连接钱包按钮”。它由链上合约与用户界面共同组成; 合约像公开 API,但每次写入都要经过签名、网络传播、区块执行与状态确认。 [1]

一条从意图到可观察结果的交付链

8 boundaries
任一边界错位都可能制造“页面看起来正常,链上却不是那回事”:ABI 与代码版本不匹配、地址属于另一条链、交易只广播未确认,都是典型例子。
SPEC

行为证据

需求、权限、错误路径与不变量。测试必须对应它们,而不是只追求覆盖率数字。

BUILD

构建证据

源码、依赖锁、编译器版本、优化设置、ABI、字节码与 metadata。

DEPLOY

部署证据

网络、部署账户、交易哈希、收据、合约地址、构造参数和部署记录。

OPERATE

运行证据

前端网络配置、源码验证、监控、权限归属、故障告警与事件响应。

02 / ARTIFACT MAP

先认清四件套:字节码、ABI、地址与 chainId

“我有合约地址”还不足以让前端安全交互

Solidity 源码是给人读的;EVM 执行字节码;前端依靠 ABI 编码函数调用; 地址只在某一条链的状态空间中定位实例。编译就是把一份人类描述拆成不同消费者需要的产物。 [2]

同一次编译,服务三个不同读者

human / EVM / frontend
ABI 不会让不安全的调用变安全;它只是精确描述“怎样编码、怎样解码”。真正的权限仍由合约在链上执行。
BYTECODE

EVM 执行什么

creation bytecode 负责构造实例;runtime bytecode 是部署后地址真正执行的代码。

0x60806040…
ABI

调用怎样编码

函数、参数、返回值、事件与自定义错误的机器可读接口说明。

incrementBy(uint256)
ADDRESS

实例在哪里

20 字节账户地址。相同地址在另一条链上可能没有代码,或是完全不同的代码。

0x7A…42F1
CHAIN ID

它属于哪条链

网络身份,也是签名交易防跨链重放的一部分。主网为 1,Sepolia 为 11155111。

11155111
可调用实例 = chainId + contract address + matching ABI
可复现部署 = 上述信息 + source + compiler version + build settings + constructor arguments

为什么同一个 0x… 不能脱离网络理解?

每条链维护独立世界状态。一个私钥可以在多条 EVM 链上导出同一个 EOA 地址,但该地址的余额、 nonce、合约代码和存储互不相通。前端若只保存地址、不绑定 chainId,就可能把 Sepolia 的 ABI 配到主网地址,或者让用户在错误网络上签名。EIP-155 把 chainId 纳入交易签名,降低跨链重放风险, 但不会替前端检查地址配置[3]

03 / EXAMPLE CONTRACT

用一个小合约,贯穿测试、部署与前端

功能越小,越容易看清每一道工程边界

LearningCounter 不收 ETH、不调用外部合约,只保留状态、权限、边界、事件与错误。 这让我们能把注意力放在交付链路上,而不是把“能部署”误当成“适合管理真实资产”。

contracts/LearningCounter.sol
// 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);
    }
}

教学合约不承载资金。实际生产合约还需要与资产规模、依赖和升级模型相称的设计与独立审查。

  1. 01 / STATEcount 是链上持久状态;公开 getter 是只读入口。
  2. 02 / AUTHORITYowner 在部署时固定;只有它能降低或重设计数。
  3. 03 / BOUNDARYMAX_COUNT 是业务不变量,不是 EVM 自带规则。
  4. 04 / ERROR自定义错误让测试和前端区分“零输入、越界、无权限”。
  5. 05 / EVENT事件记录 actor、旧值与新值,便于界面和索引器观察变化。
  6. 06 / OVERFLOW先检查 amount > MAX - count,避免在错误信息构造前先触发算术 Panic。

从“函数能跑”升级到“不变量可陈述”

INVARIANT 01

范围不越界

任意成功交易后,始终满足 0 ≤ count ≤ MAX_COUNT

INVARIANT 02

递增不下降

increment* 成功后,新值严格大于旧值。

INVARIANT 03

权限不旁路

非 owner 不能通过 reset 改变状态。

INVARIANT 04

日志与状态一致

每次成功写入都发出与实际 previous/current 对应的事件。

04 / LOCAL TESTING

本地测试不是证明“我会调用函数”,而是持续挑战承诺

正常路径只回答“能不能”,失败路径才开始回答“守不守得住”

智能合约部署后难以随意替换,测试至少是安全底线。单元、集成、性质测试、静态分析、 人工复核各自看见不同问题;任何一个绿色勾都不能单独证明“没有漏洞”。 [4]

BUILD

编译与静态检查

类型、可见性、警告、已知编译器问题与明显代码模式。

UNIT

单元测试

初始值、正常递增、事件、错误类型、权限与边界。

PROPERTY

Fuzz / invariant

让工具生成大量输入和调用序列,寻找破坏不变量的反例。

INTEGRATION

本地集成

多账户、多交易、事件解析、前端、持久节点与错误反馈。

E2E

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 / conceptual workflow
# 初始化当前 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。

ARRANGE初始条件count = 7,调用者 = Alice
ACT执行动作Alice 调用 incrementBy(3)
ASSERT断言结果count = 10,并发出对应事件

PASS:这个样例证明给定前置状态和输入时,状态与事件符合预期;它没有证明其他输入或调用序列都安全。

无 JavaScript 结论:测试应同时覆盖成功、回滚、权限、边界与长期性质;覆盖率高不等于断言质量高。

05 / ENVIRONMENTS

本地链、Sepolia 与主网,不是三个速度档位

它们分别回答“逻辑可重复吗”“产品能端到端工作吗”“是否值得承担真实后果”

Sepolia 是当前普通合约和应用开发的默认测试网;Hoodi 主要服务协议升级、验证者与质押测试。 测试网 ETH 按设计没有现实价值,不应购买。[5]

LOCAL / 31337

开发链

只在你的开发环境存在,适合快速、确定性、可重置的测试。

  • 预置账户与测试余额
  • 可操纵时间、区块与状态
  • 节点停止后状态可消失
  • 默认私钥公开,只能用于本地
SEPOLIA / 11155111

公开测试网

让真实钱包、RPC、浏览器和多人前端在公开环境完成端到端演练。

  • 公开传播、真实等待与费用机制
  • 测试 ETH 无现实价值
  • 合约与交易对所有人可见
  • 错误不能像本地快照一样撤销
MAINNET / 1

生产网络

真实资产、真实对手、永久记录和组织责任都进入系统。

  • 部署与调用消耗真实 ETH
  • 任意用户和机器人都可交互
  • 升级与应急都有信任成本
  • 必须经过独立发布闸门

同一个地址,在三条链上是三份独立状态

address ≠ global identity
不要把本地开发账户的公开助记词或私钥带到任何公开网络;测试账户也应与真实资产账户彻底隔离。

什么时候从本地进入 Sepolia?

当单元、边界与不变量测试稳定,本地前端已能重现完整读写流程,并且你已经能解释每个签名请求时, 才把同一构建产物送入 Sepolia。测试网的目标不是继续“试着改代码”,而是验证真实整合: 网络配置、部署密钥、RPC、钱包、地址、ABI、浏览器兼容、区块等待和错误反馈是否共同工作。

06 / DEPLOYMENT

部署不是上传文件,而是一笔创建合约的交易

交易被打包并成功执行后,网络才产生那个地址上的 runtime code

普通转账的 to 指向接收账户;合约创建交易没有普通接收方,输入数据包含 creation bytecode 与构造参数。成功收据会给出新合约地址。[6]

创建交易进入区块前后,产物怎样变化

transaction → receipt → code
如果构造器回滚或 Gas 不足,交易仍可能被打包并消耗 Gas,但不会留下可用合约实例。

部署前的四项输入

NETWORK

可信 RPC 与 chainId

RPC 是访问节点的通道,不是网络本身。连接后仍要读取并校验实际 chainId。

ACCOUNT

独立部署账户

只放足够的测试 ETH;私钥存入受控密钥系统或配置变量,不写入仓库。

BUILD

固定构建产物

锁定源码、依赖、编译器和优化设置,避免测试与部署不是同一份代码。

RECORD

可恢复部署记录

保存参数、交易哈希、地址与部署状态,让重试不会误发第二份实例。

ignition/modules/LearningCounter.ts
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 };
  },
);

声明式部署会记录已完成步骤并支持恢复;部署记录仍应纳入版本化发布证据,而不是只抄终端里最后一行地址。

deploy sequence
# 先在 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

你准备把这一份构建部署到哪里?

切换环境,观察同一操作的身份、公开性与风险含义。

Build冻结源码与编译设置
Simulate执行部署计划与构造器
Sign校验 chainId 与交易字段
BroadcastRPC 返回交易哈希
Receipt检查 status 与 address
Verify复现并比对字节码
Publish按 chainId 配置前端

LOCAL:快、私有、可重置。适合反复验证部署模块与前端,但本地预置 ETH、即时出块和公开测试密钥都不能代表生产环境。

无 JavaScript 结论:先本地、再 Sepolia;只有经过与真实价值相称的独立安全闸门,才讨论主网。

源码验证究竟证明什么?

验证服务会用你提交的源码、编译器、设置、库地址与构造参数重新编译,再把结果与该地址的部署字节码比对。 匹配说明“这份可读源码能够重现链上代码”,方便独立检查;它不是安全审计,更不是业务逻辑正确证明[7]

Source verification: advertised source ↔ deployed bytecode
Security review: requirements ↔ source behavior ↔ adversarial conditions

07 / FRONTEND INTERACTION

前端没有“调用合约”这一个动作,只有读与写两条完全不同的路

Provider 连接状态;Signer 代表用户授权状态改变

只读调用通常通过 eth_call 在节点上模拟执行,不写入区块,也无需用户签名; 写调用必须构造交易、估算 Gas、请求签名、广播并等待收据。 [8]

同一个 Contract 对象,连接 Provider 或 Signer 会开放不同能力

eth_call ≠ transaction
“读调用免费”只表示调用者不支付链上 Gas;RPC 节点仍然消耗计算资源。若另一个合约在一笔链上交易中调用同一 view 函数,该执行仍计入交易 Gas。
ethers v6 / browser interaction
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 中断、重组与组件卸载后的监听清理。

前端配置必须按网络组织

deployments.ts / public configuration
export const deployments = {
  31337n: {
    name: "Localhost",
    LearningCounter: "0x5FbD…0aa3",
  },
  11155111n: {
    name: "Sepolia",
    LearningCounter: "0x7A93…42F1",
  },
} as const;

合约地址、chainId 和公共 RPC URL 不是秘密;部署私钥、助记词和服务端特权凭证才是。任何进入浏览器包的“环境变量”都应按公开数据对待。

LAB 03 / RPC ROUTER

前端此刻应该读、签、阻断,还是解释错误?

选择情景,观察请求类型、权限与返回对象。

REQUESTeth_call当前 JSON-RPC 路径
SIGNATURE不需要是否需要用户授权
GAS不支付是否形成链上交易

READ:直接显示当前区块状态,并标明数据来自哪条链;不要为了读取公共状态强迫用户先连接钱包。

无 JavaScript 结论:读返回值,写返回交易响应;错误网络应在签名前阻断,用户拒绝是正常交互分支而不是系统故障。

事件不是装饰,也不是所有数据的永久数据库

外部账户发送写交易时,Solidity 函数的返回值不会像普通 HTTP 响应那样直接送回前端。 前端通常通过收据中的日志、随后读取的状态或索引服务确认结果。事件适合描述可观察变化, 但合约自身不能像读取 storage 那样读取历史日志;事件字段与索引设计要服务真实查询需求。 [9]

08 / TRANSACTION STATE

交易哈希只是“网络听见了”,不是“世界状态已经改变”

前端应当是一台状态机,而不是一个长时间转圈的按钮

一笔写交易可能在钱包阶段被拒绝,在 RPC 阶段广播失败,在 pending 阶段被替换, 被打包后因合约回滚得到失败收据,或成功后继续等待更强确定性。每一步都需要不同文案与恢复动作。

从点击按钮到可接受确定性的八个状态

UI state ≠ chain state
EIP-658 在收据中加入执行状态字段,使前端能区分成功与失败;仅保存 tx hash 不足以判断结果。[10]
前端状态 用户看到什么 系统知道什么 可恢复动作
Awaiting signature “请在钱包核对并确认” 尚无链上交易,也不应伪造 hash 允许取消;不要反复弹窗
Pending hash、网络、提交时间 已广播,但没有收据 持续查询;识别替换或超时
Included / failed “已打包,但执行回滚” receipt.status = 0 解释错误;刷新链上真实状态
Included / success 区块、Gas、更新结果 status = 1,日志和状态可读取 刷新 UI;按风险继续等待确认
Replaced “原交易被取消或加速” 同账户同 nonce 出现另一交易 跟踪替代 hash,不把原 hash 永久挂起

钱包事件也是状态变化

EIP-1193 规定 Provider 的请求接口及 chainChangedaccountsChanged 等事件。 前端不能在首次连接后永远假定网络和账户不变;变化发生时,应重建 Provider/Contract 状态或明确刷新, 并移除旧监听器。用户拒绝请求的标准错误码是 4001,属于预期分支。 [11]

09 / SECURITY BOUNDARIES

安全检查不只看 Solidity:部署、前端与运维同样能把系统带偏

合约负责最终约束;前端负责不误导;发布流程负责不把错误版本送上链

链上代码不可轻易更改,因此开发阶段需要更高质量评估;但测试、静态分析、独立审查和形式化方法 都有边界。真正的纵深防御,是让同一错误必须穿过多层独立检查才能造成损失。 [12]

01 / CONTRACT

链上规则

权限、边界、不变量、外部调用、重入、事件与错误。前端校验不能替代链上校验。

02 / BUILD

可复现构建

锁定编译器与依赖,处理警告和已知缺陷,确保测试产物就是部署产物。

03 / DEPLOYER

部署权限

独立账户、最小余额、硬件签名或受控密钥系统;记录谁在何时部署了什么。

04 / CONFIG

网络与地址

按 chainId 映射地址和 ABI;启动时检查链上确有预期代码,不接受静默回退。

05 / FRONTEND

意图呈现

在签名前显示目标、方法、参数、金额与网络;把拒签、回滚和 pending 解释清楚。

06 / OPERATIONS

监控与响应

观察事件、失败率、权限变化与前端版本;提前定义暂停、升级或迁移的责任边界。

最危险的秘密,往往泄露在“只是配置”里

PUBLIC

可以进入前端

chainId、合约地址、ABI、区块浏览器 URL、公开 RPC URL 与功能开关。

  • 仍需验证完整性与版本
  • 不要把“环境变量”误叫秘密
SERVER SECRET

只能留在受控服务端

有配额或计费权的 RPC key、监控凭证、后台服务权限与签名服务认证。

  • 限制来源、速率与能力
  • 泄露后可轮换并追踪
KEY MATERIAL

绝不进入代码或浏览器

部署私钥、助记词、多签签名材料、升级权与管理员控制根。

  • 使用硬件或专用密钥系统
  • 按职责、网络与资产隔离

LAB 04 / RELEASE GATE

你拥有的是“能部署”,还是“有资格讨论主网”?

逐项勾选。完成度只是教学提示,不能代替真实安全评审。

  • 01 CORE
  • 02 BUILD
  • 03 E2E
  • 04 VERIFY
  • 05 CRITICAL
  • 06 CRITICAL
COMPLETION0 / 6教学清单完成度
SEPOLIA可继续学习使用独立测试账户
MAINNET GATELOCKED关键项未满足

主网闸门关闭:请先建立证据。清单全勾也只表示“可以进入正式发布评审”,不自动构成安全许可。

无 JavaScript 结论:验证源码与独立安全评审是两项不同工作;任何处理真实资产的系统都不应由一张通用清单自动放行。

10 / RELEASE GATES

Sepolia 全通过,也只说明你完成了彩排

主网不是下一个命令,而是风险、责任与可逆性都改变的决策

测试网能暴露整合问题,却没有同等资产诱因、流量、对手、费用压力和治理后果。 是否进入主网,应由资产规模、权限强度、升级模型、依赖关系和事件响应能力共同决定。

一份可交接的部署记录至少包含什么?

IDENTITY

网络与实例

chainId、合约地址、部署交易、区块、deployer、构造参数。

REPRODUCE

构建与源码

Git 提交、依赖锁、编译器、优化设置、ABI、metadata 与字节码哈希。

AUTHORITY

权限与升级

owner、角色、多签阈值、时间锁、暂停权、升级权和密钥轮换流程。

OPERATE

观察与响应

关键事件、监控指标、前端版本、联系人、升级/迁移与事故流程。

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;超时不是链上失败收据。

chainId → address has code → ABI matches → caller & arguments → simulation → signature → broadcast → receipt.status → refreshed state

为什么“错误信息为空”不等于合约没有回滚原因?

回滚数据可能在 RPC、钱包或库的不同层级被包装、裁剪或无法解码。若前端 ABI 缺少对应 custom error, 即使合约返回了结构化错误,库也可能只能显示原始数据。因此发布时应让 ABI 与部署字节码同源, 并在本地测试里覆盖每种预期错误,确认前端能把它转换成对用户有意义的解释。

12 / RECAP

把整课压成六个动词:定义、编译、挑战、部署、交互、观察

每一步都有独立输入、输出和失败条件

DApp delivery quality
= reproducible build × adversarial tests × correct deployment identity × honest transaction UX × operational readiness
REMEMBER 01

ABI 不是代码

ABI 描述接口;真正执行的是该地址上的 runtime bytecode。

REMEMBER 02

地址不是全局坐标

至少要与 chainId 配对,才能定位网络中的实例。

REMEMBER 03

hash 不是成功

等收据、查 status、处理替换,再刷新链上真实状态。

REMEMBER 04

验证不是审计

字节码匹配提高可检查性,不证明需求正确或没有漏洞。

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 的官方资料核对;工具细节更新时,以这些文档为准

ETHEREUM.ORG / 04

Testing smart contracts

单元、集成、性质测试、人工测试与形式化验证的边界。

ETHEREUM.ORG / 05

Ethereum networks

Sepolia 与 Hoodi 的当前用途,以及应用测试网选择。

ETHEREUM.ORG / 06

Development networks

本地开发网络与公开测试网的能力和使用场景。

ETHEREUM.ORG / 09

JSON-RPC API

eth_call、Gas 估算、发送交易与收据等节点接口。

ETHEREUM.ORG / 10

Transactions

签名交易、费用、nonce、输入数据与执行流程。

SOLIDITY / 14

Contract Metadata

编译 metadata、源码哈希、接口生成与源码验证。

HARDHAT / 18

Testing overview

Solidity / TypeScript 测试、覆盖率、Gas 统计与测试策略。

ETHERS / 22

Providers

BrowserProvider、网络、交易等待、Signer 与 EIP-1193 包装。

ETHERS / 23

Contracts

Contract、ABI、read/write runner、事件与交易收据。

FOUNDRY / 24

Anvil

另一种快速本地 EVM 节点路径,以及分叉、时间与状态控制能力。

ETHEREUM · CHAPTER 22 · LESSON 02

真正的交付,不是让交易发出去,而是让每一步都可解释、可验证、可恢复。