Exemple #1
0
def _is_constant(f: Function) -> bool:  # pylint: disable=too-many-branches
    """
    Heuristic:
    - If view/pure with Solidity >= 0.4 -> Return true
    - If it contains assembly -> Return false (FortressCore doesn't analyze asm)
    - Otherwise check for the rules from
    https://solidity.readthedocs.io/en/v0.5.0/contracts.html?highlight=pure#view-functions
    with an exception: internal dynamic call are not correctly handled, so we consider them as non-constant
    :param f:
    :return:
    """
    if f.view or f.pure:
        if f.contract.fortress.crytic_compile and f.contract.fortress.crytic_compile.compiler_version:
            if not f.contract.fortress.crytic_compile.compiler_version.version.startswith(
                    "0.4"):
                return True
    if f.payable:
        return False
    if not f.is_implemented:
        return False
    if f.contains_assembly:
        return False
    if f.all_state_variables_written():
        return False
    for ir in f.all_slithir_operations():
        if isinstance(ir, InternalDynamicCall):
            return False
        if isinstance(ir,
                      (EventCall, NewContract, LowLevelCall, Send, Transfer)):
            return False
        if isinstance(ir, SolidityCall) and ir.function in [
                SolidityFunction("selfdestruct(address)"),
                SolidityFunction("suicide(address)"),
        ]:
            return False
        if isinstance(ir, HighLevelCall):
            if isinstance(ir.function,
                          Variable) or ir.function.view or ir.function.pure:
                # External call to constant functions are ensured to be constant only for solidity >= 0.5
                if (f.contract.fortress.crytic_compile and
                        f.contract.fortress.crytic_compile.compiler_version):
                    if f.contract.fortress.crytic_compile.compiler_version.version.startswith(
                            "0.4"):
                        return False
            else:
                return False
        if isinstance(ir, InternalCall):
            # Storage write are not properly handled by all_state_variables_written
            if any(parameter.is_storage
                   for parameter in ir.function.parameters):
                return False
    return True
Exemple #2
0
def _extract_assert(fortress: FortressCore) -> Dict[str, List[str]]:
    ret: Dict[str, List[str]] = {}
    for contract in fortress.contracts:
        functions_using_assert = []
        for f in contract.functions_entry_points:
            for v in f.all_solidity_calls():
                if v == SolidityFunction("assert(bool)"):
                    functions_using_assert.append(_get_name(f))
                    break
        if functions_using_assert:
            ret[contract.name] = functions_using_assert
    return ret
    def __str__(self):
        if (self.function == SolidityFunction("abi.decode()")
                and len(self.arguments) == 2
                and isinstance(self.arguments[1], list)):
            args = (str(self.arguments[0]) + "(" +
                    ",".join([str(a) for a in self.arguments[1]]) + ")")
        else:
            args = ",".join([str(a) for a in self.arguments])

        lvalue = ""
        if self.lvalue:
            if isinstance(self.lvalue.type, (list, )):
                lvalue = "{}({}) = ".format(
                    self.lvalue, ",".join(str(x) for x in self.lvalue.type))
            else:
                lvalue = "{}({}) = ".format(self.lvalue, self.lvalue.type)
        return lvalue + "SOLIDITY_CALL {}({})".format(self.function.full_name,
                                                      args)
Exemple #4
0
    def detect_deprecation_in_expression(self, expression):
        """Detects if an expression makes use of any deprecated standards.

        Returns:
            list of tuple: (detecting_signature, original_text, recommended_text)"""
        # Perform analysis on this expression
        export = ExportValues(expression)
        export_values = export.result()

        # Define our results list
        results = []

        # Check if there is usage of any deprecated solidity variables or functions
        for dep_var in self.DEPRECATED_SOLIDITY_VARIABLE:
            if SolidityVariableComposed(dep_var[0]) in export_values:
                results.append(dep_var)
        for dep_func in self.DEPRECATED_SOLIDITY_FUNCTIONS:
            if SolidityFunction(dep_func[0]) in export_values:
                results.append(dep_func)

        return results
