crytic_compile.platform.etherlime

  1"""
  2Etherlime platform. https://github.com/LimeChain/etherlime
  3"""
  4
  5import glob
  6import json
  7import logging
  8import os
  9import re
 10import shutil
 11import subprocess
 12from pathlib import Path
 13from typing import TYPE_CHECKING, List, Optional, Any
 14
 15from crytic_compile.compilation_unit import CompilationUnit
 16from crytic_compile.compiler.compiler import CompilerVersion
 17from crytic_compile.platform.abstract_platform import AbstractPlatform
 18from crytic_compile.platform.exceptions import InvalidCompilation
 19from crytic_compile.platform.types import Type
 20from crytic_compile.utils.naming import convert_filename
 21
 22# Cycle dependency
 23from crytic_compile.utils.natspec import Natspec
 24
 25if TYPE_CHECKING:
 26    from crytic_compile import CryticCompile
 27
 28LOGGER = logging.getLogger("CryticCompile")
 29
 30
 31def _run_etherlime(target: str, npx_disable: bool, compile_arguments: Optional[str]) -> None:
 32    """Run etherlime
 33
 34    Args:
 35        target (str): path to the target
 36        npx_disable (bool): true if npx should not be used
 37        compile_arguments (Optional[str]): additional arguments
 38
 39    Raises:
 40        InvalidCompilation: if etherlime fails
 41    """
 42    cmd = ["etherlime", "compile", target, "deleteCompiledFiles=true"]
 43
 44    if not npx_disable:
 45        cmd = ["npx"] + cmd
 46
 47    if compile_arguments:
 48        cmd += compile_arguments.split(" ")
 49
 50    try:
 51        with subprocess.Popen(
 52            cmd,
 53            stdout=subprocess.PIPE,
 54            stderr=subprocess.PIPE,
 55            cwd=target,
 56            executable=shutil.which(cmd[0]),
 57        ) as process:
 58            stdout_bytes, stderr_bytes = process.communicate()
 59            stdout, stderr = (
 60                stdout_bytes.decode(errors="backslashreplace"),
 61                stderr_bytes.decode(errors="backslashreplace"),
 62            )  # convert bytestrings to unicode strings
 63
 64            LOGGER.info(stdout)
 65
 66            if stderr:
 67                LOGGER.error(stderr)
 68    except OSError as error:
 69        # pylint: disable=raise-missing-from
 70        raise InvalidCompilation(error)
 71
 72
 73class Etherlime(AbstractPlatform):
 74    """
 75    Etherlime platform
 76    """
 77
 78    NAME = "Etherlime"
 79    PROJECT_URL = "https://github.com/LimeChain/etherlime"
 80    TYPE = Type.ETHERLIME
 81
 82    # pylint: disable=too-many-locals
 83    def compile(self, crytic_compile: "CryticCompile", **kwargs: Any) -> None:
 84        """Run the compilation
 85
 86        Args:
 87            crytic_compile (CryticCompile): Associated CryticCompile object
 88            **kwargs: optional arguments. Used "etherlime_ignore_compile", "ignore_compile"
 89
 90        Raises:
 91            InvalidCompilation: if etherlime failed to run
 92        """
 93
 94        etherlime_ignore_compile = kwargs.get("etherlime_ignore_compile", False) or kwargs.get(
 95            "ignore_compile", False
 96        )
 97
 98        build_directory = "build"
 99        compile_arguments: Optional[str] = kwargs.get("etherlime_compile_arguments", None)
