Skip to content

feat: implement generic automation payment framework (#73)#127

Open
RonTuretzky wants to merge 2 commits into
RonTuretzky/116-sorted-queuefrom
RonTuretzky/73-automation-payment
Open

feat: implement generic automation payment framework (#73)#127
RonTuretzky wants to merge 2 commits into
RonTuretzky/116-sorted-queuefrom
RonTuretzky/73-automation-payment

Conversation

@RonTuretzky
Copy link
Copy Markdown
Contributor

Summary

  • Add IAutomationPayment interface with PaymentStrategy enum (FIXED_FEE, PERCENTAGE_BASED)
  • Add AbstractPaidAutomation extending AbstractAutomation with yield sufficiency checks and fee deduction
  • Add FixedFeePayment — constant fee per execution
  • Add PercentagePayment — basis-points-based fee from yield
  • 35 comprehensive tests covering fee calculation, yield sufficiency, edge cases, and integration

Closes #73

Test plan

  • 35 payment-specific tests pass
  • 240 total tests pass (only 4 pre-existing fork failures)
  • All strategies validate inputs at construction

Stack: PR 10 of 10 (0.0.2) — stacked on #126

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Introduces a generic automation payment abstraction intended to prevent unprofitable yield distributions by requiring sufficient yield to cover an automation fee (fixed or percentage-based) and a post-fee minimum threshold.

Changes:

  • Added IAutomationPayment interface and two implementations: FixedFeePayment and PercentagePayment.
  • Added AbstractPaidAutomation to extend automation readiness checks with payment/yield sufficiency logic.
  • Added a comprehensive test suite for payment strategies and paid-automation integration behaviors.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/interfaces/IAutomationPayment.sol Defines the payment strategy interface and config struct.
src/implementation/automation/FixedFeePayment.sol Implements constant-fee payment strategy and yield sufficiency check.
src/implementation/automation/PercentagePayment.sol Implements basis-points fee calculation and sufficiency check.
src/abstract/AbstractPaidAutomation.sol Adds payment-provider-based yield sufficiency gating and fee deduction helper.
test/automation/AutomationPayment.t.sol Adds tests for fee calculation, sufficiency, and (partial) integration flow.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +40 to +49
/// @notice Deducts the automation fee from the total yield
/// @param totalYield The total yield before fee deduction
/// @return remainingYield The yield remaining after the fee
function _deductFee(uint256 totalYield) internal returns (uint256 remainingYield) {
uint256 fee = PAYMENT_PROVIDER.calculateFee(totalYield);
if (fee > totalYield) revert InsufficientYieldForFee();

remainingYield = totalYield - fee;
emit AutomationFeeDeducted(fee, remainingYield);
}
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.

_deductFee() is currently unused by the base flow (there is no override of executeDistribution() that calls it), so no fee is actually deducted/emitted during real executions. If fee deduction is intended to be part of the framework, integrate _deductFee() into the execution path (e.g., override executeDistribution() to compute the available yield, deduct the fee, and only distribute the remaining amount) or expose a clear hook that concrete paid automations must call.

Copilot uses AI. Check for mistakes.

/// @inheritdoc IAutomationPayment
function isYieldSufficient(uint256 totalYield) external view override returns (bool sufficient) {
return totalYield >= FEE_AMOUNT + MINIMUM_YIELD;
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.

isYieldSufficient() uses FEE_AMOUNT + MINIMUM_YIELD, which can overflow and revert if those constructor params are large, turning a simple sufficiency check into an unexpected DoS. Consider rewriting as totalYield >= FEE_AMOUNT && totalYield - FEE_AMOUNT >= MINIMUM_YIELD (or equivalent) to avoid overflow.

Suggested change
return totalYield >= FEE_AMOUNT + MINIMUM_YIELD;
return totalYield >= FEE_AMOUNT && totalYield - FEE_AMOUNT >= MINIMUM_YIELD;

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +29
/// @inheritdoc IAutomationPayment
function calculateFee(uint256 totalYield) external view override returns (uint256 fee) {
return (totalYield * BASIS_POINTS) / BASIS_POINTS_DENOMINATOR;
}
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.

calculateFee() (and the duplicated calculation in isYieldSufficient()) does totalYield * BASIS_POINTS which can overflow and revert for very large totalYield values, even though the final result would fit after division. Using a mulDiv-style calculation (or dividing first when safe) avoids this edge-case revert and makes fee calculation robust.

Copilot uses AI. Check for mistakes.
Comment on lines +403 to +416
function test_WhenExecutingFullFlow_ShouldDeductFeeAndDistribute() public {
vm.roll(block.number + 101);
automation.setAvailableYield(10_000);

// Verify readiness
assertTrue(automation.isDistributionReady());

// Execute distribution through the base contract
automation.executeDistribution();

// Verify distribution happened
assertEq(distModule.distributeCallCount(), 1);
assertEq(distributionManager.currentCycleNumber(), 2);
}
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.

test_WhenExecutingFullFlow_ShouldDeductFeeAndDistribute asserts only that distribution executed, but it doesn't assert that the automation fee was deducted/emitted (and currently executeDistribution() never calls _deductFee()). Either adjust the test name/assertions to match the current behavior, or (preferably) add an expectEmit + assertion that executeDistribution() enforces fee sufficiency and emits AutomationFeeDeducted as part of the execution flow.

Copilot uses AI. Check for mistakes.
Comment on lines +279 to +289
function test_WhenBaseReadyButYieldInsufficient_ShouldReturnFalse() public {
vm.roll(block.number + 101);
automation.setAvailableYield(500); // Below 100 fee + 500 minimum = 600
assertFalse(automation.isDistributionReady());
}

function test_WhenBaseReadyAndYieldSufficient_ShouldReturnTrue() public {
vm.roll(block.number + 101);
automation.setAvailableYield(1000); // Above 100 fee + 500 minimum = 600
assertTrue(automation.isDistributionReady());
}
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.

There is no test that calling executeDistribution() reverts when the base DistributionManager is ready but the payment/yield check fails (i.e., isDistributionReady() overridden here returns false). Adding this test would catch the current bypass where executeDistribution() checks only DISTRIBUTION_MANAGER.isDistributionReady() and ignores the payment provider threshold.

Copilot uses AI. Check for mistakes.
uint256 availableYield = _getAvailableYield();
return PAYMENT_PROVIDER.isYieldSufficient(availableYield);
}

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.

AbstractAutomation.executeDistribution() checks DISTRIBUTION_MANAGER.isDistributionReady() directly, so callers can bypass the paid automation yield/fee gate implemented in this override. AbstractPaidAutomation should override executeDistribution() to gate on isDistributionReady() (or explicitly re-check PAYMENT_PROVIDER.isYieldSufficient(_getAvailableYield())) before calling claimAndDistribute(), otherwise unprofitable distributions can still be forced by any caller.

Suggested change
/// @notice Executes distribution only when the paid automation readiness checks pass
/// @dev Uses the overridden readiness gate so fee/yield validation cannot be bypassed
function executeDistribution() public virtual override {
require(isDistributionReady(), "Distribution not ready");
claimAndDistribute();
}

Copilot uses AI. Check for mistakes.
@RonTuretzky RonTuretzky force-pushed the RonTuretzky/116-sorted-queue branch from fb31c91 to eae8aac Compare April 16, 2026 01:29
Add extensible payment framework for automation providers:
- IAutomationPayment interface with PaymentStrategy enum and PaymentConfig
- AbstractPaidAutomation extending AbstractAutomation with yield sufficiency
  checks and fee deduction helpers
- FixedFeePayment: constant fee per execution
- PercentagePayment: basis-points-based fee from yield
- 35 comprehensive tests with BTT naming covering fee calculation,
  yield sufficiency, edge cases, and integration
@RonTuretzky RonTuretzky force-pushed the RonTuretzky/73-automation-payment branch from 542f54b to 368f6e3 Compare April 16, 2026 01:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants