Skip to content

Commit

Permalink
feat: rewards v2.1 (#1011)
Browse files Browse the repository at this point in the history
* feat: rewards v2.1

feat: operator centric rewards

feat: add new interfaces

feat(wip): implement `createOperatorSetPerformanceRewardsSubmission`

chore: forge fmt

fix: compile

feat(wip): implement `setOperatorSetOperatorSplit`

fix: review changes

fix: add missing `onlyWhenPaused` + `checkCanCall`

feat(wip): add missing `getOperatorSetPerformanceSplit` + rename internals

test(wip): `setOperatorSetPerformanceSplit`

test(wip): `createOperatorSetPerformanceRewardsSubmission`

- some failing

chore: forge fmt

refactor: renaming

chore: storage report

refactor: review changes

refactor: review changes

fix: gap

refactor: review changes

* fix: storage layout

* chore: make storage-report

* docs: natspec

* refactor: review changes

* refactor: review changes

* refactor: review changes

* feat: rewards v2.1 bindings

* fix: not index operatorSet in events

* refactor: forge fmt

* docs: updated storage reports

* chore: updated bindings

---------

Co-authored-by: 0xrajath <[email protected]>
  • Loading branch information
0xClandestine and 0xrajath authored Jan 29, 2025
1 parent b1b2bd6 commit 5341ef8
Show file tree
Hide file tree
Showing 10 changed files with 2,649 additions and 133 deletions.
118 changes: 61 additions & 57 deletions docs/storage-report/RewardsCoordinator.md

Large diffs are not rendered by default.

78 changes: 41 additions & 37 deletions docs/storage-report/RewardsCoordinatorStorage.md

Large diffs are not rendered by default.

394 changes: 393 additions & 1 deletion pkg/bindings/IRewardsCoordinator/binding.go

Large diffs are not rendered by default.

445 changes: 434 additions & 11 deletions pkg/bindings/RewardsCoordinator/binding.go

Large diffs are not rendered by default.

443 changes: 433 additions & 10 deletions pkg/bindings/RewardsCoordinatorStorage/binding.go

Large diffs are not rendered by default.

64 changes: 58 additions & 6 deletions src/contracts/core/RewardsCoordinator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ contract RewardsCoordinator is
PermissionControllerMixin
{
using SafeERC20 for IERC20;
using OperatorSetLib for OperatorSet;

modifier onlyRewardsUpdater() {
require(msg.sender == rewardsUpdater, UnauthorizedCaller());
Expand Down Expand Up @@ -177,6 +178,37 @@ contract RewardsCoordinator is
}
}

/// @inheritdoc IRewardsCoordinator
function createOperatorDirectedOperatorSetRewardsSubmission(
OperatorSet calldata operatorSet,
OperatorDirectedRewardsSubmission[] calldata operatorDirectedRewardsSubmissions
)
external
onlyWhenNotPaused(PAUSED_OPERATOR_DIRECTED_OPERATOR_SET_REWARDS_SUBMISSION)
checkCanCall(operatorSet.avs)
nonReentrant
{
require(allocationManager.isOperatorSet(operatorSet), InvalidOperatorSet());
for (uint256 i = 0; i < operatorDirectedRewardsSubmissions.length; i++) {
OperatorDirectedRewardsSubmission calldata operatorDirectedRewardsSubmission =
operatorDirectedRewardsSubmissions[i];
uint256 nonce = submissionNonce[operatorSet.avs];
bytes32 operatorDirectedRewardsSubmissionHash =
keccak256(abi.encode(operatorSet.avs, nonce, operatorDirectedRewardsSubmission));

uint256 totalAmount = _validateOperatorDirectedRewardsSubmission(operatorDirectedRewardsSubmission);

isOperatorDirectedOperatorSetRewardsSubmissionHash[operatorSet.avs][operatorDirectedRewardsSubmissionHash] =
true;
submissionNonce[operatorSet.avs] = nonce + 1;

emit OperatorDirectedOperatorSetRewardsSubmissionCreated(
msg.sender, operatorDirectedRewardsSubmissionHash, operatorSet, nonce, operatorDirectedRewardsSubmission
);
operatorDirectedRewardsSubmission.token.safeTransferFrom(msg.sender, address(this), totalAmount);
}
}

/// @inheritdoc IRewardsCoordinator
function processClaim(
RewardsMerkleClaim calldata claim,
Expand Down Expand Up @@ -268,8 +300,8 @@ contract RewardsCoordinator is
uint16 split
) external onlyWhenNotPaused(PAUSED_OPERATOR_AVS_SPLIT) checkCanCall(operator) {
uint32 activatedAt = uint32(block.timestamp) + activationDelay;
uint16 oldSplit = _getOperatorSplit(operatorAVSSplitBips[operator][avs]);
_setOperatorSplit(operatorAVSSplitBips[operator][avs], split, activatedAt);
uint16 oldSplit = _getOperatorSplit(_operatorAVSSplitBips[operator][avs]);
_setOperatorSplit(_operatorAVSSplitBips[operator][avs], split, activatedAt);

emit OperatorAVSSplitBipsSet(msg.sender, operator, avs, activatedAt, oldSplit, split);
}
Expand All @@ -280,12 +312,27 @@ contract RewardsCoordinator is
uint16 split
) external onlyWhenNotPaused(PAUSED_OPERATOR_PI_SPLIT) checkCanCall(operator) {
uint32 activatedAt = uint32(block.timestamp) + activationDelay;
uint16 oldSplit = _getOperatorSplit(operatorPISplitBips[operator]);
_setOperatorSplit(operatorPISplitBips[operator], split, activatedAt);
uint16 oldSplit = _getOperatorSplit(_operatorPISplitBips[operator]);
_setOperatorSplit(_operatorPISplitBips[operator], split, activatedAt);

emit OperatorPISplitBipsSet(msg.sender, operator, activatedAt, oldSplit, split);
}

