🔥

RustでEthereum

2022/11/17に公開

作ったもの

https://github.com/akihokurino/rust-ethereum

RustでEthereumにアクセスする

rustを使ってEthereumと連携する場合、

https://github.com/tomusdrw/rust-web3

https://github.com/gakonst/ethers-rs

ここら辺を利用すると思いますが、どちらもReadMeを読むだけでは扱えず、外の情報もクエリはあるけど、署名されたトランザクションは載っていないものがほとんどだったので、中のコードを読みながら各種実装を行なった結果を残しておきます。

レポジトリのソースコードはローカルネットワークとGoerliネットワークで検証済みです。(2022/11/01時点)

前提

利用しているライブラリ

web3 = "0.18.0"
ethers = { version = "1.0.0", features = ["legacy"] }
ethers-signers = "1.0.0"
secp256k1 = { version = "0.21.3", features = ["rand", "rand-std"] }

rust バージョン

rustup 1.24.3 (2021-05-31)
info: This is the version for the rustup toolchain manager, not the rustc compiler.
info: The currently active `rustc` version is `rustc 1.65.0 (897e37553 2022-11-02)`

注意事項

必要最低限を抜粋して記載しているかつ、そのままコピペで動くように調整しているので、ビルドは通していますがそのまま動かない可能性があります。
その場合は上記レポジトリを参照していただけると幸いです。

コードの可読性を重視して、エラーハンドリング等は意図的に省いています。

残高の取得

rust-web3

use web3::types::Address;
use web3::{transports, Web3};

pub async fn run() {
    let chain_url = "ETHEREUM_URL";
    let wallet_address = "WALLET_ADDRESS";

    let transport = transports::Http::new(chain_url)
        .ok()
        .expect("should set ethereum url");

    let cli = Web3::new(transport);

    let balance = cli
        .eth()
        .balance(
            wallet_address
                .trim_start_matches("0x")
                .parse::<Address>()
                .unwrap(),
            None,
        )
        .await
        .unwrap();

    println!("balance wei: {}", balance);
}

ethers-rs

use ethers::prelude::*;

pub async fn run() {
    let chain_url = "ETHEREUM_URL";
    let wallet_secret = "WALLET_SECRET";
    let chain_id: u64 = 5;

    let provider = Provider::<Http>::try_from(chain_url).unwrap();

    let wallet = wallet_secret
        .parse::<LocalWallet>()
        .unwrap()
        .with_chain_id(chain_id);

    let client = SignerMiddleware::new_with_provider_chain(provider, wallet.to_owned())
        .await
        .unwrap();

    let balance = client.get_balance(wallet.address(), None).await.unwrap();

    println!("balance wei: {}", balance);
}

Etherの送信

rust-web3

use secp256k1::SecretKey;
use std::str::FromStr;
use web3::signing::SecretKeyRef;
use web3::types::{Address, TransactionParameters, U256};
use web3::{transports, Web3};

pub async fn run() {
    let chain_url = "ETHEREUM_URL";
    let wallet_secret = "WALLET_SECRET";
    let to_address = "TO_ADDRESS";
    let send_wei: u128 = 1000000000000000000;

    let prev_key = SecretKey::from_str(&wallet_secret).unwrap();

    let transport = transports::Http::new(&chain_url)
        .ok()
        .expect("should set ethereum url");

    let cli = Web3::new(transport);

    let tx = TransactionParameters {
        to: Some(
            to_address
                .trim_start_matches("0x")
                .parse::<Address>()
                .unwrap(),
        ),
        value: U256::from(send_wei),
        gas: U256::from(8500000 as i64),
        gas_price: Some(U256::from(40000000000 as i64)),
        ..Default::default()
    };

    let signed = cli
        .accounts()
        .sign_transaction(tx, SecretKeyRef::from(&prev_key))
        .await
        .unwrap();

    let tx_hash = cli
        .eth()
        .send_raw_transaction(signed.raw_transaction)
        .await
        .unwrap();

    println!("sendEth: {:?}", tx_hash);
}

ethers-rs

use ethers::prelude::*;

