- 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
152 lines
6.1 KiB
Solidity
152 lines
6.1 KiB
Solidity
// 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;
|
|
}
|
|
}
|