Repository: vyperlang/vyper

Issue:
Dynamic arrays are not allocated properly in storage, overlap with following variables
### Version Information

* vyper Version (output of `vyper --version`): 0.3.2
* OS: Release Linux Mint 20.3 Una 64-bit
* Python Version (output of `python --version`): Python 3.8.10

### What's your issue about?

Dynamic arrays are not being allocated properly in storage.  Specifically, whatever variable comes after a dynamic array will not, in fact, be placed after it, but rather will overlap with the final storage slot of the dynamic array.  This is obviously really bad!

For example, consider the following contract:

```vyper
arr: DynArray[uint256[2], 3]
interfere: uint256

@external
def run() -> uint256:
    self.arr.append([1, 2])
    self.arr.append([3, 4])
    self.arr.append([5, 6])
    self.interfere = 7
    return self.arr[2][1]
```

If you deploy this contract and then run the `run` method, then the storage afterward should look like this:

```
  0000000000000000000000000000000000000000000000000000000000000003
  0000000000000000000000000000000000000000000000000000000000000001
  0000000000000000000000000000000000000000000000000000000000000002
  0000000000000000000000000000000000000000000000000000000000000003
  0000000000000000000000000000000000000000000000000000000000000004
  0000000000000000000000000000000000000000000000000000000000000005
  0000000000000000000000000000000000000000000000000000000000000006
  0000000000000000000000000000000000000000000000000000000000000007
```

Instead, we have this:

```
  0000000000000000000000000000000000000000000000000000000000000003
  0000000000000000000000000000000000000000000000000000000000000001
  0000000000000000000000000000000000000000000000000000000000000002
  0000000000000000000000000000000000000000000000000000000000000003
  0000000000000000000000000000000000000000000000000000000000000004
  0000000000000000000000000000000000000000000000000000000000000005
  0000000000000000000000000000000000000000000000000000000000000007
```

Moreover, if (again, after a clean deploy) you do it as a call rather than as a transaction, you'll see that you get a return value of 7, rather than 6, as you should.

I'm hoping there can be some sort of hotfix for this, for obvious reasons!  Thank you!


Discussion:
Looking into this

Resolving patch:
diff --git a/vyper/semantics/types/indexable/sequence.py b/vyper/semantics/types/indexable/sequence.py
--- a/vyper/semantics/types/indexable/sequence.py
+++ b/vyper/semantics/types/indexable/sequence.py
@@ -162,7 +162,8 @@ def is_dynamic_size(self):
     # TODO rename me to memory_bytes_required
     @property
     def size_in_bytes(self):
-        return self.value_type.size_in_bytes * self.length
+        # one length word + size of the array items
+        return 32 + self.value_type.size_in_bytes * self.length
 
     def validate_index_type(self, node):
         if isinstance(node, vy_ast.Int):


Test patch:
diff --git a/tests/cli/outputs/test_storage_layout.py b/tests/cli/outputs/test_storage_layout.py
--- a/tests/cli/outputs/test_storage_layout.py
+++ b/tests/cli/outputs/test_storage_layout.py
@@ -21,6 +21,8 @@ def public_foo2():
 def _bar():
     pass
 
+arr: DynArray[uint256, 3]
+
 # mix it up a little
 baz: Bytes[65]
 bar: uint256
@@ -49,6 +51,11 @@ def public_foo3():
             "location": "storage",
             "slot": 2,
         },
-        "baz": {"type": "Bytes[65]", "location": "storage", "slot": 3},
-        "bar": {"type": "uint256", "location": "storage", "slot": 7},
+        "arr": {
+            "type": "DynArray[uint256, 3]",
+            "location": "storage",
+            "slot": 3,
+        },
+        "baz": {"type": "Bytes[65]", "location": "storage", "slot": 7},
+        "bar": {"type": "uint256", "location": "storage", "slot": 11},
     }
diff --git a/tests/functional/context/types/test_size_in_bytes.py b/tests/functional/context/types/test_size_in_bytes.py
--- a/tests/functional/context/types/test_size_in_bytes.py
+++ b/tests/functional/context/types/test_size_in_bytes.py
@@ -27,6 +27,16 @@ def test_array_value_types(build_node, type_str, location, length, size):
     assert type_definition.size_in_bytes == size
 
 
+@pytest.mark.parametrize("type_str", BASE_TYPES)
+@pytest.mark.parametrize("location", LOCATIONS)
+@pytest.mark.parametrize("length", range(1, 4))
+def test_dynamic_array_lengths(build_node, type_str, location, length):
+    node = build_node(f"DynArray[{type_str}, {length}]")
+    type_definition = get_type_from_annotation(node, location)
+
+    assert type_definition.size_in_bytes == 32 + length * 32
+
+
 @pytest.mark.parametrize("type_str", BASE_TYPES)
 @pytest.mark.parametrize("location", LOCATIONS)
 @pytest.mark.parametrize("length", range(1, 4))
diff --git a/tests/functional/test_storage_slots.py b/tests/functional/test_storage_slots.py
--- a/tests/functional/test_storage_slots.py
+++ b/tests/functional/test_storage_slots.py
@@ -14,6 +14,7 @@
 c: public(Bytes[32])
 d: public(int128[4])
 foo: public(HashMap[uint256, uint256[3]])
+dyn_array: DynArray[uint256, 3]
 e: public(String[47])
 f: public(int256[1])
 g: public(StructTwo[2])
@@ -36,6 +37,7 @@ def __init__():
             c: "whatifthisstringtakesuptheentirelengthwouldthatbesobadidothinkso"
         })
     ]
+    self.dyn_array = [1, 2, 3]
     self.h =  [123456789]
     self.foo[0] = [987, 654, 321]
     self.foo[1] = [123, 456, 789]