"""This module contains a representation class for EVM instructions and
transitions between them."""
import binascii
import logging

from copy import copy, deepcopy
from typing import cast, Callable, List, Union, Optional
from datetime import datetime

from mythril.laser.smt import (
    Extract,
    Expression,
    UDiv,
    simplify,
    Concat,
    ULT,
    UGT,
    BitVec,
    is_false,
    is_true,
    URem,
    SRem,
    If,
    Bool,
    Not,
    LShR,
    UGE,
)
from mythril.laser.smt import symbol_factory

from mythril.disassembler.disassembly import Disassembly

from mythril.laser.ethereum.state.calldata import ConcreteCalldata, SymbolicCalldata

import mythril.laser.ethereum.util as helper
from mythril.laser.ethereum import util
from mythril.laser.ethereum.keccak_function_manager import keccak_function_manager
from mythril.laser.ethereum.call import (
    get_call_parameters,
    native_call,
    get_call_data,
    SYMBOLIC_CALLDATA_SIZE,
)
from mythril.laser.ethereum.evm_exceptions import (
    VmException,
    StackUnderflowException,
    InvalidJumpDestination,
    InvalidInstruction,
    OutOfGasException,
    WriteProtection,
)
from mythril.laser.ethereum.instruction_data import get_opcode_gas, calculate_sha3_gas
from mythril.laser.ethereum.state.global_state import GlobalState
from mythril.laser.ethereum.transaction import (
    MessageCallTransaction,
    TransactionStartSignal,
    ContractCreationTransaction,
    get_next_transaction_id,
)

from mythril.support.support_utils import get_code_hash

from mythril.support.loader import DynLoader

log = logging.getLogger(__name__)

TT256 = 2 ** 256
TT256M1 = 2 ** 256 - 1


def transfer_ether(
    global_state: GlobalState,
    sender: BitVec,
    receiver: BitVec,
    value: Union[int, BitVec],
):
    """
    Perform an Ether transfer between two accounts

    :param global_state: The global state in which the Ether transfer occurs
    :param sender: The sender of the Ether
    :param receiver: The recipient of the Ether
    :param value: The amount of Ether to send
    :return:
    """
    value = value if isinstance(value, BitVec) else symbol_factory.BitVecVal(value, 256)

    global_state.world_state.constraints.append(
        UGE(global_state.world_state.balances[sender], value)
    )
    global_state.world_state.balances[receiver] += value
    global_state.world_state.balances[sender] -= value


class StateTransition(object):
    """Decorator that handles global state copy and original return.

    This decorator calls the decorated instruction mutator function on a
    copy of the state that is passed to it. After the call, the
    resulting new states' program counter is automatically incremented
    if `increment_pc=True`.
    """

    def __init__(
        self, increment_pc=True, enable_gas=True, is_state_mutation_instruction=False
    ):
        """

        :param increment_pc:
        :param enable_gas:
        :param is_state_mutation_instruction: The function mutates state
        """
        self.increment_pc = increment_pc
        self.enable_gas = enable_gas
        self.is_state_mutation_instruction = is_state_mutation_instruction

    @staticmethod
    def call_on_state_copy(func: Callable, func_obj: "Instruction", state: GlobalState):
        """

        :param func:
        :param func_obj:
        :param state:
        :return:
        """
        global_state_copy = copy(state)
        return func(func_obj, global_state_copy)

    def increment_states_pc(self, states: List[GlobalState]) -> List[GlobalState]:
        """

        :param states:
        :return:
        """
        if self.increment_pc:
            for state in states:
                state.mstate.pc += 1
        return states

    @staticmethod
    def check_gas_usage_limit(global_state: GlobalState):
        """

        :param global_state:
        :return:
        """
        global_state.mstate.check_gas()
        if isinstance(global_state.current_transaction.gas_limit, BitVec):
            value = global_state.current_transaction.gas_limit.value
            if value is None:
                return
            global_state.current_transaction.gas_limit = value
        if (
            global_state.mstate.min_gas_used
            >= global_state.current_transaction.gas_limit
        ):
            raise OutOfGasException()

    def accumulate_gas(self, global_state: GlobalState):
        """

        :param global_state:
        :return:
        """
        if not self.enable_gas:
            return global_state
        opcode = global_state.instruction["opcode"]
        min_gas, max_gas = get_opcode_gas(opcode)
        global_state.mstate.min_gas_used += min_gas
        global_state.mstate.max_gas_used += max_gas
        self.check_gas_usage_limit(global_state)

        return global_state

    def __call__(self, func: Callable) -> Callable:
        def wrapper(
            func_obj: "Instruction", global_state: GlobalState
        ) -> List[GlobalState]:
            """

            :param func_obj:
            :param global_state:
            :return:
            """
            if self.is_state_mutation_instruction and global_state.environment.static:
                raise WriteProtection(
                    "The function {} cannot be executed in a static call".format(
                        func.__name__[:-1]
                    )
                )

            new_global_states = self.call_on_state_copy(func, func_obj, global_state)
            new_global_states = [
                self.accumulate_gas(state) for state in new_global_states
            ]
            return self.increment_states_pc(new_global_states)

        return wrapper


