// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "./interfaces/IComboHandler.sol"; import "./interfaces/IAdapterRegistry.sol"; import "./interfaces/INotaryRegistry.sol"; /** * @title ComboHandler * @notice Aggregates multiple DeFi protocol calls and DLT operations into atomic transactions */ contract ComboHandler is IComboHandler, Ownable, ReentrancyGuard { IAdapterRegistry public adapterRegistry; INotaryRegistry public notaryRegistry; mapping(bytes32 => ExecutionState) public executions; struct ExecutionState { ExecutionStatus status; uint256 currentStep; Step[] steps; bool prepared; } event PlanExecuted(bytes32 indexed planId, bool success); event PlanPrepared(bytes32 indexed planId); event PlanCommitted(bytes32 indexed planId); event PlanAborted(bytes32 indexed planId); constructor(address _adapterRegistry, address _notaryRegistry) { adapterRegistry = IAdapterRegistry(_adapterRegistry); notaryRegistry = INotaryRegistry(_notaryRegistry); } /** * @notice Execute a multi-step combo plan atomically */ function executeCombo( bytes32 planId, Step[] calldata steps, bytes calldata signature ) external override nonReentrant returns (bool success, StepReceipt[] memory receipts) { require(executions[planId].status == ExecutionStatus.PENDING, "Plan already executed"); // Verify signature require(_verifySignature(planId, signature, msg.sender), "Invalid signature"); // Register with notary notaryRegistry.registerPlan(planId, steps, msg.sender); executions[planId] = ExecutionState({ status: ExecutionStatus.IN_PROGRESS, currentStep: 0, steps: steps, prepared: false }); receipts = new StepReceipt[](steps.length); // Execute steps sequentially for (uint256 i = 0; i < steps.length; i++) { (bool stepSuccess, bytes memory returnData, uint256 gasUsed) = _executeStep(steps[i], i); receipts[i] = StepReceipt({ stepIndex: i, success: stepSuccess, returnData: returnData, gasUsed: gasUsed }); if (!stepSuccess) { executions[planId].status = ExecutionStatus.FAILED; revert("Step execution failed"); } } executions[planId].status = ExecutionStatus.COMPLETE; success = true; emit PlanExecuted(planId, true); // Finalize with notary notaryRegistry.finalizePlan(planId, true); } /** * @notice Prepare phase for 2PC (two-phase commit) */ function prepare( bytes32 planId, Step[] calldata steps ) external override returns (bool prepared) { require(executions[planId].status == ExecutionStatus.PENDING, "Plan not pending"); // Validate all steps can be prepared for (uint256 i = 0; i < steps.length; i++) { require(_canPrepareStep(steps[i]), "Step cannot be prepared"); } executions[planId] = ExecutionState({ status: ExecutionStatus.IN_PROGRESS, currentStep: 0, steps: steps, prepared: true }); emit PlanPrepared(planId); prepared = true; } /** * @notice Commit phase for 2PC */ function commit(bytes32 planId) external override returns (bool committed) { ExecutionState storage state = executions[planId]; require(state.prepared, "Plan not prepared"); require(state.status == ExecutionStatus.IN_PROGRESS, "Invalid state"); // Execute all prepared steps for (uint256 i = 0; i < state.steps.length; i++) { (bool success, , ) = _executeStep(state.steps[i], i); require(success, "Commit failed"); } state.status = ExecutionStatus.COMPLETE; committed = true; emit PlanCommitted(planId); notaryRegistry.finalizePlan(planId, true); } /** * @notice Abort phase for 2PC (rollback) */ function abort(bytes32 planId) external override { ExecutionState storage state = executions[planId]; require(state.status == ExecutionStatus.IN_PROGRESS, "Cannot abort"); // Release any reserved funds/collateral _rollbackSteps(planId); state.status = ExecutionStatus.ABORTED; emit PlanAborted(planId); notaryRegistry.finalizePlan(planId, false); } /** * @notice Get execution status for a plan */ function getExecutionStatus(bytes32 planId) external view override returns (ExecutionStatus) { return executions[planId].status; } /** * @notice Execute a single step */ function _executeStep(Step memory step, uint256 stepIndex) internal returns (bool success, bytes memory returnData, uint256 gasUsed) { // Verify adapter is whitelisted require(adapterRegistry.isWhitelisted(step.target), "Adapter not whitelisted"); uint256 gasBefore = gasleft(); (success, returnData) = step.target.call{value: step.value}( abi.encodeWithSignature("executeStep(bytes)", step.data) ); gasUsed = gasBefore - gasleft(); } /** * @notice Check if step can be prepared */ function _canPrepareStep(Step memory step) internal view returns (bool) { // Check if adapter supports prepare phase return adapterRegistry.isWhitelisted(step.target); } /** * @notice Rollback steps on abort */ function _rollbackSteps(bytes32 planId) internal { // Release reserved funds, unlock collateral, etc. // Implementation depends on specific step types } /** * @notice Verify user signature on plan */ function _verifySignature(bytes32 planId, bytes calldata signature, address signer) internal pure returns (bool) { // Simplified signature verification // In production, use ECDSA.recover or similar bytes32 messageHash = keccak256(abi.encodePacked(planId, signer)); // Verify signature matches signer return true; // Simplified for now } }