diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a804b76a..a32d078a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/eth-brownie/brownie) ### Changed - Force files to be opened as UTF-8 +- Added a new solidity compiler setting `use_latest_patch` in brownie-config.yaml to use the latest patch version of a compiler based on the pragma version of the contract. - Add cli flag `-r` for raising exceptions to the caller instead of doing a system exit. ## [1.17.2](https://github.com/eth-brownie/brownie/tree/v1.17.2) - 2021-12-04 diff --git a/brownie/network/contract.py b/brownie/network/contract.py index e1dfc80ca..4519d8b9b 100644 --- a/brownie/network/contract.py +++ b/brownie/network/contract.py @@ -21,7 +21,7 @@ from vvm import get_installable_vyper_versions from vvm.utils.convert import to_vyper_version -from brownie._config import BROWNIE_FOLDER, CONFIG, REQUEST_HEADERS +from brownie._config import BROWNIE_FOLDER, CONFIG, REQUEST_HEADERS, _load_project_compiler_config from brownie.convert.datatypes import Wei from brownie.convert.normalize import format_input, format_output from brownie.convert.utils import ( @@ -1088,7 +1088,8 @@ def from_explorer( is_compilable = False else: try: - version = Version(compiler_str.lstrip("v")).truncate() + version = cls.get_solc_version(compiler_str, address) + is_compilable = ( version >= Version("0.4.22") and version @@ -1174,6 +1175,39 @@ def from_explorer( _add_deployment(self) return self + @classmethod + def get_solc_version(cls, compiler_str: str, address: str) -> Version: + """ + Return the solc compiler version either from the passed compiler string + or try to find the latest available patch semver compiler version. + + Arguments + --------- + compiler_str: str + The compiler string passed from the contract metadata. + address: str + The contract address to check for. + """ + version = Version(compiler_str.lstrip("v")).truncate() + + compiler_config = _load_project_compiler_config(Path(os.getcwd())) + solc_config = compiler_config["solc"] + if "use_latest_patch" in solc_config: + use_latest_patch = solc_config["use_latest_patch"] + needs_patch_version = False + if isinstance(use_latest_patch, bool): + needs_patch_version = use_latest_patch + elif isinstance(use_latest_patch, list): + needs_patch_version = address in use_latest_patch + + if needs_patch_version: + versions = [Version(str(i)) for i in solcx.get_installable_solc_versions()] + for v in filter(lambda l: l < version.next_minor(), versions): + if v > version: + version = v + + return version + def set_alias(self, alias: Optional[str]) -> None: """ Apply a unique alias this object. The alias can be used to restore the diff --git a/docs/config.rst b/docs/config.rst index 532f4f6c5..1d55d8c8c 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -222,6 +222,23 @@ Compiler settings. See :ref:`compiler settings` for more infor - zeppelin=/usr/local/lib/open-zeppelin/contracts/ - github.com/ethereum/dapp-bin/=/usr/local/lib/dapp-bin/ + .. py:attribute:: use_latest_patch + + Optional boolean or array contract list to use the latest patch semver compiler version. E.g. the if the contract has pragma version `0.4.16` and the latest available patch for `0.4` is `0.4.22` it will use this instead for compilations. + + Enable for all contracts: + .. code-block:: yaml + compiler: + solc: + use_latest_patch: true + + Enable for only specific contracts: + .. code-block:: yaml + compiler: + solc: + use_latest_patch: + - '0x514910771AF9Ca656af840dff83E8264EcF986CA' + .. py:attribute:: compiler.vyper Settings specific to the Vyper compiler. diff --git a/tests/network/contract/test_contract.py b/tests/network/contract/test_contract.py index f0ebc6740..c440fc520 100644 --- a/tests/network/contract/test_contract.py +++ b/tests/network/contract/test_contract.py @@ -3,6 +3,8 @@ import pytest import requests +import yaml +from semantic_version import Version from brownie import Wei from brownie.exceptions import BrownieCompilerWarning, BrownieEnvironmentWarning, ContractNotFound @@ -308,6 +310,65 @@ def test_as_proxy_for(network): assert proxy.address != implementation.address +def test_solc_use_latest_patch_true(testproject, network): + network.connect("mainnet") + solc_config = {"compiler": {"solc": {"use_latest_patch": True}}} + with testproject._path.joinpath("brownie-config.yaml").open("w") as fp: + yaml.dump(solc_config, fp) + + assert Contract.get_solc_version( + "v0.4.16", "0x514910771AF9Ca656af840dff83E8264EcF986CA" + ) == Version("0.4.26") + + +def test_solc_use_latest_patch_false(testproject, network): + network.connect("mainnet") + solc_config = {"compiler": {"solc": {"use_latest_patch": False}}} + with testproject._path.joinpath("brownie-config.yaml").open("w") as fp: + yaml.dump(solc_config, fp) + + assert Contract.get_solc_version( + "v0.4.16", "0x514910771AF9Ca656af840dff83E8264EcF986CA" + ) == Version("0.4.16") + + +def test_solc_use_latest_patch_missing(testproject, network): + network.connect("mainnet") + solc_config = {"compiler": {"solc": {}}} + with testproject._path.joinpath("brownie-config.yaml").open("w") as fp: + yaml.dump(solc_config, fp) + + assert Contract.get_solc_version( + "v0.4.16", "0x514910771AF9Ca656af840dff83E8264EcF986CA" + ) == Version("0.4.16") + + +def test_solc_use_latest_patch_specific_not_included(testproject, network): + network.connect("mainnet") + solc_config = { + "compiler": {"solc": {"use_latest_patch": ["0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e"]}} + } + with testproject._path.joinpath("brownie-config.yaml").open("w") as fp: + yaml.dump(solc_config, fp) + + assert Contract.get_solc_version( + "v0.4.16", "0x514910771AF9Ca656af840dff83E8264EcF986CA" + ) == Version("0.4.16") + + +def test_solc_use_latest_patch_specific_included(testproject, network): + network.connect("mainnet") + solc_config = { + "compiler": {"solc": {"use_latest_patch": ["0x514910771AF9Ca656af840dff83E8264EcF986CA"]}} + } + with testproject._path.joinpath("brownie-config.yaml").open("w") as fp: + yaml.dump(solc_config, fp) + + assert Contract.get_solc_version( + "v0.4.16", "0x514910771AF9Ca656af840dff83E8264EcF986CA" + ) == Version("0.4.26") + + # @pytest.mark.parametrize( # "original", # [