Example #1
0
    def _ens_lookup(self, web3: Optional[Web3],
                    name: str) -> Optional[ChecksumEthAddress]:
        """Performs an ENS lookup and returns address if found else None

        May raise:
        - RemoteError if Etherscan is used and there is a problem querying it or
        parsing its response
        """
        if web3 is not None:
            return web3.ens.resolve(name)

        # else we gotta manually query contracts via etherscan
        normal_name = normalize_name(name)
        resolver_addr = self._call_contract_etherscan(
            ENS_MAINNET_ADDR,
            abi=ENS_ABI,
            method_name='resolver',
            arguments=[normal_name_to_hash(normal_name)],
        )
        if is_none_or_zero_address(resolver_addr):
            return None
        address = self._call_contract_etherscan(
            to_checksum_address(resolver_addr),
            abi=ENS_RESOLVER_ABI,
            method_name='addr',
            arguments=[normal_name_to_hash(normal_name)],
        )

        if is_none_or_zero_address(address):
            return None
        return to_checksum_address(address)
Example #2
0
    def _ens_lookup(
            self,
            web3: Optional[Web3],
            name: str,
            blockchain: SupportedBlockchain = SupportedBlockchain.ETHEREUM,
    ) -> Optional[Union[ChecksumEthAddress, HexStr]]:
        """Performs an ENS lookup and returns address if found else None

        TODO: currently web3.py 5.15.0 does not support multichain ENS domains
        (EIP-2304), therefore requesting a non-Ethereum address won't use the
        web3 ens library and will require to extend the library resolver ABI.
        An issue in their repo (#1839) reporting the lack of support has been
        created. This function will require refactoring once they include
        support for EIP-2304.
        https://github.com/ethereum/web3.py/issues/1839

        May raise:
        - RemoteError if Etherscan is used and there is a problem querying it or
        parsing its response
        """
        normal_name = normalize_name(name)
        resolver_addr = self._call_contract(
            web3=web3,
            contract_address=ENS_MAINNET_ADDR,
            abi=ENS_ABI,
            method_name='resolver',
            arguments=[normal_name_to_hash(normal_name)],
        )
        if is_none_or_zero_address(resolver_addr):
            return None

        ens_resolver_abi = ENS_RESOLVER_ABI.copy()
        arguments = [normal_name_to_hash(normal_name)]
        if blockchain != SupportedBlockchain.ETHEREUM:
            ens_resolver_abi.extend(ENS_RESOLVER_ABI_MULTICHAIN_ADDRESS)
            arguments.append(blockchain.ens_coin_type())

        address = self._call_contract(
            web3=web3,
            contract_address=to_checksum_address(resolver_addr),
            abi=ens_resolver_abi,
            method_name='addr',
            arguments=arguments,
        )
        if is_none_or_zero_address(address):
            return None

        if blockchain != SupportedBlockchain.ETHEREUM:
            return HexStr(address.hex())

        return to_checksum_address(address)
Example #3
0
    def setup_name(
        self,
        name: str,
        address: Optional[ChecksumAddress] = None,
        transact: Optional["TxParams"] = None
    ) -> HexBytes:
        """
        Set up the address for reverse lookup, aka "caller ID".
        After successful setup, the method :meth:`~ens.main.ENS.name` will return
        `name` when supplied with `address`.

        :param str name: ENS name that address will point to
        :param str address: to set up, in checksum format
        :param dict transact: the transaction configuration, like in
            :meth:`~web3.eth.send_transaction`
        :raises AddressMismatch: if the name does not already point to the address
        :raises InvalidName: if `name` has invalid syntax
        :raises UnauthorizedError: if ``'from'`` in `transact` does not own `name`
        :raises UnownedName: if no one owns `name`
        """
        if not transact:
            transact = {}
        transact = deepcopy(transact)
        if not name:
            self._assert_control(address, 'the reverse record')
            return self._setup_reverse(None, address, transact=transact)
        else:
            resolved = self.address(name)
            if is_none_or_zero_address(address):
                address = resolved
            elif resolved and address != resolved and resolved != EMPTY_ADDR_HEX:
                raise AddressMismatch(
                    f"Could not set address {address!r} to point to name, "
                    f"because the name resolves to {resolved!r}. "
                    "To change the name for an existing address, call "
                    "setup_address() first."
                )
            if is_none_or_zero_address(address):
                address = self.owner(name)
            if is_none_or_zero_address(address):
                raise UnownedName("claim subdomain using setup_address() first")
            if is_binary_address(address):
                address = to_checksum_address(address)
            if not is_checksum_address(address):
                raise ValueError("You must supply the address in checksum format")
            self._assert_control(address, name)
            if not resolved:
                self.setup_address(name, address, transact=transact)
            return self._setup_reverse(name, address, transact=transact)
