Repository: gakonst/ethers-rs
Pull request #286 (2021-05-01 20:06:44)

fix: make chain_id mandatory
since latest versions, geth has been rejecting non-eip155 transactions. as a result, we remove the `Option<u64>` and replace it with just `u64`. **This is a breaking change**

The method `address()` was removed from the `Wallet` type, since it's present in the `Signer`. If your code starts erroring out about `address()` not being available, import `ethers::signers::Signer`.


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4c273c5e..83fed86c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,4 +1,8 @@
-on: pull_request
+on:
+  push:
+    branches: 
+      - master
+  pull_request:
 
 name: Tests
 
@@ -10,7 +14,7 @@ env:
 
 jobs:
   tests:
-    name: Check
+    name: ethereum tests
     runs-on: ubuntu-latest
     steps:
       - name: Checkout sources
@@ -51,7 +55,7 @@ jobs:
             cargo test
 
   feature-tests:
-    name: Check
+    name: celo tests
     runs-on: ubuntu-latest
     steps:
       - name: Checkout sources
@@ -95,7 +99,7 @@ jobs:
             cargo test --all-features
 
   lint:
-    name: Check
+    name: lints
     runs-on: ubuntu-latest
     steps:
       - name: Checkout sources
diff --git a/Cargo.lock b/Cargo.lock
index f769b061..3aca9dc0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -830,7 +830,6 @@ dependencies = [
  "ethers-core",
  "ethers-providers",
  "ethers-signers",
- "futures-executor",
  "futures-util",
  "hex",
  "rand 0.8.4",
@@ -889,7 +888,6 @@ dependencies = [
  "futures-util",
  "hex",
  "rand 0.8.4",
- "serde_json",
  "sha2 0.9.5",
  "tempfile",
  "thiserror",
@@ -1002,7 +1000,6 @@ dependencies = [
  "futures-core",
  "futures-task",
  "futures-util",
- "num_cpus",
 ]
 
 [[package]]
diff --git a/ethers-core/Cargo.toml b/ethers-core/Cargo.toml
index 102bcfe9..26151cc7 100644
--- a/ethers-core/Cargo.toml
+++ b/ethers-core/Cargo.toml
@@ -45,6 +45,7 @@ serde_json = { version = "1.0.64", default-features = false }
 bincode = { version = "1.3.3", default-features = false }
 once_cell = { version = "1.8.0" }
 hex-literal = "0.3.3"
+futures-util = { version = "0.3.16", default-features = false }
 
 [features]
 celo = [] # celo support extends the transaction format with extra fields
diff --git a/ethers-core/src/types/transaction.rs b/ethers-core/src/types/transaction.rs
index fc3bbc21..d32f56a6 100644
--- a/ethers-core/src/types/transaction.rs
+++ b/ethers-core/src/types/transaction.rs
@@ -7,15 +7,14 @@ use crate::{
 use rlp::RlpStream;
 use serde::{Deserialize, Serialize};
 
+const BASE_NUM_TX_FIELDS: usize = 9;
+
 // Number of tx fields before signing
 #[cfg(not(feature = "celo"))]
-const UNSIGNED_TX_FIELDS: usize = 6;
+const NUM_TX_FIELDS: usize = BASE_NUM_TX_FIELDS;
 // Celo has 3 additional fields
 #[cfg(feature = "celo")]
-const UNSIGNED_TX_FIELDS: usize = 9;
-
-// Unsigned fields + signature [r s v]
-const SIGNED_TX_FIELDS: usize = UNSIGNED_TX_FIELDS + 3;
+const NUM_TX_FIELDS: usize = BASE_NUM_TX_FIELDS + 3;
 
 /// Parameters for sending a transaction
 #[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Debug)]
@@ -130,52 +129,34 @@ impl TransactionRequest {
     }
 
     /// Hashes the transaction's data with the provided chain id
-    pub fn sighash<T: Into<U64>>(&self, chain_id: Option<T>) -> H256 {
+    pub fn sighash<T: Into<U64>>(&self, chain_id: T) -> H256 {
         keccak256(self.rlp(chain_id).as_ref()).into()
     }
 
     /// Gets the unsigned transaction's RLP encoding
-    pub fn rlp<T: Into<U64>>(&self, chain_id: Option<T>) -> Bytes {
+    pub fn rlp<T: Into<U64>>(&self, chain_id: T) -> Bytes {
         let mut rlp = RlpStream::new();
-        // "If [..] CHAIN_ID is available, then when  computing the hash of a
-        // transaction for the purposes of signing, instead of hashing only
-        // six rlp encoded elements (nonce, gasprice, startgas, to, value, data),
-        // you SHOULD hash nine rlp encoded elements
-        // (nonce, gasprice, startgas, to, value, data, chainid, 0, 0)"
-        // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md#specification
-        let num_els = if chain_id.is_some() {
-            UNSIGNED_TX_FIELDS + 3
-        } else {
-            UNSIGNED_TX_FIELDS
-        };
-
-        rlp.begin_list(num_els);
+        rlp.begin_list(NUM_TX_FIELDS);
         self.rlp_base(&mut rlp);
 
         // Only hash the 3 extra fields when preparing the
         // data to sign if chain_id is present
-        if let Some(chain_id) = chain_id {
-            rlp.append(&chain_id.into());
-            rlp.append(&0u8);
-            rlp.append(&0u8);
-        }
-
+        rlp.append(&chain_id.into());
+        rlp.append(&0u8);
+        rlp.append(&0u8);
         rlp.out().freeze().into()
     }
 
     /// Produces the RLP encoding of the transaction with the provided signature
     pub fn rlp_signed(&self, signature: &Signature) -> Bytes {
         let mut rlp = RlpStream::new();
-
-        // construct the RLP body
-        rlp.begin_list(SIGNED_TX_FIELDS);
+        rlp.begin_list(NUM_TX_FIELDS);
         self.rlp_base(&mut rlp);
 
         // append the signature
         rlp.append(&signature.v);
         rlp.append(&signature.r);
         rlp.append(&signature.s);
-
         rlp.out().freeze().into()
     }
 
@@ -326,7 +307,7 @@ impl Transaction {
 
     pub fn rlp(&self) -> Bytes {
         let mut rlp = RlpStream::new();
-        rlp.begin_list(SIGNED_TX_FIELDS);
+        rlp.begin_list(NUM_TX_FIELDS);
         rlp.append(&self.nonce);
         rlp.append(&self.gas_price);
         rlp.append(&self.gas);
diff --git a/ethers-middleware/Cargo.toml b/ethers-middleware/Cargo.toml
index ee50a813..35e1d755 100644
--- a/ethers-middleware/Cargo.toml
+++ b/ethers-middleware/Cargo.toml
@@ -36,7 +36,6 @@ tokio = { version = "1.5" }
 
 [dev-dependencies]
 ethers = { version = "0.4.0", path = "../ethers" }
-futures-executor = { version = "0.3.14", features = ["thread-pool"] }
 hex = { version = "0.4.3", default-features = false, features = ["std"] }
 rand = { version = "0.8.4", default-features = false }
 tokio = { version = "1.5", default-features = false, features = ["rt", "macros", "time"] }
diff --git a/ethers-middleware/src/lib.rs b/ethers-middleware/src/lib.rs
index 9b7fe7d7..c779a042 100644
--- a/ethers-middleware/src/lib.rs
+++ b/ethers-middleware/src/lib.rs
@@ -21,7 +21,7 @@
 //! ```no_run
 //! use ethers::{
 //!     providers::{Provider, Http},
-//!     signers::LocalWallet,
+//!     signers::{LocalWallet, Signer},
 //!     middleware::{
 //!         gas_escalator::{GasEscalatorMiddleware, GeometricGasPrice, Frequency},
 //!         gas_oracle::{GasOracleMiddleware, GasNow, GasCategory},
diff --git a/ethers-middleware/src/signer.rs b/ethers-middleware/src/signer.rs
index acda5427..7bd5d272 100644
--- a/ethers-middleware/src/signer.rs
+++ b/ethers-middleware/src/signer.rs
@@ -343,7 +343,7 @@ mod tests {
         let key = "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318"
             .parse::<LocalWallet>()
             .unwrap()
-            .set_chain_id(chain_id);
+            .with_chain_id(chain_id);
         let client = SignerMiddleware::new(provider, key);
 
         let tx = client.sign_transaction(tx).await.unwrap();
@@ -365,7 +365,7 @@ mod tests {
 
         // new SignerMiddleware
         let provider = Provider::try_from("http://localhost:8545").unwrap();
-        let key = LocalWallet::new(&mut rand::thread_rng());
+        let key = LocalWallet::new(&mut rand::thread_rng()).with_chain_id(1u32);
         let client = SignerMiddleware::new(provider, key);
 
         // an address that is not the signer address
diff --git a/ethers-signers/Cargo.toml b/ethers-signers/Cargo.toml
index ca83be81..9c67c88e 100644
--- a/ethers-signers/Cargo.toml
+++ b/ethers-signers/Cargo.toml
@@ -35,7 +35,6 @@ yubihsm = { version = "0.39.0", features = ["secp256k1", "usb", "mockhsm"] }
 
 tempfile = "3.2.0"
 tokio = { version = "1.5", default-features = false, features = ["macros"] }
-serde_json = { version = "1.0.64", default-features = false }
 
 [features]
 celo = ["ethers-core/celo"]
diff --git a/ethers-signers/src/ledger/app.rs b/ethers-signers/src/ledger/app.rs
index f27f1397..5d44b133 100644
--- a/ethers-signers/src/ledger/app.rs
+++ b/ethers-signers/src/ledger/app.rs
@@ -24,10 +24,8 @@ use super::types::*;
 pub struct LedgerEthereum {
     transport: Mutex<Ledger>,
     derivation: DerivationType,
-    pub chain_id: Option<u64>,
-
-    /// The ledger's address, instantiated at runtime
-    pub address: Address,
+    pub(crate) chain_id: u64,
+    pub(crate) address: Address,
 }
 
 impl LedgerEthereum {
@@ -38,14 +36,11 @@ impl LedgerEthereum {
     /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
     /// use ethers::signers::{Ledger, HDPath};
     ///
-    /// let ledger = Ledger::new(HDPath::LedgerLive(0), Some(1)).await?;
+    /// let ledger = Ledger::new(HDPath::LedgerLive(0), 1).await?;
     /// # Ok(())
     /// # }
     /// ```
-    pub async fn new(
-        derivation: DerivationType,
-        chain_id: Option<u64>,
-    ) -> Result<Self, LedgerError> {
+    pub async fn new(derivation: DerivationType, chain_id: u64) -> Result<Self, LedgerError> {
         let transport = Ledger::init().await?;
         let address = Self::get_address_with_path_transport(&transport, &derivation).await?;
 
@@ -123,13 +118,9 @@ impl LedgerEthereum {
     }
 
     /// Signs an Ethereum transaction (requires confirmation on the ledger)
-    pub async fn sign_tx(
-        &self,
-        tx: &TransactionRequest,
-        chain_id: Option<u64>,
-    ) -> Result<Signature, LedgerError> {
+    pub async fn sign_tx(&self, tx: &TransactionRequest) -> Result<Signature, LedgerError> {
         let mut payload = Self::path_to_bytes(&self.derivation);
-        payload.extend_from_slice(tx.rlp(chain_id).as_ref());
+        payload.extend_from_slice(tx.rlp(self.chain_id).as_ref());
         self.sign_payload(INS::SIGN, payload).await
     }
 
@@ -216,7 +207,7 @@ mod tests {
     // Replace this with your ETH addresses.
     async fn test_get_address() {
         // Instantiate it with the default ledger derivation path
-        let ledger = LedgerEthereum::new(DerivationType::LedgerLive(0), None)
+        let ledger = LedgerEthereum::new(DerivationType::LedgerLive(0), 1)
             .await
             .unwrap();
         assert_eq!(
@@ -235,7 +226,7 @@ mod tests {
     #[tokio::test]
     #[ignore]
     async fn test_sign_tx() {
-        let ledger = LedgerEthereum::new(DerivationType::LedgerLive(0), None)
+        let ledger = LedgerEthereum::new(DerivationType::LedgerLive(0), 1)
             .await
             .unwrap();
 
@@ -257,7 +248,7 @@ mod tests {
     #[tokio::test]
     #[ignore]
     async fn test_version() {
-        let ledger = LedgerEthereum::new(DerivationType::LedgerLive(0), None)
+        let ledger = LedgerEthereum::new(DerivationType::LedgerLive(0), 1)
             .await
             .unwrap();
 
@@ -268,7 +259,7 @@ mod tests {
     #[tokio::test]
     #[ignore]
     async fn test_sign_message() {
-        let ledger = LedgerEthereum::new(DerivationType::Legacy(0), None)
+        let ledger = LedgerEthereum::new(DerivationType::Legacy(0), 1)
             .await
             .unwrap();
         let message = "hello world";
diff --git a/ethers-signers/src/ledger/mod.rs b/ethers-signers/src/ledger/mod.rs
index 7b12018e..b22466e2 100644
--- a/ethers-signers/src/ledger/mod.rs
+++ b/ethers-signers/src/ledger/mod.rs
@@ -24,11 +24,20 @@ impl Signer for LedgerEthereum {
         &self,
         message: &TransactionRequest,
     ) -> Result<Signature, Self::Error> {
-        self.sign_tx(message, self.chain_id).await
+        self.sign_tx(message).await
     }
 
     /// Returns the signer's Ethereum Address
     fn address(&self) -> Address {
         self.address
     }
+
+    fn with_chain_id<T: Into<u64>>(mut self, chain_id: T) -> Self {
+        self.chain_id = chain_id.into();
+        self
+    }
+
+    fn chain_id(&self) -> u64 {
+        self.chain_id
+    }
 }
diff --git a/ethers-signers/src/lib.rs b/ethers-signers/src/lib.rs
index dd62fa41..c54c4f38 100644
--- a/ethers-signers/src/lib.rs
+++ b/ethers-signers/src/lib.rs
@@ -67,15 +67,8 @@ use ethers_core::types::{Address, Signature, TransactionRequest};
 use std::error::Error;
 
 /// Applies [EIP155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md)
-pub fn to_eip155_v<T: Into<u8>>(recovery_id: T, chain_id: Option<u64>) -> u64 {
-    let standard_v: u8 = recovery_id.into();
-    if let Some(chain_id) = chain_id {
-        // When signing with a chain ID, add chain replay protection.
-        (standard_v as u64) + 35 + chain_id * 2
-    } else {
-        // Otherwise, convert to 'Electrum' notation.
-        (standard_v as u64) + 27
-    }
+pub fn to_eip155_v<T: Into<u8>>(recovery_id: T, chain_id: u64) -> u64 {
+    (recovery_id.into() as u64) + 35 + chain_id * 2
 }
 
 /// Trait for signing transactions and messages
@@ -98,4 +91,10 @@ pub trait Signer: std::fmt::Debug + Send + Sync {
 
     /// Returns the signer's Ethereum Address
     fn address(&self) -> Address;
+
+    /// Returns the signer's chain id
+    fn chain_id(&self) -> u64;
+
+    /// Sets the signer's chain id
+    fn with_chain_id<T: Into<u64>>(self, chain_id: T) -> Self;
 }
diff --git a/ethers-signers/src/wallet/mnemonic.rs b/ethers-signers/src/wallet/mnemonic.rs
index aa2ebffd..f4d5a11b 100644
--- a/ethers-signers/src/wallet/mnemonic.rs
+++ b/ethers-signers/src/wallet/mnemonic.rs
@@ -183,7 +183,7 @@ impl<W: Wordlist> MnemonicBuilder<W> {
         Ok(Wallet::<SigningKey> {
             signer,
             address,
-            chain_id: None,
+            chain_id: 1,
         })
     }
 }
diff --git a/ethers-signers/src/wallet/mod.rs b/ethers-signers/src/wallet/mod.rs
index 58211485..ab864fef 100644
--- a/ethers-signers/src/wallet/mod.rs
+++ b/ethers-signers/src/wallet/mod.rs
@@ -43,7 +43,7 @@ use std::fmt;
 ///
 /// // Optionally, the wallet's chain id can be set, in order to use EIP-155
 /// // replay protection with different chains
-/// let wallet = wallet.set_chain_id(1337u64);
+/// let wallet = wallet.with_chain_id(1337u64);
 ///
 /// // The wallet can be used to sign messages
 /// let message = b"hello";
@@ -60,8 +60,8 @@ pub struct Wallet<D: DigestSigner<Sha256Proxy, RecoverableSignature>> {
     pub(crate) signer: D,
     /// The wallet's address
     pub(crate) address: Address,
-    /// The wallet's chain id (for EIP-155), signs w/o replay protection if left unset
-    pub(crate) chain_id: Option<u64>,
+    /// The wallet's chain id (for EIP-155)
+    pub(crate) chain_id: u64,
 }
 
 #[async_trait]
@@ -75,25 +75,44 @@ impl<D: Sync + Send + DigestSigner<Sha256Proxy, RecoverableSignature>> Signer fo
         let message = message.as_ref();
         let message_hash = hash_message(message);
 
-        Ok(self.sign_hash_with_eip155(message_hash, None))
+        Ok(self.sign_hash(message_hash, false))
     }
 
     async fn sign_transaction(&self, tx: &TransactionRequest) -> Result<Signature, Self::Error> {
         let sighash = tx.sighash(self.chain_id);
-        Ok(self.sign_hash_with_eip155(sighash, self.chain_id))
+        Ok(self.sign_hash(sighash, true))
     }
 
     fn address(&self) -> Address {
         self.address
     }
+
+    /// Gets the wallet's chain id
+    ///
+    /// # Panics
+    ///
+    /// If the chain id has not already been set.
+    fn chain_id(&self) -> u64 {
+        self.chain_id
+    }
+
+    /// Sets the wallet's chain_id, used in conjunction with EIP-155 signing
+    fn with_chain_id<T: Into<u64>>(mut self, chain_id: T) -> Self {
+        self.chain_id = chain_id.into();
+        self
+    }
 }
 
 impl<D: DigestSigner<Sha256Proxy, RecoverableSignature>> Wallet<D> {
-    fn sign_hash_with_eip155(&self, hash: H256, chain_id: Option<u64>) -> Signature {
+    fn sign_hash(&self, hash: H256, eip155: bool) -> Signature {
         let recoverable_sig: RecoverableSignature =
             self.signer.sign_digest(Sha256Proxy::from(hash));
 
-        let v = to_eip155_v(recoverable_sig.recovery_id(), chain_id);
+        let v = if eip155 {
+            to_eip155_v(recoverable_sig.recovery_id(), self.chain_id)
+        } else {
+            u8::from(recoverable_sig.recovery_id()) as u64 + 27
+        };
 
         let r_bytes: FieldBytes<Secp256k1> = recoverable_sig.r().into();
         let s_bytes: FieldBytes<Secp256k1> = recoverable_sig.s().into();
@@ -103,26 +122,10 @@ impl<D: DigestSigner<Sha256Proxy, RecoverableSignature>> Wallet<D> {
         Signature { r, s, v }
     }
 
-    /// Sets the wallet's chain_id, used in conjunction with EIP-155 signing
-    pub fn set_chain_id<T: Into<u64>>(mut self, chain_id: T) -> Self {
-        self.chain_id = Some(chain_id.into());
-        self
-    }
-
     /// Gets the wallet's signer
     pub fn signer(&self) -> &D {
         &self.signer
     }
-
-    /// Gets the wallet's chain id
-    pub fn chain_id(&self) -> Option<u64> {
-        self.chain_id
-    }
-
-    /// Returns the wallet's address
-    pub fn address(&self) -> Address {
-        self.address
-    }
 }
 
 // do not log the signer
diff --git a/ethers-signers/src/wallet/private_key.rs b/ethers-signers/src/wallet/private_key.rs
index 23f202a2..cf2289a8 100644
--- a/ethers-signers/src/wallet/private_key.rs
+++ b/ethers-signers/src/wallet/private_key.rs
@@ -66,7 +66,7 @@ impl Wallet<SigningKey> {
         Ok(Self {
             signer,
             address,
-            chain_id: None,
+            chain_id: 1,
         })
     }
 
@@ -82,7 +82,7 @@ impl Wallet<SigningKey> {
         Ok(Self {
             signer,
             address,
-            chain_id: None,
+            chain_id: 1,
         })
     }
 
@@ -93,7 +93,7 @@ impl Wallet<SigningKey> {
         Self {
             signer,
             address,
-            chain_id: None,
+            chain_id: 1,
         }
     }
 }
@@ -113,7 +113,7 @@ impl From<SigningKey> for Wallet<SigningKey> {
         Self {
             signer,
             address,
-            chain_id: None,
+            chain_id: 1,
         }
     }
 }
@@ -129,7 +129,7 @@ impl From<K256SecretKey> for Wallet<SigningKey> {
         Self {
             signer,
             address,
-            chain_id: None,
+            chain_id: 1,
         }
     }
 }
@@ -224,10 +224,10 @@ mod tests {
             "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318"
                 .parse()
                 .unwrap();
-        let wallet = wallet.set_chain_id(chain_id);
+        let wallet = wallet.with_chain_id(chain_id);
 
         let sig = wallet.sign_transaction(&tx).await.unwrap();
-        let sighash = tx.sighash(Some(chain_id));
+        let sighash = tx.sighash(chain_id);
         assert!(sig.verify(sighash, wallet.address).is_ok());
     }
 
diff --git a/ethers-signers/src/wallet/yubi.rs b/ethers-signers/src/wallet/yubi.rs
index 8c78f838..b7df0c9e 100644
--- a/ethers-signers/src/wallet/yubi.rs
+++ b/ethers-signers/src/wallet/yubi.rs
@@ -58,7 +58,7 @@ impl From<YubiSigner<Secp256k1>> for Wallet<YubiSigner<Secp256k1>> {
         Self {
             signer,
             address,
-            chain_id: None,
+            chain_id: 1,
         }
     }
 }
diff --git a/ethers/examples/ledger.rs b/ethers/examples/ledger.rs
index cefafdd7..7dcd4226 100644
--- a/ethers/examples/ledger.rs
+++ b/ethers/examples/ledger.rs
@@ -9,7 +9,7 @@ async fn main() -> anyhow::Result<()> {
     // the wallet's index. Alternatively, you may use Legacy with the wallet's
     // index or supply the  full HD path string. You may also provide the chain_id
     // (here: mainnet) for EIP155 support.
-    let ledger = Ledger::new(HDPath::LedgerLive(0), Some(1)).await?;
+    let ledger = Ledger::new(HDPath::LedgerLive(0), 1).await?;
     let client = SignerMiddleware::new(provider, ledger);
 
     // Create and broadcast a transaction (ENS enabled!)


Test patch:
diff --git a/ethers-contract/tests/common/mod.rs b/ethers-contract/tests/common/mod.rs
index ea1ffca9..8404d038 100644
--- a/ethers-contract/tests/common/mod.rs
+++ b/ethers-contract/tests/common/mod.rs
@@ -8,7 +8,7 @@ use ethers_contract::{Contract, ContractFactory, EthEvent};
 use ethers_core::utils::{GanacheInstance, Solc};
 use ethers_middleware::signer::SignerMiddleware;
 use ethers_providers::{Http, Middleware, Provider};
-use ethers_signers::LocalWallet;
+use ethers_signers::{LocalWallet, Signer};
 use std::{convert::TryFrom, sync::Arc, time::Duration};
 
 // Note: The `EthEvent` derive macro implements the necessary conversion between `Tokens` and
@@ -40,6 +40,7 @@ pub fn connect(ganache: &GanacheInstance, idx: usize) -> Arc<HttpWallet> {
         .unwrap()
         .interval(Duration::from_millis(10u64));
     let wallet: LocalWallet = ganache.keys()[idx].clone().into();
+    let wallet = wallet.with_chain_id(1u64);
     Arc::new(SignerMiddleware::new(provider, wallet))
 }
 
diff --git a/ethers-contract/tests/contract.rs b/ethers-contract/tests/contract.rs
index 059d0281..baa335fd 100644
--- a/ethers-contract/tests/contract.rs
+++ b/ethers-contract/tests/contract.rs
@@ -97,12 +97,11 @@ mod eth_tests {
         let contract = deploy(client.clone(), abi, bytecode).await;
 
         // make a call with `client`
-        let _tx_hash = *contract
+        let func = contract
             .method::<_, H256>("setValue", "hi".to_owned())
-            .unwrap()
-            .send()
-            .await
             .unwrap();
+        let tx = func.send().await.unwrap();
+        let _receipt = tx.await.unwrap();
 
         // and we can fetch the events
         let logs: Vec<ValueChanged> = contract
@@ -504,8 +503,8 @@ mod celo_tests {
     use super::*;
     use ethers::{
         middleware::signer::SignerMiddleware,
-        providers::{Http, Provider},
-        signers::LocalWallet,
+        providers::{Http, Middleware, Provider},
+        signers::{LocalWallet, Signer},
         types::BlockNumber,
     };
     use std::{convert::TryFrom, sync::Arc, time::Duration};
@@ -518,12 +517,13 @@ mod celo_tests {
         let provider = Provider::<Http>::try_from("https://alfajores-forno.celo-testnet.org")
             .unwrap()
             .interval(Duration::from_millis(6000));
+        let chain_id = provider.get_chainid().await.unwrap().as_u64();
 
         // Funded with https://celo.org/developers/faucet
         let wallet = "d652abb81e8c686edba621a895531b1f291289b63b5ef09a94f686a5ecdd5db1"
             .parse::<LocalWallet>()
             .unwrap()
-            .set_chain_id(44787u64);
+            .with_chain_id(chain_id);
 
         let client = SignerMiddleware::new(provider, wallet);
         let client = Arc::new(client);
diff --git a/ethers-middleware/tests/gas_escalator.rs b/ethers-middleware/tests/gas_escalator.rs
index 2ce00d04..ccf56256 100644
--- a/ethers-middleware/tests/gas_escalator.rs
+++ b/ethers-middleware/tests/gas_escalator.rs
@@ -4,7 +4,7 @@ use ethers_middleware::{
     signer::SignerMiddleware,
 };
 use ethers_providers::{Middleware, Provider, Ws};
-use ethers_signers::LocalWallet;
+use ethers_signers::{LocalWallet, Signer};
 use std::time::Duration;
 
 #[tokio::test]
diff --git a/ethers-middleware/tests/nonce_manager.rs b/ethers-middleware/tests/nonce_manager.rs
index 6348590f..a43962cd 100644
--- a/ethers-middleware/tests/nonce_manager.rs
+++ b/ethers-middleware/tests/nonce_manager.rs
@@ -4,7 +4,7 @@ async fn nonce_manager() {
     use ethers_core::types::*;
     use ethers_middleware::{nonce_manager::NonceManagerMiddleware, signer::SignerMiddleware};
     use ethers_providers::{Http, Middleware, Provider};
-    use ethers_signers::LocalWallet;
+    use ethers_signers::{LocalWallet, Signer};
     use std::convert::TryFrom;
     use std::time::Duration;
 
@@ -12,10 +12,12 @@ async fn nonce_manager() {
         Provider::<Http>::try_from("https://rinkeby.infura.io/v3/fd8b88b56aa84f6da87b60f5441d6778")
             .unwrap()
             .interval(Duration::from_millis(2000u64));
+    let chain_id = provider.get_chainid().await.unwrap().as_u64();
 
     let wallet = "59c37cb6b16fa2de30675f034c8008f890f4b2696c729d6267946d29736d73e4"
         .parse::<LocalWallet>()
-        .unwrap();
+        .unwrap()
+        .with_chain_id(chain_id);
     let address = wallet.address();
 
     let provider = SignerMiddleware::new(provider, wallet);
diff --git a/ethers-middleware/tests/signer.rs b/ethers-middleware/tests/signer.rs
index 0c7419f7..75c534c4 100644
--- a/ethers-middleware/tests/signer.rs
+++ b/ethers-middleware/tests/signer.rs
@@ -2,7 +2,7 @@ use ethers_providers::{Http, Middleware, Provider};
 
 use ethers_core::types::TransactionRequest;
 use ethers_middleware::signer::SignerMiddleware;
-use ethers_signers::LocalWallet;
+use ethers_signers::{LocalWallet, Signer};
 use std::{convert::TryFrom, time::Duration};
 
 #[tokio::test]
@@ -20,6 +20,8 @@ async fn send_eth() {
     let provider = Provider::<Http>::try_from(ganache.endpoint())
         .unwrap()
         .interval(Duration::from_millis(10u64));
+    let chain_id = provider.get_chainid().await.unwrap().as_u64();
+    let wallet = wallet.with_chain_id(chain_id);
     let provider = SignerMiddleware::new(provider, wallet);
 
     // craft the transaction
@@ -48,13 +50,14 @@ async fn test_send_transaction() {
     let provider = Provider::<Http>::try_from("https://alfajores-forno.celo-testnet.org")
         .unwrap()
         .interval(Duration::from_millis(3000u64));
+    let chain_id = provider.get_chainid().await.unwrap().as_u64();
 
     // Funded with https://celo.org/developers/faucet
     // Please do not drain this account :)
     let wallet = "d652abb81e8c686edba621a895531b1f291289b63b5ef09a94f686a5ecdd5db1"
         .parse::<LocalWallet>()
         .unwrap()
-        .set_chain_id(44787u64);
+        .with_chain_id(chain_id);
     let client = SignerMiddleware::new(provider, wallet);
 
     let balance_before = client.get_balance(client.address(), None).await.unwrap();
@@ -71,6 +74,7 @@ async fn test_send_transaction() {
 }
 
 #[tokio::test]
+#[cfg(not(feature = "celo"))]
 async fn send_transaction_handles_tx_from_field() {
     use ethers_core::utils::Ganache;
 
diff --git a/ethers-middleware/tests/stack.rs b/ethers-middleware/tests/stack.rs
index abd1a589..fd1ed98c 100644
--- a/ethers-middleware/tests/stack.rs
+++ b/ethers-middleware/tests/stack.rs
@@ -8,7 +8,7 @@ mod tests {
         signer::SignerMiddleware,
     };
     use ethers_providers::{Http, Middleware, Provider};
-    use ethers_signers::LocalWallet;
+    use ethers_signers::{LocalWallet, Signer};
     use std::convert::TryFrom;
 
     #[tokio::test]
@@ -58,6 +58,8 @@ mod tests {
 
         // the base provider
         let provider = Arc::new(Provider::<Http>::try_from(ganache.endpoint()).unwrap());
+        let chain_id = provider.get_chainid().await.unwrap().as_u64();
+        let signer = signer.with_chain_id(chain_id);
 
         // the Gas Price escalator middleware is the first middleware above the provider,
         // so that it receives the transaction last, after all the other middleware
diff --git a/ethers-middleware/tests/transformer.rs b/ethers-middleware/tests/transformer.rs
index 14de1663..630447e5 100644
--- a/ethers-middleware/tests/transformer.rs
+++ b/ethers-middleware/tests/transformer.rs
@@ -8,7 +8,7 @@ use ethers_middleware::{
     SignerMiddleware,
 };
 use ethers_providers::{Http, Middleware, Provider};
-use ethers_signers::LocalWallet;
+use ethers_signers::{LocalWallet, Signer};
 use rand::Rng;
 use std::{convert::TryFrom, sync::Arc, time::Duration};
 
@@ -26,6 +26,8 @@ async fn ds_proxy_transformer() {
     let provider = Provider::<Http>::try_from(ganache.endpoint())
         .unwrap()
         .interval(Duration::from_millis(10u64));
+    let chain_id = provider.get_chainid().await.unwrap().as_u64();
+    let wallet = wallet.with_chain_id(chain_id);
     let signer_middleware = SignerMiddleware::new(provider.clone(), wallet);
     let wallet_addr = signer_middleware.address();
     let provider = Arc::new(signer_middleware.clone());
@@ -111,6 +113,8 @@ async fn ds_proxy_code() {
     let provider = Provider::<Http>::try_from(ganache.endpoint())
         .unwrap()
         .interval(Duration::from_millis(10u64));
+    let chain_id = provider.get_chainid().await.unwrap().as_u64();
+    let wallet = wallet.with_chain_id(chain_id);
     let signer_middleware = SignerMiddleware::new(provider.clone(), wallet);
     let wallet_addr = signer_middleware.address();
     let provider = Arc::new(signer_middleware.clone());
diff --git a/ethers-providers/tests/provider.rs b/ethers-providers/tests/provider.rs
index e37dcaca..08a586e0 100644
--- a/ethers-providers/tests/provider.rs
+++ b/ethers-providers/tests/provider.rs
@@ -6,7 +6,7 @@ mod eth_tests {
     use super::*;
     use ethers::{
         middleware::SignerMiddleware,
-        signers::LocalWallet,
+        signers::{LocalWallet, Signer},
         types::{BlockId, TransactionRequest, H256},
         utils::Ganache,
     };
@@ -86,9 +86,11 @@ mod eth_tests {
             "https://rinkeby.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27",
         )
         .unwrap();
+        let chain_id = provider.get_chainid().await.unwrap();
         let wallet = "59c37cb6b16fa2de30675f034c8008f890f4b2696c729d6267946d29736d73e4"
             .parse::<LocalWallet>()
-            .unwrap();
+            .unwrap()
+            .with_chain_id(chain_id.as_u64());
         let address = wallet.address();
         let provider = SignerMiddleware::new(provider, wallet);
         generic_pending_txs_test(provider, address).await;
@@ -102,9 +104,11 @@ mod eth_tests {
             Provider::connect("wss://rinkeby.infura.io/ws/v3/c60b0bb42f8a4c6481ecd229eddaca27")
                 .await
                 .unwrap();
+        let chain_id = provider.get_chainid().await.unwrap();
         let wallet = "ff7f80c6e9941865266ed1f481263d780169f1d98269c51167d20c630a5fdc8a"
             .parse::<LocalWallet>()
-            .unwrap();
+            .unwrap()
+            .with_chain_id(chain_id.as_64());
         let address = wallet.address();
         let provider = SignerMiddleware::new(provider, wallet);
         generic_pending_txs_test(provider, address).await;