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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -43,6 +43,10 @@ class Disassembly(object):
self.address_to_function_name[jump_target] = function_name self.address_to_function_name[jump_target] = function_name
def get_easm(self): def get_easm(self):
"""
:return:
"""
return asm.instruction_list_to_easm(self.instruction_list) return asm.instruction_list_to_easm(self.instruction_list)

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

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

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

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

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

@ -20,6 +20,9 @@ PY3 = sys.version_info >= (3,)
# Reset terminal colors at exit # Reset terminal colors at exit
def reset(): def reset():
"""
"""
sys.stdout.write("\x1b[0m") sys.stdout.write("\x1b[0m")
sys.stdout.flush() sys.stdout.flush()
@ -49,6 +52,9 @@ COLOR_ANSI = (
class LolCat(object): class LolCat(object):
"""
"""
def __init__(self, mode=256, output=sys.stdout): def __init__(self, mode=256, output=sys.stdout):
self.mode = mode self.mode = mode
self.output = output self.output = output
@ -57,6 +63,11 @@ class LolCat(object):
return sum(map(lambda c: (c[0] - c[1]) ** 2, zip(rgb1, rgb2))) return sum(map(lambda c: (c[0] - c[1]) ** 2, zip(rgb1, rgb2)))
def ansi(self, rgb): def ansi(self, rgb):
"""
:param rgb:
:return:
"""
r, g, b = rgb r, g, b = rgb
if self.mode in (8, 16): if self.mode in (8, 16):
@ -93,15 +104,31 @@ class LolCat(object):
return "38;5;%d" % (color,) return "38;5;%d" % (color,)
def wrap(self, *codes): def wrap(self, *codes):
"""
:param codes:
:return:
"""
return "\x1b[%sm" % ("".join(codes),) return "\x1b[%sm" % ("".join(codes),)
def rainbow(self, freq, i): def rainbow(self, freq, i):
"""
:param freq:
:param i:
:return:
"""
r = math.sin(freq * i) * 127 + 128 r = math.sin(freq * i) * 127 + 128
g = math.sin(freq * i + 2 * math.pi / 3) * 127 + 128 g = math.sin(freq * i + 2 * math.pi / 3) * 127 + 128
b = math.sin(freq * i + 4 * math.pi / 3) * 127 + 128 b = math.sin(freq * i + 4 * math.pi / 3) * 127 + 128
return [r, g, b] return [r, g, b]
def cat(self, fd, options): def cat(self, fd, options):
"""
:param fd:
:param options:
"""
if options.animate: if options.animate:
self.output.write("\x1b[?25l") self.output.write("\x1b[?25l")
@ -113,6 +140,11 @@ class LolCat(object):
self.output.write("\x1b[?25h") self.output.write("\x1b[?25h")
def println(self, s, options): def println(self, s, options):
"""
:param s:
:param options:
"""
s = s.rstrip() s = s.rstrip()
if options.force or self.output.isatty(): if options.force or self.output.isatty():
s = STRIP_ANSI.sub("", s) s = STRIP_ANSI.sub("", s)
@ -126,6 +158,12 @@ class LolCat(object):
self.output.flush() self.output.flush()
def println_ani(self, s, options): def println_ani(self, s, options):
"""
:param s:
:param options:
:return:
"""
if not s: if not s:
return return
@ -137,6 +175,11 @@ class LolCat(object):
time.sleep(1.0 / options.speed) time.sleep(1.0 / options.speed)
def println_plain(self, s, options): 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")): 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) rgb = self.rainbow(options.freq, options.os + i / options.spread)
self.output.write( 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 import logging
from typing import Union from typing import Union
from z3 import simplify, ExprRef, Extract 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 from mythril.support.loader import DynLoader
import re 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( def get_call_parameters(
global_state: GlobalState, dynamic_loader: DynLoader, with_value=False global_state: GlobalState, dynamic_loader: DynLoader, with_value=False

@ -19,6 +19,9 @@ class NodeFlags(Flags):
class Node: class Node:
"""
"""
def __init__(self, contract_name: str, start_addr=0, constraints=None): def __init__(self, contract_name: str, start_addr=0, constraints=None):
constraints = constraints if constraints else [] constraints = constraints if constraints else []
self.contract_name = contract_name self.contract_name = contract_name
@ -35,6 +38,10 @@ class Node:
gbl_next_uid += 1 gbl_next_uid += 1
def get_cfg_dict(self) -> Dict: def get_cfg_dict(self) -> Dict:
"""
:return:
"""
code = "" code = ""
for state in self.states: for state in self.states:
instruction = state.get_current_instruction() instruction = state.get_current_instruction()
@ -54,6 +61,9 @@ class Node:
class Edge: class Edge:
"""
"""
def __init__( def __init__(
self, self,
node_from: int, node_from: int,
@ -71,4 +81,8 @@ class Edge:
@property @property
def as_dict(self) -> Dict[str, int]: def as_dict(self) -> Dict[str, int]:
"""
:return:
"""
return {"from": self.node_from, "to": self.node_to} 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): def calculate_native_gas(size: int, contract: str):
"""
:param size:
:param contract:
:return:
"""
gas_value = None gas_value = None
word_num = ceil32(size) // 32 word_num = ceil32(size) // 32
if contract == "ecrecover": if contract == "ecrecover":
@ -19,6 +25,11 @@ def calculate_native_gas(size: int, contract: str):
def calculate_sha3_gas(length: int): def calculate_sha3_gas(length: int):
"""
:param length:
:return:
"""
gas_val = 30 + opcodes.GSHA3WORD * (ceil32(length) // 32) gas_val = 30 + opcodes.GSHA3WORD * (ceil32(length) // 32)
return gas_val, gas_val return gas_val, gas_val

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -25,6 +25,11 @@ test_types = [
def load_test_data(designations): def load_test_data(designations):
"""
:param designations:
:return:
"""
return_data = [] return_data = []
for designation in designations: for designation in designations:
for file_reference in (evm_test_dir / designation).iterdir(): 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): class NativeTests(BaseTestCase):
@staticmethod @staticmethod
def runTest(): def runTest():
"""
"""
disassembly = SolidityContract( disassembly = SolidityContract(
"./tests/native_tests.sol", solc_binary=Mythril._init_solc_binary("0.5.0") "./tests/native_tests.sol", solc_binary=Mythril._init_solc_binary("0.5.0")
).disassembly ).disassembly

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

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

Loading…
Cancel
Save