Skip to content

Commit c918271

Browse files
committed
Update documentation and add compiling script docs
- Clean up contract compiler script
1 parent 6170703 commit c918271

File tree

2 files changed

+196
-89
lines changed

2 files changed

+196
-89
lines changed

docs/contributing.rst

Lines changed: 74 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -195,18 +195,16 @@ Within the ``pytest`` scope, :file:`conftest.py` files are used for common code
195195
shared between modules that exist within the same directory as that particular
196196
:file:`conftest.py` file.
197197

198-
Unit Testing
199-
^^^^^^^^^^^^
198+
Unit Testing and eth-tester Tests
199+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
200200

201-
Unit tests are meant to test the logic of smaller chunks (or units) of the
202-
codebase without having to be wired up to a client. Most of the time this
203-
means testing selected methods on their own. They are meant to test the logic
204-
of your code and make sure that you get expected outputs out of selected inputs.
201+
Our unit tests are grouped together with tests against the ``eth-tester`` library via
202+
the ``EthereumTesterProvider``.
205203

206-
Our unit tests live under appropriately named child directories within the
207-
``/tests`` directory. The core of the unit tests live under ``/tests/core``.
208-
Do your best to follow the existing structure when choosing where to add
209-
your unit test.
204+
These tests live under appropriately named child directories within the
205+
``/tests`` directory. The core of these tests live under ``/tests/core``.
206+
Do your best to follow the existing structure when adding a test and make sure
207+
that its location makes sense.
210208

211209
Integration Testing
212210
^^^^^^^^^^^^^^^^^^^
@@ -217,25 +215,76 @@ confused with pytest fixtures). These zip file fixtures, which also live in the
217215
``/tests/integration`` directory, are configured to run the specific client we are
218216
testing against along with a genesis configuration that gives our tests some
219217
pre-determined useful objects (like unlocked, pre-loaded accounts) to be able to
220-
interact with the client and run our tests.
218+
interact with the client when we run our tests.
221219

222-
Though the setup lives in ``/tests/integration``, our integration module tests are
223-
written across different files within ``/web3/_utils/module_testing``. The tests
224-
are written there but run configurations exist across the different files within
225-
``/tests/integration/``. The parent ``/integration`` directory houses some common
226-
configuration shared across all client tests, whereas the ``/go_ethereum`` directory
227-
houses common code to be shared among respective client tests.
220+
The parent ``/integration`` directory houses some common configuration shared across
221+
all client tests, whereas the ``/go_ethereum`` directory houses common code to be
222+
shared across geth-specific provider tests. Though the setup run configurations exist
223+
across the different files within ``/tests/integration``, our integration module tests
224+
are written across different files within ``/web3/_utils/module_testing``.
228225

