docs(plan): update milestone planning with phase 4 focus and success metrics

- Update priority areas from "100% COMPLETE" to "Next Priority Areas" with phase 4 focus
- Mark Smart Contract Development as 🔄 NEXT and Advanced AI Features as 🔄 FUTURE
- Restructure development timeline with Q2 2026 marked as COMPLETED, Q3 2026 as CURRENT PHASE
- Add Q4 2026 future planning section with weeks 25-36 roadmap
- Reorganize next development steps into completed and future sections
- Add comprehensive success metrics and
This commit is contained in:
oib
2026-03-01 00:06:33 +01:00
parent 768a0cf8fd
commit 94b9bbc7f0
17 changed files with 5669 additions and 18 deletions

View File

@@ -0,0 +1,270 @@
// 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";
/**
* @title ContractRegistry
* @dev Central registry for all modular puzzle pieces
* @notice Enables seamless inter-contract communication and supports upgrades
*/
contract ContractRegistry is IContractRegistry, Ownable, ReentrancyGuard, Pausable {
// State variables
uint256 public version = 1;
mapping(bytes32 => address) public contractAddresses;
mapping(bytes32 => uint256) public contractVersions;
mapping(address => bytes32) public addressToId;
bytes32[] public contractIds;
// Events
event ContractRegistered(bytes32 indexed contractId, address indexed contractAddress, uint256 version);
event ContractUpdated(bytes32 indexed contractId, address indexed oldAddress, address indexed newAddress);
event ContractDeregistered(bytes32 indexed contractId, address indexed contractAddress);
event RegistryPaused(address indexed pausedBy);
event RegistryUnpaused(address indexed unpausedBy);
// Errors
error ContractAlreadyRegistered(bytes32 contractId);
error ContractNotFound(bytes32 contractId);
error InvalidAddress(address contractAddress);
error RegistryPausedError();
error NotAuthorized();
modifier whenNotPausedRegistry() {
if (paused()) revert RegistryPausedError();
_;
}
modifier validAddress(address contractAddress) {
if (contractAddress == address(0)) revert InvalidAddress(contractAddress);
_;
}
modifier onlyAuthorized() {
if (msg.sender != owner() && !isContract(msg.sender)) revert NotAuthorized();
_;
}
constructor() {
// Register the registry itself
bytes32 registryId = keccak256(abi.encodePacked("ContractRegistry"));
contractAddresses[registryId] = address(this);
contractVersions[registryId] = version;
addressToId[address(this)] = registryId;
contractIds.push(registryId);
emit ContractRegistered(registryId, address(this), version);
}
/**
* @dev Initialize the registry (implements IModularContract)
*/
function initialize(address /*registry*/) external pure override {
// Registry doesn't need external initialization
revert("Self-initialization not allowed");
}
/**
* @dev Upgrade the registry version
*/
function upgrade(address /*newImplementation*/) external override onlyOwner {
version++;
emit ContractUpdated(keccak256(abi.encodePacked("ContractRegistry")), address(this), address(this));
}
/**
* @dev Pause the registry
*/
function pause() external override onlyOwner {
_pause();
emit RegistryPaused(msg.sender);
}
/**
* @dev Unpause the registry
*/
function unpause() external override onlyOwner {
_unpause();
emit RegistryUnpaused(msg.sender);
}
/**
* @dev Get the current version
*/
function getVersion() external view override returns (uint256) {
return version;
}
/**
* @dev Register a new contract
*/
function registerContract(bytes32 contractId, address contractAddress)
external
override
onlyAuthorized
whenNotPausedRegistry
validAddress(contractAddress)
nonReentrant
{
if (contractAddresses[contractId] != address(0)) {
revert ContractAlreadyRegistered(contractId);
}
contractAddresses[contractId] = contractAddress;
contractVersions[contractId] = 1;
addressToId[contractAddress] = contractId;
contractIds.push(contractId);
emit ContractRegistered(contractId, contractAddress, 1);
}
/**
* @dev Get a contract address by ID
*/
function getContract(bytes32 contractId) external view override returns (address) {
address contractAddress = contractAddresses[contractId];
if (contractAddress == address(0)) {
revert ContractNotFound(contractId);
}
return contractAddress;
}
/**
* @dev Update an existing contract address
*/
function updateContract(bytes32 contractId, address newAddress)
external
override
onlyAuthorized
whenNotPausedRegistry
validAddress(newAddress)
nonReentrant
{
address oldAddress = contractAddresses[contractId];
if (oldAddress == address(0)) {
revert ContractNotFound(contractId);
}
contractAddresses[contractId] = newAddress;
contractVersions[contractId]++;
delete addressToId[oldAddress];
addressToId[newAddress] = contractId;
emit ContractUpdated(contractId, oldAddress, newAddress);
}
/**
* @dev Deregister a contract
*/
function deregisterContract(bytes32 contractId) external onlyAuthorized whenNotPausedRegistry nonReentrant {
address contractAddress = contractAddresses[contractId];
if (contractAddress == address(0)) {
revert ContractNotFound(contractId);
}
delete contractAddresses[contractId];
delete contractVersions[contractId];
delete addressToId[contractAddress];
// Remove from contractIds array
for (uint256 i = 0; i < contractIds.length; i++) {
if (contractIds[i] == contractId) {
contractIds[i] = contractIds[contractIds.length - 1];
contractIds.pop();
break;
}
}
emit ContractDeregistered(contractId, contractAddress);
}
/**
* @dev List all registered contracts
*/
function listContracts() external view override returns (bytes32[] memory, address[] memory) {
bytes32[] memory ids = new bytes32[](contractIds.length);
address[] memory addresses = new address[](contractIds.length);
for (uint256 i = 0; i < contractIds.length; i++) {
ids[i] = contractIds[i];
addresses[i] = contractAddresses[contractIds[i]];
}
return (ids, addresses);
}
/**
* @dev Get contract version
*/
function getContractVersion(bytes32 contractId) external view returns (uint256) {
return contractVersions[contractId];
}
/**
* @dev Check if an address is a registered contract
*/
function isRegisteredContract(address contractAddress) external view returns (bool) {
bytes32 contractId = addressToId[contractAddress];
return contractAddresses[contractId] != address(0);
}
/**
* @dev Get contract ID by address
*/
function getContractId(address contractAddress) external view returns (bytes32) {
return addressToId[contractAddress];
}
/**
* @dev Batch register contracts
*/
function batchRegisterContracts(bytes32[] memory _contractIds, address[] memory _contractAddresses)
external
onlyAuthorized
whenNotPausedRegistry
{
require(_contractIds.length == _contractAddresses.length, "Array length mismatch");
for (uint256 i = 0; i < _contractIds.length; i++) {
if (_contractAddresses[i] != address(0) && contractAddresses[_contractIds[i]] == address(0)) {
contractAddresses[_contractIds[i]] = _contractAddresses[i];
contractVersions[_contractIds[i]] = 1;
addressToId[_contractAddresses[i]] = _contractIds[i];
emit ContractRegistered(_contractIds[i], _contractAddresses[i], 1);
}
}
}
/**
* @dev Check if address is a contract
*/
function isContract(address addr) internal view returns (bool) {
uint256 size;
assembly {
size := extcodesize(addr)
}
return size > 0;
}
/**
* @dev Get registry statistics
*/
function getRegistryStats() external view returns (
uint256 totalContracts,
uint256 totalVersion,
bool isPaused,
address owner
) {
return (
contractIds.length,
version,
paused(),
this.owner()
);
}
}

View File

