OpenTrade

LytPool

Cantina Security Report

Organization

@OpenTrade

Engagement Type

Cantina Reviews

Period

-


Findings

Medium Risk

2 findings

2 fixed

0 acknowledged

Low Risk

8 findings

6 fixed

2 acknowledged

Informational

7 findings

6 fixed

1 acknowledged

Gas Optimizations

2 findings

2 fixed

0 acknowledged


Medium Risk2 findings

  1. Missing necessary status checks

    State

    Fixed

    PR #2

    Severity

    Severity: Medium

    Submitted by

    Akshay Srivastav


    Description

    Various functions of protocol contracts are missing necessary protocol/account status checks.

    1. LYTPool.processRedemption: Missing withdrawStateActive modifier.
    2. LYTPool.depositOffChain: Missing depositStateActive modifier.
    3. LYTPool.depositFromTransfer: Missing depositStateActive modifier.
    4. LYTPool.changeRedemptionDestination: Missing withdrawStateActive modifier.
    5. LYTPool.processRedemption: Missing blacklist check on fundsDestination address.
    6. LYTPool.changeRedemptionDestination: Missing blacklist check on fundsDestination address.
    7. LYTPool.setIndicativeAndCollateralRates: Missing atState(ILYTPoolLifeCycleState.Active) modifier.
    8. LYTPool.setExchangeRate: Missing atState(ILYTPoolLifeCycleState.Active) modifier.
    9. LYTPool.maxRedeemRequest: Missing onlyNotPaused modifier.
    10. LYTPool.maxRedeemRequest: Missing atState(ILYTPoolLifeCycleState.Active) modifier.

    Recommendation

    Consider adding all the mentioned status checks.

  2. LYTPool: Pool tokens which are currently in active withdrawal queue can be burned.

    State

    Fixed

    PR #2

    Severity

    Severity: Medium

    Likelihood: Low

    ×

    Impact: High

    Submitted by

    Akshay Srivastav


    Description

    In LYTPool contract it is possible to burn LYTPools tokens which are currently in active withdrawal queue.

    Scenario:

    1. A market maker holds 100 pool tokens.
    2. Market maker requests a withdrawal of 50 tokens.
    3. Issuer calls addAdjustmentAmount and burns 60 tokens of market maker.
    4. Now since 60 tokens have already been burned, the redemption of 50 tokens from step 2 cannot be performed. The processRedemption call reverts at L396.
    5. 40 pool tokens of market maker becomes unusable.

    Recommendation

    Add a maxRedeemRequest check in addAdjustmentAmount function.

    function addAdjustmentAmount( ... ) public ... {    ...    if (debit > 0) {-     if (debit > assetBalanceOf(lender)) {-       revert DebitGreaterThanAssets();-     }      uint256 shares = convertToShares(debit);+     if (shares > maxRedeemRequest(lender)) {+       revert BurnExceedsBalance();+     }      _burn(lender, shares);      emit AccountDebit(lender, debit);    } else if (credit > 0) {      ...    } else {      revert InvalidAccess();    }  }