Exemple #5
0
def arbitrary_send(func):
    if func.is_protected():
        return []

    ret = []
    for node in func.nodes:
        for ir in node.irs:
            if isinstance(ir, SolidityCall):
                if ir.function == SolidityFunction(
                        "ecrecover(bytes32,uint8,bytes32,bytes32)"):
                    return False
            if isinstance(ir, Index):
                if ir.variable_right == SolidityVariableComposed("msg.sender"):
                    return False
                if is_dependent(
                        ir.variable_right,
                        SolidityVariableComposed("msg.sender"),
                        func.contract,
                ):
                    return False
            if isinstance(ir, (HighLevelCall, LowLevelCall, Transfer, Send)):
                if isinstance(ir, (HighLevelCall)):
                    if isinstance(ir.function, Function):
                        if ir.function.full_name == "transferFrom(address,address,uint256)":
                            return False
                if ir.call_value is None:
                    continue
                if ir.call_value == SolidityVariableComposed("msg.value"):
                    continue
                if is_dependent(
                        ir.call_value,
                        SolidityVariableComposed("msg.value"),
                        func.contract,
                ):
                    continue

                if is_tainted(ir.destination, func.contract):
                    ret.append(node)

    return ret
 def will_return(self) -> bool:
     if not self.sons and self.type != NodeType.THROW:
         if SolidityFunction("revert()") not in self.solidity_calls:
             if SolidityFunction("revert(string)") not in self.solidity_calls:
                 return True
     return False
class ConstCandidateStateVars(AbstractDetector):
    """
    State variables that could be declared as constant detector.
    Not all types for constants are implemented in Solidity as of 0.4.25.
    The only supported types are value types and strings (ElementaryType).
    Reference: https://solidity.readthedocs.io/en/latest/contracts.html#constant-state-variables
    """

    ARGUMENT = "constable-states"
    HELP = "State variables that could be declared constant"
    IMPACT = DetectorClassification.OPTIMIZATION
    CONFIDENCE = DetectorClassification.HIGH

    WIKI = "https://github.com/crytic/fortress/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant"

    WIKI_TITLE = "State variables that could be declared constant"
    WIKI_DESCRIPTION = "Constant state variables should be declared constant to save gas."
    WIKI_RECOMMENDATION = "Add the `constant` attributes to state variables that never change."

    @staticmethod
    def _valid_candidate(v):
        return isinstance(v.type, ElementaryType) and not v.is_constant

    # https://solidity.readthedocs.io/en/v0.5.2/contracts.html#constant-state-variables
    valid_solidity_function = [
        SolidityFunction("keccak256()"),
        SolidityFunction("keccak256(bytes)"),
        SolidityFunction("sha256()"),
        SolidityFunction("sha256(bytes)"),
        SolidityFunction("ripemd160()"),
        SolidityFunction("ripemd160(bytes)"),
        SolidityFunction("ecrecover(bytes32,uint8,bytes32,bytes32)"),
        SolidityFunction("addmod(uint256,uint256,uint256)"),
        SolidityFunction("mulmod(uint256,uint256,uint256)"),
    ]

    @staticmethod
    def _is_constant_var(v):
        if isinstance(v, StateVariable):
            return v.is_constant
        return False

    def _constant_initial_expression(self, v):
        if not v.expression:
            return True

        export = ExportValues(v.expression)
        values = export.result()
        if not values:
            return True
        if all((val in self.valid_solidity_function
                or self._is_constant_var(val) for val in values)):
            return True
        return False

    def _detect(self):
        """Detect state variables that could be const"""
        results = []

        all_variables = [c.state_variables for c in self.fortress.contracts]
        all_variables = {item for sublist in all_variables for item in sublist}
        all_non_constant_elementary_variables = {
            v
            for v in all_variables if self._valid_candidate(v)
        }

        all_functions = [
            c.all_functions_called for c in self.fortress.contracts
        ]
        all_functions = list(
            {item
             for sublist in all_functions for item in sublist})

        all_variables_written = [
            f.state_variables_written for f in all_functions
            if not f.is_constructor_variables
        ]
        all_variables_written = {
            item
            for sublist in all_variables_written for item in sublist
        }

        constable_variables = [
            v for v in all_non_constant_elementary_variables
            if (not v in all_variables_written)
            and self._constant_initial_expression(v)
        ]
        # Order for deterministic results
        constable_variables = sorted(constable_variables,
                                     key=lambda x: x.canonical_name)

        # Create a result for each finding
        for v in constable_variables:
            info = [v, " should be constant\n"]
            json = self.generate_result(info)
            results.append(json)

        return results

    @staticmethod
    def _format(fortress, result):
        custom_format(fortress, result)
