assembly{}
Solidityでは、Ethereum仮想マシン(EVM)の低レベルな命令に直接アクセスできるassemblyブロックを使用することができます。これは通常、ガスコストを最適化したり、EVMが提供するより低レベルの機能を利用したりするために使用されます。
ただし、assemblyブロックは非常に注意深く使用する必要があります。なぜなら、Solidityの型安全性や他の高レベルの抽象化がないため、エラーを犯す可能性が高くなります。
assemblyブロック内では、以下のような命令が使用できます:
-
mload(p): メモリのアドレスpから32バイトのデータをロードします。 -
mstore(p, v): メモリのアドレスpに値vをストアします。値vは32バイトとみなされます。 -
sload(p): ストレージのアドレスpから32バイトのデータをロードします。 -
sstore(p, v): ストレージのアドレスpに値vをストアします。値vは32バイトとみなされます。 -
add(x, y),sub(x, y),mul(x, y), etc: 様々な算術命令。 -
jump(p),jumpi(p, cond): 制御フロー命令。
以下に、assemblyを使用して加算を行う簡単な例を示します:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Assembly {
function add(uint _x, uint _y) public pure returns (uint) {
uint result;
assembly {
result := add(_x, _y)
}
return result;
}
}
このコードはadd関数を定義し、それは2つの整数をパラメータとして受け取ります。関数はassemblyブロックを使用してこれらの数値を加算し、結果を返します。
再度、assemblyの使用は注意が必要であり、可能な限りSolidityの高レベルの構造を使用することが推奨されます。また、私の知識は2021年9月までの情報であるため、最新のSolidityのバージョンでassemblyの使用方法が変わっている可能性もあります。最新の情報を得るためには、公式のSolidityドキュメンテーションをご確認ください。
add()についてもう少し詳しく
function _split(bytes memory _sig) internal pure returns (bytes32 r, bytes32 s, uints v) {
assembly {
r := mload (add(_sig, 32))
s := mload (add(_sig, 64))
v := byte(0, mload(add(_sig, 96))
}
}
このコードの中でのadd関数は、EVM(Ethereum Virtual Machine)のアセンブリーレベルでメモリアドレスを計算するために使われています。
引数として渡された_sigはバイト配列で、メモリに保存されています。配列の最初の32バイトは配列の長さを格納しており、実際のデータは33バイト目から始まります。
ここでのadd(_sig, 32)は、_sigの実データ開始位置(すなわち、配列の長さを格納している32バイトを飛ばした位置)を示すメモリアドレスを計算しています。同様に、add(_sig, 64)とadd(_sig, 96)は、それぞれ64バイトと96バイト先のメモリアドレスを計算しています。
そして、それぞれのアドレスから32バイトのデータをロードしています(mload関数がその役割を果たしています)。ここでロードされたデータは、それぞれECDSA署名のr成分、s成分、およびv成分(回復ID)です。
ただし、ここでのvは単一のバイトであるため、96バイト先のメモリアドレスから32バイトをロードし、その最初のバイトを抽出しています(byte関数がその役割を果たしています)。
なお、コードのuints vは恐らく誤記で、uint8 vが正しいと思われます。vは通常、単一のバイト(27または28の値)を表すためです。
See also: