From 6b501f121742909bd5c897353b0c7ebbed2e833f Mon Sep 17 00:00:00 2001 From: marczeller Date: Mon, 12 Aug 2024 11:13:16 +0000 Subject: [PATCH] fix & Automation of script --- Makefile | 25 ++--- README.md | 66 ++++++++++++- dolce_vita_collector.sh | 95 ++++++++++++++++++ dolce_vita_collector_with_notifications.sh | 64 ++++++++++++ foundry.toml | 11 ++- script/FetchReserves.s.sol | 12 +-- script/MintToTreasury.s.sol | 107 +++++++++++++-------- 7 files changed, 308 insertions(+), 72 deletions(-) create mode 100755 dolce_vita_collector.sh create mode 100755 dolce_vita_collector_with_notifications.sh diff --git a/Makefile b/Makefile index d3f36fd..122631c 100644 --- a/Makefile +++ b/Makefile @@ -30,24 +30,13 @@ fetch-reserves: $(LOG_DIR) @echo "Fetching reserves list for all networks..." @forge script ${FETCH_RESERVES_SCRIPT} -vvvv -mint-to-treasury: $(LOG_DIR) - @echo "Minting to treasury on ${NETWORK}..." - @timeout $(TIMEOUT) bash -c "\ - RESERVES=\$$(cat $(LOG_DIR)/reserves.json); \ - NETWORK=${NETWORK} forge script ${MINT_TO_TREASURY_SCRIPT} \ - --sig \"run(string)\" \"\$$RESERVES\" \ - --rpc-url ${RPC_${NETWORK}} \ - ${PRIVATE_KEY_ARG} \ - ${EXTRA_ARGS}" \ - || echo "Transaction for ${NETWORK} timed out after $(TIMEOUT) seconds. Skipping to next network." - -run-all: fetch-reserves - @echo "Running mint-to-treasury for all networks with reserves..." - @NETWORKS=$$(jq -r 'keys[]' $(LOG_DIR)/reserves.json); \ - for network in $$NETWORKS; do \ - echo "Processing $$network"; \ - $(MAKE) mint-to-treasury NETWORK=$$network || true; \ - done +mint: + @if [ -z "$(NETWORK)" ]; then \ + echo "Error: NETWORK is not set. Use 'make mint NETWORK=' or set NETWORK in .env file."; \ + exit 1; \ + fi + @echo "Minting for network: $(NETWORK)" + TARGET_NETWORK=$(NETWORK) forge script script/MintToTreasury.s.sol:MintToTreasuryScript --broadcast clean: @rm -rf $(LOG_DIR) broadcast cache out \ No newline at end of file diff --git a/README.md b/README.md index 00495ed..7c7b6b4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ACI's Dolce Vita Collector -This project automates the process of minting to treasury for various Aave Pools across multiple networks. +This project automates the process of minting to treasury for various Aave Pools across multiple networks. It includes a scheduled script that runs daily and weekly, with Telegram notifications for monitoring. ## Setup @@ -26,6 +26,11 @@ This project automates the process of minting to treasury for various Aave Pools forge build ``` +5. Set up the automated script and Telegram notifications: + - Ensure the `dolce_vita_collector_with_notifications.sh` script is in place and executable. + - Set up systemd services and timers for daily and weekly runs (see "Automated Runs" section). + - Configure the Telegram bot token and chat ID in the script. + ## Usage - To fetch reserves for all networks: @@ -38,10 +43,61 @@ This project automates the process of minting to treasury for various Aave Pools make mint-to-treasury NETWORK=MAINNET POOL=MAIN ``` -- To run the entire process (fetch reserves and mint for all networks): - ``` - make run-all - ``` +- The automated script runs daily (excluding MAINNET) and weekly (including MAINNET) at 8:00 AM UTC. + +## Automated Runs + +To set up automated runs, you need to create systemd service and timer files. Replace `/path/to/` with the actual path to your cloned repository. + +1. Create service files: + ``` + sudo nano /etc/systemd/system/dolce-vita-daily.service + sudo nano /etc/systemd/system/dolce-vita-weekly.service + ``` + +2. Create timer files: + ``` + sudo nano /etc/systemd/system/dolce-vita-daily.timer + sudo nano /etc/systemd/system/dolce-vita-weekly.timer + ``` + +3. Enable and start the timers: + ``` + sudo systemctl daemon-reload + sudo systemctl enable dolce-vita-daily.timer dolce-vita-weekly.timer + sudo systemctl start dolce-vita-daily.timer dolce-vita-weekly.timer + ``` + +Refer to the provided service and timer file templates in the `systemd` directory and adjust paths as necessary. + +## Monitoring + +The script sends Telegram notifications for: +- Start of each run +- Successful completion of each run +- Any errors encountered during the run + +To manually trigger a run: +``` +/path/to/dolce_vita_collector/dolce_vita_collector_with_notifications.sh +``` + +Add `--include-mainnet` for a run that includes MAINNET. + +## Logs + +Logs are stored in `/var/log/dolce_vita_collector/`: +- `daily.log`: For daily runs +- `weekly.log`: For weekly runs (including MAINNET) + +Ensure the log directory exists and is writable by the user running the script. + +## Customization + +When setting up this project, make sure to: +1. Update all paths in the scripts and service files to match your system's directory structure. +2. Configure your own Telegram bot token and chat ID in the notification script. +3. Adjust the systemd service files to use the correct user and group for your system. ## License diff --git a/dolce_vita_collector.sh b/dolce_vita_collector.sh new file mode 100755 index 0000000..b6a712c --- /dev/null +++ b/dolce_vita_collector.sh @@ -0,0 +1,95 @@ +#!/bin/bash + +# Get the directory where the script is located +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" + +# Log file +LOG_FILE="${SCRIPT_DIR}/logs/dolce_vita_collector_log.txt" + +# Ensure the logs directory exists +mkdir -p "${SCRIPT_DIR}/logs" + +# Function to log messages +log_message() { + echo "$(date +"%Y-%m-%d %H:%M:%S"): $1" | tee -a "$LOG_FILE" +} + +# Function to execute and log a command with timeout +execute_command_with_timeout() { + local COMMAND="$1" + local TIMEOUT=180 # 180 seconds = 3 minutes + + log_message "Executing with ${TIMEOUT}s timeout: $COMMAND" + + timeout $TIMEOUT $COMMAND + local EXIT_CODE=$? + + if [ $EXIT_CODE -eq 0 ]; then + log_message "$COMMAND successful" + return 0 + elif [ $EXIT_CODE -eq 124 ]; then + log_message "$COMMAND timed out after ${TIMEOUT} seconds" + return 1 + else + log_message "$COMMAND failed with exit code $EXIT_CODE" + return 1 + fi +} + +# Check if MAINNET should be included +INCLUDE_MAINNET=0 +if [ "$1" == "--include-mainnet" ]; then + INCLUDE_MAINNET=1 +fi + +# Array of networks +NETWORKS=( + "AVALANCHE" + "OPTIMISM" + "POLYGON" + "ARBITRUM" + "METIS" + "BASE" + "GNOSIS" + "BNB" + "SCROLL" +) + +# Add MAINNET if flag is set +if [ $INCLUDE_MAINNET -eq 1 ]; then + NETWORKS+=("MAINNET") +fi + +# Main execution +log_message "Script execution started" + +# Execute make clean +execute_command_with_timeout "make clean" + +# Execute make fetch-reserves and wait for it to complete +if execute_command_with_timeout "make fetch-reserves"; then + log_message "make fetch-reserves completed successfully. Proceeding with minting." + + log_message "Starting minting process for all networks" + + for NETWORK in "${NETWORKS[@]}"; do + log_message "Processing $NETWORK" + + COMMAND="make mint NETWORK=$NETWORK" + if execute_command_with_timeout "$COMMAND"; then + log_message "Minting successful for $NETWORK" + else + log_message "Minting failed or timed out for $NETWORK. Moving to next network." + fi + + echo "----------------------------------------" + done + + log_message "Minting process completed for all networks" +else + log_message "make fetch-reserves failed or timed out. Aborting minting process." +fi + +log_message "Script execution completed" + +exit 0 \ No newline at end of file diff --git a/dolce_vita_collector_with_notifications.sh b/dolce_vita_collector_with_notifications.sh new file mode 100755 index 0000000..528bf73 --- /dev/null +++ b/dolce_vita_collector_with_notifications.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +LOG_DIR="/var/log/dolce_vita_collector" +COUNTER_FILE="${LOG_DIR}/counter.log" +MAX_LINES=2000 + +# Function to send Telegram messages +send_telegram_message() { + local message=$1 + local bot_token="7292479786:AAGb1isSfJpjRpIsgGzwYy6GalIIc6-jNqI" + local chat_id="253560971" + curl -s -X POST "https://api.telegram.org/bot$bot_token/sendMessage" -d chat_id="$chat_id" -d text="$message" +} + +# Initialize the counter and date if they don't exist +if [ ! -f $COUNTER_FILE ]; then + echo "Loop Counter: 0" > $COUNTER_FILE + echo "Last Run: Never" >> $COUNTER_FILE +fi + +# Read the current counter value and date of last run +counter=$(grep -o '[0-9]\+' $COUNTER_FILE | head -n 1) +last_run=$(grep 'Last Run:' $COUNTER_FILE) + +# Increment the counter +counter=$((counter + 1)) +current_date=$(date '+%Y-%m-%d %H:%M:%S') + +# Update the counter file with the new counter and date +echo "Loop Counter: $counter" > $COUNTER_FILE +echo "Last Run: $current_date" >> $COUNTER_FILE + +# Determine if this is a weekly run (with MAINNET) +if [[ "$1" == "--include-mainnet" ]]; then + run_type="Weekly (including MAINNET)" + log_file="${LOG_DIR}/weekly.log" +else + run_type="Daily" + log_file="${LOG_DIR}/daily.log" +fi + +# Log the start of the process +{ + echo "🚀 Starting Dolce Vita Collector $run_type script..." + echo "Loop Counter: $counter" + echo "Last Run: $current_date" + + send_telegram_message "🚀 Starting Dolce Vita Collector $run_type script. Loop Counter: $counter. Last Run: $current_date" + + # Run the original script + if /home/mzeller/dolce_vita_collector/dolce_vita_collector.sh "$@"; then + echo "🎉 Dolce Vita Collector $run_type script completed successfully." + send_telegram_message "🎉 Dolce Vita Collector $run_type script completed successfully. Loop Counter: $counter." + else + echo "⚠️ Dolce Vita Collector $run_type script encountered errors." + send_telegram_message "⚠️ Dolce Vita Collector $run_type script encountered errors. Loop Counter: $counter." + fi + + # Truncate the log file to keep only the last $MAX_LINES lines + tail -n $MAX_LINES $log_file > $log_file.tmp && mv $log_file.tmp $log_file + + # Concatenate the counter file and the log file + cat $COUNTER_FILE $log_file > $log_file.tmp && mv $log_file.tmp $log_file +} | ts '[%Y-%m-%d %H:%M:%S]' >> $log_file diff --git a/foundry.toml b/foundry.toml index f663de9..8cee8d6 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,6 +2,13 @@ src = "src" out = "out" libs = ["lib"] -fs_permissions = [{ access = "read-write", path = "./logs"}] +solc = "0.8.21" # Specify the Solidity version +fs_permissions = [ + { access = "read-write", path = "./logs"}, + { access = "read", path = "./.env" }, + { access = "read-write", path = "./logs/reserves.json" } +] +ffi = true +verbosity = 3 -# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options \ No newline at end of file diff --git a/script/FetchReserves.s.sol b/script/FetchReserves.s.sol index ee31520..caf375d 100644 --- a/script/FetchReserves.s.sol +++ b/script/FetchReserves.s.sol @@ -81,7 +81,7 @@ contract FetchReservesScript is Script { } function getNetworkConfigs() internal pure returns (NetworkConfig[] memory) { - NetworkConfig[] memory configs = new NetworkConfig[](11); + NetworkConfig[] memory configs = new NetworkConfig[](9); string[] memory mainnetPools = new string[](2); mainnetPools[0] = "MAIN"; @@ -95,12 +95,10 @@ contract FetchReservesScript is Script { configs[2] = NetworkConfig("OPTIMISM", singlePool); configs[3] = NetworkConfig("POLYGON", singlePool); configs[4] = NetworkConfig("ARBITRUM", singlePool); - configs[5] = NetworkConfig("FANTOM", singlePool); - configs[6] = NetworkConfig("HARMONY", singlePool); - configs[7] = NetworkConfig("METIS", singlePool); - configs[8] = NetworkConfig("BASE", singlePool); - configs[9] = NetworkConfig("GNOSIS", singlePool); - configs[10] = NetworkConfig("BNB", singlePool); + configs[5] = NetworkConfig("METIS", singlePool); + configs[6] = NetworkConfig("BASE", singlePool); + configs[7] = NetworkConfig("GNOSIS", singlePool); + configs[8] = NetworkConfig("BNB", singlePool); return configs; } diff --git a/script/MintToTreasury.s.sol b/script/MintToTreasury.s.sol index a51dbe1..427b0a2 100644 --- a/script/MintToTreasury.s.sol +++ b/script/MintToTreasury.s.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.13; import "forge-std/Script.sol"; import "forge-std/console.sol"; @@ -9,54 +9,81 @@ interface IPool { } contract MintToTreasuryScript is Script { - function setUp() public {} + mapping(string => address) pools; + mapping(string => string) rpcUrls; + string[] networkNames = [ + "MAINNET", "AVALANCHE", "OPTIMISM", "POLYGON", + "ARBITRUM", "METIS", "BASE", "GNOSIS", "BNB", "SCROLL" + ]; + string constant RESERVES_PATH = "./logs/reserves.json"; - function run(string memory reservesJson) public { - string memory network = vm.envString("NETWORK"); - string memory pool = vm.envString("POOL"); - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - address poolAddress = vm.envAddress(string(abi.encodePacked(network, "_", pool, "_POOL"))); - - string memory logContent = string(abi.encodePacked( - "Network: ", network, "\n", - "Pool: ", pool, "\n", - "Pool address: ", vm.toString(poolAddress), "\n" - )); - - // Check if the network and pool exist in the JSON - bytes memory poolData = vm.parseJson(reservesJson, string(abi.encodePacked(".", network, ".", pool))); - if (poolData.length == 0) { - logContent = string(abi.encodePacked(logContent, "No reserves found for this network and pool. Skipping mintToTreasury call.\n")); - } else { - // Parse reserves from JSON string - address[] memory assets = abi.decode(poolData, (address[])); + function setUp() public { + for (uint i = 0; i < networkNames.length; i++) { + string memory networkName = networkNames[i]; + string memory poolEnvVar = string(abi.encodePacked(networkName, "_MAIN_POOL")); + string memory rpcEnvVar = string(abi.encodePacked("RPC_", networkName)); - logContent = string(abi.encodePacked(logContent, "Number of assets: ", vm.toString(assets.length), "\n\n")); + pools[networkName] = vm.envAddress(poolEnvVar); + rpcUrls[networkName] = vm.envString(rpcEnvVar); + } + // Special case for MAINNET_LIDO_POOL + pools["MAINNET_LIDO"] = vm.envAddress("MAINNET_LIDO_POOL"); + } - vm.startBroadcast(deployerPrivateKey); + function run() public { + string memory targetNetwork = vm.envOr("TARGET_NETWORK", string("")); + if (bytes(targetNetwork).length > 0) { + runForNetwork(targetNetwork); + } else { + runForAllNetworks(); + } + } - IPool poolContract = IPool(poolAddress); + function runForNetwork(string memory networkName) internal { + require(pools[networkName] != address(0), "Invalid network name"); + + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + vm.createSelectFork(rpcUrls[networkName]); + vm.startBroadcast(deployerPrivateKey); + + mintToTreasuryForPool(networkName, "MAIN", pools[networkName]); + + if (keccak256(abi.encodePacked(networkName)) == keccak256(abi.encodePacked("MAINNET"))) { + mintToTreasuryForPool("MAINNET", "LIDO", pools["MAINNET_LIDO"]); + } + + vm.stopBroadcast(); + } + + function runForAllNetworks() internal { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + + for (uint i = 0; i < networkNames.length; i++) { + string memory networkName = networkNames[i]; + vm.createSelectFork(rpcUrls[networkName]); + vm.startBroadcast(deployerPrivateKey); - // Call mintToTreasury with all assets - try poolContract.mintToTreasury(assets) { - logContent = string(abi.encodePacked(logContent, "Successfully minted to treasury\n")); - for (uint i = 0; i < assets.length; i++) { - logContent = string(abi.encodePacked(logContent, "Minted asset: ", vm.toString(assets[i]), "\n")); - } - } catch Error(string memory reason) { - logContent = string(abi.encodePacked(logContent, "Failed to mint to treasury. Reason: ", reason, "\n")); - } catch (bytes memory lowLevelData) { - logContent = string(abi.encodePacked(logContent, "Failed to mint to treasury. Low-level error: ", vm.toString(lowLevelData), "\n")); + mintToTreasuryForPool(networkName, "MAIN", pools[networkName]); + + if (keccak256(abi.encodePacked(networkName)) == keccak256(abi.encodePacked("MAINNET"))) { + mintToTreasuryForPool("MAINNET", "LIDO", pools["MAINNET_LIDO"]); } vm.stopBroadcast(); } + } - // Write log to file - string memory filename = string(abi.encodePacked("./logs/mint_to_treasury_", network, "_", pool, "_", vm.toString(block.timestamp), ".log")); - vm.writeFile(filename, logContent); - - console.log("Mint to treasury operation completed for", network, pool); - console.log("Log written to:", filename); + function mintToTreasuryForPool(string memory network, string memory poolType, address poolAddress) internal { + address[] memory reserves = getReservesForPool(network, poolType); + if (reserves.length == 0) { + return; // Skip if reserves array is empty + } + IPool(poolAddress).mintToTreasury(reserves); + } + + function getReservesForPool(string memory network, string memory poolType) internal view returns (address[] memory) { + string memory json = vm.readFile(RESERVES_PATH); + bytes memory parseJson = vm.parseJson(json, string(abi.encodePacked(".", network, ".", poolType))); + return abi.decode(parseJson, (address[])); } } \ No newline at end of file