mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 09:50:01 +00:00
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:
parent
e04023ef11
commit
9be1acc97e
50 changed files with 985 additions and 4472 deletions
|
|
@ -1 +1 @@
|
|||
{"RewardsAgent": "0xac06641381166cf085281c45292147f833C622d7","RewardsAgentOrigin": "0x0000000000000000000000000000000000000000000000000000000000000000","updateRewardsMerkleRootSelector": "0xdc3d04ec"}
|
||||
{"RewardsAgent": "0xac06641381166cf085281c45292147f833C622d7","RewardsAgentOrigin": "0x0000000000000000000000000000000000000000000000000000000000000000"}
|
||||
|
|
@ -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"}]}
|
||||
|
|
@ -1 +1 @@
|
|||
df0978809ee25447c22aca90a4064b9e4a6ea97b
|
||||
5cb16238bf8311a3f27dd130cd89f0cd7befda5d
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
14
operator/Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
]
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) -> (
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) -> (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) -> (
|
||||
|
|
|
|||
|
|
@ -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.
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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}__
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
})
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ export default defineConfig({
|
|||
"AgentExecutor.sol/**",
|
||||
"Gateway.sol/**",
|
||||
"TransparentUpgradeableProxy.sol/**",
|
||||
"RewardsRegistry.sol/**",
|
||||
"Agent.sol/**",
|
||||
"StrategyManager.sol/**",
|
||||
"AVSDirectory.sol/**",
|
||||
|
|
|
|||
Loading…
Reference in a new issue