From b6a24ddd6f05c4bc7f4bda1e5f42ac28cd045c70 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 17 Oct 2023 11:14:28 -0500 Subject: [PATCH] improve foundry config parsing - support snake and kebab case - add warning if version not available - support solc config as path --- crytic_compile/crytic_compile.py | 47 +++++++++++++++++++++--------- crytic_compile/platform/foundry.py | 38 ++++++++++++++++-------- 2 files changed, 59 insertions(+), 26 deletions(-) diff --git a/crytic_compile/crytic_compile.py b/crytic_compile/crytic_compile.py index 4df96647..419a7365 100644 --- a/crytic_compile/crytic_compile.py +++ b/crytic_compile/crytic_compile.py @@ -17,7 +17,6 @@ from solc_select.solc_select import ( install_artifacts, installed_versions, - current_version, artifact_path, ) from crytic_compile.compilation_unit import CompilationUnit @@ -86,6 +85,31 @@ def _extract_libraries(libraries_str: Optional[str]) -> Optional[Dict[str, int]] return ret +def _configure_solc(solc_requested: str, offline: bool) -> str: + """ + Determine which solc binary to use based on the requested version or path (e.g. '0.8.0' or '/usr/bin/solc-0.8.0'). + + Args: + solc_requested (str): solc version or path + offline (bool): whether to allow network requests + + Returns: + str: path to solc binary + """ + if Path(solc_requested).exists(): + solc_path = Path(solc_requested) + else: + solc_version = solc_requested + if solc_requested in installed_versions(): + solc_path = artifact_path(solc_requested) + else: + # Respect foundry offline option and skip installation. + if not offline: + install_artifacts([solc_version]) + solc_path = artifact_path(solc_version) + return solc_path.absolute().as_posix() + + # pylint: disable=too-many-instance-attributes class CryticCompile: """ @@ -139,7 +163,7 @@ def __init__(self, target: Union[str, AbstractPlatform], **kwargs: str) -> None: ), None, ) - # If no platform has been found or if it's a Solc we can't do anything + # If no platform has been found or if it's the Solc platform, we can't automatically compile. if platform_wd and not isinstance(platform_wd, Solc): platform_config = platform_wd.config(str(self._working_dir)) if platform_config: @@ -148,18 +172,13 @@ def __init__(self, target: Union[str, AbstractPlatform], **kwargs: str) -> None: if platform_config.remappings: kwargs["solc_remaps"] = platform_config.remappings - if ( - platform_config.solc_version - and platform_config.solc_version != current_version()[0] - ): - solc_version = platform_config.solc_version - if solc_version in installed_versions(): - kwargs["solc"] = str(artifact_path(solc_version).absolute()) - else: - # Respect foundry offline option and don't install a missing solc version - if not platform_config.offline: - install_artifacts([solc_version]) - kwargs["solc"] = str(artifact_path(solc_version).absolute()) + if platform_config.solc_version is None: + message = f"Could not detect solc version from {platform_wd.NAME} config. Falling back to system version..." + LOGGER.warning(message) + else: + kwargs["solc"] = _configure_solc( + platform_config.solc_version, platform_config.offline + ) if platform_config.optimizer: kwargs["solc_args"] += "--optimize" if platform_config.optimizer_runs: diff --git a/crytic_compile/platform/foundry.py b/crytic_compile/platform/foundry.py index d46494d5..47c3a890 100755 --- a/crytic_compile/platform/foundry.py +++ b/crytic_compile/platform/foundry.py @@ -5,7 +5,8 @@ import os import subprocess from pathlib import Path -from typing import TYPE_CHECKING, List, Optional +from typing import TYPE_CHECKING, List, Optional, Dict, TypeVar + import toml from crytic_compile.platform.abstract_platform import AbstractPlatform, PlatformConfig @@ -17,6 +18,8 @@ if TYPE_CHECKING: from crytic_compile import CryticCompile +T = TypeVar("T") + LOGGER = logging.getLogger("CryticCompile") @@ -135,8 +138,21 @@ def config(working_dir: str) -> Optional[PlatformConfig]: foundry_toml = toml.loads(f.read()) default_profile = foundry_toml["profile"]["default"] - if "solc_version" in default_profile: - result.solc_version = default_profile["solc_version"] + def lookup_by_keys(keys: List[str], dictionary: Dict[str, T]) -> Optional[T]: + for key in keys: + if key in dictionary: + return dictionary[key] + return None + + # Foundry supports snake and kebab case. + result.solc_version = lookup_by_keys( + ["solc", "solc_version", "solc-version"], default_profile + ) + via_ir = lookup_by_keys(["via_ir", "via-ir"], default_profile) + if via_ir: + result.via_ir = via_ir + result.allow_paths = lookup_by_keys(["allow_paths", "allow-paths"], default_profile) + if "offline" in default_profile: result.offline = default_profile["offline"] if "optimizer" in default_profile: @@ -144,17 +160,15 @@ def config(working_dir: str) -> Optional[PlatformConfig]: else: # Default to true result.optimizer = True - if "optimizer_runs" in default_profile: - result.optimizer_runs = default_profile["optimizer_runs"] - else: + optimizer_runs = lookup_by_keys(["optimizer_runs", "optimizer-runs"], default_profile) + if optimizer_runs is None: # Default to 200 result.optimizer_runs = 200 - if "via_ir" in default_profile: - result.via_ir = default_profile["via_ir"] - if "allow_paths" in default_profile: - result.allow_paths = default_profile["allow_paths"] - if "evm_version" in default_profile: - result.evm_version = default_profile["evm_version"] + else: + result.optimizer_runs = optimizer_runs + evm_version = lookup_by_keys(["evm_version", "evm-version"], default_profile) + if evm_version is None: + result.evm_version = evm_version else: # Default to london result.evm_version = "london"