Low Risk8 findings

  1. getActiveWithdraws can be gas consuming

    State

    Acknowledged

    Severity

    Severity: Low

    Likelihood: Low

    ×

    Impact: Low

    Submitted by

    ladboy233


    Description

    The getActiveWithdraws function iterates over all active withdrawal requests and returns the full list. If a large number of redeemers submit withdrawal requests, this approach can result in high gas consumption, making the function inefficient and potentially impractical to call on-chain.

    Recommendation

    Introduce pagination (e.g., via index ranges or batching) to limit the number of withdrawal requests returned per call. This will reduce gas costs, improve scalability, and make the function safer to use when handling a large volume of requests.

  2. Blocklisted users can create unprocessable redeem requests

    Severity

    Severity: Low

    Likelihood: Low

    ×

    Impact: Low

    Submitted by

    ladboy233


    Description

    Currently, blocklisted users are still able to create redeem requests, which are then added to the activeWithdrawRequest list. However, when the protocol attempts to process these requests, the transaction reverts at the step where it tries to burn the blocklisted lender’s token.

    This behavior leads to a situation where getActiveWithdrawRequest includes requests that can never be executed, causing inconsistencies and breaking assumptions for off-chain bots or automation that rely on this function to reflect executable withdrawals.

    Recommendation

    Prevent blocklisted users from creating redeem requests in the first place. Adding a validation check before requests are added to activeWithdrawRequest will ensure the list only contains valid and executable entries, avoiding stuck or unprocessable states.

  3. LYTPoolServiceConfiguration.setLiquidityAsset duplicate liquidity asset entries can be added

    State

    Fixed

    PR #2

    Severity

    Severity: Low

    Submitted by

    Akshay Srivastav


    Description

    In LYTPoolServiceConfiguration.setLiquidityAsset function duplicate liquidity asset entries can be added to liquidityAssetKeys state.

    Recommendation

    Consider reverting if isLiquidityAsset[addr] is already set to value.

  4. LYTPoolAccessControlFactory.create: Missing paused status check of _serviceConfiguration

    State

    Fixed

    PR #2

    Severity

    Severity: Low

    Submitted by

    Akshay Srivastav


    Description

    The LYTPoolAccessControlFactory.create function does not check the paused status of _serviceConfiguration

    Recommendation

    Consider adding the check.

  5. LYTPool.deposit: Breaking checks-effects-interaction pattern

    State

    Fixed

    PR #2

    Severity

    Severity: Low

    Submitted by

    Akshay Srivastav


    Description

    The LYTPool.deposit function breaks the CEI pattern and performs an external call before updating its internal storage states.

    Recommendation

    Consider performing the _mint call before the _liquidityAsset.safeTransferFrom call.

  6. LYTPoolServiceConfiguration: Incorrect IFactoryType enum datatype

    State

    Fixed

    PR #2

    Severity

    Severity: Low

    Submitted by

    Akshay Srivastav


    Description

    The IFactoryType enum datatype does not contain LYTPoolControllerFactory type. Instead it incorrectly contains FeeCollector type.

    Recommendation

    Consider replacing FeeCollector with LYTPoolControllerFactory in IFactoryType enum.

  7. LYTPool.previewWithdrawRequest rounds down the shares amount

    State

    Fixed

    PR #2

    Severity

    Severity: Low

    Submitted by

    Akshay Srivastav


    Description

    The LYTPool.previewWithdrawRequest function rounds down the calculated shares amount. This rounding direction favors the users instead of pool contract.

    As a security practice it is always recommended to always round in the direction which favors the pool contract.

    Recommendation

    Consider rounding up the shares value in previewWithdrawRequest function.

  8. LYTPool: Market makers can hit the 100 active withdrawal request threshold

    State

    Acknowledged

    Severity

    Severity: Low

    Submitted by

    Akshay Srivastav


    Description

    The LYTPool.requestRedeemExecute function enforces a threshold of 100 active withdrawal requests per market maker address. Since a LYTPool will only have a limited number of whitelisted market makers which will perform all the pool token mints and burns, it is possible that those market makers may hit the 100 active requests threshol which will block further redemption requests by such market makers.

    Recommendation

    As market makers are whitelisted entities, the threshold of 100 requests can be removed or increased to a larger limit value.

