はじめに

『DApps開発入門』という本や色々記事を書いているかるでねです。

今回は、トランザクション中のみ有効な一時的ストレージ(Transient Storage)を導入し、トランザクション実行時に有効な一時的なデータ領域を低コストで実現する仕組みを提案しているEIP1153についてまとめていきます!

以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。

他にも様々なEIPについてまとめています。

概要

EIP1153では、「トランジエントストレージ(Transient Storage)」と呼ばれる、一時的なストレージ領域を扱う新しいオペコード TLOAD(0x5c)と TSTORE(0x5d)の導入が提案されています。 トランジエントストレージは通常のストレージと同じようにアクセスできますが、その内容はトランザクションの終了時に破棄されて永続化されません。 つまり、ディスクへの読み書きが発生しないため、通常のストレージ操作よりもガスコストが大幅に安くなります。

また、TSTORE による書き込みは、従来の SSTORE に適用されるEIP2200の「ガススタイペンド(Gas Stipend)」チェックを受けずに実行できるため、より柔軟に使用することが可能です。

これにより、スマートコントラクトはストレージのような構造を使いながらも、コスト効率よく一時的なデータのやり取りができるようになります。

動機

Ethereumでは、1つのトランザクション内で複数のコントラクト呼び出し(CALL など)によって「フレーム」と呼ばれる実行コンテキストがネストされることがあります。 このような状況で、同一のコントラクトが再入可能な形で複数回呼び出される場合、各フレーム間でデータを安全かつ効率的にやり取りする方法が求められます。

現在、こうしたフレーム間の通信には主に以下の2つの手段があります。

  • CALL 時の引数・戻り値を通じたデータの受け渡し
  • 永続ストレージ(SSTORE / SLOAD)を用いた間接的な情報共有

しかし、前者は中間に信頼できないコントラクトが挟まると情報の改ざんや漏洩のリスクがあり、後者はガスコストが非常に高いという問題があります。 特にリフレンドの制限(EIP3529により、トランザクション全体で使えるストレージリファンドは消費ガス量の20%までに制限)により、一時的に使用するだけのストレージでも高コストとなる場合があります。

EIP3529については以下の記事を参考にしてください。

これらの課題に対し、トランジエントストレージは次のようなメリットを提供します。

  • 書き込み後はトランザクション終了時に自動的に消えるため、ステートの後処理が不要。
  • ストレージリファンドの制限の影響を受けない。
  • ディスクI/Oが発生しないため、高速かつ低コスト。

その結果、例えば再入防止ロック、単一トランザクションでのトークン承認(temporaryApprove)、プロキシ経由でのデータ伝達、CREATE2 によるアドレス生成時の引数渡しなど、多くのユースケースで安全かつ効率的な実装が可能になります。

さらに、Solidityなどの言語では、memory や storage に加えて transient という新しい修飾子を導入することで、トランジエントストレージの言語サポートも容易に実現できる設計になっています。

仕様

Transient Storageの仕様

新たに導入されるオペコード:TLOAD と TSTORE

EVMに新しく導入されるのは、TLOAD(オペコード0x5c)と TSTORE(オペコード0x5d)という2つの命令です。 これらは従来のストレージ操作である SLOAD(0x54)と SSTORE(0x55)と同じ引数・構文を持ちますが、操作対象はトランジエントストレージ(トランザクション終了時に破棄される一時的なストレージ)です。

TLOAD は、スタックの最上位から32バイトの値を1つ取り出してアドレスとして解釈し、そのアドレスに格納されたトランジエントストレージの32バイトの値を取得してスタックにプッシュします。

TSTORE は、スタックから32バイトの値を2つ取り出します。 1つ目はアドレス、2つ目は保存する値です。 そして、指定されたアドレスにその値をトランジエントストレージに保存します。

アドレスの扱いも永続的ストレージ(SLOAD/SSTORE)と同様で、32バイトごとに1つのスロットが割り当てられています。

ガスコストの扱い

