Skip to content
On this page

L1-3: ETH Streaming(ETH 流支付)

1. 问题

创建基于时间的 ETH 流支付合约。接收者根据距上次提款的时间长短,按比例解锁 ETH。核心公式:

已解锁 = (经过时间 * cap) / unlockTime

2. 原因

传统支付方式(月薪、按项目支付)不适合持续服务场景(如按秒计费的流媒体、实时 API 调用)。流支付允许接收方随时提取已解锁部分,无需信任支付方手动转账。

3. 方案

核心数据结构

solidity
struct Stream {
    uint256 cap;                    // 单次最大可提取量
    uint256 timeOfLastWithdrawal;   // 上次完全提取的时间戳 (0 = 新流)
}

关键机制

  1. 新流: timeOfLastWithdrawal = 0 → 完整 cap 立即可用
  2. 完全提取后: timeOfLastWithdrawal = block.timestamp → 时间从零开始累积
  3. 部分提取: timeOfLastWithdrawal 按剩余量反向偏移

4. 遇到的陷阱 ⭐

4.1 timeOfLastWithdrawal 不能简单重置

这是本关最重要的陷阱。 当接收者部分提取时,timeOfLastWithdrawal 不能直接设为 block.timestamp

4.2 算术下溢

计算 timeOfLastWithdrawal 偏移时,(remaining * unlockTime) / cap 可能大于 block.timestamp,导致下溢。

4.3 cap 上限

解锁量不应超过 cap。当 timeOfLastWithdrawal == 0 时的特殊处理。

5. 陷阱的原因

5.1 详细解释

假设 cap = 10 ETH,unlockTime = 100 秒:

  1. 新流创建(0 秒),10 ETH 可用
  2. t=50 秒:提取 3 ETH(剩余 7 ETH 可用)
  3. 如果设 timeOfLastWithdrawal = 50,到 t=80 秒时经过 = 30 秒,解锁 = 3 ETH — 但剩余 7 ETH 丢失了!

正确做法:

shift = (remainingUnlocked * unlockTime) / cap
     = (7 * 100) / 10 = 70 秒
timeOfLastWithdrawal = block.timestamp - shift
                     = 50 - 70 = 0  // 等价于"完整cap仍可用"

到 t=80 秒:经过 = 80-0 = 80 秒,解锁 = 8 ETH。但剩余 7 + 新解锁 8 > cap(10),所以实际可用 = min(8, 10) = 8 ETH...

实际上这里需要更多分析。当 shift 回溯到 0 以下时,表示「完整 cap 立即可用」,将 timeOfLastWithdrawal 设为 0。

6. 如何解决陷阱

6.1

solidity
uint256 remaining = unlocked - amount;
if (remaining == 0) {
    stream.timeOfLastWithdrawal = block.timestamp;
} else {
    uint256 shift = (remaining * unlockTime) / stream.cap;
    if (shift >= block.timestamp) {
        stream.timeOfLastWithdrawal = 0; // 等价于完整cap可用
    } else {
        stream.timeOfLastWithdrawal = block.timestamp - shift;
    }
}

6.2

在计算 block.timestamp - shift 前先检查 shift >= block.timestamp

6.3

_getUnlockedtimeOfLastWithdrawal == 0 时直接返回 cap,正常情况返回 min(cap, (elapsed * cap) / unlockTime)

7. 技术要点

要点说明
流支付数学模型线性解锁:(elapsed * cap) / unlockTime
虚拟时间戳通过偏移 timeOfLastWithdrawal 保留剩余额度
访问控制Ownable 模式限制 addStream
receive()允许任何人向合约注资
CEI 模式Checks-Effects-Interactions:先更新状态再转账

Built with AiAda