def output(self, _filename):  # pylint: disable=too-many-locals
        """
        _filename is not used
        Args:
            _filename(string)
        """

        txt = ""

        all_contracts = []
        for c in self.contracts:
            if c.is_top_level:
                continue

            is_upgradeable_proxy = c.is_upgradeable_proxy
            is_upgradeable = c.is_upgradeable

            additional_txt_info = ""

            if is_upgradeable_proxy:
                additional_txt_info += " (Upgradeable Proxy)"

            if is_upgradeable:
                additional_txt_info += " (Upgradeable)"

            if c in self.slither.contracts_derived:
                additional_txt_info += " (Most derived contract)"

            txt += blue(f"\n+ Contract {c.name}{additional_txt_info}\n")
            additional_fields = output.Output(
                "",
                additional_fields={
                    "is_upgradeable_proxy": is_upgradeable_proxy,
                    "is_upgradeable": is_upgradeable,
                    "is_most_derived": c in self.slither.contracts_derived,
                },
            )

            # Order the function with
            # contract_declarer -> list_functions
            public = [
                (f.contract_declarer.name, f)
                for f in c.functions
                if (not f.is_shadowed and not f.is_constructor_variables)
            ]
            collect = collections.defaultdict(list)
            for a, b in public:
                collect[a].append(b)
            public = list(collect.items())

            for contract, functions in public:
                txt += blue(f"  - From {contract}\n")

                functions = sorted(functions, key=lambda f: f.full_name)

                for function in functions:
                    if function.visibility in ["external", "public"]:
                        txt += green(f"    - {function.full_name} ({function.visibility})\n")
                    if function.visibility in ["internal", "private"]:
                        txt += magenta(f"    - {function.full_name} ({function.visibility})\n")
                    if function.visibility not in [
                        "external",
                        "public",
                        "internal",
                        "private",
                    ]:
                        txt += f"    - {function.full_name}  ({function.visibility})\n"

                    additional_fields.add(
                        function, additional_fields={"visibility": function.visibility}
                    )

            all_contracts.append((c, additional_fields.data))

        self.info(txt)

        res = self.generate_output(txt)
        for contract, additional_fields in all_contracts:
            res.add(contract, additional_fields=additional_fields)

        return res
Beispiel #2
0
def check_initialization(s):

    initializable = s.get_contract_from_name('Initializable')

    if initializable is None:
        logger.info(
            yellow(
                'Initializable contract not found, the contract does not follow a standard initalization schema.'
            ))
        return

    initializer = initializable.get_modifier_from_signature('initializer()')

    init_info = ''

    double_calls_found = False
    missing_call = False
    initializer_modifier_missing = False

    for contract in s.contracts:
        if initializable in contract.inheritance:
            all_init_functions = _get_initialize_functions(contract)
            for f in all_init_functions:
                if not initializer in f.modifiers:
                    initializer_modifier_missing = True
                    logger.info(
                        red(f'{f.contract.name}.{f.name} does not call initializer'
                            ))
            most_derived_init = _get_most_derived_init(contract)
            if most_derived_init is None:
                init_info += f'{contract.name} has no initialize function\n'
                continue
            else:
                init_info += f'{contract.name} needs to be initialized by {most_derived_init.full_name}\n'
            all_init_functions_called = _get_all_internal_calls(
                most_derived_init) + [most_derived_init]
            missing_calls = [
                f for f in all_init_functions
                if not f in all_init_functions_called
            ]
            for f in missing_calls:
                logger.info(
                    red(f'Missing call to {f.contract.name}.{f.name} in {contract.name}'
                        ))
                missing_call = True
            double_calls = list(
                set([
                    f for f in all_init_functions_called
                    if all_init_functions_called.count(f) > 1
                ]))
            for f in double_calls:
                logger.info(
                    red(f'{f.contract.name + "." + f.full_name} is called multiple time in {contract.name}'
                        ))
                double_calls_found = True

    if not initializer_modifier_missing:
        logger.info(
            green('All the init functions have the initiliazer modifier'))

    if not double_calls_found:
        logger.info(green('No double call to init functions found'))

    if not missing_call:
        logger.info(green('No missing call to an init function found'))

    logger.info(
        green(
            'Check the deployement script to ensure that these functions are called:\n'
            + init_info))
