Skip to content

Commit

Permalink
feat: rewards v2.1
Browse files Browse the repository at this point in the history
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
  • Loading branch information
0xClandestine committed Jan 14, 2025
1 parent 3802dec commit 6ab18c0
Show file tree
Hide file tree
Showing 7 changed files with 1,356 additions and 114 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.

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
29 changes: 20 additions & 9 deletions src/contracts/core/RewardsCoordinatorStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@ abstract contract RewardsCoordinatorStorage is IRewardsCoordinator {
uint8 internal constant PAUSED_REWARD_ALL_STAKERS_AND_OPERATORS = 4;
/// @dev Index for flag that pauses calling createOperatorDirectedAVSRewardsSubmission
uint8 internal constant PAUSED_OPERATOR_DIRECTED_AVS_REWARDS_SUBMISSION = 5;
/// @dev Index for flag that pauses calling setOperatorSetPerformanceRewardsSubmission
uint8 internal constant PAUSED_OPERATOR_DIRECTED_OPERATOR_SET_REWARDS_SUBMISSION = 6;
/// @dev Index for flag that pauses calling setOperatorAVSSplit
uint8 internal constant PAUSED_OPERATOR_AVS_SPLIT = 6;
uint8 internal constant PAUSED_OPERATOR_AVS_SPLIT = 7;
/// @dev Index for flag that pauses calling setOperatorPISplit
uint8 internal constant PAUSED_OPERATOR_PI_SPLIT = 7;
uint8 internal constant PAUSED_OPERATOR_PI_SPLIT = 8;
/// @dev Index for flag that pauses calling setOperatorSetSplit
uint8 internal constant PAUSED_OPERATOR_SET_OPERATOR_SPLIT = 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 @@ -115,14 +119,21 @@ abstract contract RewardsCoordinatorStorage is IRewardsCoordinator {

// Construction

/// @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 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 Mapping: operator => avs => OperatorSplit. The split an operator takes for a specific AVS.
mapping(address => mapping(address => OperatorSplit)) internal operatorAVSSplitBips;
/// @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;

/// @notice Mapping: operator => OperatorPISplit. The split an operator takes for Programmatic Incentives.
mapping(address => OperatorSplit) internal operatorPISplitBips;
/// @notice Returns the `split` an `operator` takes for an `avs`.
mapping(address operator => mapping(address avs => OperatorSplit split)) internal _operatorAVSSplitBips;

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

/// @notice Returns the `split` an `operator` takes for a given operator set.
mapping(address operator => mapping(bytes32 operatorSetKey => OperatorSplit split)) internal
_operatorOperatorSetSplitBips;

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;
}
55 changes: 55 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`
* @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(
address indexed caller,
OperatorSet indexed operatorSet,
bytes32 indexed performanceRewardsSubmissionHash,
uint256 submissionNonce,
OperatorDirectedRewardsSubmission performanceRewardsSubmission
);

/// @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(
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,13 @@ interface IRewardsCoordinator is IRewardsCoordinatorErrors, IRewardsCoordinatorE
OperatorDirectedRewardsSubmission[] calldata operatorDirectedRewardsSubmissions
) external;

/// @notice operatorSet parallel of createAVSPerformanceRewardsSubmission
/// @dev sender must be the avs of the given operatorSet
function createOperatorDirectedOperatorSetRewardsSubmission(
OperatorSet calldata operatorSet,
OperatorDirectedRewardsSubmission[] 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 Expand Up @@ -522,6 +566,14 @@ 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.
*/
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 +622,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 6ab18c0

Please sign in to comment.