Exemplo n.º 1
0
def _check_integer_underflow(statespace, state, node):
    """
    Checks for integer underflow
    :param state: state from node to examine
    :param node: node to examine
    :return: found issue
    """
    issues = []
    instruction = state.get_current_instruction()

    if instruction['opcode'] == "SUB":

        stack = state.mstate.stack

        op0 = stack[-1]
        op1 = stack[-2]

        constraints = copy.deepcopy(node.constraints)

        # Filter for patterns that indicate benign underflows

        # Pattern 1: (96 + calldatasize_MAIN) - (96), where (96 + calldatasize_MAIN) would underflow if calldatasize is very large.
        # Pattern 2: (256*If(1 & storage_0 == 0, 1, 0)) - 1, this would underlow if storage_0 = 0
        if type(op0) == int and type(op1) == int:
            return []
        if re.search(r'calldatasize_', str(op0)):
            return []
        if re.search(r'256\*.*If\(1', str(op0), re.DOTALL) or re.search(r'256\*.*If\(1', str(op1), re.DOTALL):
            return []
        if re.search(r'32 \+.*calldata', str(op0), re.DOTALL) or re.search(r'32 \+.*calldata', str(op1), re.DOTALL):
            return []

        logging.debug("[INTEGER_UNDERFLOW] Checking SUB {0}, {1} at address {2}".format(str(op0), str(op1),
                                                                                        str(instruction['address'])))
        allowed_types = [int, BitVecRef, BitVecNumRef]

        if type(op0) in allowed_types and type(op1) in allowed_types:
            constraints.append(UGT(op1, op0))

            try:

                model = solver.get_model(constraints)

                # If we get to this point then there has been an integer overflow
                # Find out if the overflowed value is actually used
                interesting_usages = _search_children(statespace, node, (op0 - op1), index=node.states.index(state))
                logging.info(interesting_usages)

                # Stop if it isn't
                if len(interesting_usages) == 0:
                    return issues

                issue = Issue(node.contract_name, node.function_name, instruction['address'], "Integer Underflow",
                              "Warning")

                issue.description = "A possible integer underflow exists in the function `" + node.function_name + "`.\n" \
                                                                                                                   "The subtraction may result in a value < 0."

                issue.debug = solver.pretty_print_model(model)
                issues.append(issue)

            except UnsatError:
                logging.debug("[INTEGER_UNDERFLOW] no model found")
    return issues
Exemplo n.º 2
0
def execute(statespace):

    logging.debug("Executing module: UNCHECKED_RETVAL")

    issues = []

    for k in statespace.nodes:

        node = statespace.nodes[k]

        if NodeFlags.CALL_RETURN in node.flags:

            retval_checked = False

            for state in node.states:

                instr = state.get_current_instruction()

                if (instr['opcode'] == 'ISZERO'
                        and re.search(r'retval', str(state.mstate.stack[-1]))):
                    retval_checked = True
                    break

            if not retval_checked:

                address = state.get_current_instruction()['address']
                issue = Issue(node.contract_name, node.function_name, address,
                              "Unchecked CALL return value")

                issue.description = \
                    "The return value of an external call is not checked. Note that execution continue even if the called contract throws."

                issues.append(issue)

        else:

            nStates = len(node.states)

            for idx in range(0, nStates -
                             1):  # Ignore CALLs at last position in a node

                state = node.states[idx]
                instr = state.get_current_instruction()

                if (instr['opcode'] == 'CALL'):

                    retval_checked = False

                    for _idx in range(idx, idx + 10):

                        try:
                            _state = node.states[_idx]
                            _instr = _state.get_current_instruction()

                            if (_instr['opcode'] == 'ISZERO' and re.search(
                                    r'retval', str(_state.mstate.stack[-1]))):
                                retval_checked = True
                                break

                        except IndexError:
                            break

                    if not retval_checked:

                        address = instr['address']
                        issue = Issue(node.contract_name, node.function_name,
                                      address, "Unchecked CALL return value")

                        issue.description = \
                            "The return value of an external call is not checked. Note that execution continue even if the called contract throws."

                        issues.append(issue)

    return issues
