test: fix rewards e2e test (#385)

This commit is contained in:
Ahmad Kaouk 2026-01-12 16:55:46 +01:00 committed by GitHub
parent 916261ee8b
commit e93bbb4832
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 765 additions and 975 deletions

View file

@ -1 +1,26 @@
{"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"}]}
{
"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"
}
]
}

File diff suppressed because one or more lines are too long

View file

@ -29,6 +29,7 @@ import {
ERC20PresetFixedSupply
} from "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import {
ITransparentUpgradeableProxy
@ -73,6 +74,8 @@ import {DataHavenServiceManager} from "../../src/DataHavenServiceManager.sol";
* @notice Deployment script for local development (anvil) - deploys full EigenLayer infrastructure
*/
contract DeployLocal is DeployBase {
using SafeERC20 for IERC20;
// Local-specific EigenLayer Contract declarations
EmptyContract public emptyContract;
RewardsCoordinator public rewardsCoordinatorImplementation;
@ -94,6 +97,40 @@ contract DeployLocal is DeployBase {
function run() public {
totalSteps = 4; // Total major deployment steps for local
_executeSharedDeployment();
// Fund ServiceManager with tokens for rewards distribution (local only)
_fundServiceManagerWithTokens();
}
/**
* @notice Fund the ServiceManager with tokens for rewards distribution
* @dev Only for local deployments - transfers tokens from operator to ServiceManager
*/
function _fundServiceManagerWithTokens() internal {
if (deployedStrategies.length == 0) {
Logging.logInfo("No strategies deployed, skipping ServiceManager funding");
return;
}
// Read ServiceManager address from deployments file
string memory network = _getNetworkName();
string memory deploymentPath =
string.concat(vm.projectRoot(), "/deployments/", network, ".json");
string memory json = vm.readFile(deploymentPath);
address serviceManager = vm.parseJsonAddress(json, ".ServiceManager");
// Get token address from deployed strategies
address tokenAddress = deployedStrategies[0].underlyingToken;
// Transfer 500,000 tokens (half of minted supply) to ServiceManager
uint256 fundAmount = 500000 * 1e18;
Logging.logSection("Funding ServiceManager with Reward Tokens");
vm.broadcast(_operatorPrivateKey);
IERC20(tokenAddress).safeTransfer(serviceManager, fundAmount);
Logging.logStep(
string.concat("Transferred ", vm.toString(fundAmount), " tokens to ServiceManager")
);
}
// Implementation of abstract functions from DeployBase

View file

@ -1482,6 +1482,13 @@ impl datahaven_runtime_common::rewards_adapter::RewardsSubmissionConfig for Main
runtime_params::dynamic_params::runtime_config::RewardsAgentOrigin::get()
}
fn strategies_and_multipliers() -> Vec<(H160, u128)> {
runtime_params::dynamic_params::runtime_config::RewardsStrategiesAndMultipliers::get()
.into_iter()
.filter(|(s, _)| *s != H160::zero())
.collect()
}
fn handle_remainder(remainder: u128) {
use frame_support::traits::{fungible::Mutate, tokens::Preservation};
let source = ExternalValidatorRewardsAccount::get();

View file

@ -410,6 +410,13 @@ pub mod dynamic_params {
/// Rewards duration in seconds. Fixed at 86400 (1 day) for EigenLayer.
pub static RewardsDuration: u32 = 86400;
#[codec(index = 45)]
#[allow(non_upper_case_globals)]
/// Strategy addresses and their multipliers for EigenLayer rewards (max 10).
/// Each entry is (strategy_address, multiplier).
pub static RewardsStrategiesAndMultipliers: BoundedVec<(H160, u128), ConstU32<10>> =
BoundedVec::truncate_from(vec![]);
// ╚══════════════════════ EigenLayer Rewards V2 ═══════════════════════╝
}
}

View file

@ -1478,6 +1478,13 @@ impl datahaven_runtime_common::rewards_adapter::RewardsSubmissionConfig for Stag
runtime_params::dynamic_params::runtime_config::RewardsAgentOrigin::get()
}
fn strategies_and_multipliers() -> Vec<(H160, u128)> {
runtime_params::dynamic_params::runtime_config::RewardsStrategiesAndMultipliers::get()
.into_iter()
.filter(|(s, _)| *s != H160::zero())
.collect()
}
fn handle_remainder(remainder: u128) {
use frame_support::traits::{fungible::Mutate, tokens::Preservation};
let source = ExternalValidatorRewardsAccount::get();

View file

@ -413,6 +413,13 @@ pub mod dynamic_params {
/// Rewards duration in seconds. Fixed at 86400 (1 day) for EigenLayer.
pub static RewardsDuration: u32 = 86400;
#[codec(index = 45)]
#[allow(non_upper_case_globals)]
/// Strategy addresses and their multipliers for EigenLayer rewards (max 10).
/// Each entry is (strategy_address, multiplier).
pub static RewardsStrategiesAndMultipliers: BoundedVec<(H160, u128), ConstU32<10>> =
BoundedVec::truncate_from(vec![]);
// ╚══════════════════════ EigenLayer Rewards V2 ═══════════════════════╝
}
}

