Fix merge conflicts

pull/971/head
Nikhil Parasaram 6 years ago
commit aba7e32edf
  1. 2
      .gitignore
  2. 8
      README.md
  3. 6
      mythril/analysis/modules/delegatecall.py
  4. 20
      mythril/analysis/modules/dependence_on_predictable_vars.py
  5. 10
      mythril/analysis/modules/deprecated_ops.py
  6. 7
      mythril/analysis/modules/ether_thief.py
  7. 11
      mythril/analysis/modules/exceptions.py
  8. 12
      mythril/analysis/modules/external_calls.py
  9. 18
      mythril/analysis/modules/integer.py
  10. 5
      mythril/analysis/modules/multiple_sends.py
  11. 13
      mythril/analysis/modules/suicide.py
  12. 198
      mythril/analysis/modules/transaction_order_dependence.py
  13. 7
      mythril/analysis/modules/unchecked_retval.py
  14. 2
      mythril/analysis/report.py
  15. 9
      mythril/analysis/symbolic.py
  16. 4
      mythril/ethereum/interface/leveldb/state.py
  17. 147
      mythril/interfaces/cli.py
  18. 2
      mythril/laser/ethereum/call.py
  19. 2
      mythril/laser/ethereum/instructions.py
  20. 21
      mythril/laser/ethereum/plugins/__init__.py
  21. 7
      mythril/laser/ethereum/plugins/implementations/__init__.py
  22. 4
      mythril/laser/ethereum/plugins/implementations/benchmark.py
  23. 3
      mythril/laser/ethereum/plugins/implementations/coverage/__init__.py
  24. 98
      mythril/laser/ethereum/plugins/implementations/coverage/coverage_plugin.py
  25. 43
      mythril/laser/ethereum/plugins/implementations/coverage/coverage_strategy.py
  26. 3
      mythril/laser/ethereum/plugins/implementations/mutation_pruner.py
  27. 23
      mythril/laser/ethereum/plugins/plugin.py
  28. 32
      mythril/laser/ethereum/plugins/plugin_factory.py
  29. 38
      mythril/laser/ethereum/plugins/plugin_loader.py
  30. 4
      mythril/laser/ethereum/strategy/__init__.py
  31. 59
      mythril/laser/ethereum/svm.py
  32. 454
      mythril/laser/ethereum/taint_analysis.py
  33. 4
      mythril/mythril/__init__.py
  34. 173
      mythril/mythril/mythril_analyzer.py
  35. 226
      mythril/mythril/mythril_config.py
  36. 303
      mythril/mythril/mythril_disassembler.py
  37. 49
      mythril/mythril/mythril_leveldb.py
  38. 6
      mythril/support/loader.py
  39. 2
      mythril/version.py
  40. 1
      requirements.txt
  41. 1
      setup.py
  42. 15
      tests/graph_test.py
  43. 6
      tests/laser/transaction/create_transaction_test.py
  44. 31
      tests/mythril/mythril_analyzer_test.py
  45. 58
      tests/mythril/mythril_config_test.py
  46. 70
      tests/mythril/mythril_disassembler_test.py
  47. 51
      tests/mythril/mythril_leveldb_test.py
  48. 5
      tests/native_test.py
  49. 11
      tests/report_test.py
  50. 8
      tests/solidity_contract_test.py
  51. 30
      tests/taint_mutate_stack_test.py
  52. 36
      tests/taint_record_test.py
  53. 35
      tests/taint_result_test.py
  54. 99
      tests/taint_runner_test.py
  55. 2
      tests/testdata/mythril_config_inputs/config.ini
  56. 149
      tests/testdata/outputs_expected/calls.sol.o.jsonv2
  57. 81
      tests/testdata/outputs_expected/exceptions.sol.o.jsonv2
  58. 98
      tests/testdata/outputs_expected/kinds_of_calls.sol.o.jsonv2
  59. 30
      tests/testdata/outputs_expected/multi_contracts.sol.o.jsonv2
  60. 30
      tests/testdata/outputs_expected/origin.sol.o.jsonv2
  61. 47
      tests/testdata/outputs_expected/overflow.sol.o.jsonv2
  62. 64
      tests/testdata/outputs_expected/returnvalue.sol.o.jsonv2
  63. 30
      tests/testdata/outputs_expected/suicide.sol.o.jsonv2
  64. 47
      tests/testdata/outputs_expected/underflow.sol.o.jsonv2
  65. 2
      tox.ini

2
.gitignore vendored

@ -175,7 +175,7 @@ lol*
coverage_html_report/
tests/testdata/outputs_current/
tests/testdata/outputs_current_laser_result/
tests/mythril_dir/signatures.db
tests/testdata/mythril_config_inputs/config.ini
# VSCode
.vscode

