[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_);
}