Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: AnvilProvider #611

Merged
merged 2 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 25 additions & 25 deletions crates/provider/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,52 +338,52 @@ impl<L, F, N> ProviderBuilder<L, F, N> {
#[cfg(any(all(test, feature = "reqwest"), feature = "anvil"))]
impl<L, F> ProviderBuilder<L, F, Ethereum> {
/// 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<Ethereum>
+ ProviderLayer<L::Provider, alloy_transport_http::Http<reqwest::Client>, Ethereum>,
L: ProviderLayer<
crate::ReqwestProvider<Ethereum>,
L: crate::builder::ProviderLayer<
crate::layers::AnvilProvider<
crate::provider::RootProvider<alloy_transport_http::Http<reqwest::Client>>,
alloy_transport_http::Http<reqwest::Client>,
>,
alloy_transport_http::Http<reqwest::Client>,
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,
) -> (
<JoinFill<F, SignerFiller<alloy_network::EthereumSigner>> as ProviderLayer<
L::Provider,
alloy_transport_http::Http<reqwest::Client>,
>>::Provider,
alloy_node_bindings::AnvilInstance,
)
) -> <JoinFill<F, SignerFiller<alloy_network::EthereumSigner>> as ProviderLayer<
L::Provider,
alloy_transport_http::Http<reqwest::Client>,
>>::Provider
where
L: ProviderLayer<
crate::ReqwestProvider<Ethereum>,
alloy_transport_http::Http<reqwest::Client>,
Ethereum,
>,
F: TxFiller<Ethereum>
+ ProviderLayer<L::Provider, alloy_transport_http::Http<reqwest::Client>, Ethereum>,
L: crate::builder::ProviderLayer<
crate::layers::AnvilProvider<
crate::provider::RootProvider<alloy_transport_http::Http<reqwest::Client>>,
alloy_transport_http::Http<reqwest::Client>,
>,
alloy_transport_http::Http<reqwest::Client>,
>,
{
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()
}
}

Expand Down
18 changes: 9 additions & 9 deletions crates/provider/src/fillers/gas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,18 +253,17 @@ impl<N: Network> TxFiller<N> 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),
Expand All @@ -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),
Expand All @@ -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())
Expand Down
9 changes: 4 additions & 5 deletions crates/provider/src/fillers/nonce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand All @@ -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();
Comment on lines -141 to +143
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very nice API

let tx = TransactionRequest {
from: Some(from),
value: Some(U256::from(100)),
Expand Down
2 changes: 1 addition & 1 deletion crates/provider/src/fillers/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
88 changes: 88 additions & 0 deletions crates/provider/src/layers/anvil.rs
Original file line number Diff line number Diff line change
@@ -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<Arc<AnvilInstance>>,
}

impl AnvilLayer {
/// Starts the anvil instance, or gets a reference to the existing instance.
pub fn instance(&self) -> &Arc<AnvilInstance> {
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<Anvil> for AnvilLayer {
fn from(anvil: Anvil) -> Self {
Self { anvil, instance: OnceLock::new() }
}
}

impl<P, T> ProviderLayer<P, T, Ethereum> for AnvilLayer
where
P: Provider<T>,
T: Transport + Clone,
{
type Provider = AnvilProvider<P, T>;

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<P, T> {
inner: P,
_anvil: Arc<AnvilInstance>,
_pd: PhantomData<fn() -> T>,
}

impl<P, T> AnvilProvider<P, T>
where
P: Provider<T>,
T: Transport + Clone,
{
/// Creates a new `AnvilProvider` with the given inner provider and anvil
/// instance.
pub fn new(inner: P, _anvil: Arc<AnvilInstance>) -> Self {
Self { inner, _anvil, _pd: PhantomData }
}
}

impl<P, T> Provider<T> for AnvilProvider<P, T>
where
P: Provider<T>,
T: Transport + Clone,
{
#[inline(always)]
fn root(&self) -> &RootProvider<T> {
self.inner.root()
}
}
8 changes: 8 additions & 0 deletions crates/provider/src/layers/mod.rs
Original file line number Diff line number Diff line change
@@ -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};
1 change: 1 addition & 0 deletions crates/provider/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ mod builder;
pub use builder::{Identity, ProviderBuilder, ProviderLayer, Stack};

pub mod fillers;
pub mod layers;

mod chain;

Expand Down
5 changes: 2 additions & 3 deletions crates/provider/src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
Loading