Farseen0/scar-pairs huggingface dataset:
MEDIUM severity: `BorrowerOperations` alters user debt but enforces prices are not stale only for debts that are being actively altered. **Impact**

A common pattern used in Apollon for price validation looks as follows:

https://github.com/blkswnStudio/ap/blob/8fab2b32b4f55efd92819bd1d0da9bed4b339e87/packages/contracts/contracts/BorrowerOperations.sol#L248-L270

Where `_cols` is a user provided parameter

Similarly to increase...

Vulnerable code:
function addColl(
    TokenAmount[] memory _colls,
    address _upperHint,
    address _lowerHint,
    bytes[] memory _priceUpdateData
  ) public payable override {
    address borrower = msg.sender;
    (ContractsCache memory contractsCache, LocalVariables_adjustTrove memory vars) = _prepareTroveAdjustment(
      borrower,
      _priceUpdateData,
      false
    );

    // revert debt token deposit if prices are untrusted
    for (uint i = 0; i < _colls.length; i++) {
      if (!contractsCache.tokenManager.isDebtToken(_colls[i].tokenAddress)) continue;

      TokenPrice memory tokenPriceEntry = contractsCache.priceFeed.getTokenPrice(
        vars.priceCache,
        _colls[i].tokenAddress
      );
      if (!tokenPriceEntry.isPriceTrusted) revert UntrustedOraclesDebtTokenDeposit(); /// @audit verifying Colls
    } /// But this is user param not all colls the user has

function _increaseDebt(
    address _borrower,
    address _to,
    TokenAmount[] memory _debts,
    MintMeta memory _meta,
    bytes[] memory _priceUpdateData
  ) internal {
    (ContractsCache memory contractsCache, LocalVariables_adjustTrove memory vars) = _prepareTroveAdjustment(
      _borrower,
      _priceUpdateData,
      true //will be called by swap operations, that handled the price update
    );

    _requireValidMaxFeePercentage(contractsCache.troveManager, _meta.maxFeePercentage, vars.isInRecoveryMode);
    for (uint i = 0; i < _debts.length; i++) {
      _requireNonZeroDebtChange(_debts[i].amount);

      // revert minting if the oracle is untrusted
      TokenPrice memory tokenPriceEntry = contractsCache.priceFeed.getTokenPrice(
        vars.priceCache,
        _debts[i].tokenAddress
      );
      if (!tokenPriceEntry.isPriceTrusted) revert UntrustedOraclesMintingIsFrozen();
    }

if (vars.fromMintA != 0 || vars.fromMintB != 0) {
      TokenAmount[] memory debtsToMint;
      if (vars.fromMintA != 0 && vars.fromMintB != 0) {
        // mint both
        debtsToMint = new TokenAmount[](2);
        debtsToMint[0] = TokenAmount(tokenA, vars.fromMintA);
        debtsToMint[1] = TokenAmount(tokenB, vars.fromMintB);
      } else {
        // mint only 1 token
        debtsToMint = new TokenAmount[](1);
        debtsToMint[0] = (
          vars.fromMintA != 0
            ? TokenAmount(tokenA, vars.fromMintA) // mint A
            : TokenAmount(tokenB, vars.fromMintB) // mint B
        );
      }
      borrowerOperations.increaseDebt{ value: msg.value }(
        msg.sender, // OK 
        vars.pair, // OK
        debtsToMint, // OK - See above
        _priceAndMintMeta.meta, /// @audit this looks griefable | TODO
        _priceAndMintMeta.priceUpdateData // TODO
      );
MEDIUM severity: `SwapOperations` `swapFee` is non deterministic and can cause people to lose funds. **Impact**

SwapOperations charges a fee that is computed by `SwapPair` as follows:

https://github.com/blkswnStudio/ap/blob/8fab2b32b4f55efd92819bd1d0da9bed4b339e87/packages/contracts/contracts/SwapPair.sol#L187-L209

The fee uses the previous "spot ratio" and compares it against the new pre-fee...

Vulnerable code:
function getSwapFee(uint postReserve0, uint postReserve1) public view override returns (uint feePercentage) {
    address nonStableCoin = token1; // find stable coin
    if (tokenManager.isDebtToken(nonStableCoin) && totalSupply > 0) {
      // query prices
      (uint oraclePrice, ) = priceFeed.getPrice(nonStableCoin); /// @audit not validated for staleness
      uint preDexPrice = _getDexPrice(reserve0, reserve1); /// @audit this is spot and can be altered in some way
      uint postDexPrice = _getDexPrice(postReserve0, postReserve1);

      // only apply the dynamic fee if the swap trades against the oracle peg
      if ( /// @audit ??? | No deviation threshold | Spot vs Oracle (Donate to reserves?)
        (postDexPrice > oraclePrice && postDexPrice > preDexPrice) ||
        (postDexPrice < oraclePrice && preDexPrice > postDexPrice)
      ) {
        uint avgDexPrice = (preDexPrice + postDexPrice) / 2;
        uint priceRatio = (oraclePrice * DECIMAL_PRECISION) / avgDexPrice; // 1e18
        return
          _calcFee(priceRatio > DECIMAL_PRECISION ? priceRatio - DECIMAL_PRECISION : DECIMAL_PRECISION - priceRatio) +
          swapOperations.getSwapBaseFee();
      }
    }

    return swapOperations.getSwapBaseFee();
  }
MEDIUM severity: `SwapOperations` is performing some swaps without updating the price, resulting in incorrect fees being charged. **Impact**
Operations such as: `swapExactTokensForTokens`, `swapTokensForExactTokens` receive `_priceUpdateData` but update the price only at the `_swap` operation

`_swap` uses `getAmountsOut` and `getAmountsIn` to determine the price at which to charge fees...

Vulnerable code:
function swapExactTokensForTokens(
    uint amountIn,
    uint amountOutMin,
    address[] calldata path,
    address to,
    uint deadline,
    bytes[] memory _priceUpdateData
  ) public payable virtual override ensure(deadline) returns (SwapAmount[] memory amounts) {
    amounts = getAmountsOut(amountIn, path); /// @audit Used old prices
    if (amounts[amounts.length - 1].amount < amountOutMin) revert InsufficientOutputAmount();

    safeTransferFrom(path[0], msg.sender, getPair[path[0]][path[1]], amounts[0].amount);
    _swap(amounts, path, to, _priceUpdateData, false); /// @audit Updates prices after having used old prices
  }
MEDIUM severity: Operative Risks tied to changing Risk Based Parameter. **Executive Summary**

This is a collection of operative risks that come from maintaining and updating Apollon

I highly recommend you go through this list, create your own list, and ensure that at all times these risks are considered

**Updating `setCollTokenSupportedCollateralRatio` can cause...

Vulnerable code:
function setCollTokenSupportedCollateralRatio(
    address _collTokenAddress,
    uint _supportedCollateralRatio
  ) external override onlyOwner {
    if (_supportedCollateralRatio < MCR) revert SupportedRatioUnderMCR();
    collTokenSupportedCollateralRatio[_collTokenAddress] = _supportedCollateralRatio;
    emit CollTokenSupportedCollateralRatioSet(_collTokenAddress, _supportedCollateralRatio);
  }
LOW severity: `LiquidationsOperations` Comment around `_emitLiquidationSummaryEvent` is incorrect. **Impact**

`_emitLiquidationSummaryEvent` has the following comment
https://github.com/blkswnStudio/ap/blob/8fab2b32b4f55efd92819bd1d0da9bed4b339e87/packages/contracts/contracts/LiquidationOperations.sol#L447-L455

The comment looks wrong since the array fills in collaterals first and then...

Vulnerable code:
function _emitLiquidationSummaryEvent(LocalVariables_OuterLiquidationFunction memory vars) internal {
    TokenAmount[] memory liquidatedColl = new TokenAmount[](vars.priceCache.collPrices.length);
    for (uint i = 0; i < vars.priceCache.collPrices.length; i++) {
      liquidatedColl[i] = TokenAmount(
        vars.priceCache.collPrices[i].tokenAddress,
        vars.tokensToRedistribute[i].amount // works because of the initialisation of the array (first debts, then colls) /// @audit Wrong comment
      );
    }
LOW severity: `LiquidationOperations` loop should `break` on last Trove. **Impact**

Apollon will not liquidate the last trove in `batchLiquidateTroves` 
However, the code is using a `continue` which means that the code will loop multiple times while performing no-ops

**Mitigation**

Change the code to

Vulnerable code:
if (troveManager.getTroveOwnersCount() <= 1) continue; // don't liquidate if last trove

if (troveManager.getTroveOwnersCount() <= 1) break; // no more troves to liquidate
LOW severity: `massUpdatePools` needs to be capped due to OOG reverts. **Impact**

`massUpdatePools` looks as follows:

Meaning it will iterate over all known pools

The gas limit on SEI is 10MLN gas per block

Assuming around 25k gas per update, that's 400 pools before the function reverts

I just did some quick napkin math on the amount of storage slots used, you...

Vulnerable code:
function massUpdatePools() public {
    uint length = pools.length;
    for (uint n = 0; n < length; n++) {
      updatePool(pools[n]);
    }
  }
LOW severity: `StakingOperation` will drip rewards to no-one if rewards are queued before any deposit. **Impact**

I believe they will have modest losses until someone deposits

This is further corroborated by the fact that the first deposit will continue instead of going through the early return path

https://github.com/blkswnStudio/ap/blob/8fab2b32b4f55efd92819bd1d0da9bed4b339e87/packages/contracts...

Vulnerable code:
function updatePool(ISwapPair _pid) public {
    PoolInfo storage pool = poolInfo[_pid];

    // check
    if (block.timestamp <= pool.lastRewardTime) return;

    // update
    uint tokenSupply = _pid.balanceOf(address(this));
    if (tokenSupply == 0 || totalAllocPoint == 0) {
      pool.lastRewardTime = block.timestamp;
      return;
    }
LOW severity: Riskiest Trove can be made to pay compound interest cheaply. **Impact**

Apollon charges simple interest on Trove Accrual

In general Troves can only be accrued by their owners during state-changing operations

However, the riskiest trove can be made to accrue by redeeming a very small amount of debt (e.g. 1 wei)

**Mitigation**

Either document this risk,...

Vulnerable code:
for (uint i = 0; i < _iterations.length; i++) {
      RedeemIteration memory iteration = _iterations[i];
      checkValidRedemptionHint(vars.priceCache, iteration.trove);
      troveManager.applyPendingRewards(iteration.trove, vars.priceCache); /// @audit The Hint may be wrong NOW, the CR may be underwater now
      SingleRedemptionVariables memory troveRedemption = _calculateTroveRedemption( /// TODO: KEY
        vars.priceCache,
        iteration.trove,
        _stableCoinAmount - vars.totalRedeemedStable,
        false // without pending rewards, because they got applied above
      );

      // resulting CR differs from the expected CR, we bail in that case, because all following iterations will consume too much gas by searching for a updated hints
      // allowing 1% deviation, because of time based borrowing interests
      if (troveRedemption.resultingCR > iteration.expectedCR) {
        if ((troveRedemption.resultingCR * DECIMAL_PRECISION) / iteration.expectedCR > 1.01e18) break; /// @audit Risk of overflow
      } else {
        if ((iteration.expectedCR * DECIMAL_PRECISION) / troveRedemption.resultingCR > 1.01e18) break;
      }
LOW severity: Redemptions that redeem close to 100% of the Trove Debt may revert when the hint is inaccurate. **Impact**

After a redemption, this comparison is performed:

https://github.com/blkswnStudio/ap/blob/8fab2b32b4f55efd92819bd1d0da9bed4b339e87/packages/contracts/contracts/RedemptionOperations.sol#L123-L129

Whenever expectedCR or resultingCR are greater than type(uint256).max / 1e18 the...

Vulnerable code:
// resulting CR differs from the expected CR, we bail in that case, because all following iterations will consume too much gas by searching for a updated hints
      // allowing 1% deviation, because of time based borrowing interests
      if (troveRedemption.resultingCR > iteration.expectedCR) {
        if ((troveRedemption.resultingCR * DECIMAL_PRECISION) / iteration.expectedCR > 1.01e18) break; /// @audit Risk of overflow
      } else {
        if ((iteration.expectedCR * DECIMAL_PRECISION) / troveRedemption.resultingCR > 1.01e18) break;
      }
LOW severity: Liquidation Logic will not work on all troves when the system is underwater. **Impact**

`batchLiquidateTroves` allows for out of order liquidations

In the edge case of all troves being underwater, meaning all troves should be liquidated, the following check will allow liquidations only starting from the riskiest...

Vulnerable code:
if (vars.ICR > outerVars.TCR) return false; /// @audit Looks wrong in edge cases
LOW severity: Up to (1e18 - 1) loss in interest paid due to rounding down. **Impact**

`stableInterest` in `_calculatePendingBorrowingInterest` rounds down the amount to be paid due to division before multiplication 

https://github.com/blkswnStudio/ap/blob/8fab2b32b4f55efd92819bd1d0da9bed4b339e87/packages/contracts/contracts/TroveManager.sol#L459-L462

There is no...

Vulnerable code:
stableInterest += /// Can find cheaper price to pay less, ultimately subject to "downward spiral" and other risks
        (((_priceFeed.getUSDValue(_priceCache, address(debtToken), debtTokenAmount) * borrowingInterestRate) /
          DECIMAL_PRECISION) * timePassed) /
        SECONDS_PER_YEAR;

stableInterest +=
        (((_priceFeed.getUSDValue(_priceCache, address(debtToken), debtTokenAmount) * borrowingInterestRate) * timePassed) / DECIMAL_PRECISION /
        SECONDS_PER_YEAR;
LOW severity: Bad Debt Redistribution can be avoided by removing collaterals. **Impact**

The logic for `redistributeDebtAndColl` is as follows

https://github.com/blkswnStudio/ap/blob/8fab2b32b4f55efd92819bd1d0da9bed4b339e87/packages/contracts/contracts/TroveManager.sol#L310-L337

Specifically when `collTotalStake` is zero a redistribution for that collateral is skipped,...

Vulnerable code:
function redistributeDebtAndColl(PriceCache memory _priceCache, CAmount[] memory toRedistribute) external override {
    _requireCallerIsBorrowerOpsOrRedemptionOpsOrLiquidationOps();

    // sum up all coll usd values
    uint totalRedistributedCollInUsd;
    uint[] memory collCacheInUSD = new uint[](toRedistribute.length);
    for (uint i = 0; i < toRedistribute.length; i++) {
      CAmount memory collEntry = toRedistribute[i];
      if (!collEntry.isColl || collEntry.amount == 0) continue; /// collCacheInUSD[i] = collInUSD || 0 when skipped

      uint collInUSD = priceFeed.getUSDValue(_priceCache, collEntry.tokenAddress, collEntry.amount);
      collCacheInUSD[i] = collInUSD;
      totalRedistributedCollInUsd += collInUSD;
    }

    // iterate over the coll entries and process the debt relative to the coll percentage
    uint[] memory debtsToDefaultPool = new uint[](toRedistribute.length);
    for (uint i = 0; i < toRedistribute.length; i++) { /// @audit technically same loop as above, since it skips the same entries
      CAmount memory collEntry = toRedistribute[i];
      if (!collEntry.isColl || collEntry.amount == 0) continue;

      // patch the liquidated coll tokens
      uint collTotalStake = totalStakes[collEntry.tokenAddress]; /// @audit Can you have 0 stakes by having a Trove that didn't redistribute?
      PoolType targetPool;
      if (collTotalStake == 0) {
        // the last trove with that coll type was liquidated/close, moving the assets into a claimable (unassigned) pool
        targetPool = PoolType.Unassigned;
      } else {
LOW severity: Stablecoin interest being lower than jTokens seems inconsistent + Tokens with different volatilities pay the same fee. **Impact**

Generally speaking jAssets will be more volatile and riskier than a stablecoin that denominates them

The logic for `getBorrowingRate` charges more for stableCoins than for jAssets

https://github.com/blkswnStudio/ap/blob/8fab2b32b4f55efd92819bd1d0da9bed4b339e87/packages/contracts/contra...

Vulnerable code:
function getBorrowingRate(bool isStableCoin) public view override returns (uint) {
    if (!isStableCoin) return borrowingFeeFloor;
    return _calcBorrowingRate(stableCoinBaseRate);
  }
LOW severity: `SwapOperation` First LP pays no fee and can set the price to an incorrect value, causing losses to traders, and higher fees. **Impact**

`addLiquidity` looks as follows

https://github.com/blkswnStudio/ap/blob/8fab2b32b4f55efd92819bd1d0da9bed4b339e87/packages/contracts/contracts/SwapOperations.sol#L228-L246

When no liquidity was previously added, the ratio for the LP will be taken at face value

This could be used by...

Vulnerable code:
function addLiquidity(
    address tokenA,
    address tokenB,
    uint amountADesired,
    uint amountBDesired,
    uint amountAMin,
    uint amountBMin,
    PriceUpdateAndMintMeta memory _priceAndMintMeta,
    uint deadline
  ) public payable virtual override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {
    ProvidingVars memory vars;
    vars.pair = getPair[tokenA][tokenB]; /// @audit QA not sorting tokens means this can revert | No fix is ok but it's a gotcha
    if (vars.pair == address(0)) revert PairDoesNotExist();
    /// @audit not checking that min < desired - S
    {
      (vars.reserveA, vars.reserveB) = getReserves(tokenA, tokenB);
      if (vars.reserveA == 0 && vars.reserveB == 0) {
        (amountA, amountB) = (amountADesired, amountBDesired); /// @audit First LP Can attack by massively imbalancing by performing a single sided swap or similar
      } else {
LOW severity: `claimUnassigned` may result in a slight difference between debt and coll percentages being claimed due to rounding errors. **Impact**

Slight difference in debt and coll claimed due to rounding error

May be abused to get free coll due to coll rounding up to a unit or more and debts rounding down to 0

https://github.com/blkswnStudio/ap/blob/8fab2b32b4f55efd92819bd1d0da9bed4b339e87/packages/contracts/contracts/BorrowerO...

Vulnerable code:
DebtTokenAmount[] memory debtsToAdd = new DebtTokenAmount[](vars.priceCache.debtPrices.length);
    for (uint i = 0; i < vars.priceCache.debtPrices.length; i++) {
      address debtToken = vars.priceCache.debtPrices[i].tokenAddress;

      uint unassignedDebt = contractsCache.storagePool.getValue(debtToken, false, PoolType.Unassigned);
      if (unassignedDebt == 0) continue;

      uint toClaim = (unassignedDebt * _percentage) / DECIMAL_PRECISION;
LOW severity: `claimUnassignedAsset` `_percentage` can be more than 1e18. **Impact**

https://github.com/blkswnStudio/ap/blob/8fab2b32b4f55efd92819bd1d0da9bed4b339e87/packages/contracts/contracts/BorrowerOperations.sol#L614-L620

Maybe abused for exploit, I haven't spent a lot of time on this, it's best to cap it to 1e18 to avoid any additional risk

**Mitigation**

Vulnerable code:
function claimUnassignedAssets(
    uint _percentage,
    address _upperHint,
    address _lowerHint,
    bytes[] memory _priceUpdateData
  ) external payable override {
    if (_percentage == 0) revert ZeroDebtChange();

if (_percentage > DECIMAL_PRECISION) revert Above100Pct();
LOW severity: Basic Style Guide advice. **Impact**

The impact from the current formatting is that manual review takes longer, and as such will be more expensive and error prone

**Specific Code Smells**

- Not using curly braces for `if / else`
- Using `ii` in favour of mathematical notation `n, l, m` `i, j, k`, `x, y, z`
- Breaking...

Vulnerable code:
vars.arrayIndex = contractsCache.troveManager.addTroveOwnerToArray(borrower);
    /// @audit move the transfer of coll to the bottom to avoid CEI issues
    // Move the coll to the active pool
    for (uint i = 0; i < vars.colls.length; i++) {
      TokenAmount memory collTokenAmount = vars.colls[i]; /// @audit CEI / Reentrancy
      _poolAddColl(
        borrower,
        contractsCache.storagePool,
        collTokenAmount.tokenAddress,
        collTokenAmount.amount,
        PoolType.Active
      );
    }
    /// @audit Where is the trove? 
    // Move the stable coin gas compensation to the Gas Pool
    contractsCache.storagePool.addValue(
      address(stableCoinAmount.debtToken),
      false,
      PoolType.GasCompensation,
      stableCoinAmount.netDebt
    ); /// @audit Not minting principal on open
    stableCoinAmount.debtToken.mint(address(contractsCache.storagePool), stableCoinAmount.netDebt);

    emit TroveCreated(borrower, _colls);
  }

function _poolAddColl(
    address _borrower,
    IStoragePool _pool,
    address _collAddress,
    uint _amount,
    PoolType _poolType
  ) internal {
    _pool.addValue(_collAddress, true, _poolType, _amount);
    IERC20(_collAddress).transferFrom(_borrower, address(_pool), _amount); /// @audit FOT / SafeTransfer
  }
LOW severity: WIP - Invariants. **Troves should never break the check `_debtTokenUsedAsCollRatio(contractsCache, vars) > MAX_DEBTS_AS_COLLATERAL`**

**There should be n Trove with 0 net debt in the Sorted Trove**

**At no time I can perform an operation, then update the oracle, and trigger recover mode**

**At no time, I should...

Vulnerable code:
if (collToLiquidate > rAmount.toLiquidate) collToLiquidate = rAmount.toLiquidate; // in case of IMCR > ICR, but the trove still gets liquidated because ICR < TCR in recovery mode
LOW severity: `RedeptionOperations.checkValidRedemptionHint` check should use `>=`. **Impact**

The check in `checkValidRedemptionHint` for the current hint is as follows:

https://github.com/blkswnStudio/ap/blob/8fab2b32b4f55efd92819bd1d0da9bed4b339e87/packages/contracts/contracts/RedemptionOperations.sol#L212

Which asserts that the hint is not liquidatable outside of recovery...

Vulnerable code:
if (hintCR < hintIMCR) revert HintBelowMCR(); // should be liquidated, not redeemed from

if (nextTrove != address(0) && nextTroveCR > nextTroveMCR) revert InvalidHintLowerCRExists(); /// @audit TO CHECK
LOW severity: `SwapPair.getSwapFee` charges for crossing the middle price. **Impact**

`getSwapFee` computes the fee to be paid as follows:

https://github.com/blkswnStudio/ap/blob/8fab2b32b4f55efd92819bd1d0da9bed4b339e87/packages/contracts/contracts/SwapPair.sol#L196-L199

We can chart out the logic as follows:

- Post  > Oracle
- Post > Prev > Oracle
- Post > Oracle >...

Vulnerable code:
if ( /// @audit No deviation threshold | Spot vs Oracle
        (postDexPrice > oraclePrice && postDexPrice > preDexPrice) || /// @audit Logical mechanism of swapping?
        (postDexPrice < oraclePrice && preDexPrice > postDexPrice)
      ) {
LOW severity: Incompatibility with tokens that charge a fee on transfer. **Impact**

The system stores balances in storage and then updates them

Some tokens will charge a fee on transfer

Meaning that `_amount` stored in the `StoragePool` will be higher than the actual balance...

Vulnerable code:
function _poolAddColl(
    address _borrower,
    IStoragePool _pool,
    address _collAddress,
    uint _amount,
    PoolType _poolType
  ) internal {
    _pool.addValue(_collAddress, true, _poolType, _amount);
    IERC20(_collAddress).transferFrom(_borrower, address(_pool), _amount); /// @audit FOT / SafeTransfer
  }
LOW severity: `SwapOperations.addLiquidity` is not validating desired and min amounts. **Impact**

`addLiquidity` bases it's logic on the implicit invariant that `amountDesired` are greater than `minAmounts`

https://github.com/blkswnStudio/ap/blob/8fab2b32b4f55efd92819bd1d0da9bed4b339e87/packages/contracts/contracts/SwapOperations.sol#L242-L258

However this check is missing from...

Vulnerable code:
{
      (vars.reserveA, vars.reserveB) = getReserves(tokenA, tokenB);
      if (vars.reserveA == 0 && vars.reserveB == 0) {
        (amountA, amountB) = (amountADesired, amountBDesired); /// @audit First LP Can attack by massively imbalancing by performing a single sided swap or similar
      } else {
        uint amountBOptimal = quote(amountADesired, vars.reserveA, vars.reserveB);
        if (amountBOptimal <= amountBDesired) {
          if (amountBOptimal < amountBMin) revert InsufficientBAmount();
          (amountA, amountB) = (amountADesired, amountBOptimal);
        } else {
          uint amountAOptimal = quote(amountBDesired, vars.reserveB, vars.reserveA);
          assert(amountAOptimal <= amountADesired); /// @audit Looks wrong
          if (amountAOptimal < amountAMin) revert InsufficientAAmount();
          (amountA, amountB) = (amountAOptimal, amountBDesired);
        }
      }
    }

if (amountADesired < amountAMin) revert InsufficientAmountADesired();
        if (amountBDesired < amountBMin) revert InsufficientAmountBDesired();