Feat middleware stack builder (#1890)
* ProviderBuilder struct + docs * README * Docs typos * Removed useless Arc Rename module * Unit tests * cargo +nightly fmt * CHANGELOG * CHANGELOG typo * ci: fix docs job * review: Removed Option<M>. Builder functions directly consume self. * feat: `with_signer` builder function * feat: `nonce_manager` builder function * feat: `gas_oracle` builder function * Unit tests * Docs * fix: cargo +nightly fmt * Update ethers-middleware/src/builder.rs Typo Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de> * Builder functions provided as a trait Update docs Update tests * cargo +nightly fmt * CHANGELOG Co-authored-by: Andrea Simeoni <> Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
parent
ed47eaadad
commit
16ab3d5820
|
@ -3,7 +3,7 @@
|
||||||
## ethers-core
|
## ethers-core
|
||||||
|
|
||||||
### Unreleased
|
### Unreleased
|
||||||
|
- `MiddlewareBuilder` trait to instantiate a `Provider` as `Middleware` layers.
|
||||||
- An `Event` builder can be instantiated specifying the event filter type, without the need to instantiate a contract.
|
- An `Event` builder can be instantiated specifying the event filter type, without the need to instantiate a contract.
|
||||||
- Add 'ethers_core::types::OpCode' and use in 'ethers_core::types::VMOperation' [1857](https://github.com/gakonst/ethers-rs/issues/1857)
|
- Add 'ethers_core::types::OpCode' and use in 'ethers_core::types::VMOperation' [1857](https://github.com/gakonst/ethers-rs/issues/1857)
|
||||||
- Remove rust_decimals dependency for ethers-core
|
- Remove rust_decimals dependency for ethers-core
|
||||||
|
|
|
@ -4,22 +4,22 @@ middleware functionalities that you need.
|
||||||
|
|
||||||
## Available Middleware
|
## Available Middleware
|
||||||
|
|
||||||
- [`Signer`](./signer/struct.SignerMiddleware.html): Signs transactions locally,
|
- [`Signer`](./signer/struct.SignerMiddleware.html): Signs transactions locally,
|
||||||
with a private key or a hardware wallet
|
with a private key or a hardware wallet
|
||||||
- [`Nonce Manager`](./nonce_manager/struct.NonceManagerMiddleware.html): Manages
|
- [`Nonce Manager`](./nonce_manager/struct.NonceManagerMiddleware.html): Manages
|
||||||
nonces locally, allowing the rapid broadcast of transactions without having to
|
nonces locally, allowing the rapid broadcast of transactions without having to
|
||||||
wait for them to be submitted
|
wait for them to be submitted
|
||||||
- [`Gas Escalator`](./gas_escalator/struct.GasEscalatorMiddleware.html): Bumps
|
- [`Gas Escalator`](./gas_escalator/struct.GasEscalatorMiddleware.html): Bumps
|
||||||
transaction gas prices in the background
|
transaction gas prices in the background
|
||||||
- [`Gas Oracle`](./gas_oracle/struct.GasOracleMiddleware.html): Allows getting
|
- [`Gas Oracle`](./gas_oracle/struct.GasOracleMiddleware.html): Allows getting
|
||||||
your gas price estimates from places other than `eth_gasPrice`.
|
your gas price estimates from places other than `eth_gasPrice`.
|
||||||
- [`Transformer`](./transformer/trait.Transformer.html): Allows intercepting and
|
- [`Transformer`](./transformer/trait.Transformer.html): Allows intercepting and
|
||||||
transforming a transaction to be broadcasted via a proxy wallet, e.g.
|
transforming a transaction to be broadcasted via a proxy wallet, e.g.
|
||||||
[`DSProxy`](./transformer/struct.DsProxy.html).
|
[`DSProxy`](./transformer/struct.DsProxy.html).
|
||||||
|
|
||||||
## Example of a middleware stack
|
## Example of a middleware stack
|
||||||
|
|
||||||
```no_run
|
```rust no_run
|
||||||
use ethers_providers::{Provider, Http};
|
use ethers_providers::{Provider, Http};
|
||||||
use ethers_signers::{LocalWallet, Signer};
|
use ethers_signers::{LocalWallet, Signer};
|
||||||
use ethers_middleware::{
|
use ethers_middleware::{
|
||||||
|
@ -53,3 +53,43 @@ let provider = NonceManagerMiddleware::new(provider, address);
|
||||||
|
|
||||||
// ... do something with the provider
|
// ... do something with the provider
|
||||||
```
|
```
|
||||||
|
## Example of a middleware stack using a builder
|
||||||
|
|
||||||
|
Each [`Middleware`](ethers_providers::Middleware) implements the trait [MiddlewareBuilder](crate::MiddlewareBuilder) to help composition of a [`Middleware`](ethers_providers::Middleware) stack. As usual the composition acts in a wrapping fashion. Adding a new layer results in wrapping its predecessor.
|
||||||
|
Builder can be used as follows:
|
||||||
|
```rust
|
||||||
|
use ethers_providers::{Middleware, Provider, Http};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use ethers_signers::{LocalWallet, Signer};
|
||||||
|
use ethers_middleware::{*,gas_escalator::*,gas_oracle::*};
|
||||||
|
|
||||||
|
fn builder_example() {
|
||||||
|
let key = "fdb33e2105f08abe41a8ee3b758726a31abdd57b7a443f470f23efce853af169";
|
||||||
|
let signer = key.parse::<LocalWallet>().unwrap();
|
||||||
|
let address = signer.address();
|
||||||
|
let escalator = GeometricGasPrice::new(1.125, 60_u64, None::<u64>);
|
||||||
|
let gas_oracle = EthGasStation::new(None);
|
||||||
|
|
||||||
|
let provider = Provider::<Http>::try_from("http://localhost:8545")
|
||||||
|
.unwrap()
|
||||||
|
.wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
|
||||||
|
.gas_oracle(gas_oracle)
|
||||||
|
.with_signer(signer)
|
||||||
|
.nonce_manager(address); // Outermost layer
|
||||||
|
}
|
||||||
|
|
||||||
|
fn builder_example_raw_wrap() {
|
||||||
|
let key = "fdb33e2105f08abe41a8ee3b758726a31abdd57b7a443f470f23efce853af169";
|
||||||
|
let signer = key.parse::<LocalWallet>().unwrap();
|
||||||
|
let address = signer.address();
|
||||||
|
let escalator = GeometricGasPrice::new(1.125, 60_u64, None::<u64>);
|
||||||
|
|
||||||
|
let provider = Provider::<Http>::try_from("http://localhost:8545")
|
||||||
|
.unwrap()
|
||||||
|
.wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
|
||||||
|
.wrap_into(|p| SignerMiddleware::new(p, signer))
|
||||||
|
.wrap_into(|p| GasOracleMiddleware::new(p, EthGasStation::new(None)))
|
||||||
|
.wrap_into(|p| NonceManagerMiddleware::new(p, address)); // Outermost layer
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
use crate::{
|
||||||
|
gas_oracle::{GasOracle, GasOracleMiddleware},
|
||||||
|
NonceManagerMiddleware, SignerMiddleware,
|
||||||
|
};
|
||||||
|
use ethers_core::types::Address;
|
||||||
|
use ethers_providers::Middleware;
|
||||||
|
use ethers_signers::Signer;
|
||||||
|
|
||||||
|
/// A builder trait to compose different [`Middleware`](ethers_providers::Middleware) layers
|
||||||
|
/// and then build a composed [`Provider`](ethers_providers::Provider) architecture.
|
||||||
|
/// [`Middleware`](ethers_providers::Middleware) composition acts in a wrapping fashion. Adding a
|
||||||
|
/// new layer results in wrapping its predecessor.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use ethers_providers::{Middleware, Provider, Http};
|
||||||
|
/// use std::sync::Arc;
|
||||||
|
/// use std::convert::TryFrom;
|
||||||
|
/// use ethers_signers::{LocalWallet, Signer};
|
||||||
|
/// use ethers_middleware::{*,gas_escalator::*,gas_oracle::*};
|
||||||
|
///
|
||||||
|
/// fn builder_example() {
|
||||||
|
/// let key = "fdb33e2105f08abe41a8ee3b758726a31abdd57b7a443f470f23efce853af169";
|
||||||
|
/// let signer = key.parse::<LocalWallet>().unwrap();
|
||||||
|
/// let address = signer.address();
|
||||||
|
/// let escalator = GeometricGasPrice::new(1.125, 60_u64, None::<u64>);
|
||||||
|
/// let gas_oracle = EthGasStation::new(None);
|
||||||
|
///
|
||||||
|
/// let provider = Provider::<Http>::try_from("http://localhost:8545")
|
||||||
|
/// .unwrap()
|
||||||
|
/// .wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
|
||||||
|
/// .gas_oracle(gas_oracle)
|
||||||
|
/// .with_signer(signer)
|
||||||
|
/// .nonce_manager(address); // Outermost layer
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn builder_example_raw_wrap() {
|
||||||
|
/// let key = "fdb33e2105f08abe41a8ee3b758726a31abdd57b7a443f470f23efce853af169";
|
||||||
|
/// let signer = key.parse::<LocalWallet>().unwrap();
|
||||||
|
/// let address = signer.address();
|
||||||
|
/// let escalator = GeometricGasPrice::new(1.125, 60_u64, None::<u64>);
|
||||||
|
///
|
||||||
|
/// let provider = Provider::<Http>::try_from("http://localhost:8545")
|
||||||
|
/// .unwrap()
|
||||||
|
/// .wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
|
||||||
|
/// .wrap_into(|p| SignerMiddleware::new(p, signer))
|
||||||
|
/// .wrap_into(|p| GasOracleMiddleware::new(p, EthGasStation::new(None)))
|
||||||
|
/// .wrap_into(|p| NonceManagerMiddleware::new(p, address)); // Outermost layer
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub trait MiddlewareBuilder: Middleware + Sized + 'static {
|
||||||
|
/// Wraps `self` inside a new [`Middleware`](ethers_providers::Middleware).
|
||||||
|
///
|
||||||
|
/// `f` Consumes `self`, must be used to return a new
|
||||||
|
/// [`Middleware`](ethers_providers::Middleware) around `self`.
|
||||||
|
fn wrap_into<F, T>(self, f: F) -> T
|
||||||
|
where
|
||||||
|
F: FnOnce(Self) -> T,
|
||||||
|
T: Middleware,
|
||||||
|
{
|
||||||
|
f(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wraps `self` inside a [`SignerMiddleware`](crate::SignerMiddleware).
|
||||||
|
///
|
||||||
|
/// [`Signer`] ethers_signers::Signer
|
||||||
|
fn with_signer<S>(self, s: S) -> SignerMiddleware<Self, S>
|
||||||
|
where
|
||||||
|
S: Signer,
|
||||||
|
{
|
||||||
|
SignerMiddleware::new(self, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wraps `self` inside a [`NonceManagerMiddleware`](crate::NonceManagerMiddleware).
|
||||||
|
///
|
||||||
|
/// [`Address`] ethers_core::types::Address
|
||||||
|
fn nonce_manager(self, address: Address) -> NonceManagerMiddleware<Self> {
|
||||||
|
NonceManagerMiddleware::new(self, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wraps `self` inside a [`GasOracleMiddleware`](crate::gas_oracle::GasOracleMiddleware).
|
||||||
|
///
|
||||||
|
/// [`Address`] ethers_core::types::Address
|
||||||
|
fn gas_oracle<G>(self, gas_oracle: G) -> GasOracleMiddleware<Self, G>
|
||||||
|
where
|
||||||
|
G: GasOracle,
|
||||||
|
{
|
||||||
|
GasOracleMiddleware::new(self, gas_oracle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> MiddlewareBuilder for M where M: Middleware + Sized + 'static {}
|
|
@ -36,3 +36,8 @@ pub use policy::PolicyMiddleware;
|
||||||
/// before the chain tip
|
/// before the chain tip
|
||||||
pub mod timelag;
|
pub mod timelag;
|
||||||
pub use timelag::TimeLag;
|
pub use timelag::TimeLag;
|
||||||
|
|
||||||
|
/// The [MiddlewareBuilder](crate::MiddlewareBuilder) provides a way to compose many
|
||||||
|
/// [`Middleware`](ethers_providers::Middleware) in a concise way
|
||||||
|
pub mod builder;
|
||||||
|
pub use builder::MiddlewareBuilder;
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
#![cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[cfg(not(feature = "celo"))]
|
||||||
|
mod tests {
|
||||||
|
use ethers_core::{rand::thread_rng, types::U64};
|
||||||
|
use ethers_middleware::{
|
||||||
|
builder::MiddlewareBuilder,
|
||||||
|
gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
|
||||||
|
gas_oracle::{EthGasStation, GasOracleMiddleware},
|
||||||
|
nonce_manager::NonceManagerMiddleware,
|
||||||
|
signer::SignerMiddleware,
|
||||||
|
};
|
||||||
|
use ethers_providers::{Middleware, Provider};
|
||||||
|
use ethers_signers::{LocalWallet, Signer};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn build_raw_middleware_stack() {
|
||||||
|
let (provider, mock) = Provider::mocked();
|
||||||
|
|
||||||
|
let signer = LocalWallet::new(&mut thread_rng());
|
||||||
|
let address = signer.address();
|
||||||
|
let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
|
||||||
|
|
||||||
|
let provider = provider
|
||||||
|
.wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
|
||||||
|
.wrap_into(|p| GasOracleMiddleware::new(p, EthGasStation::new(None)))
|
||||||
|
.wrap_into(|p| SignerMiddleware::new(p, signer))
|
||||||
|
.wrap_into(|p| NonceManagerMiddleware::new(p, address));
|
||||||
|
|
||||||
|
// push a response
|
||||||
|
mock.push(U64::from(12u64)).unwrap();
|
||||||
|
let block: U64 = provider.get_block_number().await.unwrap();
|
||||||
|
assert_eq!(block.as_u64(), 12);
|
||||||
|
|
||||||
|
provider.get_block_number().await.unwrap_err();
|
||||||
|
|
||||||
|
// 2 calls were made
|
||||||
|
mock.assert_request("eth_blockNumber", ()).unwrap();
|
||||||
|
mock.assert_request("eth_blockNumber", ()).unwrap();
|
||||||
|
mock.assert_request("eth_blockNumber", ()).unwrap_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn build_declarative_middleware_stack() {
|
||||||
|
let (provider, mock) = Provider::mocked();
|
||||||
|
|
||||||
|
let signer = LocalWallet::new(&mut thread_rng());
|
||||||
|
let address = signer.address();
|
||||||
|
let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
|
||||||
|
let gas_oracle = EthGasStation::new(None);
|
||||||
|
|
||||||
|
let provider = provider
|
||||||
|
.wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
|
||||||
|
.gas_oracle(gas_oracle)
|
||||||
|
.with_signer(signer)
|
||||||
|
.nonce_manager(address);
|
||||||
|
|
||||||
|
// push a response
|
||||||
|
mock.push(U64::from(12u64)).unwrap();
|
||||||
|
let block: U64 = provider.get_block_number().await.unwrap();
|
||||||
|
assert_eq!(block.as_u64(), 12);
|
||||||
|
|
||||||
|
provider.get_block_number().await.unwrap_err();
|
||||||
|
|
||||||
|
// 2 calls were made
|
||||||
|
mock.assert_request("eth_blockNumber", ()).unwrap();
|
||||||
|
mock.assert_request("eth_blockNumber", ()).unwrap();
|
||||||
|
mock.assert_request("eth_blockNumber", ()).unwrap_err();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue