Skip to content

Commit

Permalink
chore: rename factory to blueprint (#2975)
Browse files Browse the repository at this point in the history
based on the premise that factory is already overloaded with other
meanings, both in object-oriented programming and smart contracts
  • Loading branch information
charles-cooper authored Jul 21, 2022
1 parent c3e43d7 commit dbcc9c7
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 49 deletions.
22 changes: 11 additions & 11 deletions docs/built-in-functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,10 @@ Vyper has three builtins for contract creation; all three contract creation buil
* Cheap to call (no ``DELEGATECALL`` overhead), expensive to create (200 gas per deployed byte)
* Does not have the ability to call a constructor
* Performs an ``EXTCODESIZE`` check to check there is code at ``target``
* ``create_from_factory(target: address, ...)``
* ``create_from_blueprint(target: address, ...)``
* Deploys a contract using the initcode stored at ``target``
* Cheap to call (no ``DELEGATECALL`` overhead), expensive to create (200 gas per deployed byte)
* Invokes constructor, requires a special "factory" contract to be deployed
* Invokes constructor, requires a special "blueprint" contract to be deployed
* Performs an ``EXTCODESIZE`` check to check there is code at ``target``

.. py:function:: create_minimal_proxy_to(target: address, value: uint256 = 0[, salt: bytes32]) -> address
Expand Down Expand Up @@ -179,11 +179,11 @@ Vyper has three builtins for contract creation; all three contract creation buil
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_factory(target: address, *args, value: uint256 = 0, code_offset=0, [, salt: bytes32]) -> address
.. py:function:: create_from_blueprint(target: address, *args, value: uint256 = 0, code_offset=0, [, 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 factory contract to invoke
* ``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)
* ``code_offset``: The offset to start the ``EXTCODECOPY`` from (Optional, default 0)
Expand All @@ -194,25 +194,25 @@ Vyper has three builtins for contract creation; all three contract creation buil
.. code-block:: python
@external
def foo(factory: address) -> address:
def foo(blueprint: address) -> address:
arg1: uint256 = 18
arg2: String = "some string"
return create_from_factory(factory, arg1, arg2, code_offset=1)
return create_from_blueprint(blueprint, arg1, arg2, code_offset=1)
.. note::

To properly deploy a factory contract, special deploy bytecode must be used. Deploying factory contracts is generally out of scope of this article, but the following preamble, prepended to regular deploy bytecode (output of ``vyper -f bytecode``), should deploy the factory contract in an ordinary contract creation transaction: ``deploy_preamble = "61" + <bytecode len in 4 hex characters> + "3d81600a3d39f3"``. To see an example of this, please see `the setup code for testing create_from_factory <https://github.com/vyperlang/vyper/blob/2adc34ffd3bee8b6dee90f552bbd9bb844509e19/tests/base_conftest.py#L130-L160>`_.
To properly deploy a blueprint contract, special deploy bytecode must be used. Deploying blueprint contracts is generally out of scope of this article, but the following preamble, prepended to regular deploy bytecode (output of ``vyper -f bytecode``), should deploy the blueprint in an ordinary contract creation transaction: ``deploy_preamble = "61" + <bytecode len in 4 hex characters> + "3d81600a3d39f3"``. To see an example of this, please see `the setup code for testing create_from_blueprint<https://github.com/vyperlang/vyper/blob/2adc34ffd3bee8b6dee90f552bbd9bb844509e19/tests/base_conftest.py#L130-L160>`_.

.. warning::

It is recommended to deploy factory contracts with an opcode preamble like ``0xfe`` 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 factory contract directly. The ``code_offset=`` kwarg is provided to enable this pattern:
It is recommended to deploy blueprints with the ERC5202 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:

.. code-block:: python
@external
def foo(factory: address) -> address:
# `factory` is a factory contract with some known preamble b"abcd..."
return create_from_factory(factory, code_offset=<preamble length>)
def foo(blueprint: address) -> address:
# `blueprint` is a blueprint contract with some known preamble b"abcd..."
return create_from_blueprint(blueprint, code_offset=<preamble length>)
.. py:function:: raw_call(to: address, data: Bytes, max_outsize: int = 0, gas: uint256 = gasLeft, value: uint256 = 0, is_delegate_call: bool = False, is_static_call: bool = False, revert_on_failure: bool = True) -> Bytes[max_outsize]
Expand Down
10 changes: 5 additions & 5 deletions tests/base_conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def _get_contract(w3, source_code, no_optimize, *args, **kwargs):
return w3.eth.contract(address, abi=abi, bytecode=bytecode, ContractFactoryClass=VyperContract)


def _deploy_factory_for(w3, source_code, no_optimize, initcode_prefix=b"", **kwargs):
def _deploy_blueprint_for(w3, source_code, no_optimize, initcode_prefix=b"", **kwargs):
out = compiler.compile_code(
source_code,
["abi", "bytecode"],
Expand Down Expand Up @@ -165,11 +165,11 @@ def factory(address):


@pytest.fixture(scope="module")
def deploy_factory_for(w3, no_optimize):
def deploy_factory_for(source_code, *args, **kwargs):
return _deploy_factory_for(w3, source_code, no_optimize, *args, **kwargs)
def deploy_blueprint_for(w3, no_optimize):
def deploy_blueprint_for(source_code, *args, **kwargs):
return _deploy_blueprint_for(w3, source_code, no_optimize, *args, **kwargs)

return deploy_factory_for
return deploy_blueprint_for


@pytest.fixture(scope="module")
Expand Down
58 changes: 29 additions & 29 deletions tests/parser/functions/test_create_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,42 +143,42 @@ def test(_salt: bytes32) -> address:
assert_tx_failed(lambda: c.test(salt, transact={}))


# test factory with various prefixes - 0xfe would block calls to the factory
# contract, and 0xfe65766d is an old magic from EIP154
@pytest.mark.parametrize("factory_prefix", [b"", b"\xfe", b"\xfe\x65\x76\x6d"])
def test_create_from_factory(
# 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"])
def test_create_from_blueprint(
get_contract,
deploy_factory_for,
deploy_blueprint_for,
w3,
keccak,
create2_address_of,
assert_tx_failed,
factory_prefix,
blueprint_prefix,
):
code = """
@external
def foo() -> uint256:
return 123
"""

prefix_len = len(factory_prefix)
prefix_len = len(blueprint_prefix)
deployer_code = f"""
created_address: public(address)
@external
def test(target: address):
self.created_address = create_from_factory(target, code_offset={prefix_len})
self.created_address = create_from_blueprint(target, code_offset={prefix_len})
@external
def test2(target: address, salt: bytes32):
self.created_address = create_from_factory(target, code_offset={prefix_len}, salt=salt)
self.created_address = create_from_blueprint(target, code_offset={prefix_len}, 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_factory_for(code, initcode_prefix=factory_prefix)
f, FooContract = deploy_blueprint_for(code, initcode_prefix=blueprint_prefix)

d = get_contract(deployer_code)

Expand All @@ -202,54 +202,54 @@ def test2(target: address, salt: bytes32):

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

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


def test_create_from_factory_bad_code_offset(
get_contract, get_contract_from_ir, deploy_factory_for, w3, assert_tx_failed
def test_create_from_blueprint_bad_code_offset(
get_contract, get_contract_from_ir, deploy_blueprint_for, w3, assert_tx_failed
):
deployer_code = """
FACTORY: immutable(address)
BLUEPRINT: immutable(address)
@external
def __init__(factory_address: address):
FACTORY = factory_address
def __init__(blueprint_address: address):
BLUEPRINT = blueprint_address
@external
def test(code_ofst: uint256) -> address:
return create_from_factory(FACTORY, code_offset=code_ofst)
return create_from_blueprint(BLUEPRINT, code_offset=code_ofst)
"""

# use a bunch of JUMPDEST + STOP instructions as factory code
# use a bunch of JUMPDEST + STOP instructions as blueprint code
# (as any STOP instruction returns valid code, split up with
# jumpdests as optimization fence)
initcode_len = 100
f = get_contract_from_ir(["deploy", 0, ["seq"] + ["jumpdest", "stop"] * (initcode_len // 2), 0])
factory_code = w3.eth.get_code(f.address)
print(factory_code)
blueprint_code = w3.eth.get_code(f.address)
print(blueprint_code)

d = get_contract(deployer_code, f.address)

# deploy with code_ofst=0 fine
d.test(0)

# deploy with code_ofst=len(factory) - 1 fine
# deploy with code_ofst=len(blueprint) - 1 fine
d.test(initcode_len - 1)

# code_offset=len(factory_code) NOT fine! would EXTCODECOPY empty initcode
# code_offset=len(blueprint) NOT fine! would EXTCODECOPY empty initcode
assert_tx_failed(lambda: d.test(initcode_len))

# code_offset=EIP_170_LIMIT definitely not fine!
assert_tx_failed(lambda: d.test(EIP_170_LIMIT))


# test create_from_factory with args
def test_create_from_factory_args(
get_contract, deploy_factory_for, w3, keccak, create2_address_of, assert_tx_failed
# test create_from_blueprint with args
def test_create_from_blueprint_args(
get_contract, deploy_blueprint_for, w3, keccak, create2_address_of, assert_tx_failed
):
code = """
struct Bar:
Expand Down Expand Up @@ -280,15 +280,15 @@ def bar() -> Bar:
@external
def test(target: address, arg1: String[128], arg2: Bar):
self.created_address = create_from_factory(target, arg1, arg2)
self.created_address = create_from_blueprint(target, arg1, arg2)
@external
def test2(target: address, arg1: String[128], arg2: Bar, salt: bytes32):
self.created_address = create_from_factory(target, arg1, arg2, salt=salt)
self.created_address = create_from_blueprint(target, arg1, arg2, salt=salt)
@external
def should_fail(target: address, arg1: String[129], arg2: Bar):
self.created_address = create_from_factory(target, arg1, arg2)
self.created_address = create_from_blueprint(target, arg1, arg2)
"""
FOO = "hello!"
BAR = ("world!",)
Expand All @@ -297,7 +297,7 @@ def should_fail(target: address, arg1: String[129], arg2: Bar):
foo_contract = get_contract(code, FOO, BAR)
expected_runtime_code = w3.eth.get_code(foo_contract.address)

f, FooContract = deploy_factory_for(code)
f, FooContract = deploy_blueprint_for(code)

d = get_contract(deployer_code)

Expand Down
8 changes: 4 additions & 4 deletions vyper/builtin_functions/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1836,9 +1836,9 @@ def _build_create_IR(self, expr, args, context, value, salt):
return b1.resolve(b2.resolve(b3.resolve(ir)))


class CreateFromFactory(_CreateBase):
class CreateFromBlueprint(_CreateBase):

_id = "create_from_factory"
_id = "create_from_blueprint"
_inputs = [("target", AddressDefinition())]
_kwargs = {
"value": KwargSettings(Uint256Definition(), zero_value),
Expand Down Expand Up @@ -2518,7 +2518,7 @@ def build_IR(self, expr, args, kwargs, context):
"create_minimal_proxy_to": CreateMinimalProxyTo(),
"create_forwarder_to": CreateForwarderTo(),
"create_copy_of": CreateCopyOf(),
"create_from_factory": CreateFromFactory(),
"create_from_blueprint": CreateFromBlueprint(),
"min": Min(),
"max": Max(),
"empty": Empty(),
Expand All @@ -2535,7 +2535,7 @@ def build_IR(self, expr, args, kwargs, context):
"create_minimal_proxy_to": CreateMinimalProxyTo(),
"create_forwarder_to": CreateForwarderTo(),
"create_copy_of": CreateCopyOf(),
"create_from_factory": CreateFromFactory(),
"create_from_blueprint": CreateFromBlueprint(),
}

BUILTIN_FUNCTIONS = {**STMT_DISPATCH_TABLE, **DISPATCH_TABLE}.keys()
Expand Down

0 comments on commit dbcc9c7

Please sign in to comment.