integrate ENS at the provider level

This commit is contained in:
Georgios Konstantopoulos 2020-05-27 18:43:43 +03:00
parent 2e5ef68219
commit 6109003559
No known key found for this signature in database
GPG Key ID: FA607837CD26EDBC
17 changed files with 109 additions and 95 deletions

View File

@ -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<T: Into<Address>>(address: T, client: &'a Client<'a, S, P>) -> Self {
pub fn new<T: Into<Address>>(address: T, client: &'a Client<'a, P, N, S>) -> Self {
let contract = Contract::new(client, &ABI, address.into());
Self(contract)
}

View File

@ -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())

View File

@ -52,7 +52,7 @@ fn expand_filter(event: &Event) -> Result<TokenStream> {
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,
{

View File

@ -40,9 +40,9 @@ fn expand_function(function: &Function, alias: Option<Ident>) -> Result<TokenStr
let outputs = expand_fn_outputs(&function.outputs)?;
let result = if function.constant {
quote! { ContractCall<'a, S, P, #outputs> }
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);

View File

@ -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<BlockNumber>,
pub(crate) datatype: PhantomData<D>,
}
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<T: Into<Address>>(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.
///

View File

@ -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<Selector, (String, usize)>,
}
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<Event<'a, 'b, P, D>, Error>
pub fn event<'b, D: Detokenize>(&'a self, name: &str) -> Result<Event<'a, 'b, P, N, D>, Error>
where
'a: 'b,
{
@ -62,7 +62,7 @@ impl<'a, S: Signer, P: JsonRpcClient> Contract<'a, S, P> {
&self,
name: &str,
args: T,
) -> Result<ContractCall<'a, S, P, D>, Error> {
) -> Result<ContractCall<'a, P, N, S, D>, 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<ContractCall<'a, S, P, D>, Error> {
) -> Result<ContractCall<'a, P, N, S, D>, 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<ContractCall<'a, S, P, D>, Error> {
) -> Result<ContractCall<'a, P, N, S, D>, Error> {
// create the calldata
let data = function.encode_input(&args.into_tokens())?;

View File

@ -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<P>,
pub(crate) provider: &'a Provider<P, N>,
pub(crate) event: &'b AbiEvent,
pub(crate) datatype: PhantomData<D>,
}
// 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<T: Into<BlockNumber>>(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,
{

View File

@ -74,7 +74,9 @@ mod tests {
#[test]
fn test_namehash() {
dbg!(ethers_utils::id("name(bytes32)"));
dbg!("00000000000C2E074eC69A0dFb2997BA6C7d2e1e"
.from_hex::<Vec<u8>>()
.unwrap());
for (name, expected) in &[
(
"",

View File

@ -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<http::Provider>;
pub type HttpProvider<N> = Provider<http::Provider, N>;
#[async_trait]
/// Implement this trait in order to plug in different backends

View File

@ -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<U64>;
const ENS_ADDRESS: Option<Address>;
// TODO: Default providers? e.g. `mainnet.infura.io/XXX`?
}
@ -15,25 +16,19 @@ pub struct Mainnet;
impl Network for Mainnet {
const CHAIN_ID: Option<U64> = Some(U64([1]));
// 0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e
const ENS_ADDRESS: Option<Address> = 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<U64> = None;
}
pub mod instantiated {
use super::*;
use crate::Wallet;
/// A Wallet instantiated with chain_id = 1 for Ethereum Mainnet.
pub type MainnetWallet = Wallet<Mainnet>;
/// A wallet which does not use EIP-155 and does not take the chain id into account
/// when creating transactions
pub type AnyWallet = Wallet<EIP155Disabled>;
const ENS_ADDRESS: Option<Address> = None;
}

View File

@ -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>(P, Option<Address>);
pub struct Provider<P, N>(P, PhantomData<N>, Option<Address>);
// JSON RPC bindings
impl<P: JsonRpcClient> Provider<P> {
impl<P: JsonRpcClient, N: Network> Provider<P, N> {
////// Blockchain Status
//
// Functions for querying the state of the blockchain
@ -206,10 +206,13 @@ impl<P: JsonRpcClient> Provider<P> {
ens_name: &str,
selector: Selector,
) -> Result<Option<T>, 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<P: JsonRpcClient> Provider<P> {
Ok(Some(decode_bytes(param, data)))
}
pub fn ens<T: Into<Address>>(mut self, ens_addr: T) -> Self {
self.1 = Some(ens_addr.into());
pub fn ens<T: Into<Address>>(mut self, ens: T) -> Self {
self.2 = Some(ens.into());
self
}
}
@ -248,30 +251,30 @@ fn decode_bytes<T: Detokenize>(param: ParamType, bytes: Bytes) -> T {
T::from_tokens(tokens).expect("could not parse tokens as address")
}
impl TryFrom<&str> for Provider<HttpProvider> {
impl<N: Network> TryFrom<&str> for Provider<HttpProvider, N> {
type Error = ParseError;
fn try_from(src: &str) -> Result<Self, Self::Error> {
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::<Address>()
.unwrap();
let provider = Provider::<HttpProvider>::try_from(
let provider = Provider::<HttpProvider, Mainnet>::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::<Address>()
.unwrap();
let provider = Provider::<HttpProvider>::try_from(
let provider = Provider::<HttpProvider, Mainnet>::try_from(
"https://mainnet.infura.io/v3/9408f47dedf04716a03ef994182cf150",
)
.unwrap()
.ens(mainnet_ens_addr);
.unwrap();
let name = provider
.lookup_address("6fC21092DA55B392b045eD78F4732bff3C580e2c".parse().unwrap())

View File

@ -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<P>,
pub struct Client<'a, P, N, S> {
pub(crate) provider: &'a Provider<P, N>,
pub(crate) signer: Option<S>,
}
impl<'a, S, P> From<&'a Provider<P>> for Client<'a, S, P> {
fn from(provider: &'a Provider<P>) -> Self {
impl<'a, P, N, S> From<&'a Provider<P, N>> for Client<'a, P, N, S> {
fn from(provider: &'a Provider<P, N>) -> Self {
Client {
provider,
signer: None,
@ -20,7 +20,12 @@ impl<'a, S, P> From<&'a Provider<P>> 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<P> {
pub fn provider(&self) -> &Provider<P, N> {
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<P>;
impl<'a, P, N, S> Deref for Client<'a, P, N, S>
where
N: 'a,
{
type Target = &'a Provider<P, N>;
fn deref(&self) -> &Self::Target {
&self.provider

View File

@ -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<Mainnet>;
/// A wallet which does not use EIP-155 and does not take the chain id into account
/// when creating transactions
pub type AnyWallet = Wallet<Any>;

View File

@ -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<N: Network> Wallet<N> {
}
/// Connects to a provider and returns a client
pub fn connect<P: JsonRpcClient>(self, provider: &Provider<P>) -> Client<Wallet<N>, P> {
pub fn connect<P: JsonRpcClient>(self, provider: &Provider<P, N>) -> Client<P, N, Wallet<N>> {
Client {
signer: Some(self),
provider,

View File

@ -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};

View File

@ -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::<Any>::try_from("http://localhost:8545")?;
let filter = Filter::new()
.address_str("f817796F60D268A36a57b8D2dF1B97B14C0D0E1d")?

View File

@ -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::<Any>::try_from("http://localhost:8545")?;
let accounts = provider.get_accounts().await?;
let from = accounts[0];