Appearance
L1-13: Name System(名称システム)
1. 課題
分散型オンチェーン名前登録システムを構築します。人間が読める名前をEthereumアドレスにマッピングする、ENS(Ethereum Name Service)の簡易版に相当するシステムです。コア機能:名前の登録(有料)、所有権の移転、解決先アドレスの設定、名前の更新、期限切れによる解放。
2. なぜ重要か
ENS は Web3 の最も重要なインフラの一つです。vitalik.eth のような名前を 0x... アドレスにマッピングします。名前登録システムのコアパターンを理解することは、あらゆるオンチェーンアイデンティティシステムを構築する基礎となります。
- 名前マッピング:人間が読める名前 -> マシンアドレス
- 所有権モデル:登録者が名前に対する完全な制御権を持つ
- 時間ロック:登録には有効期限があり、期限切れ後は他者に解放される
- 料金メカニズム:名前の先取り取得(スクワッティング)や買い占めを防止する
このチャレンジでは ENS を簡略化しています:マルチレベルドメイン(.eth サフィックスはレジストリが管理)やリゾルバコントラクトの分離は行わず、名前所有権管理のコアロジックに集中します。
3. 解決策
コントラクトアーキテクチャ
solidity
contract NameSystem {
struct NameRecord {
address owner; // 名前の所有者
address resolvedTo; // 解決先アドレス
uint256 expiresAt; // 有効期限タイムスタンプ
}
uint256 public constant REGISTRATION_FEE = 0.01 ether;
uint256 public constant REGISTRATION_PERIOD = 365 days;
uint256 public constant MIN_NAME_LENGTH = 3;
uint256 public constant MAX_NAME_LENGTH = 32;
mapping(bytes32 => NameRecord) private _records;
mapping(address => bytes32[]) private _ownedNames;
}
コアフロー
- 登録:
REGISTRATION_FEEを支払い、名前が未使用または期限切れで、フォーマットが有効であること - 移転:所有者が名前を新しいアドレスに transfer する
- 解決:
resolvedToを対象アドレスに設定する - 更新:料金を支払って有効期間を延長する
- 期限切れ再登録:期限切れ後、誰でも再登録できる
主要な実装詳細
- 名前は
bytes32(keccak256 ハッシュ)として保存し、プライバシーを保護し固定長を保証する _ownedNames配列で各アドレスが所有する名前のリストを追跡する- 名前フォーマット検証:a-z, A-Z, 0-9,
-のみ許可、長さ 3-32 - 移転時には swap-and-pop で旧所有者の配列から削除する
4. 遭遇した落とし穴
4.1 期限切れ名前の競合状態
2人のユーザーが同じブロック内で、同じ期限切れ直後の名前を登録しようとする可能性があります。Solidity のトランザクションはアトミックであるため、1つだけが成功し、もう1つはガスが無駄になります。
4.2 _ownedNames 配列の無限増大
ユーザーが頻繁に名前を登録・移転する場合、_ownedNames 配列での swap-and-pop 削除が正しく実行される必要があります。pop 後の新しい要素が重複している場合(誤った swap ロジックによる)、名前の追跡が失われる可能性があります。
4.3 名前フォーマット検証のパフォーマンス
_isValidName でのバイト単位のチェックは32バイトの名前に対して O(32) であり、ガス的には許容範囲です。しかし長さの上限を拡張すると、文字チェックのガスコストは線形に増加します。
4.4 ハッシュ衝突による誤った安心感
keccak256(name) をキーとして使用するため、2つの異なる名前が衝突することはありません。しかし、ハッシュから名前を逆算することもできないため、フロントエンドは名前からハッシュへのマッピングを維持する必要があります。
5. 落とし穴の原因
5.1
Ethereum のトランザクションは(単一ブロック内で)逐次的に実行され、従来の並行競合状態は存在しません。しかし、2つの保留中のトランザクションが同じビルダーによってバンドルされる可能性があります。ユーザーAの登録トランザクションとユーザーBの登録トランザクションのうち、1つだけが成功します。B は失敗したトランザクションのガスを支払います。
5.2
swap-and-pop は O(1) の配列削除テクニックですが、実装では pop される要素が確実に削除位置に移動され、配列の長さが正しく1減少することを保証する必要があります。よくあるバグ:誤ったインデックスの削除、ループの break 忘れ、pop 後の配列長の不一致。
5.3
Solidity にはネイティブの正規表現がありません。バイト単位の比較が唯一の方法です。大規模なデータセットの場合は、オフチェーンでフォーマットを検証し、署名経由で送信することを検討します。
6. 落とし穴の解決方法
- フロントエンドは
NameRegisteredイベントをリッスンして登録の成否を検出し、失敗時にはユーザーに通知する _removeFromOwnedで swap-and-pop を正しく使用する:soliditynames[i] = names[names.length - 1]; names.pop(); break;- 名前フォーマット検証ロジックを pure 関数に分離し、テストを容易にする
- OpenZeppelin の
Stringsライブラリを文字列比較の補助に使用する
7. 技術的ポイント
| ポイント | 説明 |
|---|---|
| ハッシュ保存 | keccak256(name) をキーとして使用し、プライバシーを保護 |
| 時間ゲーティング | block.timestamp 比較による期限切れメカニズム |
| 所有権パターン | NFT に類似した ownable リソースモデル |
| swap-and-pop | O(1) の配列要素削除 |
| pull-over-push 料金 | 登録料は直接支払い、別途引き出し不要 |
| フォーマット検証 | オンチェーンでのバイト単位の文字チェック |