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
This commit is contained in:
DaniPopes 2022-09-19 19:51:04 +02:00 committed by GitHub
parent 59a82a1c8f
commit db75e3628e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 75 additions and 39 deletions

View File

@ -7,7 +7,7 @@ use crate::{
use ethers_core::{ use ethers_core::{
abi::{Abi, Detokenize, Error, EventExt, Function, Tokenize}, abi::{Abi, Detokenize, Error, EventExt, Function, Tokenize},
types::{Address, Filter, NameOrAddress, Selector, ValueOrArray}, types::{Address, Filter, Selector, ValueOrArray},
}; };
#[cfg(not(feature = "legacy"))] #[cfg(not(feature = "legacy"))]
@ -232,13 +232,13 @@ impl<M: Middleware> Contract<M> {
#[cfg(feature = "legacy")] #[cfg(feature = "legacy")]
let tx = TransactionRequest { let tx = TransactionRequest {
to: Some(NameOrAddress::Address(self.address)), to: Some(self.address.into()),
data: Some(data), data: Some(data),
..Default::default() ..Default::default()
}; };
#[cfg(not(feature = "legacy"))] #[cfg(not(feature = "legacy"))]
let tx = Eip1559TransactionRequest { let tx = Eip1559TransactionRequest {
to: Some(NameOrAddress::Address(self.address)), to: Some(self.address.into()),
data: Some(data), data: Some(data),
..Default::default() ..Default::default()
}; };

View File

@ -1,10 +1,9 @@
use std::cmp::Ordering;
use crate::types::Address; use crate::types::Address;
use rlp::{Decodable, Encodable, RlpStream}; use rlp::{Decodable, Encodable, RlpStream};
use serde::{ser::Error as SerializationError, Deserialize, Deserializer, Serialize, Serializer}; 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)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum NameOrAddress { pub enum NameOrAddress {
/// An ENS Name (format does not get checked) /// An ENS Name (format does not get checked)
@ -25,7 +24,7 @@ impl Encodable for &NameOrAddress {
impl Encodable for NameOrAddress { impl Encodable for NameOrAddress {
fn rlp_append(&self, s: &mut RlpStream) { fn rlp_append(&self, s: &mut RlpStream) {
if let NameOrAddress::Address(inner) = self { if let Self::Address(inner) = self {
inner.rlp_append(s); inner.rlp_append(s);
} }
} }
@ -39,29 +38,17 @@ impl Decodable for NameOrAddress {
} }
// the data needs to be 20 bytes long // 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::Less => Err(rlp::DecoderError::RlpIsTooShort),
Ordering::Greater => Err(rlp::DecoderError::RlpIsTooBig), Ordering::Greater => Err(rlp::DecoderError::RlpIsTooBig),
Ordering::Equal => { Ordering::Equal => {
let rlp_data = rlp.data()?; 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<Address> for NameOrAddress {
fn from(s: Address) -> Self {
NameOrAddress::Address(s)
}
}
// Only serialize the Address variant since it doesn't make sense to ever serialize // Only serialize the Address variant since it doesn't make sense to ever serialize
// an ENS name // an ENS name
impl Serialize for NameOrAddress { impl Serialize for NameOrAddress {
@ -70,8 +57,8 @@ impl Serialize for NameOrAddress {
S: Serializer, S: Serializer,
{ {
match self { match self {
NameOrAddress::Address(addr) => addr.serialize(serializer), Self::Address(addr) => addr.serialize(serializer),
NameOrAddress::Name(name) => Err(SerializationError::custom(format!( Self::Name(name) => Err(SerializationError::custom(format!(
"cannot serialize ENS name {}, must be address", "cannot serialize ENS name {}, must be address",
name name
))), ))),
@ -85,8 +72,57 @@ impl<'de> Deserialize<'de> for NameOrAddress {
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
let inner = Address::deserialize(deserializer)?; 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<String> 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<Address> for NameOrAddress {
fn from(s: Address) -> Self {
Self::Address(s)
}
}
impl FromStr for NameOrAddress {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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),
}
} }
} }

View File

@ -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<T: Into<NameOrAddress>>(&mut self, to: T) -> &mut Self { pub fn set_to<T: Into<NameOrAddress>>(&mut self, to: T) -> &mut Self {
let to = to.into(); let to = to.into();
match self { match self {

View File

@ -545,9 +545,7 @@ mod tests {
"0x3c24d7329e92f84f08556ceb6df1cdb0104ca49f", "0x3c24d7329e92f84f08556ceb6df1cdb0104ca49f",
]; ];
let addresses = raw_addresses let addresses = raw_addresses.iter().map(|addr| addr.parse::<Address>().unwrap().into());
.iter()
.map(|addr| NameOrAddress::Address(Address::from_str(addr).unwrap()));
// decoding will do sender recovery and we don't expect any of these to error, so we should // 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 // check that the address matches for each decoded transaction

View File

@ -177,17 +177,15 @@ impl DsProxy {
impl Transformer for DsProxy { impl Transformer for DsProxy {
fn transform(&self, tx: &mut TypedTransaction) -> Result<(), TransformerError> { fn transform(&self, tx: &mut TypedTransaction) -> Result<(), TransformerError> {
// the target address cannot be None. // the target address cannot be None.
let target = match tx.to() { let target =
Some(NameOrAddress::Address(addr)) => addr, *tx.to_addr().ok_or_else(|| TransformerError::MissingField("to".to_string()))?;
_ => return Err(TransformerError::MissingField("to".into())),
};
// fetch the data field. // fetch the data field.
let data = tx.data().cloned().unwrap_or_else(|| vec![].into()); let data = tx.data().cloned().unwrap_or_else(|| vec![].into());
// encode data as the ABI encoded data for DSProxy's execute method. // encode data as the ABI encoded data for DSProxy's execute method.
let selector = id("execute(address,bytes)"); 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. // update appropriate fields of the proxy tx.
tx.set_data(encoded_data); tx.set_data(encoded_data);

View File

@ -32,31 +32,31 @@ pub const FIELD_SELECTOR: Selector = [89, 209, 212, 60];
pub const INTERFACE_SELECTOR: Selector = [1, 255, 201, 167]; pub const INTERFACE_SELECTOR: Selector = [1, 255, 201, 167];
/// Returns a transaction request for calling the `resolver` method on the ENS server /// Returns a transaction request for calling the `resolver` method on the ENS server
pub fn get_resolver<T: Into<Address>>(ens_address: T, name: &str) -> TransactionRequest { pub fn get_resolver<T: Into<NameOrAddress>>(ens_address: T, name: &str) -> TransactionRequest {
// keccak256('resolver(bytes32)') // keccak256('resolver(bytes32)')
let data = [&RESOLVER[..], &namehash(name).0].concat(); let data = [&RESOLVER[..], &namehash(name).0].concat();
TransactionRequest { TransactionRequest {
data: Some(data.into()), data: Some(data.into()),
to: Some(NameOrAddress::Address(ens_address.into())), to: Some(ens_address.into()),
..Default::default() ..Default::default()
} }
} }
/// Returns a transaction request for checking interface support /// Returns a transaction request for checking interface support
pub fn supports_interface<T: Into<Address>>( pub fn supports_interface<T: Into<NameOrAddress>>(
resolver_address: T, resolver_address: T,
selector: Selector, selector: Selector,
) -> TransactionRequest { ) -> TransactionRequest {
let data = [&INTERFACE_SELECTOR[..], &selector[..], &[0; 28]].concat(); let data = [&INTERFACE_SELECTOR[..], &selector[..], &[0; 28]].concat();
TransactionRequest { TransactionRequest {
data: Some(data.into()), data: Some(data.into()),
to: Some(NameOrAddress::Address(resolver_address.into())), to: Some(resolver_address.into()),
..Default::default() ..Default::default()
} }
} }
/// Returns a transaction request for calling /// Returns a transaction request for calling
pub fn resolve<T: Into<Address>>( pub fn resolve<T: Into<NameOrAddress>>(
resolver_address: T, resolver_address: T,
selector: Selector, selector: Selector,
name: &str, name: &str,
@ -65,7 +65,7 @@ pub fn resolve<T: Into<Address>>(
let data = [&selector[..], &namehash(name).0, parameters.unwrap_or_default()].concat(); let data = [&selector[..], &namehash(name).0, parameters.unwrap_or_default()].concat();
TransactionRequest { TransactionRequest {
data: Some(data.into()), data: Some(data.into()),
to: Some(NameOrAddress::Address(resolver_address.into())), to: Some(resolver_address.into()),
..Default::default() ..Default::default()
} }
} }

View File

@ -7,7 +7,7 @@ async fn main() -> Result<()> {
// fork mainnet // fork mainnet
let anvil = let anvil =
Anvil::new().fork("https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27").spawn(); 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 // connect to the network
let provider = Provider::<Http>::try_from(anvil.endpoint()).unwrap().with_sender(from); let provider = Provider::<Http>::try_from(anvil.endpoint()).unwrap().with_sender(from);