Merge laser into mythril

pull/254/head
LieutenantRoger 7 years ago
parent a62ba2470d
commit f14b21e16d
  1. 5
      .circleci/config.yml
  2. 2
      .gitignore
  3. 2
      all_tests.sh
  4. 2
      coverage_report.sh
  5. 2
      mythril/analysis/callgraph.py
  6. 2
      mythril/analysis/modules/integer.py
  7. 2
      mythril/analysis/modules/unchecked_retval.py
  8. 2
      mythril/analysis/ops.py
  9. 2
      mythril/analysis/symbolic.py
  10. 2
      mythril/analysis/traceexplore.py
  11. 2
      mythril/ether/soliditycontract.py
  12. 0
      mythril/laser/__init__.py
  13. 0
      mythril/laser/ethereum/__init__.py
  14. 74
      mythril/laser/ethereum/gascost.py
  15. 92
      mythril/laser/ethereum/helper.py
  16. 0
      mythril/laser/ethereum/modules/__init__.py
  17. 1198
      mythril/laser/ethereum/svm.py
  18. 309
      mythril/laser/ethereum/taint_analysis.py
  19. 2
      mythril/support/truffle.py
  20. 3
      requirements.txt
  21. 7
      setup.py
  22. 3
      tests/__init__.py
  23. 86
      tests/svm_test.py
  24. 29
      tests/taint_mutate_stack_test.py
  25. 36
      tests/taint_record_test.py
  26. 36
      tests/taint_result_test.py
  27. 94
      tests/taint_runner_test.py
  28. 152
      tests/testdata/input_contracts/rubixi.sol
  29. 57499
      tests/testdata/outputs_expected_laser_result/calls.sol.json
  30. 61845
      tests/testdata/outputs_expected_laser_result/ether_send.sol.json
  31. 59235
      tests/testdata/outputs_expected_laser_result/exceptions.sol.json
  32. 73258
      tests/testdata/outputs_expected_laser_result/kinds_of_calls.sol.json
  33. 40884
      tests/testdata/outputs_expected_laser_result/metacoin.sol.json
  34. 11621
      tests/testdata/outputs_expected_laser_result/multi_contracts.sol.json
  35. 24336
      tests/testdata/outputs_expected_laser_result/origin.sol.json
  36. 52771
      tests/testdata/outputs_expected_laser_result/overflow.sol.json
  37. 17652
      tests/testdata/outputs_expected_laser_result/returnvalue.sol.json
  38. 1141641
      tests/testdata/outputs_expected_laser_result/rubixi.sol.json
  39. 8420
      tests/testdata/outputs_expected_laser_result/suicide.sol.json
  40. 52771
      tests/testdata/outputs_expected_laser_result/underflow.sol.json

@ -19,11 +19,6 @@ jobs:
keys: keys:
- tox-env-{{ checksum "/home/mythril/setup.py" }} - tox-env-{{ checksum "/home/mythril/setup.py" }}
- run:
name: Installing mythril tools
command: ./install-mythril-tools.sh laser-ethereum
working_directory: /home
- run: - run:
name: Install tox envs name: Install tox envs
command: tox -vv --notest command: tox -vv --notest

2
.gitignore vendored

@ -171,9 +171,9 @@ $RECYCLE.BIN/
*.rst *.rst
*.lock *.lock
*.svg *.svg
laser*
lol* lol*
.idea* .idea*
coverage_html_report/ coverage_html_report/
tests/testdata/outputs_current/ tests/testdata/outputs_current/
tests/testdata/outputs_current_laser_result/
tests/mythril_dir/signatures.json tests/mythril_dir/signatures.json

@ -8,5 +8,7 @@ echo "Please make sure you are using solc 0.4.21"
rm -rf ./tests/testdata/outputs_current/ rm -rf ./tests/testdata/outputs_current/
mkdir -p ./tests/testdata/outputs_current/ mkdir -p ./tests/testdata/outputs_current/
rm -rf ./tests/testdata/outputs_current_laser_result/
mkdir -p ./tests/testdata/outputs_current_laser_result/
mkdir -p /tmp/test-reports mkdir -p /tmp/test-reports
pytest --junitxml=/tmp/test-reports/junit.xml pytest --junitxml=/tmp/test-reports/junit.xml

