はじめに

初めまして。 CryptoGamesというブロックチェーンゲーム企業でエンジニアをしている cardene(かるでね) です! スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。

代表的なゲームはクリプトスペルズというブロックチェーンゲームです。

今回はBunzzの新機能『DeCipher』を使用して、Nounsの「NounsDAOExecutor」コントラクトを見てみようと思います。

『DeCipher』はAIを使用してコントラクトのドキュメントを自動生成してくれるサービスです。

詳しい使い方に関しては以下の記事を参考にしてください!

この記事はあくまで個人が「DeCipher」を使用してみての記事になります。 間違った部分などもあるかと思いますが、その際はコメントなどで教えてください。

今回使用する『DeCipher』のリンクは以下になります。

Etherscanのリンクは以下になります。

概要

NounsDAOExecutorコントラクトは、Nouns DAO(分散型自治組織)システムにおいて重要な要素です。

このコントラクトの目的は、Nouns DAOのガバナンス(運営方針)やトレジャリー(資金管理)を実行することです。 簡単に言えば、DAOが行った決定を実行し、DAOの資金を管理する責任を担っています。

コントラクトは、トランザクションを安全で分散的な方法で処理するように設計されています。 承認されたアクションのみが実行され、DAOの資金は透明性とセキュリティを持って管理されることが確保されています。

NounsDAOExecutorコントラクトの主な責任は次のとおりです。

トランザクションの管理

このコントラクトはトランザクションの実行を監視します。 トランザクションは正しく承認され、特定の条件を満たしている場合にのみ実行されます たとえば、トランザクションは実行される前にキューに追加され、一定の時間が経過するまで実行されません。

イベントの記録

コントラクトは、重要なアクションに関するイベントを発行します。 トランザクションのキューイング、実行、キャンセル、管理者の役割の変更などが該当します。 これにより、コントラクトの活動が透明に記録され、監視や監査に活用されます。

管理者の役割の管理

コントラクトは管理者の役割を管理します。 新しい管理者の割り当てや保留中の管理者の承認などを担当し、特定のアクションを実行できる権限を制御します。

タイムロックの管理

コントラクトはタイムロックメカニズムを利用して、即時に実行されないアクションを管理します。 指定された遅延時間が経過するまで、アクションは実行されません。 これにより、アクションのレビューやキャンセルが可能となります。

Web3、ブロックチェーン、dAppの視点から見ると、NounsDAOExecutorコントラクトは分散型アプリケーション(dApp)の重要なバックエンド要素です。 Ethereumブロックチェーンとやり取りし、ユーザーがDAOと分散化された信頼性のある方法で対話できるようにします。 また、ブロックチェーンの透明性とセキュリティを利用して、DAOのアクティビティを確実に記録します。

使い方

NounsDAOExecutorコントラクトは、分散型自治組織(DAO)内でトランザクションを管理するためのスマートコントラクトです。 このコントラクトは、トランザクションのキューイング、実行、キャンセルといった機能を提供します。

目的

コントラクトの目的は、DAO内でのトランザクションを安全かつ効率的に管理することです。 これを実現するために、コントラクトはタイムロックメカニズムを導入しています。 このメカニズムにより、トランザクションは一定の遅延期間後にのみ実行されます。 これによって、DAOメンバーはトランザクションをよく考えてから実行し、誤りや問題があればキャンセルすることができるようになります。

コントラクトの利用手順は以下の通りです。

  1. コントラクトのデプロイ NounsDAOExecutorコントラクトをEthereumブロックチェーン上にデプロイします。

  2. 管理者の設定 コントラクトがデプロイされた後、管理者を設定します。 新しい管理者のアドレスを指定してsetPendingAdmin関数を呼び出し、その後新しい管理者はacceptAdmin関数を呼び出してロールを受け入れます。

  3. トランザクションのキューイング トランザクションをキューに追加するには、queueTransaction関数を呼び出します。 この時、対象のアドレス、トランザクションの価値、関数のシグネチャ、データ、そして到着予定時刻(eta)を指定します。 ただし、etaは現在のブロックのタイムスタンプに一定の遅延を加えた値以上である必要があります。

  4. トランザクションの実行 etaが経過した後、同じパラメータを使用してexecuteTransaction関数を呼び出すことで、トランザクションを実行します。 ただし、トランザクションは一定の猶予期間内に実行される必要があります。 期間を過ぎると、トランザクションは無効になります。

  5. トランザクションのキャンセル トランザクションをキャンセルする場合、cancelTransaction関数をqueueTransaction関数と同じパラメータで呼び出します。 ただし、トランザクションのキャンセルは管理者のみが行えます。

利用シナリオ

  1. 通常のトランザクション管理 NounsDAOExecutorコントラクトは、DAO内で通常のトランザクションの管理に使用されます。 トランザクションのキューイング、実行、キャンセルなどが含まれます。

  2. 緊急時のトランザクションキャンセル 緊急の場合、管理者はトランザクションを実行前にキャンセルできます。 これは、悪意のあるトランザクションがキューに追加された場合や、誤った操作があった場合に利用されます。

  3. 管理者役割の移行 管理者役割は新しいアドレスに移行できます。 現在の管理者が職務を果たせなくなった場合に役立ちます。

パラメーター

admin_

コントラクトの最初の管理者のアドレス。 管理者は、トランザクションの実行、キャンセル、およびキューイングの権限を持っています。

delay_

キューに追加されたトランザクションを実行するための遅延時間を秒単位で表します。 この遅延時間は、最大遅延を超えないようにする必要があります。

delay_は、トランザクションがキューに追加されてから実際に実行されるまでの時間を表すパラメータです。 この遅延時間は秒単位で指定されます。

この遅延時間の設定には、最小遅延と最大遅延の制約が関与します。

最小遅延(minimum delay) トランザクションをキューに追加してから実行するまでの最短の時間です。 通常、最小遅延は、トランザクションが実行される前に他のメンバーがトランザクションを確認し、必要に応じてキャンセルできる時間を確保するために設定されます。

最大遅延(maximum delay) トランザクションをキューに追加してから実行するまでの最長の時間です。 最大遅延は、トランザクションの実行までの時間を適切に制御し、効率的な運用を確保するために設定されます。

トランザクションの実行には、delay_で指定された時間が必要です。 トランザクションがキューに追加されたとき、delay_の時間が経過するまで実行が保留されます。 この時間内に他のメンバーがトランザクションを確認し、必要に応じてキャンセルすることができるようになります。

このように、delay_ パラメータはトランザクションの実行を遅らせることで、トランザクションの管理と安全性を確保します。

コントラクト

NounsDAOExecutor

admin

admin
solidity
address public admin;

概要 現在の管理者のアドレスを格納する変数。

詳細 コントラクトの最初の管理者のアドレスが格納されます。 管理者は、トランザクションの実行、キャンセル、およびキューイングの権限を持っています。


pendingAdmin

pendingAdmin
solidity
address public pendingAdmin;

概要 新しい管理者のアドレスが保留中であることを示す変数。

詳細 新しい管理者のアドレスが保留中の場合、そのアドレスが格納されます。 新しい管理者は、保留中のアドレスを受け入れることで、実際の管理者になることができます。


delay

delay
solidity
uint256 public delay;

概要 トランザクションのキューに追加されてから実行されるまでの遅延時間を格納する変数。

詳細 トランザクションの実行にかかる遅延時間が格納されます。 トランザクションがキューに追加された後、delayで指定された時間が経過するまで実行は保留されます。


GRACE_PERIOD

GRACE_PERIOD
solidity
uint256 public constant GRACE_PERIOD = 14 days;

概要 取引がキャンセル可能な期間。

詳細 この期間内であれば、取引がキャンセルされることなく実行されます。 この変数は、uint256型で宣言されており、値は14 daysです。 daysは時間を表す単位であり、14日間を意味します。 取引がこの期間内であれば、いつでもキャンセルが可能です。


MINIMUM_DELAY

MINIMUM_DELAY
solidity
uint256 public constant MINIMUM_DELAY = 2 days;

概要 取引の実行遅延の最小許容時間を表す定数。

詳細 取引がキャンセルされずに実行されるためには、少なくともこの時間が経過する必要があります。 この変数は、uint256型で宣言されており、値は2 daysです。 daysは時間を表す単位であり、2日間を意味します。 取引がこの時間よりも早く実行されることはありません。


MAXIMUM_DELAY

MAXIMUM_DELAY
solidity
uint256 public constant MAXIMUM_DELAY = 30 days;

概要 取引の実行遅延の最大許容時間を表す定数。

詳細 取引がこの時間を超えて遅延する場合、特定の条件が満たされるまで取引はキャンセルされます。 この変数は、uint256型で宣言されており、値は30 daysです。 daysは時間を表す単位であり、30日間を意味します。 取引の遅延がこの期間を超える場合、追加の条件が必要となります。


admin

admin
solidity
address public admin;

概要 コントラクトの管理者のアドレス。

詳細 このアドレスはコントラクトの管理権限を持ち、特定の操作を行うことができます。


pendingAdmin

pendingAdmin
solidity
address public pendingAdmin;

概要 新しい管理者のアドレスが確認待ちであることを示す変数。

詳細 新しい管理者が確認されると、このアドレスに設定されます。


delay

delay
solidity
uint256 public delay;

概要 取引の実行遅延時間を表す変数。

詳細 この変数に設定された時間だけ、取引の実行が遅延されます。


queuedTransactions

queuedTransactions
solidity
mapping(bytes32 => bool) public queuedTransactions;

概要 キューに追加された取引を管理するためのマッピング配列。

詳細 このマッピングは、特定の取引がキューに追加されたかどうかを示すブール値を格納します。

パラメータ

  • key
    • マッピングのキーとなるバイト列。

戻り値

  • bool
    • キューに取引が追加されているかどうかを示す値。

setDelay

setDelay
solidity
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
solidity
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
solidity
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
solidity
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
solidity
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
solidity
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
solidity
function getBlockTimestamp() internal view returns (uint256) {
    // solium-disable-next-line security/no-block-members
    return block.timestamp;
}

概要 現在のブロックのタイムスタンプを取得する関数。

詳細

  • block.timestampを使用して現在のブロックのタイムスタンプを取得します。

戻り値

  • 現在のブロックのタイムスタンプ。

イベント

NewAdmin

NewAdmin
solidity
event NewAdmin(address indexed newAdmin);

概要 新しい管理者が設定されたときに発行されるイベント。

詳細 新しい管理者のアドレスが設定された際に発行され、そのアドレスがパラメータとして提供されます。 新しい管理者は、トランザクションの実行、キャンセル、およびキューイングの権限を持つことになります。

パラメータ

  • newAdmin
    • 新しい管理者のアドレス。

NewPendingAdmin

NewPendingAdmin
solidity
event NewPendingAdmin(address indexed newPendingAdmin);

概要 新しい管理者が保留中に設定されたときに発行されるイベント。

詳細 新しい管理者のアドレスが保留中の状態で設定された際に発行され、そのアドレスがパラメータとして提供されます。 新しい管理者は、保留中のアドレスを受け入れることで、実際の管理者になることができます。

パラメータ

  • newPendingAdmin
    • 新しい管理者のアドレス。

NewDelay

NewDelay
solidity
event NewDelay(uint256 indexed newDelay);

概要 新しい遅延時間が設定されたときに発行されるイベント。

詳細 新しい遅延時間が設定された際に発行され、その遅延時間がパラメータとして提供されます。 トランザクションがキューに追加されてから実行されるまでの時間が変更されたことを示します。

パラメータ

  • newDelay
    • 新しい遅延時間(秒単位)。

CancelTransaction

CancelTransaction
solidity
event CancelTransaction(
    bytes32 indexed txHash,
    address indexed target,
    uint256 value,
    string signature,
    bytes data,
    uint256 eta
);

概要 トランザクションがキャンセルされた時に発行されるイベント。

パラメータ

  • txHash
    • トランザクションのハッシュ。
  • target
    • トランザクションのターゲットアドレス。
  • value
    • トランザクションで送られた送金額。
  • signature
    • トランザクションの関数シグネチャ。
  • data
    • トランザクションのデータ。
  • eta
    • トランザクションの到着予定時刻。

ExecuteTransaction

ExecuteTransaction
solidity
event ExecuteTransaction(
    bytes32 indexed txHash,
    address indexed target,
    uint256 value,
    string signature,
    bytes data,
    uint256 eta
);

概要 トランザクションが実行された時に発行されるイベント。

パラメータ

  • txHash
    • トランザクションのハッシュ。
  • target
    • トランザクションのターゲットアドレス。
  • value
    • トランザクションで送られた送金額。
  • signature
    • トランザクションの関数シグネチャ。
  • data
    • トランザクションのデータ。
  • eta
    • トランザクションの到量着予定時刻。

QueueTransaction

QueueTransaction
solidity
event QueueTransaction(
    bytes32 indexed txHash,
    address indexed target,
    uint256 value,
    string signature,
    bytes data,
    uint256 eta
);

概要 トランザクションがキューに追加された時に発行されるイベント。

パラメータ

  • txHash
    • トランザクションのハッシュ。
  • target
    • トランザクションのターゲットアドレス。
  • value
    • トランザクションで送られた送金額。
  • signature
    • トランザクションの関数シグネチャ。
  • data
    • トランザクションのデータ。
  • eta
    • トランザクションの到着予定時刻。

コード

NounsDAOExecutor.sol
solidity
// 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に関する記事を挙げているので、よければ見ていってください!