所有交易都存在“数据(Data)”字段,但是实际上那包含了什么,为什么?
你可能已经注意到,在与智能合约(例如,发送代币)交互时,您的交易会自动包含输入数据。在MyCrypto上,这只是简单地标记为“Data”——但是它有什么作用?在本文中,您将看到交易输入数据的技术解释,包括它是什么以及它是如何工作的。
什么是输入数据?
让我们先看看这个代币交易开始。一个人将零个ETH发送到
0xd26114cd6ee289accf82350c8d8487fedb8a0c07 (OmiseGO合约地址),Etherscan显示这是向该地址发送0.19个OMG代币的交易。那么EVM (Ethereum Virtual Machine)是如何知道这个人想要将这些代币转移到另一个地址的呢?
如果再次查看Etherscan,你将看到交易具有输入数据,这是可以附加到交易的额外数据。它可以是普通文本或数字(格式化为十六进制),但是在这种情况下,用户使用输入数据告诉合约运行某个函数。合约是基于一组功能构建的。例如,ERC-20代币使用诸如transfer和balanceOf等函数将代币从A传输到B,并分别获得地址的余额。对于上面的交易,你可以在Etherscan上看到调用了函数transfer(address _to, uint256 _value)。
上面交易的原始输入数据是:
0xa9059cbb0000000000000000000000004bbeeb066ed09b7aed07bf39eee0460dfa261500000000000000000000000000000000000000000000000000002a34892d36d6c74。
你可以将这个长十六进制值分成几个较小的组。开头的0x表示这将是一个十六进制值,下面的8个字节(a9059cbb)是函数标识符。在此之后,所有函数参数都以32字节(在本例中是64个十六进制字符)为一组。
第一组是000000000000000000004bbeeb066ed09b7aed07bf39eee0460dfa261520,
第二组是0000000000000000000000000000000000000000002a34892d36d6c74。
如果你查看Etherscan(默认)中的格式化输入数据,将看到相同的内容:
Function: transfer(address _to, uint256 _value)
MethodID: 0xa9059cbb
[0]: 0000000000000000000000004bbeeb066ed09b7aed07bf39eee0460dfa261520
[1]: 00000000000000000000000000000000000000000000000002a34892d36d6c74
什么是十六进制?
十六进制是一种数字系统,就像十进制和二进制一样,它使用数字0到9和字母A到F(不区分大小写)来表示0到15之间的十进制值。在下图中,你可以看到与十六进制值对应的数字。十六进制用于以更易读的方式写入大数字。
单个十六进制的最大值是15,即4位长。当你把多个十六进制放在一起时,可以为每个十六进制采用二进制表示形式,然后将这些十六进制表示为十进制数。例如,0x5c可以写成0101(= 5)和1100 (= C),结合起来得到01011100,这是92的二进制数。所以0x5c的十六进制值等于92。
大多数编程语言使用0x前缀作为任意标识符,将十六进制标识符与其他标识符分开(如常规小数、二进制数等)。它没有任何意义,但是为了清晰起见,我们将在本文中为每个十六进制包含它。
有了这些,让我们继续往下。如果您还没有完全理解十六进制,不要担心,不需要理解输入数据是什么。
输入数据和智能合约
输入数据的主要用例是与智能合约进行交互。大多数合约使用合同ABI规范,允许像Etherscan这样的网站自动解码输入数据并显示调用的确切操作。在前面的示例中,它是使用ERC-20 standard代币的交易。这意味着我们知道所有可能的函数及其签名。例如,ERC-20合约的传递函数的完整签名总是transfer(address,uint256),这意味着它以一个地址作为第一个参数,将无符号的256位数作为第二个参数(来自0到2²⁵⁶ - 1)。
Solidity有一堆可以使用的可能的参数类型。如果你有兴趣了解有关Solidity和智能合约的更多信息,可以在Solidity文档中找到有关它的更多信息。
函数签名
据我们了解,传递函数的函数签名是transfer(address,uint256)。每个ERC-20合约的签名都是一样的。如果合约对传递函数使用不同的参数,比如地址和uint128(未签名的128位长整数),则该合约不符合ERC-20-compliant.。
为了获得函数的十六进制签名,我们必须从该函数的SHA-3(或Keccak-256)哈希中获取前4个字节(8个十六进制字符)。要获得Keccak-256哈希值,可以使用web3 JavaScript库或类似的在线工具。如果我们在这个工具中填写transfer(address,uint256),它将显示0xa9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b作为输出。如果我们取前8个字符(忽略0x),就会得到a9059cbb,它与上面交易中的MethodID匹配。
另一个例子:ERC-20 approve函数的函数签名是approve(address,uint256)。如果对这个函数的SHA-3哈希,得到
0x095ea7b334ae44009aa867bfb386f5c3b44b443ac6f0ee573fa91c4608fbadfba,前8个字符是095ea7b3。因此,approve交易的输入数据将从0x095ea7b3开始。例如,在这里查看DAI代币合约的这个交易。
地址及金额
每个参数(除了列表/数组和字符串——我们后面会讲到)的长度为32字节,或者64个十六进制字符,但是Ethereum地址只有40个字符长(没有0x)。为此,地址中填充了零。在在十六进制字符串中,0x0000123与0x123相同,因此上面交易的
0x000000000000000000004bbeeb066ed09b7aed0460dfa261520 (address)与
0x4bbeeb066ed09b7aed07bf39eee0460dfa261520相同,而
0x00000000000000000000000000000000000000002a34892d36d6c74相同。那么为什么要用所有的0呢?
正如我们前面学到的,Solidity合约中的最大数量是2²⁵⁶- 1,恰好是32字节。使用Solidity长度还可以使EVM和其他应用程序更容易解码数据,因为你可以假设每个组总是具有相同的长度。
那么数组和字符串呢?
如前一节所述,数组和字符串的输入数据的工作方式略有不同。数组基本上是项的列表。例如,在大多数编程语言中,1、2和3的列表将被写成[1、2、3]。要在交易中发送此数据,列表中的每个项都作为32字节组发送,并在输入数据的末尾列出,指向数组长度的指针用作参数。
例如,我们有一个名为myFunction的函数,它接受一个地址和数组(myFunction(address,uint256[]))。这个函数的函数签名是0x4b294170。对于这个地址,我们将使用与上面相同的地址。因为我们的数组有3个元素,数组的十六进制长度是0x3。我们知道每个参数正好占用32字节的空间,数组放在所有其他参数之后,所以数组从32 + 32 = 64字节开始。
本次交易的输入数据为:
0x4b2941700000000000000000000000004bbeeb066ed09b7aed07bf39eee0460dfa26152000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003
因为字符串可以具有任意长度(大于32字节),所以它们被分成32字节的组,并作为输入数据中的数组来处理。
像Etherscan这样的站点如何解码输入数据?
哈希是单向函数,因此如果你有函数签名哈希,则不可能从该哈希获得函数签名(除非进行蛮力处理)。合约所有者可以将合约ABI上传JSON,就像这里的这个,它可以用来获取函数签名散列。
即使合同所有者没有上传合同ABI,也可以解码许多合同的输入数据。正如您在本文前面学到的,ERC-20契约函数的签名总是相同的,从而允许Etherscan对这些合约使用预定义的合约ABI。例如,ERC-20合约的transfer函数的合同ABI是这样的:
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
}
如果输入数据签名与预先定义的函数之一匹配,Etherscan可以解码输入数据。
输入数据有限制吗?
没有。Ethereum对输入数据的长度没有固定的限制,但输入数据确实消耗gas费用。单个块中的最大gas值是波动的,但在撰写本文时大约为800万。每个0字节(0x00)消耗4个gas,每个非0字节消耗68个气体。标准交易的gas费用是21000单位gas,所以不考虑任何合约的执行,现在输入数据的最大长度大约是2兆字节的0,或者仅是非0的0.12兆字节。由于输入数据不太可能只有0或非0,所以实际的限制介于两者之间,并取决于输入数据。
作者:区势传媒
链接:https://www.jianshu.com/p/82561b3bb8be
共同学习,写下你的评论
评论加载中...
作者其他优质文章