View file

@ -1482,6 +1482,13 @@ impl datahaven_runtime_common::rewards_adapter::RewardsSubmissionConfig for Test
runtime_params::dynamic_params::runtime_config::RewardsAgentOrigin::get()
}
fn strategies_and_multipliers() -> Vec<(H160, u128)> {
runtime_params::dynamic_params::runtime_config::RewardsStrategiesAndMultipliers::get()
.into_iter()
.filter(|(s, _)| *s != H160::zero())
.collect()
}
fn handle_remainder(remainder: u128) {
use frame_support::traits::{fungible::Mutate, tokens::Preservation};
let source = ExternalValidatorRewardsAccount::get();

View file

@ -408,6 +408,13 @@ pub mod dynamic_params {
/// Rewards duration in seconds.
pub static RewardsDuration: u32 = 86400;
#[codec(index = 45)]
#[allow(non_upper_case_globals)]
/// Strategy addresses and their multipliers for EigenLayer rewards (max 10).
/// Each entry is (strategy_address, multiplier).
pub static RewardsStrategiesAndMultipliers: BoundedVec<(H160, u128), ConstU32<10>> =
BoundedVec::truncate_from(vec![]);
// ╚══════════════════════ EigenLayer Rewards V2 ═══════════════════════╝
}
}

View file

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

Binary file not shown.

View file

