// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import {IAutomationCompatible} from "./IAutomationCompatible.sol"; import {PluginOracleAdapter} from "../PluginOracleAdapter.sol"; import {Owned} from "../utils/Owned.sol"; /// @title PriceSnapshotUpkeep (template) /// @notice Every `interval` seconds, records a checked price from a PluginOracleAdapter. /// @dev performUpkeep is locked to an owner-set forwarder (unset = nobody) AND re-checks the /// condition, so it stays safe even if the forwarder or any other caller runs it at will. /// Plugin Automation registry/forwarder addresses on XDC are unpublished (see README). contract PriceSnapshotUpkeep is IAutomationCompatible, Owned { PluginOracleAdapter public immutable oracle; uint256 public immutable interval; address public forwarder; uint256 public lastRun; uint256 public lastPrice; uint256 public runs; error NotNeeded(); error NotForwarder(address caller); event ForwarderSet(address forwarder); event Snapshot(uint256 indexed run, uint256 price, uint256 timestamp); constructor(PluginOracleAdapter _oracle, uint256 _interval, address _owner) Owned(_owner) { oracle = _oracle; interval = _interval; } function setForwarder(address f) external onlyOwner { forwarder = f; emit ForwarderSet(f); } function _needed() internal view returns (bool) { return block.timestamp >= lastRun + interval; } function checkUpkeep(bytes calldata) external view returns (bool upkeepNeeded, bytes memory performData) { upkeepNeeded = _needed(); performData = ""; } function performUpkeep(bytes calldata) external { if (msg.sender != forwarder || forwarder == address(0)) revert NotForwarder(msg.sender); if (!_needed()) revert NotNeeded(); uint256 p = oracle.latestPrice(); // reverts if stale/invalid lastRun = block.timestamp; lastPrice = p; emit Snapshot(++runs, p, block.timestamp); } }