refactor: cleanup old rewards model (#383)

## Summary

This PR removes the old merkle root-based rewards model and completes
the migration to EigenLayer Rewards V2 distribution. The old model
required operators to claim rewards by providing merkle proofs, while
the new model uses `submitRewards` to send rewards directly to
EigenLayer's `RewardsCoordinator`.

### Key Changes

- **Smart Contracts**: Removed `RewardsRegistry`,
`RewardsRegistryStorage`, `IRewardsRegistry`, and `SortedMerkleProof`
contracts along with all merkle claim functions from
`ServiceManagerBase`
- **Substrate Pallets**: Removed merkle proof generation from
`external-validators-rewards` pallet and deleted the entire
`runtime-api` crate (no longer needed)
- **Test Framework**: Removed all RewardsRegistry-related code from
deployment scripts, CLI handlers, and TypeScript bindings
- **Runtimes**: Cleaned up all three runtimes (testnet, stagenet,
mainnet) to remove runtime API implementations and unused imports

### Files Removed

**Contracts:**
- `contracts/src/middleware/RewardsRegistry.sol`
- `contracts/src/middleware/RewardsRegistryStorage.sol`
- `contracts/src/interfaces/IRewardsRegistry.sol`
- `contracts/src/libraries/SortedMerkleProof.sol`
- `contracts/test/RewardsRegistry.t.sol`
- `contracts/test/ServiceManagerRewardsRegistry.t.sol`

**Substrate:**
- `operator/pallets/external-validators-rewards/runtime-api/` (entire
crate)

**Test Framework:**
- `test/suites/rewards-message.test.ts`

### Files Modified

**Contracts:**
- `ServiceManagerBase.sol` - Removed merkle claim functions
- `ServiceManagerBaseStorage.sol` - Removed
`operatorSetToRewardsRegistry` mapping
- `IServiceManager.sol` - Removed interface members

**Substrate:**
- `external-validators-rewards` pallet - Removed merkle proof
generation, simplified `EraRewardsUtils` struct
- All runtime configs - Removed `ExternalValidatorsRewardsApi`
implementations

**Test Framework:**
- Updated deployment scripts, CLI handlers, relayer configs, and
TypeScript bindings

### Stats

```
50 files changed, 966 insertions(+), 4453 deletions(-)
```

## Test plan

- [x] All Rust tests pass (`cargo test`)
- [x] All contract tests pass (`forge test`)
- [x] TypeScript type checking passes (`bun typecheck`)
- [x] Contracts build successfully (`forge build`)
- [x] Operator builds successfully (`cargo build --release --features
fast-runtime`)
- [ ] E2E tests pass (`bun test:e2e`)
This commit is contained in:
Ahmad Kaouk 2026-01-09 15:25:49 +01:00 committed by GitHub
parent e04023ef11
commit 9be1acc97e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
50 changed files with 985 additions and 4472 deletions

View file

@ -1 +1 @@
{"RewardsAgent": "0xac06641381166cf085281c45292147f833C622d7","RewardsAgentOrigin": "0x0000000000000000000000000000000000000000000000000000000000000000","updateRewardsMerkleRootSelector": "0xdc3d04ec"}
{"RewardsAgent": "0xac06641381166cf085281c45292147f833C622d7","RewardsAgentOrigin": "0x0000000000000000000000000000000000000000000000000000000000000000"}

View file

@ -1,27 +1 @@
{
"network": "anvil",
"BeefyClient": "0x99bbA657f2BbC93c02D617f8bA121cB8Fc104Acf",
"AgentExecutor": "0x0E801D84Fa97b50751Dbf25036d067dCf18858bF",
"Gateway": "0x9d4454B023096f34B160D6B654540c56A1F81688",
"ServiceManager": "0x809d550fca64d94Bd9F66E60752A544199cfAC3D",
"ServiceManagerImplementation": "0x36C02dA8a0983159322a80FFE9F24b1acfF8B570",
"RewardsRegistry": "0x4c5859f0F772848b2D91F1D83E2Fe57935348029",
"RewardsAgent": "0xac06641381166cf085281c45292147f833C622d7",
"DelegationManager": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82",
"StrategyManager": "0x9A676e781A523b5d0C0e43731313A708CB607508",
"AVSDirectory": "0x0B306BF915C4d645ff596e518fAf3F9669b97016",
"EigenPodManager": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1",
"EigenPodBeacon": "0x4ed7c70F96B99c776995fB64377f0d4aB3B0e1C1",
"RewardsCoordinator": "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE",
"AllocationManager": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed",
"PermissionController": "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c",
"ETHPOSDeposit": "0xC7f2Cf4845C6db0e1a1e91ED41Bcd0FcC1b0E141",
"BaseStrategyImplementation": "0xf5059a5D33d5853360D16C683c16e67980206f36",
"DeployedStrategies": [
{
"address": "0x998abeb3E57409262aE5b751f60747921B33613E",
"underlyingToken": "0x95401dc811bb5740090279Ba06cfA8fcF6113778",
"tokenCreator": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
}
]
}
{"network": "anvil","BeefyClient": "0x99bbA657f2BbC93c02D617f8bA121cB8Fc104Acf","AgentExecutor": "0x0E801D84Fa97b50751Dbf25036d067dCf18858bF","Gateway": "0x9d4454B023096f34B160D6B654540c56A1F81688","ServiceManager": "0x809d550fca64d94Bd9F66E60752A544199cfAC3D","ServiceManagerImplementation": "0x36C02dA8a0983159322a80FFE9F24b1acfF8B570","RewardsAgent": "0xac06641381166cf085281c45292147f833C622d7","DelegationManager": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82","StrategyManager": "0x9A676e781A523b5d0C0e43731313A708CB607508","AVSDirectory": "0x0B306BF915C4d645ff596e518fAf3F9669b97016","EigenPodManager": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1","EigenPodBeacon": "0x4ed7c70F96B99c776995fB64377f0d4aB3B0e1C1","RewardsCoordinator": "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE","AllocationManager": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed","PermissionController": "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c","ETHPOSDeposit": "0xC7f2Cf4845C6db0e1a1e91ED41Bcd0FcC1b0E141","BaseStrategyImplementation": "0xf5059a5D33d5853360D16C683c16e67980206f36","DeployedStrategies": [{"address": "0x998abeb3E57409262aE5b751f60747921B33613E","underlyingToken": "0x95401dc811bb5740090279Ba06cfA8fcF6113778","tokenCreator": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"}]}

View file

@ -1 +1 @@
df0978809ee25447c22aca90a4064b9e4a6ea97b
5cb16238bf8311a3f27dd130cd89f0cd7befda5d

File diff suppressed because one or more lines are too long

View file

