Vyper: Fails: Using empty `bytes[256]` default function parameter on internal constant and private functions

Created on 2 Jun 2019  路  14Comments  路  Source: vyperlang/vyper

Version Information

  • vyper Version: 0.1.0b10
  • OS: osx
  • Python Version: 3.7.3

What's your issue about?

Calling mint() on the following contract throws when making use of the default parameters:

@private
@constant
def _checkForERC777TokensInterface_Recipient(
    _to: address,
    _amount: uint256,
    _data: bytes[256]="",
  ):
    assert True


@public
def mint(
    _to: address,
    _amount: uint256,
    _data: bytes[256]="",
  ):
    # NOTE: fails (stack underflow) because of default _data parameter
    self._checkForERC777TokensInterface_Recipient(
      _to,
      _amount,
      _data,
    )

Using web3js:

This throws: Error: Returned error: VM Exception while processing transaction: stack underflow

await erc777Token.mint(
      wallet.address,
      100,
    );

This works:

    await erc777Token.mint(
      wallet.address,
      100,
      '0x0000000000000000000000000000000000000001',
    );

How can this be solved?

I posted a solution further down (use data: bytes[256]="0x0" in mint()).
Maybe everything is fine and one should just not use default parameters in internal functions...
In that case this issue can be closed.

bug

All 14 comments

The above probably fails because vyper creates two methods:

def mint(_to: address,  _amount: uint256)

# and

def mint(_to: address,  _amount: uint256,  _data: bytes[256])

And in the first method the _data parameter does not exist and therefore can't be passed to the _checkForERC777TokensInterface_Recipient call.

Does Vyper create two internal methods for _checkForERC777TokensInterface_Recipient as well?

Maybe something like this could be done:

@private
@constant
def _checkForERC777TokensInterface_Recipient(
    _to: address,
    _amount: uint256,
    _data: bytes[256]="",
  ):
    assert True


@public
def mint(
    _to: address,
    _amount: uint256,
    _data: bytes[256]="",
  ):
    self._checkForERC777TokensInterface_Recipient(
      _to,
      _amount,
      _data="", # <-------------------------- CHANGE HERE
    )

This fails as well:

    await erc777Token.mint(
      wallet.address,
      100,
      ' 0x0000000000000000000000000000000000000000',
    );

(same stack underflow error)

This works:

@private
@constant
def _checkForERC777TokensInterface_Recipient(
    _to: address,
    _amount: uint256,
    _data: bytes[256], #  <------------------------------ CHANGE IS HERE
  ):
    assert True


@public
def mint(
    _to: address,
    _amount: uint256,
    _data: bytes[256]="0x0", #  <------------------------------ CHANGE IS HERE
  ):
    self._checkForERC777TokensInterface_Recipient(
      _to,
      _amount,
      _data,
    )

web3js:

await erc777Token.mint(
      wallet.address,
      100,
    );

@ssteiger am I understanding correctly that the exception only occurs if the default parameter is an empty bytearray, on a private function?

@jacqueswww
Yes, it seems like the exception only occurs if the default parameter is an empty bytearray
And only if the function is private (functions with @public are working)

I exclude all accompanying parameter type changes (with same default value) in @private _checkForERC777TokensInterface_Recipient.

address

# works
def mint(
    _to: address,
    _amount: uint256,
    _data: address=0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24
  ):

string array

# works
def mint(
    _to: address,
    _amount: uint256,
    _data: string[4]="test"
  ):

```python

fails

def mint(
_to: address,
_amount: uint256,
_data: string[4]=""
):

### boolean
```python
# fails
# Throws: Default parameter values have to be literals.
def mint(
    _to: address,
    _amount: uint256,
    _data: bool=False
  ):

bytes32

# works
def mint(
    _to: address,
    _amount: uint256,
    _data: bytes32=0x0000000000000000000000000000000000000000000000000000000000000000
  ):

