Skip to content

Commit

Permalink
Merge pull request #19 from DanielSchiavini/feat/moccasin-overhaul
Browse files Browse the repository at this point in the history
feat: deployments DB and boa updates
  • Loading branch information
DanielSchiavini authored Oct 15, 2024
2 parents b2c83e3 + f8593c1 commit af05f66
Show file tree
Hide file tree
Showing 14 changed files with 354 additions and 103 deletions.
1 change: 1 addition & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
name: release
on:
release:
types: [published]
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
name: test
on:
push:

Expand Down
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,11 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# MacOS
.DS_Store

# uv
uv.lock

example_verify.md
18 changes: 10 additions & 8 deletions boa_zksync/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,27 @@
from boa_zksync.node import EraTestNode


def set_zksync_env(url):
boa.set_env(ZksyncEnv.from_url(url))
def set_zksync_env(url, nickname=None):
return boa.set_env(ZksyncEnv.from_url(url, nickname=nickname))


def set_zksync_test_env(node_args=()):
boa.set_env(ZksyncEnv(rpc=EraTestNode(node_args=node_args)))
def set_zksync_test_env(node_args=(), nickname=None):
return boa.set_env(
ZksyncEnv(rpc=EraTestNode(node_args=node_args), nickname=nickname)
)


def set_zksync_fork(url, *args, **kwargs):
env = ZksyncEnv.from_url(url)
def set_zksync_fork(url, nickname=None, *args, **kwargs):
env = ZksyncEnv.from_url(url, nickname=nickname)
env.fork(*args, **kwargs)
boa.set_env(env)
return boa.set_env(env)


def set_zksync_browser_env(*args, **kwargs):
# import locally because jupyter is generally not installed
from boa_zksync.browser import ZksyncBrowserEnv

boa.set_env(ZksyncBrowserEnv(*args, **kwargs))
return boa.set_env(ZksyncBrowserEnv(*args, **kwargs))


boa.set_zksync_env = set_zksync_env
Expand Down
98 changes: 96 additions & 2 deletions boa_zksync/contract.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import textwrap
from contextlib import contextmanager
from typing import TYPE_CHECKING, Optional

from boa import Env
from boa.contracts.abi.abi_contract import ABIContract, ABIFunction
from boa.contracts.vyper.vyper_contract import VyperContract
from boa.rpc import to_bytes, to_int
Expand All @@ -18,15 +20,86 @@
)
from boa_zksync.types import ZksyncCompilerData

if TYPE_CHECKING:
from boa_zksync import ZksyncEnv


class ZksyncContract(ABIContract):
"""
A contract deployed to the Zksync network.
"""

