Phase 1 Implementation Complete: - AgentBounty.sol: Automated bounty board with ZK-proof verification - AgentStaking.sol: Reputation-based yield farming with dynamic APY - BountyIntegration.sol: Cross-contract event handling and auto-verification - Database models: Complete bounty, staking, and ecosystem metrics schemas - REST APIs: Full bounty and staking management endpoints - Services: Business logic for bounty creation, verification, and staking operations - Ecosystem dashboard: Analytics and metrics tracking system Key Features: - Multi-tier bounty system (Bronze, Silver, Gold, Platinum) - Performance-based APY calculation with reputation multipliers - ZK-proof integration with PerformanceVerifier.sol - Automatic bounty completion detection - Comprehensive analytics dashboard - Risk assessment and leaderboards - Real-time metrics and predictions Security Features: - Reentrancy protection on all contracts - Role-based access control - Dispute resolution mechanism - Early unbonding penalties - Platform fee collection Economic Model: - Creation fees: 0.5% - Success fees: 2% - Platform fees: 1% - Staking APY: 5-20% based on performance - Dispute fees: 0.1%
828 lines
28 KiB
Solidity
828 lines
28 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.19;
|
|
|
|
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
|
import "@openzeppelin/contracts/security/Pausable.sol";
|
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
import "./PerformanceVerifier.sol";
|
|
import "./AIToken.sol";
|
|
|
|
/**
|
|
* @title Agent Staking System
|
|
* @dev Reputation-based yield farming for AI agents with dynamic APY calculation
|
|
* @notice Allows users to stake AITBC tokens on agent wallets and earn rewards based on agent performance
|
|
*/
|
|
contract AgentStaking is Ownable, ReentrancyGuard, Pausable {
|
|
|
|
// State variables
|
|
IERC20 public aitbcToken;
|
|
PerformanceVerifier public performanceVerifier;
|
|
|
|
uint256 public stakeCounter;
|
|
uint256 public baseAPY = 500; // 5% base APY in basis points
|
|
uint256 public maxAPY = 2000; // 20% max APY in basis points
|
|
uint256 public minStakeAmount = 100 * 10**18; // 100 AITBC minimum
|
|
uint256 public maxStakeAmount = 100000 * 10**18; // 100k AITBC maximum
|
|
uint256 public unbondingPeriod = 7 days;
|
|
uint256 public rewardDistributionInterval = 1 days;
|
|
uint256 public platformFeePercentage = 100; // 1% platform fee
|
|
uint256 public earlyUnbondPenalty = 1000; // 10% penalty for early unbonding
|
|
|
|
// Staking status
|
|
enum StakeStatus { ACTIVE, UNBONDING, COMPLETED, SLASHED }
|
|
|
|
// Agent performance tier
|
|
enum PerformanceTier { BRONZE, SILVER, GOLD, PLATINUM, DIAMOND }
|
|
|
|
// Structs
|
|
struct Stake {
|
|
uint256 stakeId;
|
|
address staker;
|
|
address agentWallet;
|
|
uint256 amount;
|
|
uint256 lockPeriod;
|
|
uint256 startTime;
|
|
uint256 endTime;
|
|
StakeStatus status;
|
|
uint256 accumulatedRewards;
|
|
uint256 lastRewardTime;
|
|
uint256 currentAPY;
|
|
PerformanceTier agentTier;
|
|
bool autoCompound;
|
|
}
|
|
|
|
struct AgentMetrics {
|
|
address agentWallet;
|
|
uint256 totalStaked;
|
|
uint256 stakerCount;
|
|
uint256 totalRewardsDistributed;
|
|
uint256 averageAccuracy;
|
|
uint256 totalSubmissions;
|
|
uint256 successfulSubmissions;
|
|
uint256 lastUpdateTime;
|
|
PerformanceTier currentTier;
|
|
uint256 tierScore;
|
|
}
|
|
|
|
struct StakingPool {
|
|
address agentWallet;
|
|
uint256 totalStaked;
|
|
uint256 totalRewards;
|
|
uint256 poolAPY;
|
|
uint256 lastDistributionTime;
|
|
mapping(address => uint256) stakerShares;
|
|
address[] stakers;
|
|
}
|
|
|
|
struct RewardCalculation {
|
|
uint256 baseRewards;
|
|
uint256 performanceBonus;
|
|
uint256 lockBonus;
|
|
uint256 tierBonus;
|
|
uint256 totalRewards;
|
|
uint256 platformFee;
|
|
}
|
|
|
|
// Mappings
|
|
mapping(uint256 => Stake) public stakes;
|
|
mapping(address => uint256[]) public stakerStakes;
|
|
mapping(address => uint256[]) public agentStakes;
|
|
mapping(address => AgentMetrics) public agentMetrics;
|
|
mapping(address => StakingPool) public stakingPools;
|
|
mapping(PerformanceTier => uint256) public tierMultipliers;
|
|
mapping(uint256 => uint256) public lockPeriodMultipliers;
|
|
|
|
// Arrays
|
|
address[] public supportedAgents;
|
|
uint256[] public activeStakeIds;
|
|
|
|
// Events
|
|
event StakeCreated(
|
|
uint256 indexed stakeId,
|
|
address indexed staker,
|
|
address indexed agentWallet,
|
|
uint256 amount,
|
|
uint256 lockPeriod,
|
|
uint256 apy
|
|
);
|
|
|
|
event StakeUpdated(
|
|
uint256 indexed stakeId,
|
|
uint256 newAmount,
|
|
uint256 newAPY
|
|
);
|
|
|
|
event RewardsDistributed(
|
|
uint256 indexed stakeId,
|
|
address indexed staker,
|
|
uint256 rewardAmount,
|
|
uint256 platformFee
|
|
);
|
|
|
|
event StakeUnbonded(
|
|
uint256 indexed stakeId,
|
|
address indexed staker,
|
|
uint256 amount,
|
|
uint256 penalty
|
|
);
|
|
|
|
event StakeCompleted(
|
|
uint256 indexed stakeId,
|
|
address indexed staker,
|
|
uint256 totalAmount,
|
|
uint256 totalRewards
|
|
);
|
|
|
|
event AgentTierUpdated(
|
|
address indexed agentWallet,
|
|
PerformanceTier oldTier,
|
|
PerformanceTier newTier,
|
|
uint256 tierScore
|
|
);
|
|
|
|
event PoolRewardsDistributed(
|
|
address indexed agentWallet,
|
|
uint256 totalRewards,
|
|
uint256 stakerCount
|
|
);
|
|
|
|
event PlatformFeeCollected(
|
|
uint256 indexed stakeId,
|
|
uint256 feeAmount,
|
|
address indexed collector
|
|
);
|
|
|
|
// Modifiers
|
|
modifier stakeExists(uint256 _stakeId) {
|
|
require(_stakeId < stakeCounter, "Stake does not exist");
|
|
_;
|
|
}
|
|
|
|
modifier onlyStakeOwner(uint256 _stakeId) {
|
|
require(stakes[_stakeId].staker == msg.sender, "Not stake owner");
|
|
_;
|
|
}
|
|
|
|
modifier supportedAgent(address _agentWallet) {
|
|
require(agentMetrics[_agentWallet].agentWallet != address(0) || _agentWallet == address(0), "Agent not supported");
|
|
_;
|
|
}
|
|
|
|
modifier validStakeAmount(uint256 _amount) {
|
|
require(_amount >= minStakeAmount && _amount <= maxStakeAmount, "Invalid stake amount");
|
|
_;
|
|
}
|
|
|
|
modifier sufficientBalance(uint256 _amount) {
|
|
require(aitbcToken.balanceOf(msg.sender) >= _amount, "Insufficient balance");
|
|
_;
|
|
}
|
|
|
|
constructor(address _aitbcToken, address _performanceVerifier) {
|
|
aitbcToken = IERC20(_aitbcToken);
|
|
performanceVerifier = PerformanceVerifier(_performanceVerifier);
|
|
|
|
// Set tier multipliers (in basis points)
|
|
tierMultipliers[PerformanceTier.BRONZE] = 1000; // 1x
|
|
tierMultipliers[PerformanceTier.SILVER] = 1200; // 1.2x
|
|
tierMultipliers[PerformanceTier.GOLD] = 1500; // 1.5x
|
|
tierMultipliers[PerformanceTier.PLATINUM] = 2000; // 2x
|
|
tierMultipliers[PerformanceTier.DIAMOND] = 3000; // 3x
|
|
|
|
// Set lock period multipliers
|
|
lockPeriodMultipliers[30 days] = 1100; // 1.1x for 30 days
|
|
lockPeriodMultipliers[90 days] = 1250; // 1.25x for 90 days
|
|
lockPeriodMultipliers[180 days] = 1500; // 1.5x for 180 days
|
|
lockPeriodMultipliers[365 days] = 2000; // 2x for 365 days
|
|
}
|
|
|
|
/**
|
|
* @dev Creates a new stake on an agent wallet
|
|
* @param _agentWallet Address of the agent wallet
|
|
* @param _amount Amount to stake
|
|
* @param _lockPeriod Lock period in seconds
|
|
* @param _autoCompound Whether to auto-compound rewards
|
|
*/
|
|
function stakeOnAgent(
|
|
address _agentWallet,
|
|
uint256 _amount,
|
|
uint256 _lockPeriod,
|
|
bool _autoCompound
|
|
) external
|
|
supportedAgent(_agentWallet)
|
|
validStakeAmount(_amount)
|
|
sufficientBalance(_amount)
|
|
nonReentrant
|
|
returns (uint256)
|
|
{
|
|
require(_lockPeriod >= 1 days, "Lock period too short");
|
|
require(_lockPeriod <= 365 days, "Lock period too long");
|
|
|
|
uint256 stakeId = stakeCounter++;
|
|
|
|
// Calculate initial APY
|
|
PerformanceTier agentTier = _getAgentTier(_agentWallet);
|
|
uint256 apy = _calculateAPY(_agentWallet, _lockPeriod, agentTier);
|
|
|
|
Stake storage stake = stakes[stakeId];
|
|
stake.stakeId = stakeId;
|
|
stake.staker = msg.sender;
|
|
stake.agentWallet = _agentWallet;
|
|
stake.amount = _amount;
|
|
stake.lockPeriod = _lockPeriod;
|
|
stake.startTime = block.timestamp;
|
|
stake.endTime = block.timestamp + _lockPeriod;
|
|
stake.status = StakeStatus.ACTIVE;
|
|
stake.accumulatedRewards = 0;
|
|
stake.lastRewardTime = block.timestamp;
|
|
stake.currentAPY = apy;
|
|
stake.agentTier = agentTier;
|
|
stake.autoCompound = _autoCompound;
|
|
|
|
// Update agent metrics
|
|
_updateAgentMetrics(_agentWallet, _amount, true);
|
|
|
|
// Update staking pool
|
|
_updateStakingPool(_agentWallet, msg.sender, _amount, true);
|
|
|
|
// Update tracking arrays
|
|
stakerStakes[msg.sender].push(stakeId);
|
|
agentStakes[_agentWallet].push(stakeId);
|
|
activeStakeIds.push(stakeId);
|
|
|
|
// Transfer tokens to contract
|
|
require(aitbcToken.transferFrom(msg.sender, address(this), _amount), "Transfer failed");
|
|
|
|
emit StakeCreated(stakeId, msg.sender, _agentWallet, _amount, _lockPeriod, apy);
|
|
|
|
return stakeId;
|
|
}
|
|
|
|
/**
|
|
* @dev Adds more tokens to an existing stake
|
|
* @param _stakeId Stake ID
|
|
* @param _additionalAmount Additional amount to stake
|
|
*/
|
|
function addToStake(
|
|
uint256 _stakeId,
|
|
uint256 _additionalAmount
|
|
) external
|
|
stakeExists(_stakeId)
|
|
onlyStakeOwner(_stakeId)
|
|
validStakeAmount(_additionalAmount)
|
|
sufficientBalance(_additionalAmount)
|
|
nonReentrant
|
|
{
|
|
Stake storage stake = stakes[_stakeId];
|
|
require(stake.status == StakeStatus.ACTIVE, "Stake not active");
|
|
|
|
// Calculate new APY
|
|
uint256 newTotalAmount = stake.amount + _additionalAmount;
|
|
uint256 newAPY = _calculateAPY(stake.agentWallet, stake.lockPeriod, stake.agentTier);
|
|
|
|
// Update stake
|
|
stake.amount = newTotalAmount;
|
|
stake.currentAPY = newAPY;
|
|
|
|
// Update agent metrics
|
|
_updateAgentMetrics(stake.agentWallet, _additionalAmount, true);
|
|
|
|
// Update staking pool
|
|
_updateStakingPool(stake.agentWallet, msg.sender, _additionalAmount, true);
|
|
|
|
// Transfer additional tokens
|
|
require(aitbcToken.transferFrom(msg.sender, address(this), _additionalAmount), "Transfer failed");
|
|
|
|
emit StakeUpdated(_stakeId, newTotalAmount, newAPY);
|
|
}
|
|
|
|
/**
|
|
* @dev Initiates unbonding for a stake
|
|
* @param _stakeId Stake ID
|
|
*/
|
|
function unbondStake(uint256 _stakeId) external
|
|
stakeExists(_stakeId)
|
|
onlyStakeOwner(_stakeId)
|
|
nonReentrant
|
|
{
|
|
Stake storage stake = stakes[_stakeId];
|
|
require(stake.status == StakeStatus.ACTIVE, "Stake not active");
|
|
require(block.timestamp >= stake.endTime, "Lock period not ended");
|
|
|
|
// Calculate final rewards
|
|
_calculateRewards(_stakeId);
|
|
|
|
stake.status = StakeStatus.UNBONDING;
|
|
|
|
// Remove from active stakes
|
|
_removeFromActiveStakes(_stakeId);
|
|
}
|
|
|
|
/**
|
|
* @dev Completes unbonding and returns stake + rewards
|
|
* @param _stakeId Stake ID
|
|
*/
|
|
function completeUnbonding(uint256 _stakeId) external
|
|
stakeExists(_stakeId)
|
|
onlyStakeOwner(_stakeId)
|
|
nonReentrant
|
|
{
|
|
Stake storage stake = stakes[_stakeId];
|
|
require(stake.status == StakeStatus.UNBONDING, "Stake not unbonding");
|
|
require(block.timestamp >= stake.endTime + unbondingPeriod, "Unbonding period not ended");
|
|
|
|
uint256 totalAmount = stake.amount;
|
|
uint256 totalRewards = stake.accumulatedRewards;
|
|
|
|
// Apply early unbonding penalty if applicable
|
|
uint256 penalty = 0;
|
|
if (block.timestamp < stake.endTime + 30 days) {
|
|
penalty = (totalAmount * earlyUnbondPenalty) / 10000;
|
|
totalAmount -= penalty;
|
|
}
|
|
|
|
stake.status = StakeStatus.COMPLETED;
|
|
|
|
// Update agent metrics
|
|
_updateAgentMetrics(stake.agentWallet, stake.amount, false);
|
|
|
|
// Update staking pool
|
|
_updateStakingPool(stake.agentWallet, msg.sender, stake.amount, false);
|
|
|
|
// Transfer tokens back to staker
|
|
if (totalAmount > 0) {
|
|
require(aitbcToken.transfer(msg.sender, totalAmount), "Stake transfer failed");
|
|
}
|
|
|
|
if (totalRewards > 0) {
|
|
require(aitbcToken.transfer(msg.sender, totalRewards), "Rewards transfer failed");
|
|
}
|
|
|
|
emit StakeCompleted(_stakeId, msg.sender, totalAmount, totalRewards);
|
|
emit StakeUnbonded(_stakeId, msg.sender, totalAmount, penalty);
|
|
}
|
|
|
|
/**
|
|
* @dev Distributes agent earnings to stakers
|
|
* @param _agentWallet Agent wallet address
|
|
* @param _totalEarnings Total earnings to distribute
|
|
*/
|
|
function distributeAgentEarnings(
|
|
address _agentWallet,
|
|
uint256 _totalEarnings
|
|
) external
|
|
supportedAgent(_agentWallet)
|
|
nonReentrant
|
|
{
|
|
require(_totalEarnings > 0, "No earnings to distribute");
|
|
|
|
StakingPool storage pool = stakingPools[_agentWallet];
|
|
require(pool.totalStaked > 0, "No stakers in pool");
|
|
|
|
// Calculate platform fee
|
|
uint256 platformFee = (_totalEarnings * platformFeePercentage) / 10000;
|
|
uint256 distributableAmount = _totalEarnings - platformFee;
|
|
|
|
// Transfer platform fee
|
|
if (platformFee > 0) {
|
|
require(aitbcToken.transferFrom(msg.sender, owner(), platformFee), "Platform fee transfer failed");
|
|
}
|
|
|
|
// Transfer distributable amount to contract
|
|
require(aitbcToken.transferFrom(msg.sender, address(this), distributableAmount), "Earnings transfer failed");
|
|
|
|
// Distribute to stakers proportionally
|
|
uint256 totalDistributed = 0;
|
|
for (uint256 i = 0; i < pool.stakers.length; i++) {
|
|
address staker = pool.stakers[i];
|
|
uint256 stakerShare = pool.stakerShares[staker];
|
|
uint256 stakerReward = (distributableAmount * stakerShare) / pool.totalStaked;
|
|
|
|
if (stakerReward > 0) {
|
|
// Find and update all stakes for this staker on this agent
|
|
uint256[] storage stakesForAgent = agentStakes[_agentWallet];
|
|
for (uint256 j = 0; j < stakesForAgent.length; j++) {
|
|
uint256 stakeId = stakesForAgent[j];
|
|
Stake storage stake = stakes[stakeId];
|
|
if (stake.staker == staker && stake.status == StakeStatus.ACTIVE) {
|
|
stake.accumulatedRewards += stakerReward;
|
|
break;
|
|
}
|
|
}
|
|
totalDistributed += stakerReward;
|
|
}
|
|
}
|
|
|
|
// Update agent metrics
|
|
agentMetrics[_agentWallet].totalRewardsDistributed += totalDistributed;
|
|
|
|
emit PoolRewardsDistributed(_agentWallet, totalDistributed, pool.stakers.length);
|
|
}
|
|
|
|
/**
|
|
* @dev Updates agent performance metrics and tier
|
|
* @param _agentWallet Agent wallet address
|
|
* @param _accuracy Latest accuracy score
|
|
* @param _successful Whether the submission was successful
|
|
*/
|
|
function updateAgentPerformance(
|
|
address _agentWallet,
|
|
uint256 _accuracy,
|
|
bool _successful
|
|
) external
|
|
supportedAgent(_agentWallet)
|
|
nonReentrant
|
|
{
|
|
AgentMetrics storage metrics = agentMetrics[_agentWallet];
|
|
|
|
metrics.totalSubmissions++;
|
|
if (_successful) {
|
|
metrics.successfulSubmissions++;
|
|
}
|
|
|
|
// Update average accuracy (weighted average)
|
|
uint256 totalAccuracy = metrics.averageAccuracy * (metrics.totalSubmissions - 1) + _accuracy;
|
|
metrics.averageAccuracy = totalAccuracy / metrics.totalSubmissions;
|
|
|
|
metrics.lastUpdateTime = block.timestamp;
|
|
|
|
// Calculate new tier
|
|
PerformanceTier newTier = _calculateAgentTier(_agentWallet);
|
|
PerformanceTier oldTier = metrics.currentTier;
|
|
|
|
if (newTier != oldTier) {
|
|
metrics.currentTier = newTier;
|
|
|
|
// Update APY for all active stakes on this agent
|
|
uint256[] storage stakesForAgent = agentStakes[_agentWallet];
|
|
for (uint256 i = 0; i < stakesForAgent.length; i++) {
|
|
uint256 stakeId = stakesForAgent[i];
|
|
Stake storage stake = stakes[stakeId];
|
|
if (stake.status == StakeStatus.ACTIVE) {
|
|
stake.currentAPY = _calculateAPY(_agentWallet, stake.lockPeriod, newTier);
|
|
stake.agentTier = newTier;
|
|
}
|
|
}
|
|
|
|
emit AgentTierUpdated(_agentWallet, oldTier, newTier, metrics.tierScore);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @dev Adds a supported agent
|
|
* @param _agentWallet Agent wallet address
|
|
* @param _initialTier Initial performance tier
|
|
*/
|
|
function addSupportedAgent(
|
|
address _agentWallet,
|
|
PerformanceTier _initialTier
|
|
) external onlyOwner {
|
|
require(_agentWallet != address(0), "Invalid agent address");
|
|
require(agentMetrics[_agentWallet].agentWallet == address(0), "Agent already supported");
|
|
|
|
agentMetrics[_agentWallet] = AgentMetrics({
|
|
agentWallet: _agentWallet,
|
|
totalStaked: 0,
|
|
stakerCount: 0,
|
|
totalRewardsDistributed: 0,
|
|
averageAccuracy: 0,
|
|
totalSubmissions: 0,
|
|
successfulSubmissions: 0,
|
|
lastUpdateTime: block.timestamp,
|
|
currentTier: _initialTier,
|
|
tierScore: _getTierScore(_initialTier)
|
|
});
|
|
|
|
// Initialize staking pool
|
|
stakingPools[_agentWallet].agentWallet = _agentWallet;
|
|
stakingPools[_agentWallet].totalStaked = 0;
|
|
stakingPools[_agentWallet].totalRewards = 0;
|
|
stakingPools[_agentWallet].poolAPY = baseAPY;
|
|
stakingPools[_agentWallet].lastDistributionTime = block.timestamp;
|
|
|
|
supportedAgents.push(_agentWallet);
|
|
}
|
|
|
|
/**
|
|
* @dev Removes a supported agent
|
|
* @param _agentWallet Agent wallet address
|
|
*/
|
|
function removeSupportedAgent(address _agentWallet) external onlyOwner {
|
|
require(agentMetrics[_agentWallet].agentWallet != address(0), "Agent not supported");
|
|
require(agentMetrics[_agentWallet].totalStaked == 0, "Agent has active stakes");
|
|
|
|
// Remove from supported agents
|
|
for (uint256 i = 0; i < supportedAgents.length; i++) {
|
|
if (supportedAgents[i] == _agentWallet) {
|
|
supportedAgents[i] = supportedAgents[supportedAgents.length - 1];
|
|
supportedAgents.pop();
|
|
break;
|
|
}
|
|
}
|
|
|
|
delete agentMetrics[_agentWallet];
|
|
delete stakingPools[_agentWallet];
|
|
}
|
|
|
|
/**
|
|
* @dev Updates configuration parameters
|
|
* @param _baseAPY New base APY
|
|
* @param _maxAPY New maximum APY
|
|
* @param _platformFee New platform fee percentage
|
|
*/
|
|
function updateConfiguration(
|
|
uint256 _baseAPY,
|
|
uint256 _maxAPY,
|
|
uint256 _platformFee
|
|
) external onlyOwner {
|
|
require(_baseAPY <= _maxAPY, "Base APY cannot exceed max APY");
|
|
require(_maxAPY <= 5000, "Max APY too high"); // Max 50%
|
|
require(_platformFee <= 500, "Platform fee too high"); // Max 5%
|
|
|
|
baseAPY = _baseAPY;
|
|
maxAPY = _maxAPY;
|
|
platformFeePercentage = _platformFee;
|
|
}
|
|
|
|
// View functions
|
|
|
|
/**
|
|
* @dev Gets stake details
|
|
* @param _stakeId Stake ID
|
|
*/
|
|
function getStake(uint256 _stakeId) external view stakeExists(_stakeId) returns (
|
|
address staker,
|
|
address agentWallet,
|
|
uint256 amount,
|
|
uint256 lockPeriod,
|
|
uint256 startTime,
|
|
uint256 endTime,
|
|
StakeStatus status,
|
|
uint256 accumulatedRewards,
|
|
uint256 currentAPY,
|
|
PerformanceTier agentTier,
|
|
bool autoCompound
|
|
) {
|
|
Stake storage stake = stakes[_stakeId];
|
|
return (
|
|
stake.staker,
|
|
stake.agentWallet,
|
|
stake.amount,
|
|
stake.lockPeriod,
|
|
stake.startTime,
|
|
stake.endTime,
|
|
stake.status,
|
|
stake.accumulatedRewards,
|
|
stake.currentAPY,
|
|
stake.agentTier,
|
|
stake.autoCompound
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @dev Gets agent metrics
|
|
* @param _agentWallet Agent wallet address
|
|
*/
|
|
function getAgentMetrics(address _agentWallet) external view returns (
|
|
uint256 totalStaked,
|
|
uint256 stakerCount,
|
|
uint256 totalRewardsDistributed,
|
|
uint256 averageAccuracy,
|
|
uint256 totalSubmissions,
|
|
uint256 successfulSubmissions,
|
|
PerformanceTier currentTier,
|
|
uint256 tierScore
|
|
) {
|
|
AgentMetrics storage metrics = agentMetrics[_agentWallet];
|
|
return (
|
|
metrics.totalStaked,
|
|
metrics.stakerCount,
|
|
metrics.totalRewardsDistributed,
|
|
metrics.averageAccuracy,
|
|
metrics.totalSubmissions,
|
|
metrics.successfulSubmissions,
|
|
metrics.currentTier,
|
|
metrics.tierScore
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @dev Gets staking pool information
|
|
* @param _agentWallet Agent wallet address
|
|
*/
|
|
function getStakingPool(address _agentWallet) external view returns (
|
|
uint256 totalStaked,
|
|
uint256 totalRewards,
|
|
uint256 poolAPY,
|
|
uint256 stakerCount
|
|
) {
|
|
StakingPool storage pool = stakingPools[_agentWallet];
|
|
return (
|
|
pool.totalStaked,
|
|
pool.totalRewards,
|
|
pool.poolAPY,
|
|
pool.stakers.length
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @dev Calculates current rewards for a stake
|
|
* @param _stakeId Stake ID
|
|
*/
|
|
function calculateRewards(uint256 _stakeId) external view stakeExists(_stakeId) returns (uint256) {
|
|
Stake storage stake = stakes[_stakeId];
|
|
if (stake.status != StakeStatus.ACTIVE) {
|
|
return stake.accumulatedRewards;
|
|
}
|
|
|
|
uint256 timeElapsed = block.timestamp - stake.lastRewardTime;
|
|
uint256 yearlyRewards = (stake.amount * stake.currentAPY) / 10000;
|
|
uint256 currentRewards = (yearlyRewards * timeElapsed) / 365 days;
|
|
|
|
return stake.accumulatedRewards + currentRewards;
|
|
}
|
|
|
|
/**
|
|
* @dev Gets all stakes for a staker
|
|
* @param _staker Staker address
|
|
*/
|
|
function getStakerStakes(address _staker) external view returns (uint256[] memory) {
|
|
return stakerStakes[_staker];
|
|
}
|
|
|
|
/**
|
|
* @dev Gets all stakes for an agent
|
|
* @param _agentWallet Agent wallet address
|
|
*/
|
|
function getAgentStakes(address _agentWallet) external view returns (uint256[] memory) {
|
|
return agentStakes[_agentWallet];
|
|
}
|
|
|
|
/**
|
|
* @dev Gets all supported agents
|
|
*/
|
|
function getSupportedAgents() external view returns (address[] memory) {
|
|
return supportedAgents;
|
|
}
|
|
|
|
/**
|
|
* @dev Gets all active stake IDs
|
|
*/
|
|
function getActiveStakes() external view returns (uint256[] memory) {
|
|
return activeStakeIds;
|
|
}
|
|
|
|
/**
|
|
* @dev Calculates APY for a stake
|
|
* @param _agentWallet Agent wallet address
|
|
* @param _lockPeriod Lock period
|
|
* @param _agentTier Agent performance tier
|
|
*/
|
|
function calculateAPY(
|
|
address _agentWallet,
|
|
uint256 _lockPeriod,
|
|
PerformanceTier _agentTier
|
|
) external view returns (uint256) {
|
|
return _calculateAPY(_agentWallet, _lockPeriod, _agentTier);
|
|
}
|
|
|
|
// Internal functions
|
|
|
|
function _calculateAPY(
|
|
address _agentWallet,
|
|
uint256 _lockPeriod,
|
|
PerformanceTier _agentTier
|
|
) internal view returns (uint256) {
|
|
uint256 tierMultiplier = tierMultipliers[_agentTier];
|
|
uint256 lockMultiplier = lockPeriodMultipliers[_lockPeriod];
|
|
|
|
uint256 apy = (baseAPY * tierMultiplier * lockMultiplier) / (10000 * 10000);
|
|
|
|
// Cap at maximum APY
|
|
return apy > maxAPY ? maxAPY : apy;
|
|
}
|
|
|
|
function _calculateRewards(uint256 _stakeId) internal {
|
|
Stake storage stake = stakes[_stakeId];
|
|
if (stake.status != StakeStatus.ACTIVE) {
|
|
return;
|
|
}
|
|
|
|
uint256 timeElapsed = block.timestamp - stake.lastRewardTime;
|
|
uint256 yearlyRewards = (stake.amount * stake.currentAPY) / 10000;
|
|
uint256 currentRewards = (yearlyRewards * timeElapsed) / 365 days;
|
|
|
|
stake.accumulatedRewards += currentRewards;
|
|
stake.lastRewardTime = block.timestamp;
|
|
|
|
// Auto-compound if enabled
|
|
if (stake.autoCompound && currentRewards >= minStakeAmount) {
|
|
stake.amount += currentRewards;
|
|
stake.accumulatedRewards = 0;
|
|
}
|
|
}
|
|
|
|
function _getAgentTier(address _agentWallet) internal view returns (PerformanceTier) {
|
|
AgentMetrics storage metrics = agentMetrics[_agentWallet];
|
|
return metrics.currentTier;
|
|
}
|
|
|
|
function _calculateAgentTier(address _agentWallet) internal view returns (PerformanceTier) {
|
|
AgentMetrics storage metrics = agentMetrics[_agentWallet];
|
|
|
|
uint256 successRate = metrics.totalSubmissions > 0 ?
|
|
(metrics.successfulSubmissions * 100) / metrics.totalSubmissions : 0;
|
|
|
|
uint256 score = (metrics.averageAccuracy * 50) / 100 + (successRate * 50) / 100;
|
|
|
|
if (score >= 95) return PerformanceTier.DIAMOND;
|
|
if (score >= 90) return PerformanceTier.PLATINUM;
|
|
if (score >= 80) return PerformanceTier.GOLD;
|
|
if (score >= 70) return PerformanceTier.SILVER;
|
|
return PerformanceTier.BRONZE;
|
|
}
|
|
|
|
function _getTierScore(PerformanceTier _tier) internal pure returns (uint256) {
|
|
if (_tier == PerformanceTier.DIAMOND) return 95;
|
|
if (_tier == PerformanceTier.PLATINUM) return 90;
|
|
if (_tier == PerformanceTier.GOLD) return 80;
|
|
if (_tier == PerformanceTier.SILVER) return 70;
|
|
return 60;
|
|
}
|
|
|
|
function _updateAgentMetrics(address _agentWallet, uint256 _amount, bool _isStake) internal {
|
|
AgentMetrics storage metrics = agentMetrics[_agentWallet];
|
|
|
|
if (_isStake) {
|
|
metrics.totalStaked += _amount;
|
|
if (metrics.totalStaked == _amount) {
|
|
metrics.stakerCount = 1;
|
|
}
|
|
} else {
|
|
metrics.totalStaked -= _amount;
|
|
if (metrics.totalStaked == 0) {
|
|
metrics.stakerCount = 0;
|
|
}
|
|
}
|
|
|
|
metrics.currentTier = _calculateAgentTier(_agentWallet);
|
|
metrics.tierScore = _getTierScore(metrics.currentTier);
|
|
}
|
|
|
|
function _updateStakingPool(address _agentWallet, address _staker, uint256 _amount, bool _isStake) internal {
|
|
StakingPool storage pool = stakingPools[_agentWallet];
|
|
|
|
if (_isStake) {
|
|
if (pool.stakerShares[_staker] == 0) {
|
|
pool.stakers.push(_staker);
|
|
}
|
|
pool.stakerShares[_staker] += _amount;
|
|
pool.totalStaked += _amount;
|
|
} else {
|
|
pool.stakerShares[_staker] -= _amount;
|
|
pool.totalStaked -= _amount;
|
|
|
|
// Remove staker from array if no shares left
|
|
if (pool.stakerShares[_staker] == 0) {
|
|
for (uint256 i = 0; i < pool.stakers.length; i++) {
|
|
if (pool.stakers[i] == _staker) {
|
|
pool.stakers[i] = pool.stakers[pool.stakers.length - 1];
|
|
pool.stakers.pop();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update pool APY
|
|
if (pool.totalStaked > 0) {
|
|
pool.poolAPY = _calculateAPY(_agentWallet, 30 days, agentMetrics[_agentWallet].currentTier);
|
|
}
|
|
}
|
|
|
|
function _removeFromActiveStakes(uint256 _stakeId) internal {
|
|
for (uint256 i = 0; i < activeStakeIds.length; i++) {
|
|
if (activeStakeIds[i] == _stakeId) {
|
|
activeStakeIds[i] = activeStakeIds[activeStakeIds.length - 1];
|
|
activeStakeIds.pop();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @dev Emergency pause function
|
|
*/
|
|
function pause() external onlyOwner {
|
|
_pause();
|
|
}
|
|
|
|
/**
|
|
* @dev Unpause function
|
|
*/
|
|
function unpause() external onlyOwner {
|
|
_unpause();
|
|
}
|
|
}
|