Source code for crytic_compile.platform.dapp

"""
Dapp platform. https://github.com/dapphub/dapptools
"""

import glob
import json
import logging
import os
import re
import subprocess
from pathlib import Path

# Cycle dependency
from typing import TYPE_CHECKING, List

from crytic_compile.compiler.compiler import CompilerVersion
from crytic_compile.platform.abstract_platform import AbstractPlatform
from crytic_compile.platform.types import Type
from crytic_compile.utils.naming import extract_filename, extract_name, convert_filename

# Handle cycle
from crytic_compile.utils.natspec import Natspec

if TYPE_CHECKING:
    from crytic_compile import CryticCompile, InvalidCompilation

LOGGER = logging.getLogger("CryticCompile")


[docs]class Dapp(AbstractPlatform): """ Dapp class """ NAME = "Dapp" PROJECT_URL = "https://github.com/dapphub/dapptools" TYPE = Type.DAPP
[docs] def compile(self, crytic_compile: "CryticCompile", **kwargs: str): """ Compile the target :param crytic_compile: :param target: :param kwargs: :return: """ dapp_ignore_compile = kwargs.get("dapp_ignore_compile", False) or kwargs.get( "ignore_compile", False ) directory = os.path.join(self._target, "out") if not dapp_ignore_compile: _run_dapp(self._target) crytic_compile.compiler_version = _get_version(self._target) files = glob.glob(directory + "/**/*.sol.json", recursive=True) for file in files: with open(file, encoding="utf8") as file_desc: targets_json = json.load(file_desc) if not "contracts" in targets_json: continue for original_contract_name, info in targets_json["contracts"].items(): contract_name = extract_name(original_contract_name) contract_filename = extract_filename(original_contract_name) contract_filename = convert_filename( contract_filename, _relative_to_short, crytic_compile, working_dir=self._target ) crytic_compile.contracts_names.add(contract_name) crytic_compile.contracts_filenames[contract_name] = contract_filename crytic_compile.abis[contract_name] = json.loads(info["abi"]) crytic_compile.bytecodes_init[contract_name] = info["bin"] crytic_compile.bytecodes_runtime[contract_name] = info["bin-runtime"] crytic_compile.srcmaps_init[contract_name] = info["srcmap"].split(";") crytic_compile.srcmaps_runtime[contract_name] = info["srcmap-runtime"].split(";") userdoc = json.loads(info.get("userdoc", "{}")) devdoc = json.loads(info.get("devdoc", "{}")) natspec = Natspec(userdoc, devdoc) crytic_compile.natspec[contract_name] = natspec for path, info in targets_json["sources"].items(): path = convert_filename( path, _relative_to_short, crytic_compile, working_dir=self._target ) crytic_compile.filenames.add(path) crytic_compile.asts[path.absolute] = info["AST"]
[docs] @staticmethod def is_supported(target: str, **kwargs: str) -> bool: """ Heuristic used: check if "dapp build" is present in Makefile :param target: :return: """ dapp_ignore = kwargs.get("dapp_ignore", False) if dapp_ignore: return False makefile = os.path.join(target, "Makefile") if os.path.isfile(makefile): with open(makefile, encoding="utf8") as file_desc: txt = file_desc.read() return "dapp build" in txt return False
[docs] def is_dependency(self, path: str) -> bool: """ Check if the path is a dependency :param path: :return: """ return "lib" in Path(path).parts
def _guessed_tests(self) -> List[str]: """ Guess the potential unit tests commands :return: """ return ["dapp test"]
def _run_dapp(target: str): """ Run Dapp :param target: :return: """ cmd = ["dapp", "build"] try: process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=target) except OSError as error: raise InvalidCompilation(error) _, _ = process.communicate() def _get_version(target: str) -> CompilerVersion: """ Get the compiler version used :param target: :return: """ files = glob.glob(target + "/**/*meta.json", recursive=True) version = None optimized = None compiler = "solc" for file in files: if version is None: with open(file, encoding="utf8") as file_desc: config = json.load(file_desc) if "compiler" in config: if "version" in config["compiler"]: version = re.findall(r"\d+\.\d+\.\d+", config["compiler"]["version"]) assert version if "settings" in config: if "optimizer" in config["settings"]: if "enabled" in config["settings"]["optimizer"]: optimized = config["settings"]["optimizer"]["enabled"] return CompilerVersion(compiler=compiler, version=version, optimized=optimized) def _relative_to_short(relative: Path) -> Path: """ Translate relative path to short :param relative: :return: """ short = relative try: short = short.relative_to(Path("src")) except ValueError: try: short = short.relative_to("lib") except ValueError: pass return short