トップへ戻る
公開日
2024年1月11日
筆者:Curvegrid

スマート・コントラクトのイベントはどのように構築するのか?

このブログポストでは、スマート・コントラクト・イベントについて、その目的から実装まで、すべてを網羅する。

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

このブログポストでは、スマート・コントラクト・イベントについて、その目的から実装まで、すべてを網羅する。

TL;DR
  • イーサリアム上のスマートコントラクトは、外部アプリケーションに状態変化を伝えるためにイベントを使用します。
  • イベントはブロックチェーンに記録され、 インデックス付きまたはインデックスなしのパラメーターを持つことができる。
  • Solidityでイベントを構築するには、イベントを宣言し、関数内でそれを発行し、外部アプリケーションを使用してそれをキャプチャします。DAppsやサードパーティーのサービスによって簡単に集約するためには、イベントの命名とパラメーターの使い方に一貫性を持たせることが重要です。

このブログポストには、Solidityでスマートコントラクトを構築してイベントを発行し、MultiBaasブロックチェーンミドルウェアを使って5~10分でイベントと対話する方法についてのガイド付きチュートリアルが含まれています。

イベントとは?

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

イベントはブロックチェーンの状態変化の影響を観察するのに便利である。ブロックチェーンは最終的には一貫しているので、代替手段は更新のためにブロックチェーンをポーリングすることだろう。例えば、トークンの残高を表示するDAppの場合、新しいブロックごとにすべてのアカウントの残高を照会しなければならず、これは非常に非効率的です。スマートコントラクト固有のイベントを監視することで、どの口座の残高が変更されたかを正確に観察したり、通知を受けたりすることができる。イベントを時系列データとして考える方法もある。この文脈では、時系列データの保存、集計などに関するすべての考慮事項が適用できる。

スマートコントラクトはインタラクティブではないため、ユーザーやブロックチェーンなど外部とコミュニケーションするための手法はあまりない。実際、ほとんど唯一の方法は、イベントを発信し、それによってブロックチェーン上にログを作成することである。

イベント例はこんな感じ:

イベント入金(
   アドレス _from、
   bytes32インデックス _id、
   uint _value
);

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

インデックス化されたパラメータの限界:

  1. インデックスされるパラメータは3つまで。
  2. インデックス付きパラメータの型が32バイトより大きい場合(文字列やバイトなど)、実際のデータは格納されず、データのKeccak256ハッシュが格納される。
インデックス付きイベント・パラメーターとインデックスなしイベント・パラメーター

イベントはEVMの低レベルのロギング機能オペコードLOG0からLOG4の上に抽象化されたものです。オペコード番号は、イベントが indexed キーワードを使用して宣言するトピックの数に依存します。トピックは、イベントに含める変数であり、Solidity にその変数でフィルタリングできるように指示します。[参照]。

topic[0]は常にイベント自体のハッシュを指し、最大3つのインデックス付き引数を持つことができ、それぞれがtopicに反映される。

LOG0~LOG4 オペコードはログレコードを作成する。[参考]。

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

[画像参照]。

この表の最初の2列は、この操作に関連するバイトコードと、それを記述するオペコードを示している。そして、その操作がスタックのどの値を使うかを示すグラフがある。
Etherscanでログがどのような情報を含んでいるか見てみましょう:

  • アドレスはイーサリアムのアドレスを16進数で表したもので、コントラクトやアカウントのアドレスを指します。
  • Nameはスマート・コントラクト内の関数名で、その後に関数署名が続く。これは、3つのパラメータを受け取る "Transfer "という関数を示唆している:「from"(アドレス)、"to"(別のアドレス)、"value"(uint256型、数値を表す)の3つのパラメータを取る。
  • トピックは、イベントに関連付けられたインデックス付きパラメータである。最初のトピック(インデックス0)はファンクションセレクタであり、2番目のトピック(インデックス1)はイベント「Transfer」を表す。後続のトピックは、イベントに関連するアドレスと値を表す。
  • Dataは、イベントのABIエンコードされた非インデックスパラメータを指す。この場合、"value "は0に設定され、イベント "Transfer "が値0で発生したことを示す。

Transaction ExplorerはMultiBaas上でこのように表示され、元の関数シグネチャ、デコードされたイベントパラメータ、発行元コントラクトへのリンク、関数引数など、すべてのイベントの詳細を見ることができます。

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

indexed 属性は、ログのデータ部分の代わりに、 "トピック "として知られる特別なデータ構造に追加する、3つまでのパラメータに追加することができます。インデックス付き引数として配列(文字列とバイトを含む)を使った場合、その Keccak-256 ハッシュは代わりにトピックとして格納されます。[参照]

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

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

