用Rust来做以太坊开发5之事件日志及签名

文章目录

本系列文章主要是用Rustethers-rs来复刻《用Go来做以太坊开发》这本书本的内容,所以本系列文章的标题叫做《用Rust来做以太坊开发》, 因为原书写得足够好了,所以本系列更多的只是代码层面的复刻,不会说明太多相关的基础知识。

这次复刻《用Go来做以太坊开发》的第五章事件日志和第六章签名

账户这一章主要包括以下内容

  • 订阅事件日志
  • 读取事件日志
  • 读取ERC-20代币的事件日志
  • 读取0x Protocol事件日志
  • 生成和验证签名

往期文章:

本节会用到的合约代码

// 文件名:simple_contract.sol
// https://youerning.top/post/ethers-rs/totorial5
pragma solidity >=0.4.24;

contract SimpleStorage {

    event ValueChanged(address indexed author, string oldValue, string newValue);

    string _value;

    constructor(string memory value) public {
        emit ValueChanged(msg.sender, _value, value);
        _value = value;
    }

    function getValue() view public returns (string memory) {
        return _value;
    }

    function setValue(string memory value) public {
        emit ValueChanged(msg.sender, _value, value);
        _value = value;
    }
}

订阅事件日志

ethers-rsabigen宏除了生成与合约绑定的结构体之外,还会为每个事件生成一个以Filter结尾的结构体,比如下文的ValueChangedFilter

use ethers::prelude::*;
use ethers::types::Address;
use std::sync::Arc;

const RPC_URL: &str = "ws://127.0.0.1:8546";

abigen!(
    SimpleContract,
    "SimpleStorage.abi"
);


#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let provider = Provider::<Ws>::connect(RPC_URL).await?;
    
    const CONTRACT_ADDRESS: &str = "0x73511669fd4de447fed18bb79bafeac93ab7f31f";

    let contract_address: Address = CONTRACT_ADDRESS.parse()?;
    let client = Arc::new(provider);
    let contract = SimpleContract::new(contract_address, client);

    println!("合约设置的值: {:?}", contract.get_value().call().await?);
    let events = contract.events();
    let mut stream = events.stream().await?;

    println!("开始监听....");
    while let Some(Ok(evt)) = stream.next().await {
        println!("{evt:?}");
        // 因为只有一种类型的事件,match看起来有点鸡肋
        match evt {
            ValueChangedFilter { author, old_value, new_value } => println!("{author:?} {old_value:?} {new_value:?}"),
        }
    }

    Ok(())
}

读取事件日志

go-ethereum不同的是,ethers-rs并不需要额外的处理,直接使用即可,就像上面代码那样。

读取ERC-20代币的事件日志

ERC-20跟普通事件并没有本质的区别,有以下两种方法可以参考

use ethers::{
    core::{
        abi::AbiDecode,
        types::{Address, Filter, U256},
    },
    providers::{Middleware, Provider, StreamExt, Ws},
};
use eyre::Result;
use std::sync::Arc;

const RPC_URL: &str = "ws://127.0.0.1:8546";

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let provider = Provider::<Ws>::connect(RPC_URL).await?;
    let client = Arc::new(provider);

    let erc20_transfer_filter =
        Filter::new().event("Transfer(address,address,uint256)");

    let mut stream = client.subscribe_logs(&erc20_transfer_filter).await?.take(2);

    println!("开始监听转账事件...");
    while let Some(log) = stream.next().await {
        println!(
            "block: {:?}, tx: {:?}, token: {:?}, from: {:?}, to: {:?}, amount: {:?}",
            log.block_number,
            log.transaction_hash,
            log.address,
            Address::from(log.topics[1]),
            Address::from(log.topics[2]),
            U256::decode(log.data)
        );
    }

    Ok(())
}

这种方法对于单个事件比较简单,不需要额外的用宏生成代码。

下面的代码使用宏,直接复制的官方示例。

use ethers::{
    contract::abigen,
    core::types::Address,
    providers::{Provider, StreamExt, Ws},
};
use eyre::Result;
use std::sync::Arc;

abigen!(
    IERC20,
    r#"[
        event Transfer(address indexed from, address indexed to, uint256 value)
        event Approval(address indexed owner, address indexed spender, uint256 value)
    ]"#,
);

const WSS_URL: &str = "wss://mainnet.infura.io/ws/v3/c60b0bb42f8a4c6481ecd229eddaca27";
const WETH_ADDRESS: &str = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";

#[tokio::main]
async fn main() -> Result<()> {
    // 监听方法一般使用websocket协议
    let provider = Provider::<Ws>::connect(WSS_URL).await?;
    let client = Arc::new(provider);
    let address: Address = WETH_ADDRESS.parse()?;
    let contract = IERC20::new(address, client);

    listen_all_events(&contract).await?;
    listen_specific_events(&contract).await?;

    Ok(())
}

// 监听所有事件
async fn listen_all_events(contract: &IERC20<Provider<Ws>>) -> Result<()> {
    let events = contract.events().from_block(16232696);
    let mut stream = events.stream().await?.take(1);

    while let Some(Ok(evt)) = stream.next().await {
        match evt {
            IERC20Events::ApprovalFilter(f) => println!("{f:?}"),
            IERC20Events::TransferFilter(f) => println!("{f:?}"),
        }
    }

    Ok(())
}

/// 监听单一事件
async fn listen_specific_events(contract: &IERC20<Provider<Ws>>) -> Result<()> {
    let events = contract.event::<ApprovalFilter>().from_block(16232696);
    let mut stream = events.stream().await?.take(1);

    while let Some(Ok(f)) = stream.next().await {
        println!("ApprovalFilter event: {f:?}");
    }

    Ok(())
}

读取0x Protocol事件日志

不想写了,这个协议没研究过。

生成和验证签名

不懂为啥生成和验证要分两节写,这里直接复制ethers-rs的示例代码了。

use eyre::Result;
use ethers::core::rand::thread_rng;
use ethers::signers::{LocalWallet, Signer};

#[tokio::main]
async fn main() -> Result<()> {
    // 创建随机钱包
    let wallet = LocalWallet::new(&mut thread_rng());

    // 说明要签名的数据
    let message = "Some data";

    // 生成签名对象
    let signature = wallet.sign_message(message).await?;
    println!("Produced signature {signature}");

    // 通过公钥验证
    signature.verify(message, wallet.address()).unwrap();
    println!("Verified signature produced by {:?}!", wallet.address());

    Ok(())
}

总结

至此,我感觉本系列的代码足够以太坊开发了(也足够我以后自己回来抄代码了),所以后面关于swarmwhisper就不贴rust的实现了,本系列完。

不过ethers-rs要被弃用了,实在是让人难过T_T。

参考链接