Repository: ethereum/web3.py

Issue:
Stop inferring .eth on name lookups without a TLD
### What was wrong?

The `ens.address(name)` method appends '.eth' if no recognized TLD is present. This is handy at the command line, but problematic in web3, where ambiguity is created, because we want to support all possible TLDs out of the gate.

### How can it be fixed?

* Change signature to `ens.address(name, guess_tld=True)`
* Set `guess_tld` to `False` in all web3 usages
* Confirm that ENS.py can resolve any TLD when `guess_tld=False`


Discussion:

Resolving patch:
diff --git a/ens/main.py b/ens/main.py
--- a/ens/main.py
+++ b/ens/main.py
@@ -27,6 +27,7 @@
     init_web3,
     is_valid_name,
     label_to_hash,
+    name_to_hash,
     normalize_name,
 )
 
@@ -75,14 +76,19 @@ def fromWeb3(cls, web3, addr=None):
         '''
         return cls(web3.manager.providers, addr=addr)
 
-    def address(self, name):
+    def address(self, name, guess_tld=True):
         '''
         Look up the Ethereum address that `name` currently points to.
 
         :param str name: an ENS name to look up
+        :param bool guess_tld: should `name` be appended with '.eth' if no common TLD found?
         :raises InvalidName: if `name` has invalid syntax
         '''
-        return self.resolve(name, 'addr')
+        if guess_tld:
+            expanded = dot_eth_name(name)
+        else:
+            expanded = name
+        return self.resolve(expanded, 'addr')
 
     def name(self, address):
         '''
@@ -173,18 +179,20 @@ def setup_name(self, name, address=None, transact={}):
             return self._setup_reverse(name, address, transact=transact)
 
     def resolve(self, name, get='addr'):
-        resolver = self.resolver(name)
+        normal_name = normalize_name(name)
+        resolver = self.resolver(normal_name)
         if resolver:
             lookup_function = getattr(resolver, get)
-            resolved = lookup_function(dot_eth_namehash(name))
+            namehash = name_to_hash(normal_name)
+            resolved = lookup_function(namehash)
             if is_address(resolved):
                 resolved = to_checksum_address(resolved)
             return resolved
         else:
             return None
 
-    def resolver(self, name):
-        resolver_addr = self.ens.resolver(dot_eth_namehash(name))
+    def resolver(self, normal_name):
+        resolver_addr = self.ens.resolver(name_to_hash(normal_name))
         if not resolver_addr:
             return None
         return self._resolverContract(address=resolver_addr)
@@ -308,5 +316,5 @@ def _setup_reverse(self, name, address, transact={}):
         return self._reverse_registrar().setName(name, transact=transact)
 
     def _reverse_registrar(self):
-        addr = self.ens.owner(dot_eth_namehash(REVERSE_REGISTRAR_DOMAIN))
+        addr = self.ens.owner(name_to_hash(REVERSE_REGISTRAR_DOMAIN))
         return self.web3.eth.contract(address=addr, abi=abis.REVERSE_REGISTRAR)
diff --git a/web3/utils/ens.py b/web3/utils/ens.py
--- a/web3/utils/ens.py
+++ b/web3/utils/ens.py
@@ -23,7 +23,7 @@ def is_ens_name(value):
 
 
 def validate_name_has_address(ens, name):
-    addr = ens.address(name)
+    addr = ens.address(name, guess_tld=False)
     if addr:
         return addr
     else:
@@ -34,5 +34,7 @@ class StaticENS:
     def __init__(self, name_addr_pairs):
         self.registry = dict(name_addr_pairs)
 
-    def address(self, name):
+    def address(self, name, guess_tld=True):
+        # no automated web3 usages should be guessing the TLD
+        assert not guess_tld
         return self.registry.get(name, None)


Test patch:
diff --git a/tests/ens/test_setup_address.py b/tests/ens/test_setup_address.py
--- a/tests/ens/test_setup_address.py
+++ b/tests/ens/test_setup_address.py
@@ -15,34 +15,71 @@
 
 
 @pytest.mark.parametrize(
-    'name, namehash_hex',
+    'name, full_name, namehash_hex',
     [
-        ('tester.eth', '0x2a7ac1c833d35677c2ff34a908951de142cc1653de6080ad4e38f4c9cc00aafe'),
-        ('tester', '0x2a7ac1c833d35677c2ff34a908951de142cc1653de6080ad4e38f4c9cc00aafe'),
-        ('TESTER', '0x2a7ac1c833d35677c2ff34a908951de142cc1653de6080ad4e38f4c9cc00aafe'),
+        (
+            'tester.eth',
+            'tester.eth',
+            '0x2a7ac1c833d35677c2ff34a908951de142cc1653de6080ad4e38f4c9cc00aafe',
+        ),
+        (
+            'tester',
+            'tester.eth',
+            '0x2a7ac1c833d35677c2ff34a908951de142cc1653de6080ad4e38f4c9cc00aafe',
+        ),
+        (
+            'TESTER',
+            'TESTER.eth',
+            '0x2a7ac1c833d35677c2ff34a908951de142cc1653de6080ad4e38f4c9cc00aafe',
+        ),
         # handles alternative dot separators
-        ('tester．eth', '0x2a7ac1c833d35677c2ff34a908951de142cc1653de6080ad4e38f4c9cc00aafe'),
-        ('tester。eth', '0x2a7ac1c833d35677c2ff34a908951de142cc1653de6080ad4e38f4c9cc00aafe'),
-        ('tester｡eth', '0x2a7ac1c833d35677c2ff34a908951de142cc1653de6080ad4e38f4c9cc00aafe'),
+        (
+            'tester．eth',
+            'tester．eth',
+            '0x2a7ac1c833d35677c2ff34a908951de142cc1653de6080ad4e38f4c9cc00aafe',
+        ),
+        (
+            'tester。eth',
+            'tester。eth',
+            '0x2a7ac1c833d35677c2ff34a908951de142cc1653de6080ad4e38f4c9cc00aafe',
+        ),
+        (
+            'tester｡eth',
+            'tester｡eth',
+            '0x2a7ac1c833d35677c2ff34a908951de142cc1653de6080ad4e38f4c9cc00aafe',
+        ),
         # confirm that set-owner works
         (
+            'lots.of.subdomains.tester.eth',
             'lots.of.subdomains.tester.eth',
             '0x0d62a759aa1f1c9680de8603a12a5eb175cd1bfa79426229868eba99f4dce692',
         ),
         (
             'lots.of.subdomains.tester',
+            'lots.of.subdomains.tester.eth',
             '0x0d62a759aa1f1c9680de8603a12a5eb175cd1bfa79426229868eba99f4dce692',
         ),
     ],
 )
-def test_set_address(ens, name, namehash_hex):
+def test_set_address(ens, name, full_name, namehash_hex):
     assert ens.address(name) is None
     owner = ens.owner('tester')
 
     ens.setup_address(name, TEST_ADDRESS)
     assert ens.address(name) == TEST_ADDRESS
+
+    # check that .eth is only appended if guess_tld is True
     namehash = Web3.toBytes(hexstr=namehash_hex)
-    assert ens.resolver(name).addr(namehash) == TEST_ADDRESS
+    normal_name = ens.nameprep(full_name)
+    if ens.nameprep(name) == normal_name:
+        assert ens.address(name, guess_tld=False) == TEST_ADDRESS
+    else:
+        assert ens.address(name, guess_tld=False) is None
+
+    # check that the correct namehash is set:
+    assert ens.resolver(normal_name).addr(namehash) == TEST_ADDRESS
+
+    # check that the correct owner is set:
     assert ens.owner(name) == owner
 
     ens.setup_address(name, None)
diff --git a/tests/ens/test_setup_name.py b/tests/ens/test_setup_name.py
--- a/tests/ens/test_setup_name.py
+++ b/tests/ens/test_setup_name.py
@@ -65,8 +65,16 @@ def test_setup_name(ens, name, normalized_name, namehash_hex):
 
     ens.setup_name(name, address)
     assert ens.name(address) == normalized_name
+
+    # check that .eth is only appended if guess_tld is True
+    if ens.nameprep(name) != normalized_name:
+        assert ens.address(name, guess_tld=False) is None
+
+    # check that the correct namehash is set:
     node = Web3.toBytes(hexstr=namehash_hex)
-    assert ens.resolver(name).addr(node) == address
+    assert ens.resolver(normalized_name).addr(node) == address
+
+    # check that the correct owner is set:
     assert ens.owner(name) == owner
 
     ens.setup_name(None, address)