Exemple #1
0
def test_build_json_unlinked_libraries(solc4source, solc5source):
    build_json = compiler.compile_and_format({"path": solc5source},
                                             solc_version="0.5.7")
    assert "__Bar__" in build_json["Foo"]["bytecode"]
    build_json = compiler.compile_and_format({"path": solc4source},
                                             solc_version="0.4.25")
    assert "__Bar__" in build_json["Foo"]["bytecode"]
Exemple #2
0
def test_compiler_errors(solc4source, solc5source):
    with pytest.raises(CompilerError):
        compiler.compile_and_format({"path": solc4source},
                                    solc_version="0.5.7")
    with pytest.raises(CompilerError):
        compiler.compile_and_format({"path": solc5source},
                                    solc_version="0.4.25")
Exemple #3
0
def test_size_limit(capfd):
    code = f"""
pragma solidity 0.6.2;
contract Foo {{ function foo() external returns (bool) {{
    require(msg.sender != address(0), "{"blah"*10000}"); }}
}}"""
    compiler.compile_and_format({"foo.sol": code})
    assert "exceeds EIP-170 limit of 24577" in capfd.readouterr()[0]
def test_multiple_compilers(solc4source, vysource):
    compiler.compile_and_format({
        "solc4.sol":
        solc4source,
        "vyper.vy":
        vysource,
        "solc6.sol":
        "pragma solidity 0.6.2; contract Foo {}",
    })
Exemple #5
0
def test_expand_build_offsets():
    source = sources.get("BrownieTester")
    build_json = compiler.compile_and_format({'path': source})['BrownieTester']
    minified_json = compiler.compile_and_format({'path': source},
                                                minify=True)['BrownieTester']
    expanded_json = build.expand_build_offsets(deepcopy(minified_json))
    for key in ('coverageMap', 'pcMap'):
        assert expanded_json[key] == build_json[key]
        assert minified_json[key] != build_json[key]
Exemple #6
0
def test_expand_build_offsets(testproject, btsource):
    source = {'contracts/BrownieTester.sol': btsource}
    build_json = compiler.compile_and_format(source, '0.5.7')['BrownieTester']
    minified_json = compiler.compile_and_format(source, '0.5.7',
                                                minify=True)['BrownieTester']
    expanded_json = testproject._build.expand_build_offsets(
        deepcopy(minified_json))
    for key in ('coverageMap', 'pcMap'):
        assert expanded_json[key] == build_json[key]
        assert minified_json[key] != build_json[key]
Exemple #7
0
def test_expand_build_offsets(testproject, btsource):
    source = {"contracts/BrownieTester.sol": btsource}
    build_json = compiler.compile_and_format(source, "0.5.7")["BrownieTester"]
    minified_json = compiler.compile_and_format(source, "0.5.7",
                                                minify=True)["BrownieTester"]
    expanded_json = testproject._build.expand_build_offsets(
        deepcopy(minified_json))
    for key in ("coverageMap", "pcMap"):
        assert expanded_json[key] == build_json[key]
        assert minified_json[key] != build_json[key]
Exemple #8
0
 def _compile(self, sources: Dict, compiler_config: Dict,
              silent: bool) -> None:
     allow_paths = None
     if self._path is not None:
         allow_paths = self._path.joinpath("contracts").as_posix()
     compiler_config.setdefault("solc", {})
     build_json = compiler.compile_and_format(
         sources,
         solc_version=compiler_config["solc"].get("version", None),
         optimize=compiler_config["solc"].get("optimize", None),
         runs=compiler_config["solc"].get("runs", None),
         evm_version=compiler_config["evm_version"],
         minify=compiler_config["minify_source"],
         silent=silent,
         allow_paths=allow_paths,
     )
     for data in build_json.values():
         if self._path is not None:
             path = self._path.joinpath(
                 f"build/contracts/{data['contractName']}.json")
             with path.open("w") as fp:
                 json.dump(data,
                           fp,
                           sort_keys=True,
                           indent=2,
                           default=sorted)
         self._build._add(data)
