Repository: vyperlang/vyper

Issue:
Fix: Support for logging lists from storage
### What's your issue about?
Currently code that retrieves a list from storage and echoes it out to a log statement throws a compile error.
```python
Bar: __log__({_value: num[4]})
x: num[4]

@public
def foo():
    log.Bar(self.x)
```

### How can it be fixed?

Check `pack_args_by_32` function and add support for it :smile: 

Thanks goes out to @yzhang90 for finding it.

#### Cute Animal Picture
![](https://i.pinimg.com/originals/35/83/1e/35831ea13d7675f139eccb52a6d655d2.jpg)


Discussion:
Please also take a look at byte array variable, the storage case is not handled there as well.
e.g.,
```
MyLog: __log__({arg1: bytes <= 29})
x:bytes<=5

@public
def foo(a:num):
    log.MyLog(self.x)
```
is compiled to
```
....
 [seq,
    [seq, [mstore, 352, [mload, [add, 0 <self.x>, 32]]]],
       [log1,
           352,
           32,
           47626619434344074294924079437217749349871914541104903996581518933486060951077]]
```

I expect to see something like `sha3_32` in the lll code.
On it.

Resolving patch:
diff --git a/viper/parser/parser.py b/viper/parser/parser.py
--- a/viper/parser/parser.py
+++ b/viper/parser/parser.py
@@ -273,7 +273,7 @@ def __init__(self, vars=None, globals=None, sigs=None, forvars=None, return_type
         self.placeholder_count = 1
         # Original code (for error pretty-printing purposes)
         self.origcode = origcode
-        # In Loop status. Wether body is currently evaluating within a for-loop or not.
+        # In Loop status. Whether body is currently evaluating within a for-loop or not.
         self.in_for_loop = set()
 
     def set_in_for_loop(self, name_of_list):
@@ -364,7 +364,7 @@ def parse_other_functions(o, otherfuncs, _globals, sigs, external_contracts, ori
 def parse_tree_to_lll(code, origcode, runtime_only=False):
     _contracts, _events, _defs, _globals = get_contracts_and_defs_and_globals(code)
     _names = [_def.name for _def in _defs] + [_event.target.id for _event in _events]
-    # Checks for duplicate funciton / event names
+    # Checks for duplicate function / event names
     if len(set(_names)) < len(_names):
         raise VariableDeclarationException("Duplicate function or event name: %s" % [name for name in _names if _names.count(name) > 1][0])
     # Initialization function
@@ -681,8 +681,15 @@ def pack_args_by_32(holder, maxlen, arg, typ, context, placeholder):
         holder.append(LLLnode.from_list(['mstore', placeholder, value], typ=typ, location='memory'))
     elif isinstance(typ, ByteArrayType):
         bytez = b''
+        # Bytes from Storage
+        if isinstance(arg, ast.Attribute) and arg.value.id == 'self':
+            stor_bytes = context.globals[arg.attr]
+            if stor_bytes.typ.maxlen > 32:
+                    raise TypeMismatchException("Can only log a maximum of 32 bytes at a time.")
+            arg2 = LLLnode.from_list(['sload', ['add', ['sha3_32', Expr(arg, context).lll_node], 1]], typ=BaseType(32))
+            holder, maxlen = pack_args_by_32(holder, maxlen, arg2, BaseType(32), context, context.new_placeholder(BaseType(32)))
         # String literals
-        if isinstance(arg, ast.Str):
+        elif isinstance(arg, ast.Str):
             if len(arg.s) > typ.maxlen:
                 raise TypeMismatchException("Data input bytes are to big: %r %r" % (len(arg.s), typ))
             for c in arg.s:
@@ -703,14 +710,32 @@ def pack_args_by_32(holder, maxlen, arg, typ, context, placeholder):
             maxlen += (typ.count - 1) * 32
             typ = typ.subtype
 
-            if isinstance(arg, ast.Name):
+            def check_list_type_match(provided):  # Check list types match.
+                if provided != typ:
+                    raise TypeMismatchException(
+                        "Log list type '%s' does not match provided, expected '%s'" % (provided, typ)
+                    )
+
+            # List from storage
+            if isinstance(arg, ast.Attribute) and arg.value.id == 'self':
+                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]],
+                                             typ=typ)
+                    holder, maxlen = pack_args_by_32(holder, maxlen, arg2, typ, context, context.new_placeholder(BaseType(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)
                 for i in range(0, size):
                     offset = 32 * i
                     arg2 = LLLnode.from_list(pos + offset, typ=typ, location='memory')
                     holder, maxlen = pack_args_by_32(holder, maxlen, arg2, typ, context, context.new_placeholder(BaseType(32)))
-            else:  # is list literal.
+            # is list literal.
+            else:
                 holder, maxlen = pack_args_by_32(holder, maxlen, arg.elts[0], typ, context, placeholder)
                 for j, arg2 in enumerate(arg.elts[1:]):
                     holder, maxlen = pack_args_by_32(holder, maxlen, arg2, typ, context, context.new_placeholder(BaseType(32)))


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
@@ -541,6 +541,29 @@ def foo():
     assert get_last_log(t, c)["_value"] == [1, 2, 3, 4]
 
 
+def test_storage_list_packing(t, get_last_log, bytes_helper, get_contract_with_gas_estimation, chain):
+    t.s = chain
+    code = """
+Bar: __log__({_value: num[4]})
+x: num[4]
+
+@public
+def foo():
+    log.Bar(self.x)
+
+@public
+def set_list():
+    self.x = [1, 2, 3, 4]
+    """
+    c = get_contract_with_gas_estimation(code)
+
+    c.foo()
+    assert get_last_log(t, c)["_value"] == [0, 0, 0, 0]
+    c.set_list()
+    c.foo()
+    assert get_last_log(t, c)["_value"] == [1, 2, 3, 4]
+
+
 def test_passed_list_packing(t, get_last_log, get_contract_with_gas_estimation, chain):
     t.s = chain
     code = """
@@ -570,3 +593,50 @@ def foo():
 
     c.foo()
     assert get_last_log(t, c)["_value"] == [1.11, 2.22, 3.33, 4.44]
+
+
+def test_storage_byte_packing(t, get_last_log, bytes_helper, get_contract_with_gas_estimation, chain):
+    t.s = chain
+    code = """
+MyLog: __log__({arg1: bytes <= 29})
+x:bytes<=5
+
+@public
+def foo(a:num):
+    log.MyLog(self.x)
+
+@public
+def setbytez():
+    self.x = 'hello'
+    """
+
+    c = get_contract_with_gas_estimation(code)
+
+    c.foo()
+    assert get_last_log(t, c)['arg1'] == bytes_helper('', 29)
+    c.setbytez()
+    c.foo()
+    assert get_last_log(t, c)['arg1'] == bytes_helper('hello', 29)
+
+
+def test_storage_decimal_list_packing(t, get_last_log, bytes_helper, get_contract_with_gas_estimation, chain):
+    t.s = chain
+    code = """
+Bar: __log__({_value: decimal[4]})
+x: decimal[4]
+
+@public
+def foo():
+    log.Bar(self.x)
+
+@public
+def set_list():
+    self.x = [1.33, 2.33, 3.33, 4.33]
+    """
+    c = get_contract_with_gas_estimation(code)
+
+    c.foo()
+    assert get_last_log(t, c)["_value"] == [0, 0, 0, 0]
+    c.set_list()
+    c.foo()
+    assert get_last_log(t, c)["_value"] == [1.33, 2.33, 3.33, 4.33]
diff --git a/tests/parser/syntax/test_logging.py b/tests/parser/syntax/test_logging.py
new file mode 100644
--- /dev/null
+++ b/tests/parser/syntax/test_logging.py
@@ -0,0 +1,46 @@
+import pytest
+from pytest import raises
+
+from viper import compiler
+from viper.exceptions import TypeMismatchException
+
+
+fail_list = [
+    """
+Bar: __log__({_value: num[4]})
+x: decimal[4]
+
+@public
+def foo():
+    log.Bar(self.x)
+    """,
+    """
+Bar: __log__({_value: num[4]})
+
+@public
+def foo():
+    x: decimal[4]
+    log.Bar(x)
+    """,
+    """
+# larger than 32 bytes logging.
+
+MyLog: __log__({arg1: bytes <= 29})
+x:bytes<=55
+
+@public
+def foo(a:num):
+    log.MyLog(self.x)
+    """
+]
+
+
+@pytest.mark.parametrize('bad_code', fail_list)
+def test_logging_fail(bad_code):
+
+    if isinstance(bad_code, tuple):
+        with raises(bad_code[1]):
+            compiler.compile(bad_code[0])
+    else:
+        with raises(TypeMismatchException):
+            compiler.compile(bad_code)