Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow for ENS name resolution #1749

Merged
merged 1 commit into from
Sep 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions ens/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,13 @@ def assert_signer_in_modifier_kwargs(modifier_kwargs: Any) -> ChecksumAddress:

def is_none_or_zero_address(addr: Union[Address, ChecksumAddress, HexAddress]) -> bool:
return not addr or addr == EMPTY_ADDR_HEX


def is_valid_ens_name(ens_name: str) -> bool:
split_domain = ens_name.split('.')
if len(split_domain) == 1:
return False
for name in split_domain:
if not is_valid_name(name):
return False
return True
47 changes: 47 additions & 0 deletions tests/core/eth-module/test_transactions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import pytest

from web3._utils.ens import (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Random plumbing work that should be done at some point. Anything under the web3 namespace like this that is purely a testing tool should get moved to live under web3.tools

ens_addresses,
)
from web3.exceptions import (
NameNotFound,
TimeExhausted,
TransactionNotFound,
ValidationError,
Expand Down Expand Up @@ -41,6 +45,49 @@ def test_send_transaction_with_valid_chain_id(web3, make_chain_id, expect_succes
assert 'chain ID' in str(exc_info.value)


@pytest.mark.parametrize(
'to, _from',
(
(
'registered-name-1.eth',
'not-a-registered-name.eth',
),
(
'not-a-registered-name.eth',
'registered-name-1.eth',
),
)
)
def test_send_transaction_with_invalid_ens_names(web3, to, _from):
with ens_addresses(web3, [
('registered-name-1.eth', web3.eth.accounts[1]),
]):
transaction = {
'to': to,
'chainId': web3.eth.chainId,
'from': _from,
}

with pytest.raises(NameNotFound):
web3.eth.sendTransaction(transaction)


def test_send_transaction_with_ens_names(web3):
with ens_addresses(web3, [
('registered-name-1.eth', web3.eth.accounts[1]),
('registered-name-2.eth', web3.eth.accounts[0])
]):
transaction = {
'to': 'registered-name-1.eth',
'chainId': web3.eth.chainId,
'from': 'registered-name-2.eth',
}

txn_hash = web3.eth.sendTransaction(transaction)
receipt = web3.eth.waitForTransactionReceipt(txn_hash, timeout=RECEIPT_TIMEOUT)
assert receipt.get('blockNumber') is not None


def test_wait_for_missing_receipt(web3):
with pytest.raises(TimeExhausted):
web3.eth.waitForTransactionReceipt(b'\0' * 32, timeout=RECEIPT_TIMEOUT)
Expand Down
10 changes: 10 additions & 0 deletions tests/integration/test_ethereum_tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,14 @@ def func_wrapper(self, eth_tester, *args, **kwargs):

class TestEthereumTesterEthModule(EthModuleTest):
test_eth_sign = not_implemented(EthModuleTest.test_eth_sign, ValueError)
test_eth_sign_ens_names = not_implemented(
EthModuleTest.test_eth_sign_ens_names, ValueError
)
test_eth_signTypedData = not_implemented(EthModuleTest.test_eth_signTypedData, ValueError)
test_eth_signTransaction = not_implemented(EthModuleTest.test_eth_signTransaction, ValueError)
test_eth_signTransaction_ens_names = not_implemented(
EthModuleTest.test_eth_signTransaction_ens_names, ValueError
)
test_eth_submitHashrate = not_implemented(EthModuleTest.test_eth_submitHashrate, ValueError)
test_eth_submitWork = not_implemented(EthModuleTest.test_eth_submitWork, ValueError)

Expand Down Expand Up @@ -283,6 +289,10 @@ def test_eth_call_old_contract_state(self, eth_tester, web3, math_contract, unlo
def test_eth_getStorageAt(self, web3, emitter_contract_address):
super().test_eth_getStorageAt(web3, emitter_contract_address)

@pytest.mark.xfail(reason='json-rpc method is not implemented on eth-tester')
def test_eth_getStorageAt_ens_name(self, web3, emitter_contract_address):
super().test_eth_getStorageAt_ens_name(web3, emitter_contract_address)

def test_eth_estimateGas_with_block(self,
web3,
unlocked_account_dual_type):
Expand Down
76 changes: 76 additions & 0 deletions web3/_utils/module_testing/eth_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,13 @@
HexBytes,
)

from web3._utils.ens import (
ens_addresses,
)
from web3.exceptions import (
BlockNotFound,
InvalidAddress,
NameNotFound,
TransactionNotFound,
)
from web3.types import ( # noqa: F401
Expand Down Expand Up @@ -136,12 +140,35 @@ def test_eth_getBalance_with_block_identifier(self, web3: "Web3") -> None:
assert is_integer(later_balance)
assert later_balance > genesis_balance

@pytest.mark.parametrize('address, expect_success', [
('test-address.eth', True),
('not-an-address.eth', False)
])
def test_eth_getBalance_with_ens_name(
self, web3: "Web3", address: ChecksumAddress, expect_success: bool
) -> None:
with ens_addresses(web3, {'test-address.eth': web3.eth.accounts[0]}):
if expect_success:
balance = web3.eth.getBalance(address)
assert is_integer(balance)
assert balance >= 0
else:
with pytest.raises(NameNotFound):
web3.eth.getBalance(address)

def test_eth_getStorageAt(
self, web3: "Web3", emitter_contract_address: ChecksumAddress
) -> None:
storage = web3.eth.getStorageAt(emitter_contract_address, 0)
assert isinstance(storage, HexBytes)

def test_eth_getStorageAt_ens_name(
self, web3: "Web3", emitter_contract_address: ChecksumAddress
) -> None:
with ens_addresses(web3, {'emitter.eth': emitter_contract_address}):
storage = web3.eth.getStorageAt('emitter.eth', 0)
assert isinstance(storage, HexBytes)

def test_eth_getStorageAt_invalid_address(self, web3: "Web3") -> None:
coinbase = web3.eth.coinbase
with pytest.raises(InvalidAddress):
Expand All @@ -154,6 +181,14 @@ def test_eth_getTransactionCount(
assert is_integer(transaction_count)
assert transaction_count >= 0

def test_eth_getTransactionCount_ens_name(
self, web3: "Web3", unlocked_account_dual_type: ChecksumAddress
) -> None:
with ens_addresses(web3, {'unlocked-acct-dual-type.eth': unlocked_account_dual_type}):
transaction_count = web3.eth.getTransactionCount('unlocked-acct-dual-type.eth')
assert is_integer(transaction_count)
assert transaction_count >= 0

def test_eth_getTransactionCount_invalid_address(self, web3: "Web3") -> None:
coinbase = web3.eth.coinbase
with pytest.raises(InvalidAddress):
Expand Down Expand Up @@ -208,6 +243,16 @@ def test_eth_getCode(self, web3: "Web3", math_contract_address: ChecksumAddress)
assert isinstance(code, HexBytes)
assert len(code) > 0

def test_eth_getCode_ens_address(
self, web3: "Web3", math_contract_address: ChecksumAddress
) -> None:
with ens_addresses(
web3, {'mathcontract.eth': math_contract_address}
):
code = web3.eth.getCode('mathcontract.eth')
assert isinstance(code, HexBytes)
assert len(code) > 0

def test_eth_getCode_invalid_address(self, web3: "Web3", math_contract: "Contract") -> None:
with pytest.raises(InvalidAddress):
web3.eth.getCode(ChecksumAddress(HexAddress(HexStr(math_contract.address.lower()))))
Expand Down Expand Up @@ -251,6 +296,16 @@ def test_eth_sign(self, web3: "Web3", unlocked_account_dual_type: ChecksumAddres
)
assert new_signature != signature

def test_eth_sign_ens_names(
self, web3: "Web3", unlocked_account_dual_type: ChecksumAddress
) -> None:
with ens_addresses(web3, {'unlocked-acct.eth': unlocked_account_dual_type}):
signature = web3.eth.sign(
'unlocked-acct.eth', text='Message tö sign. Longer than hash!'
)
assert is_bytes(signature)
assert len(signature) == 32 + 32 + 1

def test_eth_signTypedData(
self,
web3: "Web3",
Expand Down Expand Up @@ -374,6 +429,27 @@ def test_eth_signTransaction(self, web3: "Web3", unlocked_account: ChecksumAddre
assert result['tx']['gasPrice'] == txn_params['gasPrice']
assert result['tx']['nonce'] == txn_params['nonce']

def test_eth_signTransaction_ens_names(
self, web3: "Web3", unlocked_account: ChecksumAddress
) -> None:
with ens_addresses(web3, {'unlocked-account.eth': unlocked_account}):
txn_params: TxParams = {
'from': 'unlocked-account.eth',
'to': 'unlocked-account.eth',
'value': Wei(1),
'gas': Wei(21000),
'gasPrice': web3.eth.gasPrice,
'nonce': Nonce(0),
}
result = web3.eth.signTransaction(txn_params)
signatory_account = web3.eth.account.recover_transaction(result['raw'])
assert unlocked_account == signatory_account
assert result['tx']['to'] == unlocked_account
assert result['tx']['value'] == txn_params['value']
assert result['tx']['gas'] == txn_params['gas']
assert result['tx']['gasPrice'] == txn_params['gasPrice']
assert result['tx']['nonce'] == txn_params['nonce']

def test_eth_sendTransaction_addr_checksum_required(
self, web3: "Web3", unlocked_account: ChecksumAddress
) -> None:
Expand Down
12 changes: 12 additions & 0 deletions web3/_utils/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
valmap,
)

from ens.utils import (
is_valid_ens_name,
)
from web3._utils.abi import (
abi_to_signature,
filter_by_type,
Expand Down Expand Up @@ -152,10 +155,19 @@ def validate_abi_value(abi_type: TypeStr, value: Any) -> None:
)


def is_not_address_string(value: Any) -> bool:
return (is_string(value) and not is_bytes(value) and not
is_checksum_address(value) and not is_hex_address(value))


def validate_address(value: Any) -> None:
"""
Helper function for validating an address
"""
if is_not_address_string(value):
if not is_valid_ens_name(value):
raise InvalidAddress(f"ENS name: '{value}' is invalid.")
return
if is_bytes(value):
if not is_binary_address(value):
raise InvalidAddress("Address must be 20 bytes when input type is bytes", value)
Expand Down
49 changes: 28 additions & 21 deletions web3/eth.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,6 @@
LogFilter,
TransactionFilter,
)
from web3._utils.normalizers import (
abi_ens_resolver,
)
from web3._utils.rpc_abi import (
RPC,
)
Expand Down Expand Up @@ -203,50 +200,60 @@ def blockNumber(self) -> BlockNumber:
def chainId(self) -> int:
return self.web3.manager.request_blocking(RPC.eth_chainId, [])

def block_identifier_munger(
self,
*args: Any,
block_identifier: BlockIdentifier=None
) -> List[Any]:
if block_identifier is None:
block_identifier = self.defaultBlock
return [*args, block_identifier]

def address_resolver_munger(
def block_id_munger(
self,
account: Union[Address, ChecksumAddress, ENS],
block_identifier: Optional[BlockIdentifier]=None
) -> Tuple[Union[Address, ChecksumAddress, ENS], BlockIdentifier]:
resolved_addr = abi_ens_resolver(self.web3, 'address', account)[1]
if block_identifier is None:
block_identifier = self.defaultBlock
return (resolved_addr, block_identifier)
return (account, block_identifier)

getBalance: Method[Callable[..., Wei]] = Method(
RPC.eth_getBalance,
mungers=[address_resolver_munger],
mungers=[block_id_munger],
)

def get_storage_at_munger(
self,
account: Union[Address, ChecksumAddress, ENS],
position: int,
block_identifier: Optional[BlockIdentifier]=None
) -> Tuple[Union[Address, ChecksumAddress, ENS], int, BlockIdentifier]:
if block_identifier is None:
block_identifier = self.defaultBlock
return (account, position, block_identifier)

getStorageAt: Method[
Callable[..., HexBytes]
] = Method(
RPC.eth_getStorageAt,
mungers=[block_identifier_munger],
mungers=[get_storage_at_munger],
)

def get_proof_munger(
self,
account: Union[Address, ChecksumAddress, ENS],
positions: Sequence[int],
block_identifier: Optional[BlockIdentifier]=None
) -> Tuple[Union[Address, ChecksumAddress, ENS], Sequence[int], Optional[BlockIdentifier]]:
if block_identifier is None:
block_identifier = self.defaultBlock
return (account, positions, block_identifier)

getProof: Method[
Callable[
[Tuple[ChecksumAddress, Sequence[int], Optional[BlockIdentifier]]],
[Tuple[Union[Address, ChecksumAddress, ENS], Sequence[int], Optional[BlockIdentifier]]],
MerkleProof
]
] = Method(
RPC.eth_getProof,
mungers=[block_identifier_munger],
mungers=[get_proof_munger],
)

getCode: Method[Callable[..., HexBytes]] = Method(
RPC.eth_getCode,
mungers=[address_resolver_munger]
mungers=[block_id_munger]
)

def get_block_munger(
Expand Down Expand Up @@ -349,7 +356,7 @@ def waitForTransactionReceipt(

getTransactionCount: Method[Callable[..., Nonce]] = Method(
RPC.eth_getTransactionCount,
mungers=[block_identifier_munger],
mungers=[block_id_munger],
)

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