DeFiFoundry
60,000 USDC
View results
Submission Details
Severity: medium
Invalid

User may not be able to close position to realize their profit

Summary

User may not be able to close position to realize their profit due to minimum trade size limit.

Vulnerability Details

When a user calls to create an order, protocol checks trade size to enforce minimum trade size for this market.

// enforce minimum trade size for this market
perpMarket.checkTradeSize(ctx.sizeDeltaX18);

The minimum trade size can be updated by the admin, so the following scenario may happen:

  1. A user opens a position with a minimum trade size and high leverage;

  2. Later admin increases the minimum trade size;

  3. The user's position is in huge profit due to price pumping;

  4. User calls to close the position, but the transaction is reverted due to minimum trade size limit, the user can not realize their profit.

To close the position, user would be forced to increase the position size and then they can close the position, however, this is unfair to the user, and the operation can also be risky.

Please run the test PoC to verify:

function testAudit_UserCanNotClosePosition() public {
(
string memory name,
string memory symbol,
uint128 initialMarginRateX18,
uint128 maintenanceMarginRateX18,
uint128 maxOpenInterest,
uint128 maxSkew,
uint128 minTradeSizeX18,
uint256 skewScale,
OrderFees.Data memory orderFees
) = perpsEngine.getPerpMarketConfiguration(ETH_USD_MARKET_ID);
// Alice opens a position with minTradeSize
address alice = makeAddr("Alice");
uint256 depositAmount = 100e6;
deal(address(usdc), alice, depositAmount);
vm.startPrank(alice);
usdc.approve(address(perpsEngine), depositAmount);
uint128 tradingAccountId = createAccountAndDeposit(depositAmount, address(usdc));
perpsEngine.createMarketOrder(
OrderBranch.CreateMarketOrderParams({
tradingAccountId: tradingAccountId,
marketId: ETH_USD_MARKET_ID,
sizeDelta: int128(minTradeSizeX18)
})
);
vm.stopPrank();
vm.prank(marketOrderKeepers[ETH_USD_MARKET_ID]);
bytes memory mockSignedReport = getMockedSignedReport(ETH_USD_STREAM_ID, MOCK_ETH_USD_PRICE);
perpsEngine.fillMarketOrder(tradingAccountId, ETH_USD_MARKET_ID, mockSignedReport);
// Admin increase minTradeSize
changePrank({ msgSender: users.owner.account });
GlobalConfigurationBranch.UpdatePerpMarketConfigurationParams memory params = GlobalConfigurationBranch
.UpdatePerpMarketConfigurationParams({
name: name,
symbol: symbol,
priceAdapter: address(new MockPriceFeed(18, int256(MOCK_ETH_USD_PRICE))),
initialMarginRateX18: initialMarginRateX18,
maintenanceMarginRateX18: maintenanceMarginRateX18,
maxOpenInterest: maxOpenInterest,
maxSkew: maxSkew,
maxFundingVelocity: ETH_USD_MAX_FUNDING_VELOCITY,
minTradeSizeX18: minTradeSizeX18 * 2,
skewScale: skewScale,
orderFees: orderFees,
priceFeedHeartbeatSeconds: ETH_USD_PRICE_FEED_HEARTBEATS_SECONDS
});
perpsEngine.updatePerpMarketConfiguration(ETH_USD_MARKET_ID, params);
// ETH price rises and Alice is in profit
(Position.State memory positionState) = perpsEngine.getPositionState(tradingAccountId, ETH_USD_MARKET_ID, MOCK_ETH_USD_PRICE * 2);
assertEq(50000000125000000000, positionState.unrealizedPnlUsdX18.unwrap());
// However Alice cannot create order to close position due to minTradeSize limit
changePrank({ msgSender: alice });
vm.expectRevert({ revertData: abi.encodeWithSelector(Errors.TradeSizeTooSmall.selector) });
perpsEngine.createMarketOrder(
OrderBranch.CreateMarketOrderParams({
tradingAccountId: tradingAccountId,
marketId: ETH_USD_MARKET_ID,
sizeDelta: int128(positionState.sizeX18.unwrap())
})
);
}

Impact

User can not close position to realize their profit without taking more risk and increasing the position size.

Tools Used

Manual Review

Recommendations

User should be allowed to close their position despite the trade size.

Updates

Lead Judging Commences

inallhonesty Lead Judge
over 1 year ago
inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Appeal created

h2134 Submitter
over 1 year ago
inallhonesty Lead Judge
over 1 year ago
h2134 Submitter
over 1 year ago
inallhonesty Lead Judge
over 1 year ago
h2134 Submitter
over 1 year ago
inallhonesty Lead Judge
over 1 year ago
inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!