SQLite Signature DB (#758)

* Add first version of sqlite signature db

* Adapt truffle module to new signature db

* Fix tests

* Remove old DB code

* Apply black

* Add sqlite dev dependency

* Add sqlite dev dependency

* Add more type hints

* Apply black

* Adapt old naming in manifest and gitignore

* Add minor fixes to signature DB

* Add repr to signature db

* Apply black

* Add mythril dir env var

* Replace __del__ with SQLite context manager

* Add SQLite context manager repr and fix test imports

* Apply black

* Fix nasty test path issue

* Add reentrancy signature to db for tests

* Call base setUp in evm contract tests

* Remove redundant return
pull/760/head
Dominik Muhs 6 years ago committed by JoranHonig
parent 433da8940c
commit 0e9e4bf0b1
  1. 1
      .circleci/config.yml
  2. 2
      .gitignore
  3. 3
      Dockerfile
  4. 2
      MANIFEST.in
  5. 15
      mythril/disassembler/disassembly.py
  6. 26
      mythril/mythril.py
  7. 364
      mythril/support/signatures.py
  8. 3
      mythril/support/truffle.py
  9. BIN
      signatures.db
  10. 396809
      signatures.json
  11. 8
      tests/__init__.py
  12. 2
      tests/cmd_line_test.py
  13. 6
      tests/disassembler/disassembly.py
  14. 5
      tests/evmcontract_test.py
  15. 8
      tests/graph_test.py
  16. 1
      tests/laser/evm_testsuite/evm_test.py
  17. BIN
      tests/mythril_dir/signatures.db.example
  18. 396809
      tests/mythril_dir/signatures.json.example
  19. 2
      tests/native_test.py
  20. 3
      tests/rpc_test.py
  21. 7
      tests/svm_test.py
  22. 6
      tests/testdata/outputs_expected/calls.sol.o.json
  23. 6
      tests/testdata/outputs_expected/calls.sol.o.markdown
  24. 6
      tests/testdata/outputs_expected/calls.sol.o.text
  25. 2
      tox.ini

@ -51,6 +51,7 @@ jobs:
environment: environment:
LC_ALL: en_US.ASCII LC_ALL: en_US.ASCII
LANG: en_US.ASCII LANG: en_US.ASCII
MYTHRIL_DIR: '/home/mythril'
- store_test_results: - store_test_results:
path: /home/mythril/.tox/output path: /home/mythril/.tox/output

2
.gitignore vendored

@ -176,7 +176,7 @@ lol*
coverage_html_report/ coverage_html_report/
tests/testdata/outputs_current/ tests/testdata/outputs_current/
tests/testdata/outputs_current_laser_result/ tests/testdata/outputs_current_laser_result/
tests/mythril_dir/signatures.json tests/mythril_dir/signatures.db
# VSCode # VSCode
.vscode .vscode

@ -1,6 +1,9 @@
FROM ubuntu:bionic FROM ubuntu:bionic
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y \
libsqlite3-0 \
libsqlite3-dev \
&& apt-get install -y \ && apt-get install -y \
build-essential \ build-essential \
locales \ locales \

@ -1,2 +1,2 @@
include mythril/disassembler/signatures.json include mythril/disassembler/signatures.db
include mythril/analysis/templates/* include mythril/analysis/templates/*

@ -1,6 +1,6 @@
from mythril.ethereum import util from mythril.ethereum import util
from mythril.disassembler import asm from mythril.disassembler import asm
from mythril.support.signatures import SignatureDb from mythril.support.signatures import SignatureDB
import logging import logging
@ -23,16 +23,9 @@ class Disassembly(object):
self.function_name_to_address = {} self.function_name_to_address = {}
self.address_to_function_name = {} self.address_to_function_name = {}
signatures = {}
try:
# open from default locations # open from default locations
signatures = SignatureDb( # control if you want to have online signature hash lookups
enable_online_lookup=enable_online_lookup signatures = SignatureDB(enable_online_lookup=enable_online_lookup)
) # control if you want to have online signature hash lookups
except FileNotFoundError:
logging.info(
"Missing function signature file. Resolving of function names from signature file disabled."
)
# Need to take from PUSH1 to PUSH4 because solc seems to remove excess 0s at the beginning for optimizing # Need to take from PUSH1 to PUSH4 because solc seems to remove excess 0s at the beginning for optimizing
jump_table_indices = asm.find_op_code_sequence( jump_table_indices = asm.find_op_code_sequence(
@ -54,7 +47,7 @@ class Disassembly(object):
def get_function_info( def get_function_info(
index: int, instruction_list: list, signature_database: SignatureDb index: int, instruction_list: list, signature_database: SignatureDB
) -> (str, int, str): ) -> (str, int, str):
""" """
Finds the function information for a call table entry Finds the function information for a call table entry

@ -95,27 +95,10 @@ class Mythril(object):
self.mythril_dir = self._init_mythril_dir() self.mythril_dir = self._init_mythril_dir()
self.sigs = {} # tries mythril_dir/signatures.db by default (provide path= arg to make this configurable)
try: self.sigs = signatures.SignatureDB(
# tries mythril_dir/signatures.json by default (provide path= arg to make this configurable)
self.sigs = signatures.SignatureDb(
enable_online_lookup=self.enable_online_lookup enable_online_lookup=self.enable_online_lookup
) )
except FileNotFoundError as e:
logging.info(str(e))
# Create empty db file if none exists
f = open(os.path.join(self.mythril_dir, "signatures.json"), "w")
f.write("{}")
f.close()
self.sigs = signatures.SignatureDb(
enable_online_lookup=self.enable_online_lookup
)
except json.JSONDecodeError as e:
raise CriticalError(str(e))
self.solc_binary = self._init_solc_binary(solv) self.solc_binary = self._init_solc_binary(solv)
self.config_path = os.path.join(self.mythril_dir, "config.ini") self.config_path = os.path.join(self.mythril_dir, "config.ini")
@ -388,12 +371,9 @@ class Mythril(object):
try: try:
# import signatures from solidity source # import signatures from solidity source
self.sigs.import_from_solidity_source( self.sigs.import_solidity_file(
file, solc_binary=self.solc_binary, solc_args=self.solc_args file, solc_binary=self.solc_binary, solc_args=self.solc_args
) )
# Save updated function signatures
self.sigs.write() # dump signatures to disk (previously opened file or default location)
if contract_name is not None: if contract_name is not None:
contract = SolidityContract( contract = SolidityContract(
input_file=file, input_file=file,

@ -3,11 +3,11 @@
"""mythril.py: Function Signature Database """mythril.py: Function Signature Database
""" """
import os import os
import json
import time import time
import logging import logging
import sqlite3
from typing import List
from collections import defaultdict
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
from mythril.exceptions import CompilerError from mythril.exceptions import CompilerError
@ -22,280 +22,198 @@ except ImportError:
FourByteDirectoryOnlineLookupError = Exception FourByteDirectoryOnlineLookupError = Exception
try: class SQLiteDB(object):
# Posix based file locking (Linux, Ubuntu, MacOS, etc.) """
import fcntl Simple CM for sqlite3 databases. Commits everything at exit.
"""
def lock_file(f, exclusive=False):
if f.mode == "r" and exclusive:
raise Exception("Please use non exclusive mode for reading")
flag = fcntl.LOCK_EX if exclusive else fcntl.LOCK_SH
fcntl.lockf(f, flag)
def unlock_file(f):
return
except ImportError:
# Windows file locking
# TODO: confirm the existence or non existence of shared locks in windows msvcrt and make changes based on that
import msvcrt
def file_size(f): def __init__(self, path):
return os.path.getsize(os.path.realpath(f.name)) self.path = path
self.conn = None
self.cursor = None
def lock_file(f, exclusive=False): def __enter__(self):
if f.mode == "r" and exclusive: self.conn = sqlite3.connect(self.path)
raise Exception("Please use non exclusive mode for reading") self.cursor = self.conn.cursor()
msvcrt.locking(f.fileno(), msvcrt.LK_RLCK, file_size(f)) return self.cursor
def unlock_file(f): def __exit__(self, exc_class, exc, traceback):
msvcrt.locking(f.fileno(), msvcrt.LK_UNLCK, file_size(f)) self.conn.commit()
self.conn.close()
def __repr__(self):
return "<SQLiteDB path={}>".format(self.path)
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs): class SignatureDB(object):
if cls not in cls._instances: def __init__(self, enable_online_lookup: bool = False, path: str = None) -> None:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) self.enable_online_lookup = enable_online_lookup
return cls._instances[cls] self.online_lookup_miss = set()
self.online_lookup_timeout = 0
if path is None:
self.path = os.environ.get("MYTHRIL_DIR") or os.path.join(
os.path.expanduser("~"), ".mythril"
)
self.path = os.path.join(self.path, "signatures.db")
logging.info("Using signature database at %s", self.path)
# NOTE: Creates a new DB file if it doesn't exist already
with SQLiteDB(self.path) as cur:
cur.execute(
(
"CREATE TABLE IF NOT EXISTS signatures"
"(byte_sig VARCHAR(10), text_sig VARCHAR(255),"
"PRIMARY KEY (byte_sig, text_sig))"
)
)
class SignatureDb(object, metaclass=Singleton): def __getitem__(self, item: str) -> List[str]:
def __init__(self, enable_online_lookup=False):
""" """
Constr Provide dict interface db[sighash]
:param enable_online_lookup: enable onlien signature hash lookup :param item: 4-byte signature string
:return: list of matching text signature strings
""" """
self.signatures = defaultdict(list) # signatures in-mem cache return self.get(byte_sig=item)
self.signatures_file = None
self.enable_online_lookup = (
enable_online_lookup
) # enable online funcsig resolving
# temporarily track misses from onlinedb to avoid requesting the same non-existent sighash multiple times
self.online_lookup_miss = set()
# flag the online directory as unavailable for some time
self.online_directory_unavailable_until = 0
self.open()
def open(self, path=None): @staticmethod
def _normalize_byte_sig(byte_sig: str) -> str:
""" """
Open a function signature db from json file Adds a leading 0x to the byte signature if it's not already there.
:param byte_sig: 4-byte signature string
:param path: specific path to signatures.json; default mythril location if not specified :return: normalized byte signature string
:return: self
""" """
if not path: if not byte_sig.startswith("0x"):
# try default locations byte_sig = "0x" + byte_sig
try: if not len(byte_sig) == 10:
mythril_dir = os.environ["MYTHRIL_DIR"]
except KeyError:
mythril_dir = os.path.join(os.path.expanduser("~"), ".mythril")
path = os.path.join(mythril_dir, "signatures.json")
# store early to allow error handling to access the place we tried to load the file
self.signatures_file = path
if not os.path.exists(path):
logging.debug("Signatures: file not found: %s" % path)
raise FileNotFoundError(
(
"Could not find signature file in {}. Function name resolution disabled.\n"
"Consider replacing it with the pre-initialized database at "
"https://raw.githubusercontent.com/ConsenSys/mythril/master/signatures.json"
).format(path)
)
with open(path, "r") as f:
lock_file(f)
try:
sigs = json.load(f)
except json.JSONDecodeError as e:
# reraise with path
raise json.JSONDecodeError(
"Invalid JSON in the signatures file {}: {}".format(path, str(e))
)
finally:
unlock_file(f)
# assert signature file format
for sighash, funcsig in sigs.items():
if (
not sighash.startswith("0x")
or len(sighash) != 10
or not isinstance(funcsig, list)
):
raise ValueError( raise ValueError(
"Malformed signature file at {}. {}: {} is not a valid entry".format( "Invalid byte signature %s, must have 10 characters", byte_sig
path, sighash, funcsig
)
) )
self.signatures = defaultdict(list, sigs) return byte_sig
return self
def update_signatures(self, new_signatures): def add(self, byte_sig: str, text_sig: str) -> None:
for sighash, funcsigs in new_signatures.items():
# eliminate duplicates
updated_funcsigs = set(funcsigs + self.signatures[sighash])
self.signatures[sighash] = list(updated_funcsigs)
def write(self, path=None, sync=True):
""" """
Write signatures database as json to file Adds a new byte - text signature pair to the database.
:param byte_sig: 4-byte signature string
:param path: specify path otherwise update the file that was loaded with open() :param text_sig: resolved text signature
:param sync: lock signature file, load contents and merge it into memcached sighash db, then save it :return:
:return: self
""" """
path = path or self.signatures_file byte_sig = self._normalize_byte_sig(byte_sig)
directory = os.path.split(path)[0] with SQLiteDB(self.path) as cur:
# ignore new row if it's already in the DB (and would cause a unique constraint error)
if sync and os.path.exists(path): cur.execute(
# reload and save if file exists "INSERT OR IGNORE INTO signatures (byte_sig, text_sig) VALUES (?,?)",
with open(path, "r") as f: (byte_sig, text_sig),
lock_file(f) )
try:
sigs = json.load(f)
finally:
unlock_file(f)
self.update_signatures(sigs)
if directory and not os.path.exists(directory):
os.makedirs(directory) # create folder structure if not existS
if not os.path.exists(path): # creates signatures.json file if it doesn't exist
open(path, "w").close()
with open(path, "r+") as f: # placing 'w+' here will result in race conditions
lock_file(f, exclusive=True)
try:
json.dump(self.signatures, f, indent=4, sort_keys=True)
finally:
unlock_file(f)
return self
def get(self, sighash, timeout=2): def get(self, byte_sig: str, online_timeout: int = 2) -> List[str]:
""" """
get a function signature for a sighash Get a function text signature for a byte signature
1) try local cache 1) try local cache
2) try online lookup (if enabled; if not flagged as unavailable) 2) try online lookup (if enabled; if not flagged as unavailable)
:param sighash: function signature hash as hexstr :param byte_sig: function signature hash as hexstr
:param timeout: online lookup timeout :param online_timeout: online lookup timeout
:return: list of matching function signatures :return: list of matching function text signatures
""" """
if not sighash.startswith("0x"):
sighash = "0x%s" % sighash # normalize sighash format byte_sig = self._normalize_byte_sig(byte_sig)
if ( # try lookup in the local DB
with SQLiteDB(self.path) as cur:
cur.execute("SELECT text_sig FROM signatures WHERE byte_sig=?", (byte_sig,))
text_sigs = cur.fetchall()
if text_sigs:
return [t[0] for t in text_sigs]
# otherwise try the online lookup if we're allowed to
if not (
self.enable_online_lookup self.enable_online_lookup
and not self.signatures.get(sighash) and byte_sig not in self.online_lookup_miss
and sighash not in self.online_lookup_miss and time.time() > self.online_lookup_timeout,
and time.time() > self.online_directory_unavailable_until
): ):
# online lookup enabled, and signature not in cache, sighash was not a miss earlier, and online directory not down return []
logging.debug(
"Signatures: performing online lookup for sighash %r" % sighash
)
try: try:
funcsigs = SignatureDb.lookup_online( text_sigs = self.lookup_online(byte_sig=byte_sig, timeout=online_timeout)
sighash, timeout=timeout if not text_sigs:
) # might return multiple sigs self.online_lookup_miss.add(byte_sig)
if funcsigs: return []
# only store if we get at least one result
self.update_signatures({sighash: funcsigs})
else: else:
# miss for resolved in text_sigs:
self.online_lookup_miss.add(sighash) self.add(byte_sig, resolved)
return text_sigs
except FourByteDirectoryOnlineLookupError as fbdole: except FourByteDirectoryOnlineLookupError as fbdole:
self.online_directory_unavailable_until = ( # wait at least 2 mins to try again
time.time() + 2 * 60 self.online_lookup_timeout = time.time() + 2 * 60
) # wait at least 2 mins to try again logging.warning("Online lookup failed, not retrying for 2min: %s", fbdole)
logging.warning(
"online function signature lookup not available. will not try to lookup hash for the next 2 minutes. exception: %r"
% fbdole
)
if sighash not in self.signatures:
return [] return []
if type(self.signatures[sighash]) != list:
return [self.signatures[sighash]]
return self.signatures[sighash]
def __getitem__(self, item): def import_solidity_file(
""" self, file_path: str, solc_binary: str = "solc", solc_args: str = None
Provide dict interface Signatures()[sighash]
:param item: sighash
:return: list of matching signatures
"""
return self.get(sighash=item)
def import_from_solidity_source(
self, file_path, solc_binary="solc", solc_args=None
): ):
""" """
Import Function Signatures from solidity source files Import Function Signatures from solidity source files
:param file_path: solidity source code file path :param file_path: solidity source code file path
:return: self :return:
""" """
self.update_signatures( sigs = {}
self.get_sigs_from_file( cmd = [solc_binary, "--hashes", file_path]
file_path, solc_binary=solc_binary, solc_args=solc_args if solc_args:
cmd.extend(solc_args.split())
try:
p = Popen(cmd, stdout=PIPE, stderr=PIPE)
stdout, stderr = p.communicate()
ret = p.returncode
if ret != 0:
raise CompilerError(
"Solc has experienced a fatal error (code {}).\n\n{}".format(
ret, stderr.decode("utf-8")
)
)
except FileNotFoundError:
raise CompilerError(
(
"Compiler not found. Make sure that solc is installed and in PATH, "
"or the SOLC environment variable is set."
) )
) )
return self
stdout = stdout.decode("unicode_escape").split("\n")
for line in stdout:
# the ':' need not be checked but just to be sure
if all(map(lambda x: x in line, ["(", ")", ":"])):
sigs["0x" + line.split(":")[0]] = [line.split(":")[1].strip()]
logging.debug("Signatures: found %d signatures after parsing" % len(sigs))
# update DB with what we've found
for byte_sig, text_sigs in sigs.items():
for text_sig in text_sigs:
self.add(byte_sig, text_sig)
@staticmethod @staticmethod
def lookup_online(sighash, timeout=None, proxies=None): def lookup_online(byte_sig: str, timeout: int, proxies=None) -> List[str]:
""" """
Lookup function signatures from 4byte.directory. Lookup function signatures from 4byte.directory.
//tintinweb: the smart-contract-sanctuary project dumps contracts from etherscan.io and feeds them into //tintinweb: the smart-contract-sanctuary project dumps contracts from etherscan.io and feeds them into
4bytes.directory. 4bytes.directory.
https://github.com/tintinweb/smart-contract-sanctuary https://github.com/tintinweb/smart-contract-sanctuary
:param sighash: function signature hash as hexstr :param byte_sig: function signature hash as hexstr
:param timeout: optional timeout for online lookup :param timeout: optional timeout for online lookup
:param proxies: optional proxy servers for online lookup :param proxies: optional proxy servers for online lookup
:return: a list of matching function signatures for this hash :return: a list of matching function signatures for this hash
""" """
if not ethereum_input_decoder: if not ethereum_input_decoder:
return None return []
return list( return list(
ethereum_input_decoder.decoder.FourByteDirectory.lookup_signatures( ethereum_input_decoder.decoder.FourByteDirectory.lookup_signatures(
sighash, timeout=timeout, proxies=proxies byte_sig, timeout=timeout, proxies=proxies
) )
) )
@staticmethod def __repr__(self):
def get_sigs_from_file(file_name, solc_binary="solc", solc_args=None): return "<SignatureDB path='{}' enable_online_lookup={}>".format(
""" self.path, self.enable_online_lookup
:param file_name: accepts a filename
:return: their signature mappings
"""
sigs = {}
cmd = [solc_binary, "--hashes", file_name]
if solc_args:
cmd.extend(solc_args.split())
try:
p = Popen(cmd, stdout=PIPE, stderr=PIPE)
stdout, stderr = p.communicate()
ret = p.returncode
if ret != 0:
raise CompilerError(
"Solc experienced a fatal error (code %d).\n\n%s"
% (ret, stderr.decode("UTF-8"))
) )
except FileNotFoundError:
raise CompilerError(
"Compiler not found. Make sure that solc is installed and in PATH, or set the SOLC environment variable."
)
stdout = stdout.decode("unicode_escape").split("\n")
for line in stdout:
if (
"(" in line and ")" in line and ":" in line
): # the ':' need not be checked but just to be sure
sigs["0x" + line.split(":")[0]] = [line.split(":")[1].strip()]
logging.debug("Signatures: found %d signatures after parsing" % len(sigs))
return sigs

@ -138,8 +138,7 @@ def get_sigs_from_truffle(sigs, contract_data):
function_name = abi["name"] function_name = abi["name"]
list_of_args = ",".join([input["type"] for input in abi["inputs"]]) list_of_args = ",".join([input["type"] for input in abi["inputs"]])
signature = function_name + "(" + list_of_args + ")" signature = function_name + "(" + list_of_args + ")"
sigs.signatures["0x" + sha3(signature)[:4].hex()] = [signature] sigs.add("0x" + sha3(signature)[:4].hex(), signature)
sigs.write()
def get_mappings(source, deployed_source_map): def get_mappings(source, deployed_source_map):

Binary file not shown.

File diff suppressed because it is too large Load Diff

@ -18,16 +18,16 @@ MYTHRIL_DIR = TESTS_DIR / "mythril_dir"
class BaseTestCase(TestCase): class BaseTestCase(TestCase):
def setUp(self): def setUp(self):
self.changed_files = [] self.changed_files = []
self.ori_mythril_dir = getattr(os.environ, "MYTHRIL_DIR", "") self.ori_mythril_dir = os.environ.get("MYTHRIL_DIR", "")
os.environ["MYTHRIL_DIR"] = str(MYTHRIL_DIR) os.environ["MYTHRIL_DIR"] = str(MYTHRIL_DIR)
shutil.copyfile( shutil.copyfile(
str(MYTHRIL_DIR / "signatures.json.example"), str(MYTHRIL_DIR / "signatures.db.example"),
str(MYTHRIL_DIR / "signatures.json"), str(MYTHRIL_DIR / "signatures.db"),
) )
def tearDown(self): def tearDown(self):
os.environ["MYTHRIL_DIR"] = self.ori_mythril_dir os.environ["MYTHRIL_DIR"] = self.ori_mythril_dir
os.remove(str(MYTHRIL_DIR / "signatures.json")) os.remove(str(MYTHRIL_DIR / "signatures.db"))
def compare_files_error_message(self): def compare_files_error_message(self):
message = "Following output files are changed, compare them manually to see differences: \n" message = "Following output files are changed, compare them manually to see differences: \n"

@ -1,5 +1,5 @@
from subprocess import check_output from subprocess import check_output
from tests import * from tests import BaseTestCase, TESTDATA, PROJECT_DIR, TESTS_DIR
MYTH = str(PROJECT_DIR / "myth") MYTH = str(PROJECT_DIR / "myth")

@ -12,7 +12,7 @@ def test_get_function_info(mocker):
# Arrange # Arrange
global instruction_list global instruction_list
signature_database_mock = SignatureDb() signature_database_mock = SignatureDB()
mocker.patch.object(signature_database_mock, "get") mocker.patch.object(signature_database_mock, "get")
signature_database_mock.get.return_value = ["function_name"] signature_database_mock.get.return_value = ["function_name"]
@ -31,7 +31,7 @@ def test_get_function_info_multiple_names(mocker):
# Arrange # Arrange
global instruction_list global instruction_list
signature_database_mock = SignatureDb() signature_database_mock = SignatureDB()
mocker.patch.object(signature_database_mock, "get") mocker.patch.object(signature_database_mock, "get")
signature_database_mock.get.return_value = ["function_name", "another_name"] signature_database_mock.get.return_value = ["function_name", "another_name"]
@ -48,7 +48,7 @@ def test_get_function_info_no_names(mocker):
# Arrange # Arrange
global instruction_list global instruction_list
signature_database_mock = SignatureDb() signature_database_mock = SignatureDB()
mocker.patch.object(signature_database_mock, "get") mocker.patch.object(signature_database_mock, "get")
signature_database_mock.get.return_value = [] signature_database_mock.get.return_value = []

@ -1,9 +1,10 @@
import unittest
from mythril.ethereum.evmcontract import EVMContract from mythril.ethereum.evmcontract import EVMContract
from tests import BaseTestCase
class EVMContractTestCase(unittest.TestCase): class EVMContractTestCase(BaseTestCase):
def setUp(self): def setUp(self):
super().setUp()
self.code = "0x60606040525b603c5b60006010603e565b9050593681016040523660008237602060003683856040603f5a0204f41560545760206000f35bfe5b50565b005b73c3b2ae46792547a96b9f84405e36d0e07edcd05c5b905600a165627a7a7230582062a884f947232ada573f95940cce9c8bfb7e4e14e21df5af4e884941afb55e590029" self.code = "0x60606040525b603c5b60006010603e565b9050593681016040523660008237602060003683856040603f5a0204f41560545760206000f35bfe5b50565b005b73c3b2ae46792547a96b9f84405e36d0e07edcd05c5b905600a165627a7a7230582062a884f947232ada573f95940cce9c8bfb7e4e14e21df5af4e884941afb55e590029"
self.creation_code = "0x60606040525b603c5b60006010603e565b9050593681016040523660008237602060003683856040603f5a0204f41560545760206000f35bfe5b50565b005b73c3b2ae46792547a96b9f84405e36d0e07edcd05c5b905600a165627a7a7230582062a884f947232ada573f95940cce9c8bfb7e4e14e21df5af4e884941afb55e590029" self.creation_code = "0x60606040525b603c5b60006010603e565b9050593681016040523660008237602060003683856040603f5a0204f41560545760206000f35bfe5b50565b005b73c3b2ae46792547a96b9f84405e36d0e07edcd05c5b905600a165627a7a7230582062a884f947232ada573f95940cce9c8bfb7e4e14e21df5af4e884941afb55e590029"

@ -2,13 +2,17 @@ from mythril.analysis.callgraph import generate_graph
from mythril.analysis.symbolic import SymExecWrapper from mythril.analysis.symbolic import SymExecWrapper
from mythril.ethereum import util from mythril.ethereum import util
from mythril.solidity.soliditycontract import EVMContract from mythril.solidity.soliditycontract import EVMContract
from tests import * from tests import (
BaseTestCase,
TESTDATA_INPUTS,
TESTDATA_OUTPUTS_EXPECTED,
TESTDATA_OUTPUTS_CURRENT,
)
import re import re
class GraphTest(BaseTestCase): class GraphTest(BaseTestCase):
def test_generate_graph(self): def test_generate_graph(self):
for input_file in TESTDATA_INPUTS.iterdir(): for input_file in TESTDATA_INPUTS.iterdir():
output_expected = TESTDATA_OUTPUTS_EXPECTED / ( output_expected = TESTDATA_OUTPUTS_EXPECTED / (
input_file.name + ".graph.html" input_file.name + ".graph.html"

@ -7,6 +7,7 @@ from datetime import datetime
import binascii import binascii
import json import json
import os
from pathlib import Path from pathlib import Path
import pytest import pytest
from z3 import ExprRef, simplify from z3 import ExprRef, simplify

File diff suppressed because it is too large Load Diff

@ -4,7 +4,7 @@ from mythril.laser.ethereum.state.account import Account
from mythril.laser.ethereum.state.machine_state import MachineState from mythril.laser.ethereum.state.machine_state import MachineState
from mythril.laser.ethereum.state.global_state import GlobalState from mythril.laser.ethereum.state.global_state import GlobalState
from mythril.laser.ethereum import svm from mythril.laser.ethereum import svm
from tests import * from tests import BaseTestCase
SHA256_TEST = [(0, False) for _ in range(4)] SHA256_TEST = [(0, False) for _ in range(4)]
RIPEMD160_TEST = [(0, False) for _ in range(2)] RIPEMD160_TEST = [(0, False) for _ in range(2)]

@ -1,9 +1,10 @@
from unittest import TestCase from unittest import TestCase
from tests import BaseTestCase
from mythril.ethereum.interface.rpc.client import EthJsonRpc from mythril.ethereum.interface.rpc.client import EthJsonRpc
class RpcTest(TestCase): class RpcTest(BaseTestCase):
client = None client = None
def setUp(self): def setUp(self):

@ -11,7 +11,12 @@ from mythril.laser.ethereum.state.account import Account
from mythril.laser.ethereum.state.machine_state import MachineState from mythril.laser.ethereum.state.machine_state import MachineState
from mythril.laser.ethereum.state.global_state import GlobalState from mythril.laser.ethereum.state.global_state import GlobalState
from mythril.laser.ethereum import svm from mythril.laser.ethereum import svm
from tests import * from tests import (
BaseTestCase,
TESTDATA_INPUTS_CONTRACTS,
TESTDATA_OUTPUTS_EXPECTED_LASER_RESULT,
TESTDATA_OUTPUTS_CURRENT_LASER_RESULT,
)
class LaserEncoder(json.JSONEncoder): class LaserEncoder(json.JSONEncoder):

@ -66,7 +66,7 @@
"contract": "Unknown", "contract": "Unknown",
"debug": "<DEBUG-DATA>", "debug": "<DEBUG-DATA>",
"description": "This contract executes a message call to to another contract. Make sure that the called contract is trusted and does not execute user-supplied code.", "description": "This contract executes a message call to to another contract. Make sure that the called contract is trusted and does not execute user-supplied code.",
"function": "_function_0xe11f493e", "function": "reentrancy()",
"swc-id": "107", "swc-id": "107",
"min_gas_used": 709, "min_gas_used": 709,
"max_gas_used": 1320, "max_gas_used": 1320,
@ -78,7 +78,7 @@
"contract": "Unknown", "contract": "Unknown",
"debug": "<DEBUG-DATA>", "debug": "<DEBUG-DATA>",
"description": "The contract account state is changed after an external call. Consider that the called contract could re-enter the function before this state change takes place. This can lead to business logic vulnerabilities.", "description": "The contract account state is changed after an external call. Consider that the called contract could re-enter the function before this state change takes place. This can lead to business logic vulnerabilities.",
"function": "_function_0xe11f493e", "function": "reentrancy()",
"swc-id": "107", "swc-id": "107",
"min_gas_used": 709, "min_gas_used": 709,
"max_gas_used": 1320, "max_gas_used": 1320,
@ -90,7 +90,7 @@
"contract": "Unknown", "contract": "Unknown",
"debug": "<DEBUG-DATA>", "debug": "<DEBUG-DATA>",
"description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.",
"function": "_function_0xe11f493e", "function": "reentrancy()",
"swc-id": "104", "swc-id": "104",
"min_gas_used": 6432, "min_gas_used": 6432,
"max_gas_used": 61043, "max_gas_used": 61043,

@ -64,7 +64,7 @@ The return value of an external call is not checked. Note that execution continu
- SWC ID: 107 - SWC ID: 107
- Type: Informational - Type: Informational
- Contract: Unknown - Contract: Unknown
- Function name: `_function_0xe11f493e` - Function name: `reentrancy()`
- PC address: 858 - PC address: 858
- Estimated Gas Usage: 709 - 1320 - Estimated Gas Usage: 709 - 1320
@ -76,7 +76,7 @@ This contract executes a message call to to another contract. Make sure that the
- SWC ID: 107 - SWC ID: 107
- Type: Warning - Type: Warning
- Contract: Unknown - Contract: Unknown
- Function name: `_function_0xe11f493e` - Function name: `reentrancy()`
- PC address: 869 - PC address: 869
- Estimated Gas Usage: 709 - 1320 - Estimated Gas Usage: 709 - 1320
@ -88,7 +88,7 @@ The contract account state is changed after an external call. Consider that the
- SWC ID: 104 - SWC ID: 104
- Type: Informational - Type: Informational
- Contract: Unknown - Contract: Unknown
- Function name: `_function_0xe11f493e` - Function name: `reentrancy()`
- PC address: 871 - PC address: 871
- Estimated Gas Usage: 6432 - 61043 - Estimated Gas Usage: 6432 - 61043

@ -52,7 +52,7 @@ The return value of an external call is not checked. Note that execution continu
SWC ID: 107 SWC ID: 107
Type: Informational Type: Informational
Contract: Unknown Contract: Unknown
Function name: _function_0xe11f493e Function name: reentrancy()
PC address: 858 PC address: 858
Estimated Gas Usage: 709 - 1320 Estimated Gas Usage: 709 - 1320
This contract executes a message call to to another contract. Make sure that the called contract is trusted and does not execute user-supplied code. This contract executes a message call to to another contract. Make sure that the called contract is trusted and does not execute user-supplied code.
@ -62,7 +62,7 @@ This contract executes a message call to to another contract. Make sure that the
SWC ID: 107 SWC ID: 107
Type: Warning Type: Warning
Contract: Unknown Contract: Unknown
Function name: _function_0xe11f493e Function name: reentrancy()
PC address: 869 PC address: 869
Estimated Gas Usage: 709 - 1320 Estimated Gas Usage: 709 - 1320
The contract account state is changed after an external call. Consider that the called contract could re-enter the function before this state change takes place. This can lead to business logic vulnerabilities. The contract account state is changed after an external call. Consider that the called contract could re-enter the function before this state change takes place. This can lead to business logic vulnerabilities.
@ -72,7 +72,7 @@ The contract account state is changed after an external call. Consider that the
SWC ID: 104 SWC ID: 104
Type: Informational Type: Informational
Contract: Unknown Contract: Unknown
Function name: _function_0xe11f493e Function name: reentrancy()
PC address: 871 PC address: 871
Estimated Gas Usage: 6432 - 61043 Estimated Gas Usage: 6432 - 61043
The return value of an external call is not checked. Note that execution continue even if the called contract throws. The return value of an external call is not checked. Note that execution continue even if the called contract throws.

@ -5,6 +5,7 @@ envlist = py35,py36
deps = deps =
pytest pytest
pytest-mock pytest-mock
passenv = MYTHRIL_DIR = {homedir}
whitelist_externals = mkdir whitelist_externals = mkdir
commands = commands =
mkdir -p {toxinidir}/tests/testdata/outputs_current/ mkdir -p {toxinidir}/tests/testdata/outputs_current/
@ -21,6 +22,7 @@ deps =
pytest pytest
pytest-mock pytest-mock
pytest-cov pytest-cov
passenv = MYTHRIL_DIR = {homedir}
whitelist_externals = mkdir whitelist_externals = mkdir
commands = commands =
mkdir -p {toxinidir}/tests/testdata/outputs_current/ mkdir -p {toxinidir}/tests/testdata/outputs_current/

Loading…
Cancel
Save