Skip to content

Commit

Permalink
fix bug with strict check for queued amount is equal to actual withdr…
Browse files Browse the repository at this point in the history
…awn amount
  • Loading branch information
danoctavian committed Sep 28, 2024
1 parent 5514249 commit 62cf025
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 14 deletions.
13 changes: 6 additions & 7 deletions src/StakingNode.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ interface StakingNodeEvents {
);

event QueuedWithdrawals(uint256 sharesAmount, bytes32[] fullWithdrawalRoots);
event CompletedQueuedWithdrawals(IDelegationManager.Withdrawal[] withdrawals, uint256 totalWithdrawalAmount);
event CompletedQueuedWithdrawals(IDelegationManager.Withdrawal[] withdrawals, uint256 totalWithdrawalAmount, uint256 actualWithdrawalAmount);
}

/**
Expand Down Expand Up @@ -360,17 +360,16 @@ contract StakingNode is IStakingNode, StakingNodeEvents, ReentrancyGuardUpgradea

uint256 finalETHBalance = address(this).balance;
uint256 actualWithdrawalAmount = finalETHBalance - initialETHBalance;
if (actualWithdrawalAmount != totalWithdrawalAmount) {
revert MismatchInExpectedETHBalanceAfterWithdrawals(actualWithdrawalAmount, totalWithdrawalAmount);
}

// Shares are no longer queued
queuedSharesAmount -= actualWithdrawalAmount;
// NOTE: actualWithdrawalAmount may be < totalWithdrawalAmount in case of slashing !

// Shares are no longer queued; decrease what was queued for withdrawal
queuedSharesAmount -= totalWithdrawalAmount;

// Withdraw validator principal resides in the StakingNode until StakingNodesManager retrieves it.
withdrawnETH += actualWithdrawalAmount;

emit CompletedQueuedWithdrawals(withdrawals, totalWithdrawalAmount);
emit CompletedQueuedWithdrawals(withdrawals, totalWithdrawalAmount, actualWithdrawalAmount);
}

//--------------------------------------------------------------------------------------
Expand Down
63 changes: 56 additions & 7 deletions test/integration/M3/WithdrawalsWithRewards-Scenario.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ contract M3WithdrawalsWithRewardsTest is Base {
);
}

function test_userWithdrawalWithRewards_Scenario_4_slashAllValidatorsBeforeWithdrawingAndWithdrawEverything() public {
function test_userWithdrawalWithRewards_Scenario_4_slashAllValidatorsAndWithdrawEverything() public {

// Check if we're on the Holesky testnet
if (block.chainid != 17000) {
Expand All @@ -520,11 +520,57 @@ contract M3WithdrawalsWithRewardsTest is Base {
accumulatedRewards += state.validatorCount * epochCount * 1e9; // 1 GWEI per Epoch per Validator
}

// NOTE: Withdrawal is Queued BEFORE exiting validators. does not include rewards in this case.
uint256 withdrawnAmount = amount;

// NOTE: This triggers the a exit of all validators
beaconChain.slashValidators(validatorIndices);
beaconChain.advanceEpoch();

// queue withdrawals
{
vm.startPrank(actors.ops.STAKING_NODES_OPERATOR);
stakingNodesManager.nodes(nodeId).queueWithdrawals(withdrawnAmount);
vm.stopPrank();
}

runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore);

startAndVerifyCheckpoint(nodeId, state);

// Print withdrawableRestakedExecutionLayerGwei for each EigenPod
IEigenPod pod = stakingNodesManager.nodes(nodeId).eigenPod();
uint64 withdrawableGwei = pod.withdrawableRestakedExecutionLayerGwei();
console.log("Withdrawable GWEI for EigenPod:", withdrawableGwei);

uint256 totalSlashAmount = beaconChain.SLASH_AMOUNT_GWEI() * state.validatorCount * 1e9;
state.totalAssetsBefore = state.totalAssetsBefore + accumulatedRewards - totalSlashAmount;
state.stakingNodeBalancesBefore[nodeId] = state.stakingNodeBalancesBefore[nodeId] + accumulatedRewards - totalSlashAmount;
runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore);

QueuedWithdrawalInfo[] memory withdrawalInfos = new QueuedWithdrawalInfo[](1);
withdrawalInfos[0] = QueuedWithdrawalInfo({
nodeId: nodeId,
withdrawnAmount: withdrawnAmount
});
completeQueuedWithdrawals(withdrawalInfos);

runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore);
}

function test_userWithdrawalWithRewards_Scenario_4_slashAllValidatorsWithNoRewardsAndWithdrawEverything() public {

// Check if we're on the Holesky testnet
if (block.chainid != 17000) {
return;
}
// exactly 2 validators
TestState memory state = registerVerifiedValidators(64 ether);

uint256 withdrawnAmount = amount;

// NOTE: This triggers the a exit of all validators
beaconChain.slashValidators(validatorIndices);
beaconChain.advanceEpoch();

// queue withdrawals
{
Expand All @@ -533,15 +579,18 @@ contract M3WithdrawalsWithRewardsTest is Base {
vm.stopPrank();
}

// validator exits no longer needed.

runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore);

startAndVerifyCheckpoint(nodeId, state);

// Rewards accumulated are accounted after verifying the checkpoint
state.totalAssetsBefore += accumulatedRewards;
state.stakingNodeBalancesBefore[nodeId] += accumulatedRewards;
// Print withdrawableRestakedExecutionLayerGwei for each EigenPod
IEigenPod pod = stakingNodesManager.nodes(nodeId).eigenPod();
uint64 withdrawableGwei = pod.withdrawableRestakedExecutionLayerGwei();
console.log("Withdrawable GWEI for EigenPod:", withdrawableGwei);

uint256 totalSlashAmount = beaconChain.SLASH_AMOUNT_GWEI() * state.validatorCount * 1e9;
state.totalAssetsBefore = state.totalAssetsBefore - totalSlashAmount;
state.stakingNodeBalancesBefore[nodeId] = state.stakingNodeBalancesBefore[nodeId] - totalSlashAmount;
runSystemStateInvariants(state.totalAssetsBefore, state.totalSupplyBefore, state.stakingNodeBalancesBefore);

QueuedWithdrawalInfo[] memory withdrawalInfos = new QueuedWithdrawalInfo[](1);
Expand Down

0 comments on commit 62cf025

Please sign in to comment.