@ -0,0 +1,171 @@
import { beforeAll, describe, expect, it } from "bun:test";
import { FixedSizeBinary } from "polkadot-api";
import { CROSS_CHAIN_TIMEOUTS, getEvmEcdsaSigner, logger, SUBSTRATE_FUNDED_ACCOUNTS } from "utils";
import type { Address } from "viem";
import { BaseTestSuite } from "../framework";
import { getContractInstance, parseDeploymentsFile } from "../utils/contracts";
import { waitForDataHavenEvent } from "../utils/events";
/**
* Temporary helper to set V2 rewards parameters via sudo.
* This is needed until the launcher properly configures these parameters.
*/
async function setV2RewardsParameters(dhApi: any) {
const signer = getEvmEcdsaSigner(SUBSTRATE_FUNDED_ACCOUNTS.ALITH.privateKey);
// Get addresses from deployments
const deployments = await parseDeploymentsFile();
const whaveTokenAddress =
deployments.DeployedStrategies?.[0]?.underlyingToken ??
"0x95401dc811bb5740090279Ba06cfA8fcF6113778";
const strategyAddress =
deployments.DeployedStrategies?.[0]?.address ?? "0x0000000000000000000000000000000000000000";
const serviceManagerAddress = deployments.ServiceManager;
// Set RewardsGenesisTimestamp to 1 day ago (aligned to day boundary) to ensure valid rewards periods
const genesisTimestamp = Math.floor((Date.now() / 1000 - 86400) / 86400) * 86400;
logger.debug(
"Setting V2 rewards parameters:\n" +
` WHAVETokenAddress=${whaveTokenAddress}\n` +
` StrategyAddress=${strategyAddress}\n` +
` ServiceManagerAddress=${serviceManagerAddress}\n` +
` RewardsGenesisTimestamp=${genesisTimestamp}`
);
// Build sudo calls to set parameters
const calls = [
// Set ServiceManager address (required for rewards submission)
dhApi.tx.Parameters.set_parameter({
key_value: {
type: "RuntimeConfig",
value: {
type: "DatahavenServiceManagerAddress",
value: [new FixedSizeBinary(Buffer.from(serviceManagerAddress.slice(2), "hex"))]
}
}
}).decodedCall,
dhApi.tx.Parameters.set_parameter({
key_value: {
type: "RuntimeConfig",
value: {
type: "WHAVETokenAddress",
value: [new FixedSizeBinary(Buffer.from(whaveTokenAddress.slice(2), "hex"))]
}
}
}).decodedCall,
dhApi.tx.Parameters.set_parameter({
key_value: {
type: "RuntimeConfig",
value: {
type: "RewardsGenesisTimestamp",
value: [genesisTimestamp]
}
}
}).decodedCall,
// Set strategies and multipliers: [(strategy_address, multiplier)]
dhApi.tx.Parameters.set_parameter({
key_value: {
type: "RuntimeConfig",
value: {
type: "RewardsStrategiesAndMultipliers",
value: [[[new FixedSizeBinary(Buffer.from(strategyAddress.slice(2), "hex")), 1n]]]
}
}
}).decodedCall
];
const tx = dhApi.tx.Sudo.sudo({
call: dhApi.tx.Utility.batch_all({ calls }).decodedCall
});
const result = await tx.signAndSubmit(signer);
if (!result.ok) {
throw new Error("Failed to set V2 rewards parameters");
}
logger.debug("V2 rewards parameters set successfully");
}
class RewardsMessageTestSuite extends BaseTestSuite {
constructor() {
super({
suiteName: "rewards-message"
});
this.setupHooks();
}
}
const suite = new RewardsMessageTestSuite();
describe("Rewards Message Flow", () => {
let serviceManager!: any;
let publicClient!: any;
let dhApi!: any;
let eraIndex!: number;
let totalPoints!: bigint;
beforeAll(async () => {
const connectors = suite.getTestConnectors();
publicClient = connectors.publicClient;
dhApi = connectors.dhApi;
serviceManager = await getContractInstance("ServiceManager");
// Set V2 rewards parameters (temporary until launcher configures them)
await setV2RewardsParameters(dhApi);
});
it("should verify rewards infrastructure deployment", async () => {
const gateway = await getContractInstance("Gateway");
expect(serviceManager.address).toBeDefined();
expect(gateway.address).toBeDefined();
const rewardsInitiator = (await publicClient.readContract({
address: serviceManager.address,
abi: serviceManager.abi,
functionName: "rewardsInitiator",
args: []
})) as Address;
// ServiceManager must have a rewardsInitiator configured for EigenLayer rewards submission
expect(rewardsInitiator).toBeDefined();
logger.debug(`ServiceManager rewardsInitiator: ${rewardsInitiator}`);
});
it("should wait for era end and emit RewardsMessageSent", async () => {
// Get current era for event filtering
const currentEra = await dhApi.query.ExternalValidators.ActiveEra.getValue();
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();
totalPoints = payload.total_points;
eraIndex = payload.era_index;
expect(totalPoints).toBeGreaterThan(0n);
logger.debug(`RewardsMessageSent received: era=${eraIndex}, totalPoints=${totalPoints}`);
});
it("should verify reward points were recorded for the era", async () => {
// Verify reward points were recorded on DataHaven side
const rewardPoints =
await dhApi.query.ExternalValidatorsRewards.RewardPointsForEra.getValue(eraIndex);
expect(rewardPoints).toBeDefined();
expect(rewardPoints.total).toBeGreaterThan(0);
const validatorsWithPoints = rewardPoints.individual.length;
logger.debug(
`Era ${eraIndex}: ${validatorsWithPoints} validators earned ${rewardPoints.total} total points`
);
});
});