交易调用数据解密 - 理解以太坊交易调用数据的指南

  • QuickNode
  • 发布于 2025-01-30 17:58
  • 阅读 28

本文详细介绍了如何理解以太坊交易中的 calldata,包括编码和解码的原理和具体实现。通过使用 Web3.py 和 Python,读者可以掌握如何解码和编码以太坊交易的 calldata,从而更深入地了解智能合约的交互机制。文章结构清晰,涵盖了相关概念的解释及实际代码示例,适合有一定以太坊基础的开发者学习。

概述

如果你之前与以太坊上的智能合约互动过,你可能注意到你的交易包括一个在数据字段中的长十六进制值。作为用户(或开发者),你可能希望更好地理解这些值。本指南将正是涵盖这一点——你将学习如何解码交易的 calldata,以便更好地理解你遇到的其他交易。在本指南结束时,你还将学习如何在程序中使用 Web3.pyPython 进行编码和解码。

你需要的准备
你将做的事情
  • 学习如何编码和解码交易 calldata
  • 创建一个以太坊端点(在 这里注册免费账户
  • 使用 Web3.py 对交易 calldata 进行编码和解码

什么是交易 Calldata?

交易 calldata 是随交易传递的数据,允许我们向其他实体发送消息或与智能合约互动。

交易 calldata 看起来是什么样的?以下是一个以太坊上的交易,这是一次 DAI 转账,包括 calldata(即 输入数据 字段)

Etherscan

来源:Etherscan

Etherscan 为我们格式化此 calldata,这样更易于理解。但是,实际的原始交易 calldata 将如下所示:

0xa9059cbb0000000000000000000000003f5047bdb647dc39c88625e17bdbffee905a9f4400000000000000000000000000000000000000000000011c9a62d04ed0c80000

你可能想知道这些十六进制值代表什么。别担心。到本指南结束时,你将确切知道如何解密以太坊(和其他 EVM 基链)的交易 calldata!

解码 Calldata

在解码上述 calldata 之前,让我们回顾一些基本规则。

  • Calldata 是可以变长的字节数组(但受区块 gas 限制)
  • Calldata 可以包含以静态或动态格式表示的数据

一些静态 calldata 的示例是 addressesuintbytes32bool 类型,它们是固定长度的。

  • addresses:长度为 20 字节或 40 个字符
  • uint256:长度为 32 字节或 64 个字符
  • bytes32:固定大小的字节数组,32 字节(即 64 个字符长)
  • bool:长度为 1 字节或 1 个字符

而动态变量,例如 bytesint[],等,是不固定长度的。考虑到这些概念,让我们尝试解码上述交易 calldata。

引用的交易 中被调用的函数是 transfer,其定义为 transfer(address dst, uint256 wad)。再次强调,原始 calldata 由以下内容组成:

0xa9059cbb0000000000000000000000003f5047bdb647dc39c88625e17bdbffee905a9f4400000000000000000000000000000000000000000000011c9a62d04ed0c80000

让我们将其拆解。

  • calldata 的第一部分 0x 让我们知道该值是十六进制
  • 接下来的四个字节是 a9059cbb,这指的是函数选择器,通过对函数名称及其输入类型的 sha3 哈希的前 4 个字节进行计算生成。用于识别所调用的函数。查看 ABI 规范
  • 下一组字节是 0000000000000000000000003f5047bdb647dc39c88625e17bdbffee905a9f44,长度为 64 个字符,表示 address dst 输入。000000000000000000000000 表示用于确保值占用相同内存量的填充。剩余字节指的是接收地址 3f5047bdb647dc39c88625e17bdbffee905a9f44
  • 下一组字节是 00000000000000000000000000000000000000000000011c9a62d04ed0c80000,它指的是 uint256 wad 输入。我们应期望它为 32 字节(或 64 个字符长度)。000000000000000000000000000000000000000000000 是填充,剩余的 11c9a62d04ed0c8 指的是 Wei 值的金额,该金额转换并除以 18 位小数后,即为 5250。而余下的 0000 是尾随零,以确保数据总是 32 字节长。

使用 Python 解码 Calldata

在我们开始之前,我们需要访问一个以太坊节点。你可以使用公有节点或部署并管理自己的基础设施;但是,如果你想要 8 倍更快的响应时间,可以将重任交给我们。注册一个免费账户 这里

登录后,点击 创建端点 并选择以太坊主网链。

QuickNode Endpoint

端点创建完成后,保留 HTTP 提供者 URL,因为在 Python 脚本中需要它。

现在,在 Python 中,你可以使用 Web3.py 库及以下代码解码相同的 calldata:

from web3 import Web3

## RPC URL
w3 = Web3(Web3.HTTPProvider("QUICKNODE_ETHEREUM_ENDPOINT"))

## DAI 合约地址
contract_address = '0x6B175474E89094C44Da98b954EedeAC495271d0F'

## DAI 合约 ABI
abi = '[{"inputs":[{"internalType":"uint256","name":"chainId_","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"src","type":"address"},{"indexed":true,"internalType":"address","name":"guy","type":"address"},{"indexed":false,"internalType":"uint256","name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":true,"inputs":[{"indexed":true,"internalType":"bytes4","name":"sig","type":"bytes4"},{"indexed":true,"internalType":"address","name":"usr","type":"address"},{"indexed":true,"internalType":"bytes32","name":"arg1","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"arg2","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"LogNote","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"src","type":"address"},{"indexed":true,"internalType":"address","name":"dst","type":"address"},{"indexed":false,"internalType":"uint256","name":"wad","type":"uint256"}],"name":"Transfer","type":"event"},{"constant":true,"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PERMIT_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"burn","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"guy","type":"address"}],"name":"deny","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"mint","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"src","type":"address"},{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"move","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"holder","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"bool","name":"allowed","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"pull","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"push","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"guy","type":"address"}],"name":"rely","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"src","type":"address"},{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"version","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"wards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]'

## 合约对象
contract = w3.eth.contract(address=contract_address, abi=abi)

## 要解码的原始调用数据
data = "0xa9059cbb0000000000000000000000003f5047bdb647dc39c88625e17bdbffee905a9f4400000000000000000000000000000000000000000000011c9a62d04ed0c80000"

## 解码调用数据
decoded_data = contract.decode_function_input(data)

## 打印解码后的调用数据
print(decoded_data)

结果将如下所示:

(<Function transfer(address,uint256)>, {'dst': '0x3F5047BDb647Dc39C88625E17BDBffee905A9F44', 'wad': 5250000000000000000000})

给自己点个赞,因为你已经学会了理解以太坊交易 calldata 的难点!下一部分将介绍如何编码 calldata。

使用 Python 编码 Calldata

参照相同的交易合约,我们可以使用以下 Python 代码编码 transfer 函数及其有效载荷:

from web3 import Web3

## RPC url
w3 = Web3(Web3.HTTPProvider("QUICKNODE_ETHEREUM_ENDPOINT"))

## 要调用的函数的 ABI
abi = '[{"inputs":[{"internalType":"uint256","name":"chainId_","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"src","type":"address"},{"indexed":true,"internalType":"address","name":"guy","type":"address"},{"indexed":false,"internalType":"uint256","name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":true,"inputs":[{"indexed":true,"internalType":"bytes4","name":"sig","type":"bytes4"},{"indexed":true,"internalType":"address","name":"usr","type":"address"},{"indexed":true,"internalType":"bytes32","name":"arg1","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"arg2","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"LogNote","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"src","type":"address"},{"indexed":true,"internalType":"address","name":"dst","type":"address"},{"indexed":false,"internalType":"uint256","name":"wad","type":"uint256"}],"name":"Transfer","type":"event"},{"constant":true,"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PERMIT_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"burn","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"guy","type":"address"}],"name":"deny","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"mint","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"src","type":"address"},{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"move","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"holder","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"bool","name":"allowed","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"pull","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"push","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"guy","type":"address"}],"name":"rely","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"src","type":"address"},{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"version","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"wards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]'

## 合约地址
contract_address = '0x6B175474E89094C44Da98b954EedeAC495271d0F'

## 合约对象
contract = w3.eth.contract(address=contract_address, abi=abi)

## 编码函数调用
function_name = 'transfer'
args = ["0x3f5047bdb647dc39c88625e17bdbffee905a9f44", 5250000000000000000000]
encoded_data = contract.encodeABI(fn_name=function_name, args=args)

## 打印编码后的数据
print(encoded_data)

结果将看起来与 引用的交易 中包含的原始交易 calldata 一样。

结论

就是这样!你现在知道如何理解以太坊的交易 calldata,如何使用 Python 轻松编码和解码交易。检查一下其他 以太坊开发智能合约开发 指南。

我们很想知道你在构建什么!在 DiscordTwitter 上与我们分享你的应用。

我们 ❤️ 反馈!

如果你对本指南有任何反馈,请 告诉我们!

  • 原文链接: quicknode.com/guides/eth...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
QuickNode
QuickNode
江湖只有他的大名,没有他的介绍。