From 55153a1469c180aecb2901eda1630150b3a29fa8 Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Thu, 13 Dec 2018 17:29:38 +0100 Subject: [PATCH] Add basic docstrings to all classes and functions --- mythril/analysis/callgraph.py | 19 + mythril/analysis/modules/base.py | 20 +- mythril/analysis/modules/delegatecall.py | 1 + .../modules/dependence_on_predictable_vars.py | 10 + mythril/analysis/modules/deprecated_ops.py | 12 +- mythril/analysis/modules/ether_thief.py | 10 + mythril/analysis/modules/exceptions.py | 5 + mythril/analysis/modules/external_calls.py | 10 + mythril/analysis/modules/integer.py | 1 + mythril/analysis/modules/multiple_sends.py | 9 +- mythril/analysis/modules/suicide.py | 10 + .../modules/transaction_order_dependence.py | 1 + mythril/analysis/modules/unchecked_retval.py | 5 + mythril/analysis/ops.py | 17 + mythril/analysis/report.py | 33 ++ mythril/analysis/security.py | 20 + mythril/analysis/solver.py | 12 +- mythril/analysis/symbolic.py | 5 + mythril/analysis/traceexplore.py | 10 + mythril/disassembler/asm.py | 20 +- mythril/disassembler/disassembly.py | 10 +- mythril/ethereum/evmcontract.py | 16 + .../interface/leveldb/accountindexing.py | 10 + mythril/ethereum/interface/rpc/client.py | 3 + mythril/ethereum/interface/rpc/utils.py | 5 + mythril/ethereum/util.py | 31 ++ mythril/interfaces/cli.py | 8 + mythril/interfaces/epic.py | 43 ++ mythril/laser/ethereum/call.py | 10 +- mythril/laser/ethereum/cfg.py | 14 + mythril/laser/ethereum/gas.py | 11 + mythril/laser/ethereum/instructions.py | 391 ++++++++++++++++++ mythril/laser/ethereum/keccak.py | 18 + mythril/laser/ethereum/natives.py | 31 ++ mythril/laser/ethereum/state/account.py | 16 + mythril/laser/ethereum/state/calldata.py | 49 +++ mythril/laser/ethereum/state/constraints.py | 12 + mythril/laser/ethereum/state/environment.py | 4 + mythril/laser/ethereum/state/global_state.py | 26 ++ mythril/laser/ethereum/state/machine_state.py | 23 ++ mythril/laser/ethereum/state/memory.py | 7 + mythril/laser/ethereum/state/world_state.py | 8 + mythril/laser/ethereum/strategy/__init__.py | 6 + mythril/laser/ethereum/strategy/basic.py | 16 + mythril/laser/ethereum/svm.py | 51 +++ mythril/laser/ethereum/taint_analysis.py | 70 +++- .../transaction/transaction_models.py | 21 + mythril/laser/ethereum/util.py | 53 +++ mythril/mythril.py | 108 ++++- mythril/solidity/soliditycontract.py | 24 ++ mythril/support/loader.py | 13 + mythril/support/signatures.py | 20 + mythril/support/truffle.py | 15 + setup.py | 3 + tests/__init__.py | 16 + tests/cmd_line_test.py | 5 + tests/evmcontract_test.py | 12 + tests/laser/evm_testsuite/evm_test.py | 5 + tests/native_test.py | 2 + tests/report_test.py | 10 + tests/rpc_test.py | 6 + 61 files changed, 1410 insertions(+), 22 deletions(-) diff --git a/mythril/analysis/callgraph.py b/mythril/analysis/callgraph.py index b5dd1ee3..9efc5f03 100644 --- a/mythril/analysis/callgraph.py +++ b/mythril/analysis/callgraph.py @@ -121,6 +121,12 @@ phrack_color = { def extract_nodes(statespace, color_map): + """ + + :param statespace: + :param color_map: + :return: + """ nodes = [] for node_key in statespace.nodes: node = statespace.nodes[node_key] @@ -168,6 +174,11 @@ def extract_nodes(statespace, color_map): def extract_edges(statespace): + """ + + :param statespace: + :return: + """ edges = [] for edge in statespace.edges: if edge.condition is None: @@ -200,6 +211,14 @@ def generate_graph( physics=False, phrackify=False, ): + """ + + :param statespace: + :param title: + :param physics: + :param phrackify: + :return: + """ env = Environment( loader=PackageLoader("mythril.analysis"), autoescape=select_autoescape(["html", "xml"]), diff --git a/mythril/analysis/modules/base.py b/mythril/analysis/modules/base.py index c6824a90..c50ff9d8 100644 --- a/mythril/analysis/modules/base.py +++ b/mythril/analysis/modules/base.py @@ -3,6 +3,10 @@ from typing import List class DetectionModule: + """The base detection module. + + All custom-built detection modules must inherit from this class. + """ def __init__( self, name: str, @@ -10,7 +14,15 @@ class DetectionModule: hooks: List[str], description: str, entrypoint: str = "post", - ): + ) -> None: + """ + + :param name: + :param swc_id: + :param hooks: + :param description: + :param entrypoint: + """ self.name = name self.swc_id = swc_id self.hooks = hooks @@ -23,9 +35,13 @@ class DetectionModule: self.entrypoint = entrypoint def execute(self, statespace): + """ The entry point for execution, which is being called by Mythril. + :param statespace: + :return: + """ raise NotImplementedError() - def __repr__(self): + def __repr__(self) -> str: return ( "<" "DetectionModule " diff --git a/mythril/analysis/modules/delegatecall.py b/mythril/analysis/modules/delegatecall.py index 7d70e735..a50d6eb7 100644 --- a/mythril/analysis/modules/delegatecall.py +++ b/mythril/analysis/modules/delegatecall.py @@ -7,6 +7,7 @@ import logging class DelegateCallModule(DetectionModule): + """This module detects calldata being forwarded using DELEGATECALL.""" def __init__(self): super().__init__( name="DELEGATECALL Usage in Fallback Function", diff --git a/mythril/analysis/modules/dependence_on_predictable_vars.py b/mythril/analysis/modules/dependence_on_predictable_vars.py index dee9f07a..546d9fc8 100644 --- a/mythril/analysis/modules/dependence_on_predictable_vars.py +++ b/mythril/analysis/modules/dependence_on_predictable_vars.py @@ -10,6 +10,7 @@ import logging class PredictableDependenceModule(DetectionModule): + """This module detects whether Ether is sent using predictable parameters.""" def __init__(self): super().__init__( name="Dependence of Predictable Variables", @@ -23,7 +24,11 @@ class PredictableDependenceModule(DetectionModule): ) def execute(self, statespace): + """ + :param statespace: + :return: + """ logging.debug("Executing module: DEPENDENCE_ON_PREDICTABLE_VARS") issues = [] @@ -169,6 +174,11 @@ class PredictableDependenceModule(DetectionModule): return issues def solve(self, call): + """ + + :param call: + :return: + """ try: model = solver.get_model(call.node.constraints) logging.debug("[DEPENDENCE_ON_PREDICTABLE_VARS] MODEL: " + str(model)) diff --git a/mythril/analysis/modules/deprecated_ops.py b/mythril/analysis/modules/deprecated_ops.py index e8dc22aa..0bd011f1 100644 --- a/mythril/analysis/modules/deprecated_ops.py +++ b/mythril/analysis/modules/deprecated_ops.py @@ -50,22 +50,32 @@ def _analyze_state(state): class DeprecatedOperationsModule(DetectionModule): + """This module checks for the usage of deprecated op codes.""" def __init__(self): super().__init__( name="Deprecated Operations", swc_id=DEPRICATED_FUNCTIONS_USAGE, hooks=["ORIGIN", "CALLCODE"], - description=(DESCRIPTION), + description=DESCRIPTION, entrypoint="callback", ) self._issues = [] def execute(self, state: GlobalState): + """ + + :param state: + :return: + """ self._issues.extend(_analyze_state(state)) return self.issues @property def issues(self): + """ + + :return: + """ return self._issues diff --git a/mythril/analysis/modules/ether_thief.py b/mythril/analysis/modules/ether_thief.py index 292e2896..598483df 100644 --- a/mythril/analysis/modules/ether_thief.py +++ b/mythril/analysis/modules/ether_thief.py @@ -75,6 +75,7 @@ def _analyze_state(state): class EtherThief(DetectionModule): + """This module search for cases where Ether can be withdrawn to a user-specified address.""" def __init__(self): super().__init__( name="Ether Thief", @@ -86,11 +87,20 @@ class EtherThief(DetectionModule): self._issues = [] def execute(self, state: GlobalState): + """ + + :param state: + :return: + """ self._issues.extend(_analyze_state(state)) return self.issues @property def issues(self): + """ + + :return: + """ return self._issues diff --git a/mythril/analysis/modules/exceptions.py b/mythril/analysis/modules/exceptions.py index 41a6a8e8..2da8b325 100644 --- a/mythril/analysis/modules/exceptions.py +++ b/mythril/analysis/modules/exceptions.py @@ -7,6 +7,7 @@ import logging class ReachableExceptionsModule(DetectionModule): + """This module checks whether any exception states are reachable.""" def __init__(self): super().__init__( name="Reachable Exceptions", @@ -16,7 +17,11 @@ class ReachableExceptionsModule(DetectionModule): ) def execute(self, statespace): + """ + :param statespace: + :return: + """ logging.debug("Executing module: EXCEPTIONS") issues = [] diff --git a/mythril/analysis/modules/external_calls.py b/mythril/analysis/modules/external_calls.py index 1f7e27aa..aa4ff3b3 100644 --- a/mythril/analysis/modules/external_calls.py +++ b/mythril/analysis/modules/external_calls.py @@ -89,6 +89,7 @@ def _analyze_state(state): class ExternalCalls(DetectionModule): + """This module searches for low level calls (e.g. call.value()) that forward all gas to the callee.""" def __init__(self): super().__init__( name="External calls", @@ -100,11 +101,20 @@ class ExternalCalls(DetectionModule): self._issues = [] def execute(self, state: GlobalState): + """ + + :param state: + :return: + """ self._issues.extend(_analyze_state(state)) return self.issues @property def issues(self): + """ + + :return: + """ return self._issues diff --git a/mythril/analysis/modules/integer.py b/mythril/analysis/modules/integer.py index 515a0e85..dc29fc75 100644 --- a/mythril/analysis/modules/integer.py +++ b/mythril/analysis/modules/integer.py @@ -12,6 +12,7 @@ import logging class IntegerOverflowUnderflowModule(DetectionModule): + """This module searches for integer over- and underflows.""" def __init__(self): super().__init__( name="Integer Overflow and Underflow", diff --git a/mythril/analysis/modules/multiple_sends.py b/mythril/analysis/modules/multiple_sends.py index 66c994b2..6c04f79a 100644 --- a/mythril/analysis/modules/multiple_sends.py +++ b/mythril/analysis/modules/multiple_sends.py @@ -5,6 +5,7 @@ from mythril.laser.ethereum.cfg import JumpType class MultipleSendsModule(DetectionModule): + """This module checks for multiple sends in a single transaction.""" def __init__(self): super().__init__( name="Multiple Sends", @@ -14,6 +15,11 @@ class MultipleSendsModule(DetectionModule): ) def execute(self, statespace): + """ + + :param statespace: + :return: + """ issues = [] for call in statespace.calls: @@ -59,7 +65,8 @@ class MultipleSendsModule(DetectionModule): sending_children = list(filter(lambda c: c.node in children, statespace.calls)) return sending_children - def _explore_states(self, call, statespace): + @staticmethod + def _explore_states(call, statespace): other_calls = list( filter( lambda other: other.node == call.node diff --git a/mythril/analysis/modules/suicide.py b/mythril/analysis/modules/suicide.py index 403da4f5..95b5e741 100644 --- a/mythril/analysis/modules/suicide.py +++ b/mythril/analysis/modules/suicide.py @@ -58,6 +58,7 @@ def _analyze_state(state): class SuicideModule(DetectionModule): + """This module checks if the contact can be 'accidentally' killed by anyone.""" def __init__(self): super().__init__( name="Unprotected Suicide", @@ -69,11 +70,20 @@ class SuicideModule(DetectionModule): self._issues = [] def execute(self, state: GlobalState): + """ + + :param state: + :return: + """ self._issues.extend(_analyze_state(state)) return self.issues @property def issues(self): + """ + + :return: + """ return self._issues diff --git a/mythril/analysis/modules/transaction_order_dependence.py b/mythril/analysis/modules/transaction_order_dependence.py index 88f2c5d5..357c5356 100644 --- a/mythril/analysis/modules/transaction_order_dependence.py +++ b/mythril/analysis/modules/transaction_order_dependence.py @@ -11,6 +11,7 @@ from mythril.exceptions import UnsatError class TxOrderDependenceModule(DetectionModule): + """This module finds the existance of transaction order dependence.""" def __init__(self): super().__init__( name="Transaction Order Dependence", diff --git a/mythril/analysis/modules/unchecked_retval.py b/mythril/analysis/modules/unchecked_retval.py index 083482ec..39f01003 100644 --- a/mythril/analysis/modules/unchecked_retval.py +++ b/mythril/analysis/modules/unchecked_retval.py @@ -8,6 +8,7 @@ import re class UncheckedRetvalModule(DetectionModule): + """This module checks whether CALL return value is checked.""" def __init__(self): super().__init__( name="Unchecked Return Value", @@ -25,7 +26,11 @@ class UncheckedRetvalModule(DetectionModule): ) def execute(self, statespace): + """ + :param statespace: + :return: + """ logging.debug("Executing module: UNCHECKED_RETVAL") issues = [] diff --git a/mythril/analysis/ops.py b/mythril/analysis/ops.py index 6c3cfea1..b743b4c7 100644 --- a/mythril/analysis/ops.py +++ b/mythril/analysis/ops.py @@ -9,6 +9,9 @@ class VarType(Enum): class Variable: + """ + + """ def __init__(self, val, _type): self.val = val self.type = _type @@ -18,6 +21,11 @@ class Variable: def get_variable(i): + """ + + :param i: + :return: + """ try: return Variable(util.get_concrete_int(i), VarType.CONCRETE) except TypeError: @@ -25,6 +33,9 @@ def get_variable(i): class Op: + """ + + """ def __init__(self, node, state, state_index): self.node = node self.state = state @@ -32,6 +43,9 @@ class Op: class Call(Op): + """ + + """ def __init__( self, node, @@ -53,6 +67,9 @@ class Call(Op): class SStore(Op): + """ + + """ def __init__(self, node, state, state_index, value): super().__init__(node, state, state_index) self.value = value diff --git a/mythril/analysis/report.py b/mythril/analysis/report.py index 97ca0790..faf9b705 100644 --- a/mythril/analysis/report.py +++ b/mythril/analysis/report.py @@ -7,6 +7,9 @@ import hashlib class Issue: + """ + + """ def __init__( self, contract, @@ -46,7 +49,10 @@ class Issue: @property def as_dict(self): + """ + :return: + """ issue = { "title": self.title, "swc-id": self.swc_id, @@ -70,6 +76,10 @@ class Issue: return issue def add_code_info(self, contract): + """ + + :param contract: + """ if self.address: codeinfo = contract.get_source_info( self.address, constructor=(self.function == "constructor") @@ -80,6 +90,9 @@ class Issue: class Report: + """ + + """ environment = Environment( loader=PackageLoader("mythril.analysis"), trim_blocks=True ) @@ -90,15 +103,27 @@ class Report: pass def sorted_issues(self): + """ + + :return: + """ issue_list = [issue.as_dict for key, issue in self.issues.items()] return sorted(issue_list, key=operator.itemgetter("address", "title")) def append_issue(self, issue): + """ + + :param issue: + """ m = hashlib.md5() m.update((issue.contract + str(issue.address) + issue.title).encode("utf-8")) self.issues[m.digest()] = issue def as_text(self): + """ + + :return: + """ name = self._file_name() template = Report.environment.get_template("report_as_text.jinja2") return template.render( @@ -106,6 +131,10 @@ class Report: ) def as_json(self): + """ + + :return: + """ result = {"success": True, "error": None, "issues": self.sorted_issues()} return json.dumps(result, sort_keys=True) @@ -124,6 +153,10 @@ class Report: return json.dumps(result, sort_keys=True) def as_markdown(self): + """ + + :return: + """ filename = self._file_name() template = Report.environment.get_template("report_as_markdown.jinja2") return template.render( diff --git a/mythril/analysis/security.py b/mythril/analysis/security.py index a3dbecc6..593a5369 100644 --- a/mythril/analysis/security.py +++ b/mythril/analysis/security.py @@ -10,12 +10,20 @@ OPCODE_LIST = [c[0] for _, c in opcodes.items()] def reset_callback_modules(): + """ + + """ modules = get_detection_modules("callback") for module in modules: module.detector._issues = [] def get_detection_module_hooks(modules): + """ + + :param modules: + :return: + """ hook_dict = defaultdict(list) _modules = get_detection_modules(entrypoint="callback", include_modules=modules) for module in _modules: @@ -36,6 +44,12 @@ def get_detection_module_hooks(modules): def get_detection_modules(entrypoint, include_modules=()): + """ + + :param entrypoint: + :param include_modules: + :return: + """ include_modules = list(include_modules) _modules = [] @@ -59,6 +73,12 @@ def get_detection_modules(entrypoint, include_modules=()): def fire_lasers(statespace, module_names=()): + """ + + :param statespace: + :param module_names: + :return: + """ logging.info("Starting analysis") issues = [] diff --git a/mythril/analysis/solver.py b/mythril/analysis/solver.py index 8666931d..f995a5d6 100644 --- a/mythril/analysis/solver.py +++ b/mythril/analysis/solver.py @@ -7,6 +7,13 @@ import logging def get_model(constraints, minimize=(), maximize=()): + """ + + :param constraints: + :param minimize: + :param maximize: + :return: + """ s = Optimize() s.set("timeout", 100000) @@ -32,7 +39,11 @@ def get_model(constraints, minimize=(), maximize=()): def pretty_print_model(model): + """ + :param model: + :return: + """ ret = "" for d in model.decls(): @@ -57,7 +68,6 @@ def get_transaction_sequence(global_state, constraints): :param global_state: GlobalState to generate transaction sequence for :param constraints: list of constraints used to generate transaction sequence - :param caller: address of caller :param max_callvalue: maximum callvalue for a transaction """ diff --git a/mythril/analysis/symbolic.py b/mythril/analysis/symbolic.py index d9efa2a0..d58b3ebe 100644 --- a/mythril/analysis/symbolic.py +++ b/mythril/analysis/symbolic.py @@ -179,7 +179,12 @@ class SymExecWrapper: state_index += 1 def find_storage_write(self, address, index): + """ + :param address: + :param index: + :return: + """ # Find an SSTOR not constrained by caller that writes to storage index "index" try: diff --git a/mythril/analysis/traceexplore.py b/mythril/analysis/traceexplore.py index 6fd476aa..0e5bf32a 100644 --- a/mythril/analysis/traceexplore.py +++ b/mythril/analysis/traceexplore.py @@ -47,6 +47,11 @@ colors = [ def get_serializable_statespace(statespace): + """ + + :param statespace: + :return: + """ nodes = [] edges = [] @@ -77,6 +82,11 @@ def get_serializable_statespace(statespace): color = color_map[node.get_cfg_dict()["contract_name"]] def get_state_accounts(node_state): + """ + + :param node_state: + :return: + """ state_accounts = [] for key in node_state.accounts: account = node_state.accounts[key].as_dict diff --git a/mythril/disassembler/asm.py b/mythril/disassembler/asm.py index 6e5f73f9..0d9cf839 100644 --- a/mythril/disassembler/asm.py +++ b/mythril/disassembler/asm.py @@ -18,6 +18,10 @@ class EvmInstruction: self.argument = argument def to_dict(self) -> dict: + """ + + :return: + """ result = {"address": self.address, "opcode": self.op_code} if self.argument: result["argument"] = self.argument @@ -25,6 +29,11 @@ class EvmInstruction: def instruction_list_to_easm(instruction_list: list) -> str: + """ + + :param instruction_list: + :return: + """ result = "" for instruction in instruction_list: @@ -37,6 +46,11 @@ def instruction_list_to_easm(instruction_list: list) -> str: def get_opcode_from_name(operation_name: str) -> int: + """ + + :param operation_name: + :return: + """ for op_code, value in opcodes.items(): if operation_name == value[0]: return op_code @@ -46,8 +60,7 @@ def get_opcode_from_name(operation_name: str) -> int: def find_op_code_sequence(pattern: list, instruction_list: list) -> Generator: """ Returns all indices in instruction_list that point to instruction sequences following a pattern - :param pattern: The pattern to look for. - Example: [["PUSH1", "PUSH2"], ["EQ"]] where ["PUSH1", "EQ"] satisfies the pattern + :param pattern: The pattern to look for, e.g. [["PUSH1", "PUSH2"], ["EQ"]] where ["PUSH1", "EQ"] satisfies pattern :param instruction_list: List of instructions to look in :return: Indices to the instruction sequences """ @@ -59,8 +72,7 @@ def find_op_code_sequence(pattern: list, instruction_list: list) -> Generator: def is_sequence_match(pattern: list, instruction_list: list, index: int) -> bool: """ Checks if the instructions starting at index follow a pattern - :param pattern: List of lists describing a pattern. - Example: [["PUSH1", "PUSH2"], ["EQ"]] where ["PUSH1", "EQ"] satisfies the pattern + :param pattern: List of lists describing a pattern, e.g. [["PUSH1", "PUSH2"], ["EQ"]] where ["PUSH1", "EQ"] satisfies pattern :param instruction_list: List of instructions :param index: Index to check for :return: Pattern matched diff --git a/mythril/disassembler/disassembly.py b/mythril/disassembler/disassembly.py index 5c5cd938..fb4016c0 100644 --- a/mythril/disassembler/disassembly.py +++ b/mythril/disassembler/disassembly.py @@ -10,9 +10,9 @@ class Disassembly(object): Stores bytecode, and its disassembly. Additionally it will gather the following information on the existing functions in the disassembled code: - - function hashes - - function name to entry point mapping - - function entry point to function name mapping + - function hashes + - function name to entry point mapping + - function entry point to function name mapping """ def __init__(self, code: str, enable_online_lookup: bool = False): @@ -43,6 +43,10 @@ class Disassembly(object): self.address_to_function_name[jump_target] = function_name def get_easm(self): + """ + + :return: + """ return asm.instruction_list_to_easm(self.instruction_list) diff --git a/mythril/ethereum/evmcontract.py b/mythril/ethereum/evmcontract.py index b0914a8f..5c01c0a2 100644 --- a/mythril/ethereum/evmcontract.py +++ b/mythril/ethereum/evmcontract.py @@ -5,6 +5,9 @@ import re class EVMContract(persistent.Persistent): + """ + + """ def __init__( self, code="", creation_code="", name="Unknown", enable_online_lookup=False ): @@ -25,7 +28,10 @@ class EVMContract(persistent.Persistent): ) def as_dict(self): + """ + :return: + """ return { "name": self.name, "code": self.code, @@ -34,15 +40,25 @@ class EVMContract(persistent.Persistent): } def get_easm(self): + """ + :return: + """ return self.disassembly.get_easm() def get_creation_easm(self): + """ + :return: + """ return self.creation_disassembly.get_easm() def matches_expression(self, expression): + """ + :param expression: + :return: + """ str_eval = "" easm_code = None diff --git a/mythril/ethereum/interface/leveldb/accountindexing.py b/mythril/ethereum/interface/leveldb/accountindexing.py index ec75e73b..aa08dfc7 100644 --- a/mythril/ethereum/interface/leveldb/accountindexing.py +++ b/mythril/ethereum/interface/leveldb/accountindexing.py @@ -21,9 +21,19 @@ class CountableList(object): self.element_sedes = element_sedes def serialize(self, obj): + """ + + :param obj: + :return: + """ return [self.element_sedes.serialize(e) for e in obj] def deserialize(self, serial): + """ + + :param serial: + :return: + """ # needed for 2 reasons: # 1. empty lists are not zero elements # 2. underlying logs are stored as list - if empty will also except and receipts will be lost diff --git a/mythril/ethereum/interface/rpc/client.py b/mythril/ethereum/interface/rpc/client.py index 97b5af46..568749ed 100644 --- a/mythril/ethereum/interface/rpc/client.py +++ b/mythril/ethereum/interface/rpc/client.py @@ -62,4 +62,7 @@ class EthJsonRpc(BaseClient): raise BadResponseError(response) def close(self): + """ + + """ self.session.close() diff --git a/mythril/ethereum/interface/rpc/utils.py b/mythril/ethereum/interface/rpc/utils.py index c0f701a2..b5968284 100644 --- a/mythril/ethereum/interface/rpc/utils.py +++ b/mythril/ethereum/interface/rpc/utils.py @@ -17,6 +17,11 @@ def clean_hex(d): def validate_block(block): + """ + + :param block: + :return: + """ if isinstance(block, str): if block not in BLOCK_TAGS: raise ValueError("invalid block tag") diff --git a/mythril/ethereum/util.py b/mythril/ethereum/util.py index 07537efc..f8ddea44 100644 --- a/mythril/ethereum/util.py +++ b/mythril/ethereum/util.py @@ -10,7 +10,11 @@ from pathlib import Path def safe_decode(hex_encoded_string): + """ + :param hex_encoded_string: + :return: + """ if hex_encoded_string.startswith("0x"): return bytes.fromhex(hex_encoded_string[2:]) else: @@ -18,7 +22,13 @@ def safe_decode(hex_encoded_string): def get_solc_json(file, solc_binary="solc", solc_args=None): + """ + :param file: + :param solc_binary: + :param solc_args: + :return: + """ cmd = [solc_binary, "--combined-json", "bin,bin-runtime,srcmap,srcmap-runtime"] if solc_args: @@ -58,6 +68,13 @@ def get_solc_json(file, solc_binary="solc", solc_args=None): def encode_calldata(func_name, arg_types, args): + """ + + :param func_name: + :param arg_types: + :param args: + :return: + """ mid = method_id(func_name, arg_types) function_selector = zpad(encode_int(mid), 4) args = encode_abi(arg_types, args) @@ -65,14 +82,28 @@ def encode_calldata(func_name, arg_types, args): def get_random_address(): + """ + + :return: + """ return binascii.b2a_hex(os.urandom(20)).decode("UTF-8") def get_indexed_address(index): + """ + + :param index: + :return: + """ return "0x" + (hex(index)[2:] * 40) def solc_exists(version): + """ + + :param version: + :return: + """ solc_binaries = [ os.path.join( os.environ.get("HOME", str(Path.home())), diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index 27a4725a..08a1d5a1 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -20,6 +20,11 @@ import mythril.support.signatures as sigs def exit_with_error(format_, message): + """ + + :param format_: + :param message: + """ if format_ == "text" or format_ == "markdown": logging.error(message) else: @@ -29,6 +34,9 @@ def exit_with_error(format_, message): def main(): + """ + + """ parser = argparse.ArgumentParser( description="Security analysis of Ethereum smart contracts" ) diff --git a/mythril/interfaces/epic.py b/mythril/interfaces/epic.py index 0ab38c69..b048a3b1 100755 --- a/mythril/interfaces/epic.py +++ b/mythril/interfaces/epic.py @@ -20,6 +20,9 @@ PY3 = sys.version_info >= (3,) # Reset terminal colors at exit def reset(): + """ + + """ sys.stdout.write("\x1b[0m") sys.stdout.flush() @@ -49,6 +52,9 @@ COLOR_ANSI = ( class LolCat(object): + """ + + """ def __init__(self, mode=256, output=sys.stdout): self.mode = mode self.output = output @@ -57,6 +63,11 @@ class LolCat(object): return sum(map(lambda c: (c[0] - c[1]) ** 2, zip(rgb1, rgb2))) def ansi(self, rgb): + """ + + :param rgb: + :return: + """ r, g, b = rgb if self.mode in (8, 16): @@ -93,15 +104,31 @@ class LolCat(object): return "38;5;%d" % (color,) def wrap(self, *codes): + """ + + :param codes: + :return: + """ return "\x1b[%sm" % ("".join(codes),) def rainbow(self, freq, i): + """ + + :param freq: + :param i: + :return: + """ r = math.sin(freq * i) * 127 + 128 g = math.sin(freq * i + 2 * math.pi / 3) * 127 + 128 b = math.sin(freq * i + 4 * math.pi / 3) * 127 + 128 return [r, g, b] def cat(self, fd, options): + """ + + :param fd: + :param options: + """ if options.animate: self.output.write("\x1b[?25l") @@ -113,6 +140,11 @@ class LolCat(object): self.output.write("\x1b[?25h") def println(self, s, options): + """ + + :param s: + :param options: + """ s = s.rstrip() if options.force or self.output.isatty(): s = STRIP_ANSI.sub("", s) @@ -126,6 +158,12 @@ class LolCat(object): self.output.flush() def println_ani(self, s, options): + """ + + :param s: + :param options: + :return: + """ if not s: return @@ -137,6 +175,11 @@ class LolCat(object): time.sleep(1.0 / options.speed) def println_plain(self, s, options): + """ + + :param s: + :param options: + """ for i, c in enumerate(s if PY3 else s.decode(options.charset_py2, "replace")): rgb = self.rainbow(options.freq, options.os + i / options.spread) self.output.write( diff --git a/mythril/laser/ethereum/call.py b/mythril/laser/ethereum/call.py index 60f82a68..f1013cff 100644 --- a/mythril/laser/ethereum/call.py +++ b/mythril/laser/ethereum/call.py @@ -1,3 +1,8 @@ +""" +This module contains the business logic used by Instruction in instructions.py +to get the necessary elements from the stack and determine the parameters for the new global state. +""" + import logging from typing import Union from z3 import simplify, ExprRef, Extract @@ -12,11 +17,6 @@ from mythril.laser.ethereum.state.global_state import GlobalState from mythril.support.loader import DynLoader import re -""" -This module contains the business logic used by Instruction in instructions.py -to get the necessary elements from the stack and determine the parameters for the new global state. -""" - def get_call_parameters( global_state: GlobalState, dynamic_loader: DynLoader, with_value=False diff --git a/mythril/laser/ethereum/cfg.py b/mythril/laser/ethereum/cfg.py index 93950104..ac8f30e2 100644 --- a/mythril/laser/ethereum/cfg.py +++ b/mythril/laser/ethereum/cfg.py @@ -19,6 +19,9 @@ class NodeFlags(Flags): class Node: + """ + + """ def __init__(self, contract_name: str, start_addr=0, constraints=None): constraints = constraints if constraints else [] self.contract_name = contract_name @@ -35,6 +38,10 @@ class Node: gbl_next_uid += 1 def get_cfg_dict(self) -> Dict: + """ + + :return: + """ code = "" for state in self.states: instruction = state.get_current_instruction() @@ -54,6 +61,9 @@ class Node: class Edge: + """ + + """ def __init__( self, node_from: int, @@ -71,4 +81,8 @@ class Edge: @property def as_dict(self) -> Dict[str, int]: + """ + + :return: + """ return {"from": self.node_from, "to": self.node_to} diff --git a/mythril/laser/ethereum/gas.py b/mythril/laser/ethereum/gas.py index 83456fe5..470bb3f0 100644 --- a/mythril/laser/ethereum/gas.py +++ b/mythril/laser/ethereum/gas.py @@ -3,6 +3,12 @@ from ethereum.utils import ceil32 def calculate_native_gas(size: int, contract: str): + """ + + :param size: + :param contract: + :return: + """ gas_value = None word_num = ceil32(size) // 32 if contract == "ecrecover": @@ -19,6 +25,11 @@ def calculate_native_gas(size: int, contract: str): def calculate_sha3_gas(length: int): + """ + + :param length: + :return: + """ gas_val = 30 + opcodes.GSHA3WORD * (ceil32(length) // 32) return gas_val, gas_val diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index 1edcde8c..5b0000be 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -73,10 +73,22 @@ class StateTransition(object): @staticmethod def call_on_state_copy(func: Callable, func_obj: "Instruction", state: GlobalState): + """ + + :param func: + :param func_obj: + :param state: + :return: + """ global_state_copy = copy(state) return func(func_obj, global_state_copy) def increment_states_pc(self, states: List[GlobalState]) -> List[GlobalState]: + """ + + :param states: + :return: + """ if self.increment_pc: for state in states: state.mstate.pc += 1 @@ -84,6 +96,11 @@ class StateTransition(object): @staticmethod def check_gas_usage_limit(global_state: GlobalState): + """ + + :param global_state: + :return: + """ global_state.mstate.check_gas() if isinstance(global_state.current_transaction.gas_limit, BitVecRef): try: @@ -99,6 +116,11 @@ class StateTransition(object): raise OutOfGasException() def accumulate_gas(self, global_state: GlobalState): + """ + + :param global_state: + :return: + """ if not self.enable_gas: return global_state opcode = global_state.instruction["opcode"] @@ -111,6 +133,12 @@ class StateTransition(object): def wrapper( func_obj: "Instruction", global_state: GlobalState ) -> List[GlobalState]: + """ + + :param func_obj: + :param global_state: + :return: + """ new_global_states = self.call_on_state_copy(func, func_obj, global_state) new_global_states = [ self.accumulate_gas(state) for state in new_global_states @@ -156,10 +184,20 @@ class Instruction: @StateTransition() def jumpdest_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ return [global_state] @StateTransition() def push_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ push_instruction = global_state.get_current_instruction() push_value = push_instruction["argument"][2:] @@ -174,12 +212,22 @@ class Instruction: @StateTransition() def dup_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ value = int(global_state.get_current_instruction()["opcode"][3:], 10) global_state.mstate.stack.append(global_state.mstate.stack[-value]) return [global_state] @StateTransition() def swap_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ depth = int(self.op_code[4:]) stack = global_state.mstate.stack stack[-depth - 1], stack[-1] = stack[-1], stack[-depth - 1] @@ -187,11 +235,21 @@ class Instruction: @StateTransition() def pop_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ global_state.mstate.stack.pop() return [global_state] @StateTransition() def and_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ stack = global_state.mstate.stack op1, op2 = stack.pop(), stack.pop() if type(op1) == BoolRef: @@ -204,6 +262,11 @@ class Instruction: @StateTransition() def or_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ stack = global_state.mstate.stack op1, op2 = stack.pop(), stack.pop() @@ -219,18 +282,33 @@ class Instruction: @StateTransition() def xor_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ mstate = global_state.mstate mstate.stack.append(mstate.stack.pop() ^ mstate.stack.pop()) return [global_state] @StateTransition() def not_(self, global_state: GlobalState): + """ + + :param global_state: + :return: + """ mstate = global_state.mstate mstate.stack.append(TT256M1 - mstate.stack.pop()) return [global_state] @StateTransition() def byte_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ mstate = global_state.mstate op0, op1 = mstate.stack.pop(), mstate.stack.pop() if not isinstance(op1, ExprRef): @@ -256,6 +334,11 @@ class Instruction: # Arithmetic @StateTransition() def add_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ global_state.mstate.stack.append( ( helper.pop_bitvec(global_state.mstate) @@ -266,6 +349,11 @@ class Instruction: @StateTransition() def sub_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ global_state.mstate.stack.append( ( helper.pop_bitvec(global_state.mstate) @@ -276,6 +364,11 @@ class Instruction: @StateTransition() def mul_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ global_state.mstate.stack.append( ( helper.pop_bitvec(global_state.mstate) @@ -286,6 +379,11 @@ class Instruction: @StateTransition() def div_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ op0, op1 = ( util.pop_bitvec(global_state.mstate), util.pop_bitvec(global_state.mstate), @@ -298,6 +396,11 @@ class Instruction: @StateTransition() def sdiv_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ s0, s1 = ( util.pop_bitvec(global_state.mstate), util.pop_bitvec(global_state.mstate), @@ -310,6 +413,11 @@ class Instruction: @StateTransition() def mod_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ s0, s1 = ( util.pop_bitvec(global_state.mstate), util.pop_bitvec(global_state.mstate), @@ -319,6 +427,11 @@ class Instruction: @StateTransition() def smod_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ s0, s1 = ( util.pop_bitvec(global_state.mstate), util.pop_bitvec(global_state.mstate), @@ -328,6 +441,11 @@ class Instruction: @StateTransition() def addmod_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ s0, s1, s2 = ( util.pop_bitvec(global_state.mstate), util.pop_bitvec(global_state.mstate), @@ -338,6 +456,11 @@ class Instruction: @StateTransition() def mulmod_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ s0, s1, s2 = ( util.pop_bitvec(global_state.mstate), util.pop_bitvec(global_state.mstate), @@ -348,6 +471,11 @@ class Instruction: @StateTransition() def exp_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ state = global_state.mstate base, exponent = util.pop_bitvec(state), util.pop_bitvec(state) @@ -366,6 +494,11 @@ class Instruction: @StateTransition() def signextend_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ state = global_state.mstate s0, s1 = state.stack.pop(), state.stack.pop() @@ -389,6 +522,11 @@ class Instruction: # Comparisons @StateTransition() def lt_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ state = global_state.mstate exp = ULT(util.pop_bitvec(state), util.pop_bitvec(state)) state.stack.append(exp) @@ -396,6 +534,11 @@ class Instruction: @StateTransition() def gt_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ state = global_state.mstate exp = UGT(util.pop_bitvec(state), util.pop_bitvec(state)) state.stack.append(exp) @@ -403,6 +546,11 @@ class Instruction: @StateTransition() def slt_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ state = global_state.mstate exp = util.pop_bitvec(state) < util.pop_bitvec(state) state.stack.append(exp) @@ -410,6 +558,11 @@ class Instruction: @StateTransition() def sgt_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ state = global_state.mstate exp = util.pop_bitvec(state) > util.pop_bitvec(state) @@ -418,6 +571,11 @@ class Instruction: @StateTransition() def eq_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ state = global_state.mstate op1 = state.stack.pop() @@ -436,6 +594,11 @@ class Instruction: @StateTransition() def iszero_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ state = global_state.mstate val = state.stack.pop() @@ -447,6 +610,11 @@ class Instruction: # Call data @StateTransition() def callvalue_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ state = global_state.mstate environment = global_state.environment state.stack.append(environment.callvalue) @@ -455,6 +623,11 @@ class Instruction: @StateTransition() def calldataload_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ state = global_state.mstate environment = global_state.environment op0 = state.stack.pop() @@ -467,6 +640,11 @@ class Instruction: @StateTransition() def calldatasize_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ state = global_state.mstate environment = global_state.environment state.stack.append(environment.calldata.calldatasize) @@ -474,6 +652,11 @@ class Instruction: @StateTransition() def calldatacopy_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ state = global_state.mstate environment = global_state.environment op0, op1, op2 = state.stack.pop(), state.stack.pop(), state.stack.pop() @@ -568,6 +751,11 @@ class Instruction: # Environment @StateTransition() def address_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ state = global_state.mstate environment = global_state.environment state.stack.append(environment.address) @@ -575,6 +763,11 @@ class Instruction: @StateTransition() def balance_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ state = global_state.mstate address = state.stack.pop() state.stack.append(global_state.new_bitvec("balance_at_" + str(address), 256)) @@ -582,6 +775,11 @@ class Instruction: @StateTransition() def origin_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ state = global_state.mstate environment = global_state.environment state.stack.append(environment.origin) @@ -589,6 +787,11 @@ class Instruction: @StateTransition() def caller_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ state = global_state.mstate environment = global_state.environment state.stack.append(environment.sender) @@ -596,6 +799,11 @@ class Instruction: @StateTransition() def codesize_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ state = global_state.mstate environment = global_state.environment disassembly = environment.code @@ -604,6 +812,11 @@ class Instruction: @StateTransition(enable_gas=False) def sha3_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ global keccak_function_manager state = global_state.mstate @@ -650,11 +863,21 @@ class Instruction: @StateTransition() def gasprice_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ global_state.mstate.stack.append(global_state.new_bitvec("gasprice", 256)) return [global_state] @StateTransition() def codecopy_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ memory_offset, code_offset, size = ( global_state.mstate.stack.pop(), global_state.mstate.stack.pop(), @@ -741,6 +964,11 @@ class Instruction: @StateTransition() def extcodesize_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ state = global_state.mstate addr = state.stack.pop() environment = global_state.environment @@ -767,6 +995,11 @@ class Instruction: @StateTransition() def extcodecopy_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ # FIXME: not implemented state = global_state.mstate addr = state.stack.pop() @@ -776,6 +1009,11 @@ class Instruction: @StateTransition() def returndatacopy_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ # FIXME: not implemented state = global_state.mstate start, s2, size = state.stack.pop(), state.stack.pop(), state.stack.pop() @@ -784,11 +1022,21 @@ class Instruction: @StateTransition() def returndatasize_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ global_state.mstate.stack.append(global_state.new_bitvec("returndatasize", 256)) return [global_state] @StateTransition() def blockhash_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ state = global_state.mstate blocknumber = state.stack.pop() state.stack.append( @@ -798,21 +1046,41 @@ class Instruction: @StateTransition() def coinbase_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ global_state.mstate.stack.append(global_state.new_bitvec("coinbase", 256)) return [global_state] @StateTransition() def timestamp_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ global_state.mstate.stack.append(global_state.new_bitvec("timestamp", 256)) return [global_state] @StateTransition() def number_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ global_state.mstate.stack.append(global_state.new_bitvec("block_number", 256)) return [global_state] @StateTransition() def difficulty_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ global_state.mstate.stack.append( global_state.new_bitvec("block_difficulty", 256) ) @@ -820,12 +1088,22 @@ class Instruction: @StateTransition() def gaslimit_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ global_state.mstate.stack.append(global_state.mstate.gas_limit) return [global_state] # Memory operations @StateTransition() def mload_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ state = global_state.mstate op0 = state.stack.pop() @@ -849,6 +1127,11 @@ class Instruction: @StateTransition() def mstore_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ state = global_state.mstate op0, value = state.stack.pop(), state.stack.pop() @@ -873,6 +1156,11 @@ class Instruction: @StateTransition() def mstore8_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ state = global_state.mstate op0, value = state.stack.pop(), state.stack.pop() @@ -896,6 +1184,11 @@ class Instruction: @StateTransition() def sload_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ global keccak_function_manager state = global_state.mstate @@ -965,6 +1258,11 @@ class Instruction: @StateTransition() def sstore_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ global keccak_function_manager state = global_state.mstate index, value = state.stack.pop(), state.stack.pop() @@ -1041,6 +1339,11 @@ class Instruction: @StateTransition(increment_pc=False, enable_gas=False) def jump_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ state = global_state.mstate disassembly = global_state.environment.code try: @@ -1075,6 +1378,11 @@ class Instruction: @StateTransition(increment_pc=False, enable_gas=False) def jumpi_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ state = global_state.mstate disassembly = global_state.environment.code min_gas, max_gas = OPCODE_GAS["JUMPI"] @@ -1144,22 +1452,42 @@ class Instruction: @StateTransition() def pc_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ global_state.mstate.stack.append(global_state.mstate.pc - 1) return [global_state] @StateTransition() def msize_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ global_state.mstate.stack.append(global_state.new_bitvec("msize", 256)) return [global_state] @StateTransition() def gas_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ # TODO: Push a Constrained variable which lies between min gas and max gas global_state.mstate.stack.append(global_state.new_bitvec("gas", 256)) return [global_state] @StateTransition() def log_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ # TODO: implement me state = global_state.mstate dpth = int(self.op_code[3:]) @@ -1170,6 +1498,11 @@ class Instruction: @StateTransition() def create_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ # TODO: implement me state = global_state.mstate state.stack.pop(), state.stack.pop(), state.stack.pop() @@ -1179,6 +1512,10 @@ class Instruction: @StateTransition() def return_(self, global_state: GlobalState): + """ + + :param global_state: + """ state = global_state.mstate offset, length = state.stack.pop(), state.stack.pop() return_data = [global_state.new_bitvec("return_data", 8)] @@ -1192,6 +1529,10 @@ class Instruction: @StateTransition() def suicide_(self, global_state: GlobalState): + """ + + :param global_state: + """ target = global_state.mstate.stack.pop() account_created = False # Often the target of the suicide instruction will be symbolic @@ -1223,6 +1564,10 @@ class Instruction: @StateTransition() def revert_(self, global_state: GlobalState) -> None: + """ + + :param global_state: + """ state = global_state.mstate offset, length = state.stack.pop(), state.stack.pop() return_data = [global_state.new_bitvec("return_data", 8)] @@ -1238,20 +1583,36 @@ class Instruction: @StateTransition() def assert_fail_(self, global_state: GlobalState): + """ + + :param global_state: + """ # 0xfe: designated invalid opcode raise InvalidInstruction @StateTransition() def invalid_(self, global_state: GlobalState): + """ + + :param global_state: + """ raise InvalidInstruction @StateTransition() def stop_(self, global_state: GlobalState): + """ + + :param global_state: + """ global_state.current_transaction.end(global_state) @StateTransition() def call_(self, global_state: GlobalState) -> List[GlobalState]: + """ + :param global_state: + :return: + """ instr = global_state.get_current_instruction() environment = global_state.environment @@ -1333,6 +1694,11 @@ class Instruction: @StateTransition() def call_post(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ instr = global_state.get_current_instruction() try: @@ -1395,6 +1761,11 @@ class Instruction: @StateTransition() def callcode_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ instr = global_state.get_current_instruction() environment = global_state.environment @@ -1429,6 +1800,11 @@ class Instruction: @StateTransition() def callcode_post(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ instr = global_state.get_current_instruction() try: @@ -1489,6 +1865,11 @@ class Instruction: @StateTransition() def delegatecall_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ instr = global_state.get_current_instruction() environment = global_state.environment @@ -1523,6 +1904,11 @@ class Instruction: @StateTransition() def delegatecall_post(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ instr = global_state.get_current_instruction() try: @@ -1583,6 +1969,11 @@ class Instruction: @StateTransition() def staticcall_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ # TODO: implement me instr = global_state.get_current_instruction() global_state.mstate.stack.append( diff --git a/mythril/laser/ethereum/keccak.py b/mythril/laser/ethereum/keccak.py index 6899ca35..ab7f104c 100644 --- a/mythril/laser/ethereum/keccak.py +++ b/mythril/laser/ethereum/keccak.py @@ -2,17 +2,35 @@ from z3 import ExprRef class KeccakFunctionManager: + """ + + """ def __init__(self): self.keccak_expression_mapping = {} def is_keccak(self, expression: ExprRef) -> bool: + """ + + :param expression: + :return: + """ return str(expression) in self.keccak_expression_mapping.keys() def get_argument(self, expression: str) -> ExprRef: + """ + + :param expression: + :return: + """ if not self.is_keccak(expression): raise ValueError("Expression is not a recognized keccac result") return self.keccak_expression_mapping[str(expression)][1] def add_keccak(self, expression: ExprRef, argument: ExprRef) -> None: + """ + + :param expression: + :param argument: + """ index = str(expression) self.keccak_expression_mapping[index] = (expression, argument) diff --git a/mythril/laser/ethereum/natives.py b/mythril/laser/ethereum/natives.py index f6c6b0e1..827f3f3c 100644 --- a/mythril/laser/ethereum/natives.py +++ b/mythril/laser/ethereum/natives.py @@ -20,6 +20,11 @@ class NativeContractException(Exception): def int_to_32bytes( i: int ) -> bytes: # used because int can't fit as bytes function's input + """ + + :param i: + :return: + """ o = [0] * 32 for x in range(32): o[31 - x] = i & 0xFF @@ -28,6 +33,12 @@ def int_to_32bytes( def extract32(data: bytearray, i: int) -> int: + """ + + :param data: + :param i: + :return: + """ if i >= len(data): return 0 o = data[i : min(i + 32, len(data))] @@ -36,6 +47,11 @@ def extract32(data: bytearray, i: int) -> int: def ecrecover(data: Union[bytes, str, List[int]]) -> bytes: + """ + + :param data: + :return: + """ # TODO: Add type hints try: data = bytearray(data) @@ -58,6 +74,11 @@ def ecrecover(data: Union[bytes, str, List[int]]) -> bytes: def sha256(data: Union[bytes, str, List[int]]) -> bytes: + """ + + :param data: + :return: + """ try: data = bytes(data) except TypeError: @@ -66,6 +87,11 @@ def sha256(data: Union[bytes, str, List[int]]) -> bytes: def ripemd160(data: Union[bytes, str, List[int]]) -> bytes: + """ + + :param data: + :return: + """ try: data = bytes(data) except TypeError: @@ -76,6 +102,11 @@ def ripemd160(data: Union[bytes, str, List[int]]) -> bytes: def identity(data: Union[bytes, str, List[int]]) -> bytes: + """ + + :param data: + :return: + """ # Group up into an array of 32 byte words instead # of an array of bytes. If saved to memory, 32 byte # words are currently needed, but a correct memory diff --git a/mythril/laser/ethereum/state/account.py b/mythril/laser/ethereum/state/account.py index 0e51b524..795f7e7c 100644 --- a/mythril/laser/ethereum/state/account.py +++ b/mythril/laser/ethereum/state/account.py @@ -48,6 +48,10 @@ class Storage: self._storage[key] = value def keys(self) -> KeysView: + """ + + :return: + """ return self._storage.keys() @@ -90,13 +94,25 @@ class Account: return str(self.as_dict) def set_balance(self, balance: ExprRef) -> None: + """ + + :param balance: + """ self.balance = balance def add_balance(self, balance: ExprRef) -> None: + """ + + :param balance: + """ self.balance += balance @property def as_dict(self) -> Dict: + """ + + :return: + """ return { "nonce": self.nonce, "code": self.code, diff --git a/mythril/laser/ethereum/state/calldata.py b/mythril/laser/ethereum/state/calldata.py index 1d1d6ec7..2b60f5c1 100644 --- a/mythril/laser/ethereum/state/calldata.py +++ b/mythril/laser/ethereum/state/calldata.py @@ -90,6 +90,9 @@ class BaseCalldata: class ConcreteCalldata(BaseCalldata): + """ + + """ def __init__(self, tx_id: int, calldata: list): """ Initializes the ConcreteCalldata object @@ -107,14 +110,27 @@ class ConcreteCalldata(BaseCalldata): return simplify(self._calldata[item]) def concrete(self, model: Model) -> list: + """ + + :param model: + :return: + """ return self._concrete_calldata @property def size(self) -> int: + """ + + :return: + """ return len(self._concrete_calldata) class BasicConcreteCalldata(BaseCalldata): + """ + + """ + def __init__(self, tx_id: int, calldata: list): """ Initializes the ConcreteCalldata object, that doesn't use z3 arrays @@ -137,14 +153,26 @@ class BasicConcreteCalldata(BaseCalldata): return value def concrete(self, model: Model) -> list: + """ + + :param model: + :return: + """ return self._calldata @property def size(self) -> int: + """ + + :return: + """ return len(self._calldata) class SymbolicCalldata(BaseCalldata): + """ + + """ def __init__(self, tx_id: int): """ Initializes the SymbolicCalldata object @@ -161,6 +189,11 @@ class SymbolicCalldata(BaseCalldata): return simplify(If(item < self._size, simplify(self._calldata[item]), 0)) def concrete(self, model: Model) -> list: + """ + + :param model: + :return: + """ concrete_length = get_concrete_int(model.eval(self.size, model_completion=True)) result = [] for i in range(concrete_length): @@ -172,10 +205,17 @@ class SymbolicCalldata(BaseCalldata): @property def size(self) -> ExprRef: + """ + + :return: + """ return self._size class BasicSymbolicCalldata(BaseCalldata): + """ + + """ def __init__(self, tx_id: int): """ Initializes the SymbolicCalldata object @@ -202,6 +242,11 @@ class BasicSymbolicCalldata(BaseCalldata): return simplify(return_value) def concrete(self, model: Model) -> list: + """ + + :param model: + :return: + """ concrete_length = get_concrete_int(model.eval(self.size, model_completion=True)) result = [] for i in range(concrete_length): @@ -213,4 +258,8 @@ class BasicSymbolicCalldata(BaseCalldata): @property def size(self) -> ExprRef: + """ + + :return: + """ return self._size diff --git a/mythril/laser/ethereum/state/constraints.py b/mythril/laser/ethereum/state/constraints.py index ed676521..bef820c9 100644 --- a/mythril/laser/ethereum/state/constraints.py +++ b/mythril/laser/ethereum/state/constraints.py @@ -11,12 +11,24 @@ class Constraints(list): self.__possibility = possibility def check_possibility(self): + """ + + :return: + """ return True def append(self, constraint): + """ + + :param constraint: + """ super(Constraints, self).append(constraint) def pop(self, index=-1): + """ + + :param index: + """ raise NotImplementedError def __copy__(self): diff --git a/mythril/laser/ethereum/state/environment.py b/mythril/laser/ethereum/state/environment.py index a54e5c42..8066d871 100644 --- a/mythril/laser/ethereum/state/environment.py +++ b/mythril/laser/ethereum/state/environment.py @@ -44,6 +44,10 @@ class Environment: @property def as_dict(self) -> Dict: + """ + + :return: + """ return dict( active_account=self.active_account, sender=self.sender, diff --git a/mythril/laser/ethereum/state/global_state.py b/mythril/laser/ethereum/state/global_state.py index df499490..97721644 100644 --- a/mythril/laser/ethereum/state/global_state.py +++ b/mythril/laser/ethereum/state/global_state.py @@ -53,6 +53,10 @@ class GlobalState: @property def accounts(self) -> Dict: + """ + + :return: + """ return self.world_state.accounts # TODO: remove this, as two instructions are confusing @@ -66,6 +70,10 @@ class GlobalState: def current_transaction( self ) -> Union["MessageCallTransaction", "ContractCreationTransaction", None]: + """ + + :return: + """ # TODO: Remove circular to transaction package to import Transaction classes try: return self.transaction_stack[-1][0] @@ -74,13 +82,27 @@ class GlobalState: @property def instruction(self) -> Dict: + """ + + :return: + """ return self.get_current_instruction() def new_bitvec(self, name: str, size=256) -> BitVec: + """ + + :param name: + :param size: + :return: + """ transaction_id = self.current_transaction.id return BitVec("{}_{}".format(transaction_id, name), size) def annotate(self, annotation: StateAnnotation) -> None: + """ + + :param annotation: + """ self._annotations.append(annotation) if annotation.persist_to_world_state: @@ -88,4 +110,8 @@ class GlobalState: @property def annotations(self) -> List[StateAnnotation]: + """ + + :return: + """ return self._annotations diff --git a/mythril/laser/ethereum/state/machine_state.py b/mythril/laser/ethereum/state/machine_state.py index e3f11859..586b749b 100644 --- a/mythril/laser/ethereum/state/machine_state.py +++ b/mythril/laser/ethereum/state/machine_state.py @@ -97,11 +97,23 @@ class MachineState: self.depth = depth def calculate_extension_size(self, start: int, size: int) -> int: + """ + + :param start: + :param size: + :return: + """ if self.memory_size > start + size: return 0 return start + size - self.memory_size def calculate_memory_gas(self, start: int, size: int): + """ + + :param start: + :param size: + :return: + """ # https://github.com/ethereum/pyethereum/blob/develop/ethereum/vm.py#L148 oldsize = self.memory_size // 32 old_totalfee = ( @@ -114,6 +126,9 @@ class MachineState: return new_totalfee - old_totalfee def check_gas(self): + """ + + """ if self.min_gas_used > self.gas_limit: raise OutOfGasException() @@ -163,10 +178,18 @@ class MachineState: @property def memory_size(self) -> int: + """ + + :return: + """ return len(self.memory) @property def as_dict(self) -> Dict: + """ + + :return: + """ return dict( pc=self.pc, stack=self.stack, diff --git a/mythril/laser/ethereum/state/memory.py b/mythril/laser/ethereum/state/memory.py index a8b10115..05ffe2c4 100644 --- a/mythril/laser/ethereum/state/memory.py +++ b/mythril/laser/ethereum/state/memory.py @@ -5,6 +5,9 @@ from mythril.laser.ethereum import util class Memory: + """ + + """ def __init__(self): self._memory = [] @@ -12,6 +15,10 @@ class Memory: return len(self._memory) def extend(self, size): + """ + + :param size: + """ self._memory.extend(bytearray(size)) def get_word_at(self, index: int) -> Union[int, BitVecRef]: diff --git a/mythril/laser/ethereum/state/world_state.py b/mythril/laser/ethereum/state/world_state.py index 8c302253..a995779d 100644 --- a/mythril/laser/ethereum/state/world_state.py +++ b/mythril/laser/ethereum/state/world_state.py @@ -77,10 +77,18 @@ class WorldState: self._put_account(new_account) def annotate(self, annotation: StateAnnotation) -> None: + """ + + :param annotation: + """ self._annotations.append(annotation) @property def annotations(self) -> List[StateAnnotation]: + """ + + :return: + """ return self._annotations def _generate_new_address(self) -> str: diff --git a/mythril/laser/ethereum/strategy/__init__.py b/mythril/laser/ethereum/strategy/__init__.py index 099aa4fb..a30e8e68 100644 --- a/mythril/laser/ethereum/strategy/__init__.py +++ b/mythril/laser/ethereum/strategy/__init__.py @@ -2,6 +2,9 @@ from abc import ABC, abstractmethod class BasicSearchStrategy(ABC): + """ + + """ __slots__ = "work_list", "max_depth" def __init__(self, work_list, max_depth): @@ -13,6 +16,9 @@ class BasicSearchStrategy(ABC): @abstractmethod def get_strategic_global_state(self): + """ + + """ raise NotImplementedError("Must be implemented by a subclass") def __next__(self): diff --git a/mythril/laser/ethereum/strategy/basic.py b/mythril/laser/ethereum/strategy/basic.py index 85d08824..4e05b270 100644 --- a/mythril/laser/ethereum/strategy/basic.py +++ b/mythril/laser/ethereum/strategy/basic.py @@ -38,6 +38,10 @@ class DepthFirstSearchStrategy(BasicSearchStrategy): """ def get_strategic_global_state(self) -> GlobalState: + """ + + :return: + """ return self.work_list.pop() @@ -48,6 +52,10 @@ class BreadthFirstSearchStrategy(BasicSearchStrategy): """ def get_strategic_global_state(self) -> GlobalState: + """ + + :return: + """ return self.work_list.pop(0) @@ -57,6 +65,10 @@ class ReturnRandomNaivelyStrategy(BasicSearchStrategy): """ def get_strategic_global_state(self) -> GlobalState: + """ + + :return: + """ if len(self.work_list) > 0: return self.work_list.pop(randrange(len(self.work_list))) else: @@ -69,6 +81,10 @@ class ReturnWeightedRandomStrategy(BasicSearchStrategy): """ def get_strategic_global_state(self) -> GlobalState: + """ + + :return: + """ probability_distribution = [ 1 / (global_state.mstate.depth + 1) for global_state in self.work_list ] diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py index 8bd38ef2..71a60d11 100644 --- a/mythril/laser/ethereum/svm.py +++ b/mythril/laser/ethereum/svm.py @@ -81,6 +81,11 @@ class LaserEVM: ) def register_hooks(self, hook_type: str, hook_dict: Dict[str, List[Callable]]): + """ + + :param hook_type: + :param hook_dict: + """ if hook_type == "pre": entrypoint = self.pre_hooks elif hook_type == "post": @@ -95,11 +100,21 @@ class LaserEVM: @property def accounts(self) -> Dict[str, Account]: + """ + + :return: + """ return self.world_state.accounts def sym_exec( self, main_address=None, creation_code=None, contract_name=None ) -> None: + """ + + :param main_address: + :param creation_code: + :param contract_name: + """ logging.debug("Starting LASER execution") self.time = datetime.now() @@ -172,6 +187,12 @@ class LaserEVM: return total_covered_instructions def exec(self, create=False, track_gas=False) -> Union[List[GlobalState], None]: + """ + + :param create: + :param track_gas: + :return: + """ final_states = [] for global_state in self.strategy: if self.execution_timeout and not create: @@ -201,6 +222,11 @@ class LaserEVM: def execute_state( self, global_state: GlobalState ) -> Tuple[List[GlobalState], Union[str, None]]: + """ + + :param global_state: + :return: + """ instructions = global_state.environment.code.instruction_list try: op_code = instructions[global_state.mstate.pc]["opcode"] @@ -321,6 +347,11 @@ class LaserEVM: self.coverage[code][1][instruction_index] = True def manage_cfg(self, opcode: str, new_states: List[GlobalState]) -> None: + """ + + :param opcode: + :param new_states: + """ if opcode == "JUMP": assert len(new_states) <= 1 for state in new_states: @@ -416,7 +447,17 @@ class LaserEVM: hook(global_state) def pre_hook(self, op_code: str) -> Callable: + """ + + :param op_code: + :return: + """ def hook_decorator(func: Callable): + """ + + :param func: + :return: + """ if op_code not in self.pre_hooks.keys(): self.pre_hooks[op_code] = [] self.pre_hooks[op_code].append(func) @@ -425,7 +466,17 @@ class LaserEVM: return hook_decorator def post_hook(self, op_code: str) -> Callable: + """ + + :param op_code: + :return: + """ def hook_decorator(func: Callable): + """ + + :param func: + :return: + """ if op_code not in self.post_hooks.keys(): self.post_hooks[op_code] = [] self.post_hooks[op_code].append(func) diff --git a/mythril/laser/ethereum/taint_analysis.py b/mythril/laser/ethereum/taint_analysis.py index 0af7f205..dca8eac1 100644 --- a/mythril/laser/ethereum/taint_analysis.py +++ b/mythril/laser/ethereum/taint_analysis.py @@ -94,10 +94,10 @@ class TaintRunner: ) -> TaintResult: """ Runs taint analysis on the statespace + :param initial_stack: :param statespace: symbolic statespace to run taint analysis on :param node: taint introduction node :param state: taint introduction state - :param stack_indexes: stack indexes to introduce taint :return: TaintResult object containing analysis results """ if initial_stack is None: @@ -134,6 +134,14 @@ class TaintRunner: environment: Environment, transaction_stack_length: int, ) -> List[Node]: + """ + + :param node: + :param statespace: + :param environment: + :param transaction_stack_length: + :return: + """ direct_children = [ statespace.nodes[edge.node_to] for edge in statespace.edges @@ -174,6 +182,12 @@ class TaintRunner: @staticmethod def execute_state(record: TaintRecord, state: GlobalState) -> TaintRecord: + """ + + :param record: + :param state: + :return: + """ assert len(state.mstate.stack) == len(record.stack) """ Runs taint analysis on a state """ record.add_state(state) @@ -210,6 +224,11 @@ class TaintRunner: @staticmethod def mutate_stack(record: TaintRecord, mutator: Tuple[int, int]) -> None: + """ + + :param record: + :param mutator: + """ pop, push = mutator values = [] @@ -223,16 +242,31 @@ class TaintRunner: @staticmethod def mutate_push(op: str, record: TaintRecord) -> None: + """ + + :param op: + :param record: + """ TaintRunner.mutate_stack(record, (0, 1)) @staticmethod def mutate_dup(op: str, record: TaintRecord) -> None: + """ + + :param op: + :param record: + """ depth = int(op[3:]) index = len(record.stack) - depth record.stack.append(record.stack[index]) @staticmethod def mutate_swap(op: str, record: TaintRecord) -> None: + """ + + :param op: + :param record: + """ depth = int(op[4:]) l = len(record.stack) - 1 i = l - depth @@ -240,6 +274,12 @@ class TaintRunner: @staticmethod def mutate_mload(record: TaintRecord, op0: ExprRef) -> None: + """ + + :param record: + :param op0: + :return: + """ _ = record.stack.pop() try: index = helper.get_concrete_int(op0) @@ -252,6 +292,12 @@ class TaintRunner: @staticmethod def mutate_mstore(record: TaintRecord, op0: ExprRef) -> None: + """ + + :param record: + :param op0: + :return: + """ _, value_taint = record.stack.pop(), record.stack.pop() try: index = helper.get_concrete_int(op0) @@ -263,6 +309,12 @@ class TaintRunner: @staticmethod def mutate_sload(record: TaintRecord, op0: ExprRef) -> None: + """ + + :param record: + :param op0: + :return: + """ _ = record.stack.pop() try: index = helper.get_concrete_int(op0) @@ -275,6 +327,12 @@ class TaintRunner: @staticmethod def mutate_sstore(record: TaintRecord, op0: ExprRef) -> None: + """ + + :param record: + :param op0: + :return: + """ _, value_taint = record.stack.pop(), record.stack.pop() try: index = helper.get_concrete_int(op0) @@ -286,12 +344,22 @@ class TaintRunner: @staticmethod def mutate_log(record: TaintRecord, op: str) -> None: + """ + + :param record: + :param op: + """ depth = int(op[3:]) for _ in range(depth + 2): record.stack.pop() @staticmethod def mutate_call(record: TaintRecord, op: str) -> None: + """ + + :param record: + :param op: + """ pops = 6 if op in ("CALL", "CALLCODE"): pops += 1 diff --git a/mythril/laser/ethereum/transaction/transaction_models.py b/mythril/laser/ethereum/transaction/transaction_models.py index 35861290..39a3baf7 100644 --- a/mythril/laser/ethereum/transaction/transaction_models.py +++ b/mythril/laser/ethereum/transaction/transaction_models.py @@ -17,6 +17,10 @@ _next_transaction_id = 0 def get_next_transaction_id() -> int: + """ + + :return: + """ global _next_transaction_id _next_transaction_id += 1 return _next_transaction_id @@ -97,6 +101,12 @@ class BaseTransaction: self.return_data = None def initial_global_state_from_environment(self, environment, active_function): + """ + + :param environment: + :param active_function: + :return: + """ # Initialize the execution environment global_state = GlobalState(self.world_state, environment, None) global_state.environment.active_function_name = active_function @@ -126,6 +136,12 @@ class MessageCallTransaction(BaseTransaction): ) def end(self, global_state: GlobalState, return_data=None, revert=False) -> None: + """ + + :param global_state: + :param return_data: + :param revert: + """ self.return_data = return_data raise TransactionEndSignal(global_state, revert) @@ -157,7 +173,12 @@ class ContractCreationTransaction(BaseTransaction): ) def end(self, global_state: GlobalState, return_data=None, revert=False): + """ + :param global_state: + :param return_data: + :param revert: + """ if ( not all([isinstance(element, int) for element in return_data]) or len(return_data) == 0 diff --git a/mythril/laser/ethereum/util.py b/mythril/laser/ethereum/util.py index 83d8f4db..1477f4d0 100644 --- a/mythril/laser/ethereum/util.py +++ b/mythril/laser/ethereum/util.py @@ -12,10 +12,20 @@ TT255 = 2 ** 255 def sha3(seed: str) -> bytes: + """ + + :param seed: + :return: + """ return _sha3.keccak_256(bytes(seed)).digest() def safe_decode(hex_encoded_string: str) -> bytes: + """ + + :param hex_encoded_string: + :return: + """ if hex_encoded_string.startswith("0x"): return bytes.fromhex(hex_encoded_string[2:]) else: @@ -23,12 +33,23 @@ def safe_decode(hex_encoded_string: str) -> bytes: def to_signed(i: int) -> int: + """ + + :param i: + :return: + """ return i if i < TT255 else i - TT256 def get_instruction_index( instruction_list: List[Dict], address: int ) -> Union[int, None]: + """ + + :param instruction_list: + :param address: + :return: + """ index = 0 for instr in instruction_list: if instr["address"] == address: @@ -38,6 +59,12 @@ def get_instruction_index( def get_trace_line(instr: Dict, state: "MachineState") -> str: + """ + + :param instr: + :param state: + :return: + """ stack = str(state.stack[::-1]) # stack = re.sub("(\d+)", lambda m: hex(int(m.group(1))), stack) stack = re.sub("\n", "", stack) @@ -45,6 +72,11 @@ def get_trace_line(instr: Dict, state: "MachineState") -> str: def pop_bitvec(state: "MachineState") -> BitVecVal: + """ + + :param state: + :return: + """ # pop one element from stack, converting boolean expressions and # concrete Python variables to BitVecVal @@ -64,6 +96,11 @@ def pop_bitvec(state: "MachineState") -> BitVecVal: def get_concrete_int(item: Union[int, ExprRef]) -> int: + """ + + :param item: + :return: + """ if isinstance(item, int): return item elif isinstance(item, BitVecNumRef): @@ -84,6 +121,12 @@ def get_concrete_int(item: Union[int, ExprRef]) -> int: def concrete_int_from_bytes(concrete_bytes: bytes, start_index: int) -> int: + """ + + :param concrete_bytes: + :param start_index: + :return: + """ concrete_bytes = [ byte.as_long() if type(byte) == BitVecNumRef else byte for byte in concrete_bytes @@ -94,6 +137,11 @@ def concrete_int_from_bytes(concrete_bytes: bytes, start_index: int) -> int: def concrete_int_to_bytes(val): + """ + + :param val: + :return: + """ # logging.debug("concrete_int_to_bytes " + str(val)) if type(val) == int: return val.to_bytes(32, byteorder="big") @@ -101,6 +149,11 @@ def concrete_int_to_bytes(val): def bytearray_to_int(arr): + """ + + :param arr: + :return: + """ o = 0 for a in arr: o = (o << 8) + a diff --git a/mythril/mythril.py b/mythril/mythril.py index be92c9c6..3ec02d64 100644 --- a/mythril/mythril.py +++ b/mythril/mythril.py @@ -39,15 +39,15 @@ from mythril.ethereum.interface.leveldb.client import EthLevelDB class Mythril(object): - """ - Mythril main interface class. + """Mythril main interface class. 1. create mythril object 2. set rpc or leveldb interface if needed 3. load contracts (from solidity, bytecode, address) 4. fire_lasers - Example: + .. code-block:: python + mythril = Mythril() mythril.set_api_rpc_infura() @@ -70,7 +70,8 @@ class Mythril(object): # (optional) graph for contract in mythril.contracts: - print(mythril.graph_html(args)) # prints html or save it to file + # prints html or save it to file + print(mythril.graph_html(args)) # (optional) other funcs mythril.dump_statespaces(args) @@ -206,6 +207,12 @@ class Mythril(object): config.set("defaults", "dynamic_loading", "infura") def analyze_truffle_project(self, *args, **kwargs): + """ + + :param args: + :param kwargs: + :return: + """ return analyze_truffle_project( self.sigs, *args, **kwargs ) # just passthru by passing signatures for now @@ -248,15 +255,28 @@ class Mythril(object): return solc_binary def set_api_leveldb(self, leveldb): + """ + + :param leveldb: + :return: + """ self.eth_db = EthLevelDB(leveldb) self.eth = self.eth_db return self.eth def set_api_rpc_infura(self): + """ + + """ self.eth = EthJsonRpc("mainnet.infura.io", 443, True) logging.info("Using INFURA for RPC queries") def set_api_rpc(self, rpc=None, rpctls=False): + """ + + :param rpc: + :param rpctls: + """ if rpc == "ganache": rpcconfig = ("localhost", 8545, False) else: @@ -279,10 +299,16 @@ class Mythril(object): raise CriticalError("Invalid RPC settings, check help for details.") def set_api_rpc_localhost(self): + """ + + """ self.eth = EthJsonRpc("localhost", 8545) logging.info("Using default RPC settings: http://localhost:8545") def set_api_from_config_path(self): + """ + + """ config = ConfigParser(allow_no_value=False) config.optionxform = str config.read(self.config_path, "utf-8") @@ -298,7 +324,17 @@ class Mythril(object): self.set_api_rpc(dynamic_loading) def search_db(self, search): + """ + + :param search: + """ def search_callback(_, address, balance): + """ + + :param _: + :param address: + :param balance: + """ print("Address: " + address + ", balance: " + str(balance)) try: @@ -308,13 +344,23 @@ class Mythril(object): raise CriticalError("Syntax error in search expression.") def contract_hash_to_address(self, hash): + """ + + :param hash: + """ if not re.match(r"0x[a-fA-F0-9]{64}", hash): raise CriticalError("Invalid address hash. Expected format is '0x...'.") print(self.eth_db.contract_hash_to_address(hash)) def load_from_bytecode(self, code, bin_runtime=False, address=None): + """ + :param code: + :param bin_runtime: + :param address: + :return: + """ if address is None: address = util.get_indexed_address(0) if bin_runtime: @@ -336,6 +382,11 @@ class Mythril(object): return address, self.contracts[-1] # return address and contract object def load_from_address(self, address): + """ + + :param address: + :return: + """ if not re.match(r"0x[a-fA-F0-9]{40}", address): raise CriticalError("Invalid contract address. Expected format is '0x...'.") @@ -423,7 +474,16 @@ class Mythril(object): execution_timeout=None, create_timeout=None, ): + """ + :param strategy: + :param contract: + :param address: + :param max_depth: + :param execution_timeout: + :param create_timeout: + :return: + """ sym = SymExecWrapper( contract, address, @@ -451,6 +511,18 @@ class Mythril(object): execution_timeout=None, create_timeout=None, ): + """ + + :param strategy: + :param contract: + :param address: + :param max_depth: + :param enable_physics: + :param phrackify: + :param execution_timeout: + :param create_timeout: + :return: + """ sym = SymExecWrapper( contract, address, @@ -478,7 +550,19 @@ class Mythril(object): create_timeout=None, transaction_count=None, ): + """ + :param strategy: + :param contracts: + :param address: + :param modules: + :param verbose_report: + :param max_depth: + :param execution_timeout: + :param create_timeout: + :param transaction_count: + :return: + """ all_issues = [] for contract in contracts or self.contracts: sym = SymExecWrapper( @@ -513,6 +597,12 @@ class Mythril(object): return report def get_state_variable_from_storage(self, address, params=None): + """ + + :param address: + :param params: + :return: + """ if params is None: params = [] (position, length, mappings) = (0, 1, []) @@ -593,8 +683,18 @@ class Mythril(object): @staticmethod def disassemble(contract): + """ + + :param contract: + :return: + """ return contract.get_easm() @staticmethod def hash_for_function_signature(sig): + """ + + :param sig: + :return: + """ return "0x%s" % utils.sha3(sig)[:4].hex() diff --git a/mythril/solidity/soliditycontract.py b/mythril/solidity/soliditycontract.py index 133a0b24..2aca3230 100644 --- a/mythril/solidity/soliditycontract.py +++ b/mythril/solidity/soliditycontract.py @@ -5,6 +5,9 @@ from mythril.exceptions import NoContractFoundError class SourceMapping: + """ + + """ def __init__(self, solidity_file_idx, offset, length, lineno): self.solidity_file_idx = solidity_file_idx self.offset = offset @@ -13,12 +16,18 @@ class SourceMapping: class SolidityFile: + """ + + """ def __init__(self, filename, data): self.filename = filename self.data = data class SourceCodeInfo: + """ + + """ def __init__(self, filename, lineno, code): self.filename = filename self.lineno = lineno @@ -26,6 +35,12 @@ class SourceCodeInfo: def get_contracts_from_file(input_file, solc_args=None, solc_binary="solc"): + """ + + :param input_file: + :param solc_args: + :param solc_binary: + """ data = get_solc_json(input_file, solc_args=solc_args, solc_binary=solc_binary) try: @@ -43,6 +58,9 @@ def get_contracts_from_file(input_file, solc_args=None, solc_binary="solc"): class SolidityContract(EVMContract): + """ + + """ def __init__(self, input_file, name=None, solc_args=None, solc_binary="solc"): data = get_solc_json(input_file, solc_args=solc_args, solc_binary=solc_binary) @@ -101,6 +119,12 @@ class SolidityContract(EVMContract): super().__init__(code, creation_code, name=name) def get_source_info(self, address, constructor=False): + """ + + :param address: + :param constructor: + :return: + """ disassembly = self.creation_disassembly if constructor else self.disassembly mappings = self.constructor_mappings if constructor else self.mappings index = helper.get_instruction_index(disassembly.instruction_list, address) diff --git a/mythril/support/loader.py b/mythril/support/loader.py index 6d8fc7e7..a2b23a3e 100644 --- a/mythril/support/loader.py +++ b/mythril/support/loader.py @@ -4,6 +4,9 @@ import re class DynLoader: + """ + + """ def __init__(self, eth, contract_loading=True, storage_loading=True): self.eth = eth self.storage_cache = {} @@ -11,7 +14,12 @@ class DynLoader: self.storage_loading = storage_loading def read_storage(self, contract_address, index): + """ + :param contract_address: + :param index: + :return: + """ if not self.storage_loading: raise Exception( "Cannot load from the storage when the storage_loading flag is false" @@ -42,7 +50,12 @@ class DynLoader: return data def dynld(self, contract_address, dependency_address): + """ + :param contract_address: + :param dependency_address: + :return: + """ if not self.contract_loading: raise ValueError("Cannot load contract when contract_loading flag is false") diff --git a/mythril/support/signatures.py b/mythril/support/signatures.py index 4da10177..1bb4e6b1 100644 --- a/mythril/support/signatures.py +++ b/mythril/support/signatures.py @@ -22,8 +22,19 @@ def synchronized(sync_lock): """ Synchronization decorator """ def wrapper(f): + """ + + :param f: + :return: + """ @functools.wraps(f) def inner_wrapper(*args, **kw): + """ + + :param args: + :param kw: + :return: + """ with sync_lock: return f(*args, **kw) @@ -76,7 +87,14 @@ class SQLiteDB(object): class SignatureDB(object, metaclass=Singleton): + """ + + """ def __init__(self, enable_online_lookup: bool = False, path: str = None) -> None: + """ + :param enable_online_lookup: + :param path: + """ self.enable_online_lookup = enable_online_lookup self.online_lookup_miss = set() self.online_lookup_timeout = 0 @@ -193,6 +211,8 @@ class SignatureDB(object, metaclass=Singleton): ): """ Import Function Signatures from solidity source files + :param solc_binary: + :param solc_args: :param file_path: solidity source code file path :return: """ diff --git a/mythril/support/truffle.py b/mythril/support/truffle.py index 96645617..505fc090 100644 --- a/mythril/support/truffle.py +++ b/mythril/support/truffle.py @@ -16,7 +16,11 @@ from mythril.laser.ethereum.util import get_instruction_index def analyze_truffle_project(sigs, args): + """ + :param sigs: + :param args: + """ project_root = os.getcwd() build_dir = os.path.join(project_root, "build", "contracts") @@ -131,6 +135,11 @@ def analyze_truffle_project(sigs, args): def get_sigs_from_truffle(sigs, contract_data): + """ + + :param sigs: + :param contract_data: + """ abis = contract_data["abi"] for abi in abis: if abi["type"] != "function": @@ -142,6 +151,12 @@ def get_sigs_from_truffle(sigs, contract_data): def get_mappings(source, deployed_source_map): + """ + + :param source: + :param deployed_source_map: + :return: + """ mappings = [] for item in deployed_source_map: mapping = item.split(":") diff --git a/setup.py b/setup.py index ff87c3d1..bf5137d4 100755 --- a/setup.py +++ b/setup.py @@ -32,6 +32,9 @@ class VerifyVersionCommand(install): description = "verify that the git tag matches our version" def run(self): + """ + + """ tag = os.getenv("CIRCLE_TAG") if tag != VERSION: diff --git a/tests/__init__.py b/tests/__init__.py index fa2fc4f2..361d7ac8 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -17,9 +17,16 @@ MYTHRIL_DIR = TESTS_DIR / "mythril_dir" class BaseTestCase(TestCase): def setUp(self): + """ + + """ self.changed_files = [] def compare_files_error_message(self): + """ + + :return: + """ message = "Following output files are changed, compare them manually to see differences: \n" for (input_file, expected, current) in self.changed_files: @@ -30,9 +37,18 @@ class BaseTestCase(TestCase): return message def found_changed_files(self, input_file, output_expected, output_current): + """ + + :param input_file: + :param output_expected: + :param output_current: + """ self.changed_files.append((input_file, output_expected, output_current)) def assert_and_show_changed_files(self): + """ + + """ self.assertEqual( 0, len(self.changed_files), msg=self.compare_files_error_message() ) diff --git a/tests/cmd_line_test.py b/tests/cmd_line_test.py index 26545eac..fde90517 100644 --- a/tests/cmd_line_test.py +++ b/tests/cmd_line_test.py @@ -5,6 +5,11 @@ MYTH = str(PROJECT_DIR / "myth") def output_of(command): + """ + + :param command: + :return: + """ return check_output(command, shell=True).decode("UTF-8") diff --git a/tests/evmcontract_test.py b/tests/evmcontract_test.py index f9283569..bfc70637 100644 --- a/tests/evmcontract_test.py +++ b/tests/evmcontract_test.py @@ -4,6 +4,9 @@ from tests import BaseTestCase class EVMContractTestCase(BaseTestCase): def setUp(self): + """ + + """ super().setUp() self.code = "0x60606040525b603c5b60006010603e565b9050593681016040523660008237602060003683856040603f5a0204f41560545760206000f35bfe5b50565b005b73c3b2ae46792547a96b9f84405e36d0e07edcd05c5b905600a165627a7a7230582062a884f947232ada573f95940cce9c8bfb7e4e14e21df5af4e884941afb55e590029" self.creation_code = "0x60606040525b603c5b60006010603e565b9050593681016040523660008237602060003683856040603f5a0204f41560545760206000f35bfe5b50565b005b73c3b2ae46792547a96b9f84405e36d0e07edcd05c5b905600a165627a7a7230582062a884f947232ada573f95940cce9c8bfb7e4e14e21df5af4e884941afb55e590029" @@ -11,6 +14,9 @@ class EVMContractTestCase(BaseTestCase): class Getinstruction_listTestCase(EVMContractTestCase): def runTest(self): + """ + + """ contract = EVMContract(self.code, self.creation_code) disassembly = contract.disassembly @@ -24,6 +30,9 @@ class Getinstruction_listTestCase(EVMContractTestCase): class GetEASMTestCase(EVMContractTestCase): def runTest(self): + """ + + """ contract = EVMContract(self.code) instruction_list = contract.get_easm() @@ -36,6 +45,9 @@ class GetEASMTestCase(EVMContractTestCase): class MatchesExpressionTestCase(EVMContractTestCase): def runTest(self): + """ + + """ contract = EVMContract(self.code) self.assertTrue( diff --git a/tests/laser/evm_testsuite/evm_test.py b/tests/laser/evm_testsuite/evm_test.py index 2d0bdbd2..3ea11899 100644 --- a/tests/laser/evm_testsuite/evm_test.py +++ b/tests/laser/evm_testsuite/evm_test.py @@ -25,6 +25,11 @@ test_types = [ def load_test_data(designations): + """ + + :param designations: + :return: + """ return_data = [] for designation in designations: for file_reference in (evm_test_dir / designation).iterdir(): diff --git a/tests/native_test.py b/tests/native_test.py index 815e950d..bf7e3b8a 100644 --- a/tests/native_test.py +++ b/tests/native_test.py @@ -87,7 +87,9 @@ def _test_natives(laser_info, test_list, test_name): class NativeTests(BaseTestCase): @staticmethod def runTest(): + """ + """ disassembly = SolidityContract( "./tests/native_tests.sol", solc_binary=Mythril._init_solc_binary("0.5.0") ).disassembly diff --git a/tests/report_test.py b/tests/report_test.py index 39493ae9..842c38d4 100644 --- a/tests/report_test.py +++ b/tests/report_test.py @@ -82,6 +82,11 @@ def _assert_empty_json(changed_files): actual = [] def ordered(obj): + """ + + :param obj: + :return: + """ if isinstance(obj, dict): return sorted((k, ordered(v)) for k, v in obj.items()) elif isinstance(obj, list): @@ -126,6 +131,11 @@ def _get_changed_files_json(report_builder, reports): postfix = ".json" def ordered(obj): + """ + + :param obj: + :return: + """ if isinstance(obj, dict): return sorted((k, ordered(v)) for k, v in obj.items()) elif isinstance(obj, list): diff --git a/tests/rpc_test.py b/tests/rpc_test.py index e3df4dea..efb2ca4b 100644 --- a/tests/rpc_test.py +++ b/tests/rpc_test.py @@ -8,9 +8,15 @@ class RpcTest(BaseTestCase): client = None def setUp(self): + """ + + """ self.client = EthJsonRpc() def tearDown(self): + """ + + """ self.client.close() def test_eth_coinbase(self):