// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; import {SafeProxy} from "./SafeProxy.sol"; /** * @title Proxy Factory - Allows to create a new proxy contract and execute a message call to the new proxy within one transaction. * @author Stefan George - @Georgi87 */ contract SafeProxyFactory { event ProxyCreation(SafeProxy indexed proxy, address singleton); event ProxyCreationL2(SafeProxy indexed proxy, address singleton, bytes initializer, uint256 saltNonce); event ChainSpecificProxyCreationL2(SafeProxy indexed proxy, address singleton, bytes initializer, uint256 saltNonce, uint256 chainId); /// @dev Allows to retrieve the creation code used for the Proxy deployment. With this it is easily possible to calculate predicted address. function proxyCreationCode() public pure returns (bytes memory) { return type(SafeProxy).creationCode; } /** * @notice Internal method to create a new proxy contract using CREATE2. Optionally executes an initializer call to a new proxy. * @param _singleton Address of singleton contract. Must be deployed at the time of execution. * @param initializer (Optional) Payload for a message call to be sent to a new proxy contract. * @param salt Create2 salt to use for calculating the address of the new proxy contract. * @return proxy Address of the new proxy contract. */ function deployProxy(address _singleton, bytes memory initializer, bytes32 salt) internal returns (SafeProxy proxy) { require(isContract(_singleton), "Singleton contract not deployed"); bytes memory deploymentData = abi.encodePacked(type(SafeProxy).creationCode, uint256(uint160(_singleton))); /* solhint-disable no-inline-assembly */ /// @solidity memory-safe-assembly assembly { proxy := create2(0x0, add(0x20, deploymentData), mload(deploymentData), salt) } /* solhint-enable no-inline-assembly */ require(address(proxy) != address(0), "Create2 call failed"); if (initializer.length > 0) { /* solhint-disable no-inline-assembly */ /// @solidity memory-safe-assembly assembly { if iszero(call(gas(), proxy, 0, add(initializer, 0x20), mload(initializer), 0, 0)) { let ptr := mload(0x40) returndatacopy(ptr, 0x00, returndatasize()) revert(ptr, returndatasize()) } } /* solhint-enable no-inline-assembly */ } } /** * @notice Deploys a new proxy with `_singleton` singleton and `saltNonce` salt. Optionally executes an initializer call to a new proxy. * @param _singleton Address of singleton contract. Must be deployed at the time of execution. * @param initializer Payload for a message call to be sent to a new proxy contract. * @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. */ function createProxyWithNonce(address _singleton, bytes memory initializer, uint256 saltNonce) public returns (SafeProxy proxy) { // If the initializer changes the proxy address should change too. Hashing the initializer data is cheaper than just concatenating it bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce)); proxy = deployProxy(_singleton, initializer, salt); emit ProxyCreation(proxy, _singleton); } /** * @notice Deploys a new proxy with `_singleton` singleton and `saltNonce` salt. Optionally executes an initializer call to a new proxy. * @dev Emits an extra event to allow tracking of `initializer` and `saltNonce`. * @param _singleton Address of singleton contract. Must be deployed at the time of execution. * @param initializer Payload for a message call to be sent to a new proxy contract. * @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. */ function createProxyWithNonceL2(address _singleton, bytes memory initializer, uint256 saltNonce) public returns (SafeProxy proxy) { proxy = createProxyWithNonce(_singleton, initializer, saltNonce); emit ProxyCreationL2(proxy, _singleton, initializer, saltNonce); } /** * @notice Deploys a new chain-specific proxy with `_singleton` singleton and `saltNonce` salt. Optionally executes an initializer call to a new proxy. * @dev Allows to create a new proxy contract that should exist only on 1 network (e.g. specific governance or admin accounts) * by including the chain id in the create2 salt. Such proxies cannot be created on other networks by replaying the transaction. * @param _singleton Address of singleton contract. Must be deployed at the time of execution. * @param initializer Payload for a message call to be sent to a new proxy contract. * @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. */ function createChainSpecificProxyWithNonce( address _singleton, bytes memory initializer, uint256 saltNonce ) public returns (SafeProxy proxy) { // If the initializer changes the proxy address should change too. Hashing the initializer data is cheaper than just concatenating it bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce, getChainId())); proxy = deployProxy(_singleton, initializer, salt); emit ProxyCreation(proxy, _singleton); } /** * @notice Deploys a new chain-specific proxy with `_singleton` singleton and `saltNonce` salt. Optionally executes an initializer call to a new proxy. * @dev Allows to create a new proxy contract that should exist only on 1 network (e.g. specific governance or admin accounts) * by including the chain id in the create2 salt. Such proxies cannot be created on other networks by replaying the transaction. * Emits an extra event to allow tracking of `initializer` and `saltNonce`. * @param _singleton Address of singleton contract. Must be deployed at the time of execution. * @param initializer Payload for a message call to be sent to a new proxy contract. * @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. */ function createChainSpecificProxyWithNonceL2( address _singleton, bytes memory initializer, uint256 saltNonce ) public returns (SafeProxy proxy) { proxy = createChainSpecificProxyWithNonce(_singleton, initializer, saltNonce); emit ChainSpecificProxyCreationL2(proxy, _singleton, initializer, saltNonce, getChainId()); } /** * @notice Returns true if `account` is a contract. * @dev This function will return false if invoked during the constructor of a contract, * as the code is not created until after the constructor finishes. * @param account The address being queried * @return True if `account` is a contract */ function isContract(address account) internal view returns (bool) { uint256 size; /* solhint-disable no-inline-assembly */ /// @solidity memory-safe-assembly assembly { size := extcodesize(account) } /* solhint-enable no-inline-assembly */ return size > 0; } /** * @notice Returns the ID of the chain the contract is currently deployed on. * @return The ID of the current chain as a uint256. */ function getChainId() public view returns (uint256) { uint256 id; /* solhint-disable no-inline-assembly */ /// @solidity memory-safe-assembly assembly { id := chainid() } /* solhint-enable no-inline-assembly */ return id; } }