datahaven/contracts/test/RewardsSubmitter.t.sol
Ahmad Kaouk edcb13dbbc
fix: add era replay guard for rewards submissions (#477)
## Summary
- guard `DataHavenServiceManager.submitRewards` by `(startTimestamp,
duration, token)` so each reward window can only be submitted once per
token
- expose the replay-guard state and error in the interface, add Foundry
coverage, wire the missing runtime `std` features, and regenerate the
Wagmi/storage/state-diff artifacts
- fix the local slash E2E path by aligning the `anvil` Snowbridge
`messageOrigin` with `stagenet-local`, refreshing the tracked anvil
deployment metadata, and waiting for `ServiceManager.SlashingComplete`

## Testing
- `cargo fmt --all -- --check`
- `forge test --match-contract RewardsSubmitterTest`
- `forge test --match-contract StorageLayoutTest -vvv`
- `./scripts/check-storage-layout.sh`
- `./scripts/check-storage-layout-negative.sh`
- `bun ./scripts/check-generated-state.ts`
- `bun generate:wagmi`
- `bun test ./e2e/suites/slash.test.ts --timeout 1200000
--test-name-pattern "verify we have the agent origin set|Activate
slashing|use sudo to slash operator"`

## Notes
- Slash E2E verification reran the previously failing sudo slash path;
the long liveness scenario was not rerun end to end.
2026-04-17 14:27:09 +02:00

655 lines
28 KiB
Solidity

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
/* solhint-disable func-name-mixedcase */
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {
IRewardsCoordinator,
IRewardsCoordinatorTypes
} from "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol";
import {
IAllocationManagerTypes
} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol";
import {StdStorage, stdStorage} from "forge-std/StdStorage.sol";
import {AVSDeployer} from "./utils/AVSDeployer.sol";
import {ERC20FixedSupply} from "./utils/ERC20FixedSupply.sol";
import {IDataHavenServiceManagerEvents} from "../src/interfaces/IDataHavenServiceManager.sol";
contract RewardsSubmitterTest is AVSDeployer {
using SafeERC20 for IERC20;
using stdStorage for StdStorage;
// Test addresses
address public snowbridgeAgent = address(uint160(uint256(keccak256("snowbridgeAgent"))));
address public operator1 = address(uint160(uint256(keccak256("operator1"))));
address public operator2 = address(uint160(uint256(keccak256("operator2"))));
// Test token
ERC20FixedSupply public rewardToken;
// Constants aligned with test AVSDeployer's RewardsCoordinator setup (7 days)
uint32 public constant TEST_CALCULATION_INTERVAL = 7 days;
function setUp() public virtual {
_deployMockEigenLayerAndAVS();
// Deploy reward token
rewardToken = new ERC20FixedSupply("DataHaven", "HAVE", 1000000e18, address(this));
// Configure the rewards initiator
vm.prank(avsOwner);
serviceManager.setSnowbridgeInitiator(snowbridgeAgent);
// Fund the service manager with reward tokens
IERC20(address(rewardToken)).safeTransfer(address(serviceManager), 100000e18);
}
function _registerOperator(
address ethOperator,
address solochainOperator
) internal {
// Allow our operator to register
vm.prank(avsOwner);
serviceManager.addValidatorToAllowlist(ethOperator);
vm.prank(ethOperator);
delegationManager.registerAsOperator(address(0), 0, "");
uint32[] memory operatorSetIds = new uint32[](1);
operatorSetIds[0] = serviceManager.VALIDATORS_SET_ID();
IAllocationManagerTypes.RegisterParams memory registerParams =
IAllocationManagerTypes.RegisterParams({
avs: address(serviceManager),
operatorSetIds: operatorSetIds,
data: abi.encodePacked(solochainOperator)
});
vm.prank(ethOperator);
allocationManager.registerForOperatorSets(ethOperator, registerParams);
}
// Helper function to build a submission
function _buildSubmission(
uint256 rewardAmount,
address operator
) internal view returns (IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory) {
IRewardsCoordinatorTypes.StrategyAndMultiplier[] memory strategiesAndMultipliers =
new IRewardsCoordinatorTypes.StrategyAndMultiplier[](deployedStrategies.length);
for (uint256 i = 0; i < deployedStrategies.length; i++) {
strategiesAndMultipliers[i] = IRewardsCoordinatorTypes.StrategyAndMultiplier({
strategy: deployedStrategies[i], multiplier: uint96((i + 1) * 1e18)
});
}
IRewardsCoordinatorTypes.OperatorReward[] memory operatorRewards =
new IRewardsCoordinatorTypes.OperatorReward[](1);
operatorRewards[0] =
IRewardsCoordinatorTypes.OperatorReward({operator: operator, amount: rewardAmount});
return IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission({
strategiesAndMultipliers: strategiesAndMultipliers,
token: IERC20(address(rewardToken)),
operatorRewards: operatorRewards,
startTimestamp: GENESIS_REWARDS_TIMESTAMP,
duration: TEST_CALCULATION_INTERVAL,
description: "DataHaven rewards"
});
}
// ============ Configuration Tests ============
function test_setSnowbridgeInitiator() public {
address newInitiator = address(0x123);
vm.prank(avsOwner);
vm.expectEmit(true, true, false, false);
emit IDataHavenServiceManagerEvents.SnowbridgeInitiatorSet(snowbridgeAgent, newInitiator);
serviceManager.setSnowbridgeInitiator(newInitiator);
assertEq(serviceManager.snowbridgeInitiator(), newInitiator);
}
function test_setSnowbridgeInitiator_revertsIfNotOwner() public {
vm.prank(operator1);
vm.expectRevert(bytes("Ownable: caller is not the owner"));
serviceManager.setSnowbridgeInitiator(address(0x123));
}
// ============ Access Control Tests ============
function test_submitRewards_revertsIfNotRewardsInitiator() public {
_registerOperator(operator1, operator1);
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory submission =
_buildSubmission(1000e18, operator1);
vm.prank(operator1);
vm.expectRevert(abi.encodeWithSignature("OnlyRewardsInitiator()"));
serviceManager.submitRewards(submission);
}
// ============ Success Tests ============
function test_submitRewards_singleOperator() public {
_registerOperator(operator1, operator1);
uint256 rewardAmount = 1000e18;
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory submission =
_buildSubmission(rewardAmount, operator1);
// Warp to a time after the period ends
vm.warp(submission.startTimestamp + submission.duration + 1);
vm.prank(snowbridgeAgent);
vm.expectEmit(false, false, false, true);
emit IDataHavenServiceManagerEvents.RewardsSubmitted(rewardAmount, 1);
serviceManager.submitRewards(submission);
}
function test_submitRewards_multipleOperators() public {
_registerOperator(operator1, operator1);
_registerOperator(operator2, operator2);
// Build strategies
IRewardsCoordinatorTypes.StrategyAndMultiplier[] memory strategiesAndMultipliers =
new IRewardsCoordinatorTypes.StrategyAndMultiplier[](deployedStrategies.length);
for (uint256 i = 0; i < deployedStrategies.length; i++) {
strategiesAndMultipliers[i] = IRewardsCoordinatorTypes.StrategyAndMultiplier({
strategy: deployedStrategies[i], multiplier: uint96((i + 1) * 1e18)
});
}
// Ensure operators are sorted in ascending order (required by EigenLayer)
(address opLow, address opHigh) =
operator1 < operator2 ? (operator1, operator2) : (operator2, operator1);
uint256 amount1 = 600e18;
uint256 amount2 = 400e18;
uint256 totalAmount = amount1 + amount2;
IRewardsCoordinatorTypes.OperatorReward[] memory operatorRewards =
new IRewardsCoordinatorTypes.OperatorReward[](2);
operatorRewards[0] =
IRewardsCoordinatorTypes.OperatorReward({operator: opLow, amount: amount1});
operatorRewards[1] =
IRewardsCoordinatorTypes.OperatorReward({operator: opHigh, amount: amount2});
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory submission =
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission({
strategiesAndMultipliers: strategiesAndMultipliers,
token: IERC20(address(rewardToken)),
operatorRewards: operatorRewards,
startTimestamp: GENESIS_REWARDS_TIMESTAMP,
duration: TEST_CALCULATION_INTERVAL,
description: "DataHaven rewards"
});
// Warp to a time after the period ends
vm.warp(submission.startTimestamp + submission.duration + 1);
vm.prank(snowbridgeAgent);
vm.expectEmit(false, false, false, true);
emit IDataHavenServiceManagerEvents.RewardsSubmitted(totalAmount, 2);
serviceManager.submitRewards(submission);
}
function test_submitRewards_multipleSubmissions() public {
_registerOperator(operator1, operator1);
uint32 duration = TEST_CALCULATION_INTERVAL;
// Submit for period 0
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory submission0 =
_buildSubmission(1000e18, operator1);
submission0.startTimestamp = GENESIS_REWARDS_TIMESTAMP;
vm.warp(submission0.startTimestamp + duration + 1);
vm.prank(snowbridgeAgent);
serviceManager.submitRewards(submission0);
// Submit for period 1
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory submission1 =
_buildSubmission(1000e18, operator1);
submission1.startTimestamp = GENESIS_REWARDS_TIMESTAMP + duration;
vm.warp(submission1.startTimestamp + duration + 1);
vm.prank(snowbridgeAgent);
serviceManager.submitRewards(submission1);
// Submit for period 2
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory submission2 =
_buildSubmission(1000e18, operator1);
submission2.startTimestamp = GENESIS_REWARDS_TIMESTAMP + 2 * duration;
vm.warp(submission2.startTimestamp + duration + 1);
vm.prank(snowbridgeAgent);
serviceManager.submitRewards(submission2);
}
function test_submitRewards_revertsIfWindowAlreadySubmittedForToken() public {
_registerOperator(operator1, operator1);
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory submission =
_buildSubmission(1000e18, operator1);
vm.warp(submission.startTimestamp + submission.duration + 1);
vm.prank(snowbridgeAgent);
serviceManager.submitRewards(submission);
assertTrue(
serviceManager.rewardsSubmittedForWindow(
submission.startTimestamp, submission.duration, address(rewardToken)
),
"replay guard should be set for the submitted window and token"
);
vm.prank(snowbridgeAgent);
vm.expectRevert(
abi.encodeWithSignature(
"RewardsAlreadySubmittedForWindow(uint32,uint32,address)",
submission.startTimestamp,
submission.duration,
address(rewardToken)
)
);
serviceManager.submitRewards(submission);
}
function test_submitRewards_allowsDifferentDurationForSameStartAndToken() public {
_registerOperator(operator1, operator1);
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory firstSubmission =
_buildSubmission(1000e18, operator1);
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory secondSubmission =
_buildSubmission(500e18, operator1);
secondSubmission.duration = 2 * TEST_CALCULATION_INTERVAL;
vm.warp(secondSubmission.startTimestamp + secondSubmission.duration + 1);
vm.prank(snowbridgeAgent);
serviceManager.submitRewards(firstSubmission);
vm.prank(snowbridgeAgent);
serviceManager.submitRewards(secondSubmission);
assertTrue(
serviceManager.rewardsSubmittedForWindow(
firstSubmission.startTimestamp, firstSubmission.duration, address(rewardToken)
),
"first window should be tracked independently"
);
assertTrue(
serviceManager.rewardsSubmittedForWindow(
secondSubmission.startTimestamp, secondSubmission.duration, address(rewardToken)
),
"second window should be tracked independently"
);
}
function test_submitRewards_withCustomDescription() public {
_registerOperator(operator1, operator1);
// Build submission with custom description
IRewardsCoordinatorTypes.StrategyAndMultiplier[] memory strategiesAndMultipliers =
new IRewardsCoordinatorTypes.StrategyAndMultiplier[](1);
strategiesAndMultipliers[0] = IRewardsCoordinatorTypes.StrategyAndMultiplier({
strategy: deployedStrategies[0], multiplier: 1e18
});
IRewardsCoordinatorTypes.OperatorReward[] memory operatorRewards =
new IRewardsCoordinatorTypes.OperatorReward[](1);
operatorRewards[0] =
IRewardsCoordinatorTypes.OperatorReward({operator: operator1, amount: 1000e18});
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory submission =
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission({
strategiesAndMultipliers: strategiesAndMultipliers,
token: IERC20(address(rewardToken)),
operatorRewards: operatorRewards,
startTimestamp: GENESIS_REWARDS_TIMESTAMP,
duration: TEST_CALCULATION_INTERVAL,
description: "Era 42 validator rewards"
});
vm.warp(submission.startTimestamp + submission.duration + 1);
vm.prank(snowbridgeAgent);
serviceManager.submitRewards(submission);
}
function test_submitRewards_withDifferentToken() public {
_registerOperator(operator1, operator1);
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory firstSubmission =
_buildSubmission(1000e18, operator1);
// Deploy a different token
ERC20FixedSupply otherToken =
new ERC20FixedSupply("Other", "OTHER", 1000000e18, address(this));
IERC20(address(otherToken)).safeTransfer(address(serviceManager), 100000e18);
// Build submission with different token
IRewardsCoordinatorTypes.StrategyAndMultiplier[] memory strategiesAndMultipliers =
new IRewardsCoordinatorTypes.StrategyAndMultiplier[](1);
strategiesAndMultipliers[0] = IRewardsCoordinatorTypes.StrategyAndMultiplier({
strategy: deployedStrategies[0], multiplier: 1e18
});
IRewardsCoordinatorTypes.OperatorReward[] memory operatorRewards =
new IRewardsCoordinatorTypes.OperatorReward[](1);
operatorRewards[0] =
IRewardsCoordinatorTypes.OperatorReward({operator: operator1, amount: 500e18});
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory submission =
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission({
strategiesAndMultipliers: strategiesAndMultipliers,
token: IERC20(address(otherToken)),
operatorRewards: operatorRewards,
startTimestamp: GENESIS_REWARDS_TIMESTAMP,
duration: TEST_CALCULATION_INTERVAL,
description: "Bonus rewards in OTHER token"
});
vm.warp(submission.startTimestamp + submission.duration + 1);
vm.prank(snowbridgeAgent);
serviceManager.submitRewards(firstSubmission);
vm.prank(snowbridgeAgent);
vm.expectEmit(false, false, false, true);
emit IDataHavenServiceManagerEvents.RewardsSubmitted(500e18, 1);
serviceManager.submitRewards(submission);
assertTrue(
serviceManager.rewardsSubmittedForWindow(
submission.startTimestamp, submission.duration, address(rewardToken)
),
"original token should be marked as submitted for the window"
);
assertTrue(
serviceManager.rewardsSubmittedForWindow(
submission.startTimestamp, submission.duration, address(otherToken)
),
"different token should be independently tracked for the same window"
);
}
function test_submitRewards_translatesSolochainOperatorToEthOperator() public {
address solochainOperator = address(0xBEEF);
_registerOperator(operator1, solochainOperator);
assertEq(
serviceManager.validatorEthAddressToSolochainAddress(operator1),
solochainOperator,
"forward mapping should be set"
);
assertEq(
serviceManager.validatorSolochainAddressToEthAddress(solochainOperator),
operator1,
"reverse mapping should be set"
);
uint256 rewardAmount = 1000e18;
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory submission =
_buildSubmission(rewardAmount, solochainOperator);
// Warp to a time after the period ends
vm.warp(submission.startTimestamp + submission.duration + 1);
IRewardsCoordinatorTypes.OperatorReward[] memory expectedOperatorRewards =
new IRewardsCoordinatorTypes.OperatorReward[](1);
expectedOperatorRewards[0] =
IRewardsCoordinatorTypes.OperatorReward({operator: operator1, amount: rewardAmount});
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory expectedSubmission =
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission({
strategiesAndMultipliers: submission.strategiesAndMultipliers,
token: submission.token,
operatorRewards: expectedOperatorRewards,
startTimestamp: submission.startTimestamp,
duration: submission.duration,
description: submission.description
});
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission[] memory submissions =
new IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission[](1);
submissions[0] = expectedSubmission;
OperatorSet memory operatorSet =
OperatorSet({avs: address(serviceManager), id: serviceManager.VALIDATORS_SET_ID()});
vm.expectCall(
address(rewardsCoordinator),
abi.encodeCall(
IRewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission,
(operatorSet, submissions)
)
);
assertEq(
submission.operatorRewards[0].operator,
solochainOperator,
"submission should use solochain operator"
);
vm.prank(snowbridgeAgent);
serviceManager.submitRewards(submission);
}
function test_submitRewards_skipsUnknownSolochainAddress() public {
address unknownSolochainOperator = address(0xDEAD);
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory submission =
_buildSubmission(1000e18, unknownSolochainOperator);
// Unknown solochain address is silently skipped; RewardsSubmitted is emitted with zero amount and zero operator count
vm.prank(snowbridgeAgent);
vm.expectEmit();
emit IDataHavenServiceManagerEvents.RewardsSubmitted(0, 0);
serviceManager.submitRewards(submission);
}
function test_submitRewards_afterAllowlistRemovalStillTranslatesDuringDeallocationDelay()
public
{
address solochainOperator = address(0xBEEF);
_registerOperator(operator1, solochainOperator);
vm.prank(avsOwner);
serviceManager.removeValidatorFromAllowlist(operator1);
uint256 rewardAmount = 1000e18;
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory submission =
_buildSubmission(rewardAmount, solochainOperator);
vm.warp(submission.startTimestamp + submission.duration + 1);
IRewardsCoordinatorTypes.OperatorReward[] memory expectedOperatorRewards =
new IRewardsCoordinatorTypes.OperatorReward[](1);
expectedOperatorRewards[0] =
IRewardsCoordinatorTypes.OperatorReward({operator: operator1, amount: rewardAmount});
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory expectedSubmission =
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission({
strategiesAndMultipliers: submission.strategiesAndMultipliers,
token: submission.token,
operatorRewards: expectedOperatorRewards,
startTimestamp: submission.startTimestamp,
duration: submission.duration,
description: submission.description
});
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission[] memory submissions =
new IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission[](1);
submissions[0] = expectedSubmission;
OperatorSet memory operatorSet =
OperatorSet({avs: address(serviceManager), id: serviceManager.VALIDATORS_SET_ID()});
vm.expectCall(
address(rewardsCoordinator),
abi.encodeCall(
IRewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission,
(operatorSet, submissions)
)
);
vm.prank(snowbridgeAgent);
serviceManager.submitRewards(submission);
}
function test_submitRewards_mergesDuplicateTranslatedOperators() public {
// Register operator1 with solochain1
address solochain1 = address(0xAA01);
address solochain2 = address(0xAA02);
_registerOperator(operator1, solochain1);
// Simulate the scenario where solochain2 also maps to operator1
// (e.g. operator deregistered and re-registered with a new solochain address
// within the same reward window, so the pallet accumulated points under both)
stdstore.target(address(serviceManager))
.sig("validatorSolochainAddressToEthAddress(address)").with_key(solochain2)
.checked_write(operator1);
// Build submission with two entries for different solochain addresses
// that both map to the same ETH operator
uint256 amount1 = 600e18;
uint256 amount2 = 400e18;
uint256 totalAmount = amount1 + amount2;
IRewardsCoordinatorTypes.StrategyAndMultiplier[] memory strategiesAndMultipliers =
new IRewardsCoordinatorTypes.StrategyAndMultiplier[](deployedStrategies.length);
for (uint256 i = 0; i < deployedStrategies.length; i++) {
strategiesAndMultipliers[i] = IRewardsCoordinatorTypes.StrategyAndMultiplier({
strategy: deployedStrategies[i], multiplier: uint96((i + 1) * 1e18)
});
}
IRewardsCoordinatorTypes.OperatorReward[] memory operatorRewards =
new IRewardsCoordinatorTypes.OperatorReward[](2);
operatorRewards[0] =
IRewardsCoordinatorTypes.OperatorReward({operator: solochain1, amount: amount1});
operatorRewards[1] =
IRewardsCoordinatorTypes.OperatorReward({operator: solochain2, amount: amount2});
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory submission =
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission({
strategiesAndMultipliers: strategiesAndMultipliers,
token: IERC20(address(rewardToken)),
operatorRewards: operatorRewards,
startTimestamp: GENESIS_REWARDS_TIMESTAMP,
duration: TEST_CALCULATION_INTERVAL,
description: "DataHaven rewards"
});
vm.warp(submission.startTimestamp + submission.duration + 1);
// Expect the RewardsCoordinator to receive a single merged entry
IRewardsCoordinatorTypes.OperatorReward[] memory expectedOperatorRewards =
new IRewardsCoordinatorTypes.OperatorReward[](1);
expectedOperatorRewards[0] =
IRewardsCoordinatorTypes.OperatorReward({operator: operator1, amount: totalAmount});
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory expectedSubmission =
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission({
strategiesAndMultipliers: strategiesAndMultipliers,
token: submission.token,
operatorRewards: expectedOperatorRewards,
startTimestamp: submission.startTimestamp,
duration: submission.duration,
description: submission.description
});
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission[] memory submissions =
new IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission[](1);
submissions[0] = expectedSubmission;
OperatorSet memory operatorSet =
OperatorSet({avs: address(serviceManager), id: serviceManager.VALIDATORS_SET_ID()});
vm.expectCall(
address(rewardsCoordinator),
abi.encodeCall(
IRewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission,
(operatorSet, submissions)
)
);
// Event should emit with total amount and ORIGINAL operator count (2)
vm.prank(snowbridgeAgent);
vm.expectEmit(false, false, false, true);
emit IDataHavenServiceManagerEvents.RewardsSubmitted(totalAmount, 2);
serviceManager.submitRewards(submission);
}
function test_submitRewards_sortsTranslatedOperatorsByAddress() public {
(address ethLow, address ethHigh) =
operator1 < operator2 ? (operator1, operator2) : (operator2, operator1);
address solochainLow = address(0x1000);
address solochainHigh = address(0x2000);
_registerOperator(ethLow, solochainHigh);
_registerOperator(ethHigh, solochainLow);
IRewardsCoordinatorTypes.StrategyAndMultiplier[] memory strategiesAndMultipliers =
new IRewardsCoordinatorTypes.StrategyAndMultiplier[](deployedStrategies.length);
for (uint256 i = 0; i < deployedStrategies.length; i++) {
strategiesAndMultipliers[i] = IRewardsCoordinatorTypes.StrategyAndMultiplier({
strategy: deployedStrategies[i], multiplier: uint96((i + 1) * 1e18)
});
}
uint256 amountForEthLow = 600e18;
uint256 amountForEthHigh = 400e18;
uint256 totalAmount = amountForEthLow + amountForEthHigh;
IRewardsCoordinatorTypes.OperatorReward[] memory inputOperatorRewards =
new IRewardsCoordinatorTypes.OperatorReward[](2);
inputOperatorRewards[0] = IRewardsCoordinatorTypes.OperatorReward({
operator: solochainLow, amount: amountForEthHigh
});
inputOperatorRewards[1] = IRewardsCoordinatorTypes.OperatorReward({
operator: solochainHigh, amount: amountForEthLow
});
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory submission =
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission({
strategiesAndMultipliers: strategiesAndMultipliers,
token: IERC20(address(rewardToken)),
operatorRewards: inputOperatorRewards,
startTimestamp: GENESIS_REWARDS_TIMESTAMP,
duration: TEST_CALCULATION_INTERVAL,
description: "DataHaven rewards"
});
vm.warp(submission.startTimestamp + submission.duration + 1);
IRewardsCoordinatorTypes.OperatorReward[] memory expectedOperatorRewards =
new IRewardsCoordinatorTypes.OperatorReward[](2);
expectedOperatorRewards[0] =
IRewardsCoordinatorTypes.OperatorReward({operator: ethLow, amount: amountForEthLow});
expectedOperatorRewards[1] =
IRewardsCoordinatorTypes.OperatorReward({operator: ethHigh, amount: amountForEthHigh});
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory expectedSubmission =
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission({
strategiesAndMultipliers: strategiesAndMultipliers,
token: submission.token,
operatorRewards: expectedOperatorRewards,
startTimestamp: submission.startTimestamp,
duration: submission.duration,
description: submission.description
});
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission[] memory submissions =
new IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission[](1);
submissions[0] = expectedSubmission;
OperatorSet memory operatorSet =
OperatorSet({avs: address(serviceManager), id: serviceManager.VALIDATORS_SET_ID()});
vm.expectCall(
address(rewardsCoordinator),
abi.encodeCall(
IRewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission,
(operatorSet, submissions)
)
);
vm.prank(snowbridgeAgent);
vm.expectEmit(false, false, false, true);
emit IDataHavenServiceManagerEvents.RewardsSubmitted(totalAmount, 2);
serviceManager.submitRewards(submission);
}
}