DEXEngineUpgradeable

Description:

Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "DEXEngineUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "./AccessControlUpgradeable.sol";
import "./CoreRegistry.sol";
import "./TokenRegistryUpgradeable.sol";
import { IPool, PoolFactoryUpgradeable } from "./PoolFactoryUpgradeable.sol";
import "./AMMMath.sol";

interface IPoolDEX {
    function getReserves() external view returns (uint256 reserve0, uint256 reserve1, uint256 blockTimestampLast);
    function swapExactTokensForTokens(
        address tokenIn,
        address tokenOut,
        uint256 amountIn,
        uint256 amountOutMin,
        address to
    ) external returns (uint256 amountOut);
    function mint(address to) external returns (uint256 liquidity);
    function burn(address to) external returns (uint256 amount0, uint256 amount1);
    function balanceOf(address account) external view returns (uint256);
}

contract DEXEngineUpgradeable is
    Initializable,
    ReentrancyGuardUpgradeable,
    UUPSUpgradeable,
    LaxceAccessControlUpgradeable
{
    using SafeERC20 for IERC20;

    uint256 public constant MAX_SLIPPAGE = 5000;
    uint256 public constant MIN_SLIPPAGE = 1;
    uint256 public constant DEFAULT_DEADLINE = 1200;
    uint256 public constant MAX_HOPS = 5;
    uint256 public constant BASIS_POINTS = 10000;

    enum SwapType {
        EXACT_INPUT_SINGLE,
        EXACT_OUTPUT_SINGLE,
        EXACT_INPUT_MULTI,
        EXACT_OUTPUT_MULTI
    }

    enum LiquidityAction {
        ADD,
        REMOVE,
        INCREASE,
        DECREASE
    }

    struct SwapParams {
        SwapType swapType;
        address tokenIn;
        address tokenOut;
        uint256 amountIn;
        uint256 amountOut;
        uint256 amountInMaximum;
        uint256 amountOutMinimum;
        address recipient;
        uint256 deadline;
        bytes path;
        uint24 fee;
        uint160 sqrtPriceLimitX96;
    }

    struct SwapResult {
        uint256 amountIn;
        uint256 amountOut;
        uint256 gasUsed;
        uint256 effectivePrice;
        uint256 priceImpact;
        address[] poolsUsed;
        uint256 totalFee;
    }

    struct LiquidityParams {
        LiquidityAction action;
        address tokenA;
        address tokenB;
        uint24 fee;
        int24 tickLower;
        int24 tickUpper;
        uint256 amount0Desired;
        uint256 amount1Desired;
        uint256 amount0Min;
        uint256 amount1Min;
        uint256 liquidity;
        address recipient;
        uint256 deadline;
    }

    struct LiquidityResult {
        uint256 tokenId;
        uint128 liquidity;
        uint256 amount0;
        uint256 amount1;
        uint256 fees0;
        uint256 fees1;
    }

    struct DEXSettings {
        uint256 protocolFeeRate;
        uint256 maxSlippage;
        uint256 defaultDeadline;
        address feeRecipient;
        bool emergencyMode;
        bool maintenanceMode;
    }

    struct UserStats {
        uint256 totalSwaps;
        uint256 totalVolume;
        uint256 totalLiquidity;
        uint256 totalFees;
        uint256 lastActivity;
        bool isVIP;
    }

    struct DEXStats {
        uint256 totalSwaps;
        uint256 totalVolume;
        uint256 totalLiquidity;
        uint256 totalFees;
        uint256 activeUsers;
        uint256 activePools;
    }

    CoreRegistry public coreRegistry;

    TokenRegistryUpgradeable public tokenRegistry;

    PoolFactoryUpgradeable public poolFactory;

    DEXSettings public dexSettings;

    mapping(address => UserStats) public userStats;

    DEXStats public dexStats;

    mapping(bytes32 => bytes) public optimalPaths;

    mapping(address => mapping(address => uint256)) public priceCache;
    mapping(address => mapping(address => uint256)) public priceCacheTimestamp;

    mapping(address => uint256) public userDailyVolume;
    mapping(address => uint256) public userLastReset;

    mapping(address => bool) public userWhitelist;
    mapping(address => bool) public userBlacklist;

    mapping(address => uint256) public feeDiscounts;

    event SwapExecuted(
        address indexed user,
        address indexed tokenIn,
        address indexed tokenOut,
        uint256 amountIn,
        uint256 amountOut,
        uint256 fee,
        SwapType swapType
    );

    event LiquidityModified(
        address indexed user,
        address indexed tokenA,
        address indexed tokenB,
        uint24 fee,
        LiquidityAction action,
        uint256 liquidity,
        uint256 amount0,
        uint256 amount1
    );

    event PathOptimized(
        address indexed tokenIn,
        address indexed tokenOut,
        bytes oldPath,
        bytes newPath,
        uint256 improvement
    );

    event DEXSettingsUpdated(DEXSettings settings);

    event UserStatsUpdated(
        address indexed user,
        uint256 totalSwaps,
        uint256 totalVolume
    );

    error DEX__InvalidToken();
    error DEX__InsufficientAmount();
    error DEX__ExcessiveSlippage();
    error DEX__DeadlineExpired();
    error DEX__PathNotFound();
    error DEX__InsufficientLiquidity();
    error DEX__UserBlacklisted();
    error DEX__ExceedsDailyLimit();
    error DEX__EmergencyMode();
    error DEX__MaintenanceMode();
    error DEX__InvalidPath();
    error DEX__PriceImpactTooHigh();

    modifier validDeadline(uint256 deadline) {
        if (block.timestamp > deadline) revert DEX__DeadlineExpired();
        _;
    }

    modifier notBlacklisted(address user) {
        if (userBlacklist[user]) revert DEX__UserBlacklisted();
        _;
    }

    modifier notInEmergency() {
        if (dexSettings.emergencyMode) revert DEX__EmergencyMode();
        _;
    }

    modifier notInMaintenance() {
        if (dexSettings.maintenanceMode) revert DEX__MaintenanceMode();
        _;
    }

    modifier validTokens(address tokenA, address tokenB) {
        if (!tokenRegistry.isTokenTradeable(tokenA) || !tokenRegistry.isTokenTradeable(tokenB)) {
            revert DEX__InvalidToken();
        }
        _;
    }

    function initialize(
        address _coreRegistry,
        address _admin,
        DEXSettings memory _settings
    ) external initializer {
        __ReentrancyGuard_init();
        __UUPSUpgradeable_init();

        SecuritySettings memory securitySettings = SecuritySettings({
            emergencyDelay: 24 hours,
            adminDelay: 1 hours,
            emergencyMode: false,
            lastEmergencyTime: 0
        });

        __LaxceAccessControl_init(_admin, "1.0.0", securitySettings);

        coreRegistry = CoreRegistry(_coreRegistry);
        dexSettings = _settings;

        _updateRegistryReferences();
    }

    function _updateRegistryReferences() internal {
        tokenRegistry = TokenRegistryUpgradeable(
            coreRegistry.getContract(coreRegistry.TOKEN_REGISTRY())
        );
        poolFactory = PoolFactoryUpgradeable(
            coreRegistry.getContract(coreRegistry.POOL_FACTORY())
        );
    }

    function executeSwap(
        SwapParams calldata params
    ) external payable nonReentrant
        notBlacklisted(msg.sender)
        notInEmergency()
        notInMaintenance()
        validDeadline(params.deadline)
        validTokens(params.tokenIn, params.tokenOut)
        returns (SwapResult memory result)
    {

        _checkDailyLimits(msg.sender, params.amountIn);

        uint256 expectedPrice = _getExpectedPrice(params.tokenIn, params.tokenOut);

        if (params.swapType == SwapType.EXACT_INPUT_SINGLE) {
            result = _executeExactInputSingle(params);
        } else if (params.swapType == SwapType.EXACT_OUTPUT_SINGLE) {
            result = _executeExactOutputSingle(params);
        } else if (params.swapType == SwapType.EXACT_INPUT_MULTI) {
            result = _executeExactInputMulti(params);
        } else {
            result = _executeExactOutputMulti(params);
        }

        uint256 priceImpact = _calculatePriceImpact(expectedPrice, result.effectivePrice);
        if (priceImpact > dexSettings.maxSlippage) {
            revert DEX__PriceImpactTooHigh();
        }

        uint256 protocolFee = _calculateProtocolFee(msg.sender, result.amountOut);
        if (protocolFee > 0) {
            IERC20(params.tokenOut).safeTransfer(dexSettings.feeRecipient, protocolFee);
            result.amountOut -= protocolFee;
            result.totalFee += protocolFee;
        }

        _updateUserStats(msg.sender, result.amountIn, result.totalFee);
        _updateDEXStats(result.amountIn, result.totalFee);

        _updatePriceCache(params.tokenIn, params.tokenOut, result.effectivePrice);

        emit SwapExecuted(
            msg.sender,
            params.tokenIn,
            params.tokenOut,
            result.amountIn,
            result.amountOut,
            result.totalFee,
            params.swapType
        );
    }

    function _executeExactInputSingle(SwapParams calldata params) internal returns (SwapResult memory) {
        address pool = poolFactory.getPool(params.tokenIn, params.tokenOut, params.fee);
        if (pool == address(0)) revert DEX__InsufficientLiquidity();

        uint256 amountOut;
        uint256 priceImpact;

        {
            (uint256 reserve0, uint256 reserve1) = _getPoolReserves(pool);
            bool zeroForOne = params.tokenIn < params.tokenOut;
            uint256 reserveIn = zeroForOne ? reserve0 : reserve1;
            uint256 reserveOut = zeroForOne ? reserve1 : reserve0;

            amountOut = AMMMath.getAmountOut(
                params.amountIn,
                reserveIn,
                reserveOut,
                params.fee
            );

            if (amountOut < params.amountOutMinimum) revert DEX__InsufficientAmount();

            priceImpact = AMMMath.calculatePriceImpact(
                params.amountIn,
                reserveIn,
                reserveOut
            );
        }

        IERC20(params.tokenIn).safeTransferFrom(msg.sender, address(this), params.amountIn);
        IERC20(params.tokenIn).safeIncreaseAllowance(pool, params.amountIn);

        uint256 actualAmountOut = IPoolDEX(pool).swapExactTokensForTokens(
            params.tokenIn,
            params.tokenOut,
            params.amountIn,
            params.amountOutMinimum,
            params.recipient
        );

        address[] memory poolsUsed = new address[](1);
        poolsUsed[0] = pool;

        return SwapResult({
            amountIn: params.amountIn,
            amountOut: actualAmountOut,
            gasUsed: gasleft(),
            effectivePrice: (actualAmountOut * 1e18) / params.amountIn,
            priceImpact: priceImpact,
            poolsUsed: poolsUsed,
            totalFee: (params.amountIn * params.fee) / BASIS_POINTS
        });
    }

    function _executeExactOutputSingle(SwapParams calldata params) internal view returns (SwapResult memory) {

        return SwapResult({
            amountIn: 0,
            amountOut: params.amountOut,
            gasUsed: gasleft(),
            effectivePrice: 0,
            priceImpact: 0,
            poolsUsed: new address[](1),
            totalFee: 0
        });
    }

    function _executeExactInputMulti(SwapParams calldata params) internal returns (SwapResult memory) {
        address[] memory tokens = _decodePath(params.path);
        uint24[] memory fees = _decodePathFees(params.path);

        if (tokens.length < 2) revert DEX__InvalidPath();
        if (tokens[0] != params.tokenIn || tokens[tokens.length - 1] != params.tokenOut) {
            revert DEX__InvalidPath();
        }

        return _performMultiHopSwap(params, tokens, fees);
    }

    function _performMultiHopSwap(
        SwapParams calldata params,
        address[] memory tokens,
        uint24[] memory fees
    ) internal returns (SwapResult memory) {
        uint256 currentAmountIn = params.amountIn;
        uint256 totalFee = 0;
        address[] memory poolsUsed = new address[](tokens.length - 1);

        IERC20(params.tokenIn).safeTransferFrom(msg.sender, address(this), currentAmountIn);

        for (uint256 i = 0; i < tokens.length - 1; i++) {
            (currentAmountIn, totalFee) = _executeHop(
                tokens[i],
                tokens[i + 1],
                i < fees.length ? fees[i] : 3000,
                currentAmountIn,
                totalFee,
                i == tokens.length - 2 ? params.recipient : address(this),
                poolsUsed,
                i
            );
        }

        if (currentAmountIn < params.amountOutMinimum) revert DEX__InsufficientAmount();

        return SwapResult({
            amountIn: params.amountIn,
            amountOut: currentAmountIn,
            gasUsed: gasleft(),
            effectivePrice: (currentAmountIn * 1e18) / params.amountIn,
            priceImpact: _calculatePriceImpact(
                _getExpectedPrice(params.tokenIn, params.tokenOut),
                (currentAmountIn * 1e18) / params.amountIn
            ),
            poolsUsed: poolsUsed,
            totalFee: totalFee
        });
    }

    function _executeHop(
        address tokenIn,
        address tokenOut,
        uint24 fee,
        uint256 amountIn,
        uint256 totalFee,
        address recipient,
        address[] memory poolsUsed,
        uint256 index
    ) internal returns (uint256 amountOut, uint256 newTotalFee) {
        address pool = poolFactory.getPool(tokenIn, tokenOut, fee);
        if (pool == address(0)) revert DEX__InsufficientLiquidity();

        poolsUsed[index] = pool;

        IERC20(tokenIn).safeIncreaseAllowance(pool, amountIn);
        amountOut = IPoolDEX(pool).swapExactTokensForTokens(
            tokenIn,
            tokenOut,
            amountIn,
            0,
            recipient
        );

        newTotalFee = totalFee + (amountIn * fee) / BASIS_POINTS;
    }

    function _executeExactOutputMulti(SwapParams calldata params) internal view returns (SwapResult memory) {

        return SwapResult({
            amountIn: 0,
            amountOut: params.amountOut,
            gasUsed: gasleft(),
            effectivePrice: 0,
            priceImpact: 0,
            poolsUsed: new address[](0),
            totalFee: 0
        });
    }

    function manageLiquidity(
        LiquidityParams calldata params
    ) external payable nonReentrant
        notBlacklisted(msg.sender)
        notInEmergency()
        validDeadline(params.deadline)
        validTokens(params.tokenA, params.tokenB)
        returns (LiquidityResult memory result)
    {
        if (params.action == LiquidityAction.ADD) {
            result = _addLiquidity(params);
        } else if (params.action == LiquidityAction.REMOVE) {
            result = _removeLiquidity(params);
        } else if (params.action == LiquidityAction.INCREASE) {
            result = _increaseLiquidity(params);
        } else {
            result = _decreaseLiquidity(params);
        }

        _updateUserLiquidityStats(msg.sender, result.liquidity, params.action);

        emit LiquidityModified(
            msg.sender,
            params.tokenA,
            params.tokenB,
            params.fee,
            params.action,
            result.liquidity,
            result.amount0,
            result.amount1
        );
    }

    function _addLiquidity(LiquidityParams calldata params) internal returns (LiquidityResult memory) {

        address pool = poolFactory.getPool(params.tokenA, params.tokenB, params.fee);
        if (pool == address(0)) revert DEX__InsufficientLiquidity();

        (uint256 reserve0, uint256 reserve1,) = IPoolDEX(pool).getReserves();

        (uint256 amount0, uint256 amount1) = AMMMath.calculateOptimalLiquidity(
            params.amount0Desired,
            params.amount1Desired,
            reserve0,
            reserve1
        );

        if (amount0 < params.amount0Min || amount1 < params.amount1Min) {
            revert DEX__InsufficientAmount();
        }

        if (amount0 > 0) {
            IERC20(params.tokenA).safeTransferFrom(msg.sender, pool, amount0);
        }
        if (amount1 > 0) {
            IERC20(params.tokenB).safeTransferFrom(msg.sender, pool, amount1);
        }

        uint256 liquidity = IPoolDEX(pool).mint(params.recipient);

        return LiquidityResult({
            tokenId: 0,
            liquidity: uint128(liquidity),
            amount0: amount0,
            amount1: amount1,
            fees0: 0,
            fees1: 0
        });
    }

    function _removeLiquidity(LiquidityParams calldata params) internal returns (LiquidityResult memory) {

        address pool = poolFactory.getPool(params.tokenA, params.tokenB, params.fee);
        if (pool == address(0)) revert DEX__InsufficientLiquidity();

        uint256 lpBalance = IPoolDEX(pool).balanceOf(msg.sender);
        if (lpBalance < params.liquidity) revert DEX__InsufficientAmount();

        IERC20(pool).safeTransferFrom(msg.sender, pool, params.liquidity);

        (uint256 amount0, uint256 amount1) = IPoolDEX(pool).burn(params.recipient);

        if (amount0 < params.amount0Min || amount1 < params.amount1Min) {
            revert DEX__InsufficientAmount();
        }

        return LiquidityResult({
            tokenId: 0,
            liquidity: uint128(params.liquidity),
            amount0: amount0,
            amount1: amount1,
            fees0: 0,
            fees1: 0
        });
    }

    function _increaseLiquidity(LiquidityParams calldata ) internal pure returns (LiquidityResult memory) {

        return LiquidityResult({
            tokenId: 0,
            liquidity: 0,
            amount0: 0,
            amount1: 0,
            fees0: 0,
            fees1: 0
        });
    }

    function _decreaseLiquidity(LiquidityParams calldata params) internal pure returns (LiquidityResult memory) {

        return LiquidityResult({
            tokenId: 0,
            liquidity: uint128(params.liquidity),
            amount0: 0,
            amount1: 0,
            fees0: 0,
            fees1: 0
        });
    }

    function _findOptimalPath(
        address tokenIn,
        address tokenOut,
        uint256 amountIn
    ) internal view returns (bytes memory) {

        bytes32 pathKey = keccak256(abi.encode(tokenIn, tokenOut, amountIn));
        if (optimalPaths[pathKey].length > 0) {
            return optimalPaths[pathKey];
        }

        return "";
    }

    function optimizePath(
        address tokenIn,
        address tokenOut,
        uint256 amountIn
    ) external onlyRole(OPERATOR_ROLE) returns (bytes memory newPath) {
        bytes32 pathKey = keccak256(abi.encode(tokenIn, tokenOut, amountIn));
        bytes memory oldPath = optimalPaths[pathKey];

        newPath = _calculateOptimalPath(tokenIn, tokenOut, amountIn);

        optimalPaths[pathKey] = newPath;

        uint256 improvement = _calculatePathImprovement(oldPath, newPath, amountIn);

        emit PathOptimized(tokenIn, tokenOut, oldPath, newPath, improvement);
    }

    function _calculateOptimalPath(
        address ,
        address ,
        uint256
    ) internal pure returns (bytes memory) {

        return "";
    }

    function _getExpectedPrice(address tokenIn, address tokenOut) internal view returns (uint256) {

        if (priceCacheTimestamp[tokenIn][tokenOut] + 300 > block.timestamp) {
            return priceCache[tokenIn][tokenOut];
        }

        return _calculateCurrentPrice(tokenIn, tokenOut);
    }

    function _calculateCurrentPrice(address tokenIn, address tokenOut) internal view returns (uint256) {

        address pool = poolFactory.getPool(tokenIn, tokenOut, 3000);
        if (pool == address(0)) return 0;

        (uint256 reserve0, uint256 reserve1) = _getPoolReserves(pool);
        if (reserve0 == 0 || reserve1 == 0) return 0;

        bool zeroForOne = tokenIn < tokenOut;
        if (zeroForOne) {
            return (reserve1 * 1e18) / reserve0;
        } else {
            return (reserve0 * 1e18) / reserve1;
        }
    }

    function _getPoolReserves(address pool) internal view returns (uint256 reserve0, uint256 reserve1) {

        try IPoolDEX(pool).getReserves() returns (uint256 r0, uint256 r1, uint256) {
            return (r0, r1);
        } catch {
            return (0, 0);
        }
    }

    function _calculatePriceImpact(uint256 expectedPrice, uint256 actualPrice) internal pure returns (uint256) {
        if (expectedPrice == 0) return 0;

        if (actualPrice > expectedPrice) {
            return ((actualPrice - expectedPrice) * BASIS_POINTS) / expectedPrice;
        } else {
            return ((expectedPrice - actualPrice) * BASIS_POINTS) / expectedPrice;
        }
    }

    function _calculateProtocolFee(address user, uint256 amount) internal view returns (uint256) {
        uint256 baseRate = dexSettings.protocolFeeRate;
        uint256 discount = feeDiscounts[user];

        uint256 effectiveRate = baseRate > discount ? baseRate - discount : 0;

        return (amount * effectiveRate) / BASIS_POINTS;
    }

    function _checkDailyLimits(address user, uint256 amount) internal {
        if (userLastReset[user] + 1 days < block.timestamp) {
            userDailyVolume[user] = 0;
            userLastReset[user] = block.timestamp;
        }

        userDailyVolume[user] += amount;

        if (!userStats[user].isVIP) {
            uint256 dailyLimit = 100000 * 1e18;
            if (userDailyVolume[user] > dailyLimit) {
                revert DEX__ExceedsDailyLimit();
            }
        }
    }

    function _updateUserStats(address user, uint256 volume, uint256 fees) internal {
        UserStats storage stats = userStats[user];
        stats.totalSwaps++;
        stats.totalVolume += volume;
        stats.totalFees += fees;
        stats.lastActivity = block.timestamp;

        if (stats.totalVolume > 1000000 * 1e18) {
            stats.isVIP = true;
        }

        emit UserStatsUpdated(user, stats.totalSwaps, stats.totalVolume);
    }

    function _updateDEXStats(uint256 volume, uint256 fees) internal {
        dexStats.totalSwaps++;
        dexStats.totalVolume += volume;
        dexStats.totalFees += fees;
    }

    function _updateUserLiquidityStats(address user, uint128 liquidity, LiquidityAction action) internal {
        UserStats storage stats = userStats[user];

        if (action == LiquidityAction.ADD || action == LiquidityAction.INCREASE) {
            stats.totalLiquidity += liquidity;
        } else {
            stats.totalLiquidity = stats.totalLiquidity > liquidity ?
                stats.totalLiquidity - liquidity : 0;
        }

        stats.lastActivity = block.timestamp;
    }

    function _updatePriceCache(address tokenIn, address tokenOut, uint256 price) internal {
        priceCache[tokenIn][tokenOut] = price;
        priceCacheTimestamp[tokenIn][tokenOut] = block.timestamp;

        if (price > 0) {
            priceCache[tokenOut][tokenIn] = (1e36) / price;
            priceCacheTimestamp[tokenOut][tokenIn] = block.timestamp;
        }
    }

    function _calculatePathImprovement(
        bytes memory ,
        bytes memory ,
        uint256
    ) internal pure returns (uint256) {

        return 0;
    }

    function _decodePath(bytes memory path) internal pure returns (address[] memory tokens) {

        uint256 numTokens = (path.length + 3) / 23;
        tokens = new address[](numTokens);

        for (uint256 i = 0; i < numTokens; i++) {
            uint256 offset = i * 23;
            if (offset + 20 <= path.length) {
                bytes20 tokenBytes;
                assembly {
                    tokenBytes := mload(add(add(path, 0x20), offset))
                }
                tokens[i] = address(tokenBytes);
            }
        }
    }

    function _decodePathFees(bytes memory path) internal pure returns (uint24[] memory fees) {
        uint256 numFees = path.length / 23;
        fees = new uint24[](numFees);

        for (uint256 i = 0; i < numFees; i++) {
            uint256 offset = i * 23 + 20;
            if (offset + 3 <= path.length) {
                bytes3 feeBytes;
                assembly {
                    feeBytes := mload(add(add(path, 0x20), offset))
                }
                fees[i] = uint24(uint256(uint24(bytes3(feeBytes))));
            }
        }
    }

    function getSwapQuote(
        address ,
        address ,
        uint256 ,
        uint24
    ) external pure returns (uint256 amountOut, uint256 priceImpact, uint256 fee_) {

        return (0, 0, 0);
    }

    function getBestPath(
        address tokenIn,
        address tokenOut,
        uint256 amountIn
    ) external view returns (bytes memory path, uint256 expectedOutput) {
        path = _findOptimalPath(tokenIn, tokenOut, amountIn);

        expectedOutput = 0;
    }

    function getUserStats(address user) external view returns (UserStats memory) {
        return userStats[user];
    }

    function getDEXStats() external view returns (DEXStats memory) {
        return dexStats;
    }

    function updateDEXSettings(DEXSettings calldata settings) external onlyRole(ADMIN_ROLE) {
        dexSettings = settings;
        emit DEXSettingsUpdated(settings);
    }

    function setFeeDiscount(address user, uint256 discount) external onlyRole(ADMIN_ROLE) {
        require(discount <= BASIS_POINTS, "Invalid discount");
        feeDiscounts[user] = discount;
    }

    function setUserWhitelist(address user, bool whitelisted) external onlyRole(ADMIN_ROLE) {
        userWhitelist[user] = whitelisted;
    }

    function setUserBlacklist(address user, bool blacklisted) external onlyRole(ADMIN_ROLE) {
        userBlacklist[user] = blacklisted;
    }

    function setVIPStatus(address user, bool isVIP) external onlyRole(ADMIN_ROLE) {
        userStats[user].isVIP = isVIP;
    }

    function toggleEmergencyMode() external override onlyRole(EMERGENCY_ROLE) emergencyDelayMet {
        dexSettings.emergencyMode = !dexSettings.emergencyMode;
        emit EmergencyModeToggled(dexSettings.emergencyMode, msg.sender);
    }

    function updateRegistryReferences() external onlyRole(ADMIN_ROLE) {
        _updateRegistryReferences();
    }

    function _authorizeUpgrade(address newImplementation)
        internal
        override(UUPSUpgradeable, LaxceAccessControlUpgradeable)
        onlyRole(UPGRADER_ROLE)
    {}
}"
    },
    "AMMMath.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

