Skip to content
On this page

L2-18: Token Curated Registry(トークンキュレーテッドレジストリ)

1. 課題

トークンステーキングメカニズムを用いて分散型の高品質リストを構築します。経済的インセンティブがリストコンテンツの品質を促進します。申請者はトークンをステークしてエントリをリストに追加します。任意のトークン保有者がチャレンジを開始できます(同様にステークが必要)。コミュニティが投票してエントリの存続を決定します。勝者は敗者のステークトークンを獲得します。

これは Token Curated Registry (TCR) パターンです。暗号経済的インセンティブを通じて、中央集権的な審査なしのキュレーションシステムを実現します。

2. なぜ重要か

Web3 の分散型の本質には、分散型のコンテンツ審査メカニズムが必要です。従来のインターネットは中央集権的なプラットフォーム(Yelp、App Store、Trustpilot)に依存してリストの品質を維持していますが、これらのプラットフォームには審査バイアス、検閲、データ独占の問題があります。

TCR は経済的インセンティブを通じてこれらの問題を解決します。

  • ステーキングはコミットメント:申請者は実際の価値(トークン)でエントリの品質を保証する
  • チャレンジは抑制と均衡:低品質のエントリを発見した人は誰でもチャレンジでき、勝てば報酬を得る
  • 投票はガバナンス:トークン保有者は投票を通じてキュレーションの意思決定に参加する
  • 経済的安全性:不誠実な提出者はステークを失い、誠実なキュレーターは報酬を得る

ユースケース:厳選された DApp リスト、トークンホワイトリスト、コンテンツ審査、分散型 Yelp の代替。

3. 解決策

コントラクトアーキテクチャ

solidity
contract TokenCuratedRegistry {
    enum ListingState { None, Applied, Challenged, Accepted, Rejected }

    struct Listing {
        bytes32 listingHash;
        address applicant;
        address challenger;
        uint256 depositAmount;
        uint256 challengeAmount;
        uint256 applyTime;
        uint256 challengeTime;
        ListingState state;
        uint256 votesFor;
        uint256 votesAgainst;
        bool resolved;
    }

    IERC20 public immutable token;       // ステーキングトークン
    uint256 public immutable minDeposit; // 最小ステーク量
    uint256 public immutable challengePeriod; // チャレンジウィンドウ
    uint256 public immutable votePeriod; // 投票ウィンドウ

    mapping(bytes32 => Listing) public listings;
    mapping(bytes32 => bool) public isListed;
}

ライフサイクル

1. applyListing(hash) → MIN_DEPOSIT をステーク → state = Applied
2. challenge(hash)    → [チャレンジウィンドウ内] → チャレンジャーがステーク → state = Challenged
3. vote(hash, accept) → トークン加重投票 → forVotes / againstVotes が累積
4. resolve(hash)      → [投票期間終了] → 勝者が両方のステークを獲得
5. autoAccept(hash)   → [チャレンジなし + タイムアウト] → ステーク返還、エントリ受理

投票ウェイト

solidity
function vote(bytes32 listingHash, bool acceptVote) external {
    Listing storage listing = listings[listingHash];
    require(listing.state == ListingState.Challenged, "No active challenge");
    require(block.timestamp <= listing.challengeTime + votePeriod, "Vote ended");
    require(!hasVoted[listingHash][msg.sender], "Already voted");

    uint256 balance = token.balanceOf(msg.sender);
    require(balance > 0, "No tokens");

    if (acceptVote) {
        listing.votesFor += balance;
    } else {
        listing.votesAgainst += balance;
    }
}

4. 遭遇した落とし穴

4.1 Sybil 攻撃のリスク

トークン投票ウェイトは balanceOf(msg.sender) に基づいています。攻撃者はトークンを複数のアドレスに分散してより多くの投票権を得ることができます。これは DAO ガバナンスにおける Sybil 攻撃と同じ問題です。TCR の解決策は最小ステーク量を要求することです(攻撃コストを引き上げる)。

4.2 投票者の無関心(Voter Apathy)

ほとんどのトークン保有者は投票に参加しません。少数の人だけが投票する場合、TCR は少数のアクティブユーザーによって容易に操作されます。これには投票参加を促進するメカニズム設計が必要です。例えば、敗者のステークの一部を投票者で共有する仕組みです。

4.3 チャレンジウィンドウの期間設定

challengePeriod が短すぎる → コミュニティが低品質エントリを発見してチャレンジする時間が不十分。長すぎる → エントリが不確定な状態でリストに長く留まる。典型的な値は3〜7日です。

4.4 ステーク資産の流動性ロック

申請者とチャレンジャーのトークンは紛争期間中ロックされます(移転も使用も不可)。チャレンジ期間 + 投票期間が長い場合(例:14日間)、大量のトークンがロックされることでトークンの流動性に影響を与える可能性があります。

5. 落とし穴の原因

5.1

トークン残高ベースの投票は本質的に Sybil 耐性がありません。アイデンティティベースのシステム(例:1人1票)とは異なり、TCR は経済的インセンティブが行動を制約することを前提としています。攻撃者が投票を操作するには大量のトークンが必要であり、それらのトークンの保有者も TCR が正常に機能することを望んでいます(そうでなければトークンが価値を失うため)。

5.3

チャレンジウィンドウは効率性と品質のトレードオフです。金融系リスト(トークンホワイトリストなど)は短いウィンドウ(迅速な意思決定)が必要であり、文化系リスト(アートキュレーションなど)は長いウィンドウ(ゆっくりとしたキュレーション)を許容できます。

6. 落とし穴の解決方法

  • minDeposit は Sybil 攻撃のコストが顕著になるよう十分に高く設定する
  • 新しいエントリが提出されたときにコミュニティに通知するオフチェーン通知システム(Twitter bot、Discord webhook)を構築する
  • 「投票報酬」メカニズムを検討する -- 敗者のステークの一部を投票者に分配する(resolve での比例分配の実装が必要)
  • ユースケースに応じて challengePeriodvotePeriod を適切に設定する

7. 技術的ポイント

ポイント説明
TCR ライフサイクルApplied → Challenged → Accepted/Rejected
双方向ステーキング申請者とチャレンジャーの両方が MIN_DEPOSIT をステーク
トークン加重投票balanceOf(voter) が投票ウェイトを決定
経済的裁定勝者が両方のステークを獲得、敗者はステークを没収
タイムアウト自動受理チャレンジなし + タイムアウト → autoAccept でステーク返還
チャレンジウィンドウchallengePeriod が公平なチャレンジの時間枠を決定
二重投票防止hasVoted マッピングで各アドレスが1回のみ投票可能

Built with AiAda