Exemple #9
0
    def _compile(self, contract_sources: Dict, compiler_config: Dict,
                 silent: bool) -> None:
        compiler_config.setdefault("solc", {})

        allow_paths = None
        cwd = os.getcwd()
        if self._path is not None:
            _install_dependencies(self._path)
            allow_paths = self._path.as_posix()
            os.chdir(self._path)

        try:
            project_evm_version = compiler_config["evm_version"]
            evm_version = {
                "Solidity":
                compiler_config["solc"].get("evm_version",
                                            project_evm_version),
                "Vyper":
                compiler_config["vyper"].get("evm_version",
                                             project_evm_version),
            }
            build_json = compiler.compile_and_format(
                contract_sources,
                solc_version=compiler_config["solc"].get("version", None),
                vyper_version=compiler_config["vyper"].get("version", None),
                optimize=compiler_config["solc"].get("optimize", None),
                runs=compiler_config["solc"].get("runs", None),
                evm_version=evm_version,
                silent=silent,
                allow_paths=allow_paths,
                remappings=compiler_config["solc"].get("remappings", []),
                optimizer=compiler_config["solc"].get("optimizer", None),
            )
        finally:
            os.chdir(cwd)

        for alias, data in build_json.items():
            if self._build_path is not None and not data[
                    "sourcePath"].startswith("interface"):
                # interfaces should generate artifact in /build/interfaces/ not /build/contracts/
                if alias == data["contractName"]:
                    # if the alias == contract name, this is a part of the core project
                    path = self._build_path.joinpath(f"contracts/{alias}.json")
                else:
                    # otherwise, this is an artifact from an external dependency
                    path = self._build_path.joinpath(
                        f"contracts/dependencies/{alias}.json")
                    for parent in list(path.parents)[::-1]:
                        parent.mkdir(exist_ok=True)
                with path.open("w") as fp:
                    json.dump(data,
                              fp,
                              sort_keys=True,
                              indent=2,
                              default=sorted)

            if alias == data["contractName"]:
                # only add artifacts from the core project for now
                self._build._add_contract(data)
Exemple #10
0
 def _compile(self, sources, compiler_config, silent):
     build_json = compiler.compile_and_format(
         sources,
         solc_version=compiler_config['version'],
         optimize=compiler_config['optimize'],
         runs=compiler_config['runs'],
         evm_version=compiler_config['evm_version'],
         minify=compiler_config['minify_source'],
         silent=silent)
     for data in build_json.values():
         self._build.add(data)
Exemple #11
0
def test_dependencies(vysource):
    code = """
import path as foo
from vyper.interfaces import ERC20
from foo import bar
"""

    build_json = compiler.compile_and_format(
        {"path.vy": vysource, "deps.vy": code, "foo/bar.vy": vysource}
    )
    assert build_json["deps"]["dependencies"] == ["bar", "path"]
Exemple #12
0
 def _compile(self, sources: Dict, compiler_config: Dict, silent: bool) -> None:
     build_json = compiler.compile_and_format(
         sources,
         solc_version=compiler_config["version"],
         optimize=compiler_config["optimize"],
         runs=compiler_config["runs"],
         evm_version=compiler_config["evm_version"],
         minify=compiler_config["minify_source"],
         silent=silent,
     )
     for data in build_json.values():
         self._build.add(data)
Exemple #13
0
def test_multiple_compilers_evm_version_override(solc4source, vysource):
    result = compiler.compile_and_format(
        {
            "solc4.sol": solc4source,
            "vyper.vy": vysource,
            "vyperold.vy": "# @version 0.1.0b16\n",
            "solc6.sol": "pragma solidity 0.6.2; contract Foo {}",
        },
        evm_version={
            "Solidity": "byzantium",
            "Vyper": "petersburg"
        },
    )
    assert result["Bar"]["compiler"]["evm_version"] == "byzantium"
    assert result["vyper"]["compiler"]["evm_version"] == "petersburg"
Exemple #14
0
    def _compile(self, contract_sources: Dict, compiler_config: Dict,
                 silent: bool) -> None:
        compiler_config.setdefault("solc", {})

        allow_paths = None
        cwd = os.getcwd()
        if self._path is not None:
            _install_dependencies(self._path)
            allow_paths = self._path.as_posix()
            os.chdir(self._path)

        try:
            build_json = compiler.compile_and_format(
                contract_sources,
                solc_version=compiler_config["solc"].get("version", None),
                vyper_version=compiler_config["vyper"].get("version", None),
                optimize=compiler_config["solc"].get("optimize", None),
                runs=compiler_config["solc"].get("runs", None),
                evm_version=compiler_config["evm_version"],
                silent=silent,
                allow_paths=allow_paths,
                remappings=compiler_config["solc"].get("remappings", []),
                optimizer=compiler_config["solc"].get("optimizer", None),
            )
        finally:
            os.chdir(cwd)

        for data in build_json.values():
            if self._build_path is not None:
                path = self._build_path.joinpath(
                    f"contracts/{data['contractName']}.json")
                with path.open("w") as fp:
                    json.dump(data,
                              fp,
                              sort_keys=True,
                              indent=2,
                              default=sorted)
            self._build._add_contract(data)
