diff --git a/script/SendRewardsToStaker.s.sol b/script/SendRewardsToStaker.s.sol new file mode 100644 index 0000000..e3f4d3c --- /dev/null +++ b/script/SendRewardsToStaker.s.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.28; + +import {Script, console2} from "forge-std/Script.sol"; +import {RewardAccumulator} from "../src/RewardAccumulator.sol"; + +/// @notice Calls RewardAccumulator.sendRewardsToStaker() — and only that. Reverts with +/// RewardAccumulator.WaitForNextRewardTime if the reward window hasn't elapsed yet. +/// @dev sendRewardsToStaker() is permissionless in open mode (see RewardAccumulator's dev notes), so +/// PRIVATE_KEY only needs to be funded for gas, no special role required. +/// Use `script/send-rewards-to-staker.sh` to poll and wait (with live progress output) for the +/// window to open before invoking this — a Solidity wait loop inside the script can't stream +/// progress, since forge only prints console2 logs after the whole run() call finishes. +/// Required environment variables: +/// REWARD_ACCUMULATOR_ADDRESS — address of the deployed RewardAccumulator +/// PRIVATE_KEY — broadcaster private key (hex, with or without 0x prefix) +contract SendRewardsToStaker is Script { + function run() external { + address accumulatorAddress = vm.envAddress("REWARD_ACCUMULATOR_ADDRESS"); + uint256 broadcasterKey = vm.envUint("PRIVATE_KEY"); + + RewardAccumulator accumulator = RewardAccumulator(accumulatorAddress); + + vm.startBroadcast(broadcasterKey); + accumulator.sendRewardsToStaker(); + vm.stopBroadcast(); + + console2.log("Sent accumulated rewards to staker"); + } +} diff --git a/script/send-rewards-to-staker.sh b/script/send-rewards-to-staker.sh new file mode 100755 index 0000000..4a089c1 --- /dev/null +++ b/script/send-rewards-to-staker.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Polls RewardAccumulator.nextRewardTime() and waits — printing live progress — until the +# reward window elapses, then broadcasts SendRewardsToStaker.s.sol (which calls only +# sendRewardsToStaker()). +# +# The wait/poll loop lives here in bash rather than inside the Solidity script: forge only +# flushes console2 logs after the whole run() call returns, so a Solidity-side wait loop +# prints nothing while it's waiting, even across a multi-hour/day gap. +# +# Required environment variables (e.g. from .env): +# REWARD_ACCUMULATOR_ADDRESS — address of the deployed RewardAccumulator +# PRIVATE_KEY — broadcaster private key (hex, with or without 0x prefix) +# +# Optional: +# RPC_ALIAS — foundry.toml [rpc_endpoints] alias (or a raw RPC URL), defaults to "mainnet" +# POLL_INTERVAL — seconds between chain checks while waiting, defaults to 60 + +set -euo pipefail +cd "$(dirname "${BASH_SOURCE[0]}")/.." + +if [ -f .env ]; then + set -a + # shellcheck disable=SC1091 + source .env + set +a +fi + +: "${REWARD_ACCUMULATOR_ADDRESS:?REWARD_ACCUMULATOR_ADDRESS must be set}" +: "${PRIVATE_KEY:?PRIVATE_KEY must be set}" + +RPC_ALIAS="${RPC_ALIAS:-mainnet}" +POLL_INTERVAL="${POLL_INTERVAL:-2}" + +while true; do + next_reward_time=$(cast call "$REWARD_ACCUMULATOR_ADDRESS" "nextRewardTime()(uint256)" \ + --rpc-url "$RPC_ALIAS" | awk '{print $1}') + now=$(date +%s) + remaining=$((next_reward_time - now)) + + if [ "$remaining" -le 0 ]; then + echo "[$(date -u +%FT%TZ)] Reward window reached, sending rewards to staker..." + break + fi + + echo "[$(date -u +%FT%TZ)] Waiting for reward window: ${remaining}s remaining (next check in ${POLL_INTERVAL}s)" + sleep "$POLL_INTERVAL" +done + +forge script script/SendRewardsToStaker.s.sol --rpc-url "$RPC_ALIAS" --broadcast -vvvv