@ -5,6 +5,8 @@ echo "Please make sure you are using python 3.6.x"
rm -rf ./tests/testdata/outputs_current/ rm -rf ./tests/testdata/outputs_current/
mkdir -p ./tests/testdata/outputs_current/ mkdir -p ./tests/testdata/outputs_current/
rm -rf ./tests/testdata/outputs_current_laser_result/
mkdir -p ./tests/testdata/outputs_current_laser_result/
rm -rf coverage_html_report rm -rf coverage_html_report
py.test \ py.test \

@ -1,7 +1,7 @@
import re import re
from jinja2 import Environment, PackageLoader, select_autoescape from jinja2 import Environment, PackageLoader, select_autoescape
from laser.ethereum.svm import NodeFlags from mythril.laser.ethereum.svm import NodeFlags
import z3 import z3
default_opts = { default_opts = {

@ -3,7 +3,7 @@ from mythril.analysis import solver
from mythril.analysis.ops import * from mythril.analysis.ops import *
from mythril.analysis.report import Issue from mythril.analysis.report import Issue
from mythril.exceptions import UnsatError from mythril.exceptions import UnsatError
from laser.ethereum.taint_analysis import TaintRunner from mythril.laser.ethereum.taint_analysis import TaintRunner
import re import re
import copy import copy
import logging import logging

@ -1,5 +1,5 @@
from mythril.analysis.report import Issue from mythril.analysis.report import Issue
from laser.ethereum.svm import NodeFlags from mythril.laser.ethereum.svm import NodeFlags
import logging import logging
import re import re

@ -1,6 +1,6 @@
from z3 import * from z3 import *
from enum import Enum from enum import Enum
from laser.ethereum import helper from mythril.laser.ethereum import helper
class VarType(Enum): class VarType(Enum):

@ -1,5 +1,5 @@
from mythril import ether from mythril import ether
from laser.ethereum import svm from mythril.laser.ethereum import svm
import copy import copy
import logging import logging
from .ops import get_variable, SStore, Call, VarType from .ops import get_variable, SStore, Call, VarType

@ -1,5 +1,5 @@
from z3 import Z3Exception, simplify from z3 import Z3Exception, simplify
from laser.ethereum.svm import NodeFlags from mythril.laser.ethereum.svm import NodeFlags
import re import re
colors = [ colors = [

@ -1,4 +1,4 @@
import laser.ethereum.helper as helper import mythril.laser.ethereum.helper as helper
from mythril.ether.ethcontract import ETHContract from mythril.ether.ethcontract import ETHContract
from mythril.ether.util import * from mythril.ether.util import *
from mythril.exceptions import NoContractFoundError from mythril.exceptions import NoContractFoundError

@ -0,0 +1,74 @@
gascost = {
'PUSH': 3,
'DUP': 3,
'SWAP': 3,
'STOP': 0,
'ADD': 3,
'MUL': 5,
'SUB': 3,
'DIV': 5,
'SDIV': 5,
'MOD': 5,
'SMOD': 5,
'ADDMOD': 8,
'MULMOD': 8,
'EXP': 10,
'SIGNEXTEND': 5,
'LT': 3,
'GT': 3,
'SLT': 3,
'SGT': 3,
'EQ': 3,
'ISZERO': 3,
'AND': 3,
'OR': 3,
'XOR': 3,
'NOT': 3,
'BYTE': 3,
'SHA3': 30,
'ADDRESS': 2,
'BALANCE': 400,
'ORIGIN': 2,
'CALLER': 2,
'CALLVALUE': 2,
'CALLDATALOAD': 3,
'CALLDATASIZE': 2,
'CALLDATACOPY': 3,
'CODESIZE': 2,
'CODECOPY': 3,
'GASPRICE': 2,
'EXTCODESIZE': 700,
'EXTCODECOPY': 700,
'BLOCKHASH': 20,
'COINBASE': 2,
'TIMESTAMP': 2,
'NUMBER': 2,
'DIFFICULTY': 2,
'GASLIMIT': 2,
'POP': 2,
'MLOAD': 3,
'MSTORE': 3,
'MSTORE8': 3,
'SLOAD': 50,
'SSTORE': 0,
'JUMP': 8,
'JUMPI': 10,
'PC': 2,
'MSIZE': 2,
'GAS': 2,
'JUMPDEST': 1,
'LOG0': 375,
'LOG1': 750,
'LOG2': 1125,
'LOG3': 1500,
'LOG4': 1875,
'CREATE': 32000,
'CALL': 40,
'CALLCODE': 40,
'RETURN': 0,
'DELEGATECALL': 40,
'CALLBLACKBOX': 40,
'STATICCALL': 40,
'REVERT': 0,
'SUICIDE': 5000,
}

@ -0,0 +1,92 @@
import re
from z3 import *
import logging
TT256 = 2 ** 256
TT256M1 = 2 ** 256 - 1
TT255 = 2 ** 255
def safe_decode(hex_encoded_string):
if (hex_encoded_string.startswith("0x")):
return bytes.fromhex(hex_encoded_string[2:])
else:
return bytes.fromhex(hex_encoded_string)
def to_signed(i):
return i if i < TT255 else i - TT256
def get_instruction_index(instruction_list, address):
index = 0
for instr in instruction_list:
if instr['address'] == address:
return index
index += 1
return None
def get_trace_line(instr, state):
stack = str(state.stack[::-1])
# stack = re.sub("(\d+)", lambda m: hex(int(m.group(1))), stack)
stack = re.sub("\n", "", stack)
return str(instr['address']) + " " + instr['opcode'] + "\tSTACK: " + stack
def pop_bitvec(state):
# pop one element from stack, converting boolean expressions and
# concrete Python variables to BitVecVal
item = state.stack.pop()
if type(item) == BoolRef:
return If(item, BitVecVal(1, 256), BitVecVal(0, 256))
elif type(item) == bool:
if item:
return BitVecVal(1, 256)
else:
return BitVecVal(0, 256)
elif type(item) == int:
return BitVecVal(item, 256)
else:
return simplify(item)
def get_concrete_int(item):
if (type(item) == int):
return item
if (type(item) == BitVecNumRef):
return item.as_long()
return simplify(item).as_long()
def concrete_int_from_bytes(_bytes, start_index):
# logging.debug("-- concrete_int_from_bytes: " + str(_bytes[start_index:start_index+32]))
b = _bytes[start_index:start_index+32]
val = int.from_bytes(b, byteorder='big')
return val
def concrete_int_to_bytes(val):
# logging.debug("concrete_int_to_bytes " + str(val))
if (type(val) == int):
return val.to_bytes(32, byteorder='big')
return (simplify(val).as_long()).to_bytes(32, byteorder='big')

File diff suppressed because it is too large Load Diff

@ -0,0 +1,309 @@
import logging, copy
import mythril.laser.ethereum.helper as helper
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):
""" Returns taint value of stack element at index"""
if index < len(self.stack):
return self.stack[index]
return None
def memory_tainted(self, index):
""" Returns taint value of memory element at index"""
if index in self.memory.keys():
return self.memory[index]
return False
def storage_tainted(self, index):
""" Returns taint value of storage element at index"""
if index in self.storage.keys():
return self.storage[index]
return False
def add_state(self, state):
""" Adds state with this taint record """
self.states.append(state)
def clone(self):
""" Clones this record"""
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):
self.records = []
def check(self, state, stack_index):
"""
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):
""" Adds records to this taint result """
self.records += records
def _try_get_record(self, state):
""" Finds record belonging to the state """
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, node, state, initial_stack=[]):
"""
Runs taint analysis on the statespace
:param statespace: symbolic statespace to run taint analysis on
:param node: taint introduction node
:param state: taint introduction state
:param stack_indexes: stack indexes to introduce taint
:return: TaintResult object containing analysis results
"""
result = TaintResult()
# 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)]
for node, record, index in current_nodes:
records = TaintRunner.execute_node(node, record, index)
result.add_records(records)
children = [statespace.nodes[edge.node_to] for edge in statespace.edges if edge.node_from == node.uid]
for child in children:
current_nodes.append((child, records[-1], 0))
return result
@staticmethod
def execute_node(node, last_record, state_index=0):
"""
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, state):
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:
logging.debug("Unknown operation encountered: {}".format(op))
return new_record
@staticmethod
def mutate_stack(record, 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, record):
TaintRunner.mutate_stack(record, (0, 1))
@staticmethod
def mutate_dup(op, record):
depth = int(op[3:])
index = len(record.stack) - depth
record.stack.append(record.stack[index])
@staticmethod
def mutate_swap(op, 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, op0):
_ = record.stack.pop()
try:
index = helper.get_concrete_int(op0)
except AttributeError:
logging.debug("Can't MLOAD taint track symbolically")
record.stack.append(False)
return
record.stack.append(record.memory_tainted(index))
@staticmethod
def mutate_mstore(record, op0):
_, value_taint = record.stack.pop(), record.stack.pop()
try:
index = helper.get_concrete_int(op0)
except AttributeError:
logging.debug("Can't mstore taint track symbolically")
return
record.memory[index] = value_taint
@staticmethod
def mutate_sload(record, op0):
_ = record.stack.pop()
try:
index = helper.get_concrete_int(op0)
except AttributeError:
logging.debug("Can't MLOAD taint track symbolically")
record.stack.append(False)
return
record.stack.append(record.storage_tainted(index))
@staticmethod
def mutate_sstore(record, op0):
_, value_taint = record.stack.pop(), record.stack.pop()
try:
index = helper.get_concrete_int(op0)
except AttributeError:
logging.debug("Can't mstore taint track symbolically")
return
record.storage[index] = value_taint
@staticmethod
def mutate_log(record, op):
depth = int(op[3:])
for _ in range(depth + 2):
record.stack.pop()
@staticmethod
def mutate_call(record, 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),
'RETURN': (2, 0)
}

@ -10,7 +10,7 @@ from mythril.analysis.symbolic import SymExecWrapper
from mythril.analysis.report import Report from mythril.analysis.report import Report
from mythril.ether import util from mythril.ether import util
from laser.ethereum import helper from mythril.laser.ethereum import helper
def analyze_truffle_project(args): def analyze_truffle_project(args):

@ -2,7 +2,6 @@ rlp<1.0.0
ethereum>=2.3.0 ethereum>=2.3.0
ZODB>=5.3.0 ZODB>=5.3.0
z3-solver>=4.5 z3-solver>=4.5
laser-ethereum>=0.17.12
requests requests
BTrees BTrees
plyvel plyvel
@ -19,5 +18,7 @@ eth-tester>=0.1.0b21
coverage coverage
jinja2 jinja2
pytest pytest
py-flags
mock mock
pytest_mock pytest_mock
pytest-cov

@ -307,7 +307,6 @@ setup(
'ethereum>=2.3.0', 'ethereum>=2.3.0',
'ZODB>=5.3.0', 'ZODB>=5.3.0',
'z3-solver>=4.5', 'z3-solver>=4.5',
'laser-ethereum>=0.17.12',
'requests', 'requests',
'BTrees', 'BTrees',
'py-solc', 'py-solc',
@ -323,7 +322,11 @@ setup(
'eth-tester>=0.1.0b21', 'eth-tester>=0.1.0b21',
'coverage', 'coverage',
'jinja2', 'jinja2',
'rlp<1.0.0' 'rlp<1.0.0',
'py-flags',
'mock',
'pytest_mock',
'pytest-cov'
], ],
python_requires='>=3.5', python_requires='>=3.5',

