diff --git a/CHANGELOG.md b/CHANGELOG.md index 39d10bcfd..978952626 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ 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) +### 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)) + ## [1.18.2](https://github.com/eth-brownie/brownie/tree/v1.18.2) - 2022-05-15 ### Added diff --git a/brownie/data/network-config.yaml b/brownie/data/network-config.yaml index 72a067721..dbb3a5bb1 100644 --- a/brownie/data/network-config.yaml +++ b/brownie/data/network-config.yaml @@ -189,6 +189,19 @@ live: explorer: https://blockscout.com/poa/sokol/api development: + - name: Anvil + id: anvil + cmd: anvil + host: http://127.0.0.1 + cmd_settings: + port: 8545 + - name: Anvil (Mainnet Fork) + id: anvil-fork + cmd: anvil + host: http://127.0.0.1 + cmd_settings: + fork: mainnet + port: 8545 - name: Ganache-CLI id: development cmd: ganache-cli diff --git a/brownie/network/rpc/__init__.py b/brownie/network/rpc/__init__.py index b79382ffd..b4c2305d9 100644 --- a/brownie/network/rpc/__init__.py +++ b/brownie/network/rpc/__init__.py @@ -16,13 +16,14 @@ from brownie.network.state import Chain from brownie.network.web3 import web3 -from . import ganache, geth, hardhat +from . import anvil, ganache, geth, hardhat chain = Chain() ATTACH_BACKENDS = {"ethereumjs testrpc": ganache, "geth": geth, "hardhat": hardhat} LAUNCH_BACKENDS = { + "anvil": anvil, "ganache": ganache, "ethnode": geth, "geth": geth, diff --git a/brownie/network/rpc/anvil.py b/brownie/network/rpc/anvil.py new file mode 100644 index 000000000..2fbcd99e1 --- /dev/null +++ b/brownie/network/rpc/anvil.py @@ -0,0 +1,85 @@ +#!/usr/bin/python3 + +import sys +import warnings +from subprocess import DEVNULL, PIPE +from typing import Dict, List, Optional + +import psutil +from requests.exceptions import ConnectionError as RequestsConnectionError + +from brownie.exceptions import InvalidArgumentWarning, RPCRequestError +from brownie.network.web3 import web3 + +CLI_FLAGS = { + "port": "--port", + "host": "--host", + "fork": "--fork-url", + "fork_block": "--fork-block-number", + "chain_id": "--chain-id", +} + + +def launch(cmd: str, **kwargs: Dict) -> None: + """Launches the RPC client. + + Args: + cmd: command string to execute as subprocess""" + if sys.platform == "win32" and not cmd.split(" ")[0].endswith(".cmd"): + if " " in cmd: + cmd = cmd.replace(" ", ".cmd ", 1) + else: + cmd += ".cmd" + cmd_list = cmd.split(" ") + for key, value in [(k, v) for k, v in kwargs.items() if v]: + try: + cmd_list.extend([CLI_FLAGS[key], str(value)]) + except KeyError: + warnings.warn( + f"Ignoring invalid commandline setting for anvil: " + f'"{key}" with value "{value}".', + InvalidArgumentWarning, + ) + print(f"\nLaunching '{' '.join(cmd_list)}'...") + out = DEVNULL if sys.platform == "win32" else PIPE + + return psutil.Popen(cmd_list, stdin=DEVNULL, stdout=out, stderr=out) + + +def on_connection() -> None: + # set gas limit to the same as the forked network + gas_limit = web3.eth.get_block("latest").gasLimit + web3.provider.make_request("evm_setBlockGasLimit", [hex(gas_limit)]) # type: ignore + + +def _request(method: str, args: List) -> int: + try: + response = web3.provider.make_request(method, args) # type: ignore + if "result" in response: + return response["result"] + except (AttributeError, RequestsConnectionError): + raise RPCRequestError("Web3 is not connected.") + raise RPCRequestError(response["error"]["message"]) + + +def sleep(seconds: int) -> int: + _request("evm_increaseTime", [hex(seconds)]) + return seconds + + +def mine(timestamp: Optional[int] = None) -> None: + if timestamp: + _request("evm_setNextBlockTimestamp", [timestamp]) + _request("evm_mine", [1]) + + +def snapshot() -> int: + return _request("evm_snapshot", []) + + +def revert(snapshot_id: int) -> None: + _request("evm_revert", [snapshot_id]) + + +def unlock_account(address: str) -> None: + web3.provider.make_request("anvil_impersonateAccount", [address]) # type: ignore diff --git a/docs/install.rst b/docs/install.rst index 650f93e6b..8037b3d94 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -109,3 +109,19 @@ Once installed, include the ``--network hardhat`` flag to run Brownie with Hardh The first time you use Hardhat within a Brownie project, a ``hardhat.config.js`` `configuration file `_ is generated. You should not modify any of the settings within this file as they are required for compatibility. If you have updated your brownie version from older versions, hardhat networks will be missing. You have to update ``~/.brownie/network-config.yaml``. It can be updated using the one `here `_ + + +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. + +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: + + .. code-block:: bash + + brownie console --network anvil + +If you have updated your brownie version from older versions, anvil networks will be missing. You have to update ``~/.brownie/network-config.yaml``. It can be updated using the one `here `_