Appearance
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
如果用户未设置 checkInInterval,deadline = 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-pop | O(1) 删除 |
| receive() 存款 | 直接 ETH 转账自动处理 |