- 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
607 lines
19 KiB
Solidity
607 lines
19 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 "../interfaces/IModularContracts.sol";
|
|
import "./ContractRegistry.sol";
|
|
|
|
/**
|
|
* @title PerformanceAggregator
|
|
* @dev Cross-contract performance data aggregation with reputation scoring
|
|
* @notice Integrates with AgentStaking, AgentBounty, and PerformanceVerifier
|
|
*/
|
|
contract PerformanceAggregator is IPerformanceAggregator, Ownable, ReentrancyGuard, Pausable {
|
|
|
|
// State variables
|
|
uint256 public version = 1;
|
|
ContractRegistry public registry;
|
|
address public performanceVerifier;
|
|
address public agentBounty;
|
|
address public agentStaking;
|
|
|
|
// Performance metrics
|
|
struct PerformanceMetrics {
|
|
uint256 totalTasks;
|
|
uint256 completedTasks;
|
|
uint256 failedTasks;
|
|
uint256 averageAccuracy;
|
|
uint256 totalEarnings;
|
|
uint256 lastUpdated;
|
|
uint256 reputationScore;
|
|
uint256 performanceTier; // 0-5 performance tiers
|
|
}
|
|
|
|
// Performance history
|
|
struct PerformanceRecord {
|
|
uint256 recordId;
|
|
address agent;
|
|
uint256 score;
|
|
uint256 accuracy;
|
|
uint256 earnings;
|
|
uint256 timestamp;
|
|
string taskType;
|
|
bool isPositive;
|
|
}
|
|
|
|
// Performance tier thresholds
|
|
struct PerformanceTier {
|
|
uint256 minScore;
|
|
uint256 maxScore;
|
|
uint256 apyMultiplier; // In basis points (10000 = 1x)
|
|
string name;
|
|
}
|
|
|
|
// Mappings
|
|
mapping(address => PerformanceMetrics) public agentMetrics;
|
|
mapping(uint256 => PerformanceRecord) public performanceRecords;
|
|
mapping(address => uint256[]) public agentRecords;
|
|
mapping(uint256 => address) public recordToAgent;
|
|
mapping(uint256 => PerformanceTier) public performanceTiers;
|
|
|
|
// Counters
|
|
uint256 public recordCounter;
|
|
uint256 public tierCounter;
|
|
uint256[] public recordIds;
|
|
|
|
// Constants
|
|
uint256 public constant INITIAL_REPUTATION = 5000; // 50% initial reputation
|
|
uint256 public constant MAX_REPUTATION = 10000; // 100% max reputation
|
|
uint256 public constant MIN_REPUTATION = 0; // 0% min reputation
|
|
uint256 public constant REPUTATION_DECAY_RATE = 100; // 1% decay per day
|
|
uint256 public constant DECAY_INTERVAL = 1 days;
|
|
uint256 public constant MAX_HISTORY_RECORDS = 1000; // Max records per agent
|
|
|
|
// Events
|
|
event PerformanceUpdated(address indexed agent, uint256 score, uint256 reputation);
|
|
event ReputationCalculated(address indexed agent, uint256 reputation, uint256 tier);
|
|
event PerformanceRecordAdded(uint256 indexed recordId, address indexed agent, uint256 score);
|
|
event PerformanceTierUpdated(uint256 indexed tier, uint256 minScore, uint256 maxScore, uint256 multiplier);
|
|
event AgentTierChanged(address indexed agent, uint256 oldTier, uint256 newTier);
|
|
event BatchPerformanceUpdated(address[] agents, uint256[] scores);
|
|
|
|
// Errors
|
|
error InvalidScore(uint256 score);
|
|
error InvalidAgent(address agent);
|
|
error InvalidAccuracy(uint256 accuracy);
|
|
error RecordNotFound(uint256 recordId);
|
|
error MaxHistoryReached(address agent);
|
|
error RegistryNotSet();
|
|
error NotAuthorized();
|
|
|
|
modifier validScore(uint256 score) {
|
|
if (score > MAX_REPUTATION) revert InvalidScore(score);
|
|
_;
|
|
}
|
|
|
|
modifier validAgent(address agent) {
|
|
if (agent == address(0)) revert InvalidAgent(agent);
|
|
_;
|
|
}
|
|
|
|
modifier validAccuracy(uint256 accuracy) {
|
|
if (accuracy > 10000) revert InvalidAccuracy(accuracy); // Accuracy in basis points
|
|
_;
|
|
}
|
|
|
|
modifier onlyAuthorized() {
|
|
if (msg.sender != owner() && msg.sender != performanceVerifier && msg.sender != agentBounty && msg.sender != agentStaking) {
|
|
revert NotAuthorized();
|
|
}
|
|
_;
|
|
}
|
|
|
|
modifier registrySet() {
|
|
if (address(registry) == address(0)) revert RegistryNotSet();
|
|
_;
|
|
}
|
|
|
|
constructor() {
|
|
// Initialize default performance tiers
|
|
_initializeDefaultTiers();
|
|
}
|
|
|
|
/**
|
|
* @dev Initialize the performance aggregator (implements IModularContract)
|
|
*/
|
|
function initialize(address _registry) external override {
|
|
require(address(registry) == address(0), "Already initialized");
|
|
registry = ContractRegistry(_registry);
|
|
|
|
// Register this contract
|
|
bytes32 contractId = keccak256(abi.encodePacked("PerformanceAggregator"));
|
|
registry.registerContract(contractId, address(this));
|
|
|
|
// Get integration addresses from registry
|
|
performanceVerifier = registry.getContract(keccak256(abi.encodePacked("PerformanceVerifier")));
|
|
agentBounty = registry.getContract(keccak256(abi.encodePacked("AgentBounty")));
|
|
agentStaking = registry.getContract(keccak256(abi.encodePacked("AgentStaking")));
|
|
}
|
|
|
|
/**
|
|
* @dev Upgrade the contract
|
|
*/
|
|
function upgrade(address newImplementation) external override onlyOwner {
|
|
version++;
|
|
// Implementation upgrade logic would go here
|
|
}
|
|
|
|
/**
|
|
* @dev Pause the contract
|
|
*/
|
|
function pause() external override onlyOwner {
|
|
_pause();
|
|
}
|
|
|
|
/**
|
|
* @dev Unpause the contract
|
|
*/
|
|
function unpause() external override onlyOwner {
|
|
_unpause();
|
|
}
|
|
|
|
/**
|
|
* @dev Get current version
|
|
*/
|
|
function getVersion() external view override returns (uint256) {
|
|
return version;
|
|
}
|
|
|
|
/**
|
|
* @dev Update agent performance
|
|
*/
|
|
function updateAgentPerformance(address agent, uint256 score)
|
|
external
|
|
override
|
|
onlyAuthorized
|
|
whenNotPaused
|
|
validAgent(agent)
|
|
validScore(score)
|
|
nonReentrant
|
|
{
|
|
_updatePerformance(agent, score, 0, 0, "general", true);
|
|
}
|
|
|
|
/**
|
|
* @dev Update agent performance with detailed metrics
|
|
*/
|
|
function updateAgentPerformanceDetailed(
|
|
address agent,
|
|
uint256 score,
|
|
uint256 accuracy,
|
|
uint256 earnings,
|
|
string memory taskType,
|
|
bool isPositive
|
|
)
|
|
external
|
|
onlyAuthorized
|
|
whenNotPaused
|
|
validAgent(agent)
|
|
validScore(score)
|
|
validAccuracy(accuracy)
|
|
nonReentrant
|
|
{
|
|
_updatePerformance(agent, score, accuracy, earnings, taskType, isPositive);
|
|
}
|
|
|
|
/**
|
|
* @dev Batch update agent performance
|
|
*/
|
|
function batchUpdatePerformance(address[] memory agents, uint256[] memory scores)
|
|
external
|
|
onlyAuthorized
|
|
whenNotPaused
|
|
nonReentrant
|
|
{
|
|
require(agents.length == scores.length, "Array length mismatch");
|
|
|
|
for (uint256 i = 0; i < agents.length; i++) {
|
|
if (agents[i] != address(0) && scores[i] <= MAX_REPUTATION) {
|
|
_updatePerformance(agents[i], scores[i], 0, 0, "batch", true);
|
|
}
|
|
}
|
|
|
|
emit BatchPerformanceUpdated(agents, scores);
|
|
}
|
|
|
|
/**
|
|
* @dev Internal performance update logic
|
|
*/
|
|
function _updatePerformance(
|
|
address agent,
|
|
uint256 score,
|
|
uint256 accuracy,
|
|
uint256 earnings,
|
|
string memory taskType,
|
|
bool isPositive
|
|
) internal {
|
|
PerformanceMetrics storage metrics = agentMetrics[agent];
|
|
|
|
// Initialize if new agent
|
|
if (metrics.lastUpdated == 0) {
|
|
metrics.totalTasks = 0;
|
|
metrics.completedTasks = 0;
|
|
metrics.failedTasks = 0;
|
|
metrics.averageAccuracy = 0;
|
|
metrics.totalEarnings = 0;
|
|
metrics.reputationScore = INITIAL_REPUTATION;
|
|
metrics.performanceTier = 0;
|
|
}
|
|
|
|
// Update metrics
|
|
metrics.totalTasks++;
|
|
if (isPositive) {
|
|
metrics.completedTasks++;
|
|
} else {
|
|
metrics.failedTasks++;
|
|
}
|
|
|
|
// Update average accuracy
|
|
if (accuracy > 0) {
|
|
metrics.averageAccuracy = (metrics.averageAccuracy * (metrics.totalTasks - 1) + accuracy) / metrics.totalTasks;
|
|
}
|
|
|
|
// Update earnings
|
|
if (earnings > 0) {
|
|
metrics.totalEarnings += earnings;
|
|
}
|
|
|
|
// Calculate new reputation score
|
|
uint256 newReputation = _calculateReputation(metrics, score);
|
|
uint256 oldTier = metrics.performanceTier;
|
|
uint256 newTier = _getPerformanceTier(newReputation);
|
|
|
|
// Update reputation and tier
|
|
metrics.reputationScore = newReputation;
|
|
metrics.performanceTier = newTier;
|
|
metrics.lastUpdated = block.timestamp;
|
|
|
|
// Add performance record
|
|
uint256 recordId = ++recordCounter;
|
|
performanceRecords[recordId] = PerformanceRecord({
|
|
recordId: recordId,
|
|
agent: agent,
|
|
score: score,
|
|
accuracy: accuracy,
|
|
earnings: earnings,
|
|
timestamp: block.timestamp,
|
|
taskType: taskType,
|
|
isPositive: isPositive
|
|
});
|
|
|
|
agentRecords[agent].push(recordId);
|
|
recordToAgent[recordId] = agent;
|
|
recordIds.push(recordId);
|
|
|
|
// Limit history size
|
|
if (agentRecords[agent].length > MAX_HISTORY_RECORDS) {
|
|
uint256 oldRecordId = agentRecords[agent][0];
|
|
delete performanceRecords[oldRecordId];
|
|
delete recordToAgent[oldRecordId];
|
|
|
|
// Shift array
|
|
for (uint256 i = 0; i < agentRecords[agent].length - 1; i++) {
|
|
agentRecords[agent][i] = agentRecords[agent][i + 1];
|
|
}
|
|
agentRecords[agent].pop();
|
|
}
|
|
|
|
emit PerformanceUpdated(agent, score, newReputation);
|
|
emit ReputationCalculated(agent, newReputation, newTier);
|
|
emit PerformanceRecordAdded(recordId, agent, score);
|
|
|
|
if (oldTier != newTier) {
|
|
emit AgentTierChanged(agent, oldTier, newTier);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @dev Calculate reputation score based on metrics
|
|
*/
|
|
function _calculateReputation(PerformanceMetrics memory metrics, uint256 currentScore) internal view returns (uint256) {
|
|
// Base score from current performance
|
|
uint256 baseScore = currentScore;
|
|
|
|
// Adjust based on task completion rate
|
|
uint256 completionRate = metrics.totalTasks > 0 ?
|
|
(metrics.completedTasks * 10000) / metrics.totalTasks : 0;
|
|
|
|
// Adjust based on average accuracy
|
|
uint256 accuracyBonus = metrics.averageAccuracy > 0 ?
|
|
(metrics.averageAccuracy * 2000) / 10000 : 0; // Max 20% bonus
|
|
|
|
// Adjust based on earnings (logarithmic scaling)
|
|
uint256 earningsBonus = 0;
|
|
if (metrics.totalEarnings > 0) {
|
|
// Logarithmic scaling: every 10x increase in earnings adds 10% bonus
|
|
earningsBonus = (1000 * log10(metrics.totalEarnings)) / 10000;
|
|
}
|
|
|
|
// Apply time decay
|
|
uint256 timeSinceLastUpdate = block.timestamp - metrics.lastUpdated;
|
|
uint256 decayAmount = (timeSinceLastUpdate * REPUTATION_DECAY_RATE) / DECAY_INTERVAL;
|
|
|
|
// Calculate final reputation
|
|
uint256 finalScore = baseScore + completionRate + accuracyBonus + earningsBonus - decayAmount;
|
|
|
|
// Clamp to valid range
|
|
if (finalScore > MAX_REPUTATION) {
|
|
finalScore = MAX_REPUTATION;
|
|
} else if (finalScore < MIN_REPUTATION) {
|
|
finalScore = MIN_REPUTATION;
|
|
}
|
|
|
|
return finalScore;
|
|
}
|
|
|
|
/**
|
|
* @dev Simple logarithm approximation for earnings bonus
|
|
*/
|
|
function log10(uint256 value) internal pure returns (uint256) {
|
|
if (value == 0) return 0;
|
|
if (value < 10) return 0;
|
|
if (value < 100) return 1;
|
|
if (value < 1000) return 2;
|
|
if (value < 10000) return 3;
|
|
if (value < 100000) return 4;
|
|
if (value < 1000000) return 5;
|
|
if (value < 10000000) return 6;
|
|
if (value < 100000000) return 7;
|
|
if (value < 1000000000) return 8;
|
|
if (value < 10000000000) return 9;
|
|
return 10;
|
|
}
|
|
|
|
/**
|
|
* @dev Get performance tier for reputation score
|
|
*/
|
|
function _getPerformanceTier(uint256 reputation) internal view returns (uint256) {
|
|
for (uint256 i = tierCounter; i > 0; i--) {
|
|
PerformanceTier memory tier = performanceTiers[i];
|
|
if (reputation >= tier.minScore && reputation <= tier.maxScore) {
|
|
return i;
|
|
}
|
|
}
|
|
return 0; // Default tier
|
|
}
|
|
|
|
/**
|
|
* @dev Get reputation score for agent
|
|
*/
|
|
function getReputationScore(address agent) external view override returns (uint256) {
|
|
return agentMetrics[agent].reputationScore;
|
|
}
|
|
|
|
/**
|
|
* @dev Calculate APY multiplier for reputation
|
|
*/
|
|
function calculateAPYMultiplier(uint256 reputation) external view override returns (uint256) {
|
|
uint256 tier = _getPerformanceTier(reputation);
|
|
if (tier > 0) {
|
|
return performanceTiers[tier].apyMultiplier;
|
|
}
|
|
return 10000; // 1x default multiplier
|
|
}
|
|
|
|
/**
|
|
* @dev Get performance history for agent
|
|
*/
|
|
function getPerformanceHistory(address agent) external view override returns (uint256[] memory) {
|
|
return agentRecords[agent];
|
|
}
|
|
|
|
/**
|
|
* @dev Get detailed performance metrics for agent
|
|
*/
|
|
function getAgentMetrics(address agent) external view returns (
|
|
uint256 totalTasks,
|
|
uint256 completedTasks,
|
|
uint256 failedTasks,
|
|
uint256 averageAccuracy,
|
|
uint256 totalEarnings,
|
|
uint256 reputationScore,
|
|
uint256 performanceTier,
|
|
uint256 lastUpdated
|
|
) {
|
|
PerformanceMetrics memory metrics = agentMetrics[agent];
|
|
return (
|
|
metrics.totalTasks,
|
|
metrics.completedTasks,
|
|
metrics.failedTasks,
|
|
metrics.averageAccuracy,
|
|
metrics.totalEarnings,
|
|
metrics.reputationScore,
|
|
metrics.performanceTier,
|
|
metrics.lastUpdated
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @dev Get performance record details
|
|
*/
|
|
function getPerformanceRecord(uint256 recordId) external view returns (
|
|
address agent,
|
|
uint256 score,
|
|
uint256 accuracy,
|
|
uint256 earnings,
|
|
uint256 timestamp,
|
|
string memory taskType,
|
|
bool isPositive
|
|
) {
|
|
PerformanceRecord memory record = performanceRecords[recordId];
|
|
return (
|
|
record.agent,
|
|
record.score,
|
|
record.accuracy,
|
|
record.earnings,
|
|
record.timestamp,
|
|
record.taskType,
|
|
record.isPositive
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @dev Get performance tier details
|
|
*/
|
|
function getPerformanceTier(uint256 tierId) external view returns (
|
|
uint256 minScore,
|
|
uint256 maxScore,
|
|
uint256 apyMultiplier,
|
|
string memory name
|
|
) {
|
|
PerformanceTier memory tier = performanceTiers[tierId];
|
|
return (
|
|
tier.minScore,
|
|
tier.maxScore,
|
|
tier.apyMultiplier,
|
|
tier.name
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @dev Get all performance tiers
|
|
*/
|
|
function getAllPerformanceTiers() external view returns (uint256[] memory) {
|
|
uint256[] memory tierIds = new uint256[](tierCounter);
|
|
for (uint256 i = 1; i <= tierCounter; i++) {
|
|
tierIds[i - 1] = i;
|
|
}
|
|
return tierIds;
|
|
}
|
|
|
|
/**
|
|
* @dev Update performance tier
|
|
*/
|
|
function updatePerformanceTier(
|
|
uint256 tierId,
|
|
uint256 minScore,
|
|
uint256 maxScore,
|
|
uint256 apyMultiplier,
|
|
string memory name
|
|
) external onlyOwner {
|
|
require(tierId > 0 && tierId <= tierCounter, "Invalid tier ID");
|
|
|
|
performanceTiers[tierId] = PerformanceTier({
|
|
minScore: minScore,
|
|
maxScore: maxScore,
|
|
apyMultiplier: apyMultiplier,
|
|
name: name
|
|
});
|
|
|
|
emit PerformanceTierUpdated(tierId, minScore, maxScore, apyMultiplier);
|
|
}
|
|
|
|
/**
|
|
* @dev Add new performance tier
|
|
*/
|
|
function addPerformanceTier(
|
|
uint256 minScore,
|
|
uint256 maxScore,
|
|
uint256 apyMultiplier,
|
|
string memory name
|
|
) external onlyOwner {
|
|
require(minScore <= maxScore, "Invalid score range");
|
|
require(apyMultiplier <= 50000, "Multiplier too high"); // Max 5x
|
|
|
|
tierCounter++;
|
|
performanceTiers[tierCounter] = PerformanceTier({
|
|
minScore: minScore,
|
|
maxScore: maxScore,
|
|
apyMultiplier: apyMultiplier,
|
|
name: name
|
|
});
|
|
|
|
emit PerformanceTierUpdated(tierCounter, minScore, maxScore, apyMultiplier);
|
|
}
|
|
|
|
/**
|
|
* @dev Initialize default performance tiers
|
|
*/
|
|
function _initializeDefaultTiers() internal {
|
|
tierCounter++;
|
|
performanceTiers[tierCounter] = PerformanceTier({
|
|
minScore: 0,
|
|
maxScore: 2000,
|
|
apyMultiplier: 8000, // 0.8x
|
|
name: "Bronze"
|
|
});
|
|
|
|
tierCounter++;
|
|
performanceTiers[tierCounter] = PerformanceTier({
|
|
minScore: 2001,
|
|
maxScore: 4000,
|
|
apyMultiplier: 10000, // 1x
|
|
name: "Silver"
|
|
});
|
|
|
|
tierCounter++;
|
|
performanceTiers[tierCounter] = PerformanceTier({
|
|
minScore: 4001,
|
|
maxScore: 6000,
|
|
apyMultiplier: 12000, // 1.2x
|
|
name: "Gold"
|
|
});
|
|
|
|
tierCounter++;
|
|
performanceTiers[tierCounter] = PerformanceTier({
|
|
minScore: 6001,
|
|
maxScore: 8000,
|
|
apyMultiplier: 15000, // 1.5x
|
|
name: "Platinum"
|
|
});
|
|
|
|
tierCounter++;
|
|
performanceTiers[tierCounter] = PerformanceTier({
|
|
minScore: 8001,
|
|
maxScore: 10000,
|
|
apyMultiplier: 20000, // 2x
|
|
name: "Diamond"
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @dev Get aggregator statistics
|
|
*/
|
|
function getAggregatorStats() external view returns (
|
|
uint256 totalAgents,
|
|
uint256 totalRecords,
|
|
uint256 averageReputation,
|
|
uint256 activeAgents
|
|
) {
|
|
uint256 _totalAgents = 0;
|
|
uint256 _totalReputation = 0;
|
|
uint256 _activeAgents = 0;
|
|
|
|
// This would require iterating through all agents, which is gas-intensive
|
|
// For production, consider using a different approach or off-chain calculation
|
|
|
|
return (
|
|
_totalAgents,
|
|
recordCounter,
|
|
_totalReputation,
|
|
_activeAgents
|
|
);
|
|
}
|
|
}
|