Introducing Alloy: Fast, battle-tested and well-documented building blocks for Ethereum, in Rust

06.23.2023|Georgios KonstantopoulosJames PrestwichDaniPopes

Introduction

In 2020, we saw a shortfall in the performance and reliability of off-chain Ethereum infrastructure. To address this, we (gakonst & prestwich) started building in the Rust Ethereum ecosystem.

At the time, most Ethereum off-chain actors were written in Python or Javascript. These actors included MEV bots for liquidation and arbitrage (sandwiches were not that popular back then) and indexers for ETLing Ethereum events into structured data. Most codebases (open source or not) were at best PoC-level mature and could not provide the scale or reliability that crypto deserves.

We’ve come a long way since then. Using Parity’s Ethereum Types and Ethabi, we built ethers-rs, a Rust re-interpretation of the popular ethers.js library. Today, ethers-rs is the premier Rust tooling for the entire EVM ecosystem. It has a core team of dedicated maintainers, over 200 contributors, and covers everything from RPC to the Solidity compiler.

Ethers-rs is now is a staple in Rust Ethereum codebases, used by:

For the last 3 years, ethers-rs has provided strongly-typed, well-documented, tested and efficient abstractions for submitting transactions, subscribing and transforming chain data, and interacting with smart contracts. The Rust Ethereum community flourished over this time period, and we are proud of the work we’ve done to enable that. Ethers-rs far outgrew our expectations.

However, like all software, maintenance is a constant battle. Ethers-rs was our first foray into Rust in production, and it has been a learning experience for everyone involved. Over time, we merged code we should have been more thoughtful about, accumulated tech debt, and entrenched suboptimal abstractions in many codebases due to our mistakes.

We recently started a concentrated effort to address these growing pains. In this post we will share our progress so far, as well as our plans for the Rust Ethereum ecosystem for the rest of this year.

ethers-rs is now alloy

Firstly, we are rebranding.

Ethers is now called Alloy and lives under a new Github Organization: github.com/alloy-rs. Current versions of ethers will continue to live in gakonst/ethers-rs, and be otherwise in maintenance mode for v2. Future major versions will use the new name and organization.

Ethers-rs was built for stability, to be used as a well-tested and robust foundation of composable pieces to build powerful applications. We believe that Alloy communicates that well.

The project has grown enough that we felt the need to have a unique identity, which communicates our values, and allows us to be consistent about them, while also avoiding brand confusion with ricmoo’s popular ethers.js, which we’re thankful for the inspiration in the early iterations of ethers-rs.

What does Alloy include?

We also rewrote our stack from scratch. Today, we’re excited to announce alloy-rs/core, a rewrite of the popular ethers-core package and the ethabi crate. Core offers:

  • Alloy Primitives: We provide new fundamental types for hashes, fixed-byte arrays, and signed and unsigned integers (built on Remco’s uint library). These types implement common traits sanely (no more truncated hashes like “0x1234…5678”), aim for no_std compatibility, and provide common helper functions for usage in applications.
  • Alloy RLP: A canonical RLP implementation.
  • Alloy RPC Types: All the RPC types when talking to a chain are available using our newly defined types.
  • Syn Solidity: A syn-powered Solidity parser, specifically designed for Rust procedural macros. It aims to mimic the behavior of the official Solidity compiler (Solc) when it comes to parsing valid Solidity code. This is a powerful abstraction which we leverage for building performant compile-time functionalities, like static ABI coders (see below).
  • Alloy Solidity Types: A compile-time representation of Ethereum's type system with ABI and EIP-712 support. This allows for clean UX and high encoding/decoding speed without redundant layers of abstraction. This static abi encoder is built on a robust representation of Solidity’s type system in Rust. Users access it via the sol! procedural macro, which parses Solidity snippets to generate native Rust code. The static encoder benches at 2-3x faster (!!) than current Rust implementations which are 1+ order of magnitude faster than other languages.

Here is a low-level example of how you can use these new primitives:

use alloy_primitives::Address;
use alloy_sol_types::{sol, SolType};

// Type definition: generates a new struct that implements `SolType`
sol! {
    type MyType is uint256;
}

// Type aliases
type B32 = sol! { bytes32 };
// This is equivalent to the following:
// type B32 = alloy_sol_types::sol_data::Bytes<32>;

type SolArrayOf<T> = sol! { T[] };
type SolTuple = sol! { tuple(address, bytes, string) };

