Skip to content

Commit fecf04c

Browse files
authored
feat: implement deterministic proxies with CREATE2 and EIP1167 (#2460)
* 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`
1 parent b418e9f commit fecf04c

File tree

5 files changed

+74
-3
lines changed

5 files changed

+74
-3
lines changed

docs/built-in-functions.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ Bitwise Operations
9494
Chain Interaction
9595
=================
9696

97-
.. py:function:: create_forwarder_to(target: address, value: uint256 = 0) -> address
97+
.. py:function:: create_forwarder_to(target: address, value: uint256 = 0[, salt: bytes32]) -> address
9898
9999
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``.
100100

@@ -104,6 +104,7 @@ Chain Interaction
104104

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

108109
Returns the address of the duplicated contract.
109110

tests/conftest.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import pytest
55
from eth_tester import EthereumTester
6+
from hexbytes import HexBytes
67
from web3 import Web3
78
from web3.providers.eth_tester import EthereumTesterProvider
89

@@ -173,3 +174,15 @@ def search_for_sublist(lll, sublist):
173174
return False
174175

175176
return search_for_sublist
177+
178+
179+
@pytest.fixture
180+
def create2_address_of(keccak):
181+
def _f(_addr, _salt, _initcode):
182+
prefix = HexBytes("0xff")
183+
addr = HexBytes(_addr)
184+
salt = HexBytes(_salt)
185+
initcode = HexBytes(_initcode)
186+
return keccak(prefix + addr + salt + keccak(initcode))[12:]
187+
188+
return _f

tests/parser/functions/test_create_with_code_of.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
from hexbytes import HexBytes
2+
3+
4+
def initcode(_addr):
5+
addr = HexBytes(_addr)
6+
pre = HexBytes("0x602D3D8160093D39F3363d3d373d3d3d363d73")
7+
post = HexBytes("0x5af43d82803e903d91602b57fd5bf3")
8+
return HexBytes(pre + (addr + HexBytes(0) * (20 - len(addr))) + post)
9+
10+
111
def test_create_forwarder_to_create(get_contract):
212
code = """
313
main: address
@@ -91,3 +101,19 @@ def test2(a: uint256) -> Bytes[100]:
91101

92102
assert receipt["status"] == 0
93103
assert receipt["gasUsed"] < GAS_SENT
104+
105+
106+
def test_create2_forwarder_to_create(get_contract, create2_address_of, keccak):
107+
code = """
108+
main: address
109+
110+
@external
111+
def test(_salt: bytes32) -> address:
112+
self.main = create_forwarder_to(self, salt=_salt)
113+
return self.main
114+
"""
115+
116+
c = get_contract(code)
117+
118+
salt = keccak(b"vyper")
119+
assert HexBytes(c.test(salt)) == create2_address_of(c.address, salt, initcode(c.address))

tests/parser/syntax/test_create_with_code_of.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,14 @@
99
@external
1010
def foo():
1111
x: address = create_forwarder_to(0x1234567890123456789012345678901234567890, value=4, value=9)
12+
""",
1213
"""
14+
@external
15+
def foo(_salt: bytes32):
16+
x: address = create_forwarder_to(
17+
0x1234567890123456789012345678901234567890, salt=keccak256(b"Vyper Rocks!"), salt=_salt
18+
)
19+
""",
1320
]
1421

1522

@@ -38,6 +45,19 @@ def foo():
3845
def foo():
3946
x: address = create_forwarder_to(0x1234567890123456789012345678901234567890, value=9)
4047
""",
48+
"""
49+
@external
50+
def foo():
51+
x: address = create_forwarder_to(
52+
0x1234567890123456789012345678901234567890,
53+
salt=keccak256(b"Vyper Rocks!")
54+
)
55+
""",
56+
"""
57+
@external
58+
def foo(_salt: bytes32):
59+
x: address = create_forwarder_to(0x1234567890123456789012345678901234567890, salt=_salt)
60+
""",
4161
]
4262

4363

vyper/builtin_functions/functions.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,7 @@ def build_LLL(self, expr, args, kwargs, context):
10121012

10131013

10141014
zero_value = LLLnode.from_list(0, typ=BaseType("uint256"))
1015+
empty_value = LLLnode.from_list(0, typ=BaseType("bytes32"))
10151016
false_value = LLLnode.from_list(0, typ=BaseType("bool", is_literal=True))
10161017
true_value = LLLnode.from_list(1, typ=BaseType("bool", is_literal=True))
10171018

@@ -1545,12 +1546,15 @@ class CreateForwarderTo(_SimpleBuiltinFunction):
15451546

15461547
_id = "create_forwarder_to"
15471548
_inputs = [("target", AddressDefinition())]
1548-
_kwargs = {"value": Optional("uint256", zero_value)}
1549+
_kwargs = {"value": Optional("uint256", zero_value), "salt": Optional("bytes32", empty_value)}
15491550
_return_type = AddressDefinition()
15501551

15511552
@validate_inputs
15521553
def build_LLL(self, expr, args, kwargs, context):
15531554
value = kwargs["value"]
1555+
salt = kwargs["salt"]
1556+
should_use_create2 = "salt" in [kwarg.arg for kwarg in expr.keywords]
1557+
15541558
if context.is_constant():
15551559
raise StateAccessViolation(
15561560
f"Cannot make calls from {context.pp_constancy()}", expr,
@@ -1572,13 +1576,20 @@ def build_LLL(self, expr, args, kwargs, context):
15721576
else:
15731577
target_address = ["mul", args[0], 2 ** 96]
15741578

1579+
op = "create"
1580+
op_args = [value, placeholder, preamble_length + 20 + len(forwarder_post_evm)]
1581+
1582+
if should_use_create2:
1583+
op = "create2"
1584+
op_args.append(salt)
1585+
15751586
return LLLnode.from_list(
15761587
[
15771588
"seq",
15781589
["mstore", placeholder, forwarder_preamble],
15791590
["mstore", ["add", placeholder, preamble_length], target_address],
15801591
["mstore", ["add", placeholder, preamble_length + 20], forwarder_post],
1581-
["create", value, placeholder, preamble_length + 20 + len(forwarder_post_evm)],
1592+
[op, *op_args],
15821593
],
15831594
typ=BaseType("address"),
15841595
pos=getpos(expr),

0 commit comments

Comments
 (0)