diff --git a/crates/middleware/Cargo.toml b/crates/middleware/Cargo.toml index 898de7e2fa8..914a30389e4 100644 --- a/crates/middleware/Cargo.toml +++ b/crates/middleware/Cargo.toml @@ -14,4 +14,6 @@ exclude.workspace = true [dependencies] alloy-json-rpc.workspace = true alloy-networks.workspace = true +alloy-primitives.workspace = true alloy-transports.workspace = true +futures-util = "0.3.28" diff --git a/crates/middleware/src/lib.rs b/crates/middleware/src/lib.rs index 030ba306ea5..fad296093aa 100644 --- a/crates/middleware/src/lib.rs +++ b/crates/middleware/src/lib.rs @@ -1,25 +1,57 @@ -use std::{future::Future, pin::Pin}; - use alloy_json_rpc::RpcResult; -use alloy_networks::Network; +use alloy_networks::{Network, Transaction}; use alloy_transports::{BoxTransport, RpcClient, Transport, TransportError}; +use std::{future::Future, pin::Pin}; + pub type MwareFut<'a, T, E> = Pin> + Send + 'a>>; -pub trait Middleware { +/// Middleware is parameterized with a network and a transport. The default +/// transport is type-erased, but you can do `Middleware`. +pub trait Middleware: Send + Sync { fn client(&self) -> &RpcClient; + /// Return a reference to the inner Middleware. + /// + /// Middleware are object safe now :) fn inner(&self) -> &dyn Middleware; - fn send_transaction<'a>( - &self, + fn estimate_gas<'s: 'fut, 'a: 'fut, 'fut>( + &'s self, tx: &'a N::TransactionRequest, - ) -> MwareFut<'a, N::Receipt, TransportError> { + ) -> MwareFut<'fut, alloy_primitives::U256, TransportError> + where + Self: Sync + 'fut, + { + self.inner().estimate_gas(tx) + } + + /// Send a transaction to the network. + /// + /// The transaction type is defined by the network. + fn send_transaction<'s: 'fut, 'a: 'fut, 'fut>( + &'s self, + tx: &'a N::TransactionRequest, + ) -> MwareFut<'fut, N::Receipt, TransportError> { self.inner().send_transaction(tx) } + + fn populate_gas<'s: 'fut, 'a: 'fut, 'fut>( + &'s self, + tx: &'a mut N::TransactionRequest, + ) -> MwareFut<'fut, (), TransportError> + where + Self: Sync, + { + Box::pin(async move { + let gas = self.estimate_gas(&*tx).await; + + gas.map(|gas| tx.set_gas(gas)) + }) + } } -impl Middleware for RpcClient { +impl Middleware for RpcClient { fn client(&self) -> &RpcClient { self } @@ -28,14 +60,22 @@ impl Middleware for RpcClient { panic!("called inner on ") } - fn send_transaction<'a>( - &self, + fn estimate_gas<'s: 'fut, 'a: 'fut, 'fut>( + &'s self, + tx: &'a ::TransactionRequest, + ) -> MwareFut<'fut, alloy_primitives::U256, TransportError> { + self.prepare("eth_estimateGas", tx).box_pin() + } + + fn send_transaction<'s: 'fut, 'a: 'fut, 'fut>( + &'s self, tx: &'a N::TransactionRequest, - ) -> MwareFut<'a, N::Receipt, TransportError> { + ) -> MwareFut<'fut, N::Receipt, TransportError> { self.prepare("eth_sendTransaction", tx).box_pin() } } +/// Middleware use a tower-like Layer abstraction pub trait MwareLayer { type Middleware: Middleware; @@ -44,3 +84,13 @@ pub trait MwareLayer { M: Middleware, T: Transport; } + +#[cfg(test)] +mod test { + use crate::Middleware; + use alloy_networks::Network; + + fn __compile_check() -> Box> { + unimplemented!() + } +} diff --git a/crates/networks/Cargo.toml b/crates/networks/Cargo.toml index 6af87d82e4e..70982c2425a 100644 --- a/crates/networks/Cargo.toml +++ b/crates/networks/Cargo.toml @@ -12,6 +12,7 @@ exclude.workspace = true [dependencies] alloy-json-rpc.workspace = true +alloy-primitives.workspace = true alloy-rlp.workspace = true alloy-transports.workspace = true pin-project = "1.1.2" diff --git a/crates/networks/src/lib.rs b/crates/networks/src/lib.rs index 3fddb15a928..5eddd7744c2 100644 --- a/crates/networks/src/lib.rs +++ b/crates/networks/src/lib.rs @@ -1,6 +1,14 @@ use alloy_json_rpc::RpcObject; -pub trait Network { +/// Captures type info for network-specific RPC requests/responses +pub trait Network: Sized + Send + Sync + 'static { + #[doc(hidden)] + /// Asserts that this trait can only be implemented on a ZST. + const __ASSERT_ZST: bool = { + assert!(std::mem::size_of::() == 0, "Network must by a ZST"); + true + }; + /// The JSON body of a transaction request. type TransactionRequest: Transaction; @@ -11,10 +19,12 @@ pub trait Network { type TransactionResponse: Transaction; } -/// Captures getters and setters common across transactions across all networks +/// Captures getters and setters common across transactions and +/// transaction-like objects across all networks. pub trait Transaction: - alloy_rlp::Encodable + alloy_rlp::Decodable + RpcObject + Sized + 'static + alloy_rlp::Encodable + alloy_rlp::Decodable + RpcObject + Clone + Sized + 'static { + fn set_gas(&mut self, gas: alloy_primitives::U256); } /// Captures getters and setters common across EIP-1559 transactions across all networks diff --git a/crates/transports/src/batch.rs b/crates/transports/src/batch.rs index bde8a1af714..42b37bd14b1 100644 --- a/crates/transports/src/batch.rs +++ b/crates/transports/src/batch.rs @@ -63,7 +63,7 @@ where #[pin_project::pin_project(project = CallStateProj)] pub enum BatchFuture where - Conn: Transport, + Conn: Transport + Clone, Conn::Future: Send, { Prepared { @@ -110,7 +110,7 @@ impl<'a, T> BatchRequest<'a, T> { impl<'a, Conn> BatchRequest<'a, Conn> where - Conn: Transport, + Conn: Transport + Clone, { #[must_use = "Waiters do nothing unless polled. A Waiter will never resolve unless its batch is sent."] /// Add a call to the batch. @@ -139,7 +139,7 @@ where impl<'a, T> IntoFuture for BatchRequest<'a, T> where - T: Transport, + T: Transport + Clone, { type Output = as Future>::Output; type IntoFuture = BatchFuture; @@ -151,7 +151,7 @@ where impl BatchFuture where - T: Transport, + T: Transport + Clone, { fn poll_prepared( mut self: Pin<&mut Self>, @@ -248,7 +248,7 @@ where impl Future for BatchFuture where - T: Transport, + T: Transport + Clone, T::Future: Send, { type Output = Result<(), TransportError>; diff --git a/crates/transports/src/call.rs b/crates/transports/src/call.rs index f76de8535f8..1beb02b0f88 100644 --- a/crates/transports/src/call.rs +++ b/crates/transports/src/call.rs @@ -12,7 +12,7 @@ use tower::{Layer, Service}; #[pin_project::pin_project(project = CallStateProj)] enum CallState where - Conn: Transport, + Conn: Transport + Clone, Conn::Future: Send, Params: RpcParam, { @@ -29,7 +29,7 @@ where impl CallState where - Conn: Transport, + Conn: Transport + Clone, Conn::Future: Send, Params: RpcParam, { @@ -78,7 +78,7 @@ where impl Future for CallState where - Conn: Transport, + Conn: Transport + Clone, Conn::Future: Send, Params: RpcParam, { @@ -101,7 +101,7 @@ where #[pin_project::pin_project] pub struct RpcCall where - Conn: Transport, + Conn: Transport + Clone, Conn::Future: Send, Params: RpcParam, { @@ -112,7 +112,7 @@ where impl RpcCall where - Conn: Transport, + Conn: Transport + Clone, Conn::Future: Send, Params: RpcParam, { @@ -129,7 +129,7 @@ where impl<'a, Conn, Params, Resp> RpcCall where - Conn: Transport, + Conn: Transport + Clone, Conn::Future: Send, Params: RpcParam + 'a, Resp: RpcReturn, @@ -143,7 +143,7 @@ where impl Future for RpcCall where - Conn: Transport, + Conn: Transport + Clone, Conn::Future: Send, Params: RpcParam, Resp: RpcReturn, diff --git a/crates/transports/src/client.rs b/crates/transports/src/client.rs index 32f09197475..d7831275623 100644 --- a/crates/transports/src/client.rs +++ b/crates/transports/src/client.rs @@ -40,7 +40,7 @@ impl RpcClient { impl RpcClient where - T: Transport, + T: Transport + Clone, T::Future: Send, { #[inline] diff --git a/crates/transports/src/transports/json_service.rs b/crates/transports/src/transports/json_service.rs index 970e8ff82e3..e90f680df95 100644 --- a/crates/transports/src/transports/json_service.rs +++ b/crates/transports/src/transports/json_service.rs @@ -24,7 +24,7 @@ impl tower::Layer for JsonRpcLayer { impl Service> for JsonRpcService where - S: Transport, + S: Transport + Clone, Param: RpcParam, { type Response = JsonRpcResponse; diff --git a/crates/transports/src/transports/mod.rs b/crates/transports/src/transports/mod.rs index 30db5d23a12..cf7fd6cd83a 100644 --- a/crates/transports/src/transports/mod.rs +++ b/crates/transports/src/transports/mod.rs @@ -6,22 +6,28 @@ pub use json_service::{JsonRpcFuture, JsonRpcLayer, JsonRpcService}; use serde_json::value::RawValue; use std::{future::Future, pin::Pin}; -use tower::{util::BoxCloneService, Service}; +use tower::Service; use crate::TransportError; -pub type BoxTransport = BoxCloneService, Box, TransportError>; - pub trait Transport: Service< Box, Response = Box, Error = TransportError, Future = Pin, TransportError>> + Send>>, - > + Clone - + Send + > + Send + + Sync + 'static { + fn erased(self) -> BoxTransport + where + Self: Sized + Clone + Send + Sync + 'static, + { + BoxTransport { + inner: Box::new(self), + } + } } impl Transport for T where @@ -30,8 +36,60 @@ impl Transport for T where Response = Box, Error = TransportError, Future = Pin, TransportError>> + Send>>, - > + Clone - + Send + > + Send + + Sync + 'static { } + +pub struct BoxTransport { + inner: Box, +} + +impl Clone for BoxTransport { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone_box(), + } + } +} + +trait CloneTransport: Transport { + fn clone_box(&self) -> Box; +} + +impl CloneTransport for T +where + T: Transport + Clone + Send + Sync, +{ + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + +impl Service> for BoxTransport { + type Response = Box; + + type Error = TransportError; + + type Future = Pin, TransportError>> + Send>>; + + fn poll_ready( + &mut self, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, req: Box) -> Self::Future { + self.inner.call(req) + } +} + +/// checks trait + send + sync + 'static +fn __compile_check() { + fn inner() { + todo!() + } + inner::(); +}