← Return to Forge

The Anti-Louper

EIP-2535 Diamond Standard: Inside-Out

Chapter 1 CC-BY 4.0

Das Proxy-Problem — Warum Monolithe nicht skalieren

"Ein Smart Contract ist unveränderlich. Bis er es nicht mehr ist."

1.1 Das Versprechen der Unveränderlichkeit

Smart Contracts auf Ethereum sind by design unveränderlich: Einmal deployed, kann kein Code mehr geändert werden. Das ist eine Stärke — Nutzer können dem Vertrag vertrauen, weil niemand ihn heimlich ändern kann.

Aber in der Praxis brauchen Protokolle Upgrades: Bugs müssen gefixt, Features hinzugefügt und Business-Logik weiterentwickelt werden.

Interactive: The 24KB Limit (EIP-170)
MyProtocol.sol
function deposit()
function withdraw()
function liquidate()
function getPrice()
...300 weitere...
⚠️ 24.576 Bytes Limit erreicht!

Folgen des Monolithen:

  • Willkürliche Aufteilung in mehrere Verträge
  • Komplex verkettete Aufrufe (hoher Gas-Overhead)
  • Storage muss über Verträge koordiniert werden

1.3 Die Proxy-Familie: Drei Generationen

Gen 1: Einfacher Proxy

Proxy speichert State, Implementation enthält Logik.

❌ Storage-Kollisionen (Slot 0)

Gen 2: Transparent Proxy

Implementation-Adresse in Pseudo-Zufalls-Slot (EIP-1967).

⚠️ Zwei Aufrufer-Klassen, ein 24KB-Limit

Gen 3: UUPS (EIP-1822)

Upgrade-Logik wandert in die Implementation.

⚠️ Single Point of Failure bei Upgrades

1.5 Die Einsicht hinter dem Diamond

Was, wenn wir statt einer Implementation viele haben — und den Aufruf zum richtigen Stück Logik routen?

Interactive: Diamond Routing
User Call
0x2e1a7d4d (withdraw)
Diamond.sol (Router)
address facet = selectorToFacet[msg.sig];
delegatecall(facet, msg.data);
Facet A
deposit()
Facet B
withdraw()
Facet C
liquidate()

Jede Facette ist ein eigener Vertrag mit eigenem Bytecode. Der Diamond ist nur ein Router. Das 24KB-Limit existiert nicht mehr als Systemgrenze — nur als Limit pro Facette.

Chapter 2 CC-BY 4.0

EIP-2535 — Die Spezifikation Schritt für Schritt

"A diamond is a contract with external functions that are supplied by contracts called facets."
— Nick Mudge, EIP-2535

2.1 Die drei Kerninterfaces

EIP-2535 definiert drei Pflicht-Interfaces. Wer alle drei implementiert, hat einen Diamond.

Interactive: The Diamond Interfaces

IDiamond

Definiert FacetCutAction (Add, Replace, Remove) und das DiamondCut-Event für on-chain Nachvollziehbarkeit.

event DiamondCut(...)

IDiamondCut

Fügt Funktionen hinzu, ersetzt oder entfernt sie. Ermöglicht State-Migrationen via _init Call.

function diamondCut(...) external;

IDiamondLoupe

Macht den Diamond vollständig introspektierbar. Ohne Off-Chain Datenbanken kann jeder Zustand ausgelesen werden.

function facets() external view;

2.2 Der Selektor: Das Routing-Herz

Jede Solidity-Funktion hat einen 4-Byte-Selektor, der aus dem Keccak256-Hash der Signatur berechnet wird. Der Diamond hält eine interne Mapping-Tabelle (selectorToFacet).

2.3 Die fallback()-Funktion: Das Herz des Routings

// Im Diamond.sol
fallback() external payable {
  LibDiamond.DiamondStorage storage ds;
  bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION;
  assembly { ds.slot := position }

  address facet = ds.selectorToFacetAndPosition[msg.sig].facetAddress;
  require(facet != address(0), "Diamond: Function does not exist");

  assembly {
    calldatacopy(0, 0, calldatasize())
    let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0)
    ...
  }
}

Wichtig: delegatecall bedeutet, dass die Facette im Storage-Kontext des Diamonds ausgeführt wird. Der Diamond ist der Datenspeicher, die Facetten sind die Logik-Provider.

2.4 Die drei FacetCutAction-Operationen

Add Neue Funktion hinzufügen (Selektor darf noch nicht existieren)
Replace Bestehende Funktion durch neue Facette ersetzen
Remove Funktion entfernen (Ziel-Adresse ist address(0))