Logo

dev-resources.site

for different kinds of informations.

How we used the ERC-2535 Diamonds at Proof of Peacemaking Protocol

Published at
12/31/2024
Categories
solidity
diamondpattern
blockchain
opensource
Author
streamerd
Author
9 person written this
streamerd
open
How we used the ERC-2535 Diamonds at Proof of Peacemaking Protocol

Hey fellow devs! Today I want to share how we implemented EIP-2535 Diamond Pattern in our Proof of Peacemaking (POP) protocol. If you're not familiar with Diamond Pattern, it's like microservices for smart contracts, with its API gateway - but way cooler!

Visit the GitHub repo to see the full implementation.

πŸ€” Why Diamond Pattern?

Before diving into the implementation, let's talk about why we chose Diamonds:

  1. Complex Functionality Split: Our protocol handles expressions, acknowledgments, and NFTs - that's a lot of functionality to pack into a single contract. Each of these components needs its own storage and logic.

  2. Future Upgrades: We have several planned upgrades that need flexible implementation:

    • Moving gas subsidization logic off-chain
    • Implementing more sophisticated allowlist mechanisms
    • Adding new expression and acknowledgment types
    • Enhancing NFT metadata and rendering
  3. Storage Management: We're dealing with multiple storage-heavy features:

    • Gas subsidization mappings for operators and users
    • Allowlist tracking for different permission levels
    • Expression and acknowledgment content (which might move to IPFS later)
    • NFT metadata and verification data

Each contract in Solidity has 24kb limit. In our case, components will need its own storage and logic and diamond is there to save the day.

  1. Modular Development: We wanted our facets to be reusable (DRY principle FTW!). For example:

    • The gas subsidization logic could be reused in other projects
    • The NFT facet could be integrated into other diamonds
    • Expression and acknowledgment patterns might be useful for other social protocols
  2. Clean Upgrade Path: Unlike proxy patterns, Diamond Pattern gives us:

    • Ability to upgrade specific functionality without touching other parts
    • Clear separation of concerns for each component
    • Easy way to add new features without size limitations
    • No complex proxy delegation logic to manage

πŸ’‘ Storage Layout: The Fun Part

The most interesting part of our implementation is how we handle storage. Instead of using the traditional AppStorage pattern, we went full Diamond Storage. Here's how:

library LibStorage {
    // Each component gets its own storage namespace
    bytes32 constant EXPRESSION_STORAGE_POSITION = keccak256("pop.v1.expression.storage");
    bytes32 constant ACKNOWLEDGEMENT_STORAGE_POSITION = keccak256("pop.v1.acknowledgement.storage");
    bytes32 constant NFT_METADATA_STORAGE_POSITION = keccak256("pop.v1.nft.metadata.storage");
    bytes32 constant GAS_COST_STORAGE_POSITION = keccak256("pop.v1.gas.cost.storage");

    // More code...
}
Enter fullscreen mode Exit fullscreen mode

See those storage positions? Each one is like a unique apartment in the blockchain for our data. We namespace them with pop.v1 to avoid any roommate disputes (storage collisions) πŸ˜‰

To learn more about the storage types, check out this blog post. written by @mudgen, the creator of the EIP-2535 Diamonds.

πŸ—οΈ Architecture Breakdown

Our diamond has several facets, each with its own storage layout:

1. Expression Facet

struct ExpressionStorage {
    mapping(uint256 => Expression) expressions;
    uint256 expressionCount;
    mapping(uint256 => address[]) expressionAcknowledgers;
}
Enter fullscreen mode Exit fullscreen mode

2. Acknowledgement Facet

struct AcknowledgementStorage {
    // expressionId => acknowledger => Acknowledgement
    mapping(uint256 => mapping(address => Acknowledgement)) acknowledgements;
    uint256 acknowledgementCount;
}
Enter fullscreen mode Exit fullscreen mode

3. NFT Facet

struct POPNFTStorage {
    // Core ERC721 storage
    mapping(uint256 => address) owners;
    mapping(address => uint256) balances;
    mapping(uint256 => string) tokenURIs;
    // ... more fields
}
Enter fullscreen mode Exit fullscreen mode

🧩 Helper Structs vs Storage Structs

Here's a cool pattern we used: We separate our data structures into two categories:

  1. Helper Structs: Just data definitions, no storage position needed
