Files
smom-dbis-138/contracts/bridge/atomic/AtomicBridgeCoordinator.sol
defiQUG 76aa419320 feat: bridges, PMM, flash workflow, token-aggregation, and deployment docs
- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault.
- Token-aggregation service routes, planner, chain config, relay env templates.
- Config snapshots and multi-chain deployment markdown updates.
- gitignore services/btc-intake/dist/ (tsc output); do not track dist.

Run forge build && forge test before deploy (large solc graph).

Made-with: Cursor
2026-04-07 23:40:52 -07:00

325 lines
15 KiB
Solidity

// 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];
}
}