Skip to content

Commit

Permalink
Merge pull request #1977 from iamdefinitelyahuman/fix-raw_call-outsize
Browse files Browse the repository at this point in the history
Fix raw call outsize
  • Loading branch information
fubuloubu authored May 17, 2020
2 parents f0b1234 + d06ef9b commit 1ebce95
Show file tree
Hide file tree
Showing 14 changed files with 69 additions and 46 deletions.
12 changes: 8 additions & 4 deletions docs/built-in-functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -144,21 +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 ``max_outsize``. You can use ``len`` to obtain the actual size.

.. py:function:: selfdestruct(to: address) -> None
Expand Down
2 changes: 1 addition & 1 deletion examples/wallet/wallet.vy
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions tests/examples/wallet/test_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down Expand Up @@ -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,
Expand Down
4 changes: 3 additions & 1 deletion tests/parser/exceptions/test_constancy_exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
""",
"""
Expand Down
6 changes: 3 additions & 3 deletions tests/parser/exceptions/test_structure_exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion tests/parser/exceptions/test_syntax_exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/parser/features/test_assert.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion tests/parser/features/test_conditionals.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion tests/parser/functions/test_method_id.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
45 changes: 26 additions & 19 deletions tests/parser/functions/test_raw_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -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_max_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", max_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_max_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", max_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_max_outsize(get_contract):
source_code = """
@public
def foo() -> bytes[5]:
return raw_call(0x0000000000000000000000000000000000000004, b"moose", max_outsize=5)
"""
c = get_contract(source_code)
assert c.foo() == b"moose"


def test_multiple_levels(w3, get_contract_with_gas_estimation):
Expand All @@ -41,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
Expand Down Expand Up @@ -83,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
Expand Down Expand Up @@ -128,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
)
"""
Expand Down Expand Up @@ -168,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
Expand Down Expand Up @@ -200,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)
Expand Down Expand Up @@ -230,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)
Expand Down
2 changes: 1 addition & 1 deletion tests/parser/functions/test_send.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions tests/parser/syntax/test_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 7 additions & 5 deletions tests/parser/syntax/test_raw_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
""",
]
Expand All @@ -47,7 +49,7 @@ def foo():
x: bytes[9] = raw_call(
0x1234567890123456789012345678901234567890,
b"cow",
outsize=4,
max_outsize=4,
gas=595757
)
""",
Expand All @@ -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")
)
Expand All @@ -68,7 +70,7 @@ def foo():
x: bytes[9] = raw_call(
0x1234567890123456789012345678901234567890,
b"cow",
outsize=4,
max_outsize=4,
gas=595757,
value=9
)
Expand Down
14 changes: 10 additions & 4 deletions vyper/functions/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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'],
)
Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit 1ebce95

Please sign in to comment.