Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/vyperlang/vyper into refa…
Browse files Browse the repository at this point in the history
…ctor/folding_alt
  • Loading branch information
tserg committed Sep 27, 2023
2 parents 04d477a + 2bdbd84 commit 4e94eed
Show file tree
Hide file tree
Showing 13 changed files with 132 additions and 48 deletions.
13 changes: 9 additions & 4 deletions docs/built-in-functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -184,13 +184,14 @@ 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, code_offset=0, [, salt: bytes32]) -> address
.. py:function:: create_from_blueprint(target: address, *args, value: uint256 = 0, raw_args: bool = False, code_offset: int = 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 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)
* ``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)
* ``salt``: A ``bytes32`` value utilized by the deterministic ``CREATE2`` opcode (Optional, if not supplied, ``CREATE`` is used)

Expand All @@ -201,7 +202,7 @@ Vyper has three built-ins for contract creation; all three contract creation bui
@external
def foo(blueprint: address) -> address:
arg1: uint256 = 18
arg2: String = "some string"
arg2: String[32] = "some string"
return create_from_blueprint(blueprint, arg1, arg2, code_offset=1)
.. note::
Expand All @@ -226,7 +227,7 @@ Vyper has three built-ins for contract creation; all three contract creation bui
* ``to``: Destination address to call to
* ``data``: Data to send to the destination address
* ``max_outsize``: Maximum length of the bytes array returned from the call. If the returned call data exceeds this length, only this number of bytes is returned. (Optional, default ``0``)
* ``gas``: The amount of gas to attach to the call. If not set, all remaining gas is forwarded.
* ``gas``: The amount of gas to attach to the call. (Optional, defaults to ``msg.gas``).
* ``value``: The wei value to send to the address (Optional, default ``0``)
* ``is_delegate_call``: If ``True``, the call will be sent as ``DELEGATECALL`` (Optional, default ``False``)
* ``is_static_call``: If ``True``, the call will be sent as ``STATICCALL`` (Optional, default ``False``)
Expand Down Expand Up @@ -264,6 +265,10 @@ Vyper has three built-ins for contract creation; all three contract creation bui
assert success
return response
.. note::

Regarding "forwarding all gas", note that, while Vyper will provide ``msg.gas`` to the call, in practice, there are some subtleties around forwarding all remaining gas on the EVM which are out of scope of this documentation and could be subject to change. For instance, see the language in EIP-150 around "all but one 64th".

.. py:function:: raw_log(topics: bytes32[4], data: Union[Bytes, bytes32]) -> None
Provides low level access to the ``LOG`` opcodes, emitting a log without having to specify an ABI type.
Expand Down Expand Up @@ -500,7 +505,7 @@ Data Manipulation

* ``b``: ``Bytes`` list to extract from
* ``start``: Start point to extract from
* ``output_type``: Type of output (``bytes32``, ``integer``, or ``address``). Defaults to ``bytes32``.
* ``output_type``: Type of output (``bytesM``, ``integer``, or ``address``). Defaults to ``bytes32``.

Returns a value of the type specified by ``output_type``.

Expand Down
1 change: 1 addition & 0 deletions docs/compiling-a-contract.rst
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ The following is a list of supported EVM versions, and changes in the compiler i
- The ``transient`` keyword allows declaration of variables which live in transient storage
- Functions marked with ``@nonreentrant`` are protected with TLOAD/TSTORE instead of SLOAD/SSTORE
- The ``MCOPY`` opcode will be generated automatically by the compiler for most memory operations.



Expand Down
6 changes: 6 additions & 0 deletions tests/cli/vyper_json/test_output_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,9 @@ def test_solc_style():
input_json = {"settings": {"outputSelection": {"foo.vy": {"": ["abi"], "foo.vy": ["ir"]}}}}
sources = {"foo.vy": ""}
assert get_input_dict_output_formats(input_json, sources) == {"foo.vy": ["abi", "ir_dict"]}


def test_metadata():
input_json = {"settings": {"outputSelection": {"*": ["metadata"]}}}
sources = {"foo.vy": ""}
assert get_input_dict_output_formats(input_json, sources) == {"foo.vy": ["metadata"]}
28 changes: 28 additions & 0 deletions tests/parser/functions/test_abi_decode.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,34 @@ def abi_decode(x: Bytes[96]) -> (uint256, uint256):
assert_tx_failed(lambda: c.abi_decode(input_))


def test_clamper_nested_uint8(get_contract, assert_tx_failed):
# check that _abi_decode clamps on word-types even when it is in a nested expression
# decode -> validate uint8 -> revert if input >= 256 -> cast back to uint256
contract = """
@external
def abi_decode(x: uint256) -> uint256:
a: uint256 = convert(_abi_decode(slice(msg.data, 4, 32), (uint8)), uint256)
return a
"""
c = get_contract(contract)
assert c.abi_decode(255) == 255
assert_tx_failed(lambda: c.abi_decode(256))