Exemplo n.º 3
0
def execute(statespace):

    issues = []

    for call in statespace.calls:

        if ("callvalue" in str(call.value)):
            logging.debug("[ETHER_SEND] Skipping refund function")
            continue

        logging.debug("[ETHER_SEND] CALL with value " + str(call.value.val))
        issue = Issue("Ether send", "Warning")

        # We're only interested in calls that send Ether

        if call.value.type == VarType.CONCRETE:
            if call.value.val == 0:
                continue

        interesting = False

        issue.description = "In the function '" + call.node.function_name + "' "

        # Check the CALL target

        if re.search(r'caller', str(call.to)):
            issue.description += "a non-zero amount of Ether is sent to msg.sender.\n"
            interesting = True

        if re.search(r'calldata', str(call.to)):
            issue.description += "a non-zero amount of Ether is sent to an address taken from function arguments.\n"
            interesting = True

        issue.description += "Call value is " + str(call.value) + "\n"

        if interesting:

            node = call.node

            constrained = False
            can_solve = True

            while (can_solve and len(node.constraints)):

                constraint = node.constraints.pop()

                m = re.search(r'storage_([a-z0-9_&^]+)', str(constraint))

                overwrite = False

                if (m):
                    constrained = True
                    index = m.group(1)

                    try:

                        for s in statespace.sstors[index]:

                            if s.tainted:
                                issue.description += "\nThere is a check on storage index " + str(
                                    index
                                ) + ". This storage index can be written to by calling the function '" + s.node.function_name + "'."
                                break

                        if not overwrite:
                            logging.debug(
                                "[ETHER_SEND] No storage writes to index " +
                                str(index))
                            can_solve = False
                            break

                    except KeyError:
                        logging.debug(
                            "[ETHER_SEND] No storage writes to index " +
                            str(index))
                        can_solve = False
                        break

                # CALLER may also be constrained to hardcoded address. I.e. 'caller' and some integer

                elif (re.search(r"caller", str(constraint))
                      and re.search(r'[0-9]{20}', str(constraint))):
                    can_solve = False
                    break

            if not constrained:
                issue.description += "\nIt seems that this function can be called without restrictions."

            if can_solve:

                try:
                    model = solver.get_model(node.constraints)
                    issues.append(issue)

                    for d in model.decls():
                        logging.debug("[ETHER_SEND] main model: %s = 0x%x" %
                                      (d.name(), model[d].as_long()))
                except UnsatError:
                    logging.debug("[ETHER_SEND] no model found")

    return issues
Exemplo n.º 4
0
def execute(statespace):

    issues = []

    for call in statespace.calls:

        if (call.type == "DELEGATECALL"):

            state = call.state
            address = state.get_current_instruction()['address']

            if (call.node.function_name == "fallback"):

                stack = state.mstate.stack
                meminstart = get_variable(stack[-3])

                if meminstart.type == VarType.CONCRETE:

                    if (re.search(r'calldata.*_0',
                                  str(state.mstate.memory[meminstart.val]))):

                        issue = Issue(
                            call.node.contract_name, call.node.function_name,
                            address, "Call data forwarded with delegatecall()",
                            "Informational")

                        issue.description = \
                            "This contract forwards its call data via DELEGATECALL in its fallback function. " \
                            "This means that any function in the called contract can be executed. Note that the callee contract will have access to the storage of the calling contract.\n"

                        if (call.to.type == VarType.CONCRETE):
                            issue.description += ("DELEGATECALL target: " +
                                                  hex(call.to.val))
                        else:
                            issue.description += "DELEGATECALL target: " + str(
                                call.to)

                        issues.append(issue)

                if (call.to.type == VarType.SYMBOLIC):

                    issue = Issue(call.node.contract_name,
                                  call.node.function_name, address,
                                  call.type + " to a user-supplied address")

                    if ("calldata" in str(call.to)):

                        issue.description = \
                            "This contract delegates execution to a contract address obtained from calldata. "

                    else:
                        m = re.search(r'storage_([a-z0-9_&^]+)', str(call.to))

                        if (m):
                            idx = m.group(1)

                        func = statespace.find_storage_write(
                            state.environment.active_account.address, idx)

                        if (func):
                            issue.description = "This contract delegates execution to a contract address in storage slot " + str(
                                idx
                            ) + ". This storage slot can be written to by calling the function '" + func + "'. "

                        else:
                            logging.debug(
                                "[DELEGATECALL] No storage writes to index " +
                                str(idx))

                        issue.description += "Be aware that the called contract gets unrestricted access to this contract's state."
                        issues.append(issue)

    return issues
