Chapter 18

[Yul Tutorial] Loops

かるでね
かるでね
2024.03.04に更新

Loopsについて、実際のコードを用いて説明していきます。

以下のRemixなどを使用して試してみてください。

https://remix.ethereum.org/

以下のコントラクトは、Yul内でforループを使用してメモリ領域の合計を計算し、またwhileループの代わりとしてforループを使用する方法を別の形で示しています。
それぞれの例は、ループを使用してメモリ操作を行い、条件に基づいて反復処理を実行する方法を示していますが、オリジナルの処理内容に変更を加えています。

for

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract YulLoops {
    function computeSumInMemory() external pure returns (uint256) {
        uint256 sum;
        assembly {
            // 初期値0でsum変数を初期化
            sum := 0
            
            // 0x40から0x140までの範囲でメモリの値を合計
            for { let j := 0x40 } lt(j, 0x140) { j := add(j, 0x20) } {
                sum := add(sum, mload(j))
            }
            
            // 結果を外部変数に格納
            mstore(0, sum)
        }
        return sum;
    }
}

このコントラクトYulLoopscomputeSumInMemory関数は、Solidityで記述されており、Yul(Ethereumの低レベル言語)のインラインアセンブリを使用して特定のメモリ範囲内の値の合計を計算します。

アセンブリブロック

  • アセンブリの初期化
    • assembly { ... }ブロック内で、Yulのコードが書かれています。
    • このブロックを使用することで、Solidityよりも低レベルの操作が可能になります。

ループでの合計計算

  • 変数の初期化
    • sum := 0で、合計を計算するための変数sumが0で初期化されます。
  • forループ
    • for { let j := 0x40 } lt(j, 0x140) { j := add(j, 0x20) } { ... }によって、メモリアドレス0x40から始まり、0x140より小さい間、メモリの内容を合計します。
    • 各イテレーションでj0x20(32バイト、Solidityの変数の標準サイズ)ずつ増加します。
    • ループの条件
      • lt(j, 0x140)は、j0x140より小さい間ループを続けるという条件を設定します。
    • ループ内の処理`
      • sum := add(sum, mload(j))で、現在のjアドレスからメモリを読み込み(mload(j))、sum`に加算します。

結果の格納と返却

  • 結果の格納
    • mstore(0, sum)によって、計算されたsumの値をメモリの0番地に格納します。
    • これは、結果をSolidityの変数に格納する一般的な方法ですが、このコードでは実際には不要な操作となります。
    • sumは既にSolidityの変数であり、直接return文で返されます。
  • 関数の戻り値
    • 最終的にreturn sum;により、計算された合計値が呼び出し元に返されます。

while

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract YulLoops {
    function sumWithWhileStyle() external pure returns (uint256) {
        uint256 total;
        assembly {
            // totalとindex変数を0で初期化
            total := 0
            let index := 0
            
            // indexが0x100未満の間、ループを実行
            for { } lt(index, 0x100) { } {
                total := add(total, mload(index))
                index := add(index, 0x20)
            }
            
            // 結果を外部変数に格納
            mstore(0, total)
        }
        return total;
    }
}

このYulLoopsコントラクトのsumWithWhileStyle関数は、forループを使用してwhileループのように動作する例を示しています。
この関数は、特定のメモリ範囲内の値を合計することを目的としています。

アセンブリブロック

  • アセンブリの開始
    • assembly { ... }ブロックを通じて、低レベルのアセンブリ言語(Yul)を使用しています。
    • これにより、Ethereum Virtual Machine(EVM)の機能に直接アクセスできます。

ループの初期化

total := 0let index := 0により、合計値を格納するtotal変数と、ループのインデックスとして機能するindex変数がそれぞれ0で初期化されます。

ループの実行

  • forループの構造
    • for { } lt(index, 0x100) { } { ... }の形式で、初期化部分とイテレーション後の部分が空になっています。
    • これにより、whileループのような振る舞いを実現しています。
    • ループ条件
      • lt(index, 0x100)は、index0x100未満である間ループを続ける条件を設定しています。
    • ループ本体
      • total := add(total, mload(index))で、indexアドレスからメモリを読み込み(mload(index))、それをtotalに加算しています。
      • index := add(index, 0x20)で、indexを32バイト(次のSolidity変数の位置)増加させています。

結果の格納

mstore(0, total)により、計算された合計値totalをメモリの0番地に格納しています。
これは、Solidityの変数に計算結果を格納する一般的な方法ですが、このコードでは実際には不要です。
関数の最後でtotalを直接返すことで、同じ結果を達成できます。

戻り値

return total;で、計算された合計値totalが関数の呼び出し元に返されます。