Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 27 additions & 23 deletions src/implementation/automation/GelatoAutomation.sol
Original file line number Diff line number Diff line change
@@ -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().
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs ref to gelato docs

function execute(bytes calldata /* execData */) external {
executeDistribution();
}
}
95 changes: 91 additions & 4 deletions test/automation/AutomationBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
Comment on lines +145 to +153
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Gelato tests only assert execPayload.length > 0 and then call gelatoAutomation.execute("") directly. If Gelato is configured to use the resolver payload as the actual calldata to call (typical execCall(calldata) flow), these tests won’t catch an incorrect selector/encoding in execPayload. It would be stronger to assert execPayload equals the expected encoding (currently executeDistribution() via getAutomationData()) and to actually execute the returned payload via a low-level call to address(gelatoAutomation).call(execPayload) and assert it succeeds + emits the expected event.

Copilot uses AI. Check for mistakes.

// ============ 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 {
Expand Down
10 changes: 10 additions & 0 deletions test/automation/AutomationBase.tree
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down