// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/access/AccessControl.sol"; /** * @title PrivatePoolRegistry * @notice Registry of private XAU-anchored stabilization pools (Master Plan Phase 2). * @dev Only the Stabilizer (or whitelisted keeper) should execute swaps for rebalancing. * Liquidity provision can be restricted via STABILIZER_LP_ROLE when enforced by a wrapper or runbook. */ contract PrivatePoolRegistry is AccessControl { bytes32 public constant STABILIZER_LP_ROLE = keccak256("STABILIZER_LP_ROLE"); /// @dev Canonical key: (token0, token1) where token0 < token1 mapping(address => mapping(address => address)) private _pools; event PrivatePoolRegistered( address indexed tokenA, address indexed tokenB, address indexed pool ); event PrivatePoolRemoved( address indexed tokenA, address indexed tokenB, address indexed pool ); constructor(address admin) { require(admin != address(0), "PrivatePoolRegistry: zero admin"); _grantRole(DEFAULT_ADMIN_ROLE, admin); } /** * @notice Register a private pool for a token pair. * @param tokenA First token (order used for storage; getPrivatePool is order-agnostic) * @param tokenB Second token * @param poolAddress DODO pool address (e.g. from DODOPMMIntegration.createPool) */ function register( address tokenA, address tokenB, address poolAddress ) external onlyRole(DEFAULT_ADMIN_ROLE) { require(tokenA != address(0) && tokenB != address(0), "PrivatePoolRegistry: zero token"); require(poolAddress != address(0), "PrivatePoolRegistry: zero pool"); require(tokenA != tokenB, "PrivatePoolRegistry: same token"); (address t0, address t1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); require(_pools[t0][t1] == address(0), "PrivatePoolRegistry: pool already set"); _pools[t0][t1] = poolAddress; emit PrivatePoolRegistered(tokenA, tokenB, poolAddress); } /** * @notice Remove a private pool registration (e.g. migration). */ function unregister(address tokenA, address tokenB) external onlyRole(DEFAULT_ADMIN_ROLE) { (address t0, address t1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); address pool = _pools[t0][t1]; require(pool != address(0), "PrivatePoolRegistry: not registered"); delete _pools[t0][t1]; emit PrivatePoolRemoved(tokenA, tokenB, pool); } /** * @notice Get the private pool for a token pair (order-agnostic). * @param tokenIn One of the pair * @param tokenOut The other * @return pool The registered pool address, or address(0) if none */ function getPrivatePool(address tokenIn, address tokenOut) external view returns (address pool) { if (tokenIn == tokenOut) return address(0); (address t0, address t1) = tokenIn < tokenOut ? (tokenIn, tokenOut) : (tokenOut, tokenIn); return _pools[t0][t1]; } /** * @notice Check if an address is allowed to add liquidity (has STABILIZER_LP_ROLE). * @dev Use in a wrapper or runbook; DODOPMMIntegration.addLiquidity does not check this. */ function isLpAllowed(address account) external view returns (bool) { return hasRole(STABILIZER_LP_ROLE, account); } }