Files
CurrenciCombo/contracts/ComboHandler.sol

203 lines
6.3 KiB
Solidity

// 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
}
}