Golang implemented sidechain for Bytom
Revision | c673035bba5f28d1b2b0432a3d6485315eb91460 (tree) |
---|---|
Time | 2019-11-06 01:59:00 |
Author | paladz <453256728@qq.c...> |
Commiter | paladz |
same change while go over the codes
@@ -10,6 +10,7 @@ import ( | ||
10 | 10 | "github.com/vapor/protocol/bc/types" |
11 | 11 | ) |
12 | 12 | |
13 | +// MovUtxo store the utxo information for mov order | |
13 | 14 | type MovUtxo struct { |
14 | 15 | SourceID *bc.Hash |
15 | 16 | SourcePos uint64 |
@@ -17,6 +18,7 @@ type MovUtxo struct { | ||
17 | 18 | ControlProgram []byte |
18 | 19 | } |
19 | 20 | |
21 | +// Order store all the order information | |
20 | 22 | type Order struct { |
21 | 23 | FromAssetID *bc.AssetID |
22 | 24 | ToAssetID *bc.AssetID |
@@ -24,14 +26,11 @@ type Order struct { | ||
24 | 26 | Rate float64 |
25 | 27 | } |
26 | 28 | |
29 | +// OrderSlice is define for order's sort | |
27 | 30 | type OrderSlice []*Order |
28 | 31 | |
29 | -func (o OrderSlice) Len() int { | |
30 | - return len(o) | |
31 | -} | |
32 | -func (o OrderSlice) Swap(i, j int) { | |
33 | - o[i], o[j] = o[j], o[i] | |
34 | -} | |
32 | +func (o OrderSlice) Len() int { return len(o) } | |
33 | +func (o OrderSlice) Swap(i, j int) { o[i], o[j] = o[j], o[i] } | |
35 | 34 | func (o OrderSlice) Less(i, j int) bool { |
36 | 35 | if o[i].Rate == o[j].Rate { |
37 | 36 | return hex.EncodeToString(o[i].UTXOHash().Bytes()) < hex.EncodeToString(o[j].UTXOHash().Bytes()) |
@@ -39,6 +38,7 @@ func (o OrderSlice) Less(i, j int) bool { | ||
39 | 38 | return o[i].Rate < o[j].Rate |
40 | 39 | } |
41 | 40 | |
41 | +// NewOrderFromOutput convert txinput to order | |
42 | 42 | func NewOrderFromOutput(tx *types.Tx, outputIndex int) (*Order, error) { |
43 | 43 | outputID := tx.OutputID(outputIndex) |
44 | 44 | output, err := tx.IntraChainOutput(*outputID) |
@@ -65,6 +65,7 @@ func NewOrderFromOutput(tx *types.Tx, outputIndex int) (*Order, error) { | ||
65 | 65 | }, nil |
66 | 66 | } |
67 | 67 | |
68 | +// NewOrderFromInput convert txoutput to order | |
68 | 69 | func NewOrderFromInput(tx *types.Tx, inputIndex int) (*Order, error) { |
69 | 70 | input, ok := tx.Inputs[inputIndex].TypedInput.(*types.SpendInput) |
70 | 71 | if !ok { |
@@ -83,12 +84,23 @@ func NewOrderFromInput(tx *types.Tx, inputIndex int) (*Order, error) { | ||
83 | 84 | Utxo: &MovUtxo{ |
84 | 85 | SourceID: &input.SourceID, |
85 | 86 | Amount: input.Amount, |
86 | - SourcePos: input.SourcePosition, | |
87 | + SourcePos: input.SourcePosition, | |
87 | 88 | ControlProgram: input.ControlProgram, |
88 | 89 | }, |
89 | 90 | }, nil |
90 | 91 | } |
91 | 92 | |
93 | +// Key return the unique key for representing this order | |
94 | +func (o *Order) Key() string { | |
95 | + return fmt.Sprintf("%s:%d", o.Utxo.SourceID, o.Utxo.SourcePos) | |
96 | +} | |
97 | + | |
98 | +// TradePair return the trade pair info | |
99 | +func (o *Order) TradePair() *TradePair { | |
100 | + return &TradePair{FromAssetID: o.FromAssetID, ToAssetID: o.ToAssetID} | |
101 | +} | |
102 | + | |
103 | +// UTXOHash calculate the utxo hash of this order | |
92 | 104 | func (o *Order) UTXOHash() *bc.Hash { |
93 | 105 | prog := &bc.Program{VmVersion: 1, Code: o.Utxo.ControlProgram} |
94 | 106 | src := &bc.ValueSource{ |
@@ -100,20 +112,19 @@ func (o *Order) UTXOHash() *bc.Hash { | ||
100 | 112 | return &hash |
101 | 113 | } |
102 | 114 | |
103 | -func (o *Order) TradePair() *TradePair { | |
104 | - return &TradePair{FromAssetID: o.FromAssetID, ToAssetID: o.ToAssetID} | |
105 | -} | |
106 | - | |
107 | -func (o *Order) Key() string { | |
108 | - return fmt.Sprintf("%s:%d", o.Utxo.SourceID, o.Utxo.SourcePos) | |
109 | -} | |
110 | - | |
115 | +// TradePair is the object for record trade pair info | |
111 | 116 | type TradePair struct { |
112 | 117 | FromAssetID *bc.AssetID |
113 | 118 | ToAssetID *bc.AssetID |
114 | 119 | Count int |
115 | 120 | } |
116 | 121 | |
122 | +// Key return the unique key for representing this trade pair | |
123 | +func (t *TradePair) Key() string { | |
124 | + return fmt.Sprintf("%s:%s", t.FromAssetID, t.ToAssetID) | |
125 | +} | |
126 | + | |
127 | +// Reverse return the reverse trade pair object | |
117 | 128 | func (t *TradePair) Reverse() *TradePair { |
118 | 129 | return &TradePair{ |
119 | 130 | FromAssetID: t.ToAssetID, |
@@ -121,10 +132,7 @@ func (t *TradePair) Reverse() *TradePair { | ||
121 | 132 | } |
122 | 133 | } |
123 | 134 | |
124 | -func (t *TradePair) Key() string { | |
125 | - return fmt.Sprintf("%s:%s", t.FromAssetID, t.ToAssetID) | |
126 | -} | |
127 | - | |
135 | +// MovDatabaseState is object to record DB image status | |
128 | 136 | type MovDatabaseState struct { |
129 | 137 | Height uint64 |
130 | 138 | Hash *bc.Hash |
@@ -6,6 +6,7 @@ import ( | ||
6 | 6 | "github.com/vapor/protocol/bc/types" |
7 | 7 | ) |
8 | 8 | |
9 | +// IsMatchedTx check if this transaction has trade mov order input | |
9 | 10 | func IsMatchedTx(tx *types.Tx) bool { |
10 | 11 | if len(tx.Inputs) < 2 { |
11 | 12 | return false |
@@ -18,6 +19,7 @@ func IsMatchedTx(tx *types.Tx) bool { | ||
18 | 19 | return false |
19 | 20 | } |
20 | 21 | |
22 | +// IsCancelOrderTx check if this transaction has cancel mov order input | |
21 | 23 | func IsCancelOrderTx(tx *types.Tx) bool { |
22 | 24 | for _, input := range tx.Inputs { |
23 | 25 | if input.InputType() == types.SpendInputType && contract.IsCancelClauseSelector(input) && segwit.IsP2WMCScript(input.ControlProgram()) { |
@@ -26,4 +28,3 @@ func IsCancelOrderTx(tx *types.Tx) bool { | ||
26 | 28 | } |
27 | 29 | return false |
28 | 30 | } |
29 | - |
@@ -8,29 +8,34 @@ import ( | ||
8 | 8 | ) |
9 | 9 | |
10 | 10 | const ( |
11 | - sizeOfCancelClauseArgs = 3 | |
11 | + sizeOfCancelClauseArgs = 3 | |
12 | 12 | sizeOfPartialTradeClauseArgs = 3 |
13 | - sizeOfFullTradeClauseArgs = 2 | |
13 | + sizeOfFullTradeClauseArgs = 2 | |
14 | 14 | ) |
15 | 15 | |
16 | +// smart contract clause select for differnet unlock method | |
16 | 17 | const ( |
17 | 18 | PartialTradeClauseSelector int64 = iota |
18 | 19 | FullTradeClauseSelector |
19 | 20 | CancelClauseSelector |
20 | 21 | ) |
21 | 22 | |
23 | +// IsCancelClauseSelector check if input select cancel clause | |
22 | 24 | func IsCancelClauseSelector(input *types.TxInput) bool { |
23 | - return len(input.Arguments()) == sizeOfCancelClauseArgs && hex.EncodeToString(input.Arguments()[len(input.Arguments()) - 1]) == hex.EncodeToString(vm.Int64Bytes(CancelClauseSelector)) | |
25 | + return len(input.Arguments()) == sizeOfCancelClauseArgs && hex.EncodeToString(input.Arguments()[len(input.Arguments())-1]) == hex.EncodeToString(vm.Int64Bytes(CancelClauseSelector)) | |
24 | 26 | } |
25 | 27 | |
28 | +// IsTradeClauseSelector check if input select is partial trade clause or full trade clause | |
26 | 29 | func IsTradeClauseSelector(input *types.TxInput) bool { |
27 | 30 | return IsPartialTradeClauseSelector(input) || IsFullTradeClauseSelector(input) |
28 | 31 | } |
29 | 32 | |
33 | +// IsPartialTradeClauseSelector check if input select partial trade clause | |
30 | 34 | func IsPartialTradeClauseSelector(input *types.TxInput) bool { |
31 | - return len(input.Arguments()) == sizeOfPartialTradeClauseArgs && hex.EncodeToString(input.Arguments()[len(input.Arguments()) - 1]) == hex.EncodeToString(vm.Int64Bytes(PartialTradeClauseSelector)) | |
35 | + return len(input.Arguments()) == sizeOfPartialTradeClauseArgs && hex.EncodeToString(input.Arguments()[len(input.Arguments())-1]) == hex.EncodeToString(vm.Int64Bytes(PartialTradeClauseSelector)) | |
32 | 36 | } |
33 | 37 | |
38 | +// IsFullTradeClauseSelector check if input select full trade clause | |
34 | 39 | func IsFullTradeClauseSelector(input *types.TxInput) bool { |
35 | - return len(input.Arguments()) == sizeOfFullTradeClauseArgs && hex.EncodeToString(input.Arguments()[len(input.Arguments()) - 1]) == hex.EncodeToString(vm.Int64Bytes(FullTradeClauseSelector)) | |
40 | + return len(input.Arguments()) == sizeOfFullTradeClauseArgs && hex.EncodeToString(input.Arguments()[len(input.Arguments())-1]) == hex.EncodeToString(vm.Int64Bytes(FullTradeClauseSelector)) | |
36 | 41 | } |
@@ -7,21 +7,25 @@ import ( | ||
7 | 7 | "github.com/vapor/protocol/bc" |
8 | 8 | ) |
9 | 9 | |
10 | +// TradePairIterator wrap read trade pair from DB action | |
10 | 11 | type TradePairIterator struct { |
11 | 12 | movStore MovStore |
12 | 13 | tradePairs []*common.TradePair |
13 | 14 | tradePairIndex int |
14 | 15 | } |
15 | 16 | |
17 | +// NewTradePairIterator create the new TradePairIterator object | |
16 | 18 | func NewTradePairIterator(movStore MovStore) *TradePairIterator { |
17 | 19 | return &TradePairIterator{movStore: movStore} |
18 | 20 | } |
19 | 21 | |
22 | +// HasNext check if there are more trade pairs in memory or DB | |
20 | 23 | func (t *TradePairIterator) HasNext() bool { |
21 | 24 | tradePairSize := len(t.tradePairs) |
22 | 25 | if t.tradePairIndex < tradePairSize { |
23 | 26 | return true |
24 | 27 | } |
28 | + | |
25 | 29 | var fromAssetID, toAssetID *bc.AssetID |
26 | 30 | if len(t.tradePairs) > 0 { |
27 | 31 | lastTradePair := t.tradePairs[tradePairSize-1] |
@@ -44,6 +48,7 @@ func (t *TradePairIterator) HasNext() bool { | ||
44 | 48 | return true |
45 | 49 | } |
46 | 50 | |
51 | +// Next return the next available trade pair in memory or DB | |
47 | 52 | func (t *TradePairIterator) Next() *common.TradePair { |
48 | 53 | if !t.HasNext() { |
49 | 54 | return nil |
@@ -54,12 +59,14 @@ func (t *TradePairIterator) Next() *common.TradePair { | ||
54 | 59 | return tradePair |
55 | 60 | } |
56 | 61 | |
62 | +// OrderIterator wrap read order from DB action | |
57 | 63 | type OrderIterator struct { |
58 | 64 | movStore MovStore |
59 | 65 | lastOrder *common.Order |
60 | 66 | orders []*common.Order |
61 | 67 | } |
62 | 68 | |
69 | +// NewOrderIterator create the new OrderIterator object | |
63 | 70 | func NewOrderIterator(movStore MovStore, tradePair *common.TradePair) *OrderIterator { |
64 | 71 | return &OrderIterator{ |
65 | 72 | movStore: movStore, |
@@ -67,6 +74,7 @@ func NewOrderIterator(movStore MovStore, tradePair *common.TradePair) *OrderIter | ||
67 | 74 | } |
68 | 75 | } |
69 | 76 | |
77 | +// HasNext check if there are more orders in memory or DB | |
70 | 78 | func (o *OrderIterator) HasNext() bool { |
71 | 79 | if len(o.orders) == 0 { |
72 | 80 | orders, err := o.movStore.ListOrders(o.lastOrder) |
@@ -84,6 +92,7 @@ func (o *OrderIterator) HasNext() bool { | ||
84 | 92 | return true |
85 | 93 | } |
86 | 94 | |
95 | +// NextBatch return the next batch of orders in memory or DB | |
87 | 96 | func (o *OrderIterator) NextBatch() []*common.Order { |
88 | 97 | if !o.HasNext() { |
89 | 98 | return nil |
@@ -126,26 +126,26 @@ func TestOrderIterator(t *testing.T) { | ||
126 | 126 | wantOrders []*common.Order |
127 | 127 | }{ |
128 | 128 | { |
129 | - desc: "normal case", | |
130 | - tradePair: &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}, | |
129 | + desc: "normal case", | |
130 | + tradePair: &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}, | |
131 | 131 | storeOrders: []*common.Order{order1, order2, order3}, |
132 | 132 | wantOrders: []*common.Order{order1, order2, order3}, |
133 | 133 | }, |
134 | 134 | { |
135 | - desc: "num of orders more than one return", | |
136 | - tradePair: &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}, | |
135 | + desc: "num of orders more than one return", | |
136 | + tradePair: &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}, | |
137 | 137 | storeOrders: []*common.Order{order1, order2, order3, order4, order5}, |
138 | 138 | wantOrders: []*common.Order{order1, order2, order3, order4, order5}, |
139 | 139 | }, |
140 | 140 | { |
141 | - desc: "only one order", | |
142 | - tradePair: &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}, | |
141 | + desc: "only one order", | |
142 | + tradePair: &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}, | |
143 | 143 | storeOrders: []*common.Order{order1}, |
144 | 144 | wantOrders: []*common.Order{order1}, |
145 | 145 | }, |
146 | 146 | { |
147 | - desc: "store is empty", | |
148 | - tradePair: &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}, | |
147 | + desc: "store is empty", | |
148 | + tradePair: &common.TradePair{FromAssetID: assetID1, ToAssetID: assetID2}, | |
149 | 149 | storeOrders: []*common.Order{}, |
150 | 150 | wantOrders: []*common.Order{}, |
151 | 151 | }, |
@@ -15,16 +15,19 @@ import ( | ||
15 | 15 | "github.com/vapor/protocol/vm/vmutil" |
16 | 16 | ) |
17 | 17 | |
18 | +// Engine is used to generate math transactions | |
18 | 19 | type Engine struct { |
19 | 20 | orderTable *OrderTable |
20 | 21 | maxFeeRate float64 |
21 | 22 | nodeProgram []byte |
22 | 23 | } |
23 | 24 | |
25 | +// NewEngine return a new Engine | |
24 | 26 | func NewEngine(orderTable *OrderTable, maxFeeRate float64, nodeProgram []byte) *Engine { |
25 | 27 | return &Engine{orderTable: orderTable, maxFeeRate: maxFeeRate, nodeProgram: nodeProgram} |
26 | 28 | } |
27 | 29 | |
30 | +// HasMatchedTx check does the input trade pair can generate a match deal | |
28 | 31 | func (e *Engine) HasMatchedTx(tradePairs ...*common.TradePair) bool { |
29 | 32 | if err := validateTradePairs(tradePairs); err != nil { |
30 | 33 | return false |
@@ -55,141 +58,51 @@ func (e *Engine) NextMatchedTx(tradePairs ...*common.TradePair) (*types.Tx, erro | ||
55 | 58 | e.orderTable.PopOrder(tradePair) |
56 | 59 | } |
57 | 60 | |
58 | - if err := addPartialTradeOrder(tx, e.orderTable); err != nil { | |
61 | + if err := e.addPartialTradeOrder(tx); err != nil { | |
59 | 62 | return nil, err |
60 | 63 | } |
61 | 64 | return tx, nil |
62 | 65 | } |
63 | 66 | |
64 | -func (e *Engine) peekOrders(tradePairs []*common.TradePair) []*common.Order { | |
65 | - var orders []*common.Order | |
66 | - for _, tradePair := range tradePairs { | |
67 | - order := e.orderTable.PeekOrder(tradePair) | |
68 | - if order == nil { | |
69 | - return nil | |
70 | - } | |
71 | - | |
72 | - orders = append(orders, order) | |
73 | - } | |
74 | - return orders | |
75 | -} | |
76 | - | |
77 | -func validateTradePairs(tradePairs []*common.TradePair) error { | |
78 | - if len(tradePairs) < 2 { | |
79 | - return errors.New("size of trade pairs at least 2") | |
80 | - } | |
81 | - | |
82 | - for i, tradePair := range tradePairs { | |
83 | - oppositeTradePair := tradePairs[getOppositeIndex(len(tradePairs), i)] | |
84 | - if *tradePair.ToAssetID != *oppositeTradePair.FromAssetID { | |
85 | - return errors.New("specified trade pairs is invalid") | |
86 | - } | |
87 | - } | |
88 | - return nil | |
89 | -} | |
90 | - | |
91 | -func isMatched(orders []*common.Order) bool { | |
92 | - for i, order := range orders { | |
93 | - opposisteOrder := orders[getOppositeIndex(len(orders), i)] | |
94 | - if 1 / order.Rate < opposisteOrder.Rate { | |
95 | - return false | |
96 | - } | |
97 | - } | |
98 | - return true | |
99 | -} | |
100 | - | |
101 | -func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) { | |
102 | - txData := &types.TxData{Version: 1} | |
103 | - for i, order := range orders { | |
104 | - input := types.NewSpendInput(nil, *order.Utxo.SourceID, *order.FromAssetID, order.Utxo.Amount, order.Utxo.SourcePos, order.Utxo.ControlProgram) | |
105 | - txData.Inputs = append(txData.Inputs, input) | |
106 | - | |
107 | - oppositeOrder := orders[getOppositeIndex(len(orders), i)] | |
108 | - if err := addMatchTxOutput(txData, input, order, oppositeOrder.Utxo.Amount); err != nil { | |
109 | - return nil, err | |
110 | - } | |
111 | - } | |
112 | - | |
113 | - if err := e.addMatchTxFeeOutput(txData); err != nil { | |
114 | - return nil, err | |
115 | - } | |
116 | - | |
117 | - byteData, err := txData.MarshalText() | |
118 | - if err != nil { | |
119 | - return nil, err | |
120 | - } | |
121 | - | |
122 | - txData.SerializedSize = uint64(len(byteData)) | |
123 | - return types.NewTx(*txData), nil | |
124 | -} | |
125 | - | |
126 | -func addMatchTxOutput(txData *types.TxData, txInput *types.TxInput, order *common.Order, oppositeAmount uint64) error { | |
127 | - contractArgs, err := segwit.DecodeP2WMCProgram(order.Utxo.ControlProgram) | |
128 | - if err != nil { | |
129 | - return err | |
130 | - } | |
131 | - | |
132 | - requestAmount := calcRequestAmount(order.Utxo.Amount, contractArgs) | |
133 | - receiveAmount := vprMath.MinUint64(requestAmount, oppositeAmount) | |
134 | - shouldPayAmount := CalcShouldPayAmount(receiveAmount, contractArgs) | |
135 | - isPartialTrade := requestAmount > receiveAmount | |
136 | - | |
137 | - setMatchTxArguments(txInput, isPartialTrade, len(txData.Outputs), receiveAmount) | |
138 | - txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.ToAssetID, receiveAmount, contractArgs.SellerProgram)) | |
139 | - if isPartialTrade { | |
140 | - txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.FromAssetID, order.Utxo.Amount-shouldPayAmount, order.Utxo.ControlProgram)) | |
141 | - } | |
142 | - return nil | |
143 | -} | |
144 | - | |
145 | 67 | func (e *Engine) addMatchTxFeeOutput(txData *types.TxData) error { |
146 | 68 | txFee, err := CalcMatchedTxFee(txData, e.maxFeeRate) |
147 | 69 | if err != nil { |
148 | 70 | return err |
149 | 71 | } |
150 | 72 | |
151 | - for feeAssetID, amount := range txFee { | |
152 | - var reminder int64 = 0 | |
153 | - feeAmount := amount.FeeAmount | |
154 | - if amount.FeeAmount > amount.MaxFeeAmount { | |
155 | - feeAmount = amount.MaxFeeAmount | |
156 | - reminder = amount.FeeAmount - amount.MaxFeeAmount | |
73 | + for assetID, matchTxFee := range txFee { | |
74 | + feeAmount, reminder := matchTxFee.FeeAmount, int64(0) | |
75 | + if matchTxFee.FeeAmount > matchTxFee.MaxFeeAmount { | |
76 | + feeAmount = matchTxFee.MaxFeeAmount | |
77 | + reminder = matchTxFee.FeeAmount - matchTxFee.MaxFeeAmount | |
157 | 78 | } |
158 | - txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(feeAssetID, uint64(feeAmount), e.nodeProgram)) | |
79 | + txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(assetID, uint64(feeAmount), e.nodeProgram)) | |
159 | 80 | |
160 | 81 | // There is the remaining amount after paying the handling fee, assign it evenly to participants in the transaction |
161 | 82 | averageAmount := reminder / int64(len(txData.Inputs)) |
162 | 83 | if averageAmount == 0 { |
163 | 84 | averageAmount = 1 |
164 | 85 | } |
86 | + | |
165 | 87 | for i := 0; i < len(txData.Inputs) && reminder > 0; i++ { |
166 | 88 | contractArgs, err := segwit.DecodeP2WMCProgram(txData.Inputs[i].ControlProgram()) |
167 | 89 | if err != nil { |
168 | 90 | return err |
169 | 91 | } |
170 | 92 | |
171 | - if i == len(txData.Inputs)-1 { | |
172 | - txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(feeAssetID, uint64(reminder), contractArgs.SellerProgram)) | |
173 | - } else { | |
174 | - txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(feeAssetID, uint64(averageAmount), contractArgs.SellerProgram)) | |
93 | + if reminder < 2*averageAmount { | |
94 | + txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(assetID, uint64(reminder), contractArgs.SellerProgram)) | |
95 | + break | |
175 | 96 | } |
97 | + | |
98 | + txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(assetID, uint64(averageAmount), contractArgs.SellerProgram)) | |
176 | 99 | reminder -= averageAmount |
177 | 100 | } |
178 | 101 | } |
179 | 102 | return nil |
180 | 103 | } |
181 | 104 | |
182 | -func setMatchTxArguments(txInput *types.TxInput, isPartialTrade bool, position int, receiveAmounts uint64) { | |
183 | - var arguments [][]byte | |
184 | - if isPartialTrade { | |
185 | - arguments = [][]byte{vm.Int64Bytes(int64(receiveAmounts)), vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.PartialTradeClauseSelector)} | |
186 | - } else { | |
187 | - arguments = [][]byte{vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.FullTradeClauseSelector)} | |
188 | - } | |
189 | - txInput.SetArguments(arguments) | |
190 | -} | |
191 | - | |
192 | -func addPartialTradeOrder(tx *types.Tx, orderTable *OrderTable) error { | |
105 | +func (e *Engine) addPartialTradeOrder(tx *types.Tx) error { | |
193 | 106 | for i, output := range tx.Outputs { |
194 | 107 | if !segwit.IsP2WMCScript(output.ControlProgram()) { |
195 | 108 | continue |
@@ -200,41 +113,70 @@ func addPartialTradeOrder(tx *types.Tx, orderTable *OrderTable) error { | ||
200 | 113 | return err |
201 | 114 | } |
202 | 115 | |
203 | - if err := orderTable.AddOrder(order); err != nil { | |
116 | + if err := e.orderTable.AddOrder(order); err != nil { | |
204 | 117 | return err |
205 | 118 | } |
206 | 119 | } |
207 | 120 | return nil |
208 | 121 | } |
209 | 122 | |
210 | -func getOppositeIndex(size int, selfIdx int) int { | |
211 | - oppositeIdx := selfIdx + 1 | |
212 | - if selfIdx >= size-1 { | |
213 | - oppositeIdx = 0 | |
123 | +func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) { | |
124 | + txData := &types.TxData{Version: 1} | |
125 | + for i, order := range orders { | |
126 | + input := types.NewSpendInput(nil, *order.Utxo.SourceID, *order.FromAssetID, order.Utxo.Amount, order.Utxo.SourcePos, order.Utxo.ControlProgram) | |
127 | + txData.Inputs = append(txData.Inputs, input) | |
128 | + | |
129 | + oppositeOrder := orders[calcOppositeIndex(len(orders), i)] | |
130 | + if err := addMatchTxOutput(txData, input, order, oppositeOrder.Utxo.Amount); err != nil { | |
131 | + return nil, err | |
132 | + } | |
133 | + } | |
134 | + | |
135 | + if err := e.addMatchTxFeeOutput(txData); err != nil { | |
136 | + return nil, err | |
137 | + } | |
138 | + | |
139 | + byteData, err := txData.MarshalText() | |
140 | + if err != nil { | |
141 | + return nil, err | |
142 | + } | |
143 | + | |
144 | + txData.SerializedSize = uint64(len(byteData)) | |
145 | + return types.NewTx(*txData), nil | |
146 | +} | |
147 | + | |
148 | +func (e *Engine) peekOrders(tradePairs []*common.TradePair) []*common.Order { | |
149 | + var orders []*common.Order | |
150 | + for _, tradePair := range tradePairs { | |
151 | + order := e.orderTable.PeekOrder(tradePair) | |
152 | + if order == nil { | |
153 | + return nil | |
154 | + } | |
155 | + | |
156 | + orders = append(orders, order) | |
214 | 157 | } |
215 | - return oppositeIdx | |
158 | + return orders | |
216 | 159 | } |
217 | 160 | |
161 | +// MatchedTxFee is object to record the mov tx's fee information | |
218 | 162 | type MatchedTxFee struct { |
219 | 163 | MaxFeeAmount int64 |
220 | 164 | FeeAmount int64 |
221 | 165 | } |
222 | 166 | |
167 | +// CalcMatchedTxFee is used to calculate tx's MatchedTxFees | |
223 | 168 | func CalcMatchedTxFee(txData *types.TxData, maxFeeRate float64) (map[bc.AssetID]*MatchedTxFee, error) { |
224 | 169 | assetFeeMap := make(map[bc.AssetID]*MatchedTxFee) |
225 | - sellerProgramMap := make(map[string]bool) | |
226 | - assetInputMap := make(map[bc.AssetID]uint64) | |
170 | + dealProgMaps := make(map[string]bool) | |
227 | 171 | |
228 | 172 | for _, input := range txData.Inputs { |
229 | - assetFeeMap[input.AssetID()] = &MatchedTxFee{} | |
230 | - assetFeeMap[input.AssetID()].FeeAmount += int64(input.AssetAmount().Amount) | |
173 | + assetFeeMap[input.AssetID()] = &MatchedTxFee{FeeAmount: int64(input.AssetAmount().Amount)} | |
231 | 174 | contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram()) |
232 | 175 | if err != nil { |
233 | 176 | return nil, err |
234 | 177 | } |
235 | 178 | |
236 | - sellerProgramMap[hex.EncodeToString(contractArgs.SellerProgram)] = true | |
237 | - assetInputMap[input.AssetID()] = input.Amount() | |
179 | + dealProgMaps[hex.EncodeToString(contractArgs.SellerProgram)] = true | |
238 | 180 | } |
239 | 181 | |
240 | 182 | for _, input := range txData.Inputs { |
@@ -243,40 +185,87 @@ func CalcMatchedTxFee(txData *types.TxData, maxFeeRate float64) (map[bc.AssetID] | ||
243 | 185 | return nil, err |
244 | 186 | } |
245 | 187 | |
246 | - oppositeAmount := assetInputMap[contractArgs.RequestedAsset] | |
188 | + oppositeAmount := uint64(assetFeeMap[contractArgs.RequestedAsset].FeeAmount) | |
247 | 189 | receiveAmount := vprMath.MinUint64(calcRequestAmount(input.Amount(), contractArgs), oppositeAmount) |
248 | - assetFeeMap[input.AssetID()].MaxFeeAmount = CalcMaxFeeAmount(CalcShouldPayAmount(receiveAmount, contractArgs), maxFeeRate) | |
190 | + assetFeeMap[input.AssetID()].MaxFeeAmount = calcMaxFeeAmount(calcShouldPayAmount(receiveAmount, contractArgs), maxFeeRate) | |
249 | 191 | } |
250 | 192 | |
251 | 193 | for _, output := range txData.Outputs { |
252 | - // minus the amount of the re-order | |
253 | - if segwit.IsP2WMCScript(output.ControlProgram()) { | |
254 | - assetFeeMap[*output.AssetAmount().AssetId].FeeAmount -= int64(output.AssetAmount().Amount) | |
255 | - } | |
256 | - // minus the amount of seller's receiving output | |
257 | - if _, ok := sellerProgramMap[hex.EncodeToString(output.ControlProgram())]; ok { | |
258 | - assetID := *output.AssetAmount().AssetId | |
259 | - fee, ok := assetFeeMap[assetID] | |
260 | - if !ok { | |
261 | - continue | |
262 | - } | |
263 | - fee.FeeAmount -= int64(output.AssetAmount().Amount) | |
264 | - if fee.FeeAmount == 0 { | |
265 | - delete(assetFeeMap, assetID) | |
194 | + assetAmount := output.AssetAmount() | |
195 | + if _, ok := dealProgMaps[hex.EncodeToString(output.ControlProgram())]; ok || segwit.IsP2WMCScript(output.ControlProgram()) { | |
196 | + assetFeeMap[*assetAmount.AssetId].FeeAmount -= int64(assetAmount.Amount) | |
197 | + if assetFeeMap[*assetAmount.AssetId].FeeAmount <= 0 { | |
198 | + delete(assetFeeMap, *assetAmount.AssetId) | |
266 | 199 | } |
267 | 200 | } |
268 | 201 | } |
269 | 202 | return assetFeeMap, nil |
270 | 203 | } |
271 | 204 | |
205 | +func addMatchTxOutput(txData *types.TxData, txInput *types.TxInput, order *common.Order, oppositeAmount uint64) error { | |
206 | + contractArgs, err := segwit.DecodeP2WMCProgram(order.Utxo.ControlProgram) | |
207 | + if err != nil { | |
208 | + return err | |
209 | + } | |
210 | + | |
211 | + requestAmount := calcRequestAmount(order.Utxo.Amount, contractArgs) | |
212 | + receiveAmount := vprMath.MinUint64(requestAmount, oppositeAmount) | |
213 | + shouldPayAmount := calcShouldPayAmount(receiveAmount, contractArgs) | |
214 | + isPartialTrade := requestAmount > receiveAmount | |
215 | + | |
216 | + setMatchTxArguments(txInput, isPartialTrade, len(txData.Outputs), receiveAmount) | |
217 | + txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.ToAssetID, receiveAmount, contractArgs.SellerProgram)) | |
218 | + if isPartialTrade { | |
219 | + txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.FromAssetID, order.Utxo.Amount-shouldPayAmount, order.Utxo.ControlProgram)) | |
220 | + } | |
221 | + return nil | |
222 | +} | |
223 | + | |
272 | 224 | func calcRequestAmount(fromAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 { |
273 | 225 | return uint64(int64(fromAmount) * contractArg.RatioNumerator / contractArg.RatioDenominator) |
274 | 226 | } |
275 | 227 | |
276 | -func CalcShouldPayAmount(receiveAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 { | |
228 | +func calcShouldPayAmount(receiveAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 { | |
277 | 229 | return uint64(math.Floor(float64(receiveAmount) * float64(contractArg.RatioDenominator) / float64(contractArg.RatioNumerator))) |
278 | 230 | } |
279 | 231 | |
280 | -func CalcMaxFeeAmount(shouldPayAmount uint64, maxFeeRate float64) int64 { | |
232 | +func calcMaxFeeAmount(shouldPayAmount uint64, maxFeeRate float64) int64 { | |
281 | 233 | return int64(math.Ceil(float64(shouldPayAmount) * maxFeeRate)) |
282 | 234 | } |
235 | + | |
236 | +func calcOppositeIndex(size int, selfIdx int) int { | |
237 | + return (selfIdx + 1) % size | |
238 | +} | |
239 | + | |
240 | +func isMatched(orders []*common.Order) bool { | |
241 | + for i, order := range orders { | |
242 | + if opposisteOrder := orders[calcOppositeIndex(len(orders), i)]; 1/order.Rate < opposisteOrder.Rate { | |
243 | + return false | |
244 | + } | |
245 | + } | |
246 | + return true | |
247 | +} | |
248 | + | |
249 | +func setMatchTxArguments(txInput *types.TxInput, isPartialTrade bool, position int, receiveAmounts uint64) { | |
250 | + var arguments [][]byte | |
251 | + if isPartialTrade { | |
252 | + arguments = [][]byte{vm.Int64Bytes(int64(receiveAmounts)), vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.PartialTradeClauseSelector)} | |
253 | + } else { | |
254 | + arguments = [][]byte{vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.FullTradeClauseSelector)} | |
255 | + } | |
256 | + txInput.SetArguments(arguments) | |
257 | +} | |
258 | + | |
259 | +func validateTradePairs(tradePairs []*common.TradePair) error { | |
260 | + if len(tradePairs) < 2 { | |
261 | + return errors.New("size of trade pairs at least 2") | |
262 | + } | |
263 | + | |
264 | + for i, tradePair := range tradePairs { | |
265 | + oppositeTradePair := tradePairs[calcOppositeIndex(len(tradePairs), i)] | |
266 | + if *tradePair.ToAssetID != *oppositeTradePair.FromAssetID { | |
267 | + return errors.New("specified trade pairs is invalid") | |
268 | + } | |
269 | + } | |
270 | + return nil | |
271 | +} |
@@ -11,6 +11,9 @@ import ( | ||
11 | 11 | "github.com/vapor/protocol/bc/types" |
12 | 12 | ) |
13 | 13 | |
14 | +/* | |
15 | + Test: validateTradePairs vaild and invaild case for 2, 3 trade pairs | |
16 | +*/ | |
14 | 17 | func TestGenerateMatchedTxs(t *testing.T) { |
15 | 18 | btc2eth := &common.TradePair{FromAssetID: &mock.BTC, ToAssetID: &mock.ETH} |
16 | 19 | eth2btc := &common.TradePair{FromAssetID: &mock.ETH, ToAssetID: &mock.BTC} |
@@ -108,22 +111,22 @@ func TestCalcMatchedTxFee(t *testing.T) { | ||
108 | 111 | wantMatchedTxFee map[bc.AssetID]*MatchedTxFee |
109 | 112 | }{ |
110 | 113 | { |
111 | - desc: "fee less than max fee", | |
112 | - maxFeeRate: 0.05, | |
114 | + desc: "fee less than max fee", | |
115 | + maxFeeRate: 0.05, | |
113 | 116 | wantMatchedTxFee: map[bc.AssetID]*MatchedTxFee{mock.ETH: {FeeAmount: 10, MaxFeeAmount: 26}}, |
114 | - tx: &mock.MatchedTxs[1].TxData, | |
117 | + tx: &mock.MatchedTxs[1].TxData, | |
115 | 118 | }, |
116 | 119 | { |
117 | - desc: "fee refund in tx", | |
118 | - maxFeeRate: 0.05, | |
120 | + desc: "fee refund in tx", | |
121 | + maxFeeRate: 0.05, | |
119 | 122 | wantMatchedTxFee: map[bc.AssetID]*MatchedTxFee{mock.ETH: {FeeAmount: 27, MaxFeeAmount: 27}}, |
120 | - tx: &mock.MatchedTxs[2].TxData, | |
123 | + tx: &mock.MatchedTxs[2].TxData, | |
121 | 124 | }, |
122 | 125 | { |
123 | - desc: "fee is zero", | |
124 | - maxFeeRate: 0.05, | |
126 | + desc: "fee is zero", | |
127 | + maxFeeRate: 0.05, | |
125 | 128 | wantMatchedTxFee: map[bc.AssetID]*MatchedTxFee{}, |
126 | - tx: &mock.MatchedTxs[0].TxData, | |
129 | + tx: &mock.MatchedTxs[0].TxData, | |
127 | 130 | }, |
128 | 131 | } |
129 | 132 |
@@ -8,8 +8,9 @@ import ( | ||
8 | 8 | "github.com/vapor/errors" |
9 | 9 | ) |
10 | 10 | |
11 | +// OrderTable is used to handle the mov orders in memory like stack | |
11 | 12 | type OrderTable struct { |
12 | - movStore database.MovStore | |
13 | + movStore database.MovStore | |
13 | 14 | // key of tradePair -> []order |
14 | 15 | dbOrders map[string][]*common.Order |
15 | 16 | // key of tradePair -> iterator |
@@ -21,6 +22,7 @@ type OrderTable struct { | ||
21 | 22 | arrivalDelOrders map[string]*common.Order |
22 | 23 | } |
23 | 24 | |
25 | +// NewOrderTable create a new OrderTable object | |
24 | 26 | func NewOrderTable(movStore database.MovStore, arrivalAddOrders, arrivalDelOrders []*common.Order) *OrderTable { |
25 | 27 | return &OrderTable{ |
26 | 28 | movStore: movStore, |
@@ -32,21 +34,32 @@ func NewOrderTable(movStore database.MovStore, arrivalAddOrders, arrivalDelOrder | ||
32 | 34 | } |
33 | 35 | } |
34 | 36 | |
37 | +// AddOrder add the in memory temp order to order table | |
38 | +func (o *OrderTable) AddOrder(order *common.Order) error { | |
39 | + tradePairKey := order.TradePair().Key() | |
40 | + orders := o.arrivalAddOrders[tradePairKey] | |
41 | + if len(orders) > 0 && order.Rate > orders[len(orders)-1].Rate { | |
42 | + return errors.New("rate of order must less than the min order in order table") | |
43 | + } | |
44 | + | |
45 | + o.arrivalAddOrders[tradePairKey] = append(orders, order) | |
46 | + return nil | |
47 | +} | |
48 | + | |
49 | +// PeekOrder return the next lowest order of given trade pair | |
35 | 50 | func (o *OrderTable) PeekOrder(tradePair *common.TradePair) *common.Order { |
36 | 51 | if len(o.dbOrders[tradePair.Key()]) == 0 { |
37 | 52 | o.extendDBOrders(tradePair) |
38 | 53 | } |
39 | 54 | |
40 | 55 | var nextOrder *common.Order |
41 | - | |
42 | 56 | orders := o.dbOrders[tradePair.Key()] |
43 | 57 | if len(orders) != 0 { |
44 | - nextOrder = orders[len(orders) - 1] | |
58 | + nextOrder = orders[len(orders)-1] | |
45 | 59 | } |
46 | 60 | |
47 | 61 | if nextOrder != nil && o.arrivalDelOrders[nextOrder.Key()] != nil { |
48 | 62 | o.dbOrders[tradePair.Key()] = orders[0 : len(orders)-1] |
49 | - delete(o.arrivalDelOrders, nextOrder.Key()) | |
50 | 63 | return o.PeekOrder(tradePair) |
51 | 64 | } |
52 | 65 |
@@ -57,6 +70,7 @@ func (o *OrderTable) PeekOrder(tradePair *common.TradePair) *common.Order { | ||
57 | 70 | return nextOrder |
58 | 71 | } |
59 | 72 | |
73 | +// PopOrder delete the next lowest order of given trade pair | |
60 | 74 | func (o *OrderTable) PopOrder(tradePair *common.TradePair) { |
61 | 75 | order := o.PeekOrder(tradePair) |
62 | 76 | if order == nil { |
@@ -64,25 +78,34 @@ func (o *OrderTable) PopOrder(tradePair *common.TradePair) { | ||
64 | 78 | } |
65 | 79 | |
66 | 80 | orders := o.dbOrders[tradePair.Key()] |
67 | - if len(orders) != 0 && orders[len(orders) - 1].Key() == order.Key() { | |
81 | + if len(orders) != 0 && orders[len(orders)-1].Key() == order.Key() { | |
68 | 82 | o.dbOrders[tradePair.Key()] = orders[0 : len(orders)-1] |
69 | 83 | } |
70 | 84 | |
71 | 85 | arrivalOrders := o.arrivalAddOrders[tradePair.Key()] |
72 | - if len(arrivalOrders) != 0 && arrivalOrders[len(arrivalOrders) - 1].Key() == order.Key() { | |
86 | + if len(arrivalOrders) != 0 && arrivalOrders[len(arrivalOrders)-1].Key() == order.Key() { | |
73 | 87 | o.arrivalAddOrders[tradePair.Key()] = arrivalOrders[0 : len(arrivalOrders)-1] |
74 | 88 | } |
75 | 89 | } |
76 | 90 | |
77 | -func (o *OrderTable) AddOrder(order *common.Order) error { | |
78 | - tradePair := order.TradePair() | |
79 | - orders := o.dbOrders[tradePair.Key()] | |
80 | - if len(orders) > 0 && order.Rate > orders[len(orders)-1].Rate { | |
81 | - return errors.New("rate of order must less than the min order in order table") | |
91 | +func arrangeArrivalAddOrders(orders []*common.Order) map[string][]*common.Order { | |
92 | + arrivalAddOrderMap := make(map[string][]*common.Order) | |
93 | + for _, order := range orders { | |
94 | + arrivalAddOrderMap[order.TradePair().Key()] = append(arrivalAddOrderMap[order.TradePair().Key()], order) | |
82 | 95 | } |
83 | 96 | |
84 | - o.dbOrders[tradePair.Key()] = append(orders, order) | |
85 | - return nil | |
97 | + for _, orders := range arrivalAddOrderMap { | |
98 | + sort.Sort(sort.Reverse(common.OrderSlice(orders))) | |
99 | + } | |
100 | + return arrivalAddOrderMap | |
101 | +} | |
102 | + | |
103 | +func arrangeArrivalDelOrders(orders []*common.Order) map[string]*common.Order { | |
104 | + arrivalDelOrderMap := make(map[string]*common.Order) | |
105 | + for _, order := range orders { | |
106 | + arrivalDelOrderMap[order.Key()] = order | |
107 | + } | |
108 | + return arrivalDelOrderMap | |
86 | 109 | } |
87 | 110 | |
88 | 111 | func (o *OrderTable) extendDBOrders(tradePair *common.TradePair) { |
@@ -99,29 +122,8 @@ func (o *OrderTable) extendDBOrders(tradePair *common.TradePair) { | ||
99 | 122 | } |
100 | 123 | |
101 | 124 | func (o *OrderTable) peekArrivalOrder(tradePair *common.TradePair) *common.Order { |
102 | - arrivalAddOrders := o.arrivalAddOrders[tradePair.Key()] | |
103 | - if len(arrivalAddOrders) > 0 { | |
104 | - return arrivalAddOrders[len(arrivalAddOrders) -1] | |
125 | + if arrivalAddOrders := o.arrivalAddOrders[tradePair.Key()]; len(arrivalAddOrders) > 0 { | |
126 | + return arrivalAddOrders[len(arrivalAddOrders)-1] | |
105 | 127 | } |
106 | 128 | return nil |
107 | 129 | } |
108 | - | |
109 | -func arrangeArrivalAddOrders(orders []*common.Order) map[string][]*common.Order { | |
110 | - arrivalAddOrderMap := make(map[string][]*common.Order) | |
111 | - for _, order := range orders { | |
112 | - arrivalAddOrderMap[order.TradePair().Key()] = append(arrivalAddOrderMap[order.TradePair().Key()], order) | |
113 | - } | |
114 | - | |
115 | - for _, orders := range arrivalAddOrderMap { | |
116 | - sort.Sort(sort.Reverse(common.OrderSlice(orders))) | |
117 | - } | |
118 | - return arrivalAddOrderMap | |
119 | -} | |
120 | - | |
121 | -func arrangeArrivalDelOrders(orders []*common.Order) map[string]*common.Order { | |
122 | - arrivalDelOrderMap := make(map[string]*common.Order) | |
123 | - for _, order := range orders { | |
124 | - arrivalDelOrderMap[order.Key()] = order | |
125 | - } | |
126 | - return arrivalDelOrderMap | |
127 | -} |
@@ -90,7 +90,7 @@ func TestOrderTable(t *testing.T) { | ||
90 | 90 | mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3], |
91 | 91 | }), |
92 | 92 | initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0]}, |
93 | - popOrders: []*common.TradePair{btc2eth}, | |
93 | + popOrders: []*common.TradePair{btc2eth}, | |
94 | 94 | wantPeekedOrders: map[common.TradePair]*common.Order{ |
95 | 95 | *btc2eth: mock.Btc2EthOrders[0], |
96 | 96 | }, |
@@ -129,7 +129,7 @@ func TestOrderTable(t *testing.T) { | ||
129 | 129 | }), |
130 | 130 | initArrivalAddOrders: []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[2]}, |
131 | 131 | initArrivalDelOrders: []*common.Order{mock.Btc2EthOrders[3]}, |
132 | - popOrders: []*common.TradePair{btc2eth}, | |
132 | + popOrders: []*common.TradePair{btc2eth}, | |
133 | 133 | wantPeekedOrders: map[common.TradePair]*common.Order{ |
134 | 134 | *btc2eth: mock.Btc2EthOrders[2], |
135 | 135 | }, |
@@ -154,6 +154,19 @@ func TestOrderTable(t *testing.T) { | ||
154 | 154 | *btc2eth: nil, |
155 | 155 | }, |
156 | 156 | }, |
157 | + { | |
158 | + desc: "has arrival delete orders, no add order, no pop order, need recursive to peek one order", | |
159 | + initMovStore: mock.NewMovStore( | |
160 | + []*common.TradePair{btc2eth}, | |
161 | + []*common.Order{ | |
162 | + mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Btc2EthOrders[2], mock.Btc2EthOrders[3], | |
163 | + }), | |
164 | + initArrivalAddOrders: []*common.Order{}, | |
165 | + initArrivalDelOrders: []*common.Order{mock.Btc2EthOrders[3], mock.Btc2EthOrders[0], mock.Btc2EthOrders[2]}, | |
166 | + wantPeekedOrders: map[common.TradePair]*common.Order{ | |
167 | + *btc2eth: mock.Btc2EthOrders[1], | |
168 | + }, | |
169 | + }, | |
157 | 170 | } |
158 | 171 | |
159 | 172 | for i, c := range cases { |