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: