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"]
def solc_handle_contracts( targets_json: Dict, skip_filename: bool, compilation_unit: "CompilationUnit", target: str, solc_working_dir: Optional[str], ) -> None: """Populate the compilation unit from the compilation json artifacts Args: targets_json (Dict): Compilation artifacts skip_filename (bool): If true, skip the filename (for solc <0.4.10) compilation_unit (CompilationUnit): Associated compilation unit target (str): Path to the target solc_working_dir (Optional[str]): Working directory for running solc """ is_above_0_8 = _is_at_or_above_minor_version(compilation_unit, 8) if "contracts" in targets_json: for original_contract_name, info in targets_json["contracts"].items(): contract_name = extract_name(original_contract_name) # for solc < 0.4.10 we cant retrieve the filename from the ast if skip_filename: contract_filename = convert_filename( target, relative_to_short, compilation_unit.crytic_compile, working_dir=solc_working_dir, ) else: contract_filename = convert_filename( extract_filename(original_contract_name), relative_to_short, compilation_unit.crytic_compile, working_dir=solc_working_dir, ) compilation_unit.contracts_names.add(contract_name) compilation_unit.filename_to_contracts[contract_filename].add( contract_name) compilation_unit.abis[contract_name] = (json.loads( info["abi"]) if not is_above_0_8 else info["abi"]) compilation_unit.bytecodes_init[contract_name] = info["bin"] compilation_unit.bytecodes_runtime[contract_name] = info[ "bin-runtime"] compilation_unit.srcmaps_init[contract_name] = info[ "srcmap"].split(";") compilation_unit.srcmaps_runtime[contract_name] = info[ "srcmap-runtime"].split(";") userdoc = json.loads(info.get( "userdoc", "{}")) if not is_above_0_8 else info["userdoc"] devdoc = json.loads(info.get( "devdoc", "{}")) if not is_above_0_8 else info["devdoc"] natspec = Natspec(userdoc, devdoc) compilation_unit.natspec[contract_name] = natspec
def solc_handle_contracts( targets_json: Dict, skip_filename: bool, compilation_unit: "CompilationUnit", target: str, solc_working_dir: Optional[str], ): is_above_0_8 = _is_at_or_above_minor_version(compilation_unit, 8) if "contracts" in targets_json: for original_contract_name, info in targets_json["contracts"].items(): contract_name = extract_name(original_contract_name) contract_filename = extract_filename(original_contract_name) # for solc < 0.4.10 we cant retrieve the filename from the ast if skip_filename: contract_filename = convert_filename( target, relative_to_short, compilation_unit.crytic_compile, working_dir=solc_working_dir, ) else: contract_filename = convert_filename( contract_filename, relative_to_short, compilation_unit.crytic_compile, working_dir=solc_working_dir, ) compilation_unit.contracts_names.add(contract_name) compilation_unit.contracts_filenames[ contract_name] = contract_filename compilation_unit.abis[contract_name] = (json.loads( info["abi"]) if not is_above_0_8 else info["abi"]) compilation_unit.bytecodes_init[contract_name] = info["bin"] compilation_unit.bytecodes_runtime[contract_name] = info[ "bin-runtime"] compilation_unit.srcmaps_init[contract_name] = info[ "srcmap"].split(";") compilation_unit.srcmaps_runtime[contract_name] = info[ "srcmap-runtime"].split(";") userdoc = json.loads(info.get( "userdoc", "{}")) if not is_above_0_8 else info["userdoc"] devdoc = json.loads(info.get( "devdoc", "{}")) if not is_above_0_8 else info["devdoc"] natspec = Natspec(userdoc, devdoc) compilation_unit.natspec[contract_name] = natspec
def compile(self, crytic_compile: "CryticCompile", **kwargs: str): """ Compile the target :param kwargs: :return: """ hardhat_ignore_compile = kwargs.get("hardhat_ignore_compile", False) or kwargs.get( "ignore_compile", False) cache_directory = kwargs.get("hardhat_cache_directory", "cache") config_file = Path(cache_directory, "solidity-files-cache.json") build_directory = Path( kwargs.get("hardhat_cache_directory", "artifacts/build-info")) hardhat_working_dir = kwargs.get("hardhat_working_dir", None) base_cmd = ["hardhat"] if not kwargs.get("npx_disable", False): base_cmd = ["npx"] + base_cmd if not hardhat_ignore_compile: cmd = base_cmd + ["compile"] LOGGER.info( "'%s' running", " ".join(cmd), ) process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self._target) stdout_bytes, stderr_bytes = process.communicate() stdout, stderr = ( stdout_bytes.decode(), stderr_bytes.decode(), ) # convert bytestrings to unicode strings LOGGER.info(stdout) if stderr: LOGGER.error(stderr) (compiler, version_from_config, optimized) = _get_version_from_config(config_file) crytic_compile.compiler_version = CompilerVersion( compiler=compiler, version=version_from_config, optimized=optimized) skip_filename = crytic_compile.compiler_version.version in [ f"0.4.{x}" for x in range(0, 10) ] files = sorted( os.listdir(build_directory), key=lambda x: os.path.getmtime(Path(build_directory, x))) if not files: txt = f"`hardhat compile` failed. Can you run it?\n{build_directory} is empty" raise InvalidCompilation(txt) build_info = Path(build_directory, files[0]) with open(build_info, encoding="utf8") as file_desc: targets_json = json.load(file_desc)["output"] if "contracts" in targets_json: for original_filename, contracts_info in targets_json[ "contracts"].items(): for original_contract_name, info in contracts_info.items(): contract_name = extract_name(original_contract_name) contract_filename = convert_filename( original_filename, relative_to_short, crytic_compile, working_dir=hardhat_working_dir, ) crytic_compile.contracts_names.add(contract_name) crytic_compile.contracts_filenames[ contract_name] = contract_filename crytic_compile.abis[contract_name] = info["abi"] crytic_compile.bytecodes_init[contract_name] = info[ "evm"]["bytecode"]["object"] crytic_compile.bytecodes_runtime[contract_name] = info[ "evm"]["deployedBytecode"]["object"] crytic_compile.srcmaps_init[contract_name] = info[ "evm"]["bytecode"]["sourceMap"].split(";") crytic_compile.srcmaps_runtime[contract_name] = info[ "evm"]["bytecode"]["sourceMap"].split(";") userdoc = info.get("userdoc", {}) devdoc = info.get("devdoc", {}) natspec = Natspec(userdoc, devdoc) crytic_compile.natspec[contract_name] = natspec if "sources" in targets_json: for path, info in targets_json["sources"].items(): if skip_filename: path = convert_filename( self._target, relative_to_short, crytic_compile, working_dir=hardhat_working_dir, ) else: path = convert_filename( path, relative_to_short, crytic_compile, working_dir=hardhat_working_dir) crytic_compile.filenames.add(path) crytic_compile.asts[path.absolute] = info["ast"]
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) optimized = False with open(os.path.join(directory, "dapp.sol.json")) as file_desc: targets_json = json.load(file_desc) version = None if "version" in targets_json: version = re.findall(r"\d+\.\d+\.\d+", targets_json["version"])[0] for original_filename, contracts_info in targets_json[ "contracts"].items(): for original_contract_name, info in contracts_info.items(): if "metadata" in info: metadata = json.loads(info["metadata"]) if ("settings" in metadata and "optimizer" in metadata["settings"] and "enabled" in metadata["settings"]["optimizer"]): optimized |= metadata["settings"]["optimizer"][ "enabled"] contract_name = extract_name(original_contract_name) crytic_compile.contracts_names.add(contract_name) crytic_compile.contracts_filenames[ contract_name] = original_filename crytic_compile.abis[contract_name] = info["abi"] crytic_compile.bytecodes_init[contract_name] = info["evm"][ "bytecode"]["object"] crytic_compile.bytecodes_runtime[contract_name] = info[ "evm"]["deployedBytecode"]["object"] crytic_compile.srcmaps_init[contract_name] = info["evm"][ "bytecode"]["sourceMap"].split(";") crytic_compile.srcmaps_runtime[contract_name] = info[ "evm"]["bytecode"]["sourceMap"].split(";") userdoc = info.get("userdoc", {}) devdoc = info.get("devdoc", {}) natspec = Natspec(userdoc, devdoc) crytic_compile.natspec[contract_name] = natspec if version is None: metadata = json.loads(info["metadata"]) version = re.findall( r"\d+\.\d+\.\d+", metadata["compiler"]["version"])[0] 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"] crytic_compile.compiler_version = CompilerVersion(compiler="solc", version=version, optimized=optimized)
def compile(self, crytic_compile: "CryticCompile", **kwargs: str): """ Compile the target :param kwargs: :return: """ cache_directory = kwargs.get("buidler_cache_directory", "") target_solc_file = os.path.join(cache_directory, "solc-output.json") target_vyper_file = os.path.join(cache_directory, "vyper-docker-updates.json") buidler_ignore_compile = kwargs.get("buidler_ignore_compile", False) or kwargs.get( "buidler_compile", False) buidler_working_dir = kwargs.get("buidler_working_dir", None) base_cmd = ["buidler"] if not kwargs.get("npx_disable", False): base_cmd = ["npx"] + base_cmd if not buidler_ignore_compile: cmd = base_cmd + ["compile"] LOGGER.info( "'%s' running", " ".join(cmd), ) process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self._target) stdout_bytes, stderr_bytes = process.communicate() stdout, stderr = ( stdout_bytes.decode(), stderr_bytes.decode(), ) # convert bytestrings to unicode strings LOGGER.info(stdout) if stderr: LOGGER.error(stderr) if not os.path.isfile(os.path.join(self._target, target_solc_file)): if os.path.isfile(os.path.join(self._target, target_vyper_file)): txt = 'Vyper not yet supported with buidler.' txt += ' Please open an issue in https://github.com/crytic/crytic-compile' raise InvalidCompilation(txt) txt = f"`buidler compile` failed. Can you run it?\n{os.path.join(self._target, target_solc_file)} not found" raise InvalidCompilation(txt) (compiler, version_from_config, optimized) = _get_version_from_config(cache_directory) crytic_compile.compiler_version = CompilerVersion( compiler=compiler, version=version_from_config, optimized=optimized) skip_filename = crytic_compile.compiler_version.version in [ f"0.4.{x}" for x in range(0, 10) ] with open(target_solc_file, encoding="utf8") as file_desc: targets_json = json.load(file_desc) if "contracts" in targets_json: for original_filename, contracts_info in targets_json[ "contracts"].items(): for original_contract_name, info in contracts_info.items(): contract_name = extract_name(original_contract_name) contract_filename = convert_filename( original_filename, relative_to_short, crytic_compile, working_dir=buidler_working_dir, ) crytic_compile.contracts_names.add(contract_name) crytic_compile.contracts_filenames[ contract_name] = contract_filename crytic_compile.abis[contract_name] = info["abi"] crytic_compile.bytecodes_init[contract_name] = info[ "evm"]["bytecode"]["object"] crytic_compile.bytecodes_runtime[contract_name] = info[ "evm"]["deployedBytecode"]["object"] crytic_compile.srcmaps_init[contract_name] = info[ "evm"]["bytecode"]["sourceMap"].split(";") crytic_compile.srcmaps_runtime[contract_name] = info[ "evm"]["bytecode"]["sourceMap"].split(";") userdoc = json.loads(info.get("userdoc", "{}")) devdoc = json.loads(info.get("devdoc", "{}")) natspec = Natspec(userdoc, devdoc) crytic_compile.natspec[contract_name] = natspec if "sources" in targets_json: for path, info in targets_json["sources"].items(): if skip_filename: path = convert_filename( self._target, relative_to_short, crytic_compile, working_dir=buidler_working_dir, ) else: path = convert_filename( path, relative_to_short, crytic_compile, working_dir=buidler_working_dir) crytic_compile.filenames.add(path) crytic_compile.asts[path.absolute] = info["ast"]
def compile(self, crytic_compile: "CryticCompile", **kwargs: str): """ Compile the tharget :param crytic_compile: :param target: :param kwargs: :return: """ target = self._target solc = kwargs.get("solc", "solc") if target.startswith(tuple(SUPPORTED_NETWORK)): prefix: Union[None, str] = SUPPORTED_NETWORK[target[:target.find(":") + 1]][0] prefix_bytecode = SUPPORTED_NETWORK[target[:target.find(":") + 1]][1] addr = target[target.find(":") + 1:] etherscan_url = ETHERSCAN_BASE % (prefix, addr) etherscan_bytecode_url = ETHERSCAN_BASE_BYTECODE % ( prefix_bytecode, addr) else: etherscan_url = ETHERSCAN_BASE % ("", target) etherscan_bytecode_url = ETHERSCAN_BASE_BYTECODE % ("", target) addr = target prefix = None only_source = kwargs.get("etherscan_only_source_code", False) only_bytecode = kwargs.get("etherscan_only_bytecode", False) etherscan_api_key = kwargs.get("etherscan_api_key", None) if etherscan_api_key: etherscan_url += f"&apikey={etherscan_api_key}" etherscan_bytecode_url += f"&apikey={etherscan_api_key}" source_code: str = "" result: Dict[str, Union[bool, str, int]] = dict() contract_name: str = "" if not only_bytecode: with urllib.request.urlopen(etherscan_url) as response: html = response.read() info = json.loads(html) if "message" not in info: LOGGER.error("Incorrect etherscan request") raise InvalidCompilation("Incorrect etherscan request " + etherscan_url) if not info["message"].startswith("OK"): LOGGER.error("Contract has no public source code") raise InvalidCompilation( "Contract has no public source code: " + etherscan_url) if "result" not in info: LOGGER.error("Contract has no public source code") raise InvalidCompilation( "Contract has no public source code: " + etherscan_url) result = info["result"][0] # Assert to help mypy assert isinstance(result["SourceCode"], str) assert isinstance(result["ContractName"], str) source_code = result["SourceCode"] contract_name = result["ContractName"] if source_code == "" and not only_source: LOGGER.info( "Source code not available, try to fetch the bytecode only") req = urllib.request.Request(etherscan_bytecode_url, headers={"User-Agent": "Mozilla/5.0"}) with urllib.request.urlopen(req) as response: html = response.read() _handle_bytecode(crytic_compile, target, html) return if source_code == "": LOGGER.error("Contract has no public source code") raise InvalidCompilation("Contract has no public source code: " + etherscan_url) if prefix: filename = os.path.join("crytic-export", "etherscan_contracts", f"{addr}{prefix}-{contract_name}.sol") else: filename = os.path.join("crytic-export", "etherscan_contracts", f"{addr}-{contract_name}.sol") if not os.path.exists("crytic-export"): os.makedirs("crytic-export") if not os.path.exists( os.path.join("crytic-export", "etherscan_contracts")): os.makedirs(os.path.join("crytic-export", "etherscan_contracts")) with open(filename, "w", encoding="utf8") as file_desc: file_desc.write(source_code) # Assert to help mypy assert isinstance(result["CompilerVersion"], str) compiler_version = re.findall( r"\d+\.\d+\.\d+", convert_version(result["CompilerVersion"]))[0] optimization_used: bool = result["OptimizationUsed"] == "1" solc_arguments = None if optimization_used: optimized_run = int(result["Runs"]) solc_arguments = f"--optimize --optimize-runs {optimized_run}" crytic_compile.compiler_version = CompilerVersion( compiler="solc", version=compiler_version, optimized=optimization_used) targets_json = _run_solc( crytic_compile, filename, solc=solc, solc_disable_warnings=False, solc_arguments=solc_arguments, env=dict(os.environ, SOLC_VERSION=compiler_version), ) 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) 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) crytic_compile.filenames.add(path) crytic_compile.asts[path.absolute] = info["AST"]
def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None: """Run the compilation Args: crytic_compile (CryticCompile): Associated CryticCompile object **kwargs: optional arguments. Used: "hardhat_ignore", "hardhat_ignore_compile", "ignore_compile", "hardhat_artifacts_directory","hardhat_working_dir","npx_disable" Raises: InvalidCompilation: If hardhat failed to run """ hardhat_ignore_compile = kwargs.get("hardhat_ignore_compile", False) or kwargs.get( "ignore_compile", False ) build_directory = Path( self._target, kwargs.get("hardhat_artifacts_directory", "artifacts"), "build-info" ) hardhat_working_dir = kwargs.get("hardhat_working_dir", self._target) base_cmd = ["hardhat"] if not kwargs.get("npx_disable", False): base_cmd = ["npx"] + base_cmd if not hardhat_ignore_compile: cmd = base_cmd + ["compile", "--force"] LOGGER.info( "'%s' running", " ".join(cmd), ) with subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self._target, executable=shutil.which(cmd[0]), ) as process: stdout_bytes, stderr_bytes = process.communicate() stdout, stderr = ( stdout_bytes.decode(), stderr_bytes.decode(), ) # convert bytestrings to unicode strings LOGGER.info(stdout) if stderr: LOGGER.error(stderr) files = sorted( os.listdir(build_directory), key=lambda x: os.path.getmtime(Path(build_directory, x)) ) files = [f for f in files if f.endswith(".json")] if not files: txt = f"`hardhat compile` failed. Can you run it?\n{build_directory} is empty" raise InvalidCompilation(txt) for file in files: build_info = Path(build_directory, file) # The file here should always ends .json, but just in case use ife uniq_id = file if ".json" not in file else file[0:-5] compilation_unit = CompilationUnit(crytic_compile, uniq_id) with open(build_info, encoding="utf8") as file_desc: loaded_json = json.load(file_desc) targets_json = loaded_json["output"] version_from_config = loaded_json["solcVersion"] # TODO supper vyper input_json = loaded_json["input"] compiler = "solc" if input_json["language"] == "Solidity" else "vyper" optimized = input_json["settings"]["optimizer"]["enabled"] compilation_unit.compiler_version = CompilerVersion( compiler=compiler, version=version_from_config, optimized=optimized ) skip_filename = compilation_unit.compiler_version.version in [ f"0.4.{x}" for x in range(0, 10) ] if "contracts" in targets_json: for original_filename, contracts_info in targets_json["contracts"].items(): for original_contract_name, info in contracts_info.items(): contract_name = extract_name(original_contract_name) contract_filename = convert_filename( original_filename, relative_to_short, crytic_compile, working_dir=hardhat_working_dir, ) compilation_unit.contracts_names.add(contract_name) compilation_unit.filename_to_contracts[contract_filename].add( contract_name ) compilation_unit.abis[contract_name] = info["abi"] compilation_unit.bytecodes_init[contract_name] = info["evm"][ "bytecode" ]["object"] compilation_unit.bytecodes_runtime[contract_name] = info["evm"][ "deployedBytecode" ]["object"] compilation_unit.srcmaps_init[contract_name] = info["evm"]["bytecode"][ "sourceMap" ].split(";") compilation_unit.srcmaps_runtime[contract_name] = info["evm"][ "deployedBytecode" ]["sourceMap"].split(";") userdoc = info.get("userdoc", {}) devdoc = info.get("devdoc", {}) natspec = Natspec(userdoc, devdoc) compilation_unit.natspec[contract_name] = natspec if "sources" in targets_json: for path, info in targets_json["sources"].items(): if skip_filename: path = convert_filename( self._target, relative_to_short, crytic_compile, working_dir=hardhat_working_dir, ) else: path = convert_filename( path, relative_to_short, crytic_compile, working_dir=hardhat_working_dir, ) crytic_compile.filenames.add(path) compilation_unit.filenames.add(path) compilation_unit.asts[path.absolute] = info["ast"]
def compile(self, crytic_compile: "CryticCompile", **kwargs: str): """ Compile the target :param crytic_compile: :param target: :param kwargs: :return: """ embark_ignore_compile = kwargs.get("embark_ignore_compile", False) or kwargs.get( "ignore_compile", False ) embark_overwrite_config = kwargs.get("embark_overwrite_config", False) plugin_name = "@trailofbits/embark-contract-info" with open(os.path.join(self._target, "embark.json"), encoding="utf8") as file_desc: embark_json = json.load(file_desc) if embark_overwrite_config: write_embark_json = False if not "plugins" in embark_json: embark_json["plugins"] = {plugin_name: {"flags": ""}} write_embark_json = True elif not plugin_name in embark_json["plugins"]: embark_json["plugins"][plugin_name] = {"flags": ""} write_embark_json = True if write_embark_json: try: process = subprocess.Popen(["npm", "install", plugin_name], cwd=self._target) except OSError as error: raise InvalidCompilation(error) _, stderr = process.communicate() with open( os.path.join(self._target, "embark.json"), "w", encoding="utf8" ) as outfile: json.dump(embark_json, outfile, indent=2) else: if (not "plugins" in embark_json) or (not plugin_name in embark_json["plugins"]): raise InvalidCompilation( "embark-contract-info plugin was found in embark.json. " "Please install the plugin (see " "https://github.com/crytic/crytic-compile/wiki/Usage#embark)" ", or use --embark-overwrite-config." ) if not embark_ignore_compile: try: cmd = ["embark", "build", "--contracts"] if not kwargs.get("npx_disable", False): cmd = ["npx"] + cmd process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self._target) except OSError as error: raise InvalidCompilation(error) stdout, stderr = process.communicate() LOGGER.info("%s\n", stdout.decode()) if stderr: # Embark might return information to stderr, but compile without issue LOGGER.error("%s", stderr.decode()) infile = os.path.join(self._target, "crytic-export", "contracts-embark.json") if not os.path.isfile(infile): raise InvalidCompilation( "Embark did not generate the AST file. Is Embark installed " "(npm install -g embark)? Is embark-contract-info installed? (npm install -g embark)." ) crytic_compile.compiler_version = _get_version(self._target) with open(infile, "r", encoding="utf8") as file_desc: targets_loaded = json.load(file_desc) for k, ast in targets_loaded["asts"].items(): filename = convert_filename( k, _relative_to_short, crytic_compile, working_dir=self._target ) crytic_compile.asts[filename.absolute] = ast crytic_compile.filenames.add(filename) if not "contracts" in targets_loaded: LOGGER.error( "Incorrect json file generated. Are you using %s >= 1.1.0?", plugin_name ) raise InvalidCompilation( f"Incorrect json file generated. Are you using {plugin_name} >= 1.1.0?" ) for original_contract_name, info in targets_loaded["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_filenames[contract_name] = contract_filename crytic_compile.contracts_names.add(contract_name) if "abi" in info: crytic_compile.abis[contract_name] = info["abi"] if "bin" in info: crytic_compile.bytecodes_init[contract_name] = info["bin"].replace("0x", "") if "bin-runtime" in info: crytic_compile.bytecodes_runtime[contract_name] = info["bin-runtime"].replace( "0x", "" ) if "srcmap" in info: crytic_compile.srcmaps_init[contract_name] = info["srcmap"].split(";") if "srcmap-runtime" in info: crytic_compile.srcmaps_runtime[contract_name] = info["srcmap-runtime"].split( ";" ) userdoc = info.get("userdoc", {}) devdoc = info.get("devdoc", {}) natspec = Natspec(userdoc, devdoc) crytic_compile.natspec[contract_name] = natspec
def compile(self, crytic_compile: "CryticCompile", **kwargs: str): """ Compile the target :param crytic_compile: :param kwargs: :return: """ solc = kwargs.get("solc", "solc") solc_disable_warnings = kwargs.get("solc_disable_warnings", False) solc_arguments = kwargs.get("solc_args", "") solc_remaps = kwargs.get("solc_remaps", None) solc_working_dir = kwargs.get("solc_working_dir", None) force_legacy_json = kwargs.get("solc_force_legacy_json", False) crytic_compile.compiler_version = CompilerVersion( compiler="solc", version=get_version(solc), optimized=is_optimized(solc_arguments) ) # From config file, solcs is a dict (version -> path) # From command line, solc is a list # The guessing of version only works from config file # This is to prevent too complex command line solcs_path: Optional[Union[str, Dict, List[str]]] = kwargs.get("solc_solcs_bin") # solcs_env is always a list. It matches solc-select list solcs_env = kwargs.get("solc_solcs_select") if solcs_path: if isinstance(solcs_path, str): solcs_path = solcs_path.split(",") targets_json = _run_solcs_path( crytic_compile, self._target, solcs_path, solc_disable_warnings, solc_arguments, solc_remaps=solc_remaps, working_dir=solc_working_dir, force_legacy_json=force_legacy_json, ) elif solcs_env: solcs_env_list = solcs_env.split(",") targets_json = _run_solcs_env( crytic_compile, self._target, solc, solc_disable_warnings, solc_arguments, solcs_env=solcs_env_list, solc_remaps=solc_remaps, working_dir=solc_working_dir, force_legacy_json=force_legacy_json, ) else: targets_json = _run_solc( crytic_compile, self._target, solc, solc_disable_warnings, solc_arguments, solc_remaps=solc_remaps, working_dir=solc_working_dir, force_legacy_json=force_legacy_json, ) skip_filename = crytic_compile.compiler_version.version in [ f"0.4.{x}" for x in range(0, 10) ] if "contracts" in targets_json: for original_contract_name, info in targets_json["contracts"].items(): contract_name = extract_name(original_contract_name) contract_filename = extract_filename(original_contract_name) # for solc < 0.4.10 we cant retrieve the filename from the ast if skip_filename: contract_filename = convert_filename( self._target, relative_to_short, crytic_compile, working_dir=solc_working_dir, ) else: contract_filename = convert_filename( contract_filename, relative_to_short, crytic_compile, working_dir=solc_working_dir, ) 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 if "sources" in targets_json: for path, info in targets_json["sources"].items(): if skip_filename: path = convert_filename( self._target, relative_to_short, crytic_compile, working_dir=solc_working_dir, ) else: path = convert_filename( path, relative_to_short, crytic_compile, working_dir=solc_working_dir ) crytic_compile.filenames.add(path) crytic_compile.asts[path.absolute] = info["AST"]
def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None: """Run the compilation Args: crytic_compile (CryticCompile): Associated CryticCompile objects **kwargs: optional arguments. Used: "buidler_cache_directory", "buidler_ignore_compile", "ignore_compile", "buidler_working_dir", "buidler_skip_directory_name_fix", "npx_disable" Raises: InvalidCompilation: If buidler failed to run """ cache_directory = kwargs.get("buidler_cache_directory", "") target_solc_file = os.path.join(cache_directory, "solc-output.json") target_vyper_file = os.path.join(cache_directory, "vyper-docker-updates.json") buidler_ignore_compile = kwargs.get("buidler_ignore_compile", False) or kwargs.get( "ignore_compile", False) buidler_working_dir = kwargs.get("buidler_working_dir", None) # See https://github.com/crytic/crytic-compile/issues/116 skip_directory_name_fix = kwargs.get("buidler_skip_directory_name_fix", False) base_cmd = ["buidler"] if not kwargs.get("npx_disable", False): base_cmd = ["npx"] + base_cmd if not buidler_ignore_compile: cmd = base_cmd + ["compile"] LOGGER.info( "'%s' running", " ".join(cmd), ) with subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self._target, executable=shutil.which(cmd[0]), ) as process: stdout_bytes, stderr_bytes = process.communicate() stdout, stderr = ( stdout_bytes.decode(), stderr_bytes.decode(), ) # convert bytestrings to unicode strings LOGGER.info(stdout) if stderr: LOGGER.error(stderr) if not os.path.isfile(os.path.join(self._target, target_solc_file)): if os.path.isfile(os.path.join(self._target, target_vyper_file)): txt = "Vyper not yet supported with buidler." txt += " Please open an issue in https://github.com/crytic/crytic-compile" raise InvalidCompilation(txt) txt = f"`buidler compile` failed. Can you run it?\n{os.path.join(self._target, target_solc_file)} not found" raise InvalidCompilation(txt) compilation_unit = CompilationUnit(crytic_compile, str(target_solc_file)) (compiler, version_from_config, optimized) = _get_version_from_config(Path(cache_directory)) compilation_unit.compiler_version = CompilerVersion( compiler=compiler, version=version_from_config, optimized=optimized) skip_filename = compilation_unit.compiler_version.version in [ f"0.4.{x}" for x in range(0, 10) ] with open(target_solc_file, encoding="utf8") as file_desc: targets_json = json.load(file_desc) if "contracts" in targets_json: for original_filename, contracts_info in targets_json[ "contracts"].items(): for original_contract_name, info in contracts_info.items(): contract_name = extract_name(original_contract_name) if (original_filename.startswith("ontracts/") and not skip_directory_name_fix): original_filename = "c" + original_filename contract_filename = convert_filename( original_filename, relative_to_short, crytic_compile, working_dir=buidler_working_dir, ) compilation_unit.contracts_names.add(contract_name) compilation_unit.filename_to_contracts[ contract_filename].add(contract_name) compilation_unit.abis[contract_name] = info["abi"] compilation_unit.bytecodes_init[contract_name] = info[ "evm"]["bytecode"]["object"] compilation_unit.bytecodes_runtime[ contract_name] = info["evm"]["deployedBytecode"][ "object"] compilation_unit.srcmaps_init[contract_name] = info[ "evm"]["bytecode"]["sourceMap"].split(";") compilation_unit.srcmaps_runtime[contract_name] = info[ "evm"]["deployedBytecode"]["sourceMap"].split(";") userdoc = info.get("userdoc", {}) devdoc = info.get("devdoc", {}) natspec = Natspec(userdoc, devdoc) compilation_unit.natspec[contract_name] = natspec if "sources" in targets_json: for path, info in targets_json["sources"].items(): if path.startswith( "ontracts/") and not skip_directory_name_fix: path = "c" + path if skip_filename: path = convert_filename( self._target, relative_to_short, crytic_compile, working_dir=buidler_working_dir, ) else: path = convert_filename( path, relative_to_short, crytic_compile, working_dir=buidler_working_dir) compilation_unit.filenames.add(path) crytic_compile.filenames.add(path) compilation_unit.asts[path.absolute] = info["ast"]