ABIEncoderV2

求推荐ABIEncoderV2的相关材料,谢谢

# ABIEncoderV2 最近在写一些合约,在合约的编译过程中会遇到如下的错误提示,最开始都没有想到是哪里出了问题,故写下这篇文章以作记录。 ![image20210819222747300.png](https://img.learnblockchain.cn/attachments/2021/08/aVNUyUhE611e8140ea9c8.png) ## 解决方案: 在合约头部,添加如下一行,指明ABI编码方式: ```js pragma experimental ABIEncoderV2; ``` ## 问题分析: 该种类型的报错是指:嵌套的动态数组在目前的Solidity中暂未被支持。 然而在已有的合约:如`BaseBoringBatchable.sol`中的`batch`函数: ```js function batch(bytes[] calldata calls, bool revertOnFail) external payable returns (bool[] memory successes, bytes[] memory results) { uint n = calls.length; successes = new bool[](n); results = new bytes[](n); for (uint i = 0; i < n; i++) { bytes memory data = calls[i]; (bool success, bytes memory result) = address(this).delegatecall(data); require(success || !revertOnFail, _getRevertMsg(result)); successes[i] = success; results[i] = result; } } function _getRevertMsg(bytes memory data) internal returns(string) { uint len; uint pointer; assembly{ len := mload(data) pointer := add(data, 0x04) } if (len < 68) { return("Transaction reverted silently"); } return abi.decode(pointer, (string)); } ``` 在上述的`batch`函数中,其函数参数中接受一个类型为`bytes[]`的参数,如果简单的把这个函数放入到合约中进行编译,就会产生: > UnimplementedFeatureError: Nested dynamic arrays not implemented here 那么,为什么bytes[]是一个嵌套的动态数组呢? 原因是bytes类型本身就是一个动态数组,string也是,uint[]也是。 ![image20210819231253988.png](https://img.learnblockchain.cn/attachments/2021/08/h34EtjGR611e81582e614.png) 所以bytes[]就是一个嵌套的动态数组。 ## bytes[]在内存中的排布 针对上述的batch函数,其bytes[]需要存储每一个需要调用的方法的calldata。例如我们要调用如下方法: ```js function commitEth( address payable _beneficiary, bool readAndAgreedToMarketParticipationAgreement ) ``` 则我们的`calldata`应该为: ```js abi.encodePacked(address(this).commitEth.selector, uint256(uint160(address(this))), uint256(0x01)); => 0x73973fcb 000000000000000000000000c351628eb244ec633d5f21fbd6621e1a683b1181 0000000000000000000000000000000000000000000000000000000000000001 ``` 则在内存中使用bytes[]为: ```js function hack() public payable{ bytes[] memory data = new bytes[](3); bytes momory call_data = abi.encodePacked(address(this).commitEth.selector, uint256(uint160(address(this))), uint256(0x01)); data[0] = call_data; data[1] = call_data; data[2] = call_data; } ``` 当把三个call_data放置在内存中的bytes[]时,其内存排布应该如下: ```js 0x00 0000000000000000000000000000000000000000000000000000000000000003 // len 0x20 0000000000000000000000000000000000000000000000000000000000000080 // loc1 0x40 00000000000000000000000000000000000000000000000000000000000000e4 // loc2 0x60 0000000000000000000000000000000000000000000000000000000000000148 // loc3 0x80 0000000000000000000000000000000000000000000000000000000000000044 // len(part1) 0xa0 73973fcb // part1 0xa4 000000000000000000000000c351628eb244ec633d5f21fbd6621e1a683b1181 // part1 0xc4 0000000000000000000000000000000000000000000000000000000000000001 // part1 0xe4 0000000000000000000000000000000000000000000000000000000000000044 // len(part2) 0x104 73973fcb // part2 0x108 000000000000000000000000c351628eb244ec633d5f21fbd6621e1a683b1181 // part2 0x128 0000000000000000000000000000000000000000000000000000000000000001 // part2 0x148 0000000000000000000000000000000000000000000000000000000000000044 // len(part3) 0x14c 73973fcb // part3 0x16c 000000000000000000000000c351628eb244ec633d5f21fbd6621e1a683b1181 // part3 0x18c 0000000000000000000000000000000000000000000000000000000000000001 // part3 ``` ![image20210820000113755.png](https://img.learnblockchain.cn/attachments/2021/08/5l5xNMPE611e816775891.png) 可以发现并不是完全一致,它利用了data[0]=data[1]=data[2]的条件,故重复指向同一个内存位点。 ## bytes[]在插槽中的排布 同样针对上述`batch`函数,让其在EVM的插槽中进行存储,则合约应该如下设计: ```js contract Exploit { bytes[] public data; function hack() public payable{ bytes momory call_data = abi.encodePacked(bytes4(0x73973fcb), uint256(uint160(address(this))), uint256(0x01)); data.push(call_data); data.push(call_data); data.push(call_data); } } ``` 同样添加三个call_data到data中,则data在EVM存储时的排布应该为: ```js base_key = 0x0000000000000000000000000000000000000000000000000000000000000000 base_value = 3 part1_key = keccak256(base_key) + 1 part1_value = 0x89 part2_key = keccak256(base_key) + 2 part2_value = 0x89 part3_key = keccak256(base_key) + 3 part3_value = 0x89 sub_part1_key1 = keccak256(part1_key) sub_part1_value1 = 73973fcb000000000000000000000000c351628eb244ec633d5f21fbd6621e1a sub_part1_key2 = sub_part1_key1 + 1 sub_part1_value2 = 683b118100000000000000000000000000000000000000000000000000000000 sub_part1_key3 = sub_part1_key1 + 2 sub_part1_value3 = 0000000100000000000000000000000000000000000000000000000000000000 ``` ![image20210819235635522.png](https://img.learnblockchain.cn/attachments/2021/08/IvPNQJwj611e817567881.png) 求推荐ABIEncoderV2的相关材料,谢谢

