- Change SQLite database path from `/home/oib/windsurf/aitbc/data/` to `/opt/data/` - Fix foreign key references to use correct table names (users, wallets, gpu_registry) - Replace governance router with new governance and community routers - Add multi-modal RL router to main application - Simplify DEPLOYMENT_READINESS_REPORT.md to focus on production deployment status - Update governance router with decentralized DAO voting
676 lines
22 KiB
Solidity
676 lines
22 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.19;
|
|
|
|
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
|
import "@openzeppelin/contracts/security/Pausable.sol";
|
|
import "./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 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 minComputePower;
|
|
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 providerHistory;
|
|
mapping(uint256 => uint256[]) public agreementVerifications;
|
|
mapping(address => uint256[]) public providerVerifications;
|
|
mapping(bytes32 => uint256) public proofToVerification;
|
|
|
|
// Arrays for authorized oracles
|
|
address[] public authorizedOracles;
|
|
|
|
// Events
|
|
event PerformanceSubmitted(
|
|
uint256 indexed verificationId,
|
|
uint256 indexed agreementId,
|
|
address indexed 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 provider,
|
|
uint256 penaltyAmount
|
|
);
|
|
|
|
event RewardIssued(
|
|
uint256 indexed agreementId,
|
|
address indexed provider,
|
|
uint256 rewardAmount
|
|
);
|
|
|
|
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
|
|
(, address provider, , , , , , , , ) = aiPowerRental.getRentalAgreement(_agreementId);
|
|
require(provider != address(0), "Invalid agreement");
|
|
|
|
uint256 verificationId = verificationCounter++;
|
|
|
|
performanceMetrics[verificationId] = PerformanceMetrics({
|
|
verificationId: verificationId,
|
|
agreementId: _agreementId,
|
|
provider: 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);
|
|
providerVerifications[provider].push(verificationId);
|
|
proofToVerification[keccak256(_zkProof)] = verificationId;
|
|
|
|
emit PerformanceSubmitted(
|
|
verificationId,
|
|
_agreementId,
|
|
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 _minComputePower 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 _minComputePower,
|
|
uint256 _maxMemoryUsage,
|
|
uint256 _minEnergyEfficiency
|
|
) external onlyOwner {
|
|
slaParameters[_agreementId] = SLAParameters({
|
|
maxResponseTime: _maxResponseTime,
|
|
minAccuracy: _minAccuracy,
|
|
minAvailability: _minAvailability,
|
|
minComputePower: _minComputePower,
|
|
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 = minComputePower;
|
|
minComputePower = _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
|
|
(, address provider, , uint256 duration, uint256 price, , , , , ) = aiPowerRental.getRentalAgreement(metrics.agreementId);
|
|
|
|
// Penalty based on severity of violation
|
|
uint256 penaltyAmount = (price * penaltyPercentage) / 10000;
|
|
|
|
// Additional penalties for severe violations
|
|
if (metrics.responseTime > maxResponseTime * 2) {
|
|
penaltyAmount += (price * 1000) / 10000; // Additional 10%
|
|
}
|
|
|
|
if (metrics.accuracy < minAccuracy - 10) {
|
|
penaltyAmount += (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
|
|
(, address provider, , uint256 duration, uint256 price, , , , , ) = aiPowerRental.getRentalAgreement(metrics.agreementId);
|
|
|
|
// Reward based on performance quality
|
|
uint256 rewardAmount = (price * rewardPercentage) / 10000;
|
|
|
|
// Additional rewards for exceptional performance
|
|
if (metrics.responseTime < maxResponseTime / 2) {
|
|
rewardAmount += (price * 500) / 10000; // Additional 5%
|
|
}
|
|
|
|
if (metrics.accuracy > minAccuracy + 5) {
|
|
rewardAmount += (price * 500) / 10000; // Additional 5%
|
|
}
|
|
|
|
return rewardAmount;
|
|
}
|
|
|
|
/**
|
|
* @dev Gets performance history for a provider
|
|
* @param _provider Address of the provider
|
|
*/
|
|
function getProviderHistory(address _provider)
|
|
external
|
|
view
|
|
returns (PerformanceHistory memory)
|
|
{
|
|
return providerHistory[_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 provider
|
|
* @param _provider Address of the provider
|
|
*/
|
|
function getProviderVerifications(address _provider)
|
|
external
|
|
view
|
|
returns (uint256[] memory)
|
|
{
|
|
return providerVerifications[_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 = groth16Verifier.verifyProof(_groth16Proof);
|
|
|
|
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(metrics.provider, metrics.agreementId);
|
|
} else {
|
|
_penalizeProvider(metrics.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 _provider, bool _withinSLA) internal {
|
|
PerformanceHistory storage history = providerHistory[_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 unpause() external onlyOwner {
|
|
_unpause();
|
|
}
|
|
}
|