crytic_compile.platform.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

NAME: str = 'Embark'
PROJECT_URL: str = 'https://github.com/embarklabs/embark'
TYPE: crytic_compile.platform.types.Type = <Type.EMBARK: 3>
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