Repository: vyperlang/vyper

Issue:
Logging events with bytes array do not work as expected
When declaring an event with a bytes array parameter, like so;
```
Newgreeting: __log__({_sender: indexed(address), _newgreeting: bytes <= 20})
```

One would expect that when you would log a bytes array value into the log line;
```
@public
@payable
def setGreeting(_greeting: bytes <= 20) -> bool:
    self.greeting = _greeting
    log.Newgreeting( msg.sender, _greeting)
    return True
```

So with ```_greeting = "Hello"``` the expected payload of the event call back argument should be:
```
0x48656c6c6f000000000000000000000000000000
```

The code however returns
```
0x0000000000000000000000000000000000000000
```

Setting the log line with a string literal "Hello", so;
```
@public
@payable
def setGreeting(_greeting: bytes <= 20) -> bool:
    self.greeting = _greeting
    log.Newgreeting( msg.sender, "Hello")
    return True
```

results in the right string in the payload of the event call back argument.

Diving a bit deeper into this issue with a ```viper -f ir <filename.v.py>``` call shows that the compiler tries to convert the bytes array into a number of some kind.  This is not expected, one would expect a simple byte-4-byte memory copy from one bytes array to the other.

```
           # Line 16
            [mstore,
              448,
              /* bytearray to number, verify no leading zbytes */
              [with,
                _sub,
                352 <_greeting>,
                [with,
                  _el1,
                  [mload, [add, 32, _sub]],
                  [with,
                    _len,
                    [clamp, 0, [mload, _sub], 32],
                    [seq,
                      [assert, [or, [iszero, _len], [div, _el1, [exp, 256, 31]]]],
                      [div, _el1, [exp, 256, [sub, 3, _len]]]]]]]],
```

