Assessment reports>Avantis>Critical findings>Unsafe cast in take profit can lead to fund loss
Category: Business Logic

Unsafe cast in take profit can lead to fund loss

Critical Severity
Critical Impact
High Likelihood

Description

For any open trade, the value of take profit can be set to any large value using the functions updateTp and updateTpAndSl. If the value of _newTP is set to type(uint256).max for a sell order, the function _currentPercentProfit will calculate the percent profit as the max gain percent (900%) due to the casting from uint256 to int.

function _currentPercentProfit(
        uint openPrice,
        uint currentPrice,
        bool buy,
        uint leverage
    ) private pure returns (int p) {
        int diff = buy ? (int(currentPrice) - int(openPrice)) : (int(openPrice) - int(currentPrice));
        int minPnlP = int(_PRECISION) * (-100);
        int maxPnlP = int(_MAX_GAIN_P) * int(_PRECISION);
        p = (diff * 100 * int(_PRECISION.mul(leverage))) / int(openPrice);
        p = p < minPnlP ? minPnlP : p > maxPnlP ? maxPnlP : p;
    }

For example, the TP can be updated to type(uint256).max using updateTp or updateTpAndSl. In case executeLimitOrder is called of the type LimitOrder.TP for a sell order, the value of currentPrice in _currentPercentProfit will be type(uint256).max. When this value is casted to int, it will become -1, and thus the diff would be openPrice + 1 and p will become ≈100*int(_PRECISION.mul(leverage)). Therefore, if the leverage is greater than nine, the function will return the profit as 900%.

The value of currentPrice can be set to t.tp in executeLimitCloseOrderCallback if the trade is of type LimitOrder.TP as shown below.

v.price = aggregator.pairsStorage().guaranteedSlEnabled(t.pairIndex)
? o.orderType == ITradingStorage.LimitOrder.TP ? t.tp : o.orderType == ITradingStorage.LimitOrder.SL
    ? t.sl
    : a.price
: a.price;

v.profitP = _currentPercentProfit(t.openPrice, v.price, t.buy, t.leverage);

Here is an example trade to execute the attack:

Start
  Balance of trader before: 1000000000
  Trader creates a short position:
    trade.trader = _trader;
    trade.pairIndex = _pairIndex;
    trade.index = _index;
    trade.initialPosToken = 0;
    trade.positionSizeUSDC = _amount;
    trade.openPrice =  _price;
    trade.buy = true;
    trade.leverage = 10e10;
    trade.tp = 0;
    trade.sl = 0;
    trade.timestamp = block.number;
  
  Trader calls `updateTp` with the `_newTp` as `0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`
 
  When execute limit close is called of type `LimitOrder.TP`, the trader receives 900% profit.

  Balance of trader after: 9791100000           

Impact

An attacker can create a malicious trade such that they always make 900% returns instantly. They could use this to drain the protocol.

Recommendations

While setting a new TP price, use the function _correctTp to check if the TP is in correct range using the callback.

Remediation

This issue has been acknowledged by Avantis Labs, Inc., and a fix was implemented in commit de74d560.

Zellic © 2025Back to top ↑