Skip to content

Commit

Permalink
feat: add deploy_as_blueprint to VVMDeployer (#311)
Browse files Browse the repository at this point in the history

---------

Co-authored-by: Charles Cooper <cooper.charles.m@gmail.com>
  • Loading branch information
DanielSchiavini and charles-cooper authored Oct 2, 2024
1 parent f00e12b commit 7f1c700
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 15 deletions.
39 changes: 39 additions & 0 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 Down Expand Up @@ -55,6 +68,32 @@ def deploy(self, *args, env=None):

return self.at(address)

@cached_property
def _blueprint_bytecode(self):
return generate_blueprint_bytecode(self.bytecode)

@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()

address, _ = env.deploy_code(bytecode=self._blueprint_bytecode)

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
16 changes: 5 additions & 11 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 @@ -183,24 +184,17 @@ def __init__(
compiler_data,
env=None,
override_address=None,
blueprint_preamble=b"\xFE\x71\x00",
blueprint_preamble=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

# 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
17 changes: 17 additions & 0 deletions boa/util/eip5202.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,23 @@
from eth_utils import to_canonical_address, to_checksum_address
from vyper.utils import keccak256

DEFAULT_BLUEPRINT_PREAMBLE = b"\xFE\x71\x00"


def generate_blueprint_bytecode(
contract_bytecode: bytes, blueprint_preamble: bytes = None
):
if blueprint_preamble is None:
blueprint_preamble = DEFAULT_BLUEPRINT_PREAMBLE

blueprint_bytecode = blueprint_preamble + contract_bytecode

# 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"

return deploy_bytecode + blueprint_bytecode


# TODO replace return type with upcoming AddressType wrapper
def get_create2_address(
Expand Down
30 changes: 26 additions & 4 deletions tests/unitary/test_blueprints.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,45 @@
import pytest
import vyper
from eth_utils import to_canonical_address

import boa
from boa.util.eip5202 import get_create2_address

blueprint_code = """
_blueprint_code = """
# pragma version {}
@external
def some_function() -> uint256:
return 5
"""

factory_code = """
_factory_code = """
# pragma version {}
@external
def create_child(blueprint: address, salt: bytes32) -> address:
return create_from_blueprint(blueprint, code_offset=3, salt=salt)
"""

VERSIONS = [vyper.__version__, "0.3.10"]


@pytest.fixture(params=VERSIONS)
def version(request):
return request.param


@pytest.fixture
def blueprint_code(version):
return _blueprint_code.format(version)


@pytest.fixture
def factory_code(version):
return _factory_code.format(version)


def test_create2_address():
def test_create2_address(blueprint_code, factory_code):
blueprint = boa.loads_partial(blueprint_code).deploy_as_blueprint()
factory = boa.loads(factory_code)

Expand All @@ -31,7 +53,7 @@ def test_create2_address():
)


def test_create2_address_bad_salt():
def test_create2_address_bad_salt(blueprint_code):
blueprint = boa.loads_partial(blueprint_code).deploy_as_blueprint()
blueprint_bytecode = boa.env.get_code(to_canonical_address(blueprint.address))
with pytest.raises(ValueError) as e:
Expand Down

0 comments on commit 7f1c700

Please sign in to comment.