feat(coordinator-api): integrate dynamic pricing engine with GPU marketplace and add agent identity router
- Add DynamicPricingEngine and MarketDataCollector dependencies to GPU marketplace endpoints
- Implement dynamic pricing calculation for GPU registration with market_balance strategy
- Calculate real-time dynamic prices at booking time with confidence scores and pricing factors
- Enhance /marketplace/pricing/{model} endpoint with comprehensive dynamic pricing analysis
- Add static vs dynamic price
This commit is contained in:
492
contracts/contracts/AIServiceAMM.sol
Normal file
492
contracts/contracts/AIServiceAMM.sol
Normal file
@@ -0,0 +1,492 @@
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
562
contracts/contracts/AgentPortfolioManager.sol
Normal file
562
contracts/contracts/AgentPortfolioManager.sol
Normal file
@@ -0,0 +1,562 @@
|
||||
// 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 AgentPortfolioManager
|
||||
* @dev Advanced portfolio management protocol for autonomous AI agents
|
||||
* @notice Enables agents to manage portfolios, execute trades, and automate rebalancing
|
||||
*/
|
||||
contract AgentPortfolioManager is Ownable, ReentrancyGuard, Pausable {
|
||||
using SafeERC20 for IERC20;
|
||||
using Math for uint256;
|
||||
|
||||
// State variables
|
||||
IERC20 public aitbcToken;
|
||||
uint256 public portfolioCounter;
|
||||
uint256 public strategyCounter;
|
||||
uint256 public rebalanceThreshold = 500; // 5% threshold for rebalancing (in basis points)
|
||||
uint256 public maxRiskScore = 10000; // Maximum risk score (100% in basis points)
|
||||
uint256 public platformFeePercentage = 50; // 0.5% platform fee
|
||||
|
||||
// Enums
|
||||
enum StrategyType { CONSERVATIVE, BALANCED, AGGRESSIVE, DYNAMIC }
|
||||
enum TradeStatus { PENDING, EXECUTED, FAILED, CANCELLED }
|
||||
enum RiskLevel { LOW, MEDIUM, HIGH, CRITICAL }
|
||||
|
||||
// Structs
|
||||
struct Asset {
|
||||
address tokenAddress;
|
||||
string symbol;
|
||||
bool isActive;
|
||||
uint256 decimals;
|
||||
uint256 priceOracle; // Price in USD (scaled by 1e8)
|
||||
}
|
||||
|
||||
struct AgentPortfolio {
|
||||
uint256 portfolioId;
|
||||
address agentAddress;
|
||||
mapping(string => uint256) assetBalances; // Token symbol -> balance (in wei)
|
||||
mapping(string => uint256) targetAllocations; // Token symbol -> target allocation (in basis points)
|
||||
uint256 totalValue; // Total portfolio value in USD (scaled by 1e8)
|
||||
uint256 riskScore; // Risk score (0-10000 basis points)
|
||||
uint256 lastRebalance;
|
||||
StrategyType strategy;
|
||||
bool isActive;
|
||||
uint256 created_at;
|
||||
}
|
||||
|
||||
struct TradingStrategy {
|
||||
uint256 strategyId;
|
||||
string name;
|
||||
StrategyType strategyType;
|
||||
mapping(string => uint256) targetAllocations; // Token symbol -> target allocation
|
||||
uint256 maxDrawdown;
|
||||
uint256 rebalanceFrequency;
|
||||
bool isActive;
|
||||
}
|
||||
|
||||
struct Trade {
|
||||
uint256 tradeId;
|
||||
uint256 portfolioId;
|
||||
string sellToken;
|
||||
string buyToken;
|
||||
uint256 sellAmount;
|
||||
uint256 buyAmount;
|
||||
uint256 price;
|
||||
TradeStatus status;
|
||||
uint256 timestamp;
|
||||
bytes32 executionHash;
|
||||
}
|
||||
|
||||
struct RiskMetrics {
|
||||
uint256 volatility;
|
||||
uint256 maxDrawdown;
|
||||
uint256 sharpeRatio;
|
||||
uint256 beta;
|
||||
uint256 alpha;
|
||||
uint256 var95; // Value at Risk at 95% confidence
|
||||
RiskLevel riskLevel;
|
||||
}
|
||||
|
||||
// Mappings
|
||||
mapping(uint256 => AgentPortfolio) public portfolios;
|
||||
mapping(address => uint256) public agentPortfolio;
|
||||
mapping(string => Asset) public supportedAssets;
|
||||
mapping(uint256 => TradingStrategy) public strategies;
|
||||
mapping(uint256 => Trade) public trades;
|
||||
mapping(uint256 => uint256[]) public portfolioTrades;
|
||||
mapping(uint256 => RiskMetrics) public portfolioRiskMetrics;
|
||||
|
||||
// Arrays
|
||||
address[] public supportedAssetAddresses;
|
||||
string[] public supportedAssetSymbols;
|
||||
uint256[] public activePortfolioIds;
|
||||
|
||||
// Events
|
||||
event PortfolioCreated(uint256 indexed portfolioId, address indexed agent, StrategyType strategy);
|
||||
event PortfolioRebalanced(uint256 indexed portfolioId, uint256 totalValue, uint256 riskScore);
|
||||
event TradeExecuted(uint256 indexed tradeId, uint256 indexed portfolioId, string sellToken, string buyToken, uint256 amount);
|
||||
event StrategyCreated(uint256 indexed strategyId, string name, StrategyType strategyType);
|
||||
event RiskAssessment(uint256 indexed portfolioId, uint256 riskScore, RiskLevel riskLevel);
|
||||
event AssetAdded(address indexed tokenAddress, string symbol, uint256 price);
|
||||
|
||||
// Modifiers
|
||||
modifier onlyPortfolioOwner(uint256 portfolioId) {
|
||||
require(portfolios[portfolioId].agentAddress == msg.sender, "Not portfolio owner");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier validAsset(string memory symbol) {
|
||||
require(supportedAssets[symbol].isActive, "Asset not supported");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier validPortfolio(uint256 portfolioId) {
|
||||
require(portfolioId > 0 && portfolioId <= portfolioCounter, "Invalid portfolio ID");
|
||||
require(portfolios[portfolioId].isActive, "Portfolio not active");
|
||||
_;
|
||||
}
|
||||
|
||||
constructor(address _aitbcToken) {
|
||||
aitbcToken = IERC20(_aitbcToken);
|
||||
|
||||
// Initialize with basic assets
|
||||
_addAsset(address(aitbcToken), "AITBC", 18, 100000000); // $1.00 USD
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Creates a new portfolio for an agent
|
||||
* @param agentAddress The address of the agent
|
||||
* @param strategyId The strategy ID to use
|
||||
* @return portfolioId The ID of the created portfolio
|
||||
*/
|
||||
function createPortfolio(address agentAddress, uint256 strategyId)
|
||||
external
|
||||
nonReentrant
|
||||
whenNotPaused
|
||||
returns (uint256)
|
||||
{
|
||||
require(agentAddress != address(0), "Invalid agent address");
|
||||
require(strategies[strategyId].isActive, "Strategy not active");
|
||||
require(agentPortfolio[agentAddress] == 0, "Portfolio already exists");
|
||||
|
||||
portfolioCounter++;
|
||||
uint256 portfolioId = portfolioCounter;
|
||||
|
||||
AgentPortfolio storage portfolio = portfolios[portfolioId];
|
||||
portfolio.portfolioId = portfolioId;
|
||||
portfolio.agentAddress = agentAddress;
|
||||
portfolio.strategy = strategies[strategyId].strategyType;
|
||||
portfolio.lastRebalance = block.timestamp;
|
||||
portfolio.isActive = true;
|
||||
portfolio.created_at = block.timestamp;
|
||||
|
||||
// Copy target allocations from strategy
|
||||
TradingStrategy storage strategy = strategies[strategyId];
|
||||
for (uint i = 0; i < supportedAssetSymbols.length; i++) {
|
||||
string memory symbol = supportedAssetSymbols[i];
|
||||
portfolio.targetAllocations[symbol] = strategy.targetAllocations[symbol];
|
||||
}
|
||||
|
||||
agentPortfolio[agentAddress] = portfolioId;
|
||||
activePortfolioIds.push(portfolioId);
|
||||
|
||||
emit PortfolioCreated(portfolioId, agentAddress, portfolio.strategy);
|
||||
return portfolioId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Executes a trade within a portfolio
|
||||
* @param portfolioId The portfolio ID
|
||||
* @param sellToken The token symbol to sell
|
||||
* @param buyToken The token symbol to buy
|
||||
* @param sellAmount The amount to sell (in wei)
|
||||
* @param minBuyAmount The minimum amount to buy (slippage protection)
|
||||
* @return tradeId The ID of the executed trade
|
||||
*/
|
||||
function executeTrade(
|
||||
uint256 portfolioId,
|
||||
string memory sellToken,
|
||||
string memory buyToken,
|
||||
uint256 sellAmount,
|
||||
uint256 minBuyAmount
|
||||
)
|
||||
external
|
||||
nonReentrant
|
||||
whenNotPaused
|
||||
validPortfolio(portfolioId)
|
||||
onlyPortfolioOwner(portfolioId)
|
||||
validAsset(sellToken)
|
||||
validAsset(buyToken)
|
||||
returns (uint256)
|
||||
{
|
||||
require(sellAmount > 0, "Invalid sell amount");
|
||||
require(portfolios[portfolioId].assetBalances[sellToken] >= sellAmount, "Insufficient balance");
|
||||
|
||||
// Calculate buy amount based on current prices
|
||||
uint256 sellPrice = supportedAssets[sellToken].priceOracle;
|
||||
uint256 buyPrice = supportedAssets[buyToken].priceOracle;
|
||||
uint256 sellValue = (sellAmount * sellPrice) / (10 ** supportedAssets[sellToken].decimals);
|
||||
uint256 buyAmount = (sellValue * (10 ** supportedAssets[buyToken].decimals)) / buyPrice;
|
||||
|
||||
require(buyAmount >= minBuyAmount, "Insufficient buy amount (slippage)");
|
||||
|
||||
// Update portfolio balances
|
||||
portfolios[portfolioId].assetBalances[sellToken] -= sellAmount;
|
||||
portfolios[portfolioId].assetBalances[buyToken] += buyAmount;
|
||||
|
||||
// Create trade record
|
||||
uint256 tradeId = _createTradeRecord(portfolioId, sellToken, buyToken, sellAmount, buyAmount, buyPrice);
|
||||
|
||||
// Update portfolio value and risk
|
||||
_updatePortfolioValue(portfolioId);
|
||||
_calculateRiskScore(portfolioId);
|
||||
|
||||
emit TradeExecuted(tradeId, portfolioId, sellToken, buyToken, sellAmount);
|
||||
return tradeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Automatically rebalances a portfolio based on target allocations
|
||||
* @param portfolioId The portfolio ID to rebalance
|
||||
* @return success Whether the rebalancing was successful
|
||||
*/
|
||||
function rebalancePortfolio(uint256 portfolioId)
|
||||
external
|
||||
nonReentrant
|
||||
whenNotPaused
|
||||
validPortfolio(portfolioId)
|
||||
returns (bool success)
|
||||
{
|
||||
AgentPortfolio storage portfolio = portfolios[portfolioId];
|
||||
|
||||
// Check if rebalancing is needed
|
||||
if (!_needsRebalancing(portfolioId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get current allocations
|
||||
mapping(string => uint256) storage currentAllocations = portfolio.assetBalances;
|
||||
uint256 totalValue = portfolio.totalValue;
|
||||
|
||||
// Calculate required trades
|
||||
for (uint i = 0; i < supportedAssetSymbols.length; i++) {
|
||||
string memory symbol = supportedAssetSymbols[i];
|
||||
uint256 targetAllocation = portfolio.targetAllocations[symbol];
|
||||
uint256 targetValue = (totalValue * targetAllocation) / 10000;
|
||||
|
||||
uint256 currentBalance = currentAllocations[symbol];
|
||||
uint256 currentValue = (currentBalance * supportedAssets[symbol].priceOracle) /
|
||||
(10 ** supportedAssets[symbol].decimals);
|
||||
|
||||
if (currentValue > targetValue) {
|
||||
// Sell excess
|
||||
uint256 excessValue = currentValue - targetValue;
|
||||
uint256 sellAmount = (excessValue * (10 ** supportedAssets[symbol].decimals)) /
|
||||
supportedAssets[symbol].priceOracle;
|
||||
|
||||
// Find underweight asset to buy
|
||||
for (uint j = 0; j < supportedAssetSymbols.length; j++) {
|
||||
string memory buySymbol = supportedAssetSymbols[j];
|
||||
uint256 buyTargetValue = (totalValue * portfolio.targetAllocations[buySymbol]) / 10000;
|
||||
uint256 buyCurrentValue = (currentAllocations[buySymbol] * supportedAssets[buySymbol].priceOracle) /
|
||||
(10 ** supportedAssets[buySymbol].decimals);
|
||||
|
||||
if (buyCurrentValue < buyTargetValue) {
|
||||
// Execute rebalancing trade
|
||||
_executeRebalancingTrade(portfolioId, symbol, buySymbol, sellAmount);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
portfolio.lastRebalance = block.timestamp;
|
||||
_calculateRiskScore(portfolioId);
|
||||
|
||||
emit PortfolioRebalanced(portfolioId, portfolio.totalValue, portfolio.riskScore);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Creates a new trading strategy
|
||||
* @param name The strategy name
|
||||
* @param strategyType The strategy type
|
||||
* @param allocations Target allocations for each supported asset
|
||||
* @param maxDrawdown Maximum allowed drawdown
|
||||
* @param rebalanceFrequency Rebalancing frequency in seconds
|
||||
* @return strategyId The ID of the created strategy
|
||||
*/
|
||||
function createStrategy(
|
||||
string memory name,
|
||||
StrategyType strategyType,
|
||||
mapping(string => uint256) storage allocations,
|
||||
uint256 maxDrawdown,
|
||||
uint256 rebalanceFrequency
|
||||
)
|
||||
external
|
||||
onlyOwner
|
||||
returns (uint256)
|
||||
{
|
||||
strategyCounter++;
|
||||
uint256 strategyId = strategyCounter;
|
||||
|
||||
TradingStrategy storage strategy = strategies[strategyId];
|
||||
strategy.strategyId = strategyId;
|
||||
strategy.name = name;
|
||||
strategy.strategyType = strategyType;
|
||||
strategy.maxDrawdown = maxDrawdown;
|
||||
strategy.rebalanceFrequency = rebalanceFrequency;
|
||||
strategy.isActive = true;
|
||||
|
||||
// Copy allocations
|
||||
uint256 totalAllocation = 0;
|
||||
for (uint i = 0; i < supportedAssetSymbols.length; i++) {
|
||||
string memory symbol = supportedAssetSymbols[i];
|
||||
strategy.targetAllocations[symbol] = allocations[symbol];
|
||||
totalAllocation += allocations[symbol];
|
||||
}
|
||||
|
||||
require(totalAllocation == 10000, "Allocations must sum to 100%");
|
||||
|
||||
emit StrategyCreated(strategyId, name, strategyType);
|
||||
return strategyId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Calculates the risk score for a portfolio
|
||||
* @param portfolioId The portfolio ID
|
||||
* @return riskScore The calculated risk score (0-10000)
|
||||
*/
|
||||
function calculateRiskScore(uint256 portfolioId)
|
||||
external
|
||||
view
|
||||
returns (uint256 riskScore)
|
||||
{
|
||||
if (!portfolios[portfolioId].isActive) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
AgentPortfolio storage portfolio = portfolios[portfolioId];
|
||||
uint256 totalRisk = 0;
|
||||
|
||||
// Calculate risk based on asset volatility and allocation
|
||||
for (uint i = 0; i < supportedAssetSymbols.length; i++) {
|
||||
string memory symbol = supportedAssetSymbols[i];
|
||||
uint256 balance = portfolio.assetBalances[symbol];
|
||||
if (balance > 0) {
|
||||
uint256 value = (balance * supportedAssets[symbol].priceOracle) /
|
||||
(10 ** supportedAssets[symbol].decimals);
|
||||
uint256 allocation = (value * 10000) / portfolio.totalValue;
|
||||
|
||||
// Risk contribution based on asset type and allocation
|
||||
uint256 assetRisk = _getAssetRisk(symbol);
|
||||
totalRisk += (allocation * assetRisk) / 10000;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust for strategy type
|
||||
uint256 strategyMultiplier = _getStrategyRiskMultiplier(portfolio.strategy);
|
||||
riskScore = (totalRisk * strategyMultiplier) / 10000;
|
||||
|
||||
return Math.min(riskScore, maxRiskScore);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets the current portfolio value in USD
|
||||
* @param portfolioId The portfolio ID
|
||||
* @return totalValue The total portfolio value (scaled by 1e8)
|
||||
*/
|
||||
function getPortfolioValue(uint256 portfolioId)
|
||||
external
|
||||
view
|
||||
returns (uint256 totalValue)
|
||||
{
|
||||
if (!portfolios[portfolioId].isActive) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
AgentPortfolio storage portfolio = portfolios[portfolioId];
|
||||
totalValue = 0;
|
||||
|
||||
for (uint i = 0; i < supportedAssetSymbols.length; i++) {
|
||||
string memory symbol = supportedAssetSymbols[i];
|
||||
uint256 balance = portfolio.assetBalances[symbol];
|
||||
if (balance > 0) {
|
||||
uint256 value = (balance * supportedAssets[symbol].priceOracle) /
|
||||
(10 ** supportedAssets[symbol].decimals);
|
||||
totalValue += value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Internal functions
|
||||
|
||||
function _addAsset(address tokenAddress, string memory symbol, uint256 decimals, uint256 price) internal {
|
||||
supportedAssets[symbol] = Asset({
|
||||
tokenAddress: tokenAddress,
|
||||
symbol: symbol,
|
||||
isActive: true,
|
||||
decimals: decimals,
|
||||
priceOracle: price
|
||||
});
|
||||
|
||||
supportedAssetAddresses.push(tokenAddress);
|
||||
supportedAssetSymbols.push(symbol);
|
||||
|
||||
emit AssetAdded(tokenAddress, symbol, price);
|
||||
}
|
||||
|
||||
function _createTradeRecord(
|
||||
uint256 portfolioId,
|
||||
string memory sellToken,
|
||||
string memory buyToken,
|
||||
uint256 sellAmount,
|
||||
uint256 buyAmount,
|
||||
uint256 price
|
||||
) internal returns (uint256) {
|
||||
uint256 tradeId = portfolioTrades[portfolioId].length + 1;
|
||||
|
||||
trades[tradeId] = Trade({
|
||||
tradeId: tradeId,
|
||||
portfolioId: portfolioId,
|
||||
sellToken: sellToken,
|
||||
buyToken: buyToken,
|
||||
sellAmount: sellAmount,
|
||||
buyAmount: buyAmount,
|
||||
price: price,
|
||||
status: TradeStatus.EXECUTED,
|
||||
timestamp: block.timestamp,
|
||||
executionHash: keccak256(abi.encodePacked(portfolioId, sellToken, buyToken, sellAmount, block.timestamp))
|
||||
});
|
||||
|
||||
portfolioTrades[portfolioId].push(tradeId);
|
||||
return tradeId;
|
||||
}
|
||||
|
||||
function _updatePortfolioValue(uint256 portfolioId) internal {
|
||||
uint256 totalValue = 0;
|
||||
AgentPortfolio storage portfolio = portfolios[portfolioId];
|
||||
|
||||
for (uint i = 0; i < supportedAssetSymbols.length; i++) {
|
||||
string memory symbol = supportedAssetSymbols[i];
|
||||
uint256 balance = portfolio.assetBalances[symbol];
|
||||
if (balance > 0) {
|
||||
uint256 value = (balance * supportedAssets[symbol].priceOracle) /
|
||||
(10 ** supportedAssets[symbol].decimals);
|
||||
totalValue += value;
|
||||
}
|
||||
}
|
||||
|
||||
portfolio.totalValue = totalValue;
|
||||
}
|
||||
|
||||
function _calculateRiskScore(uint256 portfolioId) internal {
|
||||
uint256 riskScore = this.calculateRiskScore(portfolioId);
|
||||
portfolios[portfolioId].riskScore = riskScore;
|
||||
|
||||
// Determine risk level
|
||||
RiskLevel riskLevel;
|
||||
if (riskScore < 2500) riskLevel = RiskLevel.LOW;
|
||||
else if (riskScore < 5000) riskLevel = RiskLevel.MEDIUM;
|
||||
else if (riskScore < 7500) riskLevel = RiskLevel.HIGH;
|
||||
else riskLevel = RiskLevel.CRITICAL;
|
||||
|
||||
portfolioRiskMetrics[portfolioId].riskLevel = riskLevel;
|
||||
emit RiskAssessment(portfolioId, riskScore, riskLevel);
|
||||
}
|
||||
|
||||
function _needsRebalancing(uint256 portfolioId) internal view returns (bool) {
|
||||
AgentPortfolio storage portfolio = portfolios[portfolioId];
|
||||
|
||||
// Check time-based rebalancing
|
||||
if (block.timestamp - portfolio.lastRebalance > strategies[1].rebalanceFrequency) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check threshold-based rebalancing
|
||||
uint256 totalValue = portfolio.totalValue;
|
||||
for (uint i = 0; i < supportedAssetSymbols.length; i++) {
|
||||
string memory symbol = supportedAssetSymbols[i];
|
||||
uint256 targetAllocation = portfolio.targetAllocations[symbol];
|
||||
uint256 targetValue = (totalValue * targetAllocation) / 10000;
|
||||
|
||||
uint256 currentBalance = portfolio.assetBalances[symbol];
|
||||
uint256 currentValue = (currentBalance * supportedAssets[symbol].priceOracle) /
|
||||
(10 ** supportedAssets[symbol].decimals);
|
||||
|
||||
uint256 deviation = currentValue > targetValue ?
|
||||
((currentValue - targetValue) * 10000) / targetValue :
|
||||
((targetValue - currentValue) * 10000) / targetValue;
|
||||
|
||||
if (deviation > rebalanceThreshold) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function _executeRebalancingTrade(
|
||||
uint256 portfolioId,
|
||||
string memory sellToken,
|
||||
string memory buyToken,
|
||||
uint256 sellAmount
|
||||
) internal {
|
||||
// Calculate buy amount
|
||||
uint256 sellPrice = supportedAssets[sellToken].priceOracle;
|
||||
uint256 buyPrice = supportedAssets[buyToken].priceOracle;
|
||||
uint256 sellValue = (sellAmount * sellPrice) / (10 ** supportedAssets[sellToken].decimals);
|
||||
uint256 buyAmount = (sellValue * (10 ** supportedAssets[buyToken].decimals)) / buyPrice;
|
||||
|
||||
// Update balances
|
||||
portfolios[portfolioId].assetBalances[sellToken] -= sellAmount;
|
||||
portfolios[portfolioId].assetBalances[buyToken] += buyAmount;
|
||||
|
||||
// Create trade record
|
||||
_createTradeRecord(portfolioId, sellToken, buyToken, sellAmount, buyAmount, buyPrice);
|
||||
}
|
||||
|
||||
function _getAssetRisk(string memory symbol) internal pure returns (uint256) {
|
||||
// Return risk score for different asset types (in basis points)
|
||||
if (keccak256(bytes(symbol)) == keccak256(bytes("AITBC"))) return 3000; // Medium risk
|
||||
if (keccak256(bytes(symbol)) == keccak256(bytes("USDC"))) return 500; // Low risk
|
||||
if (keccak256(bytes(symbol)) == keccak256(bytes("ETH"))) return 6000; // High risk
|
||||
return 4000; // Default medium risk
|
||||
}
|
||||
|
||||
function _getStrategyRiskMultiplier(StrategyType strategyType) internal pure returns (uint256) {
|
||||
if (strategyType == StrategyType.CONSERVATIVE) return 5000; // 0.5x
|
||||
if (strategyType == StrategyType.BALANCED) return 10000; // 1.0x
|
||||
if (strategyType == StrategyType.AGGRESSIVE) return 15000; // 1.5x
|
||||
if (strategyType == StrategyType.DYNAMIC) return 12000; // 1.2x
|
||||
return 10000; // Default 1.0x
|
||||
}
|
||||
|
||||
// Admin functions
|
||||
|
||||
function updateAssetPrice(string memory symbol, uint256 newPrice) external onlyOwner {
|
||||
require(supportedAssets[symbol].isActive, "Asset not supported");
|
||||
supportedAssets[symbol].priceOracle = newPrice;
|
||||
}
|
||||
|
||||
function setRebalanceThreshold(uint256 newThreshold) external onlyOwner {
|
||||
require(newThreshold <= 10000, "Invalid threshold");
|
||||
rebalanceThreshold = newThreshold;
|
||||
}
|
||||
|
||||
function pause() external onlyOwner {
|
||||
_pause();
|
||||
}
|
||||
|
||||
function unpause() external onlyOwner {
|
||||
_unpause();
|
||||
}
|
||||
}
|
||||
565
contracts/contracts/CrossChainBridge.sol
Normal file
565
contracts/contracts/CrossChainBridge.sol
Normal file
@@ -0,0 +1,565 @@
|
||||
// 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/cryptography/ECDSA.sol";
|
||||
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
|
||||
|
||||
/**
|
||||
* @title CrossChainBridge
|
||||
* @dev Secure cross-chain asset transfer protocol with ZK proof validation
|
||||
* @notice Enables bridging of assets between different blockchain networks
|
||||
*/
|
||||
contract CrossChainBridge is Ownable, ReentrancyGuard, Pausable {
|
||||
using SafeERC20 for IERC20;
|
||||
using ECDSA for bytes32;
|
||||
|
||||
// Constants
|
||||
uint256 public constant BRIDGE_FEE_PERCENTAGE = 50; // 0.5% bridge fee
|
||||
uint256 public constant BASIS_POINTS = 10000;
|
||||
uint256 public constant MAX_FEE = 500; // Maximum 5% fee
|
||||
uint256 public constant MIN_CONFIRMATIONS = 3;
|
||||
uint256 public constant BRIDGE_TIMEOUT = 24 hours;
|
||||
uint256 public constant MAX_BRIDGE_AMOUNT = 1000000 * 1e18; // Max 1M tokens per bridge
|
||||
|
||||
// Enums
|
||||
enum BridgeStatus { PENDING, CONFIRMED, COMPLETED, FAILED, CANCELLED }
|
||||
enum ChainType { ETHEREUM, POLYGON, BSC, ARBITRUM, OPTIMISM, AVALANCHE }
|
||||
|
||||
// Structs
|
||||
struct BridgeRequest {
|
||||
uint256 requestId;
|
||||
address sourceToken;
|
||||
address targetToken;
|
||||
uint256 amount;
|
||||
uint256 sourceChainId;
|
||||
uint256 targetChainId;
|
||||
address recipient;
|
||||
address sender;
|
||||
uint256 fee;
|
||||
bytes32 lockTxHash;
|
||||
bytes32 unlockTxHash;
|
||||
BridgeStatus status;
|
||||
uint256 createdAt;
|
||||
uint256 confirmedAt;
|
||||
uint256 completedAt;
|
||||
uint256 confirmations;
|
||||
mapping(address => bool) hasConfirmed;
|
||||
}
|
||||
|
||||
struct SupportedToken {
|
||||
address tokenAddress;
|
||||
bool isActive;
|
||||
uint256 bridgeLimit;
|
||||
uint256 feePercentage;
|
||||
bool requiresWhitelist;
|
||||
}
|
||||
|
||||
struct ChainConfig {
|
||||
uint256 chainId;
|
||||
ChainType chainType;
|
||||
string name;
|
||||
bool isActive;
|
||||
address bridgeContract;
|
||||
uint256 minConfirmations;
|
||||
uint256 avgBlockTime;
|
||||
}
|
||||
|
||||
struct Validator {
|
||||
address validatorAddress;
|
||||
bool isActive;
|
||||
uint256 weight;
|
||||
uint256 lastValidation;
|
||||
uint256 totalValidations;
|
||||
}
|
||||
|
||||
// State variables
|
||||
uint256 public requestCounter;
|
||||
uint256 public totalBridgedAmount;
|
||||
uint256 public totalFeesCollected;
|
||||
address public feeRecipient;
|
||||
bytes32 public merkleRoot;
|
||||
|
||||
// Mappings
|
||||
mapping(uint256 => BridgeRequest) public bridgeRequests;
|
||||
mapping(address => SupportedToken) public supportedTokens;
|
||||
mapping(uint256 => ChainConfig) public supportedChains;
|
||||
mapping(address => Validator) public validators;
|
||||
mapping(uint256 => address[]) public chainValidators;
|
||||
mapping(bytes32 => bool) public processedTxHashes;
|
||||
mapping(address => uint256[]) public userBridgeHistory;
|
||||
|
||||
// Arrays
|
||||
address[] public activeValidators;
|
||||
uint256[] public supportedChainIds;
|
||||
address[] public supportedTokenAddresses;
|
||||
|
||||
// Events
|
||||
event BridgeInitiated(
|
||||
uint256 indexed requestId,
|
||||
address indexed sender,
|
||||
address indexed recipient,
|
||||
address sourceToken,
|
||||
uint256 amount,
|
||||
uint256 sourceChainId,
|
||||
uint256 targetChainId
|
||||
);
|
||||
event BridgeConfirmed(uint256 indexed requestId, address indexed validator, bytes32 lockTxHash);
|
||||
event BridgeCompleted(uint256 indexed requestId, bytes32 unlockTxHash);
|
||||
event BridgeFailed(uint256 indexed requestId, string reason);
|
||||
event BridgeCancelled(uint256 indexed requestId);
|
||||
event ValidatorAdded(address indexed validator, uint256 weight);
|
||||
event ValidatorRemoved(address indexed validator);
|
||||
event TokenSupported(address indexed token, uint256 bridgeLimit, uint256 fee);
|
||||
event ChainSupported(uint256 indexed chainId, string name, ChainType chainType);
|
||||
|
||||
// Modifiers
|
||||
modifier onlyActiveValidator() {
|
||||
require(validators[msg.sender].isActive, "Not an active validator");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier validRequest(uint256 requestId) {
|
||||
require(requestId > 0 && requestId <= requestCounter, "Invalid request ID");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier supportedToken(address token) {
|
||||
require(supportedTokens[token].isActive, "Token not supported");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier supportedChain(uint256 chainId) {
|
||||
require(supportedChains[chainId].isActive, "Chain not supported");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier withinBridgeLimit(address token, uint256 amount) {
|
||||
require(amount <= supportedTokens[token].bridgeLimit, "Amount exceeds bridge limit");
|
||||
_;
|
||||
}
|
||||
|
||||
constructor(address _feeRecipient) {
|
||||
feeRecipient = _feeRecipient;
|
||||
|
||||
// Initialize with Ethereum mainnet
|
||||
_addChain(1, ChainType.ETHEREUM, "Ethereum", true, address(0), 12, 12);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Initiates a cross-chain bridge transfer
|
||||
* @param sourceToken Address of the source token
|
||||
* @param targetToken Address of the target token
|
||||
* @param amount Amount to bridge
|
||||
* @param targetChainId Target chain ID
|
||||
* @param recipient Recipient address on target chain
|
||||
* @return requestId The bridge request ID
|
||||
*/
|
||||
function initiateBridge(
|
||||
address sourceToken,
|
||||
address targetToken,
|
||||
uint256 amount,
|
||||
uint256 targetChainId,
|
||||
address recipient
|
||||
)
|
||||
external
|
||||
nonReentrant
|
||||
whenNotPaused
|
||||
supportedToken(sourceToken)
|
||||
supportedChain(targetChainId)
|
||||
withinBridgeLimit(sourceToken, amount)
|
||||
returns (uint256 requestId)
|
||||
{
|
||||
require(sourceToken != address(0), "Invalid source token");
|
||||
require(targetToken != address(0), "Invalid target token");
|
||||
require(recipient != address(0), "Invalid recipient");
|
||||
require(amount > 0, "Amount must be greater than 0");
|
||||
require(targetChainId != block.chainid, "Same chain bridging not allowed");
|
||||
|
||||
// Calculate bridge fee
|
||||
uint256 fee = (amount * supportedTokens[sourceToken].feePercentage) / BASIS_POINTS;
|
||||
uint256 totalAmount = amount + fee;
|
||||
|
||||
// Transfer tokens to contract
|
||||
IERC20(sourceToken).safeTransferFrom(msg.sender, address(this), totalAmount);
|
||||
|
||||
// Create bridge request
|
||||
requestCounter++;
|
||||
requestId = requestCounter;
|
||||
|
||||
BridgeRequest storage request = bridgeRequests[requestId];
|
||||
request.requestId = requestId;
|
||||
request.sourceToken = sourceToken;
|
||||
request.targetToken = targetToken;
|
||||
request.amount = amount;
|
||||
request.sourceChainId = block.chainid;
|
||||
request.targetChainId = targetChainId;
|
||||
request.recipient = recipient;
|
||||
request.sender = msg.sender;
|
||||
request.fee = fee;
|
||||
request.status = BridgeStatus.PENDING;
|
||||
request.createdAt = block.timestamp;
|
||||
|
||||
// Update statistics
|
||||
totalBridgedAmount += amount;
|
||||
totalFeesCollected += fee;
|
||||
userBridgeHistory[msg.sender].push(requestId);
|
||||
|
||||
// Transfer fee to fee recipient
|
||||
if (fee > 0) {
|
||||
IERC20(sourceToken).safeTransfer(feeRecipient, fee);
|
||||
}
|
||||
|
||||
emit BridgeInitiated(
|
||||
requestId,
|
||||
msg.sender,
|
||||
recipient,
|
||||
sourceToken,
|
||||
amount,
|
||||
block.chainid,
|
||||
targetChainId
|
||||
);
|
||||
|
||||
return requestId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Confirms a bridge request by a validator
|
||||
* @param requestId The bridge request ID
|
||||
* @param lockTxHash The transaction hash of the lock transaction
|
||||
* @param signature Validator signature
|
||||
*/
|
||||
function confirmBridge(
|
||||
uint256 requestId,
|
||||
bytes32 lockTxHash,
|
||||
bytes memory signature
|
||||
)
|
||||
external
|
||||
onlyActiveValidator
|
||||
validRequest(requestId)
|
||||
{
|
||||
BridgeRequest storage request = bridgeRequests[requestId];
|
||||
|
||||
require(request.status == BridgeStatus.PENDING, "Request not pending");
|
||||
require(!request.hasConfirmed[msg.sender], "Already confirmed");
|
||||
require(block.timestamp <= request.createdAt + BRIDGE_TIMEOUT, "Bridge request expired");
|
||||
|
||||
// Verify signature
|
||||
bytes32 messageHash = keccak256(abi.encodePacked(requestId, lockTxHash, block.chainid));
|
||||
require(_verifySignature(messageHash, signature, msg.sender), "Invalid signature");
|
||||
|
||||
// Record confirmation
|
||||
request.hasConfirmed[msg.sender] = true;
|
||||
request.confirmations++;
|
||||
request.lockTxHash = lockTxHash;
|
||||
validators[msg.sender].lastValidation = block.timestamp;
|
||||
validators[msg.sender].totalValidations++;
|
||||
|
||||
// Check if we have enough confirmations
|
||||
uint256 requiredConfirmations = _getRequiredConfirmations(request.sourceChainId);
|
||||
if (request.confirmations >= requiredConfirmations) {
|
||||
request.status = BridgeStatus.CONFIRMED;
|
||||
request.confirmedAt = block.timestamp;
|
||||
}
|
||||
|
||||
emit BridgeConfirmed(requestId, msg.sender, lockTxHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Completes a bridge request on the target chain
|
||||
* @param requestId The bridge request ID
|
||||
* @param unlockTxHash The transaction hash of the unlock transaction
|
||||
* @param proof Merkle proof for verification
|
||||
*/
|
||||
function completeBridge(
|
||||
uint256 requestId,
|
||||
bytes32 unlockTxHash,
|
||||
bytes32[] calldata proof
|
||||
)
|
||||
external
|
||||
nonReentrant
|
||||
whenNotPaused
|
||||
validRequest(requestId)
|
||||
{
|
||||
BridgeRequest storage request = bridgeRequests[requestId];
|
||||
|
||||
require(request.status == BridgeStatus.CONFIRMED, "Request not confirmed");
|
||||
require(block.chainid == request.targetChainId, "Wrong chain");
|
||||
require(!processedTxHashes[unlockTxHash], "Transaction already processed");
|
||||
|
||||
// Verify Merkle proof
|
||||
bytes32 leaf = keccak256(abi.encodePacked(requestId, request.recipient, request.amount));
|
||||
require(MerkleProof.verify(proof, merkleRoot, leaf), "Invalid Merkle proof");
|
||||
|
||||
// Mark transaction as processed
|
||||
processedTxHashes[unlockTxHash] = true;
|
||||
request.unlockTxHash = unlockTxHash;
|
||||
request.status = BridgeStatus.COMPLETED;
|
||||
request.completedAt = block.timestamp;
|
||||
|
||||
// Transfer tokens to recipient
|
||||
IERC20(request.targetToken).safeTransfer(request.recipient, request.amount);
|
||||
|
||||
emit BridgeCompleted(requestId, unlockTxHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Cancels a bridge request
|
||||
* @param requestId The bridge request ID
|
||||
* @param reason Reason for cancellation
|
||||
*/
|
||||
function cancelBridge(uint256 requestId, string memory reason)
|
||||
external
|
||||
nonReentrant
|
||||
validRequest(requestId)
|
||||
{
|
||||
BridgeRequest storage request = bridgeRequests[requestId];
|
||||
|
||||
require(request.status == BridgeStatus.PENDING, "Request not pending");
|
||||
require(
|
||||
msg.sender == request.sender || msg.sender == owner(),
|
||||
"Not authorized to cancel"
|
||||
);
|
||||
require(block.timestamp > request.createdAt + BRIDGE_TIMEOUT, "Bridge not expired");
|
||||
|
||||
// Refund tokens to sender
|
||||
uint256 refundAmount = request.amount + request.fee;
|
||||
IERC20(request.sourceToken).safeTransfer(request.sender, refundAmount);
|
||||
|
||||
// Update status
|
||||
request.status = BridgeStatus.CANCELLED;
|
||||
|
||||
emit BridgeCancelled(requestId);
|
||||
emit BridgeFailed(requestId, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets bridge request details
|
||||
* @param requestId The bridge request ID
|
||||
* @return request The bridge request details
|
||||
*/
|
||||
function getBridgeRequest(uint256 requestId)
|
||||
external
|
||||
view
|
||||
validRequest(requestId)
|
||||
returns (BridgeRequest memory)
|
||||
{
|
||||
return bridgeRequests[requestId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets user's bridge history
|
||||
* @param user The user address
|
||||
* @return requestIds Array of bridge request IDs
|
||||
*/
|
||||
function getUserBridgeHistory(address user)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory requestIds)
|
||||
{
|
||||
return userBridgeHistory[user];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Validates a bridge request signature
|
||||
* @param requestId The bridge request ID
|
||||
* @param lockTxHash The lock transaction hash
|
||||
* @param signature The signature to validate
|
||||
* @return isValid Whether the signature is valid
|
||||
*/
|
||||
function validateBridgeRequest(uint256 requestId, bytes32 lockTxHash, bytes memory signature)
|
||||
external
|
||||
view
|
||||
returns (bool isValid)
|
||||
{
|
||||
bytes32 messageHash = keccak256(abi.encodePacked(requestId, lockTxHash, block.chainid));
|
||||
address recoveredAddress = messageHash.recover(signature);
|
||||
return validators[recoveredAddress].isActive;
|
||||
}
|
||||
|
||||
// Admin functions
|
||||
|
||||
/**
|
||||
* @dev Adds support for a new token
|
||||
* @param tokenAddress The token address
|
||||
* @param bridgeLimit Maximum bridge amount
|
||||
* @param feePercentage Bridge fee percentage
|
||||
* @param requiresWhitelist Whether whitelist is required
|
||||
*/
|
||||
function addSupportedToken(
|
||||
address tokenAddress,
|
||||
uint256 bridgeLimit,
|
||||
uint256 feePercentage,
|
||||
bool requiresWhitelist
|
||||
) external onlyOwner {
|
||||
require(tokenAddress != address(0), "Invalid token address");
|
||||
require(feePercentage <= MAX_FEE, "Fee too high");
|
||||
|
||||
supportedTokens[tokenAddress] = SupportedToken({
|
||||
tokenAddress: tokenAddress,
|
||||
isActive: true,
|
||||
bridgeLimit: bridgeLimit,
|
||||
feePercentage: feePercentage,
|
||||
requiresWhitelist: requiresWhitelist
|
||||
});
|
||||
|
||||
supportedTokenAddresses.push(tokenAddress);
|
||||
emit TokenSupported(tokenAddress, bridgeLimit, feePercentage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Adds support for a new blockchain
|
||||
* @param chainId The chain ID
|
||||
* @param chainType The chain type
|
||||
* @param name The chain name
|
||||
* @param bridgeContract The bridge contract address on that chain
|
||||
* @param minConfirmations Minimum confirmations required
|
||||
* @param avgBlockTime Average block time in seconds
|
||||
*/
|
||||
function addSupportedChain(
|
||||
uint256 chainId,
|
||||
ChainType chainType,
|
||||
string memory name,
|
||||
address bridgeContract,
|
||||
uint256 minConfirmations,
|
||||
uint256 avgBlockTime
|
||||
) external onlyOwner {
|
||||
require(chainId > 0, "Invalid chain ID");
|
||||
require(chainId != block.chainid, "Cannot add current chain");
|
||||
|
||||
supportedChains[chainId] = ChainConfig({
|
||||
chainId: chainId,
|
||||
chainType: chainType,
|
||||
name: name,
|
||||
isActive: true,
|
||||
bridgeContract: bridgeContract,
|
||||
minConfirmations: minConfirmations,
|
||||
avgBlockTime: avgBlockTime
|
||||
});
|
||||
|
||||
supportedChainIds.push(chainId);
|
||||
emit ChainSupported(chainId, name, chainType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Adds a new validator
|
||||
* @param validatorAddress The validator address
|
||||
* @param weight The validator weight
|
||||
*/
|
||||
function addValidator(address validatorAddress, uint256 weight) external onlyOwner {
|
||||
require(validatorAddress != address(0), "Invalid validator address");
|
||||
require(!validators[validatorAddress].isActive, "Validator already exists");
|
||||
|
||||
validators[validatorAddress] = Validator({
|
||||
validatorAddress: validatorAddress,
|
||||
isActive: true,
|
||||
weight: weight,
|
||||
lastValidation: 0,
|
||||
totalValidations: 0
|
||||
});
|
||||
|
||||
activeValidators.push(validatorAddress);
|
||||
emit ValidatorAdded(validatorAddress, weight);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Removes a validator
|
||||
* @param validatorAddress The validator address
|
||||
*/
|
||||
function removeValidator(address validatorAddress) external onlyOwner {
|
||||
require(validators[validatorAddress].isActive, "Validator not active");
|
||||
|
||||
validators[validatorAddress].isActive = false;
|
||||
|
||||
// Remove from active validators array
|
||||
for (uint i = 0; i < activeValidators.length; i++) {
|
||||
if (activeValidators[i] == validatorAddress) {
|
||||
activeValidators[i] = activeValidators[activeValidators.length - 1];
|
||||
activeValidators.pop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
emit ValidatorRemoved(validatorAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Updates the Merkle root for transaction verification
|
||||
* @param newMerkleRoot The new Merkle root
|
||||
*/
|
||||
function updateMerkleRoot(bytes32 newMerkleRoot) external onlyOwner {
|
||||
merkleRoot = newMerkleRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Sets the fee recipient address
|
||||
* @param newFeeRecipient The new fee recipient
|
||||
*/
|
||||
function setFeeRecipient(address newFeeRecipient) external onlyOwner {
|
||||
require(newFeeRecipient != address(0), "Invalid address");
|
||||
feeRecipient = newFeeRecipient;
|
||||
}
|
||||
|
||||
function pause() external onlyOwner {
|
||||
_pause();
|
||||
}
|
||||
|
||||
function unpause() external onlyOwner {
|
||||
_unpause();
|
||||
}
|
||||
|
||||
// Internal functions
|
||||
|
||||
function _verifySignature(
|
||||
bytes32 messageHash,
|
||||
bytes memory signature,
|
||||
address signer
|
||||
) internal pure returns (bool) {
|
||||
bytes32 ethSignedMessageHash = keccak256(
|
||||
abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash)
|
||||
);
|
||||
return ethSignedMessageHash.recover(signature) == signer;
|
||||
}
|
||||
|
||||
function _getRequiredConfirmations(uint256 chainId) internal view returns (uint256) {
|
||||
ChainConfig storage chain = supportedChains[chainId];
|
||||
return chain.minConfirmations > 0 ? chain.minConfirmations : MIN_CONFIRMATIONS;
|
||||
}
|
||||
|
||||
function _addChain(
|
||||
uint256 chainId,
|
||||
ChainType chainType,
|
||||
string memory name,
|
||||
bool isActive,
|
||||
address bridgeContract,
|
||||
uint256 minConfirmations,
|
||||
uint256 avgBlockTime
|
||||
) internal {
|
||||
supportedChains[chainId] = ChainConfig({
|
||||
chainId: chainId,
|
||||
chainType: chainType,
|
||||
name: name,
|
||||
isActive: isActive,
|
||||
bridgeContract: bridgeContract,
|
||||
minConfirmations: minConfirmations,
|
||||
avgBlockTime: avgBlockTime
|
||||
});
|
||||
|
||||
supportedChainIds.push(chainId);
|
||||
emit ChainSupported(chainId, name, chainType);
|
||||
}
|
||||
|
||||
// Emergency functions
|
||||
|
||||
function emergencyWithdraw(address token, uint256 amount) external onlyOwner {
|
||||
IERC20(token).safeTransfer(owner(), amount);
|
||||
}
|
||||
|
||||
function emergencyPause() external onlyOwner {
|
||||
_pause();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user