スマートコントラクト イベントの構築方法

2024/01/11

イーサリアムおよびイーサリアム仮想マシン(EVM)互換ブロックチェーン上のスマートコントラクトは、主にトランザクションとイベントを通じて外部と通信します。トランザクションはコントラクトのロジックを実行し、状態を変更しますが、イベントはこれらの状態変更に関する情報をログする効率的かつ表現力豊かな方法です。

このブログ記事では、スマートコントラクトイベントについて、その目的から実装までのすべてを学習します。

要点

  • イーサリアム上のスマートコントラクトは、状態変更を外部アプリケーションに伝えるためにイベントを使用します

  • イベントはブロックチェーンに記録され、インデックス付きまたは非インデックス付きのパラメーターを持つことができます

  • Solidityでイベントを構築するには、イベントを宣言し、関数内で発火し、外部アプリケーションを使用してキャッチします。イベント名やパラメーターの使用の
    一貫性は、DAppやサードパーティサービスによる容易な集計のために重要です。

このブログ記事には、スマートコントラクト内でイベントを構築し発火させ、それを使用してMultiBaasブロックチェーンミドルウェアと対話する方法についてのガイド付きチュートリアルが含まれています。5〜10分で可能です。

イベントとは?

Solidityのイベントは、外部アプリケーション、フロントエンドやバックエンドのWebやモバイルアプリケーションなどからキャッチできるデータをスマートコントラクトから発火する方法です。これにより、スマートコントラクトは、トークンの転送や所有権の変更など、状態変更に関する特定のデータをキャプチャする構造化されたログを発火でき、ブロックチェーン内での重要なイベントについて外部システムやアプリケーションに通知する方法を提供します。

イベントは、ブロックチェーンの状態変更の影響を観察するために有用です。ブロックチェーンは最終的に一貫性があるため、別の方法はブロックチェーンをポーリングして更新を取得することになります。たとえば、トークン残高を表示するDAppの場合、各新しいブロックごとに各アカウントの残高をクエリする必要があり、非常に非効率的です。スマートコントラクトに特有のイベントを監視することで、どのアカウントの残高が変更されたかを正確に観察したり通知を受け取ったりできます。イベントを考える別の方法は時系列データとしてです。この文脈では、時系列データの保存、集計などに関するすべての考慮事項が適用される可能性があります。

スマートコントラクトは対話型ではありません。したがって、ユーザーやブロックチェーンに外部と通信するための多くの技術を提供しません。実際、ほぼ唯一の方法はイベントを発火させ、そこからブロックチェーンにログを作成することです。

これがサンプルイベントの例です:

event Deposit(
   address indexed _from,
   bytes32 indexed _id,
   uint _value
);

ここには3つのパラメーターがあります。2つはインデックス付き、1つは非インデックスです。

インデックス付きパラメーターの制限:

  1. 最大3つのインデックス付きパラメーターがある場合があります。

  2. インデックス付きパラメーターの型が32バイト以上(すなわち、文字列やバイト)である場合、実際のデータは保存されず、データのKeccak256ハッシュが保存されます。

インデックス付きと非インデックスのイベントパラメーター

イベントは、EVMの低レベルのロギング機能 opcode LOG0からLOG4の上にある抽象化です。オペコード番号は、イベントがインデックス付きキーワードを使用して宣言するトピックの数に依存します。トピックは、イベントに含めたい変数で、Solidityに対してその変数でフィルターをかけたいことを伝えます。[参考]

トピックはイベントに対するインデックス付きパラメーターです。topic[0]は常にイベント自体のハッシュを参照し、最大で3つのインデックス付き引数を持つことができ、それぞれがトピックに反映されます。

LOG0からLOG4オペコードはログ記録を作成します。[参考]

各ログ記録は、トピックデータの両方で構成されています。トピックは32バイト(256ビット)の「単語」で、イベントで何が起こっているかを説明するために使用されます。ログ記録に含める必要があるトピックの数を説明するために、異なるオペコード(LOG0 …… LOG4)が必要です。LOG0エントリにはABIコーディングされたイベントシグネチャとイベントデータが含まれていますが、追加のインデックス付きトピックは含まれません。LOG1には1つのトピックが含まれ、LOG2には2つのトピックが含まれ、LOG3には3つのトピックが含まれ、LOG4には4つのトピックが含まれます。したがって、単一のログ記録に含めることができるトピックの最大数は4です。[参考]。

[画像参照]

