MorpheusAI

MorpheusAI
Foundry
22,500 USDC
View results
Submission Details
Severity: medium
Invalid

Incomplete Logic of `deposited_ == amount_` in `manageUsersInPrivatePool` Function and Missing Verification in `_send` and `_withdraw` in Private Pool in the `Distribution.sol` Contract

Summary

Incomplete logic of deposited_ == amount_ in manageUsersInPrivatePool function might lead to inconsistencies or unintended behavior. And missing verification in _send and _withdraw in private pool could allow unauthorized stakes, withdrawals or manipulation of balances.

Vulnerability Details

While both the stake and withdraw function are restricted to only public pools by the modifier poolPublic, manageUsersInPrivatePool function need to handle _stake and _withdraw in private pool. The lack of checks and access controls specific to private pools could create vulnerabilities. Malicious actors might exploit these vulnerabilities to manipulate pool funds or gain unauthorized access. For example , the else block in the _withdraw function subtracts the withdrawal amount from the user's deposited balance (deposited_) without check like it does in public pool.

Moreover, the manageUsersInPrivatePool function only calls _stake or _withdraw when there's a difference between deposited_ and amount_. The code doesn't handle the scenario where deposited_ equals amount_, potentially leading to missed updates or unexpected actions.

Therefore, it is recommended to add a check to verify for both private and public pools before proceeding, and add an else block to address deposited_ == amount_ as shown below:

Distribution::poolPublic
Distribution::manageUsersInPrivatePool
Distribution::stake
Distribution::_stake
Distribution::withdraw
Distribution::_withdraw

modifier poolPublic(uint256 poolId_) {
require(pools[poolId_].isPublic, "DS: pool isn't public");
_;
}
function manageUsersInPrivatePool(
uint256 poolId_,
address[] calldata users_,
uint256[] calldata amounts_
) external onlyOwner poolExists(poolId_) {
...
if (deposited_ < amount_) {
_stake(user_, poolId_, amount_ - deposited_, currentPoolRate_);
} else if (deposited_ > amount_) {
_withdraw(user_, poolId_, deposited_ - amount_, currentPoolRate_);
+ } else {
+ // Revert if deposited_ == amount_
+ revert("DS: no change in deposit");
}
}
...
}
function stake(uint256 poolId_, uint256 amount_) external poolExists(poolId_) poolPublic(poolId_) {
_stake(_msgSender(), poolId_, amount_, _getCurrentPoolRate(poolId_));
}
function _stake(address user_, uint256 poolId_, uint256 amount_, uint256 currentPoolRate_) private {
...
- if (pool.isPublic) {
// https://docs.lido.fi/guides/lido-tokens-integration-guide/#steth-internals-share-mechanics
uint256 balanceBefore_ = IERC20(depositToken).balanceOf(address(this));
IERC20(depositToken).safeTransferFrom(_msgSender(), address(this), amount_);
uint256 balanceAfter_ = IERC20(depositToken).balanceOf(address(this));
amount_ = balanceAfter_ - balanceBefore_;
require(userData.deposited + amount_ >= pool.minimalStake, "DS: amount too low");
+ if (pool.isPublic) {
totalDepositedInPublicPools += amount_;
}
...
function withdraw(uint256 poolId_, uint256 amount_) external poolExists(poolId_) poolPublic(poolId_) {
_withdraw(_msgSender(), poolId_, amount_, _getCurrentPoolRate(poolId_));
}
function _withdraw(address user_, uint256 poolId_, uint256 amount_, uint256 currentPoolRate_) private {
...
- if (pool.isPublic) {
require(
block.timestamp < pool.payoutStart ||
(block.timestamp > pool.payoutStart + pool.withdrawLockPeriod &&
block.timestamp > userData.lastStake + pool.withdrawLockPeriodAfterStake),
"DS: pool withdraw is locked"
);
uint256 depositTokenContractBalance_ = IERC20(depositToken).balanceOf(address(this));
if (amount_ > depositTokenContractBalance_) {
amount_ = depositTokenContractBalance_;
}
newDeposited_ = deposited_ - amount_;
require(amount_ > 0, "DS: nothing to withdraw");
require(newDeposited_ >= pool.minimalStake || newDeposited_ == 0, "DS: invalid withdraw amount");
- } else {
- newDeposited_ = deposited_ - amount_;
- }
...

Impact

Incomplete logic of deposited_ == amount_ in manageUsersInPrivatePool function might lead to inconsistencies or unintended behavior. And allowing stakes and withdrawals from non-public pools without restrictions can pose a security risk.

Tools Used

Manual Review

Recommendation

Therefore, it is recommended to add a check to verify for both private and public pools before proceeding, and add an else block to address deposited_ == amount_ as shown in Vulnerability Details.

Updates

Lead Judging Commences

inallhonesty Lead Judge
over 1 year ago
inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.