これは DAO コミュニティのスマートコントラクトのリポジトリです。スマートコントラクトは Solidity で書かれ、Foundry フレームワークを使用して開発およびテストされています。
これは DAO コミュニティガバナンスのためのスマートコントラクトのリポジトリで、コミュニティのための基本的なツールです。 コミュニティガバナンスのために分離されたツールがあります。これらはスマートコントラクトに基づいて説明されます。
すべてのコントラクトはsrc/
フォルダに配置されています。これらはプロトコルのコアコントラクトです。
このリポジトリは以下のスマートコントラクトで構成されています:
AmbassadorNft
- コミュニティのメンバーシップを表す NFT のスマートコントラクト。
- このスマートコントラクトは他のスマートコントラクトとは独立しています。
- コントラクトは OpenZeppelin の ERC1155 コントラクトに基づいています。
ERC20UpgradeableTokenV1
- コミュニティのユーティリティトークンを表す ERC20 トークンのスマートコントラクト。
- その使用例は、コミュニティのガバナンストークンと交換することです。
- 他の使用例も可能で、コミュニティによって定義されます。
- コントラクトは OpenZeppelin の ERC20Upgradeable コントラクトに基づいています。
GovToken
- コミュニティのガバナンストークンとして機能するスマートコントラクト。
- このトークンは、譲渡不可能な保有者の投票力を表すために使用されます。
- コントラクトは OpenZeppelin の ERC20Votes コントラクトに基づいています。
VotingPowerExchange
- コミュニティの投票力交換のためのスマートコントラクト。
- このコントラクトは、ユーザーが保有するユーティリティトークンを燃焼してガバナンストークンを取得するために使用されます。
- このコントラクトはゼロから作成されています。
DaoGovernor
- コミュニティの DAO ガバナンスのためのスマートコントラクト。
- このコントラクトは、より分散化された方法でコミュニティの提案を管理するために使用されます。
- コントラクトは OpenZeppelin の Governor コントラクトに基づいています。
Timelock
- コミュニティのタイムロックのためのスマートコントラクト。
- このコントラクトは DaoGovernor コントラクトの所有者になります。
システムは異なるロールによって管理されます。ロールはデフォルトの管理者ロールによって管理されます。
- DEFAULT_ADMIN_ROLE: デフォルトの管理者ロールはコントラクトの所有者です。
- これはコントラクト内で最高の権限を持つロールです。
- 責任: 他のすべてのロールを管理する権限を持ちます。
- 機能: 他のすべてのロールを付与および取り消すことができます。
- MANAGER_ROLE:
- 責任: コントラクトを管理し、特別な機能を呼び出すことができます。
- 機能:
setVotingPowerCap()
- EXCHANGER_ROLE: 交換ロールは、ガバナンストークンをユーティリティトークンと交換できるロールです。
- 責任: ガバナンストークンをユーティリティトークンと交換できます。
- 機能:
exchange()
- MINTER_ROLE: ミンターロールは新しいトークンを発行できるロールです。
- 責任: アンバサダー NFT、ユーティリティトークン、ガバナンストークンの新しいトークンを発行できます。
- 機能:
mint()
,mintBatch()
- BURNER_ROLE: バーナーロールはアンバサダー NFT、ユーティリティトークン、ガバナンストークンのトークンを燃焼できるロールです。
- 責任: トークンを燃焼できます。
- 機能:
burn()
,burnBatch()
,burnByBurner()
- URI_SETTER_ROLE: URI セッターロールはトークンの URI を設定できるロールです。
- 責任: トークンの URI を設定できます。
- 機能:
setURI()
- UPGRADER_ROLE: アップグレーダーロールはコントラクトをアップグレードできるロールです。
- 責任: コントラクトをアップグレードできます。
- 機能:
upgradeToAndCall()
これはコミュニティのメンバーシップを表す NFT のスマートコントラクトです。可能な使用例と NFT の機能について説明します。
このコントラクトは、現在のシステムの他のスマートコントラクトとは独立しています。
この NFT はコミュニティのメンバーシップを表すために使用できます。例えば、メンバーシップ A は NFT1155 の ID 0 で表され、メンバーシップ B は NFT1155 の ID 1 で表されます。NFT を保有する人は誰でもコミュニティのメンバーとみなされます。
デフォルトの管理者ロールは、ミンターとバーナーのロールを設定する権利を持ちます。ミンターロールは新しいトークンを発行できるロールです。バーナーロールは指定されたアカウントからトークンを燃焼できるロールです。
NFT をバッチで発行および燃焼する機能があります。これらの機能により、ミンターとバーナーのロールはメンバーのために NFT を簡単に発行および燃焼できます。
また、NFT の URI はコントラクトのデプロイ後に URI セッターロールによって設定できます。
これはコミュニティのユーティリティトークンを表す ERC20 トークンのスマートコントラクトです。このユーティリティトークンは譲渡可能です。
このトークンはコミュニティのユーティリティトークンとして使用され、ミッションの遂行やコミュニティのための他の活動によって獲得できます。ユーザーはユーティリティトークンを使用してガバナンストークンと交換できます。
デフォルトの管理者ロールは、ミンターとバーナーのロールを設定する権利を持ちます。ミンターロールは新しいトークンを発行できるロールです。バーナーロールは指定されたアカウントからトークンを燃焼できるロールです。
コントラクト自体は一時停止可能です。ポーザーロールはコントラクトを一時停止および再開できるロールです。コントラクトが一時停止されている場合、トークンの発行と譲渡は無効になります。
- コントラクトはアップグレード可能、一時停止可能、および ERC20Permit 準拠です。
これはコミュニティのガバナンストークンのスマートコントラクトです。コントラクトは OpenZeppelin の ERC20Votes コントラクトに基づいています。コントラクトは、保有者が燃焼したユーティリティトークンの量も記録します。
投票力交換コントラクトのみが、保有者が燃焼したユーティリティトークンの量を更新する権利を持ちます。
このトークンは保有者の投票力を表すために使用されます。トークンは譲渡不可能です。同時に、保有者のレベルを表すためにも使用されます。例えば、レベルが高いほど、保有者はより多くの投票力を持ちます。 例えば、1 トークンを保有することは保有者がレベル 2 であることを意味します。0 トークンを保有することは保有者がレベル 1 であることを意味します。
トークンは譲渡不可能です。つまり、トークン保有者は他のアドレスにトークンを譲渡できません。
ERC20Permit はここで継承されています。これは、OpenZeppelin のウィザードがそうしているためであり、私たちはそのコードに従いました。これは比較的安全な方法だと考えています。
このトークンをプロトコルで利用する際には 2 つのフェーズがあります。フェーズ 1 として開始した後、コミュニティガバナンスは観察下に置かれます。フェーズ 1 が安定し成熟したら、将来的にフェーズ 2 に徐々に移行します。
- フェーズ 1: トークンを使用して保有者の投票力を表します。そして、ガバナンス参加者のトークン残高を使用してオフチェーンでガバナンスを管理します。
- フェーズ 2: トークンを使用して保有者の投票力、つまりレベルを表します。そして、ガバナンス参加者のトークン残高を使用してオンチェーンでガバナンスを管理します。
GovToken は、ある意味で達成記録係です。私たちは、達成システムがすべての参加者にとって透明で検証可能であることを確認したいと考えています。そのため、このトークンをアップグレード可能にしませんでした。
実際、トークンの投票単位は、実際の投票力としてカウントされるために誰かに委任されなければなりません。その理由は、OpenZeppelin コントラクトのドキュメントに記載されています。
実際、投票単位は実際の投票としてカウントされるために委任されなければなりません。アカウントが決定に参加したい場合や信頼できる代表者がいない場合は、その投票をアカウント自身に委任する必要があります。
このコントラクトは、ユーティリティトークンをガバナンストークンと交換するために使用されます。交換は、ガバナンストークンを燃焼し、ユーティリティトークンを発行することで行われます。
投票力交換コントラクトは、ガバナンストークンのミンターであり、ユーティリティトークンのバーナーです。
このコントラクトには、保有者が燃焼したユーティリティトークンに基づいて、保有者が受け取るべきガバナンストークンの量を計算するいくつかの機能があります。
主要な機能はexchange()
です。この機能は交換者ロールのみが呼び出すことができます。
交換者ロールは、プロトコル所有者によって管理されることを想定しています。
この機能は、まず送信者の署名を含む入力の有効性をチェックします。署名はオフチェーンで生成され、送信者自身によってのみ生成できます。
これにより、送信者自身がそのような量のトークンを交換する意図を持っていることを確認します。このようにして、交換者ロールが悪用されるのを防ぎたいと考えています。
さらに、この機能は署名のノンスと有効期限をチェックして、署名が新しく有効であることを確認します。
その後、保有者が受け取るべきガバナンストークンの量を計算します。
計算は、参考資料セクションの数学的公式に基づいています。
コントラクトは、保有者が取得できるガバナンストークンの量に上限を設定します。
誰かが上限以下で、上限を超えるユーティリティトークンを交換する場合、この機能は上限が許可する量のトークンのみを交換します。
誰かがすでに上限に達している場合、この機能は交換を許可しません。
誰も上限を超える投票力を得ることはできません。
計算後、この機能は保有者にガバナンストークンを発行し、保有者からユーティリティトークンを燃焼します。
この機能は、GovToken コントラクトで保有者が燃焼したユーティリティトークンの量も更新します。
- このコントラクトはゼロから作成されています。
- 計算の精度のために使用されるいくつかの変数があります。
SafeERC20
は使用されていません。なぜなら、私たちが使用しているトークンコントラクトは安全であることが知られているからです。
これはコミュニティの DAO ガバナンスのためのスマートコントラクトです。コントラクトは OpenZeppelin の Governor コントラクトに基づいています。
このコントラクトは、コミュニティガバナンスのフェーズ 2 で使用されます。
- 基本的なガバナー機能(提案の作成、投票、実行)
- 設定可能なガバナンス設定(投票遅延、投票期間、提案閾値)
- 投票力のための ERC20Votes トークンとの統合
- 総供給量の一部に基づく定足数要件
- 遅延実行のためのタイムロック統合
主要な特徴とパラメータ:
- 投票遅延:1 日(提案作成から投票開始までの時間)
- 後で変更可能
- 投票期間:1 週間(投票フェーズの期間)
- 後で変更可能
- 提案閾値:1e18 トークン(提案を作成するために必要な最小トークン)
- 後で変更可能
- 定足数:総トークン供給量の 1%(有効な投票のための最小参加)
- このコントラクトは、特定の ERC20Votes トークン(おそらく前述の GovToken)と TimelockController と連携するように設計されています。
- いくつかのパラメータ(投票遅延、投票期間、提案閾値、定足数)は、コントラクトがデプロイされたときのセットアップのためにコンストラクタでハードコードされています。
- このコントラクトは OpenZeppelin の最新のコントラクト(v5.0.0)を使用しており、最新のセキュリティ機能とベストプラクティスを確保しています。
- このコントラクトは、コミュニティガバナンスがフェーズ 2 で完全に分散化される準備ができたときに使用されることを想定しています。
Timelock.sol
コントラクトは、OpenZeppelin のTimelockController
に基づいたタイムロックコントローラーです。
このコントラクトは、コミュニティガバナンスのフェーズ 2 で使用されます。
主な目的と機能は以下の通りです:
- 遅延実行:可決された提案に対して必須の待機期間を提供し、ガバナンスのセキュリティを強化します。
- アクセス制御:どのアドレスが操作を提案、実行、またはキャンセルできるかを管理します。提案者または実行者として address(0)が設定されている場合、誰でも提案または実行できます。
- 透明性:すべての保留中の操作は公開され、コミュニティメンバーがレビューし反応する時間を与えます。
DaoGovernor で提案が可決されると、タイムロックにキューイングされます。所定の遅延後、実際の実行はタイムロックによって行われます。
- コントラクトの管理者ロールは最初に設定され、後で取り消すことが推奨されます。
- 実行者ロールは最初に address(0)に設定できると想定しています。これは誰でも実行できることを意味します。そして、提案者ロールは最初にガバナーコントラクトに設定されます。これはガバナーコントラクトのみが提案する権利を持つことを意味します。
- テストケースでタイムロックに gov トークンを発行するミンターロールを付与する必要がある理由:
- 提案が可決されると、キュー操作が呼び出され、タイムロックの実際に呼び出される関数は
scheduledBatch()
です。その後、DaoGovernor のexecute()
関数は誰でも呼び出して提案を実行できます。そしてこの関数はタイムロックのexecuteBatch()
関数を呼び出して提案を実行します。実際の実行者はタイムロックです。
- 提案が可決されると、キュー操作が呼び出され、タイムロックの実際に呼び出される関数は
ユーザー A に gov トークンを発行するためにガバナンスを使用するテストケースのコードに書かれているように、ガバナンスのフローは以下の通りです:
準備:
- ユーザー A はいくつかの投票力を持っています。
- 提案の値、ターゲット、コールデータ、説明を準備します。
- しばらくして、ユーザー A が提案を作成します:
daoGovernor.propose()
。 - いくつかのユーザーが提案に投票します:
daoGovernor.castVote()
。 - 提案はタイムロックにキューイングされます:
daoGovernor.queue()
。 - 遅延後、誰でもこの関数を呼び出すことができます:
daoGovernor.execute()
。 - トークンがユーザー A に発行されます。
- src/
- AmbassadorNft.sol
- ERC20UpgradeableTokenV1.sol
- GovToken.sol
- VotingPowerExchange.sol
- DaoGovernor.sol
- Timelock.sol
上記のスコープにあるファイル以外のすべてのファイル。
- 2 段階の所有権移転プロセスは既知の問題です。私たちはそのリスクを受け入れています。
- 投票力と燃焼されたトークンの計算における精度の損失は存在しますが、それによって引き起こされる価値の損失は非常に小さく(例:1e9 トークン)、無視できます。
- 平方根計算を含む数学的公式(参考資料セクションで言及)を使用しているため、計算において精度の損失が発生します。例えば、理論的に 1e9 ユーティリティトークンをガバナンストークンと交換する場合、得られるのは 0 トークンです。そしてユーザーは理由なくシステムにガス料金を消費させるためにリクエストをスパムする可能性があります。これを考慮して、誰かが
exchange
関数を呼び出したい場合、ユーティリティトークンの値を少なくとも 1e18 にしました。 - テスト(ファジングテストも含む)がすべてのエッジケースをカバーできていない可能性があります。問題を見つけた場合は、お知らせください。
テストはtest/
ディレクトリに書かれています。以下のテストで構成されています:
- ユニットテスト:
test/unit/
- 統合テスト:
test/integration/
- ファジングテスト:
test/fuzz/
テストに関する詳細な情報はtest/readme.md
に記載されています。テストの詳細については、そちらを参照してください。
- リポジトリをクローンする
git clone https://github.com/codefox-inc/dao-community-contracts.git
- 依存関係を更新する
foundryup
- 依存関係をインストールする
make install
- 以下のコマンドでコントラクトをコンパイルします
forge compile
- 以下のコマンドでテストを実行します
make test
- 以下のコマンドでカバレッジを実行します
make coverage
数学的公式と表 これらは、投票力 <-> バーントークン計算のための交換関数とテストで使用した値です。
x = (2 * sqrt(306.25 + 30y) - 5) / 30 - 1
y = (15*x^2+35\*x)/2
x: 発行されたトークン
y: バーンされたトークン
Minted Token (lvl) | Level | Burned Token |
---|---|---|
0 | 1 | 0 |
1 | 2 | 25 |
2 | 3 | 65 |
3 | 4 | 120 |
4 | 5 | 190 |
5 | 6 | 275 |
6 | 7 | 375 |
7 | 8 | 490 |
8 | 9 | 620 |
9 | 10 | 765 |
10 | 11 | 925 |
11 | 12 | 1100 |
12 | 13 | 1290 |
13 | 14 | 1495 |
14 | 15 | 1715 |
15 | 16 | 1950 |
16 | 17 | 2200 |
17 | 18 | 2465 |
18 | 19 | 2745 |
19 | 20 | 3040 |
20 | 21 | 3350 |
21 | 22 | 3675 |
22 | 23 | 4015 |
23 | 24 | 4370 |
24 | 25 | 4740 |
25 | 26 | 5125 |
26 | 27 | 5525 |
27 | 28 | 5940 |
28 | 29 | 6370 |
29 | 30 | 6815 |
30 | 31 | 7275 |
31 | 32 | 7750 |
32 | 33 | 8240 |
33 | 34 | 8745 |
34 | 35 | 9265 |
35 | 36 | 9800 |
36 | 37 | 10350 |
37 | 38 | 10915 |
38 | 39 | 11495 |
39 | 40 | 12090 |
40 | 41 | 12700 |
41 | 42 | 13325 |
42 | 43 | 13965 |
43 | 44 | 14620 |
44 | 45 | 15290 |
45 | 46 | 15975 |
46 | 47 | 16675 |
47 | 48 | 17390 |
48 | 49 | 18120 |
49 | 50 | 18865 |
50 | 51 | 19625 |
51 | 52 | 20400 |
52 | 53 | 21190 |
53 | 54 | 21995 |
54 | 55 | 22815 |
55 | 56 | 23650 |
56 | 57 | 24500 |
57 | 58 | 25365 |
58 | 59 | 26245 |
59 | 60 | 27140 |
60 | 61 | 28050 |
61 | 62 | 28975 |
62 | 63 | 29915 |
63 | 64 | 30870 |
64 | 65 | 31840 |
65 | 66 | 32825 |
66 | 67 | 33825 |
67 | 68 | 34840 |
68 | 69 | 35870 |
69 | 70 | 36915 |
70 | 71 | 37975 |
71 | 72 | 39050 |
72 | 73 | 40140 |
73 | 74 | 41245 |
74 | 75 | 42365 |
75 | 76 | 43500 |
76 | 77 | 44650 |
77 | 78 | 45815 |
78 | 79 | 46995 |
79 | 80 | 48190 |
80 | 81 | 49400 |
81 | 82 | 50625 |
82 | 83 | 51865 |
83 | 84 | 53120 |
84 | 85 | 54390 |
85 | 86 | 55675 |
86 | 87 | 56975 |
87 | 88 | 58290 |
88 | 89 | 59620 |
89 | 90 | 60965 |
90 | 91 | 62325 |
91 | 92 | 63700 |
92 | 93 | 65090 |
93 | 94 | 66495 |
94 | 95 | 67915 |
95 | 96 | 69350 |
96 | 97 | 70800 |
97 | 98 | 72265 |
98 | 99 | 73745 |
99 | 100 | 75240 |
100 | 101 | 76750 |
101 | 102 | 78275 |
102 | 103 | 79815 |
103 | 104 | 81370 |
104 | 105 | 82940 |
105 | 106 | 84525 |
106 | 107 | 86125 |
107 | 108 | 87740 |
108 | 109 | 89370 |
109 | 110 | 91015 |
110 | 111 | 92675 |
....
-
すべてのコントラクトはデプロイされる必要があります。その後、Web2 側から接続することができます。
- phase 1 では、下記画像にあるようになります。
-
ユーザーに代わってリレーヤーが呼び出す必要がある主要な関数は
exchange()
関数です。- この関数を呼び出すために、ユーザーは自身の秘密鍵で署名して独自の署名を生成する必要があります。
- 信頼されたリレーヤーは、署名されたメッセージを使用してこの関数を呼び出します。ガス代はリレーやーが負担します。
-
一部のデータはオンチェーンのコントラクトに保存されます。ただし、開発者はこのデータをデータベースにも保存するかどうかを決定できます。二重管理もやむを得ず可能になったりすると考えています。
-
特別な役割を持つ一部のアカウントは、最初にコントラクト内で初期化されます。言うまでもありませんが、これらのアカウントは慎重に扱われるべきです。