Appearance
L1-7: Dead Man's Switch(デッドマンズスイッチ)
1. 問題
ユーザーがETHを預け入れ、定期的なチェックイン機構を設定するコントラクトを作成します。設定された時間間隔内にユーザーがチェックインしなかった場合、指定された受益者がその資金を引き出すことができます。ユーザー自身は常に引き出し可能です。
2. 理由
現実のシナリオ:暗号資産保有者は事故(死亡、失踪、秘密鍵の紛失)により資産にアクセスできなくなる可能性があります。デッドマンズスイッチは「デジタル遺産」の仕組みを提供します:
- 運営者は定期的にチェックインして「まだ活動中」であることを証明する
- 連絡不能になった場合、資金は事前に指定された受益者に自動的に移転する
- 第三者カストディアンを信頼する必要がない
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 受益者はインターバルを設定する必要がない
ユーザーが 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を預け入れたばかりのユーザーの資金が受益者に引き出される可能性があります(以前のインターバルが既に経過しているため)。
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 の比較 |
| 受益者パターン | 複数の受益者が引き出し権限を共有 |
| チェックイン機構 | 対話ごとに自動更新 |
| 配列のスワップ&ポップ | O(1) 削除 |
| receive() 入金 | 直接ETH転送を自動処理 |