crytic_compile.platform.foundry

Foundry platform

  1"""
  2Foundry platform
  3"""
  4import logging
  5import os
  6import subprocess
  7from pathlib import Path
  8from typing import TYPE_CHECKING, List, Optional, TypeVar
  9
 10import json
 11
 12from crytic_compile.platform.abstract_platform import AbstractPlatform, PlatformConfig
 13from crytic_compile.platform.types import Type
 14from crytic_compile.platform.hardhat import hardhat_like_parsing
 15from crytic_compile.utils.subprocess import run
 16
 17# Handle cycle
 18if TYPE_CHECKING:
 19    from crytic_compile import CryticCompile
 20
 21T = TypeVar("T")
 22
 23LOGGER = logging.getLogger("CryticCompile")
 24
 25
 26class Foundry(AbstractPlatform):
 27    """
 28    Foundry platform
 29    """
 30
 31    NAME = "Foundry"
 32    PROJECT_URL = "https://github.com/foundry-rs/foundry"
 33    TYPE = Type.FOUNDRY
 34
 35    # pylint: disable=too-many-locals,too-many-statements,too-many-branches
 36    def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None:
 37        """Compile
 38
 39        Args:
 40            crytic_compile (CryticCompile): CryticCompile object to populate
 41            **kwargs: optional arguments. Used: "foundry_ignore_compile", "foundry_out_directory"
 42
 43        """
 44
 45        ignore_compile = kwargs.get("foundry_ignore_compile", False) or kwargs.get(
 46            "ignore_compile", False
 47        )
 48
 49        out_directory = kwargs.get("foundry_out_directory", "out")
 50
 51        if ignore_compile:
 52            LOGGER.info(
 53                "--ignore-compile used, if something goes wrong, consider removing the ignore compile flag"
 54            )
 55
 56        if not ignore_compile:
 57            compilation_command = [
 58                "forge",
 59                "build",
 60                "--build-info",
 61            ]
 62
 63            compile_all = kwargs.get("foundry_compile_all", False)
 64
 65            if not compile_all:
 66                foundry_config = self.config(self._target)
 67                if foundry_config:
 68                    compilation_command += [
 69                        "--skip",
 70                        f"*/{foundry_config.tests_path}/**",
 71                        f"*/{foundry_config.scripts_path}/**",
 72                        "--force",
 73                    ]
 74
 75            run(
 76                compilation_command,
 77                cwd=self._target,
 78            )
 79
 80        build_directory = Path(
 81            self._target,
 82            out_directory,
 83            "build-info",
 84        )
 85
 86        hardhat_like_parsing(crytic_compile, self._target, build_directory, self._target)
 87
 88    def clean(self, **kwargs: str) -> None:
 89        """Clean compilation artifacts
 90
 91        Args:
 92            **kwargs: optional arguments.
 93        """
 94
 95        ignore_compile = kwargs.get("foundry_ignore_compile", False) or kwargs.get(
 96            "ignore_compile", False
 97        )
 98
 99        if ignore_compile:
100            return
101
102        run(["forge", "clean"], cwd=self._target)
103
104    @staticmethod
105    def is_supported(target: str, **kwargs: str) -> bool:
106        """Check if the target is a foundry project
107
108        Args:
109            target (str): path to the target
110            **kwargs: optional arguments. Used: "foundry_ignore"
111
112        Returns:
113            bool: True if the target is a foundry project
114        """
115        if kwargs.get("foundry_ignore", False):
116            return False
117
118        return os.path.isfile(os.path.join(target, "foundry.toml"))
119
120    @staticmethod
121    def config(working_dir: str) -> Optional[PlatformConfig]:
122        """Return configuration data that should be passed to solc, such as remappings.
123
124        Args:
125            working_dir (str): path to the working_dir
126
127        Returns:
128            Optional[PlatformConfig]: Platform configuration data such as optimization, remappings...
129        """
130        result = PlatformConfig()
131        LOGGER.info("'forge config --json' running")
132        json_config = json.loads(
133            subprocess.run(
134                ["forge", "config", "--json"], cwd=working_dir, stdout=subprocess.PIPE, check=True
135            ).stdout
136        )
137
138        # Solc configurations
139        result.solc_version = json_config.get("solc")
140        result.via_ir = json_config.get("via_ir")
141        result.allow_paths = json_config.get("allow_paths")
142        result.offline = json_config.get("offline")
143        result.evm_version = json_config.get("evm_version")
144        result.optimizer = json_config.get("optimizer")
145        result.optimizer_runs = json_config.get("optimizer_runs")
146        result.remappings = json_config.get("remappings")
147
148        # Foundry project configurations
149        result.src_path = json_config.get("src")
150        result.tests_path = json_config.get("test")
151        result.libs_path = json_config.get("libs")
152        result.scripts_path = json_config.get("script")
153
154        return result
155
156    # pylint: disable=no-self-use
157    def is_dependency(self, path: str) -> bool:
158        """Check if the path is a dependency
159
160        Args:
161            path (str): path to the target
162
163        Returns:
164            bool: True if the target is a dependency
165        """
166        if path in self._cached_dependencies:
167            return self._cached_dependencies[path]
168        ret = "lib" in Path(path).parts or "node_modules" in Path(path).parts
169        self._cached_dependencies[path] = ret
170        return ret
171
172    # pylint: disable=no-self-use
173    def _guessed_tests(self) -> List[str]:
174        """Guess the potential unit tests commands
175
176        Returns:
177            List[str]: The guessed unit tests commands
178        """
179        return ["forge test"]
LOGGER = <Logger CryticCompile (WARNING)>
 27class Foundry(AbstractPlatform):
 28    """
 29    Foundry platform
 30    """
 31
 32    NAME = "Foundry"
 33    PROJECT_URL = "https://github.com/foundry-rs/foundry"
 34    TYPE = Type.FOUNDRY
 35
 36    # pylint: disable=too-many-locals,too-many-statements,too-many-branches
 37    def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None:
 38        """Compile
 39
 40        Args:
 41            crytic_compile (CryticCompile): CryticCompile object to populate
 42            **kwargs: optional arguments. Used: "foundry_ignore_compile", "foundry_out_directory"
 43
 44        """
 45
 46        ignore_compile = kwargs.get("foundry_ignore_compile", False) or kwargs.get(
 47            "ignore_compile", False
 48        )
 49
 50        out_directory = kwargs.get("foundry_out_directory", "out")
 51
 52        if ignore_compile:
 53            LOGGER.info(
 54                "--ignore-compile used, if something goes wrong, consider removing the ignore compile flag"
 55            )
 56
 57        if not ignore_compile:
 58            compilation_command = [
 59                "forge",
 60                "build",
 61                "--build-info",
 62            ]
 63
 64            compile_all = kwargs.get("foundry_compile_all", False)
 65
 66            if not compile_all:
 67                foundry_config = self.config(self._target)
 68                if foundry_config:
 69                    compilation_command += [
 70                        "--skip",
 71                        f"*/{foundry_config.tests_path}/**",
 72                        f"*/{foundry_config.scripts_path}/**",
 73                        "--force",
 74                    ]
 75
 76            run(
 77                compilation_command,
 78                cwd=self._target,
 79            )
 80
 81        build_directory = Path(
 82            self._target,
 83            out_directory,
 84            "build-info",
 85        )
 86
 87        hardhat_like_parsing(crytic_compile, self._target, build_directory, self._target)
 88
 89    def clean(self, **kwargs: str) -> None:
 90        """Clean compilation artifacts
 91
 92        Args:
 93            **kwargs: optional arguments.
 94        """
 95
 96        ignore_compile = kwargs.get("foundry_ignore_compile", False) or kwargs.get(
 97            "ignore_compile", False
 98        )
 99
100        if ignore_compile:
101            return
102
103        run(["forge", "clean"], cwd=self._target)
104
105    @staticmethod
106    def is_supported(target: str, **kwargs: str) -> bool:
107        """Check if the target is a foundry project
108
109        Args:
110            target (str): path to the target
111            **kwargs: optional arguments. Used: "foundry_ignore"
112
113        Returns:
114            bool: True if the target is a foundry project
115        """
116        if kwargs.get("foundry_ignore", False):
117            return False
118
119        return os.path.isfile(os.path.join(target, "foundry.toml"))
120
121    @staticmethod
122    def config(working_dir: str) -> Optional[PlatformConfig]:
123        """Return configuration data that should be passed to solc, such as remappings.
124
125        Args:
126            working_dir (str): path to the working_dir
127
128        Returns:
129            Optional[PlatformConfig]: Platform configuration data such as optimization, remappings...
130        """
131        result = PlatformConfig()
132        LOGGER.info("'forge config --json' running")
133        json_config = json.loads(
134            subprocess.run(
135                ["forge", "config", "--json"], cwd=working_dir, stdout=subprocess.PIPE, check=True
136            ).stdout
137        )
138
139        # Solc configurations
140        result.solc_version = json_config.get("solc")
141        result.via_ir = json_config.get("via_ir")
142        result.allow_paths = json_config.get("allow_paths")
143        result.offline = json_config.get("offline")
144        result.evm_version = json_config.get("evm_version")
145        result.optimizer = json_config.get("optimizer")
146        result.optimizer_runs = json_config.get("optimizer_runs")
147        result.remappings = json_config.get("remappings")
148
149        # Foundry project configurations
150        result.src_path = json_config.get("src")
151        result.tests_path = json_config.get("test")
152        result.libs_path = json_config.get("libs")
153        result.scripts_path = json_config.get("script")
154
155        return result
156
157    # pylint: disable=no-self-use
158    def is_dependency(self, path: str) -> bool:
159        """Check if the path is a dependency
160
161        Args:
162            path (str): path to the target
163
164        Returns:
165            bool: True if the target is a dependency
166        """
167        if path in self._cached_dependencies:
168            return self._cached_dependencies[path]
169        ret = "lib" in Path(path).parts or "node_modules" in Path(path).parts
170        self._cached_dependencies[path] = ret
171        return ret
172
173    # pylint: disable=no-self-use
174    def _guessed_tests(self) -> List[str]:
175        """Guess the potential unit tests commands
176
177        Returns:
178            List[str]: The guessed unit tests commands
179        """
180        return ["forge test"]

