def __init__( self, executable="solc", outputs: List[str]=None, optimize: Optional[int]=None, evm_version: Optional[str]=None, warnings_as_errors: bool=False): with RaiseForParam("executable"): isfile_assert(executable) self._executable = executable with RaiseForParam("outputs"): if outputs is not None: for choice in outputs: OutputSelection.value_assert(choice) self._outputs = outputs[:] else: self._outputs = _make_default_output_selection() with RaiseForParam("optimize"): type_assert(optimize, (int, type(None))) self._optimize = optimize with RaiseForParam("evm_version"): if evm_version is not None: EvmVersion.value_assert(evm_version) self._evm_version = evm_version self._warnings_as_errors = bool(warnings_as_errors)
def add_string(self, unitname: str, source: str) -> None: """Add a source string to the list of sources :param unitname: a name for the provided source unit :param source: source code text """ with RaiseForParam("unitname"): type_assert(unitname, str) with RaiseForParam("source"): type_assert(source, str) self._text_sources[unitname] = source
def select(self, selector: str) -> dict: """Find a single contract matching the contract selector string. The selector string is a string in either of the following forms: - "{suffix}:{contractname}": source unit name suffix and contract name, separated by ':'. Example: "erc20/ERC20.sol:ERC20" matches contract named "ERC20" in source unit "/home/user/contracts/erc20/ERC20.sol". - "{contractname}": only the contract name. Example: "ERC20" matches contract named "ERC20". If the selector matches multiple contract, this function will raise an exception of type ValueError. :param selector: contract selector """ with RaiseForParam("selector"): type_assert(selector, str) if selector.count(":") == 1: suffix, contractname = selector.split(":") else: suffix, contractname = None, selector value_assert( "/" not in contractname, "Invalid contract selector syntax. Either 'path/suffix:ContractName' or 'ContractName'" ) contracts = self.find(suffix, contractname) value_assert(len(contracts) != 0, "Contract not found") value_assert( len(contracts) == 1, "Contract selector matched multiple contracts") return self._contracts[contracts[0]]
def add_file(self, path: str) -> None: """Add file to the list of sources. :param path: file path """ with RaiseForParam("path"): type_assert(path, str) self._file_sources.append(path)
def import_raw_key(self, private_key: str, passphrase: str = ""): with RaiseForParam("private_key"): value_assert(private_key.startswith("0x"), "must be a hex string prefixed with 0x") account_address = self._web3.personal.importRawKey( private_key, passphrase) if account_address not in self._accounts: self._accounts.append(account_address)
def update(self, other: "ContractObjectList") -> None: """Add all contracts from other ContractObjectList. :param other: other ContractObjectList with contracts to add """ with RaiseForParam("other"): type_assert(other, ContractObjectList) for (unitname, contractname), contract in other._contracts.items(): self.add_contract(unitname, contractname, contract)
def add_files(self, sources: List[str]) -> None: """Add list of files to the list of sources. :param sources: list of file paths """ with RaiseForParam("source"): type_assert(sources, list, ", paths to source files") for source in sources: type_assert(source, str, ", item in source file list") self._file_sources.extend(sources)
def find(self, suffix: Optional[str], contractname: str) -> List[Tuple[str, str]]: """Find contracts by unitname suffix and full contractname. Example: ("erc20/ERC20.sol", "ERC20") matches ("/home/user/contracts/erc20/ERC20.sol", "ERC20"). :param suffix: suffix to match the contract source unit name, or None; if it is None, any unit name is matched. :param contractname: full name of the contract """ with RaiseForParam("suffix"): type_assert(suffix, (str, type(None))) with RaiseForParam("contractname"): type_assert(contractname, str) out = [] units = self._name_to_units.get(contractname, []) for unit in units: if suffix is None or unit.endswith(suffix): out.append((unit, contractname)) return out
def add_directory(self, path: str, ext_filter: Optional[List[str]] = [".sol"]) -> None: """Add all sources from a directory. :param path: directory path :param ext_filter: list of allowed extensions for the source file names, including the '.' character (e.g. [".sol"]), or None for any extension """ with RaiseForParam("path"): type_assert(path, str) for root, _dirnames, filenames in os.walk(path): for filename in filenames: if ext_filter is None or os.path.splitext( filename)[1].lower() in ext_filter: self._file_sources.append(os.path.join(root, filename))
def _DEBUG_SolcEmscripten(enable_emscripten): from solitude.tools import solc with RaiseForParam("enable_emscripten"): type_assert(enable_emscripten, bool) solc.Solc = solc.SolcEmscripten
def compile( self, source_files: Optional[List[str]]=None, source_strings: Optional[Dict[str, str]]=None): cmd = [self._executable, "--standard-json"] data = _SolcStandardJsonInput() data.set_output_selection(self._outputs) if self._optimize is not None: data.set_optimizer(self._optimize) if self._evm_version is not None: data.set_evm_version(self._evm_version) # read source files and include them in the JSON unitname_to_path = {} with RaiseForParam("source_files"): if source_files is not None: type_assert(source_files, list) for path in source_files: isfile_assert(path) absolute_path = os.path.abspath(path) unitname = data.add_source_from_file(absolute_path) unitname_to_path[unitname] = absolute_path # include source strings in the JSON with RaiseForParam("source_strings"): if source_strings is not None: type_assert(source_strings, dict, ", (name: str -> code: str)") for unitname, contents in source_strings.items(): value_assert( isinstance(unitname, str), "Key must be a string containing a name") value_assert( not unitname.startswith("/"), "Key cannot start with '/'") value_assert( isinstance(contents, str), "Value must be a string containing the source code") data.add_source(unitname, contents) # invoke solc solc_out = self.call_standard_json(data.value()) timestamp = datetime.datetime.utcnow().isoformat() # collect errors and warnings errors = [] warnings = [] for error in solc_out.get("errors", []): severity = error.get("severity") if severity == "error" or self._warnings_as_errors: errors.append(error) else: warnings.append(error) # raise exception on error if errors: raise CompilerError([ _convert_error(error) for error in errors]) contracts = solc_out.get("contracts", {}) sources = solc_out.get("sources", {}) sourceid_to_unitname = [None] * len(sources) output = {} # gather AST and source id for each source unitname_to_ast = {} for unitname, unit in sources.items(): unitname_to_ast[unitname] = unit.get("ast") sourceid_to_unitname[unit["id"]] = unitname # create output dictionary, (unitname, contractname) -> contract # including the full source content and AST for unitname, contracts_in_unit in contracts.items(): for contractname, contract in contracts_in_unit.items(): try: source_path = unitname_to_path[unitname] except KeyError: source_path = unitname out_contract_data = {} out_contract_data["abi"] = contract.get("abi") out_contract_data["bin"] = ( contract.get("evm", {}).get("bytecode", {}).get("object")) out_contract_data["srcmap"] = ( contract.get("evm", {}).get("bytecode", {}).get("sourceMap")) out_contract_data["bin-runtime"] = ( contract.get("evm", {}).get("deployedBytecode", {}).get("object")) out_contract_data["srcmap-runtime"] = ( contract.get("evm", {}).get("deployedBytecode", {}).get("sourceMap")) out_contract_data["_solitude"] = { "ast": unitname_to_ast[unitname], "unitName": unitname, "contractName": contractname, "sourceList": sourceid_to_unitname, "sourcePath": source_path, "source": data.get_source(unitname), "timestamp": timestamp } output[(unitname, contractname)] = out_contract_data return output