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
|
/// 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)?;
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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")]
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")]
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> =
|
||||||
|
|
Loading…
Reference in New Issue