Unsafe cast in take profit can lead to fund loss
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↗.