@@ -76,6 +76,15 @@ contract OrderModule is IOrderModule {
76
76
Position.TradeParams tradeParams;
77
77
}
78
78
79
+ struct Runtime_cancelOrder {
80
+ uint128 accountId;
81
+ uint128 marketId;
82
+ bool isStale;
83
+ bool isReady;
84
+ bool isMarketSolvent;
85
+ Order.Data order;
86
+ }
87
+
79
88
// --- Helpers --- //
80
89
81
90
/// @dev Reverts when `fillPrice > limitPrice` when long or `fillPrice < limitPrice` when short.
@@ -472,56 +481,111 @@ contract OrderModule is IOrderModule {
472
481
) external payable {
473
482
FeatureFlag.ensureAccessToFeature (Flags.CANCEL_ORDER);
474
483
PerpMarket.Data storage market = PerpMarket.exists (marketId);
475
- Account.Data storage account = Account.exists (accountId);
476
- Order.Data storage order = market.orders[accountId];
484
+ Runtime_cancelOrder memory runtime;
477
485
478
- // No order available to settle.
479
- if (order.sizeDelta == 0 ) {
486
+ runtime.accountId = accountId;
487
+ runtime.marketId = marketId;
488
+ runtime.order = market.orders[accountId];
489
+
490
+ if (runtime.order.sizeDelta == 0 ) {
480
491
revert ErrorUtil.OrderNotFound ();
481
492
}
482
493
483
494
PerpMarketConfiguration.GlobalData storage globalConfig = PerpMarketConfiguration.load ();
484
495
485
- uint64 commitmentTime = order.commitmentTime;
486
- (bool isStale , bool isReady ) = isOrderStaleOrReady (commitmentTime, globalConfig);
496
+ (runtime.isStale, runtime.isReady) = isOrderStaleOrReady (
497
+ runtime.order.commitmentTime,
498
+ globalConfig
499
+ );
487
500
488
- if (! isReady) {
501
+ if (! runtime. isReady) {
489
502
revert ErrorUtil.OrderNotReady ();
490
503
}
491
504
492
- // Only do the price divergence check for non stale orders. All stale orders are allowed to be canceled.
493
- if (! isStale) {
494
- PerpMarketConfiguration.Data storage marketConfig = PerpMarketConfiguration.load (
495
- marketId
496
- );
505
+ if (! runtime.isStale) {
506
+ validateNonStaleOrderCancellation (runtime, priceUpdateData);
507
+ }
497
508
498
- // Order is within settlement window. Check if price tolerance has exceeded.
499
- uint256 pythPrice = PythUtil.parsePythPrice (
500
- globalConfig,
501
- marketConfig,
502
- commitmentTime,
503
- priceUpdateData
504
- );
505
- uint256 fillPrice = Order.getFillPrice (
506
- market.skew,
507
- marketConfig.skewScale,
508
- order.sizeDelta,
509
- pythPrice
510
- );
509
+ uint256 keeperFee = chargeKeeperFee (accountId, marketId);
511
510
512
- if (! isPriceToleranceExceeded (order.sizeDelta, fillPrice, order.limitPrice)) {
513
- revert ErrorUtil.PriceToleranceNotExceeded (
514
- order.sizeDelta,
515
- fillPrice,
516
- order.limitPrice
517
- );
518
- }
511
+ emit OrderCanceled (accountId, marketId, keeperFee, runtime.order.commitmentTime);
512
+ delete market.orders[accountId];
513
+ }
514
+
515
+ function validateNonStaleOrderCancellation (
516
+ Runtime_cancelOrder memory runtime ,
517
+ bytes calldata priceUpdateData
518
+ ) private {
519
+ PerpMarketConfiguration.Data storage marketConfig = PerpMarketConfiguration.load (
520
+ runtime.marketId
521
+ );
522
+ PerpMarket.Data storage market = PerpMarket.exists (runtime.marketId);
523
+
524
+ uint256 pythPrice = PythUtil.parsePythPrice (
525
+ PerpMarketConfiguration.load (),
526
+ marketConfig,
527
+ runtime.order.commitmentTime,
528
+ priceUpdateData
529
+ );
530
+ uint256 fillPrice = Order.getFillPrice (
531
+ market.skew,
532
+ marketConfig.skewScale,
533
+ runtime.order.sizeDelta,
534
+ pythPrice
535
+ );
536
+ AddressRegistry.Data memory addresses = AddressRegistry.Data ({
537
+ synthetix: ISynthetixSystem (SYNTHETIX_CORE),
538
+ sUsd: SYNTHETIX_SUSD,
539
+ oracleManager: ORACLE_MANAGER
540
+ });
541
+
542
+ Position.Data storage oldPosition = market.positions[runtime.accountId];
543
+
544
+ int128 newPositionSize = oldPosition.size + runtime.order.sizeDelta;
545
+
546
+ // lockedCreditDelta is the change in credit that would be locked if the order was filled
547
+ uint256 newMinCredit = PerpMarket.getMinimumCreditWithPositionSize (
548
+ market,
549
+ marketConfig,
550
+ market.getOraclePrice (addresses),
551
+ (MathUtil.abs (newPositionSize).toInt () - MathUtil.abs (oldPosition.size).toInt ())
552
+ .to128 (),
553
+ addresses
554
+ );
555
+
556
+ // checks if the market would be solvent with this new credit delta
557
+ runtime.isMarketSolvent = PerpMarket.isMarketSolventForCredit (
558
+ market,
559
+ newMinCredit,
560
+ market.depositedCollateral[addresses.sUsd],
561
+ addresses
562
+ );
563
+
564
+ // Allow to cancel if the cancellation is due to market insolvency while not reducing the order
565
+ // If not, check if fill price exceeded acceptable price
566
+ if (
567
+ (runtime.isMarketSolvent ||
568
+ MathUtil.isSameSideReducing (oldPosition.size, newPositionSize)) &&
569
+ ! isPriceToleranceExceeded (runtime.order.sizeDelta, fillPrice, runtime.order.limitPrice)
570
+ ) {
571
+ revert ErrorUtil.PriceToleranceNotExceeded (
572
+ runtime.order.sizeDelta,
573
+ fillPrice,
574
+ runtime.order.limitPrice
575
+ );
519
576
}
577
+ }
520
578
521
- // If `isAccountOwner` then 0 else charge cancellation fee.
579
+ function chargeKeeperFee (
580
+ uint128 accountId ,
581
+ uint128 marketId
582
+ ) private returns (uint256 keeperFee ) {
583
+ Account.Data storage account = Account.exists (accountId);
584
+ PerpMarket.Data storage market = PerpMarket.exists (marketId);
522
585
523
- uint256 keeperFee;
524
586
if (ERC2771Context ._msgSender () != account.rbac.owner) {
587
+ PerpMarketConfiguration.GlobalData storage globalConfig = PerpMarketConfiguration
588
+ .load ();
525
589
uint256 ethPrice = INodeModule (ORACLE_MANAGER)
526
590
.process (globalConfig.ethOracleNodeId)
527
591
.price
@@ -538,9 +602,6 @@ contract OrderModule is IOrderModule {
538
602
keeperFee
539
603
);
540
604
}
541
-
542
- emit OrderCanceled (accountId, marketId, keeperFee, commitmentTime);
543
- delete market.orders[accountId];
544
605
}
545
606
546
607
// --- Views --- //
0 commit comments