Repository: vyperlang/vyper

Issue:
Logs are Malformed if Functions are Called in It
### Version Information

* vyper Version: `0.1.0b7`
* OS: `Ubuntu 18.04`
* Python Version (python --version): `3.6.7`
* Environment (output of `pip freeze`): 
```
pkg-resources==0.0.0
pycryptodome==3.7.2
vyper==0.1.0b7
```


### What's your issue about?

Calling a function when emitting a log leads to a malformed log. However, a normal 
log is emitted when an object is placed in the log instead of a function.

Please include information like:

* full output of the error you received
This test is in `go` however this error is relevant as when unpacking and decoding logs emitted, I do get the below error.

```
--- FAIL: TestProcessChainStartLog (1.63s)
    log.go:31: log: Unable to unpack ChainStart log data unable to unpack logs: abi: cannot marshal in to go slice: offset 3321006415 would go over slice boundary (len=128)
```

* the code that caused the failure (see [this link](https://help.github.com/articles/basic-writing-and-formatting-syntax/) for help with formatting code)

The below code causes an error:

```py 

TestLog: event({testData: bytes[8] })

@private
@constant
def to_bytes(value: uint256) -> bytes[8]:
    return slice(concat("", convert(value, bytes32)), start=24, len=8)

@public
def test_func(value: uint256):

  # log will be malformed
   TestLog(self.to_bytes(value))
   
   loggedValue: bytes[8] = self.to_bytes(value)
   
   # log will be normal
   TestLog(loggedValue)

```


### How can it be fixed?

Fill this in if you know how to fix it.

#### Cute Animal Picture
![image](https://user-images.githubusercontent.com/33201827/51470844-ffd2d400-1daf-11e9-854c-08b60a85d164.png)




Discussion:
Seems to be be problem with return value not being handled from private function call.
@jacqueswww 
I know my example above doesnt show it, but I got the same error using a public function call.
```py
 TestLog: event({testData: bytes[8] })

@public
@constant
def to_bytes(value: uint256) -> bytes[8]:
    return slice(concat("", convert(value, bytes32)), start=24, len=8)

@public
def test_func(value: uint256):

  # log will be malformed
   TestLog(self.to_bytes(value))
   
   loggedValue: bytes[8] = self.to_bytes(value)
   
   # log will be normal
   TestLog(loggedValue)
```
The above also will lead to malformed log
@nisdas are you sure this is a vyper problem, if I run it using py-evm / vyper-debug all seems in order:

```
vyper-run 1206_logging.vy "test_func(123)"

* Calling test_func(123)
- Returns:
'[]'
- Logs:
TestLog:
{'testData': b'\x00\x00\x00\x00\x00\x00\x00{'}
TestLog:
{'testData': b'\x00\x00\x00\x00\x00\x00\x00{'}
```
- Since both outputs are the same, without any errors.
- Also: 
```
>>> int.from_bytes(b'\x00\x00\x00\x00\x00\x00\x00{', byteorder='big')
123
```

I might be using too old a version, let met try again ;)

I still get the same results, using internal vyper tests on master:

```
AttributeDict({'testData': b'\x00\x00\x00\x00\x00\x00\x00{'})
AttributeDict({'testData': b'\x00\x00\x00\x00\x00\x00\x00{'})
```

Here is the test case:

```python
def test_log_return_val(get_logs, get_contract_with_gas_estimation):
    code = """
TestLog: event({testData: bytes[8] })

@private
@constant
def to_bytes(value: uint256) -> bytes[8]:
    return slice(concat("", convert(value, bytes32)), start=24, len=8)

@public
def test_func(value: uint256):

   # log will be malformed
   log.TestLog(self.to_bytes(value))
   loggedValue: bytes[8] = self.to_bytes(value)

   # log will be normal
   log.TestLog(loggedValue)

    """
    c = get_contract_with_gas_estimation(code)

    tx_hash = c.test_func(123, transact={})
    logs = get_logs(tx_hash, c, 'TestLog')

    for log in logs:
        print(log.args)

```
What is the exact logging statement that is failing?
@jacqueswww Could you try this test case ? this may be able to reproduce the error accurately, this is 
the same log structure in the contract I used

```py
 TestLog: event({testData1: bytes32,testData2: bytes[2064], testData3: bytes[8] })

@public
@constant
def to_bytes(value: uint256) -> bytes[8]:
    return slice(concat("", convert(value, bytes32)), start=24, len=8)

@private
@ constant
def to_bytes32(value: uint256) -> bytes32:
    return convert(value, bytes32)

@public
def test_func(value: uint256):

data2: bytes[2064] = concat(self.to_bytes32(value),self.to_bytes(value),"testing")

  # log will be malformed
   log.TestLog(self.to_bytes32(value),data2,self.to_bytes(value))
   
   loggedValue: bytes32 = self.to_bytes32(value)   
   loggedValue2: bytes[8] = self.to_bytes(value)

   # log will be normal
   log.TestLog(loggedValue,data2,loggedValue2)

```
if the error still doesn't come up then maybe its an issue on my end


@nisdas this test case does fail, will investigate.

Resolving patch:
diff --git a/vyper/parser/expr.py b/vyper/parser/expr.py
--- a/vyper/parser/expr.py
+++ b/vyper/parser/expr.py
@@ -289,7 +289,7 @@ def arithmetic_get_reference(self, item):
         if item_lll.typ is None:
             raise TypeMismatchException('Arithmetic can not be performed on None (return) type.', self.expr)
         if isinstance(item, ast.Call):
-            # We only want to perform call statements once.
+            # Perform call statements once.
             placeholder = self.context.new_placeholder(item_lll.typ)
             pre_alloc = ['mstore', placeholder, item_lll]
             return pre_alloc, LLLnode.from_list(['mload', placeholder], location='memory', typ=item_lll.typ)
diff --git a/vyper/parser/parser.py b/vyper/parser/parser.py
--- a/vyper/parser/parser.py
+++ b/vyper/parser/parser.py
@@ -652,22 +652,18 @@ def pack_args_by_32(holder, maxlen, arg, typ, context, placeholder,
     """
 
     if isinstance(typ, BaseType):
-        value = parse_expr(arg, context)
-        value = base_type_conversion(value, value.typ, typ, pos)
+        if isinstance(arg, LLLnode):
+            value = unwrap_location(arg)
+        else:
+            value = parse_expr(arg, context)
+            value = base_type_conversion(value, value.typ, typ, pos)
         holder.append(LLLnode.from_list(['mstore', placeholder, value], typ=typ, location='memory'))
     elif isinstance(typ, ByteArrayType):
-        bytez = b''
-
-        source_expr = Expr(arg, context)
-        if isinstance(arg, ast.Str):
-            if len(arg.s) > typ.maxlen:
-                raise TypeMismatchException("Data input bytes are to big: %r %r" % (len(arg.s), typ), pos)
-            for c in arg.s:
-                if ord(c) >= 256:
-                    raise InvalidLiteralException("Cannot insert special character %r into byte array" % c, pos)
-                bytez += bytes([ord(c)])
 
-            holder.append(source_expr.lll_node)
+        if isinstance(arg, LLLnode):  # Is prealloacted variable.
+            source_lll = arg
+        else:
+            source_lll = parse_expr(arg, context)
 
         # Set static offset, in arg slot.
         holder.append(LLLnode.from_list(['mstore', placeholder, ['mload', dynamic_offset_counter]]))
@@ -675,10 +671,10 @@ def pack_args_by_32(holder, maxlen, arg, typ, context, placeholder,
         dest_placeholder = LLLnode.from_list(
             ['add', datamem_start, ['mload', dynamic_offset_counter]],
             typ=typ, location='memory', annotation="pack_args_by_32:dest_placeholder")
-        copier = make_byte_array_copier(dest_placeholder, source_expr.lll_node, pos=pos)
+        copier = make_byte_array_copier(dest_placeholder, source_lll, pos=pos)
         holder.append(copier)
         # Add zero padding.
-        new_maxlen = ceil32(source_expr.lll_node.typ.maxlen)
+        new_maxlen = ceil32(source_lll.typ.maxlen)
 
         holder.append(
             ['with', '_bytearray_loc', dest_placeholder,
@@ -688,10 +684,12 @@ def pack_args_by_32(holder, maxlen, arg, typ, context, placeholder,
                             ['if', ['ge', ['mload', zero_pad_i], new_maxlen], 'break'],  # stay within allocated bounds
                             ['mstore8', ['add', ['add', '_bytearray_loc', 32], ['mload', zero_pad_i]], 0]]]]]
         )
+
         # Increment offset counter.
         increment_counter = LLLnode.from_list(
             ['mstore', dynamic_offset_counter,
-                ['add', ['add', ['mload', dynamic_offset_counter], ['ceil32', ['mload', dest_placeholder]]], 32]]
+                ['add', ['add', ['mload', dynamic_offset_counter], ['ceil32', ['mload', dest_placeholder]]], 32]],
+            annotation='Increment dynamic offset counter'
         )
         holder.append(increment_counter)
     elif isinstance(typ, ListType):
@@ -709,27 +707,32 @@ def check_list_type_match(provided):  # Check list types match.
             stor_list = context.globals[arg.attr]
             check_list_type_match(stor_list.typ.subtype)
             size = stor_list.typ.count
-            for offset in range(0, size):
-                arg2 = LLLnode.from_list(['sload', ['add', ['sha3_32', Expr(arg, context).lll_node], offset]],
+            mem_offset = 0
+            for i in range(0, size):
+                storage_offset = i
+                arg2 = LLLnode.from_list(['sload', ['add', ['sha3_32', Expr(arg, context).lll_node], storage_offset]],
                                          typ=typ)
-                p_holder = context.new_placeholder(BaseType(32)) if offset > 0 else placeholder
-                holder, maxlen = pack_args_by_32(holder, maxlen, arg2, typ, context, p_holder, pos=pos)
+                holder, maxlen = pack_args_by_32(holder, maxlen, arg2, typ, context, placeholder + mem_offset, pos=pos)
+                mem_offset += get_size_of_type(typ) * 32
+
         # List from variable.
         elif isinstance(arg, ast.Name):
             size = context.vars[arg.id].size
             pos = context.vars[arg.id].pos
             check_list_type_match(context.vars[arg.id].typ.subtype)
+            mem_offset = 0
             for i in range(0, size):
-                offset = 32 * i
-                arg2 = LLLnode.from_list(pos + offset, typ=typ, location='memory')
-                p_holder = context.new_placeholder(BaseType(32)) if i > 0 else placeholder
-                holder, maxlen = pack_args_by_32(holder, maxlen, arg2, typ, context, p_holder, pos=pos)
-        # is list literal.
-        else:
-            holder, maxlen = pack_args_by_32(holder, maxlen, arg.elts[0], typ, context, placeholder, pos=pos)
-            for j, arg2 in enumerate(arg.elts[1:]):
-                holder, maxlen = pack_args_by_32(holder, maxlen, arg2, typ, context, context.new_placeholder(BaseType(32)), pos=pos)
+                arg2 = LLLnode.from_list(pos + mem_offset, typ=typ, location='memory')
+                # p_holder = context.new_placeholder(BaseType(32)) if i > 0 else placeholder
+                holder, maxlen = pack_args_by_32(holder, maxlen, arg2, typ, context, placeholder + mem_offset, pos=pos)
+                mem_offset += get_size_of_type(typ) * 32
 
+        # List from list literal.
+        else:
+            mem_offset = 0
+            for i, arg2 in enumerate(arg.elts):
+                holder, maxlen = pack_args_by_32(holder, maxlen, arg2, typ, context, placeholder + mem_offset, pos=pos)
+                mem_offset += get_size_of_type(typ) * 32
     return holder, maxlen
 
 
@@ -741,6 +744,33 @@ def pack_logging_data(expected_data, args, context, pos):
     holder = ['seq']
     maxlen = len(args) * 32  # total size of all packed args (upper limit)
 
+    # Unroll any function calls, to temp variables.
+    prealloacted = {}
+    for idx, (arg, expected_arg) in enumerate(zip(args, expected_data)):
+
+        if isinstance(arg, (ast.Str, ast.Call)):
+            expr = Expr(arg, context)
+            source_lll = expr.lll_node
+            typ = source_lll.typ
+
+            if isinstance(arg, ast.Str):
+                if len(arg.s) > typ.maxlen:
+                    raise TypeMismatchException("Data input bytes are to big: %r %r" % (len(arg.s), typ), pos)
+
+            tmp_variable = context.new_variable('_log_pack_var_%i_%i' % (arg.lineno, arg.col_offset), source_lll.typ)
+            tmp_variable_node = LLLnode.from_list(
+                tmp_variable, typ=source_lll.typ,
+                pos=getpos(arg), location="memory", annotation='log_prealloacted %r' % source_lll.typ
+            )
+            # Store len.
+            # holder.append(['mstore', len_placeholder, ['mload', unwrap_location(source_lll)]])
+            # Copy bytes.
+
+            holder.append(
+                make_setter(tmp_variable_node, source_lll, pos=getpos(arg), location='memory')
+            )
+            prealloacted[idx] = tmp_variable_node
+
     requires_dynamic_offset = any([isinstance(data.typ, ByteArrayType) for data in expected_data])
     if requires_dynamic_offset:
         zero_pad_i = context.new_placeholder(BaseType('uint256'))  # Iterator used to zero pad memory.
@@ -750,14 +780,22 @@ def pack_logging_data(expected_data, args, context, pos):
         dynamic_offset_counter = None
         zero_pad_i = None
 
-    # Populate static placeholders.
+    # Create placeholder for static args. Note: order of new_*() is important.
     placeholder_map = {}
     for i, (arg, data) in enumerate(zip(args, expected_data)):
         typ = data.typ
-        placeholder = context.new_placeholder(BaseType(32))
+        if not isinstance(typ, ByteArrayType):
+            placeholder = context.new_placeholder(typ)
+        else:
+            placeholder = context.new_placeholder(BaseType(32))
         placeholder_map[i] = placeholder
+
+    # Populate static placeholders.
+    for i, (arg, data) in enumerate(zip(args, expected_data)):
+        typ = data.typ
+        placeholder = placeholder_map[i]
         if not isinstance(typ, ByteArrayType):
-            holder, maxlen = pack_args_by_32(holder, maxlen, arg, typ, context, placeholder, zero_pad_i=zero_pad_i, pos=pos)
+            holder, maxlen = pack_args_by_32(holder, maxlen, prealloacted.get(i, arg), typ, context, placeholder, zero_pad_i=zero_pad_i, pos=pos)
 
     # Dynamic position starts right after the static args.
     if requires_dynamic_offset:
@@ -781,7 +819,7 @@ def pack_logging_data(expected_data, args, context, pos):
             pack_args_by_32(
                 holder=holder,
                 maxlen=maxlen,
-                arg=arg,
+                arg=prealloacted.get(i, arg),
                 typ=typ,
                 context=context,
                 placeholder=placeholder_map[i],


Test patch:
diff --git a/tests/parser/features/test_logging_from_call.py b/tests/parser/features/test_logging_from_call.py
new file mode 100644
--- /dev/null
+++ b/tests/parser/features/test_logging_from_call.py
@@ -0,0 +1,155 @@
+
+def test_log_dynamic_static_combo(get_logs, get_contract_with_gas_estimation, w3):
+    code = """
+TestLog: event({testData1: bytes32,testData2: bytes[60], testData3: bytes[8]})
+
+@public
+@constant
+def to_bytes(value: uint256) -> bytes[8]:
+    return slice(concat("", convert(value, bytes32)), start=24, len=8)
+
+@public
+@constant
+def to_bytes32(value: uint256) -> bytes32:
+    return convert(value, bytes32)
+
+@public
+def test_func(value: uint256):
+    data2: bytes[60] = concat(self.to_bytes32(value),self.to_bytes(value),"testing")
+    log.TestLog(self.to_bytes32(value), data2, self.to_bytes(value))
+
+    loggedValue: bytes32 = self.to_bytes32(value)
+    loggedValue2: bytes[8] = self.to_bytes(value)
+    log.TestLog(loggedValue, data2, loggedValue2)
+    """
+
+    c = get_contract_with_gas_estimation(code)
+
+    tx_hash = c.test_func(123, transact={})
+
+    logs = get_logs(tx_hash, c, 'TestLog')
+
+    log = logs[0].args
+    assert w3.toInt(log.testData1) == 123
+    assert w3.toInt(log.testData2[:32]) == 123
+    assert log.testData2[-7:] == b'testing'
+    assert log.testData2[32:] == b'\x00\x00\x00\x00\x00\x00\x00{testing'
+    assert log.testData3 == b'\x00\x00\x00\x00\x00\x00\x00{'
+    assert w3.toInt(log.testData3) == 123
+
+    assert logs[0].args == logs[1].args
+
+
+def test_log_dynamic_static_combo2(get_logs, get_contract, w3):
+    code = """
+TestLog: event({testData1: bytes32,testData2: bytes[133], testData3: bytes[8] })
+
+@public
+@constant
+def to_bytes(value: uint256) -> bytes[8]:
+    return slice(concat("", convert(value, bytes32)), start=24, len=8)
+
+@public
+@constant
+def to_bytes32(value: uint256) -> bytes32:
+    return convert(value, bytes32)
+
+@public
+def test_func(value: uint256,input: bytes[133]):
+
+    data2: bytes[200] = "hello world"
+
+    # log.TestLog(self.to_bytes32(value),input,self.to_bytes(value))
+    log.TestLog(self.to_bytes32(value),input,"bababa")
+    """
+
+    c = get_contract(code)
+
+    # assert c.test_func(2**255, b'x' * 129, call={}) == b'x' * 129
+    tx_hash = c.test_func(1234444, b'x' * 129, transact={})
+    tx_receipt = w3.eth.getTransactionReceipt(tx_hash)
+    print(tx_receipt)
+    logs = get_logs(tx_hash, c, 'TestLog')
+
+    print(logs[0].args)
+
+    assert w3.toInt(logs[0].args.testData1) == 1234444
+    assert logs[0].args.testData3 == b"bababa"
+    assert logs[0].args.testData2 == b'x' * 129
+
+
+def test_log_single_function_call(get_logs, get_contract, w3):
+    code = """
+TestLog: event({testData1: bytes32, testData2: bytes[133]})
+
+@public
+@constant
+def to_bytes32(value: uint256) -> bytes32:
+    return convert(value, bytes32)
+
+@public
+def test_func(value: uint256,input: bytes[133]):
+
+    data2: bytes[200] = "hello world"
+
+    # log will be malformed
+    # log.TestLog(self.to_bytes32(value),input,self.to_bytes(value))
+    log.TestLog(self.to_bytes32(value), input)
+    """
+
+    c = get_contract(code)
+
+    tx_hash = c.test_func(1234444, b'x' * 129, transact={})
+    logs = get_logs(tx_hash, c, 'TestLog')
+
+    assert w3.toInt(logs[0].args.testData1) == 1234444
+    assert logs[0].args.testData2 == b'x' * 129
+
+
+def test_original_problem_function(get_logs, get_contract, w3):
+    # See #1205 for further details, this is kept as test case as it introduces very specific
+    # edge cases to the ABI encoder when logging.
+    code = """
+TestLog: event({testData1: bytes32,testData2: bytes[2064], testData3: bytes[8] })
+
+@public
+@constant
+def to_bytes(value: uint256) -> bytes[8]:
+    return slice(concat("", convert(value, bytes32)), start=24, len=8)
+
+@private
+@ constant
+def to_bytes32(value: uint256) -> bytes32:
+    return convert(value, bytes32)
+
+@public
+def test_func(value: uint256,input: bytes[2048]):
+
+    data2: bytes[2064] = concat(self.to_bytes(value),self.to_bytes(value),input)
+
+    # log will be malformed
+    log.TestLog(self.to_bytes32(value), data2, self.to_bytes(value))
+
+    loggedValue: bytes[8] = self.to_bytes(value)
+
+    # log will be normal
+    log.TestLog(self.to_bytes32(value),data2,loggedValue)
+    """
+
+    c = get_contract(code)
+
+    tx_hash = c.test_func(333, b'x' * 132, transact={})
+    tx_receipt = w3.eth.getTransactionReceipt(tx_hash)
+    print(tx_receipt)
+    logs = get_logs(tx_hash, c, 'TestLog')
+
+    print(logs[0].args)
+
+    assert w3.toInt(logs[0].args.testData1) == 333
+
+    assert w3.toInt(logs[0].args.testData2[0:8]) == 333
+    assert w3.toInt(logs[0].args.testData2[8:16]) == 333
+    assert logs[0].args.testData2[16:] == b'x' * 132
+    assert w3.toInt(logs[0].args.testData3) == 333
+
+    assert logs[0].args == logs[1].args