Skip to content

Commit 90b2527

Browse files
authored
Merge pull request #1125 from ethereum/pm-api
Stabilize PM module
2 parents b1c0930 + 5fa1ffc commit 90b2527

12 files changed

+1482
-57
lines changed

docs/web3.main.rst

+4
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,8 @@ Each ``web3`` instance also exposes these namespaced APIs.
8181
8282
See :doc:`./web3.admin`
8383

84+
.. py:attribute:: Web3.pm
85+
86+
See :doc:`./web3.pm`
87+
8488

docs/web3.pm.rst

+41-18
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,64 @@
11
Package Manager API
22
===================
33

4-
.. py:module:: web3.pm
5-
.. py:class:: PM
64

75
The ``web3.pm`` object exposes methods to interact with Packages as defined by `ERC 1123 <https://github.com/ethereum/EIPs/issues/1123>`_.
86

7+
- To learn more about the EthPM spec, visit the `documentation <http://ethpm.github.io/ethpm-spec/>`__.
8+
- To learn more about the Py-EthPM library used in this module, visit the `documentation <https://py-ethpm.readthedocs.io/en/latest/>`__.
99

10-
Installation
11-
------------
1210

13-
.. 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.
11+
.. WARNING::
1412

15-
You must install the eth-pm module separately, until it is stable. Install with:
13+
The ``web3.pm`` API is still under development and likely to change quickly.
1614

17-
.. code-block:: python
15+
Now is a great time to get familiar with the API, and test out writing
16+
code that uses some of the great upcoming features.
1817

19-
pip install --upgrade ethpm
18+
By default, access to this module has been turned off in the stable version of Web3.py:
2019

21-
Attaching
22-
---------
20+
.. code-block:: python
2321
24-
To use ``web3.pm``, attach it to your ``web3`` instance.
22+
>>> from web3.auto import w3
23+
>>> w3.pm
24+
...
25+
AttributeError: The Package Management feature is disabled by default ...
2526
26-
.. code-block:: python
27+
In order to access these features, you can turn it on with...
28+
29+
.. code-block:: python
2730
28-
from web3.pm import PM
29-
PM.attach(web3, 'pm')
31+
>>> web3.enable_unstable_package_management_api()
32+
>>> w3.pm
33+
<web3.pm.PM at 0x....>
3034
3135
3236
Methods
3337
-------
38+
The following methods are available on the ``web3.pm`` namespace.
39+
40+
.. autoclass:: web3.pm.PM
41+
:members:
42+
43+
.. autoclass:: web3.pm.ERCRegistry
44+
:members: __init__, _release, _get_package_name, _get_all_package_ids, _get_release_id, _get_all_release_ids, _get_release_data, _generate_release_id, _num_package_ids, _num_release_ids
3445

35-
The follwing methods are available on the ``web3.pm`` namespace.
46+
.. autoclass:: web3.pm.VyperReferenceRegistry
47+
:members: deploy_new_instance, owner, transfer_owner
3648

37-
.. py:method:: PM.get_package_from_manifest(self, manifest)
49+
.. autoclass:: web3.pm.SolidityReferenceRegistry
50+
:members:
51+
52+
53+
Creating your own Registry class
54+
--------------------------------
55+
If you want to implement your own registry and use it with ``web3.pm``, you must create a subclass that inherits from ``ERCRegistry``, and implements all the `ERC 1319 standard methods <https://github.com/ethereum/EIPs/issues/1319>`_ prefixed with an underscore in ``ERCRegistry``. Then, you have to manually set it as the ``registry`` attribute on ``web3.pm``.
56+
57+
.. code-block:: python
3858
39-
* Manifest must currently be a dict representing a valid manifest
40-
* Returns a Package instance representing the Manifest
59+
custom_registry = CustomRegistryClass(address, w3)
60+
w3.pm.registry = custom_registry
61+
62+
One reason a user might want to create their own Registry class is if they build a custom Package Registry smart contract that has features beyond those specified in `ERC 1319 <https://github.com/ethereum/EIPs/issues/1319>`_. For example, the ability to delete a release or some micropayment feature. Rather than accessing those functions directly on the contract instance, they can create a custom ``ERCRegistry`` subclass to easily call both the standard & custom methods.
4163