Exemplo n.º 5
0
def execute(statespace):

    logging.debug("Executing module: UNCHECKED_RETVAL")

    issues = []

    for k in statespace.nodes:

        node = statespace.nodes[k]

        if NodeFlags.CALL_RETURN in node.flags:

            retval_checked = False

            for state in node.states:

                instr = state.get_current_instruction()

                if instr["opcode"] == "ISZERO" and re.search(
                    r"retval", str(state.mstate.stack[-1])
                ):
                    retval_checked = True
                    break

            if not retval_checked:

                address = state.get_current_instruction()["address"]
                issue = Issue(
                    contract=node.contract_name,
                    function_name=node.function_name,
                    address=address,
                    bytecode=state.environment.code.bytecode,
                    title="Unchecked CALL return value",
                    swc_id=UNCHECKED_RET_VAL,
                )

                issue.description = (
                    "The return value of an external call is not checked. "
                    "Note that execution continue even if the called contract throws."
                )

                issues.append(issue)

        else:

            n_states = len(node.states)

            for idx in range(
                0, n_states - 1
            ):  # Ignore CALLs at last position in a node

                state = node.states[idx]
                instr = state.get_current_instruction()

                if instr["opcode"] == "CALL":

                    retval_checked = False

                    for _idx in range(idx, idx + 10):

                        try:
                            _state = node.states[_idx]
                            _instr = _state.get_current_instruction()

                            if _instr["opcode"] == "ISZERO" and re.search(
                                r"retval", str(_state.mstate.stack[-1])
                            ):
                                retval_checked = True
                                break

                        except IndexError:
                            break

                    if not retval_checked:

                        address = instr["address"]
                        issue = Issue(
                            contract=node.contract_name,
                            function_name=node.function_name,
                            bytecode=state.environment.code.bytecode,
                            address=address,
                            title="Unchecked CALL return value",
                            swc_id=UNCHECKED_RET_VAL,
                        )

                        issue.description = (
                            "The return value of an external call is not checked. "
                            "Note that execution continue even if the called contract throws."
                        )

                        issues.append(issue)

    return issues
Exemplo n.º 6
0
def execute(statespace):

    logging.debug("Executing module: INTEGER")

    issues = []

    for k in statespace.nodes:
        node = statespace.nodes[k]

        for state in node.states:

            instruction = state.get_current_instruction()

            if (instruction['opcode'] == "SUB"):

                stack = state.mstate.stack

                op0 = stack[-1]
                op1 = stack[-2]

                constraints = copy.deepcopy(node.constraints)

                if type(op0) == int and type(op1) == int:
                    continue

                if (re.search(r'calldatasize_', str(op0))) \
                    or (re.search(r'256\*.*If\(1', str(op0), re.DOTALL) or re.search(r'256\*.*If\(1', str(op1), re.DOTALL)) \
                    or (re.search(r'32 \+.*calldata', str(op0), re.DOTALL) or re.search(r'32 \+.*calldata', str(op1), re.DOTALL)):

                    # Filter for patterns that contain bening nteger underflows.

                    # Pattern 1: (96 + calldatasize_MAIN) - (96), where (96 + calldatasize_MAIN) would underflow if calldatasize is very large.
                    # Pattern 2: (256*If(1 & storage_0 == 0, 1, 0)) - 1, this would underlow if storage_0 = 0

                    continue

                logging.debug("[INTEGER_UNDERFLOW] Checking SUB " + str(op0) +
                              ", " + str(op1) + " at address " +
                              str(instruction['address']))

                allowed_types = [int, BitVecRef, BitVecNumRef]

                if type(op0) in allowed_types and type(op1) in allowed_types:
                    constraints.append(UGT(op1, op0))

                    try:

                        model = solver.get_model(constraints)

                        issue = Issue(node.contract_name, node.function_name,
                                      instruction['address'],
                                      "Integer Underflow", "Warning")

                        issue.description = "A possible integer underflow exists in the function " + node.function_name + ".\n" \
                            "The subtraction may result in a value < 0."

                        issue.debug = solver.pretty_print_model(model)
                        issues.append(issue)

                    except UnsatError:
                        logging.debug("[INTEGER_UNDERFLOW] no model found")

    return issues