@ -39,9 +39,6 @@ import {IETHPOSDeposit} from "eigenlayer-contracts/src/contracts/interfaces/IETH
// DataHaven imports
import {DataHavenServiceManager} from "../../src/DataHavenServiceManager.sol";
import {MerkleUtils} from "../../src/libraries/MerkleUtils.sol";
import {RewardsRegistry} from "../../src/middleware/RewardsRegistry.sol";
import {IRewardsRegistry} from "../../src/interfaces/IRewardsRegistry.sol";
import {ValidatorsUtils} from "../../script/utils/ValidatorsUtils.sol";
// Shared structs
@ -132,9 +129,7 @@ abstract contract DeployBase is Script, DeployParams, Accounts {
// Deploy DataHaven contracts (same for both modes)
(
DataHavenServiceManager serviceManager,
DataHavenServiceManager serviceManagerImplementation,
RewardsRegistry rewardsRegistry,
bytes4 updateRewardsMerkleRootSelector
DataHavenServiceManager serviceManagerImplementation
) = _deployDataHavenContracts(avsConfig, proxyAdmin, gateway);
Logging.logFooter();
@ -142,14 +137,7 @@ abstract contract DeployBase is Script, DeployParams, Accounts {
// Final configuration (same for both modes)
Logging.logHeader("FINAL CONFIGURATION");
if (_txExecutionEnabled) {
vm.broadcast(_avsOwnerPrivateKey);
serviceManager.setRewardsAgent(0, address(rewardsAgentAddress));
Logging.logStep("Agent set in RewardsRegistry");
} else {
Logging.logInfo("TX EXECUTION DISABLED: call setRewardsAgent via multisig");
}
Logging.logContractDeployed("Agent Address", rewardsAgentAddress);
Logging.logContractDeployed("Rewards Agent Address", rewardsAgentAddress);
Logging.logFooter();
_logProgress();
@ -160,15 +148,10 @@ abstract contract DeployBase is Script, DeployParams, Accounts {
gateway,
serviceManager,
serviceManagerImplementation,
rewardsRegistry,
rewardsAgentAddress
);
_outputRewardsInfo(
rewardsAgentAddress,
snowbridgeConfig.rewardsMessageOrigin,
updateRewardsMerkleRootSelector
);
_outputRewardsAgentInfo(rewardsAgentAddress, snowbridgeConfig.rewardsMessageOrigin);
}
/**
@ -253,7 +236,7 @@ abstract contract DeployBase is Script, DeployParams, Accounts {
AVSConfig memory avsConfig,
ProxyAdmin proxyAdmin,
IGatewayV2 gateway
) internal returns (DataHavenServiceManager, DataHavenServiceManager, RewardsRegistry, bytes4) {
) internal returns (DataHavenServiceManager, DataHavenServiceManager) {
Logging.logHeader("DATAHAVEN CUSTOM CONTRACTS DEPLOYMENT");
// Deploy the Service Manager
@ -278,15 +261,6 @@ abstract contract DeployBase is Script, DeployParams, Accounts {
_createServiceManagerProxy(serviceManagerImplementation, proxyAdmin, initParams);
Logging.logContractDeployed("ServiceManager Proxy", address(serviceManager));
// Deploy RewardsRegistry
vm.broadcast(_deployerPrivateKey);
RewardsRegistry rewardsRegistry = new RewardsRegistry(
address(serviceManager),
address(0) // Will be set to the Agent address after creation
);
Logging.logContractDeployed("RewardsRegistry", address(rewardsRegistry));
bytes4 updateRewardsMerkleRootSelector = IRewardsRegistry.updateRewardsMerkleRoot.selector;
Logging.logSection("Configuring Service Manager");
// Register the DataHaven service in the AllocationManager
@ -298,22 +272,7 @@ abstract contract DeployBase is Script, DeployParams, Accounts {
Logging.logInfo("TX EXECUTION DISABLED: call updateAVSMetadataURI via multisig");
}
// Set the RewardsRegistry in the ServiceManager
uint32 validatorsSetId = serviceManager.VALIDATORS_SET_ID();
if (_txExecutionEnabled) {
vm.broadcast(_avsOwnerPrivateKey);
serviceManager.setRewardsRegistry(validatorsSetId, rewardsRegistry);
Logging.logStep("RewardsRegistry set in ServiceManager");
} else {
Logging.logInfo("TX EXECUTION DISABLED: call setRewardsRegistry via multisig");
}
return (
serviceManager,
serviceManagerImplementation,
rewardsRegistry,
updateRewardsMerkleRootSelector
);
return (serviceManager, serviceManagerImplementation);
}
/**
@ -334,24 +293,19 @@ abstract contract DeployBase is Script, DeployParams, Accounts {
IGatewayV2 gateway,
DataHavenServiceManager serviceManager,
DataHavenServiceManager serviceManagerImplementation,
RewardsRegistry rewardsRegistry,
address rewardsAgent
) internal virtual;
/**
* @notice Output rewards info (shared across all deployment types)
* @notice Output rewards agent info (shared across all deployment types)
*/
function _outputRewardsInfo(
function _outputRewardsAgentInfo(
address rewardsAgent,
bytes32 rewardsAgentOrigin,
bytes4 updateRewardsMerkleRootSelector
bytes32 rewardsAgentOrigin
) internal {
Logging.logHeader("REWARDS AGENT INFO");
Logging.logContractDeployed("RewardsAgent", rewardsAgent);
Logging.logAgentOrigin("RewardsAgentOrigin", vm.toString(rewardsAgentOrigin));
Logging.logFunctionSelector(
"updateRewardsMerkleRootSelector", vm.toString(updateRewardsMerkleRootSelector)
);
Logging.logFooter();
// Write to deployment file for future reference
@ -365,33 +319,11 @@ abstract contract DeployBase is Script, DeployParams, Accounts {
// Create JSON with rewards info
string memory json = "{";
json = string.concat(json, '"RewardsAgent": "', vm.toString(rewardsAgent), '",');
json = string.concat(json, '"RewardsAgentOrigin": "', vm.toString(rewardsAgentOrigin), '",');
json = string.concat(
json,
'"updateRewardsMerkleRootSelector": "',
_trimToBytes4(vm.toString(updateRewardsMerkleRootSelector)),
'"'
);
json = string.concat(json, '"RewardsAgentOrigin": "', vm.toString(rewardsAgentOrigin), '"');
json = string.concat(json, "}");
// Write to file
vm.writeFile(rewardsInfoPath, json);
Logging.logInfo(string.concat("Rewards info saved to: ", rewardsInfoPath));
}
/**
* @notice Helper function to trim a padded hex string to only the first 4 bytes
*/
function _trimToBytes4(
string memory paddedHex
) internal pure returns (string memory) {
bytes memory data = bytes(paddedHex);
bytes memory trimmed = new bytes(10); // 0x + 8 hex chars = 10 total chars
for (uint256 i = 0; i < 10; i++) {
trimmed[i] = data[i];
}
return string(trimmed);
}
}

View file

@ -24,9 +24,6 @@ import {OperatingMode} from "snowbridge/src/types/Common.sol";
import {ud60x18} from "snowbridge/lib/prb-math/src/UD60x18.sol";
import {BeefyClient} from "snowbridge/src/BeefyClient.sol";
// DataHaven imports for function signatures
import {RewardsRegistry} from "../../src/middleware/RewardsRegistry.sol";
// Additional imports specific to local deployment
import {
ERC20PresetFixedSupply
@ -70,8 +67,6 @@ import {
import {EmptyContract} from "eigenlayer-contracts/src/test/mocks/EmptyContract.sol";
import {DataHavenServiceManager} from "../../src/DataHavenServiceManager.sol";
import {RewardsRegistry} from "../../src/middleware/RewardsRegistry.sol";
import {IRewardsRegistry} from "../../src/interfaces/IRewardsRegistry.sol";
/**
* @title DeployLocal
@ -207,7 +202,6 @@ contract DeployLocal is DeployBase {
IGatewayV2 gateway,
DataHavenServiceManager serviceManager,
DataHavenServiceManager serviceManagerImplementation,
RewardsRegistry rewardsRegistry,
address rewardsAgent
) internal override {
Logging.logHeader("DEPLOYMENT SUMMARY");
@ -220,7 +214,6 @@ contract DeployLocal is DeployBase {
Logging.logSection("DataHaven Contracts");
Logging.logContractDeployed("ServiceManager", address(serviceManager));
Logging.logContractDeployed("RewardsRegistry", address(rewardsRegistry));
Logging.logSection("EigenLayer Core Contracts");
Logging.logContractDeployed("DelegationManager", address(delegation));
@ -270,9 +263,6 @@ contract DeployLocal is DeployBase {
vm.toString(address(serviceManagerImplementation)),
'",'
);
json = string.concat(
json, '"RewardsRegistry": "', vm.toString(address(rewardsRegistry)), '",'
);
json = string.concat(json, '"RewardsAgent": "', vm.toString(rewardsAgent), '",');
// EigenLayer contracts

View file

@ -12,9 +12,6 @@ import {IGatewayV2} from "snowbridge/src/v2/IGateway.sol";
// Logging import
import {Logging} from "../utils/Logging.sol";
// DataHaven imports for function signatures
import {RewardsRegistry} from "../../src/middleware/RewardsRegistry.sol";
// EigenLayer core contract imports for type casting
import {AllocationManager} from "eigenlayer-contracts/src/contracts/core/AllocationManager.sol";
import {AVSDirectory} from "eigenlayer-contracts/src/contracts/core/AVSDirectory.sol";
@ -139,7 +136,6 @@ contract DeployTestnet is DeployBase {
IGatewayV2 gateway,
DataHavenServiceManager serviceManager,
DataHavenServiceManager serviceManagerImplementation,
RewardsRegistry rewardsRegistry,
address rewardsAgent
) internal override {
Logging.logHeader("DEPLOYMENT SUMMARY");
@ -152,7 +148,6 @@ contract DeployTestnet is DeployBase {
Logging.logSection("DataHaven Contracts");
Logging.logContractDeployed("ServiceManager", address(serviceManager));
Logging.logContractDeployed("RewardsRegistry", address(rewardsRegistry));
Logging.logSection(
string.concat("EigenLayer Core Contracts (Existing on ", _getDeploymentMode(), ")")
@ -190,9 +185,6 @@ contract DeployTestnet is DeployBase {
vm.toString(address(serviceManagerImplementation)),
'",'
);
json = string.concat(
json, '"RewardsRegistry": "', vm.toString(address(rewardsRegistry)), '",'
);
json = string.concat(json, '"RewardsAgent": "', vm.toString(rewardsAgent), '",');
// EigenLayer contracts (existing on testnet)

View file

@ -6,7 +6,6 @@ import {Script} from "forge-std/Script.sol";
// DataHaven imports
import {DataHavenServiceManager} from "../../src/DataHavenServiceManager.sol";
import {RewardsRegistry} from "../../src/middleware/RewardsRegistry.sol";
/**
* @title DHScriptStorage
@ -15,7 +14,6 @@ import {RewardsRegistry} from "../../src/middleware/RewardsRegistry.sol";
contract DHScriptStorage is Script {
// DataHaven Contract declarations
DataHavenServiceManager public serviceManager;
RewardsRegistry public rewardsRegistry;
/**
* @notice Loads the DataHaven contracts from the deployment file.
@ -30,7 +28,5 @@ contract DHScriptStorage is Script {
// Store the contract addresses
serviceManager =
DataHavenServiceManager(vm.parseJsonAddress(deploymentFile, ".ServiceManager"));
rewardsRegistry =
RewardsRegistry(payable(vm.parseJsonAddress(deploymentFile, ".RewardsRegistry")));
}
}

View file

@ -1,182 +0,0 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;
/**
* @title Interface for errors in the RewardsRegistry contract
*/
interface IRewardsRegistryErrors {
/// @notice Thrown when a function is called by an address that is not the AVS.
error OnlyAVS();
/// @notice Thrown when a function is called by an address that is not the RewardsAgent.
error OnlyRewardsAgent();
/// @notice Thrown when a provided merkle proof is invalid.
error InvalidMerkleProof();
/// @notice Thrown when rewards transfer fails.
error RewardsTransferFailed();
/// @notice Thrown when the rewards merkle root is not set.
error RewardsMerkleRootNotSet();
/// @notice Thrown when trying to access a merkle root index that doesn't exist.
error InvalidMerkleRootIndex();
/// @notice Thrown when trying to claim rewards for a root index that has already been claimed.
error RewardsAlreadyClaimedForIndex();
/// @notice Thrown when the arrays provided to the batch claim function have mismatched lengths.
error ArrayLengthMismatch();
}
/**
* @title Interface for events in the RewardsRegistry contract
*/
interface IRewardsRegistryEvents {
/**
* @notice Emitted when a new merkle root is set
* @param oldRoot The previous merkle root
* @param newRoot The new merkle root
* @param newRootIndex The index of the new root in the history
*/
event RewardsMerkleRootUpdated(bytes32 oldRoot, bytes32 indexed newRoot, uint256 newRootIndex);
/**
* @notice Emitted when rewards are claimed for a specific root index
* @param operatorAddress Address of the operator that received the rewards
* @param rootIndex Index of the merkle root that the operator claimed rewards from
* @param points Points earned by the operator
* @param rewardsAmount Amount of rewards transferred
*/
event RewardsClaimedForIndex(
address indexed operatorAddress,
uint256 indexed rootIndex,
uint256 points,
uint256 rewardsAmount
);
/**
* @notice Emitted when rewards are claimed for multiple root indices in a batch
* @param operatorAddress Address of the operator that received the rewards
* @param rootIndices Array of merkle root indices that the operator claimed rewards from
* @param points Array of points earned by the operator for each root index
* @param totalRewardsAmount Total amount of rewards transferred to the operator
*/
event RewardsBatchClaimedForIndices(
address indexed operatorAddress,
uint256[] rootIndices,
uint256[] points,
uint256 totalRewardsAmount
);
}
/**
* @title Interface for the RewardsRegistry contract
* @notice Contract for managing operator rewards through a Merkle root verification process
*/
interface IRewardsRegistry is IRewardsRegistryErrors, IRewardsRegistryEvents {
/**
* @notice Update the rewards merkle root
* @param newMerkleRoot New merkle root to be set
* @dev Only callable by the rewards agent
*/
function updateRewardsMerkleRoot(
bytes32 newMerkleRoot
) external;
/**
* @notice Claim rewards for an operator from a specific merkle root index using Substrate/Snowbridge positional Merkle proofs.
* @param operatorAddress Address of the operator to receive rewards
* @param rootIndex Index of the merkle root to claim from
* @param operatorPoints Points earned by the operator
* @param numberOfLeaves The total number of leaves in the Merkle tree
* @param leafIndex The index of the operator's leaf in the Merkle tree
* @param proof Positional Merkle proof (from leaf to root)
* @dev Only callable by the AVS (Service Manager)
*/
function claimRewards(
address operatorAddress,
uint256 rootIndex,
uint256 operatorPoints,
uint256 numberOfLeaves,
uint256 leafIndex,
bytes32[] calldata proof
) external;
/**
* @notice Claim rewards for an operator from the latest merkle root using Substrate/Snowbridge positional Merkle proofs.
* @param operatorAddress Address of the operator to receive rewards
* @param operatorPoints Points earned by the operator
* @param numberOfLeaves The total number of leaves in the Merkle tree
* @param leafIndex The index of the operator's leaf in the Merkle tree
* @param proof Positional Merkle proof (from leaf to root)
* @dev Only callable by the AVS (Service Manager)
*/
function claimLatestRewards(
address operatorAddress,
uint256 operatorPoints,
uint256 numberOfLeaves,
uint256 leafIndex,
bytes32[] calldata proof
) external;
/**
* @notice Claim rewards for an operator from multiple merkle root indices using Substrate/Snowbridge positional Merkle proofs.
* @param operatorAddress Address of the operator to receive rewards
* @param rootIndices Array of merkle root indices to claim from
* @param operatorPoints Array of points earned by the operator for each root
* @param numberOfLeaves Array with the total number of leaves for each Merkle tree
* @param leafIndices Array of leaf indices for the operator in each Merkle tree
* @param proofs Array of positional Merkle proofs for each claim
* @dev Only callable by the AVS (Service Manager)
*/
function claimRewardsBatch(
address operatorAddress,
uint256[] calldata rootIndices,
uint256[] calldata operatorPoints,
uint256[] calldata numberOfLeaves,
uint256[] calldata leafIndices,
bytes32[][] calldata proofs
) external;
/**
* @notice Sets the rewards agent address in the RewardsRegistry contract
* @param rewardsAgent New rewards agent address
* @dev Only callable by the AVS (Service Manager)
*/
function setRewardsAgent(
address rewardsAgent
) external;
/**
* @notice Get the merkle root at a specific index
* @param index Index of the merkle root to retrieve
* @return The merkle root at the specified index
*/
function getMerkleRootByIndex(
uint256 index
) external view returns (bytes32);
/**
* @notice Get the latest merkle root index
* @return The index of the latest merkle root (returns 0 if no roots exist)
*/
function getLatestMerkleRootIndex() external view returns (uint256);
/**
* @notice Get the latest merkle root
* @return The latest merkle root (returns bytes32(0) if no roots exist)
*/
function getLatestMerkleRoot() external view returns (bytes32);
/**
* @notice Get the total number of merkle roots in history
* @return The total count of merkle roots
*/
function getMerkleRootHistoryLength() external view returns (uint256);
/**
* @notice Check if an operator has claimed rewards for a specific root index
* @param operatorAddress Address of the operator
* @param rootIndex Index of the merkle root to check
* @return True if the operator has claimed rewards for this root index, false otherwise
*/
function hasClaimedByIndex(
address operatorAddress,
uint256 rootIndex
) external view returns (bool);
}

View file

@ -13,7 +13,6 @@ import {
} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol";
import {IAVSRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IAVSRegistrar.sol";
import {IRewardsRegistry} from "./IRewardsRegistry.sol";
interface IServiceManagerErrors {
/// @notice Thrown when a function is called by an address that is not the RegistryCoordinator.
@ -24,8 +23,6 @@ interface IServiceManagerErrors {
error OnlyStakeRegistry();
/// @notice Thrown when a slashing proposal delay has not been met yet.
error DelayPeriodNotPassed();
/// @notice Thrown when the operator set does not have a rewards registry set.
error NoRewardsRegistryForOperatorSet();
/// @notice Thrown when the operator is not part of the specified operator set.
error OperatorNotInOperatorSet();
}
@ -37,13 +34,6 @@ interface IServiceManagerEvents {
* @param newRewardsInitiator The new rewards initiator address.
*/
event RewardsInitiatorUpdated(address prevRewardsInitiator, address newRewardsInitiator);
/**
* @notice Emitted when a rewards registry is set for an operator set.
* @param operatorSetId The ID of the operator set.
* @param rewardsRegistry The address of the rewards registry.
*/
event RewardsRegistrySet(uint32 indexed operatorSetId, address indexed rewardsRegistry);
}
interface IServiceManager is IServiceManagerUI, IServiceManagerErrors, IServiceManagerEvents {
@ -135,77 +125,4 @@ interface IServiceManager is IServiceManagerUI, IServiceManagerErrors, IServiceM
* @return The address of the AVS
*/
function avs() external view returns (address);
/**
* @notice Sets the rewards registry for an operator set
* @param operatorSetId The ID of the operator set
* @param rewardsRegistry The address of the rewards registry
* @dev Only callable by the owner
*/
function setRewardsRegistry(
uint32 operatorSetId,
IRewardsRegistry rewardsRegistry
) external;
/**
* @notice Claim rewards for an operator from a specific merkle root index using Substrate/Snowbridge positional Merkle proofs
* @param operatorSetId The ID of the operator set
* @param rootIndex Index of the merkle root to claim from
* @param operatorPoints Points earned by the operator
* @param numberOfLeaves The total number of leaves in the Merkle tree
* @param leafIndex The index of the operator's leaf in the Merkle tree
* @param proof Positional Merkle proof (from leaf to root)
*/
function claimOperatorRewards(
uint32 operatorSetId,
uint256 rootIndex,
uint256 operatorPoints,
uint256 numberOfLeaves,
uint256 leafIndex,
bytes32[] calldata proof
) external;
/**
* @notice Claim rewards for an operator from the latest merkle root using Substrate/Snowbridge positional Merkle proofs
* @param operatorSetId The ID of the operator set
* @param operatorPoints Points earned by the operator
* @param numberOfLeaves The total number of leaves in the Merkle tree
* @param leafIndex The index of the operator's leaf in the Merkle tree
* @param proof Positional Merkle proof (from leaf to root)
*/
function claimLatestOperatorRewards(
uint32 operatorSetId,
uint256 operatorPoints,
uint256 numberOfLeaves,
uint256 leafIndex,
bytes32[] calldata proof
) external;
/**
* @notice Claim rewards for an operator from multiple merkle root indices using Substrate/Snowbridge positional Merkle proofs
* @param operatorSetId The ID of the operator set
* @param rootIndices Array of merkle root indices to claim from
* @param operatorPoints Array of points earned by the operator for each root
* @param numberOfLeaves Array with the total number of leaves for each Merkle tree
* @param leafIndices Array of leaf indices for the operator in each Merkle tree
* @param proofs Array of positional Merkle proofs for each claim
*/
function claimOperatorRewardsBatch(
uint32 operatorSetId,
uint256[] calldata rootIndices,
uint256[] calldata operatorPoints,
uint256[] calldata numberOfLeaves,
uint256[] calldata leafIndices,
bytes32[][] calldata proofs
) external;
/**
* @notice Sets the rewards agent address in the RewardsRegistry contract
* @param rewardsAgent New rewards agent address
* @dev Only callable by the owner
*/
function setRewardsAgent(
uint32 operatorSetId,
address rewardsAgent
) external;
}

View file

@ -1,50 +0,0 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;
/**
* @title SortedMerkleProof
* @notice Verifies sorted-hash Merkle proofs (pair order determined by hash value).
* This matches the runtime merkle tree used in DataHaven rewards.
*/
library SortedMerkleProof {
/**
* @notice Verify that a leaf is part of a sorted-hash Merkle tree.
* @param root the root of the merkle tree
* @param leaf the leaf hash
* @param position the position of the leaf (only used for bounds check)
* @param width the number of leaves in the tree
* @param proof the array of proofs from leaf to root
*/
function verify(
bytes32 root,
bytes32 leaf,
uint256 position,
uint256 width,
bytes32[] calldata proof
) internal pure returns (bool) {
if (position >= width) {
return false;
}
bytes32 node = leaf;
for (uint256 i = 0; i < proof.length; i++) {
bytes32 sibling = proof[i];
node = node < sibling ? efficientHash(node, sibling) : efficientHash(sibling, node);
}
return node == root;
}
/**
* @notice Efficiently hashes two bytes32 values using assembly
*/
function efficientHash(
bytes32 a,
bytes32 b
) internal pure returns (bytes32 value) {
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}

View file

@ -1,295 +0,0 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;
import {SortedMerkleProof} from "../libraries/SortedMerkleProof.sol";
import {ScaleCodec} from "snowbridge/src/utils/ScaleCodec.sol";
import {IDataHavenServiceManager} from "../interfaces/IDataHavenServiceManager.sol";
import {RewardsRegistryStorage} from "./RewardsRegistryStorage.sol";
/**
* @title RewardsRegistry
* @notice Contract for managing operator rewards through a Merkle root verification process
*/
contract RewardsRegistry is RewardsRegistryStorage {
/**
* @notice Constructor to set up the rewards registry
* @param _avs Address of the AVS (Service Manager)
* @param _rewardsAgent Address of the rewards agent contract
*/
constructor(
address _avs,
address _rewardsAgent
) RewardsRegistryStorage(_avs, _rewardsAgent) {}
/**
* @notice Modifier to restrict function access to the rewards agent only
*/
modifier onlyRewardsAgent() {
if (msg.sender != rewardsAgent) {
revert OnlyRewardsAgent();
}
_;
}
/**
* @notice Modifier to restrict function access to the AVS only
*/
modifier onlyAVS() {
if (msg.sender != avs) {
revert OnlyAVS();
}
_;
}
/**
* @notice Update the rewards merkle root
* @param newMerkleRoot New merkle root to be set
* @dev Only callable by the rewards agent
*/
function updateRewardsMerkleRoot(
bytes32 newMerkleRoot
) external override onlyRewardsAgent {
// Get the old root (bytes32(0) if no roots exist)
bytes32 oldRoot = merkleRootHistory.length > 0
? merkleRootHistory[merkleRootHistory.length - 1]
: bytes32(0);
// Add the new root to the history
uint256 newRootIndex = merkleRootHistory.length;
merkleRootHistory.push(newMerkleRoot);
// Emit the corresponding event
emit RewardsMerkleRootUpdated(oldRoot, newMerkleRoot, newRootIndex);
}
/**
* @notice Update the rewards agent address
* @param _rewardsAgent New rewards agent address
* @dev Only callable by the AVS
*/
function setRewardsAgent(
address _rewardsAgent
) external onlyAVS {
rewardsAgent = _rewardsAgent;
}
/**
* @notice Claim rewards for an operator from a specific merkle root index using sorted-hash Merkle proofs.
* @param operatorAddress Address of the operator to receive rewards
* @param rootIndex Index of the merkle root to claim from
* @param operatorPoints Points earned by the operator
* @param numberOfLeaves The total number of leaves in the Merkle tree
* @param leafIndex The index of the operator's leaf in the Merkle tree
* @param proof Positional Merkle proof (from leaf to root)
* @dev Only callable by the AVS (Service Manager)
*/
function claimRewards(
address operatorAddress,
uint256 rootIndex,
uint256 operatorPoints,
uint256 numberOfLeaves,
uint256 leafIndex,
bytes32[] calldata proof
) external override onlyAVS {
uint256 rewardsAmount = _validateClaim(
operatorAddress, rootIndex, operatorPoints, numberOfLeaves, leafIndex, proof
);
_transferRewards(operatorAddress, rewardsAmount);
emit RewardsClaimedForIndex(operatorAddress, rootIndex, operatorPoints, rewardsAmount);
}
/**
* @notice Claim rewards for an operator from the latest merkle root using sorted-hash Merkle proofs.
* @param operatorAddress Address of the operator to receive rewards
* @param operatorPoints Points earned by the operator
* @param numberOfLeaves The total number of leaves in the Merkle tree
* @param leafIndex The index of the operator's leaf in the Merkle tree
* @param proof Positional Merkle proof (from leaf to root)
* @dev Only callable by the AVS (Service Manager)
*/
function claimLatestRewards(
address operatorAddress,
uint256 operatorPoints,
uint256 numberOfLeaves,
uint256 leafIndex,
bytes32[] calldata proof
) external override onlyAVS {
if (merkleRootHistory.length == 0) {
revert RewardsMerkleRootNotSet();
}
uint256 latestIndex = merkleRootHistory.length - 1;
uint256 rewardsAmount = _validateClaim(
operatorAddress, latestIndex, operatorPoints, numberOfLeaves, leafIndex, proof
);
_transferRewards(operatorAddress, rewardsAmount);
emit RewardsClaimedForIndex(operatorAddress, latestIndex, operatorPoints, rewardsAmount);
}
/**
* @notice Claim rewards for an operator from multiple merkle root indices using sorted-hash Merkle proofs.
* @param operatorAddress Address of the operator to receive rewards
* @param rootIndices Array of merkle root indices to claim from
* @param operatorPoints Array of points earned by the operator for each root
* @param numberOfLeaves Array with the total number of leaves for each Merkle tree
* @param leafIndices Array of leaf indices for the operator in each Merkle tree
* @param proofs Array of sorted-hash Merkle proofs for each claim
* @dev Only callable by the AVS (Service Manager)
*/
function claimRewardsBatch(
address operatorAddress,
uint256[] calldata rootIndices,
uint256[] calldata operatorPoints,
uint256[] calldata numberOfLeaves,
uint256[] calldata leafIndices,
bytes32[][] calldata proofs
) external override onlyAVS {
if (
rootIndices.length != operatorPoints.length || rootIndices.length != proofs.length
|| rootIndices.length != numberOfLeaves.length
|| rootIndices.length != leafIndices.length
) {
revert ArrayLengthMismatch();
}
uint256 totalRewards = 0;
for (uint256 i = 0; i < rootIndices.length; i++) {
totalRewards += _validateClaim(
operatorAddress,
rootIndices[i],
operatorPoints[i],
numberOfLeaves[i],
leafIndices[i],
proofs[i]
);
}
_transferRewards(operatorAddress, totalRewards);
emit RewardsBatchClaimedForIndices(
operatorAddress, rootIndices, operatorPoints, totalRewards
);
}
/**
* @notice Internal function to validate a claim and calculate rewards using sorted-hash Merkle proofs.
* @param operatorAddress Address of the operator to receive rewards
* @param rootIndex Index of the merkle root to claim from
* @param operatorPoints Points earned by the operator
* @param numberOfLeaves The total number of leaves in the Merkle tree
* @param leafIndex The index of the operator's leaf in the Merkle tree
* @param proof Positional Merkle proof (from leaf to root)
* @return rewardsAmount The amount of rewards calculated
*/
function _validateClaim(
address operatorAddress,
uint256 rootIndex,
uint256 operatorPoints,
uint256 numberOfLeaves,
uint256 leafIndex,
bytes32[] calldata proof
) internal returns (uint256 rewardsAmount) {
if (rootIndex >= merkleRootHistory.length) {
revert InvalidMerkleRootIndex();
}
if (operatorClaimedByIndex[operatorAddress][rootIndex]) {
revert RewardsAlreadyClaimedForIndex();
}
// Compute Substrate-compatible leaf: keccak256(SCALE(accountId || u32LE points))
// For DataHaven, AccountId comes from the AVS mapping (validatorEthAddressToSolochainAddress) if set.
address leafAccount = operatorAddress;
address mappedSolochain =
IDataHavenServiceManager(avs).validatorEthAddressToSolochainAddress(operatorAddress);
if (mappedSolochain != address(0)) {
leafAccount = mappedSolochain;
}
bytes memory preimage =
abi.encodePacked(leafAccount, ScaleCodec.encodeU32(uint32(operatorPoints)));
bytes32 substrateLeaf = keccak256(preimage);
bool ok = SortedMerkleProof.verify(
merkleRootHistory[rootIndex], substrateLeaf, leafIndex, numberOfLeaves, proof
);
if (!ok) revert InvalidMerkleProof();
rewardsAmount = operatorPoints;
operatorClaimedByIndex[operatorAddress][rootIndex] = true;
}
/**
* @notice Internal function to transfer rewards to an operator
* @param operatorAddress Address of the operator to receive rewards
* @param rewardsAmount Amount of rewards to transfer
*/
function _transferRewards(
address operatorAddress,
uint256 rewardsAmount
) internal {
// Transfer rewards to the operator
(bool success,) = operatorAddress.call{value: rewardsAmount}("");
if (!success) {
revert RewardsTransferFailed();
}
}
/**
* @notice Get the merkle root at a specific index
* @param index Index of the merkle root to retrieve
* @return The merkle root at the specified index
*/
function getMerkleRootByIndex(
uint256 index
) external view override returns (bytes32) {
if (index >= merkleRootHistory.length) {
revert InvalidMerkleRootIndex();
}
return merkleRootHistory[index];
}
/**
* @notice Get the latest merkle root index
* @return The index of the latest merkle root (returns 0 if no roots exist)
*/
function getLatestMerkleRootIndex() external view override returns (uint256) {
uint256 length = merkleRootHistory.length;
return length == 0 ? 0 : length - 1;
}
/**
* @notice Get the latest merkle root
* @return The latest merkle root (returns bytes32(0) if no roots exist)
*/
function getLatestMerkleRoot() external view override returns (bytes32) {
uint256 length = merkleRootHistory.length;
return length == 0 ? bytes32(0) : merkleRootHistory[length - 1];
}
/**
* @notice Get the total number of merkle roots in history
* @return The total count of merkle roots
*/
function getMerkleRootHistoryLength() external view override returns (uint256) {
return merkleRootHistory.length;
}
/**
* @notice Check if an operator has claimed rewards for a specific root index
* @param operatorAddress Address of the operator
* @param rootIndex Index of the merkle root to check
* @return True if the operator has claimed rewards for this root index
*/
function hasClaimedByIndex(
address operatorAddress,
uint256 rootIndex
) external view override returns (bool) {
return operatorClaimedByIndex[operatorAddress][rootIndex];
}
/**
* @notice Function to receive ETH
*/
receive() external payable {}
}

View file

@ -1,49 +0,0 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;
import {IRewardsRegistry} from "../interfaces/IRewardsRegistry.sol";
/**
* @title Storage variables for the RewardsRegistry contract
* @notice This storage contract is separate from the logic to simplify the upgrade process
*/
abstract contract RewardsRegistryStorage is IRewardsRegistry {
/**
*
* IMMUTABLES
*
*/
/// @notice Address of the AVS (Service Manager)
address public immutable avs;
/**
*
* STATE VARIABLES
*
*/
/// @notice Address of the rewards agent contract
address public rewardsAgent;
/// @notice History of all merkle roots, accessible by index
bytes32[] public merkleRootHistory;
/// @notice Mapping from operator to merkle root index to claimed status
mapping(address => mapping(uint256 => bool)) public operatorClaimedByIndex;
/**
* @notice Constructor to set up the immutable AVS address
* @param _avs Address of the AVS (Service Manager)
* @param _rewardsAgent Address of the rewards agent contract
*/
constructor(
address _avs,
address _rewardsAgent
) {
avs = _avs;
rewardsAgent = _rewardsAgent;
}
// storage gap for upgradeability
uint256[49] private __GAP;
}

View file

@ -25,7 +25,6 @@ import {
} from "eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol";
import {IServiceManager, IServiceManagerUI} from "../interfaces/IServiceManager.sol";
import {IRewardsRegistry} from "../interfaces/IRewardsRegistry.sol";
import {ServiceManagerBaseStorage} from "./ServiceManagerBaseStorage.sol";
/**
@ -254,117 +253,6 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage, IAVSRegistrar
revert("ServiceManagerBase: setRewardsInitiator is deprecated");
}
/**
* @notice Sets the rewards registry for an operator set
* @param operatorSetId The ID of the operator set
* @param rewardsRegistry The address of the rewards registry
* @dev Only callable by the owner
*/
function setRewardsRegistry(
uint32 operatorSetId,
IRewardsRegistry rewardsRegistry
) external virtual override onlyOwner {
operatorSetToRewardsRegistry[operatorSetId] = rewardsRegistry;
emit RewardsRegistrySet(operatorSetId, address(rewardsRegistry));
}
/**
* @notice Claim rewards for an operator from a specific merkle root index using Substrate/Snowbridge positional Merkle proofs
* @param operatorSetId The ID of the operator set
* @param rootIndex Index of the merkle root to claim from
* @param operatorPoints Points earned by the operator
* @param numberOfLeaves The total number of leaves in the Merkle tree
* @param leafIndex The index of the operator's leaf in the Merkle tree
* @param proof Positional Merkle proof (from leaf to root)
*/
function claimOperatorRewards(
uint32 operatorSetId,
uint256 rootIndex,
uint256 operatorPoints,
uint256 numberOfLeaves,
uint256 leafIndex,
bytes32[] calldata proof
) external virtual override {
IRewardsRegistry rewardsRegistry = operatorSetToRewardsRegistry[operatorSetId];
if (address(rewardsRegistry) == address(0)) {
revert NoRewardsRegistryForOperatorSet();
}
_ensureOperatorIsPartOfOperatorSet(msg.sender, operatorSetId);
rewardsRegistry.claimRewards(
msg.sender, rootIndex, operatorPoints, numberOfLeaves, leafIndex, proof
);
}
/**
* @notice Claim rewards for an operator from the latest merkle root using Substrate/Snowbridge positional Merkle proofs
* @param operatorSetId The ID of the operator set
* @param operatorPoints Points earned by the operator
* @param numberOfLeaves The total number of leaves in the Merkle tree
* @param leafIndex The index of the operator's leaf in the Merkle tree
* @param proof Positional Merkle proof (from leaf to root)
*/
function claimLatestOperatorRewards(
uint32 operatorSetId,
uint256 operatorPoints,
uint256 numberOfLeaves,
uint256 leafIndex,
bytes32[] calldata proof
) external virtual override {
IRewardsRegistry rewardsRegistry = operatorSetToRewardsRegistry[operatorSetId];
if (address(rewardsRegistry) == address(0)) {
revert NoRewardsRegistryForOperatorSet();
}
_ensureOperatorIsPartOfOperatorSet(msg.sender, operatorSetId);
rewardsRegistry.claimLatestRewards(
msg.sender, operatorPoints, numberOfLeaves, leafIndex, proof
);
}
/**
* @notice Claim rewards for an operator from multiple merkle root indices using Substrate/Snowbridge positional Merkle proofs
* @param operatorSetId The ID of the operator set
* @param rootIndices Array of merkle root indices to claim from
* @param operatorPoints Array of points earned by the operator for each root
* @param numberOfLeaves Array with the total number of leaves for each Merkle tree
* @param leafIndices Array of leaf indices for the operator in each Merkle tree
* @param proofs Array of positional Merkle proofs for each claim
*/
function claimOperatorRewardsBatch(
uint32 operatorSetId,
uint256[] calldata rootIndices,
uint256[] calldata operatorPoints,
uint256[] calldata numberOfLeaves,
uint256[] calldata leafIndices,
bytes32[][] calldata proofs
) external virtual override {
IRewardsRegistry rewardsRegistry = operatorSetToRewardsRegistry[operatorSetId];
if (address(rewardsRegistry) == address(0)) {
revert NoRewardsRegistryForOperatorSet();
}
_ensureOperatorIsPartOfOperatorSet(msg.sender, operatorSetId);
rewardsRegistry.claimRewardsBatch(
msg.sender, rootIndices, operatorPoints, numberOfLeaves, leafIndices, proofs
);
}
/**
* @notice Sets the rewards agent address in the RewardsRegistry contract
* @param operatorSetId The ID of the operator set
* @param rewardsAgent New rewards agent address
* @dev Only callable by the owner
*/
function setRewardsAgent(
uint32 operatorSetId,
address rewardsAgent
) external virtual override onlyOwner {
IRewardsRegistry rewardsRegistry = operatorSetToRewardsRegistry[operatorSetId];
if (address(rewardsRegistry) == address(0)) {
revert NoRewardsRegistryForOperatorSet();
}
rewardsRegistry.setRewardsAgent(rewardsAgent);
}
/**
* @notice Forwards a call to Eigenlayer's RewardsCoordinator contract to set the address of the entity that can call `processClaim` on behalf of this contract.
* @param claimer The address of the entity that can call `processClaim` on behalf of the earner

View file

@ -17,7 +17,6 @@ import {
} from "eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol";
import {IServiceManager} from "../interfaces/IServiceManager.sol";
import {IRewardsRegistry} from "../interfaces/IRewardsRegistry.sol";
/**
* @title Storage variables for the `ServiceManagerBase` contract.
@ -43,9 +42,6 @@ abstract contract ServiceManagerBaseStorage is IServiceManager, OwnableUpgradeab
/// @notice The address of the entity that can initiate rewards
address public rewardsInitiator;
/// @notice Mapping from operator set ID to its respective RewardsRegistry
mapping(uint32 => IRewardsRegistry) public operatorSetToRewardsRegistry;
/// @notice Sets the (immutable) rewardsCoordinator`, `_permissionController`, and `_allocationManager` addresses
constructor(
IRewardsCoordinator __rewardsCoordinator,

View file

@ -1,679 +0,0 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
/* solhint-disable func-name-mixedcase */
import {Test, console, stdError} from "forge-std/Test.sol";
import {AVSDeployer} from "./utils/AVSDeployer.sol";
import {RewardsRegistry} from "../src/middleware/RewardsRegistry.sol";
import {IRewardsRegistry, IRewardsRegistryErrors} from "../src/interfaces/IRewardsRegistry.sol";
import {ScaleCodec} from "snowbridge/src/utils/ScaleCodec.sol";
contract RewardsRegistryTest is AVSDeployer {
address public nonRewardsAgent;
address public operatorAddress;
// Test data
bytes32 public merkleRoot;
bytes32 public newMerkleRoot;
uint256 public operatorPoints;
uint256 public leafIndex;
uint256 public numberOfLeaves;
bytes32[] public validProof;
bytes32[] public invalidProof;
// Events
event RewardsMerkleRootUpdated(bytes32 oldRoot, bytes32 indexed newRoot, uint256 newRootIndex);
event RewardsClaimedForIndex(
address indexed operatorAddress,
uint256 indexed rootIndex,
uint256 points,
uint256 rewardsAmount
);
event RewardsBatchClaimedForIndices(
address indexed operatorAddress,
uint256[] rootIndices,
uint256[] points,
uint256 totalRewardsAmount
);
function setUp() public {
_deployMockEigenLayerAndAVS();
// Set up test addresses
nonRewardsAgent = address(0x5678);
operatorAddress = address(0xABCD);
// Set up test data
operatorPoints = 100;
leafIndex = 0; // Position of our leaf in the tree
numberOfLeaves = 2; // Simple tree with 2 leaves
// For sorted-hash Merkle proofs, we need to use SCALE encoding
// Our leaf (the one we want to prove exists in the tree)
bytes memory preimage =
abi.encodePacked(operatorAddress, ScaleCodec.encodeU32(uint32(operatorPoints)));
bytes32 leaf = keccak256(preimage);
// Sibling leaf (another element in the Merkle tree)
bytes memory siblingPreimage =
abi.encodePacked(address(0x1234), ScaleCodec.encodeU32(uint32(50)));
bytes32 siblingLeaf = keccak256(siblingPreimage);
// For sorted-hash merkle proof, smaller hash goes first
merkleRoot = leaf < siblingLeaf
? keccak256(abi.encodePacked(leaf, siblingLeaf))
: keccak256(abi.encodePacked(siblingLeaf, leaf));
// The proof to verify our leaf is just the sibling leaf
validProof = new bytes32[](1);
validProof[0] = siblingLeaf;
// For tests that need a second Merkle root
bytes memory newSiblingPreimage =
abi.encodePacked(address(0x5678), ScaleCodec.encodeU32(uint32(75)));
bytes32 newSiblingLeaf = keccak256(newSiblingPreimage);
// For sorted-hash merkle proof, smaller hash goes first
newMerkleRoot = leaf < newSiblingLeaf
? keccak256(abi.encodePacked(leaf, newSiblingLeaf))
: keccak256(abi.encodePacked(newSiblingLeaf, leaf));
// An invalid proof
invalidProof = new bytes32[](1);
invalidProof[0] = keccak256(abi.encodePacked("wrong sibling"));
}
// Helper to test our proof construction
function test_verifyProofConstruction() public {
// Test that our proof construction is valid using the contract's internal validation
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
vm.deal(address(rewardsRegistry), 1000 ether);
vm.prank(address(serviceManager));
// This should not revert if the proof is valid
rewardsRegistry.claimLatestRewards(
operatorAddress, operatorPoints, numberOfLeaves, leafIndex, validProof
);
assertTrue(
rewardsRegistry.hasClaimedByIndex(operatorAddress, 0),
"Proof verification should succeed"
);
}
/**
*
* Constructor Tests *
*
*/
function test_constructor() public view {
assertEq(
rewardsRegistry.avs(), address(serviceManager), "AVS address should be set correctly"
);
assertEq(
rewardsRegistry.rewardsAgent(),
mockRewardsAgent,
"Rewards agent address should be set correctly"
);
}
/**
*
* updateRewardsMerkleRoot Tests *
*
*/
function test_updateRewardsMerkleRoot() public {
vm.prank(mockRewardsAgent);
vm.expectEmit(true, true, true, true);
emit RewardsMerkleRootUpdated(bytes32(0), merkleRoot, 0);
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
assertEq(rewardsRegistry.getLatestMerkleRoot(), merkleRoot, "Merkle root should be updated");
}
function test_updateRewardsMerkleRoot_NotRewardsAgent() public {
vm.prank(nonRewardsAgent);
vm.expectRevert(abi.encodeWithSelector(IRewardsRegistryErrors.OnlyRewardsAgent.selector));
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
}
function test_updateRewardsMerkleRoot_EmitEvent() public {
// First update
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
// Second update with expectation of emitting event with correct old and new roots
vm.prank(mockRewardsAgent);
vm.expectEmit(true, true, true, true);
emit RewardsMerkleRootUpdated(merkleRoot, newMerkleRoot, 1);
rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot);
}
/**
*
* setRewardsAgent Tests *
*
*/
function test_setRewardsAgent() public {
address newRewardsAgent = address(0x9876);
vm.prank(address(serviceManager));
rewardsRegistry.setRewardsAgent(newRewardsAgent);
assertEq(rewardsRegistry.rewardsAgent(), newRewardsAgent, "Rewards agent should be updated");
}
function test_setRewardsAgent_NotAVS() public {
vm.prank(nonRewardsAgent);
vm.expectRevert(abi.encodeWithSelector(IRewardsRegistryErrors.OnlyAVS.selector));
rewardsRegistry.setRewardsAgent(address(0x9876));
}
/**
*
* claimRewards Tests *
*
*/
function test_claimLatestRewards() public {
// First update merkle root
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
// Add ETH to contract for rewards
vm.deal(address(rewardsRegistry), 1000 ether);
uint256 initialBalance = operatorAddress.balance;
vm.prank(address(serviceManager));
vm.expectEmit(true, true, true, true);
emit RewardsClaimedForIndex(operatorAddress, 0, operatorPoints, operatorPoints);
rewardsRegistry.claimLatestRewards(
operatorAddress, operatorPoints, numberOfLeaves, leafIndex, validProof
);
// Verify state changes
assertTrue(
rewardsRegistry.hasClaimedByIndex(operatorAddress, 0),
"Operator should have claimed from the latest root index"
);
assertEq(
operatorAddress.balance,
initialBalance + operatorPoints,
"Operator should receive correct rewards"
);
}
function test_claimLatestRewards_NotAVS() public {
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
vm.prank(nonRewardsAgent);
vm.expectRevert(abi.encodeWithSelector(IRewardsRegistryErrors.OnlyAVS.selector));
rewardsRegistry.claimLatestRewards(
operatorAddress, operatorPoints, numberOfLeaves, leafIndex, validProof
);
}
function test_claimLatestRewards_AlreadyClaimed() public {
// First update merkle root
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
// Add ETH to contract for rewards
vm.deal(address(rewardsRegistry), 1000 ether);
// First claim succeeds
vm.prank(address(serviceManager));
rewardsRegistry.claimLatestRewards(
operatorAddress, operatorPoints, numberOfLeaves, leafIndex, validProof
);
// Second claim fails
vm.prank(address(serviceManager));
vm.expectRevert(
abi.encodeWithSelector(IRewardsRegistryErrors.RewardsAlreadyClaimedForIndex.selector)
);
rewardsRegistry.claimLatestRewards(
operatorAddress, operatorPoints, numberOfLeaves, leafIndex, validProof
);
}
function test_claimLatestRewards_InvalidProof() public {
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
vm.prank(address(serviceManager));
vm.expectRevert(abi.encodeWithSelector(IRewardsRegistryErrors.InvalidMerkleProof.selector));
rewardsRegistry.claimLatestRewards(
operatorAddress, operatorPoints, numberOfLeaves, leafIndex, invalidProof
);
}
function test_claimLatestRewards_NoMerkleRoot() public {
// No merkle roots exist yet
vm.prank(address(serviceManager));
vm.expectRevert(
abi.encodeWithSelector(IRewardsRegistryErrors.RewardsMerkleRootNotSet.selector)
);
rewardsRegistry.claimLatestRewards(
operatorAddress, operatorPoints, numberOfLeaves, leafIndex, validProof
);
}
function test_claimLatestRewards_DifferentRoot() public {
// First merkle root
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
// Add ETH to contract for rewards
vm.deal(address(rewardsRegistry), 1000 ether);
// First claim succeeds
vm.prank(address(serviceManager));
rewardsRegistry.claimLatestRewards(
operatorAddress, operatorPoints, numberOfLeaves, leafIndex, validProof
);
// Update to new merkle root
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot);
// Create a new valid proof for the new root
bytes32[] memory newProof = new bytes32[](1);
bytes memory newSiblingPreimage =
abi.encodePacked(address(0x5678), ScaleCodec.encodeU32(uint32(75)));
bytes32 newSiblingLeaf = keccak256(newSiblingPreimage);
newProof[0] = newSiblingLeaf;
// Operator can claim again with new merkle root
vm.prank(address(serviceManager));
rewardsRegistry.claimLatestRewards(
operatorAddress, operatorPoints, numberOfLeaves, leafIndex, newProof
);
// Verify both indices are now claimed
assertTrue(
rewardsRegistry.hasClaimedByIndex(operatorAddress, 0),
"Operator should have claimed from first root index"
);
assertTrue(
rewardsRegistry.hasClaimedByIndex(operatorAddress, 1),
"Operator should have claimed from second root index"
);
}
function test_claimLatestRewards_InsufficientBalance() public {
// Set merkle root
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
// No ETH in contract for rewards - ensure contract has 0 balance
vm.deal(address(rewardsRegistry), 0);
vm.prank(address(serviceManager));
vm.expectRevert(
abi.encodeWithSelector(IRewardsRegistryErrors.RewardsTransferFailed.selector)
);
rewardsRegistry.claimLatestRewards(
operatorAddress, operatorPoints, numberOfLeaves, leafIndex, validProof
);
}
function test_receive() public {
// Test that the contract can receive ETH
uint256 amount = 1 ether;
vm.deal(address(this), amount);
(bool success,) = address(rewardsRegistry).call{value: amount}("");
assertTrue(success, "Contract should be able to receive ETH");
assertEq(address(rewardsRegistry).balance, amount, "Contract balance should increase");
}
/**
*
* Merkle Root History Tests *
*
*/
function test_getMerkleRootByIndex() public {
// Add first root
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
// Add second root
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot);
// Test accessing by index
assertEq(
rewardsRegistry.getMerkleRootByIndex(0),
merkleRoot,
"First root should be accessible by index 0"
);
assertEq(
rewardsRegistry.getMerkleRootByIndex(1),
newMerkleRoot,
"Second root should be accessible by index 1"
);
}
function test_getMerkleRootByIndex_InvalidIndex() public {
// Add one root
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
// Try to access invalid index
vm.expectRevert(
abi.encodeWithSelector(IRewardsRegistryErrors.InvalidMerkleRootIndex.selector)
);
rewardsRegistry.getMerkleRootByIndex(1);
}
function test_getLatestMerkleRootIndex() public {
// Initially should return 0 when no roots exist
assertEq(
rewardsRegistry.getLatestMerkleRootIndex(), 0, "Should return 0 when no roots exist"
);
// Add first root
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
assertEq(rewardsRegistry.getLatestMerkleRootIndex(), 0, "Should return 0 for first root");
// Add second root
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot);
assertEq(rewardsRegistry.getLatestMerkleRootIndex(), 1, "Should return 1 for second root");
}
function test_getMerkleRootHistoryLength() public {
// Initially should be 0
assertEq(rewardsRegistry.getMerkleRootHistoryLength(), 0, "Should be 0 initially");
// Add first root
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
assertEq(rewardsRegistry.getMerkleRootHistoryLength(), 1, "Should be 1 after first root");
// Add second root
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot);
assertEq(rewardsRegistry.getMerkleRootHistoryLength(), 2, "Should be 2 after second root");
}
function test_historyPreservesQuickAccess() public {
// Add multiple roots
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot);
// Latest root should be accessible directly without index
assertEq(
rewardsRegistry.getLatestMerkleRoot(),
newMerkleRoot,
"getLatestMerkleRoot should return latest root"
);
// But we should also be able to access by index
assertEq(
rewardsRegistry.getMerkleRootByIndex(1),
newMerkleRoot,
"Latest root should also be accessible by index"
);
assertEq(
rewardsRegistry.getMerkleRootByIndex(0),
merkleRoot,
"Previous root should be accessible by index"
);
}
/**
*
* Index-based Claim Tests *
*
*/
function test_claimRewards() public {
// Add multiple roots
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot);
// Add ETH to contract for rewards
vm.deal(address(rewardsRegistry), 1000 ether);
uint256 initialBalance = operatorAddress.balance;
// Claim from first root (index 0)
vm.prank(address(serviceManager));
vm.expectEmit(true, true, true, true);
emit RewardsClaimedForIndex(operatorAddress, 0, operatorPoints, operatorPoints);
rewardsRegistry.claimRewards(
operatorAddress, 0, operatorPoints, numberOfLeaves, leafIndex, validProof
);
// Verify state changes
assertTrue(
rewardsRegistry.hasClaimedByIndex(operatorAddress, 0),
"Operator should have claimed from index 0"
);
assertEq(
operatorAddress.balance,
initialBalance + operatorPoints,
"Operator should receive correct rewards"
);
}
function test_claimRewards_InvalidIndex() public {
vm.deal(address(rewardsRegistry), 1000 ether);
vm.prank(address(serviceManager));
vm.expectRevert(
abi.encodeWithSelector(IRewardsRegistryErrors.InvalidMerkleRootIndex.selector)
);
rewardsRegistry.claimRewards(
operatorAddress, 0, operatorPoints, numberOfLeaves, leafIndex, validProof
);
}
function test_claimRewards_AlreadyClaimed() public {
// Add root
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
vm.deal(address(rewardsRegistry), 1000 ether);
// First claim succeeds
vm.prank(address(serviceManager));
rewardsRegistry.claimRewards(
operatorAddress, 0, operatorPoints, numberOfLeaves, leafIndex, validProof
);
// Second claim fails
vm.prank(address(serviceManager));
vm.expectRevert(
abi.encodeWithSelector(IRewardsRegistryErrors.RewardsAlreadyClaimedForIndex.selector)
);
rewardsRegistry.claimRewards(
operatorAddress, 0, operatorPoints, numberOfLeaves, leafIndex, validProof
);
}
function test_hasClaimedByIndex() public {
// Add root
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
vm.deal(address(rewardsRegistry), 1000 ether);
// Initially not claimed
assertFalse(
rewardsRegistry.hasClaimedByIndex(operatorAddress, 0),
"Should not have claimed initially"
);
// Claim
vm.prank(address(serviceManager));
rewardsRegistry.claimRewards(
operatorAddress, 0, operatorPoints, numberOfLeaves, leafIndex, validProof
);
// Now claimed
assertTrue(
rewardsRegistry.hasClaimedByIndex(operatorAddress, 0), "Should have claimed after claim"
);
}
/**
*
* Batch Claim Tests *
*
*/
function test_claimRewardsBatch() public {
// Add multiple roots
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot);
vm.deal(address(rewardsRegistry), 1000 ether);
// Prepare batch claim data
uint256[] memory rootIndices = new uint256[](2);
rootIndices[0] = 0;
rootIndices[1] = 1;
uint256[] memory points = new uint256[](2);
points[0] = operatorPoints;
points[1] = operatorPoints;
bytes32[][] memory proofs = new bytes32[][](2);
proofs[0] = validProof;
// Create proof for second root
bytes32[] memory newProof = new bytes32[](1);
bytes memory newSiblingPreimage =
abi.encodePacked(address(0x5678), ScaleCodec.encodeU32(uint32(75)));
bytes32 newSiblingLeaf = keccak256(newSiblingPreimage);
newProof[0] = newSiblingLeaf;
proofs[1] = newProof;
uint256 initialBalance = operatorAddress.balance;
// Batch claim
vm.prank(address(serviceManager));
vm.expectEmit(true, true, true, true);
emit RewardsBatchClaimedForIndices(operatorAddress, rootIndices, points, operatorPoints * 2);
uint256[] memory widths = new uint256[](2);
widths[0] = numberOfLeaves;
widths[1] = numberOfLeaves;
uint256[] memory leafIdxs = new uint256[](2);
leafIdxs[0] = leafIndex;
leafIdxs[1] = leafIndex;
rewardsRegistry.claimRewardsBatch(
operatorAddress, rootIndices, points, widths, leafIdxs, proofs
);
// Verify both indices are claimed
assertTrue(
rewardsRegistry.hasClaimedByIndex(operatorAddress, 0),
"Should have claimed from index 0"
);
assertTrue(
rewardsRegistry.hasClaimedByIndex(operatorAddress, 1),
"Should have claimed from index 1"
);
// Verify total rewards received
assertEq(
operatorAddress.balance,
initialBalance + (operatorPoints * 2),
"Should receive rewards from both claims"
);
}
function test_claimRewardsBatch_ArrayLengthMismatch() public {
uint256[] memory rootIndices = new uint256[](2);
uint256[] memory points = new uint256[](1); // Wrong length
bytes32[][] memory proofs = new bytes32[][](2);
vm.prank(address(serviceManager));
vm.expectRevert(abi.encodeWithSelector(IRewardsRegistryErrors.ArrayLengthMismatch.selector));
uint256[] memory widths = new uint256[](2);
uint256[] memory leafIdxs = new uint256[](2);
rewardsRegistry.claimRewardsBatch(
operatorAddress, rootIndices, points, widths, leafIdxs, proofs
);
}
function test_claimRewardsBatch_PartialClaimFailure() public {
// Add roots
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot);
vm.deal(address(rewardsRegistry), 1000 ether);
// Claim from index 0 first
vm.prank(address(serviceManager));
rewardsRegistry.claimRewards(
operatorAddress, 0, operatorPoints, numberOfLeaves, leafIndex, validProof
);
// Now try batch claim that includes already claimed index 0
uint256[] memory rootIndices = new uint256[](2);
rootIndices[0] = 0; // Already claimed
rootIndices[1] = 1;
uint256[] memory points = new uint256[](2);
points[0] = operatorPoints;
points[1] = operatorPoints;
bytes32[][] memory proofs = new bytes32[][](2);
proofs[0] = validProof;
bytes32[] memory newProof = new bytes32[](1);
bytes memory newSiblingPreimage =
abi.encodePacked(address(0x5678), ScaleCodec.encodeU32(uint32(75)));
bytes32 newSiblingLeaf = keccak256(newSiblingPreimage);
newProof[0] = newSiblingLeaf;
proofs[1] = newProof;
// Should fail because index 0 is already claimed
vm.prank(address(serviceManager));
vm.expectRevert(
abi.encodeWithSelector(IRewardsRegistryErrors.RewardsAlreadyClaimedForIndex.selector)
);
uint256[] memory widths = new uint256[](2);
widths[0] = numberOfLeaves;
widths[1] = numberOfLeaves;
uint256[] memory leafIdxs = new uint256[](2);
leafIdxs[0] = leafIndex;
leafIdxs[1] = leafIndex;
rewardsRegistry.claimRewardsBatch(
operatorAddress, rootIndices, points, widths, leafIdxs, proofs
);
}
}

View file

@ -1,709 +0,0 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
/* solhint-disable func-name-mixedcase */
import {Test, console, stdError} from "forge-std/Test.sol";
import {
IAllocationManager
} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
import {AVSDeployer} from "./utils/AVSDeployer.sol";
import {RewardsRegistry} from "../src/middleware/RewardsRegistry.sol";
import {IRewardsRegistry, IRewardsRegistryErrors} from "../src/interfaces/IRewardsRegistry.sol";
import {ServiceManagerMock} from "./mocks/ServiceManagerMock.sol";
import {IServiceManager, IServiceManagerErrors} from "../src/interfaces/IServiceManager.sol";
import {ScaleCodec} from "snowbridge/src/utils/ScaleCodec.sol";
contract ServiceManagerRewardsRegistryTest is AVSDeployer {
// Test addresses
address public operatorAddress;
address public nonOperatorAddress;
// Test data
uint32 public operatorSetId;
bytes32 public merkleRoot;
bytes32 public secondMerkleRoot;
bytes32 public thirdMerkleRoot;
uint256 public operatorPoints;
uint256 public secondOperatorPoints;
uint256 public thirdOperatorPoints;
uint256 public leafIndex;
uint256 public numberOfLeaves;
bytes32[] public validProof;
bytes32[] public secondValidProof;
bytes32[] public thirdValidProof;
// Events
event RewardsRegistrySet(uint32 indexed operatorSetId, address indexed rewardsRegistry);
event RewardsClaimedForIndex(
address indexed operatorAddress,
uint256 indexed rootIndex,
uint256 points,
uint256 rewardsAmount
);
event RewardsBatchClaimedForIndices(
address indexed operatorAddress,
uint256[] rootIndices,
uint256[] points,
uint256 totalRewardsAmount
);
function setUp() public {
_deployMockEigenLayerAndAVS();
// Set up test addresses
operatorAddress = address(0xABCD);
nonOperatorAddress = address(0x5678);
// Configure test data
operatorSetId = 1;
operatorPoints = 100;
secondOperatorPoints = 200;
thirdOperatorPoints = 150;
leafIndex = 0; // Position of our leaf in the tree
numberOfLeaves = 2; // Simple tree with 2 leaves
// Create multiple merkle trees for comprehensive batch testing
_createFirstMerkleTree();
_createSecondMerkleTree();
_createThirdMerkleTree();
// Set up the rewards registry for the operator set
vm.prank(avsOwner);
serviceManager.setRewardsRegistry(operatorSetId, IRewardsRegistry(address(rewardsRegistry)));
// Set all three merkle roots to create a history
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(secondMerkleRoot);
vm.prank(mockRewardsAgent);
rewardsRegistry.updateRewardsMerkleRoot(thirdMerkleRoot);
// Add funds to the registry for rewards
vm.deal(address(rewardsRegistry), 1000 ether);
}
function _createFirstMerkleTree() internal {
// Create first merkle tree with SCALE encoding
bytes memory preimage =
abi.encodePacked(operatorAddress, ScaleCodec.encodeU32(uint32(operatorPoints)));
bytes32 leaf = keccak256(preimage);
bytes memory siblingPreimage =
abi.encodePacked(address(0x1111), ScaleCodec.encodeU32(uint32(50)));
bytes32 siblingLeaf = keccak256(siblingPreimage);
// For sorted-hash merkle proof, smaller hash goes first
merkleRoot = leaf < siblingLeaf
? keccak256(abi.encodePacked(leaf, siblingLeaf))
: keccak256(abi.encodePacked(siblingLeaf, leaf));
validProof = new bytes32[](1);
validProof[0] = siblingLeaf;
}
function _createSecondMerkleTree() internal {
// Create second merkle tree with different points using SCALE encoding
bytes memory preimage =
abi.encodePacked(operatorAddress, ScaleCodec.encodeU32(uint32(secondOperatorPoints)));
bytes32 leaf = keccak256(preimage);
bytes memory siblingPreimage =
abi.encodePacked(address(0x2222), ScaleCodec.encodeU32(uint32(75)));
bytes32 siblingLeaf = keccak256(siblingPreimage);
// For sorted-hash merkle proof, smaller hash goes first
secondMerkleRoot = leaf < siblingLeaf
? keccak256(abi.encodePacked(leaf, siblingLeaf))
: keccak256(abi.encodePacked(siblingLeaf, leaf));
secondValidProof = new bytes32[](1);
secondValidProof[0] = siblingLeaf;
}
function _createThirdMerkleTree() internal {
// Create third merkle tree with different points using SCALE encoding
bytes memory preimage =
abi.encodePacked(operatorAddress, ScaleCodec.encodeU32(uint32(thirdOperatorPoints)));
bytes32 leaf = keccak256(preimage);
bytes memory siblingPreimage =
abi.encodePacked(address(0x3333), ScaleCodec.encodeU32(uint32(60)));
bytes32 siblingLeaf = keccak256(siblingPreimage);
// For sorted-hash merkle proof, smaller hash goes first
thirdMerkleRoot = leaf < siblingLeaf
? keccak256(abi.encodePacked(leaf, siblingLeaf))
: keccak256(abi.encodePacked(siblingLeaf, leaf));
thirdValidProof = new bytes32[](1);
thirdValidProof[0] = siblingLeaf;
}
function test_setRewardsRegistry() public {
uint32 newOperatorSetId = 2;
RewardsRegistry newRewardsRegistry =
new RewardsRegistry(address(serviceManager), mockRewardsAgent);
vm.prank(avsOwner);
vm.expectEmit(true, true, true, true);
emit RewardsRegistrySet(newOperatorSetId, address(newRewardsRegistry));
serviceManager.setRewardsRegistry(
newOperatorSetId, IRewardsRegistry(address(newRewardsRegistry))
);
assertEq(
address(serviceManager.operatorSetToRewardsRegistry(newOperatorSetId)),
address(newRewardsRegistry),
"Rewards registry should be set correctly"
);
}
function test_setRewardsRegistry_NotOwner() public {
uint32 newOperatorSetId = 2;
RewardsRegistry newRewardsRegistry =
new RewardsRegistry(address(serviceManager), mockRewardsAgent);
vm.prank(nonOperatorAddress);
vm.expectRevert(bytes("Ownable: caller is not the owner"));
serviceManager.setRewardsRegistry(
newOperatorSetId, IRewardsRegistry(address(newRewardsRegistry))
);
}
function test_claimLatestOperatorRewards() public {
uint256 initialBalance = operatorAddress.balance;
vm.mockCall(
address(allocationManager),
abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector),
abi.encode(true)
);
vm.prank(operatorAddress);
vm.expectEmit(true, true, true, true);
emit RewardsClaimedForIndex(operatorAddress, 2, thirdOperatorPoints, thirdOperatorPoints);
serviceManager.claimLatestOperatorRewards(
operatorSetId, thirdOperatorPoints, numberOfLeaves, leafIndex, thirdValidProof
);
assertEq(
operatorAddress.balance,
initialBalance + thirdOperatorPoints,
"Operator should receive correct rewards"
);
}
function test_claimLatestOperatorRewards_NoRewardsRegistry() public {
uint32 invalidSetId = 999;
vm.prank(operatorAddress);
vm.expectRevert(
abi.encodeWithSelector(IServiceManagerErrors.NoRewardsRegistryForOperatorSet.selector)
);
serviceManager.claimLatestOperatorRewards(
invalidSetId, operatorPoints, numberOfLeaves, leafIndex, validProof
);
}
function test_claimLatestOperatorRewards_AlreadyClaimed() public {
vm.mockCall(
address(allocationManager),
abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector),
abi.encode(true)
);
// First claim (uses latest merkle root - index 2)
vm.prank(operatorAddress);
serviceManager.claimLatestOperatorRewards(
operatorSetId, thirdOperatorPoints, numberOfLeaves, leafIndex, thirdValidProof
);
// Second claim should fail
vm.prank(operatorAddress);
vm.expectRevert(
abi.encodeWithSelector(IRewardsRegistryErrors.RewardsAlreadyClaimedForIndex.selector)
);
serviceManager.claimLatestOperatorRewards(
operatorSetId, thirdOperatorPoints, numberOfLeaves, leafIndex, thirdValidProof
);
}
function test_claimOperatorRewards() public {
uint256 initialBalance = operatorAddress.balance;
vm.mockCall(
address(allocationManager),
abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector),
abi.encode(true)
);
vm.prank(operatorAddress);
vm.expectEmit(true, true, true, true);
emit RewardsClaimedForIndex(operatorAddress, 0, operatorPoints, operatorPoints);
serviceManager.claimOperatorRewards(
operatorSetId, 0, operatorPoints, numberOfLeaves, leafIndex, validProof
);
assertEq(
operatorAddress.balance,
initialBalance + operatorPoints,
"Operator should receive correct rewards"
);
}
function test_claimOperatorRewards_DifferentIndices() public {
uint256 initialBalance = operatorAddress.balance;
vm.mockCall(
address(allocationManager),
abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector),
abi.encode(true)
);
// Claim from index 1 (second merkle root)
vm.prank(operatorAddress);
serviceManager.claimOperatorRewards(
operatorSetId, 1, secondOperatorPoints, numberOfLeaves, leafIndex, secondValidProof
);
assertEq(
operatorAddress.balance,
initialBalance + secondOperatorPoints,
"Operator should receive rewards from second root"
);
// Claim from index 2 (third merkle root)
vm.prank(operatorAddress);
serviceManager.claimOperatorRewards(
operatorSetId, 2, thirdOperatorPoints, numberOfLeaves, leafIndex, thirdValidProof
);
assertEq(
operatorAddress.balance,
initialBalance + secondOperatorPoints + thirdOperatorPoints,
"Operator should receive rewards from both roots"
);
// Verify claim status
assertFalse(
rewardsRegistry.hasClaimedByIndex(operatorAddress, 0), "Index 0 should not be claimed"
);
assertTrue(
rewardsRegistry.hasClaimedByIndex(operatorAddress, 1), "Index 1 should be claimed"
);
assertTrue(
rewardsRegistry.hasClaimedByIndex(operatorAddress, 2), "Index 2 should be claimed"
);
}
function test_claimOperatorRewards_InvalidIndex() public {
vm.mockCall(
address(allocationManager),
abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector),
abi.encode(true)
);
vm.prank(operatorAddress);
vm.expectRevert(
abi.encodeWithSelector(IRewardsRegistryErrors.InvalidMerkleRootIndex.selector)
);
serviceManager.claimOperatorRewards(
operatorSetId, 999, operatorPoints, numberOfLeaves, leafIndex, validProof
);
}
function test_claimOperatorRewards_AlreadyClaimed() public {
vm.mockCall(
address(allocationManager),
abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector),
abi.encode(true)
);
// First claim
vm.prank(operatorAddress);
serviceManager.claimOperatorRewards(
operatorSetId, 0, operatorPoints, numberOfLeaves, leafIndex, validProof
);
// Second claim should fail
vm.prank(operatorAddress);
vm.expectRevert(
abi.encodeWithSelector(IRewardsRegistryErrors.RewardsAlreadyClaimedForIndex.selector)
);
serviceManager.claimOperatorRewards(
operatorSetId,
0,
operatorPoints,
2, // numberOfLeaves (operator + sibling)
0, // leafIndex (assuming operator leaf comes first)
validProof
);
}
function test_claimOperatorRewardsBatch() public {
// Test claiming from multiple different merkle root indices
uint256[] memory rootIndices = new uint256[](3);
rootIndices[0] = 0; // First merkle root
rootIndices[1] = 1; // Second merkle root
rootIndices[2] = 2; // Third merkle root
uint256[] memory points = new uint256[](3);
points[0] = operatorPoints;
points[1] = secondOperatorPoints;
points[2] = thirdOperatorPoints;
bytes32[][] memory proofs = new bytes32[][](3);
proofs[0] = validProof;
proofs[1] = secondValidProof;
proofs[2] = thirdValidProof;
uint256 expectedTotalRewards = operatorPoints + secondOperatorPoints + thirdOperatorPoints;
uint256 initialBalance = operatorAddress.balance;
vm.mockCall(
address(allocationManager),
abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector),
abi.encode(true)
);
vm.prank(operatorAddress);
vm.expectEmit(true, true, true, true);
emit RewardsBatchClaimedForIndices(
operatorAddress, rootIndices, points, expectedTotalRewards
);
uint256[] memory widths = new uint256[](3);
widths[0] = 2;
widths[1] = 2;
widths[2] = 2;
uint256[] memory leafIdxs = new uint256[](3);
leafIdxs[0] = 0;
leafIdxs[1] = 0;
leafIdxs[2] = 0;
serviceManager.claimOperatorRewardsBatch(
operatorSetId, rootIndices, points, widths, leafIdxs, proofs
);
// Verify final balance includes all rewards
assertEq(
operatorAddress.balance,
initialBalance + expectedTotalRewards,
"Operator should receive rewards from all three claims"
);
// Verify all indices are now claimed
assertTrue(
rewardsRegistry.hasClaimedByIndex(operatorAddress, 0),
"Operator should have claimed from index 0"
);
assertTrue(
rewardsRegistry.hasClaimedByIndex(operatorAddress, 1),
"Operator should have claimed from index 1"
);
assertTrue(
rewardsRegistry.hasClaimedByIndex(operatorAddress, 2),
"Operator should have claimed from index 2"
);
}
function test_claimOperatorRewardsBatch_PartialBatch() public {
// Test claiming from only some of the available merkle roots
uint256[] memory rootIndices = new uint256[](2);
rootIndices[0] = 0; // First merkle root
rootIndices[1] = 2; // Third merkle root (skipping second)
uint256[] memory points = new uint256[](2);
points[0] = operatorPoints;
points[1] = thirdOperatorPoints;
bytes32[][] memory proofs = new bytes32[][](2);
proofs[0] = validProof;
proofs[1] = thirdValidProof;
uint256 expectedTotalRewards = operatorPoints + thirdOperatorPoints;
uint256 initialBalance = operatorAddress.balance;
vm.mockCall(
address(allocationManager),
abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector),
abi.encode(true)
);
vm.prank(operatorAddress);
uint256[] memory widths2 = new uint256[](2);
widths2[0] = 2;
widths2[1] = 2;
uint256[] memory leafIdxs2 = new uint256[](2);
leafIdxs2[0] = 0;
leafIdxs2[1] = 0;
serviceManager.claimOperatorRewardsBatch(
operatorSetId, rootIndices, points, widths2, leafIdxs2, proofs
);
// Verify balance and claim status
assertEq(
operatorAddress.balance,
initialBalance + expectedTotalRewards,
"Operator should receive rewards from claimed indices"
);
assertTrue(
rewardsRegistry.hasClaimedByIndex(operatorAddress, 0),
"Operator should have claimed from index 0"
);
assertFalse(
rewardsRegistry.hasClaimedByIndex(operatorAddress, 1),
"Operator should NOT have claimed from index 1"
);
assertTrue(
rewardsRegistry.hasClaimedByIndex(operatorAddress, 2),
"Operator should have claimed from index 2"
);
}
function test_claimOperatorRewardsBatch_ArrayLengthMismatch() public {
uint256[] memory rootIndices = new uint256[](2);
uint256[] memory points = new uint256[](1); // Wrong length
bytes32[][] memory proofs = new bytes32[][](2);
vm.mockCall(
address(allocationManager),
abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector),
abi.encode(true)
);
vm.prank(operatorAddress);
vm.expectRevert(abi.encodeWithSelector(IRewardsRegistryErrors.ArrayLengthMismatch.selector));
uint256[] memory numLeaves = new uint256[](3);
numLeaves[0] = 2;
numLeaves[1] = 2;
numLeaves[2] = 2;
uint256[] memory leafIndices = new uint256[](3);
leafIndices[0] = 0;
leafIndices[1] = 0;
leafIndices[2] = 0;
serviceManager.claimOperatorRewardsBatch(
operatorSetId, rootIndices, points, numLeaves, leafIndices, proofs
);
}
function test_claimOperatorRewardsBatch_AlreadyClaimedIndex() public {
// First claim from index 1
vm.mockCall(
address(allocationManager),
abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector),
abi.encode(true)
);
vm.prank(operatorAddress);
serviceManager.claimOperatorRewards(
operatorSetId,
1,
secondOperatorPoints,
2, // numberOfLeaves (operator + sibling)
0, // leafIndex (assuming operator leaf comes first)
secondValidProof
);
// Now try to batch claim including the already claimed index 1
uint256[] memory rootIndices = new uint256[](2);
rootIndices[0] = 0;
rootIndices[1] = 1; // Already claimed
uint256[] memory points = new uint256[](2);
points[0] = operatorPoints;
points[1] = secondOperatorPoints;
bytes32[][] memory proofs = new bytes32[][](2);
proofs[0] = validProof;
proofs[1] = secondValidProof;
vm.prank(operatorAddress);
vm.expectRevert(
abi.encodeWithSelector(IRewardsRegistryErrors.RewardsAlreadyClaimedForIndex.selector)
);
uint256[] memory numLeaves = new uint256[](2);
numLeaves[0] = 2;
numLeaves[1] = 2;
uint256[] memory leafIndices = new uint256[](2);
leafIndices[0] = 0;
leafIndices[1] = 0;
serviceManager.claimOperatorRewardsBatch(
operatorSetId, rootIndices, points, numLeaves, leafIndices, proofs
);
}
function test_claimOperatorRewardsBatch_EmptyBatch() public {
uint256[] memory rootIndices = new uint256[](0);
uint256[] memory points = new uint256[](0);
bytes32[][] memory proofs = new bytes32[][](0);
vm.mockCall(
address(allocationManager),
abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector),
abi.encode(true)
);
uint256 initialBalance = operatorAddress.balance;
uint256[] memory numLeaves = new uint256[](0);
uint256[] memory leafIndices = new uint256[](0);
vm.prank(operatorAddress);
serviceManager.claimOperatorRewardsBatch(
operatorSetId, rootIndices, points, numLeaves, leafIndices, proofs
);
// Balance should remain unchanged
assertEq(
operatorAddress.balance,
initialBalance,
"Balance should remain unchanged for empty batch"
);
}
function test_integration_multipleOperatorSets() public {
// Set up a second operator set with a different registry
uint32 secondOperatorSetId = 2;
RewardsRegistry secondRegistry =
new RewardsRegistry(address(serviceManager), mockRewardsAgent);
// Set up the second registry
vm.prank(avsOwner);
serviceManager.setRewardsRegistry(
secondOperatorSetId, IRewardsRegistry(address(secondRegistry))
);
// Create a different merkle root for the second registry using SCALE encoding
bytes memory secondLeafPreimage =
abi.encodePacked(operatorAddress, ScaleCodec.encodeU32(uint32(operatorPoints)));
bytes32 secondLeaf = keccak256(secondLeafPreimage);
bytes memory secondSiblingPreimage =
abi.encodePacked(address(0x4444), ScaleCodec.encodeU32(uint32(80)));
bytes32 secondSiblingLeaf = keccak256(secondSiblingPreimage);
// For sorted-hash merkle proof, smaller hash goes first
bytes32 secondRegistryMerkleRoot = secondLeaf < secondSiblingLeaf
? keccak256(abi.encodePacked(secondLeaf, secondSiblingLeaf))
: keccak256(abi.encodePacked(secondSiblingLeaf, secondLeaf));
// Set the merkle root in the second registry
vm.prank(mockRewardsAgent);
secondRegistry.updateRewardsMerkleRoot(secondRegistryMerkleRoot);
// Fund the second registry
vm.deal(address(secondRegistry), 1000 ether);
// Create proof for second registry
bytes32[] memory secondProof = new bytes32[](1);
secondProof[0] = secondSiblingLeaf;
// Claim from first registry (uses latest merkle root - index 2)
uint256 initialBalance = operatorAddress.balance;
vm.mockCall(
address(allocationManager),
abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector),
abi.encode(true)
);
vm.prank(operatorAddress);
serviceManager.claimLatestOperatorRewards(
operatorSetId, thirdOperatorPoints, numberOfLeaves, leafIndex, thirdValidProof
); // Use latest root
// Verify balance after first claim
assertEq(
operatorAddress.balance,
initialBalance + thirdOperatorPoints,
"Operator should receive correct rewards from first registry"
);
// Claim from second registry
uint256 balanceAfterFirstClaim = operatorAddress.balance;
vm.prank(operatorAddress);
serviceManager.claimLatestOperatorRewards(
secondOperatorSetId, operatorPoints, numberOfLeaves, leafIndex, secondProof
);
// Verify balance after second claim
assertEq(
operatorAddress.balance,
balanceAfterFirstClaim + operatorPoints,
"Operator should receive correct rewards from second registry"
);
}
function test_claimLatestOperatorRewards_NotInOperatorSet() public {
vm.mockCall(
address(allocationManager),
abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector),
abi.encode(false) // Operator is NOT in the set
);
vm.prank(operatorAddress);
vm.expectRevert(
abi.encodeWithSelector(IServiceManagerErrors.OperatorNotInOperatorSet.selector)
);
serviceManager.claimLatestOperatorRewards(
operatorSetId, operatorPoints, numberOfLeaves, leafIndex, validProof
);
}
function test_claimOperatorRewards_NotInOperatorSet() public {
vm.mockCall(
address(allocationManager),
abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector),
abi.encode(false) // Operator is NOT in the set
);
vm.prank(operatorAddress);
vm.expectRevert(
abi.encodeWithSelector(IServiceManagerErrors.OperatorNotInOperatorSet.selector)
);
serviceManager.claimOperatorRewards(
operatorSetId, 0, operatorPoints, numberOfLeaves, leafIndex, validProof
);
}
function test_claimOperatorRewardsBatch_NotInOperatorSet() public {
uint256[] memory rootIndices = new uint256[](1);
rootIndices[0] = 0;
uint256[] memory points = new uint256[](1);
points[0] = operatorPoints;
bytes32[][] memory proofs = new bytes32[][](1);
proofs[0] = validProof;
vm.mockCall(
address(allocationManager),
abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector),
abi.encode(false) // Operator is NOT in the set
);
vm.prank(operatorAddress);
vm.expectRevert(
abi.encodeWithSelector(IServiceManagerErrors.OperatorNotInOperatorSet.selector)
);
uint256[] memory widths3 = new uint256[](1);
widths3[0] = 2;
uint256[] memory leafIdxs3 = new uint256[](1);
leafIdxs3[0] = 0;
serviceManager.claimOperatorRewardsBatch(
operatorSetId, rootIndices, points, widths3, leafIdxs3, proofs
);
}
}

