Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: non-beacon-chain slashing integration tests #1010

Merged
merged 10 commits into from
Jan 14, 2025
5 changes: 5 additions & 0 deletions src/contracts/core/DelegationManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,11 @@ contract DelegationManager is
slashingFactor: prevSlashingFactors[i]
});

//Do nothing if 0 shares to withdraw
if (sharesToWithdraw == 0) {
continue;
}

if (receiveAsTokens) {
// Withdraws `shares` in `strategy` to `withdrawer`. If the shares are virtual beaconChainETH shares,
// then a call is ultimately forwarded to the `staker`s EigenPod; otherwise a call is ultimately forwarded
Expand Down
29 changes: 29 additions & 0 deletions src/test/integration/IntegrationBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1133,6 +1133,23 @@ abstract contract IntegrationBase is IntegrationDeployer {

return (strategies.sort(), wadsToSlash);
}

function _strategiesAndWadsForFullSlash(
OperatorSet memory operatorSet
) internal view returns (IStrategy[] memory strategies, uint[] memory wadsToSlash) {
// Get list of all strategies in an operator set.
strategies = allocationManager.getStrategiesInOperatorSet(operatorSet);

wadsToSlash = new uint[](strategies.length);

for (uint i; i < strategies.length; ++i) {
wadsToSlash[i] = 1 ether;
}

return(strategies.sort(), wadsToSlash);
}



function _randMagnitudes(uint64 sum, uint256 len) internal returns (uint64[] memory magnitudes) {
magnitudes = new uint64[](len);
Expand All @@ -1151,6 +1168,18 @@ abstract contract IntegrationBase is IntegrationDeployer {
}
}

function _maxMagnitudes(OperatorSet memory operatorSet, User operator) internal view returns (uint64[] memory magnitudes) {
IStrategy[] memory strategies = allocationManager.getStrategiesInOperatorSet(operatorSet);
uint256 len = strategies.length;
magnitudes = new uint64[](len);

if (len == 0) return magnitudes;

for (uint256 i; i < len; ++i) {
magnitudes[i] = allocationManager.getMaxMagnitude(address(operator), strategies[i]);
}
}

function _randWithdrawal(
IStrategy[] memory strategies,
uint[] memory shares
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;

import "src/test/integration/IntegrationChecks.t.sol";
import "src/test/integration/users/User.t.sol";
import {console} from "forge-std/console.sol";

contract Integration_Deposit_Delegate_Allocate_Slash_Queue_Redeposit is IntegrationCheckUtils, IDelegationManagerTypes {

// TODO: Partial deposits don't work when beacon chain eth balance is initialized to < 64 ETH, need to write _newRandomStaker variant that ensures beacon chain ETH balance
// greater than or equal to 64
function testFuzz_deposit_delegate_allocate_fullSlash_queue_complete_redeposit(
uint24 _random
) public {
_configRand({_randomSeed: _random, _assetTypes: HOLDS_LST, _userTypes: DEFAULT});
_upgradeEigenLayerContracts();

(User staker, IStrategy[] memory strategies, uint256[] memory tokenBalances) = _newRandomStaker();
(User operator,,) = _newRandomOperator();
(AVS avs,) = _newRandomAVS();

uint256[] memory tokensToDeposit = new uint256[](tokenBalances.length);
uint256[] memory numTokensRemaining = new uint256[](tokenBalances.length);
for (uint256 i = 0; i < tokenBalances.length; i++) {
tokensToDeposit[i] = tokenBalances[i]/2;
numTokensRemaining[i] = tokenBalances[i] - tokensToDeposit[i];
}

uint256[] memory shares = _calculateExpectedShares(strategies, tokensToDeposit);

// 1. Deposit Into Strategies
staker.depositIntoEigenlayer(strategies, tokensToDeposit);
check_Deposit_State_PartialDeposit(staker, strategies, shares, numTokensRemaining);

// 2. Delegate to operator
staker.delegateTo(operator);
check_Delegation_State(staker, operator, strategies, shares);

// Create operator set and register operator
OperatorSet memory operatorSet = avs.createOperatorSet(strategies);
operator.registerForOperatorSet(operatorSet);

// 3. Allocate to operator set
IAllocationManagerTypes.AllocateParams memory allocateParams =
operator.modifyAllocations(operatorSet, _maxMagnitudes(operatorSet, operator));

assert_Snap_Allocations_Modified(
operator,
allocateParams,
false,
"operator allocations should be updated before delay"
);

_rollBlocksForCompleteAllocation(operator, operatorSet, strategies);

assert_Snap_Allocations_Modified(
operator,
allocateParams,
true,
"operator allocations should be updated after delay"
);

// 4. Fully slash operator
IAllocationManagerTypes.SlashingParams memory slashingParams;
{
(IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) =
_strategiesAndWadsForFullSlash(operatorSet);

slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash);
assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed");
assert_Snap_Unchanged_StakerDepositShares(staker, "staker deposit shares should be unchanged after slashing");
assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker deposit shares should be slashed");
}

// 5. Undelegate from an operator
IDelegationManagerTypes.Withdrawal[] memory withdrawals = staker.undelegate();
bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);

// 6. Complete withdrawal
_rollBlocksForCompleteWithdrawals(withdrawals);
for (uint256 i = 0; i < withdrawals.length; ++i) {
uint256[] memory expectedTokens =
_calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].scaledShares);
for (uint256 i = 0; i < expectedTokens.length; i++) {
}
eigenmikem marked this conversation as resolved.
Show resolved Hide resolved
staker.completeWithdrawalAsTokens(withdrawals[i]);
ypatil12 marked this conversation as resolved.
Show resolved Hide resolved
check_Withdrawal_AsTokens_State_AfterSlash(staker, operator, withdrawals[i], allocateParams, slashingParams, expectedTokens);
}