100        npx_disable: bool = kwargs.get("npx_disable", False)
101
102        if not etherlime_ignore_compile:
103            _run_etherlime(self._target, npx_disable, compile_arguments)
104
105        # similar to truffle
106        if not os.path.isdir(os.path.join(self._target, build_directory)):
107            raise InvalidCompilation(
108                "No truffle build directory found, did you run `truffle compile`?"
109            )
110        filenames = glob.glob(os.path.join(self._target, build_directory, "*.json"))
111
112        version = None
113        compiler = "solc-js"
114
115        compilation_unit = CompilationUnit(crytic_compile, str(self._target))
116
117        for file in filenames:
118            with open(file, encoding="utf8") as file_desc:
119                target_loaded = json.load(file_desc)
120
121                if version is None:
122                    if "compiler" in target_loaded:
123                        if "version" in target_loaded["compiler"]:
124                            version = re.findall(
125                                r"\d+\.\d+\.\d+", target_loaded["compiler"]["version"]
126                            )[0]
127
128                if "ast" not in target_loaded:
129                    continue
130
131                filename_txt = target_loaded["ast"]["absolutePath"]
132                filename = convert_filename(filename_txt, _relative_to_short, crytic_compile)
133
134                source_unit = compilation_unit.create_source_unit(filename)
135
136                source_unit.ast = target_loaded["ast"]
137                contract_name = target_loaded["contractName"]
138
139                compilation_unit.filename_to_contracts[filename].add(contract_name)
140                source_unit.add_contract_name(contract_name)
141                source_unit.abis[contract_name] = target_loaded["abi"]
142                source_unit.bytecodes_init[contract_name] = target_loaded["bytecode"].replace(
143                    "0x", ""
144                )
145                source_unit.bytecodes_runtime[contract_name] = target_loaded[
146                    "deployedBytecode"
147                ].replace("0x", "")
148                source_unit.srcmaps_init[contract_name] = target_loaded["sourceMap"].split(";")
149                source_unit.srcmaps_runtime[contract_name] = target_loaded[
150                    "deployedSourceMap"
151                ].split(";")
152
153                userdoc = target_loaded.get("userdoc", {})
154                devdoc = target_loaded.get("devdoc", {})
155                natspec = Natspec(userdoc, devdoc)
156                source_unit.natspec[contract_name] = natspec
157
158        compilation_unit.compiler_version = CompilerVersion(
159            compiler=compiler, version=version, optimized=_is_optimized(compile_arguments)
160        )
161
162    def clean(self, **_kwargs: str) -> None:
163        # TODO: research if there's a way to clean artifacts
164        pass
165
166    @staticmethod
167    def is_supported(target: str, **kwargs: str) -> bool:
168        """Check if the target is an etherlime project
169
170        Args:
171            target (str): path to the target
172            **kwargs: optional arguments. Used "etherlime_ignore"
173
174        Returns:
175            bool: True if the target is a etherlime project
176        """
177        etherlime_ignore = kwargs.get("etherlime_ignore", False)
178        if etherlime_ignore:
179            return False
180        if os.path.isfile(os.path.join(target, "package.json")):
181            with open(os.path.join(target, "package.json"), encoding="utf8") as file_desc:
182                package = json.load(file_desc)
183            if "dependencies" in package:
184                return (
185                    "etherlime-lib" in package["dependencies"]
186                    or "etherlime" in package["dependencies"]
187                )
188            if "devDependencies" in package:
189                return (
190                    "etherlime-lib" in package["devDependencies"]
191                    or "etherlime" in package["devDependencies"]
192                )
193        return False
194
195    def is_dependency(self, path: str) -> bool:
196        """Check if the path is a dependency
197
198        Args:
199            path (str): path to the target
200
201        Returns:
202            bool: True if the target is a dependency
203        """
204        if path in self._cached_dependencies:
205            return self._cached_dependencies[path]
206        ret = "node_modules" in Path(path).parts
207        self._cached_dependencies[path] = ret
208        return ret
209
210    def _guessed_tests(self) -> List[str]:
211        """Guess the potential unit tests commands
212
213        Returns:
214            List[str]: The guessed unit tests commands
215        """
216        return ["etherlime test"]
217
218
219def _is_optimized(compile_arguments: Optional[str]) -> bool:
220    """Check if the optimization is enabled
221
222    Args:
223        compile_arguments (Optional[str]): list of compilation arguments
224
225    Returns:
226        bool: True if the optimizations are enabled
227    """
228    if compile_arguments:
229        return "--run" in compile_arguments
230    return False
231
232
233def _relative_to_short(relative: Path) -> Path:
234    """Translate relative path to short
235
236    Args:
237        relative (Path): path to the target
238
239    Returns:
240        Path: Translated path
241    """
242    short = relative
243    try:
244        short = short.relative_to(Path("contracts"))
245    except ValueError:
246        try:
247            short = short.relative_to("node_modules")
248        except ValueError:
249            pass
250    return short
LOGGER = <Logger CryticCompile (WARNING)>
 74class Etherlime(AbstractPlatform):
 75    """
 76    Etherlime platform
 77    """
 78
 79    NAME = "Etherlime"
 80    PROJECT_URL = "https://github.com/LimeChain/etherlime"
 81    TYPE = Type.ETHERLIME
 82
 83    # pylint: disable=too-many-locals
 84    def compile(self, crytic_compile: "CryticCompile", **kwargs: Any) -> None:
 85        """Run the compilation
 86
 87        Args:
 88            crytic_compile (CryticCompile): Associated CryticCompile object
 89            **kwargs: optional arguments. Used "etherlime_ignore_compile", "ignore_compile"
 90
 91        Raises:
 92            InvalidCompilation: if etherlime failed to run
 93        """
 94
 95        etherlime_ignore_compile = kwargs.get("etherlime_ignore_compile", False) or kwargs.get(
 96            "ignore_compile", False
 97        )
 98
 99        build_directory = "build"