```python

fails

Throws: Base type conversion from or to non-base type: string[0] bytes32

def mint(
_to: address,
_amount: uint256,
_data: bytes32=""
):

### int128
```python
# works
def mint(
    _to: address,
    _amount: uint256,
    _data: int128=100
  ):

uint256

# works
def mint(
    _to: address,
    _amount: uint256,
    _data: uint256=0
  ):

decimal

# works
def mint(
    _to: address,
    _amount: uint256,
    _data: decimal=1.01
  ):

Looks like calling methods of other contracts (also if they are @public) produces the same error:

This works:

# CONTRACT 1

@public
def _checkForERC777TokensInterface_Recipient(
    _to: address,
    _amount: uint256,
    _data: bytes[256]="",
  ):
    assert True


@public
def mint(
    _to: address,
    _amount: uint256,
    _data: bytes[256]="",
  ):
    self._checkForERC777TokensInterface_Recipient(
      _to,
      _amount,
      _data,
    )

This fails:

# CONTRACT 1

@public
def _checkForERC777TokensInterface_Recipient(
    _to: address,
    _amount: uint256,
    _data: bytes[256]="",
  ):
    ERC777TokensSender(_from).tokensToSend(_operator, _from, _to, _amount, _data, _operatorData)


@public
def mint(
    _to: address,
    _amount: uint256,
    _data: bytes[256]="",
  ):
    self._checkForERC777TokensInterface_Recipient(
      _to,
      _amount,
      _data,
    )



# CONTRACT 2 (ERC777TokensSender)

ERC777Received: event({
    _operator: indexed(address),
    _from: indexed(address),
    _to: indexed(address),
    _amount: uint256,
    _data: bytes[256],
    _operatorData: bytes[256]
})

# This fails (revert)
@public
def tokensReceived(
    _operator: address,
    _from: address,
    _to: address,
    _amount: uint256,
    _data: bytes[256]="",
    _operatorData: bytes[256]=""
  ):
    log.ERC777Received(_operator, _from, _to, _amount, _data, _operatorData)

This works

# CONTRACT 2 (ERC777TokensSender)

@public
def tokensReceived(
    _operator: address,
    _from: address,
    _to: address,
    _amount: uint256,
    _data: bytes[256]="",
    _operatorData: bytes[256]=""
  ):
    assert True

@ssteiger do you have the same issue with v0.1.0b9?

@charles-cooper I've installed v0.1.0b10 on my system using make.
Do you know of a way to easily downgrade to v0.1.0b9?

If you are installing from a local repository, you can git checkout the v0.1.0-beta.9 tag and reinstall using make.

Just tested it. Same stack underflow error with vyper-0.1.0b8 and vyper-0.1.0b9.
Also only when the methods are @private. Setting them to @public makes the error disappear.

this is the most minimal reproducible example i've found:

# stack underflow
@private
def bar(x: bytes[32]):
    pass
@public
def foo(x: bytes[32] = b'\x00'):
    self.bar(x)

vs

# runs without exception
@private
def bar(x: bytes[32]):
    pass
@public
def foo(x: bytes[32] = b'\x01'):
    self.bar(x)

(these differ in one instruction - mstore 480 0 vs mstore 480 0x100000000000000000000000000000000000000000000000000000000000000)

As far as I have traced this, it is related to the dynamic packing/unpacking of the empty byte array into the argument section for the private function.
https://github.com/ethereum/vyper/blob/master/vyper/parser/self_call.py#L113
After returning back from the function the unpacker unpacks too may stack items.

@ssteiger please test on master ;)

It's working. Thank you for the quick fix. You guys rock!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nrryuya picture nrryuya  路  3Comments

denis-bogdanas picture denis-bogdanas  路  3Comments

jacqueswww picture jacqueswww  路  4Comments

fubuloubu picture fubuloubu  路  3Comments

robinsierra picture robinsierra  路  3Comments