Exemplo n.º 7
0
def execute(statespace):

    logging.debug("Executing module: INTEGER_UNDERFLOW")

    issues = []

    for k in statespace.nodes:
        node = statespace.nodes[k]

        for instruction in node.instruction_list:

            if(instruction['opcode'] == "SUB"):

                stack = node.states[instruction['address']].stack

                op0 = stack[-1]
                op1 = stack[-2]

                constraints = copy.deepcopy(node.constraints)

                if type(op0) == int and type(op1) == int:
                    continue

                if (re.search(r'calldatasize_', str(op0))) \
                    or (re.search(r'256\*.*If\(1', str(op0), re.DOTALL) or re.search(r'256\*.*If\(1', str(op1), re.DOTALL)) \
                    or (re.search(r'32 \+.*calldata', str(op0), re.DOTALL) or re.search(r'32 \+.*calldata', str(op1), re.DOTALL)):

                    # Filter for patterns that contain possible (but apparently non-exploitable) Integer underflows.

                    # Pattern 1: (96 + calldatasize_MAIN) - (96), where (96 + calldatasize_MAIN) would underflow if calldatasize is very large.
                    # Pattern 2: (256*If(1 & storage_0 == 0, 1, 0)) - 1, this would underlow if storage_0 = 0

                    # Both seem to be standard compiler outputs that exist in many contracts.

                    continue

                logging.debug("[INTEGER_UNDERFLOW] Checking SUB " + str(op0) + ", " + str(op1) + " at address " + str(instruction['address']))

                allowed_types = [int, BitVecRef, BitVecNumRef]

                if type(op0) in allowed_types and type(op1) in allowed_types:
                    constraints.append(UGT(op1,op0))

                    try:
                        
                        model = solver.get_model(constraints)

                        issue = Issue(node.module_name, node.function_name, instruction['address'], "Integer Underflow", "Warning")

                        issue.description = "A possible integer underflow exists in the function " + node.function_name + ".\n" \
                            "The SUB instruction at address " + str(instruction['address']) + " may result in a value < 0." 

                        issue.debug = "(" + str(op0) + ") - (" + str(op1) + ").]"

                        issues.append(issue)

                        for d in model.decls():
                            logging.debug("[INTEGER_UNDERFLOW] model: %s = 0x%x" % (d.name(), model[d].as_long()))

                    except UnsatError:
                        logging.debug("[INTEGER_UNDERFLOW] no model found")         

    return issues
Exemplo n.º 8
0
def execute(statespace):

    issues = []

    for call in statespace.calls:

        if (call.type == "DELEGATECALL" or call.type == "CALLCODE"):

            if (call.to.type == VarType.SYMBOLIC):

                if ("calldata" in str(call.to)):
                    issue = Issue(call.node.module_name,
                                  call.node.function_name, call.addr,
                                  call.type + " to dynamic address")

                    issue.description = \
                        "The function " + call.node.function_name + " delegates execution to a contract address obtained from calldata.\n" \
                        "Recipient address: " + str(call.to)

                    issues.append(issue)
                else:
                    m = re.search(r'storage_([a-z0-9_&^]+)', str(call.to))

                    if (m):
                        index = m.group(1)
                        logging.debug(
                            "DELEGATECALL to contract address in storage")

                        try:

                            for s in statespace.sstors[index]:

                                if s.tainted:
                                    issue = Issue(
                                        call.type +
                                        " to dynamic address in storage",
                                        "Warning")
                                    issue.description = \
                                        "The function " + call.node.function_name + " in contract '" + call.node.module_name + " delegates execution to a contract address stored in a state variable. " \
                                        "There is a check on storage index " + str(index) + ". This storage index can be written to by calling the function '" + s.node.function_name + "'.\n" \
                                        "Make sure that the contract address cannot be set by untrusted users."
                                    issues.append(issue)
                                    break

                        except KeyError:
                            logging.debug(
                                "[ETHER_SEND] No storage writes to index " +
                                str(index))

                    else:

                        issue = Issue(call.node.module_name,
                                      call.node.function_name, call.addr,
                                      "DELEGATECALL to dynamic address",
                                      "Informational")

                        issue.description = \
                            "The function " + call.node.function_name + " in contract '" + call.node.module_name + " delegates execution to a contract with a dynamic address." \
                            "To address:" + str(call.to)

                        issues.append(issue)

    return issues
