Repository: gakonst/ethers-rs
Pull request #1641 (2022-08-29 18:35:31)

feat: add support for Optimism Goerli

## Motivation

This adds support for Optimism Goerli now that Optimism Kovan will be deprecated.

## Solution

Add OptimismGoerli object and respective chain id, 420

## PR Checklist

- [ ] Added Tests
- [ ] Added Documentation
- [ ] Updated the changelog



Patch:
diff --git a/ethers-contract/src/multicall/mod.rs b/ethers-contract/src/multicall/mod.rs
index de30403d..15e77c2c 100644
--- a/ethers-contract/src/multicall/mod.rs
+++ b/ethers-contract/src/multicall/mod.rs
@@ -39,8 +39,8 @@ pub static MULTICALL_SUPPORTED_CHAIN_IDS: Lazy<[U256; 47]> = Lazy::new(|| {
         U256::from(Ropsten),                  // Ropsten
         U256::from(Sepolia),                  // Sepolia
         U256::from(Optimism),                 // Optimism
+        U256::from(OptimismGoerli),           // OptimismGoerli
         U256::from(OptimismKovan),            // OptimismKovan
-        U256::from(420),                      // OptimismGoerli
         U256::from(Arbitrum),                 // Arbitrum
         U256::from(421613),                   // ArbitrumGoerli,
         U256::from(ArbitrumTestnet),          // Arbitrum Rinkeby
diff --git a/ethers-core/src/types/chain.rs b/ethers-core/src/types/chain.rs
index c082f0a6..88ff947b 100644
--- a/ethers-core/src/types/chain.rs
+++ b/ethers-core/src/types/chain.rs
@@ -46,6 +46,7 @@ pub enum Chain {
     MoonbeamDev = 1281,
     Moonriver = 1285,
     Optimism = 10,
+    OptimismGoerli = 420,
     OptimismKovan = 69,
     Arbitrum = 42161,
     ArbitrumTestnet = 421611,
@@ -104,6 +105,7 @@ impl Chain {
             Chain::Sepolia |
             Chain::Moonbase |
             Chain::MoonbeamDev |
+            Chain::OptimismGoerli |
             Chain::OptimismKovan |
             Chain::Poa |
             Chain::Sokol |
@@ -142,6 +144,10 @@ impl Chain {
             Chain::Optimism => {
                 ("https://api-optimistic.etherscan.io/api", "https://optimistic.etherscan.io")
             }
+            Chain::OptimismGoerli => (
+                "https://api-goerli-optimistic.etherscan.io/api",
+                "https://goerli-optimism.etherscan.io",
+            ),
             Chain::OptimismKovan => (
                 "https://api-kovan-optimistic.etherscan.io/api",
                 "https://kovan-optimistic.etherscan.io",
@@ -229,6 +235,7 @@ impl fmt::Display for Chain {
             Chain::MoonbeamDev => "moonbeam-dev",
             Chain::Moonriver => "moonriver",
             Chain::Optimism => "optimism",
+            Chain::OptimismGoerli => "optimism-goerli",
             Chain::OptimismKovan => "optimism-kovan",
             Chain::Fantom => "fantom",
             Chain::Dev => "dev",
@@ -300,6 +307,7 @@ impl TryFrom<u64> for Chain {
             1281 => Chain::MoonbeamDev,
             1285 => Chain::Moonriver,
             10 => Chain::Optimism,
+            420 => Chain::OptimismGoerli,
             69 => Chain::OptimismKovan,
             56 => Chain::BinanceSmartChain,
             97 => Chain::BinanceSmartChainTestnet,
@@ -354,6 +362,7 @@ impl FromStr for Chain {
             "moonbeam-dev" => Chain::MoonbeamDev,
             "moonriver" => Chain::Moonriver,
             "optimism" => Chain::Optimism,
+            "optimism-goerli" => Chain::OptimismGoerli,
             "optimism-kovan" => Chain::OptimismKovan,
             "fantom" => Chain::Fantom,
             "fantom-testnet" => Chain::FantomTestnet,
@@ -386,6 +395,7 @@ impl Chain {
         matches!(
             self,
             Chain::Optimism |
+                Chain::OptimismGoerli |
                 Chain::OptimismKovan |
                 Chain::Fantom |
                 Chain::FantomTestnet |
diff --git a/ethers-core/src/utils/anvil.rs b/ethers-core/src/utils/anvil.rs
index ac941b5b..05cfabed 100644
--- a/ethers-core/src/utils/anvil.rs
+++ b/ethers-core/src/utils/anvil.rs
@@ -1,5 +1,5 @@
 use crate::{
-    types::Address,
+    types::{Address, Chain},
     utils::{secret_key_to_address, unused_port},
 };
 use k256::{ecdsa::SigningKey, SecretKey as K256SecretKey};
@@ -21,6 +21,7 @@ pub struct AnvilInstance {
     private_keys: Vec<K256SecretKey>,
     addresses: Vec<Address>,
     port: u16,
+    chain_id: Option<u64>,
 }
 
 impl AnvilInstance {
@@ -39,6 +40,11 @@ impl AnvilInstance {
         self.port
     }
 
+    /// Returns the chain of the anvil instance
+    pub fn chain_id(&self) -> u64 {
+        self.chain_id.unwrap_or_else(|| Chain::AnvilHardhat.into())
+    }
+
     /// Returns the HTTP endpoint of this instance
     pub fn endpoint(&self) -> String {
         format!("http://localhost:{}", self.port)
@@ -82,6 +88,7 @@ pub struct Anvil {
     program: Option<PathBuf>,
     port: Option<u16>,
     block_time: Option<u64>,
+    chain_id: Option<u64>,
     mnemonic: Option<String>,
     fork: Option<String>,
     fork_block_number: Option<u64>,
@@ -139,6 +146,13 @@ impl Anvil {
         self
     }
 
+    /// Sets the chain_id the `anvil` instance will use.
+    #[must_use]
+    pub fn chain_id<T: Into<u64>>(mut self, chain_id: T) -> Self {
+        self.chain_id = Some(chain_id.into());
+        self
+    }
+
     /// Sets the mnemonic which will be used when the `anvil` instance is launched.
     #[must_use]
     pub fn mnemonic<T: Into<String>>(mut self, mnemonic: T) -> Self {
@@ -208,6 +222,10 @@ impl Anvil {
             cmd.arg("-m").arg(mnemonic);
         }
 
+        if let Some(chain_id) = self.chain_id {
+            cmd.arg("--chain-id").arg(chain_id.to_string());
+        }
+
         if let Some(block_time) = self.block_time {
             cmd.arg("-b").arg(block_time.to_string());
         }
@@ -258,7 +276,7 @@ impl Anvil {
 
         child.stdout = Some(reader.into_inner());
 
-        AnvilInstance { pid: child, private_keys, addresses, port }
+        AnvilInstance { pid: child, private_keys, addresses, port, chain_id: self.chain_id }
     }
 }
 
diff --git a/ethers-etherscan/src/lib.rs b/ethers-etherscan/src/lib.rs
index 89ec0000..5b150d5d 100644
--- a/ethers-etherscan/src/lib.rs
+++ b/ethers-etherscan/src/lib.rs
@@ -85,6 +85,7 @@ impl Client {
             Chain::Rinkeby |
             Chain::Goerli |
             Chain::Optimism |
+            Chain::OptimismGoerli |
             Chain::OptimismKovan |
             Chain::BinanceSmartChain |
             Chain::BinanceSmartChainTestnet |
diff --git a/examples/contract_human_readable.rs b/examples/contract_human_readable.rs
index 33ddc2d2..3148dc58 100644
--- a/examples/contract_human_readable.rs
+++ b/examples/contract_human_readable.rs
@@ -42,7 +42,7 @@ async fn main() -> Result<()> {
         Provider::<Http>::try_from(anvil.endpoint())?.interval(Duration::from_millis(10u64));
 
     // 4. instantiate the client with the wallet
-    let client = SignerMiddleware::new(provider, wallet);
+    let client = SignerMiddleware::new(provider, wallet.with_chain_id(anvil.chain_id()));
     let client = Arc::new(client);
 
     // 5. create a factory which will be used to deploy instances of the contract
diff --git a/examples/contract_with_abi.rs b/examples/contract_with_abi.rs
index 67219a78..35425777 100644
--- a/examples/contract_with_abi.rs
+++ b/examples/contract_with_abi.rs
@@ -31,7 +31,7 @@ async fn main() -> Result<()> {
         Provider::<Http>::try_from(anvil.endpoint())?.interval(Duration::from_millis(10u64));
 
     // 4. instantiate the client with the wallet
-    let client = SignerMiddleware::new(provider, wallet);
+    let client = SignerMiddleware::new(provider, wallet.with_chain_id(anvil.chain_id()));
     let client = Arc::new(client);
 
     // 5. create a factory which will be used to deploy instances of the contract
diff --git a/examples/contract_with_abi_and_bytecode.rs b/examples/contract_with_abi_and_bytecode.rs
index c1920b12..2748e252 100644
--- a/examples/contract_with_abi_and_bytecode.rs
+++ b/examples/contract_with_abi_and_bytecode.rs
@@ -22,7 +22,7 @@ async fn main() -> Result<()> {
         Provider::<Http>::try_from(anvil.endpoint())?.interval(Duration::from_millis(10u64));
 
     // 4. instantiate the client with the wallet
-    let client = Arc::new(SignerMiddleware::new(provider, wallet));
+    let client = Arc::new(SignerMiddleware::new(provider, wallet.with_chain_id(anvil.chain_id())));
 
     // 5. deploy contract
     let greeter_contract =
diff --git a/examples/local_signer.rs b/examples/local_signer.rs
index 64c570c3..764de4f5 100644
--- a/examples/local_signer.rs
+++ b/examples/local_signer.rs
@@ -13,7 +13,7 @@ async fn main() -> Result<()> {
     let provider = Provider::<Http>::try_from(anvil.endpoint())?;
 
     // connect the wallet to the provider
-    let client = SignerMiddleware::new(provider, wallet);
+    let client = SignerMiddleware::new(provider, wallet.with_chain_id(anvil.chain_id()));
 
     // craft the transaction
     let tx = TransactionRequest::new().to(wallet2.address()).value(10000);


Test patch:
diff --git a/ethers-contract/tests/it/abigen.rs b/ethers-contract/tests/it/abigen.rs
index 77922d1a..273498d0 100644
--- a/ethers-contract/tests/it/abigen.rs
+++ b/ethers-contract/tests/it/abigen.rs
@@ -4,7 +4,7 @@
 use ethers_contract::{abigen, Abigen, EthCall, EthEvent};
 use ethers_core::{
     abi::{AbiDecode, AbiEncode, Address, Tokenizable},
-    types::{transaction::eip2718::TypedTransaction, Eip1559TransactionRequest, U256},
+    types::{transaction::eip2718::TypedTransaction, Chain, Eip1559TransactionRequest, U256},
     utils::Anvil,
 };
 use ethers_middleware::SignerMiddleware;
@@ -608,7 +608,8 @@ async fn can_send_struct_param() {
     let server = Anvil::new().spawn();
     let wallet: LocalWallet = server.keys()[0].clone().into();
     let provider = Provider::try_from(server.endpoint()).unwrap();
-    let client = Arc::new(SignerMiddleware::new(provider, wallet.with_chain_id(1337u64)));
+    let client =
+        Arc::new(SignerMiddleware::new(provider, wallet.with_chain_id(Chain::AnvilHardhat)));
 
     let contract = StructContract::deploy(client, ()).unwrap().legacy().send().await.unwrap();
 
@@ -689,3 +690,8 @@ fn gen_complex_function() {
     ]"#,
     );
 }
+
+#[test]
+fn can_gen_large_tuple_types() {
+    abigen!(LargeTuple, "./tests/solidity-contracts/large_tuple.json");
+}
diff --git a/ethers-contract/tests/solidity-contracts/large_tuple.json b/ethers-contract/tests/solidity-contracts/large_tuple.json
new file mode 100644
index 00000000..79de5b3f
--- /dev/null
+++ b/ethers-contract/tests/solidity-contracts/large_tuple.json
@@ -0,0 +1,88 @@
+[
+  {
+    "inputs": [
+      {
+        "internalType": "uint160",
+        "name": "input",
+        "type": "uint160"
+      }
+    ],
+    "name": "doSomething",
+    "outputs": [
+      {
+        "components": [
+          {
+            "internalType": "bytes",
+            "name": "output1",
+            "type": "bytes"
+          },
+          {
+            "internalType": "bytes",
+            "name": "output2",
+            "type": "bytes"
+          },
+          {
+            "internalType": "bytes",
+            "name": "output3",
+            "type": "bytes"
+          },
+          {
+            "internalType": "bytes",
+            "name": "output4",
+            "type": "bytes"
+          },
+          {
+            "internalType": "bytes",
+            "name": "output5",
+            "type": "bytes"
+          },
+          {
+            "internalType": "bytes",
+            "name": "output6",
+            "type": "bytes"
+          },
+          {
+            "internalType": "bytes",
+            "name": "output7",
+            "type": "bytes"
+          },
+          {
+            "internalType": "bytes",
+            "name": "output8",
+            "type": "bytes"
+          },
+          {
+            "internalType": "bytes",
+            "name": "output9",
+            "type": "bytes"
+          },
+          {
+            "internalType": "bytes",
+            "name": "output10",
+            "type": "bytes"
+          },
+          {
+            "internalType": "bytes",
+            "name": "output11",
+            "type": "bytes"
+          },
+          {
+            "internalType": "bytes",
+            "name": "output12",
+            "type": "bytes"
+          },
+          {
+            "internalType": "bytes",
+            "name": "output13",
+            "type": "bytes"
+          }
+        ],
+        "internalType": "struct Contract.Response",
+        "name": "",
+        "type": "tuple"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  }
+]
\ No newline at end of file
diff --git a/ethers-middleware/tests/signer.rs b/ethers-middleware/tests/signer.rs
index 0f1e94ff..3929e7c5 100644
--- a/ethers-middleware/tests/signer.rs
+++ b/ethers-middleware/tests/signer.rs
@@ -50,6 +50,40 @@ async fn send_eth() {
     assert!(balance_before > balance_after);
 }
 
+// hardhat compatibility test, to show hardhat rejects tx signed for other chains
+#[tokio::test]
+#[cfg(not(feature = "celo"))]
+#[ignore]
+async fn send_with_chain_id_hardhat() {
+    let wallet: LocalWallet =
+        "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".parse().unwrap();
+    let provider = Provider::try_from("http://localhost:8545").unwrap();
+    let client = SignerMiddleware::new(provider, wallet);
+
+    let tx = TransactionRequest::new().to(Address::random()).value(100u64);
+    let res = client.send_transaction(tx, None).await;
+
+    let err = res.unwrap_err();
+    assert!(err
+        .to_string()
+        .contains("Trying to send an incompatible EIP-155 transaction, signed for another chain."));
+}
+
+#[tokio::test]
+#[cfg(not(feature = "celo"))]
+#[ignore]
+async fn send_with_chain_id_anvil() {
+    let wallet: LocalWallet =
+        "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".parse().unwrap();
+    let provider = Provider::try_from("http://localhost:8545").unwrap();
+    let client = SignerMiddleware::new(provider, wallet);
+
+    let tx = TransactionRequest::new().to(Address::random()).value(100u64);
+    let res = client.send_transaction(tx, None).await;
+
+    let _err = res.unwrap_err();
+}
+
 #[tokio::test]
 #[cfg(not(feature = "celo"))]
 async fn pending_txs_with_confirmations_testnet() {
diff --git a/examples/ethers-wasm/tests/contract_with_abi.rs b/examples/ethers-wasm/tests/contract_with_abi.rs
index 678f54b2..5530df67 100644
--- a/examples/ethers-wasm/tests/contract_with_abi.rs
+++ b/examples/ethers-wasm/tests/contract_with_abi.rs
@@ -1,12 +1,14 @@
 #![cfg(target_arch = "wasm32")]
 
-use wasm_bindgen_test::*;
-
-use ethers::prelude::{
-    abigen, ContractFactory, Http, JsonRpcClient, LocalWallet, Provider, SignerMiddleware, Ws,
+use ethers::{
+    prelude::{
+        abigen, ContractFactory, Http, JsonRpcClient, LocalWallet, Provider, SignerMiddleware, Ws,
+    },
+    signers::Signer,
+    types::Chain,
 };
-
 use std::{convert::TryFrom, sync::Arc};
+use wasm_bindgen_test::*;
 
 wasm_bindgen_test_configure!(run_in_browser);
 
@@ -22,14 +24,14 @@ abigen!(
 async fn http_connect_and_deploy() {
     console_log!("connecting http...");
     let provider = Provider::<Http>::try_from("http://localhost:8545").unwrap();
-    deploy(provider, ethers_wasm::utils::key(0)).await;
+    deploy(provider, ethers_wasm::utils::key(0).with_chain_id(Chain::AnvilHardhat)).await;
 }
 
 #[wasm_bindgen_test]
 async fn ws_connect_and_deploy() {
     console_log!("connecting ws...");
     let provider = Provider::new(Ws::connect("ws://localhost:8545").await.unwrap());
-    deploy(provider, ethers_wasm::utils::key(1)).await;
+    deploy(provider, ethers_wasm::utils::key(1).with_chain_id(Chain::AnvilHardhat)).await;
 }
 
 async fn deploy<T: JsonRpcClient>(provider: Provider<T>, wallet: LocalWallet) {