求推荐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 < 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[]也是。
所以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]的条件,故重复指向同一个内存位点。
同样针对上述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的相关材料,谢谢
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!