Skip to content

Commit

Permalink
Add foundry support
Browse files Browse the repository at this point in the history
Fix 230
Related crytic/slither#1007
  • Loading branch information
montyly committed Mar 31, 2022
1 parent e7a0c72 commit 05b29f0
Show file tree
Hide file tree
Showing 17 changed files with 301 additions and 2 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
type: ["solc", "truffle", "embark", "etherlime", "brownie", "waffle", "buidler", "hardhat"]
type: ["solc", "truffle", "embark", "etherlime", "brownie", "waffle", "buidler", "hardhat", "foundry"]
steps:
- uses: actions/checkout@v1
- name: Set up Python 3.6
Expand All @@ -32,4 +32,4 @@ jobs:
TEST_TYPE: ${{ matrix.type }}
GITHUB_ETHERSCAN: ${{ secrets.GITHUB_ETHERSCAN }}
run: |
bash "scripts/travis_test_${TEST_TYPE}.sh"
bash "scripts/ci_test_${TEST_TYPE}.sh"
24 changes: 24 additions & 0 deletions crytic_compile/cryticparser/cryticparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,3 +439,27 @@ def _init_hardhat(parser: ArgumentParser) -> None:
dest="hardhat_artifacts_directory",
default=DEFAULTS_FLAG_IN_CONFIG["hardhat_artifacts_directory"],
)


def _init_foundry(parser: ArgumentParser) -> None:
"""Init foundry arguments
Args:
parser (ArgumentParser): argparser where the cli flags are added
"""
group_hardhat = parser.add_argument_group("foundry options")
group_hardhat.add_argument(
"--foundry-ignore-compile",
help="Do not run foundry compile",
action="store_true",
dest="foundry_ignore_compile",
default=DEFAULTS_FLAG_IN_CONFIG["foundry_ignore_compile"],
)

group_hardhat.add_argument(
"--foundry-out-directory",
help="Use an alternative out directory (default: out)",
action="store",
dest="foundry_out_directory",
default=DEFAULTS_FLAG_IN_CONFIG["foundry_out_directory"],
)
2 changes: 2 additions & 0 deletions crytic_compile/cryticparser/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,7 @@
"hardhat_ignore_compile": False,
"hardhat_cache_directory": "cache",
"hardhat_artifacts_directory": "artifacts",
"foundry_ignore_compile": False,
"foundry_out_directory": "out",
"export_dir": "crytic-export",
}
1 change: 1 addition & 0 deletions crytic_compile/platform/all_platforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@
from .truffle import Truffle
from .vyper import Vyper
from .waffle import Waffle
from .foundry import Foundry
252 changes: 252 additions & 0 deletions crytic_compile/platform/foundry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
"""
Truffle platform
"""
import json
import logging
import os
import subprocess
from pathlib import Path
from typing import TYPE_CHECKING, List, Tuple, Optional

from crytic_compile.compilation_unit import CompilationUnit
from crytic_compile.compiler.compiler import CompilerVersion
from crytic_compile.platform.abstract_platform import AbstractPlatform
from crytic_compile.platform.exceptions import InvalidCompilation
from crytic_compile.platform.types import Type
from crytic_compile.utils.naming import convert_filename
from crytic_compile.utils.natspec import Natspec

# Handle cycle
if TYPE_CHECKING:
from crytic_compile import CryticCompile

LOGGER = logging.getLogger("CryticCompile")


class Foundry(AbstractPlatform):
"""
Foundry platform
"""

NAME = "Foundry"
PROJECT_URL = "https://github.com/gakonst/foundry"
TYPE = Type.FOUNDRY

# pylint: disable=too-many-locals,too-many-statements,too-many-branches
def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None:
"""Compile
Args:
crytic_compile (CryticCompile): CryticCompile object to populate
**kwargs: optional arguments. Used: "foundry_ignore_compile", "foundry_out_directory"
Raises:
InvalidCompilation: If foundry failed to run
"""

ignore_compile = kwargs.get("foundry_ignore_compile", False) or kwargs.get(
"ignore_compile", False
)

out_directory = kwargs.get("foundry_out_directory", "out")

if ignore_compile:
LOGGER.info(
"--ignore-compile used, if something goes wrong, consider removing the ignore compile flag"
)

if not ignore_compile:
cmd = [
"forge",
"build",
"--extra-output",
"abi",
"--extra-output",
"userdoc",
"--extra-output",
"devdoc",
"--extra-output",
"evm.methodIdentifiers",
"--force",
]

LOGGER.info(
"'%s' running",
" ".join(cmd),
)

with subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self._target
) as process:

stdout_bytes, stderr_bytes = process.communicate()
stdout, stderr = (
stdout_bytes.decode(),
stderr_bytes.decode(),
) # convert bytestrings to unicode strings

LOGGER.info(stdout)
if stderr:
LOGGER.error(stderr)

filenames = Path(self._target, out_directory).rglob("*.json")

# foundry only support solc for now
compiler = "solc"
compilation_unit = CompilationUnit(crytic_compile, str(self._target))

for filename_txt in filenames:
with open(filename_txt, encoding="utf8") as file_desc:
target_loaded = json.load(file_desc)