イベントにインデックス化されていないパラメータがあり、イベントに記録されたデー タを解釈したい場合、それらを手動でデコードする必要があります。これには、パラメータをABIエンコード(標準フォーマットに変換)し、コントラクトのABI(Application Binary Interface)を使用して元のタイプにデコードし直す必要があります。

例えば例えば、インデックスのない文字列パラメータを持つイベントがあるとする。これをデコードするには、文字列をABIエンコードし、コントラクトのABIを使って元の文字列にデコードします。 MultiBaas型変換機能は、入出力パラメータごとのメソッド単位で、ブロックチェーンへの送信時に値がどのように解釈されるかを調整したり、ブロックチェーンからの送信時に戻り値を調整したりする機能をユーザーに提供し、イベントパラメータを自動的にデコードします。

固体の中の出来事を構築する↪_200D↩ 固体の中の出来事を構築する↪_200D↩ 固体の中の出来事を構築する↪_200D↩ 固体の中の出来事を構築する↪_200D↩ 固体の中の出来事を構築する

このミニ・チュートリアルでは、イベントを宣言し、MultiBaasを使ってイベントを発行し、イベントをリッスンする方法を紹介します。

まずはハードハットの作業環境をセットアップしよう。Hardhatは、Ethereumスマートコントラクトの開発とデプロイのためのオープンソースの開発環境とツールスイートです。インストールするには、環境をセットアップして新しい Hardhat プロジェクトを作成し、Hardhatドキュメントの手順に従ってください。

スマート・コントラクトを書く

Hardhatプロジェクトで、contractsフォルダの下にあるファイルを削除し、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をアップロードします。New Contractをクリックしてスマートコントラクトファイルをアップロードします。MultiBaasがコンパイルしてくれます。

ガス代としてセポリアのテスト用ETHも必要だ。こちらのInfuraの蛇口から入手できます。

スマートコントラクトのアップロードに成功したら、それをデプロイします。ウォレットが接続されていることを確認し、Deployをクリックしてウォレットのポップアップからトランザクションを確認します。コントラクトのデプロイにかかった費用と、コントラクトのアドレスが表示されます。

MultiBaasを使ってテストネットにイベントを発信する

イベントを発生させるコントラクト関数を呼び出して、イベントをトリガーしよう。コントラクト・ページを下にスクロールすると、イベント・セクションがあり、今は空になっています。では、イベントをトリガーしましょう。Methodsセクションで、PANDAトークンを送金してTransferイベントをトリガーします。トークンを送りたい新しいウォレットアドレスと、送りたいトークンの量を追加します。最後に Send Method をクリックし、トランザクションを確認します。MultiBaasがイベントをリッスンしてキャッチします。

トランザクションのハッシュをたどると、インデックス化されたパラメータ _from、_to、_valueとともに、トリガーされたイベントがページの下に表示される:

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

そうやってイベントを発するんだ。簡単でしょう?質問があれば、Discordに参加してください。

排出イベントのガス代

イベントの発信はガスを消費するが、ストレージ操作に比べればかなり安価だ。「ログ操作が安価なのは、ログデータがブロックチェーンに保存されていないからだ。ログは原則として、必要に応じてその場で再計算できる。特にマイナーは、将来の計算では過去のログにアクセスできないため、単純にログデータを捨てることができる。

ネットワーク全体がログのコストを負担することはない。APIサービスノードだけが、実際にログを処理し、保存し、インデックスを作成する必要がある。

つまり、ログのコスト構造は、ログスパムを防ぐための最低限のコストに過ぎないのだ。"[参考]

1つの関数でいくつのイベントを発することができますか?

技術的には、ファンクションは複数のイベントを発することができる。ここでの主な制限は、ブロックのガス制限です。すべてのイベントを含むファンクションが使用するガスの合計が、ブロックのガス制限を下回っていれば問題ありません。

スマート・コントラクトのステートに入るものとイベント

経験則だ:

  • スマートコントラクト:スマートコントラクトの機能内でアクセスしたり、他のスマートコントラクトが利用できるようにしたりする必要があるデータは、コントラクトのデータと変数に格納する必要があります。例えば、アカウントのトークン残高やNFTの所有者などです。
  • イベント:イベントは、スマートコントラクト内で発生したアクションや状態変化を記録するために使用できます。これは、外部アプリケーションが追跡するのに便利ですが、コントラクトの機能を実行するのには必要ありません。例えば、トークンの移動などです。ユーザー・アプリケーションが送金履歴を表示するのは便利だが、コントラクトが内部的に知る必要があるのは現在の残高だけである。コントラクトに多くのイベントを与え過ぎないようにするのが最善です。むしろ、最も重要なイベントや、アプリケーションに関連するイベントを出すことに集中しましょう。
結論

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

さらに読む

イベントのロギング機能をより深く知りたい方は、こちらのブログ記事が参考になります:Solidity イベントがどのように実装されているか - Ethereum VM に潜入 Part 6