后台拿到订单合约事件 Delivered(orderId) 就把业务表改成“最终交货”,至少跨过了两条未证明的边界:事件来自哪条规范历史,以及现实交货由谁证明。ABI 决定调用输入/输出如何从字节解释,合约 storage 决定持久状态,事件日志便于检索已执行路径;日志不是当前状态的唯一来源,也不会自己监督卖家。

先修区块链(20):教学托管合约先写清谁能推动状态。调用动态数据时,ABI 编码需要指向尾部数据的偏移和长度;calldata 承载外部调用的输入字节,memory 是一次执行的临时区域,storage 承载持久键值。将 storage 引用传给辅助函数可能原地改状态,将其复制到 memory 则不是同一个持久对象;具体 gas、布局和类型尺寸依赖编译器/EVM 目标,不能凭概念图推出物理槽号。

flowchart LR
  C[calldata: selector + ABI 参数] --> E[函数与内部调用]
  E --> M[memory: 临时构造]
  E --> S[storage: 持久订单状态]
  E --> L[日志: topics 与 data]
  S --> R[后状态根]
  L --> I[索引端: 可回滚的观察]

eth_call 可以在指定状态上模拟,读到返回值不产生链上写入;真实交易的执行回执携带状态与日志,两者都要绑定块哈希及其后续链地位。事件可以描述已执行的某个迁移,但读者如果只看到事件而看不到合约地址、topic 语义或确认策略,不应结算订单。

从一段字节到一条业务观察

设应用调用含订单 ID 和动态交付凭证字节的函数。外部 calldata 先有选择器,静态位置放订单编号,动态参数位置放指向尾部的偏移,尾部再以长度和填充后的字节表示凭证;长度和偏移基准若搞错,合约可能解出另一笔订单甚至直接拒绝。签名过“某笔订单”的文字描述不够,签名方、ABI 编码与接收合约必须对同一批字节达成一致。memory 中组装的凭证不会自动写回 storage;一个函数拿到 storage 引用后修改订单状态才会持久化,布局取决于确切类型及编译规则,不能把博客图中的行号当作 slot 编号。

订单合约可发出 Delivered(orderId) 事件,但索引必须同时记录发出事件的合约、链 ID、块哈希、交易和日志位置。如果恶意合约也发出同名事件,仅按主题字符串查询会污染业务表;如果旧块重组,日志历史的观察要撤销。即便是真实合约且在规范块内,事件也只证明合约执行到发射该事件的路径,仍不能证明现实配送。eth_call 读到 70 是指定前状态下的模拟值,不产生持久交易回执;实际值要按区块历史和合约当前状态重新核查。

练习一:函数把 orders[id].amount 复制到 memory 后只修改副本,会改持久余额吗?答案:一般不会,需明确修改 storage 引用或写回。练习二:eth_call 返回 claimable=70,能据此宣称真实提现交易成功吗?答案:不能;还需交易入块、回执、状态与最终性。ABI 编码向量、storage slot 与事件回执 NOT_RUN,不报告虚构槽号。

可迁移原则:输入字节、持久事实与检索视图分别验证。参考:Solidity ABI 规范、存储布局(编译器及源码 SHA 待核);导航:区块链(20):教学托管合约先写清谁能推动状态 · 21 · 区块链(22):签名域正确仍会发生业务重放。