TLOAD のガスコストは、ホットな SLOAD(直前に読み込まれた値)と同等で、現在は100 gasとされています。

TSTORE のガスコストは、ダーティなスロット(すでに変更されたが新しい値でも古い値でもない)のウォームな SSTORE と同等で、こちらも現在は100 gasです。 これは、トランジエントストレージが巻き戻し(revert)に対応する必要があるため、通常のメモリよりは重い処理になることを反映した設計です。

ライフサイクルとアクセス制限

トランジエントストレージに格納された値は、トランザクションが完了するタイミングで全て破棄されます。 これにより、次のトランザクションには一切引き継がれません。

また、このストレージは「プライベート」であり、アクセスできるのはそのストレージを所有しているコントラクトのみです。 つまり、1つのコントラクト内で複数のフレーム(呼び出しコンテキスト)が存在する場合でも、全てのフレームが共通のトランジエントストレージを参照します。 この動作は、メモリではなくストレージに近いものです。

CALL と DELEGATECALL での挙動の違い

DELEGATECALL や CALLCODE を使って他のコントラクトのコードを現在のコントラクトの文脈で実行する場合、トランジエントストレージの所有者は「呼び出し元(caller)」のコントラクトとなります。 これは永続的なストレージの仕様と同じです。

一方で、CALL や STATICCALL を使って他のコントラクトを通常の方法で呼び出した場合、ストレージの所有者は「呼び出し先(callee)」のコントラクトになります。

リバート時の扱い

あるフレームで TSTORE による書き込みが行われた場合、そのフレームがリバートされると、書き込みも含めて全ての変更は元に戻されます。 このルールは、内部的に他のコントラクトを呼び出した結果生じた変更も巻き戻す点で、永続的ストレージと同様の設計です。

STATICCALL での使用制限

TSTORE を STATICCALL の文脈で使用するとエラーが発生し、書き込みは行われません。 これは STATICCALL がステート変更を禁じているためです。 一方で、TLOAD は読み取り専用操作であるため、STATICCALL の中でも問題なく使用できます。

EIP2200との違い

EIP2200では、SSTORE において gasleft(残りガス)が gas stipend(2300 gas)以下である場合、例外が発生するという仕様が定められています。 しかし、TSTORE ではこの条件が適用されず、gas stipendにかかわらず常に実行可能です。これにより、より柔軟な制御が可能になります。

補足

他のアプローチとの比較

フレーム間の通信(inter-frame communication)のために、一時的なストレージをどう扱うかについては、他にも選択肢があります。 例えば、既存の SSTORE や SLOAD のオペコードに対するガスコストを、トランジエント的な使い方に対して安くするという方法です。 実際に、EIP2200ではそうした調整が行われました。

しかし、その後のEIP3529により、ストレージ操作によるガスリファンド(ガスの払い戻し)はトランザクション全体の使用量の20%に制限されました。 これにより、一時的にストレージを使っても大部分のリファンドが無効化され、コスト的に見合わなくなっています。

もう1つの選択肢として、トランジエント用途に限ってはリファンドの上限を撤廃し、通常のストレージとは別カウントにする方法もあります。 しかし、この方法は実装と理解の両面で複雑になりすぎます。 例えば、リファンドの20%上限がトランジエントと非トランジエントで混ざった場合、どの部分にどの制限を適用するかが難解になります。 このような複雑な仕組みよりも、そもそもリファンド処理と無関係な「独立した仕組み」としてTransient Storageを導入する方が、シンプルで将来のアップグレードにも対応しやすいと考えられています。

アドレッシング方式についての議論

一部の開発者からは、TLOAD と TSTORE のようにストレージライクな32バイト単位(word-addressed)のインターフェースではなく、メモリに近い「バイト単位(byte-addressed)」のインターフェースの方が、Transient Storageの性質に合っているのではないかという指摘もあります。

しかし、Transient Storageはトランザクション中に一時的にキー付きでデータを管理する用途(例:ERC20の一時承認)に使われることが多く、そのような用途では (address, address) のような複雑なマッピングが必要になります。 線形メモリではマッピングのようなアクセスが難しく、ガスコストやリバート処理の実装もより複雑になります。

