Skip to content

Commit

Permalink
Merge branch 'master' into wallet-eoa
Browse files Browse the repository at this point in the history
  • Loading branch information
charles-cooper committed Nov 19, 2024
2 parents 49a9147 + dff181d commit 2a1f818
Show file tree
Hide file tree
Showing 18 changed files with 356 additions and 65 deletions.
25 changes: 11 additions & 14 deletions boa/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
load,
load_abi,
load_partial,
load_vyi,
loads,
loads_abi,
loads_partial,
loads_vyi,
)
from boa.network import NetworkEnv
from boa.precompile import precompile
Expand All @@ -35,25 +37,20 @@

@contextlib.contextmanager
def swap_env(new_env):
old_env = env
try:
set_env(new_env)
with set_env(new_env):
yield
finally:
set_env(old_env)


def set_env(new_env):
def _set_env(new):
global env
env = new_env
env = new
Env._singleton = new

Env._singleton = new_env


def _env_mgr(new_env):
def set_env(new_env):
global env
get_env = lambda: env # noqa: E731
return Open(get_env, set_env, new_env)
return Open(get_env, _set_env, new_env)


def fork(
Expand All @@ -67,20 +64,20 @@ def fork(

new_env = Env()
new_env.fork(url=url, block_identifier=block_identifier, deprecated=False, **kwargs)
return _env_mgr(new_env)
return set_env(new_env)


def set_browser_env(address=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

return _env_mgr(BrowserEnv(address))
return set_env(BrowserEnv(address))


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


def set_etherscan(*args, **kwargs):
Expand Down
44 changes: 42 additions & 2 deletions boa/contracts/vvm/vvm_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from boa.contracts.abi.abi_contract import ABIContractFactory, ABIFunction
from boa.environment import Env
from boa.util.eip5202 import generate_blueprint_bytecode

# TODO: maybe this doesn't detect release candidates
VERSION_RE = re.compile(r"\s*#\s*(pragma\s+version|@version)\s+(\d+\.\d+\.\d+)")
Expand All @@ -18,7 +19,19 @@ def _detect_version(source_code: str):


class VVMDeployer:
"""
A deployer that uses the Vyper Version Manager (VVM).
This allows deployment of contracts written in older versions of Vyper that
can interact with new versions using the ABI definition.
"""

def __init__(self, abi, bytecode, filename):
"""
Initialize a VVMDeployer instance.
:param abi: The contract's ABI.
:param bytecode: The contract's bytecode.
:param filename: The filename of the contract.
"""
self.abi = abi
self.bytecode = bytecode
self.filename = filename
Expand All @@ -41,7 +54,7 @@ def constructor(self):
return ABIFunction(t, contract_name=self.filename)
return None

def deploy(self, *args, env=None):
def deploy(self, *args, contract_name=None, env=None, **kwargs):
encoded_args = b""
if self.constructor is not None:
encoded_args = self.constructor.prepare_calldata(*args)
Expand All @@ -51,10 +64,37 @@ def deploy(self, *args, env=None):
if env is None:
env = Env.get_singleton()

address, _ = env.deploy_code(bytecode=self.bytecode + encoded_args)
address, _ = env.deploy_code(bytecode=self.bytecode + encoded_args, **kwargs)

# TODO: pass thru contract_name
return self.at(address)

@cached_property
def _blueprint_deployer(self):
# TODO: add filename
return ABIContractFactory.from_abi_dict([])

def deploy_as_blueprint(self, env=None, blueprint_preamble=None, **kwargs):
"""
Deploy a new blueprint from this contract.
:param blueprint_preamble: The preamble to use for the blueprint.
:param env: The environment to deploy the blueprint in.
:param kwargs: Keyword arguments to pass to the environment `deploy_code` method.
:returns: A contract instance.
"""
if env is None:
env = Env.get_singleton()

blueprint_bytecode = generate_blueprint_bytecode(
self.bytecode, blueprint_preamble
)
address, _ = env.deploy_code(bytecode=blueprint_bytecode, **kwargs)

ret = self._blueprint_deployer.at(address)

env.register_blueprint(self.bytecode, ret)
return ret

def __call__(self, *args, **kwargs):
return self.deploy(*args, **kwargs)

Expand Down
35 changes: 16 additions & 19 deletions boa/contracts/vyper/vyper_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
from boa.environment import Env
from boa.profiling import cache_gas_used_for_computation
from boa.util.abi import Address, abi_decode, abi_encode
from boa.util.eip5202 import generate_blueprint_bytecode
from boa.util.lrudict import lrudict
from boa.vm.gas_meters import ProfilingGasMeter
from boa.vm.utils import to_bytes, to_int
Expand Down Expand Up @@ -99,16 +100,16 @@ def stomp(self, address: Any, data_section=None) -> "VyperContract":
address = Address(address)

ret = self.deploy(override_address=address, skip_initcode=True)
vm = ret.env.vm
old_bytecode = vm.state.get_code(address.canonical_address)
vm = ret.env.evm
old_bytecode = vm.get_code(address)
new_bytecode = self.compiler_data.bytecode_runtime

immutables_size = self.compiler_data.global_ctx.immutable_section_bytes
if immutables_size > 0:
data_section = old_bytecode[-immutables_size:]
new_bytecode += data_section

vm.state.set_code(address.canonical_address, new_bytecode)
vm.set_code(address, new_bytecode)
ret.env.register_contract(address, ret)
ret._set_bytecode(new_bytecode)
return ret
Expand Down Expand Up @@ -143,10 +144,13 @@ class _BaseVyperContract(_BaseEVMContract):
def __init__(
self,
compiler_data: CompilerData,
contract_name: Optional[str] = None,
env: Optional[Env] = None,
filename: Optional[str] = None,
):
contract_name = Path(compiler_data.contract_path).stem
if contract_name is None:
contract_name = Path(compiler_data.contract_path).stem

super().__init__(contract_name, env, filename)
self.compiler_data = compiler_data

Expand Down Expand Up @@ -183,24 +187,16 @@ def __init__(
compiler_data,
env=None,
override_address=None,
blueprint_preamble=b"\xFE\x71\x00",
blueprint_preamble=None,
contract_name=None,
filename=None,
gas=None,
):
# note slight code duplication with VyperContract ctor,
# maybe use common base class?
super().__init__(compiler_data, env, filename)

if blueprint_preamble is None:
blueprint_preamble = b""

blueprint_bytecode = blueprint_preamble + compiler_data.bytecode
super().__init__(compiler_data, contract_name, env, filename)

# the length of the deployed code in bytes
len_bytes = len(blueprint_bytecode).to_bytes(2, "big")
deploy_bytecode = b"\x61" + len_bytes + b"\x3d\x81\x60\x0a\x3d\x39\xf3"

deploy_bytecode += blueprint_bytecode
deploy_bytecode = generate_blueprint_bytecode(
compiler_data.bytecode, blueprint_preamble
)

addr, computation = self.env.deploy(
bytecode=deploy_bytecode, override_address=override_address, gas=gas
Expand Down Expand Up @@ -522,10 +518,11 @@ def __init__(
# whether to skip constructor
skip_initcode=False,
created_from: Address = None,
contract_name=None,
filename: str = None,
gas=None,
):
super().__init__(compiler_data, env, filename)
super().__init__(compiler_data, contract_name, env, filename)

self.created_from = created_from
self._computation = None
Expand Down
26 changes: 20 additions & 6 deletions boa/deployments.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import uuid
from dataclasses import asdict, dataclass, field, fields
from pathlib import Path
from typing import Any, Optional
from typing import Any, Generator, Optional

from boa.util.abi import Address
from boa.util.open_ctx import Open
Expand Down Expand Up @@ -39,6 +39,7 @@ class Deployment:
tx_dict: dict # raw tx fields
receipt_dict: dict # raw receipt fields
source_code: Optional[Any] # optional source code or bundle
abi: Optional[Any]
session_id: str = field(default_factory=get_session_id)
deployment_id: Optional[int] = None # the db-assigned id - primary key

Expand All @@ -49,6 +50,8 @@ def sql_values(self):
ret["receipt_dict"] = json.dumps(ret["receipt_dict"])
if ret["source_code"] is not None:
ret["source_code"] = json.dumps(ret["source_code"])
if ret["abi"] is not None:
ret["abi"] = json.dumps(ret["abi"])
return ret

def to_dict(self):
Expand All @@ -75,6 +78,8 @@ def from_sql_tuple(cls, values):
ret["receipt_dict"] = json.loads(ret["receipt_dict"])
if ret["source_code"] is not None:
ret["source_code"] = json.loads(ret["source_code"])
if ret["abi"] is not None:
ret["abi"] = json.loads(ret["abi"])
return cls(**ret)


Expand All @@ -91,7 +96,8 @@ def from_sql_tuple(cls, values):
broadcast_ts real,
tx_dict text,
receipt_dict text,
source_code text
source_code text,
abi text
);
"""

Expand Down Expand Up @@ -123,15 +129,23 @@ def insert_deployment(self, deployment: Deployment):

def _get_deployments_from_sql(self, sql_query: str, parameters=(), /):
cur = self.db.execute(sql_query, parameters)
ret = [Deployment.from_sql_tuple(item) for item in cur.fetchall()]
return ret
return (Deployment.from_sql_tuple(item) for item in cur)

def _get_fieldnames_str(self) -> str:
return ",".join(field.name for field in fields(Deployment))

def get_deployments(self) -> list[Deployment]:
def get_deployments(self) -> Generator[Deployment, None, None]:
"""
Return all the deployments from the database. Returns an iterator
which can be converted to a list with `list(db.get_deployments())`.
Returns the deployments ordered by most recent first, i.e. using
an `ORDER BY deployment_id DESC` clause.
"""
fieldnames = self._get_fieldnames_str()
return self._get_deployments_from_sql(f"SELECT {fieldnames} FROM deployments")
return self._get_deployments_from_sql(
f"SELECT {fieldnames} FROM deployments order by deployment_id desc"
)


_db: Optional[DeploymentsDB] = None
Expand Down
Loading

0 comments on commit 2a1f818

Please sign in to comment.