@ -12,10 +12,12 @@
[![Sonarcloud - Maintainability](https://sonarcloud.io/api/project_badges/measure?project=mythril&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=mythril)
[![Downloads](https://pepy.tech/badge/mythril)](https://pepy.tech/project/mythril)
Mythril Classic is an open-source security analysis tool for Ethereum smart contracts. It uses symbolic analysis, taint analysis and control flow checking to detect a variety of security vulnerabilities. It's also an experimental tool designed for security pros. If you a smart contract developer you might prefer smoother tools such as:
Mythril Classic is an open-source security analysis tool for Ethereum smart contracts. It uses symbolic analysis, taint analysis and control flow checking to detect a variety of security vulnerabilities.
- [Mythos](https://github.com/cleanunicorn/mythos)
- [Truffle Security](https://github.com/ConsenSys/truffle-security)
Note that Mythril Classic is designed for security auditors. If you are a smart contract developer, we recommend using [MythX tools](https://github.com/b-mueller/awesome-mythx-smart-contract-security) which are optimized for usability and cover a wider range of security issues:
- [Sabre](https://github.com/b-mueller/sabre)
- [MythX Plugin for Truffle](https://github.com/ConsenSys/truffle-security)
Whether you want to contribute, need support, or want to learn what we have cooking for the future, our [Discord server](https://discord.gg/E3YrVtG) will serve your needs.

@ -50,7 +50,7 @@ def _analyze_states(state: GlobalState) -> List[Issue]:
if call.type is not "DELEGATECALL":
return []
if call.node.function_name is not "fallback":
if state.environment.active_function_name is not "fallback":
return []
state = call.state
@ -77,8 +77,8 @@ def _concrete_call(
return []
issue = Issue(
contract=call.node.contract_name,
function_name=call.node.function_name,
contract=state.environment.active_account.contract_name,
function_name=state.environment.active_function_name,
address=address,
swc_id=DELEGATECALL_TO_UNTRUSTED_CONTRACT,
bytecode=state.environment.code.bytecode,

@ -71,13 +71,13 @@ def _analyze_states(state: GlobalState) -> list:
"The contract sends Ether depending on the values of the following variables:\n"
)
# First check: look for predictable state variables in node & call recipient constraints
# First check: look for predictable state variables in state & call recipient constraints
vars = ["coinbase", "gaslimit", "timestamp", "number"]
found = []
for var in vars:
for constraint in call.node.constraints[:] + [call.to]:
for constraint in call.state.mstate.constraints[:] + [call.to]:
if var in str(constraint):
found.append(var)
@ -94,8 +94,8 @@ def _analyze_states(state: GlobalState) -> list:
)
issue = Issue(
contract=call.node.contract_name,
function_name=call.node.function_name,
contract=state.environment.active_account.contract_name,
function_name=state.environment.active_function_name,
address=address,
swc_id=swc_id,
bytecode=call.state.environment.code.bytecode,
@ -112,7 +112,7 @@ def _analyze_states(state: GlobalState) -> list:
# Second check: blockhash
for constraint in call.node.constraints[:] + [call.to]:
for constraint in call.state.mstate.constraints[:] + [call.to]:
if "blockhash" in str(constraint):
if "number" in str(constraint):
m = re.search(r"blockhash\w+(\s-\s(\d+))*", str(constraint))
@ -145,8 +145,8 @@ def _analyze_states(state: GlobalState) -> list:
description += ", this expression will always be equal to zero."
issue = Issue(
contract=call.node.contract_name,
function_name=call.node.function_name,
contract=state.environment.active_account.contract_name,
function_name=state.environment.active_function_name,
address=address,
bytecode=call.state.environment.code.bytecode,
title="Dependence on Predictable Variable",
@ -186,8 +186,8 @@ def _analyze_states(state: GlobalState) -> list:
)
)
issue = Issue(
contract=call.node.contract_name,
function_name=call.node.function_name,
contract=state.environment.active_account.contract_name,
function_name=state.environment.active_function_name,
address=address,
bytecode=call.state.environment.code.bytecode,
title="Dependence on Predictable Variable",
@ -212,7 +212,7 @@ def solve(call: Call) -> bool:
:return:
"""
try:
model = solver.get_model(call.node.constraints)
model = solver.get_model(call.state.mstate.constraints)
log.debug("[DEPENDENCE_ON_PREDICTABLE_VARS] MODEL: " + str(model))
pretty_model = solver.pretty_print_model(model)

@ -30,13 +30,13 @@ def _analyze_state(state):
"Use of msg.origin is deprecated and the instruction may be removed in the future. "
"Use msg.sender instead.\nSee also: "
"https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin".format(
node.function_name
state.environment.active_function_name
)
)
swc_id = DEPRECATED_FUNCTIONS_USAGE
elif instruction["opcode"] == "CALLCODE":
log.debug("CALLCODE in function " + node.function_name)
log.debug("CALLCODE in function " + state.environment.active_function_name)
title = "Use of callcode"
description_head = "Use of callcode is deprecated."
description_tail = (
@ -45,10 +45,12 @@ def _analyze_state(state):
"therefore deprecated and may be removed in the future. Use the delegatecall method instead."
)
swc_id = DEPRECATED_FUNCTIONS_USAGE
else:
return
issue = Issue(
contract=node.contract_name,
function_name=node.function_name,
contract=state.environment.active_account.contract_name,
function_name=state.environment.active_function_name,
address=instruction["address"],
title=title,
bytecode=state.environment.code.bytecode,

@ -67,7 +67,6 @@ class EtherThief(DetectionModule):
:return:
"""
instruction = state.get_current_instruction()
node = state.node
if instruction["opcode"] != "CALL":
return []
@ -80,7 +79,7 @@ class EtherThief(DetectionModule):
eth_sent_total = symbol_factory.BitVecVal(0, 256)
constraints = copy(node.constraints)
constraints = copy(state.mstate.constraints)
for tx in state.world_state.transaction_sequence:
if tx.caller == 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF:
@ -101,8 +100,8 @@ class EtherThief(DetectionModule):
debug = json.dumps(transaction_sequence, indent=4)
issue = Issue(
contract=node.contract_name,
function_name=node.function_name,
contract=state.environment.active_account.contract_name,
function_name=state.environment.active_function_name,
address=instruction["address"],
swc_id=UNPROTECTED_ETHER_WITHDRAWAL,
title="Unprotected Ether Withdrawal",

@ -19,9 +19,8 @@ def _analyze_state(state) -> list:
:return:
"""
log.info("Exceptions module: found ASSERT_FAIL instruction")
node = state.node
log.debug("ASSERT_FAIL in function " + node.function_name)
log.debug("ASSERT_FAIL in function " + state.environment.active_function_name)
try:
address = state.get_current_instruction()["address"]
@ -34,12 +33,14 @@ def _analyze_state(state) -> list:
"Use `require()` for regular input checking."
)
transaction_sequence = solver.get_transaction_sequence(state, node.constraints)
transaction_sequence = solver.get_transaction_sequence(
state, state.mstate.constraints
)
debug = json.dumps(transaction_sequence, indent=4)
issue = Issue(
contract=node.contract_name,
function_name=node.function_name,
contract=state.environment.active_account.contract_name,
function_name=state.environment.active_function_name,
address=address,
swc_id=ASSERT_VIOLATION,
title="Exception State",

@ -8,6 +8,7 @@ from mythril.analysis.report import Issue
from mythril.laser.smt import UGT, symbol_factory
from mythril.laser.ethereum.state.global_state import GlobalState
from mythril.exceptions import UnsatError
from copy import copy
import logging
import json
@ -28,14 +29,13 @@ def _analyze_state(state):
:param state:
:return:
"""
node = state.node
gas = state.mstate.stack[-1]
to = state.mstate.stack[-2]
address = state.get_current_instruction()["address"]
try:
constraints = node.constraints
constraints = copy(state.mstate.constraints)
transaction_sequence = solver.get_transaction_sequence(
state, constraints + [UGT(gas, symbol_factory.BitVecVal(2300, 256))]
)
@ -56,8 +56,8 @@ def _analyze_state(state):
)
issue = Issue(
contract=node.contract_name,
function_name=node.function_name,
contract=state.environment.active_account.contract_name,
function_name=state.environment.active_function_name,
address=address,
swc_id=REENTRANCY,
title="External Call To User-Supplied Address",
@ -83,8 +83,8 @@ def _analyze_state(state):
)
issue = Issue(
contract=node.contract_name,
function_name=state.node.function_name,
contract=state.environment.active_account.contract_name,
function_name=state.environment.active_function_name,
address=address,
swc_id=REENTRANCY,
title="External Call To Fixed Address",

@ -120,7 +120,7 @@ class IntegerOverflowUnderflowModule(DetectionModule):
c = Not(BVAddNoOverflow(op0, op1, False))
# Check satisfiable
model = self._try_constraints(state.node.constraints, [c])
model = self._try_constraints(state.mstate.constraints, [c])
if model is None:
return
@ -132,7 +132,7 @@ class IntegerOverflowUnderflowModule(DetectionModule):
c = Not(BVMulNoOverflow(op0, op1, False))
# Check satisfiable
model = self._try_constraints(state.node.constraints, [c])
model = self._try_constraints(state.mstate.constraints, [c])
if model is None:
return
@ -144,7 +144,7 @@ class IntegerOverflowUnderflowModule(DetectionModule):
c = Not(BVSubNoUnderflow(op0, op1, False))
# Check satisfiable
model = self._try_constraints(state.node.constraints, [c])
model = self._try_constraints(state.mstate.constraints, [c])
if model is None:
return
@ -172,7 +172,7 @@ class IntegerOverflowUnderflowModule(DetectionModule):
)
else:
constraint = op0.value ** op1.value >= 2 ** 256
model = self._try_constraints(state.node.constraints, [constraint])
model = self._try_constraints(state.mstate.constraints, [constraint])
if model is None:
return
annotation = OverUnderflowAnnotation(state, "exponentiation", constraint)
@ -286,19 +286,18 @@ class IntegerOverflowUnderflowModule(DetectionModule):
):
continue
node = ostate.node
try:
transaction_sequence = solver.get_transaction_sequence(
state, node.constraints + [annotation.constraint]
state, state.mstate.constraints + [annotation.constraint]
)
except UnsatError:
continue
_type = "Underflow" if annotation.operator == "subtraction" else "Overflow"
issue = Issue(
contract=node.contract_name,
function_name=node.function_name,
contract=ostate.environment.active_account.contract_name,
function_name=ostate.environment.active_function_name,
address=ostate.get_current_instruction()["address"],
swc_id=INTEGER_OVERFLOW_AND_UNDERFLOW,
bytecode=ostate.environment.code.bytecode,
@ -319,8 +318,7 @@ class IntegerOverflowUnderflowModule(DetectionModule):
@staticmethod
def _try_constraints(constraints, new_constraints):
"""
Tries new constraints
""" Tries new constraints
:return Model if satisfiable otherwise None
"""
try:

@ -55,7 +55,6 @@ def _analyze_state(state: GlobalState):
:param state: the current state
:return: returns the issues for that corresponding state
"""
node = state.node
instruction = state.get_current_instruction()
annotations = cast(
@ -95,8 +94,8 @@ def _analyze_state(state: GlobalState):
)
issue = Issue(
contract=node.contract_name,
function_name=node.function_name,
contract=state.environment.active_account.contract_name,
function_name=state.environment.active_function_name,
address=instruction["address"],
swc_id=MULTIPLE_SENDS,
bytecode=state.environment.code.bytecode,

@ -48,13 +48,14 @@ class SuicideModule(DetectionModule):
def _analyze_state(self, state):
log.info("Suicide module: Analyzing suicide instruction")
node = state.node
instruction = state.get_current_instruction()
if self._cache_address.get(instruction["address"], False):
return []
to = state.mstate.stack[-1]
log.debug("[SUICIDE] SUICIDE in function " + node.function_name)
log.debug(
"[SUICIDE] SUICIDE in function " + state.environment.active_function_name
)
description_head = "The contract can be killed by anyone."
@ -62,7 +63,7 @@ class SuicideModule(DetectionModule):
try:
transaction_sequence = solver.get_transaction_sequence(
state,
node.constraints
state.mstate.constraints
+ [to == 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF],
)
description_tail = (
@ -71,7 +72,7 @@ class SuicideModule(DetectionModule):
)
except UnsatError:
transaction_sequence = solver.get_transaction_sequence(
state, node.constraints
state, state.mstate.constraints
)
description_tail = "Arbitrary senders can kill this contract."
@ -79,8 +80,8 @@ class SuicideModule(DetectionModule):
self._cache_address[instruction["address"]] = True
issue = Issue(
contract=node.contract_name,
function_name=node.function_name,
contract=state.environment.active_account.contract_name,
function_name=state.environment.active_function_name,
address=instruction["address"],
swc_id=UNPROTECTED_SELFDESTRUCT,
bytecode=state.environment.code.bytecode,

@ -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()

@ -61,7 +61,6 @@ class UncheckedRetvalModule(DetectionModule):
def _analyze_state(state: GlobalState) -> list:
instruction = state.get_current_instruction()
node = state.node
annotations = cast(
List[UncheckedRetvalAnnotation],
@ -80,7 +79,7 @@ def _analyze_state(state: GlobalState) -> list:
issues = []
for retval in retvals:
try:
solver.get_model(node.constraints + [retval["retval"] == 0])
solver.get_model(state.mstate.constraints + [retval["retval"] == 0])
except UnsatError:
continue
@ -91,8 +90,8 @@ def _analyze_state(state: GlobalState) -> list:
)
issue = Issue(
contract=node.contract_name,
function_name=node.function_name,
contract=state.environment.active_account.contract_name,
function_name=state.environment.active_function_name,
address=retval["address"],
bytecode=state.environment.code.bytecode,
title="Unchecked Call Return Value",

@ -214,7 +214,7 @@ class Report:
},
"severity": issue.severity,
"locations": [{"sourceMap": "%d:1:%d" % (issue.address, idx)}],
"extra": {},
"extra": {"discoveryTime": int(issue.discovery_time * 10 ** 9)},
}
)
meta_data = self._get_exception_data()

@ -12,7 +12,9 @@ from mythril.laser.ethereum.strategy.basic import (
ReturnWeightedRandomStrategy,
)
from mythril.laser.ethereum.plugins.mutation_pruner import MutationPruner
from mythril.laser.ethereum.plugins.plugin_factory import PluginFactory
from mythril.laser.ethereum.plugins.plugin_loader import LaserPluginLoader
from mythril.solidity.soliditycontract import EVMContract, SolidityContract
from .ops import Call, SStore, VarType, get_variable
@ -84,9 +86,10 @@ class SymExecWrapper:
requires_statespace=requires_statespace,
enable_iprof=enable_iprof,
)
mutation_plugin = MutationPruner()
mutation_plugin.initialize(self.laser)
plugin_loader = LaserPluginLoader(self.laser)
plugin_loader.load(PluginFactory.build_mutation_pruner_plugin())
plugin_loader.load(PluginFactory.build_instruction_coverage_plugin())
self.laser.register_hooks(
hook_type="pre",

@ -150,7 +150,7 @@ class State:
rlpdata = self.trie.get(addr)
if rlpdata != trie.BLANK_NODE:
o = rlp.decode(rlpdata, Account, db=self.db, address=addr)
o = rlp.decode(rlpdata, Account, db=self.db, addr=addr)
else:
o = Account.blank_account(self.db, addr, 0)
self.cache[addr] = o
@ -162,4 +162,4 @@ class State:
"""iterates through trie to and yields non-blank leafs as accounts."""
for address_hash, rlpdata in self.secure_trie.trie.iter_branch():
if rlpdata != trie.BLANK_NODE:
yield rlp.decode(rlpdata, Account, db=self.db, address=address_hash)
yield rlp.decode(rlpdata, Account, db=self.db, addr=address_hash)

@ -10,13 +10,18 @@ import json
import logging
import os
import sys
import traceback
import coloredlogs
import traceback
import mythril.support.signatures as sigs
from mythril.exceptions import AddressNotFoundError, CriticalError
from mythril.mythril import Mythril
from mythril.mythril import (
MythrilAnalyzer,
MythrilDisassembler,
MythrilConfig,
MythrilLevelDB,
)
from mythril.version import VERSION
# logging.basicConfig(level=logging.DEBUG)
@ -26,7 +31,6 @@ log = logging.getLogger(__name__)
def exit_with_error(format_, message):
"""
:param format_:
:param message:
"""
@ -64,10 +68,6 @@ def main() -> None:
parse_args(parser=parser, args=args)
if __name__ == "__main__":
main()
def create_parser(parser: argparse.ArgumentParser) -> None:
"""
Creates the parser by setting all the possible arguments
@ -315,61 +315,56 @@ def validate_args(parser: argparse.ArgumentParser, args: argparse.Namespace):
def quick_commands(args: argparse.Namespace):
if args.hash:
print(Mythril.hash_for_function_signature(args.hash))
print(MythrilDisassembler.hash_for_function_signature(args.hash))
sys.exit()
def set_config(args: argparse.Namespace):
mythril = Mythril(
solv=args.solv,
dynld=args.dynld,
onchain_storage_access=(not args.no_onchain_storage_access),
solc_args=args.solc_args,
enable_online_lookup=args.query_signature,
)
config = MythrilConfig()
if args.dynld or not args.no_onchain_storage_access and not (args.rpc or args.i):
mythril.set_api_from_config_path()
config.set_api_from_config_path()
if args.address:
# Establish RPC connection if necessary
mythril.set_api_rpc(rpc=args.rpc, rpctls=args.rpctls)
config.set_api_rpc(rpc=args.rpc, rpctls=args.rpctls)
elif args.search or args.contract_hash_to_address:
# Open LevelDB if necessary
mythril.set_api_leveldb(
mythril.leveldb_dir if not args.leveldb_dir else args.leveldb_dir
config.set_api_leveldb(
config.leveldb_dir if not args.leveldb_dir else args.leveldb_dir
)
return mythril
return config
def leveldb_search(mythril: Mythril, args: argparse.Namespace):
if args.search:
# Database search ops
mythril.search_db(args.search)
sys.exit()
def leveldb_search(config: MythrilConfig, args: argparse.Namespace):
if args.search or args.contract_hash_to_address:
leveldb_searcher = MythrilLevelDB(config.eth_db)
if args.search:
# Database search ops
leveldb_searcher.search_db(args.search)
if args.contract_hash_to_address:
# search corresponding address
try:
mythril.contract_hash_to_address(args.contract_hash_to_address)
except AddressNotFoundError:
print("Address not found.")
else:
# search corresponding address
try:
leveldb_searcher.contract_hash_to_address(args.contract_hash_to_address)
except AddressNotFoundError:
print("Address not found.")
sys.exit()
def get_code(mythril: Mythril, args: argparse.Namespace):
def get_code(disassembler: MythrilDisassembler, args: argparse.Namespace):
address = None
if args.code:
# Load from bytecode
code = args.code[2:] if args.code.startswith("0x") else args.code
address, _ = mythril.load_from_bytecode(code, args.bin_runtime)
address, _ = disassembler.load_from_bytecode(code, args.bin_runtime)
elif args.codefile:
bytecode = "".join([l.strip() for l in args.codefile if len(l.strip()) > 0])
bytecode = bytecode[2:] if bytecode.startswith("0x") else bytecode
address, _ = mythril.load_from_bytecode(bytecode, args.bin_runtime)
address, _ = disassembler.load_from_bytecode(bytecode, args.bin_runtime)
elif args.address:
# Get bytecode from a contract address
address, _ = mythril.load_from_address(args.address)
address, _ = disassembler.load_from_address(args.address)
elif args.solidity_file:
# Compile Solidity source file(s)
if args.graph and len(args.solidity_file) > 1:
@ -377,7 +372,9 @@ def get_code(mythril: Mythril, args: argparse.Namespace):
args.outform,
"Cannot generate call graphs from multiple input files. Please do it one at a time.",
)
address, _ = mythril.load_from_solidity(args.solidity_file) # list of files
address, _ = disassembler.load_from_solidity(
args.solidity_file
) # list of files
else:
exit_with_error(
args.outform,
@ -387,11 +384,12 @@ def get_code(mythril: Mythril, args: argparse.Namespace):
def execute_command(
mythril: Mythril,
disassembler: MythrilDisassembler,
address: str,
parser: argparse.ArgumentParser,
args: argparse.Namespace,
):
if args.storage:
if not args.address:
exit_with_error(
@ -399,36 +397,42 @@ def execute_command(
"To read storage, provide the address of a deployed contract with the -a option.",
)
storage = mythril.get_state_variable_from_storage(
storage = disassembler.get_state_variable_from_storage(
address=address, params=[a.strip() for a in args.storage.strip().split(",")]
)
print(storage)
return
analyzer = MythrilAnalyzer(
strategy=args.strategy,
disassembler=disassembler,
address=address,
max_depth=args.max_depth,
execution_timeout=args.execution_timeout,
create_timeout=args.create_timeout,
enable_iprof=args.enable_iprof,
onchain_storage_access=not args.no_onchain_storage_access,
)
elif args.disassemble:
if args.disassemble:
# or mythril.disassemble(mythril.contracts[0])
if mythril.contracts[0].code:
print("Runtime Disassembly: \n" + mythril.contracts[0].get_easm())
if mythril.contracts[0].creation_code:
print("Disassembly: \n" + mythril.contracts[0].get_creation_easm())
if disassembler.contracts[0].code:
print("Runtime Disassembly: \n" + disassembler.contracts[0].get_easm())
if disassembler.contracts[0].creation_code:
print("Disassembly: \n" + disassembler.contracts[0].get_creation_easm())
elif args.graph or args.fire_lasers:
if not mythril.contracts:
if not disassembler.contracts:
exit_with_error(
args.outform, "input files do not contain any valid contracts"
)
if args.graph:
html = mythril.graph_html(
strategy=args.strategy,
contract=mythril.contracts[0],
address=address,
html = analyzer.graph_html(
contract=analyzer.contracts[0],
enable_physics=args.enable_physics,
phrackify=args.phrack,
max_depth=args.max_depth,
execution_timeout=args.execution_timeout,
create_timeout=args.create_timeout,
enable_iprof=args.enable_iprof,
)
try:
@ -439,18 +443,12 @@ def execute_command(
else:
try:
report = mythril.fire_lasers(
strategy=args.strategy,
address=address,
report = analyzer.fire_lasers(
modules=[m.strip() for m in args.modules.strip().split(",")]
if args.modules
else [],
verbose_report=args.verbose_report,
max_depth=args.max_depth,
execution_timeout=args.execution_timeout,
create_timeout=args.create_timeout,
transaction_count=args.transaction_count,
enable_iprof=args.enable_iprof,
)
outputs = {
"json": report.as_json(),
@ -466,20 +464,12 @@ def execute_command(
elif args.statespace_json:
if not mythril.contracts:
if not analyzer.contracts:
exit_with_error(
args.outform, "input files do not contain any valid contracts"
)
statespace = mythril.dump_statespace(
strategy=args.strategy,
contract=mythril.contracts[0],
address=address,
max_depth=args.max_depth,
execution_timeout=args.execution_timeout,
create_timeout=args.create_timeout,
enable_iprof=args.enable_iprof,
)
statespace = analyzer.dump_statespace(contract=analyzer.contracts[0])
try:
with open(args.statespace_json, "w") as f:
@ -515,20 +505,27 @@ def parse_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> Non
validate_args(parser, args)
try:
quick_commands(args)
mythril = set_config(args)
leveldb_search(mythril, args)
config = set_config(args)
leveldb_search(config, args)
dissasembler = MythrilDisassembler(
eth=config.eth,
solc_version=args.solv,
solc_args=args.solc_args,
enable_online_lookup=args.query_signature,
)
if args.truffle:
try:
mythril.analyze_truffle_project(args)
dissasembler.analyze_truffle_project(args)
except FileNotFoundError:
print(
"Build directory not found. Make sure that you start the analysis from the project root, and that 'truffle compile' has executed successfully."
)
sys.exit()
address = get_code(mythril, args)
execute_command(mythril=mythril, address=address, parser=parser, args=args)
address = get_code(dissasembler, args)
execute_command(
disassembler=dissasembler, address=address, parser=parser, args=args
)
except CriticalError as ce:
exit_with_error(args.outform, str(ce))
except Exception:

@ -136,7 +136,7 @@ def get_callee_account(
log.debug("Attempting to load dependency")
try:
code = dynamic_loader.dynld(environment.active_account.address, callee_address)
code = dynamic_loader.dynld(callee_address)
except ValueError as error:
log.debug("Unable to execute dynamic loader because: {}".format(str(error)))
raise error

@ -1097,7 +1097,7 @@ class Instruction:
return [global_state]
try:
code = self.dynamic_loader.dynld(environment.active_account.address, addr)
code = self.dynamic_loader.dynld(addr)
except (ValueError, AttributeError) as e:
log.debug("error accessing contract storage due to: " + str(e))
state.stack.append(global_state.new_bitvec("extcodesize_" + str(addr), 256))

@ -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
"""

@ -1,4 +1,5 @@
from mythril.laser.ethereum.svm import LaserEVM
from mythril.laser.ethereum.plugins.plugin import LaserPlugin
from time import time
import matplotlib.pyplot as plt
import logging
@ -6,7 +7,8 @@ import logging
log = logging.getLogger(__name__)
class BenchmarkPlugin:
# TODO: introduce dependency on coverage plugin
class BenchmarkPlugin(LaserPlugin):
"""Benchmark Plugin
This plugin aggregates the following information:

@ -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)

@ -1,6 +1,7 @@
from mythril.laser.ethereum.state.annotation import StateAnnotation
from mythril.laser.ethereum.svm import LaserEVM
from mythril.laser.ethereum.plugins.signals import PluginSkipWorldState
from mythril.laser.ethereum.plugins.plugin import LaserPlugin
from mythril.laser.ethereum.state.global_state import GlobalState
from mythril.laser.ethereum.transaction.transaction_models import (
ContractCreationTransaction,
@ -17,7 +18,7 @@ class MutationAnnotation(StateAnnotation):
pass
class MutationPruner:
class MutationPruner(LaserPlugin):
"""Mutation pruner plugin
Let S be a world state from which T is a symbolic transaction, and S' is the resulting world state.

@ -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,4 +1,6 @@
from abc import ABC, abstractmethod
from typing import List
from mythril.laser.ethereum.state.global_state import GlobalState
class BasicSearchStrategy(ABC):
@ -7,7 +9,7 @@ class BasicSearchStrategy(ABC):
__slots__ = "work_list", "max_depth"
def __init__(self, work_list, max_depth):
self.work_list = work_list
self.work_list = work_list # type: List[GlobalState]
self.max_depth = max_depth
def __iter__(self):

@ -72,8 +72,6 @@ class LaserEVM:
self.world_state = world_state
self.open_states = [world_state]
self.coverage = {} # type: Dict[str, Tuple[int, List[bool]]]
self.total_states = 0
self.dynamic_loader = dynamic_loader
@ -97,6 +95,10 @@ class LaserEVM:
self._add_world_state_hooks = [] # type: List[Callable]
self._execute_state_hooks = [] # type: List[Callable]
self._start_sym_trans_hooks = [] # type: List[Callable]
self._stop_sym_trans_hooks = [] # type: List[Callable]
self._start_sym_exec_hooks = [] # type: List[Callable]
self._stop_sym_exec_hooks = [] # type: List[Callable]
@ -158,10 +160,6 @@ class LaserEVM:
len(self.edges),
self.total_states,
)
for code, coverage in self.coverage.items():
cov = sum(coverage[1]) / float(coverage[0]) * 100
log.info("Achieved {:.2f}% coverage for code: {}".format(cov, code))
if self.iprof is not None:
log.info("Instruction Statistics:\n{}".format(self.iprof))
@ -170,42 +168,25 @@ class LaserEVM:
hook()
def _execute_transactions(self, address):
"""This function executes multiple transactions on the address based on
the coverage.
"""This function executes multiple transactions on the address
:param address: Address of the contract
:return:
"""
self.coverage = {}
for i in range(self.transaction_count):
initial_coverage = self._get_covered_instructions()
self.time = datetime.now()
log.info(
"Starting message call transaction, iteration: {}, {} initial states".format(
i, len(self.open_states)
)
)
for hook in self._start_sym_trans_hooks:
hook()
execute_message_call(self, address)
end_coverage = self._get_covered_instructions()
log.info(
"Number of new instructions covered in tx %d: %d"
% (i, end_coverage - initial_coverage)
)
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
for hook in self._stop_sym_trans_hooks:
hook()
def exec(self, create=False, track_gas=False) -> Union[List[GlobalState], None]:
"""
@ -284,7 +265,6 @@ class LaserEVM:
self._execute_pre_hook(op_code, global_state)
try:
self._measure_coverage(global_state)
new_global_states = Instruction(
op_code, self.dynamic_loader, self.iprof
).evaluate(global_state)
@ -389,23 +369,6 @@ class LaserEVM:
return new_global_states
def _measure_coverage(self, global_state: GlobalState) -> None:
"""
:param global_state:
"""
code = global_state.environment.code.bytecode
number_of_instructions = len(global_state.environment.code.instruction_list)
instruction_index = global_state.mstate.pc
if code not in self.coverage.keys():
self.coverage[code] = (
number_of_instructions,
[False] * number_of_instructions,
)
self.coverage[code][1][instruction_index] = True
def manage_cfg(self, opcode: str, new_states: List[GlobalState]) -> None:
"""
@ -527,6 +490,10 @@ class LaserEVM:
self._start_sym_exec_hooks.append(hook)
elif hook_type == "stop_sym_exec":
self._stop_sym_exec_hooks.append(hook)
elif hook_type == "start_sym_trans":
self._start_sym_trans_hooks.append(hook)
elif hook_type == "stop_sym_trans":
self._stop_sym_trans_hooks.append(hook)
else:
raise ValueError(
"Invalid hook type %s. Must be one of {add_world_state}", hook_type

@ -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),
}

@ -0,0 +1,4 @@
from .mythril_disassembler import MythrilDisassembler
from .mythril_analyzer import MythrilAnalyzer
from .mythril_config import MythrilConfig
from .mythril_leveldb import MythrilLevelDB

@ -0,0 +1,173 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import logging
import traceback
from typing import Optional, List
from . import MythrilDisassembler
from mythril.support.source_support import Source
from mythril.support.loader import DynLoader
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, Issue
from mythril.ethereum.evmcontract import EVMContract
from mythril.laser.smt import SolverStatistics
from mythril.support.start_time import StartTime
log = logging.getLogger(__name__)
class MythrilAnalyzer:
"""
The Mythril Analyzer class
Responsible for the analysis of the smart contracts
"""
def __init__(
self,
disassembler: MythrilDisassembler,
requires_dynld: bool = False,
onchain_storage_access: bool = True,
strategy: str = "dfs",
address: Optional[str] = None,
max_depth: Optional[int] = None,
execution_timeout: Optional[int] = None,
create_timeout: Optional[int] = None,
enable_iprof: bool = False,
):
"""
:param disassembler: The MythrilDisassembler class
:param requires_dynld: whether dynamic loading should be done or not
:param onchain_storage_access: Whether onchain access should be done or not
"""
self.eth = disassembler.eth
self.contracts = disassembler.contracts or [] # type: List[EVMContract]
self.enable_online_lookup = disassembler.enable_online_lookup
self.dynld = requires_dynld
self.onchain_storage_access = onchain_storage_access
self.strategy = strategy
self.address = address
self.max_depth = max_depth
self.execution_timeout = execution_timeout
self.create_timeout = create_timeout
self.enable_iprof = enable_iprof
def dump_statespace(self, contract: EVMContract = None) -> str:
"""
Returns serializable statespace of the contract
:param contract: The Contract on which the analysis should be done
:return: The serialized state space
"""
sym = SymExecWrapper(
contract or self.contracts[0],
self.address,
self.strategy,
dynloader=DynLoader(
self.eth,
storage_loading=self.onchain_storage_access,
contract_loading=self.dynld,
),
max_depth=self.max_depth,
execution_timeout=self.execution_timeout,
create_timeout=self.create_timeout,
enable_iprof=self.enable_iprof,
)
return get_serializable_statespace(sym)
def graph_html(
self,
contract: EVMContract = None,
enable_physics: bool = False,
phrackify: bool = False,
transaction_count: Optional[int] = None,
) -> str:
"""
:param contract: The Contract on which the analysis should be done
:param enable_physics: If true then enables the graph physics simulation
:param phrackify: If true generates Phrack-style call graph
:param transaction_count: The amount of transactions to be executed
:return: The generated graph in html format
"""
sym = SymExecWrapper(
contract or self.contracts[0],
self.address,
self.strategy,
dynloader=DynLoader(
self.eth,
storage_loading=self.onchain_storage_access,
contract_loading=self.dynld,
),
max_depth=self.max_depth,
execution_timeout=self.execution_timeout,
transaction_count=transaction_count,
create_timeout=self.create_timeout,
enable_iprof=self.enable_iprof,
)
return generate_graph(sym, physics=enable_physics, phrackify=phrackify)
def fire_lasers(
self,
modules: Optional[List[str]] = None,
verbose_report: bool = False,
transaction_count: Optional[int] = None,
) -> Report:
"""
:param modules: The analysis modules which should be executed
:param verbose_report: Gives out the transaction sequence of the vulnerability
:param transaction_count: The amount of transactions to be executed
:return: The Report class which contains the all the issues/vulnerabilities
"""
all_issues = [] # type: List[Issue]
SolverStatistics().enabled = True
exceptions = []
for contract in self.contracts:
StartTime() # Reinitialize start time for new contracts
try:
sym = SymExecWrapper(
contract,
self.address,
self.strategy,
dynloader=DynLoader(
self.eth,
storage_loading=self.onchain_storage_access,
contract_loading=self.dynld,
),
max_depth=self.max_depth,
execution_timeout=self.execution_timeout,
create_timeout=self.create_timeout,
transaction_count=transaction_count,
modules=modules,
compulsory_statespace=False,
enable_iprof=self.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

@ -0,0 +1,226 @@
import codecs
import logging
import os
import platform
import re
from pathlib import Path
from shutil import copyfile
from configparser import ConfigParser
from typing import Optional
from mythril.exceptions import CriticalError
from mythril.ethereum.interface.rpc.client import EthJsonRpc
from mythril.ethereum.interface.leveldb.client import EthLevelDB
log = logging.getLogger(__name__)
class MythrilConfig:
"""
The Mythril Analyzer class
Responsible for setup of the mythril environment
"""
def __init__(self):
self.mythril_dir = self._init_mythril_dir()
self.config_path = os.path.join(self.mythril_dir, "config.ini")
self.leveldb_dir = None
self._init_config()
self.eth = None # type: Optional[EthJsonRpc]
self.eth_db = None # type: Optional[EthLevelDB]
@staticmethod
def _init_mythril_dir() -> str:
"""
Initializes the mythril dir and config.ini file
:return: The mythril dir's path
"""
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.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.
Defaults:-
- Default LevelDB path is specified based on OS
- dynamic loading is set to infura by default in the file
This function also sets self.leveldb_dir path
"""
leveldb_default_path = self._get_default_leveldb_path()
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_default_path)
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_default_path
)
self.leveldb_dir = os.path.expanduser(leveldb_dir)
@staticmethod
def _get_default_leveldb_path() -> str:
"""
Returns the LevelDB path
:return: The LevelDB path
"""
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")
return os.path.join(leveldb_fallback_dir, "geth", "chaindata")
@staticmethod
def _add_default_options(config: ConfigParser) -> None:
"""
Adds defaults option to config.ini
:param config: The config file object
:return: None
"""
config.add_section("defaults")
@staticmethod
def _add_leveldb_option(config: ConfigParser, leveldb_fallback_dir: str) -> None:
"""
Sets a default leveldb path in .mythril/config.ini file
:param config: The config file object
:param leveldb_fallback_dir: The leveldb dir to use by default for searches
:return: None
"""
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: ConfigParser) -> None:
"""
Sets the dynamic loading config option in .mythril/config.ini file
:param config: The config file object
:return: None
"""
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 set_api_leveldb(self, leveldb_path: str) -> None:
"""
"""
self.eth_db = EthLevelDB(leveldb_path)
def set_api_rpc_infura(self) -> None:
"""Set the RPC mode to INFURA on Mainnet."""
log.info("Using INFURA Main Net for RPC queries")
self.eth = EthJsonRpc("mainnet.infura.io", 443, True)
def set_api_rpc(self, rpc: str = None, rpctls: bool = False) -> None:
"""
Sets the RPC mode to either of ganache or infura
:param rpc: either of the strings - ganache, infura-mainnet, infura-rinkeby, infura-kovan, infura-ropsten
"""
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:
log.info("Using RPC settings: %s" % str(rpcconfig))
self.eth = EthJsonRpc(rpcconfig[0], int(rpcconfig[1]), rpcconfig[2])
else:
raise CriticalError("Invalid RPC settings, check help for details.")
def set_api_rpc_localhost(self) -> None:
"""Set the RPC mode to a local instance."""
log.info("Using default RPC settings: http://localhost:8545")
self.eth = EthJsonRpc("localhost", 8545)
def set_api_from_config_path(self) -> None:
"""Set the RPC mode based on a given config file."""
config = ConfigParser(allow_no_value=False)
# TODO: Remove this after this issue https://github.com/python/mypy/issues/2427 is closed
config.optionxform = str # type:ignore
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"
self._set_rpc(dynamic_loading)
def _set_rpc(self, rpc_type: str) -> None:
"""
Sets rpc based on the type
:param rpc_type: The type of connection: like infura, ganache, localhost
:return:
"""
if rpc_type == "infura":
self.set_api_rpc_infura()
elif rpc_type == "localhost":
self.set_api_rpc_localhost()
else:
self.set_api_rpc(rpc_type)

@ -0,0 +1,303 @@
import logging
import re
import solc
import os
from ethereum import utils
from solc.exceptions import SolcError
from typing import List, Tuple, Optional
from mythril.ethereum import util
from mythril.ethereum.interface.rpc.client import EthJsonRpc
from mythril.exceptions import CriticalError, CompilerError, NoContractFoundError
from mythril.support import signatures
from mythril.support.truffle import analyze_truffle_project
from mythril.ethereum.evmcontract import EVMContract
from mythril.ethereum.interface.rpc.exceptions import ConnectionError
from mythril.solidity.soliditycontract import SolidityContract, get_contracts_from_file
log = logging.getLogger(__name__)
class MythrilDisassembler:
"""
The Mythril Disassembler class
Responsible for generating disassembly of smart contracts
- Compiles solc code from file/onchain
- Can also be used to access onchain storage data
"""
def __init__(
self,
eth: Optional[EthJsonRpc] = None,
solc_version: str = None,
solc_args: str = None,
enable_online_lookup: bool = False,
) -> None:
self.solc_binary = self._init_solc_binary(solc_version)
self.solc_args = solc_args
self.eth = eth
self.enable_online_lookup = enable_online_lookup
self.sigs = signatures.SignatureDB(enable_online_lookup=enable_online_lookup)
self.contracts = [] # type: List[EVMContract]
@staticmethod
def _init_solc_binary(version: str) -> str:
"""
Only proper versions are supported. No nightlies, commits etc (such as available in remix).
:param version: Version of the solc binary required
:return: The solc binary of the corresponding version
"""
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 load_from_bytecode(
self, code: str, bin_runtime: bool = False, address: Optional[str] = None
) -> Tuple[str, EVMContract]:
"""
Returns the address and the contract class for the given bytecode
:param code: Bytecode
:param bin_runtime: Whether the code is runtime code or creation code
:param address: address of contract
:return: tuple(address, Contract class)
"""
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: str) -> Tuple[str, EVMContract]:
"""
Returns the contract given it's on chain address
:param address: The on chain address of a contract
:return: tuple(address, contract)
"""
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))
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: List[str]
) -> Tuple[str, List[SolidityContract]]:
"""
:param solidity_files: List of solidity_files
:return: tuple of address, contract class list
"""
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 analyze_truffle_project(self, *args, **kwargs) -> None:
"""
:param args:
:param kwargs:
:return:
"""
analyze_truffle_project(
self.sigs, *args, **kwargs
) # just passthru by passing signatures for now
@staticmethod
def hash_for_function_signature(func: str) -> str:
"""
Return function names corresponding signature hash
:param func: function name
:return: Its hash signature
"""
return "0x%s" % utils.sha3(func)[:4].hex()
def get_state_variable_from_storage(
self, address: str, params: Optional[List[str]] = None
) -> str:
"""
Get variables from the storage
:param address: The contract address
:param params: The list of parameters
param types: [position, length] or ["mapping", position, key1, key2, ... ]
or [position, length, array]
:return: The corresponding storage slot and its value
"""
params = params or []
(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)

@ -0,0 +1,49 @@
import re
from mythril.exceptions import CriticalError
class MythrilLevelDB:
"""
Class which does search operations on leveldb
There are two DBs
1) Key value pairs of hashes and it's corresponding address
2) The LevelDB Trie
"""
def __init__(self, leveldb):
"""
:param leveldb: Leveldb path
"""
self.leveldb = leveldb
def search_db(self, search):
"""
Searches the corresponding code
:param search: The code part to be searched
"""
def search_callback(_, address, balance):
"""
:param _:
:param address: The address of the contract with the code in search
:param balance: The balance of the corresponding contract
"""
print("Address: " + address + ", balance: " + str(balance))
try:
self.leveldb.search(search, search_callback)
except SyntaxError:
raise CriticalError("Syntax error in search expression.")
def contract_hash_to_address(self, contract_hash):
"""
Returns address of the corresponding hash by searching the leveldb
:param contract_hash: Hash to be searched
"""
if not re.match(r"0x[a-fA-F0-9]{64}", contract_hash):
raise CriticalError("Invalid address hash. Expected format is '0x...'.")
print(self.leveldb.contract_hash_to_address(contract_hash))

@ -58,17 +58,15 @@ class DynLoader:
return data
def dynld(self, contract_address, dependency_address):
def dynld(self, dependency_address):
"""
:param contract_address:
:param dependency_address:
:return:
"""
if not self.contract_loading:
raise ValueError("Cannot load contract when contract_loading flag is false")
log.debug("Dynld at contract " + contract_address + ": " + dependency_address)
log.debug("Dynld at contract " + dependency_address)
# Ensure that dependency_address is the correct length, with 0s prepended as needed.
dependency_address = (

@ -4,4 +4,4 @@ This file is suitable for sourcing inside POSIX shell, e.g. bash as well
as for importing into Python.
"""
VERSION = "v0.20.2" # NOQA
VERSION = "v0.20.3" # NOQA

@ -27,3 +27,4 @@ rlp>=1.0.1
transaction>=2.2.1
z3-solver-mythril>=4.8.4.1
pysha3
matplotlib

@ -97,6 +97,7 @@ setup(
"configparser>=3.5.0",
"persistent>=4.2.0",
"ethereum-input-decoder>=0.2.2",
"matplotlib",
],
tests_require=["mypy", "pytest>=3.6.0", "pytest_mock", "pytest-cov"],
python_requires=">=3.5",

@ -1,5 +1,5 @@
from mythril.analysis.callgraph import generate_graph
from mythril.analysis.symbolic import SymExecWrapper
from mythril.mythril import MythrilAnalyzer, MythrilDisassembler
from mythril.ethereum import util
from mythril.solidity.soliditycontract import EVMContract
from tests import (
@ -22,16 +22,17 @@ class GraphTest(BaseTestCase):
)
contract = EVMContract(input_file.read_text())
sym = SymExecWrapper(
contract,
address=(util.get_indexed_address(0)),
disassembler = MythrilDisassembler()
disassembler.contracts.append(contract)
analyzer = MythrilAnalyzer(
disassembler=disassembler,
strategy="dfs",
transaction_count=1,
execution_timeout=5,
max_depth=30,
address=(util.get_indexed_address(0)),
)
html = generate_graph(sym)
html = analyzer.graph_html(transaction_count=1)
output_current.write_text(html)
lines_expected = re.findall(

@ -1,4 +1,4 @@
from mythril.mythril import Mythril
from mythril.mythril import MythrilDisassembler
from mythril.laser.ethereum.transaction import execute_contract_creation
from mythril.ethereum import util
import mythril.laser.ethereum.svm as svm
@ -13,7 +13,7 @@ from mythril.analysis.symbolic import SymExecWrapper
def test_create():
contract = SolidityContract(
str(tests.TESTDATA_INPUTS_CONTRACTS / "calls.sol"),
solc_binary=Mythril._init_solc_binary("0.5.0"),
solc_binary=MythrilDisassembler._init_solc_binary("0.5.0"),
)
laser_evm = svm.LaserEVM({})
@ -37,7 +37,7 @@ def test_create():
def test_sym_exec():
contract = SolidityContract(
str(tests.TESTDATA_INPUTS_CONTRACTS / "calls.sol"),
solc_binary=Mythril._init_solc_binary("0.5.0"),
solc_binary=MythrilDisassembler._init_solc_binary("0.5.0"),
)
sym = SymExecWrapper(

@ -0,0 +1,31 @@
from pathlib import Path
from mythril.mythril import MythrilDisassembler, MythrilAnalyzer
from mythril.analysis.report import Issue
from mock import patch
@patch("mythril.analysis.report.Issue.add_code_info", return_value=None)
@patch(
"mythril.mythril.mythril_analyzer.fire_lasers",
return_value=[Issue("", "", "234", "101", "title", "0x02445")],
)
@patch("mythril.mythril.mythril_analyzer.SymExecWrapper", return_value=None)
def test_fire_lasers(mock_sym, mock_fire_lasers, mock_code_info):
disassembler = MythrilDisassembler(eth=None)
disassembler.load_from_solidity(
[
str(
(
Path(__file__).parent.parent / "testdata/input_contracts/origin.sol"
).absolute()
)
]
)
analyzer = MythrilAnalyzer(disassembler, strategy="dfs")
issues = analyzer.fire_lasers(modules=[]).sorted_issues()
mock_sym.assert_called()
mock_fire_lasers.assert_called()
mock_code_info.assert_called()
assert len(issues) == 1
assert issues[0]["swc-id"] == "101"

@ -0,0 +1,58 @@
import pytest
from configparser import ConfigParser
from pathlib import Path
from mythril.mythril import MythrilConfig
from mythril.exceptions import CriticalError
def test_config_path_dynloading():
config = MythrilConfig()
config.config_path = str(
Path(__file__).parent.parent / "testdata/mythril_config_inputs/config.ini"
)
config.set_api_from_config_path()
assert config.eth.host == "mainnet.infura.io"
assert config.eth.port == 443
rpc_types_tests = [
("infura", "mainnet.infura.io", 443, True),
("ganache", "localhost", 8545, True),
("infura-rinkeby", "rinkeby.infura.io", 443, True),
("infura-ropsten", "ropsten.infura.io", 443, True),
("infura-kovan", "kovan.infura.io", 443, True),
("localhost", "localhost", 8545, True),
("localhost:9022", "localhost", 9022, True),
("pinfura", None, None, False),
("infura-finkeby", None, None, False),
]
@pytest.mark.parametrize("rpc_type,host,port,success", rpc_types_tests)
def test_set_rpc(rpc_type, host, port, success):
config = MythrilConfig()
if success:
config._set_rpc(rpc_type)
assert config.eth.host == host
assert config.eth.port == port
else:
with pytest.raises(CriticalError):
config._set_rpc(rpc_type)
def test_leveldb_config_addition():
config = ConfigParser()
config.add_section("defaults")
MythrilConfig._add_leveldb_option(config, "test")
assert config.has_section("defaults")
assert config.get("defaults", "leveldb_dir") == "test"
def test_dynld_config_addition():
config = ConfigParser()
config.add_section("defaults")
MythrilConfig._add_dynamic_loading_option(config)
assert config.has_section("defaults")
assert config.get("defaults", "dynamic_loading") == "infura"

@ -0,0 +1,70 @@
import pytest
from mythril.mythril import MythrilConfig, MythrilDisassembler
from mythril.exceptions import CriticalError
storage_test = [
(
["438767356", "3"],
[
"0x1a270efc: 0x0000000000000000000000000000000000000000000000000000000000000000",
"0x1a270efd: 0x0000000000000000000000000000000000000000000000000000000000000000",
"0x1a270efe: 0x0000000000000000000000000000000000000000000000000000000000000000",
],
),
(
["mapping", "4588934759847", "1", "2"],
[
"0x7e523d5aeb10cdb378b0b1f76138c28063a2cb9ec8ff710f42a0972f4d53cf44: "
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0xba36da34ceec88853a2ebdde88e023c6919b90348f41e8905b422dc9ce22301c: "
"0x0000000000000000000000000000000000000000000000000000000000000000",
],
),
(
["mapping", "4588934759847", "10"],
[
"45998575720532480608987132552042185415362901038635143236141343153058112000553: "
"0x0000000000000000000000000000000000000000000000000000000000000000"
],
),
(
["4588934759847", "1", "array"],
[
"30699902832541380821728647136767910246735388184559883985790189062258823875816: "
"0x0000000000000000000000000000000000000000000000000000000000000000"
],
),
]
@pytest.mark.parametrize("params,ans", storage_test)
def test_get_data_from_storage(params, ans):
config = MythrilConfig()
config.set_api_rpc_infura()
disassembler = MythrilDisassembler(eth=config.eth, solc_version="0.4.23")
outtext = disassembler.get_state_variable_from_storage(
"0x76799f77587738bfeef09452df215b63d2cfb08a", params
).split("\n")
assert outtext == ans
storage_test_incorrect_params = [
(["1", "2", "3", "4"]),
(["mapping", "1"]),
(["a", "b", "c"]),
]
@pytest.mark.parametrize("params", storage_test_incorrect_params)
def test_get_data_from_storage_incorrect_params(params):
config = MythrilConfig()
config.set_api_rpc_infura()
disassembler = MythrilDisassembler(eth=config.eth, solc_version="0.4.23")
with pytest.raises(CriticalError):
disassembler.get_state_variable_from_storage(
"0x76799f77587738bfeef09452df215b63d2cfb08a", params
)
def test_solc_install():
MythrilDisassembler(eth=None, solc_version="0.4.19")

@ -0,0 +1,51 @@
import io
import pytest
from contextlib import redirect_stdout
from mock import patch
from mythril.mythril import MythrilLevelDB, MythrilConfig
from mythril.exceptions import CriticalError
@patch("mythril.ethereum.interface.leveldb.client.EthLevelDB.search")
@patch("mythril.ethereum.interface.leveldb.client.ETH_DB", return_value=None)
@patch("mythril.ethereum.interface.leveldb.client.LevelDBReader", return_value=None)
@patch("mythril.ethereum.interface.leveldb.client.LevelDBWriter", return_value=None)
def test_leveldb_code_search(mock_leveldb, f1, f2, f3):
config = MythrilConfig()
config.set_api_leveldb("some path")
leveldb_search = MythrilLevelDB(leveldb=config.eth_db)
leveldb_search.search_db("code#PUSH#")
mock_leveldb.assert_called()
@patch("mythril.ethereum.interface.leveldb.client.ETH_DB", return_value=None)
@patch("mythril.ethereum.interface.leveldb.client.LevelDBReader", return_value=None)
@patch("mythril.ethereum.interface.leveldb.client.LevelDBWriter", return_value=None)
def test_leveldb_hash_search_incorrect_input(f1, f2, f3):
config = MythrilConfig()
config.set_api_leveldb("some path")
leveldb_search = MythrilLevelDB(leveldb=config.eth_db)
with pytest.raises(CriticalError):
leveldb_search.contract_hash_to_address("0x23")
@patch(
"mythril.ethereum.interface.leveldb.client.EthLevelDB.contract_hash_to_address",
return_value="0xddbb615cb2ffaff7233d8a6f3601621de94795e1",
)
@patch("mythril.ethereum.interface.leveldb.client.ETH_DB", return_value=None)
@patch("mythril.ethereum.interface.leveldb.client.LevelDBReader", return_value=None)
@patch("mythril.ethereum.interface.leveldb.client.LevelDBWriter", return_value=None)
def test_leveldb_hash_search_correct_input(mock_hash_to_address, f1, f2, f3):
config = MythrilConfig()
config.set_api_leveldb("some path")
leveldb_search = MythrilLevelDB(leveldb=config.eth_db)
f = io.StringIO()
with redirect_stdout(f):
leveldb_search.contract_hash_to_address(
"0x0464e651bcc40de28fc7fcde269218d16850bac9689da5f4a6bd640fd3cdf6aa"
)
out = f.getvalue()
mock_hash_to_address.assert_called()
assert out == "0xddbb615cb2ffaff7233d8a6f3601621de94795e1\n"

@ -1,5 +1,5 @@
from mythril.solidity.soliditycontract import SolidityContract
from mythril.mythril import Mythril
from mythril.mythril import MythrilDisassembler
from mythril.laser.ethereum.state.account import Account
from mythril.laser.ethereum.state.machine_state import MachineState
from mythril.laser.ethereum.state.global_state import GlobalState
@ -84,7 +84,8 @@ class NativeTests(BaseTestCase):
def runTest():
""""""
disassembly = SolidityContract(
"./tests/native_tests.sol", solc_binary=Mythril._init_solc_binary("0.5.0")
"./tests/native_tests.sol",
solc_binary=MythrilDisassembler._init_solc_binary("0.5.0"),
).disassembly
account = Account("0x0000000000000000000000000000000000000000", disassembly)
accounts = {account.address: account}

@ -21,6 +21,13 @@ def _fix_debug_data(json_str):
return json.dumps(read_json, sort_keys=True, indent=4)
def _add_jsonv2_stubs(json_str):
read_json = json.loads(json_str)
for issue in read_json[0]["issues"]:
issue["extra"]["discoveryTime"] = "<DISCOVERY-TIME-DATA>"
return json.dumps(read_json, sort_keys=True, indent=4)
def _generate_report(input_file):
contract = EVMContract(input_file.read_text(), enable_online_lookup=False)
sym = SymExecWrapper(
@ -181,7 +188,9 @@ def test_text_report(reports):
def test_jsonv2_report(reports):
_assert_empty_json(
_get_changed_files_json(
lambda report: _fix_path(report.as_swc_standard_format()).strip(),
lambda report: _fix_path(
_add_jsonv2_stubs(report.as_swc_standard_format())
).strip(),
reports,
".jsonv2",
),

@ -1,6 +1,6 @@
from pathlib import Path
from mythril.mythril import Mythril
from mythril.mythril import MythrilDisassembler
from mythril.solidity.soliditycontract import SolidityContract
from tests import BaseTestCase
@ -11,7 +11,7 @@ class SolidityContractTest(BaseTestCase):
def test_get_source_info_without_name_gets_latest_contract_info(self):
input_file = TEST_FILES / "multi_contracts.sol"
contract = SolidityContract(
str(input_file), solc_binary=Mythril._init_solc_binary("0.5.0")
str(input_file), solc_binary=MythrilDisassembler._init_solc_binary("0.5.0")
)
code_info = contract.get_source_info(142)
@ -25,7 +25,7 @@ class SolidityContractTest(BaseTestCase):
contract = SolidityContract(
str(input_file),
name="Transfer1",
solc_binary=Mythril._init_solc_binary("0.5.0"),
solc_binary=MythrilDisassembler._init_solc_binary("0.5.0"),
)
code_info = contract.get_source_info(142)
@ -39,7 +39,7 @@ class SolidityContractTest(BaseTestCase):
contract = SolidityContract(
str(input_file),
name="AssertFail",
solc_binary=Mythril._init_solc_binary("0.5.0"),
solc_binary=MythrilDisassembler._init_solc_binary("0.5.0"),
)
code_info = contract.get_source_info(70, constructor=True)

@ -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

@ -0,0 +1,2 @@
[defaults]
dynamic_loading = infura

@ -1 +1,148 @@
[{"issues": [{"description": {"head": "The contract executes an external message call.", "tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully."}, "extra": {}, "locations": [{"sourceMap": "661:1:0"}], "severity": "Low", "swcID": "SWC-107", "swcTitle": "Reentrancy"}, {"description": {"head": "The contract executes an external message call.", "tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully."}, "extra": {}, "locations": [{"sourceMap": "779:1:0"}], "severity": "Low", "swcID": "SWC-107", "swcTitle": "Reentrancy"}, {"description": {"head": "The contract executes an external message call.", "tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully."}, "extra": {}, "locations": [{"sourceMap": "858:1:0"}], "severity": "Low", "swcID": "SWC-107", "swcTitle": "Reentrancy"}, {"description": {"head": "A call to a user-supplied address is executed.", "tail": "The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state."}, "extra": {}, "locations": [{"sourceMap": "912:1:0"}], "severity": "Medium", "swcID": "SWC-107", "swcTitle": "Reentrancy"}, {"description": {"head": "The return value of a message call is not checked.", "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states."}, "extra": {}, "locations": [{"sourceMap": "661:1:0"}], "severity": "Low", "swcID": "SWC-104", "swcTitle": "Unchecked Call Return Value"}, {"description": {"head": "The return value of a message call is not checked.", "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states."}, "extra": {}, "locations": [{"sourceMap": "779:1:0"}], "severity": "Low", "swcID": "SWC-104", "swcTitle": "Unchecked Call Return Value"}, {"description": {"head": "The return value of a message call is not checked.", "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states."}, "extra": {}, "locations": [{"sourceMap": "858:1:0"}], "severity": "Low", "swcID": "SWC-104", "swcTitle": "Unchecked Call Return Value"}, {"description": {"head": "The return value of a message call is not checked.", "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states."}, "extra": {}, "locations": [{"sourceMap": "912:1:0"}], "severity": "Low", "swcID": "SWC-104", "swcTitle": "Unchecked Call Return Value"}], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0x7cbb77986c6b1bf6e945cd3fba06d3ea3d28cfc49cdfdc9571ec30703ac5862f"], "sourceType": "raw-bytecode"}]
[
{
"issues": [
{
"description": {
"head": "The contract executes an external message call.",
"tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "661:1:0"
}
],
"severity": "Low",
"swcID": "SWC-107",
"swcTitle": "Reentrancy"
},
{
"description": {
"head": "The contract executes an external message call.",
"tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "779:1:0"
}
],
"severity": "Low",
"swcID": "SWC-107",
"swcTitle": "Reentrancy"
},
{
"description": {
"head": "The contract executes an external message call.",
"tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "858:1:0"
}
],
"severity": "Low",
"swcID": "SWC-107",
"swcTitle": "Reentrancy"
},
{
"description": {
"head": "A call to a user-supplied address is executed.",
"tail": "The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "912:1:0"
}
],
"severity": "Medium",
"swcID": "SWC-107",
"swcTitle": "Reentrancy"
},
{
"description": {
"head": "The return value of a message call is not checked.",
"tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "661:1:0"
}
],
"severity": "Low",
"swcID": "SWC-104",
"swcTitle": "Unchecked Call Return Value"
},
{
"description": {
"head": "The return value of a message call is not checked.",
"tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "779:1:0"
}
],
"severity": "Low",
"swcID": "SWC-104",
"swcTitle": "Unchecked Call Return Value"
},
{
"description": {
"head": "The return value of a message call is not checked.",
"tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "858:1:0"
}
],
"severity": "Low",
"swcID": "SWC-104",
"swcTitle": "Unchecked Call Return Value"
},
{
"description": {
"head": "The return value of a message call is not checked.",
"tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "912:1:0"
}
],
"severity": "Low",
"swcID": "SWC-104",
"swcTitle": "Unchecked Call Return Value"
}
],
"meta": {},
"sourceFormat": "evm-byzantium-bytecode",
"sourceList": [
"0x7cbb77986c6b1bf6e945cd3fba06d3ea3d28cfc49cdfdc9571ec30703ac5862f"
],
"sourceType": "raw-bytecode"
}
]

@ -1 +1,80 @@
[{"issues": [{"description": {"head": "A reachable exception has been detected.", "tail": "It is possible to trigger an exception (opcode 0xfe). Exceptions can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking."}, "extra": {}, "locations": [{"sourceMap": "446:1:0"}], "severity": "Low", "swcID": "SWC-110", "swcTitle": "Assert Violation"}, {"description": {"head": "A reachable exception has been detected.", "tail": "It is possible to trigger an exception (opcode 0xfe). Exceptions can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking."}, "extra": {}, "locations": [{"sourceMap": "484:1:0"}], "severity": "Low", "swcID": "SWC-110", "swcTitle": "Assert Violation"}, {"description": {"head": "A reachable exception has been detected.", "tail": "It is possible to trigger an exception (opcode 0xfe). Exceptions can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking."}, "extra": {}, "locations": [{"sourceMap": "506:1:0"}], "severity": "Low", "swcID": "SWC-110", "swcTitle": "Assert Violation"}, {"description": {"head": "A reachable exception has been detected.", "tail": "It is possible to trigger an exception (opcode 0xfe). Exceptions can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking."}, "extra": {}, "locations": [{"sourceMap": "531:1:0"}], "severity": "Low", "swcID": "SWC-110", "swcTitle": "Assert Violation"}], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0x4a773a86bc6fb269f88bf09bb3094de29b6073cf13b1760e9d01d957f50a9dfd"], "sourceType": "raw-bytecode"}]
[
{
"issues": [
{
"description": {
"head": "A reachable exception has been detected.",
"tail": "It is possible to trigger an exception (opcode 0xfe). Exceptions can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "446:1:0"
}
],
"severity": "Low",
"swcID": "SWC-110",
"swcTitle": "Assert Violation"
},
{
"description": {
"head": "A reachable exception has been detected.",
"tail": "It is possible to trigger an exception (opcode 0xfe). Exceptions can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "484:1:0"
}
],
"severity": "Low",
"swcID": "SWC-110",
"swcTitle": "Assert Violation"
},
{
"description": {
"head": "A reachable exception has been detected.",
"tail": "It is possible to trigger an exception (opcode 0xfe). Exceptions can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "506:1:0"
}
],
"severity": "Low",
"swcID": "SWC-110",
"swcTitle": "Assert Violation"
},
{
"description": {
"head": "A reachable exception has been detected.",
"tail": "It is possible to trigger an exception (opcode 0xfe). Exceptions can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "531:1:0"
}
],
"severity": "Low",
"swcID": "SWC-110",
"swcTitle": "Assert Violation"
}
],
"meta": {},
"sourceFormat": "evm-byzantium-bytecode",
"sourceList": [
"0x4a773a86bc6fb269f88bf09bb3094de29b6073cf13b1760e9d01d957f50a9dfd"
],
"sourceType": "raw-bytecode"
}
]

@ -1 +1,97 @@
[{"issues": [{"description": {"head": "Use of callcode is deprecated.", "tail": "The callcode method executes code of another contract in the context of the caller account. Due to a bug in the implementation it does not persist sender and value over the call. It was therefore deprecated and may be removed in the future. Use the delegatecall method instead."}, "extra": {}, "locations": [{"sourceMap": "618:1:0"}], "severity": "Medium", "swcID": "SWC-111", "swcTitle": "Use of Deprecated Solidity Functions"}, {"description": {"head": "A call to a user-supplied address is executed.", "tail": "The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state."}, "extra": {}, "locations": [{"sourceMap": "1038:1:0"}], "severity": "Medium", "swcID": "SWC-107", "swcTitle": "Reentrancy"}, {"description": {"head": "The return value of a message call is not checked.", "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states."}, "extra": {}, "locations": [{"sourceMap": "618:1:0"}], "severity": "Low", "swcID": "SWC-104", "swcTitle": "Unchecked Call Return Value"}, {"description": {"head": "The return value of a message call is not checked.", "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states."}, "extra": {}, "locations": [{"sourceMap": "849:1:0"}], "severity": "Low", "swcID": "SWC-104", "swcTitle": "Unchecked Call Return Value"}, {"description": {"head": "The return value of a message call is not checked.", "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states."}, "extra": {}, "locations": [{"sourceMap": "1038:1:0"}], "severity": "Low", "swcID": "SWC-104", "swcTitle": "Unchecked Call Return Value"}], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0x6daec61d05d8f1210661e7e7d1ed6d72bd6ade639398fac1e867aff50abfc1c1"], "sourceType": "raw-bytecode"}]
[
{
"issues": [
{
"description": {
"head": "Use of callcode is deprecated.",
"tail": "The callcode method executes code of another contract in the context of the caller account. Due to a bug in the implementation it does not persist sender and value over the call. It was therefore deprecated and may be removed in the future. Use the delegatecall method instead."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "618:1:0"
}
],
"severity": "Medium",
"swcID": "SWC-111",
"swcTitle": "Use of Deprecated Solidity Functions"
},
{
"description": {
"head": "A call to a user-supplied address is executed.",
"tail": "The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "1038:1:0"
}
],
"severity": "Medium",
"swcID": "SWC-107",
"swcTitle": "Reentrancy"
},
{
"description": {
"head": "The return value of a message call is not checked.",
"tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "618:1:0"
}
],
"severity": "Low",
"swcID": "SWC-104",
"swcTitle": "Unchecked Call Return Value"
},
{
"description": {
"head": "The return value of a message call is not checked.",
"tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "849:1:0"
}
],
"severity": "Low",
"swcID": "SWC-104",
"swcTitle": "Unchecked Call Return Value"
},
{
"description": {
"head": "The return value of a message call is not checked.",
"tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "1038:1:0"
}
],
"severity": "Low",
"swcID": "SWC-104",
"swcTitle": "Unchecked Call Return Value"
}
],
"meta": {},
"sourceFormat": "evm-byzantium-bytecode",
"sourceList": [
"0x6daec61d05d8f1210661e7e7d1ed6d72bd6ade639398fac1e867aff50abfc1c1"
],
"sourceType": "raw-bytecode"
}
]

@ -1 +1,29 @@
[{"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": {}, "locations": [{"sourceMap": "142:1:0"}], "severity": "High", "swcID": "SWC-105", "swcTitle": "Unprotected Ether Withdrawal"}], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0xbc9c3d9db56d20cf4ca3b6fd88ff9215cf728a092cca1ed8edb83272b933ff5b"], "sourceType": "raw-bytecode"}]
[
{
"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": "142:1:0"
}
],
"severity": "High",
"swcID": "SWC-105",
"swcTitle": "Unprotected Ether Withdrawal"
}
],
"meta": {},
"sourceFormat": "evm-byzantium-bytecode",
"sourceList": [
"0xbc9c3d9db56d20cf4ca3b6fd88ff9215cf728a092cca1ed8edb83272b933ff5b"
],
"sourceType": "raw-bytecode"
}
]

@ -1 +1,29 @@
[{"issues": [{"description": {"head": "Use of tx.origin is deprecated.", "tail": "The smart contract retrieves the transaction origin (tx.origin) using msg.origin. Use of msg.origin is deprecated and the instruction may be removed in the future. Use msg.sender instead.\nSee also: https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin"}, "extra": {}, "locations": [{"sourceMap": "317:1:0"}], "severity": "Medium", "swcID": "SWC-111", "swcTitle": "Use of Deprecated Solidity Functions"}], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0x25b20ef097dfc0aa56a932c4e09f06ee02a69c005767df86877f48c6c2412f03"], "sourceType": "raw-bytecode"}]
[
{
"issues": [
{
"description": {
"head": "Use of tx.origin is deprecated.",
"tail": "The smart contract retrieves the transaction origin (tx.origin) using msg.origin. Use of msg.origin is deprecated and the instruction may be removed in the future. Use msg.sender instead.\nSee also: https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin"
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "317:1:0"
}
],
"severity": "Medium",
"swcID": "SWC-111",
"swcTitle": "Use of Deprecated Solidity Functions"
}
],
"meta": {},
"sourceFormat": "evm-byzantium-bytecode",
"sourceList": [
"0x25b20ef097dfc0aa56a932c4e09f06ee02a69c005767df86877f48c6c2412f03"
],
"sourceType": "raw-bytecode"
}
]

@ -1 +1,46 @@
[{"issues": [{"description": {"head": "The binary subtraction can underflow.", "tail": "The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion."}, "extra": {}, "locations": [{"sourceMap": "567:1:0"}], "severity": "High", "swcID": "SWC-101", "swcTitle": "Integer Overflow and Underflow"}, {"description": {"head": "The binary subtraction can underflow.", "tail": "The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion."}, "extra": {}, "locations": [{"sourceMap": "649:1:0"}], "severity": "High", "swcID": "SWC-101", "swcTitle": "Integer Overflow and Underflow"}], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0xf230bec502569e8b7e7737616d0ad0f200c436624e3c223e5398c0615cd2d6b9"], "sourceType": "raw-bytecode"}]
[
{
"issues": [
{
"description": {
"head": "The binary subtraction can underflow.",
"tail": "The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "567:1:0"
}
],
"severity": "High",
"swcID": "SWC-101",
"swcTitle": "Integer Overflow and Underflow"
},
{
"description": {
"head": "The binary subtraction can underflow.",
"tail": "The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "649:1:0"
}
],
"severity": "High",
"swcID": "SWC-101",
"swcTitle": "Integer Overflow and Underflow"
}
],
"meta": {},
"sourceFormat": "evm-byzantium-bytecode",
"sourceList": [
"0xf230bec502569e8b7e7737616d0ad0f200c436624e3c223e5398c0615cd2d6b9"
],
"sourceType": "raw-bytecode"
}
]

@ -1 +1,63 @@
[{"issues": [{"description": {"head": "The contract executes an external message call.", "tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully."}, "extra": {}, "locations": [{"sourceMap": "196:1:0"}], "severity": "Low", "swcID": "SWC-107", "swcTitle": "Reentrancy"}, {"description": {"head": "The contract executes an external message call.", "tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully."}, "extra": {}, "locations": [{"sourceMap": "285:1:0"}], "severity": "Low", "swcID": "SWC-107", "swcTitle": "Reentrancy"}, {"description": {"head": "The return value of a message call is not checked.", "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states."}, "extra": {}, "locations": [{"sourceMap": "285:1:0"}], "severity": "Low", "swcID": "SWC-104", "swcTitle": "Unchecked Call Return Value"}], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0xb191cf6cc0d8cc37a91c9d88019cc011b932169fb5776df616e2bb9cd93b4039"], "sourceType": "raw-bytecode"}]
[
{
"issues": [
{
"description": {
"head": "The contract executes an external message call.",
"tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "196:1:0"
}
],
"severity": "Low",
"swcID": "SWC-107",
"swcTitle": "Reentrancy"
},
{
"description": {
"head": "The contract executes an external message call.",
"tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "285:1:0"
}
],
"severity": "Low",
"swcID": "SWC-107",
"swcTitle": "Reentrancy"
},
{
"description": {
"head": "The return value of a message call is not checked.",
"tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "285:1:0"
}
],
"severity": "Low",
"swcID": "SWC-104",
"swcTitle": "Unchecked Call Return Value"
}
],
"meta": {},
"sourceFormat": "evm-byzantium-bytecode",
"sourceList": [
"0xb191cf6cc0d8cc37a91c9d88019cc011b932169fb5776df616e2bb9cd93b4039"
],
"sourceType": "raw-bytecode"
}
]

@ -1 +1,29 @@
[{"issues": [{"description": {"head": "The contract can be killed by anyone.", "tail": "Anyone can kill this contract and withdraw its balance to an arbitrary address."}, "extra": {}, "locations": [{"sourceMap": "146:1:0"}], "severity": "High", "swcID": "SWC-106", "swcTitle": "Unprotected SELFDESTRUCT Instruction"}], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0x2fb801366b61a05b30550481a1c8f7d5f20de0b93d9f2f2ce2b28c4e322033c9"], "sourceType": "raw-bytecode"}]
[
{
"issues": [
{
"description": {
"head": "The contract can be killed by anyone.",
"tail": "Anyone can kill this contract and withdraw its balance to an arbitrary address."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "146:1:0"
}
],
"severity": "High",
"swcID": "SWC-106",
"swcTitle": "Unprotected SELFDESTRUCT Instruction"
}
],
"meta": {},
"sourceFormat": "evm-byzantium-bytecode",
"sourceList": [
"0x2fb801366b61a05b30550481a1c8f7d5f20de0b93d9f2f2ce2b28c4e322033c9"
],
"sourceType": "raw-bytecode"
}
]

@ -1 +1,46 @@
[{"issues": [{"description": {"head": "The binary subtraction can underflow.", "tail": "The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion."}, "extra": {}, "locations": [{"sourceMap": "567:1:0"}], "severity": "High", "swcID": "SWC-101", "swcTitle": "Integer Overflow and Underflow"}, {"description": {"head": "The binary subtraction can underflow.", "tail": "The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion."}, "extra": {}, "locations": [{"sourceMap": "649:1:0"}], "severity": "High", "swcID": "SWC-101", "swcTitle": "Integer Overflow and Underflow"}], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0xabef56740bf7795a9f8732e4781ebd27f2977f8a4997e3ff11cee79a4ba6c0ce"], "sourceType": "raw-bytecode"}]
[
{
"issues": [
{
"description": {
"head": "The binary subtraction can underflow.",
"tail": "The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "567:1:0"
}
],
"severity": "High",
"swcID": "SWC-101",
"swcTitle": "Integer Overflow and Underflow"
},
{
"description": {
"head": "The binary subtraction can underflow.",
"tail": "The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "649:1:0"
}
],
"severity": "High",
"swcID": "SWC-101",
"swcTitle": "Integer Overflow and Underflow"
}
],
"meta": {},
"sourceFormat": "evm-byzantium-bytecode",
"sourceList": [
"0xabef56740bf7795a9f8732e4781ebd27f2977f8a4997e3ff11cee79a4ba6c0ce"
],
"sourceType": "raw-bytecode"
}
]

@ -12,6 +12,7 @@ commands =
mkdir -p {toxinidir}/tests/testdata/outputs_current_laser_result/
py.test -v \
--junitxml={toxworkdir}/output/{envname}/junit.xml \
--disable-pytest-warnings \
{posargs}
[testenv:py36]
@ -35,6 +36,7 @@ commands =
--cov-report=xml:{toxworkdir}/output/{envname}/coverage.xml \
--cov-report=html:{toxworkdir}/output/{envname}/covhtml \
--junitxml={toxworkdir}/output/{envname}/junit.xml \
--disable-pytest-warnings \
{posargs}

Loading…
Cancel
Save