diff --git a/tests/parser/syntax/test_return_tuple.py b/tests/parser/syntax/test_return_tuple.py index 0e7fd14349..6fa373f988 100644 --- a/tests/parser/syntax/test_return_tuple.py +++ b/tests/parser/syntax/test_return_tuple.py @@ -140,3 +140,57 @@ def foo(a: address) -> (uint256, uint256, uint256, uint256, uint256): c2 = get_contract(code2) assert c2.foo(c.address) == [1, 2, 3, 4, 5] + + +def test_single_type_tuple_int(get_contract): + code = """ +@view +@external +def foo() -> (uint256[3], uint256, uint256[2][2]): + return [1,2,3], 4, [[5,6], [7,8]] + +@view +@external +def foo2(a: int128, b: int128) -> (int128[5], int128, int128[2]): + return [1,2,3,a,5], b, [7,8] + """ + + c = get_contract(code) + + assert c.foo() == [[1, 2, 3], 4, [[5, 6], [7, 8]]] + assert c.foo2(4, 6) == [[1, 2, 3, 4, 5], 6, [7, 8]] + + +def test_single_type_tuple_address(get_contract): + code = """ +@view +@external +def foo() -> (address, address[2]): + return ( + self, + [0xF5D4020dCA6a62bB1efFcC9212AAF3c9819E30D7, 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF] + ) + """ + + c = get_contract(code) + + assert c.foo() == [ + c.address, + [ + "0xF5D4020dCA6a62bB1efFcC9212AAF3c9819E30D7", + "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF", + ], + ] + + +def test_single_type_tuple_bytes(get_contract): + code = """ +@view +@external +def foo() -> (Bytes[5], Bytes[5]): + return b"hello", b"there" + """ + + c = get_contract(code) + + assert c.foo() == [b"hello", b"there"] diff --git a/vyper/codegen/return_.py b/vyper/codegen/return_.py index fbfa54b020..c179dd1924 100644 --- a/vyper/codegen/return_.py +++ b/vyper/codegen/return_.py @@ -1,7 +1,7 @@ from vyper import ast as vy_ast from vyper.parser.lll_node import LLLnode -from vyper.parser.parser_utils import getpos -from vyper.types import BaseType +from vyper.parser.parser_utils import getpos, make_setter +from vyper.types import BaseType, ListType, TupleType, get_size_of_type from vyper.types.check import check_assign from vyper.utils import MemoryPositions @@ -88,9 +88,38 @@ def gen_tuple_return(stmt, context, sub): check_assign(return_buffer, sub, pos=getpos(stmt)) - # in case of multi we can't create a variable to store location of the return expression - # as multi can have data from multiple location like store, calldata etc if sub.value == "multi": + + if isinstance(context.return_type, TupleType) and not abi_typ.dynamic_size_bound(): + # for tuples where every value is of the same type and a fixed length, + # we can simplify the encoding by treating it as though it were an array + base_types = set() + for typ in context.return_type.members: + while isinstance(typ, ListType): + typ = typ.subtype + base_types.add(typ.typ) + + if len(base_types) == 1: + new_sub = LLLnode.from_list( + context.new_internal_variable(context.return_type), + typ=context.return_type, + location="memory", + ) + setter = make_setter(new_sub, sub, "memory", pos=getpos(stmt)) + return LLLnode.from_list( + [ + "seq", + setter, + make_return_stmt( + stmt, context, new_sub, get_size_of_type(context.return_type) * 32, + ), + ], + typ=None, + pos=getpos(stmt), + ) + + # in case of multi we can't create a variable to store location of the return expression + # as multi can have data from multiple location like store, calldata etc encode_out = abi_encode(return_buffer, sub, pos=getpos(stmt), returns=True) load_return_len = ["mload", MemoryPositions.FREE_VAR_SPACE] os = [