Repository: crytic/slither

Issue:
Wrong tuple assignment
```
pragma solidity 0.5.0;


contract TuplePoc {

    mapping(address => uint256) public lastWithdrawTime;
    mapping(address => uint256) public balances;


    function withdrawFunds (uint amount) public {
        (bool success, bytes memory data) = msg.sender.call.value(amount)("");
        require(success);
        balances[msg.sender] -= amount;
        lastWithdrawTime[msg.sender] = now;
    }
 }

```
The generated IR for the above smart contract follows:

```
TUPLE_0(bool) = LOW_LEVEL_CALL, dest:msg.sender, function:call, arguments:[''] value:amount 
success = UNPACK TUPLE_0 index: 0 
data = UNPACK TUPLE_0 index: 1 
```
The above generated IR wrongly generates tuple assignments. The returned value for the low-level call in the source code contains a tuple of two variables. But, but the generated IR contains only one variable. 



Discussion:
Hi @PriyankaBose, the fix (#536) is merged in master. It will also be available in the upcoming release.

Thank you for reporting this issue. 

#536 is actually incorrect; I am reopening this issue and I am working on a better fix
It does solve the above issue. Whereas, for specific high level calls the issue still exists. For the following contract:

```
pragma solidity 0.4.19;
contract EthernautsStorage{
    

    struct Asset {
        uint16 ID;
        uint8 category;
        uint8 state;
        bytes2 attributes;
        uint64 createdAt;
        uint64 cooldownEndBlock;
        uint8[12] stats;
        uint256 cooldown;
        uint256 builtBy;
    }
    
    Asset[] public assets; 
}

contract TuplePoc {
	EthernautsStorage public ethernautsStorage;
    
    function isExploring(uint256 _tokenId) public view returns (bool) {
        uint256 cooldown;
        uint64 cooldownEndBlock;

        (,,,,,cooldownEndBlock, cooldown,) = ethernautsStorage.assets(_tokenId);
        return (cooldown > now) || (cooldownEndBlock > uint64(block.number));
    }
}
```
The IR for the above contract:

```
TUPLE_0(EthernautsStorage.Asset) = HIGH_LEVEL_CALL, dest:ethernautsStorage(EthernautsStorage), function:assets, arguments:['_tokenId']  
cooldownEndBlock = UNPACK TUPLE_0 index: 5 
cooldown = UNPACK TUPLE_0 index: 6 
TMP_0 = cooldown > now
TMP_1 = CONVERT block.number to uint64
TMP_2 = cooldownEndBlock > TMP_1
TMP_3 = TMP_0 || TMP_2
RETURN TMP_3
```
`assets` will behave a getter function for the external contracts according to my understanding. And, the return type should be tuple of the elements of the structures. However, the generated IR do not capture that.

Resolving patch:
diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py
--- a/slither/slithir/convert.py
+++ b/slither/slithir/convert.py
@@ -1,4 +1,5 @@
 import logging
+from typing import List
 
 from slither.core.declarations import (Contract, Enum, Event, Function,
                                        SolidityFunction, SolidityVariable,
@@ -8,6 +9,7 @@
                                          FunctionType, MappingType,
                                          UserDefinedType, TypeInformation)
 from slither.core.solidity_types.elementary_type import Int as ElementaryTypeInt
+from slither.core.solidity_types.type import Type
 from slither.core.variables.variable import Variable
 from slither.core.variables.state_variable import StateVariable
 from slither.slithir.variables import TupleVariable
@@ -1005,6 +1007,22 @@ def convert_type_library_call(ir, lib_contract):
     return ir
 
 
+def _convert_to_structure_to_list(return_type: Type) -> List[Type]:
+    """
+    Convert structure elements types to a list of types
+    Recursive function
+
+    :param return_type:
+    :return:
+    """
+    if isinstance(return_type, UserDefinedType) and isinstance(return_type.type, Structure):
+        ret = []
+        for v in return_type.type.elems_ordered:
+            ret += _convert_to_structure_to_list(v.type)
+        return ret
+    return [return_type.type]
+
+
 def convert_type_of_high_and_internal_level_call(ir, contract):
     func = None
     if isinstance(ir, InternalCall):
@@ -1068,6 +1086,15 @@ def convert_type_of_high_and_internal_level_call(ir, contract):
         else:
             return_type = func.type
     if return_type:
+
+        # If the return type is a structure, but the lvalue is a tuple
+        # We convert the type of the structure to a list of element
+        # TODO: explore to replace all tuple variables by structures
+        if (isinstance(ir.lvalue, TupleVariable) and
+                isinstance(return_type, UserDefinedType) and
+                isinstance(return_type.type, Structure)):
+            return_type = _convert_to_structure_to_list(return_type)
+
         ir.lvalue.set_type(return_type)
     else:
         ir.lvalue = None


Test patch: