Files
aitbc/contracts/contracts/AIServiceAMM.sol
AITBC System b033923756 chore: normalize file permissions across repository
- Remove executable permissions from configuration files (.editorconfig, .env.example, .gitignore)
- Remove executable permissions from documentation files (README.md, LICENSE, SECURITY.md)
- Remove executable permissions from web assets (HTML, CSS, JS files)
- Remove executable permissions from data files (JSON, SQL, YAML, requirements.txt)
- Remove executable permissions from source code files across all apps
- Add executable permissions to Python
2026-03-08 11:26:18 +01:00

493 lines
17 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
/**
* @title AIServiceAMM
* @dev Automated Market Making protocol for AI service tokens
* @notice Enables creation of liquidity pools and automated trading for AI services
*/
contract AIServiceAMM is Ownable, ReentrancyGuard, Pausable {
using SafeERC20 for IERC20;
using Math for uint256;
// Constants
uint256 public constant MINIMUM_LIQUIDITY = 1000; // Minimum liquidity to prevent rounding errors
uint256 public constant BASIS_POINTS = 10000; // 100% in basis points
uint256 public constant MAX_FEE = 1000; // Maximum 10% fee
uint256 public constant FEE_PRECISION = 1000000; // Fee calculation precision
// State variables
uint256 public poolCounter;
uint256 public defaultFee = 30; // 0.3% default fee
uint256 public protocolFeePercentage = 20; // 20% of fees go to protocol
address public protocolFeeRecipient;
// Structs
struct LiquidityPool {
uint256 poolId;
address tokenA;
address tokenB;
uint256 reserveA;
uint256 reserveB;
uint256 totalLiquidity;
uint256 feePercentage; // Pool-specific fee in basis points
address lpToken; // LP token address for this pool
bool isActive;
uint256 created_at;
uint256 lastTradeTime;
uint256 volume24h;
uint256 fee24h;
}
struct LiquidityPosition {
uint256 poolId;
address provider;
uint256 liquidityAmount;
uint256 sharesOwned;
uint256 lastDepositTime;
uint256 unclaimedFees;
}
struct SwapParams {
uint256 poolId;
address tokenIn;
address tokenOut;
uint256 amountIn;
uint256 minAmountOut;
address recipient;
uint256 deadline;
}
struct PoolMetrics {
uint256 totalVolume;
uint256 totalFees;
uint256 tvl; // Total Value Locked
uint256 apr; // Annual Percentage Rate for liquidity providers
uint256 utilizationRate;
}
// Mappings
mapping(uint256 => LiquidityPool) public pools;
mapping(address => mapping(uint256 => LiquidityPosition)) public liquidityPositions;
mapping(address => uint256[]) public providerPools;
mapping(address => mapping(address => uint256)) public poolByTokenPair; // tokenA -> tokenB -> poolId
// Arrays
uint256[] public activePoolIds;
// Events
event PoolCreated(uint256 indexed poolId, address indexed tokenA, address indexed tokenB, uint256 fee);
event LiquidityAdded(uint256 indexed poolId, address indexed provider, uint256 amountA, uint256 amountB, uint256 liquidity);
event LiquidityRemoved(uint256 indexed poolId, address indexed provider, uint256 amountA, uint256 amountB, uint256 liquidity);
event SwapExecuted(uint256 indexed poolId, address indexed recipient, address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOut);
event FeesCollected(uint256 indexed poolId, uint256 protocolFees, uint256 lpFees);
event PoolUpdated(uint256 indexed poolId, uint256 reserveA, uint256 reserveB);
// Modifiers
modifier validPool(uint256 poolId) {
require(poolId > 0 && poolId <= poolCounter, "Invalid pool ID");
require(pools[poolId].isActive, "Pool not active");
_;
}
modifier validDeadline(uint256 deadline) {
require(block.timestamp <= deadline, "Transaction expired");
_;
}
modifier nonZeroAmount(uint256 amount) {
require(amount > 0, "Amount must be greater than 0");
_;
}
constructor(address _protocolFeeRecipient) {
protocolFeeRecipient = _protocolFeeRecipient;
}
/**
* @dev Creates a new liquidity pool for two tokens
* @param tokenA Address of the first token
* @param tokenB Address of the second token
* @param feePercentage Fee percentage in basis points
* @return poolId The ID of the created pool
*/
function createPool(
address tokenA,
address tokenB,
uint256 feePercentage
) external nonReentrant whenNotPaused returns (uint256) {
require(tokenA != tokenB, "Identical tokens");
require(tokenA != address(0) && tokenB != address(0), "Zero address");
require(feePercentage <= MAX_FEE, "Fee too high");
require(poolByTokenPair[tokenA][tokenB] == 0 && poolByTokenPair[tokenB][tokenA] == 0, "Pool exists");
// Ensure tokenA < tokenB for consistency
if (tokenA > tokenB) {
(tokenA, tokenB) = (tokenB, tokenA);
}
poolCounter++;
uint256 poolId = poolCounter;
pools[poolId] = LiquidityPool({
poolId: poolId,
tokenA: tokenA,
tokenB: tokenB,
reserveA: 0,
reserveB: 0,
totalLiquidity: 0,
feePercentage: feePercentage,
lpToken: address(this), // Simplified LP token representation
isActive: true,
created_at: block.timestamp,
lastTradeTime: 0,
volume24h: 0,
fee24h: 0
});
poolByTokenPair[tokenA][tokenB] = poolId;
poolByTokenPair[tokenB][tokenA] = poolId;
activePoolIds.push(poolId);
emit PoolCreated(poolId, tokenA, tokenB, feePercentage);
return poolId;
}
/**
* @dev Adds liquidity to a pool
* @param poolId The pool ID
* @param amountA Amount of tokenA to add
* @param amountB Amount of tokenB to add
* @param minAmountA Minimum amount of tokenA (slippage protection)
* @param minAmountB Minimum amount of tokenB (slippage protection)
* @return liquidityAmount The amount of liquidity tokens received
*/
function addLiquidity(
uint256 poolId,
uint256 amountA,
uint256 amountB,
uint256 minAmountA,
uint256 minAmountB
)
external
nonReentrant
whenNotPaused
validPool(poolId)
nonZeroAmount(amountA)
nonZeroAmount(amountB)
returns (uint256 liquidityAmount)
{
LiquidityPool storage pool = pools[poolId];
// Calculate optimal amounts based on current reserves
uint256 optimalAmountB = _calculateOptimalAmountB(poolId, amountA);
if (pool.reserveA == 0 && pool.reserveB == 0) {
// First liquidity provider - set initial prices
optimalAmountB = amountB;
} else {
require(amountB >= optimalAmountB, "Insufficient tokenB amount");
}
// Transfer tokens to contract
IERC20(pool.tokenA).safeTransferFrom(msg.sender, address(this), amountA);
IERC20(pool.tokenB).safeTransferFrom(msg.sender, address(this), amountB);
// Calculate liquidity to mint
if (pool.totalLiquidity == 0) {
liquidityAmount = Math.sqrt(amountA * amountB) - MINIMUM_LIQUIDITY;
pool.totalLiquidity += MINIMUM_LIQUIDITY; // Lock minimum liquidity
} else {
liquidityAmount = Math.min(
(amountA * pool.totalLiquidity) / pool.reserveA,
(amountB * pool.totalLiquidity) / pool.reserveB
);
}
require(liquidityAmount > 0, "Insufficient liquidity minted");
// Update pool reserves and liquidity
pool.reserveA += amountA;
pool.reserveB += amountB;
pool.totalLiquidity += liquidityAmount;
// Update or create liquidity position
LiquidityPosition storage position = liquidityPositions[msg.sender][poolId];
position.poolId = poolId;
position.provider = msg.sender;
position.liquidityAmount += liquidityAmount;
position.sharesOwned = (position.liquidityAmount * BASIS_POINTS) / pool.totalLiquidity;
position.lastDepositTime = block.timestamp;
// Add to provider's pool list if new
if (position.liquidityAmount == liquidityAmount) {
providerPools[msg.sender].push(poolId);
}
emit LiquidityAdded(poolId, msg.sender, amountA, amountB, liquidityAmount);
emit PoolUpdated(poolId, pool.reserveA, pool.reserveB);
}
/**
* @dev Removes liquidity from a pool
* @param poolId The pool ID
* @param liquidityAmount Amount of liquidity to remove
* @param minAmountA Minimum amount of tokenA to receive
* @param minAmountB Minimum amount of tokenB to receive
* @return amountA Amount of tokenA received
* @return amountB Amount of tokenB received
*/
function removeLiquidity(
uint256 poolId,
uint256 liquidityAmount,
uint256 minAmountA,
uint256 minAmountB
)
external
nonReentrant
whenNotPaused
validPool(poolId)
nonZeroAmount(liquidityAmount)
returns (uint256 amountA, uint256 amountB)
{
LiquidityPool storage pool = pools[poolId];
LiquidityPosition storage position = liquidityPositions[msg.sender][poolId];
require(position.liquidityAmount >= liquidityAmount, "Insufficient liquidity");
// Calculate amounts to receive
amountA = (liquidityAmount * pool.reserveA) / pool.totalLiquidity;
amountB = (liquidityAmount * pool.reserveB) / pool.totalLiquidity;
require(amountA >= minAmountA && amountB >= minAmountB, "Slippage protection");
// Update pool reserves and liquidity
pool.reserveA -= amountA;
pool.reserveB -= amountB;
pool.totalLiquidity -= liquidityAmount;
// Update position
position.liquidityAmount -= liquidityAmount;
position.sharesOwned = (position.liquidityAmount * BASIS_POINTS) / pool.totalLiquidity;
// Transfer tokens to user
IERC20(pool.tokenA).safeTransfer(msg.sender, amountA);
IERC20(pool.tokenB).safeTransfer(msg.sender, amountB);
emit LiquidityRemoved(poolId, msg.sender, amountA, amountB, liquidityAmount);
emit PoolUpdated(poolId, pool.reserveA, pool.reserveB);
}
/**
* @dev Executes a token swap
* @param params Swap parameters
* @return amountOut Amount of tokens received
*/
function swap(SwapParams calldata params)
external
nonReentrant
whenNotPaused
validPool(params.poolId)
validDeadline(params.deadline)
nonZeroAmount(params.amountIn)
returns (uint256 amountOut)
{
LiquidityPool storage pool = pools[params.poolId];
// Validate tokens
require(
params.tokenIn == pool.tokenA || params.tokenIn == pool.tokenB,
"Invalid input token"
);
require(
params.tokenOut == pool.tokenA || params.tokenOut == pool.tokenB,
"Invalid output token"
);
require(params.tokenIn != params.tokenOut, "Same token swap");
// Calculate output amount
amountOut = _calculateSwapOutput(params.poolId, params.amountIn, params.tokenIn);
require(amountOut >= params.minAmountOut, "Insufficient output amount");
// Transfer input tokens
IERC20(params.tokenIn).safeTransferFrom(msg.sender, address(this), params.amountIn);
// Update reserves
if (params.tokenIn == pool.tokenA) {
pool.reserveA += params.amountIn;
pool.reserveB -= amountOut;
} else {
pool.reserveB += params.amountIn;
pool.reserveA -= amountOut;
}
// Transfer output tokens
IERC20(params.tokenOut).safeTransfer(params.recipient, amountOut);
// Update pool metrics
pool.lastTradeTime = block.timestamp;
pool.volume24h += params.amountIn;
emit SwapExecuted(params.poolId, params.recipient, params.tokenIn, params.tokenOut, params.amountIn, amountOut);
emit PoolUpdated(params.poolId, pool.reserveA, pool.reserveB);
}
/**
* @dev Calculates the optimal amount of tokenB for adding liquidity
* @param poolId The pool ID
* @param amountA Amount of tokenA
* @return optimalAmountB Optimal amount of tokenB
*/
function calculateOptimalSwap(uint256 poolId, uint256 amountIn)
external
view
validPool(poolId)
returns (uint256 amountOut)
{
return _calculateSwapOutput(poolId, amountIn, pools[poolId].tokenA);
}
/**
* @dev Gets pool metrics
* @param poolId The pool ID
* @return metrics Pool metrics
*/
function getPoolMetrics(uint256 poolId)
external
view
validPool(poolId)
returns (PoolMetrics memory metrics)
{
LiquidityPool storage pool = pools[poolId];
uint256 totalValue = pool.reserveA + pool.reserveB; // Simplified TVL calculation
uint256 annualFees = pool.fee24h * 365; // Simplified APR calculation
metrics = PoolMetrics({
totalVolume: pool.volume24h,
totalFees: pool.fee24h,
tvl: totalValue,
apr: totalValue > 0 ? (annualFees * BASIS_POINTS) / totalValue : 0,
utilizationRate: pool.totalLiquidity > 0 ? ((pool.volume24h * BASIS_POINTS) / pool.totalLiquidity) : 0
});
}
/**
* @dev Gets the amount of liquidity tokens a user owns in a pool
* @param provider The liquidity provider address
* @param poolId The pool ID
* @return liquidityAmount Amount of liquidity tokens
*/
function getLiquidityAmount(address provider, uint256 poolId)
external
view
returns (uint256 liquidityAmount)
{
return liquidityPositions[provider][poolId].liquidityAmount;
}
// Internal functions
function _calculateOptimalAmountB(uint256 poolId, uint256 amountA) internal view returns (uint256) {
LiquidityPool storage pool = pools[poolId];
if (pool.reserveA == 0) return 0;
return (amountA * pool.reserveB) / pool.reserveA;
}
function _calculateSwapOutput(uint256 poolId, uint256 amountIn, address tokenIn)
internal
view
returns (uint256 amountOut)
{
LiquidityPool storage pool = pools[poolId];
uint256 reserveIn;
uint256 reserveOut;
if (tokenIn == pool.tokenA) {
reserveIn = pool.reserveA;
reserveOut = pool.reserveB;
} else {
reserveIn = pool.reserveB;
reserveOut = pool.reserveA;
}
// Apply fee
uint256 feeAmount = (amountIn * pool.feePercentage) / BASIS_POINTS;
uint256 amountInAfterFee = amountIn - feeAmount;
// Calculate output using constant product formula
amountOut = (amountInAfterFee * reserveOut) / (reserveIn + amountInAfterFee);
// Ensure minimum output
require(amountOut > 0, "Insufficient output amount");
require(reserveOut > amountOut, "Insufficient liquidity");
}
function _collectFees(uint256 poolId) internal {
LiquidityPool storage pool = pools[poolId];
if (pool.fee24h == 0) return;
uint256 protocolFees = (pool.fee24h * protocolFeePercentage) / BASIS_POINTS;
uint256 lpFees = pool.fee24h - protocolFees;
// Distribute fees to liquidity providers
if (lpFees > 0 && pool.totalLiquidity > 0) {
for (uint i = 0; i < activePoolIds.length; i++) {
uint256 currentPoolId = activePoolIds[i];
if (currentPoolId == poolId) {
// Simplified fee distribution
// In production, this would be more sophisticated
break;
}
}
}
emit FeesCollected(poolId, protocolFees, lpFees);
pool.fee24h = 0;
}
// Admin functions
function setProtocolFeeRecipient(address newRecipient) external onlyOwner {
require(newRecipient != address(0), "Invalid address");
protocolFeeRecipient = newRecipient;
}
function setProtocolFeePercentage(uint256 newPercentage) external onlyOwner {
require(newPercentage <= BASIS_POINTS, "Invalid percentage");
protocolFeePercentage = newPercentage;
}
function setDefaultFee(uint256 newFee) external onlyOwner {
require(newFee <= MAX_FEE, "Fee too high");
defaultFee = newFee;
}
function pause() external onlyOwner {
_pause();
}
function unpause() external onlyOwner {
_unpause();
}
// Emergency functions
function emergencyWithdraw(address token, uint256 amount) external onlyOwner {
IERC20(token).safeTransfer(owner(), amount);
}
function emergencyPause() external onlyOwner {
_pause();
}
}