crytic_compile.platform.vyper

Vyper platform

  1"""
  2Vyper platform
  3"""
  4import json
  5import logging
  6import os
  7import shutil
  8import subprocess
  9from pathlib import Path
 10from typing import TYPE_CHECKING, Dict, List, Optional
 11
 12from crytic_compile.compilation_unit import CompilationUnit
 13from crytic_compile.compiler.compiler import CompilerVersion
 14from crytic_compile.platform.abstract_platform import AbstractPlatform
 15from crytic_compile.platform.exceptions import InvalidCompilation
 16from crytic_compile.platform.types import Type
 17from crytic_compile.utils.naming import convert_filename
 18
 19# Handle cycle
 20from crytic_compile.utils.natspec import Natspec
 21
 22if TYPE_CHECKING:
 23    from crytic_compile import CryticCompile
 24
 25LOGGER = logging.getLogger("CryticCompile")
 26
 27
 28class VyperStandardJson(AbstractPlatform):
 29    """
 30    Vyper platform
 31    """
 32
 33    NAME = "vyper"
 34    PROJECT_URL = "https://github.com/vyperlang/vyper"
 35    TYPE = Type.VYPER
 36
 37    def __init__(self, target: Optional[Path] = None, **_kwargs: str):
 38        super().__init__(str(target), **_kwargs)
 39        self.standard_json_input = {
 40            "language": "Vyper",
 41            "sources": {},
 42            "settings": {
 43                "outputSelection": {
 44                    "*": {
 45                        "*": [
 46                            "abi",
 47                            "devdoc",
 48                            "userdoc",
 49                            "evm.bytecode",
 50                            "evm.deployedBytecode",
 51                            "evm.deployedBytecode.sourceMap",
 52                        ],
 53                        "": ["ast"],
 54                    }
 55                }
 56            },
 57        }
 58
 59    def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None:
 60        """Compile the target
 61
 62        Args:
 63            crytic_compile (CryticCompile): CryticCompile object to populate
 64            **kwargs: optional arguments. Used "vyper"
 65
 66
 67        """
 68        target = self._target
 69        # If the target was a directory `add_source_file` should have been called
 70        # by `compile_all`. Otherwise, we should have a single file target.
 71        if self._target is not None and os.path.isfile(self._target):
 72            self.add_source_files([target])
 73
 74        vyper_bin = kwargs.get("vyper", "vyper")
 75
 76        compilation_artifacts = _run_vyper_standard_json(self.standard_json_input, vyper_bin)
 77        compilation_unit = CompilationUnit(crytic_compile, str(target))
 78
 79        compiler_version = compilation_artifacts["compiler"].split("-")[1]
 80        if compiler_version != "0.3.7":
 81            LOGGER.info("Vyper != 0.3.7 support is a best effort and might fail")
 82        compilation_unit.compiler_version = CompilerVersion(
 83            compiler="vyper", version=compiler_version, optimized=False
 84        )
 85
 86        for source_file, contract_info in compilation_artifacts["contracts"].items():
 87            filename = convert_filename(source_file, _relative_to_short, crytic_compile)
 88            source_unit = compilation_unit.create_source_unit(filename)
 89            for contract_name, contract_metadata in contract_info.items():
 90                source_unit.add_contract_name(contract_name)
 91                compilation_unit.filename_to_contracts[filename].add(contract_name)
 92
 93                source_unit.abis[contract_name] = contract_metadata["abi"]
 94                source_unit.bytecodes_init[contract_name] = contract_metadata["evm"]["bytecode"][
 95                    "object"
 96                ].replace("0x", "")
 97                # Vyper does not provide the source mapping for the init bytecode
 98                source_unit.srcmaps_init[contract_name] = []
 99                source_unit.srcmaps_runtime[contract_name] = contract_metadata["evm"][
100                    "deployedBytecode"
101                ]["sourceMap"].split(";")
102                source_unit.bytecodes_runtime[contract_name] = contract_metadata["evm"][
103                    "deployedBytecode"
104                ]["object"].replace("0x", "")
105                source_unit.natspec[contract_name] = Natspec(
106                    contract_metadata["userdoc"], contract_metadata["devdoc"]
107                )
108
109        for source_file, ast in compilation_artifacts["sources"].items():
110            filename = convert_filename(source_file, _relative_to_short, crytic_compile)
111            source_unit = compilation_unit.create_source_unit(filename)
112            source_unit.ast = ast
113
114    def add_source_files(self, file_paths: List[str]) -> None:
115        """
116        Append files
117
118        Args:
119            file_paths (List[str]): files to append
120
121        Returns:
122
123        """
124
125        for file_path in file_paths:
126            with open(file_path, "r", encoding="utf8") as f:
127                self.standard_json_input["sources"][file_path] = {  # type: ignore
128                    "content": f.read(),
129                }
130
131    def clean(self, **_kwargs: str) -> None:
132        """Clean compilation artifacts
133
134        Args:
135            **_kwargs: unused.
136        """
137        return
138
139    def is_dependency(self, _path: str) -> bool:
140        """Check if the path is a dependency (not supported for vyper)
141
142        Args:
143            _path (str): path to the target
144
145        Returns:
146            bool: True if the target is a dependency
147        """
148        return False
149
150    @staticmethod
151    def is_supported(target: str, **kwargs: str) -> bool:
152        """Check if the target is a vyper project
153
154        Args:
155            target (str): path to the target
156            **kwargs: optional arguments. Used "vyper_ignore"
157
158        Returns:
159            bool: True if the target is a vyper project
160        """
161        vyper_ignore = kwargs.get("vyper_ignore", False)
162        if vyper_ignore:
163            return False
164        return os.path.isfile(target) and target.endswith(".vy")
165
166    def _guessed_tests(self) -> List[str]:
167        """Guess the potential unit tests commands
168
169        Returns:
170            List[str]: The guessed unit tests commands
171        """
172        return []
173
174
175def _run_vyper_standard_json(
176    standard_json_input: Dict, vyper: str, env: Optional[Dict] = None
177) -> Dict:
178    """Run vyper and write compilation output to a file
179
180    Args:
181        standard_json_input (Dict): Dict containing the vyper standard json input
182        vyper (str): vyper binary
183        env (Optional[Dict], optional): Environment variables. Defaults to None.
184
185    Raises:
186        InvalidCompilation: If vyper failed to run
187
188    Returns:
189        Dict: Vyper json compilation artifact
190    """
191    cmd = [vyper, "--standard-json"]
192
193    with subprocess.Popen(
194        cmd,
195        stdin=subprocess.PIPE,
196        stdout=subprocess.PIPE,
197        stderr=subprocess.PIPE,
198        env=env,
199        executable=shutil.which(cmd[0]),
200    ) as process:
201
202        stdout_b, stderr_b = process.communicate(json.dumps(standard_json_input).encode("utf-8"))
203        stdout, _stderr = (
204            stdout_b.decode(),
205            stderr_b.decode(errors="backslashreplace"),
206        )  # convert bytestrings to unicode strings
207
208        vyper_standard_output = json.loads(stdout)
209
210        if "errors" in vyper_standard_output:
211
212            has_errors = False
213            for diagnostic in vyper_standard_output["errors"]:
214
215                if diagnostic["severity"] == "warning":
216                    continue
217
218                msg = diagnostic.get("formattedMessage", diagnostic["message"])
219                LOGGER.error(msg)
220                has_errors = True
221
222            if has_errors:
223                raise InvalidCompilation("Vyper compilation errored")
224
225        return vyper_standard_output
226
227
228def _relative_to_short(relative: Path) -> Path:
229    """Translate relative path to short (do nothing for vyper)
230
231    Args:
232        relative (Path): path to the target
233
234    Returns:
235        Path: Translated path
236    """
237    return relative
LOGGER = <Logger CryticCompile (WARNING)>
class VyperStandardJson(crytic_compile.platform.abstract_platform.AbstractPlatform):
 29class VyperStandardJson(AbstractPlatform):
 30    """
 31    Vyper platform
 32    """
 33
 34    NAME = "vyper"
 35    PROJECT_URL = "https://github.com/vyperlang/vyper"
 36    TYPE = Type.VYPER
 37
 38    def __init__(self, target: Optional[Path] = None, **_kwargs: str):
 39        super().__init__(str(target), **_kwargs)
 40        self.standard_json_input = {
 41            "language": "Vyper",
 42            "sources": {},
 43            "settings": {
 44                "outputSelection": {
 45                    "*": {
 46                        "*": [
 47                            "abi",
 48                            "devdoc",
 49                            "userdoc",
 50                            "evm.bytecode",
 51                            "evm.deployedBytecode",
 52                            "evm.deployedBytecode.sourceMap",
 53                        ],
 54                        "": ["ast"],
 55                    }
 56                }
 57            },
 58        }
 59
 60    def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None:
 61        """Compile the target
 62
 63        Args:
 64            crytic_compile (CryticCompile): CryticCompile object to populate
 65            **kwargs: optional arguments. Used "vyper"
 66
 67
 68        """
 69        target = self._target
 70        # If the target was a directory `add_source_file` should have been called
 71        # by `compile_all`. Otherwise, we should have a single file target.
 72        if self._target is not None and os.path.isfile(self._target):
 73            self.add_source_files([target])
 74
 75        vyper_bin = kwargs.get("vyper", "vyper")
 76
 77        compilation_artifacts = _run_vyper_standard_json(self.standard_json_input, vyper_bin)
 78        compilation_unit = CompilationUnit(crytic_compile, str(target))
 79
 80        compiler_version = compilation_artifacts["compiler"].split("-")[1]
 81        if compiler_version != "0.3.7":
 82            LOGGER.info("Vyper != 0.3.7 support is a best effort and might fail")
 83        compilation_unit.compiler_version = CompilerVersion(
 84            compiler="vyper", version=compiler_version, optimized=False
 85        )
 86
 87        for source_file, contract_info in compilation_artifacts["contracts"].items():
 88            filename = convert_filename(source_file, _relative_to_short, crytic_compile)
 89            source_unit = compilation_unit.create_source_unit(filename)
 90            for contract_name, contract_metadata in contract_info.items():
 91                source_unit.add_contract_name(contract_name)
 92                compilation_unit.filename_to_contracts[filename].add(contract_name)
 93
 94                source_unit.abis[contract_name] = contract_metadata["abi"]
 95                source_unit.bytecodes_init[contract_name] = contract_metadata["evm"]["bytecode"][
 96                    "object"
 97                ].replace("0x", "")
 98                # Vyper does not provide the source mapping for the init bytecode
 99                source_unit.srcmaps_init[contract_name] = []
