Summary:
In the current implementation, when tokens queued in the priorityPool are staked into the stakingPool, the generated shares remain within the priorityPool contract. This prevents users from receiving the shares corresponding to their staked assets, resulting in the loss of part of their benefits.
Vulnerability Details:
The issue lies in the PriorityPool::__depositQueuedTokens
function. When processing queued tokens, the tokens are transferred from the priorityPool to the stakingPool, but the resulting shares are not minted for the user, they are instead minted for the priorityPool itself.
PriorityPool::__depositQueuedTokens
:
function _depositQueuedTokens(
uint256 _depositMin,
uint256 _depositMax,
bytes[] memory _data
) internal {
if (poolStatus != PoolStatus.OPEN) revert DepositsDisabled();
uint256 strategyDepositRoom = stakingPool.getStrategyDepositRoom();
if (strategyDepositRoom == 0 || strategyDepositRoom < _depositMin)
revert InsufficientDepositRoom();
uint256 _totalQueued = totalQueued;
uint256 unusedDeposits = stakingPool.getUnusedDeposits();
uint256 canDeposit = _totalQueued + unusedDeposits;
if (canDeposit == 0 || canDeposit < _depositMin) revert InsufficientQueuedTokens();
uint256 toDepositFromStakingPool = MathUpgradeable.min(
MathUpgradeable.min(unusedDeposits, strategyDepositRoom),
_depositMax
);
uint256 toDepositFromQueue = MathUpgradeable.min(
MathUpgradeable.min(_totalQueued, strategyDepositRoom - toDepositFromStakingPool),
_depositMax - toDepositFromStakingPool
);
@> stakingPool.deposit(address(this), toDepositFromQueue, _data);
_totalQueued -= toDepositFromQueue;
if (_totalQueued != totalQueued) {
uint256 diff = totalQueued - _totalQueued;
depositsSinceLastUpdate += diff;
sharesSinceLastUpdate += stakingPool.getSharesByStake(diff);
totalQueued = _totalQueued;
}
emit DepositTokens(toDepositFromStakingPool, toDepositFromQueue);
}
StakingPool::deposit
:
function deposit(
address _account,
uint256 _amount,
bytes[] calldata _data
) external onlyPriorityPool {
require(strategies.length > 0, "Must be > 0 strategies to stake");
uint256 startingBalance = token.balanceOf(address(this));
if (_amount > 0) {
token.safeTransferFrom(msg.sender, address(this), _amount);
_depositLiquidity(_data);
@> _mint(_account, _amount);
totalStaked += _amount;
} else {
_depositLiquidity(_data);
}
uint256 endingBalance = token.balanceOf(address(this));
if (endingBalance > startingBalance && endingBalance > unusedDepositLimit)
revert InvalidDeposit();
}
StakingRewardsPool::_mint/_mintShares
:
* @notice Mints new shares to an account
* @dev takes an LST amount and calculates the amount of shares it corresponds to
* @param _recipient account to mint shares for
* @param _amount stake amount
*/
function _mint(address _recipient, uint256 _amount) internal override {
uint256 sharesToMint = getSharesByStake(_amount);
@> _mintShares(_recipient, sharesToMint);
emit Transfer(address(0), _recipient, _amount);
}
* @notice Mints new shares to an account
* @param _recipient account to mint shares for
* @param _amount shares amount
*/
function _mintShares(address _recipient, uint256 _amount) internal {
require(_recipient != address(0), "Mint to the zero address");
if (totalShares == 0) {
shares[address(0)] = DEAD_SHARES;
totalShares = DEAD_SHARES;
_amount -= DEAD_SHARES;
}
totalShares += _amount;
@> shares[_recipient] += _amount;
}
Impact:
Although users' tokens are staked in the stakingPool, they do not receive LST shares, depriving them of staking rewards and token appreciation. This logic could cause confusion, as users may believe they own staked assets but do not have the corresponding shares, potentially undermining trust in the staking mechanism.
Tools Used:
Manual audit.
Recommendation:
Adjust the logic to ensure that when tokens queued in the priorityPool are staked, the generated LST shares are minted and transferred to the user immediately. This will ensure that users receive the correct shares and associated benefits from their staked assets.