From 7824f822b9e5f3c652ffd893d8a696ff14304aa3 Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Sun, 17 May 2020 21:14:29 +0400 Subject: [PATCH 1/4] fix: consider returndatasize for raw_call output --- vyper/functions/functions.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/vyper/functions/functions.py b/vyper/functions/functions.py index 9589982c81..a1a5da9c99 100644 --- a/vyper/functions/functions.py +++ b/vyper/functions/functions.py @@ -843,9 +843,15 @@ def build_LLL(self, expr, args, kwargs, context): # build sequence LLL if outsize: - # only copy the return value to memory if outsize > 0 + # return minimum of outsize and returndatasize + size = [ + 'with', '_l', outsize, [ + 'with', '_r', 'returndatasize', ['if', ['gt', '_l', '_r'], '_r', '_l'] + ] + ] + seq = [ - 'seq', copier, ['assert', call_lll], ['mstore', output_node, outsize], output_node + 'seq', copier, ['assert', call_lll], ['mstore', output_node, size], output_node ] typ = ByteArrayType(outsize) else: From 5807c5ffcdf680074176917f5f114dab11ce4c0f Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Sun, 17 May 2020 21:14:52 +0400 Subject: [PATCH 2/4] test: update raw_call outsize tests --- tests/examples/wallet/test_wallet.py | 6 +++-- tests/parser/functions/test_raw_call.py | 33 +++++++++++++++---------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/tests/examples/wallet/test_wallet.py b/tests/examples/wallet/test_wallet.py index cc25727fd6..7d3e72f249 100644 --- a/tests/examples/wallet/test_wallet.py +++ b/tests/examples/wallet/test_wallet.py @@ -58,7 +58,9 @@ def pack_and_sign(seq, *args): sigs = pack_and_sign(1, k1, 0, k3, 0, k5) assert_tx_failed(lambda: c.approve(1, to_address, value, data, sigs, transact={'value': 0, 'from': a1})) # noqa: E501 sigs = pack_and_sign(1, k1, 0, k3, 0, k5) - assert c.approve(1, to_address, value, data, sigs, call={'value': value, 'from': a1}) + + # this call should succeed + c.approve(1, to_address, value, data, sigs, call={'value': value, 'from': a1}) print("Basic tests passed") @@ -104,7 +106,7 @@ def test_javascript_signatures(w3, get_contract): # There's no need to pass in signatures because the owners are 0 addresses # causing them to default to valid signatures - assert x2.approve( + x2.approve( 0, recipient, 25, diff --git a/tests/parser/functions/test_raw_call.py b/tests/parser/functions/test_raw_call.py index 52915b2400..ff9331af51 100644 --- a/tests/parser/functions/test_raw_call.py +++ b/tests/parser/functions/test_raw_call.py @@ -5,27 +5,34 @@ from vyper.functions import get_create_forwarder_to_bytecode -def test_caller_code(get_contract_with_gas_estimation): - caller_code = """ +def test_outsize_exceeds_returndatasize(get_contract): + source_code = """ @public def foo() -> bytes[7]: - return raw_call(0x0000000000000000000000000000000000000004, b"moose", gas=50000, outsize=5) + return raw_call(0x0000000000000000000000000000000000000004, b"moose", outsize=7) + """ + c = get_contract(source_code) + assert c.foo() == b"moose" -@public -def bar() -> bytes[7]: - return raw_call(0x0000000000000000000000000000000000000004, b"moose", gas=50000, outsize=3) +def test_returndatasize_exceeds_outsize(get_contract): + source_code = """ @public -def baz() -> bytes[7]: - return raw_call(0x0000000000000000000000000000000000000004, b"moose", gas=50000, outsize=7) +def foo() -> bytes[3]: + return raw_call(0x0000000000000000000000000000000000000004, b"moose", outsize=3) """ + c = get_contract(source_code) + assert c.foo() == b"moo" - c = get_contract_with_gas_estimation(caller_code) - assert c.foo() == b"moose" - assert c.bar() == b"moo" - assert c.baz() == b"moose\x00\x00" - print('Passed raw call test') +def test_returndatasize_matches_outsize(get_contract): + source_code = """ +@public +def foo() -> bytes[5]: + return raw_call(0x0000000000000000000000000000000000000004, b"moose", outsize=5) + """ + c = get_contract(source_code) + assert c.foo() == b"moose" def test_multiple_levels(w3, get_contract_with_gas_estimation): From 6037bcae176ae61347e031fd603aab2c5b8680c0 Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Sun, 17 May 2020 21:25:25 +0400 Subject: [PATCH 3/4] doc: update raw_call documentation --- docs/built-in-functions.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/built-in-functions.rst b/docs/built-in-functions.rst index 88d0b2f704..1a819d4d70 100644 --- a/docs/built-in-functions.rst +++ b/docs/built-in-functions.rst @@ -160,6 +160,10 @@ Vyper contains a set of built in functions which execute opcodes such as ``SEND` Returns ``None`` if ``outsize`` is omitted or set to ``0``. + .. note:: + + The actual size of the returned data may be less than ``outsize``. You can use ``len`` to obtain the actual size. + .. py:function:: selfdestruct(to: address) -> None Triggers the ``SELFDESTRUCT`` opcode (``0xFF``), causing the contract to be destroyed. From d06ef9b81dcbaf04bcaf9a860ebf42bc654003c1 Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Mon, 18 May 2020 00:17:05 +0400 Subject: [PATCH 4/4] style: rename `outsize` to `max_outsize` --- docs/built-in-functions.rst | 10 ++++---- examples/wallet/wallet.vy | 2 +- .../exceptions/test_constancy_exception.py | 4 +++- .../exceptions/test_structure_exception.py | 6 ++--- .../exceptions/test_syntax_exception.py | 2 +- tests/parser/features/test_assert.py | 2 +- tests/parser/features/test_conditionals.py | 2 +- tests/parser/functions/test_method_id.py | 2 +- tests/parser/functions/test_raw_call.py | 24 +++++++++---------- tests/parser/functions/test_send.py | 2 +- tests/parser/syntax/test_constants.py | 4 ++-- tests/parser/syntax/test_raw_call.py | 12 ++++++---- vyper/functions/functions.py | 4 ++-- 13 files changed, 40 insertions(+), 36 deletions(-) diff --git a/docs/built-in-functions.rst b/docs/built-in-functions.rst index 1a819d4d70..3433e8ac41 100644 --- a/docs/built-in-functions.rst +++ b/docs/built-in-functions.rst @@ -144,25 +144,25 @@ Vyper contains a set of built in functions which execute opcodes such as ``SEND` The amount to send is always specified in ``wei``. -.. py:function:: raw_call(to: address, data: bytes, outsize: int = 0, gas: uint256 = gasLeft, value: uint256 = 0, is_delegate_call: bool = False, is_static_call: bool = False) -> bytes[outsize] +.. py:function:: raw_call(to: address, data: bytes, max_outsize: int = 0, gas: uint256 = gasLeft, value: uint256 = 0, is_delegate_call: bool = False, is_static_call: bool = False) -> bytes[max_outsize] Calls to the specified Ethereum address. * ``to``: Destination address to call to * ``data``: Data to send to the destination address - * ``outsize``: Maximum length of the bytes array returned from the call. If the returned call data exceeds this length, only this number of bytes is returned. + * ``max_outsize``: Maximum length of the bytes array returned from the call. If the returned call data exceeds this length, only this number of bytes is returned. * ``gas``: The amount of gas to attach to the call. If not set, all remainaing gas is forwarded. * ``value``: The wei value to send to the address (Optional, default ``0``) * ``is_delegate_call``: If ``True``, the call will be sent as ``DELEGATECALL`` (Optional, default ``False``) * ``is_static_call``: If ``True``, the call will be sent as ``STATICCALL`` (Optional, default ``False``) - Returns the data returned by the call as a ``bytes`` list, with ``outsize`` as the max length. + Returns the data returned by the call as a ``bytes`` list, with ``max_outsize`` as the max length. - Returns ``None`` if ``outsize`` is omitted or set to ``0``. + Returns ``None`` if ``max_outsize`` is omitted or set to ``0``. .. note:: - The actual size of the returned data may be less than ``outsize``. You can use ``len`` to obtain the actual size. + The actual size of the returned data may be less than ``max_outsize``. You can use ``len`` to obtain the actual size. .. py:function:: selfdestruct(to: address) -> None diff --git a/examples/wallet/wallet.vy b/examples/wallet/wallet.vy index 23463e818c..f119290675 100644 --- a/examples/wallet/wallet.vy +++ b/examples/wallet/wallet.vy @@ -56,7 +56,7 @@ def approve(_seq: int128, to: address, _value: uint256, data: bytes[4096], sigda # Increase the number of approved transactions by 1 self.seq += 1 # Use raw_call to send the transaction - return raw_call(to, data, outsize=4096, gas=3000000, value=_value) + return raw_call(to, data, max_outsize=4096, gas=3000000, value=_value) @public diff --git a/tests/parser/exceptions/test_constancy_exception.py b/tests/parser/exceptions/test_constancy_exception.py index 6cb8794e8c..591d6bf48c 100644 --- a/tests/parser/exceptions/test_constancy_exception.py +++ b/tests/parser/exceptions/test_constancy_exception.py @@ -39,7 +39,9 @@ def foo() -> int128: @public @constant def foo() -> int128: - x = raw_call(0x1234567890123456789012345678901234567890, b"cow", outsize=4, gas=595757, value=9) + x = raw_call( + 0x1234567890123456789012345678901234567890, b"cow", max_outsize=4, gas=595757, value=9 + ) return 5 """, """ diff --git a/tests/parser/exceptions/test_structure_exception.py b/tests/parser/exceptions/test_structure_exception.py index 8d6f5d9eb5..88d93da689 100644 --- a/tests/parser/exceptions/test_structure_exception.py +++ b/tests/parser/exceptions/test_structure_exception.py @@ -64,7 +64,7 @@ def foo(): """ @public def foo(): - x: bytes[4] = raw_call(0x1234567890123456789012345678901234567890, outsize=4) + x: bytes[4] = raw_call(0x1234567890123456789012345678901234567890, max_outsize=4) """, """ @public @@ -75,13 +75,13 @@ def foo(): @public def foo(): x: bytes[4] = raw_call( - 0x1234567890123456789012345678901234567890, b"cow", gas=111111, outsize=4, moose=9 + 0x1234567890123456789012345678901234567890, b"cow", gas=111111, max_outsize=4, moose=9 ) """, """ @public def foo(): - x: bytes[4] = create_forwarder_to(0x1234567890123456789012345678901234567890, outsize=4) + x: bytes[4] = create_forwarder_to(0x1234567890123456789012345678901234567890, max_outsize=4) """, """ x: public() diff --git a/tests/parser/exceptions/test_syntax_exception.py b/tests/parser/exceptions/test_syntax_exception.py index 0840f5d08b..0305a008ca 100644 --- a/tests/parser/exceptions/test_syntax_exception.py +++ b/tests/parser/exceptions/test_syntax_exception.py @@ -59,7 +59,7 @@ def foo(): """ @public def foo(): - x: bytes[4] = raw_call(0x123456789012345678901234567890123456789, "cow", outsize=4) + x: bytes[4] = raw_call(0x123456789012345678901234567890123456789, "cow", max_outsize=4) """, """ @public diff --git a/tests/parser/features/test_assert.py b/tests/parser/features/test_assert.py index ca1ce7200a..b37a97cdc2 100644 --- a/tests/parser/features/test_assert.py +++ b/tests/parser/features/test_assert.py @@ -118,7 +118,7 @@ def test(): code = """ @public def test(): - assert raw_call(msg.sender, b'', outsize=1, gas=10, value=1000*1000) == 1 + assert raw_call(msg.sender, b'', max_outsize=1, gas=10, value=1000*1000) == 1 """ assert_compile_failed(lambda: get_contract(code), ConstancyViolation) diff --git a/tests/parser/features/test_conditionals.py b/tests/parser/features/test_conditionals.py index f10d678950..557e76c917 100644 --- a/tests/parser/features/test_conditionals.py +++ b/tests/parser/features/test_conditionals.py @@ -22,7 +22,7 @@ def test_single_branch_underflow_public(get_contract_with_gas_estimation): @public def doit(): if False: - raw_call(msg.sender, b"", outsize=0, value=0, gas=msg.gas) + raw_call(msg.sender, b"", max_outsize=0, value=0, gas=msg.gas) """ c = get_contract_with_gas_estimation(code) c.doit() diff --git a/tests/parser/functions/test_method_id.py b/tests/parser/functions/test_method_id.py index 108ec876b7..e31c0164ab 100644 --- a/tests/parser/functions/test_method_id.py +++ b/tests/parser/functions/test_method_id.py @@ -8,7 +8,7 @@ def double(x: int128) -> int128: @public def returnten() -> int128: - ans: bytes[32] = raw_call(self, concat(method_id("double(int128)", bytes[4]), convert(5, bytes32)), gas=50000, outsize=32) # noqa: E501 + ans: bytes[32] = raw_call(self, concat(method_id("double(int128)", bytes[4]), convert(5, bytes32)), gas=50000, max_outsize=32) # noqa: E501 return convert(convert(ans, bytes32), int128) """ c = get_contract_with_gas_estimation(method_id_test) diff --git a/tests/parser/functions/test_raw_call.py b/tests/parser/functions/test_raw_call.py index ff9331af51..0042e8e72e 100644 --- a/tests/parser/functions/test_raw_call.py +++ b/tests/parser/functions/test_raw_call.py @@ -5,31 +5,31 @@ from vyper.functions import get_create_forwarder_to_bytecode -def test_outsize_exceeds_returndatasize(get_contract): +def test_max_outsize_exceeds_returndatasize(get_contract): source_code = """ @public def foo() -> bytes[7]: - return raw_call(0x0000000000000000000000000000000000000004, b"moose", outsize=7) + return raw_call(0x0000000000000000000000000000000000000004, b"moose", max_outsize=7) """ c = get_contract(source_code) assert c.foo() == b"moose" -def test_returndatasize_exceeds_outsize(get_contract): +def test_returndatasize_exceeds_max_outsize(get_contract): source_code = """ @public def foo() -> bytes[3]: - return raw_call(0x0000000000000000000000000000000000000004, b"moose", outsize=3) + return raw_call(0x0000000000000000000000000000000000000004, b"moose", max_outsize=3) """ c = get_contract(source_code) assert c.foo() == b"moo" -def test_returndatasize_matches_outsize(get_contract): +def test_returndatasize_matches_max_outsize(get_contract): source_code = """ @public def foo() -> bytes[5]: - return raw_call(0x0000000000000000000000000000000000000004, b"moose", outsize=5) + return raw_call(0x0000000000000000000000000000000000000004, b"moose", max_outsize=5) """ c = get_contract(source_code) assert c.foo() == b"moose" @@ -48,7 +48,7 @@ def returnten() -> int128: @public def create_and_call_returnten(inp: address) -> int128: x: address = create_forwarder_to(inp) - o: int128 = extract32(raw_call(x, convert("\xd0\x1f\xb1\xb8", bytes[4]), outsize=32, gas=50000), 0, type=int128) # noqa: E501 + o: int128 = extract32(raw_call(x, convert("\xd0\x1f\xb1\xb8", bytes[4]), max_outsize=32, gas=50000), 0, type=int128) # noqa: E501 return o @public @@ -90,7 +90,7 @@ def returnten() -> int128: @public def create_and_call_returnten(inp: address) -> int128: x: address = create_forwarder_to(inp) - o: int128 = extract32(raw_call(x, convert("\xd0\x1f\xb1\xb8", bytes[4]), outsize=32, gas=50000), 0, type=int128) # noqa: E501 + o: int128 = extract32(raw_call(x, convert("\xd0\x1f\xb1\xb8", bytes[4]), max_outsize=32, gas=50000), 0, type=int128) # noqa: E501 return o @public @@ -135,7 +135,7 @@ def set(i: int128, owner: address): self.owner_setter_contract, cdata, gas=msg.gas, - outsize=0, + max_outsize=0, is_delegate_call=True ) """ @@ -175,7 +175,7 @@ def foo_call(_addr: address): method_id("foo(bytes32)", bytes[4]), 0x0000000000000000000000000000000000000000000000000000000000000001 ) - raw_call(_addr, cdata, outsize=0{}) + raw_call(_addr, cdata, max_outsize=0{}) """ # with no gas value given, enough will be forwarded to complete the call @@ -207,7 +207,7 @@ def foo(_addr: address) -> int128: _response: bytes[32] = raw_call( _addr, method_id("foo()", bytes[4]), - outsize=32, + max_outsize=32, is_static_call=True, ) return convert(_response, int128) @@ -237,7 +237,7 @@ def foo(_addr: address) -> int128: _response: bytes[32] = raw_call( _addr, method_id("foo()", bytes[4]), - outsize=32, + max_outsize=32, is_static_call=True, ) return convert(_response, int128) diff --git a/tests/parser/functions/test_send.py b/tests/parser/functions/test_send.py index f87907079b..38c54e15fa 100644 --- a/tests/parser/functions/test_send.py +++ b/tests/parser/functions/test_send.py @@ -41,7 +41,7 @@ def test_send(receiver: address): @public def test_call(receiver: address): - raw_call(receiver, b"", gas=50000, outsize=0, value=1) + raw_call(receiver, b"", gas=50000, max_outsize=0, value=1) """ # default function writes variable, this requires more gas than send can pass diff --git a/tests/parser/syntax/test_constants.py b/tests/parser/syntax/test_constants.py index c0ca211157..cbbc176a61 100644 --- a/tests/parser/syntax/test_constants.py +++ b/tests/parser/syntax/test_constants.py @@ -109,11 +109,11 @@ def test() -> int128: @private def test(): - raw_call(0x0000000000000000000000000000000000000005, b'hello', outsize=TEST_C, gas=2000) + raw_call(0x0000000000000000000000000000000000000005, b'hello', max_outsize=TEST_C, gas=2000) @private def test1(): - raw_call(0x0000000000000000000000000000000000000005, b'hello', outsize=256, gas=TEST_WEI) + raw_call(0x0000000000000000000000000000000000000005, b'hello', max_outsize=256, gas=TEST_WEI) """, """ LIMIT: constant(int128) = 1 diff --git a/tests/parser/syntax/test_raw_call.py b/tests/parser/syntax/test_raw_call.py index fe3a29943e..e21cd232a6 100644 --- a/tests/parser/syntax/test_raw_call.py +++ b/tests/parser/syntax/test_raw_call.py @@ -8,7 +8,9 @@ (""" @public def foo(): - x: bytes[9] = raw_call(0x1234567890123456789012345678901234567890, b"cow", outsize=4, outsize=9) + x: bytes[9] = raw_call( + 0x1234567890123456789012345678901234567890, b"cow", max_outsize=4, max_outsize=9 + ) """, SyntaxException), """ @public @@ -23,7 +25,7 @@ def foo(): """ @public def foo(): - # fails because raw_call without outsize does not return a value + # fails because raw_call without max_outsize does not return a value x: bytes[9] = raw_call(0x1234567890123456789012345678901234567890, b"cow") """, ] @@ -47,7 +49,7 @@ def foo(): x: bytes[9] = raw_call( 0x1234567890123456789012345678901234567890, b"cow", - outsize=4, + max_outsize=4, gas=595757 ) """, @@ -57,7 +59,7 @@ def foo(): x: bytes[9] = raw_call( 0x1234567890123456789012345678901234567890, b"cow", - outsize=4, + max_outsize=4, gas=595757, value=as_wei_value(9, "wei") ) @@ -68,7 +70,7 @@ def foo(): x: bytes[9] = raw_call( 0x1234567890123456789012345678901234567890, b"cow", - outsize=4, + max_outsize=4, gas=595757, value=9 ) diff --git a/vyper/functions/functions.py b/vyper/functions/functions.py index a1a5da9c99..bd9f9816ce 100644 --- a/vyper/functions/functions.py +++ b/vyper/functions/functions.py @@ -782,7 +782,7 @@ class RawCall: _id = "raw_call" _inputs = [("to", "address"), ("data", "bytes")] _kwargs = { - "outsize": Optional('num_literal', 0), + "max_outsize": Optional('num_literal', 0), "gas": Optional('uint256', 'gas'), "value": Optional('uint256', zero_value), "is_delegate_call": Optional('bool', false_value), @@ -796,7 +796,7 @@ def build_LLL(self, expr, args, kwargs, context): gas, value, outsize, delegate_call, static_call = ( kwargs['gas'], kwargs['value'], - kwargs['outsize'], + kwargs['max_outsize'], kwargs['is_delegate_call'], kwargs['is_static_call'], )