Skip to content

Commit 7f6b8fc

Browse files
committed
Add more tests and some test performance improvements
* Add a lot more testing around AsyncENS. We can think about splitting AsyncEthereumTesterProvider tests off into their own CI job if they get too big but we should provide ample testing around the async implementation to make sure it's all wired up correctly. * Cleanup for better readability and consistency across ENS and AsyncENS * Keep async_ens_setup as module scope with a module-scoped event_loop fixture and add a module-scoped async_w3 for ens module
1 parent 2b9f3e1 commit 7f6b8fc

File tree

13 files changed

+650
-411
lines changed

13 files changed

+650
-411
lines changed

ens/async_ens.py

Lines changed: 160 additions & 158 deletions
Large diffs are not rendered by default.

ens/ens.py

Lines changed: 158 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,60 @@ def fromWeb3(cls, w3: 'Web3', addr: ChecksumAddress = None) -> 'ENS':
118118
middlewares = w3.middleware_onion.middlewares
119119
return cls(provider, addr=addr, middlewares=middlewares)
120120

121+
def address(self, name: str) -> Optional[ChecksumAddress]:
122+
"""
123+
Look up the Ethereum address that `name` currently points to.
124+
125+
:param str name: an ENS name to look up
126+
:raises InvalidName: if `name` has invalid syntax
127+
"""
128+
return cast(ChecksumAddress, self._resolve(name, 'addr'))
129+
130+
def setup_address(
131+
self,
132+
name: str,
133+
address: Union[Address, ChecksumAddress, HexAddress] = cast(ChecksumAddress, default),
134+
transact: Optional["TxParams"] = None,
135+
) -> Optional[HexBytes]:
136+
"""
137+
Set up the name to point to the supplied address.
138+
The sender of the transaction must own the name, or
139+
its parent name.
140+
141+
Example: If the caller owns ``parentname.eth`` with no subdomains
142+
and calls this method with ``sub.parentname.eth``,
143+
then ``sub`` will be created as part of this call.
144+
145+
:param str name: ENS name to set up
146+
:param str address: name will point to this address, in checksum format. If ``None``,
147+
erase the record. If not specified, name will point to the owner's address.
148+
:param dict transact: the transaction configuration, like in
149+
:meth:`~web3.eth.Eth.send_transaction`
150+
:raises InvalidName: if ``name`` has invalid syntax
151+
:raises UnauthorizedError: if ``'from'`` in `transact` does not own `name`
152+
"""
153+
if not transact:
154+
transact = {}
155+
transact = deepcopy(transact)
156+
owner = self.setup_owner(name, transact=transact)
157+
self._assert_control(owner, name)
158+
if is_none_or_zero_address(address):
159+
address = None
160+
elif address is default:
161+
address = owner
162+
elif is_binary_address(address):
163+
address = to_checksum_address(cast(str, address))
164+
elif not is_checksum_address(address):
165+
raise ValueError("You must supply the address in checksum format")
166+
if self.address(name) == address:
167+
return None
168+
if address is None:
169+
address = EMPTY_ADDR_HEX
170+
transact['from'] = owner
171+
172+
resolver: 'Contract' = self._set_resolver(name, transact=transact)
173+
return resolver.functions.setAddr(raw_name_to_hash(name), address).transact(transact)
174+
121175
def name(self, address: ChecksumAddress) -> Optional[str]:
122176
"""
123177
Look up the name that the address points to, using a
@@ -183,28 +237,68 @@ def setup_name(
183237
self.setup_address(name, address, transact=transact)
184238
return self._setup_reverse(name, address, transact=transact)
185239

186-
def reverser(self, target_address: ChecksumAddress) -> Optional['Contract']:
187-
reversed_domain = address_to_reverse_domain(target_address)
188-
return self.resolver(reversed_domain)
240+
def owner(self, name: str) -> ChecksumAddress:
241+
"""
242+
Get the owner of a name. Note that this may be different from the
243+
deed holder in the '.eth' registrar. Learn more about the difference
244+
between deed and name ownership in the ENS `Managing Ownership docs
245+
<http://docs.ens.domains/en/latest/userguide.html#managing-ownership>`_
189246
190-
def _set_resolver(
247+
:param str name: ENS name to look up
248+
:return: owner address
249+
:rtype: str
250+
"""
251+
node = raw_name_to_hash(name)
252+
return self.ens.caller.owner(node)
253+
254+
def setup_owner(
191255
self,
192256
name: str,
193-
resolver_addr: Optional[ChecksumAddress] = None,
257+
new_owner: ChecksumAddress = cast(ChecksumAddress, default),
194258
transact: Optional["TxParams"] = None,
195-
) -> 'Contract':
259+
) -> Optional[ChecksumAddress]:
260+
"""
261+
Set the owner of the supplied name to `new_owner`.
262+
263+
For typical scenarios, you'll never need to call this method directly,
264+
simply call :meth:`setup_name` or :meth:`setup_address`. This method does *not*
265+
set up the name to point to an address.
266+
267+
If `new_owner` is not supplied, then this will assume you
268+
want the same owner as the parent domain.
269+
270+
If the caller owns ``parentname.eth`` with no subdomains
271+
and calls this method with ``sub.parentname.eth``,
272+
then ``sub`` will be created as part of this call.
273+
274+
:param str name: ENS name to set up
275+
:param new_owner: account that will own `name`. If ``None``, set owner to empty addr.
276+
If not specified, name will point to the parent domain owner's address.
277+
:param dict transact: the transaction configuration, like in
278+
:meth:`~web3.eth.Eth.send_transaction`
279+
:raises InvalidName: if `name` has invalid syntax
280+
:raises UnauthorizedError: if ``'from'`` in `transact` does not own `name`
281+
:returns: the new owner's address
282+
"""
196283
if not transact:
197284
transact = {}
198285
transact = deepcopy(transact)
199-
if is_none_or_zero_address(resolver_addr):
200-
resolver_addr = self.address('resolver.eth')
201-
namehash = raw_name_to_hash(name)
202-
if self.ens.caller.resolver(namehash) != resolver_addr:
203-
self.ens.functions.setResolver(
204-
namehash,
205-
resolver_addr
206-
).transact(transact)
207-
return cast('Contract', self._resolver_contract(address=resolver_addr))
286+
(super_owner, unowned, owned) = self._first_owner(name)
287+
if new_owner is default:
288+
new_owner = super_owner
289+
elif not new_owner:
290+
new_owner = ChecksumAddress(EMPTY_ADDR_HEX)
291+
else:
292+
new_owner = to_checksum_address(new_owner)
293+
current_owner = self.owner(name)
294+
if new_owner == EMPTY_ADDR_HEX and not current_owner:
295+
return None
296+
elif current_owner == new_owner:
297+
return current_owner
298+
else:
299+
self._assert_control(super_owner, name, owned)
300+
self._claim_ownership(new_owner, unowned, owned, super_owner, transact=transact)
301+
return new_owner
208302

209303
def resolver(self, name: str) -> Optional['Contract']:
210304
"""
@@ -215,19 +309,9 @@ def resolver(self, name: str) -> Optional['Contract']:
215309
normal_name = normalize_name(name)
216310
return self._get_resolver(normal_name)[0]
217311

218-
def owner(self, name: str) -> ChecksumAddress:
219-
"""
220-
Get the owner of a name. Note that this may be different from the
221-
deed holder in the '.eth' registrar. Learn more about the difference
222-
between deed and name ownership in the ENS `Managing Ownership docs
223-
<http://docs.ens.domains/en/latest/userguide.html#managing-ownership>`_
224-
225-
:param str name: ENS name to look up
226-
:return: owner address
227-
:rtype: str
228-
"""
229-
node = raw_name_to_hash(name)
230-
return self.ens.caller.owner(node)
312+
def reverser(self, target_address: ChecksumAddress) -> Optional['Contract']:
313+
reversed_domain = address_to_reverse_domain(target_address)
314+
return self.resolver(reversed_domain)
231315

232316
def get_text(self, name: str, key: str) -> str:
233317
"""
@@ -300,100 +384,6 @@ def set_text(
300384
"unsupported top level domain (tld)."
301385
)
302386

303-
def setup_address(
304-
self,
305-
name: str,
306-
address: Union[Address, ChecksumAddress, HexAddress] = cast(ChecksumAddress, default),
307-
transact: Optional["TxParams"] = None,
308-
) -> Optional[HexBytes]:
309-
"""
310-
Set up the name to point to the supplied address.
311-
The sender of the transaction must own the name, or
312-
its parent name.
313-
314-
Example: If the caller owns ``parentname.eth`` with no subdomains
315-
and calls this method with ``sub.parentname.eth``,
316-
then ``sub`` will be created as part of this call.
317-
318-
:param str name: ENS name to set up
319-
:param str address: name will point to this address, in checksum format. If ``None``,
320-
erase the record. If not specified, name will point to the owner's address.
321-
:param dict transact: the transaction configuration, like in
322-
:meth:`~web3.eth.Eth.send_transaction`
323-
:raises InvalidName: if ``name`` has invalid syntax
324-
:raises UnauthorizedError: if ``'from'`` in `transact` does not own `name`
325-
"""
326-
if not transact:
327-
transact = {}
328-
transact = deepcopy(transact)
329-
owner = self.setup_owner(name, transact=transact)
330-
self._assert_control(owner, name)
331-
if is_none_or_zero_address(address):
332-
address = None
333-
elif address is default:
334-
address = owner
335-
elif is_binary_address(address):
336-
address = to_checksum_address(cast(str, address))
337-
elif not is_checksum_address(address):
338-
raise ValueError("You must supply the address in checksum format")
339-
if self.address(name) == address:
340-
return None
341-
if address is None:
342-
address = EMPTY_ADDR_HEX
343-
transact['from'] = owner
344-
345-
resolver: 'Contract' = self._set_resolver(name, transact=transact)
346-
return resolver.functions.setAddr(raw_name_to_hash(name), address).transact(transact)
347-
348-
def setup_owner(
349-
self,
350-
name: str,
351-
new_owner: ChecksumAddress = cast(ChecksumAddress, default),
352-
transact: Optional["TxParams"] = None,
353-
) -> Optional[ChecksumAddress]:
354-
"""
355-
Set the owner of the supplied name to `new_owner`.
356-
357-
For typical scenarios, you'll never need to call this method directly,
358-
simply call :meth:`setup_name` or :meth:`setup_address`. This method does *not*
359-
set up the name to point to an address.
360-
361-
If `new_owner` is not supplied, then this will assume you
362-
want the same owner as the parent domain.
363-
364-
If the caller owns ``parentname.eth`` with no subdomains
365-
and calls this method with ``sub.parentname.eth``,
366-
then ``sub`` will be created as part of this call.
367-
368-
:param str name: ENS name to set up
369-
:param new_owner: account that will own `name`. If ``None``, set owner to empty addr.
370-
If not specified, name will point to the parent domain owner's address.
371-
:param dict transact: the transaction configuration, like in
372-
:meth:`~web3.eth.Eth.send_transaction`
373-
:raises InvalidName: if `name` has invalid syntax
374-
:raises UnauthorizedError: if ``'from'`` in `transact` does not own `name`
375-
:returns: the new owner's address
376-
"""
377-
if not transact:
378-
transact = {}
379-
transact = deepcopy(transact)
380-
(super_owner, unowned, owned) = self._first_owner(name)
381-
if new_owner is default:
382-
new_owner = super_owner
383-
elif not new_owner:
384-
new_owner = ChecksumAddress(EMPTY_ADDR_HEX)
385-
else:
386-
new_owner = to_checksum_address(new_owner)
387-
current_owner = self.owner(name)
388-
if new_owner == EMPTY_ADDR_HEX and not current_owner:
389-
return None
390-
elif current_owner == new_owner:
391-
return current_owner
392-
else:
393-
self._assert_control(super_owner, name, owned)
394-
self._claim_ownership(new_owner, unowned, owned, super_owner, transact=transact)
395-
return new_owner
396-
397387
def _get_resolver(
398388
self,
399389
normal_name: str,
@@ -418,14 +408,24 @@ def _get_resolver(
418408
# set current_name to parent and try again
419409
current_name = self.parent(current_name)
420410

421-
def address(self, name: str) -> Optional[ChecksumAddress]:
422-
"""
423-
Look up the Ethereum address that `name` currently points to.
424-
425-
:param str name: an ENS name to look up
426-
:raises InvalidName: if `name` has invalid syntax
427-
"""
428-
return cast(ChecksumAddress, self._resolve(name, 'addr'))
411+
def _set_resolver(
412+
self,
413+
name: str,
414+
resolver_addr: Optional[ChecksumAddress] = None,
415+
transact: Optional["TxParams"] = None,
416+
) -> 'Contract':
417+
if not transact:
418+
transact = {}
419+
transact = deepcopy(transact)
420+
if is_none_or_zero_address(resolver_addr):
421+
resolver_addr = self.address('resolver.eth')
422+
namehash = raw_name_to_hash(name)
423+
if self.ens.caller.resolver(namehash) != resolver_addr:
424+
self.ens.functions.setResolver(
425+
namehash,
426+
resolver_addr
427+
).transact(transact)
428+
return cast('Contract', self._resolver_contract(address=resolver_addr))
429429

430430
def _resolve(self, name: str, fn_name: str = 'addr') -> Optional[Union[ChecksumAddress, str]]:
431431
normal_name = normalize_name(name)
@@ -456,6 +456,34 @@ def _resolve(self, name: str, fn_name: str = 'addr') -> Optional[Union[ChecksumA
456456
return to_checksum_address(result) if is_address(result) else result
457457
return None
458458

459+
def _assert_control(
460+
self,
461+
account: ChecksumAddress,
462+
name: str,
463+
parent_owned: Optional[str] = None,
464+
) -> None:
465+
if not address_in(account, self.w3.eth.accounts):
466+
raise UnauthorizedError(
467+
f"in order to modify {name!r}, you must control account"
468+
f" {account!r}, which owns {parent_owned or name!r}"
469+
)
470+
471+
def _first_owner(self, name: str) -> Tuple[Optional[ChecksumAddress], Sequence[str], str]:
472+
"""
473+
Takes a name, and returns the owner of the deepest subdomain that has an owner
474+
475+
:returns: (owner or None, list(unowned_subdomain_labels), first_owned_domain)
476+
"""
477+
owner = None
478+
unowned = []
479+
pieces = normalize_name(name).split('.')
480+
while pieces and is_none_or_zero_address(owner):
481+
name = '.'.join(pieces)
482+
owner = self.owner(name)
483+
if is_none_or_zero_address(owner):
484+
unowned.append(pieces.pop(0))
485+
return (owner, unowned, name)
486+
459487
def _claim_ownership(
460488
self,
461489
owner: ChecksumAddress,
@@ -493,33 +521,6 @@ def _reverse_registrar(self) -> 'Contract':
493521
addr = self.ens.caller.owner(normal_name_to_hash(REVERSE_REGISTRAR_DOMAIN))
494522
return self.w3.eth.contract(address=addr, abi=abis.REVERSE_REGISTRAR)
495523

496-
def _assert_control(
497-
self,
498-
account: ChecksumAddress,
499-
name: str, parent_owned: Optional[str] = None,
500-
) -> None:
501-
if not address_in(account, self.w3.eth.accounts):
502-
raise UnauthorizedError(
503-
f"in order to modify {name!r}, you must control account"
504-
f" {account!r}, which owns {parent_owned or name!r}"
505-
)
506-
507-
def _first_owner(self, name: str) -> Tuple[Optional[ChecksumAddress], Sequence[str], str]:
508-
"""
509-
Takes a name, and returns the owner of the deepest subdomain that has an owner
510-
511-
:returns: (owner or None, list(unowned_subdomain_labels), first_owned_domain)
512-
"""
513-
owner = None
514-
unowned = []
515-
pieces = normalize_name(name).split('.')
516-
while pieces and is_none_or_zero_address(owner):
517-
name = '.'.join(pieces)
518-
owner = self.owner(name)
519-
if is_none_or_zero_address(owner):
520-
unowned.append(pieces.pop(0))
521-
return (owner, unowned, name)
522-
523524

524525
def _resolver_supports_interface(resolver: 'Contract', interface_id: HexStr) -> bool:
525526
if not any('supportsInterface' in repr(func) for func in resolver.all_functions()):

0 commit comments

Comments
 (0)