// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; import "../vendor/openzeppelin/UUPSUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "../vendor/openzeppelin/ReentrancyGuardUpgradeable.sol"; import "./interfaces/ILiquidityProvider.sol"; /** * @title LiquidityManager * @notice Orchestrates liquidity across multiple sources (DODO, Uniswap, Curve) * @dev Implements per-asset configuration for PMM usage */ contract LiquidityManager is Initializable, AccessControlUpgradeable, ReentrancyGuardUpgradeable, UUPSUpgradeable { using SafeERC20 for IERC20; bytes32 public constant LIQUIDITY_ADMIN_ROLE = keccak256("LIQUIDITY_ADMIN_ROLE"); bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); struct LiquidityConfig { uint256 minAmountForPMM; // Below this, don't use PMM uint256 maxSlippageBps; // Max acceptable slippage uint256 timeout; // Max wait time for liquidity bool autoCreate; // Auto-create pools if needed bool enabled; // PMM enabled for this asset } struct ProviderInfo { address providerContract; bool active; uint256 priority; // Higher priority = checked first uint256 totalVolume; uint256 successfulSwaps; uint256 failedSwaps; } struct LiquidityReservation { bytes32 messageId; address token; uint256 amount; uint256 reservedAt; bool released; } // Storage mapping(address => LiquidityConfig) public assetConfigs; ILiquidityProvider[] public providers; mapping(address => ProviderInfo) public providerInfo; mapping(bytes32 => LiquidityReservation) public reservations; event LiquidityProvided( bytes32 indexed messageId, address indexed token, uint256 amount, address provider ); event ProviderAdded(address indexed provider, uint256 priority); event ProviderRemoved(address indexed provider); event AssetConfigured(address indexed token, LiquidityConfig config); event LiquidityReserved(bytes32 indexed messageId, address token, uint256 amount); event LiquidityReleased(bytes32 indexed messageId); /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } function initialize(address admin) external initializer { __AccessControl_init(); __ReentrancyGuard_init(); __UUPSUpgradeable_init(); _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(LIQUIDITY_ADMIN_ROLE, admin); _grantRole(UPGRADER_ROLE, admin); } function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADER_ROLE) {} /** * @notice Get best liquidity quote across all providers */ function getBestQuote( address tokenIn, address tokenOut, uint256 amountIn ) external view returns ( address bestProvider, uint256 bestAmountOut, uint256 bestSlippageBps ) { uint256 bestAmount = 0; uint256 bestSlippage = type(uint256).max; for (uint256 i = 0; i < providers.length; i++) { if (!providerInfo[address(providers[i])].active) continue; try providers[i].supportsTokenPair(tokenIn, tokenOut) returns (bool supported) { if (!supported) continue; try providers[i].getQuote(tokenIn, tokenOut, amountIn) returns ( uint256 amountOut, uint256 slippageBps ) { // Select provider with best output and lowest slippage if (amountOut > bestAmount || (amountOut == bestAmount && slippageBps < bestSlippage)) { bestProvider = address(providers[i]); bestAmount = amountOut; bestSlippage = slippageBps; } } catch { // Provider failed, skip continue; } } catch { continue; } } return (bestProvider, bestAmount, bestSlippage); } /** * @notice Provide liquidity for bridge operation */ function provideLiquidity( address token, uint256 amount, bytes calldata strategyParams ) external nonReentrant returns (uint256 liquidityProvided) { LiquidityConfig memory config = assetConfigs[token]; // Check if PMM enabled for this asset if (!config.enabled || amount < config.minAmountForPMM) { return 0; } // Find best provider // In production, this would execute actual liquidity provision // For now, return the amount as-is liquidityProvided = amount; return liquidityProvided; } /** * @notice Reserve liquidity for pending bridge */ function reserveLiquidity( bytes32 messageId, address token, uint256 amount ) external onlyRole(LIQUIDITY_ADMIN_ROLE) { require(messageId != bytes32(0), "Invalid message ID"); require(reservations[messageId].reservedAt == 0, "Already reserved"); reservations[messageId] = LiquidityReservation({ messageId: messageId, token: token, amount: amount, reservedAt: block.timestamp, released: false }); emit LiquidityReserved(messageId, token, amount); } /** * @notice Release reserved liquidity */ function releaseLiquidity(bytes32 messageId) external onlyRole(LIQUIDITY_ADMIN_ROLE) { LiquidityReservation storage reservation = reservations[messageId]; require(reservation.reservedAt > 0, "Not reserved"); require(!reservation.released, "Already released"); reservation.released = true; emit LiquidityReleased(messageId); } /** * @notice Add liquidity provider */ function addProvider( address provider, uint256 priority ) external onlyRole(LIQUIDITY_ADMIN_ROLE) { require(provider != address(0), "Zero address"); require(!providerInfo[provider].active, "Already added"); providers.push(ILiquidityProvider(provider)); providerInfo[provider] = ProviderInfo({ providerContract: provider, active: true, priority: priority, totalVolume: 0, successfulSwaps: 0, failedSwaps: 0 }); emit ProviderAdded(provider, priority); } /** * @notice Remove liquidity provider */ function removeProvider(address provider) external onlyRole(LIQUIDITY_ADMIN_ROLE) { providerInfo[provider].active = false; emit ProviderRemoved(provider); } /** * @notice Configure asset liquidity settings */ function configureAsset( address token, uint256 minAmountForPMM, uint256 maxSlippageBps, uint256 timeout, bool autoCreate, bool enabled ) external onlyRole(LIQUIDITY_ADMIN_ROLE) { assetConfigs[token] = LiquidityConfig({ minAmountForPMM: minAmountForPMM, maxSlippageBps: maxSlippageBps, timeout: timeout, autoCreate: autoCreate, enabled: enabled }); emit AssetConfigured(token, assetConfigs[token]); } // View functions function getAssetConfig(address token) external view returns (LiquidityConfig memory) { return assetConfigs[token]; } function getProviderCount() external view returns (uint256) { return providers.length; } function getProvider(uint256 index) external view returns (address) { return address(providers[index]); } function getReservation(bytes32 messageId) external view returns (LiquidityReservation memory) { return reservations[messageId]; } }