Skip to content

Vyper compiler bug for functions returning byte arrays

Daejun Park edited this page Jul 22, 2019 · 2 revisions

Version Information

  • vyper Version (output of vyper --version): 0.1.0b10

What's your issue about?

A function whose return type is bytes[n] where n < 16, returns a value that does not conform to the ABI encoding, having incorrect zero-padding.

For example, the get_deposit_count function of the deposit contract, whose return type is bytes[8], returns the following 96 bytes (in hexadecimal notation):

0x0000000000000000000000000000000000000000000000000000000000000020
  0000000000000000000000000000000000000000000000000000000000000008
  ________________000000000000000000000000000000000000000000000020
                                                                ^^  <-- problem!

where the first 32 bytes (the first line) denotes the header (the offset 32 = 0x20), the second 32 bytes (the second line) denotes the length of the byte array (8), and the "________________" denotes the content of the byte array (bytes[8]). Here, the problem is that the last byte is 32 (= 0x20) while it should be 0 (= 0x00) according to the ABI specification. This means that any contract (written in either Solidity or Vyper) that calls to this type of functions expecting the correct zero-padding may misbehave.

What happens is that the compiled bytecode of get_deposit_count fails to sufficiently pad zeros when it prepares for the return value. In the following return statement,

return self.to_little_endian_64(self.deposit_count)

it first copies the returned value (of 8 bytes) to some specific region of the memory, and puts only 8 bytes of zero-padding after that, instead of 24 bytes of zeros, which results in including some garbage values in the last 16 bytes. Indeed, in this particular case, the last 16 bytes (000000000000000000000000020) came from the side-effect of the sub-function call to self.to_little_endian_64().

To be more specific, in the following zero-padding LLL code of get_deposit_count (generated by https://github.com/ethereum/vyper/blob/cdfb36d0c6d4a38ee3d9d34e001b141ec7c6f55f/vyper/parser/stmt.py#L770-L789):

            /* Zero pad */
            [with,
              _ceil32_end,
              [ceil32, [mload, 640]],
              [repeat,
                736,
                [mload, 640],
    ??? -->     8,
                [seq,
                  [if, [gt, [mload, 736], _ceil32_end], break],
                  [mstore8, [add, 672, [mload, 736]], 0]]]],
            [mstore, 608, 32],
            [return, 608, [ceil32, [add, [mload, 640], 64]]],
            # Line 58
            stop]],

the third argument of the repeat loop is 8, where it should be at least 24. In a quick examination of the Vyper compiler code, it seems that the number 8 comes from the maxlen of the type bytes[8], but I couldn't have a chance to further investigate the root cause and a quick fix.

Indeed, the same problem happens in the to_little_endian_64 function as well.

Clone this wiki locally