原文标题:《Decoding Ethereum smart contract data》
原文作者:Yifei Huang,红杉资本
原文编译:ChinaDeFi
正如我们在之前的文章中所讨论的,智能合约交易类似于智能合约驱动的 web3 应用程序中的后端 API 调用。每个智能合约交易和结果应用程序状态更改的细节都记录在称为交易、调用和日志的数据元素中。交易数据元素表示用户发起的函数调用 (更准确地说是 EOA),调用数据元素表示智能合约在交易中发起的其他函数调用,而日志数据元素表示交易执行期间发生的事件。
使用这些数据元素,可以非常精细地描述由于交易而在应用程序和区块链上发生的状态更改。当对一个去中心化的 web3 应用程序的所有交易、跟踪和日志进行汇总分析时,可以提供用户群及其在产品中的活动的整体和深刻的观点。然而,这样做是有挑战性的,因为许多显著的细节都被记录为十六进制编码字符串。例如,在以太坊网络上使用 Uniswap 交换一对 Token 的交易 (该特定记录可以在 Etherscan 上查看):
如果在 Etherscan 上查看交易,就可能已经注意到,它已经解码了这个原始记录,并提供了很好的上下文来帮助我们理解交易细节。虽然这非常有帮助,但它并不是为了回答那些需要转换和汇总数据的问题,例如,所有 Uniswap 用户的总交易价值是多少,或者 Uniswap 用户 3 个月的留存率是多少。为了回答这些问题,我们需要能够收集所有记录,对其进行解码,并批量处理相关细节。我们将在接下来的文章中详细介绍如何做到这一点。
如果我们检查原始数据记录,我们可以看到交易是由 EOA 发起的 0x3c02cebb49f6e8f1fc96158099ffa064bbfee38b,发送到与 Uniswap v2 路由器关联的智能合约地址 0x7a250d5630b4cf539739df2c5dacb4c659f2488d。但是,相关请求详细信息在 input 字段中被编码为一个长十六进制字符串。
在我们讨论如何从 input 中提取人类可读的数据之前,先谈谈它的结构将会很有指导意义。前导 0x 表示该字符串是十六进制的,因此它与实际的信息内容无关。之后,每 2 个十六进制字符代表一个字节。前四个字节,在本例中是 38ed1739,是被调用函数的哈希签名。其余字节是传递给函数的参数的哈希值。这意味着输入字符串的长度可以根据所调用的特定函数和所需的参数而变化。
为了解码这个十六进制字符串,我们需要引用应用程序二进制接口或 ABI。这是一个 json 对象,包含给定智能合约的所有函数和事件接口定义 (即名称和类型)。ABI 的功能是查找将交易数据中的散列签名与人类可读的接口定义进行匹配。ABI 示例如下所示:
Uniswap v2 路由器 ABI 的部分视图
ABI 通常可以在像 Etherscan 这样的区块浏览器上找到,以及合约源代码。这是 Uniswap v2 路由器合约的 ABI 链接。
一旦我们有了 ABI,我们就可以编写来解码交易:
在示例代码中有几点需要注意:
1. 此代码设计用于批量处理大量交易。它假设数据已经存在于本地存储中 (而不是从区块链实时获取),并且非常适合像 PySpark 这样的分布式处理框架。
2.@lru_cache(maxsize=None)—我们缓存合约对象创建,以减少在大量交易中重复相同计算的开销。这假设解码针对少量(数千个)不同的智能合约。
3. 它利用开源的 web3 包方法 decode_function_input 来基于 ABI 中提供的模板提取数据。然而,此方法返回的数据通常不可序列化的 (例如字节数组),有时还会丢失人类可读的键。因此,使用实用程序方法执行提取后处理 convert_to_hex 将数据转换为可序列化的 json 对象并在缺失的地方附加人类可以理解的键是非常有帮助的(甚至可能是必要的)。这使得持久化和重用已解码的数据变得更加容易。
4. 同样的代码也可以用于解码跟踪数据元素。这是因为它们只是由智能合约发起的内部交易。
使用上面的代码可以得到这个已解码的输入数据
function called: swapExactTokensForTokens arguments: { "amountIn": 2500000000, "amountOutMin": 194024196127819599854524737, "path": [ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "0x528B3e98c63cE21C6f680b713918E0F89DfaE555" ], "to": "0x3c02cebB49F6e8f1FC96158099fFA064bBfeE38B", "deadline": 1634603299 }
这样我们就更容易理解了。
1. 该调用是对名为 swapexacttokensfortokens 的方法的调用,用户正在放入 25 亿单位的起始 Token,并期望至少返回 194,024,196,127,819,599,854,524,737 单位的目标 Token。这些数字看起来可能是天文数字,但请记住,Token 单位通常用 1/10^n 表示,其中 n 大约是 18。N 有时被称为 Token 的十进制值。
2. 该 path 数组描述了在此交易中交换的 Token。每个数组元素都是 Token 合约的地址。第一个是 USDC(一种与美元挂钩的稳定币),第二个是 Wrapped Eth(带有 ERC20 接口的以太坊),第三个是 DXO(一种深空游戏货币)。
3. 将 1 和 2 放在一起,我们可以推断用户请求交换 2,500 USDC (USDC 的十进制值为 6) 和大约 1.94 亿 DXO (DXO 的十进制值为 18)。由于这种特殊的成对交换不能直接获得,交易将通过 WETH 的中间 Token 进行调解。
该交易在执行过程中还触发了 7 个事件,可以通过 logs 在以太坊上查询 Google 的 Public Dataset 中的表获得,也可以通过 Etherscan 查看。与用户所要求的交换相对应的两个最显著的记录是:
log_index: 47 transaction_hash: 0x87a3bc85da972583e22da329aa109ea0db57c54a2eee359b2ed12597f5cb1a64 transaction_index: 37 address: 0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc data: 0x000000000000000000000000000000000000000000000000000000009502f90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000093f8f932b016b1c topics: [ '0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822', '0x0000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d', '0x000000000000000000000000242301fa62f0de9e3842a5fb4c0cdca67e3a2fab'] block_timestamp: 2021-10-19 00:00:18 block_number: 13444845 block_hash: 0xe9ea4fc0ef9a13b1e403e68e3ff94bc94e472132528fe8f07ade422b84a43afc
还有
log_index: 50 transaction_hash: 0x87a3bc85da972583e22da329aa109ea0db57c54a2eee359b2ed12597f5cb1a64 transaction_index: 37 address: 0x242301fa62f0de9e3842a5fb4c0cdca67e3a2fab data: 0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000093f8f932b016b1c000000000000000000000000000000000000000000a137bb41b9113069a51e190000000000000000000000000000000000000000000000000000000000000000 topics: [ '0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822', '0x0000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d', '0x0000000000000000000000003c02cebb49f6e8f1fc96158099ffa064bbfee38b'] block_timestamp: 2021-10-19 00:00:18 block_number: 13444845 block_hash: 0xe9ea4fc0ef9a13b1e403e68e3ff94bc94e472132528fe8f07ade422b84a43afc
同样,相关详细信息在 topics 和 data 字段中编码为十六进制字符串。与 transaction 的情况一样,input 浏览这些数据字段的结构是有益的。topics 是一个数组,其中第一个元素表示事件接口定义的哈希签名。topics 数组中的任何其他元素通常是事件中涉及的区块链地址,根据具体上下文可能存在,也可能不存在。data 表示事件参数值,其长度根据事件定义而不同。与交易的情况一样,我们需要引用合约 ABI,以便将其转换为人类可读的形式。
敏锐的读者会注意到上面日志中的合约地址 0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc 和 0x242301fa62f0de9e3842a5fb4c0cdca67e3a2fab 与用户 EOA 最初调用的 Router v2 合约 0x7a250d5630b4cf539739df2c5dacb4c659f2488d 不同。这两个地址对应 USDC-WETH 和 DXO-WETH Token 对的 Uniswap v2 对合约。这些合约负责持有各自交易对的流动性,并实际进行交换。用户最初与之交互的 Router 合约作为一个协调器,并向适当的配对合约发起内部交易 (跟踪)。因此,为了解码这些事件,我们还需要一对合约 ABI。解码日志示例如下:
与交易解码的代码类似,示例代码针对批量解码用例进行了优化,并与类似 PySpark 的东西一起使用,以处理大量日志事件。运行以上收益率:
event emitted: Swap arguments: { "sender": "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", "to": "0x242301FA62f0De9e3842A5Fb4c0CdCa67e3A2Fab", "amount0In": 2500000000, "amount1In": 0, "amount0Out": 0, "amount1Out": 666409132118600476 }
还有
event emitted: Swap arguments: { "sender": "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", "to": "0x3c02cebB49F6e8f1FC96158099fFA064bBfeE38B", "amount0In": 0, "amount1In": 666409132118600476, "amount0Out": 194900241391490294085918233, "amount1Out": 0 }
我们可以认为这两个确实 swap 是 path 在初始请求之后发生的事件——USDC > WETH > DXO。我们可以看到路由器合约 (以 488D 结尾) 是两个事件中的发送方,充当协调者。USDC-WETH 对合约 (以 c9dc 结尾) 将 25 亿单位 USDC 换成 666,409,132,118,600,476 单位 WETH,然后将产生的 WETH 转移到 DXO-WETH 对合约 (结束 2Fab)。DXO-WETH 合约将 666,409,132,118,600,476 单位的 WETH 置换为 194,900,241,391,490,294,085,918,233 单位的 DXO,并按照最初的要求将其发送回用户 (EOA 结束于 E38B)。
正如本例所示,一旦我们有了工具,解码的过程就相对简单了,但知道要解码什么以及如何解释结果数据就不是那么简单了。根据我们尝试回答的具体问题,某些功能和事件比其他功能和事件更相关。为了分析 web3 应用程序中的经济活动和用户行为,了解特定智能合约的工作方式并确定感兴趣的指标中涉及的关键功能和事件非常重要。这最好是通过实际使用该产品、在像 Etherscan 这样的区块浏览器上检查数据消耗以及阅读智能合约源代码的组合来实现。这是制定正确的解码和分析策略的关键条件。
原文链接
欢迎加入律动 BlockBeats 官方社群:
Telegram 订阅群:https://t.me/theblockbeats
Telegram 交流群:https://t.me/BlockBeats_App
Twitter 官方账号:https://twitter.com/BlockBeatsAsia