Repository: crytic/slither

Issue:
Detector: Unused return value of a call
If the return value of a function is never used, it's likely to be bug.

- severity low/medium

SlithIR can be used to track if the value returned by a call is used:
- If the function does not return a value, the  `lvalue` of the operation is `None`, otherwise its a variable.
- We can check if the `lvalue` is in the `read` list of another operation

Example: 

```
contract Target{
    function f() returns(uint);
}

contract Function{

    function test(Target t){
        t.f();
    }

}
```


Discussion:
@montyly  any plan to add bounty option for this issue?
All issues are covered by bounties. Check the wiki :-D
Voila! I have started to work on this issue, I am beginner, make this reserve for me. thanks for confirming @dguido 
Issue Status: 1. **Open** 2. Started 3. Submitted 4. Done 

<hr>

__This issue now has a funding of 200.0 DAI (200.0 USD @ $1.0/DAI)  attached to it as part of the Ethereum Community Fund via ECF Web 3.0 Infrastructure Fund fund.__

 * If you would like to work on this issue you can 'start work' [on the Gitcoin Issue Details page](https://gitcoin.co/issue/trailofbits/slither/45/1566).
* Want to chip in? Add your own contribution [here](https://gitcoin.co/issue/trailofbits/slither/45/1566).
* Questions? Checkout <a href='https://gitcoin.co/help'>Gitcoin Help</a> or the <a href='https://gitcoin.co/slack'>Gitcoin Slack</a>
* $50,291.41 more funded OSS Work available on the [Gitcoin Issue Explorer](https://gitcoin.co/explorer)


Resolving patch:
diff --git a/slither/__main__.py b/slither/__main__.py
--- a/slither/__main__.py
+++ b/slither/__main__.py
@@ -122,6 +122,7 @@ def get_detectors_and_printers():
     from slither.detectors.statements.tx_origin import TxOrigin
     from slither.detectors.statements.assembly import Assembly
     from slither.detectors.operations.low_level_calls import LowLevelCalls
+    from slither.detectors.operations.unused_return_values import UnusedReturnValues
     from slither.detectors.naming_convention.naming_convention import NamingConvention
     from slither.detectors.functions.external_function import ExternalFunction
     from slither.detectors.statements.controlled_delegatecall import ControlledDelegateCall
@@ -144,6 +145,7 @@ def get_detectors_and_printers():
                  NamingConvention,
                  ConstCandidateStateVars,
                  #ComplexFunction,
+                 UnusedReturnValues,
                  ExternalFunction,
                  ControlledDelegateCall,
                  ConstantFunctions]
diff --git a/slither/detectors/operations/unused_return_values.py b/slither/detectors/operations/unused_return_values.py
new file mode 100644
--- /dev/null
+++ b/slither/detectors/operations/unused_return_values.py
@@ -0,0 +1,71 @@
+"""
+Module detecting unused return values from external calls
+"""
+
+from collections import defaultdict
+from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
+from slither.slithir.operations.high_level_call import HighLevelCall
+
+class UnusedReturnValues(AbstractDetector):
+    """
+    If the return value of a function is never used, it's likely to be bug
+    """
+
+    ARGUMENT = 'unused-return'
+    HELP = 'Unused return values'
+    IMPACT = DetectorClassification.LOW
+    CONFIDENCE = DetectorClassification.MEDIUM
+
+    @staticmethod
+    def lvalues_of_operations(contract):
+        ret = []
+
+        for f in contract.all_functions_called + contract.modifiers:
+            for n in f.nodes:
+                for ir in n.irs:
+                    ret.append(ir)
+
+        return ret
+
+    @staticmethod
+    def unused_lvalues_in_high_level_calls(irs):
+        # Checking HighLevelCall - same process could work for LowLevelCall
+        lvalues = []
+        for ir in irs:
+            if isinstance(ir, HighLevelCall):
+                if (ir.lvalue.contract.get_functions_reading_from_variable(ir.lvalue) == []):
+                    lvalues.append(ir.lvalue)
+
+        return lvalues
+
+    def detect_unused_return_values(self, contract):
+        lvalues_of_operations = self.lvalues_of_operations(contract)
+        unused_lvalues = self.unused_lvalues_in_high_level_calls(lvalues_of_operations)
+
+        return unused_lvalues
+
+    def detect(self):
+        """ Detect unused high level calls that return a value but are never used
+        """
+        results = []
+        for c in self.slither.contracts_derived:
+            unused_return_values = self.detect_unused_return_values(c)
+
+            if unused_return_values:
+                funcs_with_unused_return_value_by_contract = defaultdict(list)
+
+                for unused_return_value in unused_return_values:
+                    funcs_with_unused_return_value_by_contract[unused_return_value.contract.name].append(unused_return_value.function.name)
+
+                for contract, functions in funcs_with_unused_return_value_by_contract.items():
+                    info = "Unused return value from external call in %s Contract: %s, Function: %s" % (self.filename, contract, ",".join(functions))
+                    self.log(info)
+
+                    sourceMapping = [v.source_mapping for v in unused_return_values]
+
+                    results.append({'vuln': 'UnusedReturn',
+                                    'sourceMapping': sourceMapping,
+                                    'filename': self.filename,
+                                    'contract': c.name,
+                                    'unusedReturns': functions})
+        return results


Test patch:
diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh
--- a/scripts/travis_test.sh
+++ b/scripts/travis_test.sh
@@ -87,6 +87,9 @@ test_slither tests/naming_convention.sol "naming-convention"
 #test_slither tests/complex_func.sol "complex-function"
 test_slither tests/controlled_delegatecall.sol "controlled-delegatecall"
 test_slither tests/constant.sol "const-func"
+# TODO: update to the new testing framework
+#test_slither tests/unused_return.sol "unused-return" 1
+
 
 ### Test scripts
 
diff --git a/tests/unused_return.sol b/tests/unused_return.sol
new file mode 100644
--- /dev/null
+++ b/tests/unused_return.sol
@@ -0,0 +1,12 @@
+pragma solidity ^0.4.24;
+
+contract Target{
+    function f() returns(uint);
+}
+
+contract User{
+
+    function test(Target t){
+        t.f();
+    }
+}