Skip to content
New issue

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

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

Already on GitHub? # to your account

feat: add deploy_as_blueprint to VVMDeployer #311

Merged
merged 16 commits into from
Oct 2, 2024
Merged
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
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):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blueprint_preamble=None, **kwargs are unused now

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice catch, thanks

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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