Source code for mythx_cli.payload.solidity

"""This module contains functions to generate Solidity-related payloads."""

import logging
import re
from pathlib import Path
from typing import Dict, List, Optional, Tuple

import click
import solcx
import solcx.exceptions

from mythx_cli.payload.util import patch_solc_bytecode, zero_srcmap_indices

LOGGER = logging.getLogger("mythx-cli")
PRAGMA_PATTERN = r"pragma solidity [\^<>=]*(\d+\.\d+\.\d+);"


[docs]def generate_solidity_payload( file: str, version: Optional[str], contracts: List[str] = None, remappings: Tuple[str] = None, ) -> Dict: """Generate a MythX analysis request from a given Solidity file. This function will open the file, try to detect the used solc version from the pragma definition, and automatically compile it. If the given solc version is not installed on the client's system, it will be automatically downloaded. From the solc output, the following data is sent to the MythX API for analysis: * :code:`abi` * :code:`ast` * :code:`bin` * :code:`bin-runtime` * :code:`srcmap` * :code:`srcmap-runtime` :param file: The path pointing towards the Solidity file :param version: The solc version to use for compilation :param contracts: The contract name(s) to submit :param remappings: Import remappings to pass to solcx :return: The payload dictionary to be sent to MythX """ with open(file) as f: solc_version = re.findall(PRAGMA_PATTERN, f.read()) LOGGER.debug(f"solc version matches in {file}: {solc_version}") if not (solc_version or version): # no pragma found, user needs to specify the version raise click.exceptions.UsageError( "No pragma found - please specify a solc version with --solc-version" ) solc_version = f"v{version or solc_version[0]}" if solc_version not in solcx.get_installed_solc_versions(): try: LOGGER.debug(f"Installing solc {solc_version}") solcx.install_solc(solc_version) except Exception as e: raise click.exceptions.UsageError( f"Error installing solc version {solc_version}: {e}" ) solcx.set_solc_version(solc_version, silent=True) try: cwd = str(Path.cwd().absolute()) LOGGER.debug(f"Compiling {file} under allowed path {cwd}") result = solcx.compile_files( [file], output_values=( "abi", "ast", "bin", "bin-runtime", "srcmap", "srcmap-runtime", ), import_remappings=remappings or [ f"openzeppelin-solidity/={cwd}/node_modules/openzeppelin-solidity/", f"openzeppelin-zos/={cwd}/node_modules/openzeppelin-zos/", f"zos-lib/={cwd}/node_modules/zos-lib/", ], allow_paths=cwd, ) except solcx.exceptions.SolcError as e: raise click.exceptions.UsageError( f"Error compiling source with solc {solc_version}: {e}" ) # sanitize solcx keys new_result = {} for key, value in result.items(): new_key = key.split(":")[1] LOGGER.debug(f"Sanitizing solc key {key} -> {new_key}") new_result[new_key] = value result = new_result payload = {"sources": {}, "solc_version": solc_version} bytecode_max = 0 for contract_name, contract_data in result.items(): ast = contract_data["ast"] source_path = str(Path(ast.get("attributes", {}).get("absolutePath"))) creation_bytecode = contract_data["bin"] deployed_bytecode = contract_data["bin-runtime"] source_map = contract_data["srcmap"] deployed_source_map = contract_data["srcmap-runtime"] with open(source_path) as source_f: source = source_f.read() LOGGER.debug(f"Loaded contract source with {len(source)} characters") # always add source and AST, even if dependency payload["sources"][source_path] = {"source": source, "ast": ast} if (contracts and contract_name not in contracts) or ( not contracts and len(creation_bytecode) < bytecode_max ): LOGGER.debug(f"Found dependency contract {contract_name} - continung") continue bytecode_max = len(creation_bytecode) LOGGER.debug(f"Updaing main payload for {contract_name}") payload.update( { "contract_name": contract_name, "main_source": source_path, "source_list": [source_path], "bytecode": patch_solc_bytecode(creation_bytecode), "source_map": zero_srcmap_indices(source_map), "deployed_source_map": zero_srcmap_indices(deployed_source_map), "deployed_bytecode": patch_solc_bytecode(deployed_bytecode), "solc_version": solc_version, } ) return payload