@ -7,8 +7,11 @@ TESTS_DIR = Path(__file__).parent
PROJECT_DIR = TESTS_DIR.parent PROJECT_DIR = TESTS_DIR.parent
TESTDATA = TESTS_DIR / "testdata" TESTDATA = TESTS_DIR / "testdata"
TESTDATA_INPUTS = TESTDATA / "inputs" TESTDATA_INPUTS = TESTDATA / "inputs"
TESTDATA_INPUTS_CONTRACTS = TESTDATA / "input_contracts"
TESTDATA_OUTPUTS_EXPECTED = TESTDATA / "outputs_expected" TESTDATA_OUTPUTS_EXPECTED = TESTDATA / "outputs_expected"
TESTDATA_OUTPUTS_CURRENT = TESTDATA / "outputs_current" TESTDATA_OUTPUTS_CURRENT = TESTDATA / "outputs_current"
TESTDATA_OUTPUTS_CURRENT_LASER_RESULT = TESTDATA / "outputs_current_laser_result"
TESTDATA_OUTPUTS_EXPECTED_LASER_RESULT = TESTDATA / "outputs_expected_laser_result"
MYTHRIL_DIR = TESTS_DIR / "mythril_dir" MYTHRIL_DIR = TESTS_DIR / "mythril_dir"