229226
* :file:`common.py` files within the client directories contain code that is shared across
230227
all provider tests (http, ipc, and ws). This is mostly used to override tests that span
231228
across all providers.
232-
* :file:`conftest.py` files within each of these directories contain mostly code that can
233-
be *used* by all test files that exist within the same directory as the :file:`conftest.py`
234-
file. This is mostly used to house pytest fixtures to be shared among our tests. Refer to
235-
the `pytest documentation on fixtures`_ for more information.
236-
* :file:`test_{client}_{provider}.py` (e.g. :file:`test_goethereum_http.py`) files are where
237-
client-and-provider-specific test configurations exist. This is mostly used to override tests
238-
specific to the provider type for the respective client.
229+
* :file:`conftest.py` files within each of these directories contain mostly code that
230+
can be *used* by all test files that exist within the same directory or subdirectories
231+
of the :file:`conftest.py` file. This is mostly used to house pytest fixtures to be
232+
shared among our tests. Refer to the `pytest documentation on fixtures`_ for more
233+
information.
234+
* ``test_{client}_{provider}.py`` files (e.g. :file:`test_goethereum_http.py`) are where
235+
client-and-provider-specific test configurations exist. This is mostly used to
236+
override tests specific to the provider type for the respective client.
237+
238+
239+
Working With Test Contracts
240+
^^^^^^^^^^^^^^^^^^^^^^^^^^^
241+
242+
Contracts used for testing exist under ``web3/_utils/contract_sources``. These contracts
243+
get compiled via the ``compile_contracts.py`` script in the same directory. To use
244+
this script, simply pass the Solidity version to be used to compile the contracts as an
245+
argument at the command line.
246+
247+
Arguments for the script are:
248+
-v or --version Solidity version to be used to compile the contracts. If
249+
blank, the script uses the latest hard-coded version
250+
specified within the script.
251+
252+
-f or --filename If left blank, all .sol files will be compiled and the
253+
respective contract data will be generated. Pass in a
254+
specific ``.sol`` filename here to compile just one file.
255+
256+
257+
To run the script, you will need the ``py-solc-x`` library for compiling the files
258+
as well as ``black`` for linting. You can install those independently or install the
259+
full ``[dev]`` package extra as shown below.
260+
261+
.. code:: sh
262+
263+
$ pip install "web3[dev]"
264+
265+
The following example compiles all the contracts and generates their respective
266+
contract data that is used across our test files for the test suites. This data gets
267+
generated within the ``contract_data`` subdirectory within the ``contract_sources``
268+
folder.
269+
270+
.. code-block:: bash
271+
272+
$ cd ../web3.py/web3/_utils/contract_sources
273+
$ python compile_contracts.py -v 0.8.17
274+
Compiling OffchainLookup
275+
...
276+
...
277+
reformatted ...
278+
279+
To compile and generate contract data for only one ``.sol`` file, specify using the
280+
filename with the ``-f`` (or ``--filename``) argument flag.
281+
282+
.. code-block:: bash
283+
284+
$ cd ../web3.py/web3/_utils/contract_sources
285+
$ python compile_contracts.py -v 0.8.17 -f OffchainLookup.sol
286+
Compiling OffchainLookup.sol
287+
reformatted ...
239288
240289
241290
Manual Testing
@@ -246,7 +295,7 @@ you can install it from your development directory:
246295

247296
.. code:: sh
248297
249-
$ pip install -e ../path/to/web3py
298+
$ pip install -e ../path/to/web3py
250299
251300
252301
Documentation
Lines changed: 122 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,94 @@
1+
"""
2+
Arguments for the script are:
3+
-v or --version Solidity version to be used to compile the contracts. If
4+
blank, the script uses the latest hard-coded version
5+
specified within the script.
6+
7+
-f or --filename If left blank, all .sol files will be compiled and the
8+
respective contract data will be generated. Pass in a
9+
specific ``.sol`` filename here to compile just one file.
10+
11+
12+
To run the script, you will need the ``py-solc-x`` library for compiling the files
13+
as well as ``black`` for linting. You can install those independently or install the
14+
full ``[dev]`` package extra as shown below.
15+
16+
.. code:: sh
17+
18+
$ pip install "web3[dev]"
19+
20+
The following example compiles all the contracts and generates their respective
21+
contract data that is used across our test files for the test suites. This data gets
22+
generated within the ``contract_data`` subdirectory within the ``contract_sources``
23+
folder.
24+
25+
.. code-block:: bash
26+
27+
$ cd ../web3.py/web3/_utils/contract_sources
28+
$ python compile_contracts.py -v 0.8.17
29+
Compiling OffchainLookup
30+
...
31+
...
32+
reformatted ...
33+
34+
To compile and generate contract data for only one ``.sol`` file, specify using the
35+
filename with the ``-f`` (or ``--filename``) argument flag.
36+
37+
.. code-block:: bash
38+
39+
$ cd ../web3.py/web3/_utils/contract_sources
40+
$ python compile_contracts.py -v 0.8.17 -f OffchainLookup.sol
41+
Compiling OffchainLookup.sol
42+
reformatted ...
43+
"""
44+
45+
46+
import argparse
147
import os
248
import re
3-
import sys
49+
import warnings
450
from typing import (
551
Any,
652
)
753

