Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add getSharesFromQueuedWithdrawal #1078

Merged
75 changes: 44 additions & 31 deletions src/contracts/core/DelegationManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,41 @@ contract DelegationManager is
}
}

/// @dev Get the shares from a queued withdrawal.
function _getSharesFromQueuedWithdrawal(
bytes32 withdrawalRoot
) internal view returns (Withdrawal memory withdrawal, uint256[] memory shares) {
withdrawal = queuedWithdrawals[withdrawalRoot];
shares = new uint256[](withdrawal.strategies.length);

address operator = delegatedTo[withdrawal.staker];
uint32 slashableUntil = withdrawal.startBlock + MIN_WITHDRAWAL_DELAY_BLOCKS;

uint256[] memory slashingFactors;
// If slashableUntil block is in the past, read the slashing factors at that block
// Otherwise read the current slashing factors. Note that if the slashableUntil block is the current block
// or in the future then the slashing factors are still subject to change before the withdrawal is completable
// and the shares withdrawn to be less
if (slashableUntil < uint32(block.number)) {
slashingFactors = _getSlashingFactorsAtBlock({
staker: withdrawal.staker,
operator: operator,
strategies: withdrawal.strategies,
blockNumber: slashableUntil
});
} else {
slashingFactors =
_getSlashingFactors({staker: withdrawal.staker, operator: operator, strategies: withdrawal.strategies});
}

for (uint256 j; j < withdrawal.strategies.length; ++j) {
shares[j] = SlashingLib.scaleForCompleteWithdrawal({
scaledShares: withdrawal.scaledShares[j],
slashingFactor: slashingFactors[j]
});
}
}

/// @dev Depending on the strategy used, determine which ShareManager contract to make external calls to
function _getShareManager(
IStrategy strategy
Expand Down Expand Up @@ -915,7 +950,14 @@ contract DelegationManager is
}

/// @inheritdoc IDelegationManager
function getQueuedWithdrawals(
function getSharesFromQueuedWithdrawal(
bytes32 withdrawalRoot
) external view returns (Withdrawal memory withdrawal, uint256[] memory shares) {
(withdrawal, shares) = _getSharesFromQueuedWithdrawal(withdrawalRoot);
}

/// @inheritdoc IDelegationManager
function getSharesFromQueuedWithdrawals(
address staker
) external view returns (Withdrawal[] memory withdrawals, uint256[][] memory shares) {
bytes32[] memory withdrawalRoots = getQueuedWithdrawalRoots(staker);
Expand All @@ -924,37 +966,8 @@ contract DelegationManager is
withdrawals = new Withdrawal[](totalQueued);
shares = new uint256[][](totalQueued);

address operator = delegatedTo[staker];

for (uint256 i; i < totalQueued; ++i) {
withdrawals[i] = queuedWithdrawals[withdrawalRoots[i]];
shares[i] = new uint256[](withdrawals[i].strategies.length);

uint32 slashableUntil = withdrawals[i].startBlock + MIN_WITHDRAWAL_DELAY_BLOCKS;

uint256[] memory slashingFactors;
// If slashableUntil block is in the past, read the slashing factors at that block
// Otherwise read the current slashing factors. Note that if the slashableUntil block is the current block
// or in the future then the slashing factors are still subject to change before the withdrawal is completable
// and the shares withdrawn to be less
if (slashableUntil < uint32(block.number)) {
slashingFactors = _getSlashingFactorsAtBlock({
staker: staker,
operator: operator,
strategies: withdrawals[i].strategies,
blockNumber: slashableUntil
});
} else {
slashingFactors =
_getSlashingFactors({staker: staker, operator: operator, strategies: withdrawals[i].strategies});
}

for (uint256 j; j < withdrawals[i].strategies.length; ++j) {
shares[i][j] = SlashingLib.scaleForCompleteWithdrawal({
scaledShares: withdrawals[i].scaledShares[j],
slashingFactor: slashingFactors[j]
});
}
(withdrawals[i], shares[i]) = _getSharesFromQueuedWithdrawal(withdrawalRoots[i]);
}
}

Expand Down
9 changes: 7 additions & 2 deletions src/contracts/interfaces/IDelegationManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -477,8 +477,13 @@ interface IDelegationManager is ISignatureUtils, IDelegationManagerErrors, IDele
bytes32 withdrawalRoot
) external view returns (Withdrawal memory);

