Input Dataを解析/デコードしてみた@Soneium/python
これまでweb3.pyのbuildtransactionを通してInput Dataを作ることはしてきたが、Input Dataから元データを解析するという逆方向の作業はやってこなかったのでやってみようと思う。
例えば、Soneium Minato(テストネット)のSONEXで0.001WETHをSONEXにスワップしたときのトランザクションでいうと以下の画像の部分。
このInput Dataは、Method ID(関数セレクター)+関数への引数をABIエンコードしたもの、で構成されている。
つまり、Input Dataの先頭4バイトがMethod IDなので、関数が分かり(関数が分かると取るべき引数が分かり)、これをもとにDataをデコードすると引数として入力された値が分かる、という流れである。
※基本的に、コントラクト(関数)のABIが分からなければDataをデコードすることはできない。
pythonコードから生成されたData部を確認する
今回の題材は、前回の記事で書いたSoneium Minatoで0.001WETHをSONEXにスワップするトランザクションとする。
まずは、コードの中で生成されたトランザクションを確認してみる。
トランザクションを生成した後にトランザクションを表示するコードとData部のみを抽出するコードを加えてみる。
#トランザクションの作成
tx = swap_router.functions.exactInputSingle(params).build_transaction({
'value': w3.to_wei(0, 'ether'),
'gas': 200000,
'gasPrice': w3.eth.gas_price,
'nonce': w3.eth.get_transaction_count(my_wallet_address)
})
#トランザクションの表示
print(tx)
#Data部のみ取り出す
data = tx['data']
print("data = ")
print(data)
結果は以下。
ブロックエクスプローラでトランザクションを確認すると、Data部は一致していることが分かる(当たり前のことですが)。
Data部の解析
Data部の先頭はMethod IDですが、その後の引数部分についてはそれぞれ32バイトごとにエンコードされます。
Data部をmethod IDと引数(32バイトずつ)で区切って表示してみます。
#display.py
data = '0x414bf3890000000000000000000000002337511e43b7d63fbc283900be9fb25fc6ecc25c0000000000000000000000008879309d4002b8c98163ece3f58653f774de84160000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000f69fd1acae36de01e61e722cdf44b5bf9baadd5c000000000000000000000000000000000000000000000000000000006756e81400000000000000000000000000000000000000000000000000038d7ea4c6800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
methodID = data[2:10]
arg = data[10:]
print("method ID =")
print(methodID)
print("")
#print(arg)
print("argを32バイトずつ表示")
i = 0
j = 64
for _ in range(len(arg) // 64):
print(arg[i:j])
i += 64
j += 64
実行結果は以下。
methodID:414bf389である関数はexactInputSingleであり、
この関数の引数がそれぞれargの32バイトずつ(1行ずつ)に対応しているはずです。
1行目:tokenIn(address)
0000000000000000000000002337511e43b7d63fbc283900be9fb25fc6ecc25c
これは、WETHのコントラクトアドレスを32バイトになるように右詰めにしたもの。
2行目:tokenOut(address)
0000000000000000000000008879309d4002b8c98163ece3f58653f774de8416
これは、SONEXのコントラクトアドレスを32バイトになるように右詰めにしたもの。
3行目:fee(uint24)
0000000000000000000000000000000000000000000000000000000000000bb8
これは、3000を16進数表記したものです。
4行目:recipient(address)
000000000000000000000000f69fd1acae36de01e61e722cdf44b5bf9baadd5c
これは私のウォレットアドレスが32バイトになるように右詰めされたもの。
5行目:deadline(uint256)
000000000000000000000000000000000000000000000000000000006756e814
これは、このトランザクションの有効期限を示します。
16進数を10進数に直し、タイムスタンプから時刻に変換すると、このトランザクションの有効期限(いつまでにネットワークで処理されないと無効になるか)が分かります。
つまり、このトランザクションは日本時間で2024年12月9日の21:52:36まで有効だったということが分かります。
5行目:amountIn(uint256)
00000000000000000000000000000000000000000000000000038d7ea4c68000
これも10進数に変換すると、1000000000000000、つまり0.001(ETH換算)と分かります。
6行目/7行目:amountOutMinimum(uint256)/sqrtPriceLimitX96 (uint160)
これはどちらも0を入力したので0ですね。
このように、method IDから関数が特定できれば、入力引数の値を解析することができます。
Input Dataをデコード
以下、SwapRouterのコントラクトインスタンスを作成し、先ほど使ったデータをデコードするコード。
#decode.py
from web3 import Web3
from eth_abi import encode
from datetime import datetime
import json
SwapRouter = '0xfC53A3f62B4EfaC7b64CEE3Ae1dD944262b27Ff1'
#Web3のセットアップ(Soneium Minato)
w3 = Web3(Web3.HTTPProvider("https://rpc.minato.soneium.org"))
#コントラクトインスタンスの作成
with open('SwapRouter_ABI.json') as f:
SwapRouterABI = json.load(f)
swap_router = w3.eth.contract(address=SwapRouter, abi=SwapRouterABI)
#デコードするdata
data = '0x414bf3890000000000000000000000002337511e43b7d63fbc283900be9fb25fc6ecc25c0000000000000000000000008879309d4002b8c98163ece3f58653f774de84160000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000f69fd1acae36de01e61e722cdf44b5bf9baadd5c000000000000000000000000000000000000000000000000000000006756e81400000000000000000000000000000000000000000000000000038d7ea4c6800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
methodID = data[2:10]
arg = data[10:]
print("method ID =")
print(methodID)
print("")
#print(arg)
print("argを32バイトずつ表示")
i = 0
j = 64
for _ in range(len(arg) // 64):
print(arg[i:j])
i += 64
j += 64
#Dataをデコード
method, arg = swap_router.decode_function_input(data)
print(method)
print(arg)
実行結果は以下。
先ほど解析した通りの結果が得られている。
おまけ
その他のトランザクションならどうか試してみる。
0.015WETHをSONEXにスワップしてみる。Fee Rateも0.01%となっており先ほどと異なる。
このInput Dataをデコードしてみる。以下、コード。
#decode.py
from web3 import Web3
from eth_abi import encode
from datetime import datetime
import json
SwapRouter = '0xfC53A3f62B4EfaC7b64CEE3Ae1dD944262b27Ff1'
#Web3のセットアップ(Soneium Minato)
w3 = Web3(Web3.HTTPProvider("https://rpc.minato.soneium.org"))
#コントラクトインスタンスの作成
with open('SwapRouter_ABI.json') as f:
SwapRouterABI = json.load(f)
swap_router = w3.eth.contract(address=SwapRouter, abi=SwapRouterABI)
#デコードするdata
data = '0x414bf3890000000000000000000000002337511e43b7d63fbc283900be9fb25fc6ecc25c0000000000000000000000008879309d4002b8c98163ece3f58653f774de84160000000000000000000000000000000000000000000000000000000000000064000000000000000000000000f69fd1acae36de01e61e722cdf44b5bf9baadd5c000000000000000000000000000000000000000000000000000000006756fcef00000000000000000000000000000000000000000000000000354a6ba7a18000000000000000000000000000000000000000000000000000ba10e23b4d8965830000000000000000000000000000000000000000000000000000000000000000'
methodID = data[2:10]
arg = data[10:]
print("method ID =")
print(methodID)
print("")
#print(arg)
print("argを32バイトずつ表示")
i = 0
j = 64
for _ in range(len(arg) // 64):
print(arg[i:j])
i += 64
j += 64
#Dataをデコード
method, arg = swap_router.decode_function_input(data)
print(method)
print(arg)
実行結果。
特に、feeとamountInに注目すれば、きちんとデコードできていることが分かる。