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:
LC_ALL: en_US.ASCII
LANG: en_US.ASCII
MYTHRIL_DIR: '/home/mythril'
- store_test_results:
path: /home/mythril/.tox/output

2
.gitignore vendored

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

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

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

@ -1,6 +1,6 @@
from mythril.ethereum import util
from mythril.disassembler import asm
from mythril.support.signatures import SignatureDb
from mythril.support.signatures import SignatureDB
import logging
@ -23,16 +23,9 @@ class Disassembly(object):
self.function_name_to_address = {}
self.address_to_function_name = {}
signatures = {}
try:
# open from default locations
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."
)
# control if you want to have online signature hash lookups
signatures = SignatureDB(enable_online_lookup=enable_online_lookup)
# 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(
@ -54,7 +47,7 @@ class Disassembly(object):
def get_function_info(
index: int, instruction_list: list, signature_database: SignatureDb
index: int, instruction_list: list, signature_database: SignatureDB
) -> (str, int, str):
"""
Finds the function information for a call table entry

@ -95,27 +95,10 @@ class Mythril(object):
self.mythril_dir = self._init_mythril_dir()
self.sigs = {}
try:
# tries mythril_dir/signatures.json by default (provide path= arg to make this configurable)
self.sigs = signatures.SignatureDb(
# tries mythril_dir/signatures.db by default (provide path= arg to make this configurable)
self.sigs = signatures.SignatureDB(
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.config_path = os.path.join(self.mythril_dir, "config.ini")
@ -388,12 +371,9 @@ class Mythril(object):
try:
# 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
)
# Save updated function signatures
self.sigs.write() # dump signatures to disk (previously opened file or default location)
if contract_name is not None:
contract = SolidityContract(
input_file=file,

@ -3,11 +3,11 @@
"""mythril.py: Function Signature Database
"""
import os
import json
import time
import logging
import sqlite3
from typing import List
from collections import defaultdict
from subprocess import Popen, PIPE
from mythril.exceptions import CompilerError
@ -22,280 +22,198 @@ except ImportError:
FourByteDirectoryOnlineLookupError = Exception
try:
# Posix based file locking (Linux, Ubuntu, MacOS, etc.)
import fcntl
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
class SQLiteDB(object):
"""
Simple CM for sqlite3 databases. Commits everything at exit.
"""
def file_size(f):
return os.path.getsize(os.path.realpath(f.name))
def __init__(self, path):
self.path = path
self.conn = None
self.cursor = None
def lock_file(f, exclusive=False):
if f.mode == "r" and exclusive:
raise Exception("Please use non exclusive mode for reading")
msvcrt.locking(f.fileno(), msvcrt.LK_RLCK, file_size(f))
def __enter__(self):
self.conn = sqlite3.connect(self.path)
self.cursor = self.conn.cursor()
return self.cursor
def unlock_file(f):
msvcrt.locking(f.fileno(), msvcrt.LK_UNLCK, file_size(f))
def __exit__(self, exc_class, exc, traceback):
self.conn.commit()
self.conn.close()
def __repr__(self):
return "<SQLiteDB path={}>".format(self.path)
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class SignatureDB(object):
def __init__(self, enable_online_lookup: bool = False, path: str = None) -> None:
self.enable_online_lookup = enable_online_lookup
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 __init__(self, enable_online_lookup=False):
def __getitem__(self, item: str) -> List[str]:
"""
Constr
:param enable_online_lookup: enable onlien signature hash lookup
Provide dict interface db[sighash]
:param item: 4-byte signature string
:return: list of matching text signature strings
"""
self.signatures = defaultdict(list) # signatures in-mem cache
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()
return self.get(byte_sig=item)
def open(self, path=None):
@staticmethod
def _normalize_byte_sig(byte_sig: str) -> str:
"""
Open a function signature db from json file
:param path: specific path to signatures.json; default mythril location if not specified
:return: self
Adds a leading 0x to the byte signature if it's not already there.
:param byte_sig: 4-byte signature string
:return: normalized byte signature string
"""
if not path:
# try default locations
try:
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)
):
if not byte_sig.startswith("0x"):
byte_sig = "0x" + byte_sig
if not len(byte_sig) == 10:
raise ValueError(
"Malformed signature file at {}. {}: {} is not a valid entry".format(
path, sighash, funcsig
)
"Invalid byte signature %s, must have 10 characters", byte_sig
)
self.signatures = defaultdict(list, sigs)
return self
return byte_sig
def update_signatures(self, new_signatures):
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):
def add(self, byte_sig: str, text_sig: str) -> None:
"""
Write signatures database as json to file
:param path: specify path otherwise update the file that was loaded with open()
:param sync: lock signature file, load contents and merge it into memcached sighash db, then save it
:return: self
Adds a new byte - text signature pair to the database.
:param byte_sig: 4-byte signature string
:param text_sig: resolved text signature
:return:
"""
path = path or self.signatures_file
directory = os.path.split(path)[0]
if sync and os.path.exists(path):
# reload and save if file exists
with open(path, "r") as f:
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
byte_sig = self._normalize_byte_sig(byte_sig)
with SQLiteDB(self.path) as cur:
# ignore new row if it's already in the DB (and would cause a unique constraint error)
cur.execute(
"INSERT OR IGNORE INTO signatures (byte_sig, text_sig) VALUES (?,?)",
(byte_sig, text_sig),
)
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
2) try online lookup (if enabled; if not flagged as unavailable)
:param sighash: function signature hash as hexstr
:param timeout: online lookup timeout
:return: list of matching function signatures
:param byte_sig: function signature hash as hexstr
:param online_timeout: online lookup timeout
:return: list of matching function text signatures
"""
if not sighash.startswith("0x"):
sighash = "0x%s" % sighash # normalize sighash format
if (
byte_sig = self._normalize_byte_sig(byte_sig)
# 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
and not self.signatures.get(sighash)
and sighash not in self.online_lookup_miss
and time.time() > self.online_directory_unavailable_until
and byte_sig not in self.online_lookup_miss
and time.time() > self.online_lookup_timeout,
):
# online lookup enabled, and signature not in cache, sighash was not a miss earlier, and online directory not down
logging.debug(
"Signatures: performing online lookup for sighash %r" % sighash
)
return []
try:
funcsigs = SignatureDb.lookup_online(
sighash, timeout=timeout
) # might return multiple sigs
if funcsigs:
# only store if we get at least one result
self.update_signatures({sighash: funcsigs})
text_sigs = self.lookup_online(byte_sig=byte_sig, timeout=online_timeout)
if not text_sigs:
self.online_lookup_miss.add(byte_sig)
return []
else:
# miss
self.online_lookup_miss.add(sighash)
for resolved in text_sigs:
self.add(byte_sig, resolved)
return text_sigs
except FourByteDirectoryOnlineLookupError as fbdole:
self.online_directory_unavailable_until = (
time.time() + 2 * 60
) # wait at least 2 mins to try again
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:
# wait at least 2 mins to try again
self.online_lookup_timeout = time.time() + 2 * 60
logging.warning("Online lookup failed, not retrying for 2min: %s", fbdole)
return []
if type(self.signatures[sighash]) != list:
return [self.signatures[sighash]]
return self.signatures[sighash]
def __getitem__(self, item):
"""
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
def import_solidity_file(
self, file_path: str, solc_binary: str = "solc", solc_args: str = None
):
"""
Import Function Signatures from solidity source files
:param file_path: solidity source code file path
:return: self
:return:
"""
self.update_signatures(
self.get_sigs_from_file(
file_path, solc_binary=solc_binary, solc_args=solc_args
sigs = {}
cmd = [solc_binary, "--hashes", file_path]
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
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.
//tintinweb: the smart-contract-sanctuary project dumps contracts from etherscan.io and feeds them into
4bytes.directory.
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 proxies: optional proxy servers for online lookup
:return: a list of matching function signatures for this hash
"""
if not ethereum_input_decoder:
return None
return []
return list(
ethereum_input_decoder.decoder.FourByteDirectory.lookup_signatures(
sighash, timeout=timeout, proxies=proxies
byte_sig, timeout=timeout, proxies=proxies
)
)
@staticmethod
def get_sigs_from_file(file_name, solc_binary="solc", solc_args=None):
"""
: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"))
def __repr__(self):
return "<SignatureDB path='{}' enable_online_lookup={}>".format(
self.path, self.enable_online_lookup
)
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"]
list_of_args = ",".join([input["type"] for input in abi["inputs"]])
signature = function_name + "(" + list_of_args + ")"
sigs.signatures["0x" + sha3(signature)[:4].hex()] = [signature]
sigs.write()
sigs.add("0x" + sha3(signature)[:4].hex(), signature)
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):
def setUp(self):
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)
shutil.copyfile(
str(MYTHRIL_DIR / "signatures.json.example"),
str(MYTHRIL_DIR / "signatures.json"),
str(MYTHRIL_DIR / "signatures.db.example"),
str(MYTHRIL_DIR / "signatures.db"),
)
def tearDown(self):
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):
message = "Following output files are changed, compare them manually to see differences: \n"

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

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

@ -1,9 +1,10 @@
import unittest
from mythril.ethereum.evmcontract import EVMContract
from tests import BaseTestCase
class EVMContractTestCase(unittest.TestCase):
class EVMContractTestCase(BaseTestCase):
def setUp(self):
super().setUp()
self.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.ethereum import util
from mythril.solidity.soliditycontract import EVMContract
from tests import *
from tests import (
BaseTestCase,
TESTDATA_INPUTS,
TESTDATA_OUTPUTS_EXPECTED,
TESTDATA_OUTPUTS_CURRENT,
)
import re
class GraphTest(BaseTestCase):
def test_generate_graph(self):
for input_file in TESTDATA_INPUTS.iterdir():
output_expected = TESTDATA_OUTPUTS_EXPECTED / (
input_file.name + ".graph.html"

@ -7,6 +7,7 @@ from datetime import datetime
import binascii
import json
import os
from pathlib import Path
import pytest
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.global_state import GlobalState
from mythril.laser.ethereum import svm
from tests import *
from tests import BaseTestCase
SHA256_TEST = [(0, False) for _ in range(4)]
RIPEMD160_TEST = [(0, False) for _ in range(2)]

@ -1,9 +1,10 @@
from unittest import TestCase
from tests import BaseTestCase
from mythril.ethereum.interface.rpc.client import EthJsonRpc
class RpcTest(TestCase):
class RpcTest(BaseTestCase):
client = None
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.global_state import GlobalState
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):

@ -66,7 +66,7 @@
"contract": "Unknown",
"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.",
"function": "_function_0xe11f493e",
"function": "reentrancy()",
"swc-id": "107",
"min_gas_used": 709,
"max_gas_used": 1320,
@ -78,7 +78,7 @@
"contract": "Unknown",
"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.",
"function": "_function_0xe11f493e",
"function": "reentrancy()",
"swc-id": "107",
"min_gas_used": 709,
"max_gas_used": 1320,
@ -90,7 +90,7 @@
"contract": "Unknown",
"debug": "<DEBUG-DATA>",
"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",
"min_gas_used": 6432,
"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
- Type: Informational
- Contract: Unknown
- Function name: `_function_0xe11f493e`
- Function name: `reentrancy()`
- PC address: 858
- 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
- Type: Warning
- Contract: Unknown
- Function name: `_function_0xe11f493e`
- Function name: `reentrancy()`
- PC address: 869
- 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
- Type: Informational
- Contract: Unknown
- Function name: `_function_0xe11f493e`
- Function name: `reentrancy()`
- PC address: 871
- 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
Type: Informational
Contract: Unknown
Function name: _function_0xe11f493e
Function name: reentrancy()
PC address: 858
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.
@ -62,7 +62,7 @@ This contract executes a message call to to another contract. Make sure that the
SWC ID: 107
Type: Warning
Contract: Unknown
Function name: _function_0xe11f493e
Function name: reentrancy()
PC address: 869
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.
@ -72,7 +72,7 @@ The contract account state is changed after an external call. Consider that the
SWC ID: 104
Type: Informational
Contract: Unknown
Function name: _function_0xe11f493e
Function name: reentrancy()
PC address: 871
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.

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

Loading…
Cancel
Save