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

fix[ux]: remove side effects in compare_type for bytestrings #3379

Open
wants to merge 75 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
30c54db
try what happens
tserg Apr 29, 2023
1212c50
minor fix
tserg Apr 30, 2023
135629e
try fix
tserg Apr 30, 2023
c87630c
fix lint
tserg Apr 30, 2023
809278a
update comment
tserg May 1, 2023
2cc75ef
handle nested bytestrings
tserg May 1, 2023
59c8f78
fix lint
tserg May 1, 2023
40077ea
try aggressive overriding of contract function return type
tserg May 1, 2023
b7e52c5
undo changes
tserg May 1, 2023
9f9981e
undo changes
tserg May 1, 2023
b07efdd
add widening test
tserg May 5, 2023
77cdd99
Merge branch 'master' of https://github.com/vyperlang/vyper into fix/…
tserg May 5, 2023
ace4367
check for propagated type
tserg May 5, 2023
44e0023
widen bytestring by deriving larger type
tserg May 5, 2023
f4060d7
remove _min_length; add _is_literal
tserg May 6, 2023
828929a
update comment
tserg May 6, 2023
cc1d76a
fix formatting
tserg May 6, 2023
7fcbacd
improve comment
tserg May 6, 2023
36028c2
use length instead of _length
tserg May 6, 2023
1b5a390
add comment
tserg May 6, 2023
00c26cf
fix lint
tserg May 6, 2023
2cc968e
Merge branch 'master' of https://github.com/vyperlang/vyper into fix/…
tserg Nov 8, 2023
014e989
fix literal cmp
tserg Nov 8, 2023
c1a9bbc
uncomment
tserg Nov 8, 2023
38e94d2
fix wip
tserg Nov 9, 2023
013e594
Merge branch 'master' of https://github.com/vyperlang/vyper into fix/…
tserg Nov 9, 2023
9ce8c4a
fix test
tserg Nov 9, 2023
1cb5066
fix any syntax
tserg Nov 9, 2023
5d6ad38
fix comment
tserg Nov 9, 2023
114973d
rename helper; change semantics
tserg Nov 9, 2023
eb8ffc5
fix lint
tserg Nov 9, 2023
ba527d3
add is_from_abi attr
tserg Nov 11, 2023
50e5000
remove set_length fn
tserg Nov 11, 2023
a325b99
fix slice
tserg Nov 12, 2023
d4d598a
fix lint
tserg Nov 12, 2023
8e8f204
remove is_literal
tserg Nov 12, 2023
6d29d1b
fix compare_type
tserg Nov 12, 2023
b175311
revert overwriting of ContractFunctionT return typ
tserg Nov 12, 2023
5a8490e
add comment
tserg Nov 12, 2023
ac9b222
fix lint
tserg Nov 12, 2023
e788797
wip - review
charles-cooper Nov 20, 2023
b33c878
fix lint
charles-cooper Nov 20, 2023
4a2744f
fix some return types
charles-cooper Nov 20, 2023
8b537c4
fix return type for call stmts
tserg Nov 20, 2023
27490e3
Merge branch 'master' into fix/bytestring_compare_type
charles-cooper Dec 7, 2023
a44779b
rename to _any_compare_type
charles-cooper Dec 7, 2023
1208ae1
fix lint
charles-cooper Dec 7, 2023
832f17b
rename fetch_call_return to get_return_type
charles-cooper Dec 7, 2023
23ef9d1
fix some signatures
charles-cooper Dec 8, 2023
8c82d1d
Merge branch 'master' of https://github.com/vyperlang/vyper into fix/…
tserg Dec 18, 2023
87096e2
Merge branch 'fix/bytestring_compare_type' of https://github.com/tser…
tserg Dec 18, 2023
f3553f5
fix lint
tserg Dec 18, 2023
ce5b120
fix more lint
tserg Dec 18, 2023
b124308
use Optional
tserg Dec 18, 2023
b94ae77
revert return analysis
tserg Dec 18, 2023
b9a474d
fix some tests
tserg Dec 18, 2023
6b58aeb
fix more tests
tserg Dec 18, 2023
5c6135c
revert circular call in infer_arg_types
tserg Dec 18, 2023
73193d5
fix lint
tserg Dec 18, 2023
9f667ca
fix more tests
tserg Dec 18, 2023
b3eb9f2
fix lint
tserg Dec 18, 2023
34ab4bc
fix import
tserg Dec 18, 2023
1b25514
Merge branch 'master' of https://github.com/vyperlang/vyper into fix/…
tserg Sep 21, 2024
729df1e
fix lint
tserg Sep 21, 2024
a1fbb11
fix convert
tserg Sep 21, 2024
24a306a
fix lint
tserg Sep 21, 2024
7c60586
fix typo
tserg Sep 21, 2024
b91e442
fix another typo
tserg Sep 21, 2024
d50f508
fix minmax
tserg Sep 21, 2024
6e5cd12
Merge branch 'master' into fix/bytestring_compare_type
charles-cooper Feb 21, 2025
2180f18
fix a test
charles-cooper Feb 21, 2025
105575c
Merge branch 'fix/bytestring_compare_type' of https://github.com/tser…
tserg Feb 22, 2025
d3a256e
Merge branch 'master' of https://github.com/vyperlang/vyper into fix/…
tserg Feb 22, 2025
51bcfc8
fix derivation of return type for external call
tserg Feb 23, 2025
7b77c7c
fix bytes assign check
tserg Feb 23, 2025
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
34 changes: 15 additions & 19 deletions tests/functional/codegen/test_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,19 +472,19 @@ def returns_Bytes3() -> Bytes[3]:
"""

should_not_compile = """
import BadJSONInterface
import JSONInterface
@external
def foo(x: BadJSONInterface) -> Bytes[2]:
def foo(x: JSONInterface) -> Bytes[2]:
return slice(extcall x.returns_Bytes3(), 0, 2)
"""

code = """
import BadJSONInterface
import JSONInterface

