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.1 #1011

Merged
merged 12 commits into from
Jan 29, 2025
118 changes: 61 additions & 57 deletions docs/storage-report/RewardsCoordinator.md
0xrajath marked this conversation as resolved.
Show resolved Hide resolved

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.

61 changes: 55 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,34 @@ contract RewardsCoordinator is
}
}

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

uint256 totalAmount = _validateOperatorDirectedRewardsSubmission(rewardsSubmission);

isOperatorSetPerformanceRewardsSubmissionHash[operatorSet.avs][rewardsSubmissionHash] = true;
submissionNonce[operatorSet.avs] = nonce + 1;

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

/// @inheritdoc IRewardsCoordinator
function processClaim(
RewardsMerkleClaim calldata claim,
Expand Down Expand Up @@ -268,8 +297,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 +309,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_OPERATOR_SPLIT) checkCanCall(operator) {
require(allocationManager.isOperatorSet(operatorSet), InvalidOperatorSet());

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

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

/// @inheritdoc IRewardsCoordinator
function setRewardsUpdater(
address _rewardsUpdater
Expand Down Expand Up @@ -602,14 +646,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(_operatorOperatorSetSplitBips[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_OPERATOR_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
_operatorOperatorSetSplitBips;

/// @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 isOperatorSetPerformanceRewardsSubmissionHash;

// 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;
0xClandestine marked this conversation as resolved.
Show resolved Hide resolved
}
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 performance based `OperatorDirectedRewardsSubmission` for an operator set.
* @param caller The address calling `createOperatorDirectedOperatorSetRewardsSubmission`.
* @param operatorSet The operatorSet on behalf of which the performance rewards are being submitted.
* @param performanceRewardsSubmissionHash Keccak256 hash of (`avs`, `submissionNonce` and `performanceRewardsSubmission`).
* @param submissionNonce Current nonce of the avs. Used to generate a unique submission hash.
* @param performanceRewardsSubmission The Performance Rewards Submission. Contains the token, start timestamp, duration, description and, strategy and multipliers.
*/
event OperatorDirectedOperatorSetRewardsSubmissionCreated(
0xClandestine marked this conversation as resolved.
Show resolved Hide resolved
address indexed caller,
OperatorSet indexed operatorSet,
bytes32 indexed performanceRewardsSubmissionHash,
uint256 submissionNonce,
OperatorDirectedRewardsSubmission performanceRewardsSubmission
0xClandestine marked this conversation as resolved.
Show resolved Hide resolved
);
0xClandestine marked this conversation as resolved.
Show resolved Hide resolved

/// @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 OperatorOperatorSetSplitBipsSet(
0xClandestine marked this conversation as resolved.
Show resolved Hide resolved
address indexed caller,
address indexed operator,
OperatorSet indexed 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(
0xClandestine marked this conversation as resolved.
Show resolved Hide resolved
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
Loading