Skip to content

Commit

Permalink
Merge branch 'dev' into dependabot/github_actions/dev/actions/checkout-4
Browse files Browse the repository at this point in the history
  • Loading branch information
montyly authored Oct 12, 2023
2 parents b224c54 + 1401fb4 commit 9402459
Show file tree
Hide file tree
Showing 18 changed files with 570 additions and 153 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
fail-fast: false
matrix:
os: ["ubuntu-latest", "windows-2022"]
type: ["brownie", "buidler", "dapp", "embark", "hardhat", "solc", "truffle", "waffle", "foundry", "standard"]
type: ["brownie", "buidler", "dapp", "embark", "hardhat", "solc", "truffle", "waffle", "foundry", "standard", "vyper"]
exclude:
# Currently broken, tries to pull git:// which is blocked by GH
- type: embark
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
uses: pypa/[email protected]

- name: sign
uses: sigstore/gh-action-sigstore-python@v2.0.1
uses: sigstore/gh-action-sigstore-python@v2.1.0
with:
inputs: ./dist/*.tar.gz ./dist/*.whl
release-signing-artifacts: true
Expand Down
97 changes: 77 additions & 20 deletions crytic_compile/crytic_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,16 @@
from pathlib import Path
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type, Union

from solc_select.solc_select import (
install_artifacts,
installed_versions,
current_version,
artifact_path,
)
from crytic_compile.compilation_unit import CompilationUnit
from crytic_compile.platform import all_platforms, solc_standard_json
from crytic_compile.platform import all_platforms
from crytic_compile.platform.solc_standard_json import SolcStandardJson
from crytic_compile.platform.vyper import VyperStandardJson
from crytic_compile.platform.abstract_platform import AbstractPlatform
from crytic_compile.platform.all_export import PLATFORMS_EXPORT
from crytic_compile.platform.solc import Solc
Expand Down Expand Up @@ -84,6 +92,7 @@ class CryticCompile:
Main class.
"""

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

self._working_dir = Path.cwd()

# pylint: disable=too-many-nested-blocks
if isinstance(target, str):
platform = self._init_platform(target, **kwargs)
# If the platform is Solc it means we are trying to compile a single
# we try to see if we are in a known compilation framework to retrieve
# information like remappings and solc version
if isinstance(platform, Solc):
# Try to get the platform of the current working directory
platform_wd = next(
(
p(target)
for p in get_platforms()
if p.is_supported(str(self._working_dir), **kwargs)
),
None,
)
# If no platform has been found or if it's a Solc we can't do anything
if platform_wd and not isinstance(platform_wd, Solc):
platform_config = platform_wd.config(str(self._working_dir))
if platform_config:
kwargs["solc_args"] = ""
kwargs["solc_remaps"] = ""

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.optimizer:
kwargs["solc_args"] += "--optimize"
if platform_config.optimizer_runs:
kwargs[
"solc_args"
] += f"--optimize-runs {platform_config.optimizer_runs}"
if platform_config.via_ir:
kwargs["solc_args"] += "--via-ir"
if platform_config.allow_paths:
kwargs["solc_args"] += f"--allow-paths {platform_config.allow_paths}"
if platform_config.evm_version:
kwargs["solc_args"] += f"--evm-version {platform_config.evm_version}"
else:
platform = target

Expand Down Expand Up @@ -622,18 +678,14 @@ def compile_all(target: str, **kwargs: str) -> List[CryticCompile]:
**kwargs: optional arguments. Used: "solc_standard_json"
Raises:
ValueError: If the target could not be compiled
NotImplementedError: If the target could not be compiled
Returns:
List[CryticCompile]: Returns a list of CryticCompile instances for all compilations which occurred.
"""
use_solc_standard_json = kwargs.get("solc_standard_json", False)

# Attempt to perform glob expansion of target/filename
globbed_targets = glob.glob(target, recursive=True)

# Check if the target refers to a valid target already.
# If it does not, we assume it's a glob pattern.
compilations: List[CryticCompile] = []
if os.path.isfile(target) or is_supported(target):
if target.endswith(".zip"):
Expand All @@ -645,28 +697,33 @@ def compile_all(target: str, **kwargs: str) -> List[CryticCompile]:
compilations = load_from_zip(tmp.name)
else:
compilations.append(CryticCompile(target, **kwargs))
elif os.path.isdir(target) or len(globbed_targets) > 0:
# We create a new glob to find solidity files at this path (in case this is a directory)
filenames = glob.glob(os.path.join(target, "*.sol"))
if not filenames:
filenames = glob.glob(os.path.join(target, "*.vy"))
if not filenames:
filenames = globbed_targets

elif os.path.isdir(target):
solidity_filenames = glob.glob(os.path.join(target, "*.sol"))
vyper_filenames = glob.glob(os.path.join(target, "*.vy"))
# Determine if we're using --standard-solc option to
# aggregate many files into a single compilation.
if use_solc_standard_json:
# If we're using standard solc, then we generated our
# input to create a single compilation with all files
standard_json = solc_standard_json.SolcStandardJson()
for filename in filenames:
standard_json.add_source_file(filename)
compilations.append(CryticCompile(standard_json, **kwargs))
solc_standard_json = SolcStandardJson()
solc_standard_json.add_source_files(solidity_filenames)
compilations.append(CryticCompile(solc_standard_json, **kwargs))
else:
# We compile each file and add it to our compilations.
for filename in filenames:
for filename in solidity_filenames:
compilations.append(CryticCompile(filename, **kwargs))

if vyper_filenames:
vyper_standard_json = VyperStandardJson()
vyper_standard_json.add_source_files(vyper_filenames)
compilations.append(CryticCompile(vyper_standard_json, **kwargs))
else:
raise ValueError(f"{str(target)} is not a file or directory.")
raise NotImplementedError()
# TODO split glob into language
# # Attempt to perform glob expansion of target/filename
# globbed_targets = glob.glob(target, recursive=True)
# print(globbed_targets)

# raise ValueError(f"{str(target)} is not a file or directory.")

return compilations
32 changes: 32 additions & 0 deletions crytic_compile/cryticparser/cryticparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,30 @@ def _init_etherscan(parser: ArgumentParser) -> None:
default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"],
)

group_etherscan.add_argument(
"--base-apikey",
help="Basescan API key.",
action="store",
dest="base_api_key",
default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"],
)

group_etherscan.add_argument(
"--gno-apikey",
help="Gnosisscan API key.",
action="store",
dest="gno_api_key",
default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"],
)

group_etherscan.add_argument(
"--polyzk-apikey",
help="zkEVM Polygonscan API key.",
action="store",
dest="polyzk_api_key",
default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"],
)

group_etherscan.add_argument(
"--etherscan-export-directory",
help="Directory in which to save the analyzed contracts.",
Expand Down Expand Up @@ -496,3 +520,11 @@ def _init_foundry(parser: ArgumentParser) -> None:
dest="foundry_out_directory",
default=DEFAULTS_FLAG_IN_CONFIG["foundry_out_directory"],
)

group_foundry.add_argument(
"--foundry-compile-all",
help="Don't skip compiling test and script",
action="store_true",
dest="foundry_compile_all",
default=DEFAULTS_FLAG_IN_CONFIG["foundry_compile_all"],
)
1 change: 1 addition & 0 deletions crytic_compile/cryticparser/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"hardhat_artifacts_directory": None,
"foundry_ignore_compile": False,
"foundry_out_directory": "out",
"foundry_compile_all": False,
"export_dir": "crytic-export",
"compile_libraries": None,
}
36 changes: 35 additions & 1 deletion crytic_compile/platform/abstract_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
This gives the skeleton for any platform supported by crytic-compile
"""
import abc
from typing import TYPE_CHECKING, List, Dict
from typing import TYPE_CHECKING, List, Dict, Optional
from dataclasses import dataclass, field

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


# pylint: disable=too-many-instance-attributes
@dataclass
class PlatformConfig:
"""
This class represents a generic platform configuration
"""

offline: bool = False
remappings: Optional[str] = None
solc_version: Optional[str] = None
optimizer: bool = False
optimizer_runs: Optional[int] = None
via_ir: bool = False
allow_paths: Optional[str] = None
evm_version: Optional[str] = None
src_path: str = "src"
tests_path: str = "test"
libs_path: List[str] = field(default_factory=lambda: ["lib"])
scripts_path: str = "script"


class AbstractPlatform(metaclass=abc.ABCMeta):
"""
This is the abstract class for the platform
Expand Down Expand Up @@ -154,6 +176,18 @@ def is_dependency(self, path: str) -> bool:
"""
return False

@staticmethod
def config(working_dir: str) -> Optional[PlatformConfig]: # pylint: disable=unused-argument
"""Return configuration data that should be passed to solc, such as version, remappings ecc.
Args:
working_dir (str): path to the target
Returns:
Optional[PlatformConfig]: Platform configuration data such as optimization, remappings...
"""
return None

# Only _guessed_tests is an abstract method
# guessed_tests will call the generic guess_tests and appends to the list
# platforms-dependent tests
Expand Down
2 changes: 1 addition & 1 deletion crytic_compile/platform/all_platforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@
from .solc_standard_json import SolcStandardJson
from .standard import Standard
from .truffle import Truffle
from .vyper import Vyper
from .vyper import VyperStandardJson
from .waffle import Waffle
from .foundry import Foundry
4 changes: 2 additions & 2 deletions crytic_compile/platform/buidler.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
from pathlib import Path
from typing import TYPE_CHECKING, List, Tuple

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, extract_name
from crytic_compile.utils.natspec import Natspec
from crytic_compile.compilation_unit import CompilationUnit
from .abstract_platform import AbstractPlatform

# Handle cycle
from .solc import relative_to_short
Expand Down
2 changes: 1 addition & 1 deletion crytic_compile/platform/dapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
from crytic_compile.platform.abstract_platform import AbstractPlatform
from crytic_compile.platform.types import Type
from crytic_compile.utils.naming import convert_filename, extract_name
from crytic_compile.utils.subprocess import run

# Handle cycle
from crytic_compile.utils.natspec import Natspec
from crytic_compile.utils.subprocess import run

if TYPE_CHECKING:
from crytic_compile import CryticCompile
Expand Down
19 changes: 19 additions & 0 deletions crytic_compile/platform/etherscan.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
"testnet.avax:": ("-testnet.snowtrace.io", "testnet.snowtrace.io"),
"ftm:": (".ftmscan.com", "ftmscan.com"),
"goerli.base:": ("-goerli.basescan.org", "goerli.basescan.org"),
"base:": (".basescan.org", "basescan.org"),
"gno:": (".gnosisscan.io", "gnosisscan.io"),
"polyzk:": ("-zkevm.polygonscan.com", "zkevm.polygonscan.com"),
}


Expand Down Expand Up @@ -237,6 +240,9 @@ def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None:
ftmscan_api_key = kwargs.get("ftmscan_api_key", None)
bscan_api_key = kwargs.get("bscan_api_key", None)
optim_api_key = kwargs.get("optim_api_key", None)
base_api_key = kwargs.get("base_api_key", None)
gno_api_key = kwargs.get("gno_api_key", None)
polyzk_api_key = kwargs.get("polyzk_api_key", None)

export_dir = kwargs.get("export_dir", "crytic-export")
export_dir = os.path.join(
Expand Down Expand Up @@ -267,6 +273,15 @@ def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None:
if optim_api_key and "optim" in etherscan_url:
etherscan_url += f"&apikey={optim_api_key}"
etherscan_bytecode_url += f"&apikey={optim_api_key}"
if base_api_key and "base" in etherscan_url:
etherscan_url += f"&apikey={base_api_key}"
etherscan_bytecode_url += f"&apikey={base_api_key}"
if gno_api_key and "gno" in etherscan_url:
etherscan_url += f"&apikey={gno_api_key}"
etherscan_bytecode_url += f"&apikey={gno_api_key}"
if polyzk_api_key and "zkevm" in etherscan_url:
etherscan_url += f"&apikey={polyzk_api_key}"
etherscan_bytecode_url += f"&apikey={polyzk_api_key}"

source_code: str = ""
result: Dict[str, Union[bool, str, int]] = {}
Expand Down Expand Up @@ -299,6 +314,10 @@ def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None:
LOGGER.error("Incorrect etherscan request")
raise InvalidCompilation("Incorrect etherscan request " + etherscan_url)

if not info["message"].startswith("OK") and "Invalid API Key" in info["result"]:
LOGGER.error("Invalid etherscan API Key")
raise InvalidCompilation("Invalid etherscan API Key: " + etherscan_url)

if not info["message"].startswith("OK"):
LOGGER.error("Contract has no public source code")
raise InvalidCompilation("Contract has no public source code: " + etherscan_url)
Expand Down
Loading

0 comments on commit 9402459

Please sign in to comment.