# PlaceOrder 函数解析
## 功能概述
`PlaceOrder` 函数是处理新订单放置的关键函数,涉及订单验证、匹配和状态更新。
## Question

## 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
}
```