😎

solidity etherscan のログをもっと理解する (method id, signature編)

2022/06/29に公開

solidity etherscan のログをもっと理解する (method id, signature編)

solidityを勉強して、 contractの動作がわかってくると、自分のwalletに関連したやりとりが
確認できるetherscanの中によくわからない部分があるといことがわかってきます。

https://rinkeby.etherscan.io/address/0x3e7311e0fc89fa633433076be268ae007a1b827a

例えば opensea でtokenを売り買いした時に表示されるtransactionの中に出てくる
method の数字 これは一体何を表しているのでしょうか?

これは method signature (method id) と言われるもので、
functionName(type1,type2,...)という文字列をKeccak256でhashを取った先頭の4byteになります。

ERC721などのmethodはとてもよく使われるのでetherscanの方で文字列に変換して出してくれているが、
opne seaのcontructで定義されているものは登録されていないので出てこないようです。

自分で逆引きしたい時の方法を3つ調べましたので紹介します。
原理を確認したい場合は 1, これからデバッグを色々行う準備として応用力の高さなら2、単純に逆引きしたいだけであれば3がおすすめです。

逆引き1: 文字列から生成

以下のようなコードで簡単に method idを生成できます。

var Web3 = require('web3');
console.log('\n\nERC721Metadata methods ======');
str = 'name()'; console.log(Web3.utils.sha3(str).substring(0, 10), "  ",str );
str = 'symbol()'; console.log(Web3.utils.sha3(str).substring(0, 10), "  ",str );
str = 'tokenURI(uint256)'; console.log(Web3.utils.sha3(str).substring(0, 10), "  ",str );

/* output
ERC721Metadata methods ======
0x06fdde03    name()
0x95d89b41    symbol()
0xc87b56dd    tokenURI(uint256)
*/

また、ちょっと試したいだけであれば 下記のサイトでfunctionの文字列からhash 4byteを確認できます。
https://emn178.github.io/online-tools/keccak_256.html

この方法の限界

最初は私のwalletのtransactionのtoに表示されている
Openseaのseaportというcontractのコードを下記から取得してメソッドを手作業で変えれば良いと思っていました。
https://rinkeby.etherscan.io/address/0x00000000006c3852cbef3e08e8df289169ede581#code

例えば下記のような感じです。

console.log('\n\nOpenSea Seaport interface methods ======');
str = 'fulfillOrder(Order,bytes32)'; console.log(Web3.utils.sha3(str).substring(0, 10), "  ",str );

しかし、出力された signatureを etherscanの code タブの Contract Creation Code セクションのバイナリから検索しても出てこないのです。 
https://rinkeby.etherscan.io/address/0x00000000006c3852cbef3e08e8df289169ede581#code

あれ??と思って stack exchange等で調べると どうやらmethod signature の計算にはstructはできないとのこと。しょうがないので頑張ってABI定義を目視して 下記のようなfunction定義を作って計算してみました。

str = 'fulfillOrder(((address,address,(uint8,address,uint256,uint256,uint256)[],(uint8,address,uint256,uint256,uint256,address)[],uint8,uint256,uint256,bytes32,uint256,bytes32,uint256),bytes),bytes32)'; console.log(Web3.utils.sha3(str).substring(0, 10), "  ",str );

/* output
0xb3a34c4c    fulfillOrder(((address,address,(uint8,address,uint256,uint256,uint256)[],(uint8,address,uint256,uint256,uint256,address)[],uint8,uint256,uint256,bytes32,uint256,bytes32,uint256),bytes),bytes32)
*/

今度は無事contract creation codeの中に見つかりました。
これを、自分のtransactionの中に出てくるsignatureが出てくるまで繰り返す、、、やるわけがない。

逆引き2: ABI定義から出力

そもそもABI定義も公開されているんだからもっと効率的な方法があるだろうと調べたらちゃんとありました。
ethersのinterfaceを使えばsignatureを簡単に取得できる!
https://docs.ethers.io/v5/api/utils/abi/interface/#Interface--selectors

Contract ABI
ABI定義を適当な名前(seaport.abi)で保存して、

下記のようなコードで無事signature一覧を出力できました。

const { ethers } = require("ethers");
const fs = require('fs');
  
const data = fs.readFileSync('seaport.abi',
            {encoding:'utf8', flag:'r'});
const iface = new ethers.utils.Interface(data);
Object.keys(iface.functions).map((k)=>console.log(iface.getSighash(k),'  ',k));

