Skip to content

[evm]: emit an OrderCancelled event with the order commitment from cancelOrder #1160

Description

@seunlanlege

cancelOrder on IntentGatewayV2 (evm/src/apps/IntentGatewayV2.sol) emits nothing. The only event in the whole cancellation lifecycle is EscrowRefunded, which fires when the refund finally lands on the source chain — in the same transaction for a same-chain cancel, after a Hyperbridge round trip for a cross-chain one. The gateway should emit a cancel event carrying the order commitment at the moment a cancellation is initiated, on whichever chain it is initiated.

Today

cancelOrder routes to one of three paths, none of which emits a gateway event:

  • _cancelSameChain (evm/src/apps/intentsv2/IntrinsicIntents.sol): refunds via _withdraw, so EscrowRefunded(commitment, tokens) appears in the same tx. The event says "refunded", not "cancelled", and is the same event a cross-chain refund arrives with.
  • _cancelFromSource (evm/src/apps/intentsv2/ExtrinsicIntents.sol): dispatches a GET request to the destination and returns. The only trace is the host's GetRequestEvent, which does not reference the order.
  • _cancelFromDest: writes _filled[commitment] = order.user — the order is unfillable from this point — and dispatches a RefundEscrow POST. Again nothing from the gateway; the only trace is the host's PostRequestEvent. Every later fill attempt reverts with Filled() (testFillOrderAfterDestinationChainCancellationFails), which from the outside is indistinguishable from a real fill, except that no OrderFilled was ever emitted. The Cancelled() error declared in IntentsBase.sol is never used.

What that costs each consumer:

  • Indexer (sdk/packages/indexer): OrderStatus is PLACED | FILLED | REDEEMED | REFUNDED, and a cancel only becomes visible when EscrowRefunded lands on the source chain. A cross-chain order cancelled from the destination stays PLACED for the whole round trip, and one whose refund message is stuck (no relayer, fee too low) stays PLACED forever — indistinguishable from an open order. Mainnet right now has 9 PLACED orders, 8 of them past their deadline; the indexer cannot say whether any of them has a cancel in flight. (142 orders have been refunded so far: 103 same-chain, 39 cross-chain.)
  • SDK (sdk/packages/sdk/src/protocols/intents/OrderCanceller.ts): to track a cross-chain cancel it parses GetRequestEvent / PostRequestEvent out of the cancel receipt with the EvmHost ABI, because the gateway gives it nothing keyed by commitment. orderStatusStream shows a user who just cancelled PLACED until the refund arrives. Anyone not going through the SDK — explorers, the order page, ops scripts — has no signal at all.
  • Simplex (sdk/packages/simplex/src/core/event-monitor.ts, filler.ts): the filler retracts its bid when it sees OrderFilled, but a destination-side cancel kills the order silently, so the bid stays live on Hyperbridge with its deposit locked until the hourly retractStaleBids sweep, and the order sits in the queue until a fill attempt reverts.

Proposal

Add to IntentsBase:

/**
 * @dev Emitted when an order's cancellation is initiated, on the chain it is initiated from.
 * For same-chain orders the refund is processed in the same transaction and `EscrowRefunded`
 * follows; for cross-chain orders `EscrowRefunded` follows on the source chain once the
 * cancellation has travelled through Hyperbridge.
 * @param commitment The order commitment hash.
 * @param canceller The account that initiated the cancellation.
 */
event OrderCancelled(bytes32 indexed commitment, address canceller);

and emit it from all three paths:

  • _cancelSameChain: alongside the _withdraw refund.
  • _cancelFromSource: after the GET dispatch.
  • _cancelFromDest: right after _filled[commitment] = order.user, i.e. at the moment the order stops being fillable.

commitment is indexed like every other order event, so it is a cheap topic filter. canceller is msg.sender: the destination path is permissionless after expiry, so whether the user or a relayer triggered it is information that is otherwise lost. EscrowRefunded stays the terminal event; the completion paths (onGetResponse, onAccept with RefundEscrow) are unchanged.

The gateway upgrades through cross-chain governance (RequestKind.UpgradeContract, #981), so this is an implementation upgrade and an additive ABI change — no redeploy.

Optional, same area: fillOrder could revert with the existing Cancelled() when _filled[commitment] == address(uint160(uint256(order.user))) so a solver's failed fill says why, with the caveat that a user filling their own order would also match.

Consumers

  • Indexer: handler for OrderCancelled in src/handlers/events/intentGatewayV3/, topic in scripts/templates/evm-chain.yaml.hbs, IntentGatewayV3.abi.json. New OrderStatus.CANCELLED written as an IOrderV3StatusMetadata row on the initiating chain (store the canceller — the row's filler column is the natural slot, possibly renamed to actor). CANCELLED is non-terminal for cross-chain orders: REFUNDED still closes them. A cancel arriving before the source node has indexed OrderPlaced goes through PendingStatusMetadata like any other out-of-order status.
  • SDK: OrderStatus.CANCELLED in sdk/packages/sdk/src/types, the ABI in src/abis/IntentGatewayV2.ts, and OrderCanceller confirming CANCEL_STARTED from the gateway's own event. The terminal set of orderStatusStream is unchanged.
  • Simplex: add OrderCancelled to the shared scanner's event set next to OrderFilled / PartialFill and route it through the same path as orderFilledOnChain — retract the bid and drop the order immediately instead of waiting for the sweep.
  • Docs: docs/content/developers/evm/intent-gateway/overview.mdx and cancelling-orders.mdx. Both still document EscrowRefunded(bytes32 indexed commitment) without the tokens field, and the IIntentGatewayV2 interface in sdk/packages/core/contracts/apps/IntentGatewayV2.sol declares the pre-tokens signatures for OrderFilled, EscrowReleased and EscrowRefunded too — worth bringing in line while adding the new event.
  • Tests: vm.expectEmit for each path in evm/tests/foundry/IntentGatewayV2Test.sol (testCancelOrder*) and IntentGatewayV2SameChainTest.sol (testSameChainCancel_*).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions