示例#1
0
def set_storage_slots(vyper_module: vy_ast.Module) -> None:
    """
    Parse module-level Vyper AST to calculate the layout of storage variables.
    """
    # Allocate storage slots from 0
    # note storage is word-addressable, not byte-addressable
    storage_slot = 0

    for node in vyper_module.get_children(vy_ast.FunctionDef):
        type_ = node._metadata["type"]
        if type_.nonreentrant is not None:
            type_.set_reentrancy_key_position(StorageSlot(storage_slot))
            # TODO use one byte - or bit - per reentrancy key
            # requires either an extra SLOAD or caching the value of the
            # location in memory at entrance
            storage_slot += 1

    for node in vyper_module.get_children(vy_ast.AnnAssign):
        type_ = node.target._metadata["type"]
        type_.set_position(StorageSlot(storage_slot))
        # CMC 2021-07-23 note that HashMaps get assigned a slot here.
        # I'm not sure if it's safe to avoid allocating that slot
        # for HashMaps because downstream code might use the slot
        # ID as a salt.
        storage_slot += math.ceil(type_.size_in_bytes / 32)
示例#2
0
def set_storage_slots(vyper_module: vy_ast.Module) -> StorageLayout:
    """
    Parse module-level Vyper AST to calculate the layout of storage variables.
    Returns the layout as a dict of variable name -> variable info
    """
    # Allocate storage slots from 0
    # note storage is word-addressable, not byte-addressable
    storage_slot = 0

    ret: Dict[str, Dict] = {}

    for node in vyper_module.get_children(vy_ast.FunctionDef):
        type_ = node._metadata["type"]
        if type_.nonreentrant is None:
            continue

        variable_name = f"nonreentrant.{type_.nonreentrant}"

        # a nonreentrant key can appear many times in a module but it
        # only takes one slot. after the first time we see it, do not
        # increment the storage slot.
        if variable_name in ret:
            _slot = ret[variable_name]["slot"]
            type_.set_reentrancy_key_position(StorageSlot(_slot))
            continue

        type_.set_reentrancy_key_position(StorageSlot(storage_slot))

        # TODO this could have better typing but leave it untyped until
        # we nail down the format better
        ret[variable_name] = {
            "type": "nonreentrant lock",
            "location": "storage",
            "slot": storage_slot,
        }

        # TODO use one byte - or bit - per reentrancy key
        # requires either an extra SLOAD or caching the value of the
        # location in memory at entrance
        storage_slot += 1

    for node in vyper_module.get_children(vy_ast.AnnAssign):

        if node.get("annotation.func.id") == "immutable":
            continue

        type_ = node.target._metadata["type"]
        type_.set_position(StorageSlot(storage_slot))

        # this could have better typing but leave it untyped until
        # we understand the use case better
        ret[node.target.id] = {"type": str(type_), "location": "storage", "slot": storage_slot}

        # CMC 2021-07-23 note that HashMaps get assigned a slot here.
        # I'm not sure if it's safe to avoid allocating that slot
        # for HashMaps because downstream code might use the slot
        # ID as a salt.
        storage_slot += math.ceil(type_.size_in_bytes / 32)

    return ret
示例#3
0
def set_storage_slots(vyper_module: vy_ast.Module) -> None:
    """
    Parse module-level Vyper AST to calculate the layout of storage variables.
    """
    available_slot = 0
    for node in vyper_module.get_children(vy_ast.AnnAssign):
        type_ = node.target._metadata["type"]
        type_.set_position(StorageSlot(available_slot))
        available_slot += math.ceil(type_.size_in_bytes / 32)
示例#4
0
def set_code_offsets(vyper_module: vy_ast.Module) -> None:

    offset = 0
    for node in vyper_module.get_children(
        vy_ast.AnnAssign, filters={"annotation.func.id": "immutable"}
    ):
        type_ = node._metadata["type"]
        type_.set_position(CodeOffset(offset))

        offset += math.ceil(type_.size_in_bytes / 32) * 32
