New Treasury Managers
StakingManager.sol
[CRITICAL] Missing Reentrancy Protection
The stake(), unstake(), and claim() functions perform external calls but lack reentrancy protection, potentially allowing attackers to manipulate state during external calls.
Recommended solution:
function stake(uint _amount) external nonReentrant {
// ..
}
function unstake(uint _amount) external nonReentrant {
// ..
}
function claim() public nonReentrant returns (uint) {
// ..
}
[HIGH] Precision Loss in Reward Calculations
Small fee amounts may result in 0 rewards due to precision loss, especially when totalDeposited is large.
Recommended solution:
function _withdrawFees() internal {
// ..
// @audit Check for minimum fee threshold to prevent precision loss
uint minimumFeeThreshold = totalDeposited / 1e18; // Adjust threshold as needed
if (availableFees < minimumFeeThreshold) {
// @audit Skip small fee distributions to prevent precision loss
return;
}
// Update the global ETH rewards per token snapshot
globalEthRewardsPerTokenX128 += FullMath.mulDiv(availableFees, FixedPoint128.Q128, totalDeposited);
}
[MEDIUM] Division by Zero in balances() Function
The balances() function will revert when totalDeposited is 0, preventing users from checking their balances or claiming rewards. This occurs in the FullMath.mulDiv calculation where totalDeposited is used as the denominator.
Recommended solution:
function balances(address _recipient) public view override returns (uint balance_) {
// Capture our availableFees that are waiting to be claimed from the {FeeEscrow}
uint availableFees = managerFees() - _lastWithdrawBalance;
// @audit Get the existing eth owed to the caller
Position memory position = userPositions[_recipient];
uint stakeBalance = position.ethOwed;
// @audit Only calculate the `latestGlobalEthRewardsPerTokenX128` if totalDeposited != 0
if (totalDeposited == 0) {
// Get the total ETH owed to the user from their staked position, calculating the
// latest `globalEthRewardsPerTokenX128` based on the available fees balance
uint latestGlobalEthRewardsPerTokenX128 = globalEthRewardsPerTokenX128 + FullMath.mulDiv(
availableFees,
FixedPoint128.Q128,
totalDeposited
);
// Calculate the stake balance based on the latest `globalEthRewardsPerTokenX128`
stakeBalance += FullMath.mulDiv(
latestGlobalEthRewardsPerTokenX128 - position.ethRewardsPerTokenSnapshotX128,
position.amount,
FixedPoint128.Q128
);
}
// We then need to check if the `_recipient` is the creator of any tokens
uint creatorBalance = pendingCreatorFees(_recipient);
// We then need to check if the `_recipient` is the owner of the manager
uint ownerBalance;
if (_recipient == managerOwner) {
ownerBalance = claimableOwnerFees();
}
balance_ = stakeBalance + creatorBalance + ownerBalance;
}