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:
parent
ccff9fc98f
commit
dcbfacf5bc
|
@ -102,7 +102,7 @@ where
|
|||
/// Returns the estimated gas cost for the underlying transaction to be executed
|
||||
pub async fn estimate_gas(&self) -> Result<U256, ContractError<M>> {
|
||||
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<D, ContractError<M>> {
|
||||
let bytes = self
|
||||
.client
|
||||
.call(&self.tx, self.block)
|
||||
.call(&self.tx.clone().into(), self.block)
|
||||
.await
|
||||
.map_err(ContractError::MiddlewareError)?;
|
||||
|
||||
|
|
|
@ -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<U256>,
|
||||
|
||||
#[serde(
|
||||
rename = "accessList",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub access_list: Option<AccessList>,
|
||||
#[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<T: Into<AccessList>>(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<T: Into<U64>>(&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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<T: Into<NameOrAddress>>(&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<T: Into<U256>>(&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<T: Into<U256>>(&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<T: Into<U256>>(&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<T: Into<U256>>(&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<T: Into<U64>>(&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<T: Into<U64>>(&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) => {
|
||||
|
|
|
@ -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<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 {
|
||||
fn from(src: Vec<AccessListItem>) -> 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);
|
||||
}
|
||||
|
|
|
@ -21,3 +21,12 @@ pub(super) fn rlp_opt<T: rlp::Encodable>(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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<T: Into<TypedTransaction> + Send + Sync>(
|
||||
&self,
|
||||
tx: TransactionRequest,
|
||||
tx: T,
|
||||
block: Option<BlockId>,
|
||||
) -> Result<PendingTransaction<'_, Self::Provider>, 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<M: Middleware> {
|
|||
#[error("{0}")]
|
||||
/// Thrown when an internal middleware errors
|
||||
MiddlewareError(M::Error),
|
||||
|
||||
#[error("Gas escalation is only supported for EIP2930 or Legacy transactions")]
|
||||
UnsupportedTxType,
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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<M: Middleware> {
|
|||
|
||||
#[error("{0}")]
|
||||
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> {
|
||||
|
@ -56,14 +59,28 @@ where
|
|||
Ok(self.gas_oracle.fetch().await?)
|
||||
}
|
||||
|
||||
async fn send_transaction(
|
||||
async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
|
||||
&self,
|
||||
mut tx: TransactionRequest,
|
||||
tx: T,
|
||||
block: Option<BlockId>,
|
||||
) -> Result<PendingTransaction<'_, Self::Provider>, 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
|
||||
|
|
|
@ -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<T: Into<TypedTransaction> + Send + Sync>(
|
||||
&self,
|
||||
mut tx: TransactionRequest,
|
||||
tx: T,
|
||||
block: Option<BlockId>,
|
||||
) -> Result<PendingTransaction<'_, Self::Provider>, 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 {
|
||||
|
|
|
@ -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<Bytes, SignerMiddlewareError<M, S>> {
|
||||
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<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(())
|
||||
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<Address> {
|
||||
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<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
|
||||
/// 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<T: Into<TypedTransaction> + Send + Sync>(
|
||||
&self,
|
||||
mut tx: TransactionRequest,
|
||||
tx: T,
|
||||
block: Option<BlockId>,
|
||||
) -> 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 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<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")))]
|
||||
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();
|
||||
|
|
|
@ -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<TransactionRequest, TransformerError> {
|
||||
// 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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Tx: Into<TypedTransaction> + Send + Sync>(
|
||||
&self,
|
||||
mut tx: TransactionRequest,
|
||||
tx: Tx,
|
||||
block: Option<BlockId>,
|
||||
) -> Result<PendingTransaction<'_, Self::Provider>, 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)
|
||||
}
|
||||
|
|
|
@ -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<TransactionRequest, TransformerError>;
|
||||
fn transform(&self, tx: &mut TypedTransaction) -> Result<(), TransformerError>;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<T> {
|
|||
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]
|
||||
#[auto_impl(&, Box, Arc)]
|
||||
/// 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
|
||||
///
|
||||
/// ```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<T> {
|
|||
///
|
||||
/// /// 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<U256, Self::Error> {
|
||||
/// async fn estimate_gas(&self, tx: &TypedTransaction) -> Result<U256, Self::Error> {
|
||||
/// 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<Address> {
|
||||
self.inner().default_sender()
|
||||
}
|
||||
|
||||
async fn client_version(&self) -> Result<String, Self::Error> {
|
||||
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> {
|
||||
self.inner().get_block_number().await.map_err(FromErr::from)
|
||||
}
|
||||
|
||||
async fn send_transaction(
|
||||
async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
|
||||
&self,
|
||||
tx: TransactionRequest,
|
||||
tx: T,
|
||||
block: Option<BlockId>,
|
||||
) -> Result<PendingTransaction<'_, Self::Provider>, 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<U256, Self::Error> {
|
||||
async fn estimate_gas(&self, tx: &TypedTransaction) -> Result<U256, Self::Error> {
|
||||
self.inner().estimate_gas(tx).await.map_err(FromErr::from)
|
||||
}
|
||||
|
||||
async fn call(
|
||||
&self,
|
||||
tx: &TransactionRequest,
|
||||
tx: &TypedTransaction,
|
||||
block: Option<BlockId>,
|
||||
) -> Result<Bytes, Self::Error> {
|
||||
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<T: Into<TypedTransaction> + Send + Sync>(
|
||||
&self,
|
||||
req: TransactionRequest,
|
||||
req: T,
|
||||
trace_type: Vec<TraceType>,
|
||||
block: Option<BlockNumber>,
|
||||
) -> Result<BlockTrace, Self::Error> {
|
||||
|
@ -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<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")]
|
||||
|
|
|
@ -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<P: JsonRpcClient> Middleware for Provider<P> {
|
|||
self
|
||||
}
|
||||
|
||||
fn default_sender(&self) -> Option<Address> {
|
||||
self.from
|
||||
}
|
||||
|
||||
////// Blockchain Status
|
||||
//
|
||||
// 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.
|
||||
async fn call(
|
||||
&self,
|
||||
tx: &TransactionRequest,
|
||||
tx: &TypedTransaction,
|
||||
block: Option<BlockId>,
|
||||
) -> Result<Bytes, ProviderError> {
|
||||
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
|
||||
/// 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<U256, ProviderError> {
|
||||
async fn estimate_gas(&self, tx: &TypedTransaction) -> Result<U256, ProviderError> {
|
||||
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
|
||||
/// 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,
|
||||
mut tx: TransactionRequest,
|
||||
_: Option<BlockId>,
|
||||
tx: T,
|
||||
block: Option<BlockId>,
|
||||
) -> Result<PendingTransaction<'_, P>, 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<P: JsonRpcClient> Middleware for Provider<P> {
|
|||
}
|
||||
|
||||
/// 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,
|
||||
req: TransactionRequest,
|
||||
req: T,
|
||||
trace_type: Vec<TraceType>,
|
||||
block: Option<BlockNumber>,
|
||||
) -> Result<BlockTrace, ProviderError> {
|
||||
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<P: JsonRpcClient> Middleware for Provider<P> {
|
|||
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<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> {
|
||||
|
@ -709,7 +727,7 @@ impl<P: JsonRpcClient> Provider<P> {
|
|||
// 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<P: JsonRpcClient> Provider<P> {
|
|||
|
||||
// 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::<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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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::<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")]
|
||||
|
|
|
@ -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<EthSig, Self::Error> {
|
||||
async fn sign_transaction(&self, tx: &TypedTransaction) -> Result<EthSig, Self::Error> {
|
||||
let sighash = tx.sighash(self.chain_id);
|
||||
self.sign_digest_with_eip155(sighash).await
|
||||
}
|
||||
|
|
|
@ -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<Signature, LedgerError> {
|
||||
pub async fn sign_tx(&self, tx: &TypedTransaction) -> Result<Signature, LedgerError> {
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Signature, Self::Error> {
|
||||
async fn sign_transaction(&self, message: &TypedTransaction) -> Result<Signature, Self::Error> {
|
||||
self.sign_tx(message).await
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Signature, Self::Error>;
|
||||
|
||||
/// Signs the transaction
|
||||
async fn sign_transaction(
|
||||
&self,
|
||||
message: &TransactionRequest,
|
||||
) -> Result<Signature, Self::Error>;
|
||||
async fn sign_transaction(&self, message: &TypedTransaction) -> Result<Signature, Self::Error>;
|
||||
|
||||
/// Returns the signer's Ethereum Address
|
||||
fn address(&self) -> Address;
|
||||
|
|
|
@ -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<D: Sync + Send + DigestSigner<Sha256Proxy, RecoverableSignature>> Signer fo
|
|||
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);
|
||||
Ok(self.sign_hash(sighash, true))
|
||||
}
|
||||
|
|
|
@ -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<SigningKey> =
|
||||
|
|
Loading…
Reference in New Issue