760 lines
22 KiB
Markdown
760 lines
22 KiB
Markdown
|
|
# Smart Contract Interface Specifications
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
This document defines the smart contract interfaces for the ISO-20022 Combo Flow system, including handler contracts for atomic execution, notary registry for codehash tracking, adapter registry for whitelisting, and integration patterns for atomicity (2PC, HTLC, conditional finality).
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 1. Handler/Aggregator Contract Interface
|
||
|
|
|
||
|
|
### Purpose
|
||
|
|
The handler contract aggregates multiple DeFi protocol calls and DLT operations into a single atomic transaction. It executes steps sequentially, passing outputs between steps, and ensures atomicity across the entire workflow.
|
||
|
|
|
||
|
|
### Interface: `IComboHandler`
|
||
|
|
|
||
|
|
```solidity
|
||
|
|
// SPDX-License-Identifier: MIT
|
||
|
|
pragma solidity ^0.8.20;
|
||
|
|
|
||
|
|
interface IComboHandler {
|
||
|
|
/**
|
||
|
|
* @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,
|
||
|
|
Step[] calldata steps,
|
||
|
|
bytes calldata signature
|
||
|
|
) external returns (bool success, StepReceipt[] memory receipts);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @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 returns (bool prepared);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Commit phase for 2PC
|
||
|
|
* @param planId Plan identifier
|
||
|
|
* @return committed Whether commit was successful
|
||
|
|
*/
|
||
|
|
function commit(bytes32 planId) external returns (bool committed);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Abort phase for 2PC (rollback)
|
||
|
|
* @param planId Plan identifier
|
||
|
|
*/
|
||
|
|
function abort(bytes32 planId) external;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Get execution status for a plan
|
||
|
|
* @param planId Plan identifier
|
||
|
|
* @return status Execution status (PENDING, IN_PROGRESS, COMPLETE, FAILED, ABORTED)
|
||
|
|
*/
|
||
|
|
function getExecutionStatus(bytes32 planId) external view returns (ExecutionStatus status);
|
||
|
|
}
|
||
|
|
|
||
|
|
struct Step {
|
||
|
|
StepType stepType;
|
||
|
|
bytes data; // Encoded step-specific parameters
|
||
|
|
address target; // Target contract address (adapter or protocol)
|
||
|
|
uint256 value; // ETH value to send (if applicable)
|
||
|
|
}
|
||
|
|
|
||
|
|
enum StepType {
|
||
|
|
BORROW,
|
||
|
|
SWAP,
|
||
|
|
REPAY,
|
||
|
|
PAY,
|
||
|
|
DEPOSIT,
|
||
|
|
WITHDRAW,
|
||
|
|
BRIDGE
|
||
|
|
}
|
||
|
|
|
||
|
|
enum ExecutionStatus {
|
||
|
|
PENDING,
|
||
|
|
IN_PROGRESS,
|
||
|
|
COMPLETE,
|
||
|
|
FAILED,
|
||
|
|
ABORTED
|
||
|
|
}
|
||
|
|
|
||
|
|
struct StepReceipt {
|
||
|
|
uint256 stepIndex;
|
||
|
|
bool success;
|
||
|
|
bytes returnData;
|
||
|
|
uint256 gasUsed;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Implementation Example: `ComboHandler.sol`
|
||
|
|
|
||
|
|
```solidity
|
||
|
|
// SPDX-License-Identifier: MIT
|
||
|
|
pragma solidity ^0.8.20;
|
||
|
|
|
||
|
|
import "@openzeppelin/contracts/access/Ownable.sol";
|
||
|
|
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
||
|
|
import "./IComboHandler.sol";
|
||
|
|
import "./IAdapterRegistry.sol";
|
||
|
|
import "./INotaryRegistry.sol";
|
||
|
|
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
constructor(address _adapterRegistry, address _notaryRegistry) {
|
||
|
|
adapterRegistry = IAdapterRegistry(_adapterRegistry);
|
||
|
|
notaryRegistry = INotaryRegistry(_notaryRegistry);
|
||
|
|
}
|
||
|
|
|
||
|
|
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;
|
||
|
|
|
||
|
|
// Finalize with notary
|
||
|
|
notaryRegistry.finalizePlan(planId, true);
|
||
|
|
}
|
||
|
|
|
||
|
|
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
|
||
|
|
});
|
||
|
|
|
||
|
|
prepared = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
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;
|
||
|
|
|
||
|
|
notaryRegistry.finalizePlan(planId, true);
|
||
|
|
}
|
||
|
|
|
||
|
|
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;
|
||
|
|
notaryRegistry.finalizePlan(planId, false);
|
||
|
|
}
|
||
|
|
|
||
|
|
function getExecutionStatus(bytes32 planId) external view override returns (ExecutionStatus) {
|
||
|
|
return executions[planId].status;
|
||
|
|
}
|
||
|
|
|
||
|
|
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();
|
||
|
|
}
|
||
|
|
|
||
|
|
function _canPrepareStep(Step memory step) internal view returns (bool) {
|
||
|
|
// Check if adapter supports prepare phase
|
||
|
|
// Implementation depends on adapter interface
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
function _rollbackSteps(bytes32 planId) internal {
|
||
|
|
// Release reserved funds, unlock collateral, etc.
|
||
|
|
// Implementation depends on specific step types
|
||
|
|
}
|
||
|
|
|
||
|
|
function _verifySignature(bytes32 planId, bytes calldata signature, address signer) internal pure returns (bool) {
|
||
|
|
// Verify ECDSA signature
|
||
|
|
bytes32 messageHash = keccak256(abi.encodePacked(planId));
|
||
|
|
bytes32 ethSignedMessageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash));
|
||
|
|
address recovered = ecrecover(ethSignedMessageHash, v, r, s);
|
||
|
|
return recovered == signer;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 2. Notary Registry Contract Interface
|
||
|
|
|
||
|
|
### Purpose
|
||
|
|
The notary registry contract stores codehashes, plan attestations, and provides immutable audit trails for compliance and non-repudiation.
|
||
|
|
|
||
|
|
### Interface: `INotaryRegistry`
|
||
|
|
|
||
|
|
```solidity
|
||
|
|
// SPDX-License-Identifier: MIT
|
||
|
|
pragma solidity ^0.8.20;
|
||
|
|
|
||
|
|
interface INotaryRegistry {
|
||
|
|
/**
|
||
|
|
* @notice Register a new execution plan
|
||
|
|
* @param planId Unique plan identifier
|
||
|
|
* @param steps Execution steps
|
||
|
|
* @param creator Plan creator address
|
||
|
|
* @return proofHash Cryptographic proof hash
|
||
|
|
*/
|
||
|
|
function registerPlan(
|
||
|
|
bytes32 planId,
|
||
|
|
Step[] calldata steps,
|
||
|
|
address creator
|
||
|
|
) external returns (bytes32 proofHash);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Finalize a plan execution (success or failure)
|
||
|
|
* @param planId Plan identifier
|
||
|
|
* @param success Whether execution succeeded
|
||
|
|
*/
|
||
|
|
function finalizePlan(bytes32 planId, bool success) external;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Register adapter codehash for security
|
||
|
|
* @param adapter Address of adapter contract
|
||
|
|
* @param codeHash Hash of adapter contract bytecode
|
||
|
|
*/
|
||
|
|
function registerCodeHash(address adapter, bytes32 codeHash) external;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Verify adapter codehash matches registered hash
|
||
|
|
* @param adapter Adapter address
|
||
|
|
* @return matches Whether codehash matches
|
||
|
|
*/
|
||
|
|
function verifyCodeHash(address adapter) external view returns (bool matches);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Get notary proof for a plan
|
||
|
|
* @param planId Plan identifier
|
||
|
|
* @return proof Notary proof structure
|
||
|
|
*/
|
||
|
|
function getProof(bytes32 planId) external view returns (NotaryProof memory proof);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Get all plans registered by a creator
|
||
|
|
* @param creator Creator address
|
||
|
|
* @return planIds Array of plan IDs
|
||
|
|
*/
|
||
|
|
function getPlansByCreator(address creator) external view returns (bytes32[] memory planIds);
|
||
|
|
}
|
||
|
|
|
||
|
|
struct NotaryProof {
|
||
|
|
bytes32 planId;
|
||
|
|
bytes32 proofHash;
|
||
|
|
address creator;
|
||
|
|
uint256 registeredAt;
|
||
|
|
uint256 finalizedAt;
|
||
|
|
bool finalized;
|
||
|
|
bool success;
|
||
|
|
bytes32[] stepHashes;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Implementation Example: `NotaryRegistry.sol`
|
||
|
|
|
||
|
|
```solidity
|
||
|
|
// SPDX-License-Identifier: MIT
|
||
|
|
pragma solidity ^0.8.20;
|
||
|
|
|
||
|
|
import "@openzeppelin/contracts/access/Ownable.sol";
|
||
|
|
import "./INotaryRegistry.sol";
|
||
|
|
|
||
|
|
contract NotaryRegistry is INotaryRegistry, Ownable {
|
||
|
|
mapping(bytes32 => NotaryProof) public proofs;
|
||
|
|
mapping(address => bytes32[]) public creatorPlans;
|
||
|
|
mapping(address => bytes32) public codeHashes;
|
||
|
|
|
||
|
|
event PlanRegistered(bytes32 indexed planId, address creator, bytes32 proofHash);
|
||
|
|
event PlanFinalized(bytes32 indexed planId, bool success);
|
||
|
|
event CodeHashRegistered(address indexed adapter, bytes32 codeHash);
|
||
|
|
|
||
|
|
function registerPlan(
|
||
|
|
bytes32 planId,
|
||
|
|
Step[] calldata steps,
|
||
|
|
address creator
|
||
|
|
) external override returns (bytes32 proofHash) {
|
||
|
|
require(proofs[planId].planId == bytes32(0), "Plan already registered");
|
||
|
|
|
||
|
|
bytes32[] memory stepHashes = new bytes32[](steps.length);
|
||
|
|
for (uint256 i = 0; i < steps.length; i++) {
|
||
|
|
stepHashes[i] = keccak256(abi.encode(steps[i]));
|
||
|
|
}
|
||
|
|
|
||
|
|
bytes32 stepsHash = keccak256(abi.encode(stepHashes));
|
||
|
|
proofHash = keccak256(abi.encodePacked(planId, creator, stepsHash, block.timestamp));
|
||
|
|
|
||
|
|
proofs[planId] = NotaryProof({
|
||
|
|
planId: planId,
|
||
|
|
proofHash: proofHash,
|
||
|
|
creator: creator,
|
||
|
|
registeredAt: block.timestamp,
|
||
|
|
finalizedAt: 0,
|
||
|
|
finalized: false,
|
||
|
|
success: false,
|
||
|
|
stepHashes: stepHashes
|
||
|
|
});
|
||
|
|
|
||
|
|
creatorPlans[creator].push(planId);
|
||
|
|
|
||
|
|
emit PlanRegistered(planId, creator, proofHash);
|
||
|
|
}
|
||
|
|
|
||
|
|
function finalizePlan(bytes32 planId, bool success) external override {
|
||
|
|
NotaryProof storage proof = proofs[planId];
|
||
|
|
require(proof.planId != bytes32(0), "Plan not registered");
|
||
|
|
require(!proof.finalized, "Plan already finalized");
|
||
|
|
|
||
|
|
proof.finalized = true;
|
||
|
|
proof.success = success;
|
||
|
|
proof.finalizedAt = block.timestamp;
|
||
|
|
|
||
|
|
emit PlanFinalized(planId, success);
|
||
|
|
}
|
||
|
|
|
||
|
|
function registerCodeHash(address adapter, bytes32 codeHash) external override onlyOwner {
|
||
|
|
codeHashes[adapter] = codeHash;
|
||
|
|
emit CodeHashRegistered(adapter, codeHash);
|
||
|
|
}
|
||
|
|
|
||
|
|
function verifyCodeHash(address adapter) external view override returns (bool matches) {
|
||
|
|
bytes32 registeredHash = codeHashes[adapter];
|
||
|
|
if (registeredHash == bytes32(0)) return false;
|
||
|
|
|
||
|
|
bytes32 currentHash;
|
||
|
|
assembly {
|
||
|
|
currentHash := extcodehash(adapter)
|
||
|
|
}
|
||
|
|
|
||
|
|
return currentHash == registeredHash;
|
||
|
|
}
|
||
|
|
|
||
|
|
function getProof(bytes32 planId) external view override returns (NotaryProof memory) {
|
||
|
|
return proofs[planId];
|
||
|
|
}
|
||
|
|
|
||
|
|
function getPlansByCreator(address creator) external view override returns (bytes32[] memory) {
|
||
|
|
return creatorPlans[creator];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 3. Adapter Registry Contract Interface
|
||
|
|
|
||
|
|
### Purpose
|
||
|
|
The adapter registry manages whitelisting/blacklisting of adapters (both DeFi protocols and Fiat/DTL connectors), tracks versions, and enforces upgrade controls.
|
||
|
|
|
||
|
|
### Interface: `IAdapterRegistry`
|
||
|
|
|
||
|
|
```solidity
|
||
|
|
// SPDX-License-Identifier: MIT
|
||
|
|
pragma solidity ^0.8.20;
|
||
|
|
|
||
|
|
interface IAdapterRegistry {
|
||
|
|
/**
|
||
|
|
* @notice Check if adapter is whitelisted
|
||
|
|
* @param adapter Adapter contract address
|
||
|
|
* @return whitelisted Whether adapter is whitelisted
|
||
|
|
*/
|
||
|
|
function isWhitelisted(address adapter) external view returns (bool whitelisted);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Register a new adapter
|
||
|
|
* @param adapter Adapter contract address
|
||
|
|
* @param adapterType Type of adapter (DEFI or FIAT_DTL)
|
||
|
|
* @param version Adapter version string
|
||
|
|
* @param metadata Additional metadata (IPFS hash, etc.)
|
||
|
|
*/
|
||
|
|
function registerAdapter(
|
||
|
|
address adapter,
|
||
|
|
AdapterType adapterType,
|
||
|
|
string calldata version,
|
||
|
|
bytes calldata metadata
|
||
|
|
) external;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Whitelist an adapter
|
||
|
|
* @param adapter Adapter contract address
|
||
|
|
*/
|
||
|
|
function whitelistAdapter(address adapter) external;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Blacklist an adapter
|
||
|
|
* @param adapter Adapter contract address
|
||
|
|
*/
|
||
|
|
function blacklistAdapter(address adapter) external;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice Get adapter information
|
||
|
|
* @param adapter Adapter contract address
|
||
|
|
* @return info Adapter information structure
|
||
|
|
*/
|
||
|
|
function getAdapterInfo(address adapter) external view returns (AdapterInfo memory info);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @notice List all whitelisted adapters
|
||
|
|
* @param adapterType Filter by type (0 = ALL)
|
||
|
|
* @return adapters Array of adapter addresses
|
||
|
|
*/
|
||
|
|
function listAdapters(AdapterType adapterType) external view returns (address[] memory adapters);
|
||
|
|
}
|
||
|
|
|
||
|
|
enum AdapterType {
|
||
|
|
ALL,
|
||
|
|
DEFI,
|
||
|
|
FIAT_DTL
|
||
|
|
}
|
||
|
|
|
||
|
|
struct AdapterInfo {
|
||
|
|
address adapter;
|
||
|
|
AdapterType adapterType;
|
||
|
|
string version;
|
||
|
|
bool whitelisted;
|
||
|
|
bool blacklisted;
|
||
|
|
uint256 registeredAt;
|
||
|
|
bytes metadata;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Implementation Example: `AdapterRegistry.sol`
|
||
|
|
|
||
|
|
```solidity
|
||
|
|
// SPDX-License-Identifier: MIT
|
||
|
|
pragma solidity ^0.8.20;
|
||
|
|
|
||
|
|
import "@openzeppelin/contracts/access/Ownable.sol";
|
||
|
|
import "./IAdapterRegistry.sol";
|
||
|
|
|
||
|
|
contract AdapterRegistry is IAdapterRegistry, Ownable {
|
||
|
|
mapping(address => AdapterInfo) public adapters;
|
||
|
|
address[] public adapterList;
|
||
|
|
|
||
|
|
event AdapterRegistered(address indexed adapter, AdapterType adapterType, string version);
|
||
|
|
event AdapterWhitelisted(address indexed adapter);
|
||
|
|
event AdapterBlacklisted(address indexed adapter);
|
||
|
|
|
||
|
|
function registerAdapter(
|
||
|
|
address adapter,
|
||
|
|
AdapterType adapterType,
|
||
|
|
string calldata version,
|
||
|
|
bytes calldata metadata
|
||
|
|
) external override onlyOwner {
|
||
|
|
require(adapters[adapter].adapter == address(0), "Adapter already registered");
|
||
|
|
|
||
|
|
adapters[adapter] = AdapterInfo({
|
||
|
|
adapter: adapter,
|
||
|
|
adapterType: adapterType,
|
||
|
|
version: version,
|
||
|
|
whitelisted: false,
|
||
|
|
blacklisted: false,
|
||
|
|
registeredAt: block.timestamp,
|
||
|
|
metadata: metadata
|
||
|
|
});
|
||
|
|
|
||
|
|
adapterList.push(adapter);
|
||
|
|
|
||
|
|
emit AdapterRegistered(adapter, adapterType, version);
|
||
|
|
}
|
||
|
|
|
||
|
|
function whitelistAdapter(address adapter) external override onlyOwner {
|
||
|
|
require(adapters[adapter].adapter != address(0), "Adapter not registered");
|
||
|
|
require(!adapters[adapter].blacklisted, "Adapter is blacklisted");
|
||
|
|
|
||
|
|
adapters[adapter].whitelisted = true;
|
||
|
|
emit AdapterWhitelisted(adapter);
|
||
|
|
}
|
||
|
|
|
||
|
|
function blacklistAdapter(address adapter) external override onlyOwner {
|
||
|
|
require(adapters[adapter].adapter != address(0), "Adapter not registered");
|
||
|
|
|
||
|
|
adapters[adapter].blacklisted = true;
|
||
|
|
adapters[adapter].whitelisted = false;
|
||
|
|
emit AdapterBlacklisted(adapter);
|
||
|
|
}
|
||
|
|
|
||
|
|
function isWhitelisted(address adapter) external view override returns (bool) {
|
||
|
|
AdapterInfo memory info = adapters[adapter];
|
||
|
|
return info.whitelisted && !info.blacklisted;
|
||
|
|
}
|
||
|
|
|
||
|
|
function getAdapterInfo(address adapter) external view override returns (AdapterInfo memory) {
|
||
|
|
return adapters[adapter];
|
||
|
|
}
|
||
|
|
|
||
|
|
function listAdapters(AdapterType adapterType) external view override returns (address[] memory) {
|
||
|
|
uint256 count = 0;
|
||
|
|
for (uint256 i = 0; i < adapterList.length; i++) {
|
||
|
|
if (adapterType == AdapterType.ALL || adapters[adapterList[i]].adapterType == adapterType) {
|
||
|
|
if (adapters[adapterList[i]].whitelisted && !adapters[adapterList[i]].blacklisted) {
|
||
|
|
count++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
address[] memory result = new address[](count);
|
||
|
|
uint256 index = 0;
|
||
|
|
for (uint256 i = 0; i < adapterList.length; i++) {
|
||
|
|
if (adapterType == AdapterType.ALL || adapters[adapterList[i]].adapterType == adapterType) {
|
||
|
|
if (adapters[adapterList[i]].whitelisted && !adapters[adapterList[i]].blacklisted) {
|
||
|
|
result[index] = adapterList[i];
|
||
|
|
index++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 4. Integration Patterns for Atomicity
|
||
|
|
|
||
|
|
### Pattern A: Two-Phase Commit (2PC)
|
||
|
|
|
||
|
|
```solidity
|
||
|
|
// SPDX-License-Identifier: MIT
|
||
|
|
pragma solidity ^0.8.20;
|
||
|
|
|
||
|
|
contract TwoPhaseCommitHandler {
|
||
|
|
enum Phase { PREPARE, COMMIT, ABORT }
|
||
|
|
|
||
|
|
mapping(bytes32 => Phase) public phases;
|
||
|
|
|
||
|
|
function prepare(bytes32 planId, Step[] calldata steps) external {
|
||
|
|
// Mark assets as reserved
|
||
|
|
// Store prepare state
|
||
|
|
phases[planId] = Phase.PREPARE;
|
||
|
|
}
|
||
|
|
|
||
|
|
function commit(bytes32 planId) external {
|
||
|
|
require(phases[planId] == Phase.PREPARE, "Not prepared");
|
||
|
|
// Execute all steps atomically
|
||
|
|
phases[planId] = Phase.COMMIT;
|
||
|
|
}
|
||
|
|
|
||
|
|
function abort(bytes32 planId) external {
|
||
|
|
require(phases[planId] == Phase.PREPARE, "Not prepared");
|
||
|
|
// Release reserved assets
|
||
|
|
phases[planId] = Phase.ABORT;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Pattern B: HTLC-like Pattern
|
||
|
|
|
||
|
|
```solidity
|
||
|
|
// SPDX-License-Identifier: MIT
|
||
|
|
pragma solidity ^0.8.20;
|
||
|
|
|
||
|
|
contract HTLCPattern {
|
||
|
|
struct HTLC {
|
||
|
|
bytes32 hashLock;
|
||
|
|
address beneficiary;
|
||
|
|
uint256 amount;
|
||
|
|
uint256 expiry;
|
||
|
|
bool claimed;
|
||
|
|
}
|
||
|
|
|
||
|
|
mapping(bytes32 => HTLC) public htlcLocks;
|
||
|
|
|
||
|
|
function createHTLC(
|
||
|
|
bytes32 planId,
|
||
|
|
bytes32 hashLock,
|
||
|
|
address beneficiary,
|
||
|
|
uint256 amount,
|
||
|
|
uint256 expiry
|
||
|
|
) external {
|
||
|
|
htlcLocks[planId] = HTLC({
|
||
|
|
hashLock: hashLock,
|
||
|
|
beneficiary: beneficiary,
|
||
|
|
amount: amount,
|
||
|
|
expiry: expiry,
|
||
|
|
claimed: false
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function claimHTLC(bytes32 planId, bytes32 preimage) external {
|
||
|
|
HTLC storage htlc = htlcLocks[planId];
|
||
|
|
require(keccak256(abi.encodePacked(preimage)) == htlc.hashLock, "Invalid preimage");
|
||
|
|
require(block.timestamp < htlc.expiry, "Expired");
|
||
|
|
require(!htlc.claimed, "Already claimed");
|
||
|
|
|
||
|
|
htlc.claimed = true;
|
||
|
|
// Transfer funds
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Pattern C: Conditional Finality via Notary
|
||
|
|
|
||
|
|
```solidity
|
||
|
|
// SPDX-License-Identifier: MIT
|
||
|
|
pragma solidity ^0.8.20;
|
||
|
|
|
||
|
|
contract ConditionalFinalityHandler {
|
||
|
|
INotaryRegistry public notaryRegistry;
|
||
|
|
|
||
|
|
mapping(bytes32 => bool) public pendingFinalization;
|
||
|
|
|
||
|
|
function executeWithConditionalFinality(
|
||
|
|
bytes32 planId,
|
||
|
|
Step[] calldata steps
|
||
|
|
) external {
|
||
|
|
// Execute DLT steps
|
||
|
|
// Mark as pending finalization
|
||
|
|
pendingFinalization[planId] = true;
|
||
|
|
|
||
|
|
// Notary must co-sign after bank settlement
|
||
|
|
}
|
||
|
|
|
||
|
|
function finalizeWithNotary(bytes32 planId, bytes calldata notarySignature) external {
|
||
|
|
require(pendingFinalization[planId], "Not pending");
|
||
|
|
require(notaryRegistry.verifyNotarySignature(planId, notarySignature), "Invalid notary signature");
|
||
|
|
|
||
|
|
// Complete finalization
|
||
|
|
pendingFinality[planId] = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 5. Security Considerations
|
||
|
|
|
||
|
|
### Access Control
|
||
|
|
- Use OpenZeppelin's `Ownable` or `AccessControl` for admin functions
|
||
|
|
- Implement multi-sig for critical operations (adapter whitelisting, codehash registration)
|
||
|
|
|
||
|
|
### Reentrancy Protection
|
||
|
|
- Use `ReentrancyGuard` for execute functions
|
||
|
|
- Follow checks-effects-interactions pattern
|
||
|
|
|
||
|
|
### Upgradeability
|
||
|
|
- Consider using proxy patterns (Transparent/UUPS) for upgradeable contracts
|
||
|
|
- Implement timelocks for upgrades
|
||
|
|
- Require multi-sig for upgrade approvals
|
||
|
|
|
||
|
|
### Codehash Verification
|
||
|
|
- Register codehashes for all adapters
|
||
|
|
- Verify codehash before execution
|
||
|
|
- Prevent execution if codehash doesn't match
|
||
|
|
|
||
|
|
### Gas Optimization
|
||
|
|
- Batch operations where possible
|
||
|
|
- Use `calldata` instead of `memory` for arrays
|
||
|
|
- Minimize storage operations
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 6. Testing Requirements
|
||
|
|
|
||
|
|
### Unit Tests
|
||
|
|
- Test each interface function
|
||
|
|
- Test error cases (invalid inputs, unauthorized access)
|
||
|
|
- Test atomicity (all-or-nothing execution)
|
||
|
|
|
||
|
|
### Integration Tests
|
||
|
|
- Test full workflow execution
|
||
|
|
- Test 2PC prepare/commit/abort flows
|
||
|
|
- Test notary integration
|
||
|
|
- Test adapter registry whitelisting
|
||
|
|
|
||
|
|
### Fuzz Tests
|
||
|
|
- Fuzz step configurations
|
||
|
|
- Fuzz plan structures
|
||
|
|
- Fuzz edge cases (empty steps, large arrays)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**Document Version**: 1.0
|
||
|
|
**Last Updated**: 2025-01-15
|
||
|
|
**Author**: Smart Contract Team
|
||
|
|
|