コンテンツへスキップ
0%

Yulのストレージはどう機能するか?

Mark Jonathas headshot

執筆者 Mark Jonathas

2023年8月1日 公開読了時間 1 分

Yulは、スマートコントラクト内でアセンブリ言語の一種を記述するために使用できる中間プログラミング言語です。Yulを理解することで、スマートコントラクトのレベルを上げ、ユーザーのガスコストを削減できます。Yulでスマートコントラクトを書き始めるには、まずストレージの仕組みを理解することが重要です。

Yulのストレージはどのように機能するか?

Yulの仕組みをさらに深く掘り下げる前に、スマートコントラクトにおけるストレージの仕組みについてしっかり理解しておく必要があります。ストレージは一連のスロットで構成されています。1つのスマートコントラクトには2²⁵⁶個のスロットが存在します。

変数を宣言する際は、スロット0から始めてそこから増分していきます。各スロットは256ビット(32バイト)の長さで、これがuint256やbytes32という名前の由来です。すべての変数は16進数に変換されます。

uint128のような変数を使用する場合、その変数の格納にスロット全体を使うわけではありません。代わりに、左側が0でパディングされます。例を見て理解を深めましょう。

var1: uint256型の変数は32バイトに相当するため、var1はスロット0全体を占有します。

スロット0に格納される内容は以下の通りです:

0x0000000000000000000000000000000000000000000000000000000000000100

var2: アドレスは少し複雑です。ストレージを20バイトしか消費しないため、アドレスは左側を0でパディングされます。

スロット1に格納される内容は以下の通りです:

0x0000000000000000000000009acc1d6aa9b846083e8a497a661853aae07f0f00.

var3: これは単純に見えるかもしれません。スロット2はbytes32型変数全体によって消費されます。

var4とvar5: uint128がパディングされることを先ほど説明しました。変数の合計サイズが32バイト未満になるように順序を決めれば、それらを1つのスロットにまとめて収めることができます。これは変数のパッキングと呼ばれ、ガスの節約につながります。

スロット3に格納される内容を見てみましょう:

0x0000000000000000000000000000000200000000000000000000000000000001. 

0x0000000000000000000000000000020x000000000000000000000000000001は同じスロットにぴったりと収まっていることに注目してください。これは、両方とも16バイト(スロットの半分)を占めるためです。

Instruction
Explanation

sload(p)

Loads the variable in slot p from storage.

sstore(p,v)

Assigns storage slot p value v.

v.slot

Returns the storage slot of variable v.

v.offset

Returns the index in bytes of where variable v begins in a storage slot. Variables are packed from right to left.

もう1つの例を見てみましょう!

x = 3。var5がスロット3にパックされていることは既に分かっているので、これは納得できます。

y = 16。var4がスロット3の半分を占めていることも既に分かっているので、これも納得できるはずです。変数は右から左にパックされるため、var5の開始インデックスとしてバイト16が得られます。

z = 1。sstore()はスロット0に値1を割り当てています。その後、sload()によってスロット0の値をzに代入しています。

先に進む前に、この関数をremixファイルに追加しておくことをお勧めします。これにより、各ストレージスロットに何が格納されているかを確認できるようになります。

それでは、もう少し複雑なデータ構造を見ていきましょう!

静的配列を扱う場合、EVMはデータのためにいくつのスロットを割り当てるべきかを把握しています。この特定の配列では、1スロットあたり2つの要素をパックしています。

そのため、getValInHex\(4\)を呼び出すと、0x0000000000000000000000000000000100000000000000000000000000000000が返されます。

予想通り、右から左に読むと、値0と値1が確認できます。

スロット5には0x0000000000000000000000000000000300000000000000000000000000000002が含まれています。

次は動的配列について見ていきます。

getValInHex\(6\)を呼び出してみてください。0x00が返されることが分かります。EVMは何個のストレージスロットを割り当てる必要があるか把握できないため、ここに配列を格納することはできません。

代わりに、現在のストレージスロット(スロット6)のkeccak256ハッシュが配列の開始インデックスとして使用されます。ここからは、目的の要素のインデックスを加算するだけで値を取得できます。

動的配列の要素を見つける方法を示すコード例です:

ここでは配列のスロットを取得し、add()操作とsload()を組み合わせて、目的の配列要素の値を取得しています。

他の変数のスロットと衝突する可能性はないのか、と疑問に思うかもしれません。

これは完全に起こり得ることですが、2²⁵⁶が非常に大きな数であるため、極めて発生しにくいものです。

マッピングは動的配列と似た動作をしますが、スロットとキーを一緒にハッシュする点が異なります。

このデモでは、マッピングの値をvar8[1] = 2に設定しました。それでは、マッピングのキーに対応する値を取得する方法の例を見てみましょう。

ご覧の通り、コードは動的配列から要素を見つけた場合と非常によく似ています。主な違いは、キーとスロットを一緒にハッシュする点です。

ストレージに関するセクションの最後の部分として、ネストされたマッピングについて学びます。先に進む前に、これまで学んだ内容をもとに、ネストされたマップの値を読み取る独自の実装を書いてみることをお勧めします。

この例では、マッピングの値をvar9[0][1] = 2に設定しました。

コードはこちらです。早速見ていきましょう!

ここで行われていることは以下の通りです:

  1. まず、最初のキー(0)のハッシュを取得します。
  2. 次に、そのハッシュと2番目のキー(1)のハッシュを取得します。
  3. 最後に、スロットからロードして値を取得します。

おめでとうございます、Yulにおけるストレージのセクションを完了しました!

Yul MemoryYul Contract CallsYul Packed Storage Variablesもぜひご覧ください。

Background gradient

ブロックチェーンで魔法を生み出す

Alchemyは、最も強力なweb3開発者向けプロダクトとツールを、豊富なリソース、コミュニティ、そして卓越したサポートと組み合わせて提供します。