OSDN Git Service

4e5a0296546052f6ea0a0779797ed0e8eb537104
[bytom/vapor.git] / application / mov / match / engine.go
1 package match
2
3 import (
4         "math/big"
5
6         "github.com/bytom/vapor/application/mov/common"
7         "github.com/bytom/vapor/application/mov/contract"
8         "github.com/bytom/vapor/consensus/segwit"
9         "github.com/bytom/vapor/errors"
10         vprMath "github.com/bytom/vapor/math"
11         "github.com/bytom/vapor/protocol/bc"
12         "github.com/bytom/vapor/protocol/bc/types"
13         "github.com/bytom/vapor/protocol/vm"
14 )
15
16 // Engine is used to generate math transactions
17 type Engine struct {
18         orderBook     *OrderBook
19         feeStrategy   FeeStrategy
20         rewardProgram []byte
21 }
22
23 // NewEngine return a new Engine
24 func NewEngine(orderBook *OrderBook, feeStrategy FeeStrategy, rewardProgram []byte) *Engine {
25         return &Engine{orderBook: orderBook, feeStrategy: feeStrategy, rewardProgram: rewardProgram}
26 }
27
28 // HasMatchedTx check does the input trade pair can generate a match deal
29 func (e *Engine) HasMatchedTx(tradePairs ...*common.TradePair) bool {
30         if err := validateTradePairs(tradePairs); err != nil {
31                 return false
32         }
33
34         orders := e.orderBook.PeekOrders(tradePairs)
35         if len(orders) == 0 {
36                 return false
37         }
38
39         return IsMatched(orders)
40 }
41
42 // NextMatchedTx return the next matchable transaction by the specified trade pairs
43 // the size of trade pairs at least 2, and the sequence of trade pairs can form a loop
44 // for example, [assetA -> assetB, assetB -> assetC, assetC -> assetA]
45 func (e *Engine) NextMatchedTx(tradePairs ...*common.TradePair) (*types.Tx, error) {
46         if !e.HasMatchedTx(tradePairs...) {
47                 return nil, errors.New("the specified trade pairs can not be matched")
48         }
49
50         tx, err := e.buildMatchTx(sortOrders(e.orderBook.PeekOrders(tradePairs)))
51         if err != nil {
52                 return nil, err
53         }
54
55         for _, tradePair := range tradePairs {
56                 e.orderBook.PopOrder(tradePair)
57         }
58
59         if err := e.addPartialTradeOrder(tx); err != nil {
60                 return nil, err
61         }
62         return tx, nil
63 }
64
65 func (e *Engine) addMatchTxFeeOutput(txData *types.TxData, refunds RefundAssets, fees []*bc.AssetAmount) error {
66         for _, feeAmount := range fees {
67                 txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*feeAmount.AssetId, feeAmount.Amount, e.rewardProgram))
68         }
69
70         for i, refund := range refunds {
71                 // each trading participant may be refunded multiple assets
72                 for _, assetAmount := range refund {
73                         contractArgs, err := segwit.DecodeP2WMCProgram(txData.Inputs[i].ControlProgram())
74                         if err != nil {
75                                 return err
76                         }
77
78                         txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*assetAmount.AssetId, assetAmount.Amount, contractArgs.SellerProgram))
79                 }
80         }
81         return nil
82 }
83
84 func (e *Engine) addPartialTradeOrder(tx *types.Tx) error {
85         for i, output := range tx.Outputs {
86                 if !segwit.IsP2WMCScript(output.ControlProgram()) || output.AssetAmount().Amount == 0 {
87                         continue
88                 }
89
90                 order, err := common.NewOrderFromOutput(tx, i)
91                 if err != nil {
92                         return err
93                 }
94
95                 e.orderBook.AddOrder(order)
96         }
97         return nil
98 }
99
100 func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) {
101         txData := &types.TxData{Version: 1}
102         for _, order := range orders {
103                 input := types.NewSpendInput(nil, *order.Utxo.SourceID, *order.FromAssetID, order.Utxo.Amount, order.Utxo.SourcePos, order.Utxo.ControlProgram)
104                 txData.Inputs = append(txData.Inputs, input)
105         }
106
107         receivedAmounts, priceDiffs := CalcReceivedAmount(orders)
108         allocatedAssets := e.feeStrategy.Allocate(receivedAmounts, priceDiffs)
109         if err := addMatchTxOutput(txData, orders, receivedAmounts, allocatedAssets); err != nil {
110                 return nil, err
111         }
112
113         if err := e.addMatchTxFeeOutput(txData, allocatedAssets.Refunds, allocatedAssets.Fees); 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, orders []*common.Order, receivedAmounts []*bc.AssetAmount, allocatedAssets *AllocatedAssets) error {
127         for i, order := range orders {
128                 contractArgs, err := segwit.DecodeP2WMCProgram(order.Utxo.ControlProgram)
129                 if err != nil {
130                         return err
131                 }
132
133                 receivedAmount := receivedAmounts[i].Amount
134                 shouldPayAmount := calcShouldPayAmount(receivedAmount, contractArgs.RatioNumerator, contractArgs.RatioDenominator)
135
136                 exchangeAmount := order.Utxo.Amount - shouldPayAmount
137                 isPartialTrade := CalcRequestAmount(exchangeAmount, contractArgs.RatioNumerator, contractArgs.RatioDenominator) >= 1
138
139                 setMatchTxArguments(txData.Inputs[i], isPartialTrade, len(txData.Outputs), receivedAmount)
140                 txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.ToAssetID, allocatedAssets.Receives[i].Amount, contractArgs.SellerProgram))
141                 if isPartialTrade {
142                         txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.FromAssetID, exchangeAmount, order.Utxo.ControlProgram))
143                 } else if exchangeAmount > 0 {
144                         allocatedAssets.Refunds.Add(i, *order.FromAssetID, exchangeAmount)
145                 }
146         }
147         return nil
148 }
149
150 func calcOppositeIndex(size int, selfIdx int) int {
151         return (selfIdx + 1) % size
152 }
153
154 // CalcRequestAmount is from amount * numerator / ratioDenominator
155 func CalcRequestAmount(fromAmount uint64, ratioNumerator, ratioDenominator int64) uint64 {
156         res := big.NewInt(0).SetUint64(fromAmount)
157         res.Mul(res, big.NewInt(ratioNumerator)).Quo(res, big.NewInt(ratioDenominator))
158         if !res.IsUint64() {
159                 return 0
160         }
161         return res.Uint64()
162 }
163
164 func calcShouldPayAmount(receiveAmount uint64, ratioNumerator, ratioDenominator int64) uint64 {
165         res := big.NewInt(0).SetUint64(receiveAmount)
166         res.Mul(res, big.NewInt(ratioDenominator)).Quo(res, big.NewInt(ratioNumerator))
167         if !res.IsUint64() {
168                 return 0
169         }
170         return res.Uint64()
171 }
172
173 // CalcReceivedAmount return amount of assets received by each participant in the matching transaction and the price difference
174 func CalcReceivedAmount(orders []*common.Order) ([]*bc.AssetAmount, []*bc.AssetAmount) {
175         var receivedAmounts, priceDiffs, shouldPayAmounts []*bc.AssetAmount
176         for i, order := range orders {
177                 requestAmount := CalcRequestAmount(order.Utxo.Amount, order.RatioNumerator, order.RatioDenominator)
178                 oppositeOrder := orders[calcOppositeIndex(len(orders), i)]
179                 receiveAmount := vprMath.MinUint64(oppositeOrder.Utxo.Amount, requestAmount)
180                 shouldPayAmount := calcShouldPayAmount(receiveAmount, order.RatioNumerator, order.RatioDenominator)
181                 receivedAmounts = append(receivedAmounts, &bc.AssetAmount{AssetId: order.ToAssetID, Amount: receiveAmount})
182                 shouldPayAmounts = append(shouldPayAmounts, &bc.AssetAmount{AssetId: order.FromAssetID, Amount: shouldPayAmount})
183         }
184
185         for i, receivedAmount := range receivedAmounts {
186                 oppositeShouldPayAmount := shouldPayAmounts[calcOppositeIndex(len(orders), i)]
187                 if oppositeShouldPayAmount.Amount > receivedAmount.Amount {
188                         assetID := oppositeShouldPayAmount.AssetId
189                         amount := oppositeShouldPayAmount.Amount - receivedAmount.Amount
190                         priceDiffs = append(priceDiffs, &bc.AssetAmount{AssetId: assetID, Amount: amount})
191                 }
192         }
193         return receivedAmounts, priceDiffs
194 }
195
196 // IsMatched check does the orders can be exchange
197 func IsMatched(orders []*common.Order) bool {
198         sortedOrders := sortOrders(orders)
199         if len(sortedOrders) == 0 {
200                 return false
201         }
202
203         product := big.NewRat(1, 1)
204         for _, order := range orders {
205                 product.Mul(product, big.NewRat(order.RatioNumerator, order.RatioDenominator))
206         }
207         one := big.NewRat(1, 1)
208         return product.Cmp(one) <= 0
209 }
210
211 func setMatchTxArguments(txInput *types.TxInput, isPartialTrade bool, position int, receiveAmounts uint64) {
212         var arguments [][]byte
213         if isPartialTrade {
214                 arguments = [][]byte{vm.Int64Bytes(int64(receiveAmounts)), vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.PartialTradeClauseSelector)}
215         } else {
216                 arguments = [][]byte{vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.FullTradeClauseSelector)}
217         }
218         txInput.SetArguments(arguments)
219 }
220
221 func sortOrders(orders []*common.Order) []*common.Order {
222         if len(orders) == 0 {
223                 return nil
224         }
225
226         orderMap := make(map[bc.AssetID]*common.Order)
227         firstOrder := orders[0]
228         for i := 1; i < len(orders); i++ {
229                 orderMap[*orders[i].FromAssetID] = orders[i]
230         }
231
232         sortedOrders := []*common.Order{firstOrder}
233         for order := firstOrder; *order.ToAssetID != *firstOrder.FromAssetID; {
234                 nextOrder, ok := orderMap[*order.ToAssetID]
235                 if !ok {
236                         return nil
237                 }
238
239                 sortedOrders = append(sortedOrders, nextOrder)
240                 order = nextOrder
241         }
242         return sortedOrders
243 }
244
245 func validateTradePairs(tradePairs []*common.TradePair) error {
246         if len(tradePairs) < 2 {
247                 return errors.New("size of trade pairs at least 2")
248         }
249
250         assetMap := make(map[string]bool)
251         for _, tradePair := range tradePairs {
252                 assetMap[tradePair.FromAssetID.String()] = true
253                 if *tradePair.FromAssetID == *tradePair.ToAssetID {
254                         return errors.New("from asset id can't equal to asset id")
255                 }
256         }
257
258         for _, tradePair := range tradePairs {
259                 key := tradePair.ToAssetID.String()
260                 if _, ok := assetMap[key]; !ok {
261                         return errors.New("invalid trade pairs")
262                 }
263                 delete(assetMap, key)
264         }
265         return nil
266 }