library AMMMath {

    uint256 public constant BASIS_POINTS = 10000;
    uint256 public constant MIN_LIQUIDITY = 1000;

    error AMM__InsufficientLiquidity();
    error AMM__InsufficientAmount();
    error AMM__InvalidK();
    error AMM__Overflow();

    function getAmountOut(
        uint256 amountIn,
        uint256 reserveIn,
        uint256 reserveOut,
        uint256 fee
    ) internal pure returns (uint256 amountOut) {
        if (amountIn == 0) revert AMM__InsufficientAmount();
        if (reserveIn == 0 || reserveOut == 0) revert AMM__InsufficientLiquidity();

        uint256 amountInWithFee = amountIn * (BASIS_POINTS - fee);
        uint256 numerator = amountInWithFee * reserveOut;
        uint256 denominator = (reserveIn * BASIS_POINTS) + amountInWithFee;

        amountOut = numerator / denominator;
    }

    function sqrt(uint256 x) internal pure returns (uint256) {
        if (x == 0) return 0;
        uint256 z = (x + 1) / 2;
        uint256 y = x;
        while (z < y) {
            y = z;
            z = (x / z + z) / 2;
        }
        return y;
    }

    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    function calculatePriceImpact(
        uint256 amountIn,
        uint256 reserveIn,
        uint256 reserveOut
    ) internal pure returns (uint256) {
        if (reserveIn == 0 || reserveOut == 0) return 0;
        
        uint256 priceBefore = (reserveOut * 1e18) / reserveIn;
        uint256 priceAfter = (reserveOut * 1e18) / (reserveIn + amountIn);
        
        if (priceBefore == 0) return 0;
        
        uint256 priceChange = priceBefore > priceAfter ? 
            priceBefore - priceAfter : priceAfter - priceBefore;
            
        return (priceChange * BASIS_POINTS) / priceBefore;
    }

    function calculateOptimalLiquidity(
        uint256 amount0Desired,
        uint256 amount1Desired,
        uint256 reserve0,
        uint256 reserve1
    ) internal pure returns (uint256 amount0, uint256 amount1) {
        if (reserve0 == 0 && reserve1 == 0) {
            amount0 = amount0Desired;
            amount1 = amount1Desired;
        } else {
            uint256 amount1Optimal = (amount0Desired * reserve1) / reserve0;
            if (amount1Optimal <= amount1Desired) {
                amount0 = amount0Desired;
                amount1 = amount1Optimal;
            } else {
                amount0 = (amount1Desired * reserve0) / reserve1;
                amount1 = amount1Desired;
            }
        }
    }
}"
    },
    "PoolFactoryUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts/proxy/Clones.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";

