HIGH severity: Native tokens permanently stuck in `StrategyPassiveManagerUniswap` contract due to rounding in `_chargeFees`. **Description:** `StrategyPassiveManagerUniswap::_chargeFees` converts LP fees into the native token then distributes the native tokens split between:
* the caller as a reward for initiating the harvest
* beefy protocol
* the strategist registered with the strategy

However due to [rounding during.....

Vulnerable code:
pragma solidity 0.8.23;

import {Test, console} from "forge-std/Test.sol";
import {IERC20} from "@openzeppelin-4/contracts/token/ERC20/ERC20.sol";
import {BeefyVaultConcLiq} from "contracts/protocol/concliq/vault/BeefyVaultConcLiq.sol";
import {BeefyVaultConcLiqFactory} from "contracts/protocol/concliq/vault/BeefyVaultConcLiqFactory.sol";
import {StrategyPassiveManagerUniswap} from "contracts/protocol/concliq/uniswap/StrategyPassiveManagerUniswap.sol";
import {StrategyFactory} from "contracts/protocol/concliq/uniswap/StrategyFactory.sol";
import {StratFeeManagerInitializable} from "contracts/protocol/beefy/StratFeeManagerInitializable.sol";
import {IStrategyConcLiq} from "contracts/interfaces/beefy/IStrategyConcLiq.sol";
import {UniV3Utils} from "contracts/interfaces/exchanges/UniV3Utils.sol";

// Test WBTC/USDC Uniswap Strategy
contract ConLiqWBTCUSDCTest is Test {
    BeefyVaultConcLiq vault;
    BeefyVaultConcLiqFactory vaultFactory;
    StrategyPassiveManagerUniswap strategy;
    StrategyPassiveManagerUniswap implementation;
    StrategyFactory factory;
    address constant pool = 0x9a772018FbD77fcD2d25657e5C547BAfF3Fd7D16;
    address constant token0 = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599;
    address constant token1 = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
    address constant native = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address constant strategist = 0xb2e4A61D99cA58fB8aaC58Bb2F8A59d63f552fC0;
    address constant beefyFeeRecipient = 0x65f2145693bE3E75B8cfB2E318A3a74D057e6c7B;
    address constant beefyFeeConfig = 0x3d38BA27974410679afF73abD096D7Ba58870EAd;
    address constant unirouter = 0xE592427A0AEce92De3Edee1F18E0157C05861564;
    address constant keeper = 0x4fED5491693007f0CD49f4614FFC38Ab6A04B619;
    int24 constant width = 500;
    address constant user = 0x161D61e30284A33Ab1ed227beDcac6014877B3DE;
    bytes tradePath1;
    bytes tradePath2;
    bytes path0;
    bytes path1;

    function setUp() public {
        BeefyVaultConcLiq vaultImplementation = new BeefyVaultConcLiq();
        vaultFactory = new BeefyVaultConcLiqFactory(address(vaultImplementation));
        vault = vaultFactory.cloneVault();
        implementation = new StrategyPassiveManagerUniswap();
        factory = new StrategyFactory(keeper);

        address[] memory lpToken0ToNative = new address[](2);
        lpToken0ToNative[0] = token0;
        lpToken0ToNative[1] = native;

        address[] memory lpToken1ToNative = new address[](2);
        lpToken1ToNative[0] = token1;
        lpToken1ToNative[1] = native;

        uint24[] memory fees = new uint24[](1);
        fees[0] = 500;

        path0 = routeToPath(lpToken0ToNative, fees);
        path1 = routeToPath(lpToken1ToNative, fees);

        address[] memory tradeRoute1 = new address[](2);
        tradeRoute1[0] = token0;
        tradeRoute1[1] = token1;

        address[] memory tradeRoute2 = new address[](2);
        tradeRoute2[0] = token1;
        tradeRoute2[1] = token0;

        tradePath1 = routeToPath(tradeRoute1, fees);
        tradePath2 = routeToPath(tradeRoute2, fees);

        StratFeeManagerInitializable.CommonAddresses memory commonAddresses = StratFeeManagerInitializable.CommonAddresses(
            address(vault),
            unirouter,
            keeper,
            strategist,
            beefyFeeRecipient,
            beefyFeeConfig
        );

        factory.addStrategy("StrategyPassiveManagerUniswap_v1", address(implementation));

        address _strategy = factory.createStrategy("StrategyPassiveManagerUniswap_v1");
        strategy = StrategyPassiveManagerUniswap(_strategy);
        strategy.initialize(
            pool,
            native,
            width,
            path0,
            path1,
            commonAddresses
        );

        // render calm check ineffective to allow deposit to work; not related to the
        // identified bug, for some reason (possibly block forking) the first deposit
        // was failing due to the calm check
        strategy.setTwapInterval(1);

        vault.initialize(address(strategy), "Moo Vault", "mooVault");
    }

    function test_StrategyAccumulatesNativeFeeTokensDueToRounding() public {
        // strategy has no native tokens
        assertEq(IERC20(native).balanceOf(address(strategy)), 0);

        // fuzzer has no native tokens
        assertEq(IERC20(native).balanceOf(address(this)), 0);

        // user deposits a large amount; Beefy will use this
        // to establish an LP position to start earning fees
        deposit(100e8, 6000000e6);

        // user performs a couple of trades between BTC/USDC
        // this will generate LP fees
        trade(true, 3e8);
        trade(false, 123457e6);

        // trigger a Beefy harvest; this will collect the LP
        // fees, convert them into native tokens then distribute
        // all the converted native tokens between:
        // * this contract as the caller of the harvest
        // * beefy
        // * the strategist registered with the strategy
        skip(10 hours);
        strategy.harvest(address(this));

        // verify that this contract has received some LP fees
        // converted into native tokens
        assert(IERC20(native).balanceOf(address(this)) > 0);

        // none of the native tokens that were converted from the
        // collected fees should remain in strategy contract
        // this fails due to rounding during division in
        // `StrategyPassiveManagerUniswap::_chargeFees` which will
        // result in native tokens converted from fees accumulating
        // and being permanently stuck in the strategy contract
        assertEq(IERC20(native).balanceOf(address(strategy)), 0);
    }

    function deposit(uint256 token0Amount, uint256 token1Amount) public {
        vm.startPrank(user);

        deal(address(token0), user, token0Amount);
        deal(address(token1), user, token1Amount);

        IERC20(token0).approve(address(vault), token0Amount);
        IERC20(token1).approve(address(vault), token1Amount);

        uint _shares = vault.previewDeposit(token0Amount, token1Amount);

        vault.depositAll(_shares);

        vm.stopPrank();
    }

    function trade(bool tokenInd, uint256 tokenAmount) public {
        vm.startPrank(user);

        if(tokenInd) {
            deal(address(token0), user, tokenAmount);

            IERC20(token0).approve(address(unirouter), tokenAmount);
            UniV3Utils.swap(unirouter, tradePath1, tokenAmount);
        }
        else {
            deal(address(token1), user, tokenAmount);

            IERC20(token1).approve(address(unirouter), tokenAmount);
            UniV3Utils.swap(unirouter, tradePath2, tokenAmount);
        }

        vm.stopPrank();
    }

    // Convert token route to encoded path
    // uint24 type for fees so path is packed tightly
    function routeToPath(
        address[] memory _route,
        uint24[] memory _fee
    ) internal pure returns (bytes memory path) {
        path = abi.encodePacked(_route[0]);
        uint256 feeLength = _fee.length;
        for (uint256 i = 0; i < feeLength; i++) {
            path = abi.encodePacked(path, _fee[i], _route[i+1]);
        }
    }
}

uint256 callFeeAmount = nativeEarned * fees.call / DIVISOR;
IERC20Metadata(native).safeTransfer(_callFeeRecipient, callFeeAmount);

uint256 strategistFeeAmount = nativeEarned * fees.strategist / DIVISOR;
IERC20Metadata(native).safeTransfer(strategist, strategistFeeAmount);

uint256 beefyFeeAmount = nativeEarned - callFeeAmount - strategistFeeAmount;
IERC20Metadata(native).safeTransfer(beefyFeeRecipient, beefyFeeAmount);