Summary
Deposit and withdraw are useless
Vulnerability Details
Users don't have to stake inside the gauges to earn rewards. That is because both deposit and withdraw don't do anything but take tokens and record those balances inside 2 variables _totalSupply and _balances[user], which are never used
function stake(uint256 amount) external nonReentrant updateReward(msg.sender) {
if (amount == 0) revert InvalidAmount();
_totalSupply += amount;
_balances[msg.sender] += amount;
stakingToken.safeTransferFrom(msg.sender, address(this), amount);
emit Staked(msg.sender, amount);
}
function withdraw(uint256 amount) external nonReentrant updateReward(msg.sender) {
if (amount == 0) revert InvalidAmount();
if (_balances[msg.sender] < amount) revert InsufficientBalance();
_totalSupply -= amount;
_balances[msg.sender] -= amount;
stakingToken.safeTransfer(msg.sender, amount);
emit Withdrawn(msg.sender, amount);
}
Instead users earn rewards by just having veTokens:
function earned(address account) public view returns (uint256) {
return (getUserWeight(account) *
(getRewardPerToken() - userStates[account].rewardPerTokenPaid) / 1e18
) + userStates[account].rewards;
}
The even worse thing is that reward math takes into account _totalSupply to divide the rewards, but doesn't use _balances[user] to allocate each user rewards.
function getRewardPerToken() public view returns (uint256) {
if (totalSupply() == 0) {
return rewardPerTokenStored;
}
return rewardPerTokenStored + (
(lastTimeRewardApplicable() - lastUpdateTime) * rewardRate * 1e18 / totalSupply()
);
}
Impact
Contract does not work.
Reward math is broken
There are no incentives to deposit.
Tools Used
Manual review
Recommendations
Base rewards off user balances.