64+
The ``VyperReferenceRegistry`` class is an example of this, as it contains all of the ``ERC 1319`` defined functions (prefixed with an underscore, eg ``_get_package_name``) but also contains functions that are unique to the Vyper Registry reference implementation (eg ``transfer_owner``).

setup.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
'tester': [
1010
"eth-tester[py-evm]==0.1.0-beta.36",
1111
"py-geth>=2.0.1,<3.0.0",
12+
"pytest-ethereum>=0.1.3a6,<1.0.0",
1213
],
1314
'testrpc': ["eth-testrpc>=1.3.3,<2.0.0"],
1415
'linter': [
@@ -69,10 +70,11 @@
6970
install_requires=[
7071
"eth-abi>=2.0.0b4,<3.0.0",
7172
"eth-account>=0.2.1,<0.4.0",
73+
"eth-hash[pycryptodome]>=0.2.0,<1.0.0",
7274
"eth-utils>=1.3.0,<2.0.0",
75+
"ethpm>=0.1.4a10,<1.0.0",
7376
"hexbytes>=0.1.0,<1.0.0",
7477
"lru-dict>=1.1.6,<2.0.0",
75-
"eth-hash[pycryptodome]>=0.2.0,<1.0.0",
7678
"requests>=2.16.0,<3.0.0",
7779
"websockets>=7.0.0,<8.0.0",
7880
"pypiwin32>=223;platform_system=='Windows'",

tests/conftest.py

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
get_open_port,
1313
)
1414

15+
pytest_plugins = ["pytest_ethereum.plugins"]
16+
1517

1618
@pytest.fixture(scope="module", params=[lambda x: to_bytes(hexstr=x), identity])
1719
def address_conversion_func(request):

tests/core/pm-module/conftest.py

+255
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
import json
2+
import pytest
3+
4+
from eth_tester import (
5+
EthereumTester,
6+
PyEVMBackend,
7+
)
8+
from eth_utils import (
9+
function_abi_to_4byte_selector,
10+
to_bytes,
11+
to_canonical_address,
12+
)
13+
from ethpm import (
14+
ASSETS_DIR,
15+
Package,
16+
)
17+
from ethpm.contract import (
18+
LinkableContract,
19+
)
20+
from pytest_ethereum import (
21+
linker,
22+
)
23+
from pytest_ethereum.deployer import (
24+
Deployer,
25+
)
26+
27+
from web3 import Web3
28+
from web3.pm import (
29+
SolidityReferenceRegistry,
30+
VyperReferenceRegistry,
31+
)
32+
33+
VY_PACKAGE_ID_1 = to_bytes(hexstr='0xd059e8a6ea5a8bbf8dd097a7b8922316dcb7f024e8220b56d3c9e7188a6a7640') # noqa: E501
34+
VY_PACKAGE_ID_2 = to_bytes(hexstr='0x6df709a85698ad921462b8979547e3a873e22e1c73b1cb691f9376847fb2d402') # noqa: E501
35+
VY_PACKAGE_ID_3 = to_bytes(hexstr='0x80e41a42e3b8c3af0ea51c3fd50d481eef1367dd9d3797ba93d35dcf60660882') # noqa: E501
36+
VY_RELEASE_ID_1 = to_bytes(hexstr='0x595e26f1b2247bacc57e32807f8059e0b0837dd9c47e6946994196bd29c9ca97') # noqa: E501
37+
VY_RELEASE_ID_2 = to_bytes(hexstr='0x04592cb9ced5413e1b09e87b089bf696a05efb76ee87c8c41259635fac6d938a') # noqa: E501
38+
VY_RELEASE_ID_3 = to_bytes(hexstr='0xa06dbc51f8891894778ce03dc23d706d228c99789595758bd3dcac3a93f47f0a') # noqa: E501
39+
VY_RELEASE_ID_4 = to_bytes(hexstr='0x9c6f87dda6435b2506e81206b39ed782a44b92d826c24ab02bbded321b86e2ad') # noqa: E501
40+
SOL_PACKAGE_ID_1 = to_bytes(hexstr='0x60c5112b61159e6b42d54d945078394e9d5fc9c6ff0f3df78977006f8bbc06d4') # noqa: E501
41+
SOL_PACKAGE_ID_2 = to_bytes(hexstr='0xdbcfb0bd7115bf659350d77bb22bb889ca8294f61b0ca480f8a47bb8fc904cc9') # noqa: E501
42+
SOL_PACKAGE_ID_3 = to_bytes(hexstr='0xf3e4002c48a7f8f3485d62988317849c175340b66517c3b2993f725643eba84b') # noqa: E501
43+
SOL_RELEASE_ID_1 = to_bytes(hexstr='0x73835668f71c7ae85cbdcdbb5a9905fa420ffe85a847d283fa9beefcd56cacc4') # noqa: E501
44+
SOL_RELEASE_ID_2 = to_bytes(hexstr='0xe5ef0292a3b36b6ac2be07ee92df61be15e0b9f102df32cbe3f0c012ef69d462') # noqa: E501
45+
SOL_RELEASE_ID_3 = to_bytes(hexstr='0x1280148e0af5c47e95b41df15734d0726c7320fc4cf21efe3923fb047b53899d') # noqa: E501
46+
SOL_RELEASE_ID_4 = to_bytes(hexstr='0x7082e954e4fd6adf8a25c6cefe218f32ec66d8a197197f1d05aa67a65caf5111') # noqa: E501
47+
48+
49+
def setup_w3():
50+
genesis_overrides = {"gas_limit": 5500000}
51+
custom_genesis_params = PyEVMBackend._generate_genesis_params(
52+
overrides=genesis_overrides
53+
)
54+
pyevm_backend = PyEVMBackend(genesis_parameters=custom_genesis_params)
55+
t = EthereumTester(backend=pyevm_backend)
56+
w3 = Web3(Web3.EthereumTesterProvider(ethereum_tester=t))
57+
w3.eth.defaultAccount = w3.eth.accounts[0]
58+
w3.eth.defaultContractFactory = LinkableContract
59+
w3.enable_unstable_package_management_api()
60+
return w3
61+
62+
63+
def solidity_registry_strategy():
64+
def set_authority(package):
65+
w3 = package.w3
66+
authority = package.deployments.get_instance("WhitelistAuthority").address
67+
package_registry = package.deployments.get_instance("PackageRegistry")
68+
package_db = package.deployments.get_instance("PackageDB")
69+
release_db = package.deployments.get_instance("ReleaseDB")
70+
txh_1 = package_registry.functions.setAuthority(authority).transact()
71+
w3.eth.waitForTransactionReceipt(txh_1)
72+
txh_2 = package_db.functions.setAuthority(authority).transact()
73+
w3.eth.waitForTransactionReceipt(txh_2)
74+
txh_3 = release_db.functions.setAuthority(authority).transact()
75+
w3.eth.waitForTransactionReceipt(txh_3)
76+
77+
def set_dependencies(package):
78+
w3 = package.w3
79+
package_db = package.deployments.get_instance("PackageDB").address
80+
release_db = package.deployments.get_instance("ReleaseDB").address
81+
release_validator = package.deployments.get_instance("ReleaseValidator").address
82+
package_registry = package.deployments.get_instance("PackageRegistry")
83+
txh_1 = package_registry.functions.setPackageDb(package_db).transact()
84+
w3.eth.waitForTransactionReceipt(txh_1)
85+
txh_2 = package_registry.functions.setReleaseDb(release_db).transact()
86+
w3.eth.waitForTransactionReceipt(txh_2)
87+
txh_3 = package_registry.functions.setReleaseValidator(
88+
release_validator
89+
).transact()
90+
w3.eth.waitForTransactionReceipt(txh_3)
91+
92+
def get_selector(deployments, contract, fn):
93+
function_abi = [
94+
x for x in deployments.get_instance(contract).abi if x["name"] == fn
95+
][0]
96+
return function_abi_to_4byte_selector(function_abi)
97+
98+
def set_permissions(package):
99+
w3 = package.w3
100+
deployments = package.deployments
101+
set_version = get_selector(deployments, "ReleaseDB", "setVersion")
102+
set_release = get_selector(deployments, "ReleaseDB", "setRelease")
103+
set_package = get_selector(deployments, "PackageDB", "setPackage")
104+
set_package_owner = get_selector(deployments, "PackageDB", "setPackageOwner")
105+
release = get_selector(deployments, "PackageRegistry", "release")
106+
transfer_package_owner = get_selector(
107+
deployments, "PackageRegistry", "transferPackageOwner"
108+
)
109+
package_db = package.deployments.get_instance("PackageDB").address
110+
release_db = package.deployments.get_instance("ReleaseDB").address
111+
package_registry = package.deployments.get_instance("PackageRegistry").address
112+
authority = package.deployments.get_instance("WhitelistAuthority")
113+
txh_1 = authority.functions.setCanCall(
114+
package_registry, release_db, set_release, True
115+
).transact()
116+
w3.eth.waitForTransactionReceipt(txh_1)
117+
txh_2 = authority.functions.setCanCall(
118+
package_registry, package_db, set_package, True
119+
).transact()
120+
w3.eth.waitForTransactionReceipt(txh_2)
121+
txh_3 = authority.functions.setCanCall(
122+
package_registry, package_db, set_package_owner, True
123+
).transact()
124+
w3.eth.waitForTransactionReceipt(txh_3)
125+
txh_4 = authority.functions.setAnyoneCanCall(
126+
release_db, set_version, True
127+
).transact()
128+
w3.eth.waitForTransactionReceipt(txh_4)
129+
txh_5 = authority.functions.setAnyoneCanCall(
130+
package_registry, release, True
131+
).transact()
132+
w3.eth.waitForTransactionReceipt(txh_5)
133+
txh_6 = authority.functions.setAnyoneCanCall(
134+
package_registry, transfer_package_owner, True
135+
).transact()
136+
w3.eth.waitForTransactionReceipt(txh_6)
137+
138+
strategy = linker.linker(
139+
linker.deploy("IndexedOrderedSetLib"),
140+
linker.link("PackageDB", "IndexedOrderedSetLib"),
141+
linker.link("ReleaseDB", "IndexedOrderedSetLib"),
142+
linker.deploy("PackageRegistry"),
143+
linker.deploy("WhitelistAuthority"),
144+
linker.deploy("PackageDB"),
145+
linker.deploy("ReleaseDB"),
146+
linker.deploy("ReleaseValidator"),
147+
linker.run_python(set_authority),
148+
linker.run_python(set_dependencies),
149+
linker.run_python(set_permissions),
150+
)
151+
return strategy
152+
153+
154+
def sol_registry(w3):
155+
manifest = json.loads((ASSETS_DIR / "registry" / "1.0.0.json").read_text())
156+
strategy = solidity_registry_strategy()
157+
registry_package = Package(manifest, w3)
158+
registry_deployer = Deployer(registry_package)
159+
registry_deployer.register_strategy("PackageRegistry", strategy)
160+
deployed_registry_package = registry_deployer.deploy("PackageRegistry")
161+
assert isinstance(registry_package, Package)
162+
registry = deployed_registry_package.deployments.get_instance("PackageRegistry")
163+
return SolidityReferenceRegistry(to_canonical_address(registry.address), w3)
164+
165+
166+
def vy_registry(w3):
167+
registry_path = ASSETS_DIR / "vyper_registry"
168+
manifest = json.loads((registry_path / "1.0.0.json").read_text().rstrip('\n'))
169+
registry_package = Package(manifest, w3)
170+
registry_deployer = Deployer(registry_package)
171+
deployed_registry_package = registry_deployer.deploy("registry")
172+
registry_instance = deployed_registry_package.deployments.get_instance("registry")
173+
assert registry_instance.functions.owner().call() == w3.eth.defaultAccount
174+
return VyperReferenceRegistry(to_canonical_address(registry_instance.address), w3)
175+
176+
177+
def release_packages(registry):
178+
registry._release(
179+
"package", "1.0.0", "ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGV"
180+
)
181+
registry._release(
182+
"package", "1.0.1", "ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGW"
183+
)
184+
registry._release(
185+
"package", "1.0.2", "ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGX"
186+
)
187+
registry._release(
188+
"package", "1.0.3", "ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGJ"
189+
)
190+
registry._release(
191+
"package", "1.0.4", "ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGK"
192+
)
193+
registry._release(
194+
"package", "1.0.5", "ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGH"
195+
)
196+
registry._release(
197+
"package1", "1.0.1", "ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGZ"
198+
)
199+
registry._release(
200+
"package2", "1.0.1", "ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGT"
201+
)
202+
registry._release(
203+
"package3", "1.0.0", "ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGA"
204+
)
205+
registry._release(
206+
"package4", "1.0.0", "ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGB"
207+
)
208+
registry._release(
209+
"package5", "1.0.0", "ipfs://Qme4otpS88NV8yQi8TfTP89EsQC5bko3F5N1yhRoi6cwGC"
210+
)
211+
return registry
212+
213+
214+
# Module-level variables used here for efficiency
215+
# Tests are written against the sample packages released in `release_packages()` above, if more
216+
# tests are needed, they should take into account the releases that exist on a "loaded registry".
217+
W3 = setup_w3()
218+
FRESH_VY_REGISTRY = vy_registry(W3)
219+
FRESH_SOL_REGISTRY = sol_registry(W3)
220+
LOADED_VY_REGISTRY = release_packages(vy_registry(W3))
221+
LOADED_SOL_REGISTRY = release_packages(sol_registry(W3))
222+
VY_PKG_IDS = (VY_PACKAGE_ID_1, VY_PACKAGE_ID_2, VY_PACKAGE_ID_3)
223+
SOL_PKG_IDS = (SOL_PACKAGE_ID_1, SOL_PACKAGE_ID_2, SOL_PACKAGE_ID_3)
224+
VY_RLS_IDS = (VY_RELEASE_ID_1, VY_RELEASE_ID_2, VY_RELEASE_ID_3, VY_RELEASE_ID_4)
225+
SOL_RLS_IDS = (SOL_RELEASE_ID_1, SOL_RELEASE_ID_2, SOL_RELEASE_ID_3, SOL_RELEASE_ID_4)
226+
227+
228+
@pytest.fixture
229+
def w3():
230+
return W3
231+
232+
233+
@pytest.fixture
234+
def empty_vy_registry():
235+
return FRESH_VY_REGISTRY
236+
237+
238+
@pytest.fixture
239+
def empty_sol_registry():
240+
return FRESH_SOL_REGISTRY
241+
242+
243+
@pytest.fixture
244+
def loaded_vy_registry():
245+
return LOADED_VY_REGISTRY, VY_PKG_IDS, VY_RLS_IDS
246+
247+
248+
@pytest.fixture
249+
def loaded_sol_registry():
250+
return LOADED_SOL_REGISTRY, SOL_PKG_IDS, SOL_RLS_IDS
251+
252+
253+
@pytest.fixture
254+
def registry_getter(request):
255+
return request.getfixturevalue(request.param)

0 commit comments

Comments
 (0)