From 70573ffdd018468ad02a9970c163db9469826c09 Mon Sep 17 00:00:00 2001 From: Konrad Weiss Date: Wed, 13 Jun 2018 11:29:26 +0200 Subject: [PATCH 01/62] Adding base file for solidnotary extension to mythril --- mythril/analysis/modules/build_traces.py | 0 mythril/analysis/modules/dummy.py | 0 mythril/solidnotary.py | 0 mythril/solidnotary/__init__.py | 0 mythril/solidnotary/annotation.py | 0 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 mythril/analysis/modules/build_traces.py create mode 100644 mythril/analysis/modules/dummy.py create mode 100644 mythril/solidnotary.py create mode 100644 mythril/solidnotary/__init__.py create mode 100644 mythril/solidnotary/annotation.py diff --git a/mythril/analysis/modules/build_traces.py b/mythril/analysis/modules/build_traces.py new file mode 100644 index 00000000..e69de29b diff --git a/mythril/analysis/modules/dummy.py b/mythril/analysis/modules/dummy.py new file mode 100644 index 00000000..e69de29b diff --git a/mythril/solidnotary.py b/mythril/solidnotary.py new file mode 100644 index 00000000..e69de29b diff --git a/mythril/solidnotary/__init__.py b/mythril/solidnotary/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mythril/solidnotary/annotation.py b/mythril/solidnotary/annotation.py new file mode 100644 index 00000000..e69de29b From c3131a579dd152b702485146eafd4ecdf1c35a5a Mon Sep 17 00:00:00 2001 From: Konrad Weiss Date: Wed, 13 Jun 2018 11:30:39 +0200 Subject: [PATCH 02/62] Add annotation files such as the wrapper in front of the analysis and the annotation class structures --- annotationWrapper.py | 69 +++++++++++++++++++++++++++++++ mythril/analysis/modules/dummy.py | 33 +++++++++++++++ mythril/solidnotary/annotation.py | 28 +++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 annotationWrapper.py diff --git a/annotationWrapper.py b/annotationWrapper.py new file mode 100644 index 00000000..02337e84 --- /dev/null +++ b/annotationWrapper.py @@ -0,0 +1,69 @@ +""" + Parses the files for annotations to extract the information and do some transformations on the code. + On return the Annotation information is used to change mythrils output regarding the changes code pieces + +""" + +from glob import glob +import re, sys +import json + +newlines = ["\r\n", "\r", "\n"] + +def find_all(a_str, sub): + start = 0 + while True: + start = a_str.find(sub, start) + if start == -1: + return + yield start + start += len(sub) + +def count_elements(source, elements): + ret = 0 + for element in elements: + ret += source.count(element) + return ret + + +def replace_index(text, toReplace, replacement, index): + return text[:index] + replacement + text[(index + len(toReplace)):] + +""" + Here it might be better to split annotations into the containing constraint an the prefix and sufix +""" +def parse_annotation_info(filedata): + annotations = [] + for inv in re.findall(r'//invariant\(([^\)]+)\)(\r\n|\r|\n)', filedata): + match_inv = "//invariant(" + inv[0] + ")" + for pos in find_all(filedata, match_inv + inv[1]): + line = count_elements(filedata[:pos], newlines) + 1 + col = pos - max(map(lambda x: filedata[:pos].rfind(x), newlines)) + annotations.append((pos, line, col, '//invariant(', inv[0], ")", inv[1])) + return set(annotations) + + +def read_write_file(filename): + with open(filename, 'r') as file : + filedata = file.read() + + annotations = parse_annotation_info(filedata) + + annotations = sorted(list(annotations), key=lambda x: x[0], reverse=True) + for annotation in annotations: + filedata = replace_index(filedata, annotation[3] + annotation[4] + annotation[5] + annotation[6], "assert(" + + annotation[4] + ");" + annotation[6], annotation[0]) + # Replace the target string + # filedata = filedata.replace('@ensure', '@invariant') + # filedata = filedata.replace('@invariant', '@ensure') + + with open(filename, 'w') as file: + file.write(filedata) + return annotations + +annot_map = {} + +for sol_file in glob("./*.sol"): + annot_map[sol_file] = read_write_file(sol_file) +json.dump(annot_map, sys.stdout) +print("#end annotations#") diff --git a/mythril/analysis/modules/dummy.py b/mythril/analysis/modules/dummy.py index e69de29b..0d877f85 100644 --- a/mythril/analysis/modules/dummy.py +++ b/mythril/analysis/modules/dummy.py @@ -0,0 +1,33 @@ +from mythril.analysis.report import Issue +import logging + + +''' + To print content of the statespace after it was build. +''' + +def print_obj(obj): + print() + print(obj) + # print(dir(obj)) + print() + + +def execute(statespace): + + logging.debug("Executing module: Transaction End") + + issues = [] + + for k in statespace.nodes: + node = statespace.nodes[k] + + for state in node.states: + + instruction = state.get_current_instruction() + + if(instruction['opcode'] == "STOP"): + print_obj(state.environment.active_account.storage) + # print("opc: {}, add: {} {}".format(instruction['opcode'], instruction['address'], instruction['argument'] if 'argument' in instruction else "")) + + return issues diff --git a/mythril/solidnotary/annotation.py b/mythril/solidnotary/annotation.py index e69de29b..cbf95ce1 100644 --- a/mythril/solidnotary/annotation.py +++ b/mythril/solidnotary/annotation.py @@ -0,0 +1,28 @@ +from re import search + +class Annotation: + + def __init__(self, annstring, lineno, fileoffset): + self.annstring = annstring + + annotation = search(r'@(?P[^\{\}]*)(\{(?P.*)\})?', annstring) + if not annotation: + raise SyntaxError("{} is not a correct annotation".format(annstring)) + + self.aname = annotation['aname'] + self.acontent = annotation['acontent'] + self.lineno = lineno + self.length = len(annstring) + self.fileoffset = fileoffset + +class ContractAnnotation(Annotation): + pass + +class MemberAnnotation(Annotation): + pass + +class InlineAnnotation(Annotation): + pass + +class ContractAnnotation(Annotation): + pass From 0f458e7541f6118d64089c403d6bbb3fe1e881b2 Mon Sep 17 00:00:00 2001 From: Konrad Weiss Date: Wed, 13 Jun 2018 11:33:30 +0200 Subject: [PATCH 03/62] Add the first code to build transaction traces from the storage state at STOP instruction and a combination strategy to build felxibly large trace chain combinations. Starting from the trace of a constructor and using the traces of the external and public functions of a contract. All possible programmstates to a certain depth can be reached. --- mythril/analysis/modules/build_traces.py | 22 ++++++++++ mythril/solidnotary.py | 27 ++++++++++++ mythril/solidnotary/transactiontrace.py | 54 ++++++++++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 mythril/solidnotary/transactiontrace.py diff --git a/mythril/analysis/modules/build_traces.py b/mythril/analysis/modules/build_traces.py index e69de29b..d20dae21 100644 --- a/mythril/analysis/modules/build_traces.py +++ b/mythril/analysis/modules/build_traces.py @@ -0,0 +1,22 @@ +from mythril.analysis.report import Issue +import logging + + +''' + Build execution traces from the statespace +''' + +def print_obj(obj): + print() + print(obj) + # print(dir(obj)) + print() + + +def execute(statespace): + + logging.debug("Executing module: Transaction End") + + traces = [] + + return [] diff --git a/mythril/solidnotary.py b/mythril/solidnotary.py index e69de29b..2207b26f 100644 --- a/mythril/solidnotary.py +++ b/mythril/solidnotary.py @@ -0,0 +1,27 @@ +import logging +from mythril.solidnotary.transactiontrace import TransactionTrace + +class SolidNotary: + + def __init__(self): + # Todo Parse Annotations and store them in an additional structure + # Todo receive a list of files or a file, these are modified for the analysis + pass + + def notarize(self): + # Todo Instantiate an instance of Mythril, analyze and print the result + # Todo Find how they are storing results + pass + + def get_transaction_traces(statespace): + logging.debug("Executing module: Transaction End") + + traces = [] + + for k in statespace.nodes: + node = statespace.nodes[k] + for state in node.states: + instruction = state.get_current_instruction() + if instruction['opcode'] == "STOP": + traces.append(TransactionTrace(state.environment.active_account.storage)) + return traces diff --git a/mythril/solidnotary/transactiontrace.py b/mythril/solidnotary/transactiontrace.py new file mode 100644 index 00000000..cee4b6de --- /dev/null +++ b/mythril/solidnotary/transactiontrace.py @@ -0,0 +1,54 @@ +class TransactionTrace: + + def __init__(self, storage): + self.storage = storage + # Todo Identifiy addional trace information such as blocknumber and more + + """ + Applies the new trace tt on a possibly even changed trace self. + """ + def apply_trace(self, tt): + if tt is None: + return self + # Todo implement application of a trace on a existing trace. + return None + + def apply_traces_parallel(self, traces): + combined_traces = [] + for trace in traces: + combined_traces.append(self.apply_trace(trace)) + return combined_traces + + def apply_exact_trace_levels(self, traces, depth): + # Todo maybe some faster trace build not building one level at a time to e.g. + # Todo reach level 17 but build 2, then 4, then 8 and then 16 then 17 + trace_lvl_n = [self] + for i in range(depth): + trace_lvl_np1 = [] + for trace in trace_lvl_n: + trace_lvl_np1.append(trace.apply_traces_parallel(traces)) + if deep_equals(trace_lvl_np1, trace_lvl_n): # Fixpoint detected, function needs to ignore lists, dicts and objects. + return trace_lvl_n + trace_lvl_n = trace_lvl_np1 + return trace_lvl_n + + def apply_up_to_trace_levels(self, traces, depth): + traces_up_to = [[self]] # elements are trace_levels + for i in range(depth): + trace_lvl_np1 = [] + for trace in traces_up_to[-1]: + trace_lvl_np1.append(trace.apply_traces_parallel(traces)) + for trace_lvl_i in traces_up_to: + # the following might be faster to check when using a content representing hash + if deep_equals(trace_lvl_np1, trace_lvl_i): # cycle in the traces of trace chains detected: levels + # while repeat themselves, function needs to ignore lists, dicts and objects. + return traces_up_to + traces_up_to.append(trace_lvl_np1) + return traces_up_to + + """ + Either do only deep checing here and use the proper trace or storage_slot reduction in the apply function. Or do + both here. + """ + def deep_equals(trace_lvl1, trace_lvl2): + pass From 02aa2952e4336d7c4ebaaca412d84baed8426e79 Mon Sep 17 00:00:00 2001 From: Konrad Weiss Date: Thu, 14 Jun 2018 14:51:20 +0200 Subject: [PATCH 04/62] Adding first working transacation application purely working on the stack and not considering the combination of constraints --- mythril/analysis/modules/build_traces.py | 8 +++++ mythril/analysis/modules/dummy.py | 6 ++-- mythril/{ => solidnotary}/solidnotary.py | 4 +-- mythril/solidnotary/transactiontrace.py | 42 +++++++++++++++++++----- 4 files changed, 47 insertions(+), 13 deletions(-) rename mythril/{ => solidnotary}/solidnotary.py (92%) diff --git a/mythril/analysis/modules/build_traces.py b/mythril/analysis/modules/build_traces.py index d20dae21..73a39a07 100644 --- a/mythril/analysis/modules/build_traces.py +++ b/mythril/analysis/modules/build_traces.py @@ -1,4 +1,6 @@ from mythril.analysis.report import Issue +from mythril.solidnotary.transactiontrace import TransactionTrace +from mythril.solidnotary.solidnotary import SolidNotary import logging @@ -19,4 +21,10 @@ def execute(statespace): traces = [] + traces = SolidNotary().get_transaction_traces(statespace) + + print("==== Second level traces ====") + for trace in traces: + print(trace.apply_traces_parallel(traces)) + return [] diff --git a/mythril/analysis/modules/dummy.py b/mythril/analysis/modules/dummy.py index 0d877f85..f795504c 100644 --- a/mythril/analysis/modules/dummy.py +++ b/mythril/analysis/modules/dummy.py @@ -9,7 +9,8 @@ import logging def print_obj(obj): print() print(obj) - # print(dir(obj)) + print(type(obj)) + print(dir(obj)) print() @@ -27,7 +28,8 @@ def execute(statespace): instruction = state.get_current_instruction() if(instruction['opcode'] == "STOP"): - print_obj(state.environment.active_account.storage) + for k,v in state.environment.active_account.storage.items(): + print_obj(v) # print("opc: {}, add: {} {}".format(instruction['opcode'], instruction['address'], instruction['argument'] if 'argument' in instruction else "")) return issues diff --git a/mythril/solidnotary.py b/mythril/solidnotary/solidnotary.py similarity index 92% rename from mythril/solidnotary.py rename to mythril/solidnotary/solidnotary.py index 2207b26f..dd5f0806 100644 --- a/mythril/solidnotary.py +++ b/mythril/solidnotary/solidnotary.py @@ -13,7 +13,7 @@ class SolidNotary: # Todo Find how they are storing results pass - def get_transaction_traces(statespace): + def get_transaction_traces(self, statespace): logging.debug("Executing module: Transaction End") traces = [] @@ -24,4 +24,4 @@ class SolidNotary: instruction = state.get_current_instruction() if instruction['opcode'] == "STOP": traces.append(TransactionTrace(state.environment.active_account.storage)) - return traces + return traces diff --git a/mythril/solidnotary/transactiontrace.py b/mythril/solidnotary/transactiontrace.py index cee4b6de..d2937d66 100644 --- a/mythril/solidnotary/transactiontrace.py +++ b/mythril/solidnotary/transactiontrace.py @@ -1,16 +1,45 @@ +from z3 import * + class TransactionTrace: def __init__(self, storage): self.storage = storage + # Todo the constraints of a trace are probably also important here and have to be somehow aggregated # Todo Identifiy addional trace information such as blocknumber and more + """ + Either do only deep checing here and use the proper trace or storage_slot reduction in the apply function. Or do + both here. + """ + + def deep_equals(trace_lvl1, trace_lvl2): + return set(trace_lvl1) == set(trace_lvl2) # Todo Impelement an ACTUAL deep comparison + + def simplify_storage(self): + for k,v in self.storage.items(): + # Todo explore the arguments of this storage simplification in z3 to find ways to further simplify and to + # sort this expressions for equality comparison + self.storage[k] = simplify(v) + """ Applies the new trace tt on a possibly even changed trace self. """ def apply_trace(self, tt): if tt is None: return self - # Todo implement application of a trace on a existing trace. + subs_map = list(map(lambda x: (BitVec("storage_" + str(x[0]), 256), x[1]), tt.storage.items())) + print("_________") + print(subs_map) + print(self.storage) + print("+++++++++") + for k,v in self.storage.items(): + self.storage[k] = substitute(v, subs_map) + # Todo Add combination of constraints, this might by tricky if we cannot identify which entrancy constraints of + # self can be omitted (e.g. when related storage locations were overwritten) + print(self.storage) + print("=========") + self.simplify_storage() + print(self.storage) return None def apply_traces_parallel(self, traces): @@ -27,7 +56,7 @@ class TransactionTrace: trace_lvl_np1 = [] for trace in trace_lvl_n: trace_lvl_np1.append(trace.apply_traces_parallel(traces)) - if deep_equals(trace_lvl_np1, trace_lvl_n): # Fixpoint detected, function needs to ignore lists, dicts and objects. + if TransactionTrace.deep_equals(trace_lvl_np1, trace_lvl_n): # Fixpoint detected, function needs to ignore lists, dicts and objects. return trace_lvl_n trace_lvl_n = trace_lvl_np1 return trace_lvl_n @@ -40,15 +69,10 @@ class TransactionTrace: trace_lvl_np1.append(trace.apply_traces_parallel(traces)) for trace_lvl_i in traces_up_to: # the following might be faster to check when using a content representing hash - if deep_equals(trace_lvl_np1, trace_lvl_i): # cycle in the traces of trace chains detected: levels + if TransactionTrace.deep_equals(trace_lvl_np1, trace_lvl_i): # cycle in the traces of trace chains detected: levels # while repeat themselves, function needs to ignore lists, dicts and objects. return traces_up_to traces_up_to.append(trace_lvl_np1) return traces_up_to - """ - Either do only deep checing here and use the proper trace or storage_slot reduction in the apply function. Or do - both here. - """ - def deep_equals(trace_lvl1, trace_lvl2): - pass + From df6a88e0621b27751e103194a61008cb4f0b91ea Mon Sep 17 00:00:00 2001 From: Konrad Weiss Date: Thu, 14 Jun 2018 15:52:53 +0200 Subject: [PATCH 05/62] Building transactionchains of up to length 4 for trial, correcting some mistakes when creating transaction and combining them --- mythril/analysis/modules/build_traces.py | 3 ++- mythril/solidnotary/transactiontrace.py | 22 +++++++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/mythril/analysis/modules/build_traces.py b/mythril/analysis/modules/build_traces.py index 73a39a07..2e6f7c02 100644 --- a/mythril/analysis/modules/build_traces.py +++ b/mythril/analysis/modules/build_traces.py @@ -25,6 +25,7 @@ def execute(statespace): print("==== Second level traces ====") for trace in traces: - print(trace.apply_traces_parallel(traces)) + print(trace.apply_up_to_trace_levels(traces, 4)) + return [] diff --git a/mythril/solidnotary/transactiontrace.py b/mythril/solidnotary/transactiontrace.py index d2937d66..0c52d86f 100644 --- a/mythril/solidnotary/transactiontrace.py +++ b/mythril/solidnotary/transactiontrace.py @@ -1,4 +1,5 @@ from z3 import * +from copy import deepcopy class TransactionTrace: @@ -27,20 +28,23 @@ class TransactionTrace: def apply_trace(self, tt): if tt is None: return self - subs_map = list(map(lambda x: (BitVec("storage_" + str(x[0]), 256), x[1]), tt.storage.items())) + new_trace = deepcopy(tt) + subs_map = list(map(lambda x: (BitVec("storage_" + str(x[0]), 256), x[1]), self.storage.items())) print("_________") print(subs_map) print(self.storage) + print("#########") + print(new_trace.storage) print("+++++++++") - for k,v in self.storage.items(): - self.storage[k] = substitute(v, subs_map) + for k,v in new_trace.storage.items(): + new_trace.storage[k] = substitute(v, subs_map) # Todo Add combination of constraints, this might by tricky if we cannot identify which entrancy constraints of # self can be omitted (e.g. when related storage locations were overwritten) - print(self.storage) + print(new_trace) print("=========") - self.simplify_storage() - print(self.storage) - return None + new_trace.simplify_storage() + print(new_trace.storage) + return new_trace def apply_traces_parallel(self, traces): combined_traces = [] @@ -55,7 +59,7 @@ class TransactionTrace: for i in range(depth): trace_lvl_np1 = [] for trace in trace_lvl_n: - trace_lvl_np1.append(trace.apply_traces_parallel(traces)) + trace_lvl_np1.extend(trace.apply_traces_parallel(traces)) if TransactionTrace.deep_equals(trace_lvl_np1, trace_lvl_n): # Fixpoint detected, function needs to ignore lists, dicts and objects. return trace_lvl_n trace_lvl_n = trace_lvl_np1 @@ -66,7 +70,7 @@ class TransactionTrace: for i in range(depth): trace_lvl_np1 = [] for trace in traces_up_to[-1]: - trace_lvl_np1.append(trace.apply_traces_parallel(traces)) + trace_lvl_np1.extend(trace.apply_traces_parallel(traces)) for trace_lvl_i in traces_up_to: # the following might be faster to check when using a content representing hash if TransactionTrace.deep_equals(trace_lvl_np1, trace_lvl_i): # cycle in the traces of trace chains detected: levels From d6bc09e2e9bb8cf4c6801e07d6a9bf561476eb2e Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Tue, 19 Jun 2018 20:46:09 +0530 Subject: [PATCH 06/62] add typecheck for the contracts --- mythril/laser/ethereum/natives.py | 18 ++++++++++++++---- mythril/laser/ethereum/svm.py | 16 +++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/mythril/laser/ethereum/natives.py b/mythril/laser/ethereum/natives.py index 98607165..e32ab81a 100644 --- a/mythril/laser/ethereum/natives.py +++ b/mythril/laser/ethereum/natives.py @@ -1,10 +1,11 @@ # -*- coding: utf8 -*- + import copy import hashlib + import coincurve from py_ecc.secp256k1 import N as secp256k1n - from mythril.laser.ethereum.helper import ALL_BYTES, bytearray_to_int, concrete_int_to_bytes, sha3, zpad @@ -40,7 +41,10 @@ def extract32(data, i): def ecrecover(data): - data = bytearray(data) + try: + data = bytearray(data) + except TypeError: + return "ecrecover_"+str(data) message = b''.join(map(lambda x: ALL_BYTES[x], data[0:32])) v = extract32(data, 32) r = extract32(data, 64) @@ -56,12 +60,18 @@ def ecrecover(data): def sha256(data): - data = bytes(data) + try: + data = bytes(data) + except TypeError: + return "sha256_"+str(data) return hashlib.sha256(data).digest() def ripemd160(data): - data = bytes(data) + try: + data = bytes(data) + except TypeError: + return "ripemd160_"+str(data) return 12*[0]+[i for i in hashlib.new('ripemd160', data).digest()] diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py index d2f003ee..1c24eeaf 100644 --- a/mythril/laser/ethereum/svm.py +++ b/mythril/laser/ethereum/svm.py @@ -994,12 +994,14 @@ class LaserEVM: state.stack.append(ret) continue - if int(callee_address, 16) < 5 and int(callee_address, 16) > 0: + if 0 < int(callee_address, 16) < 5: logging.info("Native contract called: " + callee_address) calldata, calldata_type = self._get_calldata(meminstart, meminsz, state, pad=False) if calldata == [] and calldata_type == CalldataType.SYMBOLIC: logging.debug("CALL with symbolic data not supported") + ret = BitVec("retval_" + str(instr['address']), 256) + state.stack.append(ret) continue data = natives.native_contracts(int(callee_address, 16 ), calldata) @@ -1008,14 +1010,18 @@ class LaserEVM: mem_out_sz = memoutsz.as_long() except AttributeError: logging.debug("CALL with symbolic start or offset not supported") + ret = BitVec("retval_" + str(instr['address']), 256) + state.stack.append(ret) continue state.mem_extend(mem_out_start, mem_out_sz) - for i in range(min(len(data), mem_out_sz)): # If more data is used then it's chopped off - state.memory[mem_out_start+i] = data[i] + try: + for i in range(min(len(data), mem_out_sz)): # If more data is used then it's chopped off + state.memory[mem_out_start+i] = data[i] + except: + state.memory[mem_out_start] = BitVec(data, 256) + state.stack.append(1) - ret = BitVec("retval_" + str(instr['address']), 256) - state.stack.append(ret) continue try: From 62ae88b652c07cb76c515bdd5d0b41ca89ead82d Mon Sep 17 00:00:00 2001 From: Konrad Weiss Date: Wed, 20 Jun 2018 13:03:23 +0200 Subject: [PATCH 07/62] Adding chaining of transactions, using z3 substitution to carry over intertransactional sym. values and combine constraints. Usage of z3 simplification on a (currently only) singular constraint value. and satisfiability checking to consider only valid transactions. --- mythril/analysis/modules/build_traces.py | 33 +++-- mythril/analysis/modules/dummy.py | 20 +-- mythril/solidnotary/solidnotary.py | 48 ++++++-- mythril/solidnotary/transactiontrace.py | 150 ++++++++++++++++++++--- mythril/solidnotary/z3utility.py | 19 +++ 5 files changed, 232 insertions(+), 38 deletions(-) create mode 100644 mythril/solidnotary/z3utility.py diff --git a/mythril/analysis/modules/build_traces.py b/mythril/analysis/modules/build_traces.py index 2e6f7c02..0d6bcd38 100644 --- a/mythril/analysis/modules/build_traces.py +++ b/mythril/analysis/modules/build_traces.py @@ -1,6 +1,7 @@ from mythril.analysis.report import Issue from mythril.solidnotary.transactiontrace import TransactionTrace -from mythril.solidnotary.solidnotary import SolidNotary +from mythril.solidnotary.solidnotary import get_transaction_traces +from mythril.solidnotary.z3utility import are_satisfiable import logging @@ -10,8 +11,12 @@ import logging def print_obj(obj): print() + print(type(obj)) print(obj) - # print(dir(obj)) + print(dir(obj)) + print(obj.decl()) + print(obj.params()) + print(obj.children()) print() @@ -21,11 +26,25 @@ def execute(statespace): traces = [] - traces = SolidNotary().get_transaction_traces(statespace) - - print("==== Second level traces ====") + traces = get_transaction_traces(statespace) for trace in traces: - print(trace.apply_up_to_trace_levels(traces, 4)) - + comp_trace_lvls = trace.apply_up_to_trace_levels(traces, 3) + for trace_lvl in comp_trace_lvls: + for t in trace_lvl: + if t.lvl == 4: + t.pp_trace() + +# for trace in traces: +# trace.pp_trace() + + #print("==== Second level traces ====") + #for trace in traces: + # comp_trace_lvls = trace.apply_up_to_trace_levels(traces, 1) + #for trace_lvl in range(len(comp_trace_lvls)): + # print("\nTrace level: " + str(trace_lvl)) + #for comp_trace in comp_trace_lvls[trace_lvl]: + # print(comp_trace.storage) + # for k, s in comp_trace.storage.items(): + # print_obj(s) return [] diff --git a/mythril/analysis/modules/dummy.py b/mythril/analysis/modules/dummy.py index f795504c..44b0879f 100644 --- a/mythril/analysis/modules/dummy.py +++ b/mythril/analysis/modules/dummy.py @@ -1,5 +1,7 @@ from mythril.analysis.report import Issue import logging +from z3 import * +from mythril.solidnotary.z3utility import simplify_constraints, are_satisfiable ''' @@ -20,16 +22,20 @@ def execute(statespace): issues = [] - for k in statespace.nodes: - node = statespace.nodes[k] +# for k in statespace.nodes: +# node = statespace.nodes[k] - for state in node.states: +# for state in node.states: - instruction = state.get_current_instruction() +# instruction = state.get_current_instruction() - if(instruction['opcode'] == "STOP"): - for k,v in state.environment.active_account.storage.items(): - print_obj(v) +# if(instruction['opcode'] == "STOP"): +# print() + #print(state.environment.active_account.storage) + # print(state.mstate.constraints) + #simpl_const = simplify_constraints(state.mstate.constraints) + #print(simpl_const) + #print(are_satisfiable(simpl_const)) # print("opc: {}, add: {} {}".format(instruction['opcode'], instruction['address'], instruction['argument'] if 'argument' in instruction else "")) return issues diff --git a/mythril/solidnotary/solidnotary.py b/mythril/solidnotary/solidnotary.py index dd5f0806..07798a45 100644 --- a/mythril/solidnotary/solidnotary.py +++ b/mythril/solidnotary/solidnotary.py @@ -1,5 +1,10 @@ import logging from mythril.solidnotary.transactiontrace import TransactionTrace +from mythril.solidnotary.z3utility import are_satisfiable +from laser.ethereum.svm import Environment, GlobalState, CalldataType +from z3 import BitVec, simplify, is_false, is_bool, is_true, Solver, sat +from copy import deepcopy + class SolidNotary: @@ -13,15 +18,38 @@ class SolidNotary: # Todo Find how they are storing results pass - def get_transaction_traces(self, statespace): - logging.debug("Executing module: Transaction End") +def get_transaction_traces(statespace): + logging.debug("Executing module: Transaction End") + + traces = [] + + for k in statespace.nodes: + node = statespace.nodes[k] + for state in node.states: + instruction = state.get_current_instruction() + if instruction['opcode'] == "STOP": + if are_satisfiable(state.mstate.constraints): + traces.append(TransactionTrace(state.environment.active_account.storage, state.mstate.constraints)) + return traces + +def get_t_indexed_environment(active_account, index): + + # Initialize the execution environment + + environment = Environment( + active_account, + BitVec("caller_"+str(index), 256), + [], + BitVec("gasprice_"+str(index), 256), + BitVec("callvalue_"+str(index), 256), + BitVec("origin_"+str(index), 256), + calldata_type=CalldataType.SYMBOLIC, + ) + + return environment - traces = [] +def get_t_indexed_globstate(active_account, index): + environment = get_t_indexed_environment(active_account, index) + # Todo is this just some set of preset accounts? How should we deal with it + return GlobalState(self.accounts, environment) - for k in statespace.nodes: - node = statespace.nodes[k] - for state in node.states: - instruction = state.get_current_instruction() - if instruction['opcode'] == "STOP": - traces.append(TransactionTrace(state.environment.active_account.storage)) - return traces diff --git a/mythril/solidnotary/transactiontrace.py b/mythril/solidnotary/transactiontrace.py index 0c52d86f..e8b4c9a3 100644 --- a/mythril/solidnotary/transactiontrace.py +++ b/mythril/solidnotary/transactiontrace.py @@ -1,13 +1,128 @@ from z3 import * from copy import deepcopy +import re +from mythril.solidnotary.z3utility import are_satisfiable, simplify_constraints + +""" + Returns whether or the specified symbolic string stands for a data value that can be different from transaction to + transaction without the need of an intermediate call to the contract (e.g. a transaction params, blocknumber, ...) +""" + + +def is_t_variable(var): + var = str(var) + if (var.startswith("caller") + or var.startswith("gasprice") + or var.startswith("callvalue") + or var.startswith("origin") + or var.startswith("calldata_") + or var.startswith("calldatasize_") + or var.startswith("balance_at") + or var.startswith("KECCAC_mem_") + or var.startswith("keccac_") + or var.startswith("gasprice") + or var.startswith("extcodesize") + or var.startswith("returndatasize") + # or var.startswith(var, "blockhash_block_") should not change between transactions + or var.startswith("coinbase") + or var.startswith("timestamp") + or var.startswith("block_number") + or var.startswith("block_difficulty") + or var.startswith("block_gaslimit") + or var.startswith("mem_") + or var.startswith("msize") + or var.startswith("gas") + or var.startswith("retval_") + or var.startswith("keccac_")): + return True + else: + return False + + +def filter_for_t_variable_data(sym_vars): + return list(filter(lambda x: is_t_variable(x), sym_vars)) class TransactionTrace: - def __init__(self, storage): - self.storage = storage + def __init__(self, storage, constraints, lvl=1): + self.storage = storage # Todo give all non storage symbolic values that can be different every transaction the number one + self.constraints = constraints # Todo eliminate all constraints that are not regarding the beginning of the transaction may not be necessary + # eliminate all constraints that only contain names not in the set of names from storage + self.constraints = simplify_constraints(self.constraints) # Todo simplification of the sum of constraints + self.tran_constraints = deepcopy(self.constraints) + self.lvl = lvl + self.sym_names = self.extract_sym_names_from_storage() + self.sym_names.extend(self.extract_sym_names_from_constraints()) + if lvl == 1: + self.set_transaction_idx() + # Todo the constraints of a trace are probably also important here and have to be somehow aggregated # Todo Identifiy addional trace information such as blocknumber and more + def __str__(self): + return str(self.as_dict()) + + def as_dict(self): + + return {'lvl': self.lvl, 'storage': str(self.storage), 'constraints': str(self.constraints)} + + def pp_trace(self): + print() + print("Trace lvl: {}".format(self.lvl)) + print("Storage: {}".format({k: str(v).replace("\n", " ") for k, v in self.storage.items()})) + print("Constraints: {}".format(list(map(lambda x: str(x).replace("\n", " "), self.constraints)))) + print() + + + def add_transaction_idx(self, offset): + new_names = [] + for name in self.sym_names: + matched_name = re.search(r't([0-9]+)(_.*)', name) + num = int(matched_name.group(1)) + offset + new_names.append("t" + str(num) + matched_name.group(2)) + repl_tup = list(zip(self.sym_names, new_names)) + + self.substitute_bv_names(repl_tup) + + self.sym_names = new_names + + def set_transaction_idx(self): + repl_tup = [] + new_sym_names = [] + for name in self.sym_names: + repl_tup.append((name, "t1_" + name)) + new_sym_names.append("t1_" + name) + self.sym_names = new_sym_names + self.substitute_bv_names(repl_tup) + + def substitute_bv_names(self, subs_tuple): + subs_tuples = list(map(lambda name_t: (BitVec(name_t[0], 256), BitVec(name_t[1], 256)), subs_tuple)) + for s_num, slot in self.storage.items(): + self.storage[s_num] = substitute(slot, subs_tuples) + for c_idx in range(len(self.constraints)): + self.constraints[c_idx] = substitute(self.constraints[c_idx], subs_tuples) + + def extract_sym_names(self, obj): + if (not hasattr(obj, 'children') or len(obj.children()) == 0) and hasattr(obj, 'decl') : + return [str(obj.decl())] + else: + sym_vars = [] + for c in obj.children(): + sym_vars.extend(self.extract_sym_names(c)) + return sym_vars + + def extract_sym_names_from_constraints(self): + sym_names = [] + for k,v in self.storage.items(): + sym_names.extend(self.extract_sym_names(v)) + return filter_for_t_variable_data(sym_names) + + def extract_sym_names_from_storage(self): + sym_names = [] + for v in self.constraints: + sym_names.extend(self.extract_sym_names(v)) + return filter_for_t_variable_data(sym_names) # Todo Check whether here it is the right choice too, to filter ... + """ Either do only deep checing here and use the proper trace or storage_slot reduction in the apply function. Or do both here. @@ -29,28 +144,28 @@ class TransactionTrace: if tt is None: return self new_trace = deepcopy(tt) + new_trace.add_transaction_idx(self.lvl) subs_map = list(map(lambda x: (BitVec("storage_" + str(x[0]), 256), x[1]), self.storage.items())) - print("_________") - print(subs_map) - print(self.storage) - print("#########") - print(new_trace.storage) - print("+++++++++") for k,v in new_trace.storage.items(): new_trace.storage[k] = substitute(v, subs_map) - # Todo Add combination of constraints, this might by tricky if we cannot identify which entrancy constraints of + for c_idx in range(len(new_trace.constraints)): + new_trace.constraints[c_idx] = substitute(new_trace.constraints[c_idx], subs_map) + new_trace.lvl += self.lvl + new_trace.sym_names.extend(deepcopy(self.sym_names)) # self can be omitted (e.g. when related storage locations were overwritten) - print(new_trace) - print("=========") new_trace.simplify_storage() - print(new_trace.storage) - return new_trace + new_trace.constraints = simplify_constraints(new_trace.constraints) + # Simplify constraints in there sum to eliminate subconstraints + if are_satisfiable(new_trace.constraints): + return new_trace + else: + return None def apply_traces_parallel(self, traces): combined_traces = [] for trace in traces: combined_traces.append(self.apply_trace(trace)) - return combined_traces + return list(filter(lambda t: not t is None, combined_traces)) def apply_exact_trace_levels(self, traces, depth): # Todo maybe some faster trace build not building one level at a time to e.g. @@ -79,4 +194,11 @@ class TransactionTrace: traces_up_to.append(trace_lvl_np1) return traces_up_to + # Todo Maybe implement a function that checks whether two traces are combinable before creating objekts, adv. in + # case they are not the object creation doe not have to be done. Investigate whether a suicide trace completely + # stopes the contract from being executable. In that case a suicided transaction also is not combinable with + # successive transactions. + + # Todo write a function that allows to specify a function/invocable to explore the tracechain space in DFS manner + diff --git a/mythril/solidnotary/z3utility.py b/mythril/solidnotary/z3utility.py new file mode 100644 index 00000000..50b80bac --- /dev/null +++ b/mythril/solidnotary/z3utility.py @@ -0,0 +1,19 @@ +from z3 import Solver, sat, simplify, is_bool, is_true, is_false +from copy import deepcopy + +def are_satisfiable(constraints): + s = Solver() + for c in constraints: + s.add(c) + return s.check() == sat + +def simplify_constraints(constraints): # Todo simplification of the sum of constraints + simp_const = [] + for const in constraints: + simp_const.append(simplify(const)) + simp_const = list(filter(lambda c: not is_bool(c) or not is_true(c), simp_const)) + falses = list(filter(lambda c: is_bool(c) and is_false(c), simp_const)) + if len(falses) > 0: + return [deepcopy(falses[0])] + + return simp_const From 69372853997b7806c2ba1d4f692c8dd82be2ef33 Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Sun, 24 Jun 2018 17:24:08 +0530 Subject: [PATCH 08/62] refactor search feature --- mythril/ether/contractstorage.py | 157 ------------------------------- mythril/interfaces/cli.py | 40 ++++---- mythril/leveldb/client.py | 58 ++++-------- mythril/leveldb/state.py | 8 +- mythril/mythril.py | 93 ++++++++++-------- requirements.txt | 4 +- setup.py | 41 ++++---- tests/contractstorage_test.py | 32 ------- 8 files changed, 115 insertions(+), 318 deletions(-) delete mode 100644 mythril/ether/contractstorage.py delete mode 100644 tests/contractstorage_test.py diff --git a/mythril/ether/contractstorage.py b/mythril/ether/contractstorage.py deleted file mode 100644 index ab824c1d..00000000 --- a/mythril/ether/contractstorage.py +++ /dev/null @@ -1,157 +0,0 @@ -import os -import hashlib -import persistent.list -import transaction -from BTrees.OOBTree import BTree -import ZODB -from ZODB import FileStorage -from multiprocessing import Pool -import logging -from mythril.ether.ethcontract import ETHContract, InstanceList -from mythril import ether -import time - -BLOCKS_PER_THREAD = 256 -NUM_THREADS = 8 - - -def get_persistent_storage(db_dir=None): - if not db_dir: - db_dir = os.path.join(os.path.expanduser('~'), ".mythril") - - if not os.path.exists(db_dir): - os.makedirs(db_dir) - - db_path = os.path.join(db_dir, "contractstorage.fs") - - storage = FileStorage.FileStorage(db_path) - db = ZODB.DB(storage) - connection = db.open() - storage_root = connection.root() - - try: - contract_storage = storage_root['contractStorage'] - except KeyError: - contract_storage = ContractStorage() - storage_root['contractStorage'] = contract_storage - - return contract_storage, db - -class SyncBlocks(object): - ''' - Processes the block chunk - ''' - - def __init__(self, eth): - self.eth = eth - - def __call__(self, startblock): - ''' - Processesing method - ''' - logging.info("SYNC_BLOCKS %d to %d" % (startblock, startblock + BLOCKS_PER_THREAD)) - - contracts = {} - - for blockNum in range(startblock, startblock + BLOCKS_PER_THREAD): - block = self.eth.eth_getBlockByNumber(blockNum) - - for tx in block['transactions']: - - if not tx['to']: - - receipt = self.eth.eth_getTransactionReceipt(tx['hash']) - - if receipt is not None: - - contract_address = receipt['contractAddress'] - - contract_code = self.eth.eth_getCode(contract_address) - contract_balance = self.eth.eth_getBalance(contract_address) - - if not contract_balance: - continue - - ethcontract = ETHContract(contract_code, tx['input']) - - m = hashlib.md5() - m.update(contract_code.encode('UTF-8')) - contract_hash = m.digest() - - contracts[contract_hash] = {'ethcontract': ethcontract, 'address': contract_address, 'balance': contract_balance} - - blockNum -= 1 - - return contracts - -class ContractStorage(persistent.Persistent): - - def __init__(self): - self.contracts = BTree() - self.instance_lists = BTree() - self.last_block = 0 - self.eth = None - - def get_contract_by_hash(self, contract_hash): - return self.contracts[contract_hash] - - def initialize(self, eth): - - self.eth = eth - - if self.last_block: - blockNum = self.last_block - print("Resuming synchronization from block " + str(blockNum)) - else: - - blockNum = eth.eth_blockNumber() - print("Starting synchronization from latest block: " + str(blockNum)) - - processed = 0 - - while (blockNum > 0): - - numbers = [] - - for i in range(1, NUM_THREADS + 1): - numbers.append(max(0, blockNum - (i * BLOCKS_PER_THREAD))) - - pool = Pool(NUM_THREADS, initargs=(self.eth)) - results = pool.map(SyncBlocks(self.eth), numbers) - pool.close() - pool.join() - - for result in results: - for (contract_hash, data) in result.items(): - - try: - self.contracts[contract_hash] - except KeyError: - self.contracts[contract_hash] = data['ethcontract'] - m = InstanceList() - self.instance_lists[contract_hash] = m - - self.instance_lists[contract_hash].add(data['address'], data['balance']) - - blockNum -= NUM_THREADS * BLOCKS_PER_THREAD - processed += NUM_THREADS * BLOCKS_PER_THREAD - self.last_block = blockNum - transaction.commit() - - cost_time = time.time() - ether.start_time - print("%d blocks processed (in %d seconds), %d unique contracts in database, next block: %d" % (processed, cost_time, len(self.contracts), max(0, blockNum))) - - # If we've finished initializing the database, start over from the end of the chain if we want to initialize again - self.last_block = 0 - print("Finished synchronization") - - def search(self, expression, callback_func): - - all_keys = list(self.contracts) - - for k in all_keys: - - if self.contracts[k].matches_expression(expression): - m = self.instance_lists[k] - - callback_func(k.hex(), self.contracts[k], m.addresses, m.balances) diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index 834855fb..b5e611fa 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -49,8 +49,9 @@ def main(): outputs.add_argument('--verbose-report', action='store_true', help='Include debugging information in report') database = parser.add_argument_group('local contracts database') - database.add_argument('--init-db', action='store_true', help='initialize the contract database') database.add_argument('-s', '--search', help='search the contract database', metavar='EXPRESSION') + database.add_argument('--leveldb-dir', help='specify leveldb directory for search or direct access operations', metavar='LEVELDB_PATH') + database.add_argument('--search-all', action='store_true', help='search all contracts instead of active (non-zero balance) only') utilities = parser.add_argument_group('utilities') utilities.add_argument('--hash', help='calculate function signature hash', metavar='SIGNATURE') @@ -67,13 +68,13 @@ def main(): options.add_argument('--phrack', action='store_true', help='Phrack-style call graph') options.add_argument('--enable-physics', action='store_true', help='enable graph physics simulation') options.add_argument('-v', type=int, help='log level (0-2)', metavar='LOG_LEVEL') - options.add_argument('--leveldb', help='enable direct leveldb access operations', metavar='LEVELDB_PATH') rpc = parser.add_argument_group('RPC options') rpc.add_argument('-i', action='store_true', help='Preset: Infura Node service (Mainnet)') rpc.add_argument('--rpc', help='custom RPC settings', metavar='HOST:PORT / ganache / infura-[network_name]') rpc.add_argument('--rpctls', type=bool, default=False, help='RPC connection over TLS') rpc.add_argument('--ipc', action='store_true', help='Connect via local IPC') + rpc.add_argument('--leveldb', action='store_true', help='Enable direct leveldb access operations') # Get config values @@ -87,7 +88,7 @@ def main(): # Parse cmdline args - if not (args.search or args.init_db or args.hash or args.disassemble or args.graph or args.fire_lasers + if not (args.search or args.hash or args.disassemble or args.graph or args.fire_lasers or args.storage or args.truffle or args.statespace_json): parser.print_help() sys.exit() @@ -106,27 +107,31 @@ def main(): try: # the mythril object should be our main interface - #init_db = None, infura = None, rpc = None, rpctls = None, ipc = None, + #infura = None, rpc = None, rpctls = None, ipc = None, #solc_args = None, dynld = None, max_recursion_depth = 12): mythril = Mythril(solv=args.solv, dynld=args.dynld, solc_args=args.solc_args) - if args.leveldb: - # Open LevelDB if specified - mythril.set_db_leveldb(args.leveldb) - - elif (args.address or args.init_db) and not args.leveldb: + if args.address and not args.leveldb: # Establish RPC/IPC connection if necessary if args.i: - mythril.set_db_rpc_infura() + mythril.set_api_rpc_infura() elif args.rpc: - mythril.set_db_rpc(rpc=args.rpc, rpctls=args.rpctls) + mythril.set_api_rpc(rpc=args.rpc, rpctls=args.rpctls) elif args.ipc: - mythril.set_db_ipc() + mythril.set_api_ipc() else: - mythril.set_db_rpc_localhost() + mythril.set_api_rpc_localhost() + elif args.leveldb or args.search: + # Open LevelDB if necessary + mythril.set_api_leveldb(mythril.leveldb_dir if not args.leveldb_dir else args.leveldb_dir) + + if args.search: + # Database search ops + mythril.search_db(args.search, args.search_all) + sys.exit() if args.truffle: try: @@ -137,15 +142,6 @@ def main(): "Build directory not found. Make sure that you start the analysis from the project root, and that 'truffle compile' has executed successfully.") sys.exit() - elif args.search: - # Database search ops - mythril.search_db(args.search) - sys.exit() - - elif args.init_db: - mythril.init_db() - sys.exit() - # Load / compile input contracts address = None diff --git a/mythril/leveldb/client.py b/mythril/leveldb/client.py index d62895a6..ab52f4fa 100644 --- a/mythril/leveldb/client.py +++ b/mythril/leveldb/client.py @@ -39,55 +39,29 @@ class EthLevelDB(object): self.db = ETH_DB(path) self.headBlockHeader = None self.headState = None - self.all_contracts = None - self.active_contracts = None - self.instance_lists = None - def get_all_contracts(self): + def get_contracts(self, search_all): ''' - get all contracts + iterate through contracts with non-zero balance by default or all if search_all is set ''' - if not self.all_contracts: - self.all_contracts = [] - self.active_contracts = [] - self.instance_lists = [] - state = self._get_head_state() - accounts = state.get_all_accounts() + for account in self._get_head_state().get_all_accounts(): + if account.code is not None and (search_all or account.balance != 0): + code = _encode_hex(account.code) + md5 = hashlib.md5() + md5.update(code.encode('UTF-8')) + contract_hash = md5.digest() + contract = ETHContract(code, name=contract_hash.hex()) + instance_list = InstanceList() + instance_list.add(_encode_hex(account.address), account.balance) + yield contract, instance_list - for a in accounts: - if a.code is not None: - code = _encode_hex(a.code) - md5 = hashlib.md5() - md5.update(code.encode('UTF-8')) - contract_hash = md5.digest() - contract = ETHContract(code, name=contract_hash.hex()) - self.all_contracts.append(contract) - - if a.balance != 0: - md5 = InstanceList() - md5.add(_encode_hex(a.address), a.balance) - self.instance_lists.append(md5) - self.active_contracts.append(contract) - - return self.all_contracts - - def get_active_contracts(self): - ''' - get all contracts with non-zero balance - ''' - if not self.active_contracts: - self.get_all_contracts() # optimized - return self.active_contracts - - def search(self, expression, callback_func): + def search(self, expression, search_all, callback_func): ''' searches through non-zero balance contracts ''' - contracts = self.get_active_contracts() - for i in range(0, len(contracts)): - if contracts[i].matches_expression(expression): - m = self.instance_lists[i] - callback_func(contracts[i].name, contracts[i], m.addresses, m.balances) + for contract, instance_list in self.get_contracts(search_all): + if contract.matches_expression(expression): + callback_func(contract.name, contract, instance_list.addresses, instance_list.balances) def eth_getBlockHeaderByNumber(self, number): ''' diff --git a/mythril/leveldb/state.py b/mythril/leveldb/state.py index 71b4856c..fbf17cf0 100644 --- a/mythril/leveldb/state.py +++ b/mythril/leveldb/state.py @@ -121,10 +121,8 @@ class State(): def get_all_accounts(self): ''' - iterates through trie to get all items + iterates through trie to and yields non-blank leafs as accounts ''' - accounts = [] - for addressHash, rlpdata in self.secureTrie.trie.to_dict().items(): + for addressHash, rlpdata in self.secureTrie.trie.iter_branch(): if rlpdata != trie.BLANK_NODE: - accounts.append(rlp.decode(rlpdata, Account, db=self.db, address=addressHash)) - return accounts \ No newline at end of file + yield rlp.decode(rlpdata, Account, db=self.db, address=addressHash) \ No newline at end of file diff --git a/mythril/mythril.py b/mythril/mythril.py index 1f722649..d54dd8ae 100644 --- a/mythril/mythril.py +++ b/mythril/mythril.py @@ -11,11 +11,13 @@ import os import re from ethereum import utils +import codecs from solc.exceptions import SolcError import solc +from configparser import ConfigParser +import platform from mythril.ether import util -from mythril.ether.contractstorage import get_persistent_storage from mythril.ether.ethcontract import ETHContract from mythril.ether.soliditycontract import SolidityContract from mythril.rpc.client import EthJsonRpc @@ -37,7 +39,7 @@ from mythril.leveldb.client import EthLevelDB class Mythril(object): """ - Mythril main interface class. + Mythril main interface class. 1. create mythril object 2. set rpc or leveldb interface if needed @@ -46,17 +48,17 @@ class Mythril(object): Example: mythril = Mythril() - mythril.set_db_rpc_infura() + mythril.set_api_rpc_infura() - # (optional) other db adapters - mythril.set_db_rpc(args) - mythril.set_db_ipc() - mythril.set_db_rpc_localhost() + # (optional) other API adapters + mythril.set_api_rpc(args) + mythril.set_api_ipc() + mythril.set_api_rpc_localhost() + mythril.set_api_leveldb(path) # (optional) other func mythril.analyze_truffle_project(args) mythril.search_db(args) - mythril.init_db() # load contract mythril.load_from_bytecode(bytecode) @@ -69,7 +71,7 @@ class Mythril(object): # (optional) graph for contract in mythril.contracts: print(mythril.graph_html(args)) # prints html or save it to file - + # (optional) other funcs mythril.dump_statespaces(args) mythril.disassemble(contract) @@ -88,10 +90,10 @@ class Mythril(object): self.mythril_dir = self._init_mythril_dir() self.signatures_file, self.sigs = self._init_signatures() self.solc_binary = self._init_solc_binary(solv) + self.leveldb_dir = self._init_config() - self.eth = None - self.ethDb = None - self.dbtype = None # track type of db (rpc,ipc,leveldb) used + self.eth = None # ethereum API client + self.ethDb = None # ethereum LevelDB client self.contracts = [] # loaded contracts @@ -135,6 +137,41 @@ class Mythril(object): self.sigs = jsonsigs + def _init_config(self): + + # If no config file exists, create it. Default LevelDB path is specified based on OS + + config_path = os.path.join(self.mythril_dir, 'config.ini') + system = platform.system().lower() + fallback_dir = os.path.expanduser('~') + if system.startswith("darwin"): + fallback_dir = os.path.join(fallback_dir, "Library", "Ethereum") + elif system.startswith("windows"): + fallback_dir = os.path.join(fallback_dir, "AppData", "Roaming", "Ethereum") + else: + fallback_dir = os.path.join(fallback_dir, ".ethereum") + fallback_dir = os.path.join(fallback_dir, "geth", "chaindata") + + if not os.path.exists(config_path): + logging.info("No config file found. Creating default: " + config_path) + + config = ConfigParser(allow_no_value=True) + config.optionxform = str + config.add_section('defaults') + config.set('defaults', "#Default chaindata locations:") + config.set('defaults', "#– Mac: ~/Library/Ethereum/geth/chaindata") + config.set('defaults', "#– Linux: ~/.ethereum/geth/chaindata") + config.set('defaults', "#– Windows: %USERPROFILE%\\AppData\\Roaming\\Ethereum\\geth\\chaindata") + config.set('defaults', 'leveldb_dir', fallback_dir) + with codecs.open(config_path, 'w', 'utf-8') as fp: + config.write(fp) + + config = ConfigParser(allow_no_value=True) + config.optionxform = str + config.read(config_path) + leveldb_dir = config.get('defaults', 'leveldb_dir', fallback=fallback_dir) + return os.path.expanduser(leveldb_dir) + def analyze_truffle_project(self, *args, **kwargs): return analyze_truffle_project(*args, **kwargs) # just passthru for now @@ -168,18 +205,16 @@ class Mythril(object): solc_binary = 'solc' return solc_binary - def set_db_leveldb(self, leveldb): + def set_api_leveldb(self, leveldb): self.ethDb = EthLevelDB(leveldb) self.eth = self.ethDb - self.dbtype = "leveldb" return self.eth - def set_db_rpc_infura(self): + def set_api_rpc_infura(self): self.eth = EthJsonRpc('mainnet.infura.io', 443, True) logging.info("Using INFURA for RPC queries") - self.dbtype = "rpc" - def set_db_rpc(self, rpc=None, rpctls=False): + def set_api_rpc(self, rpc=None, rpctls=False): if rpc == 'ganache': rpcconfig = ('localhost', 7545, False) else: @@ -195,26 +230,23 @@ class Mythril(object): if rpcconfig: self.eth = EthJsonRpc(rpcconfig[0], int(rpcconfig[1]), rpcconfig[2]) - self.dbtype = "rpc" logging.info("Using RPC settings: %s" % str(rpcconfig)) else: raise CriticalError("Invalid RPC settings, check help for details.") - def set_db_ipc(self): + def set_api_ipc(self): try: self.eth = EthIpc() - self.dbtype = "ipc" except Exception as e: raise CriticalError( "IPC initialization failed. Please verify that your local Ethereum node is running, or use the -i flag to connect to INFURA. \n" + str( e)) - def set_db_rpc_localhost(self): + def set_api_rpc_localhost(self): self.eth = EthJsonRpc('localhost', 8545) - self.dbtype = "rpc" logging.info("Using default RPC settings: http://localhost:8545") - def search_db(self, search): + def search_db(self, search, search_all): def search_callback(code_hash, code, addresses, balances): print("Matched contract with code hash " + code_hash) @@ -222,24 +254,11 @@ class Mythril(object): print("Address: " + addresses[i] + ", balance: " + str(balances[i])) try: - if self.dbtype=="leveldb": - self.ethDb.search(search, search_callback) - else: - contract_storage, _ = get_persistent_storage(self.mythril_dir) - contract_storage.search(search, search_callback) + self.ethDb.search(search, search_all, search_callback) except SyntaxError: raise CriticalError("Syntax error in search expression.") - def init_db(self): - contract_storage, _ = get_persistent_storage(self.mythril_dir) - try: - contract_storage.initialize(self.eth) - except FileNotFoundError as e: - raise CriticalError("Error syncing database over IPC: " + str(e)) - except ConnectionError as e: - raise CriticalError("Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly.") - def load_from_bytecode(self, code): address = util.get_indexed_address(0) self.contracts.append(ETHContract(code, name="MAIN")) diff --git a/requirements.txt b/requirements.txt index 6b90943b..45c582e9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ -BTrees coverage eth_abi>=1.0.0 eth-account>=0.1.0a2 @@ -20,4 +19,5 @@ pytest_mock requests rlp<1.0.0 z3-solver>=4.5 -ZODB>=5.3.0 +configparser +persistent diff --git a/setup.py b/setup.py index 523308fd..4d5452f0 100755 --- a/setup.py +++ b/setup.py @@ -159,26 +159,24 @@ unfortunately completely destroys usability. Blockchain exploration ---------------------- -Mythril builds its own contract database to enable fast search -operations. This enables operations like those described in the -`legendary "Mitch Brenner" blog -post `__ -in [STRIKEOUT:seconds] minutes instead of days. Unfortunately, the -initial sync process is slow. You don't need to sync the whole -blockchain right away though: If you abort the syncing process with -``ctrl+c``, it will be auto-resumed the next time you run the -``--init-db`` command. +Mythril allows to search geth contract database directly as well as +perform other operations targetting local geth database instead of +exposed RPC/IPC API. This enables operations like those described +in the `legendary "Mitch Brenner" blog post +`__ +in [STRIKEOUT:seconds] minutes instead of days. -.. code:: bash +The default behavior is to search contracts with a non-zero balance. +You can disable this behavior with the ``--search-all`` flag. - $ myth --init-db - Starting synchronization from latest block: 4323706 - Processing block 4323000, 3 individual contracts in database - (...) +You may also use geth database directly for fetching contracts instead of +using IPC/RPC APIs by specifying ``--leveldb`` flag. This is useful +because search will return hashed addresses which will not be accepted by +IPC/RPC APIs. -The default behavior is to only sync contracts with a non-zero balance. -You can disable this behavior with the ``--sync-all`` flag, but be aware -that this will result in a huge (as in: dozens of GB) database. +By default database operations will target default geth data directory on +your system. You may edit the generated configuration at ``~/.mythril/config.ini`` +or you may supply ``--leveldb-dir `` parameter in command line. Searching from the command line ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -190,8 +188,9 @@ expressions, such as: .. code:: bash $ myth --search "func#changeMultisig(address)#" - $ myth --search "code#PUSH1 0x50,POP#" + $ myth --search "code#PUSH1 0x50,POP#" --search-all $ myth --search "func#changeMultisig(address)# and code#PUSH1 0x50#" + $ myth -s "code#PUSH#" --leveldb-dir /Volumes/MyPassport/Ether/Rinkeby/geth/chaindata Reading contract storage ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -307,10 +306,8 @@ setup( install_requires=[ 'ethereum>=2.3.0', - 'ZODB>=5.3.0', 'z3-solver>=4.5', 'requests', - 'BTrees', 'py-solc', 'plyvel', 'eth_abi>=1.0.0', @@ -325,7 +322,9 @@ setup( 'jinja2', 'rlp<1.0.0', 'py-flags', - 'mock' + 'mock', + 'configparser', + 'persistent' ], tests_require=[ diff --git a/tests/contractstorage_test.py b/tests/contractstorage_test.py deleted file mode 100644 index e0128dd8..00000000 --- a/tests/contractstorage_test.py +++ /dev/null @@ -1,32 +0,0 @@ -from mythril.ether.contractstorage import get_persistent_storage -import os -from tests import BaseTestCase - -class GetAndSearchContractTestCase(BaseTestCase): - - def setUp(self): - super(GetAndSearchContractTestCase, self).setUp() - script_path = os.path.dirname(os.path.realpath(__file__)) - storage_dir = os.path.join(script_path, 'teststorage') - self.storage, self.db = get_persistent_storage(storage_dir) - - def tearDown(self): - self.db.close() - super(GetAndSearchContractTestCase, self).tearDown() - - def mockCallback(self, code_hash, code, addresses, balances): - self.code_hash = code_hash - self.isFound = True - pass - - def runTest(self): - contract = self.storage.get_contract_by_hash(bytes.fromhex("ea061445eacbe86b7ffed2bb9e52075e")) - - self.assertTrue("0x60606040" in contract.code, 'error reading contract code from database') - - self.isFound = False - - self.storage.search("code#PUSH1#", self.mockCallback) - - self.assertTrue(self.isFound, 'storage search error') - self.assertEqual(self.code_hash, 'ea061445eacbe86b7ffed2bb9e52075e', 'storage search error') From a5a8cdaf9bee901ca2370a56a6092e6a719202ca Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Sun, 24 Jun 2018 17:31:40 +0530 Subject: [PATCH 09/62] support utf-8 for configparser --- .gitignore | 3 +++ mythril/mythril.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index d9a349cf..a4a6b6e7 100644 --- a/.gitignore +++ b/.gitignore @@ -177,3 +177,6 @@ coverage_html_report/ tests/testdata/outputs_current/ tests/testdata/outputs_current_laser_result/ tests/mythril_dir/signatures.json + +# VSCode +.vscode \ No newline at end of file diff --git a/mythril/mythril.py b/mythril/mythril.py index d54dd8ae..4760e039 100644 --- a/mythril/mythril.py +++ b/mythril/mythril.py @@ -163,12 +163,12 @@ class Mythril(object): config.set('defaults', "#– Linux: ~/.ethereum/geth/chaindata") config.set('defaults', "#– Windows: %USERPROFILE%\\AppData\\Roaming\\Ethereum\\geth\\chaindata") config.set('defaults', 'leveldb_dir', fallback_dir) - with codecs.open(config_path, 'w', 'utf-8') as fp: + with open(config_path, 'w', 'utf-8') as fp: config.write(fp) config = ConfigParser(allow_no_value=True) config.optionxform = str - config.read(config_path) + config.read(config_path, 'utf-8') leveldb_dir = config.get('defaults', 'leveldb_dir', fallback=fallback_dir) return os.path.expanduser(leveldb_dir) From a80879f688ea7fa4b986e201040f1eaeed4252de Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Sun, 24 Jun 2018 17:37:12 +0530 Subject: [PATCH 10/62] use codecs to convert to utf-8 --- mythril/mythril.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mythril/mythril.py b/mythril/mythril.py index 4760e039..611ad703 100644 --- a/mythril/mythril.py +++ b/mythril/mythril.py @@ -163,7 +163,7 @@ class Mythril(object): config.set('defaults', "#– Linux: ~/.ethereum/geth/chaindata") config.set('defaults', "#– Windows: %USERPROFILE%\\AppData\\Roaming\\Ethereum\\geth\\chaindata") config.set('defaults', 'leveldb_dir', fallback_dir) - with open(config_path, 'w', 'utf-8') as fp: + with codecs.open(config_path, 'w', 'utf-8') as fp: config.write(fp) config = ConfigParser(allow_no_value=True) From 54ce972f456681d5fb8bb4aa0c54aa0b28f852be Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Sun, 24 Jun 2018 17:41:58 +0530 Subject: [PATCH 11/62] remove persistence usage for search --- mythril/leveldb/client.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/mythril/leveldb/client.py b/mythril/leveldb/client.py index ab52f4fa..83a80d24 100644 --- a/mythril/leveldb/client.py +++ b/mythril/leveldb/client.py @@ -51,17 +51,15 @@ class EthLevelDB(object): md5.update(code.encode('UTF-8')) contract_hash = md5.digest() contract = ETHContract(code, name=contract_hash.hex()) - instance_list = InstanceList() - instance_list.add(_encode_hex(account.address), account.balance) - yield contract, instance_list + yield contract, _encode_hex(account.address), account.balance def search(self, expression, search_all, callback_func): ''' searches through non-zero balance contracts ''' - for contract, instance_list in self.get_contracts(search_all): + for contract, address, balance in self.get_contracts(search_all): if contract.matches_expression(expression): - callback_func(contract.name, contract, instance_list.addresses, instance_list.balances) + callback_func(contract.name, contract, [address], [balance]) def eth_getBlockHeaderByNumber(self, number): ''' @@ -141,7 +139,7 @@ class EthLevelDB(object): hash = self.headBlockHeader.prevhash num = self._get_block_number(hash) self.headBlockHeader = self._get_block_header(hash, num) - + return self.headBlockHeader def _get_block_number(self, hash): @@ -158,4 +156,4 @@ class EthLevelDB(object): headerKey = headerPrefix + num + hash blockHeaderData = self.db.get(headerKey) header = rlp.decode(blockHeaderData, sedes=BlockHeader) - return header \ No newline at end of file + return header From ba3181416acb14e1b5db70bb6f3e6c4a0692e309 Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Sun, 24 Jun 2018 17:49:33 +0530 Subject: [PATCH 12/62] add required versions of requirements --- requirements.txt | 4 ++-- setup.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 45c582e9..fc927d11 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +configparser>=3.5.0 coverage eth_abi>=1.0.0 eth-account>=0.1.0a2 @@ -10,6 +11,7 @@ eth-tester>=0.1.0b21 eth-utils>=1.0.1 jinja2 mock +persistent>=4.2.0 plyvel py-flags py-solc @@ -19,5 +21,3 @@ pytest_mock requests rlp<1.0.0 z3-solver>=4.5 -configparser -persistent diff --git a/setup.py b/setup.py index 4d5452f0..1d3beb3c 100755 --- a/setup.py +++ b/setup.py @@ -323,8 +323,8 @@ setup( 'rlp<1.0.0', 'py-flags', 'mock', - 'configparser', - 'persistent' + 'configparser>=3.5.0', + 'persistent>=4.2.0' ], tests_require=[ From 35ff192db18fee8bca1543c3e3a46c03942c59b9 Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Sun, 24 Jun 2018 22:47:33 +0530 Subject: [PATCH 13/62] v0.18.6 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4ae7b918..7e10f2ec 100755 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ import os # Package version (vX.Y.Z). It must match git tag being used for CircleCI # deployment; otherwise the build will failed. -VERSION = "v0.18.5" +VERSION = "v0.18.6" class VerifyVersionCommand(install): From 5ee53db95bf50555839092bbf570c86624dea2fe Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Mon, 25 Jun 2018 23:39:49 +0530 Subject: [PATCH 14/62] Remove extra space in integer overflow output field --- mythril/analysis/modules/integer.py | 4 ++-- tests/testdata/outputs_expected/ether_send.sol.json | 4 ++-- tests/testdata/outputs_expected/ether_send.sol.o.json | 4 ++-- tests/testdata/outputs_expected/ether_send.sol.o.markdown | 2 +- tests/testdata/outputs_expected/ether_send.sol.o.text | 2 +- tests/testdata/outputs_expected/metacoin.sol.o.json | 4 ++-- tests/testdata/outputs_expected/metacoin.sol.o.markdown | 2 +- tests/testdata/outputs_expected/metacoin.sol.o.text | 2 +- tests/testdata/outputs_expected/overflow.sol.json | 4 ++-- tests/testdata/outputs_expected/overflow.sol.markdown | 2 +- tests/testdata/outputs_expected/overflow.sol.o.json | 4 ++-- tests/testdata/outputs_expected/overflow.sol.o.markdown | 2 +- tests/testdata/outputs_expected/overflow.sol.o.text | 2 +- tests/testdata/outputs_expected/overflow.sol.text | 2 +- tests/testdata/outputs_expected/underflow.sol.json | 4 ++-- tests/testdata/outputs_expected/underflow.sol.markdown | 2 +- tests/testdata/outputs_expected/underflow.sol.o.json | 4 ++-- tests/testdata/outputs_expected/underflow.sol.o.markdown | 2 +- tests/testdata/outputs_expected/underflow.sol.o.text | 2 +- tests/testdata/outputs_expected/underflow.sol.text | 2 +- 20 files changed, 28 insertions(+), 28 deletions(-) diff --git a/mythril/analysis/modules/integer.py b/mythril/analysis/modules/integer.py index 8c14b225..e21dbb58 100644 --- a/mythril/analysis/modules/integer.py +++ b/mythril/analysis/modules/integer.py @@ -83,9 +83,9 @@ def _check_integer_overflow(statespace, state, node): if not _verify_integer_overflow(statespace, node, expr, state, model, constraint, op0, op1): return issues - + # Build issue - issue = Issue(node.contract_name, node.function_name, instruction['address'], "Integer Overflow ", "Warning") + issue = Issue(node.contract_name, node.function_name, instruction['address'], "Integer Overflow", "Warning") issue.description = "A possible integer overflow exists in the function `{}`.\n" \ "The addition or multiplication may result in a value higher than the maximum representable integer.".format( diff --git a/tests/testdata/outputs_expected/ether_send.sol.json b/tests/testdata/outputs_expected/ether_send.sol.json index 2c89e2e2..08a5012c 100644 --- a/tests/testdata/outputs_expected/ether_send.sol.json +++ b/tests/testdata/outputs_expected/ether_send.sol.json @@ -14,7 +14,7 @@ "code": "msg.sender.transfer(this.balance)" }, { - "title": "Integer Overflow ", + "title": "Integer Overflow", "description": "A possible integer overflow exists in the function `invest()`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "invest()", "type": "Warning", @@ -25,4 +25,4 @@ "code": "balances[msg.sender] += msg.value" } ] -} \ No newline at end of file +} diff --git a/tests/testdata/outputs_expected/ether_send.sol.o.json b/tests/testdata/outputs_expected/ether_send.sol.o.json index 701f0535..6d6caad0 100644 --- a/tests/testdata/outputs_expected/ether_send.sol.o.json +++ b/tests/testdata/outputs_expected/ether_send.sol.o.json @@ -11,7 +11,7 @@ "debug": "" }, { - "title": "Integer Overflow ", + "title": "Integer Overflow", "description": "A possible integer overflow exists in the function `invest()`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "invest()", "type": "Warning", @@ -19,4 +19,4 @@ "debug": "" } ] -} \ No newline at end of file +} diff --git a/tests/testdata/outputs_expected/ether_send.sol.o.markdown b/tests/testdata/outputs_expected/ether_send.sol.o.markdown index 6f4b46be..e5a3f7cd 100644 --- a/tests/testdata/outputs_expected/ether_send.sol.o.markdown +++ b/tests/testdata/outputs_expected/ether_send.sol.o.markdown @@ -13,7 +13,7 @@ In the function `withdrawfunds()` a non-zero amount of Ether is sent to msg.send There is a check on storage index 1. This storage slot can be written to by calling the function `crowdfunding()`. -## Integer Overflow +## Integer Overflow - Type: Warning - Contract: Unknown diff --git a/tests/testdata/outputs_expected/ether_send.sol.o.text b/tests/testdata/outputs_expected/ether_send.sol.o.text index bba084fa..d6d548c7 100644 --- a/tests/testdata/outputs_expected/ether_send.sol.o.text +++ b/tests/testdata/outputs_expected/ether_send.sol.o.text @@ -8,7 +8,7 @@ In the function `withdrawfunds()` a non-zero amount of Ether is sent to msg.send There is a check on storage index 1. This storage slot can be written to by calling the function `crowdfunding()`. -------------------- -==== Integer Overflow ==== +==== Integer Overflow ==== Type: Warning Contract: Unknown Function name: invest() diff --git a/tests/testdata/outputs_expected/metacoin.sol.o.json b/tests/testdata/outputs_expected/metacoin.sol.o.json index 1977ed30..fd59d519 100644 --- a/tests/testdata/outputs_expected/metacoin.sol.o.json +++ b/tests/testdata/outputs_expected/metacoin.sol.o.json @@ -3,7 +3,7 @@ "error": null, "issues": [ { - "title": "Integer Overflow ", + "title": "Integer Overflow", "description": "A possible integer overflow exists in the function `sendToken(address,uint256)`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "sendToken(address,uint256)", "type": "Warning", @@ -11,4 +11,4 @@ "debug": "" } ] -} \ No newline at end of file +} diff --git a/tests/testdata/outputs_expected/metacoin.sol.o.markdown b/tests/testdata/outputs_expected/metacoin.sol.o.markdown index 6c38d51d..e2495d49 100644 --- a/tests/testdata/outputs_expected/metacoin.sol.o.markdown +++ b/tests/testdata/outputs_expected/metacoin.sol.o.markdown @@ -1,6 +1,6 @@ # Analysis results for test-filename.sol -## Integer Overflow +## Integer Overflow - Type: Warning - Contract: Unknown diff --git a/tests/testdata/outputs_expected/metacoin.sol.o.text b/tests/testdata/outputs_expected/metacoin.sol.o.text index 9cb0bc12..69fbc811 100644 --- a/tests/testdata/outputs_expected/metacoin.sol.o.text +++ b/tests/testdata/outputs_expected/metacoin.sol.o.text @@ -1,4 +1,4 @@ -==== Integer Overflow ==== +==== Integer Overflow ==== Type: Warning Contract: Unknown Function name: sendToken(address,uint256) diff --git a/tests/testdata/outputs_expected/overflow.sol.json b/tests/testdata/outputs_expected/overflow.sol.json index 0c7c0faf..92a56005 100644 --- a/tests/testdata/outputs_expected/overflow.sol.json +++ b/tests/testdata/outputs_expected/overflow.sol.json @@ -14,7 +14,7 @@ "code": "balances[msg.sender] -= _value" }, { - "title": "Integer Overflow ", + "title": "Integer Overflow", "description": "A possible integer overflow exists in the function `sendeth(address,uint256)`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "sendeth(address,uint256)", "type": "Warning", @@ -36,4 +36,4 @@ "code": "balances[msg.sender] - _value" } ] -} \ No newline at end of file +} diff --git a/tests/testdata/outputs_expected/overflow.sol.markdown b/tests/testdata/outputs_expected/overflow.sol.markdown index 840bbbfc..d6d4a49c 100644 --- a/tests/testdata/outputs_expected/overflow.sol.markdown +++ b/tests/testdata/outputs_expected/overflow.sol.markdown @@ -17,7 +17,7 @@ In */inputs/overflow.sol:12* balances[msg.sender] -= _value ``` -## Integer Overflow +## Integer Overflow - Type: Warning - Contract: Over diff --git a/tests/testdata/outputs_expected/overflow.sol.o.json b/tests/testdata/outputs_expected/overflow.sol.o.json index d9e1b0b8..8345944e 100644 --- a/tests/testdata/outputs_expected/overflow.sol.o.json +++ b/tests/testdata/outputs_expected/overflow.sol.o.json @@ -11,7 +11,7 @@ "debug": "" }, { - "title": "Integer Overflow ", + "title": "Integer Overflow", "description": "A possible integer overflow exists in the function `sendeth(address,uint256)`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "sendeth(address,uint256)", "type": "Warning", @@ -27,4 +27,4 @@ "debug": "" } ] -} \ No newline at end of file +} diff --git a/tests/testdata/outputs_expected/overflow.sol.o.markdown b/tests/testdata/outputs_expected/overflow.sol.o.markdown index 07d375b8..8baede40 100644 --- a/tests/testdata/outputs_expected/overflow.sol.o.markdown +++ b/tests/testdata/outputs_expected/overflow.sol.o.markdown @@ -24,7 +24,7 @@ The subtraction may result in a value < 0. A possible integer underflow exists in the function `sendeth(address,uint256)`. The subtraction may result in a value < 0. -## Integer Overflow +## Integer Overflow - Type: Warning - Contract: Unknown diff --git a/tests/testdata/outputs_expected/overflow.sol.o.text b/tests/testdata/outputs_expected/overflow.sol.o.text index f15d633f..bd2a4c99 100644 --- a/tests/testdata/outputs_expected/overflow.sol.o.text +++ b/tests/testdata/outputs_expected/overflow.sol.o.text @@ -16,7 +16,7 @@ A possible integer underflow exists in the function `sendeth(address,uint256)`. The subtraction may result in a value < 0. -------------------- -==== Integer Overflow ==== +==== Integer Overflow ==== Type: Warning Contract: Unknown Function name: sendeth(address,uint256) diff --git a/tests/testdata/outputs_expected/overflow.sol.text b/tests/testdata/outputs_expected/overflow.sol.text index 5dec25ed..64b072ca 100644 --- a/tests/testdata/outputs_expected/overflow.sol.text +++ b/tests/testdata/outputs_expected/overflow.sol.text @@ -12,7 +12,7 @@ balances[msg.sender] -= _value -------------------- -==== Integer Overflow ==== +==== Integer Overflow ==== Type: Warning Contract: Over Function name: sendeth(address,uint256) diff --git a/tests/testdata/outputs_expected/underflow.sol.json b/tests/testdata/outputs_expected/underflow.sol.json index 290dfe4e..3a2fbd91 100644 --- a/tests/testdata/outputs_expected/underflow.sol.json +++ b/tests/testdata/outputs_expected/underflow.sol.json @@ -14,7 +14,7 @@ "code": "balances[msg.sender] -= _value" }, { - "title": "Integer Overflow ", + "title": "Integer Overflow", "description": "A possible integer overflow exists in the function `sendeth(address,uint256)`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "sendeth(address,uint256)", "type": "Warning", @@ -36,4 +36,4 @@ "code": "balances[msg.sender] - _value" } ] -} \ No newline at end of file +} diff --git a/tests/testdata/outputs_expected/underflow.sol.markdown b/tests/testdata/outputs_expected/underflow.sol.markdown index ca8a6b17..b41efee7 100644 --- a/tests/testdata/outputs_expected/underflow.sol.markdown +++ b/tests/testdata/outputs_expected/underflow.sol.markdown @@ -17,7 +17,7 @@ In */inputs/underflow.sol:12* balances[msg.sender] -= _value ``` -## Integer Overflow +## Integer Overflow - Type: Warning - Contract: Under diff --git a/tests/testdata/outputs_expected/underflow.sol.o.json b/tests/testdata/outputs_expected/underflow.sol.o.json index d9e1b0b8..8345944e 100644 --- a/tests/testdata/outputs_expected/underflow.sol.o.json +++ b/tests/testdata/outputs_expected/underflow.sol.o.json @@ -11,7 +11,7 @@ "debug": "" }, { - "title": "Integer Overflow ", + "title": "Integer Overflow", "description": "A possible integer overflow exists in the function `sendeth(address,uint256)`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "sendeth(address,uint256)", "type": "Warning", @@ -27,4 +27,4 @@ "debug": "" } ] -} \ No newline at end of file +} diff --git a/tests/testdata/outputs_expected/underflow.sol.o.markdown b/tests/testdata/outputs_expected/underflow.sol.o.markdown index 07d375b8..8baede40 100644 --- a/tests/testdata/outputs_expected/underflow.sol.o.markdown +++ b/tests/testdata/outputs_expected/underflow.sol.o.markdown @@ -24,7 +24,7 @@ The subtraction may result in a value < 0. A possible integer underflow exists in the function `sendeth(address,uint256)`. The subtraction may result in a value < 0. -## Integer Overflow +## Integer Overflow - Type: Warning - Contract: Unknown diff --git a/tests/testdata/outputs_expected/underflow.sol.o.text b/tests/testdata/outputs_expected/underflow.sol.o.text index f15d633f..bd2a4c99 100644 --- a/tests/testdata/outputs_expected/underflow.sol.o.text +++ b/tests/testdata/outputs_expected/underflow.sol.o.text @@ -16,7 +16,7 @@ A possible integer underflow exists in the function `sendeth(address,uint256)`. The subtraction may result in a value < 0. -------------------- -==== Integer Overflow ==== +==== Integer Overflow ==== Type: Warning Contract: Unknown Function name: sendeth(address,uint256) diff --git a/tests/testdata/outputs_expected/underflow.sol.text b/tests/testdata/outputs_expected/underflow.sol.text index d44e5608..a3409ad1 100644 --- a/tests/testdata/outputs_expected/underflow.sol.text +++ b/tests/testdata/outputs_expected/underflow.sol.text @@ -12,7 +12,7 @@ balances[msg.sender] -= _value -------------------- -==== Integer Overflow ==== +==== Integer Overflow ==== Type: Warning Contract: Under Function name: sendeth(address,uint256) From 8a3a74da10b4bd93329d99e20b83d772195f86b2 Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Tue, 26 Jun 2018 22:46:59 +0530 Subject: [PATCH 15/62] Revert "Merge pull request #296 from norhh/branch/295" This reverts commit 6428d9b8cf29bcb75912dbd582dc4a263c710715, reversing changes made to 35ff192db18fee8bca1543c3e3a46c03942c59b9. --- mythril/analysis/modules/integer.py | 4 ++-- tests/testdata/outputs_expected/ether_send.sol.json | 4 ++-- tests/testdata/outputs_expected/ether_send.sol.o.json | 4 ++-- tests/testdata/outputs_expected/ether_send.sol.o.markdown | 2 +- tests/testdata/outputs_expected/ether_send.sol.o.text | 2 +- tests/testdata/outputs_expected/metacoin.sol.o.json | 4 ++-- tests/testdata/outputs_expected/metacoin.sol.o.markdown | 2 +- tests/testdata/outputs_expected/metacoin.sol.o.text | 2 +- tests/testdata/outputs_expected/overflow.sol.json | 4 ++-- tests/testdata/outputs_expected/overflow.sol.markdown | 2 +- tests/testdata/outputs_expected/overflow.sol.o.json | 4 ++-- tests/testdata/outputs_expected/overflow.sol.o.markdown | 2 +- tests/testdata/outputs_expected/overflow.sol.o.text | 2 +- tests/testdata/outputs_expected/overflow.sol.text | 2 +- tests/testdata/outputs_expected/underflow.sol.json | 4 ++-- tests/testdata/outputs_expected/underflow.sol.markdown | 2 +- tests/testdata/outputs_expected/underflow.sol.o.json | 4 ++-- tests/testdata/outputs_expected/underflow.sol.o.markdown | 2 +- tests/testdata/outputs_expected/underflow.sol.o.text | 2 +- tests/testdata/outputs_expected/underflow.sol.text | 2 +- 20 files changed, 28 insertions(+), 28 deletions(-) diff --git a/mythril/analysis/modules/integer.py b/mythril/analysis/modules/integer.py index e21dbb58..8c14b225 100644 --- a/mythril/analysis/modules/integer.py +++ b/mythril/analysis/modules/integer.py @@ -83,9 +83,9 @@ def _check_integer_overflow(statespace, state, node): if not _verify_integer_overflow(statespace, node, expr, state, model, constraint, op0, op1): return issues - + # Build issue - issue = Issue(node.contract_name, node.function_name, instruction['address'], "Integer Overflow", "Warning") + issue = Issue(node.contract_name, node.function_name, instruction['address'], "Integer Overflow ", "Warning") issue.description = "A possible integer overflow exists in the function `{}`.\n" \ "The addition or multiplication may result in a value higher than the maximum representable integer.".format( diff --git a/tests/testdata/outputs_expected/ether_send.sol.json b/tests/testdata/outputs_expected/ether_send.sol.json index 08a5012c..2c89e2e2 100644 --- a/tests/testdata/outputs_expected/ether_send.sol.json +++ b/tests/testdata/outputs_expected/ether_send.sol.json @@ -14,7 +14,7 @@ "code": "msg.sender.transfer(this.balance)" }, { - "title": "Integer Overflow", + "title": "Integer Overflow ", "description": "A possible integer overflow exists in the function `invest()`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "invest()", "type": "Warning", @@ -25,4 +25,4 @@ "code": "balances[msg.sender] += msg.value" } ] -} +} \ No newline at end of file diff --git a/tests/testdata/outputs_expected/ether_send.sol.o.json b/tests/testdata/outputs_expected/ether_send.sol.o.json index 6d6caad0..701f0535 100644 --- a/tests/testdata/outputs_expected/ether_send.sol.o.json +++ b/tests/testdata/outputs_expected/ether_send.sol.o.json @@ -11,7 +11,7 @@ "debug": "" }, { - "title": "Integer Overflow", + "title": "Integer Overflow ", "description": "A possible integer overflow exists in the function `invest()`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "invest()", "type": "Warning", @@ -19,4 +19,4 @@ "debug": "" } ] -} +} \ No newline at end of file diff --git a/tests/testdata/outputs_expected/ether_send.sol.o.markdown b/tests/testdata/outputs_expected/ether_send.sol.o.markdown index e5a3f7cd..6f4b46be 100644 --- a/tests/testdata/outputs_expected/ether_send.sol.o.markdown +++ b/tests/testdata/outputs_expected/ether_send.sol.o.markdown @@ -13,7 +13,7 @@ In the function `withdrawfunds()` a non-zero amount of Ether is sent to msg.send There is a check on storage index 1. This storage slot can be written to by calling the function `crowdfunding()`. -## Integer Overflow +## Integer Overflow - Type: Warning - Contract: Unknown diff --git a/tests/testdata/outputs_expected/ether_send.sol.o.text b/tests/testdata/outputs_expected/ether_send.sol.o.text index d6d548c7..bba084fa 100644 --- a/tests/testdata/outputs_expected/ether_send.sol.o.text +++ b/tests/testdata/outputs_expected/ether_send.sol.o.text @@ -8,7 +8,7 @@ In the function `withdrawfunds()` a non-zero amount of Ether is sent to msg.send There is a check on storage index 1. This storage slot can be written to by calling the function `crowdfunding()`. -------------------- -==== Integer Overflow ==== +==== Integer Overflow ==== Type: Warning Contract: Unknown Function name: invest() diff --git a/tests/testdata/outputs_expected/metacoin.sol.o.json b/tests/testdata/outputs_expected/metacoin.sol.o.json index fd59d519..1977ed30 100644 --- a/tests/testdata/outputs_expected/metacoin.sol.o.json +++ b/tests/testdata/outputs_expected/metacoin.sol.o.json @@ -3,7 +3,7 @@ "error": null, "issues": [ { - "title": "Integer Overflow", + "title": "Integer Overflow ", "description": "A possible integer overflow exists in the function `sendToken(address,uint256)`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "sendToken(address,uint256)", "type": "Warning", @@ -11,4 +11,4 @@ "debug": "" } ] -} +} \ No newline at end of file diff --git a/tests/testdata/outputs_expected/metacoin.sol.o.markdown b/tests/testdata/outputs_expected/metacoin.sol.o.markdown index e2495d49..6c38d51d 100644 --- a/tests/testdata/outputs_expected/metacoin.sol.o.markdown +++ b/tests/testdata/outputs_expected/metacoin.sol.o.markdown @@ -1,6 +1,6 @@ # Analysis results for test-filename.sol -## Integer Overflow +## Integer Overflow - Type: Warning - Contract: Unknown diff --git a/tests/testdata/outputs_expected/metacoin.sol.o.text b/tests/testdata/outputs_expected/metacoin.sol.o.text index 69fbc811..9cb0bc12 100644 --- a/tests/testdata/outputs_expected/metacoin.sol.o.text +++ b/tests/testdata/outputs_expected/metacoin.sol.o.text @@ -1,4 +1,4 @@ -==== Integer Overflow ==== +==== Integer Overflow ==== Type: Warning Contract: Unknown Function name: sendToken(address,uint256) diff --git a/tests/testdata/outputs_expected/overflow.sol.json b/tests/testdata/outputs_expected/overflow.sol.json index 92a56005..0c7c0faf 100644 --- a/tests/testdata/outputs_expected/overflow.sol.json +++ b/tests/testdata/outputs_expected/overflow.sol.json @@ -14,7 +14,7 @@ "code": "balances[msg.sender] -= _value" }, { - "title": "Integer Overflow", + "title": "Integer Overflow ", "description": "A possible integer overflow exists in the function `sendeth(address,uint256)`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "sendeth(address,uint256)", "type": "Warning", @@ -36,4 +36,4 @@ "code": "balances[msg.sender] - _value" } ] -} +} \ No newline at end of file diff --git a/tests/testdata/outputs_expected/overflow.sol.markdown b/tests/testdata/outputs_expected/overflow.sol.markdown index d6d4a49c..840bbbfc 100644 --- a/tests/testdata/outputs_expected/overflow.sol.markdown +++ b/tests/testdata/outputs_expected/overflow.sol.markdown @@ -17,7 +17,7 @@ In */inputs/overflow.sol:12* balances[msg.sender] -= _value ``` -## Integer Overflow +## Integer Overflow - Type: Warning - Contract: Over diff --git a/tests/testdata/outputs_expected/overflow.sol.o.json b/tests/testdata/outputs_expected/overflow.sol.o.json index 8345944e..d9e1b0b8 100644 --- a/tests/testdata/outputs_expected/overflow.sol.o.json +++ b/tests/testdata/outputs_expected/overflow.sol.o.json @@ -11,7 +11,7 @@ "debug": "" }, { - "title": "Integer Overflow", + "title": "Integer Overflow ", "description": "A possible integer overflow exists in the function `sendeth(address,uint256)`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "sendeth(address,uint256)", "type": "Warning", @@ -27,4 +27,4 @@ "debug": "" } ] -} +} \ No newline at end of file diff --git a/tests/testdata/outputs_expected/overflow.sol.o.markdown b/tests/testdata/outputs_expected/overflow.sol.o.markdown index 8baede40..07d375b8 100644 --- a/tests/testdata/outputs_expected/overflow.sol.o.markdown +++ b/tests/testdata/outputs_expected/overflow.sol.o.markdown @@ -24,7 +24,7 @@ The subtraction may result in a value < 0. A possible integer underflow exists in the function `sendeth(address,uint256)`. The subtraction may result in a value < 0. -## Integer Overflow +## Integer Overflow - Type: Warning - Contract: Unknown diff --git a/tests/testdata/outputs_expected/overflow.sol.o.text b/tests/testdata/outputs_expected/overflow.sol.o.text index bd2a4c99..f15d633f 100644 --- a/tests/testdata/outputs_expected/overflow.sol.o.text +++ b/tests/testdata/outputs_expected/overflow.sol.o.text @@ -16,7 +16,7 @@ A possible integer underflow exists in the function `sendeth(address,uint256)`. The subtraction may result in a value < 0. -------------------- -==== Integer Overflow ==== +==== Integer Overflow ==== Type: Warning Contract: Unknown Function name: sendeth(address,uint256) diff --git a/tests/testdata/outputs_expected/overflow.sol.text b/tests/testdata/outputs_expected/overflow.sol.text index 64b072ca..5dec25ed 100644 --- a/tests/testdata/outputs_expected/overflow.sol.text +++ b/tests/testdata/outputs_expected/overflow.sol.text @@ -12,7 +12,7 @@ balances[msg.sender] -= _value -------------------- -==== Integer Overflow ==== +==== Integer Overflow ==== Type: Warning Contract: Over Function name: sendeth(address,uint256) diff --git a/tests/testdata/outputs_expected/underflow.sol.json b/tests/testdata/outputs_expected/underflow.sol.json index 3a2fbd91..290dfe4e 100644 --- a/tests/testdata/outputs_expected/underflow.sol.json +++ b/tests/testdata/outputs_expected/underflow.sol.json @@ -14,7 +14,7 @@ "code": "balances[msg.sender] -= _value" }, { - "title": "Integer Overflow", + "title": "Integer Overflow ", "description": "A possible integer overflow exists in the function `sendeth(address,uint256)`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "sendeth(address,uint256)", "type": "Warning", @@ -36,4 +36,4 @@ "code": "balances[msg.sender] - _value" } ] -} +} \ No newline at end of file diff --git a/tests/testdata/outputs_expected/underflow.sol.markdown b/tests/testdata/outputs_expected/underflow.sol.markdown index b41efee7..ca8a6b17 100644 --- a/tests/testdata/outputs_expected/underflow.sol.markdown +++ b/tests/testdata/outputs_expected/underflow.sol.markdown @@ -17,7 +17,7 @@ In */inputs/underflow.sol:12* balances[msg.sender] -= _value ``` -## Integer Overflow +## Integer Overflow - Type: Warning - Contract: Under diff --git a/tests/testdata/outputs_expected/underflow.sol.o.json b/tests/testdata/outputs_expected/underflow.sol.o.json index 8345944e..d9e1b0b8 100644 --- a/tests/testdata/outputs_expected/underflow.sol.o.json +++ b/tests/testdata/outputs_expected/underflow.sol.o.json @@ -11,7 +11,7 @@ "debug": "" }, { - "title": "Integer Overflow", + "title": "Integer Overflow ", "description": "A possible integer overflow exists in the function `sendeth(address,uint256)`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "sendeth(address,uint256)", "type": "Warning", @@ -27,4 +27,4 @@ "debug": "" } ] -} +} \ No newline at end of file diff --git a/tests/testdata/outputs_expected/underflow.sol.o.markdown b/tests/testdata/outputs_expected/underflow.sol.o.markdown index 8baede40..07d375b8 100644 --- a/tests/testdata/outputs_expected/underflow.sol.o.markdown +++ b/tests/testdata/outputs_expected/underflow.sol.o.markdown @@ -24,7 +24,7 @@ The subtraction may result in a value < 0. A possible integer underflow exists in the function `sendeth(address,uint256)`. The subtraction may result in a value < 0. -## Integer Overflow +## Integer Overflow - Type: Warning - Contract: Unknown diff --git a/tests/testdata/outputs_expected/underflow.sol.o.text b/tests/testdata/outputs_expected/underflow.sol.o.text index bd2a4c99..f15d633f 100644 --- a/tests/testdata/outputs_expected/underflow.sol.o.text +++ b/tests/testdata/outputs_expected/underflow.sol.o.text @@ -16,7 +16,7 @@ A possible integer underflow exists in the function `sendeth(address,uint256)`. The subtraction may result in a value < 0. -------------------- -==== Integer Overflow ==== +==== Integer Overflow ==== Type: Warning Contract: Unknown Function name: sendeth(address,uint256) diff --git a/tests/testdata/outputs_expected/underflow.sol.text b/tests/testdata/outputs_expected/underflow.sol.text index a3409ad1..d44e5608 100644 --- a/tests/testdata/outputs_expected/underflow.sol.text +++ b/tests/testdata/outputs_expected/underflow.sol.text @@ -12,7 +12,7 @@ balances[msg.sender] -= _value -------------------- -==== Integer Overflow ==== +==== Integer Overflow ==== Type: Warning Contract: Under Function name: sendeth(address,uint256) From 01be304d454f116452ee46348b99232fd9ec843e Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Wed, 27 Jun 2018 22:53:23 +0530 Subject: [PATCH 16/62] Support max depth arguments for truffle project --- mythril/support/truffle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mythril/support/truffle.py b/mythril/support/truffle.py index 3a687f2f..ab885730 100644 --- a/mythril/support/truffle.py +++ b/mythril/support/truffle.py @@ -41,7 +41,7 @@ def analyze_truffle_project(args): ethcontract = ETHContract(bytecode, name=name) address = util.get_indexed_address(0) - sym = SymExecWrapper(ethcontract, address, max_depth=10) + sym = SymExecWrapper(ethcontract, address, max_depth=args.max_depth) issues = fire_lasers(sym) if not len(issues): From 87fdd2fa3c1024bf05989ec0480efe41d13ac6c2 Mon Sep 17 00:00:00 2001 From: Josh Asplund Date: Wed, 27 Jun 2018 17:42:15 -0500 Subject: [PATCH 17/62] Updates jinja dependency Fixes #270 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7e10f2ec..b2607ebe 100755 --- a/setup.py +++ b/setup.py @@ -319,7 +319,7 @@ setup( 'eth-rlp>=0.1.0', 'eth-tester>=0.1.0b21', 'coverage', - 'jinja2', + 'jinja2>=2.9', 'rlp<1.0.0', 'py-flags', 'mock', From c36ae84096bd0a477fdf6eee0b1279c0d3bfdddf Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Thu, 28 Jun 2018 10:49:46 +0530 Subject: [PATCH 18/62] Add jinja2 version dependency to requirements --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fc927d11..13e88c78 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ eth-keys>=0.2.0b3 eth-rlp>=0.1.0 eth-tester>=0.1.0b21 eth-utils>=1.0.1 -jinja2 +jinja2>=2.9 mock persistent>=4.2.0 plyvel From a2998f5b502f56900665d5316421097cdb5f9e59 Mon Sep 17 00:00:00 2001 From: "Dr. Sergey Pogodin" Date: Thu, 28 Jun 2018 08:49:49 +0200 Subject: [PATCH 19/62] Revert "Revert "Merge pull request #296 from norhh/branch/295"" This reverts commit 8a3a74da10b4bd93329d99e20b83d772195f86b2. --- mythril/analysis/modules/integer.py | 4 ++-- tests/testdata/outputs_expected/ether_send.sol.json | 4 ++-- tests/testdata/outputs_expected/ether_send.sol.o.json | 4 ++-- tests/testdata/outputs_expected/ether_send.sol.o.markdown | 2 +- tests/testdata/outputs_expected/ether_send.sol.o.text | 2 +- tests/testdata/outputs_expected/metacoin.sol.o.json | 4 ++-- tests/testdata/outputs_expected/metacoin.sol.o.markdown | 2 +- tests/testdata/outputs_expected/metacoin.sol.o.text | 2 +- tests/testdata/outputs_expected/overflow.sol.json | 4 ++-- tests/testdata/outputs_expected/overflow.sol.markdown | 2 +- tests/testdata/outputs_expected/overflow.sol.o.json | 4 ++-- tests/testdata/outputs_expected/overflow.sol.o.markdown | 2 +- tests/testdata/outputs_expected/overflow.sol.o.text | 2 +- tests/testdata/outputs_expected/overflow.sol.text | 2 +- tests/testdata/outputs_expected/underflow.sol.json | 4 ++-- tests/testdata/outputs_expected/underflow.sol.markdown | 2 +- tests/testdata/outputs_expected/underflow.sol.o.json | 4 ++-- tests/testdata/outputs_expected/underflow.sol.o.markdown | 2 +- tests/testdata/outputs_expected/underflow.sol.o.text | 2 +- tests/testdata/outputs_expected/underflow.sol.text | 2 +- 20 files changed, 28 insertions(+), 28 deletions(-) diff --git a/mythril/analysis/modules/integer.py b/mythril/analysis/modules/integer.py index 8c14b225..e21dbb58 100644 --- a/mythril/analysis/modules/integer.py +++ b/mythril/analysis/modules/integer.py @@ -83,9 +83,9 @@ def _check_integer_overflow(statespace, state, node): if not _verify_integer_overflow(statespace, node, expr, state, model, constraint, op0, op1): return issues - + # Build issue - issue = Issue(node.contract_name, node.function_name, instruction['address'], "Integer Overflow ", "Warning") + issue = Issue(node.contract_name, node.function_name, instruction['address'], "Integer Overflow", "Warning") issue.description = "A possible integer overflow exists in the function `{}`.\n" \ "The addition or multiplication may result in a value higher than the maximum representable integer.".format( diff --git a/tests/testdata/outputs_expected/ether_send.sol.json b/tests/testdata/outputs_expected/ether_send.sol.json index 2c89e2e2..08a5012c 100644 --- a/tests/testdata/outputs_expected/ether_send.sol.json +++ b/tests/testdata/outputs_expected/ether_send.sol.json @@ -14,7 +14,7 @@ "code": "msg.sender.transfer(this.balance)" }, { - "title": "Integer Overflow ", + "title": "Integer Overflow", "description": "A possible integer overflow exists in the function `invest()`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "invest()", "type": "Warning", @@ -25,4 +25,4 @@ "code": "balances[msg.sender] += msg.value" } ] -} \ No newline at end of file +} diff --git a/tests/testdata/outputs_expected/ether_send.sol.o.json b/tests/testdata/outputs_expected/ether_send.sol.o.json index 701f0535..6d6caad0 100644 --- a/tests/testdata/outputs_expected/ether_send.sol.o.json +++ b/tests/testdata/outputs_expected/ether_send.sol.o.json @@ -11,7 +11,7 @@ "debug": "" }, { - "title": "Integer Overflow ", + "title": "Integer Overflow", "description": "A possible integer overflow exists in the function `invest()`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "invest()", "type": "Warning", @@ -19,4 +19,4 @@ "debug": "" } ] -} \ No newline at end of file +} diff --git a/tests/testdata/outputs_expected/ether_send.sol.o.markdown b/tests/testdata/outputs_expected/ether_send.sol.o.markdown index 6f4b46be..e5a3f7cd 100644 --- a/tests/testdata/outputs_expected/ether_send.sol.o.markdown +++ b/tests/testdata/outputs_expected/ether_send.sol.o.markdown @@ -13,7 +13,7 @@ In the function `withdrawfunds()` a non-zero amount of Ether is sent to msg.send There is a check on storage index 1. This storage slot can be written to by calling the function `crowdfunding()`. -## Integer Overflow +## Integer Overflow - Type: Warning - Contract: Unknown diff --git a/tests/testdata/outputs_expected/ether_send.sol.o.text b/tests/testdata/outputs_expected/ether_send.sol.o.text index bba084fa..d6d548c7 100644 --- a/tests/testdata/outputs_expected/ether_send.sol.o.text +++ b/tests/testdata/outputs_expected/ether_send.sol.o.text @@ -8,7 +8,7 @@ In the function `withdrawfunds()` a non-zero amount of Ether is sent to msg.send There is a check on storage index 1. This storage slot can be written to by calling the function `crowdfunding()`. -------------------- -==== Integer Overflow ==== +==== Integer Overflow ==== Type: Warning Contract: Unknown Function name: invest() diff --git a/tests/testdata/outputs_expected/metacoin.sol.o.json b/tests/testdata/outputs_expected/metacoin.sol.o.json index 1977ed30..fd59d519 100644 --- a/tests/testdata/outputs_expected/metacoin.sol.o.json +++ b/tests/testdata/outputs_expected/metacoin.sol.o.json @@ -3,7 +3,7 @@ "error": null, "issues": [ { - "title": "Integer Overflow ", + "title": "Integer Overflow", "description": "A possible integer overflow exists in the function `sendToken(address,uint256)`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "sendToken(address,uint256)", "type": "Warning", @@ -11,4 +11,4 @@ "debug": "" } ] -} \ No newline at end of file +} diff --git a/tests/testdata/outputs_expected/metacoin.sol.o.markdown b/tests/testdata/outputs_expected/metacoin.sol.o.markdown index 6c38d51d..e2495d49 100644 --- a/tests/testdata/outputs_expected/metacoin.sol.o.markdown +++ b/tests/testdata/outputs_expected/metacoin.sol.o.markdown @@ -1,6 +1,6 @@ # Analysis results for test-filename.sol -## Integer Overflow +## Integer Overflow - Type: Warning - Contract: Unknown diff --git a/tests/testdata/outputs_expected/metacoin.sol.o.text b/tests/testdata/outputs_expected/metacoin.sol.o.text index 9cb0bc12..69fbc811 100644 --- a/tests/testdata/outputs_expected/metacoin.sol.o.text +++ b/tests/testdata/outputs_expected/metacoin.sol.o.text @@ -1,4 +1,4 @@ -==== Integer Overflow ==== +==== Integer Overflow ==== Type: Warning Contract: Unknown Function name: sendToken(address,uint256) diff --git a/tests/testdata/outputs_expected/overflow.sol.json b/tests/testdata/outputs_expected/overflow.sol.json index 0c7c0faf..92a56005 100644 --- a/tests/testdata/outputs_expected/overflow.sol.json +++ b/tests/testdata/outputs_expected/overflow.sol.json @@ -14,7 +14,7 @@ "code": "balances[msg.sender] -= _value" }, { - "title": "Integer Overflow ", + "title": "Integer Overflow", "description": "A possible integer overflow exists in the function `sendeth(address,uint256)`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "sendeth(address,uint256)", "type": "Warning", @@ -36,4 +36,4 @@ "code": "balances[msg.sender] - _value" } ] -} \ No newline at end of file +} diff --git a/tests/testdata/outputs_expected/overflow.sol.markdown b/tests/testdata/outputs_expected/overflow.sol.markdown index 840bbbfc..d6d4a49c 100644 --- a/tests/testdata/outputs_expected/overflow.sol.markdown +++ b/tests/testdata/outputs_expected/overflow.sol.markdown @@ -17,7 +17,7 @@ In */inputs/overflow.sol:12* balances[msg.sender] -= _value ``` -## Integer Overflow +## Integer Overflow - Type: Warning - Contract: Over diff --git a/tests/testdata/outputs_expected/overflow.sol.o.json b/tests/testdata/outputs_expected/overflow.sol.o.json index d9e1b0b8..8345944e 100644 --- a/tests/testdata/outputs_expected/overflow.sol.o.json +++ b/tests/testdata/outputs_expected/overflow.sol.o.json @@ -11,7 +11,7 @@ "debug": "" }, { - "title": "Integer Overflow ", + "title": "Integer Overflow", "description": "A possible integer overflow exists in the function `sendeth(address,uint256)`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "sendeth(address,uint256)", "type": "Warning", @@ -27,4 +27,4 @@ "debug": "" } ] -} \ No newline at end of file +} diff --git a/tests/testdata/outputs_expected/overflow.sol.o.markdown b/tests/testdata/outputs_expected/overflow.sol.o.markdown index 07d375b8..8baede40 100644 --- a/tests/testdata/outputs_expected/overflow.sol.o.markdown +++ b/tests/testdata/outputs_expected/overflow.sol.o.markdown @@ -24,7 +24,7 @@ The subtraction may result in a value < 0. A possible integer underflow exists in the function `sendeth(address,uint256)`. The subtraction may result in a value < 0. -## Integer Overflow +## Integer Overflow - Type: Warning - Contract: Unknown diff --git a/tests/testdata/outputs_expected/overflow.sol.o.text b/tests/testdata/outputs_expected/overflow.sol.o.text index f15d633f..bd2a4c99 100644 --- a/tests/testdata/outputs_expected/overflow.sol.o.text +++ b/tests/testdata/outputs_expected/overflow.sol.o.text @@ -16,7 +16,7 @@ A possible integer underflow exists in the function `sendeth(address,uint256)`. The subtraction may result in a value < 0. -------------------- -==== Integer Overflow ==== +==== Integer Overflow ==== Type: Warning Contract: Unknown Function name: sendeth(address,uint256) diff --git a/tests/testdata/outputs_expected/overflow.sol.text b/tests/testdata/outputs_expected/overflow.sol.text index 5dec25ed..64b072ca 100644 --- a/tests/testdata/outputs_expected/overflow.sol.text +++ b/tests/testdata/outputs_expected/overflow.sol.text @@ -12,7 +12,7 @@ balances[msg.sender] -= _value -------------------- -==== Integer Overflow ==== +==== Integer Overflow ==== Type: Warning Contract: Over Function name: sendeth(address,uint256) diff --git a/tests/testdata/outputs_expected/underflow.sol.json b/tests/testdata/outputs_expected/underflow.sol.json index 290dfe4e..3a2fbd91 100644 --- a/tests/testdata/outputs_expected/underflow.sol.json +++ b/tests/testdata/outputs_expected/underflow.sol.json @@ -14,7 +14,7 @@ "code": "balances[msg.sender] -= _value" }, { - "title": "Integer Overflow ", + "title": "Integer Overflow", "description": "A possible integer overflow exists in the function `sendeth(address,uint256)`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "sendeth(address,uint256)", "type": "Warning", @@ -36,4 +36,4 @@ "code": "balances[msg.sender] - _value" } ] -} \ No newline at end of file +} diff --git a/tests/testdata/outputs_expected/underflow.sol.markdown b/tests/testdata/outputs_expected/underflow.sol.markdown index ca8a6b17..b41efee7 100644 --- a/tests/testdata/outputs_expected/underflow.sol.markdown +++ b/tests/testdata/outputs_expected/underflow.sol.markdown @@ -17,7 +17,7 @@ In */inputs/underflow.sol:12* balances[msg.sender] -= _value ``` -## Integer Overflow +## Integer Overflow - Type: Warning - Contract: Under diff --git a/tests/testdata/outputs_expected/underflow.sol.o.json b/tests/testdata/outputs_expected/underflow.sol.o.json index d9e1b0b8..8345944e 100644 --- a/tests/testdata/outputs_expected/underflow.sol.o.json +++ b/tests/testdata/outputs_expected/underflow.sol.o.json @@ -11,7 +11,7 @@ "debug": "" }, { - "title": "Integer Overflow ", + "title": "Integer Overflow", "description": "A possible integer overflow exists in the function `sendeth(address,uint256)`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "sendeth(address,uint256)", "type": "Warning", @@ -27,4 +27,4 @@ "debug": "" } ] -} \ No newline at end of file +} diff --git a/tests/testdata/outputs_expected/underflow.sol.o.markdown b/tests/testdata/outputs_expected/underflow.sol.o.markdown index 07d375b8..8baede40 100644 --- a/tests/testdata/outputs_expected/underflow.sol.o.markdown +++ b/tests/testdata/outputs_expected/underflow.sol.o.markdown @@ -24,7 +24,7 @@ The subtraction may result in a value < 0. A possible integer underflow exists in the function `sendeth(address,uint256)`. The subtraction may result in a value < 0. -## Integer Overflow +## Integer Overflow - Type: Warning - Contract: Unknown diff --git a/tests/testdata/outputs_expected/underflow.sol.o.text b/tests/testdata/outputs_expected/underflow.sol.o.text index f15d633f..bd2a4c99 100644 --- a/tests/testdata/outputs_expected/underflow.sol.o.text +++ b/tests/testdata/outputs_expected/underflow.sol.o.text @@ -16,7 +16,7 @@ A possible integer underflow exists in the function `sendeth(address,uint256)`. The subtraction may result in a value < 0. -------------------- -==== Integer Overflow ==== +==== Integer Overflow ==== Type: Warning Contract: Unknown Function name: sendeth(address,uint256) diff --git a/tests/testdata/outputs_expected/underflow.sol.text b/tests/testdata/outputs_expected/underflow.sol.text index d44e5608..a3409ad1 100644 --- a/tests/testdata/outputs_expected/underflow.sol.text +++ b/tests/testdata/outputs_expected/underflow.sol.text @@ -12,7 +12,7 @@ balances[msg.sender] -= _value -------------------- -==== Integer Overflow ==== +==== Integer Overflow ==== Type: Warning Contract: Under Function name: sendeth(address,uint256) From b18edc8d5da2e43da48c24ff4350eaa1e9ff6682 Mon Sep 17 00:00:00 2001 From: Konrad Weiss Date: Mon, 2 Jul 2018 15:30:48 +0200 Subject: [PATCH 20/62] Adding files for source code params to symbolic value mapping --- mythril/solidnotary/abitypemapping.py | 0 mythril/solidnotary/calldata.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 mythril/solidnotary/abitypemapping.py create mode 100644 mythril/solidnotary/calldata.py diff --git a/mythril/solidnotary/abitypemapping.py b/mythril/solidnotary/abitypemapping.py new file mode 100644 index 00000000..e69de29b diff --git a/mythril/solidnotary/calldata.py b/mythril/solidnotary/calldata.py new file mode 100644 index 00000000..e69de29b From 43b27c0290bfaf15be6c251654dd912b6c411582 Mon Sep 17 00:00:00 2001 From: Konrad Weiss Date: Mon, 2 Jul 2018 15:32:06 +0200 Subject: [PATCH 21/62] Modification to mythril to also include abi information in the contract object --- mythril/ether/soliditycontract.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mythril/ether/soliditycontract.py b/mythril/ether/soliditycontract.py index 4ef2f4b6..14fa1057 100644 --- a/mythril/ether/soliditycontract.py +++ b/mythril/ether/soliditycontract.py @@ -51,6 +51,7 @@ class SolidityContract(ETHContract): if filename == input_file and name == _name: name = name code = contract['bin-runtime'] + abi = contract['abi'] creation_code = contract['bin'] srcmap = contract['srcmap-runtime'].split(";") has_contract = True @@ -65,6 +66,7 @@ class SolidityContract(ETHContract): if filename == input_file and len(contract['bin-runtime']): name = name code = contract['bin-runtime'] + abi = contract['abi'] creation_code = contract['bin'] srcmap = contract['srcmap-runtime'].split(";") has_contract = True @@ -89,6 +91,7 @@ class SolidityContract(ETHContract): lineno = self.solidity_files[idx].data[0:offset].count('\n') + 1 self.mappings.append(SourceMapping(idx, offset, length, lineno)) + self.abi = abi super().__init__(code, creation_code, name=name) From c8ad82bc87885454ed030d7f6076fef6a95865c4 Mon Sep 17 00:00:00 2001 From: Konrad Weiss Date: Mon, 2 Jul 2018 15:34:22 +0200 Subject: [PATCH 22/62] Add retrieval of constructor trace, using RETURN instead of STOP as exit point --- mythril/solidnotary/solidnotary.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/mythril/solidnotary/solidnotary.py b/mythril/solidnotary/solidnotary.py index 07798a45..3a3091d2 100644 --- a/mythril/solidnotary/solidnotary.py +++ b/mythril/solidnotary/solidnotary.py @@ -19,7 +19,7 @@ class SolidNotary: pass def get_transaction_traces(statespace): - logging.debug("Executing module: Transaction End") + print("get_transaction_traces") traces = [] @@ -27,11 +27,28 @@ def get_transaction_traces(statespace): node = statespace.nodes[k] for state in node.states: instruction = state.get_current_instruction() + # print("op: " + str(instruction['opcode']) + ((" " + instruction['argument']) if instruction['opcode'].startswith("PUSH") else "") + " stack: " + str(state.mstate.stack).replace("\n", "")+ " mem: " + str(state.mstate.memory).replace("\n", "")) if instruction['opcode'] == "STOP": if are_satisfiable(state.mstate.constraints): traces.append(TransactionTrace(state.environment.active_account.storage, state.mstate.constraints)) return traces +def get_construction_traces(statespace): + print("get_constructor_traces") + + traces = [] + + for k in statespace.nodes: + node = statespace.nodes[k] + for state in node.states: + instruction = state.get_current_instruction() + + # print("op: " + str(instruction['opcode']) + ((" " + instruction['argument']) if instruction['opcode'].startswith("PUSH") else "") + " stack: " + str(state.mstate.stack).replace("\n", "")+ " mem: " + str(state.mstate.memory).replace("\n", "")) + if instruction['opcode'] == "RETURN": + if are_satisfiable(state.mstate.constraints): + traces.append(TransactionTrace(state.environment.active_account.storage, state.mstate.constraints)) + return traces + def get_t_indexed_environment(active_account, index): # Initialize the execution environment From 3535248b81440d4e40cc27751d290663e4b6b16d Mon Sep 17 00:00:00 2001 From: Konrad Weiss Date: Mon, 2 Jul 2018 15:35:56 +0200 Subject: [PATCH 23/62] Add abi to the combined output expected for a contract --- mythril/ether/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mythril/ether/util.py b/mythril/ether/util.py index e01cae28..467c37df 100644 --- a/mythril/ether/util.py +++ b/mythril/ether/util.py @@ -18,7 +18,7 @@ def safe_decode(hex_encoded_string): def get_solc_json(file, solc_binary="solc", solc_args=None): - cmd = [solc_binary, "--combined-json", "bin,bin-runtime,srcmap-runtime", '--allow-paths', "."] + cmd = [solc_binary, "--combined-json", "bin,bin-runtime,srcmap-runtime,abi", '--allow-paths', "."] if solc_args: cmd.extend(solc_args.split(" ")) From 9f140925c0f21e1540ddad419d7052b0cd3c6316 Mon Sep 17 00:00:00 2001 From: Konrad Weiss Date: Mon, 2 Jul 2018 15:36:51 +0200 Subject: [PATCH 24/62] Using deepcopy for storage and constraints when combining --- mythril/solidnotary/transactiontrace.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mythril/solidnotary/transactiontrace.py b/mythril/solidnotary/transactiontrace.py index e8b4c9a3..44bcaca9 100644 --- a/mythril/solidnotary/transactiontrace.py +++ b/mythril/solidnotary/transactiontrace.py @@ -38,6 +38,8 @@ def is_t_variable(var): else: return False +# Todo constructor mit den intentet initvalues versorgen + def filter_for_t_variable_data(sym_vars): return list(filter(lambda x: is_t_variable(x), sym_vars)) @@ -45,11 +47,11 @@ def filter_for_t_variable_data(sym_vars): class TransactionTrace: def __init__(self, storage, constraints, lvl=1): - self.storage = storage # Todo give all non storage symbolic values that can be different every transaction the number one + self.storage = deepcopy(storage) # Todo give all non storage symbolic values that can be different every transaction the number one self.constraints = constraints # Todo eliminate all constraints that are not regarding the beginning of the transaction may not be necessary # eliminate all constraints that only contain names not in the set of names from storage self.constraints = simplify_constraints(self.constraints) # Todo simplification of the sum of constraints - self.tran_constraints = deepcopy(self.constraints) + self.tran_constraints = deepcopy(self.constraints) # Todo minimize them if they do not involve outside symb variables self.lvl = lvl self.sym_names = self.extract_sym_names_from_storage() self.sym_names.extend(self.extract_sym_names_from_constraints()) From beac3f0ccf815bef98e20e5b5f55306104902b3f Mon Sep 17 00:00:00 2001 From: Konrad Weiss Date: Mon, 2 Jul 2018 15:39:32 +0200 Subject: [PATCH 25/62] Change mythril to allow outside specification of custom global state, adding tuple of creation params to the sym wrapper to later reuse parts for custom analysis with for example a provided global state modified from the original --- mythril/analysis/symbolic.py | 9 +++++++-- mythril/mythril.py | 8 ++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/mythril/analysis/symbolic.py b/mythril/analysis/symbolic.py index 3d3e1a6d..fe76f892 100644 --- a/mythril/analysis/symbolic.py +++ b/mythril/analysis/symbolic.py @@ -11,7 +11,7 @@ class SymExecWrapper: Wrapper class for the LASER Symbolic virtual machine. Symbolically executes the code and does a bit of pre-analysis for convenience. ''' - def __init__(self, contract, address, dynloader=None, max_depth=12): + def __init__(self, contract, address, dynloader=None, max_depth=12, gblState=None): account = svm.Account(address, contract.disassembly, contract_name=contract.name) @@ -19,7 +19,12 @@ class SymExecWrapper: self.laser = svm.LaserEVM(self.accounts, dynamic_loader=dynloader, max_depth=max_depth) - self.laser.sym_exec(address) + if not (gblState is None): + # Adding the ability to specify a custom global state, e.g. machine configuration from outside + node = self.laser._sym_exec(gblState) + self.laser.nodes[node.uid] = node + else: + self.laser.sym_exec(address) self.nodes = self.laser.nodes self.edges = self.laser.edges diff --git a/mythril/mythril.py b/mythril/mythril.py index bc123e1d..28760582 100644 --- a/mythril/mythril.py +++ b/mythril/mythril.py @@ -13,6 +13,8 @@ import re from ethereum import utils from solc.exceptions import SolcError import solc +from copy import deepcopy +from mythril.disassembler.disassembly import Disassembly from mythril.ether import util from mythril.ether.contractstorage import get_persistent_storage @@ -321,6 +323,12 @@ class Mythril(object): dynloader=DynLoader(self.eth) if self.dynld else None, max_depth=max_depth) + + contr_to_const = deepcopy(contract) + contr_to_const.disassembly = Disassembly(contr_to_const.creation_code) + contr_to_const.code = contr_to_const.creation_code + sym.sym_constr = (contr_to_const, address, DynLoader(self.eth) if self.dynld else None, max_depth) + issues = fire_lasers(sym, modules) if type(contract) == SolidityContract: From 675adbae30e44baa2cb9cb4b51f93fbaac92614c Mon Sep 17 00:00:00 2001 From: Konrad Weiss Date: Mon, 2 Jul 2018 15:40:54 +0200 Subject: [PATCH 26/62] Addign computation of minimal encoding length to use for memory initalization when symbolically executing the constructor. --- mythril/analysis/modules/build_traces.py | 56 ++++++++- mythril/solidnotary/calldata.py | 150 +++++++++++++++++++++++ 2 files changed, 200 insertions(+), 6 deletions(-) diff --git a/mythril/analysis/modules/build_traces.py b/mythril/analysis/modules/build_traces.py index 0d6bcd38..76519410 100644 --- a/mythril/analysis/modules/build_traces.py +++ b/mythril/analysis/modules/build_traces.py @@ -1,8 +1,13 @@ from mythril.analysis.report import Issue from mythril.solidnotary.transactiontrace import TransactionTrace -from mythril.solidnotary.solidnotary import get_transaction_traces +from mythril.disassembler.disassembly import Disassembly +from laser.ethereum.svm import GlobalState, Account, Environment, MachineState, CalldataType +from z3 import BitVec +from mythril.analysis.symbolic import SymExecWrapper +from mythril.solidnotary.solidnotary import get_transaction_traces, get_construction_traces from mythril.solidnotary.z3utility import are_satisfiable import logging +from mythril.solidnotary.calldata import get_minimal_constructor_param_encoding_len, abi_json_to_abi ''' @@ -19,23 +24,62 @@ def print_obj(obj): print(obj.children()) print() +def get_constr_glbstate(contract, address): + + mstate = MachineState(gas=10000000) + + minimal_const_byte_len = get_minimal_constructor_param_encoding_len(abi_json_to_abi(contract.abi)) + + # better would be to append symbolic params to the bytecode such that the codecopy instruction that copies the + # params into memory takes care of placing them onto the memory with the respective size. + for i in range(int(minimal_const_byte_len / 32)): + mstate.mem_extend(128 + 32 * i, 32) + mstate.memory.insert(128 + 32 * i, BitVec('calldata_' + contract.name + '_' + str(i * 32), 256)) + + # Todo Replace pure placement of enough symbolic 32 Byte-words with placement of symbolic variables that contain + # the name of the solidity variables + + accounts = {address: Account(address, contract.disassembly, contract_name=contract.name)} + + environment = Environment( + accounts[address], + BitVec("caller", 256), + [], + BitVec("gasprice", 256), + BitVec("callvalue", 256), + BitVec("origin", 256), + calldata_type=CalldataType.SYMBOLIC, + ) + + # Todo find source for account info, maybe the std statespace? + + return GlobalState(accounts, environment, mstate) + def execute(statespace): logging.debug("Executing module: Transaction End") - traces = [] + constructor_trace = {} + + if hasattr(statespace, "sym_constr"): + sym_exe_tuple = statespace.sym_constr + glbstate = get_constr_glbstate(sym_exe_tuple[0], sym_exe_tuple[1]) + sym_exe_tuple = statespace.sym_constr + (glbstate,) + constr_statespace = SymExecWrapper(*sym_exe_tuple) + constructor_trace = get_construction_traces(constr_statespace) # Todo the traces here should not contain references to storages anymore + for t in constructor_trace: + t.pp_trace() traces = get_transaction_traces(statespace) - for trace in traces: + for trace in constructor_trace: comp_trace_lvls = trace.apply_up_to_trace_levels(traces, 3) for trace_lvl in comp_trace_lvls: for t in trace_lvl: - if t.lvl == 4: t.pp_trace() -# for trace in traces: -# trace.pp_trace() + # for trace in traces: + # trace.pp_trace() #print("==== Second level traces ====") #for trace in traces: diff --git a/mythril/solidnotary/calldata.py b/mythril/solidnotary/calldata.py index e69de29b..27891271 100644 --- a/mythril/solidnotary/calldata.py +++ b/mythril/solidnotary/calldata.py @@ -0,0 +1,150 @@ +from re import search +from mythril.exceptions import CompilerError +from subprocess import Popen, PIPE +import json +import sys + +class CalldataMap: + + def __init__(self, abi, solc_v): + pass + + def __init__(self, solidity_file, solc_v): + pass + +def get_minimal_param_encoding_len(abi_inputs): + len = 0 + return len + +def get_minimal_byte_enc_len(abi_obj): + if type(abi_obj) == list: + return sum([head_byte_length_min(t) for t in abi_obj]) + sum([tail_byte_length_min(t) for t in abi_obj]) + if type(abi_obj) == str: + stype = abi_obj + else: + stype = abi_obj['type'] + if stype == 'tuple': + return get_minimal_byte_enc_len(abi_obj['components']) + try: + match = search(r'(?P
.*)\[(?P[0-9]+)\]', stype)
+        pre = match['pre']
+        post = match['post']
+        return int(post) * get_minimal_byte_enc_len(pre)
+    except (KeyError, TypeError) as e:
+        pass
+    if stype.endswith("[]"):
+        return 32
+
+    if stype == "string":
+        return 32
+    elif stype == "bytes":
+        return 32 # 2 was the smallest observed value, remix did not allow specification of zero size bytes
+    elif [stype.startswith(prefix_type) for prefix_type in ["int", "uint", "address", "bool", "fixed", "ufixed", "bytes"]]:
+        return 32
+
+def head_byte_length_min(abi_obj):
+    if is_dynamic(abi_obj):
+        return 32
+    else:
+        return get_minimal_byte_enc_len(abi_obj)
+
+
+def tail_byte_length_min(abi_obj):
+    if is_dynamic(abi_obj):
+        return get_minimal_byte_enc_len(abi_obj)
+    else:
+        return 0
+
+def get_minimal_wsize(abi_obj):
+    stype = abi_obj['type']
+    if type(stype) == list:
+        return sum(list(map(lambda a: get_minimal_wsize(a), stype)))
+    if stype in ["bytes", "string"] or stype.endswith("[]"):
+        return True
+    if stype == 'tuple':
+        return True in [is_dynamic(elem) for elem in abi_obj['components']]
+    try:
+        match = search(r'(?P
.*)(?P\[[0-9]+\])', stype)
+        pre = match['pre']
+        # post = match['post']
+        return is_dynamic(pre)
+    except (KeyError | TypeError):
+        pass
+    return False
+
+
+def get_minimal_constructor_param_encoding_len(abi):
+    for spec in abi:
+        try:
+            if spec['type'] == 'constructor':
+                con_inputs = spec['inputs']
+                return get_minimal_byte_enc_len(con_inputs)
+        except KeyError:
+            print("ABI does not contain inputs for constructor")
+    return -1
+
+def get_minimal_constr_param_byte_length(filename, contract_name=None):
+    abi_decoded = get_solc_abi_json(filename)
+    return get_minimal_constructor_param_encoding_len(abi_decoded)
+
+def is_dynamic(abi_obj):
+    if type(abi_obj) == list:
+        return True in list(map(lambda a: is_dynamic(a), abi_obj))
+    if type(abi_obj) == str:
+        stype = abi_obj
+    else:
+        stype = abi_obj['type']
+    if stype in ["bytes", "string"] or stype.endswith("[]"):
+        return True
+    if stype == 'tuple':
+        return True in [is_dynamic(elem) for elem in abi_obj['components']]
+    try:
+        match = search(r'(?P
.*)(?P\[[0-9]+\])', stype)
+        pre = match['pre']
+        # post = match['post']
+        return is_dynamic(pre)
+    except (KeyError, TypeError) as e:
+        pass
+    return False
+
+def get_solc_abi_json(file, solc_binary="solc", solc_args=None):
+
+    cmd = [solc_binary, "--abi", '--allow-paths', "."]
+
+    if solc_args:
+        cmd.extend(solc_args.split(" "))
+
+    cmd.append(file)
+
+    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.")
+
+    out = stdout.decode("UTF-8")
+
+    if not len(out):
+        raise CompilerError("Compilation failed.")
+
+    out = out[out.index("["):]
+
+    print(out)
+
+    return json.loads(out)
+
+def abi_json_to_abi(abi_json):
+    return json.loads(abi_json)
+
+
+if __name__ == "__main__":
+    if len(sys.argv) > 1:
+        print("Size:")
+        print(get_minimal_constr_param_byte_length(sys.argv[1]))
+    else:
+        print("Error: No file specified")

From 3ba2550354a1712518d1a0fe9f72de155d8ca108 Mon Sep 17 00:00:00 2001
From: Nikhil Parasaram 
Date: Mon, 2 Jul 2018 19:35:19 +0530
Subject: [PATCH 27/62] v0.18.7

---
 setup.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup.py b/setup.py
index b2607ebe..3cbf5f97 100755
--- a/setup.py
+++ b/setup.py
@@ -6,7 +6,7 @@ import os
 # Package version (vX.Y.Z). It must match git tag being used for CircleCI
 # deployment; otherwise the build will failed.
 
-VERSION = "v0.18.6"
+VERSION = "v0.18.7"
 
 
 class VerifyVersionCommand(install):

From 9d01a6107a9fa7ff93d959e5d3cd35b47a3164e6 Mon Sep 17 00:00:00 2001
From: Nikhil Parasaram 
Date: Mon, 2 Jul 2018 22:31:20 +0530
Subject: [PATCH 28/62] Use bytecode as CODESIZE

---
 mythril/disassembler/disassembly.py | 1 +
 mythril/laser/ethereum/svm.py       | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/mythril/disassembler/disassembly.py b/mythril/disassembler/disassembly.py
index 8a21d11a..787f297f 100644
--- a/mythril/disassembler/disassembly.py
+++ b/mythril/disassembler/disassembly.py
@@ -11,6 +11,7 @@ class Disassembly:
         self.xrefs = []
         self.func_to_addr = {}
         self.addr_to_func = {}
+        self.bytecode = code
 
         try:
             mythril_dir = os.environ['MYTHRIL_DIR']
diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py
index 1c24eeaf..b6d722c9 100644
--- a/mythril/laser/ethereum/svm.py
+++ b/mythril/laser/ethereum/svm.py
@@ -635,7 +635,7 @@ class LaserEVM:
                 state.stack.append(environment.sender)
 
             elif op == 'CODESIZE':
-                state.stack.append(len(disassembly.instruction_list))
+                state.stack.append(len(disassembly.bytecode) // 2)
 
             if op == 'SHA3':
                 op0, op1 = state.stack.pop(), state.stack.pop()

From baa67f6ebec1f6a50620ae5d2258ac7f61ead9ca Mon Sep 17 00:00:00 2001
From: Nikhil Parasaram 
Date: Tue, 3 Jul 2018 22:09:14 +0530
Subject: [PATCH 29/62] Get contract size from infura for EXTCODESIZE

---
 mythril/laser/ethereum/svm.py | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py
index b6d722c9..f864f8c1 100644
--- a/mythril/laser/ethereum/svm.py
+++ b/mythril/laser/ethereum/svm.py
@@ -1,4 +1,5 @@
 from mythril.laser.ethereum import helper, natives
+from mythril import mythril
 from ethereum import utils
 from enum import Enum
 from flags import Flags
@@ -677,7 +678,22 @@ class LaserEVM:
 
             elif op == 'EXTCODESIZE':
                 addr = state.stack.pop()
-                state.stack.append(BitVec("extcodesize", 256))
+                myth = mythril.Mythril()
+                try:
+                    myth.set_api_rpc_infura()
+                except Exception as e:
+                    logging.info("Error while connecting to infura")
+                    state.stack.append(BitVec("extcodesize", 256))
+                    continue
+
+                try:
+                    _, code = myth.load_from_address(hex(helper.get_concrete_int(addr)))
+                except AttributeError:
+                    logging.info("EXTCODECOPY is called with symbolic addr")
+                    state.stack.append(BitVec("extcodesize", 256))
+                    continue
+
+                state.stack.append(len(code.code) // 2)
 
             elif op == 'EXTCODECOPY':
                 # Not implemented

From 8c5bc3d85e5b140241f53f19366b2abb5682d2b0 Mon Sep 17 00:00:00 2001
From: Nikhil Parasaram 
Date: Tue, 3 Jul 2018 22:30:13 +0530
Subject: [PATCH 30/62] Add address for the extcodesize stack

---
 mythril/laser/ethereum/svm.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py
index f864f8c1..3a078fa4 100644
--- a/mythril/laser/ethereum/svm.py
+++ b/mythril/laser/ethereum/svm.py
@@ -683,14 +683,14 @@ class LaserEVM:
                     myth.set_api_rpc_infura()
                 except Exception as e:
                     logging.info("Error while connecting to infura")
-                    state.stack.append(BitVec("extcodesize", 256))
+                    state.stack.append(BitVec("extcodesize_"+str(addr), 256))
                     continue
 
                 try:
                     _, code = myth.load_from_address(hex(helper.get_concrete_int(addr)))
                 except AttributeError:
-                    logging.info("EXTCODECOPY is called with symbolic addr")
-                    state.stack.append(BitVec("extcodesize", 256))
+                    logging.info("unsupported symbolic address for EXTCODESIZE")
+                    state.stack.append(BitVec("extcodesize_"+str(addr), 256))
                     continue
 
                 state.stack.append(len(code.code) // 2)

From 042d5b0c8846a2ec2917b82bc8870c8d60ec60ef Mon Sep 17 00:00:00 2001
From: Nikhil Parasaram 
Date: Tue, 3 Jul 2018 23:32:05 +0530
Subject: [PATCH 31/62] Use dynloader over mythril

---
 mythril/laser/ethereum/svm.py | 17 ++++++++---------
 mythril/mythril.py            |  5 +++--
 2 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py
index 3a078fa4..f8f75d24 100644
--- a/mythril/laser/ethereum/svm.py
+++ b/mythril/laser/ethereum/svm.py
@@ -1,5 +1,4 @@
 from mythril.laser.ethereum import helper, natives
-from mythril import mythril
 from ethereum import utils
 from enum import Enum
 from flags import Flags
@@ -678,22 +677,22 @@ class LaserEVM:
 
             elif op == 'EXTCODESIZE':
                 addr = state.stack.pop()
-                myth = mythril.Mythril()
+
                 try:
-                    myth.set_api_rpc_infura()
-                except Exception as e:
-                    logging.info("Error while connecting to infura")
+                    addr = hex(helper.get_concrete_int(addr))
+                except AttributeError:
+                    logging.info("unsupported symbolic address for EXTCODESIZE")
                     state.stack.append(BitVec("extcodesize_"+str(addr), 256))
                     continue
 
                 try:
-                    _, code = myth.load_from_address(hex(helper.get_concrete_int(addr)))
-                except AttributeError:
-                    logging.info("unsupported symbolic address for EXTCODESIZE")
+                    code = self.dynamic_loader.dynld(environment.active_account.address, addr)
+                except Exception as e:
+                    logging.info("error accessing contract storage due to: "+str(e))
                     state.stack.append(BitVec("extcodesize_"+str(addr), 256))
                     continue
 
-                state.stack.append(len(code.code) // 2)
+                state.stack.append(len(code.bytecode) // 2)
 
             elif op == 'EXTCODECOPY':
                 # Not implemented
diff --git a/mythril/mythril.py b/mythril/mythril.py
index 611ad703..cb011820 100644
--- a/mythril/mythril.py
+++ b/mythril/mythril.py
@@ -336,9 +336,10 @@ class Mythril(object):
 
         all_issues = []
         for contract in (contracts or self.contracts):
-
+            if self.eth is None:
+                self.set_api_rpc_infura()
             sym = SymExecWrapper(contract, address,
-                                 dynloader=DynLoader(self.eth) if self.dynld else None,
+                                 dynloader=DynLoader(self.eth) if not self.dynld else None,
                                  max_depth=max_depth)
 
             issues = fire_lasers(sym, modules)

From 2305f29af26ebb2e4408b5a1ad17c2a09851bfc5 Mon Sep 17 00:00:00 2001
From: Nikhil Parasaram 
Date: Tue, 3 Jul 2018 23:37:23 +0530
Subject: [PATCH 32/62] Fix the argument for dynloader in fire_lasers

---
 mythril/mythril.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/mythril/mythril.py b/mythril/mythril.py
index cb011820..cda02f40 100644
--- a/mythril/mythril.py
+++ b/mythril/mythril.py
@@ -339,7 +339,7 @@ class Mythril(object):
             if self.eth is None:
                 self.set_api_rpc_infura()
             sym = SymExecWrapper(contract, address,
-                                 dynloader=DynLoader(self.eth) if not self.dynld else None,
+                                 dynloader=DynLoader(self.eth) if not self.dynld else self.dynld,
                                  max_depth=max_depth)
 
             issues = fire_lasers(sym, modules)

From 06ddeb13dc3579a6fb1083834deb854dbaff8710 Mon Sep 17 00:00:00 2001
From: Konrad Weiss 
Date: Tue, 3 Jul 2018 23:31:32 +0200
Subject: [PATCH 33/62] Modifies the symbolic suffixes enclosing them into
 brackets and symplifying them with the z3 simplification algorithm

---
 mythril/laser/ethereum/svm.py | 35 ++++++++++++++++++++---------------
 1 file changed, 20 insertions(+), 15 deletions(-)

diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py
index b6d722c9..a0896d32 100644
--- a/mythril/laser/ethereum/svm.py
+++ b/mythril/laser/ethereum/svm.py
@@ -396,7 +396,7 @@ class LaserEVM:
                     result = Concat(BitVecVal(0, 248), Extract(oft + 7, oft, s1))
                 except AttributeError:
                     logging.debug("BYTE: Unsupported symbolic byte offset")
-                    result = BitVec(str(simplify(s1)) + "_" + str(simplify(s0)), 256)
+                    result = BitVec(str(simplify(s1)) + "[" + str(simplify(s0)) + "]", 256)
 
                 state.stack.append(simplify(result))
 
@@ -442,7 +442,7 @@ class LaserEVM:
                 base, exponent = helper.pop_bitvec(state), helper.pop_bitvec(state)
 
                 if (type(base) != BitVecNumRef) or (type(exponent) != BitVecNumRef):
-                    state.stack.append(BitVec(str(base) + "_EXP_" + str(exponent), 256))
+                    state.stack.append(BitVec("(" + str(simplify(base)) + ")^(" + str(simplify(exponent)) + ")", 256))
                 elif (base.as_long() == 2):
                     if exponent.as_long() == 0:
                         state.stack.append(BitVecVal(1, 256))
@@ -535,11 +535,11 @@ class LaserEVM:
 
                 except AttributeError:
                     logging.debug("CALLDATALOAD: Unsupported symbolic index")
-                    state.stack.append(BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(op0), 256))
+                    state.stack.append(BitVec("calldata_" + str(environment.active_account.contract_name) + "[" + str(simplify(op0)) + "]", 256))
                     continue
                 except IndexError:
                     logging.debug("Calldata not set, using symbolic variable instead")
-                    state.stack.append(BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(op0), 256))
+                    state.stack.append(BitVec("calldata_" + str(environment.active_account.contract_name) + "[" + str(simplify(op0)) + "]", 256))
                     continue
 
                 if type(b) == int:
@@ -554,10 +554,10 @@ class LaserEVM:
                         state.stack.append(BitVecVal(int.from_bytes(val, byteorder='big'), 256))
 
                     except:
-                        state.stack.append(BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(op0), 256))
+                        state.stack.append(BitVec("calldata_" + str(environment.active_account.contract_name) + "[" + str(simplify(op0)) + "]", 256))
                 else:
                     # symbolic variable
-                    state.stack.append(BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(op0), 256))
+                    state.stack.append(BitVec("calldata_" + str(environment.active_account.contract_name) + "[" + str(simplify(op0)) + "]", 256))
 
             elif op == 'CALLDATASIZE':
 
@@ -575,20 +575,25 @@ class LaserEVM:
                     logging.debug("Unsupported symbolic memory offset in CALLDATACOPY")
                     continue
 
+                dstart_sym = False
                 try:
                     dstart = helper.get_concrete_int(op1)
+                    dstart_sym = True
                 except:
                     logging.debug("Unsupported symbolic calldata offset in CALLDATACOPY")
-                    state.mem_extend(mstart, 1)
-                    state.memory[mstart] = BitVec("calldata_" + str(environment.active_account.contract_name) + "_cpy", 256)
-                    continue
+                    dstart = simplify(op1)
 
+                size_sym = False
                 try:
                     size = helper.get_concrete_int(op2)
+                    size_sym = True
                 except:
                     logging.debug("Unsupported symbolic size in CALLDATACOPY")
+                    size = simplify(op2)
+
+                if dstart_sym or size_sym:
                     state.mem_extend(mstart, 1)
-                    state.memory[mstart] = BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(dstart), 256)
+                    state.memory[mstart] = BitVec("calldata_" + str(environment.active_account.contract_name) + "[" + str(dstart) + ": + " + str(size) + "]", 256)
                     continue
 
                 if size > 0:
@@ -598,7 +603,7 @@ class LaserEVM:
                     except:
                         logging.debug("Memory allocation error: mstart = " + str(mstart) + ", size = " + str(size))
                         state.mem_extend(mstart, 1)
-                        state.memory[mstart] = BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(dstart), 256)
+                        state.memory[mstart] = BitVec("calldata_" + str(environment.active_account.contract_name) + "[" + str(dstart) + ": + " + str(size) + "]", 256)
                         continue
 
                     try:
@@ -610,7 +615,7 @@ class LaserEVM:
                     except:
                         logging.debug("Exception copying calldata to memory")
 
-                        state.memory[mstart] = BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(dstart), 256)
+                        state.memory[mstart] = BitVec("calldata_" + str(environment.active_account.contract_name) + "[" + str(dstart) + ": + " + str(size) + "]", 256)
 
             elif op == 'STOP':
                 if len(self.call_stack):
@@ -645,7 +650,7 @@ class LaserEVM:
 
                 except:
                     # Can't access symbolic memory offsets
-                    state.stack.append(BitVec("KECCAC_mem_" + str(op0) + ")", 256))
+                    state.stack.append(BitVec("KECCAC_mem[" + str(simplify(op0)) + "]", 256))
                     continue
 
                 try:
@@ -717,14 +722,14 @@ class LaserEVM:
                     offset = helper.get_concrete_int(op0)
                 except AttributeError:
                     logging.debug("Can't MLOAD from symbolic index")
-                    data = BitVec("mem_" + str(op0), 256)
+                    data = BitVec("mem[" + str(simplify(op0)) + "]", 256)
                     state.stack.append(data)
                     continue
 
                 try:
                     data = helper.concrete_int_from_bytes(state.memory, offset)
                 except IndexError:  # Memory slot not allocated
-                    data = BitVec("mem_" + str(offset), 256)
+                    data = BitVec("mem[" + str(offset)+"]", 256)
                 except TypeError:  # Symbolic memory
                     data = state.memory[offset]
 

From 5c349e4a8c3e402208d79580477cd48554aaade9 Mon Sep 17 00:00:00 2001
From: Nikhil Parasaram 
Date: Wed, 4 Jul 2018 23:39:42 +0530
Subject: [PATCH 34/62] Revert Dynld changes

---
 mythril/mythril.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/mythril/mythril.py b/mythril/mythril.py
index cda02f40..b63cc137 100644
--- a/mythril/mythril.py
+++ b/mythril/mythril.py
@@ -339,7 +339,7 @@ class Mythril(object):
             if self.eth is None:
                 self.set_api_rpc_infura()
             sym = SymExecWrapper(contract, address,
-                                 dynloader=DynLoader(self.eth) if not self.dynld else self.dynld,
+                                 dynloader=DynLoader(self.eth) if self.dynld else None,
                                  max_depth=max_depth)
 
             issues = fire_lasers(sym, modules)

From 631ebe07b7d5de30453775c0a3fbfdf0ff12edba Mon Sep 17 00:00:00 2001
From: JoranHonig 
Date: Wed, 4 Jul 2018 22:35:19 +0200
Subject: [PATCH 35/62] Worklist implementation (#288)

Refactor laser to work with a worklist vs a simple loop
---
 mythril/analysis/callgraph.py                 |    4 +-
 .../modules/transaction_order_independence.py |    2 +-
 mythril/analysis/ops.py                       |    4 +-
 mythril/analysis/report.py                    |    4 +-
 mythril/analysis/traceexplore.py              |    8 +-
 mythril/ether/soliditycontract.py             |    2 +-
 mythril/laser/ethereum/call.py                |  142 ++
 mythril/laser/ethereum/cfg.py                 |   65 +
 mythril/laser/ethereum/gascost.py             |   74 -
 mythril/laser/ethereum/instructions.py        |  983 +++++++++++++
 mythril/laser/ethereum/natives.py             |    2 +-
 mythril/laser/ethereum/state.py               |  151 ++
 mythril/laser/ethereum/svm.py                 | 1253 ++---------------
 mythril/laser/ethereum/taint_analysis.py      |    3 +-
 mythril/laser/ethereum/{helper.py => util.py} |    0
 mythril/support/truffle.py                    |    6 +-
 tests/analysis/test_delegatecall.py           |   12 +-
 tests/native_test.py                          |   18 +-
 tests/svm_test.py                             |   12 +-
 tests/taint_result_test.py                    |    4 +-
 tests/taint_runner_test.py                    |   17 +-
 .../outputs_expected/calls.sol.graph.html     |  202 ---
 .../outputs_expected/calls.sol.o.graph.html   |    4 +-
 .../outputs_expected/calls.sol.o.json         |   87 +-
 .../outputs_expected/calls.sol.o.markdown     |   22 +-
 .../outputs_expected/calls.sol.o.text         |   22 +-
 .../outputs_expected/ether_send.sol.json      |   28 -
 .../ether_send.sol.o.graph.html               |    4 +-
 .../exceptions.sol.graph.html                 |  252 ----
 .../exceptions.sol.o.graph.html               |    4 +-
 .../outputs_expected/exceptions.sol.o.json    |   39 +-
 .../kinds_of_calls.sol.graph.html             |  178 ---
 .../kinds_of_calls.sol.o.graph.html           |    4 +-
 .../kinds_of_calls.sol.o.json                 |   23 +-
 .../kinds_of_calls.sol.o.markdown             |   24 +-
 .../kinds_of_calls.sol.o.text                 |   18 +-
 .../outputs_expected/metacoin.sol.graph.html  |  150 --
 .../metacoin.sol.o.graph.html                 |    4 +-
 .../multi_contracts.sol.graph.html            |  134 --
 .../outputs_expected/multi_contracts.sol.json |   17 -
 .../multi_contracts.sol.markdown              |   18 -
 .../multi_contracts.sol.o.graph.html          |    4 +-
 .../multi_contracts.sol.o.json                |   15 +-
 .../outputs_expected/multi_contracts.sol.text |   14 -
 .../nonascii.sol.o.graph.html                 |    4 +-
 .../outputs_expected/origin.sol.o.graph.html  |    4 +-
 .../outputs_expected/origin.sol.o.json        |   15 +-
 .../outputs_expected/overflow.sol.json        |   39 -
 .../outputs_expected/overflow.sol.markdown    |   52 -
 .../overflow.sol.o.graph.html                 |    4 +-
 .../outputs_expected/overflow.sol.text        |   42 -
 .../returnvalue.sol.graph.html                |  146 --
 .../returnvalue.sol.o.graph.html              |    4 +-
 .../outputs_expected/returnvalue.sol.o.json   |   31 +-
 .../returnvalue.sol.o.markdown                |    2 +-
 .../outputs_expected/returnvalue.sol.o.text   |    2 +-
 .../outputs_expected/rubixi.sol.o.json        |  166 +++
 .../outputs_expected/rubixi.sol.o.markdown    |  238 ++++
 .../outputs_expected/rubixi.sol.o.text        |  177 +++
 .../outputs_expected/suicide.sol.json         |   17 -
 .../outputs_expected/suicide.sol.markdown     |   19 -
 .../outputs_expected/suicide.sol.o.graph.html |    4 +-
 .../outputs_expected/suicide.sol.o.json       |   15 +-
 .../outputs_expected/suicide.sol.text         |   15 -
 .../outputs_expected/underflow.sol.graph.html |  168 ---
 .../outputs_expected/underflow.sol.json       |   39 -
 .../outputs_expected/underflow.sol.markdown   |   52 -
 .../underflow.sol.o.graph.html                |    4 +-
 .../outputs_expected/underflow.sol.text       |   42 -
 .../outputs_expected/weak_random.sol.o.json   |   46 +
 .../weak_random.sol.o.markdown                |   62 +
 .../outputs_expected/weak_random.sol.o.text   |   46 +
 72 files changed, 2306 insertions(+), 3182 deletions(-)
 create mode 100644 mythril/laser/ethereum/call.py
 create mode 100644 mythril/laser/ethereum/cfg.py
 delete mode 100644 mythril/laser/ethereum/gascost.py
 create mode 100644 mythril/laser/ethereum/instructions.py
 create mode 100644 mythril/laser/ethereum/state.py
 rename mythril/laser/ethereum/{helper.py => util.py} (100%)
 delete mode 100644 tests/testdata/outputs_expected/calls.sol.graph.html
 delete mode 100644 tests/testdata/outputs_expected/ether_send.sol.json
 delete mode 100644 tests/testdata/outputs_expected/exceptions.sol.graph.html
 delete mode 100644 tests/testdata/outputs_expected/kinds_of_calls.sol.graph.html
 delete mode 100644 tests/testdata/outputs_expected/metacoin.sol.graph.html
 delete mode 100644 tests/testdata/outputs_expected/multi_contracts.sol.graph.html
 delete mode 100644 tests/testdata/outputs_expected/multi_contracts.sol.json
 delete mode 100644 tests/testdata/outputs_expected/multi_contracts.sol.markdown
 delete mode 100644 tests/testdata/outputs_expected/multi_contracts.sol.text
 delete mode 100644 tests/testdata/outputs_expected/overflow.sol.json
 delete mode 100644 tests/testdata/outputs_expected/overflow.sol.markdown
 delete mode 100644 tests/testdata/outputs_expected/overflow.sol.text
 delete mode 100644 tests/testdata/outputs_expected/returnvalue.sol.graph.html
 create mode 100644 tests/testdata/outputs_expected/rubixi.sol.o.json
 create mode 100644 tests/testdata/outputs_expected/rubixi.sol.o.markdown
 create mode 100644 tests/testdata/outputs_expected/rubixi.sol.o.text
 delete mode 100644 tests/testdata/outputs_expected/suicide.sol.json
 delete mode 100644 tests/testdata/outputs_expected/suicide.sol.markdown
 delete mode 100644 tests/testdata/outputs_expected/suicide.sol.text
 delete mode 100644 tests/testdata/outputs_expected/underflow.sol.graph.html
 delete mode 100644 tests/testdata/outputs_expected/underflow.sol.json
 delete mode 100644 tests/testdata/outputs_expected/underflow.sol.markdown
 delete mode 100644 tests/testdata/outputs_expected/underflow.sol.text
 create mode 100644 tests/testdata/outputs_expected/weak_random.sol.o.json
 create mode 100644 tests/testdata/outputs_expected/weak_random.sol.o.markdown
 create mode 100644 tests/testdata/outputs_expected/weak_random.sol.o.text

diff --git a/mythril/analysis/callgraph.py b/mythril/analysis/callgraph.py
index 3d2db6ec..d9cd2a80 100644
--- a/mythril/analysis/callgraph.py
+++ b/mythril/analysis/callgraph.py
@@ -134,8 +134,8 @@ def extract_edges(statespace):
         label = re.sub(r'([^_])([\d]{2}\d+)', lambda m: m.group(1) + hex(int(m.group(2))), label)
 
         edges.append({
-            'from': str(edge.as_dict()['from']),
-            'to': str(edge.as_dict()['to']),
+            'from': str(edge.as_dict['from']),
+            'to': str(edge.as_dict['to']),
             'arrows': 'to',
             'label': label,
             'smooth': {'type': 'cubicBezier'}
diff --git a/mythril/analysis/modules/transaction_order_independence.py b/mythril/analysis/modules/transaction_order_independence.py
index 415b5a48..629d21a3 100644
--- a/mythril/analysis/modules/transaction_order_independence.py
+++ b/mythril/analysis/modules/transaction_order_independence.py
@@ -111,7 +111,7 @@ def _get_influencing_sstores(statespace, interesting_storages):
     for sstore_state, node in _get_states_with_opcode(statespace, 'SSTORE'):
         index, value = sstore_state.mstate.stack[-1], sstore_state.mstate.stack[-2]
         try:
-            index = helper.get_concrete_int(index)
+            index = util.get_concrete_int(index)
         except AttributeError:
             index = str(index)
         if "storage_{}".format(index) not in interesting_storages:
diff --git a/mythril/analysis/ops.py b/mythril/analysis/ops.py
index 632940d1..999bbb12 100644
--- a/mythril/analysis/ops.py
+++ b/mythril/analysis/ops.py
@@ -1,6 +1,6 @@
 from z3 import *
 from enum import Enum
-from mythril.laser.ethereum import helper
+from mythril.laser.ethereum import util
 
 
 class VarType(Enum):
@@ -20,7 +20,7 @@ class Variable:
 
 def get_variable(i):
     try:
-        return Variable(helper.get_concrete_int(i), VarType.CONCRETE)
+        return Variable(util.get_concrete_int(i), VarType.CONCRETE)
     except AttributeError:
         return Variable(simplify(i), VarType.SYMBOLIC)
 
diff --git a/mythril/analysis/report.py b/mythril/analysis/report.py
index 1e61092a..2e2d3425 100644
--- a/mythril/analysis/report.py
+++ b/mythril/analysis/report.py
@@ -18,6 +18,8 @@ class Issue:
         self.code = None
         self.lineno = None
 
+
+    @property
     def as_dict(self):
 
         issue = {'title': self.title, 'description':self.description, 'function': self.function, 'type': self.type, 'address': self.address, 'debug': self.debug}
@@ -47,7 +49,7 @@ class Report:
         pass
 
     def sorted_issues(self):
-        issue_list = [issue.as_dict() for key, issue in self.issues.items()]
+        issue_list = [issue.as_dict for key, issue in self.issues.items()]
         return sorted(issue_list, key=operator.itemgetter('address', 'title'))
 
     def append_issue(self, issue):
diff --git a/mythril/analysis/traceexplore.py b/mythril/analysis/traceexplore.py
index db21ddff..dc7af177 100644
--- a/mythril/analysis/traceexplore.py
+++ b/mythril/analysis/traceexplore.py
@@ -43,7 +43,7 @@ def get_serializable_statespace(statespace):
         def get_state_accounts(state):
             state_accounts = []
             for key in state.accounts:
-                account = state.accounts[key].as_dict()
+                account = state.accounts[key].as_dict
                 account.pop('code', None)
                 account['balance'] = str(account['balance'])
                 
@@ -57,7 +57,7 @@ def get_serializable_statespace(statespace):
                 })  
             return state_accounts          
                 
-        states = [{'machine': x.mstate.as_dict(), 'accounts': get_state_accounts(x)} for x in node.states]
+        states = [{'machine': x.mstate.as_dict, 'accounts': get_state_accounts(x)} for x in node.states]
         
         for state in states:
             state['machine']['stack'] = [str(s) for s in state['machine']['stack']]
@@ -94,8 +94,8 @@ def get_serializable_statespace(statespace):
         code = re.sub("([0-9a-f]{8})[0-9a-f]+", lambda m: m.group(1) + "(...)", code)
 
         s_edge = {
-            'from': str(edge.as_dict()['from']),
-            'to': str(edge.as_dict()['to']),
+            'from': str(edge.as_dict['from']),
+            'to': str(edge.as_dict['to']),
             'arrows': 'to',
             'label': label,
             'smooth': { 'type': "cubicBezier" }
diff --git a/mythril/ether/soliditycontract.py b/mythril/ether/soliditycontract.py
index b1bb41a4..919b3d30 100644
--- a/mythril/ether/soliditycontract.py
+++ b/mythril/ether/soliditycontract.py
@@ -1,4 +1,4 @@
-import mythril.laser.ethereum.helper as helper
+import mythril.laser.ethereum.util as helper
 from mythril.ether.ethcontract import ETHContract
 from mythril.ether.util import *
 from mythril.exceptions import NoContractFoundError
diff --git a/mythril/laser/ethereum/call.py b/mythril/laser/ethereum/call.py
new file mode 100644
index 00000000..23145b9e
--- /dev/null
+++ b/mythril/laser/ethereum/call.py
@@ -0,0 +1,142 @@
+import logging
+from z3 import BitVec, simplify
+import mythril.laser.ethereum.util as util
+from mythril.laser.ethereum.state import Account
+from mythril.laser.ethereum.svm import CalldataType
+import re
+
+"""
+This module contains the business logic used by Instruction in instructions.py
+to get the necessary elements from the stack and determine the parameters for the new global state.
+"""
+
+
+def get_call_parameters(global_state, dynamic_loader, with_value=False):
+    """
+    Gets call parameters from global state
+    Pops the values from the stack and determines output parameters
+    :param global_state: state to look in
+    :param dynamic_loader: dynamic loader to use
+    :param with_value: whether to pop the value argument from the stack
+    :return: callee_account, call_data, value, call_data_type, gas
+    """
+    state = global_state.mstate
+    instr = global_state.get_current_instruction()
+
+    if with_value:
+        gas, to, value, meminstart, meminsz, memory_out_offset, memory_out_size = \
+            state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop()
+    else:
+        gas, to, meminstart, meminsz, memory_out_offset, memory_out_size = \
+            state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop()
+        value = None
+
+    callee_address = get_callee_address(global_state, dynamic_loader, to)
+
+    callee_account = None
+    call_data, call_data_type = get_call_data(global_state, meminstart, meminsz, False)
+
+    if int(callee_address, 16) >= 5:
+        call_data, call_data_type = get_call_data(global_state, meminstart, meminsz)
+        callee_account = get_callee_account(global_state, callee_address, dynamic_loader)
+
+    return callee_address, callee_account, call_data, value, call_data_type, gas, memory_out_offset, memory_out_size
+
+
+def get_callee_address(global_state, dynamic_loader, to):
+    """
+    Gets the address of the callee
+    :param global_state: state to look in
+    :param dynamic_loader:  dynamic loader to use
+    :return: Address of the callee
+    """
+    environment = global_state.environment
+
+    try:
+        callee_address = hex(util.get_concrete_int(to))
+    except AttributeError:
+        logging.info("Symbolic call encountered")
+
+        match = re.search(r'storage_(\d+)', str(simplify(to)))
+        logging.debug("CALL to: " + str(simplify(to)))
+
+        if match is None or dynamic_loader is None:
+            raise ValueError()
+
+        index = int(match.group(1))
+        logging.info("Dynamic contract address at storage index {}".format(index))
+
+        # attempt to read the contract address from instance storage
+        # TODO: we need to do this correctly using multi transactional analysis
+        try:
+            callee_address = dynamic_loader.read_storage(environment.active_account.address, index)
+        except:
+            logging.debug("Error accessing contract storage.")
+            raise ValueError
+
+        # testrpc simply returns the address, geth response is more elaborate.
+        if not re.match(r"^0x[0-9a-f]{40}$", callee_address):
+            callee_address = "0x" + callee_address[26:]
+
+    return callee_address
+
+
+def get_callee_account(global_state, callee_address, dynamic_loader):
+    """
+    Gets the callees account from the global_state
+    :param global_state: state to look in
+    :param callee_address: address of the callee
+    :param dynamic_loader: dynamic loader to use
+    :return: Account belonging to callee
+    """
+    environment = global_state.environment
+    accounts = global_state.accounts
+
+    try:
+        return global_state.accounts[callee_address]
+    except KeyError:
+        # We have a valid call address, but contract is not in the modules list
+        logging.info("Module with address " + callee_address + " not loaded.")
+
+    if dynamic_loader is None:
+        raise ValueError()
+
+    logging.info("Attempting to load dependency")
+
+    try:
+        code = dynamic_loader.dynld(environment.active_account.address, callee_address)
+    except Exception as e:
+        logging.info("Unable to execute dynamic loader.")
+        raise ValueError()
+    if code is None:
+        logging.info("No code returned, not a contract account?")
+        raise ValueError()
+
+    accounts[callee_address] = Account(callee_address, code, callee_address)
+
+    logging.info("Dependency loaded: " + callee_address)
+
+
+def get_call_data(global_state, memory_start, memory_size, pad=True):
+    """
+    Gets call_data from the global_state
+    :param global_state: state to look in
+    :param memory_start: Start index
+    :param memory_size: Size
+    :return: Tuple containing: call_data array from memory or empty array if symbolic, type found
+    """
+    state = global_state.mstate
+    try:
+        # TODO: This only allows for either fully concrete or fully symbolic calldata.
+        # Improve management of memory and callata to support a mix between both types.
+        call_data = state.memory[util.get_concrete_int(memory_start):util.get_concrete_int(memory_start + memory_size)]
+        if len(call_data) < 32 and pad:
+            call_data += [0] * (32 - len(call_data))
+        call_data_type = CalldataType.CONCRETE
+        logging.debug("Calldata: " + str(call_data))
+    except AttributeError:
+        logging.info("Unsupported symbolic calldata offset")
+        call_data_type = CalldataType.SYMBOLIC
+        call_data = []
+
+    return call_data, call_data_type
diff --git a/mythril/laser/ethereum/cfg.py b/mythril/laser/ethereum/cfg.py
new file mode 100644
index 00000000..f462f5cf
--- /dev/null
+++ b/mythril/laser/ethereum/cfg.py
@@ -0,0 +1,65 @@
+from flags import Flags
+from enum import Enum
+
+gbl_next_uid = 0  # node counter
+
+class JumpType(Enum):
+    CONDITIONAL = 1
+    UNCONDITIONAL = 2
+    CALL = 3
+    RETURN = 4
+
+
+class NodeFlags(Flags):
+    FUNC_ENTRY = 1
+    CALL_RETURN = 2
+
+
+class Node:
+    def __init__(self, contract_name, start_addr=0, constraints=None):
+        constraints = constraints if constraints else []
+        self.contract_name = contract_name
+        self.start_addr = start_addr
+        self.states = []
+        self.constraints = constraints
+        self.function_name = "unknown"
+        self.flags = NodeFlags()
+
+        # Self-assign a unique ID
+
+        global gbl_next_uid
+
+        self.uid = gbl_next_uid
+        gbl_next_uid += 1
+
+    def get_cfg_dict(self):
+
+        code = ""
+
+        for state in self.states:
+
+            instruction = state.get_current_instruction()
+
+            code += str(instruction['address']) + " " + instruction['opcode']
+            if instruction['opcode'].startswith("PUSH"):
+                code += " " + instruction['argument']
+
+            code += "\\n"
+
+        return dict(contract_name=self.contract_name, start_addr=self.start_addr, function_name=self.function_name,
+                    code=code)
+
+
+class Edge:
+    def __init__(self, node_from, node_to, edge_type=JumpType.UNCONDITIONAL, condition=None):
+        self.node_from = node_from
+        self.node_to = node_to
+        self.type = edge_type
+        self.condition = condition
+
+    def __str__(self):
+        return str(self.as_dict)
+
+    @property
+    def as_dict(self):
+        return {"from": self.node_from, 'to': self.node_to}
diff --git a/mythril/laser/ethereum/gascost.py b/mythril/laser/ethereum/gascost.py
deleted file mode 100644
index 9d3a919d..00000000
--- a/mythril/laser/ethereum/gascost.py
+++ /dev/null
@@ -1,74 +0,0 @@
-gascost = {
-    'PUSH': 3,
-    'DUP': 3,
-    'SWAP': 3,
-    'STOP': 0,
-    'ADD': 3,
-    'MUL': 5,
-	'SUB': 3,
-    'DIV': 5,
-    'SDIV': 5,
-    'MOD': 5,
-    'SMOD': 5,
-    'ADDMOD': 8,
-    'MULMOD': 8,
-    'EXP': 10,
-    'SIGNEXTEND': 5,
-    'LT': 3,
-    'GT': 3,
-    'SLT': 3,
-    'SGT': 3,
-    'EQ': 3,
-    'ISZERO': 3,
-    'AND': 3,
-    'OR': 3,
-    'XOR': 3,
-    'NOT': 3,
-    'BYTE': 3,
-    'SHA3': 30,
-    'ADDRESS': 2,
-    'BALANCE': 400,
-    'ORIGIN': 2,
-    'CALLER': 2,
-    'CALLVALUE': 2,
-    'CALLDATALOAD': 3,
-    'CALLDATASIZE': 2,
-    'CALLDATACOPY': 3,
-    'CODESIZE': 2,
-    'CODECOPY': 3,
-    'GASPRICE': 2,
-    'EXTCODESIZE': 700,
-    'EXTCODECOPY': 700,
-    'BLOCKHASH': 20,
-    'COINBASE': 2,
-    'TIMESTAMP': 2,
-    'NUMBER': 2,
-    'DIFFICULTY': 2,
-    'GASLIMIT': 2,
-    'POP': 2,
-    'MLOAD': 3,
-    'MSTORE': 3,
-    'MSTORE8': 3,
-    'SLOAD': 50,
-    'SSTORE': 0,
-    'JUMP': 8,
-    'JUMPI': 10,
-    'PC': 2,
-    'MSIZE': 2,
-    'GAS': 2,
-    'JUMPDEST': 1,
-    'LOG0': 375,
-    'LOG1': 750,
-    'LOG2': 1125,
-    'LOG3': 1500,
-    'LOG4': 1875,
-    'CREATE': 32000,
-    'CALL': 40,
-    'CALLCODE': 40,
-    'RETURN': 0,
-    'DELEGATECALL': 40,
-    'CALLBLACKBOX': 40,
-    'STATICCALL': 40,
-    'REVERT': 0,
-    'SUICIDE': 5000,
-}
\ No newline at end of file
diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py
new file mode 100644
index 00000000..4d90da95
--- /dev/null
+++ b/mythril/laser/ethereum/instructions.py
@@ -0,0 +1,983 @@
+import binascii
+import logging
+from copy import copy, deepcopy
+
+import ethereum.opcodes as opcodes
+from ethereum import utils
+from z3 import BitVec, Extract, UDiv, simplify, Concat, ULT, UGT, BitVecNumRef, Not, \
+    is_false
+from z3 import BitVecVal, If, BoolRef
+
+import mythril.laser.ethereum.util as helper
+from mythril.laser.ethereum import util
+from mythril.laser.ethereum.call import get_call_parameters
+from mythril.laser.ethereum.state import GlobalState, MachineState, Environment, CalldataType
+import mythril.laser.ethereum.natives as natives
+
+TT256 = 2 ** 256
+TT256M1 = 2 ** 256 - 1
+
+
+class StackUnderflowException(Exception):
+    pass
+
+
+class StopSignal(Exception):
+    pass
+
+
+def instruction(func):
+    """ Wrapper that handles copy and original return """
+
+    def wrapper(self, global_state):
+        global_state_copy = copy(global_state)
+        new_global_states = func(self, global_state_copy)
+        for state in new_global_states:
+            state.mstate.pc += 1
+        return new_global_states
+    return wrapper
+
+
+class Instruction:
+    """
+    Instruction class is used to mutate a state according to the current instruction
+    """
+
+    def __init__(self, op_code, dynamic_loader):
+        self.dynamic_loader = dynamic_loader
+        self.op_code = op_code
+
+    def evaluate(self, global_state):
+        """ Performs the mutation for this instruction """
+        # Generalize some ops
+        logging.debug("Evaluating {}".format(self.op_code))
+        op = self.op_code.lower()
+        if self.op_code.startswith("PUSH"):
+            op = "push"
+        elif self.op_code.startswith("DUP"):
+            op = "dup"
+        elif self.op_code.startswith("SWAP"):
+            op = "swap"
+        elif self.op_code.startswith("LOG"):
+            op = "log"
+
+        instruction_mutator = getattr(self, op + '_', None)
+
+        if instruction_mutator is None:
+            raise NotImplementedError
+
+        return instruction_mutator(global_state)
+
+    @instruction
+    def jumpdest_(self, global_state):
+        return [global_state]
+
+    @instruction
+    def push_(self, global_state):
+        value = BitVecVal(int(global_state.get_current_instruction()['argument'][2:], 16), 256)
+        global_state.mstate.stack.append(value)
+        return [global_state]
+
+    @instruction
+    def dup_(self, global_state):
+        value = int(global_state.get_current_instruction()['opcode'][3:], 10)
+        global_state.mstate.stack.append(global_state.mstate.stack[-value])
+        return [global_state]
+
+    @instruction
+    def swap_(self, global_state):
+        depth = int(self.op_code[4:])
+        try:
+            stack = global_state.mstate.stack
+            stack[-depth - 1], stack[-1] = stack[-1], stack[-depth - 1]
+        except IndexError:
+            raise StackUnderflowException()
+        return [global_state]
+
+    @instruction
+    def pop_(self, global_state):
+        try:
+            global_state.mstate.stack.pop()
+        except IndexError:
+            raise StackUnderflowException()
+        return [global_state]
+
+    @instruction
+    def and_(self, global_state):
+        try:
+            stack = global_state.mstate.stack
+            op1, op2 = stack.pop(), stack.pop()
+            if type(op1) == BoolRef:
+                op1 = If(op1, BitVecVal(1, 256), BitVecVal(0, 256))
+            if type(op2) == BoolRef:
+                op2 = If(op2, BitVecVal(1, 256), BitVecVal(0, 256))
+
+            stack.append(op1 & op2)
+        except IndexError:
+            raise StackUnderflowException()
+        return [global_state]
+
+    @instruction
+    def or_(self, global_state):
+        stack = global_state.mstate.stack
+        try:
+            op1, op2 = stack.pop(), stack.pop()
+
+            if type(op1) == BoolRef:
+                op1 = If(op1, BitVecVal(1, 256), BitVecVal(0, 256))
+
+            if type(op2) == BoolRef:
+                op2 = If(op2, BitVecVal(1, 256), BitVecVal(0, 256))
+
+            stack.append(op1 | op2)
+        except IndexError:  # Stack underflow
+            raise StackUnderflowException()
+        return [global_state]
+
+    @instruction
+    def xor_(self, global_state):
+        mstate = global_state.mstate
+        mstate.stack.append(mstate.stack.pop() ^ mstate.stack.pop())
+        return [global_state]
+
+    @instruction
+    def not_(self, global_state: GlobalState):
+        mstate = global_state.mstate
+        mstate.stack.append(TT256M1 - mstate.stack.pop())
+        return [global_state]
+
+    @instruction
+    def byte_(self, global_state):
+        mstate = global_state.mstate
+        op0, op1 = mstate.stack.pop(), mstate.stack.pop()
+
+        try:
+            index = util.get_concrete_int(op0)
+            offset = (31 - index) * 8
+            result = Concat(BitVecVal(0, 248), Extract(offset + 7, offset, op1))
+        except AttributeError:
+            logging.debug("BYTE: Unsupported symbolic byte offset")
+            result = BitVec(str(simplify(op1)) + "_" + str(simplify(op0)), 256)
+
+        mstate.stack.append(simplify(result))
+        return [global_state]
+
+    # Arithmetic
+    @instruction
+    def add_(self, global_state):
+        global_state.mstate.stack.append(
+            (helper.pop_bitvec(global_state.mstate) + helper.pop_bitvec(global_state.mstate)))
+        return [global_state]
+
+    @instruction
+    def sub_(self, global_state):
+        global_state.mstate.stack.append(
+            (helper.pop_bitvec(global_state.mstate) - helper.pop_bitvec(global_state.mstate)))
+        return [global_state]
+
+    @instruction
+    def mul_(self, global_state):
+        global_state.mstate.stack.append(
+            (helper.pop_bitvec(global_state.mstate) * helper.pop_bitvec(global_state.mstate)))
+        return [global_state]
+
+    @instruction
+    def div_(self, global_state):
+        global_state.mstate.stack.append(
+            UDiv(util.pop_bitvec(global_state.mstate), util.pop_bitvec(global_state.mstate)))
+        return [global_state]
+
+    @instruction
+    def sdiv_(self, global_state):
+        s0, s1 = util.pop_bitvec(global_state.mstate), util.pop_bitvec(global_state.mstate)
+        global_state.mstate.stack.append(s0 / s1)
+        return [global_state]
+
+    @instruction
+    def smod_(self, global_state):
+        s0, s1 = util.pop_bitvec(global_state.mstate), util.pop_bitvec(global_state.mstate)
+        global_state.mstate.stack.append(0 if s1 == 0 else s0 % s1)
+        return [global_state]
+
+    @instruction
+    def addmod_(self, global_state):
+        s0, s1, s2 = util.pop_bitvec(global_state.mstate), util.pop_bitvec(global_state.mstate), util.pop_bitvec(
+            global_state.mstate)
+        global_state.mstate.stack.append((s0 + s1) % s2)
+        return [global_state]
+
+    @instruction
+    def mulmod_(self, global_state):
+        s0, s1, s2 = util.pop_bitvec(global_state.mstate), util.pop_bitvec(global_state.mstate), util.pop_bitvec(
+            global_state.mstate)
+        global_state.mstate.stack.append((s0 * s1) % s2 if s2 else 0)
+
+    @instruction
+    def exp_(self, global_state):
+        state = global_state.mstate
+        # we only implement 2 ** x
+        base, exponent = util.pop_bitvec(state), util.pop_bitvec(state)
+
+        if (type(base) != BitVecNumRef) or (type(exponent) != BitVecNumRef):
+            state.stack.append(BitVec(str(base) + "_EXP_" + str(exponent), 256))
+        elif base.as_long() == 2:
+            if exponent.as_long() == 0:
+                state.stack.append(BitVecVal(1, 256))
+            else:
+                state.stack.append(base << (exponent - 1))
+        else:
+            state.stack.append(base)
+        return [global_state]
+
+    @instruction
+    def signextend_(self, global_state):
+        state = global_state.mstate
+        s0, s1 = state.stack.pop(), state.stack.pop()
+
+        try:
+            s0 = util.get_concrete_int(s0)
+            s1 = util.get_concrete_int(s1)
+
+            if s0 <= 31:
+                testbit = s0 * 8 + 7
+                if s1 & (1 << testbit):
+                    state.stack.append(s1 | (TT256 - (1 << testbit)))
+                else:
+                    state.stack.append(s1 & ((1 << testbit) - 1))
+            else:
+                state.stack.append(s1)
+            # TODO: broad exception handler
+        except:
+            return []
+
+        return [global_state]
+
+    # Comparisons
+    @instruction
+    def lt_(self, global_state):
+        state = global_state.mstate
+        exp = ULT(util.pop_bitvec(state), util.pop_bitvec(state))
+        state.stack.append(exp)
+        return [global_state]
+
+    @instruction
+    def gt_(self, global_state):
+        state = global_state.mstate
+        exp = UGT(util.pop_bitvec(state), util.pop_bitvec(state))
+        state.stack.append(exp)
+        return [global_state]
+
+    @instruction
+    def slt_(self, global_state):
+        state = global_state.mstate
+        exp = util.pop_bitvec(state) < util.pop_bitvec(state)
+        state.stack.append(exp)
+        return [global_state]
+
+    @instruction
+    def sgt_(self, global_state):
+        state = global_state.mstate
+
+        exp = util.pop_bitvec(state) > util.pop_bitvec(state)
+        state.stack.append(exp)
+        return [global_state]
+
+    @instruction
+    def eq_(self, global_state):
+        state = global_state.mstate
+
+        op1 = state.stack.pop()
+        op2 = state.stack.pop()
+
+        if type(op1) == BoolRef:
+            op1 = If(op1, BitVecVal(1, 256), BitVecVal(0, 256))
+
+        if type(op2) == BoolRef:
+            op2 = If(op2, BitVecVal(1, 256), BitVecVal(0, 256))
+
+        exp = op1 == op2
+
+        state.stack.append(exp)
+        return [global_state]
+
+    @instruction
+    def iszero_(self, global_state):
+        state = global_state.mstate
+
+        val = state.stack.pop()
+        exp = val == False if type(val) == BoolRef else val == 0
+        state.stack.append(exp)
+
+        return [global_state]
+
+    # Call data
+    @instruction
+    def callvalue_(self, global_state):
+        state = global_state.mstate
+        environment = global_state.environment
+        state.stack.append(environment.callvalue)
+
+        return [global_state]
+
+    @instruction
+    def calldataload_(self, global_state):
+        state = global_state.mstate
+        environment = global_state.environment
+        op0 = state.stack.pop()
+
+        try:
+            offset = util.get_concrete_int(simplify(op0))
+            b = environment.calldata[offset]
+        except AttributeError:
+            logging.debug("CALLDATALOAD: Unsupported symbolic index")
+            state.stack.append(
+                BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(op0), 256))
+            return [global_state]
+        except IndexError:
+            logging.debug("Calldata not set, using symbolic variable instead")
+            state.stack.append(
+                BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(op0), 256))
+            return [global_state]
+
+        if type(b) == int:
+            val = b''
+
+            try:
+                for i in range(offset, offset + 32):
+                    val += environment.calldata[i].to_bytes(1, byteorder='big')
+
+                logging.debug("Final value: " + str(int.from_bytes(val, byteorder='big')))
+                state.stack.append(BitVecVal(int.from_bytes(val, byteorder='big'), 256))
+            # FIXME: broad exception catch
+            except:
+                state.stack.append(
+                    BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(op0), 256))
+        else:
+            # symbolic variable
+            state.stack.append(
+                BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(op0), 256))
+
+        return [global_state]
+
+    @instruction
+    def calldatasize_(self, global_state):
+        state = global_state.mstate
+        environment = global_state.environment
+        if environment.calldata_type == CalldataType.SYMBOLIC:
+            state.stack.append(BitVec("calldatasize_" + environment.active_account.contract_name, 256))
+        else:
+            state.stack.append(BitVecVal(len(environment.calldata), 256))
+        return [global_state]
+
+    @instruction
+    def calldatacopy_(self, global_state):
+        state = global_state.mstate
+        environment = global_state.environment
+        op0, op1, op2 = state.stack.pop(), state.stack.pop(), state.stack.pop()
+
+        try:
+            mstart = util.get_concrete_int(op0)
+            # FIXME: broad exception catch
+        except:
+            logging.debug("Unsupported symbolic memory offset in CALLDATACOPY")
+            return [global_state]
+
+        try:
+            dstart = util.get_concrete_int(op1)
+            # FIXME: broad exception catch
+        except:
+            logging.debug("Unsupported symbolic calldata offset in CALLDATACOPY")
+            state.mem_extend(mstart, 1)
+            state.memory[mstart] = BitVec("calldata_" + str(environment.active_account.contract_name) + "_cpy",
+                                          256)
+            return [global_state]
+
+        try:
+            size = util.get_concrete_int(op2)
+            # FIXME: broad exception catch
+        except:
+            logging.debug("Unsupported symbolic size in CALLDATACOPY")
+            state.mem_extend(mstart, 1)
+            state.memory[mstart] = BitVec(
+                "calldata_" + str(environment.active_account.contract_name) + "_" + str(dstart), 256)
+            return [global_state]
+
+        if size > 0:
+            try:
+                state.mem_extend(mstart, size)
+            # FIXME: broad exception catch
+            except:
+                logging.debug("Memory allocation error: mstart = " + str(mstart) + ", size = " + str(size))
+                state.mem_extend(mstart, 1)
+                state.memory[mstart] = BitVec(
+                    "calldata_" + str(environment.active_account.contract_name) + "_" + str(dstart), 256)
+                return [global_state]
+
+            try:
+                i_data = environment.calldata[dstart]
+
+                for i in range(mstart, mstart + size):
+                    state.memory[i] = environment.calldata[i_data]
+                    i_data += 1
+            except:
+                logging.debug("Exception copying calldata to memory")
+
+                state.memory[mstart] = BitVec(
+                    "calldata_" + str(environment.active_account.contract_name) + "_" + str(dstart), 256)
+        return [global_state]
+
+    # Environment
+    @instruction
+    def address_(self, global_state):
+        state = global_state.mstate
+        environment = global_state.environment
+        state.stack.append(environment.address)
+        return [global_state]
+
+    @instruction
+    def balance_(self, global_state):
+        state = global_state.mstate
+        address = state.stack.pop()
+        state.stack.append(BitVec("balance_at_" + str(address), 256))
+        return [global_state]
+
+    @instruction
+    def origin_(self, global_state):
+        state = global_state.mstate
+        environment = global_state.environment
+        state.stack.append(environment.origin)
+        return [global_state]
+
+    @instruction
+    def caller_(self, global_state):
+        state = global_state.mstate
+        environment = global_state.environment
+        state.stack.append(environment.sender)
+        return [global_state]
+
+    @instruction
+    def codesize_(self, global_state):
+        state = global_state.mstate
+        environment = global_state.environment
+        disassembly = environment.code
+        state.stack.append(len(disassembly.bytecode) // 2)
+        return [global_state]
+
+    @instruction
+    def sha3_(self, global_state):
+        state = global_state.mstate
+        environment = global_state.environment
+        op0, op1 = state.stack.pop(), state.stack.pop()
+
+        try:
+            index, length = util.get_concrete_int(op0), util.get_concrete_int(op1)
+        # FIXME: broad exception catch
+        except:
+            # Can't access symbolic memory offsets
+            state.stack.append(BitVec("KECCAC_mem_" + str(op0) + ")", 256))
+            return [global_state]
+
+        try:
+            data = b''
+
+            for i in range(index, index + length):
+                data += util.get_concrete_int(state.memory[i]).to_bytes(1, byteorder='big')
+                i += 1
+            # FIXME: broad exception catch
+        except:
+
+            svar = str(state.memory[index])
+
+            svar = svar.replace(" ", "_")
+
+            state.stack.append(BitVec("keccac_" + svar, 256))
+            return [global_state]
+
+        keccac = utils.sha3(utils.bytearray_to_bytestr(data))
+        logging.debug("Computed SHA3 Hash: " + str(binascii.hexlify(keccac)))
+
+        state.stack.append(BitVecVal(util.concrete_int_from_bytes(keccac, 0), 256))
+        return [global_state]
+
+    @instruction
+    def gasprice_(self, global_state):
+        global_state.mstate.stack.append(BitVec("gasprice", 256))
+        return [global_state]
+
+    @instruction
+    def codecopy(self, global_state):
+        # FIXME: not implemented
+        state = global_state.mstate
+        start, s1, size = state.stack.pop(), state.stack.pop(), state.stack.pop()
+        return [global_state]
+
+    @instruction
+    def extcodesize_(self, global_state):
+        state = global_state.mstate
+        addr = state.stack.pop()
+        environment = global_state.environment
+        try:
+            addr = hex(helper.get_concrete_int(addr))
+        except AttributeError:
+            logging.info("unsupported symbolic address for EXTCODESIZE")
+            state.stack.append(BitVec("extcodesize_" + str(addr), 256))
+            return [global_state]
+
+        try:
+            code = self.dynamic_loader.dynld(environment.active_account.address, addr)
+        except Exception as e:
+            logging.info("error accessing contract storage due to: " + str(e))
+            state.stack.append(BitVec("extcodesize_" + str(addr), 256))
+            return [global_state]
+
+        state.stack.append(len(code.bytecode) // 2)
+        return [global_state]
+
+    @instruction
+    def extcodecopy_(self, global_state):
+        # FIXME: not implemented
+        state = global_state.mstate
+        addr = state.stack.pop()
+        start, s2, size = state.stack.pop(), state.stack.pop(), state.stack.pop()
+        return [global_state]
+
+    @instruction
+    def returndatasize_(self, global_state):
+        global_state.mstate.stack.append(BitVec("returndatasize", 256))
+        return [global_state]
+
+    @instruction
+    def blockhash_(self, global_state):
+        state = global_state.mstate
+        blocknumber = state.stack.pop()
+        state.stack.append(BitVec("blockhash_block_" + str(blocknumber), 256))
+        return global_state
+
+    @instruction
+    def coinbase_(self, global_state):
+        global_state.mstate.stack.append(BitVec("coinbase", 256))
+        return [global_state]
+
+    @instruction
+    def timestamp_(self, global_state):
+        global_state.mstate.stack.append(BitVec("timestamp", 256))
+        return [global_state]
+
+    @instruction
+    def number_(self, global_state):
+        global_state.mstate.stack.append(BitVec("block_number", 256))
+        return [global_state]
+
+    @instruction
+    def difficulty_(self, global_state):
+        global_state.mstate.stack.append(BitVec("block_difficulty", 256))
+        return [global_state]
+
+    @instruction
+    def gaslimit_(self, global_state):
+        global_state.mstate.stack.append(BitVec("block_gaslimit", 256))
+        return [global_state]
+
+    # Memory operations
+    @instruction
+    def mload_(self, global_state):
+        state = global_state.mstate
+        op0 = state.stack.pop()
+
+        logging.debug("MLOAD[" + str(op0) + "]")
+
+        try:
+            offset = util.get_concrete_int(op0)
+        except AttributeError:
+            logging.debug("Can't MLOAD from symbolic index")
+            data = BitVec("mem_" + str(op0), 256)
+            state.stack.append(data)
+            return [global_state]
+
+        try:
+            data = util.concrete_int_from_bytes(state.memory, offset)
+        except IndexError:  # Memory slot not allocated
+            data = BitVec("mem_" + str(offset), 256)
+        except TypeError:  # Symbolic memory
+            data = state.memory[offset]
+
+        logging.debug("Load from memory[" + str(offset) + "]: " + str(data))
+
+        state.stack.append(data)
+        return [global_state]
+
+    @instruction
+    def mstore_(self, global_state):
+        state = global_state.mstate
+
+        op0, value = state.stack.pop(), state.stack.pop()
+
+        try:
+            mstart = util.get_concrete_int(op0)
+        except AttributeError:
+            logging.debug("MSTORE to symbolic index. Not supported")
+            return [global_state]
+
+        try:
+            state.mem_extend(mstart, 32)
+        except Exception:
+            logging.debug("Error extending memory, mstart = " + str(mstart) + ", size = 32")
+
+        logging.debug("MSTORE to mem[" + str(mstart) + "]: " + str(value))
+
+        try:
+            # Attempt to concretize value
+            _bytes = util.concrete_int_to_bytes(value)
+
+            i = 0
+
+            for b in _bytes:
+                state.memory[mstart + i] = _bytes[i]
+                i += 1
+        except:
+            try:
+                state.memory[mstart] = value
+            except:
+                logging.debug("Invalid memory access")
+
+        return [global_state]
+
+    @instruction
+    def mstore8_(self, global_state):
+        state = global_state.mstate
+        op0, value = state.stack.pop(), state.stack.pop()
+
+        try:
+            offset = util.get_concrete_int(op0)
+        except AttributeError:
+            logging.debug("MSTORE to symbolic index. Not supported")
+            return [global_state]
+
+        state.mem_extend(offset, 1)
+
+        state.memory[offset] = value % 256
+        return [global_state]
+
+    @instruction
+    def sload_(self, global_state):
+        state = global_state.mstate
+        index = state.stack.pop()
+        logging.debug("Storage access at index " + str(index))
+
+        try:
+            index = util.get_concrete_int(index)
+        except AttributeError:
+            index = str(index)
+
+        try:
+            data = global_state.environment.active_account.storage[index]
+        except KeyError:
+            data = BitVec("storage_" + str(index), 256)
+            global_state.environment.active_account.storage[index] = data
+
+        state.stack.append(data)
+        return [global_state]
+
+    @instruction
+    def sstore_(self, global_state):
+        state = global_state.mstate
+        index, value = state.stack.pop(), state.stack.pop()
+
+        logging.debug("Write to storage[" + str(index) + "]")
+
+        try:
+            index = util.get_concrete_int(index)
+        except AttributeError:
+            index = str(index)
+
+        try:
+            # Create a fresh copy of the account object before modifying storage
+
+            for k in global_state.accounts:
+                if global_state.accounts[k] == global_state.environment.active_account:
+                    global_state.accounts[k] = deepcopy(global_state.accounts[k])
+                    global_state.environment.active_account = global_state.accounts[k]
+                    break
+
+            global_state.environment.active_account.storage[index] = value
+        except KeyError:
+            logging.debug("Error writing to storage: Invalid index")
+        return [global_state]
+
+    @instruction
+    def jump_(self, global_state):
+        state = global_state.mstate
+        disassembly = global_state.environment.code
+        try:
+            jump_addr = util.get_concrete_int(state.stack.pop())
+        except AttributeError:
+            logging.debug("Invalid jump argument (symbolic address)")
+            return []
+        except IndexError:  # Stack Underflow
+            return []
+
+        index = util.get_instruction_index(disassembly.instruction_list, jump_addr)
+        if index is None:
+            logging.debug("JUMP to invalid address")
+            return []
+
+        op_code = disassembly.instruction_list[index]['opcode']
+
+        if op_code != "JUMPDEST":
+            logging.debug("Skipping JUMP to invalid destination (not JUMPDEST): " + str(jump_addr))
+            return []
+
+        new_state = copy(global_state)
+        new_state.mstate.pc = index
+        new_state.mstate.depth += 1
+
+        return [new_state]
+
+    @instruction
+    def jumpi_(self, global_state):
+        state = global_state.mstate
+        disassembly = global_state.environment.code
+        states = []
+
+        op0, condition = state.stack.pop(), state.stack.pop()
+
+        try:
+            jump_addr = util.get_concrete_int(op0)
+        # FIXME: to broad exception handler
+        except:
+            logging.debug("Skipping JUMPI to invalid destination.")
+            return [global_state]
+
+        index = util.get_instruction_index(disassembly.instruction_list, jump_addr)
+
+        if not index:
+            logging.debug("Invalid jump destination: " + str(jump_addr))
+            return [global_state]
+        instr = disassembly.instruction_list[index]
+
+        # True case
+        condi = condition if type(condition) == BoolRef else condition != 0
+        if instr['opcode'] == "JUMPDEST":
+            if (type(condi) == bool and condi) or (type(condi) == BoolRef and not is_false(simplify(condi))):
+                new_state = copy(global_state)
+                new_state.mstate.pc = index
+                new_state.mstate.depth += 1
+                new_state.mstate.constraints.append(condi)
+
+                states.append(new_state)
+            else:
+                logging.debug("Pruned unreachable states.")
+
+        # False case
+        negated = Not(condition) if type(condition) == BoolRef else condition == 0
+        sat = not is_false(simplify(negated)) if type(condi) == BoolRef else not negated
+
+        if sat:
+            new_state = copy(global_state)
+            new_state.mstate.depth += 1
+            new_state.mstate.constraints.append(negated)
+            states.append(new_state)
+        else:
+            logging.debug("Pruned unreachable states.")
+
+        return states
+
+    @instruction
+    def pc_(self, global_state):
+        global_state.mstate.stack.append(global_state.mstate.pc - 1)
+        return [global_state]
+
+    @instruction
+    def msize_(self, global_state):
+        global_state.mstate.stack.append(BitVec("msize", 256))
+        return [global_state]
+
+    @instruction
+    def gas_(self, global_state):
+        global_state.mstate.stack.append(BitVec("gas", 256))
+        return [global_state]
+
+    @instruction
+    def log_(self, global_state):
+        # TODO: implement me
+        state = global_state.mstate
+        dpth = int(self.op_code[3:])
+        state.stack.pop(), state.stack.pop()
+        [state.stack.pop() for x in range(dpth)]
+        # Not supported
+        return [global_state]
+
+    @instruction
+    def create_(self, global_state):
+        # TODO: implement me
+        state = global_state.mstate
+        state.stack.pop(), state.stack.pop(), state.stack.pop()
+        # Not supported
+        state.stack.append(0)
+        return [global_state]
+
+    @instruction
+    def return_(self, global_state):
+        # TODO: memory
+        state = global_state.mstate
+        offset, length = state.stack.pop(), state.stack.pop()
+        try:
+            _ = state.memory[util.get_concrete_int(offset):util.get_concrete_int(offset + length)]
+        except AttributeError:
+            logging.debug("Return with symbolic length or offset. Not supported")
+
+        #TODO: return 1
+        return_value = BitVec("retval_" + global_state.environment.active_function_name, 256)
+
+        state.stack.append(return_value)
+
+        if not global_state.call_stack:
+            return []
+
+        global_state.mstate.pc = global_state.call_stack.pop()
+
+        return [global_state]
+
+    @instruction
+    def suicide_(self, global_state):
+        return []
+
+    @instruction
+    def revert_(self, global_state):
+        return []
+
+    @instruction
+    def assert_fail_(self, global_state):
+        return []
+
+    @instruction
+    def invalid_(self, global_state):
+        return []
+
+    @instruction
+    def stop_(self, global_state):
+        state = global_state.mstate
+        state.stack.append(BitVecVal(0, 256))
+        if len(global_state.call_stack) is 0:
+            return []
+        global_state.mstate.pc = global_state.call_stack.pop()
+        return [global_state]
+
+    @instruction
+    def call_(self, global_state):
+        instr = global_state.get_current_instruction()
+        environment = global_state.environment
+
+        try:
+            callee_address, callee_account, call_data, value, call_data_type, gas, memory_out_offset, memory_out_size = get_call_parameters(global_state, self.dynamic_loader, True)
+        except ValueError as e:
+            logging.info(
+                "Could not determine required parameters for call, putting fresh symbol on the stack. \n{}".format(e)
+            )
+            # TODO: decide what to do in this case
+            global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256))
+            return [global_state]
+
+        if 0 < int(callee_address, 16) < 5:
+            logging.info("Native contract called: " + callee_address)
+            if call_data == [] and call_data_type == CalldataType.SYMBOLIC:
+                logging.debug("CALL with symbolic data not supported")
+                global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256))
+                return [global_state]
+
+            data = natives.native_contracts(int(callee_address, 16), call_data)
+            try:
+                mem_out_start = helper.get_concrete_int(memory_out_offset)
+                mem_out_sz = memory_out_size.as_long()
+            except AttributeError:
+                logging.debug("CALL with symbolic start or offset not supported")
+                global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256))
+                return [global_state]
+
+            global_state.mstate.mem_extend(mem_out_start, mem_out_sz)
+            try:
+                for i in range(min(len(data), mem_out_sz)):  # If more data is used then it's chopped off
+                    global_state.mstate.memory[mem_out_start + i] = data[i]
+            except:
+                global_state.mstate.memory[mem_out_start] = BitVec(data, 256)
+
+            # TODO: maybe use BitVec here constrained to 1
+            global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256))
+            return [global_state]
+
+        global_state.call_stack.append(instr['address'])
+        callee_environment = Environment(callee_account,
+                                         BitVecVal(int(environment.active_account.address, 16), 256),
+                                         call_data,
+                                         environment.gasprice,
+                                         value,
+                                         environment.origin,
+                                         calldata_type=call_data_type)
+        new_global_state = GlobalState(global_state.accounts, callee_environment, MachineState(gas))
+        new_global_state.mstate.depth = global_state.mstate.depth + 1
+        new_global_state.mstate.constraints = copy(global_state.mstate.constraints)
+        return [global_state]
+
+    @instruction
+    def callcode_(self, global_state):
+        instr = global_state.get_current_instruction()
+        environment = global_state.environment
+
+        try:
+            callee_address, callee_account, call_data, value, call_data_type, gas, _, _ = get_call_parameters(global_state, self.dynamic_loader, True)
+        except ValueError as e:
+            logging.info(
+                "Could not determine required parameters for call, putting fresh symbol on the stack. \n{}".format(e)
+            )
+            global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256))
+            return [global_state]
+
+        global_state.call_stack.append(instr['address'])
+
+        environment = deepcopy(environment)
+
+        environment.callvalue = value
+        environment.caller = environment.address
+        environment.calldata = call_data
+
+        new_global_state = GlobalState(global_state.accounts, environment, MachineState(gas))
+        new_global_state.mstate.depth = global_state.mstate.depth + 1
+        new_global_state.mstate.constraints = copy(global_state.mstate.constraints)
+
+        return [new_global_state]
+
+    @instruction
+    def delegatecall_(self, global_state):
+        instr = global_state.get_current_instruction()
+        environment = global_state.environment
+
+        try:
+            callee_address, callee_account, call_data, _, call_data_type, gas, _, _ = get_call_parameters(global_state, self.dynamic_loader)
+        except ValueError as e:
+            logging.info(
+                "Could not determine required parameters for call, putting fresh symbol on the stack. \n{}".format(e)
+            )
+            global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256))
+            return [global_state]
+
+        global_state.call_stack.append(instr['address'])
+
+        environment = deepcopy(environment)
+        environment = deepcopy(environment)
+
+        environment.code = callee_account.code
+        environment.calldata = call_data
+
+        new_global_state = GlobalState(global_state.accounts, environment, MachineState(gas))
+        new_global_state.mstate.depth = global_state.mstate.depth + 1
+        new_global_state.mstate.constraints = copy(global_state.mstate.constraints)
+
+        return [new_global_state]
+
+
+    @instruction
+    def staticcall_(self, global_state):
+        # TODO: implement me
+        instr = global_state.get_current_instruction()
+        global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256))
+        return [global_state]
diff --git a/mythril/laser/ethereum/natives.py b/mythril/laser/ethereum/natives.py
index e32ab81a..d49713c8 100644
--- a/mythril/laser/ethereum/natives.py
+++ b/mythril/laser/ethereum/natives.py
@@ -6,7 +6,7 @@ import hashlib
 import coincurve
 
 from py_ecc.secp256k1 import N as secp256k1n
-from mythril.laser.ethereum.helper import ALL_BYTES, bytearray_to_int, concrete_int_to_bytes, sha3, zpad
+from mythril.laser.ethereum.util import ALL_BYTES, bytearray_to_int, concrete_int_to_bytes, sha3, zpad
 
 
 def int_to_32bytes(i):   #used because int can't fit as bytes function's input
diff --git a/mythril/laser/ethereum/state.py b/mythril/laser/ethereum/state.py
new file mode 100644
index 00000000..932eae65
--- /dev/null
+++ b/mythril/laser/ethereum/state.py
@@ -0,0 +1,151 @@
+from z3 import BitVec, BitVecVal
+from copy import copy, deepcopy
+from enum import Enum
+
+
+class CalldataType(Enum):
+    CONCRETE = 1
+    SYMBOLIC = 2
+
+class Account:
+    """
+    Account class representing ethereum accounts
+    """
+    def __init__(self, address, code=None, contract_name="unknown", balance=None):
+        """
+        Constructor for account
+        :param address: Address of the account
+        :param code: The contract code of the account
+        :param contract_name: The name associated with the account
+        :param balance: The balance for the account
+        """
+        self.nonce = 0
+        self.code = code
+        self.balance = balance if balance else BitVec("balance", 256)
+        self.storage = {}
+
+        # Metadata
+        self.address = address
+        self.contract_name = contract_name
+
+    def __str__(self):
+        return str(self.as_dict)
+
+    def get_storage(self, index):
+        return self.storage[index] if index in self.storage.keys() else BitVec("storage_" + str(index), 256)
+
+    @property
+    def as_dict(self):
+        return {'nonce': self.nonce, 'code': self.code, 'balance': self.balance, 'storage': self.storage}
+
+
+class Environment:
+    """
+    The environment class represents the current execution environment for the symbolic executor
+    """
+    def __init__(
+        self,
+        active_account,
+        sender,
+        calldata,
+        gasprice,
+        callvalue,
+        origin,
+        calldata_type=CalldataType.SYMBOLIC,
+    ):
+        # Metadata
+
+        self.active_account = active_account
+        self.active_function_name = ""
+
+        self.address = BitVecVal(int(active_account.address, 16), 256)
+        self.code = active_account.code
+
+        self.sender = sender
+        self.calldata = calldata
+        self.calldata_type = calldata_type
+        self.gasprice = gasprice
+        self.origin = origin
+        self.callvalue = callvalue
+
+    def __str__(self):
+        return str(self.as_dict)
+
+    @property
+    def as_dict(self):
+        return dict(active_account=self.active_account, sender=self.sender, calldata=self.calldata,
+                    gasprice=self.gasprice, callvalue=self.callvalue, origin=self.origin,
+                    calldata_type=self.calldata_type)
+
+
+class MachineState:
+    """
+    MachineState represents current machine state also referenced to as \mu
+    """
+    def __init__(self, gas):
+        """ Constructor for machineState """
+        self.pc = 0
+        self.stack = []
+        self.memory = []
+        self.memory_size = 0
+        self.gas = gas
+        self.constraints = []
+        self.depth = 0
+
+    def mem_extend(self, start, size):
+        """
+        Extends the memory of this machine state
+        :param start: Start of memory extension
+        :param size: Size of memory extension
+        """
+        if start < 4096 and size < 4096:
+
+            if size and start + size > len(self.memory):
+                n_append = start + size - len(self.memory)
+
+                while n_append > 0:
+                    self.memory.append(0)
+                    n_append -= 1
+
+                # FIXME: this does not seem right
+                self.memory_size = size
+
+        else:
+            # TODO: Specific exception
+            raise Exception
+            # TODO: Deduct gas for memory extension... not yet implemented
+
+    def __str__(self):
+        return str(self.as_dict)
+
+    @property
+    def as_dict(self):
+        return dict(pc=self.pc, stack=self.stack, memory=self.memory, memsize=self.memory_size, gas=self.gas)
+
+
+class GlobalState:
+    """
+    GlobalState represents the current globalstate
+    """
+    def __init__(self, accounts, environment, node, machine_state=None, call_stack=None):
+        """ Constructor for GlobalState"""
+        self.node = node
+        self.accounts = accounts
+        self.environment = environment
+        self.mstate = machine_state if machine_state else MachineState(gas=10000000)
+        self.call_stack = call_stack if call_stack else []
+        self.op_code = ""
+
+
+
+    def __copy__(self):
+        accounts = copy(self.accounts)
+        environment = copy(self.environment)
+        mstate = deepcopy(self.mstate)
+        return GlobalState(accounts, environment, self.node, mstate)
+
+    #TODO: remove this, as two instructions are confusing
+    def get_current_instruction(self):
+        """ Gets the current instruction for this GlobalState"""
+        instructions = self.environment.code.instruction_list
+        return instructions[self.mstate.pc]
diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py
index f8f75d24..0001f8e0 100644
--- a/mythril/laser/ethereum/svm.py
+++ b/mythril/laser/ethereum/svm.py
@@ -1,255 +1,44 @@
-from mythril.laser.ethereum import helper, natives
-from ethereum import utils
-from enum import Enum
-from flags import Flags
-from z3 import *
-import binascii
-import copy
+from z3 import BitVec
 import logging
-import re
-
+from mythril.laser.ethereum.state import GlobalState, Environment, CalldataType, Account
+from mythril.laser.ethereum.instructions import Instruction
+from mythril.laser.ethereum.cfg import NodeFlags, Node, Edge, JumpType
 
 TT256 = 2 ** 256
 TT256M1 = 2 ** 256 - 1
 
-gbl_next_uid = 0  # node counter
-
-
-class CalldataType(Enum):
-    CONCRETE = 1
-    SYMBOLIC = 2
-
-
-class JumpType(Enum):
-    CONDITIONAL = 1
-    UNCONDITIONAL = 2
-    CALL = 3
-    RETURN = 4
-
 
 class SVMError(Exception):
     pass
 
 
-'''
-Classes to represent the global state, machine state and execution environment as described in the Ethereum yellow paper.
-'''
-
-
-class Account():
-
-    def __init__(self, address, code=None, contract_name="unknown", balance=BitVec("balance", 256)):
-        self.nonce = 0
-        self.code = code
-        self.balance = balance
-        self.storage = {}
-
-        '''
-        Metadata
-        '''
-
-        self.address = address
-        self.contract_name = contract_name
-
-    def __str__(self):
-        return str(self.as_dict())
-
-    def get_storage(self, index):
-        try:
-            return self.storage[index]
-        except KeyError:
-            return BitVec("storage_" + str(index), 256)
-
-    def as_dict(self):
-        return {'nonce': self.nonce, 'code': self.code, 'balance': self.balance, 'storage': self.storage}
-
-
-class Environment():
-
-    def __init__(
-        self,
-        active_account,
-        sender,
-        calldata,
-        gasprice,
-        callvalue,
-        origin,
-        calldata_type=CalldataType.SYMBOLIC,
-    ):
-
-        # Metadata
-
-        self.active_account = active_account
-        self.active_function_name = ""
-
-        self.address = BitVecVal(int(active_account.address, 16), 256)
-        self.code = active_account.code
-
-        self.sender = sender
-        self.calldata = calldata
-        self.calldata_type = calldata_type
-        self.gasprice = gasprice
-        self.origin = origin
-        self.callvalue = callvalue
-
-    def __str__(self):
-        return str(self.as_dict())
-
-    def as_dict(self):
-
-        return {'active_account': self.active_account, 'sender': self.sender, 'calldata': self.calldata, 'gasprice': self.gasprice, 'callvalue': self.callvalue, 'origin': self.origin, 'calldata_type': self.calldata_type}
-
-
-class MachineState():
-
-    def __init__(self, gas):
-        self.pc = 0
-        self.stack = []
-        self.memory = []
-        self.memsize = 0
-        self.gas = gas
-        self.constraints = []
-        self.depth = 0
-
-    def mem_extend(self, start, sz):
-
-        if (start < 4096 and sz < 4096):
-
-            if sz and start + sz > len(self.memory):
-
-                n_append = start + sz - len(self.memory)
-
-                while n_append > 0:
-                    self.memory.append(0)
-                    n_append -= 1
-
-                self.memsize = sz
-
-        else:
-            raise Exception
-
-            # Deduct gas for memory extension... not yet implemented
-
-    def __str__(self):
-        return str(self.as_dict())
-
-    def as_dict(self):
-
-        return {'pc': self.pc, 'stack': self.stack, 'memory': self.memory, 'memsize': self.memsize, 'gas': self.gas}
-
-
-class GlobalState():
-
-    def __init__(self, accounts, environment, machinestate=MachineState(gas=10000000)):
-        self.accounts = accounts
-        self.environment = environment
-        self.mstate = machinestate
-
-    # Returns the instruction currently being executed.
-
-    def get_current_instruction(self):
-        instructions = self.environment.code.instruction_list
-
-        return instructions[self.mstate.pc]
-
-
-'''
-The final analysis result is represented as a graph. Each node of the graph represents a basic block of code.
-The states[] list contains the individual global state at each program counter position. There is one set of constraints on each node.
-A list of edges between nodes with associated constraints is also saved. This is not strictly necessary for analysis, but is useful
-for drawing a nice control flow graph.
-'''
-
-
-class NodeFlags(Flags):
-    FUNC_ENTRY = 1
-    CALL_RETURN = 2
-
-
-class Node:
-
-    def __init__(self, contract_name, start_addr=0, constraints=[]):
-        self.contract_name = contract_name
-        self.start_addr = start_addr
-        self.states = []
-        self.constraints = constraints
-        self.function_name = "unknown"
-        self.flags = NodeFlags()
-
-        # Self-assign a unique ID
-
-        global gbl_next_uid
-
-        self.uid = gbl_next_uid
-        gbl_next_uid += 1
-
-    def get_cfg_dict(self):
-
-        code = ""
-
-        for state in self.states:
-
-            instruction = state.get_current_instruction()
-
-            code += str(instruction['address']) + " " + instruction['opcode']
-            if instruction['opcode'].startswith("PUSH"):
-                code += " " + instruction['argument']
-
-            code += "\\n"
-
-        return {'contract_name': self.contract_name, 'start_addr': self.start_addr, 'function_name': self.function_name, 'code': code}
-
-
-class Edge:
-
-    def __init__(self, node_from, node_to, edge_type=JumpType.UNCONDITIONAL, condition=None):
-
-        self.node_from = node_from
-        self.node_to = node_to
-        self.type = edge_type
-        self.condition = condition
-
-    def __str__(self):
-        return str(self.as_dict())
-
-    def as_dict(self):
-
-        return {'from': self.node_from, 'to': self.node_to}
-
 '''
 Main symbolic execution engine.
 '''
 
 
 class LaserEVM:
-
-    def __init__(self, accounts, dynamic_loader=None, max_depth=12):
+    """
+    Laser EVM class
+    """
+    def __init__(self, accounts, dynamic_loader=None, max_depth=22):
         self.accounts = accounts
+
         self.nodes = {}
         self.edges = []
-        self.current_func = ""
-        self.call_stack = []
-        self.pending_returns = {}
+
         self.total_states = 0
         self.dynamic_loader = dynamic_loader
+
+        self.work_list = []
         self.max_depth = max_depth
 
         logging.info("LASER EVM initialized with dynamic loader: " + str(dynamic_loader))
 
-    @staticmethod
-    def copy_global_state(gblState):
-        mstate = copy.deepcopy(gblState.mstate)
-        environment = copy.copy(gblState.environment)
-        accounts = copy.copy(gblState.accounts)
-
-        return GlobalState(accounts, environment, mstate)
-
     def sym_exec(self, main_address):
-
         logging.debug("Starting LASER execution")
 
         # Initialize the execution environment
-
         environment = Environment(
             self.accounts[main_address],
             BitVec("caller", 256),
@@ -260,962 +49,88 @@ class LaserEVM:
             calldata_type=CalldataType.SYMBOLIC,
         )
 
-        gblState = GlobalState(self.accounts, environment)
-
-        node = self._sym_exec(gblState)
-        self.nodes[node.uid] = node
-        logging.info("Execution complete")
-        logging.info("%d nodes, %d edges, %d total states", len(self.nodes), len(self.edges), self.total_states)
-
-    def _sym_exec(self, gblState):
-
-        environment = gblState.environment
-        disassembly = environment.code
-        state = gblState.mstate
-
-        start_addr = disassembly.instruction_list[state.pc]['address']
-
-        node = Node(environment.active_account.contract_name, start_addr, copy.deepcopy(state.constraints))
-
-        if start_addr == 0:
-            environment.active_function_name = "fallback"
-
-        logging.debug("- Entering node " + str(node.uid) + ", index = " + str(state.pc) + ", address = " + str(start_addr) + ", depth = " + str(state.depth))
-
-        if start_addr in disassembly.addr_to_func:
-            # Enter a new function
+        # TODO: contact name fix
+        initial_node = Node(environment.active_account.contract_name)
+        self.nodes[initial_node.uid] = initial_node
 
-            environment.active_function_name = disassembly.addr_to_func[start_addr]
-            node.flags |= NodeFlags.FUNC_ENTRY
+        global_state = GlobalState(self.accounts, environment, initial_node)
+        initial_node.states.append(global_state)
 
-            logging.info("- Entering function " + environment.active_account.contract_name + ":" + node.function_name)
+        # Empty the work_list before starting an execution
+        self.work_list = [global_state]
+        self._sym_exec()
 
-        node.function_name = environment.active_function_name
-
-        halt = False
-
-        while not halt:
+        logging.info("Execution complete")
+        logging.info("%d nodes, %d edges, %d total states", len(self.nodes), len(self.edges), self.total_states)
 
+    def _sym_exec(self):
+        while True:
             try:
-                instr = disassembly.instruction_list[state.pc]
+                global_state = self.work_list.pop(0)
+                if global_state.mstate.depth >= self.max_depth: continue
             except IndexError:
-                logging.debug("Invalid PC")
-                return node
-
-            # Save state before modifying anything
-
-            node.states.append(gblState)
-            gblState = LaserEVM.copy_global_state(gblState)
-
-            state = gblState.mstate
-
-            self.total_states += 1
-
-            # Point program counter to next instruction
-
-            state.pc += 1
-            op = instr['opcode']
-
-            # logging.debug("[" + environment.active_account.contract_name + "] " + helper.get_trace_line(instr, state))
-            # slows down execution significantly.
-
-            # Stack ops
-
-            if op.startswith("PUSH"):
-                value = BitVecVal(int(instr['argument'][2:], 16), 256)
-                state.stack.append(value)
-
-            elif op.startswith('DUP'):
-                dpth = int(op[3:])
-
-                try:
-                    state.stack.append(state.stack[-dpth])
-                except:
-                    halt = True
-
-            elif op.startswith('SWAP'):
-
-                dpth = int(op[4:])
-
-                try:
-                    temp = state.stack[-dpth - 1]
-
-                    state.stack[-dpth - 1] = state.stack[-1]
-                    state.stack[-1] = temp
-                except IndexError:  # Stack underflow
-                    halt = True
-
-            elif op == 'POP':
-                try:
-                    state.stack.pop()
-                except IndexError:  # Stack underflow
-                    halt = True
-
-            # Bitwise ops
-
-            elif op == 'AND':
-                try:
-                    op1, op2 = state.stack.pop(), state.stack.pop()
-                    if (type(op1) == BoolRef):
-                        op1 = If(op1, BitVecVal(1, 256), BitVecVal(0, 256))
-
-                    if (type(op2) == BoolRef):
-                        op2 = If(op2, BitVecVal(1, 256), BitVecVal(0, 256))
-
-                    state.stack.append(op1 & op2)
-                except IndexError:  # Stack underflow
-                    halt = True
-
-            elif op == 'OR':
-                try:
-                    op1, op2 = state.stack.pop(), state.stack.pop()
-
-                    if (type(op1) == BoolRef):
-                        op1 = If(op1, BitVecVal(1, 256), BitVecVal(0, 256))
-
-                    if (type(op2) == BoolRef):
-                        op2 = If(op2, BitVecVal(1, 256), BitVecVal(0, 256))
-
-                    state.stack.append(op1 | op2)
-                except IndexError:  # Stack underflow
-                    halt = True
-
-            elif op == 'XOR':
-                state.stack.append(state.stack.pop() ^ state.stack.pop())
-
-            elif op == 'NOT':
-                state.stack.append(TT256M1 - state.stack.pop())
-
-            elif op == 'BYTE':
-                s0, s1 = state.stack.pop(), state.stack.pop()
-                if not isinstance(s1, ExprRef):
-                    s1 = BitVecVal(s1, 256)
-                try:
-                    n = helper.get_concrete_int(s0)
-                    oft = (31 - n) * 8
-                    result = Concat(BitVecVal(0, 248), Extract(oft + 7, oft, s1))
-                except AttributeError:
-                    logging.debug("BYTE: Unsupported symbolic byte offset")
-                    result = BitVec(str(simplify(s1)) + "_" + str(simplify(s0)), 256)
-
-                state.stack.append(simplify(result))
-
-            # Arithmetics
-
-            elif op == 'ADD':
-                state.stack.append((helper.pop_bitvec(state) + helper.pop_bitvec(state)))
-
-            elif op == 'SUB':
-                state.stack.append((helper.pop_bitvec(state) - helper.pop_bitvec(state)))
-
-            elif op == 'MUL':
-                state.stack.append(helper.pop_bitvec(state) * helper.pop_bitvec(state))
-
-            elif op == 'DIV':
-                state.stack.append(UDiv(helper.pop_bitvec(state), helper.pop_bitvec(state)))
-
-            elif op == 'MOD':
-                s0, s1 = helper.pop_bitvec(state), helper.pop_bitvec(state)
-                state.stack.append(0 if s1 == 0 else URem(s0, s1))
-
-            elif op == 'SDIV':
-                s0, s1 = helper.pop_bitvec(state), helper.pop_bitvec(state)
-                state.stack.append(s0 / s1)
-
-            elif op == 'SMOD':
-                s0, s1 = helper.pop_bitvec(state), helper.pop_bitvec(state)
-                state.stack.append(0 if s1 == 0 else s0 % s1)
-
-            elif op == 'ADDMOD':
-                s0, s1, s2 = helper.pop_bitvec(state), helper.pop_bitvec(state), helper.pop_bitvec(state)
-
-                logging.info(str(type))
-
-                state.stack.append((s0 + s1) % s2)
-
-            elif op == 'MULMOD':
-                s0, s1, s2 = helper.pop_bitvec(state), helper.pop_bitvec(state), helper.pop_bitvec(state)
-                state.stack.append((s0 * s1) % s2 if s2 else 0)
-
-            elif op == 'EXP':
-                # we only implement 2 ** x
-                base, exponent = helper.pop_bitvec(state), helper.pop_bitvec(state)
-
-                if (type(base) != BitVecNumRef) or (type(exponent) != BitVecNumRef):
-                    state.stack.append(BitVec(str(base) + "_EXP_" + str(exponent), 256))
-                elif (base.as_long() == 2):
-                    if exponent.as_long() == 0:
-                        state.stack.append(BitVecVal(1, 256))
-                    else:
-                        state.stack.append(base << (exponent - 1))
-
-                else:
-                    state.stack.append(base)
-
-            elif op == 'SIGNEXTEND':
-                s0, s1 = state.stack.pop(), state.stack.pop()
-
-                try:
-                    s0 = helper.get_concrete_int(s0)
-                    s1 = helper.get_concrete_int(s1)
-
-                    if s0 <= 31:
-                        testbit = s0 * 8 + 7
-                        if s1 & (1 << testbit):
-                            state.stack.append(s1 | (TT256 - (1 << testbit)))
-                        else:
-                            state.stack.append(s1 & ((1 << testbit) - 1))
-                    else:
-                        state.stack.append(s1)
-                except:
-                    halt = True
-                    continue
-
-            # Comparisons
-
-            elif op == 'LT':
-
-                exp = ULT(helper.pop_bitvec(state), helper.pop_bitvec(state))
-                state.stack.append(exp)
-
-            elif op == 'GT':
-
-                exp = UGT(helper.pop_bitvec(state), helper.pop_bitvec(state))
-                state.stack.append(exp)
-
-            elif op == 'SLT':
-
-                exp = helper.pop_bitvec(state) < helper.pop_bitvec(state)
-                state.stack.append(exp)
-
-            elif op == 'SGT':
-
-                exp = helper.pop_bitvec(state) > helper.pop_bitvec(state)
-                state.stack.append(exp)
-
-            elif op == 'EQ':
-
-                op1 = state.stack.pop()
-                op2 = state.stack.pop()
-
-                if(type(op1) == BoolRef):
-                    op1 = If(op1, BitVecVal(1, 256), BitVecVal(0, 256))
-
-                if(type(op2) == BoolRef):
-                    op2 = If(op2, BitVecVal(1, 256), BitVecVal(0, 256))
-
-                exp = op1 == op2
-
-                state.stack.append(exp)
-
-            elif op == 'ISZERO':
-
-                val = state.stack.pop()
-
-                if (type(val) == BoolRef):
-                    exp = val == False
-                else:
-                    exp = val == 0
-
-                state.stack.append(exp)
-
-            # Call data
-
-            elif op == 'CALLVALUE':
-                state.stack.append(environment.callvalue)
-
-            elif op == 'CALLDATALOAD':
-                # unpack 32 bytes from calldata into a word and put it on the stack
-
-                op0 = state.stack.pop()
-
-                try:
-                    offset = helper.get_concrete_int(simplify(op0))
-                    b = environment.calldata[offset]
-
-                except AttributeError:
-                    logging.debug("CALLDATALOAD: Unsupported symbolic index")
-                    state.stack.append(BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(op0), 256))
-                    continue
-                except IndexError:
-                    logging.debug("Calldata not set, using symbolic variable instead")
-                    state.stack.append(BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(op0), 256))
-                    continue
-
-                if type(b) == int:
-
-                    val = b''
-
-                    try:
-                        for i in range(offset, offset + 32):
-                            val += environment.calldata[i].to_bytes(1, byteorder='big')
-
-                        logging.debug("Final value: " + str(int.from_bytes(val, byteorder='big')))
-                        state.stack.append(BitVecVal(int.from_bytes(val, byteorder='big'), 256))
-
-                    except:
-                        state.stack.append(BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(op0), 256))
-                else:
-                    # symbolic variable
-                    state.stack.append(BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(op0), 256))
-
-            elif op == 'CALLDATASIZE':
-
-                if environment.calldata_type == CalldataType.SYMBOLIC:
-                    state.stack.append(BitVec("calldatasize_" + environment.active_account.contract_name, 256))
-                else:
-                    state.stack.append(BitVecVal(len(environment.calldata), 256))
+                return
 
-            elif op == 'CALLDATACOPY':
-                op0, op1, op2 = state.stack.pop(), state.stack.pop(), state.stack.pop()
-
-                try:
-                    mstart = helper.get_concrete_int(op0)
-                except:
-                    logging.debug("Unsupported symbolic memory offset in CALLDATACOPY")
-                    continue
-
-                try:
-                    dstart = helper.get_concrete_int(op1)
-                except:
-                    logging.debug("Unsupported symbolic calldata offset in CALLDATACOPY")
-                    state.mem_extend(mstart, 1)
-                    state.memory[mstart] = BitVec("calldata_" + str(environment.active_account.contract_name) + "_cpy", 256)
-                    continue
-
-                try:
-                    size = helper.get_concrete_int(op2)
-                except:
-                    logging.debug("Unsupported symbolic size in CALLDATACOPY")
-                    state.mem_extend(mstart, 1)
-                    state.memory[mstart] = BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(dstart), 256)
-                    continue
-
-                if size > 0:
-
-                    try:
-                        state.mem_extend(mstart, size)
-                    except:
-                        logging.debug("Memory allocation error: mstart = " + str(mstart) + ", size = " + str(size))
-                        state.mem_extend(mstart, 1)
-                        state.memory[mstart] = BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(dstart), 256)
-                        continue
-
-                    try:
-                        i_data = environment.calldata[dstart]
-
-                        for i in range(mstart, mstart + size):
-                            state.memory[i] = environment.calldata[i_data]
-                            i_data += 1
-                    except:
-                        logging.debug("Exception copying calldata to memory")
-
-                        state.memory[mstart] = BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(dstart), 256)
-
-            elif op == 'STOP':
-                if len(self.call_stack):
-                    self.pending_returns[self.call_stack[-1]].append(node.uid)
-
-                halt = True
+            try:
+                new_states, op_code = self.execute_state(global_state)
+            except NotImplementedError:
+                logging.debug("Encountered unimplemented instruction")
                 continue
 
-            # Environment
-
-            elif op == 'ADDRESS':
-                state.stack.append(environment.address)
-
-            elif op == 'BALANCE':
-                addr = state.stack.pop()
-                state.stack.append(BitVec("balance_at_" + str(addr), 256))
-
-            elif op == 'ORIGIN':
-                state.stack.append(environment.origin)
-
-            elif op == 'CALLER':
-                state.stack.append(environment.sender)
-
-            elif op == 'CODESIZE':
-                state.stack.append(len(disassembly.bytecode) // 2)
-
-            if op == 'SHA3':
-                op0, op1 = state.stack.pop(), state.stack.pop()
-
-                try:
-                    index, length = helper.get_concrete_int(op0), helper.get_concrete_int(op1)
-
-                except:
-                    # Can't access symbolic memory offsets
-                    state.stack.append(BitVec("KECCAC_mem_" + str(op0) + ")", 256))
-                    continue
-
-                try:
-                    data = b''
-
-                    for i in range(index, index + length):
-                        data += helper.get_concrete_int(state.memory[i]).to_bytes(1, byteorder='big')
-                        i += 1
-                except:
-
-                    svar = str(state.memory[index])
-
-                    svar = svar.replace(" ", "_")
-
-                    state.stack.append(BitVec("keccac_" + svar, 256))
-                    continue
-
-                keccac = utils.sha3(utils.bytearray_to_bytestr(data))
-                logging.debug("Computed SHA3 Hash: " + str(binascii.hexlify(keccac)))
-
-                state.stack.append(BitVecVal(helper.concrete_int_from_bytes(keccac, 0), 256))
-
-            elif op == 'GASPRICE':
-                state.stack.append(BitVec("gasprice", 256))
-
-            elif op == 'CODECOPY':
-                # Not implemented
-                start, s1, size = state.stack.pop(), state.stack.pop(), state.stack.pop()
-
-            elif op == 'EXTCODESIZE':
-                addr = state.stack.pop()
-
-                try:
-                    addr = hex(helper.get_concrete_int(addr))
-                except AttributeError:
-                    logging.info("unsupported symbolic address for EXTCODESIZE")
-                    state.stack.append(BitVec("extcodesize_"+str(addr), 256))
-                    continue
-
-                try:
-                    code = self.dynamic_loader.dynld(environment.active_account.address, addr)
-                except Exception as e:
-                    logging.info("error accessing contract storage due to: "+str(e))
-                    state.stack.append(BitVec("extcodesize_"+str(addr), 256))
-                    continue
-
-                state.stack.append(len(code.bytecode) // 2)
-
-            elif op == 'EXTCODECOPY':
-                # Not implemented
-
-                addr = state.stack.pop()
-                start, s2, size = state.stack.pop(), state.stack.pop(), state.stack.pop()
-
-            elif op == 'RETURNDATASIZE':
-                state.stack.append(BitVec("returndatasize", 256))
-
-            elif op == 'BLOCKHASH':
-                blocknumber = state.stack.pop()
-                state.stack.append(BitVec("blockhash_block_" + str(blocknumber), 256))
-
-            elif op == 'COINBASE':
-                state.stack.append(BitVec("coinbase", 256))
-
-            elif op == 'TIMESTAMP':
-                state.stack.append(BitVec("timestamp", 256))
-
-            elif op == 'NUMBER':
-                state.stack.append(BitVec("block_number", 256))
-
-            elif op == 'DIFFICULTY':
-                state.stack.append(BitVec("block_difficulty", 256))
-
-            elif op == 'GASLIMIT':
-                state.stack.append(BitVec("block_gaslimit", 256))
-
-            elif op == 'MLOAD':
-
-                op0 = state.stack.pop()
-
-                logging.debug("MLOAD[" + str(op0) + "]")
-
-                try:
-                    offset = helper.get_concrete_int(op0)
-                except AttributeError:
-                    logging.debug("Can't MLOAD from symbolic index")
-                    data = BitVec("mem_" + str(op0), 256)
-                    state.stack.append(data)
-                    continue
-
-                try:
-                    data = helper.concrete_int_from_bytes(state.memory, offset)
-                except IndexError:  # Memory slot not allocated
-                    data = BitVec("mem_" + str(offset), 256)
-                except TypeError:  # Symbolic memory
-                    data = state.memory[offset]
-
-                logging.debug("Load from memory[" + str(offset) + "]: " + str(data))
-
-                state.stack.append(data)
-
-            elif op == 'MSTORE':
-
-                op0, value = state.stack.pop(), state.stack.pop()
-
-                try:
-                    mstart = helper.get_concrete_int(op0)
-                except AttributeError:
-                    logging.debug("MSTORE to symbolic index. Not supported")
-                    continue
-
-                try:
-                    state.mem_extend(mstart, 32)
-                except Exception:
-                    logging.debug("Error extending memory, mstart = " + str(mstart) + ", size = 32")
-
-                logging.debug("MSTORE to mem[" + str(mstart) + "]: " + str(value))
-
-                try:
-                    # Attempt to concretize value
-                    _bytes = helper.concrete_int_to_bytes(value)
-
-                    i = 0
-
-                    for b in _bytes:
-                        state.memory[mstart + i] = _bytes[i]
-                        i += 1
-
-                except:
-                    try:
-                        state.memory[mstart] = value
-                    except:
-                        logging.debug("Invalid memory access")
-                        continue
-
-            elif op == 'MSTORE8':
-                op0, value = state.stack.pop(), state.stack.pop()
-
-                try:
-                    offset = helper.get_concrete_int(op0)
-                except AttributeError:
-                    logging.debug("MSTORE to symbolic index. Not supported")
-                    continue
-
-                state.mem_extend(offset, 1)
-
-                state.memory[offset] = value % 256
-
-            elif op == 'SLOAD':
-                index = state.stack.pop()
-                logging.debug("Storage access at index " + str(index))
-
-                try:
-                    index = helper.get_concrete_int(index)
-                except AttributeError:
-                    index = str(index)
-
-                try:
-                    data = gblState.environment.active_account.storage[index]
-                except KeyError:
-                    data = BitVec("storage_" + str(index), 256)
-                    gblState.environment.active_account.storage[index] = data
-
-                state.stack.append(data)
-
-            elif op == 'SSTORE':
-                index, value = state.stack.pop(), state.stack.pop()
-
-                logging.debug("Write to storage[" + str(index) + "] at node " + str(start_addr))
-
-                try:
-                    index = helper.get_concrete_int(index)
-                except AttributeError:
-                    index = str(index)
-
-                try:
-                    # Create a fresh copy of the account object before modifying storage
-
-                    for k in gblState.accounts:
-                        if gblState.accounts[k] == gblState.environment.active_account:
-                            gblState.accounts[k] = copy.deepcopy(gblState.accounts[k])
-                            gblState.environment.active_account = gblState.accounts[k]
-                            break
-
-                    gblState.environment.active_account.storage[index] = value
-                except KeyError:
-                    logging.debug("Error writing to storage: Invalid index")
-                    continue
-
-            elif op == 'JUMP':
-
-                try:
-                    jump_addr = helper.get_concrete_int(state.stack.pop())
-                except AttributeError:
-                    logging.debug("Invalid jump argument (symbolic address)")
-                    halt = True
-                    continue
-                except IndexError:  # Stack Underflow
-                    halt = True
-                    continue
-
-                if (state.depth < self.max_depth):
-
-                    i = helper.get_instruction_index(disassembly.instruction_list, jump_addr)
-
-                    if i is None:
-                        logging.debug("JUMP to invalid address")
-                        halt = True
-                        continue
-
-                    opcode = disassembly.instruction_list[i]['opcode']
-
-                    if opcode == "JUMPDEST":
-
-                        new_gblState = LaserEVM.copy_global_state(gblState)
-                        new_gblState.mstate.pc = i
-                        new_gblState.mstate.depth += 1
-
-                        new_node = self._sym_exec(new_gblState)
-                        self.nodes[new_node.uid] = new_node
-
-                        self.edges.append(Edge(node.uid, new_node.uid, JumpType.UNCONDITIONAL))
-                        halt = True
-                        continue
-
-                    else:
-                        logging.debug("Skipping JUMP to invalid destination (not JUMPDEST): " + str(jump_addr))
-                        halt = True
-                        # continue
-                else:
-                    logging.debug("Max depth reached, skipping JUMP")
-                    halt = True
-                    # continue
-
-            elif op == 'JUMPI':
-                op0, condition = state.stack.pop(), state.stack.pop()
-
-                try:
-                    jump_addr = helper.get_concrete_int(op0)
-                except:
-                    logging.debug("Skipping JUMPI to invalid destination.")
-
-                if state.depth >= self.max_depth:
-                    logging.debug("Max depth reached, skipping JUMPI")
-                    halt = True
-                    continue
-
-                i = helper.get_instruction_index(disassembly.instruction_list, jump_addr)
-
-                if not i:
-                    logging.debug("Invalid jump destination: " + str(jump_addr))
-                    continue
-                instr = disassembly.instruction_list[i]
-
-                # True case
-                condi = condition if type(condition) == BoolRef else condition != 0
-                if instr['opcode'] == "JUMPDEST":
-                    if (type(condi) == bool and condi) or (type(condi) == BoolRef and not is_false(simplify(condi))):
-                        new_gblState = LaserEVM.copy_global_state(gblState)
-                        new_gblState.mstate.pc = i
-                        new_gblState.mstate.constraints.append(condi)
-                        new_gblState.mstate.depth += 1
-
-                        new_node = self._sym_exec(new_gblState)
-                        self.nodes[new_node.uid] = new_node
-                        self.edges.append(Edge(node.uid, new_node.uid, JumpType.CONDITIONAL, condi))
-                    else:
-                        logging.debug("Pruned unreachable states.")
-
-                # False case
-                negated = Not(condition) if type(condition) == BoolRef else condition == 0
-
-                if (type(negated)==bool and negated) or (type(condi) == BoolRef and not is_false(simplify(negated))):
-                    new_gblState = LaserEVM.copy_global_state(gblState)
-                    new_gblState.mstate.constraints.append(negated)
-                    new_gblState.mstate.depth += 1
-
-                    new_node = self._sym_exec(new_gblState)
-                    self.nodes[new_node.uid] = new_node
-                    self.edges.append(Edge(node.uid, new_node.uid, JumpType.CONDITIONAL, negated))
-                else:
-                    logging.debug("Pruned unreachable states.")
-
-                halt = True
-
-            elif op == 'PC':
-                state.stack.append(state.pc - 1)
-
-            elif op == 'MSIZE':
-                state.stack.append(BitVec("msize", 256))
-
-            elif op == 'GAS':
-                state.stack.append(BitVec("gas", 256))
-
-            elif op.startswith('LOG'):
-                dpth = int(op[3:])
-                state.stack.pop(), state.stack.pop()
-                [state.stack.pop() for x in range(dpth)]
-                # Not supported
-
-            elif op == 'CREATE':
-                state.stack.pop(), state.stack.pop(), state.stack.pop()
-                # Not supported
-                state.stack.append(0)
-
-            elif op in ('CALL', 'CALLCODE', 'DELEGATECALL', 'STATICCALL'):
-
-                if op in ('CALL', 'CALLCODE'):
-                    gas, to, value, meminstart, meminsz, memoutstart, memoutsz = \
-                        state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop()
-
-                else:
-                    gas, to, meminstart, meminsz, memoutstart, memoutsz = \
-                        state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop()
-
-                try:
-                    callee_address = hex(helper.get_concrete_int(to))
-
-                except AttributeError:
-                    # Not a concrete call address. Call target may be an address in storage.
-
-                    m = re.search(r'storage_(\d+)', str(simplify(to)))
-
-                    logging.debug("CALL to: " + str(simplify(to)))
-
-                    if (m and self.dynamic_loader is not None):
-                        idx = int(m.group(1))
-                        logging.info("Dynamic contract address at storage index " + str(idx))
-
-                        # attempt to read the contract address from instance storage
-
-                        try:
-                            callee_address = self.dynamic_loader.read_storage(environment.active_account.address, idx)
-                        except:
-                            logging.debug("Error accessing contract storage.")
-                            ret = BitVec("retval_" + str(instr['address']), 256)
-                            state.stack.append(ret)
-                            continue
-
-                        # testrpc simply returns the address, geth response is more elaborate.
-
-                        if not re.match(r"^0x[0-9a-f]{40}$", callee_address):
-
-                            callee_address = "0x" + callee_address[26:]
-
-                    else:
-                        ret = BitVec("retval_" + str(instr['address']), 256)
-                        state.stack.append(ret)
-                        # Set output memory
-                        logging.debug("memoutstart: "+ str(memoutstart))
-                        if not isinstance(memoutstart, ExprRef):
-                            state.mem_extend(memoutstart, 1)
-                            state.memory[memoutstart] = ret
-                        else:
-                            logging.debug("Unsupported memory symbolic index")
-                        continue
-
-                if (not re.match(r"^0x[0-9a-f]{40}", callee_address) and re.match(r"^0x[0-9a-f]{5,}",callee_address)):
-                        logging.debug("Invalid address: " + str(callee_address))
-                        ret = BitVec("retval_" + str(instr['address']), 256)
-                        state.stack.append(ret)
-                        continue
-
-                if 0 < int(callee_address, 16) < 5:
-
-                    logging.info("Native contract called: " + callee_address)
-                    calldata, calldata_type = self._get_calldata(meminstart, meminsz, state, pad=False)
-                    if calldata == [] and calldata_type == CalldataType.SYMBOLIC:
-                        logging.debug("CALL with symbolic data not supported")
-                        ret = BitVec("retval_" + str(instr['address']), 256)
-                        state.stack.append(ret)
-                        continue
-
-                    data = natives.native_contracts(int(callee_address, 16 ), calldata)
-                    try:
-                        mem_out_start = helper.get_concrete_int(memoutstart)
-                        mem_out_sz = memoutsz.as_long()
-                    except AttributeError:
-                        logging.debug("CALL with symbolic start or offset not supported")
-                        ret = BitVec("retval_" + str(instr['address']), 256)
-                        state.stack.append(ret)
-                        continue
-
-                    state.mem_extend(mem_out_start, mem_out_sz)
-                    try:
-                        for i in range(min(len(data), mem_out_sz)):     # If more data is used then it's chopped off
-                            state.memory[mem_out_start+i] = data[i]
-                    except:
-                       state.memory[mem_out_start] = BitVec(data, 256)
-                    state.stack.append(1)
-
-                    continue
-
-                try:
-
-                    callee_account = self.accounts[callee_address]
-
-                except KeyError:
-                    # We have a valid call address, but contract is not in the modules list
-
-                    logging.info("Module with address " + callee_address + " not loaded.")
-
-                    if self.dynamic_loader is not None:
-
-                        logging.info("Attempting to load dependency")
-
-                        try:
-                            code = self.dynamic_loader.dynld(environment.active_account.address, callee_address)
-                        except Exception as e:
-                            logging.info("Unable to execute dynamic loader.")
-
-                        if code is None:
-
-                            logging.info("No code returned, not a contract account?")
-                            ret = BitVec("retval_" + str(instr['address']), 256)
-                            state.stack.append(ret)
-                            continue
-
-                        # New contract bytecode loaded successfully, create a new contract account
-
-                        self.accounts[callee_address] = Account(callee_address, code, callee_address)
-
-                        logging.info("Dependency loaded: " + callee_address)
-
-                    else:
-                        logging.info("Dynamic loader unavailable. Skipping call")
-                        ret = BitVec("retval_" + str(instr['address']), 256)
-                        state.stack.append(ret)
-                        continue
-
-                logging.info("Executing " + op + " to: " + callee_address)
-
-                try:
-                    callee_account = self.accounts[callee_address]
-                except KeyError:
-                    logging.info("Contract " + str(callee_address) + " not loaded.")
-                    logging.info((str(self.accounts)))
-
-                    ret = BitVec("retval_" + str(instr['address']), 256)
-                    state.stack.append(ret)
-                    continue
-
-
-                calldata, calldata_type = self._get_calldata(meminstart, meminsz, state)
-                self.call_stack.append(instr['address'])
-                self.pending_returns[instr['address']] = []
-
-                if (op == 'CALL'):
-
-                    callee_environment = Environment(callee_account, BitVecVal(int(environment.active_account.address, 16), 256), calldata, environment.gasprice, value, environment.origin, calldata_type=calldata_type)
-                    new_gblState = GlobalState(gblState.accounts, callee_environment, MachineState(gas))
-                    new_gblState.mstate.depth = state.depth + 1
-                    new_gblState.mstate.constraints = copy.deepcopy(state.constraints)
-
-                    new_node = self._sym_exec(new_gblState)
-
-                    self.nodes[new_node.uid] = new_node
-
-                elif (op == 'CALLCODE'):
-
-                    temp_callvalue = environment.callvalue
-                    temp_caller = environment.caller
-                    temp_calldata = environment.calldata
-
-                    environment.callvalue = value
-                    environment.caller = environment.address
-                    environment.calldata = calldata
-
-                    new_gblState = GlobalState(gblState.accounts, environment, MachineState(gas))
-                    new_gblState.mstate.depth = state.depth + 1
-                    new_gblState.mstate.constraints = copy.deepcopy(state.constraints)
-
-                    new_node = self._sym_exec(new_gblState)
-                    self.nodes[new_node.uid] = new_node
-
-                    environment.callvalue = temp_callvalue
-                    environment.caller = temp_caller
-                    environment.calldata = temp_calldata
-
-                elif (op == 'DELEGATECALL'):
-                    temp_code = environment.code
-                    temp_calldata = environment.calldata
-
-                    environment.code = callee_account.code
-                    environment.calldata = calldata
-
-                    new_gblState = GlobalState(gblState.accounts, environment, MachineState(gas))
-                    new_gblState.mstate.depth = state.depth + 1
-                    new_gblState.mstate.constraints = copy.deepcopy(state.constraints)
-
-                    new_node = self._sym_exec(new_gblState)
-                    self.nodes[new_node.uid] = new_node
-
-                    environment.code = temp_code
-                    environment.calldata = temp_calldata
-
-                self.edges.append(Edge(node.uid, new_node.uid, JumpType.CALL))
-
-                '''
-                There may be multiple possible returns from the callee contract. Currently, we don't create separate nodes on the CFG
-                for each of them. Instead, a single "return node" is created and a separate edge is added for each return path.
-                The return value is always symbolic.
-                '''
-
-                ret = BitVec("retval_" + str(disassembly.instruction_list[state.pc]['address']), 256)
-                state.stack.append(ret)
-
-                return_address = self.call_stack.pop()
-
-                new_gblState = LaserEVM.copy_global_state(gblState)
-                new_gblState.mstate.depth += 1
-                new_node = self._sym_exec(new_gblState)
-
+            self.manage_cfg(op_code, new_states)
+
+            self.work_list += new_states
+            self.total_states += len(new_states)
+
+    def execute_state(self, global_state):
+        instructions = global_state.environment.code.instruction_list
+        op_code = instructions[global_state.mstate.pc]['opcode']
+        return Instruction(op_code, self.dynamic_loader).evaluate(global_state), op_code
+
+    def manage_cfg(self, opcode, new_states):
+        if opcode == "JUMP":
+            assert len(new_states) <= 1
+            for state in new_states:
+                self._new_node_state(state)
+        elif opcode == "JUMPI":
+            for state in new_states:
+                self._new_node_state(state, JumpType.CONDITIONAL, state.mstate.constraints[-1])
+        elif opcode in ("CALL", 'CALLCODE', 'DELEGATECALL', 'STATICCALL'):
+            assert len(new_states) <= 1
+            for state in new_states:
+                self._new_node_state(state, JumpType.CALL)
+        elif opcode == "RETURN":
+            for state in new_states:
+                self._new_node_state(state, JumpType.RETURN)
+
+        for state in new_states:
+            state.node.states.append(state)
+
+    def _new_node_state(self, state, edge_type=JumpType.UNCONDITIONAL, condition=None):
+        new_node = Node(state.environment.active_account.contract_name)
+        old_node = state.node
+        state.node = new_node
+        new_node.constraints = state.mstate.constraints
+        self.nodes[new_node.uid] = new_node
+        self.edges.append(Edge(old_node.uid, new_node.uid, edge_type=edge_type, condition=condition))
+
+        if edge_type == JumpType.RETURN:
+            new_node.flags |= NodeFlags.CALL_RETURN
+        elif edge_type == JumpType.CALL:
+            if 'retval' in str(state.mstate.stack[-1]):
                 new_node.flags |= NodeFlags.CALL_RETURN
+            else:
+                new_node.flags |= NodeFlags.FUNC_ENTRY
+        address = state.environment.code.instruction_list[state.mstate.pc - 1]['address']
+        
+        environment = state.environment
+        disassembly = environment.code
+        if address in state.environment.code.addr_to_func:
+            # Enter a new function
 
-                self.nodes[new_node.uid] = new_node
-
-                for ret_uid in self.pending_returns[return_address]:
-                    self.edges.append(Edge(ret_uid, new_node.uid, JumpType.RETURN))
-
-                state.stack.append(BitVec("retval", 256))
-
-                halt = True
-
-            elif op == 'RETURN':
-                offset, length = state.stack.pop(), state.stack.pop()
-
-                try:
-                    self.last_returned = state.memory[helper.get_concrete_int(offset):helper.get_concrete_int(offset + length)]
-                except AttributeError:
-                    logging.debug("Return with symbolic length or offset. Not supported")
-
-                if len(self.call_stack):
-                    self.pending_returns[self.call_stack[-1]].append(node.uid)
-
-                halt = True
-
-            elif op == 'SUICIDE':
-                halt = True
-
-            elif op == 'REVERT':
-                if len(self.call_stack):
-                    self.pending_returns[self.call_stack[-1]].append(node.uid)
-
-                halt = True
-
-            elif op == 'ASSERT_FAIL' or op == 'INVALID':
-                if len(self.call_stack):
-                    self.pending_returns[self.call_stack[-1]].append(node.uid)
-
-                halt = True
-
-        logging.debug("Returning from node " + str(node.uid))
-        return node
-    def _get_calldata(self,meminstart, meminsz, state, pad = True):
-        try:
-            # TODO: This only allows for either fully concrete or fully symbolic calldata.
-            # Improve management of memory and callata to support a mix between both types.
-
-            calldata = state.memory[helper.get_concrete_int(meminstart):helper.get_concrete_int(meminstart + meminsz)]
-
-            if (len(calldata) < 32 and pad):
-                calldata += [0] * (32 - len(calldata))
-
-            calldata_type = CalldataType.CONCRETE
-            logging.debug("Calldata: " + str(calldata))
+            environment.active_function_name = disassembly.addr_to_func[address]
+            new_node.flags |= NodeFlags.FUNC_ENTRY
 
-        except AttributeError:
+            logging.info("- Entering function " + environment.active_account.contract_name + ":" + new_node.function_name)
 
-            logging.info("Unsupported symbolic calldata offset")
-            calldata_type = CalldataType.SYMBOLIC
-            calldata = []
-        return calldata, calldata_type
+        new_node.function_name = environment.active_function_name
diff --git a/mythril/laser/ethereum/taint_analysis.py b/mythril/laser/ethereum/taint_analysis.py
index 5aa9f7d2..e370944d 100644
--- a/mythril/laser/ethereum/taint_analysis.py
+++ b/mythril/laser/ethereum/taint_analysis.py
@@ -1,5 +1,5 @@
 import logging, copy
-import mythril.laser.ethereum.helper as helper
+import mythril.laser.ethereum.util as helper
 
 
 class TaintRecord:
@@ -135,6 +135,7 @@ class TaintRunner:
 
         # Apply Change
         op = state.get_current_instruction()['opcode']
+
         if op in TaintRunner.stack_taint_table.keys():
             mutator = TaintRunner.stack_taint_table[op]
             TaintRunner.mutate_stack(new_record, mutator)
diff --git a/mythril/laser/ethereum/helper.py b/mythril/laser/ethereum/util.py
similarity index 100%
rename from mythril/laser/ethereum/helper.py
rename to mythril/laser/ethereum/util.py
diff --git a/mythril/support/truffle.py b/mythril/support/truffle.py
index ab885730..ad8dec36 100644
--- a/mythril/support/truffle.py
+++ b/mythril/support/truffle.py
@@ -10,7 +10,7 @@ from mythril.analysis.symbolic import SymExecWrapper
 from mythril.analysis.report import Report
 
 from mythril.ether import util
-from mythril.laser.ethereum import helper
+from mythril.laser.ethereum.util import get_instruction_index
 
 
 def analyze_truffle_project(args):
@@ -80,7 +80,7 @@ def analyze_truffle_project(args):
 
                 for issue in issues:
 
-                    index = helper.get_instruction_index(disassembly.instruction_list, issue.address)
+                    index = get_instruction_index(disassembly.instruction_list, issue.address)
 
                     if index:
                             try:
@@ -97,7 +97,7 @@ def analyze_truffle_project(args):
 
                 if (args.outform == 'json'):
 
-                    result = {'contract': name, 'result': {'success': True, 'error': None, 'issues': list(map(lambda x: x.as_dict(), issues))}}
+                    result = {'contract': name, 'result': {'success': True, 'error': None, 'issues': list(map(lambda x: x.as_dict, issues))}}
                     print(json.dumps(result))
 
                 else:
diff --git a/tests/analysis/test_delegatecall.py b/tests/analysis/test_delegatecall.py
index 8bfeb65d..244a894e 100644
--- a/tests/analysis/test_delegatecall.py
+++ b/tests/analysis/test_delegatecall.py
@@ -11,7 +11,7 @@ def test_concrete_call():
     # arrange
     address = "0x10"
 
-    state = GlobalState(None, None)
+    state = GlobalState(None, None, None)
     state.mstate.memory = ["placeholder", "calldata_bling_0"]
 
     node = Node("example")
@@ -42,7 +42,7 @@ def test_concrete_call_symbolic_to():
     # arrange
     address = "0x10"
 
-    state = GlobalState(None, None)
+    state = GlobalState(None, None, None)
     state.mstate.memory = ["placeholder", "calldata_bling_0"]
 
     node = Node("example")
@@ -71,7 +71,7 @@ def test_concrete_call_symbolic_to():
 
 def test_concrete_call_not_calldata():
     # arrange
-    state = GlobalState(None, None)
+    state = GlobalState(None, None, None)
     state.mstate.memory = ["placeholder", "not_calldata"]
     meminstart = Variable(1, VarType.CONCRETE)
 
@@ -88,7 +88,7 @@ def test_symbolic_call_storage_to(mocker):
 
     active_account = Account(address)
     environment = Environment(active_account, None, None, None, None, None)
-    state = GlobalState(None, environment)
+    state = GlobalState(None, environment, None)
     state.mstate.memory = ["placeholder", "calldata_bling_0"]
 
 
@@ -126,7 +126,7 @@ def test_symbolic_call_calldata_to(mocker):
     # arrange
     address = "0x10"
 
-    state = GlobalState(None, None)
+    state = GlobalState(None, None, None)
     state.mstate.memory = ["placeholder", "calldata_bling_0"]
 
 
@@ -172,7 +172,7 @@ def test_delegate_call(sym_mock, concrete_mock, curr_instruction):
 
     active_account = Account('0x10')
     environment = Environment(active_account, None, None, None, None, None)
-    state = GlobalState(None, environment)
+    state = GlobalState(None, environment, Node)
     state.mstate.memory = ["placeholder", "calldata_bling_0"]
     state.mstate.stack = [1, 2, 3]
     assert state.get_current_instruction() == {'address': '0x10'}
diff --git a/tests/native_test.py b/tests/native_test.py
index b9239c30..8deb4d39 100644
--- a/tests/native_test.py
+++ b/tests/native_test.py
@@ -1,7 +1,7 @@
 import json
 from mythril.ether.soliditycontract import SolidityContract
 
-from mythril.laser.ethereum.svm import GlobalState, MachineState
+from mythril.laser.ethereum.state import GlobalState, MachineState
 from mythril.laser.ethereum import svm
 from tests import *
 
@@ -48,7 +48,7 @@ IDENTITY_TEST[3] = (83269476937987, False)
 def _all_info(laser):
     accounts = {}
     for address, _account in laser.accounts.items():
-        account = _account.as_dict()
+        account = _account.as_dict
         account["code"] = account["code"].instruction_list
         account['balance'] = str(account['balance'])
         accounts[address] = account
@@ -58,14 +58,14 @@ def _all_info(laser):
         states = []
         for state in node.states:
             if isinstance(state, MachineState):
-                states.append(state.as_dict())
+                states.append(state.as_dict)
             elif isinstance(state, GlobalState):
-                environment = state.environment.as_dict()
+                environment = state.environment.as_dict
                 environment["active_account"] = environment["active_account"].address
                 states.append({
                     'accounts': state.accounts.keys(),
                     'environment': environment,
-                    'mstate': state.mstate.as_dict()
+                    'mstate': state.mstate.as_dict
                 })
 
         nodes[uid] = {
@@ -78,7 +78,7 @@ def _all_info(laser):
             'flags': str(node.flags)
         }
 
-    edges = [edge.as_dict() for edge in laser.edges]
+    edges = [edge.as_dict for edge in laser.edges]
 
     return {
         'accounts': accounts,
@@ -90,11 +90,11 @@ def _all_info(laser):
 
 def _test_natives(laser_info, test_list, test_name):
     success = 0
-    for i,j in test_list:
+    for i, j in test_list:
         if (str(i) in laser_info) == j:
-            success+=1
+            success += 1
         else:
-            print ("Failed: "+str(i)+" "+str(j))
+            print("Failed: "+str(i)+" "+str(j))
     assert(success == len(test_list))
 
 
diff --git a/tests/svm_test.py b/tests/svm_test.py
index 5c0ab1eb..4742b89e 100644
--- a/tests/svm_test.py
+++ b/tests/svm_test.py
@@ -4,7 +4,7 @@ from mythril.analysis.callgraph import generate_graph
 from mythril.ether.ethcontract import ETHContract
 from mythril.ether.soliditycontract import SolidityContract
 
-from mythril.laser.ethereum.svm import GlobalState, MachineState
+from mythril.laser.ethereum.state import GlobalState, MachineState
 from mythril.laser.ethereum import svm
 from tests import *
 
@@ -19,7 +19,7 @@ class LaserEncoder(json.JSONEncoder):
 def _all_info(laser):
     accounts = {}
     for address, _account in laser.accounts.items():
-        account = _account.as_dict()
+        account = _account.as_dict
         account["code"] = account["code"].instruction_list
         account['balance'] = str(account['balance'])
         accounts[address] = account
@@ -29,14 +29,14 @@ def _all_info(laser):
         states = []
         for state in node.states:
             if isinstance(state, MachineState):
-                states.append(state.as_dict())
+                states.append(state.as_dict)
             elif isinstance(state, GlobalState):
-                environment = state.environment.as_dict()
+                environment = state.environment.as_dict
                 environment["active_account"] = environment["active_account"].address
                 states.append({
                     'accounts': state.accounts.keys(),
                     'environment': environment,
-                    'mstate': state.mstate.as_dict()
+                    'mstate': state.mstate.as_dict
                 })
 
         nodes[uid] = {
@@ -49,7 +49,7 @@ def _all_info(laser):
             'flags': str(node.flags)
         }
 
-    edges = [edge.as_dict() for edge in laser.edges]
+    edges = [edge.as_dict for edge in laser.edges]
 
     return {
         'accounts': accounts,
diff --git a/tests/taint_result_test.py b/tests/taint_result_test.py
index ff74c78a..1b2666e6 100644
--- a/tests/taint_result_test.py
+++ b/tests/taint_result_test.py
@@ -6,7 +6,7 @@ def test_result_state():
     # arrange
     taint_result = TaintResult()
     record = TaintRecord()
-    state = GlobalState(2, None)
+    state = GlobalState(2, None, None)
     state.mstate.stack = [1,2,3]
     record.add_state(state)
     record.stack = [False, False, False]
@@ -23,7 +23,7 @@ def test_result_no_state():
     # arrange
     taint_result = TaintResult()
     record = TaintRecord()
-    state = GlobalState(2, None)
+    state = GlobalState(2, None, None)
     state.mstate.stack = [1,2,3]
 
 
diff --git a/tests/taint_runner_test.py b/tests/taint_runner_test.py
index dc334b8d..eb9df629 100644
--- a/tests/taint_runner_test.py
+++ b/tests/taint_runner_test.py
@@ -2,14 +2,15 @@ import mock
 import pytest
 from pytest_mock import mocker
 from mythril.laser.ethereum.taint_analysis import *
-from mythril.laser.ethereum.svm import GlobalState, Node, Edge, LaserEVM, MachineState
+from mythril.laser.ethereum.svm import GlobalState, Node, Edge, LaserEVM
+from mythril.laser.ethereum.state import MachineState
 
 
 def test_execute_state(mocker):
     record = TaintRecord()
     record.stack = [True, False, True]
 
-    state = GlobalState(None, None)
+    state = GlobalState(None, None, None)
     state.mstate.stack = [1, 2, 3]
     mocker.patch.object(state, 'get_current_instruction')
     state.get_current_instruction.return_value = {"opcode": "ADD"}
@@ -26,13 +27,13 @@ def test_execute_node(mocker):
     record = TaintRecord()
     record.stack = [True, True, False, False]
 
-    state_1 = GlobalState(None, None)
-    state_1.mstate.stack = [1, 2, 3]
+    state_1 = GlobalState(None, None, None)
+    state_1.mstate.stack = [1, 2, 3, 1]
     state_1.mstate.pc = 1
     mocker.patch.object(state_1, 'get_current_instruction')
     state_1.get_current_instruction.return_value = {"opcode": "SWAP1"}
 
-    state_2 = GlobalState(None, 1)
+    state_2 = GlobalState(None, 1, None)
     state_2.mstate.stack = [1, 2, 4, 1]
     mocker.patch.object(state_2, 'get_current_instruction')
     state_2.get_current_instruction.return_value = {"opcode": "ADD"}
@@ -56,12 +57,12 @@ def test_execute_node(mocker):
 
 
 def test_execute(mocker):
-    state_1 = GlobalState(None, None, MachineState(gas=10000000))
+    state_1 = GlobalState(None, None, None, MachineState(gas=10000000))
     state_1.mstate.stack = [1, 2]
     mocker.patch.object(state_1, 'get_current_instruction')
     state_1.get_current_instruction.return_value = {"opcode": "PUSH"}
 
-    state_2 = GlobalState(None, None, MachineState(gas=10000000))
+    state_2 = GlobalState(None, None, None, MachineState(gas=10000000))
     state_2.mstate.stack = [1, 2, 3]
     mocker.patch.object(state_2, 'get_current_instruction')
     state_2.get_current_instruction.return_value = {"opcode": "ADD"}
@@ -69,7 +70,7 @@ def test_execute(mocker):
     node_1 = Node("Test contract")
     node_1.states = [state_1, state_2]
 
-    state_3 = GlobalState(None, None, MachineState(gas=10000000))
+    state_3 = GlobalState(None, None, None, MachineState(gas=10000000))
     state_3.mstate.stack = [1, 2]
     mocker.patch.object(state_3, 'get_current_instruction')
     state_3.get_current_instruction.return_value = {"opcode": "ADD"}
diff --git a/tests/testdata/outputs_expected/calls.sol.graph.html b/tests/testdata/outputs_expected/calls.sol.graph.html
deleted file mode 100644
index ad713b74..00000000
--- a/tests/testdata/outputs_expected/calls.sol.graph.html
+++ /dev/null
@@ -1,202 +0,0 @@
-
- 
-
-  
-  
-
-
-  
-  
-  
- 
-
-

Mythril / LASER Symbolic VM

-


- - - diff --git a/tests/testdata/outputs_expected/calls.sol.o.graph.html b/tests/testdata/outputs_expected/calls.sol.o.graph.html index 665a8a65..0ae74c2d 100644 --- a/tests/testdata/outputs_expected/calls.sol.o.graph.html +++ b/tests/testdata/outputs_expected/calls.sol.o.graph.html @@ -18,8 +18,8 @@ diff --git a/tests/testdata/outputs_expected/calls.sol.o.json b/tests/testdata/outputs_expected/calls.sol.o.json index 58bebb0b..3a93166f 100644 --- a/tests/testdata/outputs_expected/calls.sol.o.json +++ b/tests/testdata/outputs_expected/calls.sol.o.json @@ -1,86 +1 @@ -{ - "success": true, - "error": null, - "issues": [ - { - "title": "Message call to external contract", - "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_0x5a6814ec", - "type": "Informational", - "address": 661, - "debug": "" - }, - { - "title": "Message call to external contract", - "description": "This contract executes a message call to an address found at storage slot 1. This storage slot can be written to by calling the function `_function_0x2776b163`. Generally, it is not recommended to call user-supplied addresses using Solidity's call() construct. Note that attackers might leverage reentrancy attacks to exploit race conditions or manipulate this contract's state.", - "function": "_function_0xd24b08cc", - "type": "Warning", - "address": 779, - "debug": "" - }, - { - "title": "Message call to external contract", - "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", - "type": "Informational", - "address": 858, - "debug": "" - }, - { - "title": "State change after external call", - "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", - "type": "Warning", - "address": 869, - "debug": "" - }, - { - "title": "Message call to external contract", - "description": "This contract executes a message call to an address provided as a function argument. Generally, it is not recommended to call user-supplied addresses using Solidity's call() construct. Note that attackers might leverage reentrancy attacks to exploit race conditions or manipulate this contract's state.", - "function": "_function_0xe1d10f79", - "type": "Warning", - "address": 912, - "debug": "" - }, - { - "title": "Transaction order dependence", - "description": "A possible transaction order independence vulnerability exists in function _function_0xd24b08cc. The value or direction of the call statement is determined from a tainted storage location", - "function": "_function_0xd24b08cc", - "type": "Warning", - "address": 779, - "debug": "" - }, - { - "title": "Unchecked CALL return value", - "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", - "function": "_function_0x5a6814ec", - "type": "Informational", - "address": 661, - "debug": "" - }, - { - "title": "Unchecked CALL return value", - "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", - "function": "_function_0xd24b08cc", - "type": "Informational", - "address": 779, - "debug": "" - }, - { - "title": "Unchecked CALL return value", - "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", - "function": "_function_0xe11f493e", - "type": "Informational", - "address": 858, - "debug": "" - }, - { - "title": "Unchecked CALL return value", - "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", - "function": "_function_0xe1d10f79", - "type": "Informational", - "address": 912, - "debug": "" - } - ] -} \ No newline at end of file +{"error": null, "issues": [{"address": 661, "debug": "", "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_0x5a6814ec", "title": "Message call to external contract", "type": "Informational"}, {"address": 666, "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0x5a6814ec", "title": "Unchecked CALL return value", "type": "Informational"}, {"address": 779, "debug": "", "description": "This contract executes a message call to an address found at storage slot 1. This storage slot can be written to by calling the function `_function_0x2776b163`. Generally, it is not recommended to call user-supplied addresses using Solidity's call() construct. Note that attackers might leverage reentrancy attacks to exploit race conditions or manipulate this contract's state.", "function": "_function_0xd24b08cc", "title": "Message call to external contract", "type": "Warning"}, {"address": 779, "debug": "", "description": "A possible transaction order independence vulnerability exists in function _function_0xd24b08cc. The value or direction of the call statement is determined from a tainted storage location", "function": "_function_0xd24b08cc", "title": "Transaction order dependence", "type": "Warning"}, {"address": 784, "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0xd24b08cc", "title": "Unchecked CALL return value", "type": "Informational"}, {"address": 858, "debug": "", "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", "title": "Message call to external contract", "type": "Informational"}, {"address": 869, "debug": "", "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", "title": "State change after external call", "type": "Warning"}, {"address": 871, "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0xe11f493e", "title": "Unchecked CALL return value", "type": "Informational"}, {"address": 912, "debug": "", "description": "This contract executes a message call to an address provided as a function argument. Generally, it is not recommended to call user-supplied addresses using Solidity's call() construct. Note that attackers might leverage reentrancy attacks to exploit race conditions or manipulate this contract's state.", "function": "_function_0xe1d10f79", "title": "Message call to external contract", "type": "Warning"}, {"address": 918, "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0xe1d10f79", "title": "Unchecked CALL return value", "type": "Informational"}], "success": true} \ No newline at end of file diff --git a/tests/testdata/outputs_expected/calls.sol.o.markdown b/tests/testdata/outputs_expected/calls.sol.o.markdown index 02e69173..b5f5990e 100644 --- a/tests/testdata/outputs_expected/calls.sol.o.markdown +++ b/tests/testdata/outputs_expected/calls.sol.o.markdown @@ -16,7 +16,7 @@ This contract executes a message call to to another contract. Make sure that the - Type: Informational - Contract: Unknown - Function name: `_function_0x5a6814ec` -- PC address: 661 +- PC address: 666 ### Description @@ -49,7 +49,7 @@ A possible transaction order independence vulnerability exists in function _func - Type: Informational - Contract: Unknown - Function name: `_function_0xd24b08cc` -- PC address: 779 +- PC address: 784 ### Description @@ -66,27 +66,27 @@ The return value of an external call is not checked. Note that execution continu 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. -## Unchecked CALL return value +## State change after external call -- Type: Informational +- Type: Warning - Contract: Unknown - Function name: `_function_0xe11f493e` -- PC address: 858 +- PC address: 869 ### Description -The return value of an external call is not checked. Note that execution continue even if the called contract throws. +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. -## State change after external call +## Unchecked CALL return value -- Type: Warning +- Type: Informational - Contract: Unknown - Function name: `_function_0xe11f493e` -- PC address: 869 +- PC address: 871 ### 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. +The return value of an external call is not checked. Note that execution continue even if the called contract throws. ## Message call to external contract @@ -104,7 +104,7 @@ This contract executes a message call to an address provided as a function argum - Type: Informational - Contract: Unknown - Function name: `_function_0xe1d10f79` -- PC address: 912 +- PC address: 918 ### Description diff --git a/tests/testdata/outputs_expected/calls.sol.o.text b/tests/testdata/outputs_expected/calls.sol.o.text index efdfd1e4..f126ddb5 100644 --- a/tests/testdata/outputs_expected/calls.sol.o.text +++ b/tests/testdata/outputs_expected/calls.sol.o.text @@ -10,7 +10,7 @@ This contract executes a message call to to another contract. Make sure that the Type: Informational Contract: Unknown Function name: _function_0x5a6814ec -PC address: 661 +PC address: 666 The return value of an external call is not checked. Note that execution continue even if the called contract throws. -------------------- @@ -34,7 +34,7 @@ A possible transaction order independence vulnerability exists in function _func Type: Informational Contract: Unknown Function name: _function_0xd24b08cc -PC address: 779 +PC address: 784 The return value of an external call is not checked. Note that execution continue even if the called contract throws. -------------------- @@ -46,14 +46,6 @@ PC address: 858 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. -------------------- -==== Unchecked CALL return value ==== -Type: Informational -Contract: Unknown -Function name: _function_0xe11f493e -PC address: 858 -The return value of an external call is not checked. Note that execution continue even if the called contract throws. --------------------- - ==== State change after external call ==== Type: Warning Contract: Unknown @@ -62,6 +54,14 @@ PC address: 869 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. -------------------- +==== Unchecked CALL return value ==== +Type: Informational +Contract: Unknown +Function name: _function_0xe11f493e +PC address: 871 +The return value of an external call is not checked. Note that execution continue even if the called contract throws. +-------------------- + ==== Message call to external contract ==== Type: Warning Contract: Unknown @@ -74,7 +74,7 @@ This contract executes a message call to an address provided as a function argum Type: Informational Contract: Unknown Function name: _function_0xe1d10f79 -PC address: 912 +PC address: 918 The return value of an external call is not checked. Note that execution continue even if the called contract throws. -------------------- diff --git a/tests/testdata/outputs_expected/ether_send.sol.json b/tests/testdata/outputs_expected/ether_send.sol.json deleted file mode 100644 index 08a5012c..00000000 --- a/tests/testdata/outputs_expected/ether_send.sol.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "success": true, - "error": null, - "issues": [ - { - "title": "Ether send", - "description": "In the function `withdrawfunds()` a non-zero amount of Ether is sent to msg.sender.\n\nThere is a check on storage index 1. This storage slot can be written to by calling the function `crowdfunding()`.", - "function": "withdrawfunds()", - "type": "Warning", - "address": 816, - "debug": "", - "filename": "/inputs/ether_send.sol", - "lineno": 18, - "code": "msg.sender.transfer(this.balance)" - }, - { - "title": "Integer Overflow", - "description": "A possible integer overflow exists in the function `invest()`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", - "function": "invest()", - "type": "Warning", - "address": 483, - "debug": "", - "filename": "/inputs/ether_send.sol", - "lineno": 24, - "code": "balances[msg.sender] += msg.value" - } - ] -} diff --git a/tests/testdata/outputs_expected/ether_send.sol.o.graph.html b/tests/testdata/outputs_expected/ether_send.sol.o.graph.html index 089d010f..bdb8c6bb 100644 --- a/tests/testdata/outputs_expected/ether_send.sol.o.graph.html +++ b/tests/testdata/outputs_expected/ether_send.sol.o.graph.html @@ -18,8 +18,8 @@ diff --git a/tests/testdata/outputs_expected/exceptions.sol.graph.html b/tests/testdata/outputs_expected/exceptions.sol.graph.html deleted file mode 100644 index 403c896d..00000000 --- a/tests/testdata/outputs_expected/exceptions.sol.graph.html +++ /dev/null @@ -1,252 +0,0 @@ - - - - - - - - - - - - -

Mythril / LASER Symbolic VM

-


- - - diff --git a/tests/testdata/outputs_expected/exceptions.sol.o.graph.html b/tests/testdata/outputs_expected/exceptions.sol.o.graph.html index 2ac1b798..24ba3f52 100644 --- a/tests/testdata/outputs_expected/exceptions.sol.o.graph.html +++ b/tests/testdata/outputs_expected/exceptions.sol.o.graph.html @@ -18,8 +18,8 @@ diff --git a/tests/testdata/outputs_expected/exceptions.sol.o.json b/tests/testdata/outputs_expected/exceptions.sol.o.json index 53ef733d..4434c91f 100644 --- a/tests/testdata/outputs_expected/exceptions.sol.o.json +++ b/tests/testdata/outputs_expected/exceptions.sol.o.json @@ -1,38 +1 @@ -{ - "success": true, - "error": null, - "issues": [ - { - "title": "Exception state", - "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", - "function": "_function_0x546455b5", - "type": "Informational", - "address": 446, - "debug": "" - }, - { - "title": "Exception state", - "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", - "function": "_function_0x92dd38ea", - "type": "Informational", - "address": 484, - "debug": "" - }, - { - "title": "Exception state", - "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", - "function": "_function_0xa08299f1", - "type": "Informational", - "address": 506, - "debug": "" - }, - { - "title": "Exception state", - "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", - "function": "_function_0xb34c3610", - "type": "Informational", - "address": 531, - "debug": "" - } - ] -} \ No newline at end of file +{"error": null, "issues": [{"address": 446, "debug": "", "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", "function": "_function_0x546455b5", "title": "Exception state", "type": "Informational"}, {"address": 484, "debug": "", "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", "function": "_function_0x92dd38ea", "title": "Exception state", "type": "Informational"}, {"address": 506, "debug": "", "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", "function": "_function_0xa08299f1", "title": "Exception state", "type": "Informational"}, {"address": 531, "debug": "", "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", "function": "_function_0xb34c3610", "title": "Exception state", "type": "Informational"}], "success": true} \ No newline at end of file diff --git a/tests/testdata/outputs_expected/kinds_of_calls.sol.graph.html b/tests/testdata/outputs_expected/kinds_of_calls.sol.graph.html deleted file mode 100644 index 5f161388..00000000 --- a/tests/testdata/outputs_expected/kinds_of_calls.sol.graph.html +++ /dev/null @@ -1,178 +0,0 @@ - - - - - - - - - - - - -

Mythril / LASER Symbolic VM

-


- - - diff --git a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.graph.html b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.graph.html index ae421675..82078538 100644 --- a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.graph.html +++ b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.graph.html @@ -18,8 +18,8 @@ diff --git a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.json b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.json index edc77d48..747f1118 100644 --- a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.json +++ b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.json @@ -1,22 +1 @@ -{ - "success": true, - "error": null, - "issues": [ - { - "title": "Message call to external contract", - "description": "This contract executes a message call to an address provided as a function argument. Generally, it is not recommended to call user-supplied addresses using Solidity's call() construct. Note that attackers might leverage reentrancy attacks to exploit race conditions or manipulate this contract's state.", - "function": "_function_0xeea4c864", - "type": "Warning", - "address": 1038, - "debug": "" - }, - { - "title": "Unchecked CALL return value", - "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", - "function": "_function_0xeea4c864", - "type": "Informational", - "address": 1038, - "debug": "" - } - ] -} \ No newline at end of file +{"error": null, "issues": [{"address": 626, "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0x141f32ff", "title": "Unchecked CALL return value", "type": "Informational"}, {"address": 857, "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0x9b58bc26", "title": "Unchecked CALL return value", "type": "Informational"}, {"address": 1038, "debug": "", "description": "This contract executes a message call to an address provided as a function argument. Generally, it is not recommended to call user-supplied addresses using Solidity's call() construct. Note that attackers might leverage reentrancy attacks to exploit race conditions or manipulate this contract's state.", "function": "_function_0xeea4c864", "title": "Message call to external contract", "type": "Warning"}, {"address": 1046, "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0xeea4c864", "title": "Unchecked CALL return value", "type": "Informational"}], "success": true} \ No newline at end of file diff --git a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.markdown b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.markdown index 3a6b4073..40003ab4 100644 --- a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.markdown +++ b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.markdown @@ -1,5 +1,27 @@ # Analysis results for test-filename.sol +## Unchecked CALL return value + +- Type: Informational +- Contract: Unknown +- Function name: `_function_0x141f32ff` +- PC address: 626 + +### Description + +The return value of an external call is not checked. Note that execution continue even if the called contract throws. + +## Unchecked CALL return value + +- Type: Informational +- Contract: Unknown +- Function name: `_function_0x9b58bc26` +- PC address: 857 + +### Description + +The return value of an external call is not checked. Note that execution continue even if the called contract throws. + ## Message call to external contract - Type: Warning @@ -16,7 +38,7 @@ This contract executes a message call to an address provided as a function argum - Type: Informational - Contract: Unknown - Function name: `_function_0xeea4c864` -- PC address: 1038 +- PC address: 1046 ### Description diff --git a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.text b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.text index 98deddc9..e08de551 100644 --- a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.text +++ b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.text @@ -1,3 +1,19 @@ +==== Unchecked CALL return value ==== +Type: Informational +Contract: Unknown +Function name: _function_0x141f32ff +PC address: 626 +The return value of an external call is not checked. Note that execution continue even if the called contract throws. +-------------------- + +==== Unchecked CALL return value ==== +Type: Informational +Contract: Unknown +Function name: _function_0x9b58bc26 +PC address: 857 +The return value of an external call is not checked. Note that execution continue even if the called contract throws. +-------------------- + ==== Message call to external contract ==== Type: Warning Contract: Unknown @@ -10,7 +26,7 @@ This contract executes a message call to an address provided as a function argum Type: Informational Contract: Unknown Function name: _function_0xeea4c864 -PC address: 1038 +PC address: 1046 The return value of an external call is not checked. Note that execution continue even if the called contract throws. -------------------- diff --git a/tests/testdata/outputs_expected/metacoin.sol.graph.html b/tests/testdata/outputs_expected/metacoin.sol.graph.html deleted file mode 100644 index e5617724..00000000 --- a/tests/testdata/outputs_expected/metacoin.sol.graph.html +++ /dev/null @@ -1,150 +0,0 @@ - - - - - - - - - - - - -

Mythril / LASER Symbolic VM

-


- - - diff --git a/tests/testdata/outputs_expected/metacoin.sol.o.graph.html b/tests/testdata/outputs_expected/metacoin.sol.o.graph.html index c6b8ae71..ce58facc 100644 --- a/tests/testdata/outputs_expected/metacoin.sol.o.graph.html +++ b/tests/testdata/outputs_expected/metacoin.sol.o.graph.html @@ -18,8 +18,8 @@ diff --git a/tests/testdata/outputs_expected/multi_contracts.sol.graph.html b/tests/testdata/outputs_expected/multi_contracts.sol.graph.html deleted file mode 100644 index aa7b400d..00000000 --- a/tests/testdata/outputs_expected/multi_contracts.sol.graph.html +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - - - - - - - -

Mythril / LASER Symbolic VM

-


- - - diff --git a/tests/testdata/outputs_expected/multi_contracts.sol.json b/tests/testdata/outputs_expected/multi_contracts.sol.json deleted file mode 100644 index af046a44..00000000 --- a/tests/testdata/outputs_expected/multi_contracts.sol.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "success": true, - "error": null, - "issues": [ - { - "title": "Ether send", - "description": "In the function `_function_0x8a4068dd` a non-zero amount of Ether is sent to msg.sender.\nIt seems that this function can be called without restrictions.", - "function": "_function_0x8a4068dd", - "type": "Warning", - "address": 142, - "debug": "", - "filename": "/inputs/multi_contracts.sol", - "lineno": 14, - "code": "msg.sender.transfer(2 ether)" - } - ] -} \ No newline at end of file diff --git a/tests/testdata/outputs_expected/multi_contracts.sol.markdown b/tests/testdata/outputs_expected/multi_contracts.sol.markdown deleted file mode 100644 index ff7421aa..00000000 --- a/tests/testdata/outputs_expected/multi_contracts.sol.markdown +++ /dev/null @@ -1,18 +0,0 @@ -# Analysis results for /inputs/multi_contracts.sol - -## Ether send - -- Type: Warning -- Contract: Transfer2 -- Function name: `_function_0x8a4068dd` -- PC address: 142 - -### Description - -In the function `_function_0x8a4068dd` a non-zero amount of Ether is sent to msg.sender. -It seems that this function can be called without restrictions. -In */inputs/multi_contracts.sol:14* - -``` -msg.sender.transfer(2 ether) -``` diff --git a/tests/testdata/outputs_expected/multi_contracts.sol.o.graph.html b/tests/testdata/outputs_expected/multi_contracts.sol.o.graph.html index 94a77a08..fda47d74 100644 --- a/tests/testdata/outputs_expected/multi_contracts.sol.o.graph.html +++ b/tests/testdata/outputs_expected/multi_contracts.sol.o.graph.html @@ -18,8 +18,8 @@ diff --git a/tests/testdata/outputs_expected/multi_contracts.sol.o.json b/tests/testdata/outputs_expected/multi_contracts.sol.o.json index ce4d6465..ff019037 100644 --- a/tests/testdata/outputs_expected/multi_contracts.sol.o.json +++ b/tests/testdata/outputs_expected/multi_contracts.sol.o.json @@ -1,14 +1 @@ -{ - "success": true, - "error": null, - "issues": [ - { - "title": "Ether send", - "description": "In the function `_function_0x8a4068dd` a non-zero amount of Ether is sent to msg.sender.\nIt seems that this function can be called without restrictions.", - "function": "_function_0x8a4068dd", - "type": "Warning", - "address": 142, - "debug": "" - } - ] -} \ No newline at end of file +{"error": null, "issues": [{"address": 142, "debug": "", "description": "In the function `_function_0x8a4068dd` a non-zero amount of Ether is sent to msg.sender.\nIt seems that this function can be called without restrictions.", "function": "_function_0x8a4068dd", "title": "Ether send", "type": "Warning"}], "success": true} \ No newline at end of file diff --git a/tests/testdata/outputs_expected/multi_contracts.sol.text b/tests/testdata/outputs_expected/multi_contracts.sol.text deleted file mode 100644 index 55ed6152..00000000 --- a/tests/testdata/outputs_expected/multi_contracts.sol.text +++ /dev/null @@ -1,14 +0,0 @@ -==== Ether send ==== -Type: Warning -Contract: Transfer2 -Function name: _function_0x8a4068dd -PC address: 142 -In the function `_function_0x8a4068dd` a non-zero amount of Ether is sent to msg.sender. -It seems that this function can be called without restrictions. --------------------- -In file: /inputs/multi_contracts.sol:14 - -msg.sender.transfer(2 ether) - --------------------- - diff --git a/tests/testdata/outputs_expected/nonascii.sol.o.graph.html b/tests/testdata/outputs_expected/nonascii.sol.o.graph.html index 51ce8e60..ea28a136 100644 --- a/tests/testdata/outputs_expected/nonascii.sol.o.graph.html +++ b/tests/testdata/outputs_expected/nonascii.sol.o.graph.html @@ -18,8 +18,8 @@ diff --git a/tests/testdata/outputs_expected/origin.sol.o.graph.html b/tests/testdata/outputs_expected/origin.sol.o.graph.html index 181ceb02..a7da8330 100644 --- a/tests/testdata/outputs_expected/origin.sol.o.graph.html +++ b/tests/testdata/outputs_expected/origin.sol.o.graph.html @@ -18,8 +18,8 @@ diff --git a/tests/testdata/outputs_expected/origin.sol.o.json b/tests/testdata/outputs_expected/origin.sol.o.json index 5dac16d6..2024d492 100644 --- a/tests/testdata/outputs_expected/origin.sol.o.json +++ b/tests/testdata/outputs_expected/origin.sol.o.json @@ -1,14 +1 @@ -{ - "success": true, - "error": null, - "issues": [ - { - "title": "Use of tx.origin", - "description": "Function transferOwnership(address) retrieves the transaction origin (tx.origin) using the ORIGIN opcode. Use msg.sender instead.\nSee also: https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin", - "function": "transferOwnership(address)", - "type": "Warning", - "address": 317, - "debug": "" - } - ] -} \ No newline at end of file +{"error": null, "issues": [{"address": 317, "debug": "", "description": "Function transferOwnership(address) retrieves the transaction origin (tx.origin) using the ORIGIN opcode. Use msg.sender instead.\nSee also: https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin", "function": "transferOwnership(address)", "title": "Use of tx.origin", "type": "Warning"}], "success": true} \ No newline at end of file diff --git a/tests/testdata/outputs_expected/overflow.sol.json b/tests/testdata/outputs_expected/overflow.sol.json deleted file mode 100644 index 92a56005..00000000 --- a/tests/testdata/outputs_expected/overflow.sol.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "success": true, - "error": null, - "issues": [ - { - "title": "Integer Underflow", - "description": "A possible integer underflow exists in the function `sendeth(address,uint256)`.\nThe subtraction may result in a value < 0.", - "function": "sendeth(address,uint256)", - "type": "Warning", - "address": 649, - "debug": "", - "filename": "/inputs/overflow.sol", - "lineno": 12, - "code": "balances[msg.sender] -= _value" - }, - { - "title": "Integer Overflow", - "description": "A possible integer overflow exists in the function `sendeth(address,uint256)`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", - "function": "sendeth(address,uint256)", - "type": "Warning", - "address": 725, - "debug": "", - "filename": "/inputs/overflow.sol", - "lineno": 13, - "code": "balances[_to] += _value" - }, - { - "title": "Integer Underflow", - "description": "A possible integer underflow exists in the function `sendeth(address,uint256)`.\nThe subtraction may result in a value < 0.", - "function": "sendeth(address,uint256)", - "type": "Warning", - "address": 567, - "debug": "", - "filename": "/inputs/overflow.sol", - "lineno": 11, - "code": "balances[msg.sender] - _value" - } - ] -} diff --git a/tests/testdata/outputs_expected/overflow.sol.markdown b/tests/testdata/outputs_expected/overflow.sol.markdown deleted file mode 100644 index d6d4a49c..00000000 --- a/tests/testdata/outputs_expected/overflow.sol.markdown +++ /dev/null @@ -1,52 +0,0 @@ -# Analysis results for /inputs/overflow.sol - -## Integer Underflow - -- Type: Warning -- Contract: Over -- Function name: `sendeth(address,uint256)` -- PC address: 649 - -### Description - -A possible integer underflow exists in the function `sendeth(address,uint256)`. -The subtraction may result in a value < 0. -In */inputs/overflow.sol:12* - -``` -balances[msg.sender] -= _value -``` - -## Integer Overflow - -- Type: Warning -- Contract: Over -- Function name: `sendeth(address,uint256)` -- PC address: 725 - -### Description - -A possible integer overflow exists in the function `sendeth(address,uint256)`. -The addition or multiplication may result in a value higher than the maximum representable integer. -In */inputs/overflow.sol:13* - -``` -balances[_to] += _value -``` - -## Integer Underflow - -- Type: Warning -- Contract: Over -- Function name: `sendeth(address,uint256)` -- PC address: 567 - -### Description - -A possible integer underflow exists in the function `sendeth(address,uint256)`. -The subtraction may result in a value < 0. -In */inputs/overflow.sol:11* - -``` -balances[msg.sender] - _value -``` diff --git a/tests/testdata/outputs_expected/overflow.sol.o.graph.html b/tests/testdata/outputs_expected/overflow.sol.o.graph.html index e16ddfbb..e605ca06 100644 --- a/tests/testdata/outputs_expected/overflow.sol.o.graph.html +++ b/tests/testdata/outputs_expected/overflow.sol.o.graph.html @@ -18,8 +18,8 @@ diff --git a/tests/testdata/outputs_expected/overflow.sol.text b/tests/testdata/outputs_expected/overflow.sol.text deleted file mode 100644 index 64b072ca..00000000 --- a/tests/testdata/outputs_expected/overflow.sol.text +++ /dev/null @@ -1,42 +0,0 @@ -==== Integer Underflow ==== -Type: Warning -Contract: Over -Function name: sendeth(address,uint256) -PC address: 649 -A possible integer underflow exists in the function `sendeth(address,uint256)`. -The subtraction may result in a value < 0. --------------------- -In file: /inputs/overflow.sol:12 - -balances[msg.sender] -= _value - --------------------- - -==== Integer Overflow ==== -Type: Warning -Contract: Over -Function name: sendeth(address,uint256) -PC address: 725 -A possible integer overflow exists in the function `sendeth(address,uint256)`. -The addition or multiplication may result in a value higher than the maximum representable integer. --------------------- -In file: /inputs/overflow.sol:13 - -balances[_to] += _value - --------------------- - -==== Integer Underflow ==== -Type: Warning -Contract: Over -Function name: sendeth(address,uint256) -PC address: 567 -A possible integer underflow exists in the function `sendeth(address,uint256)`. -The subtraction may result in a value < 0. --------------------- -In file: /inputs/overflow.sol:11 - -balances[msg.sender] - _value - --------------------- - diff --git a/tests/testdata/outputs_expected/returnvalue.sol.graph.html b/tests/testdata/outputs_expected/returnvalue.sol.graph.html deleted file mode 100644 index 33184917..00000000 --- a/tests/testdata/outputs_expected/returnvalue.sol.graph.html +++ /dev/null @@ -1,146 +0,0 @@ - - - - - - - - - - - - -

Mythril / LASER Symbolic VM

-


- - - diff --git a/tests/testdata/outputs_expected/returnvalue.sol.o.graph.html b/tests/testdata/outputs_expected/returnvalue.sol.o.graph.html index b58e1899..1e09f932 100644 --- a/tests/testdata/outputs_expected/returnvalue.sol.o.graph.html +++ b/tests/testdata/outputs_expected/returnvalue.sol.o.graph.html @@ -18,8 +18,8 @@ diff --git a/tests/testdata/outputs_expected/returnvalue.sol.o.json b/tests/testdata/outputs_expected/returnvalue.sol.o.json index 89972e27..a3fe1e4c 100644 --- a/tests/testdata/outputs_expected/returnvalue.sol.o.json +++ b/tests/testdata/outputs_expected/returnvalue.sol.o.json @@ -1,30 +1 @@ -{ - "success": true, - "error": null, - "issues": [ - { - "title": "Message call to external contract", - "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_0x633ab5e0", - "type": "Informational", - "address": 196, - "debug": "" - }, - { - "title": "Message call to external contract", - "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_0xe3bea282", - "type": "Informational", - "address": 285, - "debug": "" - }, - { - "title": "Unchecked CALL return value", - "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", - "function": "_function_0xe3bea282", - "type": "Informational", - "address": 285, - "debug": "" - } - ] -} \ No newline at end of file +{"error": null, "issues": [{"address": 196, "debug": "", "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_0x633ab5e0", "title": "Message call to external contract", "type": "Informational"}, {"address": 285, "debug": "", "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_0xe3bea282", "title": "Message call to external contract", "type": "Informational"}, {"address": 290, "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0xe3bea282", "title": "Unchecked CALL return value", "type": "Informational"}], "success": true} \ No newline at end of file diff --git a/tests/testdata/outputs_expected/returnvalue.sol.o.markdown b/tests/testdata/outputs_expected/returnvalue.sol.o.markdown index c8120dcd..3d12c700 100644 --- a/tests/testdata/outputs_expected/returnvalue.sol.o.markdown +++ b/tests/testdata/outputs_expected/returnvalue.sol.o.markdown @@ -27,7 +27,7 @@ This contract executes a message call to to another contract. Make sure that the - Type: Informational - Contract: Unknown - Function name: `_function_0xe3bea282` -- PC address: 285 +- PC address: 290 ### Description diff --git a/tests/testdata/outputs_expected/returnvalue.sol.o.text b/tests/testdata/outputs_expected/returnvalue.sol.o.text index 00510eba..c7a67ecb 100644 --- a/tests/testdata/outputs_expected/returnvalue.sol.o.text +++ b/tests/testdata/outputs_expected/returnvalue.sol.o.text @@ -18,7 +18,7 @@ This contract executes a message call to to another contract. Make sure that the Type: Informational Contract: Unknown Function name: _function_0xe3bea282 -PC address: 285 +PC address: 290 The return value of an external call is not checked. Note that execution continue even if the called contract throws. -------------------- diff --git a/tests/testdata/outputs_expected/rubixi.sol.o.json b/tests/testdata/outputs_expected/rubixi.sol.o.json new file mode 100644 index 00000000..d328ade0 --- /dev/null +++ b/tests/testdata/outputs_expected/rubixi.sol.o.json @@ -0,0 +1,166 @@ +{ + "success": true, + "error": null, + "issues": [ + { + "title": "Ether send", + "description": "In the function `_function_0x4229616d` a non-zero amount of Ether is sent to an address taken from storage slot 5.\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\n\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.", + "function": "_function_0x4229616d", + "type": "Warning", + "address": 1599, + "debug": "" + }, + { + "title": "Ether send", + "description": "In the function `_function_0xb4022950` a non-zero amount of Ether is sent to an address taken from storage slot 5.\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\n\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.", + "function": "_function_0xb4022950", + "type": "Warning", + "address": 1940, + "debug": "" + }, + { + "title": "Ether send", + "description": "In the function `_function_0xb4022950` a non-zero amount of Ether is sent to an address taken from storage slot 5.\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\n\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.", + "function": "_function_0xb4022950", + "type": "Warning", + "address": 2582, + "debug": "" + }, + { + "title": "Exception state", + "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", + "function": "_function_0x57d4021b", + "type": "Informational", + "address": 1653, + "debug": "" + }, + { + "title": "Exception state", + "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", + "function": "_function_0x9dbc4f9b", + "type": "Informational", + "address": 2085, + "debug": "" + }, + { + "title": "Invariant branch condition", + "description": "Found a conditional jump which always follows the same branch", + "function": "fallback", + "type": "Informational", + "address": 3111, + "debug": "" + }, + { + "title": "Invariant branch condition", + "description": "Found a conditional jump which always follows the same branch", + "function": "fallback", + "type": "Informational", + "address": 3140, + "debug": "" + }, + { + "title": "Invariant branch condition", + "description": "Found a conditional jump which always follows the same branch", + "function": "fallback", + "type": "Informational", + "address": 2950, + "debug": "" + }, + { + "title": "Invariant branch condition", + "description": "Found a conditional jump which always follows the same branch", + "function": "fallback", + "type": "Informational", + "address": 1268, + "debug": "" + }, + { + "title": "Invariant branch condition", + "description": "Found a conditional jump which always follows the same branch", + "function": "_function_0x09dfdc71", + "type": "Informational", + "address": 310, + "debug": "" + }, + { + "title": "Invariant branch condition", + "description": "Found a conditional jump which always follows the same branch", + "function": "_function_0x09dfdc71", + "type": "Informational", + "address": 1316, + "debug": "" + }, + { + "title": "Invariant branch condition", + "description": "Found a conditional jump which always follows the same branch", + "function": "_function_0x253459e3", + "type": "Informational", + "address": 1375, + "debug": "" + }, + { + "title": "Invariant branch condition", + "description": "Found a conditional jump which always follows the same branch", + "function": "_function_0x4229616d", + "type": "Informational", + "address": 1511, + "debug": "" + }, + { + "title": "Invariant branch condition", + "description": "Found a conditional jump which always follows the same branch", + "function": "_function_0x57d4021b", + "type": "Informational", + "address": 1679, + "debug": "" + }, + { + "title": "Invariant branch condition", + "description": "Found a conditional jump which always follows the same branch", + "function": "_function_0x6fbaaa1e", + "type": "Informational", + "address": 618, + "debug": "" + }, + { + "title": "Invariant branch condition", + "description": "Found a conditional jump which always follows the same branch", + "function": "_function_0x8a5fb3ca", + "type": "Informational", + "address": 805, + "debug": "" + }, + { + "title": "Invariant branch condition", + "description": "Found a conditional jump which always follows the same branch", + "function": "_function_0x9dbc4f9b", + "type": "Informational", + "address": 2187, + "debug": "" + }, + { + "title": "Unchecked CALL return value", + "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", + "function": "_function_0x4229616d", + "type": "Informational", + "address": 1599, + "debug": "" + }, + { + "title": "Unchecked CALL return value", + "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", + "function": "_function_0xb4022950", + "type": "Informational", + "address": 1940, + "debug": "" + }, + { + "title": "Unchecked CALL return value", + "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", + "function": "_function_0xb4022950", + "type": "Informational", + "address": 2582, + "debug": "" + } + ] +} \ No newline at end of file diff --git a/tests/testdata/outputs_expected/rubixi.sol.o.markdown b/tests/testdata/outputs_expected/rubixi.sol.o.markdown new file mode 100644 index 00000000..ba755c49 --- /dev/null +++ b/tests/testdata/outputs_expected/rubixi.sol.o.markdown @@ -0,0 +1,238 @@ +# Analysis results for test-filename.sol + +## Ether send + +- Type: Warning +- Contract: Unknown +- Function name: `_function_0x4229616d` +- PC address: 1599 + +### Description + +In the function `_function_0x4229616d` a non-zero amount of Ether is sent to an address taken from storage slot 5. +There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. + +There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. +There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. + +## Ether send + +- Type: Warning +- Contract: Unknown +- Function name: `_function_0xb4022950` +- PC address: 1940 + +### Description + +In the function `_function_0xb4022950` a non-zero amount of Ether is sent to an address taken from storage slot 5. +There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. + +There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. +There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. +There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. +There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. + +## Ether send + +- Type: Warning +- Contract: Unknown +- Function name: `_function_0xb4022950` +- PC address: 2582 + +### Description + +In the function `_function_0xb4022950` a non-zero amount of Ether is sent to an address taken from storage slot 5. +There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. + +There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. +There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. +There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. +There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. +There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. + +## Exception state + +- Type: Informational +- Contract: Unknown +- Function name: `_function_0x57d4021b` +- PC address: 1653 + +### Description + +A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. + +## Exception state + +- Type: Informational +- Contract: Unknown +- Function name: `_function_0x9dbc4f9b` +- PC address: 2085 + +### Description + +A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. + +## Tautology + +- Type: Informational +- Contract: Unknown +- Function name: `fallback` +- PC address: 3111 + +### Description + +Found a conditional jump which always follows the same branch, value: True + +## Tautology + +- Type: Informational +- Contract: Unknown +- Function name: `fallback` +- PC address: 3140 + +### Description + +Found a conditional jump which always follows the same branch, value: True + +## Tautology + +- Type: Informational +- Contract: Unknown +- Function name: `fallback` +- PC address: 2950 + +### Description + +Found a conditional jump which always follows the same branch, value: True + +## Tautology + +- Type: Informational +- Contract: Unknown +- Function name: `fallback` +- PC address: 1268 + +### Description + +Found a conditional jump which always follows the same branch, value: True + +## Tautology + +- Type: Informational +- Contract: Unknown +- Function name: `_function_0x09dfdc71` +- PC address: 310 + +### Description + +Found a conditional jump which always follows the same branch, value: False + +## Tautology + +- Type: Informational +- Contract: Unknown +- Function name: `_function_0x09dfdc71` +- PC address: 1316 + +### Description + +Found a conditional jump which always follows the same branch, value: True + +## Tautology + +- Type: Informational +- Contract: Unknown +- Function name: `_function_0x253459e3` +- PC address: 1375 + +### Description + +Found a conditional jump which always follows the same branch, value: True + +## Tautology + +- Type: Informational +- Contract: Unknown +- Function name: `_function_0x4229616d` +- PC address: 1511 + +### Description + +Found a conditional jump which always follows the same branch, value: True + +## Tautology + +- Type: Informational +- Contract: Unknown +- Function name: `_function_0x57d4021b` +- PC address: 1679 + +### Description + +Found a conditional jump which always follows the same branch, value: True + +## Tautology + +- Type: Informational +- Contract: Unknown +- Function name: `_function_0x6fbaaa1e` +- PC address: 618 + +### Description + +Found a conditional jump which always follows the same branch, value: False + +## Tautology + +- Type: Informational +- Contract: Unknown +- Function name: `_function_0x8a5fb3ca` +- PC address: 805 + +### Description + +Found a conditional jump which always follows the same branch, value: False + +## Tautology + +- Type: Informational +- Contract: Unknown +- Function name: `_function_0x9dbc4f9b` +- PC address: 2187 + +### Description + +Found a conditional jump which always follows the same branch, value: True + +## Unchecked CALL return value + +- Type: Informational +- Contract: Unknown +- Function name: `_function_0x4229616d` +- PC address: 1599 + +### Description + +The return value of an external call is not checked. Note that execution continue even if the called contract throws. + +## Unchecked CALL return value + +- Type: Informational +- Contract: Unknown +- Function name: `_function_0xb4022950` +- PC address: 1940 + +### Description + +The return value of an external call is not checked. Note that execution continue even if the called contract throws. + +## Unchecked CALL return value + +- Type: Informational +- Contract: Unknown +- Function name: `_function_0xb4022950` +- PC address: 2582 + +### Description + +The return value of an external call is not checked. Note that execution continue even if the called contract throws. \ No newline at end of file diff --git a/tests/testdata/outputs_expected/rubixi.sol.o.text b/tests/testdata/outputs_expected/rubixi.sol.o.text new file mode 100644 index 00000000..80a40a7f --- /dev/null +++ b/tests/testdata/outputs_expected/rubixi.sol.o.text @@ -0,0 +1,177 @@ +==== Ether send ==== +Type: Warning +Contract: Unknown +Function name: _function_0x4229616d +PC address: 1599 +In the function `_function_0x4229616d` a non-zero amount of Ether is sent to an address taken from storage slot 5. +There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. + +There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. +There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. +-------------------- + +==== Ether send ==== +Type: Warning +Contract: Unknown +Function name: _function_0xb4022950 +PC address: 1940 +In the function `_function_0xb4022950` a non-zero amount of Ether is sent to an address taken from storage slot 5. +There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. + +There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. +There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. +There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. +There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. +-------------------- + +==== Ether send ==== +Type: Warning +Contract: Unknown +Function name: _function_0xb4022950 +PC address: 2582 +In the function `_function_0xb4022950` a non-zero amount of Ether is sent to an address taken from storage slot 5. +There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. + +There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. +There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. +There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. +There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. +There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. +-------------------- + +==== Exception state ==== +Type: Informational +Contract: Unknown +Function name: _function_0x57d4021b +PC address: 1653 +A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. +-------------------- + +==== Exception state ==== +Type: Informational +Contract: Unknown +Function name: _function_0x9dbc4f9b +PC address: 2085 +A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. +-------------------- + +==== Tautology ==== +Type: Informational +Contract: Unknown +Function name: fallback +PC address: 3111 +Found a conditional jump which always follows the same branch, value: True +-------------------- + +==== Tautology ==== +Type: Informational +Contract: Unknown +Function name: fallback +PC address: 3140 +Found a conditional jump which always follows the same branch, value: True +-------------------- + +==== Tautology ==== +Type: Informational +Contract: Unknown +Function name: fallback +PC address: 2950 +Found a conditional jump which always follows the same branch, value: True +-------------------- + +==== Tautology ==== +Type: Informational +Contract: Unknown +Function name: fallback +PC address: 1268 +Found a conditional jump which always follows the same branch, value: True +-------------------- + +==== Tautology ==== +Type: Informational +Contract: Unknown +Function name: _function_0x09dfdc71 +PC address: 310 +Found a conditional jump which always follows the same branch, value: False +-------------------- + +==== Tautology ==== +Type: Informational +Contract: Unknown +Function name: _function_0x09dfdc71 +PC address: 1316 +Found a conditional jump which always follows the same branch, value: True +-------------------- + +==== Tautology ==== +Type: Informational +Contract: Unknown +Function name: _function_0x253459e3 +PC address: 1375 +Found a conditional jump which always follows the same branch, value: True +-------------------- + +==== Tautology ==== +Type: Informational +Contract: Unknown +Function name: _function_0x4229616d +PC address: 1511 +Found a conditional jump which always follows the same branch, value: True +-------------------- + +==== Tautology ==== +Type: Informational +Contract: Unknown +Function name: _function_0x57d4021b +PC address: 1679 +Found a conditional jump which always follows the same branch, value: True +-------------------- + +==== Tautology ==== +Type: Informational +Contract: Unknown +Function name: _function_0x6fbaaa1e +PC address: 618 +Found a conditional jump which always follows the same branch, value: False +-------------------- + +==== Tautology ==== +Type: Informational +Contract: Unknown +Function name: _function_0x8a5fb3ca +PC address: 805 +Found a conditional jump which always follows the same branch, value: False +-------------------- + +==== Tautology ==== +Type: Informational +Contract: Unknown +Function name: _function_0x9dbc4f9b +PC address: 2187 +Found a conditional jump which always follows the same branch, value: True +-------------------- + +==== Unchecked CALL return value ==== +Type: Informational +Contract: Unknown +Function name: _function_0x4229616d +PC address: 1599 +The return value of an external call is not checked. Note that execution continue even if the called contract throws. +-------------------- + +==== Unchecked CALL return value ==== +Type: Informational +Contract: Unknown +Function name: _function_0xb4022950 +PC address: 1940 +The return value of an external call is not checked. Note that execution continue even if the called contract throws. +-------------------- + +==== Unchecked CALL return value ==== +Type: Informational +Contract: Unknown +Function name: _function_0xb4022950 +PC address: 2582 +The return value of an external call is not checked. Note that execution continue even if the called contract throws. +-------------------- + diff --git a/tests/testdata/outputs_expected/suicide.sol.json b/tests/testdata/outputs_expected/suicide.sol.json deleted file mode 100644 index 8b13e751..00000000 --- a/tests/testdata/outputs_expected/suicide.sol.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "success": true, - "error": null, - "issues": [ - { - "title": "Unchecked SUICIDE", - "description": "The function `_function_0xcbf0b0c0` executes the SUICIDE instruction. The remaining Ether is sent to an address provided as a function argument.\n\nIt seems that this function can be called without restrictions.", - "function": "_function_0xcbf0b0c0", - "type": "Warning", - "address": 146, - "debug": "", - "filename": "/inputs/suicide.sol", - "lineno": 4, - "code": "selfdestruct(addr)" - } - ] -} \ No newline at end of file diff --git a/tests/testdata/outputs_expected/suicide.sol.markdown b/tests/testdata/outputs_expected/suicide.sol.markdown deleted file mode 100644 index 78556327..00000000 --- a/tests/testdata/outputs_expected/suicide.sol.markdown +++ /dev/null @@ -1,19 +0,0 @@ -# Analysis results for /inputs/suicide.sol - -## Unchecked SUICIDE - -- Type: Warning -- Contract: Suicide -- Function name: `_function_0xcbf0b0c0` -- PC address: 146 - -### Description - -The function `_function_0xcbf0b0c0` executes the SUICIDE instruction. The remaining Ether is sent to an address provided as a function argument. - -It seems that this function can be called without restrictions. -In */inputs/suicide.sol:4* - -``` -selfdestruct(addr) -``` diff --git a/tests/testdata/outputs_expected/suicide.sol.o.graph.html b/tests/testdata/outputs_expected/suicide.sol.o.graph.html index de3aacee..a005c53e 100644 --- a/tests/testdata/outputs_expected/suicide.sol.o.graph.html +++ b/tests/testdata/outputs_expected/suicide.sol.o.graph.html @@ -18,8 +18,8 @@ diff --git a/tests/testdata/outputs_expected/suicide.sol.o.json b/tests/testdata/outputs_expected/suicide.sol.o.json index d2316b3a..a8a189b9 100644 --- a/tests/testdata/outputs_expected/suicide.sol.o.json +++ b/tests/testdata/outputs_expected/suicide.sol.o.json @@ -1,14 +1 @@ -{ - "success": true, - "error": null, - "issues": [ - { - "title": "Unchecked SUICIDE", - "description": "The function `_function_0xcbf0b0c0` executes the SUICIDE instruction. The remaining Ether is sent to an address provided as a function argument.\n\nIt seems that this function can be called without restrictions.", - "function": "_function_0xcbf0b0c0", - "type": "Warning", - "address": 146, - "debug": "" - } - ] -} \ No newline at end of file +{"error": null, "issues": [{"address": 146, "debug": "", "description": "The function `_function_0xcbf0b0c0` executes the SUICIDE instruction. The remaining Ether is sent to an address provided as a function argument.\n\nIt seems that this function can be called without restrictions.", "function": "_function_0xcbf0b0c0", "title": "Unchecked SUICIDE", "type": "Warning"}], "success": true} \ No newline at end of file diff --git a/tests/testdata/outputs_expected/suicide.sol.text b/tests/testdata/outputs_expected/suicide.sol.text deleted file mode 100644 index 245c5d18..00000000 --- a/tests/testdata/outputs_expected/suicide.sol.text +++ /dev/null @@ -1,15 +0,0 @@ -==== Unchecked SUICIDE ==== -Type: Warning -Contract: Suicide -Function name: _function_0xcbf0b0c0 -PC address: 146 -The function `_function_0xcbf0b0c0` executes the SUICIDE instruction. The remaining Ether is sent to an address provided as a function argument. - -It seems that this function can be called without restrictions. --------------------- -In file: /inputs/suicide.sol:4 - -selfdestruct(addr) - --------------------- - diff --git a/tests/testdata/outputs_expected/underflow.sol.graph.html b/tests/testdata/outputs_expected/underflow.sol.graph.html deleted file mode 100644 index 59a46e2b..00000000 --- a/tests/testdata/outputs_expected/underflow.sol.graph.html +++ /dev/null @@ -1,168 +0,0 @@ - - - - - - - - - - - - -

Mythril / LASER Symbolic VM

-


- - - diff --git a/tests/testdata/outputs_expected/underflow.sol.json b/tests/testdata/outputs_expected/underflow.sol.json deleted file mode 100644 index 3a2fbd91..00000000 --- a/tests/testdata/outputs_expected/underflow.sol.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "success": true, - "error": null, - "issues": [ - { - "title": "Integer Underflow", - "description": "A possible integer underflow exists in the function `sendeth(address,uint256)`.\nThe subtraction may result in a value < 0.", - "function": "sendeth(address,uint256)", - "type": "Warning", - "address": 649, - "debug": "", - "filename": "/inputs/underflow.sol", - "lineno": 12, - "code": "balances[msg.sender] -= _value" - }, - { - "title": "Integer Overflow", - "description": "A possible integer overflow exists in the function `sendeth(address,uint256)`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", - "function": "sendeth(address,uint256)", - "type": "Warning", - "address": 725, - "debug": "", - "filename": "/inputs/underflow.sol", - "lineno": 13, - "code": "balances[_to] += _value" - }, - { - "title": "Integer Underflow", - "description": "A possible integer underflow exists in the function `sendeth(address,uint256)`.\nThe subtraction may result in a value < 0.", - "function": "sendeth(address,uint256)", - "type": "Warning", - "address": 567, - "debug": "", - "filename": "/inputs/underflow.sol", - "lineno": 11, - "code": "balances[msg.sender] - _value" - } - ] -} diff --git a/tests/testdata/outputs_expected/underflow.sol.markdown b/tests/testdata/outputs_expected/underflow.sol.markdown deleted file mode 100644 index b41efee7..00000000 --- a/tests/testdata/outputs_expected/underflow.sol.markdown +++ /dev/null @@ -1,52 +0,0 @@ -# Analysis results for /inputs/underflow.sol - -## Integer Underflow - -- Type: Warning -- Contract: Under -- Function name: `sendeth(address,uint256)` -- PC address: 649 - -### Description - -A possible integer underflow exists in the function `sendeth(address,uint256)`. -The subtraction may result in a value < 0. -In */inputs/underflow.sol:12* - -``` -balances[msg.sender] -= _value -``` - -## Integer Overflow - -- Type: Warning -- Contract: Under -- Function name: `sendeth(address,uint256)` -- PC address: 725 - -### Description - -A possible integer overflow exists in the function `sendeth(address,uint256)`. -The addition or multiplication may result in a value higher than the maximum representable integer. -In */inputs/underflow.sol:13* - -``` -balances[_to] += _value -``` - -## Integer Underflow - -- Type: Warning -- Contract: Under -- Function name: `sendeth(address,uint256)` -- PC address: 567 - -### Description - -A possible integer underflow exists in the function `sendeth(address,uint256)`. -The subtraction may result in a value < 0. -In */inputs/underflow.sol:11* - -``` -balances[msg.sender] - _value -``` diff --git a/tests/testdata/outputs_expected/underflow.sol.o.graph.html b/tests/testdata/outputs_expected/underflow.sol.o.graph.html index 0c60f7e1..72b54f9e 100644 --- a/tests/testdata/outputs_expected/underflow.sol.o.graph.html +++ b/tests/testdata/outputs_expected/underflow.sol.o.graph.html @@ -18,8 +18,8 @@ diff --git a/tests/testdata/outputs_expected/underflow.sol.text b/tests/testdata/outputs_expected/underflow.sol.text deleted file mode 100644 index a3409ad1..00000000 --- a/tests/testdata/outputs_expected/underflow.sol.text +++ /dev/null @@ -1,42 +0,0 @@ -==== Integer Underflow ==== -Type: Warning -Contract: Under -Function name: sendeth(address,uint256) -PC address: 649 -A possible integer underflow exists in the function `sendeth(address,uint256)`. -The subtraction may result in a value < 0. --------------------- -In file: /inputs/underflow.sol:12 - -balances[msg.sender] -= _value - --------------------- - -==== Integer Overflow ==== -Type: Warning -Contract: Under -Function name: sendeth(address,uint256) -PC address: 725 -A possible integer overflow exists in the function `sendeth(address,uint256)`. -The addition or multiplication may result in a value higher than the maximum representable integer. --------------------- -In file: /inputs/underflow.sol:13 - -balances[_to] += _value - --------------------- - -==== Integer Underflow ==== -Type: Warning -Contract: Under -Function name: sendeth(address,uint256) -PC address: 567 -A possible integer underflow exists in the function `sendeth(address,uint256)`. -The subtraction may result in a value < 0. --------------------- -In file: /inputs/underflow.sol:11 - -balances[msg.sender] - _value - --------------------- - diff --git a/tests/testdata/outputs_expected/weak_random.sol.o.json b/tests/testdata/outputs_expected/weak_random.sol.o.json new file mode 100644 index 00000000..2338b178 --- /dev/null +++ b/tests/testdata/outputs_expected/weak_random.sol.o.json @@ -0,0 +1,46 @@ +{ + "success": true, + "error": null, + "issues": [ + { + "title": "Dependence on predictable environment variable", + "description": "In the function `_function_0xe9874106` the following predictable state variables are used to determine Ether recipient:\n- block.coinbase\n", + "function": "_function_0xe9874106", + "type": "Warning", + "address": 1285, + "debug": "" + }, + { + "title": "Ether send", + "description": "In the function `_function_0xe9874106` a non-zero amount of Ether is sent to an address taken from storage slot 0.\nThere is a check on storage index 0. This storage slot can be written to by calling the function `fallback`.\n\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.", + "function": "_function_0xe9874106", + "type": "Warning", + "address": 1285, + "debug": "" + }, + { + "title": "Exception state", + "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", + "function": "fallback", + "type": "Informational", + "address": 356, + "debug": "" + }, + { + "title": "Exception state", + "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", + "function": "_function_0xe9874106", + "type": "Informational", + "address": 146, + "debug": "" + }, + { + "title": "Transaction order dependence", + "description": "A possible transaction order independence vulnerability exists in function _function_0xe9874106. The value or direction of the call statement is determined from a tainted storage location", + "function": "_function_0xe9874106", + "type": "Warning", + "address": 1285, + "debug": "" + } + ] +} \ No newline at end of file diff --git a/tests/testdata/outputs_expected/weak_random.sol.o.markdown b/tests/testdata/outputs_expected/weak_random.sol.o.markdown new file mode 100644 index 00000000..b5744566 --- /dev/null +++ b/tests/testdata/outputs_expected/weak_random.sol.o.markdown @@ -0,0 +1,62 @@ +# Analysis results for test-filename.sol + +## Dependence on predictable environment variable + +- Type: Warning +- Contract: Unknown +- Function name: `_function_0xe9874106` +- PC address: 1285 + +### Description + +In the function `_function_0xe9874106` the following predictable state variables are used to determine Ether recipient: +- block.coinbase + + +## Ether send + +- Type: Warning +- Contract: Unknown +- Function name: `_function_0xe9874106` +- PC address: 1285 + +### Description + +In the function `_function_0xe9874106` a non-zero amount of Ether is sent to an address taken from storage slot 0. +There is a check on storage index 0. This storage slot can be written to by calling the function `fallback`. + +There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. +There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. + +## Exception state + +- Type: Informational +- Contract: Unknown +- Function name: `fallback` +- PC address: 356 + +### Description + +A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. + +## Exception state + +- Type: Informational +- Contract: Unknown +- Function name: `_function_0xe9874106` +- PC address: 146 + +### Description + +A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. + +## Transaction order dependence + +- Type: Warning +- Contract: Unknown +- Function name: `_function_0xe9874106` +- PC address: 1285 + +### Description + +A possible transaction order independence vulnerability exists in function _function_0xe9874106. The value or direction of the call statement is determined from a tainted storage location \ No newline at end of file diff --git a/tests/testdata/outputs_expected/weak_random.sol.o.text b/tests/testdata/outputs_expected/weak_random.sol.o.text new file mode 100644 index 00000000..9e105cfe --- /dev/null +++ b/tests/testdata/outputs_expected/weak_random.sol.o.text @@ -0,0 +1,46 @@ +==== Dependence on predictable environment variable ==== +Type: Warning +Contract: Unknown +Function name: _function_0xe9874106 +PC address: 1285 +In the function `_function_0xe9874106` the following predictable state variables are used to determine Ether recipient: +- block.coinbase + +-------------------- + +==== Ether send ==== +Type: Warning +Contract: Unknown +Function name: _function_0xe9874106 +PC address: 1285 +In the function `_function_0xe9874106` a non-zero amount of Ether is sent to an address taken from storage slot 0. +There is a check on storage index 0. This storage slot can be written to by calling the function `fallback`. + +There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. +There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. +-------------------- + +==== Exception state ==== +Type: Informational +Contract: Unknown +Function name: fallback +PC address: 356 +A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. +-------------------- + +==== Exception state ==== +Type: Informational +Contract: Unknown +Function name: _function_0xe9874106 +PC address: 146 +A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. +-------------------- + +==== Transaction order dependence ==== +Type: Warning +Contract: Unknown +Function name: _function_0xe9874106 +PC address: 1285 +A possible transaction order independence vulnerability exists in function _function_0xe9874106. The value or direction of the call statement is determined from a tainted storage location +-------------------- + From b1d112784164e946e64cd61911c7f701620bd1ee Mon Sep 17 00:00:00 2001 From: Konrad Weiss Date: Thu, 5 Jul 2018 10:13:45 +0200 Subject: [PATCH 36/62] Portyng symbolic name formating to the new svm and instructions file structure --- mythril/laser/ethereum/instructions.py | 47 +++++++++++++++----------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index 4d90da95..89844986 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -157,7 +157,7 @@ class Instruction: result = Concat(BitVecVal(0, 248), Extract(offset + 7, offset, op1)) except AttributeError: logging.debug("BYTE: Unsupported symbolic byte offset") - result = BitVec(str(simplify(op1)) + "_" + str(simplify(op0)), 256) + result = BitVec(str(simplify(op1)) + "[" + str(simplify(op0)) + "]", 256) mstate.stack.append(simplify(result)) return [global_state] @@ -219,7 +219,7 @@ class Instruction: base, exponent = util.pop_bitvec(state), util.pop_bitvec(state) if (type(base) != BitVecNumRef) or (type(exponent) != BitVecNumRef): - state.stack.append(BitVec(str(base) + "_EXP_" + str(exponent), 256)) + state.stack.append(BitVec("(" + str(simplify(base)) + ")^(" + str(simplify(exponent)) + ")", 256)) elif base.as_long() == 2: if exponent.as_long() == 0: state.stack.append(BitVecVal(1, 256)) @@ -330,13 +330,13 @@ class Instruction: b = environment.calldata[offset] except AttributeError: logging.debug("CALLDATALOAD: Unsupported symbolic index") - state.stack.append( - BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(op0), 256)) + state.stack.append(BitVec( + "calldata_" + str(environment.active_account.contract_name) + "[" + str(simplify(op0)) + "]", 256)) return [global_state] except IndexError: logging.debug("Calldata not set, using symbolic variable instead") - state.stack.append( - BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(op0), 256)) + state.stack.append(BitVec( + "calldata_" + str(environment.active_account.contract_name) + "[" + str(simplify(op0)) + "]", 256)) return [global_state] if type(b) == int: @@ -350,12 +350,12 @@ class Instruction: state.stack.append(BitVecVal(int.from_bytes(val, byteorder='big'), 256)) # FIXME: broad exception catch except: - state.stack.append( - BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(op0), 256)) + state.stack.append(BitVec( + "calldata_" + str(environment.active_account.contract_name) + "[" + str(simplify(op0)) + "]", 256)) else: # symbolic variable - state.stack.append( - BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(op0), 256)) + state.stack.append(BitVec( + "calldata_" + str(environment.active_account.contract_name) + "[" + str(simplify(op0)) + "]", 256)) return [global_state] @@ -382,24 +382,29 @@ class Instruction: logging.debug("Unsupported symbolic memory offset in CALLDATACOPY") return [global_state] + dstart_sym = False try: dstart = util.get_concrete_int(op1) + dstart_sym = True # FIXME: broad exception catch except: logging.debug("Unsupported symbolic calldata offset in CALLDATACOPY") - state.mem_extend(mstart, 1) - state.memory[mstart] = BitVec("calldata_" + str(environment.active_account.contract_name) + "_cpy", - 256) - return [global_state] + dstart = simplify(op1) + size_sym = False try: size = util.get_concrete_int(op2) + size_sym = True # FIXME: broad exception catch except: logging.debug("Unsupported symbolic size in CALLDATACOPY") + size = simplify(op2) + + if dstart_sym or size_sym: state.mem_extend(mstart, 1) state.memory[mstart] = BitVec( - "calldata_" + str(environment.active_account.contract_name) + "_" + str(dstart), 256) + "calldata_" + str(environment.active_account.contract_name) + "[" + str(dstart) + ": + " + str( + size) + "]", 256) return [global_state] if size > 0: @@ -410,7 +415,8 @@ class Instruction: logging.debug("Memory allocation error: mstart = " + str(mstart) + ", size = " + str(size)) state.mem_extend(mstart, 1) state.memory[mstart] = BitVec( - "calldata_" + str(environment.active_account.contract_name) + "_" + str(dstart), 256) + "calldata_" + str(environment.active_account.contract_name) + "[" + str(dstart) + ": + " + str( + size) + "]", 256) return [global_state] try: @@ -423,7 +429,8 @@ class Instruction: logging.debug("Exception copying calldata to memory") state.memory[mstart] = BitVec( - "calldata_" + str(environment.active_account.contract_name) + "_" + str(dstart), 256) + "calldata_" + str(environment.active_account.contract_name) + "[" + str(dstart) + ": + " + str( + size) + "]", 256) return [global_state] # Environment @@ -474,7 +481,7 @@ class Instruction: # FIXME: broad exception catch except: # Can't access symbolic memory offsets - state.stack.append(BitVec("KECCAC_mem_" + str(op0) + ")", 256)) + state.stack.append(BitVec("KECCAC_mem[" + str(simplify(op0)) + "]", 256)) return [global_state] try: @@ -590,14 +597,14 @@ class Instruction: offset = util.get_concrete_int(op0) except AttributeError: logging.debug("Can't MLOAD from symbolic index") - data = BitVec("mem_" + str(op0), 256) + data = BitVec("mem[" + str(simplify(op0)) + "]", 256) state.stack.append(data) return [global_state] try: data = util.concrete_int_from_bytes(state.memory, offset) except IndexError: # Memory slot not allocated - data = BitVec("mem_" + str(offset), 256) + data = BitVec("mem[" + str(offset) + "]", 256) except TypeError: # Symbolic memory data = state.memory[offset] From 6bbab6855a12cabec3f22cf7fcbcde8408c3b9c3 Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Thu, 5 Jul 2018 11:30:28 +0200 Subject: [PATCH 37/62] Add return statement --- mythril/laser/ethereum/call.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mythril/laser/ethereum/call.py b/mythril/laser/ethereum/call.py index 23145b9e..483894ce 100644 --- a/mythril/laser/ethereum/call.py +++ b/mythril/laser/ethereum/call.py @@ -111,10 +111,13 @@ def get_callee_account(global_state, callee_address, dynamic_loader): if code is None: logging.info("No code returned, not a contract account?") raise ValueError() + logging.info("Dependency loaded: " + callee_address) - accounts[callee_address] = Account(callee_address, code, callee_address) + callee_account = Account(callee_address, code, callee_address) + accounts[callee_address] = callee_account + + return callee_account - logging.info("Dependency loaded: " + callee_address) def get_call_data(global_state, memory_start, memory_size, pad=True): From 5c0e65b2d80981ecfd8507acc598a80932c6ef9a Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Thu, 5 Jul 2018 11:55:29 +0200 Subject: [PATCH 38/62] Implementation of a depth first search search strategy --- mythril/laser/ethereum/strategy/__init__.py | 0 mythril/laser/ethereum/strategy/basic.py | 17 +++++++++++++++++ mythril/laser/ethereum/svm.py | 13 ++++--------- 3 files changed, 21 insertions(+), 9 deletions(-) create mode 100644 mythril/laser/ethereum/strategy/__init__.py create mode 100644 mythril/laser/ethereum/strategy/basic.py diff --git a/mythril/laser/ethereum/strategy/__init__.py b/mythril/laser/ethereum/strategy/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mythril/laser/ethereum/strategy/basic.py b/mythril/laser/ethereum/strategy/basic.py new file mode 100644 index 00000000..4dc2d968 --- /dev/null +++ b/mythril/laser/ethereum/strategy/basic.py @@ -0,0 +1,17 @@ +class DepthFirstSearchStrategy: + + def __init__(self, content, max_depth): + self.content = content + self.max_depth = max_depth + + def __iter__(self): + return self + + def __next__(self): + try: + global_state = self.content.pop(0) + if global_state.mstate.depth >= self.max_depth: + return self.__next__() + return global_state + except IndexError: + raise StopIteration() diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py index 0001f8e0..fc835662 100644 --- a/mythril/laser/ethereum/svm.py +++ b/mythril/laser/ethereum/svm.py @@ -3,6 +3,7 @@ import logging from mythril.laser.ethereum.state import GlobalState, Environment, CalldataType, Account from mythril.laser.ethereum.instructions import Instruction from mythril.laser.ethereum.cfg import NodeFlags, Node, Edge, JumpType +from mythril.laser.ethereum.strategy.basic import DepthFirstSearchStrategy TT256 = 2 ** 256 TT256M1 = 2 ** 256 - 1 @@ -31,7 +32,7 @@ class LaserEVM: self.dynamic_loader = dynamic_loader self.work_list = [] - self.max_depth = max_depth + self.strategy = DepthFirstSearchStrategy(self.work_list, max_depth) logging.info("LASER EVM initialized with dynamic loader: " + str(dynamic_loader)) @@ -57,20 +58,14 @@ class LaserEVM: initial_node.states.append(global_state) # Empty the work_list before starting an execution - self.work_list = [global_state] + self.work_list.append(global_state) self._sym_exec() logging.info("Execution complete") logging.info("%d nodes, %d edges, %d total states", len(self.nodes), len(self.edges), self.total_states) def _sym_exec(self): - while True: - try: - global_state = self.work_list.pop(0) - if global_state.mstate.depth >= self.max_depth: continue - except IndexError: - return - + for global_state in self.strategy: try: new_states, op_code = self.execute_state(global_state) except NotImplementedError: From b973a1686f269044e670704b56c07ca79336c29c Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Thu, 5 Jul 2018 12:01:26 +0200 Subject: [PATCH 39/62] Add documentation and fix pop --- mythril/laser/ethereum/strategy/basic.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/mythril/laser/ethereum/strategy/basic.py b/mythril/laser/ethereum/strategy/basic.py index 4dc2d968..754cd3ef 100644 --- a/mythril/laser/ethereum/strategy/basic.py +++ b/mythril/laser/ethereum/strategy/basic.py @@ -1,17 +1,29 @@ -class DepthFirstSearchStrategy: +""" +This module implements basic symbolic execution search strategies +""" + - def __init__(self, content, max_depth): - self.content = content +class DepthFirstSearchStrategy: + """ + Implements a depth first search strategy + I.E. Follow one path to a leaf, and then continue to the next one + """ + def __init__(self, work_list, max_depth): + self.work_list = work_list self.max_depth = max_depth def __iter__(self): return self def __next__(self): + """ Picks the next state to execute """ try: - global_state = self.content.pop(0) + # This strategies assumes that new states are appended at the end of the work_list + # By taking the last element we effectively pick the "newest" states, which amounts to dfs + global_state = self.work_list.pop() if global_state.mstate.depth >= self.max_depth: return self.__next__() return global_state except IndexError: raise StopIteration() + From b82717afedda02b94fed56e5abbed7a66480f879 Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Thu, 5 Jul 2018 12:05:08 +0200 Subject: [PATCH 40/62] re add max depth for now --- mythril/laser/ethereum/svm.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py index fc835662..1ceb3740 100644 --- a/mythril/laser/ethereum/svm.py +++ b/mythril/laser/ethereum/svm.py @@ -33,6 +33,7 @@ class LaserEVM: self.work_list = [] self.strategy = DepthFirstSearchStrategy(self.work_list, max_depth) + self.max_depth = max_depth logging.info("LASER EVM initialized with dynamic loader: " + str(dynamic_loader)) From eb963d38fde46d39e91b4be14a68263b869d35d7 Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Thu, 5 Jul 2018 12:16:18 +0200 Subject: [PATCH 41/62] Implement depth first search --- mythril/laser/ethereum/strategy/basic.py | 25 ++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/mythril/laser/ethereum/strategy/basic.py b/mythril/laser/ethereum/strategy/basic.py index 754cd3ef..33dca443 100644 --- a/mythril/laser/ethereum/strategy/basic.py +++ b/mythril/laser/ethereum/strategy/basic.py @@ -27,3 +27,28 @@ class DepthFirstSearchStrategy: except IndexError: raise StopIteration() + +class BreadthFirstSearchStrategy: + """ + Implements a breadth first search strategy + I.E. Execute all states of a "level" before continuing + """ + def __init__(self, work_list, max_depth): + self.work_list = work_list + self.max_depth = max_depth + + def __iter__(self): + return self + + def __next__(self): + """ Picks the next state to execute """ + try: + # This strategies assumes that new states are appended at the end of the work_list + # By taking the first element we effectively pick the "oldest" states, which amounts to bfs + global_state = self.work_list.pop(0) + if global_state.mstate.depth >= self.max_depth: + return self.__next__() + return global_state + except IndexError: + raise StopIteration() + From 25f76368bca4e7865811fcc578a13ac10a09f8ce Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Fri, 6 Jul 2018 21:45:18 +0530 Subject: [PATCH 42/62] Support 0x0 for extcodesize --- mythril/laser/ethereum/instructions.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index 4d90da95..64e87937 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -529,8 +529,12 @@ class Instruction: logging.info("error accessing contract storage due to: " + str(e)) state.stack.append(BitVec("extcodesize_" + str(addr), 256)) return [global_state] + + if code is None: + state.stack.append(0) + else: + state.stack.append(len(code.bytecode) // 2) - state.stack.append(len(code.bytecode) // 2) return [global_state] @instruction From 24fad14cbd498e49834cf22b3f043bfe66a7bd39 Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Fri, 6 Jul 2018 22:25:39 +0530 Subject: [PATCH 43/62] fix ethereum to 2.3.1 --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 13e88c78..aad67f69 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ configparser>=3.5.0 coverage eth_abi>=1.0.0 eth-account>=0.1.0a2 -ethereum>=2.3.0 +ethereum==2.3.1 eth-hash>=0.1.0 eth-keyfile>=0.5.1 eth-keys>=0.2.0b3 diff --git a/setup.py b/setup.py index 3cbf5f97..0e7c624d 100755 --- a/setup.py +++ b/setup.py @@ -305,7 +305,7 @@ setup( packages=find_packages(exclude=['contrib', 'docs', 'tests']), install_requires=[ - 'ethereum>=2.3.0', + 'ethereum==2.3.1', 'z3-solver>=4.5', 'requests', 'py-solc', From d0efd35ce25a817415c17e82ee4d734c3f4e17d4 Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Sat, 7 Jul 2018 19:19:26 +0530 Subject: [PATCH 44/62] connect to infura with -l --- mythril/mythril.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mythril/mythril.py b/mythril/mythril.py index b63cc137..a72a82d7 100644 --- a/mythril/mythril.py +++ b/mythril/mythril.py @@ -335,9 +335,9 @@ class Mythril(object): verbose_report=False, max_depth=12): all_issues = [] + if self.dynld and self.eth is None: + self.set_api_rpc_infura() for contract in (contracts or self.contracts): - if self.eth is None: - self.set_api_rpc_infura() sym = SymExecWrapper(contract, address, dynloader=DynLoader(self.eth) if self.dynld else None, max_depth=max_depth) From 290aaf81988e55366100c900dae4bb5b767c499e Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Sat, 7 Jul 2018 22:16:01 +0200 Subject: [PATCH 45/62] Add condition not 0 --- mythril/laser/ethereum/call.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mythril/laser/ethereum/call.py b/mythril/laser/ethereum/call.py index 483894ce..58eafed3 100644 --- a/mythril/laser/ethereum/call.py +++ b/mythril/laser/ethereum/call.py @@ -36,7 +36,7 @@ def get_call_parameters(global_state, dynamic_loader, with_value=False): callee_account = None call_data, call_data_type = get_call_data(global_state, meminstart, meminsz, False) - if int(callee_address, 16) >= 5: + if int(callee_address, 16) >= 5 or int(callee_address) != 0: call_data, call_data_type = get_call_data(global_state, meminstart, meminsz) callee_account = get_callee_account(global_state, callee_address, dynamic_loader) From c3d1eb85c36ccb0f46f638f01d05f90d82b6dbb5 Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Sat, 7 Jul 2018 22:16:47 +0200 Subject: [PATCH 46/62] Pass None as node --- mythril/laser/ethereum/instructions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index 4d90da95..72451f48 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -913,7 +913,7 @@ class Instruction: value, environment.origin, calldata_type=call_data_type) - new_global_state = GlobalState(global_state.accounts, callee_environment, MachineState(gas)) + new_global_state = GlobalState(global_state.accounts, callee_environment, None, MachineState(gas)) new_global_state.mstate.depth = global_state.mstate.depth + 1 new_global_state.mstate.constraints = copy(global_state.mstate.constraints) return [global_state] @@ -940,7 +940,7 @@ class Instruction: environment.caller = environment.address environment.calldata = call_data - new_global_state = GlobalState(global_state.accounts, environment, MachineState(gas)) + new_global_state = GlobalState(global_state.accounts, environment, None, MachineState(gas)) new_global_state.mstate.depth = global_state.mstate.depth + 1 new_global_state.mstate.constraints = copy(global_state.mstate.constraints) @@ -968,7 +968,7 @@ class Instruction: environment.code = callee_account.code environment.calldata = call_data - new_global_state = GlobalState(global_state.accounts, environment, MachineState(gas)) + new_global_state = GlobalState(global_state.accounts, environment, None, MachineState(gas)) new_global_state.mstate.depth = global_state.mstate.depth + 1 new_global_state.mstate.constraints = copy(global_state.mstate.constraints) From 09c41deb6370ffdb548e9df04d7722e55b38c39b Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Sat, 7 Jul 2018 22:43:01 +0200 Subject: [PATCH 47/62] Fix node issue --- mythril/laser/ethereum/instructions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index 72451f48..56456fdd 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -913,7 +913,7 @@ class Instruction: value, environment.origin, calldata_type=call_data_type) - new_global_state = GlobalState(global_state.accounts, callee_environment, None, MachineState(gas)) + new_global_state = GlobalState(global_state.accounts, callee_environment, global_state.node, MachineState(gas)) new_global_state.mstate.depth = global_state.mstate.depth + 1 new_global_state.mstate.constraints = copy(global_state.mstate.constraints) return [global_state] @@ -940,7 +940,7 @@ class Instruction: environment.caller = environment.address environment.calldata = call_data - new_global_state = GlobalState(global_state.accounts, environment, None, MachineState(gas)) + new_global_state = GlobalState(global_state.accounts, environment, global_state.node, MachineState(gas)) new_global_state.mstate.depth = global_state.mstate.depth + 1 new_global_state.mstate.constraints = copy(global_state.mstate.constraints) @@ -968,7 +968,7 @@ class Instruction: environment.code = callee_account.code environment.calldata = call_data - new_global_state = GlobalState(global_state.accounts, environment, None, MachineState(gas)) + new_global_state = GlobalState(global_state.accounts, environment, global_state.node, MachineState(gas)) new_global_state.mstate.depth = global_state.mstate.depth + 1 new_global_state.mstate.constraints = copy(global_state.mstate.constraints) From 7c638272cbe936ba40cea9d0b5552e0edebeb6aa Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Sat, 7 Jul 2018 23:12:49 +0200 Subject: [PATCH 48/62] Reverse comparison --- mythril/laser/ethereum/call.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mythril/laser/ethereum/call.py b/mythril/laser/ethereum/call.py index 58eafed3..63d09837 100644 --- a/mythril/laser/ethereum/call.py +++ b/mythril/laser/ethereum/call.py @@ -36,7 +36,7 @@ def get_call_parameters(global_state, dynamic_loader, with_value=False): callee_account = None call_data, call_data_type = get_call_data(global_state, meminstart, meminsz, False) - if int(callee_address, 16) >= 5 or int(callee_address) != 0: + if int(callee_address, 16) >= 5 or int(callee_address) == 0: call_data, call_data_type = get_call_data(global_state, meminstart, meminsz) callee_account = get_callee_account(global_state, callee_address, dynamic_loader) From 83c5eca66bc82ca500e6fb75e7de66c34559f734 Mon Sep 17 00:00:00 2001 From: tintinweb Date: Tue, 3 Jul 2018 23:05:24 +0200 Subject: [PATCH 49/62] added online lookup for signature hashes via 4bytes.directory --- mythril/disassembler/disassembly.py | 26 +++-------- mythril/support/signatures.py | 67 +++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 20 deletions(-) diff --git a/mythril/disassembler/disassembly.py b/mythril/disassembler/disassembly.py index 787f297f..b7414529 100644 --- a/mythril/disassembler/disassembly.py +++ b/mythril/disassembler/disassembly.py @@ -1,6 +1,5 @@ from mythril.ether import asm,util -import os -import json +from mythril.support.signatures import Signatures import logging @@ -13,21 +12,11 @@ class Disassembly: self.addr_to_func = {} self.bytecode = code + signatures = Signatures(enable_online_lookkup=True) # control if you want to have online sighash lookups try: - mythril_dir = os.environ['MYTHRIL_DIR'] - except KeyError: - mythril_dir = os.path.join(os.path.expanduser('~'), ".mythril") - - # Load function signatures - - signatures_file = os.path.join(mythril_dir, 'signatures.json') - - if not os.path.exists(signatures_file): - logging.info("Missing function signature file. Resolving of function names disabled.") - signatures = {} - else: - with open(signatures_file) as f: - signatures = json.load(f) + signatures.open() # open from default locations + except FileNotFoundError: + logging.info("Missing function signature file. Resolving of function names from disabled.") # Parse jump table & resolve function names @@ -36,7 +25,7 @@ class Disassembly: for i in jmptable_indices: func_hash = self.instruction_list[i]['argument'] try: - func_name = signatures[func_hash] + func_name = signatures.get(func_hash) # tries local cache, file and optional online lookup except KeyError: func_name = "_function_" + func_hash @@ -49,8 +38,5 @@ class Disassembly: except: continue - - def get_easm(self): - return asm.instruction_list_to_easm(self.instruction_list) diff --git a/mythril/support/signatures.py b/mythril/support/signatures.py index 0431dd21..5fb21737 100644 --- a/mythril/support/signatures.py +++ b/mythril/support/signatures.py @@ -1,7 +1,17 @@ import re +import os +import logging +import json from ethereum import utils +try: + # load if available but do not fail + import ethereum_input_decoder +except ImportError: + ethereum_input_decoder = None + +# TODO: tintinweb: move this and signature functionality from mythril.py to class Signatures to have one single interface. def add_signatures_from_file(file, sigs={}): funcs = [] @@ -42,3 +52,60 @@ def add_signatures_from_file(file, sigs={}): signature = re.sub(r'\s', '', signature) sigs["0x" + utils.sha3(signature)[:4].hex()] = signature + + +class Signatures(object): + + def __init__(self, enable_online_lookkup=True): + self.signatures = {} # signatures in-mem cache + self.enable_online_lookup =enable_online_lookkup # enable online funcsig resolving + + def open(self, path=None): + 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') + + if not os.path.exists(path): + raise FileNotFoundError("Missing function signature file. Resolving of function names disabled.") + + with open(path) as f: + sigs = json.load(f) + + # normalize it to {sighash:list(signatures,...)} + for sighash,funcsig in sigs.items(): + self.signatures.setdefault(sighash, []) + self.signatures[sighash].append(funcsig) + + return self + + def get(self, sighash): + """ + get a function signature for a sighash + 1) try local cache + 2) try online lookup + :param sighash: + :return: list of function signatures + """ + if not self.signatures.get(sighash) and self.enable_online_lookup: + self.signatures[sighash] = Signatures.lookup_online(sighash) # might return multiple sigs + return self.signatures.get(sighash) + + + @staticmethod + def lookup_online(sighash): + """ + Lookup function signatures from 4bytes.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 s: function signature as hexstr + :return: a list of possible function signatures for this hash + """ + if not ethereum_input_decoder: + return None + return list(ethereum_input_decoder.decoder.FourByteDirectory.lookup_signatures(sighash)) From cdd27383970244d943a7de89d8c58d0910e95702 Mon Sep 17 00:00:00 2001 From: tintinweb Date: Tue, 3 Jul 2018 23:12:48 +0200 Subject: [PATCH 50/62] result to online lookup can be ambiguous. use first item and note this in disassembly --- mythril/disassembler/disassembly.py | 14 +++++++++++--- mythril/support/signatures.py | 13 ++++++++----- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/mythril/disassembler/disassembly.py b/mythril/disassembler/disassembly.py index b7414529..de1c9bad 100644 --- a/mythril/disassembler/disassembly.py +++ b/mythril/disassembler/disassembly.py @@ -12,11 +12,11 @@ class Disassembly: self.addr_to_func = {} self.bytecode = code - signatures = Signatures(enable_online_lookkup=True) # control if you want to have online sighash lookups + signatures = Signatures(enable_online_lookup=True) # control if you want to have online sighash lookups try: signatures.open() # open from default locations except FileNotFoundError: - logging.info("Missing function signature file. Resolving of function names from disabled.") + logging.info("Missing function signature file. Resolving of function names from signature file disabled.") # Parse jump table & resolve function names @@ -25,7 +25,15 @@ class Disassembly: for i in jmptable_indices: func_hash = self.instruction_list[i]['argument'] try: - func_name = signatures.get(func_hash) # tries local cache, file and optional online lookup + # tries local cache, file and optional online lookup + # may return more than one function signature. since we cannot probe for the correct one we'll use the first + func_names = signatures.get(func_hash) + if len(func_names) > 1: + # ambigious result + func_name = "**ambiguous** %s"%func_names[0] # return first hit but note that result was ambiguous + else: + # only one item + func_name = func_names[0] except KeyError: func_name = "_function_" + func_hash diff --git a/mythril/support/signatures.py b/mythril/support/signatures.py index 5fb21737..04fde734 100644 --- a/mythril/support/signatures.py +++ b/mythril/support/signatures.py @@ -56,9 +56,9 @@ def add_signatures_from_file(file, sigs={}): class Signatures(object): - def __init__(self, enable_online_lookkup=True): + def __init__(self, enable_online_lookup=True): self.signatures = {} # signatures in-mem cache - self.enable_online_lookup =enable_online_lookkup # enable online funcsig resolving + self.enable_online_lookup =enable_online_lookup # enable online funcsig resolving def open(self, path=None): if not path: @@ -91,14 +91,17 @@ class Signatures(object): :return: list of function signatures """ if not self.signatures.get(sighash) and self.enable_online_lookup: - self.signatures[sighash] = Signatures.lookup_online(sighash) # might return multiple sigs - return self.signatures.get(sighash) + funcsigs = Signatures.lookup_online(sighash) # might return multiple sigs + if funcsigs: + # only store if we get at least one result + self.signatures[sighash] = funcsigs + return self.signatures[sighash] # raise keyerror @staticmethod def lookup_online(sighash): """ - Lookup function signatures from 4bytes.directory. + 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 From 73efcffde213e1f17aec220611b1d6ac58a38d7d Mon Sep 17 00:00:00 2001 From: tintinweb Date: Wed, 4 Jul 2018 19:55:11 +0200 Subject: [PATCH 51/62] rebase off master refactor function signature handling -> one single class to handle signatures -> Signatures().get(sighash) and Signatures()[sighash] interfaces -> added simple file locking mechanism ref #294 -> fix signature caching (avoid looking up same hash multiple times) --- mythril/disassembler/disassembly.py | 11 +- mythril/mythril.py | 58 +++---- mythril/support/signatures.py | 232 ++++++++++++++++++++++------ 3 files changed, 210 insertions(+), 91 deletions(-) diff --git a/mythril/disassembler/disassembly.py b/mythril/disassembler/disassembly.py index de1c9bad..a20d4ffe 100644 --- a/mythril/disassembler/disassembly.py +++ b/mythril/disassembler/disassembly.py @@ -1,9 +1,9 @@ from mythril.ether import asm,util -from mythril.support.signatures import Signatures +from mythril.support.signatures import SignatureDb import logging -class Disassembly: +class Disassembly(object): def __init__(self, code): self.instruction_list = asm.disassemble(util.safe_decode(code)) @@ -12,7 +12,7 @@ class Disassembly: self.addr_to_func = {} self.bytecode = code - signatures = Signatures(enable_online_lookup=True) # control if you want to have online sighash lookups + signatures = SignatureDb(enable_online_lookup=True) # control if you want to have online sighash lookups try: signatures.open() # open from default locations except FileNotFoundError: @@ -30,7 +30,7 @@ class Disassembly: func_names = signatures.get(func_hash) if len(func_names) > 1: # ambigious result - func_name = "**ambiguous** %s"%func_names[0] # return first hit but note that result was ambiguous + func_name = "**ambiguous** %s" % func_names[0] # return first hit but note that result was ambiguous else: # only one item func_name = func_names[0] @@ -46,5 +46,8 @@ class Disassembly: except: continue + signatures.write() # store resolved signatures (potentially resolved online) + def get_easm(self): + # todo: tintinweb - print funcsig resolved data from self.addr_to_func? return asm.instruction_list_to_easm(self.instruction_list) diff --git a/mythril/mythril.py b/mythril/mythril.py index a72a82d7..0a85366b 100644 --- a/mythril/mythril.py +++ b/mythril/mythril.py @@ -78,8 +78,6 @@ class Mythril(object): mythril.get_state_variable_from_storage(args) """ - - def __init__(self, solv=None, solc_args=None, dynld=False): @@ -88,7 +86,17 @@ class Mythril(object): self.dynld = dynld self.mythril_dir = self._init_mythril_dir() - self.signatures_file, self.sigs = self._init_signatures() + + self.sigs = signatures.SignatureDb() + try: + self.sigs.open() # tries mythril_dir/signatures.json by default (provide path= arg to make this configurable) + except FileNotFoundError as fnfe: + logging.info( + "No signature database found. Creating database if sigs are loaded in: " + self.sigs.signatures_file + "\n" + + "Consider replacing it with the pre-initialized database at https://raw.githubusercontent.com/ConsenSys/mythril/master/signatures.json") + except json.JSONDecodeError as jde: + raise CriticalError("Invalid JSON in signatures file " + self.sigs.signatures_file + "\n" + str(jde)) + self.solc_binary = self._init_solc_binary(solv) self.leveldb_dir = self._init_config() @@ -110,33 +118,6 @@ class Mythril(object): os.mkdir(mythril_dir) return mythril_dir - def _init_signatures(self): - - # If no function signature file exists, create it. Function signatures from Solidity source code are added automatically. - - signatures_file = os.path.join(self.mythril_dir, 'signatures.json') - - sigs = {} - if not os.path.exists(signatures_file): - logging.info("No signature database found. Creating empty database: " + signatures_file + "\n" + - "Consider replacing it with the pre-initialized database at https://raw.githubusercontent.com/ConsenSys/mythril/master/signatures.json") - with open(signatures_file, 'a') as f: - json.dump({}, f) - - with open(signatures_file) as f: - try: - sigs = json.load(f) - except json.JSONDecodeError as e: - raise CriticalError("Invalid JSON in signatures file " + signatures_file + "\n" + str(e)) - return signatures_file, sigs - - def _update_signatures(self, jsonsigs): - # Save updated function signatures - with open(self.signatures_file, 'w') as f: - json.dump(jsonsigs, f) - - self.sigs = jsonsigs - def _init_config(self): # If no config file exists, create it. Default LevelDB path is specified based on OS @@ -300,27 +281,32 @@ class Mythril(object): file = os.path.expanduser(file) try: - signatures.add_signatures_from_file(file, self.sigs) - self._update_signatures(self.sigs) + # import signatures from solidity source + with open(file, encoding="utf-8") as f: + self.sigs.import_from_solidity_source(f.read()) + contract = SolidityContract(file, contract_name, solc_args=self.solc_args) logging.info("Analyzing contract %s:%s" % (file, contract.name)) except FileNotFoundError: - raise CriticalError("Input file not found: " + file) + raise CriticalError("Input file not found: " + file) except CompilerError as e: - raise CriticalError(e) + raise CriticalError(e) except NoContractFoundError: logging.info("The file " + file + " does not contain a compilable contract.") else: self.contracts.append(contract) contracts.append(contract) + # Save updated function signatures + self.sigs.write() # dump signatures to disk (previously opened file or default location) + return address, contracts def dump_statespace(self, contract, address=None, max_depth=12): sym = SymExecWrapper(contract, address, - dynloader=DynLoader(self.eth) if self.dynld else None, - max_depth=max_depth) + dynloader=DynLoader(self.eth) if self.dynld else None, + max_depth=max_depth) return get_serializable_statespace(sym) diff --git a/mythril/support/signatures.py b/mythril/support/signatures.py index 04fde734..af86abc4 100644 --- a/mythril/support/signatures.py +++ b/mythril/support/signatures.py @@ -1,66 +1,90 @@ +#!/usr/bin/env python3 +# -*- coding: UTF-8 -*- +"""mythril.py: Function Signature Database +""" import re import os -import logging import json +import time +import pathlib +import logging from ethereum import utils +# todo: tintinweb - make this a normal requirement? (deps: eth-abi and requests, both already required by mythril) try: # load if available but do not fail import ethereum_input_decoder + from ethereum_input_decoder.decoder import FourByteDirectoryOnlineLookupError except ImportError: + # fake it :) ethereum_input_decoder = None + FourByteDirectoryOnlineLookupError = Exception -# TODO: tintinweb: move this and signature functionality from mythril.py to class Signatures to have one single interface. -def add_signatures_from_file(file, sigs={}): - - funcs = [] - - with open(file, encoding="utf-8") as f: - - code = f.read() - - funcs = re.findall(r'function[\s]+(\w+\([^\)]*\))', code, re.DOTALL) - - for f in funcs: +class SimpleFileLock(object): + # todo: replace with something more reliable. this is a quick shot on concurrency and might not work in all cases - f = re.sub(r'[\n]', '', f) + def __init__(self, path): + self.path = path + self.lockfile = pathlib.Path("%s.lck" % path) + self.locked = False - m = re.search(r'^([A-Za-z0-9_]+)', f) + def aquire(self, timeout=5): + if self.locked: + raise Exception("SimpleFileLock: lock already aquired") - if (m): - - signature = m.group(1) - - m = re.search(r'\((.*)\)', f) - - _args = m.group(1).split(",") - - types = [] + t_end = time.time()+timeout + while time.time() < t_end: + # try to aquire lock + try: + self.lockfile.touch(mode=0o0000, exist_ok=False) # touch the lockfile + # lockfile does not exist. we have a lock now + self.locked = True + return + except FileExistsError as fee: + # check if lockfile date exceeds age and cleanup lock + if time.time() > self.lockfile.stat().st_mtime + 60 * 5: + self.release(force=True) # cleanup old lockfile > 5mins - for arg in _args: + time.sleep(0.5) # busywait is evil + continue - _type = arg.lstrip().split(" ")[0] - if _type == "uint": - _type = "uint256" + raise Exception("SimpleFileLock: timeout hit. failed to aquire lock: %s"% (time.time()-self.lockfile.stat().st_mtime)) - types.append(_type) + def release(self, force=False): + if not force and not self.locked: + raise Exception("SimpleFileLock: aquire lock first") - typelist = ",".join(types) - signature += "(" + typelist + ")" + try: + self.lockfile.unlink() # might throw if we force unlock and the file gets removed in the meantime. TOCTOU + except FileNotFoundError as fnfe: + logging.warning("SimpleFileLock: release(force=%s) on unavailable file. race? %r" % (force, fnfe)) - signature = re.sub(r'\s', '', signature) + self.locked = False - sigs["0x" + utils.sha3(signature)[:4].hex()] = signature -class Signatures(object): +class SignatureDb(object): def __init__(self, enable_online_lookup=True): + """ + Constr + :param enable_online_lookup: enable onlien signature hash lookup + """ self.signatures = {} # signatures in-mem cache - self.enable_online_lookup =enable_online_lookup # enable online funcsig resolving + self.signatures_file = None + self.signatures_file_lock = None + self.enable_online_lookup = enable_online_lookup # enable online funcsig resolving + self.online_lookup_miss = set() # temporarily track misses from onlinedb to avoid requesting the same non-existent sighash multiple times + self.online_directory_unavailable_until = 0 # flag the online directory as unavailable for some time def open(self, path=None): + """ + Open a function signature db from json file + + :param path: specific path to signatures.json; default mythril location if not specified + :return: self + """ if not path: # try default locations try: @@ -69,46 +93,152 @@ class Signatures(object): mythril_dir = os.path.join(os.path.expanduser('~'), ".mythril") path = os.path.join(mythril_dir, 'signatures.json') + self.signatures_file = path # store early to allow error handling to access the place we tried to load the file + if not os.path.exists(path): + logging.debug("Signatures: file not found: %s" % path) raise FileNotFoundError("Missing function signature file. Resolving of function names disabled.") - with open(path) as f: + self.signatures_file_lock = SimpleFileLock(self.signatures_file) # lock file to prevent concurrency issues + self.signatures_file_lock.aquire() # try to aquire it within the next 10s + + with open(path, 'r') as f: sigs = json.load(f) + self.signatures_file_lock.release() # release lock + # normalize it to {sighash:list(signatures,...)} - for sighash,funcsig in sigs.items(): + for sighash, funcsig in sigs.items(): + if isinstance(funcsig, list): + self.signatures = sigs # keep original todo: tintinweb - super hacky. make sure signatures.json is initially in correct format fixme + break # already normalized self.signatures.setdefault(sighash, []) self.signatures[sighash].append(funcsig) return self - def get(self, sighash): + def write(self, path=None, sync=True): + """ + 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 + """ + path = path or self.signatures_file + self.signatures_file_lock = SimpleFileLock(path) # lock file to prevent concurrency issues + self.signatures_file_lock.aquire() # try to aquire it within the next 10s + + if sync and os.path.exists(path): + # reload and save if file exists + with open(path, 'r') as f: + sigs = json.load(f) + + sigs.update(self.signatures) # reload file and merge cached sigs into what we load from file + self.signatures = sigs + + with open(path, 'w') as f: + json.dump(self.signatures, f) + + self.signatures_file_lock.release() + return self + + def get(self, sighash, timeout=2): """ get a function signature for a sighash 1) try local cache - 2) try online lookup - :param sighash: - :return: list of function signatures - """ - if not self.signatures.get(sighash) and self.enable_online_lookup: - funcsigs = Signatures.lookup_online(sighash) # might return multiple sigs - if funcsigs: - # only store if we get at least one result - self.signatures[sighash] = funcsigs + 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 + """ + if not sighash.startswith("0x"): + sighash = "0x%s" % sighash # normalize sighash format + + if 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: + # 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) + try: + funcsigs = SignatureDb.lookup_online(sighash, timeout=timeout) # might return multiple sigs + if funcsigs: + # only store if we get at least one result + self.signatures[sighash] = funcsigs + else: + # miss + self.online_lookup_miss.add(sighash) + 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) return self.signatures[sighash] # raise keyerror + 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, code): + """ + Import Function Signatures from solidity source files + :param code: solidity source code + :return: self + """ + self.signatures.update(SignatureDb.parse_function_signatures_from_solidity_source(code)) + return self @staticmethod - def lookup_online(sighash): + def lookup_online(sighash, timeout=None, proxies=None): """ 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 s: function signature as hexstr - :return: a list of possible function signatures for this hash + :param sighash: 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 list(ethereum_input_decoder.decoder.FourByteDirectory.lookup_signatures(sighash)) + return list(ethereum_input_decoder.decoder.FourByteDirectory.lookup_signatures(sighash, + timeout=timeout, + proxies=proxies)) + + @staticmethod + def parse_function_signatures_from_solidity_source(code): + """ + Parse solidity sourcecode for function signatures and return the signature hash and function signature + :param code: solidity source code + :return: dictionary {sighash: function_signature} + """ + sigs = {} + + funcs = re.findall(r'function[\s]+(.*?\))', code, re.DOTALL) + for f in funcs: + f = re.sub(r'[\n]', '', f) + m = re.search(r'^([A-Za-z0-9_]+)', f) + + if m: + signature = m.group(1) + m = re.search(r'\((.*)\)', f) + _args = m.group(1).split(",") + types = [] + + for arg in _args: + _type = arg.lstrip().split(" ")[0] + + if _type == "uint": + _type = "uint256" + + types.append(_type) + + typelist = ",".join(types) + signature += "(" + typelist + ")" + signature = re.sub(r'\s', '', signature) + sigs["0x" + utils.sha3(signature)[:4].hex()] = signature + + logging.debug("Signatures: parse soldiity found %d signatures" % len(sigs)) + return sigs From 6851754059888d46bd8484a87fcfd82436df1ed3 Mon Sep 17 00:00:00 2001 From: tintinweb Date: Fri, 6 Jul 2018 18:00:47 +0200 Subject: [PATCH 52/62] remove unnecessary comment (todo) reuse SimpleFileLock instance if already set Note: requests.[get|post|request](.., proxies=None) should be using the default proxy settings (env) Note: signature db path selection logic is currently not matching mythril._init_config (but myhtril._init_mythril_dir) --- mythril/support/signatures.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mythril/support/signatures.py b/mythril/support/signatures.py index af86abc4..e1582b68 100644 --- a/mythril/support/signatures.py +++ b/mythril/support/signatures.py @@ -99,7 +99,7 @@ class SignatureDb(object): logging.debug("Signatures: file not found: %s" % path) raise FileNotFoundError("Missing function signature file. Resolving of function names disabled.") - self.signatures_file_lock = SimpleFileLock(self.signatures_file) # lock file to prevent concurrency issues + self.signatures_file_lock = self.signatures_file_lock or SimpleFileLock(self.signatures_file) # lock file to prevent concurrency issues self.signatures_file_lock.aquire() # try to aquire it within the next 10s with open(path, 'r') as f: @@ -110,7 +110,7 @@ class SignatureDb(object): # normalize it to {sighash:list(signatures,...)} for sighash, funcsig in sigs.items(): if isinstance(funcsig, list): - self.signatures = sigs # keep original todo: tintinweb - super hacky. make sure signatures.json is initially in correct format fixme + self.signatures = sigs break # already normalized self.signatures.setdefault(sighash, []) self.signatures[sighash].append(funcsig) @@ -126,7 +126,7 @@ class SignatureDb(object): :return: self """ path = path or self.signatures_file - self.signatures_file_lock = SimpleFileLock(path) # lock file to prevent concurrency issues + self.signatures_file_lock = self.signatures_file_lock or SimpleFileLock(path) # lock file to prevent concurrency issues self.signatures_file_lock.aquire() # try to aquire it within the next 10s if sync and os.path.exists(path): From 9b5bd6de777e57d48af26f5a26d04f86e8690c93 Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Sun, 8 Jul 2018 14:56:24 +0200 Subject: [PATCH 53/62] Use base 16 --- mythril/laser/ethereum/call.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mythril/laser/ethereum/call.py b/mythril/laser/ethereum/call.py index 63d09837..aee2f30c 100644 --- a/mythril/laser/ethereum/call.py +++ b/mythril/laser/ethereum/call.py @@ -36,7 +36,7 @@ def get_call_parameters(global_state, dynamic_loader, with_value=False): callee_account = None call_data, call_data_type = get_call_data(global_state, meminstart, meminsz, False) - if int(callee_address, 16) >= 5 or int(callee_address) == 0: + if int(callee_address, 16) >= 5 or int(callee_address, 16) == 0: call_data, call_data_type = get_call_data(global_state, meminstart, meminsz) callee_account = get_callee_account(global_state, callee_address, dynamic_loader) From deb98df7c5d4bd6467f06073ddb0f545f15154c6 Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Sun, 8 Jul 2018 23:17:26 +0530 Subject: [PATCH 54/62] return a list for blockhash function --- mythril/laser/ethereum/instructions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index a7d9521f..949de064 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -529,7 +529,7 @@ class Instruction: logging.info("error accessing contract storage due to: " + str(e)) state.stack.append(BitVec("extcodesize_" + str(addr), 256)) return [global_state] - + if code is None: state.stack.append(0) else: @@ -555,7 +555,7 @@ class Instruction: state = global_state.mstate blocknumber = state.stack.pop() state.stack.append(BitVec("blockhash_block_" + str(blocknumber), 256)) - return global_state + return [global_state] @instruction def coinbase_(self, global_state): From ac818454ab9bee4b762c035103718e0e95ca39c3 Mon Sep 17 00:00:00 2001 From: Konrad Weiss Date: Mon, 9 Jul 2018 08:48:58 +0200 Subject: [PATCH 55/62] Correcting the symetric variable formatting for calldatacopy --- mythril/laser/ethereum/instructions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index 89844986..1fbce31f 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -385,20 +385,20 @@ class Instruction: dstart_sym = False try: dstart = util.get_concrete_int(op1) - dstart_sym = True # FIXME: broad exception catch except: logging.debug("Unsupported symbolic calldata offset in CALLDATACOPY") dstart = simplify(op1) + dstart_sym = True size_sym = False try: size = util.get_concrete_int(op2) - size_sym = True # FIXME: broad exception catch except: logging.debug("Unsupported symbolic size in CALLDATACOPY") size = simplify(op2) + size_sym = True if dstart_sym or size_sym: state.mem_extend(mstart, 1) From f873d041ee459d78d16a560a3e6dceae1728c071 Mon Sep 17 00:00:00 2001 From: Konrad Weiss Date: Tue, 10 Jul 2018 12:50:33 +0200 Subject: [PATCH 56/62] Revert "Merge branch 'master' of https://github.com/LoCorVin/mythril into feature/reformatsymindices" This reverts commit a8de6b1a55d1e2cf12ae5710b6096df1db94b8f5, reversing changes made to ac818454ab9bee4b762c035103718e0e95ca39c3. --- annotationWrapper.py | 69 -------- mythril/analysis/modules/build_traces.py | 94 ----------- mythril/analysis/modules/dummy.py | 41 ----- mythril/analysis/symbolic.py | 9 +- mythril/ether/soliditycontract.py | 3 - mythril/ether/util.py | 2 +- mythril/mythril.py | 8 - mythril/solidnotary/__init__.py | 0 mythril/solidnotary/abitypemapping.py | 0 mythril/solidnotary/annotation.py | 28 --- mythril/solidnotary/calldata.py | 150 ----------------- mythril/solidnotary/solidnotary.py | 72 -------- mythril/solidnotary/transactiontrace.py | 206 ----------------------- mythril/solidnotary/z3utility.py | 19 --- 14 files changed, 3 insertions(+), 698 deletions(-) delete mode 100644 annotationWrapper.py delete mode 100644 mythril/analysis/modules/build_traces.py delete mode 100644 mythril/analysis/modules/dummy.py delete mode 100644 mythril/solidnotary/__init__.py delete mode 100644 mythril/solidnotary/abitypemapping.py delete mode 100644 mythril/solidnotary/annotation.py delete mode 100644 mythril/solidnotary/calldata.py delete mode 100644 mythril/solidnotary/solidnotary.py delete mode 100644 mythril/solidnotary/transactiontrace.py delete mode 100644 mythril/solidnotary/z3utility.py diff --git a/annotationWrapper.py b/annotationWrapper.py deleted file mode 100644 index 02337e84..00000000 --- a/annotationWrapper.py +++ /dev/null @@ -1,69 +0,0 @@ -""" - Parses the files for annotations to extract the information and do some transformations on the code. - On return the Annotation information is used to change mythrils output regarding the changes code pieces - -""" - -from glob import glob -import re, sys -import json - -newlines = ["\r\n", "\r", "\n"] - -def find_all(a_str, sub): - start = 0 - while True: - start = a_str.find(sub, start) - if start == -1: - return - yield start - start += len(sub) - -def count_elements(source, elements): - ret = 0 - for element in elements: - ret += source.count(element) - return ret - - -def replace_index(text, toReplace, replacement, index): - return text[:index] + replacement + text[(index + len(toReplace)):] - -""" - Here it might be better to split annotations into the containing constraint an the prefix and sufix -""" -def parse_annotation_info(filedata): - annotations = [] - for inv in re.findall(r'//invariant\(([^\)]+)\)(\r\n|\r|\n)', filedata): - match_inv = "//invariant(" + inv[0] + ")" - for pos in find_all(filedata, match_inv + inv[1]): - line = count_elements(filedata[:pos], newlines) + 1 - col = pos - max(map(lambda x: filedata[:pos].rfind(x), newlines)) - annotations.append((pos, line, col, '//invariant(', inv[0], ")", inv[1])) - return set(annotations) - - -def read_write_file(filename): - with open(filename, 'r') as file : - filedata = file.read() - - annotations = parse_annotation_info(filedata) - - annotations = sorted(list(annotations), key=lambda x: x[0], reverse=True) - for annotation in annotations: - filedata = replace_index(filedata, annotation[3] + annotation[4] + annotation[5] + annotation[6], "assert(" - + annotation[4] + ");" + annotation[6], annotation[0]) - # Replace the target string - # filedata = filedata.replace('@ensure', '@invariant') - # filedata = filedata.replace('@invariant', '@ensure') - - with open(filename, 'w') as file: - file.write(filedata) - return annotations - -annot_map = {} - -for sol_file in glob("./*.sol"): - annot_map[sol_file] = read_write_file(sol_file) -json.dump(annot_map, sys.stdout) -print("#end annotations#") diff --git a/mythril/analysis/modules/build_traces.py b/mythril/analysis/modules/build_traces.py deleted file mode 100644 index 76519410..00000000 --- a/mythril/analysis/modules/build_traces.py +++ /dev/null @@ -1,94 +0,0 @@ -from mythril.analysis.report import Issue -from mythril.solidnotary.transactiontrace import TransactionTrace -from mythril.disassembler.disassembly import Disassembly -from laser.ethereum.svm import GlobalState, Account, Environment, MachineState, CalldataType -from z3 import BitVec -from mythril.analysis.symbolic import SymExecWrapper -from mythril.solidnotary.solidnotary import get_transaction_traces, get_construction_traces -from mythril.solidnotary.z3utility import are_satisfiable -import logging -from mythril.solidnotary.calldata import get_minimal_constructor_param_encoding_len, abi_json_to_abi - - -''' - Build execution traces from the statespace -''' - -def print_obj(obj): - print() - print(type(obj)) - print(obj) - print(dir(obj)) - print(obj.decl()) - print(obj.params()) - print(obj.children()) - print() - -def get_constr_glbstate(contract, address): - - mstate = MachineState(gas=10000000) - - minimal_const_byte_len = get_minimal_constructor_param_encoding_len(abi_json_to_abi(contract.abi)) - - # better would be to append symbolic params to the bytecode such that the codecopy instruction that copies the - # params into memory takes care of placing them onto the memory with the respective size. - for i in range(int(minimal_const_byte_len / 32)): - mstate.mem_extend(128 + 32 * i, 32) - mstate.memory.insert(128 + 32 * i, BitVec('calldata_' + contract.name + '_' + str(i * 32), 256)) - - # Todo Replace pure placement of enough symbolic 32 Byte-words with placement of symbolic variables that contain - # the name of the solidity variables - - accounts = {address: Account(address, contract.disassembly, contract_name=contract.name)} - - environment = Environment( - accounts[address], - BitVec("caller", 256), - [], - BitVec("gasprice", 256), - BitVec("callvalue", 256), - BitVec("origin", 256), - calldata_type=CalldataType.SYMBOLIC, - ) - - # Todo find source for account info, maybe the std statespace? - - return GlobalState(accounts, environment, mstate) - - -def execute(statespace): - - logging.debug("Executing module: Transaction End") - - constructor_trace = {} - - if hasattr(statespace, "sym_constr"): - sym_exe_tuple = statespace.sym_constr - glbstate = get_constr_glbstate(sym_exe_tuple[0], sym_exe_tuple[1]) - sym_exe_tuple = statespace.sym_constr + (glbstate,) - constr_statespace = SymExecWrapper(*sym_exe_tuple) - constructor_trace = get_construction_traces(constr_statespace) # Todo the traces here should not contain references to storages anymore - for t in constructor_trace: - t.pp_trace() - - traces = get_transaction_traces(statespace) - for trace in constructor_trace: - comp_trace_lvls = trace.apply_up_to_trace_levels(traces, 3) - for trace_lvl in comp_trace_lvls: - for t in trace_lvl: - t.pp_trace() - - # for trace in traces: - # trace.pp_trace() - - #print("==== Second level traces ====") - #for trace in traces: - # comp_trace_lvls = trace.apply_up_to_trace_levels(traces, 1) - #for trace_lvl in range(len(comp_trace_lvls)): - # print("\nTrace level: " + str(trace_lvl)) - #for comp_trace in comp_trace_lvls[trace_lvl]: - # print(comp_trace.storage) - # for k, s in comp_trace.storage.items(): - # print_obj(s) - - return [] diff --git a/mythril/analysis/modules/dummy.py b/mythril/analysis/modules/dummy.py deleted file mode 100644 index 44b0879f..00000000 --- a/mythril/analysis/modules/dummy.py +++ /dev/null @@ -1,41 +0,0 @@ -from mythril.analysis.report import Issue -import logging -from z3 import * -from mythril.solidnotary.z3utility import simplify_constraints, are_satisfiable - - -''' - To print content of the statespace after it was build. -''' - -def print_obj(obj): - print() - print(obj) - print(type(obj)) - print(dir(obj)) - print() - - -def execute(statespace): - - logging.debug("Executing module: Transaction End") - - issues = [] - -# for k in statespace.nodes: -# node = statespace.nodes[k] - -# for state in node.states: - -# instruction = state.get_current_instruction() - -# if(instruction['opcode'] == "STOP"): -# print() - #print(state.environment.active_account.storage) - # print(state.mstate.constraints) - #simpl_const = simplify_constraints(state.mstate.constraints) - #print(simpl_const) - #print(are_satisfiable(simpl_const)) - # print("opc: {}, add: {} {}".format(instruction['opcode'], instruction['address'], instruction['argument'] if 'argument' in instruction else "")) - - return issues diff --git a/mythril/analysis/symbolic.py b/mythril/analysis/symbolic.py index 975c52bf..5d47c356 100644 --- a/mythril/analysis/symbolic.py +++ b/mythril/analysis/symbolic.py @@ -11,7 +11,7 @@ class SymExecWrapper: Wrapper class for the LASER Symbolic virtual machine. Symbolically executes the code and does a bit of pre-analysis for convenience. ''' - def __init__(self, contract, address, dynloader=None, max_depth=12, gblState=None): + def __init__(self, contract, address, dynloader=None, max_depth=12): account = svm.Account(address, contract.disassembly, contract_name=contract.name) @@ -19,12 +19,7 @@ class SymExecWrapper: self.laser = svm.LaserEVM(self.accounts, dynamic_loader=dynloader, max_depth=max_depth) - if not (gblState is None): - # Adding the ability to specify a custom global state, e.g. machine configuration from outside - node = self.laser._sym_exec(gblState) - self.laser.nodes[node.uid] = node - else: - self.laser.sym_exec(address) + self.laser.sym_exec(address) self.nodes = self.laser.nodes self.edges = self.laser.edges diff --git a/mythril/ether/soliditycontract.py b/mythril/ether/soliditycontract.py index 95b863b5..919b3d30 100644 --- a/mythril/ether/soliditycontract.py +++ b/mythril/ether/soliditycontract.py @@ -51,7 +51,6 @@ class SolidityContract(ETHContract): if filename == input_file and name == _name: name = name code = contract['bin-runtime'] - abi = contract['abi'] creation_code = contract['bin'] srcmap = contract['srcmap-runtime'].split(";") has_contract = True @@ -66,7 +65,6 @@ class SolidityContract(ETHContract): if filename == input_file and len(contract['bin-runtime']): name = name code = contract['bin-runtime'] - abi = contract['abi'] creation_code = contract['bin'] srcmap = contract['srcmap-runtime'].split(";") has_contract = True @@ -91,7 +89,6 @@ class SolidityContract(ETHContract): lineno = self.solidity_files[idx].data[0:offset].count('\n') + 1 self.mappings.append(SourceMapping(idx, offset, length, lineno)) - self.abi = abi super().__init__(code, creation_code, name=name) diff --git a/mythril/ether/util.py b/mythril/ether/util.py index 467c37df..e01cae28 100644 --- a/mythril/ether/util.py +++ b/mythril/ether/util.py @@ -18,7 +18,7 @@ def safe_decode(hex_encoded_string): def get_solc_json(file, solc_binary="solc", solc_args=None): - cmd = [solc_binary, "--combined-json", "bin,bin-runtime,srcmap-runtime,abi", '--allow-paths', "."] + cmd = [solc_binary, "--combined-json", "bin,bin-runtime,srcmap-runtime", '--allow-paths', "."] if solc_args: cmd.extend(solc_args.split(" ")) diff --git a/mythril/mythril.py b/mythril/mythril.py index e2bb6c37..b63cc137 100644 --- a/mythril/mythril.py +++ b/mythril/mythril.py @@ -16,8 +16,6 @@ from solc.exceptions import SolcError import solc from configparser import ConfigParser import platform -from copy import deepcopy -from mythril.disassembler.disassembly import Disassembly from mythril.ether import util from mythril.ether.ethcontract import ETHContract @@ -344,12 +342,6 @@ class Mythril(object): dynloader=DynLoader(self.eth) if self.dynld else None, max_depth=max_depth) - - contr_to_const = deepcopy(contract) - contr_to_const.disassembly = Disassembly(contr_to_const.creation_code) - contr_to_const.code = contr_to_const.creation_code - sym.sym_constr = (contr_to_const, address, DynLoader(self.eth) if self.dynld else None, max_depth) - issues = fire_lasers(sym, modules) if type(contract) == SolidityContract: diff --git a/mythril/solidnotary/__init__.py b/mythril/solidnotary/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/mythril/solidnotary/abitypemapping.py b/mythril/solidnotary/abitypemapping.py deleted file mode 100644 index e69de29b..00000000 diff --git a/mythril/solidnotary/annotation.py b/mythril/solidnotary/annotation.py deleted file mode 100644 index cbf95ce1..00000000 --- a/mythril/solidnotary/annotation.py +++ /dev/null @@ -1,28 +0,0 @@ -from re import search - -class Annotation: - - def __init__(self, annstring, lineno, fileoffset): - self.annstring = annstring - - annotation = search(r'@(?P[^\{\}]*)(\{(?P.*)\})?', annstring) - if not annotation: - raise SyntaxError("{} is not a correct annotation".format(annstring)) - - self.aname = annotation['aname'] - self.acontent = annotation['acontent'] - self.lineno = lineno - self.length = len(annstring) - self.fileoffset = fileoffset - -class ContractAnnotation(Annotation): - pass - -class MemberAnnotation(Annotation): - pass - -class InlineAnnotation(Annotation): - pass - -class ContractAnnotation(Annotation): - pass diff --git a/mythril/solidnotary/calldata.py b/mythril/solidnotary/calldata.py deleted file mode 100644 index 27891271..00000000 --- a/mythril/solidnotary/calldata.py +++ /dev/null @@ -1,150 +0,0 @@ -from re import search -from mythril.exceptions import CompilerError -from subprocess import Popen, PIPE -import json -import sys - -class CalldataMap: - - def __init__(self, abi, solc_v): - pass - - def __init__(self, solidity_file, solc_v): - pass - -def get_minimal_param_encoding_len(abi_inputs): - len = 0 - return len - -def get_minimal_byte_enc_len(abi_obj): - if type(abi_obj) == list: - return sum([head_byte_length_min(t) for t in abi_obj]) + sum([tail_byte_length_min(t) for t in abi_obj]) - if type(abi_obj) == str: - stype = abi_obj - else: - stype = abi_obj['type'] - if stype == 'tuple': - return get_minimal_byte_enc_len(abi_obj['components']) - try: - match = search(r'(?P
.*)\[(?P[0-9]+)\]', stype)
-        pre = match['pre']
-        post = match['post']
-        return int(post) * get_minimal_byte_enc_len(pre)
-    except (KeyError, TypeError) as e:
-        pass
-    if stype.endswith("[]"):
-        return 32
-
-    if stype == "string":
-        return 32
-    elif stype == "bytes":
-        return 32 # 2 was the smallest observed value, remix did not allow specification of zero size bytes
-    elif [stype.startswith(prefix_type) for prefix_type in ["int", "uint", "address", "bool", "fixed", "ufixed", "bytes"]]:
-        return 32
-
-def head_byte_length_min(abi_obj):
-    if is_dynamic(abi_obj):
-        return 32
-    else:
-        return get_minimal_byte_enc_len(abi_obj)
-
-
-def tail_byte_length_min(abi_obj):
-    if is_dynamic(abi_obj):
-        return get_minimal_byte_enc_len(abi_obj)
-    else:
-        return 0
-
-def get_minimal_wsize(abi_obj):
-    stype = abi_obj['type']
-    if type(stype) == list:
-        return sum(list(map(lambda a: get_minimal_wsize(a), stype)))
-    if stype in ["bytes", "string"] or stype.endswith("[]"):
-        return True
-    if stype == 'tuple':
-        return True in [is_dynamic(elem) for elem in abi_obj['components']]
-    try:
-        match = search(r'(?P
.*)(?P\[[0-9]+\])', stype)
-        pre = match['pre']
-        # post = match['post']
-        return is_dynamic(pre)
-    except (KeyError | TypeError):
-        pass
-    return False
-
-
-def get_minimal_constructor_param_encoding_len(abi):
-    for spec in abi:
-        try:
-            if spec['type'] == 'constructor':
-                con_inputs = spec['inputs']
-                return get_minimal_byte_enc_len(con_inputs)
-        except KeyError:
-            print("ABI does not contain inputs for constructor")
-    return -1
-
-def get_minimal_constr_param_byte_length(filename, contract_name=None):
-    abi_decoded = get_solc_abi_json(filename)
-    return get_minimal_constructor_param_encoding_len(abi_decoded)
-
-def is_dynamic(abi_obj):
-    if type(abi_obj) == list:
-        return True in list(map(lambda a: is_dynamic(a), abi_obj))
-    if type(abi_obj) == str:
-        stype = abi_obj
-    else:
-        stype = abi_obj['type']
-    if stype in ["bytes", "string"] or stype.endswith("[]"):
-        return True
-    if stype == 'tuple':
-        return True in [is_dynamic(elem) for elem in abi_obj['components']]
-    try:
-        match = search(r'(?P
.*)(?P\[[0-9]+\])', stype)
-        pre = match['pre']
-        # post = match['post']
-        return is_dynamic(pre)
-    except (KeyError, TypeError) as e:
-        pass
-    return False
-
-def get_solc_abi_json(file, solc_binary="solc", solc_args=None):
-
-    cmd = [solc_binary, "--abi", '--allow-paths', "."]
-
-    if solc_args:
-        cmd.extend(solc_args.split(" "))
-
-    cmd.append(file)
-
-    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.")
-
-    out = stdout.decode("UTF-8")
-
-    if not len(out):
-        raise CompilerError("Compilation failed.")
-
-    out = out[out.index("["):]
-
-    print(out)
-
-    return json.loads(out)
-
-def abi_json_to_abi(abi_json):
-    return json.loads(abi_json)
-
-
-if __name__ == "__main__":
-    if len(sys.argv) > 1:
-        print("Size:")
-        print(get_minimal_constr_param_byte_length(sys.argv[1]))
-    else:
-        print("Error: No file specified")
diff --git a/mythril/solidnotary/solidnotary.py b/mythril/solidnotary/solidnotary.py
deleted file mode 100644
index 3a3091d2..00000000
--- a/mythril/solidnotary/solidnotary.py
+++ /dev/null
@@ -1,72 +0,0 @@
-import logging
-from mythril.solidnotary.transactiontrace import TransactionTrace
-from mythril.solidnotary.z3utility import are_satisfiable
-from laser.ethereum.svm import Environment, GlobalState, CalldataType
-from z3 import BitVec, simplify, is_false, is_bool, is_true, Solver, sat
-from copy import deepcopy
-
-
-class SolidNotary:
-
-    def __init__(self):
-        # Todo Parse Annotations and store them in an additional structure
-        # Todo receive a list of files or a file, these are modified for the analysis
-        pass
-
-    def notarize(self):
-        # Todo Instantiate an instance of Mythril, analyze and print the result
-        # Todo Find how they are storing results
-        pass
-
-def get_transaction_traces(statespace):
-    print("get_transaction_traces")
-
-    traces = []
-
-    for k in statespace.nodes:
-        node = statespace.nodes[k]
-        for state in node.states:
-            instruction = state.get_current_instruction()
-            # print("op: " + str(instruction['opcode']) + ((" " + instruction['argument']) if instruction['opcode'].startswith("PUSH") else "") + " stack: " + str(state.mstate.stack).replace("\n", "")+ " mem: " + str(state.mstate.memory).replace("\n", ""))
-            if instruction['opcode'] == "STOP":
-                if are_satisfiable(state.mstate.constraints):
-                    traces.append(TransactionTrace(state.environment.active_account.storage, state.mstate.constraints))
-    return traces
-
-def get_construction_traces(statespace):
-    print("get_constructor_traces")
-
-    traces = []
-
-    for k in statespace.nodes:
-        node = statespace.nodes[k]
-        for state in node.states:
-            instruction = state.get_current_instruction()
-
-            # print("op: " + str(instruction['opcode']) + ((" " + instruction['argument']) if instruction['opcode'].startswith("PUSH") else "") + " stack: " + str(state.mstate.stack).replace("\n", "")+ " mem: " + str(state.mstate.memory).replace("\n", ""))
-            if instruction['opcode'] == "RETURN":
-                if are_satisfiable(state.mstate.constraints):
-                    traces.append(TransactionTrace(state.environment.active_account.storage, state.mstate.constraints))
-    return traces
-
-def get_t_indexed_environment(active_account, index):
-
-        # Initialize the execution environment
-
-        environment = Environment(
-            active_account,
-            BitVec("caller_"+str(index), 256),
-            [],
-            BitVec("gasprice_"+str(index), 256),
-            BitVec("callvalue_"+str(index), 256),
-            BitVec("origin_"+str(index), 256),
-            calldata_type=CalldataType.SYMBOLIC,
-        )
-
-        return environment
-
-def get_t_indexed_globstate(active_account, index):
-    environment = get_t_indexed_environment(active_account, index)
-    # Todo is this just some set of preset accounts? How should we deal with it
-    return GlobalState(self.accounts, environment)
-
diff --git a/mythril/solidnotary/transactiontrace.py b/mythril/solidnotary/transactiontrace.py
deleted file mode 100644
index 44bcaca9..00000000
--- a/mythril/solidnotary/transactiontrace.py
+++ /dev/null
@@ -1,206 +0,0 @@
-from z3 import *
-from copy import deepcopy
-import re
-from mythril.solidnotary.z3utility import are_satisfiable, simplify_constraints
-
-"""
-    Returns whether or the specified symbolic string stands for a data value that can be different from transaction to 
-    transaction without the need of an intermediate call to the contract (e.g. a transaction params, blocknumber, ...)
-"""
-
-
-def is_t_variable(var):
-    var = str(var)
-    if (var.startswith("caller")
-        or var.startswith("gasprice")
-        or var.startswith("callvalue")
-        or var.startswith("origin")
-        or var.startswith("calldata_")
-        or var.startswith("calldatasize_")
-        or var.startswith("balance_at")
-        or var.startswith("KECCAC_mem_")
-        or var.startswith("keccac_")
-        or var.startswith("gasprice")
-        or var.startswith("extcodesize")
-        or var.startswith("returndatasize")
-        # or var.startswith(var, "blockhash_block_") should not change between transactions
-        or var.startswith("coinbase")
-        or var.startswith("timestamp")
-        or var.startswith("block_number")
-        or var.startswith("block_difficulty")
-        or var.startswith("block_gaslimit")
-        or var.startswith("mem_")
-        or var.startswith("msize")
-        or var.startswith("gas")
-        or var.startswith("retval_")
-        or var.startswith("keccac_")):
-        return True
-    else:
-        return False
-
-# Todo constructor mit den intentet initvalues versorgen
-
-
-def filter_for_t_variable_data(sym_vars):
-    return list(filter(lambda x: is_t_variable(x), sym_vars))
-
-class TransactionTrace:
-
-    def __init__(self, storage, constraints, lvl=1):
-        self.storage = deepcopy(storage) # Todo give all non storage symbolic values that can be different every transaction the number one
-        self.constraints = constraints # Todo eliminate all constraints that are not regarding the beginning of the transaction may not be necessary
-        # eliminate all constraints that only contain names not in the set of names from storage
-        self.constraints = simplify_constraints(self.constraints) # Todo simplification of the sum of constraints
-        self.tran_constraints = deepcopy(self.constraints) # Todo minimize them if they do not involve outside symb variables
-        self.lvl = lvl
-        self.sym_names = self.extract_sym_names_from_storage()
-        self.sym_names.extend(self.extract_sym_names_from_constraints())
-        if lvl == 1:
-            self.set_transaction_idx()
-
-        # Todo the constraints of a trace are probably also important here and have to be somehow aggregated
-        # Todo Identifiy addional trace information such as blocknumber and more
-
-    def __str__(self):
-        return str(self.as_dict())
-
-    def as_dict(self):
-
-        return {'lvl': self.lvl, 'storage': str(self.storage), 'constraints': str(self.constraints)}
-
-    def pp_trace(self):
-        print()
-        print("Trace lvl: {}".format(self.lvl))
-        print("Storage: {}".format({k: str(v).replace("\n", " ") for k, v in self.storage.items()}))
-        print("Constraints: {}".format(list(map(lambda x: str(x).replace("\n", " "), self.constraints))))
-        print()
-
-
-    def add_transaction_idx(self, offset):
-        new_names = []
-        for name in self.sym_names:
-            matched_name = re.search(r't([0-9]+)(_.*)', name)
-            num = int(matched_name.group(1)) + offset
-            new_names.append("t" + str(num) + matched_name.group(2))
-        repl_tup = list(zip(self.sym_names, new_names))
-
-        self.substitute_bv_names(repl_tup)
-
-        self.sym_names = new_names
-
-    def set_transaction_idx(self):
-        repl_tup = []
-        new_sym_names = []
-        for name in self.sym_names:
-            repl_tup.append((name, "t1_" + name))
-            new_sym_names.append("t1_" + name)
-        self.sym_names = new_sym_names
-        self.substitute_bv_names(repl_tup)
-
-    def substitute_bv_names(self, subs_tuple):
-        subs_tuples = list(map(lambda name_t: (BitVec(name_t[0], 256), BitVec(name_t[1], 256)), subs_tuple))
-        for s_num, slot in self.storage.items():
-            self.storage[s_num] = substitute(slot, subs_tuples)
-        for c_idx in range(len(self.constraints)):
-            self.constraints[c_idx] = substitute(self.constraints[c_idx], subs_tuples)
-
-    def extract_sym_names(self, obj):
-        if (not hasattr(obj, 'children') or len(obj.children()) == 0) and hasattr(obj, 'decl') :
-                return [str(obj.decl())]
-        else:
-            sym_vars = []
-            for c in obj.children():
-                sym_vars.extend(self.extract_sym_names(c))
-            return sym_vars
-
-    def extract_sym_names_from_constraints(self):
-        sym_names = []
-        for k,v in self.storage.items():
-            sym_names.extend(self.extract_sym_names(v))
-        return filter_for_t_variable_data(sym_names)
-
-    def extract_sym_names_from_storage(self):
-        sym_names = []
-        for v in self.constraints:
-            sym_names.extend(self.extract_sym_names(v))
-        return filter_for_t_variable_data(sym_names) # Todo Check whether here it is the right choice too, to filter ...
-
-    """
-          Either do only deep checing here and use the proper trace or storage_slot reduction in the apply function. Or do
-          both here.
-      """
-
-    def deep_equals(trace_lvl1, trace_lvl2):
-        return set(trace_lvl1) == set(trace_lvl2) # Todo Impelement an ACTUAL deep comparison
-
-    def simplify_storage(self):
-        for k,v in self.storage.items():
-            # Todo explore the arguments of this storage simplification in z3 to find ways to further simplify and to
-            # sort this expressions for equality comparison
-            self.storage[k] = simplify(v)
-
-    """
-        Applies the new trace tt on a possibly even changed trace self.
-    """
-    def apply_trace(self, tt):
-        if tt is None:
-            return self
-        new_trace = deepcopy(tt)
-        new_trace.add_transaction_idx(self.lvl)
-        subs_map = list(map(lambda x: (BitVec("storage_" + str(x[0]), 256), x[1]), self.storage.items()))
-        for k,v in new_trace.storage.items():
-            new_trace.storage[k] = substitute(v, subs_map)
-        for c_idx in range(len(new_trace.constraints)):
-            new_trace.constraints[c_idx] = substitute(new_trace.constraints[c_idx], subs_map)
-        new_trace.lvl += self.lvl
-        new_trace.sym_names.extend(deepcopy(self.sym_names))
-        # self can be omitted (e.g. when related storage locations were overwritten)
-        new_trace.simplify_storage()
-        new_trace.constraints = simplify_constraints(new_trace.constraints)
-        # Simplify constraints in there sum to eliminate subconstraints
-        if are_satisfiable(new_trace.constraints):
-            return new_trace
-        else:
-            return None
-
-    def apply_traces_parallel(self, traces):
-        combined_traces = []
-        for trace in traces:
-            combined_traces.append(self.apply_trace(trace))
-        return list(filter(lambda t: not t is None, combined_traces))
-
-    def apply_exact_trace_levels(self, traces, depth):
-        # Todo maybe some faster trace build not building one level at a time to e.g.
-        # Todo reach level 17 but build 2, then 4, then 8 and then 16 then 17
-        trace_lvl_n = [self]
-        for i in range(depth):
-            trace_lvl_np1 = []
-            for trace in trace_lvl_n:
-                trace_lvl_np1.extend(trace.apply_traces_parallel(traces))
-            if TransactionTrace.deep_equals(trace_lvl_np1, trace_lvl_n): # Fixpoint detected, function needs to ignore lists, dicts and objects.
-                return trace_lvl_n
-            trace_lvl_n = trace_lvl_np1
-        return trace_lvl_n
-
-    def apply_up_to_trace_levels(self, traces, depth):
-        traces_up_to = [[self]] # elements are trace_levels
-        for i in range(depth):
-            trace_lvl_np1 = []
-            for trace in traces_up_to[-1]:
-                trace_lvl_np1.extend(trace.apply_traces_parallel(traces))
-            for trace_lvl_i in traces_up_to:
-                # the following might be faster to check when using a content representing hash
-                if TransactionTrace.deep_equals(trace_lvl_np1, trace_lvl_i): # cycle in the traces of trace chains detected: levels
-                    # while repeat themselves, function needs to ignore lists, dicts and objects.
-                    return traces_up_to
-            traces_up_to.append(trace_lvl_np1)
-        return traces_up_to
-
-    # Todo Maybe implement a function that checks whether two traces are combinable before creating objekts, adv. in
-    # case they are not the object creation doe not have to be done. Investigate whether a suicide trace completely
-    # stopes the contract from being executable. In that case a suicided transaction also is not combinable with
-    # successive transactions.
-
-    # Todo write a function that allows to specify a function/invocable to explore the tracechain space in DFS manner
-
-
diff --git a/mythril/solidnotary/z3utility.py b/mythril/solidnotary/z3utility.py
deleted file mode 100644
index 50b80bac..00000000
--- a/mythril/solidnotary/z3utility.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from z3 import Solver, sat, simplify, is_bool, is_true, is_false
-from copy import deepcopy
-
-def are_satisfiable(constraints):
-    s = Solver()
-    for c in constraints:
-        s.add(c)
-    return s.check() == sat
-
-def simplify_constraints(constraints): # Todo simplification of the sum of constraints
-    simp_const = []
-    for const in constraints:
-        simp_const.append(simplify(const))
-    simp_const = list(filter(lambda c: not is_bool(c) or not is_true(c), simp_const))
-    falses = list(filter(lambda c: is_bool(c) and is_false(c), simp_const))
-    if len(falses) > 0:
-        return [deepcopy(falses[0])]
-
-    return simp_const

From b30e70235c5004cc7490d1a570d0ea9c63b58e03 Mon Sep 17 00:00:00 2001
From: Joran Honig 
Date: Tue, 10 Jul 2018 17:03:47 +0200
Subject: [PATCH 57/62] Implement coverage measurement

---
 mythril/laser/ethereum/svm.py | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py
index 1ceb3740..943065ba 100644
--- a/mythril/laser/ethereum/svm.py
+++ b/mythril/laser/ethereum/svm.py
@@ -4,6 +4,7 @@ from mythril.laser.ethereum.state import GlobalState, Environment, CalldataType,
 from mythril.laser.ethereum.instructions import Instruction
 from mythril.laser.ethereum.cfg import NodeFlags, Node, Edge, JumpType
 from mythril.laser.ethereum.strategy.basic import DepthFirstSearchStrategy
+from functools import reduce
 
 TT256 = 2 ** 256
 TT256M1 = 2 ** 256 - 1
@@ -23,6 +24,7 @@ class LaserEVM:
     Laser EVM class
     """
     def __init__(self, accounts, dynamic_loader=None, max_depth=22):
+        self.instructions_covered = []
         self.accounts = accounts
 
         self.nodes = {}
@@ -51,7 +53,8 @@ class LaserEVM:
             calldata_type=CalldataType.SYMBOLIC,
         )
 
-        # TODO: contact name fix
+        self.instructions_covered = [False for _ in environment.code.instruction_list]
+
         initial_node = Node(environment.active_account.contract_name)
         self.nodes[initial_node.uid] = initial_node
 
@@ -63,6 +66,7 @@ class LaserEVM:
         self._sym_exec()
 
         logging.info("Execution complete")
+        logging.info("Achieved {0:.3g}% coverage".format(self.coverage))
         logging.info("%d nodes, %d edges, %d total states", len(self.nodes), len(self.edges), self.total_states)
 
     def _sym_exec(self):
@@ -81,6 +85,7 @@ class LaserEVM:
     def execute_state(self, global_state):
         instructions = global_state.environment.code.instruction_list
         op_code = instructions[global_state.mstate.pc]['opcode']
+        self.instructions_covered[global_state.mstate.pc] = True
         return Instruction(op_code, self.dynamic_loader).evaluate(global_state), op_code
 
     def manage_cfg(self, opcode, new_states):
@@ -130,3 +135,8 @@ class LaserEVM:
             logging.info("- Entering function " + environment.active_account.contract_name + ":" + new_node.function_name)
 
         new_node.function_name = environment.active_function_name
+
+    @property
+    def coverage(self):
+        return reduce(lambda sum, val: sum + 1 if val else sum, self.instructions_covered) / float(
+            len(self.instructions_covered)) * 100

From e1552435e80737f5d592f676126522ba314fe444 Mon Sep 17 00:00:00 2001
From: Joran Honig 
Date: Tue, 10 Jul 2018 17:42:27 +0200
Subject: [PATCH 58/62] Add timeout logging to solver

---
 mythril/analysis/solver.py | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/mythril/analysis/solver.py b/mythril/analysis/solver.py
index 9e59f831..0f6703e6 100644
--- a/mythril/analysis/solver.py
+++ b/mythril/analysis/solver.py
@@ -1,6 +1,6 @@
-from z3 import Solver, simplify, sat
+from z3 import Solver, simplify, sat, unknown
 from mythril.exceptions import UnsatError
-
+import logging
 
 def get_model(constraints):
     s = Solver()
@@ -8,13 +8,12 @@ def get_model(constraints):
 
     for constraint in constraints:
         s.add(constraint)
-
-    if (s.check() == sat):
-
+    result = s.check()
+    if result == sat:
         return s.model()
-
-    else:
-        raise UnsatError
+    elif result == unknown:
+        logging.info("Timeout encountered while solving expression using z3")
+    raise UnsatError
 
 
 def pretty_print_model(model):

From aa0f6064d5a6882a1f14ec7d2751112bb5d1fdba Mon Sep 17 00:00:00 2001
From: Joran Honig 
Date: Tue, 10 Jul 2018 19:17:29 +0200
Subject: [PATCH 59/62] Use sum_  vs sum

---
 mythril/laser/ethereum/svm.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py
index 943065ba..ace985d4 100644
--- a/mythril/laser/ethereum/svm.py
+++ b/mythril/laser/ethereum/svm.py
@@ -138,5 +138,5 @@ class LaserEVM:
 
     @property
     def coverage(self):
-        return reduce(lambda sum, val: sum + 1 if val else sum, self.instructions_covered) / float(
+        return reduce(lambda sum_, val: sum_ + 1 if val else sum_, self.instructions_covered) / float(
             len(self.instructions_covered)) * 100

From 390bd99b1754c9b8440e2220cd9adacd28ad408e Mon Sep 17 00:00:00 2001
From: Joran Honig 
Date: Wed, 11 Jul 2018 10:53:00 +0200
Subject: [PATCH 60/62] Add contract to as dict

---
 mythril/analysis/report.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/mythril/analysis/report.py b/mythril/analysis/report.py
index 2e2d3425..2e2e04d5 100644
--- a/mythril/analysis/report.py
+++ b/mythril/analysis/report.py
@@ -22,7 +22,7 @@ class Issue:
     @property
     def as_dict(self):
 
-        issue = {'title': self.title, 'description':self.description, 'function': self.function, 'type': self.type, 'address': self.address, 'debug': self.debug}
+        issue = {'title': self.title, 'contract': self.contract, 'description': self.description, 'function': self.function, 'type': self.type, 'address': self.address, 'debug': self.debug}
 
         if self.filename and self.lineno:
             issue['filename'] = self.filename

From b38ba4e6b9a88f04f549c6f1b991a3bec7c673a1 Mon Sep 17 00:00:00 2001
From: Joran Honig 
Date: Wed, 11 Jul 2018 11:13:15 +0200
Subject: [PATCH 61/62] Regenerate json to include contract name

---
 tests/report_test.py                          |  2 +-
 .../outputs_expected/calls.sol.o.json         |  2 +-
 .../outputs_expected/ether_send.sol.o.json    | 23 +-------------
 .../outputs_expected/exceptions.sol.o.json    |  2 +-
 .../kinds_of_calls.sol.o.json                 |  2 +-
 .../outputs_expected/metacoin.sol.o.json      | 15 +--------
 .../multi_contracts.sol.o.json                |  2 +-
 .../outputs_expected/origin.sol.o.json        |  2 +-
 .../outputs_expected/overflow.sol.o.json      | 31 +------------------
 .../outputs_expected/returnvalue.sol.o.json   |  2 +-
 .../outputs_expected/suicide.sol.o.json       |  2 +-
 .../outputs_expected/underflow.sol.o.json     | 31 +------------------
 12 files changed, 12 insertions(+), 104 deletions(-)

diff --git a/tests/report_test.py b/tests/report_test.py
index 88d4c777..a3cd0f70 100644
--- a/tests/report_test.py
+++ b/tests/report_test.py
@@ -112,7 +112,7 @@ def _get_changed_files_json(report_builder, reports):
         output_current = TESTDATA_OUTPUTS_CURRENT / (input_file.name + postfix)
         output_current.write_text(report_builder(report))
 
-        if not ordered(json.loads(output_expected.read_text())) == ordered(json.loads(output_current.read_text())):
+        if False and not ordered(json.loads(output_expected.read_text())) == ordered(json.loads(output_current.read_text())):
             yield input_file
 
 
diff --git a/tests/testdata/outputs_expected/calls.sol.o.json b/tests/testdata/outputs_expected/calls.sol.o.json
index 3a93166f..5b7e39a9 100644
--- a/tests/testdata/outputs_expected/calls.sol.o.json
+++ b/tests/testdata/outputs_expected/calls.sol.o.json
@@ -1 +1 @@
-{"error": null, "issues": [{"address": 661, "debug": "", "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_0x5a6814ec", "title": "Message call to external contract", "type": "Informational"}, {"address": 666, "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0x5a6814ec", "title": "Unchecked CALL return value", "type": "Informational"}, {"address": 779, "debug": "", "description": "This contract executes a message call to an address found at storage slot 1. This storage slot can be written to by calling the function `_function_0x2776b163`. Generally, it is not recommended to call user-supplied addresses using Solidity's call() construct. Note that attackers might leverage reentrancy attacks to exploit race conditions or manipulate this contract's state.", "function": "_function_0xd24b08cc", "title": "Message call to external contract", "type": "Warning"}, {"address": 779, "debug": "", "description": "A possible transaction order independence vulnerability exists in function _function_0xd24b08cc. The value or direction of the call statement is determined from a tainted storage location", "function": "_function_0xd24b08cc", "title": "Transaction order dependence", "type": "Warning"}, {"address": 784, "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0xd24b08cc", "title": "Unchecked CALL return value", "type": "Informational"}, {"address": 858, "debug": "", "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", "title": "Message call to external contract", "type": "Informational"}, {"address": 869, "debug": "", "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", "title": "State change after external call", "type": "Warning"}, {"address": 871, "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0xe11f493e", "title": "Unchecked CALL return value", "type": "Informational"}, {"address": 912, "debug": "", "description": "This contract executes a message call to an address provided as a function argument. Generally, it is not recommended to call user-supplied addresses using Solidity's call() construct. Note that attackers might leverage reentrancy attacks to exploit race conditions or manipulate this contract's state.", "function": "_function_0xe1d10f79", "title": "Message call to external contract", "type": "Warning"}, {"address": 918, "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0xe1d10f79", "title": "Unchecked CALL return value", "type": "Informational"}], "success": true}
\ No newline at end of file
+{"error": null, "issues": [{"address": 661, "contract": "Unknown", "debug": "", "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_0x5a6814ec", "title": "Message call to external contract", "type": "Informational"}, {"address": 666, "contract": "Unknown", "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0x5a6814ec", "title": "Unchecked CALL return value", "type": "Informational"}, {"address": 779, "contract": "Unknown", "debug": "", "description": "This contract executes a message call to an address found at storage slot 1. This storage slot can be written to by calling the function `_function_0x2776b163`. Generally, it is not recommended to call user-supplied addresses using Solidity's call() construct. Note that attackers might leverage reentrancy attacks to exploit race conditions or manipulate this contract's state.", "function": "_function_0xd24b08cc", "title": "Message call to external contract", "type": "Warning"}, {"address": 779, "contract": "Unknown", "debug": "", "description": "A possible transaction order independence vulnerability exists in function _function_0xd24b08cc. The value or direction of the call statement is determined from a tainted storage location", "function": "_function_0xd24b08cc", "title": "Transaction order dependence", "type": "Warning"}, {"address": 784, "contract": "Unknown", "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0xd24b08cc", "title": "Unchecked CALL return value", "type": "Informational"}, {"address": 858, "contract": "Unknown", "debug": "", "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", "title": "Message call to external contract", "type": "Informational"}, {"address": 869, "contract": "Unknown", "debug": "", "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", "title": "State change after external call", "type": "Warning"}, {"address": 871, "contract": "Unknown", "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0xe11f493e", "title": "Unchecked CALL return value", "type": "Informational"}, {"address": 912, "contract": "Unknown", "debug": "", "description": "This contract executes a message call to an address provided as a function argument. Generally, it is not recommended to call user-supplied addresses using Solidity's call() construct. Note that attackers might leverage reentrancy attacks to exploit race conditions or manipulate this contract's state.", "function": "_function_0xe1d10f79", "title": "Message call to external contract", "type": "Warning"}, {"address": 918, "contract": "Unknown", "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0xe1d10f79", "title": "Unchecked CALL return value", "type": "Informational"}], "success": true}
\ No newline at end of file
diff --git a/tests/testdata/outputs_expected/ether_send.sol.o.json b/tests/testdata/outputs_expected/ether_send.sol.o.json
index 6d6caad0..d893749f 100644
--- a/tests/testdata/outputs_expected/ether_send.sol.o.json
+++ b/tests/testdata/outputs_expected/ether_send.sol.o.json
@@ -1,22 +1 @@
-{
-    "success": true,
-    "error": null,
-    "issues": [
-        {
-            "title": "Ether send",
-            "description": "In the function `withdrawfunds()` a non-zero amount of Ether is sent to msg.sender.\n\nThere is a check on storage index 1. This storage slot can be written to by calling the function `crowdfunding()`.",
-            "function": "withdrawfunds()",
-            "type": "Warning",
-            "address": 722,
-            "debug": ""
-        },
-        {
-            "title": "Integer Overflow",
-            "description": "A possible integer overflow exists in the function `invest()`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.",
-            "function": "invest()",
-            "type": "Warning",
-            "address": 883,
-            "debug": ""
-        }
-    ]
-}
+{"error": null, "issues": [{"address": 722, "contract": "Unknown", "debug": "", "description": "In the function `withdrawfunds()` a non-zero amount of Ether is sent to msg.sender.\n\nThere is a check on storage index 1. This storage slot can be written to by calling the function `crowdfunding()`.", "function": "withdrawfunds()", "title": "Ether send", "type": "Warning"}, {"address": 883, "contract": "Unknown", "debug": "", "description": "A possible integer overflow exists in the function `invest()`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "invest()", "title": "Integer Overflow", "type": "Warning"}], "success": true}
\ No newline at end of file
diff --git a/tests/testdata/outputs_expected/exceptions.sol.o.json b/tests/testdata/outputs_expected/exceptions.sol.o.json
index 4434c91f..e923bb3a 100644
--- a/tests/testdata/outputs_expected/exceptions.sol.o.json
+++ b/tests/testdata/outputs_expected/exceptions.sol.o.json
@@ -1 +1 @@
-{"error": null, "issues": [{"address": 446, "debug": "", "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", "function": "_function_0x546455b5", "title": "Exception state", "type": "Informational"}, {"address": 484, "debug": "", "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", "function": "_function_0x92dd38ea", "title": "Exception state", "type": "Informational"}, {"address": 506, "debug": "", "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", "function": "_function_0xa08299f1", "title": "Exception state", "type": "Informational"}, {"address": 531, "debug": "", "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", "function": "_function_0xb34c3610", "title": "Exception state", "type": "Informational"}], "success": true}
\ No newline at end of file
+{"error": null, "issues": [{"address": 446, "contract": "Unknown", "debug": "", "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", "function": "_function_0x546455b5", "title": "Exception state", "type": "Informational"}, {"address": 484, "contract": "Unknown", "debug": "", "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", "function": "_function_0x92dd38ea", "title": "Exception state", "type": "Informational"}, {"address": 506, "contract": "Unknown", "debug": "", "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", "function": "_function_0xa08299f1", "title": "Exception state", "type": "Informational"}, {"address": 531, "contract": "Unknown", "debug": "", "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", "function": "_function_0xb34c3610", "title": "Exception state", "type": "Informational"}], "success": true}
\ No newline at end of file
diff --git a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.json b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.json
index 747f1118..ccdf57c0 100644
--- a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.json
+++ b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.json
@@ -1 +1 @@
-{"error": null, "issues": [{"address": 626, "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0x141f32ff", "title": "Unchecked CALL return value", "type": "Informational"}, {"address": 857, "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0x9b58bc26", "title": "Unchecked CALL return value", "type": "Informational"}, {"address": 1038, "debug": "", "description": "This contract executes a message call to an address provided as a function argument. Generally, it is not recommended to call user-supplied addresses using Solidity's call() construct. Note that attackers might leverage reentrancy attacks to exploit race conditions or manipulate this contract's state.", "function": "_function_0xeea4c864", "title": "Message call to external contract", "type": "Warning"}, {"address": 1046, "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0xeea4c864", "title": "Unchecked CALL return value", "type": "Informational"}], "success": true}
\ No newline at end of file
+{"error": null, "issues": [{"address": 626, "contract": "Unknown", "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0x141f32ff", "title": "Unchecked CALL return value", "type": "Informational"}, {"address": 857, "contract": "Unknown", "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0x9b58bc26", "title": "Unchecked CALL return value", "type": "Informational"}, {"address": 1038, "contract": "Unknown", "debug": "", "description": "This contract executes a message call to an address provided as a function argument. Generally, it is not recommended to call user-supplied addresses using Solidity's call() construct. Note that attackers might leverage reentrancy attacks to exploit race conditions or manipulate this contract's state.", "function": "_function_0xeea4c864", "title": "Message call to external contract", "type": "Warning"}, {"address": 1046, "contract": "Unknown", "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0xeea4c864", "title": "Unchecked CALL return value", "type": "Informational"}], "success": true}
\ No newline at end of file
diff --git a/tests/testdata/outputs_expected/metacoin.sol.o.json b/tests/testdata/outputs_expected/metacoin.sol.o.json
index fd59d519..b0610b5f 100644
--- a/tests/testdata/outputs_expected/metacoin.sol.o.json
+++ b/tests/testdata/outputs_expected/metacoin.sol.o.json
@@ -1,14 +1 @@
-{
-    "success": true,
-    "error": null,
-    "issues": [
-        {
-            "title": "Integer Overflow",
-            "description": "A possible integer overflow exists in the function `sendToken(address,uint256)`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.",
-            "function": "sendToken(address,uint256)",
-            "type": "Warning",
-            "address": 498,
-            "debug": ""
-        }
-    ]
-}
+{"error": null, "issues": [{"address": 498, "contract": "Unknown", "debug": "", "description": "A possible integer overflow exists in the function `sendToken(address,uint256)`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "sendToken(address,uint256)", "title": "Integer Overflow", "type": "Warning"}], "success": true}
\ No newline at end of file
diff --git a/tests/testdata/outputs_expected/multi_contracts.sol.o.json b/tests/testdata/outputs_expected/multi_contracts.sol.o.json
index ff019037..1d4a5e35 100644
--- a/tests/testdata/outputs_expected/multi_contracts.sol.o.json
+++ b/tests/testdata/outputs_expected/multi_contracts.sol.o.json
@@ -1 +1 @@
-{"error": null, "issues": [{"address": 142, "debug": "", "description": "In the function `_function_0x8a4068dd` a non-zero amount of Ether is sent to msg.sender.\nIt seems that this function can be called without restrictions.", "function": "_function_0x8a4068dd", "title": "Ether send", "type": "Warning"}], "success": true}
\ No newline at end of file
+{"error": null, "issues": [{"address": 142, "contract": "Unknown", "debug": "", "description": "In the function `_function_0x8a4068dd` a non-zero amount of Ether is sent to msg.sender.\nIt seems that this function can be called without restrictions.", "function": "_function_0x8a4068dd", "title": "Ether send", "type": "Warning"}], "success": true}
\ No newline at end of file
diff --git a/tests/testdata/outputs_expected/origin.sol.o.json b/tests/testdata/outputs_expected/origin.sol.o.json
index 2024d492..1ac2d554 100644
--- a/tests/testdata/outputs_expected/origin.sol.o.json
+++ b/tests/testdata/outputs_expected/origin.sol.o.json
@@ -1 +1 @@
-{"error": null, "issues": [{"address": 317, "debug": "", "description": "Function transferOwnership(address) retrieves the transaction origin (tx.origin) using the ORIGIN opcode. Use msg.sender instead.\nSee also: https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin", "function": "transferOwnership(address)", "title": "Use of tx.origin", "type": "Warning"}], "success": true}
\ No newline at end of file
+{"error": null, "issues": [{"address": 317, "contract": "Unknown", "debug": "", "description": "Function transferOwnership(address) retrieves the transaction origin (tx.origin) using the ORIGIN opcode. Use msg.sender instead.\nSee also: https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin", "function": "transferOwnership(address)", "title": "Use of tx.origin", "type": "Warning"}], "success": true}
\ No newline at end of file
diff --git a/tests/testdata/outputs_expected/overflow.sol.o.json b/tests/testdata/outputs_expected/overflow.sol.o.json
index 8345944e..341e5b46 100644
--- a/tests/testdata/outputs_expected/overflow.sol.o.json
+++ b/tests/testdata/outputs_expected/overflow.sol.o.json
@@ -1,30 +1 @@
-{
-    "success": true,
-    "error": null,
-    "issues": [
-        {
-            "title": "Integer Underflow",
-            "description": "A possible integer underflow exists in the function `sendeth(address,uint256)`.\nThe subtraction may result in a value < 0.",
-            "function": "sendeth(address,uint256)",
-            "type": "Warning",
-            "address": 649,
-            "debug": ""
-        },
-        {
-            "title": "Integer Overflow",
-            "description": "A possible integer overflow exists in the function `sendeth(address,uint256)`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.",
-            "function": "sendeth(address,uint256)",
-            "type": "Warning",
-            "address": 725,
-            "debug": ""
-        },
-        {
-            "title": "Integer Underflow",
-            "description": "A possible integer underflow exists in the function `sendeth(address,uint256)`.\nThe subtraction may result in a value < 0.",
-            "function": "sendeth(address,uint256)",
-            "type": "Warning",
-            "address": 567,
-            "debug": ""
-        }
-    ]
-}
+{"error": null, "issues": [{"address": 567, "contract": "Unknown", "debug": "", "description": "A possible integer underflow exists in the function `sendeth(address,uint256)`.\nThe subtraction may result in a value < 0.", "function": "sendeth(address,uint256)", "title": "Integer Underflow", "type": "Warning"}, {"address": 649, "contract": "Unknown", "debug": "", "description": "A possible integer underflow exists in the function `sendeth(address,uint256)`.\nThe subtraction may result in a value < 0.", "function": "sendeth(address,uint256)", "title": "Integer Underflow", "type": "Warning"}, {"address": 725, "contract": "Unknown", "debug": "", "description": "A possible integer overflow exists in the function `sendeth(address,uint256)`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "sendeth(address,uint256)", "title": "Integer Overflow", "type": "Warning"}], "success": true}
\ No newline at end of file
diff --git a/tests/testdata/outputs_expected/returnvalue.sol.o.json b/tests/testdata/outputs_expected/returnvalue.sol.o.json
index a3fe1e4c..11ebd735 100644
--- a/tests/testdata/outputs_expected/returnvalue.sol.o.json
+++ b/tests/testdata/outputs_expected/returnvalue.sol.o.json
@@ -1 +1 @@
-{"error": null, "issues": [{"address": 196, "debug": "", "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_0x633ab5e0", "title": "Message call to external contract", "type": "Informational"}, {"address": 285, "debug": "", "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_0xe3bea282", "title": "Message call to external contract", "type": "Informational"}, {"address": 290, "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0xe3bea282", "title": "Unchecked CALL return value", "type": "Informational"}], "success": true}
\ No newline at end of file
+{"error": null, "issues": [{"address": 196, "contract": "Unknown", "debug": "", "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_0x633ab5e0", "title": "Message call to external contract", "type": "Informational"}, {"address": 285, "contract": "Unknown", "debug": "", "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_0xe3bea282", "title": "Message call to external contract", "type": "Informational"}, {"address": 290, "contract": "Unknown", "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0xe3bea282", "title": "Unchecked CALL return value", "type": "Informational"}], "success": true}
\ No newline at end of file
diff --git a/tests/testdata/outputs_expected/suicide.sol.o.json b/tests/testdata/outputs_expected/suicide.sol.o.json
index a8a189b9..1b5818d1 100644
--- a/tests/testdata/outputs_expected/suicide.sol.o.json
+++ b/tests/testdata/outputs_expected/suicide.sol.o.json
@@ -1 +1 @@
-{"error": null, "issues": [{"address": 146, "debug": "", "description": "The function `_function_0xcbf0b0c0` executes the SUICIDE instruction. The remaining Ether is sent to an address provided as a function argument.\n\nIt seems that this function can be called without restrictions.", "function": "_function_0xcbf0b0c0", "title": "Unchecked SUICIDE", "type": "Warning"}], "success": true}
\ No newline at end of file
+{"error": null, "issues": [{"address": 146, "contract": "Unknown", "debug": "", "description": "The function `_function_0xcbf0b0c0` executes the SUICIDE instruction. The remaining Ether is sent to an address provided as a function argument.\n\nIt seems that this function can be called without restrictions.", "function": "_function_0xcbf0b0c0", "title": "Unchecked SUICIDE", "type": "Warning"}], "success": true}
\ No newline at end of file
diff --git a/tests/testdata/outputs_expected/underflow.sol.o.json b/tests/testdata/outputs_expected/underflow.sol.o.json
index 8345944e..341e5b46 100644
--- a/tests/testdata/outputs_expected/underflow.sol.o.json
+++ b/tests/testdata/outputs_expected/underflow.sol.o.json
@@ -1,30 +1 @@
-{
-    "success": true,
-    "error": null,
-    "issues": [
-        {
-            "title": "Integer Underflow",
-            "description": "A possible integer underflow exists in the function `sendeth(address,uint256)`.\nThe subtraction may result in a value < 0.",
-            "function": "sendeth(address,uint256)",
-            "type": "Warning",
-            "address": 649,
-            "debug": ""
-        },
-        {
-            "title": "Integer Overflow",
-            "description": "A possible integer overflow exists in the function `sendeth(address,uint256)`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.",
-            "function": "sendeth(address,uint256)",
-            "type": "Warning",
-            "address": 725,
-            "debug": ""
-        },
-        {
-            "title": "Integer Underflow",
-            "description": "A possible integer underflow exists in the function `sendeth(address,uint256)`.\nThe subtraction may result in a value < 0.",
-            "function": "sendeth(address,uint256)",
-            "type": "Warning",
-            "address": 567,
-            "debug": ""
-        }
-    ]
-}
+{"error": null, "issues": [{"address": 567, "contract": "Unknown", "debug": "", "description": "A possible integer underflow exists in the function `sendeth(address,uint256)`.\nThe subtraction may result in a value < 0.", "function": "sendeth(address,uint256)", "title": "Integer Underflow", "type": "Warning"}, {"address": 649, "contract": "Unknown", "debug": "", "description": "A possible integer underflow exists in the function `sendeth(address,uint256)`.\nThe subtraction may result in a value < 0.", "function": "sendeth(address,uint256)", "title": "Integer Underflow", "type": "Warning"}, {"address": 725, "contract": "Unknown", "debug": "", "description": "A possible integer overflow exists in the function `sendeth(address,uint256)`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "sendeth(address,uint256)", "title": "Integer Overflow", "type": "Warning"}], "success": true}
\ No newline at end of file

From 084b1ef9555d0338ac1281ccd1f2c0dcf25ef53d Mon Sep 17 00:00:00 2001
From: Joran Honig 
Date: Wed, 11 Jul 2018 11:14:02 +0200
Subject: [PATCH 62/62] Fix false

---
 tests/report_test.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/report_test.py b/tests/report_test.py
index a3cd0f70..88d4c777 100644
--- a/tests/report_test.py
+++ b/tests/report_test.py
@@ -112,7 +112,7 @@ def _get_changed_files_json(report_builder, reports):
         output_current = TESTDATA_OUTPUTS_CURRENT / (input_file.name + postfix)
         output_current.write_text(report_builder(report))
 
-        if False and not ordered(json.loads(output_expected.read_text())) == ordered(json.loads(output_current.read_text())):
+        if not ordered(json.loads(output_expected.read_text())) == ordered(json.loads(output_current.read_text())):
             yield input_file