def __init__(self, compiler_data: ZksyncCompilerData, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(
self,
compiler_data: ZksyncCompilerData,
name: str,
functions: list[ABIFunction],
*args,
value=0,
env: "ZksyncEnv" = None,
override_address: Address = None,
# whether to skip constructor
skip_initcode=False,
created_from: Address = None,
filename: str = None,
gas=None,
):
self.compiler_data = compiler_data
self.created_from = created_from

# we duplicate the following setters from the base contract, because the
# ABI contract currently reads the bytecode from the env on init.
# However, the zk contract is not yet deployed at this point.
# for the deployment, these values are needed, so we set them here.
self._abi = compiler_data.abi
self.env = Env.get_singleton() if env is None else env
self.filename = filename
self.contract_name = name

# run the constructor if not skipping
if skip_initcode:
if value:
raise Exception("nonzero value but initcode is being skipped")
address = Address(override_address)
else:
address = self._run_init(
*args, value=value, override_address=override_address, gas=gas
)

# only now initialize the ABI contract
super().__init__(
name=name,
abi=compiler_data.abi,
functions=functions,
address=address,
filename=filename,
env=env,
)
self.env.register_contract(address, self)

def _run_init(self, *args, value=0, override_address=None, gas=None):
constructor_calldata = self._ctor.prepare_calldata(*args) if self._ctor else b""
address, bytecode = self.env.deploy_code(
override_address=override_address,
gas=gas,
contract=self,
bytecode=self.compiler_data.bytecode,
value=value,
constructor_calldata=constructor_calldata,
)
self.bytecode = bytecode
return address

@cached_property
def _ctor(self) -> Optional[ABIFunction]:
"""
Get the constructor function of the contract.
:raises: StopIteration if the constructor is not found.
"""
ctor_abi = next((i for i in self.abi if i["type"] == "constructor"), None)
if ctor_abi is None:
return None
return ABIFunction(ctor_abi, contract_name=self.contract_name)

def eval(self, code):
return ZksyncEval(code, self)()
Expand All @@ -36,6 +109,16 @@ def override_vyper_namespace(self):
with self.vyper_contract.override_vyper_namespace():
yield

@cached_property
def deployer(self):
from boa_zksync.deployer import ZksyncDeployer

return ZksyncDeployer(
self.compiler_data.vyper,
filename=self.filename,
zkvyper_data=self.compiler_data,
)

@cached_property
def vyper_contract(self):
return VyperContract(
Expand Down Expand Up @@ -92,6 +175,17 @@ def get_logs(self):
return ret


class ZksyncBlueprint(ZksyncContract):
"""
In zkSync, any contract can be used as a blueprint.
The only difference here is that we don't need to run the constructor.
"""

@property
def _ctor(self) -> Optional[ABIFunction]:
return None


class _ZksyncInternal(ABIFunction):
"""
An ABI function that temporarily changes the bytecode at the contract's address.
Expand Down
85 changes: 33 additions & 52 deletions boa_zksync/deployer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,25 @@
from typing import TYPE_CHECKING

from boa import Env
from boa.contracts.abi.abi_contract import ABIContractFactory, ABIFunction
from boa.contracts.abi.abi_contract import ABIContractFactory
from boa.util.abi import Address
from vyper.compiler import CompilerData
from vyper.compiler.output import build_solc_json

from boa_zksync.compile import compile_zksync, compile_zksync_source
from boa_zksync.contract import ZksyncContract
from boa_zksync.types import ZksyncCompilerData, DEFAULT_SALT
from boa_zksync.contract import ZksyncBlueprint, ZksyncContract
from boa_zksync.types import ZksyncCompilerData

if TYPE_CHECKING:
from boa_zksync.environment import ZksyncEnv


class ZksyncDeployer(ABIContractFactory):

def __init__(self, compiler_data: CompilerData, filename=None):
def __init__(self, compiler_data: CompilerData, filename=None, zkvyper_data=None):
contract_name = Path(compiler_data.contract_path).stem
self.zkvyper_data = self._compile(compiler_data, contract_name, filename)
if zkvyper_data is None:
zkvyper_data = self._compile(compiler_data, contract_name, filename)
self.zkvyper_data = zkvyper_data
super().__init__(
contract_name, self.zkvyper_data.abi, compiler_data.contract_path
)
Expand All @@ -41,63 +43,34 @@ def _compile(
def from_abi_dict(cls, abi, name="<anonymous contract>", filename=None):
raise NotImplementedError("ZksyncDeployer does not support loading from ABI")

def deploy(
self,
*args,
value=0,
gas=None,
dependency_bytecodes=(),
salt=DEFAULT_SALT,
max_priority_fee_per_gas=None,
**kwargs,
) -> ZksyncContract:
address, _ = self.env.deploy_code(
bytecode=self.zkvyper_data.bytecode,
value=value,
gas=gas,
dependency_bytecodes=dependency_bytecodes,
salt=salt,
max_priority_fee_per_gas=max_priority_fee_per_gas,
constructor_calldata=(
self.constructor.prepare_calldata(*args, **kwargs)
if args or kwargs
else b""
),
)
return self.at(address)

def at(self, address: Address | str) -> ZksyncContract:
"""
Create an ABI contract object for a deployed contract at `address`.
"""
address = Address(address)
contract = ZksyncContract(
def deploy(self, *args, **kwargs) -> ZksyncContract:
return ZksyncContract(
self.zkvyper_data,
self._name,
self.abi,
self.functions,
address=address,
*args,
filename=self.filename,
env=self.env,
**kwargs,
)
self.env.register_contract(address, contract)
return contract

def deploy_as_blueprint(self, *args, **kwargs) -> ZksyncContract:
def at(self, address: Address | str) -> ZksyncContract:
"""
In zkSync, any contract can be used as a blueprint.
Note that we do need constructor arguments for deploying a blueprint.
Create an ABI contract object for a deployed contract at `address`.
"""
return self.deploy(*args, **kwargs)
return self.deploy(override_address=Address(address), skip_initcode=True)

@cached_property
def constructor(self) -> ABIFunction:
def deploy_as_blueprint(self, **kwargs) -> ZksyncContract:
"""
Get the constructor function of the contract.
:raises: StopIteration if the constructor is not found.
In zkSync, any contract can be used as a blueprint.
The only difference here is that we don't need to run the constructor.
"""
ctor_abi = next(i for i in self.abi if i["type"] == "constructor")
return ABIFunction(ctor_abi, contract_name=self._name)
return ZksyncBlueprint(
self.zkvyper_data,
self._name,
self.functions,
filename=self.filename,
**kwargs,
)

@property
def env(self) -> "ZksyncEnv":
Expand All @@ -112,3 +85,11 @@ def env(self) -> "ZksyncEnv":
env, ZksyncEnv
), "ZksyncDeployer can only be used in zkSync environments"
return env

@cached_property
def solc_json(self):
"""
A ZKsync compatible solc-json. Generates a solc "standard json" representation
of the Vyper contract.
"""
return build_solc_json(self.zkvyper_data.vyper)
Loading

0 comments on commit af05f66

Please sign in to comment.