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

feat: default code offset = 3 #3454

Merged
merged 9 commits into from
Feb 21, 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
10 changes: 7 additions & 3 deletions docs/built-in-functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -184,15 +184,15 @@ Vyper has three built-ins for contract creation; all three contract creation bui
The implementation of ``create_copy_of`` assumes that the code at ``target`` is smaller than 16MB. While this is much larger than the EIP-170 constraint of 24KB, it is a conservative size limit intended to future-proof deployer contracts in case the EIP-170 constraint is lifted. If the code at ``target`` is larger than 16MB, the behavior of ``create_copy_of`` is undefined.


.. py:function:: create_from_blueprint(target: address, *args, value: uint256 = 0, raw_args: bool = False, code_offset: int = 0, [, salt: bytes32]) -> address
.. py:function:: create_from_blueprint(target: address, *args, value: uint256 = 0, raw_args: bool = False, code_offset: int = 3, [, salt: bytes32]) -> address

Copy the code of ``target`` into memory and execute it as initcode. In other words, this operation interprets the code at ``target`` not as regular runtime code, but directly as initcode. The ``*args`` are interpreted as constructor arguments, and are ABI-encoded and included when executing the initcode.

* ``target``: Address of the blueprint to invoke
* ``*args``: Constructor arguments to forward to the initcode.
* ``value``: The wei value to send to the new contract address (Optional, default 0)
* ``raw_args``: If ``True``, ``*args`` must be a single ``Bytes[...]`` argument, which will be interpreted as a raw bytes buffer to forward to the create operation (which is useful for instance, if pre- ABI-encoded data is passed in from elsewhere). (Optional, default ``False``)
* ``code_offset``: The offset to start the ``EXTCODECOPY`` from (Optional, default 0)
* ``code_offset``: The offset to start the ``EXTCODECOPY`` from (Optional, default 3)
* ``salt``: A ``bytes32`` value utilized by the deterministic ``CREATE2`` opcode (Optional, if not supplied, ``CREATE`` is used)

Returns the address of the created contract. If the create operation fails (for instance, in the case of a ``CREATE2`` collision), execution will revert. If ``code_offset >= target.codesize`` (ex. if there is no code at ``target``), execution will revert.
Expand All @@ -209,9 +209,13 @@ Vyper has three built-ins for contract creation; all three contract creation bui

To properly deploy a blueprint contract, special deploy bytecode must be used. The output of ``vyper -f blueprint_bytecode`` will produce bytecode which deploys an ERC-5202 compatible blueprint.

.. note::

Prior to Vyper version ``0.4.0``, the ``code_offset`` parameter defaulted to ``0``.

.. warning::

It is recommended to deploy blueprints with the ERC-5202 preamble ``0xFE7100`` to guard them from being called as regular contracts. This is particularly important for factories where the constructor has side effects (including ``SELFDESTRUCT``!), as those could get executed by *anybody* calling the blueprint contract directly. The ``code_offset=`` kwarg is provided to enable this pattern:
It is recommended to deploy blueprints with an `ERC-5202 <https://eips.ethereum.org/EIPS/eip-5202>`_ preamble like ``0xFE7100`` to guard them from being called as regular contracts. This is particularly important for factories where the constructor has side effects (including ``SELFDESTRUCT``!), as those could get executed by *anybody* calling the blueprint contract directly. The ``code_offset=`` kwarg is provided (and defaults to the ERC-5202 default of 3) to enable this pattern:

.. code-block:: vyper

Expand Down
5 changes: 4 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from vyper.compiler.input_bundle import FilesystemInputBundle, InputBundle
from vyper.compiler.settings import OptimizationLevel, Settings, _set_debug_mode
from vyper.ir import compile_ir, optimizer
from vyper.utils import ERC5202_PREFIX

# Import the base fixtures
pytest_plugins = ["tests.fixtures.memorymock"]
Expand Down Expand Up @@ -377,7 +378,9 @@ def get_contract_module(source_code, *args, **kwargs):
return get_contract_module


def _deploy_blueprint_for(w3, source_code, optimize, output_formats, initcode_prefix=b"", **kwargs):
def _deploy_blueprint_for(
w3, source_code, optimize, output_formats, initcode_prefix=ERC5202_PREFIX, **kwargs
):
settings = Settings()
settings.evm_version = kwargs.pop("evm_version", None)
settings.optimize = optimize
Expand Down
68 changes: 63 additions & 5 deletions tests/functional/builtins/codegen/test_create_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import vyper.ir.compile_ir as compile_ir
from vyper.codegen.ir_node import IRnode
from vyper.compiler.settings import OptimizationLevel
from vyper.utils import EIP_170_LIMIT, checksum_encode, keccak256
from vyper.utils import EIP_170_LIMIT, ERC5202_PREFIX, checksum_encode, keccak256


# initcode used by create_minimal_proxy_to
Expand Down Expand Up @@ -148,7 +148,7 @@ def test(_salt: bytes32) -> address:

