Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disable forking network env #182

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ Note that in `eval()` mode, titanoboa uses slightly different optimization setti
### Forking
Create a fork of mainnet given rpc.
```python
In [1]: import boa; boa.env.fork(url="<rpc server address>")
In [1]: import boa; boa.fork(url="<rpc server address>")

In [2]: %load_ext boa.ipython

Expand All @@ -189,7 +189,7 @@ Out[5]: 'Curve DAO Token'

Cast current deployed addresses to vyper contract
```python
>>> import boa; boa.env.fork(url="<rpc server address>")
>>> import boa; boa.fork(url="<rpc server address>")
>>> c = boa.load_partial("examples/ERC20.vy").at("0xD533a949740bb3306d119CC777fa900bA034cd52")
>>> c.name()
'Curve DAO Token'
Expand Down
37 changes: 34 additions & 3 deletions boa/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import contextlib
import sys
from typing import Union

from boa.contracts.base_evm_contract import BoaError
from boa.contracts.vyper.vyper_contract import check_boa_error_matches
Expand Down Expand Up @@ -45,19 +46,49 @@ def set_env(new_env):
Env._singleton = new_env


def set_browser_env(address=None):
def set_browser_env(address=None, chain_id: Union[None, str, int] = None):
"""Set the environment to use the browser's network in Jupyter/Colab"""
# import locally because jupyter is generally not installed
from boa.integrations.jupyter import BrowserEnv
from boa.integrations.jupyter.browser import BrowserRPC, BrowserSigner

set_env(BrowserEnv(address))
rpc = BrowserRPC(chain_id)
e = NetworkEnv(rpc)
signer = BrowserSigner(address, rpc)
setattr(e, "signer", signer)
e.set_eoa(signer)
return set_env(e)


def set_network_env(url):
"""Set the environment to use a custom network URL"""
set_env(NetworkEnv.from_url(url))


def fork(*args, try_prefetch_state=False, fast_mode_enabled=False, **kwargs):
"""Fork the current environment to use a custom network URL"""
e = Env(try_prefetch_state, fast_mode_enabled)
e.fork(*args, **kwargs)
return set_env(e)


def fork_browser(
url: str,
address=None,
*args,
chain_id: Union[None, str, int] = None,
try_prefetch_state=False,
fast_mode_enabled=False,
**kwargs,
):
"""Fork the current environment to use browser RPC"""
from boa.integrations.jupyter import BrowserRPC, BrowserSigner

rpc = BrowserRPC(url)
e = Env(try_prefetch_state, fast_mode_enabled)
e.fork_rpc(rpc, *args, **kwargs)
return set_env(e)


def reset_env():
set_env(Env())

Expand Down
3 changes: 1 addition & 2 deletions boa/integrations/jupyter/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from boa.integrations.jupyter.browser import BrowserEnv, BrowserRPC, BrowserSigner
from boa.integrations.jupyter.browser import BrowserRPC, BrowserSigner
from boa.integrations.jupyter.constants import PLUGIN_NAME
from boa.integrations.jupyter.handlers import setup_handlers

Expand All @@ -19,7 +19,6 @@ def load_jupyter_server_extension(server_app):
__all__ = [ # type: ignore
BrowserSigner,
BrowserRPC,
BrowserEnv,
load_jupyter_server_extension,
_load_jupyter_server_extension,
]
33 changes: 10 additions & 23 deletions boa/integrations/jupyter/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from multiprocessing.shared_memory import SharedMemory
from os import urandom
from os.path import dirname, join, realpath
from typing import Any
from typing import Any, Union

import nest_asyncio
from IPython.display import Javascript, display
Expand All @@ -24,7 +24,6 @@
SHARED_MEMORY_LENGTH,
TRANSACTION_TIMEOUT_MESSAGE,
)
from boa.network import NetworkEnv
from boa.rpc import RPC, RPCError
from boa.util.abi import Address

Expand Down Expand Up @@ -57,7 +56,9 @@ class BrowserRPC(RPC):

_debug_mode = False

