ethers-rsを使う

## はじめに
RustでWeb3さわるなら[rust-web3](https://github.com/tomusdrw/rust-web3)かと思っていましたが、JavaScript界隈で[web3.js](https://web3js.readthedocs.io)と[ethers.js](https://docs.ethers.io/)とが存在するように、Rust界隈にも[ethers-rs](https://github.com/gakonst/ethers-rs)が存在します。

今日はethers-rsを使ってみます。

## 環境

Rust 1.56.1 stable
MacOSX 11.6 (Intel Mac)
ethers-rs https://github.com/gakonst/ethers-rs/commit/23c356ce3823272dd96e70f5453dea732c182d7b

## 準備

`Cargo.toml`

```toml:Cargo.toml
[package]
name = "ethersrs-examples"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1"
ethers = { git = "https://github.com/gakonst/ethers-rs" }
tokio = { version = "1.5", features = ["macros", "rt-multi-thread"] }
```

tokioはバージョン1.5でサンプルにあわせてfeaturesに`macros`と`rt-multi-thread`を付加してインストールしました。

## 試す

Ethereumノードは自前で起動している http://localhost:8545/ を使います。


### ENS

手始めにENSを扱ってみます。

```rust:src/main.rs
use ethers::{prelude::*};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let provider = Provider::<Http>::try_from(
        "http://localhost:8545/"
    )?;

    let address = provider.resolve_name("vitalik.eth").await?;
    println!("{:?}", address);

    let ens = provider.lookup_address(address).await?;
    println!("{:?}", ens);

    Ok(())
}
```

```shell-session
$ cargo run
   Compiling ethersrs-examples v0.1.0 (/path/to/1205_ethersrs)
    Finished dev [unoptimized + debuginfo] target(s) in 4.30s
     Running `target/debug/ethersrs-examples`
0xd8da6bf26964af9d7eed9e03e53415d37aa96045
"vitalik.eth"
```

ENS名からアドレスをひいてきたり、アドレスからENS導出もできました。

### Ganache

ユーティリティ機能としてganache-cliをラップした機能を使えるようです。

```diff
diff --git a/1205_ethersrs/src/main.rs b/1205_ethersrs/src/main.rs
index b2b70df..ff757c3 100644
--- a/1205_ethersrs/src/main.rs
+++ b/1205_ethersrs/src/main.rs
@@ -1,10 +1,9 @@
-use ethers::{prelude::*};
+use ethers::{prelude::*, utils::Ganache};

 #[tokio::main]
 async fn main() -> anyhow::Result<()> {
-    let provider = Provider::<Http>::try_from(
-        "http://localhost:8545/"
-    )?;
+    let ganache = Ganache::new().spawn();
+    let provider = Provider::<Http>::try_from(ganache.endpoint())?;

     let address = provider.resolve_name("vitalik.eth").await?;
     println!("{:?}", address);
```

ただ実行してみたのですが、私の環境ではうまく動きませんでした。
Ganacheあんまり使ったことないので設定足りないか何かっぽいですが、とりあえず自ノードで試すことにします。

```shell-session
$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.16s
     Running `target/debug/ethersrs-examples`
thread 'main' panicked at 'could not abi-decode bytes to address tokens: InvalidName("please ensure the contract and method you're calling exist! failed to decode empty bytes. if you're using jsonrpc this is likely due to jsonrpc returning `0x` in case contract or method don't exist")', $HOME/.cargo/git/checkouts/ethers-rs-c3a7c0a0ae0fe6be/23c356c/ethers-providers/src/provider.rs:1004:10
```

### コントラクト実行

JS版ethersと同じようにethers-rsでもコントラクトを実行することができます。
ほぼ同じようなインターフェースで呼び出せます。

ここでは私が携わっているプロジェクトであるDev Protocolのプロパティトークンに紐づくsToken IDを取得する例をやってみます。

まず、ABIを扱うためのcrateを追加しておきます。
`serde` `serde_json` `ethers-core`

```diff
diff --git a/1205_ethersrs/Cargo.toml b/1205_ethersrs/Cargo.toml
index 8daafc9..cfa7182 100644
--- a/1205_ethersrs/Cargo.toml
+++ b/1205_ethersrs/Cargo.toml
@@ -6,4 +6,7 @@ edition = "2021"
 [dependencies]
 anyhow = "1"
 ethers = { git = "https://github.com/gakonst/ethers-rs" }
+ethers-core = { git = "https://github.com/gakonst/ethers-rs" }
 tokio = { version = "1.5", features = ["macros", "rt-multi-thread"] }
+serde = { version = "1", features = ["derive"] }
+serde_json = "1"
```

sToken IDを取得するソースコードは以下のような感じです。

```rust:examples/devprotocol-stoken.rs
use ethers::{prelude::*};
use ethers_core::abi::Abi;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let provider = Provider::<Http>::try_from("http://localhost:8545/")?;

    // sTokenコントラクトアドレス
    let stoken_contract_address = "0x50489Ff5f879A44C87bBA85287729D663b18CeD5".parse::<Address>()?;

    // ABI定義
    // ここで参照可能 https://etherscan.io/address/0x4d34215d514d8682fc5af3652000d1ea2a325315#code
    let abi: Abi = serde_json::from_str(r#"
[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},
    :
   一部省略
    :
{"internalType":"uint256","name":"_pendingReward","type":"uint256"}],"name":"update","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]
"#)?;

    // コントラクト取得
    let contract = Contract::new(stoken_contract_address, abi, provider);

    // 対象のプロパティアドレス
    let property_address = "0xac1AC9d00314aE7B4a7d6DbEE4860bECedF92309".parse::<Address>()?;

    // プロパティに紐づくsTokens(ID)を取得するために positionsOfProperty を実行
    let res = contract.method::<_, Vec<u128>>("positionsOfProperty", (property_address, ))?.call().await?;

    // 表示
    println!("stokens: {:?}", res);

    Ok(())
}
```

実行すると以下のような感じです。

```shell-session
$ cargo run --example devprotocol-stoken
   Compiling ethersrs-examples v0.1.0 ($HOME/work/advent2021/1205_ethersrs)
    Finished dev [unoptimized + debuginfo] target(s) in 3.72s
     Running `target/debug/examples/devprotocol-stoken`
stokens: [6, 18]
```

これでDev ProtocolをJavaScript/TypeScriptで簡単に扱えるインターフェースを提供する[dev-kit-js](https://github.com/dev-protocol/dev-kit-js)をRustに簡単にポーティングできそうです。

今日はここまで。