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"]
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")
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 {}", })
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]
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]
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]
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)
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)
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)
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"]
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)
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"
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)
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]
def test_build_json_keys(solc5source): build_json = compiler.compile_and_format({"path": solc5source}) assert set(build.BUILD_KEYS) == set(build_json["Foo"])
def test_wrong_suffix(): with pytest.raises(UnsupportedLanguage): compiler.compile_and_format({"foo.bar": ""})
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]
def test_compile_empty(): compiler.compile_and_format({"empty.vy": ""})
def test_build_json_keys(vysource): build_json = compiler.compile_and_format({"path.vy": vysource}) assert set(build.BUILD_KEYS) == set(build_json["path"])
def test_compile_empty(): compiler.compile_and_format({"empty.sol": ""}, solc_version="0.4.25")
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
def test_build_json_keys(): build_json = compiler.compile_and_format({'path': _solc_5_source()}) assert set(build.BUILD_KEYS) == set(build_json['TempTester'])
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']
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']
def test_compile_empty(): compiler.compile_and_format({"empty.vy": ""}, vyper_version="0.2.4")
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()})
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
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