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::{
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<M: Middleware> Contract<M> {
#[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()
};

View File

@ -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<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
// 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<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 {
let to = to.into();
match self {

View File

@ -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::<Address>().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

View File

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

View File

@ -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<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)')
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<T: Into<Address>>(
pub fn supports_interface<T: Into<NameOrAddress>>(
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<T: Into<Address>>(
pub fn resolve<T: Into<NameOrAddress>>(
resolver_address: T,
selector: Selector,
name: &str,
@ -65,7 +65,7 @@ pub fn resolve<T: Into<Address>>(
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()
}
}

View File

@ -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::<Http>::try_from(anvil.endpoint()).unwrap().with_sender(from);