Skip to content
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

feat: Rewards v2 #837

Draft
wants to merge 9 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/forge-std
Copy link
Author

Choose a reason for hiding this comment

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

Will fix this. Something happened while rebasing.

Copy link
Author

Choose a reason for hiding this comment

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

This file was touched since dev wasn't compiling and I manually made it _verifyContractsInitialized(false);. But now I need to figure out what's up with my linting. Either these existing files haven't been linted using prettier or we're currently not using forge fmt (My IDE is auto linting them on save).

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import "./Deploy_Test_RewardsCoordinator.s.sol";
* anvil --fork-url $RPC_HOLESKY
*
* Holesky testnet: Deploy/Upgrade RewardsCoordinator
* forge script script/deploy/holesky/upgrade_preprod_rewardsCoordinator.s.sol --rpc-url $RPC_HOLESKY --private-key $PRIVATE_KEY --broadcast -vvvv
* forge script script/deploy/holesky/upgrade_preprod_rewardsCoordinator.s.sol --rpc-url $RPC_HOLESKY --private-key $PRIVATE_KEY --broadcast -vvvv
*
*/
contract Upgrade_Preprod_RewardsCoordinator is Deploy_Test_RewardsCoordinator {
Expand Down Expand Up @@ -39,6 +39,5 @@ contract Upgrade_Preprod_RewardsCoordinator is Deploy_Test_RewardsCoordinator {
_verifyImplementations();
_verifyContractsInitialized(false);
_verifyInitializationParams();

}
}
146 changes: 123 additions & 23 deletions src/contracts/core/RewardsCoordinator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ contract RewardsCoordinator is
uint8 internal constant PAUSED_SUBMIT_DISABLE_ROOTS = 3;
/// @dev Index for flag that pauses calling rewardAllStakersAndOperators
uint8 internal constant PAUSED_REWARD_ALL_STAKERS_AND_OPERATORS = 4;
/// @dev Index for flag that pauses calling createAVSPerformanceRewardsSubmission
uint8 internal constant PAUSED_AVS_PERFORMANCE_REWARDS_SUBMISSION = 5;

/// @dev Salt for the earner leaf, meant to distinguish from tokenLeaf since they have the same sized data
uint8 internal constant EARNER_LEAF_SALT = 0;
Expand Down Expand Up @@ -118,11 +120,9 @@ contract RewardsCoordinator is
*/

