Security analysis tool for EVM bytecode. Supports smart contracts built for Ethereum, Hedera, Quorum, Vechain, Roostock, Tron and other EVM-compatible blockchains.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
mythril/tests/report_test.py

201 lines
5.8 KiB

from mythril.analysis.report import Report
from mythril.analysis.security import fire_lasers, reset_callback_modules
from mythril.analysis.symbolic import SymExecWrapper
from mythril.ethereum import util
from mythril.solidity.soliditycontract import EVMContract
from multiprocessing import Pool, cpu_count
import pytest
7 years ago
import json
from tests import *
import difflib
def _fix_path(text):
return text.replace(str(TESTDATA), "<TESTDATA>")
7 years ago
def _fix_debug_data(json_str):
read_json = json.loads(json_str)
for issue in read_json["issues"]:
issue["tx_sequence"] = "<TX-DATA>"
return json.dumps(read_json, sort_keys=True, indent=4)
7 years ago
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>"
issue["extra"]["testCase"] = "<TEST-CASE>"
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(
contract,
Balance modelling and symbolic sender variables (#1025) * add actor address to symbolic This will allow us to simulate semi-symbolic transaction senders * add value transfer to transaction global state creation * add proper balance tracking to world state and account * use address value vs string * disable actor address variable * use address directly * allow balance functions with int types * use value as getters since bitvecs aren't hashable * implement correct value transfer for suicide * use actor from actor pool * allow use of Or with *arg pattern * use bitvec instead of strings * add dynamic balance implementation to state datamodels * cleanup svm interface * use balance lambda in suicide op implementation * use bitvec instead of string address * update world state and account creation in symbolic * update tests to include overflow results * apply style rules * ignore previous open states for now * update native test to conform to new laser interface * fix incorrect types in the symbolic virtual machine * allow multiple types as input for address in account * fix type hint in symbolic.py * get int out of string in call * fix type in call op implementation * adapt test_transaction to conform to new laser interface * use static address to keep contracts from trying to enter themselves and make address type more dynamic * update evm test to conform to new laser interface * implement suicide to uncreated account * apply style rules * dynamically create colormap This removes the need for a globally maintained accountlist * get value out of address * add accounts getter * change symbolic test call to be valid with respect to the world state and account apis * remove dependency on globally recorded accounts * fix typing issues * fix type annotations in symbolic.py * fix remaining mypy warnings * simplify assertion check * execute lambda to get balance * make variable name plural * add documentation to svm constructor * use list comprehension to make code cleaner * remove comment * change variable name to plural * remove commented code * change variable name to conform to changed interface
6 years ago
address=0xAFFEAFFEAFFEAFFEAFFEAFFEAFFEAFFEAFFEAFFE,
strategy="dfs",
execution_timeout=30,
transaction_count=1,
)
issues = fire_lasers(sym)
report = Report(contracts=[contract])
for issue in issues:
issue.filename = "test-filename.sol"
report.append_issue(issue)
return report, input_file
@pytest.fixture(scope="module")
def reports():
"""Fixture that analyses all reports."""
reset_callback_modules()
pool = Pool(cpu_count())
input_files = sorted(
[f for f in TESTDATA_INPUTS.iterdir() if f.name != "environments.sol.o"]
)
results = pool.map(_generate_report, input_files)
return results
7 years ago
def _assert_empty(changed_files, postfix):
"""Asserts there are no changed files and otherwise builds error
message."""
message = ""
for input_file in changed_files:
output_expected = (
(TESTDATA_OUTPUTS_EXPECTED / (input_file.name + postfix))
.read_text()
.splitlines(1)
)
output_current = (
(TESTDATA_OUTPUTS_CURRENT / (input_file.name + postfix))
.read_text()
.splitlines(1)
)
difference = "".join(difflib.unified_diff(output_expected, output_current))
message += "Found differing file for input: {} \n Difference: \n {} \n".format(
str(input_file), str(difference)
)
assert message == "", message
def _assert_empty_json(changed_files, postfix=".json"):
"""Asserts there are no changed files and otherwise builds error
message."""
expected = []
actual = []
def ordered(obj):
"""
:param obj:
:return:
"""
if isinstance(obj, dict):
return sorted((k, ordered(v)) for k, v in obj.items())
elif isinstance(obj, list):
return sorted(ordered(x) for x in obj)
else:
return obj
for input_file in changed_files:
output_expected = json.loads(
(TESTDATA_OUTPUTS_EXPECTED / (input_file.name + postfix)).read_text()
)
output_current = json.loads(
(TESTDATA_OUTPUTS_CURRENT / (input_file.name + postfix)).read_text()
)
if not ordered(output_expected) == ordered(output_current):
expected.append(output_expected)
actual.append(output_current)
print("Found difference in {}".format(str(input_file)))
assert expected == actual
def _get_changed_files(postfix, report_builder, reports):
"""Returns a generator for all unexpected changes in generated reports.
:param postfix: The applicable postfix
:param report_builder: serialization function
:param reports: The reports to serialize
:return: Changed files
"""
for report, input_file in reports:
output_expected = TESTDATA_OUTPUTS_EXPECTED / (input_file.name + postfix)
output_current = TESTDATA_OUTPUTS_CURRENT / (input_file.name + postfix)
output_current.write_text(report_builder(report))
if not (output_expected.read_text() == output_current.read_text()):
yield input_file
7 years ago
def _get_changed_files_json(report_builder, reports, postfix=".json"):
def ordered(obj):
"""
:param obj:
:return:
"""
if isinstance(obj, dict):
return sorted((k, ordered(v)) for k, v in obj.items())
elif isinstance(obj, list):
return sorted(ordered(x) for x in obj)
else:
return obj
for report, input_file in reports:
output_expected = TESTDATA_OUTPUTS_EXPECTED / (input_file.name + postfix)
output_current = TESTDATA_OUTPUTS_CURRENT / (input_file.name + postfix)
output_current.write_text(report_builder(report))
if not ordered(json.loads(output_expected.read_text())) == ordered(
json.loads(output_current.read_text())
):
yield input_file
def test_json_report(reports):
_assert_empty_json(
_get_changed_files_json(
lambda report: _fix_path(_fix_debug_data(report.as_json())).strip(), reports
)
)
7 years ago
def test_markdown_report(reports):
_assert_empty(
_get_changed_files(
".markdown", lambda report: _fix_path(report.as_markdown()), reports
),
".markdown",
)
7 years ago
def test_text_report(reports):
_assert_empty(
_get_changed_files(
".text", lambda report: _fix_path(report.as_text()), reports
),
".text",
)
def test_jsonv2_report(reports):
_assert_empty_json(
_get_changed_files_json(
lambda report: _fix_path(
_add_jsonv2_stubs(report.as_swc_standard_format())
).strip(),
reports,
".jsonv2",
),
".jsonv2",
)