はじめに
RustでWeb3さわるならrust-web3かと思っていましたが、JavaScript界隈でweb3.jsとethers.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
[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を扱ってみます。
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を取得するソースコードは以下のような感じです。
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に簡単にポーティングできそうです。
今日はここまで。