datahaven/contracts/test/Slashing.t.sol

167 lines
6.6 KiB
Solidity
Raw Permalink Normal View History

feat : Slashing integration in EigenLayer and Datahaven AVS (#345) ## Summary This PR integrate the slashing feature with EigenLayer. With this PR, slashing can now be relayed to our Datahaven AVS and then executed within EigenLayer. In addition some refactoring of the original slashing pallet has been done. ## Motivation To avoid misbehaving actor in the network, Datahaven has implemented a slashing pallet in which offenses can be reported and then if adequate can lead to a sanction on the misbehaving node. It incentive nodes to only follow good behavior in addition to the reward incentive. The rewards flow is managed directly into EigenLayer (see https://github.com/datahaven-xyz/datahaven/pull/351). ## Slashing flow <img width="2355" height="946" alt="Slashing Flow" src="https://github.com/user-attachments/assets/c1ddc3dc-2a7e-429d-94e0-1e02a3f65246" /> ## What changes * Implemented `slashValidatorsOperator` in `DataHavenServiceManager`. It received all the slashing requests batched (every new era the queued slashing are being relayed from substrate to Ethereum). It handle the slashing of the operators reported into the Validator set. * Added a `slashes_adapter.rs` utility file to remove the duplication for each runtime. In addition, we made use of the `sol!` macro from alloy to encode the calldata for the Ethereum call. This avoid rewriting encoding logic and allow to remove the hardcoded selector value used to call the slashing function. * Added some tests in solidity to test the registering and slashing of an operator in Ethereum via Eigen Layer. * Added e2e tests that test the injection of a slash request, it being relayed via the snowbridge relayer and executed by our Datahaven AVS. ## What could be better * We are only deploying one strategy for now so it is hardcoded in the slashing flow. We should be able to update the pallet in case we are adding a new strategy. So communication from Ethereum should be relayed. * We don't have error being return in case the slashing fail. Which could happen if we don't have the right number of strategy or the validator is not registered... etc. * More tests for the unhappy path
2026-01-16 19:49:45 +00:00
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;
import {AVSDeployer} from "./utils/AVSDeployer.sol";
import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol";
import {
IAllocationManagerErrors,
IAllocationManager,
IAllocationManagerTypes,
IAllocationManagerEvents
} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
import {DataHavenServiceManager} from "../src/DataHavenServiceManager.sol";
import {
IDataHavenServiceManagerEvents,
IDataHavenServiceManager
} from "../src/interfaces/IDataHavenServiceManager.sol";
import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol";
import "forge-std/Test.sol";
contract SlashingTest is AVSDeployer {
address operator = address(0xabcd);
address public snowbridgeAgent = address(uint160(uint256(keccak256("snowbridgeAgent"))));
function setUp() public virtual {
_deployMockEigenLayerAndAVS();
}
function test_fulfilSlashingRequest() public {
fix: 🩹 map validator address to operator address for rewards & slashes (#441) ## Summary Slashing and rewards submissions were submitted through the bridge with their **solochain address** , while EigenLayer expects the **ethereum operator address**, the addresses were not being translated, so the protocol was broken. This PR adds a **reverse mapping** (Solochain address → Eth address) and uses it in both the slashing and rewards paths so that: - `slashValidatorsOperator` accepts requests where `operator` is a Solochain address and translates it to the Eth operator before calling EigenLayer. - `submitRewards` translates each `operatorRewards[].operator` from Solochain to Eth before calling the RewardsCoordinator. - Unknown or unmapped solochain addresses cause a revert (`UnknownSolochainAddress`) instead of silently failing. ## What's changed ### DataHavenServiceManager - **Reverse mapping**: `mapping(address => address) public validatorSolochainAddressToEthAddress` (Solochain → Eth), with `__GAP` reduced by one slot for upgradeable layout. - **Helper**: `_ethOperatorFromSolochain(address)` – returns Eth operator for a Solochain address, reverts with `UnknownSolochainAddress()` if unmapped. - **Registration / lifecycle**: - `registerOperator`: populates both forward and reverse mappings; enforces uniqueness (one Solochain per operator) and clears old reverse entry when an operator re-registers with a new Solochain. - `deregisterOperator`: clears both forward and reverse entries. - `updateSolochainAddressForValidator`: updates both mappings, enforces uniqueness and clears the previous Solochain's reverse entry. - **Slashing**: `slashValidatorsOperator` uses `_ethOperatorFromSolochain(slashings[i].operator)` so requests keyed by Solochain address are translated before calling EigenLayer. - **Rewards**: `submitRewards` builds a translated copy of the submission with each `operatorRewards[].operator` set via `_ethOperatorFromSolochain(...)`; unmapped addresses revert. ### IDataHavenServiceManager - New getter: `validatorSolochainAddressToEthAddress(address solochain) external view returns (address)`. - New errors: `UnknownSolochainAddress()`, `SolochainAddressAlreadyAssigned()`. ### Storage and fixtures - Storage snapshot updated for the new state variable. - `DataHavenServiceManagerBadLayout.sol` updated (reverse mapping + gap) for layout negative tests. - Storage layout test extended to assert the reverse mapping is preserved across proxy upgrade. ### Tests - **Slashing.t.sol**: Slashing with Solochain address (translation and emit of Eth operator); negative test for unmapped Solochain reverting with `UnknownSolochainAddress()`. - **RewardsSubmitter.t.sol**: Rewards submission with Solochain addresses (translation to Eth in RewardsCoordinator calldata); negative test for unmapped Solochain. - **StorageLayout.t.sol**: Reverse mapping preserved after upgrade. - **OperatorAddressMappings.t.sol** (new): Uniqueness (Solochain already assigned to another operator), update/deregister clearing reverse mapping, and getter behaviour. ## Testing - **Unit tests**: `forge test` from `contracts/` (all existing and new tests pass). - **Storage**: - `./scripts/check-storage-layout.sh` - `./scripts/check-storage-layout-negative.sh` - **Coverage**: Slashing path (Solochain → Eth translation + revert), rewards path (translation + revert), registration/update/deregister (reverse mapping and uniqueness), and storage layout upgrade preservation. --------- Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com> Co-authored-by: Ahmad Kaouk <ahmadkaouk.93@gmail.com>
2026-02-18 19:38:13 +00:00
address solochainOperator = address(0xBEEF);
feat : Slashing integration in EigenLayer and Datahaven AVS (#345) ## Summary This PR integrate the slashing feature with EigenLayer. With this PR, slashing can now be relayed to our Datahaven AVS and then executed within EigenLayer. In addition some refactoring of the original slashing pallet has been done. ## Motivation To avoid misbehaving actor in the network, Datahaven has implemented a slashing pallet in which offenses can be reported and then if adequate can lead to a sanction on the misbehaving node. It incentive nodes to only follow good behavior in addition to the reward incentive. The rewards flow is managed directly into EigenLayer (see https://github.com/datahaven-xyz/datahaven/pull/351). ## Slashing flow <img width="2355" height="946" alt="Slashing Flow" src="https://github.com/user-attachments/assets/c1ddc3dc-2a7e-429d-94e0-1e02a3f65246" /> ## What changes * Implemented `slashValidatorsOperator` in `DataHavenServiceManager`. It received all the slashing requests batched (every new era the queued slashing are being relayed from substrate to Ethereum). It handle the slashing of the operators reported into the Validator set. * Added a `slashes_adapter.rs` utility file to remove the duplication for each runtime. In addition, we made use of the `sol!` macro from alloy to encode the calldata for the Ethereum call. This avoid rewriting encoding logic and allow to remove the hardcoded selector value used to call the slashing function. * Added some tests in solidity to test the registering and slashing of an operator in Ethereum via Eigen Layer. * Added e2e tests that test the injection of a slash request, it being relayed via the snowbridge relayer and executed by our Datahaven AVS. ## What could be better * We are only deploying one strategy for now so it is hardcoded in the slashing flow. We should be able to update the pallet in case we are adding a new strategy. So communication from Ethereum should be relayed. * We don't have error being return in case the slashing fail. Which could happen if we don't have the right number of strategy or the validator is not registered... etc. * More tests for the unhappy path
2026-01-16 19:49:45 +00:00
// Allow our operator to register
vm.prank(avsOwner);
serviceManager.addValidatorToAllowlist(operator);
// Configure the rewards initiator (because only the reward agent can submit slashing request)
vm.prank(avsOwner);
serviceManager.setRewardsInitiator(snowbridgeAgent);
vm.prank(operator);
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,
fix: 🩹 map validator address to operator address for rewards & slashes (#441) ## Summary Slashing and rewards submissions were submitted through the bridge with their **solochain address** , while EigenLayer expects the **ethereum operator address**, the addresses were not being translated, so the protocol was broken. This PR adds a **reverse mapping** (Solochain address → Eth address) and uses it in both the slashing and rewards paths so that: - `slashValidatorsOperator` accepts requests where `operator` is a Solochain address and translates it to the Eth operator before calling EigenLayer. - `submitRewards` translates each `operatorRewards[].operator` from Solochain to Eth before calling the RewardsCoordinator. - Unknown or unmapped solochain addresses cause a revert (`UnknownSolochainAddress`) instead of silently failing. ## What's changed ### DataHavenServiceManager - **Reverse mapping**: `mapping(address => address) public validatorSolochainAddressToEthAddress` (Solochain → Eth), with `__GAP` reduced by one slot for upgradeable layout. - **Helper**: `_ethOperatorFromSolochain(address)` – returns Eth operator for a Solochain address, reverts with `UnknownSolochainAddress()` if unmapped. - **Registration / lifecycle**: - `registerOperator`: populates both forward and reverse mappings; enforces uniqueness (one Solochain per operator) and clears old reverse entry when an operator re-registers with a new Solochain. - `deregisterOperator`: clears both forward and reverse entries. - `updateSolochainAddressForValidator`: updates both mappings, enforces uniqueness and clears the previous Solochain's reverse entry. - **Slashing**: `slashValidatorsOperator` uses `_ethOperatorFromSolochain(slashings[i].operator)` so requests keyed by Solochain address are translated before calling EigenLayer. - **Rewards**: `submitRewards` builds a translated copy of the submission with each `operatorRewards[].operator` set via `_ethOperatorFromSolochain(...)`; unmapped addresses revert. ### IDataHavenServiceManager - New getter: `validatorSolochainAddressToEthAddress(address solochain) external view returns (address)`. - New errors: `UnknownSolochainAddress()`, `SolochainAddressAlreadyAssigned()`. ### Storage and fixtures - Storage snapshot updated for the new state variable. - `DataHavenServiceManagerBadLayout.sol` updated (reverse mapping + gap) for layout negative tests. - Storage layout test extended to assert the reverse mapping is preserved across proxy upgrade. ### Tests - **Slashing.t.sol**: Slashing with Solochain address (translation and emit of Eth operator); negative test for unmapped Solochain reverting with `UnknownSolochainAddress()`. - **RewardsSubmitter.t.sol**: Rewards submission with Solochain addresses (translation to Eth in RewardsCoordinator calldata); negative test for unmapped Solochain. - **StorageLayout.t.sol**: Reverse mapping preserved after upgrade. - **OperatorAddressMappings.t.sol** (new): Uniqueness (Solochain already assigned to another operator), update/deregister clearing reverse mapping, and getter behaviour. ## Testing - **Unit tests**: `forge test` from `contracts/` (all existing and new tests pass). - **Storage**: - `./scripts/check-storage-layout.sh` - `./scripts/check-storage-layout-negative.sh` - **Coverage**: Slashing path (Solochain → Eth translation + revert), rewards path (translation + revert), registration/update/deregister (reverse mapping and uniqueness), and storage layout upgrade preservation. --------- Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com> Co-authored-by: Ahmad Kaouk <ahmadkaouk.93@gmail.com>
2026-02-18 19:38:13 +00:00
data: abi.encodePacked(solochainOperator)
feat : Slashing integration in EigenLayer and Datahaven AVS (#345) ## Summary This PR integrate the slashing feature with EigenLayer. With this PR, slashing can now be relayed to our Datahaven AVS and then executed within EigenLayer. In addition some refactoring of the original slashing pallet has been done. ## Motivation To avoid misbehaving actor in the network, Datahaven has implemented a slashing pallet in which offenses can be reported and then if adequate can lead to a sanction on the misbehaving node. It incentive nodes to only follow good behavior in addition to the reward incentive. The rewards flow is managed directly into EigenLayer (see https://github.com/datahaven-xyz/datahaven/pull/351). ## Slashing flow <img width="2355" height="946" alt="Slashing Flow" src="https://github.com/user-attachments/assets/c1ddc3dc-2a7e-429d-94e0-1e02a3f65246" /> ## What changes * Implemented `slashValidatorsOperator` in `DataHavenServiceManager`. It received all the slashing requests batched (every new era the queued slashing are being relayed from substrate to Ethereum). It handle the slashing of the operators reported into the Validator set. * Added a `slashes_adapter.rs` utility file to remove the duplication for each runtime. In addition, we made use of the `sol!` macro from alloy to encode the calldata for the Ethereum call. This avoid rewriting encoding logic and allow to remove the hardcoded selector value used to call the slashing function. * Added some tests in solidity to test the registering and slashing of an operator in Ethereum via Eigen Layer. * Added e2e tests that test the injection of a slash request, it being relayed via the snowbridge relayer and executed by our Datahaven AVS. ## What could be better * We are only deploying one strategy for now so it is hardcoded in the slashing flow. We should be able to update the pallet in case we are adding a new strategy. So communication from Ethereum should be relayed. * We don't have error being return in case the slashing fail. Which could happen if we don't have the right number of strategy or the validator is not registered... etc. * More tests for the unhappy path
2026-01-16 19:49:45 +00:00
});
vm.prank(operator);
allocationManager.registerForOperatorSets(operator, registerParams);
DataHavenServiceManager.SlashingRequest[] memory slashings =
new DataHavenServiceManager.SlashingRequest[](1);
uint256[] memory wadsToSlash = new uint256[](3); // 3 wadsToSlash because we have register 3 strategies for the Validator set
wadsToSlash[0] = 1e16;
wadsToSlash[1] = 1e16;
wadsToSlash[2] = 1e16;
OperatorSet memory operatorSet =
OperatorSet({avs: address(serviceManager), id: serviceManager.VALIDATORS_SET_ID()});
IStrategy[] memory strategies = allocationManager.getStrategiesInOperatorSet(operatorSet);
slashings[0] = IDataHavenServiceManager.SlashingRequest(
fix: 🩹 map validator address to operator address for rewards & slashes (#441) ## Summary Slashing and rewards submissions were submitted through the bridge with their **solochain address** , while EigenLayer expects the **ethereum operator address**, the addresses were not being translated, so the protocol was broken. This PR adds a **reverse mapping** (Solochain address → Eth address) and uses it in both the slashing and rewards paths so that: - `slashValidatorsOperator` accepts requests where `operator` is a Solochain address and translates it to the Eth operator before calling EigenLayer. - `submitRewards` translates each `operatorRewards[].operator` from Solochain to Eth before calling the RewardsCoordinator. - Unknown or unmapped solochain addresses cause a revert (`UnknownSolochainAddress`) instead of silently failing. ## What's changed ### DataHavenServiceManager - **Reverse mapping**: `mapping(address => address) public validatorSolochainAddressToEthAddress` (Solochain → Eth), with `__GAP` reduced by one slot for upgradeable layout. - **Helper**: `_ethOperatorFromSolochain(address)` – returns Eth operator for a Solochain address, reverts with `UnknownSolochainAddress()` if unmapped. - **Registration / lifecycle**: - `registerOperator`: populates both forward and reverse mappings; enforces uniqueness (one Solochain per operator) and clears old reverse entry when an operator re-registers with a new Solochain. - `deregisterOperator`: clears both forward and reverse entries. - `updateSolochainAddressForValidator`: updates both mappings, enforces uniqueness and clears the previous Solochain's reverse entry. - **Slashing**: `slashValidatorsOperator` uses `_ethOperatorFromSolochain(slashings[i].operator)` so requests keyed by Solochain address are translated before calling EigenLayer. - **Rewards**: `submitRewards` builds a translated copy of the submission with each `operatorRewards[].operator` set via `_ethOperatorFromSolochain(...)`; unmapped addresses revert. ### IDataHavenServiceManager - New getter: `validatorSolochainAddressToEthAddress(address solochain) external view returns (address)`. - New errors: `UnknownSolochainAddress()`, `SolochainAddressAlreadyAssigned()`. ### Storage and fixtures - Storage snapshot updated for the new state variable. - `DataHavenServiceManagerBadLayout.sol` updated (reverse mapping + gap) for layout negative tests. - Storage layout test extended to assert the reverse mapping is preserved across proxy upgrade. ### Tests - **Slashing.t.sol**: Slashing with Solochain address (translation and emit of Eth operator); negative test for unmapped Solochain reverting with `UnknownSolochainAddress()`. - **RewardsSubmitter.t.sol**: Rewards submission with Solochain addresses (translation to Eth in RewardsCoordinator calldata); negative test for unmapped Solochain. - **StorageLayout.t.sol**: Reverse mapping preserved after upgrade. - **OperatorAddressMappings.t.sol** (new): Uniqueness (Solochain already assigned to another operator), update/deregister clearing reverse mapping, and getter behaviour. ## Testing - **Unit tests**: `forge test` from `contracts/` (all existing and new tests pass). - **Storage**: - `./scripts/check-storage-layout.sh` - `./scripts/check-storage-layout-negative.sh` - **Coverage**: Slashing path (Solochain → Eth translation + revert), rewards path (translation + revert), registration/update/deregister (reverse mapping and uniqueness), and storage layout upgrade preservation. --------- Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com> Co-authored-by: Ahmad Kaouk <ahmadkaouk.93@gmail.com>
2026-02-18 19:38:13 +00:00
solochainOperator, strategies, wadsToSlash, "Testing slashing"
feat : Slashing integration in EigenLayer and Datahaven AVS (#345) ## Summary This PR integrate the slashing feature with EigenLayer. With this PR, slashing can now be relayed to our Datahaven AVS and then executed within EigenLayer. In addition some refactoring of the original slashing pallet has been done. ## Motivation To avoid misbehaving actor in the network, Datahaven has implemented a slashing pallet in which offenses can be reported and then if adequate can lead to a sanction on the misbehaving node. It incentive nodes to only follow good behavior in addition to the reward incentive. The rewards flow is managed directly into EigenLayer (see https://github.com/datahaven-xyz/datahaven/pull/351). ## Slashing flow <img width="2355" height="946" alt="Slashing Flow" src="https://github.com/user-attachments/assets/c1ddc3dc-2a7e-429d-94e0-1e02a3f65246" /> ## What changes * Implemented `slashValidatorsOperator` in `DataHavenServiceManager`. It received all the slashing requests batched (every new era the queued slashing are being relayed from substrate to Ethereum). It handle the slashing of the operators reported into the Validator set. * Added a `slashes_adapter.rs` utility file to remove the duplication for each runtime. In addition, we made use of the `sol!` macro from alloy to encode the calldata for the Ethereum call. This avoid rewriting encoding logic and allow to remove the hardcoded selector value used to call the slashing function. * Added some tests in solidity to test the registering and slashing of an operator in Ethereum via Eigen Layer. * Added e2e tests that test the injection of a slash request, it being relayed via the snowbridge relayer and executed by our Datahaven AVS. ## What could be better * We are only deploying one strategy for now so it is hardcoded in the slashing flow. We should be able to update the pallet in case we are adding a new strategy. So communication from Ethereum should be relayed. * We don't have error being return in case the slashing fail. Which could happen if we don't have the right number of strategy or the validator is not registered... etc. * More tests for the unhappy path
2026-01-16 19:49:45 +00:00
);
console.log(block.number);
vm.roll(block.number + uint32(7 days) + 1);
console.log(block.number);
// Because the current magnitude for the allocation is 0
uint256[] memory wadsToSlashed = new uint256[](3);
// We emit the event we expect to see.
vm.prank(snowbridgeAgent);
vm.expectEmit();
emit IAllocationManagerEvents.OperatorSlashed(
operator, operatorSet, strategies, wadsToSlashed, "Testing slashing"
);
vm.expectEmit();
emit IDataHavenServiceManagerEvents.SlashingComplete();
serviceManager.slashValidatorsOperator(slashings);
}
function test_fulfilSlashingRequestForOnlyOneStrategy() public {
fix: 🩹 map validator address to operator address for rewards & slashes (#441) ## Summary Slashing and rewards submissions were submitted through the bridge with their **solochain address** , while EigenLayer expects the **ethereum operator address**, the addresses were not being translated, so the protocol was broken. This PR adds a **reverse mapping** (Solochain address → Eth address) and uses it in both the slashing and rewards paths so that: - `slashValidatorsOperator` accepts requests where `operator` is a Solochain address and translates it to the Eth operator before calling EigenLayer. - `submitRewards` translates each `operatorRewards[].operator` from Solochain to Eth before calling the RewardsCoordinator. - Unknown or unmapped solochain addresses cause a revert (`UnknownSolochainAddress`) instead of silently failing. ## What's changed ### DataHavenServiceManager - **Reverse mapping**: `mapping(address => address) public validatorSolochainAddressToEthAddress` (Solochain → Eth), with `__GAP` reduced by one slot for upgradeable layout. - **Helper**: `_ethOperatorFromSolochain(address)` – returns Eth operator for a Solochain address, reverts with `UnknownSolochainAddress()` if unmapped. - **Registration / lifecycle**: - `registerOperator`: populates both forward and reverse mappings; enforces uniqueness (one Solochain per operator) and clears old reverse entry when an operator re-registers with a new Solochain. - `deregisterOperator`: clears both forward and reverse entries. - `updateSolochainAddressForValidator`: updates both mappings, enforces uniqueness and clears the previous Solochain's reverse entry. - **Slashing**: `slashValidatorsOperator` uses `_ethOperatorFromSolochain(slashings[i].operator)` so requests keyed by Solochain address are translated before calling EigenLayer. - **Rewards**: `submitRewards` builds a translated copy of the submission with each `operatorRewards[].operator` set via `_ethOperatorFromSolochain(...)`; unmapped addresses revert. ### IDataHavenServiceManager - New getter: `validatorSolochainAddressToEthAddress(address solochain) external view returns (address)`. - New errors: `UnknownSolochainAddress()`, `SolochainAddressAlreadyAssigned()`. ### Storage and fixtures - Storage snapshot updated for the new state variable. - `DataHavenServiceManagerBadLayout.sol` updated (reverse mapping + gap) for layout negative tests. - Storage layout test extended to assert the reverse mapping is preserved across proxy upgrade. ### Tests - **Slashing.t.sol**: Slashing with Solochain address (translation and emit of Eth operator); negative test for unmapped Solochain reverting with `UnknownSolochainAddress()`. - **RewardsSubmitter.t.sol**: Rewards submission with Solochain addresses (translation to Eth in RewardsCoordinator calldata); negative test for unmapped Solochain. - **StorageLayout.t.sol**: Reverse mapping preserved after upgrade. - **OperatorAddressMappings.t.sol** (new): Uniqueness (Solochain already assigned to another operator), update/deregister clearing reverse mapping, and getter behaviour. ## Testing - **Unit tests**: `forge test` from `contracts/` (all existing and new tests pass). - **Storage**: - `./scripts/check-storage-layout.sh` - `./scripts/check-storage-layout-negative.sh` - **Coverage**: Slashing path (Solochain → Eth translation + revert), rewards path (translation + revert), registration/update/deregister (reverse mapping and uniqueness), and storage layout upgrade preservation. --------- Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com> Co-authored-by: Ahmad Kaouk <ahmadkaouk.93@gmail.com>
2026-02-18 19:38:13 +00:00
address solochainOperator = address(0xBEEF);
feat : Slashing integration in EigenLayer and Datahaven AVS (#345) ## Summary This PR integrate the slashing feature with EigenLayer. With this PR, slashing can now be relayed to our Datahaven AVS and then executed within EigenLayer. In addition some refactoring of the original slashing pallet has been done. ## Motivation To avoid misbehaving actor in the network, Datahaven has implemented a slashing pallet in which offenses can be reported and then if adequate can lead to a sanction on the misbehaving node. It incentive nodes to only follow good behavior in addition to the reward incentive. The rewards flow is managed directly into EigenLayer (see https://github.com/datahaven-xyz/datahaven/pull/351). ## Slashing flow <img width="2355" height="946" alt="Slashing Flow" src="https://github.com/user-attachments/assets/c1ddc3dc-2a7e-429d-94e0-1e02a3f65246" /> ## What changes * Implemented `slashValidatorsOperator` in `DataHavenServiceManager`. It received all the slashing requests batched (every new era the queued slashing are being relayed from substrate to Ethereum). It handle the slashing of the operators reported into the Validator set. * Added a `slashes_adapter.rs` utility file to remove the duplication for each runtime. In addition, we made use of the `sol!` macro from alloy to encode the calldata for the Ethereum call. This avoid rewriting encoding logic and allow to remove the hardcoded selector value used to call the slashing function. * Added some tests in solidity to test the registering and slashing of an operator in Ethereum via Eigen Layer. * Added e2e tests that test the injection of a slash request, it being relayed via the snowbridge relayer and executed by our Datahaven AVS. ## What could be better * We are only deploying one strategy for now so it is hardcoded in the slashing flow. We should be able to update the pallet in case we are adding a new strategy. So communication from Ethereum should be relayed. * We don't have error being return in case the slashing fail. Which could happen if we don't have the right number of strategy or the validator is not registered... etc. * More tests for the unhappy path
2026-01-16 19:49:45 +00:00
// Allow our operator to register
vm.prank(avsOwner);
serviceManager.addValidatorToAllowlist(operator);
// Configure the rewards initiator (because only the reward agent can submit slashing request)
vm.prank(avsOwner);
serviceManager.setRewardsInitiator(snowbridgeAgent);
vm.prank(operator);
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,
fix: 🩹 map validator address to operator address for rewards & slashes (#441) ## Summary Slashing and rewards submissions were submitted through the bridge with their **solochain address** , while EigenLayer expects the **ethereum operator address**, the addresses were not being translated, so the protocol was broken. This PR adds a **reverse mapping** (Solochain address → Eth address) and uses it in both the slashing and rewards paths so that: - `slashValidatorsOperator` accepts requests where `operator` is a Solochain address and translates it to the Eth operator before calling EigenLayer. - `submitRewards` translates each `operatorRewards[].operator` from Solochain to Eth before calling the RewardsCoordinator. - Unknown or unmapped solochain addresses cause a revert (`UnknownSolochainAddress`) instead of silently failing. ## What's changed ### DataHavenServiceManager - **Reverse mapping**: `mapping(address => address) public validatorSolochainAddressToEthAddress` (Solochain → Eth), with `__GAP` reduced by one slot for upgradeable layout. - **Helper**: `_ethOperatorFromSolochain(address)` – returns Eth operator for a Solochain address, reverts with `UnknownSolochainAddress()` if unmapped. - **Registration / lifecycle**: - `registerOperator`: populates both forward and reverse mappings; enforces uniqueness (one Solochain per operator) and clears old reverse entry when an operator re-registers with a new Solochain. - `deregisterOperator`: clears both forward and reverse entries. - `updateSolochainAddressForValidator`: updates both mappings, enforces uniqueness and clears the previous Solochain's reverse entry. - **Slashing**: `slashValidatorsOperator` uses `_ethOperatorFromSolochain(slashings[i].operator)` so requests keyed by Solochain address are translated before calling EigenLayer. - **Rewards**: `submitRewards` builds a translated copy of the submission with each `operatorRewards[].operator` set via `_ethOperatorFromSolochain(...)`; unmapped addresses revert. ### IDataHavenServiceManager - New getter: `validatorSolochainAddressToEthAddress(address solochain) external view returns (address)`. - New errors: `UnknownSolochainAddress()`, `SolochainAddressAlreadyAssigned()`. ### Storage and fixtures - Storage snapshot updated for the new state variable. - `DataHavenServiceManagerBadLayout.sol` updated (reverse mapping + gap) for layout negative tests. - Storage layout test extended to assert the reverse mapping is preserved across proxy upgrade. ### Tests - **Slashing.t.sol**: Slashing with Solochain address (translation and emit of Eth operator); negative test for unmapped Solochain reverting with `UnknownSolochainAddress()`. - **RewardsSubmitter.t.sol**: Rewards submission with Solochain addresses (translation to Eth in RewardsCoordinator calldata); negative test for unmapped Solochain. - **StorageLayout.t.sol**: Reverse mapping preserved after upgrade. - **OperatorAddressMappings.t.sol** (new): Uniqueness (Solochain already assigned to another operator), update/deregister clearing reverse mapping, and getter behaviour. ## Testing - **Unit tests**: `forge test` from `contracts/` (all existing and new tests pass). - **Storage**: - `./scripts/check-storage-layout.sh` - `./scripts/check-storage-layout-negative.sh` - **Coverage**: Slashing path (Solochain → Eth translation + revert), rewards path (translation + revert), registration/update/deregister (reverse mapping and uniqueness), and storage layout upgrade preservation. --------- Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com> Co-authored-by: Ahmad Kaouk <ahmadkaouk.93@gmail.com>
2026-02-18 19:38:13 +00:00
data: abi.encodePacked(solochainOperator)
feat : Slashing integration in EigenLayer and Datahaven AVS (#345) ## Summary This PR integrate the slashing feature with EigenLayer. With this PR, slashing can now be relayed to our Datahaven AVS and then executed within EigenLayer. In addition some refactoring of the original slashing pallet has been done. ## Motivation To avoid misbehaving actor in the network, Datahaven has implemented a slashing pallet in which offenses can be reported and then if adequate can lead to a sanction on the misbehaving node. It incentive nodes to only follow good behavior in addition to the reward incentive. The rewards flow is managed directly into EigenLayer (see https://github.com/datahaven-xyz/datahaven/pull/351). ## Slashing flow <img width="2355" height="946" alt="Slashing Flow" src="https://github.com/user-attachments/assets/c1ddc3dc-2a7e-429d-94e0-1e02a3f65246" /> ## What changes * Implemented `slashValidatorsOperator` in `DataHavenServiceManager`. It received all the slashing requests batched (every new era the queued slashing are being relayed from substrate to Ethereum). It handle the slashing of the operators reported into the Validator set. * Added a `slashes_adapter.rs` utility file to remove the duplication for each runtime. In addition, we made use of the `sol!` macro from alloy to encode the calldata for the Ethereum call. This avoid rewriting encoding logic and allow to remove the hardcoded selector value used to call the slashing function. * Added some tests in solidity to test the registering and slashing of an operator in Ethereum via Eigen Layer. * Added e2e tests that test the injection of a slash request, it being relayed via the snowbridge relayer and executed by our Datahaven AVS. ## What could be better * We are only deploying one strategy for now so it is hardcoded in the slashing flow. We should be able to update the pallet in case we are adding a new strategy. So communication from Ethereum should be relayed. * We don't have error being return in case the slashing fail. Which could happen if we don't have the right number of strategy or the validator is not registered... etc. * More tests for the unhappy path
2026-01-16 19:49:45 +00:00
});
vm.prank(operator);
allocationManager.registerForOperatorSets(operator, registerParams);
OperatorSet memory operatorSet =
OperatorSet({avs: address(serviceManager), id: serviceManager.VALIDATORS_SET_ID()});
IStrategy[] memory strategies = allocationManager.getStrategiesInOperatorSet(operatorSet);
DataHavenServiceManager.SlashingRequest[] memory slashings =
new DataHavenServiceManager.SlashingRequest[](1);
uint256[] memory wadsToSlash = new uint256[](1); // We only want to slash 1 strategy
wadsToSlash[0] = 1e16;
IStrategy[] memory strategiesToSlash = new IStrategy[](1);
strategiesToSlash[0] = strategies[0];
slashings[0] = IDataHavenServiceManager.SlashingRequest(
fix: 🩹 map validator address to operator address for rewards & slashes (#441) ## Summary Slashing and rewards submissions were submitted through the bridge with their **solochain address** , while EigenLayer expects the **ethereum operator address**, the addresses were not being translated, so the protocol was broken. This PR adds a **reverse mapping** (Solochain address → Eth address) and uses it in both the slashing and rewards paths so that: - `slashValidatorsOperator` accepts requests where `operator` is a Solochain address and translates it to the Eth operator before calling EigenLayer. - `submitRewards` translates each `operatorRewards[].operator` from Solochain to Eth before calling the RewardsCoordinator. - Unknown or unmapped solochain addresses cause a revert (`UnknownSolochainAddress`) instead of silently failing. ## What's changed ### DataHavenServiceManager - **Reverse mapping**: `mapping(address => address) public validatorSolochainAddressToEthAddress` (Solochain → Eth), with `__GAP` reduced by one slot for upgradeable layout. - **Helper**: `_ethOperatorFromSolochain(address)` – returns Eth operator for a Solochain address, reverts with `UnknownSolochainAddress()` if unmapped. - **Registration / lifecycle**: - `registerOperator`: populates both forward and reverse mappings; enforces uniqueness (one Solochain per operator) and clears old reverse entry when an operator re-registers with a new Solochain. - `deregisterOperator`: clears both forward and reverse entries. - `updateSolochainAddressForValidator`: updates both mappings, enforces uniqueness and clears the previous Solochain's reverse entry. - **Slashing**: `slashValidatorsOperator` uses `_ethOperatorFromSolochain(slashings[i].operator)` so requests keyed by Solochain address are translated before calling EigenLayer. - **Rewards**: `submitRewards` builds a translated copy of the submission with each `operatorRewards[].operator` set via `_ethOperatorFromSolochain(...)`; unmapped addresses revert. ### IDataHavenServiceManager - New getter: `validatorSolochainAddressToEthAddress(address solochain) external view returns (address)`. - New errors: `UnknownSolochainAddress()`, `SolochainAddressAlreadyAssigned()`. ### Storage and fixtures - Storage snapshot updated for the new state variable. - `DataHavenServiceManagerBadLayout.sol` updated (reverse mapping + gap) for layout negative tests. - Storage layout test extended to assert the reverse mapping is preserved across proxy upgrade. ### Tests - **Slashing.t.sol**: Slashing with Solochain address (translation and emit of Eth operator); negative test for unmapped Solochain reverting with `UnknownSolochainAddress()`. - **RewardsSubmitter.t.sol**: Rewards submission with Solochain addresses (translation to Eth in RewardsCoordinator calldata); negative test for unmapped Solochain. - **StorageLayout.t.sol**: Reverse mapping preserved after upgrade. - **OperatorAddressMappings.t.sol** (new): Uniqueness (Solochain already assigned to another operator), update/deregister clearing reverse mapping, and getter behaviour. ## Testing - **Unit tests**: `forge test` from `contracts/` (all existing and new tests pass). - **Storage**: - `./scripts/check-storage-layout.sh` - `./scripts/check-storage-layout-negative.sh` - **Coverage**: Slashing path (Solochain → Eth translation + revert), rewards path (translation + revert), registration/update/deregister (reverse mapping and uniqueness), and storage layout upgrade preservation. --------- Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com> Co-authored-by: Ahmad Kaouk <ahmadkaouk.93@gmail.com>
2026-02-18 19:38:13 +00:00
solochainOperator, strategiesToSlash, wadsToSlash, "Testing slashing"
feat : Slashing integration in EigenLayer and Datahaven AVS (#345) ## Summary This PR integrate the slashing feature with EigenLayer. With this PR, slashing can now be relayed to our Datahaven AVS and then executed within EigenLayer. In addition some refactoring of the original slashing pallet has been done. ## Motivation To avoid misbehaving actor in the network, Datahaven has implemented a slashing pallet in which offenses can be reported and then if adequate can lead to a sanction on the misbehaving node. It incentive nodes to only follow good behavior in addition to the reward incentive. The rewards flow is managed directly into EigenLayer (see https://github.com/datahaven-xyz/datahaven/pull/351). ## Slashing flow <img width="2355" height="946" alt="Slashing Flow" src="https://github.com/user-attachments/assets/c1ddc3dc-2a7e-429d-94e0-1e02a3f65246" /> ## What changes * Implemented `slashValidatorsOperator` in `DataHavenServiceManager`. It received all the slashing requests batched (every new era the queued slashing are being relayed from substrate to Ethereum). It handle the slashing of the operators reported into the Validator set. * Added a `slashes_adapter.rs` utility file to remove the duplication for each runtime. In addition, we made use of the `sol!` macro from alloy to encode the calldata for the Ethereum call. This avoid rewriting encoding logic and allow to remove the hardcoded selector value used to call the slashing function. * Added some tests in solidity to test the registering and slashing of an operator in Ethereum via Eigen Layer. * Added e2e tests that test the injection of a slash request, it being relayed via the snowbridge relayer and executed by our Datahaven AVS. ## What could be better * We are only deploying one strategy for now so it is hardcoded in the slashing flow. We should be able to update the pallet in case we are adding a new strategy. So communication from Ethereum should be relayed. * We don't have error being return in case the slashing fail. Which could happen if we don't have the right number of strategy or the validator is not registered... etc. * More tests for the unhappy path
2026-01-16 19:49:45 +00:00
);
console.log(block.number);
vm.roll(block.number + uint32(7 days) + 1);
console.log(block.number);
// Because the current magnitude for the allocation is 0
uint256[] memory wadsToSlashed = new uint256[](1);
// We emit the event we expect to see.
vm.prank(snowbridgeAgent);
vm.expectEmit();
emit IAllocationManagerEvents.OperatorSlashed(
operator, operatorSet, strategiesToSlash, wadsToSlashed, "Testing slashing"
);
vm.expectEmit();
emit IDataHavenServiceManagerEvents.SlashingComplete();
serviceManager.slashValidatorsOperator(slashings);
}
fix: 🩹 map validator address to operator address for rewards & slashes (#441) ## Summary Slashing and rewards submissions were submitted through the bridge with their **solochain address** , while EigenLayer expects the **ethereum operator address**, the addresses were not being translated, so the protocol was broken. This PR adds a **reverse mapping** (Solochain address → Eth address) and uses it in both the slashing and rewards paths so that: - `slashValidatorsOperator` accepts requests where `operator` is a Solochain address and translates it to the Eth operator before calling EigenLayer. - `submitRewards` translates each `operatorRewards[].operator` from Solochain to Eth before calling the RewardsCoordinator. - Unknown or unmapped solochain addresses cause a revert (`UnknownSolochainAddress`) instead of silently failing. ## What's changed ### DataHavenServiceManager - **Reverse mapping**: `mapping(address => address) public validatorSolochainAddressToEthAddress` (Solochain → Eth), with `__GAP` reduced by one slot for upgradeable layout. - **Helper**: `_ethOperatorFromSolochain(address)` – returns Eth operator for a Solochain address, reverts with `UnknownSolochainAddress()` if unmapped. - **Registration / lifecycle**: - `registerOperator`: populates both forward and reverse mappings; enforces uniqueness (one Solochain per operator) and clears old reverse entry when an operator re-registers with a new Solochain. - `deregisterOperator`: clears both forward and reverse entries. - `updateSolochainAddressForValidator`: updates both mappings, enforces uniqueness and clears the previous Solochain's reverse entry. - **Slashing**: `slashValidatorsOperator` uses `_ethOperatorFromSolochain(slashings[i].operator)` so requests keyed by Solochain address are translated before calling EigenLayer. - **Rewards**: `submitRewards` builds a translated copy of the submission with each `operatorRewards[].operator` set via `_ethOperatorFromSolochain(...)`; unmapped addresses revert. ### IDataHavenServiceManager - New getter: `validatorSolochainAddressToEthAddress(address solochain) external view returns (address)`. - New errors: `UnknownSolochainAddress()`, `SolochainAddressAlreadyAssigned()`. ### Storage and fixtures - Storage snapshot updated for the new state variable. - `DataHavenServiceManagerBadLayout.sol` updated (reverse mapping + gap) for layout negative tests. - Storage layout test extended to assert the reverse mapping is preserved across proxy upgrade. ### Tests - **Slashing.t.sol**: Slashing with Solochain address (translation and emit of Eth operator); negative test for unmapped Solochain reverting with `UnknownSolochainAddress()`. - **RewardsSubmitter.t.sol**: Rewards submission with Solochain addresses (translation to Eth in RewardsCoordinator calldata); negative test for unmapped Solochain. - **StorageLayout.t.sol**: Reverse mapping preserved after upgrade. - **OperatorAddressMappings.t.sol** (new): Uniqueness (Solochain already assigned to another operator), update/deregister clearing reverse mapping, and getter behaviour. ## Testing - **Unit tests**: `forge test` from `contracts/` (all existing and new tests pass). - **Storage**: - `./scripts/check-storage-layout.sh` - `./scripts/check-storage-layout-negative.sh` - **Coverage**: Slashing path (Solochain → Eth translation + revert), rewards path (translation + revert), registration/update/deregister (reverse mapping and uniqueness), and storage layout upgrade preservation. --------- Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com> Co-authored-by: Ahmad Kaouk <ahmadkaouk.93@gmail.com>
2026-02-18 19:38:13 +00:00
function test_fulfilSlashingRequest_revertsIfUnknownSolochainAddress() public {
// Configure the rewards initiator (because only the reward agent can submit slashing request)
vm.prank(avsOwner);
serviceManager.setRewardsInitiator(snowbridgeAgent);
address unknownSolochainOperator = address(0xDEAD);
DataHavenServiceManager.SlashingRequest[] memory slashings =
new DataHavenServiceManager.SlashingRequest[](1);
slashings[0] = IDataHavenServiceManager.SlashingRequest(
unknownSolochainOperator,
new IStrategy[](0),
new uint256[](0),
"Testing unknown solochain operator"
);
vm.prank(snowbridgeAgent);
vm.expectRevert(abi.encodeWithSignature("UnknownSolochainAddress()"));
serviceManager.slashValidatorsOperator(slashings);
}
feat : Slashing integration in EigenLayer and Datahaven AVS (#345) ## Summary This PR integrate the slashing feature with EigenLayer. With this PR, slashing can now be relayed to our Datahaven AVS and then executed within EigenLayer. In addition some refactoring of the original slashing pallet has been done. ## Motivation To avoid misbehaving actor in the network, Datahaven has implemented a slashing pallet in which offenses can be reported and then if adequate can lead to a sanction on the misbehaving node. It incentive nodes to only follow good behavior in addition to the reward incentive. The rewards flow is managed directly into EigenLayer (see https://github.com/datahaven-xyz/datahaven/pull/351). ## Slashing flow <img width="2355" height="946" alt="Slashing Flow" src="https://github.com/user-attachments/assets/c1ddc3dc-2a7e-429d-94e0-1e02a3f65246" /> ## What changes * Implemented `slashValidatorsOperator` in `DataHavenServiceManager`. It received all the slashing requests batched (every new era the queued slashing are being relayed from substrate to Ethereum). It handle the slashing of the operators reported into the Validator set. * Added a `slashes_adapter.rs` utility file to remove the duplication for each runtime. In addition, we made use of the `sol!` macro from alloy to encode the calldata for the Ethereum call. This avoid rewriting encoding logic and allow to remove the hardcoded selector value used to call the slashing function. * Added some tests in solidity to test the registering and slashing of an operator in Ethereum via Eigen Layer. * Added e2e tests that test the injection of a slash request, it being relayed via the snowbridge relayer and executed by our Datahaven AVS. ## What could be better * We are only deploying one strategy for now so it is hardcoded in the slashing flow. We should be able to update the pallet in case we are adding a new strategy. So communication from Ethereum should be relayed. * We don't have error being return in case the slashing fail. Which could happen if we don't have the right number of strategy or the validator is not registered... etc. * More tests for the unhappy path
2026-01-16 19:49:45 +00:00
}