/* output
0xfd9f1e10    cancel((address,address,(uint8,address,uint256,uint256,uint256)[],(uint8,address,uint256,uint256,uint256,address)[],uint8,uint256,uint256,bytes32,uint256,bytes32,uint256)[])
0xe7acab24    fulfillAdvancedOrder(((address,address,(uint8,address,uint256,uint256,uint256)[],(uint8,address,uint256,uint256,uint256,address)[],uint8,uint256,uint256,bytes32,uint256,bytes32,uint256),uint120,uint120,bytes,bytes),(uint256,uint8,uint256,uint256,bytes32[])[],bytes32,address)
0x87201b41    fulfillAvailableAdvancedOrders(((address,address,(uint8,address,uint256,uint256,uint256)[],(uint8,address,uint256,uint256,uint256,address)[],uint8,uint256,uint256,bytes32,uint256,bytes32,uint256),uint120,uint120,bytes,bytes)[],(uint256,uint8,uint256,uint256,bytes32[])[],(uint256,uint256)[][],(uint256,uint256)[][],bytes32,address,uint256)
0xed98a574    fulfillAvailableOrders(((address,address,(uint8,address,uint256,uint256,uint256)[],(uint8,address,uint256,uint256,uint256,address)[],uint8,uint256,uint256,bytes32,uint256,bytes32,uint256),bytes)[],(uint256,uint256)[][],(uint256,uint256)[][],bytes32,uint256)
0xfb0f3ee1    fulfillBasicOrder((address,uint256,uint256,address,address,address,uint256,uint256,uint8,uint256,uint256,bytes32,uint256,bytes32,bytes32,uint256,(uint256,address)[],bytes))
0xb3a34c4c    fulfillOrder(((address,address,(uint8,address,uint256,uint256,uint256)[],(uint8,address,uint256,uint256,uint256,address)[],uint8,uint256,uint256,bytes32,uint256,bytes32,uint256),bytes),bytes32)
0xf07ec373    getCounter(address)
0x79df72bd    getOrderHash((address,address,(uint8,address,uint256,uint256,uint256)[],(uint8,address,uint256,uint256,uint256,address)[],uint8,uint256,uint256,bytes32,uint256,bytes32,uint256))
0x46423aa7    getOrderStatus(bytes32)
0x5b34b966    incrementCounter()
0xf47b7740    information()
0x55944a42    matchAdvancedOrders(((address,address,(uint8,address,uint256,uint256,uint256)[],(uint8,address,uint256,uint256,uint256,address)[],uint8,uint256,uint256,bytes32,uint256,bytes32,uint256),uint120,uint120,bytes,bytes)[],(uint256,uint8,uint256,uint256,bytes32[])[],((uint256,uint256)[],(uint256,uint256)[])[])
0xa8174404    matchOrders(((address,address,(uint8,address,uint256,uint256,uint256)[],(uint8,address,uint256,uint256,uint256,address)[],uint8,uint256,uint256,bytes32,uint256,bytes32,uint256),bytes)[],((uint256,uint256)[],(uint256,uint256)[])[])
0x06fdde03    name()
0x88147732    validate(((address,address,(uint8,address,uint256,uint256,uint256)[],(uint8,address,uint256,uint256,uint256,address)[],uint8,uint256,uint256,bytes32,uint256,bytes32,uint256),bytes)[])
*/

ここから最初のtransactionで不明だった0xfb0f3ee1を検索するとできました!下記のメソッドを呼び出していたんですね。
fulfillBasicOrder((address,uint256,uint256,address,address,address,uint256,uint256,uint8,uint256,uint256,bytes32,uint256,bytes32,bytes32,uint256,(uint256,address)[],bytes))

逆引き3 signature hash検索ページがある

ethereum 本家にreverse engineering 指南ページがあります。
https://ethereum.org/ja/developers/tutorials/reverse-engineering-a-contract/

ここから逆引きようのサイトが紹介されてます。
ここで知りたいsignatureを選ぶと、、 はい出てきます
https://www.4byte.directory/signatures/?bytes4_signature=0xfb0f3ee1

当然ですが4byteしかないので 他の関数とも被ることもありますね。
contractのアドレスはわかっているでしょうから少ない数なら両方検索すればどちらが正しいかはわかりますね。

以上 solidity debugノウハウためていこうシリーズその1でした。

Discussion