// 7. Redeposit
shares = _calculateExpectedShares(strategies, numTokensRemaining);
staker.depositIntoEigenlayer(strategies, numTokensRemaining);
check_Deposit_State(staker, strategies, shares);

// Final state checks
assert_HasExpectedShares(staker, strategies, shares, "staker should have expected shares after redeposit");
assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending");
}

function testFuzz_deposit_delegate_allocate_queue_fullSlash_complete_redeposit(
uint24 _random
) public {
_configRand({_randomSeed: _random, _assetTypes: HOLDS_LST, _userTypes: DEFAULT});
_upgradeEigenLayerContracts();

(User staker, IStrategy[] memory strategies, uint256[] memory tokenBalances) = _newRandomStaker();
(User operator,,) = _newRandomOperator();
(AVS avs,) = _newRandomAVS();

uint256[] memory tokensToDeposit = new uint256[](tokenBalances.length);
uint256[] memory numTokensRemaining = new uint256[](tokenBalances.length);
for (uint256 i = 0; i < tokenBalances.length; i++) {
tokensToDeposit[i] = tokenBalances[i]/2;
numTokensRemaining[i] = tokenBalances[i] - tokensToDeposit[i];
}

uint256[] memory shares = _calculateExpectedShares(strategies, tokensToDeposit);

// 1. Deposit Into Strategies
staker.depositIntoEigenlayer(strategies, tokensToDeposit);
check_Deposit_State_PartialDeposit(staker, strategies, shares, numTokensRemaining);

// 2. Delegate to operator
staker.delegateTo(operator);
check_Delegation_State(staker, operator, strategies, shares);

// Create operator set and register operator
OperatorSet memory operatorSet = avs.createOperatorSet(strategies);
operator.registerForOperatorSet(operatorSet);

// 3. Allocate to operator set
IAllocationManagerTypes.AllocateParams memory allocateParams =
operator.modifyAllocations(operatorSet, _maxMagnitudes(operatorSet, operator));

assert_Snap_Allocations_Modified(
operator,
allocateParams,
false,
"operator allocations should be updated before delay"
);

_rollBlocksForCompleteAllocation(operator, operatorSet, strategies);

assert_Snap_Allocations_Modified(
operator,
allocateParams,
true,
"operator allocations should be updated after delay"
);

// 4. Undelegate from an operator
IDelegationManagerTypes.Withdrawal[] memory withdrawals = staker.undelegate();
bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);

// 5. Fully slash operator
IAllocationManagerTypes.SlashingParams memory slashingParams;
{
(IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) =
_strategiesAndWadsForFullSlash(operatorSet);

slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash);
assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed");
assert_Snap_Unchanged_StakerDepositShares(staker, "staker deposit shares should be unchanged after slashing");
assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker deposit shares should be slashed");
}

