Skip to content

Latest commit

 

History

History
387 lines (300 loc) · 12.3 KB

File metadata and controls

387 lines (300 loc) · 12.3 KB

EcoFundMe Diamond Implementation Guide

📋 Overview

This document explains the Diamond Proxy Pattern (EIP-2535) implementation for EcoFundMe, including the integration with your LibAppStorage.

🏗️ Architecture

┌─────────────────────────────────────────────────────────────┐
│                        Diamond Proxy                         │
│  ┌───────────────────────────────────────────────────────┐  │
│  │                    Fallback Function                   │  │
│  │  Routes calls to appropriate facets based on selector  │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
                              │
         ┌────────────────────┼────────────────────┐
         │                    │                    │
         ▼                    ▼                    ▼
  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐
  │ CampaignFacet│    │  VotingFacet │    │IdentityFacet │
  │              │    │              │    │              │
  │  • create    │    │  • submit    │    │  • verify    │
  │  • donate    │    │    Results   │    │    KYB       │
  │  • milestone │    │  • approve   │    │  • check     │
  └──────────────┘    └──────────────┘    └──────────────┘
         │                    │                    │
         └────────────────────┼────────────────────┘
                              │
                              ▼
                    ┌──────────────────┐
                    │   LibAppStorage  │
                    │                  │
                    │  Shared Storage  │
                    │  at Fixed Slot   │
                    └──────────────────┘

📁 File Structure

src/
├── Diamond.sol                    # Main proxy contract
├── DiamondInit.sol               # Initialization contract
│
├── facets/
│   ├── DiamondCutFacet.sol       # Facet management
│   ├── DiamondLoupeFacet.sol     # Introspection
│   ├── OwnershipFacet.sol        # Ownership management
│   ├── CampaignFacet.sol         # Campaign logic (TODO)
│   ├── VotingFacet.sol           # DAO governance (TODO)
│   └── IdentityFacet.sol         # ONCHAINID integration (TODO)
│
├── libraries/
│   ├── LibDiamond.sol            # Diamond storage & operations
│   └── LibAppStorage.sol         # Your AppStorage (already created)
│
└── interfaces/
    ├── IDiamondCut.sol           # Diamond cut interface
    ├── IDiamondLoupe.sol         # Introspection interface
    └── IERC173.sol               # Ownership interface

🔑 Key Concepts

1. Two Storage Systems

The Diamond uses TWO separate storage positions:

// LibDiamond.sol - Diamond management storage
bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage");

// LibAppStorage.sol - Your application storage
bytes32 internal constant APP_STORAGE_POSITION = keccak256("ecofundme.app.storage.v1");

Why separate?

  • LibDiamond storage: Manages facet addresses and function selectors
  • LibAppStorage: Your business logic data (campaigns, milestones, etc.)
  • They never collide because they use different storage slots

2. Storage Access Pattern

Every facet accesses AppStorage the same way:

// In any facet
import {LibAppStorage} from "../libraries/LibAppStorage.sol";

contract CampaignFacet {
    function createCampaign(...) external {
        // Get AppStorage reference
        LibAppStorage.AppStorage storage s = LibAppStorage.diamondStorage();
        
        // Use it
        s.campaignCount++;
        s.campaigns[s.campaignCount] = ...;
    }
}

3. How Diamond Routing Works

// User calls: diamond.createCampaign(...)

1. Call hits Diamond fallback()
2. Diamond looks up msg.sig (createCampaign selector) in LibDiamond storage
3. Finds CampaignFacet address
4. Delegatecalls to CampaignFacet.createCampaign()
5. CampaignFacet executes in Diamond's storage context
6. Accesses LibAppStorage at fixed slot position

🚀 Deployment Steps

1. Deploy Facets

DiamondCutFacet diamondCutFacet = new DiamondCutFacet();
DiamondLoupeFacet diamondLoupeFacet = new DiamondLoupeFacet();
OwnershipFacet ownershipFacet = new OwnershipFacet();
DiamondInit diamondInit = new DiamondInit();

2. Deploy Diamond

Diamond diamond = new Diamond(owner, address(diamondCutFacet));

3. Build Facet Cuts

IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](2);

// Add DiamondLoupeFacet
bytes4[] memory loupeSelectors = new bytes4[](5);
loupeSelectors[0] = DiamondLoupeFacet.facets.selector;
loupeSelectors[1] = DiamondLoupeFacet.facetFunctionSelectors.selector;
// ... more selectors

cut[0] = IDiamondCut.FacetCut({
    facetAddress: address(diamondLoupeFacet),
    action: IDiamondCut.FacetCutAction.Add,
    functionSelectors: loupeSelectors
});

// Add OwnershipFacet
// ... similar pattern

4. Initialize AppStorage

DiamondInit.InitParams memory initParams = DiamondInit.InitParams({
    admin: owner,
    linkToken: linkTokenAddress,
    automationRegistrar: registrarAddress,
    automationRegistry: registryAddress,
    identityFactory: identityFactoryAddress,
    defaultUpkeepFunding: 5 ether,
    defaultUpkeepGasLimit: 500_000
});

bytes memory initCalldata = abi.encodeWithSelector(
    DiamondInit.init.selector,
    initParams
);

5. Execute Diamond Cut

IDiamondCut(address(diamond)).diamondCut(
    cut,
    address(diamondInit),
    initCalldata
);

🧪 Testing

# Run tests
forge test

# Run specific test
forge test --match-test test_DiamondDeployment

