Skip to content

Commit

Permalink
feat: implement deterministic proxies with CREATE2 and EIP1167 (#2460)
Browse files Browse the repository at this point in the history
* feat: add create2 functionality to `create_forwarder_to`

* test: create2 forwarder to syntax

* test: add create2 fixture

* test: runtime create2

* fix: forgot a comma

* fix: remove is_deterministic kwarg

* docs: update create_forwarder_to, salt kwarg

* fix: rename boolean variable `is_deterministic` -> `should_use_create2`

stolen from charles, booleans affecting control flow, better to name them `should_*`

* fix: rename `create2` fixture -> `create2_address_of`
  • Loading branch information
skellet0r authored Sep 18, 2021
1 parent b418e9f commit fecf04c
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 3 deletions.
3 changes: 2 additions & 1 deletion docs/built-in-functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ Bitwise Operations
Chain Interaction
=================

.. py:function:: create_forwarder_to(target: address, value: uint256 = 0) -> address
.. py:function:: create_forwarder_to(target: address, value: uint256 = 0[, salt: bytes32]) -> address
Deploys a small contract that duplicates the logic of the contract at ``target``, but has it's own state since every call to ``target`` is made using ``DELEGATECALL`` to ``target``. To the end user, this should be indistinguishable from an independantly deployed contract with the same code as ``target``.

Expand All @@ -104,6 +104,7 @@ Chain Interaction

* ``target``: Address of the contract to duplicate
* ``value``: The wei value to send to the new contract address (Optional, default 0)
* ``salt``: A ``bytes32`` value utilized by the ``CREATE2`` opcode (Optional, if supplied deterministic deployment is done via ``CREATE2``)

Returns the address of the duplicated contract.

Expand Down
13 changes: 13 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import pytest
from eth_tester import EthereumTester
from hexbytes import HexBytes
from web3 import Web3
from web3.providers.eth_tester import EthereumTesterProvider

Expand Down Expand Up @@ -173,3 +174,15 @@ def search_for_sublist(lll, sublist):
return False

return search_for_sublist


@pytest.fixture
def create2_address_of(keccak):
def _f(_addr, _salt, _initcode):
prefix = HexBytes("0xff")
addr = HexBytes(_addr)
salt = HexBytes(_salt)
initcode = HexBytes(_initcode)
return keccak(prefix + addr + salt + keccak(initcode))[12:]

return _f
26 changes: 26 additions & 0 deletions tests/parser/functions/test_create_with_code_of.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
from hexbytes import HexBytes


def initcode(_addr):
addr = HexBytes(_addr)
pre = HexBytes("0x602D3D8160093D39F3363d3d373d3d3d363d73")
post = HexBytes("0x5af43d82803e903d91602b57fd5bf3")
return HexBytes(pre + (addr + HexBytes(0) * (20 - len(addr))) + post)


def test_create_forwarder_to_create(get_contract):
code = """
main: address
Expand Down Expand Up @@ -91,3 +101,19 @@ def test2(a: uint256) -> Bytes[100]:

assert receipt["status"] == 0
assert receipt["gasUsed"] < GAS_SENT


def test_create2_forwarder_to_create(get_contract, create2_address_of, keccak):
code = """
main: address
@external
def test(_salt: bytes32) -> address:
self.main = create_forwarder_to(self, salt=_salt)
return self.main
"""

c = get_contract(code)

salt = keccak(b"vyper")
assert HexBytes(c.test(salt)) == create2_address_of(c.address, salt, initcode(c.address))
20 changes: 20 additions & 0 deletions tests/parser/syntax/test_create_with_code_of.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@
@external
def foo():
x: address = create_forwarder_to(0x1234567890123456789012345678901234567890, value=4, value=9)
""",
"""
@external
def foo(_salt: bytes32):
x: address = create_forwarder_to(
0x1234567890123456789012345678901234567890, salt=keccak256(b"Vyper Rocks!"), salt=_salt
)
""",
]


Expand Down Expand Up @@ -38,6 +45,19 @@ def foo():
def foo():
x: address = create_forwarder_to(0x1234567890123456789012345678901234567890, value=9)
""",
"""
@external
def foo():
x: address = create_forwarder_to(
0x1234567890123456789012345678901234567890,
salt=keccak256(b"Vyper Rocks!")
)
""",
"""
@external
def foo(_salt: bytes32):
x: address = create_forwarder_to(0x1234567890123456789012345678901234567890, salt=_salt)
""",
]


Expand Down
15 changes: 13 additions & 2 deletions vyper/builtin_functions/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,7 @@ def build_LLL(self, expr, args, kwargs, context):


zero_value = LLLnode.from_list(0, typ=BaseType("uint256"))
empty_value = LLLnode.from_list(0, typ=BaseType("bytes32"))
false_value = LLLnode.from_list(0, typ=BaseType("bool", is_literal=True))
true_value = LLLnode.from_list(1, typ=BaseType("bool", is_literal=True))

Expand Down Expand Up @@ -1545,12 +1546,15 @@ class CreateForwarderTo(_SimpleBuiltinFunction):

_id = "create_forwarder_to"
_inputs = [("target", AddressDefinition())]
_kwargs = {"value": Optional("uint256", zero_value)}
_kwargs = {"value": Optional("uint256", zero_value), "salt": Optional("bytes32", empty_value)}
_return_type = AddressDefinition()

@validate_inputs
def build_LLL(self, expr, args, kwargs, context):
value = kwargs["value"]
salt = kwargs["salt"]
should_use_create2 = "salt" in [kwarg.arg for kwarg in expr.keywords]

if context.is_constant():
raise StateAccessViolation(
f"Cannot make calls from {context.pp_constancy()}", expr,
Expand All @@ -1572,13 +1576,20 @@ def build_LLL(self, expr, args, kwargs, context):
else:
target_address = ["mul", args[0], 2 ** 96]

op = "create"
op_args = [value, placeholder, preamble_length + 20 + len(forwarder_post_evm)]

if should_use_create2:
op = "create2"
op_args.append(salt)

return LLLnode.from_list(
[
"seq",
["mstore", placeholder, forwarder_preamble],
["mstore", ["add", placeholder, preamble_length], target_address],
["mstore", ["add", placeholder, preamble_length + 20], forwarder_post],
["create", value, placeholder, preamble_length + 20 + len(forwarder_post_evm)],
[op, *op_args],
],
typ=BaseType("address"),
pos=getpos(expr),
Expand Down

0 comments on commit fecf04c

Please sign in to comment.