class Instruction:
    """Instruction class is used to mutate a state according to the current
    instruction."""

    def __init__(
        self,
        op_code: str,
        dynamic_loader: DynLoader,
        pre_hooks: List[Callable] = None,
        post_hooks: List[Callable] = None,
    ) -> None:
        """

        :param op_code:
        :param dynamic_loader:
        :param iprof:
        """
        self.dynamic_loader = dynamic_loader
        self.op_code = op_code.upper()
        self.pre_hook = pre_hooks if pre_hooks else []
        self.post_hook = post_hooks if post_hooks else []

    def _execute_pre_hooks(self, global_state: GlobalState):
        for hook in self.pre_hook:
            hook(global_state)

    def _execute_post_hooks(self, global_state: GlobalState):
        for hook in self.post_hook:
            hook(global_state)

    def evaluate(self, global_state: GlobalState, post=False) -> List[GlobalState]:
        """Performs the mutation for this instruction.

        :param global_state:
        :param post:
        :return:
        """
        # Generalize some ops
        log.debug("Evaluating %s at %i", self.op_code, global_state.mstate.pc)

        op = self.op_code.lower()
        if self.op_code.startswith("PUSH"):
            op = "push"
        elif self.op_code.startswith("DUP"):
            op = "dup"
        elif self.op_code.startswith("SWAP"):
            op = "swap"
        elif self.op_code.startswith("LOG"):
            op = "log"

        instruction_mutator = (
            getattr(self, op + "_", None)
            if not post
            else getattr(self, op + "_" + "post", None)
        )
        if instruction_mutator is None:
            raise NotImplementedError

        self._execute_pre_hooks(global_state)
        result = instruction_mutator(global_state)
        self._execute_post_hooks(global_state)

        return result

    @StateTransition()
    def jumpdest_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        return [global_state]

    @StateTransition()
    def push_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        push_instruction = global_state.get_current_instruction()
        push_value = push_instruction["argument"][2:]

        try:
            length_of_value = 2 * int(push_instruction["opcode"][4:])
        except ValueError:
            raise VmException("Invalid Push instruction")

        push_value += "0" * max(length_of_value - len(push_value), 0)
        global_state.mstate.stack.append(
            symbol_factory.BitVecVal(int(push_value, 16), 256)
        )
        return [global_state]

    @StateTransition()
    def dup_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        value = int(global_state.get_current_instruction()["opcode"][3:], 10)
        global_state.mstate.stack.append(global_state.mstate.stack[-value])
        return [global_state]

    @StateTransition()
    def swap_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        depth = int(self.op_code[4:])
        stack = global_state.mstate.stack
        stack[-depth - 1], stack[-1] = stack[-1], stack[-depth - 1]
        return [global_state]

    @StateTransition()
    def pop_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        global_state.mstate.stack.pop()
        return [global_state]

    @StateTransition()
    def and_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        stack = global_state.mstate.stack
        op1, op2 = stack.pop(), stack.pop()
        if isinstance(op1, Bool):
            op1 = If(
                op1, symbol_factory.BitVecVal(1, 256), symbol_factory.BitVecVal(0, 256)
            )
        if isinstance(op2, Bool):
            op2 = If(
                op2, symbol_factory.BitVecVal(1, 256), symbol_factory.BitVecVal(0, 256)
            )
        if not isinstance(op1, Expression):
            op1 = symbol_factory.BitVecVal(op1, 256)
        if not isinstance(op2, Expression):
            op2 = symbol_factory.BitVecVal(op2, 256)
        stack.append(op1 & op2)

        return [global_state]

    @StateTransition()
    def or_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        stack = global_state.mstate.stack
        op1, op2 = stack.pop(), stack.pop()

        if isinstance(op1, Bool):
            op1 = If(
                op1, symbol_factory.BitVecVal(1, 256), symbol_factory.BitVecVal(0, 256)
            )

        if isinstance(op2, Bool):
            op2 = If(
                op2, symbol_factory.BitVecVal(1, 256), symbol_factory.BitVecVal(0, 256)
            )

        stack.append(op1 | op2)

        return [global_state]

    @StateTransition()
    def xor_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        mstate = global_state.mstate
        mstate.stack.append(mstate.stack.pop() ^ mstate.stack.pop())
        return [global_state]

    @StateTransition()
    def not_(self, global_state: GlobalState):
        """

        :param global_state:
        :return:
        """
        mstate = global_state.mstate
        mstate.stack.append(symbol_factory.BitVecVal(TT256M1, 256) - mstate.stack.pop())
        return [global_state]

    @StateTransition()
    def byte_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        mstate = global_state.mstate
        op0, op1 = mstate.stack.pop(), mstate.stack.pop()
        if not isinstance(op1, Expression):
            op1 = symbol_factory.BitVecVal(op1, 256)
        try:
            index = util.get_concrete_int(op0)
            offset = (31 - index) * 8
            if offset >= 0:
                result = simplify(
                    Concat(
                        symbol_factory.BitVecVal(0, 248),
                        Extract(offset + 7, offset, op1),
                    )
                )  # type: Union[int, Expression]
            else:
                result = 0
        except TypeError:
            log.debug("BYTE: Unsupported symbolic byte offset")
            result = global_state.new_bitvec(
                str(simplify(op1)) + "[" + str(simplify(op0)) + "]", 256
            )

        mstate.stack.append(result)
        return [global_state]

    # Arithmetic
    @StateTransition()
    def add_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        global_state.mstate.stack.append(
            (
                helper.pop_bitvec(global_state.mstate)
                + helper.pop_bitvec(global_state.mstate)
            )
        )
        return [global_state]

    @StateTransition()
    def sub_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        global_state.mstate.stack.append(
            (
                helper.pop_bitvec(global_state.mstate)
                - helper.pop_bitvec(global_state.mstate)
            )
        )
        return [global_state]

    @StateTransition()
    def mul_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        global_state.mstate.stack.append(
            (
                helper.pop_bitvec(global_state.mstate)
                * helper.pop_bitvec(global_state.mstate)
            )
        )

        return [global_state]

    @StateTransition()
    def div_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        op0, op1 = (
            util.pop_bitvec(global_state.mstate),
            util.pop_bitvec(global_state.mstate),
        )
        if op1 == 0:
            global_state.mstate.stack.append(symbol_factory.BitVecVal(0, 256))
        else:
            global_state.mstate.stack.append(UDiv(op0, op1))
        return [global_state]

    @StateTransition()
    def sdiv_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        s0, s1 = (
            util.pop_bitvec(global_state.mstate),
            util.pop_bitvec(global_state.mstate),
        )
        if s1 == 0:
            global_state.mstate.stack.append(symbol_factory.BitVecVal(0, 256))
        else:
            global_state.mstate.stack.append(s0 / s1)
        return [global_state]

    @StateTransition()
    def mod_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        s0, s1 = (
            util.pop_bitvec(global_state.mstate),
            util.pop_bitvec(global_state.mstate),
        )
        global_state.mstate.stack.append(0 if s1 == 0 else URem(s0, s1))
        return [global_state]

    @StateTransition()
    def shl_(self, global_state: GlobalState) -> List[GlobalState]:
        shift, value = (
            util.pop_bitvec(global_state.mstate),
            util.pop_bitvec(global_state.mstate),
        )
        global_state.mstate.stack.append(value << shift)
        return [global_state]

    @StateTransition()
    def shr_(self, global_state: GlobalState) -> List[GlobalState]:
        shift, value = (
            util.pop_bitvec(global_state.mstate),
            util.pop_bitvec(global_state.mstate),
        )
        global_state.mstate.stack.append(LShR(value, shift))
        return [global_state]

    @StateTransition()
    def sar_(self, global_state: GlobalState) -> List[GlobalState]:
        shift, value = (
            util.pop_bitvec(global_state.mstate),
            util.pop_bitvec(global_state.mstate),
        )
        global_state.mstate.stack.append(value >> shift)
        return [global_state]

    @StateTransition()
    def smod_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        s0, s1 = (
            util.pop_bitvec(global_state.mstate),
            util.pop_bitvec(global_state.mstate),
        )
        global_state.mstate.stack.append(0 if s1 == 0 else SRem(s0, s1))
        return [global_state]

    @StateTransition()
    def addmod_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        s0, s1, s2 = (
            util.pop_bitvec(global_state.mstate),
            util.pop_bitvec(global_state.mstate),
            util.pop_bitvec(global_state.mstate),
        )
        global_state.mstate.stack.append(URem(URem(s0, s2) + URem(s1, s2), s2))
        return [global_state]

    @StateTransition()
    def mulmod_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        s0, s1, s2 = (
            util.pop_bitvec(global_state.mstate),
            util.pop_bitvec(global_state.mstate),
            util.pop_bitvec(global_state.mstate),
        )
        global_state.mstate.stack.append(URem(URem(s0, s2) * URem(s1, s2), s2))
        return [global_state]

    @StateTransition()
    def exp_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate
        base, exponent = util.pop_bitvec(state), util.pop_bitvec(state)

        if base.symbolic or exponent.symbolic:

            state.stack.append(
                global_state.new_bitvec(
                    "invhash("
                    + str(hash(simplify(base)))
                    + ")**invhash("
                    + str(hash(simplify(exponent)))
                    + ")",
                    256,
                    base.annotations.union(exponent.annotations),
                )
            )  # Hash is used because str(symbol) takes a long time to be converted to a string
        else:

            state.stack.append(
                symbol_factory.BitVecVal(
                    pow(base.value, exponent.value, 2 ** 256),
                    256,
                    annotations=base.annotations.union(exponent.annotations),
                )
            )

        return [global_state]

    @StateTransition()
    def signextend_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        mstate = global_state.mstate
        s0, s1 = mstate.stack.pop(), mstate.stack.pop()
        try:
            s0 = util.get_concrete_int(s0)
        except TypeError:
            log.debug("Unsupported symbolic argument for SIGNEXTEND")
            mstate.stack.append(
                global_state.new_bitvec(
                    "SIGNEXTEND({},{})".format(hash(s0), hash(s1)), 256
                )
            )
            return [global_state]

        if s0 <= 31:
            testbit = s0 * 8 + 7
            if not is_true(simplify((s1 & (1 << testbit)) == 0)):
                mstate.stack.append(s1 | (TT256 - (1 << testbit)))
            else:
                mstate.stack.append(s1 & ((1 << testbit) - 1))
        else:
            mstate.stack.append(s1)

        return [global_state]

    # Comparisons
    @StateTransition()
    def lt_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate
        exp = ULT(util.pop_bitvec(state), util.pop_bitvec(state))
        state.stack.append(exp)
        return [global_state]

    @StateTransition()
    def gt_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate
        op1, op2 = util.pop_bitvec(state), util.pop_bitvec(state)
        exp = UGT(op1, op2)
        state.stack.append(exp)
        return [global_state]

    @StateTransition()
    def slt_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate
        exp = util.pop_bitvec(state) < util.pop_bitvec(state)
        state.stack.append(exp)
        return [global_state]

    @StateTransition()
    def sgt_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate

        exp = util.pop_bitvec(state) > util.pop_bitvec(state)
        state.stack.append(exp)
        return [global_state]

    @StateTransition()
    def eq_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate

        op1 = state.stack.pop()
        op2 = state.stack.pop()

        if isinstance(op1, Bool):
            op1 = If(
                op1, symbol_factory.BitVecVal(1, 256), symbol_factory.BitVecVal(0, 256)
            )

        if isinstance(op2, Bool):
            op2 = If(
                op2, symbol_factory.BitVecVal(1, 256), symbol_factory.BitVecVal(0, 256)
            )

        exp = op1 == op2

        state.stack.append(exp)
        return [global_state]

    @StateTransition()
    def iszero_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate
        val = state.stack.pop()
        exp = Not(val) if isinstance(val, Bool) else val == 0

        exp = If(
            exp, symbol_factory.BitVecVal(1, 256), symbol_factory.BitVecVal(0, 256)
        )
        state.stack.append(simplify(exp))

        return [global_state]

    # Call data
    @StateTransition()
    def callvalue_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate
        environment = global_state.environment
        state.stack.append(environment.callvalue)

        return [global_state]

    @StateTransition()
    def calldataload_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate
        environment = global_state.environment
        op0 = state.stack.pop()

        value = environment.calldata.get_word_at(op0)

        state.stack.append(value)

        return [global_state]

    @StateTransition()
    def calldatasize_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate
        environment = global_state.environment

        if isinstance(global_state.current_transaction, ContractCreationTransaction):
            log.debug("Attempt to use CALLDATASIZE in creation transaction")
            state.stack.append(0)
        else:
            state.stack.append(environment.calldata.calldatasize)

        return [global_state]

    @staticmethod
    def _calldata_copy_helper(global_state, mstate, mstart, dstart, size):
        environment = global_state.environment

        try:
            mstart = util.get_concrete_int(mstart)
        except TypeError:
            log.debug("Unsupported symbolic memory offset in CALLDATACOPY")
            return [global_state]

        try:
            dstart = util.get_concrete_int(dstart)  # type: Union[int, BitVec]
        except TypeError:
            log.debug("Unsupported symbolic calldata offset in CALLDATACOPY")
            dstart = simplify(dstart)

        try:
            size = util.get_concrete_int(size)  # type: Union[int, BitVec]
        except TypeError:
            log.debug("Unsupported symbolic size in CALLDATACOPY")
            size = SYMBOLIC_CALLDATA_SIZE  # The excess size will get overwritten

        size = cast(int, size)
        if size > 0:
            try:
                mstate.mem_extend(mstart, size)
            except TypeError as e:
                log.debug("Memory allocation error: {}".format(e))
                mstate.mem_extend(mstart, 1)
                mstate.memory[mstart] = global_state.new_bitvec(
                    "calldata_"
                    + str(environment.active_account.contract_name)
                    + "["
                    + str(dstart)
                    + ": + "
                    + str(size)
                    + "]",
                    8,
                )
                return [global_state]

            try:
                i_data = dstart
                new_memory = []
                for i in range(size):
                    value = environment.calldata[i_data]
                    new_memory.append(value)

                    i_data = (
                        i_data + 1
                        if isinstance(i_data, int)
                        else simplify(cast(BitVec, i_data) + 1)
                    )
                for i in range(len(new_memory)):
                    mstate.memory[i + mstart] = new_memory[i]

            except IndexError:
                log.debug("Exception copying calldata to memory")

                mstate.memory[mstart] = global_state.new_bitvec(
                    "calldata_"
                    + str(environment.active_account.contract_name)
                    + "["
                    + str(dstart)
                    + ": + "
                    + str(size)
                    + "]",
                    8,
                )
        return [global_state]

    @StateTransition()
    def calldatacopy_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate
        op0, op1, op2 = state.stack.pop(), state.stack.pop(), state.stack.pop()

        if isinstance(global_state.current_transaction, ContractCreationTransaction):
            log.debug("Attempt to use CALLDATACOPY in creation transaction")
            return [global_state]

        return self._calldata_copy_helper(global_state, state, op0, op1, op2)

    # Environment
    @StateTransition()
    def address_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate
        environment = global_state.environment
        state.stack.append(environment.address)
        return [global_state]

    @StateTransition()
    def balance_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate
        address = state.stack.pop()
        if address.symbolic is False:
            balance = global_state.world_state.accounts_exist_or_load(
                address.value, self.dynamic_loader
            ).balance()
        else:
            balance = symbol_factory.BitVecVal(0, 256)
            for account in global_state.world_state.accounts.values():
                balance = If(address == account.address, account.balance(), balance)
        state.stack.append(balance)
        return [global_state]

    @StateTransition()
    def origin_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate
        environment = global_state.environment
        state.stack.append(environment.origin)
        return [global_state]

    @StateTransition()
    def caller_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate
        environment = global_state.environment
        state.stack.append(environment.sender)
        return [global_state]

    @StateTransition()
    def chainid_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        global_state.mstate.stack.append(global_state.environment.chainid)
        return [global_state]

    @StateTransition()
    def selfbalance_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        balance = global_state.environment.active_account.balance()
        global_state.mstate.stack.append(balance)
        return [global_state]

    @StateTransition()
    def codesize_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate
        environment = global_state.environment
        disassembly = environment.code
        calldata = global_state.environment.calldata
        if isinstance(global_state.current_transaction, ContractCreationTransaction):
            # Hacky way to ensure constructor arguments work - Pick some reasonably large size.
            no_of_bytes = len(disassembly.bytecode) // 2
            if isinstance(calldata, ConcreteCalldata):
                no_of_bytes += calldata.size
            else:
                no_of_bytes += 0x200  # space for 16 32-byte arguments
                global_state.world_state.constraints.append(
                    global_state.environment.calldata.size == no_of_bytes
                )

        else:
            no_of_bytes = len(disassembly.bytecode) // 2
        state.stack.append(no_of_bytes)
        return [global_state]

    @staticmethod
    def _sha3_gas_helper(global_state, length):
        min_gas, max_gas = calculate_sha3_gas(length)
        global_state.mstate.min_gas_used += min_gas
        global_state.mstate.max_gas_used += max_gas
        StateTransition.check_gas_usage_limit(global_state)
        return global_state

    @StateTransition(enable_gas=False)
    def sha3_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """

        state = global_state.mstate
        index, op1 = state.stack.pop(), state.stack.pop()

        try:
            length = util.get_concrete_int(op1)
        except TypeError:
            # Can't access symbolic memory offsets
            length = 64
            global_state.world_state.constraints.append(op1 == length)
        Instruction._sha3_gas_helper(global_state, length)

        state.mem_extend(index, length)
        data_list = [
            b if isinstance(b, BitVec) else symbol_factory.BitVecVal(b, 8)
            for b in state.memory[index : index + length]
        ]

        if len(data_list) > 1:
            data = simplify(Concat(data_list))
        elif len(data_list) == 1:
            data = data_list[0]
        else:
            # TODO: handle finding x where func(x)==func("")
            result = keccak_function_manager.get_empty_keccak_hash()
            state.stack.append(result)
            return [global_state]

        result, condition = keccak_function_manager.create_keccak(data)
        state.stack.append(result)
        global_state.world_state.constraints.append(condition)

        return [global_state]

    @StateTransition()
    def gasprice_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        global_state.mstate.stack.append(global_state.environment.gasprice)
        return [global_state]

    @StateTransition()
    def codecopy_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        memory_offset, code_offset, size = (
            global_state.mstate.stack.pop(),
            global_state.mstate.stack.pop(),
            global_state.mstate.stack.pop(),
        )
        code = global_state.environment.code.bytecode
        if code[0:2] == "0x":
            code = code[2:]
        code_size = len(code) // 2

        if isinstance(global_state.current_transaction, ContractCreationTransaction):
            # Treat creation code after the expected disassembly as calldata.
            # This is a slightly hacky way to ensure that symbolic constructor
            # arguments work correctly.
            mstate = global_state.mstate
            offset = code_offset - code_size
            log.debug("Copying from code offset: {} with size: {}".format(offset, size))

            if isinstance(global_state.environment.calldata, SymbolicCalldata):
                if code_offset >= code_size:
                    return self._calldata_copy_helper(
                        global_state, mstate, memory_offset, offset, size
                    )
            else:
                # Copy from both code and calldata appropriately.
                concrete_code_offset = helper.get_concrete_int(code_offset)
                concrete_size = helper.get_concrete_int(size)

                code_copy_offset = concrete_code_offset
                code_copy_size = (
                    concrete_size
                    if concrete_code_offset + concrete_size <= code_size
                    else code_size - concrete_code_offset
                )
                code_copy_size = code_copy_size if code_copy_size >= 0 else 0

                calldata_copy_offset = (
                    concrete_code_offset - code_size
                    if concrete_code_offset - code_size > 0
                    else 0
                )
                calldata_copy_size = concrete_code_offset + concrete_size - code_size
                calldata_copy_size = (
                    calldata_copy_size if calldata_copy_size >= 0 else 0
                )

                [global_state] = self._code_copy_helper(
                    code=global_state.environment.code.bytecode,
                    memory_offset=memory_offset,
                    code_offset=code_copy_offset,
                    size=code_copy_size,
                    op="CODECOPY",
                    global_state=global_state,
                )
                return self._calldata_copy_helper(
                    global_state=global_state,
                    mstate=mstate,
                    mstart=memory_offset + code_copy_size,
                    dstart=calldata_copy_offset,
                    size=calldata_copy_size,
                )

        return self._code_copy_helper(
            code=global_state.environment.code.bytecode,
            memory_offset=memory_offset,
            code_offset=code_offset,
            size=size,
            op="CODECOPY",
            global_state=global_state,
        )

    @StateTransition()
    def extcodesize_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate
        addr = state.stack.pop()
        try:
            addr = hex(helper.get_concrete_int(addr))
        except TypeError:
            log.debug("unsupported symbolic address for EXTCODESIZE")
            state.stack.append(global_state.new_bitvec("extcodesize_" + str(addr), 256))
            return [global_state]
        try:
            code = global_state.world_state.accounts_exist_or_load(
                addr, self.dynamic_loader
            ).code.bytecode
        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))
            return [global_state]

        state.stack.append(len(code) // 2)

        return [global_state]

    @staticmethod
    def _code_copy_helper(
        code: str,
        memory_offset: Union[int, BitVec],
        code_offset: Union[int, BitVec],
        size: Union[int, BitVec],
        op: str,
        global_state: GlobalState,
    ) -> List[GlobalState]:
        try:
            concrete_memory_offset = helper.get_concrete_int(memory_offset)
        except TypeError:
            log.debug("Unsupported symbolic memory offset in {}".format(op))
            return [global_state]

        try:
            concrete_size = helper.get_concrete_int(size)
            global_state.mstate.mem_extend(concrete_memory_offset, concrete_size)

        except TypeError:
            # except both attribute error and Exception
            global_state.mstate.mem_extend(concrete_memory_offset, 1)
            global_state.mstate.memory[
                concrete_memory_offset
            ] = global_state.new_bitvec(
                "code({})".format(
                    global_state.environment.active_account.contract_name
                ),
                8,
            )
            return [global_state]

        try:
            concrete_code_offset = helper.get_concrete_int(code_offset)
        except TypeError:
            log.debug("Unsupported symbolic code offset in {}".format(op))
            global_state.mstate.mem_extend(concrete_memory_offset, concrete_size)
            for i in range(concrete_size):
                global_state.mstate.memory[
                    concrete_memory_offset + i
                ] = global_state.new_bitvec(
                    "code({})".format(
                        global_state.environment.active_account.contract_name
                    ),
                    8,
                )
            return [global_state]

        if code[0:2] == "0x":
            code = code[2:]

        for i in range(concrete_size):
            if 2 * (concrete_code_offset + i + 1) > len(code):
                break
            global_state.mstate.memory[concrete_memory_offset + i] = int(
                code[
                    2 * (concrete_code_offset + i) : 2 * (concrete_code_offset + i + 1)
                ],
                16,
            )

        return [global_state]

    @StateTransition()
    def extcodecopy_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate
        addr, memory_offset, code_offset, size = (
            state.stack.pop(),
            state.stack.pop(),
            state.stack.pop(),
            state.stack.pop(),
        )
        try:
            addr = hex(helper.get_concrete_int(addr))
        except TypeError:
            log.debug("unsupported symbolic address for EXTCODECOPY")
            return [global_state]

        try:
            code = global_state.world_state.accounts_exist_or_load(
                addr, self.dynamic_loader
            ).code.bytecode
        except (ValueError, AttributeError) as e:
            log.debug("error accessing contract storage due to: " + str(e))
            return [global_state]

        return self._code_copy_helper(
            code=code,
            memory_offset=memory_offset,
            code_offset=code_offset,
            size=size,
            op="EXTCODECOPY",
            global_state=global_state,
        )

    @StateTransition()
    def extcodehash_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return: List of global states possible, list of size 1 in this case
        """
        world_state = global_state.world_state
        stack = global_state.mstate.stack
        address = Extract(159, 0, stack.pop())

        if address.symbolic:
            code_hash = symbol_factory.BitVecVal(int(get_code_hash(""), 16), 256)
        elif address.value not in world_state.accounts:
            code_hash = symbol_factory.BitVecVal(0, 256)
        else:
            addr = "0" * (40 - len(hex(address.value)[2:])) + hex(address.value)[2:]
            code = world_state.accounts_exist_or_load(
                addr, self.dynamic_loader
            ).code.bytecode
            code_hash = symbol_factory.BitVecVal(int(get_code_hash(code), 16), 256)
        stack.append(code_hash)
        return [global_state]

    @StateTransition()
    def returndatacopy_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate
        memory_offset, return_offset, size = (
            state.stack.pop(),
            state.stack.pop(),
            state.stack.pop(),
        )

        try:
            concrete_memory_offset = helper.get_concrete_int(memory_offset)
        except TypeError:
            log.debug("Unsupported symbolic memory offset in RETURNDATACOPY")
            return [global_state]

        try:
            concrete_return_offset = helper.get_concrete_int(return_offset)
        except TypeError:
            log.debug("Unsupported symbolic return offset in RETURNDATACOPY")
            return [global_state]

        try:
            concrete_size = helper.get_concrete_int(size)
        except TypeError:
            log.debug("Unsupported symbolic max_length offset in RETURNDATACOPY")
            return [global_state]

        if global_state.last_return_data is None:
            return [global_state]

        global_state.mstate.mem_extend(concrete_memory_offset, concrete_size)
        for i in range(concrete_size):
            global_state.mstate.memory[concrete_memory_offset + i] = (
                global_state.last_return_data[concrete_return_offset + i]
                if i < len(global_state.last_return_data)
                else 0
            )

        return [global_state]

    @StateTransition()
    def returndatasize_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        if global_state.last_return_data is None:
            log.debug(
                "No last_return_data found, adding an unconstrained bitvec to the stack"
            )
            global_state.mstate.stack.append(
                global_state.new_bitvec("returndatasize", 256)
            )
        else:
            global_state.mstate.stack.append(len(global_state.last_return_data))

        return [global_state]

    @StateTransition()
    def blockhash_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate
        blocknumber = state.stack.pop()
        state.stack.append(
            global_state.new_bitvec("blockhash_block_" + str(blocknumber), 256)
        )
        return [global_state]

    @StateTransition()
    def coinbase_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        global_state.mstate.stack.append(global_state.new_bitvec("coinbase", 256))
        return [global_state]

    @StateTransition()
    def timestamp_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        global_state.mstate.stack.append(global_state.new_bitvec("timestamp", 256))
        return [global_state]

    @StateTransition()
    def number_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        global_state.mstate.stack.append(global_state.environment.block_number)
        return [global_state]

    @StateTransition()
    def difficulty_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        global_state.mstate.stack.append(
            global_state.new_bitvec("block_difficulty", 256)
        )
        return [global_state]

    @StateTransition()
    def gaslimit_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        global_state.mstate.stack.append(global_state.mstate.gas_limit)
        return [global_state]

    # Memory operations
    @StateTransition()
    def mload_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate
        offset = state.stack.pop()

        state.mem_extend(offset, 32)
        data = state.memory.get_word_at(offset)
        state.stack.append(data)
        return [global_state]

    @StateTransition()
    def mstore_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate
        mstart, value = state.stack.pop(), state.stack.pop()

        try:
            state.mem_extend(mstart, 32)
        except Exception:
            log.debug("Error extending memory")

        state.memory.write_word_at(mstart, value)

        return [global_state]

    @StateTransition()
    def mstore8_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate
        offset, value = state.stack.pop(), state.stack.pop()

        state.mem_extend(offset, 1)

        try:
            value_to_write = (
                util.get_concrete_int(value) % 256
            )  # type: Union[int, BitVec]
        except TypeError:  # BitVec
            value_to_write = Extract(7, 0, value)

        state.memory[offset] = value_to_write

        return [global_state]

    @StateTransition()
    def sload_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """

        state = global_state.mstate
        index = state.stack.pop()
        state.stack.append(global_state.environment.active_account.storage[index])
        return [global_state]

    @StateTransition(is_state_mutation_instruction=True)
    def sstore_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate
        index, value = state.stack.pop(), state.stack.pop()
        global_state.environment.active_account.storage[index] = value
        return [global_state]

    @StateTransition(increment_pc=False, enable_gas=False)
    def jump_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate
        disassembly = global_state.environment.code
        try:
            jump_addr = util.get_concrete_int(state.stack.pop())
        except TypeError:
            raise InvalidJumpDestination("Invalid jump argument (symbolic address)")
        except IndexError:
            raise StackUnderflowException()

        index = util.get_instruction_index(disassembly.instruction_list, jump_addr)
        if index is None:
            raise InvalidJumpDestination("JUMP to invalid address")

        op_code = disassembly.instruction_list[index]["opcode"]

        if op_code != "JUMPDEST":
            raise InvalidJumpDestination(
                "Skipping JUMP to invalid destination (not JUMPDEST): " + str(jump_addr)
            )

        new_state = copy(global_state)
        # add JUMP gas cost
        min_gas, max_gas = get_opcode_gas("JUMP")
        new_state.mstate.min_gas_used += min_gas
        new_state.mstate.max_gas_used += max_gas

        # manually set PC to destination
        new_state.mstate.pc = index
        new_state.mstate.depth += 1

        return [new_state]

    @StateTransition(increment_pc=False, enable_gas=False)
    def jumpi_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        state = global_state.mstate
        disassembly = global_state.environment.code
        min_gas, max_gas = get_opcode_gas("JUMPI")
        states = []

        op0, condition = state.stack.pop(), state.stack.pop()
        try:
            jump_addr = util.get_concrete_int(op0)
        except TypeError:
            log.debug("Skipping JUMPI to invalid destination.")
            global_state.mstate.pc += 1
            global_state.mstate.min_gas_used += min_gas
            global_state.mstate.max_gas_used += max_gas
            return [global_state]

        # False case
        negated = (
            simplify(Not(condition)) if isinstance(condition, Bool) else condition == 0
        )
        negated.simplify()
        # True case
        condi = simplify(condition) if isinstance(condition, Bool) else condition != 0
        condi.simplify()

        negated_cond = (type(negated) == bool and negated) or (
            isinstance(negated, Bool) and not is_false(negated)
        )
        positive_cond = (type(condi) == bool and condi) or (
            isinstance(condi, Bool) and not is_false(condi)
        )

        if negated_cond:
            new_state = copy(global_state)
            # add JUMPI gas cost
            new_state.mstate.min_gas_used += min_gas
            new_state.mstate.max_gas_used += max_gas

            # manually increment PC
            new_state.mstate.depth += 1
            new_state.mstate.pc += 1
            new_state.world_state.constraints.append(negated)
            states.append(new_state)
        else:
            log.debug("Pruned unreachable states.")

        # True case

        # Get jump destination
        index = util.get_instruction_index(disassembly.instruction_list, jump_addr)

        if index is None:
            log.debug("Invalid jump destination: " + str(jump_addr))
            return states

        instr = disassembly.instruction_list[index]

        if instr["opcode"] == "JUMPDEST":
            if positive_cond:
                new_state = copy(global_state)
                # add JUMPI gas cost
                new_state.mstate.min_gas_used += min_gas
                new_state.mstate.max_gas_used += max_gas

                # manually set PC to destination
                new_state.mstate.pc = index
                new_state.mstate.depth += 1
                new_state.world_state.constraints.append(condi)
                states.append(new_state)
            else:
                log.debug("Pruned unreachable states.")
        return states

    @StateTransition()
    def beginsub_(self, global_state: GlobalState) -> List[GlobalState]:
        """
        This opcode depicts the start of the subroutine
        """
        raise OutOfGasException("Encountered BEGINSUB")

    @StateTransition()
    def jumpsub_(self, global_state: GlobalState) -> List[GlobalState]:
        """
        Jump to the subroutine
        """
        disassembly = global_state.environment.code
        try:
            location = util.get_concrete_int(global_state.mstate.stack.pop())
        except TypeError:
            raise VmException("Encountered symbolic JUMPSUB location")
        index = util.get_instruction_index(disassembly.instruction_list, location)
        instr = disassembly.instruction_list[index]

        if instr["opcode"] != "BEGINSUB":
            raise VmException(
                "Encountered invalid JUMPSUB location :{}".format(instr["address"])
            )
        global_state.mstate.subroutine_stack.append(global_state.mstate.pc + 1)
        global_state.mstate.pc = location
        return [global_state]

    @StateTransition(increment_pc=False)
    def returnsub_(self, global_state: GlobalState) -> List[GlobalState]:
        """
        Returns control to the caller of the subroutine
        """
        global_state.mstate.pc = global_state.mstate.subroutine_stack.pop()
        return [global_state]

    @StateTransition()
    def pc_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        index = global_state.mstate.pc
        program_counter = global_state.environment.code.instruction_list[index][
            "address"
        ]
        global_state.mstate.stack.append(symbol_factory.BitVecVal(program_counter, 256))

        return [global_state]

    @StateTransition()
    def msize_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        global_state.mstate.stack.append(global_state.mstate.memory_size)
        return [global_state]

    @StateTransition()
    def gas_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        # TODO: Push a Constrained variable which lies between min gas and max gas
        global_state.mstate.stack.append(global_state.new_bitvec("gas", 256))
        return [global_state]

    @StateTransition(is_state_mutation_instruction=True)
    def log_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        # TODO: implement me
        state = global_state.mstate
        dpth = int(self.op_code[3:])
        state.stack.pop(), state.stack.pop()
        log_data = [state.stack.pop() for _ in range(dpth)]
        # Not supported
        return [global_state]

    def _create_transaction_helper(
        self, global_state, call_value, mem_offset, mem_size, create2_salt=None
    ) -> List[GlobalState]:
        mstate = global_state.mstate
        environment = global_state.environment
        world_state = global_state.world_state

        call_data = get_call_data(global_state, mem_offset, mem_offset + mem_size)

        code_raw = []
        code_end = call_data.size
        size = call_data.size
        if isinstance(size, BitVec):
            # This should be fine because of the below check
            if size.symbolic:
                size = 10 ** 5
            else:
                size = size.value
        for i in range(size):
            if call_data[i].symbolic:
                code_end = i
                break
            code_raw.append(call_data[i].value)

        if len(code_raw) < 1:
            global_state.mstate.stack.append(1)
            log.debug("No code found for trying to execute a create type instruction.")
            return [global_state]

        code_str = bytes.hex(bytes(code_raw))

        next_transaction_id = get_next_transaction_id()
        constructor_arguments = ConcreteCalldata(
            next_transaction_id, call_data[code_end:]
        )
        code = Disassembly(code_str)

        caller = environment.active_account.address
        gas_price = environment.gasprice
        origin = environment.origin

        contract_address = None  # type: Union[BitVec, int]
        Instruction._sha3_gas_helper(global_state, len(code_str[2:]) // 2)

        if create2_salt:
            if create2_salt.symbolic:
                if create2_salt.size() != 256:
                    pad = symbol_factory.BitVecVal(0, 256 - create2_salt.size())
                    create2_salt = Concat(pad, create2_salt)
                address, constraint = keccak_function_manager.create_keccak(
                    Concat(
                        symbol_factory.BitVecVal(255, 8),
                        caller,
                        create2_salt,
                        symbol_factory.BitVecVal(int(get_code_hash(code_str), 16), 256),
                    )
                )
                contract_address = Extract(255, 96, address)
                global_state.world_state.constraints.append(constraint)
            else:
                salt = hex(create2_salt.value)[2:]
                salt = "0" * (64 - len(salt)) + salt

                addr = hex(caller.value)[2:]
                addr = "0" * (40 - len(addr)) + addr

                contract_address = int(
                    get_code_hash("0xff" + addr + salt + get_code_hash(code_str)[2:])[
                        26:
                    ],
                    16,
                )
        transaction = ContractCreationTransaction(
            world_state=world_state,
            caller=caller,
            code=code,
            call_data=constructor_arguments,
            gas_price=gas_price,
            gas_limit=mstate.gas_limit,
            origin=origin,
            call_value=call_value,
            contract_address=contract_address,
        )
        raise TransactionStartSignal(transaction, self.op_code, global_state)

    @StateTransition(is_state_mutation_instruction=True)
    def create_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        call_value, mem_offset, mem_size = global_state.mstate.pop(3)
        return self._create_transaction_helper(
            global_state, call_value, mem_offset, mem_size
        )

    @StateTransition()
    def create_post(self, global_state: GlobalState) -> List[GlobalState]:
        return self._handle_create_type_post(global_state)

    @StateTransition(is_state_mutation_instruction=True)
    def create2_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        call_value, mem_offset, mem_size, salt = global_state.mstate.pop(4)

        return self._create_transaction_helper(
            global_state, call_value, mem_offset, mem_size, salt
        )

    @StateTransition()
    def create2_post(self, global_state: GlobalState) -> List[GlobalState]:
        return self._handle_create_type_post(global_state, opcode="create2")

    @staticmethod
    def _handle_create_type_post(global_state, opcode="create"):
        if opcode == "create2":
            global_state.mstate.pop(4)
        else:
            global_state.mstate.pop(3)
        if global_state.last_return_data:
            return_val = symbol_factory.BitVecVal(
                int(global_state.last_return_data, 16), 256
            )
        else:
            return_val = symbol_factory.BitVecVal(0, 256)
        global_state.mstate.stack.append(return_val)
        return [global_state]

    @StateTransition()
    def return_(self, global_state: GlobalState):
        """

        :param global_state:
        """
        state = global_state.mstate
        offset, length = state.stack.pop(), state.stack.pop()
        if length.symbolic:
            return_data = [global_state.new_bitvec("return_data", 8)]
            log.debug("Return with symbolic length or offset. Not supported")
        else:
            state.mem_extend(offset, length)
            StateTransition.check_gas_usage_limit(global_state)
            return_data = state.memory[offset : offset + length]
        global_state.current_transaction.end(global_state, return_data)

    @StateTransition(is_state_mutation_instruction=True)
    def suicide_(self, global_state: GlobalState):
        """

        :param global_state:
        """
        target = global_state.mstate.stack.pop()
        transfer_amount = global_state.environment.active_account.balance()
        # Often the target of the suicide instruction will be symbolic
        # If it isn't then we'll transfer the balance to the indicated contract
        global_state.world_state.balances[target] += transfer_amount

        global_state.environment.active_account = deepcopy(
            global_state.environment.active_account
        )
        global_state.accounts[
            global_state.environment.active_account.address.value
        ] = global_state.environment.active_account

        global_state.environment.active_account.set_balance(0)
        global_state.environment.active_account.deleted = True
        global_state.current_transaction.end(global_state)

    @StateTransition()
    def revert_(self, global_state: GlobalState) -> None:
        """

        :param global_state:
        """
        state = global_state.mstate
        offset, length = state.stack.pop(), state.stack.pop()
        return_data = [global_state.new_bitvec("return_data", 8)]
        try:
            return_data = state.memory[
                util.get_concrete_int(offset) : util.get_concrete_int(offset + length)
            ]
        except TypeError:
            log.debug("Return with symbolic length or offset. Not supported")
        global_state.current_transaction.end(
            global_state, return_data=return_data, revert=True
        )

    @StateTransition()
    def assert_fail_(self, global_state: GlobalState):
        """

        :param global_state:
        """
        # 0xfe: designated invalid opcode
        raise InvalidInstruction

    @StateTransition()
    def invalid_(self, global_state: GlobalState):
        """

        :param global_state:
        """
        raise InvalidInstruction

    @StateTransition()
    def stop_(self, global_state: GlobalState):
        """

        :param global_state:
        """
        global_state.current_transaction.end(global_state)

    @staticmethod
    def _write_symbolic_returndata(
        global_state: GlobalState, memory_out_offset: BitVec, memory_out_size: BitVec
    ):
        """
        Writes symbolic return-data into memory, The memory offset and size should be concrete
        :param global_state:
        :param memory_out_offset:
        :param memory_out_size:
        :return:
        """
        if memory_out_offset.symbolic is True or memory_out_size.symbolic is True:
            return
        for i in range(memory_out_size.value):
            global_state.mstate.memory[memory_out_offset + i] = global_state.new_bitvec(
                "call_output_var({})_{}".format(
                    simplify(memory_out_offset + i), global_state.mstate.pc
                ),
                8,
            )

    @StateTransition()
    def call_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        instr = global_state.get_current_instruction()
        environment = global_state.environment

        memory_out_size, memory_out_offset = global_state.mstate.stack[-7:-5]
        try:
            (
                callee_address,
                callee_account,
                call_data,
                value,
                gas,
                memory_out_offset,
                memory_out_size,
            ) = get_call_parameters(global_state, self.dynamic_loader, True)

            if callee_account is not None and callee_account.code.bytecode == "":
                log.debug("The call is related to ether transfer between accounts")
                sender = environment.active_account.address
                receiver = callee_account.address
                transfer_ether(global_state, sender, receiver, value)

                global_state.mstate.stack.append(
                    global_state.new_bitvec("retval_" + str(instr["address"]), 256)
                )
                return [global_state]

        except ValueError as e:
            log.debug(
                "Could not determine required parameters for call, putting fresh symbol on the stack. \n{}".format(
                    e
                )
            )
            self._write_symbolic_returndata(
                global_state, memory_out_offset, memory_out_size
            )
            # TODO: decide what to do in this case
            global_state.mstate.stack.append(
                global_state.new_bitvec("retval_" + str(instr["address"]), 256)
            )
            return [global_state]

        if environment.static:
            if isinstance(value, int) and value > 0:
                raise WriteProtection(
                    "Cannot call with non zero value in a static call"
                )
            if isinstance(value, BitVec):
                if value.symbolic:
                    global_state.world_state.constraints.append(
                        value == symbol_factory.BitVecVal(0, 256)
                    )
                elif value.value > 0:
                    raise WriteProtection(
                        "Cannot call with non zero value in a static call"
                    )

        native_result = native_call(
            global_state, callee_address, call_data, memory_out_offset, memory_out_size
        )
        if native_result:
            return native_result
        transaction = MessageCallTransaction(
            world_state=global_state.world_state,
            gas_price=environment.gasprice,
            gas_limit=gas,
            origin=environment.origin,
            caller=environment.active_account.address,
            callee_account=callee_account,
            call_data=call_data,
            call_value=value,
            static=environment.static,
        )
        raise TransactionStartSignal(transaction, self.op_code, global_state)

    @StateTransition()
    def call_post(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """

        return self.post_handler(global_state, function_name="call")

    @StateTransition()
    def callcode_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        instr = global_state.get_current_instruction()
        environment = global_state.environment
        memory_out_size, memory_out_offset = global_state.mstate.stack[-7:-5]
        try:
            (
                callee_address,
                callee_account,
                call_data,
                value,
                gas,
                _,
                _,
            ) = get_call_parameters(global_state, self.dynamic_loader, True)

            if callee_account is not None and callee_account.code.bytecode == "":
                log.debug("The call is related to ether transfer between accounts")
                sender = global_state.environment.active_account.address
                receiver = callee_account.address
                transfer_ether(global_state, sender, receiver, value)

                global_state.mstate.stack.append(
                    global_state.new_bitvec("retval_" + str(instr["address"]), 256)
                )
                return [global_state]

        except ValueError as e:
            log.debug(
                "Could not determine required parameters for call, putting fresh symbol on the stack. \n{}".format(
                    e
                )
            )
            self._write_symbolic_returndata(
                global_state, memory_out_offset, memory_out_size
            )
            global_state.mstate.stack.append(
                global_state.new_bitvec("retval_" + str(instr["address"]), 256)
            )
            return [global_state]

        transaction = MessageCallTransaction(
            world_state=global_state.world_state,
            gas_price=environment.gasprice,
            gas_limit=gas,
            origin=environment.origin,
            code=callee_account.code,
            caller=environment.address,
            callee_account=environment.active_account,
            call_data=call_data,
            call_value=value,
            static=environment.static,
        )
        raise TransactionStartSignal(transaction, self.op_code, global_state)

    @StateTransition()
    def callcode_post(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        instr = global_state.get_current_instruction()
        memory_out_size, memory_out_offset = global_state.mstate.stack[-7:-5]
        try:
            (
                callee_address,
                _,
                _,
                value,
                _,
                memory_out_offset,
                memory_out_size,
            ) = get_call_parameters(global_state, self.dynamic_loader, True)
        except ValueError as e:
            log.debug(
                "Could not determine required parameters for call, putting fresh symbol on the stack. \n{}".format(
                    e
                )
            )
            self._write_symbolic_returndata(
                global_state, memory_out_offset, memory_out_size
            )
            global_state.mstate.stack.append(
                global_state.new_bitvec("retval_" + str(instr["address"]), 256)
            )
            return [global_state]

        if global_state.last_return_data is None:
            # Put return value on stack
            return_value = global_state.new_bitvec(
                "retval_" + str(instr["address"]), 256
            )
            global_state.mstate.stack.append(return_value)
            self._write_symbolic_returndata(
                global_state, memory_out_offset, memory_out_size
            )
            global_state.world_state.constraints.append(return_value == 0)
            return [global_state]

        try:
            memory_out_offset = (
                util.get_concrete_int(memory_out_offset)
                if isinstance(memory_out_offset, Expression)
                else memory_out_offset
            )
            memory_out_size = (
                util.get_concrete_int(memory_out_size)
                if isinstance(memory_out_size, Expression)
                else memory_out_size
            )
        except TypeError:
            global_state.mstate.stack.append(
                global_state.new_bitvec("retval_" + str(instr["address"]), 256)
            )
            return [global_state]

        # Copy memory
        global_state.mstate.mem_extend(
            memory_out_offset, min(memory_out_size, len(global_state.last_return_data))
        )
        for i in range(min(memory_out_size, len(global_state.last_return_data))):
            global_state.mstate.memory[
                i + memory_out_offset
            ] = global_state.last_return_data[i]

        # Put return value on stack
        return_value = global_state.new_bitvec("retval_" + str(instr["address"]), 256)
        global_state.mstate.stack.append(return_value)
        global_state.world_state.constraints.append(return_value == 1)
        return [global_state]

    @StateTransition()
    def delegatecall_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        instr = global_state.get_current_instruction()
        environment = global_state.environment
        memory_out_size, memory_out_offset = global_state.mstate.stack[-6:-4]

        try:
            (
                callee_address,
                callee_account,
                call_data,
                value,
                gas,
                _,
                _,
            ) = get_call_parameters(global_state, self.dynamic_loader)

            if callee_account is not None and callee_account.code.bytecode == "":
                log.debug("The call is related to ether transfer between accounts")
                sender = global_state.environment.active_account.address
                receiver = callee_account.address
                transfer_ether(global_state, sender, receiver, value)

                global_state.mstate.stack.append(
                    global_state.new_bitvec("retval_" + str(instr["address"]), 256)
                )
                return [global_state]
        except ValueError as e:
            log.debug(
                "Could not determine required parameters for call, putting fresh symbol on the stack. \n{}".format(
                    e
                )
            )
            self._write_symbolic_returndata(
                global_state, memory_out_offset, memory_out_size
            )
            global_state.mstate.stack.append(
                global_state.new_bitvec("retval_" + str(instr["address"]), 256)
            )
            return [global_state]

        transaction = MessageCallTransaction(
            world_state=global_state.world_state,
            gas_price=environment.gasprice,
            gas_limit=gas,
            origin=environment.origin,
            code=callee_account.code,
            caller=environment.sender,
            callee_account=environment.active_account,
            call_data=call_data,
            call_value=environment.callvalue,
            static=environment.static,
        )
        raise TransactionStartSignal(transaction, self.op_code, global_state)

    @StateTransition()
    def delegatecall_post(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        instr = global_state.get_current_instruction()
        memory_out_size, memory_out_offset = global_state.mstate.stack[-6:-4]

        try:
            (
                callee_address,
                _,
                _,
                value,
                _,
                memory_out_offset,
                memory_out_size,
            ) = get_call_parameters(global_state, self.dynamic_loader)
        except ValueError as e:
            log.debug(
                "Could not determine required parameters for call, putting fresh symbol on the stack. \n{}".format(
                    e
                )
            )
            global_state.mstate.stack.append(
                global_state.new_bitvec("retval_" + str(instr["address"]), 256)
            )
            self._write_symbolic_returndata(
                global_state, memory_out_offset, memory_out_size
            )
            return [global_state]

        if global_state.last_return_data is None:
            # Put return value on stack
            return_value = global_state.new_bitvec(
                "retval_" + str(instr["address"]), 256
            )
            global_state.mstate.stack.append(return_value)
            global_state.world_state.constraints.append(return_value == 0)
            return [global_state]

        try:
            memory_out_offset = (
                util.get_concrete_int(memory_out_offset)
                if isinstance(memory_out_offset, Expression)
                else memory_out_offset
            )
            memory_out_size = (
                util.get_concrete_int(memory_out_size)
                if isinstance(memory_out_size, Expression)
                else memory_out_size
            )
        except TypeError:
            global_state.mstate.stack.append(
                global_state.new_bitvec("retval_" + str(instr["address"]), 256)
            )
            return [global_state]

            # Copy memory
        global_state.mstate.mem_extend(
            memory_out_offset, min(memory_out_size, len(global_state.last_return_data))
        )
        for i in range(min(memory_out_size, len(global_state.last_return_data))):
            global_state.mstate.memory[
                i + memory_out_offset
            ] = global_state.last_return_data[i]

        # Put return value on stack
        return_value = global_state.new_bitvec("retval_" + str(instr["address"]), 256)
        global_state.mstate.stack.append(return_value)
        global_state.world_state.constraints.append(return_value == 1)
        return [global_state]

    @StateTransition()
    def staticcall_(self, global_state: GlobalState) -> List[GlobalState]:
        """

        :param global_state:
        :return:
        """
        instr = global_state.get_current_instruction()
        environment = global_state.environment
        memory_out_size, memory_out_offset = global_state.mstate.stack[-6:-4]
        try:
            (
                callee_address,
                callee_account,
                call_data,
                value,
                gas,
                memory_out_offset,
                memory_out_size,
            ) = get_call_parameters(global_state, self.dynamic_loader)

            if callee_account is not None and callee_account.code.bytecode == "":
                log.debug("The call is related to ether transfer between accounts")
                sender = environment.active_account.address
                receiver = callee_account.address
                transfer_ether(global_state, sender, receiver, value)

                global_state.mstate.stack.append(
                    global_state.new_bitvec("retval_" + str(instr["address"]), 256)
                )
                return [global_state]

        except ValueError as e:
            log.debug(
                "Could not determine required parameters for call, putting fresh symbol on the stack. \n{}".format(
                    e
                )
            )
            self._write_symbolic_returndata(
                global_state, memory_out_offset, memory_out_size
            )
            global_state.mstate.stack.append(
                global_state.new_bitvec("retval_" + str(instr["address"]), 256)
            )
            return [global_state]

        native_result = native_call(
            global_state, callee_address, call_data, memory_out_offset, memory_out_size
        )

        if native_result:
            return native_result

        transaction = MessageCallTransaction(
            world_state=global_state.world_state,
            gas_price=environment.gasprice,
            gas_limit=gas,
            origin=environment.origin,
            code=callee_account.code,
            caller=environment.address,
            callee_account=callee_account,
            call_data=call_data,
            call_value=value,
            static=True,
        )
        raise TransactionStartSignal(transaction, self.op_code, global_state)

    @StateTransition()
    def staticcall_post(self, global_state: GlobalState) -> List[GlobalState]:
        return self.post_handler(global_state, function_name="staticcall")

    def post_handler(self, global_state, function_name: str):
        instr = global_state.get_current_instruction()
        if function_name in ("staticcall", "delegatecall"):
            memory_out_size, memory_out_offset = global_state.mstate.stack[-6:-4]
        else:
            memory_out_size, memory_out_offset = global_state.mstate.stack[-7:-5]

        try:
            with_value = function_name is not "staticcall"
            (
                callee_address,
                callee_account,
                call_data,
                value,
                gas,
                memory_out_offset,
                memory_out_size,
            ) = get_call_parameters(global_state, self.dynamic_loader, with_value)
        except ValueError as e:
            log.debug(
                "Could not determine required parameters for {}, putting fresh symbol on the stack. \n{}".format(
                    function_name, e
                )
            )
            self._write_symbolic_returndata(
                global_state, memory_out_offset, memory_out_size
            )
            global_state.mstate.stack.append(
                global_state.new_bitvec("retval_" + str(instr["address"]), 256)
            )
            return [global_state]

        if global_state.last_return_data is None:
            # Put return value on stack
            return_value = global_state.new_bitvec(
                "retval_" + str(instr["address"]), 256
            )
            global_state.mstate.stack.append(return_value)
            return [global_state]

        try:
            memory_out_offset = (
                util.get_concrete_int(memory_out_offset)
                if isinstance(memory_out_offset, Expression)
                else memory_out_offset
            )
            memory_out_size = (
                util.get_concrete_int(memory_out_size)
                if isinstance(memory_out_size, Expression)
                else memory_out_size
            )
        except TypeError:
            global_state.mstate.stack.append(
                global_state.new_bitvec("retval_" + str(instr["address"]), 256)
            )
            return [global_state]

        # Copy memory
        global_state.mstate.mem_extend(
            memory_out_offset, min(memory_out_size, len(global_state.last_return_data))
        )
        for i in range(min(memory_out_size, len(global_state.last_return_data))):
            global_state.mstate.memory[
                i + memory_out_offset
            ] = global_state.last_return_data[i]

        # Put return value on stack
        return_value = global_state.new_bitvec("retval_" + str(instr["address"]), 256)
        global_state.mstate.stack.append(return_value)
        global_state.world_state.constraints.append(return_value == 1)

        return [global_state]