View file

@ -3,38 +3,14 @@ pragma solidity ^0.8.13;
/* solhint-disable func-name-mixedcase */
import {InboundMessageV2} from "snowbridge/src/Types.sol";
import {CommandV2, CommandKind, IGatewayV2} from "snowbridge/src/Types.sol";
import {
CallContractParams,
Payload,
Message,
MessageKind,
Asset,
AssetKind
} from "snowbridge/src/v2/Types.sol";
import {BeefyVerification} from "snowbridge/src/BeefyVerification.sol";
import {BeefyClient} from "snowbridge/src/BeefyClient.sol";
import {
IAllocationManager
} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
import {IGatewayV2} from "snowbridge/src/Types.sol";
import {Payload, Message, MessageKind, Asset} from "snowbridge/src/v2/Types.sol";
import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol";
import {MerkleUtils} from "../src/libraries/MerkleUtils.sol";
import {
IRewardsRegistryEvents,
IRewardsRegistryErrors
} from "../src/interfaces/IRewardsRegistry.sol";
import {SnowbridgeAndAVSDeployer} from "./utils/SnowbridgeAndAVSDeployer.sol";
import {ScaleCodec} from "snowbridge/src/utils/ScaleCodec.sol";
import "forge-std/Test.sol";
contract SnowbridgeIntegrationTest is SnowbridgeAndAVSDeployer {
// Storage variables to reduce stack depth
uint128[] internal _validatorPoints;
address[] internal _validatorAddresses;
bytes32 internal _validatorPointsMerkleRoot;
function setUp() public {
_deployMockAllContracts();
}
@ -48,131 +24,6 @@ contract SnowbridgeIntegrationTest is SnowbridgeAndAVSDeployer {
}
}
function test_constructor() public view {
assertEq(
rewardsRegistry.rewardsAgent(),
address(rewardsAgent),
"Rewards agent address should be set correctly"
);
assertEq(
gateway.agentOf(REWARDS_MESSAGE_ORIGIN),
address(rewardsAgent),
"Rewards agent should be set correctly"
);
}
function test_newRewardsMessage() public {
// Setup validator data.
_setupValidatorData();
// Create and submit the rewards message.
InboundMessageV2 memory updateRewardsMessage = _createRewardsMessage();
// Build messages merkle tree
// We want a proof of the first message, i.e. the actual rewards message
bytes32[] memory messagesProof =
_buildMessagesProofForGoodRewardsMessage(updateRewardsMessage);
// Create BEEFY proof.
BeefyVerification.Proof memory beefyProof = _createBeefyProof();
// This is to mock that the `BeefyClient.verifyMMRLeafProof` function returns true
// despite the fact that we never registered a BEEFY leaf with this message in the
// `BeefyClient` contract.
_mockBeefyVerification();
// Submit message to Gateway.
// We don't care about the rewardAddress that will get the Snowbridge rewards for relaying this message.
bytes32 rewardAddress = keccak256(abi.encodePacked("rewardAddress"));
vm.expectEmit(address(gateway));
emit IGatewayV2.InboundMessageDispatched(0, bytes32(0), true, rewardAddress);
gateway.v2_submit(updateRewardsMessage, messagesProof, beefyProof, rewardAddress);
// Fund the RewardsRegistry to be able to distribute rewards
vm.deal(address(rewardsRegistry), 1000000 ether);
// Build proof for the first validator to claim rewards.
bytes32[] memory rewardsProofFirstValidator =
_buildValidatorPointsProof(_validatorAddresses, _validatorPoints, 0);
// Claim rewards for the first validator.
vm.mockCall(
address(allocationManager),
abi.encodeWithSelector(IAllocationManager.isMemberOfOperatorSet.selector),
abi.encode(true)
);
vm.startPrank(_validatorAddresses[0]);
vm.expectEmit(address(rewardsRegistry));
emit IRewardsRegistryEvents.RewardsClaimedForIndex(
_validatorAddresses[0], 0, _validatorPoints[0], uint256(_validatorPoints[0])
);
serviceManager.claimLatestOperatorRewards(
0, _validatorPoints[0], 10, 0, rewardsProofFirstValidator
);
vm.stopPrank();
// Check that the validator has received the rewards.
assertEq(
address(_validatorAddresses[0]).balance,
_validatorPoints[0],
"Validator should receive rewards"
);
// Build proof for the last validator to claim rewards.
bytes32[] memory rewardsProofLastValidator =
_buildValidatorPointsProof(_validatorAddresses, _validatorPoints, 9);
// Claim rewards for the last validator.
vm.startPrank(_validatorAddresses[9]);
vm.expectEmit(address(rewardsRegistry));
emit IRewardsRegistryEvents.RewardsClaimedForIndex(
_validatorAddresses[9], 0, _validatorPoints[9], uint256(_validatorPoints[9])
);
serviceManager.claimLatestOperatorRewards(
0, _validatorPoints[9], 10, 9, rewardsProofLastValidator
);
vm.stopPrank();
// Check that the last validator has received the rewards.
assertEq(
address(_validatorAddresses[9]).balance,
_validatorPoints[9],
"Last validator should receive rewards"
);
}
function test_newRewardsMessage_OnlyRewardsAgent() public {
// Setup validator data.
_setupValidatorData();
// Create and submit the rewards message.
InboundMessageV2 memory updateRewardsMessage = _createRewardsMessage();
// Build messages merkle tree.
// We want a proof of the third message, i.e. the attempt at setting the new rewards root
// with a wrong origin.
(InboundMessageV2 memory badUpdateRewardsMessage, bytes32[] memory messagesProof) =
_buildMessagesProofForBadRewardsMessage(updateRewardsMessage);
// Create BEEFY proof.
BeefyVerification.Proof memory beefyProof = _createBeefyProof();
// This is to mock that the `BeefyClient.verifyMMRLeafProof` function returns true
// despite the fact that we never registered a BEEFY leaf with this message in the
// `BeefyClient` contract.
_mockBeefyVerification();
// Submit message to Gateway.
// We don't care about the rewardAddress that will get the Snowbridge rewards for relaying this message.
// We expect this to fail in the RewardsRegistry contract because the Agent trying to
// set the new rewards root is not the authorised Agent. Therefore there should be an
// event emitted by the Gateway saying that the message was dispatched but it failed.
bytes32 rewardAddress = keccak256(abi.encodePacked("rewardAddress"));
emit IGatewayV2.InboundMessageDispatched(0, bytes32(0), false, rewardAddress);
gateway.v2_submit(badUpdateRewardsMessage, messagesProof, beefyProof, rewardAddress);
}
function test_sendNewValidatorsSetMessage() public {
// Check that the current validators signed as operators have a registered address for the DataHaven solochain.
address[] memory currentOperators = allocationManager.getMembers(
@ -205,202 +56,4 @@ contract SnowbridgeIntegrationTest is SnowbridgeAndAVSDeployer {
cheats.prank(avsOwner);
serviceManager.sendNewValidatorSet{value: 2 ether}(1 ether, 1 ether);
}
function _setupValidatorData() internal {
// Build validator points and addresses.
_validatorPoints = new uint128[](10);
_validatorPoints[0] = uint128(1111);
_validatorPoints[1] = uint128(2222);
_validatorPoints[2] = uint128(3333);
_validatorPoints[3] = uint128(4444);
_validatorPoints[4] = uint128(5555);
_validatorPoints[5] = uint128(6666);
_validatorPoints[6] = uint128(7777);
_validatorPoints[7] = uint128(8888);
_validatorPoints[8] = uint128(9999);
_validatorPoints[9] = uint128(101010);
_validatorAddresses = new address[](10);
_validatorAddresses[0] = address(0xFFFF1);
_validatorAddresses[1] = address(0xFFFF2);
_validatorAddresses[2] = address(0xFFFF3);
_validatorAddresses[3] = address(0xFFFF4);
_validatorAddresses[4] = address(0xFFFF5);
_validatorAddresses[5] = address(0xFFFF6);
_validatorAddresses[6] = address(0xFFFF7);
_validatorAddresses[7] = address(0xFFFF8);
_validatorAddresses[8] = address(0xFFFF9);
_validatorAddresses[9] = address(0xFFFFA);
_validatorPointsMerkleRoot =
_buildValidatorPointsMerkleTree(_validatorAddresses, _validatorPoints);
}
function _createRewardsMessage() internal view returns (InboundMessageV2 memory) {
CallContractParams memory updateRewardsCommandParams = CallContractParams({
target: address(rewardsRegistry),
data: abi.encodeWithSelector(
bytes4(keccak256("updateRewardsMerkleRoot(bytes32)")), _validatorPointsMerkleRoot
),
value: 0
});
CommandV2 memory updateRewardsCommand = CommandV2({
kind: CommandKind.CallContract,
gas: 1000000,
payload: abi.encode(updateRewardsCommandParams)
});
CommandV2[] memory commands = new CommandV2[](1);
commands[0] = updateRewardsCommand;
return InboundMessageV2({
origin: REWARDS_MESSAGE_ORIGIN, nonce: 0, topic: bytes32(0), commands: commands
});
}
function _buildMessagesProofForGoodRewardsMessage(
InboundMessageV2 memory updateRewardsMessage
) internal pure returns (bytes32[] memory) {
InboundMessageV2[] memory messages = new InboundMessageV2[](3);
// The first message is the actual rewards message that we want to submit and then claim.
messages[0] = updateRewardsMessage;
// The second message is a dummy message with a different origin.
messages[1] = InboundMessageV2({
origin: WRONG_MESSAGE_ORIGIN, nonce: 1, topic: bytes32(0), commands: new CommandV2[](0)
});
// The third message is an attempt at setting the new rewards root, but with a wrong origin
// i.e. not the origin of the authorised Agent.
messages[2] = InboundMessageV2({
origin: WRONG_MESSAGE_ORIGIN,
nonce: 2,
topic: bytes32(0),
commands: updateRewardsMessage.commands
});
return _buildMessagesProof(messages, 0);
}
function _buildMessagesProofForBadRewardsMessage(
InboundMessageV2 memory goodUpdateRewardsMessage
) internal pure returns (InboundMessageV2 memory, bytes32[] memory) {
InboundMessageV2[] memory messages = new InboundMessageV2[](3);
// The first message is the actual rewards message that we want to submit and then claim.
messages[0] = goodUpdateRewardsMessage;
// The second message is a dummy message with a different origin.
messages[1] = InboundMessageV2({
origin: WRONG_MESSAGE_ORIGIN, nonce: 1, topic: bytes32(0), commands: new CommandV2[](0)
});
// The third message is an attempt at setting the new rewards root, but with a wrong origin
// i.e. not the origin of the authorised Agent.
messages[2] = InboundMessageV2({
origin: WRONG_MESSAGE_ORIGIN,
nonce: 2,
topic: bytes32(0),
commands: goodUpdateRewardsMessage.commands
});
return (messages[2], _buildMessagesProof(messages, 2));
}
function _createBeefyProof() internal pure returns (BeefyVerification.Proof memory) {
// Build BEEFY partial leaf.
BeefyVerification.MMRLeafPartial memory partialLeaf = BeefyVerification.MMRLeafPartial({
version: 0,
parentNumber: 18122022,
parentHash: keccak256(abi.encode(18122022)),
nextAuthoritySetID: 18122022,
nextAuthoritySetLen: 10,
nextAuthoritySetRoot: keccak256(abi.encode(18122022))
});
// Build BEEFY proof.
// Any non-empty BEEFY proof will do for the mock.
bytes32[] memory proof = new bytes32[](1);
proof[0] = keccak256(abi.encode(18122022));
return
BeefyVerification.Proof({leafPartial: partialLeaf, leafProof: proof, leafProofOrder: 0});
}
function _mockBeefyVerification() internal {
// Mock the BeefyVerification.verifyBeefyMMRLeaf to always return true
bytes memory encodedReturn = abi.encode(true);
// Create the function selector for verifyBeefyMMRLeaf
bytes4 selector = BeefyClient.verifyMMRLeafProof.selector;
// Mock any call to this function with any parameters to return true
vm.mockCall(address(beefyClient), abi.encodeWithSelector(selector), encodedReturn);
}
function _buildValidatorPointsMerkleTree(
address[] memory validators,
uint128[] memory points
) internal pure returns (bytes32) {
require(
validators.length == points.length,
"Validators and points arrays must be of the same length"
);
bytes32[] memory leaves = new bytes32[](validators.length);
for (uint256 i = 0; i < validators.length; i++) {
// Use SCALE encoding for Substrate compatibility
bytes memory preimage =
abi.encodePacked(validators[i], ScaleCodec.encodeU32(uint32(points[i])));
leaves[i] = keccak256(preimage);
}
// We calculate the merkle root without sorting for Substrate positional merkle tree.
return MerkleUtils.calculateMerkleRoot(leaves, false);
}
function _buildValidatorPointsProof(
address[] memory validators,
uint128[] memory points,
uint256 leafIndex
) internal pure returns (bytes32[] memory) {
require(
validators.length == points.length,
"Validators and points arrays must be of the same length"
);
bytes32[] memory leaves = new bytes32[](validators.length);
for (uint256 i = 0; i < validators.length; i++) {
// Use SCALE encoding for Substrate compatibility
bytes memory preimage =
abi.encodePacked(validators[i], ScaleCodec.encodeU32(uint32(points[i])));
leaves[i] = keccak256(preimage);
}
return MerkleUtils.buildMerkleProof(leaves, leafIndex, false);
}
function _buildMessagesMerkleTree(
InboundMessageV2[] memory messages
) internal pure returns (bytes32) {
bytes32[] memory leaves = new bytes32[](messages.length);
for (uint256 i = 0; i < messages.length; i++) {
leaves[i] = keccak256(abi.encode(messages[i]));
}
// We calculate the merkle root by sorting the pair before hashing (See Open Zeppelin Merkle Tree lib).
return MerkleUtils.calculateMerkleRoot(leaves, true);
}
function _buildMessagesProof(
InboundMessageV2[] memory messages,
uint256 leafIndex
) internal pure returns (bytes32[] memory) {
bytes32[] memory leaves = new bytes32[](messages.length);
for (uint256 i = 0; i < messages.length; i++) {
leaves[i] = keccak256(abi.encode(messages[i]));
}
return MerkleUtils.buildMerkleProof(leaves, leafIndex, true);
}
}

View file

@ -10,7 +10,6 @@ import {
import {
IAllocationManager
} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
import {IRewardsRegistry} from "../../src/interfaces/IRewardsRegistry.sol";
import {ServiceManagerBase} from "../../src/middleware/ServiceManagerBase.sol";
@ -35,17 +34,6 @@ contract ServiceManagerMock is ServiceManagerBase {
__ServiceManagerBase_init(initialOwner, rewardsInitiator);
}
/**
* @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

View file

@ -30,7 +30,6 @@ import {StrategyManager} from "eigenlayer-contracts/src/contracts/core/StrategyM
import {IEigenPodManager} from "eigenlayer-contracts/src/contracts/interfaces/IEigenPodManager.sol";
import {ERC20FixedSupply} from "./ERC20FixedSupply.sol";
import {IServiceManager} from "../../src/interfaces/IServiceManager.sol";
import {RewardsRegistry} from "../../src/middleware/RewardsRegistry.sol";
import {DataHavenServiceManager} from "../../src/DataHavenServiceManager.sol";
// Mocks
import {RewardsCoordinatorMock} from "../mocks/RewardsCoordinatorMock.sol";
@ -50,15 +49,11 @@ contract AVSDeployer is Test {
// AVS contracts
DataHavenServiceManager public serviceManager;
DataHavenServiceManager public serviceManagerImplementation;
RewardsRegistry public rewardsRegistry;
address public vetoCommitteeMember =
address(uint160(uint256(keccak256("vetoCommitteeMember"))));
uint32 public vetoWindowBlocks = 100; // 100 blocks veto window for tests
// RewardsRegistry roles and parameters
address public mockRewardsAgent = address(uint160(uint256(keccak256("rewardsAgent"))));
// EigenLayer contracts
StrategyManager public strategyManager;
StrategyManager public strategyManagerImplementation;
@ -273,16 +268,6 @@ contract AVSDeployer is Test {
);
cheats.stopPrank();
console.log("ServiceManager implementation deployed");
// Deploy the RewardsRegistry contract
cheats.prank(regularDeployer);
rewardsRegistry = new RewardsRegistry(address(serviceManager), mockRewardsAgent);
// Set the rewards registry in the ServiceManager
cheats.prank(avsOwner);
serviceManager.setRewardsRegistry(0, rewardsRegistry);
console.log("RewardsRegistry deployed and configured");
}
function _setUpDefaultStrategiesAndMultipliers() internal virtual {

View file

@ -150,11 +150,6 @@ contract SnowbridgeAndAVSDeployer is AVSDeployer {
console.log("Rewards agent deployed at", address(rewardsAgent));
cheats.prank(avsOwner);
serviceManager.setRewardsAgent(0, address(rewardsAgent));
console.log("Rewards agent set for operator set 0");
cheats.prank(regularDeployer);
gateway.v2_createAgent(WRONG_MESSAGE_ORIGIN);

14
operator/Cargo.lock generated
View file

@ -2664,7 +2664,6 @@ dependencies = [
"pallet-external-validator-slashes",
"pallet-external-validators",
"pallet-external-validators-rewards",
"pallet-external-validators-rewards-runtime-api",
"pallet-file-system",
"pallet-file-system-runtime-api",
"pallet-grandpa",
@ -2959,7 +2958,6 @@ dependencies = [
"pallet-external-validator-slashes",
"pallet-external-validators",
"pallet-external-validators-rewards",
"pallet-external-validators-rewards-runtime-api",
"pallet-file-system",
"pallet-file-system-runtime-api",
"pallet-grandpa",
@ -3111,7 +3109,6 @@ dependencies = [
"pallet-external-validator-slashes",
"pallet-external-validators",
"pallet-external-validators-rewards",
"pallet-external-validators-rewards-runtime-api",
"pallet-file-system",
"pallet-file-system-runtime-api",
"pallet-grandpa",
@ -9124,7 +9121,6 @@ dependencies = [
"parity-scale-codec",
"scale-info",
"snowbridge-core 0.12.0",
"snowbridge-merkle-tree",
"snowbridge-outbound-queue-primitives",
"sp-core",
"sp-io",
@ -9133,16 +9129,6 @@ dependencies = [
"sp-std",
]
[[package]]
name = "pallet-external-validators-rewards-runtime-api"
version = "0.12.0"
dependencies = [
"parity-scale-codec",
"snowbridge-merkle-tree",
"sp-api",
"sp-core",
]
[[package]]
name = "pallet-fast-unstake"
version = "38.1.0"

View file

@ -42,7 +42,6 @@ pallet-evm-precompile-registry = { path = "./precompiles/precompile-registry", d
pallet-external-validator-slashes = { path = "./pallets/external-validator-slashes", default-features = false }
pallet-external-validators = { path = "./pallets/external-validators", default-features = false }
pallet-external-validators-rewards = { path = "./pallets/external-validators-rewards", default-features = false }
pallet-external-validators-rewards-runtime-api = { path = "./pallets/external-validators-rewards/runtime-api", default-features = false }
pallet-outbound-commitment-store = { path = "./pallets/outbound-commitment-store", default-features = false }
# Crates.io (wasm)

View file

@ -32,7 +32,6 @@ pallet-external-validators = { workspace = true }
pallet-session = { workspace = true, features = [ "historical" ] }
snowbridge-core = { workspace = true }
snowbridge-merkle-tree = { workspace = true }
snowbridge-outbound-queue-primitives = { workspace = true }
[dev-dependencies]
@ -54,7 +53,6 @@ std = [
"parity-scale-codec/std",
"scale-info/std",
"snowbridge-core/std",
"snowbridge-merkle-tree/std",
"snowbridge-outbound-queue-primitives/std",
"sp-core/std",
"sp-io/std",

View file

@ -1,28 +0,0 @@
[package]
name = "pallet-external-validators-rewards-runtime-api"
authors = { workspace = true }
description = "Runtime API definition of pallet-external-validators-rewards"
edition = "2021"
license = "GPL-3.0-only"
version = { workspace = true }
[package.metadata.docs.rs]
targets = [ "x86_64-unknown-linux-gnu" ]
[lints]
workspace = true
[dependencies]
parity-scale-codec = { workspace = true }
snowbridge-merkle-tree = { workspace = true }
sp-api = { workspace = true }
sp-core = { workspace = true }
[features]
default = [ "std" ]
std = [
"parity-scale-codec/std",
"snowbridge-merkle-tree/std",
"sp-api/std",
"sp-core/std",
]

View file

@ -1,32 +0,0 @@
// Copyright (C) Moondance Labs Ltd.
// This file is part of Tanssi.
// Tanssi is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Tanssi is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Tanssi. If not, see <http://www.gnu.org/licenses/>
//! Runtime API for External Validators Rewards pallet
#![cfg_attr(not(feature = "std"), no_std)]
use snowbridge_merkle_tree::MerkleProof;
sp_api::decl_runtime_apis! {
pub trait ExternalValidatorsRewardsApi<AccountId, EraIndex>
where
AccountId: parity_scale_codec::Codec,
EraIndex: parity_scale_codec::Codec,
{
fn generate_rewards_merkle_proof(account_id: AccountId, era_index: EraIndex) -> Option<MerkleProof>;
fn verify_rewards_merkle_proof(merkle_proof: MerkleProof) -> bool;
}
}

View file

@ -38,7 +38,6 @@ use {
frame_support::traits::{Get, ValidatorSet},
pallet_external_validators::traits::{ExternalIndexProvider, OnEraEnd, OnEraStart},
parity_scale_codec::{Decode, Encode},
snowbridge_merkle_tree::{merkle_proof, merkle_root, verify_proof, MerkleProof},
sp_core::{H160, H256},
sp_runtime::{
traits::{Hash, Zero},
@ -185,7 +184,6 @@ pub mod pallet {
era_index: EraIndex,
total_points: u128,
inflation_amount: u128,
rewards_merkle_root: H256,
},
}
@ -197,58 +195,26 @@ pub mod pallet {
}
impl<AccountId: Ord + sp_runtime::traits::Debug + Parameter> EraRewardPoints<AccountId> {
// Helper function used to generate the following utils:
// - rewards_merkle_root: merkle root corresponding [(validatorId, rewardPoints)]
// for the era_index specified.
// - leaves: that were used to generate the previous merkle root.
// - leaf_index: index of the validatorId's leaf in the previous leaves array (if any).
// - total_points: number of total points of the era_index specified.
// - individual_points: (address, points) tuples for each validator.
// - inflation_amount: total inflation tokens to distribute.
// - era_start_timestamp: timestamp when the era started (seconds since Unix epoch).
pub fn generate_era_rewards_utils<Hasher: sp_runtime::traits::Hash<Output = H256>>(
/// Generate utils needed for EigenLayer rewards submission:
/// - total_points: number of total points of the era_index specified.
/// - individual_points: (address, points) tuples for each validator.
/// - inflation_amount: total inflation tokens to distribute.
/// - era_start_timestamp: timestamp when the era started (seconds since Unix epoch).
pub fn generate_era_rewards_utils(
&self,
era_index: EraIndex,
maybe_account_id_check: Option<AccountId>,
inflation_amount: u128,
era_start_timestamp: u32,
) -> Option<EraRewardsUtils> {
let mut leaves = Vec::with_capacity(self.individual.len());
let mut leaf_index = None;
let mut individual_points = Vec::with_capacity(self.individual.len());
if let Some(account) = &maybe_account_id_check {
if !self.individual.contains_key(account) {
log::error!(
target: "ext_validators_rewards",
"AccountId {:?} not found for era {:?}!",
account,
era_index
);
return None;
}
}
for (index, (account_id, reward_points)) in self.individual.iter().enumerate() {
let encoded = (account_id, reward_points).encode();
let hashed = <Hasher as sp_runtime::traits::Hash>::hash(&encoded);
leaves.push(hashed);
for (account_id, reward_points) in self.individual.iter() {
// Convert AccountId to H160 for EigenLayer rewards submission.
// In DataHaven, AccountId is H160, so encode() produces exactly 20 bytes.
individual_points
.push((H160::from_slice(&account_id.encode()[..20]), *reward_points));
if let Some(ref check_account_id) = maybe_account_id_check {
if account_id == check_account_id {
leaf_index = Some(index as u64);
}
}
}
let rewards_merkle_root = merkle_root::<Hasher, _>(leaves.iter().cloned());
let total_points: u128 = individual_points.iter().map(|(_, pts)| *pts as u128).sum();
if total_points.is_zero() {
@ -258,9 +224,6 @@ pub mod pallet {
Some(EraRewardsUtils {
era_index,
era_start_timestamp,
rewards_merkle_root,
leaves,
leaf_index,
total_points,
individual_points,
inflation_amount,
@ -311,33 +274,6 @@ pub mod pallet {
})
}
pub fn generate_rewards_merkle_proof(
account_id: T::AccountId,
era_index: EraIndex,
) -> Option<MerkleProof> {
let era_rewards = RewardPointsForEra::<T>::get(&era_index);
// Pass 0 for inflation_amount and era_start_timestamp as they're not needed for merkle proof generation
let utils = era_rewards.generate_era_rewards_utils::<<T as Config>::Hashing>(
era_index,
Some(account_id),
0,
0,
)?;
utils.leaf_index.map(|index| {
merkle_proof::<<T as Config>::Hashing, _>(utils.leaves.into_iter(), index)
})
}
pub fn verify_rewards_merkle_proof(merkle_proof: MerkleProof) -> bool {
verify_proof::<<T as Config>::Hashing, _, _>(
&merkle_proof.root,
merkle_proof.proof,
merkle_proof.number_of_leaves,
merkle_proof.leaf_index,
merkle_proof.leaf,
)
}
/// Helper to build, validate and deliver an outbound message.
/// Logs any error and returns None on failure.
fn send_rewards_message(utils: &EraRewardsUtils) -> Option<H256> {
@ -696,13 +632,11 @@ pub mod pallet {
// Generate era rewards utils with the scaled inflation amount.
// This ensures the message to EigenLayer matches the actual minted amount.
let utils = match RewardPointsForEra::<T>::get(&era_index)
.generate_era_rewards_utils::<<T as Config>::Hashing>(
era_index,
None,
scaled_inflation,
era_start_timestamp,
) {
let utils = match RewardPointsForEra::<T>::get(&era_index).generate_era_rewards_utils(
era_index,
scaled_inflation,
era_start_timestamp,
) {
Some(utils) => utils,
None => {
// Returns None when total_points is zero or no validators have rewards
@ -736,7 +670,6 @@ pub mod pallet {
era_index,
total_points: utils.total_points,
inflation_amount: scaled_inflation,
rewards_merkle_root: utils.rewards_merkle_root,
});
}
}

View file

@ -138,7 +138,11 @@ fn test_on_era_end() {
});
let points = vec![10u32, 30u32, 50u32];
let total_points: u32 = points.iter().cloned().sum();
let accounts = vec![H160::from_low_u64_be(1), H160::from_low_u64_be(3), H160::from_low_u64_be(5)];
let accounts = vec![
H160::from_low_u64_be(1),
H160::from_low_u64_be(3),
H160::from_low_u64_be(5),
];
let accounts_points: Vec<_> = accounts
.iter()
.cloned()
@ -154,18 +158,17 @@ fn test_on_era_end() {
ExternalValidatorsRewards::on_era_end(1);
let era_rewards = pallet_external_validators_rewards::RewardPointsForEra::<Test>::get(1);
let inflation = <Test as pallet_external_validators_rewards::Config>::EraInflationProvider::get();
// Use 0 for era_start_timestamp in tests since we're only checking merkle root
let rewards_utils = era_rewards.generate_era_rewards_utils::<<Test as pallet_external_validators_rewards::Config>::Hashing>(1, None, inflation, 0);
let root = rewards_utils.unwrap().rewards_merkle_root;
let inflation =
<Test as pallet_external_validators_rewards::Config>::EraInflationProvider::get();
// Use 0 for era_start_timestamp in tests
let rewards_utils = era_rewards.generate_era_rewards_utils(1, inflation, 0);
assert!(rewards_utils.is_some());
System::assert_last_event(RuntimeEvent::ExternalValidatorsRewards(
crate::Event::RewardsMessageSent {
message_id: Default::default(),
era_index: 1,
total_points: total_points as u128,
inflation_amount: inflation,
rewards_merkle_root: root,
},
));
})
@ -184,8 +187,11 @@ fn test_on_era_end_with_zero_inflation() {
mock.era_inflation = Some(0);
});
let points = vec![10u32, 30u32, 50u32];
let total_points: u32 = points.iter().cloned().sum();
let accounts = vec![H160::from_low_u64_be(1), H160::from_low_u64_be(3), H160::from_low_u64_be(5)];
let accounts = vec![
H160::from_low_u64_be(1),
H160::from_low_u64_be(3),
H160::from_low_u64_be(5),
];
let accounts_points: Vec<_> = accounts
.iter()
.cloned()
@ -195,23 +201,17 @@ fn test_on_era_end_with_zero_inflation() {
ExternalValidatorsRewards::on_era_end(1);
let era_rewards = pallet_external_validators_rewards::RewardPointsForEra::<Test>::get(1);
let inflation = <Test as pallet_external_validators_rewards::Config>::EraInflationProvider::get();
let rewards_utils = era_rewards.generate_era_rewards_utils::<<Test as pallet_external_validators_rewards::Config>::Hashing>(1, None, inflation, 0);
let root = rewards_utils.unwrap().rewards_merkle_root;
let expected_not_thrown_event = RuntimeEvent::ExternalValidatorsRewards(
crate::Event::RewardsMessageSent {
message_id: Default::default(),
era_index: 1,
total_points: total_points as u128,
inflation_amount: inflation,
rewards_merkle_root: root,
}
);
let inflation =
<Test as pallet_external_validators_rewards::Config>::EraInflationProvider::get();
let rewards_utils = era_rewards.generate_era_rewards_utils(1, inflation, 0);
assert!(rewards_utils.is_some());
// With zero inflation, no RewardsMessageSent event should be emitted
let events = System::events();
assert!(
!events
.iter()
.any(|record| record.event == expected_not_thrown_event),
!events.iter().any(|record| matches!(
&record.event,
RuntimeEvent::ExternalValidatorsRewards(crate::Event::RewardsMessageSent { .. })
)),
"event should not have been thrown",
);
})
@ -229,7 +229,11 @@ fn test_on_era_end_with_zero_points() {
});
});
let points = vec![0u32, 0u32, 0u32];
let accounts = vec![H160::from_low_u64_be(1), H160::from_low_u64_be(3), H160::from_low_u64_be(5)];
let accounts = vec![
H160::from_low_u64_be(1),
H160::from_low_u64_be(3),
H160::from_low_u64_be(5),
];
let accounts_points: Vec<_> = accounts
.iter()
.cloned()
@ -243,10 +247,7 @@ fn test_on_era_end_with_zero_points() {
let era_rewards = pallet_external_validators_rewards::RewardPointsForEra::<Test>::get(1);
let inflation =
<Test as pallet_external_validators_rewards::Config>::EraInflationProvider::get();
let rewards_utils = era_rewards
.generate_era_rewards_utils::<<Test as pallet_external_validators_rewards::Config>::Hashing>(
1, None, inflation, 0,
);
let rewards_utils = era_rewards.generate_era_rewards_utils(1, inflation, 0);
assert!(
rewards_utils.is_none(),
"generate_era_rewards_utils should return None when total_points is zero"

View file

@ -18,15 +18,11 @@ use snowbridge_outbound_queue_primitives::SendError;
use sp_core::{H160, H256};
use sp_std::vec::Vec;
/// Utils needed to generate/verify merkle roots/proofs inside this pallet.
/// Also contains data needed for EigenLayer rewards submission.
/// Data needed for EigenLayer rewards submission via Snowbridge.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct EraRewardsUtils {
pub era_index: u32,
pub era_start_timestamp: u32,
pub rewards_merkle_root: H256,
pub leaves: Vec<H256>,
pub leaf_index: Option<u64>,
pub total_points: u128,
pub individual_points: Vec<(H160, u32)>,
pub inflation_amount: u128,

View file

@ -741,9 +741,6 @@ mod tests {
let rewards_utils = EraRewardsUtils {
era_index: 7,
era_start_timestamp: TEST_ERA_START_TIMESTAMP,
rewards_merkle_root: H256::zero(),
leaves: vec![],
leaf_index: None,
total_points: 100u128,
individual_points: vec![
(H160::from_low_u64_be(2), 40),
@ -802,9 +799,6 @@ mod tests {
let rewards_utils = EraRewardsUtils {
era_index: 7,
era_start_timestamp: TEST_ERA_START_TIMESTAMP,
rewards_merkle_root: H256::zero(),
leaves: vec![],
leaf_index: None,
total_points: 3u128,
individual_points: vec![(H160::from_low_u64_be(1), 1), (H160::from_low_u64_be(2), 2)],
inflation_amount: 100u128,
@ -843,9 +837,6 @@ mod tests {
let rewards_utils = EraRewardsUtils {
era_index: 7,
era_start_timestamp: TEST_ERA_START_TIMESTAMP,
rewards_merkle_root: H256::zero(),
leaves: vec![],
leaf_index: None,
total_points: 1u128,
individual_points: vec![(H160::from_low_u64_be(1), 1)],
inflation_amount: 100u128,
@ -861,9 +852,6 @@ mod tests {
let rewards_utils = EraRewardsUtils {
era_index: 7,
era_start_timestamp: TEST_ERA_START_TIMESTAMP,
rewards_merkle_root: H256::zero(),
leaves: vec![],
leaf_index: None,
total_points: 1000u128,
individual_points: vec![(H160::from_low_u64_be(1), 1)],
inflation_amount: 1u128,
@ -878,9 +866,6 @@ mod tests {
let rewards_utils = EraRewardsUtils {
era_index: 7,
era_start_timestamp: TEST_ERA_START_TIMESTAMP,
rewards_merkle_root: H256::zero(),
leaves: vec![],
leaf_index: None,
total_points: 0u128,
individual_points: vec![(H160::from_low_u64_be(1), 1)],
inflation_amount: 100u128,
@ -895,9 +880,6 @@ mod tests {
let rewards_utils = EraRewardsUtils {
era_index: 7,
era_start_timestamp: TEST_ERA_START_TIMESTAMP,
rewards_merkle_root: H256::zero(),
leaves: vec![],
leaf_index: None,
total_points: 1u128,
individual_points: vec![(H160::from_low_u64_be(1), u32::MAX)],
inflation_amount: u128::MAX,
@ -912,9 +894,6 @@ mod tests {
let rewards_utils = EraRewardsUtils {
era_index: 7,
era_start_timestamp: TEST_ERA_START_TIMESTAMP,
rewards_merkle_root: H256::zero(),
leaves: vec![],
leaf_index: None,
total_points: 1u128,
individual_points: vec![(H160::from_low_u64_be(1), 1)],
inflation_amount: 100u128,
@ -929,9 +908,6 @@ mod tests {
let rewards_utils = EraRewardsUtils {
era_index: 7,
era_start_timestamp: TEST_ERA_START_TIMESTAMP,
rewards_merkle_root: H256::zero(),
leaves: vec![],
leaf_index: None,
total_points: 100u128,
individual_points: vec![
(H160::from_low_u64_be(2), 40),

View file

@ -54,7 +54,6 @@ pallet-evm-precompile-simple = { workspace = true }
pallet-external-validator-slashes = { workspace = true }
pallet-external-validators = { workspace = true }
pallet-external-validators-rewards = { workspace = true }
pallet-external-validators-rewards-runtime-api = { workspace = true }
pallet-grandpa = { workspace = true }
pallet-identity = { workspace = true }
pallet-im-online = { workspace = true }
@ -219,7 +218,6 @@ std = [
"pallet-evm-precompile-registry/std",
"pallet-external-validators/std",
"pallet-external-validators-rewards/std",
"pallet-external-validators-rewards-runtime-api/std",
"pallet-external-validator-slashes/std",
"pallet-grandpa/std",
"pallet-identity/std",

View file

@ -1741,9 +1741,6 @@ mod tests {
let rewards_utils = EraRewardsUtils {
era_index: 1,
era_start_timestamp: 1_700_000_000,
rewards_merkle_root: H256::random(),
leaves: vec![H256::random()],
leaf_index: Some(1),
total_points: 1000,
individual_points: vec![
(H160::from_low_u64_be(1), 500),
@ -1803,9 +1800,6 @@ mod tests {
let rewards_utils = EraRewardsUtils {
era_index: 1,
era_start_timestamp: 1_700_000_000,
rewards_merkle_root: H256::random(),
leaves: vec![H256::random()],
leaf_index: Some(1),
total_points: 1000,
individual_points: vec![(op1, 600), (op2, 400)],
inflation_amount: 1_000_000_000, // 1 billion smallest units

View file

@ -54,7 +54,6 @@ pub use frame_system::Call as SystemCall;
pub use pallet_balances::Call as BalancesCall;
use pallet_ethereum::{Call::transact, Transaction as EthereumTransaction};
use pallet_evm::{Account as EVMAccount, FeeCalculator, GasWeightMapping, Runner};
use pallet_external_validators::traits::EraIndex;
use pallet_file_system::types::StorageRequestMetadata;
use pallet_file_system_runtime_api::*;
use pallet_grandpa::{fg_primitives, AuthorityId as GrandpaId};
@ -72,7 +71,6 @@ pub use pallet_timestamp::Call as TimestampCall;
use shp_file_metadata::ChunkId;
use smallvec::smallvec;
use snowbridge_core::AgentId;
use snowbridge_merkle_tree::MerkleProof;
use sp_api::impl_runtime_apis;
use sp_consensus_beefy::{
ecdsa_crypto::{AuthorityId as BeefyId, Signature as BeefySignature},
@ -944,19 +942,6 @@ impl_runtime_apis! {
}
}
impl pallet_external_validators_rewards_runtime_api::ExternalValidatorsRewardsApi<Block, AccountId, EraIndex> for Runtime
where
EraIndex: codec::Codec,
{
fn generate_rewards_merkle_proof(account_id: AccountId, era_index: EraIndex) -> Option<MerkleProof> {
ExternalValidatorsRewards::generate_rewards_merkle_proof(account_id, era_index)
}
fn verify_rewards_merkle_proof(merkle_proof: MerkleProof) -> bool {
ExternalValidatorsRewards::verify_rewards_merkle_proof(merkle_proof)
}
}
#[cfg(feature = "runtime-benchmarks")]
impl frame_benchmarking::Benchmark<Block> for Runtime {
fn benchmark_metadata(extra: bool) -> (

View file

@ -54,7 +54,6 @@ pallet-evm-precompile-simple = { workspace = true }
pallet-external-validator-slashes = { workspace = true }
pallet-external-validators = { workspace = true }
pallet-external-validators-rewards = { workspace = true }
pallet-external-validators-rewards-runtime-api = { workspace = true }
pallet-grandpa = { workspace = true }
pallet-identity = { workspace = true }
pallet-im-online = { workspace = true }
@ -219,7 +218,6 @@ std = [
"pallet-evm-precompile-registry/std",
"pallet-external-validators/std",
"pallet-external-validators-rewards/std",
"pallet-external-validators-rewards-runtime-api/std",
"pallet-external-validator-slashes/std",
"pallet-grandpa/std",
"pallet-identity/std",

View file

@ -1736,9 +1736,6 @@ mod tests {
let rewards_utils = EraRewardsUtils {
era_index: 1,
era_start_timestamp: 1_700_000_000,
rewards_merkle_root: H256::random(),
leaves: vec![H256::random()],
leaf_index: Some(1),
total_points: 1000,
individual_points: vec![
(H160::from_low_u64_be(1), 500),
@ -1791,9 +1788,6 @@ mod tests {
let rewards_utils = EraRewardsUtils {
era_index: 1,
era_start_timestamp: 1_700_000_000,
rewards_merkle_root: H256::random(),
leaves: vec![H256::random()],
leaf_index: Some(1),
total_points: 1000,
individual_points: vec![(H160::from_low_u64_be(1), 600), (H160::from_low_u64_be(2), 400)],
inflation_amount: 1_000_000_000,

View file

@ -51,7 +51,6 @@ pub use frame_system::Call as SystemCall;
pub use pallet_balances::Call as BalancesCall;
use pallet_ethereum::{Call::transact, Transaction as EthereumTransaction};
use pallet_evm::{Account as EVMAccount, FeeCalculator, GasWeightMapping, Runner};
use pallet_external_validators::traits::EraIndex;
use pallet_file_system::types::StorageRequestMetadata;
use pallet_file_system_runtime_api::*;
use pallet_grandpa::{fg_primitives, AuthorityId as GrandpaId};
@ -69,7 +68,6 @@ pub use pallet_timestamp::Call as TimestampCall;
use shp_file_metadata::ChunkId;
use smallvec::smallvec;
use snowbridge_core::AgentId;
use snowbridge_merkle_tree::MerkleProof;
use sp_api::impl_runtime_apis;
use sp_consensus_beefy::{
ecdsa_crypto::{AuthorityId as BeefyId, Signature as BeefySignature},
@ -946,19 +944,6 @@ impl_runtime_apis! {
}
}
impl pallet_external_validators_rewards_runtime_api::ExternalValidatorsRewardsApi<Block, AccountId, EraIndex> for Runtime
where
EraIndex: codec::Codec,
{
fn generate_rewards_merkle_proof(account_id: AccountId, era_index: EraIndex) -> Option<MerkleProof> {
ExternalValidatorsRewards::generate_rewards_merkle_proof(account_id, era_index)
}
fn verify_rewards_merkle_proof(merkle_proof: MerkleProof) -> bool {
ExternalValidatorsRewards::verify_rewards_merkle_proof(merkle_proof)
}
}
#[cfg(feature = "runtime-benchmarks")]
impl frame_benchmarking::Benchmark<Block> for Runtime {
fn benchmark_metadata(extra: bool) -> (

View file

@ -55,7 +55,6 @@ pallet-evm-precompile-simple = { workspace = true }
pallet-external-validator-slashes = { workspace = true }
pallet-external-validators = { workspace = true }
pallet-external-validators-rewards = { workspace = true }
pallet-external-validators-rewards-runtime-api = { workspace = true }
pallet-grandpa = { workspace = true }
pallet-identity = { workspace = true }
pallet-im-online = { workspace = true }
@ -279,7 +278,6 @@ std = [
"pallet-outbound-commitment-store/std",
"pallet-external-validators/std",
"pallet-external-validators-rewards/std",
"pallet-external-validators-rewards-runtime-api/std",
"pallet-external-validator-slashes/std",
"pallet-datahaven-native-transfer/std",
# StorageHub

View file

@ -1759,9 +1759,6 @@ mod tests {
let rewards_utils = EraRewardsUtils {
era_index: 1,
era_start_timestamp: 1_700_000_000,
rewards_merkle_root: H256::random(),
leaves: vec![H256::random()],
leaf_index: Some(1),
total_points: 1000,
individual_points: vec![
(H160::from_low_u64_be(1), 500),
@ -1814,9 +1811,6 @@ mod tests {
let rewards_utils = EraRewardsUtils {
era_index: 1,
era_start_timestamp: 1_700_000_000,
rewards_merkle_root: H256::random(),
leaves: vec![H256::random()],
leaf_index: Some(1),
total_points: 1000,
individual_points: vec![(H160::from_low_u64_be(1), 600), (H160::from_low_u64_be(2), 400)],
inflation_amount: 1_000_000_000,

View file

@ -54,7 +54,6 @@ pub use frame_system::Call as SystemCall;
pub use pallet_balances::Call as BalancesCall;
use pallet_ethereum::{Call::transact, Transaction as EthereumTransaction};
use pallet_evm::{Account as EVMAccount, FeeCalculator, GasWeightMapping, Runner};
use pallet_external_validators::traits::EraIndex;
use pallet_file_system::types::StorageRequestMetadata;
use pallet_file_system_runtime_api::*;
use pallet_grandpa::{fg_primitives, AuthorityId as GrandpaId};
@ -72,7 +71,6 @@ pub use pallet_timestamp::Call as TimestampCall;
use shp_file_metadata::ChunkId;
use smallvec::smallvec;
use snowbridge_core::AgentId;
use snowbridge_merkle_tree::MerkleProof;
use sp_api::impl_runtime_apis;
use sp_consensus_beefy::{
ecdsa_crypto::{AuthorityId as BeefyId, Signature as BeefySignature},
@ -944,19 +942,6 @@ impl_runtime_apis! {
}
}
impl pallet_external_validators_rewards_runtime_api::ExternalValidatorsRewardsApi<Block, AccountId, EraIndex> for Runtime
where
EraIndex: codec::Codec,
{
fn generate_rewards_merkle_proof(account_id: AccountId, era_index: EraIndex) -> Option<MerkleProof> {
ExternalValidatorsRewards::generate_rewards_merkle_proof(account_id, era_index)
}
fn verify_rewards_merkle_proof(merkle_proof: MerkleProof) -> bool {
ExternalValidatorsRewards::verify_rewards_merkle_proof(merkle_proof)
}
}
#[cfg(feature = "runtime-benchmarks")]
impl frame_benchmarking::Benchmark<Block> for Runtime {
fn benchmark_metadata(extra: bool) -> (

View file

@ -1,5 +1,5 @@
{
"version": "0.1.0-autogenerated.3899565146829206681",
"version": "0.1.0-autogenerated.8790957017458825602",
"name": "@polkadot-api/descriptors",
"files": [
"dist"

Binary file not shown.

View file

@ -74,11 +74,9 @@ const showDatahavenContractStatus = async (chain: string, rpcUrl: string) => {
try {
const contracts = [
{ name: "DataHavenServiceManager", key: "ServiceManagerImplementation" },
{ name: "RewardsRegistry", key: "RewardsRegistry" },
{ name: "Snowbridge BeefyClient", key: "BeefyClient" },
{ name: "Snowbridge AgentExecutor", key: "AgentExecutor" },
{ name: "Snowbridge Gateway", key: "Gateway" },
{ name: "Snowbridge Agent", key: "RewardsAgent" }
{ name: "Snowbridge Gateway", key: "Gateway" }
];
logger.info("DataHaven contracts");

View file

@ -49,13 +49,6 @@ export const verifyContracts = async (options: ContractsVerifyOptions) => {
],
constructorArgTypes: ["address", "address", "address"]
},
{
name: "RewardsRegistry",
address: deployments.RewardsRegistry,
artifactName: "RewardsRegistry",
constructorArgs: [deployments.ServiceManager, deployments.RewardsAgent],
constructorArgTypes: ["address", "address"]
},
{
name: "Gateway",
address: deployments.Gateway,

View file

@ -70,10 +70,8 @@ export const deployRelayers = async (options: DeployOptions, launchedNetwork: La
const anvilDeployments = await parseDeploymentsFile();
const beefyClientAddress = anvilDeployments.BeefyClient;
const gatewayAddress = anvilDeployments.Gateway;
const rewardsRegistryAddress = anvilDeployments.RewardsRegistry;
invariant(beefyClientAddress, "❌ BeefyClient address not found in anvil.json");
invariant(gatewayAddress, "❌ Gateway address not found in anvil.json");
invariant(rewardsRegistryAddress, "❌ RewardsRegistry address not found in anvil.json");
logger.debug(`Ensuring output directory exists: ${RELAYER_CONFIG_DIR}`);
await $`mkdir -p ${RELAYER_CONFIG_DIR}`.quiet();
@ -118,7 +116,6 @@ export const deployRelayers = async (options: DeployOptions, launchedNetwork: La
substrateWsEndpoint,
beefyClientAddress,
gatewayAddress,
rewardsRegistryAddress,
ethClEndpoint
},
pk: {

View file

@ -2114,47 +2114,6 @@ export const dataHavenServiceManagerAbi = [
outputs: [{ name: '', internalType: 'bytes', type: 'bytes' }],
stateMutability: 'view',
},
{
type: 'function',
inputs: [
{ name: 'operatorSetId', internalType: 'uint32', type: 'uint32' },
{ name: 'operatorPoints', internalType: 'uint256', type: 'uint256' },
{ name: 'numberOfLeaves', internalType: 'uint256', type: 'uint256' },
{ name: 'leafIndex', internalType: 'uint256', type: 'uint256' },
{ name: 'proof', internalType: 'bytes32[]', type: 'bytes32[]' },
],
name: 'claimLatestOperatorRewards',
outputs: [],
stateMutability: 'nonpayable',
},
{
type: 'function',
inputs: [
{ name: 'operatorSetId', internalType: 'uint32', type: 'uint32' },
{ name: 'rootIndex', internalType: 'uint256', type: 'uint256' },
{ name: 'operatorPoints', internalType: 'uint256', type: 'uint256' },
{ name: 'numberOfLeaves', internalType: 'uint256', type: 'uint256' },
{ name: 'leafIndex', internalType: 'uint256', type: 'uint256' },
{ name: 'proof', internalType: 'bytes32[]', type: 'bytes32[]' },
],
name: 'claimOperatorRewards',
outputs: [],
stateMutability: 'nonpayable',
},
{
type: 'function',
inputs: [
{ name: 'operatorSetId', internalType: 'uint32', type: 'uint32' },
{ name: 'rootIndices', internalType: 'uint256[]', type: 'uint256[]' },
{ name: 'operatorPoints', internalType: 'uint256[]', type: 'uint256[]' },
{ name: 'numberOfLeaves', internalType: 'uint256[]', type: 'uint256[]' },
{ name: 'leafIndices', internalType: 'uint256[]', type: 'uint256[]' },
{ name: 'proofs', internalType: 'bytes32[][]', type: 'bytes32[][]' },
],
name: 'claimOperatorRewardsBatch',
outputs: [],
stateMutability: 'nonpayable',
},
{
type: 'function',
inputs: [
@ -2323,15 +2282,6 @@ export const dataHavenServiceManagerAbi = [
outputs: [],
stateMutability: 'nonpayable',
},
{
type: 'function',
inputs: [{ name: '', internalType: 'uint32', type: 'uint32' }],
name: 'operatorSetToRewardsRegistry',
outputs: [
{ name: '', internalType: 'contract IRewardsRegistry', type: 'address' },
],
stateMutability: 'view',
},
{
type: 'function',
inputs: [],
@ -2474,16 +2424,6 @@ export const dataHavenServiceManagerAbi = [
outputs: [],
stateMutability: 'nonpayable',
},
{
type: 'function',
inputs: [
{ name: 'operatorSetId', internalType: 'uint32', type: 'uint32' },
{ name: 'rewardsAgent', internalType: 'address', type: 'address' },
],
name: 'setRewardsAgent',
outputs: [],
stateMutability: 'nonpayable',
},
{
type: 'function',
inputs: [
@ -2493,20 +2433,6 @@ export const dataHavenServiceManagerAbi = [
outputs: [],
stateMutability: 'nonpayable',
},
{
type: 'function',
inputs: [
{ name: 'operatorSetId', internalType: 'uint32', type: 'uint32' },
{
name: 'rewardsRegistry',
internalType: 'contract IRewardsRegistry',
type: 'address',
},
],
name: 'setRewardsRegistry',
outputs: [],
stateMutability: 'nonpayable',
},
{
type: 'function',
inputs: [
@ -2726,25 +2652,6 @@ export const dataHavenServiceManagerAbi = [
],
name: 'RewardsInitiatorUpdated',
},
{
type: 'event',
anonymous: false,
inputs: [
{
name: 'operatorSetId',
internalType: 'uint32',
type: 'uint32',
indexed: true,
},
{
name: 'rewardsRegistry',
internalType: 'address',
type: 'address',
indexed: true,
},
],
name: 'RewardsRegistrySet',
},
{
type: 'event',
anonymous: false,
@ -2809,7 +2716,6 @@ export const dataHavenServiceManagerAbi = [
{ type: 'error', inputs: [], name: 'DelayPeriodNotPassed' },
{ type: 'error', inputs: [], name: 'IncorrectAVSAddress' },
{ type: 'error', inputs: [], name: 'InvalidOperatorSetId' },
{ type: 'error', inputs: [], name: 'NoRewardsRegistryForOperatorSet' },
{ type: 'error', inputs: [], name: 'OnlyRegistryCoordinator' },
{ type: 'error', inputs: [], name: 'OnlyRewardsInitiator' },
{ type: 'error', inputs: [], name: 'OnlyStakeRegistry' },
@ -8174,245 +8080,6 @@ export const rewardsCoordinatorAbi = [
{ type: 'error', inputs: [], name: 'UnauthorizedCaller' },
] as const
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// RewardsRegistry
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
export const rewardsRegistryAbi = [
{
type: 'constructor',
inputs: [
{ name: '_avs', internalType: 'address', type: 'address' },
{ name: '_rewardsAgent', internalType: 'address', type: 'address' },
],
stateMutability: 'nonpayable',
},
{ type: 'receive', stateMutability: 'payable' },
{
type: 'function',
inputs: [],
name: 'avs',
outputs: [{ name: '', internalType: 'address', type: 'address' }],
stateMutability: 'view',
},
{
type: 'function',
inputs: [
{ name: 'operatorAddress', internalType: 'address', type: 'address' },
{ name: 'operatorPoints', internalType: 'uint256', type: 'uint256' },
{ name: 'numberOfLeaves', internalType: 'uint256', type: 'uint256' },
{ name: 'leafIndex', internalType: 'uint256', type: 'uint256' },
{ name: 'proof', internalType: 'bytes32[]', type: 'bytes32[]' },
],
name: 'claimLatestRewards',
outputs: [],
stateMutability: 'nonpayable',
},
{
type: 'function',
inputs: [
{ name: 'operatorAddress', internalType: 'address', type: 'address' },
{ name: 'rootIndex', internalType: 'uint256', type: 'uint256' },
{ name: 'operatorPoints', internalType: 'uint256', type: 'uint256' },
{ name: 'numberOfLeaves', internalType: 'uint256', type: 'uint256' },
{ name: 'leafIndex', internalType: 'uint256', type: 'uint256' },
{ name: 'proof', internalType: 'bytes32[]', type: 'bytes32[]' },
],
name: 'claimRewards',
outputs: [],
stateMutability: 'nonpayable',
},
{
type: 'function',
inputs: [
{ name: 'operatorAddress', internalType: 'address', type: 'address' },
{ name: 'rootIndices', internalType: 'uint256[]', type: 'uint256[]' },
{ name: 'operatorPoints', internalType: 'uint256[]', type: 'uint256[]' },
{ name: 'numberOfLeaves', internalType: 'uint256[]', type: 'uint256[]' },
{ name: 'leafIndices', internalType: 'uint256[]', type: 'uint256[]' },
{ name: 'proofs', internalType: 'bytes32[][]', type: 'bytes32[][]' },
],
name: 'claimRewardsBatch',
outputs: [],
stateMutability: 'nonpayable',
},
{
type: 'function',
inputs: [],
name: 'getLatestMerkleRoot',
outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }],
stateMutability: 'view',
},
{
type: 'function',
inputs: [],
name: 'getLatestMerkleRootIndex',
outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }],
stateMutability: 'view',
},
{
type: 'function',
inputs: [{ name: 'index', internalType: 'uint256', type: 'uint256' }],
name: 'getMerkleRootByIndex',
outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }],
stateMutability: 'view',
},
{
type: 'function',
inputs: [],
name: 'getMerkleRootHistoryLength',
outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }],
stateMutability: 'view',
},
{
type: 'function',
inputs: [
{ name: 'operatorAddress', internalType: 'address', type: 'address' },
{ name: 'rootIndex', internalType: 'uint256', type: 'uint256' },
],
name: 'hasClaimedByIndex',
outputs: [{ name: '', internalType: 'bool', type: 'bool' }],
stateMutability: 'view',
},
{
type: 'function',
inputs: [{ name: '', internalType: 'uint256', type: 'uint256' }],
name: 'merkleRootHistory',
outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }],
stateMutability: 'view',
},
{
type: 'function',
inputs: [
{ name: '', internalType: 'address', type: 'address' },
{ name: '', internalType: 'uint256', type: 'uint256' },
],
name: 'operatorClaimedByIndex',
outputs: [{ name: '', internalType: 'bool', type: 'bool' }],
stateMutability: 'view',
},
{
type: 'function',
inputs: [],
name: 'rewardsAgent',
outputs: [{ name: '', internalType: 'address', type: 'address' }],
stateMutability: 'view',
},
{
type: 'function',
inputs: [
{ name: '_rewardsAgent', internalType: 'address', type: 'address' },
],
name: 'setRewardsAgent',
outputs: [],
stateMutability: 'nonpayable',
},
{
type: 'function',
inputs: [
{ name: 'newMerkleRoot', internalType: 'bytes32', type: 'bytes32' },
],
name: 'updateRewardsMerkleRoot',
outputs: [],
stateMutability: 'nonpayable',
},
{
type: 'event',
anonymous: false,
inputs: [
{
name: 'operatorAddress',
internalType: 'address',
type: 'address',
indexed: true,
},
{
name: 'rootIndices',
internalType: 'uint256[]',
type: 'uint256[]',
indexed: false,
},
{
name: 'points',
internalType: 'uint256[]',
type: 'uint256[]',
indexed: false,
},
{
name: 'totalRewardsAmount',
internalType: 'uint256',
type: 'uint256',
indexed: false,
},
],
name: 'RewardsBatchClaimedForIndices',
},
{
type: 'event',
anonymous: false,
inputs: [
{
name: 'operatorAddress',
internalType: 'address',
type: 'address',
indexed: true,
},
{
name: 'rootIndex',
internalType: 'uint256',
type: 'uint256',
indexed: true,
},
{
name: 'points',
internalType: 'uint256',
type: 'uint256',
indexed: false,
},
{
name: 'rewardsAmount',
internalType: 'uint256',
type: 'uint256',
indexed: false,
},
],
name: 'RewardsClaimedForIndex',
},
{
type: 'event',
anonymous: false,
inputs: [
{
name: 'oldRoot',
internalType: 'bytes32',
type: 'bytes32',
indexed: false,
},
{
name: 'newRoot',
internalType: 'bytes32',
type: 'bytes32',
indexed: true,
},
{
name: 'newRootIndex',
internalType: 'uint256',
type: 'uint256',
indexed: false,
},
],
name: 'RewardsMerkleRootUpdated',
},
{ type: 'error', inputs: [], name: 'ArrayLengthMismatch' },
{ type: 'error', inputs: [], name: 'InvalidMerkleProof' },
{ type: 'error', inputs: [], name: 'InvalidMerkleRootIndex' },
{ type: 'error', inputs: [], name: 'OnlyAVS' },
{ type: 'error', inputs: [], name: 'OnlyRewardsAgent' },
{ type: 'error', inputs: [], name: 'RewardsAlreadyClaimedForIndex' },
{ type: 'error', inputs: [], name: 'RewardsMerkleRootNotSet' },
{ type: 'error', inputs: [], name: 'RewardsTransferFailed' },
] as const
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// StrategyBaseTVLLimits
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -11235,15 +10902,6 @@ export const readDataHavenServiceManagerGetRestakeableStrategies =
functionName: 'getRestakeableStrategies',
})
/**
* Wraps __{@link readContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"operatorSetToRewardsRegistry"`
*/
export const readDataHavenServiceManagerOperatorSetToRewardsRegistry =
/*#__PURE__*/ createReadContract({
abi: dataHavenServiceManagerAbi,
functionName: 'operatorSetToRewardsRegistry',
})
/**
* Wraps __{@link readContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"owner"`
*/
@ -11350,33 +11008,6 @@ export const writeDataHavenServiceManagerAddValidatorToAllowlist =
functionName: 'addValidatorToAllowlist',
})
/**
* Wraps __{@link writeContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"claimLatestOperatorRewards"`
*/
export const writeDataHavenServiceManagerClaimLatestOperatorRewards =
/*#__PURE__*/ createWriteContract({
abi: dataHavenServiceManagerAbi,
functionName: 'claimLatestOperatorRewards',
})
/**
* Wraps __{@link writeContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"claimOperatorRewards"`
*/
export const writeDataHavenServiceManagerClaimOperatorRewards =
/*#__PURE__*/ createWriteContract({
abi: dataHavenServiceManagerAbi,
functionName: 'claimOperatorRewards',
})
/**
* Wraps __{@link writeContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"claimOperatorRewardsBatch"`
*/
export const writeDataHavenServiceManagerClaimOperatorRewardsBatch =
/*#__PURE__*/ createWriteContract({
abi: dataHavenServiceManagerAbi,
functionName: 'claimOperatorRewardsBatch',
})
/**
* Wraps __{@link writeContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"createAVSRewardsSubmission"`
*/
@ -11548,15 +11179,6 @@ export const writeDataHavenServiceManagerSetClaimerFor =
functionName: 'setClaimerFor',
})
/**
* Wraps __{@link writeContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"setRewardsAgent"`
*/
export const writeDataHavenServiceManagerSetRewardsAgent =
/*#__PURE__*/ createWriteContract({
abi: dataHavenServiceManagerAbi,
functionName: 'setRewardsAgent',
})
/**
* Wraps __{@link writeContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"setRewardsInitiator"`
*/
@ -11566,15 +11188,6 @@ export const writeDataHavenServiceManagerSetRewardsInitiator =
functionName: 'setRewardsInitiator',
})
/**
* Wraps __{@link writeContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"setRewardsRegistry"`
*/
export const writeDataHavenServiceManagerSetRewardsRegistry =
/*#__PURE__*/ createWriteContract({
abi: dataHavenServiceManagerAbi,
functionName: 'setRewardsRegistry',
})
/**
* Wraps __{@link writeContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"setSnowbridgeGateway"`
*/
@ -11662,33 +11275,6 @@ export const simulateDataHavenServiceManagerAddValidatorToAllowlist =
functionName: 'addValidatorToAllowlist',
})
/**
* Wraps __{@link simulateContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"claimLatestOperatorRewards"`
*/
export const simulateDataHavenServiceManagerClaimLatestOperatorRewards =
/*#__PURE__*/ createSimulateContract({
abi: dataHavenServiceManagerAbi,
functionName: 'claimLatestOperatorRewards',
})
/**
* Wraps __{@link simulateContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"claimOperatorRewards"`
*/
export const simulateDataHavenServiceManagerClaimOperatorRewards =
/*#__PURE__*/ createSimulateContract({
abi: dataHavenServiceManagerAbi,
functionName: 'claimOperatorRewards',
})
/**
* Wraps __{@link simulateContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"claimOperatorRewardsBatch"`
*/
export const simulateDataHavenServiceManagerClaimOperatorRewardsBatch =
/*#__PURE__*/ createSimulateContract({
abi: dataHavenServiceManagerAbi,
functionName: 'claimOperatorRewardsBatch',
})
/**
* Wraps __{@link simulateContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"createAVSRewardsSubmission"`
*/
@ -11860,15 +11446,6 @@ export const simulateDataHavenServiceManagerSetClaimerFor =
functionName: 'setClaimerFor',
})
/**
* Wraps __{@link simulateContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"setRewardsAgent"`
*/
export const simulateDataHavenServiceManagerSetRewardsAgent =
/*#__PURE__*/ createSimulateContract({
abi: dataHavenServiceManagerAbi,
functionName: 'setRewardsAgent',
})
/**
* Wraps __{@link simulateContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"setRewardsInitiator"`
*/
@ -11878,15 +11455,6 @@ export const simulateDataHavenServiceManagerSetRewardsInitiator =
functionName: 'setRewardsInitiator',
})
/**
* Wraps __{@link simulateContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"setRewardsRegistry"`
*/
export const simulateDataHavenServiceManagerSetRewardsRegistry =
/*#__PURE__*/ createSimulateContract({
abi: dataHavenServiceManagerAbi,
functionName: 'setRewardsRegistry',
})
/**
* Wraps __{@link simulateContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"setSnowbridgeGateway"`
*/
@ -11992,15 +11560,6 @@ export const watchDataHavenServiceManagerRewardsInitiatorUpdatedEvent =
eventName: 'RewardsInitiatorUpdated',
})
/**
* Wraps __{@link watchContractEvent}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `eventName` set to `"RewardsRegistrySet"`
*/
export const watchDataHavenServiceManagerRewardsRegistrySetEvent =
/*#__PURE__*/ createWatchContractEvent({
abi: dataHavenServiceManagerAbi,
eventName: 'RewardsRegistrySet',
})
/**
* Wraps __{@link watchContractEvent}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `eventName` set to `"RewardsSubmitted"`
*/
@ -15901,229 +15460,6 @@ export const watchRewardsCoordinatorUnpausedEvent =
eventName: 'Unpaused',
})
/**
* Wraps __{@link readContract}__ with `abi` set to __{@link rewardsRegistryAbi}__
*/
export const readRewardsRegistry = /*#__PURE__*/ createReadContract({
abi: rewardsRegistryAbi,
})
/**
* Wraps __{@link readContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"avs"`
*/
export const readRewardsRegistryAvs = /*#__PURE__*/ createReadContract({
abi: rewardsRegistryAbi,
functionName: 'avs',
})
/**
* Wraps __{@link readContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"getLatestMerkleRoot"`
*/
export const readRewardsRegistryGetLatestMerkleRoot =
/*#__PURE__*/ createReadContract({
abi: rewardsRegistryAbi,
functionName: 'getLatestMerkleRoot',
})
/**
* Wraps __{@link readContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"getLatestMerkleRootIndex"`
*/
export const readRewardsRegistryGetLatestMerkleRootIndex =
/*#__PURE__*/ createReadContract({
abi: rewardsRegistryAbi,
functionName: 'getLatestMerkleRootIndex',
})
/**
* Wraps __{@link readContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"getMerkleRootByIndex"`
*/
export const readRewardsRegistryGetMerkleRootByIndex =
/*#__PURE__*/ createReadContract({
abi: rewardsRegistryAbi,
functionName: 'getMerkleRootByIndex',
})
/**
* Wraps __{@link readContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"getMerkleRootHistoryLength"`
*/
export const readRewardsRegistryGetMerkleRootHistoryLength =
/*#__PURE__*/ createReadContract({
abi: rewardsRegistryAbi,
functionName: 'getMerkleRootHistoryLength',
})
/**
* Wraps __{@link readContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"hasClaimedByIndex"`
*/
export const readRewardsRegistryHasClaimedByIndex =
/*#__PURE__*/ createReadContract({
abi: rewardsRegistryAbi,
functionName: 'hasClaimedByIndex',
})
/**
* Wraps __{@link readContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"merkleRootHistory"`
*/
export const readRewardsRegistryMerkleRootHistory =
/*#__PURE__*/ createReadContract({
abi: rewardsRegistryAbi,
functionName: 'merkleRootHistory',
})
/**
* Wraps __{@link readContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"operatorClaimedByIndex"`
*/
export const readRewardsRegistryOperatorClaimedByIndex =
/*#__PURE__*/ createReadContract({
abi: rewardsRegistryAbi,
functionName: 'operatorClaimedByIndex',
})
/**
* Wraps __{@link readContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"rewardsAgent"`
*/
export const readRewardsRegistryRewardsAgent = /*#__PURE__*/ createReadContract(
{ abi: rewardsRegistryAbi, functionName: 'rewardsAgent' },
)
/**
* Wraps __{@link writeContract}__ with `abi` set to __{@link rewardsRegistryAbi}__
*/
export const writeRewardsRegistry = /*#__PURE__*/ createWriteContract({
abi: rewardsRegistryAbi,
})
/**
* Wraps __{@link writeContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"claimLatestRewards"`
*/
export const writeRewardsRegistryClaimLatestRewards =
/*#__PURE__*/ createWriteContract({
abi: rewardsRegistryAbi,
functionName: 'claimLatestRewards',
})
/**
* Wraps __{@link writeContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"claimRewards"`
*/
export const writeRewardsRegistryClaimRewards =
/*#__PURE__*/ createWriteContract({
abi: rewardsRegistryAbi,
functionName: 'claimRewards',
})
/**
* Wraps __{@link writeContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"claimRewardsBatch"`
*/
export const writeRewardsRegistryClaimRewardsBatch =
/*#__PURE__*/ createWriteContract({
abi: rewardsRegistryAbi,
functionName: 'claimRewardsBatch',
})
/**
* Wraps __{@link writeContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"setRewardsAgent"`
*/
export const writeRewardsRegistrySetRewardsAgent =
/*#__PURE__*/ createWriteContract({
abi: rewardsRegistryAbi,
functionName: 'setRewardsAgent',
})
/**
* Wraps __{@link writeContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"updateRewardsMerkleRoot"`
*/
export const writeRewardsRegistryUpdateRewardsMerkleRoot =
/*#__PURE__*/ createWriteContract({
abi: rewardsRegistryAbi,
functionName: 'updateRewardsMerkleRoot',
})
/**
* Wraps __{@link simulateContract}__ with `abi` set to __{@link rewardsRegistryAbi}__
*/
export const simulateRewardsRegistry = /*#__PURE__*/ createSimulateContract({
abi: rewardsRegistryAbi,
})
/**
* Wraps __{@link simulateContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"claimLatestRewards"`
*/
export const simulateRewardsRegistryClaimLatestRewards =
/*#__PURE__*/ createSimulateContract({
abi: rewardsRegistryAbi,
functionName: 'claimLatestRewards',
})
/**
* Wraps __{@link simulateContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"claimRewards"`
*/
export const simulateRewardsRegistryClaimRewards =
/*#__PURE__*/ createSimulateContract({
abi: rewardsRegistryAbi,
functionName: 'claimRewards',
})
/**
* Wraps __{@link simulateContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"claimRewardsBatch"`
*/
export const simulateRewardsRegistryClaimRewardsBatch =
/*#__PURE__*/ createSimulateContract({
abi: rewardsRegistryAbi,
functionName: 'claimRewardsBatch',
})
/**
* Wraps __{@link simulateContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"setRewardsAgent"`
*/
export const simulateRewardsRegistrySetRewardsAgent =
/*#__PURE__*/ createSimulateContract({
abi: rewardsRegistryAbi,
functionName: 'setRewardsAgent',
})
/**
* Wraps __{@link simulateContract}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `functionName` set to `"updateRewardsMerkleRoot"`
*/
export const simulateRewardsRegistryUpdateRewardsMerkleRoot =
/*#__PURE__*/ createSimulateContract({
abi: rewardsRegistryAbi,
functionName: 'updateRewardsMerkleRoot',
})
/**
* Wraps __{@link watchContractEvent}__ with `abi` set to __{@link rewardsRegistryAbi}__
*/
export const watchRewardsRegistryEvent = /*#__PURE__*/ createWatchContractEvent(
{ abi: rewardsRegistryAbi },
)
/**
* Wraps __{@link watchContractEvent}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `eventName` set to `"RewardsBatchClaimedForIndices"`
*/
export const watchRewardsRegistryRewardsBatchClaimedForIndicesEvent =
/*#__PURE__*/ createWatchContractEvent({
abi: rewardsRegistryAbi,
eventName: 'RewardsBatchClaimedForIndices',
})
/**
* Wraps __{@link watchContractEvent}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `eventName` set to `"RewardsClaimedForIndex"`
*/
export const watchRewardsRegistryRewardsClaimedForIndexEvent =
/*#__PURE__*/ createWatchContractEvent({
abi: rewardsRegistryAbi,
eventName: 'RewardsClaimedForIndex',
})
/**
* Wraps __{@link watchContractEvent}__ with `abi` set to __{@link rewardsRegistryAbi}__ and `eventName` set to `"RewardsMerkleRootUpdated"`
*/
export const watchRewardsRegistryRewardsMerkleRootUpdatedEvent =
/*#__PURE__*/ createWatchContractEvent({
abi: rewardsRegistryAbi,
eventName: 'RewardsMerkleRootUpdated',
})
/**
* Wraps __{@link readContract}__ with `abi` set to __{@link strategyBaseTvlLimitsAbi}__
*/