struct Expression {
    address creator;
    MediaContent content;
    uint256 timestamp;
    string ipfsHash;
}
Enter fullscreen mode Exit fullscreen mode
  1. Storage Structs: The actual storage layout with a unique position
struct ExpressionStorage {
    mapping(uint256 => Expression) expressions;
    // ... more fields
}
Enter fullscreen mode Exit fullscreen mode

πŸ” Accessing Storage

Each storage struct gets its own getter function:

function expressionStorage() internal pure returns (ExpressionStorage storage es) {
    bytes32 position = EXPRESSION_STORAGE_POSITION;
    assembly {
        es.slot := position
    }
}
Enter fullscreen mode Exit fullscreen mode

In our facets, we access storage like this:

function createExpression(...) external {
    LibStorage.ExpressionStorage storage es = LibStorage.expressionStorage();
    LibStorage.GasCostStorage storage gs = LibStorage.gasCostStorage();
    // Now we can use es and gs!
}
Enter fullscreen mode Exit fullscreen mode

πŸš€ Benefits We've Seen

  1. Clean Separation: Each component has its own storage namespace
  2. Versioning Ready: Our v1 naming makes future upgrades cleaner
  3. Reusable Facets: Any diamond can use our facets without storage conflicts
  4. Gas Efficient: Direct storage access, no proxy overhead

🎯 Tips for Your Own Implementation

  1. Keep your helper structs separate from storage structs
  2. Use consistent naming for storage positions (we use project.version.component.storage)
  3. Think about future upgrades when designing storage layout
  4. Use descriptive variable names in storage getters (es for expression storage, etc.)

πŸ€“ Final Thoughts

Diamond Pattern might seem complex at first (I definitely scratched my head a few times), but once you get the hang of it, it's like LEGO for smart contracts. Our implementation in POP protocol shows how you can build complex functionality while keeping your code modular and upgradeable.

Remember: With great power comes great responsibility... to write clean, maintainable code! 😎


Still want to check out the full implementation? Visit our GitHub repo!

blockchain #solidity #smartcontracts #ethereum #diamondpattern #web3

solidity Article's
30 articles in total
Favicon
Have You Fallen for a Phishing Scam? Let’s Talk About It πŸ‘€
Favicon
Solidity
Favicon
Why Every Crypto Developer Should Understand Tokenomics πŸš€
Favicon
How we used the ERC-2535 Diamonds at Proof of Peacemaking Protocol
Favicon
πŸ” Solidity Limitations, Solutions, Best Practices and Gas Optimization πŸš€
Favicon
go调用solidityεˆηΊ¦ζ–°ζ–Ήζ³•
Favicon
βš–οΈ The Importance of Using ReentrancyGuard in Solidity Smart Contract
Favicon
Formal Verification: An Example
Favicon
OverFlow and UnderFlow causes in Solidity
Favicon
When to Use ERC-721 vs ERC-1155: Choosing the Right NFT Standard
Favicon
Solidity Pattern - Proxy Delegate and Decorator Patterns
Favicon
Solidity Patterns - CEI
Favicon
Foundry vs Hardhat (A story version)
Favicon
Energy NFT Marketplace
Favicon
OverFlow and UnderFlow causes in Solidity
Favicon
πŸš€ Getting Started with kritisi CLI: An AI-Driven Security Tool for Solidity
Favicon
Formal Verification: The Foundation of Ethereum Smart Contracts
Favicon
The Danger of Randomness in Smart Contracts and its solution
Favicon
What is Reentrancy?
Favicon
Understanding approve and depositCollateral: The Core of ERC-20 Token Transfers in Solidity
Favicon
Ethereum Transaction Calls and State Changes
Favicon
Creating a Toy Solidity compiler and running it in a Toy EVM
Favicon
Send Tokens in Bulk with Low Fees and Fast Delivery: The Ultimate Airdrop Tool for 2024
Favicon
πŸ›‘οΈ Why Using OpenZeppelin in Smart Contracts Is Essential
Favicon
The delegatecall Function in Solidity
Favicon
The delegatecall Function in Solidity
Favicon
A Walkthrough of Solidity Custom Errors
Favicon
How to write dynamic staking smart contract step by step in practice
Favicon
Mainnet Forking in Foundry
Favicon
Every Blockchain Developer Must Know About This Scam!

Featured ones: