From db75e3628e38899443317923a881f07ed7188fa7 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 19 Sep 2022 19:51:04 +0200 Subject: [PATCH] feat(core): add utility methods to NameOrAddress (#1720) * feat: NameOrAddress impls * feat: add to_addr to TypedTransaction * fix: use NameOrAddress for ens * chore: clippy * dont clone --- ethers-contract/src/contract.rs | 6 +- ethers-core/src/types/ens.rs | 78 ++++++++++++++----- ethers-core/src/types/transaction/eip2718.rs | 4 + ethers-core/src/types/transaction/request.rs | 4 +- .../src/transformer/ds_proxy/mod.rs | 8 +- ethers-providers/src/ens.rs | 12 +-- examples/ens.rs | 2 +- 7 files changed, 75 insertions(+), 39 deletions(-) diff --git a/ethers-contract/src/contract.rs b/ethers-contract/src/contract.rs index 0769619a..0508debf 100644 --- a/ethers-contract/src/contract.rs +++ b/ethers-contract/src/contract.rs @@ -7,7 +7,7 @@ use crate::{ use ethers_core::{ abi::{Abi, Detokenize, Error, EventExt, Function, Tokenize}, - types::{Address, Filter, NameOrAddress, Selector, ValueOrArray}, + types::{Address, Filter, Selector, ValueOrArray}, }; #[cfg(not(feature = "legacy"))] @@ -232,13 +232,13 @@ impl Contract { #[cfg(feature = "legacy")] let tx = TransactionRequest { - to: Some(NameOrAddress::Address(self.address)), + to: Some(self.address.into()), data: Some(data), ..Default::default() }; #[cfg(not(feature = "legacy"))] let tx = Eip1559TransactionRequest { - to: Some(NameOrAddress::Address(self.address)), + to: Some(self.address.into()), data: Some(data), ..Default::default() }; diff --git a/ethers-core/src/types/ens.rs b/ethers-core/src/types/ens.rs index 60c6e39e..1ba9869f 100644 --- a/ethers-core/src/types/ens.rs +++ b/ethers-core/src/types/ens.rs @@ -1,10 +1,9 @@ -use std::cmp::Ordering; - use crate::types::Address; use rlp::{Decodable, Encodable, RlpStream}; use serde::{ser::Error as SerializationError, Deserialize, Deserializer, Serialize, Serializer}; +use std::{cmp::Ordering, convert::Infallible, str::FromStr}; -/// ENS name or Ethereum Address. Not RLP encoded/serialized if it's a name +/// ENS name or Ethereum Address. Not RLP encoded/serialized if it's a name. #[derive(Clone, Debug, PartialEq, Eq)] pub enum NameOrAddress { /// An ENS Name (format does not get checked) @@ -25,7 +24,7 @@ impl Encodable for &NameOrAddress { impl Encodable for NameOrAddress { fn rlp_append(&self, s: &mut RlpStream) { - if let NameOrAddress::Address(inner) = self { + if let Self::Address(inner) = self { inner.rlp_append(s); } } @@ -39,29 +38,17 @@ impl Decodable for NameOrAddress { } // the data needs to be 20 bytes long - match 20.cmp(&rlp.size()) { + match rlp.size().cmp(&20usize) { Ordering::Less => Err(rlp::DecoderError::RlpIsTooShort), Ordering::Greater => Err(rlp::DecoderError::RlpIsTooBig), Ordering::Equal => { let rlp_data = rlp.data()?; - Ok(NameOrAddress::Address(Address::from_slice(rlp_data))) + Ok(Self::Address(Address::from_slice(rlp_data))) } } } } -impl From<&str> for NameOrAddress { - fn from(s: &str) -> Self { - NameOrAddress::Name(s.to_owned()) - } -} - -impl From
for NameOrAddress { - fn from(s: Address) -> Self { - NameOrAddress::Address(s) - } -} - // Only serialize the Address variant since it doesn't make sense to ever serialize // an ENS name impl Serialize for NameOrAddress { @@ -70,8 +57,8 @@ impl Serialize for NameOrAddress { S: Serializer, { match self { - NameOrAddress::Address(addr) => addr.serialize(serializer), - NameOrAddress::Name(name) => Err(SerializationError::custom(format!( + Self::Address(addr) => addr.serialize(serializer), + Self::Name(name) => Err(SerializationError::custom(format!( "cannot serialize ENS name {}, must be address", name ))), @@ -85,8 +72,57 @@ impl<'de> Deserialize<'de> for NameOrAddress { D: Deserializer<'de>, { let inner = Address::deserialize(deserializer)?; + Ok(Self::Address(inner)) + } +} - Ok(NameOrAddress::Address(inner)) +impl From<&str> for NameOrAddress { + fn from(s: &str) -> Self { + Self::from_str(s).unwrap() + } +} + +impl From for NameOrAddress { + fn from(s: String) -> Self { + Self::Name(s) + } +} + +impl From<&String> for NameOrAddress { + fn from(s: &String) -> Self { + Self::Name(s.clone()) + } +} + +impl From
for NameOrAddress { + fn from(s: Address) -> Self { + Self::Address(s) + } +} + +impl FromStr for NameOrAddress { + type Err = Infallible; + + fn from_str(s: &str) -> Result { + Ok(Self::Name(s.to_string())) + } +} + +impl NameOrAddress { + /// Maps Address(a) to Some(a) and Name to None. + pub fn as_address(&self) -> Option<&Address> { + match self { + Self::Address(a) => Some(a), + Self::Name(_) => None, + } + } + + /// Maps Name(n) to Some(n) and Address to None. + pub fn as_name(&self) -> Option<&str> { + match self { + Self::Address(_) => None, + Self::Name(n) => Some(n), + } } } diff --git a/ethers-core/src/types/transaction/eip2718.rs b/ethers-core/src/types/transaction/eip2718.rs index 62134380..ef4a5f8d 100644 --- a/ethers-core/src/types/transaction/eip2718.rs +++ b/ethers-core/src/types/transaction/eip2718.rs @@ -104,6 +104,10 @@ impl TypedTransaction { } } + pub fn to_addr(&self) -> Option<&Address> { + self.to().and_then(|t| t.as_address()) + } + pub fn set_to>(&mut self, to: T) -> &mut Self { let to = to.into(); match self { diff --git a/ethers-core/src/types/transaction/request.rs b/ethers-core/src/types/transaction/request.rs index 34a47f86..914b11e5 100644 --- a/ethers-core/src/types/transaction/request.rs +++ b/ethers-core/src/types/transaction/request.rs @@ -545,9 +545,7 @@ mod tests { "0x3c24d7329e92f84f08556ceb6df1cdb0104ca49f", ]; - let addresses = raw_addresses - .iter() - .map(|addr| NameOrAddress::Address(Address::from_str(addr).unwrap())); + let addresses = raw_addresses.iter().map(|addr| addr.parse::
().unwrap().into()); // decoding will do sender recovery and we don't expect any of these to error, so we should // check that the address matches for each decoded transaction diff --git a/ethers-middleware/src/transformer/ds_proxy/mod.rs b/ethers-middleware/src/transformer/ds_proxy/mod.rs index 464f4cc7..7251ab2b 100644 --- a/ethers-middleware/src/transformer/ds_proxy/mod.rs +++ b/ethers-middleware/src/transformer/ds_proxy/mod.rs @@ -177,17 +177,15 @@ impl DsProxy { impl Transformer for DsProxy { fn transform(&self, tx: &mut TypedTransaction) -> Result<(), TransformerError> { // the target address cannot be None. - let target = match tx.to() { - Some(NameOrAddress::Address(addr)) => addr, - _ => return Err(TransformerError::MissingField("to".into())), - }; + let target = + *tx.to_addr().ok_or_else(|| TransformerError::MissingField("to".to_string()))?; // fetch the data field. let data = tx.data().cloned().unwrap_or_else(|| vec![].into()); // encode data as the ABI encoded data for DSProxy's execute method. let selector = id("execute(address,bytes)"); - let encoded_data = self.contract.encode_with_selector(selector, (*target, data))?; + let encoded_data = self.contract.encode_with_selector(selector, (target, data))?; // update appropriate fields of the proxy tx. tx.set_data(encoded_data); diff --git a/ethers-providers/src/ens.rs b/ethers-providers/src/ens.rs index 74ad728d..886783e6 100644 --- a/ethers-providers/src/ens.rs +++ b/ethers-providers/src/ens.rs @@ -32,31 +32,31 @@ pub const FIELD_SELECTOR: Selector = [89, 209, 212, 60]; pub const INTERFACE_SELECTOR: Selector = [1, 255, 201, 167]; /// Returns a transaction request for calling the `resolver` method on the ENS server -pub fn get_resolver>(ens_address: T, name: &str) -> TransactionRequest { +pub fn get_resolver>(ens_address: T, name: &str) -> TransactionRequest { // keccak256('resolver(bytes32)') let data = [&RESOLVER[..], &namehash(name).0].concat(); TransactionRequest { data: Some(data.into()), - to: Some(NameOrAddress::Address(ens_address.into())), + to: Some(ens_address.into()), ..Default::default() } } /// Returns a transaction request for checking interface support -pub fn supports_interface>( +pub fn supports_interface>( resolver_address: T, selector: Selector, ) -> TransactionRequest { let data = [&INTERFACE_SELECTOR[..], &selector[..], &[0; 28]].concat(); TransactionRequest { data: Some(data.into()), - to: Some(NameOrAddress::Address(resolver_address.into())), + to: Some(resolver_address.into()), ..Default::default() } } /// Returns a transaction request for calling -pub fn resolve>( +pub fn resolve>( resolver_address: T, selector: Selector, name: &str, @@ -65,7 +65,7 @@ pub fn resolve>( let data = [&selector[..], &namehash(name).0, parameters.unwrap_or_default()].concat(); TransactionRequest { data: Some(data.into()), - to: Some(NameOrAddress::Address(resolver_address.into())), + to: Some(resolver_address.into()), ..Default::default() } } diff --git a/examples/ens.rs b/examples/ens.rs index 97b706e2..5874d5c9 100644 --- a/examples/ens.rs +++ b/examples/ens.rs @@ -7,7 +7,7 @@ async fn main() -> Result<()> { // fork mainnet let anvil = Anvil::new().fork("https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27").spawn(); - let from = anvil.addresses()[0].clone(); + let from = anvil.addresses()[0]; // connect to the network let provider = Provider::::try_from(anvil.endpoint()).unwrap().with_sender(from);