Beispiel #3
0
def generate_erc20(contract: Contract, type_property: str, addresses: Addresses):
    """
    Generate the ERC20 tests
    Files generated:
    - interfaces.sol: generic crytic interface
    - Properties[CONTRACTNAME].sol: erc20 properties
    - Test[CONTRACTNAME].sol: Target, its constructor needs to be manually updated
    - If truffle
        - migrations/x_Test[CONTRACTNAME].js
        - test/crytic/InitializationTest[CONTRACTNAME].js: unit tests to check that the contract is correctly configured
        - test/crytic/Test[CONTRACTNAME].js: ERC20 checks
    - echidna_config.yaml: configuration file
    :param addresses:
    :param contract:
    :param type_property: One of ERC20_PROPERTIES.keys()
    :return:
    """
    if contract.slither.crytic_compile.type not in [PlatformType.TRUFFLE, PlatformType.SOLC]:
        logging.error(f"{contract.slither.crytic_compile.type} not yet supported by slither-prop")
        return

    # Check if the contract is an ERC20 contract and if the functions have the correct visibility
    errors = _check_compatibility(contract)
    if errors:
        logger.error(red(errors))
        return

    properties = ERC20_PROPERTIES.get(type_property, None)
    if properties is None:
        logger.error(
            f"{type_property} unknown. Types available {[x for x in ERC20_PROPERTIES.keys()]}"
        )
        return
    properties = properties.properties

    # Generate the output directory
    output_dir = _platform_to_output_dir(contract.slither.crytic_compile.platform)
    output_dir.mkdir(exist_ok=True)

    # Get the properties
    solidity_properties, unit_tests = _get_properties(contract, properties)

    # Generate the contract containing the properties
    generate_solidity_interface(output_dir, addresses)
    property_file = generate_solidity_properties(
        contract, type_property, solidity_properties, output_dir
    )

    # Generate the Test contract
    initialization_recommendation = _initialization_recommendation(type_property)
    contract_filename, contract_name = generate_test_contract(
        contract, type_property, output_dir, property_file, initialization_recommendation
    )

    # Generate Echidna config file
    echidna_config_filename = generate_echidna_config(
        Path(contract.slither.crytic_compile.target).parent, addresses
    )

    unit_test_info = ""

    # If truffle, generate unit tests
    if contract.slither.crytic_compile.type == PlatformType.TRUFFLE:
        unit_test_info = generate_truffle_test(contract, type_property, unit_tests, addresses)

    logger.info("################################################")
    logger.info(green(f"Update the constructor in {Path(output_dir, contract_filename)}"))

    if unit_test_info:
        logger.info(green(unit_test_info))

    logger.info(green("To run Echidna:"))
    txt = f"\t echidna-test {contract.slither.crytic_compile.target} "
    txt += f"--contract {contract_name} --config {echidna_config_filename}"
    logger.info(green(txt))