メモリ的なインターフェースを保ちながら内部的にはマップ構造で管理するという案もありますが、それは結局「第三のモデル」(ストレージでもメモリでもない)を定義することになり、コンパイラやクライアントにさらなる負荷がかかります。 そのため、ストレージに近い構造を保った今の形が、現実的かつ扱いやすい選択とされています。

トランザクションIDの誤解

一部では、「トランザクションに一意のIDを付ければ、Transient Storageのような仕組みは不要になるのではないか」と考える意見もあります。 しかし、これは誤解です。 トランザクションIDを使って通常のストレージを操作する方式でも、ガスリファンドの制限やリバートの複雑さといった根本的な問題は解決されません。 つまり、トランザクションIDとTransient Storageはそれぞれ別の目的を持った独立した機能であり、片方で代替できるものではありません。

Transient Storageを導入することのデメリット

Transient Storageの導入にはいくつかのコストもあります。 既存のコントラクトには影響を与えない一方で、クライアント側の実装には新たなコードが必要です。 また、EthereumのYellow Paperにも新しい概念を追加しなければならないため、更新の手間も発生します。

導入によるメリット

一方で、導入によるメリットは非常に大きく以下の点が挙げられます。

  • ガスリファンドの上限(EIP3529など)に影響されず、独立して設計できる。
  • クライアントは元のストレージ値を読み込む必要がなく、処理効率が高い。
  • 書き込み時に余計な初期コストを支払う必要がない。
  • 既存のストレージ操作の意味を壊さず、後方互換性を保てる。
  • 使用後にストレージスロットを「クリア」する手間が不要。
  • ガス計算がシンプルになり、予測性が高まる。
  • 将来的なストレージ設計(たとえば Verkle Tree)にも影響を与えずに済む。

これらの理由から、Transient Storageの導入は、EVMの効率性と安全性を高めるための合理的かつ現実的な進化といえます。

互換性

EIP1153は新しいオペコード TLOAD と TSTORE を導入するものであり、既存の SLOAD や SSTORE といったオペコードの挙動には一切変更を加えません。そのため、既にデプロイされているスマートコントラクトや、過去に動作していたコードに影響を与えることはありません。 ただし、EVMの動作仕様に新たな命令を加える必要があるため、実装にはハードフォークが必要になります。

テストケース

EIP1153の挙動を確認するためのテストスイートはすでに用意されており、仕様に沿った正確な動作が保証されています。 具体的には、トランザクション中の値の書き換え、リバート時の値の復元、ネストしたコールによる状態の分離など、Transient Storage特有の挙動に関するテストが行われています。

参考実装

Transient Storageは、単に値を記録しておくだけでなく、トランザクション中に発生する「リバート(巻き戻し)」にも対応しなければなりません。 つまり、あるコントラクト呼び出し内で書き込まれた一時的な値が、例外発生により無効となった場合、以前の状態に正確に戻る必要があります。

これを実現するために、実装上では以下の3つの構造を用います。

  • current
    • 現在のトランジエントストレージの状態を保持するマップ(ネストされたオブジェクト)。
  • journal
    • 全ての変更履歴を記録するログ(ジャーナル)。
  • checkpoints
    • 巻き戻しの基点となる時点の記録(ジャーナルの長さを記録)。

この実装により、値の読み書きはすべて高速(O(1)) に行うことができ、リバート処理時だけ O(N) の操作(ログの巻き戻し)が発生します。 これは、既存のEthereumクライアントが採用しているステートジャーナル機構と同様であり、特に新しいリスクを生むわけではありません。

セキュリティに関する考慮(DOS対策)

理論的には、1つのトランザクション内で大量のトランジエントストレージへの書き込みを行い、最終的に revert() を呼び出すことで、EVMに高負荷をかけるDoS攻撃が懸念されます。 しかし、これは既存の永続ストレージでも同様に起こりうる問題であり、Ethereumクライアントではすでにこうしたケースに対処するための安全設計がなされています。

