diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index b0a324993..216fb47b6 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -1312,10 +1312,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, @@ -1344,9 +1345,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 @@ -1357,32 +1357,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 2b7738ef7..2fed04c31 100644 --- a/src/test/integration/IntegrationChecks.t.sol +++ b/src/test/integration/IntegrationChecks.t.sol @@ -1003,10 +1003,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 @@ -1017,18 +1017,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); @@ -1037,7 +1039,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