userdoc = target_loaded.get("userdoc", {})
devdoc = target_loaded.get("devdoc", {})
natspec = Natspec(userdoc, devdoc)

if not "ast" in target_loaded:
continue

filename = target_loaded["ast"]["absolutePath"]

try:
filename = convert_filename(
filename, lambda x: x, crytic_compile, working_dir=self._target
)
except InvalidCompilation as i:
txt = str(i)
txt += "\nSomething went wrong, please open an issue in https://github.com/crytic/crytic-compile"
# pylint: disable=raise-missing-from
raise InvalidCompilation(txt)

compilation_unit.asts[filename.absolute] = target_loaded["ast"]
crytic_compile.filenames.add(filename)
compilation_unit.filenames.add(filename)

contract_name = filename_txt.parts[-1]
contract_name = contract_name[: -len(".json")]

compilation_unit.natspec[contract_name] = natspec
compilation_unit.filename_to_contracts[filename].add(contract_name)
compilation_unit.contracts_names.add(contract_name)
compilation_unit.abis[contract_name] = target_loaded["abi"]
compilation_unit.bytecodes_init[contract_name] = target_loaded["bytecode"][
"object"
].replace("0x", "")
compilation_unit.bytecodes_runtime[contract_name] = target_loaded[
"deployedBytecode"
]["object"].replace("0x", "")
compilation_unit.srcmaps_init[contract_name] = target_loaded["bytecode"][
"sourceMap"
].split(";")
compilation_unit.srcmaps_runtime[contract_name] = target_loaded["deployedBytecode"][
"sourceMap"
].split(";")

version, optimized, runs = _get_config_info(self._target)

compilation_unit.compiler_version = CompilerVersion(
compiler=compiler, version=version, optimized=optimized, optimize_runs=runs
)

@staticmethod
def is_supported(target: str, **kwargs: str) -> bool:
"""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
"""
if kwargs.get("foundry_ignore", False):
return False

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

# pylint: disable=no-self-use
def is_dependency(self, path: str) -> bool:
"""Check if the path is a dependency
Args:
path (str): path to the target
Returns:
bool: True if the target is a dependency
"""
if path in self._cached_dependencies:
return self._cached_dependencies[path]
ret = "lib" in Path(path).parts
self._cached_dependencies[path] = ret
return ret

# pylint: disable=no-self-use
def _guessed_tests(self) -> List[str]:
"""Guess the potential unit tests commands
Returns:
List[str]: The guessed unit tests commands
"""
return ["forge test"]


def _get_config_info(target: str) -> Tuple[str, Optional[bool], Optional[int]]:
"""get the compiler version from solidity-files-cache.json
Args:
target (str): path to the project directory
Returns:
(str, str, str): compiler version, optimized, runs
Raises:
InvalidCompilation: If cache/solidity-files-cache.json cannot be parsed
"""
config = Path(target, "cache", "solidity-files-cache.json")
if not config.exists():
raise InvalidCompilation(
"Could not find the cache/solidity-files-cache.json file."
+ "If you are using 'cache = true' in foundry's config file, please remove it."
+ " Otherwise please open an issue in https://github.com/crytic/crytic-compile"
)
with open(config, "r", encoding="utf8") as config_f:
config_dict = json.load(config_f)

version: Optional[str] = None
optimizer: Optional[bool] = None
runs: Optional[int] = None

if "files" in config_dict:
items = list(config_dict["files"].values())
# On the form
# { ..
# "artifacts": {
# "CONTRACT_NAME": {
# "0.8.X+commit...": "filename"}
#
if len(items) >= 1:
item = items[0]
if "artifacts" in item:
items_artifact = list(item["artifacts"].values())
if len(items_artifact) >= 1:
item_version = items_artifact[0]
version = list(item_version.keys())[0]
assert version
plus_position = version.find("+")
if plus_position > 0:
version = version[:plus_position]
if (
"solcConfig" in item
and "settings" in item["solcConfig"]
and "optimizer" in item["solcConfig"]["settings"]
):
optimizer = item["solcConfig"]["settings"]["optimizer"]["enabled"]
runs = item["solcConfig"]["settings"]["optimizer"].get("runs", None)

if version is None:
raise InvalidCompilation(
"Something went wrong with cache/solidity-files-cache.json parsing"
+ ". Please open an issue in https://github.com/crytic/crytic-compile"
)

return version, optimizer, runs
1 change: 1 addition & 0 deletions crytic_compile/platform/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class Type(IntEnum):
SOLC_STANDARD_JSON = 10
BUILDER = 11
HARDHAT = 11
FOUNDRY = 12

STANDARD = 100
ARCHIVE = 101
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
19 changes: 19 additions & 0 deletions scripts/ci_test_foundry.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env bash

### Test foundry integration


cd /tmp || exit 255

curl -L https://foundry.paradigm.xyz | bash
source ~/.bash_profile
foundryup

forge init

crytic-compile .
if [ $? -ne 0 ]
then
echo "hardhat test failed"
exit 255
fi
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 comments on commit 05b29f0

Please sign in to comment.