@@ -0,0 +1,514 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../interfaces/IModularContracts.sol";
import "./ContractRegistry.sol";
/**
* @title DAOGovernanceEnhanced
* @dev Enhanced multi-jurisdictional DAO framework with modular integrations
* @notice Integrates with TreasuryManager, CrossChainGovernance, and PerformanceAggregator
*/
contract DAOGovernanceEnhanced is IModularContract, Ownable, ReentrancyGuard {
using SafeERC20 for IERC20;
// State variables
uint256 public version = 2; // Enhanced version
IERC20 public governanceToken;
ContractRegistry public registry;
ITreasuryManager public treasuryManager;
ICrossChainGovernance public crossChainGovernance;
IPerformanceAggregator public performanceAggregator;
// Staking Parameters
uint256 public minStakeAmount;
uint256 public unbondingPeriod = 7 days;
// Enhanced Staker struct
struct Staker {
uint256 amount;
uint256 unbondingAmount;
uint256 unbondingCompleteTime;
uint256 lastStakeTime;
uint256 reputationScore;
uint256 votingPower;
bool isActive;
}
// Enhanced Proposal struct
enum ProposalState { Pending, Active, Canceled, Defeated, Succeeded, Queued, Expired, Executed }
enum ProposalType { TREASURY_ALLOCATION, PARAMETER_CHANGE, CROSS_CHAIN, REWARD_DISTRIBUTION }
struct Proposal {
uint256 id;
address proposer;
string region; // "" for global
string descriptionHash;
uint256 forVotes;
uint256 againstVotes;
uint256 abstainVotes;
uint256 startTime;
uint256 endTime;
bool executed;
bool canceled;
ProposalState state;
ProposalType proposalType;
address targetContract;
bytes callData;
uint256 value;
mapping(address => bool) hasVoted;
mapping(address => uint8) voteType; // 0=against, 1=for, 2=abstain
}
// Cross-chain proposal
struct CrossChainProposal {
uint256 sourceChainId;
bytes32 proposalHash;
uint256 localProposalId;
bool isValidated;
uint256 validationTime;
address validator;
bytes32 validationProof;
}
// Mappings
mapping(address => Staker) public stakers;
mapping(uint256 => Proposal) public proposals;
mapping(string => mapping(address => bool)) public isRegionalCouncilMember;
mapping(string => address[]) public regionalCouncilMembers;
mapping(uint256 => CrossChainProposal) public crossChainProposals;
mapping(uint256 => uint256) public proposalToCrossChain;
// Counters
uint256 public proposalCount;
uint256 public totalStaked;
uint256[] public activeProposalIds;
// Events
event Staked(address indexed user, uint256 amount);
event Unstaked(address indexed user, uint256 amount);
event ProposalCreated(uint256 indexed id, address proposer, string region, ProposalType proposalType);
event VoteCast(address indexed voter, uint256 indexed proposalId, uint8 voteType, uint256 weight);
event ProposalExecuted(uint256 indexed id);
event CrossChainProposalSubmitted(uint256 indexed localId, uint256 sourceChainId, bytes32 proposalHash);
event ReputationUpdated(address indexed staker, uint256 newReputation);
event VotingPowerUpdated(address indexed staker, uint256 newVotingPower);
// Errors
error InvalidAmount(uint256 amount);
error InsufficientStake(uint256 required, uint256 available);
error ProposalNotFound(uint256 proposalId);
error ProposalNotActive(uint256 proposalId);
error AlreadyVoted(uint256 proposalId, address voter);
error InvalidVoteType(uint8 voteType);
error NotCouncilMember(string region, address member);
error RegistryNotSet();
error CrossChainValidationFailed(uint256 proposalId);
modifier validAmount(uint256 amount) {
if (amount == 0) revert InvalidAmount(amount);
_;
}
modifier validProposal(uint256 proposalId) {
if (proposals[proposalId].id == 0) revert ProposalNotFound(proposalId);
_;
}
modifier onlyActiveStaker() {
if (stakers[msg.sender].amount < minStakeAmount) revert InsufficientStake(minStakeAmount, stakers[msg.sender].amount);
_;
}
modifier onlyCouncilMember(string memory region) {
if (bytes(region).length > 0 && !isRegionalCouncilMember[region][msg.sender]) {
revert NotCouncilMember(region, msg.sender);
}
_;
}
modifier registrySet() {
if (address(registry) == address(0)) revert RegistryNotSet();
_;
}
constructor(address _governanceToken, uint256 _minStakeAmount) {
governanceToken = IERC20(_governanceToken);
minStakeAmount = _minStakeAmount;
}
/**
* @dev Initialize the enhanced DAO governance (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("DAOGovernanceEnhanced"));
registry.registerContract(contractId, address(this));
// Get integration addresses from registry
treasuryManager = ITreasuryManager(registry.getContract(keccak256(abi.encodePacked("TreasuryManager"))));
crossChainGovernance = ICrossChainGovernance(registry.getContract(keccak256(abi.encodePacked("CrossChainGovernance"))));
performanceAggregator = IPerformanceAggregator(registry.getContract(keccak256(abi.encodePacked("PerformanceAggregator"))));
}
/**
* @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 {
// Implementation would use Pausable mixin
}
/**
* @dev Unpause the contract
*/
function unpause() external override onlyOwner {
// Implementation would use Pausable mixin
}
/**
* @dev Get current version
*/
function getVersion() external view override returns (uint256) {
return version;
}
// --- Enhanced Staking ---
function stake(uint256 _amount) external nonReentrant validAmount(_amount) {
governanceToken.safeTransferFrom(msg.sender, address(this), _amount);
Staker storage staker = stakers[msg.sender];
staker.amount += _amount;
staker.lastStakeTime = block.timestamp;
staker.isActive = true;
totalStaked += _amount;
// Update voting power based on reputation
_updateVotingPower(msg.sender);
require(staker.amount >= minStakeAmount, "Below min stake");
emit Staked(msg.sender, _amount);
}
function initiateUnstake(uint256 _amount) external nonReentrant {
Staker storage staker = stakers[msg.sender];
require(_amount > 0 && staker.amount >= _amount, "Invalid amount");
require(staker.unbondingAmount == 0, "Unbonding already in progress");
staker.amount -= _amount;
staker.unbondingAmount = _amount;
staker.unbondingCompleteTime = block.timestamp + unbondingPeriod;
totalStaked -= _amount;
// Update voting power
_updateVotingPower(msg.sender);
}
function completeUnstake() external nonReentrant {
Staker storage staker = stakers[msg.sender];
require(staker.unbondingAmount > 0, "Nothing to unstake");
require(block.timestamp >= staker.unbondingCompleteTime, "Unbonding not complete");
uint256 amount = staker.unbondingAmount;
staker.unbondingAmount = 0;
governanceToken.safeTransfer(msg.sender, amount);
emit Unstaked(msg.sender, amount);
}
// --- Enhanced Proposals & Voting ---
function createProposal(
string calldata _region,
string calldata _descriptionHash,
uint256 _votingPeriod,
ProposalType _proposalType,
address _targetContract,
bytes calldata _callData,
uint256 _value
) external onlyActiveStaker onlyCouncilMember(_region) nonReentrant returns (uint256) {
proposalCount++;
Proposal storage p = proposals[proposalCount];
p.id = proposalCount;
p.proposer = msg.sender;
p.region = _region;
p.descriptionHash = _descriptionHash;
p.startTime = block.timestamp;
p.endTime = block.timestamp + _votingPeriod;
p.state = ProposalState.Active;
p.proposalType = _proposalType;
p.targetContract = _targetContract;
p.callData = _callData;
p.value = _value;
activeProposalIds.push(proposalCount);
emit ProposalCreated(p.id, msg.sender, _region, _proposalType);
return p.id;
}
function castVote(uint256 _proposalId, uint8 _voteType) external validProposal(_proposalId) nonReentrant {
Proposal storage p = proposals[_proposalId];
require(block.timestamp >= p.startTime && block.timestamp <= p.endTime, "Voting closed");
require(!p.hasVoted[msg.sender], "Already voted");
require(_voteType <= 2, "Invalid vote type");
uint256 weight = _calculateVotingWeight(msg.sender, p.region);
require(weight > 0, "No voting weight");
p.hasVoted[msg.sender] = true;
p.voteType[msg.sender] = _voteType;
if (_voteType == 0) { // Against
p.againstVotes += weight;
} else if (_voteType == 1) { // For
p.forVotes += weight;
} else { // Abstain
p.abstainVotes += weight;
}
emit VoteCast(msg.sender, _proposalId, _voteType, weight);
}
function executeProposal(uint256 _proposalId) external validProposal(_proposalId) nonReentrant {
Proposal storage p = proposals[_proposalId];
require(block.timestamp > p.endTime, "Voting not ended");
require(p.state == ProposalState.Active, "Invalid proposal state");
// Check if proposal passed
uint256 totalVotes = p.forVotes + p.againstVotes + p.abstainVotes;
bool passed = p.forVotes > p.againstVotes && totalVotes > 0;
if (passed) {
p.state = ProposalState.Succeeded;
_executeProposalAction(_proposalId);
p.state = ProposalState.Executed;
} else {
p.state = ProposalState.Defeated;
}
// Remove from active proposals
_removeFromActiveProposals(_proposalId);
emit ProposalExecuted(_proposalId);
}
// --- Cross-Chain Integration ---
function submitCrossChainProposal(
uint256 _sourceChainId,
bytes32 _proposalHash,
string calldata _descriptionHash
) external onlyActiveStaker nonReentrant returns (uint256) {
// Create local proposal for cross-chain validation
bytes memory callData = abi.encodeWithSignature("validateCrossChainProposal(uint256,bytes32)", _sourceChainId, _proposalHash);
uint256 localProposalId = _createCrossChainProposal(
_descriptionHash,
7 days,
ProposalType.CROSS_CHAIN,
address(crossChainGovernance),
callData,
0
);
// Store cross-chain reference
crossChainProposals[localProposalId] = CrossChainProposal({
sourceChainId: _sourceChainId,
proposalHash: _proposalHash,
localProposalId: localProposalId,
isValidated: false,
validationTime: 0,
validator: address(0),
validationProof: bytes32(0)
});
proposalToCrossChain[localProposalId] = _sourceChainId;
emit CrossChainProposalSubmitted(localProposalId, _sourceChainId, _proposalHash);
return localProposalId;
}
function _createCrossChainProposal(
string calldata _descriptionHash,
uint256 _votingPeriod,
ProposalType _proposalType,
address _targetContract,
bytes memory _callData,
uint256 _value
) internal onlyActiveStaker nonReentrant returns (uint256) {
proposalCount++;
Proposal storage p = proposals[proposalCount];
p.id = proposalCount;
p.proposer = msg.sender;
p.region = "";
p.descriptionHash = _descriptionHash;
p.startTime = block.timestamp;
p.endTime = block.timestamp + _votingPeriod;
p.state = ProposalState.Active;
p.proposalType = _proposalType;
p.targetContract = _targetContract;
p.callData = _callData;
p.value = _value;
activeProposalIds.push(proposalCount);
emit ProposalCreated(p.id, msg.sender, "", _proposalType);
return p.id;
}
function validateCrossChainVote(uint256 _proposalId, bytes32 _voteProof) external {
if (address(crossChainGovernance) != address(0)) {
crossChainGovernance.validateCrossChainVote(_proposalId, _voteProof);
}
}
// --- Internal Functions ---
function _calculateVotingWeight(address _voter, string memory _region) internal view returns (uint256) {
Staker memory staker = stakers[_voter];
// Regional council members have equal voting power
if (bytes(_region).length > 0 && isRegionalCouncilMember[_region][_voter]) {
return 1;
}
// Global voting based on stake and reputation
return staker.votingPower;
}
function _executeProposalAction(uint256 _proposalId) internal {
Proposal storage p = proposals[_proposalId];
if (p.targetContract != address(0) && p.callData.length > 0) {
// Execute the proposal action
(bool success, ) = p.targetContract.call{value: p.value}(p.callData);
require(success, "Execution failed");
}
}
function _updateVotingPower(address _staker) internal {
Staker storage staker = stakers[_staker];
// Base voting power is stake amount
uint256 basePower = staker.amount;
// Apply reputation multiplier
uint256 reputationMultiplier = 10000; // 1x default
if (address(performanceAggregator) != address(0)) {
uint256 reputation = performanceAggregator.getReputationScore(_staker);
reputationMultiplier = performanceAggregator.calculateAPYMultiplier(reputation);
}
staker.votingPower = (basePower * reputationMultiplier) / 10000;
staker.reputationScore = performanceAggregator.getReputationScore(_staker);
emit VotingPowerUpdated(_staker, staker.votingPower);
emit ReputationUpdated(_staker, staker.reputationScore);
}
function _removeFromActiveProposals(uint256 _proposalId) internal {
for (uint256 i = 0; i < activeProposalIds.length; i++) {
if (activeProposalIds[i] == _proposalId) {
activeProposalIds[i] = activeProposalIds[activeProposalIds.length - 1];
activeProposalIds.pop();
break;
}
}
}
// --- View Functions ---
function getStakerInfo(address _staker) external view returns (
uint256 amount,
uint256 votingPower,
uint256 reputationScore,
bool isActive
) {
Staker memory staker = stakers[_staker];
return (
staker.amount,
staker.votingPower,
staker.reputationScore,
staker.isActive
);
}
function getProposalInfo(uint256 _proposalId) external view returns (
address proposer,
string memory region,
ProposalState state,
ProposalType proposalType,
uint256 forVotes,
uint256 againstVotes,
uint256 abstainVotes,
uint256 startTime,
uint256 endTime
) {
Proposal storage p = proposals[_proposalId];
return (
p.proposer,
p.region,
p.state,
p.proposalType,
p.forVotes,
p.againstVotes,
p.abstainVotes,
p.startTime,
p.endTime
);
}
function getActiveProposals() external view returns (uint256[] memory) {
return activeProposalIds;
}
function getRegionalCouncilMembers(string memory _region) external view returns (address[] memory) {
return regionalCouncilMembers[_region];
}
// --- Admin Functions ---
function setRegionalCouncilMember(string calldata _region, address _member, bool _status) external onlyOwner {
isRegionalCouncilMember[_region][_member] = _status;
if (_status) {
regionalCouncilMembers[_region].push(_member);
}
}
function setMinStakeAmount(uint256 _minStakeAmount) external onlyOwner {
minStakeAmount = _minStakeAmount;
}
function setUnbondingPeriod(uint256 _unbondingPeriod) external onlyOwner {
unbondingPeriod = _unbondingPeriod;
}
function emergencyPause() external onlyOwner {
// Emergency pause functionality
}
function emergencyUnpause() external onlyOwner {
// Emergency unpause functionality
}
}

