Loopsについて、実際のコードを用いて説明していきます。
以下のRemixなどを使用して試してみてください。
以下のコントラクトは、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;
}
}
このコントラクトYulLoops
のcomputeSumInMemory
関数は、Solidityで記述されており、Yul(Ethereumの低レベル言語)のインラインアセンブリを使用して特定のメモリ範囲内の値の合計を計算します。
アセンブリブロック
-
アセンブリの初期化
-
assembly { ... }
ブロック内で、Yulのコードが書かれています。 - このブロックを使用することで、Solidityよりも低レベルの操作が可能になります。
-
ループでの合計計算
-
変数の初期化
-
sum := 0
で、合計を計算するための変数sum
が0で初期化されます。
-
-
forループ
-
for { let j := 0x40 } lt(j, 0x140) { j := add(j, 0x20) } { ... }
によって、メモリアドレス0x40
から始まり、0x140
より小さい間、メモリの内容を合計します。 - 各イテレーションで
j
は0x20
(32バイト、Solidityの変数の標準サイズ)ずつ増加します。 -
ループの条件
-
lt(j, 0x140)
は、j
が0x140
より小さい間ループを続けるという条件を設定します。
-
-
ループ内の処理`
- sum := add(sum, mload(j))
で、現在の
jアドレスからメモリを読み込み(
mload(j))、
sum`に加算します。
- sum := add(sum, mload(j))
-
結果の格納と返却
-
結果の格納
-
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 := 0
とlet index := 0
により、合計値を格納するtotal
変数と、ループのインデックスとして機能するindex
変数がそれぞれ0で初期化されます。
ループの実行
-
forループの構造
-
for { } lt(index, 0x100) { } { ... }
の形式で、初期化部分とイテレーション後の部分が空になっています。 - これにより、whileループのような振る舞いを実現しています。
-
ループ条件
-
lt(index, 0x100)
は、index
が0x100
未満である間ループを続ける条件を設定しています。
-
-
ループ本体
-
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
が関数の呼び出し元に返されます。