Informational7 findings

  1. setIndicativeAndCollateralRates missing event emission

    Severity

    Severity: Informational

    Likelihood: Low

    ×

    Impact: Low

    Submitted by

    ladboy233


    Description

    setIndicativeAndCollateralRates missing event emission so the offchain service cannot track _indicativeInterestRate and _collateralRate change.

    Recommendation

    function setIndicativeAndCollateralRates(    uint256 _indicativeInterestRate,    uint256 _collateralRate  ) public onlyPoolController {    _accounting.indicativeInterestRate = _indicativeInterestRate;    _accounting.collateralRate = _collateralRate;    emit IndicativeAndCollateralRatesChanged(_indicativeInterestRate, _collateralRate);  }
  2. LYPool.sol cannot trigger updatePoolData

    Severity

    Severity: Informational

    Likelihood: Low

    ×

    Impact: Low

    Submitted by

    ladboy233


    Description

    The updatePoolData function is protected by the onlyRegisteredPool modifier. However, the LYPool.sol contract does not have a direct way to trigger updatePoolData.

    Recommendation

    Introduce a function in the controller contract that allows the controller to call into LYPool to call updatePoolData

  3. Code implementations are not consistent with the spec

    State

    Fixed

    PR #2

    Severity

    Severity: Informational

    Likelihood: Low

    ×

    Impact: Low

    Submitted by

    ladboy233


    Description

    The comments says that the functions listed below are only callable by protocol admin, but the modifier is onlyIssuer, not onlyProtocolAdmin.

    • addAdjustmentAmount
    • setFeeCollectorAddress
    • setIndicativeAndCollateralRates

    Recommendation

    Make sure Code implementations are consistent with the spec

  4. LYTPoolAccessControl: Discrepancy in event names

    State

    Fixed

    PR #2

    Severity

    Severity: Informational

    Submitted by

    Akshay Srivastav


    Description

    In LYTPoolAccessControl contract

    • the natspec of allowParticipant specifies AllowedParticipantListUpdated event but ParticipantAllowed event is emitted
    • the natspec of removeParticipant specifies AllowedParticipantListUpdated event but ParticipantRemoved event is emitted

    Recommendation

    Consider fixing the natspec comments.

  5. Inconsistent version function across contracts

    State

    Fixed

    PR #2

    Severity

    Severity: Informational

    Submitted by

    Akshay Srivastav


    Description

    Multiple protocol contracts implements version function but its implementation is inconsistent across those contracts. In some contracts version returns 257 while in some it returns 256 or 512.

    Recommendation

    Consider removing the version function from all contracts or implement it in a consistent way across protocol.

  6. Incorrect Comments

    State

    Fixed

    PR #2

    Severity

    Severity: Informational

    Submitted by

    Akshay Srivastav


    Description

    1. LYTPoolController.sol?lines=367,368: Incorrect comment as activatedAt value is never updated.

    Recommendation

    Consider fixing the comments

  7. LYTPool: Users can avoid pool token burn adjustments by transferring pool tokens

    State

    Acknowledged

    Severity

    Severity: Informational

    Submitted by

    Akshay Srivastav


    Description

    The LYTPool tokens can be freely transferred by token holders. Those holders can avoid burn adjustments by transferring their LYTPool tokens to other addresses just before the addAdjustmentAmount transaction gets executed. Repeated execution of this mechanism can prevent burn adjustments from happening.

    Recommendation

    This could be an accepted risk. If a mitigation is required then token transfers can be paused before making a burn adjustment.

Gas Optimizations2 findings

  1. Unused Code

    State

    Fixed

    PR #2

    Severity

    Severity: Gas optimization

    Likelihood: Low

    ×

    Impact: Low

    Submitted by

    ladboy233


    Description

    These code instances are present in the codebase but are never used. Hence they can be removed.

    1. LYTPool.sol?lines=197,197: isPermittedLender is not used in LYTPool.sol.
    2. LYTPoolServiceConfiguration.sol?lines=52,59: The onlyProtocolAdmin and onlyIssuer modifiers are never used inside LYTPoolServiceConfiguration contract.
    3. LYTPoolRegistry.sol?lines=24,24: The onlyOperator modifier is never used in LYTPoolRegistry contract.
    4. LYTPoolController.sol?lines=81,83: Unused modifiers: onlyProtocolAdminOrIssuer, onlyProtocolAdminOrIssuerOrAutomation & atState.
    5. ILYTPoolStructures.sol?lines=50,55: Initialized and DisruptionOrDefault states are never used.
    6. ILYTPoolStructures.sol?lines=10,10: LYTPoolSettings struct is never used.
    7. ILYTPoolStructures.sol?lines=67,67: ILYTPoolWithdrawStage.Repaid is never used.
    8. ILYTPoolStructures.sol?lines=73,73: ILYTPoolDepositType.CrossChainDeposit is never used.
    9. IPoolStructures.sol: IPoolStructures.sol file is never used.
    10. IERC4626.sol: IERC4626.sol file is never used.

    Recommendation

    Remove the unused code to save gas.

  2. LYTPool.requestRedeemExecute: Redundant withdrawStateActive modifier

    State

    Fixed

    PR #2

    Severity

    Severity: Gas optimization

    Submitted by

    Akshay Srivastav


    Description

    The withdrawStateActive modifier can be removed from requestRedeemExecute function as it is already present on requestRedeem function.

    Recommendation

    Consider removing the modifier from requestRedeemExecute function.