ProjectRegistry

0x0c4bb20ba79182b22a046f9eb2b80aa971e89a22

Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
4,799 bytes
ABI entries
28
10 read · 4 write
License
none

Contract information

Address
0x0c4bb20ba79182b22a046f9eb2b80aa971e89a22
Chain
Robinhood Chain (4663)
Compiler
v0.8.24+commit.e11b9ed9
Optimization
Enabled
Creation tx
0xa17abd3eeb…8219c8e418

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (10)

DEFAULT_ADMIN_ROLE()bytes32
PROJECT_FACTORY_ROLE()bytes32
REGISTRY_VERSION()uint32
genesisProjectNumber()uint256
getProject(uint256)tuple
getRoleAdmin(bytes32)bytes32
hasRole(bytes32, address)bool
nextProjectNumber()uint256
projectNumberByContract(address)uint256
supportsInterface(bytes4)bool

Events (5)

ProjectIdentityRecordedProjectRegisteredRoleAdminChangedRoleGrantedRoleRevoked

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "admin",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "AccessControlBadConfirmation",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "internalType": "bytes32",
        "name": "neededRole",
        "type": "bytes32"
      }
    ],
    "name": "AccessControlUnauthorizedAccount",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ContractAlreadyRegistered",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "GenesisProjectRequired",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidAddress",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidGenesisProject",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidProjectData",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ProjectNumberChanged",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "projectNumber",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "bytes32",
        "name": "metadataHash",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "bytes32",
        "name": "normalizedNameHash",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "bytes32",
        "name": "normalizedSymbolHash",
        "type": "bytes32"
      }
    ],
    "name": "ProjectIdentityRecorded",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "projectNumber",
        "type": "uint256"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "primaryContract",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "enum ProductType",
        "name": "productType",
        "type": "uint8"
      },
      {
        "indexed": false,
        "internalType": "bytes32",
        "name": "templateKey",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "uint32",
        "name": "templateVersion",
        "type": "uint32"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "isGenesis",
        "type": "bool"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "isOfficial",
        "type": "bool"
      }
    ],
    "name": "ProjectRegistered",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "previousAdminRole",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "newAdminRole",
        "type": "bytes32"
      }
    ],
    "name": "RoleAdminChanged",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "sender",
        "type": "address"
      }
    ],
    "name": "RoleGranted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "sender",
        "type": "address"
      }
    ],
    "name": "RoleRevoked",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "DEFAULT_ADMIN_ROLE",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "PROJECT_FACTORY_ROLE",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "REGISTRY_VERSION",
    "outputs": [
      {
        "internalType": "uint32",
        "name": "",
        "type": "uint32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "genesisProjectNumber",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "projectNumber",
        "type": "uint256"
      }
    ],
    "name": "getProject",
    "outputs": [
      {
        "components": [
          {
            "internalType": "bool",
            "name": "exists",
            "type": "bool"
          },
          {
            "internalType": "address",
            "name": "creator",
            "type": "address"
          },
          {
            "internalType": "enum ProductType",
            "name": "productType",
            "type": "uint8"
          },
          {
            "internalType": "address",
            "name": "primaryContract",
            "type": "address"
          },
          {
            "internalType": "bytes32",
            "name": "templateKey",
            "type": "bytes32"
          },
          {
            "internalType": "uint32",
            "name": "templateVersion",
            "type": "uint32"
          },
          {
            "internalType": "bytes32",
            "name": "metadataHash",
            "type": "bytes32"
          },
          {
            "internalType": "bytes32",
            "name": "normalizedNameHash",
            "type": "bytes32"
          },
          {
            "internalType": "bytes32",
            "name": "normalizedSymbolHash",
            "type": "bytes32"
          },
          {
            "internalType": "uint48",
            "name": "createdAt",
            "type": "uint48"
          },
          {
            "internalType": "bool",
            "name": "isGenesis",
            "type": "bool"
          },
          {
            "internalType": "bool",
            "name": "isOfficial",
            "type": "bool"
          }
        ],
        "internalType": "struct ProjectRegistry.ProjectRecord",
        "name": "project",
        "type": "tuple"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      }
    ],
    "name": "getRoleAdmin",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "grantRole",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "hasRole",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "nextProjectNumber",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "primaryContract",
        "type": "address"
      }
    ],
    "name": "projectNumberByContract",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "projectNumber",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "expectedProjectNumber",
        "type": "uint256"
      },
      {
        "components": [
          {
            "internalType": "address",
            "name": "creator",
            "type": "address"
          },
          {
            "internalType": "enum ProductType",
            "name": "productType",
            "type": "uint8"
          },
          {
            "internalType": "address",
            "name": "primaryContract",
            "type": "address"
          },
          {
            "internalType": "bytes32",
            "name": "templateKey",
            "type": "bytes32"
          },
          {
            "internalType": "uint32",
            "name": "templateVersion",
            "type": "uint32"
          },
          {
            "internalType": "bytes32",
            "name": "metadataHash",
            "type": "bytes32"
          },
          {
            "internalType": "bytes32",
            "name": "normalizedNameHash",
            "type": "bytes32"
          },
          {
            "internalType": "bytes32",
            "name": "normalizedSymbolHash",
            "type": "bytes32"
          },
          {
            "internalType": "bool",
            "name": "isGenesis",
            "type": "bool"
          },
          {
            "internalType": "bool",
            "name": "isOfficial",
            "type": "bool"
          }
        ],
        "internalType": "struct ProjectRegistry.Registration",
        "name": "registration",
        "type": "tuple"
      }
    ],
    "name": "registerProject",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "projectNumber",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "callerConfirmation",
        "type": "address"
      }
    ],
    "name": "renounceRole",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "revokeRole",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes4",
        "name": "interfaceId",
        "type": "bytes4"
      }
    ],
    "name": "supportsInterface",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol";
