crytic_compile.platform.buidler
Builder platform
1""" 2Builder platform 3""" 4import json 5import logging 6import os 7import shutil 8import subprocess 9from pathlib import Path 10from typing import TYPE_CHECKING, List, Tuple 11 12from crytic_compile.compilation_unit import CompilationUnit 13from crytic_compile.compiler.compiler import CompilerVersion 14from crytic_compile.platform.abstract_platform import AbstractPlatform 15from crytic_compile.platform.exceptions import InvalidCompilation 16from crytic_compile.platform.types import Type 17from crytic_compile.utils.naming import convert_filename, extract_name 18from crytic_compile.utils.natspec import Natspec 19 20# Handle cycle 21from .solc import relative_to_short 22 23if TYPE_CHECKING: 24 from crytic_compile import CryticCompile 25 26LOGGER = logging.getLogger("CryticCompile") 27 28 29class Buidler(AbstractPlatform): 30 """ 31 Builder platform 32 """ 33 34 NAME = "Buidler" 35 PROJECT_URL = "https://github.com/nomiclabs/buidler" 36 TYPE = Type.BUILDER 37 38 # pylint: disable=too-many-locals,too-many-statements,too-many-branches 39 def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None: 40 """Run the compilation 41 42 Args: 43 crytic_compile (CryticCompile): Associated CryticCompile objects 44 **kwargs: optional arguments. Used: "buidler_cache_directory", "buidler_ignore_compile", "ignore_compile", 45 "buidler_working_dir", "buidler_skip_directory_name_fix", "npx_disable" 46 47 Raises: 48 InvalidCompilation: If buidler failed to run 49 """ 50 51 cache_directory = kwargs.get("buidler_cache_directory", "") 52 target_solc_file = os.path.join(cache_directory, "solc-output.json") 53 target_vyper_file = os.path.join(cache_directory, "vyper-docker-updates.json") 54 buidler_ignore_compile = kwargs.get("buidler_ignore_compile", False) or kwargs.get( 55 "ignore_compile", False 56 ) 57 buidler_working_dir = kwargs.get("buidler_working_dir", None) 58 # See https://github.com/crytic/crytic-compile/issues/116 59 skip_directory_name_fix = kwargs.get("buidler_skip_directory_name_fix", False) 60 61 base_cmd = ["buidler"] 62 if not kwargs.get("npx_disable", False): 63 base_cmd = ["npx"] + base_cmd 64 65 if not buidler_ignore_compile: 66 cmd = base_cmd + ["compile"] 67 68 LOGGER.info( 69 "'%s' running", 70 " ".join(cmd), 71 ) 72 73 with subprocess.Popen( 74 cmd, 75 stdout=subprocess.PIPE, 76 stderr=subprocess.PIPE, 77 cwd=self._target, 78 executable=shutil.which(cmd[0]), 79 ) as process: 80 81 stdout_bytes, stderr_bytes = process.communicate() 82 stdout, stderr = ( 83 stdout_bytes.decode(errors="backslashreplace"), 84 stderr_bytes.decode(errors="backslashreplace"), 85 ) # convert bytestrings to unicode strings 86 87 LOGGER.info(stdout) 88 if stderr: 89 LOGGER.error(stderr) 90 91 if not os.path.isfile(os.path.join(self._target, target_solc_file)): 92 if os.path.isfile(os.path.join(self._target, target_vyper_file)): 93 txt = "Vyper not yet supported with buidler." 94 txt += " Please open an issue in https://github.com/crytic/crytic-compile" 95 raise InvalidCompilation(txt) 96 txt = f"`buidler compile` failed. Can you run it?\n{os.path.join(self._target, target_solc_file)} not found" 97 raise InvalidCompilation(txt) 98 99 compilation_unit = CompilationUnit(crytic_compile, str(target_solc_file)) 100 101 (compiler, version_from_config, optimized) = _get_version_from_config(Path(cache_directory)) 102 103 compilation_unit.compiler_version = CompilerVersion( 104 compiler=compiler, version=version_from_config, optimized=optimized 105 ) 106 107 skip_filename = compilation_unit.compiler_version.version in [ 108 f"0.4.{x}" for x in range(0, 10) 109 ] 110 111 with open(target_solc_file, encoding="utf8") as file_desc: 112 targets_json = json.load(file_desc) 113 114 if "sources" in targets_json: 115 for path, info in targets_json["sources"].items(): 116 117 if path.startswith("ontracts/") and not skip_directory_name_fix: 118 path = "c" + path 119 120 if skip_filename: 121 path = convert_filename( 122 self._target, 123 relative_to_short, 124 crytic_compile, 125 working_dir=buidler_working_dir, 126 ) 127 else: 128 path = convert_filename( 129 path, relative_to_short, crytic_compile, working_dir=buidler_working_dir 130 ) 131 source_unit = compilation_unit.create_source_unit(path) 132 source_unit.ast = info["ast"] 133 134 if "contracts" in targets_json: 135 for original_filename, contracts_info in targets_json["contracts"].items(): 136 filename = convert_filename( 137 original_filename, 138 relative_to_short, 139 crytic_compile, 140 working_dir=buidler_working_dir, 141 ) 142 source_unit = compilation_unit.create_source_unit(filename) 143 144 for original_contract_name, info in contracts_info.items(): 145 contract_name = extract_name(original_contract_name) 146 147 if ( 148 original_filename.startswith("ontracts/") 149 and not skip_directory_name_fix 150 ): 151 original_filename = "c" + original_filename 152 153 source_unit.add_contract_name(contract_name) 154 compilation_unit.filename_to_contracts[filename].add(contract_name) 155 156 source_unit.abis[contract_name] = info["abi"] 157 source_unit.bytecodes_init[contract_name] = info["evm"]["bytecode"][ 158 "object" 159 ] 160 source_unit.bytecodes_runtime[contract_name] = info["evm"][ 161 "deployedBytecode" 162 ]["object"] 163 source_unit.srcmaps_init[contract_name] = info["evm"]["bytecode"][ 164 "sourceMap" 165 ].split(";") 166 source_unit.srcmaps_runtime[contract_name] = info["evm"][ 167 "deployedBytecode" 168 ]["sourceMap"].split(";") 169 userdoc = info.get("userdoc", {}) 170 devdoc = info.get("devdoc", {}) 171 natspec = Natspec(userdoc, devdoc) 172 source_unit.natspec[contract_name] = natspec 173 174 def clean(self, **kwargs: str) -> None: 175 # TODO: call "buldler clean"? 176 pass 177 178 @staticmethod 179 def is_supported(target: str, **kwargs: str) -> bool: 180 """Check if the target is a buidler project 181 182 Args: 183 target (str): path to the target 184 **kwargs: optional arguments. Used: "buidler_ignore" 185 186 Returns: 187 bool: True if the target is a buidler project 188 """ 189 buidler_ignore = kwargs.get("buidler_ignore", False) 190 if buidler_ignore: 191 return False 192 is_javascript = os.path.isfile(os.path.join(target, "buidler.config.js")) 193 is_typescript = os.path.isfile(os.path.join(target, "buidler.config.ts")) 194 return is_javascript or is_typescript 195 196 def is_dependency(self, path: str) -> bool: 197 """Check if the path is a dependency 198 199 Args: 200 path (str): path to the target 201 202 Returns: 203 bool: True if the target is a dependency 204 """ 205 if path in self._cached_dependencies: 206 return self._cached_dependencies[path] 207 ret = "node_modules" in Path(path).parts 208 self._cached_dependencies[path] = ret 209 return ret 210 211 def _guessed_tests(self) -> List[str]: 212 """Guess the potential unit tests commands 213 214 Returns: 215 List[str]: The guessed unit tests commands 216 """ 217 return ["buidler test"] 218 219 220def _get_version_from_config(builder_directory: Path) -> Tuple[str, str, bool]: 221 """Parse the compiler version 222 223 Args: 224 builder_directory (Path): path to the project's directory 225 226 Raises: 227 InvalidCompilation: If the configuration file was not found 228 229 Returns: 230 Tuple[str, str, bool]: (compiler_name,compiler_version,is_optimized) 231 """ 232 233 # :return: (version, optimized) 234 235 path_config = Path(builder_directory, "last-solc-config.json") 236 if not path_config.exists(): 237 path_config = Path(builder_directory, "last-vyper-config.json") 238 if not path_config.exists(): 239 raise InvalidCompilation(f"{path_config} not found") 240 with open(path_config, "r", encoding="utf8") as config_f: 241 version = config_f.read() 242 return "vyper", version, False 243 with open(path_config, "r", encoding="utf8") as config_f: 244 config = json.load(config_f) 245 246 version = config["solc"]["version"] 247 248 optimized = "optimizer" in config["solc"] and config["solc"]["optimizer"] 249 return "solc", version, optimized
30class Buidler(AbstractPlatform): 31 """ 32 Builder platform 33 """ 34 35 NAME = "Buidler" 36 PROJECT_URL = "https://github.com/nomiclabs/buidler" 37 TYPE = Type.BUILDER 38 39 # pylint: disable=too-many-locals,too-many-statements,too-many-branches 40 def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None: 41 """Run the compilation 42 43 Args: 44 crytic_compile (CryticCompile): Associated CryticCompile objects 45 **kwargs: optional arguments. Used: "buidler_cache_directory", "buidler_ignore_compile", "ignore_compile", 46 "buidler_working_dir", "buidler_skip_directory_name_fix", "npx_disable" 47 48 Raises: 49 InvalidCompilation: If buidler failed to run 50 """ 51 52 cache_directory = kwargs.get("buidler_cache_directory", "") 53 target_solc_file = os.path.join(cache_directory, "solc-output.json") 54 target_vyper_file = os.path.join(cache_directory, "vyper-docker-updates.json") 55 buidler_ignore_compile = kwargs.get("buidler_ignore_compile", False) or kwargs.get( 56 "ignore_compile", False 57 ) 58 buidler_working_dir = kwargs.get("buidler_working_dir", None) 59 # See https://github.com/crytic/crytic-compile/issues/116 60 skip_directory_name_fix = kwargs.get("buidler_skip_directory_name_fix", False) 61 62 base_cmd = ["buidler"] 63 if not kwargs.get("npx_disable", False): 64 base_cmd = ["npx"] + base_cmd 65 66 if not buidler_ignore_compile: 67 cmd = base_cmd + ["compile"] 68 69 LOGGER.info( 70 "'%s' running", 71 " ".join(cmd), 72 ) 73 74 with subprocess.Popen( 75 cmd, 76 stdout=subprocess.PIPE, 77 stderr=subprocess.PIPE, 78 cwd=self._target, 79 executable=shutil.which(cmd[0]), 80 ) as process: 81 82 stdout_bytes, stderr_bytes = process.communicate() 83 stdout, stderr = ( 84 stdout_bytes.decode(errors="backslashreplace"), 85 stderr_bytes.decode(errors="backslashreplace"), 86 ) # convert bytestrings to unicode strings 87 88 LOGGER.info(stdout) 89 if stderr: 90 LOGGER.error(stderr) 91 92 if not os.path.isfile(os.path.join(self._target, target_solc_file)): 93 if os.path.isfile(os.path.join(self._target, target_vyper_file)): 94 txt = "Vyper not yet supported with buidler." 95 txt += " Please open an issue in https://github.com/crytic/crytic-compile" 96 raise InvalidCompilation(txt) 97 txt = f"`buidler compile` failed. Can you run it?\n{os.path.join(self._target, target_solc_file)} not found" 98 raise InvalidCompilation(txt) 99 100 compilation_unit = CompilationUnit(crytic_compile, str(target_solc_file)) 101 102 (compiler, version_from_config, optimized) = _get_version_from_config(Path(cache_directory)) 103 104 compilation_unit.compiler_version = CompilerVersion( 105 compiler=compiler, version=version_from_config, optimized=optimized 106 ) 107 108 skip_filename = compilation_unit.compiler_version.version in [ 109 f"0.4.{x}" for x in range(0, 10) 110 ] 111 112 with open(target_solc_file, encoding="utf8") as file_desc: 113 targets_json = json.load(file_desc) 114 115 if "sources" in targets_json: 116 for path, info in targets_json["sources"].items(): 117 118 if path.startswith("ontracts/") and not skip_directory_name_fix: 119 path = "c" + path 120 121 if skip_filename: 122 path = convert_filename( 123 self._target, 124 relative_to_short, 125 crytic_compile, 126 working_dir=buidler_working_dir, 127 ) 128 else: 129 path = convert_filename( 130 path, relative_to_short, crytic_compile, working_dir=buidler_working_dir 131 ) 132 source_unit = compilation_unit.create_source_unit(path) 133 source_unit.ast = info["ast"] 134 135 if "contracts" in targets_json: 136 for original_filename, contracts_info in targets_json["contracts"].items(): 137 filename = convert_filename( 138 original_filename, 139 relative_to_short, 140 crytic_compile, 141 working_dir=buidler_working_dir, 142 ) 143 source_unit = compilation_unit.create_source_unit(filename) 144 145 for original_contract_name, info in contracts_info.items(): 146 contract_name = extract_name(original_contract_name) 147 148 if ( 149 original_filename.startswith("ontracts/") 150 and not skip_directory_name_fix 151 ): 152 original_filename = "c" + original_filename 153 154 source_unit.add_contract_name(contract_name) 155 compilation_unit.filename_to_contracts[filename].add(contract_name) 156 157 source_unit.abis[contract_name] = info["abi"] 158 source_unit.bytecodes_init[contract_name] = info["evm"]["bytecode"][ 159 "object" 160 ] 161 source_unit.bytecodes_runtime[contract_name] = info["evm"][ 162 "deployedBytecode" 163 ]["object"] 164 source_unit.srcmaps_init[contract_name] = info["evm"]["bytecode"][ 165 "sourceMap" 166 ].split(";") 167 source_unit.srcmaps_runtime[contract_name] = info["evm"][ 168 "deployedBytecode" 169 ]["sourceMap"].split(";") 170 userdoc = info.get("userdoc", {}) 171 devdoc = info.get("devdoc", {}) 172 natspec = Natspec(userdoc, devdoc) 173 source_unit.natspec[contract_name] = natspec 174 175 def clean(self, **kwargs: str) -> None: 176 # TODO: call "buldler clean"? 177 pass 178 179 @staticmethod 180 def is_supported(target: str, **kwargs: str) -> bool: 181 """Check if the target is a buidler project 182 183 Args: 184 target (str): path to the target 185 **kwargs: optional arguments. Used: "buidler_ignore" 186 187 Returns: 188 bool: True if the target is a buidler project 189 """ 190 buidler_ignore = kwargs.get("buidler_ignore", False) 191 if buidler_ignore: 192 return False 193 is_javascript = os.path.isfile(os.path.join(target, "buidler.config.js")) 194 is_typescript = os.path.isfile(os.path.join(target, "buidler.config.ts")) 195 return is_javascript or is_typescript 196 197 def is_dependency(self, path: str) -> bool: 198 """Check if the path is a dependency 199 200 Args: 201 path (str): path to the target 202 203 Returns: 204 bool: True if the target is a dependency 205 """ 206 if path in self._cached_dependencies: 207 return self._cached_dependencies[path] 208 ret = "node_modules" in Path(path).parts 209 self._cached_dependencies[path] = ret 210 return ret 211 212 def _guessed_tests(self) -> List[str]: 213 """Guess the potential unit tests commands 214 215 Returns: 216 List[str]: The guessed unit tests commands 217 """ 218 return ["buidler test"]
Builder platform
40 def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None: 41 """Run the compilation 42 43 Args: 44 crytic_compile (CryticCompile): Associated CryticCompile objects 45 **kwargs: optional arguments. Used: "buidler_cache_directory", "buidler_ignore_compile", "ignore_compile", 46 "buidler_working_dir", "buidler_skip_directory_name_fix", "npx_disable" 47 48 Raises: 49 InvalidCompilation: If buidler failed to run 50 """ 51 52 cache_directory = kwargs.get("buidler_cache_directory", "") 53 target_solc_file = os.path.join(cache_directory, "solc-output.json") 54 target_vyper_file = os.path.join(cache_directory, "vyper-docker-updates.json") 55 buidler_ignore_compile = kwargs.get("buidler_ignore_compile", False) or kwargs.get( 56 "ignore_compile", False 57 ) 58 buidler_working_dir = kwargs.get("buidler_working_dir", None) 59 # See https://github.com/crytic/crytic-compile/issues/116 60 skip_directory_name_fix = kwargs.get("buidler_skip_directory_name_fix", False) 61 62 base_cmd = ["buidler"] 63 if not kwargs.get("npx_disable", False): 64 base_cmd = ["npx"] + base_cmd 65 66 if not buidler_ignore_compile: 67 cmd = base_cmd + ["compile"] 68 69 LOGGER.info( 70 "'%s' running", 71 " ".join(cmd), 72 ) 73 74 with subprocess.Popen( 75 cmd, 76 stdout=subprocess.PIPE, 77 stderr=subprocess.PIPE, 78 cwd=self._target, 79 executable=shutil.which(cmd[0]), 80 ) as process: 81 82 stdout_bytes, stderr_bytes = process.communicate() 83 stdout, stderr = ( 84 stdout_bytes.decode(errors="backslashreplace"), 85 stderr_bytes.decode(errors="backslashreplace"), 86 ) # convert bytestrings to unicode strings 87 88 LOGGER.info(stdout) 89 if stderr: 90 LOGGER.error(stderr) 91 92 if not os.path.isfile(os.path.join(self._target, target_solc_file)): 93 if os.path.isfile(os.path.join(self._target, target_vyper_file)): 94 txt = "Vyper not yet supported with buidler." 95 txt += " Please open an issue in https://github.com/crytic/crytic-compile" 96 raise InvalidCompilation(txt) 97 txt = f"`buidler compile` failed. Can you run it?\n{os.path.join(self._target, target_solc_file)} not found" 98 raise InvalidCompilation(txt) 99 100 compilation_unit = CompilationUnit(crytic_compile, str(target_solc_file)) 101 102 (compiler, version_from_config, optimized) = _get_version_from_config(Path(cache_directory)) 103 104 compilation_unit.compiler_version = CompilerVersion( 105 compiler=compiler, version=version_from_config, optimized=optimized 106 ) 107 108 skip_filename = compilation_unit.compiler_version.version in [ 109 f"0.4.{x}" for x in range(0, 10) 110 ] 111 112 with open(target_solc_file, encoding="utf8") as file_desc: 113 targets_json = json.load(file_desc) 114 115 if "sources" in targets_json: 116 for path, info in targets_json["sources"].items(): 117 118 if path.startswith("ontracts/") and not skip_directory_name_fix: 119 path = "c" + path 120 121 if skip_filename: 122 path = convert_filename( 123 self._target, 124 relative_to_short, 125 crytic_compile, 126 working_dir=buidler_working_dir, 127 ) 128 else: 129 path = convert_filename( 130 path, relative_to_short, crytic_compile, working_dir=buidler_working_dir 131 ) 132 source_unit = compilation_unit.create_source_unit(path) 133 source_unit.ast = info["ast"] 134 135 if "contracts" in targets_json: 136 for original_filename, contracts_info in targets_json["contracts"].items(): 137 filename = convert_filename( 138 original_filename, 139 relative_to_short, 140 crytic_compile, 141 working_dir=buidler_working_dir, 142 ) 143 source_unit = compilation_unit.create_source_unit(filename) 144 145 for original_contract_name, info in contracts_info.items(): 146 contract_name = extract_name(original_contract_name) 147 148 if ( 149 original_filename.startswith("ontracts/") 150 and not skip_directory_name_fix 151 ): 152 original_filename = "c" + original_filename 153 154 source_unit.add_contract_name(contract_name) 155 compilation_unit.filename_to_contracts[filename].add(contract_name) 156 157 source_unit.abis[contract_name] = info["abi"] 158 source_unit.bytecodes_init[contract_name] = info["evm"]["bytecode"][ 159 "object" 160 ] 161 source_unit.bytecodes_runtime[contract_name] = info["evm"][ 162 "deployedBytecode" 163 ]["object"] 164 source_unit.srcmaps_init[contract_name] = info["evm"]["bytecode"][ 165 "sourceMap" 166 ].split(";") 167 source_unit.srcmaps_runtime[contract_name] = info["evm"][ 168 "deployedBytecode" 169 ]["sourceMap"].split(";") 170 userdoc = info.get("userdoc", {}) 171 devdoc = info.get("devdoc", {}) 172 natspec = Natspec(userdoc, devdoc) 173 source_unit.natspec[contract_name] = natspec
Run the compilation
Args: crytic_compile (CryticCompile): Associated CryticCompile objects **kwargs: optional arguments. Used: "buidler_cache_directory", "buidler_ignore_compile", "ignore_compile", "buidler_working_dir", "buidler_skip_directory_name_fix", "npx_disable"
Raises: InvalidCompilation: If buidler failed to run
Clean compilation artifacts
Args: **kwargs: optional arguments.
179 @staticmethod 180 def is_supported(target: str, **kwargs: str) -> bool: 181 """Check if the target is a buidler project 182 183 Args: 184 target (str): path to the target 185 **kwargs: optional arguments. Used: "buidler_ignore" 186 187 Returns: 188 bool: True if the target is a buidler project 189 """ 190 buidler_ignore = kwargs.get("buidler_ignore", False) 191 if buidler_ignore: 192 return False 193 is_javascript = os.path.isfile(os.path.join(target, "buidler.config.js")) 194 is_typescript = os.path.isfile(os.path.join(target, "buidler.config.ts")) 195 return is_javascript or is_typescript
Check if the target is a buidler project
Args: target (str): path to the target **kwargs: optional arguments. Used: "buidler_ignore"
Returns: bool: True if the target is a buidler project
197 def is_dependency(self, path: str) -> bool: 198 """Check if the path is a dependency 199 200 Args: 201 path (str): path to the target 202 203 Returns: 204 bool: True if the target is a dependency 205 """ 206 if path in self._cached_dependencies: 207 return self._cached_dependencies[path] 208 ret = "node_modules" in Path(path).parts 209 self._cached_dependencies[path] = ret 210 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