From 1fde13ae51efc33816f48e8b67579569521b05d8 Mon Sep 17 00:00:00 2001 From: Yash Patil <40046473+ypatil12@users.noreply.github.com> Date: Thu, 20 Feb 2025 11:50:57 -0500 Subject: [PATCH 01/11] chore: push --- src/test/integration/IntegrationBase.t.sol | 32 ++++++ src/test/integration/IntegrationChecks.t.sol | 24 ++++- .../integration/tests/BC_AVS_Slashing.t.sol | 99 +++++++++++++++++++ 3 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 src/test/integration/tests/BC_AVS_Slashing.t.sol diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index f140467a1..fc74ea89e 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -537,6 +537,38 @@ 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); + } + + function assert_withdrawbleShares_bc_AVS_Slash_State( + User staker, + IStrategy strategy, + uint256 expectedDecreases, + string memory err + ) internal view { + } + /******************************************************************************* SNAPSHOT ASSERTIONS diff --git a/src/test/integration/IntegrationChecks.t.sol b/src/test/integration/IntegrationChecks.t.sol index 0174f653b..c1d687003 100644 --- a/src/test/integration/IntegrationChecks.t.sol +++ b/src/test/integration/IntegrationChecks.t.sol @@ -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 *; @@ -926,4 +926,26 @@ 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_AVSSLash_BCSLash_HandleRoundDown_State( + User staker, + uint40[] memory slashedValidators, + uint256 originalWithdrawableShares, + uint64 slashedBalanceGwei + ) 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"); + + // Between the AVS slash and the BC slash, the shares should have decreased by at least the BC slash amount + assert_withdrawableSharesDecreasedByAtLeast(staker, BEACONCHAIN_ETH_STRAT, originalWithdrawableShares, uint256(slashedBalanceGwei * GWEI_TO_WEI), "should have decreased withdrawable shares by at least the BC slash amount"); + + assert_withdrawbleShare + } } diff --git a/src/test/integration/tests/BC_AVS_Slashing.t.sol b/src/test/integration/tests/BC_AVS_Slashing.t.sol new file mode 100644 index 000000000..38aefe615 --- /dev/null +++ b/src/test/integration/tests/BC_AVS_Slashing.t.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "src/test/integration/IntegrationChecks.t.sol"; + +contract Integration_BeaconChain_AVS_Ordering is IntegrationCheckUtils { + using ArrayLib for *; + + AVS avs; + OperatorSet operatorSet; + + User operator; + AllocateParams allocateParams; + + User staker; + uint64 beaconBalanceGwei; + uint40[] validators; + IStrategy[] strategies; + uint[] initTokenBalances; + uint[] initDepositShares; + + function _init() internal override { + _configAssetTypes(HOLDS_ETH); + + // Create staker, operator, and avs + (staker, strategies, initTokenBalances) = _newRandomStaker(); + (operator,,) = _newRandomOperator(); + (avs,) = _newRandomAVS(); + + // 1. Deposit into strategies + (validators, beaconBalanceGwei) = staker.startValidators(); + beaconChain.advanceEpoch_NoRewards(); + staker.verifyWithdrawalCredentials(validators); + initDepositShares = _calculateExpectedShares(strategies, initTokenBalances); + check_Deposit_State(staker, strategies, initDepositShares); + + // 2. Delegate staker to operator + staker.delegateTo(operator); + check_Delegation_State(staker, operator, strategies, initDepositShares); + + // 3. Create an operator set containing the strategies held by the staker + operatorSet = avs.createOperatorSet(strategies); + + // 4. Register operator to AVS + operator.registerForOperatorSet(operatorSet); + check_Registration_State_NoAllocation(operator, operatorSet, allStrats); + + // 5. Allocate to the operator set + allocateParams = _genAllocation_AllAvailable(operator, operatorSet); + operator.modifyAllocations(allocateParams); + check_IncrAlloc_State_Slashable(operator, allocateParams); + + _rollBlocksForCompleteAllocation(operator, operatorSet, strategies); + } + + function testFuzz_avsSlash_bcSlash_checkpoint(uint24 _random) public rand(_random){ + // 6. Slash Operator by AVS + SlashingParams memory slashingParams; + { + (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = + _randStrategiesAndWadsToSlash(operatorSet); + console.log("Strategies to slash: %s", strategiesToSlash.length); + slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); + assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); + assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing"); + assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker deposit shares should be slashed"); + } + uint[] memory sharesGotten = _getWithdrawableShares(staker, strategies); + console.log("Shares after avs slash: ", sharesGotten[0]); + + // 7. Slash Staker on BC + uint64 slashedBalanceGwei = beaconChain.slashValidators(validators); + beaconChain.advanceEpoch_NoRewards(); + + console.log("slashedBalanceGwei: ", slashedBalanceGwei); + + // 8. Checkpoint + staker.startCheckpoint(); + check_StartCheckpoint_WithPodBalance_State(staker, beaconBalanceGwei - slashedBalanceGwei); + staker.completeCheckpoint(); + uint64[] memory maxMagnitudes = _getMaxMagnitudes(operator, strategies); + console.log("maxMagnitudes: ", maxMagnitudes[0]); + uint64 bcsf = _getBeaconChainSlashingFactor(staker); + console.log("bcsf: ", bcsf); + sharesGotten = _getWithdrawableShares(staker, strategies); + console.log("Shares after bc slash: ", sharesGotten[0]); + check_CompleteCheckPoint_AVSSLash_BCSLash_HandleRoundDown_State(staker, validators, initDepositShares[0], slashedBalanceGwei); + + + // sharesGotten = _getWithdrawableShares(staker, strategies); + // console.log("Shares after bc slash: ", sharesGotten[0]); + // console.log("Test test test"); + + // Assert state + // assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing"); + // assert_Snap_Removed_Staker_WithdrawableShares_AtLeast(staker, BEACONCHAIN_ETH_STRAT, slashedBalanceGwei * GWEI_TO_WEI, "should have decreased withdrawable shares by slashed amount"); + } + +} \ No newline at end of file From 981cfc33d1ad790e066cfac69d24632d43bf8622 Mon Sep 17 00:00:00 2001 From: Yash Patil <40046473+ypatil12@users.noreply.github.com> Date: Thu, 20 Feb 2025 12:15:24 -0500 Subject: [PATCH 02/11] chore: push --- src/test/integration/IntegrationBase.t.sol | 33 ++++++++++++++------ src/test/integration/IntegrationChecks.t.sol | 6 ++-- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index fc74ea89e..b411000f3 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -560,15 +560,6 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { ) internal view { return assert_withdrawableSharesDecreasedByAtLeast(staker, strategy.toArray(), originalShares.toArrayU256(), expectedDecreases.toArrayU256(), err); } - - function assert_withdrawbleShares_bc_AVS_Slash_State( - User staker, - IStrategy strategy, - uint256 expectedDecreases, - string memory err - ) internal view { - } - /******************************************************************************* SNAPSHOT ASSERTIONS @@ -1275,6 +1266,30 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { } } + function assert_Snap_StakerWithdrawableShares_AfterBCAndAVSSlash( + User staker, + AllocateParams memory allocateParams, + SlashingParams memory slashingParams, + uint64 slashedBalanceGwei, + string memory err + ) internal { + 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)); + } + + assertApproxEqAbs(prevShares[i] - slashedShares, curShares[i], 1, err); + } + } + // TODO: slashable stake /******************************************************************************* diff --git a/src/test/integration/IntegrationChecks.t.sol b/src/test/integration/IntegrationChecks.t.sol index c1d687003..5606bfa2b 100644 --- a/src/test/integration/IntegrationChecks.t.sol +++ b/src/test/integration/IntegrationChecks.t.sol @@ -935,7 +935,9 @@ contract IntegrationCheckUtils is IntegrationBase { User staker, uint40[] memory slashedValidators, uint256 originalWithdrawableShares, - uint64 slashedBalanceGwei + uint64 slashedBalanceGwei, + AllocateParams memory allocateParams, + SlashingParams memory slashingParams ) internal { // Checkpoint State check_CompleteCheckpoint_State(staker); @@ -946,6 +948,6 @@ contract IntegrationCheckUtils is IntegrationBase { // Between the AVS slash and the BC slash, the shares should have decreased by at least the BC slash amount assert_withdrawableSharesDecreasedByAtLeast(staker, BEACONCHAIN_ETH_STRAT, originalWithdrawableShares, uint256(slashedBalanceGwei * GWEI_TO_WEI), "should have decreased withdrawable shares by at least the BC slash amount"); - assert_withdrawbleShare + // } } From b9d3bd62afb989411f70920b206b67f1ae357fff Mon Sep 17 00:00:00 2001 From: Yash Patil <40046473+ypatil12@users.noreply.github.com> Date: Thu, 20 Feb 2025 23:03:55 -0500 Subject: [PATCH 03/11] test: 24-25 --- src/test/integration/IntegrationBase.t.sol | 36 ++++++++++++- src/test/integration/IntegrationChecks.t.sol | 7 ++- .../integration/tests/BC_AVS_Slashing.t.sol | 51 ++++++++++--------- 3 files changed, 67 insertions(+), 27 deletions(-) diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index b411000f3..76c58d094 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -1266,11 +1266,42 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { } } + function assert_Snap_StakerWithdrawableShares_AfterAVSAndBCSlash( + User staker, + uint256 originalWithdrawableShares, + AllocateParams memory allocateParams, + SlashingParams memory slashingParams, + string memory err + ) internal { + uint[] memory curShares = _getWithdrawableShares(staker, allocateParams.strategies); + uint[] memory prevShares = _getPrevWithdrawableShares(staker, allocateParams.strategies); + + // Only the BC slash is considered for + for (uint i = 0; i < allocateParams.strategies.length; i++) { + require(allocateParams.strategies.length == 1, "only beacon strategy supported"); + IStrategy strat = allocateParams.strategies[i]; + + uint256 avsSlashedShares = 0; + + if (slashingParams.strategies.contains(strat)) { + uint wadToSlash = slashingParams.wadsToSlash[slashingParams.strategies.indexOf(strat)]; + avsSlashedShares = originalWithdrawableShares.mulWadRoundUp(allocateParams.newMagnitudes[i].mulWadRoundUp(wadToSlash)); + } + + uint256 postAVSSlashShares = originalWithdrawableShares - avsSlashedShares; + + uint256 expectedDelta = postAVSSlashShares.mulWad(WAD - _getBeaconChainSlashingFactor(staker)); + + // TODO: bound this better + assertApproxEqAbs(prevShares[i] - expectedDelta, curShares[i], 1e4, err); + } + } + + // Same as above, but when a BC slash occurs before an AVS slash function assert_Snap_StakerWithdrawableShares_AfterBCAndAVSSlash( User staker, AllocateParams memory allocateParams, SlashingParams memory slashingParams, - uint64 slashedBalanceGwei, string memory err ) internal { uint[] memory curShares = _getWithdrawableShares(staker, allocateParams.strategies); @@ -1286,7 +1317,8 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { slashedShares = prevShares[i].mulWadRoundUp(allocateParams.newMagnitudes[i].mulWadRoundUp(wadToSlash)); } - assertApproxEqAbs(prevShares[i] - slashedShares, curShares[i], 1, err); + // TODO: bound this better + assertApproxEqAbs(prevShares[i] - slashedShares, curShares[i], 1e4, err); } } diff --git a/src/test/integration/IntegrationChecks.t.sol b/src/test/integration/IntegrationChecks.t.sol index 5606bfa2b..8ad685c3b 100644 --- a/src/test/integration/IntegrationChecks.t.sol +++ b/src/test/integration/IntegrationChecks.t.sol @@ -931,7 +931,7 @@ contract IntegrationCheckUtils is IntegrationBase { BC/AVS SLASHING CHECKS *******************************************************************************/ - function check_CompleteCheckPoint_AVSSLash_BCSLash_HandleRoundDown_State( + function check_CompleteCheckPoint_AfterAVSAndBCSlash( User staker, uint40[] memory slashedValidators, uint256 originalWithdrawableShares, @@ -948,6 +948,9 @@ contract IntegrationCheckUtils is IntegrationBase { // Between the AVS slash and the BC slash, the shares should have decreased by at least the BC slash amount assert_withdrawableSharesDecreasedByAtLeast(staker, BEACONCHAIN_ETH_STRAT, originalWithdrawableShares, uint256(slashedBalanceGwei * GWEI_TO_WEI), "should have decreased withdrawable shares by at least the BC slash amount"); - // + // Calculate the withdrawable shares + assert_Snap_StakerWithdrawableShares_AfterAVSAndBCSlash(staker, originalWithdrawableShares, allocateParams, slashingParams, "should have decreased withdrawable shares correctly"); } + + } diff --git a/src/test/integration/tests/BC_AVS_Slashing.t.sol b/src/test/integration/tests/BC_AVS_Slashing.t.sol index 38aefe615..510c853ec 100644 --- a/src/test/integration/tests/BC_AVS_Slashing.t.sol +++ b/src/test/integration/tests/BC_AVS_Slashing.t.sol @@ -63,37 +63,42 @@ contract Integration_BeaconChain_AVS_Ordering is IntegrationCheckUtils { slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing"); - assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker deposit shares should be slashed"); + assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker withdrawable shares should be slashed"); } - uint[] memory sharesGotten = _getWithdrawableShares(staker, strategies); - console.log("Shares after avs slash: ", sharesGotten[0]); // 7. Slash Staker on BC - uint64 slashedBalanceGwei = beaconChain.slashValidators(validators); + uint64 slashedAmountGwei = beaconChain.slashValidators(validators); beaconChain.advanceEpoch_NoRewards(); - console.log("slashedBalanceGwei: ", slashedBalanceGwei); - // 8. Checkpoint staker.startCheckpoint(); - check_StartCheckpoint_WithPodBalance_State(staker, beaconBalanceGwei - slashedBalanceGwei); + check_StartCheckpoint_WithPodBalance_State(staker, beaconBalanceGwei - slashedAmountGwei); staker.completeCheckpoint(); - uint64[] memory maxMagnitudes = _getMaxMagnitudes(operator, strategies); - console.log("maxMagnitudes: ", maxMagnitudes[0]); - uint64 bcsf = _getBeaconChainSlashingFactor(staker); - console.log("bcsf: ", bcsf); - sharesGotten = _getWithdrawableShares(staker, strategies); - console.log("Shares after bc slash: ", sharesGotten[0]); - check_CompleteCheckPoint_AVSSLash_BCSLash_HandleRoundDown_State(staker, validators, initDepositShares[0], slashedBalanceGwei); - - - // sharesGotten = _getWithdrawableShares(staker, strategies); - // console.log("Shares after bc slash: ", sharesGotten[0]); - // console.log("Test test test"); - - // Assert state - // assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing"); - // assert_Snap_Removed_Staker_WithdrawableShares_AtLeast(staker, BEACONCHAIN_ETH_STRAT, slashedBalanceGwei * GWEI_TO_WEI, "should have decreased withdrawable shares by slashed amount"); + check_CompleteCheckPoint_AfterAVSAndBCSlash(staker, validators, initDepositShares[0], slashedAmountGwei, allocateParams, slashingParams); + } + + function testFuzz_bcSlash_checkpoint_avsSlash(uint24 _random) public rand(_random) { + // 6. Slash staker on BC + uint64 slashedAmountGwei = beaconChain.slashValidators(validators); + beaconChain.advanceEpoch_NoRewards(); + + // 7. Checkpoint + staker.startCheckpoint(); + check_StartCheckpoint_WithPodBalance_State(staker, beaconBalanceGwei - slashedAmountGwei); + staker.completeCheckpoint(); + check_CompleteCheckpoint_WithSlashing_State(staker, validators, slashedAmountGwei); + + // 8. Slash operator by AVS + SlashingParams memory slashingParams; + { + (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = + _randStrategiesAndWadsToSlash(operatorSet); + console.log("Strategies to slash: %s", strategiesToSlash.length); + slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); + assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); + assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing"); + assert_Snap_StakerWithdrawableShares_AfterBCAndAVSSlash(staker, allocateParams, slashingParams, "staker withdrawable shares should be slashed"); + } } } \ No newline at end of file From fa914ed75dd1baedec47b823d203434932b6c4fe Mon Sep 17 00:00:00 2001 From: Yash Patil <40046473+ypatil12@users.noreply.github.com> Date: Thu, 20 Feb 2025 23:49:37 -0500 Subject: [PATCH 04/11] test: t-26 --- src/test/integration/IntegrationBase.t.sol | 51 ++++++++++++++++++- src/test/integration/IntegrationChecks.t.sol | 16 +++++- .../integration/tests/BC_AVS_Slashing.t.sol | 32 ++++++++++++ 3 files changed, 95 insertions(+), 4 deletions(-) diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index 76c58d094..88b217cf2 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -1293,7 +1293,7 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { uint256 expectedDelta = postAVSSlashShares.mulWad(WAD - _getBeaconChainSlashingFactor(staker)); // TODO: bound this better - assertApproxEqAbs(prevShares[i] - expectedDelta, curShares[i], 1e4, err); + assertApproxEqAbs(prevShares[i] - expectedDelta, curShares[i], 1e3, err); } } @@ -1318,7 +1318,7 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { } // TODO: bound this better - assertApproxEqAbs(prevShares[i] - slashedShares, curShares[i], 1e4, err); + assertApproxEqAbs(prevShares[i] - slashedShares, curShares[i], 1e3, err); } } @@ -1540,6 +1540,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, @@ -1616,6 +1634,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, @@ -1625,6 +1662,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, diff --git a/src/test/integration/IntegrationChecks.t.sol b/src/test/integration/IntegrationChecks.t.sol index 8ad685c3b..f1024d5b8 100644 --- a/src/test/integration/IntegrationChecks.t.sol +++ b/src/test/integration/IntegrationChecks.t.sol @@ -950,7 +950,19 @@ contract IntegrationCheckUtils is IntegrationBase { // Calculate the withdrawable shares assert_Snap_StakerWithdrawableShares_AfterAVSAndBCSlash(staker, originalWithdrawableShares, allocateParams, slashingParams, "should have decreased withdrawable shares correctly"); - } + } - + function check_CompleteCheckpoint_WithAVSAndBCSlashing_HandleRoundDown_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"); + // TODO: bound this better + assert_Snap_Removed_Staker_WithdrawableShares_AtLeast(staker, BEACONCHAIN_ETH_STRAT, slashedAmountGwei * GWEI_TO_WEI, 1e11, "should have decreased withdrawable shares by at least 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"); + } } diff --git a/src/test/integration/tests/BC_AVS_Slashing.t.sol b/src/test/integration/tests/BC_AVS_Slashing.t.sol index 510c853ec..1f09de8e6 100644 --- a/src/test/integration/tests/BC_AVS_Slashing.t.sol +++ b/src/test/integration/tests/BC_AVS_Slashing.t.sol @@ -101,4 +101,36 @@ contract Integration_BeaconChain_AVS_Ordering is IntegrationCheckUtils { } } + function testFuzz_avsSlash_verifyValidator_bcSlash_checkpoint(uint24 _random) public rand(_random) { + // 6. Slash operator by AVS + SlashingParams memory slashingParams; + { + (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = + _randStrategiesAndWadsToSlash(operatorSet); + console.log("Strategies to slash: %s", strategiesToSlash.length); + slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); + assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); + assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing"); + assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker withdrawable shares should be slashed"); + } + + // 7. Verify Validator + cheats.deal(address(staker), 32 ether); + (uint40[] memory newValidators, uint64 addedBeaconBalanceGwei) = staker.startValidators(); + beaconChain.advanceEpoch_NoRewards(); + staker.verifyWithdrawalCredentials(newValidators); + check_VerifyWC_State(staker, newValidators, addedBeaconBalanceGwei); + assert_Snap_Added_Staker_WithdrawableSharesAtLeast(staker, BEACONCHAIN_ETH_STRAT.toArray(), uint256(addedBeaconBalanceGwei * GWEI_TO_WEI).toArrayU256(), "staker withdrawable shares should increase by the added beacon balance"); + + // 8. Slash first validators on BC + uint64 slashedAmountGwei = beaconChain.slashValidators(validators); + beaconChain.advanceEpoch_NoRewards(); + + // 9. Checkpoint + staker.startCheckpoint(); + check_StartCheckpoint_WithPodBalance_State(staker, beaconBalanceGwei - slashedAmountGwei); + staker.completeCheckpoint(); + check_CompleteCheckpoint_WithAVSAndBCSlashing_HandleRoundDown_State(staker, validators, slashedAmountGwei); + } + } \ No newline at end of file From f06e5f0ae1d7e6e7e6be09be16e2cb5bb32f6f5e Mon Sep 17 00:00:00 2001 From: Yash Patil <40046473+ypatil12@users.noreply.github.com> Date: Fri, 21 Feb 2025 09:53:55 -0500 Subject: [PATCH 05/11] test: push --- src/test/integration/IntegrationBase.t.sol | 68 ++++++++++++------- src/test/integration/IntegrationChecks.t.sol | 13 ++-- .../integration/tests/BC_AVS_Slashing.t.sol | 47 +++++++++++-- 3 files changed, 91 insertions(+), 37 deletions(-) diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index 88b217cf2..5002a05ca 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -1266,60 +1266,78 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { } } - function assert_Snap_StakerWithdrawableShares_AfterAVSAndBCSlash( + /******************************************************************************* + SNAPSHOT ASSERTIONS: BEACON CHAIN AND AVS SLASHING + *******************************************************************************/ + + /// @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_AfterBCAndAVSSlash( User staker, - uint256 originalWithdrawableShares, 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); - // Only the BC slash is considered for for (uint i = 0; i < allocateParams.strategies.length; i++) { - require(allocateParams.strategies.length == 1, "only beacon strategy supported"); IStrategy strat = allocateParams.strategies[i]; - uint256 avsSlashedShares = 0; + uint256 slashedShares = 0; if (slashingParams.strategies.contains(strat)) { uint wadToSlash = slashingParams.wadsToSlash[slashingParams.strategies.indexOf(strat)]; - avsSlashedShares = originalWithdrawableShares.mulWadRoundUp(allocateParams.newMagnitudes[i].mulWadRoundUp(wadToSlash)); + slashedShares = prevShares[i].mulWadRoundUp(allocateParams.newMagnitudes[i].mulWadRoundUp(wadToSlash)); } - uint256 postAVSSlashShares = originalWithdrawableShares - avsSlashedShares; - - uint256 expectedDelta = postAVSSlashShares.mulWad(WAD - _getBeaconChainSlashingFactor(staker)); - // TODO: bound this better - assertApproxEqAbs(prevShares[i] - expectedDelta, curShares[i], 1e3, err); + assertApproxEqAbs(prevShares[i] - slashedShares, curShares[i], 1e3, err); } } - // Same as above, but when a BC slash occurs before an AVS slash - function assert_Snap_StakerWithdrawableShares_AfterBCAndAVSSlash( + /// @dev Validates behavior of "restaking", ie. that the funds can be slashed twice + function assert_Snap_StakerWithdrawableShares_AfterAVSAndBCSlash( User staker, + uint256 originalWithdrawableShares, AllocateParams memory allocateParams, SlashingParams memory slashingParams, string memory err ) internal { - uint[] memory curShares = _getWithdrawableShares(staker, allocateParams.strategies); - uint[] memory prevShares = _getPrevWithdrawableShares(staker, allocateParams.strategies); + 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"); - for (uint i = 0; i < allocateParams.strategies.length; i++) { - IStrategy strat = allocateParams.strategies[i]; + uint curShares = _getWithdrawableShares(staker, allocateParams.strategies)[0]; + uint prevShares = _getPrevWithdrawableShares(staker, allocateParams.strategies)[0]; - uint256 slashedShares = 0; + // 1. The withdrawable shares should decrease by a factor of the BCSF + assertApproxEqAbs(prevShares.mulWad(_getBeaconChainSlashingFactor(staker)), curShares, 1, err); - if (slashingParams.strategies.contains(strat)) { - uint wadToSlash = slashingParams.wadsToSlash[slashingParams.strategies.indexOf(strat)]; - slashedShares = prevShares[i].mulWadRoundUp(allocateParams.newMagnitudes[i].mulWadRoundUp(wadToSlash)); - } + // for (uint i = 0; i < allocateParams.strategies.length; i++) { + // IStrategy strat = allocateParams.strategies[i]; - // TODO: bound this better - assertApproxEqAbs(prevShares[i] - slashedShares, curShares[i], 1e3, err); - } + // uint256 avsSlashedShares = 0; + + // if (slashingParams.strategies.contains(strat)) { + // uint wadToSlash = slashingParams.wadsToSlash[slashingParams.strategies.indexOf(strat)]; + // avsSlashedShares = originalWithdrawableShares.mulWadRoundUp(allocateParams.newMagnitudes[i].mulWadRoundUp(wadToSlash)); + // } + + // uint256 postAVSSlashShares = originalWithdrawableShares - avsSlashedShares; + + // uint256 expectedDelta = postAVSSlashShares.mulWad(WAD - _getBeaconChainSlashingFactor(staker)); + + // // TODO: bound this better + // assertApproxEqAbs(prevShares[i] - expectedDelta, curShares[i], 1e3, err); + + // // Prev shares should decrease by a factor of the BCSF + // assertApproxEqAbs(prevShares[i].mulWad(_getBeaconChainSlashingFactor(staker)), curShares[i], 1e3, err); + // } } // TODO: slashable stake diff --git a/src/test/integration/IntegrationChecks.t.sol b/src/test/integration/IntegrationChecks.t.sol index f1024d5b8..bee39f651 100644 --- a/src/test/integration/IntegrationChecks.t.sol +++ b/src/test/integration/IntegrationChecks.t.sol @@ -950,19 +950,22 @@ contract IntegrationCheckUtils is IntegrationBase { // Calculate the withdrawable shares assert_Snap_StakerWithdrawableShares_AfterAVSAndBCSlash(staker, originalWithdrawableShares, allocateParams, slashingParams, "should have decreased withdrawable shares correctly"); - } + } - function check_CompleteCheckpoint_WithAVSAndBCSlashing_HandleRoundDown_State( + function check_CompleteCheckpoint_AfterAVSAndBCSlash_ExtraValidatorProven( User staker, uint40[] memory slashedValidators, + uint256 originalWithdrawableShares, uint64 slashedAmountGwei ) internal { + // Checkpoint State check_CompleteCheckpoint_State(staker); - assert_Snap_Unchanged_Staker_DepositShares(staker, "staker shares should not have decreased"); - // TODO: bound this better - assert_Snap_Removed_Staker_WithdrawableShares_AtLeast(staker, BEACONCHAIN_ETH_STRAT, slashedAmountGwei * GWEI_TO_WEI, 1e11, "should have decreased withdrawable shares by at least 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"); + + // TODO: bound this better + // assert_Snap_StakerWithdrawableShares_AfterAVSAndBCSlash_ExtraValidatorProven(staker, originalWithdrawableShares, allocateParams, slashingParams, "should have decreased withdrawable shares correctly"); + // assert_Snap_Removed_Staker_WithdrawableShares_AtLeast(staker, BEACONCHAIN_ETH_STRAT, slashedAmountGwei * GWEI_TO_WEI, 1e11, "should have decreased withdrawable shares by at least slashed amount"); } } diff --git a/src/test/integration/tests/BC_AVS_Slashing.t.sol b/src/test/integration/tests/BC_AVS_Slashing.t.sol index 1f09de8e6..cef979369 100644 --- a/src/test/integration/tests/BC_AVS_Slashing.t.sol +++ b/src/test/integration/tests/BC_AVS_Slashing.t.sol @@ -53,6 +53,7 @@ contract Integration_BeaconChain_AVS_Ordering is IntegrationCheckUtils { _rollBlocksForCompleteAllocation(operator, operatorSet, strategies); } + /// @dev Validates behavior of "restaking", ie. that the funds can be slashed twice function testFuzz_avsSlash_bcSlash_checkpoint(uint24 _random) public rand(_random){ // 6. Slash Operator by AVS SlashingParams memory slashingParams; @@ -101,7 +102,39 @@ contract Integration_BeaconChain_AVS_Ordering is IntegrationCheckUtils { } } - function testFuzz_avsSlash_verifyValidator_bcSlash_checkpoint(uint24 _random) public rand(_random) { + // function testFuzz_avsSlash_verifyValidator_bcSlash_checkpoint(uint24 _random) public rand(_random) { + // // 6. Slash operator by AVS + // SlashingParams memory slashingParams; + // { + // (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = + // _randStrategiesAndWadsToSlash(operatorSet); + // console.log("Strategies to slash: %s", strategiesToSlash.length); + // slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); + // assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); + // assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing"); + // assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker withdrawable shares should be slashed"); + // } + + // // 7. Verify Validator + // cheats.deal(address(staker), 32 ether); + // (uint40[] memory newValidators, uint64 addedBeaconBalanceGwei) = staker.startValidators(); + // beaconChain.advanceEpoch_NoRewards(); + // staker.verifyWithdrawalCredentials(newValidators); + // check_VerifyWC_State(staker, newValidators, addedBeaconBalanceGwei); + // assert_Snap_Added_Staker_WithdrawableSharesAtLeast(staker, BEACONCHAIN_ETH_STRAT.toArray(), uint256(addedBeaconBalanceGwei * GWEI_TO_WEI).toArrayU256(), "staker withdrawable shares should increase by the added beacon balance"); + + // // 8. Slash first validators on BC + // uint64 slashedAmountGwei = beaconChain.slashValidators(validators); + // beaconChain.advanceEpoch_NoRewards(); + + // // 9. Checkpoint + // staker.startCheckpoint(); + // check_StartCheckpoint_WithPodBalance_State(staker, beaconBalanceGwei - slashedAmountGwei); + // staker.completeCheckpoint(); + // check_CompleteCheckpoint_WithAVSAndBCSlashing_HandleRoundDown_State(staker, validators, slashedAmountGwei); + // } + + function testFuzz_avsSlash_bcSlash_verifyValidator_checkpoint(uint24 _random) public rand(_random) { // 6. Slash operator by AVS SlashingParams memory slashingParams; { @@ -114,7 +147,11 @@ contract Integration_BeaconChain_AVS_Ordering is IntegrationCheckUtils { assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker withdrawable shares should be slashed"); } - // 7. Verify Validator + // 7. Slash Staker on BC + uint64 slashedAmountGwei = beaconChain.slashValidators(validators); + beaconChain.advanceEpoch_NoRewards(); + + // 8. Verify Validator cheats.deal(address(staker), 32 ether); (uint40[] memory newValidators, uint64 addedBeaconBalanceGwei) = staker.startValidators(); beaconChain.advanceEpoch_NoRewards(); @@ -122,15 +159,11 @@ contract Integration_BeaconChain_AVS_Ordering is IntegrationCheckUtils { check_VerifyWC_State(staker, newValidators, addedBeaconBalanceGwei); assert_Snap_Added_Staker_WithdrawableSharesAtLeast(staker, BEACONCHAIN_ETH_STRAT.toArray(), uint256(addedBeaconBalanceGwei * GWEI_TO_WEI).toArrayU256(), "staker withdrawable shares should increase by the added beacon balance"); - // 8. Slash first validators on BC - uint64 slashedAmountGwei = beaconChain.slashValidators(validators); - beaconChain.advanceEpoch_NoRewards(); - // 9. Checkpoint staker.startCheckpoint(); check_StartCheckpoint_WithPodBalance_State(staker, beaconBalanceGwei - slashedAmountGwei); staker.completeCheckpoint(); - check_CompleteCheckpoint_WithAVSAndBCSlashing_HandleRoundDown_State(staker, validators, slashedAmountGwei); + check_CompleteCheckpoint_AfterAVSAndBCSlash_ExtraValidatorProven(staker, validators, initDepositShares[0], slashedAmountGwei); } } \ No newline at end of file From 3a150184ae899a33c4f41514007946ee3dc619c3 Mon Sep 17 00:00:00 2001 From: Yash Patil <40046473+ypatil12@users.noreply.github.com> Date: Fri, 21 Feb 2025 13:20:56 -0500 Subject: [PATCH 06/11] feat: fix t-26 and t-27 --- src/test/integration/IntegrationBase.t.sol | 91 ++++++++++++++----- src/test/integration/IntegrationChecks.t.sol | 19 ++-- .../integration/tests/BC_AVS_Slashing.t.sol | 74 +++++++-------- 3 files changed, 117 insertions(+), 67 deletions(-) diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index 5002a05ca..0e017c055 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -1269,10 +1269,11 @@ 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_AfterBCAndAVSSlash( + function assert_Snap_StakerWithdrawableShares_AfterBCSlash_AVSSlash( User staker, AllocateParams memory allocateParams, SlashingParams memory slashingParams, @@ -1301,9 +1302,8 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { } /// @dev Validates behavior of "restaking", ie. that the funds can be slashed twice - function assert_Snap_StakerWithdrawableShares_AfterAVSAndBCSlash( + function assert_Snap_StakerWithdrawableShares_AfterAVSSlash_BCSlash( User staker, - uint256 originalWithdrawableShares, AllocateParams memory allocateParams, SlashingParams memory slashingParams, string memory err @@ -1314,32 +1314,79 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { 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, 1, err); - - // for (uint i = 0; i < allocateParams.strategies.length; i++) { - // IStrategy strat = allocateParams.strategies[i]; - - // uint256 avsSlashedShares = 0; - - // if (slashingParams.strategies.contains(strat)) { - // uint wadToSlash = slashingParams.wadsToSlash[slashingParams.strategies.indexOf(strat)]; - // avsSlashedShares = originalWithdrawableShares.mulWadRoundUp(allocateParams.newMagnitudes[i].mulWadRoundUp(wadToSlash)); - // } - - // uint256 postAVSSlashShares = originalWithdrawableShares - avsSlashedShares; + 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); + } - // uint256 expectedDelta = postAVSSlashShares.mulWad(WAD - _getBeaconChainSlashingFactor(staker)); + /** + * @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"); - // // TODO: bound this better - // assertApproxEqAbs(prevShares[i] - expectedDelta, curShares[i], 1e3, err); + uint curShares = _getWithdrawableShares(staker, allocateParams.strategies)[0]; + uint prevShares = _getPrevWithdrawableShares(staker, allocateParams.strategies)[0]; - // // Prev shares should decrease by a factor of the BCSF - // assertApproxEqAbs(prevShares[i].mulWad(_getBeaconChainSlashingFactor(staker)), curShares[i], 1e3, err); - // } + // 1. The withdrawable shares should decrease by a factor of the BCSF + assertApproxEqAbs(prevShares.mulWad(_getBeaconChainSlashingFactor(staker)), curShares, 1e4, 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, 1e4, 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, 1e4, err); } + // TODO: slashable stake /******************************************************************************* diff --git a/src/test/integration/IntegrationChecks.t.sol b/src/test/integration/IntegrationChecks.t.sol index bee39f651..b5d266074 100644 --- a/src/test/integration/IntegrationChecks.t.sol +++ b/src/test/integration/IntegrationChecks.t.sol @@ -931,10 +931,10 @@ contract IntegrationCheckUtils is IntegrationBase { BC/AVS SLASHING CHECKS *******************************************************************************/ - function check_CompleteCheckPoint_AfterAVSAndBCSlash( + function check_CompleteCheckPoint_AfterAVS_BCSlash( User staker, uint40[] memory slashedValidators, - uint256 originalWithdrawableShares, + uint256 depositShares, uint64 slashedBalanceGwei, AllocateParams memory allocateParams, SlashingParams memory slashingParams @@ -945,18 +945,20 @@ contract IntegrationCheckUtils is IntegrationBase { 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"); - // Between the AVS slash and the BC slash, the shares should have decreased by at least the BC slash amount - assert_withdrawableSharesDecreasedByAtLeast(staker, BEACONCHAIN_ETH_STRAT, originalWithdrawableShares, uint256(slashedBalanceGwei * GWEI_TO_WEI), "should have decreased withdrawable shares by at least the BC slash amount"); + // 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_AfterAVSAndBCSlash(staker, originalWithdrawableShares, allocateParams, slashingParams, "should have decreased withdrawable shares correctly"); + assert_Snap_StakerWithdrawableShares_AfterAVSSlash_BCSlash(staker, allocateParams, slashingParams, "should have decreased withdrawable shares correctly"); } - function check_CompleteCheckpoint_AfterAVSAndBCSlash_ExtraValidatorProven( + function check_CompleteCheckpoint_AfterAVSSlash_ValidatorProven_BCSlash( User staker, uint40[] memory slashedValidators, uint256 originalWithdrawableShares, - uint64 slashedAmountGwei + uint256 extraValidatorShares, + AllocateParams memory allocateParams, + SlashingParams memory slashingParams ) internal { // Checkpoint State check_CompleteCheckpoint_State(staker); @@ -965,7 +967,6 @@ contract IntegrationCheckUtils is IntegrationBase { assert_Snap_Removed_ActiveValidators(staker, slashedValidators, "exited validators should each be WITHDRAWN"); // TODO: bound this better - // assert_Snap_StakerWithdrawableShares_AfterAVSAndBCSlash_ExtraValidatorProven(staker, originalWithdrawableShares, allocateParams, slashingParams, "should have decreased withdrawable shares correctly"); - // assert_Snap_Removed_Staker_WithdrawableShares_AtLeast(staker, BEACONCHAIN_ETH_STRAT, slashedAmountGwei * GWEI_TO_WEI, 1e11, "should have decreased withdrawable shares by at least slashed amount"); + assert_Snap_StakerWithdrawableShares_AVSSlash_ValidatorProven_BCSlash(staker, originalWithdrawableShares, extraValidatorShares, allocateParams, slashingParams, "should have decreased withdrawable shares correctly"); } } diff --git a/src/test/integration/tests/BC_AVS_Slashing.t.sol b/src/test/integration/tests/BC_AVS_Slashing.t.sol index cef979369..cb1165cdd 100644 --- a/src/test/integration/tests/BC_AVS_Slashing.t.sol +++ b/src/test/integration/tests/BC_AVS_Slashing.t.sol @@ -75,7 +75,7 @@ contract Integration_BeaconChain_AVS_Ordering is IntegrationCheckUtils { staker.startCheckpoint(); check_StartCheckpoint_WithPodBalance_State(staker, beaconBalanceGwei - slashedAmountGwei); staker.completeCheckpoint(); - check_CompleteCheckPoint_AfterAVSAndBCSlash(staker, validators, initDepositShares[0], slashedAmountGwei, allocateParams, slashingParams); + check_CompleteCheckPoint_AfterAVS_BCSlash(staker, validators, initDepositShares[0], slashedAmountGwei, allocateParams, slashingParams); } function testFuzz_bcSlash_checkpoint_avsSlash(uint24 _random) public rand(_random) { @@ -98,42 +98,44 @@ contract Integration_BeaconChain_AVS_Ordering is IntegrationCheckUtils { slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing"); - assert_Snap_StakerWithdrawableShares_AfterBCAndAVSSlash(staker, allocateParams, slashingParams, "staker withdrawable shares should be slashed"); + assert_Snap_StakerWithdrawableShares_AfterBCSlash_AVSSlash(staker, allocateParams, slashingParams, "staker withdrawable shares should be slashed"); } } - // function testFuzz_avsSlash_verifyValidator_bcSlash_checkpoint(uint24 _random) public rand(_random) { - // // 6. Slash operator by AVS - // SlashingParams memory slashingParams; - // { - // (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = - // _randStrategiesAndWadsToSlash(operatorSet); - // console.log("Strategies to slash: %s", strategiesToSlash.length); - // slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); - // assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); - // assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing"); - // assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker withdrawable shares should be slashed"); - // } - - // // 7. Verify Validator - // cheats.deal(address(staker), 32 ether); - // (uint40[] memory newValidators, uint64 addedBeaconBalanceGwei) = staker.startValidators(); - // beaconChain.advanceEpoch_NoRewards(); - // staker.verifyWithdrawalCredentials(newValidators); - // check_VerifyWC_State(staker, newValidators, addedBeaconBalanceGwei); - // assert_Snap_Added_Staker_WithdrawableSharesAtLeast(staker, BEACONCHAIN_ETH_STRAT.toArray(), uint256(addedBeaconBalanceGwei * GWEI_TO_WEI).toArrayU256(), "staker withdrawable shares should increase by the added beacon balance"); - - // // 8. Slash first validators on BC - // uint64 slashedAmountGwei = beaconChain.slashValidators(validators); - // beaconChain.advanceEpoch_NoRewards(); - - // // 9. Checkpoint - // staker.startCheckpoint(); - // check_StartCheckpoint_WithPodBalance_State(staker, beaconBalanceGwei - slashedAmountGwei); - // staker.completeCheckpoint(); - // check_CompleteCheckpoint_WithAVSAndBCSlashing_HandleRoundDown_State(staker, validators, slashedAmountGwei); - // } + function testFuzz_avsSlash_verifyValidator_bcSlash_checkpoint(uint24 _random) public rand(_random) { + // 6. Slash operator by AVS + SlashingParams memory slashingParams; + { + (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = + _randStrategiesAndWadsToSlash(operatorSet); + console.log("Strategies to slash: %s", strategiesToSlash.length); + slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); + assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); + assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing"); + assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker withdrawable shares should be slashed"); + } + // 7. Verify Validator + cheats.deal(address(staker), 32 ether); + (uint40[] memory newValidators, uint64 addedBeaconBalanceGwei) = staker.startValidators(); + uint beaconSharesAdded = uint256(addedBeaconBalanceGwei * GWEI_TO_WEI); + beaconChain.advanceEpoch_NoRewards(); + staker.verifyWithdrawalCredentials(newValidators); + check_VerifyWC_State(staker, newValidators, addedBeaconBalanceGwei); + assert_Snap_Added_Staker_WithdrawableSharesAtLeast(staker, BEACONCHAIN_ETH_STRAT.toArray(), beaconSharesAdded.toArrayU256(), "staker withdrawable shares should increase by the added beacon balance"); + + // 8. Slash first validators on BC + uint64 slashedAmountGwei = beaconChain.slashValidators(validators); + beaconChain.advanceEpoch_NoRewards(); + + // 9. Checkpoint + staker.startCheckpoint(); + check_StartCheckpoint_WithPodBalance_State(staker, beaconBalanceGwei - slashedAmountGwei); + staker.completeCheckpoint(); + check_CompleteCheckpoint_AfterAVSSlash_ValidatorProven_BCSlash(staker, validators, initDepositShares[0], beaconSharesAdded, allocateParams, slashingParams); + } + + /// @dev Same as above, but validator is proven after BC slash (this ordering doesn't matter) to EL function testFuzz_avsSlash_bcSlash_verifyValidator_checkpoint(uint24 _random) public rand(_random) { // 6. Slash operator by AVS SlashingParams memory slashingParams; @@ -154,16 +156,16 @@ contract Integration_BeaconChain_AVS_Ordering is IntegrationCheckUtils { // 8. Verify Validator cheats.deal(address(staker), 32 ether); (uint40[] memory newValidators, uint64 addedBeaconBalanceGwei) = staker.startValidators(); + uint beaconSharesAdded = uint256(addedBeaconBalanceGwei * GWEI_TO_WEI); beaconChain.advanceEpoch_NoRewards(); staker.verifyWithdrawalCredentials(newValidators); check_VerifyWC_State(staker, newValidators, addedBeaconBalanceGwei); - assert_Snap_Added_Staker_WithdrawableSharesAtLeast(staker, BEACONCHAIN_ETH_STRAT.toArray(), uint256(addedBeaconBalanceGwei * GWEI_TO_WEI).toArrayU256(), "staker withdrawable shares should increase by the added beacon balance"); + assert_Snap_Added_Staker_WithdrawableSharesAtLeast(staker, BEACONCHAIN_ETH_STRAT.toArray(), beaconSharesAdded.toArrayU256(), "staker withdrawable shares should increase by the added beacon balance"); // 9. Checkpoint staker.startCheckpoint(); check_StartCheckpoint_WithPodBalance_State(staker, beaconBalanceGwei - slashedAmountGwei); staker.completeCheckpoint(); - check_CompleteCheckpoint_AfterAVSAndBCSlash_ExtraValidatorProven(staker, validators, initDepositShares[0], slashedAmountGwei); + check_CompleteCheckpoint_AfterAVSSlash_ValidatorProven_BCSlash(staker, validators, initDepositShares[0], beaconSharesAdded, allocateParams, slashingParams); } - } \ No newline at end of file From e86a2c6e48c4d8e0f1a19e402a53bad1653873c1 Mon Sep 17 00:00:00 2001 From: Yash Patil <40046473+ypatil12@users.noreply.github.com> Date: Fri, 21 Feb 2025 23:03:18 -0500 Subject: [PATCH 07/11] test: dedupe code; t-28 --- src/test/integration/IntegrationBase.t.sol | 6 +- src/test/integration/IntegrationChecks.t.sol | 1 - ..._AVS_Slashing.t.sol => DualSlashing.t.sol} | 110 ++++++++++-------- 3 files changed, 62 insertions(+), 55 deletions(-) rename src/test/integration/tests/{BC_AVS_Slashing.t.sol => DualSlashing.t.sol} (71%) diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index 0e017c055..b3d0177af 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -1362,7 +1362,7 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { 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, 1e4, err); + assertApproxEqAbs(prevShares.mulWad(_getBeaconChainSlashingFactor(staker)), curShares, 1e5, err); /** * 2. The delta in shares is given by: @@ -1373,7 +1373,7 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { uint originalAVSSlashedShares = originalWithdrawableShares.mulWadRoundUp(allocateParams.newMagnitudes[0].mulWadRoundUp(wadToSlash)); uint withdrawableSharesAfterValidatorProven = originalWithdrawableShares - originalAVSSlashedShares + extraValidatorShares; uint expectedDelta = withdrawableSharesAfterValidatorProven.mulWad(WAD - beaconChainSlashingFactor); - assertApproxEqAbs(prevShares - expectedDelta, curShares, 1e4, err); + assertApproxEqAbs(prevShares - expectedDelta, curShares, 1e5, err); /** * 3. The attributable avs slashed shares should decrease by a factor of the BCSF @@ -1383,7 +1383,7 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { uint depositShares = _getStakerDepositShares(staker, allocateParams.strategies)[0]; uint bcSlashedShares = depositShares.mulWad(WAD - beaconChainSlashingFactor); uint attributableAVSSlashedShares = depositShares - bcSlashedShares - curShares; - assertApproxEqAbs(originalAVSSlashedShares.mulWad(beaconChainSlashingFactor), attributableAVSSlashedShares, 1e4, err); + assertApproxEqAbs(originalAVSSlashedShares.mulWad(beaconChainSlashingFactor), attributableAVSSlashedShares, 1e5, err); } diff --git a/src/test/integration/IntegrationChecks.t.sol b/src/test/integration/IntegrationChecks.t.sol index b5d266074..001c669b0 100644 --- a/src/test/integration/IntegrationChecks.t.sol +++ b/src/test/integration/IntegrationChecks.t.sol @@ -966,7 +966,6 @@ contract IntegrationCheckUtils is IntegrationBase { 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"); - // TODO: bound this better assert_Snap_StakerWithdrawableShares_AVSSlash_ValidatorProven_BCSlash(staker, originalWithdrawableShares, extraValidatorShares, allocateParams, slashingParams, "should have decreased withdrawable shares correctly"); } } diff --git a/src/test/integration/tests/BC_AVS_Slashing.t.sol b/src/test/integration/tests/DualSlashing.t.sol similarity index 71% rename from src/test/integration/tests/BC_AVS_Slashing.t.sol rename to src/test/integration/tests/DualSlashing.t.sol index cb1165cdd..56b2d0a89 100644 --- a/src/test/integration/tests/BC_AVS_Slashing.t.sol +++ b/src/test/integration/tests/DualSlashing.t.sol @@ -3,7 +3,8 @@ pragma solidity ^0.8.27; import "src/test/integration/IntegrationChecks.t.sol"; -contract Integration_BeaconChain_AVS_Ordering is IntegrationCheckUtils { +/// @notice Tests where we slash native eth on the Beacon Chain and by an OperatorSet +contract Integration_DualSlashing_Base is IntegrationCheckUtils { using ArrayLib for *; AVS avs; @@ -19,7 +20,7 @@ contract Integration_BeaconChain_AVS_Ordering is IntegrationCheckUtils { uint[] initTokenBalances; uint[] initDepositShares; - function _init() internal override { + function _init() internal virtual override { _configAssetTypes(HOLDS_ETH); // Create staker, operator, and avs @@ -52,31 +53,9 @@ contract Integration_BeaconChain_AVS_Ordering is IntegrationCheckUtils { _rollBlocksForCompleteAllocation(operator, operatorSet, strategies); } +} - /// @dev Validates behavior of "restaking", ie. that the funds can be slashed twice - function testFuzz_avsSlash_bcSlash_checkpoint(uint24 _random) public rand(_random){ - // 6. Slash Operator by AVS - SlashingParams memory slashingParams; - { - (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = - _randStrategiesAndWadsToSlash(operatorSet); - console.log("Strategies to slash: %s", strategiesToSlash.length); - slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); - assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); - assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing"); - assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker withdrawable shares should be slashed"); - } - - // 7. Slash Staker on BC - uint64 slashedAmountGwei = beaconChain.slashValidators(validators); - beaconChain.advanceEpoch_NoRewards(); - - // 8. Checkpoint - staker.startCheckpoint(); - check_StartCheckpoint_WithPodBalance_State(staker, beaconBalanceGwei - slashedAmountGwei); - staker.completeCheckpoint(); - check_CompleteCheckPoint_AfterAVS_BCSlash(staker, validators, initDepositShares[0], slashedAmountGwei, allocateParams, slashingParams); - } +contract Integration_DualSlashing_Base_BeaconChainFirst is Integration_DualSlashing_Base { function testFuzz_bcSlash_checkpoint_avsSlash(uint24 _random) public rand(_random) { // 6. Slash staker on BC @@ -101,20 +80,40 @@ contract Integration_BeaconChain_AVS_Ordering is IntegrationCheckUtils { assert_Snap_StakerWithdrawableShares_AfterBCSlash_AVSSlash(staker, allocateParams, slashingParams, "staker withdrawable shares should be slashed"); } } +} - function testFuzz_avsSlash_verifyValidator_bcSlash_checkpoint(uint24 _random) public rand(_random) { - // 6. Slash operator by AVS - SlashingParams memory slashingParams; - { - (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = - _randStrategiesAndWadsToSlash(operatorSet); - console.log("Strategies to slash: %s", strategiesToSlash.length); - slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); - assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); - assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing"); - assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker withdrawable shares should be slashed"); - } +contract Integration_DualSlashing_Base_AVSFirst is Integration_DualSlashing_Base { + using ArrayLib for *; + + SlashingParams slashingParams; + + function _init() internal virtual override { + super._init(); + + // 6. Slash Operator by AVS + (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = + _randStrategiesAndWadsToSlash(operatorSet); + console.log("Strategies to slash: %s", strategiesToSlash.length); + slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); + assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); + assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing"); + assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker withdrawable shares should be slashed"); + } + + /// @dev Validates behavior of "restaking", ie. that the funds can be slashed twice + function testFuzz_avsSlash_bcSlash_checkpoint(uint24 _random) public rand(_random){ + // 7. Slash Staker on BC + uint64 slashedAmountGwei = beaconChain.slashValidators(validators); + beaconChain.advanceEpoch_NoRewards(); + + // 8. Checkpoint + staker.startCheckpoint(); + check_StartCheckpoint_WithPodBalance_State(staker, beaconBalanceGwei - slashedAmountGwei); + staker.completeCheckpoint(); + check_CompleteCheckPoint_AfterAVS_BCSlash(staker, validators, initDepositShares[0], slashedAmountGwei, allocateParams, slashingParams); + } + function testFuzz_avsSlash_verifyValidator_bcSlash_checkpoint(uint24 _random) public rand(_random) { // 7. Verify Validator cheats.deal(address(staker), 32 ether); (uint40[] memory newValidators, uint64 addedBeaconBalanceGwei) = staker.startValidators(); @@ -135,20 +134,8 @@ contract Integration_BeaconChain_AVS_Ordering is IntegrationCheckUtils { check_CompleteCheckpoint_AfterAVSSlash_ValidatorProven_BCSlash(staker, validators, initDepositShares[0], beaconSharesAdded, allocateParams, slashingParams); } - /// @dev Same as above, but validator is proven after BC slash (this ordering doesn't matter) to EL + /// @dev Same as above, but validator is proven after BC slash (this ordering doesn't matter to EL) function testFuzz_avsSlash_bcSlash_verifyValidator_checkpoint(uint24 _random) public rand(_random) { - // 6. Slash operator by AVS - SlashingParams memory slashingParams; - { - (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = - _randStrategiesAndWadsToSlash(operatorSet); - console.log("Strategies to slash: %s", strategiesToSlash.length); - slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); - assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); - assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing"); - assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker withdrawable shares should be slashed"); - } - // 7. Slash Staker on BC uint64 slashedAmountGwei = beaconChain.slashValidators(validators); beaconChain.advanceEpoch_NoRewards(); @@ -168,4 +155,25 @@ contract Integration_BeaconChain_AVS_Ordering is IntegrationCheckUtils { staker.completeCheckpoint(); check_CompleteCheckpoint_AfterAVSSlash_ValidatorProven_BCSlash(staker, validators, initDepositShares[0], beaconSharesAdded, allocateParams, slashingParams); } + + function testFuzz_avsSlash_bcSlash_checkpoint_verifyValidator(uint24 _rand) public rand(_rand) { + // 7. Slash Staker on BC + uint64 slashedAmountGwei = beaconChain.slashValidators(validators); + beaconChain.advanceEpoch_NoRewards(); + + // 8. Checkpoint + staker.startCheckpoint(); + check_StartCheckpoint_WithPodBalance_State(staker, beaconBalanceGwei - slashedAmountGwei); + staker.completeCheckpoint(); + check_CompleteCheckPoint_AfterAVS_BCSlash(staker, validators, initDepositShares[0], slashedAmountGwei, allocateParams, slashingParams); + + // 9. Verify Validator + cheats.deal(address(staker), 32 ether); + (uint40[] memory newValidators, uint64 addedBeaconBalanceGwei) = staker.startValidators(); + uint beaconSharesAdded = uint256(addedBeaconBalanceGwei * GWEI_TO_WEI); + beaconChain.advanceEpoch_NoRewards(); + staker.verifyWithdrawalCredentials(newValidators); + check_VerifyWC_State(staker, newValidators, addedBeaconBalanceGwei); + assert_Snap_Added_Staker_WithdrawableSharesAtLeast(staker, BEACONCHAIN_ETH_STRAT.toArray(), beaconSharesAdded.toArrayU256(), "staker withdrawable shares should increase by the added beacon balance"); + } } \ No newline at end of file From 3342fcabde94bd4ddfca5e5a4e1de9c1a2dec3f5 Mon Sep 17 00:00:00 2001 From: Yash Patil <40046473+ypatil12@users.noreply.github.com> Date: Fri, 21 Feb 2025 23:58:37 -0500 Subject: [PATCH 08/11] test: t-29 --- src/test/integration/IntegrationChecks.t.sol | 17 +++++++++++++++ src/test/integration/tests/DualSlashing.t.sol | 21 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/test/integration/IntegrationChecks.t.sol b/src/test/integration/IntegrationChecks.t.sol index 001c669b0..66f85ceb5 100644 --- a/src/test/integration/IntegrationChecks.t.sol +++ b/src/test/integration/IntegrationChecks.t.sol @@ -968,4 +968,21 @@ contract IntegrationCheckUtils is IntegrationBase { 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"); + } } diff --git a/src/test/integration/tests/DualSlashing.t.sol b/src/test/integration/tests/DualSlashing.t.sol index 56b2d0a89..74d0a4f73 100644 --- a/src/test/integration/tests/DualSlashing.t.sol +++ b/src/test/integration/tests/DualSlashing.t.sol @@ -176,4 +176,25 @@ contract Integration_DualSlashing_Base_AVSFirst is Integration_DualSlashing_Base check_VerifyWC_State(staker, newValidators, addedBeaconBalanceGwei); assert_Snap_Added_Staker_WithdrawableSharesAtLeast(staker, BEACONCHAIN_ETH_STRAT.toArray(), beaconSharesAdded.toArrayU256(), "staker withdrawable shares should increase by the added beacon balance"); } + + function testFuzz_avsSlash_balanceIncrease_checkpoint_verifyValidator(uint24 _rand) public rand(_rand) { + // 7. Slash Staker on BC + uint64 slashedAmountGwei = beaconChain.slashValidators(validators); + beaconChain.advanceEpoch_NoRewards(); + + // 8. Send 32 ETH to pod, some random amount of ETH, greater than the amount slashed + uint ethToDeposit = 32 ether; + cheats.deal(address(staker), ethToDeposit); + cheats.prank(address(staker)); + address(staker.pod()).call{value: ethToDeposit}(""); + uint64 beaconSharesAddedGwei = uint64(ethToDeposit / GWEI_TO_WEI); + + console.log("pod balance: ", address(staker.pod()).balance); + + // 9. Checkpoint + staker.startCheckpoint(); + check_StartCheckpoint_WithPodBalance_State(staker, beaconBalanceGwei - slashedAmountGwei + beaconSharesAddedGwei); + staker.completeCheckpoint(); + check_CompleteCheckpoint_AfterAVSSlash_ETHDeposit_BCSlash(staker, validators, slashedAmountGwei, beaconSharesAddedGwei); + } } \ No newline at end of file From 0799e6017f39430194b506a4a86538b2b222b0ff Mon Sep 17 00:00:00 2001 From: Yash Patil <40046473+ypatil12@users.noreply.github.com> Date: Sat, 22 Feb 2025 09:19:19 -0800 Subject: [PATCH 09/11] chore: push --- src/test/integration/tests/DualSlashing.t.sol | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/test/integration/tests/DualSlashing.t.sol b/src/test/integration/tests/DualSlashing.t.sol index 74d0a4f73..f229d88bb 100644 --- a/src/test/integration/tests/DualSlashing.t.sol +++ b/src/test/integration/tests/DualSlashing.t.sol @@ -113,6 +113,8 @@ contract Integration_DualSlashing_Base_AVSFirst is Integration_DualSlashing_Base check_CompleteCheckPoint_AfterAVS_BCSlash(staker, validators, initDepositShares[0], slashedAmountGwei, allocateParams, slashingParams); } + /// @notice Because the validator is proven prior to the BC slash, the system applies the new balance + /// to the BC and AVS slash combined function testFuzz_avsSlash_verifyValidator_bcSlash_checkpoint(uint24 _random) public rand(_random) { // 7. Verify Validator cheats.deal(address(staker), 32 ether); @@ -156,6 +158,7 @@ contract Integration_DualSlashing_Base_AVSFirst is Integration_DualSlashing_Base check_CompleteCheckpoint_AfterAVSSlash_ValidatorProven_BCSlash(staker, validators, initDepositShares[0], beaconSharesAdded, allocateParams, slashingParams); } + /// @notice The validator proven should not be affected by the BC or AVS slashes function testFuzz_avsSlash_bcSlash_checkpoint_verifyValidator(uint24 _rand) public rand(_rand) { // 7. Slash Staker on BC uint64 slashedAmountGwei = beaconChain.slashValidators(validators); @@ -177,6 +180,8 @@ contract Integration_DualSlashing_Base_AVSFirst is Integration_DualSlashing_Base assert_Snap_Added_Staker_WithdrawableSharesAtLeast(staker, BEACONCHAIN_ETH_STRAT.toArray(), beaconSharesAdded.toArrayU256(), "staker withdrawable shares should increase by the added beacon balance"); } + /// @notice The balance increase results in the pods not processing the beacon slash as a slash, given + /// that the checkpoint had a positive delta function testFuzz_avsSlash_balanceIncrease_checkpoint_verifyValidator(uint24 _rand) public rand(_rand) { // 7. Slash Staker on BC uint64 slashedAmountGwei = beaconChain.slashValidators(validators); @@ -197,4 +202,6 @@ contract Integration_DualSlashing_Base_AVSFirst is Integration_DualSlashing_Base staker.completeCheckpoint(); check_CompleteCheckpoint_AfterAVSSlash_ETHDeposit_BCSlash(staker, validators, slashedAmountGwei, beaconSharesAddedGwei); } + + } \ No newline at end of file From b26a64685158729ff746463835074868db9d4df9 Mon Sep 17 00:00:00 2001 From: Yash Patil <40046473+ypatil12@users.noreply.github.com> Date: Mon, 24 Feb 2025 16:02:19 -0800 Subject: [PATCH 10/11] feat: last bc/avs non full slash test --- src/test/integration/IntegrationBase.t.sol | 2 ++ src/test/integration/tests/DualSlashing.t.sol | 27 ++++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index b3d0177af..da443dd15 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -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); } diff --git a/src/test/integration/tests/DualSlashing.t.sol b/src/test/integration/tests/DualSlashing.t.sol index f229d88bb..4666e0596 100644 --- a/src/test/integration/tests/DualSlashing.t.sol +++ b/src/test/integration/tests/DualSlashing.t.sol @@ -182,7 +182,7 @@ contract Integration_DualSlashing_Base_AVSFirst is Integration_DualSlashing_Base /// @notice The balance increase results in the pods not processing the beacon slash as a slash, given /// that the checkpoint had a positive delta - function testFuzz_avsSlash_balanceIncrease_checkpoint_verifyValidator(uint24 _rand) public rand(_rand) { + function testFuzz_avsSlash_bcSlash_balanceIncrease_checkpoint(uint24 _rand) public rand(_rand) { // 7. Slash Staker on BC uint64 slashedAmountGwei = beaconChain.slashValidators(validators); beaconChain.advanceEpoch_NoRewards(); @@ -194,8 +194,6 @@ contract Integration_DualSlashing_Base_AVSFirst is Integration_DualSlashing_Base address(staker.pod()).call{value: ethToDeposit}(""); uint64 beaconSharesAddedGwei = uint64(ethToDeposit / GWEI_TO_WEI); - console.log("pod balance: ", address(staker.pod()).balance); - // 9. Checkpoint staker.startCheckpoint(); check_StartCheckpoint_WithPodBalance_State(staker, beaconBalanceGwei - slashedAmountGwei + beaconSharesAddedGwei); @@ -203,5 +201,28 @@ contract Integration_DualSlashing_Base_AVSFirst is Integration_DualSlashing_Base check_CompleteCheckpoint_AfterAVSSlash_ETHDeposit_BCSlash(staker, validators, slashedAmountGwei, beaconSharesAddedGwei); } + /// @notice The balance increase occurs after the slashings are processed, so it should be unaffected by the slashings + function testFuzz_avsSlash_bcSlash_checkpoint_balanceIncrease(uint24 _rand) public rand(_rand) { + // 7. Slash Staker on BC + uint64 slashedAmountGwei = beaconChain.slashValidators(validators); + beaconChain.advanceEpoch_NoRewards(); + + // 8. Checkpoint + staker.startCheckpoint(); + check_StartCheckpoint_WithPodBalance_State(staker, beaconBalanceGwei - slashedAmountGwei); + staker.completeCheckpoint(); + check_CompleteCheckPoint_AfterAVS_BCSlash(staker, validators, initDepositShares[0], slashedAmountGwei, allocateParams, slashingParams); + // 9. Send 32 ETH to pod, some random amount of ETH, greater than the amount slashed + uint ethToDeposit = 32 ether; + cheats.deal(address(staker), ethToDeposit); + cheats.prank(address(staker)); + address(staker.pod()).call{value: ethToDeposit}(""); + uint64 beaconSharesAddedGwei = uint64(ethToDeposit / GWEI_TO_WEI); + + // 10. Checkpoint. This should immediately complete as there are no more active validators + beaconChain.advanceEpoch_NoRewards(); + staker.startCheckpoint(); + check_StartCheckpoint_NoValidators_State(staker, beaconSharesAddedGwei); + } } \ No newline at end of file From b0cbb0ade05e065f47b4ab305ef2bb4be64cb19a Mon Sep 17 00:00:00 2001 From: Yash Patil <40046473+ypatil12@users.noreply.github.com> Date: Mon, 24 Feb 2025 16:29:42 -0800 Subject: [PATCH 11/11] fix: compile --- src/test/integration/tests/DualSlashing.t.sol | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/test/integration/tests/DualSlashing.t.sol b/src/test/integration/tests/DualSlashing.t.sol index 4666e0596..945efe24b 100644 --- a/src/test/integration/tests/DualSlashing.t.sol +++ b/src/test/integration/tests/DualSlashing.t.sol @@ -71,10 +71,8 @@ contract Integration_DualSlashing_Base_BeaconChainFirst is Integration_DualSlash // 8. Slash operator by AVS SlashingParams memory slashingParams; { - (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = - _randStrategiesAndWadsToSlash(operatorSet); - console.log("Strategies to slash: %s", strategiesToSlash.length); - slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); + slashingParams = _genSlashing_Rand(operator, operatorSet); + avs.slashOperator(slashingParams); assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing"); assert_Snap_StakerWithdrawableShares_AfterBCSlash_AVSSlash(staker, allocateParams, slashingParams, "staker withdrawable shares should be slashed"); @@ -90,14 +88,12 @@ contract Integration_DualSlashing_Base_AVSFirst is Integration_DualSlashing_Base function _init() internal virtual override { super._init(); - // 6. Slash Operator by AVS - (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = - _randStrategiesAndWadsToSlash(operatorSet); - console.log("Strategies to slash: %s", strategiesToSlash.length); - slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); + // 8. Slash operator by AVS + slashingParams = _genSlashing_Rand(operator, operatorSet); + avs.slashOperator(slashingParams); assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing"); - assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker withdrawable shares should be slashed"); + assert_Snap_StakerWithdrawableShares_AfterBCSlash_AVSSlash(staker, allocateParams, slashingParams, "staker withdrawable shares should be slashed"); } /// @dev Validates behavior of "restaking", ie. that the funds can be slashed twice