合成订单改用代币后,买方先 approve,托管再 transferFrom。授权额度允许托管在限额内发起转移,不是卖家已经收到 70;approve 成功也不意味着实际 token 转入。区块链(24):fuzz 和 invariant 要围住哪笔资产中的义务单位必须先冻结为某一个 token 的最小单位,否则 UI 把“70.00”与合约整数 70 混用会导致严重错账。

sequenceDiagram
  participant B as 买方
  participant T as ERC20 合约
  participant E as 托管合约
  B->>T: approve(E, allowance)
  B->>E: 建立订单并指定资产/数量
  E->>T: transferFrom(B, E, amount)
  T-->>E: 返回值与余额变化需按 token 行为验证
  E->>E: 按实际资产和权限记录债权

授权有竞争与遗留额度风险:用户修改额度的交易与第三方支出可能以不同顺序入块。代币还有失败返回值或不按预期返回、转账收取费用、余额随外部规则变化等行为;对接方不能把“调用没 revert”一律写成收到名义金额。若承诺“合约严格拥有 70”,应限制资产为满足该假设的实现,或记录转入前后余额差并处理重入、重基等额外风险。decimals 更像展示约定,不代替合约单位核算。

名义转账额与实际收到的资产

假设买方批准托管可从其余额划走 70,实际 transferFrom 的资产只到账 68。合约若按调用参数 70 给卖家建债,available=68 < liabilities=70;即使调用没有 revert,账本已经无力兑现。一个常见防线是核对操作前后余额差,并只接受完全符合本订单资产假设的 token;但如果外部余额可重新定价或转账会发生额外外调,简单读两次余额也不能自动保证长期守恒。支持哪类 token、是否拒绝收取转账费以及允许谁紧急暂停,应作为明确的资产策略,而不是把 ERC-20 接口名当成唯一语义。

此外,买家把授权从 70 改成 20,在两笔交易之间第三方可能仍按旧额度花费,后端的“我看到新 approve 的哈希”不能决定执行顺序。签名代付与 permit 类路径还需按签名域、nonce 和有效期验证,不能复用普通链下订单 ID 作为 token 授权的去重凭证。组合 Uniswap/Aave 一类协议时,还要面对池报价/滑点、预言机时效或抵押清算等额外控制权,不属于教学托管的必需组件。没有真实 token 与合约,本篇不声称哪一个项目目前支持何种变体。

练习一:买方给托管授权 70,但没有调用 transferFrom,订单能登记可领取 70 吗?答案:不能,资产尚未入托管。练习二:代币到账实际只有 68,系统仍按 70 记债会怎样?答案:liabilities > assets;要拒绝或按明示规则处理差额,不可无声通过。没有冻结 OpenZeppelin/token 版本、代币合约、Forge 环境,正反例 NOT_RUN。

可迁移原则:接口名相同不意味着资产流转语义相同。参考:ERC-20 标准、OpenZeppelin ERC20/SafeERC20(实现 SHA 与非标准样本待核);导航:区块链(24):fuzz 和 invariant 要围住哪笔资产 · 25 · 区块链(26):代理升级把信任转移给了谁。