diff --git a/docs/built-in-functions.rst b/docs/built-in-functions.rst index afb64e71ca..f2f6632906 100644 --- a/docs/built-in-functions.rst +++ b/docs/built-in-functions.rst @@ -184,7 +184,7 @@ 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. @@ -192,7 +192,7 @@ Vyper has three built-ins for contract creation; all three contract creation bui * ``*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. @@ -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 `_ 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 diff --git a/tests/conftest.py b/tests/conftest.py index 6eb34a3e0a..201f723efa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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"] @@ -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 diff --git a/tests/functional/builtins/codegen/test_create_functions.py b/tests/functional/builtins/codegen/test_create_functions.py index 0aa718157c..75b10e47b6 100644 --- a/tests/functional/builtins/codegen/test_create_functions.py +++ b/tests/functional/builtins/codegen/test_create_functions.py @@ -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 @@ -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 ): @@ -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 ): @@ -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) @@ -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={}) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 7575f4d77e..de0158aba4 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -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 diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index f7eccdf214..af94011633 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -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") @@ -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") diff --git a/vyper/utils.py b/vyper/utils.py index ab4d789aa4..26869a6def 100644 --- a/vyper/utils.py +++ b/vyper/utils.py @@ -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