>  I guess this is where you insert a cute animal picture ;-)
![Cute Cat picture](https://i.ytimg.com/vi/W-PBFMECvTE/maxresdefault.jpg "Cat in pants")


Discussion:
The problem appears that the String passed as a byte array is seen as a Str value and again converted in a byte array. I expect this to deliver a string that is seen as an empty string and hence padding that empty string with 00 for each byte in the byte array delivers 0x0000000000000000000000000000000000000000

Resolving patch:
diff --git a/viper/parser/parser.py b/viper/parser/parser.py
--- a/viper/parser/parser.py
+++ b/viper/parser/parser.py
@@ -645,14 +645,15 @@ def parse_stmt(stmt, context):
     return Stmt(stmt, context).lll_node
 
 
-def pack_logging_topics(event_id, args, topics_types, context):
+def pack_logging_topics(event_id, args, expected_topics, context):
     topics = [event_id]
-    for pos, typ in enumerate(topics_types):
+    for pos, expected_topic in enumerate(expected_topics):
+        typ = expected_topic.typ
         arg = args[pos]
-        input = parse_expr(arg, context)
+        value = parse_expr(arg, context)
         if isinstance(typ, ByteArrayType) and (isinstance(arg, ast.Str) or (isinstance(arg, ast.Name) and arg.id not in reserved_words)):
-            if input.typ.maxlen > typ.maxlen:
-                raise TypeMismatchException("Topic input bytes are to big: %r %r" % (input.typ, typ))
+            if value.typ.maxlen > typ.maxlen:
+                raise TypeMismatchException("Topic input bytes are to big: %r %r" % (value.typ, typ))
             if isinstance(arg, ast.Str):
                 bytez, bytez_length = string_to_bytes(arg.s)
                 if len(bytez) > 32:
@@ -660,19 +661,19 @@ def pack_logging_topics(event_id, args, topics_types, context):
                 topics.append(bytes_to_int(bytez + b'\x00' * (32 - bytez_length)))
             else:
                 size = context.vars[arg.id].size
-                topics.append(byte_array_to_num(input, arg, 'num256', size))
+                topics.append(byte_array_to_num(value, arg, 'num256', size))
         else:
-            input = unwrap_location(input)
-            input = base_type_conversion(input, input.typ, typ, arg)
-            topics.append(input)
+            value = unwrap_location(value)
+            value = base_type_conversion(value, value.typ, typ)
+            topics.append(value)
     return topics
 
 
 def pack_args_by_32(holder, maxlen, arg, typ, context, placeholder):
     if isinstance(typ, BaseType):
-        input = parse_expr(arg, context)
-        input = base_type_conversion(input, input.typ, typ, arg)
-        holder.append(LLLnode.from_list(['mstore', placeholder, input], typ=typ, location='memory'))
+        value = parse_expr(arg, context)
+        value = base_type_conversion(value, value.typ, typ)
+        holder.append(LLLnode.from_list(['mstore', placeholder, value], typ=typ, location='memory'))
     elif isinstance(typ, ByteArrayType):
         bytez = b''
         # String literals
@@ -689,12 +690,10 @@ def pack_args_by_32(holder, maxlen, arg, typ, context, placeholder):
             holder.append(LLLnode.from_list(['mstore', placeholder, bytes_to_int(bytez + b'\x00' * (32 - bytez_length))], typ=typ, location='memory'))
         # Variables
         else:
-            input = parse_expr(arg, context)
-            if input.typ.maxlen > typ.maxlen:
-                raise TypeMismatchException("Data input bytes are to big: %r %r" % (input.typ, typ))
-            if arg.id in context.vars:
-                size = context.vars[arg.id].size
-                holder.append(LLLnode.from_list(['mstore', placeholder, byte_array_to_num(parse_expr(arg, context), arg, 'num256', size)], typ=typ, location='memory'))
+            value = parse_expr(arg, context)
+            if value.typ.maxlen > typ.maxlen:
+                raise TypeMismatchException("Data input bytes are to big: %r %r" % (value.typ, typ))
+            holder.append(LLLnode.from_list(['mstore', placeholder, ['mload', ['add', value, 32]]], typ=typ, location='memory'))
     elif isinstance(typ, ListType):
             maxlen += (typ.count - 1) * 32
             typ = typ.subtype
@@ -705,13 +704,14 @@ def pack_args_by_32(holder, maxlen, arg, typ, context, placeholder):
 
 
 # Pack logging data arguments
-def pack_logging_data(types, args, context):
+def pack_logging_data(expected_data, args, context):
     # Checks to see if there's any data
     if not args:
         return ['seq'], 0, 0
     holder = ['seq']
     maxlen = len(args) * 32
-    for i, (arg, typ) in enumerate(zip(args, types)):
+    for i, (arg, data) in enumerate(zip(args, expected_data)):
+        typ = data.typ
         holder, maxlen = pack_args_by_32(holder, maxlen, arg, typ, context, context.new_placeholder(BaseType(32)))
     return holder, maxlen, holder[1].to_list()[1][0]
 
diff --git a/viper/parser/stmt.py b/viper/parser/stmt.py
--- a/viper/parser/stmt.py
+++ b/viper/parser/stmt.py
@@ -135,17 +135,19 @@ def call(self):
             if self.stmt.func.attr not in self.context.sigs['self']:
                 raise VariableDeclarationException("Event not declared yet: %s" % self.stmt.func.attr)
             event = self.context.sigs['self'][self.stmt.func.attr]
-            topics_types, topics = [], []
-            data_types, data = [], []
+            if len(event.indexed_list) != len(self.stmt.args):
+                raise VariableDeclarationException("%s received %s arguments but expected %s" % (event.name, len(self.stmt.args), len(event.indexed_list)))
+            expected_topics, topics = [], []
+            expected_data, data = [], []
             for pos, is_indexed in enumerate(event.indexed_list):
                 if is_indexed:
-                    topics_types.append(event.args[pos].typ)
+                    expected_topics.append(event.args[pos])
                     topics.append(self.stmt.args[pos])
                 else:
-                    data_types.append(event.args[pos].typ)
+                    expected_data.append(event.args[pos])
                     data.append(self.stmt.args[pos])
-            topics = pack_logging_topics(event.event_id, topics, topics_types, self.context)
-            inargs, inargsize, inarg_start = pack_logging_data(data_types, data, self.context)
+            topics = pack_logging_topics(event.event_id, topics, expected_topics, self.context)
+            inargs, inargsize, inarg_start = pack_logging_data(expected_data, data, self.context)
             return LLLnode.from_list(['seq', inargs, ["log" + str(len(topics)), inarg_start, inargsize] + topics], typ=None, pos=getpos(self.stmt))
         else:
             raise StructureException("Unsupported operator: %r" % ast.dump(self.stmt), self.stmt)


Test patch:
diff --git a/tests/parser/features/test_logging.py b/tests/parser/features/test_logging.py
--- a/tests/parser/features/test_logging.py
+++ b/tests/parser/features/test_logging.py
@@ -1,6 +1,6 @@
 import pytest
 from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \
-    get_contract_with_gas_estimation, get_contract, assert_tx_failed
+    get_contract_with_gas_estimation, get_contract
 from viper.exceptions import VariableDeclarationException, TypeMismatchException, StructureException
 
 
@@ -106,10 +106,6 @@ def bar():
 def test_event_logging_cannot_have_more_than_three_topics(assert_tx_failed):
     loggy_code = """
 MyLog: __log__({arg1: indexed(bytes <= 3), arg2: indexed(bytes <= 4), arg3: indexed(address), arg4: indexed(num)})
-
-@public
-def foo():
-    log.MyLog('bar', 'home', self)
     """
 
     assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), VariableDeclarationException)
@@ -163,7 +159,7 @@ def foo():
     assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': [1, 2], 'arg2': [timestamp, timestamp + 1, timestamp + 2], 'arg3': [[1, 2], [1, 2]], '_event_type': b'MyLog'}
 
 
-def test_logging_with_input_bytes(bytes_helper):
+def test_logging_with_input_bytes_1(bytes_helper):
     loggy_code = """
 MyLog: __log__({arg1: indexed(bytes <= 4), arg2: indexed(bytes <= 29), arg3: bytes<=31})
 
@@ -171,6 +167,7 @@ def test_logging_with_input_bytes(bytes_helper):
 def foo(arg1: bytes <= 29, arg2: bytes <= 31):
     log.MyLog('bar', arg1, arg2)
 """
+
     c = get_contract_with_gas_estimation(loggy_code)
     c.foo('bar', 'foo')
     logs = s.head_state.receipts[-1].logs[-1]
@@ -185,6 +182,52 @@ def foo(arg1: bytes <= 29, arg2: bytes <= 31):
     assert c.translator.decode_event(logs.topics, logs.data) ==  {'arg1': b'bar\x00', 'arg2': bytes_helper('bar', 29), 'arg3': bytes_helper('foo', 31), '_event_type': b'MyLog'}
 
 
+def test_event_logging_with_bytes_input_2(t, bytes_helper):
+    loggy_code = """
+MyLog: __log__({arg1: bytes <= 20})
+
+@public
+def foo(_arg1: bytes <= 20):
+    log.MyLog(_arg1)
+    """
+
+    c = get_contract_with_gas_estimation(loggy_code)
+    c.foo('hello')
+    logs = s.head_state.receipts[-1].logs[-1]
+    event_id = u.bytes_to_int(u.sha3(bytes('MyLog(bytes20)', 'utf-8')))
+    # Event id is always the first topic
+    assert logs.topics[0] == event_id
+    # Event id is calculated correctly
+    assert c.translator.event_data[event_id]
+    # Event abi is created correctly
+    assert c.translator.event_data[event_id] == {'types': ['bytes20'], 'name': 'MyLog', 'names': ['arg1'], 'indexed': [False], 'anonymous': False}
+    # Event is decoded correctly
+    assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': bytes_helper('hello', 20), '_event_type': b'MyLog'}
+
+
+def test_event_logging_with_bytes_input_3(bytes_helper):
+    loggy_code = """
+MyLog: __log__({arg1: bytes <= 5})
+
+@public
+def foo(_arg1: bytes <= 5):
+    log.MyLog(_arg1)
+    """
+
+    c = get_contract(loggy_code)
+    c.foo('hello')
+    logs = s.head_state.receipts[-1].logs[-1]
+    event_id = u.bytes_to_int(u.sha3(bytes('MyLog(bytes5)', 'utf-8')))
+    # Event id is always the first topic
+    assert logs.topics[0] == event_id
+    # Event id is calculated correctly
+    assert c.translator.event_data[event_id]
+    # Event abi is created correctly
+    assert c.translator.event_data[event_id] == {'types': ['bytes5'], 'name': 'MyLog', 'names': ['arg1'], 'indexed': [False], 'anonymous': False}
+    # Event is decoded correctly
+    assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': b'hello', '_event_type': b'MyLog'}
+
+
 def test_event_logging_with_data_with_different_types():
     loggy_code = """
 MyLog: __log__({arg1: num, arg2: bytes <= 4, arg3: bytes <= 3, arg4: address, arg5: address, arg6: timestamp})
@@ -208,7 +251,7 @@ def foo():
     assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': 123, 'arg2': b'home', 'arg3': b'bar', 'arg4': '0xc305c901078781c232a2a521c2af7980f8385ee9', 'arg5': '0x' + c.address.hex(), 'arg6': s.head_state.timestamp, '_event_type': b'MyLog'}
 
 
-def test_event_logging_with_topics_and_data():
+def test_event_logging_with_topics_and_data_1():
     loggy_code = """
 MyLog: __log__({arg1: indexed(num), arg2: bytes <= 3})
 
@@ -404,17 +447,37 @@ def test_logging_fails_after_a_global_declaration(assert_tx_failed):
 
 def test_logging_fails_after_a_function_declaration(assert_tx_failed):
     loggy_code = """
-
 @public
 def foo():
     pass
 
 MyLog: __log__({arg1: bytes <= 3})
     """
-    t.s = s
     assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), StructureException)
 
 
+def test_logging_fails_when_number_of_arguments_is_greater_than_declaration(assert_tx_failed):
+    loggy_code = """
+MyLog: __log__({arg1: num})
+
+@public
+def foo():
+    log.MyLog(1, 2)
+"""
+    assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), VariableDeclarationException)
+
+
+def test_logging_fails_when_number_of_arguments_is_less_than_declaration(assert_tx_failed):
+    loggy_code = """
+MyLog: __log__({arg1: num, arg2: num})
+
+@public
+def foo():
+    log.MyLog(1)
+"""
+    assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), VariableDeclarationException)
+
+
 def test_loggy_code():
     loggy_code = """
 s: bytes <= 100