From cfda8022ca526fa298c17656c054db5c70750c14 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 20 Feb 2025 15:34:00 +0800 Subject: [PATCH 1/9] add branch for literal bytestrings --- vyper/builtins/_convert.py | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/vyper/builtins/_convert.py b/vyper/builtins/_convert.py index a494e4a344..c45b131a72 100644 --- a/vyper/builtins/_convert.py +++ b/vyper/builtins/_convert.py @@ -363,20 +363,28 @@ def to_decimal(expr, arg, out_typ): raise CompilerPanic("unreachable") -@_input_types(IntegerT, DecimalT, BytesM_T, AddressT, BytesT, BoolT) +@_input_types(IntegerT, DecimalT, BytesM_T, AddressT, BytesT, StringT, BoolT) def to_bytes_m(expr, arg, out_typ): _check_bytes(expr, arg, out_typ, max_bytes_allowed=out_typ.m) - if isinstance(arg.typ, BytesT): - bytes_val = LOAD(bytes_data_ptr(arg)) - - # zero out any dirty bytes (which can happen in the last - # word of a bytearray) - len_ = get_bytearray_length(arg) - num_zero_bits = IRnode.from_list(["mul", ["sub", 32, len_], 8]) - with num_zero_bits.cache_when_complex("bits") as (b, num_zero_bits): - arg = shl(num_zero_bits, shr(num_zero_bits, bytes_val)) - arg = b.resolve(arg) + if isinstance(arg.typ, _BytestringT): + # handle literal bytestrings first + if isinstance(expr, vy_ast.Constant) and arg.typ.length <= out_typ.m: + arg = int(expr.value.encode("utf-8").hex(), 16) + + elif isinstance(arg.typ, BytesT): + bytes_val = LOAD(bytes_data_ptr(arg)) + + # zero out any dirty bytes (which can happen in the last + # word of a bytearray) + len_ = get_bytearray_length(arg) + num_zero_bits = IRnode.from_list(["mul", ["sub", 32, len_], 8]) + with num_zero_bits.cache_when_complex("bits") as (b, num_zero_bits): + arg = shl(num_zero_bits, shr(num_zero_bits, bytes_val)) + arg = b.resolve(arg) + + else: + _FAIL(arg.typ, out_typ, expr) elif is_bytes_m_type(arg.typ): # clamp if it's a downcast From 77201add57d780f6e968f54347103f493ab00e26 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 20 Feb 2025 15:41:06 +0800 Subject: [PATCH 2/9] fix lint --- vyper/builtins/_convert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/builtins/_convert.py b/vyper/builtins/_convert.py index c45b131a72..f401fa1da4 100644 --- a/vyper/builtins/_convert.py +++ b/vyper/builtins/_convert.py @@ -382,7 +382,7 @@ def to_bytes_m(expr, arg, out_typ): with num_zero_bits.cache_when_complex("bits") as (b, num_zero_bits): arg = shl(num_zero_bits, shr(num_zero_bits, bytes_val)) arg = b.resolve(arg) - + else: _FAIL(arg.typ, out_typ, expr) From 9fca298048cf150ef16cf99cf79fcfb9a50c26c4 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 20 Feb 2025 15:53:48 +0800 Subject: [PATCH 3/9] fix encoding --- vyper/builtins/_convert.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vyper/builtins/_convert.py b/vyper/builtins/_convert.py index f401fa1da4..492271e2a7 100644 --- a/vyper/builtins/_convert.py +++ b/vyper/builtins/_convert.py @@ -370,7 +370,10 @@ def to_bytes_m(expr, arg, out_typ): if isinstance(arg.typ, _BytestringT): # handle literal bytestrings first if isinstance(expr, vy_ast.Constant) and arg.typ.length <= out_typ.m: - arg = int(expr.value.encode("utf-8").hex(), 16) + val = expr.value + if isinstance(arg.typ, StringT): + val = expr.value.encode("utf-8") + arg = int(val.hex(), 16) elif isinstance(arg.typ, BytesT): bytes_val = LOAD(bytes_data_ptr(arg)) From 4f38a7f7d58095504495963435663b2f79a27bcf Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Thu, 20 Feb 2025 16:25:56 +0800 Subject: [PATCH 4/9] fix padding --- vyper/builtins/_convert.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vyper/builtins/_convert.py b/vyper/builtins/_convert.py index 492271e2a7..a1168bd144 100644 --- a/vyper/builtins/_convert.py +++ b/vyper/builtins/_convert.py @@ -372,8 +372,10 @@ def to_bytes_m(expr, arg, out_typ): if isinstance(expr, vy_ast.Constant) and arg.typ.length <= out_typ.m: val = expr.value if isinstance(arg.typ, StringT): - val = expr.value.encode("utf-8") - arg = int(val.hex(), 16) + val = val.encode("utf-8") + + # bytes_m types are left padded with zeros + arg = int(val.hex(), 16) << 8 * (out_typ.m - arg.typ.length) elif isinstance(arg.typ, BytesT): bytes_val = LOAD(bytes_data_ptr(arg)) From 67f344cc8d34c8b0245d33976c6f50d574443576 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 21 Feb 2025 12:36:01 +0800 Subject: [PATCH 5/9] fix conversion --- vyper/builtins/_convert.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vyper/builtins/_convert.py b/vyper/builtins/_convert.py index a1168bd144..7df2573c02 100644 --- a/vyper/builtins/_convert.py +++ b/vyper/builtins/_convert.py @@ -374,8 +374,7 @@ def to_bytes_m(expr, arg, out_typ): if isinstance(arg.typ, StringT): val = val.encode("utf-8") - # bytes_m types are left padded with zeros - arg = int(val.hex(), 16) << 8 * (out_typ.m - arg.typ.length) + arg = shl(256 - out_typ.m_bits, int(val.hex(), 16)) elif isinstance(arg.typ, BytesT): bytes_val = LOAD(bytes_data_ptr(arg)) From 75ff342add1403390a23a92608a2e529cc5fa80e Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 21 Feb 2025 12:36:15 +0800 Subject: [PATCH 6/9] add test --- .../builtins/codegen/test_convert.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/functional/builtins/codegen/test_convert.py b/tests/functional/builtins/codegen/test_convert.py index 9947223e75..1c11938d50 100644 --- a/tests/functional/builtins/codegen/test_convert.py +++ b/tests/functional/builtins/codegen/test_convert.py @@ -662,6 +662,36 @@ def foo() -> {t_bytes}: assert c2.foo() == test_data +@pytest.mark.parametrize("n", range(1, 32)) +def test_literal_bytestrings_to_bytes_m(get_contract, n): + test_data = "1" * n + out = test_data.encode() + + bytes_m_typ = f"bytes{n}" + contract_1 = f""" +@external +def foo() -> {bytes_m_typ}: + return convert(b"{test_data}", {bytes_m_typ}) + +@external +def bar() -> {bytes_m_typ}: + return convert("{test_data}", {bytes_m_typ}) + """ + + contract_2 = f""" +@external +def fubar(x: String[{n}]) -> {bytes_m_typ}: + return convert(x, {bytes_m_typ}) + """ + + c1 = get_contract(contract_1) + assert c1.foo() == out + assert c1.bar() == out + + with pytest.raises(TypeMismatch): + compile_code(contract_2) + + @pytest.mark.parametrize("i_typ,o_typ,val", generate_reverting_cases()) @pytest.mark.fuzzing def test_conversion_failures(get_contract, assert_compile_failed, tx_failed, i_typ, o_typ, val): From 1be4a739a1f76d13b98912f4443febbab76a7833 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Fri, 21 Feb 2025 12:46:37 +0800 Subject: [PATCH 7/9] fix left padding --- vyper/builtins/_convert.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vyper/builtins/_convert.py b/vyper/builtins/_convert.py index 7df2573c02..00cde54a36 100644 --- a/vyper/builtins/_convert.py +++ b/vyper/builtins/_convert.py @@ -374,7 +374,9 @@ def to_bytes_m(expr, arg, out_typ): if isinstance(arg.typ, StringT): val = val.encode("utf-8") - arg = shl(256 - out_typ.m_bits, int(val.hex(), 16)) + # bytes_m types are left padded with zeros + val = int(val.hex(), 16) << 8 * (out_typ.m - arg.typ.length) + arg = shl(256 - out_typ.m_bits, val) elif isinstance(arg.typ, BytesT): bytes_val = LOAD(bytes_data_ptr(arg)) From c41b6475e00c440c5408906094b4bcdddc6581f9 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sun, 23 Feb 2025 09:32:09 +0800 Subject: [PATCH 8/9] add modifiability helper for convert --- vyper/builtins/functions.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py index 629ab684a8..979fcd8b53 100644 --- a/vyper/builtins/functions.py +++ b/vyper/builtins/functions.py @@ -55,6 +55,7 @@ ) from vyper.semantics.analysis.base import Modifiability, VarInfo from vyper.semantics.analysis.utils import ( + check_modifiability, get_common_types, get_exact_type_from_node, get_possible_types_from_node, @@ -194,6 +195,9 @@ def build_IR(self, expr, args, kwargs, context): class Convert(BuiltinFunctionT): _id = "convert" + def check_modifiability_for_call(self, node, modifiability): + return check_modifiability(node.args[0], modifiability) + def fetch_call_return(self, node): _, target_typedef = self.infer_arg_types(node) From 9e916f15a12ee9688314076d280fe39f7116578a Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Sun, 23 Feb 2025 09:32:21 +0800 Subject: [PATCH 9/9] add tests for constant --- tests/functional/syntax/test_constants.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/functional/syntax/test_constants.py b/tests/functional/syntax/test_constants.py index db2accf359..5cec6492cb 100644 --- a/tests/functional/syntax/test_constants.py +++ b/tests/functional/syntax/test_constants.py @@ -324,6 +324,17 @@ def foo(): nonpayable FOO: constant(Foo) = Foo(BAR) BAR: constant(address) = 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF """, + # conversion of literal bytestrings to bytes_M + """ +b: constant(bytes5) = convert(b"vyper", bytes5) + """, + """ +b: constant(bytes5) = convert("vyper", bytes5) + """, + """ +a: constant(Bytes[5]) = b"vyper" +b: constant(bytes5) = convert(a, bytes5) + """, ]