Exemple #8
0
def find_variable(  # pylint: disable=too-many-locals,too-many-statements
    var_name: str,
    caller_context: CallerContext,
    referenced_declaration: Optional[int] = None,
    is_super=False,
) -> Union[Variable, Function, Contract, SolidityVariable, SolidityFunction,
           Event, Enum, Structure, ]:
    from fortress.solc_parsing.declarations.contract import ContractSolc
    from fortress.solc_parsing.declarations.function import FunctionSolc

    # variable are looked from the contract declarer
    # functions can be shadowed, but are looked from the contract instance, rather than the contract declarer
    # the difference between function and variable come from the fact that an internal call, or an variable access
    # in a function does not behave similariy, for example in:
    # contract C{
    #   function f(){
    #     state_var = 1
    #     f2()
    #  }
    # state_var will refer to C.state_var, no mater if C is inherited
    # while f2() will refer to the function definition of the inherited contract (C.f2() in the context of C, or
    # the contract inheriting from C)
    # for events it's unclear what should be the behavior, as they can be shadowed, but there is not impact
    # structure/enums cannot be shadowed

    if isinstance(caller_context, ContractSolc):
        function: Optional[FunctionSolc] = None
        contract = caller_context.underlying_contract
        contract_declarer = caller_context.underlying_contract
    elif isinstance(caller_context, FunctionSolc):
        function = caller_context
        contract = function.underlying_function.contract
        contract_declarer = function.underlying_function.contract_declarer
    else:
        raise ParsingError("Incorrect caller context")

    if function:
        # We look for variable declared with the referencedDeclaration attr
        func_variables_renamed = function.variables_renamed
        if referenced_declaration and referenced_declaration in func_variables_renamed:
            return func_variables_renamed[
                referenced_declaration].underlying_variable
        # If not found, check for name
        func_variables = function.underlying_function.variables_as_dict
        if var_name in func_variables:
            return func_variables[var_name]
        # A local variable can be a pointer
        # for example
        # function test(function(uint) internal returns(bool) t) interna{
        # Will have a local variable t which will match the signature
        # t(uint256)
        func_variables_ptr = {
            get_pointer_name(f): f
            for f in function.underlying_function.variables
        }
        if var_name and var_name in func_variables_ptr:
            return func_variables_ptr[var_name]

    # variable are looked from the contract declarer
    contract_variables = contract_declarer.variables_as_dict
    if var_name in contract_variables:
        return contract_variables[var_name]

    # A state variable can be a pointer
    conc_variables_ptr = {
        get_pointer_name(f): f
        for f in contract_declarer.variables
    }
    if var_name and var_name in conc_variables_ptr:
        return conc_variables_ptr[var_name]

    if is_super:
        getter_available = lambda f: f.functions_declared
        d = {f.canonical_name: f for f in contract.functions}
        functions = {
            f.full_name: f
            for f in contract_declarer.available_elements_from_inheritances(
                d, getter_available).values()
        }
    else:
        functions = contract.available_functions_as_dict()
    if var_name in functions:
        return functions[var_name]

    if is_super:
        getter_available = lambda m: m.modifiers_declared
        d = {m.canonical_name: m for m in contract.modifiers}
        modifiers = {
            m.full_name: m
            for m in contract_declarer.available_elements_from_inheritances(
                d, getter_available).values()
        }
    else:
        modifiers = contract.available_modifiers_as_dict()
    if var_name in modifiers:
        return modifiers[var_name]

    # structures are looked on the contract declarer
    structures = contract.structures_as_dict
    if var_name in structures:
        return structures[var_name]

    structures_top_level = contract.fortress.top_level_structures
    for st in structures_top_level:
        if st.name == var_name:
            return st

    events = contract.events_as_dict
    if var_name in events:
        return events[var_name]

    enums = contract.enums_as_dict
    if var_name in enums:
        return enums[var_name]

    enums_top_level = contract.fortress.top_level_enums
    for enum in enums_top_level:
        if enum.name == var_name:
            return enum

    # If the enum is refered as its name rather than its canonicalName
    enums = {e.name: e for e in contract.enums}
    if var_name in enums:
        return enums[var_name]

    # Could refer to any enum
    all_enumss = [c.enums_as_dict for c in contract.fortress.contracts]
    all_enums = {k: v for d in all_enumss for k, v in d.items()}
    if var_name in all_enums:
        return all_enums[var_name]

    if var_name in SOLIDITY_VARIABLES:
        return SolidityVariable(var_name)

    if var_name in SOLIDITY_FUNCTIONS:
        return SolidityFunction(var_name)

    contracts = contract.fortress.contracts_as_dict
    if var_name in contracts:
        return contracts[var_name]

    if referenced_declaration:
        # id of the contracts is the referenced declaration
        # This is not true for the functions, as we dont always have the referenced_declaration
        # But maybe we could? (TODO)
        for contract_candidate in contract.fortress.contracts:
            if contract_candidate.id == referenced_declaration:
                return contract_candidate
        for function_candidate in caller_context.fortress_parser.all_functions_parser:
            if function_candidate.referenced_declaration == referenced_declaration:
                return function_candidate.underlying_function

    raise VariableNotFound("Variable not found: {} (context {})".format(
        var_name, caller_context))