Skip to content

Commit b7a78d0

Browse files
feat(incentives): add protocol fee (#1691)
**Motivation:** We want to add a configurable protocol fee mechanism to the RewardsCoordinator contract to enable revenue generation. This allows reward submitters to opt into paying fees while maintaining backward compatibility with existing integrations. **Modifications:** - Added protocolFee (fixed constant amount) and feeRecipient state variables - Implemented setOptInProtocolFee() for submitters to opt into fee payments - Implemented setFeeRecipient() (owner-only) to set the fee destination address **Result:** Submitters can voluntarily enable protocol fees on their reward distributions. When opted in, the fixed fee amount is deducted and sent to the designated recipient. Existing integrations continue working unchanged since fees are disabled by default.
1 parent c946cdc commit b7a78d0

File tree

12 files changed

+759
-83
lines changed

12 files changed

+759
-83
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ artifacts
1717
#Foundry files
1818
out
1919
cache
20+
snapshots
2021

2122
*.DS_Store
2223

script/deploy/devnet/deploy_from_scratch.s.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,8 @@ contract DeployFromScratch is Script, Test {
312312
REWARDS_COORDINATOR_INIT_PAUSED_STATUS,
313313
REWARDS_COORDINATOR_UPDATER,
314314
REWARDS_COORDINATOR_ACTIVATION_DELAY,
315-
REWARDS_COORDINATOR_GLOBAL_OPERATOR_COMMISSION_BIPS
315+
REWARDS_COORDINATOR_GLOBAL_OPERATOR_COMMISSION_BIPS,
316+
executorMultisig // feeRecipient
316317
)
317318
);
318319