View file

@ -53,7 +53,6 @@ export type SolochainConfig = {
substrateWsEndpoint: string;
beefyClientAddress: string;
gatewayAddress: string;
rewardsRegistryAddress: string;
ethClEndpoint: string;
};
@ -172,7 +171,6 @@ export const generateRelayerConfig = async (
cfg.source.beacon.datastore.location = "/relay-data";
cfg.sink.ethereum.endpoint = config.ethElRpcEndpoint;
cfg.sink.contracts.Gateway = config.gatewayAddress;
cfg["reward-address"] = config.rewardsRegistryAddress;
await Bun.write(outputFilePath, JSON.stringify(cfg, null, 4));
logger.success(`Updated solochain config written to ${outputFilePath}`);
@ -436,10 +434,8 @@ export const launchRelayers = async (
const deployments = await parseDeploymentsFile();
const beefyClientAddress = deployments.BeefyClient;
const gatewayAddress = deployments.Gateway;
const rewardsRegistryAddress = deployments.RewardsRegistry;
invariant(beefyClientAddress, "❌ BeefyClient address not found in anvil.json");
invariant(gatewayAddress, "❌ Gateway address not found in anvil.json");
invariant(rewardsRegistryAddress, "❌ RewardsRegistry address not found in anvil.json");
logger.debug(`Ensuring output directory exists: ${RELAYER_CONFIG_DIR}`);
await $`mkdir -p ${RELAYER_CONFIG_DIR}`.quiet();
@ -493,7 +489,6 @@ export const launchRelayers = async (
substrateWsEndpoint,
beefyClientAddress,
gatewayAddress,
rewardsRegistryAddress,
ethClEndpoint
},
pk: {

View file

@ -1,12 +1,7 @@
import { $ } from "bun";
import { CHAIN_CONFIGS, loadChainConfig } from "configs/contracts/config";
import invariant from "tiny-invariant";
import {
logger,
parseDeploymentsFile,
parseRewardsInfoFile,
runShellCommandWithLogger
} from "utils";
import { logger, parseDeploymentsFile, runShellCommandWithLogger } from "utils";
import type { ParameterCollection } from "utils/parameters";
import { encodeFunctionData } from "viem";
import { privateKeyToAccount } from "viem/accounts";
@ -100,11 +95,7 @@ export const executeDeployment = async (
env
});
// After deployment, read the:
// - Gateway address
// - RewardsAgent address
// - RewardsAgentOrigin (bytes32)
// and add it to parameters if collection is provided
// After deployment, read the Gateway address and add it to parameters if collection is provided
if (parameterCollection) {
await updateParameters(parameterCollection, chain);
}
@ -121,10 +112,7 @@ export const updateParameters = async (
) => {
try {
const deployments = await parseDeploymentsFile(chain);
const rewardsInfo = await parseRewardsInfoFile(chain);
const gatewayAddress = deployments.Gateway;
const rewardsAgentOrigin = rewardsInfo.RewardsAgentOrigin;
const updateRewardsMerkleRootSelector = rewardsInfo.updateRewardsMerkleRootSelector;
const serviceManagerAddress = deployments.ServiceManager;
if (gatewayAddress) {
@ -138,26 +126,6 @@ export const updateParameters = async (
logger.warn("⚠️ Gateway address not found in deployments file");
}
if (updateRewardsMerkleRootSelector) {
logger.debug(`📝 Adding RewardsUpdateSelector parameter: ${updateRewardsMerkleRootSelector}`);
parameterCollection.addParameter({
name: "RewardsUpdateSelector",
value: updateRewardsMerkleRootSelector
});
} else {
logger.warn("⚠️ updateRewardsMerkleRootSelector not found in rewards info file");
}
if (rewardsAgentOrigin) {
logger.debug(`📝 Adding RewardsAgentOrigin parameter: ${rewardsAgentOrigin}`);
parameterCollection.addParameter({
name: "RewardsAgentOrigin",
value: rewardsAgentOrigin
});
} else {
logger.warn("⚠️ RewardsAgentOrigin not found in deployments file");
}
if (serviceManagerAddress) {
logger.debug(`📝 Adding DatahavenServiceManagerAddress parameter: ${serviceManagerAddress}`);
parameterCollection.addParameter({
@ -282,14 +250,11 @@ const buildDeploymentEnv = (options: ContractDeploymentOptions) => {
const emitOwnerTransactionCalldata = async (chain?: string) => {
try {
const deployments = await parseDeploymentsFile(chain);
const rewardsInfo = await parseRewardsInfoFile(chain);
const serviceManager = deployments.ServiceManager;
const rewardsRegistry = deployments.RewardsRegistry;
const rewardsAgent = rewardsInfo.RewardsAgent;
if (!serviceManager || !rewardsRegistry || !rewardsAgent) {
logger.warn("⚠️ Missing deployment artifacts; cannot produce multisig calldata.");
if (!serviceManager) {
logger.warn("⚠️ Missing ServiceManager address; cannot produce multisig calldata.");
return;
}
@ -304,28 +269,6 @@ const emitOwnerTransactionCalldata = async (chain?: string) => {
functionName: "updateAVSMetadataURI",
args: [""]
})
},
{
label: "Attach RewardsRegistry",
description: "DataHavenServiceManager.setRewardsRegistry(VALIDATORS_SET_ID, address)",
to: serviceManager,
value: "0",
data: encodeFunctionData({
abi: dataHavenServiceManagerAbi,
functionName: "setRewardsRegistry",
args: [0, rewardsRegistry]
})
},
{
label: "Set Rewards Agent",
description: "DataHavenServiceManager.setRewardsAgent(VALIDATORS_SET_ID, address)",
to: serviceManager,
value: "0",
data: encodeFunctionData({
abi: dataHavenServiceManagerAbi,
functionName: "setRewardsAgent",
args: [0, rewardsAgent]
})
}
];

View file

@ -1,257 +0,0 @@
import { beforeAll, describe, expect, it } from "bun:test";
import { ANVIL_FUNDED_ACCOUNTS, CROSS_CHAIN_TIMEOUTS, logger } from "utils";
import { type Address, decodeEventLog, type Hex, isAddressEqual, padHex } from "viem";
import validatorSet from "../configs/validator-set.json";
import { BaseTestSuite } from "../framework";
import { getContractInstance, parseRewardsInfoFile } from "../utils/contracts";
import { waitForDataHavenEvent, waitForEthereumEvent } from "../utils/events";
class RewardsMessageTestSuite extends BaseTestSuite {
constructor() {
super({
suiteName: "rewards-message"
});
this.setupHooks();
}
}
const suite = new RewardsMessageTestSuite();
describe("Rewards Message Flow", () => {
let rewardsRegistry!: any;
let serviceManager!: any;
let publicClient!: any;
let dhApi!: any;
let eraIndex!: number;
let totalPoints!: bigint;
let newRootIndex!: bigint;
beforeAll(async () => {
const connectors = suite.getTestConnectors();
publicClient = connectors.publicClient;
dhApi = connectors.dhApi;
rewardsRegistry = await getContractInstance("RewardsRegistry");
serviceManager = await getContractInstance("ServiceManager");
});
it("should verify rewards infrastructure deployment", async () => {
const rewardsInfo = await parseRewardsInfoFile();
const gateway = await getContractInstance("Gateway");
expect(rewardsRegistry.address).toBeDefined();
expect(rewardsInfo.RewardsAgent).toBeDefined();
expect(gateway.address).toBeDefined();
const [agentAddress, avsAddress] = await Promise.all([
publicClient.readContract({
address: rewardsRegistry.address,
abi: rewardsRegistry.abi,
functionName: "rewardsAgent",
args: []
}) as Promise<Address>,
publicClient.readContract({
address: rewardsRegistry.address,
abi: rewardsRegistry.abi,
functionName: "avs",
args: []
}) as Promise<Address>
]);
expect(isAddressEqual(agentAddress, rewardsInfo.RewardsAgent as Address)).toBe(true);
expect(isAddressEqual(avsAddress, serviceManager.address as Address)).toBe(true);
});
it("should wait for era end and update merkle root on Ethereum", async () => {
// Get current era and Ethereum block for event filtering
const [currentEra, fromBlock] = await Promise.all([
dhApi.query.ExternalValidators.ActiveEra.getValue(),
publicClient.getBlockNumber()
]);
const currentEraIndex = currentEra?.index ?? 0;
logger.debug(`Waiting for RewardsMessageSent for era ${currentEraIndex}`);
const payload = await waitForDataHavenEvent<any>({
api: dhApi,
pallet: "ExternalValidatorsRewards",
event: "RewardsMessageSent",
filter: (e) => e.era_index === currentEraIndex,
timeout: CROSS_CHAIN_TIMEOUTS.DH_TO_ETH_MS
});
expect(payload).toBeDefined();
const merkleRoot: Hex = payload.rewards_merkle_root.asHex() as Hex;
totalPoints = payload.total_points;
eraIndex = payload.era_index;
expect(totalPoints).toBeGreaterThan(0n);
// Wait for RewardsMerkleRootUpdated
const expectedRoot: Hex = padHex(merkleRoot, { size: 32 });
const rootUpdatedEvent = await waitForEthereumEvent({
client: publicClient,
address: rewardsRegistry.address,
abi: rewardsRegistry.abi,
eventName: "RewardsMerkleRootUpdated",
args: { newRoot: expectedRoot },
fromBlock,
timeout: CROSS_CHAIN_TIMEOUTS.DH_TO_ETH_MS
});
const rootDecoded = decodeEventLog({
abi: rewardsRegistry.abi,
data: rootUpdatedEvent.data,
topics: rootUpdatedEvent.topics
}) as { args: { oldRoot: Hex; newRoot: Hex; newRootIndex: bigint } };
// Store the new root index for claiming tests
newRootIndex = rootDecoded.args.newRootIndex;
// Verify the stored root matches
const storedRoot: Hex = (await publicClient.readContract({
address: rewardsRegistry.address,
abi: rewardsRegistry.abi,
functionName: "merkleRootHistory",
args: [newRootIndex]
})) as Hex;
expect(storedRoot.toLowerCase()).toEqual(expectedRoot.toLowerCase());
});
it("should successfully claim rewards for validator", async () => {
// Fund RewardsRegistry for reward payouts
const { walletClient: fundingWallet } = suite.getTestConnectors();
const fundingTx = await fundingWallet.sendTransaction({
to: rewardsRegistry.address as Address,
value: totalPoints,
chain: null
});
const fundingReceipt = await publicClient.waitForTransactionReceipt({ hash: fundingTx });
expect(fundingReceipt.status).toBe("success");
// Get era reward points and pick first validator
const rewardPoints =
await dhApi.query.ExternalValidatorsRewards.RewardPointsForEra.getValue(eraIndex);
expect(rewardPoints).toBeDefined();
expect(rewardPoints.total).toBeGreaterThan(0);
const relayerEthAccount = ANVIL_FUNDED_ACCOUNTS[1].publicKey.toLowerCase();
const pick = rewardPoints.individual.find(([account]: [any, any]) => {
const v = validatorSet.validators.find(
(cfg) => cfg.solochainAddress.toLowerCase() === String(account).toLowerCase()
);
return v && v.publicKey.toLowerCase() !== relayerEthAccount;
});
const [validatorAccount, points] = (pick ?? rewardPoints.individual[0]) as [any, any];
const match = validatorSet.validators.find(
(v) => v.solochainAddress.toLowerCase() === String(validatorAccount).toLowerCase()
);
if (!match) {
throw new Error(
`Validator config not found for solochain address ${String(validatorAccount)}`
);
}
// Generate merkle proof via runtime API
const merkleProof = await dhApi.apis.ExternalValidatorsRewardsApi.generate_rewards_merkle_proof(
String(validatorAccount),
eraIndex
);
expect(merkleProof).toBeDefined();
// Get validator credentials and create operator wallet
const factory = suite.getConnectorFactory();
const operatorWallet = factory.createWalletClient(match!.privateKey as `0x${string}`);
const resolvedOperator: Address = operatorWallet.account.address;
// Ensure claim not already recorded
const claimedBefore = (await publicClient.readContract({
address: rewardsRegistry.address,
abi: rewardsRegistry.abi,
functionName: "hasClaimedByIndex",
args: [resolvedOperator, newRootIndex]
})) as boolean;
expect(claimedBefore).toBe(false);
// Record balances at a specific block to avoid stale RPC reads
const balanceBlockNumber = Number(await publicClient.getBlockNumber());
const operatorBalanceBefore = await publicClient.getBalance({
address: resolvedOperator,
blockNumber: balanceBlockNumber
});
const registryBalanceBefore = BigInt(
await publicClient.getBalance({
address: rewardsRegistry.address as Address,
blockNumber: balanceBlockNumber
})
);
// Submit claim transaction
const claimTx = await operatorWallet.writeContract({
address: serviceManager.address as Address,
abi: serviceManager.abi,
functionName: "claimOperatorRewards",
chain: null,
args: [
0, // strategy index
newRootIndex,
BigInt(points),
BigInt(merkleProof.number_of_leaves),
BigInt(merkleProof.leaf_index),
merkleProof.proof.map((node: { asHex: () => string }) => node.asHex()) as readonly Hex[]
]
});
// Wait for transaction confirmation
const claimReceipt = await publicClient.waitForTransactionReceipt({ hash: claimTx });
expect(claimReceipt.status).toBe("success");
logger.debug(
`Claim tx type: ${claimReceipt.type}, effectiveGasPrice: ${claimReceipt.effectiveGasPrice}, gasUsed: ${claimReceipt.gasUsed}`
);
// Decode and validate claim event from receipt
const claimLog = claimReceipt.logs.find(
(log: { address: string }) =>
log.address.toLowerCase() === rewardsRegistry.address.toLowerCase()
)!;
const { args: claimArgs } = decodeEventLog({
abi: rewardsRegistry.abi,
data: claimLog.data,
topics: claimLog.topics
}) as {
args: { operatorAddress: Address; rootIndex: bigint; points: bigint; rewardsAmount: bigint };
};
expect(isAddressEqual(claimArgs.operatorAddress, resolvedOperator)).toBe(true);
expect(claimArgs.rootIndex).toEqual(newRootIndex);
expect(claimArgs.points).toEqual(BigInt(points));
expect(claimArgs.rewardsAmount).toBeGreaterThan(0n);
const claimedAfter = (await publicClient.readContract({
address: rewardsRegistry.address,
abi: rewardsRegistry.abi,
functionName: "hasClaimedByIndex",
args: [resolvedOperator, newRootIndex]
})) as boolean;
expect(claimedAfter).toBe(true);
// Validate RewardsRegistry balance decrease matches claimed rewards
const claimBlockNumber = Number(claimReceipt.blockNumber);
const registryBalanceAfter = await publicClient.getBalance({
address: rewardsRegistry.address as Address,
blockNumber: claimBlockNumber
});
expect(registryBalanceBefore - registryBalanceAfter).toEqual(claimArgs.rewardsAmount);
expect(claimArgs.rewardsAmount).toEqual(BigInt(points));
// Validate operator received rewards (accounting for gas)
const operatorBalanceAfter = await publicClient.getBalance({
address: resolvedOperator,
blockNumber: claimBlockNumber
});
const gasCost = BigInt(claimReceipt.gasUsed) * BigInt(claimReceipt.effectiveGasPrice);
const netBalanceChange = BigInt(operatorBalanceAfter) - BigInt(operatorBalanceBefore);
// Operator balance should have changed by: rewards - gasCost
expect(netBalanceChange + gasCost).toEqual(claimArgs.rewardsAmount);
});
});

View file

@ -11,11 +11,6 @@ const ethAddressCustom = z.custom<`0x${string}`>(
(val) => typeof val === "string" && ethAddressRegex.test(val),
{ message: "Invalid Ethereum address" }
);
const ethBytes32Regex = /^0x[a-fA-F0-9]{64}$/;
const ethBytes32 = z.string().regex(ethBytes32Regex, "Invalid Ethereum bytes32");
const ethBytes4Regex = /^0x[a-fA-F0-9]{8}$/;
const ethBytes4 = z.string().regex(ethBytes4Regex, "Invalid Ethereum bytes4");
const DeployedStrategySchema = z.object({
address: ethAddress,
underlyingToken: ethAddress,
@ -29,8 +24,6 @@ const DeploymentsSchema = z.object({
Gateway: ethAddressCustom,
ServiceManager: ethAddressCustom,
ServiceManagerImplementation: ethAddressCustom,
RewardsRegistry: ethAddressCustom,
RewardsAgent: ethAddressCustom,
DelegationManager: ethAddressCustom,
StrategyManager: ethAddressCustom,
AVSDirectory: ethAddressCustom,
@ -46,14 +39,6 @@ const DeploymentsSchema = z.object({
export type Deployments = z.infer<typeof DeploymentsSchema>;
const RewardsInfoSchema = z.object({
RewardsAgent: ethAddressCustom,
RewardsAgentOrigin: ethBytes32,
updateRewardsMerkleRootSelector: ethBytes4
});
export type RewardsInfo = z.infer<typeof RewardsInfoSchema>;
export const parseDeploymentsFile = async (network = "anvil"): Promise<Deployments> => {
const deploymentsPath = `../contracts/deployments/${network}.json`;
const deploymentsFile = Bun.file(deploymentsPath);
@ -73,24 +58,6 @@ export const parseDeploymentsFile = async (network = "anvil"): Promise<Deploymen
}
};
export const parseRewardsInfoFile = async (network = "anvil"): Promise<RewardsInfo> => {
const rewardsInfoPath = `../contracts/deployments/${network}-rewards-info.json`;
const rewardsInfoFile = Bun.file(rewardsInfoPath);
if (!(await rewardsInfoFile.exists())) {
logger.error(`File ${rewardsInfoPath} does not exist`);
throw new Error(`Error reading ${network} rewards info file`);
}
const rewardsInfoJson = await rewardsInfoFile.json();
try {
const parsedRewardsInfo = RewardsInfoSchema.parse(rewardsInfoJson);
logger.debug(`Successfully parsed ${network} rewards info file.`);
return parsedRewardsInfo;
} catch (error) {
logger.error(`Failed to parse ${network} rewards info file:`, error);
throw new Error(`Invalid ${network} rewards info file format`);
}
};
// Add to this if we add any new contracts
const abiMap = {
BeefyClient: generated.beefyClientAbi,
@ -98,8 +65,6 @@ const abiMap = {
Gateway: generated.gatewayAbi,
ServiceManager: generated.dataHavenServiceManagerAbi,
ServiceManagerImplementation: generated.dataHavenServiceManagerAbi,
RewardsRegistry: generated.rewardsRegistryAbi,
RewardsAgent: generated.agentAbi,
DelegationManager: generated.delegationManagerAbi,
StrategyManager: generated.strategyManagerAbi,
AVSDirectory: generated.avsDirectoryAbi,
@ -111,7 +76,7 @@ const abiMap = {
ETHPOSDeposit: generated.iethposDepositAbi,
BaseStrategyImplementation: generated.strategyBaseTvlLimitsAbi,
DeployedStrategies: erc20Abi
} as const satisfies Record<keyof Omit<Deployments, "network" | "RewardsAgentOrigin">, Abi>;
} as const satisfies Record<keyof Omit<Deployments, "network">, Abi>;
type ContractName = keyof typeof abiMap;
type AbiFor<C extends ContractName> = (typeof abiMap)[C];

View file

@ -12,7 +12,6 @@ export default defineConfig({
"AgentExecutor.sol/**",
"Gateway.sol/**",
"TransparentUpgradeableProxy.sol/**",
"RewardsRegistry.sol/**",
"Agent.sol/**",
"StrategyManager.sol/**",
"AVSDirectory.sol/**",