diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 1c85a6a3d5..532905a5bd 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -98,6 +98,8 @@ The ``@nonreentrant()`` decorator places a lock on a function, and all func # this function is protected from re-entrancy ... +You can put the ``@nonreentrant()`` decorator on a ``__default__`` function but we recommend against it because in most circumstances it will not work in a meaningful way. + The `__default__` Function -------------------------- diff --git a/tests/functional/codegen/test_abi_encode.py b/tests/functional/codegen/test_abi_encode.py index 1e29f7e7ba..0cd9d4ac5f 100644 --- a/tests/functional/codegen/test_abi_encode.py +++ b/tests/functional/codegen/test_abi_encode.py @@ -1,4 +1,4 @@ -# import pytest +import pytest from decimal import Decimal @@ -116,3 +116,55 @@ def abi_encode3(x: uint256, ensure_tuple: bool, include_method_id: bool) -> Byte human_encoded = abi_encode(f"({human_t})", (human_tuple,)) assert c.abi_encode(*args, True, False).hex() == human_encoded.hex() assert c.abi_encode(*args, True, True).hex() == (method_id + human_encoded).hex() + + +@pytest.mark.parametrize("type,value", [("Bytes", b"hello"), ("String", "hello")]) +def test_abi_encode_length_failing(get_contract, assert_compile_failed, type, value): + code = f""" +struct WrappedBytes: + bs: {type}[6] + +@internal +def foo(): + x: WrappedBytes = WrappedBytes({{bs: {value}}}) + y: {type}[96] = _abi_encode(x, ensure_tuple=True) # should be Bytes[128] + """ + + assert_compile_failed( + lambda: get_contract(code) + ) + + +def test_side_effects_evaluation(get_contract, abi_encode): + contract_1 = """ +counter: uint256 + +@external +def __init__(): + self.counter = 0 + +@external +def get_counter() -> (uint256, String[6]): + self.counter += 1 + return (self.counter, "hello") + """ + + c = get_contract(contract_1) + + contract_2 = """ +interface Foo: + def get_counter() -> (uint256, String[6]): nonpayable + +@external +def foo(addr: address) -> Bytes[164]: + return _abi_encode(Foo(addr).get_counter(), method_id=0xdeadbeef) + """ + + c2 = get_contract(contract_2) + + method_id = 0xDEADBEEF .to_bytes(4, "big") + + # call to get_counter() should be evaluated only once + get_counter_encoded = abi_encode("((uint256,string))", ((1, "hello"),)) + + assert c2.foo(c.address).hex() == (method_id + get_counter_encoded).hex() diff --git a/tests/functional/codegen/test_struct_return.py b/tests/functional/codegen/test_struct_return.py index 9a0e0975ca..9b7517de38 100644 --- a/tests/functional/codegen/test_struct_return.py +++ b/tests/functional/codegen/test_struct_return.py @@ -1,7 +1,7 @@ import pytest -def test_nested_tuple(get_contract): +def test_nested_struct(get_contract): code = """ struct Animal: location: address @@ -12,7 +12,7 @@ def test_nested_tuple(get_contract): animal: Animal @external -def modify_nested_tuple(_human: Human) -> Human: +def modify_nested_struct(_human: Human) -> Human: human: Human = _human # do stuff, edit the structs @@ -25,7 +25,7 @@ def modify_nested_tuple(_human: Human) -> Human: addr1 = "0x1234567890123456789012345678901234567890" addr2 = "0x1234567890123456789012345678900000000000" # assert c.modify_nested_tuple([addr1, 123], [addr2, 456]) == [[addr1, 124], [addr2, 457]] - assert c.modify_nested_tuple( + assert c.modify_nested_struct( {"location": addr1, "animal": {"location": addr2, "fur": "wool"}} ) == ( addr1, @@ -33,8 +33,35 @@ def modify_nested_tuple(_human: Human) -> Human: ) +def test_nested_single_struct(get_contract): + code = """ +struct Animal: + fur: String[32] + +struct Human: + animal: Animal + +@external +def modify_nested_single_struct(_human: Human) -> Human: + human: Human = _human + + # do stuff, edit the structs + # (13 is the length of the result) + human.animal.fur = slice(concat(human.animal.fur, " is great"), 0, 13) + + return human + """ + c = get_contract(code) + + assert c.modify_nested_single_struct( + {"animal": {"fur": "wool"}} + ) == ( + ("wool is great",), + ) + + @pytest.mark.parametrize("string", ["a", "abc", "abcde", "potato"]) -def test_string_inside_tuple(get_contract, string): +def test_string_inside_struct(get_contract, string): code = f""" struct Person: name: String[6] @@ -61,3 +88,31 @@ def test_values(a: address) -> Person: c2 = get_contract(code) assert c2.test_values(c1.address) == (string, 42) + + +@pytest.mark.parametrize("string", ["a", "abc", "abcde", "potato"]) +def test_string_inside_single_struct(get_contract, string): + code = f""" +struct Person: + name: String[6] + +@external +def test_return() -> Person: + return Person({{ name:"{string}"}}) + """ + c1 = get_contract(code) + + code = """ +struct Person: + name: String[6] + +interface jsonabi: + def test_return() -> Person: view + +@external +def test_values(a: address) -> Person: + return jsonabi(a).test_return() + """ + + c2 = get_contract(code) + assert c2.test_values(c1.address) == (string,) diff --git a/tests/parser/features/decorators/test_nonreentrant.py b/tests/parser/features/decorators/test_nonreentrant.py index 28afe3b712..62feb29278 100644 --- a/tests/parser/features/decorators/test_nonreentrant.py +++ b/tests/parser/features/decorators/test_nonreentrant.py @@ -3,7 +3,7 @@ from vyper.exceptions import FunctionDeclarationException -def test_nonrentrant_decorator(get_contract, assert_tx_failed): +def test_nonreentrant_decorator(get_contract, assert_tx_failed): calling_contract_code = """ interface SpecialContract: def unprotected_function(val: String[100], do_callback: bool): nonpayable @@ -85,6 +85,100 @@ def unprotected_function(val: String[100], do_callback: bool): assert_tx_failed(lambda: reentrant_contract.protected_function2("zzz value", True, transact={})) +def test_nonreentrant_decorator_for_default(w3, get_contract, assert_tx_failed): + calling_contract_code = """ +@external +def send_funds(_amount: uint256): + # raw_call() is used to overcome gas limit of send() + response: Bytes[32] = raw_call( + msg.sender, + _abi_encode(msg.sender, _amount, method_id=method_id("transfer(address,uint256)")), + max_outsize=32, + value=_amount + ) + +@external +@payable +def __default__(): + pass + """ + + reentrant_code = """ +interface Callback: + def send_funds(_amount: uint256): nonpayable + +special_value: public(String[100]) +callback: public(Callback) + +@external +def set_callback(c: address): + self.callback = Callback(c) + +@external +@payable +@nonreentrant('default') +def protected_function(val: String[100], do_callback: bool) -> uint256: + self.special_value = val + _amount: uint256 = msg.value + send(self.callback.address, msg.value) + + if do_callback: + self.callback.send_funds(_amount) + return 1 + else: + return 2 + +@external +@payable +def unprotected_function(val: String[100], do_callback: bool): + self.special_value = val + _amount: uint256 = msg.value + send(self.callback.address, msg.value) + + if do_callback: + self.callback.send_funds(_amount) + +@external +@payable +@nonreentrant('default') +def __default__(): + pass + """ + + reentrant_contract = get_contract(reentrant_code) + calling_contract = get_contract(calling_contract_code) + + reentrant_contract.set_callback(calling_contract.address, transact={}) + assert reentrant_contract.callback() == calling_contract.address + + # Test unprotected function without callback. + reentrant_contract.unprotected_function("some value", False, transact={"value": 1000}) + assert reentrant_contract.special_value() == "some value" + assert w3.eth.getBalance(reentrant_contract.address) == 0 + assert w3.eth.getBalance(calling_contract.address) == 1000 + + # Test unprotected function with callback to default. + reentrant_contract.unprotected_function("another value", True, transact={"value": 1000}) + assert reentrant_contract.special_value() == "another value" + assert w3.eth.getBalance(reentrant_contract.address) == 1000 + assert w3.eth.getBalance(calling_contract.address) == 1000 + + # Test protected function without callback. + reentrant_contract.protected_function("surprise!", False, transact={"value": 1000}) + assert reentrant_contract.special_value() == "surprise!" + assert w3.eth.getBalance(reentrant_contract.address) == 1000 + assert w3.eth.getBalance(calling_contract.address) == 2000 + + # Test protected function with callback to default. + assert_tx_failed( + lambda: reentrant_contract.protected_function( + "zzz value", + True, + transact={"value": 1000} + ) + ) + + def test_disallow_on_init_function(get_contract): # nonreentrant has no effect when used on the __init__ fn # however, should disallow its usage regardless diff --git a/tests/parser/features/decorators/test_private.py b/tests/parser/features/decorators/test_private.py index 7a9568b346..ec233c7a62 100644 --- a/tests/parser/features/decorators/test_private.py +++ b/tests/parser/features/decorators/test_private.py @@ -567,6 +567,22 @@ def foo(a: int128) -> (int128, int128): ), ( """ +struct A: + one: uint8 + +@internal +def _foo(_one: uint8) ->A: + return A({one: _one}) + +@external +def foo() -> A: + return self._foo(1) + """, + (), + (1,), + ), + ( + """ struct A: many: uint256[4] one: uint256 diff --git a/tests/parser/features/external_contracts/test_external_contract_calls.py b/tests/parser/features/external_contracts/test_external_contract_calls.py index 6d02e8a489..9fb7fec869 100644 --- a/tests/parser/features/external_contracts/test_external_contract_calls.py +++ b/tests/parser/features/external_contracts/test_external_contract_calls.py @@ -9,8 +9,10 @@ UnknownType, ) +from decimal import Decimal -def test_external_contract_calls(get_contract, get_contract_with_gas_estimation, memory_mocker): + +def test_external_contract_calls(get_contract, get_contract_with_gas_estimation): contract_1 = """ @external def foo(arg1: int128) -> int128: @@ -21,208 +23,838 @@ def foo(arg1: int128) -> int128: contract_2 = """ interface Foo: - def foo(arg1: int128) -> int128: view + def foo(arg1: int128) -> int128: view + +@external +def bar(arg1: address, arg2: int128) -> int128: + return Foo(arg1).foo(arg2) + """ + c2 = get_contract(contract_2) + + assert c2.bar(c.address, 1) == 1 + print("Successfully executed an external contract call") + + +def test_complicated_external_contract_calls( + get_contract, get_contract_with_gas_estimation +): + contract_1 = """ +lucky: public(int128) + +@external +def __init__(_lucky: int128): + self.lucky = _lucky + +@external +def foo() -> int128: + return self.lucky + +@external +def array() -> Bytes[3]: + return b'dog' + """ + + lucky_number = 7 + c = get_contract_with_gas_estimation(contract_1, *[lucky_number]) + + contract_2 = """ +interface Foo: + def foo() -> int128: nonpayable + def array() -> Bytes[3]: view + +@external +def bar(arg1: address) -> int128: + return Foo(arg1).foo() + """ + c2 = get_contract(contract_2) + + assert c2.bar(c.address) == lucky_number + print("Successfully executed a complicated external contract call") + + +@pytest.mark.parametrize("length", [3, 32, 33, 64]) +def test_external_contract_calls_with_bytes(get_contract, length): + contract_1 = f""" +@external +def array() -> Bytes[{length}]: + return b'dog' + """ + + c = get_contract(contract_1) + + contract_2 = """ +interface Foo: + def array() -> Bytes[3]: view + +@external +def get_array(arg1: address) -> Bytes[3]: + return Foo(arg1).array() +""" + + c2 = get_contract(contract_2) + assert c2.get_array(c.address) == b"dog" + + +def test_bytes_too_long(get_contract, assert_tx_failed): + contract_1 = """ +@external +def array() -> Bytes[4]: + return b'doge' + """ + + c = get_contract(contract_1) + + contract_2 = """ +interface Foo: + def array() -> Bytes[3]: view + +@external +def get_array(arg1: address) -> Bytes[3]: + return Foo(arg1).array() +""" + + c2 = get_contract(contract_2) + assert_tx_failed(lambda: c2.get_array(c.address)) + + +@pytest.mark.parametrize( + "revert_string", ["Mayday, mayday!", "A very long revert string" + "." * 512] +) +def test_revert_propagation(get_contract, assert_tx_failed, revert_string): + raiser = f""" +@external +def run(): + raise "{revert_string}" + """ + caller = """ +interface Raises: + def run(): pure + +@external +def run(raiser: address): + Raises(raiser).run() + """ + c1 = get_contract(raiser) + c2 = get_contract(caller) + assert_tx_failed(lambda: c2.run(c1.address), exc_text=revert_string) + + +@pytest.mark.parametrize("a,b", [(3, 3), (4, 3), (3, 4), (32, 32), (33, 33), (64, 64)]) +@pytest.mark.parametrize("actual", [3, 32, 64]) +def test_tuple_with_bytes(get_contract, a, b, actual): + contract_1 = f""" +@external +def array() -> (Bytes[{actual}], int128, Bytes[{actual}]): + return b'dog', 255, b'cat' + """ + + c = get_contract(contract_1) + + contract_2 = f""" +interface Foo: + def array() -> (Bytes[{a}], int128, Bytes[{b}]): view + +@external +def get_array(arg1: address) -> (Bytes[{a}], int128, Bytes[{b}]): + a: Bytes[{a}] = b"" + b: int128 = 0 + c: Bytes[{b}] = b"" + a, b, c = Foo(arg1).array() + return a, b, c +""" + + c2 = get_contract(contract_2) + assert c.array() == [b"dog", 255, b"cat"] + assert c2.get_array(c.address) == [b"dog", 255, b"cat"] + + +@pytest.mark.parametrize("a,b", [(18, 7), (18, 18), (19, 6), (64, 6), (7, 19)]) +@pytest.mark.parametrize("c,d", [(19, 7), (64, 64)]) +def test_tuple_with_bytes_too_long(get_contract, assert_tx_failed, a, c, b, d): + contract_1 = f""" +@external +def array() -> (Bytes[{c}], int128, Bytes[{d}]): + return b'nineteen characters', 255, b'seven!!' + """ + + c = get_contract(contract_1) + + contract_2 = f""" +interface Foo: + def array() -> (Bytes[{a}], int128, Bytes[{b}]): view + +@external +def get_array(arg1: address) -> (Bytes[{a}], int128, Bytes[{b}]): + a: Bytes[{a}] = b"" + b: int128 = 0 + c: Bytes[{b}] = b"" + a, b, c = Foo(arg1).array() + return a, b, c +""" + + c2 = get_contract(contract_2) + assert c.array() == [b"nineteen characters", 255, b"seven!!"] + assert_tx_failed(lambda: c2.get_array(c.address)) + + +def test_tuple_with_bytes_too_long_two(get_contract, assert_tx_failed): + contract_1 = """ +@external +def array() -> (Bytes[30], int128, Bytes[30]): + return b'nineteen characters', 255, b'seven!!' + """ + + c = get_contract(contract_1) + + contract_2 = """ +interface Foo: + def array() -> (Bytes[30], int128, Bytes[3]): view + +@external +def get_array(arg1: address) -> (Bytes[30], int128, Bytes[3]): + a: Bytes[30] = b"" + b: int128 = 0 + c: Bytes[3] = b"" + a, b, c = Foo(arg1).array() + return a, b, c +""" + + c2 = get_contract(contract_2) + assert c.array() == [b"nineteen characters", 255, b"seven!!"] + assert_tx_failed(lambda: c2.get_array(c.address)) + + +@pytest.mark.parametrize("length", [8, 256]) +def test_external_contract_calls_with_uint8(get_contract, length): + contract_1 = f""" +@external +def foo() -> uint{length}: + return 255 + """ + + c = get_contract(contract_1) + + contract_2 = """ +interface Foo: + def foo() -> uint8: view + +@external +def bar(arg1: address) -> uint8: + return Foo(arg1).foo() +""" + + c2 = get_contract(contract_2) + assert c2.bar(c.address) == 255 + + +def test_uint8_too_long(get_contract, assert_tx_failed): + contract_1 = """ +@external +def foo() -> uint256: + return 2**255 + """ + + c = get_contract(contract_1) + + contract_2 = """ +interface Foo: + def foo() -> uint8: view + +@external +def bar(arg1: address) -> uint8: + return Foo(arg1).foo() +""" + + c2 = get_contract(contract_2) + assert_tx_failed(lambda: c2.bar(c.address)) + + +@pytest.mark.parametrize("a,b", [(8, 8), (8, 256), (256, 8), (256, 256)]) +@pytest.mark.parametrize("actual", [8, 256]) +def test_tuple_with_uint8(get_contract, a, b, actual): + contract_1 = f""" +@external +def foo() -> (uint{actual}, Bytes[3], uint{actual}): + return 255, b'dog', 255 + """ + + c = get_contract(contract_1) + + contract_2 = f""" +interface Foo: + def foo() -> (uint{a}, Bytes[3], uint{b}): view + +@external +def bar(arg1: address) -> (uint{a}, Bytes[3], uint{b}): + a: uint{a} = 0 + b: Bytes[3] = b"" + c: uint{b} = 0 + a, b, c = Foo(arg1).foo() + return a, b, c +""" + + c2 = get_contract(contract_2) + assert c.foo() == [255, b"dog", 255] + assert c2.bar(c.address) == [255, b"dog", 255] + + +@pytest.mark.parametrize("a,b", [(8, 256), (256, 8), (256, 256)]) +def test_tuple_with_uint8_too_long(get_contract, assert_tx_failed, a, b): + contract_1 = f""" +@external +def foo() -> (uint{a}, Bytes[3], uint{b}): + return {(2**a)-1}, b'dog', {(2**b)-1} + """ + + c = get_contract(contract_1) + + contract_2 = """ +interface Foo: + def foo() -> (uint8, Bytes[3], uint8): view + +@external +def bar(arg1: address) -> (uint8, Bytes[3], uint8): + a: uint8 = 0 + b: Bytes[3] = b"" + c: uint8 = 0 + a, b, c = Foo(arg1).foo() + return a, b, c +""" + + c2 = get_contract(contract_2) + assert c.foo() == [int(f"{(2**a)-1}"), b"dog", int(f"{(2**b)-1}")] + assert_tx_failed(lambda: c2.bar(c.address)) + + +@pytest.mark.parametrize("a,b", [(8, 256), (256, 8)]) +def test_tuple_with_uint8_too_long_two(get_contract, assert_tx_failed, a, b): + contract_1 = f""" +@external +def foo() -> (uint{b}, Bytes[3], uint{a}): + return {(2**b)-1}, b'dog', {(2**a)-1} + """ + + c = get_contract(contract_1) + + contract_2 = f""" +interface Foo: + def foo() -> (uint{a}, Bytes[3], uint{b}): view + +@external +def bar(arg1: address) -> (uint{a}, Bytes[3], uint{b}): + a: uint{a} = 0 + b: Bytes[3] = b"" + c: uint{b} = 0 + a, b, c = Foo(arg1).foo() + return a, b, c +""" + + c2 = get_contract(contract_2) + assert c.foo() == [int(f"{(2**b)-1}"), b"dog", int(f"{(2**a)-1}")] + assert_tx_failed(lambda: c2.bar(c.address)) + + +@pytest.mark.parametrize("length", [128, 256]) +def test_external_contract_calls_with_int128(get_contract, length): + contract_1 = f""" +@external +def foo() -> int{length}: + return 1 + """ + + c = get_contract(contract_1) + + contract_2 = """ +interface Foo: + def foo() -> int128: view + +@external +def bar(arg1: address) -> int128: + return Foo(arg1).foo() +""" + + c2 = get_contract(contract_2) + assert c2.bar(c.address) == 1 + + +def test_int128_too_long(get_contract, assert_tx_failed): + contract_1 = """ +@external +def foo() -> int256: + return (2**255)-1 + """ + + c = get_contract(contract_1) + + contract_2 = """ +interface Foo: + def foo() -> int128: view + +@external +def bar(arg1: address) -> int128: + return Foo(arg1).foo() +""" + + c2 = get_contract(contract_2) + assert_tx_failed(lambda: c2.bar(c.address)) + + +@pytest.mark.parametrize("a,b", [(128, 128), (128, 256), (256, 128), (256, 256)]) +@pytest.mark.parametrize("actual", [128, 256]) +def test_tuple_with_int128(get_contract, a, b, actual): + contract_1 = f""" +@external +def foo() -> (int{actual}, Bytes[3], int{actual}): + return 255, b'dog', 255 + """ + + c = get_contract(contract_1) + + contract_2 = f""" +interface Foo: + def foo() -> (int{a}, Bytes[3], int{b}): view + +@external +def bar(arg1: address) -> (int{a}, Bytes[3], int{b}): + a: int{a} = 0 + b: Bytes[3] = b"" + c: int{b} = 0 + a, b, c = Foo(arg1).foo() + return a, b, c +""" + + c2 = get_contract(contract_2) + assert c.foo() == [255, b"dog", 255] + assert c2.bar(c.address) == [255, b"dog", 255] + + +@pytest.mark.parametrize("a,b", [(128, 256), (256, 128), (256, 256)]) +def test_tuple_with_int128_too_long(get_contract, assert_tx_failed, a, b): + contract_1 = f""" +@external +def foo() -> (int{a}, Bytes[3], int{b}): + return {(2**(a-1))-1}, b'dog', {(2**(b-1))-1} + """ + + c = get_contract(contract_1) + + contract_2 = """ +interface Foo: + def foo() -> (int128, Bytes[3], int128): view + +@external +def bar(arg1: address) -> (int128, Bytes[3], int128): + a: int128 = 0 + b: Bytes[3] = b"" + c: int128 = 0 + a, b, c = Foo(arg1).foo() + return a, b, c +""" + + c2 = get_contract(contract_2) + assert c.foo() == [int(f"{(2**(a-1))-1}"), b"dog", int(f"{(2**(b-1))-1}")] + assert_tx_failed(lambda: c2.bar(c.address)) + + +@pytest.mark.parametrize("a,b", [(128, 256), (256, 128)]) +def test_tuple_with_int128_too_long_two(get_contract, assert_tx_failed, a, b): + contract_1 = f""" +@external +def foo() -> (int{b}, Bytes[3], int{a}): + return {(2**(b-1))-1}, b'dog', {(2**(a-1))-1} + """ + + c = get_contract(contract_1) + + contract_2 = f""" +interface Foo: + def foo() -> (int{a}, Bytes[3], int{b}): view + +@external +def bar(arg1: address) -> (int{a}, Bytes[3], int{b}): + a: int{a} = 0 + b: Bytes[3] = b"" + c: int{b} = 0 + a, b, c = Foo(arg1).foo() + return a, b, c +""" + + c2 = get_contract(contract_2) + assert c.foo() == [int(f"{(2**(b-1))-1}"), b"dog", int(f"{(2**(a-1))-1}")] + assert_tx_failed(lambda: c2.bar(c.address)) + + +@pytest.mark.parametrize("type", ["uint8", "uint256", "int128", "int256"]) +def test_external_contract_calls_with_decimal(get_contract, type): + contract_1 = f""" +@external +def foo() -> {type}: + return 1 + """ + + c = get_contract(contract_1) + + contract_2 = """ +interface Foo: + def foo() -> decimal: view + +@external +def bar(arg1: address) -> decimal: + return Foo(arg1).foo() +""" + + c2 = get_contract(contract_2) + assert c2.bar(c.address) == Decimal("1e-10") + + +def test_decimal_too_long(get_contract, assert_tx_failed): + contract_1 = """ +@external +def foo() -> uint256: + return 2**255 + """ + + c = get_contract(contract_1) + + contract_2 = """ +interface Foo: + def foo() -> decimal: view + +@external +def bar(arg1: address) -> decimal: + return Foo(arg1).foo() +""" + + c2 = get_contract(contract_2) + assert_tx_failed(lambda: c2.bar(c.address)) + + +@pytest.mark.parametrize("a", ["uint8", "uint256", "int128", "int256"]) +@pytest.mark.parametrize("b", ["uint8", "uint256", "int128", "int256"]) +def test_tuple_with_decimal(get_contract, a, b): + contract_1 = f""" +@external +def foo() -> ({a}, Bytes[3], {b}): + return 0, b'dog', 1 + """ + + c = get_contract(contract_1) + + contract_2 = """ +interface Foo: + def foo() -> (decimal, Bytes[3], decimal): view + +@external +def bar(arg1: address) -> (decimal, Bytes[3], decimal): + a: decimal = 0.0 + b: Bytes[3] = b"" + c: decimal = 0.0 + a, b, c = Foo(arg1).foo() + return a, b, c +""" + + c2 = get_contract(contract_2) + assert c.foo() == [0, b"dog", 1] + result = c2.bar(c.address) + assert result == [Decimal("0.0"), b"dog", Decimal("1e-10")] + + +@pytest.mark.parametrize("a,b", [(8, 256), (256, 8), (256, 256)]) +def test_tuple_with_decimal_too_long(get_contract, assert_tx_failed, a, b): + contract_1 = f""" +@external +def foo() -> (uint{a}, Bytes[3], uint{b}): + return {2**(a-1)}, b'dog', {2**(b-1)} + """ + + c = get_contract(contract_1) + + contract_2 = """ +interface Foo: + def foo() -> (decimal, Bytes[3], decimal): view + +@external +def bar(arg1: address) -> (decimal, Bytes[3], decimal): + a: decimal = 0.0 + b: Bytes[3] = b"" + c: decimal = 0.0 + a, b, c = Foo(arg1).foo() + return a, b, c +""" + + c2 = get_contract(contract_2) + assert c.foo() == [2 ** (a - 1), b"dog", 2 ** (b - 1)] + assert_tx_failed(lambda: c2.bar(c.address)) + + +@pytest.mark.parametrize("type", ["uint8", "uint256", "int128", "int256"]) +def test_external_contract_calls_with_bool(get_contract, type): + contract_1 = f""" +@external +def foo() -> {type}: + return 1 + """ + + c = get_contract(contract_1) + + contract_2 = """ +interface Foo: + def foo() -> bool: view + +@external +def bar(arg1: address) -> bool: + return Foo(arg1).foo() +""" + + c2 = get_contract(contract_2) + assert c2.bar(c.address) is True + + +def test_bool_too_long(get_contract, assert_tx_failed): + contract_1 = """ +@external +def foo() -> uint256: + return 2 + """ + + c = get_contract(contract_1) + + contract_2 = """ +interface Foo: + def foo() -> bool: view @external -def bar(arg1: address, arg2: int128) -> int128: - return Foo(arg1).foo(arg2) - """ +def bar(arg1: address) -> bool: + return Foo(arg1).foo() +""" + c2 = get_contract(contract_2) + assert_tx_failed(lambda: c2.bar(c.address)) - assert c2.bar(c.address, 1) == 1 - print("Successfully executed an external contract call") +@pytest.mark.parametrize("a", ["uint8", "uint256", "int128", "int256"]) +@pytest.mark.parametrize("b", ["uint8", "uint256", "int128", "int256"]) +def test_tuple_with_bool(get_contract, a, b): + contract_1 = f""" +@external +def foo() -> ({a}, Bytes[3], {b}): + return 1, b'dog', 0 + """ -def test_complicated_external_contract_calls( - get_contract, get_contract_with_gas_estimation, memory_mocker -): - contract_1 = """ -lucky: public(int128) + c = get_contract(contract_1) -@external -def __init__(_lucky: int128): - self.lucky = _lucky + contract_2 = """ +interface Foo: + def foo() -> (bool, Bytes[3], bool): view @external -def foo() -> int128: - return self.lucky +def bar(arg1: address) -> (bool, Bytes[3], bool): + a: bool = False + b: Bytes[3] = b"" + c: bool = False + a, b, c = Foo(arg1).foo() + return a, b, c +""" + + c2 = get_contract(contract_2) + assert c.foo() == [1, b"dog", 0] + assert c2.bar(c.address) == [True, b"dog", False] + +@pytest.mark.parametrize("a", ["uint8", "uint256", "int128", "int256"]) +@pytest.mark.parametrize("b", ["uint8", "uint256", "int128", "int256"]) +def test_tuple_with_bool_too_long(get_contract, assert_tx_failed, a, b): + contract_1 = f""" @external -def array() -> Bytes[3]: - return b'dog' +def foo() -> ({a}, Bytes[3], {b}): + return 1, b'dog', 2 """ - lucky_number = 7 - c = get_contract_with_gas_estimation(contract_1, *[lucky_number]) + c = get_contract(contract_1) contract_2 = """ interface Foo: - def foo() -> int128: nonpayable - def array() -> Bytes[3]: view + def foo() -> (bool, Bytes[3], bool): view @external -def bar(arg1: address) -> int128: - return Foo(arg1).foo() - """ - c2 = get_contract(contract_2) +def bar(arg1: address) -> (bool, Bytes[3], bool): + a: bool = False + b: Bytes[3] = b"" + c: bool = False + a, b, c = Foo(arg1).foo() + return a, b, c +""" - assert c2.bar(c.address) == lucky_number - print("Successfully executed a complicated external contract call") + c2 = get_contract(contract_2) + assert c.foo() == [1, b"dog", 2] + assert_tx_failed(lambda: c2.bar(c.address)) -@pytest.mark.parametrize("length", [3, 32, 33, 64]) -def test_external_contract_calls_with_bytes(get_contract, length, memory_mocker): +@pytest.mark.parametrize("type", ["uint8", "int128", "uint256", "int256"]) +def test_external_contract_calls_with_address(get_contract, type): contract_1 = f""" @external -def array() -> Bytes[{length}]: - return b'dog' +def foo() -> {type}: + return 1 """ c = get_contract(contract_1) contract_2 = """ interface Foo: - def array() -> Bytes[3]: view + def foo() -> address: view @external -def get_array(arg1: address) -> Bytes[3]: - return Foo(arg1).array() +def bar(arg1: address) -> address: + return Foo(arg1).foo() """ c2 = get_contract(contract_2) - assert c2.get_array(c.address) == b"dog" + assert c2.bar(c.address) == "0x0000000000000000000000000000000000000001" -def test_bytes_too_long(get_contract, assert_tx_failed, memory_mocker): - contract_1 = """ +@pytest.mark.parametrize("type", ["uint256", "int256"]) +def test_external_contract_calls_with_address_two(get_contract, type): + contract_1 = f""" @external -def array() -> Bytes[4]: - return b'doge' +def foo() -> {type}: + return (2**160)-1 """ c = get_contract(contract_1) contract_2 = """ interface Foo: - def array() -> Bytes[3]: view + def foo() -> address: view @external -def get_array(arg1: address) -> Bytes[3]: - return Foo(arg1).array() +def bar(arg1: address) -> address: + return Foo(arg1).foo() """ c2 = get_contract(contract_2) - assert_tx_failed(lambda: c2.get_array(c.address)) + assert c2.bar(c.address).lower() == "0xffffffffffffffffffffffffffffffffffffffff" -@pytest.mark.parametrize( - "revert_string", ["Mayday, mayday!", "A very long revert string" + "." * 512] -) -def test_revert_propagation(get_contract, assert_tx_failed, revert_string): - raiser = f""" +@pytest.mark.parametrize("type", ["uint256", "int256"]) +def test_address_too_long(get_contract, assert_tx_failed, type): + contract_1 = f""" @external -def run(): - raise "{revert_string}" +def foo() -> {type}: + return 2**160 """ - caller = """ -interface Raises: - def run(): pure + + c = get_contract(contract_1) + + contract_2 = """ +interface Foo: + def foo() -> address: view @external -def run(raiser: address): - Raises(raiser).run() - """ - c1 = get_contract(raiser) - c2 = get_contract(caller) - assert_tx_failed(lambda: c2.run(c1.address), exc_text=revert_string) +def bar(arg1: address) -> address: + return Foo(arg1).foo() +""" + c2 = get_contract(contract_2) + assert_tx_failed(lambda: c2.bar(c.address)) -@pytest.mark.parametrize("a,b", [(3, 3), (4, 3), (3, 4), (32, 32), (33, 33), (64, 64)]) -@pytest.mark.parametrize("actual", [3, 32, 64]) -def test_tuple_with_bytes(get_contract, assert_tx_failed, a, b, actual, memory_mocker): + +@pytest.mark.parametrize("a", ["uint8", "int128", "uint256", "int256"]) +@pytest.mark.parametrize("b", ["uint8", "int128", "uint256", "int256"]) +def test_tuple_with_address(get_contract, a, b): contract_1 = f""" @external -def array() -> (Bytes[{actual}], int128, Bytes[{actual}]): - return b'dog', 255, b'cat' +def foo() -> ({a}, Bytes[3], {b}): + return 16, b'dog', 1 """ c = get_contract(contract_1) - contract_2 = f""" + contract_2 = """ interface Foo: - def array() -> (Bytes[{a}], int128, Bytes[{b}]): view + def foo() -> (address, Bytes[3], address): view @external -def get_array(arg1: address) -> (Bytes[{a}], int128, Bytes[{b}]): - a: Bytes[{a}] = b"" - b: int128 = 0 - c: Bytes[{b}] = b"" - a, b, c = Foo(arg1).array() +def bar(arg1: address) -> (address, Bytes[3], address): + a: address = ZERO_ADDRESS + b: Bytes[3] = b"" + c: address = ZERO_ADDRESS + a, b, c = Foo(arg1).foo() return a, b, c """ c2 = get_contract(contract_2) - assert c.array() == [b"dog", 255, b"cat"] - assert c2.get_array(c.address) == [b"dog", 255, b"cat"] + assert c.foo() == [16, b"dog", 1] + assert c2.bar(c.address) == [ + "0x0000000000000000000000000000000000000010", + b"dog", + "0x0000000000000000000000000000000000000001", + ] -@pytest.mark.parametrize("a,b", [(18, 7), (18, 18), (19, 6), (64, 6), (7, 19)]) -@pytest.mark.parametrize("c,d", [(19, 7), (64, 64)]) -def test_tuple_with_bytes_too_long(get_contract, assert_tx_failed, a, c, b, d, memory_mocker): +@pytest.mark.parametrize("a", ["uint256", "int256"]) +@pytest.mark.parametrize("b", ["uint256", "int256"]) +def test_tuple_with_address_two(get_contract, a, b): contract_1 = f""" @external -def array() -> (Bytes[{c}], int128, Bytes[{d}]): - return b'nineteen characters', 255, b'seven!!' +def foo() -> ({a}, Bytes[3], {b}): + return (2**160)-1, b'dog', (2**160)-2 """ c = get_contract(contract_1) - contract_2 = f""" + contract_2 = """ interface Foo: - def array() -> (Bytes[{a}], int128, Bytes[{b}]): view + def foo() -> (address, Bytes[3], address): view @external -def get_array(arg1: address) -> (Bytes[{a}], int128, Bytes[{b}]): - a: Bytes[{a}] = b"" - b: int128 = 0 - c: Bytes[{b}] = b"" - a, b, c = Foo(arg1).array() +def bar(arg1: address) -> (address, Bytes[3], address): + a: address = ZERO_ADDRESS + b: Bytes[3] = b"" + c: address = ZERO_ADDRESS + a, b, c = Foo(arg1).foo() return a, b, c """ c2 = get_contract(contract_2) - assert c.array() == [b"nineteen characters", 255, b"seven!!"] - assert_tx_failed(lambda: c2.get_array(c.address)) + assert c.foo() == [(2 ** 160) - 1, b"dog", (2 ** 160) - 2] + result = c2.bar(c.address) + assert len(result) == 3 + assert result[0].lower() == "0xffffffffffffffffffffffffffffffffffffffff" + assert result[1] == b"dog" + assert result[2].lower() == "0xfffffffffffffffffffffffffffffffffffffffe" -def test_tuple_with_bytes_too_long_two(get_contract, assert_tx_failed, memory_mocker): - contract_1 = """ +@pytest.mark.parametrize("a", ["uint256", "int256"]) +@pytest.mark.parametrize("b", ["uint256", "int256"]) +def test_tuple_with_address_too_long(get_contract, assert_tx_failed, a, b): + contract_1 = f""" @external -def array() -> (Bytes[30], int128, Bytes[30]): - return b'nineteen characters', 255, b'seven!!' +def foo() -> ({a}, Bytes[3], {b}): + return (2**160)-1, b'dog', 2**160 """ c = get_contract(contract_1) contract_2 = """ interface Foo: - def array() -> (Bytes[30], int128, Bytes[3]): view + def foo() -> (address, Bytes[3], address): view @external -def get_array(arg1: address) -> (Bytes[30], int128, Bytes[3]): - a: Bytes[30] = b"" - b: int128 = 0 - c: Bytes[3] = b"" - a, b, c = Foo(arg1).array() +def bar(arg1: address) -> (address, Bytes[3], address): + a: address = ZERO_ADDRESS + b: Bytes[3] = b"" + c: address = ZERO_ADDRESS + a, b, c = Foo(arg1).foo() return a, b, c """ c2 = get_contract(contract_2) - assert c.array() == [b"nineteen characters", 255, b"seven!!"] - assert_tx_failed(lambda: c2.get_array(c.address)) + assert c.foo() == [(2 ** 160) - 1, b"dog", 2 ** 160] + assert_tx_failed(lambda: c2.bar(c.address)) -def test_external_contract_call_state_change(get_contract, memory_mocker): +def test_external_contract_call_state_change(get_contract): contract_1 = """ lucky: public(int128) @@ -272,7 +904,7 @@ def set_lucky_stmt(arg1: address, arg2: int128) -> int128: print("Successfully blocked an external contract call from a constant function") -def test_external_contract_can_be_changed_based_on_address(get_contract, memory_mocker): +def test_external_contract_can_be_changed_based_on_address(get_contract): contract_1 = """ lucky: public(int128) @@ -342,7 +974,7 @@ def bar(arg1: address) -> int128: print("Successfully executed an external contract call with public globals") -def test_external_contract_calls_with_multiple_contracts(get_contract, memory_mocker): +def test_external_contract_calls_with_multiple_contracts(get_contract): contract_1 = """ lucky: public(int128) @@ -382,7 +1014,55 @@ def __init__(arg1: address): print("Successfully executed a multiple external contract calls") -def test_invalid_external_contract_call_to_the_same_contract(assert_tx_failed, get_contract): +def test_external_contract_calls_with_default_value(get_contract): + contract_1 = """ +@external +def foo(arg1: uint256=1) -> uint256: + return arg1 + """ + + contract_2 = """ +interface Foo: + def foo(arg1: uint256=1) -> uint256: nonpayable + +@external +def bar(addr: address) -> uint256: + return Foo(addr).foo() + """ + + c1 = get_contract(contract_1) + c2 = get_contract(contract_2) + + assert c1.foo() == 1 + assert c1.foo(2) == 2 + assert c2.bar(c1.address) == 1 + + +def test_external_contract_calls_with_default_value_two(get_contract): + contract_1 = """ +@external +def foo(arg1: uint256, arg2: uint256=1) -> uint256: + return arg1 + arg2 + """ + + contract_2 = """ +interface Foo: + def foo(arg1: uint256, arg2: uint256=1) -> uint256: nonpayable + +@external +def bar(addr: address, arg1: uint256) -> uint256: + return Foo(addr).foo(arg1) + """ + + c1 = get_contract(contract_1) + c2 = get_contract(contract_2) + + assert c1.foo(2) == 3 + assert c1.foo(2, 3) == 5 + assert c2.bar(c1.address, 2) == 3 + + +def test_invalid_external_contract_call_to_the_same_contract(get_contract): contract_1 = """ @external def bar() -> int128: @@ -543,7 +1223,7 @@ def get_lucky(contract_address: address) -> int128: def test_complex_external_contract_call_declaration( - get_contract_with_gas_estimation, memory_mocker + get_contract_with_gas_estimation ): contract_1 = """ @external @@ -732,7 +1412,7 @@ def get_lucky(gas_amount: uint256) -> int128: assert_tx_failed(lambda: c2.get_lucky(100)) # too little gas. -def test_skip_contract_check(assert_tx_failed, get_contract_with_gas_estimation): +def test_skip_contract_check(get_contract_with_gas_estimation): contract_2 = """ @external @view @@ -842,7 +1522,7 @@ def foo(): assert_compile_failed(lambda: get_contract_with_gas_estimation(code), InvalidType) -def test_tuple_return_external_contract_call(get_contract, memory_mocker): +def test_tuple_return_external_contract_call(get_contract): contract_1 = """ @external def out_literals() -> (int128, address, Bytes[10]): @@ -932,6 +1612,154 @@ def test(addr: address) -> (int128, String[{ln}], Bytes[{ln}]): assert c2.test(c1.address) == list(c1.get_struct_x()) +def test_struct_return_external_contract_call_3(get_contract_with_gas_estimation): + contract_1 = """ +struct X: + x: int128 +@external +def out_literals() -> X: + return X({x: 1}) + """ + + contract_2 = """ +struct X: + x: int128 +interface Test: + def out_literals() -> X : view + +@external +def test(addr: address) -> int128: + ret: X = Test(addr).out_literals() + return ret.x + + """ + c1 = get_contract_with_gas_estimation(contract_1) + c2 = get_contract_with_gas_estimation(contract_2) + + assert c1.out_literals() == (1,) + assert [c2.test(c1.address)] == list(c1.out_literals()) + + +def test_dynamically_sized_struct_external_contract_call(get_contract_with_gas_estimation): + contract_1 = """ +struct X: + x: uint256 + y: Bytes[6] + +@external +def foo(x: X) -> Bytes[6]: + return x.y + """ + + contract_2 = """ +struct X: + x: uint256 + y: Bytes[6] + +interface Foo: + def foo(x: X) -> Bytes[6]: nonpayable + +@external +def bar(addr: address) -> Bytes[6]: + _X: X = X({x: 1, y: b"hello"}) + return Foo(addr).foo(_X) + """ + + c1 = get_contract_with_gas_estimation(contract_1) + c2 = get_contract_with_gas_estimation(contract_2) + + assert c1.foo((1, b"hello")) == b"hello" + assert c2.bar(c1.address) == b"hello" + + +def test_dynamically_sized_struct_external_contract_call_2(get_contract_with_gas_estimation): + contract_1 = """ +struct X: + x: uint256 + y: String[6] + +@external +def foo(x: X) -> String[6]: + return x.y + """ + + contract_2 = """ +struct X: + x: uint256 + y: String[6] + +interface Foo: + def foo(x: X) -> String[6]: nonpayable + +@external +def bar(addr: address) -> String[6]: + _X: X = X({x: 1, y: "hello"}) + return Foo(addr).foo(_X) + """ + + c1 = get_contract_with_gas_estimation(contract_1) + c2 = get_contract_with_gas_estimation(contract_2) + + assert c1.foo((1, "hello")) == "hello" + assert c2.bar(c1.address) == "hello" + + +def test_dynamically_sized_struct_member_external_contract_call(get_contract_with_gas_estimation): + contract_1 = """ +@external +def foo(b: Bytes[6]) -> Bytes[6]: + return b + """ + + contract_2 = """ +struct X: + x: uint256 + y: Bytes[6] + +interface Foo: + def foo(b: Bytes[6]) -> Bytes[6]: nonpayable + +@external +def bar(addr: address) -> Bytes[6]: + _X: X = X({x: 1, y: b"hello"}) + return Foo(addr).foo(_X.y) + """ + + c1 = get_contract_with_gas_estimation(contract_1) + c2 = get_contract_with_gas_estimation(contract_2) + + assert c1.foo(b"hello") == b"hello" + assert c2.bar(c1.address) == b"hello" + + +def test_dynamically_sized_struct_member_external_contract_call_2(get_contract_with_gas_estimation): + contract_1 = """ +@external +def foo(s: String[6]) -> String[6]: + return s + """ + + contract_2 = """ +struct X: + x: uint256 + y: String[6] + +interface Foo: + def foo(b: String[6]) -> String[6]: nonpayable + +@external +def bar(addr: address) -> String[6]: + _X: X = X({x: 1, y: "hello"}) + return Foo(addr).foo(_X.y) + """ + + c1 = get_contract_with_gas_estimation(contract_1) + c2 = get_contract_with_gas_estimation(contract_2) + + assert c1.foo("hello") == "hello" + assert c2.bar(c1.address) == "hello" + + def test_list_external_contract_call(get_contract, get_contract_with_gas_estimation): contract_1 = """ @external @@ -991,7 +1819,7 @@ def foo(_addr: address) -> int128: assert_tx_failed(lambda: c2.foo(c1.address)) -def test_returndatasize_too_long(get_contract, assert_tx_failed): +def test_returndatasize_too_long(get_contract): contract_1 = """ @external def bar(a: int128) -> (int128, int128): diff --git a/tests/parser/features/external_contracts/test_self_call_struct.py b/tests/parser/features/external_contracts/test_self_call_struct.py index ad3cd79079..27fceeb891 100644 --- a/tests/parser/features/external_contracts/test_self_call_struct.py +++ b/tests/parser/features/external_contracts/test_self_call_struct.py @@ -32,3 +32,33 @@ def wrap_get_my_struct_BROKEN(_e1: decimal) -> MyStruct: Decimal("0.1"), w3.eth.getBlock(w3.eth.blockNumber)["timestamp"], ) + + +def test_call_to_self_struct_2(get_contract): + code = """ +struct MyStruct: + e1: decimal + +@internal +@view +def get_my_struct(_e1: decimal) -> MyStruct: + return MyStruct({e1: _e1}) + +@external +@view +def wrap_get_my_struct_WORKING(_e1: decimal) -> MyStruct: + testing: MyStruct = self.get_my_struct(_e1) + return testing + +@external +@view +def wrap_get_my_struct_BROKEN(_e1: decimal) -> MyStruct: + return self.get_my_struct(_e1) + """ + c = get_contract(code) + assert c.wrap_get_my_struct_WORKING(Decimal("0.1")) == ( + Decimal("0.1"), + ) + assert c.wrap_get_my_struct_BROKEN(Decimal("0.1")) == ( + Decimal("0.1"), + ) diff --git a/tests/parser/features/test_clampers.py b/tests/parser/features/test_clampers.py index 4eeb46c27b..3d2219dc3b 100644 --- a/tests/parser/features/test_clampers.py +++ b/tests/parser/features/test_clampers.py @@ -3,6 +3,8 @@ from vyper.evm.opcodes import EVM_VERSIONS +from decimal import Decimal + def _make_tx(w3, address, signature, values): # helper function to broadcast transactions that fail clamping check @@ -39,6 +41,25 @@ def foo(s: Bytes[40]) -> Bytes[40]: assert_tx_failed(lambda: c.foo(data + b"!")) +def test_bytes_clamper_on_init(assert_tx_failed, get_contract_with_gas_estimation): + clamper_test_code = """ +foo: Bytes[3] + +@external +def __init__(x: Bytes[3]): + self.foo = x + +@external +def get_foo() -> Bytes[3]: + return self.foo + """ + + c = get_contract_with_gas_estimation(clamper_test_code, *[b"cat"]) + assert c.get_foo() == b"cat" + + assert_tx_failed(lambda: get_contract_with_gas_estimation(clamper_test_code, *[b"cats"])) + + @pytest.mark.parametrize("evm_version", list(EVM_VERSIONS)) @pytest.mark.parametrize("value", [0, 1, -1, 2 ** 127 - 1, -(2 ** 127)]) def test_int128_clamper_passing(w3, get_contract, value, evm_version): @@ -142,6 +163,43 @@ def foo(s: address) -> address: assert_tx_failed(lambda: _make_tx(w3, c.address, "foo(address)", [value])) +@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS)) +@pytest.mark.parametrize("value", [ + 0, 1, -1, 2 ** 127 - 1, -(2 ** 127), + "0.0", "1.0", "-1.0", "0.0000000001", "0.9999999999", "-0.0000000001", "-0.9999999999", + "170141183460469231731687303715884105726.9999999999", # 2 ** 127 - 1.0000000001 + "-170141183460469231731687303715884105727.9999999999" # - (2 ** 127 - 0.0000000001) +]) +def test_decimal_clamper_passing(get_contract, value, evm_version): + code = """ +@external +def foo(s: decimal) -> decimal: + return s + """ + + c = get_contract(code, evm_version=evm_version) + + assert c.foo(Decimal(value)) == Decimal(value) + + +@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS)) +@pytest.mark.parametrize("value", [ + 2 ** 127, -(2 ** 127 + 1), + "170141183460469231731687303715884105727.0000000001", # 2 ** 127 - 0.999999999 + "-170141183460469231731687303715884105728.0000000001", # - (2 ** 127 + 0.0000000001) +]) +def test_decimal_clamper_failing(assert_tx_failed, get_contract, value, evm_version): + code = """ +@external +def foo(s: decimal) -> decimal: + return s + """ + + c = get_contract(code, evm_version=evm_version) + + assert_tx_failed(lambda: c.foo(Decimal(value))) + + @pytest.mark.parametrize("value", [0, 1, -1, 2 ** 127 - 1, -(2 ** 127)]) def test_int128_array_clamper_passing(w3, get_contract, value): code = """ diff --git a/tests/parser/features/test_internal_call.py b/tests/parser/features/test_internal_call.py index ed05fb40dc..433ca51830 100644 --- a/tests/parser/features/test_internal_call.py +++ b/tests/parser/features/test_internal_call.py @@ -531,3 +531,87 @@ def test() -> (int128, String[{ln}], Bytes[{ln}]): c = get_contract_with_gas_estimation(contract) assert c.test() == [i, s, bytes(s, "utf-8")] + + +def test_dynamically_sized_struct_as_arg(get_contract_with_gas_estimation): + contract = """ +struct X: + x: uint256 + y: Bytes[6] + +@internal +def _foo(x: X) -> Bytes[6]: + return x.y + +@external +def bar() -> Bytes[6]: + _X: X = X({x: 1, y: b"hello"}) + return self._foo(_X) + """ + + c = get_contract_with_gas_estimation(contract) + + assert c.bar() == b"hello" + + +def test_dynamically_sized_struct_as_arg_2(get_contract_with_gas_estimation): + contract = """ +struct X: + x: uint256 + y: String[6] + +@internal +def _foo(x: X) -> String[6]: + return x.y + +@external +def bar() -> String[6]: + _X: X = X({x: 1, y: "hello"}) + return self._foo(_X) + """ + + c = get_contract_with_gas_estimation(contract) + + assert c.bar() == "hello" + + +def test_dynamically_sized_struct_member_as_arg(get_contract_with_gas_estimation): + contract = """ +struct X: + x: uint256 + y: Bytes[6] + +@internal +def _foo(s: Bytes[6]) -> Bytes[6]: + return s + +@external +def bar() -> Bytes[6]: + _X: X = X({x: 1, y: b"hello"}) + return self._foo(_X.y) + """ + + c = get_contract_with_gas_estimation(contract) + + assert c.bar() == b"hello" + + +def test_dynamically_sized_struct_member_as_arg_2(get_contract_with_gas_estimation): + contract = """ +struct X: + x: uint256 + y: String[6] + +@internal +def _foo(s: String[6]) -> String[6]: + return s + +@external +def bar() -> String[6]: + _X: X = X({x: 1, y: "hello"}) + return self._foo(_X.y) + """ + + c = get_contract_with_gas_estimation(contract) + + assert c.bar() == "hello" diff --git a/tests/parser/functions/test_return_struct.py b/tests/parser/functions/test_return_struct.py index 761cbf6d14..425caedb75 100644 --- a/tests/parser/functions/test_return_struct.py +++ b/tests/parser/functions/test_return_struct.py @@ -27,6 +27,28 @@ def test() -> Voter: assert c.test() == (123, True) +def test_single_struct_return_abi(get_contract_with_gas_estimation): + code = """ +struct Voter: + voted: bool + +@external +def test() -> Voter: + a: Voter = Voter({voted: True}) + return a + """ + + out = compile_code(code, ["abi"]) + abi = out["abi"][0] + + assert abi["name"] == "test" + assert abi["outputs"][0]["type"] == "tuple" + + c = get_contract_with_gas_estimation(code) + + assert c.test() == (True,) + + def test_struct_return(get_contract_with_gas_estimation): code = """ struct Foo: @@ -86,6 +108,63 @@ def pub6() -> Foo: assert c.pub6() == foo +def test_single_struct_return(get_contract_with_gas_estimation): + code = """ +struct Foo: + x: int128 + +_foo: Foo +_foos: HashMap[int128, Foo] + +@internal +def priv1() -> Foo: + return Foo({x: 1}) +@external +def pub1() -> Foo: + return self.priv1() + +@internal +def priv2() -> Foo: + foo: Foo = Foo({x: 0}) + foo.x = 3 + return foo +@external +def pub2() -> Foo: + return self.priv2() + +@external +def pub3() -> Foo: + self._foo = Foo({x: 5}) + return self._foo + +@external +def pub4() -> Foo: + self._foos[0] = Foo({x: 7}) + return self._foos[0] + +@internal +def return_arg(foo: Foo) -> Foo: + return foo +@external +def pub5(foo: Foo) -> Foo: + return self.return_arg(foo) +@external +def pub6() -> Foo: + foo: Foo = Foo({x: 123}) + return self.return_arg(foo) + """ + foo = (123,) + + c = get_contract_with_gas_estimation(code) + + assert c.pub1() == (1,) + assert c.pub2() == (3,) + assert c.pub3() == (5,) + assert c.pub4() == (7,) + assert c.pub5(foo) == foo + assert c.pub6() == foo + + def test_self_call_in_return_struct(get_contract): code = """ struct Foo: @@ -110,6 +189,26 @@ def foo() -> Foo: assert c.foo() == (1, 2, 3, 4, 5) +def test_self_call_in_return_single_struct(get_contract): + code = """ +struct Foo: + a: uint256 + +@internal +def _foo() -> uint256: + a: uint256[10] = [6,7,8,9,10,11,12,13,14,15] + return 3 + +@external +def foo() -> Foo: + return Foo({a:self._foo()}) + """ + + c = get_contract(code) + + assert c.foo() == (3,) + + def test_call_in_call(get_contract): code = """ struct Foo: @@ -138,6 +237,30 @@ def foo() -> Foo: assert c.foo() == (1, 2, 3, 4, 5) +def test_call_in_call_single_struct(get_contract): + code = """ +struct Foo: + a: uint256 + +@internal +def _foo(a: uint256) -> Foo: + return Foo({a:a}) + +@internal +def _foo2() -> uint256: + a: uint256[10] = [6,7,8,9,10,11,12,13,15,16] + return 4 + +@external +def foo() -> Foo: + return self._foo(self._foo2()) + """ + + c = get_contract(code) + + assert c.foo() == (4,) + + def test_nested_calls_in_struct_return(get_contract): code = """ struct Foo: @@ -185,6 +308,45 @@ def foo() -> Foo: assert c.foo() == (1, 2, 3, 4, 5) +def test_nested_calls_in_single_struct_return(get_contract): + code = """ +struct Foo: + a: uint256 +struct Bar: + a: uint256 + b: uint256 + +@internal +def _bar(a: uint256, b: uint256, c: uint256) -> Bar: + return Bar({a:415, b:3}) + +@internal +def _foo2(a: uint256) -> uint256: + b: uint256[10] = [6,7,8,9,10,11,12,13,14,15] + return 99 + +@internal +def _foo3(a: uint256, b: uint256) -> uint256: + c: uint256[10] = [14,15,16,17,18,19,20,21,22,23] + return 42 + +@internal +def _foo4() -> uint256: + c: uint256[10] = [14,15,16,17,18,19,20,21,22,23] + return 4 + +@external +def foo() -> Foo: + return Foo({ + a:self._bar(6, self._foo4(), self._foo2(self._foo3(9, 11))).b, + }) + """ + + c = get_contract(code) + + assert c.foo() == (3,) + + def test_external_call_in_return_struct(get_contract): code = """ struct Bar: @@ -226,6 +388,37 @@ def foo(addr: address) -> Foo: assert c2.foo(c.address) == (1, 2, 3, 4, 5) +def test_external_call_in_return_single_struct(get_contract): + code = """ +struct Bar: + a: uint256 +@view +@external +def bar() -> Bar: + return Bar({a:3}) + """ + + code2 = """ +struct Foo: + a: uint256 +struct Bar: + a: uint256 +interface IBar: + def bar() -> Bar: view + +@external +def foo(addr: address) -> Foo: + return Foo({ + a:IBar(addr).bar().a + }) + """ + + c = get_contract(code) + c2 = get_contract(code2) + + assert c2.foo(c.address) == (3,) + + def test_nested_external_call_in_return_struct(get_contract): code = """ struct Bar: @@ -273,3 +466,42 @@ def foo(addr: address) -> Foo: c2 = get_contract(code2) assert c2.foo(c.address) == (1, 2, 3, 4, 5) + + +def test_nested_external_call_in_return_single_struct(get_contract): + code = """ +struct Bar: + a: uint256 + +@view +@external +def bar() -> Bar: + return Bar({a:3}) + +@view +@external +def baz(x: uint256) -> uint256: + return x+1 + """ + + code2 = """ +struct Foo: + a: uint256 +struct Bar: + a: uint256 + +interface IBar: + def bar() -> Bar: view + def baz(a: uint256) -> uint256: view + +@external +def foo(addr: address) -> Foo: + return Foo({ + a:IBar(addr).baz(IBar(addr).bar().a) + }) + """ + + c = get_contract(code) + c2 = get_contract(code2) + + assert c2.foo(c.address) == (4,)