Othentic
  • Introduction
    • Introducing Othentic Stack
    • Use Cases
  • AVS Framework
    • Abstract
    • Quick Start
    • Othentic CLI
      • Key Management
      • Contracts Deployment
      • Operator Registration
      • AVS Logic Implementation
      • Operator Deposit
      • Node Operators
      • Rewards Distribution
      • P2P Config
        • Custom P2P Messaging
        • P2P Auth Layer
        • Metrics and Monitoring
        • Logging
        • Persistent storage
        • Latency
      • CLI Command Reference
    • Smart Contracts
      • AVS Governance
      • Attestation Center
      • Hooks
        • Task Logic
        • Operator Management
        • Rewards Fee Calculator
      • OBLS
      • Othentic Registry
      • Message Handlers
    • Othentic Consensus
      • Abstract
      • Task & Task Definitions
      • Leader Election
      • Proof of Task
      • Execution Service
      • Validation Service
      • Voting Power
      • Rewards and Penalties
      • Internal Tasks
    • FAQ
    • Supported Networks
    • Explainers
      • Networking
      • Multichain
      • Production Guidelines
      • Operator Allowlisting
      • Governance Multisig
  • External
    • AVS Examples
  • GitHub
  • Othentic Hub
Powered by GitBook
On this page
  • Overview
  • Interface
  • Deployment and setup
  • Use cases
  • Dynamic Fee Adjustment Based on Voting Power
  1. AVS Framework
  2. Smart Contracts
  3. Hooks

Rewards Fee Calculator

PreviousOperator ManagementNextOBLS

Last updated 1 month ago

Overview

The Othentic Stack enables developers to create a FeeCalculator contract, which functions as a hook for calculating task submission rewards on-chain, 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);
    }
}

Deployment and setup

After AVS implementation, you can deploy the contract on the same L2 network as the AttestationCenter.

You should deploy the AVS contracts before deploying the IFeeCalculator.

Note that only the AVS Governance Multisig is authorized to call this function.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

/*______     __      __                              __      __ 
 /      \   /  |    /  |                            /  |    /  |
/$$$$$$  | _$$ |_   $$ |____    ______   _______   _$$ |_   $$/   _______ 
$$ |  $$ |/ $$   |  $$      \  /      \ /       \ / $$   |  /  | /       |
$$ |  $$ |$$$$$$/   $$$$$$$  |/$$$$$$  |$$$$$$$  |$$$$$$/   $$ |/$$$$$$$/ 
$$ |  $$ |  $$ | __ $$ |  $$ |$$    $$ |$$ |  $$ |  $$ | __ $$ |$$ |
$$ \__$$ |  $$ |/  |$$ |  $$ |$$$$$$$$/ $$ |  $$ |  $$ |/  |$$ |$$ \_____ 
$$    $$/   $$  $$/ $$ |  $$ |$$       |$$ |  $$ |  $$  $$/ $$ |$$       |
 $$$$$$/     $$$$/  $$/   $$/  $$$$$$$/ $$/   $$/    $$$$/  $$/  $$$$$$$/
*/
/**
 * @author Othentic Labs LTD.
 * @notice Terms of Service: https://www.othentic.xyz/terms-of-service
 */

import {Script, console} from "forge-std/Script.sol";
import "../src/IAttestationCenter.sol";
import "../src/FeeCalculator.sol";

// forge script FeeCalculatorScript --rpc-url $L2_RPC --private-key $PRIVATE_KEY
// --broadcast -vvvv --verify --etherscan-api-key $L2_ETHERSCAN_API_KEY --chain
// $L2_CHAIN --verifier-url $L2_VERIFIER_URL --sig="run(address)" $ATTESTATION_CENTER_ADDRESS
contract FeeCalculatorScript is Script {
    function setUp() public {}

    function run(address attestationCenter) public {
        vm.broadcast();
        FeeCalculator feeCalculator = new FeeCalculator(attestationCenter);
        IAttestationCenter(attestationCenter).setFeeCalculator(address(feeCalculator));
    }
}

Now, the contracts are set and ready to be used.


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;
  }

After deployment, you must call the function in the AttestationCenter and provide the address of the FeeCalculator contract as the parameter, as shown in the example below.

setFeeCalculator
Full Implementation
Fee Calculator Hook