interface ITokenRegistry {
    function isTokenTradeable(address token) external view returns (bool);
}

interface IPool {
    function initialize(address _token0, address _token1, uint24 _fee, int24 _tickSpacing) external;
    function getReserves() external view returns (uint256 reserve0, uint256 reserve1, uint256 blockTimestampLast);
    function swapExactTokensForTokens(
        address tokenIn,
        address tokenOut,
        uint256 amountIn,
        uint256 amountOutMin,
        address to
    ) external returns (uint256 amountOut);
    function swap(
        address tokenIn,
        address tokenOut,
        uint256 amountIn,
        uint256 amountOut,
        address to
    ) external;
    function mint(address to) external returns (uint256 liquidity);
    function burn(address to) external returns (uint256 amount0, uint256 amount1);
    function token0() external view returns (address);
    function token1() external view returns (address);
    function fee() external view returns (uint24);
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
}

contract PoolFactoryUpgradeable is
    Initializable,
    ReentrancyGuardUpgradeable,
    UUPSUpgradeable,
    AccessControlUpgradeable,
    PausableUpgradeable
{
    using Clones for address;

    uint24 public constant MAX_FEE = 100000;
    int24 public constant MAX_TICK_SPACING = 16384;
    int24 public constant MIN_TICK_SPACING = 1;
    uint256 public constant MAX_POOLS = 50000;

    struct PoolKey {
        address token0;
        address token1;
        uint24 fee;
    }

    struct PoolInfo {
        address pool;
        address token0;
        address token1;
        uint24 fee;
        int24 tickSpacing;
        bool isActive;
        uint256 createdAt;
        address creator;
        uint128 liquidity;
        uint256 volume24h;
        uint256 totalVolume;
        uint256 feesCollected;
    }

    struct FeeTier {
        uint24 fee;
        int24 tickSpacing;
        bool enabled;
        string description;
    }

    struct FactoryStats {
        uint256 totalPools;
        uint256 activePools;
        uint256 totalVolume;
        uint256 totalLiquidity;
        uint256 totalFees;
        uint256 uniqueTokens;
    }

    address public poolImplementation;

    ITokenRegistry public tokenRegistry;

    mapping(bytes32 => address) public pools;

    mapping(address => PoolInfo) public poolInfo;

    mapping(uint24 => FeeTier) public feeTiers;

    address[] public allPools;

    mapping(address => address[]) public tokenPools;

    FactoryStats public factoryStats;

    struct FactorySettings {
        bool paused;
        bool whitelistOnly;
        uint256 creationFee;
        uint256 minLiquidity;
        address feeRecipient;
    }

    FactorySettings public factorySettings;

    mapping(address => bool) public poolCreatorWhitelist;

    mapping(address => bool) public tokenBlacklist;

    event PoolCreated(
        address indexed token0,
        address indexed token1,
        uint24 indexed fee,
        address pool,
        int24 tickSpacing,
        address creator
    );

    event PoolStatusChanged(
        address indexed pool,
        bool isActive,
        address indexed updater
    );

    event FeeTierAdded(
        uint24 indexed fee,
        int24 indexed tickSpacing,
        string description
    );

    event FeeTierUpdated(
        uint24 indexed fee,
        bool enabled
    );

    event PoolImplementationUpdated(
        address indexed oldImplementation,
        address indexed newImplementation
    );

    event FactorySettingsUpdated(FactorySettings settings);

    event PoolStatsUpdated(
        address indexed pool,
        uint128 liquidity,
        uint256 volume24h,
        uint256 totalVolume
    );

    error Factory__IdenticalTokens();
    error Factory__ZeroAddress();
    error Factory__PoolAlreadyExists();
    error Factory__FeeTierNotEnabled();
    error Factory__InvalidTickSpacing();
    error Factory__MaxPoolsReached();
    error Factory__TokenNotSupported();
    error Factory__TokenBlacklisted();
    error Factory__NotWhitelisted();
    error Factory__InsufficientFee();
    error Factory__PoolNotExists();
    error Factory__InvalidFee();
    error Factory__InvalidImplementation();

    modifier validTokens(address tokenA, address tokenB) {
        if (tokenA == tokenB) revert Factory__IdenticalTokens();
        if (tokenA == address(0) || tokenB == address(0)) revert Factory__ZeroAddress();
        _;
    }

    modifier supportedTokens(address tokenA, address tokenB) {
        if (!tokenRegistry.isTokenTradeable(tokenA) || !tokenRegistry.isTokenTradeable(tokenB)) {
            revert Factory__TokenNotSupported();
        }
        if (tokenBlacklist[tokenA] || tokenBlacklist[tokenB]) {
            revert Factory__TokenBlacklisted();
        }
        _;
    }

    modifier onlyWhitelistedCreator() {
        if (factorySettings.whitelistOnly && !poolCreatorWhitelist[msg.sender] && !hasRole(keccak256("ADMIN_ROLE"), msg.sender)) {
            revert Factory__NotWhitelisted();
        }
        _;
    }

    modifier poolExists(address pool) {
        if (poolInfo[pool].pool == address(0)) revert Factory__PoolNotExists();
        _;
    }

    function initialize(
        address _poolImplementation,
        address _tokenRegistry,
        address _admin,
        FactorySettings memory _settings
    ) external initializer {
        __ReentrancyGuard_init();
        __UUPSUpgradeable_init();
        __AccessControl_init();
        __Pausable_init();

        _grantRole(DEFAULT_ADMIN_ROLE, _admin);
        _grantRole(keccak256("ADMIN_ROLE"), _admin);
        _grantRole(keccak256("OPERATOR_ROLE"), _admin);
        _grantRole(keccak256("UPGRADER_ROLE"), _admin);

        if (_poolImplementation == address(0) || _tokenRegistry == address(0)) {
            revert Factory__ZeroAddress();
        }

        poolImplementation = _poolImplementation;
        tokenRegistry = ITokenRegistry(_tokenRegistry);
        factorySettings = _settings;

        _setupDefaultFeeTiers();
    }

    function _setupDefaultFeeTiers() internal {

        feeTiers[100] = FeeTier({
            fee: 100,
            tickSpacing: 1,
            enabled: true,
            description: "0.01% - Stablecoin pairs"
        });

        feeTiers[500] = FeeTier({
            fee: 500,
            tickSpacing: 10,
            enabled: true,
            description: "0.05% - Correlated pairs"
        });

        feeTiers[3000] = FeeTier({
            fee: 3000,
            tickSpacing: 60,
            enabled: true,
            description: "0.3% - Most pairs"
        });

        feeTiers[10000] = FeeTier({
            fee: 10000,
            tickSpacing: 200,
            enabled: true,
            description: "1% - Exotic pairs"
        });
    }

    function createPool(
        address tokenA,
        address tokenB,
        uint24 fee
    ) external payable nonReentrant whenNotPaused
        validTokens(tokenA, tokenB)
        supportedTokens(tokenA, tokenB)
        onlyWhitelistedCreator
        returns (address pool)
    {
        if (allPools.length >= MAX_POOLS) revert Factory__MaxPoolsReached();
        if (!feeTiers[fee].enabled) revert Factory__FeeTierNotEnabled();
        if (msg.value < factorySettings.creationFee) revert Factory__InsufficientFee();

        if (tokenA > tokenB) {
            (tokenA, tokenB) = (tokenB, tokenA);
        }

        bytes32 poolKey = keccak256(abi.encode(tokenA, tokenB, fee));

        if (pools[poolKey] != address(0)) revert Factory__PoolAlreadyExists();

        pool = poolImplementation.clone();

        IPool(pool).initialize(tokenA, tokenB, fee, feeTiers[fee].tickSpacing);

        pools[poolKey] = pool;
        poolInfo[pool] = PoolInfo({
            pool: pool,
            token0: tokenA,
            token1: tokenB,
            fee: fee,
            tickSpacing: feeTiers[fee].tickSpacing,
            isActive: true,
            createdAt: block.timestamp,
            creator: msg.sender,
            liquidity: 0,
            volume24h: 0,
            totalVolume: 0,
            feesCollected: 0
        });

        allPools.push(pool);
        tokenPools[tokenA].push(pool);
        tokenPools[tokenB].push(pool);

        factoryStats.totalPools++;
        factoryStats.activePools++;

        if (msg.value > 0 && factorySettings.feeRecipient != address(0)) {
            payable(factorySettings.feeRecipient).transfer(msg.value);
        }

        emit PoolCreated(tokenA, tokenB, fee, pool, feeTiers[fee].tickSpacing, msg.sender);
    }

    function setPoolStatus(
        address pool,
        bool isActive
    ) external onlyRole(keccak256("ADMIN_ROLE")) poolExists(pool) {
        PoolInfo storage info = poolInfo[pool];

        if (info.isActive == isActive) return;

        info.isActive = isActive;

        if (isActive) {
            factoryStats.activePools++;
        } else {
            factoryStats.activePools--;
        }

        emit PoolStatusChanged(pool, isActive, msg.sender);
    }

    function updatePoolStats(
        address pool,
        uint128 liquidity,
        uint256 volume24h,
        uint256 volumeIncrease,
        uint256 feesCollected
    ) external onlyRole(keccak256("OPERATOR_ROLE")) poolExists(pool) {
        PoolInfo storage info = poolInfo[pool];

        uint256 oldLiquidity = info.liquidity;

        info.liquidity = liquidity;
        info.volume24h = volume24h;
        info.totalVolume += volumeIncrease;
        info.feesCollected += feesCollected;

        factoryStats.totalVolume += volumeIncrease;
        factoryStats.totalFees += feesCollected;

        if (liquidity > oldLiquidity) {
            factoryStats.totalLiquidity += (liquidity - oldLiquidity);
        } else if (liquidity < oldLiquidity) {
            factoryStats.totalLiquidity -= (oldLiquidity - liquidity);
        }

        emit PoolStatsUpdated(pool, liquidity, volume24h, info.totalVolume);
    }

    function addFeeTier(
        uint24 fee,
        int24 tickSpacing,
        string calldata description
    ) external onlyRole(keccak256("ADMIN_ROLE")) {
        if (fee > MAX_FEE) revert Factory__InvalidFee();
        if (tickSpacing < MIN_TICK_SPACING || tickSpacing > MAX_TICK_SPACING) {
            revert Factory__InvalidTickSpacing();
        }

        feeTiers[fee] = FeeTier({
            fee: fee,
            tickSpacing: tickSpacing,
            enabled: true,
            description: description
        });

        emit FeeTierAdded(fee, tickSpacing, description);
    }

    function setFeeTierEnabled(uint24 fee, bool enabled) external onlyRole(keccak256("ADMIN_ROLE")) {
        feeTiers[fee].enabled = enabled;
        emit FeeTierUpdated(fee, enabled);
    }

    function setPoolCreatorWhitelist(
        address creator,
        bool whitelisted
    ) external onlyRole(keccak256("ADMIN_ROLE")) {
        poolCreatorWhitelist[creator] = whitelisted;
    }

    function setTokenBlacklist(
        address token,
        bool blacklisted
    ) external onlyRole(keccak256("ADMIN_ROLE")) {
        tokenBlacklist[token] = blacklisted;
    }

    function batchSetPoolStatus(
        address[] calldata poolAddresses,
        bool[] calldata statuses
    ) external onlyRole(keccak256("ADMIN_ROLE")) {
        require(poolAddresses.length == statuses.length, "Array length mismatch");

        for (uint256 i = 0; i < poolAddresses.length; i++) {
            this.setPoolStatus(poolAddresses[i], statuses[i]);
        }
    }

    function batchSetCreatorWhitelist(
        address[] calldata creators,
        bool[] calldata whitelisted
    ) external onlyRole(keccak256("ADMIN_ROLE")) {
        require(creators.length == whitelisted.length, "Array length mismatch");

        for (uint256 i = 0; i < creators.length; i++) {
            poolCreatorWhitelist[creators[i]] = whitelisted[i];
        }
    }

    function getPool(
        address tokenA,
        address tokenB,
        uint24 fee
    ) external view returns (address pool) {
        if (tokenA > tokenB) {
            (tokenA, tokenB) = (tokenB, tokenA);
        }
        bytes32 poolKey = keccak256(abi.encode(tokenA, tokenB, fee));
        return pools[poolKey];
    }

    function isPoolExists(
        address tokenA,
        address tokenB,
        uint24 fee
    ) external view returns (bool) {
        if (tokenA > tokenB) {
            (tokenA, tokenB) = (tokenB, tokenA);
        }
        bytes32 poolKey = keccak256(abi.encode(tokenA, tokenB, fee));
        return pools[poolKey] != address(0);
    }

    function getPoolCount() external view returns (uint256) {
        return allPools.length;
    }

    function getPoolsPaginated(
        uint256 offset,
        uint256 limit
    ) external view returns (address[] memory poolAddresses, PoolInfo[] memory infos) {
        uint256 total = allPools.length;

        if (offset >= total) {
            return (new address[](0), new PoolInfo[](0));
        }

        uint256 end = offset + limit;
        if (end > total) {
            end = total;
        }

        uint256 length = end - offset;
        poolAddresses = new address[](length);
        infos = new PoolInfo[](length);

        for (uint256 i = 0; i < length; i++) {
            address pool = allPools[offset + i];
            poolAddresses[i] = pool;
            infos[i] = poolInfo[pool];
        }
    }

    function getTokenPools(address token) external view returns (address[] memory) {
        return tokenPools[token];
    }

    function getEnabledFeeTiers() external view returns (FeeTier[] memory) {

        uint256 count = 0;
        uint24[] memory fees = new uint24[](4);
        fees[0] = 100;
        fees[1] = 500;
        fees[2] = 3000;
        fees[3] = 10000;

        for (uint256 i = 0; i < fees.length; i++) {
            if (feeTiers[fees[i]].enabled) {
                count++;
            }
        }

        FeeTier[] memory enabledTiers = new FeeTier[](count);
        uint256 index = 0;

        for (uint256 i = 0; i < fees.length; i++) {
            if (feeTiers[fees[i]].enabled) {
                enabledTiers[index] = feeTiers[fees[i]];
                index++;
            }
        }

        return enabledTiers;
    }

    function searchPools(
        address token,
        uint24 fee,
        bool activeOnly,
        uint256 limit
    ) external view returns (address[] memory results) {
        address[] memory matches = new address[](limit);
        uint256 matchCount = 0;

        for (uint256 i = 0; i < allPools.length && matchCount < limit; i++) {
            address pool = allPools[i];
            PoolInfo memory info = poolInfo[pool];

            bool matches_criteria = true;

            if (token != address(0) && info.token0 != token && info.token1 != token) {
                matches_criteria = false;
            }

            if (fee != 0 && info.fee != fee) {
                matches_criteria = false;
            }

            if (activeOnly && !info.isActive) {
                matches_criteria = false;
            }

            if (matches_criteria) {
                matches[matchCount] = pool;
                matchCount++;
            }
        }

        results = new address[](matchCount);
        for (uint256 i = 0; i < matchCount; i++) {
            results[i] = matches[i];
        }
    }

    function updatePoolImplementation(
        address newImplementation
    ) external onlyRole(keccak256("ADMIN_ROLE")) {
        if (newImplementation == address(0)) revert Factory__InvalidImplementation();

        address oldImplementation = poolImplementation;
        poolImplementation = newImplementation;

        emit PoolImplementationUpdated(oldImplementation, newImplementation);
    }

    function updateFactorySettings(
        FactorySettings calldata settings
    ) external onlyRole(keccak256("ADMIN_ROLE")) {
        factorySettings = settings;
        emit FactorySettingsUpdated(settings);
    }

    function updateTokenRegistry(
        address newTokenRegistry
    ) external onlyRole(keccak256("ADMIN_ROLE")) {
        if (newTokenRegistry == address(0)) revert Factory__ZeroAddress();
        tokenRegistry = ITokenRegistry(newTokenRegistry);
    }

    function withdrawFees(address to) external onlyRole(keccak256("TREASURY_ROLE")) {
        uint256 balance = address(this).balance;
        if (balance > 0) {
            payable(to).transfer(balance);
        }
    }

    function _authorizeUpgrade(address newImplementation)
        internal
        override
        onlyRole(keccak256("UPGRADER_ROLE"))
    {}
}
"
    },
    "TokenRegistryUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "./AccessControlUpgradeable.sol";

