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: bc/avs slash tests #1153

Open
wants to merge 11 commits into
base: test/slashing-integration-testing
Choose a base branch
from
Open
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
193 changes: 193 additions & 0 deletions src/test/integration/IntegrationBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,8 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
string memory err
) internal view {
EigenPod pod = staker.pod();
console.log("proofsRemaining: ", pod.currentCheckpoint().proofsRemaining);
console.log("activeValidatorCount: ", pod.activeValidatorCount());
assertEq(pod.currentCheckpoint().proofsRemaining, pod.activeValidatorCount(), err);
}

Expand Down Expand Up @@ -537,6 +539,29 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
assertEq(depositScalingFactors[i], WAD, err);
}
}

function assert_withdrawableSharesDecreasedByAtLeast(
User staker,
IStrategy[] memory strategies,
uint256[] memory originalShares,
uint256[] memory expectedDecreases,
string memory err
) internal view {
uint256[] memory currentShares = _getWithdrawableShares(staker, strategies);
for (uint i = 0; i < currentShares.length; i++) {
assertLt(currentShares[i], originalShares[i] - expectedDecreases[i], err);
}
}

function assert_withdrawableSharesDecreasedByAtLeast(
User staker,
IStrategy strategy,
uint256 originalShares,
uint256 expectedDecreases,
string memory err
) internal view {
return assert_withdrawableSharesDecreasedByAtLeast(staker, strategy.toArray(), originalShares.toArrayU256(), expectedDecreases.toArrayU256(), err);
}

/*******************************************************************************
SNAPSHOT ASSERTIONS
Expand Down Expand Up @@ -1243,6 +1268,127 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
}
}

/*******************************************************************************
SNAPSHOT ASSERTIONS: BEACON CHAIN AND AVS SLASHING
*******************************************************************************/
//TODO: handle all of these bounds properly

/// @dev Same as above, but when a BC slash occurs before an AVS slash
/// @dev There is additional rounding error when a BC and AVS slash occur together
function assert_Snap_StakerWithdrawableShares_AfterBCSlash_AVSSlash(
User staker,
AllocateParams memory allocateParams,
SlashingParams memory slashingParams,
string memory err
) internal {
require(allocateParams.strategies.length == 1 && slashingParams.strategies.length == 1, "only beacon strategy supported");
require(allocateParams.strategies[0] == BEACONCHAIN_ETH_STRAT, "only beacon strategy supported");
require(slashingParams.strategies[0] == BEACONCHAIN_ETH_STRAT, "only beacon strategy supported");

uint[] memory curShares = _getWithdrawableShares(staker, allocateParams.strategies);
uint[] memory prevShares = _getPrevWithdrawableShares(staker, allocateParams.strategies);

for (uint i = 0; i < allocateParams.strategies.length; i++) {
IStrategy strat = allocateParams.strategies[i];

uint256 slashedShares = 0;

if (slashingParams.strategies.contains(strat)) {
uint wadToSlash = slashingParams.wadsToSlash[slashingParams.strategies.indexOf(strat)];
slashedShares = prevShares[i].mulWadRoundUp(allocateParams.newMagnitudes[i].mulWadRoundUp(wadToSlash));
}

// TODO: bound this better
assertApproxEqAbs(prevShares[i] - slashedShares, curShares[i], 1e3, err);
}
}

/// @dev Validates behavior of "restaking", ie. that the funds can be slashed twice
function assert_Snap_StakerWithdrawableShares_AfterAVSSlash_BCSlash(
User staker,
AllocateParams memory allocateParams,
SlashingParams memory slashingParams,
string memory err
) internal {
require(allocateParams.strategies.length == 1 && slashingParams.strategies.length == 1, "only beacon strategy supported");
require(allocateParams.strategies[0] == BEACONCHAIN_ETH_STRAT, "only beacon strategy supported");
require(slashingParams.strategies[0] == BEACONCHAIN_ETH_STRAT, "only beacon strategy supported");

uint curShares = _getWithdrawableShares(staker, allocateParams.strategies)[0];
uint prevShares = _getPrevWithdrawableShares(staker, allocateParams.strategies)[0];
uint depositShares = _getStakerDepositShares(staker, allocateParams.strategies)[0];

// 1. The withdrawable shares should decrease by a factor of the BCSF
assertApproxEqAbs(prevShares.mulWad(_getBeaconChainSlashingFactor(staker)), curShares, 1e3, err);

/**
* 2. The delta in shares is given by:
* (depositShares * operatorMag) - (depositShares * operatorMag * BCSF)
* = depositShares * operatorMag * (1 - BCSF)
*/
uint beaconChainSlashingFactor = _getBeaconChainSlashingFactor(staker);
uint wadToSlash = slashingParams.wadsToSlash[0];
uint originalAVSSlashedShares = depositShares.mulWadRoundUp(allocateParams.newMagnitudes[0].mulWadRoundUp(wadToSlash));
uint withdrawableSharesAfterAVSSlash = depositShares - originalAVSSlashedShares;
uint expectedDelta = withdrawableSharesAfterAVSSlash.mulWad(WAD - beaconChainSlashingFactor);
assertApproxEqAbs(prevShares - expectedDelta, curShares, 1e3, err);

/**
* 3. The attributable avs slashed shares should decrease by a factor of the BCSF
* Attributable avs slashed shares = originalWithdrawableShares - bcSlashedShares - curShares
* Where bcSlashedShares = originalWithdrawableShares * (1 - BCSF)
*/
uint bcSlashedShares = depositShares.mulWad(WAD - beaconChainSlashingFactor);
uint attributableAVSSlashedShares = depositShares - bcSlashedShares - curShares;
assertApproxEqAbs(originalAVSSlashedShares.mulWad(beaconChainSlashingFactor), attributableAVSSlashedShares, 1e3, err);
}

