开源验证合约时出现ABI错误怎么办?提供4种ABI编码与1种解码方式

fffmCQ.jpg

开源验证合约时出现ABI错误怎么办?提供4种ABI编码与1种解码方式

很多人都遇到这样一个问题:合约编译、部署已经完成,但是到了合约开源验证的时候,往往会出现一个提示:Error! Invalid constructor arguments provided. Please verify that they are in ABI-encoded format。这里的提示就是:无法获取构造函数输入参数的ABI码,导致开源失败。

那么,具体该如何解决这个问题呢?这个所谓的ABI编码又是个什么东西?今天,就给大家提供四种ABI编码的方式,以及1种ABI解码方式,帮助大家解决这个难题。

开源验证合约时出现ABI错误怎么办?提供4种ABI编码与1种解码方式

一、什么是ABI编码

ABI英文全称是Application Binary Interface,直译就是:应用二进制接口。通俗来说,ABI是以太坊生态系统中与合约交互的标准方式,相当于定义访问合约接口协议规范,统一了合约与合约、不同平台的客户端与合约之间的交互形式。

更通俗的理解说,ABI包含两个意思:

  1. ABI是合约接口的说明。
  2. ABI定义与合约进行交互数据编码规则。

ABI作为合约接口的说明,内容包括合约的接口列表、接口名称、参数名称、参数类型、返回类型等。这些信息以JSON格式保存,可以在solidity文件编译时由合约编译器生成。

Solidity中,ABI编码有4个函数:abi.encode, abi.encodePacked, abi.encodeWithSignature, abi.encodeWithSelector。而ABI解码有1个函数:abi.decode,用于解码abi.encode的数据。

今天,我们将学习如何使用这些函数。

二、ABI编码函数

我们将用编码4个变量,他们的类型分别是uint256, address, string, uint256:

    uint x = 10;
    address addr = 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71;
    string name = "0xAA";
    uint[2] array = [5, 6]; 

abi.encode

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

    function encode() public view returns(bytes memory result) {
        result = abi.encode(x, addr, name, array);
    }

编码的结果为0x000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c7100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000,由于abi.encode将每个数据都填充为32字节,中间有很多0。

abi.encodePacked

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

    function encodePacked() public view returns(bytes memory result) {
        result = abi.encodePacked(x, addr, name, array);
    }

编码的结果为0x000000000000000000000000000000000000000000000000000000000000000a7a58c0be72be218b41c608b7fe7c5bb630736c713078414100000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006,由于abi.encodePacked对编码进行了压缩,长度比abi.encode短很多。

abi.encodeWithSignature

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

    function encodeWithSignature() public view returns(bytes memory result) {
        result = abi.encodeWithSignature("foo(uint256,address,string,uint256[2])", x, addr, name, array);
    }

编码的结果为0xe87082f1000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c7100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000,等同于在abi.encode编码结果前加上了4字节的函数选择器。

abi.encodeWithSelector

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

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

编码的结果为0xe87082f1000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c7100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000,与abi.encodeWithSignature结果一样。

三、ABI解码函数

abi.decode

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

    function decode(bytes memory data) public pure returns(uint dx, address daddr, string memory dname, uint[2] memory darray) {
        (dx, daddr, dname, darray) = abi.decode(data, (uint, address, string, uint[2]));
    }

我们将abi.encode的二进制编码输入给decode,将解码出原来的参数:

开源验证合约时出现ABI错误怎么办?提供4种ABI编码与1种解码方式

四、在remix上验证

  • 部署合约查看abi.encode方法的编码结果 开源验证合约时出现ABI错误怎么办?提供4种ABI编码与1种解码方式

  • 对比验证四种编码方法的异同点 开源验证合约时出现ABI错误怎么办?提供4种ABI编码与1种解码方式

  • 查看abi.decode方法的解码结果 开源验证合约时出现ABI错误怎么办?提供4种ABI编码与1种解码方式

五、ABI的使用场景

  1. 在合约开发中,ABI常配合call来实现对合约的底层调用。
    bytes4 selector = contract.getValue.selector;

    bytes memory data = abi.encodeWithSelector(selector, _x);
    (bool success, bytes memory returnedData) = address(contract).staticcall(data);
    require(success);

    return abi.decode(returnedData, (uint256));
  1. ethers.js中常用ABI实现合约的导入和函数调用。
    const wavePortalContract = new ethers.Contract(contractAddress, contractABI, signer);
    /*
        * Call the getAllWaves method from your Smart Contract
        */
    const waves = await wavePortalContract.getAllWaves();
  1. 对不开源合约进行反编译后,某些函数无法查到函数签名,可通过ABI进行调用。
  • 0x533ba33a() 是一个反编译后显示的函数,只有函数编码后的结果,并且无法查到函数签名 开源验证合约时出现ABI错误怎么办?提供4种ABI编码与1种解码方式 开源验证合约时出现ABI错误怎么办?提供4种ABI编码与1种解码方式
  • 这种情况无法通过构造interface接口或contract来进行调用 开源验证合约时出现ABI错误怎么办?提供4种ABI编码与1种解码方式

这种情况下,就可以通过ABI函数选择器来调用

    bytes memory data = abi.encodeWithSelector(bytes4(0x533ba33a));

    (bool success, bytes memory returnedData) = address(contract).staticcall(data);
    require(success);

    return abi.decode(returnedData, (uint256));

六、如何处理ABI编码故障

明白了ABI编码的原理之后,我们再回到开头的问题,当开源出现“无法获取构造函数输入参数的ABI码”时,应该怎么办呢?

需要补充ABI码,把构造函数输入参数填充到以上位置,构造函数输入的四个参数类型分别为:string,string,uint8,uint,将ABI码补充到开源页面中的构造函数ABI码处即可。
开源验证合约时出现ABI错误怎么办?提供4种ABI编码与1种解码方式

六、总结

在以太坊中,数据必须编码成字节码才能和智能合约交互。但是ABI编码本身也有局限性:

  • ABI编码本身的规则很复杂,这增加了用户实现的难度,不过除了个别ABI库的作者外,普通用户并不需要自己实现。
  • ABI的编码会对所有的数据编码强制32字节对齐,最终这些编码数据都需要随交易进行持久化,浪费了很多的存储空间。
  • 升级困难:ABI添加新的类型支持甚至是新的规则时,所有平台的实现都需要升级,这些新的特性在有的平台上不一定容易支持。比如:ABIEncoderV2到目前为止,各个库的支持仍然不是很完善。

最后,如果您有任何关于ABI编码的问题,都可以随时联系我沟通解决。V:btc6540/飞机:@btc6540

声明:该文观点仅代表作者本人,与炒币网无关。炒币网系信息发布平台,仅提供信息存储空间服务。对所包含内容的准确性、可靠性或者完整性不提供任何明示或暗示的保证,并不对文章观点负责。 提示:投资有风险,入市须谨慎。本资讯仅供参阅,不作为投资理财建议。

发表评论

登录后才能评论