@ -1,10 +1,92 @@
import unittest import json
from mythril.analysis.symbolic import SymExecWrapper from mythril.analysis.symbolic import SymExecWrapper
from mythril.analysis.callgraph import generate_graph from mythril.analysis.callgraph import generate_graph
from mythril.ether.ethcontract import ETHContract from mythril.ether.ethcontract import ETHContract
from mythril.ether.soliditycontract import SolidityContract
from mythril.laser.ethereum.svm import GlobalState, MachineState
from mythril.laser.ethereum import svm
from tests import *
class SVMTestCase(unittest.TestCase):
class LaserEncoder(json.JSONEncoder):
def default(self, o):
if getattr(o, "__module__", None) == "z3.z3":
return str(o)
return str(o)
def _all_info(laser):
accounts = {}
for address, _account in laser.accounts.items():
account = _account.as_dict()
account["code"] = account["code"].instruction_list
account['balance'] = str(account['balance'])
accounts[address] = account
nodes = {}
for uid, node in laser.nodes.items():
states = []
for state in node.states:
if isinstance(state, MachineState):
states.append(state.as_dict())
elif isinstance(state, GlobalState):
environment = state.environment.as_dict()
environment["active_account"] = environment["active_account"].address
states.append({
'accounts': state.accounts.keys(),
'environment': environment,
'mstate': state.mstate.as_dict()
})
nodes[uid] = {
'uid': node.uid,
'contract_name': node.contract_name,
'start_addr': node.start_addr,
'states': states,
'constraints': node.constraints,
'function_name': node.function_name,
'flags': str(node.flags)
}
edges = [edge.as_dict() for edge in laser.edges]
return {
'accounts': accounts,
'nodes': nodes,
'edges': edges,
'total_states': laser.total_states,
'max_depth': laser.max_depth
}
class SVMTestCase(BaseTestCase):
def setUp(self):
super(SVMTestCase, self).setUp()
svm.gbl_next_uid = 0
def test_laser_result(self):
for input_file in TESTDATA_INPUTS_CONTRACTS.iterdir():
if input_file.name == "weak_random.sol":
continue
output_expected = TESTDATA_OUTPUTS_EXPECTED_LASER_RESULT / (input_file.name + ".json")
output_current = TESTDATA_OUTPUTS_CURRENT_LASER_RESULT / (input_file.name + ".json")
disassembly = SolidityContract(str(input_file)).disassembly
account = svm.Account("0x0000000000000000000000000000000000000000", disassembly)
accounts = {account.address: account}
laser = svm.LaserEVM(accounts)
laser.sym_exec(account.address)
laser_info = _all_info(laser)
output_current.write_text(json.dumps(laser_info, cls=LaserEncoder, indent=4))
if not (output_expected.read_text() == output_expected.read_text()):
self.found_changed_files(input_file, output_expected, output_current)
self.assert_and_show_changed_files()
def runTest(self): def runTest(self):

@ -0,0 +1,29 @@
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]

@ -0,0 +1,36 @@
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

@ -0,0 +1,36 @@
from mythril.laser.ethereum.taint_analysis import *
from mythril.laser.ethereum.svm import GlobalState
def test_result_state():
# arrange
taint_result = TaintResult()
record = TaintRecord()
state = GlobalState(2, 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)
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

