DiamondProxy Standard on Soroban #1700
Replies: 2 comments 3 replies
-
|
This proposal is really exciting! I love the modular approach with the Diamond Proxy architecture and the flexibility it brings through the use of facets. I was curious about best practices for managing shared storage or existing state during upgrades. Are there strategies or recommendations for ensuring that changes in a facet or upgrades to the diamond itself don't inadvertently impact shared data or introduce unintended side effects? Looking forward to learning more and seeing how this develops! |
Beta Was this translation helpful? Give feedback.
-
|
Thanks for submitting this. Without diving too deep into motivation behind this, I would like to point out some issues that immediately strike me problematic or anti-patterns for Soroban. My main concern is authorization - with the specification provided I struggle to see how it would be possible to implement the pattern in the safe fashion while following the interface. But there is also a bunch of under-specification and usability concerns.
3.1 Key naming convention
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Stellar Diamond Proxy Proposal: Enabling Modular and Upgradeable Smart Contracts
Preamble
SEP: TBD
Title: Diamond Proxy Standard for Soroban
Author: Nomyx
Track: Standard
Status: Draft
Created: 2025-03-28
Updated: 2025-03-31
Version: 0.1.0
Discussion: TBD
Simple Summary
The Diamond Proxy Standard proposes a modular smart contract architecture for Soroban that enables multiple contract facets to share storage and functionality through a single entry point, allowing upgradeable contracts with improved modularity, code sharing, and domain-specific logic separation.
Dependencies
This SEP has no dependencies on other SEPs or CAPs.
Motivation
Current smart contract patterns on Soroban face several challenges:
The Diamond Proxy Standard addresses these issues by providing a modular architecture where multiple facet contracts share a common storage layer while being accessible through a single proxy entry point. This enables granular upgrades, clean separation of concerns, and efficient shared state management.
Abstract
The Diamond Proxy Standard defines a pattern for creating modular, upgradeable smart contracts on Soroban. Inspired by Ethereum's ERC-2535 (Diamond Standard) but adapted for Soroban's unique environment, this standard introduces a proxy contract that delegates calls to various facet contracts while providing a shared storage solution and standard introspection capabilities.
The key innovation is the combination of a delegation proxy with a dedicated shared storage contract that all facets can access. This architecture enables:
The standard includes definitions for the diamond proxy (including Loupe functions), facets, shared storage, and the factory contract responsible for deployment and initialization.
Specification
1. Core Components
1.1 DiamondProxy
The DiamondProxy is the entry point contract that users interact with. Internally, it maintains a mapping (e.g., Map<Symbol, Address>) associating function selectors with the Address of the facet contract responsible for handling them. It delegates calls to the appropriate facet based on this mapping and provides standard Loupe functions for introspection.
1.2 SharedStorageLayer
The SharedStorageLayer is a specialized contract that provides unified storage access for all facet contracts associated with a DiamondProxy. It stores data as raw Bytes internally but is accessed by facets via helper functions that handle serialization/deserialization. It segregates storage into instance, persistent, and temporary types.
1.3 Facets
Facets are individual contracts that implement specific functionality. They are designed to operate using the shared storage provided via the DiamondProxy.
Facets MUST use the
#[facet]macro. This macro automatically injects a standard initialization function with the signatureinit(env: Env, owner: Address, shared_storage: Address). During thediamond_cutprocess when a facet is added, the DiamondProxy calls this init function on the newly deployed facet instance, providing it with the necessary context (owner identity and the address of the SharedStorageLayer contract) to operate correctly.1.4 DiamondFactory
The DiamondFactory is responsible for deploying new diamond proxies, ensuring a standardized deployment process.
2. Diamond Operations
2.1 Initialization
Diamond initialization follows these steps:
2.2 Diamond Cut (Adding/Replacing/Removing Facets)
The diamond_cut operation is the core mechanism for modifying the diamond's structure. It takes a vector of FacetCut actions.
Behavior of FacetAction:
2.3 Facet Execution
When a function call is made to the DiamondProxy address (that isn't init, diamond_cut, or a Loupe function):
3. Shared Storage Access
Facets access the shared storage through helper methods provided via the env or a standard library/macro associated with the
#[facet]macro. These helpers abstract away the direct interaction with the SharedStorageLayer contract and handle serialization/deserialization.Key Naming Convention: Even though developers interact with helpers using logical keys (like Symbols or string literals), these keys are ultimately used to generate deterministic Bytes keys for the underlying storage. To prevent accidental collisions between different facets or different data items within a facet, developers MUST adopt clear and unique logical key names. A recommended practice is to use highly descriptive keys, potentially prefixed to indicate their purpose or owning facet (e.g., __token_balances,__config_adminFee). Disciplined logical key management remains essential for avoiding data corruption in the shared environment.
Design Rationale
Architecture
graph TD User["User"] --> DiamondProxy["DiamondProxy"] Factory["Factory"] --> DiamondProxy DiamondProxy --> LoupeStorage["Loupe Storage Selectors"] DiamondProxy --> FacetA["Facet A"] DiamondProxy --> FacetB["Facet B"] FacetA --> StorageHelper["Storage Helper"] FacetB --> StorageHelper StorageHelper --> SharedStorage["Shared Storage"] style User fill:#D5F5E3,stroke:#2ECC71,rx:5px style DiamondProxy fill:#D6EAF8,stroke:#3498DB,rx:5px style LoupeStorage fill:#F2F3F4,stroke:#95A5A6,rx:5px style Factory fill:#EAEDED,stroke:#5D6D7E,rx:5px style FacetA fill:#FCF3CF,stroke:#F1C40F,rx:5px style FacetB fill:#FCF3CF,stroke:#F1C40F,rx:5px style StorageHelper fill:#FADBD8,stroke:#E74C3C,rx:5px style SharedStorage fill:#F5EEF8,stroke:#8E44AD,rx:5pxDeployment Flow (Simplified)
sequenceDiagram participant User participant Factory participant Proxy participant Storage participant FacetA User->>Factory: deploy_diamond Factory->>Proxy: deploy Proxy->>Storage: deploy_storage User->>Proxy: diamond_cut Proxy->>FacetA: deploy_facet Proxy->>FacetA: init_facet Note over Proxy: Update selector mapping User->>Proxy: call function Proxy->>FacetA: invoke_contract FacetA->>Storage: set_storage FacetA-->>Proxy: return result Proxy-->>User: return resultDiamond Proxy vs ERC-2535
While inspired by Ethereum's ERC-2535 Diamond Standard, this implementation has several Soroban-specific differences:
#[facet]): Introduces a standardized macro for consistent facet initialization.Comparison with Standard Soroban Contract Upgrades
Security Concerns
Implementing the Diamond Standard requires careful attention to security:
#[facet]macro and init call pattern are critical for ensuring facets operate correctly. Failure here can break functionality.#[facet]macro and initialization pattern. Ensure diamond_cut correctly calls init. Add runtime checks (is_initialized) within facets for critical operations if paranoid.Future Considerations
Future enhancements could focus on:
Examples and Use Cases
(Use cases remain the same as the previous version - DEX, Tokenization, Anchors, Governance, dApps, etc. - benefiting from modularity, upgradeability, and now easier introspection via Loupe).
Changelog
Conclusion
The Diamond Proxy Standard for Soroban, emphasizing ergonomic shared storage access, offers a powerful and flexible architecture for smart contract development on Stellar. It enables enhanced modularity, safe upgradeability, and on-chain transparency, addressing key limitations of monolithic designs. By adopting this standard, developers can build more complex, maintainable, and adaptable decentralized applications.
The decision to propose a comprehensive Diamond Proxy implementation focuses on introducing core modularity, upgradeability, and introspection benefits while considering Soroban's architecture. Including diamond_cut, fallback, shared storage (with ergonomic helpers), facets, and Loupe functions addresses challenges associated with managing complex smart contract logic and facilitating controlled upgrades and discovery.
The trade-offs of increased architectural complexity and gas overhead for delegation are offset by the significant benefits, particularly for large or evolving systems. Successful adoption will depend on robust implementations, clear developer documentation, supporting tooling, and continued attention to security best practices, especially regarding diamond_cut authorization and shared storage discipline. This SEP provides a solid foundation for bringing these advanced capabilities to the Stellar ecosystem.
Beta Was this translation helpful? Give feedback.
All reactions