diff --git a/crates/ethers-contract/ethers-contract-abigen/src/contract.rs b/crates/ethers-contract/ethers-contract-abigen/src/contract.rs index 97d52289..aed7edd2 100644 --- a/crates/ethers-contract/ethers-contract-abigen/src/contract.rs +++ b/crates/ethers-contract/ethers-contract-abigen/src/contract.rs @@ -82,11 +82,11 @@ impl Context { #struct_decl - impl<'a, S: Signer, P: JsonRpcClient> #name<'a, S, P> { + impl<'a, P: JsonRpcClient, N: Network, S: Signer> #name<'a, P, N, S> { /// Creates a new contract instance with the specified `ethers` /// client at the given `Address`. The contract derefs to a `ethers::Contract` /// object - pub fn new>(address: T, client: &'a Client<'a, S, P>) -> Self { + pub fn new>(address: T, client: &'a Client<'a, P, N, S>) -> Self { let contract = Contract::new(client, &ABI, address.into()); Self(contract) } diff --git a/crates/ethers-contract/ethers-contract-abigen/src/contract/common.rs b/crates/ethers-contract/ethers-contract-abigen/src/contract/common.rs index 467e7edd..f01ef54b 100644 --- a/crates/ethers-contract/ethers-contract-abigen/src/contract/common.rs +++ b/crates/ethers-contract/ethers-contract-abigen/src/contract/common.rs @@ -13,7 +13,7 @@ pub(crate) fn imports() -> TokenStream { Contract, ContractCall, Event, Lazy, signers::{Client, Signer}, types::*, // import all the types so that we can codegen for everything - providers::JsonRpcClient, + providers::{JsonRpcClient, networks::Network}, }; } } @@ -29,17 +29,17 @@ pub(crate) fn struct_declaration(cx: &Context) -> TokenStream { // Struct declaration #[derive(Clone)] - pub struct #name<'a, S, P>(Contract<'a, S, P>); + pub struct #name<'a, P, N, S>(Contract<'a, P, N, S>); // Deref to the inner contract in order to access more specific functions functions - impl<'a, S, P> std::ops::Deref for #name<'a, S, P> { - type Target = Contract<'a, S, P>; + impl<'a, P, N, S> std::ops::Deref for #name<'a, P, N, S> { + type Target = Contract<'a, P, N, S>; fn deref(&self) -> &Self::Target { &self.0 } } - impl<'a, S: Signer, P: JsonRpcClient> std::fmt::Debug for #name<'a, S, P> { + impl<'a, P: JsonRpcClient, N: Network, S: Signer> std::fmt::Debug for #name<'a, P, N, S> { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.debug_tuple(stringify!(#name)) .field(&self.address()) diff --git a/crates/ethers-contract/ethers-contract-abigen/src/contract/events.rs b/crates/ethers-contract/ethers-contract-abigen/src/contract/events.rs index c3681f00..fe46656a 100644 --- a/crates/ethers-contract/ethers-contract-abigen/src/contract/events.rs +++ b/crates/ethers-contract/ethers-contract-abigen/src/contract/events.rs @@ -52,7 +52,7 @@ fn expand_filter(event: &Event) -> Result { Ok(quote! { #doc - pub fn #name<'b>(&'a self) -> Event<'a, 'b, P, #result> where 'a: 'b, { + pub fn #name<'b>(&'a self) -> Event<'a, 'b, P, N, #result> where 'a: 'b, { self.0.event(#ev_name).expect("event not found (this should never happen)") } }) @@ -314,7 +314,7 @@ mod tests { assert_quote!(expand_filter(&event).unwrap(), { #[doc = "Gets the contract's `Transfer` event"] - pub fn transfer<'b>(&'a self) -> Event<'a, 'b, P, Transfer> + pub fn transfer<'b>(&'a self) -> Event<'a, 'b, P, N, Transfer> where 'a: 'b, { diff --git a/crates/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/crates/ethers-contract/ethers-contract-abigen/src/contract/methods.rs index 211e867f..af1e5388 100644 --- a/crates/ethers-contract/ethers-contract-abigen/src/contract/methods.rs +++ b/crates/ethers-contract/ethers-contract-abigen/src/contract/methods.rs @@ -40,9 +40,9 @@ fn expand_function(function: &Function, alias: Option) -> Result } + quote! { ContractCall<'a, P, N, S, #outputs> } } else { - quote! { ContractCall<'a, S, P, H256> } + quote! { ContractCall<'a, P, N, S, H256> } }; let arg = expand_inputs_call_arg(&function.inputs); diff --git a/crates/ethers-contract/src/call.rs b/crates/ethers-contract/src/call.rs index 160b6ba0..9565761c 100644 --- a/crates/ethers-contract/src/call.rs +++ b/crates/ethers-contract/src/call.rs @@ -1,5 +1,5 @@ use ethers_abi::{Detokenize, Function}; -use ethers_providers::JsonRpcClient; +use ethers_providers::{networks::Network, JsonRpcClient}; use ethers_signers::{Client, Signer}; use ethers_types::{Address, BlockNumber, TransactionRequest, H256, U256}; @@ -7,15 +7,15 @@ use std::{fmt::Debug, marker::PhantomData}; use thiserror::Error as ThisError; -pub struct ContractCall<'a, S, P, D> { +pub struct ContractCall<'a, P, N, S, D> { pub(crate) tx: TransactionRequest, pub(crate) function: Function, - pub(crate) client: &'a Client<'a, S, P>, + pub(crate) client: &'a Client<'a, P, N, S>, pub(crate) block: Option, pub(crate) datatype: PhantomData, } -impl<'a, S, P, D: Detokenize> ContractCall<'a, S, P, D> { +impl<'a, P, N, S, D: Detokenize> ContractCall<'a, S, P, N, D> { /// Sets the `from` field in the transaction to the provided value pub fn from>(mut self, from: T) -> Self { self.tx.from = Some(from.into()); @@ -55,9 +55,13 @@ where CallError(P::Error), } -impl<'a, S: Signer, P: JsonRpcClient, D: Detokenize> ContractCall<'a, S, P, D> +impl<'a, P, N, S, D> ContractCall<'a, P, N, S, D> where + S: Signer, + P: JsonRpcClient, P::Error: 'static, + N: Network, + D: Detokenize, { /// Queries the blockchain via an `eth_call` for the provided transaction. /// diff --git a/crates/ethers-contract/src/contract.rs b/crates/ethers-contract/src/contract.rs index f2130fa4..5c9ba69e 100644 --- a/crates/ethers-contract/src/contract.rs +++ b/crates/ethers-contract/src/contract.rs @@ -1,7 +1,7 @@ use crate::{ContractCall, Event}; use ethers_abi::{Abi, Detokenize, Error, EventExt, Function, FunctionExt, Tokenize}; -use ethers_providers::JsonRpcClient; +use ethers_providers::{networks::Network, JsonRpcClient}; use ethers_signers::{Client, Signer}; use ethers_types::{Address, Filter, Selector, TransactionRequest}; @@ -13,8 +13,8 @@ use std::{collections::HashMap, fmt::Debug, hash::Hash, marker::PhantomData}; // TODO: Should we separate the lifetimes for the two references? // https://stackoverflow.com/a/29862184 #[derive(Debug, Clone)] -pub struct Contract<'a, S, P> { - client: &'a Client<'a, S, P>, +pub struct Contract<'a, P, N, S> { + client: &'a Client<'a, P, N, S>, abi: &'a Abi, address: Address, @@ -25,9 +25,9 @@ pub struct Contract<'a, S, P> { methods: HashMap, } -impl<'a, S: Signer, P: JsonRpcClient> Contract<'a, S, P> { +impl<'a, P: JsonRpcClient, N: Network, S: Signer> Contract<'a, P, N, S> { /// Creates a new contract from the provided client, abi and address - pub fn new(client: &'a Client<'a, S, P>, abi: &'a Abi, address: Address) -> Self { + pub fn new(client: &'a Client<'a, P, N, S>, abi: &'a Abi, address: Address) -> Self { let methods = create_mapping(&abi.functions, |function| function.selector()); Self { @@ -41,7 +41,7 @@ impl<'a, S: Signer, P: JsonRpcClient> Contract<'a, S, P> { /// Returns an `Event` builder for the provided event name. If there are /// multiple functions with the same name due to overloading, consider using /// the `method_hash` method instead, since this will use the first match. - pub fn event<'b, D: Detokenize>(&'a self, name: &str) -> Result, Error> + pub fn event<'b, D: Detokenize>(&'a self, name: &str) -> Result, Error> where 'a: 'b, { @@ -62,7 +62,7 @@ impl<'a, S: Signer, P: JsonRpcClient> Contract<'a, S, P> { &self, name: &str, args: T, - ) -> Result, Error> { + ) -> Result, Error> { // get the function let function = self.abi.function(name)?; self.method_func(function, args) @@ -74,7 +74,7 @@ impl<'a, S: Signer, P: JsonRpcClient> Contract<'a, S, P> { &self, signature: Selector, args: T, - ) -> Result, Error> { + ) -> Result, Error> { let function = self .methods .get(&signature) @@ -87,7 +87,7 @@ impl<'a, S: Signer, P: JsonRpcClient> Contract<'a, S, P> { &self, function: &Function, args: T, - ) -> Result, Error> { + ) -> Result, Error> { // create the calldata let data = function.encode_input(&args.into_tokens())?; diff --git a/crates/ethers-contract/src/event.rs b/crates/ethers-contract/src/event.rs index 13982f6d..ec18c692 100644 --- a/crates/ethers-contract/src/event.rs +++ b/crates/ethers-contract/src/event.rs @@ -1,21 +1,21 @@ use crate::ContractError; use ethers_abi::{Detokenize, Event as AbiEvent, RawLog}; -use ethers_providers::{JsonRpcClient, Provider}; +use ethers_providers::{networks::Network, JsonRpcClient, Provider}; use ethers_types::{BlockNumber, Filter, ValueOrArray, H256}; use std::marker::PhantomData; -pub struct Event<'a, 'b, P, D> { +pub struct Event<'a, 'b, P, N, D> { pub filter: Filter, - pub(crate) provider: &'a Provider

, + pub(crate) provider: &'a Provider, pub(crate) event: &'b AbiEvent, pub(crate) datatype: PhantomData, } // TODO: Improve these functions -impl<'a, 'b, P, D: Detokenize> Event<'a, 'b, P, D> { +impl<'a, 'b, P, N, D: Detokenize> Event<'a, 'b, P, N, D> { #[allow(clippy::wrong_self_convention)] pub fn from_block>(mut self, block: T) -> Self { self.filter.from_block = Some(block.into()); @@ -40,7 +40,7 @@ impl<'a, 'b, P, D: Detokenize> Event<'a, 'b, P, D> { } // TODO: Can we get rid of the static? -impl<'a, 'b, P: JsonRpcClient, D: Detokenize> Event<'a, 'b, P, D> +impl<'a, 'b, P: JsonRpcClient, N: Network, D: Detokenize> Event<'a, 'b, P, N, D> where P::Error: 'static, { diff --git a/crates/ethers-providers/src/ens.rs b/crates/ethers-providers/src/ens.rs index 29df4d83..48a5254d 100644 --- a/crates/ethers-providers/src/ens.rs +++ b/crates/ethers-providers/src/ens.rs @@ -74,7 +74,9 @@ mod tests { #[test] fn test_namehash() { - dbg!(ethers_utils::id("name(bytes32)")); + dbg!("00000000000C2E074eC69A0dFb2997BA6C7d2e1e" + .from_hex::>() + .unwrap()); for (name, expected) in &[ ( "", diff --git a/crates/ethers-providers/src/lib.rs b/crates/ethers-providers/src/lib.rs index 5acffe62..16a03700 100644 --- a/crates/ethers-providers/src/lib.rs +++ b/crates/ethers-providers/src/lib.rs @@ -7,6 +7,8 @@ mod http; mod provider; +pub mod networks; + /// ENS support pub mod ens; @@ -17,7 +19,7 @@ use std::{error::Error, fmt::Debug}; pub use provider::Provider; /// An HTTP provider for interacting with an Ethereum-compatible blockchain -pub type HttpProvider = Provider; +pub type HttpProvider = Provider; #[async_trait] /// Implement this trait in order to plug in different backends diff --git a/crates/ethers-signers/src/networks.rs b/crates/ethers-providers/src/networks.rs similarity index 54% rename from crates/ethers-signers/src/networks.rs rename to crates/ethers-providers/src/networks.rs index 0f8ae6bf..60f3f892 100644 --- a/crates/ethers-signers/src/networks.rs +++ b/crates/ethers-providers/src/networks.rs @@ -2,10 +2,11 @@ //! a transaction that is designed to work with testnet does not accidentally work //! with mainnet because the URL was changed. -use ethers_types::U64; +use ethers_types::{Address, H160, U64}; pub trait Network { const CHAIN_ID: Option; + const ENS_ADDRESS: Option

; // TODO: Default providers? e.g. `mainnet.infura.io/XXX`? } @@ -15,25 +16,19 @@ pub struct Mainnet; impl Network for Mainnet { const CHAIN_ID: Option = Some(U64([1])); + + // 0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e + const ENS_ADDRESS: Option
= Some(H160([ + // cannot set type aliases as constructors + 0, 0, 0, 0, 0, 12, 46, 7, 78, 198, 154, 13, 251, 41, 151, 186, 108, 125, 46, 30, + ])); } /// No EIP155 #[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct EIP155Disabled; +pub struct Any; -// EIP155 being disabled means no chainId will be used -impl Network for EIP155Disabled { +impl Network for Any { const CHAIN_ID: Option = None; -} - -pub mod instantiated { - use super::*; - use crate::Wallet; - - /// A Wallet instantiated with chain_id = 1 for Ethereum Mainnet. - pub type MainnetWallet = Wallet; - - /// A wallet which does not use EIP-155 and does not take the chain id into account - /// when creating transactions - pub type AnyWallet = Wallet; + const ENS_ADDRESS: Option
= None; } diff --git a/crates/ethers-providers/src/provider.rs b/crates/ethers-providers/src/provider.rs index dc5db310..654f4da2 100644 --- a/crates/ethers-providers/src/provider.rs +++ b/crates/ethers-providers/src/provider.rs @@ -1,4 +1,4 @@ -use crate::{ens, http::Provider as HttpProvider, JsonRpcClient}; +use crate::{ens, http::Provider as HttpProvider, networks::Network, JsonRpcClient}; use ethers_abi::{Detokenize, ParamType}; use ethers_types::{ @@ -10,15 +10,15 @@ use ethers_utils as utils; use serde::Deserialize; use url::{ParseError, Url}; -use std::{convert::TryFrom, fmt::Debug}; +use std::{convert::TryFrom, fmt::Debug, marker::PhantomData}; /// An abstract provider for interacting with the [Ethereum JSON RPC /// API](https://github.com/ethereum/wiki/wiki/JSON-RPC) #[derive(Clone, Debug)] -pub struct Provider

(P, Option

); +pub struct Provider(P, PhantomData, Option
); // JSON RPC bindings -impl Provider

{ +impl Provider { ////// Blockchain Status // // Functions for querying the state of the blockchain @@ -206,10 +206,13 @@ impl Provider

{ ens_name: &str, selector: Selector, ) -> Result, P::Error> { - let ens_addr = if let Some(ens_addr) = self.1 { - ens_addr - } else { - return Ok(None); + // Get the ENS address, prioritize the local override variable + let ens_addr = match self.2 { + Some(ens_addr) => ens_addr, + None => match N::ENS_ADDRESS { + Some(ens_addr) => ens_addr, + None => return Ok(None), + }, }; // first get the resolver responsible for this name @@ -231,8 +234,8 @@ impl Provider

{ Ok(Some(decode_bytes(param, data))) } - pub fn ens>(mut self, ens_addr: T) -> Self { - self.1 = Some(ens_addr.into()); + pub fn ens>(mut self, ens: T) -> Self { + self.2 = Some(ens.into()); self } } @@ -248,30 +251,30 @@ fn decode_bytes(param: ParamType, bytes: Bytes) -> T { T::from_tokens(tokens).expect("could not parse tokens as address") } -impl TryFrom<&str> for Provider { +impl TryFrom<&str> for Provider { type Error = ParseError; fn try_from(src: &str) -> Result { - Ok(Provider(HttpProvider::new(Url::parse(src)?), None)) + Ok(Provider( + HttpProvider::new(Url::parse(src)?), + PhantomData, + None, + )) } } #[cfg(test)] mod ens_tests { use super::*; + use crate::networks::Mainnet; #[tokio::test] // Test vector from: https://docs.ethers.io/ethers.js/v5-beta/api-providers.html#id2 async fn mainnet_resolve_name() { - let mainnet_ens_addr = "00000000000C2E074eC69A0dFb2997BA6C7d2e1e" - .parse::

() - .unwrap(); - - let provider = Provider::::try_from( + let provider = Provider::::try_from( "https://mainnet.infura.io/v3/9408f47dedf04716a03ef994182cf150", ) - .unwrap() - .ens(mainnet_ens_addr); + .unwrap(); let addr = provider .resolve_name("registrar.firefly.eth") @@ -297,15 +300,10 @@ mod ens_tests { #[tokio::test] // Test vector from: https://docs.ethers.io/ethers.js/v5-beta/api-providers.html#id2 async fn mainnet_lookup_address() { - let mainnet_ens_addr = "00000000000C2E074eC69A0dFb2997BA6C7d2e1e" - .parse::
() - .unwrap(); - - let provider = Provider::::try_from( + let provider = Provider::::try_from( "https://mainnet.infura.io/v3/9408f47dedf04716a03ef994182cf150", ) - .unwrap() - .ens(mainnet_ens_addr); + .unwrap(); let name = provider .lookup_address("6fC21092DA55B392b045eD78F4732bff3C580e2c".parse().unwrap()) diff --git a/crates/ethers-signers/src/client.rs b/crates/ethers-signers/src/client.rs index d6438ee9..08a9addd 100644 --- a/crates/ethers-signers/src/client.rs +++ b/crates/ethers-signers/src/client.rs @@ -1,18 +1,18 @@ use crate::Signer; -use ethers_providers::{JsonRpcClient, Provider}; +use ethers_providers::{networks::Network, JsonRpcClient, Provider}; use ethers_types::{Address, BlockNumber, TransactionRequest, TxHash}; use std::ops::Deref; #[derive(Clone, Debug)] -pub struct Client<'a, S, P> { - pub(crate) provider: &'a Provider

, +pub struct Client<'a, P, N, S> { + pub(crate) provider: &'a Provider, pub(crate) signer: Option, } -impl<'a, S, P> From<&'a Provider

> for Client<'a, S, P> { - fn from(provider: &'a Provider

) -> Self { +impl<'a, P, N, S> From<&'a Provider> for Client<'a, P, N, S> { + fn from(provider: &'a Provider) -> Self { Client { provider, signer: None, @@ -20,7 +20,12 @@ impl<'a, S, P> From<&'a Provider

> for Client<'a, S, P> { } } -impl<'a, S: Signer, P: JsonRpcClient> Client<'a, S, P> { +impl<'a, P, N, S> Client<'a, P, N, S> +where + S: Signer, + P: JsonRpcClient, + N: Network, +{ /// Signs the transaction and then broadcasts its RLP encoding via the `eth_sendRawTransaction` /// API pub async fn send_transaction( @@ -84,7 +89,7 @@ impl<'a, S: Signer, P: JsonRpcClient> Client<'a, S, P> { .unwrap_or_default() } - pub fn provider(&self) -> &Provider

{ + pub fn provider(&self) -> &Provider { self.provider } } @@ -92,8 +97,11 @@ impl<'a, S: Signer, P: JsonRpcClient> Client<'a, S, P> { // Abuse Deref to use the Provider's methods without re-writing everything. // This is an anti-pattern and should not be encouraged, but this improves the UX while // keeping the LoC low -impl<'a, S, P> Deref for Client<'a, S, P> { - type Target = &'a Provider

; +impl<'a, P, N, S> Deref for Client<'a, P, N, S> +where + N: 'a, +{ + type Target = &'a Provider; fn deref(&self) -> &Self::Target { &self.provider diff --git a/crates/ethers-signers/src/lib.rs b/crates/ethers-signers/src/lib.rs index 7588f80f..6ac4a081 100644 --- a/crates/ethers-signers/src/lib.rs +++ b/crates/ethers-signers/src/lib.rs @@ -10,10 +10,6 @@ //! //! TODO: We might need a `SignerAsync` trait for HSM use cases? -mod networks; -pub use networks::instantiated::*; -use networks::Network; - mod wallet; pub use wallet::Wallet; @@ -37,3 +33,12 @@ pub trait Signer { /// Returns the signer's Ethereum Address fn address(&self) -> Address; } + +use ethers_providers::networks::{Any, Mainnet}; + +/// A Wallet instantiated with chain_id = 1 for Ethereum Mainnet. +pub type MainnetWallet = Wallet; + +/// A wallet which does not use EIP-155 and does not take the chain id into account +/// when creating transactions +pub type AnyWallet = Wallet; diff --git a/crates/ethers-signers/src/wallet.rs b/crates/ethers-signers/src/wallet.rs index be03059f..6370ffe5 100644 --- a/crates/ethers-signers/src/wallet.rs +++ b/crates/ethers-signers/src/wallet.rs @@ -1,6 +1,6 @@ -use crate::{Client, Network, Signer}; +use crate::{Client, Signer}; -use ethers_providers::{JsonRpcClient, Provider}; +use ethers_providers::{networks::Network, JsonRpcClient, Provider}; use ethers_types::{ rand::Rng, secp256k1, Address, PrivateKey, PublicKey, Signature, Transaction, @@ -52,7 +52,7 @@ impl Wallet { } /// Connects to a provider and returns a client - pub fn connect(self, provider: &Provider

) -> Client, P> { + pub fn connect(self, provider: &Provider) -> Client> { Client { signer: Some(self), provider, diff --git a/crates/ethers-types/src/lib.rs b/crates/ethers-types/src/lib.rs index 10b3189a..dc81dbb5 100644 --- a/crates/ethers-types/src/lib.rs +++ b/crates/ethers-types/src/lib.rs @@ -4,7 +4,7 @@ pub type Selector = [u8; 4]; // Re-export common ethereum datatypes with more specific names pub use ethereum_types::H256 as TxHash; -pub use ethereum_types::{Address, Bloom, H256, U128, U256, U64}; +pub use ethereum_types::{Address, Bloom, H160, H256, U128, U256, U64}; mod transaction; pub use transaction::{Overrides, Transaction, TransactionReceipt, TransactionRequest}; diff --git a/crates/ethers/examples/get_logs.rs b/crates/ethers/examples/get_logs.rs index 197102d4..8d885d53 100644 --- a/crates/ethers/examples/get_logs.rs +++ b/crates/ethers/examples/get_logs.rs @@ -1,6 +1,6 @@ use anyhow::Result; use ethers::{ - providers::HttpProvider, + providers::{networks::Any, HttpProvider}, types::{Address, Filter}, }; use std::convert::TryFrom; @@ -8,7 +8,7 @@ use std::convert::TryFrom; #[tokio::main] async fn main() -> Result<()> { // connect to the network - let provider = HttpProvider::try_from("http://localhost:8545")?; + let provider = HttpProvider::::try_from("http://localhost:8545")?; let filter = Filter::new() .address_str("f817796F60D268A36a57b8D2dF1B97B14C0D0E1d")? diff --git a/crates/ethers/examples/transfer_eth.rs b/crates/ethers/examples/transfer_eth.rs index f51ee97e..c55effe5 100644 --- a/crates/ethers/examples/transfer_eth.rs +++ b/crates/ethers/examples/transfer_eth.rs @@ -1,6 +1,6 @@ use anyhow::Result; use ethers::{ - providers::HttpProvider, + providers::{networks::Any, HttpProvider}, types::{BlockNumber, TransactionRequest}, }; use std::convert::TryFrom; @@ -8,7 +8,7 @@ use std::convert::TryFrom; #[tokio::main] async fn main() -> Result<()> { // connect to the network - let provider = HttpProvider::try_from("http://localhost:8545")?; + let provider = HttpProvider::::try_from("http://localhost:8545")?; let accounts = provider.get_accounts().await?; let from = accounts[0];