sherlock-audit / 2024-08-midas-minter-redeemer-judging

1 stars 1 forks source link

EFCCWEB3 - Logical Flaw in the Calculation of :: amountMTokenWithoutFee within :: _calcAndValidateDeposit Function allowing user to mint more tokens #52

Closed sherlock-admin3 closed 2 months ago

sherlock-admin3 commented 2 months ago

EFCCWEB3

High

Logical Flaw in the Calculation of :: amountMTokenWithoutFee within :: _calcAndValidateDeposit Function allowing user to mint more tokens

Summary

A critical flaw has been discovered in the _calcAndValidateDeposit function, which is integral to the smart contract's handling of token deposits. The issue pertains to the improper calculation of the amountMTokenWithoutFee variable, a value that represents the net mTokenamount after deducting applicable fees. The current implementation fails to adequately subtract the fee's mToken equivalent from the total mToken amount, resulting in an erroneous amountMTokenWithoutFee value. This oversight has profound implications for the contract's financial operations, potentially leading to users being over-credited in their mToken balances.

Vulnerability Detail

The _calcAndValidateDeposit function is a core component of the contract, responsible for processing user deposits. It performs a series of critical calculations:

Validation of Minimum Deposit Amount: The function checks if the resulting mintAmount meets the minimum mToken deposit requirements. If the user's total mToken balance is zero, an additional check ensures that the first deposit meets a higher minimum threshold.

Final Assignments: The function then assigns the calculated values to the respective variables, readying them for further use in the transaction process.

This oversight leads to a situation where amountMTokenWithoutFee does not accurately represent the mToken amount post-fee deduction.

Steps to produce: Initial Deposit: A user initiates a deposit of 100 tokens, with a fee rate of 2%. The _calcAndValidateDeposit function converts the tokens to USD, applies the fee, and then calculates the mToken equivalent.

Examine Calculations: Observe that amountTokenWithoutFee is correctly calculated as amountToken - feeTokenAmount. However, amountMTokenWithoutFee is incorrectly assigned without subtracting the mToken equivalent of the fee.

Final Output: The function outputs an inflated amountMTokenWithoutFee, which does not reflect the proper deduction of fees in mToken terms.

The calculation of amountMTokenWithoutFee should closely follow the pattern used for amountTokenWithoutFee. Specifically, after converting the fee amount to its mToken equivalent, this value should be subtracted from the total mToken amount to yield an accurate amountMTokenWithoutFee.

Since fees will not be deducted users will mint more tokens than intended as fee will not be removed from it value.

Two functions calls this internally and the logical bugs affect them every time it would be called uponupon as you can see below: DeposiitRequest and DepositInstant, however the depositrequest does call _calcAndValidateDeposit but doesn't mint directly to users but just to make a request for a deposit but An admin can call _approveRequest to approve this and mint unintended amount to the user.

Impact

Users may receive more mTokens than they should.

Code Snippet

DepositInstant

function depositInstant(
        address tokenIn,
        uint256 amountToken,
        uint256 minReceiveAmount,
        bytes32 referrerId
    )
        external
        whenFnNotPaused(this.depositInstant.selector)
        onlyGreenlisted(msg.sender)
        onlyNotBlacklisted(msg.sender)
        onlyNotSanctioned(msg.sender)
    {
        address user = msg.sender;

        address tokenInCopy = tokenIn;
        uint256 amountTokenCopy = amountToken;

        (
            uint256 tokenAmountInUsd,
            uint256 feeTokenAmount,
            uint256 amountTokenWithoutFee,
            uint256 mintAmount,
            ,
            ,
            uint256 tokenDecimals
        ) = _calcAndValidateDeposit(user, tokenInCopy, amountTokenCopy, true);  <--------------- started from here! 

        require(
            mintAmount >= minReceiveAmount,
            "DV: minReceiveAmount > actual"
        );

        totalMinted[user] += mintAmount;

        _requireAndUpdateLimit(mintAmount);

        _tokenTransferFromUser(
            tokenInCopy,
            tokensReceiver,
            amountTokenWithoutFee,
            tokenDecimals
        );

        if (feeTokenAmount > 0)
            _tokenTransferFromUser(
                tokenInCopy,
                feeReceiver,
                feeTokenAmount,
                tokenDecimals
            );

        mToken.mint(user, mintAmount);

        bytes32 referrerIdCopy = referrerId;

        emit DepositInstant(
            user,
            tokenInCopy,
            tokenAmountInUsd,
            amountTokenCopy,
            feeTokenAmount,
            mintAmount,
            referrerIdCopy
        );
    }
