diff --git a/crates/provider/src/builder.rs b/crates/provider/src/builder.rs index b84a18117d7..462e143eefd 100644 --- a/crates/provider/src/builder.rs +++ b/crates/provider/src/builder.rs @@ -338,52 +338,52 @@ impl ProviderBuilder { #[cfg(any(all(test, feature = "reqwest"), feature = "anvil"))] impl ProviderBuilder { /// Build this provider with anvil, using an Reqwest HTTP transport. - pub fn on_anvil(self) -> (F::Provider, alloy_node_bindings::AnvilInstance) + pub fn on_anvil(self) -> F::Provider where F: TxFiller + ProviderLayer, Ethereum>, - L: ProviderLayer< - crate::ReqwestProvider, + L: crate::builder::ProviderLayer< + crate::layers::AnvilProvider< + crate::provider::RootProvider>, + alloy_transport_http::Http, + >, alloy_transport_http::Http, - Ethereum, >, { - let anvil = alloy_node_bindings::Anvil::new().spawn(); - let url = anvil.endpoint().parse().unwrap(); + let anvil_layer = crate::layers::AnvilLayer::default(); + let url = anvil_layer.endpoint_url(); - (self.on_http(url).unwrap(), anvil) + self.layer(anvil_layer).on_http(url).unwrap() } /// Build this provider with anvil, using an Reqwest HTTP transport. This /// function configures a signer backed by anvil keys, and is intended for /// use in tests. - #[allow(clippy::type_complexity)] pub fn on_anvil_with_signer( self, - ) -> ( - > as ProviderLayer< - L::Provider, - alloy_transport_http::Http, - >>::Provider, - alloy_node_bindings::AnvilInstance, - ) + ) -> > as ProviderLayer< + L::Provider, + alloy_transport_http::Http, + >>::Provider where - L: ProviderLayer< - crate::ReqwestProvider, - alloy_transport_http::Http, - Ethereum, - >, F: TxFiller + ProviderLayer, Ethereum>, + L: crate::builder::ProviderLayer< + crate::layers::AnvilProvider< + crate::provider::RootProvider>, + alloy_transport_http::Http, + >, + alloy_transport_http::Http, + >, { - let anvil = alloy_node_bindings::Anvil::new().spawn(); - let url = anvil.endpoint().parse().unwrap(); + let anvil_layer = crate::layers::AnvilLayer::default(); + let url = anvil_layer.endpoint_url(); - let wallet = alloy_signer_wallet::Wallet::from(anvil.keys()[0].clone()); + let wallet = alloy_signer_wallet::Wallet::from(anvil_layer.instance().keys()[0].clone()); - let this = self.signer(crate::network::EthereumSigner::from(wallet)); + let signer = crate::network::EthereumSigner::from(wallet); - (this.on_http(url).unwrap(), anvil) + self.signer(signer).layer(anvil_layer).on_http(url).unwrap() } } diff --git a/crates/provider/src/fillers/gas.rs b/crates/provider/src/fillers/gas.rs index c3327c4ee4a..50e411e74f5 100644 --- a/crates/provider/src/fillers/gas.rs +++ b/crates/provider/src/fillers/gas.rs @@ -253,18 +253,17 @@ impl TxFiller for GasFiller { #[cfg(test)] mod tests { use super::*; - use crate::ProviderBuilder; + use crate::{ProviderBuilder, WalletProvider}; use alloy_primitives::{address, U256}; use alloy_rpc_types::TransactionRequest; #[tokio::test] async fn no_gas_price_or_limit() { - let (provider, anvil) = - ProviderBuilder::new().with_recommended_fillers().on_anvil_with_signer(); - + let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil_with_signer(); + let from = provider.default_signer_address(); // GasEstimationLayer requires chain_id to be set to handle EIP-1559 tx let tx = TransactionRequest { - from: Some(anvil.addresses()[0]), + from: Some(from), value: Some(U256::from(100)), to: address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045").into(), chain_id: Some(31337), @@ -281,12 +280,13 @@ mod tests { #[tokio::test] async fn no_gas_limit() { - let (provider, anvil) = - ProviderBuilder::new().with_recommended_fillers().on_anvil_with_signer(); + let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil_with_signer(); + + let from = provider.default_signer_address(); let gas_price = provider.get_gas_price().await.unwrap(); let tx = TransactionRequest { - from: Some(anvil.addresses()[0]), + from: Some(from), value: Some(U256::from(100)), to: address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045").into(), gas_price: Some(gas_price), @@ -302,7 +302,7 @@ mod tests { #[tokio::test] async fn non_eip1559_network() { - let (provider, _anvil) = ProviderBuilder::new() + let provider = ProviderBuilder::new() .filler(crate::fillers::GasFiller) .filler(crate::fillers::NonceFiller::default()) .filler(crate::fillers::ChainIdFiller::default()) diff --git a/crates/provider/src/fillers/nonce.rs b/crates/provider/src/fillers/nonce.rs index ab4ba18cb92..d4fa16726e7 100644 --- a/crates/provider/src/fillers/nonce.rs +++ b/crates/provider/src/fillers/nonce.rs @@ -116,13 +116,13 @@ impl NonceFiller { #[cfg(test)] mod tests { use super::*; - use crate::ProviderBuilder; + use crate::{ProviderBuilder, WalletProvider}; use alloy_primitives::{address, U256}; use alloy_rpc_types::TransactionRequest; #[tokio::test] async fn no_nonce_if_sender_unset() { - let (provider, _anvil) = ProviderBuilder::new().with_nonce_management().on_anvil(); + let provider = ProviderBuilder::new().with_nonce_management().on_anvil(); let tx = TransactionRequest { value: Some(U256::from(100)), @@ -138,10 +138,9 @@ mod tests { #[tokio::test] async fn increments_nonce() { - let (provider, anvil) = - ProviderBuilder::new().with_nonce_management().on_anvil_with_signer(); + let provider = ProviderBuilder::new().with_nonce_management().on_anvil_with_signer(); - let from = anvil.addresses()[0]; + let from = provider.default_signer_address(); let tx = TransactionRequest { from: Some(from), value: Some(U256::from(100)), diff --git a/crates/provider/src/fillers/signer.rs b/crates/provider/src/fillers/signer.rs index c1e04c525d4..82a27590e0f 100644 --- a/crates/provider/src/fillers/signer.rs +++ b/crates/provider/src/fillers/signer.rs @@ -115,7 +115,7 @@ mod tests { #[tokio::test] async fn poc() { - let (provider, _anvil) = ProviderBuilder::new().on_anvil_with_signer(); + let provider = ProviderBuilder::new().on_anvil_with_signer(); let tx = TransactionRequest { nonce: Some(0), diff --git a/crates/provider/src/layers/anvil.rs b/crates/provider/src/layers/anvil.rs new file mode 100644 index 00000000000..274bce7726f --- /dev/null +++ b/crates/provider/src/layers/anvil.rs @@ -0,0 +1,88 @@ +use alloy_network::Ethereum; +use alloy_node_bindings::{Anvil, AnvilInstance}; +use alloy_transport::Transport; +use std::{ + marker::PhantomData, + sync::{Arc, OnceLock}, +}; +use url::Url; + +use crate::{Provider, ProviderLayer, RootProvider}; + +/// A layer that wraps an [`Anvil`] config. The config will be used +/// to spawn an [`AnvilInstance`] when the layer is applied, or when the user +/// requests any information about the anvil node (e.g. via the +/// [`AnvilLayer::ws_endpoint_url`] method ). +#[derive(Debug, Clone, Default)] +pub struct AnvilLayer { + anvil: Anvil, + instance: OnceLock>, +} + +impl AnvilLayer { + /// Starts the anvil instance, or gets a reference to the existing instance. + pub fn instance(&self) -> &Arc { + self.instance.get_or_init(|| Arc::new(self.anvil.clone().spawn())) + } + + /// Get the instance http endpoint. + pub fn endpoint_url(&self) -> Url { + self.instance().endpoint_url() + } + + /// Get the instance ws endpoint. + pub fn ws_endpoint_url(&self) -> Url { + self.instance().ws_endpoint_url() + } +} + +impl From for AnvilLayer { + fn from(anvil: Anvil) -> Self { + Self { anvil, instance: OnceLock::new() } + } +} + +impl ProviderLayer for AnvilLayer +where + P: Provider, + T: Transport + Clone, +{ + type Provider = AnvilProvider; + + fn layer(&self, inner: P) -> Self::Provider { + let anvil = self.instance(); + AnvilProvider::new(inner, anvil.clone()) + } +} + +/// A provider that wraps an [`AnvilInstance`], preventing the instance from +/// being dropped while the provider is in use. +#[derive(Clone, Debug)] +pub struct AnvilProvider { + inner: P, + _anvil: Arc, + _pd: PhantomData T>, +} + +impl AnvilProvider +where + P: Provider, + T: Transport + Clone, +{ + /// Creates a new `AnvilProvider` with the given inner provider and anvil + /// instance. + pub fn new(inner: P, _anvil: Arc) -> Self { + Self { inner, _anvil, _pd: PhantomData } + } +} + +impl Provider for AnvilProvider +where + P: Provider, + T: Transport + Clone, +{ + #[inline(always)] + fn root(&self) -> &RootProvider { + self.inner.root() + } +} diff --git a/crates/provider/src/layers/mod.rs b/crates/provider/src/layers/mod.rs new file mode 100644 index 00000000000..2ac0795941e --- /dev/null +++ b/crates/provider/src/layers/mod.rs @@ -0,0 +1,8 @@ +//! Useful layer implementations for the provider. Currently this +//! module contains the `AnvilLayer` and `AnvilProvider` types, when the anvil +//! feature is enabled. + +#[cfg(any(test, feature = "anvil"))] +mod anvil; +#[cfg(any(test, feature = "anvil"))] +pub use anvil::{AnvilLayer, AnvilProvider}; diff --git a/crates/provider/src/lib.rs b/crates/provider/src/lib.rs index c4ab1111e50..c715d97f178 100644 --- a/crates/provider/src/lib.rs +++ b/crates/provider/src/lib.rs @@ -40,6 +40,7 @@ mod builder; pub use builder::{Identity, ProviderBuilder, ProviderLayer, Stack}; pub mod fillers; +pub mod layers; mod chain; diff --git a/crates/provider/src/wallet.rs b/crates/provider/src/wallet.rs index 03c1950f7eb..37b07a4e76d 100644 --- a/crates/provider/src/wallet.rs +++ b/crates/provider/src/wallet.rs @@ -98,15 +98,14 @@ mod test { #[test] fn basic_usage() { - let (provider, _anvil) = ProviderBuilder::new().on_anvil_with_signer(); + let provider = ProviderBuilder::new().on_anvil_with_signer(); assert_eq!(provider.default_signer_address(), provider.signer_addresses().next().unwrap()); } #[test] fn bubbles_through_fillers() { - let (provider, _anvil) = - ProviderBuilder::new().with_recommended_fillers().on_anvil_with_signer(); + let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil_with_signer(); assert_eq!(provider.default_signer_address(), provider.signer_addresses().next().unwrap()); }