Repository: vyperlang/vyper

Issue:
Gas consumption of logging bytes array
### Version Information

* vyper Version: 0.1.0b7
* OS: osx
* Python Version (python --version): 3.6.0

### What's your issue about?

I was documenting the gas consumption of [eth2.0 deposit contract](https://github.com/ethereum/deposit_contract/blob/master/deposit_contract/contracts/validator_registration.v.py) and after a little experiments some unintuitive results came up.
**Summary**: with an event that logs a byte array, the larger the byte array data are logged, the **less** gas it cost(and transaction also runs faster in local testing environment).

* Vyper contract code(extracted and modified from the deposit contract) used for testing:
```
Deposit: event({data: bytes[2064]})

@public
def deposit2(deposit_input: bytes[2064]):
    log.Deposit(deposit_input)
```

* Results
    * [tx on rinkeby](https://rinkeby.etherscan.io/tx/0x6238be1f1d27bb53e9226420b7c5f94e2c48c15b0464be7b232abbedb4de7e97) with `deposit_input` being `b'\x01' * 2048`
            * estimated cost for logging: 2048 * 68 + 2064 * 8 + 21000 = 176776
            * actual gas cost = 183846
    * [tx on rinkeby](https://rinkeby.etherscan.io/tx/0xf73b476ddb0662fe7ec3d2254e6b3ff996dfcfcd91ae0f44ed6b4afd5e9ef6a6) with `deposit_input` being `b'\x01' * 1024`
            * estimated cost for logging: 1024 * 68 + 1040 * 8 + 21000 = 98952
            * actual gas cost = 209350
    * [tx on rinkeby](https://rinkeby.etherscan.io/tx/0x6143f0812476ccb05f0c8bee0d4e8f8c671bb1ddca75c94b75b14eb65509cf93) with `deposit_input` being `b'\x01'`
            * estimated cost for logging: 1 * 68 + 17 * 8 + 21000 = 21204
            * actual gas cost = 235204
___

* Try Solidity
```
pragma solidity ^0.4.24;

contract testlogbytes {
    event LogBytes(bytes log);
    
    function logbytes(bytes _log) public {
        emit LogBytes(_log);
    }
}
```

* Results
    * [tx on rinkeby](https://rinkeby.etherscan.io/tx/0x6238be1f1d27bb53e9226420b7c5f94e2c48c15b0464be7b232abbedb4de7e97) with `_log` being `b'\x01' * 2048`
            * estimated cost for logging: 2048 * 68 + 2064 * 8 + 21000 = 176776
            * actual gas cost = 183997
    * [tx on rinkeby](https://rinkeby.etherscan.io/tx/0x7b0b1464f835b67a290778bb0ab072bde86c6e7e2626c6212d97893510afe314) with `_log` being `b'\x01' * 1024`
            * estimated cost for logging: 1024 * 68 + 1040 * 8 + 21000 = 98952
            * actual gas cost = 103715
    * [tx on rinkeby](https://rinkeby.etherscan.io/tx/0x6143f0812476ccb05f0c8bee0d4e8f8c671bb1ddca75c94b75b14eb65509cf93) with `_log` being `b'\x01'`
            * estimated cost for logging: 1 * 68 + 17 * 8 + 21000 = 21204
            * actual gas cost = 24090

### How can it be fixed?

Fill this in if you know how to fix it.

#### Cute Animal Picture

> put a cute animal picture here.

![](https://cdn.pixabay.com/photo/2017/12/21/22/52/dog-3032728_1280.jpg)



Discussion:
Very interesting, my initial reaction would be that vypers' performance has to be linear to being reverse of the stated results - because vyper will copy calldata to memory after applying clamps.

Could you run the same tests on https://github.com/ethereum/vyper/pull/1213 ?
@NIC619  I think I figured it out - currently vyper pads the bytearray with empty bytes - this would describe the above behaviour.
```
 [with,
              _bytearray_loc,
              /* pack_args_by_32:dest_placeholder */ 
              [add,
                2592,
                [mload, 2528]],
              [seq,
                [repeat,
                  2496,
                  [mload, _bytearray_loc],
                  2080,
                  [seq,
                    [if, [ge, [mload, 2496], 2080], break],
                    [mstore8,
                      [add, [add, _bytearray_loc, 32], [mload, 2496]],
                      0]]]]]
```

The reason this is done because `eth-abi` doesn't allow dirty bytes (non zero bytes to occur after allocated bytes).
https://github.com/ethereum/eth-abi/blob/082819a1879068810e50b487d6b5aa1a9f1d7551/eth_abi/decoding.py#L328

I think to fix this - one would just have to zero pad up until `ceil32(len_val)`, instead of the whole array.
The other option is the figure out if this is even a constraint (or should even be a constraint on the abi).

Resolving patch:
diff --git a/vyper/parser/parser.py b/vyper/parser/parser.py
--- a/vyper/parser/parser.py
+++ b/vyper/parser/parser.py
@@ -681,12 +681,14 @@ def pack_args_by_32(holder, maxlen, arg, typ, context, placeholder,
         new_maxlen = ceil32(source_expr.lll_node.typ.maxlen)
 
         holder.append(
-            ['with', '_bytearray_loc', dest_placeholder,
+            ['with', '_ceil32_end', ['ceil32', ['mload', dest_placeholder]],
                 ['seq',
-                    ['repeat', zero_pad_i, ['mload', '_bytearray_loc'], new_maxlen,
+                    ['with', '_bytearray_loc', dest_placeholder,
                         ['seq',
-                            ['if', ['ge', ['mload', zero_pad_i], new_maxlen], 'break'],  # stay within allocated bounds
-                            ['mstore8', ['add', ['add', '_bytearray_loc', 32], ['mload', zero_pad_i]], 0]]]]]
+                            ['repeat', zero_pad_i, ['mload', '_bytearray_loc'], new_maxlen,
+                                ['seq',
+                                    ['if', ['ge', ['mload', zero_pad_i], '_ceil32_end'], 'break'],  # stay within allocated bounds
+                                    ['mstore8', ['add', ['add', '_bytearray_loc', 32], ['mload', zero_pad_i]], 0]]]]]]]
         )
         # Increment offset counter.
         increment_counter = LLLnode.from_list(
diff --git a/vyper/parser/stmt.py b/vyper/parser/stmt.py
--- a/vyper/parser/stmt.py
+++ b/vyper/parser/stmt.py
@@ -544,10 +544,11 @@ def zero_pad(bytez_placeholder, maxlen):
             if maxlen > 0:
                 zero_pad_i = self.context.new_placeholder(BaseType('uint256'))  # Iterator used to zero pad memory.
                 zero_padder = LLLnode.from_list(
-                    ['repeat', zero_pad_i, ['mload', bytez_placeholder], maxlen,
-                        ['seq',
-                            ['if', ['gt', ['mload', zero_pad_i], maxlen], 'break'],  # stay within allocated bounds
-                            ['mstore8', ['add', ['add', 32, bytez_placeholder], ['mload', zero_pad_i]], 0]]],
+                    ['with', '_ceil32_end', ['ceil32', ['mload', bytez_placeholder]],
+                        ['repeat', zero_pad_i, ['mload', bytez_placeholder], maxlen,
+                            ['seq',
+                                ['if', ['gt', ['mload', zero_pad_i], '_ceil32_end'], 'break'],  # stay within allocated bounds
+                                ['mstore8', ['add', ['add', 32, bytez_placeholder], ['mload', zero_pad_i]], 0]]]],
                     annotation="Zero pad"
                 )
             return zero_padder


Test patch: