Repository: vyperlang/vyper

Issue:
CompilerPanic caused by using String[64] directly as HashMap key
### Version Information

* vyper Version (output of `vyper --version`): 0.2.7
* OS: OSX 10.15.6 (19G2021)
* Python Version (output of `python --version`): 3.6.8

### What's your issue about?

Full Error:

  >Error compiling: test.vy
  >vyper.exceptions.CompilerPanic: Number of arguments mismatched: add []
  >
  >This is an unhandled internal compiler error. Please create an issue on Github to notify the developers.
  >https://github.com/vyperlang/vyper/issues/new?template=bug.md

* what command you ran? `vyper test.vy`
* the code that caused the failure (see [this link](https://help.github.com/articles/basic-writing-and-formatting-syntax/) for help with formatting code)

```
struct Thing:
    name: String[64]

indexToThing: public(HashMap[uint256, Thing])
NameToStatus: public(HashMap[String[64], bool])

@external
def foo(_index: uint256):
    
    self.NameToStatus[self.indexToThing[_index].name] = True
    self.indexToThing[_index] = empty(Thing)
    
```

### Detailed steps to reproduce the issue
Save the code above as test.vy and run `vyper test.vy` to reproduce the issue.
### What was the result of the issue
The program failed to compile.
### What the expected behaviour is
It is expected to compile successfully.


### Temporary Fix

I stored `self.indexToThing[_index].name` in a variable and then used the variable as the key for self.NameToStatus instead of directly using `self.indexToThing[_index].name`
Working code:

```
struct Thing:
    name: String[64]

indexToThing: public(HashMap[uint256, Thing])
NameToStatus: public(HashMap[String[64], bool])


@external
def foo(_index: uint256):
    
    name: String[64] = self.indexToItem[_index].name
    self.NameToStatus[name] = True
    self.indexToThing[_index] = empty(Thing)

```


Discussion:

Resolving patch:
diff --git a/vyper/parser/expr.py b/vyper/parser/expr.py
--- a/vyper/parser/expr.py
+++ b/vyper/parser/expr.py
@@ -18,6 +18,7 @@
     add_variable_offset,
     get_number_as_fraction,
     getpos,
+    make_byte_array_copier,
     make_setter,
     unwrap_location,
 )
@@ -412,6 +413,29 @@ def parse_Subscript(self):
         sub = Expr.parse_variable_location(self.expr.value, self.context)
         if isinstance(sub.typ, (MappingType, ListType)):
             index = Expr.parse_value_expr(self.expr.slice.value, self.context)
+            if isinstance(index.typ, ByteArrayLike) and index.args[0].location == "storage":
+                # Special case - if the key value is a bytes-array type located in
+                # storage, we have to copy it to memory prior to calculating the hash
+                placeholder = self.context.new_internal_variable(index.typ)
+                placeholder_node = LLLnode.from_list(placeholder, typ=index.typ, location="memory")
+                copier = make_byte_array_copier(
+                    placeholder_node,
+                    LLLnode.from_list(index.args[0], typ=index.typ, location="storage"),
+                )
+                return LLLnode.from_list(
+                    [
+                        "seq",
+                        copier,
+                        [
+                            "sha3_64",
+                            sub,
+                            ["sha3", ["add", placeholder, 32], ["mload", placeholder]],
+                        ],
+                    ],
+                    typ=sub.typ.valuetype,
+                    pos=getpos(self.expr),
+                    location="storage",
+                )
         elif isinstance(sub.typ, TupleType):
             index = self.expr.slice.value.n
             if not 0 <= index < len(sub.typ.members):
diff --git a/vyper/parser/parser_utils.py b/vyper/parser/parser_utils.py
--- a/vyper/parser/parser_utils.py
+++ b/vyper/parser/parser_utils.py
@@ -348,9 +348,11 @@ def add_variable_offset(parent, key, pos, array_bounds_check=True):
                         ]
                     )
                 else:
