- 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
325 lines
15 KiB
Solidity
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];
|
|
}
|
|
}
|