def get_vyper_pragma_spec(source: str, path: Optional[str] = None) -> NpmSpec: """ Extracts pragma information from Vyper source code. Args: source: Vyper source code path: Optional path to the source (only used for error reporting) Returns: NpmSpec object """ pragma_match = next( re.finditer(r"(?:\n|^)\s*#\s*@version\s*([^\n]*)", source), None) if pragma_match is None: if path: raise PragmaError(f"No version pragma in '{path}'") raise PragmaError("String does not contain a version pragma") pragma_string = pragma_match.groups()[0] pragma_string = " ".join(pragma_string.split()) try: return NpmSpec(pragma_string) except ValueError: pass try: # special case for Vyper 0.1.0-beta.X version = to_vyper_version(pragma_string) return NpmSpec(str(version)) except Exception: pass path = "" if path is None else f"{path}: " raise PragmaError( f"{path}Cannot parse Vyper version from pragma: {pragma_string}")
def compile_source( source: str, solc_version: Optional[str] = None, vyper_version: Optional[str] = None, optimize: bool = True, runs: Optional[int] = 200, evm_version: Optional[str] = None, ) -> "TempProject": """ Compile the given source code string and return a TempProject container with the ContractContainer instances. """ compiler_config: Dict = {"evm_version": evm_version, "solc": {}, "vyper": {}} # if no compiler version was given, first try to find a Solidity pragma if solc_version is None and vyper_version is None: try: solc_version = compiler.solidity.find_best_solc_version( {"<stdin>": source}, install_needed=True, silent=False ) except (PragmaError, SolcNotInstalled): pass if vyper_version is None: # if no vyper compiler version is given, try to compile using solidity compiler_config["solc"] = { "version": solc_version or str(compiler.solidity.get_version()), "optimize": optimize, "runs": runs, } try: return TempProject("TempSolcProject", {"<stdin>.sol": source}, compiler_config) except Exception as exc: # if compilation fails, raise when a solc version was given or we found a pragma if solc_version is not None: raise exc if vyper_version is None: # if no vyper compiler version was given, try to find a pragma try: vyper_version = compiler.vyper.find_best_vyper_version( {"<stdin>": source}, install_needed=True, silent=False ) except (PragmaError, VyperNotInstalled): pass compiler_config["vyper"] = {"version": vyper_version or compiler.vyper.get_version()} try: return TempProject("TempVyperProject", {"<stdin>.vy": source}, compiler_config) except Exception as exc: if solc_version is None and vyper_version is None: raise PragmaError( "No compiler version specified, no pragma statement in the source, " "and compilation failed with both solc and vyper" ) from None raise exc
def get_pragma_spec(source: str, path: Optional[str] = None) -> NpmSpec: """ Extracts pragma information from Solidity source code. Args: source: Solidity source code path: Optional path to the source (only used for error reporting) Returns: NpmSpec object """ pragma_match = next(re.finditer(r"pragma +solidity([^;]*);", source), None) if pragma_match is not None: pragma_string = pragma_match.groups()[0] pragma_string = " ".join(pragma_string.split()) return NpmSpec(pragma_string) if path: raise PragmaError(f"No version pragma in '{path}'") raise PragmaError("String does not contain a version pragma")
def find_best_solc_version( contract_sources: Dict[str, str], install_needed: bool = False, install_latest: bool = False, silent: bool = True, ) -> str: """ Analyzes contract pragmas and finds the best version compatible with all sources. Args: contract_sources: a dictionary in the form of {'path': "source code"} install_needed: if True, will install when no installed version matches the contract pragma install_latest: if True, will install when a newer version is available than the installed one silent: set to False to enable verbose reporting Returns: version string """ available_versions, installed_versions = _get_solc_version_list() for path, source in contract_sources.items(): pragma_string = next(PRAGMA_REGEX.finditer(source), None) if pragma_string is None: raise PragmaError(f"No version pragma in '{path}'") pragma_spec = NpmSpec(pragma_string.groups()[0]) installed_versions = [ i for i in installed_versions if i in pragma_spec ] available_versions = [ i for i in available_versions if i in pragma_spec ] if not available_versions: raise IncompatibleSolcVersion( "No installable solc version compatible across all sources") if not installed_versions and not (install_needed or install_latest): raise IncompatibleSolcVersion( "No installed solc version compatible across all sources") if max(available_versions) > max(installed_versions, default=Version("0.0.0")): if install_latest or (install_needed and not installed_versions): install_solc(max(available_versions)) return str(max(available_versions)) if not silent: print( f"New compatible solc version available: {max(available_versions)}" ) return str(max(installed_versions))
def find_solc_versions( contract_sources: Dict[str, str], install_needed: bool = False, install_latest: bool = False, silent: bool = True, ) -> Dict: """ Analyzes contract pragmas and determines which solc version(s) to use. Args: contract_sources: a dictionary in the form of {'path': "source code"} install_needed: if True, will install when no installed version matches the contract pragma install_latest: if True, will install when a newer version is available than the installed one silent: set to False to enable verbose reporting Returns: dictionary of {'version': ['path', 'path', ..]} """ available_versions, installed_versions = _get_solc_version_list() pragma_specs: Dict = {} to_install = set() new_versions = set() for path, source in contract_sources.items(): pragma_string = next(PRAGMA_REGEX.finditer(source), None) if pragma_string is None: raise PragmaError(f"No version pragma in '{path}'") pragma_specs[path] = NpmSpec(pragma_string.groups()[0]) version = pragma_specs[path].select(installed_versions) if not version and not (install_needed or install_latest): raise IncompatibleSolcVersion( f"No installed solc version matching '{pragma_string[0]}' in '{path}'" ) # if no installed version of solc matches the pragma, find the latest available version latest = pragma_specs[path].select(available_versions) if not version and not latest: raise IncompatibleSolcVersion( f"No installable solc version matching '{pragma_string[0]}' in '{path}'" ) if not version or (install_latest and latest > version): to_install.add(latest) elif latest and latest > version: new_versions.add(str(version)) # install new versions if needed if to_install: install_solc(*to_install) installed_versions = [ Version(i[1:]) for i in solcx.get_installed_solc_versions() ] elif new_versions and not silent: print( f"New compatible solc version{'s' if len(new_versions) > 1 else ''}" f" available: {', '.join(new_versions)}") # organize source paths by latest available solc version compiler_versions: Dict = {} for path, spec in pragma_specs.items(): version = spec.select(installed_versions) compiler_versions.setdefault(str(version), []).append(path) return compiler_versions
def find_solc_versions(contracts, install_needed=False, install_latest=False, silent=True): '''Analyzes contract pragmas and determines which solc version(s) to use. Args: contracts: a dictionary in the form of {'path': "source code"} install_needed: if True, will install when no installed version matches the contract pragma install_latest: if True, will install when a newer version is available than the installed one silent: enables verbose reporting Returns: dictionary of {'version': ['path', 'path', ..]} ''' installed_versions = [ Version(i[1:]) for i in solcx.get_installed_solc_versions() ] try: available_versions = [ Version(i[1:]) for i in solcx.get_available_solc_versions() ] except ConnectionError: if not installed_versions: raise ConnectionError( "Solc not installed and cannot connect to GitHub") available_versions = installed_versions pragma_regex = re.compile(r"pragma +solidity([^;]*);") version_regex = re.compile(r"(([<>]?=?|\^)\d+\.\d+\.\d+)+") pragma_specs = dict((i, set()) for i in contracts) to_install = set() for path, source in contracts.items(): try: pragma_string = next(pragma_regex.finditer(source))[0] except StopIteration: raise PragmaError(f"No version pragma in '{path}'") from None # convert pragma to Version objects comparator_set_range = pragma_string.replace(" ", "").split('||') for comparator_set in comparator_set_range: spec = Spec(*(i[0] for i in version_regex.findall(comparator_set))) spec = _standardize_spec(spec) pragma_specs[path].add(spec) # if no installed version of solc matches the pragma, find the latest available version if not next( (i for i in pragma_specs[path] if i.select(installed_versions)), None): try: version = _select_max(pragma_specs[path], available_versions) to_install.add(version) except ValueError: raise PragmaError( f"No installable solc version matching '{pragma_string}' in '{path}'" ) from None if not install_needed: raise IncompatibleSolcVersion( f"No installed solc version matching '{pragma_string}' in '{path}'" ) # install new versions if needed if to_install: install_solc(*to_install) installed_versions = [ Version(i[1:]) for i in solcx.get_installed_solc_versions() ] # organize source paths by latest available solc version compiler_versions = {} new_versions = set() for path, spec_list in pragma_specs.items(): version = _select_max(spec_list, installed_versions) compiler_versions.setdefault(str(version), []).append(path) latest = _select_max(spec_list, available_versions) if latest > version: new_versions.add(str(latest)) if new_versions: if install_latest: install_solc(*new_versions) return find_solc_versions(contracts) if not silent: print( f"New compatible solc version{'s' if len(new_versions) > 1 else ''}" f" available: {', '.join(new_versions)}") return compiler_versions