Esempio n. 1
0
def test_unknown_language():
    with pytest.raises(UnsupportedLanguage):
        compiler.generate_input_json({"foo": ""}, language="Bar")
    with pytest.raises(UnsupportedLanguage):
        compiler.compile_from_input_json({"language": "FooBar"})
    with pytest.raises(UnsupportedLanguage):
        compiler.generate_build_json({"language": "BarBaz"}, {})
Esempio n. 2
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