mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 01:38:32 +00:00
test: fix rewards e2e test (#385)
This commit is contained in:
parent
916261ee8b
commit
e93bbb4832
12 changed files with 765 additions and 975 deletions
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 ═══════════════════════╝
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 ═══════════════════════╝
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 ═══════════════════════╝
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
171
test/suites/rewards-message.test.ts
Normal file
171
test/suites/rewards-message.test.ts
Normal 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`
|
||||
);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue