From 9de5e00b521a84febb0f185fa44321ab3f53551e Mon Sep 17 00:00:00 2001 From: Nick Gheorghita Date: Thu, 25 Oct 2018 16:52:41 +0200 Subject: [PATCH] pm-api branch cleanup --- docs/web3.pm.rst | 14 +- setup.py | 2 + tests/core/pm-module/test_ens_integration.py | 21 +- tests/core/pm-module/test_pm_init.py | 34 +-- tests/core/pm-module/test_registry.py | 234 ++++++++++++++++++ .../pm-module/test_registry_integration.py | 213 +++++----------- .../core/providers/test_websocket_provider.py | 4 +- tests/generate_go_ethereum_fixture.py | 2 +- .../generate_fixtures/go_ethereum.py | 4 +- tests/integration/generate_fixtures/parity.py | 6 +- .../go_ethereum/test_goethereum_http.py | 1 + .../go_ethereum/test_goethereum_ipc.py | 1 + .../go_ethereum/test_goethereum_ws.py | 1 + tests/integration/parity/test_parity_http.py | 1 + tests/integration/parity/test_parity_ipc.py | 1 + tests/integration/parity/test_parity_ws.py | 1 + web3/pm.py | 203 +++++++++++---- 17 files changed, 490 insertions(+), 253 deletions(-) create mode 100644 tests/core/pm-module/test_registry.py diff --git a/docs/web3.pm.rst b/docs/web3.pm.rst index 75916bfbaa..efc954d38c 100644 --- a/docs/web3.pm.rst +++ b/docs/web3.pm.rst @@ -12,12 +12,6 @@ Installation .. warning:: The PM module is still under development, and not all use-cases are currently supported, so it is not included by default in the web3 instance. -You must install the eth-pm module separately, until it is stable. Install with: - -.. code-block:: python - - pip install --upgrade ethpm - Attaching --------- @@ -34,4 +28,10 @@ Methods The following methods are available on the ``web3.pm`` namespace. -TODO: autoclass docstrings once ``web3.pm`` is stable. +.. autoclass:: web3.pm.PM + :members: + +.. note:: The ``web3.pm.Registry`` class is not designed to be interacted with directly, rather via the ``web3.pm.PM`` api. + +.. autoclass:: web3.pm.Registry + :members: diff --git a/setup.py b/setup.py index fc78289f8d..559f232bf5 100644 --- a/setup.py +++ b/setup.py @@ -73,6 +73,8 @@ "eth-abi>=1.2.0,<2.0.0", "eth-account>=0.2.1,<0.4.0", "eth-utils>=1.2.0,<2.0.0", + "ethpm>=0.1.4a5,<1", + "pytest-ethereum>=0.1.3a1,<1", "hexbytes>=0.1.0,<1.0.0", "lru-dict>=1.1.6,<2.0.0", "eth-hash[pycryptodome]>=0.2.0,<1.0.0", diff --git a/tests/core/pm-module/test_ens_integration.py b/tests/core/pm-module/test_ens_integration.py index 41f8670a7a..84d943ce82 100644 --- a/tests/core/pm-module/test_ens_integration.py +++ b/tests/core/pm-module/test_ens_integration.py @@ -3,24 +3,18 @@ from eth_utils import ( to_canonical_address, ) +from ethpm import ( + ASSETS_DIR, +) from ens import ENS from web3 import Web3 from web3.exceptions import ( InvalidAddress, ) - -try: - from ethpm import ( - ASSETS_DIR, - ) - from web3.pm import ( - PM, - ) -except ImportError: - ethpm_installed = False -else: - ethpm_installed = True +from web3.pm import ( + PM, +) def bytes32(val): @@ -128,7 +122,6 @@ def ens(ens_setup, mocker): return ens_setup -@pytest.mark.skipif(ethpm_installed is False, reason="ethpm is not installed") def test_ens_must_be_set_before_ens_methods_can_be_used(ens): w3 = ens.web3 PM.attach(w3, 'pm') @@ -136,7 +129,6 @@ def test_ens_must_be_set_before_ens_methods_can_be_used(ens): w3.pm.set_registry("tester.eth") -@pytest.mark.skipif(ethpm_installed is False, reason="ethpm is not installed") def test_web3_ens(ens): w3 = ens.web3 PM.attach(w3, 'pm') @@ -155,7 +147,6 @@ def test_web3_ens(ens): assert manifest_uri.rstrip(b'\x00') == b'1.com' -@pytest.mark.skipif(ethpm_installed is False, reason="ethpm is not installed") def test_registry_init_with_ens_name(ens): w3 = ens.web3 PM.attach(w3, 'pm') diff --git a/tests/core/pm-module/test_pm_init.py b/tests/core/pm-module/test_pm_init.py index e0af48d24f..306af4c26b 100644 --- a/tests/core/pm-module/test_pm_init.py +++ b/tests/core/pm-module/test_pm_init.py @@ -3,19 +3,20 @@ from eth_utils import ( to_canonical_address, ) +from ethpm import ( + Package, +) +from ethpm.exceptions import ( + InsufficientAssetsError, +) +from ethpm.tools import ( + get_manifest as get_ethpm_manifest, +) from web3 import Web3 - -try: - from ethpm import Package # noqa: E402 - from ethpm.tools import get_manifest as get_ethpm_manifest - from ethpm.exceptions import ( - InsufficientAssetsError, - ) -except ImportError: - ethpm_installed = False -else: - ethpm_installed = True +from web3.pm import ( + PM, +) # Returns web3 instance with `pm` module attached @@ -23,22 +24,16 @@ def web3(): w3 = Web3(Web3.EthereumTesterProvider()) w3.eth.defaultAccount = w3.eth.accounts[0] - try: - from web3.pm import PM # noqa: F811 - except ModuleNotFoundError as exc: - assert False, "eth-pm import failed because: %s" % exc PM.attach(w3, 'pm') return w3 -@pytest.mark.skipif(ethpm_installed is False, reason="ethpm is not installed") def test_pm_init_with_minimal_manifest(web3): owned_manifest = get_ethpm_manifest('owned', '1.0.1.json') pm = web3.pm.get_package_from_manifest(owned_manifest) assert pm.name == 'owned' -@pytest.mark.skipif(ethpm_installed is False, reason="ethpm is not installed") def test_get_contract_factory_raises_insufficient_assets_error(web3): insufficient_owned_manifest = get_ethpm_manifest('owned', '1.0.0.json') owned_package = web3.pm.get_package_from_manifest(insufficient_owned_manifest) @@ -46,7 +41,6 @@ def test_get_contract_factory_raises_insufficient_assets_error(web3): owned_package.get_contract_factory('Owned') -@pytest.mark.skipif(ethpm_installed is False, reason="ethpm is not installed") def test_get_contract_factory_with_valid_owned_manifest(web3): owned_manifest = get_ethpm_manifest('owned', '1.0.1.json') owned_package = web3.pm.get_package_from_manifest(owned_manifest) @@ -58,7 +52,6 @@ def test_get_contract_factory_with_valid_owned_manifest(web3): assert owned_instance.abi == owned_factory.abi -@pytest.mark.skipif(ethpm_installed is False, reason="ethpm is not installed") def test_get_contract_factory_with_valid_safe_math_lib_manifest(web3): safe_math_lib_manifest = get_ethpm_manifest('safe-math-lib', '1.0.1.json') safe_math_package = web3.pm.get_package_from_manifest(safe_math_lib_manifest) @@ -70,7 +63,6 @@ def test_get_contract_factory_with_valid_safe_math_lib_manifest(web3): assert safe_math_instance.functions.safeAdd(1, 2).call() == 3 -@pytest.mark.skipif(ethpm_installed is False, reason="ethpm is not installed") def test_get_contract_factory_with_valid_escrow_manifest(web3): escrow_manifest = get_ethpm_manifest("escrow", "1.0.2.json") escrow_package = web3.pm.get_package_from_manifest(escrow_manifest) @@ -89,7 +81,6 @@ def test_get_contract_factory_with_valid_escrow_manifest(web3): assert escrow_instance.functions.sender().call() == web3.eth.accounts[0] -@pytest.mark.skipif(ethpm_installed is False, reason="ethpm is not installed") def test_deploy_a_standalone_package_integration(web3): standard_token_manifest = get_ethpm_manifest("standard-token", "1.0.1.json") token_package = web3.pm.get_package_from_manifest(standard_token_manifest) @@ -104,7 +95,6 @@ def test_deploy_a_standalone_package_integration(web3): assert total_supply == 100 -@pytest.mark.skipif(ethpm_installed is False, reason="ethpm is not installed") def test_pm_init_with_manifest_uri(web3, monkeypatch): monkeypatch.setenv( "ETHPM_IPFS_BACKEND_CLASS", "ethpm.backends.ipfs.DummyIPFSBackend" diff --git a/tests/core/pm-module/test_registry.py b/tests/core/pm-module/test_registry.py new file mode 100644 index 0000000000..7e08acb5ad --- /dev/null +++ b/tests/core/pm-module/test_registry.py @@ -0,0 +1,234 @@ +import json +import pytest + +from eth_utils import ( + is_address, +) +from ethpm import ( + ASSETS_DIR, +) +from ethpm.contract import ( + LinkableContract, +) +import pytest_ethereum as pte + +from web3 import Web3 +from web3.exceptions import ( + PMError, +) +from web3.pm import ( + PM, + Registry, +) + + +@pytest.fixture +def w3(): + w3 = Web3(Web3.EthereumTesterProvider()) + w3.eth.defaultAccount = w3.eth.accounts[0] + w3.eth.defaultContractFactory = LinkableContract + PM.attach(w3, "pm") + return w3 + + +# Solidity registry +def test_with_solidity_registry_manifest(w3): + manifest_path = ASSETS_DIR / "registry" / "1.0.3.json" + manifest = json.loads(manifest_path.read_text()) + Registry = w3.pm.get_package_from_manifest(manifest) + assert Registry.name == "registry" + PackageRegistryFactory = Registry.get_contract_factory("PackageRegistry") + PackageDBFactory = Registry.get_contract_factory("PackageDB") + ReleaseDBFactory = Registry.get_contract_factory("ReleaseDB") + assert PackageRegistryFactory.needs_bytecode_linking is False + assert PackageDBFactory.needs_bytecode_linking + assert ReleaseDBFactory.needs_bytecode_linking + + +# Vyper registry +@pytest.fixture +def vy_registry(twig_deployer, w3): + registry_path = ASSETS_DIR / "vyper_registry" + registry_deployer = twig_deployer(registry_path) + registry_package = registry_deployer.deploy("registryV2") + registry_instance = registry_package.deployments.get_instance("registryV2") + owner = registry_instance.functions.owner().call() + assert owner == w3.eth.accounts[0] + return Registry(registry_instance.address, w3) + + +def test_registry_init(vy_registry, w3): + assert vy_registry.registry.functions.owner().call() == w3.eth.accounts[0] + + +def test_registry_init_deploy_new_instance(w3): + registry = Registry.deploy_new_instance(w3) + assert isinstance(registry, Registry) + assert is_address(registry.address) + + +def test_registry_releases_properly(vy_registry): + # uri > 32 bytes + vy_registry.release( + b"package", b"1.0.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGV" + ) + # uri = 32 bytes + vy_registry.release(b"package", b"1.0.2", b"ipfs://Qme4otpS88NV8yQi8TfTP89Es") + # uri < 32 bytes + vy_registry.release(b"package", b"1.0.1", b"ipfs://Qme4otpS88N") + release_data = vy_registry.get_release_data(b"package", b"1.0.0") + release_data_2 = vy_registry.get_release_data(b"package", b"1.0.1") + assert release_data[0][:7] == b"package" + assert release_data[1][:5] == b"1.0.0" + assert release_data[2] == b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGV" + assert release_data_2[2] == b"ipfs://Qme4otpS88N" + + +def test_registry_release_raises_exception_for_invalid_types(vy_registry): + with pytest.raises(PMError): + vy_registry.release( + "package", b"1.0.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi" + ) + with pytest.raises(PMError): + vy_registry.release( + b"package", "1.0.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi" + ) + with pytest.raises(PMError): + vy_registry.release( + b"package", b"1.0.0", "ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi" + ) + # uri > 64 bytes (not currently supported) + with pytest.raises(PMError): + vy_registry.release( + b"package", + b"1.0.0", + b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGV123abc123abc1", + ) + + +def test_registry_get_all_package_names(vy_registry): + vy_registry.release( + b"package1", b"1.0.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGV" + ) + vy_registry.release( + b"package1", b"1.0.1", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGU" + ) + all_pkgs_1 = vy_registry.get_all_package_names() + assert all_pkgs_1 == (b"package1",) + vy_registry.release( + b"package2", b"1.0.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGT" + ) + vy_registry.release( + b"package3", b"1.0.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGx" + ) + vy_registry.release( + b"package3", b"1.0.1", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGA" + ) + all_pkgs_2 = vy_registry.get_all_package_names() + assert all_pkgs_2 == (b"package1", b"package2", b"package3") + vy_registry.release( + b"package4", b"1.0.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGb" + ) + vy_registry.release( + b"package5", b"1.0.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGC" + ) + vy_registry.release( + b"package6", b"1.0.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGs" + ) + vy_registry.release( + b"package7", b"1.0.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGZ" + ) + all_pkgs_3 = vy_registry.get_all_package_names() + assert all_pkgs_3 == ( + b"package1", + b"package2", + b"package3", + b"package4", + b"package5", + b"package6", + b"package7", + ) + + +def test_registry_get_release_count(vy_registry): + vy_registry.release( + b"package1", b"1.0.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGa" + ) + vy_registry.release( + b"package1", b"1.0.1", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGb" + ) + vy_registry.release( + b"package2", b"1.0.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGc" + ) + vy_registry.release( + b"package2", b"1.0.1", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGd" + ) + vy_registry.release( + b"package2", b"1.0.2", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGe" + ) + pkg_1_release_count = vy_registry.get_release_count(b"package1") + pkg_2_release_count = vy_registry.get_release_count(b"package2") + assert pkg_1_release_count == 2 + assert pkg_2_release_count == 3 + vy_registry.release( + b"package2", b"1.0.3", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGf" + ) + pkg_2_release_count_2 = vy_registry.get_release_count(b"package2") + assert pkg_2_release_count_2 == 4 + # nonexistent package + with pte.tx_fail(): + vy_registry.get_release_count(b"nope") + + +def test_registry_get_all_package_versions(vy_registry): + vy_registry.release( + b"package", b"1.0.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGa" + ) + vy_registry.release( + b"package", b"1.0.1", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGb" + ) + vy_registry.release( + b"package", b"1.0.2", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGc" + ) + vy_registry.release( + b"package", b"1.1.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGd" + ) + vy_registry.release( + b"package", b"2.0.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGe" + ) + all_rls_1 = vy_registry.get_all_package_versions(b"package") + assert all_rls_1 == ( + (b"1.0.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGa"), + (b"1.0.1", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGb"), + (b"1.0.2", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGc"), + (b"1.1.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGd"), + (b"2.0.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGe"), + ) + vy_registry.release( + b"package", b"3.0.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGf" + ) + vy_registry.release( + b"package", b"4.0.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGg" + ) + vy_registry.release( + b"package", b"5.0.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGh" + ) + all_rls_2 = vy_registry.get_all_package_versions(b"package") + assert all_rls_2 == ( + (b"1.0.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGa"), + (b"1.0.1", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGb"), + (b"1.0.2", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGc"), + (b"1.1.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGd"), + (b"2.0.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGe"), + (b"3.0.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGf"), + (b"4.0.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGg"), + (b"5.0.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGh"), + ) + + +def test_registry_transfer_owner(vy_registry, w3): + vy_registry.transfer_owner(w3.eth.accounts[1]) + assert vy_registry.registry.functions.owner().call() == w3.eth.accounts[1] + # default account no longer registry owner + with pte.tx_fail(): + vy_registry.transfer_owner(w3.eth.accounts[2]) diff --git a/tests/core/pm-module/test_registry_integration.py b/tests/core/pm-module/test_registry_integration.py index d74cc088db..2b3319fd84 100644 --- a/tests/core/pm-module/test_registry_integration.py +++ b/tests/core/pm-module/test_registry_integration.py @@ -2,32 +2,28 @@ import pytest from eth_utils import ( + is_address, to_canonical_address, ) +from ethpm import ( + ASSETS_DIR, + Package, +) +from ethpm.contract import ( + LinkableContract, +) +from ethpm.exceptions import ( + CannotHandleURI, +) from web3 import Web3 from web3.exceptions import ( PMError, ) - -try: - from ethpm import ( - ASSETS_DIR, - ) - from ethpm.contract import ( - LinkableContract, - ) - from ethpm.exceptions import ( - CannotHandleURI, - ) - import pytest_ethereum as pte - from web3.pm import ( - Registry, - ) -except ImportError: - ethpm_installed = False -else: - ethpm_installed = True +from web3.pm import ( + PM, + Registry, +) @pytest.fixture @@ -35,18 +31,13 @@ def w3(): w3 = Web3(Web3.EthereumTesterProvider()) w3.eth.defaultAccount = w3.eth.accounts[0] w3.eth.defaultContractFactory = LinkableContract - try: - from web3.pm import PM # noqa: F811 - except ModuleNotFoundError as exc: - assert False, "eth-pm import failed because: %s" % exc - PM.attach(w3, 'pm') + PM.attach(w3, "pm") return w3 # Solidity registry -@pytest.mark.skipif(ethpm_installed is False, reason="ethpm is not installed") def test_with_solidity_registry_manifest(w3): - manifest_path = ASSETS_DIR / 'registry' / "1.0.2.json" + manifest_path = ASSETS_DIR / "registry" / "1.0.3.json" manifest = json.loads(manifest_path.read_text()) Registry = w3.pm.get_package_from_manifest(manifest) assert Registry.name == "registry" @@ -61,159 +52,89 @@ def test_with_solidity_registry_manifest(w3): # Vyper registry @pytest.fixture def vy_registry(twig_deployer, w3): - registry_path = ASSETS_DIR / 'vyper_registry' + registry_path = ASSETS_DIR / "vyper_registry" registry_deployer = twig_deployer(registry_path) - registry_package = registry_deployer.deploy("registry") - registry_instance = registry_package.deployments.get_instance("registry") + registry_package = registry_deployer.deploy("registryV2") + registry_instance = registry_package.deployments.get_instance("registryV2") owner = registry_instance.functions.owner().call() assert owner == w3.eth.accounts[0] return Registry(registry_instance.address, w3) -# `Registry` class tests -@pytest.mark.skipif(ethpm_installed is False, reason="ethpm is not installed") -def test_registry_init(vy_registry, w3): - # update to get_package_from_uri - assert vy_registry.registry.functions.owner().call() == w3.eth.accounts[0] - - -@pytest.mark.skipif(ethpm_installed is False, reason="ethpm is not installed") -def test_registry_releases_properly(vy_registry): - vy_registry.release(b"package", b"1.0.0", b"google.com") - release_data = vy_registry.get_release_data(b'package', b'1.0.0') - assert release_data[0][:7] == b'package' - assert release_data[1][:5] == b'1.0.0' - assert release_data[2][:10] == b'google.com' - - -@pytest.mark.skipif(ethpm_installed is False, reason="ethpm is not installed") -def test_registry_get_all_package_names(vy_registry): - vy_registry.release(b"package1", b"1.0.0", b"p1.v1.0.0.com") - vy_registry.release(b"package1", b"1.0.1", b"p1.v1.0.1.com") - all_pkgs_1 = vy_registry.get_all_package_names() - assert all_pkgs_1 == (b'package1',) - vy_registry.release(b"package2", b"1.0.0", b"p2.v1.0.0.com") - vy_registry.release(b"package3", b"1.0.0", b"p3.v1.0.0.com") - vy_registry.release(b"package3", b"1.0.1", b"p3.v1.0.1.com") - all_pkgs_2 = vy_registry.get_all_package_names() - assert all_pkgs_2 == ( - b'package1', - b'package2', - b'package3', - ) - vy_registry.release(b"package4", b"1.0.0", b"p2.v1.0.0.com") - vy_registry.release(b"package5", b"1.0.0", b"p2.v1.0.0.com") - vy_registry.release(b"package6", b"1.0.0", b"p2.v1.0.0.com") - vy_registry.release(b"package7", b"1.0.0", b"p2.v1.0.0.com") - all_pkgs_3 = vy_registry.get_all_package_names() - assert all_pkgs_3 == ( - b'package1', - b'package2', - b'package3', - b'package4', - b'package5', - b'package6', - b'package7', - ) - - -@pytest.mark.skipif(ethpm_installed is False, reason="ethpm is not installed") -def test_registry_get_release_count(vy_registry): - vy_registry.release(b"package1", b"1.0.0", b"p1.v1.0.0.com") - vy_registry.release(b"package1", b"1.0.1", b"p1.v1.0.1.com") - vy_registry.release(b"package2", b"1.0.0", b"p1.v1.0.0.com") - vy_registry.release(b"package2", b"1.0.1", b"p1.v1.0.1.com") - vy_registry.release(b"package2", b"1.0.2", b"p1.v1.0.2.com") - pkg_1_release_count = vy_registry.get_release_count(b'package1') - pkg_2_release_count = vy_registry.get_release_count(b'package2') - assert pkg_1_release_count == 2 - assert pkg_2_release_count == 3 - # nonexistent package - with pte.tx_fail(): - vy_registry.get_release_count(b'nope') - - -@pytest.mark.skipif(ethpm_installed is False, reason="ethpm is not installed") -def test_registry_get_all_package_versions(vy_registry): - vy_registry.release(b"package", b"1.0.0", b"p1.v1.0.0.com") - vy_registry.release(b"package", b"1.0.1", b"p1.v1.0.1.com") - vy_registry.release(b"package", b"1.0.2", b"p1.v1.0.2.com") - vy_registry.release(b"package", b"1.1.0", b"p1.v1.1.0.com") - vy_registry.release(b"package", b"2.0.0", b"p1.v2.0.0.com") - all_rls_1 = vy_registry.get_all_package_versions(b"package") - assert all_rls_1 == ( - (b'1.0.0', b'p1.v1.0.0.com'), - (b'1.0.1', b'p1.v1.0.1.com'), - (b'1.0.2', b'p1.v1.0.2.com'), - (b'1.1.0', b'p1.v1.1.0.com'), - (b'2.0.0', b'p1.v2.0.0.com'), - ) - vy_registry.release(b"package", b"3.0.0", b"p1.v3.0.0.com") - vy_registry.release(b"package", b"4.0.0", b"p1.v4.0.0.com") - vy_registry.release(b"package", b"5.0.0", b"p1.v5.0.0.com") - all_rls_2 = vy_registry.get_all_package_versions(b'package') - assert all_rls_2 == ( - (b'1.0.0', b'p1.v1.0.0.com'), - (b'1.0.1', b'p1.v1.0.1.com'), - (b'1.0.2', b'p1.v1.0.2.com'), - (b'1.1.0', b'p1.v1.1.0.com'), - (b'2.0.0', b'p1.v2.0.0.com'), - (b'3.0.0', b'p1.v3.0.0.com'), - (b'4.0.0', b'p1.v4.0.0.com'), - (b'5.0.0', b'p1.v5.0.0.com'), - ) - - -@pytest.mark.skipif(ethpm_installed is False, reason="ethpm is not installed") -def test_registry_transfer_owner(vy_registry, w3): - vy_registry.transfer_owner(w3.eth.accounts[1]) - assert vy_registry.registry.functions.owner().call() == w3.eth.accounts[1] +def test_pm_get_package_from_manifest(w3): + manifest_path = ASSETS_DIR / "vyper_registry" / "1.0.2.json" + manifest = json.loads(manifest_path.read_text()) + package = w3.pm.get_package_from_manifest(manifest) + assert isinstance(package, Package) + assert package.name == "registry-v2" -# web3.pm integration -@pytest.mark.skipif(ethpm_installed is False, reason="ethpm is not installed") def test_pm_deploy_and_set_registry(w3): - assert not hasattr(w3.pm, 'registry') + assert not hasattr(w3.pm, "registry") w3.pm.deploy_and_set_registry() assert isinstance(w3.pm.registry, Registry) + assert is_address(w3.pm.registry.address) -@pytest.mark.skipif(ethpm_installed is False, reason="ethpm is not installed") def test_pm_set_registry(vy_registry, w3): - assert not hasattr(w3.pm, 'registry') + assert not hasattr(w3.pm, "registry") w3.pm.set_registry(address=to_canonical_address(vy_registry.address)) assert isinstance(w3.pm.registry, Registry) + assert is_address(w3.pm.registry.address) -@pytest.mark.skipif(ethpm_installed is False, reason="ethpm is not installed") def test_pm_must_set_registry_before_registry_interaction_functions(w3): with pytest.raises(PMError): - w3.pm.release_package(b'package', b'1.0.0', b'google.com') + w3.pm.release_package( + b"package", + b"1.0.0", + b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGe", + ) with pytest.raises(PMError): - w3.pm.get_release_data(b'package', b'1.0.0') + w3.pm.get_release_data(b"package", b"1.0.0") with pytest.raises(PMError): - w3.pm.get_package(b'package', b'1.0.0') + w3.pm.get_package(b"package", b"1.0.0") -@pytest.mark.skipif(ethpm_installed is False, reason="ethpm is not installed") def test_pm_release_package(w3): w3.pm.deploy_and_set_registry() - w3.pm.release_package(b'package', b'1.0.0', b'google.com') - package_data = w3.pm.get_release_data(b'package', b'1.0.0') - assert package_data[0][:7] == b'package' + w3.pm.release_package( + b"package", b"1.0.0", b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGe" + ) + package_data = w3.pm.get_release_data(b"package", b"1.0.0") + assert package_data[0] == b"package" + assert package_data[1] == b"1.0.0" + assert package_data[2] == b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGe" -@pytest.mark.skipif(ethpm_installed is False, reason="ethpm is not installed") def test_pm_release_package_raises_exception_with_invalid_from_address(vy_registry, w3): w3.pm.deploy_and_set_registry() w3.eth.defaultAccount = w3.eth.accounts[1] with pytest.raises(PMError): - w3.pm.release_package(b'package', b'1.0.0', b'google.com') + w3.pm.release_package( + b"package", + b"1.0.0", + b"ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGe", + ) -@pytest.mark.skipif(ethpm_installed is False, reason="ethpm is not installed") -def test_pm_get_package(w3): +def test_pm_get_package(w3, monkeypatch): + monkeypatch.setenv( + "ETHPM_IPFS_BACKEND_CLASS", "ethpm.backends.ipfs.DummyIPFSBackend" + ) + w3.pm.deploy_and_set_registry() + w3.pm.release_package( + b"package", b"1.0.0", b"ipfs://QmbeVyFLSuEUxiXKwSsEjef6icpdTdA4kGG9BcrJXKNKUW" + ) + pkg = w3.pm.get_package(b"package", b"1.0.0") + assert isinstance(pkg, Package) + assert pkg.name == "owned" + + +def test_pm_get_package_raises_exception_with_invalid_uri(w3): w3.pm.deploy_and_set_registry() - w3.pm.release_package(b'package', b'1.0.0', b'google.com') + w3.pm.release_package( + b"package", b"1.0.0", b"https://raw.githubusercontent.com/invalid/uri" + ) with pytest.raises(CannotHandleURI): - w3.pm.get_package(b'package', b'1.0.0') + w3.pm.get_package(b"package", b"1.0.0") diff --git a/tests/core/providers/test_websocket_provider.py b/tests/core/providers/test_websocket_provider.py index 789fbe2225..7c04e8198b 100644 --- a/tests/core/providers/test_websocket_provider.py +++ b/tests/core/providers/test_websocket_provider.py @@ -7,11 +7,11 @@ Thread, ) -import websockets - from tests.utils import ( wait_for_ws, ) +import websockets + from web3 import Web3 from web3.exceptions import ( ValidationError, diff --git a/tests/generate_go_ethereum_fixture.py b/tests/generate_go_ethereum_fixture.py index 90e593ffbe..13cdaf5a02 100644 --- a/tests/generate_go_ethereum_fixture.py +++ b/tests/generate_go_ethereum_fixture.py @@ -21,10 +21,10 @@ to_text, to_wei, ) - from tests.utils import ( get_open_port, ) + from web3 import Web3 from web3._utils.module_testing.emitter_contract import ( EMITTER_ABI, diff --git a/tests/integration/generate_fixtures/go_ethereum.py b/tests/integration/generate_fixtures/go_ethereum.py index b9a0e7a83d..0d50eec3e7 100644 --- a/tests/integration/generate_fixtures/go_ethereum.py +++ b/tests/integration/generate_fixtures/go_ethereum.py @@ -9,11 +9,11 @@ is_dict, is_same_address, ) - -import common from tests.utils import ( get_open_port, ) + +import common from web3 import Web3 from web3._utils.module_testing.emitter_contract import ( EMITTER_ABI, diff --git a/tests/integration/generate_fixtures/parity.py b/tests/integration/generate_fixtures/parity.py index a1c8ec0423..393b3d4562 100644 --- a/tests/integration/generate_fixtures/parity.py +++ b/tests/integration/generate_fixtures/parity.py @@ -9,12 +9,12 @@ from eth_utils import ( to_text, ) - -import common -import go_ethereum from tests.utils import ( get_open_port, ) + +import common +import go_ethereum from web3 import Web3 from web3._utils.toolz import ( merge, diff --git a/tests/integration/go_ethereum/test_goethereum_http.py b/tests/integration/go_ethereum/test_goethereum_http.py index 251e558ead..1e611f72cf 100644 --- a/tests/integration/go_ethereum/test_goethereum_http.py +++ b/tests/integration/go_ethereum/test_goethereum_http.py @@ -3,6 +3,7 @@ from tests.utils import ( get_open_port, ) + from web3 import Web3 from .common import ( diff --git a/tests/integration/go_ethereum/test_goethereum_ipc.py b/tests/integration/go_ethereum/test_goethereum_ipc.py index 8bc53ff6f9..8b923b3951 100644 --- a/tests/integration/go_ethereum/test_goethereum_ipc.py +++ b/tests/integration/go_ethereum/test_goethereum_ipc.py @@ -5,6 +5,7 @@ from tests.utils import ( get_open_port, ) + from web3 import Web3 from .common import ( diff --git a/tests/integration/go_ethereum/test_goethereum_ws.py b/tests/integration/go_ethereum/test_goethereum_ws.py index 5166bae242..ba69412bfc 100644 --- a/tests/integration/go_ethereum/test_goethereum_ws.py +++ b/tests/integration/go_ethereum/test_goethereum_ws.py @@ -7,6 +7,7 @@ get_open_port, wait_for_ws, ) + from web3 import Web3 from .common import ( diff --git a/tests/integration/parity/test_parity_http.py b/tests/integration/parity/test_parity_http.py index 1b0ad45458..31d8a190d7 100644 --- a/tests/integration/parity/test_parity_http.py +++ b/tests/integration/parity/test_parity_http.py @@ -7,6 +7,7 @@ from tests.utils import ( get_open_port, ) + from web3 import Web3 from web3._utils.module_testing import ( NetModuleTest, diff --git a/tests/integration/parity/test_parity_ipc.py b/tests/integration/parity/test_parity_ipc.py index 48dc537875..1fb72f3eae 100644 --- a/tests/integration/parity/test_parity_ipc.py +++ b/tests/integration/parity/test_parity_ipc.py @@ -5,6 +5,7 @@ from tests.integration.parity.utils import ( wait_for_socket, ) + from web3 import Web3 from web3._utils.module_testing import ( NetModuleTest, diff --git a/tests/integration/parity/test_parity_ws.py b/tests/integration/parity/test_parity_ws.py index 3d24520818..cb855a0648 100644 --- a/tests/integration/parity/test_parity_ws.py +++ b/tests/integration/parity/test_parity_ws.py @@ -8,6 +8,7 @@ get_open_port, wait_for_ws, ) + from web3 import Web3 from web3._utils.module_testing import ( NetModuleTest, diff --git a/web3/pm.py b/web3/pm.py index a1242cd8d0..bdfd2d90fb 100644 --- a/web3/pm.py +++ b/web3/pm.py @@ -1,11 +1,20 @@ import json from eth_utils import ( + is_bytes, is_canonical_address, is_checksum_address, to_canonical_address, + to_text, to_tuple, ) +from ethpm import ( + ASSETS_DIR, + Package, +) +from pytest_ethereum.deployer import ( + Deployer, +) from web3._utils.ens import ( is_ens_name, @@ -22,41 +31,32 @@ Module, ) -try: - from ethpm import ( - ASSETS_DIR, - Package, - ) - from pytest_ethereum.deployer import ( - Deployer, - ) -except ImportError as exc: - raise ImportError( - "To use web3's alpha package management features, you must install the " - "`ethpm` dependency manually: pip install --upgrade ethpm" - ) from exc - - -# Package Management is currently still in alpha. -# It is not automatically available on a web3 object. -# To use the `PM` module, attach it to your web3 object -# i.e. PM.attach(web3, 'pm') +# Package Management is still in alpha. It is not automatically available on a web3 object. +# To use the `PM` module, attach it to your web3 object using the `attach` facility +# PM.attach(web3, 'pm') class PM(Module): def get_package_from_manifest(self, manifest): """ - * :manifest: must be a dict representing a valid manifest - * Returns a ``Package`` instance representing the Manifest + Returns a ``Package`` instance representing the Manifest + + * Parameters: + * ``manifest``: A dict representing a valid manifest """ pkg = Package(manifest, self.web3) return pkg def get_package_from_uri(self, manifest_uri): """ - * :uri: *must* be a valid content-addressed URI, as defined in the - `Py-EthPM Documentation `_. - * Returns a ``Package`` isntance representing the Manifest stored at the URI. + Returns a ``Package`` instance representing the Manifest stored at the URI. + If you want to use a specific IPFS backend, set ``ETHPM_IPFS_BACKEND_CLASS`` + to your desired backend. Defaults to Infura IPFS backend. + + * Parameters: + * ``uri``: Must be a valid content-addressed URI, as defined in + `Py-EthPM Docs `_. + """ pkg = Package.from_uri(manifest_uri, self.web3) return pkg @@ -64,10 +64,11 @@ def get_package_from_uri(self, manifest_uri): def set_registry(self, address): """ Sets the current registry used in ``web3.pm`` functions that read/write to an - onchain registry. - Requires a valid ENS instance set as web3.ens property to pass ENS domains in as :address: + on-chain registry. Requires a valid ENS instance set as web3.ens property to + pass ENS domains in as ``address``. - :address: Address of the on-chain registry - Accepts ENS name, if web3.ens is valid + * Parameters: + * ``address``: Address of on-chain registry - Accepts ENS name, if web3.ens is valid. """ if is_canonical_address(address) or is_checksum_address(address): self.registry = Registry(address, self.web3) @@ -87,15 +88,26 @@ def set_registry(self, address): def deploy_and_set_registry(self): """ - Deploys a new instance of a Registry.vy and sets that Registry to ``web3.pm``. - (py-ethpm/ethpm/assets/vyper_registry/registry.v.py) + Deploys a new instance of a vyper registry and sets that registry to ``web3.pm``. + Registry contract can be found `here `__. To tie your registry to an + ENS name, use web3's ENS module, ie. + + .. code-block:: python + + w3.ens.setup_address(ens_name, w3.pm.registry.address) """ self.registry = Registry.deploy_new_instance(self.web3) def release_package(self, name, version, manifest_uri): """ - Publishes a version release to the current registry. Requires ``web3.PM`` to have a - registry set. Requires ``web3.eth.defaultAccount`` to be the registry owner. + Publishes a version release to the current registry. Requires ``web3.PM`` to + have a registry set. Requires ``web3.eth.defaultAccount`` to be the registry owner. + + * Parameters: + * ``name``: Must be a valid package name of bytes type, with less than 32 bytes. + * ``version``: Must be a valid package version of bytes type, with less than 32 bytes. + * ``manifest_uri``: Must be a valid manifest URI of bytes type, with less than 64 bytes. """ self._validate_set_registry() if self.web3.eth.defaultAccount != self.registry.owner: @@ -104,8 +116,12 @@ def release_package(self, name, version, manifest_uri): def get_release_data(self, name, version): """ - Returns ``(package_name, version, manifest_uri`` associated with the given package name - and version, if they are published to the currently set registry. + Returns ``(package_name, version, manifest_uri)`` associated with the given + package name and version, *if* they are published to the currently set registry. + + * Parameters: + * ``name``: Must be a valid package name of bytes type, with less than 32 bytes. + * ``version``: Must be a valid package version of bytes type, with less than 32 bytes. """ self._validate_set_registry() return self.registry.get_release_data(name, version) @@ -114,10 +130,14 @@ def get_package(self, name, version): """ Returns a ``Package`` instance, generated by the ``manifest_uri`` associated with the given package name and version, if they are published to the currently set registry. + + * Parameters: + * ``name``: Must be a valid package name of bytes type, with less than 32 bytes. + * ``version``: Must be a valid package version of bytes type, with less than 32 bytes. """ self._validate_set_registry() _, _, release_uri = self.registry.get_release_data(name, version) - return self.get_package_from_uri(release_uri.rstrip(b'\x00')) + return self.get_package_from_uri(to_text(release_uri)) def _validate_set_registry(self): try: @@ -138,26 +158,37 @@ def _validate_set_registry(self): def _validate_set_ens(self): if not self.web3: raise InvalidAddress( - "xxxCould not look up name %r because no web3" - " connection available" + "Could not look up ENS address because no web3 " + "connection available" ) elif not self.web3.ens: raise InvalidAddress( - "xxxCould not look up name %r because ENS is" - " set to None" + "Could not look up ENS address because web3.ens is " + "set to None" ) class Registry(Contract): + """ + A class that represents an on-chain Registry. Currently tied to the vyper registry + implementation found `here `__. This implementation is not yet + `ERC1319 `__ compliant, however efforts + to make the vyper implementation and this class + `ERC1319 `__ compliant are currently underway, + and will both be updated accordingly. + + * Parameters: + * ``address``: The address of previously deployed vyper registry on ``w3`` + * ``w3``: A web3 instance connected to the chain on which the vyper registry is deployed. + """ def __init__(self, address, w3): - # only works with v.py registry in ethpm/assets - # todo: 100% ERC1319 compatibility if is_canonical_address(address) or is_checksum_address(address): registry_address = to_canonical_address(address) else: - raise PMError("Registry class only accepts canonical/checksum addresses.") - manifest = json.loads((ASSETS_DIR / 'vyper_registry' / '1.0.0.json').read_text()) - registry_package = Package(manifest, w3) + raise PMError("Registry class only accepts canonical or checksum addresses.") + + registry_package = get_vyper_registry_package(w3) self.registry = registry_package.get_contract_instance( "registry", registry_address ) @@ -166,8 +197,14 @@ def __init__(self, address, w3): @classmethod def deploy_new_instance(cls, w3): - manifest = json.loads((ASSETS_DIR / 'vyper_registry' / '1.0.0.json').read_text()) - registry_package = Package(manifest, w3) + """ + Returns a ``Registry`` instance, connected to a newly-deployed instance of a + vyper registry on the provided ``web3`` connected blockchain. + + * Parameters: + * ``w3``: An active web3 instance on which to deploy the vyper registry. + """ + registry_package = get_vyper_registry_package(w3) registry_deployer = Deployer(registry_package) deployed_registry_package = registry_deployer.deploy("registry") registry_address = deployed_registry_package.deployments.get_instance("registry").address @@ -175,29 +212,60 @@ def deploy_new_instance(cls, w3): def release(self, name, version, manifest_uri): """ - Returns tx_receipt from adding a new release. + Returns a tx_receipt generated from adding a new release to the registry. Trailing + underscores are added to manifest uri to allow them to compliant with vyper + type constraints. + + * Parameters: + * ``name``: Must be a valid package name of bytes type, with less than 32 bytes. + * ``version``: Must be a valid package version of bytes type, with less than 32 bytes. + * ``manifest_uri``: Must be a valid manifest URI of bytes type, with less than 64 bytes. """ - tx_hash = self.registry.functions.release(name, version, manifest_uri).transact() + if not is_bytes(name) or not is_bytes(version) or not is_bytes(manifest_uri): + raise PMError( + "Expected bytes type for name, version, and manifest_uri. " + "Instead got {0}, {1}, {2}.".format(type(name), type(version), type(manifest_uri)) + ) + if len(name) > 32 or len(version) > 32: + raise PMError( + "Name and version must be shorter than 32 bytes. " + "Instead got lengths of {0}, {1}.".format(len(name), len(version)) + ) + if len(manifest_uri) <= 32: + uri_1 = manifest_uri.ljust(32, b'_') + uri_2 = b'_' * 32 + elif len(manifest_uri) <= 64: + uri_1 = manifest_uri[:32] + uri_2 = manifest_uri[32:64].ljust(32, b'_') + else: + raise PMError("URI lengths of more than 64 are not currently supported.") + + tx_hash = self.registry.functions.release(name, version, uri_1, uri_2).transact() return self.w3.eth.waitForTransactionReceipt(tx_hash) def get_release_data(self, name, version): """ - Returns (package_name, version, manifest_uri) associated with given name, version. + Returns ``(package_name, version, manifest_uri)`` associated with given name, version. + + * Parameters: + * ``name``: Must be a valid package name of bytes type, with less than 32 bytes. + * ``version``: Must be a valid package version of bytes type, with less than 32 bytes. """ release_id = self.registry.functions.getReleaseId(name, version).call() - return self.registry.functions.getReleaseData(release_id).call() + release_data = self.registry.functions.getReleaseData(release_id).call() + return normalize_release_data(release_data) @property def owner(self): """ - Returns the owner address. + Returns the owner address of the registry. """ return self.registry.functions.owner().call() @to_tuple def get_all_package_names(self): """ - Returns the package_name for every package on registry. + Returns the ``package_name`` for every package on registry. """ package_count = self.registry.functions.packageCount().call() for offset in range(0, package_count, 4): @@ -210,7 +278,10 @@ def get_all_package_names(self): def get_release_count(self, name): """ - Returns the release count for a given package_name. + Returns the release count for a given ``package_name``. + + * Parameters: + * ``name``: Must be a valid package name of bytes type, with less than 32 bytes. """ _, _, release_count = self.registry.functions.getPackageData(name).call() return release_count @@ -218,7 +289,10 @@ def get_release_count(self, name): @to_tuple def get_all_package_versions(self, name): """ - Returns (version, manifest_uri) for every release of a given package. + Returns ``(version, manifest_uri)`` for every release of a given package. + + * Parameters: + * ``name``: Must be a valid package name of bytes type, with less than 32 bytes. """ name, package_id, release_count = self.registry.functions.getPackageData(name).call() for index in range(0, release_count, 4): @@ -226,12 +300,31 @@ def get_all_package_versions(self, name): for r_id in release_ids: if r_id != b'\x00' * 32: _, version, uri = self.registry.functions.getReleaseData(r_id).call() - # rstrip used to trim trailing bytes returned in package_name: bytes32 - yield (version.rstrip(b'\x00'), uri.rstrip(b'\x00')) + # rstrip used to trim trailing bytes returned in vyper bytes32 types + yield (version.rstrip(b'\x00'), uri.rstrip(b'_')) def transfer_owner(self, new_address): """ Transfers ownership of registry to new_address. + + * Parameters: + * ``new_address``: Address of the new registry owner account. """ tx_hash = self.registry.functions.transferOwner(new_address).transact() return self.w3.eth.waitForTransactionReceipt(tx_hash) + + +def get_vyper_registry_package(w3): + manifest = json.loads((ASSETS_DIR / 'vyper_registry' / '1.0.2.json').read_text()) + return Package(manifest, w3) + + +@to_tuple +def normalize_release_data(release_data): + """ + ``rstrip.(b'\x00')`` used to trim trailing bytes returned in vyper bytes32 types. + ``rstrip.(b'_')`` used to trim trailing underscores padding manifest uris. + """ + yield release_data[0].rstrip(b'\x00') + yield release_data[1].rstrip(b'\x00') + yield release_data[2].rstrip(b'_')