Neutrl: Mint Redeem
Cantina Security Report
Organization
- @Neutrl
Engagement Type
Cantina Solo
Period
-
Researchers
Findings
Informational
4 findings
2 fixed
2 acknowledged
Informational4 findings
Fee calculations round in favor of the user
State
- Acknowledged
Severity
- Severity: Informational
Submitted by
Kurt Barry
Description
In both
StableMinterV2andRedeemerV2, 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.
Ambiguous fee setting events
State
- Fixed
PR #70
Severity
- Severity: Informational
Submitted by
Kurt Barry
Description
In both
StableMinterV2andRedeemerV2, the events emitted when an account is assigned an exceptional zero fee viasetWhitelistedFee()(an explicitly supported functionality according to the comments) and when an account is removed from the fee whitelist viaremoveWhitelistedFee()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).Misleading comment
State
- Fixed
PR #71
Severity
- Severity: Informational
Submitted by
Kurt Barry
Description
The
RedeemerV2.getRedemptionMode()function is annotated with an@devcomment 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.Reuse of ASSET_RESERVE to store fees is an unexpected behavior
State
- Acknowledged
Severity
- Severity: Informational
Submitted by
Kurt Barry
Description
The
ASSET_RESERVEaddress 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_RESERVEhas two different functions.Neutrl Response
Acknowledged; using the
ASSET_RESERVEto 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.