/**
* @dev Validates behavior of "restaking", ie. that the funds can be slashed twice. Also validates
* the edge case where a validator is proven prior to the BC slash.
* @dev TODO: handle the bounds properly
*/
function assert_Snap_StakerWithdrawableShares_AVSSlash_ValidatorProven_BCSlash(
User staker,
uint256 originalWithdrawableShares,
uint256 extraValidatorShares,
AllocateParams memory allocateParams,
SlashingParams memory slashingParams,
string memory err
) internal {
require(allocateParams.strategies.length == 1 && slashingParams.strategies.length == 1, "only beacon strategy supported");
require(allocateParams.strategies[0] == BEACONCHAIN_ETH_STRAT, "only beacon strategy supported");
require(slashingParams.strategies[0] == BEACONCHAIN_ETH_STRAT, "only beacon strategy supported");

uint curShares = _getWithdrawableShares(staker, allocateParams.strategies)[0];
uint prevShares = _getPrevWithdrawableShares(staker, allocateParams.strategies)[0];

// 1. The withdrawable shares should decrease by a factor of the BCSF
assertApproxEqAbs(prevShares.mulWad(_getBeaconChainSlashingFactor(staker)), curShares, 1e5, err);

/**
* 2. The delta in shares is given by:
* (originalWithdrawableShares * operatorMag) + extraValidatorShares - (depositShares * operatorMag * BCSF * dsf)
*/
uint beaconChainSlashingFactor = _getBeaconChainSlashingFactor(staker);
uint wadToSlash = slashingParams.wadsToSlash[0];
uint originalAVSSlashedShares = originalWithdrawableShares.mulWadRoundUp(allocateParams.newMagnitudes[0].mulWadRoundUp(wadToSlash));
uint withdrawableSharesAfterValidatorProven = originalWithdrawableShares - originalAVSSlashedShares + extraValidatorShares;
uint expectedDelta = withdrawableSharesAfterValidatorProven.mulWad(WAD - beaconChainSlashingFactor);
assertApproxEqAbs(prevShares - expectedDelta, curShares, 1e5, err);

/**
* 3. The attributable avs slashed shares should decrease by a factor of the BCSF
* Attributable avs slashed shares = depositShares - bcSlashedShares - curShars
* Where bcSlashedShares = depositShares * (1 - BCSF)
*/
uint depositShares = _getStakerDepositShares(staker, allocateParams.strategies)[0];
uint bcSlashedShares = depositShares.mulWad(WAD - beaconChainSlashingFactor);
uint attributableAVSSlashedShares = depositShares - bcSlashedShares - curShares;
assertApproxEqAbs(originalAVSSlashedShares.mulWad(beaconChainSlashingFactor), attributableAVSSlashedShares, 1e5, err);
}


// TODO: slashable stake

/*******************************************************************************
Expand Down Expand Up @@ -1461,6 +1607,24 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
}
}

/// @dev This is currently used
/// TODO: Figure out how to bound this better
function assert_Snap_Added_Staker_WithdrawableSharesAtLeast(
User staker,
IStrategy[] memory strategies,
uint[] memory addedShares,
string memory err
) internal {
uint[] memory curShares = _getStakerWithdrawableShares(staker, strategies);
// Use timewarp to get previous staker shares
uint[] memory prevShares = _getPrevStakerWithdrawableShares(staker, strategies);

// For each strategy, check (prev - removed == cur)
for (uint i = 0; i < strategies.length; i++) {
assertApproxEqAbs(prevShares[i] + addedShares[i], curShares[i], 1e3, err);
}
}

/// @dev Check that the staker's withdrawable shares have decreased by `removedShares`
function assert_Snap_Removed_Staker_WithdrawableShares(
User staker,
Expand Down Expand Up @@ -1537,6 +1701,25 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
}
}

/// @dev Check that the staker's withdrawable shares have decreased by at least `removedShares`
/// @dev Used to handle overslashing of beacon chain with AVS slashings
function assert_Snap_Removed_Staker_WithdrawableShares_AtLeast(
User staker,
IStrategy[] memory strategies,
uint[] memory removedShares,
uint errBound,
string memory err
) internal {
uint[] memory curShares = _getStakerWithdrawableShares(staker, strategies);
// Use timewarp to get previous staker shares
uint[] memory prevShares = _getPrevStakerWithdrawableShares(staker, strategies);

// For each strategy, check diff between (prev-removed) and curr is at most 1 gwei
for (uint i = 0; i < strategies.length; i++) {
assertApproxEqAbs(prevShares[i] - removedShares[i], curShares[i], errBound, err);
}
}

function assert_Snap_Removed_Staker_WithdrawableShares_AtLeast(
User staker,
IStrategy strat,
Expand All @@ -1546,6 +1729,16 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
assert_Snap_Removed_Staker_WithdrawableShares_AtLeast(staker, strat.toArray(), removedShares.toArrayU256(), err);
}

function assert_Snap_Removed_Staker_WithdrawableShares_AtLeast(
User staker,
IStrategy strat,
uint removedShares,
uint errBound,
string memory err
) internal {
assert_Snap_Removed_Staker_WithdrawableShares_AtLeast(staker, strat.toArray(), removedShares.toArrayU256(), errBound, err);
}

function assert_Snap_Delta_StakerShares(
User staker,
IStrategy[] memory strategies,
Expand Down
61 changes: 60 additions & 1 deletion src/test/integration/IntegrationChecks.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import "src/test/integration/users/User_M2.t.sol";

/// @notice Contract that provides utility functions to reuse common test blocks & checks
contract IntegrationCheckUtils is IntegrationBase {
using ArrayLib for IStrategy[];
using ArrayLib for *;
using SlashingLib for *;
using StdStyle for *;

Expand Down Expand Up @@ -926,4 +926,63 @@ contract IntegrationCheckUtils is IntegrationBase {
assert_Snap_Removed_AllocatedSet(operator, allocateParams.operatorSet, "should not have updated allocated sets");
assert_Snap_Removed_AllocatedStrats(operator, allocateParams.operatorSet, slashParams.strategies, "should not have updated allocated strategies");
}

/*******************************************************************************
BC/AVS SLASHING CHECKS
*******************************************************************************/

function check_CompleteCheckPoint_AfterAVS_BCSlash(
User staker,
uint40[] memory slashedValidators,
uint256 depositShares,
uint64 slashedBalanceGwei,
AllocateParams memory allocateParams,
SlashingParams memory slashingParams
) internal {
// Checkpoint State
check_CompleteCheckpoint_State(staker);
assert_Snap_Unchanged_Staker_DepositShares(staker, "staker shares should not have decreased");
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");

// From the original shares to the BC slash, the shares should have decreased by at least the BC slash amount
assert_withdrawableSharesDecreasedByAtLeast(staker, BEACONCHAIN_ETH_STRAT, depositShares, uint256(slashedBalanceGwei * GWEI_TO_WEI), "should have decreased withdrawable shares by at least the BC slash amount");

// Calculate the withdrawable shares
assert_Snap_StakerWithdrawableShares_AfterAVSSlash_BCSlash(staker, allocateParams, slashingParams, "should have decreased withdrawable shares correctly");
}

function check_CompleteCheckpoint_AfterAVSSlash_ValidatorProven_BCSlash(
User staker,
uint40[] memory slashedValidators,
uint256 originalWithdrawableShares,
uint256 extraValidatorShares,
AllocateParams memory allocateParams,
SlashingParams memory slashingParams
) internal {
// Checkpoint State
check_CompleteCheckpoint_State(staker);
assert_Snap_Unchanged_Staker_DepositShares(staker, "staker shares should not have decreased");
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_Snap_StakerWithdrawableShares_AVSSlash_ValidatorProven_BCSlash(staker, originalWithdrawableShares, extraValidatorShares, allocateParams, slashingParams, "should have decreased withdrawable shares correctly");
}

/// @dev Assumes the eth deposit was greater than the amount slashed
function check_CompleteCheckpoint_AfterAVSSlash_ETHDeposit_BCSlash(
User staker,
uint40[] memory slashedValidators,
uint64 slashedBalanceGwei,
uint256 beaconSharesAddedGwei
) internal {
uint sharesAdded = uint(beaconSharesAddedGwei - slashedBalanceGwei) * GWEI_TO_WEI;
// Checkpoint State
check_CompleteCheckpoint_State(staker);
assert_Snap_Added_Staker_DepositShares(staker, BEACONCHAIN_ETH_STRAT, sharesAdded, "staker deposit shares should have increased");
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_Snap_Added_Staker_WithdrawableSharesAtLeast(staker, BEACONCHAIN_ETH_STRAT.toArray(), sharesAdded.toArrayU256(), "staker withdrawable shares should increase by diff of deposit and slash");
}
}
Loading
Loading