// 6. Complete withdrawal
_rollBlocksForCompleteWithdrawals(withdrawals);
for (uint256 i = 0; i < withdrawals.length; ++i) {
uint256[] memory expectedTokens =
_calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].scaledShares);
for (uint256 i = 0; i < expectedTokens.length; i++) {
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stub?

staker.completeWithdrawalAsTokens(withdrawals[i]);
check_Withdrawal_AsTokens_State_AfterSlash(staker, operator, withdrawals[i], allocateParams, slashingParams, expectedTokens);
}

// 7. Redeposit
shares = _calculateExpectedShares(strategies, numTokensRemaining);
staker.depositIntoEigenlayer(strategies, numTokensRemaining);
check_Deposit_State(staker, strategies, shares);

// Final state checks
assert_HasExpectedShares(staker, strategies, shares, "staker should have expected shares after redeposit");
assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending");
}

function testFuzz_deposit_delegate_allocateMultSets_fullSlash_queue_complete_redeposit(
uint24 _random
) public {
_configRand({_randomSeed: _random, _assetTypes: HOLDS_LST, _userTypes: DEFAULT});
_upgradeEigenLayerContracts();

(User staker, IStrategy[] memory strategies, uint256[] memory tokenBalances) = _newRandomStaker();
(User operator,,) = _newRandomOperator();
(AVS avs,) = _newRandomAVS();
OperatorSet memory operatorSet;

uint256[] memory tokensToDeposit = new uint256[](tokenBalances.length);
uint256[] memory numTokensRemaining = new uint256[](tokenBalances.length);
for (uint256 i = 0; i < tokenBalances.length; i++) {
tokensToDeposit[i] = tokenBalances[i]/2;
numTokensRemaining[i] = tokenBalances[i] - tokensToDeposit[i];
}

uint256[] memory shares = _calculateExpectedShares(strategies, tokensToDeposit);

// 1. Deposit Into Strategies
staker.depositIntoEigenlayer(strategies, tokensToDeposit);
check_Deposit_State_PartialDeposit(staker, strategies, shares, numTokensRemaining);

// 2. Delegate to operator
staker.delegateTo(operator);
check_Delegation_State(staker, operator, strategies, shares);

// Create operator sets and register operator
{
uint numOpSets = _randUint({min: 2, max: 4});
OperatorSet[] memory operatorSets = new OperatorSet[](numOpSets);
for (uint i = 0; i < numOpSets; i++){
operatorSets[i] = avs.createOperatorSet(strategies);
}
operatorSet = operatorSets[_randUint({min: 0, max: numOpSets-1})];
}

operator.registerForOperatorSet(operatorSet);

// 3. Allocate to operator set
IAllocationManagerTypes.AllocateParams memory allocateParams =
operator.modifyAllocations(operatorSet, _maxMagnitudes(operatorSet, operator));

assert_Snap_Allocations_Modified(
operator,
allocateParams,
false,
"operator allocations should be updated before delay"
);

_rollBlocksForCompleteAllocation(operator, operatorSet, strategies);

assert_Snap_Allocations_Modified(
operator,
allocateParams,
true,
"operator allocations should be updated after delay"
);

// 4. Fully slash operator
IAllocationManagerTypes.SlashingParams memory slashingParams;
{
(IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) =
_strategiesAndWadsForFullSlash(operatorSet);

slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash);
assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed");
assert_Snap_Unchanged_StakerDepositShares(staker, "staker deposit shares should be unchanged after slashing");
assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker deposit shares should be slashed");
}

// 5. Undelegate from an operator
IDelegationManagerTypes.Withdrawal[] memory withdrawals = staker.undelegate();
bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);

// 6. Complete withdrawal
_rollBlocksForCompleteWithdrawals(withdrawals);
for (uint256 i = 0; i < withdrawals.length; ++i) {
uint256[] memory expectedTokens =
_calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].scaledShares);
for (uint256 i = 0; i < expectedTokens.length; i++) {
}
eigenmikem marked this conversation as resolved.
Show resolved Hide resolved
staker.completeWithdrawalAsTokens(withdrawals[i]);
check_Withdrawal_AsTokens_State_AfterSlash(staker, operator, withdrawals[i], allocateParams, slashingParams, expectedTokens);
}

// 7. Redeposit
shares = _calculateExpectedShares(strategies, numTokensRemaining);
staker.depositIntoEigenlayer(strategies, numTokensRemaining);
check_Deposit_State(staker, strategies, shares);

// Final state checks
assert_HasExpectedShares(staker, strategies, shares, "staker should have expected shares after redeposit");
assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending");
}
}
Loading
Loading