Repository: ConsenSys/mythril-classic
Pull request #971 (2019-03-23 16:15:36)

Fix the jsonv2 output for the type of the contract
related to #954 


Patch:
diff --git a/mythril/analysis/report.py b/mythril/analysis/report.py
index a3dfed4f..4ba3b942 100644
--- a/mythril/analysis/report.py
+++ b/mythril/analysis/report.py
@@ -4,13 +4,13 @@ import json
 import operator
 from jinja2 import PackageLoader, Environment
 from typing import Dict, List
-import _pysha3 as sha3
 import hashlib
 
 from mythril.solidity.soliditycontract import SolidityContract
 from mythril.analysis.swc_data import SWC_TO_TITLE
 from mythril.support.source_support import Source
 from mythril.support.start_time import StartTime
+from mythril.support.support_utils import get_code_hash
 from time import time
 
 log = logging.getLogger(__name__)
@@ -63,20 +63,7 @@ class Issue:
         self.lineno = None
         self.source_mapping = None
         self.discovery_time = time() - StartTime().global_start_time
-
-        try:
-            keccak = sha3.keccak_256()
-            keccak.update(
-                bytes.fromhex(bytecode[2:])
-                if bytecode[:2] == "0x"
-                else bytes.fromhex(bytecode)
-            )
-            self.bytecode_hash = "0x" + keccak.hexdigest()
-        except ValueError:
-            log.debug(
-                "Unable to change the bytecode to bytes. Bytecode: {}".format(bytecode)
-            )
-            self.bytecode_hash = ""
+        self.bytecode_hash = get_code_hash(bytecode)
 
     @property
     def as_dict(self):
@@ -144,7 +131,7 @@ class Report:
         loader=PackageLoader("mythril.analysis"), trim_blocks=True
     )
 
-    def __init__(self, verbose=False, source=None, exceptions=None):
+    def __init__(self, verbose=False, contracts=None, exceptions=None):
         """
 
         :param verbose:
@@ -153,7 +140,8 @@ class Report:
         self.verbose = verbose
         self.solc_version = ""
         self.meta = {}
-        self.source = source or Source()
+        self.source = Source()
+        self.source.get_source_from_contracts_list(contracts)
         self.exceptions = exceptions or []
 
     def sorted_issues(self):
@@ -210,12 +198,7 @@ class Report:
 
         for key, issue in self.issues.items():
 
-            if issue.bytecode_hash not in source_list:
-                idx = len(source_list)
-                source_list.append(issue.bytecode_hash)
-            else:
-                idx = source_list.index(issue.bytecode_hash)
-
+            idx = self.source.get_source_index(issue.bytecode_hash)
             try:
                 title = SWC_TO_TITLE[issue.swc_id]
             except KeyError:
@@ -238,9 +221,9 @@ class Report:
         result = [
             {
                 "issues": _issues,
-                "sourceType": "raw-bytecode",
-                "sourceFormat": "evm-byzantium-bytecode",
-                "sourceList": source_list,
+                "sourceType": self.source.source_type,
+                "sourceFormat": self.source.source_format,
+                "sourceList": self.source.source_list,
                 "meta": meta_data,
             }
         ]
diff --git a/mythril/ethereum/evmcontract.py b/mythril/ethereum/evmcontract.py
index 47a87f83..8502058f 100644
--- a/mythril/ethereum/evmcontract.py
+++ b/mythril/ethereum/evmcontract.py
@@ -1,15 +1,14 @@
 """This module contains the class representing EVM contracts, aka Smart
 Contracts."""
 import re
-import _pysha3 as sha3
 import logging
-
-log = logging.getLogger(__name__)
-
 import persistent
-from ethereum import utils
 
+from ethereum import utils
 from mythril.disassembler.disassembly import Disassembly
+from mythril.support.support_utils import get_code_hash
+
+log = logging.getLogger(__name__)
 
 
 class EVMContract(persistent.Persistent):
@@ -47,7 +46,7 @@ class EVMContract(persistent.Persistent):
 
         :return: runtime bytecode hash
         """
-        return self._get_hash(self.code[2:])
+        return get_code_hash(self.code)
 
     @property
     def creation_bytecode_hash(self):
