Repository: vyperlang/vyper

Issue:
Incorrect logging of indexed byte arrays
### What's your issue about?

Logging indexed byte arrays works either if they have runtime length precisely 3! or are literals.

Here is a test that fails:

    def test_event_logging_with_multiple_topics(get_contract_with_gas_estimation, chain, utils):
        loggy_code = """
    MyLog: event({arg1: indexed(bytes <= 3), arg2: indexed(bytes <= 4), arg3: indexed(address)})

    @public
    def foo():
        a : bytes <= 4 = 'home'
        log.MyLog('bar', a, self)
        """

        c = get_contract_with_gas_estimation(loggy_code)
        c.foo()
        logs = chain.head_state.receipts[-1].logs[-1]
        event_id = utils.bytes_to_int(utils.sha3(bytes('MyLog(bytes3,bytes4,address)', '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': ['bytes3', 'bytes4', 'address'],
            'name': 'MyLog',
            'names': ['arg1', 'arg2', 'arg3'],
            'indexed': [True, True, True],
            'anonymous': False}
        )
        # Event is decoded correctly
        assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': b'bar', 'arg2': b'home', 'arg3': '0x' + c.address.hex(), '_event_type': b'MyLog'}



### How can it be fixed?

The issue is in the way `size` is used on these lines:

https://github.com/ethereum/vyper/blob/9eebb7c575545fcf880d815b9c888665dccbea51/vyper/parser/parser.py#L725-L726

#### Cute Animal Picture

> put a cute animal picture here.



Discussion:
Starting on this.

Resolving patch:
diff --git a/vyper/parser/expr.py b/vyper/parser/expr.py
--- a/vyper/parser/expr.py
+++ b/vyper/parser/expr.py
@@ -109,7 +109,8 @@ def string(self):
         seq.append(['mstore', placeholder, bytez_length])
         for i in range(0, len(bytez), 32):
             seq.append(['mstore', ['add', placeholder, i + 32], bytes_to_int((bytez + b'\x00' * 31)[i: i + 32])])
-        return LLLnode.from_list(['seq'] + seq + [placeholder], typ=ByteArrayType(bytez_length), location='memory', pos=getpos(self.expr))
+        return LLLnode.from_list(['seq'] + seq + [placeholder],
+            typ=ByteArrayType(bytez_length), location='memory', pos=getpos(self.expr), annotation='Create ByteArray: %s' % bytez)
 
     # True, False, None constants
     def constants(self):
diff --git a/vyper/parser/parser.py b/vyper/parser/parser.py
--- a/vyper/parser/parser.py
+++ b/vyper/parser/parser.py
@@ -52,7 +52,6 @@
 from vyper.utils import (
     MemoryPositions,
     LOADED_LIMIT_MAP,
-    reserved_words,
     string_to_bytes,
     valid_global_keywords
 )
@@ -645,27 +644,33 @@ def parse_stmt(stmt, context):
     return Stmt(stmt, context).lll_node
 
 
-def pack_logging_topics(event_id, args, expected_topics, context):
+def pack_logging_topics(event_id, args, expected_topics, context, pos):
     topics = [event_id]
     for pos, expected_topic in enumerate(expected_topics):
-        typ = expected_topic.typ
+        expected_type = expected_topic.typ
         arg = args[pos]
         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 value.typ.maxlen > typ.maxlen:
-                raise TypeMismatchException("Topic input bytes are to big: %r %r" % (value.typ, typ))
+        arg_type = value.typ
+
+        if isinstance(arg_type, ByteArrayType) and isinstance(expected_type, ByteArrayType):
+            if arg_type.maxlen > expected_type.maxlen:
+                raise TypeMismatchException("Topic input bytes are too big: %r %r" % (arg_type, expected_type), pos)
             if isinstance(arg, ast.Str):
                 bytez, bytez_length = string_to_bytes(arg.s)
                 if len(bytez) > 32:
-                    raise InvalidLiteralException("Can only log a maximum of 32 bytes at a time.")
+                    raise InvalidLiteralException("Can only log a maximum of 32 bytes at a time.", pos)
                 topics.append(bytes_to_int(bytez + b'\x00' * (32 - bytez_length)))
             else:
-                size = context.vars[arg.id].size
+                if value.location == "memory":
+                    size = ['mload', value]
+                elif value.location == "storage":
+                    size = ['sload', ['sha3_32', value]]
                 topics.append(byte_array_to_num(value, arg, 'uint256', size))
         else:
             value = unwrap_location(value)
-            value = base_type_conversion(value, value.typ, typ)
+            value = base_type_conversion(value, arg_type, expected_type)
             topics.append(value)
+
     return topics
 
 
diff --git a/vyper/parser/stmt.py b/vyper/parser/stmt.py
--- a/vyper/parser/stmt.py
+++ b/vyper/parser/stmt.py
@@ -203,7 +203,7 @@ def call(self):
                 else:
                     expected_data.append(event.args[pos])
                     data.append(self.stmt.args[pos])
-            topics = pack_logging_topics(event.event_id, topics, expected_topics, self.context)
+            topics = pack_logging_topics(event.event_id, topics, expected_topics, self.context, pos=getpos(self.stmt))
             inargs, inargsize, inargsize_node, inarg_start = pack_logging_data(expected_data, data, self.context)
             if inargsize_node is None:
                 sz = inargsize


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
@@ -76,6 +76,34 @@ def foo():
     assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': b'bar', 'arg2': b'home', 'arg3': '0x' + c.address.hex(), '_event_type': b'MyLog'}
 
 
+def test_event_logging_with_multiple_topics_var_and_store(get_contract_with_gas_estimation, chain, get_logs):
+    code = """
+MyLog: event({arg1: indexed(bytes[3]), arg2: indexed(bytes[4]), arg3: indexed(address), arg4: bytes[10]})
+b: bytes[10]
+
+@public
+def foo(arg1: bytes[3]):
+    a: bytes[4] = 'home'
+    self.b = 'hellothere'
+    log.MyLog(arg1, a,  self, self.b)
+    """
+
+    c = get_contract_with_gas_estimation(code)
+    c.foo('hel')
+
+    # Event is decoded correctly
+    receipt = chain.head_state.receipts[-1]
+    log = get_logs(receipt, c)[0]
+
+    assert log == {
+        'arg1': b'hel',
+        'arg2': b'home',
+        'arg3': '0x' + c.address.hex(),
+        'arg4': b'hellothere',
+        '_event_type': b'MyLog'
+    }
+
+
 def test_logging_the_same_event_multiple_times_with_topics(get_contract_with_gas_estimation, chain, utils):
     loggy_code = """
 MyLog: event({arg1: indexed(int128), arg2: indexed(address)})