この表の最初の2つの列は、この操作に関連するバイトコードと、それを説明するオペコードを示しています。次に、操作が使用するスタックの値を示すグラフがあります。右側の列は、これらがどのように使用されるかを説明しています。
イーサスキャンでログが含む情報を見てみましょう:

  • アドレスは、契約またはアカウントのアドレスを指すイーサリアムアドレスの16進表現です。

  • 名前は、スマートコントラクト内の関数の名前で、関数シグネチャが続きます。これは、「Transfer」という名前の関数が3つのパラメーターを持つことを示唆しています。「from」(アドレス)、「to」(別のアドレス)、および「value」(数値を表すuint256型)の3つのパラメーターです。

  • トピックは、イベントに関連するインデックス付きパラメーターです。最初のトピック(インデックス0)は関数セレクターで、2番目のトピック(インデックス1)はイベント「Transfer」を表します。続くトピックは、イベントに関連するアドレスと値を表します。

  • データは、イベントのABIコーディングされた非インデックス付きパラメーターを指します。この場合、「value」は0に設定されており、イベント「Transfer」が0の値で発生したことを示しています。

これがMultiBaasでのトランザクションエクスプローラーの表示です。元の関数シグネチャ、デコードされたイベントパラメーター、発火したコントラクトへのリンク、および関数引数を含むすべてのイベントの詳細を確認できます。

ハッシュ化されたイベントパラメーター

インデックス属性を最大3つのパラメーターに追加することができ、これらはログのデータ部分ではなく、「トピック」として知られている特別なデータ構造に追加されます。インデックス付き引数として配列(文字列やバイトを含む)を使用すると、Keccak-256ハッシュがトピックとして保存されます。これは、トピックが単一の単語(32バイト)のみを保持できるためです。[参考]

インデックス属性を持たないすべてのパラメーターは、ABIコーディングされます。ログのデータ部分に。[参考]

イベントパラメーターの手動デコード

イベントに非インデックス付きパラメーターが含まれている場合、ログされたデータを解釈するには手動でデコードする必要があります。これは、パラメーターをABIコーディング(標準化された形式に変換)し、その後コントラクトのABI(アプリケーションバイナリインターフェース)を使用して元の型にデコードすることを含みます。

たとえば、非インデックス付き文字列パラメーターを持つイベントがあるとします。それをデコードするには、文字列をABIコーディングし、その後コントラクトのABIを使用して元の文字列にデコードします。MultiBaasの型変換機能を使用することで、ユーザーはメソッドごとに入力/出力パラメーターの基準として、値がブロックチェーンに到達する際の解釈方法を調整したり、ブロックチェーンからの戻り値の調整を行ったりして、自動的にイベントパラメーターをデコードすることができます。

Solidityでのイベントの構築

このミニチュートリアルでは、イベントの宣言、MultiBaasを使用してイベントを発火させ、イベントをリッスンする方法を示します。

まず、ハードハット作業環境を設定しましょう。Hardhatは、イーサリアムのスマートコントラクト開発および展開のためのオープンソースの開発環境とツールスイートです。インストールするには、環境を設定し、新しいHardhatプロジェクトを作成します。その後、Hardhatのドキュメントに従ってください。

スマートコントラクトの作成

Hardhatプロジェクトで、コントラクトフォルダ内のファイルを削除し、Token.solという名前の新しいファイルを作成します。これは、転送可能なトークンを実装するシンプルなスマートコントラクトです。

私たちは、PandaPesoというトークンを作成し、シンボルを$PANDAにします。トークンの名前は、任意のものに変更できます。このスマートコントラクトは転送可能なトークンを実装し、変更できない固定の総供給量があります。以下のコードを確認してください(非常に詳細にコメントされています)。新しく作成したToken.solファイルに追加します:

// SPDX-License-Identifier: UNLICENSED
// Solidity files have to start with this pragma.
// It will be used by the Solidity compiler to validate its version.
pragma solidity ^0.8.0;
// This is the main building block for smart contracts.
contract Token {
    // Some string type variables to identify the token.
    string public name = "PandaPeso";
    string public symbol = "PANDA";
    // The fixed amount of tokens, stored in an unsigned integer type variable.
    uint256 public totalSupply = 69420;
    // An address type variable is used to store ethereum accounts.
    address public owner;
    // A mapping is a key/value map. Here we store each account's balance.
    mapping(address => uint256) balances;
    // The Transfer event helps off-chain applications understand
    // what happens within your contract.
    event Transfer(address indexed _from, address indexed _to, uint256 _value);
    /**
     * Contract initialization.
     */
    constructor() {
        // The totalSupply is assigned to the transaction sender, which is the
        // account that is deploying the contract.
        balances[msg.sender] = totalSupply;
        owner = msg.sender;
    }
 /**
     * A function to transfer tokens.
     *
     * The `external` modifier makes a function *only* callable from *outside*
     * the contract.
     */
    function transfer(address to, uint256 amount) external {
        // Check if the transaction sender has enough tokens.
        // If `require`'s first argument evaluates to `false` then the
        // transaction will revert.
        require(balances[msg.sender] >= amount, "Not enough tokens");

        // Transfer the amount.
        balances[msg.sender] -= amount;
        balances[to] += amount;

        // Notify off-chain applications of the transfer.
        emit Transfer(msg.sender, to, amount);
    }
    /**
     * Read only function to retrieve the token balance of a given account.
     *
     * The `view` modifier indicates that it doesn't modify the contract's
     * state, which allows us to call it without executing a transaction.
     */
    function balanceOf(address account) external view returns (uint256) {
        return balances[account];
    }
}
テストネットにスマートコントラクトをデプロイする

