diff --git a/ethers-contract/src/call.rs b/ethers-contract/src/call.rs index b8a4c76b..d9f18f30 100644 --- a/ethers-contract/src/call.rs +++ b/ethers-contract/src/call.rs @@ -102,7 +102,7 @@ where /// Returns the estimated gas cost for the underlying transaction to be executed pub async fn estimate_gas(&self) -> Result> { self.client - .estimate_gas(&self.tx) + .estimate_gas(&self.tx.clone().into()) .await .map_err(ContractError::MiddlewareError) } @@ -119,7 +119,7 @@ where pub async fn call(&self) -> Result> { let bytes = self .client - .call(&self.tx, self.block) + .call(&self.tx.clone().into(), self.block) .await .map_err(ContractError::MiddlewareError)?; diff --git a/ethers-core/src/types/transaction/eip1559.rs b/ethers-core/src/types/transaction/eip1559.rs index 29d39da2..d8c2adda 100644 --- a/ethers-core/src/types/transaction/eip1559.rs +++ b/ethers-core/src/types/transaction/eip1559.rs @@ -1,4 +1,4 @@ -use super::{eip2930::AccessList, rlp_opt}; +use super::{eip2930::AccessList, normalize_v, rlp_opt}; use crate::{ types::{Address, Bytes, NameOrAddress, Signature, H256, U256, U64}, utils::keccak256, @@ -37,12 +37,8 @@ pub struct Eip1559TransactionRequest { #[serde(skip_serializing_if = "Option::is_none")] pub nonce: Option, - #[serde( - rename = "accessList", - default, - skip_serializing_if = "Option::is_none" - )] - pub access_list: Option, + #[serde(rename = "accessList", default)] + pub access_list: AccessList, #[serde( rename = "maxPriorityFeePerGas", @@ -121,7 +117,7 @@ impl Eip1559TransactionRequest { /// Sets the `access_list` field in the transaction to the provided value pub fn access_list>(mut self, access_list: T) -> Self { - self.access_list = Some(access_list.into()); + self.access_list = access_list.into(); self } @@ -148,10 +144,12 @@ impl Eip1559TransactionRequest { pub fn rlp_signed>(&self, chain_id: T, signature: &Signature) -> Bytes { let mut rlp = RlpStream::new(); rlp.begin_unbounded_list(); + let chain_id = chain_id.into(); self.rlp_base(chain_id, &mut rlp); // append the signature - rlp.append(&signature.v); + let v = normalize_v(signature.v, chain_id); + rlp.append(&v); rlp.append(&signature.r); rlp.append(&signature.s); rlp.finalize_unbounded_list(); @@ -167,6 +165,6 @@ impl Eip1559TransactionRequest { rlp_opt(rlp, &self.to.as_ref()); rlp_opt(rlp, &self.value); rlp_opt(rlp, &self.data.as_ref().map(|d| d.as_ref())); - rlp_opt(rlp, &self.access_list); + rlp.append(&self.access_list); } } diff --git a/ethers-core/src/types/transaction/eip2718.rs b/ethers-core/src/types/transaction/eip2718.rs index fd23e178..1ae93f25 100644 --- a/ethers-core/src/types/transaction/eip2718.rs +++ b/ethers-core/src/types/transaction/eip2718.rs @@ -19,9 +19,10 @@ pub enum TypedTransaction { Eip1559(Eip1559TransactionRequest), } +use TypedTransaction::*; + impl TypedTransaction { pub fn from(&self) -> Option<&Address> { - use TypedTransaction::*; match self { Legacy(inner) => inner.from.as_ref(), Eip2930(inner) => inner.tx.from.as_ref(), @@ -30,7 +31,6 @@ impl TypedTransaction { } pub fn set_from(&mut self, from: Address) { - use TypedTransaction::*; match self { Legacy(inner) => inner.from = Some(from), Eip2930(inner) => inner.tx.from = Some(from), @@ -39,7 +39,6 @@ impl TypedTransaction { } pub fn to(&self) -> Option<&NameOrAddress> { - use TypedTransaction::*; match self { Legacy(inner) => inner.to.as_ref(), Eip2930(inner) => inner.tx.to.as_ref(), @@ -49,7 +48,6 @@ impl TypedTransaction { pub fn set_to>(&mut self, to: T) { let to = to.into(); - use TypedTransaction::*; match self { Legacy(inner) => inner.to = Some(to), Eip2930(inner) => inner.tx.to = Some(to), @@ -58,7 +56,6 @@ impl TypedTransaction { } pub fn nonce(&self) -> Option<&U256> { - use TypedTransaction::*; match self { Legacy(inner) => inner.nonce.as_ref(), Eip2930(inner) => inner.tx.nonce.as_ref(), @@ -68,7 +65,6 @@ impl TypedTransaction { pub fn set_nonce>(&mut self, nonce: T) { let nonce = nonce.into(); - use TypedTransaction::*; match self { Legacy(inner) => inner.nonce = Some(nonce), Eip2930(inner) => inner.tx.nonce = Some(nonce), @@ -77,7 +73,6 @@ impl TypedTransaction { } pub fn value(&self) -> Option<&U256> { - use TypedTransaction::*; match self { Legacy(inner) => inner.value.as_ref(), Eip2930(inner) => inner.tx.value.as_ref(), @@ -87,7 +82,6 @@ impl TypedTransaction { pub fn set_value>(&mut self, value: T) { let value = value.into(); - use TypedTransaction::*; match self { Legacy(inner) => inner.value = Some(value), Eip2930(inner) => inner.tx.value = Some(value), @@ -96,7 +90,6 @@ impl TypedTransaction { } pub fn gas(&self) -> Option<&U256> { - use TypedTransaction::*; match self { Legacy(inner) => inner.gas.as_ref(), Eip2930(inner) => inner.tx.gas.as_ref(), @@ -106,7 +99,6 @@ impl TypedTransaction { pub fn set_gas>(&mut self, gas: T) { let gas = gas.into(); - use TypedTransaction::*; match self { Legacy(inner) => inner.gas = Some(gas), Eip2930(inner) => inner.tx.gas = Some(gas), @@ -116,7 +108,6 @@ impl TypedTransaction { pub fn set_gas_price>(&mut self, gas_price: T) { let gas_price = gas_price.into(); - use TypedTransaction::*; match self { Legacy(inner) => inner.gas_price = Some(gas_price), Eip2930(inner) => inner.tx.gas_price = Some(gas_price), @@ -128,7 +119,6 @@ impl TypedTransaction { } pub fn data(&self) -> Option<&Bytes> { - use TypedTransaction::*; match self { Legacy(inner) => inner.data.as_ref(), Eip2930(inner) => inner.tx.data.as_ref(), @@ -137,7 +127,6 @@ impl TypedTransaction { } pub fn set_data(&mut self, data: Bytes) { - use TypedTransaction::*; match self { Legacy(inner) => inner.data = Some(data), Eip2930(inner) => inner.tx.data = Some(data), @@ -146,12 +135,10 @@ impl TypedTransaction { } pub fn rlp_signed>(&self, chain_id: T, signature: &Signature) -> Bytes { - use TypedTransaction::*; let mut encoded = vec![]; match self { - Legacy(inner) => { - encoded.extend_from_slice(&[0x0]); - encoded.extend_from_slice(inner.rlp_signed(signature).as_ref()); + Legacy(ref tx) => { + encoded.extend_from_slice(tx.rlp_signed(signature).as_ref()); } Eip2930(inner) => { encoded.extend_from_slice(&[0x1]); @@ -162,17 +149,14 @@ impl TypedTransaction { encoded.extend_from_slice(inner.rlp_signed(chain_id, signature).as_ref()); } }; - - rlp::encode(&encoded).freeze().into() + encoded.into() } pub fn rlp>(&self, chain_id: T) -> Bytes { let chain_id = chain_id.into(); let mut encoded = vec![]; - use TypedTransaction::*; match self { Legacy(inner) => { - encoded.extend_from_slice(&[0x0]); encoded.extend_from_slice(inner.rlp(chain_id).as_ref()); } Eip2930(inner) => { diff --git a/ethers-core/src/types/transaction/eip2930.rs b/ethers-core/src/types/transaction/eip2930.rs index b8577d69..f78730ba 100644 --- a/ethers-core/src/types/transaction/eip2930.rs +++ b/ethers-core/src/types/transaction/eip2930.rs @@ -1,5 +1,5 @@ -use super::request::TransactionRequest; -use crate::types::{Address, Bytes, Signature, H256, U64}; +use super::{normalize_v, request::TransactionRequest}; +use crate::types::{Address, Bytes, Signature, H256, U256, U64}; use rlp::RlpStream; use rlp_derive::{RlpEncodable, RlpEncodableWrapper}; @@ -13,6 +13,13 @@ const NUM_EIP2930_FIELDS: usize = 8; #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, RlpEncodableWrapper)] pub struct AccessList(pub Vec); +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct AccessListWithGasUsed { + pub access_list: AccessList, + pub gas_used: U256, +} + impl From> for AccessList { fn from(src: Vec) -> AccessList { AccessList(src) @@ -69,13 +76,15 @@ impl Eip2930TransactionRequest { let mut rlp = RlpStream::new(); rlp.begin_list(NUM_EIP2930_FIELDS + 3); - rlp.append(&chain_id.into()); + let chain_id = chain_id.into(); + rlp.append(&chain_id); self.tx.rlp_base(&mut rlp); // append the access list in addition to the base rlp encoding rlp.append(&self.access_list); // append the signature - rlp.append(&signature.v); + let v = normalize_v(signature.v, chain_id); + rlp.append(&v); rlp.append(&signature.r); rlp.append(&signature.s); rlp.out().freeze().into() @@ -104,8 +113,6 @@ mod tests { let hash = tx.sighash(1); let sig: Signature = "c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b266032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d3752101".parse().unwrap(); - let enc = tx.rlp_signed(1, &sig); - assert_eq!( hash, "49b486f0ec0a60dfbbca2d30cb07c9e8ffb2a2ff41f29a1ab6737475f6ff69f3" @@ -113,6 +120,7 @@ mod tests { .unwrap() ); + let enc = rlp::encode(&tx.rlp_signed(1, &sig).as_ref()); let expected = "b86601f8630103018261a894b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a825544c001a0c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b2660a032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d37521"; assert_eq!(hex::encode(enc.to_vec()), expected); } diff --git a/ethers-core/src/types/transaction/mod.rs b/ethers-core/src/types/transaction/mod.rs index 5e95d089..51c5f9c9 100644 --- a/ethers-core/src/types/transaction/mod.rs +++ b/ethers-core/src/types/transaction/mod.rs @@ -21,3 +21,12 @@ pub(super) fn rlp_opt(rlp: &mut rlp::RlpStream, opt: &Option< rlp.append(&""); } } + +/// normalizes the signature back to 0/1 +pub(crate) fn normalize_v(v: u64, chain_id: crate::types::U64) -> u64 { + if v > 1 { + v - chain_id.as_u64() * 2 - 35 + } else { + v + } +} diff --git a/ethers-middleware/src/gas_escalator/mod.rs b/ethers-middleware/src/gas_escalator/mod.rs index 5eff862f..f8301f10 100644 --- a/ethers-middleware/src/gas_escalator/mod.rs +++ b/ethers-middleware/src/gas_escalator/mod.rs @@ -1,4 +1,5 @@ mod geometric; +use ethers_core::types::transaction::eip2718::TypedTransaction; pub use geometric::GeometricGasPrice; mod linear; @@ -82,17 +83,25 @@ where &self.inner } - async fn send_transaction( + async fn send_transaction + Send + Sync>( &self, - tx: TransactionRequest, + tx: T, block: Option, ) -> Result, Self::Error> { + let tx = tx.into(); + let pending_tx = self .inner() .send_transaction(tx.clone(), block) .await .map_err(GasEscalatorError::MiddlewareError)?; + let tx = match tx { + TypedTransaction::Legacy(inner) => inner, + TypedTransaction::Eip2930(inner) => inner.tx, + _ => return Err(GasEscalatorError::UnsupportedTxType), + }; + // insert the tx in the pending txs let mut lock = self.txs.lock().await; lock.push((*pending_tx, tx, Instant::now(), block)); @@ -231,4 +240,7 @@ pub enum GasEscalatorError { #[error("{0}")] /// Thrown when an internal middleware errors MiddlewareError(M::Error), + + #[error("Gas escalation is only supported for EIP2930 or Legacy transactions")] + UnsupportedTxType, } diff --git a/ethers-middleware/src/gas_oracle/eth_gas_station.rs b/ethers-middleware/src/gas_oracle/eth_gas_station.rs index 6c7bc230..f5c37d09 100644 --- a/ethers-middleware/src/gas_oracle/eth_gas_station.rs +++ b/ethers-middleware/src/gas_oracle/eth_gas_station.rs @@ -21,7 +21,7 @@ pub struct EthGasStation { #[derive(Deserialize)] struct EthGasStationResponse { #[serde(rename = "safeLow")] - safe_low: u64, + safe_low: f64, average: u64, fast: u64, fastest: u64, @@ -63,7 +63,7 @@ impl GasOracle for EthGasStation { .await?; let gas_price = match self.gas_category { - GasCategory::SafeLow => U256::from((res.safe_low * GWEI_TO_WEI) / 10), + GasCategory::SafeLow => U256::from((res.safe_low.ceil() as u64 * GWEI_TO_WEI) / 10), GasCategory::Standard => U256::from((res.average * GWEI_TO_WEI) / 10), GasCategory::Fast => U256::from((res.fast * GWEI_TO_WEI) / 10), GasCategory::Fastest => U256::from((res.fastest * GWEI_TO_WEI) / 10), diff --git a/ethers-middleware/src/gas_oracle/middleware.rs b/ethers-middleware/src/gas_oracle/middleware.rs index ac8ee97b..d77f3262 100644 --- a/ethers-middleware/src/gas_oracle/middleware.rs +++ b/ethers-middleware/src/gas_oracle/middleware.rs @@ -1,6 +1,6 @@ use super::{GasOracle, GasOracleError}; use async_trait::async_trait; -use ethers_core::types::*; +use ethers_core::types::{transaction::eip2718::TypedTransaction, *}; use ethers_providers::{FromErr, Middleware, PendingTransaction}; use thiserror::Error; @@ -28,6 +28,9 @@ pub enum MiddlewareError { #[error("{0}")] MiddlewareError(M::Error), + + #[error("This gas price oracle only works with Legacy and EIP2930 transactions.")] + UnsupportedTxType, } impl FromErr for MiddlewareError { @@ -56,14 +59,28 @@ where Ok(self.gas_oracle.fetch().await?) } - async fn send_transaction( + async fn send_transaction + Send + Sync>( &self, - mut tx: TransactionRequest, + tx: T, block: Option, ) -> Result, Self::Error> { - if tx.gas_price.is_none() { - tx.gas_price = Some(self.get_gas_price().await?); - } + let mut tx = tx.into(); + + match tx { + TypedTransaction::Legacy(ref mut tx) => { + if tx.gas_price.is_none() { + tx.gas_price = Some(self.get_gas_price().await?); + } + } + TypedTransaction::Eip2930(ref mut inner) => { + if inner.tx.gas_price.is_none() { + inner.tx.gas_price = Some(self.get_gas_price().await?); + } + } + TypedTransaction::Eip1559(_) => { + return Err(MiddlewareError::UnsupportedTxType); + } + }; self.inner .send_transaction(tx, block) .await diff --git a/ethers-middleware/src/nonce_manager.rs b/ethers-middleware/src/nonce_manager.rs index b7f22cb1..94c82f28 100644 --- a/ethers-middleware/src/nonce_manager.rs +++ b/ethers-middleware/src/nonce_manager.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use ethers_core::types::transaction::eip2718::TypedTransaction; use ethers_core::types::*; use ethers_providers::{FromErr, Middleware, PendingTransaction}; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; @@ -84,17 +85,18 @@ where /// Signs and broadcasts the transaction. The optional parameter `block` can be passed so that /// gas cost and nonce calculations take it into account. For simple transactions this can be /// left to `None`. - async fn send_transaction( + async fn send_transaction + Send + Sync>( &self, - mut tx: TransactionRequest, + tx: T, block: Option, ) -> Result, Self::Error> { - if tx.nonce.is_none() { - tx.nonce = Some(self.get_transaction_count_with_manager(block).await?); + let mut tx = tx.into(); + + if tx.nonce().is_none() { + tx.set_nonce(self.get_transaction_count_with_manager(block).await?); } - let mut tx_clone = tx.clone(); - match self.inner.send_transaction(tx, block).await { + match self.inner.send_transaction(tx.clone(), block).await { Ok(tx_hash) => Ok(tx_hash), Err(err) => { let nonce = self.get_transaction_count(self.address, block).await?; @@ -102,9 +104,9 @@ where // try re-submitting the transaction with the correct nonce if there // was a nonce mismatch self.nonce.store(nonce.as_u64(), Ordering::SeqCst); - tx_clone.nonce = Some(nonce); + tx.set_nonce(nonce); self.inner - .send_transaction(tx_clone, block) + .send_transaction(tx, block) .await .map_err(FromErr::from) } else { diff --git a/ethers-middleware/src/signer.rs b/ethers-middleware/src/signer.rs index a7333884..1dd5f088 100644 --- a/ethers-middleware/src/signer.rs +++ b/ethers-middleware/src/signer.rs @@ -1,10 +1,10 @@ -use ethers_core::types::{Address, BlockId, Bytes, NameOrAddress, Signature, TransactionRequest}; -use ethers_providers::{FromErr, Middleware, PendingTransaction}; +use ethers_core::types::{ + transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, Signature, +}; +use ethers_providers::{maybe, FromErr, Middleware, PendingTransaction}; use ethers_signers::Signer; use async_trait::async_trait; -use futures_util::{future::ok, join}; -use std::future::Future; use thiserror::Error; #[derive(Clone, Debug)] @@ -118,7 +118,7 @@ where /// Signs and returns the RLP encoding of the signed transaction async fn sign_transaction( &self, - tx: TransactionRequest, + tx: TypedTransaction, ) -> Result> { let signature = self .signer @@ -127,33 +127,7 @@ where .map_err(SignerMiddlewareError::SignerError)?; // Return the raw rlp-encoded signed transaction - Ok(tx.rlp_signed(&signature)) - } - - async fn fill_transaction( - &self, - tx: &mut TransactionRequest, - block: Option, - ) -> Result<(), SignerMiddlewareError> { - // set the `from` field - if tx.from.is_none() { - tx.from = Some(self.address()); - } - - // will poll and await the futures concurrently - let (gas_price, gas, nonce) = join!( - maybe(tx.gas_price, self.inner.get_gas_price()), - maybe(tx.gas, self.inner.estimate_gas(tx)), - maybe( - tx.nonce, - self.inner.get_transaction_count(self.address(), block) - ), - ); - tx.gas_price = Some(gas_price.map_err(SignerMiddlewareError::MiddlewareError)?); - tx.gas = Some(gas.map_err(SignerMiddlewareError::MiddlewareError)?); - tx.nonce = Some(nonce.map_err(SignerMiddlewareError::MiddlewareError)?); - - Ok(()) + Ok(tx.rlp_signed(self.signer.chain_id(), &signature)) } /// Returns the client's address @@ -192,21 +166,54 @@ where &self.inner } + /// Returns the client's address + fn default_sender(&self) -> Option
{ + Some(self.address) + } + /// `SignerMiddleware` is instantiated with a signer. async fn is_signer(&self) -> bool { true } + /// Helper for filling a transaction's nonce using the wallet + async fn fill_transaction( + &self, + tx: &mut TypedTransaction, + block: Option, + ) -> Result<(), Self::Error> { + // get the `from` field's nonce if it's set, else get the signer's nonce + let from = if tx.from().is_some() && tx.from() != Some(&self.address()) { + *tx.from().unwrap() + } else { + self.address + }; + tx.set_from(from); + + let nonce = maybe(tx.nonce().cloned(), self.get_transaction_count(from, block)).await?; + tx.set_nonce(nonce); + self.inner() + .fill_transaction(tx, block) + .await + .map_err(SignerMiddlewareError::MiddlewareError)?; + Ok(()) + } + /// Signs and broadcasts the transaction. The optional parameter `block` can be passed so that /// gas cost and nonce calculations take it into account. For simple transactions this can be /// left to `None`. - async fn send_transaction( + async fn send_transaction + Send + Sync>( &self, - mut tx: TransactionRequest, + tx: T, block: Option, ) -> Result, Self::Error> { + let mut tx = tx.into(); + + // fill any missing fields + self.fill_transaction(&mut tx, block).await?; + // If the from address is set and is not our signer, delegate to inner - if tx.from.is_some() && tx.from != Some(self.address()) { + if tx.from().is_some() && tx.from() != Some(&self.address()) { return self .inner .send_transaction(tx, block) @@ -214,18 +221,6 @@ where .map_err(SignerMiddlewareError::MiddlewareError); } - if let Some(NameOrAddress::Name(ens_name)) = tx.to { - let addr = self - .inner - .resolve_name(&ens_name) - .await - .map_err(SignerMiddlewareError::MiddlewareError)?; - tx.to = Some(addr.into()) - } - - // fill any missing fields - self.fill_transaction(&mut tx, block).await?; - // if we have a nonce manager set, we should try handling the result in // case there was a nonce mismatch let signed_tx = self.sign_transaction(tx).await?; @@ -251,23 +246,14 @@ where } } -/// Calls the future if `item` is None, otherwise returns a `futures::ok` -async fn maybe(item: Option, f: F) -> Result -where - F: Future>, -{ - if let Some(item) = item { - ok(item).await - } else { - f.await - } -} - #[cfg(all(test, not(feature = "celo")))] mod tests { use super::*; use ethers::{providers::Provider, signers::LocalWallet}; - use ethers_core::utils::{self, keccak256, Ganache}; + use ethers_core::{ + types::TransactionRequest, + utils::{self, keccak256, Ganache}, + }; use std::convert::TryFrom; #[tokio::test] @@ -287,7 +273,8 @@ mod tests { nonce: Some(0.into()), gas_price: Some(21_000_000_000u128.into()), data: None, - }; + } + .into(); let chain_id = 1u64; let provider = Provider::try_from("http://localhost:8545").unwrap(); diff --git a/ethers-middleware/src/transformer/ds_proxy/mod.rs b/ethers-middleware/src/transformer/ds_proxy/mod.rs index df75aa0c..e55e4535 100644 --- a/ethers-middleware/src/transformer/ds_proxy/mod.rs +++ b/ethers-middleware/src/transformer/ds_proxy/mod.rs @@ -3,7 +3,9 @@ use factory::{CreatedFilter, DsProxyFactory, ADDRESS_BOOK}; use super::{Transformer, TransformerError}; use ethers_contract::{builders::ContractCall, BaseContract, ContractError}; -use ethers_core::{abi::parse_abi, types::*, utils::id}; +use ethers_core::{ + abi::parse_abi, types::transaction::eip2718::TypedTransaction, types::*, utils::id, +}; use ethers_providers::Middleware; use std::sync::Arc; @@ -178,29 +180,26 @@ impl DsProxy { } impl Transformer for DsProxy { - fn transform(&self, tx: TransactionRequest) -> Result { - // clone the tx into a new proxy tx. - let mut proxy_tx = tx.clone(); - + fn transform(&self, tx: &mut TypedTransaction) -> Result<(), TransformerError> { // the target address cannot be None. - let target = match tx.to { - Some(NameOrAddress::Address(addr)) => Ok(addr), - _ => Err(TransformerError::MissingField("to".into())), - }?; + let target = match tx.to() { + Some(NameOrAddress::Address(addr)) => addr, + _ => return Err(TransformerError::MissingField("to".into())), + }; // fetch the data field. - let data = tx.data.unwrap_or_else(|| vec![].into()); + let data = tx.data().cloned().unwrap_or_else(|| vec![].into()); // encode data as the ABI encoded data for DSProxy's execute method. let selector = id("execute(address,bytes)"); let encoded_data = self .contract - .encode_with_selector(selector, (target, data))?; + .encode_with_selector(selector, (*target, data))?; // update appropriate fields of the proxy tx. - proxy_tx.data = Some(encoded_data); - proxy_tx.to = Some(NameOrAddress::Address(self.address)); + tx.set_data(encoded_data); + tx.set_to(self.address); - Ok(proxy_tx) + Ok(()) } } diff --git a/ethers-middleware/src/transformer/middleware.rs b/ethers-middleware/src/transformer/middleware.rs index 65919c6f..5b8a9985 100644 --- a/ethers-middleware/src/transformer/middleware.rs +++ b/ethers-middleware/src/transformer/middleware.rs @@ -1,6 +1,6 @@ use super::{Transformer, TransformerError}; use async_trait::async_trait; -use ethers_core::types::*; +use ethers_core::types::{transaction::eip2718::TypedTransaction, *}; use ethers_providers::{FromErr, Middleware, PendingTransaction}; use thiserror::Error; @@ -53,27 +53,20 @@ where &self.inner } - async fn send_transaction( + async fn send_transaction + Send + Sync>( &self, - mut tx: TransactionRequest, + tx: Tx, block: Option, ) -> Result, Self::Error> { - // resolve the to field if that's an ENS name. - if let Some(NameOrAddress::Name(ens_name)) = tx.to { - let addr = self - .inner - .resolve_name(&ens_name) - .await - .map_err(TransformerMiddlewareError::MiddlewareError)?; - tx.to = Some(addr.into()) - } + let mut tx = tx.into(); // construct the appropriate proxy tx. - let proxy_tx = self.transformer.transform(tx)?; + self.transformer.transform(&mut tx)?; + self.fill_transaction(&mut tx, block).await?; // send the proxy tx. self.inner - .send_transaction(proxy_tx, block) + .send_transaction(tx, block) .await .map_err(TransformerMiddlewareError::MiddlewareError) } diff --git a/ethers-middleware/src/transformer/mod.rs b/ethers-middleware/src/transformer/mod.rs index 7cf123aa..ab31b5ee 100644 --- a/ethers-middleware/src/transformer/mod.rs +++ b/ethers-middleware/src/transformer/mod.rs @@ -5,7 +5,7 @@ mod middleware; pub use middleware::TransformerMiddleware; use ethers_contract::AbiError; -use ethers_core::{abi::ParseError, types::*}; +use ethers_core::{abi::ParseError, types::transaction::eip2718::TypedTransaction}; use thiserror::Error; #[derive(Error, Debug)] @@ -31,5 +31,5 @@ pub trait Transformer: Send + Sync + std::fmt::Debug { /// proxy contract. /// /// [`transaction request`]: struct@ethers_core::types::TransactionRequest - fn transform(&self, tx: TransactionRequest) -> Result; + fn transform(&self, tx: &mut TypedTransaction) -> Result<(), TransformerError>; } diff --git a/ethers-middleware/tests/gas_oracle.rs b/ethers-middleware/tests/gas_oracle.rs index ae79f461..9974d9ad 100644 --- a/ethers-middleware/tests/gas_oracle.rs +++ b/ethers-middleware/tests/gas_oracle.rs @@ -33,6 +33,7 @@ async fn using_gas_oracle() { #[tokio::test] #[ignore] +// TODO: Re-enable, EthGasStation changed its response api @ https://ethgasstation.info/api/ethgasAPI.json async fn eth_gas_station() { // initialize and fetch gas estimates from EthGasStation let eth_gas_station_oracle = EthGasStation::new(None); @@ -60,6 +61,8 @@ async fn etherscan() { #[tokio::test] #[ignore] +// TODO: Etherchain has Cloudflare DDOS protection which makes the request fail +// https://twitter.com/gakonst/status/1421796226316578816 async fn etherchain() { // initialize and fetch gas estimates from Etherchain let etherchain_oracle = Etherchain::new().category(GasCategory::Fast); diff --git a/ethers-providers/src/lib.rs b/ethers-providers/src/lib.rs index 09f4817d..2b115c9b 100644 --- a/ethers-providers/src/lib.rs +++ b/ethers-providers/src/lib.rs @@ -83,7 +83,8 @@ pub use pubsub::{PubsubClient, SubscriptionStream}; use async_trait::async_trait; use auto_impl::auto_impl; -use serde::{de::DeserializeOwned, Serialize}; +use ethers_core::types::transaction::{eip2718::TypedTransaction, eip2930::AccessListWithGasUsed}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::{error::Error, fmt::Debug, future::Future, pin::Pin}; pub use provider::{FilterKind, Provider, ProviderError}; @@ -112,6 +113,18 @@ pub trait FromErr { fn from(src: T) -> Self; } +/// Calls the future if `item` is None, otherwise returns a `futures::ok` +pub async fn maybe(item: Option, f: F) -> Result +where + F: Future>, +{ + if let Some(item) = item { + futures_util::future::ok(item).await + } else { + f.await + } +} + #[async_trait] #[auto_impl(&, Box, Arc)] /// A middleware allows customizing requests send and received from an ethereum node. @@ -122,7 +135,7 @@ pub trait FromErr { /// 3. implementing any of the methods you want to override /// /// ```rust -/// use ethers::{providers::{Middleware, FromErr}, types::{U64, TransactionRequest, U256}}; +/// use ethers::{providers::{Middleware, FromErr}, types::{U64, TransactionRequest, U256, transaction::eip2718::TypedTransaction}}; /// use thiserror::Error; /// use async_trait::async_trait; /// @@ -163,7 +176,7 @@ pub trait FromErr { /// /// /// Overrides the default `estimate_gas` method to log that it was called, /// /// before forwarding the call to the next layer. -/// async fn estimate_gas(&self, tx: &TransactionRequest) -> Result { +/// async fn estimate_gas(&self, tx: &TypedTransaction) -> Result { /// println!("Estimating gas..."); /// self.inner().estimate_gas(tx).await.map_err(FromErr::from) /// } @@ -182,17 +195,90 @@ pub trait Middleware: Sync + Send + Debug { self.inner().provider() } + fn default_sender(&self) -> Option
{ + self.inner().default_sender() + } + async fn client_version(&self) -> Result { self.inner().client_version().await.map_err(FromErr::from) } + /// Helper for filling a transaction + async fn fill_transaction( + &self, + tx: &mut TypedTransaction, + block: Option, + ) -> Result<(), Self::Error> { + let tx_clone = tx.clone(); + + // TODO: Maybe deduplicate the code in a nice way + match tx { + TypedTransaction::Legacy(ref mut inner) => { + if let Some(NameOrAddress::Name(ref ens_name)) = inner.to { + let addr = self.resolve_name(ens_name).await?; + inner.to = Some(addr.into()); + }; + + if inner.from.is_none() { + inner.from = self.default_sender(); + } + + let (gas_price, gas) = futures_util::try_join!( + maybe(inner.gas_price, self.get_gas_price()), + maybe(inner.gas, self.estimate_gas(&tx_clone)), + )?; + inner.gas = Some(gas); + inner.gas_price = Some(gas_price); + } + TypedTransaction::Eip2930(inner) => { + if let Ok(lst) = self.create_access_list(&tx_clone, block).await { + inner.access_list = lst.access_list; + } + + if let Some(NameOrAddress::Name(ref ens_name)) = inner.tx.to { + let addr = self.resolve_name(ens_name).await?; + inner.tx.to = Some(addr.into()); + }; + + let (gas_price, gas) = futures_util::try_join!( + maybe(inner.tx.gas_price, self.get_gas_price()), + maybe(inner.tx.gas, self.estimate_gas(&tx_clone)), + )?; + inner.tx.gas = Some(gas); + inner.tx.gas_price = Some(gas_price); + } + TypedTransaction::Eip1559(inner) => { + if let Ok(lst) = self.create_access_list(&tx_clone, block).await { + inner.access_list = lst.access_list; + } + + if let Some(NameOrAddress::Name(ref ens_name)) = inner.to { + let addr = self.resolve_name(ens_name).await?; + inner.to = Some(addr.into()); + }; + + let (max_priority_fee_per_gas, max_fee_per_gas, gas) = futures_util::try_join!( + // TODO: Replace with algorithms using eth_feeHistory + maybe(inner.max_priority_fee_per_gas, self.get_gas_price()), + maybe(inner.max_fee_per_gas, self.get_gas_price()), + maybe(inner.gas, self.estimate_gas(&tx_clone)), + )?; + inner.gas = Some(gas); + inner.max_fee_per_gas = Some(max_fee_per_gas); + inner.max_priority_fee_per_gas = Some(max_priority_fee_per_gas); + } + }; + + Ok(()) + } + async fn get_block_number(&self) -> Result { self.inner().get_block_number().await.map_err(FromErr::from) } - async fn send_transaction( + async fn send_transaction + Send + Sync>( &self, - tx: TransactionRequest, + tx: T, block: Option, ) -> Result, Self::Error> { self.inner() @@ -246,13 +332,13 @@ pub trait Middleware: Sync + Send + Debug { .map_err(FromErr::from) } - async fn estimate_gas(&self, tx: &TransactionRequest) -> Result { + async fn estimate_gas(&self, tx: &TypedTransaction) -> Result { self.inner().estimate_gas(tx).await.map_err(FromErr::from) } async fn call( &self, - tx: &TransactionRequest, + tx: &TypedTransaction, block: Option, ) -> Result { self.inner().call(tx, block).await.map_err(FromErr::from) @@ -427,9 +513,9 @@ pub trait Middleware: Sync + Send + Debug { // Parity `trace` support /// Executes the given call and returns a number of possible traces for it - async fn trace_call( + async fn trace_call + Send + Sync>( &self, - req: TransactionRequest, + req: T, trace_type: Vec, block: Option, ) -> Result { @@ -574,6 +660,38 @@ pub trait Middleware: Sync + Send + Debug { .await .map_err(FromErr::from) } + + async fn fee_history( + &self, + block_count: u64, + last_block: BlockNumber, + reward_percentiles: &[f64], + ) -> Result { + self.inner() + .fee_history(block_count, last_block, reward_percentiles) + .await + .map_err(FromErr::from) + } + + async fn create_access_list( + &self, + tx: &TypedTransaction, + block: Option, + ) -> Result { + self.inner() + .create_access_list(tx, block) + .await + .map_err(FromErr::from) + } +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct FeeHistory { + pub base_fee_per_gas: Vec, + pub gas_used_ratio: Vec, + pub oldest_block: u64, + pub reward: Vec>, } #[cfg(feature = "celo")] diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs index 956216fd..8e61b3ab 100644 --- a/ethers-providers/src/provider.rs +++ b/ethers-providers/src/provider.rs @@ -2,15 +2,16 @@ use crate::{ ens, pubsub::{PubsubClient, SubscriptionStream}, stream::{FilterWatcher, DEFAULT_POLL_INTERVAL}, - FromErr, Http as HttpProvider, JsonRpcClient, MockProvider, PendingTransaction, + FeeHistory, FromErr, Http as HttpProvider, JsonRpcClient, MockProvider, PendingTransaction, }; use ethers_core::{ abi::{self, Detokenize, ParamType}, types::{ + transaction::{eip2718::TypedTransaction, eip2930::AccessListWithGasUsed}, Address, Block, BlockId, BlockNumber, BlockTrace, Bytes, Filter, Log, NameOrAddress, Selector, Signature, Trace, TraceFilter, TraceType, Transaction, TransactionReceipt, - TransactionRequest, TxHash, TxpoolContent, TxpoolInspect, TxpoolStatus, H256, U256, U64, + TxHash, TxpoolContent, TxpoolInspect, TxpoolStatus, H256, U256, U64, }, utils, }; @@ -192,6 +193,10 @@ impl Middleware for Provider