@ -0,0 +1,94 @@
import mock
import pytest
from pytest_mock import mocker
from mythril.laser.ethereum.taint_analysis import *
from mythril.laser.ethereum.svm import GlobalState, Node, Edge, LaserEVM, MachineState
def test_execute_state(mocker):
record = TaintRecord()
record.stack = [True, False, True]
state = GlobalState(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)
state_1.mstate.stack = [1, 2, 3]
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)
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):
state_1 = GlobalState(None, None, MachineState(gas=10000000))
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, None, MachineState(gas=10000000))
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, None, MachineState(gas=10000000))
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,152 @@
contract Rubixi {
//Declare variables for storage critical to contract
uint private balance = 0;
uint private collectedFees = 0;
uint private feePercent = 10;
uint private pyramidMultiplier = 300;
uint private payoutOrder = 0;
address private creator;
//Sets creator
function DynamicPyramid() {
creator = msg.sender;
}
modifier onlyowner {
if (msg.sender == creator) _;
}
struct Participant {
address etherAddress;
uint payout;
}
Participant[] private participants;
//Fallback function
function() {
init();
}
//init function run on fallback
function init() private {
//Ensures only tx with value of 1 ether or greater are processed and added to pyramid
if (msg.value < 1 ether) {
collectedFees += msg.value;
return;
}
uint _fee = feePercent;
//50% fee rebate on any ether value of 50 or greater
if (msg.value >= 50 ether) _fee /= 2;
addPayout(_fee);
}
//Function called for valid tx to the contract
function addPayout(uint _fee) private {
//Adds new address to participant array
participants.push(Participant(msg.sender, (msg.value * pyramidMultiplier) / 100));
//These statements ensure a quicker payout system to later pyramid entrants, so the pyramid has a longer lifespan
if (participants.length == 10) pyramidMultiplier = 200;
else if (participants.length == 25) pyramidMultiplier = 150;
// collect fees and update contract balance
balance += (msg.value * (100 - _fee)) / 100;
collectedFees += (msg.value * _fee) / 100;
//Pays earlier participiants if balance sufficient
while (balance > participants[payoutOrder].payout) {
uint payoutToSend = participants[payoutOrder].payout;
participants[payoutOrder].etherAddress.send(payoutToSend);
balance -= participants[payoutOrder].payout;
payoutOrder += 1;
}
}
//Fee functions for creator
function collectAllFees() onlyowner {
if (collectedFees == 0) throw;
creator.send(collectedFees);
collectedFees = 0;
}
function collectFeesInEther(uint _amt) onlyowner {
_amt *= 1 ether;
if (_amt > collectedFees) collectAllFees();
if (collectedFees == 0) throw;
creator.send(_amt);
collectedFees -= _amt;
}
function collectPercentOfFees(uint _pcent) onlyowner {
if (collectedFees == 0 || _pcent > 100) throw;
uint feesToCollect = collectedFees / 100 * _pcent;
creator.send(feesToCollect);
collectedFees -= feesToCollect;
}
//Functions for changing variables related to the contract
function changeOwner(address _owner) onlyowner {
creator = _owner;
}
function changeMultiplier(uint _mult) onlyowner {
if (_mult > 300 || _mult < 120) throw;
pyramidMultiplier = _mult;
}
function changeFeePercentage(uint _fee) onlyowner {
if (_fee > 10) throw;
feePercent = _fee;
}
//Functions to provide information to end-user using JSON interface or other interfaces
function currentMultiplier() constant returns(uint multiplier, string info) {
multiplier = pyramidMultiplier;
info = 'This multiplier applies to you as soon as transaction is received, may be lowered to hasten payouts or increased if payouts are fast enough. Due to no float or decimals, multiplier is x100 for a fractional multiplier e.g. 250 is actually a 2.5x multiplier. Capped at 3x max and 1.2x min.';
}
function currentFeePercentage() constant returns(uint fee, string info) {
fee = feePercent;
info = 'Shown in % form. Fee is halved(50%) for amounts equal or greater than 50 ethers. (Fee may change, but is capped to a maximum of 10%)';
}
function currentPyramidBalanceApproximately() constant returns(uint pyramidBalance, string info) {
pyramidBalance = balance / 1 ether;
info = 'All balance values are measured in Ethers, note that due to no decimal placing, these values show up as integers only, within the contract itself you will get the exact decimal value you are supposed to';
}
function nextPayoutWhenPyramidBalanceTotalsApproximately() constant returns(uint balancePayout) {
balancePayout = participants[payoutOrder].payout / 1 ether;
}
function feesSeperateFromBalanceApproximately() constant returns(uint fees) {
fees = collectedFees / 1 ether;
}
function totalParticipants() constant returns(uint count) {
count = participants.length;
}
function numberOfParticipantsWaitingForPayout() constant returns(uint count) {
count = participants.length - payoutOrder;
}
function participantDetails(uint orderInPyramid) constant returns(address Address, uint Payout) {
if (orderInPyramid <= participants.length) {
Address = participants[orderInPyramid].etherAddress;
Payout = participants[orderInPyramid].payout / 1 ether;
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save