Repository: crytic/slither

Issue:
support for parsing solidity assembly .slot 
### Describe the desired feature

```
contract XXX {
  mapping(address => uint256) private _counters;
  function xxxxxx() {
       assembly {
              mstore(xxx, _counters.slot)
          }
  }
}
```

Slither Output
```
slither.exceptions.SlitherException: unresolved reference to identifier _counters.slot
Error:
unresolved reference to identifier _counters.slot
```

When slither is paring the solidity code with interacting with storage slot, it throws the exception.


Discussion:
Maybe related: Referencing a function defined in a different function within the same assembly block crashes slither with the same "unresolved reference to identifier" error.

```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;

contract A {

    function test() internal pure {
        assembly {
            function asm_fn1() {
                revert(0, 0)
            }

            asm_fn1() // fine

            function asm_fn2() {
                asm_fn1() // crashes
            }
        }
    }
}
```

Error:
```
File "slither/slither/solc_parsing/yul/parse_yul.py", line 819, in parse_yul_identifier
    raise SlitherException(f"unresolved reference to identifier {name}")
slither.exceptions.SlitherException: unresolved reference to identifier asm_fn1
```
Am running into the same issue:

```solidity
  function getPackedBucketGlobalState(uint256 bucketId) internal view returns (uint256 packedGlobalState) {
        assembly {
            mstore(0x0, bucketId)
            mstore(0x20, _bucketGlobalState.slot)
            let slot := keccak256(0x0, 0x40)
            packedGlobalState := sload(slot)
        }
    }
```
  File "/opt/homebrew/lib/python3.11/site-packages/slither/solc_parsing/yul/parse_yul.py", line 847, in parse_yul_identifier
    raise SlitherException(f"unresolved reference to identifier {name}")
slither.exceptions.SlitherException: unresolved reference to identifier _bucketGlobalState.slot
ERROR:root:Error:
ERROR:root:unresolved reference to identifier _bucketGlobalState.slot
ERROR:root:Please report an issue to https://github.com/crytic/slither/issues
Similar fix as https://github.com/crytic/slither/pull/1085
Hi!

I am interested in helping if issue is still open but I would like more information.

Is .slot parsing already handled by the function `_parse_yul_magic_suffixes()` in the PR 1085?

https://github.com/crytic/slither/blob/master/slither/solc_parsing/yul/parse_yul.py#L763-L770

You're right. The issue is bit more complex than I first thought. The underlying problem is that  `get_state_variable_from_name ` only searches accessible variables i.e. not private variables in inherited contracts. However, inherited internal functions in the derived contract can access their respective contract's private variables. So, we should probably change this to `root_function.contract_declarer.get_state_variable_from_name(potential_name)`.

https://github.com/crytic/slither/blob/e5f2a86f0906fd62c6c4eccb9dbfa5ab30671a78/slither/solc_parsing/yul/parse_yul.py#L751

Here's a an example that will reproduce the issue:
```
contract YYY {
mapping(address => uint256) private _counters;
  function _getPackedBucketGlobalState(uint256 bucketId) internal view returns (uint256 packedGlobalState) {
        assembly {
            mstore(0x0, bucketId)
            mstore(0x20, _counters.slot)
            let slot := keccak256(0x0, 0x40)
            packedGlobalState := sload(slot)
        }
    }
}  
contract XXX is YYY {
    function getPackedBucketGlobalState(uint256 bucketId) external {
        _getPackedBucketGlobalState(bucketId);
    }
}
```

Resolving patch:
diff --git a/slither/solc_parsing/yul/parse_yul.py b/slither/solc_parsing/yul/parse_yul.py
--- a/slither/solc_parsing/yul/parse_yul.py
+++ b/slither/solc_parsing/yul/parse_yul.py
@@ -748,7 +748,7 @@ def parse_yul_function_call(root: YulScope, node: YulNode, ast: Dict) -> Optiona
 def _check_for_state_variable_name(root: YulScope, potential_name: str) -> Optional[Identifier]:
     root_function = root.function
     if isinstance(root_function, FunctionContract):
-        var = root_function.contract.get_state_variable_from_name(potential_name)
+        var = root_function.contract_declarer.get_state_variable_from_name(potential_name)
         if var:
             return Identifier(var)
     return None


Test patch:
diff --git a/tests/unit/slithir/test_data/assembly_storage_slot.sol b/tests/unit/slithir/test_data/assembly_storage_slot.sol
new file mode 100644
--- /dev/null
+++ b/tests/unit/slithir/test_data/assembly_storage_slot.sol
@@ -0,0 +1,18 @@
+contract YYY {
+    mapping(address => uint256) private _counters;
+    function _getPackedBucketGlobalState(uint256 bucketId) internal view returns (uint256 packedGlobalState) {
+        assembly {
+            mstore(0x0, bucketId)
+            mstore(0x20, _counters.slot)
+            let slot := keccak256(0x0, 0x40)
+            packedGlobalState := sload(slot)
+        }
+    }
+}
+
+
+contract XXX is YYY {
+    function getPackedBucketGlobalState(uint256 bucketId) external {
+        _getPackedBucketGlobalState(bucketId);
+    }
+}
\ No newline at end of file
diff --git a/tests/unit/slithir/test_yul_parser_assembly_slot.py b/tests/unit/slithir/test_yul_parser_assembly_slot.py
new file mode 100644
--- /dev/null
+++ b/tests/unit/slithir/test_yul_parser_assembly_slot.py
@@ -0,0 +1,39 @@
+from pathlib import Path
+from slither import Slither
+
+from slither.core.expressions import CallExpression
+from slither.core.expressions.identifier import Identifier
+from slither.core.expressions.literal import Literal
+from slither.core.variables.state_variable import StateVariable
+from slither.core.variables.local_variable import LocalVariable
+
+
+TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data"
+
+
+def test_yul_parser_assembly_slot(solc_binary_path) -> None:
+    # mstore(0x0, bucketId)
+    # mstore(0x20, _counters.slot)
+    data = {
+        '0x0': 'bucketId',
+        '0x20': '_counters'
+    }
+
+    solc_path = solc_binary_path("0.8.18")
+    slither = Slither(Path(TEST_DATA_DIR, "assembly_storage_slot.sol").as_posix(), solc=solc_path)
+
+    contract = slither.get_contract_from_name('XXX')[0]
+    func = contract.get_function_from_full_name('getPackedBucketGlobalState(uint256)')
+    calls = [node.expression for node in func.all_nodes() if node.expression and 'mstore' in str(node.expression)]
+
+    for call in calls:
+        assert isinstance(call, CallExpression)
+        memory_location = call.arguments[0]
+        value = call.arguments[1]
+        assert isinstance(memory_location, Literal)
+        assert isinstance(value, Identifier)
+        assert value.value.name == data[memory_location.value]
+        if value.value.name == '_counters':
+            assert isinstance(value.value, StateVariable)
+        elif value.value.name == 'bucketId':
+            assert isinstance(value.value, LocalVariable)