View File

@@ -0,0 +1,606 @@
// 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
);
}
}

View File

@@ -0,0 +1,584 @@
// 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 "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../interfaces/IModularContracts.sol";
import "./ContractRegistry.sol";
/**
* @title RewardDistributor
* @dev Multi-token reward distribution engine with automated claiming
* @notice Integrates with AgentStaking, TreasuryManager, and PerformanceAggregator
*/
contract RewardDistributor is IRewardDistributor, Ownable, ReentrancyGuard, Pausable {
using SafeERC20 for IERC20;
// State variables
uint256 public version = 1;
ContractRegistry public registry;
address public performanceAggregator;
address public stakingPoolFactory;
// Reward pool
struct RewardPool {
uint256 poolId;
address token;
uint256 totalRewards;
uint256 distributedRewards;
uint256 claimedRewards;
bool isActive;
uint256 createdAt;
address creator;
string description;
}
// Reward claim
struct RewardClaim {
uint256 claimId;
uint256 poolId;
address recipient;
uint256 amount;
uint256 claimedAt;
bool isClaimed;
bytes32 merkleProof; // For batch claims
}
// Performance-based reward
struct PerformanceReward {
uint256 rewardId;
address agent;
uint256 baseAmount;
uint256 performanceMultiplier; // In basis points (10000 = 1x)
uint256 finalAmount;
bool isDistributed;
uint256 distributedAt;
}
// Mappings
mapping(uint256 => RewardPool) public rewardPools;
mapping(uint256 => RewardClaim) public rewardClaims;
mapping(uint256 => PerformanceReward) public performanceRewards;
mapping(address => uint256[]) public userClaims;
mapping(address => uint256) public userTotalRewards;
mapping(address => mapping(address => uint256)) public userTokenRewards; // user => token => amount
mapping(uint256 => uint256[]) public poolClaims;
mapping(address => uint256[]) public agentPerformanceRewards;
// Counters
uint256 public poolCounter;
uint256 public claimCounter;
uint256 public performanceRewardCounter;
uint256[] public activePoolIds;
// Constants
uint256 public constant MIN_REWARD_AMOUNT = 1 * 10**18; // 1 token minimum
uint256 public constant MAX_PERFORMANCE_MULTIPLIER = 50000; // 5x max multiplier
uint256 public constant DEFAULT_PERFORMANCE_MULTIPLIER = 10000; // 1x default
uint256 public constant CLAIM_FEE_PERCENTAGE = 10; // 0.1% claim fee
uint256 public constant BASIS_POINTS = 10000;
// Events
event RewardPoolCreated(uint256 indexed poolId, address indexed token, uint256 totalRewards, string description);
event RewardsDistributed(uint256 indexed poolId, uint256 recipientCount, uint256 totalAmount);
event RewardClaimed(uint256 indexed claimId, address indexed recipient, uint256 amount);
event PerformanceRewardCreated(uint256 indexed rewardId, address indexed agent, uint256 baseAmount);
event PerformanceRewardDistributed(uint256 indexed rewardId, address indexed agent, uint256 finalAmount);
event PoolDeactivated(uint256 indexed poolId);
event TokensDeposited(address indexed token, address indexed depositor, uint256 amount);
// Errors
error InvalidAmount(uint256 amount);
error PoolNotFound(uint256 poolId);
error PoolNotActive(uint256 poolId);
error InsufficientPoolBalance(uint256 poolId, uint256 requested, uint256 available);
error ClaimNotFound(uint256 claimId);
error ClaimAlreadyUsed(uint256 claimId);
error InvalidRecipient(address recipient);
error InvalidToken(address token);
error PerformanceMultiplierTooHigh(uint256 multiplier);
error RegistryNotSet();
error NotAuthorized();
modifier validAmount(uint256 amount) {
if (amount < MIN_REWARD_AMOUNT) revert InvalidAmount(amount);
_;
}
modifier validPool(uint256 poolId) {
if (rewardPools[poolId].poolId == 0) revert PoolNotFound(poolId);
if (!rewardPools[poolId].isActive) revert PoolNotActive(poolId);
_;
}
modifier validRecipient(address recipient) {
if (recipient == address(0)) revert InvalidRecipient(recipient);
_;
}
modifier onlyAuthorized() {
if (msg.sender != owner() && msg.sender != stakingPoolFactory && msg.sender != performanceAggregator) {
revert NotAuthorized();
}
_;
}
modifier registrySet() {
if (address(registry) == address(0)) revert RegistryNotSet();
_;
}
constructor() {}
/**
* @dev Initialize the reward distributor (implements IModularContract)
*/
function initialize(address _registry) external override {
require(address(registry) == address(0), "Already initialized");
registry = ContractRegistry(_registry);
// Register this contract if not already registered
bytes32 contractId = keccak256(abi.encodePacked("RewardDistributor"));
try registry.getContract(contractId) returns (address) {
// Already registered, skip
} catch {
// Not registered, register now
registry.registerContract(contractId, address(this));
}
// Get integration addresses from registry
try registry.getContract(keccak256(abi.encodePacked("PerformanceAggregator"))) returns (address perfAddress) {
performanceAggregator = perfAddress;
} catch {
// PerformanceAggregator not found, keep as zero address
}
try registry.getContract(keccak256(abi.encodePacked("StakingPoolFactory"))) returns (address stakingAddress) {
stakingPoolFactory = stakingAddress;
} catch {
// StakingPoolFactory not found, keep as zero address
}
}
/**
* @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 Create a reward pool
*/
function createRewardPool(address token, uint256 totalRewards)
external
override
onlyAuthorized
whenNotPaused
validAmount(totalRewards)
nonReentrant
returns (uint256)
{
if (token == address(0)) revert InvalidToken(token);
uint256 poolId = ++poolCounter;
rewardPools[poolId] = RewardPool({
poolId: poolId,
token: token,
totalRewards: totalRewards,
distributedRewards: 0,
claimedRewards: 0,
isActive: true,
createdAt: block.timestamp,
creator: msg.sender,
description: ""
});
activePoolIds.push(poolId);
emit RewardPoolCreated(poolId, token, totalRewards, "");
}
/**
* @dev Create a reward pool with description
*/
function createRewardPoolWithDescription(
address token,
uint256 totalRewards,
string memory description
)
external
onlyAuthorized
whenNotPaused
validAmount(totalRewards)
nonReentrant
{
if (token == address(0)) revert InvalidToken(token);
uint256 poolId = ++poolCounter;
rewardPools[poolId] = RewardPool({
poolId: poolId,
token: token,
totalRewards: totalRewards,
distributedRewards: 0,
claimedRewards: 0,
isActive: true,
createdAt: block.timestamp,
creator: msg.sender,
description: description
});
activePoolIds.push(poolId);
emit RewardPoolCreated(poolId, token, totalRewards, description);
}
/**
* @dev Distribute rewards to multiple recipients
*/
function distributeRewards(uint256 poolId, address[] memory recipients, uint256[] memory amounts)
external
override
onlyAuthorized
whenNotPaused
validPool(poolId)
nonReentrant
{
require(recipients.length == amounts.length, "Array length mismatch");
RewardPool storage pool = rewardPools[poolId];
uint256 totalAmount = 0;
// Calculate total amount and check pool balance
for (uint256 i = 0; i < amounts.length; i++) {
require(recipients[i] != address(0), "Invalid recipient");
totalAmount += amounts[i];
}
uint256 availableBalance = pool.totalRewards - pool.distributedRewards;
if (totalAmount > availableBalance) {
revert InsufficientPoolBalance(poolId, totalAmount, availableBalance);
}
// Create claims and update user balances
for (uint256 i = 0; i < recipients.length; i++) {
if (amounts[i] > 0) {
uint256 claimId = ++claimCounter;
rewardClaims[claimId] = RewardClaim({
claimId: claimId,
poolId: poolId,
recipient: recipients[i],
amount: amounts[i],
claimedAt: 0,
isClaimed: false,
merkleProof: bytes32(0)
});
userClaims[recipients[i]].push(claimId);
userTotalRewards[recipients[i]] += amounts[i];
userTokenRewards[recipients[i]][pool.token] += amounts[i];
poolClaims[poolId].push(claimId);
}
}
// Update pool
pool.distributedRewards += totalAmount;
emit RewardsDistributed(poolId, recipients.length, totalAmount);
}
/**
* @dev Claim a reward
*/
function claimReward(uint256 claimId)
external
override
whenNotPaused
nonReentrant
{
RewardClaim storage claim = rewardClaims[claimId];
if (claim.claimId == 0) revert ClaimNotFound(claimId);
if (claim.isClaimed) revert ClaimAlreadyUsed(claimId);
if (msg.sender != claim.recipient) revert InvalidRecipient(msg.sender);
// Mark as claimed
claim.isClaimed = true;
claim.claimedAt = block.timestamp;
// Update pool
RewardPool storage pool = rewardPools[claim.poolId];
pool.claimedRewards += claim.amount;
// Calculate claim fee
uint256 fee = (claim.amount * CLAIM_FEE_PERCENTAGE) / BASIS_POINTS;
uint256 netAmount = claim.amount - fee;
// Transfer tokens
IERC20(pool.token).safeTransfer(claim.recipient, netAmount);
// Transfer fee to owner
if (fee > 0) {
IERC20(pool.token).safeTransfer(owner(), fee);
}
emit RewardClaimed(claimId, claim.recipient, netAmount);
}
/**
* @dev Create performance-based reward
*/
function createPerformanceReward(address agent, uint256 baseAmount)
external
onlyAuthorized
whenNotPaused
validAmount(baseAmount)
nonReentrant
{
if (agent == address(0)) revert InvalidRecipient(agent);
uint256 rewardId = ++performanceRewardCounter;
// Get performance multiplier from PerformanceAggregator
uint256 performanceMultiplier = DEFAULT_PERFORMANCE_MULTIPLIER;
if (performanceAggregator != address(0)) {
try IPerformanceAggregator(performanceAggregator).getReputationScore(agent) returns (uint256 reputation) {
performanceMultiplier = IPerformanceAggregator(performanceAggregator).calculateAPYMultiplier(reputation);
if (performanceMultiplier > MAX_PERFORMANCE_MULTIPLIER) {
performanceMultiplier = MAX_PERFORMANCE_MULTIPLIER;
}
} catch {
// Use default multiplier if call fails
performanceMultiplier = DEFAULT_PERFORMANCE_MULTIPLIER;
}
}
uint256 finalAmount = (baseAmount * performanceMultiplier) / BASIS_POINTS;
performanceRewards[rewardId] = PerformanceReward({
rewardId: rewardId,
agent: agent,
baseAmount: baseAmount,
performanceMultiplier: performanceMultiplier,
finalAmount: finalAmount,
isDistributed: false,
distributedAt: 0
});
agentPerformanceRewards[agent].push(rewardId);
emit PerformanceRewardCreated(rewardId, agent, baseAmount);
}
/**
* @dev Distribute performance reward
*/
function distributePerformanceReward(uint256 rewardId)
external
onlyAuthorized
whenNotPaused
nonReentrant
{
PerformanceReward storage reward = performanceRewards[rewardId];
if (reward.rewardId == 0) revert ClaimNotFound(rewardId);
if (reward.isDistributed) revert ClaimAlreadyUsed(rewardId);
// Mark as distributed
reward.isDistributed = true;
reward.distributedAt = block.timestamp;
// Update user rewards
userTotalRewards[reward.agent] += reward.finalAmount;
emit PerformanceRewardDistributed(rewardId, reward.agent, reward.finalAmount);
}
/**
* @dev Get pool balance
*/
function getPoolBalance(uint256 poolId) external view override returns (uint256) {
RewardPool memory pool = rewardPools[poolId];
return pool.totalRewards - pool.distributedRewards;
}
/**
* @dev Get user rewards
*/
function getUserRewards(address user) external view override returns (uint256) {
return userTotalRewards[user];
}
/**
* @dev Get user rewards for specific token
*/
function getUserTokenRewards(address user, address token) external view returns (uint256) {
return userTokenRewards[user][token];
}
/**
* @dev Get user claim IDs
*/
function getUserClaims(address user) external view returns (uint256[] memory) {
return userClaims[user];
}
/**
* @dev Get pool claim IDs
*/
function getPoolClaims(uint256 poolId) external view returns (uint256[] memory) {
return poolClaims[poolId];
}
/**
* @dev Get agent performance reward IDs
*/
function getAgentPerformanceRewards(address agent) external view returns (uint256[] memory) {
return agentPerformanceRewards[agent];
}
/**
* @dev Get active pool IDs
*/
function getActivePoolIds() external view returns (uint256[] memory) {
return activePoolIds;
}
/**
* @dev Get pool details
*/
function getPoolDetails(uint256 poolId) external view returns (
address token,
uint256 totalRewards,
uint256 distributedRewards,
uint256 claimedRewards,
bool isActive,
string memory description
) {
RewardPool memory pool = rewardPools[poolId];
return (
pool.token,
pool.totalRewards,
pool.distributedRewards,
pool.claimedRewards,
pool.isActive,
pool.description
);
}
/**
* @dev Get claim details
*/
function getClaimDetails(uint256 claimId) external view returns (
uint256 poolId,
address recipient,
uint256 amount,
bool isClaimed,
uint256 claimedAt
) {
RewardClaim memory claim = rewardClaims[claimId];
return (
claim.poolId,
claim.recipient,
claim.amount,
claim.isClaimed,
claim.claimedAt
);
}
/**
* @dev Deactivate a reward pool
*/
function deactivatePool(uint256 poolId) external onlyAuthorized validPool(poolId) {
rewardPools[poolId].isActive = false;
// Remove from active pools
for (uint256 i = 0; i < activePoolIds.length; i++) {
if (activePoolIds[i] == poolId) {
activePoolIds[i] = activePoolIds[activePoolIds.length - 1];
activePoolIds.pop();
break;
}
}
emit PoolDeactivated(poolId);
}
/**
* @dev Deposit tokens for rewards
*/
function depositTokens(address token, uint256 amount) external whenNotPaused validAmount(amount) {
if (token == address(0)) revert InvalidToken(token);
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
emit TokensDeposited(token, msg.sender, amount);
}
/**
* @dev Emergency withdraw tokens
*/
function emergencyWithdraw(address token, uint256 amount) external onlyOwner {
if (token == address(0)) revert InvalidToken(token);
IERC20(token).safeTransfer(msg.sender, amount);
}
/**
* @dev Get reward distributor statistics
*/
function getRewardStats() external view returns (
uint256 totalPools,
uint256 activePools,
uint256 totalClaims,
uint256 totalDistributed,
uint256 totalClaimed
) {
uint256 _activePools = 0;
uint256 _totalDistributed = 0;
uint256 _totalClaimed = 0;
for (uint256 i = 0; i < activePoolIds.length; i++) {
RewardPool memory pool = rewardPools[activePoolIds[i]];
if (pool.isActive) {
_activePools++;
_totalDistributed += pool.distributedRewards;
_totalClaimed += pool.claimedRewards;
}
}
return (
poolCounter,
_activePools,
claimCounter,
_totalDistributed,
_totalClaimed
);
}
}

