From 2f4c123df7231fd954f4bf2187281a5a7a08b5e8 Mon Sep 17 00:00:00 2001 From: Lance Herman Date: Thu, 8 May 2025 12:59:40 +0600 Subject: [PATCH 1/2] Issue #2498 has been resolved. --- vyper/builtins/functions.py | 126 +++++++++++++++++++++++++++++------- 1 file changed, 103 insertions(+), 23 deletions(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index eab04a2e4b..79840c0366 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -191,37 +191,61 @@ def build_IR(self, expr, args, kwargs, context): class Convert(BuiltinFunctionT): _id = "convert" + def _try_fold(self, node): + validate_call_args(node, 2) + value = node.args[0].get_folded_value() + target_type = node.args[1].get_folded_value() + if not isinstance(target_type, vy_ast.Name): + raise UnfoldableNode + + # Get the target type + target_typedef = type_from_annotation(target_type) + if not isinstance(target_typedef, (BoolT, AddressT, IntegerT, BytesM_T)): + raise UnfoldableNode + + # Convert the value based on target type + if isinstance(target_typedef, BoolT): + if isinstance(value, vy_ast.Int): + result = bool(value.value) + elif isinstance(value, vy_ast.Bool): + result = value.value + else: + raise UnfoldableNode + return vy_ast.Bool.from_node(node, value=result) + + elif isinstance(target_typedef, AddressT): + if isinstance(value, vy_ast.Hex): + result = value.value + else: + raise UnfoldableNode + return vy_ast.Hex.from_node(node, value=result) + + elif isinstance(target_typedef, IntegerT): + if isinstance(value, (vy_ast.Int, vy_ast.Decimal)): + result = int(value.value) + else: + raise UnfoldableNode + return vy_ast.Int.from_node(node, value=result) + + elif isinstance(target_typedef, BytesM_T): + if isinstance(value, vy_ast.Hex): + result = value.value + else: + raise UnfoldableNode + return vy_ast.Hex.from_node(node, value=result) + + raise UnfoldableNode + def fetch_call_return(self, node): _, target_typedef = self.infer_arg_types(node) # note: more type conversion validation happens in convert.py return target_typedef.typedef - # TODO: push this down into convert.py for more consistency def infer_arg_types(self, node, expected_return_typ=None): - validate_call_args(node, 2) - + self._validate_arg_types(node) + value_type = get_possible_types_from_node(node.args[0]).pop() target_type = type_from_annotation(node.args[1]) - value_types = get_possible_types_from_node(node.args[0]) - - # For `convert` of integer literals, we need to match type inference rules in - # convert.py codegen routines. - # TODO: This can probably be removed once constant folding for `convert` is implemented - if len(value_types) > 1 and all(isinstance(v, IntegerT) for v in value_types): - # Get the smallest (and unsigned if available) type for non-integer target types - # (note this is different from the ordering returned by `get_possible_types_from_node`) - if not isinstance(target_type, IntegerT): - value_types = sorted(value_types, key=lambda v: (v.is_signed, v.bits), reverse=True) - else: - # filter out the target type from list of possible types - value_types = [i for i in value_types if not target_type.compare_type(i)] - - value_type = value_types.pop() - - # block conversions between same type - if target_type.compare_type(value_type): - raise InvalidType(f"Value and target type are both '{target_type}'", node) - return [value_type, TYPE_T(target_type)] def build_IR(self, expr, context): @@ -290,6 +314,39 @@ class Slice(BuiltinFunctionT): ("length", UINT256_T), ] + def _try_fold(self, node): + validate_call_args(node, 3) + value = node.args[0].get_folded_value() + start = node.args[1].get_folded_value() + length = node.args[2].get_folded_value() + + if not isinstance(start, vy_ast.Int) or not isinstance(length, vy_ast.Int): + raise UnfoldableNode + + start_val = start.value + length_val = length.value + + if length_val < 1: + raise ArgumentException("Length cannot be less than 1", node.args[2]) + + if isinstance(value, vy_ast.Bytes): + if start_val + length_val > len(value.value): + raise ArgumentException("slice out of bounds", node) + result = value.value[start_val:start_val + length_val] + return vy_ast.Bytes.from_node(node, value=result) + elif isinstance(value, vy_ast.Str): + if start_val + length_val > len(value.value): + raise ArgumentException("slice out of bounds", node) + result = value.value[start_val:start_val + length_val] + return vy_ast.Str.from_node(node, value=result) + elif isinstance(value, vy_ast.Hex): + if start_val + length_val > len(value.bytes_value): + raise ArgumentException("slice out of bounds", node) + result = value.bytes_value[start_val:start_val + length_val] + return vy_ast.Bytes.from_node(node, value=result) + else: + raise UnfoldableNode + def fetch_call_return(self, node): arg_type, _, _ = self.infer_arg_types(node) @@ -833,6 +890,29 @@ class Extract32(BuiltinFunctionT): _inputs = [("b", BytesT.any()), ("start", IntegerT.unsigneds())] _kwargs = {"output_type": KwargSettings(TYPE_T.any(), BYTES32_T)} + def _try_fold(self, node): + validate_call_args(node, 2) + value = node.args[0].get_folded_value() + start = node.args[1].get_folded_value() + + if not isinstance(start, vy_ast.Int): + raise UnfoldableNode + + start_val = start.value + + if isinstance(value, vy_ast.Bytes): + if start_val + 32 > len(value.value): + raise ArgumentException("extract32 out of bounds", node) + result = value.value[start_val:start_val + 32] + return vy_ast.Bytes.from_node(node, value=result) + elif isinstance(value, vy_ast.Hex): + if start_val + 32 > len(value.bytes_value): + raise ArgumentException("extract32 out of bounds", node) + result = value.bytes_value[start_val:start_val + 32] + return vy_ast.Bytes.from_node(node, value=result) + else: + raise UnfoldableNode + def fetch_call_return(self, node): self._validate_arg_types(node) return_type = self.infer_kwarg_types(node)["output_type"].typedef From 6cd3a7ed3c439edbde7c51371271bcde8d84cdeb Mon Sep 17 00:00:00 2001 From: Lance Herman Date: Thu, 8 May 2025 15:56:54 +0600 Subject: [PATCH 2/2] This is a second commit of Issue #2498 --- vyper/builtins/functions.py | 81 +++++++++++++++++++++++++++++-------- 1 file changed, 65 insertions(+), 16 deletions(-) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 79840c0366..9d59c197ee 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -198,12 +198,10 @@ def _try_fold(self, node): if not isinstance(target_type, vy_ast.Name): raise UnfoldableNode - # Get the target type target_typedef = type_from_annotation(target_type) if not isinstance(target_typedef, (BoolT, AddressT, IntegerT, BytesM_T)): raise UnfoldableNode - # Convert the value based on target type if isinstance(target_typedef, BoolT): if isinstance(value, vy_ast.Int): result = bool(value.value) @@ -215,20 +213,31 @@ def _try_fold(self, node): elif isinstance(target_typedef, AddressT): if isinstance(value, vy_ast.Hex): + if len(value.value) != 42: # 0x + 40 hex chars + raise InvalidLiteral("Address must be exactly 20 bytes", node.args[0]) result = value.value else: raise UnfoldableNode return vy_ast.Hex.from_node(node, value=result) elif isinstance(target_typedef, IntegerT): - if isinstance(value, (vy_ast.Int, vy_ast.Decimal)): - result = int(value.value) - else: + if not isinstance(value, (vy_ast.Int, vy_ast.Decimal)): raise UnfoldableNode + result = int(value.value) + lo, hi = target_typedef.ast_bounds + if result < lo or result > hi: + raise InvalidLiteral( + f"value {result} out of range for {target_typedef}", node.args[0] + ) return vy_ast.Int.from_node(node, value=result) elif isinstance(target_typedef, BytesM_T): if isinstance(value, vy_ast.Hex): + if len(value.bytes_value) != target_typedef.length: + raise InvalidLiteral( + f"Expected {target_typedef.length} bytes, got {len(value.bytes_value)}", + node.args[0] + ) result = value.value else: raise UnfoldableNode @@ -244,7 +253,11 @@ def fetch_call_return(self, node): def infer_arg_types(self, node, expected_return_typ=None): self._validate_arg_types(node) - value_type = get_possible_types_from_node(node.args[0]).pop() + possible = sorted( + get_possible_types_from_node(node.args[0]), + key=lambda t: (t.typ, getattr(t, "bits", 0)) + ) + value_type = possible[0] target_type = type_from_annotation(node.args[1]) return [value_type, TYPE_T(target_type)] @@ -328,20 +341,31 @@ def _try_fold(self, node): if length_val < 1: raise ArgumentException("Length cannot be less than 1", node.args[2]) + if start_val < 0: + raise ArgumentException("Start index cannot be negative", node.args[1]) if isinstance(value, vy_ast.Bytes): if start_val + length_val > len(value.value): - raise ArgumentException("slice out of bounds", node) + raise ArgumentException( + f"slice out of bounds: start={start_val}, length={length_val}, max_length={len(value.value)}", + node + ) result = value.value[start_val:start_val + length_val] return vy_ast.Bytes.from_node(node, value=result) elif isinstance(value, vy_ast.Str): if start_val + length_val > len(value.value): - raise ArgumentException("slice out of bounds", node) + raise ArgumentException( + f"slice out of bounds: start={start_val}, length={length_val}, max_length={len(value.value)}", + node + ) result = value.value[start_val:start_val + length_val] return vy_ast.Str.from_node(node, value=result) elif isinstance(value, vy_ast.Hex): if start_val + length_val > len(value.bytes_value): - raise ArgumentException("slice out of bounds", node) + raise ArgumentException( + f"slice out of bounds: start={start_val}, length={length_val}, max_length={len(value.bytes_value)}", + node + ) result = value.bytes_value[start_val:start_val + length_val] return vy_ast.Bytes.from_node(node, value=result) else: @@ -393,8 +417,11 @@ def fetch_call_return(self, node): def infer_arg_types(self, node, expected_return_typ=None): self._validate_arg_types(node) - # return a concrete type for `b` - b_type = get_possible_types_from_node(node.args[0]).pop() + possible = sorted( + get_possible_types_from_node(node.args[0]), + key=lambda t: (t.typ, getattr(t, "bits", 0)) + ) + b_type = possible[0] return [b_type, self._inputs[1][1], self._inputs[2][1]] @process_inputs @@ -900,16 +927,34 @@ def _try_fold(self, node): start_val = start.value + if start_val < 0: + raise ArgumentException("Start index cannot be negative", node.args[1]) + + output_type = self.infer_kwarg_types(node)["output_type"].typedef + if isinstance(value, vy_ast.Bytes): if start_val + 32 > len(value.value): - raise ArgumentException("extract32 out of bounds", node) + raise ArgumentException( + f"extract32 out of bounds: start={start_val}, max_length={len(value.value)}", + node + ) result = value.value[start_val:start_val + 32] - return vy_ast.Bytes.from_node(node, value=result) elif isinstance(value, vy_ast.Hex): if start_val + 32 > len(value.bytes_value): - raise ArgumentException("extract32 out of bounds", node) + raise ArgumentException( + f"extract32 out of bounds: start={start_val}, max_length={len(value.bytes_value)}", + node + ) result = value.bytes_value[start_val:start_val + 32] - return vy_ast.Bytes.from_node(node, value=result) + else: + raise UnfoldableNode + + if isinstance(output_type, BytesM_T): + return vy_ast.Hex.from_node(node, value=f"0x{result.hex()}") + elif isinstance(output_type, IntegerT): + return vy_ast.Int.from_node(node, value=int.from_bytes(result, "big")) + elif isinstance(output_type, AddressT): + return vy_ast.Hex.from_node(node, value=f"0x{result.hex()}") else: raise UnfoldableNode @@ -920,7 +965,11 @@ def fetch_call_return(self, node): def infer_arg_types(self, node, expected_return_typ=None): self._validate_arg_types(node) - input_type = get_possible_types_from_node(node.args[0]).pop() + possible = sorted( + get_possible_types_from_node(node.args[0]), + key=lambda t: (t.typ, getattr(t, "bits", 0)) + ) + input_type = possible[0] return [input_type, UINT256_T] def infer_kwarg_types(self, node):