14
14
from crytic_compile .platform .abstract_platform import AbstractPlatform
15
15
from crytic_compile .platform .exceptions import InvalidCompilation
16
16
from crytic_compile .platform .types import Type
17
- from crytic_compile .utils .naming import convert_filename
17
+ from crytic_compile .utils .naming import convert_filename , extract_name
18
18
from crytic_compile .utils .natspec import Natspec
19
19
20
+ from .solc import relative_to_short
21
+
20
22
# Handle cycle
21
23
if TYPE_CHECKING :
22
24
from crytic_compile import CryticCompile
@@ -60,14 +62,7 @@ def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None:
60
62
cmd = [
61
63
"forge" ,
62
64
"build" ,
63
- "--extra-output" ,
64
- "abi" ,
65
- "--extra-output" ,
66
- "userdoc" ,
67
- "--extra-output" ,
68
- "devdoc" ,
69
- "--extra-output" ,
70
- "evm.methodIdentifiers" ,
65
+ "--build-info" ,
71
66
"--force" ,
72
67
]
73
68
@@ -94,68 +89,99 @@ def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None:
94
89
if stderr :
95
90
LOGGER .error (stderr )
96
91
97
- filenames = Path (self ._target , out_directory ).rglob ("*.json" )
98
-
99
- # foundry only support solc for now
100
- compiler = "solc"
101
- compilation_unit = CompilationUnit (crytic_compile , str (self ._target ))
102
-
103
- for filename_txt in filenames :
104
- with open (filename_txt , encoding = "utf8" ) as file_desc :
105
- target_loaded = json .load (file_desc )
106
-
107
- userdoc = target_loaded .get ("userdoc" , {})
108
- devdoc = target_loaded .get ("devdoc" , {})
109
- natspec = Natspec (userdoc , devdoc )
110
-
111
- if not "ast" in target_loaded :
112
- continue
113
-
114
- filename_str = target_loaded ["ast" ]["absolutePath" ]
115
-
116
- try :
117
- filename = convert_filename (
118
- filename_str , lambda x : x , crytic_compile , working_dir = self ._target
119
- )
120
- except InvalidCompilation as i :
121
- txt = str (i )
122
- txt += "\n Something went wrong, please open an issue in https://github.com/crytic/crytic-compile"
123
- # pylint: disable=raise-missing-from
124
- raise InvalidCompilation (txt )
125
-
126
- source_unit = compilation_unit .create_source_unit (filename )
127
-
128
- source_unit .ast = target_loaded ["ast" ]
129
-
130
- contract_name = filename_txt .parts [- 1 ]
131
- contract_name = contract_name [: - len (".json" )]
132
-
133
- source_unit .natspec [contract_name ] = natspec
134
- compilation_unit .filename_to_contracts [filename ].add (contract_name )
135
- source_unit .contracts_names .add (contract_name )
136
- source_unit .abis [contract_name ] = target_loaded ["abi" ]
137
- source_unit .bytecodes_init [contract_name ] = target_loaded ["bytecode" ][
138
- "object"
139
- ].replace ("0x" , "" )
140
- source_unit .bytecodes_runtime [contract_name ] = target_loaded ["deployedBytecode" ][
141
- "object"
142
- ].replace ("0x" , "" )
143
- source_unit .srcmaps_init [contract_name ] = (
144
- target_loaded ["bytecode" ]["sourceMap" ].split (";" )
145
- if target_loaded ["bytecode" ].get ("sourceMap" )
146
- else []
147
- )
148
- source_unit .srcmaps_runtime [contract_name ] = (
149
- target_loaded ["deployedBytecode" ]["sourceMap" ].split (";" )
150
- if target_loaded ["deployedBytecode" ].get ("sourceMap" )
151
- else []
152
- )
92
+ build_directory = Path (
93
+ self ._target ,
94
+ out_directory ,
95
+ "build-info" ,
96
+ )
97
+ files = sorted (
98
+ os .listdir (build_directory ), key = lambda x : os .path .getmtime (Path (build_directory , x ))
99
+ )
100
+ files = [f for f in files if f .endswith (".json" )]
101
+ if not files :
102
+ txt = f"`forge build` failed. Can you run it?\n { build_directory } is empty"
103
+ raise InvalidCompilation (txt )
153
104
154
- version , optimized , runs = _get_config_info (self ._target )
105
+ for file in files :
106
+ build_info = Path (build_directory , file )
155
107
156
- compilation_unit .compiler_version = CompilerVersion (
157
- compiler = compiler , version = version , optimized = optimized , optimize_runs = runs
158
- )
108
+ # The file here should always ends .json, but just in case use ife
109
+ uniq_id = file if ".json" not in file else file [0 :- 5 ]
110
+ compilation_unit = CompilationUnit (crytic_compile , uniq_id )
111
+
112
+ with open (build_info , encoding = "utf8" ) as file_desc :
113
+ loaded_json = json .load (file_desc )
114
+
115
+ targets_json = loaded_json ["output" ]
116
+
117
+ version_from_config = loaded_json ["solcVersion" ] # TODO supper vyper
118
+ input_json = loaded_json ["input" ]
119
+ compiler = "solc" if input_json ["language" ] == "Solidity" else "vyper"
120
+ optimized = input_json ["settings" ]["optimizer" ]["enabled" ]
121
+
122
+ compilation_unit .compiler_version = CompilerVersion (
123
+ compiler = compiler , version = version_from_config , optimized = optimized
124
+ )
125
+
126
+ skip_filename = compilation_unit .compiler_version .version in [
127
+ f"0.4.{ x } " for x in range (0 , 10 )
128
+ ]
129
+
130
+ if "contracts" in targets_json :
131
+ for original_filename , contracts_info in targets_json ["contracts" ].items ():
132
+
133
+ filename = convert_filename (
134
+ original_filename ,
135
+ relative_to_short ,
136
+ crytic_compile ,
137
+ working_dir = self ._target ,
138
+ )
139
+
140
+ source_unit = compilation_unit .create_source_unit (filename )
141
+
142
+ for original_contract_name , info in contracts_info .items ():
143
+ contract_name = extract_name (original_contract_name )
144
+
145
+ source_unit .contracts_names .add (contract_name )
146
+ compilation_unit .filename_to_contracts [filename ].add (contract_name )
147
+
148
+ source_unit .abis [contract_name ] = info ["abi" ]
149
+ source_unit .bytecodes_init [contract_name ] = info ["evm" ]["bytecode" ][
150
+ "object"
151
+ ]
152
+ source_unit .bytecodes_runtime [contract_name ] = info ["evm" ][
153
+ "deployedBytecode"
154
+ ]["object" ]
155
+ source_unit .srcmaps_init [contract_name ] = info ["evm" ]["bytecode" ][
156
+ "sourceMap"
157
+ ].split (";" )
158
+ source_unit .srcmaps_runtime [contract_name ] = info ["evm" ][
159
+ "deployedBytecode"
160
+ ]["sourceMap" ].split (";" )
161
+ userdoc = info .get ("userdoc" , {})
162
+ devdoc = info .get ("devdoc" , {})
163
+ natspec = Natspec (userdoc , devdoc )
164
+ source_unit .natspec [contract_name ] = natspec
165
+
166
+ if "sources" in targets_json :
167
+ for path , info in targets_json ["sources" ].items ():
168
+ if skip_filename :
169
+ path = convert_filename (
170
+ self ._target ,
171
+ relative_to_short ,
172
+ crytic_compile ,
173
+ working_dir = self ._target ,
174
+ )
175
+ else :
176
+ path = convert_filename (
177
+ path ,
178
+ relative_to_short ,
179
+ crytic_compile ,
180
+ working_dir = self ._target ,
181
+ )
182
+
183
+ source_unit = compilation_unit .create_source_unit (path )
184
+ source_unit .ast = info ["ast" ]
159
185
160
186
@staticmethod
161
187
def is_supported (target : str , ** kwargs : str ) -> bool :
@@ -197,65 +223,3 @@ def _guessed_tests(self) -> List[str]:
197
223
List[str]: The guessed unit tests commands
198
224
"""
199
225
return ["forge test" ]
200
-
201
-
202
- def _get_config_info (target : str ) -> Tuple [str , Optional [bool ], Optional [int ]]:
203
- """get the compiler version from solidity-files-cache.json
204
-
205
- Args:
206
- target (str): path to the project directory
207
-
208
- Returns:
209
- (str, str, str): compiler version, optimized, runs
210
-
211
- Raises:
212
- InvalidCompilation: If cache/solidity-files-cache.json cannot be parsed
213
- """
214
- config = Path (target , "cache" , "solidity-files-cache.json" )
215
- if not config .exists ():
216
- raise InvalidCompilation (
217
- "Could not find the cache/solidity-files-cache.json file."
218
- + "If you are using 'cache = true' in foundry's config file, please remove it."
219
- + " Otherwise please open an issue in https://github.com/crytic/crytic-compile"
220
- )
221
- with open (config , "r" , encoding = "utf8" ) as config_f :
222
- config_dict = json .load (config_f )
223
-
224
- version : Optional [str ] = None
225
- optimizer : Optional [bool ] = None
226
- runs : Optional [int ] = None
227
-
228
- if "files" in config_dict :
229
- items = list (config_dict ["files" ].values ())
230
- # On the form
231
- # { ..
232
- # "artifacts": {
233
- # "CONTRACT_NAME": {
234
- # "0.8.X+commit...": "filename"}
235
- #
236
- if len (items ) >= 1 :
237
- item = items [0 ]
238
- if "artifacts" in item :
239
- items_artifact = list (item ["artifacts" ].values ())
240
- if len (items_artifact ) >= 1 :
241
- item_version = items_artifact [0 ]
242
- version = list (item_version .keys ())[0 ]
243
- assert version
244
- plus_position = version .find ("+" )
245
- if plus_position > 0 :
246
- version = version [:plus_position ]
247
- if (
248
- "solcConfig" in item
249
- and "settings" in item ["solcConfig" ]
250
- and "optimizer" in item ["solcConfig" ]["settings" ]
251
- ):
252
- optimizer = item ["solcConfig" ]["settings" ]["optimizer" ]["enabled" ]
253
- runs = item ["solcConfig" ]["settings" ]["optimizer" ].get ("runs" , None )
254
-
255
- if version is None :
256
- raise InvalidCompilation (
257
- "Something went wrong with cache/solidity-files-cache.json parsing"
258
- + ". Please open an issue in https://github.com/crytic/crytic-compile"
259
- )
260
-
261
- return version , optimizer , runs
0 commit comments