Repository: gakonst/ethers-rs
Pull request #1901 (2022-11-28 03:05:39)

chore(deps): bump syn from 1.0.103 to 1.0.104
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.103 to 1.0.104.
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/dtolnay/syn/commit/78fa618e96e26856d108089cd849e69d25ff3181"><code>78fa618</code></a> Release 1.0.104</li>
<li><a href="https://github.com/dtolnay/syn/commit/e054b03a3b8affa3f9461254b40ca043fd8d8ad8"><code>e054b03</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/dtolnay/syn/issues/1244">#1244</a> from dtolnay/isnone</li>
<li><a href="https://github.com/dtolnay/syn/commit/3a0406db04cc83e3a022c63e0e259f4fc41f61e0"><code>3a0406d</code></a> Make PathArguments::is_none() public</li>
<li><a href="https://github.com/dtolnay/syn/commit/ac1dbf5d85a12e079fda980844f368ced4c0e491"><code>ac1dbf5</code></a> Time out workflows after 45 minutes</li>
<li><a href="https://github.com/dtolnay/syn/commit/f510eb0ba9ef87ff616558e493567a907d1a2580"><code>f510eb0</code></a> Update test suite to nightly-2022-11-25</li>
<li><a href="https://github.com/dtolnay/syn/commit/20087fc51ceb753b86cba41fee8d4644a569fea8"><code>20087fc</code></a> Update test suite to nightly-2022-11-24</li>
<li><a href="https://github.com/dtolnay/syn/commit/dbc86fe1eddebd477a8bc84bb36eed9acd0fbcca"><code>dbc86fe</code></a> Suppress &quot;emit&quot;:&quot;dep-info&quot; json from syn-test-suite-feature-check</li>
<li><a href="https://github.com/dtolnay/syn/commit/1e8227295e4c5550de85826bd7715d7de40340b1"><code>1e82272</code></a> Fix renamed let_underscore_drop lint</li>
<li><a href="https://github.com/dtolnay/syn/commit/754236dbe6918f4bae8ecac042fb371bbcdbfdb7"><code>754236d</code></a> Update test suite to nightly-2022-11-23</li>
<li><a href="https://github.com/dtolnay/syn/commit/4245c41f2210ed0ad5e80dc0fcd374e157148557"><code>4245c41</code></a> Update test suite to nightly-2022-11-19</li>
<li>Additional commits viewable in <a href="https://github.com/dtolnay/syn/compare/1.0.103...1.0.104">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=syn&package-manager=cargo&previous-version=1.0.103&new-version=1.0.104)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 59f25388..b8c22654 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,7 +3,7 @@
 ## ethers-core
 
 ### 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.
 -   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
diff --git a/Cargo.lock b/Cargo.lock
index 3ecf6d76..265fbd92 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3918,9 +3918,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "1.0.103"
+version = "1.0.104"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
+checksum = "4ae548ec36cf198c0ef7710d3c230987c2d6d7bd98ad6edc0274462724c585ce"
 dependencies = [
  "proc-macro2",
  "quote",
diff --git a/ethers-core/Cargo.toml b/ethers-core/Cargo.toml
index 8edd188c..e221c89d 100644
--- a/ethers-core/Cargo.toml
+++ b/ethers-core/Cargo.toml
@@ -40,7 +40,7 @@ cargo_metadata = { version = "0.15.2", optional = true }
 
 # eip712 feature enabled dependencies
 convert_case = { version = "0.6.0", optional = true }
-syn = { version = "1.0.103", optional = true }
+syn = { version = "1.0.104", optional = true }
 proc-macro2 = { version = "1.0.47", optional = true }
 
 [dev-dependencies]
diff --git a/ethers-middleware/README.md b/ethers-middleware/README.md
index 3d24264f..186c571c 100644
--- a/ethers-middleware/README.md
+++ b/ethers-middleware/README.md
@@ -4,22 +4,22 @@ middleware functionalities that you need.
 
 ## Available Middleware
 
--   [`Signer`](./signer/struct.SignerMiddleware.html): Signs transactions locally,
-    with a private key or a hardware wallet
--   [`Nonce Manager`](./nonce_manager/struct.NonceManagerMiddleware.html): Manages
-    nonces locally, allowing the rapid broadcast of transactions without having to
-    wait for them to be submitted
--   [`Gas Escalator`](./gas_escalator/struct.GasEscalatorMiddleware.html): Bumps
-    transaction gas prices in the background
--   [`Gas Oracle`](./gas_oracle/struct.GasOracleMiddleware.html): Allows getting
-    your gas price estimates from places other than `eth_gasPrice`.
--   [`Transformer`](./transformer/trait.Transformer.html): Allows intercepting and
-    transforming a transaction to be broadcasted via a proxy wallet, e.g.
-    [`DSProxy`](./transformer/struct.DsProxy.html).
+- [`Signer`](./signer/struct.SignerMiddleware.html): Signs transactions locally,
+  with a private key or a hardware wallet
+- [`Nonce Manager`](./nonce_manager/struct.NonceManagerMiddleware.html): Manages
+  nonces locally, allowing the rapid broadcast of transactions without having to
+  wait for them to be submitted
+- [`Gas Escalator`](./gas_escalator/struct.GasEscalatorMiddleware.html): Bumps
+  transaction gas prices in the background
+- [`Gas Oracle`](./gas_oracle/struct.GasOracleMiddleware.html): Allows getting
+  your gas price estimates from places other than `eth_gasPrice`.
+- [`Transformer`](./transformer/trait.Transformer.html): Allows intercepting and
+  transforming a transaction to be broadcasted via a proxy wallet, e.g.
+  [`DSProxy`](./transformer/struct.DsProxy.html).
 
 ## Example of a middleware stack
 
-```no_run
+```rust no_run
 use ethers_providers::{Provider, Http};
 use ethers_signers::{LocalWallet, Signer};
 use ethers_middleware::{
@@ -53,3 +53,43 @@ let provider = NonceManagerMiddleware::new(provider, address);
 
 // ... 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
+}
+```
diff --git a/ethers-middleware/src/builder.rs b/ethers-middleware/src/builder.rs
new file mode 100644
index 00000000..c9801962
--- /dev/null
+++ b/ethers-middleware/src/builder.rs
@@ -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 {}
diff --git a/ethers-middleware/src/lib.rs b/ethers-middleware/src/lib.rs
index 33e90ef2..8593a10b 100644
--- a/ethers-middleware/src/lib.rs
+++ b/ethers-middleware/src/lib.rs
@@ -36,3 +36,8 @@ pub use policy::PolicyMiddleware;
 /// before the chain tip
 pub mod 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;


Test patch:
diff --git a/ethers-middleware/tests/builder.rs b/ethers-middleware/tests/builder.rs
new file mode 100644
index 00000000..7c988bb8
--- /dev/null
+++ b/ethers-middleware/tests/builder.rs
@@ -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();
+    }
+}