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
LOGGER = <Logger CryticCompile (WARNING)>
 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

NAME: str = 'Buidler'
PROJECT_URL: str = 'https://github.com/nomiclabs/buidler'
TYPE: crytic_compile.platform.types.Type = <Type.BUILDER: 11>
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 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

def clean(self, **kwargs: str) -> None:
175    def clean(self, **kwargs: str) -> None:
176        # TODO: call "buldler clean"?
177        pass

Clean compilation artifacts

Args: **kwargs: optional arguments.

@staticmethod
def is_supported(target: str, **kwargs: str) -> bool:
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

def is_dependency(self, path: str) -> bool:
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