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"]
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
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"
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.
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
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...
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