Skip to content

Commit fa60e8d

Browse files
authored
Merge pull request #488 from crytic/dev-auto-compile
Improve foundry compilation
2 parents feac1a1 + 7f85442 commit fa60e8d

File tree

10 files changed

+199
-19
lines changed

10 files changed

+199
-19
lines changed

crytic_compile/crytic_compile.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414
from pathlib import Path
1515
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type, Union
1616

17+
from solc_select.solc_select import (
18+
install_artifacts,
19+
installed_versions,
20+
current_version,
21+
artifact_path,
22+
)
1723
from crytic_compile.compilation_unit import CompilationUnit
1824
from crytic_compile.platform import all_platforms, solc_standard_json
1925
from crytic_compile.platform.abstract_platform import AbstractPlatform
@@ -84,6 +90,7 @@ class CryticCompile:
8490
Main class.
8591
"""
8692

93+
# pylint: disable=too-many-branches
8794
def __init__(self, target: Union[str, AbstractPlatform], **kwargs: str) -> None:
8895
"""See https://github.com/crytic/crytic-compile/wiki/Configuration
8996
Target is usually a file or a project directory. It can be an AbstractPlatform
@@ -114,8 +121,55 @@ def __init__(self, target: Union[str, AbstractPlatform], **kwargs: str) -> None:
114121

115122
self._working_dir = Path.cwd()
116123

124+
# pylint: disable=too-many-nested-blocks
117125
if isinstance(target, str):
118126
platform = self._init_platform(target, **kwargs)
127+
# If the platform is Solc it means we are trying to compile a single
128+
# we try to see if we are in a known compilation framework to retrieve
129+
# information like remappings and solc version
130+
if isinstance(platform, Solc):
131+
# Try to get the platform of the current working directory
132+
platform_wd = next(
133+
(
134+
p(target)
135+
for p in get_platforms()
136+
if p.is_supported(str(self._working_dir), **kwargs)
137+
),
138+
None,
139+
)
140+
# If no platform has been found or if it's a Solc we can't do anything
141+
if platform_wd and not isinstance(platform_wd, Solc):
142+
platform_config = platform_wd.config(str(self._working_dir))
143+
if platform_config:
144+
kwargs["solc_args"] = ""
145+
kwargs["solc_remaps"] = ""
146+
147+
if platform_config.remappings:
148+
kwargs["solc_remaps"] = platform_config.remappings
149+
if (
150+
platform_config.solc_version
151+
and platform_config.solc_version != current_version()[0]
152+
):
153+
solc_version = platform_config.solc_version
154+
if solc_version in installed_versions():
155+
kwargs["solc"] = str(artifact_path(solc_version).absolute())
156+
else:
157+
# Respect foundry offline option and don't install a missing solc version
158+
if not platform_config.offline:
159+
install_artifacts([solc_version])
160+
kwargs["solc"] = str(artifact_path(solc_version).absolute())
161+
if platform_config.optimizer:
162+
kwargs["solc_args"] += "--optimize"
163+
if platform_config.optimizer_runs:
164+
kwargs[
165+
"solc_args"
166+
] += f"--optimize-runs {platform_config.optimizer_runs}"
167+
if platform_config.via_ir:
168+
kwargs["solc_args"] += "--via-ir"
169+
if platform_config.allow_paths:
170+
kwargs["solc_args"] += f"--allow-paths {platform_config.allow_paths}"
171+
if platform_config.evm_version:
172+
kwargs["solc_args"] += f"--evm-version {platform_config.evm_version}"
119173
else:
120174
platform = target
121175

crytic_compile/cryticparser/cryticparser.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,3 +520,11 @@ def _init_foundry(parser: ArgumentParser) -> None:
520520
dest="foundry_out_directory",
521521
default=DEFAULTS_FLAG_IN_CONFIG["foundry_out_directory"],
522522
)
523+
524+
group_foundry.add_argument(
525+
"--foundry-compile-all",
526+
help="Don't skip compiling test and script",
527+
action="store_true",
528+
dest="foundry_compile_all",
529+
default=DEFAULTS_FLAG_IN_CONFIG["foundry_compile_all"],
530+
)

crytic_compile/cryticparser/defaults.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"hardhat_artifacts_directory": None,
4646
"foundry_ignore_compile": False,
4747
"foundry_out_directory": "out",
48+
"foundry_compile_all": False,
4849
"export_dir": "crytic-export",
4950
"compile_libraries": None,
5051
}

crytic_compile/platform/abstract_platform.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
This gives the skeleton for any platform supported by crytic-compile
55
"""
66
import abc
7-
from typing import TYPE_CHECKING, List, Dict
7+
from typing import TYPE_CHECKING, List, Dict, Optional
8+
from dataclasses import dataclass, field
89

910
from crytic_compile.platform import Type
1011
from crytic_compile.utils.unit_tests import guess_tests
@@ -22,6 +23,27 @@ class IncorrectPlatformInitialization(Exception):
2223
pass
2324

2425

26+
# pylint: disable=too-many-instance-attributes
27+
@dataclass
28+
class PlatformConfig:
29+
"""
30+
This class represents a generic platform configuration
31+
"""
32+
33+
offline: bool = False
34+
remappings: Optional[str] = None
35+
solc_version: Optional[str] = None
36+
optimizer: bool = False
37+
optimizer_runs: Optional[int] = None
38+
via_ir: bool = False
39+
allow_paths: Optional[str] = None
40+
evm_version: Optional[str] = None
41+
src_path: str = "src"
42+
tests_path: str = "test"
43+
libs_path: List[str] = field(default_factory=lambda: ["lib"])
44+
scripts_path: str = "script"
45+
46+
2547
class AbstractPlatform(metaclass=abc.ABCMeta):
2648
"""
2749
This is the abstract class for the platform
@@ -154,6 +176,18 @@ def is_dependency(self, path: str) -> bool:
154176
"""
155177
return False
156178

179+
@staticmethod
180+
def config(working_dir: str) -> Optional[PlatformConfig]: # pylint: disable=unused-argument
181+
"""Return configuration data that should be passed to solc, such as version, remappings ecc.
182+
183+
Args:
184+
working_dir (str): path to the target
185+
186+
Returns:
187+
Optional[PlatformConfig]: Platform configuration data such as optimization, remappings...
188+
"""
189+
return None
190+
157191
# Only _guessed_tests is an abstract method
158192
# guessed_tests will call the generic guess_tests and appends to the list
159193
# platforms-dependent tests

crytic_compile/platform/buidler.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@
99
from pathlib import Path
1010
from typing import TYPE_CHECKING, List, Tuple
1111

12+
from crytic_compile.compilation_unit import CompilationUnit
1213
from crytic_compile.compiler.compiler import CompilerVersion
14+
from crytic_compile.platform.abstract_platform import AbstractPlatform
1315
from crytic_compile.platform.exceptions import InvalidCompilation
1416
from crytic_compile.platform.types import Type
1517
from crytic_compile.utils.naming import convert_filename, extract_name
1618
from crytic_compile.utils.natspec import Natspec
17-
from crytic_compile.compilation_unit import CompilationUnit
18-
from .abstract_platform import AbstractPlatform
1919

2020
# Handle cycle
2121
from .solc import relative_to_short

crytic_compile/platform/dapp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@
1919
from crytic_compile.platform.abstract_platform import AbstractPlatform
2020
from crytic_compile.platform.types import Type
2121
from crytic_compile.utils.naming import convert_filename, extract_name
22-
from crytic_compile.utils.subprocess import run
2322

2423
# Handle cycle
2524
from crytic_compile.utils.natspec import Natspec
25+
from crytic_compile.utils.subprocess import run
2626

2727
if TYPE_CHECKING:
2828
from crytic_compile import CryticCompile

crytic_compile/platform/foundry.py

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
"""
44
import logging
55
import os
6+
import subprocess
67
from pathlib import Path
7-
from typing import TYPE_CHECKING, List
8+
from typing import TYPE_CHECKING, List, Optional
9+
import toml
810

9-
from crytic_compile.platform.abstract_platform import AbstractPlatform
11+
from crytic_compile.platform.abstract_platform import AbstractPlatform, PlatformConfig
1012
from crytic_compile.platform.types import Type
1113
from crytic_compile.platform.hardhat import hardhat_like_parsing
1214
from crytic_compile.utils.subprocess import run
@@ -24,7 +26,7 @@ class Foundry(AbstractPlatform):
2426
"""
2527

2628
NAME = "Foundry"
27-
PROJECT_URL = "https://github.com/gakonst/foundry"
29+
PROJECT_URL = "https://github.com/foundry-rs/foundry"
2830
TYPE = Type.FOUNDRY
2931

3032
# pylint: disable=too-many-locals,too-many-statements,too-many-branches
@@ -49,12 +51,26 @@ def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None:
4951
)
5052

5153
if not ignore_compile:
54+
compilation_command = [
55+
"forge",
56+
"build",
57+
"--build-info",
58+
]
59+
60+
compile_all = kwargs.get("foundry_compile_all", False)
61+
62+
if not compile_all:
63+
foundry_config = self.config(str(crytic_compile.working_dir.absolute()))
64+
if foundry_config:
65+
compilation_command += [
66+
"--skip",
67+
f"*/{foundry_config.tests_path}/**",
68+
f"*/{foundry_config.scripts_path}/**",
69+
"--force",
70+
]
71+
5272
run(
53-
[
54-
"forge",
55-
"build",
56-
"--build-info",
57-
],
73+
compilation_command,
5874
cwd=self._target,
5975
)
6076

@@ -98,6 +114,61 @@ def is_supported(target: str, **kwargs: str) -> bool:
98114

99115
return os.path.isfile(os.path.join(target, "foundry.toml"))
100116

117+
@staticmethod
118+
def config(working_dir: str) -> Optional[PlatformConfig]:
119+
"""Return configuration data that should be passed to solc, such as remappings.
120+
121+
Args:
122+
working_dir (str): path to the working directory
123+
124+
Returns:
125+
Optional[PlatformConfig]: Platform configuration data such as optimization, remappings...
126+
"""
127+
result = PlatformConfig()
128+
result.remappings = (
129+
subprocess.run(["forge", "remappings"], stdout=subprocess.PIPE, check=True)
130+
.stdout.decode("utf-8")
131+
.replace("\n", " ")
132+
.strip()
133+
)
134+
with open("foundry.toml", "r", encoding="utf-8") as f:
135+
foundry_toml = toml.loads(f.read())
136+
default_profile = foundry_toml["profile"]["default"]
137+
138+
if "solc_version" in default_profile:
139+
result.solc_version = default_profile["solc_version"]
140+
if "offline" in default_profile:
141+
result.offline = default_profile["offline"]
142+
if "optimizer" in default_profile:
143+
result.optimizer = default_profile["optimizer"]
144+
else:
145+
# Default to true
146+
result.optimizer = True
147+
if "optimizer_runs" in default_profile:
148+
result.optimizer_runs = default_profile["optimizer_runs"]
149+
else:
150+
# Default to 200
151+
result.optimizer_runs = 200
152+
if "via_ir" in default_profile:
153+
result.via_ir = default_profile["via_ir"]
154+
if "allow_paths" in default_profile:
155+
result.allow_paths = default_profile["allow_paths"]
156+
if "evm_version" in default_profile:
157+
result.evm_version = default_profile["evm_version"]
158+
else:
159+
# Default to london
160+
result.evm_version = "london"
161+
if "src" in default_profile:
162+
result.src_path = default_profile["src"]
163+
if "test" in default_profile:
164+
result.tests_path = default_profile["test"]
165+
if "libs" in default_profile:
166+
result.libs_path = default_profile["libs"]
167+
if "script" in default_profile:
168+
result.scripts_path = default_profile["script"]
169+
170+
return result
171+
101172
# pylint: disable=no-self-use
102173
def is_dependency(self, path: str) -> bool:
103174
"""Check if the path is a dependency

crytic_compile/platform/hardhat.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@
99
from pathlib import Path
1010
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
1111

12+
from crytic_compile.compilation_unit import CompilationUnit
1213
from crytic_compile.compiler.compiler import CompilerVersion
14+
from crytic_compile.platform.abstract_platform import AbstractPlatform
1315
from crytic_compile.platform.exceptions import InvalidCompilation
16+
17+
# Handle cycle
18+
from crytic_compile.platform.solc import relative_to_short
1419
from crytic_compile.platform.types import Type
1520
from crytic_compile.utils.naming import convert_filename, extract_name
1621
from crytic_compile.utils.natspec import Natspec
1722
from crytic_compile.utils.subprocess import run
18-
from crytic_compile.platform.abstract_platform import AbstractPlatform
19-
20-
# Handle cycle
21-
from crytic_compile.platform.solc import relative_to_short
22-
from crytic_compile.compilation_unit import CompilationUnit
2323

2424
if TYPE_CHECKING:
2525
from crytic_compile import CryticCompile

crytic_compile/platform/waffle.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from crytic_compile.compilation_unit import CompilationUnit
1616
from crytic_compile.compiler.compiler import CompilerVersion
17-
from crytic_compile.platform.abstract_platform import AbstractPlatform
17+
from crytic_compile.platform.abstract_platform import AbstractPlatform, PlatformConfig
1818
from crytic_compile.platform.exceptions import InvalidCompilation
1919
from crytic_compile.platform.types import Type
2020
from crytic_compile.utils.naming import convert_filename
@@ -260,6 +260,18 @@ def is_supported(target: str, **kwargs: str) -> bool:
260260

261261
return False
262262

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+
263275
def is_dependency(self, path: str) -> bool:
264276
"""Check if the path is a dependency
265277

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
version="0.3.4",
1515
packages=find_packages(),
1616
python_requires=">=3.8",
17-
install_requires=["pycryptodome>=3.4.6", "cbor2", "solc-select>=v1.0.4"],
17+
install_requires=["pycryptodome>=3.4.6", "cbor2", "solc-select>=v1.0.4", "toml>=0.10.2"],
1818
extras_require={
1919
"test": [
2020
"pytest",

0 commit comments

Comments
 (0)