feat: typed txs provider / middleware changes (part 3) (#357)

* feat(providers): add eth_feeHistory api

* add access list

* feat: fill transactions with access list / default sender info

* feat: add helpers for operating on the txs enum

* feat: send_transaction takes TypedTransaction now

* fix(contract): temp wrap all contract txs as Legacy txs

* feat(middleware): use TypedTransaction in Transformer

* feat(signers): use TypedTransaction in Wallet/Ledger

* feat(core): add helpers for setting typed tx fields

* feat(signer): use typed transactions

* fix(middleware): adjust nonce/gas/escalators for TypedTxs

The GPO and the Escalators will throw an error if they are provided an EIP1559 transaction

* fix(providers): ensure the correct account's nonce is filled

* fix: add .into() to txs until we make the fn call more generic

* Revert "fix: add .into() to txs until we make the fn call more generic"

This reverts commit 04dc34b26d0e3f418ed3fc69ea35ad538b83dd50.

* feat: generalize send_transaction interface

* fix: only set the nonce manually in the Signer middleware

* fix(transformer): fill the transaction after transformation

* chore: fix compilation errors & lints

* fix(signer): set the correct account's nonce

* feat: make trace_call / call take TypedTransaction

* fix: set sender to transaction in signer

* chore: ethgasstation broke

* chore: cargo fmt / lints

* Fix(signer): pass the chain id

* fix: final tx encoding fixes

1. Normalize v values for eip1559/2730
2. Make access lists mandatory for 1559
3. do not double-rlp on rlp_signed

* fix: set access list only if available

* test: check 1559 / 2930 txs

* fix: do not prepend a 0 for Legacy txs & test

* chore: code review comments

* chore: fix aws signer signature
This commit is contained in:
Georgios Konstantopoulos 2021-08-09 03:31:11 +03:00 committed by GitHub
parent ccff9fc98f
commit dcbfacf5bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 418 additions and 204 deletions

View File

@ -102,7 +102,7 @@ where
/// Returns the estimated gas cost for the underlying transaction to be executed /// Returns the estimated gas cost for the underlying transaction to be executed
pub async fn estimate_gas(&self) -> Result<U256, ContractError<M>> { pub async fn estimate_gas(&self) -> Result<U256, ContractError<M>> {
self.client self.client
.estimate_gas(&self.tx) .estimate_gas(&self.tx.clone().into())
.await .await
.map_err(ContractError::MiddlewareError) .map_err(ContractError::MiddlewareError)
} }
@ -119,7 +119,7 @@ where
pub async fn call(&self) -> Result<D, ContractError<M>> { pub async fn call(&self) -> Result<D, ContractError<M>> {
let bytes = self let bytes = self
.client .client
.call(&self.tx, self.block) .call(&self.tx.clone().into(), self.block)
.await .await
.map_err(ContractError::MiddlewareError)?; .map_err(ContractError::MiddlewareError)?;

View File

@ -1,4 +1,4 @@
use super::{eip2930::AccessList, rlp_opt}; use super::{eip2930::AccessList, normalize_v, rlp_opt};
use crate::{ use crate::{
types::{Address, Bytes, NameOrAddress, Signature, H256, U256, U64}, types::{Address, Bytes, NameOrAddress, Signature, H256, U256, U64},
utils::keccak256, utils::keccak256,
@ -37,12 +37,8 @@ pub struct Eip1559TransactionRequest {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub nonce: Option<U256>, pub nonce: Option<U256>,
#[serde( #[serde(rename = "accessList", default)]
rename = "accessList", pub access_list: AccessList,
default,
skip_serializing_if = "Option::is_none"
)]
pub access_list: Option<AccessList>,
#[serde( #[serde(
rename = "maxPriorityFeePerGas", rename = "maxPriorityFeePerGas",
@ -121,7 +117,7 @@ impl Eip1559TransactionRequest {
/// Sets the `access_list` field in the transaction to the provided value /// Sets the `access_list` field in the transaction to the provided value
pub fn access_list<T: Into<AccessList>>(mut self, access_list: T) -> Self { pub fn access_list<T: Into<AccessList>>(mut self, access_list: T) -> Self {
self.access_list = Some(access_list.into()); self.access_list = access_list.into();
self self
} }
@ -148,10 +144,12 @@ impl Eip1559TransactionRequest {
pub fn rlp_signed<T: Into<U64>>(&self, chain_id: T, signature: &Signature) -> Bytes { pub fn rlp_signed<T: Into<U64>>(&self, chain_id: T, signature: &Signature) -> Bytes {
let mut rlp = RlpStream::new(); let mut rlp = RlpStream::new();
rlp.begin_unbounded_list(); rlp.begin_unbounded_list();
let chain_id = chain_id.into();
self.rlp_base(chain_id, &mut rlp); self.rlp_base(chain_id, &mut rlp);
// append the signature // 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.r);
rlp.append(&signature.s); rlp.append(&signature.s);
rlp.finalize_unbounded_list(); rlp.finalize_unbounded_list();
@ -167,6 +165,6 @@ impl Eip1559TransactionRequest {
rlp_opt(rlp, &self.to.as_ref()); rlp_opt(rlp, &self.to.as_ref());
rlp_opt(rlp, &self.value); rlp_opt(rlp, &self.value);
rlp_opt(rlp, &self.data.as_ref().map(|d| d.as_ref())); rlp_opt(rlp, &self.data.as_ref().map(|d| d.as_ref()));
rlp_opt(rlp, &self.access_list); rlp.append(&self.access_list);
} }
} }

View File

@ -19,9 +19,10 @@ pub enum TypedTransaction {
Eip1559(Eip1559TransactionRequest), Eip1559(Eip1559TransactionRequest),
} }
use TypedTransaction::*;
impl TypedTransaction { impl TypedTransaction {
pub fn from(&self) -> Option<&Address> { pub fn from(&self) -> Option<&Address> {
use TypedTransaction::*;
match self { match self {
Legacy(inner) => inner.from.as_ref(), Legacy(inner) => inner.from.as_ref(),
Eip2930(inner) => inner.tx.from.as_ref(), Eip2930(inner) => inner.tx.from.as_ref(),
@ -30,7 +31,6 @@ impl TypedTransaction {
} }
pub fn set_from(&mut self, from: Address) { pub fn set_from(&mut self, from: Address) {
use TypedTransaction::*;
match self { match self {
Legacy(inner) => inner.from = Some(from), Legacy(inner) => inner.from = Some(from),
Eip2930(inner) => inner.tx.from = Some(from), Eip2930(inner) => inner.tx.from = Some(from),
@ -39,7 +39,6 @@ impl TypedTransaction {
} }
pub fn to(&self) -> Option<&NameOrAddress> { pub fn to(&self) -> Option<&NameOrAddress> {
use TypedTransaction::*;
match self { match self {
Legacy(inner) => inner.to.as_ref(), Legacy(inner) => inner.to.as_ref(),
Eip2930(inner) => inner.tx.to.as_ref(), Eip2930(inner) => inner.tx.to.as_ref(),
@ -49,7 +48,6 @@ impl TypedTransaction {
pub fn set_to<T: Into<NameOrAddress>>(&mut self, to: T) { pub fn set_to<T: Into<NameOrAddress>>(&mut self, to: T) {
let to = to.into(); let to = to.into();
use TypedTransaction::*;
match self { match self {
Legacy(inner) => inner.to = Some(to), Legacy(inner) => inner.to = Some(to),
Eip2930(inner) => inner.tx.to = Some(to), Eip2930(inner) => inner.tx.to = Some(to),
@ -58,7 +56,6 @@ impl TypedTransaction {
} }
pub fn nonce(&self) -> Option<&U256> { pub fn nonce(&self) -> Option<&U256> {
use TypedTransaction::*;
match self { match self {
Legacy(inner) => inner.nonce.as_ref(), Legacy(inner) => inner.nonce.as_ref(),
Eip2930(inner) => inner.tx.nonce.as_ref(), Eip2930(inner) => inner.tx.nonce.as_ref(),
@ -68,7 +65,6 @@ impl TypedTransaction {
pub fn set_nonce<T: Into<U256>>(&mut self, nonce: T) { pub fn set_nonce<T: Into<U256>>(&mut self, nonce: T) {
let nonce = nonce.into(); let nonce = nonce.into();
use TypedTransaction::*;
match self { match self {
Legacy(inner) => inner.nonce = Some(nonce), Legacy(inner) => inner.nonce = Some(nonce),
Eip2930(inner) => inner.tx.nonce = Some(nonce), Eip2930(inner) => inner.tx.nonce = Some(nonce),
@ -77,7 +73,6 @@ impl TypedTransaction {
} }
pub fn value(&self) -> Option<&U256> { pub fn value(&self) -> Option<&U256> {
use TypedTransaction::*;
match self { match self {
Legacy(inner) => inner.value.as_ref(), Legacy(inner) => inner.value.as_ref(),
Eip2930(inner) => inner.tx.value.as_ref(), Eip2930(inner) => inner.tx.value.as_ref(),
@ -87,7 +82,6 @@ impl TypedTransaction {
pub fn set_value<T: Into<U256>>(&mut self, value: T) { pub fn set_value<T: Into<U256>>(&mut self, value: T) {
let value = value.into(); let value = value.into();
use TypedTransaction::*;
match self { match self {
Legacy(inner) => inner.value = Some(value), Legacy(inner) => inner.value = Some(value),
Eip2930(inner) => inner.tx.value = Some(value), Eip2930(inner) => inner.tx.value = Some(value),
@ -96,7 +90,6 @@ impl TypedTransaction {
} }
pub fn gas(&self) -> Option<&U256> { pub fn gas(&self) -> Option<&U256> {
use TypedTransaction::*;
match self { match self {
Legacy(inner) => inner.gas.as_ref(), Legacy(inner) => inner.gas.as_ref(),
Eip2930(inner) => inner.tx.gas.as_ref(), Eip2930(inner) => inner.tx.gas.as_ref(),
@ -106,7 +99,6 @@ impl TypedTransaction {
pub fn set_gas<T: Into<U256>>(&mut self, gas: T) { pub fn set_gas<T: Into<U256>>(&mut self, gas: T) {
let gas = gas.into(); let gas = gas.into();
use TypedTransaction::*;
match self { match self {
Legacy(inner) => inner.gas = Some(gas), Legacy(inner) => inner.gas = Some(gas),
Eip2930(inner) => inner.tx.gas = Some(gas), Eip2930(inner) => inner.tx.gas = Some(gas),
@ -116,7 +108,6 @@ impl TypedTransaction {
pub fn set_gas_price<T: Into<U256>>(&mut self, gas_price: T) { pub fn set_gas_price<T: Into<U256>>(&mut self, gas_price: T) {
let gas_price = gas_price.into(); let gas_price = gas_price.into();
use TypedTransaction::*;
match self { match self {
Legacy(inner) => inner.gas_price = Some(gas_price), Legacy(inner) => inner.gas_price = Some(gas_price),
Eip2930(inner) => inner.tx.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> { pub fn data(&self) -> Option<&Bytes> {
use TypedTransaction::*;
match self { match self {
Legacy(inner) => inner.data.as_ref(), Legacy(inner) => inner.data.as_ref(),
Eip2930(inner) => inner.tx.data.as_ref(), Eip2930(inner) => inner.tx.data.as_ref(),
@ -137,7 +127,6 @@ impl TypedTransaction {
} }
pub fn set_data(&mut self, data: Bytes) { pub fn set_data(&mut self, data: Bytes) {
use TypedTransaction::*;
match self { match self {
Legacy(inner) => inner.data = Some(data), Legacy(inner) => inner.data = Some(data),
Eip2930(inner) => inner.tx.data = Some(data), Eip2930(inner) => inner.tx.data = Some(data),
@ -146,12 +135,10 @@ impl TypedTransaction {
} }
pub fn rlp_signed<T: Into<U64>>(&self, chain_id: T, signature: &Signature) -> Bytes { pub fn rlp_signed<T: Into<U64>>(&self, chain_id: T, signature: &Signature) -> Bytes {
use TypedTransaction::*;
let mut encoded = vec![]; let mut encoded = vec![];
match self { match self {
Legacy(inner) => { Legacy(ref tx) => {
encoded.extend_from_slice(&[0x0]); encoded.extend_from_slice(tx.rlp_signed(signature).as_ref());
encoded.extend_from_slice(inner.rlp_signed(signature).as_ref());
} }
Eip2930(inner) => { Eip2930(inner) => {
encoded.extend_from_slice(&[0x1]); encoded.extend_from_slice(&[0x1]);
@ -162,17 +149,14 @@ impl TypedTransaction {
encoded.extend_from_slice(inner.rlp_signed(chain_id, signature).as_ref()); encoded.extend_from_slice(inner.rlp_signed(chain_id, signature).as_ref());
} }
}; };
encoded.into()
rlp::encode(&encoded).freeze().into()
} }
pub fn rlp<T: Into<U64>>(&self, chain_id: T) -> Bytes { pub fn rlp<T: Into<U64>>(&self, chain_id: T) -> Bytes {
let chain_id = chain_id.into(); let chain_id = chain_id.into();
let mut encoded = vec![]; let mut encoded = vec![];
use TypedTransaction::*;
match self { match self {
Legacy(inner) => { Legacy(inner) => {
encoded.extend_from_slice(&[0x0]);
encoded.extend_from_slice(inner.rlp(chain_id).as_ref()); encoded.extend_from_slice(inner.rlp(chain_id).as_ref());
} }
Eip2930(inner) => { Eip2930(inner) => {

View File

@ -1,5 +1,5 @@
use super::request::TransactionRequest; use super::{normalize_v, request::TransactionRequest};
use crate::types::{Address, Bytes, Signature, H256, U64}; use crate::types::{Address, Bytes, Signature, H256, U256, U64};
use rlp::RlpStream; use rlp::RlpStream;
use rlp_derive::{RlpEncodable, RlpEncodableWrapper}; use rlp_derive::{RlpEncodable, RlpEncodableWrapper};
@ -13,6 +13,13 @@ const NUM_EIP2930_FIELDS: usize = 8;
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, RlpEncodableWrapper)] #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, RlpEncodableWrapper)]
pub struct AccessList(pub Vec<AccessListItem>); pub struct AccessList(pub Vec<AccessListItem>);
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct AccessListWithGasUsed {
pub access_list: AccessList,
pub gas_used: U256,
}
impl From<Vec<AccessListItem>> for AccessList { impl From<Vec<AccessListItem>> for AccessList {
fn from(src: Vec<AccessListItem>) -> AccessList { fn from(src: Vec<AccessListItem>) -> AccessList {
AccessList(src) AccessList(src)
@ -69,13 +76,15 @@ impl Eip2930TransactionRequest {
let mut rlp = RlpStream::new(); let mut rlp = RlpStream::new();
rlp.begin_list(NUM_EIP2930_FIELDS + 3); 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); self.tx.rlp_base(&mut rlp);
// append the access list in addition to the base rlp encoding // append the access list in addition to the base rlp encoding
rlp.append(&self.access_list); rlp.append(&self.access_list);
// append the signature // 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.r);
rlp.append(&signature.s); rlp.append(&signature.s);
rlp.out().freeze().into() rlp.out().freeze().into()
@ -104,8 +113,6 @@ mod tests {
let hash = tx.sighash(1); let hash = tx.sighash(1);
let sig: Signature = "c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b266032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d3752101".parse().unwrap(); let sig: Signature = "c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b266032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d3752101".parse().unwrap();
let enc = tx.rlp_signed(1, &sig);
assert_eq!( assert_eq!(
hash, hash,
"49b486f0ec0a60dfbbca2d30cb07c9e8ffb2a2ff41f29a1ab6737475f6ff69f3" "49b486f0ec0a60dfbbca2d30cb07c9e8ffb2a2ff41f29a1ab6737475f6ff69f3"
@ -113,6 +120,7 @@ mod tests {
.unwrap() .unwrap()
); );
let enc = rlp::encode(&tx.rlp_signed(1, &sig).as_ref());
let expected = "b86601f8630103018261a894b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a825544c001a0c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b2660a032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d37521"; let expected = "b86601f8630103018261a894b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a825544c001a0c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b2660a032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d37521";
assert_eq!(hex::encode(enc.to_vec()), expected); assert_eq!(hex::encode(enc.to_vec()), expected);
} }

View File

@ -21,3 +21,12 @@ pub(super) fn rlp_opt<T: rlp::Encodable>(rlp: &mut rlp::RlpStream, opt: &Option<
rlp.append(&""); 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
}
}

View File

@ -1,4 +1,5 @@
mod geometric; mod geometric;
use ethers_core::types::transaction::eip2718::TypedTransaction;
pub use geometric::GeometricGasPrice; pub use geometric::GeometricGasPrice;
mod linear; mod linear;
@ -82,17 +83,25 @@ where
&self.inner &self.inner
} }
async fn send_transaction( async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
&self, &self,
tx: TransactionRequest, tx: T,
block: Option<BlockId>, block: Option<BlockId>,
) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> { ) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
let tx = tx.into();
let pending_tx = self let pending_tx = self
.inner() .inner()
.send_transaction(tx.clone(), block) .send_transaction(tx.clone(), block)
.await .await
.map_err(GasEscalatorError::MiddlewareError)?; .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 // insert the tx in the pending txs
let mut lock = self.txs.lock().await; let mut lock = self.txs.lock().await;
lock.push((*pending_tx, tx, Instant::now(), block)); lock.push((*pending_tx, tx, Instant::now(), block));
@ -231,4 +240,7 @@ pub enum GasEscalatorError<M: Middleware> {
#[error("{0}")] #[error("{0}")]
/// Thrown when an internal middleware errors /// Thrown when an internal middleware errors
MiddlewareError(M::Error), MiddlewareError(M::Error),
#[error("Gas escalation is only supported for EIP2930 or Legacy transactions")]
UnsupportedTxType,
} }

View File

@ -21,7 +21,7 @@ pub struct EthGasStation {
#[derive(Deserialize)] #[derive(Deserialize)]
struct EthGasStationResponse { struct EthGasStationResponse {
#[serde(rename = "safeLow")] #[serde(rename = "safeLow")]
safe_low: u64, safe_low: f64,
average: u64, average: u64,
fast: u64, fast: u64,
fastest: u64, fastest: u64,
@ -63,7 +63,7 @@ impl GasOracle for EthGasStation {
.await?; .await?;
let gas_price = match self.gas_category { 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::Standard => U256::from((res.average * GWEI_TO_WEI) / 10),
GasCategory::Fast => U256::from((res.fast * GWEI_TO_WEI) / 10), GasCategory::Fast => U256::from((res.fast * GWEI_TO_WEI) / 10),
GasCategory::Fastest => U256::from((res.fastest * GWEI_TO_WEI) / 10), GasCategory::Fastest => U256::from((res.fastest * GWEI_TO_WEI) / 10),

View File

@ -1,6 +1,6 @@
use super::{GasOracle, GasOracleError}; use super::{GasOracle, GasOracleError};
use async_trait::async_trait; use async_trait::async_trait;
use ethers_core::types::*; use ethers_core::types::{transaction::eip2718::TypedTransaction, *};
use ethers_providers::{FromErr, Middleware, PendingTransaction}; use ethers_providers::{FromErr, Middleware, PendingTransaction};
use thiserror::Error; use thiserror::Error;
@ -28,6 +28,9 @@ pub enum MiddlewareError<M: Middleware> {
#[error("{0}")] #[error("{0}")]
MiddlewareError(M::Error), MiddlewareError(M::Error),
#[error("This gas price oracle only works with Legacy and EIP2930 transactions.")]
UnsupportedTxType,
} }
impl<M: Middleware> FromErr<M::Error> for MiddlewareError<M> { impl<M: Middleware> FromErr<M::Error> for MiddlewareError<M> {
@ -56,14 +59,28 @@ where
Ok(self.gas_oracle.fetch().await?) Ok(self.gas_oracle.fetch().await?)
} }
async fn send_transaction( async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
&self, &self,
mut tx: TransactionRequest, tx: T,
block: Option<BlockId>, block: Option<BlockId>,
) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> { ) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
let mut tx = tx.into();
match tx {
TypedTransaction::Legacy(ref mut tx) => {
if tx.gas_price.is_none() { if tx.gas_price.is_none() {
tx.gas_price = Some(self.get_gas_price().await?); 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 self.inner
.send_transaction(tx, block) .send_transaction(tx, block)
.await .await

View File

@ -1,4 +1,5 @@
use async_trait::async_trait; use async_trait::async_trait;
use ethers_core::types::transaction::eip2718::TypedTransaction;
use ethers_core::types::*; use ethers_core::types::*;
use ethers_providers::{FromErr, Middleware, PendingTransaction}; use ethers_providers::{FromErr, Middleware, PendingTransaction};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; 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 /// 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 /// gas cost and nonce calculations take it into account. For simple transactions this can be
/// left to `None`. /// left to `None`.
async fn send_transaction( async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
&self, &self,
mut tx: TransactionRequest, tx: T,
block: Option<BlockId>, block: Option<BlockId>,
) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> { ) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
if tx.nonce.is_none() { let mut tx = tx.into();
tx.nonce = Some(self.get_transaction_count_with_manager(block).await?);
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.clone(), block).await {
match self.inner.send_transaction(tx, block).await {
Ok(tx_hash) => Ok(tx_hash), Ok(tx_hash) => Ok(tx_hash),
Err(err) => { Err(err) => {
let nonce = self.get_transaction_count(self.address, block).await?; 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 // try re-submitting the transaction with the correct nonce if there
// was a nonce mismatch // was a nonce mismatch
self.nonce.store(nonce.as_u64(), Ordering::SeqCst); self.nonce.store(nonce.as_u64(), Ordering::SeqCst);
tx_clone.nonce = Some(nonce); tx.set_nonce(nonce);
self.inner self.inner
.send_transaction(tx_clone, block) .send_transaction(tx, block)
.await .await
.map_err(FromErr::from) .map_err(FromErr::from)
} else { } else {

View File

@ -1,10 +1,10 @@
use ethers_core::types::{Address, BlockId, Bytes, NameOrAddress, Signature, TransactionRequest}; use ethers_core::types::{
use ethers_providers::{FromErr, Middleware, PendingTransaction}; transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, Signature,
};
use ethers_providers::{maybe, FromErr, Middleware, PendingTransaction};
use ethers_signers::Signer; use ethers_signers::Signer;
use async_trait::async_trait; use async_trait::async_trait;
use futures_util::{future::ok, join};
use std::future::Future;
use thiserror::Error; use thiserror::Error;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -118,7 +118,7 @@ where
/// Signs and returns the RLP encoding of the signed transaction /// Signs and returns the RLP encoding of the signed transaction
async fn sign_transaction( async fn sign_transaction(
&self, &self,
tx: TransactionRequest, tx: TypedTransaction,
) -> Result<Bytes, SignerMiddlewareError<M, S>> { ) -> Result<Bytes, SignerMiddlewareError<M, S>> {
let signature = self let signature = self
.signer .signer
@ -127,33 +127,7 @@ where
.map_err(SignerMiddlewareError::SignerError)?; .map_err(SignerMiddlewareError::SignerError)?;
// Return the raw rlp-encoded signed transaction // Return the raw rlp-encoded signed transaction
Ok(tx.rlp_signed(&signature)) Ok(tx.rlp_signed(self.signer.chain_id(), &signature))
}
async fn fill_transaction(
&self,
tx: &mut TransactionRequest,
block: Option<BlockId>,
) -> Result<(), SignerMiddlewareError<M, S>> {
// 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(())
} }
/// Returns the client's address /// Returns the client's address
@ -192,21 +166,54 @@ where
&self.inner &self.inner
} }
/// Returns the client's address
fn default_sender(&self) -> Option<Address> {
Some(self.address)
}
/// `SignerMiddleware` is instantiated with a signer. /// `SignerMiddleware` is instantiated with a signer.
async fn is_signer(&self) -> bool { async fn is_signer(&self) -> bool {
true true
} }
/// Helper for filling a transaction's nonce using the wallet
async fn fill_transaction(
&self,
tx: &mut TypedTransaction,
block: Option<BlockId>,
) -> 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 /// 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 /// gas cost and nonce calculations take it into account. For simple transactions this can be
/// left to `None`. /// left to `None`.
async fn send_transaction( async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
&self, &self,
mut tx: TransactionRequest, tx: T,
block: Option<BlockId>, block: Option<BlockId>,
) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> { ) -> Result<PendingTransaction<'_, Self::Provider>, 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 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 return self
.inner .inner
.send_transaction(tx, block) .send_transaction(tx, block)
@ -214,18 +221,6 @@ where
.map_err(SignerMiddlewareError::MiddlewareError); .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 // if we have a nonce manager set, we should try handling the result in
// case there was a nonce mismatch // case there was a nonce mismatch
let signed_tx = self.sign_transaction(tx).await?; 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<F, T, E>(item: Option<T>, f: F) -> Result<T, E>
where
F: Future<Output = Result<T, E>>,
{
if let Some(item) = item {
ok(item).await
} else {
f.await
}
}
#[cfg(all(test, not(feature = "celo")))] #[cfg(all(test, not(feature = "celo")))]
mod tests { mod tests {
use super::*; use super::*;
use ethers::{providers::Provider, signers::LocalWallet}; 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; use std::convert::TryFrom;
#[tokio::test] #[tokio::test]
@ -287,7 +273,8 @@ mod tests {
nonce: Some(0.into()), nonce: Some(0.into()),
gas_price: Some(21_000_000_000u128.into()), gas_price: Some(21_000_000_000u128.into()),
data: None, data: None,
}; }
.into();
let chain_id = 1u64; let chain_id = 1u64;
let provider = Provider::try_from("http://localhost:8545").unwrap(); let provider = Provider::try_from("http://localhost:8545").unwrap();

View File

@ -3,7 +3,9 @@ use factory::{CreatedFilter, DsProxyFactory, ADDRESS_BOOK};
use super::{Transformer, TransformerError}; use super::{Transformer, TransformerError};
use ethers_contract::{builders::ContractCall, BaseContract, ContractError}; 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 ethers_providers::Middleware;
use std::sync::Arc; use std::sync::Arc;
@ -178,29 +180,26 @@ impl DsProxy {
} }
impl Transformer for DsProxy { impl Transformer for DsProxy {
fn transform(&self, tx: TransactionRequest) -> Result<TransactionRequest, TransformerError> { fn transform(&self, tx: &mut TypedTransaction) -> Result<(), TransformerError> {
// clone the tx into a new proxy tx.
let mut proxy_tx = tx.clone();
// the target address cannot be None. // the target address cannot be None.
let target = match tx.to { let target = match tx.to() {
Some(NameOrAddress::Address(addr)) => Ok(addr), Some(NameOrAddress::Address(addr)) => addr,
_ => Err(TransformerError::MissingField("to".into())), _ => return Err(TransformerError::MissingField("to".into())),
}?; };
// fetch the data field. // 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. // encode data as the ABI encoded data for DSProxy's execute method.
let selector = id("execute(address,bytes)"); let selector = id("execute(address,bytes)");
let encoded_data = self let encoded_data = self
.contract .contract
.encode_with_selector(selector, (target, data))?; .encode_with_selector(selector, (*target, data))?;
// update appropriate fields of the proxy tx. // update appropriate fields of the proxy tx.
proxy_tx.data = Some(encoded_data); tx.set_data(encoded_data);
proxy_tx.to = Some(NameOrAddress::Address(self.address)); tx.set_to(self.address);
Ok(proxy_tx) Ok(())
} }
} }

View File

@ -1,6 +1,6 @@
use super::{Transformer, TransformerError}; use super::{Transformer, TransformerError};
use async_trait::async_trait; use async_trait::async_trait;
use ethers_core::types::*; use ethers_core::types::{transaction::eip2718::TypedTransaction, *};
use ethers_providers::{FromErr, Middleware, PendingTransaction}; use ethers_providers::{FromErr, Middleware, PendingTransaction};
use thiserror::Error; use thiserror::Error;
@ -53,27 +53,20 @@ where
&self.inner &self.inner
} }
async fn send_transaction( async fn send_transaction<Tx: Into<TypedTransaction> + Send + Sync>(
&self, &self,
mut tx: TransactionRequest, tx: Tx,
block: Option<BlockId>, block: Option<BlockId>,
) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> { ) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
// resolve the to field if that's an ENS name. let mut tx = tx.into();
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())
}
// construct the appropriate proxy tx. // 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. // send the proxy tx.
self.inner self.inner
.send_transaction(proxy_tx, block) .send_transaction(tx, block)
.await .await
.map_err(TransformerMiddlewareError::MiddlewareError) .map_err(TransformerMiddlewareError::MiddlewareError)
} }

View File

@ -5,7 +5,7 @@ mod middleware;
pub use middleware::TransformerMiddleware; pub use middleware::TransformerMiddleware;
use ethers_contract::AbiError; use ethers_contract::AbiError;
use ethers_core::{abi::ParseError, types::*}; use ethers_core::{abi::ParseError, types::transaction::eip2718::TypedTransaction};
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -31,5 +31,5 @@ pub trait Transformer: Send + Sync + std::fmt::Debug {
/// proxy contract. /// proxy contract.
/// ///
/// [`transaction request`]: struct@ethers_core::types::TransactionRequest /// [`transaction request`]: struct@ethers_core::types::TransactionRequest
fn transform(&self, tx: TransactionRequest) -> Result<TransactionRequest, TransformerError>; fn transform(&self, tx: &mut TypedTransaction) -> Result<(), TransformerError>;
} }

View File

@ -33,6 +33,7 @@ async fn using_gas_oracle() {
#[tokio::test] #[tokio::test]
#[ignore] #[ignore]
// TODO: Re-enable, EthGasStation changed its response api @ https://ethgasstation.info/api/ethgasAPI.json
async fn eth_gas_station() { async fn eth_gas_station() {
// initialize and fetch gas estimates from EthGasStation // initialize and fetch gas estimates from EthGasStation
let eth_gas_station_oracle = EthGasStation::new(None); let eth_gas_station_oracle = EthGasStation::new(None);
@ -60,6 +61,8 @@ async fn etherscan() {
#[tokio::test] #[tokio::test]
#[ignore] #[ignore]
// TODO: Etherchain has Cloudflare DDOS protection which makes the request fail
// https://twitter.com/gakonst/status/1421796226316578816
async fn etherchain() { async fn etherchain() {
// initialize and fetch gas estimates from Etherchain // initialize and fetch gas estimates from Etherchain
let etherchain_oracle = Etherchain::new().category(GasCategory::Fast); let etherchain_oracle = Etherchain::new().category(GasCategory::Fast);

View File

@ -83,7 +83,8 @@ pub use pubsub::{PubsubClient, SubscriptionStream};
use async_trait::async_trait; use async_trait::async_trait;
use auto_impl::auto_impl; 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}; use std::{error::Error, fmt::Debug, future::Future, pin::Pin};
pub use provider::{FilterKind, Provider, ProviderError}; pub use provider::{FilterKind, Provider, ProviderError};
@ -112,6 +113,18 @@ pub trait FromErr<T> {
fn from(src: T) -> Self; fn from(src: T) -> Self;
} }
/// Calls the future if `item` is None, otherwise returns a `futures::ok`
pub async fn maybe<F, T, E>(item: Option<T>, f: F) -> Result<T, E>
where
F: Future<Output = Result<T, E>>,
{
if let Some(item) = item {
futures_util::future::ok(item).await
} else {
f.await
}
}
#[async_trait] #[async_trait]
#[auto_impl(&, Box, Arc)] #[auto_impl(&, Box, Arc)]
/// A middleware allows customizing requests send and received from an ethereum node. /// A middleware allows customizing requests send and received from an ethereum node.
@ -122,7 +135,7 @@ pub trait FromErr<T> {
/// 3. implementing any of the methods you want to override /// 3. implementing any of the methods you want to override
/// ///
/// ```rust /// ```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 thiserror::Error;
/// use async_trait::async_trait; /// use async_trait::async_trait;
/// ///
@ -163,7 +176,7 @@ pub trait FromErr<T> {
/// ///
/// /// Overrides the default `estimate_gas` method to log that it was called, /// /// Overrides the default `estimate_gas` method to log that it was called,
/// /// before forwarding the call to the next layer. /// /// before forwarding the call to the next layer.
/// async fn estimate_gas(&self, tx: &TransactionRequest) -> Result<U256, Self::Error> { /// async fn estimate_gas(&self, tx: &TypedTransaction) -> Result<U256, Self::Error> {
/// println!("Estimating gas..."); /// println!("Estimating gas...");
/// self.inner().estimate_gas(tx).await.map_err(FromErr::from) /// self.inner().estimate_gas(tx).await.map_err(FromErr::from)
/// } /// }
@ -182,17 +195,90 @@ pub trait Middleware: Sync + Send + Debug {
self.inner().provider() self.inner().provider()
} }
fn default_sender(&self) -> Option<Address> {
self.inner().default_sender()
}
async fn client_version(&self) -> Result<String, Self::Error> { async fn client_version(&self) -> Result<String, Self::Error> {
self.inner().client_version().await.map_err(FromErr::from) self.inner().client_version().await.map_err(FromErr::from)
} }
/// Helper for filling a transaction
async fn fill_transaction(
&self,
tx: &mut TypedTransaction,
block: Option<BlockId>,
) -> 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<U64, Self::Error> { async fn get_block_number(&self) -> Result<U64, Self::Error> {
self.inner().get_block_number().await.map_err(FromErr::from) self.inner().get_block_number().await.map_err(FromErr::from)
} }
async fn send_transaction( async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
&self, &self,
tx: TransactionRequest, tx: T,
block: Option<BlockId>, block: Option<BlockId>,
) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> { ) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
self.inner() self.inner()
@ -246,13 +332,13 @@ pub trait Middleware: Sync + Send + Debug {
.map_err(FromErr::from) .map_err(FromErr::from)
} }
async fn estimate_gas(&self, tx: &TransactionRequest) -> Result<U256, Self::Error> { async fn estimate_gas(&self, tx: &TypedTransaction) -> Result<U256, Self::Error> {
self.inner().estimate_gas(tx).await.map_err(FromErr::from) self.inner().estimate_gas(tx).await.map_err(FromErr::from)
} }
async fn call( async fn call(
&self, &self,
tx: &TransactionRequest, tx: &TypedTransaction,
block: Option<BlockId>, block: Option<BlockId>,
) -> Result<Bytes, Self::Error> { ) -> Result<Bytes, Self::Error> {
self.inner().call(tx, block).await.map_err(FromErr::from) self.inner().call(tx, block).await.map_err(FromErr::from)
@ -427,9 +513,9 @@ pub trait Middleware: Sync + Send + Debug {
// Parity `trace` support // Parity `trace` support
/// Executes the given call and returns a number of possible traces for it /// Executes the given call and returns a number of possible traces for it
async fn trace_call( async fn trace_call<T: Into<TypedTransaction> + Send + Sync>(
&self, &self,
req: TransactionRequest, req: T,
trace_type: Vec<TraceType>, trace_type: Vec<TraceType>,
block: Option<BlockNumber>, block: Option<BlockNumber>,
) -> Result<BlockTrace, Self::Error> { ) -> Result<BlockTrace, Self::Error> {
@ -574,6 +660,38 @@ pub trait Middleware: Sync + Send + Debug {
.await .await
.map_err(FromErr::from) .map_err(FromErr::from)
} }
async fn fee_history(
&self,
block_count: u64,
last_block: BlockNumber,
reward_percentiles: &[f64],
) -> Result<FeeHistory, Self::Error> {
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<BlockId>,
) -> Result<AccessListWithGasUsed, Self::Error> {
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<U256>,
pub gas_used_ratio: Vec<f64>,
pub oldest_block: u64,
pub reward: Vec<Vec<U256>>,
} }
#[cfg(feature = "celo")] #[cfg(feature = "celo")]

View File

@ -2,15 +2,16 @@ use crate::{
ens, ens,
pubsub::{PubsubClient, SubscriptionStream}, pubsub::{PubsubClient, SubscriptionStream},
stream::{FilterWatcher, DEFAULT_POLL_INTERVAL}, stream::{FilterWatcher, DEFAULT_POLL_INTERVAL},
FromErr, Http as HttpProvider, JsonRpcClient, MockProvider, PendingTransaction, FeeHistory, FromErr, Http as HttpProvider, JsonRpcClient, MockProvider, PendingTransaction,
}; };
use ethers_core::{ use ethers_core::{
abi::{self, Detokenize, ParamType}, abi::{self, Detokenize, ParamType},
types::{ types::{
transaction::{eip2718::TypedTransaction, eip2930::AccessListWithGasUsed},
Address, Block, BlockId, BlockNumber, BlockTrace, Bytes, Filter, Log, NameOrAddress, Address, Block, BlockId, BlockNumber, BlockTrace, Bytes, Filter, Log, NameOrAddress,
Selector, Signature, Trace, TraceFilter, TraceType, Transaction, TransactionReceipt, Selector, Signature, Trace, TraceFilter, TraceType, Transaction, TransactionReceipt,
TransactionRequest, TxHash, TxpoolContent, TxpoolInspect, TxpoolStatus, H256, U256, U64, TxHash, TxpoolContent, TxpoolInspect, TxpoolStatus, H256, U256, U64,
}, },
utils, utils,
}; };
@ -192,6 +193,10 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
self self
} }
fn default_sender(&self) -> Option<Address> {
self.from
}
////// Blockchain Status ////// Blockchain Status
// //
// Functions for querying the state of the blockchain // Functions for querying the state of the blockchain
@ -307,7 +312,7 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
/// This is free, since it does not change any state on the blockchain. /// This is free, since it does not change any state on the blockchain.
async fn call( async fn call(
&self, &self,
tx: &TransactionRequest, tx: &TypedTransaction,
block: Option<BlockId>, block: Option<BlockId>,
) -> Result<Bytes, ProviderError> { ) -> Result<Bytes, ProviderError> {
let tx = utils::serialize(tx); let tx = utils::serialize(tx);
@ -318,33 +323,29 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
/// Sends a transaction to a single Ethereum node and return the estimated amount of gas required (as a U256) to send it /// 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 /// This is free, but only an estimate. Providing too little gas will result in a transaction being rejected
/// (while still consuming all provided gas). /// (while still consuming all provided gas).
async fn estimate_gas(&self, tx: &TransactionRequest) -> Result<U256, ProviderError> { async fn estimate_gas(&self, tx: &TypedTransaction) -> Result<U256, ProviderError> {
self.request("eth_estimateGas", [tx]).await self.request("eth_estimateGas", [tx]).await
} }
async fn create_access_list(
&self,
tx: &TypedTransaction,
block: Option<BlockId>,
) -> Result<AccessListWithGasUsed, ProviderError> {
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 /// 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. /// This will consume gas from the account that signed the transaction.
async fn send_transaction( async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
&self, &self,
mut tx: TransactionRequest, tx: T,
_: Option<BlockId>, block: Option<BlockId>,
) -> Result<PendingTransaction<'_, P>, ProviderError> { ) -> Result<PendingTransaction<'_, P>, ProviderError> {
if tx.from.is_none() { let mut tx = tx.into();
tx.from = self.from; self.fill_transaction(&mut tx, block).await?;
}
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 tx_hash = self.request("eth_sendTransaction", [tx]).await?; let tx_hash = self.request("eth_sendTransaction", [tx]).await?;
Ok(PendingTransaction::new(tx_hash, self).interval(self.get_interval())) Ok(PendingTransaction::new(tx_hash, self).interval(self.get_interval()))
@ -557,12 +558,13 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
} }
/// Executes the given call and returns a number of possible traces for it /// Executes the given call and returns a number of possible traces for it
async fn trace_call( async fn trace_call<T: Into<TypedTransaction> + Send + Sync>(
&self, &self,
req: TransactionRequest, req: T,
trace_type: Vec<TraceType>, trace_type: Vec<TraceType>,
block: Option<BlockNumber>, block: Option<BlockNumber>,
) -> Result<BlockTrace, ProviderError> { ) -> Result<BlockTrace, ProviderError> {
let req = req.into();
let req = utils::serialize(&req); let req = utils::serialize(&req);
let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest)); let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest));
let trace_type = utils::serialize(&trace_type); let trace_type = utils::serialize(&trace_type);
@ -694,6 +696,22 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
let filter = utils::serialize(filter); let filter = utils::serialize(filter);
self.subscribe([logs, filter]).await self.subscribe([logs, filter]).await
} }
async fn fee_history(
&self,
block_count: u64,
last_block: BlockNumber,
reward_percentiles: &[f64],
) -> Result<FeeHistory, Self::Error> {
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<P: JsonRpcClient> Provider<P> { impl<P: JsonRpcClient> Provider<P> {
@ -709,7 +727,7 @@ impl<P: JsonRpcClient> Provider<P> {
// first get the resolver responsible for this name // first get the resolver responsible for this name
// the call will return a Bytes array which we convert to an address // the call will return a Bytes array which we convert to an address
let data = self let data = self
.call(&ens::get_resolver(ens_addr, ens_name), None) .call(&ens::get_resolver(ens_addr, ens_name).into(), None)
.await?; .await?;
let resolver_address: Address = decode_bytes(ParamType::Address, data); let resolver_address: Address = decode_bytes(ParamType::Address, data);
@ -719,7 +737,10 @@ impl<P: JsonRpcClient> Provider<P> {
// resolve // resolve
let data = self let data = self
.call(&ens::resolve(resolver_address, selector, ens_name), None) .call(
&ens::resolve(resolver_address, selector, ens_name).into(),
None,
)
.await?; .await?;
Ok(decode_bytes(param, data)) Ok(decode_bytes(param, data))
@ -885,7 +906,7 @@ mod ens_tests {
mod tests { mod tests {
use super::*; use super::*;
use crate::Http; use crate::Http;
use ethers_core::types::H256; use ethers_core::types::{TransactionRequest, H256};
use ethers_core::utils::Geth; use ethers_core::utils::Geth;
use futures_util::StreamExt; use futures_util::StreamExt;
@ -1020,4 +1041,19 @@ mod tests {
.await; .await;
assert_eq!(blocks, vec![1, 2, 3]); assert_eq!(blocks, vec![1, 2, 3]);
} }
#[tokio::test]
#[cfg_attr(feature = "celo", ignore)]
async fn fee_history() {
let provider = Provider::<Http>::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);
}
} }

View File

@ -6,6 +6,7 @@ mod eth_tests {
use super::*; use super::*;
use ethers::{ use ethers::{
middleware::SignerMiddleware, middleware::SignerMiddleware,
prelude::transaction::eip2718::TypedTransaction,
signers::{LocalWallet, Signer}, signers::{LocalWallet, Signer},
types::{BlockId, TransactionRequest, H256}, types::{BlockId, TransactionRequest, H256},
utils::Ganache, utils::Ganache,
@ -148,6 +149,56 @@ mod eth_tests {
// got the correct receipt // got the correct receipt
assert_eq!(receipt.transaction_hash, tx_hash); assert_eq!(receipt.transaction_hash, tx_hash);
} }
#[tokio::test]
async fn typed_txs() {
use ethers_core::types::Eip1559TransactionRequest;
let provider = Provider::<Http>::try_from(
"https://rinkeby.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27",
)
.unwrap();
let chain_id = provider.get_chainid().await.unwrap();
let wallet = "59c37cb6b16fa2de30675f034c8008f890f4b2696c729d6267946d29736d73e4"
.parse::<LocalWallet>()
.unwrap()
.with_chain_id(chain_id.as_u64());
let address = wallet.address();
let provider = SignerMiddleware::new(provider, wallet);
async fn check_tx<M: Middleware>(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")] #[cfg(feature = "celo")]

View File

@ -2,7 +2,7 @@
use ethers_core::{ use ethers_core::{
k256::ecdsa::{Error as K256Error, Signature as KSig, VerifyingKey}, 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, utils::hash_message,
}; };
use rusoto_core::RusotoError; use rusoto_core::RusotoError;
@ -240,7 +240,7 @@ impl<'a> super::Signer for AwsSigner<'a> {
} }
#[instrument(err)] #[instrument(err)]
async fn sign_transaction(&self, tx: &TransactionRequest) -> Result<EthSig, Self::Error> { async fn sign_transaction(&self, tx: &TypedTransaction) -> Result<EthSig, Self::Error> {
let sighash = tx.sighash(self.chain_id); let sighash = tx.sighash(self.chain_id);
self.sign_digest_with_eip155(sighash).await self.sign_digest_with_eip155(sighash).await
} }

View File

@ -8,7 +8,8 @@ use futures_util::lock::Mutex;
use ethers_core::{ use ethers_core::{
types::{ types::{
Address, NameOrAddress, Signature, Transaction, TransactionRequest, TxHash, H256, U256, transaction::eip2718::TypedTransaction, Address, NameOrAddress, Signature, Transaction,
TransactionRequest, TxHash, H256, U256,
}, },
utils::keccak256, utils::keccak256,
}; };
@ -118,7 +119,7 @@ impl LedgerEthereum {
} }
/// Signs an Ethereum transaction (requires confirmation on the ledger) /// Signs an Ethereum transaction (requires confirmation on the ledger)
pub async fn sign_tx(&self, tx: &TransactionRequest) -> Result<Signature, LedgerError> { pub async fn sign_tx(&self, tx: &TypedTransaction) -> Result<Signature, LedgerError> {
let mut payload = Self::path_to_bytes(&self.derivation); let mut payload = Self::path_to_bytes(&self.derivation);
payload.extend_from_slice(tx.rlp(self.chain_id).as_ref()); payload.extend_from_slice(tx.rlp(self.chain_id).as_ref());
self.sign_payload(INS::SIGN, payload).await self.sign_payload(INS::SIGN, payload).await
@ -241,7 +242,8 @@ mod tests {
.gas_price(400e9 as u64) .gas_price(400e9 as u64)
.nonce(5) .nonce(5)
.data(data) .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(); let tx = ledger.sign_transaction(&tx_req).await.unwrap();
} }

View File

@ -4,7 +4,7 @@ pub mod types;
use crate::Signer; use crate::Signer;
use app::LedgerEthereum; use app::LedgerEthereum;
use async_trait::async_trait; use async_trait::async_trait;
use ethers_core::types::{Address, Signature, TransactionRequest}; use ethers_core::types::{transaction::eip2718::TypedTransaction, Address, Signature};
use types::LedgerError; use types::LedgerError;
#[async_trait] #[async_trait]
@ -20,10 +20,7 @@ impl Signer for LedgerEthereum {
} }
/// Signs the transaction /// Signs the transaction
async fn sign_transaction( async fn sign_transaction(&self, message: &TypedTransaction) -> Result<Signature, Self::Error> {
&self,
message: &TransactionRequest,
) -> Result<Signature, Self::Error> {
self.sign_tx(message).await self.sign_tx(message).await
} }

View File

@ -25,7 +25,7 @@
//! // create a transaction //! // create a transaction
//! let tx = TransactionRequest::new() //! let tx = TransactionRequest::new()
//! .to("vitalik.eth") // this will use ENS //! .to("vitalik.eth") // this will use ENS
//! .value(10000); //! .value(10000).into();
//! //!
//! // sign it //! // sign it
//! let signature = wallet.sign_transaction(&tx).await?; //! let signature = wallet.sign_transaction(&tx).await?;
@ -70,7 +70,7 @@ mod aws;
pub use aws::{AwsSigner, AwsSignerError}; pub use aws::{AwsSigner, AwsSignerError};
use async_trait::async_trait; 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; use std::error::Error;
/// Applies [EIP155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md) /// 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<Signature, Self::Error>; ) -> Result<Signature, Self::Error>;
/// Signs the transaction /// Signs the transaction
async fn sign_transaction( async fn sign_transaction(&self, message: &TypedTransaction) -> Result<Signature, Self::Error>;
&self,
message: &TransactionRequest,
) -> Result<Signature, Self::Error>;
/// Returns the signer's Ethereum Address /// Returns the signer's Ethereum Address
fn address(&self) -> Address; fn address(&self) -> Address;

View File

@ -16,7 +16,7 @@ use ethers_core::{
elliptic_curve::FieldBytes, elliptic_curve::FieldBytes,
Secp256k1, Secp256k1,
}, },
types::{Address, Signature, TransactionRequest, H256}, types::{transaction::eip2718::TypedTransaction, Address, Signature, H256},
utils::hash_message, utils::hash_message,
}; };
use hash::Sha256Proxy; use hash::Sha256Proxy;
@ -78,7 +78,7 @@ impl<D: Sync + Send + DigestSigner<Sha256Proxy, RecoverableSignature>> Signer fo
Ok(self.sign_hash(message_hash, false)) Ok(self.sign_hash(message_hash, false))
} }
async fn sign_transaction(&self, tx: &TransactionRequest) -> Result<Signature, Self::Error> { async fn sign_transaction(&self, tx: &TypedTransaction) -> Result<Signature, Self::Error> {
let sighash = tx.sighash(self.chain_id); let sighash = tx.sighash(self.chain_id);
Ok(self.sign_hash(sighash, true)) Ok(self.sign_hash(sighash, true))
} }

View File

@ -217,7 +217,8 @@ mod tests {
nonce: Some(0.into()), nonce: Some(0.into()),
gas_price: Some(21_000_000_000u128.into()), gas_price: Some(21_000_000_000u128.into()),
data: None, data: None,
}; }
.into();
let chain_id = 1u64; let chain_id = 1u64;
let wallet: Wallet<SigningKey> = let wallet: Wallet<SigningKey> =