ABIEncoderV2

  • bixia1994
  • 更新于 2021-08-20 00:06
  • 阅读 307

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

ABIEncoderV2

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

解决方案:

在合约头部,添加如下一行,指明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 < 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

所以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

image20210820000113755.png

可以发现并不是完全一致,它利用了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

image20210819235635522.png

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

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

0 条评论

请先 登录 后评论
bixia1994
bixia1994
目前在运营一个微信公众号:bug合约写手,欢迎关注. 对我感兴趣可以加我微信:woodward1993