Skip to content

Commit

Permalink
refactored factory code
Browse files Browse the repository at this point in the history
  • Loading branch information
dan13ram committed Sep 5, 2024
1 parent 410c61a commit 795ce49
Show file tree
Hide file tree
Showing 9 changed files with 155 additions and 115 deletions.
2 changes: 1 addition & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ forge-std/=node_modules/forge-std/src/
@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/
@openzeppelin/contracts-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable/
@solmate/=node_modules/solmate/src/
@adapters/=src/
@/=src/
@factory/=src/factory/
@interfaces/=src/interfaces/
solidity-bytes-utils/=node_modules/solidity-bytes-utils/
Expand Down
139 changes: 76 additions & 63 deletions src/factory/ImmutableMultiChainDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,61 +6,57 @@ import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/acce
import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import {RateLimiter} from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/utils/RateLimiter.sol";
import {IImmutableMultiChainDeployer} from "@interfaces/IImmutableMultiChainDeployer.sol";
import {L2YnOFTAdapterUpgradeable} from "@adapters/L2YnOFTAdapterUpgradeable.sol";
import {L2YnERC20Upgradeable} from "@adapters/L2YnERC20Upgradeable.sol";
import "forge-std/console.sol";
import {L2YnOFTAdapterUpgradeable} from "@/L2YnOFTAdapterUpgradeable.sol";
import {L2YnERC20Upgradeable} from "@/L2YnERC20Upgradeable.sol";

contract ImmutableMultiChainDeployer is IImmutableMultiChainDeployer {
event ContractCreated(address deployedAddress);
/// @notice Emitted when a new contract is deployed
/// @param _deployedAddress The address of the newly deployed contract
event ContractCreated(address indexed _deployedAddress);

// mapping to track the already deployed addresses
mapping(address => bool) private _deployed;
/// @notice Mapping to track deployed addresses
mapping(address => bool) private _deployedContracts;

/// @dev Custom errors
error InvalidSalt();
error AlreadyDeployed();
error IncorrectDeploymentAddress();

/// @dev Modifier to ensure that the first 20 bytes of a submitted salt match
/// those of the calling account. This provides protection against the salt
/// being stolen by frontrunners or other attackers. The protection can also be
/// bypassed if desired by setting each of the first 20 bytes to zero.
/// @param salt bytes32 The salt value to check against the calling address.
modifier containsCaller(bytes32 salt) {
// prevent contract submissions from being stolen from tx.pool by requiring
// that the first 20 bytes of the submitted salt match msg.sender.
require(
(address(bytes20(salt)) == msg.sender) || (bytes20(salt) == bytes20(0)),
"Invalid salt - first 20 bytes of the salt must match calling address."
);
/// those of the calling account, providing protection against salt misuse.
/// @param _salt The salt value to check against the calling address.
modifier containsCaller(bytes32 _salt) {
if (address(bytes20(_salt)) != msg.sender && bytes20(_salt) != bytes20(0)) {
revert InvalidSalt();
}
_;
}

/// @inheritdoc IImmutableMultiChainDeployer
function deploy(bytes32 salt, bytes memory initCode)
/// @inheritdoc IImmutableMultiChainDeployer
function deploy(bytes32 _salt, bytes memory _initCode)
public
payable
override
containsCaller(salt)
returns (address _deployedContract)
containsCaller(_salt)
returns (address deployedContract)
{
// get target deployment
address targetDeploymentAddress = CREATE3.getDeployed(salt);

require(
!_deployed[targetDeploymentAddress],
"Invalid deployment. a contract has already been deployed at this address"
);
address _targetDeploymentAddress = CREATE3.getDeployed(_salt);

// use create 3 to deploy contract
_deployedContract = CREATE3.deploy(salt, initCode, msg.value);
if (_deployedContracts[_targetDeploymentAddress]) {
revert AlreadyDeployed();
}

// check address against target to make sure deployment was successful
require(targetDeploymentAddress == _deployedContract, "failed to deploy to correct address");
deployedContract = CREATE3.deploy(_salt, _initCode, msg.value);

// record the deployment of the contract to prevent redeploys.
_deployed[_deployedContract] = true;
if (_targetDeploymentAddress != deployedContract) {
revert IncorrectDeploymentAddress();
}

// emit event
emit ContractCreated(_deployedContract);
_deployedContracts[deployedContract] = true;
emit ContractCreated(deployedContract);
}

/// @inheritdoc IImmutableMultiChainDeployer
/// @inheritdoc IImmutableMultiChainDeployer
function deployL2YnOFTAdapter(
bytes32 _implSalt,
bytes32 _proxySalt,
Expand All @@ -70,45 +66,62 @@ contract ImmutableMultiChainDeployer is IImmutableMultiChainDeployer {
RateLimiter.RateLimitConfig[] calldata _rateLimitConfigs,
address _proxyController,
bytes memory _l2YnOFTAdapterBytecode
) public returns (address _deployedContract) {
bytes memory constructorParams = abi.encode(_token, _lzEndpoint);
bytes memory contractCode = abi.encodePacked(_l2YnOFTAdapterBytecode, constructorParams);

address adapterImpl = deploy(_implSalt, contractCode);
_deployedContract = deployProxy(_proxySalt, adapterImpl, _proxyController);
L2YnOFTAdapterUpgradeable(_deployedContract).initialize(_owner, _rateLimitConfigs);
) public override returns (address deployedContract) {
bytes memory _constructorParams = abi.encode(_token, _lzEndpoint);
bytes memory _contractCode = abi.encodePacked(_l2YnOFTAdapterBytecode, _constructorParams);
bytes memory _initializeArgs =
abi.encodeWithSelector(L2YnOFTAdapterUpgradeable.initialize.selector, _owner, _rateLimitConfigs);
deployedContract =
deployContractAndProxy(_implSalt, _proxySalt, _proxyController, _contractCode, _initializeArgs);
}

/// @inheritdoc IImmutableMultiChainDeployer
/// @inheritdoc IImmutableMultiChainDeployer
function deployL2YnERC20(
bytes32 _implSalt,
bytes32 _proxySalt,
string memory _name,
string memory _symbol,
string calldata _name,
string calldata _symbol,
address _owner,
address _proxyController,
bytes memory _l2YnERC20UpgradeableByteCode
) public returns (address _deployedContract) {
address adapterImpl = deploy(_implSalt, _l2YnERC20UpgradeableByteCode);
_deployedContract = deployProxy(_proxySalt, adapterImpl, _proxyController);
L2YnERC20Upgradeable(_deployedContract).initialize(_name, _symbol, _owner);
) public override returns (address deployedContract) {
bytes memory _initializeArgs =
abi.encodeWithSelector(L2YnERC20Upgradeable.initialize.selector, _name, _symbol, _owner);
deployedContract = deployContractAndProxy(
_implSalt, _proxySalt, _proxyController, _l2YnERC20UpgradeableByteCode, _initializeArgs
);
}

/// @inheritdoc IImmutableMultiChainDeployer
function getDeployed(bytes32 salt) public view override returns (address deployed) {
// hash salt with the deployer address to give each deployer its own namespace
return CREATE3.getDeployed(salt);
/// @inheritdoc IImmutableMultiChainDeployer
function getDeployed(bytes32 _salt) external view override returns (address deployed) {
return CREATE3.getDeployed(_salt);
}

function hasBeenDeployed(address deploymentAddress) external view returns (bool) {
// determine if a contract has been deployed to the provided address.
return _deployed[deploymentAddress];
/// @inheritdoc IImmutableMultiChainDeployer
function hasBeenDeployed(address _deploymentAddress) external view override returns (bool beenDeployed) {
beenDeployed = _deployedContracts[_deploymentAddress];
}

function deployProxy(bytes32 salt, address implementation, address controller) internal returns (address proxy) {
bytes memory bytecode = type(TransparentUpgradeableProxy).creationCode;
bytes memory constructorParams = abi.encode(implementation, controller, "");
bytes memory contractCode = abi.encodePacked(bytecode, constructorParams);
proxy = deploy(salt, contractCode);
/// @inheritdoc IImmutableMultiChainDeployer
function deployProxy(bytes32 _salt, address _implementation, address _controller, bytes memory _initializeArgs)
public
returns (address proxy)
{
bytes memory _constructorParams = abi.encode(_implementation, _controller, _initializeArgs);
bytes memory _contractCode =
abi.encodePacked(type(TransparentUpgradeableProxy).creationCode, _constructorParams);
proxy = deploy(_salt, _contractCode);
}

/// @inheritdoc IImmutableMultiChainDeployer
function deployContractAndProxy(
bytes32 _implSalt,
bytes32 _proxySalt,
address _controller,
bytes memory _bytecode,
bytes memory _initializeArgs
) public returns (address addr) {
address _implAddr = deploy(_implSalt, _bytecode);
return deployProxy(_proxySalt, _implAddr, _controller, _initializeArgs);
}
}
103 changes: 65 additions & 38 deletions src/interfaces/IImmutableMultiChainDeployer.sol
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.6.0;
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {RateLimiter} from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/utils/RateLimiter.sol";

/// @title Factory for deploying yieldnest contracts to deterministic addresses via CREATE3
/// @author Raid Guild
/// @notice Enables deploying contracts using CREATE3 and then initializing the upgradeable contracts. Each deployer (msg.sender) has
/// its own namespace for deployed addresses.
/// @dev Uses CREATE3 for deterministic contract deployment.
interface IImmutableMultiChainDeployer {
/// @notice Deploys a contract using CREATE3
/// @dev The provided salt is hashed together with msg.sender to generate the final salt
/// @param salt The deployer-specific salt for determining the deployed contract's address
/// @param creationCode The creation code of the contract to deploy
/// @return deployed The address of the deployed contract
/// @dev The provided salt is combined with msg.sender to create a unique deployment address.
/// @param salt A deployer-specific salt for determining the deployed contract's address.
/// @param creationCode The creation code of the contract to deploy.
/// @return deployed The address of the deployed contract.
function deploy(bytes32 salt, bytes calldata creationCode) external payable returns (address deployed);

/// @notice Deploys a deployOFTAdapter contract using CREATE3 and initializes in the same call
/// @dev The provided salt is hashed together with msg.sender to generate the final salt
/// @param _implSalt the salt for the oft adapter to be passed to the initializer
/// @param _proxySalt the salt for the oft adapter to be passed to the initializer
/// @param _token the token address for the oft adapter to be passed to the initializer
/// @param _lzEndpoint the lz endpoint for the oft adapter to be passed to the initializer
/// @param _rateLimitConfigs the desired rate limit configs for the oft adapter to be passed to the initializer
/// @param _proxyController the proxy controller of the erc20 to be passed to the initializer
/// @return deployed The address of the deployed contract
/// @notice Deploys and initializes a deployOFTAdapter contract using CREATE3.
/// @dev The provided salts and parameters are used to configure the deployment and initialization.
/// @param _implSalt The salt for the OFT adapter implementation.
/// @param _proxySalt The salt for the OFT adapter proxy.
/// @param _token The token address for the OFT adapter.
/// @param _lzEndpoint The LayerZero endpoint for the OFT adapter.
/// @param _owner The owner address for the OFT adapter.
/// @param _rateLimitConfigs The rate limit configurations for the OFT adapter.
/// @param _proxyController The proxy controller address for the OFT adapter.
/// @param _l2YnOFTAdapterBytecode The bytecode of the L2YnOFTAdapter contract to deploy.
/// @return deployed The address of the deployed contract.
function deployL2YnOFTAdapter(
bytes32 _implSalt,
bytes32 _proxySalt,
Expand All @@ -32,37 +34,62 @@ interface IImmutableMultiChainDeployer {
address _owner,
RateLimiter.RateLimitConfig[] calldata _rateLimitConfigs,
address _proxyController,
bytes memory _l2YnOFTAdapterBytecode
bytes calldata _l2YnOFTAdapterBytecode
) external returns (address deployed);

/// @notice Deploys a deployYnERC20 contract using CREATE3 and initializes in the same call
/// @dev The provided salt is hashed together with msg.sender to generate the final salt
/// @param _implSalt the salt for the oft adapter to be passed to the initializer
/// @param _proxySalt the salt for the oft adapter to be passed to the initializer
/// @param _name the name of the erc20 to be passed to the initializer
/// @param _symbol the symbol of the erc20 to be passed to the initializer
/// @param _owner the owner of the erc20 to be passed to the initializer
/// @param _proxyController the proxy controller of the erc20 to be passed to the initializer
/// @return deployed The address of the deployed contract
/// @notice Deploys and initializes a deployYnERC20 contract using CREATE3.
/// @dev The provided salts and parameters are used to configure the deployment and initialization.
/// @param _implSalt The salt for the ERC20 implementation.
/// @param _proxySalt The salt for the ERC20 proxy.
/// @param _name The name of the ERC20 token.
/// @param _symbol The symbol of the ERC20 token.
/// @param _owner The owner address of the ERC20 token.
/// @param _proxyController The proxy controller address of the ERC20 token.
/// @param _l2YnOFTAdapterBytecode The bytecode of the L2YnOFTAdapter contract to deploy.
/// @return deployed The address of the deployed contract.
function deployL2YnERC20(
bytes32 _implSalt,
bytes32 _proxySalt,
string memory _name,
string memory _symbol,
string calldata _name,
string calldata _symbol,
address _owner,
address _proxyController,
bytes memory _l2YnOFTAdapterBytecode
bytes calldata _l2YnOFTAdapterBytecode
) external returns (address deployed);

/// @notice Predicts the address of a deployed contract
/// @dev The provided salt is hashed together with the deployer address to generate the final salt
/// @param salt The deployer-specific salt for determining the deployed contract's address
/// @return deployed The address of the contract that will be deployed
/// @notice Predicts the address of a contract deployed using CREATE3.
/// @dev The provided salt is combined with the deployer address to create a unique predicted address.
/// @param salt A deployer-specific salt for determining the deployed contract's address.
/// @return deployed The address of the contract that will be deployed.
function getDeployed(bytes32 salt) external view returns (address deployed);

/// @dev Determine if a contract has already been deployed by the factory to a
/// given address.
/// @param deploymentAddress address The contract address to check.
/// @return True if the contract has been deployed, false otherwise.
function hasBeenDeployed(address deploymentAddress) external view returns (bool);
/// @notice Checks if a contract has already been deployed by the factory to a specific address.
/// @param deploymentAddress The contract address to check.
/// @return beenDeployed as true if the contract has been deployed, false otherwise.
function hasBeenDeployed(address deploymentAddress) external view returns (bool beenDeployed);

/// @notice Deploys a TransparentUpgradeableProxy with given parameters.
/// @param salt The salt used for deployment.
/// @param implementation The address of the implementation contract.
/// @param controller The address of the proxy controller.
/// @param _initializeArgs The initialization arguments for the proxy.
/// @return proxy The address of the deployed proxy.
function deployProxy(bytes32 salt, address implementation, address controller, bytes memory _initializeArgs)
external
returns (address proxy);

/// @notice Deploys an implementation and proxy contract.
/// @param _implSalt The salt used for the implementation deployment.
/// @param _proxySalt The salt used for the proxy deployment.
/// @param _controller The address of the proxy controller.
/// @param _bytecode The bytecode of the implementation contract.
/// @param _initializeArgs The initialization arguments for the proxy.
/// @return addr The address of the deployed proxy.
function deployContractAndProxy(
bytes32 _implSalt,
bytes32 _proxySalt,
address _controller,
bytes memory _bytecode,
bytes memory _initializeArgs
) external returns (address addr);
}
6 changes: 3 additions & 3 deletions test/CrossChainBaseTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import {Test} from "forge-std/Test.sol";
import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import {ImmutableMultiChainDeployer} from "@factory/ImmutableMultiChainDeployer.sol";
import {IMintableBurnableERC20} from "@interfaces/IMintableBurnableERC20.sol";
import {L1YnOFTAdapterUpgradeable} from "@adapters/L1YnOFTAdapterUpgradeable.sol";
import {L2YnERC20Upgradeable} from "@adapters/L2YnERC20Upgradeable.sol";
import {L2YnOFTAdapterUpgradeable} from "@adapters/L2YnOFTAdapterUpgradeable.sol";
import {L1YnOFTAdapterUpgradeable} from "@/L1YnOFTAdapterUpgradeable.sol";
import {L2YnERC20Upgradeable} from "@/L2YnERC20Upgradeable.sol";
import {L2YnOFTAdapterUpgradeable} from "@/L2YnOFTAdapterUpgradeable.sol";
import {RateLimiter} from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/utils/RateLimiter.sol";
import {ERC20Mock} from "@layerzerolabs/lz-evm-oapp-v2/test/mocks/ERC20Mock.sol";
import {EndpointV2} from "@layerzerolabs/lz-evm-protocol-v2/contracts/EndpointV2.sol";
Expand Down
8 changes: 4 additions & 4 deletions test/MultiChainDeployer.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ pragma solidity ^0.8.24;
import {CrossChainBaseTest} from "./CrossChainBaseTest.t.sol";
import {ImmutableMultiChainDeployer} from "@factory/ImmutableMultiChainDeployer.sol";
import {IMintableBurnableERC20} from "@interfaces/IMintableBurnableERC20.sol";
import {L1YnOFTAdapterUpgradeable} from "@adapters/L1YnOFTAdapterUpgradeable.sol";
import {L2YnERC20Upgradeable} from "@adapters/L2YnERC20Upgradeable.sol";
import {L2YnOFTAdapterUpgradeable} from "@adapters/L2YnOFTAdapterUpgradeable.sol";
import {L1YnOFTAdapterUpgradeable} from "@/L1YnOFTAdapterUpgradeable.sol";
import {L2YnERC20Upgradeable} from "@/L2YnERC20Upgradeable.sol";
import {L2YnOFTAdapterUpgradeable} from "@/L2YnOFTAdapterUpgradeable.sol";
import {RateLimiter} from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/utils/RateLimiter.sol";

contract Test_ImmutableMultiChainDeployer is CrossChainBaseTest {
Expand All @@ -26,7 +26,7 @@ contract Test_ImmutableMultiChainDeployer is CrossChainBaseTest {
RateLimiter.RateLimitConfig[] memory _rateLimitConfigs = new RateLimiter.RateLimitConfig[](1);
_rateLimitConfigs[0] = RateLimiter.RateLimitConfig({dstEid: uint32(1), limit: 1 ether, window: 1 days});

vm.expectRevert("Invalid salt - first 20 bytes of the salt must match calling address.");
vm.expectRevert(ImmutableMultiChainDeployer.InvalidSalt.selector);
address(
arbitrumDeployer.deployL2YnOFTAdapter(
keccak256(abi.encode("test")),
Expand Down
Loading

0 comments on commit 795ce49

Please sign in to comment.