// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import {AtomicTypes} from "./AtomicTypes.sol"; import {IAtomicBridgeCoordinator} from "./interfaces/IAtomicBridgeCoordinator.sol"; import {IAtomicLiquidityVault} from "./interfaces/IAtomicLiquidityVault.sol"; import {IAtomicFulfillerRegistry} from "./interfaces/IAtomicFulfillerRegistry.sol"; import {AtomicObligationEscrow} from "./AtomicObligationEscrow.sol"; import {AtomicSettlementRouter} from "./AtomicSettlementRouter.sol"; import {AtomicFeePolicy} from "./AtomicFeePolicy.sol"; import {AtomicSlashingManager} from "./AtomicSlashingManager.sol"; contract AtomicBridgeCoordinator is AccessControl, ReentrancyGuard, IAtomicBridgeCoordinator { bytes32 public constant CORRIDOR_MANAGER_ROLE = keccak256("CORRIDOR_MANAGER_ROLE"); bytes32 public constant SETTLEMENT_MANAGER_ROLE = keccak256("SETTLEMENT_MANAGER_ROLE"); IAtomicLiquidityVault public immutable liquidityVault; IAtomicFulfillerRegistry public immutable fulfillerRegistry; AtomicObligationEscrow public immutable obligationEscrow; AtomicSettlementRouter public immutable settlementRouter; AtomicFeePolicy public immutable feePolicy; AtomicSlashingManager public immutable slashingManager; address public immutable protocolTreasury; struct CreateIntentParams { uint64 sourceChain; uint64 destinationChain; address assetIn; address assetOut; uint256 amountIn; uint256 minAmountOut; address recipient; uint256 deadline; bytes32 routeId; } uint256 public intentNonce; mapping(bytes32 => AtomicTypes.CorridorConfig) private _corridors; mapping(bytes32 => AtomicTypes.AtomicIntent) private _intents; mapping(bytes32 => AtomicTypes.AtomicCommitment) private _commitments; mapping(bytes32 => AtomicTypes.AtomicObligation) private _obligations; event CorridorConfigured(bytes32 indexed corridorId, address indexed assetIn, address indexed assetOut); event IntentCreated(bytes32 indexed obligationId, bytes32 indexed corridorId, bytes32 indexed intentId, address sender); event CommitmentAccepted(bytes32 indexed obligationId, address indexed fulfiller, uint256 bondAmount); event SettlementInitiated(bytes32 indexed obligationId, bytes32 indexed settlementId, bytes32 indexed settlementMode); event SettlementConfirmed(bytes32 indexed obligationId, uint256 replenishAmount); event IntentRefunded(bytes32 indexed obligationId, uint256 refundedAmount); event CorridorDegraded(bytes32 indexed corridorId, bytes32 indexed obligationId, bytes32 reason); error CorridorDisabled(); error CorridorDegradedError(); error InvalidCorridor(); error InvalidDeadline(); error MaxNotionalExceeded(); error ReservedLiquidityLimitExceeded(); error SettlementBacklogExceeded(); error InvalidStatus(); error DeadlineNotReached(); error SettlementTimeoutNotReached(); error MinimumReplenishNotMet(); constructor( address liquidityVault_, address fulfillerRegistry_, address obligationEscrow_, address settlementRouter_, address feePolicy_, address slashingManager_, address protocolTreasury_, address admin ) { liquidityVault = IAtomicLiquidityVault(liquidityVault_); fulfillerRegistry = IAtomicFulfillerRegistry(fulfillerRegistry_); obligationEscrow = AtomicObligationEscrow(obligationEscrow_); settlementRouter = AtomicSettlementRouter(settlementRouter_); feePolicy = AtomicFeePolicy(feePolicy_); slashingManager = AtomicSlashingManager(slashingManager_); protocolTreasury = protocolTreasury_; _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(CORRIDOR_MANAGER_ROLE, admin); _grantRole(SETTLEMENT_MANAGER_ROLE, admin); } function getCorridorId( uint64 sourceChain, uint64 destinationChain, address assetIn, address assetOut ) public pure returns (bytes32) { return keccak256(abi.encode(sourceChain, destinationChain, assetIn, assetOut)); } function configureCorridor(AtomicTypes.CorridorConfig calldata cfg) external onlyRole(CORRIDOR_MANAGER_ROLE) { bytes32 corridorId = getCorridorId(cfg.sourceChain, cfg.destinationChain, cfg.assetIn, cfg.assetOut); _corridors[corridorId] = cfg; emit CorridorConfigured(corridorId, cfg.assetIn, cfg.assetOut); } function setCorridorDegraded(bytes32 corridorId, bool degraded) external onlyRole(CORRIDOR_MANAGER_ROLE) { _corridors[corridorId].degraded = degraded; } function createIntent(CreateIntentParams calldata p) external nonReentrant returns (bytes32 obligationId) { bytes32 corridorId = getCorridorId(p.sourceChain, p.destinationChain, p.assetIn, p.assetOut); AtomicTypes.CorridorConfig memory cfg = _corridors[corridorId]; if (!cfg.enabled) revert CorridorDisabled(); if (cfg.degraded) revert CorridorDegradedError(); if (cfg.assetIn != p.assetIn || cfg.assetOut != p.assetOut) revert InvalidCorridor(); if (p.deadline <= block.timestamp || p.deadline > block.timestamp + cfg.fulfilmentTimeout) revert InvalidDeadline(); if (p.amountIn > cfg.maxNotional) revert MaxNotionalExceeded(); AtomicTypes.CorridorLiquidityState memory state = liquidityVault.getCorridorLiquidityState(corridorId, p.assetOut); if (p.minAmountOut > state.freeLiquidity) revert ReservedLiquidityLimitExceeded(); if (state.totalLiquidity > 0) { uint256 nextReserved = state.reservedLiquidity + p.minAmountOut; if ((nextReserved * 10_000) / state.totalLiquidity > cfg.maxReservedBps) { revert ReservedLiquidityLimitExceeded(); } } if (state.settlementBacklog > cfg.maxSettlementBacklog) revert SettlementBacklogExceeded(); bytes32 intentId = keccak256( abi.encode( block.chainid, msg.sender, ++intentNonce, corridorId, p.amountIn, p.minAmountOut, p.deadline, p.routeId ) ); obligationId = keccak256(abi.encode(intentId, p.recipient)); _intents[obligationId] = AtomicTypes.AtomicIntent({ sourceChain: p.sourceChain, destinationChain: p.destinationChain, assetIn: p.assetIn, assetOut: p.assetOut, amountIn: p.amountIn, minAmountOut: p.minAmountOut, recipient: p.recipient, deadline: p.deadline, routeId: p.routeId, intentId: intentId }); _obligations[obligationId] = AtomicTypes.AtomicObligation({ obligationId: obligationId, intentId: intentId, sourceEscrow: p.amountIn, destinationReserve: p.minAmountOut, fulfiller: address(0), status: AtomicTypes.ObligationStatus.IntentCreated, settlementInitiatedAt: 0 }); obligationEscrow.escrowFunds(obligationId, p.assetIn, msg.sender, p.amountIn); liquidityVault.reserveLiquidity(corridorId, p.assetOut, obligationId, p.minAmountOut); emit IntentCreated(obligationId, corridorId, intentId, msg.sender); } function submitCommitment(bytes32 obligationId, bytes32 settlementMode) external nonReentrant { AtomicTypes.AtomicIntent memory intent = _intents[obligationId]; AtomicTypes.AtomicObligation storage obligation = _obligations[obligationId]; if (obligation.status != AtomicTypes.ObligationStatus.IntentCreated) revert InvalidStatus(); if (block.timestamp > intent.deadline) revert DeadlineNotReached(); bytes32 corridorId = getCorridorId(intent.sourceChain, intent.destinationChain, intent.assetIn, intent.assetOut); bytes32 mode = settlementMode == bytes32(0) ? _corridors[corridorId].defaultSettlementMode : settlementMode; uint256 requiredBond = feePolicy.requiredBond(corridorId, obligation.destinationReserve); fulfillerRegistry.lockBond(obligationId, msg.sender, corridorId, requiredBond); liquidityVault.fulfillReservedLiquidity(obligationId, intent.recipient); _commitments[obligationId] = AtomicTypes.AtomicCommitment({ intentId: intent.intentId, fulfiller: msg.sender, reservedLiquidity: obligation.destinationReserve, bondAmount: requiredBond, expiry: block.timestamp + _corridors[corridorId].settlementTimeout, settlementMode: mode }); obligation.fulfiller = msg.sender; obligation.status = AtomicTypes.ObligationStatus.Fulfilled; emit CommitmentAccepted(obligationId, msg.sender, requiredBond); } function initiateSettlement(bytes32 obligationId, bytes calldata settlementData) external payable nonReentrant onlyRole(SETTLEMENT_MANAGER_ROLE) returns (bytes32 settlementId) { AtomicTypes.AtomicIntent memory intent = _intents[obligationId]; AtomicTypes.AtomicCommitment memory commitment = _commitments[obligationId]; AtomicTypes.AtomicObligation storage obligation = _obligations[obligationId]; if (obligation.status != AtomicTypes.ObligationStatus.Fulfilled) revert InvalidStatus(); bytes32 corridorId = getCorridorId(intent.sourceChain, intent.destinationChain, intent.assetIn, intent.assetOut); uint256 settlementAmount = _releaseEscrowForSettlement( obligationId, corridorId, intent.amountIn, commitment.fulfiller ); settlementId = _executeSettlement( obligationId, commitment.settlementMode, intent.assetIn, settlementAmount, intent.recipient, settlementData ); obligation.status = AtomicTypes.ObligationStatus.SettlementPending; obligation.settlementInitiatedAt = block.timestamp; emit SettlementInitiated(obligationId, settlementId, commitment.settlementMode); } function _releaseEscrowForSettlement( bytes32 obligationId, bytes32 corridorId, uint256 amountIn, address fulfiller ) internal returns (uint256 settlementAmount) { (uint256 fulfillerFee, uint256 protocolFee) = feePolicy.quoteFees(corridorId, amountIn); settlementAmount = amountIn - fulfillerFee - protocolFee; if (protocolFee > 0) { obligationEscrow.release(obligationId, protocolTreasury, protocolFee); } if (fulfillerFee > 0) { obligationEscrow.release(obligationId, fulfiller, fulfillerFee); } obligationEscrow.release(obligationId, address(settlementRouter), settlementAmount); } function _executeSettlement( bytes32 obligationId, bytes32 settlementMode, address assetIn, uint256 settlementAmount, address recipient, bytes calldata settlementData ) internal returns (bytes32 settlementId) { settlementId = settlementRouter.executeSettlement{value: msg.value}( obligationId, settlementMode, assetIn, settlementAmount, recipient, settlementData ); } function confirmSettlement(bytes32 obligationId, uint256 replenishAmount) external nonReentrant onlyRole(SETTLEMENT_MANAGER_ROLE) { AtomicTypes.AtomicIntent memory intent = _intents[obligationId]; AtomicTypes.AtomicObligation storage obligation = _obligations[obligationId]; if (obligation.status != AtomicTypes.ObligationStatus.SettlementPending) revert InvalidStatus(); if (replenishAmount < obligation.destinationReserve) revert MinimumReplenishNotMet(); bytes32 corridorId = getCorridorId(intent.sourceChain, intent.destinationChain, intent.assetIn, intent.assetOut); liquidityVault.reconcileSettlement(corridorId, intent.assetOut, replenishAmount, msg.sender); fulfillerRegistry.releaseBond(obligationId); obligation.status = AtomicTypes.ObligationStatus.Settled; emit SettlementConfirmed(obligationId, replenishAmount); } function refundExpiredIntent(bytes32 obligationId) external nonReentrant { AtomicTypes.AtomicIntent memory intent = _intents[obligationId]; AtomicTypes.AtomicObligation storage obligation = _obligations[obligationId]; if (obligation.status != AtomicTypes.ObligationStatus.IntentCreated) revert InvalidStatus(); if (block.timestamp <= intent.deadline) revert DeadlineNotReached(); bytes32 corridorId = getCorridorId(intent.sourceChain, intent.destinationChain, intent.assetIn, intent.assetOut); (, address payer,,,) = obligationEscrow.escrows(obligationId); liquidityVault.releaseReservation(obligationId); uint256 refunded = obligationEscrow.refundRemaining(obligationId, payer); _corridors[corridorId].degraded = true; obligation.status = AtomicTypes.ObligationStatus.Refunded; emit IntentRefunded(obligationId, refunded); emit CorridorDegraded(corridorId, obligationId, keccak256("FULFILMENT_TIMEOUT")); } function handleSettlementTimeout(bytes32 obligationId) external nonReentrant { AtomicTypes.AtomicIntent memory intent = _intents[obligationId]; AtomicTypes.AtomicCommitment memory commitment = _commitments[obligationId]; AtomicTypes.AtomicObligation storage obligation = _obligations[obligationId]; if (obligation.status != AtomicTypes.ObligationStatus.SettlementPending) revert InvalidStatus(); if (block.timestamp <= commitment.expiry) revert SettlementTimeoutNotReached(); bytes32 corridorId = getCorridorId(intent.sourceChain, intent.destinationChain, intent.assetIn, intent.assetOut); _corridors[corridorId].degraded = true; slashingManager.slash(obligationId, protocolTreasury, keccak256("SETTLEMENT_TIMEOUT")); obligation.status = AtomicTypes.ObligationStatus.Slashed; emit CorridorDegraded(corridorId, obligationId, keccak256("SETTLEMENT_TIMEOUT")); } function getCorridorConfig(bytes32 corridorId) external view returns (AtomicTypes.CorridorConfig memory) { return _corridors[corridorId]; } function getIntent(bytes32 obligationId) external view returns (AtomicTypes.AtomicIntent memory) { return _intents[obligationId]; } function getCommitment(bytes32 obligationId) external view returns (AtomicTypes.AtomicCommitment memory) { return _commitments[obligationId]; } function getObligation(bytes32 obligationId) external view returns (AtomicTypes.AtomicObligation memory) { return _obligations[obligationId]; } }