Skip to content

Commit 6e2f404

Browse files
committed
Merge branch 'dev' into foundry-multiple-compilation-units-mypy
2 parents 25c1245 + 5af9cbb commit 6e2f404

File tree

14 files changed

+244
-103
lines changed

14 files changed

+244
-103
lines changed

.github/dependabot.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
---
12
version: 2
23
updates:
34
- package-ecosystem: "github-actions"

.github/workflows/ci.yml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
fail-fast: false
1919
matrix:
2020
os: ["ubuntu-latest", "windows-2022"]
21-
type: ["brownie", "buidler", "dapp", "embark", "etherlime", "hardhat", "solc", "truffle", "waffle", "foundry"]
21+
type: ["brownie", "buidler", "dapp", "embark", "etherlime", "hardhat", "solc", "truffle", "waffle", "foundry", "standard"]
2222
exclude:
2323
# Currently broken, tries to pull git:// which is blocked by GH
2424
- type: embark
@@ -39,13 +39,13 @@ jobs:
3939
id: node
4040
shell: bash
4141
run: |
42-
if [ ${{ matrix.type }} = etherlime ]; then
43-
echo '::set-output name=version::10.17.0'
42+
if [ "${{ matrix.type }}" = "etherlime" ]; then
43+
echo 'version=10.17.0' >> "$GITHUB_OUTPUT"
4444
else
45-
echo '::set-output name=version::lts/*'
45+
echo 'version=lts/*' >> "$GITHUB_OUTPUT"
4646
fi
4747
- name: Set up Node
48-
uses: actions/setup-node@v2
48+
uses: actions/setup-node@v3
4949
with:
5050
node-version: ${{ steps.node.outputs.version }}
5151
- name: Set up Python 3.8
@@ -69,6 +69,7 @@ jobs:
6969
env:
7070
TEST_TYPE: ${{ matrix.type }}
7171
GITHUB_ETHERSCAN: ${{ secrets.GITHUB_ETHERSCAN }}
72+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
7273
shell: bash
7374
run: |
7475
bash "scripts/ci_test_${TEST_TYPE}.sh"

.github/workflows/pytest.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
---
12
name: Pytest
23

34
defaults:
@@ -14,16 +15,16 @@ on:
1415
branches: [main, dev]
1516
schedule:
1617
# run CI every day even if no PRs/merges occur
17-
- cron: '0 12 * * *'
18+
- cron: '0 12 * * *'
1819

1920
jobs:
2021
tests:
2122
runs-on: ubuntu-latest
2223

2324
steps:
24-
- uses: actions/checkout@v1
25+
- uses: actions/checkout@v3
2526
- name: Set up Python 3.8
26-
uses: actions/setup-python@v1
27+
uses: actions/setup-python@v4
2728
with:
2829
python-version: 3.8
2930

@@ -35,4 +36,4 @@ jobs:
3536
pip install solc-select
3637
- name: Run Tests
3738
run: |
38-
pytest tests/test_metadata.py
39+
pytest tests/test_metadata.py

crytic_compile/__main__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from crytic_compile.crytic_compile import compile_all, get_platforms
1414
from crytic_compile.cryticparser import DEFAULTS_FLAG_IN_CONFIG, cryticparser
1515
from crytic_compile.platform import InvalidCompilation
16+
from crytic_compile.platform.all_export import PLATFORMS_EXPORT
1617
from crytic_compile.utils.zip import ZIP_TYPES_ACCEPTED, save_to_zip
1718

1819
if TYPE_CHECKING:
@@ -50,8 +51,8 @@ def parse_args() -> argparse.Namespace:
5051

