Repository: crytic/slither

Issue:
[Bug]: Assertion Error with no context of what code throws error. 
### Describe the issue:

Slither throws an Assertion Error with no context of what solidity triggers the error. 

I debugged slither to identify the line. The bug I am pointing out is two issues 

- [ ] thrown AssertionError gives no context regarding the contract
- [ ] IERC20[6] rejected as an argument being bassed in a tuple to `abi.decode`

### Code example to reproduce the issue:

```solidity
        _tokens = abi.decode(data, (IERC20[6]));
```

### Version:

0.9.3

### Relevant log output:

```shell
Traceback (most recent call last):
  File "/opt/slither/lib/python3.9/site-packages/slither/__main__.py", line 837, in main_impl
    ) = process_all(filename, args, detector_classes, printer_classes)
  File "/opt/slither/lib/python3.9/site-packages/slither/__main__.py", line 101, in process_all
    ) = process_single(compilation, args, detector_classes, printer_classes)
  File "/opt/slither/lib/python3.9/site-packages/slither/__main__.py", line 79, in process_single
    slither = Slither(target, ast_format=ast, **vars(args))
  File "/opt/slither/lib/python3.9/site-packages/slither/slither.py", line 135, in __init__
    self._init_parsing_and_analyses(kwargs.get("skip_analyze", False))
  File "/opt/slither/lib/python3.9/site-packages/slither/slither.py", line 155, in _init_parsing_and_analyses
    raise e
  File "/opt/slither/lib/python3.9/site-packages/slither/slither.py", line 151, in _init_parsing_and_analyses
    parser.analyze_contracts()
  File "/opt/slither/lib/python3.9/site-packages/slither/solc_parsing/slither_compilation_unit_solc.py", line 528, in analyze_contracts
    self._convert_to_slithir()
  File "/opt/slither/lib/python3.9/site-packages/slither/solc_parsing/slither_compilation_unit_solc.py", line 739, in _convert_to_slithir
    func.generate_slithir_and_analyze()
  File "/opt/slither/lib/python3.9/site-packages/slither/core/declarations/function.py", line 1761, in generate_slithir_and_analyze
    node.slithir_generation()
  File "/opt/slither/lib/python3.9/site-packages/slither/core/cfg/node.py", line 679, in slithir_generation
    self._irs = convert_expression(expression, self)
  File "/opt/slither/lib/python3.9/site-packages/slither/slithir/convert.py", line 137, in convert_expression
    visitor = ExpressionToSlithIR(expression, node)
  File "/opt/slither/lib/python3.9/site-packages/slither/visitors/slithir/expression_to_slithir.py", line 160, in __init__
    self._visit_expression(self.expression)
  File "/opt/slither/lib/python3.9/site-packages/slither/visitors/expression/expression.py", line 46, in _visit_expression
    self._visit_assignement_operation(expression)
  File "/opt/slither/lib/python3.9/site-packages/slither/visitors/expression/expression.py", line 102, in _visit_assignement_operation
    self._visit_expression(expression.expression_right)
  File "/opt/slither/lib/python3.9/site-packages/slither/visitors/expression/expression.py", line 52, in _visit_expression
    self._visit_call_expression(expression)
  File "/opt/slither/lib/python3.9/site-packages/slither/visitors/expression/expression.py", line 112, in _visit_call_expression
    self._visit_expression(arg)
  File "/opt/slither/lib/python3.9/site-packages/slither/visitors/expression/expression.py", line 82, in _visit_expression
    self._visit_tuple_expression(expression)
  File "/opt/slither/lib/python3.9/site-packages/slither/visitors/expression/expression.py", line 155, in _visit_tuple_expression
    self._visit_expression(e)
  File "/opt/slither/lib/python3.9/site-packages/slither/visitors/expression/expression.py", line 96, in _visit_expression
    self._post_visit(expression)
  File "/opt/slither/lib/python3.9/site-packages/slither/visitors/expression/expression.py", line 288, in _post_visit
    self._post_index_access(expression)
  File "/opt/slither/lib/python3.9/site-packages/slither/visitors/slithir/expression_to_slithir.py", line 415, in _post_index_access
    operation = Index(val, left, right, expression.type)
  File "/opt/slither/lib/python3.9/site-packages/slither/slithir/operations/index.py", line 21, in __init__
    assert is_valid_lvalue(left_variable) or left_variable == SolidityVariableComposed(
AssertionError
ERROR:root:Error in .
ERROR:root:Traceback (most recent call last):
  File "/opt/slither/lib/python3.9/site-packages/slither/__main__.py", line 837, in main_impl
    ) = process_all(filename, args, detector_classes, printer_classes)
  File "/opt/slither/lib/python3.9/site-packages/slither/__main__.py", line 101, in process_all
    ) = process_single(compilation, args, detector_classes, printer_classes)
  File "/opt/slither/lib/python3.9/site-packages/slither/__main__.py", line 79, in process_single
    slither = Slither(target, ast_format=ast, **vars(args))
  File "/opt/slither/lib/python3.9/site-packages/slither/slither.py", line 135, in __init__
    self._init_parsing_and_analyses(kwargs.get("skip_analyze", False))
  File "/opt/slither/lib/python3.9/site-packages/slither/slither.py", line 155, in _init_parsing_and_analyses
    raise e
  File "/opt/slither/lib/python3.9/site-packages/slither/slither.py", line 151, in _init_parsing_and_analyses
    parser.analyze_contracts()
  File "/opt/slither/lib/python3.9/site-packages/slither/solc_parsing/slither_compilation_unit_solc.py", line 528, in analyze_contracts
    self._convert_to_slithir()
  File "/opt/slither/lib/python3.9/site-packages/slither/solc_parsing/slither_compilation_unit_solc.py", line 739, in _convert_to_slithir
    func.generate_slithir_and_analyze()
  File "/opt/slither/lib/python3.9/site-packages/slither/core/declarations/function.py", line 1761, in generate_slithir_and_analyze
    node.slithir_generation()
  File "/opt/slither/lib/python3.9/site-packages/slither/core/cfg/node.py", line 679, in slithir_generation
    self._irs = convert_expression(expression, self)
  File "/opt/slither/lib/python3.9/site-packages/slither/slithir/convert.py", line 137, in convert_expression
    visitor = ExpressionToSlithIR(expression, node)
  File "/opt/slither/lib/python3.9/site-packages/slither/visitors/slithir/expression_to_slithir.py", line 160, in __init__
    self._visit_expression(self.expression)
  File "/opt/slither/lib/python3.9/site-packages/slither/visitors/expression/expression.py", line 46, in _visit_expression
    self._visit_assignement_operation(expression)
  File "/opt/slither/lib/python3.9/site-packages/slither/visitors/expression/expression.py", line 102, in _visit_assignement_operation
    self._visit_expression(expression.expression_right)
  File "/opt/slither/lib/python3.9/site-packages/slither/visitors/expression/expression.py", line 52, in _visit_expression
    self._visit_call_expression(expression)
  File "/opt/slither/lib/python3.9/site-packages/slither/visitors/expression/expression.py", line 112, in _visit_call_expression
    self._visit_expression(arg)
  File "/opt/slither/lib/python3.9/site-packages/slither/visitors/expression/expression.py", line 82, in _visit_expression
    self._visit_tuple_expression(expression)
  File "/opt/slither/lib/python3.9/site-packages/slither/visitors/expression/expression.py", line 155, in _visit_tuple_expression
    self._visit_expression(e)
  File "/opt/slither/lib/python3.9/site-packages/slither/visitors/expression/expression.py", line 96, in _visit_expression
    self._post_visit(expression)
  File "/opt/slither/lib/python3.9/site-packages/slither/visitors/expression/expression.py", line 288, in _post_visit
    self._post_index_access(expression)
  File "/opt/slither/lib/python3.9/site-packages/slither/visitors/slithir/expression_to_slithir.py", line 415, in _post_index_access
    operation = Index(val, left, right, expression.type)
  File "/opt/slither/lib/python3.9/site-packages/slither/slithir/operations/index.py", line 21, in __init__
    assert is_valid_lvalue(left_variable) or left_variable == SolidityVariableComposed(
AssertionError
```
```



Discussion:

Resolving patch:
diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py
--- a/slither/visitors/slithir/expression_to_slithir.py
+++ b/slither/visitors/slithir/expression_to_slithir.py
@@ -10,6 +10,7 @@
     Contract,
     EnumContract,
     EnumTopLevel,
+    Enum,
 )
 from slither.core.expressions import (
     AssignmentOperation,
@@ -405,10 +406,13 @@ def _post_index_access(self, expression: IndexAccess) -> None:
         right = get(expression.expression_right)
         operation: Operation
         # Left can be a type for abi.decode(var, uint[2])
-        if isinstance(left, Type):
+        if isinstance(left, (Type, Contract, Enum)):
             # Nested type are not yet supported by abi.decode, so the assumption
             # Is that the right variable must be a constant
             assert isinstance(right, Constant)
+            # Case for abi.decode(var, I[2]) where I is an interface/contract or an enum
+            if isinstance(left, (Contract, Enum)):
+                left = UserDefinedType(left)
             t = ArrayType(left, int(right.value))
             set_val(expression, t)
             return


Test patch:
diff --git a/tests/e2e/solc_parsing/test_ast_parsing.py b/tests/e2e/solc_parsing/test_ast_parsing.py
--- a/tests/e2e/solc_parsing/test_ast_parsing.py
+++ b/tests/e2e/solc_parsing/test_ast_parsing.py
@@ -428,6 +428,7 @@ def make_version(minor: int, patch_min: int, patch_max: int) -> List[str]:
     Test("user_defined_value_type/in_parenthesis-0.8.8.sol", ["0.8.8"] + make_version(8, 10, 15)),
     Test("user_defined_value_type/top-level-0.8.8.sol", ["0.8.8"] + make_version(8, 10, 15)),
     Test("user_defined_value_type/using-for-0.8.8.sol", ["0.8.8"] + make_version(8, 10, 15)),
+    Test("user_defined_value_type/abi-decode-fixed-array.sol", ["0.8.8"] + make_version(8, 10, 15)),
     Test("bytes_call.sol", ["0.8.12"]),
     Test("modifier_identifier_path.sol", VERSIONS_08),
     Test("free_functions/libraries_from_free.sol", ["0.8.12"]),
diff --git a/tests/e2e/solc_parsing/test_data/compile/user_defined_value_type/abi-decode-fixed-array.sol-0.8.10-compact.zip b/tests/e2e/solc_parsing/test_data/compile/user_defined_value_type/abi-decode-fixed-array.sol-0.8.10-compact.zip
new file mode 100644
Binary files /dev/null and b/tests/e2e/solc_parsing/test_data/compile/user_defined_value_type/abi-decode-fixed-array.sol-0.8.10-compact.zip differ
diff --git a/tests/e2e/solc_parsing/test_data/compile/user_defined_value_type/abi-decode-fixed-array.sol-0.8.11-compact.zip b/tests/e2e/solc_parsing/test_data/compile/user_defined_value_type/abi-decode-fixed-array.sol-0.8.11-compact.zip
new file mode 100644
Binary files /dev/null and b/tests/e2e/solc_parsing/test_data/compile/user_defined_value_type/abi-decode-fixed-array.sol-0.8.11-compact.zip differ
diff --git a/tests/e2e/solc_parsing/test_data/compile/user_defined_value_type/abi-decode-fixed-array.sol-0.8.12-compact.zip b/tests/e2e/solc_parsing/test_data/compile/user_defined_value_type/abi-decode-fixed-array.sol-0.8.12-compact.zip
new file mode 100644
Binary files /dev/null and b/tests/e2e/solc_parsing/test_data/compile/user_defined_value_type/abi-decode-fixed-array.sol-0.8.12-compact.zip differ
diff --git a/tests/e2e/solc_parsing/test_data/compile/user_defined_value_type/abi-decode-fixed-array.sol-0.8.13-compact.zip b/tests/e2e/solc_parsing/test_data/compile/user_defined_value_type/abi-decode-fixed-array.sol-0.8.13-compact.zip
new file mode 100644
Binary files /dev/null and b/tests/e2e/solc_parsing/test_data/compile/user_defined_value_type/abi-decode-fixed-array.sol-0.8.13-compact.zip differ
diff --git a/tests/e2e/solc_parsing/test_data/compile/user_defined_value_type/abi-decode-fixed-array.sol-0.8.14-compact.zip b/tests/e2e/solc_parsing/test_data/compile/user_defined_value_type/abi-decode-fixed-array.sol-0.8.14-compact.zip
new file mode 100644
Binary files /dev/null and b/tests/e2e/solc_parsing/test_data/compile/user_defined_value_type/abi-decode-fixed-array.sol-0.8.14-compact.zip differ
diff --git a/tests/e2e/solc_parsing/test_data/compile/user_defined_value_type/abi-decode-fixed-array.sol-0.8.15-compact.zip b/tests/e2e/solc_parsing/test_data/compile/user_defined_value_type/abi-decode-fixed-array.sol-0.8.15-compact.zip
new file mode 100644
Binary files /dev/null and b/tests/e2e/solc_parsing/test_data/compile/user_defined_value_type/abi-decode-fixed-array.sol-0.8.15-compact.zip differ
diff --git a/tests/e2e/solc_parsing/test_data/compile/user_defined_value_type/abi-decode-fixed-array.sol-0.8.8-compact.zip b/tests/e2e/solc_parsing/test_data/compile/user_defined_value_type/abi-decode-fixed-array.sol-0.8.8-compact.zip
new file mode 100644
Binary files /dev/null and b/tests/e2e/solc_parsing/test_data/compile/user_defined_value_type/abi-decode-fixed-array.sol-0.8.8-compact.zip differ
diff --git a/tests/e2e/solc_parsing/test_data/expected/user_defined_value_type/abi-decode-fixed-array.sol-0.8.10-compact.json b/tests/e2e/solc_parsing/test_data/expected/user_defined_value_type/abi-decode-fixed-array.sol-0.8.10-compact.json
new file mode 100644
--- /dev/null
+++ b/tests/e2e/solc_parsing/test_data/expected/user_defined_value_type/abi-decode-fixed-array.sol-0.8.10-compact.json
@@ -0,0 +1,7 @@
+{
+  "I": {},
+  "C": {
+    "test_decode_interface_array(bytes)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n",
+    "test_decode_enum_array(bytes)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
+  }
+}
\ No newline at end of file
diff --git a/tests/e2e/solc_parsing/test_data/expected/user_defined_value_type/abi-decode-fixed-array.sol-0.8.11-compact.json b/tests/e2e/solc_parsing/test_data/expected/user_defined_value_type/abi-decode-fixed-array.sol-0.8.11-compact.json
new file mode 100644
--- /dev/null
+++ b/tests/e2e/solc_parsing/test_data/expected/user_defined_value_type/abi-decode-fixed-array.sol-0.8.11-compact.json
@@ -0,0 +1,7 @@
+{
+  "I": {},
+  "C": {
+    "test_decode_interface_array(bytes)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n",
+    "test_decode_enum_array(bytes)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
+  }
+}
\ No newline at end of file
diff --git a/tests/e2e/solc_parsing/test_data/expected/user_defined_value_type/abi-decode-fixed-array.sol-0.8.12-compact.json b/tests/e2e/solc_parsing/test_data/expected/user_defined_value_type/abi-decode-fixed-array.sol-0.8.12-compact.json
new file mode 100644
--- /dev/null
+++ b/tests/e2e/solc_parsing/test_data/expected/user_defined_value_type/abi-decode-fixed-array.sol-0.8.12-compact.json
@@ -0,0 +1,7 @@
+{
+  "I": {},
+  "C": {
+    "test_decode_interface_array(bytes)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n",
+    "test_decode_enum_array(bytes)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
+  }
+}
\ No newline at end of file
diff --git a/tests/e2e/solc_parsing/test_data/expected/user_defined_value_type/abi-decode-fixed-array.sol-0.8.13-compact.json b/tests/e2e/solc_parsing/test_data/expected/user_defined_value_type/abi-decode-fixed-array.sol-0.8.13-compact.json
new file mode 100644
--- /dev/null
+++ b/tests/e2e/solc_parsing/test_data/expected/user_defined_value_type/abi-decode-fixed-array.sol-0.8.13-compact.json
@@ -0,0 +1,7 @@
+{
+  "I": {},
+  "C": {
+    "test_decode_interface_array(bytes)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n",
+    "test_decode_enum_array(bytes)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
+  }
+}
\ No newline at end of file
diff --git a/tests/e2e/solc_parsing/test_data/expected/user_defined_value_type/abi-decode-fixed-array.sol-0.8.14-compact.json b/tests/e2e/solc_parsing/test_data/expected/user_defined_value_type/abi-decode-fixed-array.sol-0.8.14-compact.json
new file mode 100644
--- /dev/null
+++ b/tests/e2e/solc_parsing/test_data/expected/user_defined_value_type/abi-decode-fixed-array.sol-0.8.14-compact.json
@@ -0,0 +1,7 @@
+{
+  "I": {},
+  "C": {
+    "test_decode_interface_array(bytes)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n",
+    "test_decode_enum_array(bytes)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
+  }
+}
\ No newline at end of file
diff --git a/tests/e2e/solc_parsing/test_data/expected/user_defined_value_type/abi-decode-fixed-array.sol-0.8.15-compact.json b/tests/e2e/solc_parsing/test_data/expected/user_defined_value_type/abi-decode-fixed-array.sol-0.8.15-compact.json
new file mode 100644
--- /dev/null
+++ b/tests/e2e/solc_parsing/test_data/expected/user_defined_value_type/abi-decode-fixed-array.sol-0.8.15-compact.json
@@ -0,0 +1,7 @@
+{
+  "I": {},
+  "C": {
+    "test_decode_interface_array(bytes)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n",
+    "test_decode_enum_array(bytes)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
+  }
+}
\ No newline at end of file
diff --git a/tests/e2e/solc_parsing/test_data/expected/user_defined_value_type/abi-decode-fixed-array.sol-0.8.8-compact.json b/tests/e2e/solc_parsing/test_data/expected/user_defined_value_type/abi-decode-fixed-array.sol-0.8.8-compact.json
new file mode 100644
--- /dev/null
+++ b/tests/e2e/solc_parsing/test_data/expected/user_defined_value_type/abi-decode-fixed-array.sol-0.8.8-compact.json
@@ -0,0 +1,7 @@
+{
+  "I": {},
+  "C": {
+    "test_decode_interface_array(bytes)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n",
+    "test_decode_enum_array(bytes)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
+  }
+}
\ No newline at end of file
diff --git a/tests/e2e/solc_parsing/test_data/user_defined_value_type/abi-decode-fixed-array.sol b/tests/e2e/solc_parsing/test_data/user_defined_value_type/abi-decode-fixed-array.sol
new file mode 100644
--- /dev/null
+++ b/tests/e2e/solc_parsing/test_data/user_defined_value_type/abi-decode-fixed-array.sol
@@ -0,0 +1,16 @@
+interface I {}
+enum A {a,b}
+
+contract C {
+    I[6] interfaceArray;
+    A[6] enumArray;
+
+    function test_decode_interface_array(bytes memory data) public {
+        interfaceArray = abi.decode(data, (I[6]));
+    }
+
+    function test_decode_enum_array(bytes memory data) public {
+        enumArray = abi.decode(data, (A[6]));
+    }
+
+}