//! A middleware supporting development-specific JSON RPC methods //! //! # Example //! //!``` //! use ethers_providers::{Provider, Http, Middleware, DevRpcMiddleware}; //! use ethers_core::types::TransactionRequest; //! use ethers_core::utils::Anvil; //! use std::convert::TryFrom; //! //! # #[tokio::main(flavor = "current_thread")] //! # async fn main() -> Result<(), Box> { //! let anvil = Anvil::new().spawn(); //! let provider = Provider::::try_from(anvil.endpoint()).unwrap(); //! let client = DevRpcMiddleware::new(provider); //! //! // snapshot the initial state //! let block0 = client.get_block_number().await.unwrap(); //! let snap_id = client.snapshot().await.unwrap(); //! //! // send a transaction //! let accounts = client.get_accounts().await?; //! let from = accounts[0]; //! let to = accounts[1]; //! let balance_before = client.get_balance(to, None).await?; //! let tx = TransactionRequest::new().to(to).value(1000).from(from); //! client.send_transaction(tx, None).await?.await?; //! let balance_after = client.get_balance(to, None).await?; //! assert_eq!(balance_after, balance_before + 1000); //! //! // revert to snapshot //! client.revert_to_snapshot(snap_id).await.unwrap(); //! let balance_after_revert = client.get_balance(to, None).await?; //! assert_eq!(balance_after_revert, balance_before); //! # Ok(()) //! # } //! ``` use crate::{Middleware, MiddlewareError, ProviderError}; use async_trait::async_trait; use ethers_core::types::U256; use thiserror::Error; use std::fmt::Debug; /// `DevRpcMiddleware` #[derive(Clone, Debug)] pub struct DevRpcMiddleware(M); /// DevRpcMiddleware Errors #[derive(Error, Debug)] pub enum DevRpcMiddlewareError { /// Internal Middleware error #[error("{0}")] MiddlewareError(M::Error), /// Internal Provider error #[error("{0}")] ProviderError(ProviderError), /// Attempted to revert to unavailable snapshot #[error("Could not revert to snapshot")] NoSnapshot, } #[async_trait] impl Middleware for DevRpcMiddleware { type Error = DevRpcMiddlewareError; type Provider = M::Provider; type Inner = M; fn inner(&self) -> &M { &self.0 } } impl MiddlewareError for DevRpcMiddlewareError { type Inner = M::Error; fn from_err(src: M::Error) -> DevRpcMiddlewareError { DevRpcMiddlewareError::MiddlewareError(src) } fn as_inner(&self) -> Option<&Self::Inner> { match self { DevRpcMiddlewareError::MiddlewareError(e) => Some(e), _ => None, } } } impl From for DevRpcMiddlewareError where M: Middleware, { fn from(src: ProviderError) -> Self { Self::ProviderError(src) } } impl DevRpcMiddleware { /// Instantiate a new `DevRpcMiddleware` pub fn new(inner: M) -> Self { Self(inner) } /// Create a new snapshot on the DevRpc node. Return the Snapshot ID /// /// ### Note /// /// Ganache, Hardhat and Anvil increment snapshot ID even if no state has changed pub async fn snapshot(&self) -> Result> { self.provider().request::<(), U256>("evm_snapshot", ()).await.map_err(From::from) } /// Revert the state of the DevRpc node to the Snapshot, specified by its ID pub async fn revert_to_snapshot(&self, id: U256) -> Result<(), DevRpcMiddlewareError> { let ok = self .provider() .request::<[U256; 1], bool>("evm_revert", [id]) .await .map_err(DevRpcMiddlewareError::ProviderError)?; if ok { Ok(()) } else { Err(DevRpcMiddlewareError::NoSnapshot) } } } #[cfg(test)] // Celo blocks can not get parsed when used with Ganache #[cfg(not(feature = "celo"))] mod tests { use super::*; use crate::{Http, Provider}; use ethers_core::utils::Anvil; use std::convert::TryFrom; #[tokio::test] async fn test_snapshot() { let anvil = Anvil::new().spawn(); let provider = Provider::::try_from(anvil.endpoint()).unwrap(); let client = DevRpcMiddleware::new(provider); // snapshot initial state let block0 = client.get_block_number().await.unwrap(); let time0 = client.get_block(block0).await.unwrap().unwrap().timestamp; let snap_id0 = client.snapshot().await.unwrap(); // mine a new block client.provider().mine(1).await.unwrap(); // snapshot state let block1 = client.get_block_number().await.unwrap(); let time1 = client.get_block(block1).await.unwrap().unwrap().timestamp; let snap_id1 = client.snapshot().await.unwrap(); // mine some blocks client.provider().mine(5).await.unwrap(); // snapshot state let block2 = client.get_block_number().await.unwrap(); let time2 = client.get_block(block2).await.unwrap().unwrap().timestamp; let snap_id2 = client.snapshot().await.unwrap(); // mine some blocks client.provider().mine(5).await.unwrap(); // revert_to_snapshot should reset state to snap id client.revert_to_snapshot(snap_id2).await.unwrap(); let block = client.get_block_number().await.unwrap(); let time = client.get_block(block).await.unwrap().unwrap().timestamp; assert_eq!(block, block2); assert_eq!(time, time2); client.revert_to_snapshot(snap_id1).await.unwrap(); let block = client.get_block_number().await.unwrap(); let time = client.get_block(block).await.unwrap().unwrap().timestamp; assert_eq!(block, block1); assert_eq!(time, time1); // revert_to_snapshot should throw given non-existent or // previously used snapshot let result = client.revert_to_snapshot(snap_id1).await; assert!(result.is_err()); client.revert_to_snapshot(snap_id0).await.unwrap(); let block = client.get_block_number().await.unwrap(); let time = client.get_block(block).await.unwrap().unwrap().timestamp; assert_eq!(block, block0); assert_eq!(time, time0); } }