示例#5
0
def _get_module_definitions(
        base_node: vy_ast.Module) -> Tuple[OrderedDict, Dict]:
    functions: OrderedDict = OrderedDict()
    events: Dict = {}
    for node in base_node.get_children(vy_ast.FunctionDef):
        if "external" in [
                i.id for i in node.decorator_list
                if isinstance(i, vy_ast.Name)
        ]:
            func = ContractFunction.from_FunctionDef(node)
            if node.name in functions:
                # compare the input arguments of the new function and the previous one
                # if one function extends the inputs, this is a valid function name overload
                existing_args = list(functions[node.name].arguments)
                new_args = list(func.arguments)
                for a, b in zip(existing_args, new_args):
                    if not isinstance(a, type(b)):
                        raise NamespaceCollision(
                            f"Interface contains multiple functions named '{node.name}' "
                            "with incompatible input types",
                            base_node,
                        )
                if len(new_args) <= len(existing_args):
                    # only keep the `ContractFunction` with the longest set of input args
                    continue
            functions[node.name] = func
    for node in base_node.get_children(vy_ast.AnnAssign,
                                       {"annotation.func.id": "public"}):
        name = node.target.id
        if name in functions:
            raise NamespaceCollision(
                f"Interface contains multiple functions named '{name}'",
                base_node)
        functions[name] = ContractFunction.from_AnnAssign(node)
    for node in base_node.get_children(vy_ast.EventDef):
        name = node.name
        if name in functions or name in events:
            raise NamespaceCollision(
                f"Interface contains multiple objects named '{name}'",
                base_node)
        events[name] = Event.from_EventDef(node)

    return functions, events
示例#6
0
def remove_unused_statements(vyper_module: vy_ast.Module) -> None:
    """
    Remove statement nodes that are unused after type checking.

    Once type checking is complete, we can remove now-meaningless statements to
    simplify the AST prior to IR generation.

    Arguments
    ---------
    vyper_module : Module
        Top-level Vyper AST node.
    """

    # constant declarations - values were substituted within the AST during folding
    for node in vyper_module.get_children(vy_ast.AnnAssign, {"annotation.func.id": "constant"}):
        vyper_module.remove_from_body(node)

    # `implements: interface` statements - validated during type checking
    for node in vyper_module.get_children(vy_ast.AnnAssign, {"target.id": "implements"}):
        vyper_module.remove_from_body(node)
示例#7
0
def validate_functions(vy_module: vy_ast.Module) -> None:
    """Analyzes a vyper ast and validates the function-level namespaces."""

    err_list = ExceptionList()
    namespace = get_namespace()
    for node in vy_module.get_children(vy_ast.FunctionDef):
        with namespace.enter_scope():
            try:
                FunctionNodeVisitor(vy_module, node, namespace)
            except VyperException as e:
                err_list.append(e)

    err_list.raise_if_not_empty()
示例#8
0
def remove_constant_declarations(vyper_module: vy_ast.Module) -> None:
    """
    Remove constant declaration nodes.

    Values for constants are subsituted within the AST during folding.
    Once type checking is complete their declarations are removed to
    simplify the AST prior to IR generation.

    Arguments
    ---------
    vyper_module : Module
        Top-level Vyper AST node.
    """

    for node in vyper_module.get_children(vy_ast.AnnAssign, {"annotation.func.id": "constant"}):
        vyper_module.remove_from_body(node)
示例#9
0
def set_code_offsets(vyper_module: vy_ast.Module) -> Dict:

    ret = {}
    offset = 0
    for node in vyper_module.get_children(
            vy_ast.VariableDecl, filters={"annotation.func.id": "immutable"}):
        type_ = node._metadata["type"]
        type_.set_position(CodeOffset(offset))

        len_ = math.ceil(type_.size_in_bytes / 32) * 32

        # this could have better typing but leave it untyped until
        # we understand the use case better
        ret[node.target.id] = {
            "type": str(type_),
            "offset": offset,
            "length": len_
        }

        offset += len_

    return ret
