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: operator set rewards #353

Open
wants to merge 5 commits into
base: feat/slashing-release-branch
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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/eigenlayer-contracts
37 changes: 26 additions & 11 deletions src/ServiceManagerBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {Initializable} from "@openzeppelin-upgrades/contracts/proxy/utils/Initia
import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol";
import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol";
import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol";
import {IRewardsCoordinator} from
import {IRewardsCoordinator, IERC20, OperatorSet} from
"eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol";
import {IAllocationManager, IAllocationManagerTypes} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";

Expand Down Expand Up @@ -97,6 +97,10 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage {
_allocationManager.slashOperator(address(this), params);
}

function _increaseAllowance(IERC20 token, address spender, uint256 amount) internal {
token.approve(spender, amount + token.allowance(address(this), spender));
}

/**
* @notice Creates a new rewards submission to the EigenLayer RewardsCoordinator contract, to be split amongst the
* set of stakers delegated to operators who are registered to this `avs`
Expand All @@ -112,21 +116,32 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage {
IRewardsCoordinator.RewardsSubmission[] calldata rewardsSubmissions
) public virtual onlyRewardsInitiator {
for (uint256 i = 0; i < rewardsSubmissions.length; ++i) {
// transfer token to ServiceManager and approve RewardsCoordinator to transfer again
// in createAVSRewardsSubmission() call
rewardsSubmissions[i].token.transferFrom(
msg.sender, address(this), rewardsSubmissions[i].amount
);
uint256 allowance =
rewardsSubmissions[i].token.allowance(address(this), address(_rewardsCoordinator));
rewardsSubmissions[i].token.approve(
address(_rewardsCoordinator), rewardsSubmissions[i].amount + allowance
);
IRewardsCoordinator.RewardsSubmission calldata submission = rewardsSubmissions[i];

submission.token.transferFrom(msg.sender, address(this), submission.amount);

_increaseAllowance(submission.token, address(_rewardsCoordinator), submission.amount);
}

_rewardsCoordinator.createAVSRewardsSubmission(rewardsSubmissions);
}

function createOperatorDirectedOperatorSetRewardsSubmission(
OperatorSet memory operatorSet,
IRewardsCoordinator.OperatorDirectedRewardsSubmission[] calldata rewardsSubmissions,
uint256[] memory totalAmounts
) public virtual onlyRewardsInitiator {
for (uint256 i = 0; i < rewardsSubmissions.length; ++i) {
IRewardsCoordinator.OperatorDirectedRewardsSubmission calldata submission = rewardsSubmissions[i];

submission.token.transferFrom(msg.sender, address(this), totalAmounts[i]);

_increaseAllowance(submission.token, address(_rewardsCoordinator), totalAmounts[i]);
}

_rewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission(operatorSet, rewardsSubmissions);
}

function createOperatorSets(IAllocationManager.CreateSetParams[] memory params) external onlyRegistryCoordinator {
_allocationManager.createOperatorSets(address(this), params);
}
Expand Down
2 changes: 1 addition & 1 deletion test/integration/User.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ contract User is Test {
params[0] = IDelegationManagerTypes.QueuedWithdrawalParams({
strategies: strategies,
depositShares: shares,
withdrawer: address(this)
__deprecated_withdrawer: address(this)
});

delegationManager.queueWithdrawals(params);
Expand Down
20 changes: 18 additions & 2 deletions test/mocks/AllocationManagerMock.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;

import {IAllocationManager, OperatorSet} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
import {IAllocationManager, OperatorSet } from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
import {OperatorSetLib} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol";

import {IAVSRegistrar } from "eigenlayer-contracts/src/contracts/interfaces/IAVSRegistrar.sol";
import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol";
import {IPauserRegistry} from "eigenlayer-contracts/src/contracts/interfaces/IPauserRegistry.sol";
contract AllocationManagerIntermediate is IAllocationManager {
using OperatorSetLib for OperatorSet;


mapping(bytes32 => bool) internal _isOperatorSet;

function initialize(
address initialOwner,
uint256 initialPausedStatus
Expand Down Expand Up @@ -139,7 +146,16 @@ contract AllocationManagerIntermediate is IAllocationManager {

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

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

function getMembers(
OperatorSet memory operatorSet
Expand Down
26 changes: 26 additions & 0 deletions test/mocks/DelegationMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ contract DelegationIntermediate is IDelegationManager {
uint256 initialPausedStatus
) external virtual {}

function convertToDepositShares(
address staker,
IStrategy[] memory strategies,
uint256[] memory withdrawableShares
) external view returns (uint256[] memory) {}


function registerAsOperator(
OperatorDetails calldata registeringOperatorDetails,
uint32 allocationDelay,
Expand Down Expand Up @@ -239,6 +246,25 @@ contract DelegationIntermediate is IDelegationManager {
) external virtual {}

function minWithdrawalDelayBlocks() external view virtual override returns (uint32) {}

/// @notice Returns the Withdrawal associated with a `withdrawalRoot`, if it exists. NOTE that
/// withdrawals queued before the slashing release can NOT be queried with this method.
function getQueuedWithdrawal(
bytes32 withdrawalRoot
) external virtual override view returns (Withdrawal memory) {}

/// @notice Returns a list of queued withdrawal roots for the `staker`.
/// NOTE that this only returns withdrawals queued AFTER the slashing release.
function getQueuedWithdrawalRoots(
address staker
) external virtual override view returns (bytes32[] memory) {}

function slashOperatorShares(
address operator,
IStrategy strategy,
uint64 prevMaxMagnitude,
uint64 newMaxMagnitude
) external virtual override {}
}

contract DelegationMock is DelegationIntermediate {
Expand Down
4 changes: 4 additions & 0 deletions test/mocks/EigenPodManagerMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ contract EigenPodManagerMock is Test, Pausable, IEigenPodManager {
_setPausedStatus(0);
}

function burnableETHShares() external view returns (uint256) {}

function increaseBurnableShares(IStrategy strategy, uint256 addedSharesToBurn) external {}

function podOwnerShares(address podOwner) external view returns (int256) {
return podShares[podOwner];
}
Expand Down
12 changes: 10 additions & 2 deletions test/mocks/RewardsCoordinatorMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,18 @@ contract RewardsCoordinatorMock is IRewardsCoordinator {

function createOperatorDirectedAVSRewardsSubmission(
address avs,
OperatorDirectedRewardsSubmission[]
calldata operatorDirectedRewardsSubmissions
OperatorDirectedRewardsSubmission[] calldata operatorDirectedRewardsSubmissions
) external override {}

function createOperatorDirectedOperatorSetRewardsSubmission(
OperatorSet calldata operatorSet,
OperatorDirectedRewardsSubmission[] calldata operatorDirectedRewardsSubmissions
) external override {}

function getOperatorSetSplit(address operator, OperatorSet calldata operatorSet) external override view returns (uint16) {}

function setOperatorSetSplit(address operator, OperatorSet calldata operatorSet, uint16 split) external override {}

function processClaim(
RewardsMerkleClaim calldata claim,
address recipient
Expand Down
146 changes: 123 additions & 23 deletions test/unit/ServiceManagerBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol";
import {
RewardsCoordinator,
IRewardsCoordinator,
IRewardsCoordinatorErrors,
IRewardsCoordinatorTypes,
OperatorSet,
IERC20
} from "eigenlayer-contracts/src/contracts/core/RewardsCoordinator.sol";
import {IAllocationManagerTypes} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
import {PermissionController} from "eigenlayer-contracts/src/contracts/permissions/PermissionController.sol";
import {StrategyBase} from "eigenlayer-contracts/src/contracts/strategies/StrategyBase.sol";
import {IStrategyManager} from "eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol";
Expand Down Expand Up @@ -40,8 +43,9 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve
IStrategy strategyMock1;
IStrategy strategyMock2;
IStrategy strategyMock3;
IStrategy[] strategies;
StrategyBase strategyImplementation;
IRewardsCoordinator.StrategyAndMultiplier[] defaultStrategyAndMultipliers;
IRewardsCoordinatorTypes.StrategyAndMultiplier[] defaultStrategyAndMultipliers;

// mapping to setting fuzzed inputs
mapping(address => bool) public addressIsExcludedFromFuzzedInputs;
Expand Down Expand Up @@ -177,11 +181,14 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve
)
)
);
IStrategy[] memory strategies = new IStrategy[](3);
strategies[0] = strategyMock1;
strategies[1] = strategyMock2;
strategies[2] = strategyMock3;
strategies = _sortArrayAsc(strategies);

IStrategy[] memory strats = new IStrategy[](3);
strats[0] = strategyMock1;
strats[1] = strategyMock2;
strats[2] = strategyMock3;
strats = _sortArrayAsc(strats);

strategies = strats;

strategyManagerMock.setStrategyWhitelist(strategies[0], true);
strategyManagerMock.setStrategyWhitelist(strategies[1], true);
Expand Down Expand Up @@ -217,12 +224,29 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve
return timestamp1 > timestamp2 ? timestamp1 : timestamp2;
}

// function test_setRewardsInitiator() public {
Copy link
Author

Choose a reason for hiding this comment

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

TODO: uncomment

// address newRewardsInitiator = address(uint160(uint256(keccak256("newRewardsInitiator"))));
// cheats.prank(serviceManagerOwner);
// serviceManager.setRewardsInitiator(newRewardsInitiator);
// assertEq(newRewardsInitiator, serviceManager.rewardsInitiator());
// }

// function test_setRewardsInitiator_revert_notOwner() public {
// address caller = address(uint160(uint256(keccak256("caller"))));
// address newRewardsInitiator = address(uint160(uint256(keccak256("newRewardsInitiator"))));
// cheats.expectRevert("Ownable: caller is not the owner");
// cheats.prank(caller);
// serviceManager.setRewardsInitiator(newRewardsInitiator);
// }
}

contract ServiceManagerBase_createAVSRewardsSubmission_UnitTests is ServiceManagerBase_UnitTests {
function testFuzz_createAVSRewardsSubmission_Revert_WhenNotOwner(address caller)
public
filterFuzzedAddressInputs(caller)
{
cheats.assume(caller != rewardsInitiator);
IRewardsCoordinator.RewardsSubmission[] memory rewardsSubmissions;
IRewardsCoordinatorTypes.RewardsSubmission[] memory rewardsSubmissions;

cheats.prank(caller);
cheats.expectRevert(
Expand Down Expand Up @@ -337,8 +361,8 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve
cheats.assume(2 <= numSubmissions && numSubmissions <= 10);
cheats.prank(rewardsCoordinator.owner());

IRewardsCoordinator.RewardsSubmission[] memory rewardsSubmissions =
new IRewardsCoordinator.RewardsSubmission[](numSubmissions);
IRewardsCoordinatorTypes.RewardsSubmission[] memory rewardsSubmissions =
new IRewardsCoordinatorTypes.RewardsSubmission[](numSubmissions);
bytes32[] memory avsSubmissionHashes = new bytes32[](numSubmissions);
uint256 startSubmissionNonce = rewardsCoordinator.submissionNonce(address(serviceManager));
_deployMockRewardTokens(rewardsInitiator, numSubmissions);
Expand Down Expand Up @@ -430,8 +454,8 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve
cheats.assume(2 <= numSubmissions && numSubmissions <= 10);
cheats.prank(rewardsCoordinator.owner());

IRewardsCoordinator.RewardsSubmission[] memory rewardsSubmissions =
new IRewardsCoordinator.RewardsSubmission[](numSubmissions);
IRewardsCoordinatorTypes.RewardsSubmission[] memory rewardsSubmissions =
new IRewardsCoordinatorTypes.RewardsSubmission[](numSubmissions);
bytes32[] memory avsSubmissionHashes = new bytes32[](numSubmissions);
uint256 startSubmissionNonce = rewardsCoordinator.submissionNonce(address(serviceManager));
IERC20 rewardToken = new ERC20PresetFixedSupply(
Expand Down Expand Up @@ -518,19 +542,95 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve
);
}
}
}

function test_setRewardsInitiator() public {
address newRewardsInitiator = address(uint160(uint256(keccak256("newRewardsInitiator"))));
cheats.prank(serviceManagerOwner);
serviceManager.setRewardsInitiator(newRewardsInitiator);
assertEq(newRewardsInitiator, serviceManager.rewardsInitiator());
}
contract ServiceManagerBase_createOperatorDirectedOperatorSetRewardsSubmission_UnitTests is
ServiceManagerBase_UnitTests
{
OperatorSet operatorSet = OperatorSet(address(this), 1);

function testFuzz_createOperatorDirectedOperatorSetRewardsSubmission_Revert_WhenNotOwner(
address caller
) public filterFuzzedAddressInputs(caller) {
cheats.assume(caller != rewardsInitiator);

function test_setRewardsInitiator_revert_notOwner() public {
address caller = address(uint160(uint256(keccak256("caller"))));
address newRewardsInitiator = address(uint160(uint256(keccak256("newRewardsInitiator"))));
cheats.expectRevert("Ownable: caller is not the owner");
cheats.prank(caller);
serviceManager.setRewardsInitiator(newRewardsInitiator);
cheats.expectRevert(
"ServiceManagerBase.onlyRewardsInitiator: caller is not the rewards initiator"
);
serviceManager.createOperatorDirectedOperatorSetRewardsSubmission(
operatorSet,
new IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission[](0),
new uint256[](0)
);
}

function testFuzz_createOperatorDirectedOperatorSetRewardsSubmission_Revert_InvalidOperatorSet() public {
permissionControllerMock.setCanCall({
account: address(this),
caller: address(serviceManager),
target: address(rewardsCoordinator),
selector: IRewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission.selector
});

cheats.prank(rewardsInitiator);
cheats.expectRevert(IRewardsCoordinatorErrors.InvalidOperatorSet.selector);
serviceManager.createOperatorDirectedOperatorSetRewardsSubmission(
operatorSet, new IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission[](0), new uint256[](0)
);
}

function testFuzz_createOperatorDirectedOperatorSetRewardsSubmission_Correctness() public {
vm.warp(block.timestamp - (block.timestamp % CALCULATION_INTERVAL_SECONDS));
allocationManagerMock.setIsOperatorSet(operatorSet, true);

uint256 numSubmissions = cheats.randomUint(1, 10);
uint256 maxOperatorsPerSubmission = cheats.randomUint(1, 32);

_deployMockRewardTokens(rewardsInitiator, numSubmissions);

IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission[] memory rewardsSubmissions =
new IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission[](numSubmissions);
uint256[] memory totalAmounts = new uint256[](numSubmissions);

for (uint256 i = 0; i < numSubmissions; ++i) {
uint256 numOperators = cheats.randomUint(1, maxOperatorsPerSubmission);
IRewardsCoordinatorTypes.OperatorReward[] memory operatorRewards =
new IRewardsCoordinatorTypes.OperatorReward[](numOperators);
uint256 totalAmount = 0;

for (uint256 j = 0; j < numOperators; ++j) {
address operator = address(uint160(j + 1));
uint256 amount = cheats.randomUint(1 ether, 100 ether);
operatorRewards[j] =
IRewardsCoordinatorTypes.OperatorReward({operator: operator, amount: amount});
totalAmount += amount;
}

rewardsSubmissions[i] = IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission({
strategiesAndMultipliers: defaultStrategyAndMultipliers,
token: rewardTokens[i % rewardTokens.length],
operatorRewards: operatorRewards,
startTimestamp: uint32(block.timestamp - CALCULATION_INTERVAL_SECONDS),
duration: uint32(CALCULATION_INTERVAL_SECONDS),
description: string.concat("Test submission #", cheats.toString(i))
});

totalAmounts[i] = totalAmount;
}

vm.warp(block.timestamp + 1);

permissionControllerMock.setCanCall({
account: address(this),
caller: address(serviceManager),
target: address(rewardsCoordinator),
selector: IRewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission.selector
});

cheats.prank(rewardsInitiator);
serviceManager.createOperatorDirectedOperatorSetRewardsSubmission(
operatorSet, rewardsSubmissions, totalAmounts
);
}
}
}
Loading