@@ -55,23 +54,7 @@ class EVMContract(persistent.Persistent):
 
         :return: Creation bytecode hash
         """
-        return self._get_hash(self.creation_code[2:])
-
-    @staticmethod
-    def _get_hash(code):
-        """
-        :param code: bytecode
-        :return: Returns hash of the given bytecode
-        """
-        try:
-            keccak = sha3.keccak_256()
-            keccak.update(bytes.fromhex(code[2:]))
-            return "0x" + keccak.hexdigest()
-        except ValueError:
-            log.debug(
-                "Unable to change the bytecode to bytes. Bytecode: {}".format(code)
-            )
-            return ""
+        return get_code_hash(self.creation_code)
 
     def as_dict(self):
         """
diff --git a/mythril/mythril.py b/mythril/mythril.py
index 872f23ae..86aa6c2f 100644
--- a/mythril/mythril.py
+++ b/mythril/mythril.py
@@ -608,10 +608,8 @@ class Mythril(object):
             all_issues += issues
             log.info("Solver statistics: \n{}".format(str(SolverStatistics())))
 
-        source_data = Source()
-        source_data.get_source_from_contracts_list(self.contracts)
         # Finally, output the results
-        report = Report(verbose_report, source_data, exceptions=exceptions)
+        report = Report(verbose_report, self.contracts, exceptions=exceptions)
         for issue in all_issues:
             report.append_issue(issue)
 
diff --git a/mythril/mythril/mythril_analyzer.py b/mythril/mythril/mythril_analyzer.py
index e42e943c..5d0596e2 100644
--- a/mythril/mythril/mythril_analyzer.py
+++ b/mythril/mythril/mythril_analyzer.py
@@ -166,7 +166,7 @@ class MythrilAnalyzer:
         source_data = Source()
         source_data.get_source_from_contracts_list(self.contracts)
         # Finally, output the results
-        report = Report(verbose_report, source_data, exceptions=exceptions)
+        report = Report(verbose_report, contracts=self.contracts, exceptions=exceptions)
         for issue in all_issues:
             report.append_issue(issue)
 
diff --git a/mythril/support/source_support.py b/mythril/support/source_support.py
index c774a425..51d94ee8 100644
--- a/mythril/support/source_support.py
+++ b/mythril/support/source_support.py
@@ -5,9 +5,7 @@ from mythril.ethereum.evmcontract import EVMContract
 class Source:
     """Class to handle to source data"""
 
-    def __init__(
-        self, source_type=None, source_format=None, source_list=None, meta=None
-    ):
+    def __init__(self, source_type=None, source_format=None, source_list=None):
         """
         :param source_type: whether it is a solidity-file or evm-bytecode
         :param source_format: whether it is bytecode, ethereum-address or text
@@ -17,7 +15,7 @@ class Source:
         self.source_type = source_type
         self.source_format = source_format
         self.source_list = source_list or []
-        self.meta = meta
+        self._source_hash = []
 
     def get_source_from_contracts_list(self, contracts):
         """
@@ -32,16 +30,28 @@ class Source:
             self.source_format = "text"
             for contract in contracts:
                 self.source_list += [file.filename for file in contract.solidity_files]
+                self._source_hash.append(contract.bytecode_hash)
         elif isinstance(contracts[0], EVMContract):
             self.source_format = "evm-byzantium-bytecode"
             self.source_type = (
-                "raw-bytecode" if contracts[0].name == "MAIN" else "ethereum-address"
+                "ethereum-address"
+                if len(contracts[0].name) == 42 and contracts[0].name[0:2] == "0x"
+                else "raw-bytecode"
             )
             for contract in contracts:
                 if contract.creation_code:
                     self.source_list.append(contract.creation_bytecode_hash)
                 if contract.code:
                     self.source_list.append(contract.bytecode_hash)
+            self._source_hash = self.source_list
 
         else:
             assert False  # Fail hard
