Deep EVM #6:Yulメモリ管理 — mstore、mload、フリーメモリポインタ
Engineering Team
ガベージコレクタなしのメモリ管理
Yulでは、あなたがメモリマネージャです。アロケータも、ガベージコレクタも、境界チェックもありません。メモリはMLOADとMSTOREで読み書きするフラットなバイト配列です。間違ったオフセットに書き込むと、自分のデータを破損させます。
このレベルの制御こそが、Yulをガス最適化に強力にし、同時に危険にしている理由です。
MSTORE、MLOAD、MSTORE8
// MSTORE(offset, value) — オフセットに32バイトを書き込む
mstore(0x00, 0xdeadbeef)
// MLOAD(offset) — オフセットから32バイトを読み取る
let value := mload(0x00)
// MSTORE8(offset, value) — 1バイトを書き込む
mstore8(0x00, 0xff)
重要な詳細:MSTOREはビッグエンディアンで書き込みます。
フリーメモリポインタ(0x40)
Solidityはオフセット0x40のフリーメモリポインタを値0x80で初期化します。使用可能なメモリは0x80から開始し、0x00-0x7fは予約されています。
Yulでのメモリ割り当て
function allocate(size) -> ptr {
ptr := mload(0x40)
mstore(0x40, add(ptr, size))
}
フリーメモリポインタを無視する場合
純粋なYulコントラクト(Solidityなし)では、フリーメモリポインタを完全に無視し、メモリを手動で管理できます。多くのMEVボットがこれを行います。
YulでのABIエンコードCalldata構築
最も一般的なYulタスクの1つは、外部コール用のcalldataの構築です。transfer(address,uint256)コールを構築する例を示します。
スクラッチスペース(0x00)にcalldataを格納し、call、リターンチェック — これがMEVボットでのガス効率的なトークン転送の標準パターンです。
メモリ効率的なハッシュ
Keccak256ハッシュは頻繁な操作です。Yulでは何がハッシュされるかを正確に制御できます:
function getMappingSlot(key, baseSlot) -> slot {
mstore(0x00, key)
mstore(0x20, baseSlot)
slot := keccak256(0x00, 0x40)
}
スクラッチスペース(0x00-0x3f)をハッシュに使用するのは、フリーメモリポインタを進める必要がないため、一般的なパターンです。
パターン:Returndataフォワーディング
let success := call(gas(), target, value, inOffset, inSize, 0, 0)
let size := returndatasize()
returndatacopy(0x00, 0x00, size)
switch success
case 0 { revert(0x00, size) }
default { return(0x00, size) }
このパターンはプロキシコントラクトの標準です。
実例:Uniswap V2リザーブの取得
function getReserves(pair) -> reserve0, reserve1 {
mstore(0x00, 0x0902f1ac00000000000000000000000000000000000000000000000000000000)
let success := staticcall(gas(), pair, 0x00, 0x04, 0x00, 0x60)
if iszero(success) { revert(0, 0) }
reserve0 := mload(0x00)
reserve1 := mload(0x20)
}
これがMEVボットがプールリザーブを読み取る方法です:最小メモリ使用の単一staticcall、ABIデコーディングオーバーヘッドなし。
まとめ
Yulでのメモリ管理は手動で、精密で、パワフルです。この記事のパターン — スクラッチスペースの使用、calldata構築、ハッシュ計算、returndataフォワーディング — は、すべてのガス最適化スマートコントラクトの構成要素です。