sherlock-audit / 2024-06-velocimeter-judging

11 stars 7 forks source link

Active Lace Hippo - `Minter` Calculates Team Emissions Incorrectly #695

Closed sherlock-admin4 closed 4 months ago

sherlock-admin4 commented 4 months ago

Active Lace Hippo

Low/Info

Minter Calculates Team Emissions Incorrectly

Summary

The Minter contract unintentionally amplifies the _teamEmissions in excess of the declared amount.

Vulnerability Detail

The Minter contract exports the function update_period(), which is responsible for mint(address,uint256)ing the _flow token at a throttled pre-defined rate:

// update period can only be called once per cycle (1 week)
function update_period() external returns (uint) {
    uint _period = active_period;
@>  if (block.timestamp >= _period + WEEK && initializer == address(0)) { // only trigger if new week
        _period = (block.timestamp / WEEK) * WEEK;
        active_period = _period;
        uint256 weekly = weekly_emission();

@>      uint _teamEmissions = (teamRate * weekly) / (PRECISION - teamRate);

        uint _required =  weekly + _teamEmissions;
        uint _balanceOf = _flow.balanceOf(address(this));
        if (_balanceOf < _required) {
            _flow.mint(address(this), _required - _balanceOf);
        }

        require(_flow.transfer(teamEmissions, _teamEmissions));
        // ...
        emit Mint(msg.sender, weekly, circulating_supply());
    }
    return _period;
}

Specifically, the weekly _teamEmissions are calculated as follows:

uint _teamEmissions = (teamRate * weekly) / (PRECISION - teamRate);

[!NOTE] Here, teamRate is a scalar from 0 to MAX_TEAM RATE (50), and PRECISION is a constant divisor of 1000.

In this system, the MAX_TEAM RATE on weekly_emission()s is 50 / 1000 == 0.05 == 5%.

Notice then, that rather than scaling the weekly by treating the relation between teamRate and PRECISION as a ratio, we instead decrease the PRECISION of the calculation, inadvertently amplifying the evaluated ratio.

Let's imagine the teamRate is 5% and the weekly emissions were 1 ether. Instead of calculating 0.05 ether, instead we determine:

Welcome to Chisel! Type `!help` to show available commands.
➜ uint256 x = 1 ether * 50
➜ uint256 y = 1000 - 50
➜ x / y
Type: uint256
├ Hex: 0x00000000000000000000000000000000000000000000000000bafc24672035e5
├ Hex (full word): 0x00000000000000000000000000000000000000000000000000bafc24672035e5
└ Decimal: 52631578947368421

This is a relative overpayment of 5.2%.

Impact

Medium, the MAX_TEAM RATE invariant is not respected.

Aggregated across multiple gauges, this would result in unexpected dilution of _flow.

Code Snippet

uint _teamEmissions = (teamRate * weekly) /
     (PRECISION - teamRate);

https://github.com/sherlock-audit/2024-06-velocimeter/blob/63818925987a5115a80eff4bd12578146a844cfd/v4-contracts/contracts/Minter.sol#L119C13-L120C40

Tool used

Manual Review, Chisel

Recommendation

Correct the calculation:

uint _teamEmissions = (teamRate * weekly) /
-   (PRECISION - teamRate);
+   PRECISION;