Inconsistency in BunniToken Price Calculation
The deviation check (_validateReserves()) from BunniPrice.sol considers both position reserves and uncollected fees when validating the deviation with TWAP, while the final price calculation (_getTotalValue()) only accounts for position reserves, excluding uncollected fees.\\nThe same is applied to BunniSupply.sol where `getProtocolOwnedLiquidityOhm()` validates reserves + fee deviation from TWAP and then returns only Ohm reserves using `lens_.getReserves(key_)`\\nNote that `BunniSupply.sol#getProtocolOwnedLiquidityReserves()` validates deviation using reserves+fees with TWAP and then return reserves+fees in a good way without discrepancy.\\nBut this could lead to a misalignment between the deviation check and actual price computation.\\nDeviation Check : `_validateReserves` Function:\\n```\\n### BunniPrice.sol and BunniSupply.sol : \\n    function _validateReserves( BunniKey memory key_,BunniLens lens_,uint16 twapMaxDeviationBps_,uint32 twapObservationWindow_) internal view \\n        {\\n        uint256 reservesTokenRatio = BunniHelper.getReservesRatio(key_, lens_);\\n        uint256 twapTokenRatio = UniswapV3OracleHelper.getTWAPRatio(address(key_.pool),twapObservationWindow_);\\n\\n        // Revert if the relative deviation is greater than the maximum.\\n        if (\\n            // `isDeviatingWithBpsCheck()` will revert if `deviationBps` is invalid.\\n            Deviation.isDeviatingWithBpsCheck(\\n                reservesTokenRatio,\\n                twapTokenRatio,\\n                twapMaxDeviationBps_,\\n                TWAP_MAX_DEVIATION_BASE\\n            )\\n        ) {\\n            revert BunniPrice_PriceMismatch(address(key_.pool), twapTokenRatio, reservesTokenRatio);\\n        }\\n    }\\n\\n### BunniHelper.sol : \\n    function getReservesRatio(BunniKey memory key_, BunniLens lens_) public view returns (uint256) {\\n        IUniswapV3Pool pool = key_.pool;\\n        uint8 token0Decimals = ERC20(pool.token0()).decimals();\\n\\n        (uint112 reserve0, uint112 reserve1) = lens_.getReserves(key_);\\n        \\n        //E compute fees and return values \\n        (uint256 fee0, uint256 fee1) = lens_.getUncollectedFees(key_);\\n        \\n        //E calculates ratio of token1 in token0\\n        return (reserve1 + fee1).mulDiv(10 ** token0Decimals, reserve0 + fee0);\\n    }\\n\\n### UniswapV3OracleHelper.sol : \\n    //E Returns the ratio of token1 to token0 in token1 decimals based on the TWAP\\n        //E used in bophades/src/modules/PRICE/submodules/feeds/BunniPrice.sol, and SPPLY/submodules/BunniSupply.sol\\n    function getTWAPRatio(\\n        address pool_, \\n        uint32 period_ //E period of the TWAP in seconds \\n    ) public view returns (uint256) \\n    {\\n        //E return the time-weighted tick from period_ to now\\n        int56 timeWeightedTick = getTimeWeightedTick(pool_, period_);\\n\\n        IUniswapV3Pool pool = IUniswapV3Pool(pool_);\\n        ERC20 token0 = ERC20(pool.token0());\\n        ERC20 token1 = ERC20(pool.token1());\\n\\n        // Quantity of token1 for 1 unit of token0 at the time-weighted tick\\n        // Scale: token1 decimals\\n        uint256 baseInQuote = OracleLibrary.getQuoteAtTick(\\n            int24(timeWeightedTick),\\n            uint128(10 ** token0.decimals()), // 1 unit of token0 => baseAmount\\n            address(token0),\\n            address(token1)\\n        );\\n        return baseInQuote;\\n    }\\n```\\n\\nYou can see that the deviation check includes uncollected fees in the `reservesTokenRatio`, potentially leading to a higher or more volatile ratio compared to the historical `twapTokenRatio`.\\nFinal Price Calculation in `BunniPrice.sol#_getTotalValue()` :\\n```\\n    function _getTotalValue(\\n        BunniToken token_,\\n        BunniLens lens_,\\n        uint8 outputDecimals_\\n    ) internal view returns (uint256) {\\n        (address token0, uint256 reserve0, address token1, uint256 reserve1) = _getBunniReserves(\\n            token_,\\n            lens_,\\n            outputDecimals_\\n        );\\n        uint256 outputScale = 10 ** outputDecimals_;\\n\\n        // Determine the value of each reserve token in USD\\n        uint256 totalValue;\\n        totalValue += _PRICE().getPrice(token0).mulDiv(reserve0, outputScale);\\n        totalValue += _PRICE().getPrice(token1).mulDiv(reserve1, outputScale);\\n\\n        return totalValue;\\n    }\\n```\\n\\nYou can see that this function (_getTotalValue()) excludes uncollected fees in the final valuation, potentially overestimating the total value within deviation check process, meaning the check could pass in certain conditions whereas it could have not pass if fees where not accounted on the deviation check. Moreover the below formula used :\\n$$ price_{LP} = {reserve_0 \\times price_0 + reserve_1 \\times price_1} $$\\nwhere $reserve_i$ is token $i$ reserve amount, $price_i$ is the price of token $i$\\nIn short, it is calculated by getting all underlying balances, multiplying those by their market prices\\nHowever, this approach of directly computing the price of LP tokens via spot reserves is well-known to be vulnerable to manipulation, even if TWAP Deviation is checked, the above summary proved that this method is not 100% bullet proof as there are discrepancy on what is mesured. Taken into the fact that the process to check deviation is not that good plus the fact that methodology used to compute price is bad, the impact of this is high\\nThe same can be found in BunnySupply.sol `getProtocolOwnedLiquidityReserves()` :\\n```\\n    function getProtocolOwnedLiquidityReserves()\\n        external\\n        view\\n        override\\n        returns (SPPLYv1.Reserves[] memory)\\n    {\\n        // Iterate through tokens and total up the reserves of each pool\\n        uint256 len = bunniTokens.length;\\n        SPPLYv1.Reserves[] memory reserves = new SPPLYv1.Reserves[](len);\\n        for (uint256 i; i < len; ) {\\n            TokenData storage tokenData = bunniTokens[i];\\n            BunniToken token = tokenData.token;\\n            BunniLens lens = tokenData.lens;\\n            BunniKey memory key = _getBunniKey(token);\\n            (\\n                address token0,\\n                address token1,\\n                uint256 reserve0,\\n                uint256 reserve1\\n            ) = _getReservesWithFees(key, lens);\\n\\n            // Validate reserves\\n            _validateReserves(\\n                key,\\n                lens,\\n                tokenData.twapMaxDeviationBps,\\n                tokenData.twapObservationWindow\\n            );\\n\\n            address[] memory underlyingTokens = new address[](2);\\n            underlyingTokens[0] = token0;\\n            underlyingTokens[1] = token1;\\n            uint256[] memory underlyingReserves = new uint256[](2);\\n            underlyingReserves[0] = reserve0;\\n            underlyingReserves[1] = reserve1;\\n\\n            reserves[i] = SPPLYv1.Reserves({\\n                source: address(token),\\n                tokens: underlyingTokens,\\n                balances: underlyingReserves\\n            });\\n\\n            unchecked {\\n                ++i;\\n            }\\n        }\\n\\n        return reserves;\\n    }\\n```\\n\\nWhere returned value does not account for uncollected fees whereas deviation check was accounting for it
Align the methodology used in both the deviation check and the final price computation. This could involve either including the uncollected fees in both calculations or excluding them in both.\\nIt's ok for BunniSupply as there are 2 functions handling both reserves and reserves+fees but change deviation check process on the second one to include only reserves when checking deviation twap ratio
`_getTotalValue()` from BunniPrice.sol and `getProtocolOwnedLiquidityReserves()` from BunniSupply.sol have both ratio computation that includes uncollected fees to compare with TWAP ratio, potentially overestimating the total value compared to what these functions are aim to, which is returning only the reserves or LP Prices by only taking into account the reserves of the pool. Meaning the check could pass in certain conditions where fees are included in the ratio computation and the deviation check process whereas the deviation check should not have pass without the fees accounted.
```\\n### BunniPrice.sol and BunniSupply.sol : \\n    function _validateReserves( BunniKey memory key_,BunniLens lens_,uint16 twapMaxDeviationBps_,uint32 twapObservationWindow_) internal view \\n        {\\n        uint256 reservesTokenRatio = BunniHelper.getReservesRatio(key_, lens_);\\n        uint256 twapTokenRatio = UniswapV3OracleHelper.getTWAPRatio(address(key_.pool),twapObservationWindow_);\\n\\n        // Revert if the relative deviation is greater than the maximum.\\n        if (\\n            // `isDeviatingWithBpsCheck()` will revert if `deviationBps` is invalid.\\n            Deviation.isDeviatingWithBpsCheck(\\n                reservesTokenRatio,\\n                twapTokenRatio,\\n                twapMaxDeviationBps_,\\n                TWAP_MAX_DEVIATION_BASE\\n            )\\n        ) {\\n            revert BunniPrice_PriceMismatch(address(key_.pool), twapTokenRatio, reservesTokenRatio);\\n        }\\n    }\\n\\n### BunniHelper.sol : \\n    function getReservesRatio(BunniKey memory key_, BunniLens lens_) public view returns (uint256) {\\n        IUniswapV3Pool pool = key_.pool;\\n        uint8 token0Decimals = ERC20(pool.token0()).decimals();\\n\\n        (uint112 reserve0, uint112 reserve1) = lens_.getReserves(key_);\\n        \\n        //E compute fees and return values \\n        (uint256 fee0, uint256 fee1) = lens_.getUncollectedFees(key_);\\n        \\n        //E calculates ratio of token1 in token0\\n        return (reserve1 + fee1).mulDiv(10 ** token0Decimals, reserve0 + fee0);\\n    }\\n\\n### UniswapV3OracleHelper.sol : \\n    //E Returns the ratio of token1 to token0 in token1 decimals based on the TWAP\\n        //E used in bophades/src/modules/PRICE/submodules/feeds/BunniPrice.sol, and SPPLY/submodules/BunniSupply.sol\\n    function getTWAPRatio(\\n        address pool_, \\n        uint32 period_ //E period of the TWAP in seconds \\n    ) public view returns (uint256) \\n    {\\n        //E return the time-weighted tick from period_ to now\\n        int56 timeWeightedTick = getTimeWeightedTick(pool_, period_);\\n\\n        IUniswapV3Pool pool = IUniswapV3Pool(pool_);\\n        ERC20 token0 = ERC20(pool.token0());\\n        ERC20 token1 = ERC20(pool.token1());\\n\\n        // Quantity of token1 for 1 unit of token0 at the time-weighted tick\\n        // Scale: token1 decimals\\n        uint256 baseInQuote = OracleLibrary.getQuoteAtTick(\\n            int24(timeWeightedTick),\\n            uint128(10 ** token0.decimals()), // 1 unit of token0 => baseAmount\\n            address(token0),\\n            address(token1)\\n        );\\n        return baseInQuote;\\n    }\\n```\\n