Rewards Fee Calculator

Overview

The Othentic Stack enables developers to create a FeeCalculator contract, which functions as a hook for configuring rewards, offering flexible and customizable logic tailored to specific needs.

Interface

The interface defines two functions: calculateBaseRewardFees and calculateFeesPerId. You should select one of these options.

// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.25;

/*______     __      __                              __      __ 
 /      \   /  |    /  |                            /  |    /  |
/$$$$$$  | _$$ |_   $$ |____    ______   _______   _$$ |_   $$/   _______ 
$$ |  $$ |/ $$   |  $$      \  /      \ /       \ / $$   |  /  | /       |
$$ |  $$ |$$$$$$/   $$$$$$$  |/$$$$$$  |$$$$$$$  |$$$$$$/   $$ |/$$$$$$$/ 
$$ |  $$ |  $$ | __ $$ |  $$ |$$    $$ |$$ |  $$ |  $$ | __ $$ |$$ |
$$ \__$$ |  $$ |/  |$$ |  $$ |$$$$$$$$/ $$ |  $$ |  $$ |/  |$$ |$$ \_____ 
$$    $$/   $$  $$/ $$ |  $$ |$$       |$$ |  $$ |  $$  $$/ $$ |$$       |
 $$$$$$/     $$$$/  $$/   $$/  $$$$$$$/ $$/   $$/    $$$$/  $$/  $$$$$$$/
*/

/**
 * @author Othentic Labs LTD.
 * @notice Terms of Service: https://www.othentic.xyz/terms-of-service
 * @notice Depending on the application, it may be necessary to add reentrancy gaurds to hooks
 */
 
import { IAttestationCenter } from "@othentic/NetworkManagement/L2/interfaces/IAttestationCenter.sol";

interface IFeeCalculator {

    struct FeeCalculatorData {
        IAttestationCenter.TaskInfo data;
        uint256 aggregatorId;
        uint256 performerId;
        uint256[] attestersIds;
    }

    struct FeePerId {
        uint256 index;
        uint256 fee;
    }

    function calculateBaseRewardFees(FeeCalculatorData calldata _feeCalculatorData) external returns (uint256 baseRewardFeeForAttesters, uint256 baseRewardFeeForAggregator, uint256 baseRewardFeeForPerformer);
    function calculateFeesPerId(FeeCalculatorData calldata _feeCalculatorData) external returns (FeePerId[] memory feesPerId);
    function isBaseRewardFee() external pure returns (bool);
}

These functions are invoked within the AttestationCenter contract as part of the submitTask function by the Task Aggregator.

calculateBaseRewardFees function is called in the _calculateBaseRewardFees function.

function _calculateBaseRewardFees(AttestationCenterStorageData storage _sd, uint16 _taskDefinitionId, TaskDefinition memory _taskDefinition, IFeeCalculator.FeeCalculatorData memory _feeCalculatorData) private returns (uint256 _baseRewardFeeForAttesters, uint256 _baseRewardFeeForPerformer, uint256 _baseRewardFeeForAggregator) {
    IFeeCalculator _feeCalculator = _sd.feeCalculator;
    bool _isFeeCalculatorSet = address(_feeCalculator) != address(0);
    if(_isFeeCalculatorSet){
        (_baseRewardFeeForAttesters, 
        _baseRewardFeeForPerformer, 
        _baseRewardFeeForAggregator) = _feeCalculator.calculateBaseRewardFees(_feeCalculatorData);
    }
    ...
}

calculateFeesPerId is called in the _submitTaskBusinessLogic function.

function _submitTaskBusinessLogic(AttestationCenterStorageData storage _sd, TaskInfo calldata _taskInfo, TaskDefinition memory _taskDefinition, bool _isApproved, uint256[] memory _attestersIds) private {
    ...
    IFeeCalculator _feeCalculator = _sd.feeCalculator;
    bool _isFeeCalculatorSet = address(_feeCalculator) != address(0);
    if (!_isFeeCalculatorSet || (_isFeeCalculatorSet && _feeCalculator.isBaseRewardFee())) {
        ...
    }
    else {
        IFeeCalculator.FeePerId[] memory _feesPerId = _feeCalculator.calculateFeesPerId(_feeCalculatorData);
        _accumulateRewardsForOperatorsById(_sd, _feesPerId, _taskNumber);
    }
}

Use cases

Dynamic Fee Adjustment Based on Voting Power

The FeeCalculator hook can be used to distribute rewards among Operators based on their voting power. You can also enforce below depending on the usecase.

  • Skipping Operators: Operators below a predefined threshold (e.g., ID < 6) can be excluded.

  • Enforcing a Maximum Reward Cap: Ensures that total allocated rewards never exceed the predefined reward per task.

  • On-Chain Calculation: Provides transparency and efficiency.

  function calculateFeesPerId(
    FeeCalculatorData calldata _feeCalculatorData
  ) external view returns (FeePerId[] memory feesPerId) {
    uint256 attestersLen = _feeCalculatorData.attestersIds.length;
    uint256 rewardsCount = 0;

    for (uint256 i = 0; i < attestersLen; ) {
      uint256 attesterId = _feeCalculatorData.attestersIds[i];
      if (!shouldSkipAttester(attesterId)) {
        unchecked {
          rewardsCount++;
        }
      }
      unchecked {
        i++;
      }
    }

    FeePerId[] memory result = new FeePerId[](rewardsCount);
    IOBLS oblsContract = getOblsContract();
    uint256 rewardPerTask = getRewardPerTask();
    uint256 totalVotingPower = oblsContract.totalVotingPower();
    uint256 resultIdx = 0;
    uint256 totalReward = 0;

    for (uint256 i = 0; i < attestersLen; ) {
      uint256 attesterId = _feeCalculatorData.attestersIds[i];
      unchecked {
        i++;
      }
      if (shouldSkipAttester(attesterId)) {
        continue;
      }
      uint256 attesterVotingPower = oblsContract.votingPower(attesterId);
      uint256 reward = (rewardPerTask * attesterVotingPower) / totalVotingPower;
      totalReward += reward;
      result[resultIdx] = FeePerId(attesterId, reward);
      unchecked {
        resultIdx++;
      }
    }
    if (totalReward > rewardPerTask) {
      revert MaxRewardExceeded(totalReward);
    }
    return result;
  }

Full Implementation

Last updated