From 530bfe2b71d8b6b5f701d248b025cd5a81319ddb Mon Sep 17 00:00:00 2001 From: Gabriel Coutinho de Paula <38709327+GCdePaula@users.noreply.github.com> Date: Tue, 16 Mar 2021 16:46:07 -0300 Subject: [PATCH] feat: add support for EIP-234 and EIP-1898 (#231) * Add support for EIP-234 * Add support for EIP-1898 * Remove redundant field names * Remove useless conversion * Change `unwrap_or` to `unwrap_or_else` --- ethers-contract/src/call.rs | 6 +- ethers-contract/src/event.rs | 12 +- ethers-contract/src/factory.rs | 2 +- ethers-contract/tests/contract.rs | 134 +++++++++++++++++- ethers-core/src/types/log.rs | 88 ++++++++++-- ethers-middleware/src/gas_escalator/mod.rs | 6 +- .../src/gas_oracle/middleware.rs | 2 +- ethers-middleware/src/nonce_manager.rs | 4 +- ethers-middleware/src/signer.rs | 7 +- .../src/transformer/middleware.rs | 2 +- ethers-middleware/tests/nonce_manager.rs | 2 +- ethers-providers/src/lib.rs | 12 +- ethers-providers/src/provider.rs | 22 +-- ethers/examples/transfer_eth.rs | 4 +- 14 files changed, 252 insertions(+), 51 deletions(-) diff --git a/ethers-contract/src/call.rs b/ethers-contract/src/call.rs index 35e2040e..b8a4c76b 100644 --- a/ethers-contract/src/call.rs +++ b/ethers-contract/src/call.rs @@ -1,7 +1,7 @@ use super::base::{decode_function_data, AbiError}; use ethers_core::{ abi::{Detokenize, Function, InvalidOutputType}, - types::{Address, BlockNumber, Bytes, TransactionRequest, U256}, + types::{Address, BlockId, Bytes, TransactionRequest, U256}, }; use ethers_providers::{Middleware, PendingTransaction, ProviderError}; @@ -52,7 +52,7 @@ pub struct ContractCall { /// The ABI of the function being called pub function: Function, /// Optional block number to be used when calculating the transaction's gas and nonce - pub block: Option, + pub block: Option, pub(crate) client: Arc, pub(crate) datatype: PhantomData, } @@ -83,7 +83,7 @@ impl ContractCall { } /// Sets the `block` field for sending the tx to the chain - pub fn block>(mut self, block: T) -> Self { + pub fn block>(mut self, block: T) -> Self { self.block = Some(block.into()); self } diff --git a/ethers-contract/src/event.rs b/ethers-contract/src/event.rs index f74d1be7..2c4abf69 100644 --- a/ethers-contract/src/event.rs +++ b/ethers-contract/src/event.rs @@ -40,14 +40,22 @@ impl Event<'_, '_, M, D> { /// Sets the filter's `from` block #[allow(clippy::wrong_self_convention)] pub fn from_block>(mut self, block: T) -> Self { - self.filter.from_block = Some(block.into()); + self.filter = self.filter.from_block(block); self } /// Sets the filter's `to` block #[allow(clippy::wrong_self_convention)] pub fn to_block>(mut self, block: T) -> Self { - self.filter.to_block = Some(block.into()); + self.filter = self.filter.to_block(block); + self + } + + /// Sets the filter's `blockHash`. Setting this will override previously + /// set `from_block` and `to_block` fields. + #[allow(clippy::wrong_self_convention)] + pub fn at_block_hash>(mut self, hash: T) -> Self { + self.filter = self.filter.at_block_hash(hash); self } diff --git a/ethers-contract/src/factory.rs b/ethers-contract/src/factory.rs index bef22c23..0ad62731 100644 --- a/ethers-contract/src/factory.rs +++ b/ethers-contract/src/factory.rs @@ -37,7 +37,7 @@ impl Deployer { pub async fn send(self) -> Result, ContractError> { let pending_tx = self .client - .send_transaction(self.tx, Some(self.block)) + .send_transaction(self.tx, Some(self.block.into())) .await .map_err(ContractError::MiddlewareError)?; diff --git a/ethers-contract/tests/contract.rs b/ethers-contract/tests/contract.rs index 95ef67b2..5a17c643 100644 --- a/ethers-contract/tests/contract.rs +++ b/ethers-contract/tests/contract.rs @@ -1,4 +1,7 @@ -use ethers::{contract::ContractFactory, types::H256}; +use ethers::{ + contract::ContractFactory, + types::{BlockId, H256}, +}; mod common; pub use common::*; @@ -96,7 +99,7 @@ mod eth_tests { let client = connect(&ganache, 0); let contract = deploy(client.clone(), abi, bytecode).await; - // make a call with `client2` + // make a call with `client` let _tx_hash = *contract .method::<_, H256>("setValue", "hi".to_owned()) .unwrap() @@ -116,6 +119,133 @@ mod eth_tests { assert_eq!(logs[0].new_value, "initial value"); assert_eq!(logs[1].new_value, "hi"); assert_eq!(logs.len(), 2); + + // and we can fetch the events at a block hash + let hash = client.get_block(1).await.unwrap().unwrap().hash.unwrap(); + let logs: Vec = contract + .event("ValueChanged") + .unwrap() + .at_block_hash(hash) + .topic1(client.address()) // Corresponds to the first indexed parameter + .query() + .await + .unwrap(); + assert_eq!(logs[0].new_value, "initial value"); + assert_eq!(logs.len(), 1); + } + + #[tokio::test] + async fn call_past_state() { + let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol"); + let ganache = Ganache::new().spawn(); + let client = connect(&ganache, 0); + let contract = deploy(client.clone(), abi, bytecode).await; + let deployed_block = client.get_block_number().await.unwrap(); + + // assert initial state + let value = contract + .method::<_, String>("getValue", ()) + .unwrap() + .call() + .await + .unwrap(); + assert_eq!(value, "initial value"); + + // make a call with `client` + let _tx_hash = *contract + .method::<_, H256>("setValue", "hi".to_owned()) + .unwrap() + .send() + .await + .unwrap(); + + // assert new value + let value = contract + .method::<_, String>("getValue", ()) + .unwrap() + .call() + .await + .unwrap(); + assert_eq!(value, "hi"); + + // assert previous value + let value = contract + .method::<_, String>("getValue", ()) + .unwrap() + .block(BlockId::Number(deployed_block.into())) + .call() + .await + .unwrap(); + assert_eq!(value, "initial value"); + + // Here would be the place to test EIP-1898, specifying the `BlockId` of `call` as the + // first block hash. However, Ganache does not implement this :/ + + // let hash = client.get_block(1).await.unwrap().unwrap().hash.unwrap(); + // let value = contract + // .method::<_, String>("getValue", ()) + // .unwrap() + // .block(BlockId::Hash(hash)) + // .call() + // .await + // .unwrap(); + // assert_eq!(value, "initial value"); + } + + #[tokio::test] + #[ignore] + async fn call_past_hash_test() { + // geth --dev --http --http.api eth,web3 + let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol"); + let provider = Provider::::try_from("http://localhost:8545").unwrap(); + let deployer = provider.get_accounts().await.unwrap()[0]; + + let client = Arc::new(provider.with_sender(deployer)); + let contract = deploy(client.clone(), abi, bytecode).await; + let deployed_block = client.get_block_number().await.unwrap(); + + // assert initial state + let value = contract + .method::<_, String>("getValue", ()) + .unwrap() + .call() + .await + .unwrap(); + assert_eq!(value, "initial value"); + + // make a call with `client` + let _tx_hash = *contract + .method::<_, H256>("setValue", "hi".to_owned()) + .unwrap() + .send() + .await + .unwrap(); + + // assert new value + let value = contract + .method::<_, String>("getValue", ()) + .unwrap() + .call() + .await + .unwrap(); + assert_eq!(value, "hi"); + + // assert previous value using block hash + let hash = client + .get_block(deployed_block) + .await + .unwrap() + .unwrap() + .hash + .unwrap(); + let value = contract + .method::<_, String>("getValue", ()) + .unwrap() + .block(BlockId::Hash(hash)) + .call() + .await + .unwrap(); + assert_eq!(value, "initial value"); } #[tokio::test] diff --git a/ethers-core/src/types/log.rs b/ethers-core/src/types/log.rs index 61f6af79..b2d05221 100644 --- a/ethers-core/src/types/log.rs +++ b/ethers-core/src/types/log.rs @@ -62,14 +62,63 @@ pub struct Log { pub removed: Option, } +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum FilterBlockOption { + Range { + from_block: Option, + to_block: Option, + }, + AtBlockHash(H256), +} + +impl Default for FilterBlockOption { + fn default() -> Self { + FilterBlockOption::Range { + from_block: None, + to_block: None, + } + } +} + +impl FilterBlockOption { + pub fn set_from_block(&self, block: BlockNumber) -> Self { + let to_block = if let FilterBlockOption::Range { to_block, .. } = self { + *to_block + } else { + None + }; + + FilterBlockOption::Range { + from_block: Some(block), + to_block, + } + } + + pub fn set_to_block(&self, block: BlockNumber) -> Self { + let from_block = if let FilterBlockOption::Range { from_block, .. } = self { + *from_block + } else { + None + }; + + FilterBlockOption::Range { + from_block, + to_block: Some(block), + } + } + + pub fn set_hash(&self, hash: H256) -> Self { + FilterBlockOption::AtBlockHash(hash) + } +} + /// Filter for #[derive(Default, Debug, PartialEq, Clone)] pub struct Filter { - /// From Block - pub from_block: Option, - - /// To Block - pub to_block: Option, + /// Filter block options, specifying on which blocks the filter should + /// match. + // https://eips.ethereum.org/EIPS/eip-234 + pub block_option: FilterBlockOption, /// Address // TODO: The spec says that this can also be an array, do we really want to @@ -91,12 +140,21 @@ impl Serialize for Filter { S: Serializer, { let mut s = serializer.serialize_struct("Filter", 5)?; - if let Some(ref from_block) = self.from_block { - s.serialize_field("fromBlock", from_block)?; - } + match self.block_option { + FilterBlockOption::Range { + from_block, + to_block, + } => { + if let Some(ref from_block) = from_block { + s.serialize_field("fromBlock", from_block)?; + } - if let Some(ref to_block) = self.to_block { - s.serialize_field("toBlock", to_block)?; + if let Some(ref to_block) = to_block { + s.serialize_field("toBlock", to_block)?; + } + } + + FilterBlockOption::AtBlockHash(ref h) => s.serialize_field("blockHash", h)?, } if let Some(ref address) = self.address { @@ -131,13 +189,19 @@ impl Filter { #[allow(clippy::wrong_self_convention)] pub fn from_block>(mut self, block: T) -> Self { - self.from_block = Some(block.into()); + self.block_option = self.block_option.set_from_block(block.into()); self } #[allow(clippy::wrong_self_convention)] pub fn to_block>(mut self, block: T) -> Self { - self.to_block = Some(block.into()); + self.block_option = self.block_option.set_to_block(block.into()); + self + } + + #[allow(clippy::wrong_self_convention)] + pub fn at_block_hash>(mut self, hash: T) -> Self { + self.block_option = self.block_option.set_hash(hash.into()); self } diff --git a/ethers-middleware/src/gas_escalator/mod.rs b/ethers-middleware/src/gas_escalator/mod.rs index 99be5ffc..5eff862f 100644 --- a/ethers-middleware/src/gas_escalator/mod.rs +++ b/ethers-middleware/src/gas_escalator/mod.rs @@ -5,7 +5,7 @@ mod linear; pub use linear::LinearGasPrice; use async_trait::async_trait; -use ethers_core::types::{BlockNumber, TransactionRequest, TxHash, U256}; +use ethers_core::types::{BlockId, TransactionRequest, TxHash, U256}; use ethers_providers::{interval, FromErr, Middleware, PendingTransaction, StreamExt}; use futures_util::lock::Mutex; use std::sync::Arc; @@ -64,7 +64,7 @@ pub struct GasEscalatorMiddleware { pub(crate) escalator: E, /// The transactions which are currently being monitored for escalation #[allow(clippy::type_complexity)] - pub txs: Arc)>>>, + pub txs: Arc)>>>, frequency: Frequency, } @@ -85,7 +85,7 @@ where async fn send_transaction( &self, tx: TransactionRequest, - block: Option, + block: Option, ) -> Result, Self::Error> { let pending_tx = self .inner() diff --git a/ethers-middleware/src/gas_oracle/middleware.rs b/ethers-middleware/src/gas_oracle/middleware.rs index d5ee88fd..ac8ee97b 100644 --- a/ethers-middleware/src/gas_oracle/middleware.rs +++ b/ethers-middleware/src/gas_oracle/middleware.rs @@ -59,7 +59,7 @@ where async fn send_transaction( &self, mut tx: TransactionRequest, - block: Option, + block: Option, ) -> Result, Self::Error> { if tx.gas_price.is_none() { tx.gas_price = Some(self.get_gas_price().await?); diff --git a/ethers-middleware/src/nonce_manager.rs b/ethers-middleware/src/nonce_manager.rs index a7c18181..b7f22cb1 100644 --- a/ethers-middleware/src/nonce_manager.rs +++ b/ethers-middleware/src/nonce_manager.rs @@ -37,7 +37,7 @@ where async fn get_transaction_count_with_manager( &self, - block: Option, + block: Option, ) -> Result> { // initialize the nonce the first time the manager is called if !self.initialized.load(Ordering::SeqCst) { @@ -87,7 +87,7 @@ where async fn send_transaction( &self, mut tx: TransactionRequest, - block: Option, + block: Option, ) -> Result, Self::Error> { if tx.nonce.is_none() { tx.nonce = Some(self.get_transaction_count_with_manager(block).await?); diff --git a/ethers-middleware/src/signer.rs b/ethers-middleware/src/signer.rs index e8e7a7f5..f9c771e6 100644 --- a/ethers-middleware/src/signer.rs +++ b/ethers-middleware/src/signer.rs @@ -1,7 +1,6 @@ use ethers_core::{ types::{ - Address, BlockNumber, Bytes, NameOrAddress, Signature, Transaction, TransactionRequest, - U256, + Address, BlockId, Bytes, NameOrAddress, Signature, Transaction, TransactionRequest, U256, }, utils::keccak256, }; @@ -176,7 +175,7 @@ where async fn fill_transaction( &self, tx: &mut TransactionRequest, - block: Option, + block: Option, ) -> Result<(), SignerMiddlewareError> { // set the `from` field if tx.from.is_none() { @@ -246,7 +245,7 @@ where async fn send_transaction( &self, mut tx: TransactionRequest, - block: Option, + block: Option, ) -> Result, Self::Error> { if let Some(NameOrAddress::Name(ens_name)) = tx.to { let addr = self diff --git a/ethers-middleware/src/transformer/middleware.rs b/ethers-middleware/src/transformer/middleware.rs index 09d49987..65919c6f 100644 --- a/ethers-middleware/src/transformer/middleware.rs +++ b/ethers-middleware/src/transformer/middleware.rs @@ -56,7 +56,7 @@ where async fn send_transaction( &self, mut tx: TransactionRequest, - block: Option, + block: Option, ) -> Result, Self::Error> { // resolve the to field if that's an ENS name. if let Some(NameOrAddress::Name(ens_name)) = tx.to { diff --git a/ethers-middleware/tests/nonce_manager.rs b/ethers-middleware/tests/nonce_manager.rs index e539f41a..6348590f 100644 --- a/ethers-middleware/tests/nonce_manager.rs +++ b/ethers-middleware/tests/nonce_manager.rs @@ -25,7 +25,7 @@ async fn nonce_manager() { let provider = NonceManagerMiddleware::new(provider, address); let nonce = provider - .get_transaction_count(address, Some(BlockNumber::Pending)) + .get_transaction_count(address, Some(BlockNumber::Pending.into())) .await .unwrap() .as_u64(); diff --git a/ethers-providers/src/lib.rs b/ethers-providers/src/lib.rs index db141447..8a95adf4 100644 --- a/ethers-providers/src/lib.rs +++ b/ethers-providers/src/lib.rs @@ -188,7 +188,7 @@ pub trait Middleware: Sync + Send + Debug { async fn send_transaction( &self, tx: TransactionRequest, - block: Option, + block: Option, ) -> Result, Self::Error> { self.inner() .send_transaction(tx, block) @@ -233,7 +233,7 @@ pub trait Middleware: Sync + Send + Debug { async fn get_transaction_count + Send + Sync>( &self, from: T, - block: Option, + block: Option, ) -> Result { self.inner() .get_transaction_count(from, block) @@ -248,7 +248,7 @@ pub trait Middleware: Sync + Send + Debug { async fn call( &self, tx: &TransactionRequest, - block: Option, + block: Option, ) -> Result { self.inner().call(tx, block).await.map_err(FromErr::from) } @@ -260,7 +260,7 @@ pub trait Middleware: Sync + Send + Debug { async fn get_balance + Send + Sync>( &self, from: T, - block: Option, + block: Option, ) -> Result { self.inner() .get_balance(from, block) @@ -375,7 +375,7 @@ pub trait Middleware: Sync + Send + Debug { async fn get_code + Send + Sync>( &self, at: T, - block: Option, + block: Option, ) -> Result { self.inner() .get_code(at, block) @@ -387,7 +387,7 @@ pub trait Middleware: Sync + Send + Debug { &self, from: T, location: H256, - block: Option, + block: Option, ) -> Result { self.inner() .get_storage_at(from, location, block) diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs index edd5ad4f..936f62b4 100644 --- a/ethers-providers/src/provider.rs +++ b/ethers-providers/src/provider.rs @@ -221,7 +221,7 @@ impl Middleware for Provider

{ async fn get_transaction_count + Send + Sync>( &self, from: T, - block: Option, + block: Option, ) -> Result { let from = match from.into() { NameOrAddress::Name(ens_name) => self.resolve_name(&ens_name).await?, @@ -229,7 +229,7 @@ impl Middleware for Provider

{ }; let from = utils::serialize(&from); - let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest)); + let block = utils::serialize(&block.unwrap_or_else(|| BlockNumber::Latest.into())); self.request("eth_getTransactionCount", [from, block]).await } @@ -237,7 +237,7 @@ impl Middleware for Provider

{ async fn get_balance + Send + Sync>( &self, from: T, - block: Option, + block: Option, ) -> Result { let from = match from.into() { NameOrAddress::Name(ens_name) => self.resolve_name(&ens_name).await?, @@ -245,7 +245,7 @@ impl Middleware for Provider

{ }; let from = utils::serialize(&from); - let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest)); + let block = utils::serialize(&block.unwrap_or_else(|| BlockNumber::Latest.into())); self.request("eth_getBalance", [from, block]).await } @@ -264,10 +264,10 @@ impl Middleware for Provider

{ async fn call( &self, tx: &TransactionRequest, - block: Option, + block: Option, ) -> Result { let tx = utils::serialize(tx); - let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest)); + let block = utils::serialize(&block.unwrap_or_else(|| BlockNumber::Latest.into())); self.request("eth_call", [tx, block]).await } @@ -283,7 +283,7 @@ impl Middleware for Provider

