Ethereum（ブロックチェーン）のスマートコントラクトの処理履歴を取得する方法

# はじめに
この記事では、Ethereumのスマートコントラクトの実行結果の履歴を取得する方法について解説していこうと思います。

自身でdApps（スマートコントラクトを活用したアプリケーション）を作る場合は、スマートコントラクトの処理の状態を確認するために、スマートコントラクトの実行結果を監視したりします。それ以外にも、他者が作ったdAppsの利用状況を把握するといった目的で、スマートコントラクトの実行結果の履歴を取得することもできます。

今回は、dAppsの利用状況を把握するケースを例に解説していきます。

# 環境
この記事では、下記を利用します。
- 言語：Javascript
- 実行環境：Node.js
- 接続先ブロックチェーンノード：Infura

# 利用状況を確認するdApps
今回はRaribleというdAppsを対象にスマートコントラクトの実行結果を取得します。Raribleは、デジタルアート（NFT）を売買できるdAppsで、2020年12月現在、利用量で世界トップクラスのdAppsです。
https://rarible.com/
![スクリーンショット 2020-11-10 16.30.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409677/c8ba59b2-80b6-3321-704b-a58e10acc9a0.png)

Raribleでは、デジタルアートの購入処理、つまりデジタルアートの所有者変更と購入代金の送金をスマートコントラクトで行っています。
下記を算出するために、Raribleのスマートコントラクトで行われた購入処理の結果を取得したいと思います。
- デジタルアートの取引高
- デジタルアートの平均売買金額
- デジタルアートを売買しているユーザー数

# スマートコントラクトの実行結果の取得
Raribleでデジタルアートを購入する際の処理フローは下図のようになっていると考えられます。
![Rarible処理フロー図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409677/e7f87dfb-458f-05b2-530e-fc1645e41cdf.png)