Exemplo n.º 9
0
def _check_integer_overflow(statespace, state, node):
    """
    Checks for integer overflow
    :param statespace: statespace that is being examined
    :param state: state from node to examine
    :param node: node to examine
    :return: found issue
    """
    issues = []

    # Check the instruction
    instruction = state.get_current_instruction()
    if instruction['opcode'] not in ("ADD", "MUL"):
        return issues

    # Formulate overflow constraints
    stack = state.mstate.stack
    op0, op1 = stack[-1], stack[-2]

    # An integer overflow is possible if op0 + op1 or op0 * op1 > MAX_UINT
    # Do a type check
    allowed_types = [int, BitVecRef, BitVecNumRef]
    if not (type(op0) in allowed_types and type(op1) in allowed_types):
        return issues

    # Change ints to BitVec
    if type(op0) is int:
        op0 = BitVecVal(op0, 256)
    if type(op1) is int:
        op1 = BitVecVal(op1, 256)

    # Formulate expression
    if instruction['opcode'] == "ADD":
        expr = op0 + op1
    else:
        expr = op1 * op0

    # Check satisfiable
    constraint = Or(And(ULT(expr, op0), op1 != 0), And(ULT(expr, op1),
                                                       op0 != 0))
    model = _try_constraints(node.constraints, [constraint])

    if model is None:
        logging.debug("[INTEGER_OVERFLOW] no model found")
        return issues

    if not _verify_integer_overflow(statespace, node, expr, state, model,
                                    constraint, op0, op1):
        return issues

    # Build issue
    issue = Issue(node.contract_name, node.function_name,
                  instruction['address'], "Integer Overflow ", "Warning")

    issue.description = "A possible integer overflow exists in the function `{}`.\n" \
                        "The addition or multiplication may result in a value higher than the maximum representable integer.".format(
        node.function_name)
    issue.debug = solver.pretty_print_model(model)
    issues.append(issue)

    return issues
Exemplo n.º 10
0
def execute(statespace):

    logging.debug("Executing module: INTEGER_OVERFLOW")

    issues = []

    for k in statespace.nodes:
        node = statespace.nodes[k]

        for instruction in node.instruction_list:

            ''' This generates a lot of noise.

            if(instruction['opcode'] == "ADD"):

                stack = node.states[instruction['address']].stack

                op0 = stack[-1]
                op1 = stack[-2]

                if type(op0) == int and type(op1) == int:
                    continue

                logging.debug("[INTEGER_OVERFLOW] Checking ADD " + str(op0) + ", " + str(op1) + " at address " + str(instruction['address']))

                constraints = copy.deepcopy(node.constraints)

                constraints.append(UGT(op0, UINT_MAX - op1))

                try:
                    
                    model = solver.get_model(constraints)

                    issue = Issue(node.module_name, node.function_name, instruction['address'], "Integer Overflow", "Warning")

                    issue.description = "A possible integer overflow exists in the function " + node.function_name + ".\n" \
                        "The addition at address " + str(instruction['address']) + " may result in a value greater than UINT_MAX." 

                    issue.debug = "(" + str(op0) + ") + (" + str(op1) + ") > (" + hex(UINT_MAX.as_long()) + ")"

                    issues.append(issue)

                    for d in model.decls():
                        logging.debug("[INTEGER_OVERFLOW] model: %s = 0x%x" % (d.name(), model[d].as_long()))

                except UnsatError:
                    logging.debug("[INTEGER_OVERFLOW] no model found")   

            '''

            if(instruction['opcode'] == "MUL"):

                stack = node.states[instruction['address']].stack

                op0 = stack[-1]
                op1 = stack[-2]

                if (type(op0) == int and type(op1) == int) or type(op0) == BoolRef or type(op1) == BoolRef:
                    continue

                logging.debug("[INTEGER_OVERFLOW] Checking MUL " + str(op0) + ", " + str(op1) + " at address " + str(instruction['address']))

                if re.search(r'146150163733', str(op0), re.DOTALL) or re.search(r'146150163733', str(op1), re.DOTALL) or "(2 << 160 - 1)" in str(op0) or "(2 << 160 - 1)" in str(op1):
                    continue

                constraints = copy.deepcopy(node.constraints)

                constraints.append(UGT(op0, UDiv(UINT_MAX, op1)))
                
                try:
                    
                    model = solver.get_model(constraints)

                    issue = Issue(node.module_name, node.function_name, instruction['address'], "Integer Overflow", "Warning")

                    issue.description = "A possible integer overflow exists in the function " + node.function_name + ".\n" \
                        "The multiplication at address " + str(instruction['address']) + " may result in a value greater than UINT_MAX." 

                    issue.debug = "(" + str(op0) + ") * (" + str(op1) + ") > (" + hex(UINT_MAX.as_long()) + ")"

                    issues.append(issue)

                    logging.debug("Constraints: " + str(constraints))   

                    for d in model.decls():
                        logging.debug("[INTEGER_OVERFLOW] model: %s = 0x%x" % (d.name(), model[d].as_long()))

                except UnsatError:
                    logging.debug("[INTEGER_OVERFLOW] no model found")   

    return issues
