美文网首页Solidity智能合约专题
Solidity的ABI编码函数详解:encode、encode

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

作者: 梁帆 | 来源:发表于2022-12-05 15:03 被阅读0次

    编码函数:

    • 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结果,就是我们加密前的参数。

    相关文章

      网友评论

        本文标题:Solidity的ABI编码函数详解:encode、encode

        本文链接:https://www.haomeiwen.com/subject/ugtoxdtx.html