+
+    def get_source_index(self, bytecode_hash: str) -> int:
+        """
+        Find the contract index in the list
+        :param bytecode_hash: The contract hash
+        :return: The index of the contract in the _source_hash list
+        """
+        return self._source_hash.index(bytecode_hash)
diff --git a/mythril/support/support_utils.py b/mythril/support/support_utils.py
index b437d795..1edbefff 100644
--- a/mythril/support/support_utils.py
+++ b/mythril/support/support_utils.py
@@ -1,5 +1,9 @@
 """This module contains utility functions for the Mythril support package."""
 from typing import Dict
+import logging
+import _pysha3 as sha3
+
+log = logging.getLogger(__name__)
 
 
 class Singleton(type):
@@ -20,3 +24,18 @@ class Singleton(type):
         if cls not in cls._instances:
             cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
         return cls._instances[cls]
+
+
+def get_code_hash(code: str) -> str:
+    """
+    :param code: bytecode
+    :return: Returns hash of the given bytecode
+    """
+    code = code[2:] if code[:2] == "0x" else code
+    try:
+        keccak = sha3.keccak_256()
+        keccak.update(bytes.fromhex(code))
+        return "0x" + keccak.hexdigest()
+    except ValueError:
+        log.debug("Unable to change the bytecode to bytes. Bytecode: {}".format(code))
+        return ""


Test patch:
diff --git a/tests/report_test.py b/tests/report_test.py
index e9f21001..30e9cabd 100644
--- a/tests/report_test.py
+++ b/tests/report_test.py
@@ -39,7 +39,7 @@ def _generate_report(input_file):
     )
     issues = fire_lasers(sym)
 
-    report = Report()
+    report = Report(contracts=[contract])
     for issue in issues:
         issue.filename = "test-filename.sol"
         report.append_issue(issue)
diff --git a/tests/testdata/outputs_expected/ether_send.sol.o.jsonv2 b/tests/testdata/outputs_expected/ether_send.sol.o.jsonv2
index 0d1e9df5..3bce29c2 100644
--- a/tests/testdata/outputs_expected/ether_send.sol.o.jsonv2
+++ b/tests/testdata/outputs_expected/ether_send.sol.o.jsonv2
@@ -1,9 +1,1 @@
-[
-    {
-        "issues": [],
-        "meta": {},
-        "sourceFormat": "evm-byzantium-bytecode",
-        "sourceList": [],
-        "sourceType": "raw-bytecode"
-    }
-]
\ No newline at end of file
+[{"issues": [], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0x3746c7c2ae7b0d4c3f8b1905df9a7ea169b9f93bec68a10a00b4c9d27a18c6fb"], "sourceType": "raw-bytecode"}]
diff --git a/tests/testdata/outputs_expected/metacoin.sol.o.jsonv2 b/tests/testdata/outputs_expected/metacoin.sol.o.jsonv2
index 0d1e9df5..c669f304 100644
--- a/tests/testdata/outputs_expected/metacoin.sol.o.jsonv2
+++ b/tests/testdata/outputs_expected/metacoin.sol.o.jsonv2
@@ -1,9 +1,1 @@
-[
-    {
-        "issues": [],
-        "meta": {},
-        "sourceFormat": "evm-byzantium-bytecode",
-        "sourceList": [],
-        "sourceType": "raw-bytecode"
-    }
-]
\ No newline at end of file
+[{"issues": [], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0x0e6f727bb3301e02d3be831bf34357522fd2f1d40e90dff8e2214553b06b5f6c"], "sourceType": "raw-bytecode"}]
diff --git a/tests/testdata/outputs_expected/nonascii.sol.o.jsonv2 b/tests/testdata/outputs_expected/nonascii.sol.o.jsonv2
index 0d1e9df5..be2cc307 100644
--- a/tests/testdata/outputs_expected/nonascii.sol.o.jsonv2
+++ b/tests/testdata/outputs_expected/nonascii.sol.o.jsonv2
@@ -1,9 +1,1 @@
-[
-    {
-        "issues": [],
-        "meta": {},
-        "sourceFormat": "evm-byzantium-bytecode",
-        "sourceList": [],
-        "sourceType": "raw-bytecode"
-    }
-]
\ No newline at end of file
+[{"issues": [], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0x11a78eb09819f505ba4f10747e6d1f7a44480e602c67573b7abac2f733a85d93"], "sourceType": "raw-bytecode"}]