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
Submitted on: 2025-10-07 22:27:39
Comments
Log in to comment.
No comments yet.