ソース: Denchain コミュニティ
今日の投稿では、Solidity イベント(一般化されたEtherやEVMではログとして知られている)を見ていきます。これらの使用方法、定義、イベントトピックハッシュとシグネチャを使用したログのフィルタリング方法、また、これらがいつ使用されるべきかについてのアドバイスを見ていきます。
また、check-event-interactionパターンも取り上げます。なぜ、このようなパターンがトリガーイベントにも適用されるべきなのか、そして、潜在的なリスクとセキュリティの脆弱性が関係しているのかを見ていきます。
どのようにSolidityでイベントを定義するのですか?
Solidityでは、イベント
を使用してイベントを定義できます。span style="font-size: 18px;">キーワードを使用して、Solidity でイベントを定義できます。
interface ILight {
event SwitchedON();
event SwitchedOFF();
event BulbReplaced();
}
契約名を完全に修飾し、その後にを続けることでアクセスできます。
とイベント名を入力すると、次のように別の契約からイベントにアクセスできます:
event RegisteredSuccessfully(address user)
イベントの署名は次のようになります:
event RegisteredSuccessfully(address user)
The event subject hash will be:
bytes32 topicHash = RegisteredSuccessfully.selector;
Solidity v0.8.15以降のみ、イベントの.selector
メンバーのみ。
発信されたブロックチェーンのログを見ると、ログの件名のインデックス0(最初の)エントリーは、イベントトピックハッシュに対応します。トピックはログから検索できるコンテンツなので、イベントトピックハッシュでフィルタリングすることができます:
anonymous
匿名イベントはこのルールの例外です。anonymous
キーワードによって検索できなくなるので、"anonymous"となります。
この事実に基づいて、Solidity で定義された最も単純なイベントは、上記のようにパラメータを持たないことも推測できます。span>BulbReplaced
またはBulbReplaced
またはSwitchedON
は、一番下のLOG1
イベント自体が検索可能なので、ログ内のトピックをトリガーするためのオペコードです。
さらにトピックを追加することができ、他のトピックも使用されますLOG2, LOG3
LOG4
andandLOG5
、これらのパラメータがインデックスとしてマークされている限り、
としてマークされている限り、これらのパラメータはインデックス化されます。次のセクションでインデックス・パラメーターを見てみましょう。
イベント・パラメーターとインデックス・パラメーター
bytesN,bool
,address
...)struct
,,enum
およびユーザー定義値型。
この記事を書くにあたって私が調べたところによると、唯一許されない型は内部関数型です。外部関数型は許可されますが、内部関数型は許可されません。例えば、以下のコードはコンパイルできません。
// SPDX-License- 以下のコードはコンパイルできません。Identifier: UNLICENSED
pragma solidity ^0.8.0;
contract AnonymousEvents {
event SecretPasswordHashUpdated(bytes32 secretPasswordHash) anonymous;
}
イベントがとして宣言されている場合。span>anonymous
と宣言されている場合、コントラクトABIでは、イベントの"anonymous"
フィールドはtrue
としてマークされます。 とマークされます。
.;">img https://github.com/ethereum/solidity/issues/13086匿名イベントの利点の1つは、契約を展開するのが安くなり、トリガーされたときのガスが安くなることです。
匿名イベントの良いユースケースは、イベントが1つしかないコントラクトです。その1つのイベントだけがイベントログに表示されるため、コントラクト内のすべてのイベントをリッスンすることは理にかなっています。コントラクトが発するイベントは1つだけなので、その名前をサブスクライブすることは関係ありません。したがって、イベントを匿名として定義し、コントラクトからすべてのイベントログを購読し、それらがすべて同じイベントであることを確認することができます。
DappHubのDS-Noteコントラクト[7]など、人気のあるコードベースにおける匿名イベントの使用例を参照してください。
img ソースコードへ[8]上のコード・スニペットでは、イベント宣言が匿名であるため、4番目の"indexed "パラメータを定義することが可能になります。
匿名イベントはbytes32 subject hash.を持たないため、サポートされていないことに注意してください.selector
メンバーです。
LOGオペコードを使用してアセンブリーでイベントをトリガーする
LOG オペコードを使用してアセンブリーでイベントをトリガーする。
img https://docs.soliditylang.org/en/v0.8.19/yul.html#evm-dialectアセンブリでイベントをトリガーすることは可能です。EVM 命令セットのオペコードに対応する logN
命令を使用します。
アセンブリでイベントをトリガーするには、イベントによって発行されるすべてのデータをメモリに格納する必要があります。18px;">memory
特定の場所に格納します。
イベントによって発行されるデータをメモリに格納したら、次のパラメータをlogNディレクティブに割り当てることができます:
p = データの取得を開始するメモリの場所。これは基本的にメモリポインタであり、呼び方によっては「オフセット」や「メモリインデックス」とも呼ばれます。
s = pから始まる、イベントで送信したいバイト数。span>,t2
、t3
およびはt4
は、どちらもインデックス化したいイベントパラメータです。ここで重要なことが2つあることに注意してください: 1) これらは、イベント定義の中で同じ順序で定義されている同じパラメータでなければならないこと、2) これらは、データを取得するためにメモリに配置されなければならないことです。
次のコード・スニペットは、アセンブリでこの操作を実行する方法を示しています。
<span ) 10px 10px / 40px no-repeat ;height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;'>イベント ExampleEventAsm(bytes32 tokenId);
function _emitEventAssembly(bytes32 tokenId) internal{
bytes32 topicHash = ExampleEventAsm.selector;
;assembly {
let freeMemoryPointer := mload(0x40)
mstore(freeMemoryPointer、topicHash)
mstore(add(freeMemoryPointer, 32), tokenId)
// `ExampleEventAsm` イベントを発行する。ExampleEventAsm` イベントを2つのトピックで発生させる
log2(
freeMemoryPointer, // `p` = メモリの開始オフセット。メモリ上の開始オフセット
64, // `s` = イベントデータに含める `p` からメモリ上のバイト数
 Hash, // イベント自体をフィルタリングするためのトピック
tokenId // 1番目のindexed parameter
)
}
}
イベントのガス代

