用Rust来做以太坊开发5之事件日志及签名
文章目录
本系列文章主要是用Rust
的ethers-rs
来复刻《用Go来做以太坊开发》这本书本的内容,所以本系列文章的标题叫做《用Rust来做以太坊开发》, 因为原书写得足够好了,所以本系列更多的只是代码层面的复刻,不会说明太多相关的基础知识。
这次复刻《用Go来做以太坊开发》的第五章事件日志和第六章签名
账户这一章主要包括以下内容
- 订阅事件日志
- 读取事件日志
- 读取ERC-20代币的事件日志
- 读取0x Protocol事件日志
- 生成和验证签名
往期文章:
- https://youerning.top/post/ethers-rs/totorial-1
- https://youerning.top/post/ethers-rs/totorial-2
- https://youerning.top/post/ethers-rs/totorial-3
- https://youerning.top/post/ethers-rs/totorial-4
本节会用到的合约代码
// 文件名: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-rs
的abigen
宏除了生成与合约绑定的结构体之外,还会为每个事件生成一个以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(())
}
总结
至此,我感觉本系列的代码足够以太坊开发了(也足够我以后自己回来抄代码了),所以后面关于swarm
和whisper
就不贴rust的实现了,本系列完。
不过ethers-rs
要被弃用了,实在是让人难过T_T。