This standard introduces Cento, an index-based multi-facet routing model for the Ethereum Virtual Machine.
Unlike existing modular proxy standards, this specification establishes an explicit facet identity, realized as routing indices appended to calldata, while preserving conventional function selectors for compatibility with standardized Ethereum interfaces and external tooling.
The introduction of an explicit facet identity enables facet-level routing granularity with significantly reduced routing metadata, eliminates selector management overhead, and avoids selector collision risks while maintaining full compatibility with existing Ethereum infrastructure, protocols, wallets, explorers, and development tools.
The specification additionally defines how interoperable implementations of the Cento Proxy architecture organize calldata routing, atomic upgrades, facet management, introspection, and security.
The Ethereum ecosystem has demonstrated strong demand for modular smart contract architectures. ERC-2535 introduced selector-centric modular routing (Diamond), enabling protocols to distribute logic across multiple facets while maintaining a single external address. This approach has proven valuable in production deployments and established modular proxies as a practical foundation for complex upgradeable protocols.
Being an alternative design point for multi-facet protocols, the same motivations of ERC-2535 apply to this standard.
In multi-facet routing models, routing ultimately resolves to selecting a target facet for execution based on routing information contained in calldata. Existing selector-centric routing models infer this facet identity from behavioral identifiers, conflating routing and behavioral concerns.
As protocols become larger and more modular, using function selectors as routing identifiers causes routing metadata to scale with exported protocol functions rather than facets. Consequently, evolution of this routing metadata becomes increasingly coupled to selector management.
This architectural coupling gives rise to several recurring engineering challenges:
Routing metadata scales with function count: As protocols evolve, selector tables grow proportionally with externally callable functions rather than module count. Every upgrade requires selector management regardless of whether protocol organization changes.
Selector collision concerns: Protocol developers must ensure that independently developed facets do not unintentionally expose identical function selectors. While selector collisions are uncommon, avoiding them remains an ongoing consideration during protocol composition, library development, and integration.
Misaligned abstractions: Function selectors identify externally observable behavior, whereas facets represent implementation modules. Coupling these distinct concerns constrains routing to operate at the granularity of functions rather than protocol modules.
Upgrade complexity: Multi-facet upgrades require modifying routing metadata for potentially hundreds of individual selectors even when the protocol evolves at the level of facets.
Because the target facet is the fundamental routing destination in multi-facet routing, its identity should be represented explicitly rather than inferred from behavioral identifiers.
This standard approaches modular routing from a different architectural perspective. Instead of using function selectors as facet identifiers, it introduces the Cento routing model, which realizes that principle by representing facet identity as a routing index appended to calldata. This model treats protocol routing and interface compatibility as independent concerns:
This way, function selectors retain their original purpose as compatibility identifiers for standardized Ethereum interfaces, while routing indices become dedicated facet identifiers for Protocol Routing.
By separating routing and behavioral concerns in this way, routing metadata naturally scales with facets rather than exported functions, enabling facet-level routing granularity while preserving compatibility with existing Ethereum standards and tooling.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.
The terminology defined below follows the conceptual hierarchy established by this specification, beginning with foundational concepts, followed by routing-model semantics, architectural organization, and Cento-specific terminology.
Proxy: A software design pattern that intercepts and forwards interactions to another implementation while preserving a stable interface and storage context.
Routing Model: A conceptual model defining how routing destinations are identified within a proxy-based system. It establishes the permissible routing semantics of a multi-facet proxy architecture, including the facet identity, identifier collection, routing mechanisms, routing adapters, and upgrade operation. Routing models are further characterized by their architectural approach, facet identity type, routing model type, function routing types, and routing granularity. A routing model does not prescribe architectural organization and may be realized by multiple proxy architectures.
Proxy Architecture: An architectural pattern that organizes modular systems around the Proxy design pattern. A proxy architecture defines the architectural components, their responsibilities, and their interactions. It MAY directly employ the Proxy design pattern or build upon a routing model.
Multi-Facet Proxy Architecture: A proxy architecture that organizes a modular system into multiple implementation modules ("facets") and realizes a routing model by selecting and constraining its permissible set of routing semantics. It additionally defines its constituent components, routing metadata, identifier collection location, and the upgrade, observability, deployment, and storage layout models.
Architectural Approach: The fundamental architectural perspective adopted by a routing model for representing facet identity. This specification recognizes selector-centric and facet-centric approaches.
Facet Identity Type: The manner in which a routing model represents facet identity. Facet identity MAY be implicit or explicit.
Routing Model Type: The concrete realization of a routing model's facet identity. Examples include selector-based and index-based routing models.
Function Routing Types: The categories of externally callable functions defined by a routing model. Examples include Protocol Functions and Compatibility Functions.
Routing Granularity: The architectural level at which routing resolves execution. Function-level routing identifies individual externally callable functions, whereas facet-level routing identifies implementation modules.
Facet Identity: The architectural identity assigned to a facet for routing purposes. A routing model defines how facet identity is represented and resolved. Examples include function selectors and routing indices. Any facet identity MUST be derivable from some portion of calldata.
Identifier Collection: A routing data structure associating facet identities with their corresponding facet implementations. Examples include selector-to-facet mappings and routing tables.
Routing Mechanism: The procedure by which a routing model resolves a target facet from its facet identity. Examples include Selector Routing (routing mechanism implemented by Diamond) and Protocol Routing.
Routing Adapter: An optional compatibility layer translating one routing representation into another before the routing mechanism resolves the target facet. Interface Compatibility is an example of such an adapter.
Upgrade Operation: The operation defined by a routing model for atomically
modifying its identifier collection while maintaining routing metadata
consistency. Examples include diamondCut()
and atomicUpdate().
Architectural Component: A contract fulfilling a distinct architectural role within a multi-facet proxy architecture. Such components collectively realize the architecture by implementing its routing, upgrade, observability, deployment, storage layout, or other architectural models.
Router: An architectural component serving as the entrypoint of a multi-facet proxy architecture. The router receives external calls, maintains the shared storage context, resolves the destination facet according to the routing model, and delegates execution.
Facet: An architectural component serving as an implementation module in a multi-facet proxy architecture. Facets execute protocol logic within the router's shared storage context.
Routing Metadata: The data describing a routing architecture. It consists of an identifier collection and any additional auxiliary routing data. While the identifier collection defines how the router dispatches calls, the auxiliary routing data describes what that collection implies. Examples include an occupancy bitmap, implied by the routing table to accelerate introspection, and the set of supported interfaces, implied by the installed facets represented by the routing table.
Identifier Collection Location: The architectural location where an identifier collection is maintained. Examples include router storage, router bytecode, and beacon storage.
Upgrade Model: The architectural organization exposing and governing a
routing model's upgrade operation. Examples include
FacetManager and
DiamondCutFacet.
Observability Model: The architectural organization exposing routing state
and protocol composition for external inspection. Examples include
Observability and
DiamondLoupeFacet.
Deployment Model: The architectural organization governing deployment and instantiation of routers and facets. Examples include default deployment, clone deployment, and parameterized clone deployment.
Storage Layout Model: The architectural organization governing how storage is allocated to prevent collisions between implementation modules.
Cento: An index-based multi-facet routing model that identifies facets using routing indices. Hereafter, Cento MAY also be referred to as the Cento routing model. The model encompasses both Protocol Routing and Interface Compatibility mechanisms, but only the former is mandatory for an implementation to conform to the Cento routing model. The term "Cento" MAY also serve as an architectural identifier for patterns, standards, implementations, and implementation components based on this routing model. A proxy implementing the Cento routing model MAY be classified as a member of the Cento Proxy family.
Cento Proxy: A multi-facet proxy architecture realizing the Cento routing model. This specification defines the Cento Proxy architecture by constraining the permissible semantics of the Cento routing model and specifying the architectural organization required for interoperable implementations.
Routing Index: An explicit facet identifier represented by an unsigned integer. This specification standardizes 8-bit routing indices (0–255) appended to the end of calldata for Protocol Routing, which implementations of this standard MUST conform to. Future specifications MAY support wider routing indices while preserving interoperability.
Routing Table: A facet identifier collection associating routing indices with active facet addresses.
Protocol Routing: A routing mechanism that resolves the destination facet using a routing index appended to the end of calldata.
Interface Compatibility: A routing adapter mapping standardized Ethereum function selectors to routing indices before Protocol Routing.
Protocol Function: A function exposed through Protocol Routing. Protocol functions do not require globally unique function selectors.
Compatibility Function: A function additionally exposed through Interface Compatibility to support standardized Ethereum interfaces.
Atomic Update: An upgrade operation that atomically modifies the routing
metadata by installing, replacing, or removing facets while maintaining
consistency between the facet identifier collection and its auxiliary routing
data. The term MAY refer to the architectural operation itself, its canonical
implementation (atomicUpdate()), or its corresponding event
(AtomicUpdate),
depending on context.
This specification realizes the Cento routing model through a Protocol Routing mechanism and an Interface Compatibility routing adapter. Both paths resolve calls to routing indices and share the same facet dispatch mechanism.
A routing architecture of this specification MUST maintain its identifier collection in the router's storage, making the router the authoritative owner of routing state.
Protocol Functions are invoked by appending a routing index (one byte) to calldata.
+------------------+---------------+
| Arbitrary Bytes | Routing Index |
+------------------+---------------+
(0 or more) (1 byte)
The preceding bytes may be ABI-encoded function calls, raw data, or empty.
The router MUST:
DELEGATECALLThe router delegates all preceding calldata unchanged. The facet interprets this calldata according to its own logic (standard ABI decoding, raw parsing, or receive function handling if empty).
Compatibility Functions are any standardized Ethereum interfaces exposed by the protocol. In Compatibility Routing, they are dispatched through the Interface Compatibility routing adapter:
+----------+-----------+
| Selector | Function |
| | Arguments |
+----------+-----------+
(4 bytes)
The router MUST detect standardized interface selectors and dispatch the corresponding Compatibility Functions through the Interface Compatibility routing adapter without appending a routing index. Compatibility Routing coexists with Protocol Routing without mutual interference.
Examples of standardized interfaces include ERC-165 (interface detection) and ERC-173 (ownership), though any Ethereum interface using function selectors SHOULD be supported through compatibility routing.
Protocol Routing appends a routing index to the end of arbitrary calldata:
+------------------+---------------+
| Calldata (0+ B) | Index (1 B) |
+------------------+---------------+
The preceding calldata MAY consist of:
function(args) + indexarbitrary bytes + indexindex byte only (triggers receive function)The router removes the routing index before delegation, preserving the preceding calldata exactly as supplied.
Examples:
function(args) + index byte → facet receives function(args)raw data + index byte → facet receives raw data index byte only → facet receives empty calldata (triggers receive function)Compatibility Functions are dispatched through Interface Compatibility routing adapter and do not append a routing index.
Protocol execution uses DELEGATECALL:
delegatecall(gas(), facetAddress, _calldata, calldataSize, 0, 0)
The delegated facet executes within the router's shared storage context. The router MUST forward all available gas to the delegated facet and MUST propagate returndata, including reverts, unchanged to the caller.
The following educational example demonstrates one implementation of the routing architecture defined by this specification. An extensive reference implementation is available in the Reference Implementation section.
pragma solidity ^0.8.29;
fallback() external payable {
assembly {
let cds := calldatasize()
let idx := 0
let stripLen := 0
// Example dispatch heuristic:
// odd calldata -> Protocol Routing
// even calldata -> Compatibility Routing
if and(cds, 1) {
idx := byte(0, calldataload(sub(cds, 1)))
stripLen := 1
} else {
// Compatibility routing: dispatch by selector
switch shr(224, calldataload(0))
case 0x01ffc9a7 { idx := ERC165_INDEX } // ERC-165
case 0x8da5cb5b { idx := ERC173_INDEX } // ERC-173: owner()
case 0xf2fde38b { idx := ERC173_INDEX } // ERC-173: transferOwnership()
// Non-uniform calldata size may also be supported
default { idx := byte(0, calldataload(sub(cds, 1))) }
}
// Unified dispatch
let facet := sload(add(STORAGE_SLOT, idx))
if iszero(facet) {
mstore(0x00, ERR_FACET_NOT_FOUND)
mstore(0x04, idx)
revert(0x00, 0x24)
}
let size := sub(cds, stripLen)
calldatacopy(0, 0, size)
let ok := delegatecall(gas(), facet, 0, size, 0, 0)
returndatacopy(0, 0, returndatasize())
switch ok
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
Each installed facet SHALL be associated with exactly one routing index within the routing table.
A facet routing assignment associates a routing index with a facet contract address and is represented by the following structure:
pragma solidity ^0.8.29;
struct Facet {
/// @dev Routing index.
uint8 index;
/// @dev Facet contract address.
address facet;
}
Routing indices identify protocol modules rather than individual functions.
Multiple externally callable Protocol Functions MAY be implemented by the same facet without requiring additional routing metadata.
Protocol-specific function selectors are not required to be globally unique across different facets.
Implementations MUST perform upgrades at the granularity of facets rather than individual functions.
A compliant implementation MUST support the following atomic facet management operations:
Implementations MAY expose these operations through any authorization mechanism.
Routers MUST provide an ownership mechanism responsible for authorizing modifications to the routing table.
Ownership control MUST gate access to Atomic Updates. The ownership mechanism is implementation-specific and is not prescribed by this standard. See Security Considerations for authorization requirements.
Atomic Update is responsible for maintaining routing metadata consistency by updating both the identifier collection and its auxiliary routing data atomically.
Implementations MUST support any combination of the following operations within a single Atomic Update:
Either every modification succeeds, or the entire transaction reverts.
Storage migrations MAY be supplied as part of an Atomic Update.
If supplied, a storage migration contract SHALL be executed through
DELEGATECALL immediately after the routing metadata has been updated.
This enables:
If the storage migration fails, the entire Atomic Update MUST revert.
If no migration is required, the upgrade omits the migration contract.
Facet management is the mechanism by which protocol behavior evolves. The identifier collection determines the protocol composition, while its auxiliary routing data describes and supports that composition. Atomic Updates modify facet assignments and maintain the consistency of the associated routing data.
The IFacetManager interface standardizes the Atomic Update workflow.
All post-initialization facet modifications (installation, replacement,
removal) MUST occur through this interface with atomic semantics: either
all modifications in a single transaction succeed, or the entire transaction
reverts.
Implementations MUST emit an AtomicUpdate event for every Atomic Update.
This event provides an immutable, queryable record of the operations
performed by every Atomic Update, enabling indexers, governance systems,
and auditing tools to track protocol evolution.
pragma solidity ^0.8.29;
interface IFacetManager {
/// @notice Atomically updates the protocol configuration.
/// @param setF Facets to install, replace, or remove.
/// @param addI ERC interface identifiers to register.
/// @param remI ERC interface identifiers to unregister.
/// @param migrator Optional storage migration contract.
/// @param _calldata Encoded migration calldata.
/// @dev Executes all routing and interface updates before performing
/// an optional storage migration.
function atomicUpdate(
Facet[] calldata setF,
bytes4[] calldata addI,
bytes4[] calldata remI,
address migrator,
bytes calldata _calldata
) external;
/// @notice Emitted after an atomic protocol update.
/// @param setF Updated facet assignments.
/// @param addI Registered interface identifiers.
/// @param remI Unregistered interface identifiers.
/// @param migrator Storage migration contract.
/// @param _calldata Migration calldata.
event AtomicUpdate(
Facet[] setF,
bytes4[] addI,
bytes4[] remI,
address migrator,
bytes _calldata
);
}
The IObservability interface enables external systems to inspect the
protocol's routing table and derived occupancy state. This is essential for:
The observability functions provide complementary views of the routing table and its occupancy state:
These functions operate in view mode and impose no state changes, making them
safe for external tooling and governance systems to call repeatedly.
pragma solidity ^0.8.29;
interface IObservability {
/// @notice Returns all installed facet addresses.
/// @return Array of installed facet addresses.
function getFacets() external view returns (address[] memory);
/// @notice Returns all installed facet entries.
/// @dev Each entry contains both the routing index and facet address.
/// @return Array of installed facet entries.
function getFacetEntries() external view returns (Facet[] memory);
/// @notice Returns the facet installed at a routing index.
/// @param index Routing index.
/// @return Facet address, or the zero address if the slot is empty.
function getFacetAt(uint8 index) external view returns (address);
/// @notice Returns the number of installed facets.
/// @return Number of occupied routing slots.
function getFacetCount() external view returns (uint16);
/// @notice Returns the first available routing slot.
/// @return Index of the first unoccupied routing slot.
/// @dev Reverts if all routing slots are occupied.
function getFirstFreeSlot() external view returns (uint8);
}
ERC-165 interface detection allows external systems to discover the architectural and protocol interfaces supported by a router. Routers MUST accurately report the interface identifiers they support, including:
0x01ffc9a7 (IERC165)0x5378f98e (IFacetManager)0x1c60a259 (IObservability)Interface identifiers exposed by installed facets MUST likewise be reported through ERC-165. This allows contracts and external tooling to discover architectural capabilities and protocol interfaces dynamically as facets are installed, replaced, or removed.
Routers MUST initialize with three core facets installed at standard routing indices:
IFacetManager (facet management)IERC165, IObservability (interface detection and facet
introspection)The Ownership facet MUST provide an ownership mechanism authorizing routing table modifications.
Initial deployment MAY emit an AtomicUpdate event documenting these core
facets, but this is OPTIONAL.
The architectural decisions of this specification follow directly from the separation between routing and behavioral concerns introduced in the Motivation. The remaining sections explain why the selected realization of the Cento routing model was chosen over alternative realizations.
Scaling: selector-centric systems scale routing metadata with function count. Facet-centric systems scale with facet count. Most protocols have far fewer facets than externally callable functions.
Atomicity: Replacing a facet requires updating exactly one routing entry. Selector-centric systems require updating multiple entries (one per function).
Simplicity: Protocol developers organize code around facets. Facet-centric routing reflects this naturally.
Collision Safety: Because Protocol Routing identifies facets using routing indices rather than function selectors, Protocol Functions do not require globally unique selectors across facets, eliminating selector collision concerns.
This specification standardizes an 8-bit routing index (0–255). The choice of an 8-bit routing index provides several advantages:
Standardizing the routing index width promotes interoperability between compliant implementations.
Protocols requiring larger routing tables MAY extend the routing index width or introduce additional routing mechanisms. Such extensions are outside the scope of this specification, provided they preserve the routing semantics defined herein.
Routing semantics are intentionally independent from the storage layout model. Storage layout represents an implementation concern rather than an interoperability concern.
Consequently, this standard neither requires nor discourages any particular storage layout model. Future storage layout standards remain fully compatible with this specification.
In selector-centric routing models, function selectors serve two distinct purposes: routing (identifying which facet should execute) and compatibility (identifying which standardized interface is being invoked). These concerns evolve under different constraints. Routing benefits from facet-level abstraction. Compatibility requires backward-compatible selector tables.
This specification separates these concerns by assigning routing and compatibility to different architectural mechanisms: Protocol Routing uses routing indices (implementation-focused), whereas Interface Compatibility preserves selector-based interoperability for standardized Ethereum interfaces. Each mechanism can evolve independently.
ERC-2535 pioneered standardized modular proxy architectures. This standard shares the same goal: enabling protocol modularity.
The architectural difference is fundamental:
Both standards may coexist. Protocols prioritizing function-level routing flexibility may prefer ERC-2535. Protocols prioritizing facet-level routing efficiency and modularity should evaluate this standard.
Protocol developers organize software around implementation modules rather than individual selectors.
Development, auditing, testing, deployment, upgrades, documentation, and maintenance naturally occur at the facet level.
This specification therefore treats facets as first-class architectural components, and thus routing metadata reflects software architecture directly instead of indirectly through exported selectors.
This specification standardizes routing semantics while intentionally leaving implementation-specific concerns unspecified. In particular, it does not mandate:
Implementations remain free to innovate within these areas while preserving the interoperability defined by this specification.
This intentionally narrow scope also leaves room for future ERC standards to define interoperable extensions, including:
Such extensions remain compatible with this specification provided they preserve the routing semantics defined by this specification.
This specification preserves compatibility with existing Ethereum standards through the Interface Compatibility routing adapter.
Compliant implementations remain compatible with existing tooling supporting standardized Ethereum interfaces, including wallets, block explorers, development frameworks, ABI encoding and decoding, and Solidity external function calls.
This specification requires no modifications to Solidity, the EVM, ABI encoding, or Ethereum protocol rules.
Because the router removes the routing index before delegated execution, facets receive calldata identical to that of conventional contract calls. Existing Solidity code may therefore be reused within facets without modification.
Architectures realizing selector-centric routing models are not automatically compliant with this specification because they employ a different routing model.
Conversely, compliant implementations of this specification are not required to expose selector-based routing metadata.
Selector-centric and facet-centric routing models are complementary architectural approaches and may coexist within the Ethereum ecosystem.
The reference implementation consists of the Solidity contracts, interfaces, libraries, and structs implementing the protocol defined by this specification. It focuses on the protocol logic, intentionally excludes deployment scripts, tests, and development tooling, and comprises the following source files:
CentoRouter.sol:
Router implementation and protocol entry point.
facets/
FacetManager.sol:
Facet Management interface implementationObservability.sol:
Protocol introspection and ERC-165 interface implementationOwnership.sol:
Ownership implementation conforming to the ERC-173 interface
interfaces/
IERC173.sol:
ERC-173 ownership interface definitionIFacetManager.sol:
Facet Management interface definitionIObservability.sol:
Protocol introspection interface definition
libraries/
LibBitmap.sol:
Bitmap utilities for tracking routing index occupancyLibCento.sol:
Shared protocol storage access and facet management operations
structs/
CentoStorage.sol:
Shared storage layout used by the router and facetsFacet.sol:
Facet descriptor structureThe implementation uses an ERC-7201 storage namespace for shared protocol state and tracks routing index occupancy using a compact bitmap-based data structure.
Atomic Updates directly modify routing metadata and therefore directly modify protocol composition. Consequently, implementations should:
atomicUpdate() to authorized entitiesImplementations relying on DELEGATECALL inherit the associated execution
risks:
Mitigation:
Facets should be deployed smart contracts containing executable code. Routers should reject installation of the following:
EOAs (Externally Owned Accounts): Facets must not be externally owned addresses. Delegation to an EOA will silently succeed without executing any code, creating undefined protocol behavior.
Empty contracts: Facets must not be contracts with no code. This includes newly created contracts or contracts that have self-destructed.
EIP-7702 delegated EOAs: EIP-7702 introduces a mechanism allowing EOAs to delegate code execution. Routers should reject EIP-7702 delegated EOAs as facets because they introduce a secondary authorization path outside the router's control. An EOA may change its delegation at any time, fundamentally altering protocol behavior without modifying the routing table. This is a critical security boundary violation.
Implementations should perform bytecode validation before installing a facet.
The reference implementation detects EIP-7702 delegations by checking for the
EIP-7702 magic prefix (0xef0100) in the first three bytes of the account's
code.
Failure to validate facets during installation can result in:
Routers must validate routing inputs and delegation targets before delegated execution. Invalid routing inputs or delegation targets should cause the transaction to revert before delegated execution.
Implementations should minimize the amount of work performed before completing this validation.
Routers should validate the following:
receive() or perform an explicit calldata-length check.Failure to validate can result in:
CALLDATACOPY)Protocol Routing removes the routing index from calldata before delegation.
Attempting to remove a routing index from empty calldata may underflow the
computed calldata length, causing CALLDATACOPY to attempt copying an
effectively unbounded memory region and exhausting available gas.
Implementations should prevent fallback routing from processing empty calldata.
Implementations should provide a receive() function that consumes
empty calldata before fallback routing logic is reached. Alternatively,
add an explicit check in fallback() itself before copying calldata.
Equivalent protection mechanisms are also acceptable.
While Protocol Functions do not require unique function selectors to resolve target facets, Compatibility Functions do.
Consequently, custom Compatibility Functions should ensure that their selectors do not collide with standardized Ethereum interfaces or other Compatibility Functions exposed by the router.
If used, storage migrations introduce upgrade risks:
Installing, replacing, or removing facets changes protocol behavior. Implementations should:
Router immutability reduces the trusted computing base by ensuring that routing behavior cannot itself be modified after deployment. Implementations should prefer immutable routers whenever practical, because they:
If router upgrades are supported, they should be protected by authorization mechanisms at least as strong as those governing Atomic Updates.
Copyright and related rights waived via CC0.