def write_file(
    output_dir: Path,
    filename: str,
    content: str,
    allow_overwrite: bool = True,
    discard_if_exist: bool = False,
):
    """
    Write the content into output_dir/filename
    :param output_dir:
    :param filename:
    :param content:
    :param allow_overwrite: If true, allows to overwrite existing file (default: true). Emit warning if overwrites
    :param discard_if_exist: If true, it will not emit warning or overwrite the file if it exists, (default: False)
    :return:
    """
    file_to_write = Path(output_dir, filename)
    if file_to_write.exists():
        if discard_if_exist:
            return
        if not allow_overwrite:
            logger.info(
                yellow(
                    f"{file_to_write} already exist and will not be overwritten"
                ))
            return
        logger.info(yellow(f"Overwrite {file_to_write}"))
    else:
        logger.info(green(f"Write {file_to_write}"))
    with open(file_to_write, "w") as f:
        f.write(content)
Exemplo n.º 2
0
    def is_complex_code(self, contract):
        """
            Check if the code is complex
            Heuristic, the code is complex if:
                - One function has a cyclomatic complexity > 7
        Args:
            contract
        """

        is_complex = self._is_complex_code(contract)

        result = red("Yes") if is_complex else green("No")
        return result
Exemplo n.º 3
0
    def get_detectors_result(
            self) -> Tuple[str, List[Dict], int, int, int, int, int]:
        (
            all_results,
            optimization,
            informational,
            low,
            medium,
            high,
        ) = self._get_detectors_result()
        txt = "Number of optimization issues: {}\n".format(green(optimization))
        txt += "Number of informational issues: {}\n".format(
            green(informational))
        txt += "Number of low issues: {}\n".format(green(low))
        if medium > 0:
            txt += "Number of medium issues: {}\n".format(yellow(medium))
        else:
            txt += "Number of medium issues: {}\n".format(green(medium))
        if high > 0:
            txt += "Number of high issues: {}\n".format(red(high))
        else:
            txt += "Number of high issues: {}\n\n".format(green(high))

        return txt, all_results, optimization, informational, low, medium, high
Exemplo n.º 4
0
    def get_summary_erc20(self, contract):
        txt = ""

        pause, mint_unlimited, race_condition_mitigated = self._get_summary_erc20(
            contract)

        if pause:
            txt += yellow("Pausable") + "\n"

        if mint_unlimited is None:
            txt += green("No Minting") + "\n"
        else:
            if mint_unlimited:
                txt += red("∞ Minting") + "\n"
            else:
                txt += yellow("Minting") + "\n"

        if not race_condition_mitigated:
            txt += red("Approve Race Cond.") + "\n"

        return txt
Exemplo n.º 5
0
    def output(self, _filename):
        """
        _filename is not used
        Args:
            _filename(string)
        """

        txt = ""

        if not self.fortress.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.fortress)

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

            contract_file = self.fortress.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
Exemplo n.º 6
0
    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.fortress.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.fortress.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
    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 generate_erc20(
    contract: Contract, type_property: str, addresses: Addresses
):  # pylint: disable=too-many-locals
    """
    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.fortress.crytic_compile is None:
        logging.error("Please compile with crytic-compile")
        return
    if contract.fortress.crytic_compile.type not in [
        PlatformType.TRUFFLE,
        PlatformType.SOLC,
    ]:
        logging.error(f"{contract.fortress.crytic_compile.type} not yet supported by fortress-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

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

    # Generate the output directory
    output_dir = _platform_to_output_dir(contract.fortress.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.fortress.crytic_compile.target).parent, addresses
    )

    unit_test_info = ""

    # If truffle, generate unit tests
    if contract.fortress.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.fortress.crytic_compile.target} "
    txt += f"--contract {contract_name} --config {echidna_config_filename}"
    logger.info(green(txt))