ERC-8155: Kernel-Orchestrated Modular Contracts

Kernel-Orchestrated Modular Smart Contracts

Abstract

This ERC proposes a kernel-orchestrated modular smart contract framework built on EIP-2535 Diamond proxies. It introduces self-describing modules (Orbitals) with first-class on-chain metadata including identity, semantic versioning, storage schema hashes, and exported function selectors.

Key Features

  • IValenceModule interface for self-describing modules
  • Kernel-managed lifecycle (install, upgrade, remove) with explicit hooks
  • EIP-7201 namespaced storage for collision-free module composition
  • Machine-legible metadata for AI-first and automated orchestration
  • Module marketplace suitability with stable identifiers

Discussion Points

I’m looking for feedback on:

  1. The interface design and lifecycle hooks
  2. Storage namespacing approach using EIP-7201
  3. Semantic versioning on-chain representation
  4. Use cases and potential applications

Please share your thoughts and suggestions!

https://github.com/ethereum/ERCs/pull/1528

1 Like

Modular contracts using DELEGATECALL are vulnerable to attacks that alter the storage layout, which the owner intends to keep immutable. I recommend InvariantGuard, a library that helps maintain the security of locations declared immutable while maintaining upgradeability. Here’s an example in a diamond contract:

// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.8.20;
import "https://github.com/Helkomine/invariant-guard/blob/main/invariant-guard/InvariantGuardInternal.sol";

contract InvariantDiamondSimple is InvariantGuardInternal {
    bytes32 constant OWNER_STORAGE_POSITION = keccak256("erc8109.owner");

    /**
     * @notice Storage for owner of the diamond.
     *
     * @custom:storage-location erc8042:erc8109.owner
     */
    struct OwnerStorage {
        address owner;
    }

    bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("erc8109.diamond");

    /**
     * @notice Data stored for each function selector
     * @dev Facet address of function selector
     *      Position of selector in the 'bytes4[] selectors' array
     */
    struct FacetAndPosition {
        address facet;
        uint32 position;
    }

    /**
     * @custom:storage-location erc8042:erc8109.diamond
     */
    struct DiamondStorage {
        mapping(bytes4 functionSelector => FacetAndPosition) facetAndPosition;
        /**
         * Array of all function selectors that can be called in the diamond
         */
        bytes4[] selectors;
    }

    function getDiamondStorage() internal pure returns (DiamondStorage storage s) {
        bytes32 position = DIAMOND_STORAGE_POSITION;
        assembly {
            s.slot := position
        }
    }

    error FunctionNotFound(bytes4 _selector);

    function getInvariantSlots() internal pure returns (bytes32[] memory slots) {
        slots = new bytes32[](1);
        slots[0] = OWNER_STORAGE_POSITION;
    }

    function _diamondFallback() internal invariantStorage(getInvariantSlots()) returns (bool success, bytes memory data) {
        DiamondStorage storage s = getDiamondStorage();
        // Get facet from function selector
        address facet = s.facetAndPosition[msg.sig].facet;
        if (facet == address(0)) {
            revert FunctionNotFound(msg.sig);
        }

        (success, data) = facet.delegatecall(msg.data);
    }

    // Any attempt to replace `owner` variable of the diamond contract within the `fallback` function results in a revert
    fallback() external payable {
        (bool success, bytes memory data) = _diamondFallback();
        assembly {
            let ptr := add(data, 32)
            let len := mload(data)
            switch success
            case 0 { revert(ptr, len) }
            default { return(ptr, len) }
        }
    }
}

Hopefully, this will be helpful in your design context.
Link to InvariantGuard

Thanks, agreed on the core risk: any modular proxy/diamond that uses DELEGATECALL can be compromised if an upgrade introduces logic that writes into storage locations that were intended to remain immutable (or if the upgrade itself changes/assumes a different storage layout).

How Valence addresses this (by design):

  • Namespaced storage as the default state model (ERC-7201 style):
    Valence does not rely on a single shared “global layout”. Each package/module owns a dedicated storage namespace (a storage root slot), and all state reads/writes are routed through that namespace. This dramatically reduces the attack surface for “layout drift” because modules are not supposed to touch arbitrary slots.

  • Contract-as-Manifest + upgrade policy:
    Upgrades are orchestrated through a manifest that explicitly declares (a) which modules are installed, (b) which namespaces they own, and (c) which invariants / frozen slots must not be mutated. This makes “storage immutability” an explicit upgrade constraint rather than an implicit assumption.

  • Verification-first and CI checks:
    Valence upgrades are meant to be validated by automated checks (e.g., storage namespace ownership, forbidden-slot writes, selector-to-facet mapping integrity, and post-upgrade invariant checks). In other words, we treat upgrade safety as a pipeline problem, not only an on-chain pattern problem.

On InvariantGuard:
InvariantGuard is compatible with this approach and fits well as a defense-in-depth mechanism for globally critical slots (e.g., diamond ownership, upgrade admin, emergency stop). In Valence, we typically (i) keep those critical roots in a dedicated “Kernel/Admin” namespace and (ii) optionally guard them at the dispatch boundary (fallback / router) exactly as you show.

So yes your recommendation is helpful. In Valence terms, InvariantGuard is an additional hard fail-safe that complements namespaced storage + manifest-governed upgrades.

1 Like

Thanks, I want to clarify some differences in design intent, even though we have points of intersection in design. Actually, I’m looking for a more comprehensive and secure solution for installing arbitrary modules, which are often unverified and therefore potentially contain malicious backdoors for wallet hijacking. InvariantGuard is designed to protect users when making arbitrary external calls without unintentionally altering the storage layout. Issues related to administration and layout design (such as ERC-7201) are not the focus, although they are still important. But even so, they still can’t protect complex structures like mapping, and this would require a change from the protocol, I’ve already proposed an EIP for this problem. The module verification methods you proposed are still very valuable, but I need a more robust solution for unverified modules to maximize the potential of modular contract programming.

The modular contract design also needs further discussion because most of them are based on the diamond contract prototype. Because the diamond contract itself is already configured, it’s difficult to configure it in a way that allows it to define a new configuration, making it difficult to adapt to future upgrades. A temporary solution is to use an unconfigured, upgradeable proxy (like ERC-7229), but this makes the diamond a logic layer instead of a complete prototype. I’m currently researching a Diamond proxylation solution with the design approach described above. What do you think?