// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IAtomicLiquidityVault} from "./interfaces/IAtomicLiquidityVault.sol"; import {AtomicTypes} from "./AtomicTypes.sol"; contract AtomicLiquidityVault is AccessControl, IAtomicLiquidityVault { using SafeERC20 for IERC20; bytes32 public constant COORDINATOR_ROLE = keccak256("COORDINATOR_ROLE"); bytes32 public constant RECONCILER_ROLE = keccak256("RECONCILER_ROLE"); bytes32 public constant BUFFER_MANAGER_ROLE = keccak256("BUFFER_MANAGER_ROLE"); struct StoredCorridorState { uint256 totalLiquidity; uint256 reservedLiquidity; uint256 targetBuffer; uint256 settlementBacklog; } struct Reservation { bytes32 corridorId; address token; uint256 amount; bool exists; bool fulfilled; } mapping(bytes32 => mapping(address => StoredCorridorState)) private _corridorState; mapping(bytes32 => Reservation) public reservations; event CorridorFunded(bytes32 indexed corridorId, address indexed token, address indexed funder, uint256 amount); event LiquidityReserved(bytes32 indexed obligationId, bytes32 indexed corridorId, address indexed token, uint256 amount); event ReservedLiquidityFulfilled(bytes32 indexed obligationId, address indexed recipient, uint256 amount); event ReservationReleased(bytes32 indexed obligationId, uint256 amount); event SettlementReconciled(bytes32 indexed corridorId, address indexed token, uint256 amount); event TargetBufferSet(bytes32 indexed corridorId, address indexed token, uint256 targetBuffer); error ReservationExists(); error ReservationMissing(); error ReservationAlreadyFulfilled(); error InsufficientFreeLiquidity(); constructor(address admin) { _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(RECONCILER_ROLE, admin); _grantRole(BUFFER_MANAGER_ROLE, admin); } function fundCorridor(bytes32 corridorId, address token, uint256 amount) external { IERC20(token).safeTransferFrom(msg.sender, address(this), amount); _corridorState[corridorId][token].totalLiquidity += amount; emit CorridorFunded(corridorId, token, msg.sender, amount); } function setTargetBuffer(bytes32 corridorId, address token, uint256 targetBuffer) external onlyRole(BUFFER_MANAGER_ROLE) { _corridorState[corridorId][token].targetBuffer = targetBuffer; emit TargetBufferSet(corridorId, token, targetBuffer); } function reserveLiquidity(bytes32 corridorId, address token, bytes32 obligationId, uint256 amount) external onlyRole(COORDINATOR_ROLE) { if (reservations[obligationId].exists) revert ReservationExists(); if (freeLiquidity(corridorId, token) < amount) revert InsufficientFreeLiquidity(); reservations[obligationId] = Reservation({ corridorId: corridorId, token: token, amount: amount, exists: true, fulfilled: false }); _corridorState[corridorId][token].reservedLiquidity += amount; emit LiquidityReserved(obligationId, corridorId, token, amount); } function fulfillReservedLiquidity(bytes32 obligationId, address recipient) external onlyRole(COORDINATOR_ROLE) returns (uint256 amount) { Reservation storage reservation = reservations[obligationId]; if (!reservation.exists) revert ReservationMissing(); if (reservation.fulfilled) revert ReservationAlreadyFulfilled(); reservation.fulfilled = true; StoredCorridorState storage state = _corridorState[reservation.corridorId][reservation.token]; state.reservedLiquidity -= reservation.amount; state.totalLiquidity -= reservation.amount; state.settlementBacklog += reservation.amount; IERC20(reservation.token).safeTransfer(recipient, reservation.amount); emit ReservedLiquidityFulfilled(obligationId, recipient, reservation.amount); return reservation.amount; } function releaseReservation(bytes32 obligationId) external onlyRole(COORDINATOR_ROLE) returns (uint256 amount) { Reservation storage reservation = reservations[obligationId]; if (!reservation.exists) revert ReservationMissing(); if (reservation.fulfilled) revert ReservationAlreadyFulfilled(); amount = reservation.amount; _corridorState[reservation.corridorId][reservation.token].reservedLiquidity -= amount; delete reservations[obligationId]; emit ReservationReleased(obligationId, amount); } function reconcileSettlement(bytes32 corridorId, address token, uint256 amount, address from) external onlyRole(RECONCILER_ROLE) { IERC20(token).safeTransferFrom(from, address(this), amount); StoredCorridorState storage state = _corridorState[corridorId][token]; state.totalLiquidity += amount; uint256 backlog = state.settlementBacklog; state.settlementBacklog = amount >= backlog ? 0 : backlog - amount; emit SettlementReconciled(corridorId, token, amount); } function getCorridorLiquidityState(bytes32 corridorId, address token) external view returns (AtomicTypes.CorridorLiquidityState memory state) { StoredCorridorState memory stored = _corridorState[corridorId][token]; state = AtomicTypes.CorridorLiquidityState({ totalLiquidity: stored.totalLiquidity, reservedLiquidity: stored.reservedLiquidity, freeLiquidity: stored.totalLiquidity - stored.reservedLiquidity, targetBuffer: stored.targetBuffer, settlementBacklog: stored.settlementBacklog }); } function freeLiquidity(bytes32 corridorId, address token) public view returns (uint256) { StoredCorridorState memory state = _corridorState[corridorId][token]; return state.totalLiquidity - state.reservedLiquidity; } }