Files
smom-dbis-138/contracts/bridge/atomic/AtomicBridgeCoordinator.sol

393 lines
18 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");
bytes32 public constant INTENT_ADAPTER_ROLE = keccak256("INTENT_ADAPTER_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;
mapping(bytes32 => address) private _refundRecipients;
mapping(bytes32 => bool) private _asyncDestinationCorridors;
event CorridorConfigured(bytes32 indexed corridorId, address indexed assetIn, address indexed assetOut);
event AsyncDestinationCorridorSet(bytes32 indexed corridorId, bool enabled);
event IntentCreated(
bytes32 indexed obligationId, bytes32 indexed corridorId, bytes32 indexed intentId, address sender
);
event CommitmentAccepted(bytes32 indexed obligationId, address indexed fulfiller, uint256 bondAmount);
event DestinationFulfillmentRecorded(
bytes32 indexed obligationId, bytes32 indexed destinationTxHash, bytes32 indexed invoiceHash, address fulfiller
);
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();
error ZeroPayer();
error ZeroRefundRecipient();
error AsyncDestinationProofRequired();
error NotAsyncDestinationCorridor();
error ZeroDestinationProof();
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 setAsyncDestinationCorridor(bytes32 corridorId, bool enabled) external onlyRole(CORRIDOR_MANAGER_ROLE) {
_asyncDestinationCorridors[corridorId] = enabled;
emit AsyncDestinationCorridorSet(corridorId, enabled);
}
function createIntent(CreateIntentParams calldata p) external nonReentrant returns (bytes32 obligationId) {
obligationId = _createIntent(p, msg.sender, msg.sender);
}
function createIntentFor(CreateIntentParams calldata p, address payer, address refundTo)
external
nonReentrant
onlyRole(INTENT_ADAPTER_ROLE)
returns (bytes32 obligationId)
{
if (payer == address(0)) revert ZeroPayer();
if (refundTo == address(0)) revert ZeroRefundRecipient();
obligationId = _createIntent(p, payer, refundTo);
}
function _createIntent(CreateIntentParams calldata p, address payer, address refundTo)
internal
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();
bool asyncDestination = _asyncDestinationCorridors[corridorId];
if (!asyncDestination) {
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, payer, ++intentNonce, corridorId, p.amountIn, p.minAmountOut, p.deadline, p.routeId
)
);
obligationId = keccak256(abi.encode(intentId, p.recipient, refundTo));
_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
});
_refundRecipients[obligationId] = refundTo;
obligationEscrow.escrowFunds(obligationId, p.assetIn, payer, p.amountIn);
if (!asyncDestination) {
liquidityVault.reserveLiquidity(corridorId, p.assetOut, obligationId, p.minAmountOut);
}
emit IntentCreated(obligationId, corridorId, intentId, payer);
}
function submitCommitment(bytes32 obligationId, bytes32 settlementMode) external nonReentrant {
AtomicTypes.AtomicIntent memory intent = _intents[obligationId];
bytes32 corridorId = getCorridorId(intent.sourceChain, intent.destinationChain, intent.assetIn, intent.assetOut);
if (_asyncDestinationCorridors[corridorId]) revert AsyncDestinationProofRequired();
_submitCommitment(obligationId, settlementMode, msg.sender, true);
}
function submitAsyncDestinationFulfillment(
bytes32 obligationId,
bytes32 settlementMode,
bytes32 destinationTxHash,
bytes32 invoiceHash
) external nonReentrant {
if (destinationTxHash == bytes32(0)) revert ZeroDestinationProof();
AtomicTypes.AtomicIntent memory intent = _intents[obligationId];
bytes32 corridorId = getCorridorId(intent.sourceChain, intent.destinationChain, intent.assetIn, intent.assetOut);
if (!_asyncDestinationCorridors[corridorId]) revert NotAsyncDestinationCorridor();
_submitCommitment(obligationId, settlementMode, msg.sender, false);
emit DestinationFulfillmentRecorded(obligationId, destinationTxHash, invoiceHash, msg.sender);
}
function _submitCommitment(
bytes32 obligationId,
bytes32 settlementMode,
address fulfiller,
bool releaseLocalLiquidity
) internal {
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, fulfiller, corridorId, requiredBond);
if (releaseLocalLiquidity) {
liquidityVault.fulfillReservedLiquidity(obligationId, intent.recipient);
}
_commitments[obligationId] = AtomicTypes.AtomicCommitment({
intentId: intent.intentId,
fulfiller: fulfiller,
reservedLiquidity: obligation.destinationReserve,
bondAmount: requiredBond,
expiry: block.timestamp + _corridors[corridorId].settlementTimeout,
settlementMode: mode
});
obligation.fulfiller = fulfiller;
obligation.status = AtomicTypes.ObligationStatus.Fulfilled;
emit CommitmentAccepted(obligationId, fulfiller, 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);
if (!_asyncDestinationCorridors[corridorId]) {
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);
address refundTo = _refundRecipients[obligationId];
if (refundTo == address(0)) {
refundTo = payer;
}
if (!_asyncDestinationCorridors[corridorId]) {
liquidityVault.releaseReservation(obligationId);
}
uint256 refunded = obligationEscrow.refundRemaining(obligationId, refundTo);
_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];
}
function getRefundRecipient(bytes32 obligationId) external view returns (address) {
return _refundRecipients[obligationId];
}
function isAsyncDestinationCorridor(bytes32 corridorId) external view returns (bool) {
return _asyncDestinationCorridors[corridorId];
}
}