Skip to content

Latest commit

 

History

History
117 lines (92 loc) · 4.46 KB

033.md

File metadata and controls

117 lines (92 loc) · 4.46 KB

Bitter Rouge Alpaca

High

The nuAssetManager.ethToNuAsset() pass the ETH decimal instead of the nuAsset decimal, leading incorrect output assetAmount

Summary

Due to incorrect decimal parameter, price conversion gets corrupted, alternatively, incorrect output assetAmount being returned or used.

Root Cause

https://github.com/sherlock-audit/2024-12-numa-audit/blob/ae1d7781efb4cb2c3a40c642887ddadeecabb97d/Numa/contracts/nuAssets/nuAssetManager.sol#L195 https://github.com/sherlock-audit/2024-12-numa-audit/blob/ae1d7781efb4cb2c3a40c642887ddadeecabb97d/Numa/contracts/nuAssets/nuAssetManager.sol#L205

    function ethToNuAsset(
        address _nuAsset,
        uint256 _amount
    ) public view returns (uint256 EthValue) {
        require(contains(_nuAsset), "bad nuAsset");
        nuAssetInfo memory info = getNuAssetInfo(_nuAsset);
        (address priceFeed, uint128 heartbeat) = (info.feed, info.heartbeat);
        return ethToToken(_amount, priceFeed, heartbeat, 18);    // @audit-issue should pass nuAssetDecimal 
    }

    function ethToNuAssetRoundUp(
        address _nuAsset,
        uint256 _amount
    ) public view returns (uint256 EthValue) {
        require(contains(_nuAsset), "bad nuAsset");
        nuAssetInfo memory info = getNuAssetInfo(_nuAsset);
        (address priceFeed, uint128 heartbeat) = (info.feed, info.heartbeat);
        return ethToTokenRoundUp(_amount, priceFeed, heartbeat, 18);      // @audit-issue should pass nuAssetDecimal
    }

The conversion of input ETH _amount to nuAssetAmount is done by ethToToken/ethToTokenRoundUp() function, where instead of nuAsset decimal, the 18 ETH decimal is passed. Let's see how it will cause issue,

https://github.com/sherlock-audit/2024-12-numa-audit/blob/ae1d7781efb4cb2c3a40c642887ddadeecabb97d/Numa/contracts/libraries/OracleUtils.sol#L98

considering ethLeftSide(_pricefeed)==false, which returns token X price in ETH,

    function ethToToken(
        uint256 _ethAmount,
        address _pricefeed,
        uint128 _chainlink_heartbeat,
        uint256 _decimals
    ) public view checkSequencerActive returns (uint256 tokenAmount) {
    ...snip...
        //if ETH is on the left side of the fraction in the price feed
        if (ethLeftSide(_pricefeed)) {
            tokenAmount = FullMath.mulDiv(
                _ethAmount,
                uint256(price),
                10 ** AggregatorV3Interface(_pricefeed).decimals()
            );
        } else {
            tokenAmount = FullMath.mulDiv(    // @audit (1) 
                _ethAmount,
                10 ** AggregatorV3Interface(_pricefeed).decimals(),
                uint256(price)
            );
        }

        tokenAmount = tokenAmount * 10 ** (18 - _decimals);   // @audit (2) 
    }

As can be seen above, the calculation (1) returns output tokenAmount in _ethAmount, which is in 18 decimal. And later the decimal of tokenAmount readjusted in (2) , since _decimal==18, the tokenAmount remains unchanged causing to return output token X to be in same 18 decimal.

Note that this issue is different than the other oracleUtils ethToToken() incorrect division of decimal issue that I've reported in separate report, and also both requires different fix in order to mitigate.

Internal pre-conditions

No response

External pre-conditions

No response

Attack Path

No response

Impact

This calculation error will lead incorrect accounting and transfer of tokens.

PoC

No response

Mitigation

Modify it as;

    function ethToNuAsset(
        address _nuAsset,
        uint256 _amount
    ) public view returns (uint256 EthValue) {
        require(contains(_nuAsset), "bad nuAsset");
        nuAssetInfo memory info = getNuAssetInfo(_nuAsset);
        (address priceFeed, uint128 heartbeat) = (info.feed, info.heartbeat);
-       return ethToToken(_amount, priceFeed, heartbeat, 18);
+       return ethToToken(_amount, priceFeed, heartbeat, IERC20Metadata(nuAsset).decimals());

    }

    function ethToNuAssetRoundUp(
        address _nuAsset,
        uint256 _amount
    ) public view returns (uint256 EthValue) {
        require(contains(_nuAsset), "bad nuAsset");
        nuAssetInfo memory info = getNuAssetInfo(_nuAsset);
        (address priceFeed, uint128 heartbeat) = (info.feed, info.heartbeat);
-       return ethToTokenRoundUp(_amount, priceFeed, heartbeat, 18);
+       return ethToTokenRoundUp(_amount, priceFeed, heartbeat, IERC20Metadata(nuAsset).decimals());
    }