Skip to content

Commit

Permalink
Handles issue with low constructor loop bound and new way to deal wit…
Browse files Browse the repository at this point in the history
…h solver-timeout (#1668)

* Add exit code

* Update docs with tutorial

* Fix docs

* Reformat

* Fix the issue with loop-bound and solver-timeout

* Add test changes

* Restrict hexbytes
  • Loading branch information
norhh authored Aug 20, 2022
1 parent 614ea8c commit eb2c11e
Show file tree
Hide file tree
Showing 12 changed files with 45 additions and 20 deletions.
7 changes: 7 additions & 0 deletions mythril/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ class UnsatError(MythrilBaseException):
pass


class SolverTimeOutException(UnsatError):
"""A Mythril exception denoting the unsatisfiability of a series of
constraints."""

pass


class NoContractFoundError(MythrilBaseException):
"""A Mythril exception denoting that a given contract file was not
found."""
Expand Down
9 changes: 5 additions & 4 deletions mythril/interfaces/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,9 +511,10 @@ def add_analysis_args(options):
help="Don't attempt to retrieve contract code, variables and balances from the blockchain",
)
options.add_argument(
"--sparse-pruning",
action="store_true",
help="Checks for reachability after the end of tx. Recommended for short execution timeouts < 1 min",
"--pruning-factor",
type=float,
default=1,
help="Checks for reachability at the rate of <pruning-factor>. Where 1 means checks every execution",
)
options.add_argument(
"--unconstrained-storage",
Expand Down Expand Up @@ -752,7 +753,7 @@ def execute_command(

elif args.command == SAFE_FUNCTIONS_COMMAND:
args.unconstrained_storage = True
args.sparse_pruning = False
args.pruning_factor = 1
args.disable_dependency_pruning = True
args.no_onchain_data = True
function_analyzer = MythrilAnalyzer(
Expand Down
14 changes: 10 additions & 4 deletions mythril/laser/ethereum/state/constraints.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""This module contains the class used to represent state-change constraints in
the call graph."""
from mythril.exceptions import UnsatError
from mythril.exceptions import UnsatError, SolverTimeOutException
from mythril.laser.smt import symbol_factory, simplify, Bool
from mythril.support.model import get_model
from mythril.laser.ethereum.function_managers import keccak_function_manager
Expand All @@ -25,13 +25,19 @@ def __init__(self, constraint_list: Optional[List[Bool]] = None) -> None:
constraint_list = self._get_smt_bool_list(constraint_list)
super(Constraints, self).__init__(constraint_list)

@property
def is_possible(self) -> bool:
def is_possible(self, solver_timeout=None) -> bool:
"""
:param solver_timeout: The default timeout uses analysis timeout from args.solver_timeout
:return: True/False based on the existence of solution of constraints
"""
try:
get_model(self)
get_model(self, solver_timeout=solver_timeout)
except SolverTimeOutException:
# If it uses the long analysis solver timeout
if solver_timeout is None:
return False
# If it uses a short custom solver timeout
return True
except UnsatError:
return False
return True
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def get_strategic_global_state(self) -> GlobalState:
# TODO: There's probably a nicer way to do this
if isinstance(
state.current_transaction, ContractCreationTransaction
) and count < max(20, self.bound):
) and count < max(128, self.bound):
return state

elif count > self.bound:
Expand Down
9 changes: 6 additions & 3 deletions mythril/laser/ethereum/svm.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from collections import defaultdict
from copy import copy
from datetime import datetime, timedelta
import random
from typing import Callable, Dict, DefaultDict, List, Tuple, Optional

from mythril.support.opcodes import OPCODES
Expand Down Expand Up @@ -218,7 +219,9 @@ def _execute_transactions(self, address):
old_states_count = len(self.open_states)
if self.use_reachability_check:
self.open_states = [
state for state in self.open_states if state.constraints.is_possible
state
for state in self.open_states
if state.constraints.is_possible()
]
prune_count = old_states_count - len(self.open_states)
if prune_count:
Expand Down Expand Up @@ -282,11 +285,11 @@ def exec(self, create=False, track_gas=False) -> Optional[List[GlobalState]]:
except NotImplementedError:
log.debug("Encountered unimplemented instruction")
continue
if args.sparse_pruning is False:
if len(new_states) > 1 and random.uniform(0, 1) < args.pruning_factor:
new_states = [
state
for state in new_states
if state.world_state.constraints.is_possible
if state.world_state.constraints.is_possible()
]

self.manage_cfg(op_code, new_states) # TODO: What about op_code is None?
Expand Down
2 changes: 1 addition & 1 deletion mythril/mythril/mythril_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def __init__(
if cmd_args.custom_modules_directory
else ""
)
args.sparse_pruning = cmd_args.sparse_pruning
args.pruning_factor = cmd_args.pruning_factor
args.solver_timeout = cmd_args.solver_timeout
args.parallel_solving = cmd_args.parallel_solving
args.unconstrained_storage = cmd_args.unconstrained_storage
Expand Down
13 changes: 10 additions & 3 deletions mythril/support/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,21 @@
from mythril.support.support_args import args
from mythril.laser.smt import Optimize
from mythril.laser.ethereum.time_handler import time_handler
from mythril.exceptions import UnsatError
from mythril.exceptions import UnsatError, SolverTimeOutException
import logging

log = logging.getLogger(__name__)
# LRU cache works great when used in powers of 2


@lru_cache(maxsize=2**23)
def get_model(constraints, minimize=(), maximize=(), enforce_execution_time=True):
def get_model(
constraints,
minimize=(),
maximize=(),
enforce_execution_time=True,
solver_timeout=None,
):
"""
Returns a model based on given constraints as a tuple
:param constraints: Tuple of constraints
Expand All @@ -23,7 +29,7 @@ def get_model(constraints, minimize=(), maximize=(), enforce_execution_time=True
:return:
"""
s = Optimize()
timeout = args.solver_timeout
timeout = solver_timeout or args.solver_timeout
if enforce_execution_time:
timeout = min(timeout, time_handler.time_remaining() - 500)
if timeout <= 0:
Expand Down Expand Up @@ -60,4 +66,5 @@ def get_model(constraints, minimize=(), maximize=(), enforce_execution_time=True
return s.model()
elif result == unknown:
log.debug("Timeout/Error encountered while solving expression using z3")
raise SolverTimeOutException
raise UnsatError
2 changes: 1 addition & 1 deletion mythril/support/support_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Args(object, metaclass=Singleton):

def __init__(self):
self.solver_timeout = 10000
self.sparse_pruning = True
self.pruning_factor = 1
self.unconstrained_storage = False
self.parallel_solving = False
self.call_depth_limit = 3
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ eth-keys<0.4.0,>=0.2.1
eth-rlp<0.3.0,>=0.1.0
eth-typing<3.0.0,>=2.1.0
eth-utils<2
hexbytes<0.3.0
jinja2>=2.9
MarkupSafe<2.1.0
mock
Expand Down
2 changes: 1 addition & 1 deletion tests/graph_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def test_generate_graph():
create_timeout=None,
disable_dependency_pruning=False,
custom_modules_directory=None,
sparse_pruning=True,
pruning_factor=0,
parallel_solving=True,
unconstrained_storage=True,
call_depth_limit=3,
Expand Down
2 changes: 1 addition & 1 deletion tests/mythril/mythril_analyzer_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def test_fire_lasers(mock_sym, mock_fire_lasers, mock_code_info):
create_timeout=None,
disable_dependency_pruning=False,
custom_modules_directory=None,
sparse_pruning=True,
pruning_factor=0,
parallel_solving=True,
unconstrained_storage=True,
call_depth_limit=3,
Expand Down
2 changes: 1 addition & 1 deletion tests/statespace_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def test_statespace_dump():
create_timeout=None,
disable_dependency_pruning=False,
custom_modules_directory=None,
sparse_pruning=True,
pruning_factor=0,
parallel_solving=True,
unconstrained_storage=True,
call_depth_limit=3,
Expand Down

0 comments on commit eb2c11e

Please sign in to comment.