Solidty ABIのエンコード関数の挙動を見ていきます。
Foundry のテストを使用します。
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Test, console2} from "forge-std/Test.sol";
import {Counter} from "../src/Counter.sol";
contract Test_abiEncode is Test {
abi.encodePacked
の動作
まずは一番簡単なabi.encodePacked
から。こちらは単純にバイト列を連結していきます。
bytes memory str1 = "ABC";
bytes memory str2 = "DEF";
bytes memory str3 = "GHI";
bytes memory str4 = "ABCDEFGHI"
// encodePacked Test
assertEq(abi.encodePacked(str1, str2, str3),str4);
padding
次にabi.encode
に入る前にパディングの挙動を見ていきます。なぜパディングの挙動をみるかというとabi.encode
は32バイトにパディングするからです。
-
bytes, bytes4
は右パディング -
uint32, address
は左パディング - (注)EVMはBigEndianです。たとえばuint32の0x22は 0x00000022となります。 (x86ではlittile endian なので uint32は 0x220000000 となる。)
// padding test
//
bytes memory pad28= hex"00000000000000000000000000000000000000000000000000000000";
bytes memory pad29= hex"0000000000000000000000000000000000000000000000000000000000";
bytes memory pad12= hex"000000000000000000000000";
assertEq(bytes32(str1), bytes32(abi.encodePacked(str1, pad29))); // right padding
bytes4 str5 = "abcd";
bytes4 str6 = "efgh";
assertEq(bytes32(abi.encodePacked(str5,pad28)), bytes32(str5)); // right padding
uint32 num1 = 0xC0FFEE;
assertEq(bytes32(uint256(num1)), bytes32(abi.encodePacked(pad28, num1))); // left padding
address addr1 = address(0xC0FFEE);
assertEq(bytes32(abi.encodePacked(pad12, addr1)), bytes32(abi.encode(addr1))); // left padding
abi.encode
の引数がすべて固定サイズの場合。
- 各変数を32バイトでパディングしてから連結
// static encoding
// byte4
assertEq(abi.encode(str5, str6), abi.encodePacked(bytes32(str5), bytes32(str6)));
// byte4 + uint32
assertEq(abi.encode(num1, str6), abi.encodePacked(bytes32(uint256(num1)), bytes32(str6)));
abi.encode
の引数に可変長のデータがある場合
- 可変長データの開始位置もしくは固定長データの値を32byteにパディングする。
- 開始位置からそのデータのサイズ32bytes, 次にその可変長データが入る。可変長データは32bytesの倍数になるようにパディングされる。
// dynamic encoding (including dynamic size varriable)
assertEq(abi.encode(str1, num1, str2),
abi.encodePacked(
bytes32(uint256(0x60)), // offset of the fist varriable
bytes32(uint256(num1)), // value of static varriable
bytes32(uint256(0xa0)), // offset of the 3rd varriable
bytes32(str1.length), // start from 0x60
bytes32((str1)),
bytes32(str2.length), // start from 0xa0
bytes32((str2))
));
実際のデータ
0000000000000000000000000000000000000000000000000000000000000060 ... str1の開始位置
0000000000000000000000000000000000000000000000000000000000c0ffee .... num1の値
00000000000000000000000000000000000000000000000000000000000000a0 ....str2の開始位置
0000000000000000000000000000000000000000000000000000000000000003 .... str1のサイズ (開始位置として指定されている所はここ)
4142430000000000000000000000000000000000000000000000000000000000 .... str1の値
0000000000000000000000000000000000000000000000000000000000000003 .... str2のサイズ (開始位置として指定されている所はここ)
4445460000000000000000000000000000000000000000000000000000000000 ... str2の値
abi.encodeWithSelector
の動作
- 第2引数以降で可変長のデータを引数にとると 第2引数以降はabi.encodeでまとめたものと同じ。
- [4 bytes selector] [abi.encode param data] の形になる。通常これがcalldataとしてcall先にわたる。
// encode with selector
bytes4 mySelector = 0x11223344;
assertEq(abi.encodeWithSelector(mySelector,str1,str2),
abi.encodePacked(mySelector, abi.encode(str1,str2)));
abi.encodeWithSignature の動作
- selector 部分を関数の文字列から求めなくても、そのまま文字列を引数にとる。
bytes memory funcStr = "balanceOf(address)";
bytes4 funcId = bytes4(keccak256(funcStr)); // called selector
assertEq(abi.encodeWithSelector(funcId,addr1),
abi.encodePacked(funcId, abi.encode(addr1)));
assertEq(abi.encodeWithSelector(funcId, addr1),
abi.encodeWithSignature(string(funcStr),addr1));
- 追記
このテストコードで以下のエラーがでたのでforge test -vvvv --via-ir
で実行しています。
Error:
Compiler run failed:
Error: Compiler error (/solidity/libsolidity/codegen/LValue.cpp:51):Stack too deep. Try compiling with `--via-ir` (cli) or the equivalent `viaIR: true` (standard JSON) while enabling the optimizer. Otherwise, try removing local variables.
--> test/AbiEncode.t.sol:67:30:
|
67 | bytes32((str1)),
| ^^^^