{ async fn send_transaction( &self, mut tx: TransactionRequest, - _: Option, + _: Option, ) -> Result, ProviderError> { if tx.from.is_none() { tx.from = self.3; @@ -426,7 +426,7 @@ impl Middleware for Provider

{ &self, from: T, location: H256, - block: Option, + block: Option, ) -> Result { let from = match from.into() { NameOrAddress::Name(ens_name) => self.resolve_name(&ens_name).await?, @@ -435,7 +435,7 @@ impl Middleware for Provider

{ let from = utils::serialize(&from); let location = utils::serialize(&location); - let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest)); + let block = utils::serialize(&block.unwrap_or_else(|| BlockNumber::Latest.into())); // get the hex encoded value. let value: String = self @@ -450,7 +450,7 @@ impl Middleware for Provider

{ async fn get_code + Send + Sync>( &self, at: T, - block: Option, + block: Option, ) -> Result { let at = match at.into() { NameOrAddress::Name(ens_name) => self.resolve_name(&ens_name).await?, @@ -458,7 +458,7 @@ impl Middleware for Provider

{ }; let at = utils::serialize(&at); - let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest)); + let block = utils::serialize(&block.unwrap_or_else(|| BlockNumber::Latest.into())); self.request("eth_getCode", [at, block]).await } diff --git a/ethers/examples/transfer_eth.rs b/ethers/examples/transfer_eth.rs index ad46f36a..b3a9be6a 100644 --- a/ethers/examples/transfer_eth.rs +++ b/ethers/examples/transfer_eth.rs @@ -23,11 +23,11 @@ async fn main() -> Result<()> { println!("{}", serde_json::to_string(&tx)?); let nonce1 = provider - .get_transaction_count(from, Some(BlockNumber::Latest)) + .get_transaction_count(from, Some(BlockNumber::Latest.into())) .await?; let nonce2 = provider - .get_transaction_count(from, Some(BlockNumber::Number(0.into()))) + .get_transaction_count(from, Some(BlockNumber::Number(0.into()).into())) .await?; assert!(nonce2 < nonce1);