# test blueprints with various prefixes - 0xfe would block calls to the blueprint
# contract, and 0xfe7100 is ERC5202 magic
@pytest.mark.parametrize("blueprint_prefix", [b"", b"\xfe", b"\xfe\71\x00"])
@pytest.mark.parametrize("blueprint_prefix", [b"", b"\xfe", ERC5202_PREFIX])
def test_create_from_blueprint(
get_contract, deploy_blueprint_for, w3, keccak, create2_address_of, tx_failed, blueprint_prefix
):
Expand Down Expand Up @@ -208,6 +208,66 @@ def test2(target: address, salt: bytes32):
d.test2(f.address, salt)


# test blueprints with 0xfe7100 prefix, which is the EIP 5202 standard.
# code offset by default should be 3 here.
def test_create_from_blueprint_default_offset(
get_contract, deploy_blueprint_for, w3, keccak, create2_address_of, tx_failed
):
code = """
@external
def foo() -> uint256:
return 123
"""

deployer_code = """
created_address: public(address)

@external
def test(target: address):
self.created_address = create_from_blueprint(target)

@external
def test2(target: address, salt: bytes32):
self.created_address = create_from_blueprint(target, salt=salt)
"""

# deploy a foo so we can compare its bytecode with factory deployed version
foo_contract = get_contract(code)
expected_runtime_code = w3.eth.get_code(foo_contract.address)

f, FooContract = deploy_blueprint_for(code)

d = get_contract(deployer_code)

d.test(f.address, transact={})

test = FooContract(d.created_address())
assert w3.eth.get_code(test.address) == expected_runtime_code
assert test.foo() == 123

# extcodesize check
zero_address = "0x" + "00" * 20
with tx_failed():
d.test(zero_address)

# now same thing but with create2
salt = keccak(b"vyper")
d.test2(f.address, salt, transact={})

test = FooContract(d.created_address())
assert w3.eth.get_code(test.address) == expected_runtime_code
assert test.foo() == 123

# check if the create2 address matches our offchain calculation
initcode = w3.eth.get_code(f.address)
initcode = initcode[len(ERC5202_PREFIX) :] # strip the prefix
assert HexBytes(test.address) == create2_address_of(d.address, salt, initcode)

# can't collide addresses
with tx_failed():
d.test2(f.address, salt)


def test_create_from_blueprint_bad_code_offset(
get_contract, get_contract_from_ir, deploy_blueprint_for, w3, tx_failed
):
Expand Down Expand Up @@ -238,8 +298,6 @@ def test(code_ofst: uint256) -> address:
tx_info = {"from": w3.eth.accounts[0], "value": 0, "gasPrice": 0}
tx_hash = deploy_transaction.transact(tx_info)
blueprint_address = w3.eth.get_transaction_receipt(tx_hash)["contractAddress"]
blueprint_code = w3.eth.get_code(blueprint_address)
print("BLUEPRINT CODE:", blueprint_code)

d = get_contract(deployer_code, blueprint_address)

Expand Down Expand Up @@ -320,7 +378,7 @@ def should_fail(target: address, arg1: String[129], arg2: Bar):

d = get_contract(deployer_code)

initcode = w3.eth.get_code(f.address)
initcode = w3.eth.get_code(f.address)[3:]

d.test(f.address, FOO, BAR, transact={})

Expand Down
2 changes: 1 addition & 1 deletion vyper/builtins/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1828,7 +1828,7 @@ class CreateFromBlueprint(_CreateBase):
"value": KwargSettings(UINT256_T, zero_value),
"salt": KwargSettings(BYTES32_T, empty_value),
"raw_args": KwargSettings(BoolT(), False, require_literal=True),
"code_offset": KwargSettings(UINT256_T, zero_value),
"code_offset": KwargSettings(UINT256_T, IRnode.from_list(3, typ=UINT256_T)),
}
_has_varargs = True

Expand Down
4 changes: 2 additions & 2 deletions vyper/compiler/phases.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from vyper.semantics.types.function import ContractFunctionT
from vyper.semantics.types.module import ModuleT
from vyper.typing import StorageLayout
from vyper.utils import ERC5202_PREFIX
from vyper.venom import generate_assembly_experimental, generate_ir

DEFAULT_CONTRACT_PATH = PurePath("VyperContract.vy")
Expand Down Expand Up @@ -228,8 +229,7 @@ def bytecode_runtime(self) -> bytes:

@cached_property
def blueprint_bytecode(self) -> bytes:
blueprint_preamble = b"\xFE\x71\x00" # ERC5202 preamble
blueprint_bytecode = blueprint_preamble + self.bytecode
blueprint_bytecode = ERC5202_PREFIX + self.bytecode

# the length of the deployed code in bytes
len_bytes = len(blueprint_bytecode).to_bytes(2, "big")
Expand Down
1 change: 1 addition & 0 deletions vyper/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ class SizeLimits:


EIP_170_LIMIT = 0x6000 # 24kb
ERC5202_PREFIX = b"\xFE\x71\x00" # default prefix from ERC-5202

SHA3_BASE = 30
SHA3_PER_WORD = 6
Expand Down
Loading