Exemple #15
0
def test_size_limit(capfd):
    code = f"@external\ndef baz():\n    assert msg.sender != ZERO_ADDRESS, '{'blah'*10000}'"
    compiler.compile_and_format({"foo.vy": code}, vyper_version="0.2.4")
    assert "exceeds EIP-170 limit of 24577" in capfd.readouterr()[0]
Exemple #16
0
def test_build_json_keys(solc5source):
    build_json = compiler.compile_and_format({"path": solc5source})
    assert set(build.BUILD_KEYS) == set(build_json["Foo"])
Exemple #17
0
def test_wrong_suffix():
    with pytest.raises(UnsupportedLanguage):
        compiler.compile_and_format({"foo.bar": ""})
Exemple #18
0
def test_size_limit(capfd):
    code = f"@public\ndef baz():\n    assert msg.sender != ZERO_ADDRESS, '{'blah'*10000}'"
    compiler.compile_and_format({"foo.vy": code})
    assert "exceeds EIP-170 limit of 24577" in capfd.readouterr()[0]
Exemple #19
0
def test_compile_empty():
    compiler.compile_and_format({"empty.vy": ""})
Exemple #20
0
def test_build_json_keys(vysource):
    build_json = compiler.compile_and_format({"path.vy": vysource})
    assert set(build.BUILD_KEYS) == set(build_json["path"])
Exemple #21
0
def test_compile_empty():
    compiler.compile_and_format({"empty.sol": ""}, solc_version="0.4.25")
Exemple #22
0
    def from_explorer(cls,
                      address: str,
                      as_proxy_for: Optional[str] = None,
                      owner: Optional[AccountsType] = None) -> "Contract":
        """
        Create a new `Contract` object with source code queried from a block explorer.

        Arguments
        ---------
        address : str
            Address where the contract is deployed.
        as_proxy_for : str, optional
            Address of the implementation contract, if `address` is a proxy contract.
            The generated object will send transactions to `address`, but use the ABI
            and NatSpec of `as_proxy_for`.
        owner : Account, optional
            Contract owner. If set, transactions without a `from` field will be
            performed using this account.
        """
        url = CONFIG.active_network.get("explorer")
        if url is None:
            raise ValueError("Explorer API not set for this network")

        address = _resolve_address(address)
        params: Dict = {
            "module": "contract",
            "action": "getsourcecode",
            "address": address
        }
        if "etherscan" in url:
            if os.getenv("ETHERSCAN_TOKEN"):
                params["apiKey"] = os.getenv("ETHERSCAN_TOKEN")
            else:
                warnings.warn(
                    "No Etherscan API token set. You may experience issues with rate limiting. "
                    "Visit https://etherscan.io/register to obtain a token, and then store it "
                    "as the environment variable $ETHERSCAN_TOKEN")

        print(f"Fetching source of {color('bright blue')}{address}{color} "
              f"from {color('bright blue')}{urlparse(url).netloc}{color}...")
        response = requests.get(url, params=params)

        if response.status_code != 200:
            raise ConnectionError(
                f"Status {response.status_code} when querying {url}: {response.text}"
            )
        data = response.json()
        if int(data["status"]) != 1:
            raise ValueError(
                f"Failed to retrieve data from API: {data['result']}")
        if not data["result"][0].get("SourceCode"):
            raise ValueError(f"Source for {address} has not been verified")

        if as_proxy_for is not None:
            implementation_contract = Contract.from_explorer(as_proxy_for)
            abi = implementation_contract._build["abi"]
        else:
            abi = json.loads(data["result"][0]["ABI"])

        name = data["result"][0]["ContractName"]
        try:
            version = Version(
                data["result"][0]["CompilerVersion"].lstrip("v")).truncate()
        except Exception:
            version = Version("0.0.0")
        if version < Version("0.4.22"):
            warnings.warn(
                f"{address}: target compiler '{data['result'][0]['CompilerVersion']}' is "
                "unsupported by Brownie. Some functionality will not be available."
            )
            return cls.from_abi(name, address, abi)

        sources = {f"{name}-flattened.sol": data["result"][0]["SourceCode"]}
        optimizer = {
            "enabled": bool(int(data["result"][0]["OptimizationUsed"])),
            "runs": int(data["result"][0]["Runs"]),
        }
        build = compile_and_format(sources,
                                   solc_version=str(version),
                                   optimizer=optimizer)
        build = build[name]
        if as_proxy_for is not None:
            build.update(abi=abi,
                         natspec=implementation_contract._build["natspec"])

        if not _verify_deployed_code(address, build["deployedBytecode"],
                                     build["language"]):
            warnings.warn(
                f"{address}: Locally compiled and on-chain bytecode do not match!"
            )
            del build["pcMap"]

        self = cls.__new__(cls)
        _ContractBase.__init__(self, None, build, sources)  # type: ignore
        _DeployedContractBase.__init__(self, address, owner)
        _add_deployment(self)
        return self