# Run with verbosity
forge test -vvv

# Run with gas reporting
forge test --gas-report

📝 Adding New Facets

Step 1: Create Facet Contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {LibAppStorage} from "../libraries/LibAppStorage.sol";

contract CampaignFacet {
    /// @notice Create a new campaign
    function createCampaign(
        string calldata title,
        uint256 goalAmount,
        uint64 duration,
        address erc20Token
    ) external returns (uint256 campaignId) {
        LibAppStorage.AppStorage storage s = LibAppStorage.diamondStorage();
        
        // Your logic here
        campaignId = LibAppStorage.allocateCampaignId(s, msg.sender);
        
        LibAppStorage.CampaignData storage campaign = s.campaigns[campaignId];
        campaign.title = title;
        campaign.goalAmount = goalAmount;
        campaign.duration = duration;
        campaign.erc20Token = erc20Token;
        campaign.deadline = uint64(block.timestamp) + duration;
        campaign.state = LibAppStorage.CampaignState.Active;
        
        return campaignId;
    }
    
    // More functions...
}

Step 2: Deploy Facet

CampaignFacet campaignFacet = new CampaignFacet();

Step 3: Add to Diamond

IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1);

bytes4[] memory selectors = new bytes4[](1);
selectors[0] = CampaignFacet.createCampaign.selector;
// Add more selectors for other functions

cut[0] = IDiamondCut.FacetCut({
    facetAddress: address(campaignFacet),
    action: IDiamondCut.FacetCutAction.Add,
    functionSelectors: selectors
});

IDiamondCut(address(diamond)).diamondCut(cut, address(0), "");

🔄 Upgrading Facets

Replace a Function

// Deploy new version
CampaignFacetV2 newFacet = new CampaignFacetV2();

// Prepare cut
IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1);
bytes4[] memory selectors = new bytes4[](1);
selectors[0] = CampaignFacetV2.createCampaign.selector;

cut[0] = IDiamondCut.FacetCut({
    facetAddress: address(newFacet),
    action: IDiamondCut.FacetCutAction.Replace, // REPLACE, not Add
    functionSelectors: selectors
});

IDiamondCut(address(diamond)).diamondCut(cut, address(0), "");

Remove a Function

IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1);
bytes4[] memory selectors = new bytes4[](1);
selectors[0] = CampaignFacet.deprecatedFunction.selector;

cut[0] = IDiamondCut.FacetCut({
    facetAddress: address(0), // address(0) for removal
    action: IDiamondCut.FacetCutAction.Remove,
    functionSelectors: selectors
});

IDiamondCut(address(diamond)).diamondCut(cut, address(0), "");

⚠️ Critical Rules

Storage Rules

  1. NEVER reorder fields in LibAppStorage.AppStorage
  2. ALWAYS add new fields at the end of structs
  3. Use assembly for storage access (already done in LibAppStorage.diamondStorage())
  4. Mappings inside structs are safe (you're already doing this correctly)

Facet Rules

  1. NO storage variables in facets - Use LibAppStorage only
  2. Import LibAppStorage in every facet that needs storage
  3. Use external for public functions (gas optimization)
  4. Add events to facets, not storage library

Selector Collision Prevention

// Check for selector collisions before adding
bytes4 selector = CampaignFacet.createCampaign.selector;
address existingFacet = IDiamondLoupe(diamond).facetAddress(selector);
require(existingFacet == address(0), "Selector already exists");

🛠️ Useful Commands

# Deploy to localhost
forge script script/DeployDiamond.s.sol --rpc-url http://localhost:8545 --broadcast

# Deploy to Sepolia
forge script script/DeployDiamond.s.sol \
  --rpc-url $SEPOLIA_RPC_URL \
  --private-key $DEPLOYER_PRIVATE_KEY \
  --broadcast \
  --verify

# Verify on Etherscan
forge verify-contract <DIAMOND_ADDRESS> \
  src/Diamond.sol:Diamond \
  --chain-id 11155111 \
  --etherscan-api-key $ETHERSCAN_API_KEY \
  --constructor-args $(cast abi-encode "constructor(address,address)" "OWNER" "DIAMOND_CUT_FACET")

# Get function selector
cast sig "createCampaign(string,uint256,uint64,address)"

# Check facet functions
cast call $DIAMOND_ADDRESS "facetFunctionSelectors(address)(bytes4[])" $FACET_ADDRESS --rpc-url $RPC

📊 Gas Optimization Tips

  1. Use calldata for arrays/strings in external functions
  2. Pack structs efficiently (already done in LibAppStorage)
  3. Use uint32/uint64 instead of uint256 where possible (you're doing this!)
  4. Batch operations when possible
  5. Use events for off-chain data instead of storing everything

🔒 Security Checklist

  • DiamondCut is protected by owner-only modifier ✅
  • All facets use LibAppStorage for storage ✅
  • No storage variables declared in facets ✅
  • AppStorage uses append-only layout ✅
  • Critical functions have access control (add in facets)
  • Reentrancy guards where needed (add in facets)
  • Input validation on all external functions (add in facets)
  • Events emitted for important state changes (add in facets)

📚 Next Steps

  1. ✅ Diamond infrastructure deployed
  2. ✅ LibAppStorage integrated
  3. ⏳ Create CampaignFacet
  4. ⏳ Create VotingFacet
  5. ⏳ Create IdentityFacet
  6. ⏳ Create VerificationFacet
  7. ⏳ Add comprehensive tests
  8. ⏳ Security audit

🔗 Resources