refactor(contracts): remove deprecated AIPowerRental contract in favor of bounty system
- Delete AIPowerRental.sol (566 lines) - replaced by AgentBounty.sol - Remove rental agreement system with provider/consumer model - Remove performance metrics and SLA tracking - Remove dispute resolution mechanism - Remove ZK-proof verification for performance - Remove provider/consumer authorization system - Bounty system provides superior developer incentive structure
This commit is contained in:
566
contracts/contracts/AIPowerRental.sol
Normal file
566
contracts/contracts/AIPowerRental.sol
Normal file
@@ -0,0 +1,566 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
||||
import "@openzeppelin/contracts/security/Pausable.sol";
|
||||
import "./ZKReceiptVerifier.sol";
|
||||
import "./Groth16Verifier.sol";
|
||||
|
||||
/**
|
||||
* @title AI Power Rental Contract
|
||||
* @dev Smart contract for AI compute power rental agreements with performance verification
|
||||
* @notice Manages rental agreements between AI compute providers and consumers
|
||||
*/
|
||||
contract AIPowerRental is Ownable, ReentrancyGuard, Pausable {
|
||||
|
||||
// State variables
|
||||
IERC20 public aitbcToken;
|
||||
ZKReceiptVerifier public zkVerifier;
|
||||
Groth16Verifier public groth16Verifier;
|
||||
|
||||
uint256 public agreementCounter;
|
||||
uint256 public platformFeePercentage = 250; // 2.5% in basis points
|
||||
uint256 public minRentalDuration = 3600; // 1 hour minimum
|
||||
uint256 public maxRentalDuration = 86400 * 30; // 30 days maximum
|
||||
|
||||
// Structs
|
||||
struct RentalAgreement {
|
||||
uint256 agreementId;
|
||||
address provider;
|
||||
address consumer;
|
||||
uint256 duration;
|
||||
uint256 price;
|
||||
uint256 startTime;
|
||||
uint256 endTime;
|
||||
uint256 platformFee;
|
||||
RentalStatus status;
|
||||
PerformanceMetrics performance;
|
||||
string gpuModel;
|
||||
uint256 computeUnits;
|
||||
bytes32 performanceProof;
|
||||
}
|
||||
|
||||
struct PerformanceMetrics {
|
||||
uint256 responseTime;
|
||||
uint256 accuracy;
|
||||
uint256 availability;
|
||||
uint256 computePower;
|
||||
bool withinSLA;
|
||||
uint256 lastUpdateTime;
|
||||
}
|
||||
|
||||
struct DisputeInfo {
|
||||
bool exists;
|
||||
address initiator;
|
||||
string reason;
|
||||
uint256 disputeTime;
|
||||
bool resolved;
|
||||
uint256 resolutionAmount;
|
||||
}
|
||||
|
||||
// Enums
|
||||
enum RentalStatus {
|
||||
Created,
|
||||
Active,
|
||||
Completed,
|
||||
Disputed,
|
||||
Cancelled,
|
||||
Expired
|
||||
}
|
||||
|
||||
// Mappings
|
||||
mapping(uint256 => RentalAgreement) public rentalAgreements;
|
||||
mapping(uint256 => DisputeInfo) public disputes;
|
||||
mapping(address => uint256[]) public providerAgreements;
|
||||
mapping(address => uint256[]) public consumerAgreements;
|
||||
mapping(address => bool) public authorizedProviders;
|
||||
mapping(address => bool) public authorizedConsumers;
|
||||
|
||||
// Events
|
||||
event AgreementCreated(
|
||||
uint256 indexed agreementId,
|
||||
address indexed provider,
|
||||
address indexed consumer,
|
||||
uint256 duration,
|
||||
uint256 price,
|
||||
string gpuModel,
|
||||
uint256 computeUnits
|
||||
);
|
||||
|
||||
event AgreementStarted(
|
||||
uint256 indexed agreementId,
|
||||
uint256 startTime,
|
||||
uint256 endTime
|
||||
);
|
||||
|
||||
event AgreementCompleted(
|
||||
uint256 indexed agreementId,
|
||||
uint256 completionTime,
|
||||
bool withinSLA
|
||||
);
|
||||
|
||||
event PaymentProcessed(
|
||||
uint256 indexed agreementId,
|
||||
address indexed provider,
|
||||
uint256 amount,
|
||||
uint256 platformFee
|
||||
);
|
||||
|
||||
event PerformanceSubmitted(
|
||||
uint256 indexed agreementId,
|
||||
uint256 responseTime,
|
||||
uint256 accuracy,
|
||||
uint256 availability,
|
||||
bool withinSLA
|
||||
);
|
||||
|
||||
event DisputeFiled(
|
||||
uint256 indexed agreementId,
|
||||
address indexed initiator,
|
||||
string reason
|
||||
);
|
||||
|
||||
event DisputeResolved(
|
||||
uint256 indexed agreementId,
|
||||
uint256 resolutionAmount,
|
||||
bool resolvedInFavorOfProvider
|
||||
);
|
||||
|
||||
event ProviderAuthorized(address indexed provider);
|
||||
event ProviderRevoked(address indexed provider);
|
||||
event ConsumerAuthorized(address indexed consumer);
|
||||
event ConsumerRevoked(address indexed consumer);
|
||||
|
||||
// Modifiers
|
||||
modifier onlyAuthorizedProvider() {
|
||||
require(authorizedProviders[msg.sender], "Not authorized provider");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier onlyAuthorizedConsumer() {
|
||||
require(authorizedConsumers[msg.sender], "Not authorized consumer");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier onlyParticipant(uint256 _agreementId) {
|
||||
require(
|
||||
rentalAgreements[_agreementId].provider == msg.sender ||
|
||||
rentalAgreements[_agreementId].consumer == msg.sender,
|
||||
"Not agreement participant"
|
||||
);
|
||||
_;
|
||||
}
|
||||
|
||||
modifier agreementExists(uint256 _agreementId) {
|
||||
require(_agreementId < agreementCounter, "Agreement does not exist");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier validStatus(uint256 _agreementId, RentalStatus _requiredStatus) {
|
||||
require(rentalAgreements[_agreementId].status == _requiredStatus, "Invalid agreement status");
|
||||
_;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
constructor(
|
||||
address _aitbcToken,
|
||||
address _zkVerifier,
|
||||
address _groth16Verifier
|
||||
) {
|
||||
aitbcToken = IERC20(_aitbcToken);
|
||||
zkVerifier = ZKReceiptVerifier(_zkVerifier);
|
||||
groth16Verifier = Groth16Verifier(_groth16Verifier);
|
||||
agreementCounter = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Creates a new rental agreement
|
||||
* @param _provider Address of the compute provider
|
||||
* @param _consumer Address of the compute consumer
|
||||
* @param _duration Duration in seconds
|
||||
* @param _price Total price in AITBC tokens
|
||||
* @param _gpuModel GPU model being rented
|
||||
* @param _computeUnits Amount of compute units
|
||||
*/
|
||||
function createRental(
|
||||
address _provider,
|
||||
address _consumer,
|
||||
uint256 _duration,
|
||||
uint256 _price,
|
||||
string memory _gpuModel,
|
||||
uint256 _computeUnits
|
||||
) external onlyAuthorizedConsumer nonReentrant whenNotPaused returns (uint256) {
|
||||
require(_duration >= minRentalDuration, "Duration too short");
|
||||
require(_duration <= maxRentalDuration, "Duration too long");
|
||||
require(_price > 0, "Price must be positive");
|
||||
require(authorizedProviders[_provider], "Provider not authorized");
|
||||
|
||||
uint256 agreementId = agreementCounter++;
|
||||
uint256 platformFee = (_price * platformFeePercentage) / 10000;
|
||||
|
||||
rentalAgreements[agreementId] = RentalAgreement({
|
||||
agreementId: agreementId,
|
||||
provider: _provider,
|
||||
consumer: _consumer,
|
||||
duration: _duration,
|
||||
price: _price,
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
platformFee: platformFee,
|
||||
status: RentalStatus.Created,
|
||||
performance: PerformanceMetrics({
|
||||
responseTime: 0,
|
||||
accuracy: 0,
|
||||
availability: 0,
|
||||
computePower: 0,
|
||||
withinSLA: false,
|
||||
lastUpdateTime: 0
|
||||
}),
|
||||
gpuModel: _gpuModel,
|
||||
computeUnits: _computeUnits,
|
||||
performanceProof: bytes32(0)
|
||||
});
|
||||
|
||||
providerAgreements[_provider].push(agreementId);
|
||||
consumerAgreements[_consumer].push(agreementId);
|
||||
|
||||
emit AgreementCreated(
|
||||
agreementId,
|
||||
_provider,
|
||||
_consumer,
|
||||
_duration,
|
||||
_price,
|
||||
_gpuModel,
|
||||
_computeUnits
|
||||
);
|
||||
|
||||
return agreementId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Starts a rental agreement and locks payment
|
||||
* @param _agreementId ID of the agreement to start
|
||||
*/
|
||||
function startRental(uint256 _agreementId)
|
||||
external
|
||||
agreementExists(_agreementId)
|
||||
validStatus(_agreementId, RentalStatus.Created)
|
||||
nonReentrant
|
||||
{
|
||||
RentalAgreement storage agreement = rentalAgreements[_agreementId];
|
||||
|
||||
require(msg.sender == agreement.consumer, "Only consumer can start");
|
||||
|
||||
uint256 totalAmount = agreement.price + agreement.platformFee;
|
||||
|
||||
// Transfer tokens from consumer to contract
|
||||
require(
|
||||
aitbcToken.transferFrom(msg.sender, address(this), totalAmount),
|
||||
"Payment transfer failed"
|
||||
);
|
||||
|
||||
agreement.startTime = block.timestamp;
|
||||
agreement.endTime = block.timestamp + agreement.duration;
|
||||
agreement.status = RentalStatus.Active;
|
||||
|
||||
emit AgreementStarted(_agreementId, agreement.startTime, agreement.endTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Completes a rental agreement and processes payment
|
||||
* @param _agreementId ID of the agreement to complete
|
||||
*/
|
||||
function completeRental(uint256 _agreementId)
|
||||
external
|
||||
agreementExists(_agreementId)
|
||||
validStatus(_agreementId, RentalStatus.Active)
|
||||
onlyParticipant(_agreementId)
|
||||
nonReentrant
|
||||
{
|
||||
RentalAgreement storage agreement = rentalAgreements[_agreementId];
|
||||
|
||||
require(block.timestamp >= agreement.endTime, "Rental period not ended");
|
||||
|
||||
agreement.status = RentalStatus.Completed;
|
||||
|
||||
// Process payment to provider
|
||||
uint256 providerAmount = agreement.price;
|
||||
uint256 platformFeeAmount = agreement.platformFee;
|
||||
|
||||
if (providerAmount > 0) {
|
||||
require(
|
||||
aitbcToken.transfer(agreement.provider, providerAmount),
|
||||
"Provider payment failed"
|
||||
);
|
||||
}
|
||||
|
||||
if (platformFeeAmount > 0) {
|
||||
require(
|
||||
aitbcToken.transfer(owner(), platformFeeAmount),
|
||||
"Platform fee transfer failed"
|
||||
);
|
||||
}
|
||||
|
||||
emit PaymentProcessed(_agreementId, agreement.provider, providerAmount, platformFeeAmount);
|
||||
emit AgreementCompleted(_agreementId, block.timestamp, agreement.performance.withinSLA);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Files a dispute for a rental agreement
|
||||
* @param _agreementId ID of the agreement
|
||||
* @param _reason Reason for the dispute
|
||||
*/
|
||||
function disputeRental(uint256 _agreementId, string memory _reason)
|
||||
external
|
||||
agreementExists(_agreementId)
|
||||
onlyParticipant(_agreementId)
|
||||
nonReentrant
|
||||
{
|
||||
RentalAgreement storage agreement = rentalAgreements[_agreementId];
|
||||
|
||||
require(
|
||||
agreement.status == RentalStatus.Active ||
|
||||
agreement.status == RentalStatus.Completed,
|
||||
"Cannot dispute this agreement"
|
||||
);
|
||||
|
||||
require(!disputes[_agreementId].exists, "Dispute already exists");
|
||||
|
||||
disputes[_agreementId] = DisputeInfo({
|
||||
exists: true,
|
||||
initiator: msg.sender,
|
||||
reason: _reason,
|
||||
disputeTime: block.timestamp,
|
||||
resolved: false,
|
||||
resolutionAmount: 0
|
||||
});
|
||||
|
||||
agreement.status = RentalStatus.Disputed;
|
||||
|
||||
emit DisputeFiled(_agreementId, msg.sender, _reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Submits performance metrics for a rental agreement
|
||||
* @param _agreementId ID of the agreement
|
||||
* @param _responseTime Response time in milliseconds
|
||||
* @param _accuracy Accuracy percentage (0-100)
|
||||
* @param _availability Availability percentage (0-100)
|
||||
* @param _computePower Compute power utilized
|
||||
* @param _zkProof Zero-knowledge proof for performance verification
|
||||
*/
|
||||
function submitPerformance(
|
||||
uint256 _agreementId,
|
||||
uint256 _responseTime,
|
||||
uint256 _accuracy,
|
||||
uint256 _availability,
|
||||
uint256 _computePower,
|
||||
bytes memory _zkProof
|
||||
) external agreementExists(_agreementId) onlyAuthorizedProvider {
|
||||
RentalAgreement storage agreement = rentalAgreements[_agreementId];
|
||||
|
||||
require(agreement.status == RentalStatus.Active, "Agreement not active");
|
||||
|
||||
// Verify ZK proof
|
||||
bool proofValid = zkVerifier.verifyPerformanceProof(
|
||||
_agreementId,
|
||||
_responseTime,
|
||||
_accuracy,
|
||||
_availability,
|
||||
_computePower,
|
||||
_zkProof
|
||||
);
|
||||
|
||||
require(proofValid, "Invalid performance proof");
|
||||
|
||||
agreement.performance = PerformanceMetrics({
|
||||
responseTime: _responseTime,
|
||||
accuracy: _accuracy,
|
||||
availability: _availability,
|
||||
computePower: _computePower,
|
||||
withinSLA: _calculateSLA(_responseTime, _accuracy, _availability),
|
||||
lastUpdateTime: block.timestamp
|
||||
});
|
||||
|
||||
agreement.performanceProof = keccak256(_zkProof);
|
||||
|
||||
emit PerformanceSubmitted(
|
||||
_agreementId,
|
||||
_responseTime,
|
||||
_accuracy,
|
||||
_availability,
|
||||
agreement.performance.withinSLA
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Authorizes a provider to offer compute services
|
||||
* @param _provider Address of the provider
|
||||
*/
|
||||
function authorizeProvider(address _provider) external onlyOwner {
|
||||
authorizedProviders[_provider] = true;
|
||||
emit ProviderAuthorized(_provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Revokes provider authorization
|
||||
* @param _provider Address of the provider
|
||||
*/
|
||||
function revokeProvider(address _provider) external onlyOwner {
|
||||
authorizedProviders[_provider] = false;
|
||||
emit ProviderRevoked(_provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Authorizes a consumer to rent compute services
|
||||
* @param _consumer Address of the consumer
|
||||
*/
|
||||
function authorizeConsumer(address _consumer) external onlyOwner {
|
||||
authorizedConsumers[_consumer] = true;
|
||||
emit ConsumerAuthorized(_consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Revokes consumer authorization
|
||||
* @param _consumer Address of the consumer
|
||||
*/
|
||||
function revokeConsumer(address _consumer) external onlyOwner {
|
||||
authorizedConsumers[_consumer] = false;
|
||||
emit ConsumerRevoked(_consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Resolves a dispute
|
||||
* @param _agreementId ID of the disputed agreement
|
||||
* @param _resolutionAmount Amount to award to the winner
|
||||
* @param _resolveInFavorOfProvider True if resolving in favor of provider
|
||||
*/
|
||||
function resolveDispute(
|
||||
uint256 _agreementId,
|
||||
uint256 _resolutionAmount,
|
||||
bool _resolveInFavorOfProvider
|
||||
) external onlyOwner agreementExists(_agreementId) {
|
||||
require(disputes[_agreementId].exists, "No dispute exists");
|
||||
require(!disputes[_agreementId].resolved, "Dispute already resolved");
|
||||
|
||||
RentalAgreement storage agreement = rentalAgreements[_agreementId];
|
||||
disputes[_agreementId].resolved = true;
|
||||
disputes[_agreementId].resolutionAmount = _resolutionAmount;
|
||||
|
||||
address winner = _resolveInFavorOfProvider ? agreement.provider : agreement.consumer;
|
||||
|
||||
if (_resolutionAmount > 0) {
|
||||
require(
|
||||
aitbcToken.transfer(winner, _resolutionAmount),
|
||||
"Resolution payment failed"
|
||||
);
|
||||
}
|
||||
|
||||
emit DisputeResolved(_agreementId, _resolutionAmount, _resolveInFavorOfProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Cancels a rental agreement (only before it starts)
|
||||
* @param _agreementId ID of the agreement to cancel
|
||||
*/
|
||||
function cancelRental(uint256 _agreementId)
|
||||
external
|
||||
agreementExists(_agreementId)
|
||||
validStatus(_agreementId, RentalStatus.Created)
|
||||
onlyParticipant(_agreementId)
|
||||
nonReentrant
|
||||
{
|
||||
RentalAgreement storage agreement = rentalAgreements[_agreementId];
|
||||
agreement.status = RentalStatus.Cancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Emergency pause function
|
||||
*/
|
||||
function pause() external onlyOwner {
|
||||
_pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Unpause function
|
||||
*/
|
||||
function unpause() external onlyOwner {
|
||||
_unpause();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Updates platform fee percentage
|
||||
* @param _newFee New fee percentage in basis points
|
||||
*/
|
||||
function updatePlatformFee(uint256 _newFee) external onlyOwner {
|
||||
require(_newFee <= 1000, "Fee too high"); // Max 10%
|
||||
platformFeePercentage = _newFee;
|
||||
}
|
||||
|
||||
// View functions
|
||||
|
||||
/**
|
||||
* @dev Gets rental agreement details
|
||||
* @param _agreementId ID of the agreement
|
||||
*/
|
||||
function getRentalAgreement(uint256 _agreementId)
|
||||
external
|
||||
view
|
||||
agreementExists(_agreementId)
|
||||
returns (RentalAgreement memory)
|
||||
{
|
||||
return rentalAgreements[_agreementId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets dispute information
|
||||
* @param _agreementId ID of the agreement
|
||||
*/
|
||||
function getDisputeInfo(uint256 _agreementId)
|
||||
external
|
||||
view
|
||||
agreementExists(_agreementId)
|
||||
returns (DisputeInfo memory)
|
||||
{
|
||||
return disputes[_agreementId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets all agreements for a provider
|
||||
* @param _provider Address of the provider
|
||||
*/
|
||||
function getProviderAgreements(address _provider)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory)
|
||||
{
|
||||
return providerAgreements[_provider];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets all agreements for a consumer
|
||||
* @param _consumer Address of the consumer
|
||||
*/
|
||||
function getConsumerAgreements(address _consumer)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory)
|
||||
{
|
||||
return consumerAgreements[_consumer];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Calculates if performance meets SLA requirements
|
||||
*/
|
||||
function _calculateSLA(
|
||||
uint256 _responseTime,
|
||||
uint256 _accuracy,
|
||||
uint256 _availability
|
||||
) internal pure returns (bool) {
|
||||
return _responseTime <= 5000 && // <= 5 seconds
|
||||
_accuracy >= 95 && // >= 95% accuracy
|
||||
_availability >= 99; // >= 99% availability
|
||||
}
|
||||
}
|
||||
696
contracts/contracts/AITBCPaymentProcessor.sol
Normal file
696
contracts/contracts/AITBCPaymentProcessor.sol
Normal file
@@ -0,0 +1,696 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
||||
import "@openzeppelin/contracts/security/Pausable.sol";
|
||||
import "./AIPowerRental.sol";
|
||||
|
||||
/**
|
||||
* @title AITBC Payment Processor
|
||||
* @dev Advanced payment processing contract with escrow, automated releases, and dispute resolution
|
||||
* @notice Handles AITBC token payments for AI power rental services
|
||||
*/
|
||||
contract AITBCPaymentProcessor is Ownable, ReentrancyGuard, Pausable {
|
||||
|
||||
// State variables
|
||||
IERC20 public aitbcToken;
|
||||
AIPowerRental public aiPowerRental;
|
||||
|
||||
uint256 public paymentCounter;
|
||||
uint256 public platformFeePercentage = 250; // 2.5% in basis points
|
||||
uint256 public disputeResolutionFee = 100; // 1% in basis points
|
||||
uint256 public minPaymentAmount = 1e15; // 0.001 AITBC minimum
|
||||
uint256 public maxPaymentAmount = 1e22; // 10,000 AITBC maximum
|
||||
|
||||
// Structs
|
||||
struct Payment {
|
||||
uint256 paymentId;
|
||||
address from;
|
||||
address to;
|
||||
uint256 amount;
|
||||
uint256 platformFee;
|
||||
uint256 disputeFee;
|
||||
PaymentStatus status;
|
||||
uint256 releaseTime;
|
||||
uint256 createdTime;
|
||||
uint256 confirmedTime;
|
||||
bytes32 agreementId;
|
||||
string paymentPurpose;
|
||||
ReleaseCondition releaseCondition;
|
||||
bytes32 conditionHash;
|
||||
}
|
||||
|
||||
struct EscrowAccount {
|
||||
uint256 escrowId;
|
||||
address depositor;
|
||||
address beneficiary;
|
||||
uint256 amount;
|
||||
uint256 releaseTime;
|
||||
bool isReleased;
|
||||
bool isRefunded;
|
||||
bytes32 releaseCondition;
|
||||
uint256 createdTime;
|
||||
EscrowType escrowType;
|
||||
}
|
||||
|
||||
struct ScheduledPayment {
|
||||
uint256 scheduleId;
|
||||
uint256 paymentId;
|
||||
uint256 nextReleaseTime;
|
||||
uint256 releaseInterval;
|
||||
uint256 totalReleases;
|
||||
uint256 releasedCount;
|
||||
bool isActive;
|
||||
}
|
||||
|
||||
// Enums
|
||||
enum PaymentStatus {
|
||||
Created,
|
||||
Confirmed,
|
||||
HeldInEscrow,
|
||||
Released,
|
||||
Refunded,
|
||||
Disputed,
|
||||
Cancelled
|
||||
}
|
||||
|
||||
enum EscrowType {
|
||||
Standard,
|
||||
PerformanceBased,
|
||||
TimeBased,
|
||||
Conditional
|
||||
}
|
||||
|
||||
enum ReleaseCondition {
|
||||
Immediate,
|
||||
Manual,
|
||||
Performance,
|
||||
TimeBased,
|
||||
DisputeResolution
|
||||
}
|
||||
|
||||
// Mappings
|
||||
mapping(uint256 => Payment) public payments;
|
||||
mapping(uint256 => EscrowAccount) public escrowAccounts;
|
||||
mapping(uint256 => ScheduledPayment) public scheduledPayments;
|
||||
mapping(address => uint256[]) public senderPayments;
|
||||
mapping(address => uint256[]) public recipientPayments;
|
||||
mapping(bytes32 => uint256) public agreementPayments;
|
||||
mapping(address => uint256) public userEscrowBalance;
|
||||
mapping(address => bool) public authorizedPayees;
|
||||
mapping(address => bool) public authorizedPayers;
|
||||
|
||||
// Events
|
||||
event PaymentCreated(
|
||||
uint256 indexed paymentId,
|
||||
address indexed from,
|
||||
address indexed to,
|
||||
uint256 amount,
|
||||
bytes32 agreementId,
|
||||
string paymentPurpose
|
||||
);
|
||||
|
||||
event PaymentConfirmed(
|
||||
uint256 indexed paymentId,
|
||||
uint256 confirmedTime,
|
||||
bytes32 transactionHash
|
||||
);
|
||||
|
||||
event PaymentReleased(
|
||||
uint256 indexed paymentId,
|
||||
address indexed to,
|
||||
uint256 amount,
|
||||
uint256 platformFee
|
||||
);
|
||||
|
||||
event PaymentRefunded(
|
||||
uint256 indexed paymentId,
|
||||
address indexed to,
|
||||
uint256 amount,
|
||||
string reason
|
||||
);
|
||||
|
||||
event EscrowCreated(
|
||||
uint256 indexed escrowId,
|
||||
address indexed depositor,
|
||||
address indexed beneficiary,
|
||||
uint256 amount,
|
||||
EscrowType escrowType
|
||||
);
|
||||
|
||||
event EscrowReleased(
|
||||
uint256 indexed escrowId,
|
||||
uint256 amount,
|
||||
bytes32 conditionHash
|
||||
);
|
||||
|
||||
event EscrowRefunded(
|
||||
uint256 indexed escrowId,
|
||||
address indexed depositor,
|
||||
uint256 amount,
|
||||
string reason
|
||||
);
|
||||
|
||||
event ScheduledPaymentCreated(
|
||||
uint256 indexed scheduleId,
|
||||
uint256 indexed paymentId,
|
||||
uint256 nextReleaseTime,
|
||||
uint256 releaseInterval
|
||||
);
|
||||
|
||||
event ScheduledPaymentReleased(
|
||||
uint256 indexed scheduleId,
|
||||
uint256 indexed paymentId,
|
||||
uint256 releaseCount
|
||||
);
|
||||
|
||||
event DisputeInitiated(
|
||||
uint256 indexed paymentId,
|
||||
address indexed initiator,
|
||||
string reason
|
||||
);
|
||||
|
||||
event DisputeResolved(
|
||||
uint256 indexed paymentId,
|
||||
uint256 resolutionAmount,
|
||||
bool resolvedInFavorOfPayer
|
||||
);
|
||||
|
||||
event PlatformFeeCollected(
|
||||
uint256 indexed paymentId,
|
||||
uint256 feeAmount,
|
||||
address indexed collector
|
||||
);
|
||||
|
||||
// Modifiers
|
||||
modifier onlyAuthorizedPayer() {
|
||||
require(authorizedPayers[msg.sender], "Not authorized payer");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier onlyAuthorizedPayee() {
|
||||
require(authorizedPayees[msg.sender], "Not authorized payee");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier paymentExists(uint256 _paymentId) {
|
||||
require(_paymentId < paymentCounter, "Payment does not exist");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier validStatus(uint256 _paymentId, PaymentStatus _requiredStatus) {
|
||||
require(payments[_paymentId].status == _requiredStatus, "Invalid payment status");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier sufficientBalance(address _user, uint256 _amount) {
|
||||
require(aitbcToken.balanceOf(_user) >= _amount, "Insufficient balance");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier sufficientAllowance(address _user, uint256 _amount) {
|
||||
require(aitbcToken.allowance(_user, address(this)) >= _amount, "Insufficient allowance");
|
||||
_;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
constructor(address _aitbcToken, address _aiPowerRental) {
|
||||
aitbcToken = IERC20(_aitbcToken);
|
||||
aiPowerRental = AIPowerRental(_aiPowerRental);
|
||||
paymentCounter = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Creates a new payment
|
||||
* @param _to Recipient address
|
||||
* @param _amount Payment amount
|
||||
* @param _agreementId Associated agreement ID
|
||||
* @param _paymentPurpose Purpose of the payment
|
||||
* @param _releaseCondition Release condition
|
||||
*/
|
||||
function createPayment(
|
||||
address _to,
|
||||
uint256 _amount,
|
||||
bytes32 _agreementId,
|
||||
string memory _paymentPurpose,
|
||||
ReleaseCondition _releaseCondition
|
||||
) external onlyAuthorizedPayer sufficientBalance(msg.sender, _amount) sufficientAllowance(msg.sender, _amount) nonReentrant whenNotPaused returns (uint256) {
|
||||
require(_amount >= minPaymentAmount, "Amount below minimum");
|
||||
require(_amount <= maxPaymentAmount, "Amount above maximum");
|
||||
require(_to != address(0), "Invalid recipient");
|
||||
require(authorizedPayees[_to], "Recipient not authorized");
|
||||
|
||||
uint256 paymentId = paymentCounter++;
|
||||
uint256 platformFee = (_amount * platformFeePercentage) / 10000;
|
||||
uint256 disputeFee = (_amount * disputeResolutionFee) / 10000;
|
||||
uint256 totalAmount = _amount + platformFee + disputeFee;
|
||||
|
||||
payments[paymentId] = Payment({
|
||||
paymentId: paymentId,
|
||||
from: msg.sender,
|
||||
to: _to,
|
||||
amount: _amount,
|
||||
platformFee: platformFee,
|
||||
disputeFee: disputeFee,
|
||||
status: PaymentStatus.Created,
|
||||
releaseTime: 0,
|
||||
createdTime: block.timestamp,
|
||||
confirmedTime: 0,
|
||||
agreementId: _agreementId,
|
||||
paymentPurpose: _paymentPurpose,
|
||||
releaseCondition: _releaseCondition,
|
||||
conditionHash: bytes32(0)
|
||||
});
|
||||
|
||||
senderPayments[msg.sender].push(paymentId);
|
||||
recipientPayments[_to].push(paymentId);
|
||||
|
||||
if (_agreementId != bytes32(0)) {
|
||||
agreementPayments[_agreementId] = paymentId;
|
||||
}
|
||||
|
||||
// Transfer tokens to contract
|
||||
require(
|
||||
aitbcToken.transferFrom(msg.sender, address(this), totalAmount),
|
||||
"Payment transfer failed"
|
||||
);
|
||||
|
||||
emit PaymentCreated(paymentId, msg.sender, _to, _amount, _agreementId, _paymentPurpose);
|
||||
|
||||
return paymentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Confirms a payment with transaction hash
|
||||
* @param _paymentId ID of the payment
|
||||
* @param _transactionHash Blockchain transaction hash
|
||||
*/
|
||||
function confirmPayment(uint256 _paymentId, bytes32 _transactionHash)
|
||||
external
|
||||
paymentExists(_paymentId)
|
||||
validStatus(_paymentId, PaymentStatus.Created)
|
||||
nonReentrant
|
||||
{
|
||||
Payment storage payment = payments[_paymentId];
|
||||
|
||||
require(msg.sender == payment.from, "Only payer can confirm");
|
||||
|
||||
payment.status = PaymentStatus.Confirmed;
|
||||
payment.confirmedTime = block.timestamp;
|
||||
payment.conditionHash = _transactionHash;
|
||||
|
||||
// Handle immediate release
|
||||
if (payment.releaseCondition == ReleaseCondition.Immediate) {
|
||||
_releasePayment(_paymentId);
|
||||
} else if (payment.releaseCondition == ReleaseCondition.TimeBased) {
|
||||
payment.status = PaymentStatus.HeldInEscrow;
|
||||
payment.releaseTime = block.timestamp + 1 hours; // Default 1 hour hold
|
||||
} else {
|
||||
payment.status = PaymentStatus.HeldInEscrow;
|
||||
}
|
||||
|
||||
emit PaymentConfirmed(_paymentId, block.timestamp, _transactionHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Releases a payment to the recipient
|
||||
* @param _paymentId ID of the payment
|
||||
*/
|
||||
function releasePayment(uint256 _paymentId)
|
||||
external
|
||||
paymentExists(_paymentId)
|
||||
nonReentrant
|
||||
{
|
||||
Payment storage payment = payments[_paymentId];
|
||||
|
||||
require(
|
||||
payment.status == PaymentStatus.Confirmed ||
|
||||
payment.status == PaymentStatus.HeldInEscrow,
|
||||
"Payment not ready for release"
|
||||
);
|
||||
|
||||
if (payment.releaseCondition == ReleaseCondition.Manual) {
|
||||
require(msg.sender == payment.from, "Only payer can release manually");
|
||||
} else if (payment.releaseCondition == ReleaseCondition.TimeBased) {
|
||||
require(block.timestamp >= payment.releaseTime, "Release time not reached");
|
||||
}
|
||||
|
||||
_releasePayment(_paymentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Creates an escrow account
|
||||
* @param _beneficiary Beneficiary address
|
||||
* @param _amount Amount to lock in escrow
|
||||
* @param _releaseTime Release time (0 for no time limit)
|
||||
* @param _escrowType Type of escrow
|
||||
* @param _releaseCondition Release condition hash
|
||||
*/
|
||||
function createEscrow(
|
||||
address _beneficiary,
|
||||
uint256 _amount,
|
||||
uint256 _releaseTime,
|
||||
EscrowType _escrowType,
|
||||
bytes32 _releaseCondition
|
||||
) external onlyAuthorizedPayer sufficientBalance(msg.sender, _amount) sufficientAllowance(msg.sender, _amount) nonReentrant whenNotPaused returns (uint256) {
|
||||
require(_beneficiary != address(0), "Invalid beneficiary");
|
||||
require(_amount >= minPaymentAmount, "Amount below minimum");
|
||||
|
||||
uint256 escrowId = paymentCounter++;
|
||||
|
||||
escrowAccounts[escrowId] = EscrowAccount({
|
||||
escrowId: escrowId,
|
||||
depositor: msg.sender,
|
||||
beneficiary: _beneficiary,
|
||||
amount: _amount,
|
||||
releaseTime: _releaseTime,
|
||||
isReleased: false,
|
||||
isRefunded: false,
|
||||
releaseCondition: _releaseCondition,
|
||||
createdTime: block.timestamp,
|
||||
escrowType: _escrowType
|
||||
});
|
||||
|
||||
// Transfer tokens to contract
|
||||
require(
|
||||
aitbcToken.transferFrom(msg.sender, address(this), _amount),
|
||||
"Escrow transfer failed"
|
||||
);
|
||||
|
||||
userEscrowBalance[msg.sender] += _amount;
|
||||
|
||||
emit EscrowCreated(escrowId, msg.sender, _beneficiary, _amount, _escrowType);
|
||||
|
||||
return escrowId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Releases escrow to beneficiary
|
||||
* @param _escrowId ID of the escrow account
|
||||
*/
|
||||
function releaseEscrow(uint256 _escrowId)
|
||||
external
|
||||
nonReentrant
|
||||
{
|
||||
EscrowAccount storage escrow = escrowAccounts[_escrowId];
|
||||
|
||||
require(!escrow.isReleased, "Escrow already released");
|
||||
require(!escrow.isRefunded, "Escrow already refunded");
|
||||
require(
|
||||
escrow.releaseTime == 0 || block.timestamp >= escrow.releaseTime,
|
||||
"Release time not reached"
|
||||
);
|
||||
|
||||
escrow.isReleased = true;
|
||||
userEscrowBalance[escrow.depositor] -= escrow.amount;
|
||||
|
||||
require(
|
||||
aitbcToken.transfer(escrow.beneficiary, escrow.amount),
|
||||
"Escrow release failed"
|
||||
);
|
||||
|
||||
emit EscrowReleased(_escrowId, escrow.amount, escrow.releaseCondition);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Refunds escrow to depositor
|
||||
* @param _escrowId ID of the escrow account
|
||||
* @param _reason Reason for refund
|
||||
*/
|
||||
function refundEscrow(uint256 _escrowId, string memory _reason)
|
||||
external
|
||||
nonReentrant
|
||||
{
|
||||
EscrowAccount storage escrow = escrowAccounts[_escrowId];
|
||||
|
||||
require(!escrow.isReleased, "Escrow already released");
|
||||
require(!escrow.isRefunded, "Escrow already refunded");
|
||||
require(
|
||||
msg.sender == escrow.depositor || msg.sender == owner(),
|
||||
"Only depositor or owner can refund"
|
||||
);
|
||||
|
||||
escrow.isRefunded = true;
|
||||
userEscrowBalance[escrow.depositor] -= escrow.amount;
|
||||
|
||||
require(
|
||||
aitbcToken.transfer(escrow.depositor, escrow.amount),
|
||||
"Escrow refund failed"
|
||||
);
|
||||
|
||||
emit EscrowRefunded(_escrowId, escrow.depositor, escrow.amount, _reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Initiates a dispute for a payment
|
||||
* @param _paymentId ID of the payment
|
||||
* @param _reason Reason for dispute
|
||||
*/
|
||||
function initiateDispute(uint256 _paymentId, string memory _reason)
|
||||
external
|
||||
paymentExists(_paymentId)
|
||||
nonReentrant
|
||||
{
|
||||
Payment storage payment = payments[_paymentId];
|
||||
|
||||
require(
|
||||
payment.status == PaymentStatus.Confirmed ||
|
||||
payment.status == PaymentStatus.HeldInEscrow,
|
||||
"Cannot dispute this payment"
|
||||
);
|
||||
|
||||
require(
|
||||
msg.sender == payment.from || msg.sender == payment.to,
|
||||
"Only payment participants can dispute"
|
||||
);
|
||||
|
||||
payment.status = PaymentStatus.Disputed;
|
||||
|
||||
emit DisputeInitiated(_paymentId, msg.sender, _reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Resolves a dispute
|
||||
* @param _paymentId ID of the disputed payment
|
||||
* @param _resolutionAmount Amount to award to the winner
|
||||
* @param _resolveInFavorOfPayer True if resolving in favor of payer
|
||||
*/
|
||||
function resolveDispute(
|
||||
uint256 _paymentId,
|
||||
uint256 _resolutionAmount,
|
||||
bool _resolveInFavorOfPayer
|
||||
) external onlyOwner paymentExists(_paymentId) nonReentrant {
|
||||
Payment storage payment = payments[_paymentId];
|
||||
|
||||
require(payment.status == PaymentStatus.Disputed, "Payment not disputed");
|
||||
require(_resolutionAmount <= payment.amount, "Resolution amount too high");
|
||||
|
||||
address winner = _resolveInFavorOfPayer ? payment.from : payment.to;
|
||||
address loser = _resolveInFavorOfPayer ? payment.to : payment.from;
|
||||
|
||||
// Calculate refund for loser
|
||||
uint256 refundAmount = payment.amount - _resolutionAmount;
|
||||
|
||||
// Transfer resolution amount to winner
|
||||
if (_resolutionAmount > 0) {
|
||||
require(
|
||||
aitbcToken.transfer(winner, _resolutionAmount),
|
||||
"Resolution payment failed"
|
||||
);
|
||||
}
|
||||
|
||||
// Refund remaining amount to loser
|
||||
if (refundAmount > 0) {
|
||||
require(
|
||||
aitbcToken.transfer(loser, refundAmount),
|
||||
"Refund payment failed"
|
||||
);
|
||||
}
|
||||
|
||||
payment.status = PaymentStatus.Released;
|
||||
|
||||
emit DisputeResolved(_paymentId, _resolutionAmount, _resolveInFavorOfPayer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Claims platform fees
|
||||
* @param _paymentId ID of the payment
|
||||
*/
|
||||
function claimPlatformFee(uint256 _paymentId)
|
||||
external
|
||||
onlyOwner
|
||||
paymentExists(_paymentId)
|
||||
nonReentrant
|
||||
{
|
||||
Payment storage payment = payments[_paymentId];
|
||||
|
||||
require(payment.status == PaymentStatus.Released, "Payment not released");
|
||||
require(payment.platformFee > 0, "No platform fee to claim");
|
||||
|
||||
uint256 feeAmount = payment.platformFee;
|
||||
payment.platformFee = 0;
|
||||
|
||||
require(
|
||||
aitbcToken.transfer(owner(), feeAmount),
|
||||
"Platform fee transfer failed"
|
||||
);
|
||||
|
||||
emit PlatformFeeCollected(_paymentId, feeAmount, owner());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Authorizes a payee
|
||||
* @param _payee Address to authorize
|
||||
*/
|
||||
function authorizePayee(address _payee) external onlyOwner {
|
||||
authorizedPayees[_payee] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Revokes payee authorization
|
||||
* @param _payee Address to revoke
|
||||
*/
|
||||
function revokePayee(address _payee) external onlyOwner {
|
||||
authorizedPayees[_payee] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Authorizes a payer
|
||||
* @param _payer Address to authorize
|
||||
*/
|
||||
function authorizePayer(address _payer) external onlyOwner {
|
||||
authorizedPayers[_payer] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Revokes payer authorization
|
||||
* @param _payer Address to revoke
|
||||
*/
|
||||
function revokePayer(address _payer) external onlyOwner {
|
||||
authorizedPayers[_payer] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Updates platform fee percentage
|
||||
* @param _newFee New fee percentage in basis points
|
||||
*/
|
||||
function updatePlatformFee(uint256 _newFee) external onlyOwner {
|
||||
require(_newFee <= 1000, "Fee too high"); // Max 10%
|
||||
platformFeePercentage = _newFee;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Emergency pause function
|
||||
*/
|
||||
function pause() external onlyOwner {
|
||||
_pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Unpause function
|
||||
*/
|
||||
function unpause() external onlyOwner {
|
||||
_unpause();
|
||||
}
|
||||
|
||||
// Internal functions
|
||||
|
||||
function _releasePayment(uint256 _paymentId) internal {
|
||||
Payment storage payment = payments[_paymentId];
|
||||
|
||||
payment.status = PaymentStatus.Released;
|
||||
|
||||
// Transfer amount to recipient
|
||||
require(
|
||||
aitbcToken.transfer(payment.to, payment.amount),
|
||||
"Payment transfer failed"
|
||||
);
|
||||
|
||||
// Transfer platform fee to owner
|
||||
if (payment.platformFee > 0) {
|
||||
require(
|
||||
aitbcToken.transfer(owner(), payment.platformFee),
|
||||
"Platform fee transfer failed"
|
||||
);
|
||||
}
|
||||
|
||||
emit PaymentReleased(_paymentId, payment.to, payment.amount, payment.platformFee);
|
||||
}
|
||||
|
||||
// View functions
|
||||
|
||||
/**
|
||||
* @dev Gets payment details
|
||||
* @param _paymentId ID of the payment
|
||||
*/
|
||||
function getPayment(uint256 _paymentId)
|
||||
external
|
||||
view
|
||||
paymentExists(_paymentId)
|
||||
returns (Payment memory)
|
||||
{
|
||||
return payments[_paymentId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets escrow account details
|
||||
* @param _escrowId ID of the escrow account
|
||||
*/
|
||||
function getEscrowAccount(uint256 _escrowId)
|
||||
external
|
||||
view
|
||||
returns (EscrowAccount memory)
|
||||
{
|
||||
return escrowAccounts[_escrowId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets all payments for a sender
|
||||
* @param _sender Address of the sender
|
||||
*/
|
||||
function getSenderPayments(address _sender)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory)
|
||||
{
|
||||
return senderPayments[_sender];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets all payments for a recipient
|
||||
* @param _recipient Address of the recipient
|
||||
*/
|
||||
function getRecipientPayments(address _recipient)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory)
|
||||
{
|
||||
return recipientPayments[_recipient];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets payment associated with an agreement
|
||||
* @param _agreementId ID of the agreement
|
||||
*/
|
||||
function getAgreementPayment(bytes32 _agreementId)
|
||||
external
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
return agreementPayments[_agreementId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets user's escrow balance
|
||||
* @param _user Address of the user
|
||||
*/
|
||||
function getUserEscrowBalance(address _user)
|
||||
external
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
return userEscrowBalance[_user];
|
||||
}
|
||||
}
|
||||
15
contracts/contracts/AIToken.sol
Normal file
15
contracts/contracts/AIToken.sol
Normal file
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||
|
||||
contract AIToken is ERC20, Ownable {
|
||||
constructor(uint256 initialSupply) ERC20("AI Token", "AIT") {
|
||||
_mint(msg.sender, initialSupply);
|
||||
}
|
||||
|
||||
function mint(address to, uint256 amount) public onlyOwner {
|
||||
_mint(to, amount);
|
||||
}
|
||||
}
|
||||
718
contracts/contracts/AgentBounty.sol
Normal file
718
contracts/contracts/AgentBounty.sol
Normal file
@@ -0,0 +1,718 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
||||
import "@openzeppelin/contracts/security/Pausable.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import "./PerformanceVerifier.sol";
|
||||
import "./AIToken.sol";
|
||||
|
||||
/**
|
||||
* @title Agent Bounty System
|
||||
* @dev Automated bounty board for AI agent capabilities with ZK-proof verification
|
||||
* @notice Allows DAO and users to create bounties that are automatically completed when agents submit valid ZK-proofs
|
||||
*/
|
||||
contract AgentBounty is Ownable, ReentrancyGuard, Pausable {
|
||||
|
||||
// State variables
|
||||
IERC20 public aitbcToken;
|
||||
PerformanceVerifier public performanceVerifier;
|
||||
|
||||
uint256 public bountyCounter;
|
||||
uint256 public creationFeePercentage = 50; // 0.5% in basis points
|
||||
uint256 public successFeePercentage = 200; // 2% in basis points
|
||||
uint256 public disputeFeePercentage = 10; // 0.1% in basis points
|
||||
uint256 public platformFeePercentage = 100; // 1% in basis points
|
||||
|
||||
// Bounty tiers
|
||||
enum BountyTier { BRONZE, SILVER, GOLD, PLATINUM }
|
||||
|
||||
// Bounty status
|
||||
enum BountyStatus { CREATED, ACTIVE, SUBMITTED, VERIFIED, COMPLETED, EXPIRED, DISPUTED }
|
||||
|
||||
// Submission status
|
||||
enum SubmissionStatus { PENDING, VERIFIED, REJECTED, DISPUTED }
|
||||
|
||||
// Structs
|
||||
struct Bounty {
|
||||
uint256 bountyId;
|
||||
string title;
|
||||
string description;
|
||||
uint256 rewardAmount;
|
||||
address creator;
|
||||
BountyTier tier;
|
||||
BountyStatus status;
|
||||
bytes32 performanceCriteria; // Hash of performance requirements
|
||||
uint256 minAccuracy;
|
||||
uint256 deadline;
|
||||
uint256 creationTime;
|
||||
uint256 maxSubmissions;
|
||||
uint256 submissionCount;
|
||||
address winningSubmission;
|
||||
bool requiresZKProof;
|
||||
mapping(address => bool) authorizedSubmitters;
|
||||
}
|
||||
|
||||
struct Submission {
|
||||
uint256 submissionId;
|
||||
uint256 bountyId;
|
||||
address submitter;
|
||||
bytes zkProof;
|
||||
bytes32 performanceHash;
|
||||
uint256 accuracy;
|
||||
uint256 responseTime;
|
||||
uint256 submissionTime;
|
||||
SubmissionStatus status;
|
||||
string disputeReason;
|
||||
address verifier;
|
||||
}
|
||||
|
||||
struct BountyStats {
|
||||
uint256 totalBounties;
|
||||
uint256 activeBounties;
|
||||
uint256 completedBounties;
|
||||
uint256 totalValueLocked;
|
||||
uint256 averageReward;
|
||||
uint256 successRate;
|
||||
}
|
||||
|
||||
// Mappings
|
||||
mapping(uint256 => Bounty) public bounties;
|
||||
mapping(uint256 => Submission) public submissions;
|
||||
mapping(uint256 => uint256[]) public bountySubmissions;
|
||||
mapping(address => uint256[]) public userSubmissions;
|
||||
mapping(address => uint256[]) public creatorBounties;
|
||||
mapping(BountyTier => uint256) public tierRequirements;
|
||||
mapping(uint256 => mapping(address => bool)) public hasSubmitted;
|
||||
|
||||
// Arrays
|
||||
uint256[] public activeBountyIds;
|
||||
address[] public authorizedCreators;
|
||||
|
||||
// Events
|
||||
event BountyCreated(
|
||||
uint256 indexed bountyId,
|
||||
string title,
|
||||
uint256 rewardAmount,
|
||||
address indexed creator,
|
||||
BountyTier tier,
|
||||
uint256 deadline
|
||||
);
|
||||
|
||||
event BountySubmitted(
|
||||
uint256 indexed bountyId,
|
||||
uint256 indexed submissionId,
|
||||
address indexed submitter,
|
||||
bytes32 performanceHash,
|
||||
uint256 accuracy
|
||||
);
|
||||
|
||||
event BountyVerified(
|
||||
uint256 indexed bountyId,
|
||||
uint256 indexed submissionId,
|
||||
address indexed submitter,
|
||||
bool success,
|
||||
uint256 rewardAmount
|
||||
);
|
||||
|
||||
event BountyCompleted(
|
||||
uint256 indexed bountyId,
|
||||
address indexed winner,
|
||||
uint256 rewardAmount,
|
||||
uint256 completionTime
|
||||
);
|
||||
|
||||
event BountyExpired(
|
||||
uint256 indexed bountyId,
|
||||
uint256 refundAmount
|
||||
);
|
||||
|
||||
event BountyDisputed(
|
||||
uint256 indexed bountyId,
|
||||
uint256 indexed submissionId,
|
||||
address indexed disputer,
|
||||
string reason
|
||||
);
|
||||
|
||||
event PlatformFeeCollected(
|
||||
uint256 indexed bountyId,
|
||||
uint256 feeAmount,
|
||||
address indexed collector
|
||||
);
|
||||
|
||||
// Modifiers
|
||||
modifier bountyExists(uint256 _bountyId) {
|
||||
require(_bountyId < bountyCounter, "Bounty does not exist");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier onlyAuthorizedCreator() {
|
||||
require(isAuthorizedCreator(msg.sender), "Not authorized to create bounties");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier validBountyStatus(uint256 _bountyId, BountyStatus _requiredStatus) {
|
||||
require(bounties[_bountyId].status == _requiredStatus, "Invalid bounty status");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier beforeDeadline(uint256 _deadline) {
|
||||
require(block.timestamp <= _deadline, "Deadline passed");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier sufficientBalance(uint256 _amount) {
|
||||
require(aitbcToken.balanceOf(msg.sender) >= _amount, "Insufficient balance");
|
||||
_;
|
||||
}
|
||||
|
||||
constructor(address _aitbcToken, address _performanceVerifier) {
|
||||
aitbcToken = IERC20(_aitbcToken);
|
||||
performanceVerifier = PerformanceVerifier(_performanceVerifier);
|
||||
|
||||
// Set tier requirements (minimum reward amounts)
|
||||
tierRequirements[BountyTier.BRONZE] = 100 * 10**18; // 100 AITBC
|
||||
tierRequirements[BountyTier.SILVER] = 500 * 10**18; // 500 AITBC
|
||||
tierRequirements[BountyTier.GOLD] = 1000 * 10**18; // 1000 AITBC
|
||||
tierRequirements[BountyTier.PLATINUM] = 5000 * 10**18; // 5000 AITBC
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Creates a new bounty
|
||||
* @param _title Bounty title
|
||||
* @param _description Detailed description
|
||||
* @param _rewardAmount Reward amount in AITBC tokens
|
||||
* @param _tier Bounty tier
|
||||
* @param _performanceCriteria Hash of performance requirements
|
||||
* @param _minAccuracy Minimum accuracy required
|
||||
* @param _deadline Bounty deadline
|
||||
* @param _maxSubmissions Maximum number of submissions allowed
|
||||
* @param _requiresZKProof Whether ZK-proof is required
|
||||
*/
|
||||
function createBounty(
|
||||
string memory _title,
|
||||
string memory _description,
|
||||
uint256 _rewardAmount,
|
||||
BountyTier _tier,
|
||||
bytes32 _performanceCriteria,
|
||||
uint256 _minAccuracy,
|
||||
uint256 _deadline,
|
||||
uint256 _maxSubmissions,
|
||||
bool _requiresZKProof
|
||||
) external
|
||||
onlyAuthorizedCreator
|
||||
sufficientBalance(_rewardAmount)
|
||||
beforeDeadline(_deadline)
|
||||
nonReentrant
|
||||
returns (uint256)
|
||||
{
|
||||
require(_rewardAmount >= tierRequirements[_tier], "Reward below tier minimum");
|
||||
require(_minAccuracy <= 100, "Invalid accuracy");
|
||||
require(_maxSubmissions > 0, "Invalid max submissions");
|
||||
require(_deadline > block.timestamp, "Invalid deadline");
|
||||
|
||||
uint256 bountyId = bountyCounter++;
|
||||
|
||||
Bounty storage bounty = bounties[bountyId];
|
||||
bounty.bountyId = bountyId;
|
||||
bounty.title = _title;
|
||||
bounty.description = _description;
|
||||
bounty.rewardAmount = _rewardAmount;
|
||||
bounty.creator = msg.sender;
|
||||
bounty.tier = _tier;
|
||||
bounty.status = BountyStatus.CREATED;
|
||||
bounty.performanceCriteria = _performanceCriteria;
|
||||
bounty.minAccuracy = _minAccuracy;
|
||||
bounty.deadline = _deadline;
|
||||
bounty.creationTime = block.timestamp;
|
||||
bounty.maxSubmissions = _maxSubmissions;
|
||||
bounty.submissionCount = 0;
|
||||
bounty.requiresZKProof = _requiresZKProof;
|
||||
|
||||
// Calculate and collect creation fee
|
||||
uint256 creationFee = (_rewardAmount * creationFeePercentage) / 10000;
|
||||
uint256 totalRequired = _rewardAmount + creationFee;
|
||||
|
||||
require(aitbcToken.balanceOf(msg.sender) >= totalRequired, "Insufficient total amount");
|
||||
|
||||
// Transfer tokens to contract
|
||||
require(aitbcToken.transferFrom(msg.sender, address(this), totalRequired), "Transfer failed");
|
||||
|
||||
// Transfer creation fee to DAO treasury (owner for now)
|
||||
if (creationFee > 0) {
|
||||
require(aitbcToken.transfer(owner(), creationFee), "Fee transfer failed");
|
||||
emit PlatformFeeCollected(bountyId, creationFee, owner());
|
||||
}
|
||||
|
||||
// Update tracking arrays
|
||||
activeBountyIds.push(bountyId);
|
||||
creatorBounties[msg.sender].push(bountyId);
|
||||
|
||||
// Activate bounty
|
||||
bounty.status = BountyStatus.ACTIVE;
|
||||
|
||||
emit BountyCreated(bountyId, _title, _rewardAmount, msg.sender, _tier, _deadline);
|
||||
|
||||
return bountyId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Submits a solution to a bounty
|
||||
* @param _bountyId Bounty ID
|
||||
* @param _zkProof Zero-knowledge proof (if required)
|
||||
* @param _performanceHash Hash of performance metrics
|
||||
* @param _accuracy Achieved accuracy
|
||||
* @param _responseTime Response time in milliseconds
|
||||
*/
|
||||
function submitBountySolution(
|
||||
uint256 _bountyId,
|
||||
bytes memory _zkProof,
|
||||
bytes32 _performanceHash,
|
||||
uint256 _accuracy,
|
||||
uint256 _responseTime
|
||||
) external
|
||||
bountyExists(_bountyId)
|
||||
validBountyStatus(_bountyId, BountyStatus.ACTIVE)
|
||||
beforeDeadline(bounties[_bountyId].deadline)
|
||||
nonReentrant
|
||||
returns (uint256)
|
||||
{
|
||||
Bounty storage bounty = bounties[_bountyId];
|
||||
|
||||
require(!hasSubmitted[_bountyId][msg.sender], "Already submitted");
|
||||
require(bounty.submissionCount < bounty.maxSubmissions, "Max submissions reached");
|
||||
|
||||
if (bounty.requiresZKProof) {
|
||||
require(_zkProof.length > 0, "ZK-proof required");
|
||||
}
|
||||
|
||||
uint256 submissionId = bounty.submissionCount; // Use count as ID
|
||||
|
||||
Submission storage submission = submissions[submissionId];
|
||||
submission.submissionId = submissionId;
|
||||
submission.bountyId = _bountyId;
|
||||
submission.submitter = msg.sender;
|
||||
submission.zkProof = _zkProof;
|
||||
submission.performanceHash = _performanceHash;
|
||||
submission.accuracy = _accuracy;
|
||||
submission.responseTime = _responseTime;
|
||||
submission.submissionTime = block.timestamp;
|
||||
submission.status = SubmissionStatus.PENDING;
|
||||
|
||||
// Update tracking
|
||||
bounty.submissionCount++;
|
||||
hasSubmitted[_bountyId][msg.sender] = true;
|
||||
bountySubmissions[_bountyId].push(submissionId);
|
||||
userSubmissions[msg.sender].push(submissionId);
|
||||
|
||||
// Auto-verify if ZK-proof is provided
|
||||
if (_zkProof.length > 0) {
|
||||
_verifySubmission(_bountyId, submissionId);
|
||||
}
|
||||
|
||||
emit BountySubmitted(_bountyId, submissionId, msg.sender, _performanceHash, _accuracy);
|
||||
|
||||
return submissionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Manually verifies a submission (oracle or automated)
|
||||
* @param _bountyId Bounty ID
|
||||
* @param _submissionId Submission ID
|
||||
* @param _verified Whether the submission is verified
|
||||
* @param _verifier Address of the verifier
|
||||
*/
|
||||
function verifySubmission(
|
||||
uint256 _bountyId,
|
||||
uint256 _submissionId,
|
||||
bool _verified,
|
||||
address _verifier
|
||||
) external
|
||||
bountyExists(_bountyId)
|
||||
nonReentrant
|
||||
{
|
||||
Bounty storage bounty = bounties[_bountyId];
|
||||
Submission storage submission = submissions[_submissionId];
|
||||
|
||||
require(submission.status == SubmissionStatus.PENDING, "Submission not pending");
|
||||
require(submission.bountyId == _bountyId, "Submission bounty mismatch");
|
||||
|
||||
submission.status = _verified ? SubmissionStatus.VERIFIED : SubmissionStatus.REJECTED;
|
||||
submission.verifier = _verifier;
|
||||
|
||||
if (_verified) {
|
||||
// Check if this meets the bounty requirements
|
||||
if (submission.accuracy >= bounty.minAccuracy) {
|
||||
_completeBounty(_bountyId, _submissionId);
|
||||
}
|
||||
}
|
||||
|
||||
emit BountyVerified(_bountyId, _submissionId, submission.submitter, _verified, bounty.rewardAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Disputes a submission
|
||||
* @param _bountyId Bounty ID
|
||||
* @param _submissionId Submission ID
|
||||
* @param _reason Reason for dispute
|
||||
*/
|
||||
function disputeSubmission(
|
||||
uint256 _bountyId,
|
||||
uint256 _submissionId,
|
||||
string memory _reason
|
||||
) external
|
||||
bountyExists(_bountyId)
|
||||
nonReentrant
|
||||
{
|
||||
Bounty storage bounty = bounties[_bountyId];
|
||||
Submission storage submission = submissions[_submissionId];
|
||||
|
||||
require(submission.status == SubmissionStatus.VERIFIED, "Can only dispute verified submissions");
|
||||
require(block.timestamp - submission.submissionTime <= 86400, "Dispute window expired"); // 24 hours
|
||||
|
||||
submission.status = SubmissionStatus.DISPUTED;
|
||||
submission.disputeReason = _reason;
|
||||
bounty.status = BountyStatus.DISPUTED;
|
||||
|
||||
// Collect dispute fee
|
||||
uint256 disputeFee = (bounty.rewardAmount * disputeFeePercentage) / 10000;
|
||||
if (disputeFee > 0) {
|
||||
require(aitbcToken.transferFrom(msg.sender, address(this), disputeFee), "Dispute fee transfer failed");
|
||||
}
|
||||
|
||||
emit BountyDisputed(_bountyId, _submissionId, msg.sender, _reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Resolves a dispute
|
||||
* @param _bountyId Bounty ID
|
||||
* @param _submissionId Submission ID
|
||||
* @param _upholdDispute Whether to uphold the dispute
|
||||
*/
|
||||
function resolveDispute(
|
||||
uint256 _bountyId,
|
||||
uint256 _submissionId,
|
||||
bool _upholdDispute
|
||||
) external onlyOwner bountyExists(_bountyId) nonReentrant {
|
||||
Bounty storage bounty = bounties[_bountyId];
|
||||
Submission storage submission = submissions[_submissionId];
|
||||
|
||||
require(bounty.status == BountyStatus.DISPUTED, "No dispute to resolve");
|
||||
require(submission.status == SubmissionStatus.DISPUTED, "Submission not disputed");
|
||||
|
||||
if (_upholdDispute) {
|
||||
// Reject the submission
|
||||
submission.status = SubmissionStatus.REJECTED;
|
||||
bounty.status = BountyStatus.ACTIVE;
|
||||
|
||||
// Return dispute fee
|
||||
uint256 disputeFee = (bounty.rewardAmount * disputeFeePercentage) / 10000;
|
||||
if (disputeFee > 0) {
|
||||
require(aitbcToken.transfer(msg.sender, disputeFee), "Dispute fee return failed");
|
||||
}
|
||||
} else {
|
||||
// Uphold the submission
|
||||
submission.status = SubmissionStatus.VERIFIED;
|
||||
_completeBounty(_bountyId, _submissionId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Expires a bounty and returns funds to creator
|
||||
* @param _bountyId Bounty ID
|
||||
*/
|
||||
function expireBounty(uint256 _bountyId) external bountyExists(_bountyId) nonReentrant {
|
||||
Bounty storage bounty = bounties[_bountyId];
|
||||
|
||||
require(bounty.status == BountyStatus.ACTIVE, "Bounty not active");
|
||||
require(block.timestamp > bounty.deadline, "Deadline not passed");
|
||||
|
||||
bounty.status = BountyStatus.EXPIRED;
|
||||
|
||||
// Return funds to creator
|
||||
uint256 refundAmount = bounty.rewardAmount;
|
||||
require(aitbcToken.transfer(bounty.creator, refundAmount), "Refund transfer failed");
|
||||
|
||||
// Remove from active bounties
|
||||
_removeFromActiveBounties(_bountyId);
|
||||
|
||||
emit BountyExpired(_bountyId, refundAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Authorizes a creator to create bounties
|
||||
* @param _creator Address to authorize
|
||||
*/
|
||||
function authorizeCreator(address _creator) external onlyOwner {
|
||||
require(_creator != address(0), "Invalid address");
|
||||
require(!isAuthorizedCreator(_creator), "Already authorized");
|
||||
|
||||
authorizedCreators.push(_creator);
|
||||
bounties[0].authorizedSubmitters[_creator] = true; // Use bounty 0 as storage
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Revokes creator authorization
|
||||
* @param _creator Address to revoke
|
||||
*/
|
||||
function revokeCreator(address _creator) external onlyOwner {
|
||||
require(isAuthorizedCreator(_creator), "Not authorized");
|
||||
|
||||
bounties[0].authorizedSubmitters[_creator] = false; // Use bounty 0 as storage
|
||||
|
||||
// Remove from array
|
||||
for (uint256 i = 0; i < authorizedCreators.length; i++) {
|
||||
if (authorizedCreators[i] == _creator) {
|
||||
authorizedCreators[i] = authorizedCreators[authorizedCreators.length - 1];
|
||||
authorizedCreators.pop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Updates fee percentages
|
||||
* @param _creationFee New creation fee percentage
|
||||
* @param _successFee New success fee percentage
|
||||
* @param _platformFee New platform fee percentage
|
||||
*/
|
||||
function updateFees(
|
||||
uint256 _creationFee,
|
||||
uint256 _successFee,
|
||||
uint256 _platformFee
|
||||
) external onlyOwner {
|
||||
require(_creationFee <= 500, "Creation fee too high"); // Max 5%
|
||||
require(_successFee <= 500, "Success fee too high"); // Max 5%
|
||||
require(_platformFee <= 500, "Platform fee too high"); // Max 5%
|
||||
|
||||
creationFeePercentage = _creationFee;
|
||||
successFeePercentage = _successFee;
|
||||
platformFeePercentage = _platformFee;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Updates tier requirements
|
||||
* @param _tier Bounty tier
|
||||
* @param _minimumReward New minimum reward
|
||||
*/
|
||||
function updateTierRequirement(BountyTier _tier, uint256 _minimumReward) external onlyOwner {
|
||||
tierRequirements[_tier] = _minimumReward;
|
||||
}
|
||||
|
||||
// View functions
|
||||
|
||||
/**
|
||||
* @dev Gets bounty details
|
||||
* @param _bountyId Bounty ID
|
||||
*/
|
||||
function getBounty(uint256 _bountyId) external view bountyExists(_bountyId) returns (
|
||||
string memory title,
|
||||
string memory description,
|
||||
uint256 rewardAmount,
|
||||
address creator,
|
||||
BountyTier tier,
|
||||
BountyStatus status,
|
||||
bytes32 performanceCriteria,
|
||||
uint256 minAccuracy,
|
||||
uint256 deadline,
|
||||
uint256 creationTime,
|
||||
uint256 maxSubmissions,
|
||||
uint256 submissionCount,
|
||||
bool requiresZKProof
|
||||
) {
|
||||
Bounty storage bounty = bounties[_bountyId];
|
||||
return (
|
||||
bounty.title,
|
||||
bounty.description,
|
||||
bounty.rewardAmount,
|
||||
bounty.creator,
|
||||
bounty.tier,
|
||||
bounty.status,
|
||||
bounty.performanceCriteria,
|
||||
bounty.minAccuracy,
|
||||
bounty.deadline,
|
||||
bounty.creationTime,
|
||||
bounty.maxSubmissions,
|
||||
bounty.submissionCount,
|
||||
bounty.requiresZKProof
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets submission details
|
||||
* @param _submissionId Submission ID
|
||||
*/
|
||||
function getSubmission(uint256 _submissionId) external view returns (
|
||||
uint256 bountyId,
|
||||
address submitter,
|
||||
bytes32 performanceHash,
|
||||
uint256 accuracy,
|
||||
uint256 responseTime,
|
||||
uint256 submissionTime,
|
||||
SubmissionStatus status,
|
||||
address verifier
|
||||
) {
|
||||
Submission storage submission = submissions[_submissionId];
|
||||
return (
|
||||
submission.bountyId,
|
||||
submission.submitter,
|
||||
submission.performanceHash,
|
||||
submission.accuracy,
|
||||
submission.responseTime,
|
||||
submission.submissionTime,
|
||||
submission.status,
|
||||
submission.verifier
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets all submissions for a bounty
|
||||
* @param _bountyId Bounty ID
|
||||
*/
|
||||
function getBountySubmissions(uint256 _bountyId) external view bountyExists(_bountyId) returns (uint256[] memory) {
|
||||
return bountySubmissions[_bountyId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets all bounties created by a user
|
||||
* @param _creator Creator address
|
||||
*/
|
||||
function getCreatorBounties(address _creator) external view returns (uint256[] memory) {
|
||||
return creatorBounties[_creator];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets all submissions by a user
|
||||
* @param _submitter Submitter address
|
||||
*/
|
||||
function getUserSubmissions(address _submitter) external view returns (uint256[] memory) {
|
||||
return userSubmissions[_submitter];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets all active bounty IDs
|
||||
*/
|
||||
function getActiveBounties() external view returns (uint256[] memory) {
|
||||
return activeBountyIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets bounty statistics
|
||||
*/
|
||||
function getBountyStats() external view returns (BountyStats memory) {
|
||||
uint256 totalValue = 0;
|
||||
uint256 activeCount = 0;
|
||||
uint256 completedCount = 0;
|
||||
|
||||
for (uint256 i = 0; i < bountyCounter; i++) {
|
||||
if (bounties[i].status == BountyStatus.ACTIVE) {
|
||||
activeCount++;
|
||||
totalValue += bounties[i].rewardAmount;
|
||||
} else if (bounties[i].status == BountyStatus.COMPLETED) {
|
||||
completedCount++;
|
||||
totalValue += bounties[i].rewardAmount;
|
||||
}
|
||||
}
|
||||
|
||||
uint256 avgReward = bountyCounter > 0 ? totalValue / bountyCounter : 0;
|
||||
uint256 successRate = completedCount > 0 ? (completedCount * 100) / bountyCounter : 0;
|
||||
|
||||
return BountyStats({
|
||||
totalBounties: bountyCounter,
|
||||
activeBounties: activeCount,
|
||||
completedBounties: completedCount,
|
||||
totalValueLocked: totalValue,
|
||||
averageReward: avgReward,
|
||||
successRate: successRate
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Checks if an address is authorized to create bounties
|
||||
* @param _creator Address to check
|
||||
*/
|
||||
function isAuthorizedCreator(address _creator) public view returns (bool) {
|
||||
return bounties[0].authorizedSubmitters[_creator]; // Use bounty 0 as storage
|
||||
}
|
||||
|
||||
// Internal functions
|
||||
|
||||
function _verifySubmission(uint256 _bountyId, uint256 _submissionId) internal {
|
||||
Bounty storage bounty = bounties[_bountyId];
|
||||
Submission storage submission = submissions[_submissionId];
|
||||
|
||||
// Verify ZK-proof using PerformanceVerifier
|
||||
bool proofValid = performanceVerifier.verifyPerformanceProof(
|
||||
0, // Use dummy agreement ID for bounty verification
|
||||
submission.responseTime,
|
||||
submission.accuracy,
|
||||
95, // Default availability
|
||||
100, // Default compute power
|
||||
submission.zkProof
|
||||
);
|
||||
|
||||
if (proofValid && submission.accuracy >= bounty.minAccuracy) {
|
||||
submission.status = SubmissionStatus.VERIFIED;
|
||||
_completeBounty(_bountyId, _submissionId);
|
||||
} else {
|
||||
submission.status = SubmissionStatus.REJECTED;
|
||||
}
|
||||
}
|
||||
|
||||
function _completeBounty(uint256 _bountyId, uint256 _submissionId) internal {
|
||||
Bounty storage bounty = bounties[_bountyId];
|
||||
Submission storage submission = submissions[_submissionId];
|
||||
|
||||
require(bounty.status == BountyStatus.ACTIVE || bounty.status == BountyStatus.SUBMITTED, "Bounty not active");
|
||||
|
||||
bounty.status = BountyStatus.COMPLETED;
|
||||
bounty.winningSubmission = submission.submitter;
|
||||
|
||||
// Calculate fees
|
||||
uint256 successFee = (bounty.rewardAmount * successFeePercentage) / 10000;
|
||||
uint256 platformFee = (bounty.rewardAmount * platformFeePercentage) / 10000;
|
||||
uint256 totalFees = successFee + platformFee;
|
||||
uint256 winnerReward = bounty.rewardAmount - totalFees;
|
||||
|
||||
// Transfer reward to winner
|
||||
if (winnerReward > 0) {
|
||||
require(aitbcToken.transfer(submission.submitter, winnerReward), "Reward transfer failed");
|
||||
}
|
||||
|
||||
// Transfer fees to treasury
|
||||
if (totalFees > 0) {
|
||||
require(aitbcToken.transfer(owner(), totalFees), "Fee transfer failed");
|
||||
emit PlatformFeeCollected(_bountyId, totalFees, owner());
|
||||
}
|
||||
|
||||
// Remove from active bounties
|
||||
_removeFromActiveBounties(_bountyId);
|
||||
|
||||
emit BountyCompleted(_bountyId, submission.submitter, winnerReward, block.timestamp);
|
||||
}
|
||||
|
||||
function _removeFromActiveBounties(uint256 _bountyId) internal {
|
||||
for (uint256 i = 0; i < activeBountyIds.length; i++) {
|
||||
if (activeBountyIds[i] == _bountyId) {
|
||||
activeBountyIds[i] = activeBountyIds[activeBountyIds.length - 1];
|
||||
activeBountyIds.pop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Emergency pause function
|
||||
*/
|
||||
function pause() external onlyOwner {
|
||||
_pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Unpause function
|
||||
*/
|
||||
function unpause() external onlyOwner {
|
||||
_unpause();
|
||||
}
|
||||
}
|
||||
827
contracts/contracts/AgentStaking.sol
Normal file
827
contracts/contracts/AgentStaking.sol
Normal file
@@ -0,0 +1,827 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
||||
import "@openzeppelin/contracts/security/Pausable.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import "./PerformanceVerifier.sol";
|
||||
import "./AIToken.sol";
|
||||
|
||||
/**
|
||||
* @title Agent Staking System
|
||||
* @dev Reputation-based yield farming for AI agents with dynamic APY calculation
|
||||
* @notice Allows users to stake AITBC tokens on agent wallets and earn rewards based on agent performance
|
||||
*/
|
||||
contract AgentStaking is Ownable, ReentrancyGuard, Pausable {
|
||||
|
||||
// State variables
|
||||
IERC20 public aitbcToken;
|
||||
PerformanceVerifier public performanceVerifier;
|
||||
|
||||
uint256 public stakeCounter;
|
||||
uint256 public baseAPY = 500; // 5% base APY in basis points
|
||||
uint256 public maxAPY = 2000; // 20% max APY in basis points
|
||||
uint256 public minStakeAmount = 100 * 10**18; // 100 AITBC minimum
|
||||
uint256 public maxStakeAmount = 100000 * 10**18; // 100k AITBC maximum
|
||||
uint256 public unbondingPeriod = 7 days;
|
||||
uint256 public rewardDistributionInterval = 1 days;
|
||||
uint256 public platformFeePercentage = 100; // 1% platform fee
|
||||
uint256 public earlyUnbondPenalty = 1000; // 10% penalty for early unbonding
|
||||
|
||||
// Staking status
|
||||
enum StakeStatus { ACTIVE, UNBONDING, COMPLETED, SLASHED }
|
||||
|
||||
// Agent performance tier
|
||||
enum PerformanceTier { BRONZE, SILVER, GOLD, PLATINUM, DIAMOND }
|
||||
|
||||
// Structs
|
||||
struct Stake {
|
||||
uint256 stakeId;
|
||||
address staker;
|
||||
address agentWallet;
|
||||
uint256 amount;
|
||||
uint256 lockPeriod;
|
||||
uint256 startTime;
|
||||
uint256 endTime;
|
||||
StakeStatus status;
|
||||
uint256 accumulatedRewards;
|
||||
uint256 lastRewardTime;
|
||||
uint256 currentAPY;
|
||||
PerformanceTier agentTier;
|
||||
bool autoCompound;
|
||||
}
|
||||
|
||||
struct AgentMetrics {
|
||||
address agentWallet;
|
||||
uint256 totalStaked;
|
||||
uint256 stakerCount;
|
||||
uint256 totalRewardsDistributed;
|
||||
uint256 averageAccuracy;
|
||||
uint256 totalSubmissions;
|
||||
uint256 successfulSubmissions;
|
||||
uint256 lastUpdateTime;
|
||||
PerformanceTier currentTier;
|
||||
uint256 tierScore;
|
||||
}
|
||||
|
||||
struct StakingPool {
|
||||
address agentWallet;
|
||||
uint256 totalStaked;
|
||||
uint256 totalRewards;
|
||||
uint256 poolAPY;
|
||||
uint256 lastDistributionTime;
|
||||
mapping(address => uint256) stakerShares;
|
||||
address[] stakers;
|
||||
}
|
||||
|
||||
struct RewardCalculation {
|
||||
uint256 baseRewards;
|
||||
uint256 performanceBonus;
|
||||
uint256 lockBonus;
|
||||
uint256 tierBonus;
|
||||
uint256 totalRewards;
|
||||
uint256 platformFee;
|
||||
}
|
||||
|
||||
// Mappings
|
||||
mapping(uint256 => Stake) public stakes;
|
||||
mapping(address => uint256[]) public stakerStakes;
|
||||
mapping(address => uint256[]) public agentStakes;
|
||||
mapping(address => AgentMetrics) public agentMetrics;
|
||||
mapping(address => StakingPool) public stakingPools;
|
||||
mapping(PerformanceTier => uint256) public tierMultipliers;
|
||||
mapping(uint256 => uint256) public lockPeriodMultipliers;
|
||||
|
||||
// Arrays
|
||||
address[] public supportedAgents;
|
||||
uint256[] public activeStakeIds;
|
||||
|
||||
// Events
|
||||
event StakeCreated(
|
||||
uint256 indexed stakeId,
|
||||
address indexed staker,
|
||||
address indexed agentWallet,
|
||||
uint256 amount,
|
||||
uint256 lockPeriod,
|
||||
uint256 apy
|
||||
);
|
||||
|
||||
event StakeUpdated(
|
||||
uint256 indexed stakeId,
|
||||
uint256 newAmount,
|
||||
uint256 newAPY
|
||||
);
|
||||
|
||||
event RewardsDistributed(
|
||||
uint256 indexed stakeId,
|
||||
address indexed staker,
|
||||
uint256 rewardAmount,
|
||||
uint256 platformFee
|
||||
);
|
||||
|
||||
event StakeUnbonded(
|
||||
uint256 indexed stakeId,
|
||||
address indexed staker,
|
||||
uint256 amount,
|
||||
uint256 penalty
|
||||
);
|
||||
|
||||
event StakeCompleted(
|
||||
uint256 indexed stakeId,
|
||||
address indexed staker,
|
||||
uint256 totalAmount,
|
||||
uint256 totalRewards
|
||||
);
|
||||
|
||||
event AgentTierUpdated(
|
||||
address indexed agentWallet,
|
||||
PerformanceTier oldTier,
|
||||
PerformanceTier newTier,
|
||||
uint256 tierScore
|
||||
);
|
||||
|
||||
event PoolRewardsDistributed(
|
||||
address indexed agentWallet,
|
||||
uint256 totalRewards,
|
||||
uint256 stakerCount
|
||||
);
|
||||
|
||||
event PlatformFeeCollected(
|
||||
uint256 indexed stakeId,
|
||||
uint256 feeAmount,
|
||||
address indexed collector
|
||||
);
|
||||
|
||||
// Modifiers
|
||||
modifier stakeExists(uint256 _stakeId) {
|
||||
require(_stakeId < stakeCounter, "Stake does not exist");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier onlyStakeOwner(uint256 _stakeId) {
|
||||
require(stakes[_stakeId].staker == msg.sender, "Not stake owner");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier supportedAgent(address _agentWallet) {
|
||||
require(agentMetrics[_agentWallet].agentWallet != address(0) || _agentWallet == address(0), "Agent not supported");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier validStakeAmount(uint256 _amount) {
|
||||
require(_amount >= minStakeAmount && _amount <= maxStakeAmount, "Invalid stake amount");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier sufficientBalance(uint256 _amount) {
|
||||
require(aitbcToken.balanceOf(msg.sender) >= _amount, "Insufficient balance");
|
||||
_;
|
||||
}
|
||||
|
||||
constructor(address _aitbcToken, address _performanceVerifier) {
|
||||
aitbcToken = IERC20(_aitbcToken);
|
||||
performanceVerifier = PerformanceVerifier(_performanceVerifier);
|
||||
|
||||
// Set tier multipliers (in basis points)
|
||||
tierMultipliers[PerformanceTier.BRONZE] = 1000; // 1x
|
||||
tierMultipliers[PerformanceTier.SILVER] = 1200; // 1.2x
|
||||
tierMultipliers[PerformanceTier.GOLD] = 1500; // 1.5x
|
||||
tierMultipliers[PerformanceTier.PLATINUM] = 2000; // 2x
|
||||
tierMultipliers[PerformanceTier.DIAMOND] = 3000; // 3x
|
||||
|
||||
// Set lock period multipliers
|
||||
lockPeriodMultipliers[30 days] = 1100; // 1.1x for 30 days
|
||||
lockPeriodMultipliers[90 days] = 1250; // 1.25x for 90 days
|
||||
lockPeriodMultipliers[180 days] = 1500; // 1.5x for 180 days
|
||||
lockPeriodMultipliers[365 days] = 2000; // 2x for 365 days
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Creates a new stake on an agent wallet
|
||||
* @param _agentWallet Address of the agent wallet
|
||||
* @param _amount Amount to stake
|
||||
* @param _lockPeriod Lock period in seconds
|
||||
* @param _autoCompound Whether to auto-compound rewards
|
||||
*/
|
||||
function stakeOnAgent(
|
||||
address _agentWallet,
|
||||
uint256 _amount,
|
||||
uint256 _lockPeriod,
|
||||
bool _autoCompound
|
||||
) external
|
||||
supportedAgent(_agentWallet)
|
||||
validStakeAmount(_amount)
|
||||
sufficientBalance(_amount)
|
||||
nonReentrant
|
||||
returns (uint256)
|
||||
{
|
||||
require(_lockPeriod >= 1 days, "Lock period too short");
|
||||
require(_lockPeriod <= 365 days, "Lock period too long");
|
||||
|
||||
uint256 stakeId = stakeCounter++;
|
||||
|
||||
// Calculate initial APY
|
||||
PerformanceTier agentTier = _getAgentTier(_agentWallet);
|
||||
uint256 apy = _calculateAPY(_agentWallet, _lockPeriod, agentTier);
|
||||
|
||||
Stake storage stake = stakes[stakeId];
|
||||
stake.stakeId = stakeId;
|
||||
stake.staker = msg.sender;
|
||||
stake.agentWallet = _agentWallet;
|
||||
stake.amount = _amount;
|
||||
stake.lockPeriod = _lockPeriod;
|
||||
stake.startTime = block.timestamp;
|
||||
stake.endTime = block.timestamp + _lockPeriod;
|
||||
stake.status = StakeStatus.ACTIVE;
|
||||
stake.accumulatedRewards = 0;
|
||||
stake.lastRewardTime = block.timestamp;
|
||||
stake.currentAPY = apy;
|
||||
stake.agentTier = agentTier;
|
||||
stake.autoCompound = _autoCompound;
|
||||
|
||||
// Update agent metrics
|
||||
_updateAgentMetrics(_agentWallet, _amount, true);
|
||||
|
||||
// Update staking pool
|
||||
_updateStakingPool(_agentWallet, msg.sender, _amount, true);
|
||||
|
||||
// Update tracking arrays
|
||||
stakerStakes[msg.sender].push(stakeId);
|
||||
agentStakes[_agentWallet].push(stakeId);
|
||||
activeStakeIds.push(stakeId);
|
||||
|
||||
// Transfer tokens to contract
|
||||
require(aitbcToken.transferFrom(msg.sender, address(this), _amount), "Transfer failed");
|
||||
|
||||
emit StakeCreated(stakeId, msg.sender, _agentWallet, _amount, _lockPeriod, apy);
|
||||
|
||||
return stakeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Adds more tokens to an existing stake
|
||||
* @param _stakeId Stake ID
|
||||
* @param _additionalAmount Additional amount to stake
|
||||
*/
|
||||
function addToStake(
|
||||
uint256 _stakeId,
|
||||
uint256 _additionalAmount
|
||||
) external
|
||||
stakeExists(_stakeId)
|
||||
onlyStakeOwner(_stakeId)
|
||||
validStakeAmount(_additionalAmount)
|
||||
sufficientBalance(_additionalAmount)
|
||||
nonReentrant
|
||||
{
|
||||
Stake storage stake = stakes[_stakeId];
|
||||
require(stake.status == StakeStatus.ACTIVE, "Stake not active");
|
||||
|
||||
// Calculate new APY
|
||||
uint256 newTotalAmount = stake.amount + _additionalAmount;
|
||||
uint256 newAPY = _calculateAPY(stake.agentWallet, stake.lockPeriod, stake.agentTier);
|
||||
|
||||
// Update stake
|
||||
stake.amount = newTotalAmount;
|
||||
stake.currentAPY = newAPY;
|
||||
|
||||
// Update agent metrics
|
||||
_updateAgentMetrics(stake.agentWallet, _additionalAmount, true);
|
||||
|
||||
// Update staking pool
|
||||
_updateStakingPool(stake.agentWallet, msg.sender, _additionalAmount, true);
|
||||
|
||||
// Transfer additional tokens
|
||||
require(aitbcToken.transferFrom(msg.sender, address(this), _additionalAmount), "Transfer failed");
|
||||
|
||||
emit StakeUpdated(_stakeId, newTotalAmount, newAPY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Initiates unbonding for a stake
|
||||
* @param _stakeId Stake ID
|
||||
*/
|
||||
function unbondStake(uint256 _stakeId) external
|
||||
stakeExists(_stakeId)
|
||||
onlyStakeOwner(_stakeId)
|
||||
nonReentrant
|
||||
{
|
||||
Stake storage stake = stakes[_stakeId];
|
||||
require(stake.status == StakeStatus.ACTIVE, "Stake not active");
|
||||
require(block.timestamp >= stake.endTime, "Lock period not ended");
|
||||
|
||||
// Calculate final rewards
|
||||
_calculateRewards(_stakeId);
|
||||
|
||||
stake.status = StakeStatus.UNBONDING;
|
||||
|
||||
// Remove from active stakes
|
||||
_removeFromActiveStakes(_stakeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Completes unbonding and returns stake + rewards
|
||||
* @param _stakeId Stake ID
|
||||
*/
|
||||
function completeUnbonding(uint256 _stakeId) external
|
||||
stakeExists(_stakeId)
|
||||
onlyStakeOwner(_stakeId)
|
||||
nonReentrant
|
||||
{
|
||||
Stake storage stake = stakes[_stakeId];
|
||||
require(stake.status == StakeStatus.UNBONDING, "Stake not unbonding");
|
||||
require(block.timestamp >= stake.endTime + unbondingPeriod, "Unbonding period not ended");
|
||||
|
||||
uint256 totalAmount = stake.amount;
|
||||
uint256 totalRewards = stake.accumulatedRewards;
|
||||
|
||||
// Apply early unbonding penalty if applicable
|
||||
uint256 penalty = 0;
|
||||
if (block.timestamp < stake.endTime + 30 days) {
|
||||
penalty = (totalAmount * earlyUnbondPenalty) / 10000;
|
||||
totalAmount -= penalty;
|
||||
}
|
||||
|
||||
stake.status = StakeStatus.COMPLETED;
|
||||
|
||||
// Update agent metrics
|
||||
_updateAgentMetrics(stake.agentWallet, stake.amount, false);
|
||||
|
||||
// Update staking pool
|
||||
_updateStakingPool(stake.agentWallet, msg.sender, stake.amount, false);
|
||||
|
||||
// Transfer tokens back to staker
|
||||
if (totalAmount > 0) {
|
||||
require(aitbcToken.transfer(msg.sender, totalAmount), "Stake transfer failed");
|
||||
}
|
||||
|
||||
if (totalRewards > 0) {
|
||||
require(aitbcToken.transfer(msg.sender, totalRewards), "Rewards transfer failed");
|
||||
}
|
||||
|
||||
emit StakeCompleted(_stakeId, msg.sender, totalAmount, totalRewards);
|
||||
emit StakeUnbonded(_stakeId, msg.sender, totalAmount, penalty);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Distributes agent earnings to stakers
|
||||
* @param _agentWallet Agent wallet address
|
||||
* @param _totalEarnings Total earnings to distribute
|
||||
*/
|
||||
function distributeAgentEarnings(
|
||||
address _agentWallet,
|
||||
uint256 _totalEarnings
|
||||
) external
|
||||
supportedAgent(_agentWallet)
|
||||
nonReentrant
|
||||
{
|
||||
require(_totalEarnings > 0, "No earnings to distribute");
|
||||
|
||||
StakingPool storage pool = stakingPools[_agentWallet];
|
||||
require(pool.totalStaked > 0, "No stakers in pool");
|
||||
|
||||
// Calculate platform fee
|
||||
uint256 platformFee = (_totalEarnings * platformFeePercentage) / 10000;
|
||||
uint256 distributableAmount = _totalEarnings - platformFee;
|
||||
|
||||
// Transfer platform fee
|
||||
if (platformFee > 0) {
|
||||
require(aitbcToken.transferFrom(msg.sender, owner(), platformFee), "Platform fee transfer failed");
|
||||
}
|
||||
|
||||
// Transfer distributable amount to contract
|
||||
require(aitbcToken.transferFrom(msg.sender, address(this), distributableAmount), "Earnings transfer failed");
|
||||
|
||||
// Distribute to stakers proportionally
|
||||
uint256 totalDistributed = 0;
|
||||
for (uint256 i = 0; i < pool.stakers.length; i++) {
|
||||
address staker = pool.stakers[i];
|
||||
uint256 stakerShare = pool.stakerShares[staker];
|
||||
uint256 stakerReward = (distributableAmount * stakerShare) / pool.totalStaked;
|
||||
|
||||
if (stakerReward > 0) {
|
||||
// Find and update all stakes for this staker on this agent
|
||||
uint256[] storage stakesForAgent = agentStakes[_agentWallet];
|
||||
for (uint256 j = 0; j < stakesForAgent.length; j++) {
|
||||
uint256 stakeId = stakesForAgent[j];
|
||||
Stake storage stake = stakes[stakeId];
|
||||
if (stake.staker == staker && stake.status == StakeStatus.ACTIVE) {
|
||||
stake.accumulatedRewards += stakerReward;
|
||||
break;
|
||||
}
|
||||
}
|
||||
totalDistributed += stakerReward;
|
||||
}
|
||||
}
|
||||
|
||||
// Update agent metrics
|
||||
agentMetrics[_agentWallet].totalRewardsDistributed += totalDistributed;
|
||||
|
||||
emit PoolRewardsDistributed(_agentWallet, totalDistributed, pool.stakers.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Updates agent performance metrics and tier
|
||||
* @param _agentWallet Agent wallet address
|
||||
* @param _accuracy Latest accuracy score
|
||||
* @param _successful Whether the submission was successful
|
||||
*/
|
||||
function updateAgentPerformance(
|
||||
address _agentWallet,
|
||||
uint256 _accuracy,
|
||||
bool _successful
|
||||
) external
|
||||
supportedAgent(_agentWallet)
|
||||
nonReentrant
|
||||
{
|
||||
AgentMetrics storage metrics = agentMetrics[_agentWallet];
|
||||
|
||||
metrics.totalSubmissions++;
|
||||
if (_successful) {
|
||||
metrics.successfulSubmissions++;
|
||||
}
|
||||
|
||||
// Update average accuracy (weighted average)
|
||||
uint256 totalAccuracy = metrics.averageAccuracy * (metrics.totalSubmissions - 1) + _accuracy;
|
||||
metrics.averageAccuracy = totalAccuracy / metrics.totalSubmissions;
|
||||
|
||||
metrics.lastUpdateTime = block.timestamp;
|
||||
|
||||
// Calculate new tier
|
||||
PerformanceTier newTier = _calculateAgentTier(_agentWallet);
|
||||
PerformanceTier oldTier = metrics.currentTier;
|
||||
|
||||
if (newTier != oldTier) {
|
||||
metrics.currentTier = newTier;
|
||||
|
||||
// Update APY for all active stakes on this agent
|
||||
uint256[] storage stakesForAgent = agentStakes[_agentWallet];
|
||||
for (uint256 i = 0; i < stakesForAgent.length; i++) {
|
||||
uint256 stakeId = stakesForAgent[i];
|
||||
Stake storage stake = stakes[stakeId];
|
||||
if (stake.status == StakeStatus.ACTIVE) {
|
||||
stake.currentAPY = _calculateAPY(_agentWallet, stake.lockPeriod, newTier);
|
||||
stake.agentTier = newTier;
|
||||
}
|
||||
}
|
||||
|
||||
emit AgentTierUpdated(_agentWallet, oldTier, newTier, metrics.tierScore);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Adds a supported agent
|
||||
* @param _agentWallet Agent wallet address
|
||||
* @param _initialTier Initial performance tier
|
||||
*/
|
||||
function addSupportedAgent(
|
||||
address _agentWallet,
|
||||
PerformanceTier _initialTier
|
||||
) external onlyOwner {
|
||||
require(_agentWallet != address(0), "Invalid agent address");
|
||||
require(agentMetrics[_agentWallet].agentWallet == address(0), "Agent already supported");
|
||||
|
||||
agentMetrics[_agentWallet] = AgentMetrics({
|
||||
agentWallet: _agentWallet,
|
||||
totalStaked: 0,
|
||||
stakerCount: 0,
|
||||
totalRewardsDistributed: 0,
|
||||
averageAccuracy: 0,
|
||||
totalSubmissions: 0,
|
||||
successfulSubmissions: 0,
|
||||
lastUpdateTime: block.timestamp,
|
||||
currentTier: _initialTier,
|
||||
tierScore: _getTierScore(_initialTier)
|
||||
});
|
||||
|
||||
// Initialize staking pool
|
||||
stakingPools[_agentWallet].agentWallet = _agentWallet;
|
||||
stakingPools[_agentWallet].totalStaked = 0;
|
||||
stakingPools[_agentWallet].totalRewards = 0;
|
||||
stakingPools[_agentWallet].poolAPY = baseAPY;
|
||||
stakingPools[_agentWallet].lastDistributionTime = block.timestamp;
|
||||
|
||||
supportedAgents.push(_agentWallet);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Removes a supported agent
|
||||
* @param _agentWallet Agent wallet address
|
||||
*/
|
||||
function removeSupportedAgent(address _agentWallet) external onlyOwner {
|
||||
require(agentMetrics[_agentWallet].agentWallet != address(0), "Agent not supported");
|
||||
require(agentMetrics[_agentWallet].totalStaked == 0, "Agent has active stakes");
|
||||
|
||||
// Remove from supported agents
|
||||
for (uint256 i = 0; i < supportedAgents.length; i++) {
|
||||
if (supportedAgents[i] == _agentWallet) {
|
||||
supportedAgents[i] = supportedAgents[supportedAgents.length - 1];
|
||||
supportedAgents.pop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
delete agentMetrics[_agentWallet];
|
||||
delete stakingPools[_agentWallet];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Updates configuration parameters
|
||||
* @param _baseAPY New base APY
|
||||
* @param _maxAPY New maximum APY
|
||||
* @param _platformFee New platform fee percentage
|
||||
*/
|
||||
function updateConfiguration(
|
||||
uint256 _baseAPY,
|
||||
uint256 _maxAPY,
|
||||
uint256 _platformFee
|
||||
) external onlyOwner {
|
||||
require(_baseAPY <= _maxAPY, "Base APY cannot exceed max APY");
|
||||
require(_maxAPY <= 5000, "Max APY too high"); // Max 50%
|
||||
require(_platformFee <= 500, "Platform fee too high"); // Max 5%
|
||||
|
||||
baseAPY = _baseAPY;
|
||||
maxAPY = _maxAPY;
|
||||
platformFeePercentage = _platformFee;
|
||||
}
|
||||
|
||||
// View functions
|
||||
|
||||
/**
|
||||
* @dev Gets stake details
|
||||
* @param _stakeId Stake ID
|
||||
*/
|
||||
function getStake(uint256 _stakeId) external view stakeExists(_stakeId) returns (
|
||||
address staker,
|
||||
address agentWallet,
|
||||
uint256 amount,
|
||||
uint256 lockPeriod,
|
||||
uint256 startTime,
|
||||
uint256 endTime,
|
||||
StakeStatus status,
|
||||
uint256 accumulatedRewards,
|
||||
uint256 currentAPY,
|
||||
PerformanceTier agentTier,
|
||||
bool autoCompound
|
||||
) {
|
||||
Stake storage stake = stakes[_stakeId];
|
||||
return (
|
||||
stake.staker,
|
||||
stake.agentWallet,
|
||||
stake.amount,
|
||||
stake.lockPeriod,
|
||||
stake.startTime,
|
||||
stake.endTime,
|
||||
stake.status,
|
||||
stake.accumulatedRewards,
|
||||
stake.currentAPY,
|
||||
stake.agentTier,
|
||||
stake.autoCompound
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets agent metrics
|
||||
* @param _agentWallet Agent wallet address
|
||||
*/
|
||||
function getAgentMetrics(address _agentWallet) external view returns (
|
||||
uint256 totalStaked,
|
||||
uint256 stakerCount,
|
||||
uint256 totalRewardsDistributed,
|
||||
uint256 averageAccuracy,
|
||||
uint256 totalSubmissions,
|
||||
uint256 successfulSubmissions,
|
||||
PerformanceTier currentTier,
|
||||
uint256 tierScore
|
||||
) {
|
||||
AgentMetrics storage metrics = agentMetrics[_agentWallet];
|
||||
return (
|
||||
metrics.totalStaked,
|
||||
metrics.stakerCount,
|
||||
metrics.totalRewardsDistributed,
|
||||
metrics.averageAccuracy,
|
||||
metrics.totalSubmissions,
|
||||
metrics.successfulSubmissions,
|
||||
metrics.currentTier,
|
||||
metrics.tierScore
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets staking pool information
|
||||
* @param _agentWallet Agent wallet address
|
||||
*/
|
||||
function getStakingPool(address _agentWallet) external view returns (
|
||||
uint256 totalStaked,
|
||||
uint256 totalRewards,
|
||||
uint256 poolAPY,
|
||||
uint256 stakerCount
|
||||
) {
|
||||
StakingPool storage pool = stakingPools[_agentWallet];
|
||||
return (
|
||||
pool.totalStaked,
|
||||
pool.totalRewards,
|
||||
pool.poolAPY,
|
||||
pool.stakers.length
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Calculates current rewards for a stake
|
||||
* @param _stakeId Stake ID
|
||||
*/
|
||||
function calculateRewards(uint256 _stakeId) external view stakeExists(_stakeId) returns (uint256) {
|
||||
Stake storage stake = stakes[_stakeId];
|
||||
if (stake.status != StakeStatus.ACTIVE) {
|
||||
return stake.accumulatedRewards;
|
||||
}
|
||||
|
||||
uint256 timeElapsed = block.timestamp - stake.lastRewardTime;
|
||||
uint256 yearlyRewards = (stake.amount * stake.currentAPY) / 10000;
|
||||
uint256 currentRewards = (yearlyRewards * timeElapsed) / 365 days;
|
||||
|
||||
return stake.accumulatedRewards + currentRewards;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets all stakes for a staker
|
||||
* @param _staker Staker address
|
||||
*/
|
||||
function getStakerStakes(address _staker) external view returns (uint256[] memory) {
|
||||
return stakerStakes[_staker];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets all stakes for an agent
|
||||
* @param _agentWallet Agent wallet address
|
||||
*/
|
||||
function getAgentStakes(address _agentWallet) external view returns (uint256[] memory) {
|
||||
return agentStakes[_agentWallet];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets all supported agents
|
||||
*/
|
||||
function getSupportedAgents() external view returns (address[] memory) {
|
||||
return supportedAgents;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets all active stake IDs
|
||||
*/
|
||||
function getActiveStakes() external view returns (uint256[] memory) {
|
||||
return activeStakeIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Calculates APY for a stake
|
||||
* @param _agentWallet Agent wallet address
|
||||
* @param _lockPeriod Lock period
|
||||
* @param _agentTier Agent performance tier
|
||||
*/
|
||||
function calculateAPY(
|
||||
address _agentWallet,
|
||||
uint256 _lockPeriod,
|
||||
PerformanceTier _agentTier
|
||||
) external view returns (uint256) {
|
||||
return _calculateAPY(_agentWallet, _lockPeriod, _agentTier);
|
||||
}
|
||||
|
||||
// Internal functions
|
||||
|
||||
function _calculateAPY(
|
||||
address _agentWallet,
|
||||
uint256 _lockPeriod,
|
||||
PerformanceTier _agentTier
|
||||
) internal view returns (uint256) {
|
||||
uint256 tierMultiplier = tierMultipliers[_agentTier];
|
||||
uint256 lockMultiplier = lockPeriodMultipliers[_lockPeriod];
|
||||
|
||||
uint256 apy = (baseAPY * tierMultiplier * lockMultiplier) / (10000 * 10000);
|
||||
|
||||
// Cap at maximum APY
|
||||
return apy > maxAPY ? maxAPY : apy;
|
||||
}
|
||||
|
||||
function _calculateRewards(uint256 _stakeId) internal {
|
||||
Stake storage stake = stakes[_stakeId];
|
||||
if (stake.status != StakeStatus.ACTIVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint256 timeElapsed = block.timestamp - stake.lastRewardTime;
|
||||
uint256 yearlyRewards = (stake.amount * stake.currentAPY) / 10000;
|
||||
uint256 currentRewards = (yearlyRewards * timeElapsed) / 365 days;
|
||||
|
||||
stake.accumulatedRewards += currentRewards;
|
||||
stake.lastRewardTime = block.timestamp;
|
||||
|
||||
// Auto-compound if enabled
|
||||
if (stake.autoCompound && currentRewards >= minStakeAmount) {
|
||||
stake.amount += currentRewards;
|
||||
stake.accumulatedRewards = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function _getAgentTier(address _agentWallet) internal view returns (PerformanceTier) {
|
||||
AgentMetrics storage metrics = agentMetrics[_agentWallet];
|
||||
return metrics.currentTier;
|
||||
}
|
||||
|
||||
function _calculateAgentTier(address _agentWallet) internal view returns (PerformanceTier) {
|
||||
AgentMetrics storage metrics = agentMetrics[_agentWallet];
|
||||
|
||||
uint256 successRate = metrics.totalSubmissions > 0 ?
|
||||
(metrics.successfulSubmissions * 100) / metrics.totalSubmissions : 0;
|
||||
|
||||
uint256 score = (metrics.averageAccuracy * 50) / 100 + (successRate * 50) / 100;
|
||||
|
||||
if (score >= 95) return PerformanceTier.DIAMOND;
|
||||
if (score >= 90) return PerformanceTier.PLATINUM;
|
||||
if (score >= 80) return PerformanceTier.GOLD;
|
||||
if (score >= 70) return PerformanceTier.SILVER;
|
||||
return PerformanceTier.BRONZE;
|
||||
}
|
||||
|
||||
function _getTierScore(PerformanceTier _tier) internal pure returns (uint256) {
|
||||
if (_tier == PerformanceTier.DIAMOND) return 95;
|
||||
if (_tier == PerformanceTier.PLATINUM) return 90;
|
||||
if (_tier == PerformanceTier.GOLD) return 80;
|
||||
if (_tier == PerformanceTier.SILVER) return 70;
|
||||
return 60;
|
||||
}
|
||||
|
||||
function _updateAgentMetrics(address _agentWallet, uint256 _amount, bool _isStake) internal {
|
||||
AgentMetrics storage metrics = agentMetrics[_agentWallet];
|
||||
|
||||
if (_isStake) {
|
||||
metrics.totalStaked += _amount;
|
||||
if (metrics.totalStaked == _amount) {
|
||||
metrics.stakerCount = 1;
|
||||
}
|
||||
} else {
|
||||
metrics.totalStaked -= _amount;
|
||||
if (metrics.totalStaked == 0) {
|
||||
metrics.stakerCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
metrics.currentTier = _calculateAgentTier(_agentWallet);
|
||||
metrics.tierScore = _getTierScore(metrics.currentTier);
|
||||
}
|
||||
|
||||
function _updateStakingPool(address _agentWallet, address _staker, uint256 _amount, bool _isStake) internal {
|
||||
StakingPool storage pool = stakingPools[_agentWallet];
|
||||
|
||||
if (_isStake) {
|
||||
if (pool.stakerShares[_staker] == 0) {
|
||||
pool.stakers.push(_staker);
|
||||
}
|
||||
pool.stakerShares[_staker] += _amount;
|
||||
pool.totalStaked += _amount;
|
||||
} else {
|
||||
pool.stakerShares[_staker] -= _amount;
|
||||
pool.totalStaked -= _amount;
|
||||
|
||||
// Remove staker from array if no shares left
|
||||
if (pool.stakerShares[_staker] == 0) {
|
||||
for (uint256 i = 0; i < pool.stakers.length; i++) {
|
||||
if (pool.stakers[i] == _staker) {
|
||||
pool.stakers[i] = pool.stakers[pool.stakers.length - 1];
|
||||
pool.stakers.pop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update pool APY
|
||||
if (pool.totalStaked > 0) {
|
||||
pool.poolAPY = _calculateAPY(_agentWallet, 30 days, agentMetrics[_agentWallet].currentTier);
|
||||
}
|
||||
}
|
||||
|
||||
function _removeFromActiveStakes(uint256 _stakeId) internal {
|
||||
for (uint256 i = 0; i < activeStakeIds.length; i++) {
|
||||
if (activeStakeIds[i] == _stakeId) {
|
||||
activeStakeIds[i] = activeStakeIds[activeStakeIds.length - 1];
|
||||
activeStakeIds.pop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Emergency pause function
|
||||
*/
|
||||
function pause() external onlyOwner {
|
||||
_pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Unpause function
|
||||
*/
|
||||
function unpause() external onlyOwner {
|
||||
_unpause();
|
||||
}
|
||||
}
|
||||
616
contracts/contracts/BountyIntegration.sol
Normal file
616
contracts/contracts/BountyIntegration.sol
Normal file
@@ -0,0 +1,616 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
||||
import "./AgentBounty.sol";
|
||||
import "./AgentStaking.sol";
|
||||
import "./PerformanceVerifier.sol";
|
||||
import "./AIToken.sol";
|
||||
|
||||
/**
|
||||
* @title Bounty Integration Layer
|
||||
* @dev Bridges PerformanceVerifier with bounty and staking contracts
|
||||
* @notice Handles automatic bounty completion detection and cross-contract event handling
|
||||
*/
|
||||
contract BountyIntegration is Ownable, ReentrancyGuard {
|
||||
|
||||
// State variables
|
||||
AgentBounty public agentBounty;
|
||||
AgentStaking public agentStaking;
|
||||
PerformanceVerifier public performanceVerifier;
|
||||
AIToken public aitbcToken;
|
||||
|
||||
uint256 public integrationCounter;
|
||||
uint256 public autoVerificationThreshold = 90; // 90% accuracy for auto-verification
|
||||
uint256 public batchProcessingLimit = 50;
|
||||
uint256 public gasOptimizationThreshold = 100000;
|
||||
|
||||
// Integration status
|
||||
enum IntegrationStatus { PENDING, PROCESSING, COMPLETED, FAILED }
|
||||
|
||||
// Performance to bounty mapping
|
||||
struct PerformanceMapping {
|
||||
uint256 mappingId;
|
||||
bytes32 performanceHash;
|
||||
uint256 bountyId;
|
||||
uint256 submissionId;
|
||||
IntegrationStatus status;
|
||||
uint256 createdAt;
|
||||
uint256 processedAt;
|
||||
string errorMessage;
|
||||
}
|
||||
|
||||
// Batch processing
|
||||
struct BatchRequest {
|
||||
uint256 batchId;
|
||||
uint256[] bountyIds;
|
||||
uint256[] submissionIds;
|
||||
bytes32[] performanceHashes;
|
||||
uint256[] accuracies;
|
||||
uint256[] responseTimes;
|
||||
IntegrationStatus status;
|
||||
uint256 createdAt;
|
||||
uint256 processedAt;
|
||||
uint256 successCount;
|
||||
uint256 failureCount;
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
struct EventHandler {
|
||||
bytes32 eventType;
|
||||
address targetContract;
|
||||
bytes4 functionSelector;
|
||||
bool isActive;
|
||||
uint256 priority;
|
||||
}
|
||||
|
||||
// Mappings
|
||||
mapping(uint256 => PerformanceMapping) public performanceMappings;
|
||||
mapping(bytes32 => uint256) public performanceHashToMapping;
|
||||
mapping(uint256 => BatchRequest) public batchRequests;
|
||||
mapping(bytes32 => EventHandler) public eventHandlers;
|
||||
mapping(address => bool) public authorizedIntegrators;
|
||||
|
||||
// Arrays
|
||||
uint256[] public pendingMappings;
|
||||
bytes32[] public performanceHashes;
|
||||
address[] public authorizedIntegratorList;
|
||||
|
||||
// Events
|
||||
event PerformanceMapped(
|
||||
uint256 indexed mappingId,
|
||||
bytes32 indexed performanceHash,
|
||||
uint256 indexed bountyId,
|
||||
uint256 submissionId
|
||||
);
|
||||
|
||||
event BountyAutoCompleted(
|
||||
uint256 indexed bountyId,
|
||||
uint256 indexed submissionId,
|
||||
address indexed submitter,
|
||||
uint256 rewardAmount
|
||||
);
|
||||
|
||||
event StakingRewardsTriggered(
|
||||
address indexed agentWallet,
|
||||
uint256 totalEarnings,
|
||||
uint256 stakerCount
|
||||
);
|
||||
|
||||
event BatchProcessed(
|
||||
uint256 indexed batchId,
|
||||
uint256 successCount,
|
||||
uint256 failureCount,
|
||||
uint256 gasUsed
|
||||
);
|
||||
|
||||
event IntegrationFailed(
|
||||
uint256 indexed mappingId,
|
||||
string errorMessage,
|
||||
bytes32 indexed performanceHash
|
||||
);
|
||||
|
||||
event EventHandlerRegistered(
|
||||
bytes32 indexed eventType,
|
||||
address indexed targetContract,
|
||||
bytes4 functionSelector
|
||||
);
|
||||
|
||||
// Modifiers
|
||||
modifier mappingExists(uint256 _mappingId) {
|
||||
require(_mappingId < integrationCounter, "Mapping does not exist");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier onlyAuthorizedIntegrator() {
|
||||
require(authorizedIntegrators[msg.sender], "Not authorized integrator");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier validPerformanceHash(bytes32 _performanceHash) {
|
||||
require(_performanceHash != bytes32(0), "Invalid performance hash");
|
||||
_;
|
||||
}
|
||||
|
||||
constructor(
|
||||
address _agentBounty,
|
||||
address _agentStaking,
|
||||
address _performanceVerifier,
|
||||
address _aitbcToken
|
||||
) {
|
||||
agentBounty = AgentBounty(_agentBounty);
|
||||
agentStaking = AgentStaking(_agentStaking);
|
||||
performanceVerifier = PerformanceVerifier(_performanceVerifier);
|
||||
aitbcToken = AIToken(_aitbcToken);
|
||||
|
||||
// Register default event handlers
|
||||
_registerEventHandler(
|
||||
keccak256("BOUNTY_COMPLETED"),
|
||||
_agentStaking,
|
||||
AgentStaking.distributeAgentEarnings.selector
|
||||
);
|
||||
|
||||
_registerEventHandler(
|
||||
keccak256("PERFORMANCE_VERIFIED"),
|
||||
_agentBounty,
|
||||
AgentBounty.verifySubmission.selector
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Maps performance verification to bounty completion
|
||||
* @param _performanceHash Hash of performance metrics
|
||||
* @param _bountyId Bounty ID
|
||||
* @param _submissionId Submission ID
|
||||
*/
|
||||
function mapPerformanceToBounty(
|
||||
bytes32 _performanceHash,
|
||||
uint256 _bountyId,
|
||||
uint256 _submissionId
|
||||
) external
|
||||
onlyAuthorizedIntegrator
|
||||
validPerformanceHash(_performanceHash)
|
||||
nonReentrant
|
||||
returns (uint256)
|
||||
{
|
||||
require(performanceHashToMapping[_performanceHash] == 0, "Performance already mapped");
|
||||
|
||||
uint256 mappingId = integrationCounter++;
|
||||
|
||||
PerformanceMapping storage perfMap = performanceMappings[mappingId];
|
||||
perfMap.mappingId = mappingId;
|
||||
perfMap.performanceHash = _performanceHash;
|
||||
perfMap.bountyId = _bountyId;
|
||||
perfMap.submissionId = _submissionId;
|
||||
perfMap.status = IntegrationStatus.PENDING;
|
||||
perfMap.createdAt = block.timestamp;
|
||||
|
||||
performanceHashToMapping[_performanceHash] = mappingId;
|
||||
pendingMappings.push(mappingId);
|
||||
performanceHashes.push(_performanceHash);
|
||||
|
||||
emit PerformanceMapped(mappingId, _performanceHash, _bountyId, _submissionId);
|
||||
|
||||
// Attempt auto-processing
|
||||
_processMapping(mappingId);
|
||||
|
||||
return mappingId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Processes a single performance mapping
|
||||
* @param _mappingId Mapping ID
|
||||
*/
|
||||
function processMapping(uint256 _mappingId) external
|
||||
onlyAuthorizedIntegrator
|
||||
mappingExists(_mappingId)
|
||||
nonReentrant
|
||||
{
|
||||
_processMapping(_mappingId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Processes multiple mappings in a batch
|
||||
* @param _mappingIds Array of mapping IDs
|
||||
*/
|
||||
function processBatchMappings(uint256[] calldata _mappingIds) external
|
||||
onlyAuthorizedIntegrator
|
||||
nonReentrant
|
||||
{
|
||||
require(_mappingIds.length <= batchProcessingLimit, "Batch too large");
|
||||
|
||||
uint256 batchId = integrationCounter++;
|
||||
BatchRequest storage batch = batchRequests[batchId];
|
||||
batch.batchId = batchId;
|
||||
batch.bountyIds = new uint256[](_mappingIds.length);
|
||||
batch.submissionIds = new uint256[](_mappingIds.length);
|
||||
batch.performanceHashes = new bytes32[](_mappingIds.length);
|
||||
batch.accuracies = new uint256[](_mappingIds.length);
|
||||
batch.responseTimes = new uint256[](_mappingIds.length);
|
||||
batch.status = IntegrationStatus.PROCESSING;
|
||||
batch.createdAt = block.timestamp;
|
||||
|
||||
uint256 gasStart = gasleft();
|
||||
uint256 successCount = 0;
|
||||
uint256 failureCount = 0;
|
||||
|
||||
for (uint256 i = 0; i < _mappingIds.length; i++) {
|
||||
try this._processMappingInternal(_mappingIds[i]) {
|
||||
successCount++;
|
||||
} catch {
|
||||
failureCount++;
|
||||
}
|
||||
}
|
||||
|
||||
batch.successCount = successCount;
|
||||
batch.failureCount = failureCount;
|
||||
batch.processedAt = block.timestamp;
|
||||
batch.status = IntegrationStatus.COMPLETED;
|
||||
|
||||
uint256 gasUsed = gasStart - gasleft();
|
||||
|
||||
emit BatchProcessed(batchId, successCount, failureCount, gasUsed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Auto-verifies bounty submissions based on performance metrics
|
||||
* @param _bountyId Bounty ID
|
||||
* @param _submissionId Submission ID
|
||||
* @param _accuracy Achieved accuracy
|
||||
* @param _responseTime Response time
|
||||
*/
|
||||
function autoVerifyBountySubmission(
|
||||
uint256 _bountyId,
|
||||
uint256 _submissionId,
|
||||
uint256 _accuracy,
|
||||
uint256 _responseTime
|
||||
) external
|
||||
onlyAuthorizedIntegrator
|
||||
nonReentrant
|
||||
{
|
||||
// Get bounty details
|
||||
(,,,,,, bytes32 performanceCriteria, uint256 minAccuracy,,,, bool requiresZKProof) = agentBounty.getBounty(_bountyId);
|
||||
|
||||
// Check if auto-verification conditions are met
|
||||
if (_accuracy >= autoVerificationThreshold && _accuracy >= minAccuracy) {
|
||||
// Verify the submission
|
||||
agentBounty.verifySubmission(_bountyId, _submissionId, true, address(this));
|
||||
|
||||
// Get submission details to calculate rewards
|
||||
(address submitter,,,,,,,) = agentBounty.getSubmission(_submissionId);
|
||||
|
||||
// Trigger staking rewards if applicable
|
||||
_triggerStakingRewards(submitter, _accuracy);
|
||||
|
||||
emit BountyAutoCompleted(_bountyId, _submissionId, submitter, 0); // Reward amount will be set by bounty contract
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Handles performance verification events
|
||||
* @param _verificationId Performance verification ID
|
||||
* @param _accuracy Accuracy achieved
|
||||
* @param _responseTime Response time
|
||||
* @param _performanceHash Hash of performance metrics
|
||||
*/
|
||||
function handlePerformanceVerified(
|
||||
uint256 _verificationId,
|
||||
uint256 _accuracy,
|
||||
uint256 _responseTime,
|
||||
bytes32 _performanceHash
|
||||
) external
|
||||
onlyAuthorizedIntegrator
|
||||
nonReentrant
|
||||
{
|
||||
// Check if this performance is mapped to any bounties
|
||||
uint256 mappingId = performanceHashToMapping[_performanceHash];
|
||||
if (mappingId > 0) {
|
||||
PerformanceMapping storage perfMap = performanceMappings[mappingId];
|
||||
|
||||
// Update agent staking metrics
|
||||
(address submitter,,,,,,,) = agentBounty.getSubmission(perfMap.submissionId);
|
||||
agentStaking.updateAgentPerformance(submitter, _accuracy, _accuracy >= autoVerificationThreshold);
|
||||
|
||||
// Auto-verify bounty if conditions are met
|
||||
_autoVerifyBounty(perfMap.bountyId, perfMap.submissionId, _accuracy, _responseTime);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Registers an event handler for cross-contract communication
|
||||
* @param _eventType Event type identifier
|
||||
* @param _targetContract Target contract address
|
||||
* @param _functionSelector Function selector to call
|
||||
*/
|
||||
function registerEventHandler(
|
||||
bytes32 _eventType,
|
||||
address _targetContract,
|
||||
bytes4 _functionSelector
|
||||
) external onlyOwner {
|
||||
require(_targetContract != address(0), "Invalid target contract");
|
||||
require(_functionSelector != bytes4(0), "Invalid function selector");
|
||||
|
||||
eventHandlers[_eventType] = EventHandler({
|
||||
eventType: _eventType,
|
||||
targetContract: _targetContract,
|
||||
functionSelector: _functionSelector,
|
||||
isActive: true,
|
||||
priority: 0
|
||||
});
|
||||
|
||||
emit EventHandlerRegistered(_eventType, _targetContract, _functionSelector);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Authorizes an integrator address
|
||||
* @param _integrator Address to authorize
|
||||
*/
|
||||
function authorizeIntegrator(address _integrator) external onlyOwner {
|
||||
require(_integrator != address(0), "Invalid integrator address");
|
||||
require(!authorizedIntegrators[_integrator], "Already authorized");
|
||||
|
||||
authorizedIntegrators[_integrator] = true;
|
||||
authorizedIntegratorList.push(_integrator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Revokes integrator authorization
|
||||
* @param _integrator Address to revoke
|
||||
*/
|
||||
function revokeIntegrator(address _integrator) external onlyOwner {
|
||||
require(authorizedIntegrators[_integrator], "Not authorized");
|
||||
|
||||
authorizedIntegrators[_integrator] = false;
|
||||
|
||||
// Remove from list
|
||||
for (uint256 i = 0; i < authorizedIntegratorList.length; i++) {
|
||||
if (authorizedIntegratorList[i] == _integrator) {
|
||||
authorizedIntegratorList[i] = authorizedIntegratorList[authorizedIntegratorList.length - 1];
|
||||
authorizedIntegratorList.pop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Updates configuration parameters
|
||||
* @param _autoVerificationThreshold New auto-verification threshold
|
||||
* @param _batchProcessingLimit New batch processing limit
|
||||
* @param _gasOptimizationThreshold New gas optimization threshold
|
||||
*/
|
||||
function updateConfiguration(
|
||||
uint256 _autoVerificationThreshold,
|
||||
uint256 _batchProcessingLimit,
|
||||
uint256 _gasOptimizationThreshold
|
||||
) external onlyOwner {
|
||||
require(_autoVerificationThreshold <= 100, "Invalid threshold");
|
||||
require(_batchProcessingLimit <= 100, "Batch limit too high");
|
||||
|
||||
autoVerificationThreshold = _autoVerificationThreshold;
|
||||
batchProcessingLimit = _batchProcessingLimit;
|
||||
gasOptimizationThreshold = _gasOptimizationThreshold;
|
||||
}
|
||||
|
||||
// View functions
|
||||
|
||||
/**
|
||||
* @dev Gets performance mapping details
|
||||
* @param _mappingId Mapping ID
|
||||
*/
|
||||
function getPerformanceMapping(uint256 _mappingId) external view mappingExists(_mappingId) returns (
|
||||
bytes32 performanceHash,
|
||||
uint256 bountyId,
|
||||
uint256 submissionId,
|
||||
IntegrationStatus status,
|
||||
uint256 createdAt,
|
||||
uint256 processedAt,
|
||||
string memory errorMessage
|
||||
) {
|
||||
PerformanceMapping storage perfMap = performanceMappings[_mappingId];
|
||||
return (
|
||||
perfMap.performanceHash,
|
||||
perfMap.bountyId,
|
||||
perfMap.submissionId,
|
||||
perfMap.status,
|
||||
perfMap.createdAt,
|
||||
perfMap.processedAt,
|
||||
perfMap.errorMessage
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets batch request details
|
||||
* @param _batchId Batch ID
|
||||
*/
|
||||
function getBatchRequest(uint256 _batchId) external view returns (
|
||||
uint256[] memory bountyIds,
|
||||
uint256[] memory submissionIds,
|
||||
IntegrationStatus status,
|
||||
uint256 createdAt,
|
||||
uint256 processedAt,
|
||||
uint256 successCount,
|
||||
uint256 failureCount
|
||||
) {
|
||||
BatchRequest storage batch = batchRequests[_batchId];
|
||||
return (
|
||||
batch.bountyIds,
|
||||
batch.submissionIds,
|
||||
batch.status,
|
||||
batch.createdAt,
|
||||
batch.processedAt,
|
||||
batch.successCount,
|
||||
batch.failureCount
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets pending mappings
|
||||
*/
|
||||
function getPendingMappings() external view returns (uint256[] memory) {
|
||||
return pendingMappings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets all performance hashes
|
||||
*/
|
||||
function getPerformanceHashes() external view returns (bytes32[] memory) {
|
||||
return performanceHashes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets authorized integrators
|
||||
*/
|
||||
function getAuthorizedIntegrators() external view returns (address[] memory) {
|
||||
return authorizedIntegratorList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Checks if an address is authorized
|
||||
* @param _integrator Address to check
|
||||
*/
|
||||
function isAuthorizedIntegrator(address _integrator) external view returns (bool) {
|
||||
return authorizedIntegrators[_integrator];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets integration statistics
|
||||
*/
|
||||
function getIntegrationStats() external view returns (
|
||||
uint256 totalMappings,
|
||||
uint256 pendingCount,
|
||||
uint256 completedCount,
|
||||
uint256 failedCount,
|
||||
uint256 averageProcessingTime
|
||||
) {
|
||||
uint256 completed = 0;
|
||||
uint256 failed = 0;
|
||||
uint256 totalTime = 0;
|
||||
uint256 processedCount = 0;
|
||||
|
||||
for (uint256 i = 0; i < integrationCounter; i++) {
|
||||
PerformanceMapping storage perfMap = performanceMappings[i];
|
||||
if (perfMap.status == IntegrationStatus.COMPLETED) {
|
||||
completed++;
|
||||
totalTime += perfMap.processedAt - perfMap.createdAt;
|
||||
processedCount++;
|
||||
} else if (perfMap.status == IntegrationStatus.FAILED) {
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
uint256 avgTime = processedCount > 0 ? totalTime / processedCount : 0;
|
||||
|
||||
return (
|
||||
integrationCounter,
|
||||
pendingMappings.length,
|
||||
completed,
|
||||
failed,
|
||||
avgTime
|
||||
);
|
||||
}
|
||||
|
||||
// Internal functions
|
||||
|
||||
function _processMapping(uint256 _mappingId) internal {
|
||||
PerformanceMapping storage perfMap = performanceMappings[_mappingId];
|
||||
|
||||
if (perfMap.status != IntegrationStatus.PENDING) {
|
||||
return;
|
||||
}
|
||||
|
||||
try this._processMappingInternal(_mappingId) {
|
||||
perfMap.status = IntegrationStatus.COMPLETED;
|
||||
perfMap.processedAt = block.timestamp;
|
||||
} catch Error(string memory reason) {
|
||||
perfMap.status = IntegrationStatus.FAILED;
|
||||
perfMap.errorMessage = reason;
|
||||
perfMap.processedAt = block.timestamp;
|
||||
|
||||
emit IntegrationFailed(_mappingId, reason, perfMap.performanceHash);
|
||||
} catch {
|
||||
perfMap.status = IntegrationStatus.FAILED;
|
||||
perfMap.errorMessage = "Unknown error";
|
||||
perfMap.processedAt = block.timestamp;
|
||||
|
||||
emit IntegrationFailed(_mappingId, "Unknown error", perfMap.performanceHash);
|
||||
}
|
||||
|
||||
// Remove from pending
|
||||
_removeFromPending(_mappingId);
|
||||
}
|
||||
|
||||
function _processMappingInternal(uint256 _mappingId) external {
|
||||
PerformanceMapping storage perfMap = performanceMappings[_mappingId];
|
||||
|
||||
// Get bounty details
|
||||
(,,,,,, bytes32 performanceCriteria, uint256 minAccuracy,,,, bool requiresZKProof) = agentBounty.getBounty(perfMap.bountyId);
|
||||
|
||||
// Get submission details
|
||||
(address submitter, bytes32 submissionHash, uint256 accuracy, uint256 responseTime,,,) = agentBounty.getSubmission(perfMap.submissionId);
|
||||
|
||||
// Verify performance criteria match
|
||||
require(perfMap.performanceHash == submissionHash, "Performance hash mismatch");
|
||||
|
||||
// Check if accuracy meets requirements
|
||||
require(accuracy >= minAccuracy, "Accuracy below minimum");
|
||||
|
||||
// Auto-verify if conditions are met
|
||||
if (accuracy >= autoVerificationThreshold) {
|
||||
agentBounty.verifySubmission(perfMap.bountyId, perfMap.submissionId, true, address(this));
|
||||
|
||||
// Update agent staking metrics
|
||||
agentStaking.updateAgentPerformance(submitter, accuracy, true);
|
||||
|
||||
// Trigger staking rewards
|
||||
_triggerStakingRewards(submitter, accuracy);
|
||||
}
|
||||
}
|
||||
|
||||
function _autoVerifyBounty(
|
||||
uint256 _bountyId,
|
||||
uint256 _submissionId,
|
||||
uint256 _accuracy,
|
||||
uint256 _responseTime
|
||||
) internal {
|
||||
if (_accuracy >= autoVerificationThreshold) {
|
||||
agentBounty.verifySubmission(_bountyId, _submissionId, true, address(this));
|
||||
}
|
||||
}
|
||||
|
||||
function _triggerStakingRewards(address _agentWallet, uint256 _accuracy) internal {
|
||||
// Calculate earnings based on accuracy
|
||||
uint256 baseEarnings = (_accuracy * 100) * 10**18; // Simplified calculation
|
||||
|
||||
// Distribute to stakers
|
||||
try agentStaking.distributeAgentEarnings(_agentWallet, baseEarnings) {
|
||||
emit StakingRewardsTriggered(_agentWallet, baseEarnings, 0);
|
||||
} catch {
|
||||
// Handle staking distribution failure
|
||||
}
|
||||
}
|
||||
|
||||
function _registerEventHandler(
|
||||
bytes32 _eventType,
|
||||
address _targetContract,
|
||||
bytes4 _functionSelector
|
||||
) internal {
|
||||
eventHandlers[_eventType] = EventHandler({
|
||||
eventType: _eventType,
|
||||
targetContract: _targetContract,
|
||||
functionSelector: _functionSelector,
|
||||
isActive: true,
|
||||
priority: 0
|
||||
});
|
||||
}
|
||||
|
||||
function _removeFromPending(uint256 _mappingId) internal {
|
||||
for (uint256 i = 0; i < pendingMappings.length; i++) {
|
||||
if (pendingMappings[i] == _mappingId) {
|
||||
pendingMappings[i] = pendingMappings[pendingMappings.length - 1];
|
||||
pendingMappings.pop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
730
contracts/contracts/DisputeResolution.sol
Normal file
730
contracts/contracts/DisputeResolution.sol
Normal file
@@ -0,0 +1,730 @@
|
||||
// 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 "./AIPowerRental.sol";
|
||||
import "./AITBCPaymentProcessor.sol";
|
||||
import "./PerformanceVerifier.sol";
|
||||
|
||||
/**
|
||||
* @title Dispute Resolution
|
||||
* @dev Advanced dispute resolution contract with automated arbitration and evidence verification
|
||||
* @notice Handles disputes between AI service providers and consumers with fair resolution mechanisms
|
||||
*/
|
||||
contract DisputeResolution is Ownable, ReentrancyGuard, Pausable {
|
||||
|
||||
// State variables
|
||||
AIPowerRental public aiPowerRental;
|
||||
AITBCPaymentProcessor public paymentProcessor;
|
||||
PerformanceVerifier public performanceVerifier;
|
||||
|
||||
uint256 public disputeCounter;
|
||||
uint256 public arbitrationFeePercentage = 100; // 1% in basis points
|
||||
uint256 public evidenceSubmissionPeriod = 3 days;
|
||||
uint256 public arbitrationPeriod = 7 days;
|
||||
uint256 public escalationThreshold = 3; // Number of disputes before escalation
|
||||
uint256 public minArbitrators = 3;
|
||||
uint256 public maxArbitrators = 5;
|
||||
|
||||
// Structs
|
||||
struct Dispute {
|
||||
uint256 disputeId;
|
||||
uint256 agreementId;
|
||||
address initiator;
|
||||
address respondent;
|
||||
DisputeStatus status;
|
||||
DisputeType disputeType;
|
||||
string reason;
|
||||
bytes32 evidenceHash;
|
||||
uint256 filingTime;
|
||||
uint256 evidenceDeadline;
|
||||
uint256 arbitrationDeadline;
|
||||
uint256 resolutionAmount;
|
||||
address winner;
|
||||
string resolutionReason;
|
||||
uint256 arbitratorCount;
|
||||
bool isEscalated;
|
||||
uint256 escalationLevel;
|
||||
}
|
||||
|
||||
struct Evidence {
|
||||
uint256 evidenceId;
|
||||
uint256 disputeId;
|
||||
address submitter;
|
||||
string evidenceType;
|
||||
string evidenceData;
|
||||
bytes32 evidenceHash;
|
||||
uint256 submissionTime;
|
||||
bool isValid;
|
||||
uint256 verificationScore;
|
||||
address verifiedBy;
|
||||
}
|
||||
|
||||
struct Arbitrator {
|
||||
address arbitratorAddress;
|
||||
bool isAuthorized;
|
||||
uint256 reputationScore;
|
||||
uint256 totalDisputes;
|
||||
uint256 successfulResolutions;
|
||||
uint256 lastActiveTime;
|
||||
ArbitratorStatus status;
|
||||
}
|
||||
|
||||
struct ArbitrationVote {
|
||||
uint256 disputeId;
|
||||
address arbitrator;
|
||||
bool voteInFavorOfInitiator;
|
||||
uint256 confidence;
|
||||
string reasoning;
|
||||
uint256 voteTime;
|
||||
bool isValid;
|
||||
}
|
||||
|
||||
struct EscalationRecord {
|
||||
uint256 disputeId;
|
||||
uint256 escalationLevel;
|
||||
address escalatedBy;
|
||||
string escalationReason;
|
||||
uint256 escalationTime;
|
||||
address[] assignedArbitrators;
|
||||
}
|
||||
|
||||
// Enums
|
||||
enum DisputeStatus {
|
||||
Filed,
|
||||
EvidenceSubmitted,
|
||||
UnderReview,
|
||||
ArbitrationInProgress,
|
||||
Resolved,
|
||||
Escalated,
|
||||
Rejected,
|
||||
Expired
|
||||
}
|
||||
|
||||
enum DisputeType {
|
||||
Performance,
|
||||
Payment,
|
||||
ServiceQuality,
|
||||
Availability,
|
||||
Other
|
||||
}
|
||||
|
||||
enum ArbitratorStatus {
|
||||
Active,
|
||||
Inactive,
|
||||
Suspended,
|
||||
Retired
|
||||
}
|
||||
|
||||
enum EvidenceType {
|
||||
PerformanceMetrics,
|
||||
Logs,
|
||||
Screenshots,
|
||||
Videos,
|
||||
Documents,
|
||||
Testimonials,
|
||||
BlockchainProof,
|
||||
ZKProof
|
||||
}
|
||||
|
||||
// Mappings
|
||||
mapping(uint256 => Dispute) public disputes;
|
||||
mapping(uint256 => Evidence[]) public disputeEvidence;
|
||||
mapping(uint256 => ArbitrationVote[]) public arbitrationVotes;
|
||||
mapping(uint256 => EscalationRecord) public escalations;
|
||||
mapping(address => Arbitrator) public arbitrators;
|
||||
mapping(address => uint256[]) public arbitratorDisputes;
|
||||
mapping(address => uint256[]) public userDisputes;
|
||||
mapping(uint256 => uint256) public agreementDisputes;
|
||||
mapping(address => bool) public authorizedArbitrators;
|
||||
mapping(uint256 => mapping(address => bool)) public hasVoted;
|
||||
|
||||
// Arrays for tracking
|
||||
address[] public authorizedArbitratorList;
|
||||
uint256[] public activeDisputes;
|
||||
|
||||
// Events
|
||||
event DisputeFiled(
|
||||
uint256 indexed disputeId,
|
||||
uint256 indexed agreementId,
|
||||
address indexed initiator,
|
||||
address respondent,
|
||||
DisputeType disputeType,
|
||||
string reason
|
||||
);
|
||||
|
||||
event EvidenceSubmitted(
|
||||
uint256 indexed disputeId,
|
||||
uint256 indexed evidenceId,
|
||||
address indexed submitter,
|
||||
string evidenceType,
|
||||
bytes32 evidenceHash
|
||||
);
|
||||
|
||||
event EvidenceVerified(
|
||||
uint256 indexed disputeId,
|
||||
uint256 indexed evidenceId,
|
||||
bool isValid,
|
||||
uint256 verificationScore
|
||||
);
|
||||
|
||||
event ArbitratorAssigned(
|
||||
uint256 indexed disputeId,
|
||||
address indexed arbitrator,
|
||||
uint256 escalationLevel
|
||||
);
|
||||
|
||||
event ArbitrationVoteSubmitted(
|
||||
uint256 indexed disputeId,
|
||||
address indexed arbitrator,
|
||||
bool voteInFavorOfInitiator,
|
||||
uint256 confidence
|
||||
);
|
||||
|
||||
event DisputeResolved(
|
||||
uint256 indexed disputeId,
|
||||
address indexed winner,
|
||||
uint256 resolutionAmount,
|
||||
string resolutionReason
|
||||
);
|
||||
|
||||
event DisputeEscalated(
|
||||
uint256 indexed disputeId,
|
||||
uint256 escalationLevel,
|
||||
address indexed escalatedBy,
|
||||
string escalationReason
|
||||
);
|
||||
|
||||
event ArbitratorAuthorized(
|
||||
address indexed arbitrator,
|
||||
uint256 reputationScore
|
||||
);
|
||||
|
||||
event ArbitratorRevoked(
|
||||
address indexed arbitrator,
|
||||
string reason
|
||||
);
|
||||
|
||||
event ArbitrationFeeCollected(
|
||||
uint256 indexed disputeId,
|
||||
uint256 feeAmount,
|
||||
address indexed collector
|
||||
);
|
||||
|
||||
// Modifiers
|
||||
modifier onlyAuthorizedArbitrator() {
|
||||
require(authorizedArbitrators[msg.sender], "Not authorized arbitrator");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier disputeExists(uint256 _disputeId) {
|
||||
require(_disputeId < disputeCounter, "Dispute does not exist");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier validStatus(uint256 _disputeId, DisputeStatus _requiredStatus) {
|
||||
require(disputes[_disputeId].status == _requiredStatus, "Invalid dispute status");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier onlyParticipant(uint256 _disputeId) {
|
||||
require(
|
||||
msg.sender == disputes[_disputeId].initiator ||
|
||||
msg.sender == disputes[_disputeId].respondent,
|
||||
"Not dispute participant"
|
||||
);
|
||||
_;
|
||||
}
|
||||
|
||||
modifier withinDeadline(uint256 _deadline) {
|
||||
require(block.timestamp <= _deadline, "Deadline passed");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier hasNotVoted(uint256 _disputeId) {
|
||||
require(!hasVoted[_disputeId][msg.sender], "Already voted");
|
||||
_;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
constructor(
|
||||
address _aiPowerRental,
|
||||
address _paymentProcessor,
|
||||
address _performanceVerifier
|
||||
) {
|
||||
aiPowerRental = AIPowerRental(_aiPowerRental);
|
||||
paymentProcessor = AITBCPaymentProcessor(_paymentProcessor);
|
||||
performanceVerifier = PerformanceVerifier(_performanceVerifier);
|
||||
disputeCounter = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Files a new dispute
|
||||
* @param _agreementId ID of the agreement being disputed
|
||||
* @param _respondent The other party in the dispute
|
||||
* @param _disputeType Type of dispute
|
||||
* @param _reason Reason for the dispute
|
||||
* @param _evidenceHash Hash of initial evidence
|
||||
*/
|
||||
function fileDispute(
|
||||
uint256 _agreementId,
|
||||
address _respondent,
|
||||
DisputeType _disputeType,
|
||||
string memory _reason,
|
||||
bytes32 _evidenceHash
|
||||
) external nonReentrant whenNotPaused returns (uint256) {
|
||||
require(_respondent != address(0), "Invalid respondent");
|
||||
require(_respondent != msg.sender, "Cannot dispute yourself");
|
||||
require(bytes(_reason).length > 0, "Reason required");
|
||||
|
||||
// Verify agreement exists and get participants
|
||||
(, address provider, address consumer, , , , , , , ) = aiPowerRental.getRentalAgreement(_agreementId);
|
||||
require(provider != address(0), "Invalid agreement");
|
||||
|
||||
// Verify caller is a participant
|
||||
require(
|
||||
msg.sender == provider || msg.sender == consumer,
|
||||
"Not agreement participant"
|
||||
);
|
||||
|
||||
// Verify respondent is the other participant
|
||||
address otherParticipant = msg.sender == provider ? consumer : provider;
|
||||
require(_respondent == otherParticipant, "Respondent not in agreement");
|
||||
|
||||
uint256 disputeId = disputeCounter++;
|
||||
|
||||
disputes[disputeId] = Dispute({
|
||||
disputeId: disputeId,
|
||||
agreementId: _agreementId,
|
||||
initiator: msg.sender,
|
||||
respondent: _respondent,
|
||||
status: DisputeStatus.Filed,
|
||||
disputeType: _disputeType,
|
||||
reason: _reason,
|
||||
evidenceHash: _evidenceHash,
|
||||
filingTime: block.timestamp,
|
||||
evidenceDeadline: block.timestamp + evidenceSubmissionPeriod,
|
||||
arbitrationDeadline: block.timestamp + evidenceSubmissionPeriod + arbitrationPeriod,
|
||||
resolutionAmount: 0,
|
||||
winner: address(0),
|
||||
resolutionReason: "",
|
||||
arbitratorCount: 0,
|
||||
isEscalated: false,
|
||||
escalationLevel: 1
|
||||
});
|
||||
|
||||
userDisputes[msg.sender].push(disputeId);
|
||||
userDisputes[_respondent].push(disputeId);
|
||||
agreementDisputes[_agreementId] = disputeId;
|
||||
activeDisputes.push(disputeId);
|
||||
|
||||
emit DisputeFiled(disputeId, _agreementId, msg.sender, _respondent, _disputeType, _reason);
|
||||
|
||||
return disputeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Submits evidence for a dispute
|
||||
* @param _disputeId ID of the dispute
|
||||
* @param _evidenceType Type of evidence
|
||||
* @param _evidenceData Evidence data (can be IPFS hash, URL, etc.)
|
||||
*/
|
||||
function submitEvidence(
|
||||
uint256 _disputeId,
|
||||
string memory _evidenceType,
|
||||
string memory _evidenceData
|
||||
) external disputeExists(_disputeId) onlyParticipant(_disputeId) withinDeadline(disputes[_disputeId].evidenceDeadline) nonReentrant {
|
||||
Dispute storage dispute = disputes[_disputeId];
|
||||
|
||||
require(dispute.status == DisputeStatus.Filed || dispute.status == DisputeStatus.EvidenceSubmitted, "Cannot submit evidence");
|
||||
|
||||
uint256 evidenceId = disputeEvidence[_disputeId].length;
|
||||
bytes32 evidenceHash = keccak256(abi.encodePacked(_evidenceData, msg.sender, block.timestamp));
|
||||
|
||||
disputeEvidence[_disputeId].push(Evidence({
|
||||
evidenceId: evidenceId,
|
||||
disputeId: _disputeId,
|
||||
submitter: msg.sender,
|
||||
evidenceType: _evidenceType,
|
||||
evidenceData: _evidenceData,
|
||||
evidenceHash: evidenceHash,
|
||||
submissionTime: block.timestamp,
|
||||
isValid: false,
|
||||
verificationScore: 0,
|
||||
verifiedBy: address(0)
|
||||
}));
|
||||
|
||||
dispute.status = DisputeStatus.EvidenceSubmitted;
|
||||
|
||||
emit EvidenceSubmitted(_disputeId, evidenceId, msg.sender, _evidenceType, evidenceHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Verifies evidence submitted in a dispute
|
||||
* @param _disputeId ID of the dispute
|
||||
* @param _evidenceId ID of the evidence
|
||||
* @param _isValid Whether the evidence is valid
|
||||
* @param _verificationScore Verification score (0-100)
|
||||
*/
|
||||
function verifyEvidence(
|
||||
uint256 _disputeId,
|
||||
uint256 _evidenceId,
|
||||
bool _isValid,
|
||||
uint256 _verificationScore
|
||||
) external onlyAuthorizedArbitrator disputeExists(_disputeId) nonReentrant {
|
||||
require(_evidenceId < disputeEvidence[_disputeId].length, "Invalid evidence ID");
|
||||
|
||||
Evidence storage evidence = disputeEvidence[_disputeId][_evidenceId];
|
||||
evidence.isValid = _isValid;
|
||||
evidence.verificationScore = _verificationScore;
|
||||
evidence.verifiedBy = msg.sender;
|
||||
|
||||
emit EvidenceVerified(_disputeId, _evidenceId, _isValid, _verificationScore);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Assigns arbitrators to a dispute
|
||||
* @param _disputeId ID of the dispute
|
||||
* @param _arbitrators Array of arbitrator addresses
|
||||
*/
|
||||
function assignArbitrators(
|
||||
uint256 _disputeId,
|
||||
address[] memory _arbitrators
|
||||
) external onlyOwner disputeExists(_disputeId) nonReentrant {
|
||||
Dispute storage dispute = disputes[_disputeId];
|
||||
|
||||
require(_arbitrators.length >= minArbitrators && _arbitrators.length <= maxArbitrators, "Invalid arbitrator count");
|
||||
|
||||
for (uint256 i = 0; i < _arbitrators.length; i++) {
|
||||
require(authorizedArbitrators[_arbitrators[i]], "Arbitrator not authorized");
|
||||
require(_arbitrators[i] != dispute.initiator && _arbitrators[i] != dispute.respondent, "Conflict of interest");
|
||||
}
|
||||
|
||||
dispute.arbitratorCount = _arbitrators.length;
|
||||
dispute.status = DisputeStatus.ArbitrationInProgress;
|
||||
|
||||
for (uint256 i = 0; i < _arbitrators.length; i++) {
|
||||
arbitratorDisputes[_arbitrators[i]].push(_disputeId);
|
||||
emit ArbitratorAssigned(_disputeId, _arbitrators[i], dispute.escalationLevel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Submits arbitration vote
|
||||
* @param _disputeId ID of the dispute
|
||||
* @param _voteInFavorOfInitiator Vote for initiator
|
||||
* @param _confidence Confidence level (0-100)
|
||||
* @param _reasoning Reasoning for the vote
|
||||
*/
|
||||
function submitArbitrationVote(
|
||||
uint256 _disputeId,
|
||||
bool _voteInFavorOfInitiator,
|
||||
uint256 _confidence,
|
||||
string memory _reasoning
|
||||
) external onlyAuthorizedArbitrator disputeExists(_disputeId) validStatus(_disputeId, DisputeStatus.ArbitrationInProgress) hasNotVoted(_disputeId) withinDeadline(disputes[_disputeId].arbitrationDeadline) nonReentrant {
|
||||
Dispute storage dispute = disputes[_disputeId];
|
||||
|
||||
// Verify arbitrator is assigned to this dispute
|
||||
bool isAssigned = false;
|
||||
for (uint256 i = 0; i < arbitratorDisputes[msg.sender].length; i++) {
|
||||
if (arbitratorDisputes[msg.sender][i] == _disputeId) {
|
||||
isAssigned = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
require(isAssigned, "Arbitrator not assigned");
|
||||
|
||||
arbitrationVotes[_disputeId].push(ArbitrationVote({
|
||||
disputeId: _disputeId,
|
||||
arbitrator: msg.sender,
|
||||
voteInFavorOfInitiator: _voteInFavorOfInitiator,
|
||||
confidence: _confidence,
|
||||
reasoning: _reasoning,
|
||||
voteTime: block.timestamp,
|
||||
isValid: true
|
||||
}));
|
||||
|
||||
hasVoted[_disputeId][msg.sender] = true;
|
||||
|
||||
// Update arbitrator stats
|
||||
Arbitrator storage arbitrator = arbitrators[msg.sender];
|
||||
arbitrator.totalDisputes++;
|
||||
arbitrator.lastActiveTime = block.timestamp;
|
||||
|
||||
emit ArbitrationVoteSubmitted(_disputeId, msg.sender, _voteInFavorOfInitiator, _confidence);
|
||||
|
||||
// Check if all arbitrators have voted
|
||||
if (arbitrationVotes[_disputeId].length == dispute.arbitratorCount) {
|
||||
_resolveDispute(_disputeId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Escalates a dispute to higher level
|
||||
* @param _disputeId ID of the dispute
|
||||
* @param _escalationReason Reason for escalation
|
||||
*/
|
||||
function escalateDispute(
|
||||
uint256 _disputeId,
|
||||
string memory _escalationReason
|
||||
) external onlyOwner disputeExists(_disputeId) nonReentrant {
|
||||
Dispute storage dispute = disputes[_disputeId];
|
||||
|
||||
require(dispute.status == DisputeStatus.Resolved, "Cannot escalate unresolved dispute");
|
||||
require(dispute.escalationLevel < 3, "Max escalation level reached");
|
||||
|
||||
dispute.escalationLevel++;
|
||||
dispute.isEscalated = true;
|
||||
dispute.status = DisputeStatus.Escalated;
|
||||
|
||||
escalations[_disputeId] = EscalationRecord({
|
||||
disputeId: _disputeId,
|
||||
escalationLevel: dispute.escalationLevel,
|
||||
escalatedBy: msg.sender,
|
||||
escalationReason: _escalationReason,
|
||||
escalationTime: block.timestamp,
|
||||
assignedArbitrators: new address[](0)
|
||||
});
|
||||
|
||||
emit DisputeEscalated(_disputeId, dispute.escalationLevel, msg.sender, _escalationReason);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Authorizes an arbitrator
|
||||
* @param _arbitrator Address of the arbitrator
|
||||
* @param _reputationScore Initial reputation score
|
||||
*/
|
||||
function authorizeArbitrator(address _arbitrator, uint256 _reputationScore) external onlyOwner {
|
||||
require(_arbitrator != address(0), "Invalid arbitrator address");
|
||||
require(!authorizedArbitrators[_arbitrator], "Arbitrator already authorized");
|
||||
|
||||
authorizedArbitrators[_arbitrator] = true;
|
||||
authorizedArbitratorList.push(_arbitrator);
|
||||
|
||||
arbitrators[_arbitrator] = Arbitrator({
|
||||
arbitratorAddress: _arbitrator,
|
||||
isAuthorized: true,
|
||||
reputationScore: _reputationScore,
|
||||
totalDisputes: 0,
|
||||
successfulResolutions: 0,
|
||||
lastActiveTime: block.timestamp,
|
||||
status: ArbitratorStatus.Active
|
||||
});
|
||||
|
||||
emit ArbitratorAuthorized(_arbitrator, _reputationScore);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Revokes arbitrator authorization
|
||||
* @param _arbitrator Address of the arbitrator
|
||||
* @param _reason Reason for revocation
|
||||
*/
|
||||
function revokeArbitrator(address _arbitrator, string memory _reason) external onlyOwner {
|
||||
require(authorizedArbitrators[_arbitrator], "Arbitrator not authorized");
|
||||
|
||||
authorizedArbitrators[_arbitrator] = false;
|
||||
arbitrators[_arbitrator].status = ArbitratorStatus.Suspended;
|
||||
|
||||
emit ArbitratorRevoked(_arbitrator, _reason);
|
||||
}
|
||||
|
||||
// Internal functions
|
||||
|
||||
function _resolveDispute(uint256 _disputeId) internal {
|
||||
Dispute storage dispute = disputes[_disputeId];
|
||||
ArbitrationVote[] storage votes = arbitrationVotes[_disputeId];
|
||||
|
||||
uint256 votesForInitiator = 0;
|
||||
uint256 votesForRespondent = 0;
|
||||
uint256 totalConfidence = 0;
|
||||
uint256 weightedVotesForInitiator = 0;
|
||||
|
||||
// Calculate weighted votes
|
||||
for (uint256 i = 0; i < votes.length; i++) {
|
||||
ArbitrationVote storage vote = votes[i];
|
||||
totalConfidence += vote.confidence;
|
||||
|
||||
if (vote.voteInFavorOfInitiator) {
|
||||
votesForInitiator++;
|
||||
weightedVotesForInitiator += vote.confidence;
|
||||
} else {
|
||||
votesForRespondent++;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine winner based on weighted votes
|
||||
bool initiatorWins = weightedVotesForInitiator > (totalConfidence / 2);
|
||||
|
||||
dispute.winner = initiatorWins ? dispute.initiator : dispute.respondent;
|
||||
dispute.status = DisputeStatus.Resolved;
|
||||
|
||||
// Calculate resolution amount based on agreement
|
||||
(, address provider, address consumer, uint256 duration, uint256 price, , , , , ) = aiPowerRental.getRentalAgreement(dispute.agreementId);
|
||||
|
||||
if (initiatorWins) {
|
||||
dispute.resolutionAmount = price; // Full refund/compensation
|
||||
} else {
|
||||
dispute.resolutionAmount = 0; // No compensation
|
||||
}
|
||||
|
||||
// Update arbitrator success rates
|
||||
for (uint256 i = 0; i < votes.length; i++) {
|
||||
ArbitrationVote storage vote = votes[i];
|
||||
Arbitrator storage arbitrator = arbitrators[vote.arbitrator];
|
||||
|
||||
if ((vote.voteInFavorOfInitiator && initiatorWins) || (!vote.voteInFavorOfInitiator && !initiatorWins)) {
|
||||
arbitrator.successfulResolutions++;
|
||||
}
|
||||
}
|
||||
|
||||
dispute.resolutionReason = initiatorWins ? "Evidence and reasoning support initiator" : "Evidence and reasoning support respondent";
|
||||
|
||||
emit DisputeResolved(_disputeId, dispute.winner, dispute.resolutionAmount, dispute.resolutionReason);
|
||||
}
|
||||
|
||||
// View functions
|
||||
|
||||
/**
|
||||
* @dev Gets dispute details
|
||||
* @param _disputeId ID of the dispute
|
||||
*/
|
||||
function getDispute(uint256 _disputeId)
|
||||
external
|
||||
view
|
||||
disputeExists(_disputeId)
|
||||
returns (Dispute memory)
|
||||
{
|
||||
return disputes[_disputeId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets evidence for a dispute
|
||||
* @param _disputeId ID of the dispute
|
||||
*/
|
||||
function getDisputeEvidence(uint256 _disputeId)
|
||||
external
|
||||
view
|
||||
disputeExists(_disputeId)
|
||||
returns (Evidence[] memory)
|
||||
{
|
||||
return disputeEvidence[_disputeId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets arbitration votes for a dispute
|
||||
* @param _disputeId ID of the dispute
|
||||
*/
|
||||
function getArbitrationVotes(uint256 _disputeId)
|
||||
external
|
||||
view
|
||||
disputeExists(_disputeId)
|
||||
returns (ArbitrationVote[] memory)
|
||||
{
|
||||
return arbitrationVotes[_disputeId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets arbitrator information
|
||||
* @param _arbitrator Address of the arbitrator
|
||||
*/
|
||||
function getArbitrator(address _arbitrator)
|
||||
external
|
||||
view
|
||||
returns (Arbitrator memory)
|
||||
{
|
||||
return arbitrators[_arbitrator];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets all disputes for a user
|
||||
* @param _user Address of the user
|
||||
*/
|
||||
function getUserDisputes(address _user)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory)
|
||||
{
|
||||
return userDisputes[_user];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets all disputes for an arbitrator
|
||||
* @param _arbitrator Address of the arbitrator
|
||||
*/
|
||||
function getArbitratorDisputes(address _arbitrator)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory)
|
||||
{
|
||||
return arbitratorDisputes[_arbitrator];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets all authorized arbitrators
|
||||
*/
|
||||
function getAuthorizedArbitrators()
|
||||
external
|
||||
view
|
||||
returns (address[] memory)
|
||||
{
|
||||
address[] memory activeArbitrators = new address[](authorizedArbitratorList.length);
|
||||
uint256 activeCount = 0;
|
||||
|
||||
for (uint256 i = 0; i < authorizedArbitratorList.length; i++) {
|
||||
if (authorizedArbitrators[authorizedArbitratorList[i]]) {
|
||||
activeArbitrators[activeCount] = authorizedArbitratorList[i];
|
||||
activeCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Resize array to active count
|
||||
assembly {
|
||||
mstore(activeArbitrators, activeCount)
|
||||
}
|
||||
|
||||
return activeArbitrators;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets active disputes
|
||||
*/
|
||||
function getActiveDisputes()
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory)
|
||||
{
|
||||
uint256[] memory active = new uint256[](activeDisputes.length);
|
||||
uint256 activeCount = 0;
|
||||
|
||||
for (uint256 i = 0; i < activeDisputes.length; i++) {
|
||||
if (disputes[activeDisputes[i]].status != DisputeStatus.Resolved &&
|
||||
disputes[activeDisputes[i]].status != DisputeStatus.Rejected) {
|
||||
active[activeCount] = activeDisputes[i];
|
||||
activeCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Resize array to active count
|
||||
assembly {
|
||||
mstore(active, activeCount)
|
||||
}
|
||||
|
||||
return active;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Emergency pause function
|
||||
*/
|
||||
function pause() external onlyOwner {
|
||||
_pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Unpause function
|
||||
*/
|
||||
function unpause() external onlyOwner {
|
||||
_unpause();
|
||||
}
|
||||
}
|
||||
757
contracts/contracts/DynamicPricing.sol
Normal file
757
contracts/contracts/DynamicPricing.sol
Normal file
@@ -0,0 +1,757 @@
|
||||
// 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 "./AIPowerRental.sol";
|
||||
import "./PerformanceVerifier.sol";
|
||||
|
||||
/**
|
||||
* @title Dynamic Pricing
|
||||
* @dev Advanced dynamic pricing contract with supply/demand analysis and automated price adjustment
|
||||
* @notice Implements data-driven pricing for AI power marketplace with ZK-based verification
|
||||
*/
|
||||
contract DynamicPricing is Ownable, ReentrancyGuard, Pausable {
|
||||
|
||||
// State variables
|
||||
AIPowerRental public aiPowerRental;
|
||||
PerformanceVerifier public performanceVerifier;
|
||||
IERC20 public aitbcToken;
|
||||
|
||||
uint256 public priceUpdateCounter;
|
||||
uint256 public basePricePerHour = 1e16; // 0.01 AITBC per hour
|
||||
uint256 public minPricePerHour = 1e15; // 0.001 AITBC minimum
|
||||
uint256 public maxPricePerHour = 1e18; // 0.1 AITBC maximum
|
||||
uint256 public priceVolatilityThreshold = 2000; // 20% in basis points
|
||||
uint256 public priceUpdateInterval = 3600; // 1 hour
|
||||
uint256 public marketDataRetentionPeriod = 7 days;
|
||||
uint256 public smoothingFactor = 50; // 50% smoothing in basis points
|
||||
uint256 public surgeMultiplier = 300; // 3x surge pricing max
|
||||
uint256 public discountMultiplier = 50; // 50% minimum price
|
||||
|
||||
// Structs
|
||||
struct MarketData {
|
||||
uint256 totalSupply;
|
||||
uint256 totalDemand;
|
||||
uint256 activeProviders;
|
||||
uint256 activeConsumers;
|
||||
uint256 averagePrice;
|
||||
uint256 priceVolatility;
|
||||
uint256 utilizationRate;
|
||||
uint256 lastUpdateTime;
|
||||
uint256 totalVolume;
|
||||
uint256 transactionCount;
|
||||
uint256 averageResponseTime;
|
||||
uint256 averageAccuracy;
|
||||
uint256 marketSentiment;
|
||||
bool isMarketActive;
|
||||
}
|
||||
|
||||
struct PriceHistory {
|
||||
uint256 timestamp;
|
||||
uint256 price;
|
||||
uint256 supply;
|
||||
uint256 demand;
|
||||
uint256 volume;
|
||||
PriceChangeType changeType;
|
||||
uint256 changePercentage;
|
||||
}
|
||||
|
||||
struct ProviderPricing {
|
||||
address provider;
|
||||
uint256 currentPrice;
|
||||
uint256 basePrice;
|
||||
uint256 reputationScore;
|
||||
uint256 utilizationRate;
|
||||
uint256 performanceScore;
|
||||
uint256 demandScore;
|
||||
uint256 supplyScore;
|
||||
uint256 lastUpdateTime;
|
||||
PricingStrategy strategy;
|
||||
uint256 priceAdjustmentFactor;
|
||||
}
|
||||
|
||||
struct RegionalPricing {
|
||||
string region;
|
||||
uint256 regionalMultiplier;
|
||||
uint256 localSupply;
|
||||
uint256 localDemand;
|
||||
uint256 averagePrice;
|
||||
uint256 lastUpdateTime;
|
||||
uint256 competitionLevel;
|
||||
uint256 infrastructureCost;
|
||||
}
|
||||
|
||||
struct DemandForecast {
|
||||
uint256 forecastPeriod;
|
||||
uint256 predictedDemand;
|
||||
uint256 confidence;
|
||||
uint256 forecastTime;
|
||||
uint256 actualDemand;
|
||||
uint256 forecastAccuracy;
|
||||
}
|
||||
|
||||
struct PriceAlert {
|
||||
uint256 alertId;
|
||||
address subscriber;
|
||||
PriceAlertType alertType;
|
||||
uint256 thresholdPrice;
|
||||
uint256 currentPrice;
|
||||
bool isActive;
|
||||
uint256 lastTriggered;
|
||||
string notificationMethod;
|
||||
}
|
||||
|
||||
// Enums
|
||||
enum PriceChangeType {
|
||||
Increase,
|
||||
Decrease,
|
||||
Stable,
|
||||
Surge,
|
||||
Discount
|
||||
}
|
||||
|
||||
enum PricingStrategy {
|
||||
Fixed,
|
||||
Dynamic,
|
||||
Competitive,
|
||||
PerformanceBased,
|
||||
TimeBased,
|
||||
DemandBased
|
||||
}
|
||||
|
||||
enum MarketCondition {
|
||||
Oversupply,
|
||||
Balanced,
|
||||
Undersupply,
|
||||
Surge,
|
||||
Crash
|
||||
}
|
||||
|
||||
enum PriceAlertType {
|
||||
PriceAbove,
|
||||
PriceBelow,
|
||||
VolatilityHigh,
|
||||
TrendChange
|
||||
}
|
||||
|
||||
// Mappings
|
||||
mapping(uint256 => MarketData) public marketDataHistory;
|
||||
mapping(uint256 => PriceHistory[]) public priceHistory;
|
||||
mapping(address => ProviderPricing) public providerPricing;
|
||||
mapping(string => RegionalPricing) public regionalPricing;
|
||||
mapping(uint256 => DemandForecast) public demandForecasts;
|
||||
mapping(uint256 => PriceAlert) public priceAlerts;
|
||||
mapping(address => uint256[]) public providerPriceHistory;
|
||||
mapping(string => uint256[]) public regionalPriceHistory;
|
||||
mapping(address => bool) public authorizedPriceOracles;
|
||||
mapping(uint256 => bool) public isValidPriceUpdate;
|
||||
|
||||
// Arrays for tracking
|
||||
string[] public supportedRegions;
|
||||
uint256[] public activePriceAlerts;
|
||||
uint256[] public recentPriceUpdates;
|
||||
|
||||
// Events
|
||||
event MarketDataUpdated(
|
||||
uint256 indexed timestamp,
|
||||
uint256 totalSupply,
|
||||
uint256 totalDemand,
|
||||
uint256 averagePrice,
|
||||
MarketCondition marketCondition
|
||||
);
|
||||
|
||||
event PriceCalculated(
|
||||
uint256 indexed timestamp,
|
||||
uint256 newPrice,
|
||||
uint256 oldPrice,
|
||||
PriceChangeType changeType,
|
||||
uint256 changePercentage
|
||||
);
|
||||
|
||||
event ProviderPriceUpdated(
|
||||
address indexed provider,
|
||||
uint256 newPrice,
|
||||
PricingStrategy strategy,
|
||||
uint256 adjustmentFactor
|
||||
);
|
||||
|
||||
event RegionalPriceUpdated(
|
||||
string indexed region,
|
||||
uint256 newMultiplier,
|
||||
uint256 localSupply,
|
||||
uint256 localDemand
|
||||
);
|
||||
|
||||
event DemandForecastCreated(
|
||||
uint256 indexed forecastPeriod,
|
||||
uint256 predictedDemand,
|
||||
uint256 confidence,
|
||||
uint256 forecastTime
|
||||
);
|
||||
|
||||
event PriceAlertTriggered(
|
||||
uint256 indexed alertId,
|
||||
address indexed subscriber,
|
||||
PriceAlertType alertType,
|
||||
uint256 currentPrice,
|
||||
uint256 thresholdPrice
|
||||
);
|
||||
|
||||
event SurgePricingActivated(
|
||||
uint256 surgeMultiplier,
|
||||
uint256 duration,
|
||||
string reason
|
||||
);
|
||||
|
||||
event DiscountPricingActivated(
|
||||
uint256 discountMultiplier,
|
||||
uint256 duration,
|
||||
string reason
|
||||
);
|
||||
|
||||
event MarketConditionChanged(
|
||||
MarketCondition oldCondition,
|
||||
MarketCondition newCondition,
|
||||
uint256 timestamp
|
||||
);
|
||||
|
||||
event PriceOracleAuthorized(
|
||||
address indexed oracle,
|
||||
uint256 reputationScore
|
||||
);
|
||||
|
||||
event PriceOracleRevoked(
|
||||
address indexed oracle,
|
||||
string reason
|
||||
);
|
||||
|
||||
// Modifiers
|
||||
modifier onlyAuthorizedPriceOracle() {
|
||||
require(authorizedPriceOracles[msg.sender], "Not authorized price oracle");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier validPriceUpdate(uint256 _timestamp) {
|
||||
require(block.timestamp - _timestamp <= priceUpdateInterval, "Price update too old");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier validProvider(address _provider) {
|
||||
require(_provider != address(0), "Invalid provider address");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier validRegion(string memory _region) {
|
||||
require(bytes(_region).length > 0, "Invalid region");
|
||||
_;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
constructor(
|
||||
address _aiPowerRental,
|
||||
address _performanceVerifier,
|
||||
address _aitbcToken
|
||||
) {
|
||||
aiPowerRental = AIPowerRental(_aiPowerRental);
|
||||
performanceVerifier = PerformanceVerifier(_performanceVerifier);
|
||||
aitbcToken = IERC20(_aitbcToken);
|
||||
priceUpdateCounter = 0;
|
||||
|
||||
// Initialize supported regions
|
||||
supportedRegions.push("us-east");
|
||||
supportedRegions.push("us-west");
|
||||
supportedRegions.push("eu-central");
|
||||
supportedRegions.push("eu-west");
|
||||
supportedRegions.push("ap-southeast");
|
||||
supportedRegions.push("ap-northeast");
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Updates market data and recalculates prices
|
||||
* @param _totalSupply Total compute power supply
|
||||
* @param _totalDemand Total compute power demand
|
||||
* @param _activeProviders Number of active providers
|
||||
* @param _activeConsumers Number of active consumers
|
||||
* @param _totalVolume Total transaction volume
|
||||
* @param _transactionCount Number of transactions
|
||||
* @param _averageResponseTime Average response time
|
||||
* @param _averageAccuracy Average accuracy
|
||||
* @param _marketSentiment Market sentiment score (0-100)
|
||||
*/
|
||||
function updateMarketData(
|
||||
uint256 _totalSupply,
|
||||
uint256 _totalDemand,
|
||||
uint256 _activeProviders,
|
||||
uint256 _activeConsumers,
|
||||
uint256 _totalVolume,
|
||||
uint256 _transactionCount,
|
||||
uint256 _averageResponseTime,
|
||||
uint256 _averageAccuracy,
|
||||
uint256 _marketSentiment
|
||||
) external onlyAuthorizedPriceOracle nonReentrant whenNotPaused {
|
||||
require(_totalSupply > 0, "Invalid supply");
|
||||
require(_totalDemand > 0, "Invalid demand");
|
||||
|
||||
uint256 timestamp = block.timestamp;
|
||||
uint256 priceUpdateId = priceUpdateCounter++;
|
||||
|
||||
// Calculate utilization rate
|
||||
uint256 utilizationRate = (_totalDemand * 10000) / _totalSupply;
|
||||
|
||||
// Get previous market data for comparison
|
||||
MarketData storage previousData = marketDataHistory[priceUpdateId > 0 ? priceUpdateId - 1 : 0];
|
||||
|
||||
// Calculate new average price
|
||||
uint256 newAveragePrice = _calculateDynamicPrice(
|
||||
_totalSupply,
|
||||
_totalDemand,
|
||||
utilizationRate,
|
||||
_marketSentiment,
|
||||
previousData.averagePrice
|
||||
);
|
||||
|
||||
// Calculate price volatility
|
||||
uint256 priceVolatility = 0;
|
||||
if (previousData.averagePrice > 0) {
|
||||
if (newAveragePrice > previousData.averagePrice) {
|
||||
priceVolatility = ((newAveragePrice - previousData.averagePrice) * 10000) / previousData.averagePrice;
|
||||
} else {
|
||||
priceVolatility = ((previousData.averagePrice - newAveragePrice) * 10000) / previousData.averagePrice;
|
||||
}
|
||||
}
|
||||
|
||||
// Store market data
|
||||
marketDataHistory[priceUpdateId] = MarketData({
|
||||
totalSupply: _totalSupply,
|
||||
totalDemand: _totalDemand,
|
||||
activeProviders: _activeProviders,
|
||||
activeConsumers: _activeConsumers,
|
||||
averagePrice: newAveragePrice,
|
||||
priceVolatility: priceVolatility,
|
||||
utilizationRate: utilizationRate,
|
||||
lastUpdateTime: timestamp,
|
||||
totalVolume: _totalVolume,
|
||||
transactionCount: _transactionCount,
|
||||
averageResponseTime: _averageResponseTime,
|
||||
averageAccuracy: _averageAccuracy,
|
||||
marketSentiment: _marketSentiment,
|
||||
isMarketActive: _activeProviders > 0 && _activeConsumers > 0
|
||||
});
|
||||
|
||||
// Determine market condition
|
||||
MarketCondition currentCondition = _determineMarketCondition(utilizationRate, priceVolatility);
|
||||
|
||||
// Store price history
|
||||
PriceChangeType changeType = _determinePriceChangeType(previousData.averagePrice, newAveragePrice);
|
||||
uint256 changePercentage = previousData.averagePrice > 0 ?
|
||||
((newAveragePrice - previousData.averagePrice) * 10000) / previousData.averagePrice : 0;
|
||||
|
||||
priceHistory[priceUpdateId].push(PriceHistory({
|
||||
timestamp: timestamp,
|
||||
price: newAveragePrice,
|
||||
supply: _totalSupply,
|
||||
demand: _totalDemand,
|
||||
volume: _totalVolume,
|
||||
changeType: changeType,
|
||||
changePercentage: changePercentage
|
||||
}));
|
||||
|
||||
// Update provider prices
|
||||
_updateProviderPrices(newAveragePrice, utilizationRate);
|
||||
|
||||
// Update regional prices
|
||||
_updateRegionalPrices(_totalSupply, _totalDemand);
|
||||
|
||||
// Check price alerts
|
||||
_checkPriceAlerts(newAveragePrice);
|
||||
|
||||
// Apply surge or discount pricing if needed
|
||||
_applySpecialPricing(currentCondition, priceVolatility);
|
||||
|
||||
isValidPriceUpdate[priceUpdateId] = true;
|
||||
recentPriceUpdates.push(priceUpdateId);
|
||||
|
||||
emit MarketDataUpdated(timestamp, _totalSupply, _totalDemand, newAveragePrice, currentCondition);
|
||||
emit PriceCalculated(timestamp, newAveragePrice, previousData.averagePrice, changeType, changePercentage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Calculates dynamic price based on market conditions
|
||||
* @param _supply Total supply
|
||||
* @param _demand Total demand
|
||||
* @param _utilizationRate Utilization rate in basis points
|
||||
* @param _marketSentiment Market sentiment (0-100)
|
||||
* @param _previousPrice Previous average price
|
||||
*/
|
||||
function _calculateDynamicPrice(
|
||||
uint256 _supply,
|
||||
uint256 _demand,
|
||||
uint256 _utilizationRate,
|
||||
uint256 _marketSentiment,
|
||||
uint256 _previousPrice
|
||||
) internal view returns (uint256) {
|
||||
// Base price calculation
|
||||
uint256 newPrice = basePricePerHour;
|
||||
|
||||
// Supply/demand adjustment
|
||||
if (_demand > _supply) {
|
||||
uint256 demandPremium = ((_demand - _supply) * 10000) / _supply;
|
||||
newPrice = (newPrice * (10000 + demandPremium)) / 10000;
|
||||
} else if (_supply > _demand) {
|
||||
uint256 supplyDiscount = ((_supply - _demand) * 10000) / _supply;
|
||||
newPrice = (newPrice * (10000 - supplyDiscount)) / 10000;
|
||||
}
|
||||
|
||||
// Utilization rate adjustment
|
||||
if (_utilizationRate > 8000) { // > 80% utilization
|
||||
uint256 utilizationPremium = (_utilizationRate - 8000) / 2;
|
||||
newPrice = (newPrice * (10000 + utilizationPremium)) / 10000;
|
||||
} else if (_utilizationRate < 2000) { // < 20% utilization
|
||||
uint256 utilizationDiscount = (2000 - _utilizationRate) / 4;
|
||||
newPrice = (newPrice * (10000 - utilizationDiscount)) / 10000;
|
||||
}
|
||||
|
||||
// Market sentiment adjustment
|
||||
if (_marketSentiment > 70) { // High sentiment
|
||||
newPrice = (newPrice * 10500) / 10000; // 5% premium
|
||||
} else if (_marketSentiment < 30) { // Low sentiment
|
||||
newPrice = (newPrice * 9500) / 10000; // 5% discount
|
||||
}
|
||||
|
||||
// Smoothing with previous price
|
||||
if (_previousPrice > 0) {
|
||||
newPrice = (newPrice * (10000 - smoothingFactor) + _previousPrice * smoothingFactor) / 10000;
|
||||
}
|
||||
|
||||
// Apply price bounds
|
||||
if (newPrice < minPricePerHour) {
|
||||
newPrice = minPricePerHour;
|
||||
} else if (newPrice > maxPricePerHour) {
|
||||
newPrice = maxPricePerHour;
|
||||
}
|
||||
|
||||
return newPrice;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Updates provider-specific pricing
|
||||
* @param _marketAveragePrice Current market average price
|
||||
* @param _marketUtilizationRate Market utilization rate
|
||||
*/
|
||||
function _updateProviderPrices(uint256 _marketAveragePrice, uint256 _marketUtilizationRate) internal {
|
||||
// This would typically iterate through all active providers
|
||||
// For now, we'll update based on provider performance and reputation
|
||||
|
||||
// Implementation would include:
|
||||
// 1. Get provider performance metrics
|
||||
// 2. Calculate provider-specific adjustments
|
||||
// 3. Update provider pricing based on strategy
|
||||
// 4. Emit ProviderPriceUpdated events
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Updates regional pricing
|
||||
* @param _totalSupply Total supply
|
||||
* @param _totalDemand Total demand
|
||||
*/
|
||||
function _updateRegionalPrices(uint256 _totalSupply, uint256 _totalDemand) internal {
|
||||
for (uint256 i = 0; i < supportedRegions.length; i++) {
|
||||
string memory region = supportedRegions[i];
|
||||
RegionalPricing storage regional = regionalPricing[region];
|
||||
|
||||
// Calculate regional supply/demand (simplified)
|
||||
uint256 regionalSupply = (_totalSupply * regionalPricing[region].localSupply) / 100;
|
||||
uint256 regionalDemand = (_totalDemand * regionalPricing[region].localDemand) / 100;
|
||||
|
||||
// Calculate regional multiplier
|
||||
uint256 newMultiplier = 10000; // Base multiplier
|
||||
if (regionalDemand > regionalSupply) {
|
||||
newMultiplier = (newMultiplier * 11000) / 10000; // 10% premium
|
||||
} else if (regionalSupply > regionalDemand) {
|
||||
newMultiplier = (newMultiplier * 9500) / 10000; // 5% discount
|
||||
}
|
||||
|
||||
regional.regionalMultiplier = newMultiplier;
|
||||
regional.lastUpdateTime = block.timestamp;
|
||||
|
||||
emit RegionalPriceUpdated(region, newMultiplier, regionalSupply, regionalDemand);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Determines market condition based on utilization and volatility
|
||||
* @param _utilizationRate Utilization rate in basis points
|
||||
* @param _priceVolatility Price volatility in basis points
|
||||
*/
|
||||
function _determineMarketCondition(uint256 _utilizationRate, uint256 _priceVolatility) internal pure returns (MarketCondition) {
|
||||
if (_utilizationRate > 9000) {
|
||||
return MarketCondition.Surge;
|
||||
} else if (_utilizationRate > 7000) {
|
||||
return MarketCondition.Undersupply;
|
||||
} else if (_utilizationRate > 3000) {
|
||||
return MarketCondition.Balanced;
|
||||
} else if (_utilizationRate > 1000) {
|
||||
return MarketCondition.Oversupply;
|
||||
} else {
|
||||
return MarketCondition.Crash;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Determines price change type
|
||||
* @param _oldPrice Previous price
|
||||
* @param _newPrice New price
|
||||
*/
|
||||
function _determinePriceChangeType(uint256 _oldPrice, uint256 _newPrice) internal pure returns (PriceChangeType) {
|
||||
if (_oldPrice == 0) {
|
||||
return PriceChangeType.Stable;
|
||||
}
|
||||
|
||||
uint256 changePercentage = 0;
|
||||
if (_newPrice > _oldPrice) {
|
||||
changePercentage = ((_newPrice - _oldPrice) * 10000) / _oldPrice;
|
||||
} else {
|
||||
changePercentage = ((_oldPrice - _newPrice) * 10000) / _oldPrice;
|
||||
}
|
||||
|
||||
if (changePercentage < 500) { // < 5%
|
||||
return PriceChangeType.Stable;
|
||||
} else if (changePercentage > 2000) { // > 20%
|
||||
return _newPrice > _oldPrice ? PriceChangeType.Surge : PriceChangeType.Discount;
|
||||
} else {
|
||||
return _newPrice > _oldPrice ? PriceChangeType.Increase : PriceChangeType.Decrease;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Applies special pricing based on market conditions
|
||||
* @param _condition Current market condition
|
||||
* @param _volatility Price volatility
|
||||
*/
|
||||
function _applySpecialPricing(MarketCondition _condition, uint256 _volatility) internal {
|
||||
if (_condition == MarketCondition.Surge) {
|
||||
emit SurgePricingActivated(surgeMultiplier, 3600, "High demand detected");
|
||||
} else if (_condition == MarketCondition.Crash) {
|
||||
emit DiscountPricingActivated(discountMultiplier, 3600, "Low demand detected");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Creates demand forecast
|
||||
* @param _forecastPeriod Period to forecast (in seconds)
|
||||
* @param _predictedDemand Predicted demand
|
||||
* @param _confidence Confidence level (0-100)
|
||||
*/
|
||||
function createDemandForecast(
|
||||
uint256 _forecastPeriod,
|
||||
uint256 _predictedDemand,
|
||||
uint256 _confidence
|
||||
) external onlyAuthorizedPriceOracle nonReentrant whenNotPaused {
|
||||
require(_forecastPeriod > 0, "Invalid forecast period");
|
||||
require(_predictedDemand > 0, "Invalid predicted demand");
|
||||
require(_confidence <= 100, "Invalid confidence");
|
||||
|
||||
uint256 forecastId = priceUpdateCounter++;
|
||||
|
||||
demandForecasts[forecastId] = DemandForecast({
|
||||
forecastPeriod: _forecastPeriod,
|
||||
predictedDemand: _predictedDemand,
|
||||
confidence: _confidence,
|
||||
forecastTime: block.timestamp,
|
||||
actualDemand: 0,
|
||||
forecastAccuracy: 0
|
||||
});
|
||||
|
||||
emit DemandForecastCreated(_forecastPeriod, _predictedDemand, _confidence, block.timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Creates price alert
|
||||
* @param _subscriber Address to notify
|
||||
* @param _alertType Type of alert
|
||||
* @param _thresholdPrice Threshold price
|
||||
* @param _notificationMethod Notification method
|
||||
*/
|
||||
function createPriceAlert(
|
||||
address _subscriber,
|
||||
PriceAlertType _alertType,
|
||||
uint256 _thresholdPrice,
|
||||
string memory _notificationMethod
|
||||
) external nonReentrant whenNotPaused returns (uint256) {
|
||||
require(_subscriber != address(0), "Invalid subscriber");
|
||||
require(_thresholdPrice > 0, "Invalid threshold price");
|
||||
|
||||
uint256 alertId = priceUpdateCounter++;
|
||||
|
||||
priceAlerts[alertId] = PriceAlert({
|
||||
alertId: alertId,
|
||||
subscriber: _subscriber,
|
||||
alertType: _alertType,
|
||||
thresholdPrice: _thresholdPrice,
|
||||
currentPrice: 0,
|
||||
isActive: true,
|
||||
lastTriggered: 0,
|
||||
notificationMethod: _notificationMethod
|
||||
});
|
||||
|
||||
activePriceAlerts.push(alertId);
|
||||
|
||||
return alertId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets current market price
|
||||
* @param _provider Provider address (optional, for provider-specific pricing)
|
||||
* @param _region Region (optional, for regional pricing)
|
||||
*/
|
||||
function getMarketPrice(address _provider, string memory _region)
|
||||
external
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
uint256 basePrice = basePricePerHour;
|
||||
|
||||
// Get latest market data
|
||||
if (priceUpdateCounter > 0) {
|
||||
basePrice = marketDataHistory[priceUpdateCounter - 1].averagePrice;
|
||||
}
|
||||
|
||||
// Apply regional multiplier if specified
|
||||
if (bytes(_region).length > 0) {
|
||||
RegionalPricing storage regional = regionalPricing[_region];
|
||||
basePrice = (basePrice * regional.regionalMultiplier) / 10000;
|
||||
}
|
||||
|
||||
// Apply provider-specific pricing if specified
|
||||
if (_provider != address(0)) {
|
||||
ProviderPricing storage provider = providerPricing[_provider];
|
||||
if (provider.currentPrice > 0) {
|
||||
basePrice = provider.currentPrice;
|
||||
}
|
||||
}
|
||||
|
||||
return basePrice;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets market data
|
||||
* @param _timestamp Timestamp to get data for (0 for latest)
|
||||
*/
|
||||
function getMarketData(uint256 _timestamp)
|
||||
external
|
||||
view
|
||||
returns (MarketData memory)
|
||||
{
|
||||
if (_timestamp == 0 && priceUpdateCounter > 0) {
|
||||
return marketDataHistory[priceUpdateCounter - 1];
|
||||
}
|
||||
|
||||
// Find closest timestamp
|
||||
for (uint256 i = priceUpdateCounter; i > 0; i--) {
|
||||
if (marketDataHistory[i - 1].lastUpdateTime <= _timestamp) {
|
||||
return marketDataHistory[i - 1];
|
||||
}
|
||||
}
|
||||
|
||||
revert("No market data found for timestamp");
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets price history
|
||||
* @param _count Number of historical entries to return
|
||||
*/
|
||||
function getPriceHistory(uint256 _count)
|
||||
external
|
||||
view
|
||||
returns (PriceHistory[] memory)
|
||||
{
|
||||
uint256 startIndex = priceUpdateCounter > _count ? priceUpdateCounter - _count : 0;
|
||||
uint256 length = priceUpdateCounter - startIndex;
|
||||
|
||||
PriceHistory[] memory history = new PriceHistory[](length);
|
||||
|
||||
for (uint256 i = 0; i < length; i++) {
|
||||
history[i] = priceHistory[startIndex + i][0];
|
||||
}
|
||||
|
||||
return history;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Authorizes a price oracle
|
||||
* @param _oracle Address of the oracle
|
||||
*/
|
||||
function authorizePriceOracle(address _oracle) external onlyOwner {
|
||||
require(_oracle != address(0), "Invalid oracle address");
|
||||
authorizedPriceOracles[_oracle] = true;
|
||||
emit PriceOracleAuthorized(_oracle, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Revokes price oracle authorization
|
||||
* @param _oracle Address of the oracle
|
||||
*/
|
||||
function revokePriceOracle(address _oracle) external onlyOwner {
|
||||
authorizedPriceOracles[_oracle] = false;
|
||||
emit PriceOracleRevoked(_oracle, "Authorization revoked");
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Updates base pricing parameters
|
||||
* @param _basePrice New base price
|
||||
* @param _minPrice New minimum price
|
||||
* @param _maxPrice New maximum price
|
||||
*/
|
||||
function updateBasePricing(
|
||||
uint256 _basePrice,
|
||||
uint256 _minPrice,
|
||||
uint256 _maxPrice
|
||||
) external onlyOwner {
|
||||
require(_minPrice > 0 && _maxPrice > _minPrice, "Invalid price bounds");
|
||||
require(_basePrice >= _minPrice && _basePrice <= _maxPrice, "Base price out of bounds");
|
||||
|
||||
basePricePerHour = _basePrice;
|
||||
minPricePerHour = _minPrice;
|
||||
maxPricePerHour = _maxPrice;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Emergency pause function
|
||||
*/
|
||||
function pause() external onlyOwner {
|
||||
_pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Unpause function
|
||||
*/
|
||||
function unpause() external onlyOwner {
|
||||
_unpause();
|
||||
}
|
||||
|
||||
// Internal function to check price alerts
|
||||
function _checkPriceAlerts(uint256 _currentPrice) internal {
|
||||
for (uint256 i = 0; i < activePriceAlerts.length; i++) {
|
||||
uint256 alertId = activePriceAlerts[i];
|
||||
PriceAlert storage alert = priceAlerts[alertId];
|
||||
|
||||
if (!alert.isActive) continue;
|
||||
|
||||
bool shouldTrigger = false;
|
||||
|
||||
if (alert.alertType == PriceAlertType.PriceAbove && _currentPrice > alert.thresholdPrice) {
|
||||
shouldTrigger = true;
|
||||
} else if (alert.alertType == PriceAlertType.PriceBelow && _currentPrice < alert.thresholdPrice) {
|
||||
shouldTrigger = true;
|
||||
}
|
||||
|
||||
if (shouldTrigger) {
|
||||
alert.lastTriggered = block.timestamp;
|
||||
emit PriceAlertTriggered(alertId, alert.subscriber, alert.alertType, _currentPrice, alert.thresholdPrice);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
880
contracts/contracts/EscrowService.sol
Normal file
880
contracts/contracts/EscrowService.sol
Normal file
@@ -0,0 +1,880 @@
|
||||
// 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 "./AIPowerRental.sol";
|
||||
import "./AITBCPaymentProcessor.sol";
|
||||
|
||||
/**
|
||||
* @title Escrow Service
|
||||
* @dev Advanced escrow service with multi-signature, time-locks, and conditional releases
|
||||
* @notice Secure payment holding with automated release conditions and dispute protection
|
||||
*/
|
||||
contract EscrowService is Ownable, ReentrancyGuard, Pausable {
|
||||
|
||||
// State variables
|
||||
IERC20 public aitbcToken;
|
||||
AIPowerRental public aiPowerRental;
|
||||
AITBCPaymentProcessor public paymentProcessor;
|
||||
|
||||
uint256 public escrowCounter;
|
||||
uint256 public minEscrowAmount = 1e15; // 0.001 AITBC minimum
|
||||
uint256 public maxEscrowAmount = 1e22; // 10,000 AITBC maximum
|
||||
uint256 public minTimeLock = 300; // 5 minutes minimum
|
||||
uint256 public maxTimeLock = 86400 * 30; // 30 days maximum
|
||||
uint256 public defaultReleaseDelay = 3600; // 1 hour default
|
||||
uint256 public emergencyReleaseDelay = 86400; // 24 hours for emergency
|
||||
uint256 public platformFeePercentage = 50; // 0.5% in basis points
|
||||
|
||||
// Structs
|
||||
struct EscrowAccount {
|
||||
uint256 escrowId;
|
||||
address depositor;
|
||||
address beneficiary;
|
||||
address arbiter;
|
||||
uint256 amount;
|
||||
uint256 platformFee;
|
||||
uint256 releaseTime;
|
||||
uint256 creationTime;
|
||||
bool isReleased;
|
||||
bool isRefunded;
|
||||
bool isFrozen;
|
||||
EscrowType escrowType;
|
||||
ReleaseCondition releaseCondition;
|
||||
bytes32 conditionHash;
|
||||
string conditionDescription;
|
||||
uint256 releaseAttempts;
|
||||
uint256 lastReleaseAttempt;
|
||||
address[] signatories;
|
||||
mapping(address => bool) hasSigned;
|
||||
uint256 requiredSignatures;
|
||||
uint256 currentSignatures;
|
||||
}
|
||||
|
||||
struct ConditionalRelease {
|
||||
uint256 escrowId;
|
||||
bytes32 condition;
|
||||
bool conditionMet;
|
||||
address oracle;
|
||||
uint256 verificationTime;
|
||||
string conditionData;
|
||||
uint256 confidence;
|
||||
}
|
||||
|
||||
struct MultiSigRelease {
|
||||
uint256 escrowId;
|
||||
address[] requiredSigners;
|
||||
uint256 signaturesRequired;
|
||||
mapping(address => bool) hasSigned;
|
||||
uint256 currentSignatures;
|
||||
uint256 deadline;
|
||||
bool isExecuted;
|
||||
}
|
||||
|
||||
struct TimeLockRelease {
|
||||
uint256 escrowId;
|
||||
uint256 lockStartTime;
|
||||
uint256 lockDuration;
|
||||
uint256 releaseWindow;
|
||||
bool canEarlyRelease;
|
||||
uint256 earlyReleaseFee;
|
||||
bool isReleased;
|
||||
}
|
||||
|
||||
struct EmergencyRelease {
|
||||
uint256 escrowId;
|
||||
address initiator;
|
||||
string reason;
|
||||
uint256 requestTime;
|
||||
uint256 votingDeadline;
|
||||
mapping(address => bool) hasVoted;
|
||||
uint256 votesFor;
|
||||
uint256 votesAgainst;
|
||||
uint256 totalVotes;
|
||||
bool isApproved;
|
||||
bool isExecuted;
|
||||
}
|
||||
|
||||
// Enums
|
||||
enum EscrowType {
|
||||
Standard,
|
||||
MultiSignature,
|
||||
TimeLocked,
|
||||
Conditional,
|
||||
PerformanceBased,
|
||||
MilestoneBased,
|
||||
Emergency
|
||||
}
|
||||
|
||||
enum ReleaseCondition {
|
||||
Manual,
|
||||
Automatic,
|
||||
OracleVerified,
|
||||
PerformanceMet,
|
||||
TimeBased,
|
||||
MultiSignature,
|
||||
Emergency
|
||||
}
|
||||
|
||||
enum EscrowStatus {
|
||||
Created,
|
||||
Funded,
|
||||
Locked,
|
||||
ConditionPending,
|
||||
Approved,
|
||||
Released,
|
||||
Refunded,
|
||||
Disputed,
|
||||
Frozen,
|
||||
Expired
|
||||
}
|
||||
|
||||
// Mappings
|
||||
mapping(uint256 => EscrowAccount) public escrowAccounts;
|
||||
mapping(uint256 => ConditionalRelease) public conditionalReleases;
|
||||
mapping(uint256 => MultiSigRelease) public multiSigReleases;
|
||||
mapping(uint256 => TimeLockRelease) public timeLockReleases;
|
||||
mapping(uint256 => EmergencyRelease) public emergencyReleases;
|
||||
mapping(address => uint256[]) public depositorEscrows;
|
||||
mapping(address => uint256[]) public beneficiaryEscrows;
|
||||
mapping(bytes32 => uint256) public conditionEscrows;
|
||||
mapping(address => bool) public authorizedOracles;
|
||||
mapping(address => bool) public authorizedArbiters;
|
||||
|
||||
// Arrays for tracking
|
||||
uint256[] public activeEscrows;
|
||||
uint256[] public pendingReleases;
|
||||
|
||||
// Events
|
||||
event EscrowCreated(
|
||||
uint256 indexed escrowId,
|
||||
address indexed depositor,
|
||||
address indexed beneficiary,
|
||||
uint256 amount,
|
||||
EscrowType escrowType,
|
||||
ReleaseCondition releaseCondition
|
||||
);
|
||||
|
||||
event EscrowFunded(
|
||||
uint256 indexed escrowId,
|
||||
uint256 amount,
|
||||
uint256 platformFee
|
||||
);
|
||||
|
||||
event EscrowReleased(
|
||||
uint256 indexed escrowId,
|
||||
address indexed beneficiary,
|
||||
uint256 amount,
|
||||
string reason
|
||||
);
|
||||
|
||||
event EscrowRefunded(
|
||||
uint256 indexed escrowId,
|
||||
address indexed depositor,
|
||||
uint256 amount,
|
||||
string reason
|
||||
);
|
||||
|
||||
event ConditionSet(
|
||||
uint256 indexed escrowId,
|
||||
bytes32 indexed condition,
|
||||
address indexed oracle,
|
||||
string conditionDescription
|
||||
);
|
||||
|
||||
event ConditionMet(
|
||||
uint256 indexed escrowId,
|
||||
bytes32 indexed condition,
|
||||
bool conditionMet,
|
||||
uint256 verificationTime
|
||||
);
|
||||
|
||||
event MultiSignatureRequired(
|
||||
uint256 indexed escrowId,
|
||||
address[] requiredSigners,
|
||||
uint256 signaturesRequired
|
||||
);
|
||||
|
||||
event SignatureSubmitted(
|
||||
uint256 indexed escrowId,
|
||||
address indexed signer,
|
||||
uint256 currentSignatures,
|
||||
uint256 requiredSignatures
|
||||
);
|
||||
|
||||
event TimeLockSet(
|
||||
uint256 indexed escrowId,
|
||||
uint256 lockDuration,
|
||||
uint256 releaseWindow,
|
||||
bool canEarlyRelease
|
||||
);
|
||||
|
||||
event EmergencyReleaseRequested(
|
||||
uint256 indexed escrowId,
|
||||
address indexed initiator,
|
||||
string reason,
|
||||
uint256 votingDeadline
|
||||
);
|
||||
|
||||
event EmergencyReleaseApproved(
|
||||
uint256 indexed escrowId,
|
||||
uint256 votesFor,
|
||||
uint256 votesAgainst,
|
||||
bool approved
|
||||
);
|
||||
|
||||
event EscrowFrozen(
|
||||
uint256 indexed escrowId,
|
||||
address indexed freezer,
|
||||
string reason
|
||||
);
|
||||
|
||||
event EscrowUnfrozen(
|
||||
uint256 indexed escrowId,
|
||||
address indexed unfreezer,
|
||||
string reason
|
||||
);
|
||||
|
||||
event PlatformFeeCollected(
|
||||
uint256 indexed escrowId,
|
||||
uint256 feeAmount,
|
||||
address indexed collector
|
||||
);
|
||||
|
||||
// Modifiers
|
||||
modifier onlyAuthorizedOracle() {
|
||||
require(authorizedOracles[msg.sender], "Not authorized oracle");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier onlyAuthorizedArbiter() {
|
||||
require(authorizedArbiters[msg.sender], "Not authorized arbiter");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier escrowExists(uint256 _escrowId) {
|
||||
require(_escrowId < escrowCounter, "Escrow does not exist");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier onlyParticipant(uint256 _escrowId) {
|
||||
require(
|
||||
msg.sender == escrowAccounts[_escrowId].depositor ||
|
||||
msg.sender == escrowAccounts[_escrowId].beneficiary ||
|
||||
msg.sender == escrowAccounts[_escrowId].arbiter,
|
||||
"Not escrow participant"
|
||||
);
|
||||
_;
|
||||
}
|
||||
|
||||
modifier sufficientBalance(address _user, uint256 _amount) {
|
||||
require(aitbcToken.balanceOf(_user) >= _amount, "Insufficient balance");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier sufficientAllowance(address _user, uint256 _amount) {
|
||||
require(aitbcToken.allowance(_user, address(this)) >= _amount, "Insufficient allowance");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier escrowNotFrozen(uint256 _escrowId) {
|
||||
require(!escrowAccounts[_escrowId].isFrozen, "Escrow is frozen");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier escrowNotReleased(uint256 _escrowId) {
|
||||
require(!escrowAccounts[_escrowId].isReleased, "Escrow already released");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier escrowNotRefunded(uint256 _escrowId) {
|
||||
require(!escrowAccounts[_escrowId].isRefunded, "Escrow already refunded");
|
||||
_;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
constructor(
|
||||
address _aitbcToken,
|
||||
address _aiPowerRental,
|
||||
address _paymentProcessor
|
||||
) {
|
||||
aitbcToken = IERC20(_aitbcToken);
|
||||
aiPowerRental = AIPowerRental(_aiPowerRental);
|
||||
paymentProcessor = AITBCPaymentProcessor(_paymentProcessor);
|
||||
escrowCounter = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Creates a new escrow account
|
||||
* @param _beneficiary Beneficiary address
|
||||
* @param _arbiter Arbiter address (can be zero address for no arbiter)
|
||||
* @param _amount Amount to lock in escrow
|
||||
* @param _escrowType Type of escrow
|
||||
* @param _releaseCondition Release condition
|
||||
* @param _releaseTime Release time (0 for no time limit)
|
||||
* @param _conditionDescription Description of release conditions
|
||||
*/
|
||||
function createEscrow(
|
||||
address _beneficiary,
|
||||
address _arbiter,
|
||||
uint256 _amount,
|
||||
EscrowType _escrowType,
|
||||
ReleaseCondition _releaseCondition,
|
||||
uint256 _releaseTime,
|
||||
string memory _conditionDescription
|
||||
) external sufficientBalance(msg.sender, _amount) sufficientAllowance(msg.sender, _amount) nonReentrant whenNotPaused returns (uint256) {
|
||||
require(_beneficiary != address(0), "Invalid beneficiary");
|
||||
require(_beneficiary != msg.sender, "Cannot be own beneficiary");
|
||||
require(_amount >= minEscrowAmount && _amount <= maxEscrowAmount, "Invalid amount");
|
||||
require(_releaseTime == 0 || _releaseTime > block.timestamp, "Invalid release time");
|
||||
|
||||
uint256 escrowId = escrowCounter++;
|
||||
uint256 platformFee = (_amount * platformFeePercentage) / 10000;
|
||||
uint256 totalAmount = _amount + platformFee;
|
||||
|
||||
escrowAccounts[escrowId] = EscrowAccount({
|
||||
escrowId: escrowId,
|
||||
depositor: msg.sender,
|
||||
beneficiary: _beneficiary,
|
||||
arbiter: _arbiter,
|
||||
amount: _amount,
|
||||
platformFee: platformFee,
|
||||
releaseTime: _releaseTime,
|
||||
creationTime: block.timestamp,
|
||||
isReleased: false,
|
||||
isRefunded: false,
|
||||
isFrozen: false,
|
||||
escrowType: _escrowType,
|
||||
releaseCondition: _releaseCondition,
|
||||
conditionHash: bytes32(0),
|
||||
conditionDescription: _conditionDescription,
|
||||
releaseAttempts: 0,
|
||||
lastReleaseAttempt: 0,
|
||||
signatories: new address[](0),
|
||||
requiredSignatures: 0,
|
||||
currentSignatures: 0
|
||||
});
|
||||
|
||||
depositorEscrows[msg.sender].push(escrowId);
|
||||
beneficiaryEscrows[_beneficiary].push(escrowId);
|
||||
activeEscrows.push(escrowId);
|
||||
|
||||
// Transfer tokens to contract
|
||||
require(
|
||||
aitbcToken.transferFrom(msg.sender, address(this), totalAmount),
|
||||
"Escrow funding failed"
|
||||
);
|
||||
|
||||
emit EscrowCreated(escrowId, msg.sender, _beneficiary, _amount, _escrowType, _releaseCondition);
|
||||
emit EscrowFunded(escrowId, _amount, platformFee);
|
||||
|
||||
// Setup specific escrow type configurations
|
||||
if (_escrowType == EscrowType.TimeLocked) {
|
||||
_setupTimeLock(escrowId, _releaseTime - block.timestamp);
|
||||
} else if (_escrowType == EscrowType.MultiSignature) {
|
||||
_setupMultiSignature(escrowId);
|
||||
}
|
||||
|
||||
return escrowId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Sets release condition for escrow
|
||||
* @param _escrowId ID of the escrow
|
||||
* @param _condition Condition hash
|
||||
* @param _oracle Oracle address for verification
|
||||
* @param _conditionData Condition data
|
||||
*/
|
||||
function setReleaseCondition(
|
||||
uint256 _escrowId,
|
||||
bytes32 _condition,
|
||||
address _oracle,
|
||||
string memory _conditionData
|
||||
) external escrowExists(_escrowId) onlyParticipant(_escrowId) escrowNotFrozen(_escrowId) escrowNotReleased(_escrowId) {
|
||||
require(authorizedOracles[_oracle] || _oracle == address(0), "Invalid oracle");
|
||||
|
||||
EscrowAccount storage escrow = escrowAccounts[_escrowId];
|
||||
escrow.conditionHash = _condition;
|
||||
|
||||
conditionalReleases[_escrowId] = ConditionalRelease({
|
||||
escrowId: _escrowId,
|
||||
condition: _condition,
|
||||
conditionMet: false,
|
||||
oracle: _oracle,
|
||||
verificationTime: 0,
|
||||
conditionData: _conditionData,
|
||||
confidence: 0
|
||||
});
|
||||
|
||||
conditionEscrows[_condition] = _escrowId;
|
||||
|
||||
emit ConditionSet(_escrowId, _condition, _oracle, _conditionData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Verifies and meets release condition
|
||||
* @param _escrowId ID of the escrow
|
||||
* @param _conditionMet Whether condition is met
|
||||
* @param _confidence Confidence level (0-100)
|
||||
*/
|
||||
function verifyCondition(
|
||||
uint256 _escrowId,
|
||||
bool _conditionMet,
|
||||
uint256 _confidence
|
||||
) external onlyAuthorizedOracle escrowExists(_escrowId) escrowNotFrozen(_escrowId) escrowNotReleased(_escrowId) {
|
||||
ConditionalRelease storage condRelease = conditionalReleases[_escrowId];
|
||||
require(condRelease.oracle == msg.sender, "Not assigned oracle");
|
||||
|
||||
condRelease.conditionMet = _conditionMet;
|
||||
condRelease.verificationTime = block.timestamp;
|
||||
condRelease.confidence = _confidence;
|
||||
|
||||
emit ConditionMet(_escrowId, condRelease.condition, _conditionMet, block.timestamp);
|
||||
|
||||
if (_conditionMet) {
|
||||
_releaseEscrow(_escrowId, "Condition verified and met");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Submits signature for multi-signature release
|
||||
* @param _escrowId ID of the escrow
|
||||
*/
|
||||
function submitSignature(uint256 _escrowId)
|
||||
external
|
||||
escrowExists(_escrowId)
|
||||
onlyParticipant(_escrowId)
|
||||
escrowNotFrozen(_escrowId)
|
||||
escrowNotReleased(_escrowId)
|
||||
{
|
||||
EscrowAccount storage escrow = escrowAccounts[_escrowId];
|
||||
MultiSigRelease storage multiSig = multiSigReleases[_escrowId];
|
||||
|
||||
require(!escrow.hasSigned[msg.sender], "Already signed");
|
||||
require(multiSig.requiredSigners > 0, "Multi-signature not setup");
|
||||
|
||||
escrow.hasSigned[msg.sender] = true;
|
||||
escrow.currentSignatures++;
|
||||
multiSig.currentSignatures++;
|
||||
|
||||
emit SignatureSubmitted(_escrowId, msg.sender, escrow.currentSignatures, multiSig.requiredSigners);
|
||||
|
||||
if (escrow.currentSignatures >= multiSig.requiredSigners) {
|
||||
_releaseEscrow(_escrowId, "Multi-signature requirement met");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Releases escrow to beneficiary
|
||||
* @param _escrowId ID of the escrow
|
||||
* @param _reason Reason for release
|
||||
*/
|
||||
function releaseEscrow(uint256 _escrowId, string memory _reason)
|
||||
external
|
||||
escrowExists(_escrowId)
|
||||
escrowNotFrozen(_escrowId)
|
||||
escrowNotReleased(_escrowId)
|
||||
escrowNotRefunded(_escrowId)
|
||||
nonReentrant
|
||||
{
|
||||
EscrowAccount storage escrow = escrowAccounts[_escrowId];
|
||||
|
||||
require(
|
||||
msg.sender == escrow.depositor ||
|
||||
msg.sender == escrow.beneficiary ||
|
||||
msg.sender == escrow.arbiter ||
|
||||
msg.sender == owner(),
|
||||
"Not authorized to release"
|
||||
);
|
||||
|
||||
// Check release conditions
|
||||
if (escrow.releaseCondition == ReleaseCondition.Manual) {
|
||||
require(msg.sender == escrow.depositor || msg.sender == escrow.arbiter, "Manual release requires depositor or arbiter");
|
||||
} else if (escrow.releaseCondition == ReleaseCondition.TimeBased) {
|
||||
require(block.timestamp >= escrow.releaseTime, "Release time not reached");
|
||||
} else if (escrow.releaseCondition == ReleaseCondition.OracleVerified) {
|
||||
require(conditionalReleases[_escrowId].conditionMet, "Condition not met");
|
||||
} else if (escrow.releaseCondition == ReleaseCondition.MultiSignature) {
|
||||
require(escrow.currentSignatures >= escrow.requiredSignatures, "Insufficient signatures");
|
||||
}
|
||||
|
||||
_releaseEscrow(_escrowId, _reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Refunds escrow to depositor
|
||||
* @param _escrowId ID of the escrow
|
||||
* @param _reason Reason for refund
|
||||
*/
|
||||
function refundEscrow(uint256 _escrowId, string memory _reason)
|
||||
external
|
||||
escrowExists(_escrowId)
|
||||
escrowNotFrozen(_escrowId)
|
||||
escrowNotReleased(_escrowId)
|
||||
escrowNotRefunded(_escrowId)
|
||||
nonReentrant
|
||||
{
|
||||
EscrowAccount storage escrow = escrowAccounts[_escrowId];
|
||||
|
||||
require(
|
||||
msg.sender == escrow.depositor ||
|
||||
msg.sender == escrow.arbiter ||
|
||||
msg.sender == owner(),
|
||||
"Not authorized to refund"
|
||||
);
|
||||
|
||||
// Check refund conditions
|
||||
if (escrow.releaseCondition == ReleaseCondition.TimeBased) {
|
||||
require(block.timestamp < escrow.releaseTime, "Release time passed, cannot refund");
|
||||
} else if (escrow.releaseCondition == ReleaseCondition.OracleVerified) {
|
||||
require(!conditionalReleases[_escrowId].conditionMet, "Condition met, cannot refund");
|
||||
}
|
||||
|
||||
escrow.isRefunded = true;
|
||||
|
||||
require(
|
||||
aitbcToken.transfer(escrow.depositor, escrow.amount),
|
||||
"Refund transfer failed"
|
||||
);
|
||||
|
||||
emit EscrowRefunded(_escrowId, escrow.depositor, escrow.amount, _reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Requests emergency release
|
||||
* @param _escrowId ID of the escrow
|
||||
* @param _reason Reason for emergency release
|
||||
*/
|
||||
function requestEmergencyRelease(uint256 _escrowId, string memory _reason)
|
||||
external
|
||||
escrowExists(_escrowId)
|
||||
onlyParticipant(_escrowId)
|
||||
escrowNotFrozen(_escrowId)
|
||||
escrowNotReleased(_escrowId)
|
||||
escrowNotRefunded(_escrowId)
|
||||
{
|
||||
EscrowAccount storage escrow = escrowAccounts[_escrowId];
|
||||
|
||||
EmergencyRelease storage emergency = emergencyReleases[_escrowId];
|
||||
require(emergency.requestTime == 0, "Emergency release already requested");
|
||||
|
||||
emergencyReleases[_escrowId] = EmergencyRelease({
|
||||
escrowId: _escrowId,
|
||||
initiator: msg.sender,
|
||||
reason: _reason,
|
||||
requestTime: block.timestamp,
|
||||
votingDeadline: block.timestamp + emergencyReleaseDelay,
|
||||
votesFor: 0,
|
||||
votesAgainst: 0,
|
||||
totalVotes: 0,
|
||||
isApproved: false,
|
||||
isExecuted: false
|
||||
});
|
||||
|
||||
emit EmergencyReleaseRequested(_escrowId, msg.sender, _reason, block.timestamp + emergencyReleaseDelay);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Votes on emergency release
|
||||
* @param _escrowId ID of the escrow
|
||||
* @param _vote True to approve, false to reject
|
||||
*/
|
||||
function voteEmergencyRelease(uint256 _escrowId, bool _vote)
|
||||
external
|
||||
escrowExists(_escrowId)
|
||||
onlyAuthorizedArbiter
|
||||
{
|
||||
EmergencyRelease storage emergency = emergencyReleases[_escrowId];
|
||||
|
||||
require(emergency.requestTime > 0, "No emergency release requested");
|
||||
require(block.timestamp <= emergency.votingDeadline, "Voting deadline passed");
|
||||
require(!emergency.hasVoted[msg.sender], "Already voted");
|
||||
|
||||
emergency.hasVoted[msg.sender] = true;
|
||||
emergency.totalVotes++;
|
||||
|
||||
if (_vote) {
|
||||
emergency.votesFor++;
|
||||
} else {
|
||||
emergency.votesAgainst++;
|
||||
}
|
||||
|
||||
// Check if voting is complete and approved
|
||||
if (emergency.totalVotes >= 3 && emergency.votesFor > emergency.votesAgainst) {
|
||||
emergency.isApproved = true;
|
||||
emit EmergencyReleaseApproved(_escrowId, emergency.votesFor, emergency.votesAgainst, true);
|
||||
_releaseEscrow(_escrowId, "Emergency release approved");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Freezes an escrow account
|
||||
* @param _escrowId ID of the escrow
|
||||
* @param _reason Reason for freezing
|
||||
*/
|
||||
function freezeEscrow(uint256 _escrowId, string memory _reason)
|
||||
external
|
||||
escrowExists(_escrowId)
|
||||
escrowNotFrozen(_escrowId)
|
||||
{
|
||||
require(
|
||||
msg.sender == escrowAccounts[_escrowId].arbiter ||
|
||||
msg.sender == owner(),
|
||||
"Not authorized to freeze"
|
||||
);
|
||||
|
||||
escrowAccounts[_escrowId].isFrozen = true;
|
||||
|
||||
emit EscrowFrozen(_escrowId, msg.sender, _reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Unfreezes an escrow account
|
||||
* @param _escrowId ID of the escrow
|
||||
* @param _reason Reason for unfreezing
|
||||
*/
|
||||
function unfreezeEscrow(uint256 _escrowId, string memory _reason)
|
||||
external
|
||||
escrowExists(_escrowId)
|
||||
{
|
||||
require(
|
||||
msg.sender == escrowAccounts[_escrowId].arbiter ||
|
||||
msg.sender == owner(),
|
||||
"Not authorized to unfreeze"
|
||||
);
|
||||
|
||||
escrowAccounts[_escrowId].isFrozen = false;
|
||||
|
||||
emit EscrowUnfrozen(_escrowId, msg.sender, _reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Authorizes an oracle
|
||||
* @param _oracle Address of the oracle
|
||||
*/
|
||||
function authorizeOracle(address _oracle) external onlyOwner {
|
||||
require(_oracle != address(0), "Invalid oracle address");
|
||||
authorizedOracles[_oracle] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Revokes oracle authorization
|
||||
* @param _oracle Address of the oracle
|
||||
*/
|
||||
function revokeOracle(address _oracle) external onlyOwner {
|
||||
authorizedOracles[_oracle] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Authorizes an arbiter
|
||||
* @param _arbiter Address of the arbiter
|
||||
*/
|
||||
function authorizeArbiter(address _arbiter) external onlyOwner {
|
||||
require(_arbiter != address(0), "Invalid arbiter address");
|
||||
authorizedArbiters[_arbiter] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Revokes arbiter authorization
|
||||
* @param _arbiter Address of the arbiter
|
||||
*/
|
||||
function revokeArbiter(address _arbiter) external onlyOwner {
|
||||
authorizedArbiters[_arbiter] = false;
|
||||
}
|
||||
|
||||
// Internal functions
|
||||
|
||||
function _setupTimeLock(uint256 _escrowId, uint256 _duration) internal {
|
||||
require(_duration >= minTimeLock && _duration <= maxTimeLock, "Invalid duration");
|
||||
|
||||
timeLockReleases[_escrowId] = TimeLockRelease({
|
||||
escrowId: _escrowId,
|
||||
lockStartTime: block.timestamp,
|
||||
lockDuration: _duration,
|
||||
releaseWindow: _duration / 10, // 10% of lock duration as release window
|
||||
canEarlyRelease: false,
|
||||
earlyReleaseFee: 1000, // 10% fee for early release
|
||||
isReleased: false
|
||||
});
|
||||
|
||||
emit TimeLockSet(_escrowId, _duration, _duration / 10, false);
|
||||
}
|
||||
|
||||
function _setupMultiSignature(uint256 _escrowId) internal {
|
||||
EscrowAccount storage escrow = escrowAccounts[_escrowId];
|
||||
|
||||
// Default to requiring depositor + beneficiary signatures
|
||||
address[] memory requiredSigners = new address[](2);
|
||||
requiredSigners[0] = escrow.depositor;
|
||||
requiredSigners[1] = escrow.beneficiary;
|
||||
|
||||
multiSigReleases[_escrowId] = MultiSigRelease({
|
||||
escrowId: _escrowId,
|
||||
requiredSigners: requiredSigners,
|
||||
signaturesRequired: 2,
|
||||
currentSignatures: 0,
|
||||
deadline: block.timestamp + 7 days,
|
||||
isExecuted: false
|
||||
});
|
||||
|
||||
escrow.requiredSignatures = 2;
|
||||
|
||||
emit MultiSignatureRequired(_escrowId, requiredSigners, 2);
|
||||
}
|
||||
|
||||
function _releaseEscrow(uint256 _escrowId, string memory _reason) internal {
|
||||
EscrowAccount storage escrow = escrowAccounts[_escrowId];
|
||||
|
||||
escrow.isReleased = true;
|
||||
escrow.lastReleaseAttempt = block.timestamp;
|
||||
|
||||
// Transfer amount to beneficiary
|
||||
require(
|
||||
aitbcToken.transfer(escrow.beneficiary, escrow.amount),
|
||||
"Escrow release failed"
|
||||
);
|
||||
|
||||
// Transfer platform fee to owner
|
||||
if (escrow.platformFee > 0) {
|
||||
require(
|
||||
aitbcToken.transfer(owner(), escrow.platformFee),
|
||||
"Platform fee transfer failed"
|
||||
);
|
||||
|
||||
emit PlatformFeeCollected(_escrowId, escrow.platformFee, owner());
|
||||
}
|
||||
|
||||
emit EscrowReleased(_escrowId, escrow.beneficiary, escrow.amount, _reason);
|
||||
}
|
||||
|
||||
// View functions
|
||||
|
||||
/**
|
||||
* @dev Gets escrow account details
|
||||
* @param _escrowId ID of the escrow
|
||||
*/
|
||||
function getEscrowAccount(uint256 _escrowId)
|
||||
external
|
||||
view
|
||||
escrowExists(_escrowId)
|
||||
returns (EscrowAccount memory)
|
||||
{
|
||||
return escrowAccounts[_escrowId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets conditional release details
|
||||
* @param _escrowId ID of the escrow
|
||||
*/
|
||||
function getConditionalRelease(uint256 _escrowId)
|
||||
external
|
||||
view
|
||||
returns (ConditionalRelease memory)
|
||||
{
|
||||
return conditionalReleases[_escrowId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets multi-signature release details
|
||||
* @param _escrowId ID of the escrow
|
||||
*/
|
||||
function getMultiSigRelease(uint256 _escrowId)
|
||||
external
|
||||
view
|
||||
returns (MultiSigRelease memory)
|
||||
{
|
||||
return multiSigReleases[_escrowId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets time-lock release details
|
||||
* @param _escrowId ID of the escrow
|
||||
*/
|
||||
function getTimeLockRelease(uint256 _escrowId)
|
||||
external
|
||||
view
|
||||
returns (TimeLockRelease memory)
|
||||
{
|
||||
return timeLockReleases[_escrowId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets emergency release details
|
||||
* @param _escrowId ID of the escrow
|
||||
*/
|
||||
function getEmergencyRelease(uint256 _escrowId)
|
||||
external
|
||||
view
|
||||
returns (EmergencyRelease memory)
|
||||
{
|
||||
return emergencyReleases[_escrowId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets all escrows for a depositor
|
||||
* @param _depositor Address of the depositor
|
||||
*/
|
||||
function getDepositorEscrows(address _depositor)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory)
|
||||
{
|
||||
return depositorEscrows[_depositor];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets all escrows for a beneficiary
|
||||
* @param _beneficiary Address of the beneficiary
|
||||
*/
|
||||
function getBeneficiaryEscrows(address _beneficiary)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory)
|
||||
{
|
||||
return beneficiaryEscrows[_beneficiary];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets active escrows
|
||||
*/
|
||||
function getActiveEscrows()
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory)
|
||||
{
|
||||
uint256[] memory active = new uint256[](activeEscrows.length);
|
||||
uint256 activeCount = 0;
|
||||
|
||||
for (uint256 i = 0; i < activeEscrows.length; i++) {
|
||||
EscrowAccount storage escrow = escrowAccounts[activeEscrows[i]];
|
||||
if (!escrow.isReleased && !escrow.isRefunded) {
|
||||
active[activeCount] = activeEscrows[i];
|
||||
activeCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Resize array to active count
|
||||
assembly {
|
||||
mstore(active, activeCount)
|
||||
}
|
||||
|
||||
return active;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Emergency pause function
|
||||
*/
|
||||
function pause() external onlyOwner {
|
||||
_pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Unpause function
|
||||
*/
|
||||
function unpause() external onlyOwner {
|
||||
_unpause();
|
||||
}
|
||||
}
|
||||
68
contracts/contracts/Groth16Verifier.sol
Normal file
68
contracts/contracts/Groth16Verifier.sol
Normal file
@@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
/**
|
||||
* @title Groth16Verifier
|
||||
* @dev Auto-generated Groth16 proof verifier for the SimpleReceipt circuit.
|
||||
*
|
||||
* To regenerate from the actual circuit:
|
||||
* cd apps/zk-circuits
|
||||
* npx snarkjs groth16 setup receipt_simple.r1cs pot12_final.ptau circuit_0000.zkey
|
||||
* npx snarkjs zkey contribute circuit_0000.zkey circuit_final.zkey --name="AITBC" -v
|
||||
* npx snarkjs zkey export solidityverifier circuit_final.zkey ../../contracts/Groth16Verifier.sol
|
||||
*
|
||||
* This file is a functional stub that matches the interface expected by
|
||||
* ZKReceiptVerifier.sol. Replace with the snarkjs-generated version for production.
|
||||
*/
|
||||
contract Groth16Verifier {
|
||||
|
||||
// Verification key points (placeholder — replace with real VK from snarkjs export)
|
||||
uint256 constant ALPHA_X = 0x0000000000000000000000000000000000000000000000000000000000000001;
|
||||
uint256 constant ALPHA_Y = 0x0000000000000000000000000000000000000000000000000000000000000002;
|
||||
uint256 constant BETA_X1 = 0x0000000000000000000000000000000000000000000000000000000000000001;
|
||||
uint256 constant BETA_X2 = 0x0000000000000000000000000000000000000000000000000000000000000002;
|
||||
uint256 constant BETA_Y1 = 0x0000000000000000000000000000000000000000000000000000000000000003;
|
||||
uint256 constant BETA_Y2 = 0x0000000000000000000000000000000000000000000000000000000000000004;
|
||||
uint256 constant GAMMA_X1 = 0x0000000000000000000000000000000000000000000000000000000000000001;
|
||||
uint256 constant GAMMA_X2 = 0x0000000000000000000000000000000000000000000000000000000000000002;
|
||||
uint256 constant GAMMA_Y1 = 0x0000000000000000000000000000000000000000000000000000000000000003;
|
||||
uint256 constant GAMMA_Y2 = 0x0000000000000000000000000000000000000000000000000000000000000004;
|
||||
uint256 constant DELTA_X1 = 0x0000000000000000000000000000000000000000000000000000000000000001;
|
||||
uint256 constant DELTA_X2 = 0x0000000000000000000000000000000000000000000000000000000000000002;
|
||||
uint256 constant DELTA_Y1 = 0x0000000000000000000000000000000000000000000000000000000000000003;
|
||||
uint256 constant DELTA_Y2 = 0x0000000000000000000000000000000000000000000000000000000000000004;
|
||||
|
||||
// IC points for 1 public signal (SimpleReceipt: receiptHash)
|
||||
uint256 constant IC0_X = 0x0000000000000000000000000000000000000000000000000000000000000001;
|
||||
uint256 constant IC0_Y = 0x0000000000000000000000000000000000000000000000000000000000000002;
|
||||
uint256 constant IC1_X = 0x0000000000000000000000000000000000000000000000000000000000000003;
|
||||
uint256 constant IC1_Y = 0x0000000000000000000000000000000000000000000000000000000000000004;
|
||||
|
||||
/**
|
||||
* @dev Verify a Groth16 proof.
|
||||
* @param a Proof element a (G1 point)
|
||||
* @param b Proof element b (G2 point)
|
||||
* @param c Proof element c (G1 point)
|
||||
* @param input Public signals array (1 element for SimpleReceipt)
|
||||
* @return r Whether the proof is valid
|
||||
*
|
||||
* NOTE: This stub always returns true for development/testing.
|
||||
* Replace with the snarkjs-generated verifier for production use.
|
||||
*/
|
||||
function verifyProof(
|
||||
uint[2] calldata a,
|
||||
uint[2][2] calldata b,
|
||||
uint[2] calldata c,
|
||||
uint[1] calldata input
|
||||
) public view returns (bool r) {
|
||||
// Production: pairing check using bn256 precompiles
|
||||
// ecPairing(a, b, alpha, beta, vk_x, gamma, c, delta)
|
||||
//
|
||||
// Stub: validate proof elements are non-zero
|
||||
if (a[0] == 0 && a[1] == 0) return false;
|
||||
if (c[0] == 0 && c[1] == 0) return false;
|
||||
if (input[0] == 0) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
684
contracts/contracts/PerformanceVerifier.sol
Normal file
684
contracts/contracts/PerformanceVerifier.sol
Normal file
@@ -0,0 +1,684 @@
|
||||
// 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 "./ZKReceiptVerifier.sol";
|
||||
import "./Groth16Verifier.sol";
|
||||
import "./AIPowerRental.sol";
|
||||
|
||||
/**
|
||||
* @title Performance Verifier
|
||||
* @dev Advanced performance verification contract with ZK proofs and oracle integration
|
||||
* @notice Verifies AI service performance metrics and enforces SLA compliance
|
||||
*/
|
||||
contract PerformanceVerifier is Ownable, ReentrancyGuard, Pausable {
|
||||
|
||||
// State variables
|
||||
ZKReceiptVerifier public zkVerifier;
|
||||
Groth16Verifier public groth16Verifier;
|
||||
AIPowerRental public aiPowerRental;
|
||||
|
||||
uint256 public verificationCounter;
|
||||
uint256 public minResponseTime = 100; // 100ms minimum
|
||||
uint256 public maxResponseTime = 5000; // 5 seconds maximum
|
||||
uint256 public minAccuracy = 90; // 90% minimum accuracy
|
||||
uint256 public minAvailability = 95; // 95% minimum availability
|
||||
uint256 public verificationWindow = 3600; // 1 hour verification window
|
||||
uint256 public penaltyPercentage = 500; // 5% penalty in basis points
|
||||
uint256 public rewardPercentage = 200; // 2% reward in basis points
|
||||
|
||||
// Optimistic Rollup / Dispute variables
|
||||
uint256 public disputeWindow = 3600; // 1 hour dispute window before execution is final
|
||||
mapping(uint256 => uint256) public verificationFinalizedAt;
|
||||
|
||||
|
||||
// Structs
|
||||
struct PerformanceMetrics {
|
||||
uint256 verificationId;
|
||||
uint256 agreementId;
|
||||
address agreement.provider;
|
||||
uint256 responseTime;
|
||||
uint256 accuracy;
|
||||
uint256 availability;
|
||||
uint256 computePower;
|
||||
uint256 throughput;
|
||||
uint256 memoryUsage;
|
||||
uint256 energyEfficiency;
|
||||
bool withinSLA;
|
||||
uint256 timestamp;
|
||||
bytes32 zkProof;
|
||||
bytes32 groth16Proof;
|
||||
VerificationStatus status;
|
||||
uint256 penaltyAmount;
|
||||
uint256 rewardAmount;
|
||||
}
|
||||
|
||||
struct SLAParameters {
|
||||
uint256 maxResponseTime;
|
||||
uint256 minAccuracy;
|
||||
uint256 minAvailability;
|
||||
uint256 _newValue;
|
||||
uint256 maxMemoryUsage;
|
||||
uint256 minEnergyEfficiency;
|
||||
bool isActive;
|
||||
uint256 lastUpdated;
|
||||
}
|
||||
|
||||
struct OracleData {
|
||||
address oracleAddress;
|
||||
uint256 lastUpdateTime;
|
||||
bool isAuthorized;
|
||||
uint256 reputationScore;
|
||||
uint256 totalReports;
|
||||
uint256 accurateReports;
|
||||
}
|
||||
|
||||
struct PerformanceHistory {
|
||||
uint256 totalVerifications;
|
||||
uint256 successfulVerifications;
|
||||
uint256 averageResponseTime;
|
||||
uint256 averageAccuracy;
|
||||
uint256 averageAvailability;
|
||||
uint256 lastVerificationTime;
|
||||
uint256 currentStreak;
|
||||
uint256 bestStreak;
|
||||
}
|
||||
|
||||
// Enums
|
||||
enum VerificationStatus {
|
||||
Submitted,
|
||||
Pending,
|
||||
Verified,
|
||||
Rejected,
|
||||
Expired,
|
||||
Disputed
|
||||
}
|
||||
|
||||
enum MetricType {
|
||||
ResponseTime,
|
||||
Accuracy,
|
||||
Availability,
|
||||
ComputePower,
|
||||
Throughput,
|
||||
MemoryUsage,
|
||||
EnergyEfficiency
|
||||
}
|
||||
|
||||
// Mappings
|
||||
mapping(uint256 => PerformanceMetrics) public performanceMetrics;
|
||||
mapping(uint256 => SLAParameters) public slaParameters;
|
||||
mapping(address => OracleData) public oracles;
|
||||
mapping(address => PerformanceHistory) public agreement.providerHistory;
|
||||
mapping(uint256 => uint256[]) public agreementVerifications;
|
||||
mapping(address => uint256[]) public agreement.providerVerifications;
|
||||
mapping(bytes32 => uint256) public proofToVerification;
|
||||
|
||||
// Arrays for authorized oracles
|
||||
address[] public authorizedOracles;
|
||||
|
||||
// Events
|
||||
event PerformanceSubmitted(
|
||||
uint256 indexed verificationId,
|
||||
uint256 indexed agreementId,
|
||||
address indexed agreement.provider,
|
||||
uint256 responseTime,
|
||||
uint256 accuracy,
|
||||
uint256 availability
|
||||
);
|
||||
|
||||
event PerformanceVerified(
|
||||
uint256 indexed verificationId,
|
||||
bool withinSLA,
|
||||
uint256 penaltyAmount,
|
||||
uint256 rewardAmount
|
||||
);
|
||||
|
||||
event PerformanceRejected(
|
||||
uint256 indexed verificationId,
|
||||
string reason,
|
||||
bytes32 invalidProof
|
||||
);
|
||||
|
||||
event SLAParametersUpdated(
|
||||
uint256 indexed agreementId,
|
||||
uint256 maxResponseTime,
|
||||
uint256 minAccuracy,
|
||||
uint256 minAvailability
|
||||
);
|
||||
|
||||
event OracleAuthorized(
|
||||
address indexed oracle,
|
||||
uint256 reputationScore
|
||||
);
|
||||
|
||||
event OracleRevoked(
|
||||
address indexed oracle,
|
||||
string reason
|
||||
);
|
||||
|
||||
event OracleReportSubmitted(
|
||||
address indexed oracle,
|
||||
uint256 indexed verificationId,
|
||||
bool accurate
|
||||
);
|
||||
|
||||
event PenaltyApplied(
|
||||
uint256 indexed agreementId,
|
||||
address indexed agreement.provider,
|
||||
uint256 penaltyAmount
|
||||
);
|
||||
|
||||
event RewardIssued(
|
||||
uint256 indexed agreementId,
|
||||
address indexed agreement.provider,
|
||||
uint256 rewardAmount
|
||||
);
|
||||
|
||||
event VerificationChallenged(uint256 indexed verificationId, address indexed challenger, string challengeData);
|
||||
event PerformanceThresholdUpdated(
|
||||
MetricType indexed metricType,
|
||||
uint256 oldValue,
|
||||
uint256 newValue
|
||||
);
|
||||
|
||||
// Modifiers
|
||||
modifier onlyAuthorizedOracle() {
|
||||
require(oracles[msg.sender].isAuthorized, "Not authorized oracle");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier verificationExists(uint256 _verificationId) {
|
||||
require(_verificationId < verificationCounter, "Verification does not exist");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier validStatus(uint256 _verificationId, VerificationStatus _requiredStatus) {
|
||||
require(performanceMetrics[_verificationId].status == _requiredStatus, "Invalid verification status");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier withinVerificationWindow(uint256 _timestamp) {
|
||||
require(block.timestamp - _timestamp <= verificationWindow, "Verification window expired");
|
||||
_;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
constructor(
|
||||
address _zkVerifier,
|
||||
address _groth16Verifier,
|
||||
address _aiPowerRental
|
||||
) {
|
||||
zkVerifier = ZKReceiptVerifier(_zkVerifier);
|
||||
groth16Verifier = Groth16Verifier(_groth16Verifier);
|
||||
aiPowerRental = AIPowerRental(_aiPowerRental);
|
||||
verificationCounter = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Submits performance metrics for verification
|
||||
* @param _agreementId ID of the rental agreement
|
||||
* @param _responseTime Response time in milliseconds
|
||||
* @param _accuracy Accuracy percentage (0-100)
|
||||
* @param _availability Availability percentage (0-100)
|
||||
* @param _computePower Compute power utilized
|
||||
* @param _throughput Throughput in requests per second
|
||||
* @param _memoryUsage Memory usage in MB
|
||||
* @param _energyEfficiency Energy efficiency score
|
||||
* @param _zkProof Zero-knowledge proof for performance verification
|
||||
* @param _groth16Proof Groth16 proof for additional verification
|
||||
*/
|
||||
function submitPerformance(
|
||||
uint256 _agreementId,
|
||||
uint256 _responseTime,
|
||||
uint256 _accuracy,
|
||||
uint256 _availability,
|
||||
uint256 _computePower,
|
||||
uint256 _throughput,
|
||||
uint256 _memoryUsage,
|
||||
uint256 _energyEfficiency,
|
||||
bytes memory _zkProof,
|
||||
bytes memory _groth16Proof
|
||||
) external nonReentrant whenNotPaused returns (uint256) {
|
||||
require(_responseTime >= minResponseTime && _responseTime <= maxResponseTime, "Invalid response time");
|
||||
require(_accuracy <= 100, "Invalid accuracy");
|
||||
require(_availability <= 100, "Invalid availability");
|
||||
|
||||
// Get agreement details
|
||||
AIPowerRental.RentalAgreement memory agreement = aiPowerRental.getRentalAgreement(_agreementId);
|
||||
require(agreement.provider != address(0), "Invalid agreement");
|
||||
|
||||
uint256 verificationId = verificationCounter++;
|
||||
|
||||
performanceMetrics[verificationId] = PerformanceMetrics({
|
||||
verificationId: verificationId,
|
||||
agreementId: _agreementId,
|
||||
agreement.provider: agreement.provider,
|
||||
responseTime: _responseTime,
|
||||
accuracy: _accuracy,
|
||||
availability: _availability,
|
||||
computePower: _computePower,
|
||||
throughput: _throughput,
|
||||
memoryUsage: _memoryUsage,
|
||||
energyEfficiency: _energyEfficiency,
|
||||
withinSLA: false,
|
||||
timestamp: block.timestamp,
|
||||
zkProof: keccak256(_zkProof),
|
||||
groth16Proof: keccak256(_groth16Proof),
|
||||
status: VerificationStatus.Submitted,
|
||||
penaltyAmount: 0,
|
||||
rewardAmount: 0
|
||||
});
|
||||
|
||||
agreementVerifications[_agreementId].push(verificationId);
|
||||
agreement.providerVerifications[agreement.provider].push(verificationId);
|
||||
proofToVerification[keccak256(_zkProof)] = verificationId;
|
||||
|
||||
emit PerformanceSubmitted(
|
||||
verificationId,
|
||||
_agreementId,
|
||||
agreement.provider,
|
||||
_responseTime,
|
||||
_accuracy,
|
||||
_availability
|
||||
);
|
||||
|
||||
// Auto-verify if proofs are valid
|
||||
if (_verifyProofs(_zkProof, _groth16Proof, verificationId)) {
|
||||
_verifyPerformance(verificationId);
|
||||
} else {
|
||||
performanceMetrics[verificationId].status = VerificationStatus.Pending;
|
||||
}
|
||||
|
||||
return verificationId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Verifies performance metrics (oracle verification)
|
||||
* @param _verificationId ID of the verification
|
||||
* @param _accurate Whether the metrics are accurate
|
||||
* @param _additionalData Additional verification data
|
||||
*/
|
||||
function verifyPerformance(
|
||||
uint256 _verificationId,
|
||||
bool _accurate,
|
||||
string memory _additionalData
|
||||
) external onlyAuthorizedOracle verificationExists(_verificationId) validStatus(_verificationId, VerificationStatus.Pending) {
|
||||
PerformanceMetrics storage metrics = performanceMetrics[_verificationId];
|
||||
|
||||
require(block.timestamp - metrics.timestamp <= verificationWindow, "Verification window expired");
|
||||
|
||||
// Update oracle statistics
|
||||
OracleData storage oracle = oracles[msg.sender];
|
||||
oracle.totalReports++;
|
||||
if (_accurate) {
|
||||
oracle.accurateReports++;
|
||||
}
|
||||
oracle.lastUpdateTime = block.timestamp;
|
||||
|
||||
if (_accurate) {
|
||||
_verifyPerformance(_verificationId);
|
||||
} else {
|
||||
metrics.status = VerificationStatus.Rejected;
|
||||
emit PerformanceRejected(_verificationId, _additionalData, metrics.zkProof);
|
||||
}
|
||||
|
||||
emit OracleReportSubmitted(msg.sender, _verificationId, _accurate);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Sets SLA parameters for an agreement
|
||||
* @param _agreementId ID of the agreement
|
||||
* @param _maxResponseTime Maximum allowed response time
|
||||
* @param _minAccuracy Minimum required accuracy
|
||||
* @param _minAvailability Minimum required availability
|
||||
* @param __newValue Minimum required compute power
|
||||
* @param _maxMemoryUsage Maximum allowed memory usage
|
||||
* @param _minEnergyEfficiency Minimum energy efficiency
|
||||
*/
|
||||
function setSLAParameters(
|
||||
uint256 _agreementId,
|
||||
uint256 _maxResponseTime,
|
||||
uint256 _minAccuracy,
|
||||
uint256 _minAvailability,
|
||||
uint256 __newValue,
|
||||
uint256 _maxMemoryUsage,
|
||||
uint256 _minEnergyEfficiency
|
||||
) external onlyOwner {
|
||||
slaParameters[_agreementId] = SLAParameters({
|
||||
maxResponseTime: _maxResponseTime,
|
||||
minAccuracy: _minAccuracy,
|
||||
minAvailability: _minAvailability,
|
||||
_newValue: __newValue,
|
||||
maxMemoryUsage: _maxMemoryUsage,
|
||||
minEnergyEfficiency: _minEnergyEfficiency,
|
||||
isActive: true,
|
||||
lastUpdated: block.timestamp
|
||||
});
|
||||
|
||||
emit SLAParametersUpdated(
|
||||
_agreementId,
|
||||
_maxResponseTime,
|
||||
_minAccuracy,
|
||||
_minAvailability
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Authorizes an oracle
|
||||
* @param _oracle Address of the oracle
|
||||
* @param _reputationScore Initial reputation score
|
||||
*/
|
||||
function authorizeOracle(address _oracle, uint256 _reputationScore) external onlyOwner {
|
||||
require(_oracle != address(0), "Invalid oracle address");
|
||||
require(!oracles[_oracle].isAuthorized, "Oracle already authorized");
|
||||
|
||||
oracles[_oracle] = OracleData({
|
||||
oracleAddress: _oracle,
|
||||
lastUpdateTime: block.timestamp,
|
||||
isAuthorized: true,
|
||||
reputationScore: _reputationScore,
|
||||
totalReports: 0,
|
||||
accurateReports: 0
|
||||
});
|
||||
|
||||
authorizedOracles.push(_oracle);
|
||||
|
||||
emit OracleAuthorized(_oracle, _reputationScore);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Revokes oracle authorization
|
||||
* @param _oracle Address of the oracle
|
||||
* @param _reason Reason for revocation
|
||||
*/
|
||||
function revokeOracle(address _oracle, string memory _reason) external onlyOwner {
|
||||
require(oracles[_oracle].isAuthorized, "Oracle not authorized");
|
||||
|
||||
oracles[_oracle].isAuthorized = false;
|
||||
|
||||
emit OracleRevoked(_oracle, _reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Updates performance thresholds
|
||||
* @param _metricType Type of metric
|
||||
* @param _newValue New threshold value
|
||||
*/
|
||||
function updatePerformanceThreshold(MetricType _metricType, uint256 _newValue) external onlyOwner {
|
||||
uint256 oldValue;
|
||||
|
||||
if (_metricType == MetricType.ResponseTime) {
|
||||
oldValue = maxResponseTime;
|
||||
maxResponseTime = _newValue;
|
||||
} else if (_metricType == MetricType.Accuracy) {
|
||||
oldValue = minAccuracy;
|
||||
minAccuracy = _newValue;
|
||||
} else if (_metricType == MetricType.Availability) {
|
||||
oldValue = minAvailability;
|
||||
minAvailability = _newValue;
|
||||
} else if (_metricType == MetricType.ComputePower) {
|
||||
oldValue = _newValue;
|
||||
_newValue = _newValue;
|
||||
} else {
|
||||
revert("Invalid metric type");
|
||||
}
|
||||
|
||||
emit PerformanceThresholdUpdated(_metricType, oldValue, _newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Calculates penalty for SLA violation
|
||||
* @param _verificationId ID of the verification
|
||||
*/
|
||||
function calculatePenalty(uint256 _verificationId)
|
||||
external
|
||||
view
|
||||
verificationExists(_verificationId)
|
||||
returns (uint256)
|
||||
{
|
||||
PerformanceMetrics memory metrics = performanceMetrics[_verificationId];
|
||||
|
||||
if (metrics.withinSLA) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get agreement details to calculate penalty amount
|
||||
AIPowerRental.RentalAgreement memory agreement = aiPowerRental.getRentalAgreement(metrics.agreementId);
|
||||
|
||||
// Penalty based on severity of violation
|
||||
uint256 penaltyAmount = (agreement.price * penaltyPercentage) / 10000;
|
||||
|
||||
// Additional penalties for severe violations
|
||||
if (metrics.responseTime > maxResponseTime * 2) {
|
||||
penaltyAmount += (agreement.price * 1000) / 10000; // Additional 10%
|
||||
}
|
||||
|
||||
if (metrics.accuracy < minAccuracy - 10) {
|
||||
penaltyAmount += (agreement.price * 1000) / 10000; // Additional 10%
|
||||
}
|
||||
|
||||
return penaltyAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Calculates reward for exceeding SLA
|
||||
* @param _verificationId ID of the verification
|
||||
*/
|
||||
function calculateReward(uint256 _verificationId)
|
||||
external
|
||||
view
|
||||
verificationExists(_verificationId)
|
||||
returns (uint256)
|
||||
{
|
||||
PerformanceMetrics memory metrics = performanceMetrics[_verificationId];
|
||||
|
||||
if (!metrics.withinSLA) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get agreement details
|
||||
AIPowerRental.RentalAgreement memory agreement = aiPowerRental.getRentalAgreement(metrics.agreementId);
|
||||
|
||||
// Reward based on performance quality
|
||||
uint256 rewardAmount = (agreement.price * rewardPercentage) / 10000;
|
||||
|
||||
// Additional rewards for exceptional performance
|
||||
if (metrics.responseTime < maxResponseTime / 2) {
|
||||
rewardAmount += (agreement.price * 500) / 10000; // Additional 5%
|
||||
}
|
||||
|
||||
if (metrics.accuracy > minAccuracy + 5) {
|
||||
rewardAmount += (agreement.price * 500) / 10000; // Additional 5%
|
||||
}
|
||||
|
||||
return rewardAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets performance history for a agreement.provider
|
||||
* @param _agreement.provider Address of the agreement.provider
|
||||
*/
|
||||
function getProviderHistory(address _agreement.provider)
|
||||
external
|
||||
view
|
||||
returns (PerformanceHistory memory)
|
||||
{
|
||||
return agreement.providerHistory[_agreement.provider];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets all verifications for an agreement
|
||||
* @param _agreementId ID of the agreement
|
||||
*/
|
||||
function getAgreementVerifications(uint256 _agreementId)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory)
|
||||
{
|
||||
return agreementVerifications[_agreementId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets all verifications for a agreement.provider
|
||||
* @param _agreement.provider Address of the agreement.provider
|
||||
*/
|
||||
function getProviderVerifications(address _agreement.provider)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory)
|
||||
{
|
||||
return agreement.providerVerifications[_agreement.provider];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets oracle information
|
||||
* @param _oracle Address of the oracle
|
||||
*/
|
||||
function getOracleInfo(address _oracle)
|
||||
external
|
||||
view
|
||||
returns (OracleData memory)
|
||||
{
|
||||
return oracles[_oracle];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets all authorized oracles
|
||||
*/
|
||||
function getAuthorizedOracles()
|
||||
external
|
||||
view
|
||||
returns (address[] memory)
|
||||
{
|
||||
address[] memory activeOracles = new address[](authorizedOracles.length);
|
||||
uint256 activeCount = 0;
|
||||
|
||||
for (uint256 i = 0; i < authorizedOracles.length; i++) {
|
||||
if (oracles[authorizedOracles[i]].isAuthorized) {
|
||||
activeOracles[activeCount] = authorizedOracles[i];
|
||||
activeCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Resize array to active count
|
||||
assembly {
|
||||
mstore(activeOracles, activeCount)
|
||||
}
|
||||
|
||||
return activeOracles;
|
||||
}
|
||||
|
||||
// Internal functions
|
||||
|
||||
function _verifyProofs(
|
||||
bytes memory _zkProof,
|
||||
bytes memory _groth16Proof,
|
||||
uint256 _verificationId
|
||||
) internal view returns (bool) {
|
||||
PerformanceMetrics memory metrics = performanceMetrics[_verificationId];
|
||||
|
||||
// Verify ZK proof
|
||||
bool zkValid = zkVerifier.verifyPerformanceProof(
|
||||
metrics.agreementId,
|
||||
metrics.responseTime,
|
||||
metrics.accuracy,
|
||||
metrics.availability,
|
||||
metrics.computePower,
|
||||
_zkProof
|
||||
);
|
||||
|
||||
// Verify Groth16 proof
|
||||
bool groth16Valid = true; // Placeholder for Groth16 verification
|
||||
|
||||
return zkValid && groth16Valid;
|
||||
}
|
||||
|
||||
function _verifyPerformance(uint256 _verificationId) internal {
|
||||
PerformanceMetrics storage metrics = performanceMetrics[_verificationId];
|
||||
|
||||
// Setup optimistic rollup finalization time
|
||||
verificationFinalizedAt[_verificationId] = block.timestamp + disputeWindow;
|
||||
metrics.status = VerificationStatus.Verified;
|
||||
|
||||
emit PerformanceVerified(_verificationId, metrics.score, metrics.zkProof);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Finalizes an optimistic verification after the dispute window has passed
|
||||
* @param _verificationId ID of the verification
|
||||
*/
|
||||
function finalizeOptimisticVerification(uint256 _verificationId) external verificationExists(_verificationId) {
|
||||
PerformanceMetrics storage metrics = performanceMetrics[_verificationId];
|
||||
require(metrics.status == VerificationStatus.Verified, "Verification not in verified status");
|
||||
require(block.timestamp >= verificationFinalizedAt[_verificationId], "Dispute window still open");
|
||||
|
||||
metrics.status = VerificationStatus.Completed;
|
||||
|
||||
// Execute SLA logic (distribute rewards/penalties)
|
||||
if (metrics.score >= minAccuracy) {
|
||||
_rewardProvider(agreement.agreement.provider, metrics.agreementId);
|
||||
} else {
|
||||
_penalizeProvider(agreement.agreement.provider, metrics.agreementId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Challenge an optimistic verification within the dispute window
|
||||
* @param _verificationId ID of the verification
|
||||
* @param _challengeData Evidence of invalid performance
|
||||
*/
|
||||
function challengeVerification(uint256 _verificationId, string memory _challengeData) external verificationExists(_verificationId) {
|
||||
PerformanceMetrics storage metrics = performanceMetrics[_verificationId];
|
||||
require(metrics.status == VerificationStatus.Verified, "Verification not in verified status");
|
||||
require(block.timestamp < verificationFinalizedAt[_verificationId], "Dispute window closed");
|
||||
|
||||
// A watcher node challenges the verification
|
||||
// Switch to manual review or on-chain full ZK validation
|
||||
metrics.status = VerificationStatus.Challenged;
|
||||
emit VerificationChallenged(_verificationId, msg.sender, _challengeData);
|
||||
}
|
||||
|
||||
function _updateProviderHistory(address _agreement.provider, bool _withinSLA) internal {
|
||||
PerformanceHistory storage history = agreement.providerHistory[_agreement.provider];
|
||||
|
||||
history.totalVerifications++;
|
||||
if (_withinSLA) {
|
||||
history.successfulVerifications++;
|
||||
history.currentStreak++;
|
||||
if (history.currentStreak > history.bestStreak) {
|
||||
history.bestStreak = history.currentStreak;
|
||||
}
|
||||
} else {
|
||||
history.currentStreak = 0;
|
||||
}
|
||||
|
||||
history.lastVerificationTime = block.timestamp;
|
||||
|
||||
// Update averages (simplified calculation)
|
||||
// In a real implementation, you'd want to maintain running averages
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Emergency pause function
|
||||
*/
|
||||
function pause() external onlyOwner {
|
||||
_pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Unpause function
|
||||
*/
|
||||
function _rewardProvider(address _agreement.provider, uint256 _agreementId) internal {
|
||||
emit RewardIssued(_agreementId, _agreement.provider, 0);
|
||||
}
|
||||
|
||||
function _penalizeProvider(address _agreement.provider, uint256 _agreementId) internal {
|
||||
emit PenaltyApplied(_agreementId, _agreement.provider, 0);
|
||||
}
|
||||
|
||||
function unpause() external onlyOwner {
|
||||
_unpause();
|
||||
}
|
||||
}
|
||||
259
contracts/contracts/ZKReceiptVerifier.sol
Normal file
259
contracts/contracts/ZKReceiptVerifier.sol
Normal file
@@ -0,0 +1,259 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
// Note: Groth16Verifier is generated by snarkjs from the circuit's verification key
|
||||
// Run: snarkjs zkey export solidityverifier circuit_final.zkey Groth16Verifier.sol
|
||||
import "./Groth16Verifier.sol";
|
||||
|
||||
/**
|
||||
* @title ZKReceiptVerifier
|
||||
* @dev Contract for verifying zero-knowledge proofs for receipt attestation
|
||||
*/
|
||||
contract ZKReceiptVerifier is Groth16Verifier {
|
||||
|
||||
// Events
|
||||
event ProofVerified(
|
||||
bytes32 indexed receiptHash,
|
||||
uint256 settlementAmount,
|
||||
uint256 timestamp,
|
||||
address indexed verifier
|
||||
);
|
||||
|
||||
event ProofVerificationFailed(
|
||||
bytes32 indexed receiptHash,
|
||||
string reason
|
||||
);
|
||||
|
||||
// Mapping to prevent double-spending
|
||||
mapping(bytes32 => bool) public verifiedReceipts;
|
||||
|
||||
// Mapping for authorized verifiers
|
||||
mapping(address => bool) public authorizedVerifiers;
|
||||
|
||||
// Address of the settlement contract
|
||||
address public settlementContract;
|
||||
|
||||
// Circuit version
|
||||
uint256 public constant CIRCUIT_VERSION = 1;
|
||||
|
||||
// Minimum settlement amount
|
||||
uint256 public constant MIN_SETTLEMENT_AMOUNT = 0;
|
||||
|
||||
// Maximum timestamp drift (in seconds)
|
||||
uint256 public constant MAX_TIMESTAMP_DRIFT = 3600; // 1 hour
|
||||
|
||||
modifier onlyAuthorized() {
|
||||
require(
|
||||
authorizedVerifiers[msg.sender] ||
|
||||
msg.sender == settlementContract,
|
||||
"ZKReceiptVerifier: Unauthorized"
|
||||
);
|
||||
_;
|
||||
}
|
||||
|
||||
modifier onlySettlementContract() {
|
||||
require(
|
||||
msg.sender == settlementContract,
|
||||
"ZKReceiptVerifier: Only settlement contract"
|
||||
);
|
||||
_;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
// Deployer is initially authorized
|
||||
authorizedVerifiers[msg.sender] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Verify a ZK proof for receipt attestation
|
||||
* @param a Proof parameter a (G1 point)
|
||||
* @param b Proof parameter b (G2 point)
|
||||
* @param c Proof parameter c (G1 point)
|
||||
* @param publicSignals Public signals [receiptHash] - matches receipt_simple circuit
|
||||
* @return valid Whether the proof is valid
|
||||
*/
|
||||
function verifyReceiptProof(
|
||||
uint[2] calldata a,
|
||||
uint[2][2] calldata b,
|
||||
uint[2] calldata c,
|
||||
uint[1] calldata publicSignals
|
||||
) external view returns (bool valid) {
|
||||
// Extract public signal - receiptHash only for SimpleReceipt circuit
|
||||
bytes32 receiptHash = bytes32(publicSignals[0]);
|
||||
|
||||
// Validate receipt hash is not zero
|
||||
if (receiptHash == bytes32(0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify the proof using Groth16 (inherited from Groth16Verifier)
|
||||
return _verifyProof(a, b, c, publicSignals);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Verify and record a proof for settlement
|
||||
* @param a Proof parameter a (G1 point)
|
||||
* @param b Proof parameter b (G2 point)
|
||||
* @param c Proof parameter c (G1 point)
|
||||
* @param publicSignals Public signals [receiptHash]
|
||||
* @param settlementAmount Amount to settle (passed separately, not in proof)
|
||||
* @return success Whether verification succeeded
|
||||
*/
|
||||
function verifyAndRecord(
|
||||
uint[2] calldata a,
|
||||
uint[2][2] calldata b,
|
||||
uint[2] calldata c,
|
||||
uint[1] calldata publicSignals,
|
||||
uint256 settlementAmount
|
||||
) external onlyAuthorized returns (bool success) {
|
||||
// Extract public signal
|
||||
bytes32 receiptHash = bytes32(publicSignals[0]);
|
||||
|
||||
// Check if receipt already verified (prevent double-spend)
|
||||
if (verifiedReceipts[receiptHash]) {
|
||||
emit ProofVerificationFailed(receiptHash, "Receipt already verified");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate receipt hash
|
||||
if (receiptHash == bytes32(0)) {
|
||||
emit ProofVerificationFailed(receiptHash, "Invalid receipt hash");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify the proof
|
||||
bool valid = _verifyProof(a, b, c, publicSignals);
|
||||
|
||||
if (valid) {
|
||||
// Mark as verified
|
||||
verifiedReceipts[receiptHash] = true;
|
||||
|
||||
// Emit event with settlement amount
|
||||
emit ProofVerified(receiptHash, settlementAmount, block.timestamp, msg.sender);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
emit ProofVerificationFailed(receiptHash, "Invalid proof");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Internal proof verification - calls inherited Groth16 verifier
|
||||
* @param a Proof parameter a
|
||||
* @param b Proof parameter b
|
||||
* @param c Proof parameter c
|
||||
* @param publicSignals Public signals array
|
||||
* @return valid Whether the proof is valid
|
||||
*/
|
||||
function _verifyProof(
|
||||
uint[2] calldata a,
|
||||
uint[2][2] calldata b,
|
||||
uint[2] calldata c,
|
||||
uint[1] calldata publicSignals
|
||||
) internal view returns (bool valid) {
|
||||
// Convert to format expected by Groth16Verifier
|
||||
// The Groth16Verifier.verifyProof is generated by snarkjs
|
||||
return Groth16Verifier.verifyProof(a, b, c, publicSignals);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Set the settlement contract address
|
||||
* @param _settlementContract Address of the settlement contract
|
||||
*/
|
||||
function setSettlementContract(address _settlementContract) external {
|
||||
require(authorizedVerifiers[msg.sender], "ZKReceiptVerifier: Unauthorized");
|
||||
settlementContract = _settlementContract;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Add an authorized verifier
|
||||
* @param verifier Address to authorize
|
||||
*/
|
||||
function addAuthorizedVerifier(address verifier) external {
|
||||
require(authorizedVerifiers[msg.sender], "ZKReceiptVerifier: Unauthorized");
|
||||
authorizedVerifiers[verifier] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Remove an authorized verifier
|
||||
* @param verifier Address to remove
|
||||
*/
|
||||
function removeAuthorizedVerifier(address verifier) external {
|
||||
require(authorizedVerifiers[msg.sender], "ZKReceiptVerifier: Unauthorized");
|
||||
authorizedVerifiers[verifier] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Check if a receipt has been verified
|
||||
* @param receiptHash Hash of the receipt
|
||||
* @return verified Whether the receipt has been verified
|
||||
*/
|
||||
function isReceiptVerified(bytes32 receiptHash) external view returns (bool verified) {
|
||||
return verifiedReceipts[receiptHash];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Batch verify multiple proofs
|
||||
* @param proofs Array of proof data
|
||||
* @return results Array of verification results
|
||||
*/
|
||||
function batchVerify(
|
||||
BatchProof[] calldata proofs
|
||||
) external view returns (bool[] memory results) {
|
||||
results = new bool[](proofs.length);
|
||||
|
||||
for (uint256 i = 0; i < proofs.length; i++) {
|
||||
results[i] = _verifyProof(
|
||||
proofs[i].a,
|
||||
proofs[i].b,
|
||||
proofs[i].c,
|
||||
proofs[i].publicSignals
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Struct for batch verification
|
||||
struct BatchProof {
|
||||
uint[2] a;
|
||||
uint[2][2] b;
|
||||
uint[2] c;
|
||||
uint[1] publicSignals; // Matches SimpleReceipt circuit
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Verify a performance proof
|
||||
* @return valid Whether the proof is valid
|
||||
*/
|
||||
function verifyPerformanceProof(
|
||||
uint256 agreementId,
|
||||
uint256 responseTime,
|
||||
uint256 accuracy,
|
||||
uint256 availability,
|
||||
uint256 computePower,
|
||||
bytes memory zkProof
|
||||
) external pure returns (bool valid) {
|
||||
// Implementation for performance proof verification
|
||||
// This is a placeholder since the actual implementation would need
|
||||
// to parse the zkProof bytes and call the appropriate Circom verifier
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Verify a performance proof
|
||||
* @return valid Whether the proof is valid
|
||||
*/
|
||||
function verifyPerformanceProof(
|
||||
uint256 agreementId,
|
||||
uint256 responseTime,
|
||||
uint256 accuracy,
|
||||
uint256 availability,
|
||||
uint256 computePower,
|
||||
bytes memory zkProof
|
||||
) external pure returns (bool valid) {
|
||||
// Implementation for performance proof verification
|
||||
// This is a placeholder since the actual implementation would need
|
||||
// to parse the zkProof bytes and call the appropriate Circom verifier
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user