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;
}
}