diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b3618fee..5a16aab17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ ## Fixed +* Fixed wrong encoding result in tuple get last item. ([#663](https://github.com/algorand/pyteal/pull/663)) + ## Changed # v0.22.0 diff --git a/pyteal/ast/abi/array_base.py b/pyteal/ast/abi/array_base.py index 7fdf51dc6..cd7fa1f1b 100644 --- a/pyteal/ast/abi/array_base.py +++ b/pyteal/ast/abi/array_base.py @@ -84,7 +84,7 @@ def decode( *, start_index: Expr | None = None, end_index: Expr | None = None, - length: Expr | None = None + length: Expr | None = None, ) -> Expr: """Decode a substring of the passed in encoded byte string and set it as this type's value. @@ -128,11 +128,7 @@ def set(self, values: Sequence[T]) -> Expr: for index, value in enumerate(values): if self.type_spec().value_type_spec() != value.type_spec(): raise TealInputError( - "Cannot assign type {} at index {} to {}".format( - value.type_spec(), - index, - self.type_spec().value_type_spec(), - ) + f"Cannot assign type {value.type_spec()} at index {index} to {self.type_spec().value_type_spec()}" ) encoded = _encode_tuple(values) diff --git a/pyteal/ast/abi/tuple.py b/pyteal/ast/abi/tuple.py index 21782af71..a671518f5 100644 --- a/pyteal/ast/abi/tuple.py +++ b/pyteal/ast/abi/tuple.py @@ -198,9 +198,9 @@ def _index_tuple( if offset == 0: # This is the first and only value in the tuple, so decode all of encoded return output.decode(encoded) - # This is the last value in the tuple, so decode the substring from start_index to the end of - # encoded - return output.decode(encoded, start_index=start_index) + if all(not x.is_dynamic() for x in value_types): + # This is the last element in tuple with all elements being static typed + return output.decode(encoded, start_index=start_index) if offset == 0: # This is the first value in the tuple, so decode the substring from 0 with length length diff --git a/pyteal/ast/abi/tuple_test.py b/pyteal/ast/abi/tuple_test.py index db7f2808f..c3aaf02f4 100644 --- a/pyteal/ast/abi/tuple_test.py +++ b/pyteal/ast/abi/tuple_test.py @@ -236,6 +236,7 @@ class IndexTest(NamedTuple): tuple_t = abi.TupleTypeSpec(abi.BoolTypeSpec(), abi.BoolTypeSpec()) dynamic_array_t1 = abi.DynamicArrayTypeSpec(abi.Uint64TypeSpec()) dynamic_array_t2 = abi.DynamicArrayTypeSpec(abi.Uint16TypeSpec()) + static_bytes_t = abi.StaticBytesTypeSpec(2) encoded = pt.Bytes("encoded") @@ -371,6 +372,23 @@ class IndexTest(NamedTuple): encoded, start_index=pt.ExtractUint16(encoded, pt.Int(0)) ), ), + IndexTest( + types=[dynamic_array_t1, static_bytes_t], + typeIndex=1, + expected=lambda output: output.decode( + encoded, + start_index=pt.Int(2), + length=pt.Int(2), + ), + ), + IndexTest( + types=[static_bytes_t, static_bytes_t], + typeIndex=1, + expected=lambda output: output.decode( + encoded, + start_index=pt.Int(2), + ), + ), IndexTest( types=[byte_t, dynamic_array_t1, byte_t], typeIndex=1, diff --git a/tests/compile_asserts.py b/tests/compile_asserts.py index 85d660818..1e1a2b28e 100644 --- a/tests/compile_asserts.py +++ b/tests/compile_asserts.py @@ -17,7 +17,7 @@ def compile_and_save(approval, version: int, test_name: str) -> tuple[Path, str, with open(tealdir / (name + ".teal"), "w") as f: f.write(compiled) print( - f"""Successfuly tested approval program <<{name}>> having + f"""Successfuly tested approval program <<{name}>> having compiled it into {len(compiled)} characters. See the results in: {tealdir} """ @@ -51,7 +51,7 @@ def assert_new_v_old(approve_func, version: int, test_name: str): tealdir, name, compiled = compile_and_save(approve_func, version, test_name) print( - f"""Compilation resulted in TEAL program of length {len(compiled)}. + f"""Compilation resulted in TEAL program of length {len(compiled)}. To view output SEE <{name}.teal> in ({tealdir}) --------------""" ) diff --git a/tests/integration/abi_roundtrip_test.py b/tests/integration/abi_roundtrip_test.py index feda75870..5e6447422 100644 --- a/tests/integration/abi_roundtrip_test.py +++ b/tests/integration/abi_roundtrip_test.py @@ -76,6 +76,7 @@ class NamedTupleInherit(abi.NamedTuple): abi.Tuple1[abi.Uint32], abi.Tuple1[abi.Uint64], abi.Tuple2[abi.Bool, abi.Byte], + abi.Tuple2[abi.DynamicBytes, abi.StaticBytes[Literal[3]]], abi.Tuple3[abi.Bool, abi.Uint64, abi.Uint32], abi.Tuple3[abi.Byte, abi.Bool, abi.Uint64], abi.Tuple3[abi.Uint8, abi.Byte, abi.Bool], diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(byte[],byte[3])_v6.teal b/tests/integration/teal/roundtrip/app_roundtrip_(byte[],byte[3])_v6.teal new file mode 100644 index 000000000..016ef564e --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_(byte[],byte[3])_v6.teal @@ -0,0 +1,251 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 3 +load 3 +callsub roundtripper_1 +store 2 +byte 0x151f7c75 +load 2 +concat +log +int 1 +return + +// tuple_complement +tuplecomplement_0: +store 8 +load 8 +load 8 +int 0 +extract_uint16 +dig 1 +len +substring3 +store 0 +load 8 +extract 2 3 +store 1 +load 0 +callsub arraycomplement_3 +store 0 +load 1 +callsub arraycomplement_5 +store 1 +load 0 +store 27 +load 27 +store 26 +int 5 +store 25 +load 25 +itob +extract 6 0 +load 1 +concat +load 26 +concat +store 9 +load 9 +retsub + +// round_tripper +roundtripper_1: +store 4 +load 4 +callsub tuplecomplement_0 +store 6 +load 6 +callsub tuplecomplement_0 +store 7 +load 4 +store 31 +load 31 +store 30 +int 6 +store 28 +load 28 +load 31 +len ++ +store 29 +load 29 +int 65536 +< +assert +load 28 +itob +extract 6 0 +load 6 +store 31 +load 30 +load 31 +concat +store 30 +load 29 +store 28 +load 28 +load 31 +len ++ +store 29 +load 29 +int 65536 +< +assert +load 28 +itob +extract 6 0 +concat +load 7 +store 31 +load 30 +load 31 +concat +store 30 +load 29 +store 28 +load 28 +itob +extract 6 0 +concat +load 30 +concat +store 5 +load 5 +retsub + +// numerical_comp +numericalcomp_2: +store 15 +int 255 +load 15 +- +store 16 +load 16 +int 256 +< +assert +load 16 +retsub + +// array_complement +arraycomplement_3: +store 10 +load 10 +int 1 +int 0 +* +int 2 ++ +getbyte +store 12 +load 10 +int 1 +int 1 +* +int 2 ++ +getbyte +store 13 +load 10 +int 1 +int 2 +* +int 2 ++ +getbyte +store 14 +load 12 +callsub numericalcomp_2 +store 12 +load 13 +callsub numericalcomp_2 +store 13 +load 14 +callsub numericalcomp_2 +store 14 +int 3 +store 17 +load 17 +itob +extract 6 0 +byte 0x00 +int 0 +load 12 +setbyte +byte 0x00 +int 0 +load 13 +setbyte +concat +byte 0x00 +int 0 +load 14 +setbyte +concat +concat +store 11 +load 11 +retsub + +// numerical_comp +numericalcomp_4: +store 23 +int 255 +load 23 +- +store 24 +load 24 +int 256 +< +assert +load 24 +retsub + +// array_complement +arraycomplement_5: +store 18 +load 18 +int 1 +int 0 +* +getbyte +store 20 +load 18 +int 1 +int 1 +* +getbyte +store 21 +load 18 +int 1 +int 2 +* +getbyte +store 22 +load 20 +callsub numericalcomp_4 +store 20 +load 21 +callsub numericalcomp_4 +store 21 +load 22 +callsub numericalcomp_4 +store 22 +byte 0x00 +int 0 +load 20 +setbyte +byte 0x00 +int 0 +load 21 +setbyte +concat +byte 0x00 +int 0 +load 22 +setbyte +concat +store 19 +load 19 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(byte[],byte[3])_v8.teal b/tests/integration/teal/roundtrip/app_roundtrip_(byte[],byte[3])_v8.teal new file mode 100644 index 000000000..a3b3139c6 --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_(byte[],byte[3])_v8.teal @@ -0,0 +1,284 @@ +#pragma version 8 +txna ApplicationArgs 0 +store 3 +load 3 +callsub roundtripper_1 +store 2 +byte 0x151f7c75 +load 2 +concat +log +int 1 +return + +// tuple_complement +tuplecomplement_0: +proto 1 1 +byte "" +int 0 +dup +byte "" +dup +int 0 +dupn 13 +byte "" +dup +int 0 +byte "" +dup +int 0 +dupn 10 +byte "" +dup +int 0 +dup +byte "" +dup +frame_dig -1 +frame_dig -1 +int 0 +extract_uint16 +dig 1 +len +substring3 +store 0 +frame_dig -1 +extract 2 3 +store 1 +load 0 +callsub arraycomplement_3 +store 0 +load 1 +callsub arraycomplement_5 +store 1 +load 0 +frame_bury 40 +frame_dig 40 +frame_bury 39 +int 5 +frame_bury 37 +frame_dig 37 +itob +extract 6 0 +load 1 +concat +frame_dig 39 +concat +frame_bury 0 +retsub + +// round_tripper +roundtripper_1: +proto 1 1 +byte "" +dupn 2 +int 0 +dup +byte "" +dup +frame_dig -1 +callsub tuplecomplement_0 +frame_bury 1 +frame_dig 1 +callsub tuplecomplement_0 +frame_bury 2 +frame_dig -1 +frame_bury 6 +frame_dig 6 +frame_bury 5 +int 6 +frame_bury 3 +frame_dig 3 +frame_dig 6 +len ++ +frame_bury 4 +frame_dig 4 +int 65536 +< +assert +frame_dig 3 +itob +extract 6 0 +frame_dig 1 +frame_bury 6 +frame_dig 5 +frame_dig 6 +concat +frame_bury 5 +frame_dig 4 +frame_bury 3 +frame_dig 3 +frame_dig 6 +len ++ +frame_bury 4 +frame_dig 4 +int 65536 +< +assert +frame_dig 3 +itob +extract 6 0 +concat +frame_dig 2 +frame_bury 6 +frame_dig 5 +frame_dig 6 +concat +frame_bury 5 +frame_dig 4 +frame_bury 3 +frame_dig 3 +itob +extract 6 0 +concat +frame_dig 5 +concat +frame_bury 0 +retsub + +// numerical_comp +numericalcomp_2: +proto 1 1 +int 0 +int 255 +frame_dig -1 +- +frame_bury 0 +frame_dig 0 +int 256 +< +assert +retsub + +// array_complement +arraycomplement_3: +proto 1 1 +byte "" +int 0 +dupn 13 +byte "" +dup +int 0 +frame_dig -1 +int 1 +int 0 +* +int 2 ++ +getbyte +frame_bury 1 +frame_dig -1 +int 1 +int 1 +* +int 2 ++ +getbyte +frame_bury 2 +frame_dig -1 +int 1 +int 2 +* +int 2 ++ +getbyte +frame_bury 3 +frame_dig 1 +callsub numericalcomp_2 +frame_bury 1 +frame_dig 2 +callsub numericalcomp_2 +frame_bury 2 +frame_dig 3 +callsub numericalcomp_2 +frame_bury 3 +int 3 +frame_bury 17 +frame_dig 17 +itob +extract 6 0 +byte 0x00 +int 0 +frame_dig 1 +setbyte +byte 0x00 +int 0 +frame_dig 2 +setbyte +concat +byte 0x00 +int 0 +frame_dig 3 +setbyte +concat +concat +frame_bury 0 +retsub + +// numerical_comp +numericalcomp_4: +proto 1 1 +int 0 +int 255 +frame_dig -1 +- +frame_bury 0 +frame_dig 0 +int 256 +< +assert +retsub + +// array_complement +arraycomplement_5: +proto 1 1 +byte "" +int 0 +dupn 10 +byte "" +dup +frame_dig -1 +int 1 +int 0 +* +getbyte +frame_bury 1 +frame_dig -1 +int 1 +int 1 +* +getbyte +frame_bury 2 +frame_dig -1 +int 1 +int 2 +* +getbyte +frame_bury 3 +frame_dig 1 +callsub numericalcomp_4 +frame_bury 1 +frame_dig 2 +callsub numericalcomp_4 +frame_bury 2 +frame_dig 3 +callsub numericalcomp_4 +frame_bury 3 +byte 0x00 +int 0 +frame_dig 1 +setbyte +byte 0x00 +int 0 +frame_dig 2 +setbyte +concat +byte 0x00 +int 0 +frame_dig 3 +setbyte +concat +frame_bury 0 +retsub \ No newline at end of file