Beispiel #4
0
    def output(self, _filename):
        """
        _filename is not used
        Args:
            _filename(string)
        """

        txt = ""
        if not self.slither.crytic_compile:
            txt = "The EVM printer requires to compile with crytic-compile"
            self.info(red(txt))
            res = self.generate_output(txt)
            return res
        evm_info = _extract_evm_info(self.slither)

        for contract in self.slither.contracts_derived:
            txt += blue("Contract {}\n".format(contract.name))

            contract_file = self.slither.source_code[
                contract.source_mapping["filename_absolute"]
            ].encode("utf-8")
            contract_file_lines = open(
                contract.source_mapping["filename_absolute"], "r"
            ).readlines()

            contract_pcs = {}
            contract_cfg = {}

            for function in contract.functions:
                txt += blue(f"\tFunction {function.canonical_name}\n")

                # CFG and source mapping depend on function being constructor or not
                if function.is_constructor:
                    contract_cfg = evm_info["cfg_init", contract.name]
                    contract_pcs = evm_info["mapping_init", contract.name]
                else:
                    contract_cfg = evm_info["cfg", contract.name]
                    contract_pcs = evm_info["mapping", contract.name]

                for node in function.nodes:
                    txt += green("\t\tNode: " + str(node) + "\n")
                    node_source_line = (
                        contract_file[0 : node.source_mapping["start"]].count("\n".encode("utf-8"))
                        + 1
                    )
                    txt += green(
                        "\t\tSource line {}: {}\n".format(
                            node_source_line,
                            contract_file_lines[node_source_line - 1].rstrip(),
                        )
                    )
                    txt += magenta("\t\tEVM Instructions:\n")
                    node_pcs = contract_pcs.get(node_source_line, [])
                    for pc in node_pcs:
                        txt += magenta(
                            "\t\t\t0x{:x}: {}\n".format(
                                int(pc), contract_cfg.get_instruction_at(pc)
                            )
                        )

            for modifier in contract.modifiers:
                txt += blue(f"\tModifier {modifier.canonical_name}\n")
                for node in modifier.nodes:
                    txt += green("\t\tNode: " + str(node) + "\n")
                    node_source_line = (
                        contract_file[0 : node.source_mapping["start"]].count("\n".encode("utf-8"))
                        + 1
                    )
                    txt += green(
                        "\t\tSource line {}: {}\n".format(
                            node_source_line,
                            contract_file_lines[node_source_line - 1].rstrip(),
                        )
                    )
                    txt += magenta("\t\tEVM Instructions:\n")
                    node_pcs = contract_pcs.get(node_source_line, [])
                    for pc in node_pcs:
                        txt += magenta(
                            "\t\t\t0x{:x}: {}\n".format(
                                int(pc), contract_cfg.get_instruction_at(pc)
                            )
                        )

        self.info(txt)
        res = self.generate_output(txt)
        return res
Beispiel #5
0
    def output(self, _filename):
        """
            _filename is not used
            Args:
                _filename(string)
        """

        txt = ""

        all_contracts = []
        for c in self.contracts:

            is_upgradeable_proxy = c.is_upgradeable_proxy
            is_upgradeable = c.is_upgradeable

            additional_txt_info = ''

            if is_upgradeable_proxy:
                additional_txt_info += ' (Upgradeable Proxy)'

            if is_upgradeable:
                additional_txt_info += ' (Upgradeable)'

            if c in self.slither.contracts_derived:
                additional_txt_info += ' (Most derived contract)'

            txt += blue(f"\n+ Contract {c.name}{additional_txt_info}\n")
            additional_fields = output.Output(
                '',
                additional_fields={
                    'is_upgradeable_proxy': is_upgradeable_proxy,
                    'is_upgradeable': is_upgradeable,
                    'is_most_derived': c in self.slither.contracts_derived
                })

            # Order the function with
            # contract_declarer -> list_functions
            public = [(f.contract_declarer.name, f) for f in c.functions
                      if (not f.is_shadowed and not f.is_constructor_variables)
                      ]
            collect = collections.defaultdict(list)
            for a, b in public:
                collect[a].append(b)
            public = list(collect.items())

            for contract, functions in public:
                txt += blue("  - From {}\n".format(contract))

                functions = sorted(functions, key=lambda f: f.full_name)

                for function in functions:
                    if function.visibility in ['external', 'public']:
                        txt += green("    - {} ({})\n".format(
                            function.full_name, function.visibility))
                    if function.visibility in ['internal', 'private']:
                        txt += magenta("    - {} ({})\n".format(
                            function.full_name, function.visibility))
                    if function.visibility not in [
                            'external', 'public', 'internal', 'private'
                    ]:
                        txt += "    - {}  ({})\n".format(
                            function.full_name, function.visibility)

                    additional_fields.add(
                        function,
                        additional_fields={"visibility": function.visibility})

            all_contracts.append((c, additional_fields.data))

        self.info(txt)

        res = self.generate_output(txt)
        for contract, additional_fields in all_contracts:
            res.add(contract, additional_fields=additional_fields)

        return res
