[l-02] We can make Distribution::_stake function much more gas efficient.
Description: The Distribution::_stake function is checking the pool.minimalStake after transferring token from user to protocol. we can check exactly pool.minimalStake before the transferring operation and the logic will work same as intended.
Impact: The Distribution::_stake function is not gas efficient.
Proof of Concept:
The below code from Distribution::_stake is showing that the function is checking userData.deposited + amount_ greater than pool.minimalStake after the transfer operations from user to contract address. We can check the same logic before doing the token transfer and the function logic would be same as it should be, see recommended mitigation.
function _stake(address user_, uint256 poolId_, uint256 amount_, uint256 currentPoolRate_) private {
require(amount_ > 0, "DS: nothing to stake");
Pool storage pool = pools[poolId_];
PoolData storage poolData = poolsData[poolId_];
UserData storage userData = usersData[user_][poolId_];
if (pool.isPublic) {
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");
totalDepositedInPublicPools += amount_;
}
userData.pendingRewards = _getCurrentUserReward(currentPoolRate_, userData);
poolData.lastUpdate = uint128(block.timestamp);
poolData.rate = currentPoolRate_;
poolData.totalDeposited += amount_;
userData.lastStake = uint128(block.timestamp);
userData.rate = currentPoolRate_;
userData.deposited += amount_;
emit UserStaked(poolId_, user_, amount_);
}
Recommended Mitigation: The check for userData.deposited + amount_ greater than pool.minimalStake should be made before the transfer of token.
function _stake(address user_, uint256 poolId_, uint256 amount_, uint256 currentPoolRate_) private {
require(amount_ > 0, "DS: nothing to stake");
Pool storage pool = pools[poolId_];
PoolData storage poolData = poolsData[poolId_];
UserData storage userData = usersData[user_][poolId_];
+ require(userData.deposited + amount_ >= pool.minimalStake, "DS: amount too low");
if (pool.isPublic) {
// https://docs.lido.fi/guides/lido-tokens-integration-guide/#steth-internals-share-mechanics
uint256 balanceBefore_ = IERC20(depositToken).balanceOf(address(this)); //getting this contract token balance
//calling external contract addresss
IERC20(depositToken).safeTransferFrom(_msgSender(), address(this), amount_); //transfer token from user to this contract
uint256 balanceAfter_ = IERC20(depositToken).balanceOf(address(this)); //getting this contract balance after staking
amount_ = balanceAfter_ - balanceBefore_;
//@audit it should check before any transfer of token, which will costs less gas.
- require(userData.deposited + amount_ >= pool.minimalStake, "DS: amount too low");
totalDepositedInPublicPools += amount_;
}
userData.pendingRewards = _getCurrentUserReward(currentPoolRate_, userData);
// Update pool data
poolData.lastUpdate = uint128(block.timestamp);
poolData.rate = currentPoolRate_;
poolData.totalDeposited += amount_;
// Update user data
userData.lastStake = uint128(block.timestamp);
userData.rate = currentPoolRate_;
userData.deposited += amount_;
emit UserStaked(poolId_, user_, amount_);
}