forked from ethereum/web3.py
/
compile_contracts.py
182 lines (142 loc) · 6.28 KB
/
compile_contracts.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
"""
Arguments for the script are:
-v or --version Solidity version to be used to compile the contracts. If
blank, the script uses the latest hard-coded version
specified within the script.
-f or --filename If left blank, all .sol files will be compiled and the
respective contract data will be generated. Pass in a
specific ``.sol`` filename here to compile just one file.
To run the script, you will need the ``py-solc-x`` library for compiling the files
as well as ``black`` for linting. You can install those independently or install the
full ``[dev]`` package extra as shown below.
.. code:: sh
$ pip install "web3[dev]"
The following example compiles all the contracts and generates their respective
contract data that is used across our test files for the test suites. This data gets
generated within the ``contract_data`` subdirectory within the ``contract_sources``
folder.
.. code-block:: bash
$ cd ../web3.py/web3/_utils/contract_sources
$ python compile_contracts.py -v 0.8.17
Compiling OffchainLookup
...
...
reformatted ...
To compile and generate contract data for only one ``.sol`` file, specify using the
filename with the ``-f`` (or ``--filename``) argument flag.
.. code-block:: bash
$ cd ../web3.py/web3/_utils/contract_sources
$ python compile_contracts.py -v 0.8.17 -f OffchainLookup.sol
Compiling OffchainLookup.sol
reformatted ...
"""
import argparse
import os
import re
from typing import (
Any,
Dict,
List,
)
import solcx
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument(
"-v", "--version", help="Solidity version for compiling contracts."
)
arg_parser.add_argument(
"-f",
"--filename",
help="(optional) The filename if only one file is to be compiled - "
"otherwise all .sol files will be compiled at once.",
)
user_args = arg_parser.parse_args()
CONFIGURED_SOLIDITY_VERSION = "0.8.18"
# establish Solidity version from user-provided arg or use hard-coded version
user_sol_version = user_args.version
solidity_version = user_sol_version if user_sol_version else CONFIGURED_SOLIDITY_VERSION
solcx.install_solc(solidity_version)
solcx.set_solc_version(solidity_version)
# compile just the .sol file specified or all .sol files
all_dot_sol_files = [f for f in os.listdir(os.getcwd()) if f.endswith(".sol")]
user_filename = user_args.filename
files_to_compile = [user_filename] if user_filename else all_dot_sol_files
def _compile_dot_sol_files(dot_sol_filename: str) -> Dict[str, Any]:
compiled = solcx.compile_files(
[f"./{dot_sol_filename}"],
output_values=["abi", "bin", "bin-runtime"],
)
return compiled
def _get_compiled_contract_data(
sol_file_output: Dict[str, Dict[str, str]],
dot_sol_filename: str,
contract_name: str = None,
) -> Dict[str, str]:
if not contract_name:
contract_name = dot_sol_filename.replace(".sol", "")
contract_data = None
for key in sol_file_output.keys():
if f":{contract_name}" in key:
contract_data = sol_file_output[key]
if not contract_data:
raise Exception(f"Could not find compiled data for contract: {contract_name}")
contract_data["bin"] = f"0x{contract_data['bin']}"
contract_data["bin-runtime"] = f"0x{contract_data['bin-runtime']}"
return contract_data
contracts_in_file = {}
def compile_files(file_list: List[str]) -> None:
for filename in file_list:
with open(os.path.join(os.getcwd(), filename), "r") as f:
dot_sol_file = f.readlines()
contract_names = []
for line in dot_sol_file:
if all(_ in line for _ in ["contract", "{"]) and "abstract" not in line:
start_index = line.find("contract ") + len("contract ")
end_index = line[start_index:].find(" ") + start_index
contract_name = line[start_index:end_index]
contract_names.append(contract_name)
contracts_in_file[filename] = contract_names
for dot_sol_filename in contracts_in_file.keys():
filename_no_extension = dot_sol_filename.replace(".sol", "")
split_and_lowercase = [
i.lower() for i in re.split(r"([A-Z][a-z]*)", filename_no_extension) if i
]
python_filename = f"{'_'.join(split_and_lowercase)}.py"
python_file_path = os.path.join(os.getcwd(), "contract_data", python_filename)
try:
# clean up existing files
os.remove(python_file_path)
except FileNotFoundError:
pass
print(f"compiling {dot_sol_filename}")
compiled_dot_sol_data = _compile_dot_sol_files(dot_sol_filename)
with open(python_file_path, "w") as f:
f.write(f'"""\nGenerated by `{os.path.basename(__file__)}` script.\n')
f.write(f'Compiled with Solidity v{solidity_version}.\n"""\n\n')
for c in contracts_in_file[dot_sol_filename]:
c_name_split_and_uppercase = [
i.upper() for i in re.split(r"([A-Z0-9][a-z0-9]*)", c) if i
]
contract_upper = "_".join(c_name_split_and_uppercase)
c_data = _get_compiled_contract_data(
compiled_dot_sol_data, dot_sol_filename, c
)
contract_source = (
f"# source: web3/_utils/contract_sources/{dot_sol_filename}:{c}"
)
if len(contract_source) > 88:
contract_source += " # noqa: E501"
f.write(f"{contract_source}\n")
f.write(
f'{contract_upper}_BYTECODE = "{c_data["bin"]}" # noqa: E501\n'
)
f.write(
f'{contract_upper}_RUNTIME = "{c_data["bin-runtime"]}" # noqa: E501\n'
)
f.write(f"{contract_upper}_ABI = {c_data['abi']}\n")
f.write(contract_upper + "_DATA = {\n")
f.write(f' "bytecode": {contract_upper}_BYTECODE,\n')
f.write(f' "bytecode_runtime": {contract_upper}_RUNTIME,\n')
f.write(f' "abi": {contract_upper}_ABI,\n')
f.write("}\n\n\n")
compile_files(files_to_compile)
os.system(f"black {os.getcwd()}")