100                source_unit.srcmaps_runtime[contract_name] = contract_metadata["evm"][
101                    "deployedBytecode"
102                ]["sourceMap"].split(";")
103                source_unit.bytecodes_runtime[contract_name] = contract_metadata["evm"][
104                    "deployedBytecode"
105                ]["object"].replace("0x", "")
106                source_unit.natspec[contract_name] = Natspec(
107                    contract_metadata["userdoc"], contract_metadata["devdoc"]
108                )
109
110        for source_file, ast in compilation_artifacts["sources"].items():
111            filename = convert_filename(source_file, _relative_to_short, crytic_compile)
112            source_unit = compilation_unit.create_source_unit(filename)
113            source_unit.ast = ast
114
115    def add_source_files(self, file_paths: List[str]) -> None:
116        """
117        Append files
118
119        Args:
120            file_paths (List[str]): files to append
121
122        Returns:
123
124        """
125
126        for file_path in file_paths:
127            with open(file_path, "r", encoding="utf8") as f:
128                self.standard_json_input["sources"][file_path] = {  # type: ignore
129                    "content": f.read(),
130                }
131
132    def clean(self, **_kwargs: str) -> None:
133        """Clean compilation artifacts
134
135        Args:
136            **_kwargs: unused.
137        """
138        return
139
140    def is_dependency(self, _path: str) -> bool:
141        """Check if the path is a dependency (not supported for vyper)
142
143        Args:
144            _path (str): path to the target
145
146        Returns:
147            bool: True if the target is a dependency
148        """
149        return False
150
151    @staticmethod
152    def is_supported(target: str, **kwargs: str) -> bool:
153        """Check if the target is a vyper project
154
155        Args:
156            target (str): path to the target
157            **kwargs: optional arguments. Used "vyper_ignore"
158
159        Returns:
160            bool: True if the target is a vyper project
161        """
162        vyper_ignore = kwargs.get("vyper_ignore", False)
163        if vyper_ignore:
164            return False
165        return os.path.isfile(target) and target.endswith(".vy")
166
167    def _guessed_tests(self) -> List[str]:
168        """Guess the potential unit tests commands
169
170        Returns:
171            List[str]: The guessed unit tests commands
172        """
173        return []

