Summary
getAccountLeverage() may revert due to underflow error.
Vulnerability Details
getAccountLeverage()
returns the current leverage of a given account id, based on its cross margin collateral and open positions.
This function first gets the trading account's marginBalanceUsdX18
, data type SD59x18
:
SD59x18 marginBalanceUsdX18 = tradingAccount.getMarginBalanceUsd(tradingAccount.getAccountUnrealizedPnlUsd());
Then iterates the trading account's active markets to get totalPositionsNotionalValue
, data type is UD60x18
:
function getAccountLeverage(uint128 tradingAccountId) external view returns (UD60x18) {
UD60x18 totalPositionsNotionalValue;
...
for (uint256 i; i < tradingAccount.activeMarketsIds.length(); i++) {
...
totalPositionsNotionalValue = totalPositionsNotionalValue.add(positionNotionalValueX18);
}
}
At last, totalPositionsNotionalValue
is converted to type SD59x18
, and is divided by marginBalanceUsdX18
, the result value is converted to UD60x18
and returned:
return totalPositionsNotionalValue.intoSD59x18().div(marginBalanceUsdX18).intoUD60x18();
The problem is that marginBalanceUsdX18
can be negative if the trading account is in a loss (negative unPnl), so the result value is negative, the transaction will be reverted when it tries to convert the negative SD59x18
value to UD60x18
.
Please run the PoC in fillMarketOrder.t.sol to verify:
function testAudit() public {
address alice = makeAddr("Alice");
deal(address(usdc), alice, 2000e6);
vm.startPrank(alice);
usdc.approve(address(perpsEngine), 2000e6);
uint128 tradingAccountId = createAccountAndDeposit(2000e6, address(usdc));
perpsEngine.createMarketOrder(
OrderBranch.CreateMarketOrderParams({
tradingAccountId: tradingAccountId,
marketId: BTC_USD_MARKET_ID,
sizeDelta: 1e18
})
);
vm.stopPrank();
vm.startPrank(marketOrderKeepers[BTC_USD_MARKET_ID]);
bytes memory mockSignedReport = getMockedSignedReport(BTC_USD_STREAM_ID, MOCK_BTC_USD_PRICE);
perpsEngine.fillMarketOrder(tradingAccountId, BTC_USD_MARKET_ID, mockSignedReport);
vm.stopPrank();
updateMockPriceFeed(BTC_USD_MARKET_ID, MOCK_BTC_USD_PRICE * 9 / 10);
(
SD59x18 marginBalanceUsdX18,
,
,
) = perpsEngine.getAccountMarginBreakdown(tradingAccountId);
assertTrue(marginBalanceUsdX18.unwrap() < 0);
vm.expectRevert();
UD60x18 leverage = perpsEngine.getAccountLeverage(tradingAccountId);
}
Impact
getAccountLeverage()
reverts due to underflow error.
Tools Used
Manual Review
Recommendations
Return ud60x18(type(uint256).max)
if marginBalanceUsdX18
is a negative value.
function getAccountLeverage(uint128 tradingAccountId) external view returns (UD60x18) {
TradingAccount.Data storage tradingAccount = TradingAccount.loadExisting(tradingAccountId);
SD59x18 marginBalanceUsdX18 = tradingAccount.getMarginBalanceUsd(tradingAccount.getAccountUnrealizedPnlUsd());
UD60x18 totalPositionsNotionalValue;
if (marginBalanceUsdX18.isZero()) return marginBalanceUsdX18.intoUD60x18();
+ if (marginBalanceUsdX18.lt(SD59x18_ZERO)) return ud60x18(type(uint256).max);
...
}