ABIEncoderV2

最近在写一些合约,在合约的编译过程中会遇到如下的错误提示,最开始都没有想到是哪里出了问题,故写下这篇文章以作记录。

解决方案:

在合约头部,添加如下一行,指明ABI编码方式:

pragma experimental ABIEncoderV2;

问题分析:

该种类型的报错是指:嵌套的动态数组在目前的Solidity中暂未被支持。

然而在已有的合约:如BaseBoringBatchable.sol中的batch函数:

function batch(bytes[] calldata calls, bool revertOnFail) external payable returns (bool[] memory successes, bytes[] memory results) {
    uint n = calls.length;
    successes = new bool[](n);
    results = new bytes[](n);
    for (uint i = 0; i &lt; n; i++) {
        bytes memory data = calls[i];
        (bool success, bytes memory result) = address(this).delegatecall(data);
        require(success || !revertOnFail, _getRevertMsg(result));
        successes[i] = success;
        results[i] = result;
    }
}
function _getRevertMsg(bytes memory data) internal returns(string) {
    uint len;
    uint pointer;
    assembly{
        len := mload(data)
        pointer := add(data, 0x04)
    }
    if (len &lt; 68) {
        return("Transaction reverted silently");
    }
    return abi.decode(pointer, (string));

}

在上述的batch函数中,其函数参数中接受一个类型为bytes[]的参数,如果简单的把这个函数放入到合约中进行编译,就会产生:

UnimplementedFeatureError: Nested dynamic arrays not implemented here

那么,为什么bytes[]是一个嵌套的动态数组呢?

原因是bytes类型本身就是一个动态数组,string也是,uint[]也是。

所以bytes[]就是一个嵌套的动态数组。

bytes[]在内存中的排布

针对上述的batch函数,其bytes[]需要存储每一个需要调用的方法的calldata。例如我们要调用如下方法:

function commitEth(
    address payable _beneficiary,
    bool readAndAgreedToMarketParticipationAgreement
)

则我们的calldata应该为:

abi.encodePacked(address(this).commitEth.selector, 
                 uint256(uint160(address(this))), 
                 uint256(0x01));
=>
0x73973fcb
000000000000000000000000c351628eb244ec633d5f21fbd6621e1a683b1181
0000000000000000000000000000000000000000000000000000000000000001

则在内存中使用bytes[]为:

function hack() public payable{
    bytes[] memory data = new bytes[](3);
    bytes momory call_data = abi.encodePacked(address(this).commitEth.selector, 
                                             uint256(uint160(address(this))), 
                                             uint256(0x01));
    data[0] = call_data;
    data[1] = call_data;
    data[2] = call_data;  
}

当把三个call_data放置在内存中的bytes[]时,其内存排布应该如下:

0x00 0000000000000000000000000000000000000000000000000000000000000003 // len
0x20 0000000000000000000000000000000000000000000000000000000000000080 // loc1
0x40 00000000000000000000000000000000000000000000000000000000000000e4 // loc2
0x60 0000000000000000000000000000000000000000000000000000000000000148 // loc3

0x80 0000000000000000000000000000000000000000000000000000000000000044 // len(part1)
0xa0 73973fcb                                                         // part1
0xa4 000000000000000000000000c351628eb244ec633d5f21fbd6621e1a683b1181 // part1
0xc4 0000000000000000000000000000000000000000000000000000000000000001 // part1

0xe4 0000000000000000000000000000000000000000000000000000000000000044 // len(part2)
0x104 73973fcb                                                        // part2
0x108 000000000000000000000000c351628eb244ec633d5f21fbd6621e1a683b1181 // part2
0x128 0000000000000000000000000000000000000000000000000000000000000001 // part2

0x148 0000000000000000000000000000000000000000000000000000000000000044 // len(part3)
0x14c 73973fcb                                                         // part3
0x16c 000000000000000000000000c351628eb244ec633d5f21fbd6621e1a683b1181 // part3
0x18c 0000000000000000000000000000000000000000000000000000000000000001 // part3

可以发现并不是完全一致,它利用了data[0]=data[1]=data[2]的条件,故重复指向同一个内存位点。

bytes[]在插槽中的排布

同样针对上述batch函数,让其在EVM的插槽中进行存储,则合约应该如下设计:

contract Exploit {
    bytes[] public data;
    function hack() public payable{
        bytes momory call_data = abi.encodePacked(bytes4(0x73973fcb), 
                                             uint256(uint160(address(this))), 
                                             uint256(0x01));
        data.push(call_data);
        data.push(call_data);
        data.push(call_data);
    }
}

同样添加三个call_data到data中,则data在EVM存储时的排布应该为:

base_key = 0x0000000000000000000000000000000000000000000000000000000000000000
base_value = 3
part1_key = keccak256(base_key) + 1
part1_value = 0x89
part2_key = keccak256(base_key) + 2
part2_value = 0x89
part3_key = keccak256(base_key) + 3
part3_value = 0x89
sub_part1_key1 = keccak256(part1_key)
sub_part1_value1 = 73973fcb000000000000000000000000c351628eb244ec633d5f21fbd6621e1a
sub_part1_key2 = sub_part1_key1 + 1
sub_part1_value2 = 683b118100000000000000000000000000000000000000000000000000000000
sub_part1_key3 = sub_part1_key1 + 2
sub_part1_value3 = 0000000100000000000000000000000000000000000000000000000000000000

求推荐ABIEncoderV2的相关材料,谢谢

本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

  • 发表于 2021-08-20 00:06
  • 阅读 ( 440 )
  • 学分 ( 11 )
  • 分类:智能合约

评论