Skip to content

Commit 818a733

Browse files
committed
Merge branch 'dev' of github.com:crytic/crytic-compile
2 parents 35d95d6 + e1a695a commit 818a733

19 files changed

+318
-5
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,16 @@ jobs:
1717
strategy:
1818
matrix:
1919
os: ["ubuntu-latest", "windows-2022"]
20-
type: ["brownie", "buidler", "dapp", "embark", "etherlime", "hardhat", "solc", "truffle", "waffle"]
20+
type: ["brownie", "buidler", "dapp", "embark", "etherlime", "hardhat", "solc", "truffle", "waffle", "foundry"]
2121
exclude:
2222
# Currently broken, tries to pull git:// which is blocked by GH
2323
- type: embark
2424
# Requires nix
2525
- os: windows-2022
2626
type: dapp
27+
# Explore foundry support in windows
28+
- os: windows-2022
29+
type: foundry
2730
steps:
2831
- uses: actions/checkout@v3
2932
- name: Set up shell
@@ -67,4 +70,4 @@ jobs:
6770
GITHUB_ETHERSCAN: ${{ secrets.GITHUB_ETHERSCAN }}
6871
shell: bash
6972
run: |
70-
bash "scripts/travis_test_${TEST_TYPE}.sh"
73+
bash "scripts/ci_test_${TEST_TYPE}.sh"

.github/workflows/etherscan.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ on:
66
branches:
77
- master
88
- dev
9+
pull_request:
10+
branches: [ master, dev ]
911
schedule:
1012
# run CI every day even if no PRs/merges occur
1113
- cron: '0 12 * * *'
@@ -39,4 +41,4 @@ jobs:
3941
GITHUB_ETHERSCAN: ${{ secrets.GITHUB_ETHERSCAN }}
4042
shell: bash
4143
run: |
42-
bash "scripts/travis_test_${TEST_TYPE}.sh"
44+
bash "scripts/ci_test_${TEST_TYPE}.sh"

crytic_compile/cryticparser/cryticparser.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,3 +439,27 @@ def _init_hardhat(parser: ArgumentParser) -> None:
439439
dest="hardhat_artifacts_directory",
440440
default=DEFAULTS_FLAG_IN_CONFIG["hardhat_artifacts_directory"],
441441
)
442+
443+
444+
def _init_foundry(parser: ArgumentParser) -> None:
445+
"""Init foundry arguments
446+
447+
Args:
448+
parser (ArgumentParser): argparser where the cli flags are added
449+
"""
450+
group_hardhat = parser.add_argument_group("foundry options")
451+
group_hardhat.add_argument(
452+
"--foundry-ignore-compile",
453+
help="Do not run foundry compile",
454+
action="store_true",
455+
dest="foundry_ignore_compile",
456+
default=DEFAULTS_FLAG_IN_CONFIG["foundry_ignore_compile"],
457+
)
458+
459+
group_hardhat.add_argument(
460+
"--foundry-out-directory",
461+
help="Use an alternative out directory (default: out)",
462+
action="store",
463+
dest="foundry_out_directory",
464+
default=DEFAULTS_FLAG_IN_CONFIG["foundry_out_directory"],
465+
)

crytic_compile/cryticparser/defaults.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,7 @@
4242
"hardhat_ignore_compile": False,
4343
"hardhat_cache_directory": "cache",
4444
"hardhat_artifacts_directory": "artifacts",
45+
"foundry_ignore_compile": False,
46+
"foundry_out_directory": "out",
4547
"export_dir": "crytic-export",
4648
}

crytic_compile/platform/all_platforms.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@
1616
from .truffle import Truffle
1717
from .vyper import Vyper
1818
from .waffle import Waffle
19+
from .foundry import Foundry