{ self } + fn default_sender(&self) -> Option

{ + self.from + } + ////// Blockchain Status // // Functions for querying the state of the blockchain @@ -307,7 +312,7 @@ impl Middleware for Provider

{ /// This is free, since it does not change any state on the blockchain. async fn call( &self, - tx: &TransactionRequest, + tx: &TypedTransaction, block: Option, ) -> Result { let tx = utils::serialize(tx); @@ -318,33 +323,29 @@ impl Middleware for Provider

{ /// Sends a transaction to a single Ethereum node and return the estimated amount of gas required (as a U256) to send it /// This is free, but only an estimate. Providing too little gas will result in a transaction being rejected /// (while still consuming all provided gas). - async fn estimate_gas(&self, tx: &TransactionRequest) -> Result { + async fn estimate_gas(&self, tx: &TypedTransaction) -> Result { self.request("eth_estimateGas", [tx]).await } + async fn create_access_list( + &self, + tx: &TypedTransaction, + block: Option, + ) -> Result { + let tx = utils::serialize(tx); + let block = utils::serialize(&block.unwrap_or_else(|| BlockNumber::Latest.into())); + self.request("eth_createAccessList", [tx, block]).await + } + /// Sends the transaction to the entire Ethereum network and returns the transaction's hash /// This will consume gas from the account that signed the transaction. - async fn send_transaction( + async fn send_transaction + Send + Sync>( &self, - mut tx: TransactionRequest, - _: Option, + tx: T, + block: Option, ) -> Result, ProviderError> { - if tx.from.is_none() { - tx.from = self.from; - } - - if tx.gas.is_none() { - tx.gas = Some(self.estimate_gas(&tx).await?); - } - - if let Some(NameOrAddress::Name(ref ens_name)) = tx.to { - // resolve to an address - let addr = self.resolve_name(ens_name).await?; - - // set the value - tx.to = Some(addr.into()) - } - + let mut tx = tx.into(); + self.fill_transaction(&mut tx, block).await?; let tx_hash = self.request("eth_sendTransaction", [tx]).await?; Ok(PendingTransaction::new(tx_hash, self).interval(self.get_interval())) @@ -557,12 +558,13 @@ impl Middleware for Provider

{ } /// Executes the given call and returns a number of possible traces for it - async fn trace_call( + async fn trace_call + Send + Sync>( &self, - req: TransactionRequest, + req: T, trace_type: Vec, block: Option, ) -> Result { + let req = req.into(); let req = utils::serialize(&req); let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest)); let trace_type = utils::serialize(&trace_type); @@ -694,6 +696,22 @@ impl Middleware for Provider

{ let filter = utils::serialize(filter); self.subscribe([logs, filter]).await } + + async fn fee_history( + &self, + block_count: u64, + last_block: BlockNumber, + reward_percentiles: &[f64], + ) -> Result { + let block_count = utils::serialize(&block_count); + let last_block = utils::serialize(&last_block); + let reward_percentiles = utils::serialize(&reward_percentiles); + self.request( + "eth_feeHistory", + [block_count, last_block, reward_percentiles], + ) + .await + } } impl Provider

{ @@ -709,7 +727,7 @@ impl Provider

{ // first get the resolver responsible for this name // the call will return a Bytes array which we convert to an address let data = self - .call(&ens::get_resolver(ens_addr, ens_name), None) + .call(&ens::get_resolver(ens_addr, ens_name).into(), None) .await?; let resolver_address: Address = decode_bytes(ParamType::Address, data); @@ -719,7 +737,10 @@ impl Provider

{ // resolve let data = self - .call(&ens::resolve(resolver_address, selector, ens_name), None) + .call( + &ens::resolve(resolver_address, selector, ens_name).into(), + None, + ) .await?; Ok(decode_bytes(param, data)) @@ -885,7 +906,7 @@ mod ens_tests { mod tests { use super::*; use crate::Http; - use ethers_core::types::H256; + use ethers_core::types::{TransactionRequest, H256}; use ethers_core::utils::Geth; use futures_util::StreamExt; @@ -1020,4 +1041,19 @@ mod tests { .await; assert_eq!(blocks, vec![1, 2, 3]); } + + #[tokio::test] + #[cfg_attr(feature = "celo", ignore)] + async fn fee_history() { + let provider = Provider::::try_from( + "https://goerli.infura.io/v3/fd8b88b56aa84f6da87b60f5441d6778", + ) + .unwrap(); + + let history = provider + .fee_history(10, BlockNumber::Latest, &[10.0, 40.0]) + .await + .unwrap(); + dbg!(&history); + } } diff --git a/ethers-providers/tests/provider.rs b/ethers-providers/tests/provider.rs index ba35d2a6..18a2426b 100644 --- a/ethers-providers/tests/provider.rs +++ b/ethers-providers/tests/provider.rs @@ -6,6 +6,7 @@ mod eth_tests { use super::*; use ethers::{ middleware::SignerMiddleware, + prelude::transaction::eip2718::TypedTransaction, signers::{LocalWallet, Signer}, types::{BlockId, TransactionRequest, H256}, utils::Ganache, @@ -148,6 +149,56 @@ mod eth_tests { // got the correct receipt assert_eq!(receipt.transaction_hash, tx_hash); } + + #[tokio::test] + async fn typed_txs() { + use ethers_core::types::Eip1559TransactionRequest; + let provider = Provider::::try_from( + "https://rinkeby.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27", + ) + .unwrap(); + + let chain_id = provider.get_chainid().await.unwrap(); + let wallet = "59c37cb6b16fa2de30675f034c8008f890f4b2696c729d6267946d29736d73e4" + .parse::() + .unwrap() + .with_chain_id(chain_id.as_u64()); + let address = wallet.address(); + let provider = SignerMiddleware::new(provider, wallet); + + async fn check_tx(provider: &M, tx: TypedTransaction, expected: u64) { + let receipt = provider + .send_transaction(tx, None) + .await + .unwrap() + .await + .unwrap() + .unwrap(); + let tx = provider + .get_transaction(receipt.transaction_hash) + .await + .unwrap() + .unwrap(); + assert_eq!(receipt.transaction_type, Some(expected.into())); + assert_eq!(tx.transaction_type, Some(expected.into())); + } + + let tx: TypedTransaction = TransactionRequest::new().from(address).to(address).into(); + check_tx(&provider, tx, 0).await; + + let tx: TypedTransaction = TransactionRequest::new() + .from(address) + .to(address) + .with_access_list(vec![]) + .into(); + check_tx(&provider, tx, 1).await; + + let tx: TypedTransaction = Eip1559TransactionRequest::new() + .from(address) + .to(address) + .into(); + check_tx(&provider, tx, 2).await; + } } #[cfg(feature = "celo")] diff --git a/ethers-signers/src/aws/mod.rs b/ethers-signers/src/aws/mod.rs index a58cce2a..e99ee1b5 100644 --- a/ethers-signers/src/aws/mod.rs +++ b/ethers-signers/src/aws/mod.rs @@ -2,7 +2,7 @@ use ethers_core::{ k256::ecdsa::{Error as K256Error, Signature as KSig, VerifyingKey}, - types::{Address, Signature as EthSig, TransactionRequest, H256}, + types::{Address, Signature as EthSig, H256, transaction::eip2718::TypedTransaction}, utils::hash_message, }; use rusoto_core::RusotoError; @@ -240,7 +240,7 @@ impl<'a> super::Signer for AwsSigner<'a> { } #[instrument(err)] - async fn sign_transaction(&self, tx: &TransactionRequest) -> Result { + async fn sign_transaction(&self, tx: &TypedTransaction) -> Result { let sighash = tx.sighash(self.chain_id); self.sign_digest_with_eip155(sighash).await } diff --git a/ethers-signers/src/ledger/app.rs b/ethers-signers/src/ledger/app.rs index 5d44b133..2579b3df 100644 --- a/ethers-signers/src/ledger/app.rs +++ b/ethers-signers/src/ledger/app.rs @@ -8,7 +8,8 @@ use futures_util::lock::Mutex; use ethers_core::{ types::{ - Address, NameOrAddress, Signature, Transaction, TransactionRequest, TxHash, H256, U256, + transaction::eip2718::TypedTransaction, Address, NameOrAddress, Signature, Transaction, + TransactionRequest, TxHash, H256, U256, }, utils::keccak256, }; @@ -118,7 +119,7 @@ impl LedgerEthereum { } /// Signs an Ethereum transaction (requires confirmation on the ledger) - pub async fn sign_tx(&self, tx: &TransactionRequest) -> Result { + pub async fn sign_tx(&self, tx: &TypedTransaction) -> Result { let mut payload = Self::path_to_bytes(&self.derivation); payload.extend_from_slice(tx.rlp(self.chain_id).as_ref()); self.sign_payload(INS::SIGN, payload).await @@ -241,7 +242,8 @@ mod tests { .gas_price(400e9 as u64) .nonce(5) .data(data) - .value(ethers_core::utils::parse_ether(100).unwrap()); + .value(ethers_core::utils::parse_ether(100).unwrap()) + .into(); let tx = ledger.sign_transaction(&tx_req).await.unwrap(); } diff --git a/ethers-signers/src/ledger/mod.rs b/ethers-signers/src/ledger/mod.rs index b22466e2..91a54344 100644 --- a/ethers-signers/src/ledger/mod.rs +++ b/ethers-signers/src/ledger/mod.rs @@ -4,7 +4,7 @@ pub mod types; use crate::Signer; use app::LedgerEthereum; use async_trait::async_trait; -use ethers_core::types::{Address, Signature, TransactionRequest}; +use ethers_core::types::{transaction::eip2718::TypedTransaction, Address, Signature}; use types::LedgerError; #[async_trait] @@ -20,10 +20,7 @@ impl Signer for LedgerEthereum { } /// Signs the transaction - async fn sign_transaction( - &self, - message: &TransactionRequest, - ) -> Result { + async fn sign_transaction(&self, message: &TypedTransaction) -> Result { self.sign_tx(message).await } diff --git a/ethers-signers/src/lib.rs b/ethers-signers/src/lib.rs index b39619da..e2ee6aff 100644 --- a/ethers-signers/src/lib.rs +++ b/ethers-signers/src/lib.rs @@ -25,7 +25,7 @@ //! // create a transaction //! let tx = TransactionRequest::new() //! .to("vitalik.eth") // this will use ENS -//! .value(10000); +//! .value(10000).into(); //! //! // sign it //! let signature = wallet.sign_transaction(&tx).await?; @@ -70,7 +70,7 @@ mod aws; pub use aws::{AwsSigner, AwsSignerError}; use async_trait::async_trait; -use ethers_core::types::{Address, Signature, TransactionRequest}; +use ethers_core::types::{transaction::eip2718::TypedTransaction, Address, Signature}; use std::error::Error; /// Applies [EIP155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md) @@ -91,10 +91,7 @@ pub trait Signer: std::fmt::Debug + Send + Sync { ) -> Result; /// Signs the transaction - async fn sign_transaction( - &self, - message: &TransactionRequest, - ) -> Result; + async fn sign_transaction(&self, message: &TypedTransaction) -> Result; /// Returns the signer's Ethereum Address fn address(&self) -> Address; diff --git a/ethers-signers/src/wallet/mod.rs b/ethers-signers/src/wallet/mod.rs index ab864fef..53d3a294 100644 --- a/ethers-signers/src/wallet/mod.rs +++ b/ethers-signers/src/wallet/mod.rs @@ -16,7 +16,7 @@ use ethers_core::{ elliptic_curve::FieldBytes, Secp256k1, }, - types::{Address, Signature, TransactionRequest, H256}, + types::{transaction::eip2718::TypedTransaction, Address, Signature, H256}, utils::hash_message, }; use hash::Sha256Proxy; @@ -78,7 +78,7 @@ impl> Signer fo Ok(self.sign_hash(message_hash, false)) } - async fn sign_transaction(&self, tx: &TransactionRequest) -> Result { + async fn sign_transaction(&self, tx: &TypedTransaction) -> Result { let sighash = tx.sighash(self.chain_id); Ok(self.sign_hash(sighash, true)) } diff --git a/ethers-signers/src/wallet/private_key.rs b/ethers-signers/src/wallet/private_key.rs index cf2289a8..f14f1a0f 100644 --- a/ethers-signers/src/wallet/private_key.rs +++ b/ethers-signers/src/wallet/private_key.rs @@ -217,7 +217,8 @@ mod tests { nonce: Some(0.into()), gas_price: Some(21_000_000_000u128.into()), data: None, - }; + } + .into(); let chain_id = 1u64; let wallet: Wallet =