Add basic docstrings to all classes and functions

pull/845/head
Dominik Muhs 6 years ago
parent 338c55be08
commit 55153a1469
  1. 19
      mythril/analysis/callgraph.py
  2. 20
      mythril/analysis/modules/base.py
  3. 1
      mythril/analysis/modules/delegatecall.py
  4. 10
      mythril/analysis/modules/dependence_on_predictable_vars.py
  5. 12
      mythril/analysis/modules/deprecated_ops.py
  6. 10
      mythril/analysis/modules/ether_thief.py
  7. 5
      mythril/analysis/modules/exceptions.py
  8. 10
      mythril/analysis/modules/external_calls.py
  9. 1
      mythril/analysis/modules/integer.py
  10. 9
      mythril/analysis/modules/multiple_sends.py
  11. 10
      mythril/analysis/modules/suicide.py
  12. 1
      mythril/analysis/modules/transaction_order_dependence.py
  13. 5
      mythril/analysis/modules/unchecked_retval.py
  14. 17
      mythril/analysis/ops.py
  15. 33
      mythril/analysis/report.py
  16. 20
      mythril/analysis/security.py
  17. 12
      mythril/analysis/solver.py
  18. 5
      mythril/analysis/symbolic.py
  19. 10
      mythril/analysis/traceexplore.py
  20. 20
      mythril/disassembler/asm.py
  21. 10
      mythril/disassembler/disassembly.py
  22. 16
      mythril/ethereum/evmcontract.py
  23. 10
      mythril/ethereum/interface/leveldb/accountindexing.py
  24. 3
      mythril/ethereum/interface/rpc/client.py
  25. 5
      mythril/ethereum/interface/rpc/utils.py
  26. 31
      mythril/ethereum/util.py
  27. 8
      mythril/interfaces/cli.py
  28. 43
      mythril/interfaces/epic.py
  29. 10
      mythril/laser/ethereum/call.py
  30. 14
      mythril/laser/ethereum/cfg.py
  31. 11
      mythril/laser/ethereum/gas.py
  32. 391
      mythril/laser/ethereum/instructions.py
  33. 18
      mythril/laser/ethereum/keccak.py
  34. 31
      mythril/laser/ethereum/natives.py
  35. 16
      mythril/laser/ethereum/state/account.py
  36. 49
      mythril/laser/ethereum/state/calldata.py
  37. 12
      mythril/laser/ethereum/state/constraints.py
  38. 4
      mythril/laser/ethereum/state/environment.py
  39. 26
      mythril/laser/ethereum/state/global_state.py
  40. 23
      mythril/laser/ethereum/state/machine_state.py
  41. 7
      mythril/laser/ethereum/state/memory.py
  42. 8
      mythril/laser/ethereum/state/world_state.py
  43. 6
      mythril/laser/ethereum/strategy/__init__.py
  44. 16
      mythril/laser/ethereum/strategy/basic.py
  45. 51
      mythril/laser/ethereum/svm.py
  46. 70
      mythril/laser/ethereum/taint_analysis.py
  47. 21
      mythril/laser/ethereum/transaction/transaction_models.py
  48. 53
      mythril/laser/ethereum/util.py
  49. 108
      mythril/mythril.py
  50. 24
      mythril/solidity/soliditycontract.py
  51. 13
      mythril/support/loader.py
  52. 20
      mythril/support/signatures.py
  53. 15
      mythril/support/truffle.py
  54. 3
      setup.py
  55. 16
      tests/__init__.py
  56. 5
      tests/cmd_line_test.py
  57. 12
      tests/evmcontract_test.py
  58. 5
      tests/laser/evm_testsuite/evm_test.py
  59. 2
      tests/native_test.py
  60. 10
      tests/report_test.py
  61. 6
      tests/rpc_test.py

