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);
|
|
|
|
|
|
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)
|
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"
|
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);
|
|
|
|
|
|
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)
|
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"
|
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);
|
|
|
|
|
}
|
2026-01-16 19:49:45 +00:00
|
|
|
}
|