Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ artifacts
dist
.DS_Store
out/
zkout/
lcov.info
lcov.info.pruned
coverage/
Expand Down
202 changes: 202 additions & 0 deletions contracts/factory/zksync/MagicDropCloneFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import {Ownable} from "solady/src/auth/Ownable.sol";
import {TokenStandard} from "../../common/Structs.sol";
import {MagicDropTokenImplRegistry} from "../../registry/MagicDropTokenImplRegistry.sol";
import {ZKProxy} from "./ZKProxy.sol";

/// @title MagicDropCloneFactory
/// @notice A zksync compatible factory contract for creating and managing clones of MagicDrop contracts
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 = address(new ZKProxy{salt: salt}(impl));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replaced LibClone


// 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 = address(new ZKProxy(impl));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replaced LibClone


// 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 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 {}
}
36 changes: 36 additions & 0 deletions contracts/factory/zksync/ZKProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
pragma solidity ^0.8.20;

// This is a ZKSync compatible proxy and a replacement for OZ Clones
contract ZKProxy {
address immutable implementation;

constructor(address _implementation) {
implementation = _implementation;
}

fallback() external payable {
address impl = implementation;
assembly {
// The pointer to the free memory slot
let ptr := mload(0x40)
// Copy function signature and arguments from calldata at zero position into memory at pointer position
calldatacopy(ptr, 0, calldatasize())
// Delegatecall method of the implementation contract returns 0 on error
let result := delegatecall(gas(), impl, ptr, calldatasize(), 0, 0)
// Get the size of the last return data
let size := returndatasize()
// Copy the size length of bytes from return data at zero position to pointer position
returndatacopy(ptr, 0, size)
// Depending on the result value
switch result
case 0 {
// End execution and revert state changes
revert(ptr, size)
}
default {
// Return data with length of size at pointers position
return(ptr, size)
}
}
}
}
Loading
Loading