5152
parser.add_argument(
5253
"--export-format",
53-
help="""Export json with non crytic-compile format
54-
(default None. Accepted: standard, solc, truffle)""",
54+
help=f"""Export json with non crytic-compile format
55+
(default None. Accepted: ({", ".join(list(PLATFORMS_EXPORT))})""",
5556
action="store",
5657
dest="export_format",
5758
default=None,

crytic_compile/compilation_unit.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
"""
44
import uuid
55
from collections import defaultdict
6-
from typing import TYPE_CHECKING, Dict, Set
7-
6+
from typing import TYPE_CHECKING, Dict, Set, Optional
87

98
from crytic_compile.compiler.compiler import CompilerVersion
109
from crytic_compile.source_unit import SourceUnit
@@ -35,6 +34,9 @@ def __init__(self, crytic_compile: "CryticCompile", unique_id: str):
3534
# set containing all the filenames of this compilation unit
3635
self._filenames: Set[Filename] = set()
3736

37+
# mapping from absolute/relative/used to filename
38+
self._filenames_lookup: Optional[Dict[str, Filename]] = None
39+
3840
# compiler.compiler
3941
self._compiler_version: CompilerVersion = CompilerVersion(
4042
compiler="N/A", version="N/A", optimized=False
@@ -118,7 +120,6 @@ def create_source_unit(self, filename: Filename) -> SourceUnit:
118120
if not filename in self._source_units:
119121
source_unit = SourceUnit(self, filename) # type: ignore
120122
self.filenames.add(filename)
121-
self.crytic_compile.filenames.add(filename)
122123
self._source_units[filename] = source_unit
123124
return self._source_units[filename]
124125

@@ -194,6 +195,36 @@ def relative_filename_from_absolute_filename(self, absolute_filename: str) -> st
194195
raise ValueError("f{absolute_filename} does not exist in {d}")
195196
return d_file[absolute_filename]
196197

198+
def filename_lookup(self, filename: str) -> Filename:
199+
"""Return a crytic_compile.naming.Filename from a any filename
200+
201+
Args:
202+
filename (str): filename (used/absolute/relative)
203+
204+
Raises:
205+
ValueError: If the filename is not in the project
206+
207+
Returns:
208+
Filename: Associated Filename object
209+
"""
210+
# pylint: disable=import-outside-toplevel
211+
from crytic_compile.platform.truffle import Truffle
212+
213+
if isinstance(self.crytic_compile.platform, Truffle) and filename.startswith("project:/"):
214+
filename = filename[len("project:/") :]
215+
216+
if self._filenames_lookup is None:
217+
self._filenames_lookup = {}
218+
for file in self._filenames:
219+
self._filenames_lookup[file.absolute] = file
220+
self._filenames_lookup[file.relative] = file
221+
self._filenames_lookup[file.used] = file
222+
if filename not in self._filenames_lookup:
223+
raise ValueError(
224+
f"{filename} does not exist in {[f.absolute for f in self._filenames_lookup.values()]}"
225+
)
226+
return self._filenames_lookup[filename]
227+
197228
# endregion
198229
###################################################################################
199230
###################################################################################

crytic_compile/crytic_compile.py

Lines changed: 14 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
from crytic_compile.platform.all_export import PLATFORMS_EXPORT
2020
from crytic_compile.platform.solc import Solc
2121
from crytic_compile.platform.standard import export_to_standard
22-
from crytic_compile.platform.truffle import Truffle
2322
from crytic_compile.utils.naming import Filename
2423
from crytic_compile.utils.npm import get_package_name
2524
from crytic_compile.utils.zip import load_from_zip
@@ -78,12 +77,6 @@ def __init__(self, target: Union[str, AbstractPlatform], **kwargs: str) -> None:
7877
# dependencies is needed for platform conversion
7978
self._dependencies: Set = set()
8079

81-
# set containing all the filenames
82-
self._filenames: Set[Filename] = set()
83-
84-
# mapping from absolute/relative/used to filename
85-
self._filenames_lookup: Optional[Dict[str, Filename]] = None
86-
8780
self._src_content: Dict = {}
8881

8982
# Mapping each file to
@@ -152,27 +145,21 @@ def is_in_multiple_compilation_unit(self, contract: str) -> bool:
152145

153146
###################################################################################
154147
###################################################################################
155-
# region Filenames
148+
# region Utils
156149
###################################################################################
157150
###################################################################################
158-
159151
@property
160152
def filenames(self) -> Set[Filename]:
161-
"""All the project filenames
162-
163-
Returns:
164-
Set[Filename]: Project's filenames
165153
"""
166-
return self._filenames
154+
Return the set of all the filenames used
167155
168-
@filenames.setter
169-
def filenames(self, all_filenames: Set[Filename]) -> None:
170-
"""Set the filenames
171-
172-
Args:
173-
all_filenames (Set[Filename]): New filenames
156+
Returns:
157+
Set[Filename]: list of filenames
174158
"""
175-
self._filenames = all_filenames
159+
filenames: Set[Filename] = set()
160+
for compile_unit in self._compilation_units.values():
161+
filenames = filenames.union(compile_unit.filenames)
162+
return filenames
176163

177164
def filename_lookup(self, filename: str) -> Filename:
178165
"""Return a crytic_compile.naming.Filename from a any filename
@@ -186,21 +173,13 @@ def filename_lookup(self, filename: str) -> Filename:
186173
Returns:
187174
Filename: Associated Filename object
188175
"""
176+
for compile_unit in self.compilation_units.values():
177+
try:
178+
return compile_unit.filename_lookup(filename)
179+
except ValueError:
180+
pass
189181

190-
if isinstance(self.platform, Truffle) and filename.startswith("project:/"):
191-
filename = filename[len("project:/") :]
192-
193-
if self._filenames_lookup is None:
194-
self._filenames_lookup = {}
195-
for file in self._filenames:
196-
self._filenames_lookup[file.absolute] = file
197-
self._filenames_lookup[file.relative] = file
198-
self._filenames_lookup[file.used] = file
199-
if filename not in self._filenames_lookup:
200-
raise ValueError(
201-
f"{filename} does not exist in {[f.absolute for f in self._filenames_lookup.values()]}"
202-
)
203-
return self._filenames_lookup[filename]
182+
raise ValueError(f"{filename} does not exist")
204183

205184
@property
206185
def dependencies(self) -> Set[str]:

crytic_compile/cryticparser/defaults.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@
4040
"buidler_cache_directory": "cache",
4141
"buidler_skip_directory_name_fix": False,
4242
"hardhat_ignore_compile": False,
43-
"hardhat_cache_directory": "cache",
44-
"hardhat_artifacts_directory": "artifacts",
43+
"hardhat_cache_directory": None,
44+
"hardhat_artifacts_directory": None,
4545
"foundry_ignore_compile": False,
4646
"foundry_out_directory": "out",
4747
"export_dir": "crytic-export",

crytic_compile/platform/etherscan.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,9 @@ def _handle_multiple_files(
152152
filtered_paths: List[str] = []
153153
for filename, source_code in source_codes.items():
154154
path_filename = PurePosixPath(filename)
155-
if "contracts" in path_filename.parts and not filename.startswith("@"):
155+
# https://etherscan.io/address/0x19bb64b80cbf61e61965b0e5c2560cc7364c6546#code has an import of erc721a/contracts/ERC721A.sol
156+
# if the full path is lost then won't compile
157+
if "contracts" == path_filename.parts[0] and not filename.startswith("@"):
156158
path_filename = PurePosixPath(
157159
*path_filename.parts[path_filename.parts.index("contracts") :]
158160
)

crytic_compile/platform/hardhat.py

Lines changed: 85 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import shutil
88
import subprocess
99
from pathlib import Path
10-
from typing import TYPE_CHECKING, List
10+
from typing import TYPE_CHECKING, Dict, List, Optional, Union
1111

1212
from crytic_compile.compiler.compiler import CompilerVersion
1313
from crytic_compile.platform.exceptions import InvalidCompilation
@@ -49,7 +49,7 @@ def hardhat_like_parsing(
4949
)
5050
files = [str(f) for f in files if str(f).endswith(".json")]
5151
if not files:
52-
txt = f"`hardhat compile` failed. Can you run it?\n{build_directory} is empty"
52+
txt = f"`compile` failed. Can you run it?\n{build_directory} is empty"
5353
raise InvalidCompilation(txt)
5454

5555
for file in files:
@@ -157,16 +157,20 @@ def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None:
157157
"ignore_compile", False
158158
)
159159

160-
build_directory = Path(
161-
self._target, kwargs.get("hardhat_artifacts_directory", "artifacts"), "build-info"
162-
)
163-
164-
hardhat_working_dir: str = kwargs.get("hardhat_working_dir", self._target)
165-
166160
base_cmd = ["hardhat"]
167161
if not kwargs.get("npx_disable", False):
168162
base_cmd = ["npx"] + base_cmd
169163

164+
detected_paths = self._get_hardhat_paths(base_cmd, kwargs)
165+
166+
build_directory = Path(
167+
self._target,
168+
detected_paths["artifacts"],
169+
"build-info",
170+
)
171+
172+
hardhat_working_dir = str(Path(self._target, detected_paths["root"]))
173+
170174
if not hardhat_ignore_compile:
171175
cmd = base_cmd + ["compile", "--force"]
172176

@@ -235,3 +239,76 @@ def _guessed_tests(self) -> List[str]:
235239
List[str]: The guessed unit tests commands
236240
"""
237241
return ["hardhat test"]
242+
243+
def _get_hardhat_paths(
244+
self, base_cmd: List[str], args: Dict[str, str]
245+
) -> Dict[str, Union[Path, str]]:
246+
"""Obtain hardhat configuration paths, defaulting to the
247+
standard config if needed.
248+
249+
Args:
250+
base_cmd ([str]): hardhat command
251+
args (Dict[str, str]): crytic-compile options that may affect paths
252+
253+
Returns:
254+
Dict[str, str]: hardhat paths configuration
255+
"""
256+
target_path = Path(self._target)
257+
default_paths = {
258+
"root": target_path,
259+
"configFile": target_path.joinpath("hardhat.config.js"),
260+
"sources": target_path.joinpath("contracts"),
261+
"cache": target_path.joinpath("cache"),
262+
"artifacts": target_path.joinpath("artifacts"),
263+
"tests": target_path.joinpath("test"),
264+
}
265+
override_paths = {}
266+
267+
if args.get("hardhat_cache_directory", None):
268+
override_paths["cache"] = Path(target_path, args["hardhat_cache_directory"])
269+
270+
if args.get("hardhat_artifacts_directory", None):
271+
override_paths["artifacts"] = Path(target_path, args["hardhat_artifacts_directory"])
272+
273+
if args.get("hardhat_working_dir", None):
274+
override_paths["root"] = Path(target_path, args["hardhat_working_dir"])
275+
276+
print_paths = "console.log(JSON.stringify(config.paths))"
277+
config_str = self._run_hardhat_console(base_cmd, print_paths)
278+
279+
try:
280+
paths = json.loads(config_str or "{}")
281+
return {**default_paths, **paths, **override_paths}
282+
except ValueError as e:
283+
LOGGER.info("Problem deserializing hardhat configuration: %s", e)
284+
return {**default_paths, **override_paths}
285+
286+
def _run_hardhat_console(self, base_cmd: List[str], command: str) -> Optional[str]:
287+
"""Run a JS command in the hardhat console
288+
289+
Args:
290+
base_cmd ([str]): hardhat command
291+
command (str): console command to run
292+
293+
Returns:
294+
Optional[str]: command output if execution succeeds
295+
"""
296+
with subprocess.Popen(
297+
base_cmd + ["console", "--no-compile"],
298+
stdin=subprocess.PIPE,
299+
stdout=subprocess.PIPE,
300+
stderr=subprocess.PIPE,
301+
cwd=self._target,
302+
executable=shutil.which(base_cmd[0]),
303+
) as process:
304+
stdout_bytes, stderr_bytes = process.communicate(command.encode("utf-8"))
305+
stdout, stderr = (
306+
stdout_bytes.decode(),
307+
stderr_bytes.decode(),
308+
)
309+
310+
if stderr:
311+
LOGGER.info("Problem executing hardhat: %s", stderr)
312+
return None
313+
314+
return stdout

0 commit comments

Comments
 (0)