本文深入探讨了以太坊ABI编码机制,详细解释了Solidity函数调用和自定义结构体数据如何被编码成EVM可处理的十六进制字节。文章通过具体示例,包括函数选择器和参数的编码规则,展示了静态和动态类型数据的处理过程,并总结了ABI编码的核心原理。
当你在 Solidity 中调用 store({ number: 123, owner: "bob" }) 这样的函数时,你的钱包不会将“123”或“bob”发送到区块链。
相反,它发送的是一长串十六进制字节,以太坊虚拟机必须将它们解码回这些值。
这个过程由以太坊应用二进制接口 (ABI) 定义,它是一种描述数据结构和函数调用如何编码和解码的规范。
在这篇文章中,我们将通过手动拆解一个带有自定义结构体的函数调用,深入探讨 ABI 编码。你将确切了解 Solidity 如何将你的输入转换为字节,以及如何再次解码它们。
我目前乐于接受区块链、智能合约和全栈系统方面的合作和开发项目,如果你正在构建有趣的东西,欢迎在 LinkedIn 上联系我。
在以太坊中,应用二进制接口 (ABI) 定义了在与智能合约交互时数据结构和函数是如何编码和解码的。无论是手动调用函数、分析交易 calldata,还是使用低级别的 call,理解 ABI 编码都至关重要。
让我们使用一个比简单的 uint256 函数稍复杂的例子来探索 ABI 编码:一个带有自定义结构体的合约。
pragma solidity 0.8.12;
contract Storage {
struct my_storage_struct {
uint256 number;
string owner;
}
my_storage_struct my_storage;
function store(my_storage_struct calldata new_storage) public {
my_storage = new_storage;
}
function retrieve() public view returns (my_storage_struct memory){
return my_storage;
}
}
输出 ABI:
[\
{\
"inputs": [\
{\
"components": [\
{\
"internalType": "uint256",\
"name": "number",\
"type": "uint256"\
},\
{\
"internalType": "string",\
"name": "owner",\
"type": "string"\
}\
],\
"internalType": "struct Storage.my_storage_struct",\
"name": "new_storage",\
"type": "tuple"\
}\
],\
"name": "store",\
"outputs": [],\
"stateMutability": "nonpayable",\
"type": "function"\
},\
{\
"inputs": [],\
"name": "retrieve",\
"outputs": [\
{\
"components": [\
{\
"internalType": "uint256",\
"name": "number",\
"type": "uint256"\
},\
{\
"internalType": "string",\
"name": "owner",\
"type": "string"\
}\
],\
"internalType": "struct Storage.my_storage_struct",\
"name": "",\
"type": "tuple"\
}\
],\
"stateMutability": "view",\
"type": "function"\
}\
]
调用store(...)
记住我以便更快登录
当调用 store({ number: 123456789, owner: "bob"}) 时,ABI 编码过程如下所示:
它是函数签名的 Keccak-256 哈希值的前 4 个字节。
签名:"store((uint256,string))"
哈希:0xddd456b3...
选择器:0xddd456b3
keccak256("store((uint256,string))") = 0xddd456b3
由于我们正在传递一个带有字段 (uint256 number,string) 的结构体,ABI 将值编码为填充的 32 字节字:
2.1. number 的编码:0x00000000000000000000000000000000000000000000000000000000075bcd15
2.2. 字符串“bob”的编码:
在 ABI 编码中,字符串是动态大小的。它们被编码为指向偏移量的指针,后跟字符串长度和 UTF-8 字节。
0x40 (十进制 64),因为字符串在 2 个字(32 字节 * 2)之后开始。0x0000000000000000000000000000000000000000000000000000000000000040
0x0000000000000000000000000000000000000000000000000000000000000003
0x626f62 (ASCII 表示 "bob"),填充至 32 字节0x626f620000000000000000000000000000000000000000000000000000000000
0xddd356b3
0000000000000000000000000000000000000000000000000000000000000020
00000000000000000000000000000000000000000000000000000000075bcd15
0000000000000000000000000000000000000000000000000000000000000040
0000000000000000000000000000000000000000000000000000000000000003
626f620000000000000000000000000000000000000000000000000000000000
调用retrieve()
当调用函数 retrieve() public view returns (my_storage_struct memory) 时,你将收到一个ABI 编码为元组的返回值:(uint256 number, string owner)
从智能合约的角度来看,解码会是这样:
(uint256 number, string memory owner) = abi.decode(returnData, (uint256, string));
// 你将得到
number = 123456789
owner = "bob"
当你调用 Solidity 函数时,你发送给 EVM 的所有内容都会根据 ABI 规范转换为字节。
keccak256(functionSignature)[0:4]。uint256、address、bool)直接编码为 32 字节的字。string、bytes、数组)被编码为指向其实际数据(在 calldata 中稍后出现)的偏移量指针。returnData 解码时是相反的。简而言之,ABI 编码就是将人类可读的 Solidity 调用转换为 EVM 可以处理的结构化十六进制数据。
一旦你理解了它,你就可以读取原始 calldata、解码 trace,甚至手动构造函数调用。
- 原文链接: medium.com/@andrey_obruc...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!