contract TokenRegistryUpgradeable is
    Initializable,
    ReentrancyGuardUpgradeable,
    UUPSUpgradeable,
    LaxceAccessControlUpgradeable
{

    uint256 public constant MAX_TOKENS = 10000;
    uint256 public constant MAX_NAME_LENGTH = 50;
    uint256 public constant MAX_SYMBOL_LENGTH = 10;
    uint256 public constant MIN_DECIMALS = 6;
    uint256 public constant MAX_DECIMALS = 18;

    enum TokenStatus {
        PENDING,
        APPROVED,
        SUSPENDED,
        BLACKLISTED
    }

    enum TokenType {
        STANDARD,
        LP_TOKEN,
        WRAPPED,
        STABLECOIN,
        GOVERNANCE
    }

    struct TokenInfo {
        address tokenAddress;
        string name;
        string symbol;
        uint8 decimals;
        TokenType tokenType;
        TokenStatus status;
        uint256 registeredAt;
        uint256 lastUpdate;
        address registrar;
        bool isActive;
        uint256 tradingVolume24h;
        uint256 totalTradingVolume;
    }

    struct TokenMetadata {
        string description;
        string website;
        string logoUrl;
        string[] socialLinks;
        bytes32[] tags;
    }

    struct TokenFees {
        uint256 registrationFee;
        uint256 listingFee;
        uint256 tradingFee;
        bool feeDiscountEligible;
    }

    struct TokenLimits {
        uint256 maxSupply;
        uint256 minLiquidity;
        uint256 maxPriceImpact;
        bool hasLimits;
    }

    mapping(address => TokenInfo) public tokenInfo;

    mapping(address => TokenMetadata) public tokenMetadata;

    mapping(address => TokenFees) public tokenFees;

    mapping(address => TokenLimits) public tokenLimits;

    address[] public allTokens;

    mapping(TokenStatus => address[]) public tokensByStatus;

    mapping(TokenType => address[]) public tokensByType;

    mapping(string => address) public symbolToAddress;

    struct RegistrySettings {
        uint256 defaultRegistrationFee;
        uint256 defaultListingFee;
        uint256 defaultTradingFee;
        bool autoApproval;
        bool requireMetadata;
        uint256 minRegistrationDelay;
    }

    RegistrySettings public registrySettings;

    mapping(address => bool) public whitelist;
    mapping(address => bool) public blacklist;

    struct RegistryStats {
        uint256 totalTokens;
        uint256 activeTokens;
        uint256 pendingTokens;
        uint256 totalVolume;
        uint256 totalFees;
    }

    RegistryStats public registryStats;

    event TokenRegistered(
        address indexed tokenAddress,
        string name,
        string symbol,
        TokenType tokenType,
        address indexed registrar
    );

    event TokenStatusChanged(
        address indexed tokenAddress,
        TokenStatus oldStatus,
        TokenStatus newStatus,
        address indexed updater
    );

    event TokenMetadataUpdated(
        address indexed tokenAddress,
        address indexed updater
    );

    event TokenFeesUpdated(
        address indexed tokenAddress,
        TokenFees fees
    );

    event TokenLimitsUpdated(
        address indexed tokenAddress,
        TokenLimits limits
    );

    event TokenWhitelisted(address indexed tokenAddress, bool whitelisted);
    event TokenBlacklisted(address indexed tokenAddress, bool blacklisted);

    event RegistrySettingsUpdated(RegistrySettings settings);

    event TradingVolumeUpdated(
        address indexed tokenAddress,
        uint256 volume24h,
        uint256 totalVolume
    );

    error Registry__TokenAlreadyExists();
    error Registry__TokenNotExists();
    error Registry__InvalidTokenAddress();
    error Registry__InvalidTokenData();
    error Registry__MaxTokensReached();
    error Registry__TokenSuspended();
    error Registry__TokenBlacklisted();
    error Registry__InsufficientFee();
    error Registry__RegistrationDelayNotMet();
    error Registry__InvalidSymbol();
    error Registry__MetadataRequired();

    modifier onlyValidToken(address token) {
        if (tokenInfo[token].tokenAddress == address(0)) revert Registry__TokenNotExists();
        _;
    }

    modifier onlyActiveToken(address token) {
        TokenInfo memory info = tokenInfo[token];
        if (info.status == TokenStatus.SUSPENDED) revert Registry__TokenSuspended();
        if (info.status == TokenStatus.BLACKLISTED) revert Registry__TokenBlacklisted();
        _;
    }

    modifier notBlacklisted(address token) {
        if (blacklist[token]) revert Registry__TokenBlacklisted();
        _;
    }

    function initialize(
        address _admin,
        RegistrySettings memory _settings
    ) external initializer {
        __ReentrancyGuard_init();
        __UUPSUpgradeable_init();

        SecuritySettings memory securitySettings = SecuritySettings({
            emergencyDelay: 24 hours,
            adminDelay: 1 hours,
            emergencyMode: false,
            lastEmergencyTime: 0
        });

        __LaxceAccessControl_init(_admin, "1.0.0", securitySettings);

        registrySettings = _settings;
    }

    function registerToken(
        address tokenAddress,
        TokenType tokenType,
        TokenMetadata calldata metadata,
        TokenLimits calldata limits
    ) external payable nonReentrant notBlacklisted(tokenAddress) {
        if (tokenAddress == address(0)) revert Registry__InvalidTokenAddress();
        if (tokenInfo[tokenAddress].tokenAddress != address(0)) revert Registry__TokenAlreadyExists();
        if (allTokens.length >= MAX_TOKENS) revert Registry__MaxTokensReached();

        if (msg.value < registrySettings.defaultRegistrationFee) revert Registry__InsufficientFee();

        IERC20Metadata token = IERC20Metadata(tokenAddress);
        string memory name = token.name();
        string memory symbol = token.symbol();
        uint8 decimals = token.decimals();

        _validateTokenData(name, symbol, decimals);

        if (symbolToAddress[symbol] != address(0)) revert Registry__InvalidSymbol();

        if (registrySettings.requireMetadata && bytes(metadata.description).length == 0) {
            revert Registry__MetadataRequired();
        }

        TokenStatus status = registrySettings.autoApproval ? TokenStatus.APPROVED : TokenStatus.PENDING;

        tokenInfo[tokenAddress] = TokenInfo({
            tokenAddress: tokenAddress,
            name: name,
            symbol: symbol,
            decimals: decimals,
            tokenType: tokenType,
            status: status,
            registeredAt: block.timestamp,
            lastUpdate: block.timestamp,
            registrar: msg.sender,
            isActive: status == TokenStatus.APPROVED,
            tradingVolume24h: 0,
            totalTradingVolume: 0
        });

        tokenMetadata[tokenAddress] = metadata;

        tokenLimits[tokenAddress] = limits;

        tokenFees[tokenAddress] = TokenFees({
            registrationFee: registrySettings.defaultRegistrationFee,
            listingFee: registrySettings.defaultListingFee,
            tradingFee: registrySettings.defaultTradingFee,
            feeDiscountEligible: false
        });

        allTokens.push(tokenAddress);
        tokensByStatus[status].push(tokenAddress);
        tokensByType[tokenType].push(tokenAddress);
        symbolToAddress[symbol] = tokenAddress;

        registryStats.totalTokens++;
        if (status == TokenStatus.APPROVED) {
            registryStats.activeTokens++;
        } else {
            registryStats.pendingTokens++;
        }
        registryStats.totalFees += msg.value;

        emit TokenRegistered(tokenAddress, name, symbol, tokenType, msg.sender);
    }

    function _validateTokenData(
        string memory name,
        string memory symbol,
        uint8 decimals
    ) internal pure {
        if (bytes(name).length == 0 || bytes(name).length > MAX_NAME_LENGTH) {
            revert Registry__InvalidTokenData();
        }
        if (bytes(symbol).length == 0 || bytes(symbol).length > MAX_SYMBOL_LENGTH) {
            revert Registry__InvalidTokenData();
        }
        if (decimals < MIN_DECIMALS || decimals > MAX_DECIMALS) {
            revert Registry__InvalidTokenData();
        }
    }

    function setTokenStatus(
        address tokenAddress,
        TokenStatus newStatus
    ) external onlyRole(ADMIN_ROLE) onlyValidToken(tokenAddress) {
        TokenInfo storage info = tokenInfo[tokenAddress];
        TokenStatus oldStatus = info.status;

        if (oldStatus == newStatus) return;

        info.status = newStatus;
        info.isActive = (newStatus == TokenStatus.APPROVED);
        info.lastUpdate = block.timestamp;

        _removeFromStatusList(tokenAddress, oldStatus);
        tokensByStatus[newStatus].push(tokenAddress);

        if (oldStatus == TokenStatus.PENDING && newStatus == TokenStatus.APPROVED) {
            registryStats.pendingTokens--;
            registryStats.activeTokens++;
        } else if (oldStatus == TokenStatus.APPROVED && newStatus == TokenStatus.PENDING) {
            registryStats.activeTokens--;
            registryStats.pendingTokens++;
        } else if (oldStatus == TokenStatus.APPROVED) {
            registryStats.activeTokens--;
        } else if (newStatus == TokenStatus.APPROVED) {
            registryStats.activeTokens++;
        }

        emit TokenStatusChanged(tokenAddress, oldStatus, newStatus, msg.sender);
    }

    function _removeFromStatusList(address tokenAddress, TokenStatus status) internal {
        address[] storage statusList = tokensByStatus[status];
        for (uint256 i = 0; i < statusList.length; i++) {
            if (statusList[i] == tokenAddress) {
                statusList[i] = statusList[statusList.length - 1];
                statusList.pop();
                break;
            }
        }
    }

    function updateTokenMetadata(
        address tokenAddress,
        TokenMetadata calldata metadata
    ) external onlyValidToken(tokenAddress) {

        TokenInfo memory info = tokenInfo[tokenAddress];
        require(
            msg.sender == info.registrar || hasRole(ADMIN_ROLE, msg.sender),
            "Not authorized"
        );

        tokenMetadata[tokenAddress] = metadata;
        tokenInfo[tokenAddress].lastUpdate = block.timestamp;

        emit TokenMetadataUpdated(tokenAddress, msg.sender);
    }

    function updateTokenFees(
        address tokenAddress,
        TokenFees calldata fees
    ) external onlyRole(ADMIN_ROLE) onlyValidToken(tokenAddress) {
        tokenFees[tokenAddress] = fees;
        emit TokenFeesUpdated(tokenAddress, fees);
    }

    function updateTokenLimits(
        address tokenAddress,
        TokenLimits calldata limits
    ) external onlyRole(ADMIN_ROLE) onlyValidToken(tokenAddress) {
        tokenLimits[tokenAddress] = limits;
        emit TokenLimitsUpdated(tokenAddress, limits);
    }

    function setWhitelist(
        address tokenAddress,
        bool whitelisted
    ) external onlyRole(ADMIN_ROLE) {
        whitelist[tokenAddress] = whitelisted;
        emit TokenWhitelisted(tokenAddress, whitelisted);
    }

    function setBlacklist(
        address tokenAddress,
        bool blacklisted
    ) external onlyRole(ADMIN_ROLE) {
        blacklist[tokenAddress] = blacklisted;

        if (blacklisted && tokenInfo[tokenAddress].tokenAddress != address(0)) {
            this.setTokenStatus(tokenAddress, TokenStatus.BLACKLISTED);
        }

        emit TokenBlacklisted(tokenAddress, blacklisted);
    }

    function updateTradingVolume(
        address tokenAddress,
        uint256 volume
    ) external onlyRole(OPERATOR_ROLE) onlyValidToken(tokenAddress) {
        TokenInfo storage info = tokenInfo[tokenAddress];
        info.tradingVolume24h = volume;
        info.totalTradingVolume += volume;

        registryStats.totalVolume += volume;

        emit TradingVolumeUpdated(tokenAddress, volume, info.totalTradingVolume);
    }

    function batchApproveTokens(address[] calldata tokens) external onlyRole(ADMIN_ROLE) {
        for (uint256 i = 0; i < tokens.length; i++) {
            this.setTokenStatus(tokens[i], TokenStatus.APPROVED);
        }
    }

    function batchSuspendTokens(address[] calldata tokens) external onlyRole(ADMIN_ROLE) {
        for (uint256 i = 0; i < tokens.length; i++) {
            this.setTokenStatus(tokens[i], TokenStatus.SUSPENDED);
        }
    }

    function isTokenTradeable(address tokenAddress) external view returns (bool) {
        TokenInfo memory info = tokenInfo[tokenAddress];
        return info.isActive &&
               info.status == TokenStatus.APPROVED &&
               !blacklist[tokenAddress];
    }

    function getTokensByStatus(TokenStatus status) external view returns (address[] memory) {
        return tokensByStatus[status];
    }

    function getTokensByType(TokenType tokenType) external view returns (address[] memory) {
        return tokensByType[tokenType];
    }

    function getTokenCount() external view returns (uint256) {
        return allTokens.length;
    }

    function getTokensPaginated(
        uint256 offset,
        uint256 limit
    ) external view returns (address[] memory tokens, uint256 total) {
        total = allTokens.length;

        if (offset >= total) {
            return (new address[](0), total);
        }

        uint256 end = offset + limit;
        if (end > total) {
            end = total;
        }

        tokens = new address[](end - offset);
        for (uint256 i = offset; i < end; i++) {
            tokens[i - offset] = allTokens[i];
        }
    }

    function searchTokens(
        string calldata query,
        uint256 limit
    ) external view returns (address[] memory results) {
        address[] memory matches = new address[](limit);
        uint256 matchCount = 0;

        for (uint256 i = 0; i < allTokens.length && matchCount < limit; i++) {
            address token = allTokens[i];
            TokenInfo memory info = tokenInfo[token];

            if (_stringContains(info.name, query) || _stringContains(info.symbol, query)) {
                matches[matchCount] = token;
                matchCount++;
            }
        }

        results = new address[](matchCount);
        for (uint256 i = 0; i < matchCount; i++) {
            results[i] = matches[i];
        }
    }

    function _stringContains(string memory str, string memory substr) internal pure returns (bool) {
        bytes memory strBytes = bytes(str);
        bytes memory substrBytes = bytes(substr);

        if (substrBytes.length > strBytes.length) return false;
        if (substrBytes.length == 0) return true;

        for (uint256 i = 0; i <= strBytes.length - substrBytes.length; i++) {
            bool found = true;
            for (uint256 j = 0; j < substrBytes.length; j++) {
                if (strBytes[i + j] != substrBytes[j]) {
                    found = false;
                    break;
                }
            }
            if (found) return true;
        }

        return false;
    }

    function updateRegistrySettings(
        RegistrySettings calldata settings
    ) external onlyRole(ADMIN_ROLE) {
        registrySettings = settings;
        emit RegistrySettingsUpdated(settings);
    }

    function withdrawFees(address to) external onlyRole(TREASURY_ROLE) {
        uint256 balance = address(this).balance;
        if (balance > 0) {
            payable(to).transfer(balance);
        }
    }

    function _authorizeUpgrade(address newImplementation)
        internal
        override(UUPSUpgradeable, LaxceAccessControlUpgradeable)
        onlyRole(UPGRADER_ROLE)
    {}
}
"
    },
    "CoreRegistry.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "./AccessControlUpgradeable.sol";

contract CoreRegistry is Initializable, UUPSUpgradeable, LaxceAccessControlUpgradeable {

    bytes32 public constant DEX_ENGINE = keccak256("DEX_ENGINE");
    bytes32 public constant POOL_FACTORY = keccak256("POOL

Tags:
ERC20, ERC165, Proxy, Mintable, Burnable, Pausable, Swap, Liquidity, Staking, Voting, Upgradeable, Factory, Oracle|addr:0x02827b70a2023f31e765315eab1a5710320e3138|verified:true|block:23527959|tx:0x147e2c7a73287c162062ee7db1b5742dc841c66bdc4647b423ddef17e3fcde08|first_check:1759868859

Submitted on: 2025-10-07 22:27:39

Comments

Log in to comment.

No comments yet.