From d684067a934aab9c80500e646501090c24ecee97 Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Thu, 16 Jan 2025 15:38:59 -0500 Subject: [PATCH 1/3] make zk registry Signed-off-by: Adam Wolf --- .../zksync/MagicDropTokenImplRegistry.sol | 291 ++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 contracts/registry/zksync/MagicDropTokenImplRegistry.sol diff --git a/contracts/registry/zksync/MagicDropTokenImplRegistry.sol b/contracts/registry/zksync/MagicDropTokenImplRegistry.sol new file mode 100644 index 00000000..ca6fb372 --- /dev/null +++ b/contracts/registry/zksync/MagicDropTokenImplRegistry.sol @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import {Ownable} from "solady/src/auth/Ownable.sol"; +import {UUPSUpgradeable} from "solady/src/utils/UUPSUpgradeable.sol"; +import {IMagicDropTokenImplRegistry, TokenStandard} from "./interfaces/IMagicDropTokenImplRegistry.sol"; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.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; + } +} From d1c6cf2ce25a4b3787912fdd3a6359842c8dd8b6 Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Thu, 16 Jan 2025 15:41:07 -0500 Subject: [PATCH 2/3] update solady Signed-off-by: Adam Wolf --- lib/solady | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solady b/lib/solady index 362b2efd..6122858a 160000 --- a/lib/solady +++ b/lib/solady @@ -1 +1 @@ -Subproject commit 362b2efd20f38aea7252b391e5e016633ff79641 +Subproject commit 6122858a3aed96ee9493b99f70a245237681a95f From d688df45794bbbb377e793b03806d53391be67dd Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Thu, 16 Jan 2025 16:44:25 -0500 Subject: [PATCH 3/3] add zksync clone factory Signed-off-by: Adam Wolf --- .../factory/zksync/MagicDropCloneFactory.sol | 222 +++++++++++++ .../zksync/MagicDropTokenImplRegistry.sol | 291 ------------------ test/factory/MagicDropCloneFactoryTest.t.sol | 2 - .../MagicDropTokenImplRegistryTest.t.sol | 1 - 4 files changed, 222 insertions(+), 294 deletions(-) create mode 100644 contracts/factory/zksync/MagicDropCloneFactory.sol delete mode 100644 contracts/registry/zksync/MagicDropTokenImplRegistry.sol diff --git a/contracts/factory/zksync/MagicDropCloneFactory.sol b/contracts/factory/zksync/MagicDropCloneFactory.sol new file mode 100644 index 00000000..15702bb0 --- /dev/null +++ b/contracts/factory/zksync/MagicDropCloneFactory.sol @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import {Ownable} from "solady/src/auth/Ownable.sol"; +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; +import {TokenStandard} from "../../common/Structs.sol"; +import {MagicDropTokenImplRegistry} from "../../registry/MagicDropTokenImplRegistry.sol"; + +/// @title MagicDropCloneFactory +/// @notice A factory contract for creating and managing clones of MagicDrop contracts +/// @dev This contract uses the UUPS proxy pattern +contract MagicDropCloneFactory is Ownable { + /*============================================================== + = CONSTANTS = + ==============================================================*/ + + MagicDropTokenImplRegistry private _registry; + bytes4 private constant INITIALIZE_SELECTOR = bytes4(keccak256("initialize(string,string,address)")); + + /*============================================================== + = EVENTS = + ==============================================================*/ + + event MagicDropFactoryInitialized(); + event NewContractInitialized( + address contractAddress, address initialOwner, uint32 implId, TokenStandard standard, string name, string symbol + ); + event Withdrawal(address to, uint256 amount); + + /*============================================================== + = ERRORS = + ==============================================================*/ + + error InitializationFailed(); + error RegistryAddressCannotBeZero(); + error InsufficientDeploymentFee(); + error WithdrawalFailed(); + error InitialOwnerCannotBeZero(); + + /*============================================================== + = CONSTRUCTOR = + ==============================================================*/ + + /// @param initialOwner The address of the initial owner + /// @param registry The address of the registry contract + constructor(address initialOwner, address registry) public { + if (registry == address(0)) { + revert RegistryAddressCannotBeZero(); + } + + _registry = MagicDropTokenImplRegistry(registry); + _initializeOwner(initialOwner); + + emit MagicDropFactoryInitialized(); + } + + /*============================================================== + = PUBLIC WRITE METHODS = + ==============================================================*/ + + /// @notice Creates a new deterministic clone of a MagicDrop contract + /// @param name The name of the new contract + /// @param symbol The symbol of the new contract + /// @param standard The token standard of the new contract + /// @param initialOwner The initial owner of the new contract + /// @param implId The implementation ID + /// @param salt A unique salt for deterministic address generation + /// @return The address of the newly created contract + function createContractDeterministic( + string calldata name, + string calldata symbol, + TokenStandard standard, + address payable initialOwner, + uint32 implId, + bytes32 salt + ) external payable returns (address) { + address impl; + // Retrieve the implementation address from the registry + if (implId == 0) { + impl = _registry.getDefaultImplementation(standard); + } else { + impl = _registry.getImplementation(standard, implId); + } + + if (initialOwner == address(0)) { + revert InitialOwnerCannotBeZero(); + } + + // Retrieve the deployment fee for the implementation and ensure the caller has sent the correct amount + uint256 deploymentFee = _registry.getDeploymentFee(standard, implId); + if (msg.value < deploymentFee) { + revert InsufficientDeploymentFee(); + } + + // Create a deterministic clone of the implementation contract + address instance = Clones.cloneDeterministic(impl, salt); + + // Initialize the newly created contract + (bool success,) = instance.call(abi.encodeWithSelector(INITIALIZE_SELECTOR, name, symbol, initialOwner)); + if (!success) { + revert InitializationFailed(); + } + + emit NewContractInitialized({ + contractAddress: instance, + initialOwner: initialOwner, + implId: implId, + standard: standard, + name: name, + symbol: symbol + }); + + return instance; + } + + /// @notice Creates a new clone of a MagicDrop contract + /// @param name The name of the new contract + /// @param symbol The symbol of the new contract + /// @param standard The token standard of the new contract + /// @param initialOwner The initial owner of the new contract + /// @param implId The implementation ID + /// @return The address of the newly created contract + function createContract( + string calldata name, + string calldata symbol, + TokenStandard standard, + address payable initialOwner, + uint32 implId + ) external payable returns (address) { + address impl; + // Retrieve the implementation address from the registry + if (implId == 0) { + impl = _registry.getDefaultImplementation(standard); + } else { + impl = _registry.getImplementation(standard, implId); + } + + if (initialOwner == address(0)) { + revert InitialOwnerCannotBeZero(); + } + + // Retrieve the deployment fee for the implementation and ensure the caller has sent the correct amount + uint256 deploymentFee = _registry.getDeploymentFee(standard, implId); + if (msg.value < deploymentFee) { + revert InsufficientDeploymentFee(); + } + + // Create a non-deterministic clone of the implementation contract + address instance = Clones.clone(impl); + + // Initialize the newly created contract + (bool success,) = instance.call(abi.encodeWithSelector(INITIALIZE_SELECTOR, name, symbol, initialOwner)); + if (!success) { + revert InitializationFailed(); + } + + emit NewContractInitialized({ + contractAddress: instance, + initialOwner: initialOwner, + implId: implId, + standard: standard, + name: name, + symbol: symbol + }); + + return instance; + } + + /*============================================================== + = PUBLIC VIEW METHODS = + ==============================================================*/ + + /// @notice Predicts the deployment address of a deterministic clone + /// @param standard The token standard of the contract + /// @param implId The implementation ID + /// @param salt The salt used for address generation + /// @return The predicted deployment address + function predictDeploymentAddress(TokenStandard standard, uint32 implId, bytes32 salt) + external + view + returns (address) + { + address impl; + if (implId == 0) { + impl = _registry.getDefaultImplementation(standard); + } else { + impl = _registry.getImplementation(standard, implId); + } + return Clones.predictDeterministicAddress(impl, salt, address(this)); + } + + /// @notice Retrieves the address of the registry contract + /// @return The address of the registry contract + function getRegistry() external view returns (address) { + return address(_registry); + } + + /*============================================================== + = ADMIN OPERATIONS = + ==============================================================*/ + + /// @notice Withdraws the contract's balance + function withdraw(address to) external onlyOwner { + (bool success,) = to.call{value: address(this).balance}(""); + if (!success) { + revert WithdrawalFailed(); + } + + emit Withdrawal(to, address(this).balance); + } + + /// @dev Overriden to prevent double-initialization of the owner. + function _guardInitializeOwner() internal pure virtual override returns (bool) { + return true; + } + + /// @notice Receives ETH + receive() external payable {} + + /// @notice Fallback function to receive ETH + fallback() external payable {} +} diff --git a/contracts/registry/zksync/MagicDropTokenImplRegistry.sol b/contracts/registry/zksync/MagicDropTokenImplRegistry.sol deleted file mode 100644 index ca6fb372..00000000 --- a/contracts/registry/zksync/MagicDropTokenImplRegistry.sol +++ /dev/null @@ -1,291 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.22; - -import {Ownable} from "solady/src/auth/Ownable.sol"; -import {UUPSUpgradeable} from "solady/src/utils/UUPSUpgradeable.sol"; -import {IMagicDropTokenImplRegistry, TokenStandard} from "./interfaces/IMagicDropTokenImplRegistry.sol"; -import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.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; - } -} diff --git a/test/factory/MagicDropCloneFactoryTest.t.sol b/test/factory/MagicDropCloneFactoryTest.t.sol index 006e9bc6..7d601b9e 100644 --- a/test/factory/MagicDropCloneFactoryTest.t.sol +++ b/test/factory/MagicDropCloneFactoryTest.t.sol @@ -5,8 +5,6 @@ import {console} from "forge-std/console.sol"; import {Test} from "forge-std/Test.sol"; import {MockERC721} from "solady/test/utils/mocks/MockERC721.sol"; import {MockERC1155} from "solady/test/utils/mocks/MockERC1155.sol"; -import {LibClone} from "solady/src/utils/LibClone.sol"; - import {MagicDropCloneFactory} from "../../contracts/factory/MagicDropCloneFactory.sol"; import {MagicDropTokenImplRegistry} from "../../contracts/registry/MagicDropTokenImplRegistry.sol"; import {TokenStandard} from "../../contracts/common/Structs.sol"; diff --git a/test/factory/MagicDropTokenImplRegistryTest.t.sol b/test/factory/MagicDropTokenImplRegistryTest.t.sol index 781d1e68..93da0ba7 100644 --- a/test/factory/MagicDropTokenImplRegistryTest.t.sol +++ b/test/factory/MagicDropTokenImplRegistryTest.t.sol @@ -5,7 +5,6 @@ import {Test} from "forge-std/Test.sol"; import {MagicDropTokenImplRegistry} from "../../contracts/registry/MagicDropTokenImplRegistry.sol"; import {TokenStandard} from "../../contracts/common/Structs.sol"; import {MockERC721} from "solady/test/utils/mocks/MockERC721.sol"; -import {LibClone} from "solady/src/utils/LibClone.sol"; import {IERC165} from "openzeppelin-contracts/contracts/interfaces/IERC165.sol"; contract MagicDropTokenImplRegistryTest is Test {