Solidity的ABI编码函数详解:encode、encodePacked、encodeWithSignature、encodeWithSelector

编码函数:

  • abi.encode
  • abi.encodePacked
  • abi.encodeWithSignature
  • abi.encodeWithSelector

解码函数:

  • abi.decode,用于解码被abi.encode的数据

一、测试合约

为了测试这几个函数的功能,我们写了这样的测试合约:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

// Uncomment this line to use console.log
import "hardhat/console.sol";

contract Test {
    uint256 x = 10;
    address addr = 0x13a6D1fe418de7e5B03Fb4a15352DfeA3249eAA4;
    string str = "This is China";
    uint256[2] arr = [1, 2];

    function core(uint256 _x, address _addr, string calldata _str, uint256[2] calldata _arr) public {

    }

    function testEncode() public view returns (bytes memory result) {
        result = abi.encode(x, addr, str, arr);
    }

    function testEncodePacked() public view returns (bytes memory result) {
        result = abi.encodePacked(x, addr, str, arr);
    }

    function testEncodeWithSignature() public view returns (bytes memory result) {
        result = abi.encodeWithSignature("core(uint256,address,string,uint256[2])", x, addr, str, arr);
    }

    function testEncodeWithSelector() public view returns (bytes memory result) {
        result = abi.encodeWithSelector(bytes4(keccak256("core(uint256,address,string,uint256[2])")), x, addr, str, arr);
    }

    function testDecode() public view returns (uint256 _x, address _addr, string memory _str, uint256[2] memory _arr) {
        bytes memory result = testEncode();
        return abi.decode(result, (uint256, address, string, uint256[2]));
    }
}

在发布了合约之后,我们又用hardhat写了task:

task("test-transaction", "This task is broken")
    .setAction(async () => {
        const contractAddress = "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512";
        const test = await ethers.getContractAt('Test', contractAddress);

        const encodeRes = await test.testEncode();
        const encodePackedRes = await test.testEncodePacked();
        const encodeWithSignatureRes = await test.testEncodeWithSignature();
        const encodeWithSelectorRes = await test.testEncodeWithSelector();
        const decodeRes = await test.testDecode();

        console.log("encodeRes: ", encodeRes);
        console.log("encodePackedRes: ", encodePackedRes);
        console.log("encodeWithSignatureRes: ", encodeWithSignatureRes);
        console.log("encodeWithSelectorRes: ", encodeWithSelectorRes);
        console.log("decodeRes: ", decodeRes);
    });

我们下面将通过输出的结果来阐述这几个函数的作用。

二、函数详解

1.encode

将给定参数利用ABI 规则编码。ABI 被设计出来跟智能合约交互,他将每个参数转填充为 32 字节的数据,并拼接在一起。如果你要和合约交互,你要用的就是 abi.encode

编码的结果为:

0x000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000013a6d1fe418de7e5b03fb4a15352dfea3249eaa400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000d54686973206973204368696e6100000000000000000000000000000000000000

由于 abi.encode 将每个数据都填充为 32 字节,中间有很多 0。
将其分割开,则有:
0x
000000000000000000000000000000000000000000000000000000000000000a
00000000000000000000000013a6d1fe418de7e5b03fb4a15352dfea3249eaa4
00000000000000000000000000000000000000000000000000000000000000a0
0000000000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000000000000000000000000000000000002
000000000000000000000000000000000000000000000000000000000000000d
54686973206973204368696e6100000000000000000000000000000000000000

第1个32字节存储了x,它就是uint256 x = 10
第2个32字节存储了addr,即address addr = 0x13a6D1fe418de7e5B03Fb4a15352DfeA3249eAA4
第3个32字节存储了动态类型string的存储位置,0xa0即160个字节,即说明string类型存储在了160字节的位置,以0位开始计数,则第6个32字节开始存储string类型的信息(感谢用户925bb9eb72ce的帮助
);
第4个32字节存储了arr的第一个值arr[0];
第5个32字节存储了arr的第一个值arr[1];
第6个32字节存储了str的长度,值为0xd,即10进制的13,是我们这里This is China的长度;
第7个32字节即是This is China的内容本身。

2.encodePacked

将给定参数根据其所需最低空间编码。它类似 abi.encode,但是会把其中填充的很多 0 省略。比如,只用 1 字节来编码 uint 类型。当你想省空间,并且不与合约交互的时候,可以使用 abi.encodePacked,例如算一些数据的 hash 时。

编码的结果为:

0x000000000000000000000000000000000000000000000000000000000000000a13a6d1fe418de7e5b03fb4a15352dfea3249eaa454686973206973204368696e6100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002

将其分割开,则有:
0x
000000000000000000000000000000000000000000000000000000000000000a
13a6d1fe418de7e5b03fb4a15352dfea3249eaa4
54686973206973204368696e61
0000000000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000000000000000000000000000000000002

可以看到这里没有要与EVM底层执行的格式适配,就仅仅是实际存储内容的拼接加密,所以没有多余的要凑齐256位字长的0值。

3.encodeWithSignature

与 abi.encode 功能类似,只不过第一个参数为函数签名,比如"foo(uint256,address)"。当调用其他合约的时候可以使用。

编码的结果为:

0x58d382a9000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000013a6d1fe418de7e5b03fb4a15352dfea3249eaa400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000d54686973206973204368696e6100000000000000000000000000000000000000

等同于在 abi.encode 编码结果前加上了 4 字节的函数选择器。
将其分割开,则有:
0x
58d382a9
000000000000000000000000000000000000000000000000000000000000000a
00000000000000000000000013a6d1fe418de7e5b03fb4a15352dfea3249eaa4
00000000000000000000000000000000000000000000000000000000000000a0
0000000000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000000000000000000000000000000000002
000000000000000000000000000000000000000000000000000000000000000d
54686973206973204368696e6100000000000000000000000000000000000000

这里的第一行的58d382a9是函数签名对core(uint256,address,string,uint256[2])进行keccak256运算后取前4字节的结果,这样的结果作为一种函数选择器,作为函数的唯一标识。剩下的字节就跟abi.encode结果一样了,所以说,abi.encode是用于合约交互的,因为合约交互就涉及到函数的调用,函数的调用就需要abi.encode这种对数据的编码格式。

4.encodeWithSelector

与 abi.encodeWithSignature 功能类似,只不过第一个参数为函数选择器,为函数签名 Keccak 哈希的前 4 个字节。

编码的结果为:

0x58d382a9000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000013a6d1fe418de7e5b03fb4a15352dfea3249eaa400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000d54686973206973204368696e6100000000000000000000000000000000000000

将其分割开,则有:
0x
58d382a9
000000000000000000000000000000000000000000000000000000000000000a
00000000000000000000000013a6d1fe418de7e5b03fb4a15352dfea3249eaa4
00000000000000000000000000000000000000000000000000000000000000a0
0000000000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000000000000000000000000000000000002
000000000000000000000000000000000000000000000000000000000000000d
54686973206973204368696e6100000000000000000000000000000000000000

这个从结果上是跟encodeWithSignature一样的,就是用法上存在差别:

result = abi.encodeWithSignature("core(uint256,address,string,uint256[2])", x, addr, str, arr);
result = abi.encodeWithSelector(bytes4(keccak256("core(uint256,address,string,uint256[2])")), x, addr, str, arr);

可以认为encodeWithSignature时一种对encodeWithSelector的简写,因为他会自动对函数签名先进行keccak256运算再取前4字节。

5.decode

abi.decode 用于解码 abi.encode 生成的二进制编码,将它还原成原本的参数。
decode结果

可以看到我们的decode结果,就是我们加密前的参数。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容