示例#10
0
def set_storage_slots_with_overrides(
    vyper_module: vy_ast.Module, storage_layout_overrides: StorageLayout
) -> StorageLayout:
    """
    Parse module-level Vyper AST to calculate the layout of storage variables.
    Returns the layout as a dict of variable name -> variable info
    """

    ret: Dict[str, Dict] = {}
    reserved_slots = StorageAllocator()

    # Search through function definitions to find non-reentrant functions
    for node in vyper_module.get_children(vy_ast.FunctionDef):
        type_ = node._metadata["type"]

        # Ignore functions without non-reentrant
        if type_.nonreentrant is None:
            continue

        variable_name = f"nonreentrant.{type_.nonreentrant}"

        # re-entrant key was already identified
        if variable_name in ret:
            _slot = ret[variable_name]["slot"]
            type_.set_reentrancy_key_position(StorageSlot(_slot))
            continue

        # Expect to find this variable within the storage layout override
        if variable_name in storage_layout_overrides:
            reentrant_slot = storage_layout_overrides[variable_name]["slot"]
            # Ensure that this slot has not been used, and prevents other storage variables
            # from using the same slot
            reserved_slots.reserve_slot_range(reentrant_slot, 1, variable_name)

            type_.set_reentrancy_key_position(StorageSlot(reentrant_slot))

            ret[variable_name] = {
                "type": "nonreentrant lock",
                "location": "storage",
                "slot": reentrant_slot,
            }
        else:
            raise StorageLayoutException(
                f"Could not find storage_slot for {variable_name}. "
                "Have you used the correct storage layout file?",
                node,
            )

    # Iterate through variables
    for node in vyper_module.get_children(vy_ast.AnnAssign):

        # Ignore immutable parameters
        if node.get("annotation.func.id") == "immutable":
            continue

        type_ = node.target._metadata["type"]

        # Expect to find this variable within the storage layout overrides
        if node.target.id in storage_layout_overrides:
            var_slot = storage_layout_overrides[node.target.id]["slot"]
            # Calculate how many storage slots are required
            storage_length = math.ceil(type_.size_in_bytes / 32)
            # Ensure that all required storage slots are reserved, and prevents other variables
            # from using these slots
            reserved_slots.reserve_slot_range(var_slot, storage_length, node.target.id)
            type_.set_position(StorageSlot(var_slot))

            ret[node.target.id] = {"type": str(type_), "location": "storage", "slot": var_slot}
        else:
            raise StorageLayoutException(
                f"Could not find storage_slot for {node.target.id}. "
                "Have you used the correct storage layout file?",
                node,
            )

    return ret
示例#11
0
def generate_public_variable_getters(vyper_module: vy_ast.Module) -> None:
    """
    Create getter functions for public variables.

    Arguments
    ---------
    vyper_module : Module
        Top-level Vyper AST node.
    """

    for node in vyper_module.get_children(vy_ast.AnnAssign, {"annotation.func.id": "public"}):
        func_type = node._metadata["type"]
        input_types, return_type = func_type.get_signature()
        input_nodes = []

        # use the annotation node as a base to build the input args and return type
        # starting with `args[0]` to remove the surrounding `public()` call`
        annotation = copy.deepcopy(node.annotation.args[0])

        # the base return statement is an `Attribute` node, e.g. `self.<var_name>`
        # for each input type we wrap it in a `Subscript` to access a specific member
        return_stmt: vy_ast.VyperNode = vy_ast.Attribute(
            value=vy_ast.Name(id="self"), attr=func_type.name
        )

        for i, type_ in enumerate(input_types):
            if not isinstance(annotation, vy_ast.Subscript):
                # if we get here something has failed in type checking
                raise CompilerPanic("Mismatch between node and input type while building getter")
            if annotation.value.get("id") == "HashMap":  # type: ignore
                # for a HashMap, split the key/value types and use the key type as the next arg
                arg, annotation = annotation.slice.value.elements  # type: ignore
            else:
                # for other types, build an input arg node from the expected type
                # and remove the outer `Subscript` from the annotation
                arg = vy_ast.Name(id=type_._id)
                annotation = annotation.value
            input_nodes.append(vy_ast.arg(arg=f"arg{i}", annotation=arg))

            # wrap the return statement in a `Subscript`
            return_stmt = vy_ast.Subscript(
                value=return_stmt, slice=vy_ast.Index(value=vy_ast.Name(id=f"arg{i}"))
            )

        # after iterating the input types, the remaining annotation node is our return type
        return_node = annotation
        if isinstance(return_node, vy_ast.Name) and return_node.id != return_type._id:
            # special case when the return type is an interface
            # TODO allow interfaces as return types and remove this
            return_node.id = return_type._id

        # join everything together as a new `FunctionDef` node, annotate it
        # with the type, and append it to the existing `Module` node
        expanded = vy_ast.FunctionDef.from_node(
            node.annotation,
            name=func_type.name,
            args=vy_ast.arguments(args=input_nodes, defaults=[],),
            body=[vy_ast.Return(value=return_stmt)],
            decorator_list=[vy_ast.Name(id="external"), vy_ast.Name(id="view")],
            returns=return_node,
        )
        expanded._metadata["type"] = func_type
        vyper_module.add_to_body(expanded)