Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/test/integration/IntegrationBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,14 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
assertEq(depositScalingFactors[i], WAD, err);
}
}

function assert_BCSF_Zero(
User staker,
string memory err
) internal {
uint64 curBCSF = _getBeaconChainSlashingFactor(staker);
assertEq(curBCSF, 0, err);
}

/*******************************************************************************
SNAPSHOT ASSERTIONS
Expand Down Expand Up @@ -2260,7 +2268,7 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
uint40[] memory validators = staker.getActiveValidators();
emit log_named_uint("slashing validators", validators.length);

deltaGwei = -int64(beaconChain.slashValidators(validators));
deltaGwei = -int64(beaconChain.slashValidators(validators, BeaconChainMock.SlashType.Minor));
beaconChain.advanceEpoch_NoRewards();

emit log_named_int("slashed amount", deltaGwei);
Expand Down
16 changes: 16 additions & 0 deletions src/test/integration/IntegrationChecks.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,22 @@ contract IntegrationCheckUtils is IntegrationBase {
assert_SlashableStake_Decrease_BCSlash(staker);
}

function check_CompleteCheckpoint_FullySlashed_State(
User staker,
uint40[] memory slashedValidators,
uint64 slashedAmountGwei
) internal {
check_CompleteCheckpoint_State(staker);

assert_Snap_Unchanged_Staker_DepositShares(staker, "staker shares should not have decreased");
assert_Snap_Removed_Staker_WithdrawableShares(staker, BEACONCHAIN_ETH_STRAT, slashedAmountGwei * GWEI_TO_WEI, "should have decreased withdrawable shares by slashed amount");
assert_Snap_Removed_ActiveValidatorCount(staker, slashedValidators.length, "should have decreased active validator count");
assert_Snap_Removed_ActiveValidators(staker, slashedValidators, "exited validators should each be WITHDRAWN");
assert_BCSF_Zero(staker, "BCSF should be 0");
assert_Snap_Unchanged_DSF(staker, BEACONCHAIN_ETH_STRAT.toArray(), "DSF should be unchanged");
assert_SlashableStake_Decrease_BCSlash(staker);
}

function check_CompleteCheckpoint_WithSlashing_HandleRoundDown_State(
User staker,
uint40[] memory slashedValidators,
Expand Down
32 changes: 26 additions & 6 deletions src/test/integration/mocks/BeaconChainMock.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,19 @@ contract BeaconChainMock is Logger {
uint64 exitEpoch;
}

/// @dev The type of slash to apply to a validator
enum SlashType {
Minor, // `MINOR_SLASH_AMOUNT_GWEI`
Half, // Half of the validator's balance
Full // The validator's entire balance
}

/// @dev All withdrawals are processed with index == 0
uint constant ZERO_NODES_LENGTH = 100;

// Rewards given to each validator during epoch processing
uint64 public constant CONSENSUS_REWARD_AMOUNT_GWEI = 1;
uint64 public constant SLASH_AMOUNT_GWEI = 10;
uint64 public constant MINOR_SLASH_AMOUNT_GWEI = 10;

/// PROOF CONSTANTS (PROOF LENGTHS, FIELD SIZES):

Expand Down Expand Up @@ -212,7 +219,7 @@ contract BeaconChainMock is Logger {
return exitedBalanceGwei;
}

function slashValidators(uint40[] memory _validators) public returns (uint64 slashedBalanceGwei) {
function slashValidators(uint40[] memory _validators, SlashType _slashType) public returns (uint64 slashedBalanceGwei) {
print.method("slashValidators");

for (uint i = 0; i < _validators.length; i++) {
Expand All @@ -225,19 +232,32 @@ contract BeaconChainMock is Logger {
v.isSlashed = true;
v.exitEpoch = currentEpoch() + 1;
}

// Calculate slash amount
uint64 slashAmountGwei;
uint64 curBalanceGwei = _currentBalanceGwei(validatorIndex);
if (SLASH_AMOUNT_GWEI > curBalanceGwei) {

if (_slashType == SlashType.Minor) {
slashAmountGwei = MINOR_SLASH_AMOUNT_GWEI;
} else if (_slashType == SlashType.Half) {
slashAmountGwei = curBalanceGwei / 2;
} else if (_slashType == SlashType.Full) {
slashAmountGwei = curBalanceGwei;
}

// Calculate slash amount
if (slashAmountGwei > curBalanceGwei) {
slashedBalanceGwei += curBalanceGwei;
curBalanceGwei = 0;
} else {
slashedBalanceGwei += SLASH_AMOUNT_GWEI;
curBalanceGwei -= SLASH_AMOUNT_GWEI;
slashedBalanceGwei += slashAmountGwei;
curBalanceGwei -= slashAmountGwei;
}

// Decrease current balance (effective balance updated during epoch processing)
_setCurrentBalance(validatorIndex, curBalanceGwei);

console.log(" - Slashed validator %s by %s gwei", validatorIndex, slashAmountGwei);
}

return slashedBalanceGwei;
Expand Down
58 changes: 58 additions & 0 deletions src/test/integration/tests/FullySlashed_EigenPod.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;

import "src/test/integration/IntegrationChecks.t.sol";

contract Integration_FullySlashedEigenpod is IntegrationCheckUtils {
using ArrayLib for *;

User staker;
IStrategy[] strategies;
uint[] initTokenBalances;
uint[] initDepositShares;
uint64 slashedGwei;

function _init() internal override {
_configAssetTypes(HOLDS_ETH);
(staker, strategies, initTokenBalances) = _newRandomStaker();

cheats.assume(initTokenBalances[0] >= 64 ether);

// Deposit staker
(uint40[] memory validators,) = staker.startValidators();
beaconChain.advanceEpoch_NoRewards();
staker.verifyWithdrawalCredentials(validators);
uint[] memory shares = _calculateExpectedShares(strategies, initTokenBalances);
initDepositShares = shares;
check_Deposit_State(staker, strategies, shares);

// Slash all validators fully
slashedGwei = beaconChain.slashValidators(validators, BeaconChainMock.SlashType.Full);
beaconChain.advanceEpoch_NoRewards(); // Withdraw slashed validators to pod

// Start & complete a checkpoint
staker.startCheckpoint();
check_StartCheckpoint_WithPodBalance_State(staker, 0);
staker.completeCheckpoint();
check_CompleteCheckpoint_FullySlashed_State(staker, validators, slashedGwei);
}

function test_fullSlash_Delegate(uint24 _rand) public rand(_rand) {
(User operator,,) = _newRandomOperator();

// Delegate to an operator - should succeed given that delegation only checks the operator's slashing factor
staker.delegateTo(operator);
check_Delegation_State(staker, operator, strategies, initDepositShares);
}

function test_fullSlash_Revert_Redeposit(uint24 _rand) public rand(_rand) {
// Start a new validator & verify withdrawal credentials
cheats.deal(address(staker), 32 ether);
(uint40[] memory newValidators, uint64 addedBeaconBalanceGwei) = staker.startValidators();
beaconChain.advanceEpoch_NoRewards();

// We should revert on verifyWithdrawalCredentials since the staker's slashing factor is 0
cheats.expectRevert(IDelegationManagerErrors.FullySlashed.selector);
staker.verifyWithdrawalCredentials(newValidators);
}
}
2 changes: 1 addition & 1 deletion src/test/integration/tests/Slashed_Eigenpod.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ contract Integration_SlashedEigenpod is IntegrationCheckUtils {
check_Deposit_State(staker, strategies, shares);

uint40[] memory slashedValidators = _choose(validators);
slashedGwei = beaconChain.slashValidators(slashedValidators);
slashedGwei = beaconChain.slashValidators(slashedValidators, BeaconChainMock.SlashType.Minor);
console.log(slashedGwei);
beaconChain.advanceEpoch_NoWithdrawNoRewards();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ contract Integration_VerifyWC_StartCP_CompleteCP is IntegrationCheckUtils {
(User staker, ,) = _newRandomStaker();

(uint40[] memory validators, ) = staker.startValidators();
beaconChain.slashValidators(validators);
beaconChain.slashValidators(validators, BeaconChainMock.SlashType.Minor);
// Advance epoch, withdrawing slashed validators to pod
beaconChain.advanceEpoch_NoRewards();

Expand All @@ -316,7 +316,7 @@ contract Integration_VerifyWC_StartCP_CompleteCP is IntegrationCheckUtils {
staker.verifyWithdrawalCredentials(validators);
check_VerifyWC_State(staker, validators, beaconBalanceGwei);

uint64 slashedBalanceGwei = beaconChain.slashValidators(validators);
uint64 slashedBalanceGwei = beaconChain.slashValidators(validators, BeaconChainMock.SlashType.Minor);
beaconChain.advanceEpoch_NoRewards();

staker.startCheckpoint();
Expand Down Expand Up @@ -348,7 +348,7 @@ contract Integration_VerifyWC_StartCP_CompleteCP is IntegrationCheckUtils {
staker.startCheckpoint();
check_StartCheckpoint_State(staker);

uint64 slashedBalanceGwei = beaconChain.slashValidators(validators);
uint64 slashedBalanceGwei = beaconChain.slashValidators(validators, BeaconChainMock.SlashType.Minor);
beaconChain.advanceEpoch_NoRewards();

staker.completeCheckpoint();
Expand Down Expand Up @@ -377,7 +377,7 @@ contract Integration_VerifyWC_StartCP_CompleteCP is IntegrationCheckUtils {
check_VerifyWC_State(staker, validators, beaconBalanceGwei);

// Slash validators
uint64 slashedBalanceGwei = beaconChain.slashValidators(validators);
uint64 slashedBalanceGwei = beaconChain.slashValidators(validators, BeaconChainMock.SlashType.Minor);
beaconChain.advanceEpoch_NoRewards();

// Start a checkpoint
Expand Down Expand Up @@ -414,7 +414,7 @@ contract Integration_VerifyWC_StartCP_CompleteCP is IntegrationCheckUtils {
staker.verifyWithdrawalCredentials(validators);
check_VerifyWC_State(staker, validators, beaconBalanceGwei);

uint64 slashedBalanceGwei = beaconChain.slashValidators(validators);
uint64 slashedBalanceGwei = beaconChain.slashValidators(validators, BeaconChainMock.SlashType.Minor);
beaconChain.advanceEpoch_NoRewards();

staker.verifyStaleBalance(validators[0]);
Expand All @@ -440,7 +440,7 @@ contract Integration_VerifyWC_StartCP_CompleteCP is IntegrationCheckUtils {
check_VerifyWC_State(staker, validators, beaconBalanceGwei);

// Slash validators but do not process exits to pod
uint64 slashedBalanceGwei = beaconChain.slashValidators(validators);
uint64 slashedBalanceGwei = beaconChain.slashValidators(validators, BeaconChainMock.SlashType.Minor);
beaconChain.advanceEpoch_NoWithdraw();

staker.verifyStaleBalance(validators[0]);
Expand All @@ -450,7 +450,7 @@ contract Integration_VerifyWC_StartCP_CompleteCP is IntegrationCheckUtils {
check_CompleteCheckpoint_WithCLSlashing_State(staker, slashedBalanceGwei);

// Slash validators again but do not process exits to pod
uint64 secondSlashedBalanceGwei = beaconChain.slashValidators(validators);
uint64 secondSlashedBalanceGwei = beaconChain.slashValidators(validators, BeaconChainMock.SlashType.Minor);
beaconChain.advanceEpoch_NoWithdraw();

staker.verifyStaleBalance(validators[0]);
Expand Down Expand Up @@ -482,7 +482,7 @@ contract Integration_VerifyWC_StartCP_CompleteCP is IntegrationCheckUtils {
staker.startCheckpoint();
check_StartCheckpoint_State(staker);

uint64 slashedBalanceGwei = beaconChain.slashValidators(validators);
uint64 slashedBalanceGwei = beaconChain.slashValidators(validators, BeaconChainMock.SlashType.Minor);
beaconChain.advanceEpoch_NoRewards();

staker.completeCheckpoint();
Expand Down
10 changes: 5 additions & 5 deletions src/test/unit/EigenPodUnit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1568,7 +1568,7 @@ contract EigenPodUnitTests_verifyStaleBalance is EigenPodUnitTests {
staker.verifyWithdrawalCredentials(validators);

// Slash validators and advance epoch
beaconChain.slashValidators(validators);
beaconChain.slashValidators(validators, BeaconChainMock.SlashType.Minor);
beaconChain.advanceEpoch();
StaleBalanceProofs memory proofs = beaconChain.getStaleBalanceProofs(validator);

Expand Down Expand Up @@ -1597,7 +1597,7 @@ contract EigenPodUnitTests_verifyStaleBalance is EigenPodUnitTests {
staker.verifyWithdrawalCredentials(validators);

// Slash validators and advance epoch
beaconChain.slashValidators(validators);
beaconChain.slashValidators(validators, BeaconChainMock.SlashType.Minor);
beaconChain.advanceEpoch();
StaleBalanceProofs memory proofs = beaconChain.getStaleBalanceProofs(validator);

Expand Down Expand Up @@ -1627,7 +1627,7 @@ contract EigenPodUnitTests_verifyStaleBalance is EigenPodUnitTests {
staker.verifyWithdrawalCredentials(validators);

// Slash validators and advance epoch
beaconChain.slashValidators(validators);
beaconChain.slashValidators(validators, BeaconChainMock.SlashType.Minor);
beaconChain.advanceEpoch();
StaleBalanceProofs memory proofs = beaconChain.getStaleBalanceProofs(validator);

Expand Down Expand Up @@ -1673,7 +1673,7 @@ contract EigenPodUnitTests_verifyStaleBalance is EigenPodUnitTests {
staker.verifyWithdrawalCredentials(validators);

// Slash validators and advance epoch
beaconChain.slashValidators(validators);
beaconChain.slashValidators(validators, BeaconChainMock.SlashType.Minor);
beaconChain.advanceEpoch();
StaleBalanceProofs memory proofs = beaconChain.getStaleBalanceProofs(validator);

Expand All @@ -1700,7 +1700,7 @@ contract EigenPodUnitTests_verifyStaleBalance is EigenPodUnitTests {
staker.verifyWithdrawalCredentials(validators);

// Slash validators and advance epoch
beaconChain.slashValidators(validators);
beaconChain.slashValidators(validators, BeaconChainMock.SlashType.Minor);
beaconChain.advanceEpoch();
StaleBalanceProofs memory proofs = beaconChain.getStaleBalanceProofs(validator);

Expand Down
Loading