100        compile_arguments: Optional[str] = kwargs.get("etherlime_compile_arguments", None)
101        npx_disable: bool = kwargs.get("npx_disable", False)
102
103        if not etherlime_ignore_compile:
104            _run_etherlime(self._target, npx_disable, compile_arguments)
105
106        # similar to truffle
107        if not os.path.isdir(os.path.join(self._target, build_directory)):
108            raise InvalidCompilation(
109                "No truffle build directory found, did you run `truffle compile`?"
110            )
111        filenames = glob.glob(os.path.join(self._target, build_directory, "*.json"))
112
113        version = None
114        compiler = "solc-js"
115
116        compilation_unit = CompilationUnit(crytic_compile, str(self._target))
117
118        for file in filenames:
119            with open(file, encoding="utf8") as file_desc:
120                target_loaded = json.load(file_desc)
121
122                if version is None:
123                    if "compiler" in target_loaded:
124                        if "version" in target_loaded["compiler"]:
125                            version = re.findall(
126                                r"\d+\.\d+\.\d+", target_loaded["compiler"]["version"]
127                            )[0]
128
129                if "ast" not in target_loaded:
130                    continue
131
132                filename_txt = target_loaded["ast"]["absolutePath"]
133                filename = convert_filename(filename_txt, _relative_to_short, crytic_compile)
134
135                source_unit = compilation_unit.create_source_unit(filename)
136
137                source_unit.ast = target_loaded["ast"]
138                contract_name = target_loaded["contractName"]
139
140                compilation_unit.filename_to_contracts[filename].add(contract_name)
141                source_unit.add_contract_name(contract_name)
142                source_unit.abis[contract_name] = target_loaded["abi"]
143                source_unit.bytecodes_init[contract_name] = target_loaded["bytecode"].replace(
144                    "0x", ""
145                )
146                source_unit.bytecodes_runtime[contract_name] = target_loaded[
147                    "deployedBytecode"
148                ].replace("0x", "")
149                source_unit.srcmaps_init[contract_name] = target_loaded["sourceMap"].split(";")
150                source_unit.srcmaps_runtime[contract_name] = target_loaded[
151                    "deployedSourceMap"
152                ].split(";")
153
154                userdoc = target_loaded.get("userdoc", {})
155                devdoc = target_loaded.get("devdoc", {})
156                natspec = Natspec(userdoc, devdoc)
157                source_unit.natspec[contract_name] = natspec
158
159        compilation_unit.compiler_version = CompilerVersion(
160            compiler=compiler, version=version, optimized=_is_optimized(compile_arguments)
161        )
162
163    def clean(self, **_kwargs: str) -> None:
164        # TODO: research if there's a way to clean artifacts
165        pass
166
167    @staticmethod
168    def is_supported(target: str, **kwargs: str) -> bool:
169        """Check if the target is an etherlime project
170
171        Args:
172            target (str): path to the target
173            **kwargs: optional arguments. Used "etherlime_ignore"
174
175        Returns:
176            bool: True if the target is a etherlime project
177        """
178        etherlime_ignore = kwargs.get("etherlime_ignore", False)
179        if etherlime_ignore:
180            return False
181        if os.path.isfile(os.path.join(target, "package.json")):
182            with open(os.path.join(target, "package.json"), encoding="utf8") as file_desc:
183                package = json.load(file_desc)
184            if "dependencies" in package:
185                return (
186                    "etherlime-lib" in package["dependencies"]
187                    or "etherlime" in package["dependencies"]
188                )
189            if "devDependencies" in package:
190                return (
191                    "etherlime-lib" in package["devDependencies"]
192                    or "etherlime" in package["devDependencies"]
193                )
194        return False
195
196    def is_dependency(self, path: str) -> bool:
197        """Check if the path is a dependency
198
199        Args:
200            path (str): path to the target
201
202        Returns:
203            bool: True if the target is a dependency
204        """
205        if path in self._cached_dependencies:
206            return self._cached_dependencies[path]
207        ret = "node_modules" in Path(path).parts
208        self._cached_dependencies[path] = ret
209        return ret
210
211    def _guessed_tests(self) -> List[str]:
212        """Guess the potential unit tests commands
213
214        Returns:
215            List[str]: The guessed unit tests commands
216        """
217        return ["etherlime test"]

