2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Solidity ABI encoding test (abi.encode 可変長変数、固定長変数の比較)

Last updated at Posted at 2023-11-06

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)),
   |                              ^^^^

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?