pub async fn run() {
    let chain_url = "ETHEREUM_URL";
    let wallet_secret = "WALLET_SECRET";
    let chain_id: u64 = 5;
    let to_address = "TO_ADDRESS";
    let send_wei: u128 = 1000000000000000000;

    let provider = Provider::<Http>::try_from(chain_url).unwrap();

    let wallet = wallet_secret
        .parse::<LocalWallet>()
        .unwrap()
        .with_chain_id(chain_id);

    let client = SignerMiddleware::new_with_provider_chain(provider, wallet)
        .await
        .unwrap();

    let tx = TransactionRequest::new()
        .to(to_address.parse::<Address>().unwrap())
        .value(send_wei)
        .gas(8500000 as u64)
        .gas_price(40000000000 as u64)
        .chain_id(chain_id);

    let res = client.send_transaction(tx, None).await.unwrap();

    let receipt = res.confirmations(1).await.unwrap();

    println!("sendEth: {:?}", receipt);
}

コントラクトへのクエリ

rust-web3

use web3::contract::{Contract, Options};
use web3::transports::Http;
use web3::types::Address;
use web3::Web3;

pub async fn run() {
    let chain_url = "ETHEREUM_URL";
    let contract_address = "CONTRACT_ADDRESS";
    
    let transport = Http::new(&chain_url).ok().unwrap();

    let cli = Web3::new(transport);

    let contract = Contract::from_json(
        cli.eth(),
        contract_address
            .trim_start_matches("0x")
            .parse::<Address>()
            .unwrap(),
        include_bytes!("abi.json"),
    )
    .unwrap();

    let result: String = contract
        .query("METHOD_NAME", (), None, Options::default(), None)
        .await
        .unwrap();

    println!("{}", result);
}

resultの型は実際にコントラクトから返ってくる型に合わせます
引数が必要な場合は、メソッドネームの次に入れるタプル内にセットします

ethers-rs

use ethers::abi::Abi;
use ethers::contract::Contract;
use ethers::prelude::*;

pub async fn run() {
    let chain_url = "ETHEREUM_URL";
    let contract_address = "CONTRACT_ADDRESS";

    let provider = Provider::<Http>::try_from(chain_url).unwrap();

    let abi: Abi = serde_json::from_str(include_str!("abi.json")).unwrap();

    let contract = Contract::new(contract_address.parse::<Address>().unwrap(), abi, provider);

    let res = contract
        .method::<_, String>("METHOD_NAME", ())
        .unwrap()
        .call()
        .await
        .unwrap();

    println!("{}:", res);
}

method関数の第2型引数は実際にコントラクトから返ってくる型に合わせます
引数が必要な場合は、メソッドネームの次に入れるタプル内にセットします

コントラクトへのトランザクション

rust-web3

use secp256k1::SecretKey;
use std::str::FromStr;
use web3::contract::{Contract, Options};
use web3::signing::SecretKeyRef;
use web3::transports::Http;
use web3::types::Address;
use web3::types::U256;
use web3::Web3;

pub async fn run() {
    let chain_url = "ETHEREUM_URL";
    let wallet_secret = "WALLET_SECRET";
    let contract_address = "CONTRACT_ADDRESS";

    let transport = Http::new(&chain_url).ok().unwrap();

    let cli = Web3::new(transport);

    let contract = Contract::from_json(
        cli.eth(),
        contract_address
            .trim_start_matches("0x")
            .parse::<Address>()
            .unwrap(),
        include_bytes!("abi.json"),
    )
    .unwrap();

    let prev_key = SecretKey::from_str(wallet_secret).unwrap();

    let result = contract
        .signed_call_with_confirmations(
            "METHOD_NAME",
            (),
            Options::with(|opt| {
                opt.gas = Some(U256::from(8500000 as u64));
                opt.gas_price = Some(U256::from(40000000000 as u64));
            }),
            1,
            SecretKeyRef::from(&prev_key),
        )
        .await
        .unwrap();

    println!("tx id: {:?}", result.transaction_hash);
    println!("gas used: {:?}", result.gas_used.unwrap_or_default());
    println!("status: {:?}", result.status.unwrap_or_default());
}

引数が必要な場合は、メソッドネームの次に入れるタプル内にセットします

