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[lang]: add raw_create() builtin #4204

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
37 changes: 37 additions & 0 deletions tests/functional/builtins/codegen/test_create_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import vyper.ir.compile_ir as compile_ir
from tests.utils import ZERO_ADDRESS
from vyper.codegen.ir_node import IRnode
from vyper.compiler import compile_code
from vyper.compiler.settings import OptimizationLevel
from vyper.utils import EIP_170_LIMIT, ERC5202_PREFIX, checksum_encode, keccak256

Expand Down Expand Up @@ -746,3 +747,39 @@ def test(target: address) -> address:
c.test(c.address, value=2)
test1 = c.address
assert env.get_code(test1) == bytecode


def test_raw_create(get_contract, env):
to_deploy_code = """
foo: public(uint256)
"""

out = compile_code(to_deploy_code, output_formats=["bytecode", "bytecode_runtime"])
initcode = bytes.fromhex(out["bytecode"].removeprefix("0x"))
runtime = bytes.fromhex(out["bytecode_runtime"].removeprefix("0x"))

deployer_code = f"""
@external
def deploy_from_literal() -> address:
return raw_create({initcode})

@external
def deploy_from_calldata(s: Bytes[1024]) -> address:
return raw_create(s)

@external
def deploy_from_memory() -> address:
s: Bytes[1024] = {initcode}
return raw_create(s)
"""

deployer = get_contract(deployer_code)

res = deployer.deploy_from_literal()
assert env.get_code(res) == runtime

res = deployer.deploy_from_memory()
assert env.get_code(res) == runtime

res = deployer.deploy_from_calldata(initcode)
assert env.get_code(res) == runtime
41 changes: 41 additions & 0 deletions vyper/builtins/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
from vyper.utils import (
DECIMAL_DIVISOR,
EIP_170_LIMIT,
EIP_3860_LIMIT,
SHA3_PER_WORD,
MemoryPositions,
bytes_to_int,
Expand Down Expand Up @@ -1679,6 +1680,45 @@ def build_IR(self, expr, args, kwargs, context):
)


class RawCreate(_CreateBase):
_id = "raw_create"
_inputs = [("bytecode", BytesT(EIP_3860_LIMIT))]
_has_varargs = True

def _add_gas_estimate(self, args, should_use_create2):
return _create_addl_gas_estimate(EIP_170_LIMIT, should_use_create2)

def _build_create_IR(self, expr, args, context, value, salt, revert_on_failure):
initcode = args[0]
ctor_args = args[1:]

ctor_args = [ensure_in_memory(arg, context) for arg in ctor_args]

# encode the varargs
to_encode = ir_tuple_from_args(ctor_args)
bufsz = initcode.typ.maxlen + to_encode.typ.abi_type.size_bound()

buf = context.new_internal_variable(get_type_for_exact_size(bufsz))

ret = ["seq"]

with scope_multi((initcode, value, salt), ("initcode", "value", "salt")) as (
b1,
(initcode, value, salt),
):
bytecode_len = get_bytearray_length(initcode)

maxlen = initcode.typ.maxlen
ret.append(copy_bytes(buf, bytes_data_ptr(initcode), bytecode_len, maxlen))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it shouldn't be problematic that copy_bytes might pad to ceil32, right?

it might copy some dirty data, but if arguments are provided, those should overwrite it

if no args are provided, we still operate with bytecode_len which will lead to ignoring the dirty data


argbuf = add_ofst(buf, bytecode_len)
argslen = abi_encode(argbuf, to_encode, context, bufsz=bufsz, returns_len=True)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this reminds of something: #4202

total_len = add_ofst(argbuf, argslen)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this is wrong - isn't the length now absolute wrt 0?
shouldn't it be smth like total_len = ["sub", add_ofst(argbuf, argslen), buf]

ret.append(_create_ir(value, buf, total_len, salt, revert_on_failure))

return b1.resolve(IRnode.from_list(ret))


class CreateMinimalProxyTo(_CreateBase):
# create an EIP1167 "minimal proxy" to the target contract

Expand Down Expand Up @@ -2683,6 +2723,7 @@ def _try_fold(self, node):
"breakpoint": Breakpoint(),
"selfdestruct": SelfDestruct(),
"raw_call": RawCall(),
"raw_create": RawCreate(),
"raw_log": RawLog(),
"raw_revert": RawRevert(),
"create_minimal_proxy_to": CreateMinimalProxyTo(),
Expand Down
3 changes: 3 additions & 0 deletions vyper/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,8 +465,11 @@ def quantize(d: decimal.Decimal, places=MAX_DECIMAL_PLACES, rounding_mode=decima


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

assert EIP_3860_LIMIT == 49152 # directly from the EIP

SHA3_BASE = 30
SHA3_PER_WORD = 6

Expand Down
Loading