HIGH severity: Linearity assumption on the royalty can lead to denial of service. **Description:**
`VeryFastRouter::swap` relies on the internal functions [`VeryFastRouter::_findMaxFillableAmtForSell`](https://github.com/sudoswap/lssvm2/blob/78d38753b2042d7813132f26e5573c6699b605ef/src/VeryFastRouter.sol#L556) and...

Vulnerable code:
VeryFastRouter.sol
576:         // Perform binary search
577:         while (start <= end) {
578:             // We check the price to sell index + 1
579:             (
580:                 CurveErrorCodes.Error error,
581:                 /* newSpotPrice */
582:                 ,
583:                 /* newDelta */
584:                 ,
585:                 uint256 currentOutput,
586:                 /* tradeFee */
587:                 ,
588:                 /* protocolFee */
589:             ) = pair.bondingCurve().getSellInfo(
590:                 spotPrice,
591:                 // get delta from deltaAndFeeMultiplier
592:                 uint128(deltaAndFeeMultiplier >> 96),
593:                 (start + end) / 2,
594:                 // get feeMultiplier from deltaAndFeeMultiplier
595:                 uint96(deltaAndFeeMultiplier),
596:                 protocolFeeMultiplier
597:             );
598:             currentOutput -= currentOutput * royaltyAmount / BASE;//@audit-info assumes royalty amount is linear
599:             // If the bonding curve has a math error, or
600:             // if the current output is too low relative to our max output, or
601:             // if the current output is greater than the pair's token balance,
602:             // then we recurse on the left half (i.e. less items)
603:             if (
604:                 error != CurveErrorCodes.Error.OK || currentOutput < minOutputPerNumNFTs[(start + end) / 2 - 1] /* this is the max cost we are willing to pay, zero-indexed */
605:                     || currentOutput > pairTokenBalance
606:             ) {
607:                 end = (start + end) / 2 - 1;
608:             }
609:             // Otherwise, we recurse on the right half (i.e. more items)
610:             else {
611:                 numItemsToFill = (start + end) / 2;
612:                 start = (start + end) / 2 + 1;
613:                 priceToFillAt = currentOutput;
614:             }
615:         }

function getKODAV2RoyaltyInfo(address _tokenAddress, uint256 _id, uint256 _amount)
        external
        view
        override
        returns (address payable[] memory receivers, uint256[] memory amounts)
    {
        // Get the edition the token is part of
        uint256 _editionNumber = IKODAV2(_tokenAddress).editionOfTokenId(_id);
        require(_editionNumber > 0, "Edition not found for token ID");

        // Get existing artist commission
        (address artistAccount, uint256 artistCommissionRate) = IKODAV2(_tokenAddress).artistCommission(_editionNumber);

        // work out the expected royalty payment
        uint256 totalRoyaltyToPay = (_amount / modulo) * creatorRoyaltiesFee;

        // Get optional commission set against the edition and work out the expected commission
        (uint256 optionalCommissionRate, address optionalCommissionRecipient) =
            IKODAV2(_tokenAddress).editionOptionalCommission(_editionNumber);
        if (optionalCommissionRate > 0) {
            receivers = new address payable[](2);
            amounts = new uint256[](2);

            uint256 totalCommission = artistCommissionRate + optionalCommissionRate;

            // Add the artist and commission
            receivers[0] = payable(artistAccount);
            amounts[0] = (totalRoyaltyToPay / totalCommission) * artistCommissionRate;//@audit-info rounding occurs here

            // Add optional splits
            receivers[1] = payable(optionalCommissionRecipient);
            amounts[1] = (totalRoyaltyToPay / totalCommission) * optionalCommissionRate;//@audit-info rounding occurs here
        } else {
            receivers = new address payable[](1);
            amounts = new uint256[](1);

            // Add the artist and commission
            receivers[0] = payable(artistAccount);
            amounts[0] = totalRoyaltyToPay;
        }

        return (receivers, amounts);
    }

VeryFastRouter.sol
345:                 // If we can sell at least 1 item...
346:                 if (numItemsToFill != 0) {
347:                     // If property checking is needed, do the property check swap
348:                     if (order.doPropertyCheck) {
349:                         outputAmount = ILSSVMPairERC721(address(order.pair)).swapNFTsForToken(
350:                             order.nftIds[:numItemsToFill],
351:                             priceToFillAt,//@audit-info min expected output
352:                             swapOrder.tokenRecipient,
353:                             true,
354:                             msg.sender,
355:                             order.propertyCheckParams
356:                         );
357:                     }
358:                     // Otherwise do a normal sell swap
359:                     else {
360:                         // Get subarray if ERC721
361:                         if (order.isERC721) {
362:                             outputAmount = order.pair.swapNFTsForToken(
363:                                 order.nftIds[:numItemsToFill], priceToFillAt, swapOrder.tokenRecipient, true, msg.sender
364:                             );
365:                         }
366:                         // For 1155 swaps, wrap as number
367:                         else {
368:                             outputAmount = order.pair.swapNFTsForToken(
369:                                 _wrapUintAsArray(numItemsToFill),
370:                                 priceToFillAt,
371:                                 swapOrder.tokenRecipient,
372:                                 true,
373:                                 msg.sender
374:                             );
375:                         }
376:                     }
377:                 }