Introduction

Welcome to the Rust Ethereum book, where we'll go through examples of how to interact with Ethereum using ethers-rs.

We assume familiarity with Ethereum's mechanics such as:

  • Deploying a contract & interacting with it
  • Sending funds across accounts
  • Signing messages and recovering their sender

Installing Ethers-rs

First create a new Rust project:

$ cargo new ethers-hello-world && cd ethers-hello-world

Edit your Cargo.toml and insert the following dependency:

[dependencies]

ethers = { git = "https://github.com/gakonst/ethers-rs" }

Tests require the following installed:

  1. solc. We also recommend using solc-select for more flexibility.
  2. ganache-cli

In addition, it is recommended that you set the ETHERSCAN_API_KEY environment variable for the abigen via Etherscan tests. You can get one here.

Getting Help

First, see if the answer to your question can be easily found here or in the API documentation . If the answer is not there, try opening an issue with the question.

Join the ethers-rs telegram to chat with the community!

Overview

Using ethers-rs

ethers-rs provides extensive tooling to generate type safe bindings for smart contracts. Under the hood, it relies on the ethabi crate's data model for contracts and all core encoding/decoding related traits. ethers-rs provides additional types, traits and macros that help with

ABI Encoding & Decoding

The solidity docs cover the contract ABI specification at great length. ethabi provides the Token enum which represents the fixed set of ABI types,


#![allow(unused)]
fn main() {
pub enum Token {
    Address(Address),
    FixedBytes(FixedBytes),
    Bytes(Bytes),
    Int(Int),
    Uint(Uint),
    Bool(bool),
    String(String),
    FixedArray(Vec<Token>),
    Array(Vec<Token>),
    Tuple(Vec<Token>),
}
}

An encoded payload is always represented as Vec<Token>, a series of encoded values:


#![allow(unused)]
fn main() {
pub fn encode(tokens: &[Token]) -> Bytes { ... }
}

To make this generally applicable, ethers-rs provides additional helper traits for tokenizing (encoding) and detokenizable (decoding), most importantly Tokenizable. All primitive types and commonly used data structures, such as tuples are Tokenizable.

Implement ABI encoding/decoding

Contract function input can be very verbose and can consist of quite a few parameters, so it may be convenient to bundle them into user-defined data that also supports ABI compliant encoding/decoding.

A custom ABI type might look like this:


#![allow(unused)]
fn main() {
#[derive(Debug, Clone)]
struct ValueChanged {
    old_author: Address,
    new_author: Address,
    old_value: String,
    new_value: String,
}
}

Implement Tokenizable manually

In order to make custom types tokenizable you'd need to implement the Tokenizable trait:


#![allow(unused)]

fn main() {
impl ethers_core::abi::Tokenizable for ValueChanged
{
    /// Tries to convert a `Token` into an instance of the type 
    fn from_token(
        token: ethers_core::abi::Token,
    ) -> Result<Self, ethers_core::abi::InvalidOutputType>
        where
            Self: Sized,
    {
        if let ethers_core::abi::Token::Tuple(tokens) = token {
            // -- snip --
        }
    }

    /// Encodes the type into a `Token`
    fn into_token(self) -> ethers_core::abi::Token {
        ethers_core::abi::Token::Tuple(vec![
            self.old_author.into_token(),
            self.new_author.into_token(),
            self.old_value.into_token(),
            self.new_value.into_token(),
        ])
    }
}
}

Implement Tokenizable using derive macro EthAbiType

Implementing Tokenizable can be very verbose itself and error-prone, therefore ethers-rs provides the EthAbiType derive proc macro that automatically generates all the necessary from and into token code.

By adding a EthAbiType the code above will be automatically generated


#![allow(unused)]
fn main() {
use ethers::abi::EthAbiType;
use ethers::types::*;

#[derive(Debug, Clone, EthAbiType)]
struct ValueChanged {
    old_author: Address,
    new_author: Address,
    old_value: String,
    new_value: String,
}
}

This will work for every type that is Tokenizable, so it's possible to nest them:


#![allow(unused)]
fn main() {
#[derive(Debug, Clone, EthAbiType)]
struct ValueChangedOuter {
    inner: ValueChanged,
    msg: String,
}
}

Implement Event bindings

Ethereum Events (aka logs) work similarly, but their encoding is dependent on their indexed parameters, see solidity docs for more details.

In ethers-rs Event types are abstracted via the EthEvent trait, which provides necessary functions to identify and decode events.

To easily create event bindings, ethers-rs provides the EthEvent as derive proc macro as well.

In order to turn a custom type into an EthEvent you only need to add it as derive:


#![allow(unused)]
fn main() {
use ethers::abi::EthEvent;

#[derive(Debug, Clone, EthEvent)]
struct ValueChangedEvent {
    old_author: Address,
    new_author: Address,
    old_value: String,
    new_value: String,
}

assert_eq!(
    "ValueChangedEvent(address,address,string,string)",
    ValueChangedEvent::abi_signature()
);
}

An event can be further customized via the ethevent attribute.

Rename the event


#![allow(unused)]
fn main() {
#[derive(Debug, Clone, EthEvent)]
#[ethevent(
name = "MyEvent",
)]
struct ValueChangedEvent {
    old_author: Address,
    new_author: Address,
    old_value: String,
    new_value: String,
}

assert_eq!(
    "MyEvent(address,address,string,string)",
    ValueChangedEvent::abi_signature()
);
}

Mark fields as indexed


#![allow(unused)]
fn main() {
#[derive(Debug, Clone, EthEvent)]
struct ValueChangedEvent {
    old_author: Address,
    #[ethevent(indexed, name = "newAuthor")]
    new_author: Address,
    old_value: String,
    new_value: String,
}

assert_eq!(
    "ValueChangedEvent(address,address,string,string)",
    ValueChangedEvent::abi_signature()
);
}

Mark event as anonymous


#![allow(unused)]
fn main() {
#[derive(Debug, Clone, EthEvent)]
#[ethevent(anonymous)]
struct ValueChangedEvent {
    old_author: Address,
    new_author: Address,
    old_value: String,
    new_value: String,
}

assert_eq!(
    "ValueChangedEvent(address,address,string,string) anonymous",
    ValueChangedEvent::abi_signature()
);
}

Rename and override the ABI as well


#![allow(unused)]

fn main() {
#[derive(Debug, Clone, EthAbiType)]
struct SomeType {
    inner: Address,
    msg: String,
}

#[ethevent(
    name = "MyEvent",
    abi = "MyEvent(address,(address,string),string)"
)]
struct ValueChangedEvent {
    old_author: Address,
    val: SomeType,
    new_value: String,
}

assert_eq!(
    "MyEvent(address,(address,string),string)",
    ValueChangedEvent::abi_signature()
);

}

Contract Bindings with abigen!

Examples

WASM Support

Feature flags