function _calcAndValidateDeposit(
        address user,
        address tokenIn,
        uint256 amountToken,
        bool isInstant
    )
        internal
        returns (
            uint256 tokenAmountInUsd,
            uint256 feeTokenAmount,
            uint256 amountTokenWithoutFee,
            uint256 mintAmount,
            uint256 tokenInRate,
            uint256 tokenOutRate,
            uint256 tokenDecimals
        )
    {
        require(amountToken > 0, "DV: invalid amount");

        tokenDecimals = _tokenDecimals(tokenIn);

        _requireTokenExists(tokenIn);

        (uint256 amountInUsd, uint256 tokenInUSDRate) = _convertTokenToUsd(
            tokenIn,
            amountToken
        );
        tokenAmountInUsd = amountInUsd;
        tokenInRate = tokenInUSDRate;
        address userCopy = user;

        _requireAndUpdateAllowance(tokenIn, amountToken);

        feeTokenAmount = _truncate(
            _getFeeAmount(userCopy, tokenIn, amountToken, isInstant, 0),
            tokenDecimals
        );
        amountTokenWithoutFee = amountToken - feeTokenAmount;

        uint256 feeInUsd = (feeTokenAmount * tokenInRate) / 10**18;

        (uint256 mTokenAmount, uint256 mTokenRate) = _convertUsdToMToken(
            tokenAmountInUsd - feeInUsd
        );
        mintAmount = mTokenAmount;    <--------------- 
        tokenOutRate = mTokenRate;

        if (!isFreeFromMinAmount[userCopy]) {
            _validateMinAmount(userCopy, mintAmount);         <------------------  Uses minAmount instead of amountTokenWithoutFee
        }
        require(mintAmount > 0, "DV: invalid mint amount");
    }
function _validateMinAmount(address user, uint256 amountMTokenWithoutFee)     <-----------
        internal
        view
    {
        require(amountMTokenWithoutFee >= minAmount, "DV: mToken amount < min");    <---------------- No fee taken from minAmount

        if (totalMinted[user] != 0) return;

        require(
            amountMTokenWithoutFee >= minMTokenAmountForFirstDeposit,
            "DV: mint amount < min"
        );
    }

However since there was no fee deduction user gets to mint tokens more than intended as you can see here at line 130.

mToken.mint(user, mintAmount);

Where the MintAmount should have been deducted but it wasn't.

Tool used

Manual Review

Recommendation

Amend the _calcAndValidateDeposit function to properly deduct the fee from the USD equivalent before calculating mintAmount. This ensures that the mTokens minted accurately reflect the amount after fee deduction.

Solution:

Convert the total USD value (tokenAmountInUsd) into mTokens:

(uint256 totalMTokenAmount, uint256 mTokenRate) = _convertUsdToMToken(tokenAmountInUsd);

Convert the fee USD value (feeInUsd) into mTokens:

(uint256 feeMTokenAmount, ) = _convertUsdToMToken(feeInUsd);

Now, subtract the mToken equivalent of the fee from the total mToken amount:

uint256 amountMTokenWithoutFee = totalMTokenAmount - feeMTokenAmount
sherlock-admin3 commented 2 months ago

1 comment(s) were left on this issue during the judging contest.

merlinboii commented:

Invalid assumption. The amountMTokenWithoutFee is calculated from tokenAmountInUsd - feeInUsd