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 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): """ 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"]