Etherlime platform

NAME: str = 'Etherlime'
PROJECT_URL: str = 'https://github.com/LimeChain/etherlime'
TYPE: crytic_compile.platform.types.Type = <Type.ETHERLIME: 5>
def compile( self, crytic_compile: crytic_compile.crytic_compile.CryticCompile, **kwargs: Any) -> None:
 84    def compile(self, crytic_compile: "CryticCompile", **kwargs: Any) -> None:
 85        """Run the compilation
 86
 87        Args:
 88            crytic_compile (CryticCompile): Associated CryticCompile object
 89            **kwargs: optional arguments. Used "etherlime_ignore_compile", "ignore_compile"
 90
 91        Raises:
 92            InvalidCompilation: if etherlime failed to run
 93        """
 94
 95        etherlime_ignore_compile = kwargs.get("etherlime_ignore_compile", False) or kwargs.get(
 96            "ignore_compile", False
 97        )
 98
 99        build_directory = "build"
100        compile_arguments: Optional[str] = kwargs.get("etherlime_compile_arguments", None)
101        npx_disable: bool = kwargs.get("npx_disable", False)
102
103        if not etherlime_ignore_compile:
104            _run_etherlime(self._target, npx_disable, compile_arguments)
105
106        # similar to truffle
107        if not os.path.isdir(os.path.join(self._target, build_directory)):
108            raise InvalidCompilation(
109                "No truffle build directory found, did you run `truffle compile`?"
110            )
111        filenames = glob.glob(os.path.join(self._target, build_directory, "*.json"))
112
113        version = None
114        compiler = "solc-js"
115
116        compilation_unit = CompilationUnit(crytic_compile, str(self._target))
117
118        for file in filenames:
119            with open(file, encoding="utf8") as file_desc:
120                target_loaded = json.load(file_desc)
121
122                if version is None:
123                    if "compiler" in target_loaded:
124                        if "version" in target_loaded["compiler"]:
125                            version = re.findall(
126                                r"\d+\.\d+\.\d+", target_loaded["compiler"]["version"]
127                            )[0]
128
129                if "ast" not in target_loaded:
130                    continue
131
132                filename_txt = target_loaded["ast"]["absolutePath"]
133                filename = convert_filename(filename_txt, _relative_to_short, crytic_compile)
134
135                source_unit = compilation_unit.create_source_unit(filename)
136
137                source_unit.ast = target_loaded["ast"]
138                contract_name = target_loaded["contractName"]
139
140                compilation_unit.filename_to_contracts[filename].add(contract_name)
141                source_unit.add_contract_name(contract_name)
142                source_unit.abis[contract_name] = target_loaded["abi"]
143                source_unit.bytecodes_init[contract_name] = target_loaded["bytecode"].replace(
144                    "0x", ""
145                )
146                source_unit.bytecodes_runtime[contract_name] = target_loaded[
147                    "deployedBytecode"
148                ].replace("0x", "")
149                source_unit.srcmaps_init[contract_name] = target_loaded["sourceMap"].split(";")
150                source_unit.srcmaps_runtime[contract_name] = target_loaded[
151                    "deployedSourceMap"
152                ].split(";")
153
154                userdoc = target_loaded.get("userdoc", {})
155                devdoc = target_loaded.get("devdoc", {})
156                natspec = Natspec(userdoc, devdoc)
157                source_unit.natspec[contract_name] = natspec
158
159        compilation_unit.compiler_version = CompilerVersion(
160            compiler=compiler, version=version, optimized=_is_optimized(compile_arguments)
161        )

