# PlaceOrder 函数解析 ## 功能概述 `PlaceOrder` 函数是处理新订单放置的关键函数,涉及订单验证、匹配和状态更新。 ## Question ![image](https://hackmd.io/_uploads/Hk4o1pmYp.png) ## Answer ⬇️ ## 主要操作 1. **订单验证** (`Validate the order against memclob in-memory state`): - 通过 `validateNewOrder` 函数完成。 - 检查订单是否已被取消、是否是有效的替换订单、是否是有效的减少仓位订单、以及订单是否已完全成交。 2. **订单重叠检查** (`If the newly placed order causes an overlap`): - 在 `mustPerformTakerOrderMatching` 函数中进行。 - 处理订单匹配和相关的业务逻辑。它确保订单按照规则被正确填充,并对抵押情况进行验证,以确保市场的稳定和安全。 3. **订单匹配逻辑** (`Match orders within that orderbook`): - 也在 `matchOrder` 函数中实现。 - 处理订单匹配的逻辑,包括找到最佳匹配订单、执行匹配、以及处理匹配结果。 4. **maker订单抵押检查** (`If any maker orders fail collateralization checks`): - 在 `ProcessSingleMatch` 函数中进行。 - 检查每个匹配的订单是否满足抵押要求。 - 预处理检查(Pre-processing Check): 在交易匹配之前,对订单中的抵押物进行预处理检查。这包括确保订单中的抵押物满足最低要求,以及检查是否有足够的抵押物来执行交易。这一阶段的检查旨在在交易执行之前快速确定订单的合法性。 - 交易匹配后检查(Post-Match Check): 在交易匹配成功后,对交易匹配后的抵押物进行检查。这包括检查交易是否满足抵押物的要求,确保抵押物的变化不会导致系统风险增加等。这一阶段的检查旨在确保匹配的交易在实际执行时不会引发问题。 5. **移除失败的maker订单** (`They will be removed`): - 在 `mustPerformTakerOrderMatching` 函数中处理。 - 包括了一个检查,用于确定是否需要从订单簿中移除特定的订单。 6. **memclob [状态内容](https://github.com/dydxprotocol/v4-chain/blob/main/protocol/x/clob/memclob/memclob.go#L30)** (`Memclob state`): 这个结构体封装了基于内存的中心限价订单簿(CLOB)的状态和行为,重点是订单匹配的价格-时间优先原则 - **openOrders**:指向一个 `memclobOpenOrders` 结构体的指针,可能用于管理CLOB中的开放订单。 - **cancels**:指向一个 `memclobCancels` 结构体的指针,可能用于处理订单取消。 - **operationsToPropose**:类型为 `types.OperationsToPropose` 的结构体,似乎用于存储提议执行的操作。 - **clobKeeper**:`types.MemClobKeeper` 类型的字段,可能用于与CLOB交互并维护其状态。 - **generateOffchainUpdates**:一个布尔值,指示是否应生成链下更新。 7. **更新 memclob 状态** (`How is memclob state updated`): - 通过 `mustUpdateMemclobStateWithMatches` 函数。 - 处理匹配结果,更新订单簿状态,记录匹配的订单,并在必要时生成离线更新消息。 8. **优化填充大小计算** (`Amount of optimistically filled size`): - 在 `PlaceOrder` 函数中计算。表示在尝试将接单方订单与订单簿匹配时填充的订单量,通过比较订单匹配前后的剩余大小来计算。 9. **订单验证失败的处理** (`If order validation failed`): - PlaceOrder 函数将不会修改 memclob 状态,并返回一个错误。 - 确保只有验证通过的订单才会影响市场状态。 ## 关键函数 ### [validateNewOrder](https://github.com/dydxprotocol/v4-chain/blob/main/protocol/x/clob/memclob/memclob.go#L1326) 1. **检查短期订单**: - 首先检查订单是否为短期订单。 - 如果是短期订单,则验证是否存在具有大于或等于新订单的`GoodTilBlock`的取消订单。如果存在这样的取消订单,它会返回一个指示订单已取消的错误。 2. **检查现有订单**: - 检查是否已经存在具有相同`OrderId`的订单(作为挂单或已匹配订单)。 - 如果存在这样的订单,它会确保新订单的`GoodTilBlock`大于等于现有订单。如果不大于,则返回一个指示替代无效的错误。 3. **检查减仓订单**: - 如果订单是减仓订单,它会检查订单大小的符号是否与当前仓位大小相反。 - 如果它们的符号相同,它会返回一个指示减仓订单会增加仓位大小的错误。 4. **剩余数量检查**: - 它检查被替代的订单是否至少剩余`MinOrderBaseQuantums`数量的大小。 - 如果剩余数量小于`MinOrderBaseQuantums`,它会返回一个指示订单已完全成交的错误。 5. **即时或取消和全部成交订单检查**: - 对于即时或取消(IOC)和全部成交(FOK)订单,它确保它们只能被成交一次,剩余数量变得不可成交。 - 它防止IOC订单替代部分成交的订单。 - 如果IOC或FOK订单已经成交,它会返回一个错误。 #### 代码: ``` // validateNewOrder 将执行以下验证以确保订单可以被放置(如果任何条件为false,则返回错误): func (m *MemClobPriceTimePriority) validateNewOrder( ctx sdk.Context, order types.Order, ) ( err error, ) { defer telemetry.ModuleMeasureSince( types.ModuleName, time.Now(), metrics.PlaceOrder, metrics.Memclob, metrics.ValidateOrder, metrics.Latency, ) orderId := order.OrderId // 1. 检查是否为短期订单和取消订单 if orderId.IsShortTermOrder() { // 如果取消订单的GoodTilBlock大于等于新订单,则返回错误 if cancelTilBlock, cancelExists := m.cancels.get(orderId); cancelExists && cancelTilBlock >= order.GetGoodTilBlock() { return errorsmod.Wrapf( types.ErrOrderIsCanceled, "Order: %+v, Cancellation GoodTilBlock: %d", order, cancelTilBlock, ) } } existingRestingOrder, restingOrderExists := m.openOrders.getOrder(ctx, orderId) existingMatchedOrder, matchedOrderExists := m.operationsToPropose.MatchedOrderIdToOrder[orderId] // 2. 检查具有相同OrderId的现有订单 // 如果具有相同OrderId的订单已存在于订单簿中(或已匹配),则必须验证新订单的GoodTilBlock是否大于旧订单。 // 如果大于,那么可以放置新订单(替代旧订单如果旧订单已在订单簿中)。 // 如果等于或小于,则被拒绝。 if restingOrderExists && existingRestingOrder.MustCmpReplacementOrder(&order) >= 0 { return types.ErrInvalidReplacement } if matchedOrderExists && existingMatchedOrder.MustCmpReplacementOrder(&order) >= 0 { return types.ErrInvalidReplacement } // 3. 检查减仓订单 if order.IsReduceOnly() { existingPositionSize := m.clobKeeper.GetStatePosition(ctx, orderId.SubaccountId, order.GetClobPairId()) orderSize := order.GetBigQuantums() // 如果减仓订单的大小与现有仓位大小的符号相同,返回错误。 if orderSize.Sign()*existingPositionSize.Sign() != -1 { return types.ErrReduceOnlyWouldIncreasePositionSize } } // 4. 检查剩余数量 orderbook := m.openOrders.mustGetOrderbook(ctx, order.GetClobPairId()) remainingAmount, hasRemainingAmount := m.GetOrderRemainingAmount(ctx, order) if !hasRemainingAmount || remainingAmount < orderbook.MinOrderBaseQuantums { return errorsmod.Wrapf( types.ErrOrderFullyFilled, "Order remaining amount is less than `MinOrderBaseQuantums`. Remaining amount: %d. Order: %+v", remainingAmount, order.GetOrderTextString(), ) } // 5. 检查即时或取消和全部成交订单 if order.RequiresImmediateExecution() && remainingAmount < order.GetBaseQuantums() { // 防止IOC/FOK订单替代部分成交订单。 if restingOrderExists { return errorsmod.Wrapf( types.ErrInvalidReplacement, "Cannot replace partially filled order with IOC order. Size: %d, Fill Amount: %d.", order.GetBaseQuantums(), order.GetBaseQuantums()-remainingAmount, ) } return errorsmod.Wrapf( types.ErrImmediateExecutionOrderAlreadyFilled, "Order: %s", order.GetOrderTextString(), ) } return nil } ``` --- ### [mustPerformTakerOrderMatching](https://github.com/dydxprotocol/v4-chain/blob/main/protocol/x/clob/memclob/memclob.go#L1509) 1. **初始化和准备**: - 函数一开始进行了各种初始化操作,包括设置用于存储匹配结果和状态信息的变量,以及获取与匹配相关的订单簿和其他信息。 2. **遍历订单簿**: - 函数使用循环遍历订单簿,以查找可匹配的订单。这是交易所核心功能的一部分,因为它决定了哪些订单将被匹配。 3. **匹配逻辑**: - 当找到适当的maker订单时,函数执行订单匹配操作。这包括计算匹配数量、检查抵押和更新订单状态。 - 匹配是核心功能,确保订单按照规则和条件得到填充。 4. **抵押检查**: - 函数执行抵押检查,以确保匹配的订单在抵押方面是合法的。这是为了保护市场免受潜在的风险和问题。 5. **清算订单**: - 清算订单是一种特殊类型的订单,通常用于处理违约情况。函数可以处理清算订单,确保它们被妥善处理。 6. **更新状态**: - 函数在成功匹配订单后,更新订单的状态信息,包括订单的剩余数量和乐观填充数量。 7. **返回结果**: - 最后,函数返回了有关匹配和状态的详细信息,以便其他系统组件可以根据需要进行进一步处理。 #### 代码: 这部分代码有点过多,为了方便浏览,分模块展示 ``` // 1. 初始化和准备 // 在这部分,函数开始执行各种初始化操作。这包括设置用于存储匹配结果的变量和状态信息, // 以及获取与匹配相关的订单簿和其他信息。 // 初始化返回变量。 newMakerFills = make([]types.MakerFill, 0) matchedOrderHashToOrder = make(map[types.OrderHash]types.MatchableOrder) matchedMakerOrderIdToOrder = make(map[types.OrderId]types.Order) takerOrderStatus.OrderStatus = types.Success makerOrdersToRemove = make([]OrderWithRemovalReason, 0) // 初始化用于遍历订单簿的变量。 clobPairId := newTakerOrder.GetClobPairId() orderbook := m.openOrders.mustGetOrderbook(ctx, clobPairId) takerIsBuy := newTakerOrder.IsBuy() takerSubaccountId := newTakerOrder.GetSubaccountId() takerIsLiquidation := newTakerOrder.IsLiquidation() // 存储taker订单的剩余大小,以确定匹配结束后订单的填充量。 // 如果订单是清算类型,则剩余大小为订单的全部大小。 // 否则,这是一个常规订单,可能已经部分匹配,所以我们获取这个订单的剩余大小。 var takerRemainingSize satypes.BaseQuantums if takerIsLiquidation { takerRemainingSize = newTakerOrder.GetBaseQuantums() } else { var takerHasRemainingSize bool takerRemainingSize, takerHasRemainingSize = m.GetOrderRemainingAmount( ctx, newTakerOrder.MustGetOrder(), ) if !takerHasRemainingSize { panic(fmt.Sprintf("mustPerformTakerOrderMatching: order has no remaining amount %v", newTakerOrder)) } } takerRemainingSizeBeforeMatching := takerRemainingSize // 初始化用于跟踪本次匹配周期中完成的匹配的变量。 var makerLevelOrder *types.LevelOrder var takerOrderHash types.OrderHash var takerOrderHashWasSet bool var bigTotalMatchedAmount *big.Int = big.NewInt(0) ``` ``` // 2. **遍历订单簿**: // - 函数使用循环遍历订单簿,以查找可匹配的订单。这是交易所核心功能的一部分,因为它决定了哪些订单将被匹配。 for { var foundMakerOrder bool // 如果 maker level 订单尚未初始化,则我们刚开始匹配,并且需要查找相反方向上的最佳订单。 // 否则,maker 订单必定已完全匹配(因为 taker 订单有非零剩余数量),我们需要查找下一个最佳的 maker 订单。 if makerLevelOrder == nil { makerLevelOrder, foundMakerOrder = m.openOrders.getBestOrderOnSide(orderbook, !takerIsBuy) } else { makerLevelOrder, foundMakerOrder = m.openOrders.findNextBestLevelOrder(ctx, makerLevelOrder) } // 如果没有找到下一个最佳的 maker 订单,停止匹配。 if !foundMakerOrder { break } makerOrder := makerLevelOrder.Value // 检查订单簿是否交叉。 var takerOrderCrossesMakerOrder bool if takerIsBuy { takerOrderCrossesMakerOrder = newTakerOrder.GetOrderSubticks() >= makerOrder.Order.GetOrderSubticks() } else { takerOrderCrossesMakerOrder = newTakerOrder.GetOrderSubticks() <= makerOrder.Order.GetOrderSubticks() } // 如果 taker 订单不再交叉 maker 订单,停止匹配。 if !takerOrderCrossesMakerOrder { break } makerOrderId := makerOrder.Order.OrderId makerSubaccountId := makerOrderId.SubaccountId // 如果 taker 订单替代了 maker 订单,跳过此订单并继续匹配。 // 注意,在匹配结束后,maker 订单将从订单簿中移除。 if !takerIsLiquidation && makerOrderId == newTakerOrder.MustGetOrder().OrderId { continue } // 如果匹配的 maker 订单的订单 ID 不同且来自与 taker 订单相同的子账户, // 那么我们不能匹配这些订单。取消 maker 订单并继续匹配。 // TODO(DEC-1562): 确定是否应该以不同方式处理自成交交易。 if makerSubaccountId == takerSubaccountId { makerOrdersToRemove = append( makerOrdersToRemove, OrderWithRemovalReason{ Order: makerOrder.Order, RemovalReason: types.OrderRemoval_REMOVAL_REASON_INVALID_SELF_TRADE, }, ) continue } makerRemainingSize, makerHasRemainingSize := m.GetOrderRemainingAmount(ctx, makerOrder.Order) if !makerHasRemainingSize { panic(fmt.Sprintf("mustPerformTakerOrderMatching: maker 订单没有剩余数量 %v", makerOrder.Order)) } // 匹配数量是两个订单剩余数量的最小值。 var matchedAmount satypes.BaseQuantums if takerRemainingSize >= makerRemainingSize { matchedAmount = makerRemainingSize } else { matchedAmount = takerRemainingSize } ``` ``` // 3. **匹配逻辑**: // - 当找到适当的 maker 订单时,函数执行订单匹配操作。这包括计算匹配数量、检查抵押和更新订单状态。 // - 匹配是核心功能,确保订单按照规则和条件得到填充。 // 对于每个涉及匹配的子账户,如果订单是仅减少,则我们应验证位置大小是否因匹配订单而未更改或增加。 if makerOrder.Order.IsReduceOnly() { currentPositionSize := m.clobKeeper.GetStatePosition(ctx, makerSubaccountId, clobPairId) resizedMatchAmount := m.resizeReduceOnlyMatchIfNecessary( ctx, makerSubaccountId, clobPairId, currentPositionSize, matchedAmount, !takerIsBuy, ) // 如果匹配大小为零,表示 maker 订单是仅减少订单,可能会增加制造商的持仓大小, // 我们需要查找下一个最佳的 maker 订单。 // 如果制造商在此匹配循环中具有先前匹配的比率, // 从而更改其持仓方向,则所有其余的休眠仅减少订单都是无效的。 if resizedMatchAmount == 0 { // TODO(DEC-1415):撤销此仅减少错误补丁。 makerOrdersToRemove = append( makerOrdersToRemove, OrderWithRemovalReason{ Order: makerOrder.Order, RemovalReason: types.OrderRemoval_REMOVAL_REASON_INVALID_REDUCE_ONLY, }, ) continue } matchedAmount = resizedMatchAmount } if newTakerOrder.IsReduceOnly() { currentPositionSize := m.clobKeeper.GetStatePosition(ctx, takerSubaccountId, clobPairId) resizedMatchAmount := m.resizeReduceOnlyMatchIfNecessary( ctx, takerSubaccountId, clobPairId, currentPositionSize, matchedAmount, takerIsBuy, ) // 如果 taker 仅减少订单的大小调整为零,表示订单与 taker 的持仓方向相同, // 此订单应已无法通过验证而失败。 if resizedMatchAmount == 0 { panic("mustPerformTakerOrderMatching: taker reduce-only order resized to 0") } matchedAmount = resizedMatchAmount } // 执行抵押检查以验证订单是否可以填充。 matchWithOrders := types.MatchWithOrders{ TakerOrder: newTakerOrder, MakerOrder: &makerOrder.Order, FillAmount: matchedAmount, } success, takerUpdateResult, makerUpdateResult, _, err := m.clobKeeper.ProcessSingleMatch(ctx, &matchWithOrders) if err != nil && !errors.Is(err, satypes.ErrFailedToUpdateSubaccounts) { if errors.Is(err, types.ErrLiquidationExceedsSubaccountMaxInsuranceLost) { // 子账户已达到最大保险亏损区块限制。停止匹配。 telemetry.IncrCounter(1, types.ModuleName, metrics.SubaccountMaxInsuranceLost, metrics.Count) takerOrderStatus.OrderStatus = types.LiquidationExceededSubaccountMaxInsuranceLost break } if errors.Is(err, types.ErrLiquidationExceedsSubaccountMaxNotionalLiquidated) { // 子账户已达到最大名义清算区块限制。停止匹配。 telemetry.IncrCounter(1, types.ModuleName, metrics.SubaccountMaxNotionalLiquidated, metrics.Count) takerOrderStatus.OrderStatus = types.LiquidationExceededSubaccountMaxNotionalLiquidated break } if errors.Is(err, types.ErrInsuranceFundHasInsufficientFunds) { // 需要减杠杆。停止匹配。 telemetry.IncrCounter(1, types.ModuleName, metrics.LiquidationRequiresDeleveraging, metrics.Count) takerOrderStatus.OrderStatus = types.LiquidationRequiresDeleveraging break } // 由于这是未知错误,所以引发恐慌。 m.clobKeeper.Logger(ctx).Error( "Unexpected error from `ProcessSingleMatch", "error", err, "matchWithOrders", matchWithOrders, ) panic(err) } // 如果抵押检查失败,taker 或 maker 订单中的一个或两个都未通过抵押检查。 // 请注意,如果 taker 是订单,则只有 maker 可能会在匹配期间未通过抵押检查。 // 因此,我们必须执行以下条件逻辑: // - 如果 maker 订单未通过抵押检查,则必须从订单簿中删除它。 // - 如果 taker 订单不是清算订单并且未通过抵押检查, // 我们必须停止匹配。 // - 如果 taker 订单是清算订单或通过抵押检查, // 则需要继续匹配,尝试找到新的重叠 maker 订单。 if !success { makerCollatOkay := updateResultToOrderStatus(makerUpdateResult).IsSuccess() takerCollatOkay := takerIsLiquidation || updateResultToOrderStatus(takerUpdateResult).IsSuccess() // 如果 maker 订单未通过抵押检查,将 maker 订单 ID 添加到待删除订单的列表中, // 在匹配结束后将其删除。 if !makerCollatOkay { makerOrdersToRemove = append( makerOrdersToRemove, OrderWithRemovalReason{ Order: makerOrder.Order, RemovalReason: types.OrderRemoval_REMOVAL_REASON_UNDERCOLLATERALIZED, }, ) } // 如果这不是清算订单且 taker 订单未通过抵押检查,停止匹配。 if !takerCollatOkay { takerOrderStatus.OrderStatus = updateResultToOrderStatus( takerUpdateResult, ) break } // taker 订单是清算或已通过抵押检查,因此我们可以继续匹配,尝试找到新的重叠 maker 订单。 continue } // 订单已成功匹配,并且状态已更新。 // 为了将订单标记为已匹配,执行以下操作: // 1. 从 taker 订单的剩余数量中扣除 `matchedAmount`, // 并将匹配数量添加到此匹配循环的总匹配数量中。 // 2. 将 maker 和 taker 订单哈希添加到订单哈希映射中。 // 3. 将新挂单的挂单添加到已排序的新挂单切片中。 // 4. 如果 taker 订单是仅减少订单且用户的持仓大小现在为零, // 则取消剩余的仅减少订单大小,并停止匹配。 // 1. takerRemainingSize -= matchedAmount if newTakerOrder.IsBuy() { bigTotalMatchedAmount.Add(bigTotalMatchedAmount, matchedAmount.ToBigInt()) } else { bigTotalMatchedAmount.Sub(bigTotalMatchedAmount, matchedAmount.ToBigInt()) } // 2. makerOrderHash := makerOrder.Order.GetOrderHash() matchedOrderHashToOrder[makerOrderHash] = &makerOrder.Order matchedMakerOrderIdToOrder[makerOrderId] = makerOrder.Order // 请注意,只有在每个匹配循环中首次进入 if 语句才会执行此 if 语句。 // 因为我们预计下达与匹配订单的比率约为 100:1, // 所以如果订单没有匹配,我们希望避免对 taker 订单进行哈希处理。 if !takerOrderHashWasSet { takerOrderHash = newTakerOrder.GetOrderHash() matchedOrderHashToOrder[takerOrderHash] = newTakerOrder takerOrderHashWasSet = true } // 3. newMakerFills = append(newMakerFills, types.MakerFill{ MakerOrderId: makerOrderId, FillAmount: matchedAmount.ToUint64(), }) // 4. if newTakerOrder.IsReduceOnly() && takerRemainingSize > 0 { takerStatePositionSize := m.clobKeeper.GetStatePosition(ctx, takerSubaccountId, clobPairId) if takerStatePositionSize.Sign() == 0 { // TODO(DEC-847):更新逻辑以正确删除具有状态的 taker 仅减少订单。 takerOrderStatus.OrderStatus = types.ReduceOnlyResized break } } // 如果 taker 订单已完全匹配,停止匹配。 if takerRemainingSize == 0 { break } } // 更新匹配结束后 taker 订单的剩余数量。 takerOrderStatus.RemainingQuantums = takerRemainingSize takerOrderStatus.OrderOptimisticallyFilledQuantums = takerRemainingSizeBeforeMatching - takerRemainingSize return newMakerFills, matchedOrderHashToOrder, matchedMakerOrderIdToOrder, makerOrdersToRemove, takerOrderStatus } ``` ``` // 4.抵押检查 // 函数执行抵押检查,以确保匹配的订单在抵押方面是合法的。这是为了保护市场免受潜在的风险和问题。 if !success { makerCollatOkay := updateResultToOrderStatus(makerUpdateResult).IsSuccess() takerCollatOkay := takerIsLiquidation || updateResultToOrderStatus(takerUpdateResult).IsSuccess() // 如果 maker 订单或 taker 订单未通过抵押检查,它们将被标记为未通过抵押检查的订单。 // 在抵押检查不通过的情况下,订单可能会导致市场中的不稳定或风险。 // 如果 maker 订单未通过抵押检查,将 maker 订单 ID 添加到待删除订单的列表中, // 因为这是不稳定的订单,应从订单簿中移除。 if !makerCollatOkay { makerOrdersToRemove = append( makerOrdersToRemove, OrderWithRemovalReason{ Order: makerOrder.Order, RemovalReason: types.OrderRemoval_REMOVAL_REASON_UNDERCOLLATERALIZED, }, ) } // 如果 taker 订单不是清算订单且未通过抵押检查,停止匹配。 // 在这种情况下,匹配可能会导致市场中的不稳定或风险。 if !takerCollatOkay { takerOrderStatus.OrderStatus = updateResultToOrderStatus( takerUpdateResult, ) break } // 如果 taker 订单是清算订单或通过抵押检查, // 则需要继续匹配,尝试找到新的重叠 maker 订单。 // 在清算订单或通过抵押检查的情况下,匹配是允许的。 continue } ``` ``` // 5.清算订单 // 清算订单是一种特殊类型的订单,通常用于处理违约情况。函数可以处理清算订单,确保它们被妥善处理。 // 更新本地订单簿变量,以便清除已清算的订单。 for _, removal := range makerOrdersToRemove { if takerIsBuy { orderbook.RemoveSellOrder(removal.Order) } else { orderbook.RemoveBuyOrder(removal.Order) } } // 继续匹配,寻找下一个重叠的 maker 订单。 continue ``` ``` // 6.更新状态 // 函数在成功匹配订单后,更新订单的状态信息,包括订单的剩余数量和乐观填充数量。 // 1. 从taker订单的剩余数量中扣除`matchedAmount`,并将匹配数量添加到此匹配循环的总匹配数量中。 takerRemainingSize -= matchedAmount // 计算总匹配数量,根据是买单还是卖单来增加或减少。 if newTakerOrder.IsBuy() { bigTotalMatchedAmount.Add(bigTotalMatchedAmount, matchedAmount.ToBigInt()) } else { bigTotalMatchedAmount.Sub(bigTotalMatchedAmount, matchedAmount.ToBigInt()) } // 2. 将maker订单和taker订单的哈希值添加到订单哈希的映射中,以跟踪已匹配的订单。 makerOrderHash := makerOrder.Order.GetOrderHash() matchedOrderHashToOrder[makerOrderHash] = &makerOrder.Order matchedMakerOrderIdToOrder[makerOrderId] = makerOrder.Order // 注意:这个if语句只会在每个匹配循环中进入一次。因为我们期望已下单与已匹配订单的比率大约为100:1, // 所以我们希望避免在订单没有匹配时计算taker订单的哈希值。 if !takerOrderHashWasSet { takerOrderHash = newTakerOrder.GetOrderHash() matchedOrderHashToOrder[takerOrderHash] = newTakerOrder takerOrderHashWasSet = true } // 3. 将新的匹配挂单添加到挂单的有序切片中,以供后续处理。 newMakerFills = append(newMakerFills, types.MakerFill{ MakerOrderId: makerOrderId, FillAmount: matchedAmount.ToUint64(), }) // 4. 如果taker订单是reduce-only订单,并且用户的持仓大小现在为零,取消任何剩余数量的reduce-only订单,并停止匹配。 if newTakerOrder.IsReduceOnly() && takerRemainingSize > 0 { takerStatePositionSize := m.clobKeeper.GetStatePosition(ctx, takerSubaccountId, clobPairId) if takerStatePositionSize.Sign() == 0 { // TODO:(DEC-847):更新逻辑以正确删除有状态的taker reduce-only订单。 takerOrderStatus.OrderStatus = types.ReduceOnlyResized break } } // 如果taker订单已完全匹配,停止匹配。 if takerRemainingSize == 0 { break } ``` ``` // 7.返回结果 // 最后,函数返回了有关匹配和状态的详细信息,以便其他系统组件可以根据需要进行进一步处理。 // 函数返回多个值,包括: // - 匹配的新maker订单列表 // - 匹配的订单哈希到订单的映射 // - 匹配的maker订单ID到订单的映射 // - 需要从订单簿中移除的maker订单列表 // - taker订单的状态信息,包括剩余数量、乐观填充数量和最后的抵押检查结果。 // 1. 更新taker订单的剩余数量,现在匹配已结束。 // 2. 计算taker订单的乐观填充数量,这是在匹配之前的剩余数量与匹配后的剩余数量之差。 // 3. 返回匹配的结果,包括新的maker挂单、匹配的订单哈希到订单的映射、匹配的maker订单ID到订单的映射、需要从订单簿中移除的maker订单和taker订单的状态信息。 // 1. 更新taker订单的剩余数量,现在匹配已结束。 takerOrderStatus.RemainingQuantums = takerRemainingSize // 2. 计算taker订单的乐观填充数量,这是在匹配之前的剩余数量与匹配后的剩余数量之差。 takerOrderStatus.OrderOptimisticallyFilledQuantums = takerRemainingSizeBeforeMatching - takerRemainingSize // 3. 返回匹配的结果,包括新的maker挂单、匹配的订单哈希到订单的映射、匹配的maker订单ID到订单的映射、需要从订单簿中移除的maker订单和taker订单的状态信息。 return newMakerFills, matchedOrderHashToOrder, matchedMakerOrderIdToOrder, makerOrdersToRemove, takerOrderStatus ``` --- ### [matchOrder](https://github.com/dydxprotocol/v4-chain/blob/main/protocol/x/clob/memclob/memclob.go#L743) 主要作用是将提供的MatchableOrder作为接收方订单与相应的订单簿进行匹配。该函数将返回匹配订单的状态,以及新的接收方待处理的匹配。如果订单匹配导致任何错误,将丢弃所有状态更新。 1. **初始化变量**: 函数开始时,初始化了一些变量,包括用于保存离线更新的`offchainUpdates`以及分支状态的`branchedContext`和`writeCache`。 2. **尝试匹配订单**: - 调用`mustPerformTakerOrderMatching`函数来尝试将订单与订单簿匹配。 - 获取新的制造商成交记录(`newMakerFills`)、匹配的订单哈希到订单的映射、匹配的制造商订单ID到订单的映射、要删除的制造商订单以及接收方订单的状态。 3. **处理替代订单**: - 如果这是一个替代订单(非清算订单),则确保从订单簿中移除现有订单。 - 将要移除的订单添加到`makerOrdersToRemove`列表中。 4. **处理制造商订单的移除**: - 针对每个应该被移除的制造商订单,从订单簿中移除它,并在离线更新中生成相关的信息。 - 在生成离线更新时,根据订单是否与接收方订单来自同一子帐户,设置原因为“SELF_TRADE错误”或“UNDERCOLLATERALIZED”。 5. **处理匹配错误**: - 根据情况,设置匹配错误。如果订单是“全部成交或取消”(FOK)订单且未完全成交,或者订单是“仅发布”(Post-Only)订单且导致与制造商订单交叉,将匹配错误设置为相应的错误类型。 6. **更新memclob状态**: - 如果匹配是有效的,并且放置接收方订单生成了有效的匹配,使用匹配结果来更新memclob状态。 - 生成的离线更新被添加到`offchainUpdates`中,并提交分支状态的写入缓存。 #### 代码: ``` // matchOrder will match the provided `MatchableOrder` as a taker order against the respective orderbook. // This function will return the status of the matched order, along with the new taker pending matches. // If order matching results in any error, all state updates wil be discarded. func (m *MemClobPriceTimePriority) matchOrder( ctx sdk.Context, order types.MatchableOrder, ) ( orderStatus types.TakerOrderStatus, offchainUpdates *types.OffchainUpdates, makerOrdersToRemove []OrderWithRemovalReason, err error, ) { // 1. **初始化变量**: // 函数开始时,初始化了一些变量,包括用于保存离线更新的`offchainUpdates`以及分支状态的`branchedContext`和`writeCache`。 offchainUpdates = types.NewOffchainUpdates() branchedContext, writeCache := ctx.CacheContext() // 2. **尝试匹配订单**: // 调用`mustPerformTakerOrderMatching`函数来尝试将订单与订单簿匹配。 // 获取新的制造商成交记录(`newMakerFills`)、匹配的订单哈希到订单的映射、匹配的制造商订单ID到订单的映射、要删除的制造商订单以及接收方订单的状态。 newMakerFills, matchedOrderHashToOrder, matchedMakerOrderIdToOrder, makerOrdersToRemove, takerOrderStatus := m.mustPerformTakerOrderMatching(branchedContext, order) // 3. **处理替代订单**: // 如果这是一个替代订单(非清算订单),则确保从订单簿中移除现有订单。 // 将要移除的订单添加到`makerOrdersToRemove`列表中。 if !order.IsLiquidation() { orderId := order.MustGetOrder().OrderId if orderToBeReplaced, found := m.openOrders.getOrder(branchedContext, orderId); found { makerOrdersToRemove = append(makerOrdersToRemove, OrderWithRemovalReason{Order: orderToBeReplaced}) } } // 4. **处理制造商订单的移除**: // 针对每个应该被移除的制造商订单,从订单簿中移除它,并在离线更新中生成相关的信息。 // 在生成离线更新时,根据订单是否与接收方订单来自同一子帐户,设置原因为“SELF_TRADE错误”或“UNDERCOLLATERALIZED”。 for _, makerOrderWithRemovalReason := range makerOrdersToRemove { makerOrderId := makerOrderWithRemovalReason.Order.OrderId if m.generateOffchainUpdates && (order.IsLiquidation() || makerOrderId != order.MustGetOrder().OrderId) { reason := indexershared.ConvertOrderRemovalReasonToIndexerOrderRemovalReason( makerOrderWithRemovalReason.RemovalReason, ) if message, success := off_chain_updates.CreateOrderRemoveMessageWithReason( branchedContext.Logger(), makerOrderId, reason, off_chain_updates.OrderRemoveV1_ORDER_REMOVAL_STATUS_BEST_EFFORT_CANCELED, ); success { offchainUpdates.AddRemoveMessage(makerOrderId, message) } } m.mustRemoveOrder(branchedContext, makerOrderId) if makerOrderId.IsStatefulOrder() && !m.operationsToPropose.IsOrderRemovalInOperationsQueue(makerOrderId) { m.operationsToPropose.MustAddOrderRemovalToOperationsQueue( makerOrderId, makerOrderWithRemovalReason.RemovalReason, ) } } // 5. **处理匹配错误**: // 根据情况,设置匹配错误。如果订单是“全部成交或取消”(FOK)订单且未完全成交,或者订单是“仅发布”(Post-Only)订单且导致与制造商订单交叉,将匹配错误设置为相应的错误类型。 var matchingErr error if !order.IsLiquidation() && order.MustGetOrder().TimeInForce == types.Order_TIME_IN_FORCE_FILL_OR_KILL && takerOrderStatus.RemainingQuantums > 0 { matchingErr = types.ErrFokOrderCouldNotBeFullyFilled } if len(newMakerFills) > 0 && !order.IsLiquidation() && order.MustGetOrder().TimeInForce == types.Order_TIME_IN_FORCE_POST_ONLY { matchingErr = types.ErrPostOnlyWouldCrossMakerOrder } // 6. **更新memclob状态**: // 如果匹配是有效的,并且放置接收方订单生成了有效的匹配,使用匹配结果来更新memclob状态。 // 生成的离线更新被添加到`offchainUpdates`中,并提交分支状态的写入缓存。 takerGeneratedValidMatches := len(newMakerFills) > 0 && matchingErr == nil if takerGeneratedValidMatches { matchOffchainUpdates := m.mustUpdateMemclobStateWithMatches( branchedContext, order, newMakerFills, matchedOrderHashToOrder, matchedMakerOrderIdToOrder, ) offchainUpdates.Append(matchOffchainUpdates) writeCache() } return takerOrderStatus, offchainUpdates, makerOrdersToRemove, matchingErr } ``` ### [ProcessSingleMatch](https://github.com/dydxprotocol/v4-chain/blob/main/protocol/x/clob/keeper/process_single_match.go#L42) 1. **接受和验证匹配信息** - 这个函数首先接受一个匹配(`MatchWithOrders`)和与之相关的订单。 - 它进行了一系列的验证,包括验证订单是否属于有效的`ClobPair`(交易对),验证订单是否属于有效的永续合约(Perpetual)等。 - 还验证了匹配的`fillAmount`是否可以被`ClobPair`的`StepBaseQuantums`整除。 2. **计算订单的交易费用和资金变化** - 根据订单的性质(是否清算订单等)计算了交易费用。 - 对于清算订单,还进行了额外的验证,并计算了保险金的变化。 3. **更新订单的填充数量和可裁剪块高度** - 对于非清算订单,更新了订单的填充数量和可裁剪块高度,以反映订单的最新状态。 4. **更新子帐户信息** - 如果是清算订单,更新了与清算相关的子帐户信息,包括总的清算报价量和保险基金变化 #### code: ``` // 1. 接受和验证匹配信息 // ProcessSingleMatch函数接受一个匹配(MatchWithOrders)和相关订单。 // 它进行了一系列的验证,包括验证订单是否属于有效的ClobPair(交易对),验证订单是否属于有效的永续合约(Perpetual)等。 // 还验证了匹配的fillAmount是否可以被ClobPair的StepBaseQuantums整除。 func (k Keeper) ProcessSingleMatch( ctx sdk.Context, matchWithOrders *types.MatchWithOrders, ) ( success bool, takerUpdateResult satypes.UpdateResult, makerUpdateResult satypes.UpdateResult, offchainUpdates *types.OffchainUpdates, err error, ) { if matchWithOrders.TakerOrder.IsLiquidation() { defer func() { // 如果Liquidation订单失败了更新子帐户,记录相关日志信息 if errors.Is(err, satypes.ErrFailedToUpdateSubaccounts) && !takerUpdateResult.IsSuccess() { takerSubaccount := k.subaccountsKeeper.GetSubaccount(ctx, matchWithOrders.TakerOrder.GetSubaccountId()) takerTnc, takerIMR, takerMMR, _ := k.subaccountsKeeper.GetNetCollateralAndMarginRequirements( ctx, satypes.Update{SubaccountId: *takerSubaccount.Id}, ) log.ErrorLog(ctx, "collateralization check failed for liquidation", "takerSubaccount", fmt.Sprintf("%+v", takerSubaccount), "takerTNC", takerTnc, "takerIMR", takerIMR, "takerMMR", takerMMR, "liquidationOrder", fmt.Sprintf("%+v", matchWithOrders.TakerOrder), "makerOrder", fmt.Sprintf("%+v", matchWithOrders.MakerOrder), "fillAmount", matchWithOrders.FillAmount, "result", takerUpdateResult, ) } }() } // 2. 计算订单的交易费用和资金变化 // 执行匹配前对交易进行状态验证。 //确保匹配不构成自成交。 //确保匹配的fillAmount大于0。 //确保匹配中的订单属于相同的ClobPairId。 //确保匹配中的订单是对立面的。 //确保订单是交叉的。 //验证makerOrder和takerOrder中的初始量子的最小值不超过fillAmount。 //确保匹配中引用的makerOrder不是清算订单。 //确保匹配中引用的makerOrder不是IOC订单(Immediate or Cancel,即立即执行或取消订单)。 //如果在这些验证中的任何一项失败,函数将返回相应的错误消息。否则,它将返回nil,表示匹配是有效的。 if err := matchWithOrders.Validate(); err != nil { return false, takerUpdateResult, makerUpdateResult, nil, errorsmod.Wrapf( err, "ProcessSingleMatch: Invalid MatchWithOrders: %+v", matchWithOrders, ) } offchainUpdates = types.NewOffchainUpdates() makerMatchableOrder := matchWithOrders.MakerOrder takerMatchableOrder := matchWithOrders.TakerOrder fillAmount := matchWithOrders.FillAmount // 从状态中检索ClobPair(交易对)。 clobPairId := makerMatchableOrder.GetClobPairId() clobPair, found := k.GetClobPair(ctx, clobPairId) if !found { return false, takerUpdateResult, makerUpdateResult, nil, types.ErrInvalidClob } // 验证`fillAmount`是否可以被`clobPair`的`StepBaseQuantums`整除。 if fillAmount.ToUint64()%clobPair.StepBaseQuantums != 0 { return false, takerUpdateResult, makerUpdateResult, nil, types.ErrFillAmountNotDivisibleByStepSize } // 定义用于基于填充数量检索QuoteQuantums的本地变量。 makerSubticks := makerMatchableOrder.GetOrderSubticks() // 基于maker订单的subticks计算匹配的报价量。 bigFillQuoteQuantums, err := getFillQuoteQuantums(clobPair, makerSubticks, fillAmount) if err != nil { return false, takerUpdateResult, makerUpdateResult, nil, err } // 如果bigFillQuoteQuantums为零,记录错误日志。 if bigFillQuoteQuantums.Sign() == 0 { // 注意:如果`subticks`和`baseQuantums`足够小,`quantumConversionExponent`为负数, // 则对于非零的`baseQuantums`可能会得到零的`quoteQuantums`。 // 这可能意味着订单卖方在订单簿上的价格(subticks)非常不利,可能会收到零的`quoteQuantums`。 log.ErrorLog( ctx, "Match resulted in zero quote quantums", "MakerOrder", fmt.Sprintf("%+v", matchWithOrders.MakerOrder), "TakerOrder", fmt.Sprintf("%+v", matchWithOrders.TakerOrder), "FillAmount", matchWithOrders.FillAmount.ToUint64(), ) } // 检索与`ClobPair`相关的永续合约的ID。 perpetualId, err := clobPair.GetPerpetualId() if err != nil { return false, takerUpdateResult, makerUpdateResult, nil, err } // 计算taker和maker的交易费率(fee ppm)。 takerFeePpm := k.feeTiersKeeper.GetPerpetualFeePpm( ctx, matchWithOrders.TakerOrder.GetSubaccountId().Owner, true) makerFeePpm := k.feeTiersKeeper.GetPerpetualFeePpm( ctx, matchWithOrders.MakerOrder.GetSubaccountId().Owner, false) takerInsuranceFundDelta := new(big.Int) if takerMatchableOrder.IsLiquidation() { // 清算订单不支付交易费,因为它们已经支付清算费。 takerFeePpm = 0 // 暂时将maker回报费用上限设为0以防止费用收取者资金不足以支付maker回报费。 // TODO(CLOB-812):找到长期处理清算订单的maker回报费的解决方案。 makerFeePpm = lib.Max(makerFeePpm, 0) takerInsuranceFundDelta, err = k.validateMatchedLiquidation( ctx, takerMatchableOrder, perpetualId, fillAmount, makerMatchableOrder.GetOrderSubticks(), ) if err != nil { return false, takerUpdateResult, makerUpdateResult, nil, err } } // 3. 更新订单的填充数量和可裁剪块高度 // 检查是否有足够的剩余数量,以及计算新的总填充数量。 // 对于非清算订单,还更新了订单的可裁剪块高度。 var curTakerFillAmount satypes.BaseQuantums var curTakerPruneableBlockHeight uint32 var newTakerTotalFillAmount satypes.BaseQuantums var curMakerFillAmount satypes.BaseQuantums var curMakerPruneableBlockHeight uint32 var newMakerTotalFillAmount satypes.BaseQuantums // Liquidation订单不能重复放置,因此无需跟踪其填充数量。 if !takerMatchableOrder.IsLiquidation() { // 检索taker订单的当前填充数量和当前可裁剪块高度。 // 如果订单以前没有被填充过,这两者都将为0。 _, curTakerFillAmount, curTakerPruneableBlockHeight = k.GetOrderFillAmount( ctx, matchWithOrders.TakerOrder.MustGetOrder().OrderId, ) // 验证订单是否有足够的剩余数量,并计算新的总填充数量。 newTakerTotalFillAmount, err = getUpdatedOrderFillAmount( matchWithOrders.TakerOrder.MustGetOrder().OrderId, matchWithOrders.TakerOrder.GetBaseQuantums(), curTakerFillAmount, fillAmount, ) if err != nil { return false, takerUpdateResult, makerUpdateResult, nil, err } } // 检索maker订单的当前填充数量和当前可裁剪块高度。 // 如果订单以前没有被填充过,这两者都将为0。 _, curMakerFillAmount, curMakerPruneableBlockHeight = k.GetOrderFillAmount( ctx, matchWithOrders.MakerOrder.MustGetOrder().OrderId, ) // 验证订单是否有足够的剩余数量,并计算新的总填充数量。 // 确保订单的填充数量不会超过其总基准数量限制。 newMakerTotalFillAmount, err = getUpdatedOrderFillAmount( matchWithOrders.MakerOrder.MustGetOrder().OrderId, matchWithOrders.MakerOrder.GetBaseQuantums(), curMakerFillAmount, fillAmount, ) ❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️ //注意 在之前所有返回makerUpdateResult的判断中,可以理解为只是数据合法性校验,只有persistMatchedOrders中的函数才是真正计算交易后的UpdateResult的Collat是不是ok的, 处理匹配的订单,包括计算费用、更新账户余额、处理保险金支付、触发事件等操作。 takerUpdateResult, makerUpdateResult, err = k.persistMatchedOrders( ctx, matchWithOrders, perpetualId, takerFeePpm, makerFeePpm, bigFillQuoteQuantums, takerInsuranceFundDelta, ) if err != nil { return false, takerUpdateResult, makerUpdateResult, nil, err } // 4. 更新子帐户信息 // 如果是Liquidation订单,更新与清算相关的子帐户信息,包括总的清算报价量和保险基金变化。 if matchWithOrders.TakerOrder.IsLiquidation() { notionalLiquidatedQuoteQuantums, err := k.perpetualsKeeper.GetNetNotional( ctx, perpetualId, fillAmount.ToBigInt(), ) if err != nil { return false, takerUpdateResult, makerUpdateResult, nil, err } k.UpdateSubaccountLiquidationInfo( ctx, matchWithOrders.TakerOrder.GetSubaccountId(), notionalLiquidatedQuoteQuantums, takerInsuranceFundDelta, ) labels := []gometrics.Label{ metrics.GetLabelForIntValue(metrics.PerpetualId, int(perpetualId)), metrics.GetLabelForBoolValue(metrics.CheckTx, ctx.IsCheckTx()), } if matchWithOrders.TakerOrder.IsBuy() { labels = append(labels, metrics.GetLabelForStringValue(metrics.OrderSide, metrics.Buy)) } else { labels = append(labels, metrics.GetLabelForStringValue(metrics.OrderSide, metrics.Sell)) } // 统计清算报价量。 gometrics.AddSampleWithLabels( []string{metrics.Liquidations, metrics.PlacePerpetualLiquidation, metrics.Filled, metrics.QuoteQuantums}, metrics.GetMetricValueFromBigInt(notionalLiquidatedQuoteQuantums), labels, ) // 统计保险基金变化。 gometrics.AddSampleWithLabels( []string{metrics.Liquidations, metrics.InsuranceFundDelta}, metrics.GetMetricValueFromBigInt(new(big.Int).Abs(takerInsuranceFundDelta)), append(labels, metrics.GetLabelForBoolValue(metrics.Positive, takerInsuranceFundDelta.Sign() == 1)), ) } // Liquidation订单不能重复放置,因此无需跟踪其填充数量。 if !matchWithOrders.TakerOrder.IsLiquidation() { takerOffchainUpdates := k.setOrderFillAmountsAndPruning( ctx, matchWithOrders.TakerOrder.MustGetOrder(), newTakerTotalFillAmount, curTakerPruneableBlockHeight, ) offchainUpdates.Append(takerOffchainUpdates) } makerOffchainUpdates := k.setOrderFillAmountsAndPruning( ctx, matchWithOrders.MakerOrder.MustGetOrder(), newMakerTotalFillAmount, curMakerPruneableBlockHeight, ) offchainUpdates.Append(makerOffchainUpdates) return true, takerUpdateResult, makerUpdateResult, offchainUpdates, nil } ``` ### [persistMatchedOrders](https://github.com/dydxprotocol/v4-chain/blob/main/protocol/x/clob/keeper/process_single_match.go#L304) 这部分主要负责成单后的数据和抵押物计算部分 1. **初始化和费用计算模块**: - 计算taker和maker的交易费用(takerFee和makerFee)。 - 处理Liquidation订单的特殊情况,将taker的费用设置为保险金(insurance fund)支付,而不是标准的taker费用。 2. **计算资产和永续合约的变化量模块**: - 计算taker和maker的报价币余额变化(quoteBalanceDelta)。 - 计算taker和maker的永续合约仓位变化(perpetualQuantumsDelta)。 - 处理买单和卖单的情况,根据订单类型对变化量进行相应的调整。 3. **创建子账户更新模块**: - 创建包含taker和maker更新的子账户更新(subaccounts.Update),每个更新包含了资产和永续合约的变化量以及子账户ID。 4. **应用子账户更新模块**: - 调用`subaccountsKeeper.UpdateSubaccounts`来应用子账户的更新,同时捕获更新的结果。 5. **处理保险金支付和费用转移模块**: - 处理保险金支付,将保险金支付从子账户模块转移到保险基金。 - 转移交易费用,将交易费用从子账户模块转移到费用收集模块。 6. **更新永续合约的最新交易价格模块**: - 更新永续合约的最新交易价格。 7. **处理奖励和统计信息模块**: - 处理奖励相关信息,包括奖励份额的计算。 - 记录交易相关的统计信息。 #### code: ``` // 初始化和费用计算模块 func (k Keeper) persistMatchedOrders( ctx sdk.Context, matchWithOrders *types.MatchWithOrders, perpetualId uint32, takerFeePpm int32, makerFeePpm int32, bigFillQuoteQuantums *big.Int, insuranceFundDelta *big.Int, ) ( takerUpdateResult satypes.UpdateResult, makerUpdateResult satypes.UpdateResult, err error, ) { isTakerLiquidation := matchWithOrders.TakerOrder.IsLiquidation() // 计算费用 bigTakerFeeQuoteQuantums := lib.BigIntMulSignedPpm(bigFillQuoteQuantums, takerFeePpm, true) bigMakerFeeQuoteQuantums := lib.BigIntMulSignedPpm(bigFillQuoteQuantums, makerFeePpm, true) matchWithOrders.MakerFee = bigMakerFeeQuoteQuantums.Int64() // 处理Liquidation订单的特殊情况 if matchWithOrders.TakerOrder.IsLiquidation() { matchWithOrders.TakerFee = insuranceFundDelta.Int64() } else { matchWithOrders.TakerFee = bigTakerFeeQuoteQuantums.Int64() } // 处理taker是Liquidation订单的情况 if isTakerLiquidation && bigTakerFeeQuoteQuantums.Sign() != 0 { panic(fmt.Sprintf( `Taker order is liquidation and should never pay taker fees. TakerOrder: %v bigTakerFeeQuoteQuantums: %v`, matchWithOrders.TakerOrder, bigTakerFeeQuoteQuantums, )) } // 计算资产和永续合约的变化量 bigTakerQuoteBalanceDelta := new(big.Int).Set(bigFillQuoteQuantums) bigMakerQuoteBalanceDelta := new(big.Int).Set(bigFillQuoteQuantums) bigTakerPerpetualQuantumsDelta := matchWithOrders.FillAmount.ToBigInt() bigMakerPerpetualQuantumsDelta := matchWithOrders.FillAmount.ToBigInt() if matchWithOrders.TakerOrder.IsBuy() { bigTakerQuoteBalanceDelta.Neg(bigTakerQuoteBalanceDelta) bigMakerPerpetualQuantumsDelta.Neg(bigMakerPerpetualQuantumsDelta) } else { bigMakerQuoteBalanceDelta.Neg(bigMakerQuoteBalanceDelta) bigTakerPerpetualQuantumsDelta.Neg(bigTakerPerpetualQuantumsDelta) } // 创建子账户更新 updates := []satypes.Update{ // Taker update { AssetUpdates: []satypes.AssetUpdate{ { AssetId: assettypes.AssetUsdc.Id, BigQuantumsDelta: bigTakerQuoteBalanceDelta, }, }, PerpetualUpdates: []satypes.PerpetualUpdate{ { PerpetualId: perpetualId, BigQuantumsDelta: bigTakerPerpetualQuantumsDelta, }, }, SubaccountId: matchWithOrders.TakerOrder.GetSubaccountId(), }, // Maker update { AssetUpdates: []satypes.AssetUpdate{ { AssetId: assettypes.AssetUsdc.Id, BigQuantumsDelta: bigMakerQuoteBalanceDelta, }, }, PerpetualUpdates: []satypes.PerpetualUpdate{ { PerpetualId: perpetualId, BigQuantumsDelta: bigMakerPerpetualQuantumsDelta, }, }, SubaccountId: matchWithOrders.MakerOrder.GetSubaccountId(), }, } // 应用子账户更新 success, successPerUpdate, err := k.subaccountsKeeper.UpdateSubaccounts( ctx, updates, satypes.Match, ) if err != nil { return satypes.UpdateCausedError, satypes.UpdateCausedError, err } takerUpdateResult = successPerUpdate[0] makerUpdateResult = successPerUpdate[1] // 处理更新失败的情况 if updateResultErr := satypes.GetErrorFromUpdateResults( success, successPerUpdate, updates, ); updateResultErr != nil { return takerUpdateResult, makerUpdateResult, updateResultErr } if !success { panic( fmt.Sprintf( "persistMatchedOrders: UpdateSubaccounts failed but err == nil and no error returned"+ "from successPerUpdate but success was false. Error: %v, Updates: %+v, SuccessPerUpdate: %+v", err, updates, successPerUpdate, ), ) } // 处理保险金支付和费用转移 if err := k.subaccountsKeeper.TransferInsuranceFundPayments(ctx, insuranceFundDelta); err != nil { return takerUpdateResult, makerUpdateResult, err } bigTotalFeeQuoteQuantums := new(big.Int).Add(bigTakerFeeQuoteQuantums, bigMakerFeeQuoteQuantums) if err := k.subaccountsKeeper.TransferFeesToFeeCollectorModule( ctx, assettypes.AssetUsdc.Id, bigTotalFeeQuoteQuantums, ); err != nil { return takerUpdateResult, makerUpdateResult, errorsmod.Wrapf( types.ErrSubaccountFeeTransferFailed, "persistMatchedOrders: subaccounts (%v, %v) updated, but fee transfer (bigFeeQuoteQuantums: %v)"+ " to fee-collector failed. Err: %v", matchWithOrders.MakerOrder.GetSubaccountId(), matchWithOrders.TakerOrder.GetSubaccountId(), bigTotalFeeQuoteQuantums, err, ) } // 更新永续合约的最新交易价格 k.SetTradePricesForPerpetual(ctx, perpetualId, matchWithOrders.MakerOrder.GetOrderSubticks()) // 处理奖励和统计信息 k.rewardsKeeper.AddRewardSharesForFill( ctx, matchWithOrders.TakerOrder.GetSubaccountId().Owner, matchWithOrders.MakerOrder.GetSubaccountId().Owner, bigFillQuoteQuantums, bigTakerFeeQuoteQuantums, bigMakerFeeQuoteQuantums, ) k.statsKeeper.RecordFill( ctx, matchWithOrders.TakerOrder.GetSubaccountId().Owner, matchWithOrders.MakerOrder.GetSubaccountId().Owner, bigFillQuoteQuantums, ) // 生成匹配事件 ctx.EventManager().EmitEvent( types.NewCreateMatchEvent( matchWithOrders.TakerOrder.GetSubaccountId(), matchWithOrders.MakerOrder.GetSubaccountId(), bigTakerFeeQuoteQuantums, bigMakerFeeQuoteQuantums, bigTakerQuoteBalanceDelta, bigMakerQuoteBalanceDelta, bigTakerPerpetualQuantumsDelta, bigMakerPerpetualQuantumsDelta, insuranceFundDelta, isTakerLiquidation, false, perpetualId, ), ) return takerUpdateResult, makerUpdateResult, nil } ``` ### [UpdateSubaccounts](https://github.com/dydxprotocol/v4-chain/blob/main/protocol/x/subaccounts/keeper/subaccount.go#L213) 1. 获取已结算的更新和资金支付信息: - 函数首先获取已结算的更新信息和子账户的资金支付信息。 2. 验证更新操作: - 调用 `internalCanUpdateSubaccounts` 函数验证是否可以更新子账户状态,包括检查更新的合法性和唯一性。 3. 更新永续合约仓位: - 函数会获取永续合约的当前资金索引,并将更新应用到永续合约仓位中。 4. 更新资产仓位: - 函数将更新应用到资产仓位中。 5. 应用所有更新和生成事件: - 针对每个已结算的更新,函数会将更新应用到子账户状态中,并通过事件管理器添加子账户更新事件。 - 同时,函数会为每个已结算的资金支付产生一个事件,指示该子账户支付或接收了资金。 #### code: ``` // UpdateSubaccounts 验证并应用所有 `updates` 到相关子账户,只要对所有涉及的子账户都是有效的状态转换。 // 所有更新都会以原子方式执行,这意味着所有状态更改要么都成功,要么都失败。 // // 返回一个布尔值,指示更新是否成功应用。如果为 `false`,则没有对任何子账户进行更新。 // 第二个返回值返回一个 `UpdateResult` 数组,与 `updates` 相对应,以指示哪些更新导致了失败,如果有的话。 // // `updates` 中的每个 `SubaccountId` 必须是唯一的,否则将返回错误。 func (k Keeper) UpdateSubaccounts( ctx sdk.Context, updates []types.Update, updateType types.UpdateType, ) ( success bool, successPerUpdate []types.UpdateResult, err error, ) { defer metrics.ModuleMeasureSinceWithLabels( types.ModuleName, []string{metrics.UpdateSubaccounts, metrics.Latency}, time.Now(), []gometrics.Label{ metrics.GetLabelForStringValue(metrics.UpdateType, updateType.String()), }, ) // 1. 获取已结算的更新和资金支付信息: settledUpdates, subaccountIdToFundingPayments, err := k.getSettledUpdates(ctx, updates, true) if err != nil { return false, nil, err } // 2. 验证更新操作: success, successPerUpdate, err = k.internalCanUpdateSubaccounts(ctx, settledUpdates, updateType) if !success || err != nil { return success, successPerUpdate, err } // 获取永续合约资金索引 allPerps := k.perpetualsKeeper.GetAllPerpetuals(ctx) perpIdToFundingIndex := make(map[uint32]dtypes.SerializableInt) for _, perp := range allPerps { perpIdToFundingIndex[perp.Params.Id] = perp.FundingIndex } // 3. 更新永续合约仓位: UpdatePerpetualPositions( settledUpdates, perpIdToFundingIndex, ) // 4. 更新资产仓位: UpdateAssetPositions(settledUpdates) // 5. 应用所有更新和生成事件: for _, u := range settledUpdates { k.SetSubaccount(ctx, u.SettledSubaccount) fundingPayments := subaccountIdToFundingPayments[*u.SettledSubaccount.Id] // 添加子账户更新事件到事件管理器 k.GetIndexerEventManager().AddTxnEvent( ctx, indexerevents.SubtypeSubaccountUpdate, indexerevents.SubaccountUpdateEventVersion, indexer_manager.GetBytes( indexerevents.NewSubaccountUpdateEvent( u.SettledSubaccount.Id, getUpdatedPerpetualPositions(u, fundingPayments), getUpdatedAssetPositions(u), fundingPayments, ), ), ) // 为每个已结算的资金支付生成事件 sortedPerpIds := lib.GetSortedKeys[lib.Sortable[uint32]](fundingPayments) for _, perpetualId := range sortedPerpIds { fundingPaid := fundingPayments[perpetualId] ctx.EventManager().EmitEvent( types.NewCreateSettledFundingEvent( *u.SettledSubaccount.Id, perpetualId, fundingPaid.BigInt(), ), ) } } return success, successPerUpdate, err } ``` ### [mustUpdateMemclobStateWithMatches](https://github.com/dydxprotocol/v4-chain/blob/main/protocol/x/clob/memclob/memclob.go#L238) `mustUpdateMemclobStateWithMatches` 函数负责处理匹配订单后的状态更新。它包括以下模块: 1. **更新匹配的订单信息**:遍历匹配的订单并将它们添加到 `orderIdToOrder` 中,以便后续使用。 2. **处理 Maker 订单的匹配和更新**:针对每个匹配的 Maker 订单,检查是否需要将其订单信息添加到操作队列,并更新订单簿状态以反映订单的匹配。同时,计算总匹配量。 3. **处理 Taker 订单的匹配和更新**:处理匹配的 Taker 订单,将其订单信息添加到操作队列中。 4. **添加新匹配到操作队列**:将新匹配的订单和相关信息添加到操作队列中。 5. **取消开仓只减仓订单**:检查是否需要取消开仓只减仓订单,并将取消的订单添加到操作队列。 #### code ``` // mustUpdateMemclobStateWithMatches 更新 memclob 状态,通过应用匹配来更新所有簿记数据结构。 // 具体来说,它将执行以下操作: // - 将所有新匹配的订单以及所有新匹配添加到操作队列。 // - 通过更新所有匹配的 Maker 订单的已成交量,并在完全成交时将其移除,来更新订单簿状态。 func (m *MemClobPriceTimePriority) mustUpdateMemclobStateWithMatches( ctx sdk.Context, takerOrder types.MatchableOrder, newMakerFills []types.MakerFill, matchedOrderHashToOrder map[types.OrderHash]types.MatchableOrder, matchedMakerOrderIdToOrder map[types.OrderId]types.Order, ) (offchainUpdates *types.OffchainUpdates) { offchainUpdates = types.NewOffchainUpdates() // 1. 更新匹配的订单信息 // 遍历匹配的订单并将它们添加到 `orderIdToOrder` 中,以便后续使用。 for _, matchedOrder := range matchedOrderHashToOrder { // 如果这不是清算订单,更新 `orderIdToOrder`。 if !matchedOrder.IsLiquidation() { order := matchedOrder.MustGetOrder() orderId := order.OrderId other, exists := m.operationsToPropose.MatchedOrderIdToOrder[orderId] if exists && order.MustCmpReplacementOrder(&other) < 0 { // 不应该发生,因为订单应该已经被替换或拒绝。 panic(" "mustUpdateMemclobStateWithMatches: newly matched order is lesser than existing order " + "Newly matched order %v, existing order %v",") } m.operationsToPropose.MatchedOrderIdToOrder[orderId] = order } } // 2. 处理 Maker 订单的匹配和更新 // 针对每个匹配的 Maker 订单,检查是否需要将其订单信息添加到操作队列,并更新订单簿状态以反映订单的匹配。同时,计算总匹配量。 makerFillWithOrders := make([]types.MakerFillWithOrder, 0, len(newMakerFills)) for _, newFill := range newMakerFills { matchedMakerOrder, exists := matchedMakerOrderIdToOrder[newFill.MakerOrderId] if !exists { panic("mustUpdateMemclobStateWithMatches: matched maker order %s does not exist in `matchedMakerOrderIdToOrder`") } makerFillWithOrders = append( makerFillWithOrders, types.MakerFillWithOrder{ Order: matchedMakerOrder, MakerFill: newFill, }, ) // 跳过如果订单已存在,则不添加订单放置到操作队列。 if !m.operationsToPropose.IsOrderPlacementInOperationsQueue( matchedMakerOrder, ) { // 添加 Maker 订单放置到操作队列。 if matchedMakerOrder.IsStatefulOrder() { m.operationsToPropose.MustAddStatefulOrderPlacementToOperationsQueue( matchedMakerOrder, ) } else { m.operationsToPropose.MustAddShortTermOrderPlacementToOperationsQueue( matchedMakerOrder, ) } } // 更新订单簿状态以反映 Maker 订单被匹配。 matchOffchainUpdates := m.mustUpdateOrderbookStateWithMatchedMakerOrder( ctx, matchedMakerOrder, ) offchainUpdates.Append(matchOffchainUpdates) // 更新总匹配量。 matchedQuantums := satypes.BaseQuantums(newFill.GetFillAmount()) // 安全性检查。 if matchedQuantums == 0 { panic("mustUpdateMemclobStateWithMatches: Fill has 0 quantums. Fill %v and maker order %v") } // 更新 subaccountTotalMatchedQuantums 以记录匹配的总量。 // 这将用于开仓只减仓订单逻辑。 for _, order := range []types.MatchableOrder{ &matchedMakerOrder, takerOrder, } { bigTotalMatchedQuantums, exists := subaccountTotalMatchedQuantums[order.GetSubaccountId()] if !exists { bigTotalMatchedQuantums = big.NewInt(0) } bigMatchedQuantums := matchedQuantums.ToBigInt() if order.IsBuy() { bigTotalMatchedQuantums = bigTotalMatchedQuantums.Add(bigTotalMatchedQuantums, bigMatchedQuantums) } else { bigTotalMatchedQuantums = bigTotalMatchedQuantums.Sub(bigTotalMatchedQuantums, bigMatchedQuantums) } subaccountTotalMatchedQuantums[order.GetSubaccountId()] = bigTotalMatchedQuantums } } // 3. 处理 Taker 订单的匹配和更新 // 如果 Taker 订单不是清算订单,将其订单信息添加到操作队列。 if !takerOrder.IsLiquidation() { taker := takerOrder.MustGetOrder() // 添加 Taker 订单放置到操作队列。 if taker.IsStatefulOrder() { m.operationsToPropose.MustAddStatefulOrderPlacementToOperationsQueue( taker, ) } else { m.operationsToPropose.MustAddShortTermOrderTxBytes( taker, ctx.TxBytes(), ) m.operationsToPropose.MustAddShortTermOrderPlacementToOperationsQueue( taker, ) } } // 4. 添加新匹配到操作队列 // 将新匹配添加到操作队列。 m.operationsToPropose.MustAddMatchToOperationsQueue(takerOrder, makerFillWithOrders) // 5. 处理开仓只减仓订单逻辑 // 针对每个有匹配的 subaccount,确定是否应取消其开仓只减仓订单。这发生在匹配前后的持仓大小符号不同的情况下。 for _, subaccountId := range allSubaccounts { cancelledOffchainUpdates := m.maybeCancelReduceOnlyOrders( ctx, subaccountId, takerOrder.GetClobPairId(), subaccountTotalMatchedQuantums[subaccountId], ) offchainUpdates.Append(cancelledOffchainUpdates) } return offchainUpdates } ``` ### [PlaceOrder](https://github.com/dydxprotocol/v4-chain/blob/main/protocol/x/clob/memclob/memclob.go#L416) **1. 验证订单** - 首先,函数会验证新下达的订单是否合法,包括检查抵押物、数量、价格等方面的条件。 - 如果验证失败,函数将返回错误,不对 memclob 内存状态进行任何修改。 **2. 尝试匹配订单** - 如果订单验证通过,函数将尝试匹配订单。 - 如果新下达的订单与已有订单有重叠,它将与订单簿中的订单进行匹配。 - 如果任何 Maker 订单未通过抵押物检查,它们将被移除,如果 Taker 订单未通过抵押物检查,则匹配将停止。 - 如果匹配结果导致了匹配,memclob 状态将相应地进行更新。 - 如果订单在匹配后仍然有剩余数量且通过抵押物检查,订单将被添加到订单簿和其他簿记数据结构中。 **3. 处理异常情况** - 如果在匹配订单时发生错误,函数将处理异常情况,例如: - 对于 Stateful Order,如果订单类型不支持 IOC(Immediate or Cancel)或者 Post-Only 订单无法匹配 Maker 订单,会将订单状态标记为相应的异常情况,并添加到操作队列中以删除订单。 - 如果生成 Offchain 更新,函数将发送相应的 Offchain 更新消息。 **4. 处理未完全匹配的订单** - 如果订单匹配成功但有未完全匹配的剩余数量,函数将继续处理这部分未匹配的数量。 - 对于 IOC 订单,未匹配的数量会被取消。 - 对于 Maker 订单,将进行抵押物检查以确定是否可以添加到订单簿。 - 如果未完全匹配的数量可以添加到订单簿,函数将相应地处理。 **5. 最终处理和更新** - 函数将最终处理订单,包括将订单添加到订单簿和其他簿记数据结构中。 - 如果生成 Offchain 更新,函数将发送相应的 Offchain 更新消息,包括订单的更新或删除消息。 - 最后,函数返回订单的优化填充数量、订单状态、Offchain 更新和可能的错误。 #### code: ``` // 1. 验证订单 // - 首先,函数会验证新下达的订单是否合法,包括检查抵押物、数量、价格等方面的条件。 // - 如果验证失败,函数将返回错误,不对 memclob 内存状态进行任何修改。 func (m *MemClobPriceTimePriority) PlaceOrder( ctx sdk.Context, order types.Order, ) ( orderSizeOptimisticallyFilledFromMatchingQuantums satypes.BaseQuantums, orderStatus types.OrderStatus, offchainUpdates *types.OffchainUpdates, err error, ) { // ... // 2. 尝试匹配订单 // - 如果订单验证通过,函数将尝试匹配订单。 // - 如果新下达的订单与已有订单有重叠,它将与订单簿中的订单进行匹配。 // - 如果任何 Maker 订单未通过抵押物检查,它们将被移除,如果 Taker 订单未通过抵押物检查,则匹配将停止。 // - 如果匹配结果导致了匹配,memclob 状态将相应地进行更新。 // - 如果订单在匹配后仍然有剩余数量且通过抵押物检查,订单将被添加到订单簿和其他簿记数据结构中。 takerOrderStatus, takerOffchainUpdates, _, err := m.matchOrder(ctx, &order) offchainUpdates.Append(takerOffchainUpdates) // ... // 3. 处理异常情况 // - 如果在匹配订单时发生错误,函数将处理异常情况,例如: // - 对于 Stateful Order,如果订单类型不支持 IOC(Immediate or Cancel)或者 Post-Only 订单无法匹配 Maker 订单, // 会将订单状态标记为相应的异常情况,并添加到操作队列中以删除订单。 // - 如果生成 Offchain 更新,函数将发送相应的 Offchain 更新消息。 if err != nil { // ... // 如果订单状态为异常状态,添加订单移除消息到 Offchain 更新 // 标记状态并生成 Offchain 更新消息 // (此处省略了部分代码) if m.generateOffchainUpdates { // ... } return 0, 0, offchainUpdates, err } // 4. 处理未完全匹配的订单 // - 如果订单匹配成功但有未完全匹配的剩余数量,函数将继续处理这部分未匹配的数量。 // - 对于 IOC 订单,未匹配的数量会被取消。 // - 对于 Maker 订单,将进行抵押物检查以确定是否可以添加到订单簿。 // - 如果未完全匹配的数量可以添加到订单簿,函数将相应地处理。 remainingSize := takerOrderStatus.RemainingQuantums orderSizeOptimisticallyFilledFromMatchingQuantums = takerOrderStatus.OrderOptimisticallyFilledQuantums if remainingSize == 0 { // ... return orderSizeOptimisticallyFilledFromMatchingQuantums, takerOrderStatus.OrderStatus, offchainUpdates, nil } // ... // 5. 最终处理和更新 // - 函数将最终处理订单,包括将订单添加到订单簿和其他簿记数据结构中。 // - 如果生成 Offchain 更新,函数将发送相应的 Offchain 更新消息,包括订单的更新或删除消息。 // - 最后,函数返回订单的优化填充数量、订单状态、Offchain 更新和可能的错误。 if m.generateOffchainUpdates { // ... if message, success := off_chain_updates.CreateOrderUpdateMessage( m.clobKeeper.Logger(ctx), order.OrderId, order.GetBaseQuantums()-remainingSize, ); success { offchainUpdates.AddUpdateMessage(order.OrderId, message) } } // ... return orderSizeOptimisticallyFilledFromMatchingQuantums, types.Success, offchainUpdates, nil } ```