[Bunzz Decipher] Nounsの『NounsDAOExecutor』コントラクトを理解しよう!
はじめに
初めまして。
CryptoGamesというブロックチェーンゲーム企業でエンジニアをしている cardene(かるでね) です!
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。
代表的なゲームはクリプトスペルズというブロックチェーンゲームです。
今回はBunzzの新機能『DeCipher』を使用して、Nounsの「NounsDAOExecutor」コントラクトを見てみようと思います。
『DeCipher』はAIを使用してコントラクトのドキュメントを自動生成してくれるサービスです。
詳しい使い方に関しては以下の記事を参考にしてください!
今回使用する『DeCipher』のリンクは以下になります。
Etherscanのリンクは以下になります。
概要
NounsDAOExecutorコントラクトは、Nouns DAO(分散型自治組織)システムにおいて重要な要素です。
このコントラクトの目的は、Nouns DAOのガバナンス(運営方針)やトレジャリー(資金管理)を実行することです。
簡単に言えば、DAOが行った決定を実行し、DAOの資金を管理する責任を担っています。
コントラクトは、トランザクションを安全で分散的な方法で処理するように設計されています。
承認されたアクションのみが実行され、DAOの資金は透明性とセキュリティを持って管理されることが確保されています。
NounsDAOExecutorコントラクトの主な責任は次のとおりです。
トランザクションの管理
このコントラクトはトランザクションの実行を監視します。
トランザクションは正しく承認され、特定の条件を満たしている場合にのみ実行されます
たとえば、トランザクションは実行される前にキューに追加され、一定の時間が経過するまで実行されません。
イベントの記録
コントラクトは、重要なアクションに関するイベントを発行します。
トランザクションのキューイング、実行、キャンセル、管理者の役割の変更などが該当します。
これにより、コントラクトの活動が透明に記録され、監視や監査に活用されます。
管理者の役割の管理
コントラクトは管理者の役割を管理します。
新しい管理者の割り当てや保留中の管理者の承認などを担当し、特定のアクションを実行できる権限を制御します。
タイムロックの管理
コントラクトはタイムロックメカニズムを利用して、即時に実行されないアクションを管理します。
指定された遅延時間が経過するまで、アクションは実行されません。
これにより、アクションのレビューやキャンセルが可能となります。
Web3、ブロックチェーン、dAppの視点から見ると、NounsDAOExecutorコントラクトは分散型アプリケーション(dApp)の重要なバックエンド要素です。
Ethereumブロックチェーンとやり取りし、ユーザーがDAOと分散化された信頼性のある方法で対話できるようにします。
また、ブロックチェーンの透明性とセキュリティを利用して、DAOのアクティビティを確実に記録します。
使い方
NounsDAOExecutorコントラクトは、分散型自治組織(DAO)内でトランザクションを管理するためのスマートコントラクトです。
このコントラクトは、トランザクションのキューイング、実行、キャンセルといった機能を提供します。
目的
コントラクトの目的は、DAO内でのトランザクションを安全かつ効率的に管理することです。
これを実現するために、コントラクトはタイムロックメカニズムを導入しています。
このメカニズムにより、トランザクションは一定の遅延期間後にのみ実行されます。
これによって、DAOメンバーはトランザクションをよく考えてから実行し、誤りや問題があればキャンセルすることができるようになります。
コントラクトの利用手順は以下の通りです。
-
コントラクトのデプロイ
NounsDAOExecutorコントラクトをEthereumブロックチェーン上にデプロイします。 -
管理者の設定
コントラクトがデプロイされた後、管理者を設定します。
新しい管理者のアドレスを指定してsetPendingAdmin
関数を呼び出し、その後新しい管理者はacceptAdmin
関数を呼び出してロールを受け入れます。 -
トランザクションのキューイング
トランザクションをキューに追加するには、queueTransaction
関数を呼び出します。
この時、対象のアドレス、トランザクションの価値、関数のシグネチャ、データ、そして到着予定時刻(eta
)を指定します。
ただし、eta
は現在のブロックのタイムスタンプに一定の遅延を加えた値以上である必要があります。 -
トランザクションの実行
eta
が経過した後、同じパラメータを使用してexecuteTransaction
関数を呼び出すことで、トランザクションを実行します。
ただし、トランザクションは一定の猶予期間内に実行される必要があります。
期間を過ぎると、トランザクションは無効になります。 -
トランザクションのキャンセル
トランザクションをキャンセルする場合、cancelTransaction
関数をqueueTransaction
関数と同じパラメータで呼び出します。
ただし、トランザクションのキャンセルは管理者のみが行えます。
利用シナリオ
-
通常のトランザクション管理
NounsDAOExecutorコントラクトは、DAO内で通常のトランザクションの管理に使用されます。
トランザクションのキューイング、実行、キャンセルなどが含まれます。 -
緊急時のトランザクションキャンセル
緊急の場合、管理者はトランザクションを実行前にキャンセルできます。
これは、悪意のあるトランザクションがキューに追加された場合や、誤った操作があった場合に利用されます。 -
管理者役割の移行
管理者役割は新しいアドレスに移行できます。
現在の管理者が職務を果たせなくなった場合に役立ちます。
パラメーター
admin_
コントラクトの最初の管理者のアドレス。
管理者は、トランザクションの実行、キャンセル、およびキューイングの権限を持っています。
delay_
キューに追加されたトランザクションを実行するための遅延時間を秒単位で表します。
この遅延時間は、最大遅延を超えないようにする必要があります。
コントラクト
NounsDAOExecutor
admin
admin
address public admin;
概要
現在の管理者のアドレスを格納する変数。
詳細
コントラクトの最初の管理者のアドレスが格納されます。
管理者は、トランザクションの実行、キャンセル、およびキューイングの権限を持っています。
pendingAdmin
pendingAdmin
address public pendingAdmin;
概要
新しい管理者のアドレスが保留中であることを示す変数。
詳細
新しい管理者のアドレスが保留中の場合、そのアドレスが格納されます。
新しい管理者は、保留中のアドレスを受け入れることで、実際の管理者になることができます。
delay
delay
uint256 public delay;
概要
トランザクションのキューに追加されてから実行されるまでの遅延時間を格納する変数。
詳細
トランザクションの実行にかかる遅延時間が格納されます。
トランザクションがキューに追加された後、delay
で指定された時間が経過するまで実行は保留されます。
GRACE_PERIOD
GRACE_PERIOD
uint256 public constant GRACE_PERIOD = 14 days;
概要
取引がキャンセル可能な期間。
詳細
この期間内であれば、取引がキャンセルされることなく実行されます。
この変数は、uint256
型で宣言されており、値は14 days
です。
days
は時間を表す単位であり、14日間を意味します。
取引がこの期間内であれば、いつでもキャンセルが可能です。
MINIMUM_DELAY
MINIMUM_DELAY
uint256 public constant MINIMUM_DELAY = 2 days;
概要
取引の実行遅延の最小許容時間を表す定数。
詳細
取引がキャンセルされずに実行されるためには、少なくともこの時間が経過する必要があります。
この変数は、uint256
型で宣言されており、値は2 days
です。
days
は時間を表す単位であり、2日間を意味します。
取引がこの時間よりも早く実行されることはありません。
MAXIMUM_DELAY
MAXIMUM_DELAY
uint256 public constant MAXIMUM_DELAY = 30 days;
概要
取引の実行遅延の最大許容時間を表す定数。
詳細
取引がこの時間を超えて遅延する場合、特定の条件が満たされるまで取引はキャンセルされます。
この変数は、uint256
型で宣言されており、値は30 days
です。
days
は時間を表す単位であり、30日間を意味します。
取引の遅延がこの期間を超える場合、追加の条件が必要となります。
admin
admin
address public admin;
概要
コントラクトの管理者のアドレス。
詳細
このアドレスはコントラクトの管理権限を持ち、特定の操作を行うことができます。
pendingAdmin
pendingAdmin
address public pendingAdmin;
概要
新しい管理者のアドレスが確認待ちであることを示す変数。
詳細
新しい管理者が確認されると、このアドレスに設定されます。
delay
delay
uint256 public delay;
概要
取引の実行遅延時間を表す変数。
詳細
この変数に設定された時間だけ、取引の実行が遅延されます。
queuedTransactions
queuedTransactions
mapping(bytes32 => bool) public queuedTransactions;
概要
キューに追加された取引を管理するためのマッピング配列。
詳細
このマッピングは、特定の取引がキューに追加されたかどうかを示すブール値を格納します。
パラメータ
-
key
- マッピングのキーとなるバイト列。
戻り値
-
bool
- キューに取引が追加されているかどうかを示す値。
setDelay
setDelay
function setDelay(uint256 delay_) public {
require(msg.sender == address(this), 'NounsDAOExecutor::setDelay: Call must come from NounsDAOExecutor.');
require(delay_ >= MINIMUM_DELAY, 'NounsDAOExecutor::setDelay: Delay must exceed minimum delay.');
require(delay_ <= MAXIMUM_DELAY, 'NounsDAOExecutor::setDelay: Delay must not exceed maximum delay.');
delay = delay_;
emit NewDelay(delay);
}
概要
NounsDAOExecutorのディレイ(遅延時間)を設定する関数。
詳細
-
delay_
を設定します。 - メッセージ送信者がNounsDAOExecutorであることを確認します。
-
delay_
が最小ディレイを超えていることを確認します。 -
delay_
が最大ディレイを超えていないことを確認します。 - ディレイの値を設定し、
NewDelay
イベントを発行します。
引数
-
delay_
- 遅延時間として設定する時間。
戻り値
なし
acceptAdmin
acceptAdmin
function acceptAdmin() public {
require(msg.sender == pendingAdmin, 'NounsDAOExecutor::acceptAdmin: Call must come from pendingAdmin.');
admin = msg.sender;
pendingAdmin = address(0);
emit NewAdmin(admin);
}
概要
pendingAdmin
からの呼び出しによって管理者(admin
)を受け入れる関数。
詳細
-
pendingAdmin
からの呼び出しであることを確認します。 - 呼び出し元を新しい管理者(
admin
)として設定し、pendingAdmin
をクリアします。 -
NewAdmin
イベントを発行します。
setPendingAdmin
setPendingAdmin
function setPendingAdmin(address pendingAdmin_) public {
require(
msg.sender == address(this),
'NounsDAOExecutor::setPendingAdmin: Call must come from NounsDAOExecutor.'
);
pendingAdmin = pendingAdmin_;
emit NewPendingAdmin(pendingAdmin);
}
概要
pendingAdmin
を設定する関数。
詳細
- メッセージ送信者がNounsDAOExecutorであることを確認します。
-
pendingAdmin
を指定されたアドレスに設定します。 -
NewPendingAdmin
イベントを発行します。
引数
-
pendingAdmin_
- 設定する
pendingAdmin
のアドレス。
- 設定する
戻り値
なし
queueTransaction
queueTransaction
function queueTransaction(
address target,
uint256 value,
string memory signature,
bytes memory data,
uint256 eta
) public returns (bytes32) {
require(msg.sender == admin, 'NounsDAOExecutor::queueTransaction: Call must come from admin.');
require(
eta >= getBlockTimestamp() + delay,
'NounsDAOExecutor::queueTransaction: Estimated execution block must satisfy delay.'
);
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
queuedTransactions[txHash] = true;
emit QueueTransaction(txHash, target, value, signature, data, eta);
return txHash;
}
概要
トランザクションをキューに追加する関数。
詳細
- メッセージ送信者が管理者(
admin
)であることを確認します。 - トランザクションの実行予定ブロックが遅延時間の条件を満たしていることを確認します。
- トランザクションの情報を元にハッシュを生成し、
queuedTransactions
マップ配列に追加します。 -
QueueTransaction
イベントを発行します。
引数
-
target
- トランザクションの対象アドレス。
-
value
- トランザクションに含まれる送金額。
-
signature
- 関数のシグネチャ。
-
data
- トランザクションデータ。
-
eta
- 予定された実行ブロックのタイムスタンプ。
戻り値
-
txHash
- 追加されたトランザクションのハッシュ。
cancelTransaction
cancelTransaction
function cancelTransaction(
address target,
uint256 value,
string memory signature,
bytes memory data,
uint256 eta
) public {
require(msg.sender == admin, 'NounsDAOExecutor::cancelTransaction: Call must come from admin.');
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
queuedTransactions[txHash] = false;
emit CancelTransaction(txHash, target, value, signature, data, eta);
}
概要
キューに追加されたトランザクションをキャンセルする関数。
詳細
- メッセージ送信者が管理者(
admin
)であることを確認します。 - トランザクションの情報を元にハッシュを生成し、
queuedTransactions
マップ配列の値をキャンセルに設定します。 -
CancelTransaction
イベントを発行します。
引数
-
target
- トランザクションの対象アドレス。
-
value
- トランザクションに含まれる送金額。
-
signature
- 関数のシグネチャ。
-
data
- トランザクションデータ。
-
eta
- 予定された実行ブロックのタイムスタンプ。
戻り値
なし
executeTransaction
executeTransaction
function executeTransaction(
address target,
uint256 value,
string memory signature,
bytes memory data,
uint256 eta
) public returns (bytes memory) {
require(msg.sender == admin, 'NounsDAOExecutor::executeTransaction: Call must come from admin.');
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
require(queuedTransactions[txHash], "NounsDAOExecutor::executeTransaction: Transaction hasn't been queued.");
require(
getBlockTimestamp() >= eta,
"NounsDAOExecutor::executeTransaction: Transaction hasn't surpassed time lock."
);
require(
getBlockTimestamp() <= eta + GRACE_PERIOD,
'NounsDAOExecutor::executeTransaction: Transaction is stale.'
);
queuedTransactions[txHash] = false;
bytes memory callData;
if (bytes(signature).length == 0) {
callData = data;
} else {
callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
}
// solium-disable-next-line security/no-call-value
(bool success, bytes memory returnData) = target.call{ value: value }(callData);
require(success, 'NounsDAOExecutor::executeTransaction: Transaction execution reverted.');
emit ExecuteTransaction(txHash, target, value, signature, data, eta);
return returnData;
}
概要
キューに追加されたトランザクションを実行する関数。
詳細
- メッセージ送信者が管理者(
admin
)であることを確認します。 - トランザクションの情報を元にハッシュを生成し、
queuedTransactions
マップ配列にトランザクションがキューに追加されていることを確認します。 - トランザクションの実行時制限(タイムロック)を確認します。
- トランザクションの実行が時制限内であることを確認します。
- トランザクションがキューから削除されます。
- トランザクションデータを構築し、ターゲットコントラクトを呼び出します。
- トランザクション実行が成功したかどうかを確認します。
引数
-
target
- トランザクションの対象アドレス。
-
value
- トランザクションに含まれる送金額。
-
signature
- 関数のシグネチャ。
-
data
- トランザクションデータ。
-
eta
- 予定された実行ブロックのタイムスタンプ。
戻り値
-
returnData
- トランザクション実行結果のデータ(bytes)。
getBlockTimestamp
getBlockTimestamp
function getBlockTimestamp() internal view returns (uint256) {
// solium-disable-next-line security/no-block-members
return block.timestamp;
}
概要
現在のブロックのタイムスタンプを取得する関数。
詳細
-
block.timestamp
を使用して現在のブロックのタイムスタンプを取得します。
戻り値
- 現在のブロックのタイムスタンプ。
イベント
NewAdmin
NewAdmin
event NewAdmin(address indexed newAdmin);
概要
新しい管理者が設定されたときに発行されるイベント。
詳細
新しい管理者のアドレスが設定された際に発行され、そのアドレスがパラメータとして提供されます。
新しい管理者は、トランザクションの実行、キャンセル、およびキューイングの権限を持つことになります。
パラメータ
-
newAdmin
- 新しい管理者のアドレス。
NewPendingAdmin
NewPendingAdmin
event NewPendingAdmin(address indexed newPendingAdmin);
概要
新しい管理者が保留中に設定されたときに発行されるイベント。
詳細
新しい管理者のアドレスが保留中の状態で設定された際に発行され、そのアドレスがパラメータとして提供されます。
新しい管理者は、保留中のアドレスを受け入れることで、実際の管理者になることができます。
パラメータ
-
newPendingAdmin
- 新しい管理者のアドレス。
NewDelay
NewDelay
event NewDelay(uint256 indexed newDelay);
概要
新しい遅延時間が設定されたときに発行されるイベント。
詳細
新しい遅延時間が設定された際に発行され、その遅延時間がパラメータとして提供されます。
トランザクションがキューに追加されてから実行されるまでの時間が変更されたことを示します。
パラメータ
-
newDelay
- 新しい遅延時間(秒単位)。
CancelTransaction
CancelTransaction
event CancelTransaction(
bytes32 indexed txHash,
address indexed target,
uint256 value,
string signature,
bytes data,
uint256 eta
);
概要
トランザクションがキャンセルされた時に発行されるイベント。
パラメータ
-
txHash
- トランザクションのハッシュ。
-
target
- トランザクションのターゲットアドレス。
-
value
- トランザクションで送られた送金額。
-
signature
- トランザクションの関数シグネチャ。
-
data
- トランザクションのデータ。
-
eta
- トランザクションの到着予定時刻。
ExecuteTransaction
ExecuteTransaction
event ExecuteTransaction(
bytes32 indexed txHash,
address indexed target,
uint256 value,
string signature,
bytes data,
uint256 eta
);
概要
トランザクションが実行された時に発行されるイベント。
パラメータ
-
txHash
- トランザクションのハッシュ。
-
target
- トランザクションのターゲットアドレス。
-
value
- トランザクションで送られた送金額。
-
signature
- トランザクションの関数シグネチャ。
-
data
- トランザクションのデータ。
-
eta
- トランザクションの到量着予定時刻。
QueueTransaction
QueueTransaction
event QueueTransaction(
bytes32 indexed txHash,
address indexed target,
uint256 value,
string signature,
bytes data,
uint256 eta
);
概要
トランザクションがキューに追加された時に発行されるイベント。
パラメータ
-
txHash
- トランザクションのハッシュ。
-
target
- トランザクションのターゲットアドレス。
-
value
- トランザクションで送られた送金額。
-
signature
- トランザクションの関数シグネチャ。
-
data
- トランザクションのデータ。
-
eta
- トランザクションの到着予定時刻。
コード
NounsDAOExecutor.sol
// SPDX-License-Identifier: BSD-3-Clause
/// @title The Nouns DAO executor and treasury
/*********************************
* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
* ░░░░░░█████████░░█████████░░░ *
* ░░░░░░██░░░████░░██░░░████░░░ *
* ░░██████░░░████████░░░████░░░ *
* ░░██░░██░░░████░░██░░░████░░░ *
* ░░██░░██░░░████░░██░░░████░░░ *
* ░░░░░░█████████░░█████████░░░ *
* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
*********************************/
// LICENSE
// NounsDAOExecutor.sol is a modified version of Compound Lab's Timelock.sol:
// https://github.com/compound-finance/compound-protocol/blob/20abad28055a2f91df48a90f8bb6009279a4cb35/contracts/Timelock.sol
//
// Timelock.sol source code Copyright 2020 Compound Labs, Inc. licensed under the BSD-3-Clause license.
// With modifications by Nounders DAO.
//
// Additional conditions of BSD-3-Clause can be found here: https://opensource.org/licenses/BSD-3-Clause
//
// MODIFICATIONS
// NounsDAOExecutor.sol modifies Timelock to use Solidity 0.8.x receive(), fallback(), and built-in over/underflow protection
// This contract acts as executor of Nouns DAO governance and its treasury, so it has been modified to accept ETH.
pragma solidity ^0.8.6;
contract NounsDAOExecutor {
event NewAdmin(address indexed newAdmin);
event NewPendingAdmin(address indexed newPendingAdmin);
event NewDelay(uint256 indexed newDelay);
event CancelTransaction(
bytes32 indexed txHash,
address indexed target,
uint256 value,
string signature,
bytes data,
uint256 eta
);
event ExecuteTransaction(
bytes32 indexed txHash,
address indexed target,
uint256 value,
string signature,
bytes data,
uint256 eta
);
event QueueTransaction(
bytes32 indexed txHash,
address indexed target,
uint256 value,
string signature,
bytes data,
uint256 eta
);
uint256 public constant GRACE_PERIOD = 14 days;
uint256 public constant MINIMUM_DELAY = 2 days;
uint256 public constant MAXIMUM_DELAY = 30 days;
address public admin;
address public pendingAdmin;
uint256 public delay;
mapping(bytes32 => bool) public queuedTransactions;
constructor(address admin_, uint256 delay_) {
require(delay_ >= MINIMUM_DELAY, 'NounsDAOExecutor::constructor: Delay must exceed minimum delay.');
require(delay_ <= MAXIMUM_DELAY, 'NounsDAOExecutor::setDelay: Delay must not exceed maximum delay.');
admin = admin_;
delay = delay_;
}
function setDelay(uint256 delay_) public {
require(msg.sender == address(this), 'NounsDAOExecutor::setDelay: Call must come from NounsDAOExecutor.');
require(delay_ >= MINIMUM_DELAY, 'NounsDAOExecutor::setDelay: Delay must exceed minimum delay.');
require(delay_ <= MAXIMUM_DELAY, 'NounsDAOExecutor::setDelay: Delay must not exceed maximum delay.');
delay = delay_;
emit NewDelay(delay);
}
function acceptAdmin() public {
require(msg.sender == pendingAdmin, 'NounsDAOExecutor::acceptAdmin: Call must come from pendingAdmin.');
admin = msg.sender;
pendingAdmin = address(0);
emit NewAdmin(admin);
}
function setPendingAdmin(address pendingAdmin_) public {
require(
msg.sender == address(this),
'NounsDAOExecutor::setPendingAdmin: Call must come from NounsDAOExecutor.'
);
pendingAdmin = pendingAdmin_;
emit NewPendingAdmin(pendingAdmin);
}
function queueTransaction(
address target,
uint256 value,
string memory signature,
bytes memory data,
uint256 eta
) public returns (bytes32) {
require(msg.sender == admin, 'NounsDAOExecutor::queueTransaction: Call must come from admin.');
require(
eta >= getBlockTimestamp() + delay,
'NounsDAOExecutor::queueTransaction: Estimated execution block must satisfy delay.'
);
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
queuedTransactions[txHash] = true;
emit QueueTransaction(txHash, target, value, signature, data, eta);
return txHash;
}
function cancelTransaction(
address target,
uint256 value,
string memory signature,
bytes memory data,
uint256 eta
) public {
require(msg.sender == admin, 'NounsDAOExecutor::cancelTransaction: Call must come from admin.');
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
queuedTransactions[txHash] = false;
emit CancelTransaction(txHash, target, value, signature, data, eta);
}
function executeTransaction(
address target,
uint256 value,
string memory signature,
bytes memory data,
uint256 eta
) public returns (bytes memory) {
require(msg.sender == admin, 'NounsDAOExecutor::executeTransaction: Call must come from admin.');
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
require(queuedTransactions[txHash], "NounsDAOExecutor::executeTransaction: Transaction hasn't been queued.");
require(
getBlockTimestamp() >= eta,
"NounsDAOExecutor::executeTransaction: Transaction hasn't surpassed time lock."
);
require(
getBlockTimestamp() <= eta + GRACE_PERIOD,
'NounsDAOExecutor::executeTransaction: Transaction is stale.'
);
queuedTransactions[txHash] = false;
bytes memory callData;
if (bytes(signature).length == 0) {
callData = data;
} else {
callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
}
// solium-disable-next-line security/no-call-value
(bool success, bytes memory returnData) = target.call{ value: value }(callData);
require(success, 'NounsDAOExecutor::executeTransaction: Transaction execution reverted.');
emit ExecuteTransaction(txHash, target, value, signature, data, eta);
return returnData;
}
function getBlockTimestamp() internal view returns (uint256) {
// solium-disable-next-line security/no-block-members
return block.timestamp;
}
receive() external payable {}
fallback() external payable {}
}
最後に
今回の記事では、Bunzzの新機能『DeCipher』を使用して、Nounsの「NounsDAOExecutor」のコントラクトを見てきました。
いかがだったでしょうか?
今後も特定のNFTやコントラクトをピックアップしてまとめて行きたいと思います。
普段はブログやQiitaでブロックチェーンやAIに関する記事を挙げているので、よければ見ていってください!
Discussion