2025-03-11 20:16:16 +00:00
|
|
|
// SPDX-License-Identifier: UNLICENSED
|
|
|
|
|
pragma solidity ^0.8.13;
|
|
|
|
|
|
2025-10-20 08:20:59 +00:00
|
|
|
import {
|
|
|
|
|
IRewardsCoordinator
|
|
|
|
|
} from "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol";
|
|
|
|
|
import {
|
|
|
|
|
IPermissionController
|
|
|
|
|
} from "eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol";
|
|
|
|
|
import {
|
|
|
|
|
IAllocationManager
|
|
|
|
|
} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
|
feat: ✨ initial rewards registry (#17)
This PR adds an initial implementation for a rewards registry, which
will be the contract in charge of allowing DataHaven validators to claim
the rewards they earned for being validators in the previous epoch. The
logic behind it is as follows:
- Whenever an epoch finishes, the corresponding BEEFY block gets relayed
to Ethereum through Snowbridge. This BEEFY block contains, in its
`extra` field, the merkle root of the tree that contains as leafs all
the message commitments of the messages of corresponding block, one of
which is the rewards distribution message.
- The rewards distribution message commitment is the root of the merkle
tree where each leaf is a tuple of the operator ID and the obtained era
points in the finished epoch. In this case, the operator ID is the
corresponding validator's Ethereum address.
- When the rewards distribution message is received, Snowbridge
validates it using the aforementioned BEEFY block and then dispatches
it. The dispatch invokes the `callContract` function of the
`RewardsAgent` agent, with the corresponding parameters so that this
agent calls the `updateRewardsMerkleRoot` function of the
`RewardsRegistry` contract with the new rewards distribution message
commitment.
- After this root is updated, any validator/operator can submit a proof
that it is in a leaf of the merkle tree that produced that root, which
means it has pending rewards to claim, through the
`ServiceManagerBase`'s `claimOperatorRewards` function.
- Each operator set of the AVS can have an assigned `RewardsRegistry`
contract. Operator sets that do not have an assigned `RewardsRegistry`
contract won't be able to received rewards.
This PR also adds two separate unit-test suites: one for the added
functionality to the `ServiceManagerBase` contract and one specific to
the new `RewardsRegistry` contract.
> [!CAUTION]
The `RewardsAgent` agent is the only one allowed to update the rewards'
merkle root, which means if a malicious user could get access to it it
could set the pending rewards to be claimed to an arbitrary tree that
benefits it. Extreme caution must be taken in the Substrate side so only
validated messages are sent to the Ethereum side, as to not allow any
users to impersonate being this agent.
### TODO:
Ideally, we would use the `RewardsCoordinator` contract from the
EigenLayer core to distribute the rewards, but currently that adds a
huge overhead for Operators since they'd have to wait for EigenLayer's
SideCar to snapshot state and update the distribution root (which
happens once a day), generate a proof that they belong to the tree of
that distribution root, store it while waiting for the `activationDelay`
(currently a week) to pass, and just then they would be able to claim
their earned rewards.
---------
Co-authored-by: Facundo Farall <37149322+ffarall@users.noreply.github.com>
2025-03-31 19:54:23 +00:00
|
|
|
import {IRewardsRegistry} from "../../src/interfaces/IRewardsRegistry.sol";
|
2025-03-11 20:16:16 +00:00
|
|
|
|
|
|
|
|
import {ServiceManagerBase} from "../../src/middleware/ServiceManagerBase.sol";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @title Minimal implementation of a ServiceManager-type contract.
|
|
|
|
|
* Uses the ServiceManagerBase contract as is.
|
|
|
|
|
*/
|
|
|
|
|
contract ServiceManagerMock is ServiceManagerBase {
|
|
|
|
|
uint256 public number;
|
|
|
|
|
|
|
|
|
|
/// @notice Sets the (immutable) `_registryCoordinator` address
|
|
|
|
|
constructor(
|
|
|
|
|
IRewardsCoordinator __rewardsCoordinator,
|
|
|
|
|
IPermissionController __permissionController,
|
|
|
|
|
IAllocationManager __allocationManager
|
|
|
|
|
) ServiceManagerBase(__rewardsCoordinator, __permissionController, __allocationManager) {}
|
|
|
|
|
|
2025-04-16 15:49:35 +00:00
|
|
|
function initialise(
|
2025-03-11 20:16:16 +00:00
|
|
|
address initialOwner,
|
|
|
|
|
address rewardsInitiator
|
|
|
|
|
) public virtual initializer {
|
|
|
|
|
__ServiceManagerBase_init(initialOwner, rewardsInitiator);
|
|
|
|
|
}
|
2025-03-27 15:20:15 +00:00
|
|
|
|
feat: ✨ initial rewards registry (#17)
This PR adds an initial implementation for a rewards registry, which
will be the contract in charge of allowing DataHaven validators to claim
the rewards they earned for being validators in the previous epoch. The
logic behind it is as follows:
- Whenever an epoch finishes, the corresponding BEEFY block gets relayed
to Ethereum through Snowbridge. This BEEFY block contains, in its
`extra` field, the merkle root of the tree that contains as leafs all
the message commitments of the messages of corresponding block, one of
which is the rewards distribution message.
- The rewards distribution message commitment is the root of the merkle
tree where each leaf is a tuple of the operator ID and the obtained era
points in the finished epoch. In this case, the operator ID is the
corresponding validator's Ethereum address.
- When the rewards distribution message is received, Snowbridge
validates it using the aforementioned BEEFY block and then dispatches
it. The dispatch invokes the `callContract` function of the
`RewardsAgent` agent, with the corresponding parameters so that this
agent calls the `updateRewardsMerkleRoot` function of the
`RewardsRegistry` contract with the new rewards distribution message
commitment.
- After this root is updated, any validator/operator can submit a proof
that it is in a leaf of the merkle tree that produced that root, which
means it has pending rewards to claim, through the
`ServiceManagerBase`'s `claimOperatorRewards` function.
- Each operator set of the AVS can have an assigned `RewardsRegistry`
contract. Operator sets that do not have an assigned `RewardsRegistry`
contract won't be able to received rewards.
This PR also adds two separate unit-test suites: one for the added
functionality to the `ServiceManagerBase` contract and one specific to
the new `RewardsRegistry` contract.
> [!CAUTION]
The `RewardsAgent` agent is the only one allowed to update the rewards'
merkle root, which means if a malicious user could get access to it it
could set the pending rewards to be claimed to an arbitrary tree that
benefits it. Extreme caution must be taken in the Substrate side so only
validated messages are sent to the Ethereum side, as to not allow any
users to impersonate being this agent.
### TODO:
Ideally, we would use the `RewardsCoordinator` contract from the
EigenLayer core to distribute the rewards, but currently that adds a
huge overhead for Operators since they'd have to wait for EigenLayer's
SideCar to snapshot state and update the distribution root (which
happens once a day), generate a proof that they belong to the tree of
that distribution root, store it while waiting for the `activationDelay`
(currently a week) to pass, and just then they would be able to claim
their earned rewards.
---------
Co-authored-by: Facundo Farall <37149322+ffarall@users.noreply.github.com>
2025-03-31 19:54:23 +00:00
|
|
|
/**
|
|
|
|
|
* @notice Get the rewards registry for an operator set (exposing for testing)
|
|
|
|
|
* @param operatorSetId The ID of the operator set
|
|
|
|
|
* @return The rewards registry for the operator set
|
|
|
|
|
*/
|
|
|
|
|
function getOperatorSetRewardsRegistry(
|
|
|
|
|
uint32 operatorSetId
|
|
|
|
|
) external view returns (IRewardsRegistry) {
|
|
|
|
|
return operatorSetToRewardsRegistry[operatorSetId];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @notice Override the internal _ensureOperatorIsPartOfOperatorSet function to simplify testing
|
|
|
|
|
* @param operator The operator address
|
|
|
|
|
* @param operatorSetId The operator set ID
|
|
|
|
|
* @dev This should be removed once the AllocationManagerMock is updated to be able to handle operator sets
|
|
|
|
|
*/
|
|
|
|
|
function _ensureOperatorIsPartOfOperatorSet(
|
|
|
|
|
address operator,
|
|
|
|
|
uint32 operatorSetId
|
|
|
|
|
) internal view override {
|
|
|
|
|
// No-op for testing
|
|
|
|
|
}
|
2025-03-11 20:16:16 +00:00
|
|
|
}
|