Vyper platform

VyperStandardJson(target: Union[pathlib.Path, NoneType] = None, **_kwargs: str)
38    def __init__(self, target: Optional[Path] = None, **_kwargs: str):
39        super().__init__(str(target), **_kwargs)
40        self.standard_json_input = {
41            "language": "Vyper",
42            "sources": {},
43            "settings": {
44                "outputSelection": {
45                    "*": {
46                        "*": [
47                            "abi",
48                            "devdoc",
49                            "userdoc",
50                            "evm.bytecode",
51                            "evm.deployedBytecode",
52                            "evm.deployedBytecode.sourceMap",
53                        ],
54                        "": ["ast"],
55                    }
56                }
57            },
58        }

Init the object

Args: target (str): path to the target **_kwargs: optional arguments.

Raises: IncorrectPlatformInitialization: If the Platform was not correctly designed

NAME: str = 'vyper'
PROJECT_URL: str = 'https://github.com/vyperlang/vyper'
TYPE: crytic_compile.platform.types.Type = <Type.VYPER: 7>
standard_json_input
def compile( self, crytic_compile: crytic_compile.crytic_compile.CryticCompile, **kwargs: str) -> None:
 60    def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None:
 61        """Compile the target
 62
 63        Args:
 64            crytic_compile (CryticCompile): CryticCompile object to populate
 65            **kwargs: optional arguments. Used "vyper"
 66
 67
 68        """
 69        target = self._target
 70        # If the target was a directory `add_source_file` should have been called
 71        # by `compile_all`. Otherwise, we should have a single file target.
 72        if self._target is not None and os.path.isfile(self._target):
 73            self.add_source_files([target])
 74
 75        vyper_bin = kwargs.get("vyper", "vyper")
 76
 77        compilation_artifacts = _run_vyper_standard_json(self.standard_json_input, vyper_bin)
 78        compilation_unit = CompilationUnit(crytic_compile, str(target))
 79
 80        compiler_version = compilation_artifacts["compiler"].split("-")[1]
 81        if compiler_version != "0.3.7":
 82            LOGGER.info("Vyper != 0.3.7 support is a best effort and might fail")
 83        compilation_unit.compiler_version = CompilerVersion(
 84            compiler="vyper", version=compiler_version, optimized=False
 85        )
 86
 87        for source_file, contract_info in compilation_artifacts["contracts"].items():
 88            filename = convert_filename(source_file, _relative_to_short, crytic_compile)
 89            source_unit = compilation_unit.create_source_unit(filename)
 90            for contract_name, contract_metadata in contract_info.items():
 91                source_unit.add_contract_name(contract_name)
 92                compilation_unit.filename_to_contracts[filename].add(contract_name)
 93
 94                source_unit.abis[contract_name] = contract_metadata["abi"]
 95                source_unit.bytecodes_init[contract_name] = contract_metadata["evm"]["bytecode"][
 96                    "object"
 97                ].replace("0x", "")
 98                # Vyper does not provide the source mapping for the init bytecode
 99                source_unit.srcmaps_init[contract_name] = []
