From b6b9ebffbe32835be7983de8aada019cd3a04151 Mon Sep 17 00:00:00 2001 From: SionoiS Date: Fri, 4 Feb 2022 15:12:26 -0500 Subject: [PATCH] ENS documentation (#587) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * deployment WIP * Tests & better errors * Better Documentation * New Contract Error Variant * Fixed trying to compile doc example * Removing Tests * Fix doc import * ignore doc test * Update src/contract/ens/mod.rs Co-authored-by: Tomasz Drwięga * Example & Docs fixes * Example fix * Opss * last fix Co-authored-by: Tomasz Drwięga --- src/contract/ens/eth_ens.rs | 364 +++++++++++++++++++-------- src/contract/ens/mod.rs | 23 +- src/contract/ens/public_resolver.rs | 21 +- src/contract/ens/registry.rs | 9 +- src/contract/ens/reverse_resolver.rs | 11 +- src/contract/error.rs | 3 + 6 files changed, 316 insertions(+), 115 deletions(-) diff --git a/src/contract/ens/eth_ens.rs b/src/contract/ens/eth_ens.rs index 56f1cf67..8a9a658f 100644 --- a/src/contract/ens/eth_ens.rs +++ b/src/contract/ens/eth_ens.rs @@ -27,6 +27,7 @@ pub struct Ens { } impl Namespace for Ens { + /// Create a new ENS interface with the given transport. fn new(transport: T) -> Self where Self: Sized, @@ -64,73 +65,106 @@ impl Ens { /*** Main ENS Registry Functions Below ***/ - /// Returns the owner of a name. - pub async fn owner(&self, domain: &str) -> Result { - let domain = self.normalize_name(domain)?; - let node = namehash(&domain); + /// Returns the owner of the name specified by ```node```. + pub async fn owner(&self, node: &str) -> Result { + let node = self.normalize_name(node)?; + let node = namehash(&node); self.registry.owner(node).await } - /// Returns the address of the resolver responsible for the name specified. - pub async fn resolver(&self, domain: &str) -> Result { - let domain = self.normalize_name(domain)?; - let node = namehash(&domain); + /// Returns the address of the resolver responsible for the name specified by ```node```. + pub async fn resolver(&self, node: &str) -> Result { + let node = self.normalize_name(node)?; + let node = namehash(&node); self.registry.resolver(node).await } - /// Returns the caching TTL (time-to-live) of a name. - pub async fn ttl(&self, domain: &str) -> Result { - let domain = self.normalize_name(domain)?; - let node = namehash(&domain); + /// Returns the caching time-to-live of the name specified by ```node```. + /// + /// Systems that wish to cache information about a name, including ownership, resolver address, and records, should respect this value. + /// + /// If TTL is zero, new data should be fetched on each query. + pub async fn ttl(&self, node: &str) -> Result { + let node = self.normalize_name(node)?; + let node = namehash(&node); self.registry.ttl(node).await } - /// Sets the owner of the given name. - pub async fn set_owner(&self, from: Address, domain: &str, owner: Address) -> Result { - let domain = self.normalize_name(domain)?; - let node = namehash(&domain); + /// Reassigns ownership of the name identified by ```node``` to ```owner```. + /// + /// Only callable by the current owner of the name. + /// + /// Emits the following event: + /// ```solidity + /// event Transfer(bytes32 indexed node, address owner); + /// ``` + pub async fn set_owner(&self, from: Address, node: &str, owner: Address) -> Result { + let node = self.normalize_name(node)?; + let node = namehash(&node); self.registry.set_owner(from, node, owner).await } - /// Sets the resolver contract address of a name. + /// Updates the resolver associated with the name identified by ```node``` to ```resolver```. + /// + /// Only callable by the current owner of the name. + /// ```resolver``` must specify the address of a contract that implements the Resolver interface. + /// + /// Emits the following event: + /// ```solidity + /// event NewResolver(bytes32 indexed node, address resolver); + /// ``` pub async fn set_resolver( &self, from: Address, - domain: &str, + node: &str, address: Address, ) -> Result { - let domain = self.normalize_name(domain)?; - let node = namehash(&domain); + let node = self.normalize_name(node)?; + let node = namehash(&node); self.registry.set_resolver(from, node, address).await } - /// Sets the caching TTL (time-to-live) of a name. - pub async fn set_ttl(&self, from: Address, domain: &str, ttl: u64) -> Result { - let domain = self.normalize_name(domain)?; - let node = namehash(&domain); + /// Updates the caching time-to-live of the name identified by ```node```. + /// + /// Only callable by the current owner of the name. + /// + /// Emits the following event: + /// ```solidity + /// event NewTTL(bytes32 indexed node, uint64 ttl); + /// ``` + pub async fn set_ttl(&self, from: Address, node: &str, ttl: u64) -> Result { + let node = self.normalize_name(node)?; + let node = namehash(&node); self.registry.set_ttl(from, node, ttl).await } - /// Creates a new subdomain of the given node, assigning ownership of it to the specified owner. + /// Creates a new subdomain of ```node```, assigning ownership of it to the specified ```owner```. /// /// If the domain already exists, ownership is reassigned but the resolver and TTL are left unmodified. + /// + /// For example, if you own alice.eth and want to create the subdomain iam.alice.eth, supply ```alice.eth``` as the ```node```, and ```iam``` as the ```label```. + /// + /// Emits the following event: + /// ```solidity + /// event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner); + /// ``` pub async fn set_subdomain_owner( &self, from: Address, - domain: &str, - subdomain: &str, + node: &str, + label: &str, owner: Address, ) -> Result { - let domain = self.normalize_name(domain)?; - let node = namehash(&domain); + let node = self.normalize_name(node)?; + let node = namehash(&node); - let label = self.normalize_name(subdomain)?; + let label = self.normalize_name(label)?; let label = crate::signing::keccak256(label.as_bytes()); self.registry.set_subnode_owner(from, node, label, owner).await @@ -138,17 +172,17 @@ impl Ens { /// Sets the owner, resolver, and TTL for an ENS record in a single operation. /// - /// This function is offered for convenience, and is exactly equivalent to calling set_resolver, set_ttl and set_owner in that order. + /// This function is offered for convenience, and is exactly equivalent to calling [`set_resolver`](#method.set_resolver), [`set_ttl`](#method.set_ttl) and [`set_owner`](#method.set_owner) in that order. pub async fn set_record( &self, from: Address, - domain: &str, + node: &str, owner: Address, resolver: Address, ttl: u64, ) -> Result { - let domain = self.normalize_name(domain)?; - let node = namehash(&domain); + let node = self.normalize_name(node)?; + let node = namehash(&node); self.registry.set_record(from, node, owner, resolver, ttl).await } @@ -159,16 +193,16 @@ impl Ens { pub async fn set_subdomain_record( &self, from: Address, - domain: &str, - subdomain: &str, + node: &str, + label: &str, owner: Address, resolver: Address, ttl: u64, ) -> Result { - let domain = self.normalize_name(domain)?; - let node = namehash(&domain); + let node = self.normalize_name(node)?; + let node = namehash(&node); - let label = self.normalize_name(subdomain)?; + let label = self.normalize_name(label)?; let label = crate::signing::keccak256(label.as_bytes()); self.registry @@ -188,27 +222,36 @@ impl Ens { self.registry.set_approval_for_all(from, operator, approved).await } - /// Returns true if the operator is approved to make ENS registry operations on behalf of the owner. + /// Returns true if ```operator``` is approved to make ENS registry operations on behalf of ```owner```. pub async fn is_approved_for_all(&self, owner: Address, operator: Address) -> Result { self.registry.check_approval(owner, operator).await } - /// Returns true if domain exists in the ENS registry. + /// Returns true if ```node``` exists in this ENS registry. /// /// This will return false for records that are in the legacy ENS registry but have not yet been migrated to the new one. - pub async fn record_exists(&self, domain: &str) -> Result { - let domain = self.normalize_name(domain)?; - let node = namehash(&domain); + pub async fn record_exists(&self, node: &str) -> Result { + let node = self.normalize_name(node)?; + let node = namehash(&node); self.registry.check_record_existence(node).await } /*** Public Resolver Functions Below ***/ - /// Returns true if the related Public Resolver does support the given interfaceId. - pub async fn supports_interface(&self, domain: &str, interface_id: [u8; 4]) -> Result { - let domain = self.normalize_name(domain)?; - let node = namehash(&domain); + /// ENS uses [ERC 165](https://eips.ethereum.org/EIPS/eip-165) for interface detection. + /// + /// ERC 165 requires that supporting contracts implement a function, ```supportsInterface```, which takes an interface ID and returns a boolean value indicating if this interface is supported or not. + /// Interface IDs are calculated as the exclusive-or of the four-byte function identifiers of each function included in the interface. + /// + /// For example, ```addr(bytes32)``` has the function ID *0x3b3b57de*. + /// Because it is the only function in the Ethereum Address interface, its interface ID is also *0x3b3b57de*, and so calling ```supportsInterface(0x3b3b57de)``` will return *true* for any resolver that supports ```addr()```. + /// ERC 165 has an interface ID of *0x01ffc9a7*, so ```supportsInterface(0x01ffc9a7)``` will always return *true* for any ERC 165 supporting contract (and hence for any resolver). + /// + /// Note that the public resolver does not expose explicit interfaces for setter functions, so there are no automated means to check for support for a given setter function. + pub async fn supports_interface(&self, node: &str, interface_id: [u8; 4]) -> Result { + let node = self.normalize_name(node)?; + let node = namehash(&node); let resolver_addr = self.registry.resolver(node).await?; let resolver = PublicResolver::new(self.web3.eth(), resolver_addr); @@ -216,94 +259,136 @@ impl Ens { resolver.check_interface_support(interface_id).await } - /// Resolves an ENS name to an Ethereum address. - pub async fn eth_address(&self, domain: &str) -> Result { - let domain = self.normalize_name(domain)?; - let node = namehash(&domain); + /// Returns the Ethereum address associated with the provided ```node```, or 0 if none. + /// + /// This function has interface ID *0x3b3b57de*. + /// + /// This function is specified in [EIP 137](https://eips.ethereum.org/EIPS/eip-137). + pub async fn eth_address(&self, node: &str) -> Result { + let node = self.normalize_name(node)?; + let node = namehash(&node); let resolver_addr = self.registry.resolver(node).await?; let resolver = PublicResolver::new(self.web3.eth(), resolver_addr); if !resolver.check_interface_support(*ADDR_INTERFACE_ID).await? { - return Err(ContractError::Abi(EthError::InvalidData)); + return Err(ContractError::InterfaceUnsupported); } resolver.ethereum_address(node).await } - /// Sets the address of an ENS name in this resolver. + /// Sets the Ethereum address associated with the provided ```node``` to ```addr```. + /// + /// Only callable by the owner of ```node```. + /// + /// Emits the following event: + /// ```solidity + /// event AddrChanged(bytes32 indexed node, address a); + /// ``` pub async fn set_eth_address( &self, from: Address, - domain: &str, - address: Address, + node: &str, + addr: Address, ) -> Result { - let domain = self.normalize_name(domain)?; - let node = namehash(&domain); + let node = self.normalize_name(node)?; + let node = namehash(&node); let resolver_addr = self.registry.resolver(node).await?; let resolver = PublicResolver::new(self.web3.eth(), resolver_addr); - resolver.set_ethereum_address(from, node, address).await + resolver.set_ethereum_address(from, node, addr).await } - /// Returns the Blockchain address associated with the provided node and coinType, or 0 if none. - pub async fn blockchain_address(&self, domain: &str, coin_type: U256) -> Result, ContractError> { - let domain = self.normalize_name(domain)?; - let node = namehash(&domain); + /// Returns the Blockchain address associated with the provided ```node``` and ```coin_type```, or 0 if none. + /// + /// This function has interface ID *0xf1cb7e06*. + /// + /// This function is specified in [EIP 2304](https://eips.ethereum.org/EIPS/eip-2304). + /// + /// The return value is the cryptocurency address in its native binary format and each blockchain address has a different encoding and decoding method. + /// + /// For example, the Bitcoin address ```1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa``` base58check decodes to the 21 bytes ```0062e907b15cbf27d5425399ebf6f0fb50ebb88f18``` then scriptPubkey encodes to 25 bytes ```76a91462e907b15cbf27d5425399ebf6f0fb50ebb88f1888ac``` whereas the BNB address ```bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2``` Bech32 decodes to the binary representation ```40c2979694bbc961023d1d27be6fc4d21a9febe6```. + /// + /// A zero-length string will be returned if the specified coin ID does not exist on the specified node. + pub async fn blockchain_address(&self, node: &str, coin_type: U256) -> Result, ContractError> { + let node = self.normalize_name(node)?; + let node = namehash(&node); let resolver_addr = self.registry.resolver(node).await?; let resolver = PublicResolver::new(self.web3.eth(), resolver_addr); if !resolver.check_interface_support(*BLOCKCHAIN_ADDR_INTERFACE_ID).await? { - return Err(ContractError::Abi(EthError::InvalidData)); + return Err(ContractError::InterfaceUnsupported); } resolver.blockchain_address(node, coin_type).await } - /// Sets the blockchain address associated with the provided node and coinType to addr. + /// Sets the blockchain address associated with the provided ```node``` and ```coin_type``` to ```addr```. + /// + /// ```coinType``` is the cryptocurrency coin type index from [SLIP44](https://github.com/satoshilabs/slips/blob/master/slip-0044.md). + /// + /// Only callable by the owner of ```node```. + /// + /// Emits the following event: + /// ```solidity + /// event AddressChanged(bytes32 indexed node, uint coinType, bytes newAddress); + /// ``` pub async fn set_blockchain_address( &self, from: Address, - domain: &str, + node: &str, coin_type: U256, - a: Vec, + addr: Vec, ) -> Result { - let domain = self.normalize_name(domain)?; - let node = namehash(&domain); + let node = self.normalize_name(node)?; + let node = namehash(&node); let resolver_addr = self.registry.resolver(node).await?; let resolver = PublicResolver::new(self.web3.eth(), resolver_addr); - resolver.set_blockchain_address(from, node, coin_type, a).await + resolver.set_blockchain_address(from, node, coin_type, addr).await } - /// Returns the X and Y coordinates of the curve point for the public key. - pub async fn pubkey(&self, domain: &str) -> Result<([u8; 32], [u8; 32]), ContractError> { - let domain = self.normalize_name(domain)?; - let node = namehash(&domain); + /// Returns the ECDSA SECP256k1 public key for ```node```, as a 2-tuple ```(x, y)```. + /// If no public key is set, ```(0, 0)``` is returned. + /// + /// This function has interface ID *0xc8690233*. + /// + /// This function is specified in [EIP 619](https://github.com/ethereum/EIPs/issues/619). + pub async fn pubkey(&self, node: &str) -> Result<([u8; 32], [u8; 32]), ContractError> { + let node = self.normalize_name(node)?; + let node = namehash(&node); let resolver_addr = self.registry.resolver(node).await?; let resolver = PublicResolver::new(self.web3.eth(), resolver_addr); if !resolver.check_interface_support(*PUBKEY_INTERFACE_ID).await? { - return Err(ContractError::Abi(EthError::InvalidData)); + return Err(ContractError::InterfaceUnsupported); } resolver.public_key(node).await } - /// Sets the SECP256k1 public key associated with an ENS node. + /// Sets the ECDSA SECP256k1 public key for ```node``` to ```(x, y)```. + /// + /// Only callable by the owner of node. + /// + /// Emits the following event: + /// ```solidity + /// event PubkeyChanged(bytes32 indexed node, bytes32 x, bytes32 y); + /// ``` pub async fn set_pubkey( &self, from: Address, - domain: &str, + node: &str, x: [u8; 32], y: [u8; 32], ) -> Result { - let domain = self.normalize_name(domain)?; - let node = namehash(&domain); + let node = self.normalize_name(node)?; + let node = namehash(&node); let resolver_addr = self.registry.resolver(node).await?; let resolver = PublicResolver::new(self.web3.eth(), resolver_addr); @@ -311,36 +396,53 @@ impl Ens { resolver.set_public_key(from, node, x, y).await } - /// Returns the content hash object associated with an ENS node. - pub async fn content_hash(&self, domain: &str) -> Result, ContractError> { - let domain = self.normalize_name(domain)?; - let node = namehash(&domain); + /// Returns the content hash for ```node```, if one exists. + /// + /// Values are formatted as machine-readable [multicodecs](https://github.com/multiformats/multicodec), as specified in [EIP 1577](https://eips.ethereum.org/EIPS/eip-1577). + /// + /// ```content_hash``` is used to store IPFS and Swarm content hashes, which permit resolving ENS addresses to distributed content (eg, websites) hosted on these distributed networks. + /// + /// This function has interface ID *0xbc1c58d1*. + /// + /// This function is specified in [EIP 1577](https://eips.ethereum.org/EIPS/eip-1157). + pub async fn content_hash(&self, node: &str) -> Result, ContractError> { + let node = self.normalize_name(node)?; + let node = namehash(&node); let resolver_addr = self.registry.resolver(node).await?; let resolver = PublicResolver::new(self.web3.eth(), resolver_addr); if !resolver.check_interface_support(*CONTENTHASH_INTERFACE_ID).await? { - return Err(ContractError::Abi(EthError::InvalidData)); + return Err(ContractError::InterfaceUnsupported); } resolver.content_hash(node).await } - /// Sets the content hash associated with an ENS node. + /// Sets the content hash for the provided ```node``` to ```hash```. + /// + /// Only callable by the owner of ```node```. + /// + /// Values are formatted as machine-readable [multicodecs](https://github.com/multiformats/multicodec), as specified in [EIP 1577](https://eips.ethereum.org/EIPS/eip-1577). + /// + /// Emits the following event: + /// ```solidity + /// event ContenthashChanged(bytes32 indexed node, bytes hash); + /// ``` pub async fn set_content_hash( &self, from: Address, - domain: &str, + node: &str, hash: Vec, ) -> Result { - let domain = self.normalize_name(domain)?; - let node = namehash(&domain); + let node = self.normalize_name(node)?; + let node = namehash(&node); let resolver_addr = self.registry.resolver(node).await?; let resolver = PublicResolver::new(self.web3.eth(), resolver_addr); if !resolver.check_interface_support(*CONTENTHASH_INTERFACE_ID).await? { - return Err(ContractError::Abi(EthError::InvalidData)); + return Err(ContractError::InterfaceUnsupported); } // https://eips.ethereum.org/EIPS/eip-1577 @@ -351,45 +453,84 @@ impl Ens { resolver.set_content_hash(from, node, hash).await } - /// Returns the text record for a given key for the current ENS name. - pub async fn text(&self, domain: &str, key: String) -> Result { - let domain = self.normalize_name(domain)?; - let node = namehash(&domain); + /// Retrieves text metadata for ```node```. + /// Each name may have multiple pieces of metadata, identified by a unique string key. + /// If no text data exists for ```node``` with the key ```key```, the empty string is returned. + /// + /// Standard values for ```key``` are: + /// + /// | key | Meaning | + /// |-------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------| + /// | email | An email address | + /// | url | A URL | + /// | avatar | A URL to an image used as an avatar or logo | + /// | description | A description of the name | + /// | notice | A notice regarding this name | + /// | keywords | A list of comma-separated keywords, ordered by most significant first; clients that interpresent this field may choose a threshold beyond which to ignore | + /// + /// In addition, anyone may specify vendor-specific keys, which must be prefixed with ```vnd.```. The following vendor-specific keys are currently known: + /// + /// | key | Meaning | + /// |-------------|-----------------| + /// | com.twitter | Twitter handle | | + /// | com.github | Github username | + /// + /// This function has interface ID *0x59d1d43c*. + /// + /// This function is specified in [EIP 634](). + pub async fn text(&self, node: &str, key: String) -> Result { + let node = self.normalize_name(node)?; + let node = namehash(&node); let resolver_addr = self.registry.resolver(node).await?; let resolver = PublicResolver::new(self.web3.eth(), resolver_addr); if !resolver.check_interface_support(*TEXT_INTERFACE_ID).await? { - return Err(ContractError::Abi(EthError::InvalidData)); + return Err(ContractError::InterfaceUnsupported); } resolver.text_data(node, key).await } - /// Sets the text record for a given key for the current ENS name. + /// Sets text metadata for ```node``` with the unique key ```key``` to ```value```, overwriting anything previously stored for ```node``` and ```key```. + /// To clear a text field, set it to the empty string. + /// + /// Only callable by the owner of ```node```. + /// + /// Emits the following event: + /// ```solidity + /// event TextChanged(bytes32 indexed node, string indexedKey, string key); + /// ``` pub async fn set_text( &self, from: Address, - domain: &str, + node: &str, key: String, value: String, ) -> Result { - let domain = self.normalize_name(domain)?; - let node = namehash(&domain); + let node = self.normalize_name(node)?; + let node = namehash(&node); let resolver_addr = self.registry.resolver(node).await?; let resolver = PublicResolver::new(self.web3.eth(), resolver_addr); if !resolver.check_interface_support(*TEXT_INTERFACE_ID).await? { - return Err(ContractError::Abi(EthError::InvalidData)); + return Err(ContractError::InterfaceUnsupported); } resolver.set_text_data(from, node, key, value).await } - /// Returns the reverse record for a particular Ethereum address. - pub async fn canonical_name(&self, from: Address) -> Result { - let mut hex: String = from.encode_hex(); + /*** Reverse Resolver Functions Below ***/ + + /// Returns the canonical ENS name associated with the provided ```addr```. + /// Used exclusively for reverse resolution. + /// + /// This function has interface ID *0x691f3431*. + /// + /// This function is specified in [EIP 181](https://eips.ethereum.org/EIPS/eip-181). + pub async fn canonical_name(&self, addr: Address) -> Result { + let mut hex: String = addr.encode_hex(); hex.push_str(".addr.reverse"); let node = namehash(&hex); @@ -399,28 +540,35 @@ impl Ens { // The reverse resolver does not support checking interfaces yet. /* if !resolver.check_interface_support(*NAME_INTERFACE_ID).await? { - return Err(ContractError::Abi(EthError::InvalidData)); + return Err(ContractError::Abi(EthError::Other("Interface Unsupported".into()))); } */ resolver.canonical_name(node).await } - /// Sets the reverse record for the current Ethereum address + /// Sets the canonical ENS name for the provided ```node``` to ```name```. + /// + /// Only callable by the owner of ```node```. + /// + /// Emits the following event: + /// ```solidity + /// event NameChanged(bytes32 indexed node, string name); + /// ``` pub async fn set_canonical_name( &self, from: Address, - domain: &str, + node: &str, name: String, ) -> Result { - let domain = self.normalize_name(domain)?; - let node = namehash(&domain); + let node = self.normalize_name(node)?; + let node = namehash(&node); let resolver_addr = self.registry.resolver(node).await?; let resolver = ReverseResolver::new(self.web3.eth(), resolver_addr); // The reverse resolver does not support checking interfaces yet. /* if !resolver.check_interface_support(*NAME_INTERFACE_ID).await? { - return Err(ContractError::Abi(EthError::InvalidData)); + return Err(ContractError::Abi(EthError::Other("Interface Unsupported".into()))); } */ resolver.set_canonical_name(from, node, name).await diff --git a/src/contract/ens/mod.rs b/src/contract/ens/mod.rs index a4d7b31c..5a7c4d7a 100644 --- a/src/contract/ens/mod.rs +++ b/src/contract/ens/mod.rs @@ -1,4 +1,25 @@ -//! Ethereum Name Service +//! Ethereum Name Service Interface +//! +//! This interface provides most functions implemented in ENS. +//! With it you can resolve ethereum addresses to domain names, domain name to blockchain adresses and more! +//! +//! # Example +//! ```no_run +//! ##[tokio::main] +//! async fn main() -> web3::Result<()> { +//! use crate::web3::api::Namespace; +//! +//! let transport = web3::transports::Http::new("http://localhost:8545")?; +//! +//! let ens = web3::contract::ens::Ens::new(transport); +//! +//! let address = ens.eth_address("vitalik.eth").await.unwrap(); +//! +//! println!("Address: {:?}", address); +//! +//! Ok(()) +//! } +//! ``` mod eth_ens; pub mod public_resolver; diff --git a/src/contract/ens/public_resolver.rs b/src/contract/ens/public_resolver.rs index d0a76bd4..6d67ada1 100644 --- a/src/contract/ens/public_resolver.rs +++ b/src/contract/ens/public_resolver.rs @@ -10,9 +10,25 @@ use crate::{ type ContractError = crate::contract::Error; -/// Public Resolver contract interface. +/// [`PublicResolver`] implements a general-purpose ENS resolver that is suitable for most standard ENS use-cases. /// -/// [Specification](https://github.com/ensdomains/resolvers/blob/master/contracts/Resolver.sol) +/// The public resolver permits updates to ENS records by the owner of the corresponding name. +/// +/// The public resolver implements the following EIPs: +/// - [EIP 137](https://eips.ethereum.org/EIPS/eip-137) Contract address interface. +/// - [EIP 165](https://eips.ethereum.org/EIPS/eip-165) Interface Detection. +/// - [EIP 181](https://eips.ethereum.org/EIPS/eip-181) - Reverse resolution. +/// - [EIP 205](https://eips.ethereum.org/EIPS/eip-205) - ABI support. +/// - [EIP 619](https://github.com/ethereum/EIPs/pull/619) - SECP256k1 public keys. +/// - [EIP 634](https://eips.ethereum.org/EIPS/eip-634) - Text records. +/// - [EIP 1577](https://eips.ethereum.org/EIPS/eip-1577) - Content hash support. +/// - [EIP 2304](https://eips.ethereum.org/EIPS/eip-2304) - Multicoin support. +/// +/// While the [`PublicResolver`] provides a convenient default implementation, many resolver implementations and versions exist. +/// Callers must not assume that a domain uses the current version of the public resolver, or that all of the methods described here are present. +/// To check if a resolver supports a feature, see [`check_interface_support`](#method.check_interface_support). +/// +/// [Source](https://github.com/ensdomains/resolvers/blob/master/contracts/Resolver.sol) #[derive(Debug, Clone)] pub struct PublicResolver { contract: Contract, @@ -184,6 +200,7 @@ impl PublicResolver { } /// Returns the ECDSA SECP256k1 public key for node, as a 2-tuple (x, y). + /// If no public key is set, (0, 0) is returned. /// /// [Specification](https://docs.ens.domains/contract-api-reference/publicresolver#get-public-key) pub async fn public_key(&self, node: NameHash) -> Result<([u8; 32], [u8; 32]), ContractError> { diff --git a/src/contract/ens/registry.rs b/src/contract/ens/registry.rs index 32605b8d..4c5ae48b 100644 --- a/src/contract/ens/registry.rs +++ b/src/contract/ens/registry.rs @@ -12,9 +12,14 @@ type ContractError = crate::contract::Error; const ENS_REGISTRY_ADDRESS: &str = "00000000000C2E074eC69A0dFb2997BA6C7d2e1e"; -/// Registry contract interface. +/// The ENS registry is the core contract that lies at the heart of ENS resolution. /// -/// [Specification](https://github.com/ensdomains/ens/blob/master/contracts/ENS.sol) +/// All ENS lookups start by querying the registry. +/// The registry maintains a list of domains, recording the owner, resolver, and TTL for each, and allows the owner of a domain to make changes to that data. +/// +/// The ENS registry is specified in [EIP 137](https://eips.ethereum.org/EIPS/eip-137). +/// +/// [Source](https://github.com/ensdomains/ens/blob/master/contracts/ENS.sol) #[derive(Debug, Clone)] pub struct Registry { contract: Contract, diff --git a/src/contract/ens/reverse_resolver.rs b/src/contract/ens/reverse_resolver.rs index c7566c9e..567995f6 100644 --- a/src/contract/ens/reverse_resolver.rs +++ b/src/contract/ens/reverse_resolver.rs @@ -10,9 +10,16 @@ use crate::{ type ContractError = crate::contract::Error; -/// Reverse Resolver contract interface +/// Reverse resolution in ENS - the process of mapping from an Ethereum address (eg, 0x1234...) to an ENS name - is handled using a special namespace, *.addr.reverse*. +/// A special-purpose registrar controls this namespace and allocates subdomains to any caller based on their address. /// -/// [Specification](https://github.com/ensdomains/resolvers/blob/master/contracts/DefaultReverseResolver.sol) +/// For example, the account *0x314159265dd8dbb310642f98f50c066173c1259b* can claim *314159265dd8dbb310642f98f50c066173c1259b.addr.reverse*. After doing so, it can configure a resolver and expose metadata, such as a canonical ENS name for this address. +/// +/// The reverse registrar provides functions to claim a reverse record, as well as a convenience function to configure the record as it's most commonly used, as a way of specifying a canonical name for an address. +/// +/// The reverse registrar is specified in [EIP 181](https://eips.ethereum.org/EIPS/eip-181). +/// +/// [Source](https://github.com/ensdomains/ens/blob/master/contracts/ReverseRegistrar.sol) #[derive(Debug, Clone)] pub struct ReverseResolver { contract: Contract, diff --git a/src/contract/error.rs b/src/contract/error.rs index f1858b47..c1229d11 100644 --- a/src/contract/error.rs +++ b/src/contract/error.rs @@ -19,6 +19,8 @@ pub enum Error { /// An error during deployment. #[display(fmt = "Deployment error: {}", _0)] Deployment(crate::contract::deploy::Error), + /// Contract does not support this interface. + InterfaceUnsupported, } impl std::error::Error for Error { @@ -28,6 +30,7 @@ impl std::error::Error for Error { Error::Abi(ref e) => Some(e), Error::Api(ref e) => Some(e), Error::Deployment(ref e) => Some(e), + Error::InterfaceUnsupported => None, } } }