diff --git a/README.md b/README.md index 7fbfb3e1..c3482271 100644 --- a/README.md +++ b/README.md @@ -88,3 +88,12 @@ This library would not have been possibly without the great work done in: A lot of the code was inspired and adapted from them, to a unified and opinionated interface, built with async/await and std futures from the ground up. + +## Projects using ethers-rs + +- [Yield Liquidator](https://github.com/yieldprotocol/yield-liquidator/): Liquidator for Yield Protocol +- [MEV Inspect](https://github.com/flashbots/mev-inspect-rs/): Miner Extractable Value inspector +- [Ethers Fireblocks](https://github.com/gakonst/ethers-fireblocks): Ethers middleware and signer for [Fireblocks](https://fireblocks.io)' API +- [Celo Threshold BLS DKG](https://github.com/celo-org/celo-threshold-bls-rs/): CLI for using Celo as a data availability network for the Joint-Feldman BLS DKG +- [Celo Plumo Prover](https://github.com/celo-org/plumo-prover): Creates Celo's ultralight client proof from on-chain data +- [Celo SNARK Setup Coordinator](https://github.com/celo-org/snark-setup-operator): Coordinator for executing a pipelined Groth16 SNARK setup diff --git a/ethers-contract/src/base.rs b/ethers-contract/src/base.rs index 05d826d7..f030e507 100644 --- a/ethers-contract/src/base.rs +++ b/ethers-contract/src/base.rs @@ -53,7 +53,7 @@ impl BaseContract { /// versions, consider using `encode_with_selector` pub fn encode(&self, name: &str, args: T) -> Result { let function = self.abi.function(name)?; - encode_fn(function, args) + encode_function_data(function, args) } /// Returns the ABI encoded data for the provided function selector and arguments @@ -63,7 +63,7 @@ impl BaseContract { args: T, ) -> Result { let function = self.get_from_signature(signature)?; - encode_fn(function, args) + encode_function_data(function, args) } /// Decodes the provided ABI encoded function arguments with the selected function name. @@ -76,7 +76,7 @@ impl BaseContract { bytes: T, ) -> Result { let function = self.abi.function(name)?; - decode_fn(function, bytes, true) + decode_function_data(function, bytes, true) } /// Decodes for a given event name, given the `log.topics` and @@ -98,7 +98,7 @@ impl BaseContract { bytes: T, ) -> Result { let function = self.get_from_signature(signature)?; - decode_fn(function, bytes, true) + decode_function_data(function, bytes, true) } fn get_from_signature(&self, signature: Selector) -> Result<&Function, AbiError> { @@ -147,14 +147,14 @@ pub(crate) fn decode_event( Ok(D::from_tokens(tokens)?) } -// Helper for encoding arguments for a specific function -pub(crate) fn encode_fn(function: &Function, args: T) -> Result { +/// Helper for ABI encoding arguments for a specific function +pub fn encode_function_data(function: &Function, args: T) -> Result { let tokens = args.into_tokens(); Ok(function.encode_input(&tokens).map(Into::into)?) } -// Helper for decoding bytes from a specific function -pub fn decode_fn>( +/// Helper for ABI decoding raw data based on a function's input or output. +pub fn decode_function_data>( function: &Function, bytes: T, is_input: bool, diff --git a/ethers-contract/src/call.rs b/ethers-contract/src/call.rs index 29a0d2ad..cbaf1508 100644 --- a/ethers-contract/src/call.rs +++ b/ethers-contract/src/call.rs @@ -1,4 +1,4 @@ -use super::base::{decode_fn, AbiError}; +use super::base::{decode_function_data, AbiError}; use ethers_core::{ abi::{Detokenize, Function, InvalidOutputType}, types::{Address, BlockNumber, Bytes, TransactionRequest, U256}, @@ -120,7 +120,7 @@ where .map_err(ContractError::MiddlewareError)?; // decode output - let data = decode_fn(&self.function, &bytes, false)?; + let data = decode_function_data(&self.function, &bytes, false)?; Ok(data) } diff --git a/ethers-contract/src/contract.rs b/ethers-contract/src/contract.rs index d96c3871..bbadfe57 100644 --- a/ethers-contract/src/contract.rs +++ b/ethers-contract/src/contract.rs @@ -1,5 +1,5 @@ use super::{ - base::{encode_fn, AbiError, BaseContract}, + base::{encode_function_data, AbiError, BaseContract}, call::ContractCall, event::Event, }; @@ -227,7 +227,7 @@ impl Contract { function: &Function, args: T, ) -> Result, AbiError> { - let data = encode_fn(function, args)?; + let data = encode_function_data(function, args)?; // create the tx object let tx = TransactionRequest { diff --git a/ethers-contract/src/event.rs b/ethers-contract/src/event.rs index 3e53b60b..15a72116 100644 --- a/ethers-contract/src/event.rs +++ b/ethers-contract/src/event.rs @@ -1,4 +1,4 @@ -use crate::{base::decode_event, ContractError, EventStream}; +use crate::{base::decode_event, stream::EventStream, ContractError}; use ethers_core::{ abi::{Detokenize, Event as AbiEvent}, diff --git a/ethers-contract/src/lib.rs b/ethers-contract/src/lib.rs index 423d32a5..02af00f4 100644 --- a/ethers-contract/src/lib.rs +++ b/ethers-contract/src/lib.rs @@ -17,7 +17,7 @@ mod contract; pub use contract::Contract; mod base; -pub use base::{decode_fn, BaseContract}; +pub use base::{decode_function_data, encode_function_data, BaseContract}; mod call; pub use call::ContractError; @@ -28,7 +28,6 @@ pub use factory::ContractFactory; mod event; mod stream; -pub use stream::EventStream; mod multicall; pub use multicall::Multicall; diff --git a/ethers-middleware/src/gas_oracle/eth_gas_station.rs b/ethers-middleware/src/gas_oracle/eth_gas_station.rs index de87a2c1..6c7bc230 100644 --- a/ethers-middleware/src/gas_oracle/eth_gas_station.rs +++ b/ethers-middleware/src/gas_oracle/eth_gas_station.rs @@ -28,6 +28,7 @@ struct EthGasStationResponse { } impl EthGasStation { + /// Creates a new [EthGasStation](https://docs.ethgasstation.info/) gas oracle pub fn new(api_key: Option<&'static str>) -> Self { let url = match api_key { Some(key) => format!("{}?api-key={}", ETH_GAS_STATION_URL_PREFIX, key), @@ -43,6 +44,7 @@ impl EthGasStation { } } + /// Sets the gas price category to be used when fetching the gas price. pub fn category(mut self, gas_category: GasCategory) -> Self { self.gas_category = gas_category; self diff --git a/ethers-middleware/src/gas_oracle/etherchain.rs b/ethers-middleware/src/gas_oracle/etherchain.rs index 79e38600..74057f05 100644 --- a/ethers-middleware/src/gas_oracle/etherchain.rs +++ b/ethers-middleware/src/gas_oracle/etherchain.rs @@ -39,6 +39,7 @@ struct EtherchainResponse { } impl Etherchain { + /// Creates a new [Etherchain](https://etherchain.org/tools/gasPriceOracle) gas price oracle. pub fn new() -> Self { let url = Url::parse(ETHERCHAIN_URL).expect("invalid url"); @@ -49,6 +50,7 @@ impl Etherchain { } } + /// Sets the gas price category to be used when fetching the gas price. pub fn category(mut self, gas_category: GasCategory) -> Self { self.gas_category = gas_category; self diff --git a/ethers-middleware/src/gas_oracle/etherscan.rs b/ethers-middleware/src/gas_oracle/etherscan.rs index ba142121..d7ce2d17 100644 --- a/ethers-middleware/src/gas_oracle/etherscan.rs +++ b/ethers-middleware/src/gas_oracle/etherscan.rs @@ -39,6 +39,7 @@ struct EtherscanResponseInner { } impl Etherscan { + /// Creates a new [Etherscan](https://etherscan.io/gastracker) gas price oracle. pub fn new(api_key: Option<&str>) -> Self { let url = match api_key { Some(key) => format!("{}&apikey={}", ETHERSCAN_URL_PREFIX, key), @@ -54,6 +55,7 @@ impl Etherscan { } } + /// Sets the gas price category to be used when fetching the gas price. pub fn category(mut self, gas_category: GasCategory) -> Self { self.gas_category = gas_category; self diff --git a/ethers-middleware/src/gas_oracle/gas_now.rs b/ethers-middleware/src/gas_oracle/gas_now.rs index e878d56d..bd6f3cf9 100644 --- a/ethers-middleware/src/gas_oracle/gas_now.rs +++ b/ethers-middleware/src/gas_oracle/gas_now.rs @@ -40,6 +40,7 @@ struct GasNowResponseInner { } impl GasNow { + /// Creates a new [GasNow](https://gasnow.org) gas price oracle. pub fn new() -> Self { let url = Url::parse(GAS_NOW_URL).expect("invalid url"); @@ -50,6 +51,7 @@ impl GasNow { } } + /// Sets the gas price category to be used when fetching the gas price. pub fn category(mut self, gas_category: GasCategory) -> Self { self.gas_category = gas_category; self diff --git a/ethers-middleware/src/lib.rs b/ethers-middleware/src/lib.rs index f976c199..936f5069 100644 --- a/ethers-middleware/src/lib.rs +++ b/ethers-middleware/src/lib.rs @@ -1,14 +1,18 @@ //! # Ethers Middleware //! -//! Ethers uses a middleware architecture. You start the middleware stack with +//! Ethers uses a middleware-based architecture. You start the middleware stack with //! a [`Provider`](ethers_providers::Provider), and wrap it with additional //! middleware functionalities that you need. //! //! ## Available Middleware -//! - Signer -//! - Nonce Manager -//! - Gas Escalator -//! - Gas Oracle +//! - [`Signer`](crate::SignerMiddleware): Signs transactions locally, with a private +//! key or a hardware wallet +//! - [`Nonce Manager`](crate::NonceManagerMiddleware): Manages nonces locally, allowing +//! the rapid broadcast of transactions without having to wait for them to be submitted +//! - [`Gas Escalator`](crate::gas_escalator::GasEscalatorMiddleware): Bumps transaction +//! gas prices in the background +//! - [`Gas Oracle`](crate::gas_oracle): Allows getting your gas price estimates from +//! places other than `eth_gasPrice`. //! //! ## Example of a middleware stack //! @@ -49,19 +53,22 @@ //! // ... do something with the provider //! ``` -/// The gas escalator middleware is used to re-broadcast transactions with an -/// increasing gas price to guarantee their timely inclusion +/// The [Gas Escalator middleware](crate::gas_escalator::GasEscalatorMiddleware) +/// is used to re-broadcast transactions with an increasing gas price to guarantee +/// their timely inclusion. pub mod gas_escalator; /// The gas oracle middleware is used to get the gas price from a list of gas oracles -/// instead of using eth_gasPrice +/// instead of using eth_gasPrice. For usage examples, refer to the +/// [`GasOracle`](crate::gas_oracle::GasOracle) trait. pub mod gas_oracle; -/// The nonce manager middleware is used to locally calculate nonces instead of +/// The [Nonce Manager](crate::NonceManagerMiddleware) is used to locally calculate nonces instead of /// using eth_getTransactionCount pub mod nonce_manager; +pub use nonce_manager::NonceManagerMiddleware; -/// The signer middleware is used to locally sign transactions and messages +/// The [Signer](crate::SignerMiddleware) is used to locally sign transactions and messages /// instead of using eth_sendTransaction and eth_sign pub mod signer; pub use signer::SignerMiddleware; diff --git a/ethers-middleware/src/nonce_manager.rs b/ethers-middleware/src/nonce_manager.rs index 361f7880..a7c18181 100644 --- a/ethers-middleware/src/nonce_manager.rs +++ b/ethers-middleware/src/nonce_manager.rs @@ -8,17 +8,18 @@ use thiserror::Error; /// Middleware used for calculating nonces locally, useful for signing multiple /// consecutive transactions without waiting for them to hit the mempool pub struct NonceManagerMiddleware { - pub inner: M, - pub initialized: AtomicBool, - pub nonce: AtomicU64, - pub address: Address, + inner: M, + initialized: AtomicBool, + nonce: AtomicU64, + address: Address, } impl NonceManagerMiddleware where M: Middleware, { - /// Instantiates the nonce manager with a 0 nonce. + /// Instantiates the nonce manager with a 0 nonce. The `address` should be the + /// address which you'll be sending transactions from pub fn new(inner: M, address: Address) -> Self { Self { initialized: false.into(), diff --git a/ethers-providers/src/lib.rs b/ethers-providers/src/lib.rs index 26a93efd..c75d477e 100644 --- a/ethers-providers/src/lib.rs +++ b/ethers-providers/src/lib.rs @@ -113,13 +113,70 @@ pub trait FromErr { #[async_trait] #[auto_impl(&, Box, Arc)] +/// A middleware allows customizing requests send and received from an ethereum node. +/// +/// Writing a middleware is as simple as: +/// 1. implementing the [`inner`](crate::Middleware::inner) method to point to the next layer in the "middleware onion", +/// 2. implementing the [`FromErr`](crate::FromErr) trait on your middleware's error type +/// 3. implementing any of the methods you want to override +/// +/// ```rust +/// use ethers::{providers::{Middleware, FromErr}, types::{U64, TransactionRequest, U256}}; +/// use thiserror::Error; +/// use async_trait::async_trait; +/// +/// #[derive(Debug)] +/// struct MyMiddleware(M); +/// +/// #[derive(Error, Debug)] +/// pub enum MyError { +/// #[error("{0}")] +/// MiddlewareError(M::Error), +/// +/// // Add your middleware's specific errors here +/// } +/// +/// impl FromErr for MyError { +/// fn from(src: M::Error) -> MyError { +/// MyError::MiddlewareError(src) +/// } +/// } +/// +/// #[async_trait] +/// impl Middleware for MyMiddleware +/// where +/// M: Middleware, +/// { +/// type Error = MyError; +/// type Provider = M::Provider; +/// type Inner = M; +/// +/// fn inner(&self) -> &M { +/// &self.0 +/// } +/// +/// /// Overrides the default `get_block_number` method to always return 0 +/// async fn get_block_number(&self) -> Result { +/// Ok(U64::zero()) +/// } +/// +/// /// Overrides the default `estimate_gas` method to log that it was called, +/// /// before forwarding the call to the next layer. +/// async fn estimate_gas(&self, tx: &TransactionRequest) -> Result { +/// println!("Estimating gas..."); +/// self.inner().estimate_gas(tx).await.map_err(FromErr::from) +/// } +/// } +/// ``` pub trait Middleware: Sync + Send + Debug { type Error: Sync + Send + Error + FromErr<::Error>; type Provider: JsonRpcClient; type Inner: Middleware; + /// The next middleware in the stack fn inner(&self) -> &Self::Inner; + /// The HTTP or Websocket provider. fn provider(&self) -> &Provider { self.inner().provider() } diff --git a/ethers-providers/src/pubsub.rs b/ethers-providers/src/pubsub.rs index 9b449bf9..fbd1e13b 100644 --- a/ethers-providers/src/pubsub.rs +++ b/ethers-providers/src/pubsub.rs @@ -26,6 +26,7 @@ pub trait PubsubClient: JsonRpcClient { #[must_use = "subscriptions do nothing unless you stream them"] #[pin_project(PinnedDrop)] +/// Streams data from an installed filter via `eth_subscribe` pub struct SubscriptionStream<'a, P: PubsubClient, R: DeserializeOwned> { /// The subscription's installed id on the ethereum node pub id: U256, diff --git a/ethers-providers/src/stream.rs b/ethers-providers/src/stream.rs index 69cdb438..48b6d4bb 100644 --- a/ethers-providers/src/stream.rs +++ b/ethers-providers/src/stream.rs @@ -31,6 +31,7 @@ enum FilterWatcherState<'a, R> { #[must_use = "filters do nothing unless you stream them"] #[pin_project] +/// Streams data from an installed filter via `eth_getFilterCahnges` pub struct FilterWatcher<'a, P, R> { /// The filter's installed id on the ethereum node pub id: U256, diff --git a/ethers-signers/src/ledger/types.rs b/ethers-signers/src/ledger/types.rs index 8a1509a5..6c7edf2b 100644 --- a/ethers-signers/src/ledger/types.rs +++ b/ethers-signers/src/ledger/types.rs @@ -4,9 +4,13 @@ use std::fmt; use thiserror::Error; #[derive(Clone, Debug)] +/// Ledger wallet type pub enum DerivationType { + /// Ledger Live-generated HD path LedgerLive(usize), + /// Legacy generated HD Path Legacy(usize), + /// Any other path Other(String), } @@ -25,6 +29,7 @@ impl fmt::Display for DerivationType { } #[derive(Error, Debug)] +/// Error when using the Ledger transport pub enum LedgerError { /// Underlying ledger transport error #[error(transparent)] @@ -34,10 +39,8 @@ pub enum LedgerError { UnexpectedNullResponse, #[error(transparent)] + /// Error when converting from a hex string HexError(#[from] hex::FromHexError), - - #[error("Error when decoding UTF8 Response: {0}")] - Utf8Error(#[from] std::str::Utf8Error), } pub const P1_FIRST: u8 = 0x00; diff --git a/ethers-signers/src/lib.rs b/ethers-signers/src/lib.rs index 8a0c46d6..4eb6431e 100644 --- a/ethers-signers/src/lib.rs +++ b/ethers-signers/src/lib.rs @@ -7,14 +7,15 @@ //! and the [`TransactionRequest`] to a [`Transaction`], look at the signing middleware. //! //! Supported signers: -//! - Private key -//! - Ledger +//! - [Private key](crate::LocalWallet) +//! - [Ledger](crate::Ledger) +//! - [YubiHSM2](crate::YubiWallet) //! //! ```no_run //! # use ethers::{ -//! signers::{LocalWallet, Signer}, -//! core::{k256::ecdsa::SigningKey, types::TransactionRequest}, -//! }; +//! # signers::{LocalWallet, Signer}, +//! # core::{k256::ecdsa::SigningKey, types::TransactionRequest}, +//! # }; //! # async fn foo() -> Result<(), Box> { //! // instantiate the wallet //! let wallet = "dcf2cbdd171a21c480aa7f53d77f31bb102282b3ff099c78e3118b37348c72f7" @@ -37,8 +38,6 @@ //! //! [`Transaction`]: ethers_core::types::Transaction //! [`TransactionRequest`]: ethers_core::types::TransactionRequest -// mod wallet; -// pub use wallet::Wallet; mod wallet; pub use wallet::Wallet; diff --git a/ethers/src/lib.rs b/ethers/src/lib.rs index a5001529..3bc18299 100644 --- a/ethers/src/lib.rs +++ b/ethers/src/lib.rs @@ -14,10 +14,6 @@ //! //! > ethers-rs is a port of [ethers-js](github.com/ethers-io/ethers.js) in Rust. //! -//! _Note: All examples using `await` are assuming that they are called from inside an `async` -//! function which is run in an async runtime. You are free to use any runtime and executor of -//! your preference._ -//! //! ## Quickstart: `prelude` //! //! A prelude is provided which imports all the important data types and traits for you. Use this @@ -55,8 +51,9 @@ //! //! ## `signers` //! -//! For security reasons, you typically do not want your private keys to be stored on the nodes. -//! This module provides a [`Wallet`] type for connecting to a private key or a YubiHSM2 +//! This module provides a [`Signer`] trait which can be used for signing messages +//! or transactions. A [`Wallet`] type is implemented which can be used with a +//! raw private key, or a YubiHSM2. We also provide Ledger support. //! //! ## `contract` //! @@ -71,14 +68,23 @@ //! [`Contract`] and [`ContractFactory`] abstractions so that you do not have to worry about that. //! It also provides typesafe bindings via the [`abigen`] macro and the [`Abigen` builder]. //! -//! [`Provider`]: providers::Provider -//! [`Wallet`]: signers::Wallet +//! ## `middleware` //! +//! In order to keep the ethers architecture as modular as possible, providers define a [`Middleware`] +//! trait which defines all the methods to interact with an Ethereum node. By implementing the +//! middleware trait, you are able to override the default behavior of methods and do things such +//! as using other gas oracles, escalating your transactions' gas prices, or signing your transactions +//! with a [`Signer`]. The middleware architecture allows users to either use one of the existing +//! middleware, or they are free to write on of their own. +//! +//! [`Provider`]: providers::Provider +//! [`Middleware`]: providers::Middleware +//! [`Wallet`]: signers::Wallet +//! [`Signer`]: signers::Signer //! [`ContractFactory`]: contract::ContractFactory //! [`Contract`]: contract::Contract //! [`abigen`]: ./contract/macro.abigen.html //! [`Abigen` builder]: contract::Abigen -//! //! [`utils`]: core::utils //! [`abi`]: core::abi //! [`types`]: core::types