100                source_unit.srcmaps_runtime[contract_name] = contract_metadata["evm"][
101                    "deployedBytecode"
102                ]["sourceMap"].split(";")
103                source_unit.bytecodes_runtime[contract_name] = contract_metadata["evm"][
104                    "deployedBytecode"
105                ]["object"].replace("0x", "")
106                source_unit.natspec[contract_name] = Natspec(
107                    contract_metadata["userdoc"], contract_metadata["devdoc"]
108                )
109
110        for source_file, ast in compilation_artifacts["sources"].items():
111            filename = convert_filename(source_file, _relative_to_short, crytic_compile)
112            source_unit = compilation_unit.create_source_unit(filename)
113            source_unit.ast = ast

Compile the target

Args: crytic_compile (CryticCompile): CryticCompile object to populate **kwargs: optional arguments. Used "vyper"

def add_source_files(self, file_paths: List[str]) -> None:
115    def add_source_files(self, file_paths: List[str]) -> None:
116        """
117        Append files
118
119        Args:
120            file_paths (List[str]): files to append
121
122        Returns:
123
124        """
125
126        for file_path in file_paths:
127            with open(file_path, "r", encoding="utf8") as f:
128                self.standard_json_input["sources"][file_path] = {  # type: ignore
129                    "content": f.read(),
130                }

Append files

Args: file_paths (List[str]): files to append

Returns:

def clean(self, **_kwargs: str) -> None:
132    def clean(self, **_kwargs: str) -> None:
133        """Clean compilation artifacts
134
135        Args:
136            **_kwargs: unused.
137        """
138        return

Clean compilation artifacts

Args: **_kwargs: unused.

def is_dependency(self, _path: str) -> bool:
140    def is_dependency(self, _path: str) -> bool:
141        """Check if the path is a dependency (not supported for vyper)
142
143        Args:
144            _path (str): path to the target
145
146        Returns:
147            bool: True if the target is a dependency
148        """
149        return False

Check if the path is a dependency (not supported for vyper)

Args: _path (str): path to the target

Returns: bool: True if the target is a dependency

@staticmethod
def is_supported(target: str, **kwargs: str) -> bool:
151    @staticmethod
152    def is_supported(target: str, **kwargs: str) -> bool:
153        """Check if the target is a vyper project
154
155        Args:
156            target (str): path to the target
157            **kwargs: optional arguments. Used "vyper_ignore"
158
159        Returns:
160            bool: True if the target is a vyper project
161        """
162        vyper_ignore = kwargs.get("vyper_ignore", False)
163        if vyper_ignore:
164            return False
165        return os.path.isfile(target) and target.endswith(".vy")

Check if the target is a vyper project

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

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