Update OpenZeppelin contracts submodule to a dirty state
Some checks failed
Verify Deployment / Verify Deployment (push) Has been cancelled
CI/CD Pipeline / Solidity Contracts (push) Has been cancelled
CI/CD Pipeline / Security Scanning (push) Has been cancelled
CI/CD Pipeline / Lint and Format (push) Has been cancelled
CI/CD Pipeline / Terraform Validation (push) Has been cancelled
CI/CD Pipeline / Kubernetes Validation (push) Has been cancelled
Validation / validate-genesis (push) Has been cancelled
Validation / validate-terraform (push) Has been cancelled
Validation / validate-kubernetes (push) Has been cancelled
Validation / validate-smart-contracts (push) Has been cancelled
Validation / validate-security (push) Has been cancelled
Validation / validate-documentation (push) Has been cancelled
Some checks failed
Verify Deployment / Verify Deployment (push) Has been cancelled
CI/CD Pipeline / Solidity Contracts (push) Has been cancelled
CI/CD Pipeline / Security Scanning (push) Has been cancelled
CI/CD Pipeline / Lint and Format (push) Has been cancelled
CI/CD Pipeline / Terraform Validation (push) Has been cancelled
CI/CD Pipeline / Kubernetes Validation (push) Has been cancelled
Validation / validate-genesis (push) Has been cancelled
Validation / validate-terraform (push) Has been cancelled
Validation / validate-kubernetes (push) Has been cancelled
Validation / validate-smart-contracts (push) Has been cancelled
Validation / validate-security (push) Has been cancelled
Validation / validate-documentation (push) Has been cancelled
This commit is contained in:
211
scripts/reserve/keeper-service.js
Executable file
211
scripts/reserve/keeper-service.js
Executable file
@@ -0,0 +1,211 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Price Feed Keeper Service
|
||||
* Automatically updates price feeds at regular intervals
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/reserve/keeper-service.js
|
||||
*
|
||||
* Environment Variables:
|
||||
* RPC_URL_138 - ChainID 138 RPC endpoint
|
||||
* KEEPER_PRIVATE_KEY - Keeper wallet private key
|
||||
* PRICE_FEED_KEEPER_ADDRESS - PriceFeedKeeper contract address
|
||||
* UPDATE_INTERVAL - Update interval in seconds (default: 30)
|
||||
*/
|
||||
|
||||
const { ethers } = require('ethers');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Load environment variables
|
||||
require('dotenv').config();
|
||||
|
||||
// Configuration
|
||||
const CONFIG = {
|
||||
rpcUrl: process.env.RPC_URL_138 || 'http://localhost:8545',
|
||||
keeperPrivateKey: process.env.KEEPER_PRIVATE_KEY,
|
||||
keeperAddress: process.env.PRICE_FEED_KEEPER_ADDRESS,
|
||||
updateInterval: parseInt(process.env.UPDATE_INTERVAL || '30') * 1000, // Convert to milliseconds
|
||||
maxRetries: 3,
|
||||
retryDelay: 5000, // 5 seconds
|
||||
};
|
||||
|
||||
// ABI for PriceFeedKeeper
|
||||
const KEEPER_ABI = [
|
||||
"function checkUpkeep() external view returns (bool needsUpdate, address[] memory assets)",
|
||||
"function performUpkeep() external returns (bool success, address[] memory updatedAssets)",
|
||||
"function updateAssets(address[] calldata assets) external",
|
||||
"function getTrackedAssets() external view returns (address[] memory)",
|
||||
"function needsUpdate(address asset) external view returns (bool)",
|
||||
"function updateInterval() external view returns (uint256)",
|
||||
"function maxUpdatesPerCall() external view returns (uint256)",
|
||||
"event PriceFeedsUpdated(address[] assets, uint256 timestamp)"
|
||||
];
|
||||
|
||||
class PriceFeedKeeperService {
|
||||
constructor() {
|
||||
if (!CONFIG.keeperPrivateKey) {
|
||||
throw new Error('KEEPER_PRIVATE_KEY environment variable is required');
|
||||
}
|
||||
if (!CONFIG.keeperAddress) {
|
||||
throw new Error('PRICE_FEED_KEEPER_ADDRESS environment variable is required');
|
||||
}
|
||||
|
||||
this.provider = new ethers.JsonRpcProvider(CONFIG.rpcUrl);
|
||||
this.wallet = new ethers.Wallet(CONFIG.keeperPrivateKey, this.provider);
|
||||
this.keeper = new ethers.Contract(CONFIG.keeperAddress, KEEPER_ABI, this.wallet);
|
||||
this.isRunning = false;
|
||||
this.updateCount = 0;
|
||||
this.errorCount = 0;
|
||||
}
|
||||
|
||||
async start() {
|
||||
console.log('=== Price Feed Keeper Service ===');
|
||||
console.log(`RPC URL: ${CONFIG.rpcUrl}`);
|
||||
console.log(`Keeper Address: ${CONFIG.keeperAddress}`);
|
||||
console.log(`Wallet Address: ${this.wallet.address}`);
|
||||
console.log(`Update Interval: ${CONFIG.updateInterval / 1000} seconds`);
|
||||
console.log('');
|
||||
|
||||
// Verify keeper contract
|
||||
try {
|
||||
const trackedAssets = await this.keeper.getTrackedAssets();
|
||||
const updateInterval = await this.keeper.updateInterval();
|
||||
const maxUpdates = await this.keeper.maxUpdatesPerCall();
|
||||
|
||||
console.log(`Tracked Assets: ${trackedAssets.length}`);
|
||||
trackedAssets.forEach((asset, i) => {
|
||||
console.log(` ${i + 1}. ${asset}`);
|
||||
});
|
||||
console.log(`Update Interval: ${updateInterval.toString()} seconds`);
|
||||
console.log(`Max Updates Per Call: ${maxUpdates.toString()}`);
|
||||
console.log('');
|
||||
} catch (error) {
|
||||
console.error('Error verifying keeper contract:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
this.isRunning = true;
|
||||
console.log('Keeper service started. Press Ctrl+C to stop.');
|
||||
console.log('');
|
||||
|
||||
// Start update loop
|
||||
this.updateLoop();
|
||||
}
|
||||
|
||||
async updateLoop() {
|
||||
while (this.isRunning) {
|
||||
try {
|
||||
await this.performUpkeep();
|
||||
} catch (error) {
|
||||
console.error(`Error in update loop: ${error.message}`);
|
||||
this.errorCount++;
|
||||
}
|
||||
|
||||
// Wait for next interval
|
||||
await this.sleep(CONFIG.updateInterval);
|
||||
}
|
||||
}
|
||||
|
||||
async performUpkeep() {
|
||||
try {
|
||||
// Check if upkeep is needed
|
||||
const [needsUpdate, assets] = await this.keeper.checkUpkeep();
|
||||
|
||||
if (!needsUpdate || assets.length === 0) {
|
||||
console.log(`[${new Date().toISOString()}] No updates needed`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[${new Date().toISOString()}] Updating ${assets.length} asset(s)...`);
|
||||
|
||||
// Perform upkeep
|
||||
let retries = 0;
|
||||
let success = false;
|
||||
|
||||
while (retries < CONFIG.maxRetries && !success) {
|
||||
try {
|
||||
const tx = await this.keeper.performUpkeep();
|
||||
console.log(` Transaction sent: ${tx.hash}`);
|
||||
|
||||
const receipt = await tx.wait();
|
||||
if (receipt.status === 1) {
|
||||
success = true;
|
||||
this.updateCount++;
|
||||
|
||||
// Parse events
|
||||
const events = receipt.logs.filter(log => {
|
||||
try {
|
||||
const parsed = this.keeper.interface.parseLog(log);
|
||||
return parsed.name === 'PriceFeedsUpdated';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (events.length > 0) {
|
||||
const event = this.keeper.interface.parseLog(events[0]);
|
||||
console.log(` Updated assets: ${event.args.assets.length}`);
|
||||
console.log(` Timestamp: ${event.args.timestamp.toString()}`);
|
||||
}
|
||||
|
||||
console.log(` ✓ Update successful (Gas: ${receipt.gasUsed.toString()})`);
|
||||
} else {
|
||||
throw new Error('Transaction failed');
|
||||
}
|
||||
} catch (error) {
|
||||
retries++;
|
||||
if (retries < CONFIG.maxRetries) {
|
||||
console.log(` Retry ${retries}/${CONFIG.maxRetries}...`);
|
||||
await this.sleep(CONFIG.retryDelay);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
throw new Error('Failed after retries');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(` ✗ Update failed: ${error.message}`);
|
||||
this.errorCount++;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async stop() {
|
||||
console.log('\nStopping keeper service...');
|
||||
this.isRunning = false;
|
||||
|
||||
console.log('\n=== Statistics ===');
|
||||
console.log(`Total Updates: ${this.updateCount}`);
|
||||
console.log(`Total Errors: ${this.errorCount}`);
|
||||
console.log(`Success Rate: ${this.updateCount > 0 ? ((this.updateCount / (this.updateCount + this.errorCount)) * 100).toFixed(2) : 0}%`);
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
}
|
||||
|
||||
// Main execution
|
||||
if (require.main === module) {
|
||||
const keeper = new PriceFeedKeeperService();
|
||||
|
||||
// Handle graceful shutdown
|
||||
process.on('SIGINT', () => keeper.stop());
|
||||
process.on('SIGTERM', () => keeper.stop());
|
||||
|
||||
// Start keeper
|
||||
keeper.start().catch(error => {
|
||||
console.error('Fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = PriceFeedKeeperService;
|
||||
|
||||
109
scripts/reserve/keeper-service.sh
Executable file
109
scripts/reserve/keeper-service.sh
Executable file
@@ -0,0 +1,109 @@
|
||||
#!/bin/bash
|
||||
# Price Feed Keeper Service (Bash version)
|
||||
# Simple bash-based keeper that calls the keeper contract periodically
|
||||
|
||||
set -e
|
||||
|
||||
# Load environment variables
|
||||
if [ -f .env ]; then
|
||||
source .env
|
||||
fi
|
||||
|
||||
# Configuration
|
||||
RPC_URL="${RPC_URL_138:-http://localhost:8545}"
|
||||
KEEPER_ADDRESS="${PRICE_FEED_KEEPER_ADDRESS}"
|
||||
UPDATE_INTERVAL="${UPDATE_INTERVAL:-30}" # seconds
|
||||
MAX_RETRIES=3
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Statistics
|
||||
UPDATE_COUNT=0
|
||||
ERROR_COUNT=0
|
||||
|
||||
# Function to log messages
|
||||
log() {
|
||||
echo -e "[$(date +'%Y-%m-%d %H:%M:%S')] $1"
|
||||
}
|
||||
|
||||
# Function to check if upkeep is needed
|
||||
check_upkeep() {
|
||||
forge script script/reserve/CheckUpkeep.s.sol:CheckUpkeep \
|
||||
--rpc-url "$RPC_URL" \
|
||||
--silent 2>/dev/null | grep -E "(needsUpdate|assets)" || echo "false"
|
||||
}
|
||||
|
||||
# Function to perform upkeep
|
||||
perform_upkeep() {
|
||||
log "${YELLOW}Performing upkeep...${NC}"
|
||||
|
||||
local retries=0
|
||||
local success=false
|
||||
|
||||
while [ $retries -lt $MAX_RETRIES ] && [ "$success" = false ]; do
|
||||
if forge script script/reserve/PerformUpkeep.s.sol:PerformUpkeep \
|
||||
--rpc-url "$RPC_URL" \
|
||||
--broadcast \
|
||||
--silent 2>/dev/null; then
|
||||
success=true
|
||||
UPDATE_COUNT=$((UPDATE_COUNT + 1))
|
||||
log "${GREEN}✓ Upkeep successful${NC}"
|
||||
else
|
||||
retries=$((retries + 1))
|
||||
if [ $retries -lt $MAX_RETRIES ]; then
|
||||
log "${YELLOW}Retry $retries/$MAX_RETRIES...${NC}"
|
||||
sleep 5
|
||||
else
|
||||
ERROR_COUNT=$((ERROR_COUNT + 1))
|
||||
log "${RED}✗ Upkeep failed after $MAX_RETRIES retries${NC}"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Function to stop keeper
|
||||
stop_keeper() {
|
||||
log "\nStopping keeper service..."
|
||||
log "\n=== Statistics ==="
|
||||
log "Total Updates: $UPDATE_COUNT"
|
||||
log "Total Errors: $ERROR_COUNT"
|
||||
if [ $UPDATE_COUNT -gt 0 ]; then
|
||||
local success_rate=$(echo "scale=2; ($UPDATE_COUNT * 100) / ($UPDATE_COUNT + $ERROR_COUNT)" | bc)
|
||||
log "Success Rate: ${success_rate}%"
|
||||
fi
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Trap signals for graceful shutdown
|
||||
trap stop_keeper SIGINT SIGTERM
|
||||
|
||||
# Main loop
|
||||
main() {
|
||||
log "${GREEN}=== Price Feed Keeper Service ===${NC}"
|
||||
log "RPC URL: $RPC_URL"
|
||||
log "Keeper Address: $KEEPER_ADDRESS"
|
||||
log "Update Interval: $UPDATE_INTERVAL seconds"
|
||||
log ""
|
||||
log "Keeper service started. Press Ctrl+C to stop."
|
||||
log ""
|
||||
|
||||
while true; do
|
||||
# Check if upkeep is needed
|
||||
if check_upkeep | grep -q "true"; then
|
||||
perform_upkeep
|
||||
else
|
||||
log "No updates needed"
|
||||
fi
|
||||
|
||||
# Wait for next interval
|
||||
sleep "$UPDATE_INTERVAL"
|
||||
done
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main
|
||||
|
||||
Reference in New Issue
Block a user