全記録のオペコード(LOG0
,LOG1
,,LOG2
,LOG3<
,LOG4
) はすべてガスを消費する。パラメータ(トピック)が多いほど、より多くのガスを消費します。
image.14px;">image-20240226195203141さらに、インデックスやデータサイズなどの他の要因によって、イベントが必要以上に時間を消費することがあります。
Check-Event-Interaction Pattern
Check-Enforcement-Interaction Pattern[9] もイベントに適用されます。はイベントにも適用されます。
これらのパターンを検出する1つの方法は、Remix静的解析ツールを使用することです。
このパターンはスリザーでも検出できます。外部呼び出しの後にイベントをトリガーするコントラクトでSlitherを実行すると、「再入可能なイベント」を示唆するディスカバリーが表示されます。
つまり、dAppsでは、どのイベントが最初、次、最後に発信されたかを正しく確認できるように、順番が重要なのです。これは、再帰的または再入可能な呼び出しの場合に特に重要です。外部呼び出しの後にイベントがトリガーされ、その外部呼び出しが再入可能な呼び出しを行う場合、
最初に発せられるイベントは、2回目のリエントラント呼び出しが完了した後のイベントです。
2番目に出されるイベントは、最初のトランザクションの後に出されるイベントです。
これを理解することで、コントラクトの呼び出しを監視するために、チェーンに至るまで明確な監査証跡を提供することも可能になります。どの関数が最初で最後に呼び出されるのか、トランザクションの実行中に各ルーチンが実行される順番を確認できます。
Documentation for the slither detector[10] - SolidityとVyper用の静的アナライザーです。
この潜在的な脆弱性は、Trail of BitsによるLiquity[11]スマートコントラクトの監査でも発見され、報告されています。
img
img イベントはいつトリガーされるべきか?
あなたの契約では、イベントをトリガーすることが重要で役に立つ状況がいくつかあるかもしれません。
img
imgスリザー検出器のドキュメント[12]にこれらのケースについての詳細が記載されています。
これはTrailのLooksRareに関する監査レポートにも記載されています。
img
img0xprotocol[13]
の詳細を見る。sup>を参照してください。
参考資料
匿名イベントの使用目的に関するドキュメントの欠落(何のためかを知る)[14]
[匿名イベントの利点]
。