Add ECDSA signature verification and enhance ComboHandler functionality

- Integrated ECDSA for signature verification in ComboHandler.
- Updated event emissions to include additional parameters for better tracking.
- Improved gas tracking during execution of combo plans.
- Enhanced database interactions for storing and retrieving plans, including conflict resolution and status updates.
- Added new dependencies for security and database management in orchestrator.
This commit is contained in:
defiQUG
2025-11-05 16:28:48 -08:00
parent 3b09c35c47
commit f600b7b15e
48 changed files with 3381 additions and 46 deletions

View File

@@ -3,6 +3,7 @@ pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "./interfaces/IComboHandler.sol";
import "./interfaces/IAdapterRegistry.sol";
import "./interfaces/INotaryRegistry.sol";
@@ -10,10 +11,13 @@ import "./interfaces/INotaryRegistry.sol";
/**
* @title ComboHandler
* @notice Aggregates multiple DeFi protocol calls and DLT operations into atomic transactions
* @dev Implements 2PC pattern and proper signature verification
*/
contract ComboHandler is IComboHandler, Ownable, ReentrancyGuard {
IAdapterRegistry public adapterRegistry;
INotaryRegistry public notaryRegistry;
using ECDSA for bytes32;
IAdapterRegistry public immutable adapterRegistry;
INotaryRegistry public immutable notaryRegistry;
mapping(bytes32 => ExecutionState) public executions;
@@ -22,20 +26,28 @@ contract ComboHandler is IComboHandler, Ownable, ReentrancyGuard {
uint256 currentStep;
Step[] steps;
bool prepared;
address creator;
}
event PlanExecuted(bytes32 indexed planId, bool success);
event PlanPrepared(bytes32 indexed planId);
event PlanExecuted(bytes32 indexed planId, bool success, uint256 gasUsed);
event PlanPrepared(bytes32 indexed planId, address indexed creator);
event PlanCommitted(bytes32 indexed planId);
event PlanAborted(bytes32 indexed planId);
event PlanAborted(bytes32 indexed planId, string reason);
constructor(address _adapterRegistry, address _notaryRegistry) {
require(_adapterRegistry != address(0), "Invalid adapter registry");
require(_notaryRegistry != address(0), "Invalid notary registry");
adapterRegistry = IAdapterRegistry(_adapterRegistry);
notaryRegistry = INotaryRegistry(_notaryRegistry);
}
/**
* @notice Execute a multi-step combo plan atomically
* @param planId Unique identifier for the execution plan
* @param steps Array of step configurations
* @param signature User's cryptographic signature on the plan
* @return success Whether execution completed successfully
* @return receipts Array of transaction receipts for each step
*/
function executeCombo(
bytes32 planId,
@@ -43,35 +55,44 @@ contract ComboHandler is IComboHandler, Ownable, ReentrancyGuard {
bytes calldata signature
) external override nonReentrant returns (bool success, StepReceipt[] memory receipts) {
require(executions[planId].status == ExecutionStatus.PENDING, "Plan already executed");
require(steps.length > 0, "Plan must have at least one step");
// Verify signature
require(_verifySignature(planId, signature, msg.sender), "Invalid signature");
// Verify signature using ECDSA
bytes32 messageHash = keccak256(abi.encodePacked(planId, steps, msg.sender));
bytes32 ethSignedMessageHash = messageHash.toEthSignedMessageHash();
address signer = ethSignedMessageHash.recover(signature);
require(signer == msg.sender, "Invalid signature");
// Register with notary
notaryRegistry.registerPlan(planId, steps, msg.sender);
uint256 gasStart = gasleft();
executions[planId] = ExecutionState({
status: ExecutionStatus.IN_PROGRESS,
currentStep: 0,
steps: steps,
prepared: false
prepared: false,
creator: msg.sender
});
receipts = new StepReceipt[](steps.length);
// Execute steps sequentially
for (uint256 i = 0; i < steps.length; i++) {
uint256 stepGasStart = gasleft();
(bool stepSuccess, bytes memory returnData, uint256 gasUsed) = _executeStep(steps[i], i);
receipts[i] = StepReceipt({
stepIndex: i,
success: stepSuccess,
returnData: returnData,
gasUsed: gasUsed
gasUsed: stepGasStart - gasleft()
});
if (!stepSuccess) {
executions[planId].status = ExecutionStatus.FAILED;
notaryRegistry.finalizePlan(planId, false);
revert("Step execution failed");
}
}
@@ -79,7 +100,8 @@ contract ComboHandler is IComboHandler, Ownable, ReentrancyGuard {
executions[planId].status = ExecutionStatus.COMPLETE;
success = true;
emit PlanExecuted(planId, true);
uint256 totalGasUsed = gasStart - gasleft();
emit PlanExecuted(planId, true, totalGasUsed);
// Finalize with notary
notaryRegistry.finalizePlan(planId, true);
@@ -87,12 +109,16 @@ contract ComboHandler is IComboHandler, Ownable, ReentrancyGuard {
/**
* @notice Prepare phase for 2PC (two-phase commit)
* @param planId Plan identifier
* @param steps Execution steps
* @return prepared Whether all steps are prepared
*/
function prepare(
bytes32 planId,
Step[] calldata steps
) external override returns (bool prepared) {
require(executions[planId].status == ExecutionStatus.PENDING, "Plan not pending");
require(steps.length > 0, "Plan must have at least one step");
// Validate all steps can be prepared
for (uint256 i = 0; i < steps.length; i++) {
@@ -103,15 +129,18 @@ contract ComboHandler is IComboHandler, Ownable, ReentrancyGuard {
status: ExecutionStatus.IN_PROGRESS,
currentStep: 0,
steps: steps,
prepared: true
prepared: true,
creator: msg.sender
});
emit PlanPrepared(planId);
emit PlanPrepared(planId, msg.sender);
prepared = true;
}
/**
* @notice Commit phase for 2PC
* @param planId Plan identifier
* @return committed Whether commit was successful
*/
function commit(bytes32 planId) external override returns (bool committed) {
ExecutionState storage state = executions[planId];
@@ -134,6 +163,7 @@ contract ComboHandler is IComboHandler, Ownable, ReentrancyGuard {
/**
* @notice Abort phase for 2PC (rollback)
* @param planId Plan identifier
*/
function abort(bytes32 planId) external override {
ExecutionState storage state = executions[planId];
@@ -144,7 +174,7 @@ contract ComboHandler is IComboHandler, Ownable, ReentrancyGuard {
state.status = ExecutionStatus.ABORTED;
emit PlanAborted(planId);
emit PlanAborted(planId, "User aborted");
notaryRegistry.finalizePlan(planId, false);
}
@@ -158,6 +188,7 @@ contract ComboHandler is IComboHandler, Ownable, ReentrancyGuard {
/**
* @notice Execute a single step
* @dev Internal function with gas tracking
*/
function _executeStep(Step memory step, uint256 stepIndex) internal returns (bool success, bytes memory returnData, uint256 gasUsed) {
// Verify adapter is whitelisted
@@ -165,11 +196,21 @@ contract ComboHandler is IComboHandler, Ownable, ReentrancyGuard {
uint256 gasBefore = gasleft();
// Check gas limit
require(gasleft() > 100000, "Insufficient gas");
(success, returnData) = step.target.call{value: step.value}(
abi.encodeWithSignature("executeStep(bytes)", step.data)
);
gasUsed = gasBefore - gasleft();
// Emit event for step execution
if (success) {
// Log successful step
} else {
// Log failed step with return data
}
}
/**
@@ -184,19 +225,10 @@ contract ComboHandler is IComboHandler, Ownable, ReentrancyGuard {
* @notice Rollback steps on abort
*/
function _rollbackSteps(bytes32 planId) internal {
ExecutionState storage state = executions[planId];
// 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
// For now, just mark as aborted
}
}