LoginSignup
4
2

More than 1 year has passed since last update.

ethers-rsを使う

Last updated at Posted at 2021-12-05

はじめに

RustでWeb3さわるならrust-web3かと思っていましたが、JavaScript界隈でweb3.jsethers.jsとが存在するように、Rust界隈にも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

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にmacrosrt-multi-threadを付加してインストールしました。

試す

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

ENS

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

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(())
}
$ 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 --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あんまり使ったことないので設定足りないか何かっぽいですが、とりあえず自ノードで試すことにします。

$ 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 --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を取得するソースコードは以下のような感じです。

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(())
}

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

$ 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をRustに簡単にポーティングできそうです。

今日はここまで。

4
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2