import { ProductType } from "../types/ProductType.sol";

/// @title Equifold Project Registry
/// @author Equifold
/// @notice Permanent protocol-level project identity independent from frontend and contract address.
contract ProjectRegistry is AccessControl {
    error ContractAlreadyRegistered();
    error GenesisProjectRequired();
    error InvalidAddress();
    error InvalidGenesisProject();
    error InvalidProjectData();
    error ProjectNumberChanged();

    uint32 public constant REGISTRY_VERSION = 1;
    bytes32 public constant PROJECT_FACTORY_ROLE = keccak256("PROJECT_FACTORY_ROLE");

    struct Registration {
        address creator;
        ProductType productType;
        address primaryContract;
        bytes32 templateKey;
        uint32 templateVersion;
        bytes32 metadataHash;
        bytes32 normalizedNameHash;
        bytes32 normalizedSymbolHash;
        bool isGenesis;
        bool isOfficial;
    }

    struct ProjectRecord {
        bool exists;
        address creator;
        ProductType productType;
        address primaryContract;
        bytes32 templateKey;
        uint32 templateVersion;
        bytes32 metadataHash;
        bytes32 normalizedNameHash;
        bytes32 normalizedSymbolHash;
        uint48 createdAt;
        bool isGenesis;
        bool isOfficial;
    }

    uint256 public nextProjectNumber = 1;
    uint256 public genesisProjectNumber;
    mapping(uint256 projectNumber => ProjectRecord project) private _projects;
    mapping(address primaryContract => uint256 projectNumber) public projectNumberByContract;

    event ProjectRegistered(
        uint256 indexed projectNumber,
        address indexed primaryContract,
        address indexed creator,
        ProductType productType,
        bytes32 templateKey,
        uint32 templateVersion,
        bool isGenesis,
        bool isOfficial
    );
    event ProjectIdentityRecorded(
        uint256 indexed projectNumber,
        bytes32 metadataHash,
        bytes32 normalizedNameHash,
        bytes32 normalizedSymbolHash
    );

    constructor(
        address admin
    ) {
        if (admin == address(0)) revert InvalidAddress();
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
    }

    /// @notice Registers the next project. Project one must be the generic Genesis launch.
    function registerProject(
        uint256 expectedProjectNumber,
        Registration calldata registration
    ) external onlyRole(PROJECT_FACTORY_ROLE) returns (uint256 projectNumber) {
        projectNumber = nextProjectNumber;
        if (expectedProjectNumber != projectNumber) revert ProjectNumberChanged();
        _validateRegistration(projectNumber, registration);
        _storeProject(projectNumber, registration);
        ++nextProjectNumber;

        emit ProjectRegistered(
            projectNumber,
            registration.primaryContract,
            registration.creator,
            registration.productType,
            registration.templateKey,
            registration.templateVersion,
            registration.isGenesis,
            registration.isOfficial
        );
        emit ProjectIdentityRecorded(
            projectNumber,
            registration.metadataHash,
            registration.normalizedNameHash,
            registration.normalizedSymbolHash
        );
    }

    function _validateRegistration(
        uint256 projectNumber,
        Registration calldata registration
    ) private view {
        if (
            registration.creator == address(0) || registration.primaryContract == address(0)
                || registration.primaryContract.code.length == 0
        ) revert InvalidAddress();
        if (
            registration.templateKey == bytes32(0) || registration.templateVersion == 0
                || registration.metadataHash == bytes32(0)
                || registration.normalizedNameHash == bytes32(0)
                || registration.normalizedSymbolHash == bytes32(0)
        ) revert InvalidProjectData();
        if (projectNumberByContract[registration.primaryContract] != 0) {
            revert ContractAlreadyRegistered();
        }
        if (projectNumber == 1 && !registration.isGenesis) revert GenesisProjectRequired();
        if (registration.isGenesis && (projectNumber != 1 || genesisProjectNumber != 0)) {
            revert InvalidGenesisProject();
        }
    }

    function _storeProject(
        uint256 projectNumber,
        Registration calldata registration
    ) private {
        _projects[projectNumber] = ProjectRecord({
            exists: true,
            creator: registration.creator,
            productType: registration.productType,
            primaryContract: registration.primaryContract,
            templateKey: registration.templateKey,
            templateVersion: registration.templateVersion,
            metadataHash: registration.metadataHash,
            normalizedNameHash: registration.normalizedNameHash,
            normalizedSymbolHash: registration.normalizedSymbolHash,
            createdAt: uint48(block.timestamp),
            isGenesis: registration.isGenesis,
            isOfficial: registration.isOfficial
        });
        projectNumberByContract[registration.primaryContract] = projectNumber;
        if (registration.isGenesis) genesisProjectNumber = projectNumber;
    }

    function getProject(
        uint256 projectNumber
    ) external view returns (ProjectRecord memory project) {
        project = _projects[projectNumber];
        if (!project.exists) revert InvalidProjectData();
    }
}
Chain explorer6447msChain node92ms