From 7d2368dc6562afb51c72166978bd8fc3d864e3f5 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:47:28 +0800 Subject: [PATCH 01/25] add int value property to hex node --- vyper/ast/nodes.py | 8 ++++++++ vyper/ast/nodes.pyi | 2 ++ 2 files changed, 10 insertions(+) diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py index 974685f403..5e9132b643 100644 --- a/vyper/ast/nodes.py +++ b/vyper/ast/nodes.py @@ -31,6 +31,7 @@ SizeLimits, annotate_source_code, evm_div, + hex_to_int, quantize, sha256sum, ) @@ -883,6 +884,13 @@ def bytes_value(self): """ return bytes.fromhex(self.value.removeprefix("0x")) + @property + def int_value(self): + """ + This value as integer + """ + return hex_to_int(self.value) + class Str(Constant): __slots__ = () diff --git a/vyper/ast/nodes.pyi b/vyper/ast/nodes.pyi index 783764271d..d4c7c60b95 100644 --- a/vyper/ast/nodes.pyi +++ b/vyper/ast/nodes.pyi @@ -120,6 +120,8 @@ class Decimal(Num): ... class Hex(Num): @property def n_bytes(self): ... + @property + def int_value(self): ... class Str(Constant): ... class Bytes(Constant): ... From b866b07fdef7001dc34b655128528571564193c5 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:47:46 +0800 Subject: [PATCH 02/25] add hex as valid literal node for integer --- vyper/semantics/types/primitives.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/vyper/semantics/types/primitives.py b/vyper/semantics/types/primitives.py index 5c0362e662..a04ae7a015 100644 --- a/vyper/semantics/types/primitives.py +++ b/vyper/semantics/types/primitives.py @@ -138,9 +138,14 @@ def is_signed(self) -> bool: def validate_literal(self, node: vy_ast.Constant) -> None: super().validate_literal(node) lower, upper = self.ast_bounds - if node.value < lower: + + value = node.value + if isinstance(node, vy_ast.Hex): + value = node.int_value + + if value < lower: raise OverflowException(f"Value is below lower bound for given type ({lower})", node) - if node.value > upper: + if value > upper: raise OverflowException(f"Value exceeds upper bound for given type ({upper})", node) def validate_numeric_op( @@ -242,7 +247,7 @@ class IntegerT(NumericT): typeclass = "integer" - _valid_literal = (vy_ast.Int,) + _valid_literal = (vy_ast.Hex, vy_ast.Int) _equality_attrs = ("is_signed", "bits") ast_type = int From 467abfc23e850e0a45d6cd9e9f460eab1abfea06 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:47:53 +0800 Subject: [PATCH 03/25] handle codegen --- vyper/codegen/expr.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/vyper/codegen/expr.py b/vyper/codegen/expr.py index 3a09bbe6c0..cdf9be8c19 100644 --- a/vyper/codegen/expr.py +++ b/vyper/codegen/expr.py @@ -16,6 +16,7 @@ is_array_like, is_bytes_m_type, is_flag_type, + is_integer_type, is_numeric_type, is_tuple_like, make_setter, @@ -133,6 +134,12 @@ def parse_Hex(self): return IRnode.from_list(val, typ=t) + elif is_integer_type(t): + assert not t.is_signed + + val = self.expr.int_value + return IRnode.from_list(val, typ=t) + # String literals def parse_Str(self): bytez, bytez_length = string_to_bytes(self.expr.value) From 8a46ebc5e657b03459154e121057f8cf83ea4a8e Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:47:59 +0800 Subject: [PATCH 04/25] add tests --- .../codegen/types/numbers/test_constants.py | 17 ++++++++++++++++- .../codegen/types/numbers/test_unsigned_ints.py | 15 +++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/tests/functional/codegen/types/numbers/test_constants.py b/tests/functional/codegen/types/numbers/test_constants.py index f29a20153b..a243d87aab 100644 --- a/tests/functional/codegen/types/numbers/test_constants.py +++ b/tests/functional/codegen/types/numbers/test_constants.py @@ -5,7 +5,7 @@ from tests.utils import ZERO_ADDRESS, decimal_to_int from vyper.compiler import compile_code from vyper.exceptions import TypeMismatch -from vyper.utils import MemoryPositions +from vyper.utils import MemoryPositions, hex_to_int def search_for_sublist(ir, sublist): @@ -244,3 +244,18 @@ def contains(a: int128) -> bool: assert c.contains(44) is True assert c.contains(33) is True assert c.contains(3) is False + + +def test_constant_hex_int(get_contract): + test_value = "0xfa" + code = f""" +X: constant(uint8) = {test_value} +@external +def test() -> uint8: + y: uint8 = X + return y + """ + + c = get_contract(code) + + assert c.test() == hex_to_int(test_value) diff --git a/tests/functional/codegen/types/numbers/test_unsigned_ints.py b/tests/functional/codegen/types/numbers/test_unsigned_ints.py index 2bd3184ec0..59ed26e7e9 100644 --- a/tests/functional/codegen/types/numbers/test_unsigned_ints.py +++ b/tests/functional/codegen/types/numbers/test_unsigned_ints.py @@ -210,7 +210,8 @@ def foo(x: {typ}, y: {typ}) -> bool: @pytest.mark.parametrize("typ", types) -def test_uint_literal(get_contract, typ): +@pytest.mark.parametrize("is_hex_int", [True, False]) +def test_uint_literal(get_contract, typ, is_hex_int): lo, hi = typ.ast_bounds good_cases = [0, 1, 2, 3, hi // 2 - 1, hi // 2, hi // 2 + 1, hi - 1, hi] @@ -222,11 +223,21 @@ def test() -> {typ}: return o """ + def _to_hex_int(v): + n_nibbles = typ.bits // 4 + return "0x" + hex(v)[2:].rjust(n_nibbles, "0") + for val in good_cases: - c = get_contract(code_template.format(typ=typ, val=val)) + input_val = val + if is_hex_int: + n_nibbles = typ.bits // 4 + input_val = "0x" + hex(val)[2:].rjust(n_nibbles, "0") + c = get_contract(code_template.format(typ=typ, val=input_val)) assert c.test() == val for val in bad_cases: + if is_hex_int: + return exc = ( TypeMismatch if SizeLimits.MIN_INT256 <= val <= SizeLimits.MAX_UINT256 From 0306cc8018e140c8c65253bda5699872e1266899 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:53:18 +0800 Subject: [PATCH 05/25] remove dead code --- tests/functional/codegen/types/numbers/test_unsigned_ints.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/functional/codegen/types/numbers/test_unsigned_ints.py b/tests/functional/codegen/types/numbers/test_unsigned_ints.py index 59ed26e7e9..d49256d46b 100644 --- a/tests/functional/codegen/types/numbers/test_unsigned_ints.py +++ b/tests/functional/codegen/types/numbers/test_unsigned_ints.py @@ -223,10 +223,6 @@ def test() -> {typ}: return o """ - def _to_hex_int(v): - n_nibbles = typ.bits // 4 - return "0x" + hex(v)[2:].rjust(n_nibbles, "0") - for val in good_cases: input_val = val if is_hex_int: From b731e8a6fda0bac7e0c205cbd341018b677565ab Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 5 Dec 2024 11:08:06 +0800 Subject: [PATCH 06/25] catch casing; add todo note --- vyper/semantics/types/primitives.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vyper/semantics/types/primitives.py b/vyper/semantics/types/primitives.py index a04ae7a015..c0603edf16 100644 --- a/vyper/semantics/types/primitives.py +++ b/vyper/semantics/types/primitives.py @@ -141,6 +141,11 @@ def validate_literal(self, node: vy_ast.Constant) -> None: value = node.value if isinstance(node, vy_ast.Hex): + # TODO: raise if signed integer + if node.value not in (node.value.lower(), node.value.upper()): + raise InvalidLiteral( + f"Cannot mix uppercase and lowercase for hex integers", node + ) value = node.int_value if value < lower: From c99612375a4491be67dbc5a502167a3e2ecf8f40 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 5 Dec 2024 11:38:26 +0800 Subject: [PATCH 07/25] fix lint --- vyper/semantics/types/primitives.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/vyper/semantics/types/primitives.py b/vyper/semantics/types/primitives.py index c0603edf16..bb1ebb0f94 100644 --- a/vyper/semantics/types/primitives.py +++ b/vyper/semantics/types/primitives.py @@ -143,9 +143,7 @@ def validate_literal(self, node: vy_ast.Constant) -> None: if isinstance(node, vy_ast.Hex): # TODO: raise if signed integer if node.value not in (node.value.lower(), node.value.upper()): - raise InvalidLiteral( - f"Cannot mix uppercase and lowercase for hex integers", node - ) + raise InvalidLiteral("Cannot mix uppercase and lowercase for hex integers", node) value = node.int_value if value < lower: From 3d95a4928508e484ccbb66457ece14b7a0af62ef Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 5 Dec 2024 12:26:03 +0800 Subject: [PATCH 08/25] catch signed hex ints --- vyper/semantics/types/primitives.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vyper/semantics/types/primitives.py b/vyper/semantics/types/primitives.py index bb1ebb0f94..853e6a635d 100644 --- a/vyper/semantics/types/primitives.py +++ b/vyper/semantics/types/primitives.py @@ -142,6 +142,9 @@ def validate_literal(self, node: vy_ast.Constant) -> None: value = node.value if isinstance(node, vy_ast.Hex): # TODO: raise if signed integer + if self.is_signed: + raise InvalidLiteral("Hex integers must be unsigned", node) + if node.value not in (node.value.lower(), node.value.upper()): raise InvalidLiteral("Cannot mix uppercase and lowercase for hex integers", node) value = node.int_value From 22a4384c2109ec4a8df84f1f8f021e636db08588 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 5 Dec 2024 12:26:11 +0800 Subject: [PATCH 09/25] fix tests for lists --- tests/functional/syntax/test_list.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/functional/syntax/test_list.py b/tests/functional/syntax/test_list.py index e55b060542..8bdecc527d 100644 --- a/tests/functional/syntax/test_list.py +++ b/tests/functional/syntax/test_list.py @@ -85,7 +85,7 @@ def foo(x: int128[2][2]): def foo(): self.bar = [1, 2, 0x1234567890123456789012345678901234567890] """, - InvalidLiteral, + TypeMismatch, ), ( """ @@ -309,6 +309,12 @@ def foo(): for i: DynArray[uint256, 3] in [[], []]: x = i """, + """ +bar: uint160[3] +@external +def foo(): + self.bar = [1, 2, 0x1234567890123456789012345678901234567890] + """, ] From 08b264afc733dee0c6e32e817636ca45f9d8bdbe Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 5 Dec 2024 12:28:55 +0800 Subject: [PATCH 10/25] add signed int test --- .../codegen/types/numbers/test_signed_ints.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/functional/codegen/types/numbers/test_signed_ints.py b/tests/functional/codegen/types/numbers/test_signed_ints.py index 608783000a..de66c704a1 100644 --- a/tests/functional/codegen/types/numbers/test_signed_ints.py +++ b/tests/functional/codegen/types/numbers/test_signed_ints.py @@ -478,3 +478,13 @@ def foo(): compile_code(code) assert e.value._hint == "did you mean `-5 // 9`?" + + +def test_invalid_hex_int(): + code = """ +@external +def foo(): + a: int8 = 0xff + """ + with pytest.raises(TypeMismatch): + compile_code(code) From a0478a9d86e466d08176b72f736196097e14daf2 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 5 Dec 2024 12:29:56 +0800 Subject: [PATCH 11/25] fix array index test --- tests/unit/semantics/analysis/test_array_index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/semantics/analysis/test_array_index.py b/tests/unit/semantics/analysis/test_array_index.py index aa9a702be3..3ad31d66f8 100644 --- a/tests/unit/semantics/analysis/test_array_index.py +++ b/tests/unit/semantics/analysis/test_array_index.py @@ -25,7 +25,7 @@ def foo(b: {value}): analyze_module(vyper_module) -@pytest.mark.parametrize("value", ["1.0", "0.0", "'foo'", "0x00", "b'\x01'", "False"]) +@pytest.mark.parametrize("value", ["1.0", "0.0", "'foo'", "b'\x01'", "False"]) def test_invalid_literal(namespace, value): code = f""" From a33ae800b7cfd79b25c2a27d8fcb0b99ac7511b0 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 5 Dec 2024 14:52:32 +0800 Subject: [PATCH 12/25] handle method_id in abi_encode --- vyper/builtins/functions.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 0cfcb636d7..8273873bdc 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -2363,7 +2363,16 @@ def infer_kwarg_types(self, node): for kwarg in node.keywords: kwarg_name = kwarg.arg validate_expected_type(kwarg.value, self._kwargs[kwarg_name].typ) - ret[kwarg_name] = get_exact_type_from_node(kwarg.value) + if kwarg_name == "method_id": + # Filter out unsigned integer types in the case of hex integers + p_types = [ + t + for t in get_possible_types_from_node(kwarg.value) + if not isinstance(t, IntegerT) + ] + ret[kwarg_name] = p_types.pop() + else: + ret[kwarg_name] = get_exact_type_from_node(kwarg.value) return ret def fetch_call_return(self, node): From fd4110567a579654ad6f3164486b97cdc18ef09b Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 5 Dec 2024 15:00:03 +0800 Subject: [PATCH 13/25] fix send test --- tests/functional/syntax/test_send.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/tests/functional/syntax/test_send.py b/tests/functional/syntax/test_send.py index ffad1b3792..c9ed69937f 100644 --- a/tests/functional/syntax/test_send.py +++ b/tests/functional/syntax/test_send.py @@ -22,14 +22,6 @@ def foo(): ), ( """ -@external -def foo(): - send(0x1234567890123456789012345678901234567890, 0x1234567890123456789012345678901234567890) - """, - TypeMismatch, - ), - ( - """ x: int128 @external @@ -161,6 +153,11 @@ def foo(): def foo(): send(0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe, 5, gas=self.x) """, + """ +@external +def foo(): + send(0x1234567890123456789012345678901234567890, 0x1234567890123456789012345678901234567890) + """, ] From ad01f5aab27b76fe8225993537f3836f316a0c88 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 5 Dec 2024 15:01:53 +0800 Subject: [PATCH 14/25] add failing test for signed int constant --- tests/functional/syntax/test_constants.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/functional/syntax/test_constants.py b/tests/functional/syntax/test_constants.py index db2accf359..6d871bd91e 100644 --- a/tests/functional/syntax/test_constants.py +++ b/tests/functional/syntax/test_constants.py @@ -171,6 +171,13 @@ def hello() : """, ImmutableViolation, ), + ( + # hex values not allowed for signed integer types + """ +FOO: constant(int8) = 0xff + """, + TypeMismatch, + ), ] From 97feac2eab35c61ba8b5ea8826116e4974945fe6 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 5 Dec 2024 15:03:11 +0800 Subject: [PATCH 15/25] clean up --- vyper/semantics/types/primitives.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vyper/semantics/types/primitives.py b/vyper/semantics/types/primitives.py index 853e6a635d..1f35038410 100644 --- a/vyper/semantics/types/primitives.py +++ b/vyper/semantics/types/primitives.py @@ -141,12 +141,11 @@ def validate_literal(self, node: vy_ast.Constant) -> None: value = node.value if isinstance(node, vy_ast.Hex): - # TODO: raise if signed integer if self.is_signed: raise InvalidLiteral("Hex integers must be unsigned", node) - if node.value not in (node.value.lower(), node.value.upper()): raise InvalidLiteral("Cannot mix uppercase and lowercase for hex integers", node) + value = node.int_value if value < lower: From b56c5abfd4f262eb965a9119e33fddc8f398250d Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:41:28 +0800 Subject: [PATCH 16/25] support signed ints --- vyper/ast/nodes.py | 4 ++-- vyper/ast/nodes.pyi | 2 +- vyper/codegen/expr.py | 7 ++++--- vyper/semantics/types/primitives.py | 8 ++++---- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py index 5e9132b643..daae397d4c 100644 --- a/vyper/ast/nodes.py +++ b/vyper/ast/nodes.py @@ -885,9 +885,9 @@ def bytes_value(self): return bytes.fromhex(self.value.removeprefix("0x")) @property - def int_value(self): + def uint_value(self): """ - This value as integer + This value as unsigned integer """ return hex_to_int(self.value) diff --git a/vyper/ast/nodes.pyi b/vyper/ast/nodes.pyi index d4c7c60b95..90497d1cc9 100644 --- a/vyper/ast/nodes.pyi +++ b/vyper/ast/nodes.pyi @@ -121,7 +121,7 @@ class Hex(Num): @property def n_bytes(self): ... @property - def int_value(self): ... + def uint_value(self): ... class Str(Constant): ... class Bytes(Constant): ... diff --git a/vyper/codegen/expr.py b/vyper/codegen/expr.py index cdf9be8c19..f7e1cac9a2 100644 --- a/vyper/codegen/expr.py +++ b/vyper/codegen/expr.py @@ -67,6 +67,7 @@ bytes_to_int, is_checksum_encoded, string_to_bytes, + unsigned_to_signed, vyper_warn, ) @@ -135,9 +136,9 @@ def parse_Hex(self): return IRnode.from_list(val, typ=t) elif is_integer_type(t): - assert not t.is_signed - - val = self.expr.int_value + val = self.expr.uint_value + if t.is_signed: + val = unsigned_to_signed(val, t.bits, strict=True) return IRnode.from_list(val, typ=t) # String literals diff --git a/vyper/semantics/types/primitives.py b/vyper/semantics/types/primitives.py index 1f35038410..1c0bea5e51 100644 --- a/vyper/semantics/types/primitives.py +++ b/vyper/semantics/types/primitives.py @@ -13,7 +13,7 @@ OverflowException, VyperException, ) -from vyper.utils import checksum_encode, int_bounds, is_checksum_encoded +from vyper.utils import checksum_encode, int_bounds, is_checksum_encoded, unsigned_to_signed from .base import VyperType from .bytestrings import BytesT @@ -141,12 +141,12 @@ def validate_literal(self, node: vy_ast.Constant) -> None: value = node.value if isinstance(node, vy_ast.Hex): - if self.is_signed: - raise InvalidLiteral("Hex integers must be unsigned", node) if node.value not in (node.value.lower(), node.value.upper()): raise InvalidLiteral("Cannot mix uppercase and lowercase for hex integers", node) - value = node.int_value + value = node.uint_value + if self.is_signed: + value = unsigned_to_signed(value, self.bits, strict=True) if value < lower: raise OverflowException(f"Value is below lower bound for given type ({lower})", node) From 2593b1840fb74f8931d2d4ef079c15141a6f5135 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:43:46 +0800 Subject: [PATCH 17/25] remove failing signed int tests --- .../codegen/types/numbers/test_constants.py | 17 +---------------- .../codegen/types/numbers/test_signed_ints.py | 10 ---------- tests/functional/syntax/test_constants.py | 7 ------- 3 files changed, 1 insertion(+), 33 deletions(-) diff --git a/tests/functional/codegen/types/numbers/test_constants.py b/tests/functional/codegen/types/numbers/test_constants.py index a243d87aab..f29a20153b 100644 --- a/tests/functional/codegen/types/numbers/test_constants.py +++ b/tests/functional/codegen/types/numbers/test_constants.py @@ -5,7 +5,7 @@ from tests.utils import ZERO_ADDRESS, decimal_to_int from vyper.compiler import compile_code from vyper.exceptions import TypeMismatch -from vyper.utils import MemoryPositions, hex_to_int +from vyper.utils import MemoryPositions def search_for_sublist(ir, sublist): @@ -244,18 +244,3 @@ def contains(a: int128) -> bool: assert c.contains(44) is True assert c.contains(33) is True assert c.contains(3) is False - - -def test_constant_hex_int(get_contract): - test_value = "0xfa" - code = f""" -X: constant(uint8) = {test_value} -@external -def test() -> uint8: - y: uint8 = X - return y - """ - - c = get_contract(code) - - assert c.test() == hex_to_int(test_value) diff --git a/tests/functional/codegen/types/numbers/test_signed_ints.py b/tests/functional/codegen/types/numbers/test_signed_ints.py index de66c704a1..608783000a 100644 --- a/tests/functional/codegen/types/numbers/test_signed_ints.py +++ b/tests/functional/codegen/types/numbers/test_signed_ints.py @@ -478,13 +478,3 @@ def foo(): compile_code(code) assert e.value._hint == "did you mean `-5 // 9`?" - - -def test_invalid_hex_int(): - code = """ -@external -def foo(): - a: int8 = 0xff - """ - with pytest.raises(TypeMismatch): - compile_code(code) diff --git a/tests/functional/syntax/test_constants.py b/tests/functional/syntax/test_constants.py index 6d871bd91e..db2accf359 100644 --- a/tests/functional/syntax/test_constants.py +++ b/tests/functional/syntax/test_constants.py @@ -171,13 +171,6 @@ def hello() : """, ImmutableViolation, ), - ( - # hex values not allowed for signed integer types - """ -FOO: constant(int8) = 0xff - """, - TypeMismatch, - ), ] From 01a5bd4a6e5b1761b251ec42447d0bb1e6315fe7 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:50:43 +0800 Subject: [PATCH 18/25] parametrize signed int test --- .../codegen/types/numbers/test_signed_ints.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/tests/functional/codegen/types/numbers/test_signed_ints.py b/tests/functional/codegen/types/numbers/test_signed_ints.py index 608783000a..3910a62e0f 100644 --- a/tests/functional/codegen/types/numbers/test_signed_ints.py +++ b/tests/functional/codegen/types/numbers/test_signed_ints.py @@ -13,7 +13,7 @@ ZeroDivisionException, ) from vyper.semantics.types import IntegerT -from vyper.utils import evm_div, evm_mod +from vyper.utils import evm_div, evm_mod, unsigned_to_signed types = sorted(IntegerT.signeds()) @@ -253,8 +253,9 @@ def num_sub() -> {typ}: @pytest.mark.parametrize("op", sorted(ARITHMETIC_OPS.keys())) @pytest.mark.parametrize("typ", types) +@pytest.mark.parametrize("is_hex_int", [True, False]) @pytest.mark.fuzzing -def test_arithmetic_thorough(get_contract, tx_failed, op, typ): +def test_arithmetic_thorough(get_contract, tx_failed, op, typ, is_hex_int): # both variables code_1 = f""" @external @@ -329,9 +330,16 @@ def foo() -> {typ}: ok = in_bounds and not div_by_zero - code_2 = code_2_template.format(typ=typ, op=op, y=y) - code_3 = code_3_template.format(typ=typ, op=op, x=x) - code_4 = code_4_template.format(typ=typ, op=op, x=x, y=y) + formatted_x = x + formatted_y = y + + if is_hex_int: + formatted_x = unsigned_to_signed(x, typ.bits) + formatted_y = unsigned_to_signed(y, typ.bits) + + code_2 = code_2_template.format(typ=typ, op=op, y=formatted_y) + code_3 = code_3_template.format(typ=typ, op=op, x=formatted_x) + code_4 = code_4_template.format(typ=typ, op=op, x=formatted_x, y=formatted_y) if ok: assert c.foo(x, y) == expected From 4bc2558de4888a6dfebbfaec07bab2acb172e109 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:10:27 +0800 Subject: [PATCH 19/25] relax strictness of conversion when validating literal --- vyper/semantics/types/primitives.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/semantics/types/primitives.py b/vyper/semantics/types/primitives.py index 1c0bea5e51..55b0ed0ab0 100644 --- a/vyper/semantics/types/primitives.py +++ b/vyper/semantics/types/primitives.py @@ -146,7 +146,7 @@ def validate_literal(self, node: vy_ast.Constant) -> None: value = node.uint_value if self.is_signed: - value = unsigned_to_signed(value, self.bits, strict=True) + value = unsigned_to_signed(value, self.bits) if value < lower: raise OverflowException(f"Value is below lower bound for given type ({lower})", node) From 05f675779a123b68ad5dcc34fc4e409aedc740af Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:35:05 +0800 Subject: [PATCH 20/25] fix signed int test --- tests/functional/codegen/types/numbers/test_signed_ints.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/functional/codegen/types/numbers/test_signed_ints.py b/tests/functional/codegen/types/numbers/test_signed_ints.py index 3910a62e0f..0028a8ca56 100644 --- a/tests/functional/codegen/types/numbers/test_signed_ints.py +++ b/tests/functional/codegen/types/numbers/test_signed_ints.py @@ -13,7 +13,7 @@ ZeroDivisionException, ) from vyper.semantics.types import IntegerT -from vyper.utils import evm_div, evm_mod, unsigned_to_signed +from vyper.utils import evm_div, evm_mod, signed_to_unsigned types = sorted(IntegerT.signeds()) @@ -334,8 +334,9 @@ def foo() -> {typ}: formatted_y = y if is_hex_int: - formatted_x = unsigned_to_signed(x, typ.bits) - formatted_y = unsigned_to_signed(y, typ.bits) + n_nibbles = typ.bits // 4 + formatted_x = "0x" + hex(signed_to_unsigned(x, typ.bits))[2:].rjust(n_nibbles, "0") + formatted_y = "0x" + hex(signed_to_unsigned(y, typ.bits))[2:].rjust(n_nibbles, "0") code_2 = code_2_template.format(typ=typ, op=op, y=formatted_y) code_3 = code_3_template.format(typ=typ, op=op, x=formatted_x) From 389842fc633c21b387da0d4ecfa2bf613e74f169 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:41:57 +0800 Subject: [PATCH 21/25] fix constant folding --- vyper/semantics/analysis/constant_folding.py | 7 ++++--- vyper/semantics/analysis/utils.py | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/vyper/semantics/analysis/constant_folding.py b/vyper/semantics/analysis/constant_folding.py index 98cab0f8cb..6f885f025d 100644 --- a/vyper/semantics/analysis/constant_folding.py +++ b/vyper/semantics/analysis/constant_folding.py @@ -131,10 +131,11 @@ def visit_UnaryOp(self, node): def visit_BinOp(self, node): left, right = [i.get_folded_value() for i in (node.left, node.right)] - if type(left) is not type(right): - raise UnfoldableNode("invalid operation", node) - if not isinstance(left, vy_ast.Num): + valid_integer_nodes = (vy_ast.Hex, vy_ast.Int) + if not type(left) not in valid_integer_nodes: raise UnfoldableNode("not a number!", node.left) + if not type(right) not in valid_integer_nodes: + raise UnfoldableNode("invalid operation", node) # this validation is performed to prevent the compiler from hanging # on very large shifts and improve the error message for negative diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index a31ce7acc1..a8e13a06a4 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -306,6 +306,7 @@ def types_from_Constant(self, node): if not isinstance(node, t._valid_literal): continue + print("type: ", t) # special handling for bytestrings since their # class objects are in the type map, not the type itself # (worth rethinking this design at some point.) @@ -315,7 +316,7 @@ def types_from_Constant(self, node): # any more validation which needs to occur t.validate_literal(node) types_list.append(t) - except VyperException: + except VyperException as e: continue if types_list: From 1f9bfcffe5b3126e8f30091ad7bcbc729c3520a8 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:45:01 +0800 Subject: [PATCH 22/25] fix lint --- vyper/semantics/analysis/utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py index a8e13a06a4..a31ce7acc1 100644 --- a/vyper/semantics/analysis/utils.py +++ b/vyper/semantics/analysis/utils.py @@ -306,7 +306,6 @@ def types_from_Constant(self, node): if not isinstance(node, t._valid_literal): continue - print("type: ", t) # special handling for bytestrings since their # class objects are in the type map, not the type itself # (worth rethinking this design at some point.) @@ -316,7 +315,7 @@ def types_from_Constant(self, node): # any more validation which needs to occur t.validate_literal(node) types_list.append(t) - except VyperException as e: + except VyperException: continue if types_list: From d50eef15a19adb0fefd0479dfee4726fb6a8ba14 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:08:23 +0800 Subject: [PATCH 23/25] convert hex values to uint for folding --- vyper/semantics/analysis/constant_folding.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/vyper/semantics/analysis/constant_folding.py b/vyper/semantics/analysis/constant_folding.py index 6f885f025d..ecdb28a5e5 100644 --- a/vyper/semantics/analysis/constant_folding.py +++ b/vyper/semantics/analysis/constant_folding.py @@ -132,18 +132,27 @@ def visit_UnaryOp(self, node): def visit_BinOp(self, node): left, right = [i.get_folded_value() for i in (node.left, node.right)] valid_integer_nodes = (vy_ast.Hex, vy_ast.Int) - if not type(left) not in valid_integer_nodes: + if not isinstance(left, valid_integer_nodes): raise UnfoldableNode("not a number!", node.left) - if not type(right) not in valid_integer_nodes: + if not isinstance(right, valid_integer_nodes): raise UnfoldableNode("invalid operation", node) + l_val = left.value + r_val = right.value + + # hex literals default to unsigned values during constant folding + if isinstance(left, vy_ast.Hex): + l_val = left.uint_value + if isinstance(right, vy_ast.Hex): + r_val = right.uint_value + # this validation is performed to prevent the compiler from hanging # on very large shifts and improve the error message for negative # values. - if isinstance(node.op, (vy_ast.LShift, vy_ast.RShift)) and not (0 <= right.value <= 256): + if isinstance(node.op, (vy_ast.LShift, vy_ast.RShift)) and not (0 <= r_val <= 256): raise InvalidLiteral("Shift bits must be between 0 and 256", node.right) - value = node.op._op(left.value, right.value) + value = node.op._op(l_val, r_val) return type(left).from_node(node, value=value) def visit_BoolOp(self, node): From ebb15e14f2e804ee4cba1c0cb814ef7d1c27ada7 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:23:22 +0800 Subject: [PATCH 24/25] handle decimal nodes separately --- vyper/semantics/analysis/constant_folding.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/vyper/semantics/analysis/constant_folding.py b/vyper/semantics/analysis/constant_folding.py index ecdb28a5e5..53a276831f 100644 --- a/vyper/semantics/analysis/constant_folding.py +++ b/vyper/semantics/analysis/constant_folding.py @@ -132,10 +132,12 @@ def visit_UnaryOp(self, node): def visit_BinOp(self, node): left, right = [i.get_folded_value() for i in (node.left, node.right)] valid_integer_nodes = (vy_ast.Hex, vy_ast.Int) - if not isinstance(left, valid_integer_nodes): - raise UnfoldableNode("not a number!", node.left) - if not isinstance(right, valid_integer_nodes): + dissimilar_integer_nodes = isinstance(left, valid_integer_nodes) and not isinstance(right, valid_integer_nodes) + dissimilar_decimal_nodes = isinstance(left, vy_ast.Decimal) and type(left) is not type(right) + if dissimilar_decimal_nodes and dissimilar_integer_nodes: raise UnfoldableNode("invalid operation", node) + if not isinstance(left, (vy_ast.Hex, vy_ast.Num)): + raise UnfoldableNode("not a number!", node.left) l_val = left.value r_val = right.value From f850e81a1b3d6d0b82ba7d38b4326831e3ef4954 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:35:04 +0800 Subject: [PATCH 25/25] fold hex into int --- vyper/semantics/analysis/constant_folding.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/vyper/semantics/analysis/constant_folding.py b/vyper/semantics/analysis/constant_folding.py index 53a276831f..d6b99432eb 100644 --- a/vyper/semantics/analysis/constant_folding.py +++ b/vyper/semantics/analysis/constant_folding.py @@ -155,7 +155,13 @@ def visit_BinOp(self, node): raise InvalidLiteral("Shift bits must be between 0 and 256", node.right) value = node.op._op(l_val, r_val) - return type(left).from_node(node, value=value) + + new_node_type = type(left) + # fold hex integers into Int nodes + if isinstance(left, vy_ast.Hex): + new_node_type = vy_ast.Int + + return new_node_type.from_node(node, value=value) def visit_BoolOp(self, node): values = [v.get_folded_value() for v in node.values]