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