OSDN Git Service

add maker taker (#537)
[bytom/vapor.git] / application / mov / mov_core_test.go
1 package mov
2
3 import (
4         "encoding/hex"
5         "math"
6         "os"
7         "testing"
8
9         "github.com/bytom/vapor/application/mov/common"
10         "github.com/bytom/vapor/application/mov/database"
11         "github.com/bytom/vapor/application/mov/match"
12         "github.com/bytom/vapor/application/mov/mock"
13         "github.com/bytom/vapor/consensus"
14         dbm "github.com/bytom/vapor/database/leveldb"
15         "github.com/bytom/vapor/protocol/bc"
16         "github.com/bytom/vapor/protocol/bc/types"
17         "github.com/bytom/vapor/protocol/vm"
18         "github.com/bytom/vapor/testutil"
19 )
20
21 var initBlockHeader = &types.BlockHeader{Height: 1, PreviousBlockHash: bc.Hash{}}
22
23 func TestApplyBlock(t *testing.T) {
24         cases := []struct {
25                 desc        string
26                 block       *types.Block
27                 blockFunc   testFun
28                 initOrders  []*common.Order
29                 wantOrders  []*common.Order
30                 wantDBState *common.MovDatabaseState
31                 wantError   error
32         }{
33                 {
34                         desc: "apply block has pending order transaction",
35                         block: &types.Block{
36                                 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
37                                 Transactions: []*types.Tx{
38                                         mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[0],
39                                 },
40                         },
41                         blockFunc:   applyBlock,
42                         wantOrders:  []*common.Order{mock.MustNewOrderFromOutputV2(mock.Btc2EthMakerTxs[0], 0, 2, 0), mock.MustNewOrderFromOutputV2(mock.Eth2BtcMakerTxs[0], 0, 2, 1)},
43                         wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
44                 },
45                 {
46                         desc: "apply block has two different trade pairs & different trade pair won't affect each order",
47                         block: &types.Block{
48                                 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
49                                 Transactions: []*types.Tx{
50                                         mock.Btc2EthMakerTxs[0],
51                                         mock.Eth2BtcMakerTxs[0],
52                                         mock.Eos2EtcMakerTxs[0],
53                                         mock.Eth2EosMakerTxs[0],
54                                 },
55                         },
56                         blockFunc: applyBlock,
57                         wantOrders: []*common.Order{
58                                 mock.MustNewOrderFromOutputV2(mock.Btc2EthMakerTxs[0], 0, 2, 0),
59                                 mock.MustNewOrderFromOutputV2(mock.Eth2BtcMakerTxs[0], 0, 2, 1),
60                                 mock.MustNewOrderFromOutputV2(mock.Eos2EtcMakerTxs[0], 0, 2, 2),
61                                 mock.MustNewOrderFromOutputV2(mock.Eth2EosMakerTxs[0], 0, 2, 3),
62                         },
63                         wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
64                 },
65                 {
66                         desc: "apply block has full matched transaction",
67                         block: &types.Block{
68                                 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
69                                 Transactions: []*types.Tx{
70                                         mock.MatchedTxs[1],
71                                 },
72                         },
73                         blockFunc:   applyBlock,
74                         initOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[0]},
75                         wantOrders:  []*common.Order{mock.Btc2EthOrders[1]},
76                         wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
77                 },
78                 {
79                         desc: "apply block has partial matched transaction",
80                         block: &types.Block{
81                                 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
82                                 Transactions: []*types.Tx{
83                                         mock.MatchedTxs[0],
84                                 },
85                         },
86                         blockFunc:   applyBlock,
87                         initOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[1]},
88                         wantOrders:  []*common.Order{mock.MustNewOrderFromOutputV2(mock.MatchedTxs[0], 1, 2, 0)},
89                         wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
90                 },
91                 {
92                         desc: "apply block has two partial matched transaction",
93                         block: &types.Block{
94                                 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
95                                 Transactions: []*types.Tx{
96                                         mock.MatchedTxs[2], mock.MatchedTxs[3],
97                                 },
98                         },
99                         blockFunc:   applyBlock,
100                         initOrders:  []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
101                         wantOrders:  []*common.Order{mock.MustNewOrderFromOutputV2(mock.MatchedTxs[3], 1, 2, 1)},
102                         wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
103                 },
104                 {
105                         desc: "apply block has partial matched transaction by pending orders from tx pool",
106                         block: &types.Block{
107                                 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
108                                 Transactions: []*types.Tx{
109                                         mock.Btc2EthMakerTxs[0],
110                                         mock.Eth2BtcMakerTxs[1],
111                                         mock.MatchedTxs[4],
112                                 },
113                         },
114                         blockFunc:   applyBlock,
115                         initOrders:  []*common.Order{},
116                         wantOrders:  []*common.Order{mock.MustNewOrderFromOutputV2(mock.MatchedTxs[4], 1, 2, 2)},
117                         wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
118                 },
119                 {
120                         desc: "apply block which node packed maker tx and match transaction in random orde",
121                         block: &types.Block{
122                                 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
123                                 Transactions: []*types.Tx{
124                                         mock.Eos2EtcMakerTxs[0],
125                                         mock.Btc2EthMakerTxs[0],
126                                         mock.Eth2BtcMakerTxs[1],
127                                         mock.MatchedTxs[4],
128                                         mock.Eth2EosMakerTxs[0],
129                                         mock.Etc2EosMakerTxs[0],
130                                         mock.MatchedTxs[5],
131                                 },
132                         },
133                         blockFunc:  applyBlock,
134                         initOrders: []*common.Order{},
135                         wantOrders: []*common.Order{
136                                 mock.MustNewOrderFromOutputV2(mock.MatchedTxs[4], 1, 2, 3),
137                                 mock.MustNewOrderFromOutputV2(mock.Eth2EosMakerTxs[0], 0, 2, 4),
138                         },
139                         wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
140                 },
141                 {
142                         desc: "apply block has partial matched transaction chain",
143                         block: &types.Block{
144                                 BlockHeader: types.BlockHeader{Height: 2, PreviousBlockHash: initBlockHeader.Hash()},
145                                 Transactions: []*types.Tx{
146                                         mock.Btc2EthMakerTxs[0],
147                                         mock.Eth2BtcMakerTxs[1],
148                                         mock.MatchedTxs[4],
149                                         mock.Eth2BtcMakerTxs[0],
150                                         mock.MatchedTxs[7],
151                                 },
152                         },
153                         blockFunc:   applyBlock,
154                         initOrders:  []*common.Order{},
155                         wantOrders:  []*common.Order{mock.MustNewOrderFromOutputV2(mock.MatchedTxs[7], 2, 2, 4)},
156                         wantDBState: &common.MovDatabaseState{Height: 2, Hash: hashPtr(testutil.MustDecodeHash("88dbcde57bb2b53b107d7494f20f1f1a892307a019705980c3510890449c0020"))},
157                 },
158                 {
159                         desc: "detach block has pending order transaction",
160                         block: &types.Block{
161                                 BlockHeader: *initBlockHeader,
162                                 Transactions: []*types.Tx{
163                                         mock.Btc2EthMakerTxs[0], mock.Eth2BtcMakerTxs[1],
164                                 },
165                         },
166                         blockFunc:   detachBlock,
167                         initOrders:  []*common.Order{mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0), mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[1], 0)},
168                         wantOrders:  []*common.Order{},
169                         wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
170                 },
171                 {
172                         desc: "detach block has two different trade pairs & different trade pair won't affect each order",
173                         block: &types.Block{
174                                 BlockHeader: *initBlockHeader,
175                                 Transactions: []*types.Tx{
176                                         mock.Btc2EthMakerTxs[0],
177                                         mock.Eth2BtcMakerTxs[0],
178                                         mock.Eos2EtcMakerTxs[0],
179                                         mock.Eth2EosMakerTxs[0],
180                                 },
181                         },
182                         blockFunc: detachBlock,
183                         initOrders: []*common.Order{
184                                 mock.MustNewOrderFromOutput(mock.Btc2EthMakerTxs[0], 0),
185                                 mock.MustNewOrderFromOutput(mock.Eth2BtcMakerTxs[0], 0),
186                                 mock.MustNewOrderFromOutput(mock.Eos2EtcMakerTxs[0], 0),
187                                 mock.MustNewOrderFromOutput(mock.Eth2EosMakerTxs[0], 0),
188                         },
189                         wantOrders:  []*common.Order{},
190                         wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
191                 },
192                 {
193                         desc: "detach block has full matched transaction",
194                         block: &types.Block{
195                                 BlockHeader: *initBlockHeader,
196                                 Transactions: []*types.Tx{
197                                         mock.MatchedTxs[1],
198                                 },
199                         },
200                         blockFunc:  detachBlock,
201                         initOrders: []*common.Order{mock.Btc2EthOrders[1]},
202                         wantOrders: []*common.Order{
203                                 orderWithHeightAndTxIndex(mock.Btc2EthOrders[0], 0, 0),
204                                 mock.Btc2EthOrders[1],
205                                 orderWithHeightAndTxIndex(mock.Eth2BtcOrders[0], 0, 0),
206                         },
207                         wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
208                 },
209                 {
210                         desc: "detach block has partial matched transaction",
211                         block: &types.Block{
212                                 BlockHeader: *initBlockHeader,
213                                 Transactions: []*types.Tx{
214                                         mock.MatchedTxs[0],
215                                 },
216                         },
217                         blockFunc:  detachBlock,
218                         initOrders: []*common.Order{mock.MustNewOrderFromOutputV2(mock.MatchedTxs[0], 1, 1, 0)},
219                         wantOrders: []*common.Order{
220                                 orderWithHeightAndTxIndex(mock.Btc2EthOrders[0], 0, 0),
221                                 orderWithHeightAndTxIndex(mock.Eth2BtcOrders[1], 0, 0),
222                         },
223                         wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
224                 },
225                 {
226                         desc: "detach block has two partial matched transaction",
227                         block: &types.Block{
228                                 BlockHeader: *initBlockHeader,
229                                 Transactions: []*types.Tx{
230                                         mock.MatchedTxs[2], mock.MatchedTxs[3],
231                                 },
232                         },
233                         blockFunc:  detachBlock,
234                         initOrders: []*common.Order{mock.MustNewOrderFromOutput(mock.MatchedTxs[3], 1)},
235                         wantOrders: []*common.Order{
236                                 orderWithHeightAndTxIndex(mock.Btc2EthOrders[0], 0, 0),
237                                 orderWithHeightAndTxIndex(mock.Btc2EthOrders[1], 0, 0),
238                                 orderWithHeightAndTxIndex(mock.Eth2BtcOrders[2], 0, 0),
239                         },
240                         wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
241                 },
242                 {
243                         desc: "detach block which node packed maker tx and match transaction in random orde",
244                         block: &types.Block{
245                                 BlockHeader: *initBlockHeader,
246                                 Transactions: []*types.Tx{
247                                         mock.Eos2EtcMakerTxs[0],
248                                         mock.Btc2EthMakerTxs[0],
249                                         mock.MatchedTxs[4],
250                                         mock.Eth2EosMakerTxs[0],
251                                         mock.Eth2BtcMakerTxs[1],
252                                         mock.MatchedTxs[5],
253                                         mock.Etc2EosMakerTxs[0],
254                                 },
255                         },
256                         blockFunc: detachBlock,
257                         initOrders: []*common.Order{
258                                 mock.MustNewOrderFromOutput(mock.MatchedTxs[4], 1),
259                                 mock.MustNewOrderFromOutput(mock.Eth2EosMakerTxs[0], 0),
260                         },
261                         wantOrders:  []*common.Order{},
262                         wantDBState: &common.MovDatabaseState{Height: 0, Hash: &bc.Hash{}},
263                 },
264         }
265
266         defer os.RemoveAll("temp")
267         for i, c := range cases {
268                 testDB := dbm.NewDB("testdb", "leveldb", "temp")
269                 store := database.NewLevelDBMovStore(testDB)
270                 if err := store.InitDBState(0, &bc.Hash{}); err != nil {
271                         t.Fatal(err)
272                 }
273
274                 if err := store.ProcessOrders(c.initOrders, nil, initBlockHeader); err != nil {
275                         t.Fatal(err)
276                 }
277
278                 movCore := &Core{movStore: store}
279                 if err := c.blockFunc(movCore, c.block); err != c.wantError {
280                         t.Errorf("#%d(%s):want error(%v), got error(%v)", i, c.desc, c.wantError, err)
281                 }
282
283                 gotOrders := queryAllOrders(store)
284                 if !ordersEquals(c.wantOrders, gotOrders) {
285                         t.Errorf("#%d(%s):want orders(%v), got orders(%v)", i, c.desc, c.wantOrders, gotOrders)
286                 }
287
288                 dbState, err := store.GetMovDatabaseState()
289                 if err != nil {
290                         t.Fatal(err)
291                 }
292
293                 if !testutil.DeepEqual(c.wantDBState, dbState) {
294                         t.Errorf("#%d(%s):want db state(%v), got db state(%v)", i, c.desc, c.wantDBState, dbState)
295                 }
296
297                 testDB.Close()
298                 os.RemoveAll("temp")
299         }
300 }
301
302 func TestValidateBlock(t *testing.T) {
303         consensus.ActiveNetParams.MovRewardPrograms = []consensus.MovRewardProgram{
304                 {
305                         BeginBlock: 0,
306                         EndBlock:   100,
307                         Program:    hex.EncodeToString(mock.RewardProgram),
308                 },
309         }
310
311         cases := []struct {
312                 desc          string
313                 block         *types.Block
314                 verifyResults []*bc.TxVerifyResult
315                 wantError     error
316         }{
317                 {
318                         desc: "block only has maker tx",
319                         block: &types.Block{
320                                 Transactions: []*types.Tx{
321                                         mock.Eth2BtcMakerTxs[0],
322                                         mock.Btc2EthMakerTxs[0],
323                                 },
324                         },
325                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}},
326                         wantError:     nil,
327                 },
328                 {
329                         desc: "block only has matched tx",
330                         block: &types.Block{
331                                 Transactions: []*types.Tx{
332                                         mock.MatchedTxs[0],
333                                         mock.MatchedTxs[1],
334                                         mock.MatchedTxs[2],
335                                 },
336                         },
337                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}, {StatusFail: false}},
338                         wantError:     nil,
339                 },
340                 {
341                         desc: "block has maker tx and matched tx",
342                         block: &types.Block{
343                                 Transactions: []*types.Tx{
344                                         mock.Eth2BtcMakerTxs[0],
345                                         mock.Btc2EthMakerTxs[0],
346                                         mock.MatchedTxs[0],
347                                         mock.MatchedTxs[1],
348                                         mock.MatchedTxs[2],
349                                 },
350                         },
351                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: false}, {StatusFail: false}, {StatusFail: false}, {StatusFail: false}},
352                         wantError:     nil,
353                 },
354                 {
355                         desc: "status fail of maker tx is true",
356                         block: &types.Block{
357                                 Transactions: []*types.Tx{
358                                         mock.Eth2BtcMakerTxs[0],
359                                         mock.Btc2EthMakerTxs[0],
360                                 },
361                         },
362                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
363                         wantError:     errStatusFailMustFalse,
364                 },
365                 {
366                         desc: "status fail of matched tx is true",
367                         block: &types.Block{
368                                 Transactions: []*types.Tx{
369                                         mock.MatchedTxs[1],
370                                         mock.MatchedTxs[2],
371                                 },
372                         },
373                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
374                         wantError:     errStatusFailMustFalse,
375                 },
376                 {
377                         desc: "asset id in matched tx is not unique",
378                         block: &types.Block{
379                                 Transactions: []*types.Tx{
380                                         types.NewTx(types.TxData{
381                                                 Inputs: []*types.TxInput{
382                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, mock.Btc2EthOrders[0].Utxo.ControlProgram),
383                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *mock.Eth2BtcOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Eth2BtcOrders[0].Utxo.Amount, mock.Eth2BtcOrders[0].Utxo.SourcePos, mock.Eth2BtcOrders[0].Utxo.ControlProgram),
384                                                 },
385                                                 Outputs: []*types.TxOutput{
386                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("51")),
387                                                         types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("53")),
388                                                         // fee
389                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 11, mock.RewardProgram),
390                                                         types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 1, mock.RewardProgram),
391                                                 },
392                                         }),
393                                 },
394                         },
395                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}, {StatusFail: true}},
396                         wantError:     errAssetIDMustUniqueInMatchedTx,
397                 },
398                 {
399                         desc: "common input in the matched tx",
400                         block: &types.Block{
401                                 Transactions: []*types.Tx{
402                                         types.NewTx(types.TxData{
403                                                 Inputs: []*types.TxInput{
404                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, mock.Btc2EthOrders[0].Utxo.ControlProgram),
405                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *mock.Eth2BtcOrders[0].Utxo.SourceID, *mock.Eth2BtcOrders[0].FromAssetID, mock.Eth2BtcOrders[0].Utxo.Amount, mock.Eth2BtcOrders[0].Utxo.SourcePos, mock.Eth2BtcOrders[0].Utxo.ControlProgram),
406                                                         types.NewSpendInput(nil, testutil.MustDecodeHash("28b7b53d8dc90006bf97e0a4eaae2a72ec3d869873188698b694beaf20789f21"), *consensus.BTMAssetID, 100, 0, []byte{0x51}),
407                                                 },
408                                                 Outputs: []*types.TxOutput{
409                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("51")),
410                                                         types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("53")),
411                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 11, mock.RewardProgram),
412                                                         types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 1, mock.RewardProgram),
413                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 100, []byte{0x51}),
414                                                 },
415                                         }),
416                                 },
417                         },
418                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
419                         wantError:     errInputProgramMustP2WMCScript,
420                 },
421                 {
422                         desc: "cancel order in the matched tx",
423                         block: &types.Block{
424                                 Transactions: []*types.Tx{
425                                         types.NewTx(types.TxData{
426                                                 Inputs: []*types.TxInput{
427                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, mock.Btc2EthOrders[0].Utxo.ControlProgram),
428                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, *mock.Eth2BtcOrders[0].Utxo.SourceID, *mock.Eth2BtcOrders[0].FromAssetID, mock.Eth2BtcOrders[0].Utxo.Amount, mock.Eth2BtcOrders[0].Utxo.SourcePos, mock.Eth2BtcOrders[0].Utxo.ControlProgram),
429                                                         types.NewSpendInput([][]byte{{}, {}, vm.Int64Bytes(2)}, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, mock.Btc2EthOrders[0].Utxo.ControlProgram),
430                                                 },
431                                                 Outputs: []*types.TxOutput{
432                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("51")),
433                                                         types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 9, testutil.MustDecodeHexString("53")),
434                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 11, mock.RewardProgram),
435                                                         types.NewIntraChainOutput(*mock.Eth2BtcOrders[0].ToAssetID, 1, mock.RewardProgram),
436                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, 10, testutil.MustDecodeHexString("51")),
437                                                 },
438                                         }),
439                                 },
440                         },
441                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
442                         wantError:     errExistCancelOrderInMatchedTx,
443                 },
444                 {
445                         desc: "common input in the cancel order tx",
446                         block: &types.Block{
447                                 Transactions: []*types.Tx{
448                                         types.NewTx(types.TxData{
449                                                 Inputs: []*types.TxInput{
450                                                         types.NewSpendInput([][]byte{{}, {}, vm.Int64Bytes(2)}, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, mock.Btc2EthOrders[0].Utxo.ControlProgram),
451                                                         types.NewSpendInput(nil, testutil.MustDecodeHash("28b7b53d8dc90006bf97e0a4eaae2a72ec3d869873188698b694beaf20789f21"), *consensus.BTMAssetID, 100, 0, []byte{0x51}),
452                                                 },
453                                                 Outputs: []*types.TxOutput{
454                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, 10, testutil.MustDecodeHexString("51")),
455                                                         types.NewIntraChainOutput(*consensus.BTMAssetID, 100, mock.RewardProgram),
456                                                 },
457                                         }),
458                                 },
459                         },
460                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
461                         wantError:     errInputProgramMustP2WMCScript,
462                 },
463                 {
464                         desc: "amount of fee greater than max fee amount",
465                         block: &types.Block{
466                                 Transactions: []*types.Tx{
467                                         types.NewTx(types.TxData{
468                                                 Inputs: []*types.TxInput{
469                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, mock.Btc2EthOrders[0].Utxo.ControlProgram),
470                                                         types.NewSpendInput([][]byte{vm.Int64Bytes(10), vm.Int64Bytes(1), vm.Int64Bytes(0)}, *mock.Eth2BtcOrders[2].Utxo.SourceID, *mock.Eth2BtcOrders[2].FromAssetID, mock.Eth2BtcOrders[2].Utxo.Amount, mock.Eth2BtcOrders[2].Utxo.SourcePos, mock.Eth2BtcOrders[2].Utxo.ControlProgram),
471                                                 },
472                                                 Outputs: []*types.TxOutput{
473                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 499, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
474                                                         types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].ToAssetID, 9, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
475                                                         // re-order
476                                                         types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].FromAssetID, 270, mock.Eth2BtcOrders[2].Utxo.ControlProgram),
477                                                         // fee
478                                                         types.NewIntraChainOutput(*mock.Btc2EthOrders[2].ToAssetID, 41, mock.RewardProgram),
479                                                         types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].ToAssetID, 1, mock.RewardProgram),
480                                                 },
481                                         }),
482                                 },
483                         },
484                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
485                         wantError:     match.ErrInvalidAmountOfFee,
486                 },
487                 {
488                         desc: "ratio numerator is zero",
489                         block: &types.Block{
490                                 Transactions: []*types.Tx{
491                                         types.NewTx(types.TxData{
492                                                 Inputs:  []*types.TxInput{types.NewSpendInput(nil, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, []byte{0x51})},
493                                                 Outputs: []*types.TxOutput{types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.MustCreateP2WMCProgram(mock.ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"), 0, 1))},
494                                         }),
495                                 },
496                         },
497                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
498                         wantError:     errRatioOfTradeLessThanZero,
499                 },
500                 {
501                         desc: "ratio denominator is zero",
502                         block: &types.Block{
503                                 Transactions: []*types.Tx{
504                                         types.NewTx(types.TxData{
505                                                 Inputs:  []*types.TxInput{types.NewSpendInput(nil, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, []byte{0x51})},
506                                                 Outputs: []*types.TxOutput{types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.MustCreateP2WMCProgram(mock.ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"), 1, 0))},
507                                         }),
508                                 },
509                         },
510                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
511                         wantError:     errRatioOfTradeLessThanZero,
512                 },
513                 {
514                         desc: "want amount is overflow",
515                         block: &types.Block{
516                                 Transactions: []*types.Tx{
517                                         types.NewTx(types.TxData{
518                                                 Inputs:  []*types.TxInput{types.NewSpendInput(nil, *mock.Btc2EthOrders[0].Utxo.SourceID, *mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.Btc2EthOrders[0].Utxo.SourcePos, []byte{0x51})},
519                                                 Outputs: []*types.TxOutput{types.NewIntraChainOutput(*mock.Btc2EthOrders[0].FromAssetID, mock.Btc2EthOrders[0].Utxo.Amount, mock.MustCreateP2WMCProgram(mock.ETH, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251"), math.MaxInt64, 1))},
520                                         }),
521                                 },
522                         },
523                         verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
524                         wantError:     errRequestAmountMath,
525                 },
526         }
527
528         for i, c := range cases {
529                 movCore := &Core{}
530                 c.block.Height = 84000000
531                 if err := movCore.ValidateBlock(c.block, c.verifyResults); err != c.wantError {
532                         t.Errorf("#%d(%s):validate block want error(%v), got error(%v)", i, c.desc, c.wantError, err)
533                 }
534         }
535 }
536
537 func TestCalcMatchedTxFee(t *testing.T) {
538         cases := []struct {
539                 desc             string
540                 tx               types.TxData
541                 wantMatchedTxFee map[bc.AssetID]*matchedTxFee
542         }{
543                 {
544                         desc: "fee less than max fee",
545                         wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{
546                                 mock.BTC: {amount: 1, rewardProgram: mock.RewardProgram},
547                                 mock.ETH: {amount: 2, rewardProgram: mock.RewardProgram},
548                         },
549                         tx: mock.MatchedTxs[1].TxData,
550                 },
551                 {
552                         desc: "fee refund in tx",
553                         wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{
554                                 mock.BTC: {amount: 1, rewardProgram: mock.RewardProgram},
555                                 mock.ETH: {amount: 1, rewardProgram: mock.RewardProgram},
556                         },
557                         tx: mock.MatchedTxs[2].TxData,
558                 },
559                 {
560                         desc: "no price diff",
561                         wantMatchedTxFee: map[bc.AssetID]*matchedTxFee{
562                                 mock.BTC: {amount: 1, rewardProgram: mock.RewardProgram},
563                                 mock.ETH: {amount: 1, rewardProgram: mock.RewardProgram},
564                         },
565                         tx: mock.MatchedTxs[0].TxData,
566                 },
567         }
568
569         for i, c := range cases {
570                 gotMatchedTxFee, err := calcFeeAmount(types.NewTx(c.tx))
571                 if err != nil {
572                         t.Fatal(err)
573                 }
574
575                 if !testutil.DeepEqual(gotMatchedTxFee, c.wantMatchedTxFee) {
576                         t.Errorf("#%d(%s):fail to caculate matched tx fee, got (%v), want (%v)", i, c.desc, gotMatchedTxFee, c.wantMatchedTxFee)
577                 }
578         }
579 }
580
581 func TestBeforeProposalBlock(t *testing.T) {
582         consensus.ActiveNetParams.MovRewardPrograms = []consensus.MovRewardProgram{
583                 {
584                         BeginBlock: 0,
585                         EndBlock:   100,
586                         Program:    hex.EncodeToString(mock.RewardProgram),
587                 },
588         }
589
590         cases := []struct {
591                 desc           string
592                 initOrders     []*common.Order
593                 gasLeft        int64
594                 wantMatchedTxs []*types.Tx
595         }{
596                 {
597                         desc:           "has matched tx, but gas left is zero",
598                         initOrders:     []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
599                         gasLeft:        0,
600                         wantMatchedTxs: []*types.Tx{},
601                 },
602                 {
603                         desc:           "has one matched tx, and gas is sufficient",
604                         initOrders:     []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
605                         gasLeft:        2000,
606                         wantMatchedTxs: []*types.Tx{mock.MatchedTxs[1]},
607                 },
608                 {
609                         desc: "has two matched tx, but gas is only enough to pack a matched tx",
610                         initOrders: []*common.Order{
611                                 mock.Btc2EthOrders[0],
612                                 mock.Btc2EthOrders[1],
613                                 mock.Eth2BtcOrders[2],
614                         },
615                         gasLeft:        2000,
616                         wantMatchedTxs: []*types.Tx{mock.MatchedTxs[2]},
617                 },
618                 {
619                         desc: "has two matched tx, and gas left is sufficient",
620                         initOrders: []*common.Order{
621                                 mock.Btc2EthOrders[0],
622                                 mock.Btc2EthOrders[1],
623                                 mock.Eth2BtcOrders[2],
624                         },
625                         gasLeft:        4000,
626                         wantMatchedTxs: []*types.Tx{mock.MatchedTxs[2], mock.MatchedTxs[3]},
627                 },
628                 {
629                         desc: "has multiple trade pairs, and gas left is sufficient",
630                         initOrders: []*common.Order{
631                                 mock.Btc2EthOrders[0],
632                                 mock.Btc2EthOrders[1],
633                                 mock.Eth2BtcOrders[2],
634                                 mock.Eos2EtcOrders[0],
635                                 mock.Etc2EosOrders[0],
636                         },
637                         gasLeft:        6000,
638                         wantMatchedTxs: []*types.Tx{mock.MatchedTxs[2], mock.MatchedTxs[3], mock.MatchedTxs[5]},
639                 },
640         }
641
642         for i, c := range cases {
643                 testDB := dbm.NewDB("testdb", "leveldb", "temp")
644                 store := database.NewLevelDBMovStore(testDB)
645                 if err := store.InitDBState(0, &bc.Hash{}); err != nil {
646                         t.Fatal(err)
647                 }
648
649                 if err := store.ProcessOrders(c.initOrders, nil, initBlockHeader); err != nil {
650                         t.Fatal(err)
651                 }
652
653                 movCore := &Core{movStore: store}
654                 gotMatchedTxs, err := movCore.BeforeProposalBlock(&types.Block{BlockHeader: types.BlockHeader{Height: 2}}, c.gasLeft, func() bool { return false })
655                 if err != nil {
656                         t.Fatal(err)
657                 }
658
659                 gotMatchedTxMap := make(map[string]interface{})
660                 for _, matchedTx := range gotMatchedTxs {
661                         gotMatchedTxMap[matchedTx.ID.String()] = nil
662                 }
663
664                 wantMatchedTxMap := make(map[string]interface{})
665                 for _, matchedTx := range c.wantMatchedTxs {
666                         wantMatchedTxMap[matchedTx.ID.String()] = nil
667                 }
668
669                 if !testutil.DeepEqual(gotMatchedTxMap, wantMatchedTxMap) {
670                         t.Errorf("#%d(%s):want matched tx(%v) is not equals got matched tx(%v)", i, c.desc, c.wantMatchedTxs, gotMatchedTxs)
671                 }
672
673                 testDB.Close()
674                 os.RemoveAll("temp")
675         }
676 }
677
678 func TestValidateMatchedTxSequence(t *testing.T) {
679         cases := []struct {
680                 desc         string
681                 initOrders   []*common.Order
682                 transactions []*Tx
683                 wantError    error
684         }{
685                 {
686                         desc:         "both db orders and transactions is empty",
687                         initOrders:   []*common.Order{},
688                         transactions: []*Tx{},
689                         wantError:    nil,
690                 },
691                 {
692                         desc:         "existing matched orders in db, and transactions is empty",
693                         initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
694                         transactions: []*Tx{},
695                         wantError:    nil,
696                 },
697                 {
698                         desc:         "db orders is empty, but transactions has matched tx",
699                         initOrders:   []*common.Order{},
700                         transactions: []*Tx{{rawTx: mock.MatchedTxs[1]}},
701                         wantError:    errNotMatchedOrder,
702                 },
703                 {
704                         desc:         "existing matched orders in db, and corresponding matched tx in transactions",
705                         initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
706                         transactions: []*Tx{{rawTx: mock.MatchedTxs[1]}},
707                         wantError:    nil,
708                 },
709                 {
710                         desc:         "package matched tx, one order from db, and the another order from transactions",
711                         initOrders:   []*common.Order{mock.Btc2EthOrders[0]},
712                         transactions: []*Tx{{rawTx: mock.Eth2BtcMakerTxs[0]}, {rawTx: mock.MatchedTxs[10]}},
713                         wantError:    nil,
714                 },
715                 {
716                         desc:         "two matched txs use the same orders",
717                         initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
718                         transactions: []*Tx{{rawTx: mock.MatchedTxs[1]}, {rawTx: mock.MatchedTxs[1]}},
719                         wantError:    errNotMatchedOrder,
720                 },
721                 {
722                         desc: "existing two matched orders in db, and only one corresponding matched tx in transactions",
723                         initOrders: []*common.Order{
724                                 mock.Btc2EthOrders[3], mock.Eth2BtcOrders[2],
725                                 mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0],
726                         },
727                         transactions: []*Tx{{rawTx: mock.MatchedTxs[8]}},
728                         wantError:    nil,
729                 },
730                 {
731                         desc: "existing two matched orders in db, and the sequence of match txs in incorrect",
732                         initOrders: []*common.Order{
733                                 mock.Btc2EthOrders[3], mock.Eth2BtcOrders[2],
734                                 mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0],
735                         },
736                         transactions: []*Tx{{rawTx: mock.MatchedTxs[1]}, {rawTx: mock.MatchedTxs[8]}},
737                         wantError:    errSpendOutputIDIsIncorrect,
738                 },
739                 {
740                         desc:         "matched tx and orders from packaged transactions",
741                         initOrders:   []*common.Order{},
742                         transactions: []*Tx{{rawTx: mock.Btc2EthMakerTxs[0]}, {rawTx: mock.Eth2BtcMakerTxs[1]}, {rawTx: mock.MatchedTxs[4]}},
743                         wantError:    nil,
744                 },
745                 {
746                         desc:         "package the matched tx first, then package match orders",
747                         initOrders:   []*common.Order{},
748                         transactions: []*Tx{{rawTx: mock.MatchedTxs[4]}, {rawTx: mock.Btc2EthMakerTxs[0]}, {rawTx: mock.Eth2BtcMakerTxs[1]}},
749                         wantError:    errNotMatchedOrder,
750                 },
751                 {
752                         desc:         "cancel order in transactions",
753                         initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
754                         transactions: []*Tx{{rawTx: mock.Btc2EthCancelTxs[0]}, {rawTx: mock.MatchedTxs[1]}},
755                         wantError:    errNotMatchedOrder,
756                 },
757                 {
758                         desc:         "package cancel order after match tx",
759                         initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0]},
760                         transactions: []*Tx{{rawTx: mock.MatchedTxs[1]}, {rawTx: mock.Btc2EthCancelTxs[0]}},
761                         wantError:    nil,
762                 },
763                 {
764                         desc: "package matched txs of different trade pairs",
765                         initOrders: []*common.Order{
766                                 mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0],
767                                 mock.Eos2EtcOrders[0], mock.Etc2EosOrders[0],
768                         },
769                         transactions: []*Tx{{rawTx: mock.MatchedTxs[1]}, {rawTx: mock.MatchedTxs[9]}},
770                         wantError:    nil,
771                 },
772                 {
773                         desc: "package matched txs of different trade pairs in different sequence",
774                         initOrders: []*common.Order{
775                                 mock.Btc2EthOrders[0], mock.Eth2BtcOrders[0],
776                                 mock.Eos2EtcOrders[0], mock.Etc2EosOrders[0],
777                         },
778                         transactions: []*Tx{{rawTx: mock.MatchedTxs[9]}, {rawTx: mock.MatchedTxs[1]}},
779                         wantError:    nil,
780                 },
781                 {
782                         desc:         "package partial matched tx from db orders, and the re-pending order continue to match",
783                         initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
784                         transactions: []*Tx{{rawTx: mock.MatchedTxs[2]}, {rawTx: mock.MatchedTxs[3]}},
785                         wantError:    nil,
786                 },
787                 {
788                         desc:         "cancel the re-pending order",
789                         initOrders:   []*common.Order{mock.Btc2EthOrders[0], mock.Btc2EthOrders[1], mock.Eth2BtcOrders[2]},
790                         transactions: []*Tx{{rawTx: mock.MatchedTxs[2]}, {rawTx: mock.Btc2EthCancelTxs[1]}, {rawTx: mock.MatchedTxs[3]}},
791                         wantError:    errNotMatchedOrder,
792                 },
793         }
794
795         for i, c := range cases {
796                 testDB := dbm.NewDB("testdb", "leveldb", "temp")
797                 store := database.NewLevelDBMovStore(testDB)
798                 if err := store.InitDBState(0, &bc.Hash{}); err != nil {
799                         t.Fatal(err)
800                 }
801
802                 if err := store.ProcessOrders(c.initOrders, nil, initBlockHeader); err != nil {
803                         t.Fatal(err)
804                 }
805
806                 movCore := &Core{movStore: store}
807                 if err := movCore.validateMatchedTxSequence(c.transactions); err != c.wantError {
808                         t.Errorf("#%d(%s):wanet error(%v), got error(%v)", i, c.desc, c.wantError, err)
809                 }
810
811                 testDB.Close()
812                 os.RemoveAll("temp")
813         }
814 }
815
816 type testFun func(movCore *Core, block *types.Block) error
817
818 func applyBlock(movCore *Core, block *types.Block) error {
819         return movCore.ApplyBlock(block)
820 }
821
822 func detachBlock(movCore *Core, block *types.Block) error {
823         return movCore.DetachBlock(block)
824 }
825
826 func queryAllOrders(store *database.LevelDBMovStore) []*common.Order {
827         var orders []*common.Order
828         tradePairIterator := database.NewTradePairIterator(store)
829         for tradePairIterator.HasNext() {
830                 orderIterator := database.NewOrderIterator(store, tradePairIterator.Next())
831                 for orderIterator.HasNext() {
832                         orders = append(orders, orderIterator.NextBatch()...)
833                 }
834         }
835         return orders
836 }
837
838 func ordersEquals(orders1 []*common.Order, orders2 []*common.Order) bool {
839         orderMap1 := make(map[string]*common.Order)
840         for _, order := range orders1 {
841                 orderMap1[order.Key()] = order
842         }
843
844         orderMap2 := make(map[string]*common.Order)
845         for _, order := range orders2 {
846                 orderMap2[order.Key()] = order
847         }
848         return testutil.DeepEqual(orderMap1, orderMap2)
849 }
850
851 func hashPtr(hash bc.Hash) *bc.Hash {
852         return &hash
853 }
854
855 func orderWithHeightAndTxIndex(order *common.Order, blockHeight, txIndex uint64) *common.Order {
856         order.BlockHeight = blockHeight
857         order.TxIndex = txIndex
858         return order
859 }