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()
);

}