Example #4
0
    def _first_owner(self, name: str) -> Tuple[Optional[ChecksumAddress], Sequence[str], str]:
        """
        Takes a name, and returns the owner of the deepest subdomain that has an owner

        :returns: (owner or None, list(unowned_subdomain_labels), first_owned_domain)
        """
        owner = None
        unowned = []
        pieces = normalize_name(name).split('.')
        while pieces and is_none_or_zero_address(owner):
            name = '.'.join(pieces)
            owner = self.owner(name)
            if is_none_or_zero_address(owner):
                unowned.append(pieces.pop(0))
        return (owner, unowned, name)
Example #5
0
    def _resolve(self, name: str, fn_name: str = 'addr') -> Optional[Union[ChecksumAddress, str]]:
        normal_name = normalize_name(name)

        resolver, current_name = self._get_resolver(normal_name, fn_name)
        if not resolver:
            return None

        node = self.namehash(normal_name)

        # handle extended resolver case
        if _resolver_supports_interface(resolver, EXTENDED_RESOLVER_INTERFACE_ID):
            contract_func_with_args = (fn_name, [node])

            calldata = resolver.encodeABI(*contract_func_with_args)
            contract_call_result = resolver.caller.resolve(
                ens_encode_name(normal_name), calldata
            )
            result = self._decode_ensip10_resolve_data(
                contract_call_result, resolver, fn_name
            )
            return to_checksum_address(result) if is_address(result) else result
        elif normal_name == current_name:
            lookup_function = getattr(resolver.functions, fn_name)
            result = lookup_function(node).call()
            if is_none_or_zero_address(result):
                return None
            return to_checksum_address(result) if is_address(result) else result
        return None
Example #6
0
    def setup_address(self, name, address=default, transact={}):
        """
        Set up the name to point to the supplied address.
        The sender of the transaction must own the name, or
        its parent name.

        Example: If the caller owns ``parentname.eth`` with no subdomains
        and calls this method with ``sub.parentname.eth``,
        then ``sub`` will be created as part of this call.

        :param str name: ENS name to set up
        :param str address: name will point to this address, in checksum format. If ``None``,
            erase the record. If not specified, name will point to the owner's address.
        :param dict transact: the transaction configuration, like in
            :meth:`~web3.eth.Eth.sendTransaction`
        :raises InvalidName: if ``name`` has invalid syntax
        :raises UnauthorizedError: if ``'from'`` in `transact` does not own `name`
        """
        owner = self.setup_owner(name, transact=transact)
        self._assert_control(owner, name)
        if is_none_or_zero_address(address):
            address = None
        elif address is default:
            address = owner
        elif is_binary_address(address):
            address = to_checksum_address(address)
        elif not is_checksum_address(address):
            raise ValueError("You must supply the address in checksum format")
        if self.address(name) == address:
            return None
        if address is None:
            address = EMPTY_ADDR_HEX
        transact['from'] = owner
        resolver = self._set_resolver(name, transact=transact)
        return resolver.functions.setAddr(raw_name_to_hash(name), address).transact(transact)
Example #7
0
 def _set_resolver(self, name, resolver_addr=None, transact={}):
     if is_none_or_zero_address(resolver_addr):
         resolver_addr = self.address('resolver.eth')
     namehash = raw_name_to_hash(name)
     if self.ens.caller.resolver(namehash) != resolver_addr:
         self.ens.functions.setResolver(
             namehash,
             resolver_addr
         ).transact(transact)
     return self._resolverContract(address=resolver_addr)
Example #8
0
 def _set_resolver(self,
                   name: str,
                   resolver_addr: ChecksumAddress = None,
                   transact: "TxParams" = {}) -> 'Contract':
     if is_none_or_zero_address(resolver_addr):
         resolver_addr = self.address('resolver.eth')
     namehash = raw_name_to_hash(name)
     if self.ens.caller.resolver(namehash) != resolver_addr:
         self.ens.functions.setResolver(namehash,
                                        resolver_addr).transact(transact)
     return self._resolverContract(address=resolver_addr)
Example #9
0
 def resolve(self, name: str, get: str='addr') -> Optional[Union[ChecksumAddress, str]]:
     normal_name = normalize_name(name)
     resolver = self.resolver(normal_name)
     if resolver:
         lookup_function = getattr(resolver.functions, get)
         namehash = normal_name_to_hash(normal_name)
         address = lookup_function(namehash).call()
         if is_none_or_zero_address(address):
             return None
         return lookup_function(namehash).call()
     else:
         return None
