Skip to content

Commit

Permalink
feat: add share helpers (#964)
Browse files Browse the repository at this point in the history
* feat: add share helpers

* fix: add deposit scaling factor

* fix: rebase
  • Loading branch information
gpsanant authored Dec 18, 2024
1 parent 2535685 commit 1777a64
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 0 deletions.
19 changes: 19 additions & 0 deletions src/contracts/core/DelegationManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -948,6 +948,25 @@ contract DelegationManager is
}
}

/// @inheritdoc IDelegationManager
function convertToDepositShares(
address staker,
IStrategy[] memory strategies,
uint256[] memory withdrawableShares
) external view returns (uint256[] memory) {
// Get the slashing factors for the staker/operator/strategies
address operator = delegatedTo[staker];
uint256[] memory slashingFactors = _getSlashingFactors(staker, operator, strategies);

// Calculate the deposit shares based on the slashing factor
uint256[] memory depositShares = new uint256[](strategies.length);
for (uint256 i = 0; i < strategies.length; ++i) {
DepositScalingFactor memory dsf = _depositScalingFactor[staker][strategies[i]];
depositShares[i] = dsf.calcDepositShares(withdrawableShares[i], slashingFactors[i]);
}
return depositShares;
}

/// @inheritdoc IDelegationManager
function calculateWithdrawalRoot(
Withdrawal memory withdrawal
Expand Down
14 changes: 14 additions & 0 deletions src/contracts/interfaces/IDelegationManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,20 @@ interface IDelegationManager is ISignatureUtils, IDelegationManagerErrors, IDele
address staker
) external view returns (Withdrawal[] memory withdrawals, uint256[][] memory shares);

/**
* @notice Converts shares for a set of strategies to deposit shares, likely in order to input into `queueWithdrawals`
* @param staker the staker to convert shares for
* @param strategies the strategies to convert shares for
* @param withdrawableShares the shares to convert
* @return the deposit shares
* @dev will be a few wei off due to rounding errors
*/
function convertToDepositShares(
address staker,
IStrategy[] memory strategies,
uint256[] memory withdrawableShares
) external view returns (uint256[] memory);