def __init__(self):
def __init__(self, chain_id: Union[None, str, int] = None):
if chain_id is not None:
self.set_chain_id(chain_id)
if not colab_eval_js:
# colab creates a new iframe for every call, we need to re-inject it every time
# for jupyterlab we only need to do it once
Expand Down Expand Up @@ -93,6 +94,12 @@ def wait_for_tx_receipt(self, tx_hash, timeout: float, poll_latency=1):
timeout_message=RPC_TIMEOUT_MESSAGE,
)

def set_chain_id(self, chain_id: int | str):
return self.fetch(
"wallet_switchEthereumChain",
[{"chainId": chain_id if isinstance(chain_id, str) else hex(chain_id)}],
)


class BrowserSigner:
"""
Expand Down Expand Up @@ -144,26 +151,6 @@ def sign_typed_data(self, full_message: dict[str, Any]) -> str:
)


class BrowserEnv(NetworkEnv):
"""
A NetworkEnv object that uses the BrowserSigner and BrowserRPC classes.
"""

_rpc = BrowserRPC() # Browser is always global anyway, we can make it static

def __init__(self, address=None, **kwargs):
super().__init__(self._rpc, **kwargs)
self.signer = BrowserSigner(address, self._rpc)
self.set_eoa(self.signer)

def set_chain_id(self, chain_id: int | str):
self._rpc.fetch(
"wallet_switchEthereumChain",
[{"chainId": chain_id if isinstance(chain_id, str) else hex(chain_id)}],
)
self._reset_fork()


def _javascript_call(js_func: str, *args, timeout_message: str) -> Any:
"""
This function attempts to call a Javascript function in the browser and then
Expand Down
5 changes: 4 additions & 1 deletion boa/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,10 +460,13 @@ def _debug_tt(self, tx_hash):
def _get_nonce(self, addr):
return self._rpc.fetch("eth_getTransactionCount", [addr, "latest"])

def fork_rpc(self, rpc: RPC, reset_traces=True, block_identifier="safe", **kwargs):
raise TypeError("cannot fork a network env")

def _reset_fork(self, block_identifier="latest"):
# use "latest" to make sure we are forking with up-to-date state
# but use reset_traces=False to help with storage dumps
self.fork_rpc(
super().fork_rpc(
self._rpc,
reset_traces=False,
block_identifier=block_identifier,
Expand Down
4 changes: 2 additions & 2 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ High-Level Functionality
.. code-block:: python

>>> import boa, os
>>> boa.env.fork(os.environ["ALCHEMY_MAINNET_ENDPOINT"])
>>> boa.fork(os.environ["ALCHEMY_MAINNET_ENDPOINT"])
>>> crvusd = boa.from_etherscan("0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E", name="crvUSD")
>>> crvusd
<crvUSD interface at 0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E>
Expand Down Expand Up @@ -467,7 +467,7 @@ Low-Level Functionality
>>> import boa
>>> boa.env.vm.state.block_number
1
>>> boa.env.fork("https://rpc.ankr.com/eth")
>>> boa.fork("https://rpc.ankr.com/eth")
>>> boa.env.vm.state.block_number
16038471

Expand Down
2 changes: 1 addition & 1 deletion tests/integration/fork/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ def rpc_url():
def forked_env(rpc_url):
with boa.swap_env(Env()):
block_id = 18801970 # some block we know the state of
boa.env.fork(rpc_url, block_identifier=block_id)
boa.fork(rpc_url, block_identifier=block_id)
yield
4 changes: 2 additions & 2 deletions tests/unitary/jupyter/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,8 @@ def test_browser_chain_id(token, env, display_mock, mock_callback):
mock_callback("eth_chainId", "0x1234")
assert env.get_chain_id() == 4660
mock_callback("wallet_switchEthereumChain")
env.set_chain_id(1)
assert display_mock.call_count == 4
env._rpc.set_chain_id(1)
assert display_mock.call_count == 2
(js,), _ = display_mock.call_args_list[1]
assert (
f'rpc("{token}", "wallet_switchEthereumChain", [{{"chainId": "0x1"}}])'
Expand Down
Loading