ethers-rs

use ethers::abi::Abi;
use ethers::contract::Contract;
use ethers::prelude::*;
use std::sync::Arc;

pub async fn run() {
    let chain_url = "ETHEREUM_URL";
    let chain_id: u64 = 5;
    let contract_address = "CONTRACT_ADDRESS";
    let wallet_secret = "WALLET_SECRET";

    let wallet = wallet_secret
        .parse::<LocalWallet>()
        .unwrap()
        .with_chain_id(chain_id);

    let provider = Provider::<Http>::try_from(chain_url).unwrap();

    let abi: Abi = serde_json::from_str(include_str!("abi.json")).unwrap();

    let client = SignerMiddleware::new_with_provider_chain(provider, wallet)
        .await
        .unwrap();
    let client = Arc::new(client);

    let contract =
        Contract::<SignerMiddleware<Provider<Http>, Wallet<k256::ecdsa::SigningKey>>>::new(
            contract_address.parse::<Address>().unwrap(),
            abi,
            client.clone(),
        );

    let call = contract
        .method::<_, H256>("METHOD_NAME", ())
        .unwrap()
        .gas(8500000 as u64)
        .gas_price(40000000000 as u64);

    let tx = call.send().await.unwrap();
    let receipt = tx.confirmations(1).await.unwrap();

    println!("{:?}", receipt);
}

引数が必要な場合は、メソッドネームの次に入れるタプル内にセットします

コントラクトのデプロイ

rust-web3

use secp256k1::SecretKey;
use std::str::FromStr;
use std::time;
use web3::contract::{Contract, Options};
use web3::signing::SecretKeyRef;
use web3::transports::Http;
use web3::types::U256;
use web3::Web3;

pub async fn run() {
    let chain_url = "ETHEREUM_URL";
    let wallet_secret = "WALLET_SECRET";
    let chain_id = 5;

    let transport = Http::new(&chain_url).ok().unwrap();

    let cli = Web3::new(transport);

    let prev_key = SecretKey::from_str(wallet_secret).unwrap();

    let contract = Contract::deploy(cli.eth(), include_bytes!("abi.json"))
        .unwrap()
        .confirmations(1)
        .poll_interval(time::Duration::from_secs(10))
        .options(Options::with(|opt| {
            opt.gas = Some(U256::from(8500000 as u64));
            opt.gas_price = Some(U256::from(40000000000 as u64));
        }))
        .sign_with_key_and_execute(
            include_str!("contract.bin").trim(),
            (),
            SecretKeyRef::from(&prev_key),
            Some(chain_id),
        )
        .await
        .unwrap();

    println!("deployed to: {:?}", contract.address());
}

ethers-rs

use ethers::abi::Abi;
use ethers::prelude::*;
use ethers::types::transaction::eip2718::TypedTransaction;
use std::str::FromStr;
use std::sync::Arc;

pub async fn run() {
    let chain_url = "ETHEREUM_URL";
    let chain_id: u64 = 5;
    let wallet_secret = "WALLET_SECRET";

    let wallet = wallet_secret
        .parse::<LocalWallet>()
        .unwrap()
        .with_chain_id(chain_id);

    let provider = Provider::<Http>::try_from(chain_url).unwrap();

    let abi: Abi = serde_json::from_str(include_str!("abi.json")).unwrap();

    let client = SignerMiddleware::new_with_provider_chain(provider, wallet)
        .await
        .unwrap();
    let client = Arc::new(client);

    let bytecode = include_str!("contract.bin");
    let factory = ContractFactory::new(abi, Bytes::from_str(bytecode).unwrap(), client.clone());

    let mut deployer = factory.deploy(()).unwrap();
    deployer.tx = TypedTransaction::Legacy(TransactionRequest {
        to: None,
        data: deployer.tx.data().cloned(),
        gas: Some(U256::from(8500000 as u64)),
        gas_price: Some(U256::from(40000000000 as u64)),
        ..Default::default()
    });

    let contract = deployer
        .confirmations(1 as usize)
        .legacy()
        .send()
        .await
        .unwrap();

    println!("deployed to: {:?}", contract.address());
}

Discussion