Beispiel #1
0
def __execute(compiler_version: VersionString, input_config: Dict, allow_paths: Optional[List[str]]):
    """Executes the solcx command and underlying solc wrapper"""

    # Lazy import to allow for optional installation of solcx
    try:
        from solcx.install import get_executable
        from solcx.main import compile_standard
    except ImportError:
        raise DevelopmentInstallationRequired(importable_name='solcx')

    # Prepare Solc Command
    solc_binary_path: Path = get_executable(version=compiler_version)

    _allow_paths = ',' + ','.join(str(p) for p in allow_paths)

    # Execute Compilation
    try:
        compiler_output = compile_standard(input_data=input_config,
                                           allow_paths=_allow_paths,
                                           solc_binary=solc_binary_path)
    except FileNotFoundError:
        raise CompilationError("The solidity compiler is not at the specified path. "
                               "Check that the file exists and is executable.")
    except PermissionError:
        raise CompilationError(f"The solidity compiler binary at {solc_binary_path} is not executable. "
                               "Check the file's permissions.")

    errors = compiler_output.get('errors')
    if errors:
        formatted = '\n'.join([error['formattedMessage'] for error in errors])
        SOLC_LOGGER.warn(f"Errors during compilation: \n{formatted}")

    SOLC_LOGGER.info(f"Successfully compiled {len(compiler_output)} sources with {OPTIMIZER_RUNS} optimization runs")
    return compiler_output
Beispiel #2
0
def _compile_combined_json(
    output_values: Optional[List],
    solc_binary: Union[str, Path, None],
    solc_version: Optional[Version],
    output_dir: Union[str, Path, None],
    overwrite: Optional[bool],
    allow_empty: Optional[bool],
    **kwargs: Any,
) -> Dict:

    if output_values is None:
        combined_json = _get_combined_json_outputs()
    else:
        combined_json = ",".join(output_values)

    if solc_binary is None:
        solc_binary = get_executable(solc_version)

    if output_dir:
        output_dir = Path(output_dir)
        if output_dir.is_file():
            raise FileExistsError(
                "`output_dir` must be as a directory, not a file")
        if output_dir.joinpath("combined.json").exists() and not overwrite:
            target_path = output_dir.joinpath("combined.json")
            raise FileExistsError(
                f"Target output file {target_path} already exists - use overwrite=True to overwrite"
            )

    stdoutdata, stderrdata, command, proc = wrapper.solc_wrapper(
        solc_binary=solc_binary,
        combined_json=combined_json,
        output_dir=output_dir,
        overwrite=overwrite,
        **kwargs,
    )

    if output_dir:
        output_path = Path(output_dir).joinpath("combined.json")
        if stdoutdata:
            output_path.parent.mkdir(parents=True, exist_ok=True)
            with output_path.open("w") as fp:
                fp.write(stdoutdata)
        else:
            with output_path.open() as fp:
                stdoutdata = fp.read()

    contracts = _parse_compiler_output(stdoutdata)

    if not contracts and not allow_empty:
        raise ContractsNotFound(
            command=command,
            return_code=proc.returncode,
            stdout_data=stdoutdata,
            stderr_data=stderrdata,
        )
    return contracts
Beispiel #3
0
def _get_combined_json_outputs(solc_binary: Union[Path, str] = None) -> str:
    if solc_binary is None:
        solc_binary = get_executable()

    help_str = wrapper.solc_wrapper(solc_binary=solc_binary,
                                    help=True)[0].split("\n")
    combined_json_args = next(i for i in help_str
                              if i.startswith("  --combined-json"))
    return combined_json_args.split(" ")[-1]
Beispiel #4
0
def get_solc_version() -> Version:
    """
    Get the version of the active `solc` binary.

    Returns
    -------
    Version
        solc version
    """
    solc_binary = get_executable()
    return wrapper._get_solc_version(solc_binary)
Beispiel #5
0
def get_solc_version(with_commit_hash: bool = False) -> Version:
    """
    Get the version of the active `solc` binary.

    Arguments
    ---------
    with_commit_hash : bool, optional
        If True, the commit hash is included within the version

    Returns
    -------
    Version
        solc version
    """
    solc_binary = get_executable()
    return wrapper._get_solc_version(solc_binary, with_commit_hash)