def test_clamper_nested_bytes(get_contract, assert_tx_failed):
# check that _abi_decode clamps dynamic even when it is in a nested expression
# decode -> validate Bytes[20] -> revert if len(input) > 20 -> convert back to -> add 1
contract = """
@external
def abi_decode(x: Bytes[96]) -> Bytes[21]:
a: Bytes[21] = concat(b"a", _abi_decode(x, Bytes[20]))
return a
"""
c = get_contract(contract)
assert c.abi_decode(abi.encode("(bytes)", (b"bc",))) == b"abc"
assert_tx_failed(lambda: c.abi_decode(abi.encode("(bytes)", (b"a" * 22,))))


@pytest.mark.parametrize(
"output_typ,input_",
[
Expand Down
45 changes: 45 additions & 0 deletions tests/parser/syntax/test_abi_decode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import pytest

from vyper import compiler
from vyper.exceptions import TypeMismatch

fail_list = [
(
"""
@external
def foo(j: uint256) -> bool:
s: bool = _abi_decode(j, bool, unwrap_tuple= False)
return s
""",
TypeMismatch,
),
(
"""
@external
def bar(j: String[32]) -> bool:
s: bool = _abi_decode(j, bool, unwrap_tuple= False)
return s
""",
TypeMismatch,
),
]


@pytest.mark.parametrize("bad_code,exc", fail_list)
def test_abi_encode_fail(bad_code, exc):
with pytest.raises(exc):
compiler.compile_code(bad_code)


valid_list = [
"""
@external
def foo(x: Bytes[32]) -> uint256:
return _abi_decode(x, uint256)
"""
]


@pytest.mark.parametrize("good_code", valid_list)
def test_abi_encode_success(good_code):
assert compiler.compile_code(good_code) is not None
58 changes: 26 additions & 32 deletions vyper/builtins/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
get_type_for_exact_size,
ir_tuple_from_args,
make_setter,
needs_external_call_wrap,
promote_signed_int,
sar,
shl,
Expand Down Expand Up @@ -1425,7 +1424,7 @@ class BitwiseNot(BuiltinFunction):

def evaluate(self, node):
if not self.__class__._warned:
vyper_warn("`bitwise_not()` is deprecated! Please use the ^ operator instead.")
vyper_warn("`bitwise_not()` is deprecated! Please use the ~ operator instead.")
self.__class__._warned = True

validate_call_args(node, 1)
Expand Down Expand Up @@ -1924,9 +1923,8 @@ def _build_create_IR(self, expr, args, context, value, salt, code_offset, raw_ar

# copy the target code into memory.
# layout starting from mem_ofst:
# 00...00 (22 0's) | preamble | bytecode
# <target initcode> | <abi-encoded args OR arg buffer if raw_arg=True>
ir.append(["extcodecopy", target, mem_ofst, code_offset, codesize])

ir.append(copy_bytes(add_ofst(mem_ofst, codesize), argbuf, encoded_args_len, bufsz))

# theoretically, dst = "msize", but just be safe.
Expand Down Expand Up @@ -2384,8 +2382,6 @@ def build_IR(self, expr, args, kwargs, context):
class ABIEncode(BuiltinFunction):
_id = "_abi_encode" # TODO prettier to rename this to abi.encode
# signature: *, ensure_tuple=<literal_bool> -> Bytes[<calculated len>]
# (check the signature manually since we have no utility methods
# to handle varargs.)
# explanation of ensure_tuple:
# default is to force even a single value into a tuple,
# e.g. _abi_encode(bytes) -> _abi_encode((bytes,))
Expand Down Expand Up @@ -2546,24 +2542,11 @@ def build_IR(self, expr, args, kwargs, context):
)

data = ensure_in_memory(data, context)

with data.cache_when_complex("to_decode") as (b1, data):
data_ptr = bytes_data_ptr(data)
data_len = get_bytearray_length(data)

# Normally, ABI-encoded data assumes the argument is a tuple
# (See comments for `wrap_value_for_external_return`)
# However, we do not want to use `wrap_value_for_external_return`
# technique as used in external call codegen because in order to be
# type-safe we would need an extra memory copy. To avoid a copy,
# we manually add the ABI-dynamic offset so that it is
# re-interpreted in-place.
if (
unwrap_tuple is True
and needs_external_call_wrap(output_typ)
and output_typ.abi_type.is_dynamic()
):
data_ptr = add_ofst(data_ptr, 32)

ret = ["seq"]

if abi_min_size == abi_size_bound:
Expand All @@ -2572,18 +2555,30 @@ def build_IR(self, expr, args, kwargs, context):
# runtime assert: abi_min_size <= data_len <= abi_size_bound
ret.append(clamp2(abi_min_size, data_len, abi_size_bound, signed=False))

# return pointer to the buffer
ret.append(data_ptr)

return b1.resolve(
IRnode.from_list(
ret,
typ=output_typ,
location=data.location,
encoding=Encoding.ABI,
annotation=f"abi_decode({output_typ})",
)
to_decode = IRnode.from_list(
data_ptr,
typ=wrapped_typ,
location=data.location,
encoding=Encoding.ABI,
annotation=f"abi_decode({output_typ})",
)
to_decode.encoding = Encoding.ABI

# TODO optimization: skip make_setter when we don't need
# input validation

output_buf = context.new_internal_variable(wrapped_typ)
output = IRnode.from_list(output_buf, typ=wrapped_typ, location=MEMORY)

# sanity check buffer size for wrapped output type will not buffer overflow
assert wrapped_typ.memory_bytes_required == output_typ.memory_bytes_required
ret.append(make_setter(output, to_decode))

ret.append(output)
# finalize. set the type and location for the return buffer.
# (note: unwraps the tuple type if necessary)
ret = IRnode.from_list(ret, typ=output_typ, location=MEMORY)
return b1.resolve(ret)


class _MinMaxValue(TypenameFoldedFunction):
Expand All @@ -2602,7 +2597,6 @@ def evaluate(self, node):
if isinstance(input_type, IntegerT):
ret = vy_ast.Int.from_node(node, value=val)

# TODO: to change to known_type once #3213 is merged
ret._metadata["type"] = input_type
return ret

Expand Down
2 changes: 1 addition & 1 deletion vyper/cli/vyper_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"interface": "interface",
"ir": "ir_dict",
"ir_runtime": "ir_runtime_dict",
# "metadata": "metadata", # don't include in "*" output for now
"metadata": "metadata",
"layout": "layout",
"userdoc": "userdoc",
}
Expand Down
7 changes: 7 additions & 0 deletions vyper/codegen/function_definitions/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ class EntryPointInfo:
min_calldatasize: int # the min calldata required for this entry point
ir_node: IRnode # the ir for this entry point

def __post_init__(self):
# ABI v2 property guaranteed by the spec.
# https://docs.soliditylang.org/en/v0.8.21/abi-spec.html#formal-specification-of-the-encoding states: # noqa: E501
# > Note that for any X, len(enc(X)) is a multiple of 32.
assert self.min_calldatasize >= 4
assert (self.min_calldatasize - 4) % 32 == 0


@dataclass
class ExternalFuncIR(FuncIR):
Expand Down
11 changes: 4 additions & 7 deletions vyper/codegen/function_definitions/external_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,20 +140,17 @@ def handler_for(calldata_kwargs, default_kwargs, default_kwargs_code):
return ret


# TODO it would be nice if this returned a data structure which were
# amenable to generating a jump table instead of the linear search for
# method_id we have now.
def generate_ir_for_external_function(code, func_t, context):
# TODO type hints:
# def generate_ir_for_external_function(
# code: vy_ast.FunctionDef,
# func_t: ContractFunctionT,
# context: Context,
# check_nonpayable: bool,
# ) -> IRnode:
"""Return the IR for an external function. Includes code to inspect the method_id,
enter the function (nonpayable and reentrancy checks), handle kwargs and exit
the function (clean up reentrancy storage variables)
"""
Return the IR for an external function. Returns IR for the body
of the function, handle kwargs and exit the function. Also returns
metadata required for `module.py` to construct the selector table.
"""
nonreentrant_pre, nonreentrant_post = get_nonreentrant_lock(func_t)

Expand Down
5 changes: 4 additions & 1 deletion vyper/codegen/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,12 @@ def _generate_external_entry_points(external_functions, global_ctx):
for code in external_functions:
func_ir = generate_ir_for_function(code, global_ctx)
for abi_sig, entry_point in func_ir.entry_points.items():
method_id = method_id_int(abi_sig)
assert abi_sig not in entry_points
assert method_id not in sig_of

entry_points[abi_sig] = entry_point
sig_of[method_id_int(abi_sig)] = abi_sig
sig_of[method_id] = abi_sig

# stick function common body into final entry point to save a jump
ir_node = IRnode.from_list(["seq", entry_point.ir_node, func_ir.common_ir])
Expand Down
1 change: 0 additions & 1 deletion vyper/compiler/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ def build_ir_runtime_dict_output(compiler_data: CompilerData) -> dict:


def build_metadata_output(compiler_data: CompilerData) -> dict:
warnings.warn("metadata output format is unstable!")
sigs = compiler_data.function_signatures

def _var_rec_dict(variable_record):
Expand Down
2 changes: 1 addition & 1 deletion vyper/semantics/analysis/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ def _validate_msg_data_attribute(node: vy_ast.Attribute) -> None:
allowed_builtins = ("slice", "len", "raw_call")
if not isinstance(parent, vy_ast.Call) or parent.get("func.id") not in allowed_builtins:
raise StructureException(
"msg.data is only allowed inside of the slice or len functions", node
"msg.data is only allowed inside of the slice, len or raw_call functions", node
)
if parent.get("func.id") == "slice":
ok_args = len(parent.args) == 3 and isinstance(parent.args[2], vy_ast.Int)
Expand Down
1 change: 0 additions & 1 deletion vyper/semantics/analysis/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ def __init__(
_ns._constants = self.namespace._constants
module_node._metadata["namespace"] = _ns

# check for collisions between 4byte function selectors
self_members = namespace["self"].typ.members

# get list of internal function calls made by each function
Expand Down

0 comments on commit 4e94eed

Please sign in to comment.