実際の例として、以下のようなDoSを試みるコードが紹介されています。

solidity
pragma solidity =0.8.13;

contract TryDOS {
    uint256 slot;

    constructor() {
        slot = 1;
    }

    function tryDOS() external {
        uint256 i = 1;
        while (gasleft() > 5000) {
            unchecked {
                slot = i++;
            }
        }
        revert();
    }
}

このコードはガスを使い切るまで書き込みを続けた後にリバートを実行しますが、クライアント側ではこの程度の操作で性能が破綻することはありません。Transient Storageにおいても同様に、安全性は確保されています。

セキュリティ

TSTOREによる新たなメモリ割り当て手段

TSTORE は、1回の操作で32バイトのデータを100 gasで格納できる新たな手段を提供します。 これにより、コントラクト内で比較的安価に大容量の一時的データ領域を確保できるようになります。 理論上、1トランザクションあたり最大で約30,000,000 gasを使用した場合、およそ9.15MBのデータをトランジエントストレージに保存できます。

これは、同じガス量で MSTORE(通常のメモリ割り当て)を用いて確保できるメモリ容量約3.75MBを大きく上回ります。 さらに、メモリ展開コストのリセットを目的とした関数呼び出しを繰り返すことで、1ミリオンガスあたり約700KBの割り当てを繰り返し、最終的には約20MBものメモリ相当の領域をトランジエントストレージに確保することも可能です。

このように、TSTORE によって大容量の一時ストレージを効率的に確保できる一方で、EVMのガスリミットを考慮した適切な使用が求められます。

トランジエントストレージのライフサイクルの理解

Transient Storageは、トランザクションが完了すると自動的にすべての値が破棄されるという特徴があります。 このため、開発者の中には「どうせ消えるから、わざわざ値を0に戻す必要はない」と考えることがあります。

しかし、これは非常に危険な設計判断となる可能性があります。 例えば、再入防止用のロック機構などでは、スロットの値を適切に初期化(またはクリア)しなければ、そのトランザクション内での後続の呼び出しに悪影響を及ぼす可能性があります。 また、意図せず残された非ゼロの値が、そのトランザクション内でバグや予期せぬ動作を引き起こすリスクも存在します。

そのため、トランジエントストレージに非ゼロの値を残すのは、同一トランザクション内でそのスロットが明示的に再利用されることが保証されている場合のみに留めるべきです。

メモリ代替としての使用に関する注意点

一部の開発者は、トランジエントストレージを通常のメモリの代替として、in-memoryマッピング(動的なマップ構造)の実装手段と見なすかもしれません。 しかし、これは推奨されません。

通常のEVMメモリは、呼び出しフレームの終了や revert によって完全に破棄される一方で、トランジエントストレージはトランザクション全体のスコープで共有され続けます。 つまり、ある関数呼び出しで意図せず書き込まれたトランジエントストレージの値が、後続の別フレームに影響を与える可能性があります。 これは再入時にバグを引き起こす要因となります。

加えて、TSTORE による操作は MSTORE よりもはるかに高コストであり、ガス効率の面からもメモリの代替には不向きです。実際、メモリ上でのマッピング的な構造は、ソート済みのリストなどで代替可能なことが多く、EVMにおけるin-memoryマッピングの本格的な使用例は非常に少ないとされています。

引用

Alexey Akhunov (@AlexeyAkhunov), Moody Salem (@moodysalem), "EIP-1153: Transient storage opcodes," Ethereum Improvement Proposals, no. 1153, June 2018. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-1153.

最後に

今回は「トランザクション中のみ有効な一時的ストレージ(Transient Storage)を導入し、トランザクション実行時に有効な一時的なデータ領域を低コストで実現する仕組みを提案しているEIP1153」についてまとめてきました! いかがだったでしょうか?

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!

他の媒体でも情報発信しているのでぜひ他も見ていってください!