foo: BadJSONInterface
foo: JSONInterface

@deploy
def __init__(addr: BadJSONInterface):
def __init__(addr: JSONInterface):
self.foo = addr

@external
Expand All @@ -499,30 +499,26 @@ def test_fail2() -> Bytes[2]:
return x

@external
def test_fail3() -> Bytes[3]:
# should revert - returns_Bytes3 is inferred to have return type Bytes[2]
# (because test_fail3 comes after test_fail1)
def test_success() -> Bytes[3]:
return extcall self.foo.returns_Bytes3()
"""
json_c = get_contract(external_contract)

bad_c = get_contract(external_contract)

bad_json_interface = json.dumps(compile_code(external_contract, output_formats=["abi"])["abi"])
input_bundle = make_input_bundle({"BadJSONInterface.json": bad_json_interface})
json_interface = json.dumps(compile_code(external_contract, output_formats=["abi"])["abi"])
input_bundle = make_input_bundle({"JSONInterface.json": json_interface})

assert_compile_failed(
lambda: get_contract(should_not_compile, input_bundle=input_bundle), ArgumentException
)
with pytest.raises(ArgumentException):
compile_code(should_not_compile, input_bundle=input_bundle)

c = get_contract(code, bad_c.address, input_bundle=input_bundle)
assert bad_c.returns_Bytes3() == b"123"
c = get_contract(code, json_c.address, input_bundle=input_bundle)
assert json_c.returns_Bytes3() == b"123"

with tx_failed():
c.test_fail1()
with tx_failed():
c.test_fail2()
with tx_failed():
c.test_fail3()

assert c.test_success() == json_c.returns_Bytes3() == b"123"


def test_units_interface(env, get_contract, make_input_bundle):
Expand Down
13 changes: 11 additions & 2 deletions vyper/builtins/_signatures.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
@functools.wraps(wrapped_fn)
def decorator_fn(self, node, context):
subs = []

for arg in node.args:
arg_ir = process_arg(arg, arg._metadata["type"], context)
# TODO annotate arg_ir with argname from self._inputs?
Expand Down Expand Up @@ -137,10 +138,17 @@
def check_modifiability_for_call(self, node: vy_ast.Call, modifiability: Modifiability) -> bool:
return self._modifiability <= modifiability

def fetch_call_return(self, node: vy_ast.Call) -> Optional[VyperType]:
def get_return_type(
self, node: vy_ast.Call, expected_type: Optional[VyperType] = None
) -> Optional[VyperType]:
self._validate_arg_types(node)

return self._return_type
ret = self._return_type

if expected_type is not None and not expected_type.compare_type(ret): # type: ignore
raise TypeMismatch(f"{self._id}() returns {ret}, but expected {expected_type}", node)

Check warning on line 149 in vyper/builtins/_signatures.py

View check run for this annotation

Codecov / codecov/patch

vyper/builtins/_signatures.py#L149

Added line #L149 was not covered by tests

return ret

def infer_arg_types(self, node: vy_ast.Call, expected_return_typ=None) -> list[VyperType]:
self._validate_arg_types(node)
Expand All @@ -152,6 +160,7 @@
if len(varargs) > 0:
assert self._has_varargs
ret.extend(get_exact_type_from_node(arg) for arg in varargs)

return ret

def infer_kwarg_types(self, node: vy_ast.Call) -> dict[str, VyperType]:
Expand Down
76 changes: 35 additions & 41 deletions vyper/builtins/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@
# (2) should always be folded.
_inputs = [("typename", TYPE_T.any())]

def fetch_call_return(self, node):
type_ = self.infer_arg_types(node)[0].typedef
def get_return_type(self, node, expected_type=None):
type_ = self.infer_arg_types(node, expected_type)[0].typedef
return type_

def infer_arg_types(self, node, expected_return_typ=None):
Expand Down Expand Up @@ -194,8 +194,8 @@
class Convert(BuiltinFunctionT):
_id = "convert"

def fetch_call_return(self, node):
_, target_typedef = self.infer_arg_types(node)
def get_return_type(self, node, expected_typ=None):
_, target_typedef = self.infer_arg_types(node, expected_return_typ=expected_typ)

# note: more type conversion validation happens in convert.py
return target_typedef.typedef
Expand Down Expand Up @@ -293,14 +293,9 @@
("length", UINT256_T),
]

def fetch_call_return(self, node):
def get_return_type(self, node, expected_type=None):
arg_type, _, _ = self.infer_arg_types(node)

if isinstance(arg_type, StringT):
return_type = StringT()
else:
return_type = BytesT()

# validate start and length are in bounds

arg = node.args[0]
Expand Down Expand Up @@ -330,10 +325,11 @@
raise ArgumentException(f"slice out of bounds for {arg_type}", node)

# we know the length statically
if length_literal is not None:
return_type.set_length(length_literal)
length = length_literal if length_literal is not None else 0
if isinstance(arg_type, StringT):
return_type = StringT(length)
else:
return_type.set_min_length(arg_type.length)
return_type = BytesT(length)

return return_type

Expand Down Expand Up @@ -491,18 +487,17 @@
class Concat(BuiltinFunctionT):
_id = "concat"

def fetch_call_return(self, node):
def get_return_type(self, node, expected_type=None):
arg_types = self.infer_arg_types(node)

length = 0
for arg_t in arg_types:
length += arg_t.length

if isinstance(arg_types[0], (StringT)):
return_type = StringT()
return_type = StringT(length)
else:
return_type = BytesT()
return_type.set_length(length)
return_type = BytesT(length)
return return_type

def infer_arg_types(self, node, expected_return_typ=None):
Expand Down Expand Up @@ -732,7 +727,7 @@
else:
return vy_ast.Bytes.from_node(node, value=value)

def fetch_call_return(self, node):
def get_return_type(self, node, expected_type=None):
validate_call_args(node, 1, ["output_type"])

type_ = self.infer_kwarg_types(node)["output_type"].typedef
Expand Down Expand Up @@ -837,7 +832,7 @@
_inputs = [("b", BytesT.any()), ("start", IntegerT.unsigneds())]
_kwargs = {"output_type": KwargSettings(TYPE_T.any(), BYTES32_T)}

def fetch_call_return(self, node):
def get_return_type(self, node, expected_type=None):
self._validate_arg_types(node)
return_type = self.infer_kwarg_types(node)["output_type"].typedef
return return_type
Expand Down Expand Up @@ -952,7 +947,7 @@

return vy_ast.Int.from_node(node, value=int(value * denom))

def fetch_call_return(self, node):
def get_return_type(self, node, expected_type=None):
self.infer_arg_types(node)
return self._return_type

Expand Down Expand Up @@ -1015,7 +1010,7 @@
"revert_on_failure": KwargSettings(BoolT(), True, require_literal=True),
}

def fetch_call_return(self, node):
def get_return_type(self, node, expected_type=None):

Check notice

Code scanning / CodeQL

Explicit returns mixed with implicit (fall through) returns Note

Mixing implicit and explicit returns may indicate an error as implicit returns always return None.
self._validate_arg_types(node)

kwargz = {i.arg: i.value for i in node.keywords}
Expand All @@ -1039,8 +1034,7 @@
raise

if outsize.value:
return_type = BytesT()
return_type.set_min_length(outsize.value)
return_type = BytesT(outsize.value)

if revert_on_failure:
return return_type
Expand Down Expand Up @@ -1231,7 +1225,7 @@
_return_type = None
_is_terminus = True

def fetch_call_return(self, node):
def get_return_type(self, node, expected_type=None):
return None

def infer_arg_types(self, node, expected_return_typ=None):
Expand All @@ -1251,7 +1245,7 @@
_id = "raw_log"
_inputs = [("topics", DArrayT(BYTES32_T, 4)), ("data", (BYTES32_T, BytesT.any()))]

def fetch_call_return(self, node):
def get_return_type(self, node, expected_type=None):
self.infer_arg_types(node)

def infer_arg_types(self, node, expected_return_typ=None):
Expand Down Expand Up @@ -1423,7 +1417,7 @@
value = (value << shift) % (2**256)
return vy_ast.Int.from_node(node, value=value)

def fetch_call_return(self, node):
def get_return_type(self, node, expected_type=None):
# return type is the type of the first argument
return self.infer_arg_types(node)[0]

Expand Down Expand Up @@ -1903,7 +1897,7 @@
def __repr__(self):
return f"builtin function unsafe_{self.op}"

def fetch_call_return(self, node):
def get_return_type(self, node, expected_type=None):
return_type = self.infer_arg_types(node).pop()
return return_type

Expand Down Expand Up @@ -1988,7 +1982,8 @@
value = self._eval_fn(left.value, right.value)
return type(left).from_node(node, value=value)

def fetch_call_return(self, node):
# TODO: this returns a list of types. should we revert to `fetch_call_return`?
def get_return_type(self, node, expected_type=None):
self._validate_arg_types(node)

types_list = get_common_types(
Expand All @@ -2000,7 +1995,7 @@
return types_list

def infer_arg_types(self, node, expected_return_typ=None):
types_list = self.fetch_call_return(node)
types_list = self.get_return_type(node)
# type mismatch should have been caught in `fetch_call_return`
assert expected_return_typ in types_list
return [expected_return_typ, expected_return_typ]
Expand Down Expand Up @@ -2041,7 +2036,7 @@
_id = "uint2str"
_inputs = [("x", IntegerT.unsigneds())]

def fetch_call_return(self, node):
def get_return_type(self, node, expected_type=None):
arg_t = self.infer_arg_types(node)[0]
bits = arg_t.bits
len_needed = math.ceil(bits * math.log(2) / math.log(10))
Expand All @@ -2066,7 +2061,7 @@

@process_inputs
def build_IR(self, expr, args, kwargs, context):
return_t = self.fetch_call_return(expr)
return_t = self.get_return_type(expr)
n_digits = return_t.maxlen

with args[0].cache_when_complex("val") as (b1, val):
Expand Down Expand Up @@ -2227,7 +2222,7 @@
class Empty(TypenameFoldedFunctionT):
_id = "empty"

def fetch_call_return(self, node):
def get_return_type(self, node, expected_type=None):
type_ = self.infer_arg_types(node)[0].typedef
if isinstance(type_, HashMapT):
raise TypeMismatch("Cannot use empty on HashMap", node)
Expand All @@ -2245,7 +2240,7 @@

_warned = False

def fetch_call_return(self, node):
def get_return_type(self, node, expected_type=None):
if not self._warned:
vyper_warn("`breakpoint` should only be used for debugging!", node)
self._warned = True
Expand All @@ -2265,7 +2260,7 @@

_warned = False

def fetch_call_return(self, node):
def get_return_type(self, node, expected_type=None):
if not self._warned:
vyper_warn("`print` should only be used for debugging!", node)
self._warned = True
Expand Down Expand Up @@ -2365,7 +2360,7 @@
ret[kwarg_name] = typ
return ret

def fetch_call_return(self, node):
def get_return_type(self, node, expected_type=None):
self._validate_arg_types(node)
ensure_tuple = next(
(arg.value.value for arg in node.keywords if arg.arg == "ensure_tuple"), True
Expand All @@ -2392,9 +2387,7 @@
# the output includes 4 bytes for the method_id.
maxlen += 4

ret = BytesT()
ret.set_length(maxlen)
return ret
return BytesT(maxlen)

@staticmethod
def _parse_method_id(method_id_literal):
Expand All @@ -2411,6 +2404,7 @@
def build_IR(self, expr, args, kwargs, context):
ensure_tuple = kwargs["ensure_tuple"]
method_id = self._parse_method_id(kwargs["method_id"])
expr_type = expr._metadata["type"]

if len(args) < 1:
raise StructureException("abi_encode expects at least one argument", expr)
Expand All @@ -2428,7 +2422,7 @@
maxlen += 4

buf_t = BytesT(maxlen)
assert self.fetch_call_return(expr).length == maxlen
assert self.get_return_type(expr, expr_type).length == maxlen
buf = context.new_internal_variable(buf_t)

ret = ["seq"]
Expand Down Expand Up @@ -2464,8 +2458,8 @@
_inputs = [("data", BytesT.any()), ("output_type", TYPE_T.any())]
_kwargs = {"unwrap_tuple": KwargSettings(BoolT(), True, require_literal=True)}

def fetch_call_return(self, node):
_, output_type = self.infer_arg_types(node)
def get_return_type(self, node, expected_type=None):
_, output_type = self.infer_arg_types(node, expected_return_typ=expected_type)
return output_type.typedef

def infer_arg_types(self, node, expected_return_typ=None):
Expand Down
4 changes: 4 additions & 0 deletions vyper/codegen/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,10 @@ def dummy_node_for_type(typ):


def _check_assign_bytes(left, right):
# allow assignment if length is 0 e.g. function was imported via json ABI
if left.typ.length == 0:
return

if right.typ.maxlen > left.typ.maxlen: # pragma: nocover
raise TypeMismatch(f"Cannot cast from {right.typ} to {left.typ}")

Expand Down
Loading