Solidityスマートコントラクトを開発するときは、アセンブリを使用しないことを常にお勧めします。ただし、他に選択肢がない場合もあるため、Solidityアセンブリの開発に関する知識を学ぶ必要があります。このチュートリアルでは、Solidityアセンブリ開発で動的バイト配列を使用する方法を学習します。
独自の使い慣れた言語学習イーサネットスクエアDApp開発を使用してください:
Java | Php | Python | .Net / C# | golang | Node.JS | Flutter / Dart
1.リミックスエディタを使用します
まず、この単純な契約をリミックスエディタに貼り付けましょう:
pragma solidity ^0.5.10;
contract AssemblyArrays {
bytes testArray;
function getLength() public view returns (uint256) {
return testArray.length;
}
function getElement(uint256 index) public view returns (bytes1) {
return testArray[index];
}
function pushElement(bytes1 value) public {
testArray.push(value);
}
function updateElement(bytes1 value, uint256 index) public {
testArray[index] = value;
}
}
まず、Remixエディターについて理解します。最初にコンパイラのバージョンを選択し、次にコントラクトをコンパイルし、コントラクトをデプロイし、いくつかの機能を実行してから、デバッグする必要があります。
2.アセンブリコードの最初の行
次に、getLength
関数を変更して、アセンブリコードの最初の行を記述します。
function getLength() public view returns (uint256) {
bytes memory memoryTestArray = testArray;
uint256 result;
assembly {
result := mload(memoryTestArray)
}
return result;
}
上記の数行のコードで多くのことが起こりました。アセンブラーはそのようなもので、非常に単純な機能を実現するには、多くのコードも必要です。我々はされますtestArray
この問題をこの資料の焦点であるので、メモリに記憶装置からコピー。将来的にはストレージスロットについて話すことができます。
アセンブリ言語ブロックを詳しく調べる前に、アセンブリ命令は32バイトのワードで動作することに注意してください。したがって、mload命令は、memoryTestArrayが指す
32バイトのメモリ位置をスタックにプッシュします。。
3.ブレークポイント設定とSolidityアセンブリコードのシングルステップ実行
今それをデバッグします。Remixでは、行番号をクリックしてブレークポイントを設定できます。11行目にブレークポイントを設定して、次のようにします。
getLength
関数を更新した後、必ずコントラクトをコンパイルして再デプロイしてください。ここで、pushElement
関数を呼び出してバイト0x05を配列に挿入してから、を呼び出すgetLength
と、関数は1を返すはずです。
getLength
を呼び出した後、デバッグできます。下部パネルの最後の呼び出しで[デバッグ]ボタンをクリックすると、左側のサイドバーにデバッガーが開きます。次のブレークポイントにジャンプする早送り用のボタン(fast_forward:など)があります。それをクリックしてみましょう。別のコンパイラまたは別の設定を使用する場合、それは完全に同じではない可能性がありますが、コアは同じになります。基本的な考え方はmload
、実行前にデバッガーを取得することです。私の環境では、#0871命令です。
4.Solidityアセンブリコードがスタックに与える影響を表示します
次に、デバッガーのサイドバーにあるスタック/スタックの内容を見てみましょう。
スタックの最上位では、位置0は0x0…80です。これは、mload命令のパラメーターとして使用される、メモリー内のmemoryTestArrayの場所です。
5.Solidityアセンブリコードがメモリに与える影響を表示します
それでは、アドレス0x0 ... 80から始まる、デバッガーサイドバーの「メモリ」セクションを見てみましょう。
31バイトの0x00、1バイトの0x01、1バイトの0x05、31バイトの0x00があります。これは少し混乱する可能性があるので、一歩下がって、1バイト(8ビット)が2つの16進数で表されていることに注意してください(1つの16進数は4桁を表します)。同様に、0x10の16進数の10進数は16に等しくなります。したがって、メモリ内では、場所0x80は16バイトを保持し、場所0x90(0x80 + 0x10)は次の16バイトを保持し、場所0xa0(0x90 + 0x10)は次の16バイトを保持し、場所0xb0は最後の16を保持します。バイト。アセンブリ内の命令は32バイト単位で動作するため、mload(0x80)を呼び出すと、メモリ位置0x80から32バイトを取得して、スタックに配置します。
6.mload命令をステップスルーします
実際の実装を見てみましょう。デバッガーの「シングルステップイン」ボタン(つまり下向き矢印)をクリックして、mload命令を実行してみましょう。次に、スタックの一番上を見てください。
mload
この命令は、スタックの最上位の内容(0x0…80)をフェッチしてから、メモリ内の位置0x0…1に32バイトをプッシュします。これは、メモリ内のバイト配列を理解するための最も重要なポイントです。最初の32バイトは配列の長さを格納します。
pushElement
関数を呼び出して、要素0x06を配列に挿入してみてください。次に、呼び出しgetLength
て再度デバッグします。同様に、mload
32バイトがメモリ位置0x80からロードされますが、今回はメモリの内容は0x0 ... 2です。新しい要素を追加すると、Solidityは配列のサイズを更新します。
メモリ内で変更されたもう1つの点は、位置0xa0が0x050600 ... 00になったことです。したがって、メモリ内では、バイト配列変数はその長さを最初の32バイトに格納してから、特定のメンバーの格納を開始します。最初に0x05を押し、次に0x06を押します。
7.Solidityアセンブリを使用してgetLengthメソッドを書き直します
さらにいくつかの要素をプッシュし、呼び出しgetLength
てデバッグし、メモリ内の新しいバイトを確認してください。我々は場合getElement
アセンブリに変換し、プロセスが明確になるだろう。
function getElement(uint256 index) public view returns (bytes1) {
uint256 length = getLength();
require(index < length);
bytes memory memoryTestArray = testArray;
bytes1 result;
assembly {
let wordIndex := div(index, 32)
let initialElement := add(memoryTestArray, 32)
let resultWord := mload(add(initialElement, mul(wordIndex, 32)))
let indexInWord := mod(index, 32)
result := shl(mul(indexInWord, 8), resultWord)
}
return result;
}
まあ、これはあなたを少し怖がらせるかもしれません!ゆっくりと撫でましょう。
最初の非常に重要なことはrequire
、index
それが範囲外ではないことを確認するためのステートメントを追加したことです。これは、mloadを呼び出すときに非常に重要です。ロードするメモリの場所が正しいことを確認する必要があります。そうしないと、呼び出し元がアクセスしてはならない情報が漏洩し、契約が攻撃の重大なリスクにさらされる可能性があります。 。
次に、アセンブリコードブロックを見てみましょう。以来mload
32のバイトを一度に読み込み、それだけで1バイトを読み取ることは容易ではありません。インデックスを32で割って切り上げると、検出されるメンバーの32バイトのシーケンス番号が取得されます。例えば:
div(0, 32) = 0
div(18, 32) = 0
div(32, 32) = 1
div(65, 32) = 2
かなり良さそうです。ただし、memoryTestArrayが指す組み込みメモリの最初のワード(32バイト)は、ストレージアレイの長さであることを忘れないでください。したがって、最初の配列メンバーを見つけるには、32バイトを追加する必要があります。これらすべての要素を考慮した後、必要なものを含む1バイトのワード(32バイト)をロードできます。
memoryTestArray
各ワードには32バイトがあるため、メモリ位置に32バイトを追加して配列の長さをスキップし、wordIndexに32を掛けたものを追加します。
しかし、それはまだ終わっていません。次に、このワードから正確に1バイトを抽出する必要があります。これを行うには、ワード内のバイトのインデックスを見つける必要があります。これは、単語インデックスの残りを32で割ったものであり、mod
指示によって取得できます。例えば:
mod(0, 32) = 0
mod(18, 32) = 18
mod(32, 32) = 0
mod(65, 32) = 1
はい、最後のステップを完了して、バイトを抽出しましょう。最前面のバイトを作成するには、必要なビット数を左に移動します。shl
命令は一度に1ビット移動するため、指定されたビット数を移動するには、indexInWordに8を掛ける必要があります。
このバイトで始まる32バイトのワードを結果変数に割り当てると、
そのタイプをとして宣言したため、他のすべてのバイトが削除されbytes1
ます。