crytic_compile.platform.waffle

Waffle platform

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

Waffle platform

NAME: str = 'Waffle'
PROJECT_URL: str = 'https://github.com/EthWorks/Waffle'
TYPE: crytic_compile.platform.types.Type = <Type.WAFFLE: 8>
def compile( self, crytic_compile: crytic_compile.crytic_compile.CryticCompile, **kwargs: str) -> None:
 42    def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None:
 43        """Compile the project and populate the CryticCompile object
 44
 45        Args:
 46            crytic_compile (CryticCompile): Associated CryticCompile
 47            **kwargs: optional arguments. Used "waffle_ignore_compile", "ignore_compile", "npx_disable",
 48                "waffle_config_file"
 49
 50        Raises:
 51            InvalidCompilation: If the waffle failed to run
 52        """
 53
 54        waffle_ignore_compile = kwargs.get("waffle_ignore_compile", False) or kwargs.get(
 55            "ignore_compile", False
 56        )
 57        target = self._target
 58
 59        cmd = ["waffle"]
 60        if not kwargs.get("npx_disable", False):
 61            cmd = ["npx"] + cmd
 62
 63        # Default behaviour (without any config_file)
 64        build_directory = os.path.join("build")
 65        compiler = "native"
 66        config: Dict = {}
 67
 68        config_file = kwargs.get("waffle_config_file", "waffle.json")
 69
 70        potential_config_files = list(Path(target).rglob("*waffle*.json"))
 71        if potential_config_files and len(potential_config_files) == 1:
 72            config_file = str(potential_config_files[0])
 73
 74        # Read config file
 75        if config_file:
 76            config = _load_config(config_file)
 77
 78            # old version
 79            if "compiler" in config:
 80                compiler = config["compiler"]
 81            if "compilerType" in config:
 82                compiler = config["compilerType"]
 83
 84            if "compilerVersion" in config:
 85                version = config["compilerVersion"]
 86            else:
 87                version = _get_version(compiler, target, config=config)
 88
 89            if "targetPath" in config:
 90                build_directory = config["targetPath"]
 91
 92        else:
 93            version = _get_version(compiler, target)
 94
 95        if "outputType" not in config or config["outputType"] != "all":
 96            config["outputType"] = "all"
 97
 98        needed_config = {
 99            "compilerOptions": {
100                "outputSelection": {
101                    "*": {
102                        "*": [
103                            "evm.bytecode.object",
104                            "evm.deployedBytecode.object",
105                            "abi",
106                            "evm.bytecode.sourceMap",
107                            "evm.deployedBytecode.sourceMap",
108                        ],
109                        "": ["ast"],
110                    }
111                }
112            }
113        }
114
115        # Set the config as it should be
116        if "compilerOptions" in config:
117            curr_config: Dict = config["compilerOptions"]
118            curr_needed_config: Dict = needed_config["compilerOptions"]
119            if "outputSelection" in curr_config:
120                curr_config = curr_config["outputSelection"]
121                curr_needed_config = curr_needed_config["outputSelection"]
122                if "*" in curr_config:
123                    curr_config = curr_config["*"]
124                    curr_needed_config = curr_needed_config["*"]
125                    if "*" in curr_config:
126                        curr_config["*"] += curr_needed_config["*"]
127                    else:
128                        curr_config["*"] = curr_needed_config["*"]
129
130                    if "" in curr_config:
131                        curr_config[""] += curr_needed_config[""]
132                    else:
133                        curr_config[""] = curr_needed_config[""]
134
135                else:
136                    curr_config["*"] = curr_needed_config["*"]
137
138            else:
139                curr_config["outputSelection"] = curr_needed_config["outputSelection"]
140        else:
141            config["compilerOptions"] = needed_config["compilerOptions"]
142
143        if not waffle_ignore_compile:
144            with tempfile.NamedTemporaryFile(mode="w", suffix=".json", dir=target) as file_desc:
145                json.dump(config, file_desc)
146                file_desc.flush()
147
148                # cmd += [os.path.relpath(file_desc.name)]
149                cmd += [Path(file_desc.name).name]
150
151                LOGGER.info("Temporary file created: %s", file_desc.name)
152                LOGGER.info("'%s running", " ".join(cmd))
153
154                try:
155                    with subprocess.Popen(
156                        cmd,
157                        stdout=subprocess.PIPE,
158                        stderr=subprocess.PIPE,
159                        cwd=target,
160                        executable=shutil.which(cmd[0]),
161                    ) as process:
162                        stdout, stderr = process.communicate()
163                        if stdout:
164                            LOGGER.info(stdout.decode(errors="backslashreplace"))
165                        if stderr:
166                            LOGGER.error(stderr.decode(errors="backslashreplace"))
167                except OSError as error:
168                    # pylint: disable=raise-missing-from
169                    raise InvalidCompilation(error)
170
171        if not os.path.isdir(os.path.join(target, build_directory)):
172            raise InvalidCompilation("`waffle` compilation failed: build directory not found")
173
174        combined_path = os.path.join(target, build_directory, "Combined-Json.json")
175        if not os.path.exists(combined_path):
176            raise InvalidCompilation("`Combined-Json.json` not found")
177
178        with open(combined_path, encoding="utf8") as f:
179            target_all = json.load(f)
180
181        optimized = None
182
183        compilation_unit = CompilationUnit(crytic_compile, str(target))
184
185        if "sources" in target_all:
186            compilation_unit.filenames = [
187                convert_filename(path, _relative_to_short, crytic_compile, working_dir=target)
188                for path in target_all["sources"]
189            ]
190
191        for contract in target_all["contracts"]:
192            target_loaded = target_all["contracts"][contract]
193            contract = contract.split(":")
194            filename = convert_filename(
195                contract[0], _relative_to_short, crytic_compile, working_dir=target
196            )
197
198            contract_name = contract[1]
199            source_unit = compilation_unit.create_source_unit(filename)
200
201            source_unit.ast = target_all["sources"][contract[0]]["AST"]
202            compilation_unit.filename_to_contracts[filename].add(contract_name)
203            source_unit.add_contract_name(contract_name)
204            source_unit.abis[contract_name] = target_loaded["abi"]
205
206            userdoc = target_loaded.get("userdoc", {})
207            devdoc = target_loaded.get("devdoc", {})
208            natspec = Natspec(userdoc, devdoc)
209            source_unit.natspec[contract_name] = natspec
210
211            source_unit.bytecodes_init[contract_name] = target_loaded["evm"]["bytecode"]["object"]
212            source_unit.srcmaps_init[contract_name] = target_loaded["evm"]["bytecode"][
213                "sourceMap"
214            ].split(";")
215            source_unit.bytecodes_runtime[contract_name] = target_loaded["evm"]["deployedBytecode"][
216                "object"
217            ]
218            source_unit.srcmaps_runtime[contract_name] = target_loaded["evm"]["deployedBytecode"][
219                "sourceMap"
220            ].split(";")
221
222        compilation_unit.compiler_version = CompilerVersion(
223            compiler=compiler, version=version, optimized=optimized
224        )