View File

@@ -0,0 +1,682 @@
// 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 "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../interfaces/IModularContracts.sol";
import "./ContractRegistry.sol";
/**
* @title StakingPoolFactory
* @dev Dynamic staking pool creation with performance-based APY
* @notice Integrates with AgentStaking, PerformanceAggregator, and RewardDistributor
*/
contract StakingPoolFactory is IStakingPoolFactory, Ownable, ReentrancyGuard, Pausable {
using SafeERC20 for IERC20;
// State variables
uint256 public version = 1;
IERC20 public stakingToken;
ContractRegistry public registry;
IPerformanceAggregator public performanceAggregator;
IRewardDistributor public rewardDistributor;
// Staking pool
struct StakingPool {
uint256 poolId;
string poolName;
uint256 baseAPY; // In basis points (10000 = 100%)
uint256 currentAPY;
uint256 totalStaked;
uint256 lockPeriod;
uint256 minStakeAmount;
uint256 maxStakeAmount;
uint256 totalStakers;
uint256 createdRewards;
uint256 distributedRewards;
bool isActive;
uint256 createdAt;
address creator;
string description;
}
// Staking position
struct StakingPosition {
uint256 positionId;
uint256 poolId;
address staker;
uint256 amount;
uint256 lockStart;
uint256 lockEnd;
uint256 apyAtStake;
uint256 accumulatedRewards;
uint256 lastRewardTime;
bool isActive;
bool isWithdrawn;
}
// Pool performance metrics
struct PoolPerformance {
uint256 totalRewardsGenerated;
uint256 averageStakingDuration;
uint256 retentionRate; // In basis points
uint256 utilizationRate; // In basis points
uint256 distributedRewards;
uint256 lastUpdated;
}
// Mappings
mapping(uint256 => StakingPool) public stakingPools;
mapping(uint256 => StakingPosition) public stakingPositions;
mapping(uint256 => uint256[]) public poolStakers;
mapping(address => uint256[]) public stakerPositions;
mapping(uint256 => PoolPerformance) public poolPerformance;
mapping(string => uint256) public poolNameToId;
// Counters
uint256 public poolCounter;
uint256 public positionCounter;
uint256[] public activePoolIds;
// Constants
uint256 public constant MIN_BASE_APY = 100; // 1% minimum
uint256 public constant MAX_BASE_APY = 5000; // 50% maximum
uint256 public constant DEFAULT_LOCK_PERIOD = 30 days;
uint256 public constant MAX_LOCK_PERIOD = 365 days;
uint256 public constant DEFAULT_MIN_STAKE = 100 * 10**18; // 100 tokens
uint256 public constant DEFAULT_MAX_STAKE = 1000000 * 10**18; // 1M tokens
uint256 public constant PERFORMANCE_UPDATE_INTERVAL = 1 days;
uint256 public constant BASIS_POINTS = 10000;
// Events
event PoolCreated(uint256 indexed poolId, string poolName, uint256 baseAPY, uint256 lockPeriod);
event PoolUpdated(uint256 indexed poolId, uint256 newAPY);
event Staked(uint256 indexed positionId, uint256 indexed poolId, address indexed staker, uint256 amount);
event Unstaked(uint256 indexed positionId, address indexed staker, uint256 amount, uint256 rewards);
event RewardsDistributed(uint256 indexed poolId, uint256 totalAmount);
event PoolDeactivated(uint256 indexed poolId);
event PerformanceUpdated(uint256 indexed poolId, uint256 newAPY);
// Errors
error InvalidAmount(uint256 amount);
error PoolNotFound(uint256 poolId);
error PoolNotActive(uint256 poolId);
error PositionNotFound(uint256 positionId);
error PositionNotActive(uint256 positionId);
error InvalidLockPeriod(uint256 period);
error InvalidAPY(uint256 apy);
error InsufficientBalance(uint256 requested, uint256 available);
error LockPeriodNotEnded(uint256 positionId);
error MaxStakeReached(uint256 poolId);
error RegistryNotSet();
error NotAuthorized();
modifier validAmount(uint256 amount) {
if (amount == 0) revert InvalidAmount(amount);
_;
}
modifier validPool(uint256 poolId) {
if (stakingPools[poolId].poolId == 0) revert PoolNotFound(poolId);
if (!stakingPools[poolId].isActive) revert PoolNotActive(poolId);
_;
}
modifier validPosition(uint256 positionId) {
if (stakingPositions[positionId].positionId == 0) revert PositionNotFound(positionId);
if (!stakingPositions[positionId].isActive) revert PositionNotActive(positionId);
_;
}
modifier validLockPeriod(uint256 period) {
if (period < DEFAULT_LOCK_PERIOD || period > MAX_LOCK_PERIOD) {
revert InvalidLockPeriod(period);
}
_;
}
modifier validAPY(uint256 apy) {
if (apy < MIN_BASE_APY || apy > MAX_BASE_APY) revert InvalidAPY(apy);
_;
}
modifier onlyAuthorized() {
if (msg.sender != owner() && msg.sender != address(rewardDistributor)) {
revert NotAuthorized();
}
_;
}
modifier registrySet() {
if (address(registry) == address(0)) revert RegistryNotSet();
_;
}
constructor(address _stakingToken) {
stakingToken = IERC20(_stakingToken);
}
/**
* @dev Initialize the staking pool factory (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("StakingPoolFactory"));
registry.registerContract(contractId, address(this));
// Get integration addresses from registry
performanceAggregator = IPerformanceAggregator(registry.getContract(keccak256(abi.encodePacked("PerformanceAggregator"))));
rewardDistributor = IRewardDistributor(registry.getContract(keccak256(abi.encodePacked("RewardDistributor"))));
}
/**
* @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 Create a new staking pool
*/
function createPool(string memory poolName, uint256 baseAPY, uint256 lockPeriod)
external
override
onlyAuthorized
whenNotPaused
validAPY(baseAPY)
validLockPeriod(lockPeriod)
nonReentrant
returns (uint256)
{
require(bytes(poolName).length > 0, "Empty pool name");
require(poolNameToId[poolName] == 0, "Pool name already exists");
uint256 poolId = ++poolCounter;
stakingPools[poolId] = StakingPool({
poolId: poolId,
poolName: poolName,
baseAPY: baseAPY,
currentAPY: baseAPY,
totalStaked: 0,
lockPeriod: lockPeriod,
minStakeAmount: DEFAULT_MIN_STAKE,
maxStakeAmount: DEFAULT_MAX_STAKE,
totalStakers: 0,
createdRewards: 0,
distributedRewards: 0,
isActive: true,
createdAt: block.timestamp,
creator: msg.sender,
description: ""
});
poolNameToId[poolName] = poolId;
activePoolIds.push(poolId);
// Initialize performance tracking
poolPerformance[poolId] = PoolPerformance({
totalRewardsGenerated: 0,
averageStakingDuration: 0,
retentionRate: 10000, // 100% initial
utilizationRate: 0,
distributedRewards: 0,
lastUpdated: block.timestamp
});
emit PoolCreated(poolId, poolName, baseAPY, lockPeriod);
}
/**
* @dev Create a staking pool with custom parameters
*/
function createPoolWithParameters(
string memory poolName,
uint256 baseAPY,
uint256 lockPeriod,
uint256 minStakeAmount,
uint256 maxStakeAmount,
string memory description
)
external
onlyAuthorized
whenNotPaused
validAPY(baseAPY)
validLockPeriod(lockPeriod)
nonReentrant
{
require(bytes(poolName).length > 0, "Empty pool name");
require(poolNameToId[poolName] == 0, "Pool name already exists");
require(minStakeAmount > 0 && minStakeAmount <= maxStakeAmount, "Invalid stake amounts");
uint256 poolId = ++poolCounter;
stakingPools[poolId] = StakingPool({
poolId: poolId,
poolName: poolName,
baseAPY: baseAPY,
currentAPY: baseAPY,
totalStaked: 0,
lockPeriod: lockPeriod,
minStakeAmount: minStakeAmount,
maxStakeAmount: maxStakeAmount,
totalStakers: 0,
createdRewards: 0,
distributedRewards: 0,
isActive: true,
createdAt: block.timestamp,
creator: msg.sender,
description: description
});
poolNameToId[poolName] = poolId;
activePoolIds.push(poolId);
// Initialize performance tracking
poolPerformance[poolId] = PoolPerformance({
totalRewardsGenerated: 0,
averageStakingDuration: 0,
retentionRate: 10000,
utilizationRate: 0,
distributedRewards: 0,
lastUpdated: block.timestamp
});
emit PoolCreated(poolId, poolName, baseAPY, lockPeriod);
}
/**
* @dev Update pool APY
*/
function updatePoolAPY(uint256 poolId, uint256 newAPY)
external
override
onlyAuthorized
whenNotPaused
validPool(poolId)
validAPY(newAPY)
nonReentrant
{
StakingPool storage pool = stakingPools[poolId];
pool.currentAPY = newAPY;
emit PoolUpdated(poolId, newAPY);
}
/**
* @dev Stake in a pool
*/
function stakeInPool(uint256 poolId, uint256 amount)
external
override
whenNotPaused
validPool(poolId)
validAmount(amount)
nonReentrant
{
StakingPool storage pool = stakingPools[poolId];
// Check stake limits
if (amount < pool.minStakeAmount) {
revert InvalidAmount(amount);
}
if (pool.totalStaked + amount > pool.maxStakeAmount) {
revert MaxStakeReached(poolId);
}
// Check user balance
uint256 userBalance = stakingToken.balanceOf(msg.sender);
if (amount > userBalance) {
revert InsufficientBalance(amount, userBalance);
}
// Create staking position
uint256 positionId = ++positionCounter;
stakingPositions[positionId] = StakingPosition({
positionId: positionId,
poolId: poolId,
staker: msg.sender,
amount: amount,
lockStart: block.timestamp,
lockEnd: block.timestamp + pool.lockPeriod,
apyAtStake: pool.currentAPY,
accumulatedRewards: 0,
lastRewardTime: block.timestamp,
isActive: true,
isWithdrawn: false
});
// Update pool
pool.totalStaked += amount;
pool.totalStakers++;
poolStakers[poolId].push(positionId);
stakerPositions[msg.sender].push(positionId);
// Update performance metrics
_updatePoolPerformance(poolId);
// Transfer tokens
stakingToken.safeTransferFrom(msg.sender, address(this), amount);
emit Staked(positionId, poolId, msg.sender, amount);
}
/**
* @dev Unstake from a pool
*/
function unstakeFromPool(uint256 poolId, uint256 amount)
external
override
whenNotPaused
validPool(poolId)
validAmount(amount)
nonReentrant
{
// Find user's position in the pool
uint256[] memory userPositions = stakerPositions[msg.sender];
uint256 positionId = 0;
bool found = false;
for (uint256 i = 0; i < userPositions.length; i++) {
if (stakingPositions[userPositions[i]].poolId == poolId &&
stakingPositions[userPositions[i]].isActive &&
!stakingPositions[userPositions[i]].isWithdrawn) {
positionId = userPositions[i];
found = true;
break;
}
}
if (!found) {
revert PositionNotFound(positionId);
}
StakingPosition storage position = stakingPositions[positionId];
// Check lock period
if (block.timestamp < position.lockEnd) {
revert LockPeriodNotEnded(positionId);
}
// Check amount
if (amount > position.amount) {
revert InvalidAmount(amount);
}
// Calculate rewards
uint256 rewards = _calculateRewards(position);
position.accumulatedRewards += rewards;
// Update position
position.amount -= amount;
if (position.amount == 0) {
position.isActive = false;
position.isWithdrawn = true;
}
// Update pool
StakingPool storage pool = stakingPools[poolId];
pool.totalStaked -= amount;
if (position.amount == 0) {
pool.totalStakers--;
}
// Update performance metrics
_updatePoolPerformance(poolId);
// Transfer tokens and rewards
stakingToken.safeTransfer(msg.sender, amount);
if (rewards > 0 && address(rewardDistributor) != address(0)) {
// Create reward claim
uint256 rewardPoolId = 1; // Assuming first pool, or get it dynamically
rewardDistributor.createRewardPool(address(stakingToken), rewards);
rewardDistributor.distributeRewards(
rewardPoolId,
new address[](1),
new uint256[](1)
);
}
emit Unstaked(positionId, msg.sender, amount, rewards);
}
/**
* @dev Get pool performance
*/
function getPoolPerformance(uint256 poolId) external view override returns (uint256) {
StakingPool memory pool = stakingPools[poolId];
PoolPerformance memory performance = poolPerformance[poolId];
// Calculate performance score based on multiple factors
uint256 utilizationScore = pool.totalStaked > 0 ?
(pool.totalStaked * 10000) / pool.maxStakeAmount : 0;
uint256 retentionScore = performance.retentionRate;
uint256 rewardEfficiency = performance.totalRewardsGenerated > 0 ?
(performance.distributedRewards * 10000) / performance.totalRewardsGenerated : 10000;
// Weighted average performance score
uint256 performanceScore = (utilizationScore * 40 + retentionScore * 30 + rewardEfficiency * 30) / 100;
return performanceScore;
}
/**
* @dev Calculate rewards for a position
*/
function _calculateRewards(StakingPosition memory position) internal view returns (uint256) {
uint256 timeElapsed = block.timestamp - position.lastRewardTime;
uint256 stakingDuration = block.timestamp - position.lockStart;
// Base rewards calculation
uint256 baseRewards = (position.amount * position.apyAtStake * timeElapsed) / (BASIS_POINTS * 365 days);
// Performance bonus
uint256 performanceBonus = 0;
if (address(performanceAggregator) != address(0)) {
uint256 reputationScore = performanceAggregator.getReputationScore(position.staker);
uint256 multiplier = performanceAggregator.calculateAPYMultiplier(reputationScore);
performanceBonus = (baseRewards * (multiplier - BASIS_POINTS)) / BASIS_POINTS;
}
return baseRewards + performanceBonus;
}
/**
* @dev Update pool performance metrics
*/
function _updatePoolPerformance(uint256 poolId) internal {
PoolPerformance storage performance = poolPerformance[poolId];
StakingPool storage pool = stakingPools[poolId];
// Update utilization rate
performance.utilizationRate = pool.totalStaked > 0 ?
(pool.totalStaked * 10000) / pool.maxStakeAmount : 0;
// Update retention rate (simplified calculation)
if (pool.totalStakers > 0) {
uint256 activePositions = 0;
uint256[] memory positions = poolStakers[poolId];
for (uint256 i = 0; i < positions.length; i++) {
if (stakingPositions[positions[i]].isActive) {
activePositions++;
}
}
performance.retentionRate = (activePositions * 10000) / pool.totalStakers;
}
// Update distributed rewards (simplified)
performance.distributedRewards = performance.totalRewardsGenerated; // Simplified for now
performance.lastUpdated = block.timestamp;
}
/**
* @dev Get pool details
*/
function getPoolDetails(uint256 poolId) external view returns (
string memory poolName,
uint256 baseAPY,
uint256 currentAPY,
uint256 totalStaked,
uint256 lockPeriod,
uint256 totalStakers,
bool isActive,
string memory description
) {
StakingPool memory pool = stakingPools[poolId];
return (
pool.poolName,
pool.baseAPY,
pool.currentAPY,
pool.totalStaked,
pool.lockPeriod,
pool.totalStakers,
pool.isActive,
pool.description
);
}
/**
* @dev Get position details
*/
function getPositionDetails(uint256 positionId) external view returns (
uint256 poolId,
address staker,
uint256 amount,
uint256 lockStart,
uint256 lockEnd,
uint256 apyAtStake,
uint256 accumulatedRewards,
bool isActive,
bool isWithdrawn
) {
StakingPosition memory position = stakingPositions[positionId];
return (
position.poolId,
position.staker,
position.amount,
position.lockStart,
position.lockEnd,
position.apyAtStake,
position.accumulatedRewards,
position.isActive,
position.isWithdrawn
);
}
/**
* @dev Get user positions
*/
function getUserPositions(address user) external view returns (uint256[] memory) {
return stakerPositions[user];
}
/**
* @dev Get pool stakers
*/
function getPoolStakers(uint256 poolId) external view returns (uint256[] memory) {
return poolStakers[poolId];
}
/**
* @dev Get active pool IDs
*/
function getActivePoolIds() external view returns (uint256[] memory) {
return activePoolIds;
}
/**
* @dev Get pool by name
*/
function getPoolByName(string memory poolName) external view returns (uint256) {
return poolNameToId[poolName];
}
/**
* @dev Deactivate a pool
*/
function deactivatePool(uint256 poolId) external onlyAuthorized validPool(poolId) {
stakingPools[poolId].isActive = false;
// Remove from active pools
for (uint256 i = 0; i < activePoolIds.length; i++) {
if (activePoolIds[i] == poolId) {
activePoolIds[i] = activePoolIds[activePoolIds.length - 1];
activePoolIds.pop();
break;
}
}
emit PoolDeactivated(poolId);
}
/**
* @dev Emergency withdraw
*/
function emergencyWithdraw(address token, uint256 amount) external onlyOwner {
if (token == address(stakingToken)) {
stakingToken.safeTransfer(msg.sender, amount);
} else {
IERC20(token).safeTransfer(msg.sender, amount);
}
}
/**
* @dev Get factory statistics
*/
function getFactoryStats() external view returns (
uint256 totalPools,
uint256 activePools,
uint256 totalStaked,
uint256 totalStakers,
uint256 totalPositions
) {
uint256 _totalStaked = 0;
uint256 _totalStakers = 0;
for (uint256 i = 0; i < activePoolIds.length; i++) {
StakingPool memory pool = stakingPools[activePoolIds[i]];
_totalStaked += pool.totalStaked;
_totalStakers += pool.totalStakers;
}
return (
poolCounter,
activePoolIds.length,
_totalStaked,
_totalStakers,
positionCounter
);
}
}

