crytic_compile.platform.waffle
Waffle platform
1""" 2Waffle platform 3""" 4 5import json 6import logging 7import os 8import re 9import shutil 10import subprocess 11import tempfile 12from pathlib import Path 13from typing import TYPE_CHECKING, Dict, List, Optional 14 15from crytic_compile.compilation_unit import CompilationUnit 16from crytic_compile.compiler.compiler import CompilerVersion 17from crytic_compile.platform.abstract_platform import AbstractPlatform, PlatformConfig 18from crytic_compile.platform.exceptions import InvalidCompilation 19from crytic_compile.platform.types import Type 20from crytic_compile.utils.naming import convert_filename 21 22# Handle cycle 23from crytic_compile.utils.natspec import Natspec 24 25if TYPE_CHECKING: 26 from crytic_compile import CryticCompile 27 28LOGGER = logging.getLogger("CryticCompile") 29 30 31class Waffle(AbstractPlatform): 32 """ 33 Waffle platform 34 """ 35 36 NAME = "Waffle" 37 PROJECT_URL = "https://github.com/EthWorks/Waffle" 38 TYPE = Type.WAFFLE 39 40 # pylint: disable=too-many-locals,too-many-branches,too-many-statements 41 def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None: 42 """Compile the project and populate the CryticCompile object 43 44 Args: 45 crytic_compile (CryticCompile): Associated CryticCompile 46 **kwargs: optional arguments. Used "waffle_ignore_compile", "ignore_compile", "npx_disable", 47 "waffle_config_file" 48 49 Raises: 50 InvalidCompilation: If the waffle failed to run 51 """ 52 53 waffle_ignore_compile = kwargs.get("waffle_ignore_compile", False) or kwargs.get( 54 "ignore_compile", False 55 ) 56 target = self._target 57 58 cmd = ["waffle"] 59 if not kwargs.get("npx_disable", False): 60 cmd = ["npx"] + cmd 61 62 # Default behaviour (without any config_file) 63 build_directory = os.path.join("build") 64 compiler = "native" 65 config: Dict = {} 66 67 config_file = kwargs.get("waffle_config_file", "waffle.json") 68 69 potential_config_files = list(Path(target).rglob("*waffle*.json")) 70 if potential_config_files and len(potential_config_files) == 1: 71 config_file = str(potential_config_files[0]) 72 73 # Read config file 74 if config_file: 75 config = _load_config(config_file) 76 77 # old version 78 if "compiler" in config: 79 compiler = config["compiler"] 80 if "compilerType" in config: 81 compiler = config["compilerType"] 82 83 if "compilerVersion" in config: 84 version = config["compilerVersion"] 85 else: 86 version = _get_version(compiler, target, config=config) 87 88 if "targetPath" in config: 89 build_directory = config["targetPath"] 90 91 else: 92 version = _get_version(compiler, target) 93 94 if "outputType" not in config or config["outputType"] != "all": 95 config["outputType"] = "all" 96 97 needed_config = { 98 "compilerOptions": { 99 "outputSelection": { 100 "*": { 101 "*": [ 102 "evm.bytecode.object", 103 "evm.deployedBytecode.object", 104 "abi", 105 "evm.bytecode.sourceMap", 106 "evm.deployedBytecode.sourceMap", 107 ], 108 "": ["ast"], 109 } 110 } 111 } 112 } 113 114 # Set the config as it should be 115 if "compilerOptions" in config: 116 curr_config: Dict = config["compilerOptions"] 117 curr_needed_config: Dict = needed_config["compilerOptions"] 118 if "outputSelection" in curr_config: 119 curr_config = curr_config["outputSelection"] 120 curr_needed_config = curr_needed_config["outputSelection"] 121 if "*" in curr_config: 122 curr_config = curr_config["*"] 123 curr_needed_config = curr_needed_config["*"] 124 if "*" in curr_config: 125 curr_config["*"] += curr_needed_config["*"] 126 else: 127 curr_config["*"] = curr_needed_config["*"] 128 129 if "" in curr_config: 130 curr_config[""] += curr_needed_config[""] 131 else: 132 curr_config[""] = curr_needed_config[""] 133 134 else: 135 curr_config["*"] = curr_needed_config["*"] 136 137 else: 138 curr_config["outputSelection"] = curr_needed_config["outputSelection"] 139 else: 140 config["compilerOptions"] = needed_config["compilerOptions"] 141 142 if not waffle_ignore_compile: 143 with tempfile.NamedTemporaryFile(mode="w", suffix=".json", dir=target) as file_desc: 144 json.dump(config, file_desc) 145 file_desc.flush() 146 147 # cmd += [os.path.relpath(file_desc.name)] 148 cmd += [Path(file_desc.name).name] 149 150 LOGGER.info("Temporary file created: %s", file_desc.name) 151 LOGGER.info("'%s running", " ".join(cmd)) 152 153 try: 154 with subprocess.Popen( 155 cmd, 156 stdout=subprocess.PIPE, 157 stderr=subprocess.PIPE, 158 cwd=target, 159 executable=shutil.which(cmd[0]), 160 ) as process: 161 stdout, stderr = process.communicate() 162 if stdout: 163 LOGGER.info(stdout.decode(errors="backslashreplace")) 164 if stderr: 165 LOGGER.error(stderr.decode(errors="backslashreplace")) 166 except OSError as error: 167 # pylint: disable=raise-missing-from 168 raise InvalidCompilation(error) 169 170 if not os.path.isdir(os.path.join(target, build_directory)): 171 raise InvalidCompilation("`waffle` compilation failed: build directory not found") 172 173 combined_path = os.path.join(target, build_directory, "Combined-Json.json") 174 if not os.path.exists(combined_path): 175 raise InvalidCompilation("`Combined-Json.json` not found") 176 177 with open(combined_path, encoding="utf8") as f: 178 target_all = json.load(f) 179 180 optimized = None 181 182 compilation_unit = CompilationUnit(crytic_compile, str(target)) 183 184 if "sources" in target_all: 185 compilation_unit.filenames = [ 186 convert_filename(path, _relative_to_short, crytic_compile, working_dir=target) 187 for path in target_all["sources"] 188 ] 189 190 for contract in target_all["contracts"]: 191 target_loaded = target_all["contracts"][contract] 192 contract = contract.split(":") 193 filename = convert_filename( 194 contract[0], _relative_to_short, crytic_compile, working_dir=target 195 ) 196 197 contract_name = contract[1] 198 source_unit = compilation_unit.create_source_unit(filename) 199 200 source_unit.ast = target_all["sources"][contract[0]]["AST"] 201 compilation_unit.filename_to_contracts[filename].add(contract_name) 202 source_unit.add_contract_name(contract_name) 203 source_unit.abis[contract_name] = target_loaded["abi"] 204 205 userdoc = target_loaded.get("userdoc", {}) 206 devdoc = target_loaded.get("devdoc", {}) 207 natspec = Natspec(userdoc, devdoc) 208 source_unit.natspec[contract_name] = natspec 209 210 source_unit.bytecodes_init[contract_name] = target_loaded["evm"]["bytecode"]["object"] 211 source_unit.srcmaps_init[contract_name] = target_loaded["evm"]["bytecode"][ 212 "sourceMap" 213 ].split(";") 214 source_unit.bytecodes_runtime[contract_name] = target_loaded["evm"]["deployedBytecode"][ 215 "object" 216 ] 217 source_unit.srcmaps_runtime[contract_name] = target_loaded["evm"]["deployedBytecode"][ 218 "sourceMap" 219 ].split(";") 220 221 compilation_unit.compiler_version = CompilerVersion( 222 compiler=compiler, version=version, optimized=optimized 223 ) 224 225 def clean(self, **_kwargs: str) -> None: 226 """Clean compilation artifacts 227 228 Args: 229 **_kwargs: unused. 230 """ 231 return 232 233 @staticmethod 234 def is_supported(target: str, **kwargs: str) -> bool: 235 """Check if the target is a waffle project 236 237 Args: 238 target (str): path to the target 239 **kwargs: optional arguments. Used "waffle_ignore" 240 241 Returns: 242 bool: True if the target is a waffle project 243 """ 244 waffle_ignore = kwargs.get("waffle_ignore", False) 245 if waffle_ignore: 246 return False 247 248 if os.path.isfile(os.path.join(target, "waffle.json")) or os.path.isfile( 249 os.path.join(target, ".waffle.json") 250 ): 251 return True 252 253 if os.path.isfile(os.path.join(target, "package.json")): 254 with open(os.path.join(target, "package.json"), encoding="utf8") as file_desc: 255 package = json.load(file_desc) 256 if "dependencies" in package: 257 return "ethereum-waffle" in package["dependencies"] 258 if "devDependencies" in package: 259 return "ethereum-waffle" in package["devDependencies"] 260 261 return False 262 263 @staticmethod 264 def config(working_dir: str) -> Optional[PlatformConfig]: 265 """Return configuration data that should be passed to solc, such as remappings. 266 267 Args: 268 working_dir (str): path to the working directory 269 270 Returns: 271 Optional[PlatformConfig]: Platform configuration data such as optimization, remappings... 272 """ 273 return None 274 275 def is_dependency(self, path: str) -> bool: 276 """Check if the path is a dependency 277 278 Args: 279 path (str): path to the target 280 281 Returns: 282 bool: True if the target is a dependency 283 """ 284 if path in self._cached_dependencies: 285 return self._cached_dependencies[path] 286 ret = "node_modules" in Path(path).parts 287 self._cached_dependencies[path] = ret 288 return ret 289 290 def _guessed_tests(self) -> List[str]: 291 """Guess the potential unit tests commands 292 293 Returns: 294 List[str]: The guessed unit tests commands 295 """ 296 return ["npx mocha"] 297 298 299def _load_config(config_file: str) -> Dict: 300 """Load the config file 301 302 Args: 303 config_file (str): config file to load 304 305 Raises: 306 InvalidCompilation: If the config file lacks "module.export" 307 308 Returns: 309 Dict: [description] 310 """ 311 with open( 312 config_file, 313 "r", 314 encoding="utf8", 315 ) as file_desc: 316 content = file_desc.read() 317 318 if "module.exports" in content: 319 raise InvalidCompilation("module.export is required for waffle") 320 return json.loads(content) 321 322 323def _get_version(compiler: str, cwd: str, config: Optional[Dict] = None) -> str: 324 """Return the solidity version used 325 326 Args: 327 compiler (str): compiler used 328 cwd (str): Working directory 329 config (Optional[Dict], optional): Config as a json. Defaults to None. 330 331 Raises: 332 InvalidCompilation: If the solidity version was not found 333 334 Returns: 335 str: Solidity version used 336 """ 337 version = "" 338 if config is not None and "solcVersion" in config: 339 version = re.findall(r"\d+\.\d+\.\d+", config["solcVersion"])[0] 340 341 elif config is not None and compiler == "dockerized-solc": 342 version = config["docker-tag"] 343 344 elif compiler == "native": 345 cmd = ["solc", "--version"] 346 try: 347 with subprocess.Popen( 348 cmd, 349 stdout=subprocess.PIPE, 350 stderr=subprocess.PIPE, 351 cwd=cwd, 352 executable=shutil.which(cmd[0]), 353 ) as process: 354 stdout_bytes, _ = process.communicate() 355 stdout_txt = stdout_bytes.decode() # convert bytestrings to unicode strings 356 stdout = stdout_txt.split("\n") 357 for line in stdout: 358 if "Version" in line: 359 version = re.findall(r"\d+\.\d+\.\d+", line)[0] 360 except OSError as error: 361 # pylint: disable=raise-missing-from 362 raise InvalidCompilation(error) 363 364 elif compiler in ["solc-js"]: 365 cmd = ["solcjs", "--version"] 366 try: 367 with subprocess.Popen( 368 cmd, 369 stdout=subprocess.PIPE, 370 stderr=subprocess.PIPE, 371 cwd=cwd, 372 executable=shutil.which(cmd[0]), 373 ) as process: 374 stdout_bytes, _ = process.communicate() 375 stdout_txt = stdout_bytes.decode() # convert bytestrings to unicode strings 376 version = re.findall(r"\d+\.\d+\.\d+", stdout_txt)[0] 377 except OSError as error: 378 # pylint: disable=raise-missing-from 379 raise InvalidCompilation(error) 380 381 else: 382 raise InvalidCompilation(f"Solidity version not found {compiler}") 383 384 return version 385 386 387def _relative_to_short(relative: Path) -> Path: 388 """Translate relative path to short 389 390 Args: 391 relative (Path): path to the target 392 393 Returns: 394 Path: Translated path 395 """ 396 short = relative 397 try: 398 short = short.relative_to(Path("contracts")) 399 except ValueError: 400 try: 401 short = short.relative_to("node_modules") 402 except ValueError: 403 pass 404 return short
32class Waffle(AbstractPlatform): 33 """ 34 Waffle platform 35 """ 36 37 NAME = "Waffle" 38 PROJECT_URL = "https://github.com/EthWorks/Waffle" 39 TYPE = Type.WAFFLE 40 41 # pylint: disable=too-many-locals,too-many-branches,too-many-statements 42 def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None: 43 """Compile the project and populate the CryticCompile object 44 45 Args: 46 crytic_compile (CryticCompile): Associated CryticCompile 47 **kwargs: optional arguments. Used "waffle_ignore_compile", "ignore_compile", "npx_disable", 48 "waffle_config_file" 49 50 Raises: 51 InvalidCompilation: If the waffle failed to run 52 """ 53 54 waffle_ignore_compile = kwargs.get("waffle_ignore_compile", False) or kwargs.get( 55 "ignore_compile", False 56 ) 57 target = self._target 58 59 cmd = ["waffle"] 60 if not kwargs.get("npx_disable", False): 61 cmd = ["npx"] + cmd 62 63 # Default behaviour (without any config_file) 64 build_directory = os.path.join("build") 65 compiler = "native" 66 config: Dict = {} 67 68 config_file = kwargs.get("waffle_config_file", "waffle.json") 69 70 potential_config_files = list(Path(target).rglob("*waffle*.json")) 71 if potential_config_files and len(potential_config_files) == 1: 72 config_file = str(potential_config_files[0]) 73 74 # Read config file 75 if config_file: 76 config = _load_config(config_file) 77 78 # old version 79 if "compiler" in config: 80 compiler = config["compiler"] 81 if "compilerType" in config: 82 compiler = config["compilerType"] 83 84 if "compilerVersion" in config: 85 version = config["compilerVersion"] 86 else: 87 version = _get_version(compiler, target, config=config) 88 89 if "targetPath" in config: 90 build_directory = config["targetPath"] 91 92 else: 93 version = _get_version(compiler, target) 94 95 if "outputType" not in config or config["outputType"] != "all": 96 config["outputType"] = "all" 97 98 needed_config = { 99 "compilerOptions": { 100 "outputSelection": { 101 "*": { 102 "*": [ 103 "evm.bytecode.object", 104 "evm.deployedBytecode.object", 105 "abi", 106 "evm.bytecode.sourceMap", 107 "evm.deployedBytecode.sourceMap", 108 ], 109 "": ["ast"], 110 } 111 } 112 } 113 } 114 115 # Set the config as it should be 116 if "compilerOptions" in config: 117 curr_config: Dict = config["compilerOptions"] 118 curr_needed_config: Dict = needed_config["compilerOptions"] 119 if "outputSelection" in curr_config: 120 curr_config = curr_config["outputSelection"] 121 curr_needed_config = curr_needed_config["outputSelection"] 122 if "*" in curr_config: 123 curr_config = curr_config["*"] 124 curr_needed_config = curr_needed_config["*"] 125 if "*" in curr_config: 126 curr_config["*"] += curr_needed_config["*"] 127 else: 128 curr_config["*"] = curr_needed_config["*"] 129 130 if "" in curr_config: 131 curr_config[""] += curr_needed_config[""] 132 else: 133 curr_config[""] = curr_needed_config[""] 134 135 else: 136 curr_config["*"] = curr_needed_config["*"] 137 138 else: 139 curr_config["outputSelection"] = curr_needed_config["outputSelection"] 140 else: 141 config["compilerOptions"] = needed_config["compilerOptions"] 142 143 if not waffle_ignore_compile: 144 with tempfile.NamedTemporaryFile(mode="w", suffix=".json", dir=target) as file_desc: 145 json.dump(config, file_desc) 146 file_desc.flush() 147 148 # cmd += [os.path.relpath(file_desc.name)] 149 cmd += [Path(file_desc.name).name] 150 151 LOGGER.info("Temporary file created: %s", file_desc.name) 152 LOGGER.info("'%s running", " ".join(cmd)) 153 154 try: 155 with subprocess.Popen( 156 cmd, 157 stdout=subprocess.PIPE, 158 stderr=subprocess.PIPE, 159 cwd=target, 160 executable=shutil.which(cmd[0]), 161 ) as process: 162 stdout, stderr = process.communicate() 163 if stdout: 164 LOGGER.info(stdout.decode(errors="backslashreplace")) 165 if stderr: 166 LOGGER.error(stderr.decode(errors="backslashreplace")) 167 except OSError as error: 168 # pylint: disable=raise-missing-from 169 raise InvalidCompilation(error) 170 171 if not os.path.isdir(os.path.join(target, build_directory)): 172 raise InvalidCompilation("`waffle` compilation failed: build directory not found") 173 174 combined_path = os.path.join(target, build_directory, "Combined-Json.json") 175 if not os.path.exists(combined_path): 176 raise InvalidCompilation("`Combined-Json.json` not found") 177 178 with open(combined_path, encoding="utf8") as f: 179 target_all = json.load(f) 180 181 optimized = None 182 183 compilation_unit = CompilationUnit(crytic_compile, str(target)) 184 185 if "sources" in target_all: 186 compilation_unit.filenames = [ 187 convert_filename(path, _relative_to_short, crytic_compile, working_dir=target) 188 for path in target_all["sources"] 189 ] 190 191 for contract in target_all["contracts"]: 192 target_loaded = target_all["contracts"][contract] 193 contract = contract.split(":") 194 filename = convert_filename( 195 contract[0], _relative_to_short, crytic_compile, working_dir=target 196 ) 197 198 contract_name = contract[1] 199 source_unit = compilation_unit.create_source_unit(filename) 200 201 source_unit.ast = target_all["sources"][contract[0]]["AST"] 202 compilation_unit.filename_to_contracts[filename].add(contract_name) 203 source_unit.add_contract_name(contract_name) 204 source_unit.abis[contract_name] = target_loaded["abi"] 205 206 userdoc = target_loaded.get("userdoc", {}) 207 devdoc = target_loaded.get("devdoc", {}) 208 natspec = Natspec(userdoc, devdoc) 209 source_unit.natspec[contract_name] = natspec 210 211 source_unit.bytecodes_init[contract_name] = target_loaded["evm"]["bytecode"]["object"] 212 source_unit.srcmaps_init[contract_name] = target_loaded["evm"]["bytecode"][ 213 "sourceMap" 214 ].split(";") 215 source_unit.bytecodes_runtime[contract_name] = target_loaded["evm"]["deployedBytecode"][ 216 "object" 217 ] 218 source_unit.srcmaps_runtime[contract_name] = target_loaded["evm"]["deployedBytecode"][ 219 "sourceMap" 220 ].split(";") 221 222 compilation_unit.compiler_version = CompilerVersion( 223 compiler=compiler, version=version, optimized=optimized 224 ) 225 226 def clean(self, **_kwargs: str) -> None: 227 """Clean compilation artifacts 228 229 Args: 230 **_kwargs: unused. 231 """ 232 return 233 234 @staticmethod 235 def is_supported(target: str, **kwargs: str) -> bool: 236 """Check if the target is a waffle project 237 238 Args: 239 target (str): path to the target 240 **kwargs: optional arguments. Used "waffle_ignore" 241 242 Returns: 243 bool: True if the target is a waffle project 244 """ 245 waffle_ignore = kwargs.get("waffle_ignore", False) 246 if waffle_ignore: 247 return False 248 249 if os.path.isfile(os.path.join(target, "waffle.json")) or os.path.isfile( 250 os.path.join(target, ".waffle.json") 251 ): 252 return True 253 254 if os.path.isfile(os.path.join(target, "package.json")): 255 with open(os.path.join(target, "package.json"), encoding="utf8") as file_desc: 256 package = json.load(file_desc) 257 if "dependencies" in package: 258 return "ethereum-waffle" in package["dependencies"] 259 if "devDependencies" in package: 260 return "ethereum-waffle" in package["devDependencies"] 261 262 return False 263 264 @staticmethod 265 def config(working_dir: str) -> Optional[PlatformConfig]: 266 """Return configuration data that should be passed to solc, such as remappings. 267 268 Args: 269 working_dir (str): path to the working directory 270 271 Returns: 272 Optional[PlatformConfig]: Platform configuration data such as optimization, remappings... 273 """ 274 return None 275 276 def is_dependency(self, path: str) -> bool: 277 """Check if the path is a dependency 278 279 Args: 280 path (str): path to the target 281 282 Returns: 283 bool: True if the target is a dependency 284 """ 285 if path in self._cached_dependencies: 286 return self._cached_dependencies[path] 287 ret = "node_modules" in Path(path).parts 288 self._cached_dependencies[path] = ret 289 return ret 290 291 def _guessed_tests(self) -> List[str]: 292 """Guess the potential unit tests commands 293 294 Returns: 295 List[str]: The guessed unit tests commands 296 """ 297 return ["npx mocha"]
Waffle platform
42 def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None: 43 """Compile the project and populate the CryticCompile object 44 45 Args: 46 crytic_compile (CryticCompile): Associated CryticCompile 47 **kwargs: optional arguments. Used "waffle_ignore_compile", "ignore_compile", "npx_disable", 48 "waffle_config_file" 49 50 Raises: 51 InvalidCompilation: If the waffle failed to run 52 """ 53 54 waffle_ignore_compile = kwargs.get("waffle_ignore_compile", False) or kwargs.get( 55 "ignore_compile", False 56 ) 57 target = self._target 58 59 cmd = ["waffle"] 60 if not kwargs.get("npx_disable", False): 61 cmd = ["npx"] + cmd 62 63 # Default behaviour (without any config_file) 64 build_directory = os.path.join("build") 65 compiler = "native" 66 config: Dict = {} 67 68 config_file = kwargs.get("waffle_config_file", "waffle.json") 69 70 potential_config_files = list(Path(target).rglob("*waffle*.json")) 71 if potential_config_files and len(potential_config_files) == 1: 72 config_file = str(potential_config_files[0]) 73 74 # Read config file 75 if config_file: 76 config = _load_config(config_file) 77 78 # old version 79 if "compiler" in config: 80 compiler = config["compiler"] 81 if "compilerType" in config: 82 compiler = config["compilerType"] 83 84 if "compilerVersion" in config: 85 version = config["compilerVersion"] 86 else: 87 version = _get_version(compiler, target, config=config) 88 89 if "targetPath" in config: 90 build_directory = config["targetPath"] 91 92 else: 93 version = _get_version(compiler, target) 94 95 if "outputType" not in config or config["outputType"] != "all": 96 config["outputType"] = "all" 97 98 needed_config = { 99 "compilerOptions": { 100 "outputSelection": { 101 "*": { 102 "*": [ 103 "evm.bytecode.object", 104 "evm.deployedBytecode.object", 105 "abi", 106 "evm.bytecode.sourceMap", 107 "evm.deployedBytecode.sourceMap", 108 ], 109 "": ["ast"], 110 } 111 } 112 } 113 } 114 115 # Set the config as it should be 116 if "compilerOptions" in config: 117 curr_config: Dict = config["compilerOptions"] 118 curr_needed_config: Dict = needed_config["compilerOptions"] 119 if "outputSelection" in curr_config: 120 curr_config = curr_config["outputSelection"] 121 curr_needed_config = curr_needed_config["outputSelection"] 122 if "*" in curr_config: 123 curr_config = curr_config["*"] 124 curr_needed_config = curr_needed_config["*"] 125 if "*" in curr_config: 126 curr_config["*"] += curr_needed_config["*"] 127 else: 128 curr_config["*"] = curr_needed_config["*"] 129 130 if "" in curr_config: 131 curr_config[""] += curr_needed_config[""] 132 else: 133 curr_config[""] = curr_needed_config[""] 134 135 else: 136 curr_config["*"] = curr_needed_config["*"] 137 138 else: 139 curr_config["outputSelection"] = curr_needed_config["outputSelection"] 140 else: 141 config["compilerOptions"] = needed_config["compilerOptions"] 142 143 if not waffle_ignore_compile: 144 with tempfile.NamedTemporaryFile(mode="w", suffix=".json", dir=target) as file_desc: 145 json.dump(config, file_desc) 146 file_desc.flush() 147 148 # cmd += [os.path.relpath(file_desc.name)] 149 cmd += [Path(file_desc.name).name] 150 151 LOGGER.info("Temporary file created: %s", file_desc.name) 152 LOGGER.info("'%s running", " ".join(cmd)) 153 154 try: 155 with subprocess.Popen( 156 cmd, 157 stdout=subprocess.PIPE, 158 stderr=subprocess.PIPE, 159 cwd=target, 160 executable=shutil.which(cmd[0]), 161 ) as process: 162 stdout, stderr = process.communicate() 163 if stdout: 164 LOGGER.info(stdout.decode(errors="backslashreplace")) 165 if stderr: 166 LOGGER.error(stderr.decode(errors="backslashreplace")) 167 except OSError as error: 168 # pylint: disable=raise-missing-from 169 raise InvalidCompilation(error) 170 171 if not os.path.isdir(os.path.join(target, build_directory)): 172 raise InvalidCompilation("`waffle` compilation failed: build directory not found") 173 174 combined_path = os.path.join(target, build_directory, "Combined-Json.json") 175 if not os.path.exists(combined_path): 176 raise InvalidCompilation("`Combined-Json.json` not found") 177 178 with open(combined_path, encoding="utf8") as f: 179 target_all = json.load(f) 180 181 optimized = None 182 183 compilation_unit = CompilationUnit(crytic_compile, str(target)) 184 185 if "sources" in target_all: 186 compilation_unit.filenames = [ 187 convert_filename(path, _relative_to_short, crytic_compile, working_dir=target) 188 for path in target_all["sources"] 189 ] 190 191 for contract in target_all["contracts"]: 192 target_loaded = target_all["contracts"][contract] 193 contract = contract.split(":") 194 filename = convert_filename( 195 contract[0], _relative_to_short, crytic_compile, working_dir=target 196 ) 197 198 contract_name = contract[1] 199 source_unit = compilation_unit.create_source_unit(filename) 200 201 source_unit.ast = target_all["sources"][contract[0]]["AST"] 202 compilation_unit.filename_to_contracts[filename].add(contract_name) 203 source_unit.add_contract_name(contract_name) 204 source_unit.abis[contract_name] = target_loaded["abi"] 205 206 userdoc = target_loaded.get("userdoc", {}) 207 devdoc = target_loaded.get("devdoc", {}) 208 natspec = Natspec(userdoc, devdoc) 209 source_unit.natspec[contract_name] = natspec 210 211 source_unit.bytecodes_init[contract_name] = target_loaded["evm"]["bytecode"]["object"] 212 source_unit.srcmaps_init[contract_name] = target_loaded["evm"]["bytecode"][ 213 "sourceMap" 214 ].split(";") 215 source_unit.bytecodes_runtime[contract_name] = target_loaded["evm"]["deployedBytecode"][ 216 "object" 217 ] 218 source_unit.srcmaps_runtime[contract_name] = target_loaded["evm"]["deployedBytecode"][ 219 "sourceMap" 220 ].split(";") 221 222 compilation_unit.compiler_version = CompilerVersion( 223 compiler=compiler, version=version, optimized=optimized 224 )
Compile the project and populate the CryticCompile object
Args: crytic_compile (CryticCompile): Associated CryticCompile **kwargs: optional arguments. Used "waffle_ignore_compile", "ignore_compile", "npx_disable", "waffle_config_file"
Raises: InvalidCompilation: If the waffle failed to run
226 def clean(self, **_kwargs: str) -> None: 227 """Clean compilation artifacts 228 229 Args: 230 **_kwargs: unused. 231 """ 232 return
Clean compilation artifacts
Args: **_kwargs: unused.
234 @staticmethod 235 def is_supported(target: str, **kwargs: str) -> bool: 236 """Check if the target is a waffle project 237 238 Args: 239 target (str): path to the target 240 **kwargs: optional arguments. Used "waffle_ignore" 241 242 Returns: 243 bool: True if the target is a waffle project 244 """ 245 waffle_ignore = kwargs.get("waffle_ignore", False) 246 if waffle_ignore: 247 return False 248 249 if os.path.isfile(os.path.join(target, "waffle.json")) or os.path.isfile( 250 os.path.join(target, ".waffle.json") 251 ): 252 return True 253 254 if os.path.isfile(os.path.join(target, "package.json")): 255 with open(os.path.join(target, "package.json"), encoding="utf8") as file_desc: 256 package = json.load(file_desc) 257 if "dependencies" in package: 258 return "ethereum-waffle" in package["dependencies"] 259 if "devDependencies" in package: 260 return "ethereum-waffle" in package["devDependencies"] 261 262 return False
Check if the target is a waffle project
Args: target (str): path to the target **kwargs: optional arguments. Used "waffle_ignore"
Returns: bool: True if the target is a waffle project
264 @staticmethod 265 def config(working_dir: str) -> Optional[PlatformConfig]: 266 """Return configuration data that should be passed to solc, such as remappings. 267 268 Args: 269 working_dir (str): path to the working directory 270 271 Returns: 272 Optional[PlatformConfig]: Platform configuration data such as optimization, remappings... 273 """ 274 return None
Return configuration data that should be passed to solc, such as remappings.
Args: working_dir (str): path to the working directory
Returns: Optional[PlatformConfig]: Platform configuration data such as optimization, remappings...
276 def is_dependency(self, path: str) -> bool: 277 """Check if the path is a dependency 278 279 Args: 280 path (str): path to the target 281 282 Returns: 283 bool: True if the target is a dependency 284 """ 285 if path in self._cached_dependencies: 286 return self._cached_dependencies[path] 287 ret = "node_modules" in Path(path).parts 288 self._cached_dependencies[path] = ret 289 return ret
Check if the path is a dependency
Args: path (str): path to the target
Returns: bool: True if the target is a dependency