Beispiel #6
0
    def output(self, filename):
        """
            Output the inheritance relation

            _filename is not used
            Args:
                _filename(string)
        """
        info = 'Inheritance\n'

        if not self.contracts:
            return

        info += blue('Child_Contract -> ') + green('Immediate_Base_Contracts')
        info += green(' [Not_Immediate_Base_Contracts]')

        result = {'child_to_base': {}}

        for child in self.contracts:
            if child.is_top_level:
                continue
            info += blue(f'\n+ {child.name}\n')
            result['child_to_base'][child.name] = {
                'immediate': [],
                'not_immediate': []
            }
            if child.inheritance:

                immediate = child.immediate_inheritance
                not_immediate = [
                    i for i in child.inheritance if i not in immediate
                ]

                info += ' -> ' + green(", ".join(map(str, immediate))) + '\n'
                result['child_to_base'][child.name]['immediate'] = list(
                    map(str, immediate))
                if not_immediate:
                    info += ", [" + green(", ".join(map(
                        str, not_immediate))) + "]\n"
                    result['child_to_base'][
                        child.name]['not_immediate'] = list(
                            map(str, not_immediate))

        info += green('\n\nBase_Contract -> ') + blue(
            'Immediate_Child_Contracts') + '\n'
        info += blue(' [Not_Immediate_Child_Contracts]') + '\n'

        result['base_to_child'] = {}
        for base in self.contracts:
            if base.is_top_level:
                continue
            info += green(f'\n+ {base.name}') + '\n'
            children = list(self._get_child_contracts(base))

            result['base_to_child'][base.name] = {
                'immediate': [],
                'not_immediate': []
            }
            if children:
                immediate = [
                    child for child in children
                    if base in child.immediate_inheritance
                ]
                not_immediate = [
                    child for child in children if not child in immediate
                ]

                info += ' -> ' + blue(", ".join(map(str, immediate))) + '\n'
                result['base_to_child'][base.name]['immediate'] = list(
                    map(str, immediate))
                if not_immediate:
                    info += ', [' + blue(", ".join(map(
                        str, not_immediate))) + ']' + '\n'
                    result['base_to_child'][base.name]['not_immediate'] = list(
                        map(str, immediate))
        self.info(info)

        res = self.generate_output(info, additional_fields=result)

        return res
def compare_variables_order(contract1, contract2, missing_variable_check=True):

    results = {
        'missing_variables': [],
        'different-variables': [],
        'extra-variables': []
    }

    logger.info(
        green(
            f'\n## Run variables ordering checks between {contract1.name} and {contract2.name}... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks)'
        ))

    order1 = [
        variable for variable in contract1.state_variables
        if not variable.is_constant
    ]
    order2 = [
        variable for variable in contract2.state_variables
        if not variable.is_constant
    ]

    error_found = False
    idx = 0
    for idx in range(0, len(order1)):
        variable1 = order1[idx]
        if len(order2) <= idx:
            if missing_variable_check:
                info = f'Variable only in {contract1.name}: {variable1.name} ({variable1.source_mapping_str})'
                logger.info(yellow(info))

                res = Output(info)
                res.add(variable1)
                results['missing_variables'].append(res.data)

                error_found = True
            continue

        variable2 = order2[idx]

        if (variable1.name != variable2.name) or (variable1.type !=
                                                  variable2.type):
            info = f'Different variables between {contract1.name} and {contract2.name}:\n'
            info += f'\t Variable {idx} in {contract1.name}: {variable1.name} {variable1.type} ({variable1.source_mapping_str})\n'
            info += f'\t Variable {idx} in {contract2.name}: {variable2.name} {variable2.type} ({variable2.source_mapping_str})\n'
            logger.info(red(info))

            res = Output(info, additional_fields={'index': idx})
            res.add(variable1)
            res.add(variable2)
            results['different-variables'].append(res.data)

            error_found = True

    idx = idx + 1

    while idx < len(order2):
        variable2 = order2[idx]

        info = f'Extra variables in {contract2.name}: {variable2.name} ({variable2.source_mapping_str})\n'
        logger.info(yellow(info))
        res = Output(info, additional_fields={'index': idx})
        res.add(variable2)
        results['extra-variables'].append(res.data)
        idx = idx + 1

    if not error_found:
        logger.info(green('No error found'))

    return results