Exemple #23
0
def test_build_json_keys():
    build_json = compiler.compile_and_format({'path': _solc_5_source()})
    assert set(build.BUILD_KEYS) == set(build_json['TempTester'])
Exemple #24
0
def test_build_json_unlinked_libraries():
    build_json = compiler.compile_and_format({'path': _solc_5_source()})
    assert '__TestLib__' in build_json['TempTester']['bytecode']
    compiler.set_solc_version("v0.4.25")
    build_json = compiler.compile_and_format({'path': _solc_4_source()})
    assert '__TestLib__' in build_json['TempTester']['bytecode']
Exemple #25
0
def test_build_json_unlinked_libraries(solc4source, solc5source):
    build_json = compiler.compile_and_format({'path': solc5source}, solc_version="0.5.7")
    assert '__Bar__' in build_json['Foo']['bytecode']
    build_json = compiler.compile_and_format({'path': solc4source}, solc_version="0.4.25")
    assert '__Bar__' in build_json['Foo']['bytecode']
Exemple #26
0
def test_compile_empty():
    compiler.compile_and_format({"empty.vy": ""}, vyper_version="0.2.4")
Exemple #27
0
def test_compiler_errors():
    with pytest.raises(CompilerError):
        compiler.compile_and_format({'path': _solc_4_source()})
    solcx.set_solc_version('0.4.25')
    with pytest.raises(CompilerError):
        compiler.compile_and_format({'path': _solc_5_source()})
Exemple #28
0
    def from_explorer(
        cls,
        address: str,
        as_proxy_for: Optional[str] = None,
        owner: Optional[AccountsType] = None,
        silent: bool = False,
    ) -> "Contract":
        """
        Create a new `Contract` object with source code queried from a block explorer.

        Arguments
        ---------
        address : str
            Address where the contract is deployed.
        as_proxy_for : str, optional
            Address of the implementation contract, if `address` is a proxy contract.
            The generated object will send transactions to `address`, but use the ABI
            and NatSpec of `as_proxy_for`. This field is only required when the
            block explorer API does not provide an implementation address.
        owner : Account, optional
            Contract owner. If set, transactions without a `from` field will be
            performed using this account.
        """
        address = _resolve_address(address)
        data = _fetch_from_explorer(address, "getsourcecode", silent)
        is_verified = bool(data["result"][0].get("SourceCode"))

        if is_verified:
            abi = json.loads(data["result"][0]["ABI"])
            name = data["result"][0]["ContractName"]
        else:
            # if the source is not available, try to fetch only the ABI
            try:
                data_abi = _fetch_from_explorer(address, "getabi", True)
            except ValueError as exc:
                _unverified_addresses.add(address)
                raise exc
            abi = json.loads(data_abi["result"].strip())
            name = "UnknownContractName"
            warnings.warn(
                f"{address}: Was able to fetch the ABI but not the source code. "
                "Some functionality will not be available.",
                BrownieCompilerWarning,
            )

        if as_proxy_for is None and data["result"][0].get("Implementation"):
            as_proxy_for = _resolve_address(
                data["result"][0]["Implementation"])

        # if this is a proxy, fetch information for the implementation contract
        if as_proxy_for is not None:
            implementation_contract = Contract.from_explorer(as_proxy_for)
            abi = implementation_contract._build["abi"]

        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()):
            if not silent:
                warnings.warn(
                    f"{address}: target compiler '{data['result'][0]['CompilerVersion']}' is "
                    "unsupported by Brownie. Some functionality will not be available.",
                    BrownieCompilerWarning,
                )
            return cls.from_abi(name, address, abi, owner)

        optimizer = {
            "enabled": bool(int(data["result"][0]["OptimizationUsed"])),
            "runs": int(data["result"][0]["Runs"]),
        }
        evm_version = data["result"][0].get("EVMVersion", "Default")
        if evm_version == "Default":
            evm_version = None

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

            compiler.set_solc_version(str(version))
            input_json.update(
                compiler.generate_input_json(sources,
                                             optimizer=optimizer,
                                             evm_version=evm_version))
            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"]
            }
            build_json = compiler.compile_and_format(sources,
                                                     solc_version=str(version),
                                                     optimizer=optimizer,
                                                     evm_version=evm_version)

        build_json = build_json[name]
        if as_proxy_for is not None:
            build_json.update(
                abi=abi, natspec=implementation_contract._build.get("natspec"))

        if not _verify_deployed_code(address, build_json["deployedBytecode"],
                                     build_json["language"]):
            warnings.warn(
                f"{address}: Locally compiled and on-chain bytecode do not match!",
                BrownieCompilerWarning,
            )
            del build_json["pcMap"]

        self = cls.__new__(cls)
        _ContractBase.__init__(self, None, build_json, sources)  # type: ignore
        _DeployedContractBase.__init__(self, address, owner)
        _add_deployment(self)
        return self
