prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Infinite loop from multidimensional array in calldata
  ### Version Information
  
  * vyper Version (output of `vyper --version`): latest master
  * OS: linux
  * Python Version: `3.8.2`
  
  ### What's your issue about?
  
  Using a multidimensional array in the function inputs, where the length of the >=2nd dimension is 6 or more, causes an infinite loop when calling the function.
  
  For example, each of the following methods will compile but attempting to call them fails with out of gas:
  
  ```python
  @public
  def foo(a: uint256[1][6]):
      pass
  
  @public
  def bar(a: uint256[1][1][6]):
      pass
  ```
  
  For comparison, these methods execute as expected:
  
  ```python
  @public
  def foo(a: uint256[6][1]):
      pass
  
  @public
  def bar(a: uint256[100][5][5][5]):
      pass
  ```
  
  #### Some observations:
  
  * The length of the first array element has no effect.
  * The data type has no effect.
  * The location of the array within calldata, and total number of arguments, has no effect.
  * The number of dimensions, or dimension that exceeds a length of 5, has no effect.
  
  ### How can it be fixed?
  Unsure at this time.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `vyper/parser/arg_clamps.py`
  Content:
  ```
  1 import functools
  2 import uuid
  3 
  4 from vyper.parser.lll_node import LLLnode
  5 from vyper.types.types import (
  6     ByteArrayLike,
  7     ListType,
  8     get_size_of_type,
  9     is_base_type,
  10 )
  11 from vyper.utils import MemoryPositions
  12 
  13 
  14 def _mk_calldatacopy_copier(pos, sz, mempos):
  15     return ["calldatacopy", mempos, ["add", 4, pos], sz]
  16 
  17 
  18 def _mk_codecopy_copier(pos, sz, mempos):
  19     return ["codecopy", mempos, ["add", "~codelen", pos], sz]
  20 
  21 
  22 def make_arg_clamper(datapos, mempos, typ, is_init=False):
  23     """
  24     Clamps argument to type limits.
  25     """
  26 
  27     if not is_init:
  28         data_decl = ["calldataload", ["add", 4, datapos]]
  29         copier = functools.partial(_mk_calldatacopy_copier, mempos=mempos)
  30     else:
  31         data_decl = ["codeload", ["add", "~codelen", datapos]]
  32         copier = functools.partial(_mk_codecopy_copier, mempos=mempos)
  33     # Numbers: make sure they're in range
  34     if is_base_type(typ, "int128"):
  35         return LLLnode.from_list(
  36             [
  37                 "clamp",
  38                 ["mload", MemoryPositions.MINNUM],
  39                 data_decl,
  40                 ["mload", MemoryPositions.MAXNUM],
  41             ],
  42             typ=typ,
  43             annotation="checking int128 input",
  44         )
  45     # Booleans: make sure they're zero or one
  46     elif is_base_type(typ, "bool"):
  47         return LLLnode.from_list(
  48             ["uclamplt", data_decl, 2], typ=typ, annotation="checking bool input",
  49         )
  50     # Addresses: make sure they're in range
  51     elif is_base_type(typ, "address"):
  52         return LLLnode.from_list(
  53             ["uclamplt", data_decl, ["mload", MemoryPositions.ADDRSIZE]],
  54             typ=typ,
  55             annotation="checking address input",
  56         )
  57     # Bytes: make sure they have the right size
  58     elif isinstance(typ, ByteArrayLike):
  59         return LLLnode.from_list(
  60             [
  61                 "seq",
  62                 copier(data_decl, 32 + typ.maxlen),
  63                 ["assert", ["le", ["calldataload", ["add", 4, data_decl]], typ.maxlen]],
  64             ],
  65             typ=None,
  66             annotation="checking bytearray input",
  67         )
  68     # Lists: recurse
  69     elif isinstance(typ, ListType):
  70         if typ.count > 5 or (type(datapos) is list and type(mempos) is list):
  71             subtype_size = get_size_of_type(typ.subtype)
  72             i_incr = subtype_size * 32
  73 
  74             mem_to = subtype_size * 32 * (typ.count - 1)
  75             loop_label = f"_check_list_loop_{str(uuid.uuid4())}"
  76 
  77             offset = 288
  78             o = [
  79                 ["mstore", offset, 0],  # init loop
  80                 ["label", loop_label],
  81                 make_arg_clamper(
  82                     ["add", datapos, ["mload", offset]],
  83                     ["add", mempos, ["mload", offset]],
  84                     typ.subtype,
  85                     is_init,
  86                 ),
  87                 ["mstore", offset, ["add", ["mload", offset], i_incr]],
  88                 ["if", ["lt", ["mload", offset], mem_to], ["goto", loop_label]],
  89             ]
  90         else:
  91             o = []
  92             for i in range(typ.count):
  93                 offset = get_size_of_type(typ.subtype) * 32 * i
  94                 o.append(make_arg_clamper(datapos + offset, mempos + offset, typ.subtype, is_init))
  95         return LLLnode.from_list(["seq"] + o, typ=None, annotation="checking list input")
  96     # Otherwise don't make any checks
  97     else:
  98         return LLLnode.from_list("pass")
  99
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/vyper/parser/arg_clamps.py b/vyper/parser/arg_clamps.py
  --- a/vyper/parser/arg_clamps.py
  +++ b/vyper/parser/arg_clamps.py
  @@ -22,6 +22,22 @@
   def make_arg_clamper(datapos, mempos, typ, is_init=False):
       """
       Clamps argument to type limits.
  +
  +    Arguments
  +    ---------
  +    datapos : int | LLLnode
  +        Calldata offset of the value being clamped
  +    mempos : int | LLLnode
  +        Memory offset that the value is stored at during clamping
  +    typ : vyper.types.types.BaseType
  +        Type of the value
  +    is_init : bool, optional
  +        Boolean indicating if we are generating init bytecode
  +
  +    Returns
  +    -------
  +    LLLnode
  +        Arg clamper LLL
       """
   
       if not is_init:
  @@ -68,31 +84,45 @@
       # Lists: recurse
       elif isinstance(typ, ListType):
           if typ.count > 5 or (type(datapos) is list and type(mempos) is list):
  -            subtype_size = get_size_of_type(typ.subtype)
  -            i_incr = subtype_size * 32
  +            # find ultimate base type
  +            subtype = typ.subtype
  +            while hasattr(subtype, "subtype"):
  +                subtype = subtype.subtype
  +
  +            # make arg clamper for the base type
  +            offset = MemoryPositions.FREE_LOOP_INDEX
  +            clamper = make_arg_clamper(
  +                ["add", datapos, ["mload", offset]],
  +                ["add", mempos, ["mload", offset]],
  +                subtype,
  +                is_init,
  +            )
  +            if clamper.value == "pass":
  +                # no point looping if the base type doesn't require clamping
  +                return clamper
  +
  +            # loop the entire array at once, even if it's multidimensional
  +            type_size = get_size_of_type(typ)
  +            i_incr = get_size_of_type(subtype) * 32
   
  -            mem_to = subtype_size * 32 * (typ.count - 1)
  +            mem_to = type_size * 32
               loop_label = f"_check_list_loop_{str(uuid.uuid4())}"
   
  -            offset = 288
  -            o = [
  +            lll_node = [
                   ["mstore", offset, 0],  # init loop
                   ["label", loop_label],
  -                make_arg_clamper(
  -                    ["add", datapos, ["mload", offset]],
  -                    ["add", mempos, ["mload", offset]],
  -                    typ.subtype,
  -                    is_init,
  -                ),
  +                clamper,
                   ["mstore", offset, ["add", ["mload", offset], i_incr]],
                   ["if", ["lt", ["mload", offset], mem_to], ["goto", loop_label]],
               ]
           else:
  -            o = []
  +            lll_node = []
               for i in range(typ.count):
                   offset = get_size_of_type(typ.subtype) * 32 * i
  -                o.append(make_arg_clamper(datapos + offset, mempos + offset, typ.subtype, is_init))
  -        return LLLnode.from_list(["seq"] + o, typ=None, annotation="checking list input")
  +                lll_node.append(
  +                    make_arg_clamper(datapos + offset, mempos + offset, typ.subtype, is_init)
  +                )
  +        return LLLnode.from_list(["seq"] + lll_node, typ=None, annotation="checking list input")
       # Otherwise don't make any checks
       else:
           return LLLnode.from_list("pass")
  
verification_info: {"golden_diff": "diff --git a/vyper/parser/arg_clamps.py b/vyper/parser/arg_clamps.py\n--- a/vyper/parser/arg_clamps.py\n+++ b/vyper/parser/arg_clamps.py\n@@ -22,6 +22,22 @@\n def make_arg_clamper(datapos, mempos, typ, is_init=False):\n     \"\"\"\n     Clamps argument to type limits.\n+\n+    Arguments\n+    ---------\n+    datapos : int | LLLnode\n+        Calldata offset of the value being clamped\n+    mempos : int | LLLnode\n+        Memory offset that the value is stored at during clamping\n+    typ : vyper.types.types.BaseType\n+        Type of the value\n+    is_init : bool, optional\n+        Boolean indicating if we are generating init bytecode\n+\n+    Returns\n+    -------\n+    LLLnode\n+        Arg clamper LLL\n     \"\"\"\n \n     if not is_init:\n@@ -68,31 +84,45 @@\n     # Lists: recurse\n     elif isinstance(typ, ListType):\n         if typ.count > 5 or (type(datapos) is list and type(mempos) is list):\n-            subtype_size = get_size_of_type(typ.subtype)\n-            i_incr = subtype_size * 32\n+            # find ultimate base type\n+            subtype = typ.subtype\n+            while hasattr(subtype, \"subtype\"):\n+                subtype = subtype.subtype\n+\n+            # make arg clamper for the base type\n+            offset = MemoryPositions.FREE_LOOP_INDEX\n+            clamper = make_arg_clamper(\n+                [\"add\", datapos, [\"mload\", offset]],\n+                [\"add\", mempos, [\"mload\", offset]],\n+                subtype,\n+                is_init,\n+            )\n+            if clamper.value == \"pass\":\n+                # no point looping if the base type doesn't require clamping\n+                return clamper\n+\n+            # loop the entire array at once, even if it's multidimensional\n+            type_size = get_size_of_type(typ)\n+            i_incr = get_size_of_type(subtype) * 32\n \n-            mem_to = subtype_size * 32 * (typ.count - 1)\n+            mem_to = type_size * 32\n             loop_label = f\"_check_list_loop_{str(uuid.uuid4())}\"\n \n-            offset = 288\n-            o = [\n+            lll_node = [\n                 [\"mstore\", offset, 0],  # init loop\n                 [\"label\", loop_label],\n-                make_arg_clamper(\n-                    [\"add\", datapos, [\"mload\", offset]],\n-                    [\"add\", mempos, [\"mload\", offset]],\n-                    typ.subtype,\n-                    is_init,\n-                ),\n+                clamper,\n                 [\"mstore\", offset, [\"add\", [\"mload\", offset], i_incr]],\n                 [\"if\", [\"lt\", [\"mload\", offset], mem_to], [\"goto\", loop_label]],\n             ]\n         else:\n-            o = []\n+            lll_node = []\n             for i in range(typ.count):\n                 offset = get_size_of_type(typ.subtype) * 32 * i\n-                o.append(make_arg_clamper(datapos + offset, mempos + offset, typ.subtype, is_init))\n-        return LLLnode.from_list([\"seq\"] + o, typ=None, annotation=\"checking list input\")\n+                lll_node.append(\n+                    make_arg_clamper(datapos + offset, mempos + offset, typ.subtype, is_init)\n+                )\n+        return LLLnode.from_list([\"seq\"] + lll_node, typ=None, annotation=\"checking list input\")\n     # Otherwise don't make any checks\n     else:\n         return LLLnode.from_list(\"pass\")\n", "issue": "Infinite loop from multidimensional array in calldata\n### Version Information\r\n\r\n* vyper Version (output of `vyper --version`): latest master\r\n* OS: linux\r\n* Python Version: `3.8.2`\r\n\r\n### What's your issue about?\r\n\r\nUsing a multidimensional array in the function inputs, where the length of the >=2nd dimension is 6 or more, causes an infinite loop when calling the function.\r\n\r\nFor example, each of the following methods will compile but attempting to call them fails with out of gas:\r\n\r\n```python\r\n@public\r\ndef foo(a: uint256[1][6]):\r\n    pass\r\n\r\n@public\r\ndef bar(a: uint256[1][1][6]):\r\n    pass\r\n```\r\n\r\nFor comparison, these methods execute as expected:\r\n\r\n```python\r\n@public\r\ndef foo(a: uint256[6][1]):\r\n    pass\r\n\r\n@public\r\ndef bar(a: uint256[100][5][5][5]):\r\n    pass\r\n```\r\n\r\n#### Some observations:\r\n\r\n* The length of the first array element has no effect.\r\n* The data type has no effect.\r\n* The location of the array within calldata, and total number of arguments, has no effect.\r\n* The number of dimensions, or dimension that exceeds a length of 5, has no effect.\r\n\r\n### How can it be fixed?\r\nUnsure at this time.\n", "before_files": [{"content": "import functools\nimport uuid\n\nfrom vyper.parser.lll_node import LLLnode\nfrom vyper.types.types import (\n    ByteArrayLike,\n    ListType,\n    get_size_of_type,\n    is_base_type,\n)\nfrom vyper.utils import MemoryPositions\n\n\ndef _mk_calldatacopy_copier(pos, sz, mempos):\n    return [\"calldatacopy\", mempos, [\"add\", 4, pos], sz]\n\n\ndef _mk_codecopy_copier(pos, sz, mempos):\n    return [\"codecopy\", mempos, [\"add\", \"~codelen\", pos], sz]\n\n\ndef make_arg_clamper(datapos, mempos, typ, is_init=False):\n    \"\"\"\n    Clamps argument to type limits.\n    \"\"\"\n\n    if not is_init:\n        data_decl = [\"calldataload\", [\"add\", 4, datapos]]\n        copier = functools.partial(_mk_calldatacopy_copier, mempos=mempos)\n    else:\n        data_decl = [\"codeload\", [\"add\", \"~codelen\", datapos]]\n        copier = functools.partial(_mk_codecopy_copier, mempos=mempos)\n    # Numbers: make sure they're in range\n    if is_base_type(typ, \"int128\"):\n        return LLLnode.from_list(\n            [\n                \"clamp\",\n                [\"mload\", MemoryPositions.MINNUM],\n                data_decl,\n                [\"mload\", MemoryPositions.MAXNUM],\n            ],\n            typ=typ,\n            annotation=\"checking int128 input\",\n        )\n    # Booleans: make sure they're zero or one\n    elif is_base_type(typ, \"bool\"):\n        return LLLnode.from_list(\n            [\"uclamplt\", data_decl, 2], typ=typ, annotation=\"checking bool input\",\n        )\n    # Addresses: make sure they're in range\n    elif is_base_type(typ, \"address\"):\n        return LLLnode.from_list(\n            [\"uclamplt\", data_decl, [\"mload\", MemoryPositions.ADDRSIZE]],\n            typ=typ,\n            annotation=\"checking address input\",\n        )\n    # Bytes: make sure they have the right size\n    elif isinstance(typ, ByteArrayLike):\n        return LLLnode.from_list(\n            [\n                \"seq\",\n                copier(data_decl, 32 + typ.maxlen),\n                [\"assert\", [\"le\", [\"calldataload\", [\"add\", 4, data_decl]], typ.maxlen]],\n            ],\n            typ=None,\n            annotation=\"checking bytearray input\",\n        )\n    # Lists: recurse\n    elif isinstance(typ, ListType):\n        if typ.count > 5 or (type(datapos) is list and type(mempos) is list):\n            subtype_size = get_size_of_type(typ.subtype)\n            i_incr = subtype_size * 32\n\n            mem_to = subtype_size * 32 * (typ.count - 1)\n            loop_label = f\"_check_list_loop_{str(uuid.uuid4())}\"\n\n            offset = 288\n            o = [\n                [\"mstore\", offset, 0],  # init loop\n                [\"label\", loop_label],\n                make_arg_clamper(\n                    [\"add\", datapos, [\"mload\", offset]],\n                    [\"add\", mempos, [\"mload\", offset]],\n                    typ.subtype,\n                    is_init,\n                ),\n                [\"mstore\", offset, [\"add\", [\"mload\", offset], i_incr]],\n                [\"if\", [\"lt\", [\"mload\", offset], mem_to], [\"goto\", loop_label]],\n            ]\n        else:\n            o = []\n            for i in range(typ.count):\n                offset = get_size_of_type(typ.subtype) * 32 * i\n                o.append(make_arg_clamper(datapos + offset, mempos + offset, typ.subtype, is_init))\n        return LLLnode.from_list([\"seq\"] + o, typ=None, annotation=\"checking list input\")\n    # Otherwise don't make any checks\n    else:\n        return LLLnode.from_list(\"pass\")\n", "path": "vyper/parser/arg_clamps.py"}], "after_files": [{"content": "import functools\nimport uuid\n\nfrom vyper.parser.lll_node import LLLnode\nfrom vyper.types.types import (\n    ByteArrayLike,\n    ListType,\n    get_size_of_type,\n    is_base_type,\n)\nfrom vyper.utils import MemoryPositions\n\n\ndef _mk_calldatacopy_copier(pos, sz, mempos):\n    return [\"calldatacopy\", mempos, [\"add\", 4, pos], sz]\n\n\ndef _mk_codecopy_copier(pos, sz, mempos):\n    return [\"codecopy\", mempos, [\"add\", \"~codelen\", pos], sz]\n\n\ndef make_arg_clamper(datapos, mempos, typ, is_init=False):\n    \"\"\"\n    Clamps argument to type limits.\n\n    Arguments\n    ---------\n    datapos : int | LLLnode\n        Calldata offset of the value being clamped\n    mempos : int | LLLnode\n        Memory offset that the value is stored at during clamping\n    typ : vyper.types.types.BaseType\n        Type of the value\n    is_init : bool, optional\n        Boolean indicating if we are generating init bytecode\n\n    Returns\n    -------\n    LLLnode\n        Arg clamper LLL\n    \"\"\"\n\n    if not is_init:\n        data_decl = [\"calldataload\", [\"add\", 4, datapos]]\n        copier = functools.partial(_mk_calldatacopy_copier, mempos=mempos)\n    else:\n        data_decl = [\"codeload\", [\"add\", \"~codelen\", datapos]]\n        copier = functools.partial(_mk_codecopy_copier, mempos=mempos)\n    # Numbers: make sure they're in range\n    if is_base_type(typ, \"int128\"):\n        return LLLnode.from_list(\n            [\n                \"clamp\",\n                [\"mload\", MemoryPositions.MINNUM],\n                data_decl,\n                [\"mload\", MemoryPositions.MAXNUM],\n            ],\n            typ=typ,\n            annotation=\"checking int128 input\",\n        )\n    # Booleans: make sure they're zero or one\n    elif is_base_type(typ, \"bool\"):\n        return LLLnode.from_list(\n            [\"uclamplt\", data_decl, 2], typ=typ, annotation=\"checking bool input\",\n        )\n    # Addresses: make sure they're in range\n    elif is_base_type(typ, \"address\"):\n        return LLLnode.from_list(\n            [\"uclamplt\", data_decl, [\"mload\", MemoryPositions.ADDRSIZE]],\n            typ=typ,\n            annotation=\"checking address input\",\n        )\n    # Bytes: make sure they have the right size\n    elif isinstance(typ, ByteArrayLike):\n        return LLLnode.from_list(\n            [\n                \"seq\",\n                copier(data_decl, 32 + typ.maxlen),\n                [\"assert\", [\"le\", [\"calldataload\", [\"add\", 4, data_decl]], typ.maxlen]],\n            ],\n            typ=None,\n            annotation=\"checking bytearray input\",\n        )\n    # Lists: recurse\n    elif isinstance(typ, ListType):\n        if typ.count > 5 or (type(datapos) is list and type(mempos) is list):\n            # find ultimate base type\n            subtype = typ.subtype\n            while hasattr(subtype, \"subtype\"):\n                subtype = subtype.subtype\n\n            # make arg clamper for the base type\n            offset = MemoryPositions.FREE_LOOP_INDEX\n            clamper = make_arg_clamper(\n                [\"add\", datapos, [\"mload\", offset]],\n                [\"add\", mempos, [\"mload\", offset]],\n                subtype,\n                is_init,\n            )\n            if clamper.value == \"pass\":\n                # no point looping if the base type doesn't require clamping\n                return clamper\n\n            # loop the entire array at once, even if it's multidimensional\n            type_size = get_size_of_type(typ)\n            i_incr = get_size_of_type(subtype) * 32\n\n            mem_to = type_size * 32\n            loop_label = f\"_check_list_loop_{str(uuid.uuid4())}\"\n\n            lll_node = [\n                [\"mstore\", offset, 0],  # init loop\n                [\"label\", loop_label],\n                clamper,\n                [\"mstore\", offset, [\"add\", [\"mload\", offset], i_incr]],\n                [\"if\", [\"lt\", [\"mload\", offset], mem_to], [\"goto\", loop_label]],\n            ]\n        else:\n            lll_node = []\n            for i in range(typ.count):\n                offset = get_size_of_type(typ.subtype) * 32 * i\n                lll_node.append(\n                    make_arg_clamper(datapos + offset, mempos + offset, typ.subtype, is_init)\n                )\n        return LLLnode.from_list([\"seq\"] + lll_node, typ=None, annotation=\"checking list input\")\n    # Otherwise don't make any checks\n    else:\n        return LLLnode.from_list(\"pass\")\n", "path": "vyper/parser/arg_clamps.py"}]}