Neutrl

Neutrl: Mint Redeem

Cantina Security Report

Organization

@Neutrl

Engagement Type

Cantina Solo

Period

-

Researchers


Findings

Informational

4 findings

2 fixed

2 acknowledged


Informational4 findings

  1. Fee calculations round in favor of the user

    State

    Acknowledged

    Severity

    Severity: Informational

    Submitted by

    Kurt Barry


    Description

    In both StableMinterV2 and RedeemerV2, fees are calculated with truncating division:

    feeAmount = (_amount * applicableFee) / FEE_DENOMINATOR;

    This rounding is in favor of the user, allowing fees to be avoided entirely for sufficiently small mint or redemption amounts. On Ethereum mainnet, gas costs should prevent this from ever being exploitable, although it remains a potential issue on other chains or in scenarios where a user's gas costs are somehow subsidized.

    Recommendation

    Considering rounding these divisions up; this should have minimal impact on typically-sized mints and redemptions, but guarantees there is no possible fee-avoidance exploit.

    Neutrl Response

    Acknowledged; in addition to mainnet gas constraints, only whitelisted addresses trigger mints and redemptions and size minimums are in place offchain for these operations.

  2. Ambiguous fee setting events

    State

    Fixed

    PR #70

    Severity

    Severity: Informational

    Submitted by

    Kurt Barry


    Description

    In both StableMinterV2 and RedeemerV2, the events emitted when an account is assigned an exceptional zero fee via setWhitelistedFee() (an explicitly supported functionality according to the comments) and when an account is removed from the fee whitelist via removeWhitelistedFee() are indistinguishable. An indexer could in principle examine the transaction to see which function was actually invoked to distinguish them, but this defeats the purpose of using events to emit information.

    Recommendation

    Use a different event when removing an account from the fee whitelist, e.g. WhitelistedFeeRemoved(address indexed account).

  3. Misleading comment

    State

    Fixed

    PR #71

    Severity

    Severity: Informational

    Submitted by

    Kurt Barry


    Description

    The RedeemerV2.getRedemptionMode() function is annotated with an @dev comment as follows:

    /// @dev Quotes against the post-fee amount using the default fee. A whitelisted user with a lower fee may see///      QUEUED here even though their `redeem` would clear instantly — preferable to the inverse surprise///      (INSTANT here, then revert in `redeem`).

    This suggests that the behavior of an actual redemption could differ from the return value of this function--however, this is not possible because Router.redeem() uses the return value of this function to determine the redemption mode:

    MintRedeemMode redemptionMode = getRedemptionMode(order.collateralAsset, order.nusdAmount);redemptionMode == MintRedeemMode.INSTANT ? _redeem(order, order.benefactor) : _requestRedemption(order);

    Recommendation

    Re-word the comment to be more reflective of the implemented behavior, for example:

    /// @dev Quotes against the post-fee amount using the default fee. A whitelisted user with a lower fee ///      may have their request QUEUED even though `redeem` could in principle clear instantly.
  4. Reuse of ASSET_RESERVE to store fees is an unexpected behavior

    State

    Acknowledged

    Severity

    Severity: Informational

    Submitted by

    Kurt Barry


    Description

    The ASSET_RESERVE address is used to accrue fees as well as to store idle NUSD collateral assets. At a minimum, this re-use for a secondary purpose may create confusion, and allows for the possibility of mistakes (e.g. moving NUSD when it was intended to move collateral).

    Recommendation

    With correct operational practices there is no concrete risk, so no action is deemed strictly necessary. It is recommended to clearly document that the ASSET_RESERVE has two different functions.

    Neutrl Response

    Acknowledged; using the ASSET_RESERVE to receive fees is convenient because the infrastructure already exists to move tokens to and from this address. NUSD will never be used for its own mints and redemptions.