def parse_yul_function_call(root: YulScope, node: YulNode,
                            ast: Dict) -> Optional[Expression]:
    args = [parse_yul(root, node, arg) for arg in ast["arguments"]]
    ident = parse_yul(root, node, ast["functionName"])

    if not isinstance(ident, Identifier):
        raise FortressException(
            "expected identifier from parsing function name")

    if isinstance(ident.value, YulBuiltin):
        name = ident.value.name
        if name in binary_ops:
            if name in ["shl", "shr", "sar"]:
                # lmao ok
                return BinaryOperation(args[1], args[0], binary_ops[name])

            return BinaryOperation(args[0], args[1], binary_ops[name])

        if name in unary_ops:
            return UnaryOperation(args[0], unary_ops[name])

        ident = Identifier(
            SolidityFunction(format_function_descriptor(ident.value.name)))

    if isinstance(ident.value, Function):
        return CallExpression(ident, args,
                              vars_to_typestr(ident.value.returns))
    if isinstance(ident.value, SolidityFunction):
        return CallExpression(ident, args,
                              vars_to_typestr(ident.value.return_type))

    raise FortressException(
        f"unexpected function call target type {str(type(ident.value))}")
Esempio n. 2
0
 def state_variable(self):
     if self._name.endswith("_slot"):
         return self._name[:-5]
     if self._name.endswith("_offset"):
         return self._name[:-7]
     to_log = f"Incorrect YUL parsing. {self} is not a solidity variable that can be seen as a state variable"
     raise FortressException(to_log)
Esempio n. 3
0
    def storage_size(self) -> Tuple[int, bool]:
        from fortress.core.declarations.structure import Structure
        from fortress.core.declarations.enum import Enum
        from fortress.core.declarations.contract import Contract

        if isinstance(self._type, Contract):
            return 20, False
        if isinstance(self._type, Enum):
            return int(math.ceil(math.log2(len(self._type.values)) / 8)), False
        if isinstance(self._type, Structure):
            # todo there's some duplicate logic here and fortress_core, can we refactor this?
            slot = 0
            offset = 0
            for elem in self._type.elems_ordered:
                size, new_slot = elem.type.storage_size
                if new_slot:
                    if offset > 0:
                        slot += 1
                        offset = 0
                elif size + offset > 32:
                    slot += 1
                    offset = 0

                if new_slot:
                    slot += math.ceil(size / 32)
                else:
                    offset += size
            if offset > 0:
                slot += 1
            return slot * 32, True
        to_log = f"{self} does not have storage size"
        raise FortressException(to_log)
    def analyze_contracts(self):  # pylint: disable=too-many-statements,too-many-branches
        if not self._parsed:
            raise FortressException(
                "Parse the contract before running analyses")
        self._convert_to_slithir()

        compute_dependency(self._core)
        self._core.compute_storage_layout()
        self._analyzed = True
    def new_node(self, node_type: NodeType, src: Union[str, Dict]) -> YulNode:
        if self._parent_func:
            node = self._parent_func.new_node(node_type, src)
        else:
            raise FortressException(
                "standalone yul objects are not supported yet")

        yul_node = YulNode(node, self)
        self._nodes.append(yul_node)
        return yul_node
    def new_node(self, node_type, src) -> YulNode:
        if self._function:
            node = self._function.new_node(node_type, src)
        else:
            raise FortressException(
                "standalone yul objects are not supported yet")

        yul_node = YulNode(node, self)
        self._nodes.append(yul_node)
        return yul_node