@ -121,6 +121,12 @@ phrack_color = {
def extract_nodes(statespace, color_map):
"""
:param statespace:
:param color_map:
:return:
"""
nodes = []
for node_key in statespace.nodes:
node = statespace.nodes[node_key]
@ -168,6 +174,11 @@ def extract_nodes(statespace, color_map):
def extract_edges(statespace):
"""
:param statespace:
:return:
"""
edges = []
for edge in statespace.edges:
if edge.condition is None:
@ -200,6 +211,14 @@ def generate_graph(
physics=False,
phrackify=False,
):
"""
:param statespace:
:param title:
:param physics:
:param phrackify:
:return:
"""
env = Environment(
loader=PackageLoader("mythril.analysis"),
autoescape=select_autoescape(["html", "xml"]),

@ -3,6 +3,10 @@ from typing import List
class DetectionModule:
"""The base detection module.
All custom-built detection modules must inherit from this class.
"""
def __init__(
self,
name: str,
@ -10,7 +14,15 @@ class DetectionModule:
hooks: List[str],
description: str,
entrypoint: str = "post",
):
) -> None:
"""
:param name:
:param swc_id:
:param hooks:
:param description:
:param entrypoint:
"""
self.name = name
self.swc_id = swc_id
self.hooks = hooks
@ -23,9 +35,13 @@ class DetectionModule:
self.entrypoint = entrypoint
def execute(self, statespace):
""" The entry point for execution, which is being called by Mythril.
:param statespace:
:return:
"""
raise NotImplementedError()
def __repr__(self):
def __repr__(self) -> str:
return (
"<"
"DetectionModule "

@ -7,6 +7,7 @@ import logging
class DelegateCallModule(DetectionModule):
"""This module detects calldata being forwarded using DELEGATECALL."""
def __init__(self):
super().__init__(
name="DELEGATECALL Usage in Fallback Function",

@ -10,6 +10,7 @@ import logging
class PredictableDependenceModule(DetectionModule):
"""This module detects whether Ether is sent using predictable parameters."""
def __init__(self):
super().__init__(
name="Dependence of Predictable Variables",
@ -23,7 +24,11 @@ class PredictableDependenceModule(DetectionModule):
)
def execute(self, statespace):
"""
:param statespace:
:return:
"""
logging.debug("Executing module: DEPENDENCE_ON_PREDICTABLE_VARS")
issues = []
@ -169,6 +174,11 @@ class PredictableDependenceModule(DetectionModule):
return issues
def solve(self, call):
"""
:param call:
:return:
"""
try:
model = solver.get_model(call.node.constraints)
logging.debug("[DEPENDENCE_ON_PREDICTABLE_VARS] MODEL: " + str(model))

@ -50,22 +50,32 @@ def _analyze_state(state):
class DeprecatedOperationsModule(DetectionModule):
"""This module checks for the usage of deprecated op codes."""
def __init__(self):
super().__init__(
name="Deprecated Operations",
swc_id=DEPRICATED_FUNCTIONS_USAGE,
hooks=["ORIGIN", "CALLCODE"],
description=(DESCRIPTION),
description=DESCRIPTION,
entrypoint="callback",
)
self._issues = []
def execute(self, state: GlobalState):
"""
:param state:
:return:
"""
self._issues.extend(_analyze_state(state))
return self.issues
@property
def issues(self):
"""
:return:
"""
return self._issues

@ -75,6 +75,7 @@ def _analyze_state(state):
class EtherThief(DetectionModule):
"""This module search for cases where Ether can be withdrawn to a user-specified address."""
def __init__(self):
super().__init__(
name="Ether Thief",
@ -86,11 +87,20 @@ class EtherThief(DetectionModule):
self._issues = []
def execute(self, state: GlobalState):
"""
:param state:
:return:
"""
self._issues.extend(_analyze_state(state))
return self.issues
@property
def issues(self):
"""
:return:
"""
return self._issues

@ -7,6 +7,7 @@ import logging
class ReachableExceptionsModule(DetectionModule):
"""This module checks whether any exception states are reachable."""
def __init__(self):
super().__init__(
name="Reachable Exceptions",
@ -16,7 +17,11 @@ class ReachableExceptionsModule(DetectionModule):
)
def execute(self, statespace):
"""
:param statespace:
:return:
"""
logging.debug("Executing module: EXCEPTIONS")
issues = []

@ -89,6 +89,7 @@ def _analyze_state(state):
class ExternalCalls(DetectionModule):
"""This module searches for low level calls (e.g. call.value()) that forward all gas to the callee."""
def __init__(self):
super().__init__(
name="External calls",
@ -100,11 +101,20 @@ class ExternalCalls(DetectionModule):
self._issues = []
def execute(self, state: GlobalState):
"""
:param state:
:return:
"""
self._issues.extend(_analyze_state(state))
return self.issues
@property
def issues(self):
"""
:return:
"""
return self._issues

@ -12,6 +12,7 @@ import logging
class IntegerOverflowUnderflowModule(DetectionModule):
"""This module searches for integer over- and underflows."""
def __init__(self):
super().__init__(
name="Integer Overflow and Underflow",

@ -5,6 +5,7 @@ from mythril.laser.ethereum.cfg import JumpType
class MultipleSendsModule(DetectionModule):
"""This module checks for multiple sends in a single transaction."""
def __init__(self):
super().__init__(
name="Multiple Sends",
@ -14,6 +15,11 @@ class MultipleSendsModule(DetectionModule):
)
def execute(self, statespace):
"""
:param statespace:
:return:
"""
issues = []
for call in statespace.calls:
@ -59,7 +65,8 @@ class MultipleSendsModule(DetectionModule):
sending_children = list(filter(lambda c: c.node in children, statespace.calls))
return sending_children
def _explore_states(self, call, statespace):
@staticmethod
def _explore_states(call, statespace):
other_calls = list(
filter(
lambda other: other.node == call.node

@ -58,6 +58,7 @@ def _analyze_state(state):
class SuicideModule(DetectionModule):
"""This module checks if the contact can be 'accidentally' killed by anyone."""
def __init__(self):
super().__init__(
name="Unprotected Suicide",
@ -69,11 +70,20 @@ class SuicideModule(DetectionModule):
self._issues = []
def execute(self, state: GlobalState):
"""
:param state:
:return:
"""
self._issues.extend(_analyze_state(state))
return self.issues
@property
def issues(self):
"""
:return:
"""
return self._issues

@ -11,6 +11,7 @@ from mythril.exceptions import UnsatError
class TxOrderDependenceModule(DetectionModule):
"""This module finds the existance of transaction order dependence."""
def __init__(self):
super().__init__(
name="Transaction Order Dependence",

@ -8,6 +8,7 @@ import re
class UncheckedRetvalModule(DetectionModule):
"""This module checks whether CALL return value is checked."""
def __init__(self):
super().__init__(
name="Unchecked Return Value",
@ -25,7 +26,11 @@ class UncheckedRetvalModule(DetectionModule):
)
def execute(self, statespace):
"""
:param statespace:
:return:
"""
logging.debug("Executing module: UNCHECKED_RETVAL")
issues = []

@ -9,6 +9,9 @@ class VarType(Enum):
class Variable:
"""
"""
def __init__(self, val, _type):
self.val = val
self.type = _type
@ -18,6 +21,11 @@ class Variable:
def get_variable(i):
"""
:param i:
:return:
"""
try:
return Variable(util.get_concrete_int(i), VarType.CONCRETE)
except TypeError:
@ -25,6 +33,9 @@ def get_variable(i):
class Op:
"""
"""
def __init__(self, node, state, state_index):
self.node = node
self.state = state
@ -32,6 +43,9 @@ class Op:
class Call(Op):
"""
"""
def __init__(
self,
node,
@ -53,6 +67,9 @@ class Call(Op):
class SStore(Op):
"""
"""
def __init__(self, node, state, state_index, value):
super().__init__(node, state, state_index)
self.value = value

@ -7,6 +7,9 @@ import hashlib
class Issue:
"""
"""
def __init__(
self,
contract,
@ -46,7 +49,10 @@ class Issue:
@property
def as_dict(self):
"""
:return:
"""
issue = {
"title": self.title,
"swc-id": self.swc_id,
@ -70,6 +76,10 @@ class Issue:
return issue
def add_code_info(self, contract):
"""
:param contract:
"""
if self.address:
codeinfo = contract.get_source_info(
self.address, constructor=(self.function == "constructor")
@ -80,6 +90,9 @@ class Issue:
class Report:
"""
"""
environment = Environment(
loader=PackageLoader("mythril.analysis"), trim_blocks=True
)
@ -90,15 +103,27 @@ class Report:
pass
def sorted_issues(self):
"""
:return:
"""
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):
"""
:param issue:
"""
m = hashlib.md5()
m.update((issue.contract + str(issue.address) + issue.title).encode("utf-8"))
self.issues[m.digest()] = issue
def as_text(self):
"""
:return:
"""
name = self._file_name()
template = Report.environment.get_template("report_as_text.jinja2")
return template.render(
@ -106,6 +131,10 @@ class Report:
)
def as_json(self):
"""
:return:
"""
result = {"success": True, "error": None, "issues": self.sorted_issues()}
return json.dumps(result, sort_keys=True)
@ -124,6 +153,10 @@ class Report:
return json.dumps(result, sort_keys=True)
def as_markdown(self):
"""
:return:
"""
filename = self._file_name()
template = Report.environment.get_template("report_as_markdown.jinja2")
return template.render(

@ -10,12 +10,20 @@ OPCODE_LIST = [c[0] for _, c in opcodes.items()]
def reset_callback_modules():
"""
"""
modules = get_detection_modules("callback")
for module in modules:
module.detector._issues = []
def get_detection_module_hooks(modules):
"""
:param modules:
:return:
"""
hook_dict = defaultdict(list)
_modules = get_detection_modules(entrypoint="callback", include_modules=modules)
for module in _modules:
@ -36,6 +44,12 @@ def get_detection_module_hooks(modules):
def get_detection_modules(entrypoint, include_modules=()):
"""
:param entrypoint:
:param include_modules:
:return:
"""
include_modules = list(include_modules)
_modules = []
@ -59,6 +73,12 @@ def get_detection_modules(entrypoint, include_modules=()):
def fire_lasers(statespace, module_names=()):
"""
:param statespace:
:param module_names:
:return:
"""
logging.info("Starting analysis")
issues = []

@ -7,6 +7,13 @@ import logging
def get_model(constraints, minimize=(), maximize=()):
"""
:param constraints:
:param minimize:
:param maximize:
:return:
"""
s = Optimize()
s.set("timeout", 100000)
@ -32,7 +39,11 @@ def get_model(constraints, minimize=(), maximize=()):
def pretty_print_model(model):
"""
:param model:
:return:
"""
ret = ""
for d in model.decls():
@ -57,7 +68,6 @@ def get_transaction_sequence(global_state, constraints):
:param global_state: GlobalState to generate transaction sequence for
:param constraints: list of constraints used to generate transaction sequence
:param caller: address of caller
:param max_callvalue: maximum callvalue for a transaction
"""

@ -179,7 +179,12 @@ class SymExecWrapper:
state_index += 1
def find_storage_write(self, address, index):
"""
:param address:
:param index:
:return:
"""
# Find an SSTOR not constrained by caller that writes to storage index "index"
try:

@ -47,6 +47,11 @@ colors = [
def get_serializable_statespace(statespace):
"""
:param statespace:
:return:
"""
nodes = []
edges = []
@ -77,6 +82,11 @@ def get_serializable_statespace(statespace):
color = color_map[node.get_cfg_dict()["contract_name"]]
def get_state_accounts(node_state):
"""
:param node_state:
:return:
"""
state_accounts = []
for key in node_state.accounts:
account = node_state.accounts[key].as_dict

@ -18,6 +18,10 @@ class EvmInstruction:
self.argument = argument
def to_dict(self) -> dict:
"""
:return:
"""
result = {"address": self.address, "opcode": self.op_code}
if self.argument:
result["argument"] = self.argument
@ -25,6 +29,11 @@ class EvmInstruction:
def instruction_list_to_easm(instruction_list: list) -> str:
"""
:param instruction_list:
:return:
"""
result = ""
for instruction in instruction_list:
@ -37,6 +46,11 @@ def instruction_list_to_easm(instruction_list: list) -> str:
def get_opcode_from_name(operation_name: str) -> int:
"""
:param operation_name:
:return:
"""
for op_code, value in opcodes.items():
if operation_name == value[0]:
return op_code
@ -46,8 +60,7 @@ def get_opcode_from_name(operation_name: str) -> int:
def find_op_code_sequence(pattern: list, instruction_list: list) -> Generator:
"""
Returns all indices in instruction_list that point to instruction sequences following a pattern
:param pattern: The pattern to look for.
Example: [["PUSH1", "PUSH2"], ["EQ"]] where ["PUSH1", "EQ"] satisfies the pattern
:param pattern: The pattern to look for, e.g. [["PUSH1", "PUSH2"], ["EQ"]] where ["PUSH1", "EQ"] satisfies pattern
:param instruction_list: List of instructions to look in
:return: Indices to the instruction sequences
"""
@ -59,8 +72,7 @@ def find_op_code_sequence(pattern: list, instruction_list: list) -> Generator:
def is_sequence_match(pattern: list, instruction_list: list, index: int) -> bool:
"""
Checks if the instructions starting at index follow a pattern
:param pattern: List of lists describing a pattern.
Example: [["PUSH1", "PUSH2"], ["EQ"]] where ["PUSH1", "EQ"] satisfies the pattern
:param pattern: List of lists describing a pattern, e.g. [["PUSH1", "PUSH2"], ["EQ"]] where ["PUSH1", "EQ"] satisfies pattern
:param instruction_list: List of instructions
:param index: Index to check for
:return: Pattern matched

@ -10,9 +10,9 @@ class Disassembly(object):
Stores bytecode, and its disassembly.
Additionally it will gather the following information on the existing functions in the disassembled code:
- function hashes
- function name to entry point mapping
- function entry point to function name mapping
- function hashes
- function name to entry point mapping
- function entry point to function name mapping
"""
def __init__(self, code: str, enable_online_lookup: bool = False):
@ -43,6 +43,10 @@ class Disassembly(object):
self.address_to_function_name[jump_target] = function_name
def get_easm(self):
"""
:return:
"""
return asm.instruction_list_to_easm(self.instruction_list)

@ -5,6 +5,9 @@ import re
class EVMContract(persistent.Persistent):
"""
"""
def __init__(
self, code="", creation_code="", name="Unknown", enable_online_lookup=False
):
@ -25,7 +28,10 @@ class EVMContract(persistent.Persistent):
)
def as_dict(self):
"""
:return:
"""
return {
"name": self.name,
"code": self.code,
@ -34,15 +40,25 @@ class EVMContract(persistent.Persistent):
}
def get_easm(self):
"""
:return:
"""
return self.disassembly.get_easm()
def get_creation_easm(self):
"""
:return:
"""
return self.creation_disassembly.get_easm()
def matches_expression(self, expression):
"""
:param expression:
:return:
"""
str_eval = ""
easm_code = None

@ -21,9 +21,19 @@ class CountableList(object):
self.element_sedes = element_sedes
def serialize(self, obj):
"""
:param obj:
:return:
"""
return [self.element_sedes.serialize(e) for e in obj]
def deserialize(self, serial):
"""
:param serial:
:return:
"""
# needed for 2 reasons:
# 1. empty lists are not zero elements
# 2. underlying logs are stored as list - if empty will also except and receipts will be lost

@ -62,4 +62,7 @@ class EthJsonRpc(BaseClient):
raise BadResponseError(response)
def close(self):
"""
"""
self.session.close()

@ -17,6 +17,11 @@ def clean_hex(d):
def validate_block(block):
"""
:param block:
:return:
"""
if isinstance(block, str):
if block not in BLOCK_TAGS:
raise ValueError("invalid block tag")

@ -10,7 +10,11 @@ from pathlib import Path
def safe_decode(hex_encoded_string):
"""
:param hex_encoded_string:
:return:
"""
if hex_encoded_string.startswith("0x"):
return bytes.fromhex(hex_encoded_string[2:])
else:
@ -18,7 +22,13 @@ def safe_decode(hex_encoded_string):
def get_solc_json(file, solc_binary="solc", solc_args=None):
"""
:param file:
:param solc_binary:
:param solc_args:
:return:
"""
cmd = [solc_binary, "--combined-json", "bin,bin-runtime,srcmap,srcmap-runtime"]
if solc_args:
@ -58,6 +68,13 @@ def get_solc_json(file, solc_binary="solc", solc_args=None):
def encode_calldata(func_name, arg_types, args):
"""
:param func_name:
:param arg_types:
:param args:
:return:
"""
mid = method_id(func_name, arg_types)
function_selector = zpad(encode_int(mid), 4)
args = encode_abi(arg_types, args)
@ -65,14 +82,28 @@ def encode_calldata(func_name, arg_types, args):
def get_random_address():
"""
:return:
"""
return binascii.b2a_hex(os.urandom(20)).decode("UTF-8")
def get_indexed_address(index):
"""
:param index:
:return:
"""
return "0x" + (hex(index)[2:] * 40)
def solc_exists(version):
"""
:param version:
:return:
"""
solc_binaries = [
os.path.join(
os.environ.get("HOME", str(Path.home())),

@ -20,6 +20,11 @@ import mythril.support.signatures as sigs
def exit_with_error(format_, message):
"""
:param format_:
:param message:
"""
if format_ == "text" or format_ == "markdown":
logging.error(message)
else:
@ -29,6 +34,9 @@ def exit_with_error(format_, message):
def main():
"""
"""
parser = argparse.ArgumentParser(
description="Security analysis of Ethereum smart contracts"
)

@ -20,6 +20,9 @@ PY3 = sys.version_info >= (3,)
# Reset terminal colors at exit
def reset():
"""
"""
sys.stdout.write("\x1b[0m")
sys.stdout.flush()
@ -49,6 +52,9 @@ COLOR_ANSI = (
class LolCat(object):
"""
"""
def __init__(self, mode=256, output=sys.stdout):
self.mode = mode
self.output = output
@ -57,6 +63,11 @@ class LolCat(object):
return sum(map(lambda c: (c[0] - c[1]) ** 2, zip(rgb1, rgb2)))
def ansi(self, rgb):
"""
:param rgb:
:return:
"""
r, g, b = rgb
if self.mode in (8, 16):
@ -93,15 +104,31 @@ class LolCat(object):
return "38;5;%d" % (color,)
def wrap(self, *codes):
"""
:param codes:
:return:
"""
return "\x1b[%sm" % ("".join(codes),)
def rainbow(self, freq, i):
"""
:param freq:
:param i:
:return:
"""
r = math.sin(freq * i) * 127 + 128
g = math.sin(freq * i + 2 * math.pi / 3) * 127 + 128
b = math.sin(freq * i + 4 * math.pi / 3) * 127 + 128
return [r, g, b]
def cat(self, fd, options):
"""
:param fd:
:param options:
"""
if options.animate:
self.output.write("\x1b[?25l")
@ -113,6 +140,11 @@ class LolCat(object):
self.output.write("\x1b[?25h")
def println(self, s, options):
"""
:param s:
:param options:
"""
s = s.rstrip()
if options.force or self.output.isatty():
s = STRIP_ANSI.sub("", s)
@ -126,6 +158,12 @@ class LolCat(object):
self.output.flush()
def println_ani(self, s, options):
"""
:param s:
:param options:
:return:
"""
if not s:
return
@ -137,6 +175,11 @@ class LolCat(object):
time.sleep(1.0 / options.speed)
def println_plain(self, s, options):
"""
:param s:
:param options:
"""
for i, c in enumerate(s if PY3 else s.decode(options.charset_py2, "replace")):
rgb = self.rainbow(options.freq, options.os + i / options.spread)
self.output.write(

@ -1,3 +1,8 @@
"""
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.
"""
import logging
from typing import Union
from z3 import simplify, ExprRef, Extract
@ -12,11 +17,6 @@ from mythril.laser.ethereum.state.global_state import GlobalState
from mythril.support.loader import DynLoader
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: GlobalState, dynamic_loader: DynLoader, with_value=False

@ -19,6 +19,9 @@ class NodeFlags(Flags):
class Node:
"""
"""
def __init__(self, contract_name: str, start_addr=0, constraints=None):
constraints = constraints if constraints else []
self.contract_name = contract_name
@ -35,6 +38,10 @@ class Node:
gbl_next_uid += 1
def get_cfg_dict(self) -> Dict:
"""
:return:
"""
code = ""
for state in self.states:
instruction = state.get_current_instruction()
@ -54,6 +61,9 @@ class Node:
class Edge:
"""
"""
def __init__(
self,
node_from: int,
@ -71,4 +81,8 @@ class Edge:
@property
def as_dict(self) -> Dict[str, int]:
"""
:return:
"""
return {"from": self.node_from, "to": self.node_to}

@ -3,6 +3,12 @@ from ethereum.utils import ceil32
def calculate_native_gas(size: int, contract: str):
"""
:param size:
:param contract:
:return:
"""
gas_value = None
word_num = ceil32(size) // 32
if contract == "ecrecover":
@ -19,6 +25,11 @@ def calculate_native_gas(size: int, contract: str):
def calculate_sha3_gas(length: int):
"""
:param length:
:return:
"""
gas_val = 30 + opcodes.GSHA3WORD * (ceil32(length) // 32)
return gas_val, gas_val

@ -73,10 +73,22 @@ class StateTransition(object):
@staticmethod
def call_on_state_copy(func: Callable, func_obj: "Instruction", state: GlobalState):
"""
:param func:
:param func_obj:
:param state:
:return:
"""
global_state_copy = copy(state)
return func(func_obj, global_state_copy)
def increment_states_pc(self, states: List[GlobalState]) -> List[GlobalState]:
"""
:param states:
:return:
"""
if self.increment_pc:
for state in states:
state.mstate.pc += 1
@ -84,6 +96,11 @@ class StateTransition(object):
@staticmethod
def check_gas_usage_limit(global_state: GlobalState):
"""
:param global_state:
:return:
"""
global_state.mstate.check_gas()
if isinstance(global_state.current_transaction.gas_limit, BitVecRef):
try:
@ -99,6 +116,11 @@ class StateTransition(object):
raise OutOfGasException()
def accumulate_gas(self, global_state: GlobalState):
"""
:param global_state:
:return:
"""
if not self.enable_gas:
return global_state
opcode = global_state.instruction["opcode"]
@ -111,6 +133,12 @@ class StateTransition(object):
def wrapper(
func_obj: "Instruction", global_state: GlobalState
) -> List[GlobalState]:
"""
:param func_obj:
:param global_state:
:return:
"""
new_global_states = self.call_on_state_copy(func, func_obj, global_state)
new_global_states = [
self.accumulate_gas(state) for state in new_global_states
@ -156,10 +184,20 @@ class Instruction:
@StateTransition()
def jumpdest_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
return [global_state]
@StateTransition()
def push_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
push_instruction = global_state.get_current_instruction()
push_value = push_instruction["argument"][2:]
@ -174,12 +212,22 @@ class Instruction:
@StateTransition()
def dup_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
value = int(global_state.get_current_instruction()["opcode"][3:], 10)
global_state.mstate.stack.append(global_state.mstate.stack[-value])
return [global_state]
@StateTransition()
def swap_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
depth = int(self.op_code[4:])
stack = global_state.mstate.stack
stack[-depth - 1], stack[-1] = stack[-1], stack[-depth - 1]
@ -187,11 +235,21 @@ class Instruction:
@StateTransition()
def pop_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
global_state.mstate.stack.pop()
return [global_state]
@StateTransition()
def and_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
stack = global_state.mstate.stack
op1, op2 = stack.pop(), stack.pop()
if type(op1) == BoolRef:
@ -204,6 +262,11 @@ class Instruction:
@StateTransition()
def or_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
stack = global_state.mstate.stack
op1, op2 = stack.pop(), stack.pop()
@ -219,18 +282,33 @@ class Instruction:
@StateTransition()
def xor_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
mstate = global_state.mstate
mstate.stack.append(mstate.stack.pop() ^ mstate.stack.pop())
return [global_state]
@StateTransition()
def not_(self, global_state: GlobalState):
"""
:param global_state:
:return:
"""
mstate = global_state.mstate
mstate.stack.append(TT256M1 - mstate.stack.pop())
return [global_state]
@StateTransition()
def byte_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
mstate = global_state.mstate
op0, op1 = mstate.stack.pop(), mstate.stack.pop()
if not isinstance(op1, ExprRef):
@ -256,6 +334,11 @@ class Instruction:
# Arithmetic
@StateTransition()
def add_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
global_state.mstate.stack.append(
(
helper.pop_bitvec(global_state.mstate)
@ -266,6 +349,11 @@ class Instruction:
@StateTransition()
def sub_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
global_state.mstate.stack.append(
(
helper.pop_bitvec(global_state.mstate)
@ -276,6 +364,11 @@ class Instruction:
@StateTransition()
def mul_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
global_state.mstate.stack.append(
(
helper.pop_bitvec(global_state.mstate)
@ -286,6 +379,11 @@ class Instruction:
@StateTransition()
def div_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
op0, op1 = (
util.pop_bitvec(global_state.mstate),
util.pop_bitvec(global_state.mstate),
@ -298,6 +396,11 @@ class Instruction:
@StateTransition()
def sdiv_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
s0, s1 = (
util.pop_bitvec(global_state.mstate),
util.pop_bitvec(global_state.mstate),
@ -310,6 +413,11 @@ class Instruction:
@StateTransition()
def mod_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
s0, s1 = (
util.pop_bitvec(global_state.mstate),
util.pop_bitvec(global_state.mstate),
@ -319,6 +427,11 @@ class Instruction:
@StateTransition()
def smod_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
s0, s1 = (
util.pop_bitvec(global_state.mstate),
util.pop_bitvec(global_state.mstate),
@ -328,6 +441,11 @@ class Instruction:
@StateTransition()
def addmod_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
s0, s1, s2 = (
util.pop_bitvec(global_state.mstate),
util.pop_bitvec(global_state.mstate),
@ -338,6 +456,11 @@ class Instruction:
@StateTransition()
def mulmod_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
s0, s1, s2 = (
util.pop_bitvec(global_state.mstate),
util.pop_bitvec(global_state.mstate),
@ -348,6 +471,11 @@ class Instruction:
@StateTransition()
def exp_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
state = global_state.mstate
base, exponent = util.pop_bitvec(state), util.pop_bitvec(state)
@ -366,6 +494,11 @@ class Instruction:
@StateTransition()
def signextend_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
state = global_state.mstate
s0, s1 = state.stack.pop(), state.stack.pop()
@ -389,6 +522,11 @@ class Instruction:
# Comparisons
@StateTransition()
def lt_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
state = global_state.mstate
exp = ULT(util.pop_bitvec(state), util.pop_bitvec(state))
state.stack.append(exp)
@ -396,6 +534,11 @@ class Instruction:
@StateTransition()
def gt_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
state = global_state.mstate
exp = UGT(util.pop_bitvec(state), util.pop_bitvec(state))
state.stack.append(exp)
@ -403,6 +546,11 @@ class Instruction:
@StateTransition()
def slt_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
state = global_state.mstate
exp = util.pop_bitvec(state) < util.pop_bitvec(state)
state.stack.append(exp)
@ -410,6 +558,11 @@ class Instruction:
@StateTransition()
def sgt_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
state = global_state.mstate
exp = util.pop_bitvec(state) > util.pop_bitvec(state)
@ -418,6 +571,11 @@ class Instruction:
@StateTransition()
def eq_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
state = global_state.mstate
op1 = state.stack.pop()
@ -436,6 +594,11 @@ class Instruction:
@StateTransition()
def iszero_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
state = global_state.mstate
val = state.stack.pop()
@ -447,6 +610,11 @@ class Instruction:
# Call data
@StateTransition()
def callvalue_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
state = global_state.mstate
environment = global_state.environment
state.stack.append(environment.callvalue)
@ -455,6 +623,11 @@ class Instruction:
@StateTransition()
def calldataload_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
state = global_state.mstate
environment = global_state.environment
op0 = state.stack.pop()
@ -467,6 +640,11 @@ class Instruction:
@StateTransition()
def calldatasize_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
state = global_state.mstate
environment = global_state.environment
state.stack.append(environment.calldata.calldatasize)
@ -474,6 +652,11 @@ class Instruction:
@StateTransition()
def calldatacopy_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
state = global_state.mstate
environment = global_state.environment
op0, op1, op2 = state.stack.pop(), state.stack.pop(), state.stack.pop()
@ -568,6 +751,11 @@ class Instruction:
# Environment
@StateTransition()
def address_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
state = global_state.mstate
environment = global_state.environment
state.stack.append(environment.address)
@ -575,6 +763,11 @@ class Instruction:
@StateTransition()
def balance_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
state = global_state.mstate
address = state.stack.pop()
state.stack.append(global_state.new_bitvec("balance_at_" + str(address), 256))
@ -582,6 +775,11 @@ class Instruction:
@StateTransition()
def origin_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
state = global_state.mstate
environment = global_state.environment
state.stack.append(environment.origin)
@ -589,6 +787,11 @@ class Instruction:
@StateTransition()
def caller_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
state = global_state.mstate
environment = global_state.environment
state.stack.append(environment.sender)
@ -596,6 +799,11 @@ class Instruction:
@StateTransition()
def codesize_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
state = global_state.mstate
environment = global_state.environment
disassembly = environment.code
@ -604,6 +812,11 @@ class Instruction:
@StateTransition(enable_gas=False)
def sha3_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
global keccak_function_manager
state = global_state.mstate
@ -650,11 +863,21 @@ class Instruction:
@StateTransition()
def gasprice_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
global_state.mstate.stack.append(global_state.new_bitvec("gasprice", 256))
return [global_state]
@StateTransition()
def codecopy_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
memory_offset, code_offset, size = (
global_state.mstate.stack.pop(),
global_state.mstate.stack.pop(),
@ -741,6 +964,11 @@ class Instruction:
@StateTransition()
def extcodesize_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
state = global_state.mstate
addr = state.stack.pop()
environment = global_state.environment
@ -767,6 +995,11 @@ class Instruction:
@StateTransition()
def extcodecopy_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
# FIXME: not implemented
state = global_state.mstate
addr = state.stack.pop()
@ -776,6 +1009,11 @@ class Instruction:
@StateTransition()
def returndatacopy_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
# FIXME: not implemented
state = global_state.mstate
start, s2, size = state.stack.pop(), state.stack.pop(), state.stack.pop()
@ -784,11 +1022,21 @@ class Instruction:
@StateTransition()
def returndatasize_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
global_state.mstate.stack.append(global_state.new_bitvec("returndatasize", 256))
return [global_state]
@StateTransition()
def blockhash_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
state = global_state.mstate
blocknumber = state.stack.pop()
state.stack.append(
@ -798,21 +1046,41 @@ class Instruction:
@StateTransition()
def coinbase_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
global_state.mstate.stack.append(global_state.new_bitvec("coinbase", 256))
return [global_state]
@StateTransition()
def timestamp_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
global_state.mstate.stack.append(global_state.new_bitvec("timestamp", 256))
return [global_state]
@StateTransition()
def number_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
global_state.mstate.stack.append(global_state.new_bitvec("block_number", 256))
return [global_state]
@StateTransition()
def difficulty_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
global_state.mstate.stack.append(
global_state.new_bitvec("block_difficulty", 256)
)
@ -820,12 +1088,22 @@ class Instruction:
@StateTransition()
def gaslimit_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
global_state.mstate.stack.append(global_state.mstate.gas_limit)
return [global_state]
# Memory operations
@StateTransition()
def mload_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
state = global_state.mstate
op0 = state.stack.pop()
@ -849,6 +1127,11 @@ class Instruction:
@StateTransition()
def mstore_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
state = global_state.mstate
op0, value = state.stack.pop(), state.stack.pop()
@ -873,6 +1156,11 @@ class Instruction:
@StateTransition()
def mstore8_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
state = global_state.mstate
op0, value = state.stack.pop(), state.stack.pop()
@ -896,6 +1184,11 @@ class Instruction:
@StateTransition()
def sload_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
global keccak_function_manager
state = global_state.mstate
@ -965,6 +1258,11 @@ class Instruction:
@StateTransition()
def sstore_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
global keccak_function_manager
state = global_state.mstate
index, value = state.stack.pop(), state.stack.pop()
@ -1041,6 +1339,11 @@ class Instruction:
@StateTransition(increment_pc=False, enable_gas=False)
def jump_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
state = global_state.mstate
disassembly = global_state.environment.code
try:
@ -1075,6 +1378,11 @@ class Instruction:
@StateTransition(increment_pc=False, enable_gas=False)
def jumpi_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
state = global_state.mstate
disassembly = global_state.environment.code
min_gas, max_gas = OPCODE_GAS["JUMPI"]
@ -1144,22 +1452,42 @@ class Instruction:
@StateTransition()
def pc_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
global_state.mstate.stack.append(global_state.mstate.pc - 1)
return [global_state]
@StateTransition()
def msize_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
global_state.mstate.stack.append(global_state.new_bitvec("msize", 256))
return [global_state]
@StateTransition()
def gas_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
# TODO: Push a Constrained variable which lies between min gas and max gas
global_state.mstate.stack.append(global_state.new_bitvec("gas", 256))
return [global_state]
@StateTransition()
def log_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
# TODO: implement me
state = global_state.mstate
dpth = int(self.op_code[3:])
@ -1170,6 +1498,11 @@ class Instruction:
@StateTransition()
def create_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
# TODO: implement me
state = global_state.mstate
state.stack.pop(), state.stack.pop(), state.stack.pop()
@ -1179,6 +1512,10 @@ class Instruction:
@StateTransition()
def return_(self, global_state: GlobalState):
"""
:param global_state:
"""
state = global_state.mstate
offset, length = state.stack.pop(), state.stack.pop()
return_data = [global_state.new_bitvec("return_data", 8)]
@ -1192,6 +1529,10 @@ class Instruction:
@StateTransition()
def suicide_(self, global_state: GlobalState):
"""
:param global_state:
"""
target = global_state.mstate.stack.pop()
account_created = False
# Often the target of the suicide instruction will be symbolic
@ -1223,6 +1564,10 @@ class Instruction:
@StateTransition()
def revert_(self, global_state: GlobalState) -> None:
"""
:param global_state:
"""
state = global_state.mstate
offset, length = state.stack.pop(), state.stack.pop()
return_data = [global_state.new_bitvec("return_data", 8)]
@ -1238,20 +1583,36 @@ class Instruction:
@StateTransition()
def assert_fail_(self, global_state: GlobalState):
"""
:param global_state:
"""
# 0xfe: designated invalid opcode
raise InvalidInstruction
@StateTransition()
def invalid_(self, global_state: GlobalState):
"""
:param global_state:
"""
raise InvalidInstruction
@StateTransition()
def stop_(self, global_state: GlobalState):
"""
:param global_state:
"""
global_state.current_transaction.end(global_state)
@StateTransition()
def call_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
instr = global_state.get_current_instruction()
environment = global_state.environment
@ -1333,6 +1694,11 @@ class Instruction:
@StateTransition()
def call_post(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
instr = global_state.get_current_instruction()
try:
@ -1395,6 +1761,11 @@ class Instruction:
@StateTransition()
def callcode_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
instr = global_state.get_current_instruction()
environment = global_state.environment
@ -1429,6 +1800,11 @@ class Instruction:
@StateTransition()
def callcode_post(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
instr = global_state.get_current_instruction()
try:
@ -1489,6 +1865,11 @@ class Instruction:
@StateTransition()
def delegatecall_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
instr = global_state.get_current_instruction()
environment = global_state.environment
@ -1523,6 +1904,11 @@ class Instruction:
@StateTransition()
def delegatecall_post(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
instr = global_state.get_current_instruction()
try:
@ -1583,6 +1969,11 @@ class Instruction:
@StateTransition()
def staticcall_(self, global_state: GlobalState) -> List[GlobalState]:
"""
:param global_state:
:return:
"""
# TODO: implement me
instr = global_state.get_current_instruction()
global_state.mstate.stack.append(

@ -2,17 +2,35 @@ from z3 import ExprRef
class KeccakFunctionManager:
"""
"""
def __init__(self):
self.keccak_expression_mapping = {}
def is_keccak(self, expression: ExprRef) -> bool:
"""
:param expression:
:return:
"""
return str(expression) in self.keccak_expression_mapping.keys()
def get_argument(self, expression: str) -> ExprRef:
"""
:param expression:
:return:
"""
if not self.is_keccak(expression):
raise ValueError("Expression is not a recognized keccac result")
return self.keccak_expression_mapping[str(expression)][1]
def add_keccak(self, expression: ExprRef, argument: ExprRef) -> None:
"""
:param expression:
:param argument:
"""
index = str(expression)
self.keccak_expression_mapping[index] = (expression, argument)

@ -20,6 +20,11 @@ class NativeContractException(Exception):
def int_to_32bytes(
i: int
) -> bytes: # used because int can't fit as bytes function's input
"""
:param i:
:return:
"""
o = [0] * 32
for x in range(32):
o[31 - x] = i & 0xFF
@ -28,6 +33,12 @@ def int_to_32bytes(
def extract32(data: bytearray, i: int) -> int:
"""
:param data:
:param i:
:return:
"""
if i >= len(data):
return 0
o = data[i : min(i + 32, len(data))]
@ -36,6 +47,11 @@ def extract32(data: bytearray, i: int) -> int:
def ecrecover(data: Union[bytes, str, List[int]]) -> bytes:
"""
:param data:
:return:
"""
# TODO: Add type hints
try:
data = bytearray(data)
@ -58,6 +74,11 @@ def ecrecover(data: Union[bytes, str, List[int]]) -> bytes:
def sha256(data: Union[bytes, str, List[int]]) -> bytes:
"""
:param data:
:return:
"""
try:
data = bytes(data)
except TypeError:
@ -66,6 +87,11 @@ def sha256(data: Union[bytes, str, List[int]]) -> bytes:
def ripemd160(data: Union[bytes, str, List[int]]) -> bytes:
"""
:param data:
:return:
"""
try:
data = bytes(data)
except TypeError:
@ -76,6 +102,11 @@ def ripemd160(data: Union[bytes, str, List[int]]) -> bytes:
def identity(data: Union[bytes, str, List[int]]) -> bytes:
"""
:param data:
:return:
"""
# Group up into an array of 32 byte words instead
# of an array of bytes. If saved to memory, 32 byte
# words are currently needed, but a correct memory

@ -48,6 +48,10 @@ class Storage:
self._storage[key] = value
def keys(self) -> KeysView:
"""
:return:
"""
return self._storage.keys()
@ -90,13 +94,25 @@ class Account:
return str(self.as_dict)
def set_balance(self, balance: ExprRef) -> None:
"""
:param balance:
"""
self.balance = balance
def add_balance(self, balance: ExprRef) -> None:
"""
:param balance:
"""
self.balance += balance
@property
def as_dict(self) -> Dict:
"""
:return:
"""
return {
"nonce": self.nonce,
"code": self.code,

@ -90,6 +90,9 @@ class BaseCalldata:
class ConcreteCalldata(BaseCalldata):
"""
"""
def __init__(self, tx_id: int, calldata: list):
"""
Initializes the ConcreteCalldata object
@ -107,14 +110,27 @@ class ConcreteCalldata(BaseCalldata):
return simplify(self._calldata[item])
def concrete(self, model: Model) -> list:
"""
:param model:
:return:
"""
return self._concrete_calldata
@property
def size(self) -> int:
"""
:return:
"""
return len(self._concrete_calldata)
class BasicConcreteCalldata(BaseCalldata):
"""
"""
def __init__(self, tx_id: int, calldata: list):
"""
Initializes the ConcreteCalldata object, that doesn't use z3 arrays
@ -137,14 +153,26 @@ class BasicConcreteCalldata(BaseCalldata):
return value
def concrete(self, model: Model) -> list:
"""
:param model:
:return:
"""
return self._calldata
@property
def size(self) -> int:
"""
:return:
"""
return len(self._calldata)
class SymbolicCalldata(BaseCalldata):
"""
"""
def __init__(self, tx_id: int):
"""
Initializes the SymbolicCalldata object
@ -161,6 +189,11 @@ class SymbolicCalldata(BaseCalldata):
return simplify(If(item < self._size, simplify(self._calldata[item]), 0))
def concrete(self, model: Model) -> list:
"""
:param model:
:return:
"""
concrete_length = get_concrete_int(model.eval(self.size, model_completion=True))
result = []
for i in range(concrete_length):
@ -172,10 +205,17 @@ class SymbolicCalldata(BaseCalldata):
@property
def size(self) -> ExprRef:
"""
:return:
"""
return self._size
class BasicSymbolicCalldata(BaseCalldata):
"""
"""
def __init__(self, tx_id: int):
"""
Initializes the SymbolicCalldata object
@ -202,6 +242,11 @@ class BasicSymbolicCalldata(BaseCalldata):
return simplify(return_value)
def concrete(self, model: Model) -> list:
"""
:param model:
:return:
"""
concrete_length = get_concrete_int(model.eval(self.size, model_completion=True))
result = []
for i in range(concrete_length):
@ -213,4 +258,8 @@ class BasicSymbolicCalldata(BaseCalldata):
@property
def size(self) -> ExprRef:
"""
:return:
"""
return self._size

@ -11,12 +11,24 @@ class Constraints(list):
self.__possibility = possibility
def check_possibility(self):
"""
:return:
"""
return True
def append(self, constraint):
"""
:param constraint:
"""
super(Constraints, self).append(constraint)
def pop(self, index=-1):
"""
:param index:
"""
raise NotImplementedError
def __copy__(self):

@ -44,6 +44,10 @@ class Environment:
@property
def as_dict(self) -> Dict:
"""
:return:
"""
return dict(
active_account=self.active_account,
sender=self.sender,

@ -53,6 +53,10 @@ class GlobalState:
@property
def accounts(self) -> Dict:
"""
:return:
"""
return self.world_state.accounts
# TODO: remove this, as two instructions are confusing
@ -66,6 +70,10 @@ class GlobalState:
def current_transaction(
self
) -> Union["MessageCallTransaction", "ContractCreationTransaction", None]:
"""
:return:
"""
# TODO: Remove circular to transaction package to import Transaction classes
try:
return self.transaction_stack[-1][0]
@ -74,13 +82,27 @@ class GlobalState:
@property
def instruction(self) -> Dict:
"""
:return:
"""
return self.get_current_instruction()
def new_bitvec(self, name: str, size=256) -> BitVec:
"""
:param name:
:param size:
:return:
"""
transaction_id = self.current_transaction.id
return BitVec("{}_{}".format(transaction_id, name), size)
def annotate(self, annotation: StateAnnotation) -> None:
"""
:param annotation:
"""
self._annotations.append(annotation)
if annotation.persist_to_world_state:
@ -88,4 +110,8 @@ class GlobalState:
@property
def annotations(self) -> List[StateAnnotation]:
"""
:return:
"""
return self._annotations

@ -97,11 +97,23 @@ class MachineState:
self.depth = depth
def calculate_extension_size(self, start: int, size: int) -> int:
"""
:param start:
:param size:
:return:
"""
if self.memory_size > start + size:
return 0
return start + size - self.memory_size
def calculate_memory_gas(self, start: int, size: int):
"""
:param start:
:param size:
:return:
"""
# https://github.com/ethereum/pyethereum/blob/develop/ethereum/vm.py#L148
oldsize = self.memory_size // 32
old_totalfee = (
@ -114,6 +126,9 @@ class MachineState:
return new_totalfee - old_totalfee
def check_gas(self):
"""
"""
if self.min_gas_used > self.gas_limit:
raise OutOfGasException()
@ -163,10 +178,18 @@ class MachineState:
@property
def memory_size(self) -> int:
"""
:return:
"""
return len(self.memory)
@property
def as_dict(self) -> Dict:
"""
:return:
"""
return dict(
pc=self.pc,
stack=self.stack,

@ -5,6 +5,9 @@ from mythril.laser.ethereum import util
class Memory:
"""
"""
def __init__(self):
self._memory = []
@ -12,6 +15,10 @@ class Memory:
return len(self._memory)
def extend(self, size):
"""
:param size:
"""
self._memory.extend(bytearray(size))
def get_word_at(self, index: int) -> Union[int, BitVecRef]:

@ -77,10 +77,18 @@ class WorldState:
self._put_account(new_account)
def annotate(self, annotation: StateAnnotation) -> None:
"""
:param annotation:
"""
self._annotations.append(annotation)
@property
def annotations(self) -> List[StateAnnotation]:
"""
:return:
"""
return self._annotations
def _generate_new_address(self) -> str:

@ -2,6 +2,9 @@ from abc import ABC, abstractmethod
class BasicSearchStrategy(ABC):
"""
"""
__slots__ = "work_list", "max_depth"
def __init__(self, work_list, max_depth):
@ -13,6 +16,9 @@ class BasicSearchStrategy(ABC):
@abstractmethod
def get_strategic_global_state(self):
"""
"""
raise NotImplementedError("Must be implemented by a subclass")
def __next__(self):

@ -38,6 +38,10 @@ class DepthFirstSearchStrategy(BasicSearchStrategy):
"""
def get_strategic_global_state(self) -> GlobalState:
"""
:return:
"""
return self.work_list.pop()
@ -48,6 +52,10 @@ class BreadthFirstSearchStrategy(BasicSearchStrategy):
"""
def get_strategic_global_state(self) -> GlobalState:
"""
:return:
"""
return self.work_list.pop(0)
@ -57,6 +65,10 @@ class ReturnRandomNaivelyStrategy(BasicSearchStrategy):
"""
def get_strategic_global_state(self) -> GlobalState:
"""
:return:
"""
if len(self.work_list) > 0:
return self.work_list.pop(randrange(len(self.work_list)))
else:
@ -69,6 +81,10 @@ class ReturnWeightedRandomStrategy(BasicSearchStrategy):
"""
def get_strategic_global_state(self) -> GlobalState:
"""
:return:
"""
probability_distribution = [
1 / (global_state.mstate.depth + 1) for global_state in self.work_list
]

@ -81,6 +81,11 @@ class LaserEVM:
)
def register_hooks(self, hook_type: str, hook_dict: Dict[str, List[Callable]]):
"""
:param hook_type:
:param hook_dict:
"""
if hook_type == "pre":
entrypoint = self.pre_hooks
elif hook_type == "post":
@ -95,11 +100,21 @@ class LaserEVM:
@property
def accounts(self) -> Dict[str, Account]:
"""
:return:
"""
return self.world_state.accounts
def sym_exec(
self, main_address=None, creation_code=None, contract_name=None
) -> None:
"""
:param main_address:
:param creation_code:
:param contract_name:
"""
logging.debug("Starting LASER execution")
self.time = datetime.now()
@ -172,6 +187,12 @@ class LaserEVM:
return total_covered_instructions
def exec(self, create=False, track_gas=False) -> Union[List[GlobalState], None]:
"""
:param create:
:param track_gas:
:return:
"""
final_states = []
for global_state in self.strategy:
if self.execution_timeout and not create:
@ -201,6 +222,11 @@ class LaserEVM:
def execute_state(
self, global_state: GlobalState
) -> Tuple[List[GlobalState], Union[str, None]]:
"""
:param global_state:
:return:
"""
instructions = global_state.environment.code.instruction_list
try:
op_code = instructions[global_state.mstate.pc]["opcode"]
@ -321,6 +347,11 @@ class LaserEVM:
self.coverage[code][1][instruction_index] = True
def manage_cfg(self, opcode: str, new_states: List[GlobalState]) -> None:
"""
:param opcode:
:param new_states:
"""
if opcode == "JUMP":
assert len(new_states) <= 1
for state in new_states:
@ -416,7 +447,17 @@ class LaserEVM:
hook(global_state)
def pre_hook(self, op_code: str) -> Callable:
"""
:param op_code:
:return:
"""
def hook_decorator(func: Callable):
"""
:param func:
:return:
"""
if op_code not in self.pre_hooks.keys():
self.pre_hooks[op_code] = []
self.pre_hooks[op_code].append(func)
@ -425,7 +466,17 @@ class LaserEVM:
return hook_decorator
def post_hook(self, op_code: str) -> Callable:
"""
:param op_code:
:return:
"""
def hook_decorator(func: Callable):
"""
:param func:
:return:
"""
if op_code not in self.post_hooks.keys():
self.post_hooks[op_code] = []
self.post_hooks[op_code].append(func)

@ -94,10 +94,10 @@ class TaintRunner:
) -> TaintResult:
"""
Runs taint analysis on the statespace
:param initial_stack:
:param statespace: symbolic statespace to run taint analysis on
:param node: taint introduction node
:param state: taint introduction state
:param stack_indexes: stack indexes to introduce taint
:return: TaintResult object containing analysis results
"""
if initial_stack is None:
@ -134,6 +134,14 @@ class TaintRunner:
environment: Environment,
transaction_stack_length: int,
) -> List[Node]:
"""
:param node:
:param statespace:
:param environment:
:param transaction_stack_length:
:return:
"""
direct_children = [
statespace.nodes[edge.node_to]
for edge in statespace.edges
@ -174,6 +182,12 @@ class TaintRunner:
@staticmethod
def execute_state(record: TaintRecord, state: GlobalState) -> TaintRecord:
"""
:param record:
:param state:
:return:
"""
assert len(state.mstate.stack) == len(record.stack)
""" Runs taint analysis on a state """
record.add_state(state)
@ -210,6 +224,11 @@ class TaintRunner:
@staticmethod
def mutate_stack(record: TaintRecord, mutator: Tuple[int, int]) -> None:
"""
:param record:
:param mutator:
"""
pop, push = mutator
values = []
@ -223,16 +242,31 @@ class TaintRunner:
@staticmethod
def mutate_push(op: str, record: TaintRecord) -> None:
"""
:param op:
:param record:
"""
TaintRunner.mutate_stack(record, (0, 1))
@staticmethod
def mutate_dup(op: str, record: TaintRecord) -> None:
"""
:param op:
:param record:
"""
depth = int(op[3:])
index = len(record.stack) - depth
record.stack.append(record.stack[index])
@staticmethod
def mutate_swap(op: str, record: TaintRecord) -> None:
"""
:param op:
:param record:
"""
depth = int(op[4:])
l = len(record.stack) - 1
i = l - depth
@ -240,6 +274,12 @@ class TaintRunner:
@staticmethod
def mutate_mload(record: TaintRecord, op0: ExprRef) -> None:
"""
:param record:
:param op0:
:return:
"""
_ = record.stack.pop()
try:
index = helper.get_concrete_int(op0)
@ -252,6 +292,12 @@ class TaintRunner:
@staticmethod
def mutate_mstore(record: TaintRecord, op0: ExprRef) -> None:
"""
:param record:
:param op0:
:return:
"""
_, value_taint = record.stack.pop(), record.stack.pop()
try:
index = helper.get_concrete_int(op0)
@ -263,6 +309,12 @@ class TaintRunner:
@staticmethod
def mutate_sload(record: TaintRecord, op0: ExprRef) -> None:
"""
:param record:
:param op0:
:return:
"""
_ = record.stack.pop()
try:
index = helper.get_concrete_int(op0)
@ -275,6 +327,12 @@ class TaintRunner:
@staticmethod
def mutate_sstore(record: TaintRecord, op0: ExprRef) -> None:
"""
:param record:
:param op0:
:return:
"""
_, value_taint = record.stack.pop(), record.stack.pop()
try:
index = helper.get_concrete_int(op0)
@ -286,12 +344,22 @@ class TaintRunner:
@staticmethod
def mutate_log(record: TaintRecord, op: str) -> None:
"""
:param record:
:param op:
"""
depth = int(op[3:])
for _ in range(depth + 2):
record.stack.pop()
@staticmethod
def mutate_call(record: TaintRecord, op: str) -> None:
"""
:param record:
:param op:
"""
pops = 6
if op in ("CALL", "CALLCODE"):
pops += 1

@ -17,6 +17,10 @@ _next_transaction_id = 0
def get_next_transaction_id() -> int:
"""
:return:
"""
global _next_transaction_id
_next_transaction_id += 1
return _next_transaction_id
@ -97,6 +101,12 @@ class BaseTransaction:
self.return_data = None
def initial_global_state_from_environment(self, environment, active_function):
"""
:param environment:
:param active_function:
:return:
"""
# Initialize the execution environment
global_state = GlobalState(self.world_state, environment, None)
global_state.environment.active_function_name = active_function
@ -126,6 +136,12 @@ class MessageCallTransaction(BaseTransaction):
)
def end(self, global_state: GlobalState, return_data=None, revert=False) -> None:
"""
:param global_state:
:param return_data:
:param revert:
"""
self.return_data = return_data
raise TransactionEndSignal(global_state, revert)
@ -157,7 +173,12 @@ class ContractCreationTransaction(BaseTransaction):
)
def end(self, global_state: GlobalState, return_data=None, revert=False):
"""
:param global_state:
:param return_data:
:param revert:
"""
if (
not all([isinstance(element, int) for element in return_data])
or len(return_data) == 0

@ -12,10 +12,20 @@ TT255 = 2 ** 255
def sha3(seed: str) -> bytes:
"""
:param seed:
:return:
"""
return _sha3.keccak_256(bytes(seed)).digest()
def safe_decode(hex_encoded_string: str) -> bytes:
"""
:param hex_encoded_string:
:return:
"""
if hex_encoded_string.startswith("0x"):
return bytes.fromhex(hex_encoded_string[2:])
else:
@ -23,12 +33,23 @@ def safe_decode(hex_encoded_string: str) -> bytes:
def to_signed(i: int) -> int:
"""
:param i:
:return:
"""
return i if i < TT255 else i - TT256
def get_instruction_index(
instruction_list: List[Dict], address: int
) -> Union[int, None]:
"""
:param instruction_list:
:param address:
:return:
"""
index = 0
for instr in instruction_list:
if instr["address"] == address:
@ -38,6 +59,12 @@ def get_instruction_index(
def get_trace_line(instr: Dict, state: "MachineState") -> str:
"""
:param instr:
:param state:
:return:
"""
stack = str(state.stack[::-1])
# stack = re.sub("(\d+)", lambda m: hex(int(m.group(1))), stack)
stack = re.sub("\n", "", stack)
@ -45,6 +72,11 @@ def get_trace_line(instr: Dict, state: "MachineState") -> str:
def pop_bitvec(state: "MachineState") -> BitVecVal:
"""
:param state:
:return:
"""
# pop one element from stack, converting boolean expressions and
# concrete Python variables to BitVecVal
@ -64,6 +96,11 @@ def pop_bitvec(state: "MachineState") -> BitVecVal:
def get_concrete_int(item: Union[int, ExprRef]) -> int:
"""
:param item:
:return:
"""
if isinstance(item, int):
return item
elif isinstance(item, BitVecNumRef):
@ -84,6 +121,12 @@ def get_concrete_int(item: Union[int, ExprRef]) -> int:
def concrete_int_from_bytes(concrete_bytes: bytes, start_index: int) -> int:
"""
:param concrete_bytes:
:param start_index:
:return:
"""
concrete_bytes = [
byte.as_long() if type(byte) == BitVecNumRef else byte
for byte in concrete_bytes
@ -94,6 +137,11 @@ def concrete_int_from_bytes(concrete_bytes: bytes, start_index: int) -> int:
def concrete_int_to_bytes(val):
"""
:param val:
:return:
"""
# logging.debug("concrete_int_to_bytes " + str(val))
if type(val) == int:
return val.to_bytes(32, byteorder="big")
@ -101,6 +149,11 @@ def concrete_int_to_bytes(val):
def bytearray_to_int(arr):
"""
:param arr:
:return:
"""
o = 0
for a in arr:
o = (o << 8) + a

@ -39,15 +39,15 @@ from mythril.ethereum.interface.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
3. load contracts (from solidity, bytecode, address)
4. fire_lasers
Example:
.. code-block:: python
mythril = Mythril()
mythril.set_api_rpc_infura()
@ -70,7 +70,8 @@ class Mythril(object):
# (optional) graph
for contract in mythril.contracts:
print(mythril.graph_html(args)) # prints html or save it to file
# prints html or save it to file
print(mythril.graph_html(args))
# (optional) other funcs
mythril.dump_statespaces(args)
@ -206,6 +207,12 @@ class Mythril(object):
config.set("defaults", "dynamic_loading", "infura")
def analyze_truffle_project(self, *args, **kwargs):
"""
:param args:
:param kwargs:
:return:
"""
return analyze_truffle_project(
self.sigs, *args, **kwargs
) # just passthru by passing signatures for now
@ -248,15 +255,28 @@ class Mythril(object):
return solc_binary
def set_api_leveldb(self, leveldb):
"""
:param leveldb:
:return:
"""
self.eth_db = EthLevelDB(leveldb)
self.eth = self.eth_db
return self.eth
def set_api_rpc_infura(self):
"""
"""
self.eth = EthJsonRpc("mainnet.infura.io", 443, True)
logging.info("Using INFURA for RPC queries")
def set_api_rpc(self, rpc=None, rpctls=False):
"""
:param rpc:
:param rpctls:
"""
if rpc == "ganache":
rpcconfig = ("localhost", 8545, False)
else:
@ -279,10 +299,16 @@ class Mythril(object):
raise CriticalError("Invalid RPC settings, check help for details.")
def set_api_rpc_localhost(self):
"""
"""
self.eth = EthJsonRpc("localhost", 8545)
logging.info("Using default RPC settings: http://localhost:8545")
def set_api_from_config_path(self):
"""
"""
config = ConfigParser(allow_no_value=False)
config.optionxform = str
config.read(self.config_path, "utf-8")
@ -298,7 +324,17 @@ class Mythril(object):
self.set_api_rpc(dynamic_loading)
def search_db(self, search):
"""
:param search:
"""
def search_callback(_, address, balance):
"""
:param _:
:param address:
:param balance:
"""
print("Address: " + address + ", balance: " + str(balance))
try:
@ -308,13 +344,23 @@ class Mythril(object):
raise CriticalError("Syntax error in search expression.")
def contract_hash_to_address(self, hash):
"""
:param hash:
"""
if not re.match(r"0x[a-fA-F0-9]{64}", hash):
raise CriticalError("Invalid address hash. Expected format is '0x...'.")
print(self.eth_db.contract_hash_to_address(hash))
def load_from_bytecode(self, code, bin_runtime=False, address=None):
"""
:param code:
:param bin_runtime:
:param address:
:return:
"""
if address is None:
address = util.get_indexed_address(0)
if bin_runtime:
@ -336,6 +382,11 @@ class Mythril(object):
return address, self.contracts[-1] # return address and contract object
def load_from_address(self, address):
"""
:param address:
:return:
"""
if not re.match(r"0x[a-fA-F0-9]{40}", address):
raise CriticalError("Invalid contract address. Expected format is '0x...'.")
@ -423,7 +474,16 @@ class Mythril(object):
execution_timeout=None,
create_timeout=None,
):
"""
:param strategy:
:param contract:
:param address:
:param max_depth:
:param execution_timeout:
:param create_timeout:
:return:
"""
sym = SymExecWrapper(
contract,
address,
@ -451,6 +511,18 @@ class Mythril(object):
execution_timeout=None,
create_timeout=None,
):
"""
:param strategy:
:param contract:
:param address:
:param max_depth:
:param enable_physics:
:param phrackify:
:param execution_timeout:
:param create_timeout:
:return:
"""
sym = SymExecWrapper(
contract,
address,
@ -478,7 +550,19 @@ class Mythril(object):
create_timeout=None,
transaction_count=None,
):
"""
:param strategy:
:param contracts:
:param address:
:param modules:
:param verbose_report:
:param max_depth:
:param execution_timeout:
:param create_timeout:
:param transaction_count:
:return:
"""
all_issues = []
for contract in contracts or self.contracts:
sym = SymExecWrapper(
@ -513,6 +597,12 @@ class Mythril(object):
return report
def get_state_variable_from_storage(self, address, params=None):
"""
:param address:
:param params:
:return:
"""
if params is None:
params = []
(position, length, mappings) = (0, 1, [])
@ -593,8 +683,18 @@ class Mythril(object):
@staticmethod
def disassemble(contract):
"""
:param contract:
:return:
"""
return contract.get_easm()
@staticmethod
def hash_for_function_signature(sig):
"""
:param sig:
:return:
"""
return "0x%s" % utils.sha3(sig)[:4].hex()

@ -5,6 +5,9 @@ from mythril.exceptions import NoContractFoundError
class SourceMapping:
"""
"""
def __init__(self, solidity_file_idx, offset, length, lineno):
self.solidity_file_idx = solidity_file_idx
self.offset = offset
@ -13,12 +16,18 @@ class SourceMapping:
class SolidityFile:
"""
"""
def __init__(self, filename, data):
self.filename = filename
self.data = data
class SourceCodeInfo:
"""
"""
def __init__(self, filename, lineno, code):
self.filename = filename
self.lineno = lineno
@ -26,6 +35,12 @@ class SourceCodeInfo:
def get_contracts_from_file(input_file, solc_args=None, solc_binary="solc"):
"""
:param input_file:
:param solc_args:
:param solc_binary:
"""
data = get_solc_json(input_file, solc_args=solc_args, solc_binary=solc_binary)
try:
@ -43,6 +58,9 @@ def get_contracts_from_file(input_file, solc_args=None, solc_binary="solc"):
class SolidityContract(EVMContract):
"""
"""
def __init__(self, input_file, name=None, solc_args=None, solc_binary="solc"):
data = get_solc_json(input_file, solc_args=solc_args, solc_binary=solc_binary)
@ -101,6 +119,12 @@ class SolidityContract(EVMContract):
super().__init__(code, creation_code, name=name)
def get_source_info(self, address, constructor=False):
"""
:param address:
:param constructor:
:return:
"""
disassembly = self.creation_disassembly if constructor else self.disassembly
mappings = self.constructor_mappings if constructor else self.mappings
index = helper.get_instruction_index(disassembly.instruction_list, address)

@ -4,6 +4,9 @@ import re
class DynLoader:
"""
"""
def __init__(self, eth, contract_loading=True, storage_loading=True):
self.eth = eth
self.storage_cache = {}
@ -11,7 +14,12 @@ class DynLoader:
self.storage_loading = storage_loading
def read_storage(self, contract_address, index):
"""
:param contract_address:
:param index:
:return:
"""
if not self.storage_loading:
raise Exception(
"Cannot load from the storage when the storage_loading flag is false"
@ -42,7 +50,12 @@ class DynLoader:
return data
def dynld(self, contract_address, dependency_address):
"""
:param contract_address:
:param dependency_address:
:return:
"""
if not self.contract_loading:
raise ValueError("Cannot load contract when contract_loading flag is false")

@ -22,8 +22,19 @@ def synchronized(sync_lock):
""" Synchronization decorator """
def wrapper(f):
"""
:param f:
:return:
"""
@functools.wraps(f)
def inner_wrapper(*args, **kw):
"""
:param args:
:param kw:
:return:
"""
with sync_lock:
return f(*args, **kw)
@ -76,7 +87,14 @@ class SQLiteDB(object):
class SignatureDB(object, metaclass=Singleton):
"""
"""
def __init__(self, enable_online_lookup: bool = False, path: str = None) -> None:
"""
:param enable_online_lookup:
:param path:
"""
self.enable_online_lookup = enable_online_lookup
self.online_lookup_miss = set()
self.online_lookup_timeout = 0
@ -193,6 +211,8 @@ class SignatureDB(object, metaclass=Singleton):
):
"""
Import Function Signatures from solidity source files
:param solc_binary:
:param solc_args:
:param file_path: solidity source code file path
:return:
"""

@ -16,7 +16,11 @@ from mythril.laser.ethereum.util import get_instruction_index
def analyze_truffle_project(sigs, args):
"""
:param sigs:
:param args:
"""
project_root = os.getcwd()
build_dir = os.path.join(project_root, "build", "contracts")
@ -131,6 +135,11 @@ def analyze_truffle_project(sigs, args):
def get_sigs_from_truffle(sigs, contract_data):
"""
:param sigs:
:param contract_data:
"""
abis = contract_data["abi"]
for abi in abis:
if abi["type"] != "function":
@ -142,6 +151,12 @@ def get_sigs_from_truffle(sigs, contract_data):
def get_mappings(source, deployed_source_map):
"""
:param source:
:param deployed_source_map:
:return:
"""
mappings = []
for item in deployed_source_map:
mapping = item.split(":")

@ -32,6 +32,9 @@ class VerifyVersionCommand(install):
description = "verify that the git tag matches our version"
def run(self):
"""
"""
tag = os.getenv("CIRCLE_TAG")
if tag != VERSION:

@ -17,9 +17,16 @@ MYTHRIL_DIR = TESTS_DIR / "mythril_dir"
class BaseTestCase(TestCase):
def setUp(self):
"""
"""
self.changed_files = []
def compare_files_error_message(self):
"""
:return:
"""
message = "Following output files are changed, compare them manually to see differences: \n"
for (input_file, expected, current) in self.changed_files:
@ -30,9 +37,18 @@ class BaseTestCase(TestCase):
return message
def found_changed_files(self, input_file, output_expected, output_current):
"""
:param input_file:
:param output_expected:
:param output_current:
"""
self.changed_files.append((input_file, output_expected, output_current))
def assert_and_show_changed_files(self):
"""
"""
self.assertEqual(
0, len(self.changed_files), msg=self.compare_files_error_message()
)

@ -5,6 +5,11 @@ MYTH = str(PROJECT_DIR / "myth")
def output_of(command):
"""
:param command:
:return:
"""
return check_output(command, shell=True).decode("UTF-8")

@ -4,6 +4,9 @@ from tests import BaseTestCase
class EVMContractTestCase(BaseTestCase):
def setUp(self):
"""
"""
super().setUp()
self.code = "0x60606040525b603c5b60006010603e565b9050593681016040523660008237602060003683856040603f5a0204f41560545760206000f35bfe5b50565b005b73c3b2ae46792547a96b9f84405e36d0e07edcd05c5b905600a165627a7a7230582062a884f947232ada573f95940cce9c8bfb7e4e14e21df5af4e884941afb55e590029"
self.creation_code = "0x60606040525b603c5b60006010603e565b9050593681016040523660008237602060003683856040603f5a0204f41560545760206000f35bfe5b50565b005b73c3b2ae46792547a96b9f84405e36d0e07edcd05c5b905600a165627a7a7230582062a884f947232ada573f95940cce9c8bfb7e4e14e21df5af4e884941afb55e590029"
@ -11,6 +14,9 @@ class EVMContractTestCase(BaseTestCase):
class Getinstruction_listTestCase(EVMContractTestCase):
def runTest(self):
"""
"""
contract = EVMContract(self.code, self.creation_code)
disassembly = contract.disassembly
@ -24,6 +30,9 @@ class Getinstruction_listTestCase(EVMContractTestCase):
class GetEASMTestCase(EVMContractTestCase):
def runTest(self):
"""
"""
contract = EVMContract(self.code)
instruction_list = contract.get_easm()
@ -36,6 +45,9 @@ class GetEASMTestCase(EVMContractTestCase):
class MatchesExpressionTestCase(EVMContractTestCase):
def runTest(self):
"""
"""
contract = EVMContract(self.code)
self.assertTrue(

@ -25,6 +25,11 @@ test_types = [
def load_test_data(designations):
"""
:param designations:
:return:
"""
return_data = []
for designation in designations:
for file_reference in (evm_test_dir / designation).iterdir():

@ -87,7 +87,9 @@ def _test_natives(laser_info, test_list, test_name):
class NativeTests(BaseTestCase):
@staticmethod
def runTest():
"""
"""
disassembly = SolidityContract(
"./tests/native_tests.sol", solc_binary=Mythril._init_solc_binary("0.5.0")
).disassembly

@ -82,6 +82,11 @@ def _assert_empty_json(changed_files):
actual = []
def ordered(obj):
"""
:param obj:
:return:
"""
if isinstance(obj, dict):
return sorted((k, ordered(v)) for k, v in obj.items())
elif isinstance(obj, list):
@ -126,6 +131,11 @@ def _get_changed_files_json(report_builder, reports):
postfix = ".json"
def ordered(obj):
"""
:param obj:
:return:
"""
if isinstance(obj, dict):
return sorted((k, ordered(v)) for k, v in obj.items())
elif isinstance(obj, list):

@ -8,9 +8,15 @@ class RpcTest(BaseTestCase):
client = None
def setUp(self):
"""
"""
self.client = EthJsonRpc()
def tearDown(self):
"""
"""
self.client.close()
def test_eth_coinbase(self):

Loading…
Cancel
Save