diff --git a/src/implementation/automation/GelatoAutomation.sol b/src/implementation/automation/GelatoAutomation.sol index c75b3b7..ac15ac5 100644 --- a/src/implementation/automation/GelatoAutomation.sol +++ b/src/implementation/automation/GelatoAutomation.sol @@ -1,27 +1,31 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity ^0.8.20; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; -// import "./AutomationBase.sol"; +import {AbstractAutomation} from "../../abstract/AbstractAutomation.sol"; -// /// @title GelatoAutomation -// /// @notice Gelato Network compatible automation implementation -// /// @dev Implements Gelato automation interface for yield distribution -// contract GelatoAutomation is AutomationBase { -// constructor(address _distributionManager) AutomationBase(_distributionManager) {} +/// @title GelatoAutomation +/// @notice Gelato Network compatible automation implementation +/// @dev Implements Gelato automation interface for yield distribution. +/// No auth is required on execute() — the DistributionManager enforces +/// its own access controls and condition checks, so any caller +/// (including Gelato executors) can safely trigger execution. +contract GelatoAutomation is AbstractAutomation { + constructor(address _distributionManager) AbstractAutomation(_distributionManager) {} -// /// @notice Gelato-compatible resolver function -// /// @dev Called by Gelato executors to check if work needs to be performed -// /// @return canExec Whether execution can proceed -// /// @return execPayload The calldata to execute -// function checker() external view returns (bool canExec, bytes memory execPayload) { -// canExec = isDistributionReady(); -// execPayload = canExec ? getAutomationData() : new bytes(0); -// } + /// @notice Gelato-compatible resolver function + /// @dev Called by Gelato executors to check if work needs to be performed + /// @return canExec Whether execution can proceed + /// @return execPayload The calldata to execute + function checker() external view returns (bool canExec, bytes memory execPayload) { + canExec = isDistributionReady(); + execPayload = canExec ? abi.encodeCall(this.execute, ("")) : new bytes(0); + } -// /// @notice Gelato-compatible execution function -// /// @dev Called by Gelato executors when checker returns true -// /// @param execData The data for execution (not used but can be for validation) -// function execute(bytes calldata execData) external { -// executeDistribution(); -// } -// } + /// @notice Gelato-compatible execution function + /// @dev Called by Gelato executors when checker returns true. + /// No auth guard is needed here — DistributionManager.claimAndDistribute() + /// enforces its own readiness checks via isDistributionReady(). + function execute(bytes calldata /* execData */) external { + executeDistribution(); + } +} diff --git a/test/automation/AutomationBase.t.sol b/test/automation/AutomationBase.t.sol index a8a7d35..a6a5f6a 100644 --- a/test/automation/AutomationBase.t.sol +++ b/test/automation/AutomationBase.t.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {ChainlinkAutomation} from "../../src/implementation/automation/ChainlinkAutomation.sol"; +import {GelatoAutomation} from "../../src/implementation/automation/GelatoAutomation.sol"; import {AbstractAutomation} from "../../src/abstract/AbstractAutomation.sol"; -// import "../../src/implementation/automation/GelatoAutomation.sol"; import {MockDistributionManager} from "../mocks/MockDistributionManager.sol"; import {IDistributionModule} from "../../src/interfaces/IDistributionModule.sol"; import {IRecipientRegistry} from "../../src/interfaces/IRecipientRegistry.sol"; @@ -69,12 +69,12 @@ contract MockDistributionModule is IDistributionModule { contract AutomationBaseTest is Test { ChainlinkAutomation public chainlinkAutomation; - // GelatoAutomation public gelatoAutomation; + GelatoAutomation public gelatoAutomation; MockDistributionManager public distributionManager; MockDistributionModule public distributionModule; address public chainlinkKeeper = address(0x1); - // address public gelatoExecutor = address(0x2); + address public gelatoExecutor = address(0x2); event AutomationExecuted(address indexed executor, uint256 blockNumber); event DistributionExecuted(uint256 blockNumber, uint256 yield, uint256 votes); @@ -88,7 +88,7 @@ contract AutomationBaseTest is Test { // Deploy automation implementations chainlinkAutomation = new ChainlinkAutomation(address(distributionManager)); - // gelatoAutomation = new GelatoAutomation(address(distributionManager)); + gelatoAutomation = new GelatoAutomation(address(distributionManager)); // Setup initial state distributionManager.setCurrentVotes(100); @@ -134,6 +134,93 @@ contract AutomationBaseTest is Test { assertEq(distributionManager.currentCycleNumber(), 2); } + // ============ when checking Gelato checker ============ + + function test_WhenCheckingGelatoChecker_ShouldReturnFalseWhenTooSoon() public { + // Initially should not be ready (too soon) + (bool canExec,) = gelatoAutomation.checker(); + assertFalse(canExec); + } + + function test_WhenCheckingGelatoChecker_ShouldReturnTrueWhenReady() public { + // Advance blocks + vm.roll(block.number + 101); + + // Now should be ready + (bool canExec, bytes memory execPayload) = gelatoAutomation.checker(); + assertTrue(canExec); + assertGt(execPayload.length, 0); + } + + // ============ when executing Gelato ============ + + function test_WhenExecutingGelato_ShouldDistributeAndEmitEvent() public { + // Advance blocks to make distribution ready + vm.roll(block.number + 101); + + // Verify checker returns true + (bool canExec,) = gelatoAutomation.checker(); + assertTrue(canExec); + + // Execute via Gelato executor + vm.expectEmit(true, false, false, true); + emit AutomationExecuted(gelatoExecutor, block.number); + + vm.prank(gelatoExecutor); + gelatoAutomation.execute(""); + + // Verify distribution was called + assertEq(distributionModule.distributeCallCount(), 1); + assertEq(distributionManager.currentCycleNumber(), 2); + } + + function test_WhenExecutingGelato_ShouldAllowAnyCaller() public { + // Advance blocks to make distribution ready + vm.roll(block.number + 101); + + // Any address should be able to call execute + address randomCaller = address(0xBEEF); + vm.prank(randomCaller); + gelatoAutomation.execute(""); + + // Verify distribution was called + assertEq(distributionModule.distributeCallCount(), 1); + } + + // ============ when Gelato checker not ready ============ + + function test_WhenGelatoCheckerNotReady_ShouldReturnFalseForEachCondition() public { + // Condition 1: not enough blocks passed + (bool canExec,) = gelatoAutomation.checker(); + assertFalse(canExec); + + // Condition 2: no votes + vm.roll(block.number + 101); + distributionManager.setCurrentVotes(0); + (canExec,) = gelatoAutomation.checker(); + assertFalse(canExec); + + // Condition 3: low yield + distributionManager.setCurrentVotes(100); + distributionManager.setAvailableYield(500); + (canExec,) = gelatoAutomation.checker(); + assertFalse(canExec); + + // Condition 4: system disabled + distributionManager.setAvailableYield(2000); + distributionManager.setEnabled(false); + (canExec,) = gelatoAutomation.checker(); + assertFalse(canExec); + } + + // ============ when Gelato executing without conditions met ============ + + function test_RevertWhen_GelatoExecutingWithoutConditionsMet() public { + // Try to execute when conditions not met + vm.expectRevert(AbstractAutomation.NotResolved.selector); + gelatoAutomation.execute(""); + } + // ============ when resolving distribution conditions ============ function test_WhenResolvingDistributionConditions_ShouldFailWhenNotEnoughBlocksPassed() public { diff --git a/test/automation/AutomationBase.tree b/test/automation/AutomationBase.tree index 6c46cd8..0229c9d 100644 --- a/test/automation/AutomationBase.tree +++ b/test/automation/AutomationBase.tree @@ -4,6 +4,16 @@ AutomationBaseTest │ └── it should return true when ready ├── when performing Chainlink upkeep │ └── it should execute distribution and emit event +├── when checking Gelato checker +│ ├── it should return false when too soon +│ └── it should return true when ready +├── when executing Gelato +│ ├── it should distribute and emit event +│ └── it should allow any caller +├── when Gelato checker not ready +│ └── it should return false for each condition +├── when Gelato executing without conditions met +│ └── it should revert with NotResolved ├── when resolving distribution conditions │ ├── it should fail when not enough blocks passed │ ├── it should fail when no votes