Beispiel #6
0
    def __init__(self,
                 source_dirs: List[SourceDirs] = None,
                 ignore_solidity_check: bool = False) -> None:

        # Allow for optional installation
        from solcx.install import get_executable

        self.log = Logger('solidity-compiler')

        version = SOLIDITY_COMPILER_VERSION if not ignore_solidity_check else None
        self.__sol_binary_path = get_executable(version=version)

        if source_dirs is None or len(source_dirs) == 0:
            self.source_dirs = [
                SourceDirs(root_source_dir=self.__default_contract_dir)
            ]
        else:
            self.source_dirs = source_dirs
Beispiel #7
0
def link_code(
    unlinked_bytecode: str,
    libraries: Dict,
    solc_binary: Union[str, Path] = None,
    solc_version: Version = None,
) -> str:
    """
    Add library addresses into unlinked bytecode.

    Arguments
    ---------
    unlinked_bytecode : str
        Compiled bytecode containing one or more library placeholders.
    libraries : Dict
        Library addresses given as {"library name": "address"}
    solc_binary : str | Path, optional
        Path of the `solc` binary to use. If not given, the currently active
        version is used (as set by `solcx.set_solc_version`)
    solc_version: Version, optional
        `solc` version to use. If not given, the currently active version is used.
        Ignored if `solc_binary` is also given.

    Returns
    -------
    str
        Linked bytecode
    """
    if solc_binary is None:
        solc_binary = get_executable(solc_version)

    library_list = [f"{name}:{address}" for name, address in libraries.items()]

    stdoutdata = wrapper.solc_wrapper(solc_binary=solc_binary,
                                      stdin=unlinked_bytecode,
                                      link=True,
                                      libraries=library_list)[0]

    return stdoutdata.replace("Linking completed.", "").strip()
Beispiel #8
0
def compile_standard(
    input_data: Dict,
    base_path: str = None,
    allow_paths: List = None,
    output_dir: str = None,
    overwrite: bool = False,
    solc_binary: Union[str, Path] = None,
    solc_version: Version = None,
    allow_empty: bool = False,
) -> Dict:
    """
    Compile Solidity contracts using the JSON-input-output interface.

    See the Solidity documentation for details on the expected JSON input and output
    formats.

    Arguments
    ---------
    input_data : Dict
        Compiler JSON input.
    base_path : Path | str, optional
        Use the given path as the root of the source tree instead of the root
        of the filesystem.
    allow_paths : List | Path | str, optional
        A path, or list of paths, to allow for imports.
    output_dir : str, optional
        Creates one file per component and contract/file at the specified directory.
    overwrite : bool, optional
        Overwrite existing files (used in combination with `output_dir`)
    solc_binary : str | Path, optional
        Path of the `solc` binary to use. If not given, the currently active
        version is used (as set by `solcx.set_solc_version`)
    solc_version: Version, optional
        `solc` version to use. If not given, the currently active version is used.
        Ignored if `solc_binary` is also given.
    allow_empty : bool, optional
        If `True`, do not raise when no compiled contracts are returned.

    Returns
    -------
    Dict
        Compiler JSON output.
    """
    if not input_data.get("sources") and not allow_empty:
        raise ContractsNotFound(
            "Input JSON does not contain any sources",
            stdin_data=json.dumps(input_data, sort_keys=True, indent=2),
        )

    if solc_binary is None:
        solc_binary = get_executable(solc_version)

    stdoutdata, stderrdata, command, proc = wrapper.solc_wrapper(
        solc_binary=solc_binary,
        stdin=json.dumps(input_data),
        standard_json=True,
        base_path=base_path,
        allow_paths=allow_paths,
        output_dir=output_dir,
        overwrite=overwrite,
    )

    compiler_output = json.loads(stdoutdata)
    if "errors" in compiler_output:
        has_errors = any(error["severity"] == "error"
                         for error in compiler_output["errors"])
        if has_errors:
            error_message = "\n".join(
                tuple(error["formattedMessage"]
                      for error in compiler_output["errors"]
                      if error["severity"] == "error"))
            raise SolcError(
                error_message,
                command=command,
                return_code=proc.returncode,
                stdin_data=json.dumps(input_data),
                stdout_data=stdoutdata,
                stderr_data=stderrdata,
                error_dict=compiler_output["errors"],
            )
    return compiler_output
