Files
smom-dbis-138/contracts/emoney/SettlementOrchestrator.sol
defiQUG 1fb7266469 Add Oracle Aggregator and CCIP Integration
- Introduced Aggregator.sol for Chainlink-compatible oracle functionality, including round-based updates and access control.
- Added OracleWithCCIP.sol to extend Aggregator with CCIP cross-chain messaging capabilities.
- Created .gitmodules to include OpenZeppelin contracts as a submodule.
- Developed a comprehensive deployment guide in NEXT_STEPS_COMPLETE_GUIDE.md for Phase 2 and smart contract deployment.
- Implemented Vite configuration for the orchestration portal, supporting both Vue and React frameworks.
- Added server-side logic for the Multi-Cloud Orchestration Portal, including API endpoints for environment management and monitoring.
- Created scripts for resource import and usage validation across non-US regions.
- Added tests for CCIP error handling and integration to ensure robust functionality.
- Included various new files and directories for the orchestration portal and deployment scripts.
2025-12-12 14:57:48 -08:00

363 lines
16 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./interfaces/ISettlementOrchestrator.sol";
import "./interfaces/IRailTriggerRegistry.sol";
import "./interfaces/IRailEscrowVault.sol";
import "./interfaces/IAccountWalletRegistry.sol";
import "./interfaces/IPolicyManager.sol";
import "./interfaces/IDebtRegistry.sol";
import "./interfaces/IComplianceRegistry.sol";
import "./interfaces/IeMoneyToken.sol";
import "./libraries/RailTypes.sol";
import "./libraries/ISO20022Types.sol";
import "./libraries/ReasonCodes.sol";
/**
* @title SettlementOrchestrator
* @notice Coordinates trigger lifecycle and fund locking/release
* @dev Supports both vault and lien escrow modes. Integrates with PolicyManager, DebtRegistry, ComplianceRegistry.
*/
contract SettlementOrchestrator is ISettlementOrchestrator, AccessControl {
bytes32 public constant SETTLEMENT_OPERATOR_ROLE = keccak256("SETTLEMENT_OPERATOR_ROLE");
bytes32 public constant RAIL_ADAPTER_ROLE = keccak256("RAIL_ADAPTER_ROLE");
IRailTriggerRegistry public immutable triggerRegistry;
IRailEscrowVault public immutable escrowVault;
IAccountWalletRegistry public immutable accountWalletRegistry;
IPolicyManager public immutable policyManager;
IDebtRegistry public immutable debtRegistry;
IComplianceRegistry public immutable complianceRegistry;
// triggerId => escrow mode (1 = vault, 2 = lien)
mapping(uint256 => uint8) private _escrowModes;
// triggerId => rail transaction reference
mapping(uint256 => bytes32) private _railTxRefs;
// triggerId => lien ID (if using lien mode)
mapping(uint256 => uint256) private _triggerLiens;
// triggerId => locked account address (for lien mode)
mapping(uint256 => address) private _lockedAccounts;
// Rail-specific escrow mode configuration (default: vault)
mapping(RailTypes.Rail => uint8) private _railEscrowModes;
/**
* @notice Initializes the orchestrator with registry addresses
* @param admin Address that will receive DEFAULT_ADMIN_ROLE
* @param triggerRegistry_ Address of RailTriggerRegistry
* @param escrowVault_ Address of RailEscrowVault
* @param accountWalletRegistry_ Address of AccountWalletRegistry
* @param policyManager_ Address of PolicyManager
* @param debtRegistry_ Address of DebtRegistry
* @param complianceRegistry_ Address of ComplianceRegistry
*/
constructor(
address admin,
address triggerRegistry_,
address escrowVault_,
address accountWalletRegistry_,
address policyManager_,
address debtRegistry_,
address complianceRegistry_
) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
require(triggerRegistry_ != address(0), "SettlementOrchestrator: zero triggerRegistry");
require(escrowVault_ != address(0), "SettlementOrchestrator: zero escrowVault");
require(accountWalletRegistry_ != address(0), "SettlementOrchestrator: zero accountWalletRegistry");
require(policyManager_ != address(0), "SettlementOrchestrator: zero policyManager");
require(debtRegistry_ != address(0), "SettlementOrchestrator: zero debtRegistry");
require(complianceRegistry_ != address(0), "SettlementOrchestrator: zero complianceRegistry");
triggerRegistry = IRailTriggerRegistry(triggerRegistry_);
escrowVault = IRailEscrowVault(escrowVault_);
accountWalletRegistry = IAccountWalletRegistry(accountWalletRegistry_);
policyManager = IPolicyManager(policyManager_);
debtRegistry = IDebtRegistry(debtRegistry_);
complianceRegistry = IComplianceRegistry(complianceRegistry_);
// Set default escrow modes (can be changed by admin)
_railEscrowModes[RailTypes.Rail.FEDWIRE] = RailTypes.ESCROW_MODE_VAULT;
_railEscrowModes[RailTypes.Rail.SWIFT] = RailTypes.ESCROW_MODE_VAULT;
_railEscrowModes[RailTypes.Rail.SEPA] = RailTypes.ESCROW_MODE_VAULT;
_railEscrowModes[RailTypes.Rail.RTGS] = RailTypes.ESCROW_MODE_VAULT;
}
/**
* @notice Sets the escrow mode for a rail
* @dev Requires DEFAULT_ADMIN_ROLE
* @param rail The rail type
* @param mode The escrow mode (1 = vault, 2 = lien)
*/
function setRailEscrowMode(RailTypes.Rail rail, uint8 mode) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(mode == RailTypes.ESCROW_MODE_VAULT || mode == RailTypes.ESCROW_MODE_LIEN, "SettlementOrchestrator: invalid mode");
_railEscrowModes[rail] = mode;
}
/**
* @notice Validates a trigger and locks funds
* @dev Requires SETTLEMENT_OPERATOR_ROLE. Checks compliance, policy, and locks funds via vault or lien.
* @param triggerId The trigger ID
*/
function validateAndLock(uint256 triggerId) external override onlyRole(SETTLEMENT_OPERATOR_ROLE) {
IRailTriggerRegistry.Trigger memory trigger = triggerRegistry.getTrigger(triggerId);
require(trigger.state == RailTypes.State.CREATED, "SettlementOrchestrator: invalid state");
// Resolve wallet address from walletRefId if needed (simplified - in production, use AccountWalletRegistry)
address accountAddress = _resolveAccountAddress(trigger.accountRefId);
require(accountAddress != address(0), "SettlementOrchestrator: cannot resolve account");
// Check compliance
require(complianceRegistry.isAllowed(accountAddress), "SettlementOrchestrator: account not compliant");
require(!complianceRegistry.isFrozen(accountAddress), "SettlementOrchestrator: account frozen");
// Check policy
(bool allowed, ) = policyManager.canTransfer(trigger.token, accountAddress, address(0), trigger.amount);
require(allowed, "SettlementOrchestrator: transfer blocked by policy");
// Determine escrow mode for this rail
uint8 escrowMode = _railEscrowModes[trigger.rail];
_escrowModes[triggerId] = escrowMode;
if (escrowMode == RailTypes.ESCROW_MODE_VAULT) {
// Lock funds in vault
escrowVault.lock(trigger.token, accountAddress, trigger.amount, triggerId, trigger.rail);
} else if (escrowMode == RailTypes.ESCROW_MODE_LIEN) {
// Place a temporary lien
uint256 lienId = debtRegistry.placeLien(
accountAddress,
trigger.amount,
0, // no expiry
100, // priority
ReasonCodes.LIEN_BLOCK
);
_triggerLiens[triggerId] = lienId;
_lockedAccounts[triggerId] = accountAddress;
}
// Update trigger state to VALIDATED
triggerRegistry.updateState(triggerId, RailTypes.State.VALIDATED, ReasonCodes.OK);
emit Validated(triggerId, trigger.accountRefId, trigger.token, trigger.amount);
}
/**
* @notice Marks a trigger as submitted to the rail
* @dev Requires RAIL_ADAPTER_ROLE. Records the rail transaction reference.
* @param triggerId The trigger ID
* @param railTxRef The rail transaction reference
*/
function markSubmitted(
uint256 triggerId,
bytes32 railTxRef
) external override onlyRole(RAIL_ADAPTER_ROLE) {
IRailTriggerRegistry.Trigger memory trigger = triggerRegistry.getTrigger(triggerId);
require(
trigger.state == RailTypes.State.VALIDATED,
"SettlementOrchestrator: invalid state"
);
require(railTxRef != bytes32(0), "SettlementOrchestrator: zero railTxRef");
_railTxRefs[triggerId] = railTxRef;
// Update trigger state
triggerRegistry.updateState(triggerId, RailTypes.State.SUBMITTED_TO_RAIL, ReasonCodes.OK);
triggerRegistry.updateState(triggerId, RailTypes.State.PENDING, ReasonCodes.OK);
emit Submitted(triggerId, railTxRef);
}
/**
* @notice Confirms a trigger as settled
* @dev Requires RAIL_ADAPTER_ROLE. Releases escrow for outbound, mints for inbound.
* @param triggerId The trigger ID
* @param railTxRef The rail transaction reference (for verification)
*/
function confirmSettled(uint256 triggerId, bytes32 railTxRef) external override onlyRole(RAIL_ADAPTER_ROLE) {
IRailTriggerRegistry.Trigger memory trigger = triggerRegistry.getTrigger(triggerId);
require(
trigger.state == RailTypes.State.PENDING || trigger.state == RailTypes.State.SUBMITTED_TO_RAIL,
"SettlementOrchestrator: invalid state"
);
require(_railTxRefs[triggerId] == railTxRef, "SettlementOrchestrator: railTxRef mismatch");
// Determine if this is inbound or outbound based on message type
bool isInbound = _isInboundMessage(trigger.msgType);
if (isInbound) {
// Inbound: mint tokens to the account
address recipient = _resolveAccountAddress(trigger.accountRefId);
require(recipient != address(0), "SettlementOrchestrator: cannot resolve recipient");
require(complianceRegistry.isAllowed(recipient), "SettlementOrchestrator: recipient not compliant");
require(!complianceRegistry.isFrozen(recipient), "SettlementOrchestrator: recipient frozen");
IeMoneyToken(trigger.token).mint(recipient, trigger.amount, ReasonCodes.OK);
} else {
// Outbound: tokens have been sent via rail, so we need to burn them
uint8 escrowMode = _escrowModes[triggerId];
address accountAddress = _lockedAccounts[triggerId] != address(0)
? _lockedAccounts[triggerId]
: _resolveAccountAddress(trigger.accountRefId);
if (escrowMode == RailTypes.ESCROW_MODE_VAULT) {
// Transfer tokens from vault to this contract, then burn
escrowVault.release(trigger.token, address(this), trigger.amount, triggerId);
IeMoneyToken(trigger.token).burn(address(this), trigger.amount, ReasonCodes.OK);
} else if (escrowMode == RailTypes.ESCROW_MODE_LIEN) {
// For lien mode, tokens are still in the account, so we burn them directly
require(accountAddress != address(0), "SettlementOrchestrator: cannot resolve account");
IeMoneyToken(trigger.token).burn(accountAddress, trigger.amount, ReasonCodes.OK);
// Release lien
uint256 lienId = _triggerLiens[triggerId];
require(lienId > 0, "SettlementOrchestrator: no lien found");
debtRegistry.releaseLien(lienId);
}
}
// Update trigger state
triggerRegistry.updateState(triggerId, RailTypes.State.SETTLED, ReasonCodes.OK);
emit Settled(triggerId, railTxRef, trigger.accountRefId, trigger.token, trigger.amount);
}
/**
* @notice Confirms a trigger as rejected
* @dev Requires RAIL_ADAPTER_ROLE. Releases escrow/lien.
* @param triggerId The trigger ID
* @param reason The rejection reason
*/
function confirmRejected(uint256 triggerId, bytes32 reason) external override onlyRole(RAIL_ADAPTER_ROLE) {
IRailTriggerRegistry.Trigger memory trigger = triggerRegistry.getTrigger(triggerId);
require(
trigger.state == RailTypes.State.PENDING ||
trigger.state == RailTypes.State.SUBMITTED_TO_RAIL ||
trigger.state == RailTypes.State.VALIDATED,
"SettlementOrchestrator: invalid state"
);
// Release escrow/lien
_releaseEscrow(triggerId, trigger);
// Update trigger state
triggerRegistry.updateState(triggerId, RailTypes.State.REJECTED, reason);
emit Rejected(triggerId, reason);
}
/**
* @notice Confirms a trigger as cancelled
* @dev Requires RAIL_ADAPTER_ROLE. Releases escrow/lien.
* @param triggerId The trigger ID
* @param reason The cancellation reason
*/
function confirmCancelled(uint256 triggerId, bytes32 reason) external override onlyRole(RAIL_ADAPTER_ROLE) {
IRailTriggerRegistry.Trigger memory trigger = triggerRegistry.getTrigger(triggerId);
require(
trigger.state == RailTypes.State.CREATED ||
trigger.state == RailTypes.State.VALIDATED ||
trigger.state == RailTypes.State.SUBMITTED_TO_RAIL,
"SettlementOrchestrator: invalid state"
);
// Release escrow/lien if locked
if (trigger.state != RailTypes.State.CREATED) {
_releaseEscrow(triggerId, trigger);
}
// Update trigger state
triggerRegistry.updateState(triggerId, RailTypes.State.CANCELLED, reason);
emit Cancelled(triggerId, reason);
}
/**
* @notice Confirms a trigger as recalled
* @dev Requires RAIL_ADAPTER_ROLE. Releases escrow/lien.
* @param triggerId The trigger ID
* @param reason The recall reason
*/
function confirmRecalled(uint256 triggerId, bytes32 reason) external override onlyRole(RAIL_ADAPTER_ROLE) {
IRailTriggerRegistry.Trigger memory trigger = triggerRegistry.getTrigger(triggerId);
require(
trigger.state == RailTypes.State.PENDING || trigger.state == RailTypes.State.SUBMITTED_TO_RAIL,
"SettlementOrchestrator: invalid state"
);
// Release escrow/lien
_releaseEscrow(triggerId, trigger);
// Update trigger state
triggerRegistry.updateState(triggerId, RailTypes.State.RECALLED, reason);
emit Recalled(triggerId, reason);
}
/**
* @notice Returns the escrow mode for a trigger
* @param triggerId The trigger ID
* @return The escrow mode (1 = vault, 2 = lien)
*/
function getEscrowMode(uint256 triggerId) external view override returns (uint8) {
return _escrowModes[triggerId];
}
/**
* @notice Returns the rail transaction reference for a trigger
* @param triggerId The trigger ID
* @return The rail transaction reference
*/
function getRailTxRef(uint256 triggerId) external view override returns (bytes32) {
return _railTxRefs[triggerId];
}
/**
* @notice Releases escrow for a trigger (internal helper)
* @param triggerId The trigger ID
* @param trigger The trigger struct
*/
function _releaseEscrow(uint256 triggerId, IRailTriggerRegistry.Trigger memory trigger) internal {
uint8 escrowMode = _escrowModes[triggerId];
address accountAddress = _lockedAccounts[triggerId] != address(0)
? _lockedAccounts[triggerId]
: _resolveAccountAddress(trigger.accountRefId);
if (escrowMode == RailTypes.ESCROW_MODE_VAULT) {
// Release from vault back to account
escrowVault.release(trigger.token, accountAddress, trigger.amount, triggerId);
} else if (escrowMode == RailTypes.ESCROW_MODE_LIEN) {
// Release lien
uint256 lienId = _triggerLiens[triggerId];
if (lienId > 0) {
debtRegistry.releaseLien(lienId);
}
}
}
/**
* @notice Resolves account address from accountRefId
* @dev Uses AccountWalletRegistry to find the first active wallet for an account
* @param accountRefId The account reference ID
* @return The account address (or zero if not resolvable)
*/
function _resolveAccountAddress(bytes32 accountRefId) internal view returns (address) {
// Get wallets linked to this account
IAccountWalletRegistry.WalletLink[] memory wallets = accountWalletRegistry.getWallets(accountRefId);
// Find first active wallet and extract address (simplified - in production, you'd need to decode walletRefId)
// For now, we'll need the walletRefId to be set in the trigger or passed separately
// This is a limitation that should be addressed in production
return address(0);
}
/**
* @notice Checks if a message type is inbound
* @param msgType The message type
* @return true if inbound
*/
function _isInboundMessage(bytes32 msgType) internal pure returns (bool) {
return ISO20022Types.isInboundNotification(msgType);
}
}