/// @inheritdoc IRewardsCoordinator
function setOperatorSetSplit(
address operator,
OperatorSet calldata operatorSet,
uint16 split
) external onlyWhenNotPaused(PAUSED_OPERATOR_SET_SPLIT) checkCanCall(operator) {
require(allocationManager.isOperatorSet(operatorSet), InvalidOperatorSet());

uint32 activatedAt = uint32(block.timestamp) + activationDelay;
uint16 oldSplit = _getOperatorSplit(_operatorSetSplitBips[operator][operatorSet.key()]);
_setOperatorSplit(_operatorSetSplitBips[operator][operatorSet.key()], split, activatedAt);

emit OperatorSetSplitBipsSet(msg.sender, operator, operatorSet, activatedAt, oldSplit, split);
}

/// @inheritdoc IRewardsCoordinator
function setRewardsUpdater(
address _rewardsUpdater
Expand Down Expand Up @@ -602,14 +649,19 @@ contract RewardsCoordinator is

/// @inheritdoc IRewardsCoordinator
function getOperatorAVSSplit(address operator, address avs) external view returns (uint16) {
return _getOperatorSplit(operatorAVSSplitBips[operator][avs]);
return _getOperatorSplit(_operatorAVSSplitBips[operator][avs]);
}

/// @inheritdoc IRewardsCoordinator
function getOperatorPISplit(
address operator
) external view returns (uint16) {
return _getOperatorSplit(operatorPISplitBips[operator]);
return _getOperatorSplit(_operatorPISplitBips[operator]);
}

/// @inheritdoc IRewardsCoordinator
function getOperatorSetSplit(address operator, OperatorSet calldata operatorSet) external view returns (uint16) {
return _getOperatorSplit(_operatorSetSplitBips[operator][operatorSet.key()]);
}

/// @inheritdoc IRewardsCoordinator
Expand Down
27 changes: 19 additions & 8 deletions src/contracts/core/RewardsCoordinatorStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ abstract contract RewardsCoordinatorStorage is IRewardsCoordinator {
uint8 internal constant PAUSED_OPERATOR_AVS_SPLIT = 6;
/// @dev Index for flag that pauses calling setOperatorPISplit
uint8 internal constant PAUSED_OPERATOR_PI_SPLIT = 7;
/// @dev Index for flag that pauses calling setOperatorSetSplit
uint8 internal constant PAUSED_OPERATOR_SET_SPLIT = 8;
/// @dev Index for flag that pauses calling setOperatorSetPerformanceRewardsSubmission
uint8 internal constant PAUSED_OPERATOR_DIRECTED_OPERATOR_SET_REWARDS_SUBMISSION = 9;

/// @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 @@ -113,16 +117,23 @@ abstract contract RewardsCoordinatorStorage is IRewardsCoordinator {
/// @notice Returns whether a `hash` is a `valid` rewards submission for all earners hash for a given `avs`.
mapping(address avs => mapping(bytes32 hash => bool valid)) public isRewardsSubmissionForAllEarnersHash;

// Construction
/// @notice Returns whether a `hash` is a `valid` operator set performance rewards submission hash for a given `avs`.
mapping(address avs => mapping(bytes32 hash => bool valid)) public isOperatorDirectedAVSRewardsSubmissionHash;

/// @notice Returns the `split` an `operator` takes for an `avs`.
mapping(address operator => mapping(address avs => OperatorSplit split)) internal _operatorAVSSplitBips;

/// @notice Mapping: avs => operatorDirectedAVSRewardsSubmissionHash => bool to check if operator-directed rewards submission hash has been submitted
mapping(address => mapping(bytes32 => bool)) public isOperatorDirectedAVSRewardsSubmissionHash;
/// @notice Returns the `split` an `operator` takes for Programmatic Incentives.
mapping(address operator => OperatorSplit split) internal _operatorPISplitBips;

/// @notice Mapping: operator => avs => OperatorSplit. The split an operator takes for a specific AVS.
mapping(address => mapping(address => OperatorSplit)) internal operatorAVSSplitBips;
/// @notice Returns the `split` an `operator` takes for a given operator set.
mapping(address operator => mapping(bytes32 operatorSetKey => OperatorSplit split)) internal _operatorSetSplitBips;

/// @notice Mapping: operator => OperatorPISplit. The split an operator takes for Programmatic Incentives.
mapping(address => OperatorSplit) internal operatorPISplitBips;
/// @notice Returns whether a `hash` is a `valid` operator set performance rewards submission hash for a given `avs`.
mapping(address avs => mapping(bytes32 hash => bool valid)) public
isOperatorDirectedOperatorSetRewardsSubmissionHash;

// Construction

constructor(
IDelegationManager _delegationManager,
Expand Down Expand Up @@ -153,5 +164,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[37] private __gap;
uint256[35] private __gap;
}
69 changes: 69 additions & 0 deletions src/contracts/interfaces/IRewardsCoordinator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.27;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../libraries/OperatorSetLib.sol";
import "./IPauserRegistry.sol";
import "./IStrategy.sol";

Expand All @@ -27,6 +28,8 @@ interface IRewardsCoordinatorErrors {
error NewRootMustBeForNewCalculatedPeriod();
/// @dev Thrown when rewards end timestamp has not elapsed.
error RewardsEndTimestampNotElapsed();
/// @dev Thrown when an invalid operator set is provided.
error InvalidOperatorSet();

/// Rewards Submissions

Expand Down Expand Up @@ -277,6 +280,22 @@ interface IRewardsCoordinatorEvents is IRewardsCoordinatorTypes {
OperatorDirectedRewardsSubmission operatorDirectedRewardsSubmission
);

/**
* @notice Emitted when an AVS creates a valid `OperatorDirectedRewardsSubmission` for an operator set.
* @param caller The address calling `createOperatorDirectedOperatorSetRewardsSubmission`.
* @param operatorDirectedRewardsSubmissionHash Keccak256 hash of (`avs`, `submissionNonce` and `operatorDirectedRewardsSubmission`).
* @param operatorSet The operatorSet on behalf of which the operator-directed rewards are being submitted.
* @param submissionNonce Current nonce of the avs. Used to generate a unique submission hash.
* @param operatorDirectedRewardsSubmission The Operator-Directed Rewards Submission. Contains the token, start timestamp, duration, operator rewards, description and, strategy and multipliers.
*/
event OperatorDirectedOperatorSetRewardsSubmissionCreated(
address indexed caller,
bytes32 indexed operatorDirectedRewardsSubmissionHash,
OperatorSet operatorSet,
uint256 submissionNonce,
OperatorDirectedRewardsSubmission operatorDirectedRewardsSubmission
);

/// @notice rewardsUpdater is responsible for submiting DistributionRoots, only owner can set rewardsUpdater
event RewardsUpdaterSet(address indexed oldRewardsUpdater, address indexed newRewardsUpdater);

Expand Down Expand Up @@ -321,6 +340,24 @@ interface IRewardsCoordinatorEvents is IRewardsCoordinatorTypes {
uint16 newOperatorPISplitBips
);

/**
* @notice Emitted when the operator split for a given operatorSet is set.
* @param caller The address calling `setOperatorSetSplit`.
* @param operator The operator on behalf of which the split is being set.
* @param operatorSet The operatorSet for which the split is being set.
* @param activatedAt The timestamp at which the split will be activated.
* @param oldOperatorSetSplitBips The old split for the operator for the operatorSet.
* @param newOperatorSetSplitBips The new split for the operator for the operatorSet.
*/
event OperatorSetSplitBipsSet(
address indexed caller,
address indexed operator,
OperatorSet operatorSet,
uint32 activatedAt,
uint16 oldOperatorSetSplitBips,
uint16 newOperatorSetSplitBips
);

event ClaimerForSet(address indexed earner, address indexed oldClaimer, address indexed claimer);

/// @notice rootIndex is the specific array index of the newly created root in the storage array
Expand Down Expand Up @@ -420,6 +457,24 @@ interface IRewardsCoordinator is IRewardsCoordinatorErrors, IRewardsCoordinatorE
OperatorDirectedRewardsSubmission[] calldata operatorDirectedRewardsSubmissions
) external;

/**
* @notice Creates a new operator-directed rewards submission for an operator set, to be split amongst the operators and
* set of stakers delegated to operators who are part of the operator set.
* @param operatorSet The operator set for which the rewards are being submitted
* @param operatorDirectedRewardsSubmissions The operator-directed rewards submissions being created
* @dev Expected to be called by the AVS that created the operator set
* @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 `operatorDirectedRewardsSubmissions`, 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 `operatorDirectedRewardsSubmissions` is malformed
*/
function createOperatorDirectedOperatorSetRewardsSubmission(
OperatorSet calldata operatorSet,
OperatorDirectedRewardsSubmission[] calldata operatorDirectedRewardsSubmissions
) 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 Expand Up @@ -522,6 +577,17 @@ interface IRewardsCoordinator is IRewardsCoordinatorErrors, IRewardsCoordinatorE
*/
function setOperatorPISplit(address operator, uint16 split) external;

/**
* @notice Sets the split for a specific operator for a specific operatorSet.
* @param operator The operator who is setting the split.
* @param operatorSet The operatorSet for which the split is being set by the operator.
* @param split The split for the operator for the specific operatorSet in bips.
* @dev Only callable by the operator
* @dev Split has to be between 0 and 10000 bips (inclusive)
* @dev The split will be activated after the activation delay
*/
function setOperatorSetSplit(address operator, OperatorSet calldata operatorSet, uint16 split) external;

/**
* @notice Sets the permissioned `rewardsUpdater` address which can post new roots
* @dev Only callable by the contract owner
Expand Down Expand Up @@ -570,6 +636,9 @@ interface IRewardsCoordinator is IRewardsCoordinatorErrors, IRewardsCoordinatorE
address operator
) external view returns (uint16);

/// @notice Returns the split for a specific `operator` for a given `operatorSet`
function getOperatorSetSplit(address operator, OperatorSet calldata operatorSet) external view returns (uint16);

/// @notice return the hash of the earner's leaf
function calculateEarnerLeafHash(
EarnerTreeMerkleLeaf calldata leaf
Expand Down
15 changes: 13 additions & 2 deletions src/test/mocks/AllocationManagerMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,29 @@
pragma solidity ^0.8.9;

import "forge-std/Test.sol";
import "../../contracts/interfaces/IStrategy.sol";
import "../../contracts/libraries/Snapshots.sol";
import "src/contracts/interfaces/IStrategy.sol";
import "src/contracts/libraries/Snapshots.sol";
import "src/contracts/libraries/OperatorSetLib.sol";

contract AllocationManagerMock is Test {
using Snapshots for Snapshots.DefaultWadHistory;
using OperatorSetLib for OperatorSet;

receive() external payable {}
fallback() external payable {}

mapping(bytes32 operatorSetKey => bool) public _isOperatorSet;
mapping(address avs => uint256) public getOperatorSetCount;
mapping(address => mapping(IStrategy => Snapshots.DefaultWadHistory)) internal _maxMagnitudeHistory;

function setIsOperatorSet(OperatorSet memory operatorSet, bool boolean) external {
_isOperatorSet[operatorSet.key()] = boolean;
}

function isOperatorSet(OperatorSet memory operatorSet) external view returns (bool) {
return _isOperatorSet[operatorSet.key()];
}

function setMaxMagnitudes(
address operator,
IStrategy[] calldata strategies,
Expand Down
Loading

0 comments on commit 5341ef8

Please sign in to comment.