script/deploy/local/deploy_from_scratch.slashing.s.sol

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,9 +219,6 @@ contract DeployFromScratch is Script, Test {
219219
allocationManager = AllocationManager(
220220
address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
221221
);
222-
allocationManagerView = AllocationManagerView(
223-
address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
224-
);
225222
permissionController = PermissionController(
226223
address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
227224
);
@@ -237,6 +234,9 @@ contract DeployFromScratch is Script, Test {
237234
eigenPodBeacon = new UpgradeableBeacon(address(eigenPodImplementation));
238235

239236
// Second, deploy the *implementation* contracts, using the *proxy contracts* as inputs
237+
// Deploy AllocationManagerView as a standalone implementation (not a proxy)
238+
allocationManagerView =
239+
new AllocationManagerView(delegation, eigenStrategy, DEALLOCATION_DELAY, ALLOCATION_CONFIGURATION_DELAY);
240240

241241
delegationImplementation = new DelegationManager(
242242
strategyManager,
@@ -327,7 +327,8 @@ contract DeployFromScratch is Script, Test {
327327
REWARDS_COORDINATOR_INIT_PAUSED_STATUS,
328328
REWARDS_COORDINATOR_UPDATER,
329329
REWARDS_COORDINATOR_ACTIVATION_DELAY,
330-
REWARDS_COORDINATOR_DEFAULT_OPERATOR_SPLIT_BIPS
330+
REWARDS_COORDINATOR_DEFAULT_OPERATOR_SPLIT_BIPS,
331+
executorMultisig // feeRecipient
331332
)
332333
);
333334

script/releases/TestUtils.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -926,7 +926,7 @@ library TestUtils {
926926
RewardsCoordinator rewardsCoordinator
927927
) internal {
928928
vm.expectRevert(errInit);
929-
rewardsCoordinator.initialize(address(0), 0, address(0), 0, 0);
929+
rewardsCoordinator.initialize(address(0), 0, address(0), 0, 0, address(0));
930930
}
931931

932932
function validateStrategyManagerInitialized(

script/utils/ExistingDeploymentParser.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,8 @@ contract ExistingDeploymentParser is Script, Logger {
522522
0, // initialPausedStatus
523523
address(0), // rewardsUpdater
524524
0, // activationDelay
525-
0 // defaultSplitBips
525+
0, // defaultSplitBips
526+
address(0) // feeRecipient
526527
);
527528
// DelegationManager
528529
cheats.expectRevert(bytes("Initializable: contract is already initialized"));

snapshots/Integration_ALM_Multi.json

Lines changed: 0 additions & 3 deletions
This file was deleted.

src/contracts/core/EmissionsController.sol

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,6 @@ contract EmissionsController is
202202
success = _tryCallRewardsCoordinator(
203203
abi.encodeCall(IRewardsCoordinator.createAVSRewardsSubmission, (rewardsSubmissions))
204204
);
205-
} else {
206-
revert InvalidDistributionType(); // Only reachable if the distribution type is `Disabled`.
207205
}
208206
} else {
209207
(success,) =

src/contracts/core/RewardsCoordinator.sol

Lines changed: 179 additions & 49 deletions
Large diffs are not rendered by default.

src/contracts/core/storage/RewardsCoordinatorStorage.sol

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ abstract contract RewardsCoordinatorStorage is IRewardsCoordinator {
4848
/// @notice Canonical, virtual beacon chain ETH strategy
4949
IStrategy public constant beaconChainETHStrategy = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0);
5050

51+
/// @notice Protocol fee percentage in basis points (20%).
52+
uint16 internal constant PROTOCOL_FEE_BIPS = 2000;
53+
5154
// Immutables
5255

5356
/// @notice The DelegationManager contract for EigenLayer
@@ -136,6 +139,12 @@ abstract contract RewardsCoordinatorStorage is IRewardsCoordinator {
136139
/// @notice Returns whether a `hash` is a `valid` total stake rewards submission hash for a given `avs`.
137140
mapping(address avs => mapping(bytes32 hash => bool valid)) public isTotalStakeRewardsSubmissionHash;
138141

142+
/// @notice Returns whether a `submitter` is opted in for protocol fees.
143+
mapping(address submitter => bool isOptedIn) public isOptedInForProtocolFee;
144+
145+
/// @notice The address that receives optional protocol fees
146+
address public feeRecipient;
147+
139148
// Construction
140149
constructor(
141150
IDelegationManager _delegationManager,
@@ -164,5 +173,5 @@ abstract contract RewardsCoordinatorStorage is IRewardsCoordinator {
164173
/// @dev This empty reserved space is put in place to allow future versions to add new
165174
/// variables without shifting down storage in the inheritance chain.
166175
/// See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
167-
uint256[33] private __gap;
176+
uint256[31] private __gap;
168177
}

src/contracts/interfaces/IRewardsCoordinator.sol

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,17 @@ interface IRewardsCoordinatorEvents is IRewardsCoordinatorTypes {
414414
IERC20 token,
415415
uint256 claimedAmount
416416
);
417+
418+
/// @notice Emitted when the fee recipient is set.
419+
/// @param oldFeeRecipient The old fee recipient
420+
/// @param newFeeRecipient The new fee recipient
421+
event FeeRecipientSet(address indexed oldFeeRecipient, address indexed newFeeRecipient);
422+
423+
/// @notice Emitted when the opt in for protocol fee is set.
424+
/// @param submitter The address of the submitter
425+
/// @param oldValue The old value of the opt in for protocol fee
426+
/// @param newValue The new value of the opt in for protocol fee
427+
event OptInForProtocolFeeSet(address indexed submitter, bool indexed oldValue, bool indexed newValue);
417428
}
418429

419430
/// @title Interface for the `IRewardsCoordinator` contract.
@@ -431,7 +442,8 @@ interface IRewardsCoordinator is IRewardsCoordinatorErrors, IRewardsCoordinatorE
431442
uint256 initialPausedStatus,
432443
address _rewardsUpdater,
433444
uint32 _activationDelay,
434-
uint16 _defaultSplitBips
445+
uint16 _defaultSplitBips,
446+
address _feeRecipient
435447
) external;
436448

437449
/// @notice Creates a new rewards submission on behalf of an AVS, to be split amongst the
@@ -606,6 +618,13 @@ interface IRewardsCoordinator is IRewardsCoordinatorErrors, IRewardsCoordinatorE
606618
uint16 split
607619
) external;
608620

621+
/// @notice Sets the fee recipient address which receives optional protocol fees
622+
/// @dev Only callable by the contract owner
623+
/// @param _feeRecipient The address of the new fee recipient
624+
function setFeeRecipient(
625+
address _feeRecipient
626+
) external;
627+
609628
/// @notice Sets the split for a specific operator for a specific avs
610629
/// @param operator The operator who is setting the split
611630
/// @param avs The avs for which the split is being set by the operator
@@ -643,6 +662,15 @@ interface IRewardsCoordinator is IRewardsCoordinatorErrors, IRewardsCoordinatorE
643662
uint16 split
644663
) external;
645664

665+
/// @notice Sets whether the submitter wants to pay the protocol fee on their rewards submissions.
666+
/// @dev Submitters must opt-in to pay the protocol fee to be eligible for rewards.
667+
/// @param submitter The address of the submitter that wants to opt-in or out of the protocol fee.
668+
/// @param optInForProtocolFee Whether the submitter wants to pay the protocol fee.
669+
function setOptInForProtocolFee(
670+
address submitter,
671+
bool optInForProtocolFee
672+
) external;
673+
646674
/// @notice Sets the permissioned `rewardsUpdater` address which can post new roots
647675
/// @dev Only callable by the contract owner
648676
/// @param _rewardsUpdater The address of the new rewardsUpdater

0 commit comments

Comments
 (0)