Consolidate webapp structure by merging nested components into the main repository
This commit is contained in:
80
contracts/AdapterRegistry.sol
Normal file
80
contracts/AdapterRegistry.sol
Normal file
@@ -0,0 +1,80 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||
import "./interfaces/IAdapterRegistry.sol";
|
||||
|
||||
/**
|
||||
* @title AdapterRegistry
|
||||
* @notice Manages whitelist/blacklist of protocol adapters
|
||||
*/
|
||||
contract AdapterRegistry is IAdapterRegistry, Ownable {
|
||||
mapping(address => AdapterInfo) public adapters;
|
||||
mapping(address => bool) public whitelist;
|
||||
mapping(address => bool) public blacklist;
|
||||
|
||||
event AdapterRegistered(address indexed adapter, string name, AdapterType adapterType);
|
||||
event AdapterWhitelisted(address indexed adapter, bool whitelisted);
|
||||
event AdapterBlacklisted(address indexed adapter, bool blacklisted);
|
||||
|
||||
/**
|
||||
* @notice Register a new adapter
|
||||
*/
|
||||
function registerAdapter(
|
||||
address adapter,
|
||||
string calldata name,
|
||||
AdapterType adapterType
|
||||
) external onlyOwner {
|
||||
require(adapters[adapter].registeredAt == 0, "Adapter already registered");
|
||||
|
||||
adapters[adapter] = AdapterInfo({
|
||||
name: name,
|
||||
adapterType: adapterType,
|
||||
registeredAt: block.timestamp,
|
||||
whitelisted: false
|
||||
});
|
||||
|
||||
emit AdapterRegistered(adapter, name, adapterType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Whitelist an adapter
|
||||
*/
|
||||
function setWhitelist(address adapter, bool _whitelisted) external onlyOwner {
|
||||
require(adapters[adapter].registeredAt > 0, "Adapter not registered");
|
||||
|
||||
adapters[adapter].whitelisted = _whitelisted;
|
||||
whitelist[adapter] = _whitelisted;
|
||||
|
||||
emit AdapterWhitelisted(adapter, _whitelisted);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Blacklist an adapter
|
||||
*/
|
||||
function setBlacklist(address adapter, bool _blacklisted) external onlyOwner {
|
||||
blacklist[adapter] = _blacklisted;
|
||||
|
||||
if (_blacklisted) {
|
||||
adapters[adapter].whitelisted = false;
|
||||
whitelist[adapter] = false;
|
||||
}
|
||||
|
||||
emit AdapterBlacklisted(adapter, _blacklisted);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Check if adapter is whitelisted
|
||||
*/
|
||||
function isWhitelisted(address adapter) external view override returns (bool) {
|
||||
return !blacklist[adapter] && adapters[adapter].whitelisted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Get adapter info
|
||||
*/
|
||||
function getAdapter(address adapter) external view returns (AdapterInfo memory) {
|
||||
return adapters[adapter];
|
||||
}
|
||||
}
|
||||
|
||||
202
contracts/ComboHandler.sol
Normal file
202
contracts/ComboHandler.sol
Normal file
@@ -0,0 +1,202 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
101
contracts/NotaryRegistry.sol
Normal file
101
contracts/NotaryRegistry.sol
Normal file
@@ -0,0 +1,101 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||
import "./interfaces/INotaryRegistry.sol";
|
||||
|
||||
/**
|
||||
* @title NotaryRegistry
|
||||
* @notice Immutable registry for plan hashes, codehashes, and audit trail
|
||||
*/
|
||||
contract NotaryRegistry is INotaryRegistry, Ownable {
|
||||
mapping(bytes32 => PlanRecord) public plans;
|
||||
mapping(bytes32 => CodehashRecord) public codehashes;
|
||||
|
||||
event PlanRegistered(bytes32 indexed planId, address indexed creator, bytes32 planHash);
|
||||
event PlanFinalized(bytes32 indexed planId, bool success, bytes32 receiptHash);
|
||||
event CodehashRegistered(address indexed contractAddress, bytes32 codehash, string version);
|
||||
|
||||
/**
|
||||
* @notice Register a plan with notary
|
||||
*/
|
||||
function registerPlan(
|
||||
bytes32 planId,
|
||||
IComboHandler.Step[] calldata steps,
|
||||
address creator
|
||||
) external override {
|
||||
require(plans[planId].registeredAt == 0, "Plan already registered");
|
||||
|
||||
bytes32 planHash = keccak256(abi.encode(planId, steps, creator));
|
||||
|
||||
plans[planId] = PlanRecord({
|
||||
planHash: planHash,
|
||||
creator: creator,
|
||||
registeredAt: block.timestamp,
|
||||
finalizedAt: 0,
|
||||
success: false,
|
||||
receiptHash: bytes32(0)
|
||||
});
|
||||
|
||||
emit PlanRegistered(planId, creator, planHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Finalize a plan with execution result
|
||||
*/
|
||||
function finalizePlan(
|
||||
bytes32 planId,
|
||||
bool success
|
||||
) external override {
|
||||
PlanRecord storage record = plans[planId];
|
||||
require(record.registeredAt > 0, "Plan not registered");
|
||||
require(record.finalizedAt == 0, "Plan already finalized");
|
||||
|
||||
bytes32 receiptHash = keccak256(abi.encode(planId, success, block.timestamp));
|
||||
|
||||
record.finalizedAt = block.timestamp;
|
||||
record.success = success;
|
||||
record.receiptHash = receiptHash;
|
||||
|
||||
emit PlanFinalized(planId, success, receiptHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Register contract codehash for upgrade verification
|
||||
*/
|
||||
function registerCodehash(
|
||||
address contractAddress,
|
||||
bytes32 codehash,
|
||||
string calldata version
|
||||
) external onlyOwner {
|
||||
codehashes[keccak256(abi.encodePacked(contractAddress, version))] = CodehashRecord({
|
||||
contractAddress: contractAddress,
|
||||
codehash: codehash,
|
||||
version: version,
|
||||
registeredAt: block.timestamp
|
||||
});
|
||||
|
||||
emit CodehashRegistered(contractAddress, codehash, version);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Get plan record
|
||||
*/
|
||||
function getPlan(bytes32 planId) external view returns (PlanRecord memory) {
|
||||
return plans[planId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Verify codehash matches registered version
|
||||
*/
|
||||
function verifyCodehash(
|
||||
address contractAddress,
|
||||
bytes32 codehash,
|
||||
string calldata version
|
||||
) external view returns (bool) {
|
||||
bytes32 key = keccak256(abi.encodePacked(contractAddress, version));
|
||||
CodehashRecord memory record = codehashes[key];
|
||||
return record.codehash == codehash && record.registeredAt > 0;
|
||||
}
|
||||
}
|
||||
|
||||
51
contracts/adapters/AaveAdapter.sol
Normal file
51
contracts/adapters/AaveAdapter.sol
Normal file
@@ -0,0 +1,51 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import "../interfaces/IAdapter.sol";
|
||||
|
||||
/**
|
||||
* @title AaveAdapter
|
||||
* @notice Adapter for Aave lending protocol
|
||||
*/
|
||||
contract AaveAdapter is IAdapter {
|
||||
string public constant override name = "Aave V3";
|
||||
|
||||
// Mock Aave pool (in production, use actual Aave pool)
|
||||
address public pool;
|
||||
|
||||
constructor(address _pool) {
|
||||
pool = _pool;
|
||||
}
|
||||
|
||||
function executeStep(bytes calldata data) external override returns (bool success, bytes memory returnData) {
|
||||
// Decode operation type and parameters
|
||||
// (uint8 operation, address asset, uint256 amount, address collateral)
|
||||
(uint8 operation, address asset, uint256 amount, address collateral) =
|
||||
abi.decode(data, (uint8, address, uint256, address));
|
||||
|
||||
if (operation == 0) {
|
||||
// Borrow
|
||||
// In production: pool.borrow(asset, amount, ...)
|
||||
success = true;
|
||||
returnData = abi.encode(amount);
|
||||
} else if (operation == 1) {
|
||||
// Repay
|
||||
// In production: pool.repay(asset, amount, ...)
|
||||
success = true;
|
||||
returnData = abi.encode(uint256(0));
|
||||
} else {
|
||||
success = false;
|
||||
returnData = "";
|
||||
}
|
||||
}
|
||||
|
||||
function prepareStep(bytes calldata data) external override returns (bool prepared) {
|
||||
// Check if borrow/repay can be prepared (collateral check, etc.)
|
||||
return true;
|
||||
}
|
||||
|
||||
function adapterType() external pure override returns (uint8) {
|
||||
return 0; // DEFI
|
||||
}
|
||||
}
|
||||
|
||||
95
contracts/adapters/Iso20022PayAdapter.sol
Normal file
95
contracts/adapters/Iso20022PayAdapter.sol
Normal file
@@ -0,0 +1,95 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import "../interfaces/IAdapter.sol";
|
||||
|
||||
/**
|
||||
* @title Iso20022PayAdapter
|
||||
* @notice Adapter for ISO-20022 payment instructions (off-chain bridge)
|
||||
*/
|
||||
contract Iso20022PayAdapter is IAdapter {
|
||||
string public constant override name = "ISO-20022 Pay";
|
||||
|
||||
// Event emitted when payment instruction is ready
|
||||
event PaymentInstruction(bytes32 indexed planId, bytes32 messageHash, string isoMessage);
|
||||
|
||||
function executeStep(bytes calldata data) external override returns (bool success, bytes memory returnData) {
|
||||
// Decode payment parameters
|
||||
// (bytes32 planId, string beneficiaryIBAN, uint256 amount, string currency)
|
||||
(bytes32 planId, string memory beneficiaryIBAN, uint256 amount, string memory currency) =
|
||||
abi.decode(data, (bytes32, string, uint256, string));
|
||||
|
||||
// Generate ISO-20022 message (off-chain)
|
||||
bytes32 messageHash = keccak256(abi.encode(planId, beneficiaryIBAN, amount, currency));
|
||||
string memory isoMessage = _generateIsoMessage(planId, beneficiaryIBAN, amount, currency);
|
||||
|
||||
emit PaymentInstruction(planId, messageHash, isoMessage);
|
||||
|
||||
// In production, this would trigger off-chain ISO message generation
|
||||
// The actual payment happens off-chain via banking rails
|
||||
|
||||
success = true;
|
||||
returnData = abi.encode(messageHash);
|
||||
}
|
||||
|
||||
function prepareStep(bytes calldata data) external override returns (bool prepared) {
|
||||
// Check if payment can be prepared (compliance check, etc.)
|
||||
return true;
|
||||
}
|
||||
|
||||
function adapterType() external pure override returns (uint8) {
|
||||
return 1; // FIAT_DTL
|
||||
}
|
||||
|
||||
function _generateIsoMessage(
|
||||
bytes32 planId,
|
||||
string memory beneficiaryIBAN,
|
||||
uint256 amount,
|
||||
string memory currency
|
||||
) internal pure returns (string memory) {
|
||||
// Simplified ISO message generation
|
||||
// In production, use proper XML builder
|
||||
return string(abi.encodePacked(
|
||||
"ISO-20022 Message for Plan: ",
|
||||
_bytes32ToString(planId),
|
||||
", Amount: ",
|
||||
_uint256ToString(amount),
|
||||
" ",
|
||||
currency,
|
||||
", IBAN: ",
|
||||
beneficiaryIBAN
|
||||
));
|
||||
}
|
||||
|
||||
function _bytes32ToString(bytes32 value) internal pure returns (string memory) {
|
||||
bytes memory buffer = new bytes(64);
|
||||
for (uint256 i = 0; i < 32; i++) {
|
||||
buffer[i * 2] = _toHexChar(uint8(value[i]) >> 4);
|
||||
buffer[i * 2 + 1] = _toHexChar(uint8(value[i]) & 0x0f);
|
||||
}
|
||||
return string(buffer);
|
||||
}
|
||||
|
||||
function _uint256ToString(uint256 value) internal pure returns (string memory) {
|
||||
if (value == 0) return "0";
|
||||
uint256 temp = value;
|
||||
uint256 digits;
|
||||
while (temp != 0) {
|
||||
digits++;
|
||||
temp /= 10;
|
||||
}
|
||||
bytes memory buffer = new bytes(digits);
|
||||
while (value != 0) {
|
||||
digits--;
|
||||
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
|
||||
value /= 10;
|
||||
}
|
||||
return string(buffer);
|
||||
}
|
||||
|
||||
function _toHexChar(uint8 value) internal pure returns (bytes1) {
|
||||
if (value < 10) return bytes1(value + 48);
|
||||
else return bytes1(value + 87);
|
||||
}
|
||||
}
|
||||
|
||||
43
contracts/adapters/UniswapAdapter.sol
Normal file
43
contracts/adapters/UniswapAdapter.sol
Normal file
@@ -0,0 +1,43 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import "../interfaces/IAdapter.sol";
|
||||
|
||||
/**
|
||||
* @title UniswapAdapter
|
||||
* @notice Adapter for Uniswap V3 swaps
|
||||
*/
|
||||
contract UniswapAdapter is IAdapter {
|
||||
string public constant override name = "Uniswap V3";
|
||||
|
||||
// Mock Uniswap router (in production, use actual Uniswap V3 router)
|
||||
address public router;
|
||||
|
||||
constructor(address _router) {
|
||||
router = _router;
|
||||
}
|
||||
|
||||
function executeStep(bytes calldata data) external override returns (bool success, bytes memory returnData) {
|
||||
// Decode swap parameters
|
||||
// (address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOutMin, uint24 fee)
|
||||
(address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOutMin, uint24 fee) =
|
||||
abi.decode(data, (address, address, uint256, uint256, uint24));
|
||||
|
||||
// In production, call Uniswap router
|
||||
// (success, returnData) = router.call(abi.encodeWithSignature("exactInputSingle(...)"));
|
||||
|
||||
// Mock implementation
|
||||
success = true;
|
||||
returnData = abi.encode(amountOutMin); // Return amount out
|
||||
}
|
||||
|
||||
function prepareStep(bytes calldata data) external override returns (bool prepared) {
|
||||
// Check if swap can be prepared (liquidity check, etc.)
|
||||
return true;
|
||||
}
|
||||
|
||||
function adapterType() external pure override returns (uint8) {
|
||||
return 0; // DEFI
|
||||
}
|
||||
}
|
||||
|
||||
28
contracts/hardhat.config.ts
Normal file
28
contracts/hardhat.config.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { HardhatUserConfig } from "hardhat/config";
|
||||
import "@nomicfoundation/hardhat-toolbox";
|
||||
|
||||
const config: HardhatUserConfig = {
|
||||
solidity: {
|
||||
version: "0.8.20",
|
||||
settings: {
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
networks: {
|
||||
hardhat: {
|
||||
chainId: 1337,
|
||||
},
|
||||
},
|
||||
paths: {
|
||||
sources: "./",
|
||||
tests: "./test",
|
||||
cache: "./cache",
|
||||
artifacts: "./artifacts",
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
34
contracts/interfaces/IAdapter.sol
Normal file
34
contracts/interfaces/IAdapter.sol
Normal file
@@ -0,0 +1,34 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
/**
|
||||
* @title IAdapter
|
||||
* @notice Interface for protocol adapters
|
||||
*/
|
||||
interface IAdapter {
|
||||
/**
|
||||
* @notice Execute a step using this adapter
|
||||
* @param data Encoded step parameters
|
||||
* @return success Whether execution succeeded
|
||||
* @return returnData Return data from execution
|
||||
*/
|
||||
function executeStep(bytes calldata data) external returns (bool success, bytes memory returnData);
|
||||
|
||||
/**
|
||||
* @notice Prepare step (2PC prepare phase)
|
||||
* @param data Encoded step parameters
|
||||
* @return prepared Whether preparation succeeded
|
||||
*/
|
||||
function prepareStep(bytes calldata data) external returns (bool prepared);
|
||||
|
||||
/**
|
||||
* @notice Get adapter name
|
||||
*/
|
||||
function name() external view returns (string memory);
|
||||
|
||||
/**
|
||||
* @notice Get adapter type (DEFI or FIAT_DTL)
|
||||
*/
|
||||
function adapterType() external view returns (uint8);
|
||||
}
|
||||
|
||||
19
contracts/interfaces/IAdapterRegistry.sol
Normal file
19
contracts/interfaces/IAdapterRegistry.sol
Normal file
@@ -0,0 +1,19 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
interface IAdapterRegistry {
|
||||
enum AdapterType {
|
||||
DEFI,
|
||||
FIAT_DTL
|
||||
}
|
||||
|
||||
struct AdapterInfo {
|
||||
string name;
|
||||
AdapterType adapterType;
|
||||
uint256 registeredAt;
|
||||
bool whitelisted;
|
||||
}
|
||||
|
||||
function isWhitelisted(address adapter) external view returns (bool);
|
||||
}
|
||||
|
||||
54
contracts/interfaces/IComboHandler.sol
Normal file
54
contracts/interfaces/IComboHandler.sol
Normal file
@@ -0,0 +1,54 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
interface IComboHandler {
|
||||
enum StepType {
|
||||
BORROW,
|
||||
SWAP,
|
||||
REPAY,
|
||||
PAY,
|
||||
DEPOSIT,
|
||||
WITHDRAW,
|
||||
BRIDGE
|
||||
}
|
||||
|
||||
enum ExecutionStatus {
|
||||
PENDING,
|
||||
IN_PROGRESS,
|
||||
COMPLETE,
|
||||
FAILED,
|
||||
ABORTED
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
struct StepReceipt {
|
||||
uint256 stepIndex;
|
||||
bool success;
|
||||
bytes returnData;
|
||||
uint256 gasUsed;
|
||||
}
|
||||
|
||||
function executeCombo(
|
||||
bytes32 planId,
|
||||
Step[] calldata steps,
|
||||
bytes calldata signature
|
||||
) external returns (bool success, StepReceipt[] memory receipts);
|
||||
|
||||
function prepare(
|
||||
bytes32 planId,
|
||||
Step[] calldata steps
|
||||
) external returns (bool prepared);
|
||||
|
||||
function commit(bytes32 planId) external returns (bool committed);
|
||||
|
||||
function abort(bytes32 planId) external;
|
||||
|
||||
function getExecutionStatus(bytes32 planId) external view returns (ExecutionStatus status);
|
||||
}
|
||||
|
||||
31
contracts/interfaces/INotaryRegistry.sol
Normal file
31
contracts/interfaces/INotaryRegistry.sol
Normal file
@@ -0,0 +1,31 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import "./IComboHandler.sol";
|
||||
|
||||
interface INotaryRegistry {
|
||||
struct PlanRecord {
|
||||
bytes32 planHash;
|
||||
address creator;
|
||||
uint256 registeredAt;
|
||||
uint256 finalizedAt;
|
||||
bool success;
|
||||
bytes32 receiptHash;
|
||||
}
|
||||
|
||||
struct CodehashRecord {
|
||||
address contractAddress;
|
||||
bytes32 codehash;
|
||||
string version;
|
||||
uint256 registeredAt;
|
||||
}
|
||||
|
||||
function registerPlan(
|
||||
bytes32 planId,
|
||||
IComboHandler.Step[] calldata steps,
|
||||
address creator
|
||||
) external;
|
||||
|
||||
function finalizePlan(bytes32 planId, bool success) external;
|
||||
}
|
||||
|
||||
20
contracts/package.json
Normal file
20
contracts/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "combo-flow-contracts",
|
||||
"version": "1.0.0",
|
||||
"description": "Smart contracts for ISO-20022 Combo Flow",
|
||||
"scripts": {
|
||||
"compile": "hardhat compile",
|
||||
"test": "hardhat test",
|
||||
"deploy": "hardhat run scripts/deploy.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nomicfoundation/hardhat-toolbox": "^4.0.0",
|
||||
"@openzeppelin/contracts": "^5.0.0",
|
||||
"hardhat": "^2.19.0",
|
||||
"typescript": "^5.3.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"chai": "^4.3.10",
|
||||
"@types/chai": "^4.3.11"
|
||||
}
|
||||
}
|
||||
|
||||
151
contracts/test/ComboHandler.test.ts
Normal file
151
contracts/test/ComboHandler.test.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import { expect } from "chai";
|
||||
import { ethers } from "hardhat";
|
||||
import type { ComboHandler, AdapterRegistry, NotaryRegistry } from "../typechain-types";
|
||||
|
||||
describe("ComboHandler", function () {
|
||||
let handler: ComboHandler;
|
||||
let adapterRegistry: AdapterRegistry;
|
||||
let notaryRegistry: NotaryRegistry;
|
||||
|
||||
beforeEach(async function () {
|
||||
// Deploy AdapterRegistry
|
||||
const AdapterRegistryFactory = await ethers.getContractFactory("AdapterRegistry");
|
||||
adapterRegistry = await AdapterRegistryFactory.deploy();
|
||||
await adapterRegistry.deployed();
|
||||
|
||||
// Deploy NotaryRegistry
|
||||
const NotaryRegistryFactory = await ethers.getContractFactory("NotaryRegistry");
|
||||
notaryRegistry = await NotaryRegistryFactory.deploy();
|
||||
await notaryRegistry.deployed();
|
||||
|
||||
// Deploy ComboHandler
|
||||
const HandlerFactory = await ethers.getContractFactory("ComboHandler");
|
||||
handler = await HandlerFactory.deploy(adapterRegistry.address, notaryRegistry.address);
|
||||
await handler.deployed();
|
||||
});
|
||||
|
||||
it("Should register plan when executing", async function () {
|
||||
const planId = ethers.utils.id("test-plan");
|
||||
const steps: any[] = [];
|
||||
const signature = "0x";
|
||||
|
||||
// This would require a whitelisted adapter
|
||||
// For now, test that plan registration happens
|
||||
await expect(
|
||||
handler.executeCombo(planId, steps, signature)
|
||||
).to.be.revertedWith("Adapter not whitelisted");
|
||||
});
|
||||
|
||||
it("Should prepare and commit plan (2PC)", async function () {
|
||||
const planId = ethers.utils.id("test-plan");
|
||||
const steps: any[] = [];
|
||||
|
||||
// Prepare
|
||||
await expect(handler.prepare(planId, steps))
|
||||
.to.emit(handler, "PlanPrepared")
|
||||
.withArgs(planId);
|
||||
|
||||
// Commit
|
||||
await expect(handler.commit(planId))
|
||||
.to.emit(handler, "PlanCommitted")
|
||||
.withArgs(planId);
|
||||
});
|
||||
|
||||
it("Should abort prepared plan", async function () {
|
||||
const planId = ethers.utils.id("test-plan");
|
||||
const steps: any[] = [];
|
||||
|
||||
// Prepare
|
||||
await handler.prepare(planId, steps);
|
||||
|
||||
// Abort
|
||||
await expect(handler.abort(planId))
|
||||
.to.emit(handler, "PlanAborted")
|
||||
.withArgs(planId);
|
||||
});
|
||||
|
||||
it("Should return execution status", async function () {
|
||||
const planId = ethers.utils.id("test-plan");
|
||||
|
||||
const status = await handler.getExecutionStatus(planId);
|
||||
expect(status).to.equal(0); // PENDING
|
||||
});
|
||||
});
|
||||
|
||||
describe("AdapterRegistry", function () {
|
||||
let registry: AdapterRegistry;
|
||||
|
||||
beforeEach(async function () {
|
||||
const Factory = await ethers.getContractFactory("AdapterRegistry");
|
||||
registry = await Factory.deploy();
|
||||
await registry.deployed();
|
||||
});
|
||||
|
||||
it("Should register adapter", async function () {
|
||||
const [owner] = await ethers.getSigners();
|
||||
const adapterAddress = ethers.Wallet.createRandom().address;
|
||||
|
||||
await expect(
|
||||
registry.registerAdapter(adapterAddress, "Test Adapter", 0) // DEFI
|
||||
)
|
||||
.to.emit(registry, "AdapterRegistered")
|
||||
.withArgs(adapterAddress, "Test Adapter", 0);
|
||||
});
|
||||
|
||||
it("Should whitelist adapter", async function () {
|
||||
const [owner] = await ethers.getSigners();
|
||||
const adapterAddress = ethers.Wallet.createRandom().address;
|
||||
|
||||
await registry.registerAdapter(adapterAddress, "Test Adapter", 0);
|
||||
await registry.setWhitelist(adapterAddress, true);
|
||||
|
||||
expect(await registry.isWhitelisted(adapterAddress)).to.be.true;
|
||||
});
|
||||
|
||||
it("Should blacklist adapter", async function () {
|
||||
const [owner] = await ethers.getSigners();
|
||||
const adapterAddress = ethers.Wallet.createRandom().address;
|
||||
|
||||
await registry.registerAdapter(adapterAddress, "Test Adapter", 0);
|
||||
await registry.setWhitelist(adapterAddress, true);
|
||||
await registry.setBlacklist(adapterAddress, true);
|
||||
|
||||
expect(await registry.isWhitelisted(adapterAddress)).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe("NotaryRegistry", function () {
|
||||
let registry: NotaryRegistry;
|
||||
|
||||
beforeEach(async function () {
|
||||
const Factory = await ethers.getContractFactory("NotaryRegistry");
|
||||
registry = await Factory.deploy();
|
||||
await registry.deployed();
|
||||
});
|
||||
|
||||
it("Should register plan", async function () {
|
||||
const planId = ethers.utils.id("test-plan");
|
||||
const steps: any[] = [];
|
||||
const [creator] = await ethers.getSigners();
|
||||
|
||||
await expect(
|
||||
registry.registerPlan(planId, steps, creator.address)
|
||||
)
|
||||
.to.emit(registry, "PlanRegistered");
|
||||
});
|
||||
|
||||
it("Should finalize plan", async function () {
|
||||
const planId = ethers.utils.id("test-plan");
|
||||
const steps: any[] = [];
|
||||
const [creator] = await ethers.getSigners();
|
||||
|
||||
await registry.registerPlan(planId, steps, creator.address);
|
||||
|
||||
await expect(
|
||||
registry.finalizePlan(planId, true)
|
||||
)
|
||||
.to.emit(registry, "PlanFinalized")
|
||||
.withArgs(planId, true, ethers.utils.id(""));
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user