如何解碼以太坊智能合約數據,並批量處理相關細節?

原標題:《解碼以太坊智能合約數據

如何解碼以太坊智能合約數據,並批量處理相關細節?

正如我們在之前的文章中所討論的,智能合約交易類似於智能合約驅動的web3應用程序中的後端API調用。每個智能合約交易和結果應用程序狀態更改的細節都記錄在稱為交易、調用和日誌的數據元素中。交易數據元素表示用戶發起的函數調用(更準確地說是EOA),調用數據元素表示智能合約在交易中發起的其他函數調用,而日誌數據元素表示交易執行期間發生的事件。

使用這些數據元素,可以非常精細地描述由於交易而在應用程序和區塊鏈上發生的狀態更改。當對一個去中心化的web3應用程序的所有交易、跟蹤和日誌進行匯總分析時,可以提供用戶群及其在產品中的活動的整體和深刻的觀點。然而,這樣做是有挑戰性的,因為許多顯著的細節都被記錄為十六進制編碼字符串。例如,在以太坊網絡上使用Uniswap交換一對代幣的交易(該特定記錄可以在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,我們就可以編寫來解碼交易:

import traceback
import sys
from functools import lru_cache
from web3 import Web3
from web3.auto import w3
from web3.contract import Contract
from web3._utils.events import get_event_data
from web3._utils.abi import exclude_indexed_event_inputs, get_abi_input_names, get_indexed_event_inputs, normalize_event_input_types
from web3.exceptions import MismatchedABI, LogTopicError
from web3.types import ABIEvent
from eth_utils import event_abi_to_log_topic, to_hex
from hexbytes import HexBytes

import json
import re

def decode_tuple(t, target_field):
output = dict()
for i in range(len(t)):
if isinstance(t[i], (bytes, bytearray)):
output[target_field[i]['name']] = to_hex(t[i])
elif isinstance(t[i], (tuple)):
output[target_field[i]['name']] = decode_tuple(t[i], target_field[i]['components'])
else:
output[target_field[i]['name']] = t[i]
return output

def decode_list_tuple(l, target_field):
output = l
for i in range(len(l)):
output[i] = decode_tuple(l[i], target_field)
return output

def decode_list(l):
output = l
for i in range(len(l)):
if isinstance(l[i], (bytes, bytearray)):
output[i] = to_hex(l[i])
else:
output[i] = l[i]
return output

def convert_to_hex(arg, target_schema):
"""
utility function to convert byte codes into human readable and json serializable data structures
"""
output = dict()
for k in arg:
if isinstance(arg[k], (bytes, bytearray)):
output[k] = to_hex(arg[k])
elif isinstance(arg[k], (list)) and len(arg[k]) > 0:
target = [a for a in target_schema if 'name' in a and a['name'] == k][0]
if target['type'] == 'tuple[]':
target_field = target['components']
output[k] = decode_list_tuple(arg[k], target_field)
else:
output[k] = decode_list(arg[k])
elif isinstance(arg[k], (tuple)):
target_field = [a['components'] for a in target_schema if 'name' in a and a['name'] == k][0]
output[k] = decode_tuple(arg[k], target_field)
else:
output[k] = arg[k]
return output

@lru_cache(maxsize=None)
def _get_contract(address, abi):
"""
This helps speed up execution of decoding across a large dataset by caching the contract object
It assumes that we are decoding a small set, on the order of thousands, of target smart contracts
"""
if isinstance(abi, (str)):
abi = json.loads(abi)

contract = w3.eth.contract(address=Web3.toChecksumAddress(address), abi=abi)
return (contract, abi)

def decode_tx(address, input_data, abi):
if abi is not None:
try:
(contract, abi) = _get_contract(address, abi)
func_obj, func_params = contract.decode_function_input(input_data)
target_schema = [a['inputs'] for a in abi if 'name' in a and a['name'] == func_obj.fn_name][0]
decoded_func_params = convert_to_hex(func_params, target_schema)
return (func_obj.fn_name, json.dumps(decoded_func_params), json.dumps(target_schema))
except:
e = sys.exc_info()[0]
return ('decode error', repr(e), None)
else:
return ('no matching abi', None, None)

sample_abi = '[{"inputs":[{"internalType":"address","name":"_factory","type":"address"},{"internalType":"address","name":"_WETH","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"WETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"amountADesired","type":"uint256"},{"internalType":"uint256","name":"amountBDesired","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"addLiquidity","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"},{"internalType":"uint256","name":"liquidity","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountTokenDesired","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"addLiquidityETH","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"},{"internalType":"uint256","name":"liquidity","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"reserveIn","type":"uint256"},{"internalType":"uint256","name":"reserveOut","type":"uint256"}],"name":"getAmountIn","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"reserveIn","type":"uint256"},{"internalType":"uint256","name":"reserveOut","type":"uint256"}],"name":"getAmountOut","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"}],"name":"getAmountsIn","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"}],"name":"getAmountsOut","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"reserveA","type":"uint256"},{"internalType":"uint256","name":"reserveB","type":"uint256"}],"name":"quote","outputs":[{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidity","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidityETH","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidityETHSupportingFeeOnTransferTokens","outputs":[{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityETHWithPermit","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityETHWithPermitSupportingFeeOnTransferTokens","outputs":[{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityWithPermit","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapETHForExactTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactETHForTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactETHForTokensSupportingFeeOnTransferTokens","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForETH","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForETHSupportingFeeOnTransferTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForTokensSupportingFeeOnTransferTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapTokensForExactETH","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapTokensForExactTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]'
output = decode_tx('0x7a250d5630b4cf539739df2c5dacb4c659f2488d', '0x38ed1739000000000000000000000000000000000000000000000000000000009502f900000000000000000000000000000000000000000000a07e38bf71936cbe39594100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000003c02cebb49f6e8f1fc96158099ffa064bbfee38b00000000000000000000000000000000000000000000000000000000616e11230000000000000000000000000000000000000000000000000000000000000003000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000528b3e98c63ce21c6f680b713918e0f89dfae555', sample_abi)
print('function called: ', output[0])
print('arguments: ', json.dumps(json.loads(output[1]), indent=2))

在示例代碼中有幾點需要注意:

  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億單位的起始代幣,並期望至少返回194,024,196,127,819,599,854,524,737單位的目標代幣。這些數字看起來可能是天文數字,但請記住,代幣單位通常用1/10^n表示,其中n大約是18。N有時被稱為代幣的十進制值。
  2. 該path數組描述了在此交易中交換的代幣。每個數組元素都是代幣合約的地址。第一個是USDC(一種與美元掛鈎的穩定幣),第二個是Wrapped Eth(帶有ERC20接口的以太坊),第三個是DXO(一種深空遊戲貨幣)。
  3. 將1和2放在一起,我們可以推斷用戶請求交換2,500 USDC (USDC的十進制值為6)和大約 1.94億DXO (DXO的十進制值為18)。由於這種特殊的成對交換不能直接獲得,交易將通過WETH的中間代幣進行調解。

解碼日誌

該交易在執行過程中還觸發了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代幣對的Uniswap v2對合約。這些合約負責持有各自交易對的流動性,並實際進行交換。用戶最初與之交互的Router合約作為一個協調器,並向適當的配對合約發起內部交易(跟蹤)。因此,為了解碼這些事件,我們還需要一對合約ABI。解碼日誌示例如下:

from web3._utils.events import get_event_data

@lru_cache(maxsize=None)
def _get_topic2abi(abi):
if isinstance(abi, (str)):
abi = json.loads(abi)

event_abi = [a for a in abi if a['type'] == 'event']
topic2abi = {event_abi_to_log_topic(_): _ for _ in event_abi}
return topic2abi

@lru_cache(maxsize=None)
def _get_hex_topic(t):
hex_t = HexBytes(t)
return hex_t


def decode_log(data, topics, abi):
if abi is not None:
try:
topic2abi = _get_topic2abi(abi)

log = {
'address': None, #Web3.toChecksumAddress(address),
'blockHash': None, #HexBytes(blockHash),
'blockNumber': None,
'data': data,
'logIndex': None,
'topics': [_get_hex_topic(_) for _ in topics],
'transactionHash': None, #HexBytes(transactionHash),
'transactionIndex': None
}
event_abi = topic2abi[log['topics'][0]]
evt_name = event_abi['name']

data = get_event_data(w3.codec, event_abi, log)['args']
target_schema = event_abi['inputs']
decoded_data = convert_to_hex(data, target_schema)


return (evt_name, json.dumps(decoded_data), json.dumps(target_schema))
except Exception:
return ('decode error', traceback.format_exc(), None)

else:
return ('no matching abi', None, None)

pair_abi = '[{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"Burn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0In","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1In","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount0Out","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1Out","type":"uint256"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"Swap","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint112","name":"reserve0","type":"uint112"},{"indexed":false,"internalType":"uint112","name":"reserve1","type":"uint112"}],"name":"Sync","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","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":"MINIMUM_LIQUIDITY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"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":"spender","type":"address"},{"internalType":"uint256","name":"value","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":"to","type":"address"}],"name":"burn","outputs":[{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getReserves","outputs":[{"internalType":"uint112","name":"_reserve0","type":"uint112"},{"internalType":"uint112","name":"_reserve1","type":"uint112"},{"internalType":"uint32","name":"_blockTimestampLast","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_token0","type":"address"},{"internalType":"address","name":"_token1","type":"address"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"kLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"mint","outputs":[{"internalType":"uint256","name":"liquidity","type":"uint256"}],"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":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"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":true,"inputs":[],"name":"price0CumulativeLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"price1CumulativeLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"skim","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"amount0Out","type":"uint256"},{"internalType":"uint256","name":"amount1Out","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"swap","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":false,"inputs":[],"name":"sync","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"token0","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"token1","outputs":[{"internalType":"address","name":"","type":"address"}],"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":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]'
output = decode_log(
'0x000000000000000000000000000000000000000000000000000000009502f90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000093f8f932b016b1c',
[
'0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822',
'0x0000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d',
'0x000000000000000000000000242301fa62f0de9e3842a5fb4c0cdca67e3a2fab'],
pair_abi
)

print('event emitted: ', output[0])
print('arguments: ', json.dumps(json.loads(output[1]), indent=2))

與交易解碼的代碼類似,示例代碼針對批量解碼用例進行了優化,並與類似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這樣的區塊瀏覽器上檢查數據消耗以及閱讀智能合約源代碼的組合來實現。這是制定正確的解碼和分析策略的關鍵條件。

Source:https://towardsdatascience.com/decoding-ethereum-smart-contract-data-eed513a65f76

本文鏈接:https://www.8btc.com/article/6705599

轉載請註明文章出處

(0)
上一篇 2021-11-05 18:16
下一篇 2021-11-05 19:03

相关推荐