Skip to content
On this page

L1-7: Dead Man's Switch(死人开关)

1. 问题

创建合约让用户存入 ETH,设置定时签到机制。如果用户在配置的时间间隔内未签到,指定的受益人可提取其资金。用户自己始终可以提取。

2. 原因

现实场景:加密货币持有者可能因意外(死亡、失踪、丢失私钥)无法访问资产。Dead Man's Switch 提供「数字遗产」机制:

  • 运营者定期签到证明「仍然活跃」
  • 失联后资金自动转给预先指定的受益人
  • 无需信任第三方托管

3. 方案

核心机制

  • 每个用户设置自己的 checkInInterval
  • deposit() / checkIn() 更新 lastCheckIn[user]
  • 账户持有者永远可以提取
  • 受益人仅当 block.timestamp > lastCheckIn[account] + checkInInterval[account] 时可提取

数据结构

solidity
mapping(address => uint256) public balanceOf;
mapping(address => uint256) public lastCheckIn;
mapping(address => uint256) public checkInInterval;
mapping(address => address[]) public beneficiaries;

4. 遇到的陷阱

4.1 受益人无需设置 interval

如果用户未设置 checkInIntervaldeadline = lastCheckIn + 0 = lastCheckIn,这会导致 block.timestamp > deadline 始终为 true,受益人立即可提取。

4.2 存款时自动签到

deposit() 应同时更新 lastCheckIn,否则新存款后可能立即被认为「失联」。

4.3 受益人列表管理

removeBeneficiary 需要从数组中删除元素。使用 swap-and-pop 模式 O(1)。

5. 陷阱的原因

5.1

checkInInterval 默认为 0。lastCheckIn + 0 = lastCheckIn,任何 block.timestamp > lastCheckIn 都满足条件。需显式检查 interval == 0

5.2

如果存款不更新 lastCheckIn,用户刚存入大额 ETH 就可能被受益人提取(因为之前的 interval 可能已过)。

5.3

Solidity 数组无内置删除方法。使用 list[i] = list[last]; list.pop() 是最优解。

6. 如何解决陷阱

solidity
function withdraw(address account, uint256 amount) external {
    if (!isAccountHolder) {
        uint256 interval = checkInInterval[account];
        if (interval == 0) revert NotExpired(); // 必须显式设置
        if (block.timestamp <= lastCheckIn[account] + interval) revert NotExpired();
    }
    // ...
}

7. 技术要点

要点说明
时间条件访问控制block.timestamp 比较
受益人模式多受益人共享提取权限
签到机制每次交互自动续期
数组 swap-and-popO(1) 删除
receive() 存款直接 ETH 转账自动处理

Built with AiAda