Example #10
0
 def resolve(self, name, get='addr'):
     normal_name = normalize_name(name)
     resolver = self.resolver(normal_name)
     if resolver:
         lookup_function = getattr(resolver.functions, get)
         namehash = normal_name_to_hash(normal_name)
         address = lookup_function(namehash).call()
         if is_none_or_zero_address(address):
             return None
         return lookup_function(namehash).call()
     else:
         return None
Example #11
0
    def ens_reverse_lookup(self, reversed_addresses: List[ChecksumEthAddress]) -> Dict[ChecksumEthAddress, Optional[str]]:  # noqa: E501
        """Performs a reverse ENS lookup on a list of addresses

        Because a multicall is used, no exceptions are raised.
        If any exceptions occur, they are logged and None is returned for that
        """
        human_names: Dict[ChecksumEthAddress, Optional[str]] = {}
        # Querying resolvers' addresses
        resolver_params = [
            EnsContractParams(address=addr, abi=ENS_ABI, method_name='resolver', arguments=_prepare_ens_call_arguments(addr))  # noqa: E501
            for addr in reversed_addresses
        ]
        resolvers_output = multicall(
            ethereum=self,
            calls=[(ENS_MAINNET_ADDR, _encode_ens_contract(params=params)) for params in resolver_params],  # noqa: E501
        )
        resolvers = []
        # We need a new list for reversed_addresses because not all addresses have resolver
        filtered_reversed_addresses = []
        # Processing resolvers query output
        for reversed_addr, params, resolver_output in zip(reversed_addresses, resolver_params, resolvers_output):  # noqa: E501
            decoded_resolver = _decode_ens_contract(params=params, result_encoded=resolver_output)
            if is_none_or_zero_address(decoded_resolver):
                human_names[reversed_addr] = None
                continue
            try:
                deserialized_resolver = deserialize_ethereum_address(decoded_resolver)
            except DeserializationError:
                log.error(
                    f'Error deserializing address {decoded_resolver} while doing reverse ens lookup',  # noqa: E501
                )
                human_names[reversed_addr] = None
                continue
            resolvers.append(deserialized_resolver)
            filtered_reversed_addresses.append(reversed_addr)

        # Querying human names
        human_names_params = [
            EnsContractParams(address=resolver, abi=ENS_RESOLVER_ABI, method_name='name', arguments=_prepare_ens_call_arguments(addr))  # noqa: E501
            for addr, resolver in zip(filtered_reversed_addresses, resolvers)]
        human_names_output = multicall(
            ethereum=self,
            calls=[(params.address, _encode_ens_contract(params=params)) for params in human_names_params],  # noqa: E501
        )

        # Processing human names query output
        for addr, params, human_name_output in zip(filtered_reversed_addresses, human_names_params, human_names_output):  # noqa: E501
            human_names[addr] = _decode_ens_contract(params=params, result_encoded=human_name_output)  # noqa: E501

        return human_names
Example #12
0
 def _set_resolver(
     self,
     name: str,
     resolver_addr: Optional[ChecksumAddress] = None,
     transact: Optional["TxParams"] = None
 ) -> Union['Contract', 'AsyncContract']:
     if not transact:
         transact = {}
     transact = deepcopy(transact)
     if is_none_or_zero_address(resolver_addr):
         resolver_addr = self.address('resolver.eth')
     namehash = raw_name_to_hash(name)
     if self.ens.caller.resolver(namehash) != resolver_addr:
         self.ens.functions.setResolver(
             namehash,
             resolver_addr
         ).transact(transact)
     return self._resolver_contract(address=resolver_addr)
Example #13
0
    def _get_resolver(
        self,
        normal_name: str,
        fn_name: str = 'addr'
    ) -> Tuple[Optional[Union['Contract', 'AsyncContract']], str]:
        current_name = normal_name

        # look for a resolver, starting at the full name and taking the parent each time that no
        # resolver is found
        while True:
            if is_empty_name(current_name):
                # if no resolver found across all iterations, current_name will eventually be the
                # empty string '' which returns here
                return None, current_name

            resolver_addr = self.ens.caller.resolver(normal_name_to_hash(current_name))
            if not is_none_or_zero_address(resolver_addr):
                # if resolver found, return it
                return self._type_aware_resolver(resolver_addr, fn_name), current_name

            # set current_name to parent and try again
            current_name = self.parent(current_name)
Example #14
0
 def resolver(self, normal_name: str) -> Optional['Contract']:
     resolver_addr = self.ens.caller.resolver(
         normal_name_to_hash(normal_name))
     if is_none_or_zero_address(resolver_addr):
         return None
     return self._resolverContract(address=resolver_addr)