Abstract Testnet

Contract Diff Checker

Contract Name:
MagicDropTokenImplRegistry

Contract Source Code:

File 1 of 1 : MagicDropTokenImplRegistry

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0 ^0.8.22 ^0.8.4;

// lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol

// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

// lib/solady/src/auth/Ownable.sol

/// @notice Simple single owner authorization mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol)
///
/// @dev Note:
/// This implementation does NOT auto-initialize the owner to `msg.sender`.
/// You MUST call the `_initializeOwner` in the constructor / initializer.
///
/// While the ownable portion follows
/// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility,
/// the nomenclature for the 2-step ownership handover may be unique to this codebase.
abstract contract Ownable {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The caller is not authorized to call the function.
    error Unauthorized();

    /// @dev The `newOwner` cannot be the zero address.
    error NewOwnerIsZeroAddress();

    /// @dev The `pendingOwner` does not have a valid handover request.
    error NoHandoverRequest();

    /// @dev Cannot double-initialize.
    error AlreadyInitialized();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                           EVENTS                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The ownership is transferred from `oldOwner` to `newOwner`.
    /// This event is intentionally kept the same as OpenZeppelin's Ownable to be
    /// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173),
    /// despite it not being as lightweight as a single argument event.
    event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);

    /// @dev An ownership handover to `pendingOwner` has been requested.
    event OwnershipHandoverRequested(address indexed pendingOwner);

    /// @dev The ownership handover to `pendingOwner` has been canceled.
    event OwnershipHandoverCanceled(address indexed pendingOwner);

    /// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`.
    uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE =
        0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0;

    /// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`.
    uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE =
        0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d;

    /// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`.
    uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE =
        0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                          STORAGE                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The owner slot is given by:
    /// `bytes32(~uint256(uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))))`.
    /// It is intentionally chosen to be a high value
    /// to avoid collision with lower slots.
    /// The choice of manual storage layout is to enable compatibility
    /// with both regular and upgradeable contracts.
    bytes32 internal constant _OWNER_SLOT =
        0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927;

    /// The ownership handover slot of `newOwner` is given by:
    /// ```
    ///     mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED))
    ///     let handoverSlot := keccak256(0x00, 0x20)
    /// ```
    /// It stores the expiry timestamp of the two-step ownership handover.
    uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                     INTERNAL FUNCTIONS                     */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Override to return true to make `_initializeOwner` prevent double-initialization.
    function _guardInitializeOwner() internal pure virtual returns (bool guard) {}

    /// @dev Initializes the owner directly without authorization guard.
    /// This function must be called upon initialization,
    /// regardless of whether the contract is upgradeable or not.
    /// This is to enable generalization to both regular and upgradeable contracts,
    /// and to save gas in case the initial owner is not the caller.
    /// For performance reasons, this function will not check if there
    /// is an existing owner.
    function _initializeOwner(address newOwner) internal virtual {
        if (_guardInitializeOwner()) {
            /// @solidity memory-safe-assembly
            assembly {
                let ownerSlot := _OWNER_SLOT
                if sload(ownerSlot) {
                    mstore(0x00, 0x0dc149f0) // `AlreadyInitialized()`.
                    revert(0x1c, 0x04)
                }
                // Clean the upper 96 bits.
                newOwner := shr(96, shl(96, newOwner))
                // Store the new value.
                sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
                // Emit the {OwnershipTransferred} event.
                log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
            }
        } else {
            /// @solidity memory-safe-assembly
            assembly {
                // Clean the upper 96 bits.
                newOwner := shr(96, shl(96, newOwner))
                // Store the new value.
                sstore(_OWNER_SLOT, newOwner)
                // Emit the {OwnershipTransferred} event.
                log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
            }
        }
    }

    /// @dev Sets the owner directly without authorization guard.
    function _setOwner(address newOwner) internal virtual {
        if (_guardInitializeOwner()) {
            /// @solidity memory-safe-assembly
            assembly {
                let ownerSlot := _OWNER_SLOT
                // Clean the upper 96 bits.
                newOwner := shr(96, shl(96, newOwner))
                // Emit the {OwnershipTransferred} event.
                log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
                // Store the new value.
                sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
            }
        } else {
            /// @solidity memory-safe-assembly
            assembly {
                let ownerSlot := _OWNER_SLOT
                // Clean the upper 96 bits.
                newOwner := shr(96, shl(96, newOwner))
                // Emit the {OwnershipTransferred} event.
                log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
                // Store the new value.
                sstore(ownerSlot, newOwner)
            }
        }
    }

    /// @dev Throws if the sender is not the owner.
    function _checkOwner() internal view virtual {
        /// @solidity memory-safe-assembly
        assembly {
            // If the caller is not the stored owner, revert.
            if iszero(eq(caller(), sload(_OWNER_SLOT))) {
                mstore(0x00, 0x82b42900) // `Unauthorized()`.
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Returns how long a two-step ownership handover is valid for in seconds.
    /// Override to return a different value if needed.
    /// Made internal to conserve bytecode. Wrap it in a public function if needed.
    function _ownershipHandoverValidFor() internal view virtual returns (uint64) {
        return 48 * 3600;
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                  PUBLIC UPDATE FUNCTIONS                   */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Allows the owner to transfer the ownership to `newOwner`.
    function transferOwnership(address newOwner) public payable virtual onlyOwner {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(shl(96, newOwner)) {
                mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`.
                revert(0x1c, 0x04)
            }
        }
        _setOwner(newOwner);
    }

    /// @dev Allows the owner to renounce their ownership.
    function renounceOwnership() public payable virtual onlyOwner {
        _setOwner(address(0));
    }

    /// @dev Request a two-step ownership handover to the caller.
    /// The request will automatically expire in 48 hours (172800 seconds) by default.
    function requestOwnershipHandover() public payable virtual {
        unchecked {
            uint256 expires = block.timestamp + _ownershipHandoverValidFor();
            /// @solidity memory-safe-assembly
            assembly {
                // Compute and set the handover slot to `expires`.
                mstore(0x0c, _HANDOVER_SLOT_SEED)
                mstore(0x00, caller())
                sstore(keccak256(0x0c, 0x20), expires)
                // Emit the {OwnershipHandoverRequested} event.
                log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller())
            }
        }
    }

    /// @dev Cancels the two-step ownership handover to the caller, if any.
    function cancelOwnershipHandover() public payable virtual {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute and set the handover slot to 0.
            mstore(0x0c, _HANDOVER_SLOT_SEED)
            mstore(0x00, caller())
            sstore(keccak256(0x0c, 0x20), 0)
            // Emit the {OwnershipHandoverCanceled} event.
            log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller())
        }
    }

    /// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`.
    /// Reverts if there is no existing ownership handover requested by `pendingOwner`.
    function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute and set the handover slot to 0.
            mstore(0x0c, _HANDOVER_SLOT_SEED)
            mstore(0x00, pendingOwner)
            let handoverSlot := keccak256(0x0c, 0x20)
            // If the handover does not exist, or has expired.
            if gt(timestamp(), sload(handoverSlot)) {
                mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`.
                revert(0x1c, 0x04)
            }
            // Set the handover slot to 0.
            sstore(handoverSlot, 0)
        }
        _setOwner(pendingOwner);
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                   PUBLIC READ FUNCTIONS                    */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns the owner of the contract.
    function owner() public view virtual returns (address result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := sload(_OWNER_SLOT)
        }
    }

    /// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`.
    function ownershipHandoverExpiresAt(address pendingOwner)
        public
        view
        virtual
        returns (uint256 result)
    {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute the handover slot.
            mstore(0x0c, _HANDOVER_SLOT_SEED)
            mstore(0x00, pendingOwner)
            // Load the handover slot.
            result := sload(keccak256(0x0c, 0x20))
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         MODIFIERS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Marks a function as only callable by the owner.
    modifier onlyOwner() virtual {
        _checkOwner();
        _;
    }
}

// lib/solady/src/utils/UUPSUpgradeable.sol

/// @notice UUPS proxy mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/UUPSUpgradeable.sol)
/// @author Modified from OpenZeppelin
/// (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/UUPSUpgradeable.sol)
///
/// @dev Note:
/// - This implementation is intended to be used with ERC1967 proxies.
/// See: `LibClone.deployERC1967` and related functions.
/// - This implementation is NOT compatible with legacy OpenZeppelin proxies
/// which do not store the implementation at `_ERC1967_IMPLEMENTATION_SLOT`.
abstract contract UUPSUpgradeable {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The upgrade failed.
    error UpgradeFailed();

    /// @dev The call is from an unauthorized call context.
    error UnauthorizedCallContext();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         IMMUTABLES                         */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev For checking if the context is a delegate call.
    uint256 private immutable __self = uint256(uint160(address(this)));

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                           EVENTS                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Emitted when the proxy's implementation is upgraded.
    event Upgraded(address indexed implementation);

    /// @dev `keccak256(bytes("Upgraded(address)"))`.
    uint256 private constant _UPGRADED_EVENT_SIGNATURE =
        0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                          STORAGE                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The ERC-1967 storage slot for the implementation in the proxy.
    /// `uint256(keccak256("eip1967.proxy.implementation")) - 1`.
    bytes32 internal constant _ERC1967_IMPLEMENTATION_SLOT =
        0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                      UUPS OPERATIONS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Please override this function to check if `msg.sender` is authorized
    /// to upgrade the proxy to `newImplementation`, reverting if not.
    /// ```
    ///     function _authorizeUpgrade(address) internal override onlyOwner {}
    /// ```
    function _authorizeUpgrade(address newImplementation) internal virtual;

    /// @dev Returns the storage slot used by the implementation,
    /// as specified in [ERC1822](https://eips.ethereum.org/EIPS/eip-1822).
    ///
    /// Note: The `notDelegated` modifier prevents accidental upgrades to
    /// an implementation that is a proxy contract.
    function proxiableUUID() public view virtual notDelegated returns (bytes32) {
        // This function must always return `_ERC1967_IMPLEMENTATION_SLOT` to comply with ERC1967.
        return _ERC1967_IMPLEMENTATION_SLOT;
    }

    /// @dev Upgrades the proxy's implementation to `newImplementation`.
    /// Emits a {Upgraded} event.
    ///
    /// Note: Passing in empty `data` skips the delegatecall to `newImplementation`.
    function upgradeToAndCall(address newImplementation, bytes calldata data)
        public
        payable
        virtual
        onlyProxy
    {
        _authorizeUpgrade(newImplementation);
        /// @solidity memory-safe-assembly
        assembly {
            newImplementation := shr(96, shl(96, newImplementation)) // Clears upper 96 bits.
            mstore(0x00, returndatasize())
            mstore(0x01, 0x52d1902d) // `proxiableUUID()`.
            let s := _ERC1967_IMPLEMENTATION_SLOT
            // Check if `newImplementation` implements `proxiableUUID` correctly.
            if iszero(eq(mload(staticcall(gas(), newImplementation, 0x1d, 0x04, 0x01, 0x20)), s)) {
                mstore(0x01, 0x55299b49) // `UpgradeFailed()`.
                revert(0x1d, 0x04)
            }
            // Emit the {Upgraded} event.
            log2(codesize(), 0x00, _UPGRADED_EVENT_SIGNATURE, newImplementation)
            sstore(s, newImplementation) // Updates the implementation.

            // Perform a delegatecall to `newImplementation` if `data` is non-empty.
            if data.length {
                // Forwards the `data` to `newImplementation` via delegatecall.
                let m := mload(0x40)
                calldatacopy(m, data.offset, data.length)
                if iszero(delegatecall(gas(), newImplementation, m, data.length, codesize(), 0x00))
                {
                    // Bubble up the revert if the call reverts.
                    returndatacopy(m, 0x00, returndatasize())
                    revert(m, returndatasize())
                }
            }
        }
    }

    /// @dev Requires that the execution is performed through a proxy.
    modifier onlyProxy() {
        uint256 s = __self;
        /// @solidity memory-safe-assembly
        assembly {
            // To enable use cases with an immutable default implementation in the bytecode,
            // (see: ERC6551Proxy), we don't require that the proxy address must match the
            // value stored in the implementation slot, which may not be initialized.
            if eq(s, address()) {
                mstore(0x00, 0x9f03a026) // `UnauthorizedCallContext()`.
                revert(0x1c, 0x04)
            }
        }
        _;
    }

    /// @dev Requires that the execution is NOT performed via delegatecall.
    /// This is the opposite of `onlyProxy`.
    modifier notDelegated() {
        uint256 s = __self;
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(eq(s, address())) {
                mstore(0x00, 0x9f03a026) // `UnauthorizedCallContext()`.
                revert(0x1c, 0x04)
            }
        }
        _;
    }
}

// src/common/Structs.sol

enum TokenStandard {
    ERC721,
    ERC1155,
    ERC20
}

struct MintStageInfo {
    uint80 price;
    uint80 mintFee;
    uint32 walletLimit; // 0 for unlimited
    bytes32 merkleRoot; // 0x0 for no presale enforced
    uint24 maxStageSupply; // 0 for unlimited
    uint256 startTimeUnixSeconds;
    uint256 endTimeUnixSeconds;
}

struct MintStageInfo1155 {
    uint80[] price;
    uint80[] mintFee;
    uint32[] walletLimit; // 0 for unlimited
    bytes32[] merkleRoot; // 0x0 for no presale enforced
    uint24[] maxStageSupply; // 0 for unlimited
    uint256 startTimeUnixSeconds;
    uint256 endTimeUnixSeconds;
}

struct SetupConfig {
    /// @dev The maximum number of tokens that can be minted.
    ///      - Can be decreased if current supply < new max supply
    ///      - Cannot be increased once set
    uint256 maxSupply;
    /// @dev The maximum number of tokens that can be minted per wallet
    /// @notice A value of 0 indicates unlimited mints per wallet
    uint256 walletLimit;
    /// @dev The base URI of the token.
    string baseURI;
    /// @dev The contract URI of the token.
    string contractURI;
    /// @dev The mint stages of the token.
    MintStageInfo[] stages;
    /// @dev The payout recipient of the token.
    address payoutRecipient;
    /// @dev The royalty recipient of the token.
    address royaltyRecipient;
    /// @dev The royalty basis points of the token.
    uint96 royaltyBps;
}

// src/registry/interfaces/IMagicDropTokenImplRegistry.sol

interface IMagicDropTokenImplRegistry {
    function registerImplementation(TokenStandard standard, address impl, bool isDefault, uint256 deploymentFee)
        external
        returns (uint32);
    function unregisterImplementation(TokenStandard standard, uint32 implId) external;
    function getImplementation(TokenStandard standard, uint32 implId) external view returns (address);
    function getDeploymentFee(TokenStandard standard, uint32 implId) external view returns (uint256);
    function setDeploymentFee(TokenStandard standard, uint32 implId, uint256 deploymentFee) external;
}

// src/registry/MagicDropTokenImplRegistry.sol

/// @title MagicDropTokenImplRegistry
/// @dev A registry for managing token implementation addresses for different token standards.
/// This contract is upgradeable and uses the UUPS pattern.
contract MagicDropTokenImplRegistry is UUPSUpgradeable, Ownable, IMagicDropTokenImplRegistry {
    /*==============================================================
    =                            STRUCTS                           =
    ==============================================================*/

    struct RegistryData {
        bytes4 interfaceId;
        uint32 nextImplId;
        uint32 defaultImplId;
        mapping(uint256 => address) implementations;
        mapping(uint256 => uint256) deploymentFees; //implementationId => deploymentFee
    }

    struct RegistryStorage {
        mapping(TokenStandard => RegistryData) tokenStandardData;
    }

    /*==============================================================
    =                            STORAGE                           =
    ==============================================================*/

    // keccak256(abi.encode(uint256(keccak256("magicdrop.registry.MagicDropTokenImplRegistry")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant MAGICDROP_REGISTRY_STORAGE =
        0xfd008fcd1deb21680f735a35fafc51691c5fb3daec313cfea4dc62938bee9000;

    /*==============================================================
    =                            EVENTS                            =
    ==============================================================*/

    event ImplementationRegistered(TokenStandard standard, address impl, uint32 implId, uint256 deploymentFee);
    event ImplementationUnregistered(TokenStandard standard, uint32 implId);
    event DefaultImplementationSet(TokenStandard standard, uint32 implId);
    event DeploymentFeeSet(TokenStandard standard, uint32 implId, uint256 deploymentFee);

    /*==============================================================
    =                            ERRORS                            =
    ==============================================================*/

    error InvalidImplementation();
    error ImplementationDoesNotSupportStandard();
    error UnsupportedTokenStandard();
    error DefaultImplementationNotRegistered();

    /*==============================================================
    =                          CONSTRUCTOR                         =
    ==============================================================*/

    /// @param initialOwner The address of the initial owner
    constructor(address initialOwner) public {
        _initializeOwner(initialOwner);

        // Initialize nextImplId and interface IDs for each token standard
        RegistryStorage storage $ = _loadRegistryStorage();
        $.tokenStandardData[TokenStandard.ERC721].nextImplId = 1;
        $.tokenStandardData[TokenStandard.ERC721].interfaceId = 0x80ac58cd; // ERC721 interface ID

        $.tokenStandardData[TokenStandard.ERC1155].nextImplId = 1;
        $.tokenStandardData[TokenStandard.ERC1155].interfaceId = 0xd9b67a26; // ERC1155 interface ID
    }

    /*==============================================================
    =                      PUBLIC VIEW METHODS                     =
    ==============================================================*/

    /// @dev Retrieves the implementation address for a given token standard and implementation ID.
    /// @param standard The token standard (ERC721, ERC1155).
    /// @param implId The ID of the implementation.
    /// @notice Reverts if the implementation is not registered.
    /// @return implAddress The address of the implementation contract.
    function getImplementation(TokenStandard standard, uint32 implId) external view returns (address implAddress) {
        assembly {
            // Compute s1 = keccak256(abi.encode(standard, MAGICDROP_REGISTRY_STORAGE))
            mstore(0x00, standard)
            mstore(0x20, MAGICDROP_REGISTRY_STORAGE)
            let s1 := keccak256(0x00, 0x40)

            // Compute storage slot for implementations[implId]
            mstore(0x00, implId)
            mstore(0x20, add(s1, 1))
            let implSlot := keccak256(0x00, 0x40)
            implAddress := sload(implSlot)

            // Revert if the implementation is not registered
            if iszero(implAddress) {
                mstore(0x00, 0x68155f9a) // revert InvalidImplementation()
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Gets the default implementation ID for a given token standard
    /// @param standard The token standard (ERC721, ERC1155)
    /// @notice Reverts if the default implementation is not registered.
    /// @return defaultImplId The default implementation ID for the given standard
    function getDefaultImplementationID(TokenStandard standard) external view returns (uint32 defaultImplId) {
        assembly {
            // Compute storage slot for tokenStandardData[standard]
            mstore(0x00, standard)
            mstore(0x20, MAGICDROP_REGISTRY_STORAGE)
            let slot := keccak256(0x00, 0x40)

            // Extract 'defaultImplId' by shifting and masking
            // Shift right by 64 bits to bring 'defaultImplId' to bits 0-31
            let shiftedData := shr(64, sload(slot))
            // Mask to extract the lower 32 bits
            defaultImplId := and(shiftedData, 0xffffffff)

            // Check if defaultImplId is 0 and revert if so
            if iszero(defaultImplId) {
                // revert DefaultImplementationNotRegistered()
                mstore(0x00, 0x161378fc)
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Gets the default implementation address for a given token standard
    /// @param standard The token standard (ERC721, ERC1155)
    /// @notice Reverts if the default implementation is not registered.
    /// @return implAddress The default implementation address for the given standard
    function getDefaultImplementation(TokenStandard standard) external view returns (address implAddress) {
        assembly {
            mstore(0x00, standard)
            mstore(0x20, MAGICDROP_REGISTRY_STORAGE)
            let slot := keccak256(0x00, 0x40)

            // Extract 'defaultImplId' by shifting and masking
            // Shift right by 64 bits to bring 'defaultImplId' to bits 0-31
            let shiftedData := shr(64, sload(slot))
            // Mask to extract the lower 32 bits
            let defaultImplId := and(shiftedData, 0xffffffff)

            // Revert if the default implementation is not registered
            if iszero(defaultImplId) {
                // revert DefaultImplementationNotRegistered()
                mstore(0x00, 0x161378fc)
                revert(0x1c, 0x04)
            }

            // Compute storage slot for implementations[defaultImplId]
            mstore(0x00, defaultImplId)
            mstore(0x20, add(slot, 1))
            let implSlot := keccak256(0x00, 0x40)
            implAddress := sload(implSlot)
        }
    }

    /// @dev Gets the deployment fee for a given token standard
    /// @param standard The token standard (ERC721, ERC1155, ERC20)
    /// @param implId The implementation ID
    /// @return deploymentFee The deployment fee for the given standard
    function getDeploymentFee(TokenStandard standard, uint32 implId) external view returns (uint256 deploymentFee) {
        assembly {
            mstore(0x00, standard)
            mstore(0x20, MAGICDROP_REGISTRY_STORAGE)
            let slot := keccak256(0x00, 0x40)

            mstore(0x00, implId)
            mstore(0x20, add(slot, 2))
            let implSlot := keccak256(0x00, 0x40)
            deploymentFee := sload(implSlot)
        }
    }

    /*==============================================================
    =                       INTERNAL HELPERS                       =
    ==============================================================*/

    /// @dev Loads the registry storage.
    /// @return $ The registry storage.
    function _loadRegistryStorage() internal pure returns (RegistryStorage storage $) {
        assembly {
            $.slot := MAGICDROP_REGISTRY_STORAGE
        }
    }

    /*==============================================================
    =                      ADMIN OPERATIONS                        =
    ==============================================================*/

    /// @dev Registers a new implementation for a given token standard.
    /// @param standard The token standard (ERC721, ERC1155, ERC20).
    /// @param impl The address of the implementation contract.
    /// @param isDefault Whether the implementation should be set as the default implementation
    /// @param deploymentFee The deployment fee for the implementation
    /// @notice Only the contract owner can call this function.
    /// @notice Reverts if an implementation with the same name is already registered.
    /// @return The ID of the newly registered implementation
    function registerImplementation(TokenStandard standard, address impl, bool isDefault, uint256 deploymentFee)
        external
        onlyOwner
        returns (uint32)
    {
        RegistryStorage storage $ = _loadRegistryStorage();
        bytes4 interfaceId = $.tokenStandardData[standard].interfaceId;
        if (interfaceId == 0) {
            revert UnsupportedTokenStandard();
        }

        if (!IERC165(impl).supportsInterface(interfaceId)) {
            revert ImplementationDoesNotSupportStandard();
        }

        uint32 implId = $.tokenStandardData[standard].nextImplId;
        $.tokenStandardData[standard].implementations[implId] = impl;
        $.tokenStandardData[standard].nextImplId = implId + 1;
        $.tokenStandardData[standard].deploymentFees[implId] = deploymentFee;
        emit ImplementationRegistered(standard, impl, implId, deploymentFee);
        emit DeploymentFeeSet(standard, implId, deploymentFee);

        if (isDefault) {
            $.tokenStandardData[standard].defaultImplId = implId;
            emit DefaultImplementationSet(standard, implId);
        }

        return implId;
    }

    /// @dev Unregisters an implementation for a given token standard.
    /// @param standard The token standard (ERC721, ERC1155).
    /// @param implId The ID of the implementation to unregister.
    /// @notice Only the contract owner can call this function.
    /// @notice Reverts if the implementation is not registered.
    function unregisterImplementation(TokenStandard standard, uint32 implId) external onlyOwner {
        RegistryStorage storage $ = _loadRegistryStorage();
        address implData = $.tokenStandardData[standard].implementations[implId];

        if (implData == address(0)) {
            revert InvalidImplementation();
        }

        $.tokenStandardData[standard].implementations[implId] = address(0);

        if ($.tokenStandardData[standard].defaultImplId == implId) {
            $.tokenStandardData[standard].defaultImplId = 0;
            emit DefaultImplementationSet(standard, 0);
        }

        emit ImplementationUnregistered(standard, implId);
    }

    /// @dev Sets the default implementation ID for a given token standard
    /// @param standard The token standard (ERC721, ERC1155, ERC20)
    /// @param implId The ID of the implementation to set as default
    /// @notice Reverts if the implementation is not registered.
    /// @notice Only the contract owner can call this function
    function setDefaultImplementation(TokenStandard standard, uint32 implId) external onlyOwner {
        RegistryStorage storage $ = _loadRegistryStorage();
        address implData = $.tokenStandardData[standard].implementations[implId];

        if (implData == address(0)) {
            revert InvalidImplementation();
        }

        $.tokenStandardData[standard].defaultImplId = implId;

        emit DefaultImplementationSet(standard, implId);
    }

    /// @dev Sets the deployment fee for an implementation
    /// @param standard The token standard (ERC721, ERC1155, ERC20)
    /// @param implId The implementation ID
    /// @param deploymentFee The deployment fee to set
    /// @notice Only the contract owner can call this function
    function setDeploymentFee(TokenStandard standard, uint32 implId, uint256 deploymentFee) external onlyOwner {
        RegistryStorage storage $ = _loadRegistryStorage();
        $.tokenStandardData[standard].deploymentFees[implId] = deploymentFee;
        emit DeploymentFeeSet(standard, implId, deploymentFee);
    }

    /// @dev Internal function to authorize an upgrade.
    /// @param newImplementation Address of the new implementation.
    /// @notice Only the contract owner can upgrade the contract.
    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

    /// @dev Overriden to prevent double-initialization of the owner.
    function _guardInitializeOwner() internal pure virtual override returns (bool) {
        return true;
    }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):