crytic_compile/platform/brownie.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,10 @@ def is_supported(target: str, **kwargs: str) -> bool:
9898
return False
9999
# < 1.1.0: brownie-config.json
100100
# >= 1.1.0: brownie-config.yaml
101-
return os.path.isfile(os.path.join(target, "brownie-config.json")) or os.path.isfile(
102-
os.path.join(target, "brownie-config.yaml")
101+
return (
102+
os.path.isfile(os.path.join(target, "brownie-config.json"))
103+
or os.path.isfile(os.path.join(target, "brownie-config.yaml"))
104+
or os.path.isfile(os.path.join(target, "brownie-config.yml"))
103105
)
104106

105107
def is_dependency(self, _path: str) -> bool:

crytic_compile/platform/foundry.py

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
"""
2+
Truffle platform
3+
"""
4+
import json
5+
import logging
6+
import os
7+
import shutil
8+
import subprocess
9+
from pathlib import Path
10+
from typing import TYPE_CHECKING, List, Tuple, Optional
11+
12+
from crytic_compile.compilation_unit import CompilationUnit
13+
from crytic_compile.compiler.compiler import CompilerVersion
14+
from crytic_compile.platform.abstract_platform import AbstractPlatform
15+
from crytic_compile.platform.exceptions import InvalidCompilation
16+
from crytic_compile.platform.types import Type
17+
from crytic_compile.utils.naming import convert_filename
18+
from crytic_compile.utils.natspec import Natspec
19+
20+
# Handle cycle
21+
if TYPE_CHECKING:
22+
from crytic_compile import CryticCompile
23+
24+
LOGGER = logging.getLogger("CryticCompile")
25+
26+
27+
class Foundry(AbstractPlatform):
28+
"""
29+
Foundry platform
30+
"""
31+
32+
NAME = "Foundry"
33+
PROJECT_URL = "https://github.com/gakonst/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+
Raises:
45+
InvalidCompilation: If foundry failed to run
46+
"""
47+
48+
ignore_compile = kwargs.get("foundry_ignore_compile", False) or kwargs.get(
49+
"ignore_compile", False
50+
)
51+
52+
out_directory = kwargs.get("foundry_out_directory", "out")
53+
54+
if ignore_compile:
55+
LOGGER.info(
56+
"--ignore-compile used, if something goes wrong, consider removing the ignore compile flag"
57+
)
58+
59+
if not ignore_compile:
60+
cmd = [
61+
"forge",
62+
"build",
63+
"--extra-output",
64+
"abi",
65+
"--extra-output",
66+
"userdoc",
67+
"--extra-output",
68+
"devdoc",
69+
"--extra-output",
70+
"evm.methodIdentifiers",
71+
"--force",
72+
]
73+
74+
LOGGER.info(
75+
"'%s' running",
76+
" ".join(cmd),
77+
)
78+
79+
with subprocess.Popen(
80+
cmd,
81+
stdout=subprocess.PIPE,
82+
stderr=subprocess.PIPE,
83+
cwd=self._target,
84+
executable=shutil.which(cmd[0]),
85+
) as process:
86+
87+
stdout_bytes, stderr_bytes = process.communicate()
88+
stdout, stderr = (
89+
stdout_bytes.decode(),
90+
stderr_bytes.decode(),
91+
) # convert bytestrings to unicode strings
92+
93+
LOGGER.info(stdout)
94+
if stderr:
95+
LOGGER.error(stderr)
96+
97+
filenames = Path(self._target, out_directory).rglob("*.json")
98+
99+
# foundry only support solc for now
100+
compiler = "solc"
101+
compilation_unit = CompilationUnit(crytic_compile, str(self._target))
102+
103+
for filename_txt in filenames:
104+
with open(filename_txt, encoding="utf8") as file_desc:
105+
target_loaded = json.load(file_desc)
106+
107+
userdoc = target_loaded.get("userdoc", {})
108+
devdoc = target_loaded.get("devdoc", {})
109+
natspec = Natspec(userdoc, devdoc)
110+
111+
if not "ast" in target_loaded:
112+
continue
113+
114+
filename = target_loaded["ast"]["absolutePath"]
115+
116+
try:
117+
filename = convert_filename(
118+
filename, lambda x: x, crytic_compile, working_dir=self._target
119+
)
120+
except InvalidCompilation as i:
121+
txt = str(i)
122+
txt += "\nSomething went wrong, please open an issue in https://github.com/crytic/crytic-compile"
123+
# pylint: disable=raise-missing-from
124+
raise InvalidCompilation(txt)
125+
126+
compilation_unit.asts[filename.absolute] = target_loaded["ast"]
127+
crytic_compile.filenames.add(filename)
128+
compilation_unit.filenames.add(filename)
129+
130+
contract_name = filename_txt.parts[-1]
131+
contract_name = contract_name[: -len(".json")]
132+
133+
compilation_unit.natspec[contract_name] = natspec
134+
compilation_unit.filename_to_contracts[filename].add(contract_name)
135+
compilation_unit.contracts_names.add(contract_name)
136+
compilation_unit.abis[contract_name] = target_loaded["abi"]
137+
compilation_unit.bytecodes_init[contract_name] = target_loaded["bytecode"][
138+
"object"
139+
].replace("0x", "")
140+
compilation_unit.bytecodes_runtime[contract_name] = target_loaded[
141+
"deployedBytecode"
142+
]["object"].replace("0x", "")
143+
compilation_unit.srcmaps_init[contract_name] = target_loaded["bytecode"][
144+
"sourceMap"
145+
].split(";")
146+
compilation_unit.srcmaps_runtime[contract_name] = target_loaded["deployedBytecode"][
147+
"sourceMap"
148+
].split(";")
149+
150+
version, optimized, runs = _get_config_info(self._target)
151+
152+
compilation_unit.compiler_version = CompilerVersion(
153+
compiler=compiler, version=version, optimized=optimized, optimize_runs=runs
154+
)
155+
156+
@staticmethod
157+
def is_supported(target: str, **kwargs: str) -> bool:
158+
"""Check if the target is a foundry project
159+
160+
Args:
161+
target (str): path to the target
162+
**kwargs: optional arguments. Used: "foundry_ignore"
163+
164+
Returns:
165+
bool: True if the target is a foundry project
166+
"""
167+
if kwargs.get("foundry_ignore", False):
168+
return False
169+
170+
return os.path.isfile(os.path.join(target, "foundry.toml"))
171+
172+
# pylint: disable=no-self-use
173+
def is_dependency(self, path: str) -> bool:
174+
"""Check if the path is a dependency
175+
176+
Args:
177+
path (str): path to the target
178+
179+
Returns:
180+
bool: True if the target is a dependency
181+
"""
182+
if path in self._cached_dependencies:
183+
return self._cached_dependencies[path]
184+
ret = "lib" in Path(path).parts
185+
self._cached_dependencies[path] = ret
186+
return ret
187+
188+
# pylint: disable=no-self-use
189+
def _guessed_tests(self) -> List[str]:
190+
"""Guess the potential unit tests commands
191+
192+
Returns:
193+
List[str]: The guessed unit tests commands
194+
"""
195+
return ["forge test"]
196+
197+
198+
def _get_config_info(target: str) -> Tuple[str, Optional[bool], Optional[int]]:
199+
"""get the compiler version from solidity-files-cache.json
200+
201+
Args:
202+
target (str): path to the project directory
203+
204+
Returns:
205+
(str, str, str): compiler version, optimized, runs
206+
207+
Raises:
208+
InvalidCompilation: If cache/solidity-files-cache.json cannot be parsed
209+
"""
210+
config = Path(target, "cache", "solidity-files-cache.json")
211+
if not config.exists():
212+
raise InvalidCompilation(
213+
"Could not find the cache/solidity-files-cache.json file."
214+
+ "If you are using 'cache = true' in foundry's config file, please remove it."
215+
+ " Otherwise please open an issue in https://github.com/crytic/crytic-compile"
216+
)
217+
with open(config, "r", encoding="utf8") as config_f:
218+
config_dict = json.load(config_f)
219+
220+
version: Optional[str] = None
221+
optimizer: Optional[bool] = None
222+
runs: Optional[int] = None
223+
224+
if "files" in config_dict:
225+
items = list(config_dict["files"].values())
226+
# On the form
227+
# { ..
228+
# "artifacts": {
229+
# "CONTRACT_NAME": {
230+
# "0.8.X+commit...": "filename"}
231+
#
232+
if len(items) >= 1:
233+
item = items[0]
234+
if "artifacts" in item:
235+
items_artifact = list(item["artifacts"].values())
236+
if len(items_artifact) >= 1:
237+
item_version = items_artifact[0]
238+
version = list(item_version.keys())[0]
239+
assert version
240+
plus_position = version.find("+")
241+
if plus_position > 0:
242+
version = version[:plus_position]
243+
if (
244+
"solcConfig" in item
245+
and "settings" in item["solcConfig"]
246+
and "optimizer" in item["solcConfig"]["settings"]
247+
):
248+
optimizer = item["solcConfig"]["settings"]["optimizer"]["enabled"]
249+
runs = item["solcConfig"]["settings"]["optimizer"].get("runs", None)
250+
251+
if version is None:
252+
raise InvalidCompilation(
253+
"Something went wrong with cache/solidity-files-cache.json parsing"
254+
+ ". Please open an issue in https://github.com/crytic/crytic-compile"
255+
)
256+
257+
return version, optimizer, runs

crytic_compile/platform/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class Type(IntEnum):
2323
SOLC_STANDARD_JSON = 10
2424
BUILDER = 11
2525
HARDHAT = 11
26+
FOUNDRY = 12
2627

2728
STANDARD = 100
2829
ARCHIVE = 101
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)