mirror of https://github.com/ConsenSys/mythril
commit
d74545f0a7
@ -0,0 +1,128 @@ |
||||
"""This module contains the detection code SWC-128 - DOS with block gas limit.""" |
||||
|
||||
import logging |
||||
from typing import Dict, cast, List |
||||
|
||||
from mythril.analysis.swc_data import DOS_WITH_BLOCK_GAS_LIMIT |
||||
from mythril.analysis.report import Issue |
||||
from mythril.analysis.modules.base import DetectionModule |
||||
from mythril.laser.ethereum.state.global_state import GlobalState |
||||
from mythril.laser.ethereum.state.annotation import StateAnnotation |
||||
from mythril.laser.ethereum import util |
||||
|
||||
log = logging.getLogger(__name__) |
||||
|
||||
|
||||
class LoopAnnotation(StateAnnotation): |
||||
def __init__(self, loop_start: int, loop_end: int) -> None: |
||||
self.loop_start = loop_start |
||||
self.loop_end = loop_end |
||||
|
||||
def contains(self, address: int) -> bool: |
||||
return self.loop_start < address < self.loop_end |
||||
|
||||
|
||||
class DOS(DetectionModule): |
||||
"""This module consists of a makeshift loop detector that annotates the state with |
||||
a list of byte ranges likely to be loops. If a CALL or SSTORE detection is found in |
||||
one of the ranges it creates a low-severity issue. This is not super precise but |
||||
good enough to identify places that warrant a closer look. Checking the loop condition |
||||
would be a possible improvement. |
||||
""" |
||||
|
||||
def __init__(self) -> None: |
||||
"""""" |
||||
super().__init__( |
||||
name="DOS", |
||||
swc_id=DOS_WITH_BLOCK_GAS_LIMIT, |
||||
description="Check for DOS", |
||||
entrypoint="callback", |
||||
pre_hooks=["JUMPI", "CALL", "SSTORE"], |
||||
) |
||||
|
||||
"""Keeps track of how often jump destinations are reached.""" |
||||
self._jumpdest_count = {} # type: Dict[object, dict] |
||||
|
||||
def execute(self, state: GlobalState) -> None: |
||||
""" |
||||
|
||||
:param state: |
||||
:return: |
||||
""" |
||||
log.debug("Executing module: DOS") |
||||
|
||||
self._issues.extend(self._analyze_states(state)) |
||||
|
||||
def _analyze_states(self, state: GlobalState) -> List[Issue]: |
||||
""" |
||||
:param state: the current state |
||||
:return: returns the issues for that corresponding state |
||||
""" |
||||
|
||||
opcode = state.get_current_instruction()["opcode"] |
||||
address = state.get_current_instruction()["address"] |
||||
|
||||
if opcode == "JUMPI": |
||||
|
||||
target = util.get_concrete_int(state.mstate.stack[-1]) |
||||
|
||||
transaction = state.current_transaction |
||||
if state.current_transaction in self._jumpdest_count: |
||||
|
||||
try: |
||||
self._jumpdest_count[transaction][target] += 1 |
||||
if self._jumpdest_count[transaction][target] == 4: |
||||
|
||||
annotation = ( |
||||
LoopAnnotation(address, target) |
||||
if target > address |
||||
else LoopAnnotation(target, address) |
||||
) |
||||
|
||||
state.annotate(annotation) |
||||
except KeyError: |
||||
self._jumpdest_count[transaction][target] = 0 |
||||
|
||||
else: |
||||
self._jumpdest_count[transaction] = {} |
||||
self._jumpdest_count[transaction][target] = 0 |
||||
|
||||
else: |
||||
|
||||
annotations = cast( |
||||
List[LoopAnnotation], list(state.get_annotations(LoopAnnotation)) |
||||
) |
||||
|
||||
for annotation in annotations: |
||||
|
||||
if annotation.contains(address): |
||||
|
||||
operation = ( |
||||
"A storage modification" |
||||
if opcode == "SSTORE" |
||||
else "An external call" |
||||
) |
||||
|
||||
description_head = ( |
||||
"Potential denial-of-service if block gas limit is reached." |
||||
) |
||||
description_tail = "{} is executed in a loop.".format(operation) |
||||
|
||||
issue = Issue( |
||||
contract=state.environment.active_account.contract_name, |
||||
function_name=state.environment.active_function_name, |
||||
address=annotation.loop_start, |
||||
swc_id=DOS_WITH_BLOCK_GAS_LIMIT, |
||||
bytecode=state.environment.code.bytecode, |
||||
title="Potential denial-of-service if block gas limit is reached", |
||||
severity="Low", |
||||
description_head=description_head, |
||||
description_tail=description_tail, |
||||
gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used), |
||||
) |
||||
return [issue] |
||||
|
||||
return [] |
||||
|
||||
|
||||
detector = DOS() |
@ -0,0 +1,172 @@ |
||||
from mythril.analysis.swc_data import REENTRANCY |
||||
from mythril.analysis.modules.base import DetectionModule |
||||
from mythril.analysis.report import Issue |
||||
from mythril.laser.smt import symbol_factory, UGT, BitVec, Or |
||||
from mythril.laser.ethereum.state.global_state import GlobalState |
||||
from mythril.laser.ethereum.state.annotation import StateAnnotation |
||||
from mythril.analysis import solver |
||||
from mythril.exceptions import UnsatError |
||||
from typing import List, cast, Optional |
||||
from copy import copy |
||||
|
||||
import logging |
||||
|
||||
log = logging.getLogger(__name__) |
||||
|
||||
DESCRIPTION = """ |
||||
|
||||
Check whether there is a state change of the contract after the execution of an external call |
||||
""" |
||||
|
||||
|
||||
class StateChangeCallsAnnotation(StateAnnotation): |
||||
def __init__(self, call_state: GlobalState, user_defined_address: bool) -> None: |
||||
self.call_state = call_state |
||||
self.state_change_states = [] # type: List[GlobalState] |
||||
self.user_defined_address = user_defined_address |
||||
|
||||
def __copy__(self): |
||||
new_annotation = StateChangeCallsAnnotation( |
||||
self.call_state, self.user_defined_address |
||||
) |
||||
new_annotation.state_change_states = self.state_change_states[:] |
||||
return new_annotation |
||||
|
||||
def get_issue(self, global_state: GlobalState) -> Optional[Issue]: |
||||
if not self.state_change_states: |
||||
return None |
||||
|
||||
severity = "Medium" if self.user_defined_address else "Low" |
||||
address = global_state.get_current_instruction()["address"] |
||||
logging.debug( |
||||
"[EXTERNAL_CALLS] Detected state changes at addresses: {}".format(address) |
||||
) |
||||
description_head = ( |
||||
"The contract account state is changed after an external call. " |
||||
) |
||||
description_tail = ( |
||||
"Consider that the called contract could re-enter the function before this " |
||||
"state change takes place. This can lead to business logic vulnerabilities." |
||||
) |
||||
|
||||
return Issue( |
||||
contract=global_state.environment.active_account.contract_name, |
||||
function_name=global_state.environment.active_function_name, |
||||
address=address, |
||||
title="State change after external call", |
||||
severity=severity, |
||||
description_head=description_head, |
||||
description_tail=description_tail, |
||||
swc_id=REENTRANCY, |
||||
bytecode=global_state.environment.code.bytecode, |
||||
) |
||||
|
||||
|
||||
class StateChange(DetectionModule): |
||||
"""This module searches for state change after low level calls (e.g. call.value()) that |
||||
forward gas to the callee.""" |
||||
|
||||
def __init__(self): |
||||
"""""" |
||||
super().__init__( |
||||
name="State Change After External calls", |
||||
swc_id=REENTRANCY, |
||||
description=DESCRIPTION, |
||||
entrypoint="callback", |
||||
pre_hooks=[ |
||||
"CALL", |
||||
"SSTORE", |
||||
"DELEGATECALL", |
||||
"STATICCALL", |
||||
"CREATE", |
||||
"CREATE2", |
||||
"CALLCODE", |
||||
], |
||||
) |
||||
|
||||
def execute(self, state: GlobalState): |
||||
self._issues.extend(self._analyze_state(state)) |
||||
return self.issues |
||||
|
||||
@staticmethod |
||||
def _add_external_call(global_state: GlobalState) -> None: |
||||
gas = global_state.mstate.stack[-1] |
||||
to = global_state.mstate.stack[-2] |
||||
try: |
||||
constraints = copy(global_state.mstate.constraints) |
||||
solver.get_model( |
||||
constraints |
||||
+ [ |
||||
UGT(gas, symbol_factory.BitVecVal(2300, 256)), |
||||
Or( |
||||
to > symbol_factory.BitVecVal(16, 256), |
||||
to == symbol_factory.BitVecVal(0, 256), |
||||
), |
||||
] |
||||
) |
||||
|
||||
# Check whether we can also set the callee address |
||||
try: |
||||
constraints += [to == 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF] |
||||
solver.get_model(constraints) |
||||
|
||||
global_state.annotate(StateChangeCallsAnnotation(global_state, True)) |
||||
except UnsatError: |
||||
global_state.annotate(StateChangeCallsAnnotation(global_state, False)) |
||||
except UnsatError: |
||||
pass |
||||
|
||||
@staticmethod |
||||
def _analyze_state(global_state: GlobalState) -> List[Issue]: |
||||
|
||||
annotations = cast( |
||||
List[StateChangeCallsAnnotation], |
||||
list(global_state.get_annotations(StateChangeCallsAnnotation)), |
||||
) |
||||
op_code = global_state.get_current_instruction()["opcode"] |
||||
|
||||
if len(annotations) == 0: |
||||
if op_code in ("SSTORE", "CREATE", "CREATE2"): |
||||
return [] |
||||
if op_code in ("SSTORE", "CREATE", "CREATE2"): |
||||
for annotation in annotations: |
||||
annotation.state_change_states.append(global_state) |
||||
|
||||
# Record state changes following from a transfer of ether |
||||
if op_code in ("CALL", "DELEGATECALL", "CALLCODE"): |
||||
value = global_state.mstate.stack[-3] # type: BitVec |
||||
if StateChange._balance_change(value, global_state): |
||||
for annotation in annotations: |
||||
annotation.state_change_states.append(global_state) |
||||
|
||||
# Record external calls |
||||
if op_code in ("CALL", "DELEGATECALL", "CALLCODE"): |
||||
StateChange._add_external_call(global_state) |
||||
|
||||
# Check for vulnerabilities |
||||
vulnerabilities = [] |
||||
for annotation in annotations: |
||||
if not annotation.state_change_states: |
||||
continue |
||||
vulnerabilities.append(annotation.get_issue(global_state)) |
||||
return vulnerabilities |
||||
|
||||
@staticmethod |
||||
def _balance_change(value: BitVec, global_state: GlobalState) -> bool: |
||||
if not value.symbolic: |
||||
assert value.value is not None |
||||
return value.value > 0 |
||||
|
||||
else: |
||||
constraints = copy(global_state.mstate.constraints) |
||||
|
||||
try: |
||||
solver.get_model( |
||||
constraints + [value > symbol_factory.BitVecVal(0, 256)] |
||||
) |
||||
return True |
||||
except UnsatError: |
||||
return False |
||||
|
||||
|
||||
detector = StateChange() |
@ -1,198 +0,0 @@ |
||||
"""This module contains the detection code to find the existence of transaction |
||||
order dependence.""" |
||||
import copy |
||||
import logging |
||||
import re |
||||
|
||||
from mythril.analysis import solver |
||||
from mythril.analysis.modules.base import DetectionModule |
||||
from mythril.analysis.ops import * |
||||
from mythril.analysis.report import Issue |
||||
from mythril.analysis.swc_data import TX_ORDER_DEPENDENCE |
||||
from mythril.exceptions import UnsatError |
||||
|
||||
log = logging.getLogger(__name__) |
||||
|
||||
|
||||
class TxOrderDependenceModule(DetectionModule): |
||||
"""This module finds the existence of transaction order dependence.""" |
||||
|
||||
def __init__(self): |
||||
super().__init__( |
||||
name="Transaction Order Dependence", |
||||
swc_id=TX_ORDER_DEPENDENCE, |
||||
description=( |
||||
"This module finds the existance of transaction order dependence " |
||||
"vulnerabilities. The following webpage contains an extensive description " |
||||
"of the vulnerability: " |
||||
"https://consensys.github.io/smart-contract-best-practices/known_attacks/#transaction-ordering-dependence-tod-front-running" |
||||
), |
||||
) |
||||
|
||||
def execute(self, statespace): |
||||
"""Executes the analysis module. |
||||
|
||||
:param statespace: |
||||
:return: |
||||
""" |
||||
log.debug("Executing module: TOD") |
||||
|
||||
issues = [] |
||||
|
||||
for call in statespace.calls: |
||||
# Do analysis |
||||
interesting_storages = list(self._get_influencing_storages(call)) |
||||
changing_sstores = list( |
||||
self._get_influencing_sstores(statespace, interesting_storages) |
||||
) |
||||
|
||||
description_tail = ( |
||||
"A transaction order dependence vulnerability may exist in this contract. The value or " |
||||
"target of the call statement is loaded from a writable storage location." |
||||
) |
||||
|
||||
# Build issue if necessary |
||||
if len(changing_sstores) > 0: |
||||
node = call.node |
||||
instruction = call.state.get_current_instruction() |
||||
issue = Issue( |
||||
contract=node.contract_name, |
||||
function_name=node.function_name, |
||||
address=instruction["address"], |
||||
title="Transaction Order Dependence", |
||||
bytecode=call.state.environment.code.bytecode, |
||||
swc_id=TX_ORDER_DEPENDENCE, |
||||
severity="Medium", |
||||
description_head="The call outcome may depend on transaction order.", |
||||
description_tail=description_tail, |
||||
gas_used=( |
||||
call.state.mstate.min_gas_used, |
||||
call.state.mstate.max_gas_used, |
||||
), |
||||
) |
||||
|
||||
issues.append(issue) |
||||
|
||||
return issues |
||||
|
||||
# TODO: move to __init__ or util module |
||||
@staticmethod |
||||
def _get_states_with_opcode(statespace, opcode): |
||||
"""Gets all (state, node) tuples in statespace with opcode. |
||||
|
||||
:param statespace: |
||||
:param opcode: |
||||
""" |
||||
for k in statespace.nodes: |
||||
node = statespace.nodes[k] |
||||
for state in node.states: |
||||
if state.get_current_instruction()["opcode"] == opcode: |
||||
yield state, node |
||||
|
||||
@staticmethod |
||||
def _dependent_on_storage(expression): |
||||
"""Checks if expression is dependent on a storage symbol and returns |
||||
the influencing storages. |
||||
|
||||
:param expression: |
||||
:return: |
||||
""" |
||||
pattern = re.compile(r"storage_[a-z0-9_&^]*[0-9]+") |
||||
return pattern.findall(str(simplify(expression))) |
||||
|
||||
@staticmethod |
||||
def _get_storage_variable(storage, state): |
||||
"""Get storage z3 object given storage name and the state. |
||||
|
||||
:param storage: storage name example: storage_0 |
||||
:param state: state to retrieve the variable from |
||||
:return: z3 object representing storage |
||||
""" |
||||
index = int(re.search("[0-9]+", storage).group()) |
||||
try: |
||||
return state.environment.active_account.storage[index] |
||||
except KeyError: |
||||
return None |
||||
|
||||
def _can_change(self, constraints, variable): |
||||
"""Checks if the variable can change given some constraints. |
||||
|
||||
:param constraints: |
||||
:param variable: |
||||
:return: |
||||
""" |
||||
_constraints = copy.deepcopy(constraints) |
||||
try: |
||||
model = solver.get_model(_constraints) |
||||
except UnsatError: |
||||
return False |
||||
try: |
||||
initial_value = int(str(model.eval(variable, model_completion=True))) |
||||
return ( |
||||
self._try_constraints(constraints, [variable != initial_value]) |
||||
is not None |
||||
) |
||||
except AttributeError: |
||||
return False |
||||
|
||||
def _get_influencing_storages(self, call): |
||||
"""Examines a Call object and returns an iterator of all storages that |
||||
influence the call value or direction. |
||||
|
||||
:param call: |
||||
""" |
||||
state = call.state |
||||
node = call.node |
||||
|
||||
# Get relevant storages |
||||
to, value = call.to, call.value |
||||
storages = [] |
||||
if to.type == VarType.SYMBOLIC: |
||||
storages += self._dependent_on_storage(to.val) |
||||
if value.type == VarType.SYMBOLIC: |
||||
storages += self._dependent_on_storage(value.val) |
||||
|
||||
# See if they can change within the constraints of the node |
||||
for storage in storages: |
||||
variable = self._get_storage_variable(storage, state) |
||||
can_change = self._can_change(node.constraints, variable) |
||||
if can_change: |
||||
yield storage |
||||
|
||||
def _get_influencing_sstores(self, statespace, interesting_storages): |
||||
"""Gets sstore (state, node) tuples that write to interesting_storages. |
||||
|
||||
:param statespace: |
||||
:param interesting_storages: |
||||
""" |
||||
for sstore_state, node in self._get_states_with_opcode(statespace, "SSTORE"): |
||||
index, value = sstore_state.mstate.stack[-1], sstore_state.mstate.stack[-2] |
||||
try: |
||||
index = util.get_concrete_int(index) |
||||
except TypeError: |
||||
index = str(index) |
||||
if "storage_{}".format(index) not in interesting_storages: |
||||
continue |
||||
|
||||
yield sstore_state, node |
||||
|
||||
# TODO: remove |
||||
@staticmethod |
||||
def _try_constraints(constraints, new_constraints): |
||||
"""Tries new constraints. |
||||
|
||||
:param constraints: |
||||
:param new_constraints: |
||||
:return Model if satisfiable otherwise None |
||||
""" |
||||
_constraints = copy.deepcopy(constraints) |
||||
for constraint in new_constraints: |
||||
_constraints.append(copy.deepcopy(constraint)) |
||||
try: |
||||
model = solver.get_model(_constraints) |
||||
return model |
||||
except UnsatError: |
||||
return None |
||||
|
||||
|
||||
detector = TxOrderDependenceModule() |
@ -0,0 +1,21 @@ |
||||
""" Laser plugins |
||||
|
||||
This module contains everything to do with laser plugins |
||||
|
||||
Laser plugins are a way of extending laser's functionality without complicating the core business logic. |
||||
Different features that have been implemented in the form of plugins are: |
||||
- benchmarking |
||||
- path pruning |
||||
|
||||
Plugins also provide a way to implement optimisations outside of the mythril code base and to inject them. |
||||
The api that laser currently provides is still unstable and will probably change to suit our needs |
||||
as more plugins get developed. |
||||
|
||||
For the implementation of plugins the following modules are of interest: |
||||
- laser.plugins.plugin |
||||
- laser.plugins.signals |
||||
- laser.svm |
||||
|
||||
Which show the basic interfaces with which plugins are able to interact |
||||
""" |
||||
from mythril.laser.ethereum.plugins.signals import PluginSignal |
@ -0,0 +1,7 @@ |
||||
""" Plugin implementations |
||||
|
||||
This module contains the implementation of some features |
||||
|
||||
- benchmarking |
||||
- pruning |
||||
""" |
@ -0,0 +1,3 @@ |
||||
from mythril.laser.ethereum.plugins.implementations.coverage.coverage_plugin import ( |
||||
InstructionCoveragePlugin, |
||||
) |
@ -0,0 +1,98 @@ |
||||
from mythril.laser.ethereum.svm import LaserEVM |
||||
from mythril.laser.ethereum.plugins.plugin import LaserPlugin |
||||
from mythril.laser.ethereum.state.global_state import GlobalState |
||||
|
||||
from typing import Dict, Tuple, List |
||||
|
||||
import logging |
||||
|
||||
log = logging.getLogger(__name__) |
||||
|
||||
|
||||
class InstructionCoveragePlugin(LaserPlugin): |
||||
"""InstructionCoveragePlugin |
||||
|
||||
This plugin measures the instruction coverage of mythril. |
||||
The instruction coverage is the ratio between the instructions that have been executed |
||||
and the total amount of instructions. |
||||
|
||||
Note that with lazy constraint solving enabled that this metric will be "unsound" as |
||||
reachability will not be considered for the calculation of instruction coverage. |
||||
|
||||
""" |
||||
|
||||
def __init__(self): |
||||
self.coverage = {} # type: Dict[str, Tuple[int, List[bool]]] |
||||
self.initial_coverage = 0 |
||||
self.tx_id = 0 |
||||
|
||||
def initialize(self, symbolic_vm: LaserEVM): |
||||
"""Initializes the instruction coverage plugin |
||||
|
||||
Introduces hooks for each instruction |
||||
:param symbolic_vm: |
||||
:return: |
||||
""" |
||||
self.coverage = {} |
||||
self.initial_coverage = 0 |
||||
self.tx_id = 0 |
||||
|
||||
@symbolic_vm.laser_hook("stop_sym_exec") |
||||
def stop_sym_exec_hook(): |
||||
# Print results |
||||
for code, code_cov in self.coverage.items(): |
||||
cov_percentage = sum(code_cov[1]) / float(code_cov[0]) * 100 |
||||
|
||||
log.info( |
||||
"Achieved {:.2f}% coverage for code: {}".format( |
||||
cov_percentage, code |
||||
) |
||||
) |
||||
|
||||
@symbolic_vm.laser_hook("execute_state") |
||||
def execute_state_hook(global_state: GlobalState): |
||||
# Record coverage |
||||
code = global_state.environment.code.bytecode |
||||
|
||||
if code not in self.coverage.keys(): |
||||
number_of_instructions = len( |
||||
global_state.environment.code.instruction_list |
||||
) |
||||
self.coverage[code] = ( |
||||
number_of_instructions, |
||||
[False] * number_of_instructions, |
||||
) |
||||
|
||||
self.coverage[code][1][global_state.mstate.pc] = True |
||||
|
||||
@symbolic_vm.laser_hook("start_sym_trans") |
||||
def execute_start_sym_trans_hook(): |
||||
self.initial_coverage = self._get_covered_instructions() |
||||
|
||||
@symbolic_vm.laser_hook("stop_sym_trans") |
||||
def execute_stop_sym_trans_hook(): |
||||
end_coverage = self._get_covered_instructions() |
||||
log.info( |
||||
"Number of new instructions covered in tx %d: %d" |
||||
% (self.tx_id, end_coverage - self.initial_coverage) |
||||
) |
||||
self.tx_id += 1 |
||||
|
||||
def _get_covered_instructions(self) -> int: |
||||
"""Gets the total number of covered instructions for all accounts in |
||||
the svm. |
||||
:return: |
||||
""" |
||||
total_covered_instructions = 0 |
||||
for _, cv in self.coverage.items(): |
||||
total_covered_instructions += sum(cv[1]) |
||||
return total_covered_instructions |
||||
|
||||
def is_instruction_covered(self, bytecode, index): |
||||
if bytecode not in self.coverage.keys(): |
||||
return False |
||||
|
||||
try: |
||||
return self.coverage[bytecode][index] |
||||
except IndexError: |
||||
return False |
@ -0,0 +1,43 @@ |
||||
from mythril.laser.ethereum.strategy import BasicSearchStrategy |
||||
from mythril.laser.ethereum.state.global_state import GlobalState |
||||
from mythril.laser.ethereum.plugins.implementations.coverage import ( |
||||
InstructionCoveragePlugin, |
||||
) |
||||
|
||||
|
||||
class CoverageStrategy(BasicSearchStrategy): |
||||
"""Implements a instruction coverage based search strategy |
||||
|
||||
This strategy is quite simple and effective, it prioritizes the execution of instructions that have previously been |
||||
uncovered. Once there is no such global state left in the work list, it will resort to using the super_strategy. |
||||
|
||||
This strategy is intended to be used "on top of" another one |
||||
""" |
||||
|
||||
def __init__( |
||||
self, |
||||
super_strategy: BasicSearchStrategy, |
||||
instruction_coverage_plugin: InstructionCoveragePlugin, |
||||
): |
||||
self.super_strategy = super_strategy |
||||
self.instruction_coverage_plugin = instruction_coverage_plugin |
||||
BasicSearchStrategy.__init__( |
||||
self, super_strategy.work_list, super_strategy.max_depth |
||||
) |
||||
|
||||
def get_strategic_global_state(self) -> GlobalState: |
||||
""" |
||||
Returns the first uncovered global state in the work list if it exists, |
||||
otherwise super_strategy.get_strategic_global_state() is returned. |
||||
""" |
||||
for global_state in self.work_list: |
||||
if not self._is_covered(global_state): |
||||
self.work_list.remove(global_state) |
||||
return global_state |
||||
return self.super_strategy.get_strategic_global_state() |
||||
|
||||
def _is_covered(self, global_state: GlobalState) -> bool: |
||||
""" Checks if the instruction for the given global state is already covered""" |
||||
bytecode = global_state.environment.code.bytecode |
||||
index = global_state.mstate.pc |
||||
return self.instruction_coverage_plugin.is_instruction_covered(bytecode, index) |
@ -0,0 +1,23 @@ |
||||
from mythril.laser.ethereum.svm import LaserEVM |
||||
|
||||
|
||||
class LaserPlugin: |
||||
""" Base class for laser plugins |
||||
|
||||
Functionality in laser that the symbolic execution process does not need to depend on |
||||
can be implemented in the form of a laser plugin. |
||||
|
||||
Laser plugins implement the function initialize(symbolic_vm) which is called with the laser virtual machine |
||||
when they are loaded. |
||||
Regularly a plugin will introduce several hooks into laser in this function |
||||
|
||||
Plugins can direct actions by raising Signals defined in mythril.laser.ethereum.plugins.signals |
||||
For example, a pruning plugin might raise the PluginSkipWorldState signal. |
||||
""" |
||||
|
||||
def initialize(self, symbolic_vm: LaserEVM) -> None: |
||||
""" Initializes this plugin on the symbolic virtual machine |
||||
|
||||
:param symbolic_vm: symbolic virtual machine to initialize the laser plugin on |
||||
""" |
||||
raise NotImplementedError |
@ -0,0 +1,32 @@ |
||||
from mythril.laser.ethereum.plugins.plugin import LaserPlugin |
||||
|
||||
|
||||
class PluginFactory: |
||||
""" The plugin factory constructs the plugins provided with laser """ |
||||
|
||||
@staticmethod |
||||
def build_benchmark_plugin(name: str) -> LaserPlugin: |
||||
""" Creates an instance of the benchmark plugin with the given name """ |
||||
from mythril.laser.ethereum.plugins.implementations.benchmark import ( |
||||
BenchmarkPlugin, |
||||
) |
||||
|
||||
return BenchmarkPlugin(name) |
||||
|
||||
@staticmethod |
||||
def build_mutation_pruner_plugin() -> LaserPlugin: |
||||
""" Creates an instance of the mutation pruner plugin""" |
||||
from mythril.laser.ethereum.plugins.implementations.mutation_pruner import ( |
||||
MutationPruner, |
||||
) |
||||
|
||||
return MutationPruner() |
||||
|
||||
@staticmethod |
||||
def build_instruction_coverage_plugin() -> LaserPlugin: |
||||
""" Creates an instance of the instruction coverage plugin""" |
||||
from mythril.laser.ethereum.plugins.implementations.coverage import ( |
||||
InstructionCoveragePlugin, |
||||
) |
||||
|
||||
return InstructionCoveragePlugin() |
@ -0,0 +1,38 @@ |
||||
from mythril.laser.ethereum.svm import LaserEVM |
||||
from mythril.laser.ethereum.plugins.plugin import LaserPlugin |
||||
|
||||
from typing import List |
||||
import logging |
||||
|
||||
log = logging.getLogger(__name__) |
||||
|
||||
|
||||
class LaserPluginLoader: |
||||
""" |
||||
The LaserPluginLoader is used to abstract the logic relating to plugins. |
||||
Components outside of laser thus don't have to be aware of the interface that plugins provide |
||||
""" |
||||
|
||||
def __init__(self, symbolic_vm: LaserEVM) -> None: |
||||
""" Initializes the plugin loader |
||||
|
||||
:param symbolic_vm: symbolic virtual machine to load plugins for |
||||
""" |
||||
self.symbolic_vm = symbolic_vm |
||||
self.laser_plugins = [] # type: List[LaserPlugin] |
||||
|
||||
def load(self, laser_plugin: LaserPlugin) -> None: |
||||
""" Loads the plugin |
||||
|
||||
:param laser_plugin: plugin that will be loaded in the symbolic virtual machine |
||||
""" |
||||
log.info("Loading plugin: {}".format(str(laser_plugin))) |
||||
laser_plugin.initialize(self.symbolic_vm) |
||||
self.laser_plugins.append(laser_plugin) |
||||
|
||||
def is_enabled(self, laser_plugin: LaserPlugin) -> bool: |
||||
""" Returns whether the plugin is loaded in the symbolic_vm |
||||
|
||||
:param laser_plugin: plugin that will be checked |
||||
""" |
||||
return laser_plugin in self.laser_plugins |
@ -1,454 +0,0 @@ |
||||
"""This module implements classes needed to perform taint analysis.""" |
||||
import copy |
||||
import logging |
||||
from typing import List, Tuple, Union |
||||
|
||||
import mythril.laser.ethereum.util as helper |
||||
from mythril.analysis.symbolic import SymExecWrapper |
||||
from mythril.laser.ethereum.cfg import JumpType, Node |
||||
from mythril.laser.ethereum.state.environment import Environment |
||||
from mythril.laser.ethereum.state.global_state import GlobalState |
||||
from mythril.laser.smt import Expression |
||||
|
||||
log = logging.getLogger(__name__) |
||||
|
||||
|
||||
class TaintRecord: |
||||
"""TaintRecord contains tainting information for a specific (state, node) |
||||
the information specifies the taint status before executing the operation |
||||
belonging to the state.""" |
||||
|
||||
def __init__(self): |
||||
"""Builds a taint record.""" |
||||
self.stack = [] |
||||
self.memory = {} |
||||
self.storage = {} |
||||
self.states = [] |
||||
|
||||
def stack_tainted(self, index: int) -> Union[bool, None]: |
||||
"""Returns taint value of stack element at index. |
||||
|
||||
:param index: |
||||
:return: |
||||
""" |
||||
if index < len(self.stack): |
||||
return self.stack[index] |
||||
return None |
||||
|
||||
def memory_tainted(self, index: int) -> bool: |
||||
"""Returns taint value of memory element at index. |
||||
|
||||
:param index: |
||||
:return: |
||||
""" |
||||
if index in self.memory.keys(): |
||||
return self.memory[index] |
||||
return False |
||||
|
||||
def storage_tainted(self, index: int) -> bool: |
||||
"""Returns taint value of storage element at index. |
||||
|
||||
:param index: |
||||
:return: |
||||
""" |
||||
if index in self.storage.keys(): |
||||
return self.storage[index] |
||||
return False |
||||
|
||||
def add_state(self, state: GlobalState) -> None: |
||||
"""Adds state with this taint record. |
||||
|
||||
:param state: |
||||
""" |
||||
self.states.append(state) |
||||
|
||||
def clone(self) -> "TaintRecord": |
||||
"""Clones this record. |
||||
|
||||
:return: |
||||
""" |
||||
clone = TaintRecord() |
||||
clone.stack = copy.deepcopy(self.stack) |
||||
clone.memory = copy.deepcopy(self.memory) |
||||
clone.storage = copy.deepcopy(self.storage) |
||||
return clone |
||||
|
||||
|
||||
class TaintResult: |
||||
"""Taint analysis result obtained after having ran the taint runner.""" |
||||
|
||||
def __init__(self): |
||||
"""Create a new tains result.""" |
||||
self.records = [] |
||||
|
||||
def check(self, state: GlobalState, stack_index: int) -> Union[bool, None]: |
||||
"""Checks if stack variable is tainted, before executing the |
||||
instruction. |
||||
|
||||
:param state: state to check variable in |
||||
:param stack_index: index of stack variable |
||||
:return: tainted |
||||
""" |
||||
record = self._try_get_record(state) |
||||
if record is None: |
||||
return None |
||||
return record.stack_tainted(stack_index) |
||||
|
||||
def add_records(self, records: List[TaintRecord]) -> None: |
||||
"""Adds records to this taint result. |
||||
|
||||
:param records: |
||||
""" |
||||
self.records += records |
||||
|
||||
def _try_get_record(self, state: GlobalState) -> Union[TaintRecord, None]: |
||||
"""Finds record belonging to the state. |
||||
|
||||
:param state: |
||||
:return: |
||||
""" |
||||
for record in self.records: |
||||
if state in record.states: |
||||
return record |
||||
return None |
||||
|
||||
|
||||
class TaintRunner: |
||||
"""Taint runner, is able to run taint analysis on symbolic execution |
||||
result.""" |
||||
|
||||
@staticmethod |
||||
def execute( |
||||
statespace: SymExecWrapper, node: Node, state: GlobalState, initial_stack=None |
||||
) -> 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 |
||||
:return: TaintResult object containing analysis results |
||||
""" |
||||
if initial_stack is None: |
||||
initial_stack = [] |
||||
result = TaintResult() |
||||
transaction_stack_length = len(node.states[0].transaction_stack) |
||||
# Build initial current_node |
||||
init_record = TaintRecord() |
||||
init_record.stack = initial_stack |
||||
|
||||
state_index = node.states.index(state) |
||||
|
||||
# List of (Node, TaintRecord, index) |
||||
current_nodes = [(node, init_record, state_index)] |
||||
environment = node.states[0].environment |
||||
|
||||
for node, record, index in current_nodes: |
||||
records = TaintRunner.execute_node(node, record, index) |
||||
|
||||
result.add_records(records) |
||||
if len(records) == 0: # continue if there is no record to work on |
||||
continue |
||||
children = TaintRunner.children( |
||||
node, statespace, environment, transaction_stack_length |
||||
) |
||||
for child in children: |
||||
current_nodes.append((child, records[-1], 0)) |
||||
return result |
||||
|
||||
@staticmethod |
||||
def children( |
||||
node: Node, |
||||
statespace: SymExecWrapper, |
||||
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 |
||||
if edge.node_from == node.uid and edge.type != JumpType.Transaction |
||||
] |
||||
children = [] |
||||
for child in direct_children: |
||||
if all( |
||||
len(state.transaction_stack) == transaction_stack_length |
||||
for state in child.states |
||||
): |
||||
children.append(child) |
||||
elif all( |
||||
len(state.transaction_stack) > transaction_stack_length |
||||
for state in child.states |
||||
): |
||||
children += TaintRunner.children( |
||||
child, statespace, environment, transaction_stack_length |
||||
) |
||||
return children |
||||
|
||||
@staticmethod |
||||
def execute_node( |
||||
node: Node, last_record: TaintRecord, state_index=0 |
||||
) -> List[TaintRecord]: |
||||
"""Runs taint analysis on a given node. |
||||
|
||||
:param node: node to analyse |
||||
:param last_record: last taint record to work from |
||||
:param state_index: state index to start from |
||||
:return: List of taint records linked to the states in this node |
||||
""" |
||||
records = [last_record] |
||||
for index in range(state_index, len(node.states)): |
||||
current_state = node.states[index] |
||||
records.append(TaintRunner.execute_state(records[-1], current_state)) |
||||
return records[1:] |
||||
|
||||
@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) |
||||
new_record = record.clone() |
||||
|
||||
# Apply Change |
||||
op = state.get_current_instruction()["opcode"] |
||||
|
||||
if op in TaintRunner.stack_taint_table.keys(): |
||||
mutator = TaintRunner.stack_taint_table[op] |
||||
TaintRunner.mutate_stack(new_record, mutator) |
||||
elif op.startswith("PUSH"): |
||||
TaintRunner.mutate_push(op, new_record) |
||||
elif op.startswith("DUP"): |
||||
TaintRunner.mutate_dup(op, new_record) |
||||
elif op.startswith("SWAP"): |
||||
TaintRunner.mutate_swap(op, new_record) |
||||
elif op is "MLOAD": |
||||
TaintRunner.mutate_mload(new_record, state.mstate.stack[-1]) |
||||
elif op.startswith("MSTORE"): |
||||
TaintRunner.mutate_mstore(new_record, state.mstate.stack[-1]) |
||||
elif op is "SLOAD": |
||||
TaintRunner.mutate_sload(new_record, state.mstate.stack[-1]) |
||||
elif op is "SSTORE": |
||||
TaintRunner.mutate_sstore(new_record, state.mstate.stack[-1]) |
||||
elif op.startswith("LOG"): |
||||
TaintRunner.mutate_log(new_record, op) |
||||
elif op in ("CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"): |
||||
TaintRunner.mutate_call(new_record, op) |
||||
else: |
||||
log.debug("Unknown operation encountered: {}".format(op)) |
||||
|
||||
return new_record |
||||
|
||||
@staticmethod |
||||
def mutate_stack(record: TaintRecord, mutator: Tuple[int, int]) -> None: |
||||
""" |
||||
|
||||
:param record: |
||||
:param mutator: |
||||
""" |
||||
pop, push = mutator |
||||
|
||||
values = [] |
||||
for i in range(pop): |
||||
values.append(record.stack.pop()) |
||||
|
||||
taint = any(values) |
||||
|
||||
for i in range(push): |
||||
record.stack.append(taint) |
||||
|
||||
@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 |
||||
record.stack[l], record.stack[i] = record.stack[i], record.stack[l] |
||||
|
||||
@staticmethod |
||||
def mutate_mload(record: TaintRecord, op0: Expression) -> None: |
||||
""" |
||||
|
||||
:param record: |
||||
:param op0: |
||||
:return: |
||||
""" |
||||
_ = record.stack.pop() |
||||
try: |
||||
index = helper.get_concrete_int(op0) |
||||
except TypeError: |
||||
log.debug("Can't MLOAD taint track symbolically") |
||||
record.stack.append(False) |
||||
return |
||||
|
||||
record.stack.append(record.memory_tainted(index)) |
||||
|
||||
@staticmethod |
||||
def mutate_mstore(record: TaintRecord, op0: Expression) -> None: |
||||
""" |
||||
|
||||
:param record: |
||||
:param op0: |
||||
:return: |
||||
""" |
||||
_, value_taint = record.stack.pop(), record.stack.pop() |
||||
try: |
||||
index = helper.get_concrete_int(op0) |
||||
except TypeError: |
||||
log.debug("Can't mstore taint track symbolically") |
||||
return |
||||
|
||||
record.memory[index] = value_taint |
||||
|
||||
@staticmethod |
||||
def mutate_sload(record: TaintRecord, op0: Expression) -> None: |
||||
""" |
||||
|
||||
:param record: |
||||
:param op0: |
||||
:return: |
||||
""" |
||||
_ = record.stack.pop() |
||||
try: |
||||
index = helper.get_concrete_int(op0) |
||||
except TypeError: |
||||
log.debug("Can't MLOAD taint track symbolically") |
||||
record.stack.append(False) |
||||
return |
||||
|
||||
record.stack.append(record.storage_tainted(index)) |
||||
|
||||
@staticmethod |
||||
def mutate_sstore(record: TaintRecord, op0: Expression) -> None: |
||||
""" |
||||
|
||||
:param record: |
||||
:param op0: |
||||
:return: |
||||
""" |
||||
_, value_taint = record.stack.pop(), record.stack.pop() |
||||
try: |
||||
index = helper.get_concrete_int(op0) |
||||
except TypeError: |
||||
log.debug("Can't mstore taint track symbolically") |
||||
return |
||||
|
||||
record.storage[index] = value_taint |
||||
|
||||
@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 |
||||
for _ in range(pops): |
||||
record.stack.pop() |
||||
|
||||
record.stack.append(False) |
||||
|
||||
stack_taint_table = { |
||||
# instruction: (taint source, taint target) |
||||
"POP": (1, 0), |
||||
"ADD": (2, 1), |
||||
"MUL": (2, 1), |
||||
"SUB": (2, 1), |
||||
"AND": (2, 1), |
||||
"OR": (2, 1), |
||||
"XOR": (2, 1), |
||||
"NOT": (1, 1), |
||||
"BYTE": (2, 1), |
||||
"DIV": (2, 1), |
||||
"MOD": (2, 1), |
||||
"SDIV": (2, 1), |
||||
"SMOD": (2, 1), |
||||
"ADDMOD": (3, 1), |
||||
"MULMOD": (3, 1), |
||||
"EXP": (2, 1), |
||||
"SIGNEXTEND": (2, 1), |
||||
"LT": (2, 1), |
||||
"GT": (2, 1), |
||||
"SLT": (2, 1), |
||||
"SGT": (2, 1), |
||||
"EQ": (2, 1), |
||||
"ISZERO": (1, 1), |
||||
"CALLVALUE": (0, 1), |
||||
"CALLDATALOAD": (1, 1), |
||||
"CALLDATACOPY": (3, 0), # todo |
||||
"CALLDATASIZE": (0, 1), |
||||
"ADDRESS": (0, 1), |
||||
"BALANCE": (1, 1), |
||||
"ORIGIN": (0, 1), |
||||
"CALLER": (0, 1), |
||||
"CODESIZE": (0, 1), |
||||
"SHA3": (2, 1), |
||||
"GASPRICE": (0, 1), |
||||
"CODECOPY": (3, 0), |
||||
"EXTCODESIZE": (1, 1), |
||||
"EXTCODECOPY": (4, 0), |
||||
"RETURNDATASIZE": (0, 1), |
||||
"BLOCKHASH": (1, 1), |
||||
"COINBASE": (0, 1), |
||||
"TIMESTAMP": (0, 1), |
||||
"NUMBER": (0, 1), |
||||
"DIFFICULTY": (0, 1), |
||||
"GASLIMIT": (0, 1), |
||||
"JUMP": (1, 0), |
||||
"JUMPI": (2, 0), |
||||
"PC": (0, 1), |
||||
"MSIZE": (0, 1), |
||||
"GAS": (0, 1), |
||||
"CREATE": (3, 1), |
||||
"CREATE2": (4, 1), |
||||
"RETURN": (2, 0), |
||||
} |
@ -1,721 +0,0 @@ |
||||
#!/usr/bin/env python3 |
||||
# -*- coding: utf-8 -*- |
||||
"""mythril.py: Bug hunting on the Ethereum blockchain |
||||
|
||||
http://www.github.com/b-mueller/mythril |
||||
""" |
||||
|
||||
import codecs |
||||
import logging |
||||
import os |
||||
import platform |
||||
import re |
||||
import traceback |
||||
from pathlib import Path |
||||
from shutil import copyfile |
||||
from configparser import ConfigParser |
||||
|
||||
import solc |
||||
from ethereum import utils |
||||
from solc.exceptions import SolcError |
||||
|
||||
from mythril.ethereum import util |
||||
from mythril.ethereum.evmcontract import EVMContract |
||||
from mythril.ethereum.interface.rpc.client import EthJsonRpc |
||||
from mythril.ethereum.interface.rpc.exceptions import ConnectionError |
||||
from mythril.solidity.soliditycontract import SolidityContract, get_contracts_from_file |
||||
from mythril.support import signatures |
||||
from mythril.support.source_support import Source |
||||
from mythril.support.loader import DynLoader |
||||
from mythril.exceptions import CompilerError, NoContractFoundError, CriticalError |
||||
from mythril.analysis.symbolic import SymExecWrapper |
||||
from mythril.analysis.callgraph import generate_graph |
||||
from mythril.analysis.traceexplore import get_serializable_statespace |
||||
from mythril.analysis.security import fire_lasers, retrieve_callback_issues |
||||
from mythril.analysis.report import Report |
||||
from mythril.support.truffle import analyze_truffle_project |
||||
from mythril.ethereum.interface.leveldb.client import EthLevelDB |
||||
from mythril.laser.smt import SolverStatistics |
||||
from mythril.support.start_time import StartTime |
||||
|
||||
|
||||
log = logging.getLogger(__name__) |
||||
|
||||
|
||||
class Mythril(object): |
||||
"""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 |
||||
|
||||
.. code-block:: python |
||||
|
||||
mythril = Mythril() |
||||
mythril.set_api_rpc_infura() |
||||
|
||||
# (optional) other API adapters |
||||
mythril.set_api_rpc(args) |
||||
mythril.set_api_rpc_localhost() |
||||
mythril.set_api_leveldb(path) |
||||
|
||||
# (optional) other func |
||||
mythril.analyze_truffle_project(args) |
||||
mythril.search_db(args) |
||||
|
||||
# load contract |
||||
mythril.load_from_bytecode(bytecode) |
||||
mythril.load_from_address(address) |
||||
mythril.load_from_solidity(solidity_file) |
||||
|
||||
# analyze |
||||
print(mythril.fire_lasers(args).as_text()) |
||||
|
||||
# (optional) graph |
||||
for contract in mythril.contracts: |
||||
# prints html or save it to file |
||||
print(mythril.graph_html(args)) |
||||
|
||||
# (optional) other funcs |
||||
mythril.dump_statespaces(args) |
||||
mythril.disassemble(contract) |
||||
mythril.get_state_variable_from_storage(args) |
||||
""" |
||||
|
||||
def __init__( |
||||
self, |
||||
solv=None, |
||||
solc_args=None, |
||||
dynld=False, |
||||
enable_online_lookup=False, |
||||
onchain_storage_access=True, |
||||
): |
||||
|
||||
self.solv = solv |
||||
self.solc_args = solc_args |
||||
self.dynld = dynld |
||||
self.onchain_storage_access = onchain_storage_access |
||||
self.enable_online_lookup = enable_online_lookup |
||||
|
||||
self.mythril_dir = self._init_mythril_dir() |
||||
|
||||
# tries mythril_dir/signatures.db by default (provide path= arg to make this configurable) |
||||
self.sigs = signatures.SignatureDB( |
||||
enable_online_lookup=self.enable_online_lookup |
||||
) |
||||
|
||||
self.solc_binary = self._init_solc_binary(solv) |
||||
self.config_path = os.path.join(self.mythril_dir, "config.ini") |
||||
self.leveldb_dir = self._init_config() |
||||
|
||||
self.eth = None # ethereum API client |
||||
self.eth_db = None # ethereum LevelDB client |
||||
|
||||
self.contracts = [] # loaded contracts |
||||
|
||||
@staticmethod |
||||
def _init_mythril_dir(): |
||||
try: |
||||
mythril_dir = os.environ["MYTHRIL_DIR"] |
||||
except KeyError: |
||||
mythril_dir = os.path.join(os.path.expanduser("~"), ".mythril") |
||||
|
||||
if not os.path.exists(mythril_dir): |
||||
# Initialize data directory |
||||
log.info("Creating mythril data directory") |
||||
os.mkdir(mythril_dir) |
||||
|
||||
db_path = str(Path(mythril_dir) / "signatures.db") |
||||
if not os.path.exists(db_path): |
||||
# if the default mythril dir doesn't contain a signature DB |
||||
# initialize it with the default one from the project root |
||||
asset_dir = Path(__file__).parent / "support" / "assets" |
||||
copyfile(str(asset_dir / "signatures.db"), db_path) |
||||
|
||||
return mythril_dir |
||||
|
||||
def _init_config(self): |
||||
"""If no config file exists, create it and add default options. |
||||
|
||||
Default LevelDB path is specified based on OS |
||||
dynamic loading is set to infura by default in the file |
||||
Returns: leveldb directory |
||||
""" |
||||
|
||||
system = platform.system().lower() |
||||
leveldb_fallback_dir = os.path.expanduser("~") |
||||
if system.startswith("darwin"): |
||||
leveldb_fallback_dir = os.path.join( |
||||
leveldb_fallback_dir, "Library", "Ethereum" |
||||
) |
||||
elif system.startswith("windows"): |
||||
leveldb_fallback_dir = os.path.join( |
||||
leveldb_fallback_dir, "AppData", "Roaming", "Ethereum" |
||||
) |
||||
else: |
||||
leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, ".ethereum") |
||||
leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, "geth", "chaindata") |
||||
|
||||
if not os.path.exists(self.config_path): |
||||
log.info("No config file found. Creating default: " + self.config_path) |
||||
open(self.config_path, "a").close() |
||||
|
||||
config = ConfigParser(allow_no_value=True) |
||||
config.optionxform = str |
||||
config.read(self.config_path, "utf-8") |
||||
if "defaults" not in config.sections(): |
||||
self._add_default_options(config) |
||||
|
||||
if not config.has_option("defaults", "leveldb_dir"): |
||||
self._add_leveldb_option(config, leveldb_fallback_dir) |
||||
|
||||
if not config.has_option("defaults", "dynamic_loading"): |
||||
self._add_dynamic_loading_option(config) |
||||
|
||||
with codecs.open(self.config_path, "w", "utf-8") as fp: |
||||
config.write(fp) |
||||
|
||||
leveldb_dir = config.get( |
||||
"defaults", "leveldb_dir", fallback=leveldb_fallback_dir |
||||
) |
||||
return os.path.expanduser(leveldb_dir) |
||||
|
||||
@staticmethod |
||||
def _add_default_options(config): |
||||
config.add_section("defaults") |
||||
|
||||
@staticmethod |
||||
def _add_leveldb_option(config, leveldb_fallback_dir): |
||||
config.set("defaults", "#Default chaindata locations:") |
||||
config.set("defaults", "#– Mac: ~/Library/Ethereum/geth/chaindata") |
||||
config.set("defaults", "#– Linux: ~/.ethereum/geth/chaindata") |
||||
config.set( |
||||
"defaults", |
||||
"#– Windows: %USERPROFILE%\\AppData\\Roaming\\Ethereum\\geth\\chaindata", |
||||
) |
||||
config.set("defaults", "leveldb_dir", leveldb_fallback_dir) |
||||
|
||||
@staticmethod |
||||
def _add_dynamic_loading_option(config): |
||||
config.set("defaults", "#– To connect to Infura use dynamic_loading: infura") |
||||
config.set( |
||||
"defaults", |
||||
"#– To connect to Rpc use " |
||||
"dynamic_loading: HOST:PORT / ganache / infura-[network_name]", |
||||
) |
||||
config.set( |
||||
"defaults", "#– To connect to local host use dynamic_loading: localhost" |
||||
) |
||||
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 |
||||
|
||||
@staticmethod |
||||
def _init_solc_binary(version): |
||||
"""Figure out solc binary and version. |
||||
|
||||
Only proper versions are supported. No nightlies, commits etc (such as available in remix). |
||||
""" |
||||
|
||||
if not version: |
||||
return os.environ.get("SOLC") or "solc" |
||||
|
||||
# tried converting input to semver, seemed not necessary so just slicing for now |
||||
main_version = solc.main.get_solc_version_string() |
||||
main_version_number = re.match(r"\d+.\d+.\d+", main_version) |
||||
if main_version is None: |
||||
raise CriticalError( |
||||
"Could not extract solc version from string {}".format(main_version) |
||||
) |
||||
if version == main_version_number: |
||||
log.info("Given version matches installed version") |
||||
solc_binary = os.environ.get("SOLC") or "solc" |
||||
else: |
||||
solc_binary = util.solc_exists(version) |
||||
if solc_binary: |
||||
log.info("Given version is already installed") |
||||
else: |
||||
try: |
||||
solc.install_solc("v" + version) |
||||
solc_binary = util.solc_exists(version) |
||||
if not solc_binary: |
||||
raise SolcError() |
||||
except SolcError: |
||||
raise CriticalError( |
||||
"There was an error when trying to install the specified solc version" |
||||
) |
||||
|
||||
log.info("Setting the compiler to %s", solc_binary) |
||||
|
||||
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): |
||||
"""Set the RPC mode to INFURA on mainnet.""" |
||||
self.eth = EthJsonRpc("mainnet.infura.io", 443, True) |
||||
log.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: |
||||
m = re.match(r"infura-(.*)", rpc) |
||||
if m and m.group(1) in ["mainnet", "rinkeby", "kovan", "ropsten"]: |
||||
rpcconfig = (m.group(1) + ".infura.io", 443, True) |
||||
else: |
||||
try: |
||||
host, port = rpc.split(":") |
||||
rpcconfig = (host, int(port), rpctls) |
||||
except ValueError: |
||||
raise CriticalError( |
||||
"Invalid RPC argument, use 'ganache', 'infura-[network]' or 'HOST:PORT'" |
||||
) |
||||
|
||||
if rpcconfig: |
||||
self.eth = EthJsonRpc(rpcconfig[0], int(rpcconfig[1]), rpcconfig[2]) |
||||
log.info("Using RPC settings: %s" % str(rpcconfig)) |
||||
else: |
||||
raise CriticalError("Invalid RPC settings, check help for details.") |
||||
|
||||
def set_api_rpc_localhost(self): |
||||
"""Set the RPC mode to a local instance.""" |
||||
self.eth = EthJsonRpc("localhost", 8545) |
||||
log.info("Using default RPC settings: http://localhost:8545") |
||||
|
||||
def set_api_from_config_path(self): |
||||
"""Set the RPC mode based on a given config file.""" |
||||
config = ConfigParser(allow_no_value=False) |
||||
config.optionxform = str |
||||
config.read(self.config_path, "utf-8") |
||||
if config.has_option("defaults", "dynamic_loading"): |
||||
dynamic_loading = config.get("defaults", "dynamic_loading") |
||||
else: |
||||
dynamic_loading = "infura" |
||||
if dynamic_loading == "infura": |
||||
self.set_api_rpc_infura() |
||||
elif dynamic_loading == "localhost": |
||||
self.set_api_rpc_localhost() |
||||
else: |
||||
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: |
||||
self.eth_db.search(search, search_callback) |
||||
|
||||
except SyntaxError: |
||||
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: |
||||
self.contracts.append( |
||||
EVMContract( |
||||
code=code, |
||||
name="MAIN", |
||||
enable_online_lookup=self.enable_online_lookup, |
||||
) |
||||
) |
||||
else: |
||||
self.contracts.append( |
||||
EVMContract( |
||||
creation_code=code, |
||||
name="MAIN", |
||||
enable_online_lookup=self.enable_online_lookup, |
||||
) |
||||
) |
||||
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...'.") |
||||
|
||||
try: |
||||
code = self.eth.eth_getCode(address) |
||||
except FileNotFoundError as e: |
||||
raise CriticalError("IPC error: " + str(e)) |
||||
except ConnectionError: |
||||
raise CriticalError( |
||||
"Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly." |
||||
) |
||||
except Exception as e: |
||||
raise CriticalError("IPC / RPC error: " + str(e)) |
||||
else: |
||||
if code == "0x" or code == "0x0": |
||||
raise CriticalError( |
||||
"Received an empty response from eth_getCode. Check the contract address and verify that you are on the correct chain." |
||||
) |
||||
else: |
||||
self.contracts.append( |
||||
EVMContract( |
||||
code, |
||||
name=address, |
||||
enable_online_lookup=self.enable_online_lookup, |
||||
) |
||||
) |
||||
return address, self.contracts[-1] # return address and contract object |
||||
|
||||
def load_from_solidity(self, solidity_files): |
||||
""" |
||||
|
||||
:param solidity_files: |
||||
:return: |
||||
""" |
||||
address = util.get_indexed_address(0) |
||||
contracts = [] |
||||
for file in solidity_files: |
||||
if ":" in file: |
||||
file, contract_name = file.split(":") |
||||
else: |
||||
contract_name = None |
||||
|
||||
file = os.path.expanduser(file) |
||||
|
||||
try: |
||||
# import signatures from solidity source |
||||
self.sigs.import_solidity_file( |
||||
file, solc_binary=self.solc_binary, solc_args=self.solc_args |
||||
) |
||||
if contract_name is not None: |
||||
contract = SolidityContract( |
||||
input_file=file, |
||||
name=contract_name, |
||||
solc_args=self.solc_args, |
||||
solc_binary=self.solc_binary, |
||||
) |
||||
self.contracts.append(contract) |
||||
contracts.append(contract) |
||||
else: |
||||
for contract in get_contracts_from_file( |
||||
input_file=file, |
||||
solc_args=self.solc_args, |
||||
solc_binary=self.solc_binary, |
||||
): |
||||
self.contracts.append(contract) |
||||
contracts.append(contract) |
||||
|
||||
except FileNotFoundError: |
||||
raise CriticalError("Input file not found: " + file) |
||||
except CompilerError as e: |
||||
raise CriticalError(e) |
||||
except NoContractFoundError: |
||||
log.error( |
||||
"The file " + file + " does not contain a compilable contract." |
||||
) |
||||
|
||||
return address, contracts |
||||
|
||||
def dump_statespace( |
||||
self, |
||||
strategy, |
||||
contract, |
||||
address=None, |
||||
max_depth=None, |
||||
execution_timeout=None, |
||||
create_timeout=None, |
||||
enable_iprof=False, |
||||
): |
||||
""" |
||||
|
||||
:param strategy: |
||||
:param contract: |
||||
:param address: |
||||
:param max_depth: |
||||
:param execution_timeout: |
||||
:param create_timeout: |
||||
:return: |
||||
""" |
||||
sym = SymExecWrapper( |
||||
contract, |
||||
address, |
||||
strategy, |
||||
dynloader=DynLoader( |
||||
self.eth, |
||||
storage_loading=self.onchain_storage_access, |
||||
contract_loading=self.dynld, |
||||
), |
||||
max_depth=max_depth, |
||||
execution_timeout=execution_timeout, |
||||
create_timeout=create_timeout, |
||||
enable_iprof=enable_iprof, |
||||
) |
||||
|
||||
return get_serializable_statespace(sym) |
||||
|
||||
def graph_html( |
||||
self, |
||||
strategy, |
||||
contract, |
||||
address, |
||||
max_depth=None, |
||||
enable_physics=False, |
||||
phrackify=False, |
||||
execution_timeout=None, |
||||
create_timeout=None, |
||||
enable_iprof=False, |
||||
): |
||||
""" |
||||
|
||||
: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, |
||||
strategy, |
||||
dynloader=DynLoader( |
||||
self.eth, |
||||
storage_loading=self.onchain_storage_access, |
||||
contract_loading=self.dynld, |
||||
), |
||||
max_depth=max_depth, |
||||
execution_timeout=execution_timeout, |
||||
create_timeout=create_timeout, |
||||
enable_iprof=enable_iprof, |
||||
) |
||||
return generate_graph(sym, physics=enable_physics, phrackify=phrackify) |
||||
|
||||
def fire_lasers( |
||||
self, |
||||
strategy, |
||||
contracts=None, |
||||
address=None, |
||||
modules=None, |
||||
verbose_report=False, |
||||
max_depth=None, |
||||
execution_timeout=None, |
||||
create_timeout=None, |
||||
transaction_count=None, |
||||
enable_iprof=False, |
||||
): |
||||
""" |
||||
|
||||
: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 = [] |
||||
SolverStatistics().enabled = True |
||||
exceptions = [] |
||||
for contract in contracts or self.contracts: |
||||
StartTime() # Reinitialize start time for new contracts |
||||
try: |
||||
sym = SymExecWrapper( |
||||
contract, |
||||
address, |
||||
strategy, |
||||
dynloader=DynLoader( |
||||
self.eth, |
||||
storage_loading=self.onchain_storage_access, |
||||
contract_loading=self.dynld, |
||||
), |
||||
max_depth=max_depth, |
||||
execution_timeout=execution_timeout, |
||||
create_timeout=create_timeout, |
||||
transaction_count=transaction_count, |
||||
modules=modules, |
||||
compulsory_statespace=False, |
||||
enable_iprof=enable_iprof, |
||||
) |
||||
issues = fire_lasers(sym, modules) |
||||
except KeyboardInterrupt: |
||||
log.critical("Keyboard Interrupt") |
||||
issues = retrieve_callback_issues(modules) |
||||
except Exception: |
||||
log.critical( |
||||
"Exception occurred, aborting analysis. Please report this issue to the Mythril GitHub page.\n" |
||||
+ traceback.format_exc() |
||||
) |
||||
issues = retrieve_callback_issues(modules) |
||||
exceptions.append(traceback.format_exc()) |
||||
for issue in issues: |
||||
issue.add_code_info(contract) |
||||
|
||||
all_issues += issues |
||||
log.info("Solver statistics: \n{}".format(str(SolverStatistics()))) |
||||
|
||||
source_data = Source() |
||||
source_data.get_source_from_contracts_list(self.contracts) |
||||
# Finally, output the results |
||||
report = Report(verbose_report, source_data, exceptions=exceptions) |
||||
for issue in all_issues: |
||||
report.append_issue(issue) |
||||
|
||||
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, []) |
||||
try: |
||||
if params[0] == "mapping": |
||||
if len(params) < 3: |
||||
raise CriticalError("Invalid number of parameters.") |
||||
position = int(params[1]) |
||||
position_formatted = utils.zpad(utils.int_to_big_endian(position), 32) |
||||
for i in range(2, len(params)): |
||||
key = bytes(params[i], "utf8") |
||||
key_formatted = utils.rzpad(key, 32) |
||||
mappings.append( |
||||
int.from_bytes( |
||||
utils.sha3(key_formatted + position_formatted), |
||||
byteorder="big", |
||||
) |
||||
) |
||||
|
||||
length = len(mappings) |
||||
if length == 1: |
||||
position = mappings[0] |
||||
|
||||
else: |
||||
if len(params) >= 4: |
||||
raise CriticalError("Invalid number of parameters.") |
||||
|
||||
if len(params) >= 1: |
||||
position = int(params[0]) |
||||
if len(params) >= 2: |
||||
length = int(params[1]) |
||||
if len(params) == 3 and params[2] == "array": |
||||
position_formatted = utils.zpad( |
||||
utils.int_to_big_endian(position), 32 |
||||
) |
||||
position = int.from_bytes( |
||||
utils.sha3(position_formatted), byteorder="big" |
||||
) |
||||
|
||||
except ValueError: |
||||
raise CriticalError( |
||||
"Invalid storage index. Please provide a numeric value." |
||||
) |
||||
|
||||
outtxt = [] |
||||
|
||||
try: |
||||
if length == 1: |
||||
outtxt.append( |
||||
"{}: {}".format( |
||||
position, self.eth.eth_getStorageAt(address, position) |
||||
) |
||||
) |
||||
else: |
||||
if len(mappings) > 0: |
||||
for i in range(0, len(mappings)): |
||||
position = mappings[i] |
||||
outtxt.append( |
||||
"{}: {}".format( |
||||
hex(position), |
||||
self.eth.eth_getStorageAt(address, position), |
||||
) |
||||
) |
||||
else: |
||||
for i in range(position, position + length): |
||||
outtxt.append( |
||||
"{}: {}".format( |
||||
hex(i), self.eth.eth_getStorageAt(address, i) |
||||
) |
||||
) |
||||
except FileNotFoundError as e: |
||||
raise CriticalError("IPC error: " + str(e)) |
||||
except ConnectionError: |
||||
raise CriticalError( |
||||
"Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly." |
||||
) |
||||
return "\n".join(outtxt) |
||||
|
||||
@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() |
@ -1,30 +0,0 @@ |
||||
from mythril.laser.ethereum.taint_analysis import * |
||||
|
||||
|
||||
def test_mutate_not_tainted(): |
||||
# Arrange |
||||
record = TaintRecord() |
||||
|
||||
record.stack = [True, False, False] |
||||
# Act |
||||
TaintRunner.mutate_stack(record, (2, 1)) |
||||
|
||||
# Assert |
||||
assert record.stack_tainted(0) |
||||
assert record.stack_tainted(1) is False |
||||
assert record.stack == [True, False] |
||||
|
||||
|
||||
def test_mutate_tainted(): |
||||
# Arrange |
||||
record = TaintRecord() |
||||
|
||||
record.stack = [True, False, True] |
||||
|
||||
# Act |
||||
TaintRunner.mutate_stack(record, (2, 1)) |
||||
|
||||
# Assert |
||||
assert record.stack_tainted(0) |
||||
assert record.stack_tainted(1) |
||||
assert record.stack == [True, True] |
@ -1,36 +0,0 @@ |
||||
from mythril.laser.ethereum.taint_analysis import * |
||||
|
||||
|
||||
def test_record_tainted_check(): |
||||
# arrange |
||||
record = TaintRecord() |
||||
record.stack = [True, False, True] |
||||
|
||||
# act |
||||
tainted = record.stack_tainted(2) |
||||
|
||||
# assert |
||||
assert tainted is True |
||||
|
||||
|
||||
def test_record_untainted_check(): |
||||
# arrange |
||||
record = TaintRecord() |
||||
record.stack = [True, False, False] |
||||
|
||||
# act |
||||
tainted = record.stack_tainted(2) |
||||
|
||||
# assert |
||||
assert tainted is False |
||||
|
||||
|
||||
def test_record_untouched_check(): |
||||
# arrange |
||||
record = TaintRecord() |
||||
|
||||
# act |
||||
tainted = record.stack_tainted(3) |
||||
|
||||
# assert |
||||
assert tainted is None |
@ -1,35 +0,0 @@ |
||||
from mythril.laser.ethereum.taint_analysis import * |
||||
from mythril.laser.ethereum.state.global_state import GlobalState |
||||
|
||||
|
||||
def test_result_state(): |
||||
# arrange |
||||
taint_result = TaintResult() |
||||
record = TaintRecord() |
||||
state = GlobalState(2, None, None) |
||||
state.mstate.stack = [1, 2, 3] |
||||
record.add_state(state) |
||||
record.stack = [False, False, False] |
||||
# act |
||||
taint_result.add_records([record]) |
||||
tainted = taint_result.check(state, 2) |
||||
|
||||
# assert |
||||
assert tainted is False |
||||
assert record in taint_result.records |
||||
|
||||
|
||||
def test_result_no_state(): |
||||
# arrange |
||||
taint_result = TaintResult() |
||||
record = TaintRecord() |
||||
state = GlobalState(2, None, None) |
||||
state.mstate.stack = [1, 2, 3] |
||||
|
||||
# act |
||||
taint_result.add_records([record]) |
||||
tainted = taint_result.check(state, 2) |
||||
|
||||
# assert |
||||
assert tainted is None |
||||
assert record in taint_result.records |
@ -1,99 +0,0 @@ |
||||
import mock |
||||
import pytest |
||||
from pytest_mock import mocker |
||||
from mythril.laser.ethereum.taint_analysis import * |
||||
from mythril.laser.ethereum.cfg import Node, Edge |
||||
from mythril.laser.ethereum.state.account import Account |
||||
from mythril.laser.ethereum.state.environment import Environment |
||||
from mythril.laser.ethereum.state.machine_state import MachineState |
||||
from mythril.laser.ethereum.state.global_state import GlobalState |
||||
from mythril.laser.ethereum.svm import LaserEVM |
||||
|
||||
|
||||
def test_execute_state(mocker): |
||||
record = TaintRecord() |
||||
record.stack = [True, False, True] |
||||
|
||||
state = GlobalState(None, None, None) |
||||
state.mstate.stack = [1, 2, 3] |
||||
mocker.patch.object(state, "get_current_instruction") |
||||
state.get_current_instruction.return_value = {"opcode": "ADD"} |
||||
|
||||
# Act |
||||
new_record = TaintRunner.execute_state(record, state) |
||||
|
||||
# Assert |
||||
assert new_record.stack == [True, True] |
||||
assert record.stack == [True, False, True] |
||||
|
||||
|
||||
def test_execute_node(mocker): |
||||
record = TaintRecord() |
||||
record.stack = [True, True, False, False] |
||||
|
||||
state_1 = GlobalState(None, None, None) |
||||
state_1.mstate.stack = [1, 2, 3, 1] |
||||
state_1.mstate.pc = 1 |
||||
mocker.patch.object(state_1, "get_current_instruction") |
||||
state_1.get_current_instruction.return_value = {"opcode": "SWAP1"} |
||||
|
||||
state_2 = GlobalState(None, 1, None) |
||||
state_2.mstate.stack = [1, 2, 4, 1] |
||||
mocker.patch.object(state_2, "get_current_instruction") |
||||
state_2.get_current_instruction.return_value = {"opcode": "ADD"} |
||||
|
||||
node = Node("Test contract") |
||||
node.states = [state_1, state_2] |
||||
|
||||
# Act |
||||
records = TaintRunner.execute_node(node, record) |
||||
|
||||
# Assert |
||||
assert len(records) == 2 |
||||
|
||||
assert records[0].stack == [True, True, False, False] |
||||
assert records[1].stack == [True, True, False] |
||||
|
||||
assert state_2 in records[0].states |
||||
assert state_1 in record.states |
||||
|
||||
|
||||
def test_execute(mocker): |
||||
active_account = Account("0x00") |
||||
environment = Environment(active_account, None, None, None, None, None) |
||||
state_1 = GlobalState(None, environment, None, MachineState(gas_limit=8000000)) |
||||
state_1.mstate.stack = [1, 2] |
||||
mocker.patch.object(state_1, "get_current_instruction") |
||||
state_1.get_current_instruction.return_value = {"opcode": "PUSH"} |
||||
|
||||
state_2 = GlobalState(None, environment, None, MachineState(gas_limit=8000000)) |
||||
state_2.mstate.stack = [1, 2, 3] |
||||
mocker.patch.object(state_2, "get_current_instruction") |
||||
state_2.get_current_instruction.return_value = {"opcode": "ADD"} |
||||
|
||||
node_1 = Node("Test contract") |
||||
node_1.states = [state_1, state_2] |
||||
|
||||
state_3 = GlobalState(None, environment, None, MachineState(gas_limit=8000000)) |
||||
state_3.mstate.stack = [1, 2] |
||||
mocker.patch.object(state_3, "get_current_instruction") |
||||
state_3.get_current_instruction.return_value = {"opcode": "ADD"} |
||||
|
||||
node_2 = Node("Test contract") |
||||
node_2.states = [state_3] |
||||
|
||||
edge = Edge(node_1.uid, node_2.uid) |
||||
|
||||
statespace = LaserEVM(None) |
||||
statespace.edges = [edge] |
||||
statespace.nodes[node_1.uid] = node_1 |
||||
statespace.nodes[node_2.uid] = node_2 |
||||
|
||||
# Act |
||||
result = TaintRunner.execute(statespace, node_1, state_1, [True, True]) |
||||
|
||||
# Assert |
||||
print(result) |
||||
assert len(result.records) == 3 |
||||
assert result.records[2].states == [] |
||||
assert state_3 in result.records[1].states |
File diff suppressed because one or more lines are too long
@ -1,36 +0,0 @@ |
||||
{ |
||||
"error": null, |
||||
"issues": [ |
||||
{ |
||||
"address": 158, |
||||
"contract": "Unknown", |
||||
"debug": "<DEBUG-DATA>", |
||||
"description": "The arithmetic operation can result in integer overflow.\n", |
||||
"function": "_function_0x83f12fec", |
||||
"swc-id": "101", |
||||
"title": "Integer Overflow", |
||||
"type": "Warning" |
||||
}, |
||||
{ |
||||
"address": 278, |
||||
"contract": "Unknown", |
||||
"debug": "<DEBUG-DATA>", |
||||
"description": "The arithmetic operation can result in integer overflow.\n", |
||||
"function": "_function_0x83f12fec", |
||||
"swc-id": "101", |
||||
"title": "Integer Overflow", |
||||
"type": "Warning" |
||||
}, |
||||
{ |
||||
"address": 378, |
||||
"contract": "Unknown", |
||||
"debug": "<DEBUG-DATA>", |
||||
"description": "The substraction can result in an integer underflow.\n", |
||||
"function": "_function_0x83f12fec", |
||||
"swc-id": "101", |
||||
"title": "Integer Underflow", |
||||
"type": "Warning" |
||||
} |
||||
], |
||||
"success": true |
||||
} |
@ -1,34 +0,0 @@ |
||||
# Analysis results for test-filename.sol |
||||
|
||||
## Integer Overflow |
||||
- SWC ID: 101 |
||||
- Type: Warning |
||||
- Contract: Unknown |
||||
- Function name: `_function_0x83f12fec` |
||||
- PC address: 158 |
||||
|
||||
### Description |
||||
|
||||
The arithmetic operation can result in integer overflow. |
||||
|
||||
## Integer Overflow |
||||
- SWC ID: 101 |
||||
- Type: Warning |
||||
- Contract: Unknown |
||||
- Function name: `_function_0x83f12fec` |
||||
- PC address: 278 |
||||
|
||||
### Description |
||||
|
||||
The arithmetic operation can result in integer overflow. |
||||
|
||||
## Integer Underflow |
||||
- SWC ID: 101 |
||||
- Type: Warning |
||||
- Contract: Unknown |
||||
- Function name: `_function_0x83f12fec` |
||||
- PC address: 378 |
||||
|
||||
### Description |
||||
|
||||
The substraction can result in an integer underflow. |
@ -1,30 +0,0 @@ |
||||
==== Integer Overflow ==== |
||||
SWC ID: 101 |
||||
Type: Warning |
||||
Contract: Unknown |
||||
Function name: _function_0x83f12fec |
||||
PC address: 158 |
||||
The arithmetic operation can result in integer overflow. |
||||
|
||||
-------------------- |
||||
|
||||
==== Integer Overflow ==== |
||||
SWC ID: 101 |
||||
Type: Warning |
||||
Contract: Unknown |
||||
Function name: _function_0x83f12fec |
||||
PC address: 278 |
||||
The arithmetic operation can result in integer overflow. |
||||
|
||||
-------------------- |
||||
|
||||
==== Integer Underflow ==== |
||||
SWC ID: 101 |
||||
Type: Warning |
||||
Contract: Unknown |
||||
Function name: _function_0x83f12fec |
||||
PC address: 378 |
||||
The substraction can result in an integer underflow. |
||||
|
||||
-------------------- |
||||
|
File diff suppressed because one or more lines are too long
@ -1,5 +1,32 @@ |
||||
{ |
||||
"error": null, |
||||
"issues": [], |
||||
"issues": [ |
||||
{ |
||||
"address": 722, |
||||
"contract": "Unknown", |
||||
"debug": "<DEBUG-DATA>", |
||||
"description": "Anyone can withdraw ETH from the contract account.\nArbitrary senders other than the contract creator can withdraw ETH from the contract account without previously having sent an equivalent amount of ETH to it. This is likely to be a vulnerability.", |
||||
"function": "withdrawfunds()", |
||||
"max_gas_used": 1749, |
||||
"min_gas_used": 1138, |
||||
"severity": "High", |
||||
"sourceMap": null, |
||||
"swc-id": "105", |
||||
"title": "Unprotected Ether Withdrawal" |
||||
}, |
||||
{ |
||||
"address": 883, |
||||
"contract": "Unknown", |
||||
"debug": "<DEBUG-DATA>", |
||||
"description": "The binary addition can overflow.\nThe operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion.", |
||||
"function": "invest()", |
||||
"max_gas_used": 26883, |
||||
"min_gas_used": 6598, |
||||
"severity": "High", |
||||
"sourceMap": null, |
||||
"swc-id": "101", |
||||
"title": "Integer Overflow" |
||||
} |
||||
], |
||||
"success": true |
||||
} |
@ -1,9 +1,46 @@ |
||||
[ |
||||
{ |
||||
"issues": [], |
||||
"issues": [ |
||||
{ |
||||
"description": { |
||||
"head": "Anyone can withdraw ETH from the contract account.", |
||||
"tail": "Arbitrary senders other than the contract creator can withdraw ETH from the contract account without previously having sent an equivalent amount of ETH to it. This is likely to be a vulnerability." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "722:1:0" |
||||
} |
||||
], |
||||
"severity": "High", |
||||
"swcID": "SWC-105", |
||||
"swcTitle": "Unprotected Ether Withdrawal" |
||||
}, |
||||
{ |
||||
"description": { |
||||
"head": "The binary addition can overflow.", |
||||
"tail": "The operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "883:1:0" |
||||
} |
||||
], |
||||
"severity": "High", |
||||
"swcID": "SWC-101", |
||||
"swcTitle": "Integer Overflow and Underflow" |
||||
} |
||||
], |
||||
"meta": {}, |
||||
"sourceFormat": "evm-byzantium-bytecode", |
||||
"sourceList": [], |
||||
"sourceList": [ |
||||
"0x3746c7c2ae7b0d4c3f8b1905df9a7ea169b9f93bec68a10a00b4c9d27a18c6fb" |
||||
], |
||||
"sourceType": "raw-bytecode" |
||||
} |
||||
] |
@ -1,3 +1,27 @@ |
||||
# Analysis results for None |
||||
# Analysis results for test-filename.sol |
||||
|
||||
The analysis was completed successfully. No issues were detected. |
||||
## Unprotected Ether Withdrawal |
||||
- SWC ID: 105 |
||||
- Severity: High |
||||
- Contract: Unknown |
||||
- Function name: `withdrawfunds()` |
||||
- PC address: 722 |
||||
- Estimated Gas Usage: 1138 - 1749 |
||||
|
||||
### Description |
||||
|
||||
Anyone can withdraw ETH from the contract account. |
||||
Arbitrary senders other than the contract creator can withdraw ETH from the contract account without previously having sent an equivalent amount of ETH to it. This is likely to be a vulnerability. |
||||
|
||||
## Integer Overflow |
||||
- SWC ID: 101 |
||||
- Severity: High |
||||
- Contract: Unknown |
||||
- Function name: `invest()` |
||||
- PC address: 883 |
||||
- Estimated Gas Usage: 6598 - 26883 |
||||
|
||||
### Description |
||||
|
||||
The binary addition can overflow. |
||||
The operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion. |
||||
|
@ -1 +1,22 @@ |
||||
The analysis was completed successfully. No issues were detected. |
||||
==== Unprotected Ether Withdrawal ==== |
||||
SWC ID: 105 |
||||
Severity: High |
||||
Contract: Unknown |
||||
Function name: withdrawfunds() |
||||
PC address: 722 |
||||
Estimated Gas Usage: 1138 - 1749 |
||||
Anyone can withdraw ETH from the contract account. |
||||
Arbitrary senders other than the contract creator can withdraw ETH from the contract account without previously having sent an equivalent amount of ETH to it. This is likely to be a vulnerability. |
||||
-------------------- |
||||
|
||||
==== Integer Overflow ==== |
||||
SWC ID: 101 |
||||
Severity: High |
||||
Contract: Unknown |
||||
Function name: invest() |
||||
PC address: 883 |
||||
Estimated Gas Usage: 6598 - 26883 |
||||
The binary addition can overflow. |
||||
The operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion. |
||||
-------------------- |
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,166 +0,0 @@ |
||||
{ |
||||
"success": true, |
||||
"error": null, |
||||
"issues": [ |
||||
{ |
||||
"title": "Ether send", |
||||
"description": "In the function `_function_0x4229616d` a non-zero amount of Ether is sent to an address taken from storage slot 5.\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\n\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.", |
||||
"function": "_function_0x4229616d", |
||||
"type": "Warning", |
||||
"address": 1599, |
||||
"debug": "<DEBUG-DATA>" |
||||
}, |
||||
{ |
||||
"title": "Ether send", |
||||
"description": "In the function `_function_0xb4022950` a non-zero amount of Ether is sent to an address taken from storage slot 5.\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\n\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.", |
||||
"function": "_function_0xb4022950", |
||||
"type": "Warning", |
||||
"address": 1940, |
||||
"debug": "<DEBUG-DATA>" |
||||
}, |
||||
{ |
||||
"title": "Ether send", |
||||
"description": "In the function `_function_0xb4022950` a non-zero amount of Ether is sent to an address taken from storage slot 5.\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\n\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.", |
||||
"function": "_function_0xb4022950", |
||||
"type": "Warning", |
||||
"address": 2582, |
||||
"debug": "<DEBUG-DATA>" |
||||
}, |
||||
{ |
||||
"title": "Exception state", |
||||
"description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", |
||||
"function": "_function_0x57d4021b", |
||||
"type": "Informational", |
||||
"address": 1653, |
||||
"debug": "<DEBUG-DATA>" |
||||
}, |
||||
{ |
||||
"title": "Exception state", |
||||
"description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", |
||||
"function": "_function_0x9dbc4f9b", |
||||
"type": "Informational", |
||||
"address": 2085, |
||||
"debug": "<DEBUG-DATA>" |
||||
}, |
||||
{ |
||||
"title": "Invariant branch condition", |
||||
"description": "Found a conditional jump which always follows the same branch", |
||||
"function": "fallback", |
||||
"type": "Informational", |
||||
"address": 3111, |
||||
"debug": "<DEBUG-DATA>" |
||||
}, |
||||
{ |
||||
"title": "Invariant branch condition", |
||||
"description": "Found a conditional jump which always follows the same branch", |
||||
"function": "fallback", |
||||
"type": "Informational", |
||||
"address": 3140, |
||||
"debug": "<DEBUG-DATA>" |
||||
}, |
||||
{ |
||||
"title": "Invariant branch condition", |
||||
"description": "Found a conditional jump which always follows the same branch", |
||||
"function": "fallback", |
||||
"type": "Informational", |
||||
"address": 2950, |
||||
"debug": "<DEBUG-DATA>" |
||||
}, |
||||
{ |
||||
"title": "Invariant branch condition", |
||||
"description": "Found a conditional jump which always follows the same branch", |
||||
"function": "fallback", |
||||
"type": "Informational", |
||||
"address": 1268, |
||||
"debug": "<DEBUG-DATA>" |
||||
}, |
||||
{ |
||||
"title": "Invariant branch condition", |
||||
"description": "Found a conditional jump which always follows the same branch", |
||||
"function": "_function_0x09dfdc71", |
||||
"type": "Informational", |
||||
"address": 310, |
||||
"debug": "<DEBUG-DATA>" |
||||
}, |
||||
{ |
||||
"title": "Invariant branch condition", |
||||
"description": "Found a conditional jump which always follows the same branch", |
||||
"function": "_function_0x09dfdc71", |
||||
"type": "Informational", |
||||
"address": 1316, |
||||
"debug": "<DEBUG-DATA>" |
||||
}, |
||||
{ |
||||
"title": "Invariant branch condition", |
||||
"description": "Found a conditional jump which always follows the same branch", |
||||
"function": "_function_0x253459e3", |
||||
"type": "Informational", |
||||
"address": 1375, |
||||
"debug": "<DEBUG-DATA>" |
||||
}, |
||||
{ |
||||
"title": "Invariant branch condition", |
||||
"description": "Found a conditional jump which always follows the same branch", |
||||
"function": "_function_0x4229616d", |
||||
"type": "Informational", |
||||
"address": 1511, |
||||
"debug": "<DEBUG-DATA>" |
||||
}, |
||||
{ |
||||
"title": "Invariant branch condition", |
||||
"description": "Found a conditional jump which always follows the same branch", |
||||
"function": "_function_0x57d4021b", |
||||
"type": "Informational", |
||||
"address": 1679, |
||||
"debug": "<DEBUG-DATA>" |
||||
}, |
||||
{ |
||||
"title": "Invariant branch condition", |
||||
"description": "Found a conditional jump which always follows the same branch", |
||||
"function": "_function_0x6fbaaa1e", |
||||
"type": "Informational", |
||||
"address": 618, |
||||
"debug": "<DEBUG-DATA>" |
||||
}, |
||||
{ |
||||
"title": "Invariant branch condition", |
||||
"description": "Found a conditional jump which always follows the same branch", |
||||
"function": "_function_0x8a5fb3ca", |
||||
"type": "Informational", |
||||
"address": 805, |
||||
"debug": "<DEBUG-DATA>" |
||||
}, |
||||
{ |
||||
"title": "Invariant branch condition", |
||||
"description": "Found a conditional jump which always follows the same branch", |
||||
"function": "_function_0x9dbc4f9b", |
||||
"type": "Informational", |
||||
"address": 2187, |
||||
"debug": "<DEBUG-DATA>" |
||||
}, |
||||
{ |
||||
"title": "Unchecked CALL return value", |
||||
"description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", |
||||
"function": "_function_0x4229616d", |
||||
"type": "Informational", |
||||
"address": 1599, |
||||
"debug": "<DEBUG-DATA>" |
||||
}, |
||||
{ |
||||
"title": "Unchecked CALL return value", |
||||
"description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", |
||||
"function": "_function_0xb4022950", |
||||
"type": "Informational", |
||||
"address": 1940, |
||||
"debug": "<DEBUG-DATA>" |
||||
}, |
||||
{ |
||||
"title": "Unchecked CALL return value", |
||||
"description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", |
||||
"function": "_function_0xb4022950", |
||||
"type": "Informational", |
||||
"address": 2582, |
||||
"debug": "<DEBUG-DATA>" |
||||
} |
||||
] |
||||
} |
@ -1,238 +0,0 @@ |
||||
# Analysis results for test-filename.sol |
||||
|
||||
## Ether send |
||||
|
||||
- Type: Warning |
||||
- Contract: Unknown |
||||
- Function name: `_function_0x4229616d` |
||||
- PC address: 1599 |
||||
|
||||
### Description |
||||
|
||||
In the function `_function_0x4229616d` a non-zero amount of Ether is sent to an address taken from storage slot 5. |
||||
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. |
||||
|
||||
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. |
||||
There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. |
||||
|
||||
## Ether send |
||||
|
||||
- Type: Warning |
||||
- Contract: Unknown |
||||
- Function name: `_function_0xb4022950` |
||||
- PC address: 1940 |
||||
|
||||
### Description |
||||
|
||||
In the function `_function_0xb4022950` a non-zero amount of Ether is sent to an address taken from storage slot 5. |
||||
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. |
||||
|
||||
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. |
||||
There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. |
||||
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. |
||||
There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. |
||||
|
||||
## Ether send |
||||
|
||||
- Type: Warning |
||||
- Contract: Unknown |
||||
- Function name: `_function_0xb4022950` |
||||
- PC address: 2582 |
||||
|
||||
### Description |
||||
|
||||
In the function `_function_0xb4022950` a non-zero amount of Ether is sent to an address taken from storage slot 5. |
||||
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. |
||||
|
||||
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. |
||||
There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. |
||||
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. |
||||
There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. |
||||
There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. |
||||
|
||||
## Exception state |
||||
|
||||
- Type: Informational |
||||
- Contract: Unknown |
||||
- Function name: `_function_0x57d4021b` |
||||
- PC address: 1653 |
||||
|
||||
### Description |
||||
|
||||
A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. |
||||
|
||||
## Exception state |
||||
|
||||
- Type: Informational |
||||
- Contract: Unknown |
||||
- Function name: `_function_0x9dbc4f9b` |
||||
- PC address: 2085 |
||||
|
||||
### Description |
||||
|
||||
A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. |
||||
|
||||
## Tautology |
||||
|
||||
- Type: Informational |
||||
- Contract: Unknown |
||||
- Function name: `fallback` |
||||
- PC address: 3111 |
||||
|
||||
### Description |
||||
|
||||
Found a conditional jump which always follows the same branch, value: True |
||||
|
||||
## Tautology |
||||
|
||||
- Type: Informational |
||||
- Contract: Unknown |
||||
- Function name: `fallback` |
||||
- PC address: 3140 |
||||
|
||||
### Description |
||||
|
||||
Found a conditional jump which always follows the same branch, value: True |
||||
|
||||
## Tautology |
||||
|
||||
- Type: Informational |
||||
- Contract: Unknown |
||||
- Function name: `fallback` |
||||
- PC address: 2950 |
||||
|
||||
### Description |
||||
|
||||
Found a conditional jump which always follows the same branch, value: True |
||||
|
||||
## Tautology |
||||
|
||||
- Type: Informational |
||||
- Contract: Unknown |
||||
- Function name: `fallback` |
||||
- PC address: 1268 |
||||
|
||||
### Description |
||||
|
||||
Found a conditional jump which always follows the same branch, value: True |
||||
|
||||
## Tautology |
||||
|
||||
- Type: Informational |
||||
- Contract: Unknown |
||||
- Function name: `_function_0x09dfdc71` |
||||
- PC address: 310 |
||||
|
||||
### Description |
||||
|
||||
Found a conditional jump which always follows the same branch, value: False |
||||
|
||||
## Tautology |
||||
|
||||
- Type: Informational |
||||
- Contract: Unknown |
||||
- Function name: `_function_0x09dfdc71` |
||||
- PC address: 1316 |
||||
|
||||
### Description |
||||
|
||||
Found a conditional jump which always follows the same branch, value: True |
||||
|
||||
## Tautology |
||||
|
||||
- Type: Informational |
||||
- Contract: Unknown |
||||
- Function name: `_function_0x253459e3` |
||||
- PC address: 1375 |
||||
|
||||
### Description |
||||
|
||||
Found a conditional jump which always follows the same branch, value: True |
||||
|
||||
## Tautology |
||||
|
||||
- Type: Informational |
||||
- Contract: Unknown |
||||
- Function name: `_function_0x4229616d` |
||||
- PC address: 1511 |
||||
|
||||
### Description |
||||
|
||||
Found a conditional jump which always follows the same branch, value: True |
||||
|
||||
## Tautology |
||||
|
||||
- Type: Informational |
||||
- Contract: Unknown |
||||
- Function name: `_function_0x57d4021b` |
||||
- PC address: 1679 |
||||
|
||||
### Description |
||||
|
||||
Found a conditional jump which always follows the same branch, value: True |
||||
|
||||
## Tautology |
||||
|
||||
- Type: Informational |
||||
- Contract: Unknown |
||||
- Function name: `_function_0x6fbaaa1e` |
||||
- PC address: 618 |
||||
|
||||
### Description |
||||
|
||||
Found a conditional jump which always follows the same branch, value: False |
||||
|
||||
## Tautology |
||||
|
||||
- Type: Informational |
||||
- Contract: Unknown |
||||
- Function name: `_function_0x8a5fb3ca` |
||||
- PC address: 805 |
||||
|
||||
### Description |
||||
|
||||
Found a conditional jump which always follows the same branch, value: False |
||||
|
||||
## Tautology |
||||
|
||||
- Type: Informational |
||||
- Contract: Unknown |
||||
- Function name: `_function_0x9dbc4f9b` |
||||
- PC address: 2187 |
||||
|
||||
### Description |
||||
|
||||
Found a conditional jump which always follows the same branch, value: True |
||||
|
||||
## Unchecked CALL return value |
||||
|
||||
- Type: Informational |
||||
- Contract: Unknown |
||||
- Function name: `_function_0x4229616d` |
||||
- PC address: 1599 |
||||
|
||||
### Description |
||||
|
||||
The return value of an external call is not checked. Note that execution continue even if the called contract throws. |
||||
|
||||
## Unchecked CALL return value |
||||
|
||||
- Type: Informational |
||||
- Contract: Unknown |
||||
- Function name: `_function_0xb4022950` |
||||
- PC address: 1940 |
||||
|
||||
### Description |
||||
|
||||
The return value of an external call is not checked. Note that execution continue even if the called contract throws. |
||||
|
||||
## Unchecked CALL return value |
||||
|
||||
- Type: Informational |
||||
- Contract: Unknown |
||||
- Function name: `_function_0xb4022950` |
||||
- PC address: 2582 |
||||
|
||||
### Description |
||||
|
||||
The return value of an external call is not checked. Note that execution continue even if the called contract throws. |
@ -1,177 +0,0 @@ |
||||
==== Ether send ==== |
||||
Type: Warning |
||||
Contract: Unknown |
||||
Function name: _function_0x4229616d |
||||
PC address: 1599 |
||||
In the function `_function_0x4229616d` a non-zero amount of Ether is sent to an address taken from storage slot 5. |
||||
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. |
||||
|
||||
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. |
||||
There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. |
||||
-------------------- |
||||
|
||||
==== Ether send ==== |
||||
Type: Warning |
||||
Contract: Unknown |
||||
Function name: _function_0xb4022950 |
||||
PC address: 1940 |
||||
In the function `_function_0xb4022950` a non-zero amount of Ether is sent to an address taken from storage slot 5. |
||||
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. |
||||
|
||||
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. |
||||
There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. |
||||
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. |
||||
There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. |
||||
-------------------- |
||||
|
||||
==== Ether send ==== |
||||
Type: Warning |
||||
Contract: Unknown |
||||
Function name: _function_0xb4022950 |
||||
PC address: 2582 |
||||
In the function `_function_0xb4022950` a non-zero amount of Ether is sent to an address taken from storage slot 5. |
||||
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. |
||||
|
||||
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. |
||||
There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. |
||||
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`. |
||||
There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. |
||||
There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`. |
||||
-------------------- |
||||
|
||||
==== Exception state ==== |
||||
Type: Informational |
||||
Contract: Unknown |
||||
Function name: _function_0x57d4021b |
||||
PC address: 1653 |
||||
A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. |
||||
-------------------- |
||||
|
||||
==== Exception state ==== |
||||
Type: Informational |
||||
Contract: Unknown |
||||
Function name: _function_0x9dbc4f9b |
||||
PC address: 2085 |
||||
A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. |
||||
-------------------- |
||||
|
||||
==== Tautology ==== |
||||
Type: Informational |
||||
Contract: Unknown |
||||
Function name: fallback |
||||
PC address: 3111 |
||||
Found a conditional jump which always follows the same branch, value: True |
||||
-------------------- |
||||
|
||||
==== Tautology ==== |
||||
Type: Informational |
||||
Contract: Unknown |
||||
Function name: fallback |
||||
PC address: 3140 |
||||
Found a conditional jump which always follows the same branch, value: True |
||||
-------------------- |
||||
|
||||
==== Tautology ==== |
||||
Type: Informational |
||||
Contract: Unknown |
||||
Function name: fallback |
||||
PC address: 2950 |
||||
Found a conditional jump which always follows the same branch, value: True |
||||
-------------------- |
||||
|
||||
==== Tautology ==== |
||||
Type: Informational |
||||
Contract: Unknown |
||||
Function name: fallback |
||||
PC address: 1268 |
||||
Found a conditional jump which always follows the same branch, value: True |
||||
-------------------- |
||||
|
||||
==== Tautology ==== |
||||
Type: Informational |
||||
Contract: Unknown |
||||
Function name: _function_0x09dfdc71 |
||||
PC address: 310 |
||||
Found a conditional jump which always follows the same branch, value: False |
||||
-------------------- |
||||
|
||||
==== Tautology ==== |
||||
Type: Informational |
||||
Contract: Unknown |
||||
Function name: _function_0x09dfdc71 |
||||
PC address: 1316 |
||||
Found a conditional jump which always follows the same branch, value: True |
||||
-------------------- |
||||
|
||||
==== Tautology ==== |
||||
Type: Informational |
||||
Contract: Unknown |
||||
Function name: _function_0x253459e3 |
||||
PC address: 1375 |
||||
Found a conditional jump which always follows the same branch, value: True |
||||
-------------------- |
||||
|
||||
==== Tautology ==== |
||||
Type: Informational |
||||
Contract: Unknown |
||||
Function name: _function_0x4229616d |
||||
PC address: 1511 |
||||
Found a conditional jump which always follows the same branch, value: True |
||||
-------------------- |
||||
|
||||
==== Tautology ==== |
||||
Type: Informational |
||||
Contract: Unknown |
||||
Function name: _function_0x57d4021b |
||||
PC address: 1679 |
||||
Found a conditional jump which always follows the same branch, value: True |
||||
-------------------- |
||||
|
||||
==== Tautology ==== |
||||
Type: Informational |
||||
Contract: Unknown |
||||
Function name: _function_0x6fbaaa1e |
||||
PC address: 618 |
||||
Found a conditional jump which always follows the same branch, value: False |
||||
-------------------- |
||||
|
||||
==== Tautology ==== |
||||
Type: Informational |
||||
Contract: Unknown |
||||
Function name: _function_0x8a5fb3ca |
||||
PC address: 805 |
||||
Found a conditional jump which always follows the same branch, value: False |
||||
-------------------- |
||||
|
||||
==== Tautology ==== |
||||
Type: Informational |
||||
Contract: Unknown |
||||
Function name: _function_0x9dbc4f9b |
||||
PC address: 2187 |
||||
Found a conditional jump which always follows the same branch, value: True |
||||
-------------------- |
||||
|
||||
==== Unchecked CALL return value ==== |
||||
Type: Informational |
||||
Contract: Unknown |
||||
Function name: _function_0x4229616d |
||||
PC address: 1599 |
||||
The return value of an external call is not checked. Note that execution continue even if the called contract throws. |
||||
-------------------- |
||||
|
||||
==== Unchecked CALL return value ==== |
||||
Type: Informational |
||||
Contract: Unknown |
||||
Function name: _function_0xb4022950 |
||||
PC address: 1940 |
||||
The return value of an external call is not checked. Note that execution continue even if the called contract throws. |
||||
-------------------- |
||||
|
||||
==== Unchecked CALL return value ==== |
||||
Type: Informational |
||||
Contract: Unknown |
||||
Function name: _function_0xb4022950 |
||||
PC address: 2582 |
||||
The return value of an external call is not checked. Note that execution continue even if the called contract throws. |
||||
-------------------- |
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue