mirror of https://github.com/ConsenSys/mythril
Add summaries and additional bugfixes (#1830)
* Add summaries and additional bugfixes * Update test w.r.t bugfixes * Update namespace * Disable sonarz3
parent
f61c2d4515
commit
205d50a50f
@ -0,0 +1 @@ |
||||
from .core import SymbolicSummaryPluginBuilder |
@ -0,0 +1,44 @@ |
||||
from mythril.laser.ethereum.state.annotation import StateAnnotation |
||||
from mythril.laser.ethereum.state.global_state import GlobalState |
||||
from mythril.laser.ethereum.state.environment import Environment |
||||
from mythril.laser.smt import Bool, BaseArray |
||||
from typing import List, Tuple |
||||
|
||||
from copy import deepcopy, copy |
||||
|
||||
|
||||
class SummaryTrackingAnnotation(StateAnnotation): |
||||
"""SummaryTrackingAnnotation |
||||
This annotation is used by the symbolic summary plugin to keep track of data related to a summary that |
||||
will be computed during the future exploration of the annotated world state. |
||||
""" |
||||
|
||||
def __init__( |
||||
self, |
||||
entry: GlobalState, |
||||
storage_pairs: List[Tuple[BaseArray, BaseArray]], |
||||
storage_constraints: List[Bool], |
||||
environment_pair: Tuple[Environment, Environment], |
||||
balance_pair: Tuple[BaseArray, BaseArray], |
||||
code: str, |
||||
): |
||||
self.entry = entry |
||||
self.trace = [] |
||||
self.storage_pairs = storage_pairs |
||||
self.storage_constraints = storage_constraints |
||||
self.environment_pair = environment_pair |
||||
self.balance_pair = balance_pair |
||||
self.code = code |
||||
|
||||
def __copy__(self): |
||||
|
||||
annotation = SummaryTrackingAnnotation( |
||||
entry=self.entry, |
||||
storage_pairs=deepcopy(self.storage_pairs), |
||||
storage_constraints=deepcopy(self.storage_constraints), |
||||
environment_pair=deepcopy(self.environment_pair), |
||||
balance_pair=deepcopy(self.balance_pair), |
||||
code=self.code, |
||||
) |
||||
annotation.trace = self.trace |
||||
return annotation |
@ -0,0 +1,439 @@ |
||||
from .summary import SymbolicSummary, substitute_exprs |
||||
from .annotations import SummaryTrackingAnnotation |
||||
from mythril.analysis.issue_annotation import IssueAnnotation |
||||
from mythril.analysis.potential_issues import check_potential_issues |
||||
from mythril.analysis import solver |
||||
from mythril.analysis.solver import get_transaction_sequence |
||||
from mythril.exceptions import UnsatError |
||||
|
||||
from mythril.laser.ethereum.svm import LaserEVM |
||||
from mythril.laser.plugin.builder import PluginBuilder |
||||
from mythril.laser.plugin.interface import LaserPlugin |
||||
from mythril.laser.plugin.signals import PluginSkipState |
||||
from mythril.laser.plugin.plugins.plugin_annotations import MutationAnnotation |
||||
from mythril.laser.ethereum.transaction.transaction_models import ( |
||||
ContractCreationTransaction, |
||||
BaseTransaction, |
||||
) |
||||
from mythril.support.support_utils import get_code_hash |
||||
from mythril.laser.ethereum.function_managers import ( |
||||
keccak_function_manager, |
||||
KeccakFunctionManager, |
||||
) |
||||
|
||||
from mythril.laser.ethereum.state.global_state import GlobalState |
||||
from mythril.laser.ethereum.state.constraints import Constraints |
||||
from mythril.laser.ethereum.state.environment import Environment |
||||
from mythril.laser.ethereum.state.calldata import SymbolicCalldata |
||||
from mythril.laser.ethereum.state.account import Account |
||||
from mythril.laser.smt import ( |
||||
K, |
||||
Array, |
||||
BaseArray, |
||||
Bool, |
||||
simplify, |
||||
Solver, |
||||
Not, |
||||
Or, |
||||
symbol_factory, |
||||
Expression, |
||||
) |
||||
from mythril.support.support_args import args |
||||
import z3 |
||||
from typing import Dict, Tuple, List, Optional, Set |
||||
from copy import copy, deepcopy |
||||
from mythril.support.model import get_model |
||||
|
||||
import logging |
||||
|
||||
log = logging.getLogger(__name__) |
||||
|
||||
|
||||
class SymbolicSummaryPluginBuilder(PluginBuilder): |
||||
name = "Symbolic Summaries" |
||||
|
||||
def __call__(self, *args, **kwargs): |
||||
return SymbolicSummaryPlugin() |
||||
|
||||
|
||||
class SymbolicSummaryPlugin(LaserPlugin): |
||||
def __init__(self): |
||||
self.summaries = [] |
||||
args.use_issue_annotations = True |
||||
self.issue_cache: Set[Tuple[str, str, int]] = set() |
||||
self.init_save_states = [] |
||||
self.save_init_balance = None |
||||
|
||||
def initialize(self, symbolic_vm: LaserEVM): |
||||
"""Initializes the symbolic summary generating plugin |
||||
|
||||
Introduces hooks for each instruction |
||||
:param symbolic_vm: the symbolic virtual machine to initialize this plugin for |
||||
""" |
||||
|
||||
""" |
||||
@symbolic_vm.laser_hook("start_execute_transactions"): |
||||
def start_exec_txs_hook(): |
||||
log.info(f"Started executing transactions") |
||||
|
||||
symbolic_vm.executed_transactions = True |
||||
""" |
||||
|
||||
@symbolic_vm.laser_hook("stop_sym_exec") |
||||
def stop_sym_exec_hook(): |
||||
# Print results |
||||
log.info(f"Generated {len(self.summaries)} summaries") |
||||
|
||||
@symbolic_vm.laser_hook("execute_state") |
||||
def execute_start_sym_trans_hook(global_state: GlobalState): |
||||
if global_state.mstate.pc == 0: |
||||
if len(global_state.world_state.transaction_sequence) == 2: |
||||
self.init_save_states.append(deepcopy(global_state)) |
||||
self._apply_summaries(laser_evm=symbolic_vm, global_state=global_state) |
||||
self.save_init_balance = deepcopy(global_state.world_state.balances) |
||||
self._summary_entry(global_state) |
||||
|
||||
@symbolic_vm.post_hook("JUMPI") |
||||
@symbolic_vm.post_hook("JUMP") |
||||
def call_mutator_hook(global_state: GlobalState): |
||||
for annotation in global_state.get_annotations(SummaryTrackingAnnotation): |
||||
annotation.trace.append(global_state.instruction["address"]) |
||||
|
||||
@symbolic_vm.laser_hook("transaction_end") |
||||
def transaction_end_hook( |
||||
global_state: GlobalState, |
||||
transaction: BaseTransaction, |
||||
return_global_state: Optional[GlobalState], |
||||
revert: bool = True, |
||||
): |
||||
if return_global_state is not None: |
||||
return |
||||
if ( |
||||
not isinstance(transaction, ContractCreationTransaction) |
||||
or transaction.return_data |
||||
) and (not revert or list(global_state.get_annotations(IssueAnnotation))): |
||||
check_potential_issues(global_state) |
||||
self._summary_exit(global_state, transaction, revert) |
||||
|
||||
def _summary_entry(self, global_state: GlobalState): |
||||
"""Handles logic for when the analysis reaches an entry point of a to-be recorded symbolic summary |
||||
|
||||
:param global_state: The state at the entry of the symbolic summary |
||||
""" |
||||
summary_constraints = [] |
||||
storage_pairs = [] |
||||
# Rewrite storage |
||||
for index, account in global_state.world_state.accounts.items(): |
||||
actual_storage = deepcopy(account.storage._standard_storage) |
||||
symbolic_storage = Array(f"{index}_symbolic_storage", 256, 256) |
||||
account.storage._standard_storage = symbolic_storage |
||||
storage_pairs.append((actual_storage, symbolic_storage)) |
||||
account.storage.keys_get = set() |
||||
account.storage.keys_set = set() |
||||
|
||||
# Rewrite balances |
||||
previous_balances, summary_balances = ( |
||||
global_state.world_state.balances, |
||||
Array("summary_balance", 256, 256), |
||||
) |
||||
global_state.world_state.balances = summary_balances |
||||
balances_pair = (previous_balances, summary_balances) |
||||
|
||||
# Rewrite environment |
||||
previous_environment = global_state.environment |
||||
summary_environment = self._create_summary_environment(previous_environment) |
||||
environment_pair = (previous_environment, summary_environment) |
||||
|
||||
# Construct the summary tracking annotation |
||||
summary_annotation = SummaryTrackingAnnotation( |
||||
global_state, |
||||
storage_pairs, |
||||
summary_constraints, |
||||
environment_pair, |
||||
balances_pair, |
||||
global_state.environment.code.bytecode, |
||||
) |
||||
|
||||
# Introduce annotation and constraints to the global state |
||||
for constraint in summary_constraints: |
||||
global_state.world_state.constraints.append(constraint) |
||||
global_state.annotate(summary_annotation) |
||||
|
||||
def _create_summary_environment(self, base_environment: Environment) -> Environment: |
||||
return Environment( |
||||
# No need to rewrite, accounts are handled in other procedure |
||||
active_account=base_environment.active_account, |
||||
# Need to rewrite, different symbol for each transaction |
||||
sender=symbol_factory.BitVecSym("summary_sender", 256), |
||||
# Need to rewrite, different symbol for each transaction |
||||
origin=symbol_factory.BitVecSym("summary_origin", 256), |
||||
# Need to rewrite, different symbol for each transaction |
||||
calldata=SymbolicCalldata("summary"), |
||||
# Need to rewrite, different symbol for each transaction |
||||
gasprice=symbol_factory.BitVecSym("summary_origin", 256), |
||||
# Need to rewrite, different symbol for each transaction |
||||
callvalue=symbol_factory.BitVecSym("summary_callvalue", 256), |
||||
# No need to rewrite, this can be inherited from the original environment |
||||
static=base_environment.static, |
||||
# No need to rewrite, this can be inherited from the original environment |
||||
code=base_environment.code, |
||||
basefee=base_environment.basefee, |
||||
) |
||||
|
||||
@classmethod |
||||
def _restore_environment( |
||||
cls, |
||||
summary_tracking_annotation: SummaryTrackingAnnotation, |
||||
global_state: GlobalState, |
||||
): |
||||
global_state.environment = summary_tracking_annotation.environment_pair[0] |
||||
original, summary = summary_tracking_annotation.environment_pair |
||||
# Rewrite sender |
||||
cls._rewrite(global_state, summary.sender, original.sender) |
||||
# Rewrite origin |
||||
cls._rewrite(global_state, summary.origin, original.origin) |
||||
# Rewrite calldata |
||||
cls._rewrite( |
||||
global_state, summary.calldata.calldatasize, original.calldata.calldatasize |
||||
) |
||||
cls._rewrite( |
||||
global_state, summary.calldata._calldata, original.calldata._calldata |
||||
) |
||||
# Rewrite gasprice |
||||
cls._rewrite(global_state, summary.gasprice, original.gasprice) |
||||
# Rewrite callvalue |
||||
cls._rewrite(global_state, summary.callvalue, original.callvalue) |
||||
|
||||
def check_for_issues(self, global_state): |
||||
for summary in self.summaries: |
||||
for issue in summary.issues: |
||||
self._check_issue(global_state, issue) |
||||
|
||||
def storage_dependent(self, summary, global_state: GlobalState) -> bool: |
||||
""" |
||||
Checks if storage of summary depends on global state's previous storage stores |
||||
""" |
||||
total_key_set = set() |
||||
for index in global_state.accounts: |
||||
total_key_set = total_key_set.union( |
||||
global_state.accounts[index].storage.keys_set |
||||
) |
||||
if len(global_state.world_state.transaction_sequence) <= 3: |
||||
return True |
||||
for index, storage_get in summary.get_map.items(): |
||||
for key in storage_get: |
||||
if key.symbolic is False: |
||||
if key in global_state.accounts[index].storage.keys_set: |
||||
return True |
||||
else: |
||||
for state_key in global_state.accounts[index].storage.keys_set: |
||||
s = Solver() |
||||
s.set_timeout(3000) |
||||
s.add(state_key == key) |
||||
s.add(keccak_function_manager.create_conditions()) |
||||
sat = s.check() == z3.sat |
||||
if sat: |
||||
return True |
||||
|
||||
return False |
||||
|
||||
def _apply_summaries(self, laser_evm: LaserEVM, global_state: GlobalState): |
||||
""" |
||||
Applies summaries on the EVM |
||||
""" |
||||
|
||||
pc = global_state.instruction["address"] |
||||
self.check_for_issues(global_state) |
||||
summaries = [ |
||||
summary |
||||
for summary in self.summaries |
||||
if summary.entry == pc |
||||
and summary.code == global_state.environment.code.bytecode |
||||
and not summary.revert |
||||
and summary.storage_effect |
||||
] |
||||
|
||||
for summary in summaries: |
||||
resulting_state = summary.apply_summary(global_state) |
||||
if resulting_state: |
||||
laser_evm._add_world_state(resulting_state[0]) |
||||
|
||||
if summaries: |
||||
raise PluginSkipState |
||||
|
||||
def issue_in_cache( |
||||
self, global_state: GlobalState, issue_annotation: IssueAnnotation |
||||
) -> bool: |
||||
address = ( |
||||
issue_annotation.issue.source_location or issue_annotation.issue.address |
||||
) |
||||
return ( |
||||
issue_annotation.detector.swc_id, |
||||
address, |
||||
get_code_hash(global_state.environment.code.bytecode), |
||||
) in self.issue_cache |
||||
|
||||
def _check_issue( |
||||
self, global_state: GlobalState, issue_annotation: IssueAnnotation |
||||
): |
||||
if self.issue_in_cache(global_state, issue_annotation): |
||||
return |
||||
|
||||
success = 0 |
||||
tx_seq = [] |
||||
for constraint in issue_annotation.conditions: |
||||
condition = self._translate_condition( |
||||
global_state, |
||||
[constraint, deepcopy(keccak_function_manager.create_conditions())], |
||||
) |
||||
condition = [ |
||||
expr |
||||
for expr in global_state.world_state.constraints.as_list + condition |
||||
] |
||||
try: |
||||
tx_seq = get_transaction_sequence(global_state, Constraints(condition)) |
||||
success += 1 |
||||
except UnsatError: |
||||
break |
||||
|
||||
if success == len(issue_annotation.conditions): |
||||
log.info("Found an issue") |
||||
new_issue = copy(issue_annotation.issue) |
||||
new_issue.transaction_sequence = tx_seq |
||||
issue_annotation.detector.issues += [new_issue] |
||||
addresss = ( |
||||
issue_annotation.issue.source_location or issue_annotation.issue.address |
||||
) |
||||
self.issue_cache.add( |
||||
( |
||||
issue_annotation.detector.swc_id, |
||||
addresss, |
||||
get_code_hash(global_state.environment.code.bytecode), |
||||
) |
||||
) |
||||
|
||||
def _translate_condition(self, global_state: GlobalState, condition: List[Bool]): |
||||
condition = deepcopy(condition) |
||||
for account_id, account in global_state.world_state.accounts.items(): |
||||
for expression in condition: |
||||
substitute_exprs(expression, account_id, account, global_state) |
||||
|
||||
return condition |
||||
|
||||
def _summary_exit( |
||||
self, global_state: GlobalState, transaction: BaseTransaction, revert: bool |
||||
): |
||||
"""Handles logic for when the analysis reaches the summary exit |
||||
|
||||
This function populates self.summaries with the discovered symbolic summaries |
||||
:param global_state: The state at the exit of the discovered symbolic summary |
||||
""" |
||||
summary_annotation = self._get_and_remove_summary_tracking_annotation( |
||||
global_state |
||||
) |
||||
if not summary_annotation: |
||||
log.error("Missing Annotation") |
||||
return |
||||
|
||||
self._record_symbolic_summary( |
||||
global_state, summary_annotation, transaction, revert |
||||
) |
||||
|
||||
self._restore_previous_state(global_state, summary_annotation) |
||||
|
||||
@staticmethod |
||||
def _get_and_remove_summary_tracking_annotation( |
||||
global_state: GlobalState, |
||||
) -> Optional[SummaryTrackingAnnotation]: |
||||
"""Retrieves symbolic summary from the global state""" |
||||
summary_annotation: List[SummaryTrackingAnnotation] = list( |
||||
global_state.get_annotations(SummaryTrackingAnnotation) |
||||
) |
||||
if len(summary_annotation) != 1: |
||||
logging.warning( |
||||
f"Unexpected number of summary tracking annotations found: {len(summary_annotation)}\nSkipping..." |
||||
) |
||||
|
||||
summary_annotation: SummaryTrackingAnnotation = summary_annotation[0] |
||||
global_state.annotations.remove(summary_annotation) |
||||
return summary_annotation |
||||
|
||||
def _record_symbolic_summary( |
||||
self, |
||||
global_state: GlobalState, |
||||
tracking_annotation: SummaryTrackingAnnotation, |
||||
transaction: BaseTransaction, |
||||
revert, |
||||
): |
||||
"""Records a summary between tracking_annotation.entry and global_state""" |
||||
if ( |
||||
len(list(global_state.get_annotations(MutationAnnotation))) == 0 |
||||
and list(global_state.get_annotations(IssueAnnotation)) == 0 |
||||
): |
||||
return |
||||
|
||||
storage_mutations = [] |
||||
return_value = transaction.return_data |
||||
set_map = {} |
||||
get_map = {} |
||||
for index, account in global_state.world_state.accounts.items(): |
||||
if account.storage._standard_storage not in [ |
||||
p[1] for p in tracking_annotation.storage_pairs |
||||
]: |
||||
get_map[account.address] = account.storage.keys_get |
||||
set_map[account.address] = account.storage.keys_set |
||||
storage_mutations.append( |
||||
(index, copy(account.storage._standard_storage)) |
||||
) |
||||
|
||||
condition = global_state.world_state.constraints.get_all_constraints() |
||||
for constraint in tracking_annotation.entry.world_state.constraints: |
||||
condition.remove(constraint) |
||||
annotations = list(global_state.get_annotations(IssueAnnotation)) |
||||
summary = SymbolicSummary( |
||||
storage_effect=deepcopy(storage_mutations), |
||||
balance_effect=copy(global_state.world_state.balances), |
||||
condition=deepcopy(condition), |
||||
return_value=return_value, |
||||
entry=tracking_annotation.entry.mstate.pc, |
||||
exit=global_state.mstate.pc, |
||||
trace=tracking_annotation.trace, |
||||
code=tracking_annotation.code, |
||||
issues=list(global_state.get_annotations(IssueAnnotation)), |
||||
revert=revert, |
||||
get_map=get_map, |
||||
set_map=set_map, |
||||
) |
||||
log.debug(list(global_state.get_annotations(IssueAnnotation))) |
||||
# Calculate issues for the first transaction |
||||
if len(global_state.world_state.transaction_sequence) == 2: |
||||
for state in self.init_save_states: |
||||
for issue in summary.issues: |
||||
self._check_issue(state, issue) |
||||
|
||||
self.summaries.append(summary) |
||||
|
||||
@classmethod |
||||
def _restore_previous_state( |
||||
cls, global_state: GlobalState, tracking_annotation: SummaryTrackingAnnotation |
||||
): |
||||
"""Restores the previous persistent variables to the global state""" |
||||
for og_storage, sym_storage in tracking_annotation.storage_pairs: |
||||
cls._rewrite(global_state, sym_storage, og_storage) |
||||
|
||||
cls._rewrite( |
||||
global_state, |
||||
tracking_annotation.balance_pair[1], |
||||
tracking_annotation.balance_pair[0], |
||||
) |
||||
cls._restore_environment(tracking_annotation, global_state) |
||||
|
||||
@staticmethod |
||||
def _rewrite(global_state: GlobalState, original: Expression, new: Expression): |
||||
for account in global_state.world_state.accounts.values(): |
||||
account.storage._standard_storage.substitute(original, new) |
||||
|
||||
global_state.world_state.balances.substitute(original, new) |
||||
|
||||
for constraint in global_state.world_state.constraints: |
||||
constraint.substitute(original, new) |
@ -0,0 +1,151 @@ |
||||
from mythril.laser.smt import BaseArray, Array, Solver, symbol_factory |
||||
from mythril.support.support_args import args |
||||
from mythril.laser.ethereum.state.global_state import GlobalState |
||||
from mythril.laser.plugin.plugins.plugin_annotations import MutationAnnotation |
||||
from copy import deepcopy |
||||
|
||||
import logging |
||||
import z3 |
||||
|
||||
log = logging.getLogger(__name__) |
||||
|
||||
|
||||
class SymbolicSummary: |
||||
"""Symbolic Summary |
||||
|
||||
A symbolic summary is an awesome construct that allows mythril to record and re-use partial analysis results |
||||
""" |
||||
|
||||
def __init__( |
||||
self, |
||||
storage_effect, |
||||
balance_effect, |
||||
condition, |
||||
return_value, |
||||
entry, |
||||
exit, |
||||
trace, |
||||
code, |
||||
issues, |
||||
revert, |
||||
set_map=None, |
||||
get_map=None, |
||||
): |
||||
self.storage_effect = storage_effect |
||||
self.balance_effect = balance_effect |
||||
self.condition = condition |
||||
self.return_value = return_value |
||||
self.entry = entry |
||||
self.exit = exit |
||||
self.trace = trace |
||||
self.code = code |
||||
self.issues = issues |
||||
self.revert = revert |
||||
self.set_map = set_map |
||||
self.get_map = get_map |
||||
|
||||
@property |
||||
def as_csv(self, delimiter=",", sub_array_delimiter=";", tuple_delimiter=":"): |
||||
condition = sub_array_delimiter.join(map(str, self.condition)) |
||||
storage_effect = sub_array_delimiter.join( |
||||
[f"{ap[0]}{tuple_delimiter}{ap[1]}" for ap in self.storage_effect] |
||||
) |
||||
return_value = None |
||||
trace = sub_array_delimiter.join(map(str, self.trace)) |
||||
return ( |
||||
delimiter.join( |
||||
map( |
||||
str, |
||||
[ |
||||
self.entry, |
||||
condition, |
||||
self.exit, |
||||
storage_effect, |
||||
return_value, |
||||
trace, |
||||
], |
||||
) |
||||
) |
||||
.replace("\n", "") |
||||
.replace(" ", "") |
||||
) |
||||
|
||||
@property |
||||
def as_dict(self): |
||||
return dict( |
||||
entry=self.entry, |
||||
condition=list(map(str, self.condition)), |
||||
exit=self.exit, |
||||
storage_effect=list(map(str, self.storage_effect)), |
||||
balance_effect=str(self.balance_effect), |
||||
return_value=self.return_value, |
||||
trace=self.trace[:], |
||||
code=self.code, |
||||
issues=len(self.issues), |
||||
revert=self.revert, |
||||
) |
||||
|
||||
def apply_summary(self, global_state: GlobalState): |
||||
|
||||
# Copy and apply summary |
||||
global_state = deepcopy(global_state) |
||||
conditions = deepcopy(self.condition) |
||||
for account_id, account in global_state.world_state.accounts.items(): |
||||
for expression in conditions: |
||||
substitute_exprs(expression, account_id, account, global_state) |
||||
|
||||
for account_id, effect in self.storage_effect: |
||||
account = global_state.world_state.accounts[account_id] |
||||
new_storage = deepcopy(effect) |
||||
substitute_exprs(new_storage, account_id, account, global_state) |
||||
account.storage._standard_storage = new_storage |
||||
|
||||
new_balances = deepcopy(self.balance_effect) |
||||
new_balances.substitute( |
||||
Array("summary_balance", 256, 256), global_state.world_state.balances |
||||
) |
||||
global_state.world_state.balances = new_balances |
||||
|
||||
# Set constraints |
||||
global_state.world_state.constraints += [c for c in conditions] |
||||
|
||||
# Check Condition |
||||
solver = Solver() |
||||
solver.set_timeout(args.solver_timeout) |
||||
solver.add(*(global_state.world_state.constraints.as_list)) |
||||
sat = solver.check() == z3.sat |
||||
|
||||
if not sat: |
||||
return [] |
||||
|
||||
global_state.node.constraints = global_state.world_state.constraints |
||||
global_state.world_state.node = global_state.node |
||||
global_state.annotate(MutationAnnotation()) |
||||
return [global_state] |
||||
|
||||
|
||||
def substitute_exprs(expression, account_id, account, global_state): |
||||
|
||||
a = Array("2_calldata", 256, 8) |
||||
b = Array(f"{global_state.current_transaction.id}_calldata", 256, 8) |
||||
expression.substitute(a, b) |
||||
a = symbol_factory.BitVecSym("2_calldatasize", 256) |
||||
b = symbol_factory.BitVecSym( |
||||
f"{global_state.current_transaction.id}_calldatasize", 256 |
||||
) |
||||
expression.substitute(a, b) |
||||
a = symbol_factory.BitVecSym("sender_2", 256) |
||||
b = symbol_factory.BitVecSym(f"sender_{global_state.current_transaction.id}", 256) |
||||
expression.substitute(a, b) |
||||
a = symbol_factory.BitVecSym("call_value2", 256) |
||||
b = symbol_factory.BitVecSym( |
||||
f"call_value{global_state.current_transaction.id}", 256 |
||||
) |
||||
expression.substitute(a, b) |
||||
a = Array(f"{account_id}_symbolic_storage", 256, 256) |
||||
b = account.storage._standard_storage |
||||
expression.substitute(a, b) |
||||
|
||||
a = Array("summary_balance", 256, 256) |
||||
b = global_state.world_state.balances |
||||
expression.substitute(a, b) |
@ -0,0 +1,114 @@ |
||||
import pytest |
||||
import json |
||||
import sys |
||||
import os |
||||
|
||||
from tests import PROJECT_DIR, TESTDATA |
||||
from subprocess import check_output, CalledProcessError |
||||
|
||||
MYTH = str(PROJECT_DIR / "myth") |
||||
|
||||
|
||||
def output_of(command): |
||||
""" |
||||
|
||||
:param command: |
||||
:return: |
||||
""" |
||||
try: |
||||
return check_output(command, shell=True).decode("UTF-8") |
||||
except CalledProcessError as exc: |
||||
return exc.output.decode("UTF-8") |
||||
|
||||
|
||||
test_data = ( |
||||
# TODO: The commented tests should be sped up! |
||||
# ( |
||||
# "destruct.sol", |
||||
# { |
||||
# "TX_COUNT": 5, |
||||
# "MODULE": "AccidentallyKillable", |
||||
# "ISSUE_COUNT": 1, |
||||
# "VERSION": "v0.5.0", |
||||
# }, |
||||
# ), |
||||
# ( |
||||
# "destruct.sol", |
||||
# { |
||||
# "TX_COUNT": 4, |
||||
# "MODULE": "AccidentallyKillable", |
||||
# "ISSUE_COUNT": 0, |
||||
# "VERSION": "v0.5.0", |
||||
# }, |
||||
# ), |
||||
( |
||||
"theft.sol", |
||||
{"TX_COUNT": 4, "MODULE": "EtherThief", "ISSUE_COUNT": 1, "VERSION": "v0.5.0"}, |
||||
), |
||||
( |
||||
"theft.sol", |
||||
{"TX_COUNT": 3, "MODULE": "EtherThief", "ISSUE_COUNT": 0, "VERSION": "v0.5.0"}, |
||||
), |
||||
( |
||||
"large.sol", |
||||
{ |
||||
"TX_COUNT": 11, |
||||
"MODULE": "AccidentallyKillable", |
||||
"ISSUE_COUNT": 1, |
||||
"VERSION": "v0.5.0", |
||||
}, |
||||
), |
||||
( |
||||
"large.sol", |
||||
{ |
||||
"TX_COUNT": 10, |
||||
"MODULE": "AccidentallyKillable", |
||||
"ISSUE_COUNT": 0, |
||||
"VERSION": "v0.5.0", |
||||
}, |
||||
), |
||||
( |
||||
"hash_test.sol", |
||||
{ |
||||
"TX_COUNT": 2, |
||||
"MODULE": "AccidentallyKillable", |
||||
"ISSUE_COUNT": 1, |
||||
"VERSION": "v0.4.24", |
||||
}, |
||||
), |
||||
( |
||||
"complex.sol", |
||||
{ |
||||
"TX_COUNT": 2, |
||||
"MODULE": "AccidentallyKillable", |
||||
"ISSUE_COUNT": 1, |
||||
"VERSION": "v0.5.0", |
||||
}, |
||||
), |
||||
( |
||||
"base_case.sol", |
||||
{ |
||||
"TX_COUNT": 1, |
||||
"MODULE": "AccidentallyKillable", |
||||
"ISSUE_COUNT": 1, |
||||
"VERSION": "v0.5.0", |
||||
}, |
||||
), |
||||
( |
||||
"simple_theft.sol", |
||||
{ |
||||
"TX_COUNT": 1, |
||||
"MODULE": "EtherThief", |
||||
"ISSUE_COUNT": 0, |
||||
"VERSION": "v0.5.0", |
||||
}, |
||||
), |
||||
) |
||||
|
||||
|
||||
@pytest.mark.parametrize("file_name, tx_data", test_data) |
||||
def test_analysis(file_name, tx_data): |
||||
file_path = str(TESTDATA / "input_contracts" / file_name) |
||||
command = f"""python3 {MYTH} analyze {file_path} -t {tx_data["TX_COUNT"]} -o jsonv2 -m {tx_data["MODULE"]} --solver-timeout 60000 --solv {tx_data["VERSION"]} --execution-timeout 300 --enable-summaries""" |
||||
output = json.loads(output_of(command)) |
||||
assert len(output[0]["issues"]) == tx_data["ISSUE_COUNT"] |
@ -0,0 +1,21 @@ |
||||
contract B{ |
||||
uint x=0; |
||||
function incr() public returns(uint){ |
||||
require(x==0); |
||||
x += 1; |
||||
} |
||||
function incr2() public payable returns(uint){ |
||||
require(x==1); |
||||
x += 1; |
||||
|
||||
} |
||||
function continous_incr() public payable returns(uint){ |
||||
require(x>=2); |
||||
x += 1; |
||||
} |
||||
|
||||
function destroy() public returns(uint){ |
||||
selfdestruct(msg.sender); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,88 @@ |
||||
|
||||
pragma solidity 0.5.0; |
||||
|
||||
contract WalletLibrary { |
||||
|
||||
struct PendingState { |
||||
uint yetNeeded; |
||||
uint ownersDone; |
||||
uint index; |
||||
} |
||||
|
||||
struct Transaction { |
||||
address to; |
||||
uint value; |
||||
bytes data; |
||||
} |
||||
|
||||
modifier onlymanyowners(bytes32 _operation) { |
||||
if (confirmAndCheck(_operation)) |
||||
_; |
||||
} |
||||
|
||||
|
||||
|
||||
function initMultiowned(address[] memory _owners, uint _required) public only_uninitialized { |
||||
m_numOwners = _owners.length + 1; |
||||
m_owners[1] = uint(msg.sender); |
||||
m_ownerIndex[uint(msg.sender)] = 1; |
||||
for (uint i = 0; i < _owners.length; ++i) |
||||
{ |
||||
m_owners[2 + i] = uint(_owners[i]); |
||||
m_ownerIndex[uint(_owners[i])] = 2 + i; |
||||
} |
||||
m_required = _required; |
||||
} |
||||
|
||||
modifier only_uninitialized { require(m_numOwners == 0); _; } |
||||
|
||||
|
||||
function kill(address payable _to) onlymanyowners(keccak256(msg.data)) external { |
||||
selfdestruct(_to); |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
function confirmAndCheck(bytes32 _operation) internal returns (bool) { |
||||
uint ownerIndex = m_ownerIndex[uint(msg.sender)]; |
||||
if (ownerIndex == 0) return false; |
||||
|
||||
PendingState memory pending = m_pending[_operation]; |
||||
if (pending.yetNeeded == 0) { |
||||
pending.yetNeeded = m_required; |
||||
pending.ownersDone = 0; |
||||
pending.index = m_pendingIndex.length++; |
||||
m_pendingIndex[pending.index] = _operation; |
||||
} |
||||
uint ownerIndexBit = 2**ownerIndex; |
||||
if (pending.ownersDone & ownerIndexBit == 0) { |
||||
if (pending.yetNeeded <= 1) { |
||||
delete m_pendingIndex[m_pending[_operation].index]; |
||||
delete m_pending[_operation]; |
||||
return true; |
||||
} |
||||
else |
||||
{ |
||||
// not enough: record that this owner in particular confirmed. |
||||
pending.yetNeeded--; |
||||
pending.ownersDone |= ownerIndexBit; |
||||
} |
||||
} |
||||
|
||||
|
||||
} |
||||
|
||||
|
||||
uint public m_required; |
||||
uint public m_numOwners; |
||||
|
||||
|
||||
// list of owners |
||||
uint[256] m_owners; |
||||
|
||||
mapping(uint => uint) m_ownerIndex; |
||||
mapping(bytes32 => PendingState) m_pending; |
||||
bytes32[] m_pendingIndex; |
||||
|
||||
} |
@ -0,0 +1,88 @@ |
||||
|
||||
pragma solidity 0.5.0; |
||||
|
||||
contract WalletLibrary { |
||||
|
||||
struct PendingState { |
||||
uint yetNeeded; |
||||
uint ownersDone; |
||||
uint index; |
||||
} |
||||
|
||||
struct Transaction { |
||||
address to; |
||||
uint value; |
||||
bytes data; |
||||
} |
||||
|
||||
modifier onlymanyowners(bytes32 _operation) { |
||||
if (confirmAndCheck(_operation)) |
||||
_; |
||||
} |
||||
|
||||
|
||||
|
||||
function initMultiowned(address[] memory _owners, uint _required) public only_uninitialized { |
||||
m_numOwners = _owners.length + 1; |
||||
m_owners[1] = uint(msg.sender); |
||||
m_ownerIndex[uint(msg.sender)] = 1; |
||||
for (uint i = 0; i < _owners.length; ++i) |
||||
{ |
||||
m_owners[2 + i] = uint(_owners[i]); |
||||
m_ownerIndex[uint(_owners[i])] = 2 + i; |
||||
} |
||||
m_required = _required; |
||||
} |
||||
|
||||
modifier only_uninitialized { require(m_numOwners == 0); _; } |
||||
|
||||
|
||||
function kill(address payable _to) onlymanyowners(keccak256(msg.data)) external { |
||||
selfdestruct(_to); |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
function confirmAndCheck(bytes32 _operation) internal returns (bool) { |
||||
uint ownerIndex = m_ownerIndex[uint(msg.sender)]; |
||||
if (ownerIndex == 0) return false; |
||||
|
||||
PendingState memory pending = m_pending[_operation]; |
||||
if (pending.yetNeeded == 0) { |
||||
pending.yetNeeded = m_required; |
||||
pending.ownersDone = 0; |
||||
pending.index = m_pendingIndex.length++; |
||||
m_pendingIndex[pending.index] = _operation; |
||||
} |
||||
uint ownerIndexBit = 2**ownerIndex; |
||||
if (pending.ownersDone & ownerIndexBit == 0) { |
||||
if (pending.yetNeeded <= 1) { |
||||
delete m_pendingIndex[m_pending[_operation].index]; |
||||
delete m_pending[_operation]; |
||||
return true; |
||||
} |
||||
else |
||||
{ |
||||
// not enough: record that this owner in particular confirmed. |
||||
pending.yetNeeded--; |
||||
pending.ownersDone |= ownerIndexBit; |
||||
} |
||||
} |
||||
|
||||
|
||||
} |
||||
|
||||
|
||||
uint public m_required; |
||||
uint public m_numOwners; |
||||
|
||||
|
||||
// list of owners |
||||
uint[256] m_owners; |
||||
|
||||
mapping(uint => uint) m_ownerIndex; |
||||
mapping(bytes32 => PendingState) m_pending; |
||||
bytes32[] m_pendingIndex; |
||||
|
||||
} |
@ -0,0 +1,15 @@ |
||||
|
||||
contract StorageTest { |
||||
mapping(bytes32 => address) data; |
||||
|
||||
function confirmAndCheck(uint256 x) public{ |
||||
data[keccak256(abi.encodePacked(x))] = msg.sender; |
||||
} |
||||
|
||||
function destruct(bytes32 x) public{ |
||||
require(data[x] == msg.sender); |
||||
selfdestruct(data[x]); |
||||
} |
||||
|
||||
} |
||||
|
@ -0,0 +1,14 @@ |
||||
contract B{ |
||||
uint x=0; |
||||
uint total=0; |
||||
function incr() public returns(uint){ |
||||
x += 1; |
||||
} |
||||
|
||||
|
||||
function foo() public returns(uint){ |
||||
require(x==10); |
||||
selfdestruct(msg.sender); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,8 @@ |
||||
pragma solidity ^0.8.0; |
||||
|
||||
contract Fallback { |
||||
|
||||
function withdraw() public { payable(msg.sender).transfer(address(this).balance); } |
||||
|
||||
|
||||
} |
@ -0,0 +1,25 @@ |
||||
contract B{ |
||||
uint x=0; |
||||
uint total = 0; |
||||
function incr() public returns(uint){ |
||||
require(x==0); |
||||
x += 1; |
||||
} |
||||
function incr2() public payable returns(uint){ |
||||
require(x==1); |
||||
x += 1; |
||||
total += msg.value; |
||||
} |
||||
function continous_incr(uint val) public payable returns(uint){ |
||||
require(x>=2); |
||||
x += val; |
||||
total += msg.value; |
||||
} |
||||
|
||||
function foo() public returns(uint){ |
||||
require(x==4); |
||||
x += 1; |
||||
msg.sender.transfer(total); |
||||
} |
||||
} |
||||
|
Loading…
Reference in new issue