-
Notifications
You must be signed in to change notification settings - Fork 91
/
match.go
298 lines (264 loc) · 8.15 KB
/
match.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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
// This code is available on the terms of the project LICENSE.md file,
// also available online at https://blueoakcouncil.org/license/1.0.0.
package order
import (
"database/sql/driver"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"time"
"decred.org/dcrdex/dex/calc"
"github.com/decred/dcrd/crypto/blake256"
)
// MatchIDSize defines the length in bytes of an MatchID.
const MatchIDSize = blake256.Size
// MatchID is the unique identifier for each match.
type MatchID [MatchIDSize]byte
// MatchID implements fmt.Stringer.
func (id MatchID) String() string {
return hex.EncodeToString(id[:])
}
// MarshalJSON satisfies the json.Marshaller interface, and will marshal the
// id to a hex string.
func (id MatchID) MarshalJSON() ([]byte, error) {
return json.Marshal(id.String())
}
// Bytes returns the match ID as a []byte.
func (id MatchID) Bytes() []byte {
return id[:]
}
// Value implements the sql/driver.Valuer interface.
func (id MatchID) Value() (driver.Value, error) {
return id[:], nil // []byte
}
// Scan implements the sql.Scanner interface.
func (id *MatchID) Scan(src any) error {
idB, ok := src.([]byte)
if !ok {
return fmt.Errorf("cannot convert %T to OrderID", src)
}
copy(id[:], idB)
return nil
}
var zeroMatchID MatchID
// DecodeMatchID checks a string as being both hex and the right length and
// returns its bytes encoded as an order.MatchID.
func DecodeMatchID(matchIDStr string) (MatchID, error) {
var matchID MatchID
if len(matchIDStr) != MatchIDSize*2 {
return matchID, errors.New("match id has incorrect length")
}
if _, err := hex.Decode(matchID[:], []byte(matchIDStr)); err != nil {
return matchID, fmt.Errorf("could not decode match id: %w", err)
}
return matchID, nil
}
// MatchStatus represents the current negotiation step for a match.
type MatchStatus uint8
// The different states of order execution.
const (
// NewlyMatched: DEX has sent match notifications, but the maker has not yet
// acted.
NewlyMatched MatchStatus = iota // 0
// MakerSwapCast: Maker has acknowledged their match notification and
// broadcast their swap notification. The DEX has validated the swap
// notification and sent the details to the taker.
MakerSwapCast // 1
// TakerSwapCast: Taker has acknowledged their match notification and
// broadcast their swap notification. The DEX has validated the swap
// notification and sent the details to the maker.
TakerSwapCast // 2
// MakerRedeemed: Maker has acknowledged their audit request and broadcast
// their redemption transaction. The DEX has validated the redemption and
// sent the details to the taker.
MakerRedeemed // 3
// MatchComplete: Taker has acknowledged their audit request and broadcast
// their redemption transaction. The DEX has validated the redemption and
// sent the details to the maker.
MatchComplete // 4
// MatchConfirmed is a status used only by the client that represents
// that the user's redemption transaction has been confirmed.
MatchConfirmed // 5
)
// String satisfies fmt.Stringer.
func (status MatchStatus) String() string {
switch status {
case NewlyMatched:
return "NewlyMatched"
case MakerSwapCast:
return "MakerSwapCast"
case TakerSwapCast:
return "TakerSwapCast"
case MakerRedeemed:
return "MakerRedeemed"
case MatchComplete:
return "MatchComplete"
case MatchConfirmed:
return "MatchConfirmed"
}
return "MatchStatusUnknown"
}
// MatchSide is the client's side in a match. It will be one of Maker or Taker.
type MatchSide uint8
const (
// Maker is the order that matches out of the epoch queue.
Maker MatchSide = iota
// Taker is the order from the order book.
Taker
)
func (side MatchSide) String() string {
switch side {
case Maker:
return "Maker"
case Taker:
return "Taker"
}
return "UnknownMatchSide"
}
// Signatures holds the acknowledgement signatures required for swap
// negotiation.
type Signatures struct {
TakerMatch []byte
MakerMatch []byte
TakerAudit []byte
MakerAudit []byte
TakerRedeem []byte
MakerRedeem []byte
}
// EpochID contains the uniquely-identifying information for an epoch: index and
// duration.
type EpochID struct {
Idx uint64
Dur uint64
}
// End is the end time of the epoch.
func (e *EpochID) End() time.Time {
return time.UnixMilli(int64((e.Idx + 1) * e.Dur))
}
// Match represents a match between two orders.
type Match struct {
Taker Order
Maker *LimitOrder
Quantity uint64
Rate uint64
// The following fields are not part of the serialization of Match.
FeeRateBase uint64
FeeRateQuote uint64
Epoch EpochID
Status MatchStatus
Sigs Signatures
cachedHash MatchID
}
// A UserMatch is similar to a Match, but contains less information about the
// counter-party, and it clarifies which side the user is on. This is the
// information that might be provided to the client when they are resyncing
// their matches after a reconnect.
type UserMatch struct {
OrderID OrderID
MatchID MatchID
Quantity uint64
Rate uint64
Address string
Status MatchStatus
Side MatchSide
FeeRateSwap uint64
// TODO: include Sell bool?
}
// String is the match ID string, implements fmt.Stringer.
func (m *UserMatch) String() string {
return m.MatchID.String()
}
// A constructor for a Match with Status = NewlyMatched. This is the preferred
// method of making a Match, since it pre-calculates and caches the match ID.
func newMatch(taker Order, maker *LimitOrder, qty, rate, feeRateBase, feeRateQuote uint64, epochID EpochID) *Match {
m := &Match{
Taker: taker,
Maker: maker,
Quantity: qty,
Rate: rate,
Epoch: epochID,
FeeRateBase: feeRateBase,
FeeRateQuote: feeRateQuote,
}
// Pre-cache the ID.
m.ID()
return m
}
// ID computes the match ID and stores it for future calls.
// BLAKE256([maker order id] + [taker order id] + [match qty] + [match rate])
func (match *Match) ID() MatchID {
if match.cachedHash != zeroMatchID {
return match.cachedHash
}
b := make([]byte, 0, 2*OrderIDSize+8+8)
b = appendOrderID(b, match.Taker)
b = appendOrderID(b, match.Maker) // this maker and taker may only be matched once
b = appendUint64Bytes(b, match.Quantity)
b = appendUint64Bytes(b, match.Rate)
match.cachedHash = blake256.Sum256(b)
return match.cachedHash
}
// MatchSet represents the result of matching a single Taker order from the
// epoch queue with one or more standing limit orders from the book, the Makers.
// The Amounts and Rates of each standing order paired are stored. The Rates
// slice is for convenience since each rate must be the same as the Maker's
// rate. However, a amount in Amounts may be less than the full quantity of the
// corresponding Maker order, indicating a partial fill of the Maker. The sum
// of the amounts, Total, is provided for convenience.
type MatchSet struct {
Epoch EpochID
Taker Order
Makers []*LimitOrder
Amounts []uint64
Rates []uint64
Total uint64
FeeRateBase uint64
FeeRateQuote uint64
}
// Matches converts the MatchSet to a []*Match.
func (set *MatchSet) Matches() []*Match {
matches := make([]*Match, 0, len(set.Makers))
for i, maker := range set.Makers {
match := newMatch(set.Taker, maker, set.Amounts[i], set.Rates[i], set.FeeRateBase, set.FeeRateQuote, set.Epoch)
matches = append(matches, match)
}
return matches
}
// HighLowRates gets the highest and lowest rate from all matches.
func (set *MatchSet) HighLowRates() (high uint64, low uint64) {
for _, rate := range set.Rates {
if rate > high {
high = rate
}
if rate < low || low == 0 {
low = rate
}
}
return
}
// QuoteVolume is the matched quantity in terms of the quote asset.
func (set *MatchSet) QuoteVolume() (v uint64) {
for i := range set.Rates {
v += calc.BaseToQuote(set.Rates[i], set.Amounts[i])
}
return
}
func appendUint64Bytes(b []byte, i uint64) []byte {
iBytes := make([]byte, 8)
binary.BigEndian.PutUint64(iBytes, i)
return append(b, iBytes...)
}
func appendOrderID(b []byte, order Order) []byte {
oid := order.ID()
return append(b, oid[:]...)
}
// MatchProof contains the key results of an epoch's order matching.
type MatchProof struct {
Epoch EpochID
Preimages []Preimage
Misses []Order
CSum []byte
Seed []byte
}