-
Notifications
You must be signed in to change notification settings - Fork 0
/
clob.go
226 lines (200 loc) · 6.08 KB
/
clob.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
package ante
import (
errorsmod "cosmossdk.io/errors"
"github.com/cometbft/cometbft/crypto/tmhash"
"github.com/cometbft/cometbft/libs/log"
"github.com/jinxprotocol/v4-chain/protocol/lib"
"github.com/jinxprotocol/v4-chain/protocol/x/clob/types"
satypes "github.com/jinxprotocol/v4-chain/protocol/x/subaccounts/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
// ClobDecorator is an AnteDecorator which is responsible for:
// - adding short term order placements and cancelations to the in-memory orderbook (`CheckTx` only).
// - adding stateful order placements and cancelations to state (`CheckTx` and `RecheckTx` only).
//
// This AnteDecorator also enforces that any Transaction which contains a `MsgPlaceOrder`,
// or a `MsgCancelOrder`, must consist only of a single message.
//
// This AnteDecorator is a no-op if:
// - No messages in the transaction are `MsgPlaceOrder` or `MsgCancelOrder`.
// - This AnteDecorator is called during `DeliverTx`.
//
// This AnteDecorator returns an error if:
// - The transaction contains multiple messages, and one of them is a `MsgPlaceOrder`
// or `MsgCancelOrder` message.
// - The underlying `PlaceStatefulOrder`, `PlaceShortTermOrder`, `CancelStatefulOrder`, or `CancelShortTermOrder`
// methods on the keeper return errors.
type ClobDecorator struct {
clobKeeper types.ClobKeeper
}
func NewClobDecorator(clobKeeper types.ClobKeeper) ClobDecorator {
return ClobDecorator{
clobKeeper,
}
}
func (cd ClobDecorator) AnteHandle(
ctx sdk.Context,
tx sdk.Tx,
simulate bool,
next sdk.AnteHandler,
) (sdk.Context, error) {
// No need to process during `DeliverTx` or simulation, call next `AnteHandler`.
if lib.IsDeliverTxMode(ctx) || simulate {
return next(ctx, tx, simulate)
}
// Ensure that if this is a clob message then that there is only one.
// If it isn't a clob message then pass to the next AnteHandler.
isSingleClobMsgTx, err := IsSingleClobMsgTx(ctx, tx)
if err != nil {
return ctx, err
}
if !isSingleClobMsgTx {
return next(ctx, tx, simulate)
}
msgs := tx.GetMsgs()
var msg = msgs[0]
switch msg := msg.(type) {
case *types.MsgCancelOrder:
if msg.OrderId.IsStatefulOrder() {
err = cd.clobKeeper.CancelStatefulOrder(ctx, msg)
} else {
// No need to process short term order cancelations on `ReCheckTx`.
if ctx.IsReCheckTx() {
return next(ctx, tx, simulate)
}
// Note that `msg.ValidateBasic` is called before the AnteHandlers.
// This guarantees that `MsgCancelOrder` has undergone stateless validation.
err = cd.clobKeeper.CancelShortTermOrder(ctx, msg)
}
cd.clobKeeper.Logger(ctx).Debug("Received new order cancelation",
"tx",
log.NewLazySprintf("%X", tmhash.Sum(ctx.TxBytes())),
"msg",
msg,
"err",
err,
"block",
ctx.BlockHeight(),
"txMode",
lib.TxMode(ctx),
)
case *types.MsgPlaceOrder:
if msg.Order.OrderId.IsStatefulOrder() {
err = cd.clobKeeper.PlaceStatefulOrder(ctx, msg)
cd.clobKeeper.Logger(ctx).Debug("Received new stateful order",
"tx",
log.NewLazySprintf("%X", tmhash.Sum(ctx.TxBytes())),
"orderHash",
log.NewLazySprintf("%X", msg.Order.GetOrderHash()),
"msg",
msg,
"err",
err,
"block",
ctx.BlockHeight(),
"txMode",
lib.TxMode(ctx),
)
} else {
// No need to process short term orders on `ReCheckTx`.
if ctx.IsReCheckTx() {
return next(ctx, tx, simulate)
}
var orderSizeOptimisticallyFilledFromMatchingQuantums satypes.BaseQuantums
var status types.OrderStatus
// Note that `msg.ValidateBasic` is called before all AnteHandlers.
// This guarantees that `MsgPlaceOrder` has undergone stateless validation.
orderSizeOptimisticallyFilledFromMatchingQuantums, status, err = cd.clobKeeper.PlaceShortTermOrder(
ctx,
msg,
)
cd.clobKeeper.Logger(ctx).Debug("Received new short term order",
"tx",
log.NewLazySprintf("%X", tmhash.Sum(ctx.TxBytes())),
"orderHash",
log.NewLazySprintf("%X", msg.Order.GetOrderHash()),
"msg",
msg,
"status",
status,
"orderSizeOptimisticallyFilledFromMatchingQuantums",
orderSizeOptimisticallyFilledFromMatchingQuantums,
"err",
err,
"block",
ctx.BlockHeight(),
"txMode",
lib.TxMode(ctx),
)
}
}
if err != nil {
return ctx, err
}
return next(ctx, tx, simulate)
}
// IsSingleClobMsgTx returns `true` if the supplied `tx` consist of a single clob message
// (`MsgPlaceOrder` or `MsgCancelOrder`). If `msgs` consist of multiple clob messages,
// or a mix of on-chain and clob messages, an error is returned.
func IsSingleClobMsgTx(ctx sdk.Context, tx sdk.Tx) (bool, error) {
msgs := tx.GetMsgs()
var hasMessage = false
for _, msg := range msgs {
switch msg.(type) {
case *types.MsgCancelOrder, *types.MsgPlaceOrder:
hasMessage = true
}
if hasMessage {
break
}
}
if !hasMessage {
return false, nil
}
numMsgs := len(msgs)
if numMsgs > 1 {
return false, errorsmod.Wrap(
sdkerrors.ErrInvalidRequest,
"a transaction containing MsgCancelOrder or MsgPlaceOrder may not contain more than one message",
)
}
return true, nil
}
// IsShortTermClobMsgTx returns `true` if the supplied `tx` consist of a single clob message
// (`MsgPlaceOrder` or `MsgCancelOrder`) which references a Short-Term Order. If `msgs` consist of multiple
// clob messages, or a mix of on-chain and clob messages, an error is returned.
func IsShortTermClobMsgTx(ctx sdk.Context, tx sdk.Tx) (bool, error) {
msgs := tx.GetMsgs()
var isShortTermOrder = false
for _, msg := range msgs {
switch msg := msg.(type) {
case *types.MsgCancelOrder:
{
if msg.OrderId.IsShortTermOrder() {
isShortTermOrder = true
}
}
case *types.MsgPlaceOrder:
{
if msg.Order.OrderId.IsShortTermOrder() {
isShortTermOrder = true
}
}
}
if isShortTermOrder {
break
}
}
if !isShortTermOrder {
return false, nil
}
numMsgs := len(msgs)
if numMsgs > 1 {
return false, errorsmod.Wrap(
sdkerrors.ErrInvalidRequest,
"a transaction containing MsgCancelOrder or MsgPlaceOrder may not contain more than one message",
)
}
return true, nil
}