Exemplo n.º 11
0
    def _check_integer_overflow(self, statespace, state, node):
        """
        Checks for integer overflow
        :param statespace: statespace that is being examined
        :param state: state from node to examine
        :param node: node to examine
        :return: found issue
        """
        issues = []

        # Check the instruction
        instruction = state.get_current_instruction()
        if instruction["opcode"] not in ("ADD", "MUL"):
            return issues

        # Formulate overflow constraints
        stack = state.mstate.stack
        op0, op1 = stack[-1], stack[-2]

        # An integer overflow is possible if op0 + op1 or op0 * op1 > MAX_UINT
        # Do a type check
        allowed_types = [int, BitVecRef, BitVecNumRef]
        if not (type(op0) in allowed_types and type(op1) in allowed_types):
            return issues

        # Change ints to BitVec
        if type(op0) is int:
            op0 = BitVecVal(op0, 256)
        if type(op1) is int:
            op1 = BitVecVal(op1, 256)

        # Formulate expression
        # FIXME: handle exponentiation
        if instruction["opcode"] == "ADD":
            operator = "add"
            expr = op0 + op1
            constraint = Not(BVAddNoOverflow(op0, op1, signed=False))
        else:
            operator = "multiply"
            expr = op1 * op0
            constraint = Not(BVMulNoOverflow(op0, op1, signed=False))

        # Check satisfiable
        model = self._try_constraints(node.constraints, [constraint])

        if model is None:
            logging.debug("[INTEGER_OVERFLOW] no model found")
            return issues

        # Build issue
        issue = Issue(
            contract=node.contract_name,
            function_name=node.function_name,
            address=instruction["address"],
            swc_id=INTEGER_OVERFLOW_AND_UNDERFLOW,
            bytecode=state.environment.code.bytecode,
            title="Integer Overflow",
            _type="Warning",
            gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used),
        )

        issue.description = "This binary {} operation can result in integer overflow.\n".format(
            operator)
        try:
            issue.debug = str(
                solver.get_transaction_sequence(
                    state, node.constraints + [constraint]))
        except UnsatError:
            return issues

        issues.append(issue)

        return issues
Exemplo n.º 12
0
    def _check_integer_underflow(self, statespace, state, node):
        """
        Checks for integer underflow
        :param state: state from node to examine
        :param node: node to examine
        :return: found issue
        """
        issues = []
        instruction = state.get_current_instruction()
        if instruction["opcode"] == "SUB":

            stack = state.mstate.stack

            op0 = stack[-1]
            op1 = stack[-2]

            constraints = copy.deepcopy(node.constraints)

            if type(op0) == int and type(op1) == int:
                return []

            logging.debug(
                "[INTEGER_UNDERFLOW] Checking SUB {0}, {1} at address {2}".
                format(str(op0), str(op1), str(instruction["address"])))

            allowed_types = [int, BitVecRef, BitVecNumRef]

            if type(op0) in allowed_types and type(op1) in allowed_types:
                constraints.append(
                    Not(BVSubNoUnderflow(op0, op1, signed=False)))
                try:

                    model = solver.get_model(constraints)

                    # If we get to this point then there has been an integer overflow
                    # Find out if the overflowed value is actually used
                    interesting_usages = self._search_children(
                        statespace,
                        node, (op0 - op1),
                        index=node.states.index(state))

                    # Stop if it isn't
                    if len(interesting_usages) == 0:
                        return issues

                    issue = Issue(
                        contract=node.contract_name,
                        function_name=node.function_name,
                        address=instruction["address"],
                        swc_id=INTEGER_OVERFLOW_AND_UNDERFLOW,
                        bytecode=state.environment.code.bytecode,
                        title="Integer Underflow",
                        _type="Warning",
                        gas_used=(state.mstate.min_gas_used,
                                  state.mstate.max_gas_used),
                    )

                    issue.description = (
                        "The subtraction can result in an integer underflow.\n"
                    )

                    issue.debug = str(
                        solver.get_transaction_sequence(
                            state, node.constraints))
                    issues.append(issue)

                except UnsatError:
                    logging.debug("[INTEGER_UNDERFLOW] no model found")
        return issues