Exemple #29
0
    def from_explorer(
        cls,
        address: str,
        as_proxy_for: Optional[str] = None,
        owner: Optional[AccountsType] = None,
        silent: bool = False,
    ) -> "Contract":
        """
        Create a new `Contract` object with source code queried from a block explorer.

        Arguments
        ---------
        address : str
            Address where the contract is deployed.
        as_proxy_for : str, optional
            Address of the implementation contract, if `address` is a proxy contract.
            The generated object will send transactions to `address`, but use the ABI
            and NatSpec of `as_proxy_for`.
        owner : Account, optional
            Contract owner. If set, transactions without a `from` field will be
            performed using this account.
        """
        address = _resolve_address(address)
        data = _fetch_from_explorer(address, "getsourcecode", silent)
        is_verified = bool(data["result"][0].get("SourceCode"))

        if is_verified:
            abi = json.loads(data["result"][0]["ABI"])
            name = data["result"][0]["ContractName"]
        else:
            # if the source is not available, try to fetch only the ABI
            try:
                data = _fetch_from_explorer(address, "getabi", True)
            except ValueError as exc:
                _unverified_addresses.add(address)
                raise exc
            abi = json.loads(data["result"].strip())
            name = "UnknownContractName"
            warnings.warn(
                f"{address}: Was able to fetch the ABI but not the source code. "
                "Some functionality will not be available.",
                BrownieCompilerWarning,
            )

        # if this is a proxy, fetch information for the implementation contract
        if as_proxy_for is not None:
            implementation_contract = Contract.from_explorer(as_proxy_for)
            abi = implementation_contract._build["abi"]

        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"):
            if not silent:
                warnings.warn(
                    f"{address}: target compiler '{data['result'][0]['CompilerVersion']}' is "
                    "unsupported by Brownie. Some functionality will not be available.",
                    BrownieCompilerWarning,
                )
            return cls.from_abi(name, address, abi, owner)

        sources = {f"{name}-flattened.sol": data["result"][0]["SourceCode"]}
        optimizer = {
            "enabled": bool(int(data["result"][0]["OptimizationUsed"])),
            "runs": int(data["result"][0]["Runs"]),
        }
        build = compile_and_format(sources,
                                   solc_version=str(version),
                                   optimizer=optimizer)
        build = build[name]
        if as_proxy_for is not None:
            build.update(abi=abi,
                         natspec=implementation_contract._build.get("natspec"))

        if not _verify_deployed_code(address, build["deployedBytecode"],
                                     build["language"]):
            warnings.warn(
                f"{address}: Locally compiled and on-chain bytecode do not match!",
                BrownieCompilerWarning,
            )
            del build["pcMap"]

        self = cls.__new__(cls)
        _ContractBase.__init__(self, None, build, sources)  # type: ignore
        _DeployedContractBase.__init__(self, address, owner)
        _add_deployment(self)
        return self