mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 09:50:01 +00:00
## 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
142 lines
5.7 KiB
Solidity
142 lines
5.7 KiB
Solidity
// 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 {
|
|
// 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,
|
|
data: abi.encodePacked(address(operator))
|
|
});
|
|
|
|
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(
|
|
operator, strategies, wadsToSlash, "Testing slashing"
|
|
);
|
|
|
|
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 {
|
|
// 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,
|
|
data: abi.encodePacked(address(operator))
|
|
});
|
|
|
|
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(
|
|
operator, strategiesToSlash, wadsToSlash, "Testing slashing"
|
|
);
|
|
|
|
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);
|
|
}
|
|
}
|