今、私たちはデプロイスクリプトを書いてターミナルからコントラクトをデプロイすることができますが、MultiBaasを使用すると簡単になります。MultiBaasは、追加のコードを書くことなく、スマートコントラクトを簡単にデプロイし、対話する手助けをしてくれます。

まず、アカウントを作成しましょう–MultiBaasにアクセスしてアカウントを作成してください。Sepoliaテストネットで新しいデプロイメントを作成します。

重要: 後でネットワークを変更することはできません。新しいデプロイメントを作成し、スマートコントラクトを再アップロードすることしかできません。無料のデプロイメントタイプを選択し、作成をクリックしてください。

デプロイメントを作成したら、ログインしてください。今から行うのは、Token.solをアップロードすることです。+ 新しいコントラクトをクリックしてスマートコントラクトファイルをアップロードします。MultiBaasがそれをコンパイルしてくれます。

また、ガスを支払うためにいくつかのSepoliaテストETHが必要です。Infuraのファウセットから入手できますここ

スマートコントラクトを正常にアップロードしたら、それをデプロイします。ウォレットが接続されていることを確認し、ウォレットポップアップからトランザクションを確認するためにデプロイをクリックしてください。コントラクトデプロイにかかるガス量とコントラクトアドレスを見ることができます。

MultiBaasを使用してテストネットでイベントを発火させる

イベントを発火させるために、イベントを発火させるコントラクト関数を呼び出します。コントラクトページで、下にスクロールするとイベントセクションがあり、現在は空です。さて、イベントを発火させましょう。メソッドセクションで、いくつかのPANDAトークンを転送してTransferイベントを発火させます。トークンを送信するための新しいウォレットアドレスと送信したいトークンの量を追加してください。最後に、Send Methodをクリックしてトランザクションを確認します。MultiBaasはイベントを聞き、それをキャッチします。

トランザクションハッシュを追うと、インデックス付きパラメーター_from、_to、_valueを含む発火したイベントを見ることができます:

Etherscanでも同じトランザクションハッシュを確認できます。ここでログを見ることができます:

これがイベントを発火させる方法です。簡単でしたよね?質問がある場合は、ぜひDiscordに参加してください。

イベント発火のガスコスト

イベントの発火にはガスを消費しますが、ストレージ操作よりもかなり安価です。「ログ操作が安価である理由は、ログデータが実際にブロックチェーンに保存されないからです。基本的に、ログは必要に応じて動的に再計算できます。特にマイナーは、将来の計算が過去のログにアクセスできないため、単にログデータを捨てることができます。

ネットワーク全体がログのコストを負担するわけではありません。実際にログを処理、保存、インデックス化するのは、APIサービスノードだけです。

したがって、ロギングのコスト構造は、ログスパムを防ぐための最小限のコストだけです。」 [参考]

関数内で発火可能なイベントの数は?

技術的には、関数は複数のイベントを発火させることができます。ここでの主な制限は、ブロックガス制限です。関数が使用する総ガスが、すべてのイベントを含めてブロックのガス制限を下回る限り、問題ありません。

スマートコントラクトの状態とイベントの違い

原則として:

  • スマートコントラクト: スマートコントラクトの関数内でアクセスする必要があるデータや、他のスマートコントラクトに利用可能にする必要があるデータは、コントラクトのデータと変数に保存する必要があります。例としては、アカウントのトークン残高やNFTの所有者があります。

  • イベント: イベントは、スマートコントラクト内で発生するアクションや状態変更を記録するために使用され、外部アプリケーションが追跡するのに役立ちますが、コントラクトの関数を実行するために必要ではありません。例としては、トークンの転送があります。ユーザーアプリケーションが転送履歴を表示するのに役立ちますが、コントラクト内部では現在の残高だけを知っていれば十分です。コントラクトをあまり多くのイベントで過負荷にするのは避け、主に重要で、自分のアプリケーションに関連するイベントを発火させることに集中してください。

結論

本質的に、イーサリアムのスマートコントラクトイベントは、コントラクトと外部との間のメッセージングシステムとして機能し、特定のブロックチェーンイベントについて外部アプリケーションに通知するために重要です。

さらに読む

イベントのロギング機能に深く潜りたい方には、このブログ記事が優れたリソースです: Solidityイベントの実装 – Ethereum VMの深堀 Part 6