Run the compilation

Args: crytic_compile (CryticCompile): Associated CryticCompile object **kwargs: optional arguments. Used "etherlime_ignore_compile", "ignore_compile"

Raises: InvalidCompilation: if etherlime failed to run

def clean(self, **_kwargs: str) -> None:
163    def clean(self, **_kwargs: str) -> None:
164        # TODO: research if there's a way to clean artifacts
165        pass

Clean compilation artifacts

Args: **kwargs: optional arguments.

@staticmethod
def is_supported(target: str, **kwargs: str) -> bool:
167    @staticmethod
168    def is_supported(target: str, **kwargs: str) -> bool:
169        """Check if the target is an etherlime project
170
171        Args:
172            target (str): path to the target
173            **kwargs: optional arguments. Used "etherlime_ignore"
174
175        Returns:
176            bool: True if the target is a etherlime project
177        """
178        etherlime_ignore = kwargs.get("etherlime_ignore", False)
179        if etherlime_ignore:
180            return False
181        if os.path.isfile(os.path.join(target, "package.json")):
182            with open(os.path.join(target, "package.json"), encoding="utf8") as file_desc:
183                package = json.load(file_desc)
184            if "dependencies" in package:
185                return (
186                    "etherlime-lib" in package["dependencies"]
187                    or "etherlime" in package["dependencies"]
188                )
189            if "devDependencies" in package:
190                return (
191                    "etherlime-lib" in package["devDependencies"]
192                    or "etherlime" in package["devDependencies"]
193                )
194        return False

Check if the target is an etherlime project

Args: target (str): path to the target **kwargs: optional arguments. Used "etherlime_ignore"

Returns: bool: True if the target is a etherlime project

def is_dependency(self, path: str) -> bool:
196    def is_dependency(self, path: str) -> bool:
197        """Check if the path is a dependency
198
199        Args:
200            path (str): path to the target
201
202        Returns:
203            bool: True if the target is a dependency
204        """
205        if path in self._cached_dependencies:
206            return self._cached_dependencies[path]
207        ret = "node_modules" in Path(path).parts
208        self._cached_dependencies[path] = ret
209        return ret

Check if the path is a dependency

Args: path (str): path to the target

Returns: bool: True if the target is a dependency