MEDIUM severity: Binary search implementation may not always find the optimal solution. **Description:**
`VeryFastRouter::_findMaxFillableAmtForBuy` and `VeryFastRouter::_findMaxFillableAmtForSell` utilize binary search to determine the maximum trade amount. The binary search seeks a solution in a linear, sorted space based on a test function (a criteria function used to decide the...

Vulnerable code:
function testSwapBinarySearch_audit() public{
        //START_INDEX=0, END_INDEX=10
        uint256[] memory nftIds;
        LSSVMPair pair;
        uint256 numNFTsForQuote = END_INDEX + 1;

        //1. create an array of nft ids
        nftIds = _getArray(START_INDEX, END_INDEX);
        assertEq(nftIds.length, END_INDEX - START_INDEX + 1);

        //2. setup a ERC721 ETH pair with property checker is zero address and zero deposit amount
        pair = setUpPairERC721ForSale(0, address(0), nftIds);

        //3. get the inputAmount needed to buy all NFTs in the pool
        (,,,uint256 inputAmount,,) = pair.getBuyNFTQuote(0, numNFTsForQuote);

        //4. get partialFillAmounts
        uint256[] memory partialFillAmounts = router.getNFTQuoteForBuyOrderWithPartialFill(pair, numNFTsForQuote, 0, 0);

        console.log("*********Max cost generated by getNFTQuoteForBuyOrderWithPartialFill**********");
        for(uint256 i; i< END_INDEX-START_INDEX + 1; i++){
            console.log("Max Cost (default) to buy %i items, %i", i+1, partialFillAmounts[i]);
        }

        // with no slippage, inputAmount should exactly be equal to the last partialFillAmount
        assertEq(inputAmount, partialFillAmounts[END_INDEX-START_INDEX]);


        uint256 midIndex = (END_INDEX - START_INDEX + 1 ) / 2;
        console.log("**********Max cost custom created by a user**********");
         for(uint256 j; j< END_INDEX-START_INDEX + 1; j++){
            if(j <= midIndex){
                    partialFillAmounts[j] -= partialFillAmounts[j] * 25 / 100; //@audit reduce max bid by 25%
            }
            else{
                   partialFillAmounts[j] += partialFillAmounts[j] * 10 / 100; //@audit increase max bid by 10%
            }
            console.log("Max Cost (custom) to buy %i items, %i", j+1, partialFillAmounts[j]);
        }
        //6 creating a single buy order for the pair
        VeryFastRouter.BuyOrderWithPartialFill memory buyOrder = VeryFastRouter.BuyOrderWithPartialFill({
            pair: pair,
            nftIds: nftIds,
            maxInputAmount: inputAmount,
            ethAmount: inputAmount,
            expectedSpotPrice: pair.spotPrice() + 1, //getPairBaseQuoteTokenBalancetriggers partial fill logic
            isERC721: true,
            maxCostPerNumNFTs: partialFillAmounts
        });

        VeryFastRouter.BuyOrderWithPartialFill[] memory buyOrders =
            new VeryFastRouter.BuyOrderWithPartialFill[](1); //adding a single buy order
        VeryFastRouter.SellOrderWithPartialFill[] memory sellOrders; //no sell orders, just a 0 length array

        buyOrders[0] = buyOrder;

        //check that nft recipient before swap does not have any nfts
        assertEq(IERC721(pair.nft()).balanceOf(NFT_RECIPIENT),0);

        //-n check that pair holds all the nfts that are part of the buy order
        assertEq(IERC721(pair.nft()).balanceOf(address(pair)), END_INDEX - START_INDEX + 1);

        VeryFastRouter.Order memory swapOrder = VeryFastRouter.Order({
            buyOrders: buyOrders,
            sellOrders: sellOrders, //-n no sell orders for this case
            tokenRecipient: payable(address(TOKEN_RECIPIENT)),
            nftRecipient: NFT_RECIPIENT,
            recycleETH: false
        });

        // Prank as the router caller and do the swap
        vm.startPrank(ROUTER_CALLER);
        address tokenAddress = getTokenAddress();

        // Set up approval for token if it is a token pair (for router caller)
        if (tokenAddress != address(0)) {
            ERC20(tokenAddress).approve(address(router), 1e18 ether); //-n give approval for very high amount
            IMintable(tokenAddress).mint(ROUTER_CALLER, 1e18 ether); //-n mint 1e18 ether
        }

        // // Store the swap results
        uint256[] memory swapResults = router.swap{value: inputAmount}(swapOrder);
        vm.stopPrank();

        //-n should only have one order
        assertEq(swapResults.length, buyOrders.length);

        console.log("Total NFTs in the pair", IERC721(pair.nft()).balanceOf(address(pair)));
        console.log("Total NFTs received by nft recipient", IERC721(pair.nft()).balanceOf(NFT_RECIPIENT));

        assertEq(IERC721(pair.nft()).balanceOf(NFT_RECIPIENT), midIndex);

    }

*********Max cost generated by getNFTQuoteForBuyOrderWithPartialFill**********
  Max Cost (default) to buy 1 items, 1710339358116313477
  Max Cost (default) to buy 2 items, 3339233984893754885
  Max Cost (default) to buy 3 items, 4890562200872270508
  Max Cost (default) to buy 4 items, 6368017644661333008
  Max Cost (default) to buy 5 items, 7775118067317583008
  Max Cost (default) to buy 6 items, 9115213707942583008
  Max Cost (default) to buy 7 items, 10391495270442583008
  Max Cost (default) to buy 8 items, 11607001520442583008
  Max Cost (default) to buy 9 items, 12764626520442583008
  Max Cost (default) to buy 10 items, 13867126520442583003
  Max Cost (default) to buy 11 items, 14917126520442583017
  **********Max cost custom created by a user**********
  Max Cost (custom) to buy 1 items, 1282754518587235108
  Max Cost (custom) to buy 2 items, 2504425488670316164
  Max Cost (custom) to buy 3 items, 3667921650654202881
  Max Cost (custom) to buy 4 items, 4776013233495999756
  Max Cost (custom) to buy 5 items, 5831338550488187256
  Max Cost (custom) to buy 6 items, 6836410280956937256
  Max Cost (custom) to buy 7 items, 11430644797486841308
  Max Cost (custom) to buy 8 items, 12767701672486841308
  Max Cost (custom) to buy 9 items, 14041089172486841308
  Max Cost (custom) to buy 10 items, 15253839172486841303
  Max Cost (custom) to buy 11 items, 16408839172486841318
  Total NFTs in the pair 6
  Total NFTs received by nft recipient 5