/// @inheritdoc IRewardsCoordinator
function createAVSRewardsSubmission(RewardsSubmission[] calldata rewardsSubmissions)
external
onlyWhenNotPaused(PAUSED_AVS_REWARDS_SUBMISSION)
nonReentrant
{
function createAVSRewardsSubmission(
RewardsSubmission[] calldata rewardsSubmissions
) external onlyWhenNotPaused(PAUSED_AVS_REWARDS_SUBMISSION) nonReentrant {
Comment on lines +123 to +125
Copy link
Author

Choose a reason for hiding this comment

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

This was changed due to auto linting.

Copy link
Author

Choose a reason for hiding this comment

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

Multiple other auto-linting instances in this PR.

for (uint256 i = 0; i < rewardsSubmissions.length; i++) {
RewardsSubmission calldata rewardsSubmission = rewardsSubmissions[i];
uint256 nonce = submissionNonce[msg.sender];
Expand All @@ -139,12 +139,9 @@ contract RewardsCoordinator is
}

/// @inheritdoc IRewardsCoordinator
function createRewardsForAllSubmission(RewardsSubmission[] calldata rewardsSubmissions)
external
onlyWhenNotPaused(PAUSED_REWARDS_FOR_ALL_SUBMISSION)
onlyRewardsForAllSubmitter
nonReentrant
{
function createRewardsForAllSubmission(
RewardsSubmission[] calldata rewardsSubmissions
) external onlyWhenNotPaused(PAUSED_REWARDS_FOR_ALL_SUBMISSION) onlyRewardsForAllSubmitter nonReentrant {
for (uint256 i = 0; i < rewardsSubmissions.length; i++) {
RewardsSubmission calldata rewardsSubmission = rewardsSubmissions[i];
uint256 nonce = submissionNonce[msg.sender];
Expand All @@ -161,12 +158,9 @@ contract RewardsCoordinator is
}

/// @inheritdoc IRewardsCoordinator
function createRewardsForAllEarners(RewardsSubmission[] calldata rewardsSubmissions)
external
onlyWhenNotPaused(PAUSED_REWARD_ALL_STAKERS_AND_OPERATORS)
onlyRewardsForAllSubmitter
nonReentrant
{
function createRewardsForAllEarners(
RewardsSubmission[] calldata rewardsSubmissions
) external onlyWhenNotPaused(PAUSED_REWARD_ALL_STAKERS_AND_OPERATORS) onlyRewardsForAllSubmitter nonReentrant {
for (uint256 i = 0; i < rewardsSubmissions.length; i++) {
RewardsSubmission calldata rewardsSubmission = rewardsSubmissions[i];
uint256 nonce = submissionNonce[msg.sender];
Expand All @@ -178,12 +172,41 @@ contract RewardsCoordinator is
submissionNonce[msg.sender] = nonce + 1;

emit RewardsSubmissionForAllEarnersCreated(
msg.sender, nonce, rewardsSubmissionForAllEarnersHash, rewardsSubmission
msg.sender,
nonce,
rewardsSubmissionForAllEarnersHash,
rewardsSubmission
);
rewardsSubmission.token.safeTransferFrom(msg.sender, address(this), rewardsSubmission.amount);
}
}

/// @inheritdoc IRewardsCoordinator
function createAVSPerformanceRewardsSubmission(
PerformanceRewardsSubmission[] calldata performanceRewardsSubmissions
) external onlyWhenNotPaused(PAUSED_AVS_PERFORMANCE_REWARDS_SUBMISSION) nonReentrant {
for (uint256 i = 0; i < performanceRewardsSubmissions.length; i++) {
PerformanceRewardsSubmission calldata performanceRewardsSubmission = performanceRewardsSubmissions[i];
uint256 nonce = submissionNonce[msg.sender];
bytes32 performanceRewardsSubmissionHash = keccak256(
abi.encode(msg.sender, nonce, performanceRewardsSubmission)
);

uint256 totalAmount = _validatePerformanceRewardsSubmission(performanceRewardsSubmission);

isAVSPerformanceRewardsSubmissionHash[msg.sender][performanceRewardsSubmissionHash] = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

do we need to check that it already exists

Copy link
Author

@0xrajath 0xrajath Oct 17, 2024

Choose a reason for hiding this comment

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

The nonce keeps monotonically increasing. So it can theoretically never exist already. That was the point of the nonce here.

Copy link
Contributor

Choose a reason for hiding this comment

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

ah, sounds good

submissionNonce[msg.sender] = nonce + 1;

emit AVSPerformanceRewardsSubmissionCreated(
msg.sender,
nonce,
performanceRewardsSubmissionHash,
performanceRewardsSubmission
);
performanceRewardsSubmission.token.safeTransferFrom(msg.sender, address(this), totalAmount);
}
}

/// @inheritdoc IRewardsCoordinator
function processClaim(
RewardsMerkleClaim calldata claim,
Expand Down Expand Up @@ -315,8 +338,8 @@ contract RewardsCoordinator is
"RewardsCoordinator._validateRewardsSubmission: startTimestamp must be a multiple of CALCULATION_INTERVAL_SECONDS"
);
require(
block.timestamp - MAX_RETROACTIVE_LENGTH <= rewardsSubmission.startTimestamp
&& GENESIS_REWARDS_TIMESTAMP <= rewardsSubmission.startTimestamp,
block.timestamp - MAX_RETROACTIVE_LENGTH <= rewardsSubmission.startTimestamp &&
GENESIS_REWARDS_TIMESTAMP <= rewardsSubmission.startTimestamp,
"RewardsCoordinator._validateRewardsSubmission: startTimestamp too far in the past"
);
require(
Expand All @@ -340,6 +363,83 @@ contract RewardsCoordinator is
}
}

/**
* @notice Validate a PerformanceRewardsSubmission. Called from `createAVSPerformanceRewardsSubmission`.
* @param performanceRewardsSubmission PerformanceRewardsSubmission to validate.
* @return total amount to be transferred from the avs to the contract.
*/
function _validatePerformanceRewardsSubmission(
Copy link
Author

Choose a reason for hiding this comment

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

Not checking for MAX_FUTURE_LENGTH (Since Performance based reward submissions are retroactive) or MAX_REWARDS_AMOUNT (Since we no longer have the 1e38 - 1 limitation in the offchain calculation.

Copy link
Contributor

Choose a reason for hiding this comment

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

could u add this in code comments?

Copy link
Author

Choose a reason for hiding this comment

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

Done in 179aa1f as a @dev natspec.

Copy link
Author

Choose a reason for hiding this comment

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

Thoughts on using revert with custom errors? Or just custom errors in general instead of error strings? I kept it similar to the current setup but using custom errors will be more gas efficient.

Copy link
Contributor

Choose a reason for hiding this comment

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

let's do that with slashing like we are on #727

Copy link
Author

Choose a reason for hiding this comment

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

Alright holding off on custom errors for now.

PerformanceRewardsSubmission calldata performanceRewardsSubmission
) internal view returns (uint256) {
require(
performanceRewardsSubmission.strategiesAndMultipliers.length > 0,
"RewardsCoordinator._validatePerformanceRewardsSubmission: no strategies set"
);
require(
performanceRewardsSubmission.operatorRewards.length > 0,
"RewardsCoordinator._validatePerformanceRewardsSubmission: no operators rewarded"
);

uint256 totalAmount = 0;
address currOperatorAddress = address(0);
for (uint256 i = 0; i < performanceRewardsSubmission.operatorRewards.length; ++i) {
OperatorReward calldata operatorReward = performanceRewardsSubmission.operatorRewards[i];
require(
operatorReward.operator != address(0),
"RewardsCoordinator._validatePerformanceRewardsSubmission: operator cannot be 0 address"
);
require(
currOperatorAddress < operatorReward.operator,
"RewardsCoordinator._validatePerformanceRewardsSubmission: operators must be in ascending order to handle duplicates"
Comment on lines +396 to +397

Choose a reason for hiding this comment

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

Is this a more gas efficient way of not having a temporary storage slot? Seems rather restrictive on the client but not seemingly impossible I suppose.

Choose a reason for hiding this comment

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

Also, who cares if there are duplicates? Is it up to us to infer their intention? Are there any side effects of allowing duplicates?

Copy link
Author

Choose a reason for hiding this comment

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

Is your question: why are we doing this check? If that's the case we're doing it so that the AVS can't pass in duplicate operator addresses into the reward submission, and this is the cheapest way we can check that without doing any searching of the array.

The cli/sdk can easily sort it into this order and warn if there are duplicates before the transaction is submitted onchain.

Copy link
Author

Choose a reason for hiding this comment

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

We do this duplicate check for the Rewards v1 submission. So I'm assuming it's some constraint on the pipeline and to make some calculation easier (I'm just speculating, but I could be wrong - Need to check this assumption).

But besides that why would an AVS need to pay the same operator 2 different amounts in the same reward submission? This seems like a nice constraint to have.

);
currOperatorAddress = operatorReward.operator;
require(
operatorReward.amount > 0,
"RewardsCoordinator._validatePerformanceRewardsSubmission: operator reward amount cannot be 0"
);
Copy link
Collaborator

Choose a reason for hiding this comment

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

What are the gas costs for 200,500,1000 operators?

Copy link
Author

Choose a reason for hiding this comment

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

Is that question wrt to this check or in general how much it would cost if 200,500,1000 operators were passed in calldata?

Copy link
Collaborator

Choose a reason for hiding this comment

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

The latter

totalAmount += operatorReward.amount;
}

require(
performanceRewardsSubmission.duration <= MAX_REWARDS_DURATION,
"RewardsCoordinator._validatePerformanceRewardsSubmission: duration exceeds MAX_REWARDS_DURATION"
);
require(
performanceRewardsSubmission.duration % CALCULATION_INTERVAL_SECONDS == 0,
"RewardsCoordinator._validatePerformanceRewardsSubmission: duration must be a multiple of CALCULATION_INTERVAL_SECONDS"
);
require(
performanceRewardsSubmission.startTimestamp % CALCULATION_INTERVAL_SECONDS == 0,
"RewardsCoordinator._validatePerformanceRewardsSubmission: startTimestamp must be a multiple of CALCULATION_INTERVAL_SECONDS"
);
require(
block.timestamp - MAX_RETROACTIVE_LENGTH <= performanceRewardsSubmission.startTimestamp &&
GENESIS_REWARDS_TIMESTAMP <= performanceRewardsSubmission.startTimestamp,
"RewardsCoordinator._validatePerformanceRewardsSubmission: startTimestamp too far in the past"
);
require(
performanceRewardsSubmission.startTimestamp + performanceRewardsSubmission.duration < block.timestamp,
"RewardsCoordinator._validatePerformanceRewardsSubmission: performance rewards submission is not retroactive"
);

// Require performanceRewardsSubmission is for whitelisted strategy or beaconChainETHStrategy
address currAddress = address(0);
for (uint256 i = 0; i < performanceRewardsSubmission.strategiesAndMultipliers.length; ++i) {
IStrategy strategy = performanceRewardsSubmission.strategiesAndMultipliers[i].strategy;
require(
strategyManager.strategyIsWhitelistedForDeposit(strategy) || strategy == beaconChainETHStrategy,
"RewardsCoordinator._validatePerformanceRewardsSubmission: invalid strategy considered"
);
Comment on lines +433 to +436

Choose a reason for hiding this comment

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

does this propertly support longtail assets?

Copy link
Author

Choose a reason for hiding this comment

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

Good question. It was something I had noted down myself to check.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Rewards works out of the box for longtail assets without overflow since we limit the number of shares that can be minted for any strategy

require(
currAddress < address(strategy),
"RewardsCoordinator._validatePerformanceRewardsSubmission: strategies must be in ascending order to handle duplicates"
);
currAddress = address(strategy);
}

return totalAmount;
}

function _checkClaim(RewardsMerkleClaim calldata claim, DistributionRoot memory root) internal view {
require(!root.disabled, "RewardsCoordinator._checkClaim: root is disabled");
require(block.timestamp >= root.activatedAt, "RewardsCoordinator._checkClaim: root not activated yet");
Expand Down Expand Up @@ -430,9 +530,9 @@ contract RewardsCoordinator is
// forgefmt: disable-next-item
require(
Merkle.verifyInclusionKeccak({
root: root,
index: earnerLeafIndex,
proof: earnerProof,
root: root,
index: earnerLeafIndex,
proof: earnerProof,
leaf: earnerLeafHash
}),
"RewardsCoordinator._verifyEarnerClaimProof: invalid earner claim proof"
Expand Down
5 changes: 4 additions & 1 deletion src/contracts/core/RewardsCoordinatorStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ abstract contract RewardsCoordinatorStorage is IRewardsCoordinator {
/// if rewards submission hash for all stakers and operators has been submitted
mapping(address => mapping(bytes32 => bool)) public isRewardsSubmissionForAllEarnersHash;

/// @notice Mapping: avs => avsPerformanceRewardsSubmissionHash => bool to check if performance rewards submission hash has been submitted
mapping(address => mapping(bytes32 => bool)) public isAVSPerformanceRewardsSubmissionHash;
Comment on lines +92 to +93
Copy link
Author

Choose a reason for hiding this comment

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

Was there a reason we needed separate mappings for each reward type? I just copied the current setup but questioning the need for it here.

Copy link
Contributor

Choose a reason for hiding this comment

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

let's combine into one, agreed

Copy link
Author

Choose a reason for hiding this comment

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

I'm assuming this is used by the rewards pipeline/sidecar in some form. I don't want to break backwards compatibility if that's the case.

Copy link
Contributor

Choose a reason for hiding this comment

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

@ypatil12 @8sunyuan may have opinions

Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be fine since we include the nonce in the rewards submission hash. cc @8sunyuan to work

Copy link
Collaborator

Choose a reason for hiding this comment

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

Internally we're not using this mapping offchain

Copy link
Author

@0xrajath 0xrajath Oct 17, 2024

Choose a reason for hiding this comment

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

Oh if it's not being used offchain, what was the purpose of this mapping? I don't see it being used anywhere else besides just setting it during reward submission.

Is it meant to be used as a view function by some other contract in the future?

Copy link
Collaborator

Choose a reason for hiding this comment

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

@8sunyuan do you remember? I can't recall off the top of my head

Copy link
Collaborator

Choose a reason for hiding this comment

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

It appears it may actually have no use... potentially we wanted it for some sort of future pipeline integration


constructor(
IDelegationManager _delegationManager,
IStrategyManager _strategyManager,
Expand Down Expand Up @@ -120,5 +123,5 @@ abstract contract RewardsCoordinatorStorage is IRewardsCoordinator {
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[40] private __gap;
uint256[39] private __gap;
Copy link
Author

Choose a reason for hiding this comment

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

I've updated this since we added the mapping above. Just wanted to double confirm that this looks right. I remember seeing a PR a while back changing this to 40.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yup, this is correct

}
73 changes: 72 additions & 1 deletion src/contracts/interfaces/IRewardsCoordinator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ interface IRewardsCoordinator {
uint96 multiplier;
}

/**
* @notice A reward struct for an operator
* @param operator The operator to be rewarded
* @param amount The reward amount for the operator
*/
struct OperatorReward {
address operator;
uint256 amount;
}

/**
* Sliding Window for valid RewardsSubmission startTimestamp
*
Expand Down Expand Up @@ -60,6 +70,42 @@ interface IRewardsCoordinator {
uint32 duration;
}

/**
* Sliding Window for valid RewardsSubmission startTimestamp
*
* Scenario A: GENESIS_REWARDS_TIMESTAMP IS WITHIN RANGE
* <--------MAX_RETROACTIVE_LENGTH--------> t (block.timestamp)
* <--valid range for startTimestamp--
* ^
* GENESIS_REWARDS_TIMESTAMP
*
*
* Scenario B: GENESIS_REWARDS_TIMESTAMP IS OUT OF RANGE
* <--------MAX_RETROACTIVE_LENGTH--------> t (block.timestamp)
* <----valid range for startTimestamp----
* ^
* GENESIS_REWARDS_TIMESTAMP
Copy link
Collaborator

Choose a reason for hiding this comment

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

GENESIS_REWARDS_TIMESTAMP is never in range?

Copy link
Author

Choose a reason for hiding this comment

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

What do you mean?

Copy link
Collaborator

Choose a reason for hiding this comment

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

For the performance based rewards it's not possible to submit a reward that starts at GENESIS_REWARDS_TIMESTAMP (ie. scenario B)

*
* @notice PerformanceRewardsSubmission struct submitted by AVSs when making performance-based rewards for their operators and stakers
* PerformanceRewardsSubmission can be for a time range within the valid window for startTimestamp and must be within max duration.
* See `createAVSPerformanceRewardsSubmission()` for more details.
* @param strategiesAndMultipliers The strategies and their relative weights
* cannot have duplicate strategies and need to be sorted in ascending address order
* @param token The rewards token to be distributed.
* @param operatorRewards The rewards for the operators. The sum of all operator rewards equals the total amount of tokens deposited in the submission.
* The operators cannot have duplicate addresses and need to be sorted in ascending address order.
* @param startTimestamp The timestamp (seconds) at which the submission range is considered for distribution
* It is a retroactive payment and has to be strictly less than `block.timestamp`. See the diagram above.
* @param duration The duration of the submission range in seconds. Must be <= MAX_REWARDS_DURATION.
*/
struct PerformanceRewardsSubmission {
StrategyAndMultiplier[] strategiesAndMultipliers;
IERC20 token;
OperatorReward[] operatorRewards;
uint32 startTimestamp;
uint32 duration;
}

/**
* @notice A distribution root is a merkle root of the distribution of earnings for a given period.
* The RewardsCoordinator stores all historical distribution roots so that earners can claim their earnings against older roots
Expand Down Expand Up @@ -150,10 +196,19 @@ interface IRewardsCoordinator {
bytes32 indexed rewardsSubmissionHash,
RewardsSubmission rewardsSubmission
);
/// @notice emitted when an AVS creates a valid PerformanceRewardsSubmission
event AVSPerformanceRewardsSubmissionCreated(
address indexed avs,
uint256 indexed submissionNonce,
bytes32 indexed performanceRewardsSubmissionHash,
PerformanceRewardsSubmission performanceRewardsSubmission
);
/// @notice rewardsUpdater is responsible for submiting DistributionRoots, only owner can set rewardsUpdater
event RewardsUpdaterSet(address indexed oldRewardsUpdater, address indexed newRewardsUpdater);
event RewardsForAllSubmitterSet(
address indexed rewardsForAllSubmitter, bool indexed oldValue, bool indexed newValue
address indexed rewardsForAllSubmitter,
bool indexed oldValue,
bool indexed newValue
);
event ActivationDelaySet(uint32 oldActivationDelay, uint32 newActivationDelay);
event GlobalCommissionBipsSet(uint16 oldGlobalCommissionBips, uint16 newGlobalCommissionBips);
Expand Down Expand Up @@ -284,6 +339,22 @@ interface IRewardsCoordinator {
*/
function createRewardsForAllEarners(RewardsSubmission[] calldata rewardsSubmissions) external;

/**
* @notice Creates a new performance-based rewards submission on behalf of an AVS, to be split amongst the operators and
* set of stakers delegated to operators who are registered to the `avs`.
* @param performanceRewardsSubmissions The performance rewards submissions being created
* @dev Expected to be called by the ServiceManager of the AVS on behalf of which the submission is being made
* @dev The duration of the `rewardsSubmission` cannot exceed `MAX_REWARDS_DURATION`
* @dev The tokens are sent to the `RewardsCoordinator` contract
* @dev The `RewardsCoordinator` contract needs a token approval of sum of all `operatorRewards` in the `performanceRewardsSubmissions`, before calling this function.
* @dev Strategies must be in ascending order of addresses to check for duplicates
* @dev Operators must be in ascending order of addresses to check for duplicates.
* @dev This function will revert if the `performanceRewardsSubmissions` is malformed.
*/
function createAVSPerformanceRewardsSubmission(
PerformanceRewardsSubmission[] calldata performanceRewardsSubmissions
) external;

/**
* @notice Claim rewards against a given root (read from _distributionRoots[claim.rootIndex]).
* Earnings are cumulative so earners don't have to claim against all distribution roots they have earnings for,
Expand Down
Loading