From ef8563961603b0c28e254b01dd82112908a7bf94 Mon Sep 17 00:00:00 2001 From: Marvin Kruse Date: Tue, 20 May 2025 02:59:16 +0200 Subject: [PATCH 1/3] Feat: Add external contract deployment option --- src/factories/OrchestratorFactory_v1.sol | 18 ++++++++ .../factories/OrchestratorFactory_v1.t.sol | 44 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/src/factories/OrchestratorFactory_v1.sol b/src/factories/OrchestratorFactory_v1.sol index 7c16f4af7..5e2e62237 100644 --- a/src/factories/OrchestratorFactory_v1.sol +++ b/src/factories/OrchestratorFactory_v1.sol @@ -37,6 +37,8 @@ import { Initializable } from "@oz-up/access/Ownable2StepUpgradeable.sol"; +import {Create2} from "@oz/utils/Create2.sol"; + /** * @title Inverter Orchestrator Factory * @@ -244,6 +246,22 @@ contract OrchestratorFactory_v1 is return _orchestratorIdCounter; } + /// @notice Deploys an external contract using the CREATE2 opcode. + /// @dev Any further calls to the contract that serve its initialization + /// can be provided via the calls array and will be executed after. + /// @param code The creation code of the contract. + /// @param calls Additional calls to be made to the deployed contract. + function deployExternalContract(bytes calldata code, bytes[] calldata calls) + external + returns (address deploymentAddress) + { + deploymentAddress = Create2.deploy(0, _createSalt(), code); + for (uint i; i < calls.length; ++i) { + (bool success,) = deploymentAddress.call(calls[i]); + require(success, "External contract deployment failed"); + } + } + //-------------------------------------------------------------------------- // Internal Functions diff --git a/test/unit/factories/OrchestratorFactory_v1.t.sol b/test/unit/factories/OrchestratorFactory_v1.t.sol index c9a5375d3..0cbcfb20c 100644 --- a/test/unit/factories/OrchestratorFactory_v1.t.sol +++ b/test/unit/factories/OrchestratorFactory_v1.t.sol @@ -342,6 +342,50 @@ contract OrchestratorFactoryV1Test is Test { assertEq(address(orchestrator), address(orchestrator_retry_alice)); } + function testExternalContractDeployment() public { + // Prepare external contract to be deployed + // As this will mostly be used for IssuanceToken deployments, we will + // use a simple ERC20 contract. + bytes memory constructorArgs = abi.encode("Test Token", "TT", 18); + bytes memory bytecode = abi.encodePacked( + vm.getCode("ERC20Mock.sol:ERC20Mock"), constructorArgs + ); + + // Create encoded calls to the two function calls we want to do + // into a bytes array + address transferTarget = makeAddr("Target"); + + // 1. Calling the mint function with the address of the IssuanceToken + bytes memory mintCall = abi.encodeWithSelector( + ERC20Mock.mint.selector, address(factory), 100 + ); + + // 2. Calling the transfer function to transfer the minted tokens + bytes memory transferCall = abi.encodeWithSelector( + ERC20Mock.transfer.selector, address(transferTarget), 100 + ); + + // Assemble the calls array + bytes[] memory calls = new bytes[](2); + calls[0] = mintCall; + calls[1] = transferCall; + + vm.expectEmit(true, false, false, false); + emit IERC20.Transfer(address(0), address(factory), 100); + emit IERC20.Transfer(address(factory), address(transferTarget), 100); + + address deployedAddress = + factory.deployExternalContract(bytecode, calls); + + // Verify that the deployed contract exists and the post-deployment + // calls were executed + assertTrue(ERC20Mock(deployedAddress).balanceOf(address(factory)) == 0); + assertTrue( + ERC20Mock(deployedAddress).balanceOf(address(transferTarget)) == 100 + ); + assertTrue(ERC20Mock(deployedAddress).decimals() == 18); + } + function _deployOrchestrator() private returns (address) { // Create Empty ModuleConfig IOrchestratorFactory_v1.ModuleConfig[] memory moduleConfigs = From b7916ea6e290c3c9eb2dd870df7cc3fcd3bb6e91 Mon Sep 17 00:00:00 2001 From: Marvin Kruse Date: Tue, 20 May 2025 03:13:00 +0200 Subject: [PATCH 2/3] Fix: Properly test event emissions --- test/unit/factories/OrchestratorFactory_v1.t.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/unit/factories/OrchestratorFactory_v1.t.sol b/test/unit/factories/OrchestratorFactory_v1.t.sol index 0cbcfb20c..f501ac1b2 100644 --- a/test/unit/factories/OrchestratorFactory_v1.t.sol +++ b/test/unit/factories/OrchestratorFactory_v1.t.sol @@ -370,8 +370,10 @@ contract OrchestratorFactoryV1Test is Test { calls[0] = mintCall; calls[1] = transferCall; - vm.expectEmit(true, false, false, false); + vm.expectEmit(true, true, true, true); emit IERC20.Transfer(address(0), address(factory), 100); + + vm.expectEmit(true, true, true, true); emit IERC20.Transfer(address(factory), address(transferTarget), 100); address deployedAddress = From 38a1c94dade47dd0df3653879ff7a3a024b42bdb Mon Sep 17 00:00:00 2001 From: Marvin Kruse Date: Tue, 20 May 2025 03:30:42 +0200 Subject: [PATCH 3/3] Fix: Add new function to interface --- src/factories/OrchestratorFactory_v1.sol | 6 +----- src/factories/interfaces/IOrchestratorFactory_v1.sol | 9 +++++++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/factories/OrchestratorFactory_v1.sol b/src/factories/OrchestratorFactory_v1.sol index 5e2e62237..c2fb30fdf 100644 --- a/src/factories/OrchestratorFactory_v1.sol +++ b/src/factories/OrchestratorFactory_v1.sol @@ -246,11 +246,7 @@ contract OrchestratorFactory_v1 is return _orchestratorIdCounter; } - /// @notice Deploys an external contract using the CREATE2 opcode. - /// @dev Any further calls to the contract that serve its initialization - /// can be provided via the calls array and will be executed after. - /// @param code The creation code of the contract. - /// @param calls Additional calls to be made to the deployed contract. + /// @inheritdoc IOrchestratorFactory_v1 function deployExternalContract(bytes calldata code, bytes[] calldata calls) external returns (address deploymentAddress) diff --git a/src/factories/interfaces/IOrchestratorFactory_v1.sol b/src/factories/interfaces/IOrchestratorFactory_v1.sol index fa13ea249..25356eb9f 100644 --- a/src/factories/interfaces/IOrchestratorFactory_v1.sol +++ b/src/factories/interfaces/IOrchestratorFactory_v1.sol @@ -88,6 +88,15 @@ interface IOrchestratorFactory_v1 { ModuleConfig[] memory moduleConfigs ) external returns (IOrchestrator_v1); + /// @notice Deploys an external contract using the CREATE2 opcode. + /// @dev Any further calls to the contract that serve its initialization + /// can be provided via the calls array and will be executed after. + /// @param code The creation code of the contract. + /// @param calls Additional calls to be made to the deployed contract. + function deployExternalContract(bytes calldata code, bytes[] calldata calls) + external + returns (address deploymentAddress); + /// @notice Returns the {IOrchestrator_v1} {IInverterBeacon_v1} address. /// @return OrchestratorImplementationBeacon The {IInverterBeacon_v1} of the {Orchestrator_v1} Implementation. function beacon() external view returns (IInverterBeacon_v1);