solidity之abi编码函数

711 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第7天,点击查看活动详情

前言

ABI 全称是 Application Binary Interface,翻译过来就是:应用程序二进制接口,简单来说就是 以太坊的调用合约时的接口说明,合约编译的时候都会生成对应的abi和字节码。

函数编码函数

  1. abi.encode(…) returns (bytes) 计算参数的编码。
   function encodeString() public pure returns (bytes memory) {
        bytes memory someString = abi.encode("some string");
        return someString;
    }

image.png 调用合约方法可以得到 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b736f6d6520737472696e67000000000000000000000000000000000000000000

相对应的解码函数,参数传入编码后的结果,和转码成的类型即可

   function decodeString() public pure returns (string memory) {
        string memory someString = abi.decode(encodeString(), (string));
        return someString;
    }

image.png

  1. abi.encodePacked(…) returns (bytes):计算参数的紧密打包编码
  function encodeStringPacked() public pure returns (bytes memory) {
        bytes memory someString = abi.encodePacked("some string");
        return someString;
    }

image.png 这个编码方式还有另外一种实现方式

 function encodeStringBytes() public pure returns (bytes memory) {
        bytes memory someString = bytes("some string");
        return someString;
    }

image.png

image.png

对比这2种方式发现,第二种还能更省gas,因为第一个是复制内存,第二个只是转换指针类型。 转换指针类型这种方式显然更省gas。

  1. abi. encodeWithSelector(bytes4 selector, …) returns (bytes): 计算函数选择器和参数的 ABI 编码
    function getSelectorOne() public pure returns (bytes4 selector) {
        selector = bytes4(keccak256(bytes("transfer(address,uint256)")));
    }
    
    function getDataToCallTransfer(address someAddress, uint256 amount)
        public
        pure
        returns (bytes memory)
    {
        return abi.encodeWithSelector(getSelectorOne(), someAddress, amount);
    }

函数选择器,官方文档定义如下: 一个函数调用数据的前 4 字节,指定了要调用的函数。这就是某个函数签名的 Keccak(SHA-3)哈希的前 4 字节(高位在左的大端序)

image.png 即可得到这个函数选择器。

接着通过encodeWithSelector 即可得到函数选择器和参数的 ABI 编码

image.png

0xa9059cbb0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000000000000000000000001

可以看到前四个字节就是函数选择器,后面的表示参数,即地址和金额 2个参数的编码,每个参数用三十二个字节表示,不足的补0。

  1. abi.encodeWithSignature(string signature, …) returns (bytes): 等价于* abi.encodeWithSelector(bytes4(keccak256(signature), …)
 function getSelectorTwo() public view returns (bytes4 selector) {
        bytes memory functionCallData = abi.encodeWithSignature(
            "transfer(address,uint256)",
            address(this),
            123
        );
        selector = bytes4(
            bytes.concat(
                functionCallData[0],
                functionCallData[1],
                functionCallData[2],
                functionCallData[3]
            )
        );
    }
    
    function getCallData() public view returns (bytes memory) {
        return abi.encodeWithSignature("transfer(address,uint256)", address(this), 123);
    }

image.png 这个方法本质上也是得到函数的选择器。

通过函数abi编码调用合约

 function callTransferFunctionDirectlyThree(address someAddress, uint256 amount)
        public
        returns (bytes4, bool)
    {
        (bool success, bytes memory returnData) = s_selectorsAndSignaturesAddress.call(
            abi.encodeWithSignature("transfer(address,uint256)", someAddress, amount)
        );
        return (bytes4(returnData), success);
    }

学会了函数选择器,我们就可以直接通过函数选择器加上参数编码后的结果直接调用,写了个方法测试如下

image.png

可以看到通过call方法传入编码结果, 可以直接调用了转账的方法。

总结

abi编码函数主要就是以上的使用方式,重点是函数选择器,掌握这个对在以后合约开发是比较重要的。