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

feat: Rewards v2 #837

wants to merge 9 commits into from

Conversation

0xrajath
Copy link

@0xrajath 0xrajath commented Oct 16, 2024

Motivation

As part of the Rewards v2 release, we need to support the following features:

  1. Performance-based Rewards submission by AVSs.
  2. Batching claiming by users to be more gas efficient.
  3. Variable Operator commission per AVS (Settable by the operator with a 7-day delay. Default of 10%)

Modifications

  • IRewardsCoordinator :

    • PerformanceRewardsSubmission struct added.
    • OperatorReward struct added.
    • AVSPerformanceRewardsSubmissionCreated event added.
    • createAVSPerformanceRewardsSubmission external function added.
    • processClaims external function added.
    • OperatorAVSCommission struct added.
    • OperatorAVSCommissionBipsSet event added.
    • setOperatorAVSCommission external function added.
  • RewardsCoordinator :

    • PAUSED_AVS_PERFORMANCE_REWARDS_SUBMISSION constant added,
    • createAVSPerformanceRewardsSubmission external function added.
    • _validatePerformanceRewardsSubmission internal function added.
    • processClaims external function added.
    • _processClaim internal function added.
    • processClaim external function was refactored to use _processClaim
    • setOperatorAVSCommission external function added.
  • RewardsCoordinatorStorage :

    • isAVSPerformanceRewardsSubmissionHash nested mapping added.
    • operatorAVSCommissionBips nested mapping added.
    • Updated storage gap from 40 to 38 slots to accomodate isAVSPerformanceRewardsSubmissionHash and operatorAVSCommissionBips

TODO

  1. Test Cases.
  2. Update operatorCommissionBips view function to have logic for operatorAVSCommission too.
  3. 7 day delay in setOperatorAVSCommission.
  4. Add a check to ensure that the operator is registered to the AVS in setOperatorAVSCommission.
  5. Update createAVSPerformanceRewardsSubmission and related stuff to be delegation-aware and future-proofed by taking avs as a param.

Open Questions

  1. Do we need delegation-aware interfaces for these new setters in this release?
  2. Do we need variable Operator commission for all Programmatic Incentives (Settable by the operator with a 7-day delay. Default of 10%)?
  3. Do we need to support Operator-set commissions in this release or be forward compatible for it (What does that entail for the setters and getters. Eg: Operator set ID, etc.)?
  4. Do we need Operator key rotation in this release (To give time for Operators to rotate before slashing)?

Result

Rewards v2 release.

@0xrajath 0xrajath added the enhancement New feature or request label Oct 16, 2024
@0xrajath 0xrajath self-assigned this Oct 16, 2024
lib/forge-std Outdated
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).

Comment on lines +123 to +125
function createAVSRewardsSubmission(
RewardsSubmission[] calldata rewardsSubmissions
) external onlyWhenNotPaused(PAUSED_AVS_REWARDS_SUBMISSION) nonReentrant {
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.

* @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.

Comment on lines +92 to +93
/// @notice Mapping: avs => avsPerformanceRewardsSubmissionHash => bool to check if performance rewards submission hash has been submitted
mapping(address => mapping(bytes32 => bool)) public isAVSPerformanceRewardsSubmissionHash;
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

@@ -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

Comment on lines +219 to +221
function processClaims(
RewardsMerkleClaim[] calldata claims,
address recipient
Copy link
Author

Choose a reason for hiding this comment

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

We could also claim for multiple recipients instead of claiming to a single recipient. Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

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

single recipient seems fine

* @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.

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.

@ypatil12 ypatil12 self-requested a review October 17, 2024 02:34

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

@0xrajath 0xrajath changed the title feat: Rewards V2 feat: Rewards v2 Oct 17, 2024
Comment on lines +378 to +379
currOperatorAddress < operatorReward.operator,
"RewardsCoordinator._validatePerformanceRewardsSubmission: operators must be in ascending order to handle duplicates"

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.

Comment on lines +415 to +418
require(
strategyManager.strategyIsWhitelistedForDeposit(strategy) || strategy == beaconChainETHStrategy,
"RewardsCoordinator._validatePerformanceRewardsSubmission: invalid strategy considered"
);

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(
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

* <--------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)

Comment on lines +415 to +418
require(
strategyManager.strategyIsWhitelistedForDeposit(strategy) || strategy == beaconChainETHStrategy,
"RewardsCoordinator._validatePerformanceRewardsSubmission: invalid strategy considered"
);
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

@@ -57,7 +57,7 @@ abstract contract RewardsCoordinatorStorage is IRewardsCoordinator {
*/
DistributionRoot[] internal _distributionRoots;

/// Slot 3
/// Slot 2
Copy link
Author

Choose a reason for hiding this comment

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

Updated the comment here. It is in fact Slot 2 since we start from Slot 0 (rather than Slot 1)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants