Skip to content

Commit c0909fe

Browse files
committed
Allow for ENS name resolution
1 parent 4b4740c commit c0909fe

File tree

6 files changed

+183
-21
lines changed

6 files changed

+183
-21
lines changed

ens/utils.py

+10
Original file line numberDiff line numberDiff line change
@@ -212,3 +212,13 @@ def assert_signer_in_modifier_kwargs(modifier_kwargs: Any) -> ChecksumAddress:
212212

213213
def is_none_or_zero_address(addr: Union[Address, ChecksumAddress, HexAddress]) -> bool:
214214
return not addr or addr == EMPTY_ADDR_HEX
215+
216+
217+
def is_valid_ens_name(ens_name: str) -> bool:
218+
split_domain = ens_name.split('.')
219+
if len(split_domain) == 1:
220+
return False
221+
for name in split_domain:
222+
if not is_valid_name(name):
223+
return False
224+
return True

tests/core/eth-module/test_transactions.py

+47
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import pytest
22

3+
from web3._utils.ens import (
4+
ens_addresses,
5+
)
36
from web3.exceptions import (
7+
NameNotFound,
48
TimeExhausted,
59
TransactionNotFound,
610
ValidationError,
@@ -41,6 +45,49 @@ def test_send_transaction_with_valid_chain_id(web3, make_chain_id, expect_succes
4145
assert 'chain ID' in str(exc_info.value)
4246

4347

48+
@pytest.mark.parametrize(
49+
'to, _from',
50+
(
51+
(
52+
'registered-name-1.eth',
53+
'not-a-registered-name.eth',
54+
),
55+
(
56+
'not-a-registered-name.eth',
57+
'registered-name-1.eth',
58+
),
59+
)
60+
)
61+
def test_send_transaction_with_invalid_ens_names(web3, to, _from):
62+
with ens_addresses(web3, [
63+
('registered-name-1.eth', web3.eth.accounts[1]),
64+
]):
65+
transaction = {
66+
'to': to,
67+
'chainId': web3.eth.chainId,
68+
'from': _from,
69+
}
70+
71+
with pytest.raises(NameNotFound):
72+
web3.eth.sendTransaction(transaction)
73+
74+
75+
def test_send_transaction_with_ens_names(web3):
76+
with ens_addresses(web3, [
77+
('registered-name-1.eth', web3.eth.accounts[1]),
78+
('registered-name-2.eth', web3.eth.accounts[0])
79+
]):
80+
transaction = {
81+
'to': 'registered-name-1.eth',
82+
'chainId': web3.eth.chainId,
83+
'from': 'registered-name-2.eth',
84+
}
85+
86+
txn_hash = web3.eth.sendTransaction(transaction)
87+
receipt = web3.eth.waitForTransactionReceipt(txn_hash, timeout=RECEIPT_TIMEOUT)
88+
assert receipt.get('blockNumber') is not None
89+
90+
4491
def test_wait_for_missing_receipt(web3):
4592
with pytest.raises(TimeExhausted):
4693
web3.eth.waitForTransactionReceipt(b'\0' * 32, timeout=RECEIPT_TIMEOUT)

tests/integration/test_ethereum_tester.py

+10
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,14 @@ def func_wrapper(self, eth_tester, *args, **kwargs):
215215

216216
class TestEthereumTesterEthModule(EthModuleTest):
217217
test_eth_sign = not_implemented(EthModuleTest.test_eth_sign, ValueError)
218+
test_eth_sign_ens_names = not_implemented(
219+
EthModuleTest.test_eth_sign_ens_names, ValueError
220+
)
218221
test_eth_signTypedData = not_implemented(EthModuleTest.test_eth_signTypedData, ValueError)
219222
test_eth_signTransaction = not_implemented(EthModuleTest.test_eth_signTransaction, ValueError)
223+
test_eth_signTransaction_ens_names = not_implemented(
224+
EthModuleTest.test_eth_signTransaction_ens_names, ValueError
225+
)
220226
test_eth_submitHashrate = not_implemented(EthModuleTest.test_eth_submitHashrate, ValueError)
221227
test_eth_submitWork = not_implemented(EthModuleTest.test_eth_submitWork, ValueError)
222228

@@ -283,6 +289,10 @@ def test_eth_call_old_contract_state(self, eth_tester, web3, math_contract, unlo
283289
def test_eth_getStorageAt(self, web3, emitter_contract_address):
284290
super().test_eth_getStorageAt(web3, emitter_contract_address)
285291

292+
@pytest.mark.xfail(reason='json-rpc method is not implemented on eth-tester')
293+
def test_eth_getStorageAt_ens_name(self, web3, emitter_contract_address):
294+
super().test_eth_getStorageAt_ens_name(web3, emitter_contract_address)
295+
286296
def test_eth_estimateGas_with_block(self,
287297
web3,
288298
unlocked_account_dual_type):

web3/_utils/module_testing/eth_module.py

+76
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,13 @@
3030
HexBytes,
3131
)
3232

33+
from web3._utils.ens import (
34+
ens_addresses,
35+
)
3336
from web3.exceptions import (
3437
BlockNotFound,
3538
InvalidAddress,
39+
NameNotFound,
3640
TransactionNotFound,
3741
)
3842
from web3.types import ( # noqa: F401
@@ -136,12 +140,35 @@ def test_eth_getBalance_with_block_identifier(self, web3: "Web3") -> None:
136140
assert is_integer(later_balance)
137141
assert later_balance > genesis_balance
138142

143+
@pytest.mark.parametrize('address, expect_success', [
144+
('test-address.eth', True),
145+
('not-an-address.eth', False)
146+
])
147+
def test_eth_getBalance_with_ens_name(
148+
self, web3: "Web3", address: ChecksumAddress, expect_success: bool
149+
) -> None:
150+
with ens_addresses(web3, {'test-address.eth': web3.eth.accounts[0]}):
151+
if expect_success:
152+
balance = web3.eth.getBalance(address)
153+
assert is_integer(balance)
154+
assert balance >= 0
155+
else:
156+
with pytest.raises(NameNotFound):
157+
web3.eth.getBalance(address)
158+
139159
def test_eth_getStorageAt(
140160
self, web3: "Web3", emitter_contract_address: ChecksumAddress
141161
) -> None:
142162
storage = web3.eth.getStorageAt(emitter_contract_address, 0)
143163
assert isinstance(storage, HexBytes)
144164

165+
def test_eth_getStorageAt_ens_name(
166+
self, web3: "Web3", emitter_contract_address: ChecksumAddress
167+
) -> None:
168+
with ens_addresses(web3, {'emitter.eth': emitter_contract_address}):
169+
storage = web3.eth.getStorageAt('emitter.eth', 0)
170+
assert isinstance(storage, HexBytes)
171+
145172
def test_eth_getStorageAt_invalid_address(self, web3: "Web3") -> None:
146173
coinbase = web3.eth.coinbase
147174
with pytest.raises(InvalidAddress):
@@ -154,6 +181,14 @@ def test_eth_getTransactionCount(
154181
assert is_integer(transaction_count)
155182
assert transaction_count >= 0
156183

184+
def test_eth_getTransactionCount_ens_name(
185+
self, web3: "Web3", unlocked_account_dual_type: ChecksumAddress
186+
) -> None:
187+
with ens_addresses(web3, {'unlocked-acct-dual-type.eth': unlocked_account_dual_type}):
188+
transaction_count = web3.eth.getTransactionCount('unlocked-acct-dual-type.eth')
189+
assert is_integer(transaction_count)
190+
assert transaction_count >= 0
191+
157192
def test_eth_getTransactionCount_invalid_address(self, web3: "Web3") -> None:
158193
coinbase = web3.eth.coinbase
159194
with pytest.raises(InvalidAddress):
@@ -208,6 +243,16 @@ def test_eth_getCode(self, web3: "Web3", math_contract_address: ChecksumAddress)
208243
assert isinstance(code, HexBytes)
209244
assert len(code) > 0
210245

246+
def test_eth_getCode_ens_address(
247+
self, web3: "Web3", math_contract_address: ChecksumAddress
248+
) -> None:
249+
with ens_addresses(
250+
web3, {'mathcontract.eth': math_contract_address}
251+
):
252+
code = web3.eth.getCode('mathcontract.eth')
253+
assert isinstance(code, HexBytes)
254+
assert len(code) > 0
255+
211256
def test_eth_getCode_invalid_address(self, web3: "Web3", math_contract: "Contract") -> None:
212257
with pytest.raises(InvalidAddress):
213258
web3.eth.getCode(ChecksumAddress(HexAddress(HexStr(math_contract.address.lower()))))
@@ -251,6 +296,16 @@ def test_eth_sign(self, web3: "Web3", unlocked_account_dual_type: ChecksumAddres
251296
)
252297
assert new_signature != signature
253298

299+
def test_eth_sign_ens_names(
300+
self, web3: "Web3", unlocked_account_dual_type: ChecksumAddress
301+
) -> None:
302+
with ens_addresses(web3, {'unlocked-acct.eth': unlocked_account_dual_type}):
303+
signature = web3.eth.sign(
304+
'unlocked-acct.eth', text='Message tö sign. Longer than hash!'
305+
)
306+
assert is_bytes(signature)
307+
assert len(signature) == 32 + 32 + 1
308+
254309
def test_eth_signTypedData(
255310
self,
256311
web3: "Web3",
@@ -374,6 +429,27 @@ def test_eth_signTransaction(self, web3: "Web3", unlocked_account: ChecksumAddre
374429
assert result['tx']['gasPrice'] == txn_params['gasPrice']
375430
assert result['tx']['nonce'] == txn_params['nonce']
376431

432+
def test_eth_signTransaction_ens_names(
433+
self, web3: "Web3", unlocked_account: ChecksumAddress
434+
) -> None:
435+
with ens_addresses(web3, {'unlocked-account.eth': unlocked_account}):
436+
txn_params: TxParams = {
437+
'from': 'unlocked-account.eth',
438+
'to': 'unlocked-account.eth',
439+
'value': Wei(1),
440+
'gas': Wei(21000),
441+
'gasPrice': web3.eth.gasPrice,
442+
'nonce': Nonce(0),
443+
}
444+
result = web3.eth.signTransaction(txn_params)
445+
signatory_account = web3.eth.account.recover_transaction(result['raw'])
446+
assert unlocked_account == signatory_account
447+
assert result['tx']['to'] == unlocked_account
448+
assert result['tx']['value'] == txn_params['value']
449+
assert result['tx']['gas'] == txn_params['gas']
450+
assert result['tx']['gasPrice'] == txn_params['gasPrice']
451+
assert result['tx']['nonce'] == txn_params['nonce']
452+
377453
def test_eth_sendTransaction_addr_checksum_required(
378454
self, web3: "Web3", unlocked_account: ChecksumAddress
379455
) -> None:

web3/_utils/validation.py

+12
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
valmap,
3535
)
3636

37+
from ens.utils import (
38+
is_valid_ens_name,
39+
)
3740
from web3._utils.abi import (
3841
abi_to_signature,
3942
filter_by_type,
@@ -152,10 +155,19 @@ def validate_abi_value(abi_type: TypeStr, value: Any) -> None:
152155
)
153156

154157

158+
def is_not_address_string(value: Any) -> bool:
159+
return (is_string(value) and not is_bytes(value) and not
160+
is_checksum_address(value) and not is_hex_address(value))
161+
162+
155163
def validate_address(value: Any) -> None:
156164
"""
157165
Helper function for validating an address
158166
"""
167+
if is_not_address_string(value):
168+
if not is_valid_ens_name(value):
169+
raise InvalidAddress(f"ENS name: '{value}' is invalid.")
170+
return
159171
if is_bytes(value):
160172
if not is_binary_address(value):
161173
raise InvalidAddress("Address must be 20 bytes when input type is bytes", value)

web3/eth.py

+28-21
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,6 @@
5252
LogFilter,
5353
TransactionFilter,
5454
)
55-
from web3._utils.normalizers import (
56-
abi_ens_resolver,
57-
)
5855
from web3._utils.rpc_abi import (
5956
RPC,
6057
)
@@ -203,50 +200,60 @@ def blockNumber(self) -> BlockNumber:
203200
def chainId(self) -> int:
204201
return self.web3.manager.request_blocking(RPC.eth_chainId, [])
205202

206-
def block_identifier_munger(
207-
self,
208-
*args: Any,
209-
block_identifier: BlockIdentifier=None
210-
) -> List[Any]:
211-
if block_identifier is None:
212-
block_identifier = self.defaultBlock
213-
return [*args, block_identifier]
214-
215-
def address_resolver_munger(
203+
def block_id_munger(
216204
self,
217205
account: Union[Address, ChecksumAddress, ENS],
218206
block_identifier: Optional[BlockIdentifier]=None
219207
) -> Tuple[Union[Address, ChecksumAddress, ENS], BlockIdentifier]:
220-
resolved_addr = abi_ens_resolver(self.web3, 'address', account)[1]
221208
if block_identifier is None:
222209
block_identifier = self.defaultBlock
223-
return (resolved_addr, block_identifier)
210+
return (account, block_identifier)
224211

225212
getBalance: Method[Callable[..., Wei]] = Method(
226213
RPC.eth_getBalance,
227-
mungers=[address_resolver_munger],
214+
mungers=[block_id_munger],
228215
)
229216

217+
def get_storage_at_munger(
218+
self,
219+
account: Union[Address, ChecksumAddress, ENS],
220+
position: int,
221+
block_identifier: Optional[BlockIdentifier]=None
222+
) -> Tuple[Union[Address, ChecksumAddress, ENS], int, BlockIdentifier]:
223+
if block_identifier is None:
224+
block_identifier = self.defaultBlock
225+
return (account, position, block_identifier)
226+
230227
getStorageAt: Method[
231228
Callable[..., HexBytes]
232229
] = Method(
233230
RPC.eth_getStorageAt,
234-
mungers=[block_identifier_munger],
231+
mungers=[get_storage_at_munger],
235232
)
236233

234+
def get_proof_munger(
235+
self,
236+
account: Union[Address, ChecksumAddress, ENS],
237+
positions: Sequence[int],
238+
block_identifier: Optional[BlockIdentifier]=None
239+
) -> Tuple[Union[Address, ChecksumAddress, ENS], Sequence[int], Optional[BlockIdentifier]]:
240+
if block_identifier is None:
241+
block_identifier = self.defaultBlock
242+
return (account, positions, block_identifier)
243+
237244
getProof: Method[
238245
Callable[
239-
[Tuple[ChecksumAddress, Sequence[int], Optional[BlockIdentifier]]],
246+
[Tuple[Union[Address, ChecksumAddress, ENS], Sequence[int], Optional[BlockIdentifier]]],
240247
MerkleProof
241248
]
242249
] = Method(
243250
RPC.eth_getProof,
244-
mungers=[block_identifier_munger],
251+
mungers=[get_proof_munger],
245252
)
246253

247254
getCode: Method[Callable[..., HexBytes]] = Method(
248255
RPC.eth_getCode,
249-
mungers=[address_resolver_munger]
256+
mungers=[block_id_munger]
250257
)
251258

252259
def get_block_munger(
@@ -349,7 +356,7 @@ def waitForTransactionReceipt(
349356

350357
getTransactionCount: Method[Callable[..., Nonce]] = Method(
351358
RPC.eth_getTransactionCount,
352-
mungers=[block_identifier_munger],
359+
mungers=[block_id_munger],
353360
)
354361

355362
def replaceTransaction(self, transaction_hash: _Hash32, new_transaction: TxParams) -> HexBytes:

0 commit comments

Comments
 (0)