let _ = <sol!(bool)>::encode_single(&true);
let _ = B32::encode_single(&[0; 32]);
let _ = SolArrayOf::<sol!(bool)>::encode_single(&vec![true, false]);
let _ = SolTuple::encode_single(&(Address::ZERO, vec![0; 32], "hello".to_string()));

Here's a higher-level example, showing a roundtrip ABI encoding of an ERC20 transfer:

use alloy_primitives::{Address, U256};
use alloy_sol_types::{sol, SolCall};
use hex_literal::hex;

sol! {
    #[derive(Debug, PartialEq)]
    interface IERC20 {
        function transfer(address to, uint256 amount) external returns (bool);
    }
}

// random mainnet ERC20 transfer
// https://etherscan.io/tx/0x947332ff624b5092fb92e8f02cdbb8a50314e861a4b39c29a286b3b75432165e
let data = hex!(
    "a9059cbb"
    "0000000000000000000000008bc47be1e3abbaba182069c89d08a61fa6c2b292"
    "0000000000000000000000000000000000000000000000000000000253c51700"
);
let expected = IERC20::transferCall {
    to: Address::from(hex!("8bc47be1e3abbaba182069c89d08a61fa6c2b292")),
    amount: U256::from(9995360000_u64),
};

assert_eq!(data[..4], IERC20::transferCall::SELECTOR);
let decoded = IERC20::IERC20Calls::decode(&data, true).unwrap();
assert_eq!(decoded, IERC20::IERC20Calls::transfer(expected));
assert_eq!(decoded.encode(), data);

Alloy is really powerful!

These crates will act as the solid foundation we’ve always wanted for Ethereum in Rust, informed by the lessons from our last 3 years of Rust Ethereum engineering. The code for these crates is under active development on github. We love new contributors. The pre-1.0.0 version of these crates are available on crates.io, and docs.rs.

Refactoring the Rust Ethereum ecosystem

In 2020 with ethers-rs, we made a decision to re-use Parity’s existing type libraries. These types are deeply embedded in the codebase and exposed to dependencies via U256, H256, and other commonly-used structs. Over time Rust has improved faster than Parity’s implementations could keep up.

Reth and Revm did not use Parity types, choosing modern Rust equivalents instead. Other Paradigm-supported projects like Foundry or Artemis have not migrated yet. Reth and revm are incompatible by default with Foundry and ethers-rs. This creates a gap in the ecosystem, requiring extreme amounts of type conversion at the boundary.

To address this, we will be migrating the Rust Ethereum ecosystem built on our libraries to shared types. These types will live in alloy-rs/core, and will reuse the excellent work in Remco’s ruint library. We will phase out the Parity types. The alloy-types library will be the root of the Rust Ethereum ecosystem. This migration will take place over the next 6 months.

Going forward, alloy-rs/core will form the shared base for all our Rust Ethereum projects.

To be clear, there is no action needed from developers right now. Ethers-rs will continue to be available under the same great name in the same great repo for the foreseeable future. We will publish migration plans in advance. Until we have more integrations in the rest of the ecosystem, we encourage developers to try out alloy in their low-level projects and share their feedback on the abstractions, documentation and performance.

Conclusion

Using our learnings from the last 3 years of building in the Rust Ethereum ecosystem, we are excited to release Alloy, our rewrite of ethers-rs with exciting new features, documentation, and a new brand.

As part of that, we are excited to be also formalizing maintainership of ethers-rs/alloy as James Prestwich, Georgios Konstantopoulos and DaniPopes.

These upgrades will pay down a lot of tech debt, remove weird type conversions, improve performance across the board, and overall set us up for the next 5 years of high-assurance Rust Ethereum engineering.

If you are excited about contributing, please reach out to georgios@paradigm.xyz or james@prestwi.ch. You can find links to all the support and development chatrooms in the Alloy Core README.

Authors' note: Would like to take a moment to shout out Dani for his extremely high quality work on this project. We have been talking about rewriting ethers-rs for a long time, and they drove big parts of the project from beginning to where we are today. Big kudos.

See you on Github.

Disclaimer: This post is for general information purposes only. It does not constitute investment advice or a recommendation or solicitation to buy or sell any investment and should not be used in the evaluation of the merits of making any investment decision. It should not be relied upon for accounting, legal or tax advice or investment recommendations. This post reflects the current opinions of the authors and is not made on behalf of Paradigm or its affiliates and does not necessarily reflect the opinions of Paradigm, its affiliates or individuals associated with Paradigm. The opinions reflected herein are subject to change without being updated.

Copyright © 2024 Paradigm Operations LP All rights reserved. “Paradigm” is a trademark, and the triangular mobius symbol is a registered trademark of Paradigm Operations LP