Beispiel #9
0
def solc_wrapper(
    solc_binary: Union[Path, str] = None,
    stdin: str = None,
    source_files: Union[List, Path, str] = None,
    import_remappings: Union[Dict, List, str] = None,
    success_return_code: int = None,
    **kwargs: Any,
) -> Tuple[str, str, List, subprocess.Popen]:
    """
    Wrapper function for calling to `solc`.

    Arguments
    ---------
    solc_binary : Path | str, optional
        Location of the `solc` binary. If not given, the current default binary is used.
    stdin : str, optional
        Input to pass to `solc` via stdin
    source_files : list | Path | str, optional
        Path, or list of paths, of sources to compile
    import_remappings : Dict | List | str,  optional
        Path remappings. May be given as a string or list of strings formatted as `"prefix=path"`
        or a dict of `{"prefix": "path"}`
    success_return_code : int, optional
        Expected exit code. Raises `SolcError` if the process returns a different value.

    Keyword Arguments
    -----------------
    **kwargs : Any
        Flags to be passed to `solc`. Keywords are converted to flags by prepending `--` and
        replacing `_` with `-`, for example the keyword `evm_version` becomes `--evm-version`.
        Values may be given in the following formats:

            * `False`, `None`: ignored
            * `True`: flag is used without any arguments
            * str: given as an argument without modification
            * int: given as an argument, converted to a string
            * Path: converted to a string via `Path.as_posix()`
            * List, Tuple: elements are converted to strings and joined with `,`

    Returns
    -------
    str
        Process `stdout` output
    str
        Process `stderr` output
    List
        Full command executed by the function
    Popen
        Subprocess object used to call `solc`
    """
    if solc_binary:
        solc_binary = Path(solc_binary)
    else:
        solc_binary = install.get_executable()

    solc_version = _get_solc_version(solc_binary)
    command: List = [str(solc_binary)]

    if success_return_code is None:
        success_return_code = 1 if "help" in kwargs else 0

    if source_files is not None:
        if isinstance(source_files, (str, Path)):
            command.append(_to_string("source_files", source_files))
        else:
            command.extend([_to_string("source_files", i) for i in source_files])

    if import_remappings is not None:
        if isinstance(import_remappings, str):
            command.append(import_remappings)
        else:
            if isinstance(import_remappings, dict):
                import_remappings = [f"{k}={v}" for k, v in import_remappings.items()]
            command.extend(import_remappings)

    for key, value in kwargs.items():
        if value is None or value is False:
            continue

        key = f"--{key.replace('_', '-')}"
        if value is True:
            command.append(key)
        else:
            command.extend([key, _to_string(key, value)])

    if "standard_json" not in kwargs and not source_files:
        # indicates that solc should read from stdin
        command.append("-")

    if stdin is not None:
        stdin = str(stdin)

    proc = subprocess.Popen(
        command,
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        encoding="utf8",
    )

    stdoutdata, stderrdata = proc.communicate(stdin)

    if proc.returncode != success_return_code:
        if stderrdata.startswith("unrecognised option"):
            # unrecognised option '<FLAG>'
            flag = stderrdata.split("'")[1]
            raise UnknownOption(f"solc {solc_version} does not support the '{flag}' option'")
        if stderrdata.startswith("Invalid option"):
            # Invalid option to <FLAG>: <OPTION>
            flag, option = stderrdata.split(": ")
            flag = flag.split(" ")[-1]
            raise UnknownValue(
                f"solc {solc_version} does not accept '{option}' as an option for the '{flag}' flag"
            )

        raise SolcError(
            command=command,
            return_code=proc.returncode,
            stdin_data=stdin,
            stdout_data=stdoutdata,
            stderr_data=stderrdata,
        )

    return stdoutdata, stderrdata, command, proc