-
Notifications
You must be signed in to change notification settings - Fork 4
feat: implement generic automation payment framework (#73) #127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: RonTuretzky/116-sorted-queue
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.20; | ||
|
|
||
| import {AbstractAutomation} from "./AbstractAutomation.sol"; | ||
| import {IAutomationPayment} from "../interfaces/IAutomationPayment.sol"; | ||
|
|
||
| /// @title AbstractPaidAutomation | ||
| /// @notice Extends AbstractAutomation with payment validation for automation providers | ||
| /// @dev Adds yield sufficiency checks and fee deduction logic on top of base automation | ||
| abstract contract AbstractPaidAutomation is AbstractAutomation { | ||
| IAutomationPayment public immutable PAYMENT_PROVIDER; | ||
|
|
||
| /// @notice Emitted when an automation fee is deducted from yield | ||
| /// @param fee The fee amount deducted | ||
| /// @param remainingYield The yield remaining after fee deduction | ||
| event AutomationFeeDeducted(uint256 fee, uint256 remainingYield); | ||
|
|
||
| /// @notice Thrown when yield is insufficient to cover fees and minimum yield | ||
| error InsufficientYieldForFee(); | ||
|
|
||
| /// @param _distributionManager The distribution manager address | ||
| /// @param _paymentProvider The payment provider address | ||
| constructor(address _distributionManager, address _paymentProvider) AbstractAutomation(_distributionManager) { | ||
| require(_paymentProvider != address(0), "Invalid payment provider"); | ||
| PAYMENT_PROVIDER = IAutomationPayment(_paymentProvider); | ||
| } | ||
|
|
||
| /// @notice Checks if distribution is ready, including yield sufficiency for fees | ||
| /// @dev Extends base readiness check with payment validation | ||
| /// @return ready Whether the distribution is ready and yield is sufficient | ||
| function isDistributionReady() public view virtual override returns (bool ready) { | ||
| if (!super.isDistributionReady()) { | ||
| return false; | ||
| } | ||
| // Additional check: is yield sufficient to cover automation fees? | ||
| uint256 availableYield = _getAvailableYield(); | ||
| return PAYMENT_PROVIDER.isYieldSufficient(availableYield); | ||
| } | ||
|
|
||
| /// @notice Executes the distribution after verifying paid-automation readiness | ||
| /// @dev Gates on this contract's isDistributionReady() (which includes yield sufficiency) | ||
| /// before delegating to the parent, preventing bypass of fee checks | ||
| function executeDistribution() public virtual override { | ||
| if (!isDistributionReady()) revert NotResolved(); | ||
| super.executeDistribution(); | ||
| } | ||
|
|
||
| /// @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); | ||
| } | ||
|
Comment on lines
+48
to
+57
|
||
|
|
||
| /// @notice Returns the available yield for distribution | ||
| /// @dev Subclasses may override to query the actual yield source | ||
| /// @return yield The available yield amount | ||
| function _getAvailableYield() internal view virtual returns (uint256 yield); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.20; | ||
|
|
||
| import {IAutomationPayment} from "../../interfaces/IAutomationPayment.sol"; | ||
|
|
||
| /// @title FixedFeePayment | ||
| /// @notice Implements IAutomationPayment with a fixed fee per execution | ||
| /// @dev The fee is a constant amount regardless of total yield | ||
| contract FixedFeePayment is IAutomationPayment { | ||
| uint256 public immutable FEE_AMOUNT; | ||
| uint256 public immutable MINIMUM_YIELD; | ||
|
|
||
| /// @param _feeAmount The fixed fee amount charged per execution | ||
| /// @param _minimumYield The minimum yield required after fee deduction | ||
| constructor(uint256 _feeAmount, uint256 _minimumYield) { | ||
| FEE_AMOUNT = _feeAmount; | ||
| MINIMUM_YIELD = _minimumYield; | ||
| } | ||
|
|
||
| /// @inheritdoc IAutomationPayment | ||
| function calculateFee(uint256 /* totalYield */) external view override returns (uint256 fee) { | ||
| return FEE_AMOUNT; | ||
| } | ||
|
|
||
| /// @inheritdoc IAutomationPayment | ||
| function getPaymentConfig() external view override returns (PaymentConfig memory config) { | ||
| return PaymentConfig({strategy: PaymentStrategy.FIXED_FEE, feeAmount: FEE_AMOUNT, minimumYield: MINIMUM_YIELD}); | ||
| } | ||
|
|
||
| /// @inheritdoc IAutomationPayment | ||
| function isYieldSufficient(uint256 totalYield) external view override returns (bool sufficient) { | ||
| return totalYield >= FEE_AMOUNT && totalYield - FEE_AMOUNT >= MINIMUM_YIELD; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.20; | ||
|
|
||
| import {IAutomationPayment} from "../../interfaces/IAutomationPayment.sol"; | ||
|
|
||
| /// @title PercentagePayment | ||
| /// @notice Implements IAutomationPayment with a percentage-based fee | ||
| /// @dev Fee is calculated as a percentage of total yield using basis points (10000 = 100%) | ||
| contract PercentagePayment is IAutomationPayment { | ||
| uint256 public constant BASIS_POINTS_DENOMINATOR = 10_000; | ||
|
|
||
| uint256 public immutable BASIS_POINTS; | ||
| uint256 public immutable MINIMUM_YIELD; | ||
|
|
||
| /// @notice Thrown when basis points exceed 10000 (100%) | ||
| error InvalidBasisPoints(); | ||
|
|
||
| /// @param _basisPoints Fee percentage in basis points (e.g. 500 = 5%) | ||
| /// @param _minimumYield The minimum yield required after fee deduction | ||
| constructor(uint256 _basisPoints, uint256 _minimumYield) { | ||
| if (_basisPoints > BASIS_POINTS_DENOMINATOR) revert InvalidBasisPoints(); | ||
| BASIS_POINTS = _basisPoints; | ||
| MINIMUM_YIELD = _minimumYield; | ||
| } | ||
|
|
||
| /// @inheritdoc IAutomationPayment | ||
| /// @dev Uses division-first approach for large values to avoid overflow: | ||
| /// fee = (totalYield / DENOMINATOR) * BASIS_POINTS + (totalYield % DENOMINATOR) * BASIS_POINTS / DENOMINATOR | ||
| function calculateFee(uint256 totalYield) external view override returns (uint256 fee) { | ||
| return (totalYield / BASIS_POINTS_DENOMINATOR) * BASIS_POINTS | ||
| + (totalYield % BASIS_POINTS_DENOMINATOR) * BASIS_POINTS / BASIS_POINTS_DENOMINATOR; | ||
| } | ||
|
Comment on lines
+26
to
+32
|
||
|
|
||
| /// @inheritdoc IAutomationPayment | ||
| function getPaymentConfig() external view override returns (PaymentConfig memory config) { | ||
| return PaymentConfig({ | ||
| strategy: PaymentStrategy.PERCENTAGE_BASED, | ||
| feeAmount: BASIS_POINTS, | ||
| minimumYield: MINIMUM_YIELD | ||
| }); | ||
| } | ||
|
|
||
| /// @inheritdoc IAutomationPayment | ||
| function isYieldSufficient(uint256 totalYield) external view override returns (bool sufficient) { | ||
| uint256 fee = (totalYield / BASIS_POINTS_DENOMINATOR) * BASIS_POINTS | ||
| + (totalYield % BASIS_POINTS_DENOMINATOR) * BASIS_POINTS / BASIS_POINTS_DENOMINATOR; | ||
| if (fee > totalYield) return false; | ||
| uint256 remaining = totalYield - fee; | ||
| return remaining >= MINIMUM_YIELD; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.20; | ||
|
|
||
| /// @title IAutomationPayment | ||
| /// @notice Interface for automation payment strategies | ||
| /// @dev Implementations define how automation provider fees are calculated and validated | ||
| interface IAutomationPayment { | ||
| /// @notice Payment strategy type | ||
| enum PaymentStrategy { | ||
| FIXED_FEE, | ||
| PERCENTAGE_BASED | ||
| } | ||
|
|
||
| /// @notice Configuration for automation payments | ||
| /// @param strategy The payment strategy to use | ||
| /// @param feeAmount Fixed fee amount (for FIXED_FEE) or basis points (for PERCENTAGE_BASED) | ||
| /// @param minimumYield Minimum yield required after fee deduction | ||
| struct PaymentConfig { | ||
| PaymentStrategy strategy; | ||
| uint256 feeAmount; | ||
| uint256 minimumYield; | ||
| } | ||
|
|
||
| /// @notice Calculates the automation fee for a given yield amount | ||
| /// @param totalYield The total yield available for distribution | ||
| /// @return fee The fee amount to deduct | ||
| function calculateFee(uint256 totalYield) external view returns (uint256 fee); | ||
|
|
||
| /// @notice Returns the current payment configuration | ||
| /// @return config The payment configuration | ||
| function getPaymentConfig() external view returns (PaymentConfig memory config); | ||
|
|
||
| /// @notice Checks whether the yield is sufficient to cover fees and minimum yield | ||
| /// @param totalYield The total yield available | ||
| /// @return sufficient Whether the yield meets the minimum threshold after fees | ||
| function isYieldSufficient(uint256 totalYield) external view returns (bool sufficient); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AbstractAutomation.executeDistribution()checksDISTRIBUTION_MANAGER.isDistributionReady()directly, so callers can bypass the paid automation yield/fee gate implemented in this override.AbstractPaidAutomationshould overrideexecuteDistribution()to gate onisDistributionReady()(or explicitly re-checkPAYMENT_PROVIDER.isYieldSufficient(_getAvailableYield())) before callingclaimAndDistribute(), otherwise unprofitable distributions can still be forced by any caller.