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

Yulにおけるメモリの仕組み

Mark Jonathas headshot

執筆者 Mark Jonathas

2023年7月28日 公開読了時間 3 分

Yulは、スマートコントラクト内でアセンブリ言語の一種を書くために使用できる中間言語です。Yulのstorageと、パックされたstorage変数の読み書き方法について学んだら、次はYulスマートコントラクトにおけるメモリの動作について学ぶことが重要です。

Yulにおけるメモリの動作

メモリはstorageとは異なる動作をします。メモリは永続的ではなく、関数の実行が終わるとすべての変数がクリアされます。

メモリは他の言語におけるヒープに似ていますが、ガベージコレクタは存在しません。

メモリはstorageよりもはるかに安価です。最初の22ワードのメモリコストは線形に計算されますが、それ以降はメモリコストが二次関数的に増加するため注意が必要です。

メモリは32バイト単位で配置されます。詳しくは後述しますが、ここでは0x00 - 0x20が1つの単位であることを理解しておいてください(スロットのようなものと考えても構いませんが、両者は異なるものです)。

Solidityは0x00 - 0x40をスクラッチスペースとして割り当てます。このメモリ領域が空であることは保証されておらず、特定の操作に使用されます。

0x40 - 0x60には、フリーメモリポインタと呼ばれるものの位置が格納されており、これは新しい値をメモリに書き込む際に使用されます。

0x60 - 0x80は空のギャップとして残されています。

0x80から実際の操作を開始します。

メモリは値をパックしません。storageから取得した値は、それぞれ独自の32バイト単位で格納されます(例:0x80-0xa0)。

Yulでメモリを使用する操作とは

メモリは以下の操作で使用されます。

  • 外部呼び出しの戻り値
  • 外部呼び出しの関数値の設定
  • 外部呼び出しからの値の取得
  • エラー文字列を伴うrevert
  • ログメッセージ
  • keccak256()によるハッシュ化
  • 他のスマートコントラクトの作成

メモリに関する便利なYul命令を紹介します:

Instruction
Explanation

mload(p)

sload()に似ていますが、pの後の32バイトをロードすることを意味します

mstore(p, v)

sstore()に似ていますが、値vをpからさらに32バイトの位置に格納することを意味します

mstore8(p, v)

mstore()に似ていますが、1バイトのみを対象とします

msize()

アクセスされた中で最大のメモリインデックスを返します

pop(x)

値xを破棄します

return(p, s)

実行を終了し、メモリ位置p - vからデータを返します

revert(p, s)

状態変更を保存せずに実行を終了し、メモリ位置p - vからデータを返します

続いて、さらに別のデータ構造を見ていきましょう!

Yulでstructとメモリを使う方法

structと固定長配列は実際には同じように動作しますが、固定長配列についてはすでにstorageのセクションで扱ったため、ここではstructを見ていきます。以下のstructをご覧ください。

特に変わったところはなく、単純なstructです。

それでは実際のコードを見てみましょう!

ここでは、s.subVar1をメモリ位置0x80 - 0xa0に、s.subVar2をメモリ位置0xa0 - 0xc0に設定しています。そのため、0x80 - 0xc0をreturnしています。トランザクション終了直前のメモリレイアウトを表にまとめると以下のようになります。

Memory Location
Value Stored

0x00

スクラッチスペース(空)

0x20

スクラッチスペース(空)

0x40

0xc0(フリーメモリポインタ)

0x60

0x80

s.subVar1: 0x20(10進数で32)

0xa0

s.subVar1: 0x40(10進数で64)

0xc0

新しいフリーメモリポインタ。これはmsize()が返す値です。(空)

ここから分かること:

  • 0x00 - 0x40はスクラッチスペースとして空になっている
  • 0x40はフリーメモリポインタを示している
  • Solidityは0x60にギャップを残している
  • 0x80と0xa0はstructの値の格納に使われている
  • 0xc0は新しいフリーメモリポインタになっている

このメモリのセクションの最後に、動的配列がメモリ上でどのように扱われるかを見ていきます。この例では、パラメータarrとして[0, 1, 2, 3]を渡します。さらにこの例では、配列に要素を1つ追加してみます。ただし本番環境でこれを行う際は、別のメモリ変数を上書きしてしまう可能性があるため注意してください。

コードは以下の通りです:

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

  • 配列がメモリ上のどこに格納されているかを取得する
  • 配列の先頭のメモリ位置に格納されている、配列の長さを取得する
  • その位置に32バイトを加算し(配列の長さ部分をスキップし)、次に使用可能な位置を確認する
  • 配列の長さに32バイトを掛けて、次のメモリ位置まで進める
  • 新しい値(4)を格納する
  • 配列の長さを1増やして更新する
  • フリーメモリポインタを更新する
  • 配列をreturnする

もう一度メモリレイアウトを見てみましょう。

Memory Location
Value Stored

0x00

スクラッチスペース(空)

0x20

スクラッチスペース(空)

0x40

0x140(フリーメモリポインタ)

0x60

0x80

配列の新しい長さ(6)

0xa0

arr[0](0)

0xc0

arr[1](1)

0xe0

arr[2](2)

0x100

arr[3](3)

0x120

arr[4](4)

0x140

フリーメモリポインタ(空)

以上でメモリに関するセクションは終わりです!

次は、Yulを使ってスマートコントラクトを呼び出す方法を学ぶか、パックされたstorage変数の読み書き方法に戻って学び直してください!

Background gradient

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

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