View File

@@ -0,0 +1,501 @@
// 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 "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../interfaces/IModularContracts.sol";
import "./ContractRegistry.sol";
/**
* @title TreasuryManager
* @dev Modular treasury management with budget categories and automated allocation
* @notice Integrates with DAOGovernance for automated execution and RewardDistributor for rewards
*/
contract TreasuryManager is ITreasuryManager, Ownable, ReentrancyGuard, Pausable {
using SafeERC20 for IERC20;
// State variables
uint256 public version = 1;
IERC20 public treasuryToken;
ContractRegistry public registry;
address public daoGovernance;
// Budget categories
struct BudgetCategory {
string name;
uint256 totalBudget;
uint256 allocatedAmount;
uint256 spentAmount;
bool isActive;
uint256 createdAt;
address creator;
}
// Fund allocation
struct FundAllocation {
uint256 allocationId;
string category;
address recipient;
uint256 totalAmount;
uint256 releasedAmount;
uint256 vestingPeriod;
uint256 vestingStart;
uint256 lastRelease;
bool isCompleted;
bool isActive;
address allocatedBy;
uint256 createdAt;
}
// Mappings
mapping(string => BudgetCategory) public budgetCategories;
mapping(uint256 => FundAllocation) public allocations;
mapping(string => uint256[]) public categoryAllocations;
mapping(address => uint256[]) public recipientAllocations;
// Counters
uint256 public categoryCounter;
uint256 public allocationCounter;
string[] public categoryNames;
// Constants
uint256 public constant MIN_ALLOCATION = 100 * 10**18; // 100 tokens minimum
uint256 public constant MAX_VESTING_PERIOD = 365 days; // 1 year maximum
uint256 public constant DEFAULT_VESTING_PERIOD = 30 days; // 30 days default
// Events
event BudgetCategoryCreated(string indexed category, uint256 budget, address indexed creator);
event BudgetCategoryUpdated(string indexed category, uint256 newBudget);
event FundsAllocated(uint256 indexed allocationId, string indexed category, address indexed recipient, uint256 amount);
event FundsReleased(uint256 indexed allocationId, address indexed recipient, uint256 amount);
event AllocationCompletedEvent(uint256 indexed allocationId);
event TreasuryDeposited(address indexed depositor, uint256 amount);
event TreasuryWithdrawn(address indexed recipient, uint256 amount);
event CategoryDeactivated(string indexed category);
// Errors
error InvalidAmount(uint256 amount);
error InvalidCategory(string category);
error InsufficientBudget(string category, uint256 requested, uint256 available);
error AllocationNotFound(uint256 allocationId);
error AllocationCompletedError(uint256 allocationId);
error InvalidVestingPeriod(uint256 period);
error InsufficientBalance(uint256 requested, uint256 available);
error NotAuthorized();
error RegistryNotSet();
modifier validAmount(uint256 amount) {
if (amount == 0) revert InvalidAmount(amount);
_;
}
modifier validCategory(string memory category) {
if (bytes(category).length == 0 || !budgetCategories[category].isActive) {
revert InvalidCategory(category);
}
_;
}
modifier onlyAuthorized() {
if (msg.sender != owner() && msg.sender != daoGovernance) revert NotAuthorized();
_;
}
modifier registrySet() {
if (address(registry) == address(0)) revert RegistryNotSet();
_;
}
constructor(address _treasuryToken) {
treasuryToken = IERC20(_treasuryToken);
}
/**
* @dev Initialize the treasury manager (implements IModularContract)
*/
function initialize(address _registry) external override {
require(address(registry) == address(0), "Already initialized");
registry = ContractRegistry(_registry);
// Register this contract if not already registered
bytes32 contractId = keccak256(abi.encodePacked("TreasuryManager"));
try registry.getContract(contractId) returns (address) {
// Already registered, skip
} catch {
// Not registered, register now
registry.registerContract(contractId, address(this));
}
// Get DAO governance address from registry
try registry.getContract(keccak256(abi.encodePacked("DAOGovernance"))) returns (address govAddress) {
daoGovernance = govAddress;
} catch {
// DAO governance not found, keep as zero address
}
}
/**
* @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 Create a budget category
*/
function createBudgetCategory(string memory category, uint256 budget)
external
override
onlyAuthorized
whenNotPaused
validAmount(budget)
nonReentrant
{
require(budgetCategories[category].createdAt == 0, "Category already exists");
budgetCategories[category] = BudgetCategory({
name: category,
totalBudget: budget,
allocatedAmount: 0,
spentAmount: 0,
isActive: true,
createdAt: block.timestamp,
creator: msg.sender
});
categoryNames.push(category);
categoryCounter++;
emit BudgetCategoryCreated(category, budget, msg.sender);
}
/**
* @dev Update budget category
*/
function updateBudgetCategory(string memory category, uint256 newBudget)
external
onlyAuthorized
whenNotPaused
validAmount(newBudget)
validCategory(category)
nonReentrant
{
BudgetCategory storage budgetCategory = budgetCategories[category];
// Ensure new budget is not less than allocated amount
require(newBudget >= budgetCategory.allocatedAmount, "Budget below allocated amount");
budgetCategory.totalBudget = newBudget;
emit BudgetCategoryUpdated(category, newBudget);
}
/**
* @dev Allocate funds to a recipient
*/
function allocateFunds(string memory category, address recipient, uint256 amount)
external
override
onlyAuthorized
whenNotPaused
validAmount(amount)
validCategory(category)
nonReentrant
{
BudgetCategory storage budgetCategory = budgetCategories[category];
// Check budget availability
uint256 availableBudget = budgetCategory.totalBudget - budgetCategory.allocatedAmount;
if (amount > availableBudget) {
revert InsufficientBudget(category, amount, availableBudget);
}
// Check treasury balance
uint256 treasuryBalance = treasuryToken.balanceOf(address(this));
if (amount > treasuryBalance) {
revert InsufficientBalance(amount, treasuryBalance);
}
// Create allocation
uint256 allocationId = ++allocationCounter;
allocations[allocationId] = FundAllocation({
allocationId: allocationId,
category: category,
recipient: recipient,
totalAmount: amount,
releasedAmount: 0,
vestingPeriod: DEFAULT_VESTING_PERIOD,
vestingStart: block.timestamp,
lastRelease: block.timestamp,
isCompleted: false,
isActive: true,
allocatedBy: msg.sender,
createdAt: block.timestamp
});
// Update budget category
budgetCategory.allocatedAmount += amount;
categoryAllocations[category].push(allocationId);
recipientAllocations[recipient].push(allocationId);
emit FundsAllocated(allocationId, category, recipient, amount);
}
/**
* @dev Allocate funds with custom vesting period
*/
function allocateFundsWithVesting(
string memory category,
address recipient,
uint256 amount,
uint256 vestingPeriod
)
external
onlyAuthorized
whenNotPaused
validAmount(amount)
validCategory(category)
nonReentrant
{
if (vestingPeriod > MAX_VESTING_PERIOD) {
revert InvalidVestingPeriod(vestingPeriod);
}
BudgetCategory storage budgetCategory = budgetCategories[category];
// Check budget availability
uint256 availableBudget = budgetCategory.totalBudget - budgetCategory.allocatedAmount;
if (amount > availableBudget) {
revert InsufficientBudget(category, amount, availableBudget);
}
// Check treasury balance
uint256 treasuryBalance = treasuryToken.balanceOf(address(this));
if (amount > treasuryBalance) {
revert InsufficientBalance(amount, treasuryBalance);
}
// Create allocation with custom vesting
uint256 allocationId = ++allocationCounter;
allocations[allocationId] = FundAllocation({
allocationId: allocationId,
category: category,
recipient: recipient,
totalAmount: amount,
releasedAmount: 0,
vestingPeriod: vestingPeriod,
vestingStart: block.timestamp,
lastRelease: block.timestamp,
isCompleted: false,
isActive: true,
allocatedBy: msg.sender,
createdAt: block.timestamp
});
// Update budget category
budgetCategory.allocatedAmount += amount;
categoryAllocations[category].push(allocationId);
recipientAllocations[recipient].push(allocationId);
emit FundsAllocated(allocationId, category, recipient, amount);
}
/**
* @dev Release vested funds
*/
function releaseVestedFunds(uint256 allocationId)
external
override
whenNotPaused
nonReentrant
{
FundAllocation storage allocation = allocations[allocationId];
if (allocation.allocationId == 0) {
revert AllocationNotFound(allocationId);
}
if (allocation.isCompleted) {
revert AllocationCompletedError(allocationId);
}
if (msg.sender != allocation.recipient && msg.sender != owner() && msg.sender != daoGovernance) {
revert NotAuthorized();
}
// Calculate vested amount
uint256 vestedAmount = calculateVestedAmount(allocation);
uint256 releasableAmount = vestedAmount - allocation.releasedAmount;
if (releasableAmount == 0) {
return; // Nothing to release
}
// Update allocation
allocation.releasedAmount += releasableAmount;
allocation.lastRelease = block.timestamp;
// Update budget category spent amount
budgetCategories[allocation.category].spentAmount += releasableAmount;
// Check if allocation is completed
if (allocation.releasedAmount >= allocation.totalAmount) {
allocation.isCompleted = true;
emit AllocationCompletedEvent(allocationId);
}
// Transfer tokens
treasuryToken.safeTransfer(allocation.recipient, releasableAmount);
emit FundsReleased(allocationId, allocation.recipient, releasableAmount);
}
/**
* @dev Calculate vested amount for an allocation
*/
function calculateVestedAmount(FundAllocation memory allocation) public view returns (uint256) {
if (block.timestamp < allocation.vestingStart) {
return 0;
}
uint256 timePassed = block.timestamp - allocation.vestingStart;
if (timePassed >= allocation.vestingPeriod) {
return allocation.totalAmount;
}
return (allocation.totalAmount * timePassed) / allocation.vestingPeriod;
}
/**
* @dev Get budget balance for a category
*/
function getBudgetBalance(string memory category) external view override returns (uint256) {
BudgetCategory memory budgetCategory = budgetCategories[category];
return budgetCategory.totalBudget - budgetCategory.allocatedAmount;
}
/**
* @dev Get allocation details
*/
function getAllocation(uint256 allocationId) external view override returns (address, uint256, uint256) {
FundAllocation memory allocation = allocations[allocationId];
return (allocation.recipient, allocation.totalAmount, allocation.releasedAmount);
}
/**
* @dev Get vested amount for an allocation
*/
function getVestedAmount(uint256 allocationId) external view returns (uint256) {
FundAllocation memory allocation = allocations[allocationId];
return calculateVestedAmount(allocation);
}
/**
* @dev Get all allocations for a recipient
*/
function getRecipientAllocations(address recipient) external view returns (uint256[] memory) {
return recipientAllocations[recipient];
}
/**
* @dev Get all allocations for a category
*/
function getCategoryAllocations(string memory category) external view returns (uint256[] memory) {
return categoryAllocations[category];
}
/**
* @dev Get all budget categories
*/
function getBudgetCategories() external view returns (string[] memory) {
return categoryNames;
}
/**
* @dev Get treasury statistics
*/
function getTreasuryStats() external view returns (
uint256 totalBudget,
uint256 allocatedAmount,
uint256 spentAmount,
uint256 availableBalance,
uint256 activeCategories
) {
uint256 _totalBudget = 0;
uint256 _allocatedAmount = 0;
uint256 _spentAmount = 0;
uint256 _activeCategories = 0;
for (uint256 i = 0; i < categoryNames.length; i++) {
BudgetCategory memory category = budgetCategories[categoryNames[i]];
if (category.isActive) {
_totalBudget += category.totalBudget;
_allocatedAmount += category.allocatedAmount;
_spentAmount += category.spentAmount;
_activeCategories++;
}
}
return (
_totalBudget,
_allocatedAmount,
_spentAmount,
treasuryToken.balanceOf(address(this)),
_activeCategories
);
}
/**
* @dev Deposit funds into treasury
*/
function depositFunds(uint256 amount) external whenNotPaused validAmount(amount) nonReentrant {
treasuryToken.safeTransferFrom(msg.sender, address(this), amount);
emit TreasuryDeposited(msg.sender, amount);
}
/**
* @dev Emergency withdraw from treasury
*/
function emergencyWithdraw(address token, uint256 amount) external onlyOwner {
if (token == address(treasuryToken)) {
treasuryToken.safeTransfer(msg.sender, amount);
} else {
IERC20(token).safeTransfer(msg.sender, amount);
}
emit TreasuryWithdrawn(msg.sender, amount);
}
/**
* @dev Deactivate a budget category
*/
function deactivateCategory(string memory category) external onlyAuthorized validCategory(category) {
budgetCategories[category].isActive = false;
emit CategoryDeactivated(category);
}
}