/// @notice Returns a list of pending queued withdrawals for a `staker`, and the `shares` to be withdrawn.
function getQueuedWithdrawals(
/// @notice Returns the shares from a queued withdrawal.
function getSharesFromQueuedWithdrawal(
bytes32 withdrawalRoot
) external view returns (Withdrawal memory withdrawal, uint256[] memory shares);

/// @notice Returns a list of shares from a list of queued withdrawals.
function getSharesFromQueuedWithdrawals(
address staker
) external view returns (Withdrawal[] memory withdrawals, uint256[][] memory shares);

Expand Down
30 changes: 15 additions & 15 deletions src/test/unit/DelegationUnit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1114,7 +1114,7 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag
delegationManager.pendingWithdrawals(withdrawalRootToCheck),
"withdrawalRoot not pending"
);
(Withdrawal[] memory withdrawalsInStorage, ) = delegationManager.getQueuedWithdrawals(staker);
(Withdrawal[] memory withdrawalsInStorage, ) = delegationManager.getSharesFromQueuedWithdrawals(staker);
for (uint256 j = 0; j < withdrawalsInStorage.length; ++j) {
assertTrue(
withdrawalRootToCheck != delegationManager.calculateWithdrawalRoot(withdrawalsInStorage[j]),
Expand Down Expand Up @@ -1234,7 +1234,7 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag
"withdrawalRoot not pending"
);

(Withdrawal[] memory withdrawals, ) = delegationManager.getQueuedWithdrawals(staker);
(Withdrawal[] memory withdrawals, ) = delegationManager.getSharesFromQueuedWithdrawals(staker);
for (uint256 j = 0; j < withdrawals.length; ++j) {
if (withdrawalRootToCheck == delegationManager.calculateWithdrawalRoot(withdrawals[j])) {
assertEq(
Expand Down Expand Up @@ -1291,7 +1291,7 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag
"withdrawalRoot not pending"
);

(Withdrawal[] memory withdrawals, ) = delegationManager.getQueuedWithdrawals(staker);
(Withdrawal[] memory withdrawals, ) = delegationManager.getSharesFromQueuedWithdrawals(staker);
for (uint256 i = 0; i < withdrawals.length; ++i) {
assertEq(
withdrawals[i].staker,
Expand Down Expand Up @@ -6544,7 +6544,7 @@ contract DelegationManagerUnitTests_completeQueuedWithdrawal is DelegationManage
cheats.roll(startBlock + 1);
delegationManager.queueWithdrawals(queuedParams[1].toArray());

(Withdrawal[] memory firstWithdrawals, ) = delegationManager.getQueuedWithdrawals(defaultStaker);
(Withdrawal[] memory firstWithdrawals, ) = delegationManager.getSharesFromQueuedWithdrawals(defaultStaker);

cheats.roll(startBlock + 2);
delegationManager.queueWithdrawals(queuedParams[2].toArray());
Expand Down Expand Up @@ -8568,15 +8568,15 @@ contract DelegationManagerUnitTests_ConvertToDepositShares is DelegationManagerU
}
}

contract DelegationManagerUnitTests_getQueuedWithdrawals is DelegationManagerUnitTests {
contract DelegationManagerUnitTests_getSharesFromQueuedWithdrawals is DelegationManagerUnitTests {
using ArrayLib for *;
using SlashingLib for *;

function _withdrawalRoot(Withdrawal memory withdrawal) internal pure returns (bytes32) {
return keccak256(abi.encode(withdrawal));
}

function test_getQueuedWithdrawals_Correctness(Randomness r) public rand(r) {
function test_getSharesFromQueuedWithdrawals_Correctness(Randomness r) public rand(r) {
uint256 numStrategies = r.Uint256(2, 8);
uint256[] memory depositShares = r.Uint256Array({
len: numStrategies,
Expand Down Expand Up @@ -8612,7 +8612,7 @@ contract DelegationManagerUnitTests_getQueuedWithdrawals is DelegationManagerUni
delegationManager.queueWithdrawals(queuedWithdrawalParams);

// Get queued withdrawals.
(Withdrawal[] memory withdrawals, uint256[][] memory shares) = delegationManager.getQueuedWithdrawals(defaultStaker);
(Withdrawal[] memory withdrawals, uint256[][] memory shares) = delegationManager.getSharesFromQueuedWithdrawals(defaultStaker);
// Checks
for (uint256 i; i < strategies.length; ++i) {
uint256 newStakerShares = depositShares[i] / 2;
Expand All @@ -8623,7 +8623,7 @@ contract DelegationManagerUnitTests_getQueuedWithdrawals is DelegationManagerUni
assertEq(_withdrawalRoot(withdrawal), withdrawalRoot, "_withdrawalRoot(withdrawal) != withdrawalRoot");
}

function test_getQueuedWithdrawals_TotalQueuedGreaterThanTotalStrategies(
function test_getSharesFromQueuedWithdrawals_TotalQueuedGreaterThanTotalStrategies(
Randomness r
) public rand(r) {
uint256 totalDepositShares = r.Uint256(2, 100 ether);
Expand Down Expand Up @@ -8667,7 +8667,7 @@ contract DelegationManagerUnitTests_getQueuedWithdrawals is DelegationManagerUni
delegationManager.queueWithdrawals(queuedWithdrawalParams1);

// Get queued withdrawals.
(Withdrawal[] memory withdrawals, uint256[][] memory shares) = delegationManager.getQueuedWithdrawals(defaultStaker);
(Withdrawal[] memory withdrawals, uint256[][] memory shares) = delegationManager.getSharesFromQueuedWithdrawals(defaultStaker);

// Sanity
assertEq(withdrawals.length, 2, "withdrawal.length != 2");
Expand All @@ -8684,12 +8684,12 @@ contract DelegationManagerUnitTests_getQueuedWithdrawals is DelegationManagerUni
}

/**
* @notice Assert that the shares returned in the view function `getQueuedWithdrawals` are unaffected from a
* @notice Assert that the shares returned in the view function `getSharesFromQueuedWithdrawals` are unaffected from a
* slash that occurs after the withdrawal is completed. Also assert that completing the withdrawal matches the
* expected withdrawn shares from the view function.
* Slashing on the completableBlock of the withdrawal should have no affect on the withdrawn shares.
*/
function test_getQueuedWithdrawals_SlashAfterWithdrawalCompletion(Randomness r) public rand(r) {
function test_getSharesFromQueuedWithdrawals_SlashAfterWithdrawalCompletion(Randomness r) public rand(r) {
uint256 depositAmount = r.Uint256(1, MAX_STRATEGY_SHARES);

// Deposit Staker
Expand Down Expand Up @@ -8747,9 +8747,9 @@ contract DelegationManagerUnitTests_getQueuedWithdrawals is DelegationManagerUni
);
}

// Assert that the getQueuedWithdrawals returns shares that are halved as a result of being slashed 50%
// Assert that the getSharesFromQueuedWithdrawals returns shares that are halved as a result of being slashed 50%
{
(Withdrawal[] memory withdrawals, uint256[][] memory shares) = delegationManager.getQueuedWithdrawals(defaultStaker);
(Withdrawal[] memory withdrawals, uint256[][] memory shares) = delegationManager.getSharesFromQueuedWithdrawals(defaultStaker);
assertEq(withdrawals.length, 1, "withdrawals.length != 1");
assertEq(withdrawals[0].strategies.length, 1, "withdrawals[0].strategies.length != 1");
assertEq(shares[0][0], depositAmount / 2, "shares[0][0] != depositAmount / 2");
Expand Down Expand Up @@ -8778,12 +8778,12 @@ contract DelegationManagerUnitTests_getQueuedWithdrawals is DelegationManagerUni
);
}

// Assert that the getQueuedWithdrawals returns shares that are halved as a result of being slashed 50% and hasn't been
// Assert that the getSharesFromQueuedWithdrawals returns shares that are halved as a result of being slashed 50% and hasn't been
// affected by the second slash
uint256 expectedSharesIncrease = depositAmount / 2;
uint256 queuedWithdrawableShares;
{
(Withdrawal[] memory withdrawals, uint256[][] memory shares) = delegationManager.getQueuedWithdrawals(defaultStaker);
(Withdrawal[] memory withdrawals, uint256[][] memory shares) = delegationManager.getSharesFromQueuedWithdrawals(defaultStaker);
queuedWithdrawableShares = shares[0][0];
assertEq(withdrawals.length, 1, "withdrawals.length != 1");
assertEq(withdrawals[0].strategies.length, 1, "withdrawals[0].strategies.length != 1");
Expand Down
Loading