Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multiple vyper versions #731

Merged
merged 12 commits into from
Aug 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Brownie is a Python-based development and testing framework for smart contracts

## Features

* Full support for [Solidity](https://github.com/ethereum/solidity) (`>=0.4.22`) and [Vyper](https://github.com/vyperlang/vyper) (`0.2.x`)
* Full support for [Solidity](https://github.com/ethereum/solidity) (`>=0.4.22`) and [Vyper](https://github.com/vyperlang/vyper) (`>=0.1.0-beta.16`)
* Contract testing via [`pytest`](https://github.com/pytest-dev/pytest), including trace-based coverage evaluation
* Property-based and stateful testing via [`hypothesis`](https://github.com/HypothesisWorks/hypothesis/tree/master/hypothesis-python)
* Powerful debugging tools, including python-style tracebacks and custom error strings
Expand Down
2 changes: 2 additions & 0 deletions brownie/data/default-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ compiler:
enabled: true
runs: 200
remappings: null
vyper:
version: null

console:
show_colors: true
Expand Down
13 changes: 10 additions & 3 deletions brownie/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,15 +159,22 @@ class ProjectNotFound(Exception):


class CompilerError(Exception):
def __init__(self, e: Type[psutil.Popen]) -> None:
err = [i["formattedMessage"] for i in yaml.safe_load(e.stdout_data)["errors"]]
super().__init__("Compiler returned the following errors:\n\n" + "\n".join(err))
def __init__(self, e: Type[psutil.Popen], compiler: str = "Compiler") -> None:
self.compiler = compiler

err_json = yaml.safe_load(e.stdout_data)
err = [i.get("formattedMessage") or i["message"] for i in err_json["errors"]]
super().__init__(f"{compiler} returned the following errors:\n\n" + "\n".join(err))


class IncompatibleSolcVersion(Exception):
pass


class IncompatibleVyperVersion(Exception):
pass


class PragmaError(Exception):
pass

Expand Down
55 changes: 39 additions & 16 deletions brownie/network/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
from eth_utils import remove_0x_prefix
from hexbytes import HexBytes
from semantic_version import Version
from vvm import get_installable_vyper_versions
from vvm.utils.convert import to_vyper_version

from brownie._config import CONFIG, REQUEST_HEADERS
from brownie.convert.datatypes import Wei
Expand Down Expand Up @@ -670,19 +672,30 @@ def from_explorer(
if not is_verified:
return cls.from_abi(name, address, abi, owner)

try:
version = Version(data["result"][0]["CompilerVersion"].lstrip("v")).truncate()
except Exception:
version = Version("0.0.0")
if version < Version("0.4.22") or (
# special case for OSX because installing 0.4.x versions is problematic
sys.platform == "darwin"
and version < Version("0.5.0")
and f"v{version}" not in solcx.get_installed_solc_versions()
):
compiler_str = data["result"][0]["CompilerVersion"]
if compiler_str.startswith("vyper:"):
try:
version = to_vyper_version(compiler_str[6:])
is_compilable = version in get_installable_vyper_versions()
except Exception:
is_compilable = False
else:
try:
version = Version(compiler_str.lstrip("v")).truncate()
if sys.platform == "darwin":
is_compilable = (
version >= Version("0.5.0")
or f"v{version}" in solcx.get_installed_solc_versions()
)
else:
is_compilable = f"v{version}" in solcx.get_available_solc_versions()
except Exception:
is_compilable = False

if not is_compilable:
if not silent:
warnings.warn(
f"{address}: target compiler '{data['result'][0]['CompilerVersion']}' is "
f"{address}: target compiler '{compiler_str}' is "
"unsupported by Brownie. Some functionality will not be available.",
BrownieCompilerWarning,
)
Expand All @@ -696,9 +709,10 @@ def from_explorer(
if evm_version == "Default":
evm_version = None

if data["result"][0]["SourceCode"].startswith("{"):
source_str = "\n".join(data["result"][0]["SourceCode"].splitlines())
if source_str.startswith("{"):
# source was verified using compiler standard JSON
input_json = json.loads(data["result"][0]["SourceCode"][1:-1])
input_json = json.loads(source_str[1:-1])
sources = {k: v["content"] for k, v in input_json["sources"].items()}
evm_version = input_json["settings"].get("evmVersion", evm_version)

Expand All @@ -709,10 +723,19 @@ def from_explorer(
output_json = compiler.compile_from_input_json(input_json)
build_json = compiler.generate_build_json(input_json, output_json)
else:
# source was submitted as a single flattened file
sources = {f"{name}-flattened.sol": data["result"][0]["SourceCode"]}
# source was submitted as a single file
if compiler_str.startswith("vyper"):
path_str = f"{name}.vy"
else:
path_str = f"{name}-flattened.sol"

sources = {path_str: source_str}
build_json = compiler.compile_and_format(
sources, solc_version=str(version), optimizer=optimizer, evm_version=evm_version
sources,
solc_version=str(version),
vyper_version=str(version),
optimizer=optimizer,
evm_version=evm_version,
)

build_json = build_json[name]
Expand Down
22 changes: 15 additions & 7 deletions brownie/project/compiler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
set_solc_version,
)
from brownie.project.compiler.utils import merge_natspec
from brownie.project.compiler.vyper import find_vyper_versions, set_vyper_version
from brownie.utils import notify

from . import solidity, vyper
Expand Down Expand Up @@ -47,6 +48,7 @@
def compile_and_format(
contract_sources: Dict[str, str],
solc_version: Optional[str] = None,
vyper_version: Optional[str] = None,
optimize: bool = True,
runs: int = 200,
evm_version: Optional[str] = None,
Expand Down Expand Up @@ -86,10 +88,15 @@ def compile_and_format(
build_json: Dict = {}
compiler_targets = {}

vyper_paths = [i for i in contract_sources if Path(i).suffix == ".vy"]
if vyper_paths:
compiler_targets["vyper"] = vyper_paths

vyper_sources = {k: v for k, v in contract_sources.items() if Path(k).suffix == ".vy"}
if vyper_sources:
# TODO add `vyper_version` input arg to manually specify, support in config file
if vyper_version is None:
compiler_targets.update(
find_vyper_versions(vyper_sources, install_needed=True, silent=silent)
)
else:
compiler_targets[vyper_version] = list(vyper_sources)
solc_sources = {k: v for k, v in contract_sources.items() if Path(k).suffix == ".sol"}
if solc_sources:
if solc_version is None:
Expand All @@ -104,7 +111,8 @@ def compile_and_format(

for version, path_list in compiler_targets.items():
compiler_data: Dict = {}
if version == "vyper":
if path_list[0].endswith(".vy"):
set_vyper_version(version)
language = "Vyper"
compiler_data["version"] = str(vyper.get_version())
interfaces = {k: v for k, v in interface_sources.items() if Path(k).suffix != ".sol"}
Expand Down Expand Up @@ -287,8 +295,8 @@ def generate_build_json(

abi = output_json["contracts"][path_str][contract_name]["abi"]
natspec = merge_natspec(
output_json["contracts"][path_str][contract_name]["devdoc"],
output_json["contracts"][path_str][contract_name]["userdoc"],
output_json["contracts"][path_str][contract_name].get("devdoc", {}),
output_json["contracts"][path_str][contract_name].get("userdoc", {}),
)
output_evm = output_json["contracts"][path_str][contract_name]["evm"]

Expand Down
2 changes: 1 addition & 1 deletion brownie/project/compiler/solidity.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def compile_from_input_json(
allow_paths=allow_paths,
)
except solcx.exceptions.SolcError as e:
raise CompilerError(e)
raise CompilerError(e, "solc")


def set_solc_version(version: str) -> str:
Expand Down