854
import solcx
955

10-
SOLIDITY_VERSION_REGEX = re.compile(r"\d*\.\d+") # 0.0.0 pattern
11-
CONFIGURED_SOLIDITY_VERSION = "0.8.17"
12-
13-
user_provided_version = sys.argv[-1]
14-
15-
solidity_version = (
16-
user_provided_version
17-
if SOLIDITY_VERSION_REGEX.match(user_provided_version)
18-
else CONFIGURED_SOLIDITY_VERSION
56+
arg_parser = argparse.ArgumentParser()
57+
arg_parser.add_argument(
58+
"-v", "--version", help="Solidity version for compiling contracts."
59+
)
60+
arg_parser.add_argument(
61+
"-f",
62+
"--filename",
63+
help="(optional) The filename if only one file is to be compiled - "
64+
"otherwise all .sol files will be compiled at once.",
1965
)
66+
user_args = arg_parser.parse_args()
2067

68+
CONFIGURED_SOLIDITY_VERSION = "0.8.17"
69+
# establish Solidity version from user-provided arg or use hard-coded version
70+
user_sol_version = user_args.version
2171

72+
solidity_version = user_sol_version if user_sol_version else CONFIGURED_SOLIDITY_VERSION
2273
solcx.install_solc(solidity_version)
2374
solcx.set_solc_version(solidity_version)
2475

2576

26-
def compile_dot_sol_files(dot_sol_filename: str) -> dict[str, Any]:
77+
# compile just the .sol file specified or all .sol files
78+
all_dot_sol_files = [f for f in os.listdir(os.getcwd()) if f.endswith(".sol")]
79+
user_filename = user_args.filename
80+
files_to_compile = [user_filename] if user_filename else all_dot_sol_files
81+
82+
83+
def _compile_dot_sol_files(dot_sol_filename: str) -> dict[str, Any]:
2784
compiled = solcx.compile_files(
2885
[f"./{dot_sol_filename}"],
2986
output_values=["abi", "bin", "bin-runtime"],
3087
)
3188
return compiled
3289

3390

34-
def get_compiled_contract_data(
91+
def _get_compiled_contract_data(
3592
sol_file_output: dict[str, dict[str, str]],
3693
dot_sol_filename: str,
3794
contract_name: str = None,
@@ -54,8 +111,9 @@ def get_compiled_contract_data(
54111

55112
contracts_in_file = {}
56113

57-
for filename in os.listdir(os.getcwd()):
58-
if ".sol" in filename:
114+
115+
def compile_files(file_list: list[str]) -> None:
116+
for filename in file_list:
59117
with open(os.path.join(os.getcwd(), filename), "r") as f:
60118
dot_sol_file = f.readlines()
61119

@@ -70,54 +128,54 @@ def get_compiled_contract_data(
70128

71129
contracts_in_file[filename] = contract_names
72130

73-
74-
for dot_sol_filename in contracts_in_file.keys():
75-
filename_no_extension = dot_sol_filename.replace(".sol", "")
76-
split_and_lowercase = [
77-
i.lower() for i in re.split(r"([A-Z][a-z]*)", filename_no_extension) if i
78-
]
79-
python_filename = f"{'_'.join(split_and_lowercase)}.py"
80-
python_file_path = os.path.join(os.getcwd(), "contract_data", python_filename)
81-
82-
try:
83-
# clean up existing files
84-
os.remove(python_file_path)
85-
except FileNotFoundError:
86-
pass
87-
88-
print(f"compiling {dot_sol_filename}")
89-
compiled_dot_sol_data = compile_dot_sol_files(dot_sol_filename)
90-
91-
with open(python_file_path, "w") as f:
92-
f.write(f'"""\nGenerated by `{os.path.basename(__file__)}` script.\n')
93-
f.write(f'Compiled with Solidity v{solidity_version}.\n"""\n\n')
94-
95-
for c in contracts_in_file[dot_sol_filename]:
96-
c_name_split_and_uppercase = [
97-
i.upper() for i in re.split(r"([A-Z0-9][a-z0-9]*)", c) if i
98-
]
99-
contract_upper = "_".join(c_name_split_and_uppercase)
100-
101-
c_data = get_compiled_contract_data(
102-
compiled_dot_sol_data, dot_sol_filename, c
103-
)
104-
105-
contract_source = (
106-
f"# source: web3/_utils/contract_sources/{dot_sol_filename}:{c}"
107-
)
108-
if len(contract_source) > 88:
109-
contract_source += " # noqa: E501"
110-
111-
f.write(f"{contract_source}\n")
112-
f.write(f'{contract_upper}_BYTECODE = "{c_data["bin"]}" # noqa: E501\n')
113-
f.write(
114-
f'{contract_upper}_RUNTIME = "{c_data["bin-runtime"]}" # noqa: E501\n'
115-
)
116-
f.write(f"{contract_upper}_ABI = {c_data['abi']}\n")
117-
f.write(contract_upper + "_DATA = {\n")
118-
f.write(f' "bytecode": {contract_upper}_BYTECODE,\n')
119-
f.write(f' "bytecode_runtime": {contract_upper}_RUNTIME,\n')
120-
f.write(f' "abi": {contract_upper}_ABI,\n')
121-
f.write("}\n\n\n")
122-
131+
for dot_sol_filename in contracts_in_file.keys():
132+
filename_no_extension = dot_sol_filename.replace(".sol", "")
133+
split_and_lowercase = [
134+
i.lower() for i in re.split(r"([A-Z][a-z]*)", filename_no_extension) if i
135+
]
136+
python_filename = f"{'_'.join(split_and_lowercase)}.py"
137+
python_file_path = os.path.join(os.getcwd(), "contract_data", python_filename)
138+
try:
139+
# clean up existing files
140+
os.remove(python_file_path)
141+
except FileNotFoundError:
142+
pass
143+
print(f"compiling {dot_sol_filename}")
144+
compiled_dot_sol_data = _compile_dot_sol_files(dot_sol_filename)
145+
with open(python_file_path, "w") as f:
146+
f.write(f'"""\nGenerated by `{os.path.basename(__file__)}` script.\n')
147+
f.write(f'Compiled with Solidity v{solidity_version}.\n"""\n\n')
148+
149+
for c in contracts_in_file[dot_sol_filename]:
150+
c_name_split_and_uppercase = [
151+
i.upper() for i in re.split(r"([A-Z0-9][a-z0-9]*)", c) if i
152+
]
153+
contract_upper = "_".join(c_name_split_and_uppercase)
154+
155+
c_data = _get_compiled_contract_data(
156+
compiled_dot_sol_data, dot_sol_filename, c
157+
)
158+
159+
contract_source = (
160+
f"# source: web3/_utils/contract_sources/{dot_sol_filename}:{c}"
161+
)
162+
if len(contract_source) > 88:
163+
contract_source += " # noqa: E501"
164+
165+
f.write(f"{contract_source}\n")
166+
f.write(
167+
f'{contract_upper}_BYTECODE = "{c_data["bin"]}" # noqa: E501\n'
168+
)
169+
f.write(
170+
f'{contract_upper}_RUNTIME = "{c_data["bin-runtime"]}" # noqa: E501\n'
171+
)
172+
f.write(f"{contract_upper}_ABI = {c_data['abi']}\n")
173+
f.write(contract_upper + "_DATA = {\n")
174+
f.write(f' "bytecode": {contract_upper}_BYTECODE,\n')
175+
f.write(f' "bytecode_runtime": {contract_upper}_RUNTIME,\n')
176+
f.write(f' "abi": {contract_upper}_ABI,\n')
177+
f.write("}\n\n\n")
178+
179+
180+
compile_files(files_to_compile)
123181
os.system(f"black {os.getcwd()}")

0 commit comments

Comments
 (0)