feat(ethers-contract): typed txs (part 4) (#362)

* feat(contract): use eip-1559 txs by default

This may break any tests which use Ganache, since it only supports legacy transactions.
We should replace Ganache with hardhat where possible

* fix(providers): always try setting default_sender

* chore: make all test transactions legacy

* feat(multicall): allow submitting legacy txs

* chore: default to legacy txs via feature flag

This is useful for chains like Celo that do not support the Typed Envelope

* fix: use legacy txs in ds proxy deployment / tests

* chore: fix review comments
This commit is contained in:
Georgios Konstantopoulos 2021-08-09 03:50:38 +03:00 committed by GitHub
parent dcbfacf5bc
commit 25c2e0e199
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 158 additions and 33 deletions

View File

@ -34,7 +34,8 @@ ethers-middleware = { version = "0.4.0", path = "../ethers-middleware" }
[features] [features]
abigen = ["ethers-contract-abigen", "ethers-contract-derive"] abigen = ["ethers-contract-abigen", "ethers-contract-derive"]
celo = ["ethers-core/celo", "ethers-core/celo", "ethers-providers/celo"] celo = ["legacy", "ethers-core/celo", "ethers-core/celo", "ethers-providers/celo"]
legacy = []
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View File

@ -1,7 +1,9 @@
use super::base::{decode_function_data, AbiError}; use super::base::{decode_function_data, AbiError};
use ethers_core::{ use ethers_core::{
abi::{Detokenize, Function, InvalidOutputType}, abi::{Detokenize, Function, InvalidOutputType},
types::{Address, BlockId, Bytes, TransactionRequest, U256}, types::{
transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, TransactionRequest, U256,
},
}; };
use ethers_providers::{Middleware, PendingTransaction, ProviderError}; use ethers_providers::{Middleware, PendingTransaction, ProviderError};
@ -48,7 +50,7 @@ pub enum ContractError<M: Middleware> {
/// Helper for managing a transaction before submitting it to a node /// Helper for managing a transaction before submitting it to a node
pub struct ContractCall<M, D> { pub struct ContractCall<M, D> {
/// The raw transaction object /// The raw transaction object
pub tx: TransactionRequest, pub tx: TypedTransaction,
/// The ABI of the function being called /// The ABI of the function being called
pub function: Function, pub function: Function,
/// Optional block number to be used when calculating the transaction's gas and nonce /// Optional block number to be used when calculating the transaction's gas and nonce
@ -60,25 +62,39 @@ pub struct ContractCall<M, D> {
impl<M, D: Detokenize> ContractCall<M, D> { impl<M, D: Detokenize> ContractCall<M, D> {
/// Sets the `from` field in the transaction to the provided value /// Sets the `from` field in the transaction to the provided value
pub fn from<T: Into<Address>>(mut self, from: T) -> Self { pub fn from<T: Into<Address>>(mut self, from: T) -> Self {
self.tx.from = Some(from.into()); self.tx.set_from(from.into());
self
}
/// Uses a Legacy transaction instead of an EIP-1559 one to execute the call
pub fn legacy(mut self) -> Self {
self.tx = match self.tx {
TypedTransaction::Eip1559(inner) => {
let tx: TransactionRequest = inner.into();
TypedTransaction::Legacy(tx)
}
other => other,
};
self self
} }
/// Sets the `gas` field in the transaction to the provided value /// Sets the `gas` field in the transaction to the provided value
pub fn gas<T: Into<U256>>(mut self, gas: T) -> Self { pub fn gas<T: Into<U256>>(mut self, gas: T) -> Self {
self.tx.gas = Some(gas.into()); self.tx.set_gas(gas);
self self
} }
/// Sets the `gas_price` field in the transaction to the provided value /// Sets the `gas_price` field in the transaction to the provided value
/// If the internal transaction is an EIP-1559 one, then it sets both
/// `max_fee_per_gas` and `max_priority_fee_per_gas` to the same value
pub fn gas_price<T: Into<U256>>(mut self, gas_price: T) -> Self { pub fn gas_price<T: Into<U256>>(mut self, gas_price: T) -> Self {
self.tx.gas_price = Some(gas_price.into()); self.tx.set_gas_price(gas_price);
self self
} }
/// Sets the `value` field in the transaction to the provided value /// Sets the `value` field in the transaction to the provided value
pub fn value<T: Into<U256>>(mut self, value: T) -> Self { pub fn value<T: Into<U256>>(mut self, value: T) -> Self {
self.tx.value = Some(value.into()); self.tx.set_value(value);
self self
} }
@ -96,13 +112,13 @@ where
{ {
/// Returns the underlying transaction's ABI encoded data /// Returns the underlying transaction's ABI encoded data
pub fn calldata(&self) -> Option<Bytes> { pub fn calldata(&self) -> Option<Bytes> {
self.tx.data.clone() self.tx.data().cloned()
} }
/// 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.clone().into()) .estimate_gas(&self.tx)
.await .await
.map_err(ContractError::MiddlewareError) .map_err(ContractError::MiddlewareError)
} }
@ -119,7 +135,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.clone().into(), self.block) .call(&self.tx.clone(), self.block)
.await .await
.map_err(ContractError::MiddlewareError)?; .map_err(ContractError::MiddlewareError)?;

View File

@ -7,8 +7,14 @@ use crate::{
use ethers_core::{ use ethers_core::{
abi::{Abi, Detokenize, Error, EventExt, Function, Tokenize}, abi::{Abi, Detokenize, Error, EventExt, Function, Tokenize},
types::{Address, Filter, NameOrAddress, Selector, TransactionRequest}, types::{Address, Filter, NameOrAddress, Selector},
}; };
#[cfg(not(feature = "legacy"))]
use ethers_core::types::Eip1559TransactionRequest;
#[cfg(feature = "legacy")]
use ethers_core::types::TransactionRequest;
use ethers_providers::Middleware; use ethers_providers::Middleware;
use std::{fmt::Debug, marker::PhantomData, sync::Arc}; use std::{fmt::Debug, marker::PhantomData, sync::Arc};
@ -220,12 +226,20 @@ impl<M: Middleware> Contract<M> {
) -> Result<ContractCall<M, D>, AbiError> { ) -> Result<ContractCall<M, D>, AbiError> {
let data = encode_function_data(function, args)?; let data = encode_function_data(function, args)?;
// create the tx object #[cfg(feature = "legacy")]
let tx = TransactionRequest { let tx = TransactionRequest {
to: Some(NameOrAddress::Address(self.address)), to: Some(NameOrAddress::Address(self.address)),
data: Some(data), data: Some(data),
..Default::default() ..Default::default()
}; };
#[cfg(not(feature = "legacy"))]
let tx = Eip1559TransactionRequest {
to: Some(NameOrAddress::Address(self.address)),
data: Some(data),
..Default::default()
};
let tx = tx.into();
Ok(ContractCall { Ok(ContractCall {
tx, tx,

View File

@ -2,17 +2,20 @@ use crate::{Contract, ContractError};
use ethers_core::{ use ethers_core::{
abi::{Abi, Tokenize}, abi::{Abi, Tokenize},
types::{BlockNumber, Bytes, TransactionRequest}, types::{transaction::eip2718::TypedTransaction, BlockNumber, Bytes, TransactionRequest},
}; };
use ethers_providers::Middleware; use ethers_providers::Middleware;
#[cfg(not(feature = "legacy"))]
use ethers_core::types::Eip1559TransactionRequest;
use std::sync::Arc; use std::sync::Arc;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
/// Helper which manages the deployment transaction of a smart contract /// Helper which manages the deployment transaction of a smart contract
pub struct Deployer<M> { pub struct Deployer<M> {
/// The deployer's transaction, exposed for overriding the defaults /// The deployer's transaction, exposed for overriding the defaults
pub tx: TransactionRequest, pub tx: TypedTransaction,
abi: Abi, abi: Abi,
client: Arc<M>, client: Arc<M>,
confs: usize, confs: usize,
@ -31,6 +34,18 @@ impl<M: Middleware> Deployer<M> {
self self
} }
/// Uses a Legacy transaction instead of an EIP-1559 one to do the deployment
pub fn legacy(mut self) -> Self {
self.tx = match self.tx {
TypedTransaction::Eip1559(inner) => {
let tx: TransactionRequest = inner.into();
TypedTransaction::Legacy(tx)
}
other => other,
};
self
}
/// Broadcasts the contract deployment transaction and after waiting for it to /// Broadcasts the contract deployment transaction and after waiting for it to
/// be sufficiently confirmed (default: 1), it returns a [`Contract`](crate::Contract) /// be sufficiently confirmed (default: 1), it returns a [`Contract`](crate::Contract)
/// struct at the deployed contract's address. /// struct at the deployed contract's address.
@ -150,11 +165,21 @@ impl<M: Middleware> ContractFactory<M> {
}; };
// create the tx object. Since we're deploying a contract, `to` is `None` // create the tx object. Since we're deploying a contract, `to` is `None`
// We default to EIP-1559 transactions, but the sender can convert it back
// to a legacy one
#[cfg(feature = "legacy")]
let tx = TransactionRequest { let tx = TransactionRequest {
to: None, to: None,
data: Some(data), data: Some(data),
..Default::default() ..Default::default()
}; };
#[cfg(not(feature = "legacy"))]
let tx = Eip1559TransactionRequest {
to: None,
data: Some(data),
..Default::default()
};
let tx = tx.into();
Ok(Deployer { Ok(Deployer {
client: Arc::clone(&self.client), // cheap clone behind the arc client: Arc::clone(&self.client), // cheap clone behind the arc

View File

@ -137,6 +137,7 @@ pub struct Multicall<M> {
calls: Vec<Call>, calls: Vec<Call>,
block: Option<BlockNumber>, block: Option<BlockNumber>,
contract: MulticallContract<M>, contract: MulticallContract<M>,
legacy: bool,
} }
#[derive(Clone)] #[derive(Clone)]
@ -188,9 +189,16 @@ impl<M: Middleware> Multicall<M> {
calls: vec![], calls: vec![],
block: None, block: None,
contract, contract,
legacy: false,
}) })
} }
/// Makes a legacy transaction instead of an EIP-1559 one
pub fn legacy(mut self) -> Self {
self.legacy = true;
self
}
/// Sets the `block` field for the multicall aggregate call /// Sets the `block` field for the multicall aggregate call
pub fn block<T: Into<BlockNumber>>(mut self, block: T) -> Self { pub fn block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
self.block = Some(block.into()); self.block = Some(block.into());
@ -208,11 +216,11 @@ impl<M: Middleware> Multicall<M> {
panic!("Cannot support more than {} calls", 16); panic!("Cannot support more than {} calls", 16);
} }
match (call.tx.to, call.tx.data) { match (call.tx.to(), call.tx.data()) {
(Some(NameOrAddress::Address(target)), Some(data)) => { (Some(NameOrAddress::Address(target)), Some(data)) => {
let call = Call { let call = Call {
target, target: *target,
data, data: data.clone(),
function: call.function, function: call.function,
}; };
self.calls.push(call); self.calls.push(call);
@ -366,11 +374,15 @@ impl<M: Middleware> Multicall<M> {
.collect(); .collect();
// Construct the ContractCall for `aggregate` function to broadcast the transaction // Construct the ContractCall for `aggregate` function to broadcast the transaction
let contract_call = self.contract.aggregate(calls); let mut contract_call = self.contract.aggregate(calls);
if let Some(block) = self.block { if let Some(block) = self.block {
contract_call.block(block) contract_call = contract_call.block(block)
} else { };
contract_call
} if self.legacy {
contract_call = contract_call.legacy();
};
contract_call
} }
} }

View File

@ -50,6 +50,7 @@ pub async fn deploy<M: Middleware>(client: Arc<M>, abi: Abi, bytecode: Bytes) ->
factory factory
.deploy("initial value".to_string()) .deploy("initial value".to_string())
.unwrap() .unwrap()
.legacy()
.send() .send()
.await .await
.unwrap() .unwrap()

View File

@ -32,7 +32,10 @@ mod eth_tests {
// `send` consumes the deployer so it must be cloned for later re-use // `send` consumes the deployer so it must be cloned for later re-use
// (practically it's not expected that you'll need to deploy multiple instances of // (practically it's not expected that you'll need to deploy multiple instances of
// the _same_ deployer, so it's fine to clone here from a dev UX vs perf tradeoff) // the _same_ deployer, so it's fine to clone here from a dev UX vs perf tradeoff)
let deployer = factory.deploy("initial value".to_string()).unwrap(); let deployer = factory
.deploy("initial value".to_string())
.unwrap()
.legacy();
let contract = deployer.clone().send().await.unwrap(); let contract = deployer.clone().send().await.unwrap();
let get_value = contract.method::<_, String>("getValue", ()).unwrap(); let get_value = contract.method::<_, String>("getValue", ()).unwrap();
@ -51,6 +54,7 @@ mod eth_tests {
.unwrap(); .unwrap();
let calldata = contract_call.calldata().unwrap(); let calldata = contract_call.calldata().unwrap();
let gas_estimate = contract_call.estimate_gas().await.unwrap(); let gas_estimate = contract_call.estimate_gas().await.unwrap();
let contract_call = contract_call.legacy();
let pending_tx = contract_call.send().await.unwrap(); let pending_tx = contract_call.send().await.unwrap();
let tx = client.get_transaction(*pending_tx).await.unwrap().unwrap(); let tx = client.get_transaction(*pending_tx).await.unwrap().unwrap();
let tx_receipt = pending_tx.await.unwrap().unwrap(); let tx_receipt = pending_tx.await.unwrap().unwrap();
@ -82,6 +86,7 @@ mod eth_tests {
let _tx_hash = contract let _tx_hash = contract
.method::<_, H256>("setValues", ("hi".to_owned(), "bye".to_owned())) .method::<_, H256>("setValues", ("hi".to_owned(), "bye".to_owned()))
.unwrap() .unwrap()
.legacy()
.send() .send()
.await .await
.unwrap() .unwrap()
@ -99,7 +104,8 @@ mod eth_tests {
// make a call with `client` // make a call with `client`
let func = contract let func = contract
.method::<_, H256>("setValue", "hi".to_owned()) .method::<_, H256>("setValue", "hi".to_owned())
.unwrap(); .unwrap()
.legacy();
let tx = func.send().await.unwrap(); let tx = func.send().await.unwrap();
let _receipt = tx.await.unwrap(); let _receipt = tx.await.unwrap();
@ -171,6 +177,7 @@ mod eth_tests {
let value = contract let value = contract
.method::<_, String>("getValue", ()) .method::<_, String>("getValue", ())
.unwrap() .unwrap()
.legacy()
.call() .call()
.await .await
.unwrap(); .unwrap();
@ -180,6 +187,7 @@ mod eth_tests {
let _tx_hash = *contract let _tx_hash = *contract
.method::<_, H256>("setValue", "hi".to_owned()) .method::<_, H256>("setValue", "hi".to_owned())
.unwrap() .unwrap()
.legacy()
.send() .send()
.await .await
.unwrap(); .unwrap();
@ -188,6 +196,7 @@ mod eth_tests {
let value = contract let value = contract
.method::<_, String>("getValue", ()) .method::<_, String>("getValue", ())
.unwrap() .unwrap()
.legacy()
.call() .call()
.await .await
.unwrap(); .unwrap();
@ -197,6 +206,7 @@ mod eth_tests {
let value = contract let value = contract
.method::<_, String>("getValue", ()) .method::<_, String>("getValue", ())
.unwrap() .unwrap()
.legacy()
.block(BlockId::Number(deployed_block.into())) .block(BlockId::Number(deployed_block.into()))
.call() .call()
.await .await
@ -302,7 +312,8 @@ mod eth_tests {
for i in 0..num_calls { for i in 0..num_calls {
let call = contract let call = contract
.method::<_, H256>("setValue", i.to_string()) .method::<_, H256>("setValue", i.to_string())
.unwrap(); .unwrap()
.legacy();
let pending_tx = call.send().await.unwrap(); let pending_tx = call.send().await.unwrap();
let _receipt = pending_tx.await.unwrap(); let _receipt = pending_tx.await.unwrap();
} }
@ -341,7 +352,6 @@ mod eth_tests {
// get the first account // get the first account
let deployer = provider.get_accounts().await.unwrap()[0]; let deployer = provider.get_accounts().await.unwrap()[0];
let client = Arc::new(provider.with_sender(deployer)); let client = Arc::new(provider.with_sender(deployer));
dbg!(deployer);
let contract = deploy(client, abi, bytecode).await; let contract = deploy(client, abi, bytecode).await;
@ -396,18 +406,26 @@ mod eth_tests {
let not_so_simple_factory = let not_so_simple_factory =
ContractFactory::new(not_so_simple_abi, not_so_simple_bytecode, client3.clone()); ContractFactory::new(not_so_simple_abi, not_so_simple_bytecode, client3.clone());
let multicall_contract = multicall_factory.deploy(()).unwrap().send().await.unwrap(); let multicall_contract = multicall_factory
.deploy(())
.unwrap()
.legacy()
.send()
.await
.unwrap();
let addr = multicall_contract.address(); let addr = multicall_contract.address();
let simple_contract = simple_factory let simple_contract = simple_factory
.deploy("the first one".to_string()) .deploy("the first one".to_string())
.unwrap() .unwrap()
.legacy()
.send() .send()
.await .await
.unwrap(); .unwrap();
let not_so_simple_contract = not_so_simple_factory let not_so_simple_contract = not_so_simple_factory
.deploy("the second one".to_string()) .deploy("the second one".to_string())
.unwrap() .unwrap()
.legacy()
.send() .send()
.await .await
.unwrap(); .unwrap();
@ -417,6 +435,7 @@ mod eth_tests {
.connect(client2.clone()) .connect(client2.clone())
.method::<_, H256>("setValue", "reset first".to_owned()) .method::<_, H256>("setValue", "reset first".to_owned())
.unwrap() .unwrap()
.legacy()
.send() .send()
.await .await
.unwrap(); .unwrap();
@ -424,6 +443,7 @@ mod eth_tests {
.connect(client3.clone()) .connect(client3.clone())
.method::<_, H256>("setValue", "reset second".to_owned()) .method::<_, H256>("setValue", "reset second".to_owned())
.unwrap() .unwrap()
.legacy()
.send() .send()
.await .await
.unwrap(); .unwrap();
@ -479,7 +499,7 @@ mod eth_tests {
.add_call(broadcast2); .add_call(broadcast2);
// broadcast the transaction and wait for it to be mined // broadcast the transaction and wait for it to be mined
let tx_hash = multicall_send.send().await.unwrap(); let tx_hash = multicall_send.legacy().send().await.unwrap();
let _tx_receipt = PendingTransaction::new(tx_hash, client.provider()) let _tx_receipt = PendingTransaction::new(tx_hash, client.provider())
.await .await
.unwrap(); .unwrap();
@ -544,7 +564,10 @@ mod celo_tests {
let client = Arc::new(client); let client = Arc::new(client);
let factory = ContractFactory::new(abi, bytecode, client); let factory = ContractFactory::new(abi, bytecode, client);
let deployer = factory.deploy("initial value".to_string()).unwrap(); let deployer = factory
.deploy("initial value".to_string())
.unwrap()
.legacy();
let contract = deployer.block(BlockNumber::Pending).send().await.unwrap(); let contract = deployer.block(BlockNumber::Pending).send().await.unwrap();
let value: String = contract let value: String = contract

View File

@ -168,3 +168,23 @@ impl Eip1559TransactionRequest {
rlp.append(&self.access_list); rlp.append(&self.access_list);
} }
} }
impl From<Eip1559TransactionRequest> for super::request::TransactionRequest {
fn from(tx: Eip1559TransactionRequest) -> Self {
Self {
from: tx.from,
to: tx.to,
gas: tx.gas,
gas_price: tx.max_fee_per_gas,
value: tx.value,
data: tx.data,
nonce: tx.nonce,
#[cfg(feature = "celo")]
fee_currency: None,
#[cfg(feature = "celo")]
gateway_fee_recipient: None,
#[cfg(feature = "celo")]
gateway_fee: None,
}
}
}

View File

@ -109,6 +109,7 @@ impl DsProxy {
let ds_proxy_factory = DsProxyFactory::new(factory, client); let ds_proxy_factory = DsProxyFactory::new(factory, client);
let tx_receipt = ds_proxy_factory let tx_receipt = ds_proxy_factory
.build(owner) .build(owner)
.legacy()
.send() .send()
.await? .await?
.await .await

View File

@ -44,7 +44,8 @@ async fn ds_proxy_transformer() {
contract.bytecode.clone(), contract.bytecode.clone(),
Arc::clone(&provider), Arc::clone(&provider),
); );
let ds_proxy_factory = factory.deploy(()).unwrap().send().await.unwrap(); let ds_proxy_factory = factory.deploy(()).unwrap().legacy();
let ds_proxy_factory = ds_proxy_factory.send().await.unwrap();
// deploy a new DsProxy contract. // deploy a new DsProxy contract.
let ds_proxy = DsProxy::build::<HttpWallet, Arc<HttpWallet>>( let ds_proxy = DsProxy::build::<HttpWallet, Arc<HttpWallet>>(
@ -68,7 +69,8 @@ async fn ds_proxy_transformer() {
contract.bytecode.clone(), contract.bytecode.clone(),
Arc::clone(&provider), Arc::clone(&provider),
); );
let simple_storage = factory.deploy(()).unwrap().send().await.unwrap(); let deployer = factory.deploy(()).unwrap().legacy();
let simple_storage = deployer.send().await.unwrap();
// instantiate a new transformer middleware. // instantiate a new transformer middleware.
let provider = TransformerMiddleware::new(signer_middleware, ds_proxy.clone()); let provider = TransformerMiddleware::new(signer_middleware, ds_proxy.clone());
@ -131,7 +133,8 @@ async fn ds_proxy_code() {
contract.bytecode.clone(), contract.bytecode.clone(),
Arc::clone(&provider), Arc::clone(&provider),
); );
let ds_proxy_factory = factory.deploy(()).unwrap().send().await.unwrap(); let ds_proxy_factory = factory.deploy(()).unwrap().legacy();
let ds_proxy_factory = ds_proxy_factory.send().await.unwrap();
// deploy a new DsProxy contract. // deploy a new DsProxy contract.
let ds_proxy = DsProxy::build::<HttpWallet, Arc<HttpWallet>>( let ds_proxy = DsProxy::build::<HttpWallet, Arc<HttpWallet>>(
@ -164,6 +167,7 @@ async fn ds_proxy_code() {
calldata, calldata,
) )
.expect("could not construct DSProxy contract call") .expect("could not construct DSProxy contract call")
.legacy()
.send() .send()
.await .await
.unwrap(); .unwrap();

View File

@ -240,6 +240,10 @@ pub trait Middleware: Sync + Send + Debug {
inner.tx.to = Some(addr.into()); inner.tx.to = Some(addr.into());
}; };
if inner.tx.from.is_none() {
inner.tx.from = self.default_sender();
}
let (gas_price, gas) = futures_util::try_join!( let (gas_price, gas) = futures_util::try_join!(
maybe(inner.tx.gas_price, self.get_gas_price()), maybe(inner.tx.gas_price, self.get_gas_price()),
maybe(inner.tx.gas, self.estimate_gas(&tx_clone)), maybe(inner.tx.gas, self.estimate_gas(&tx_clone)),
@ -257,6 +261,10 @@ pub trait Middleware: Sync + Send + Debug {
inner.to = Some(addr.into()); inner.to = Some(addr.into());
}; };
if inner.from.is_none() {
inner.from = self.default_sender();
}
let (max_priority_fee_per_gas, max_fee_per_gas, gas) = futures_util::try_join!( let (max_priority_fee_per_gas, max_fee_per_gas, gas) = futures_util::try_join!(
// TODO: Replace with algorithms using eth_feeHistory // TODO: Replace with algorithms using eth_feeHistory
maybe(inner.max_priority_fee_per_gas, self.get_gas_price()), maybe(inner.max_priority_fee_per_gas, self.get_gas_price()),

View File

@ -2,7 +2,7 @@
use ethers_core::{ use ethers_core::{
k256::ecdsa::{Error as K256Error, Signature as KSig, VerifyingKey}, k256::ecdsa::{Error as K256Error, Signature as KSig, VerifyingKey},
types::{Address, Signature as EthSig, H256, transaction::eip2718::TypedTransaction}, types::{transaction::eip2718::TypedTransaction, Address, Signature as EthSig, H256},
utils::hash_message, utils::hash_message,
}; };
use rusoto_core::RusotoError; use rusoto_core::RusotoError;