-                    sub = LLLnode.from_list(
-                        ["sha3", ["add", key.args[0].value, 32], ["mload", key.args[0].value]]
-                    )
+                    value = key.args[0].value
+                    if value == "add":
+                        # special case, key is a bytes array within a tuple/struct
+                        value = key.args[0]
+                    sub = LLLnode.from_list(["sha3", ["add", value, 32], key])
         else:
             subtype = typ.valuetype
             sub = base_type_conversion(key, key.typ, typ.keytype, pos=pos)


Test patch:
diff --git a/tests/parser/features/test_bytes_map_keys.py b/tests/parser/features/test_bytes_map_keys.py
--- a/tests/parser/features/test_bytes_map_keys.py
+++ b/tests/parser/features/test_bytes_map_keys.py
@@ -104,3 +104,127 @@ def get_it3(key: Bytes[100000]) -> int128:
     assert c.get_it2() == 1069
     assert c.get_it3(b"a" * 33) == 1069
     assert c.get_it3(b"test") == 0
+
+
+def test_struct_bytes_key_memory(get_contract):
+    code = """
+struct Foo:
+    one: Bytes[5]
+    two: Bytes[100]
+
+a: HashMap[Bytes[100000], int128]
+
+@external
+def __init__():
+    self.a[b"hello"] = 1069
+    self.a[b"potato"] = 31337
+
+@external
+def get_one() -> int128:
+    b: Foo = Foo({one: b"hello", two: b"potato"})
+    return self.a[b.one]
+
+@external
+def get_two() -> int128:
+    b: Foo = Foo({one: b"hello", two: b"potato"})
+    return self.a[b.two]
+"""
+
+    c = get_contract(code)
+
+    assert c.get_one() == 1069
+    assert c.get_two() == 31337
+
+
+def test_struct_bytes_key_storage(get_contract):
+    code = """
+struct Foo:
+    one: Bytes[5]
+    two: Bytes[100]
+
+a: HashMap[Bytes[100000], int128]
+b: Foo
+
+@external
+def __init__():
+    self.a[b"hello"] = 1069
+    self.a[b"potato"] = 31337
+    self.b = Foo({one: b"hello", two: b"potato"})
+
+@external
+def get_one() -> int128:
+    return self.a[self.b.one]
+
+@external
+def get_two() -> int128:
+    return self.a[self.b.two]
+"""
+
+    c = get_contract(code)
+
+    assert c.get_one() == 1069
+    assert c.get_two() == 31337
+
+
+def test_bytes_key_storage(get_contract):
+    code = """
+
+a: HashMap[Bytes[100000], int128]
+b: Bytes[5]
+
+@external
+def __init__():
+    self.a[b"hello"] = 1069
+    self.b = b"hello"
+
+@external
+def get_storage() -> int128:
+    return self.a[self.b]
+"""
+
+    c = get_contract(code)
+
+    assert c.get_storage() == 1069
+
+
+def test_bytes_key_calldata(get_contract):
+    code = """
+
+a: HashMap[Bytes[100000], int128]
+
+
+@external
+def __init__():
+    self.a[b"hello"] = 1069
+
+@external
+def get_calldata(b: Bytes[5]) -> int128:
+    return self.a[b]
+"""
+
+    c = get_contract(code)
+
+    assert c.get_calldata(b"hello") == 1069
+
+
+def test_struct_bytes_hashmap_as_key_in_other_hashmap(get_contract):
+    code = """
+struct Thing:
+    name: Bytes[64]
+
+bar: public(HashMap[uint256, Thing])
+foo: public(HashMap[Bytes[64], uint256])
+
+@external
+def __init__():
+    self.foo[b"hello"] = 31337
+    self.bar[12] = Thing({name: b"hello"})
+
+@external
+def do_the_thing(_index: uint256) -> uint256:
+    return self.foo[self.bar[_index].name]
+    """
+
+    c = get_contract(code)
+
+    assert c.do_the_thing(12) == 31337