Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions tests/functional/builtins/codegen/test_create_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1145,10 +1145,13 @@ def deploy_from_calldata(s: Bytes[1024], arg: uint256, salt: bytes32) -> address
assert env.get_code(res) == runtime


# evaluation of the value kwarg changes the value of the salt kwarg
# value kwarg comes after the salt kwarg in the source code
@pytest.mark.xfail(raises=AssertionError, reason="salt kwarg is evaluated after value kwarg")
def test_raw_create_order_of_eval_of_kwargs(get_contract, env, create2_address_of, keccak):
# value is evaluated after salt because that is the source order.
Comment thread
banteg marked this conversation as resolved.
def test_raw_create_order_of_eval_of_kwargs(
get_contract, env, create2_address_of, keccak, experimental_codegen
):
if not experimental_codegen:
pytest.xfail("legacy codegen does not preserve this source order")

to_deploy_code = """
foo: public(uint256)

Expand All @@ -1162,8 +1165,6 @@ def __init__(arg: uint256):
initcode = bytes.fromhex(out["bytecode"].removeprefix("0x"))
runtime = bytes.fromhex(out["bytecode_runtime"].removeprefix("0x"))

# the implementation of `raw_create` firstly caches
# `value` and then `salt`, here the source order is `salt` then `value`
deployer_code = """
c: Bytes[1024]
salt: bytes32
Expand Down Expand Up @@ -1194,11 +1195,10 @@ def deploy_from_calldata(s: Bytes[1024], arg: uint256, salt: bytes32, value_: ui


# test vararg and kwarg order of evaluation
# test fails because `value` gets evaluated
# before the 1st vararg which doesn't follow
# source code order
@pytest.mark.xfail(raises=AssertionError)
def test_raw_create_eval_order(get_contract):
def test_raw_create_eval_order(get_contract, experimental_codegen):
if not experimental_codegen:
pytest.xfail("legacy codegen does not preserve this source order")

code = """
a: public(uint256)

Expand Down
47 changes: 47 additions & 0 deletions tests/functional/builtins/codegen/test_raw_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,53 @@ def foo() -> String[32]:
assert c.foo() == "goo"


def test_raw_call_runtime_kwargs_source_order(get_contract, experimental_codegen):
if not experimental_codegen:
pytest.xfail("legacy codegen does not preserve this source order")

code = """
canary: public(uint256)

@internal
def set_gas(v: uint256) -> uint256:
self.canary = v
return 50000

@internal
def set_value(v: uint256) -> uint256:
self.canary = v
return 0

@external
def value_then_gas() -> uint256:
success: bool = raw_call(
0x0000000000000000000000000000000000000004,
b"",
max_outsize=0,
value=self.set_value(1),
gas=self.set_gas(2),
revert_on_failure=False,
)
return self.canary

@external
def gas_then_value() -> uint256:
success: bool = raw_call(
0x0000000000000000000000000000000000000004,
b"",
max_outsize=0,
gas=self.set_gas(1),
value=self.set_value(2),
revert_on_failure=False,
)
return self.canary
"""
c = get_contract(code)

assert c.value_then_gas() == 2
assert c.gas_then_value() == 2


def test_raw_call_clean_mem_kwargs_value(get_contract, env):
# test msize uses clean memory and does not get overwritten by
# any raw_call() kwargs
Expand Down
77 changes: 68 additions & 9 deletions tests/unit/compiler/venom/test_builtin_kwargs.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,85 @@
import pytest

from vyper import ast as vy_ast
from vyper.codegen_venom.builtins.abi import _get_bool_kwarg as get_abi_bool_kwarg
from vyper.codegen_venom.builtins.misc import _get_bool_kwarg as get_misc_bool_kwarg
from vyper.codegen_venom.builtins._kwargs import (
get_bool_kwarg,
get_kwarg_ast_constants,
get_literal_kwarg,
kwarg_is_provided,
)
from vyper.exceptions import CompilerPanic


def _call_node(source):
return vy_ast.parse_to_ast(source).body[0].value


@pytest.mark.parametrize("get_bool_kwarg", [get_abi_bool_kwarg, get_misc_bool_kwarg])
def test_bool_kwarg_uses_reduced_value(get_bool_kwarg):
def test_kwarg_ast_constants_return_folded_nodes():
call_node = _call_node("foo(flag=FLAG)")
call_node.keywords[0].value._set_folded_value(vy_ast.NameConstant(value=False))

assert get_bool_kwarg(call_node, "flag", True) is False
constants = get_kwarg_ast_constants(
call_node, ("flag",), allowed_kwarg_names=("flag",)
)

assert constants["flag"].value is False

@pytest.mark.parametrize("get_bool_kwarg", [get_abi_bool_kwarg, get_misc_bool_kwarg])
def test_bool_kwarg_rejects_unreduced_value(get_bool_kwarg):

def test_kwarg_ast_constants_reject_unfolded_values():
call_node = _call_node("foo(flag=FLAG)")

with pytest.raises(CompilerPanic, match="unfoldable constant kwarg: flag"):
get_kwarg_ast_constants(call_node, ("flag",))


def test_kwarg_helpers_reject_unexpected_kwargs():
call_node = _call_node("foo(flag=False)")

with pytest.raises(CompilerPanic, match="unexpected kwarg: flag"):
kwarg_is_provided(call_node, "flag", allowed_kwarg_names=("other",))


def test_kwarg_helpers_reject_duplicate_kwargs():
call_node = _call_node("foo(flag=False, flag=True)")

with pytest.raises(CompilerPanic, match="duplicate kwarg: flag"):
get_kwarg_ast_constants(call_node, ("flag",))


def test_kwarg_is_provided():
call_node = _call_node("foo(flag=False)")

assert kwarg_is_provided(call_node, "flag", allowed_kwarg_names=("flag",)) is True
assert kwarg_is_provided(call_node, "other", allowed_kwarg_names=("flag",)) is False


def test_bool_kwarg_uses_reduced_value():
call_node = _call_node("foo(flag=FLAG)")
call_node.keywords[0].value._set_folded_value(vy_ast.NameConstant(value=False))
constants = get_kwarg_ast_constants(call_node, ("flag",), allowed_kwarg_names=("flag",))

assert get_bool_kwarg(constants, "flag", True) is False


def test_bool_kwarg_rejects_unreduced_value():
call_node = _call_node("foo(flag=FLAG)")

with pytest.raises(CompilerPanic, match="unfoldable constant kwarg: flag"):
get_kwarg_ast_constants(call_node, ("flag",))


def test_literal_kwarg_uses_reduced_value():
call_node = _call_node("foo(revert_on_failure=REVERT)")
call_node.keywords[0].value._set_folded_value(vy_ast.NameConstant(value=False))
constants = get_kwarg_ast_constants(
call_node, ("revert_on_failure",), allowed_kwarg_names=("revert_on_failure",)
)

assert get_literal_kwarg(constants, "revert_on_failure", True) is False


def test_literal_kwarg_rejects_unreduced_value():
call_node = _call_node("foo(revert_on_failure=REVERT)")

with pytest.raises(CompilerPanic, match="unfoldable boolean kwarg: flag"):
get_bool_kwarg(call_node, "flag", True)
with pytest.raises(CompilerPanic, match="unfoldable constant kwarg: revert_on_failure"):
get_kwarg_ast_constants(call_node, ("revert_on_failure",))
112 changes: 112 additions & 0 deletions vyper/codegen_venom/builtins/_kwargs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
from __future__ import annotations

from collections.abc import Iterable
from typing import Any

from vyper import ast as vy_ast
from vyper.exceptions import CompilerPanic


_UNSET = object()


def _validate_allowed_kwargs(
node: vy_ast.Call, allowed_kwarg_names: Iterable[str] | None
) -> None:
seen = set()
for kw in node.keywords:
if kw.arg in seen: # pragma: nocover
raise CompilerPanic(f"duplicate kwarg: {kw.arg}", kw)
seen.add(kw.arg)

if allowed_kwarg_names is None:
Comment thread
banteg marked this conversation as resolved.
Outdated
return

allowed_kwarg_names = set(allowed_kwarg_names)
Comment thread
banteg marked this conversation as resolved.
Outdated
for kw in node.keywords:
Comment thread
banteg marked this conversation as resolved.
if kw.arg not in allowed_kwarg_names: # pragma: nocover
raise CompilerPanic(f"unexpected kwarg: {kw.arg}", kw)


def kwarg_is_provided(
node: vy_ast.Call, kwarg_name: str, allowed_kwarg_names: Iterable[str] | None = None
) -> bool:
_validate_allowed_kwargs(node, allowed_kwarg_names)
return any(kw.arg == kwarg_name for kw in node.keywords)


def get_kwarg_ast_constants(
node: vy_ast.Call,
kwarg_names: Iterable[str],
Comment thread
banteg marked this conversation as resolved.
Outdated
allowed_kwarg_names: Iterable[str] | None = None,
error_prefix: str = "unfoldable constant kwarg",
) -> dict[str, vy_ast.Constant]:
_validate_allowed_kwargs(node, allowed_kwarg_names)
kwarg_names = set(kwarg_names)
ret = {}
for kw in node.keywords:
if kw.arg not in kwarg_names:
Comment thread
banteg marked this conversation as resolved.
Outdated
continue

kw_node = kw.value.reduced()
if not isinstance(kw_node, vy_ast.Constant): # pragma: nocover
raise CompilerPanic(f"{error_prefix}: {kw.arg}", kw_node)
ret[kw.arg] = kw_node
Comment thread
banteg marked this conversation as resolved.
Outdated

return ret


def get_kwarg_values(
node: vy_ast.Call,
ctx,
kwarg_names: Iterable[str],
allowed_kwarg_names: Iterable[str] | None = None,
):
from vyper.codegen_venom.expr import Expr

_validate_allowed_kwargs(node, allowed_kwarg_names)
kwarg_names = set(kwarg_names)
ret = {}
for kw in node.keywords:
if kw.arg in kwarg_names:
ret[kw.arg] = Expr(kw.value.reduced(), ctx).lower_value()
return ret


def _literal_value(node: vy_ast.VyperNode) -> Any:
if isinstance(node, vy_ast.Int):
return node.value
if isinstance(node, vy_ast.NameConstant):
return node.value
return _UNSET


def get_bool_kwarg(
Comment thread
banteg marked this conversation as resolved.
Outdated
kwarg_constants: dict[str, vy_ast.Constant],
kwarg_name: str,
default: bool,
) -> bool:
kw_node = kwarg_constants.get(kwarg_name)
if kw_node is None:
return default
if isinstance(kw_node, vy_ast.NameConstant):
return kw_node.value
if isinstance(kw_node, vy_ast.Int):
return bool(kw_node.value)
raise CompilerPanic(f"unfoldable boolean kwarg: {kwarg_name}", kw_node)


def get_literal_kwarg(
kwarg_constants: dict[str, vy_ast.Constant],
kwarg_name: str,
default,
):
kw_node = kwarg_constants.get(kwarg_name)
if kw_node is None:
return default

value = _literal_value(kw_node)
if value is not _UNSET:
return value

raise CompilerPanic(f"unfoldable literal kwarg: {kwarg_name}", kw_node)
Loading