diff --git a/.github/workflows/core-ganache-3.10.yaml b/.github/workflows/core-ganache-3.10.yaml index 8073c62cc..d4164c4f2 100644 --- a/.github/workflows/core-ganache-3.10.yaml +++ b/.github/workflows/core-ganache-3.10.yaml @@ -1,6 +1,11 @@ name: Core Ganache (py3.10) on: ["push", "pull_request"] +# This limits the workflow to 1 active run at any given time for a specific branch +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: ETHERSCAN_TOKEN: 9MKURTHE8FNA9NRUUJBHMUEVY6IQ5K1EGY GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/core-ganache-3.11.yaml b/.github/workflows/core-ganache-3.11.yaml index bb8775fe5..2536cb39b 100644 --- a/.github/workflows/core-ganache-3.11.yaml +++ b/.github/workflows/core-ganache-3.11.yaml @@ -1,6 +1,11 @@ name: Core Ganache (py3.11) on: ["push", "pull_request"] +# This limits the workflow to 1 active run at any given time for a specific branch +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: ETHERSCAN_TOKEN: 9MKURTHE8FNA9NRUUJBHMUEVY6IQ5K1EGY GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/core-ganache-3.12.yaml b/.github/workflows/core-ganache-3.12.yaml index 158fac1fa..60a3b04d9 100644 --- a/.github/workflows/core-ganache-3.12.yaml +++ b/.github/workflows/core-ganache-3.12.yaml @@ -1,6 +1,11 @@ name: Core Ganache (py3.12) on: ["push", "pull_request"] +# This limits the workflow to 1 active run at any given time for a specific branch +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: ETHERSCAN_TOKEN: 9MKURTHE8FNA9NRUUJBHMUEVY6IQ5K1EGY GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/evm.yaml b/.github/workflows/evm.yaml index e364b3f21..f5ed5071e 100644 --- a/.github/workflows/evm.yaml +++ b/.github/workflows/evm.yaml @@ -2,6 +2,11 @@ on: ["push", "pull_request"] name: evm tests +# This limits the workflow to 1 active run at any given time for a specific branch +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: ETHERSCAN_TOKEN: 9MKURTHE8FNA9NRUUJBHMUEVY6IQ5K1EGY GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/functionality.yaml b/.github/workflows/functionality.yaml index 34ec13af4..1494b6da2 100644 --- a/.github/workflows/functionality.yaml +++ b/.github/workflows/functionality.yaml @@ -2,6 +2,11 @@ on: ["push", "pull_request"] name: functionality +# This limits the workflow to 1 active run at any given time for a specific branch +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: ETHERSCAN_TOKEN: 9MKURTHE8FNA9NRUUJBHMUEVY6IQ5K1EGY GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 6a89de0b3..774df0ea8 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -2,6 +2,11 @@ on: ["push", "pull_request"] name: linting +# This limits the workflow to 1 active run at any given time for a specific branch +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: ETHERSCAN_TOKEN: 9MKURTHE8FNA9NRUUJBHMUEVY6IQ5K1EGY GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pip-compile.yaml b/.github/workflows/pip-compile.yaml new file mode 100644 index 000000000..f8d52f9ab --- /dev/null +++ b/.github/workflows/pip-compile.yaml @@ -0,0 +1,49 @@ +name: Pip Compile + +on: + pull_request: + branches: + - master + paths: + - 'requirements.in' + - 'requirements-windows.in' + - 'requirements-dev.in' + +jobs: + format: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + ref: ${{ github.head_ref }} # Check out the PR branch + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install Pip Tools + run: pip install pip-tools + + - name: Run Pip Compile + run: pip-compile + + - name: Check for changes + id: changes + run: | + if [[ -n $(git status --porcelain) ]]; then + echo "changes_detected=true" >> $GITHUB_ENV + else + echo "changes_detected=false" >> $GITHUB_ENV + fi + + - name: Commit changes + if: env.changes_detected == 'true' + run: | + git config --local user.name "github-actions[bot]" + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git add . + git commit -m "chore: \`pip-compile\`" + git push diff --git a/CHANGELOG.md b/CHANGELOG.md index 6473f5b86..f8de38dbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,32 @@ This changelog format is based on [Keep a Changelog](https://keepachangelog.com/ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased](https://github.com/eth-brownie/brownie) +### Changed +- replace `eth_utils.to_checksum_address` with `cchecksum.to_checksum_address` for ~2x faster checksumming ([#1796](https://github.com/eth-brownie/brownie/pull/1796)) + +## [1.20.7](https://github.com/eth-brownie/brownie/tree/v1.20.7) - 2025-01-07 +### Added +- Support for vyper `0.4.0` ([#1793](https://github.com/eth-brownie/brownie/pull/1793)) +- `py.typed` marker ([#1794](https://github.com/eth-brownie/brownie/pull/1794)) + +### Fixed +- Improvements to caching ([#1786](https://github.com/eth-brownie/brownie/pull/1786)) + +### Removed +- `tqdm` progress bars during compiler installation ([#1785](https://github.com/eth-brownie/brownie/pull/1785)) + +## [1.20.6](https://github.com/eth-brownie/brownie/tree/v1.20.6) - 2024-06-22 +### Added +- `include` kwarg for address strategy, type-dependent strategy overloads ([#1780](https://github.com/eth-brownie/brownie/pull/1780)) + +### Fixed +- ds-note decoding ([#1781](https://github.com/eth-brownie/brownie/pull/1781)) +- "Dropped without known replacement" tx race condition ([#1782](https://github.com/eth-brownie/brownie/pull/1782)) + +## [1.20.5](https://github.com/eth-brownie/brownie/tree/v1.20.5) - 2024-05-22 +### Fixed +- Handle missing `blockNumber` while awaiting confirmation ([#1774](https://github.com/eth-brownie/brownie/pull/1774)) +- Search parent paths for file import during source verification ([#1776](https://github.com/eth-brownie/brownie/pull/1776)) ## [1.20.4](https://github.com/eth-brownie/brownie/tree/v1.20.4) - 2024-05-08 ### Fixed @@ -102,7 +128,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.19.0](https://github.com/eth-brownie/brownie/tree/v1.19.0) - 2022-05-29 ### Added -- Initial support for [Anvil](https://github.com/foundry-rs/foundry/tree/master/anvil), a blazing-fast local testnet node implementation in Rust ([#1541](https://github.com/eth-brownie/brownie/pull/1541)) +- Initial support for [Anvil](https://github.com/foundry-rs/foundry/tree/master/crates/anvil), a blazing-fast local testnet node implementation in Rust ([#1541](https://github.com/eth-brownie/brownie/pull/1541)) - Support configurable initial wallet balance, chain id, and gas limit. ### Fixed diff --git a/README.md b/README.md index ba2372975..859192a89 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ To upgrade to the latest version: pipx upgrade eth-brownie ``` -To use lastest master or another branch as version: +To use latest master or another branch as version: ```bash pipx install git+https://github.com/eth-brownie/brownie.git@master ``` diff --git a/brownie/_config.py b/brownie/_config.py index f17b9a02f..2e378fccb 100644 --- a/brownie/_config.py +++ b/brownie/_config.py @@ -19,7 +19,7 @@ from brownie._expansion import expand_posix_vars from brownie._singleton import _Singleton -__version__ = "1.20.4" +__version__ = "1.20.7" BROWNIE_FOLDER = Path(__file__).parent DATA_FOLDER = Path.home().joinpath(".brownie") diff --git a/brownie/convert/datatypes.py b/brownie/convert/datatypes.py index 3209258a2..202e17d39 100644 --- a/brownie/convert/datatypes.py +++ b/brownie/convert/datatypes.py @@ -9,6 +9,7 @@ except ImportError: DecimalOverrideException = BaseException # regular catch blocks shouldn't catch +import cchecksum import eth_utils from hexbytes import HexBytes @@ -205,7 +206,7 @@ def __new__(cls, value: Union[bytes, str]) -> str: converted_value = HexBytes(value).hex() converted_value = eth_utils.add_0x_prefix(str(converted_value)) # type: ignore try: - converted_value = eth_utils.to_checksum_address(converted_value) + converted_value = cchecksum.to_checksum_address(converted_value) except ValueError: raise ValueError(f"'{value}' is not a valid ETH address") from None return super().__new__(cls, converted_value) # type: ignore diff --git a/brownie/network/contract.py b/brownie/network/contract.py index 1a060eb90..c6f996b72 100644 --- a/brownie/network/contract.py +++ b/brownie/network/contract.py @@ -708,7 +708,9 @@ def __init__( self, address: str, owner: Optional[AccountsType] = None, tx: TransactionReceiptType = None ) -> None: address = _resolve_address(address) - self.bytecode = web3.eth.get_code(address).hex()[2:] + self.bytecode = ( + self._build.get("deployedBytecode", None) or web3.eth.get_code(address).hex()[2:] + ) if not self.bytecode: raise ContractNotFound(f"No contract deployed at {address}") self._owner = owner @@ -948,7 +950,13 @@ def from_abi( will be performed using this account. """ address = _resolve_address(address) - build = {"abi": abi, "address": address, "contractName": name, "type": "contract"} + build = { + "abi": abi, + "address": address, + "contractName": name, + "type": "contract", + "deployedBytecode": web3.eth.get_code(address).hex()[2:], + } self = cls.__new__(cls) _ContractBase.__init__(self, None, build, {}) # type: ignore @@ -1258,7 +1266,7 @@ def subscribe( ) -> None: """ Subscribe to event with a name matching 'event_name', calling the 'callback' - function on new occurence giving as parameter the event log receipt. + function on new occurrence giving as parameter the event log receipt. Args: event_name (str): Name of the event to subscribe to. @@ -1285,10 +1293,10 @@ def get_sequence( Returns: if 'event_type' is specified: - [list]: List of events of type 'event_type' that occured between + [list]: List of events of type 'event_type' that occurred between 'from_block' and 'to_block'. else: - event_logbook [dict]: Dictionnary of events of the contract that occured + event_logbook [dict]: Dictionary of events of the contract that occurred between 'from_block' and 'to_block'. """ if to_block is None or to_block > web3.eth.block_number: @@ -1315,7 +1323,7 @@ def listen(self, event_name: str, timeout: float = 0) -> Coroutine: 'event_name' occurs. If timeout is superior to zero and no event matching 'event_name' has occured, the Coroutine ends when the timeout is reached. - The Coroutine return value is an AttributeDict filled with the following fileds : + The Coroutine return value is an AttributeDict filled with the following fields : - 'event_data' (AttributeDict): The event log receipt that was caught. - 'timed_out' (bool): False if the event did not timeout, else True diff --git a/brownie/network/event.py b/brownie/network/event.py index 437a0658a..ed0c6fd39 100644 --- a/brownie/network/event.py +++ b/brownie/network/event.py @@ -502,7 +502,7 @@ def _decode_ds_note(log, contract): # type: ignore if selector.hex() not in contract.selectors or sum(tail): return name = contract.selectors[selector.hex()] - data = bytes.fromhex(log.data[2:]) + data = bytes.fromhex(log.data[2:]) if isinstance(log.data, str) else log.data # data uses ABI encoding of [uint256, bytes] or [bytes] in different versions # instead of trying them all, assume the payload starts from selector try: diff --git a/brownie/network/middlewares/geth_poa.py b/brownie/network/middlewares/geth_poa.py index b86265d6a..6f232c42c 100644 --- a/brownie/network/middlewares/geth_poa.py +++ b/brownie/network/middlewares/geth_poa.py @@ -10,9 +10,12 @@ class GethPOAMiddleware(BrownieMiddlewareABC): @classmethod def get_layer(cls, w3: Web3, network_type: str) -> Optional[int]: - block_ident = "latest" if network_type == "live" else 0 + # NOTE: 0 because Ganache sometimes injects a block of their own. It doesn't have the extra data that we are checking for. + # "latest" because On Polygon networks, anvil in forked development mode doesn't throw ExtraDataLengthError on the first block. + block_idents = ("latest",) if network_type == "live" else (0, "latest") try: - w3.eth.get_block(block_ident) + for block_ident in block_idents: + w3.eth.get_block(block_ident) return None except ExtraDataLengthError: return -1 diff --git a/brownie/network/state.py b/brownie/network/state.py index e73f8b0a4..fde7951a8 100644 --- a/brownie/network/state.py +++ b/brownie/network/state.py @@ -632,6 +632,12 @@ def _add_deployment(contract: Any, alias: Optional[str] = None) -> None: f"(address UNIQUE, alias UNIQUE, paths, {', '.join(DEPLOYMENT_KEYS)})" ) + if "compiler" not in contract._build: + # do not replace full contract artifacts with ABI-only ones + row = cur.fetchone(f"SELECT compiler FROM {name} WHERE address=?", (address,)) + if row and row[0]: + return + all_sources = {} for key, path in contract._build.get("allSourcePaths", {}).items(): source = contract._sources.get(path) diff --git a/brownie/network/transaction.py b/brownie/network/transaction.py index 9e561f65e..4175378bc 100644 --- a/brownie/network/transaction.py +++ b/brownie/network/transaction.py @@ -216,10 +216,12 @@ def __init__( # await confirmation of tx in a separate thread which is blocking if # required_confs > 0 or tx has already confirmed (`blockNumber` != None) confirm_thread = threading.Thread( - target=self._await_confirmation, args=(tx["blockNumber"], required_confs), daemon=True + target=self._await_confirmation, + args=(tx.get("blockNumber"), required_confs), + daemon=True, ) confirm_thread.start() - if is_blocking and (required_confs > 0 or tx["blockNumber"]): + if is_blocking and (required_confs > 0 or tx.get("blockNumber")): confirm_thread.join() def __repr__(self) -> str: @@ -403,17 +405,22 @@ def wait(self, required_confs: int) -> None: print(f"This transaction already has {self.confirmations} confirmations.") return + if self.nonce is not None: + # if we know the transaction nonce, it's more efficient to watch the tx count + # this (i hope) also fixes a longstanding bug that sometimes gave an incorrect + # "tx dropped without known replacement" error due to a race condition + while web3.eth.get_transaction_count(str(self.sender)) <= self.nonce: + time.sleep(1) + while True: try: tx: Dict = web3.eth.get_transaction(self.txid) break except TransactionNotFound: if self.nonce is not None: - sender_nonce = web3.eth.get_transaction_count(str(self.sender)) - if sender_nonce > self.nonce: - self.status = Status(-2) - self._confirmed.set() - return + self.status = Status(-2) + self._confirmed.set() + return time.sleep(1) self._await_confirmation(tx["blockNumber"], required_confs) @@ -508,7 +515,7 @@ def _await_confirmation(self, block_number: int = None, required_confs: int = 1) # check if tx is still in mempool, this will raise otherwise tx = web3.eth.get_transaction(self.txid) self.block_number = None - return self._await_confirmation(tx["blockNumber"], required_confs) + return self._await_confirmation(tx.get("blockNumber"), required_confs) if required_confs - self.confirmations != remaining_confs: remaining_confs = required_confs - self.confirmations if not self._silent: diff --git a/brownie/project/compiler/solidity.py b/brownie/project/compiler/solidity.py index a1b3d09e3..8fd48449f 100644 --- a/brownie/project/compiler/solidity.py +++ b/brownie/project/compiler/solidity.py @@ -93,7 +93,7 @@ def set_solc_version(version: Union[str, Version]) -> str: def install_solc(*versions: Union[Version, str]) -> None: """Installs solc versions.""" for version in versions: - solcx.install_solc(version, show_progress=True) + solcx.install_solc(version, show_progress=False) def get_abi(contract_source: str, allow_paths: Optional[str] = None) -> Dict: diff --git a/brownie/project/compiler/utils.py b/brownie/project/compiler/utils.py index 7c0627094..98c947252 100644 --- a/brownie/project/compiler/utils.py +++ b/brownie/project/compiler/utils.py @@ -8,6 +8,13 @@ def expand_source_map(source_map_str: str) -> List: # Expands the compressed sourceMap supplied by solc into a list of lists + + if isinstance(source_map_str, dict): + # NOTE: vyper >= 0.4 gives us a dict that contains the source map + source_map_str = source_map_str["pc_pos_map_compressed"] + if not isinstance(source_map_str, str): + raise TypeError(source_map_str) + source_map: List = [_expand_row(i) if i else None for i in source_map_str.split(";")] for i, value in enumerate(source_map[1:], 1): if value is None: diff --git a/brownie/project/compiler/vyper.py b/brownie/project/compiler/vyper.py index 3448f6ee4..8b93d5933 100644 --- a/brownie/project/compiler/vyper.py +++ b/brownie/project/compiler/vyper.py @@ -7,6 +7,7 @@ import vvm import vyper +from packaging.version import Version as PVersion from requests.exceptions import ConnectionError from semantic_version import Version from vyper.cli import vyper_json @@ -46,11 +47,14 @@ def set_vyper_version(version: Union[str, Version]) -> str: if isinstance(version, str): version = Version(version) if version != Version(vyper.__version__): + # NOTE: vvm uses `packaging.version.Version` which is not compatible with + # `semantic_version.Version` so we first must cast it as a string + version_str = str(version) try: - vvm.set_vyper_version(version, silent=True) + vvm.set_vyper_version(version_str, silent=True) except vvm.exceptions.VyperNotInstalled: install_vyper(version) - vvm.set_vyper_version(version, silent=True) + vvm.set_vyper_version(version_str, silent=True) _active_version = version return str(_active_version) @@ -73,7 +77,7 @@ def get_abi(contract_source: str, name: str) -> Dict: raise exc.with_traceback(None) else: try: - compiled = vvm.compile_standard(input_json, vyper_version=_active_version) + compiled = vvm.compile_standard(input_json, vyper_version=str(_active_version)) except vvm.exceptions.VyperError as exc: raise CompilerError(exc, "vyper") @@ -82,13 +86,13 @@ def get_abi(contract_source: str, name: str) -> Dict: def _get_vyper_version_list() -> Tuple[List, List]: global AVAILABLE_VYPER_VERSIONS - installed_versions = vvm.get_installed_vyper_versions() + installed_versions = _convert_to_semver(vvm.get_installed_vyper_versions()) lib_version = Version(vyper.__version__) if lib_version not in installed_versions: installed_versions.append(lib_version) if AVAILABLE_VYPER_VERSIONS is None: try: - AVAILABLE_VYPER_VERSIONS = vvm.get_installable_vyper_versions() + AVAILABLE_VYPER_VERSIONS = _convert_to_semver(vvm.get_installable_vyper_versions()) except ConnectionError: if not installed_versions: raise ConnectionError("Vyper not installed and cannot connect to GitHub") @@ -99,7 +103,7 @@ def _get_vyper_version_list() -> Tuple[List, List]: def install_vyper(*versions: str) -> None: """Installs vyper versions.""" for version in versions: - vvm.install_vyper(version, show_progress=True) + vvm.install_vyper(str(version), show_progress=False) def find_vyper_versions( @@ -146,7 +150,7 @@ def find_vyper_versions( ) if not version or (install_latest and latest > version): - to_install.add(latest) + to_install.add(str(latest)) elif latest and latest > version: new_versions.add(str(version)) @@ -237,11 +241,14 @@ def compile_from_input_json( outputs.remove("devdoc") if version == Version(vyper.__version__): try: - return vyper_json.compile_json(input_json, root_path=allow_paths) + return vyper_json.compile_json(input_json) except VyperException as exc: raise exc.with_traceback(None) else: try: + # NOTE: vvm uses `packaging.version.Version` which is not compatible with + # `semantic_version.Version` so we first must cast it as a string + version = str(version) return vvm.compile_standard(input_json, base_path=allow_paths, vyper_version=version) except vvm.exceptions.VyperError as exc: raise CompilerError(exc, "vyper") @@ -397,7 +404,7 @@ def _generate_coverage_data( if node["ast_type"] in ("Assert", "If") or ( node["ast_type"] == "Expr" - and node["value"]["func"].get("id", None) == "assert_modifiable" + and node["value"].get("func", {}).get("id", None) == "assert_modifiable" ): # branch coverage pc_list[-1]["branch"] = count @@ -449,3 +456,24 @@ def _get_statement_nodes(ast_json: List) -> List: else: stmt_nodes.append(node) return stmt_nodes + + +def _convert_to_semver(versions: List[PVersion]) -> List[Version]: + """ + Converts a list of `packaging.version.Version` objects to a list of + `semantic_version.Version` objects. + + vvm 0.2.0 switched to packaging.version but we are not ready to + migrate brownie off of semantic-version. + + This function serves as a stopgap. + """ + return [ + Version( + major=version.major, + minor=version.minor, + patch=version.micro, + prerelease="".join(str(x) for x in version.pre) if version.pre else None, + ) + for version in versions + ] diff --git a/brownie/project/flattener.py b/brownie/project/flattener.py index 2d77bf5d0..08f2650cd 100644 --- a/brownie/project/flattener.py +++ b/brownie/project/flattener.py @@ -87,7 +87,7 @@ def flattened_source(self) -> str: # all pragma statements, we already have the license used + know which compiler # version is used via the build info pragmas = set((match.strip() for src in sources for match in PRAGMA_PATTERN.findall(src))) - # now we go thorugh and remove all imports/pragmas/license stuff + # now we go through and remove all imports/pragmas/license stuff wipe = lambda src: PRAGMA_PATTERN.sub( # noqa: E731 "", LICENSE_PATTERN.sub("", IMPORT_PATTERN.sub("", src)) ) @@ -147,4 +147,11 @@ def make_import_absolute(import_path: str, source_file_dir: str) -> str: if path.is_absolute(): return path.as_posix() - return (Path(source_file_dir) / path).resolve().as_posix() + source_file_dir = Path(source_file_dir).resolve() + newpath = (source_file_dir / path).resolve() + while not newpath.exists(): + source_file_dir = source_file_dir.parent + newpath = (source_file_dir / path).resolve() + if source_file_dir == Path("/"): + raise FileNotFoundError(f"Cannot determine location of {import_path}") + return newpath.as_posix() diff --git a/brownie/project/main.py b/brownie/project/main.py index d0138b055..a0d0d3b78 100644 --- a/brownie/project/main.py +++ b/brownie/project/main.py @@ -811,7 +811,7 @@ def _install_from_github(package_id: str) -> str: install_path.mkdir(exist_ok=True) install_path = install_path.joinpath(f"{repo}@{version}") if install_path.exists(): - raise FileExistsError("Package is aleady installed") + raise FileExistsError("Package is already installed") headers = REQUEST_HEADERS.copy() headers.update(_maybe_retrieve_github_auth()) diff --git a/brownie/py.typed b/brownie/py.typed new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/brownie/py.typed @@ -0,0 +1 @@ + diff --git a/brownie/test/strategies.py b/brownie/test/strategies.py index 136fa0890..bb089e566 100644 --- a/brownie/test/strategies.py +++ b/brownie/test/strategies.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 -from typing import Any, Callable, Iterable, Optional, Tuple, Union +from typing import Any, Callable, Iterable, Literal, Optional, Tuple, Union, overload from eth_abi.grammar import BasicType, TupleType, parse from hypothesis import strategies as st @@ -16,6 +16,76 @@ ArrayLengthType = Union[int, list, None] NumberType = Union[float, int, None] +EvmIntType = Literal[ + "int8", + "int16", + "int24", + "int32", + "int40", + "int48", + "int56", + "int64", + "int72", + "int80", + "int88", + "int96", + "int104", + "int112", + "int120", + "int128", + "int136", + "int144", + "int152", + "int160", + "int168", + "int176", + "int184", + "int192", + "int200", + "int208", + "int216", + "int224", + "int232", + "int240", + "int248", + "int256", +] + +EvmUintType = Literal[ + "uint8", + "uint16", + "uint24", + "uint32", + "uint40", + "uint48", + "uint56", + "uint64", + "uint72", + "uint80", + "uint88", + "uint96", + "uint104", + "uint112", + "uint120", + "uint128", + "uint136", + "uint144", + "uint152", + "uint160", + "uint168", + "uint176", + "uint184", + "uint192", + "uint200", + "uint208", + "uint216", + "uint224", + "uint232", + "uint240", + "uint248", + "uint256", +] + class _DeferredStrategyRepr(DeferredStrategy): def __init__(self, fn: Callable, repr_target: str) -> None: @@ -76,9 +146,9 @@ def _decimal_strategy( @_exclude_filter -def _address_strategy(length: Optional[int] = None) -> SearchStrategy: +def _address_strategy(length: Optional[int] = None, include: list = []) -> SearchStrategy: return _DeferredStrategyRepr( - lambda: st.sampled_from(list(network.accounts)[:length]), "accounts" + lambda: st.sampled_from(list(network.accounts)[:length] + include), "accounts" ) @@ -153,6 +223,22 @@ def _contract_deferred(name): return _DeferredStrategyRepr(lambda: _contract_deferred(contract_name), contract_name) +@overload +def strategy( + type_str: Literal["address"], + length: Optional[int] = None, + include: list = [], +) -> SearchStrategy: ... + + +@overload +def strategy( + type_str: Union[EvmIntType, EvmUintType], + min_value: Optional[int] = None, + max_value: Optional[int] = None, +) -> SearchStrategy: ... + + def strategy(type_str: str, **kwargs: Any) -> SearchStrategy: type_str = TYPE_STR_TRANSLATIONS.get(type_str, type_str) if type_str == "fixed168x10": diff --git a/docs/api-brownie.rst b/docs/api-brownie.rst index 6281d0c5f..db04b0c62 100644 --- a/docs/api-brownie.rst +++ b/docs/api-brownie.rst @@ -41,7 +41,7 @@ Exceptions .. py:exception:: brownie.exceptions.IncompatibleEVMVersion - Raised when attempting to deploy a contract that was compiled to target an EVM version that is imcompatible than the currently active local RPC client. + Raised when attempting to deploy a contract that was compiled to target an EVM version that is incompatible than the currently active local RPC client. .. py:exception:: brownie.exceptions.IncompatibleSolcVersion diff --git a/docs/api-network.rst b/docs/api-network.rst index 802a3e5f1..c6afef8b9 100644 --- a/docs/api-network.rst +++ b/docs/api-network.rst @@ -1642,7 +1642,7 @@ Internal Methods .. py:method:: brownie.network.event._get_topics(abi) - Generates encoded topics from the given ABI, merges them with those already known in ``topics.json``, and returns a dictioary in the form of ``{'Name': "encoded topic hexstring"}``. + Generates encoded topics from the given ABI, merges them with those already known in ``topics.json``, and returns a dictionary in the form of ``{'Name': "encoded topic hexstring"}``. .. code-block:: python diff --git a/docs/install.rst b/docs/install.rst index 8037b3d94..bcba38699 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -114,9 +114,9 @@ If you have updated your brownie version from older versions, hardhat networks w Using Brownie with Anvil ========================== -`Anvil `_ is a blazing-fast local testnet node implementation in Rust. Anvil may be used as an alternative to Ganache within Brownie. +`Anvil `_ is a blazing-fast local testnet node implementation in Rust. Anvil may be used as an alternative to Ganache within Brownie. -To use Anvil with Brownie, you must first `follow their steps to install Anvil `_. +To use Anvil with Brownie, you must first `follow their steps to install Anvil `_. Once installed, include the ``--network anvil`` or ``--network anvil-fork`` flag to run Brownie with Anvil. For example, to launch the console: diff --git a/requirements-dev.txt b/requirements-dev.txt index 3f5ed5f74..b472d66f2 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -47,7 +47,7 @@ coverage[toml]==7.4.3 # via # -r requirements-dev.in # pytest-cov -cryptography==42.0.5 +cryptography==44.0.1 # via secretstorage distlib==0.3.8 # via virtualenv @@ -85,7 +85,7 @@ jeepney==0.8.0 # via # keyring # secretstorage -jinja2==3.1.3 +jinja2==3.1.6 # via sphinx keyring==24.3.0 # via twine @@ -221,7 +221,7 @@ urllib3==2.2.1 # -c requirements.txt # requests # twine -virtualenv==20.25.1 +virtualenv==20.26.6 # via tox wheel==0.42.0 # via diff --git a/requirements-windows.txt b/requirements-windows.txt index 4e27a2174..c0c1c30da 100644 --- a/requirements-windows.txt +++ b/requirements-windows.txt @@ -4,7 +4,11 @@ # # pip-compile requirements-windows.in # -aiohttp==3.9.3 +aiohappyeyeballs==2.6.1 + # via + # -r requirements.txt + # aiohttp +aiohttp==3.11.0b0 # via # -r requirements.txt # web3 @@ -34,6 +38,8 @@ cbor2==5.6.2 # via # -r requirements.txt # vyper +cchecksum==0.0.7 + # via -r requirements.txt certifi==2024.2.2 # via # -r requirements.txt @@ -58,7 +64,7 @@ dataclassy==0.11.1 # eip712 eip712==0.2.4 # via -r requirements.txt -eth-abi==5.0.0 +eth-abi==5.0.1 # via # -r requirements.txt # eip712 @@ -77,7 +83,6 @@ eth-hash[pycryptodome]==0.6.0 # -r requirements.txt # eip712 # eth-event - # eth-hash # eth-utils # web3 eth-keyfile==0.7.0 @@ -96,6 +101,7 @@ eth-rlp==1.0.1 eth-typing==3.5.2 # via # -r requirements.txt + # cchecksum # eip712 # eth-abi # eth-keys @@ -104,6 +110,7 @@ eth-typing==3.5.2 eth-utils==2.3.1 # via # -r requirements.txt + # cchecksum # eip712 # eth-abi # eth-account @@ -173,6 +180,7 @@ packaging==23.2 # -r requirements.txt # black # pytest + # vvm # vyper parsimonious==0.9.0 # via @@ -192,6 +200,11 @@ pluggy==1.4.0 # pytest prompt-toolkit==3.0.43 # via -r requirements.txt +propcache==0.3.0 + # via + # -r requirements.txt + # aiohttp + # yarl protobuf==4.25.3 # via # -r requirements.txt @@ -267,7 +280,6 @@ semantic-version==2.10.0 # via # -r requirements.txt # py-solc-x - # vvm six==1.16.0 # via # -r requirements.txt @@ -285,7 +297,7 @@ toolz==0.12.1 # via # -r requirements.txt # cytoolz -tqdm==4.66.2 +tqdm==4.66.3 # via -r requirements.txt typing-extensions==4.9.0 # via @@ -297,9 +309,9 @@ urllib3==2.2.1 # via # -r requirements.txt # requests -vvm==0.1.0 +vvm==0.2.1 # via -r requirements.txt -vyper==0.3.10 +vyper==0.4.0 # via -r requirements.txt wcwidth==0.2.13 # via @@ -317,7 +329,7 @@ wheel==0.42.0 # vyper wrapt==1.16.0 # via -r requirements.txt -yarl==1.9.4 +yarl==1.18.3 # via # -r requirements.txt # aiohttp diff --git a/requirements.in b/requirements.in index 56d07b964..b4fb18cb2 100644 --- a/requirements.in +++ b/requirements.in @@ -1,4 +1,5 @@ black>=20.8b1 +cchecksum>=0.0.3 eip712 eth-abi eth-account @@ -23,7 +24,7 @@ requests>=2.25.0,<3 rlp semantic-version<3 tqdm<5 -vvm==0.1.0 # 0.2.0 switches from semantic-version to packaging.version and things break -vyper>=0.3.8,<0.4 +vvm==0.2.1 +vyper>=0.4.0,<0.5 web3>=6,<7 wrapt>=1.12.1,<2 diff --git a/requirements.txt b/requirements.txt index 9758d4e63..f9dc52ac6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,12 @@ # -# This file is autogenerated by pip-compile with Python 3.11 +# This file is autogenerated by pip-compile with Python 3.10 # by the following command: # # pip-compile requirements.in # -aiohttp==3.9.3 +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.11.0b0 # via web3 aiosignal==1.3.1 # via aiohttp @@ -23,6 +25,8 @@ black==24.2.0 # via -r requirements.in cbor2==5.6.2 # via vyper +cchecksum==0.0.7 + # via -r requirements.in certifi==2024.2.2 # via requests charset-normalizer==3.3.2 @@ -35,7 +39,7 @@ dataclassy==0.11.1 # via eip712 eip712==0.2.4 # via -r requirements.in -eth-abi==5.0.0 +eth-abi==5.0.1 # via # -r requirements.in # eip712 @@ -66,6 +70,7 @@ eth-rlp==1.0.1 # via eth-account eth-typing==3.5.2 # via + # cchecksum # eip712 # eth-abi # eth-keys @@ -74,6 +79,7 @@ eth-typing==3.5.2 eth-utils==2.3.1 # via # -r requirements.in + # cchecksum # eip712 # eth-abi # eth-account @@ -125,6 +131,7 @@ packaging==23.2 # via # black # pytest + # vvm # vyper parsimonious==0.9.0 # via eth-abi @@ -136,6 +143,10 @@ pluggy==1.4.0 # via pytest prompt-toolkit==3.0.43 # via -r requirements.in +propcache==0.3.0 + # via + # aiohttp + # yarl protobuf==4.25.3 # via web3 psutil==5.9.8 @@ -200,7 +211,6 @@ semantic-version==2.10.0 # via # -r requirements.in # py-solc-x - # vvm six==1.16.0 # via # asttokens @@ -211,7 +221,7 @@ toml==0.10.2 # via pytest toolz==0.12.1 # via cytoolz -tqdm==4.66.2 +tqdm==4.66.3 # via -r requirements.in typing-extensions==4.9.0 # via @@ -220,9 +230,9 @@ typing-extensions==4.9.0 # web3 urllib3==2.2.1 # via requests -vvm==0.1.0 +vvm==0.2.1 # via -r requirements.in -vyper==0.3.10 +vyper==0.4.0 # via -r requirements.in wcwidth==0.2.13 # via prompt-toolkit @@ -234,7 +244,7 @@ wheel==0.42.0 # via vyper wrapt==1.16.0 # via -r requirements.in -yarl==1.9.4 +yarl==1.18.3 # via aiohttp zipp==3.17.0 # via importlib-metadata diff --git a/setup.cfg b/setup.cfg index 05091dbc0..28560d976 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.20.4 +current_version = 1.20.7 [bumpversion:file:setup.py] @@ -8,7 +8,7 @@ current_version = 1.20.4 [flake8] exclude = tests/data/* max-line-length = 100 -ignore = E203,W503 +ignore = E203,E704,W503 [tool:isort] force_grid_wrap = 0 diff --git a/setup.py b/setup.py index 16a135c60..99ef113a1 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ setup( name="eth-brownie", packages=find_packages(), - version="1.20.4", # don't change this manually, use bumpversion instead + version="1.20.7", # don't change this manually, use bumpversion instead license="MIT", description="A Python framework for Ethereum smart contract deployment, testing and interaction.", # noqa: E501 long_description=long_description, @@ -38,6 +38,9 @@ "console_scripts": ["brownie=brownie._cli.__main__:main"], "pytest11": ["pytest-brownie=brownie.test.plugin"], }, + package_data={ + "brownie": ["py.typed"], + }, include_package_data=True, python_requires=">=3.10,<4", classifiers=[