crytic_compile.platform.embark
Embark platform. https://github.com/embark-framework/embark
1""" 2Embark platform. https://github.com/embark-framework/embark 3""" 4 5import json 6import logging 7import os 8import shutil 9import subprocess 10from pathlib import Path 11from typing import TYPE_CHECKING, List 12 13from crytic_compile.compilation_unit import CompilationUnit 14from crytic_compile.compiler.compiler import CompilerVersion 15from crytic_compile.platform.abstract_platform import AbstractPlatform 16from crytic_compile.platform.exceptions import InvalidCompilation 17from crytic_compile.platform.types import Type 18from crytic_compile.utils.naming import convert_filename, extract_filename, extract_name 19 20# Cycle dependency 21from crytic_compile.utils.natspec import Natspec 22 23if TYPE_CHECKING: 24 from crytic_compile import CryticCompile 25 26LOGGER = logging.getLogger("CryticCompile") 27 28 29class Embark(AbstractPlatform): 30 """ 31 Embark platform 32 """ 33 34 NAME = "Embark" 35 PROJECT_URL = "https://github.com/embarklabs/embark" 36 TYPE = Type.EMBARK 37 38 # pylint:disable=too-many-branches,too-many-statements,too-many-locals 39 def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None: 40 """Run the compilation 41 42 Args: 43 crytic_compile (CryticCompile): Associated CryticCompile object 44 **kwargs: optional arguments. Used: "embark_ignore_compile", "ignore_compile", "embark_overwrite_config" 45 46 Raises: 47 InvalidCompilation: if embark failed to run 48 """ 49 embark_ignore_compile = kwargs.get("embark_ignore_compile", False) or kwargs.get( 50 "ignore_compile", False 51 ) 52 embark_overwrite_config = kwargs.get("embark_overwrite_config", False) 53 54 plugin_name = "@trailofbits/embark-contract-info" 55 with open(os.path.join(self._target, "embark.json"), encoding="utf8") as file_desc: 56 embark_json = json.load(file_desc) 57 if embark_overwrite_config: 58 write_embark_json = False 59 if not "plugins" in embark_json: 60 embark_json["plugins"] = {plugin_name: {"flags": ""}} 61 write_embark_json = True 62 elif not plugin_name in embark_json["plugins"]: 63 embark_json["plugins"][plugin_name] = {"flags": ""} 64 write_embark_json = True 65 if write_embark_json: 66 try: 67 with subprocess.Popen( 68 ["npm", "install", plugin_name], 69 cwd=self._target, 70 executable=shutil.which("npm"), 71 ) as process: 72 _, stderr = process.communicate() 73 with open( 74 os.path.join(self._target, "embark.json"), "w", encoding="utf8" 75 ) as outfile: 76 json.dump(embark_json, outfile, indent=2) 77 except OSError as error: 78 # pylint: disable=raise-missing-from 79 raise InvalidCompilation(error) 80 81 else: 82 if (not "plugins" in embark_json) or (not plugin_name in embark_json["plugins"]): 83 raise InvalidCompilation( 84 "embark-contract-info plugin was found in embark.json. " 85 "Please install the plugin (see " 86 "https://github.com/crytic/crytic-compile/wiki/Usage#embark)" 87 ", or use --embark-overwrite-config." 88 ) 89 90 if not embark_ignore_compile: 91 try: 92 cmd = ["embark", "build", "--contracts"] 93 if not kwargs.get("npx_disable", False): 94 cmd = ["npx"] + cmd 95 # pylint: disable=consider-using-with 96 process = subprocess.Popen( 97 cmd, 98 stdout=subprocess.PIPE, 99 stderr=subprocess.PIPE, 100 cwd=self._target, 101 executable=shutil.which(cmd[0]), 102 ) 103 except OSError as error: 104 # pylint: disable=raise-missing-from 105 raise InvalidCompilation(error) 106 stdout, stderr = process.communicate() 107 LOGGER.info("%s\n", stdout.decode(errors="backslashreplace")) 108 if stderr: 109 # Embark might return information to stderr, but compile without issue 110 LOGGER.error("%s", stderr.decode(errors="backslashreplace")) 111 infile = os.path.join(self._target, "crytic-export", "contracts-embark.json") 112 if not os.path.isfile(infile): 113 raise InvalidCompilation( 114 "Embark did not generate the AST file. Is Embark installed " 115 "(npm install -g embark)? Is embark-contract-info installed? (npm install -g embark)." 116 ) 117 compilation_unit = CompilationUnit(crytic_compile, str(self._target)) 118 119 compilation_unit.compiler_version = _get_version(self._target) 120 121 with open(infile, "r", encoding="utf8") as file_desc: 122 targets_loaded = json.load(file_desc) 123 124 if "sources" in targets_loaded: 125 compilation_unit.filenames = [ 126 convert_filename( 127 path, _relative_to_short, crytic_compile, working_dir=self._target 128 ) 129 for path in targets_loaded["sources"] 130 ] 131 132 for k, ast in targets_loaded["asts"].items(): 133 filename = convert_filename( 134 k, _relative_to_short, crytic_compile, working_dir=self._target 135 ) 136 source_unit = compilation_unit.create_source_unit(filename) 137 source_unit.ast = ast 138 139 if not "contracts" in targets_loaded: 140 LOGGER.error( 141 "Incorrect json file generated. Are you using %s >= 1.1.0?", plugin_name 142 ) 143 raise InvalidCompilation( 144 f"Incorrect json file generated. Are you using {plugin_name} >= 1.1.0?" 145 ) 146 147 for original_contract_name, info in targets_loaded["contracts"].items(): 148 contract_name = extract_name(original_contract_name) 149 filename = convert_filename( 150 extract_filename(original_contract_name), 151 _relative_to_short, 152 crytic_compile, 153 working_dir=self._target, 154 ) 155 156 source_unit = compilation_unit.create_source_unit(filename) 157 158 compilation_unit.filename_to_contracts[filename].add(contract_name) 159 source_unit.add_contract_name(contract_name) 160 161 if "abi" in info: 162 source_unit.abis[contract_name] = info["abi"] 163 if "bin" in info: 164 source_unit.bytecodes_init[contract_name] = info["bin"].replace("0x", "") 165 if "bin-runtime" in info: 166 source_unit.bytecodes_runtime[contract_name] = info["bin-runtime"].replace( 167 "0x", "" 168 ) 169 if "srcmap" in info: 170 source_unit.srcmaps_init[contract_name] = info["srcmap"].split(";") 171 if "srcmap-runtime" in info: 172 source_unit.srcmaps_runtime[contract_name] = info["srcmap-runtime"].split(";") 173 174 userdoc = info.get("userdoc", {}) 175 devdoc = info.get("devdoc", {}) 176 natspec = Natspec(userdoc, devdoc) 177 source_unit.natspec[contract_name] = natspec 178 179 def clean(self, **_kwargs: str) -> None: 180 """Clean compilation artifacts 181 182 Args: 183 **_kwargs: unused. 184 """ 185 return 186 187 @staticmethod 188 def is_supported(target: str, **kwargs: str) -> bool: 189 """Check if the target is an embark project 190 191 Args: 192 target (str): path to the target 193 **kwargs: optional arguments. Used: "embark_ignore" 194 195 Returns: 196 bool: True if the target is an embark project 197 """ 198 embark_ignore = kwargs.get("embark_ignore", False) 199 if embark_ignore: 200 return False 201 return os.path.isfile(os.path.join(target, "embark.json")) 202 203 def is_dependency(self, path: str) -> bool: 204 """Check if the path is a dependency 205 206 Args: 207 path (str): path to the target 208 209 Returns: 210 bool: True if the target is a dependency 211 """ 212 if path in self._cached_dependencies: 213 return self._cached_dependencies[path] 214 ret = "node_modules" in Path(path).parts 215 self._cached_dependencies[path] = ret 216 return ret 217 218 def _guessed_tests(self) -> List[str]: 219 """Guess the potential unit tests commands 220 221 Returns: 222 List[str]: The guessed unit tests commands 223 """ 224 return ["embark test"] 225 226 227def _get_version(target: str) -> CompilerVersion: 228 """Get the compiler information 229 230 Args: 231 target (str): path to the target 232 233 Returns: 234 CompilerVersion: Compiler information 235 """ 236 with open(os.path.join(target, "embark.json"), encoding="utf8") as file_desc: 237 config = json.load(file_desc) 238 version = "0.5.0" # default version with Embark 0.4 239 if "versions" in config: 240 if "solc" in config["versions"]: 241 version = config["versions"]["solc"] 242 optimized = False 243 if "options" in config: 244 if "solc" in config["options"]: 245 if "optimize" in config["options"]["solc"]: 246 optimized = config["options"]["solc"] 247 248 return CompilerVersion(compiler="solc-js", version=version, optimized=optimized) 249 250 251def _relative_to_short(relative: Path) -> Path: 252 """Translate relative path to short 253 254 Args: 255 relative (Path): path to the target 256 257 Returns: 258 Path: Translated path 259 """ 260 short = relative 261 try: 262 short = short.relative_to(Path(".embark", "contracts")) 263 except ValueError: 264 try: 265 short = short.relative_to("node_modules") 266 except ValueError: 267 pass 268 return short
LOGGER =
<Logger CryticCompile (WARNING)>
30class Embark(AbstractPlatform): 31 """ 32 Embark platform 33 """ 34 35 NAME = "Embark" 36 PROJECT_URL = "https://github.com/embarklabs/embark" 37 TYPE = Type.EMBARK 38 39 # pylint:disable=too-many-branches,too-many-statements,too-many-locals 40 def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None: 41 """Run the compilation 42 43 Args: 44 crytic_compile (CryticCompile): Associated CryticCompile object 45 **kwargs: optional arguments. Used: "embark_ignore_compile", "ignore_compile", "embark_overwrite_config" 46 47 Raises: 48 InvalidCompilation: if embark failed to run 49 """ 50 embark_ignore_compile = kwargs.get("embark_ignore_compile", False) or kwargs.get( 51 "ignore_compile", False 52 ) 53 embark_overwrite_config = kwargs.get("embark_overwrite_config", False) 54 55 plugin_name = "@trailofbits/embark-contract-info" 56 with open(os.path.join(self._target, "embark.json"), encoding="utf8") as file_desc: 57 embark_json = json.load(file_desc) 58 if embark_overwrite_config: 59 write_embark_json = False 60 if not "plugins" in embark_json: 61 embark_json["plugins"] = {plugin_name: {"flags": ""}} 62 write_embark_json = True 63 elif not plugin_name in embark_json["plugins"]: 64 embark_json["plugins"][plugin_name] = {"flags": ""} 65 write_embark_json = True 66 if write_embark_json: 67 try: 68 with subprocess.Popen( 69 ["npm", "install", plugin_name], 70 cwd=self._target, 71 executable=shutil.which("npm"), 72 ) as process: 73 _, stderr = process.communicate() 74 with open( 75 os.path.join(self._target, "embark.json"), "w", encoding="utf8" 76 ) as outfile: 77 json.dump(embark_json, outfile, indent=2) 78 except OSError as error: 79 # pylint: disable=raise-missing-from 80 raise InvalidCompilation(error) 81 82 else: 83 if (not "plugins" in embark_json) or (not plugin_name in embark_json["plugins"]): 84 raise InvalidCompilation( 85 "embark-contract-info plugin was found in embark.json. " 86 "Please install the plugin (see " 87 "https://github.com/crytic/crytic-compile/wiki/Usage#embark)" 88 ", or use --embark-overwrite-config." 89 ) 90 91 if not embark_ignore_compile: 92 try: 93 cmd = ["embark", "build", "--contracts"] 94 if not kwargs.get("npx_disable", False): 95 cmd = ["npx"] + cmd 96 # pylint: disable=consider-using-with 97 process = subprocess.Popen( 98 cmd, 99 stdout=subprocess.PIPE, 100 stderr=subprocess.PIPE, 101 cwd=self._target, 102 executable=shutil.which(cmd[0]), 103 ) 104 except OSError as error: 105 # pylint: disable=raise-missing-from 106 raise InvalidCompilation(error) 107 stdout, stderr = process.communicate() 108 LOGGER.info("%s\n", stdout.decode(errors="backslashreplace")) 109 if stderr: 110 # Embark might return information to stderr, but compile without issue 111 LOGGER.error("%s", stderr.decode(errors="backslashreplace")) 112 infile = os.path.join(self._target, "crytic-export", "contracts-embark.json") 113 if not os.path.isfile(infile): 114 raise InvalidCompilation( 115 "Embark did not generate the AST file. Is Embark installed " 116 "(npm install -g embark)? Is embark-contract-info installed? (npm install -g embark)." 117 ) 118 compilation_unit = CompilationUnit(crytic_compile, str(self._target)) 119 120 compilation_unit.compiler_version = _get_version(self._target) 121 122 with open(infile, "r", encoding="utf8") as file_desc: 123 targets_loaded = json.load(file_desc) 124 125 if "sources" in targets_loaded: 126 compilation_unit.filenames = [ 127 convert_filename( 128 path, _relative_to_short, crytic_compile, working_dir=self._target 129 ) 130 for path in targets_loaded["sources"] 131 ] 132 133 for k, ast in targets_loaded["asts"].items(): 134 filename = convert_filename( 135 k, _relative_to_short, crytic_compile, working_dir=self._target 136 ) 137 source_unit = compilation_unit.create_source_unit(filename) 138 source_unit.ast = ast 139 140 if not "contracts" in targets_loaded: 141 LOGGER.error( 142 "Incorrect json file generated. Are you using %s >= 1.1.0?", plugin_name 143 ) 144 raise InvalidCompilation( 145 f"Incorrect json file generated. Are you using {plugin_name} >= 1.1.0?" 146 ) 147 148 for original_contract_name, info in targets_loaded["contracts"].items(): 149 contract_name = extract_name(original_contract_name) 150 filename = convert_filename( 151 extract_filename(original_contract_name), 152 _relative_to_short, 153 crytic_compile, 154 working_dir=self._target, 155 ) 156 157 source_unit = compilation_unit.create_source_unit(filename) 158 159 compilation_unit.filename_to_contracts[filename].add(contract_name) 160 source_unit.add_contract_name(contract_name) 161 162 if "abi" in info: 163 source_unit.abis[contract_name] = info["abi"] 164 if "bin" in info: 165 source_unit.bytecodes_init[contract_name] = info["bin"].replace("0x", "") 166 if "bin-runtime" in info: 167 source_unit.bytecodes_runtime[contract_name] = info["bin-runtime"].replace( 168 "0x", "" 169 ) 170 if "srcmap" in info: 171 source_unit.srcmaps_init[contract_name] = info["srcmap"].split(";") 172 if "srcmap-runtime" in info: 173 source_unit.srcmaps_runtime[contract_name] = info["srcmap-runtime"].split(";") 174 175 userdoc = info.get("userdoc", {}) 176 devdoc = info.get("devdoc", {}) 177 natspec = Natspec(userdoc, devdoc) 178 source_unit.natspec[contract_name] = natspec 179 180 def clean(self, **_kwargs: str) -> None: 181 """Clean compilation artifacts 182 183 Args: 184 **_kwargs: unused. 185 """ 186 return 187 188 @staticmethod 189 def is_supported(target: str, **kwargs: str) -> bool: 190 """Check if the target is an embark project 191 192 Args: 193 target (str): path to the target 194 **kwargs: optional arguments. Used: "embark_ignore" 195 196 Returns: 197 bool: True if the target is an embark project 198 """ 199 embark_ignore = kwargs.get("embark_ignore", False) 200 if embark_ignore: 201 return False 202 return os.path.isfile(os.path.join(target, "embark.json")) 203 204 def is_dependency(self, path: str) -> bool: 205 """Check if the path is a dependency 206 207 Args: 208 path (str): path to the target 209 210 Returns: 211 bool: True if the target is a dependency 212 """ 213 if path in self._cached_dependencies: 214 return self._cached_dependencies[path] 215 ret = "node_modules" in Path(path).parts 216 self._cached_dependencies[path] = ret 217 return ret 218 219 def _guessed_tests(self) -> List[str]: 220 """Guess the potential unit tests commands 221 222 Returns: 223 List[str]: The guessed unit tests commands 224 """ 225 return ["embark test"]
Embark platform
def
compile( self, crytic_compile: crytic_compile.crytic_compile.CryticCompile, **kwargs: str) -> None:
40 def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None: 41 """Run the compilation 42 43 Args: 44 crytic_compile (CryticCompile): Associated CryticCompile object 45 **kwargs: optional arguments. Used: "embark_ignore_compile", "ignore_compile", "embark_overwrite_config" 46 47 Raises: 48 InvalidCompilation: if embark failed to run 49 """ 50 embark_ignore_compile = kwargs.get("embark_ignore_compile", False) or kwargs.get( 51 "ignore_compile", False 52 ) 53 embark_overwrite_config = kwargs.get("embark_overwrite_config", False) 54 55 plugin_name = "@trailofbits/embark-contract-info" 56 with open(os.path.join(self._target, "embark.json"), encoding="utf8") as file_desc: 57 embark_json = json.load(file_desc) 58 if embark_overwrite_config: 59 write_embark_json = False 60 if not "plugins" in embark_json: 61 embark_json["plugins"] = {plugin_name: {"flags": ""}} 62 write_embark_json = True 63 elif not plugin_name in embark_json["plugins"]: 64 embark_json["plugins"][plugin_name] = {"flags": ""} 65 write_embark_json = True 66 if write_embark_json: 67 try: 68 with subprocess.Popen( 69 ["npm", "install", plugin_name], 70 cwd=self._target, 71 executable=shutil.which("npm"), 72 ) as process: 73 _, stderr = process.communicate() 74 with open( 75 os.path.join(self._target, "embark.json"), "w", encoding="utf8" 76 ) as outfile: 77 json.dump(embark_json, outfile, indent=2) 78 except OSError as error: 79 # pylint: disable=raise-missing-from 80 raise InvalidCompilation(error) 81 82 else: 83 if (not "plugins" in embark_json) or (not plugin_name in embark_json["plugins"]): 84 raise InvalidCompilation( 85 "embark-contract-info plugin was found in embark.json. " 86 "Please install the plugin (see " 87 "https://github.com/crytic/crytic-compile/wiki/Usage#embark)" 88 ", or use --embark-overwrite-config." 89 ) 90 91 if not embark_ignore_compile: 92 try: 93 cmd = ["embark", "build", "--contracts"] 94 if not kwargs.get("npx_disable", False): 95 cmd = ["npx"] + cmd 96 # pylint: disable=consider-using-with 97 process = subprocess.Popen( 98 cmd, 99 stdout=subprocess.PIPE, 100 stderr=subprocess.PIPE, 101 cwd=self._target, 102 executable=shutil.which(cmd[0]), 103 ) 104 except OSError as error: 105 # pylint: disable=raise-missing-from 106 raise InvalidCompilation(error) 107 stdout, stderr = process.communicate() 108 LOGGER.info("%s\n", stdout.decode(errors="backslashreplace")) 109 if stderr: 110 # Embark might return information to stderr, but compile without issue 111 LOGGER.error("%s", stderr.decode(errors="backslashreplace")) 112 infile = os.path.join(self._target, "crytic-export", "contracts-embark.json") 113 if not os.path.isfile(infile): 114 raise InvalidCompilation( 115 "Embark did not generate the AST file. Is Embark installed " 116 "(npm install -g embark)? Is embark-contract-info installed? (npm install -g embark)." 117 ) 118 compilation_unit = CompilationUnit(crytic_compile, str(self._target)) 119 120 compilation_unit.compiler_version = _get_version(self._target) 121 122 with open(infile, "r", encoding="utf8") as file_desc: 123 targets_loaded = json.load(file_desc) 124 125 if "sources" in targets_loaded: 126 compilation_unit.filenames = [ 127 convert_filename( 128 path, _relative_to_short, crytic_compile, working_dir=self._target 129 ) 130 for path in targets_loaded["sources"] 131 ] 132 133 for k, ast in targets_loaded["asts"].items(): 134 filename = convert_filename( 135 k, _relative_to_short, crytic_compile, working_dir=self._target 136 ) 137 source_unit = compilation_unit.create_source_unit(filename) 138 source_unit.ast = ast 139 140 if not "contracts" in targets_loaded: 141 LOGGER.error( 142 "Incorrect json file generated. Are you using %s >= 1.1.0?", plugin_name 143 ) 144 raise InvalidCompilation( 145 f"Incorrect json file generated. Are you using {plugin_name} >= 1.1.0?" 146 ) 147 148 for original_contract_name, info in targets_loaded["contracts"].items(): 149 contract_name = extract_name(original_contract_name) 150 filename = convert_filename( 151 extract_filename(original_contract_name), 152 _relative_to_short, 153 crytic_compile, 154 working_dir=self._target, 155 ) 156 157 source_unit = compilation_unit.create_source_unit(filename) 158 159 compilation_unit.filename_to_contracts[filename].add(contract_name) 160 source_unit.add_contract_name(contract_name) 161 162 if "abi" in info: 163 source_unit.abis[contract_name] = info["abi"] 164 if "bin" in info: 165 source_unit.bytecodes_init[contract_name] = info["bin"].replace("0x", "") 166 if "bin-runtime" in info: 167 source_unit.bytecodes_runtime[contract_name] = info["bin-runtime"].replace( 168 "0x", "" 169 ) 170 if "srcmap" in info: 171 source_unit.srcmaps_init[contract_name] = info["srcmap"].split(";") 172 if "srcmap-runtime" in info: 173 source_unit.srcmaps_runtime[contract_name] = info["srcmap-runtime"].split(";") 174 175 userdoc = info.get("userdoc", {}) 176 devdoc = info.get("devdoc", {}) 177 natspec = Natspec(userdoc, devdoc) 178 source_unit.natspec[contract_name] = natspec
Run the compilation
Args: crytic_compile (CryticCompile): Associated CryticCompile object **kwargs: optional arguments. Used: "embark_ignore_compile", "ignore_compile", "embark_overwrite_config"
Raises: InvalidCompilation: if embark failed to run
def
clean(self, **_kwargs: str) -> None:
180 def clean(self, **_kwargs: str) -> None: 181 """Clean compilation artifacts 182 183 Args: 184 **_kwargs: unused. 185 """ 186 return
Clean compilation artifacts
Args: **_kwargs: unused.
@staticmethod
def
is_supported(target: str, **kwargs: str) -> bool:
188 @staticmethod 189 def is_supported(target: str, **kwargs: str) -> bool: 190 """Check if the target is an embark project 191 192 Args: 193 target (str): path to the target 194 **kwargs: optional arguments. Used: "embark_ignore" 195 196 Returns: 197 bool: True if the target is an embark project 198 """ 199 embark_ignore = kwargs.get("embark_ignore", False) 200 if embark_ignore: 201 return False 202 return os.path.isfile(os.path.join(target, "embark.json"))
Check if the target is an embark project
Args: target (str): path to the target **kwargs: optional arguments. Used: "embark_ignore"
Returns: bool: True if the target is an embark project
def
is_dependency(self, path: str) -> bool:
204 def is_dependency(self, path: str) -> bool: 205 """Check if the path is a dependency 206 207 Args: 208 path (str): path to the target 209 210 Returns: 211 bool: True if the target is a dependency 212 """ 213 if path in self._cached_dependencies: 214 return self._cached_dependencies[path] 215 ret = "node_modules" in Path(path).parts 216 self._cached_dependencies[path] = ret 217 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