Foundry platform

NAME: str = 'Foundry'
PROJECT_URL: str = 'https://github.com/foundry-rs/foundry'
TYPE: crytic_compile.platform.types.Type = <Type.FOUNDRY: 12>
def compile( self, crytic_compile: crytic_compile.crytic_compile.CryticCompile, **kwargs: str) -> None:
37    def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None:
38        """Compile
39
40        Args:
41            crytic_compile (CryticCompile): CryticCompile object to populate
42            **kwargs: optional arguments. Used: "foundry_ignore_compile", "foundry_out_directory"
43
44        """
45
46        ignore_compile = kwargs.get("foundry_ignore_compile", False) or kwargs.get(
47            "ignore_compile", False
48        )
49
50        out_directory = kwargs.get("foundry_out_directory", "out")
51
52        if ignore_compile:
53            LOGGER.info(
54                "--ignore-compile used, if something goes wrong, consider removing the ignore compile flag"
55            )
56
57        if not ignore_compile:
58            compilation_command = [
59                "forge",
60                "build",
61                "--build-info",
62            ]
63
64            compile_all = kwargs.get("foundry_compile_all", False)
65
66            if not compile_all:
67                foundry_config = self.config(self._target)
68                if foundry_config:
69                    compilation_command += [
70                        "--skip",
71                        f"*/{foundry_config.tests_path}/**",
72                        f"*/{foundry_config.scripts_path}/**",
73                        "--force",
74                    ]
75
76            run(
77                compilation_command,
78                cwd=self._target,
79            )
80
81        build_directory = Path(
82            self._target,
83            out_directory,
84            "build-info",
85        )
86
87        hardhat_like_parsing(crytic_compile, self._target, build_directory, self._target)

Compile

Args: crytic_compile (CryticCompile): CryticCompile object to populate **kwargs: optional arguments. Used: "foundry_ignore_compile", "foundry_out_directory"

def clean(self, **kwargs: str) -> None:
 89    def clean(self, **kwargs: str) -> None:
 90        """Clean compilation artifacts
 91
 92        Args:
 93            **kwargs: optional arguments.
 94        """
 95
 96        ignore_compile = kwargs.get("foundry_ignore_compile", False) or kwargs.get(
 97            "ignore_compile", False
 98        )
 99
100        if ignore_compile:
101            return
102
103        run(["forge", "clean"], cwd=self._target)

Clean compilation artifacts

Args: **kwargs: optional arguments.

@staticmethod
def is_supported(target: str, **kwargs: str) -> bool:
105    @staticmethod
106    def is_supported(target: str, **kwargs: str) -> bool:
107        """Check if the target is a foundry project
108
109        Args:
110            target (str): path to the target
111            **kwargs: optional arguments. Used: "foundry_ignore"
112
113        Returns:
114            bool: True if the target is a foundry project
115        """
116        if kwargs.get("foundry_ignore", False):
117            return False
118
119        return os.path.isfile(os.path.join(target, "foundry.toml"))

Check if the target is a foundry project

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

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

@staticmethod
def config( working_dir: str) -> Union[crytic_compile.platform.abstract_platform.PlatformConfig, NoneType]:
121    @staticmethod
122    def config(working_dir: str) -> Optional[PlatformConfig]:
123        """Return configuration data that should be passed to solc, such as remappings.
124
125        Args:
126            working_dir (str): path to the working_dir
127
128        Returns:
129            Optional[PlatformConfig]: Platform configuration data such as optimization, remappings...
130        """
131        result = PlatformConfig()
132        LOGGER.info("'forge config --json' running")
133        json_config = json.loads(
134            subprocess.run(
135                ["forge", "config", "--json"], cwd=working_dir, stdout=subprocess.PIPE, check=True
136            ).stdout
137        )
138
139        # Solc configurations
140        result.solc_version = json_config.get("solc")
141        result.via_ir = json_config.get("via_ir")
142        result.allow_paths = json_config.get("allow_paths")
143        result.offline = json_config.get("offline")
144        result.evm_version = json_config.get("evm_version")
145        result.optimizer = json_config.get("optimizer")
146        result.optimizer_runs = json_config.get("optimizer_runs")
147        result.remappings = json_config.get("remappings")
148
149        # Foundry project configurations
150        result.src_path = json_config.get("src")
151        result.tests_path = json_config.get("test")
152        result.libs_path = json_config.get("libs")
153        result.scripts_path = json_config.get("script")
154
155        return result

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

Args: working_dir (str): path to the working_dir

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

def is_dependency(self, path: str) -> bool:
158    def is_dependency(self, path: str) -> bool:
159        """Check if the path is a dependency
160
161        Args:
162            path (str): path to the target
163
164        Returns:
165            bool: True if the target is a dependency
166        """
167        if path in self._cached_dependencies:
168            return self._cached_dependencies[path]
169        ret = "lib" in Path(path).parts or "node_modules" in Path(path).parts
170        self._cached_dependencies[path] = ret
171        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