code-423n4 / 2022-05-velodrome-findings

0 stars 0 forks source link

Anyone can add Gauge reward tokens and cause DoS #190

Closed code423n4 closed 2 years ago

code423n4 commented 2 years ago

Lines of code

https://github.com/code-423n4/2022-05-velodrome/blob/7fda97c570b758bbfa7dd6724a336c43d4041740/contracts/contracts/Gauge.sol#L590

Vulnerability details

Impact

The Gauge.notifyRewardAmount function does not have any access restriction. Anyone (an attacker) can frontrun and call this function to add arbitrary (even malicious) gauge reward tokens up to MAX_REWARD_TOKENS = 16.

An attacker is able to frontrun and add 16 fake ERC20 token contracts. Due to the limit of MAX_REWARD_TOKENS, no more gauge reward tokens can be added. Any attempt to add a new token will revert.

This will prevent adding the proper reward tokens due to DoS when calling the Gauge.notifyRewardAmount function and results in DoS when calling the Voter.distribute function.

Proof of Concept

Gauge.sol#L590

function notifyRewardAmount(address token, uint amount) external lock { // @audit-info no access restriction
    require(token != stake);
    require(amount > 0);
    if (!isReward[token]) {
        require(rewards.length < MAX_REWARD_TOKENS, "too many rewards tokens");
    }
    // rewards accrue only during the bribe period
    uint bribeStart = block.timestamp - (block.timestamp % (7 days)) + BRIBE_LAG;
    uint adjustedTstamp = block.timestamp < bribeStart ? bribeStart : bribeStart + 7 days;
    if (rewardRate[token] == 0) _writeRewardPerTokenCheckpoint(token, 0, adjustedTstamp);
    (rewardPerTokenStored[token], lastUpdateTime[token]) = _updateRewardPerToken(token);
    _claimFees();

    if (block.timestamp >= periodFinish[token]) {
        _safeTransferFrom(token, msg.sender, address(this), amount);
        rewardRate[token] = amount / DURATION;
    } else {
        uint _remaining = periodFinish[token] - block.timestamp;
        uint _left = _remaining * rewardRate[token];
        require(amount > _left);
        _safeTransferFrom(token, msg.sender, address(this), amount);
        rewardRate[token] = (amount + _left) / DURATION;
    }
    require(rewardRate[token] > 0);
    uint balance = IERC20(token).balanceOf(address(this));
    require(rewardRate[token] <= balance / DURATION, "Provided reward too high");
    periodFinish[token] = adjustedTstamp + DURATION;
    if (!isReward[token]) {
        isReward[token] = true;
        rewards.push(token);
        IBribe(bribe).addRewardToken(token);
    }

    emit NotifyReward(msg.sender, token, amount);
}

Tools Used

Manual review

Recommended mitigation steps

Consider adding access restriction to the Gauge.notifyRewardAmount function to prevent malicious actors from calling the function or add a whitelist of possible reward tokens.

pooltypes commented 2 years ago

Duplicate of #182

GalloDaSballo commented 2 years ago

Dup by same warden, closing