図中のBuy Function実行の結果を取得することでRaribleの利用状況を確認します。
まず、Buy Functionのコードを確認すると下記のようになっています。（多くのスマートコントラクトは、[Etherscan](https://etherscan.io/address/0x93F2a75d771628856f37f256dA95e99Ea28AaFbE#code)でコードが公開されています。）

```Solidity:Buyのコード
function buy(IERC1155 token, uint256 tokenId, address payable owner, uint256 selling, uint256 buying, uint256 price, uint256 sellerFee, Sig memory signature) public payable {
        uint256 nonce = verifySignature(address(token), tokenId, owner, selling, price, sellerFee, signature);
        uint256 total = price.mul(buying);
        uint256 buyerFeeValue = total.mul(buyerFee).div(10000);
        require(total + buyerFeeValue == msg.value, "msg.value is incorrect");
        bool closed = verifyOpenAndModifyState(address(token), tokenId, owner, nonce, selling, buying);

        transferProxy.erc1155safeTransferFrom(token, owner, msg.sender, tokenId, buying, EMPTY);

        transferEther(token, tokenId, owner, total, sellerFee);
        emit Buy(address(token), tokenId, owner, price, msg.sender, buying);
        if (closed) {
            emit CloseOrder(address(token), tokenId, owner, nonce + 1);
        }
    }
```

このコードの下から4行目に下記の記述があります。

```Solidity:実行結果の出力箇所
emit Buy(address(token), tokenId, owner, price, msg.sender, buying);
```

`emit Buy(...)`では、Eventというものを発火（出力）しています。

Eventとは、スマートコントラクトの開発言語であるSolidityの仕組みで、スマートコントラクトで発生した出来事をWebアプリケーションなど、ブロックチェーンの外部に伝達するためのものです。`emit EventName(...)`のように記述することで、Eventを発火できます。スマートコントラクトの処理が終了したことを外部に伝達するために、Functionの処理完了を示すEventをFunction内部に記述することが慣例となっています。

なお、Eventの定義はFunctionの外で行われており、BuyのEventは下記の通り定義されています。

```Solidity:Eventの定義
event Buy(address indexed token, uint256 indexed tokenId, address owner, uint256 price, address buyer, uint256 value);
```

この`event Buy(...)`の`...`部分には引数が記述されており、引数に入れた値がEventに出力されます。BuyのEventには下記の引数が含まれており、このEventを取得すればRaribleの利用状況を確認するために必要な情報が得られることがわかります。
- owner: 販売者
- price: 販売価格
- buyer: 購入者

## Eventの取得
Eventを取得すれば良いとわかったところで、Eventを取得するコードを書いていきたいと思います。

EventはEthereumのノードから取得できます。そのため、ノードと接続する必要があります。ノードと接続するためには下記の2つを用意します。
- 接続先のノード
- ノードと接続するためのライブラリー

### 接続先のノード
接続先のノードは自前でサーバーを構築することもできますが、手間がかかるのでノードのホスティングサービスを使用します。

Ethereumのノードのホスティングサービスでは、Infuraというサービスがメジャーで、リクエスト数が少なければ無料で使えるのでこちらを使います。
https://Infura.io/

### ノードと接続するためのライブラリー
ノードと接続するライブラリーとして、Ethereumで最も使用されているweb3jsを使用します。web3jsでは、HTTPやWebSocketでノードから情報を取得できるAPIが多数用意されています。

今回は、2020年12月現在で1系最新の1.3.0を使用します。なお、web3jsのドキュメントは下記に掲載されています。
https://web3js.readthedocs.io/en/v1.3.0/

### コードの実装

#### web3jsのインストール
まず、web3jsをプロジェクトにインストールします。

```
yarn add web3
```

### ABIの取得
スマートコントラクトのFunctionを実行するためには、ABI(Application Binary Interface)という、スマートコントラクトのインタフェースが定義された情報を取得する必要があります。（ABIについて詳しく知りたい方は[こちら](https://y-nakajo.hatenablog.com/entry/2019/05/14/182854)をご参照ください。）

多くのスマートコントラクトは、ABIをEtherscanで取得できます。
[Raribleの売買用コントラクトのABI](https://etherscan.io/address/0x93F2a75d771628856f37f256dA95e99Ea28AaFbE#code)

EtherscanにはABIが下記画像のように掲載されており、赤枠のボタンをクリックするとJSON形式でABIをコピーできます。
![etherscan_abi.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409677/a17069cd-2473-4b2b-c12d-48d81ed7c5db.png)
今回は、コピーしたABIをプロジェクトのディレクトリー直下に`RaribleSaleABI.json`という名前で保存します。

### イベントを取得するコードの実装
イベントを取得するコードは下記のようになります。

```Javascript:eventGetter.js
const Web3 = require('web3');
const web3 = new Web3();

main();

async function main() {

  // 接続先のノードを設定
  web3.setProvider(new web3.providers.HttpProvider("https://mainnet.infura.io/v3/{PROJECT ID}"));

  // 売買用のコントラクトのABI
  const abi = require('./RaribleSaleABI.json');

  // 売買用のコントラクトアドレス
  const contractAddress = '0x93F2a75d771628856f37f256dA95e99Ea28AaFbE';

  // 売買用のコントラクトと接続するためのインスタンス作成
  const contract = new web3.eth.Contract(abi, contractAddress);

  // 売買用のコントラクトがデプロイされたBlock Number
  const createdBlock = 10786886;

  // 最新のBlock Number
  const latestBlock = await web3.eth.getBlockNumber();
  
  const blockRange = 10000;
  let fromBlock = createdBlock;
  let toBlock = (fromBlock + blockRange) > latestBlock ? latestBlock : fromBlock + blockRange;

  let events = []

  while (true) {
    // Eventを取得
    const event = await contract.getPastEvents('Buy', { fromBlock: fromBlock, toBlock: toBlock });
    events = events.concat(event);
    if (toBlock >= latestBlock) {
      break;
    }
    fromBlock = toBlock + 1;
    toBlock = (fromBlock + blockRange) > latestBlock ? latestBlock : fromBlock + blockRange;
  }

  // 取得したEventを出力
  console.log(events);
}
```

### コードの解説

コードの内容を上から順番に解説していきます。

#### Step1. web3jsの読み込み
まず、インストールしたweb3jsを読み込み、インスタンス化します。

```Javascript
const Web3 = require('web3');
const web3 = new Web3();
```

#### Step2. 接続先ノードの設定
web3に接続先のノードを設定します。今回は、InfuraにHTTPで接続するので、InfuraのエンドポイントをHttpProviderとして設定します。エンドポイントに指定する`PROJECT ID`は、[Infura](https://Infura.io/)のアカウントを作成後、Infuraのダッシュボードでプロジェクトを作成すると取得できます。

```Javascript
// 接続先のノードを設定
web3.setProvider(new web3.providers.HttpProvider("https://mainnet.infura.io/v3/{PROJECT ID}"));
```

#### Step3. ABIの読み込み
先ほどEtherscanからABIをコピーして作成したJSONファイルを読み込みます。

```Javascript
// 売買用のコントラクトのABI
const abi = require('./RaribleSaleABI.json');
```
#### Step4. スマートコントラクト接続用のインスタンス作成
web3でスマートコントラクトに接続するために必要なインスタンスを作成します。インスタンスの作成時にスマートコントラクトのabiとアドレスを指定します。（スマートコントラクトのアドレスは、[DaapRadar](https://dappradar.com/ethereum/marketplaces/rarible)などで、dAppsのページにアクセスすると調べられます。）

```Javascript
// 売買用のコントラクトアドレス
const contractAddress = '0x93F2a75d771628856f37f256dA95e99Ea28AaFbE';

// 売買用のコントラクトと接続するためのObject生成
const contract = new web3.eth.Contract(abi, contractAddress);
```

#### Step5. Eventの取得対象のBlock指定
Eventを取得する際に、どのBlockからどのBlockまでのEventを取得するのか指定する必要があります。今回は、対象のスマートコントラクトがデプロイされてから、現在（最新Block）までのEventを取得したいので、スマートコントラクトのデプロイ時のBlock Numberから最新のBlock NumberをEventの取得範囲とします。デプロイ時のBlock Numberは、[Etherscan](https://etherscan.io/address/0x93F2a75d771628856f37f256dA95e99Ea28AaFbE)で確認できます。

```Javascript
// 売買用のコントラクトがデプロイされたBlock Number
const createdBlock = 10786886;

// 最新のBlock Number
const latestBlock = await web3.eth.getBlockNumber();
```

#### Step6. Eventの取得
先ほど定義した`createdBlock`から`latestBlock`まで、一気にEventを取得したいところですが、Infuraでは一度に取得できるEvent数に上限があるため、一度にEventを取得するBlockの範囲に上限を設けます。今回は上限を10000 Blockとしています。

`createdBlock`から`latestBlock`までのBlock数の差が10000（`blockRange`）を超える場合、`createdBlock`から10000 Block刻みに`getPastEvents`を呼び出して、Eventを取得しています。

なお、`getPastEvents`の第一引数にはEvent名を指定します。このEvent名にはスマートコントラクトで定義されているEvent名を指定します。今回は、`Buy`を指定します。

```Javascript
const blockRange = 10000;
let fromBlock = createdBlock;
let toBlock = (fromBlock + blockRange) > latestBlock ? latestBlock : fromBlock + blockRange;

let events = []

while (true) {
  // Eventを取得
  const event = await contract.getPastEvents('Buy', { fromBlock: fromBlock, toBlock: toBlock });
  events = events.concat(event);
  if (toBlock >= latestBlock) {
    break;
  }
  fromBlock = toBlock + 1;
  toBlock = (fromBlock + blockRange) > latestBlock ? latestBlock : fromBlock + blockRange;
}
```

#### Step.7 取得したEventの出力
最後に、取得したEventを出力します。本来は、デジタルアートの取引高などを集計するのであれば、DBなどに保存すべきですが、今回は説明を簡略化するために出力先をconsoleとしています。

```Javascript
// 取得したEventを出力
console.log(events);
```

### 動作

プロジェクトのディレクトリで下記コマンドを実行し、書いたコードを実行します。

```
node eventGetter.js
```

コードを実行すると、consoleに下記のようなオブジェクトが配列で出力されます。

```
{ address: '0x93F2a75d771628856f37f256dA95e99Ea28AaFbE',
    blockHash:
     '0xa9f21bb59fd00eb84671585a49ac55ebbddc65468178e9bf89dd7efcfd4c2db9',
    blockNumber: 10797033,
    logIndex: 85,
    removed: false,
    transactionHash:
     '0x87933885bb4ec394327e7eacb6db1562d9c010cc00041763682588402f930f21',
    transactionIndex: 69,
    id: 'log_62a86732',
    returnValues:
     Result {
       '0': '0xd07dc4262BCDbf85190C01c996b4C06a461d2430',
       '1': '3741',
       '2': '0x1c356b638B32B3a6CDF2Cb49f6ee01f8d9Ac4dEf',
       '3': '250000000000000000',
       '4': '0x28f5c952482948c2605996847F272c1E4A4C50eF',
       '5': '1',
       token: '0xd07dc4262BCDbf85190C01c996b4C06a461d2430',
       tokenId: '3741',
       owner: '0x1c356b638B32B3a6CDF2Cb49f6ee01f8d9Ac4dEf',
       price: '250000000000000000',
       buyer: '0x28f5c952482948c2605996847F272c1E4A4C50eF',
       value: '1' },
    event: 'Buy',
    signature:
     '0x710791c544fdcb0c8c5b17f5bfa5b6721dfff68224047778e2d64e426ded61c6',
    raw:
     { data:
        '0x0000000000000000000000001c356b638b32b3a6cdf2cb49f6ee01f8d9ac4def00000000000000000000000000000000000000000000000003782dace9d9000000000000000000000000000028f5c952482948c2605996847f272c1e4a4c50ef0000000000000000000000000000000000000000000000000000000000000001',
       topics: [Array] } }
```

`returnValues`の中にEventの引数である`owner`、`price`、`buyer`などが入っています。これらを集計することで、下記を算出できます。
- デジタルアートの取引高
- デジタルアートの平均売買金額
- デジタルアートを売買しているユーザー数

# 最後に
2020年12月現在、EthereumではDeFi（decentralized finance：分散型金融）やGame、NFT（Non-fungible token）取引などの領域でdAppsが多く作られています。

Ethereumを使って開発されたdAppsは、[DappRadar](https://dappradar.com/rankings/protocol/eth)や[Dapp.com](https://www.dapp.com/dapps/ethereum?sort=0&time=0&type=0)などのサイトでで確認できるので、気になるdAppsを見つけて利用状況を解析してみると面白いかもしれません。