def parse_yul_identifier(root: YulScope, _node: YulNode,
                         ast: Dict) -> Optional[Expression]:
    name = ast["name"]

    if name in builtins:
        return Identifier(YulBuiltin(name))

    # check function-scoped variables
    if root.parent_func:
        variable = root.parent_func.get_local_variable_from_name(name)
        if variable:
            return Identifier(variable)

        variable = root.parent_func.contract.get_state_variable_from_name(name)
        if variable:
            return Identifier(variable)

    # check yul-scoped variable
    variable = root.get_yul_local_variable_from_name(name)
    if variable:
        return Identifier(variable.underlying)

    # check yul-scoped function

    func = root.get_yul_local_function_from_name(name)
    if func:
        return Identifier(func.underlying)

    # check for magic suffixes
    if name.endswith("_slot"):
        potential_name = name[:-5]
        var = root.function.contract.get_state_variable_from_name(
            potential_name)
        if var:
            return Identifier(var)
        var = root.function.get_local_variable_from_name(potential_name)
        if var and var.is_storage:
            return Identifier(var)
    if name.endswith("_offset"):
        potential_name = name[:-7]
        var = root.function.contract.get_state_variable_from_name(
            potential_name)
        if var:
            return Identifier(var)

    raise FortressException(f"unresolved reference to identifier {name}")
    def parse_contracts(self):  # pylint: disable=too-many-statements,too-many-branches
        if not self._underlying_contract_to_parser:
            logger.info(
                f"No contract were found in {self._core.filename}, check the correct compilation"
            )
        if self._parsed:
            raise Exception("Contract analysis can be run only once!")

        # First we save all the contracts in a dict
        # the key is the contractid
        for contract in self._underlying_contract_to_parser:
            if (contract.name.startswith("FortressInternalTopLevelContract")
                    and not contract.is_top_level):
                raise FortressException(
                    """Your codebase has a contract named 'FortressInternalTopLevelContract'.
Please rename it, this name is reserved for Fortress's internals""")
            if contract.name in self._core.contracts_as_dict:
                if contract.id != self._core.contracts_as_dict[
                        contract.name].id:
                    self._core.contract_name_collisions[contract.name].append(
                        contract.source_mapping_str)
                    self._core.contract_name_collisions[contract.name].append(
                        self._core.contracts_as_dict[
                            contract.name].source_mapping_str)
            else:
                self._contracts_by_id[contract.id] = contract
                self._core.contracts_as_dict[contract.name] = contract

        # Update of the inheritance
        for contract_parser in self._underlying_contract_to_parser.values():
            # remove the first elem in linearizedBaseContracts as it is the contract itself
            ancestors = []
            fathers = []
            father_constructors = []
            # try:
            # Resolve linearized base contracts.
            missing_inheritance = False

            for i in contract_parser.linearized_base_contracts[1:]:
                if i in contract_parser.remapping:
                    ancestors.append(
                        self._core.get_contract_from_name(
                            contract_parser.remapping[i]))
                elif i in self._contracts_by_id:
                    ancestors.append(self._contracts_by_id[i])
                else:
                    missing_inheritance = True

            # Resolve immediate base contracts
            for i in contract_parser.baseContracts:
                if i in contract_parser.remapping:
                    fathers.append(
                        self._core.get_contract_from_name(
                            contract_parser.remapping[i]))
                elif i in self._contracts_by_id:
                    fathers.append(self._contracts_by_id[i])
                else:
                    missing_inheritance = True

            # Resolve immediate base constructor calls
            for i in contract_parser.baseConstructorContractsCalled:
                if i in contract_parser.remapping:
                    father_constructors.append(
                        self._core.get_contract_from_name(
                            contract_parser.remapping[i]))
                elif i in self._contracts_by_id:
                    father_constructors.append(self._contracts_by_id[i])
                else:
                    missing_inheritance = True

            contract_parser.underlying_contract.set_inheritance(
                ancestors, fathers, father_constructors)

            if missing_inheritance:
                self._core.contracts_with_missing_inheritance.add(
                    contract_parser.underlying_contract)
                contract_parser.log_incorrect_parsing(
                    f"Missing inheritance {contract_parser}")
                contract_parser.set_is_analyzed(True)
                contract_parser.delete_content()

        contracts_to_be_analyzed = list(
            self._underlying_contract_to_parser.values())

        # Any contract can refer another contract enum without need for inheritance
        self._analyze_all_enums(contracts_to_be_analyzed)
        # pylint: disable=expression-not-assigned
        [
            c.set_is_analyzed(False)
            for c in self._underlying_contract_to_parser.values()
        ]

        libraries = [
            c for c in contracts_to_be_analyzed
            if c.underlying_contract.contract_kind == "library"
        ]
        contracts_to_be_analyzed = [
            c for c in contracts_to_be_analyzed
            if c.underlying_contract.contract_kind != "library"
        ]

        # We first parse the struct/variables/functions/contract
        self._analyze_first_part(contracts_to_be_analyzed, libraries)
        # pylint: disable=expression-not-assigned
        [
            c.set_is_analyzed(False)
            for c in self._underlying_contract_to_parser.values()
        ]

        # We analyze the struct and parse and analyze the events
        # A contract can refer in the variables a struct or a event from any contract
        # (without inheritance link)
        self._analyze_second_part(contracts_to_be_analyzed, libraries)
        [
            c.set_is_analyzed(False)
            for c in self._underlying_contract_to_parser.values()
        ]

        # Then we analyse state variables, functions and modifiers
        self._analyze_third_part(contracts_to_be_analyzed, libraries)

        self._parsed = True
    def _get_source_code(self, contract: Contract):  # pylint: disable=too-many-branches,too-many-statements
        """
        Save the source code of the contract in self._source_codes
        Patch the source code
        :param contract:
        :return:
        """
        src_mapping = contract.source_mapping
        content = self._fortress.source_code[
            src_mapping["filename_absolute"]].encode("utf8")
        start = src_mapping["start"]
        end = src_mapping["start"] + src_mapping["length"]

        to_patch = []
        # interface must use external
        if self._external_to_public and contract.contract_kind != "interface":
            for f in contract.functions_declared:
                # fallback must be external
                if f.is_fallback or f.is_constructor_variables:
                    continue
                if f.visibility == "external":
                    attributes_start = (
                        f.parameters_src.source_mapping["start"] +
                        f.parameters_src.source_mapping["length"])
                    attributes_end = f.returns_src.source_mapping["start"]
                    attributes = content[attributes_start:attributes_end]
                    regex = re.search(
                        r"((\sexternal)\s+)|(\sexternal)$|(\)external)$",
                        attributes)
                    if regex:
                        to_patch.append(
                            Patch(
                                attributes_start + regex.span()[0] + 1,
                                "public_to_external",
                            ))
                    else:
                        raise FortressException(
                            f"External keyword not found {f.name} {attributes}"
                        )

                    for var in f.parameters:
                        if var.location == "calldata":
                            calldata_start = var.source_mapping["start"]
                            calldata_end = calldata_start + var.source_mapping[
                                "length"]
                            calldata_idx = content[
                                calldata_start:calldata_end].find(" calldata ")
                            to_patch.append(
                                Patch(
                                    calldata_start + calldata_idx + 1,
                                    "calldata_to_memory",
                                ))

        if self._private_to_internal:
            for variable in contract.state_variables_declared:
                if variable.visibility == "private":
                    print(variable.source_mapping)
                    attributes_start = variable.source_mapping["start"]
                    attributes_end = attributes_start + variable.source_mapping[
                        "length"]
                    attributes = content[attributes_start:attributes_end]
                    print(attributes)
                    regex = re.search(r" private ", attributes)
                    if regex:
                        to_patch.append(
                            Patch(
                                attributes_start + regex.span()[0] + 1,
                                "private_to_internal",
                            ))
                    else:
                        raise FortressException(
                            f"private keyword not found {variable.name} {attributes}"
                        )

        if self._remove_assert:
            for function in contract.functions_and_modifiers_declared:
                for node in function.nodes:
                    for ir in node.irs:
                        if isinstance(
                                ir, SolidityCall
                        ) and ir.function == SolidityFunction("assert(bool)"):
                            to_patch.append(
                                Patch(node.source_mapping["start"],
                                      "line_removal"))
                            logger.info(
                                f"Code commented: {node.expression} ({node.source_mapping_str})"
                            )

        to_patch.sort(key=lambda x: x.index, reverse=True)

        content = content[start:end]
        for patch in to_patch:
            patch_type = patch.patch_type
            index = patch.index
            index = index - start
            if patch_type == "public_to_external":
                content = content[:index] + "public" + content[
                    index + len("external"):]
            if patch_type == "private_to_internal":
                content = content[:index] + "internal" + content[
                    index + len("private"):]
            elif patch_type == "calldata_to_memory":
                content = content[:index] + "memory" + content[
                    index + len("calldata"):]
            else:
                assert patch_type == "line_removal"
                content = content[:index] + " // " + content[index:]

        self._source_codes[contract] = content.decode("utf8")
 def max(self) -> int:
     if self.name in MaxValues:
         return MaxValues[self.name]
     raise FortressException(f"{self.name} does not have a max value")
 def min(self) -> int:
     if self.name in MinValues:
         return MinValues[self.name]
     raise FortressException(f"{self.name} does not have a min value")
def parse_yul_unsupported(_root: YulScope, _node: YulNode,
                          ast: Dict) -> Optional[Expression]:
    raise FortressException(
        f"no parser available for {ast['nodeType']} {json.dumps(ast, indent=2)}"
    )
def convert_yul_unsupported(root: YulScope, parent: YulNode,
                            ast: Dict) -> YulNode:
    raise FortressException(
        f"no converter available for {ast['nodeType']} {json.dumps(ast, indent=2)}"
    )