Compile the project and populate the CryticCompile object

Args: crytic_compile (CryticCompile): Associated CryticCompile **kwargs: optional arguments. Used "waffle_ignore_compile", "ignore_compile", "npx_disable", "waffle_config_file"

Raises: InvalidCompilation: If the waffle failed to run

def clean(self, **_kwargs: str) -> None:
226    def clean(self, **_kwargs: str) -> None:
227        """Clean compilation artifacts
228
229        Args:
230            **_kwargs: unused.
231        """
232        return

Clean compilation artifacts

Args: **_kwargs: unused.

@staticmethod
def is_supported(target: str, **kwargs: str) -> bool:
234    @staticmethod
235    def is_supported(target: str, **kwargs: str) -> bool:
236        """Check if the target is a waffle project
237
238        Args:
239            target (str): path to the target
240            **kwargs: optional arguments. Used "waffle_ignore"
241
242        Returns:
243            bool: True if the target is a waffle project
244        """
245        waffle_ignore = kwargs.get("waffle_ignore", False)
246        if waffle_ignore:
247            return False
248
249        if os.path.isfile(os.path.join(target, "waffle.json")) or os.path.isfile(
250            os.path.join(target, ".waffle.json")
251        ):
252            return True
253
254        if os.path.isfile(os.path.join(target, "package.json")):
255            with open(os.path.join(target, "package.json"), encoding="utf8") as file_desc:
256                package = json.load(file_desc)
257            if "dependencies" in package:
258                return "ethereum-waffle" in package["dependencies"]
259            if "devDependencies" in package:
260                return "ethereum-waffle" in package["devDependencies"]
261
262        return False

Check if the target is a waffle project

Args: target (str): path to the target **kwargs: optional arguments. Used "waffle_ignore"

Returns: bool: True if the target is a waffle project

@staticmethod
def config( working_dir: str) -> Union[crytic_compile.platform.abstract_platform.PlatformConfig, NoneType]:
264    @staticmethod
265    def config(working_dir: str) -> Optional[PlatformConfig]:
266        """Return configuration data that should be passed to solc, such as remappings.
267
268        Args:
269            working_dir (str): path to the working directory
270
271        Returns:
272            Optional[PlatformConfig]: Platform configuration data such as optimization, remappings...
273        """
274        return None

Return configuration data that should be passed to solc, such as remappings.

Args: working_dir (str): path to the working directory

Returns: Optional[PlatformConfig]: Platform configuration data such as optimization, remappings...

def is_dependency(self, path: str) -> bool:
276    def is_dependency(self, path: str) -> bool:
277        """Check if the path is a dependency
278
279        Args:
280            path (str): path to the target
281
282        Returns:
283            bool: True if the target is a dependency
284        """
285        if path in self._cached_dependencies:
286            return self._cached_dependencies[path]
287        ret = "node_modules" in Path(path).parts
288        self._cached_dependencies[path] = ret
289        return ret

Check if the path is a dependency

Args: path (str): path to the target

Returns: bool: True if the target is a dependency