/// @notice Returns the keccak256 hash of `withdrawal`.
function calculateWithdrawalRoot(
Withdrawal memory withdrawal
Expand Down
11 changes: 11 additions & 0 deletions src/contracts/libraries/SlashingLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,17 @@ library SlashingLib {
.mulWad(slashingFactor);
}

function calcDepositShares(
DepositScalingFactor memory dsf,
uint256 withdrawableShares,
uint256 slashingFactor
) internal pure returns (uint256) {
/// forgefmt: disable-next-item
return withdrawableShares
.divWad(dsf.scalingFactor())
.divWad(slashingFactor);
}

function calcSlashedAmount(
uint256 operatorShares,
uint256 prevMaxMagnitude,
Expand Down
130 changes: 130 additions & 0 deletions src/test/unit/DelegationUnit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8192,3 +8192,133 @@ contract DelegationManagerUnitTests_Lifecycle is DelegationManagerUnitTests {
assertEq(delegationManager.operatorShares(newOperator, strategy), 0, "new operator shares should be unchanged");
}
}

contract DelegationManagerUnitTests_ConvertToDepositShares is DelegationManagerUnitTests {
using ArrayLib for *;

function test_convertToDepositShares_noSlashing() public {
IStrategy[] memory strategies = new IStrategy[](1);
strategies[0] = strategyMock;
uint256[] memory shares = uint256(100 ether).toArrayU256();

// Set the staker deposits in the strategies
strategyManagerMock.addDeposit(defaultStaker, strategies[0], shares[0]);

_checkDepositSharesConvertCorrectly(strategies, shares);
}

function test_convertToDepositShares_withSlashing() public {
IStrategy[] memory strategies = new IStrategy[](1);
strategies[0] = strategyMock;
uint256[] memory shares = uint256(100 ether).toArrayU256();

// Set the staker deposits in the strategies
strategyManagerMock.addDeposit(defaultStaker, strategies[0], shares[0]);

// register *this contract* as an operator
_registerOperatorWithBaseDetails(defaultOperator);
_delegateToOperatorWhoAcceptsAllStakers(defaultStaker, defaultOperator);
_setOperatorMagnitude(defaultOperator, strategyMock, WAD/3);

_checkDepositSharesConvertCorrectly(strategies, shares);

// queue and complete a withdrawal for half the deposit shares
(uint256[] memory withdrawableShares,) = delegationManager.getWithdrawableShares(defaultStaker, strategies);
_queueAndCompleteWithdrawalForSingleStrategy(strategies[0], shares[0] / 2);

// queued a withdrawal for half the deposit shares, and added back as withdrawable shares
shares[0] = shares[0] / 2 + withdrawableShares[0] / 2;
_checkDepositSharesConvertCorrectly(strategies, shares);
}

function test_convertToDepositShares_beaconChainETH() public {
IStrategy[] memory strategies = new IStrategy[](1);
strategies[0] = beaconChainETHStrategy;
uint256[] memory shares = uint256(100 ether).toArrayU256();

// Set the staker deposits in the strategies
eigenPodManagerMock.setPodOwnerShares(defaultStaker, int256(shares[0]));

uint256[] memory depositShares = delegationManager.convertToDepositShares(defaultStaker, strategies, shares);
assertEq(depositShares[0], shares[0], "deposit shares not converted correctly");

// delegate to an operator and slash
_registerOperatorWithBaseDetails(defaultOperator);
_delegateToOperatorWhoAcceptsAllStakers(defaultStaker, defaultOperator);
_setOperatorMagnitude(defaultOperator, beaconChainETHStrategy, WAD/3);

_checkDepositSharesConvertCorrectly(strategies, shares);

// slash on beacon chain by 1/3
_decreaseBeaconChainShares(defaultStaker, int256(shares[0]), shares[0]/3);

_checkDepositSharesConvertCorrectly(strategies, shares);

// queue and complete a withdrawal for half the deposit shares
(uint256[] memory withdrawableShares,) = delegationManager.getWithdrawableShares(defaultStaker, strategies);
_queueAndCompleteWithdrawalForSingleStrategy(strategies[0], shares[0] / 2);

// queued a withdrawal for half the deposit shares, and added back as withdrawable shares
shares[0] = shares[0] / 2 + withdrawableShares[0] / 2;
_checkDepositSharesConvertCorrectly(strategies, shares);
}

function _checkDepositSharesConvertCorrectly(IStrategy[] memory strategies, uint256[] memory expectedDepositShares) public {
(uint256[] memory withdrawableShares,) = delegationManager.getWithdrawableShares(defaultStaker, strategies);
// get the deposit shares
uint256[] memory depositShares = delegationManager.convertToDepositShares(defaultStaker, strategies, withdrawableShares);

for (uint256 i = 0; i < strategies.length; i++) {
assertApproxEqRel(
expectedDepositShares[i],
depositShares[i],
APPROX_REL_DIFF,
"deposit shares not converted correctly"
);

// make sure that the deposit shares are less than or equal to the shares,
// so this value is sane to input into `completeQueuedWithdrawals`
assertLe(
depositShares[i],
expectedDepositShares[i],
"deposit shares should be less than or equal to expected deposit shares"
);
}

// get the deposit shares
uint256[] memory oneThirdWithdrawableShares = new uint256[](strategies.length);
for (uint256 i = 0; i < strategies.length; i++) {
oneThirdWithdrawableShares[i] = withdrawableShares[i]/3;
}
uint256[] memory oneThirdDepositShares = delegationManager.convertToDepositShares(defaultStaker, strategies, oneThirdWithdrawableShares);
for (uint256 i = 0; i < strategies.length; i++) {
assertApproxEqRel(
expectedDepositShares[i]/3,
oneThirdDepositShares[i],
APPROX_REL_DIFF,
"deposit shares not converted correctly"
);
}
}

function _queueAndCompleteWithdrawalForSingleStrategy(IStrategy strategy, uint256 shares) public {
IStrategy[] memory strategies = new IStrategy[](1);
strategies[0] = strategy;
uint256[] memory depositShares = uint256(shares).toArrayU256();

(QueuedWithdrawalParams[] memory queuedWithdrawalParams, Withdrawal memory withdrawal, bytes32 withdrawalRoot) = _setUpQueueWithdrawalsSingleStrat({
staker: defaultStaker,
withdrawer: defaultStaker,
strategy: strategy,
depositSharesToWithdraw: shares
});

cheats.prank(defaultStaker);
delegationManager.queueWithdrawals(queuedWithdrawalParams);

cheats.roll(block.number + delegationManager.minWithdrawalDelayBlocks());
cheats.prank(defaultStaker);
delegationManager.completeQueuedWithdrawal(withdrawal, tokenMock.toArray(), false);
}
}

0 comments on commit 1777a64

Please sign in to comment.