OSDN Git Service

77de1113b78835534d5e231704e314a22b47482a
[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.Receives); 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, deductFeeReceives []*bc.AssetAmount) 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                 requestAmount := CalcRequestAmount(order.Utxo.Amount, contractArgs.RatioNumerator, contractArgs.RatioDenominator)
134                 receivedAmount := receivedAmounts[i].Amount
135                 shouldPayAmount := calcShouldPayAmount(receivedAmount, contractArgs.RatioNumerator, contractArgs.RatioDenominator)
136                 isPartialTrade := requestAmount > receivedAmount
137
138                 setMatchTxArguments(txData.Inputs[i], isPartialTrade, len(txData.Outputs), receivedAmounts[i].Amount)
139                 txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.ToAssetID, deductFeeReceives[i].Amount, contractArgs.SellerProgram))
140                 if isPartialTrade {
141                         txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.FromAssetID, order.Utxo.Amount-shouldPayAmount, order.Utxo.ControlProgram))
142                 }
143         }
144         return nil
145 }
146
147 func calcOppositeIndex(size int, selfIdx int) int {
148         return (selfIdx + 1) % size
149 }
150
151 // CalcRequestAmount is from amount * numerator / ratioDenominator
152 func CalcRequestAmount(fromAmount uint64, ratioNumerator, ratioDenominator int64) uint64 {
153         res := big.NewInt(0).SetUint64(fromAmount)
154         res.Mul(res, big.NewInt(ratioNumerator)).Quo(res, big.NewInt(ratioDenominator))
155         if !res.IsUint64() {
156                 return 0
157         }
158         return res.Uint64()
159 }
160
161 func calcShouldPayAmount(receiveAmount uint64, ratioNumerator, ratioDenominator int64) uint64 {
162         res := big.NewInt(0).SetUint64(receiveAmount)
163         res.Mul(res, big.NewInt(ratioDenominator)).Quo(res, big.NewInt(ratioNumerator))
164         if !res.IsUint64() {
165                 return 0
166         }
167         return res.Uint64()
168 }
169
170 // CalcReceivedAmount return amount of assets received by each participant in the matching transaction and the price difference
171 func CalcReceivedAmount(orders []*common.Order) ([]*bc.AssetAmount, []*bc.AssetAmount) {
172         var receivedAmounts, priceDiffs, shouldPayAmounts []*bc.AssetAmount
173         for i, order := range orders {
174                 requestAmount := CalcRequestAmount(order.Utxo.Amount, order.RatioNumerator, order.RatioDenominator)
175                 oppositeOrder := orders[calcOppositeIndex(len(orders), i)]
176                 receiveAmount := vprMath.MinUint64(oppositeOrder.Utxo.Amount, requestAmount)
177                 shouldPayAmount := calcShouldPayAmount(receiveAmount, order.RatioNumerator, order.RatioDenominator)
178                 receivedAmounts = append(receivedAmounts, &bc.AssetAmount{AssetId: order.ToAssetID, Amount: receiveAmount})
179                 shouldPayAmounts = append(shouldPayAmounts, &bc.AssetAmount{AssetId: order.FromAssetID, Amount: shouldPayAmount})
180         }
181
182         for i, receivedAmount := range receivedAmounts {
183                 oppositeShouldPayAmount := shouldPayAmounts[calcOppositeIndex(len(orders), i)]
184                 if oppositeShouldPayAmount.Amount > receivedAmount.Amount {
185                         assetID := oppositeShouldPayAmount.AssetId
186                         amount := oppositeShouldPayAmount.Amount - receivedAmount.Amount
187                         priceDiffs = append(priceDiffs, &bc.AssetAmount{AssetId: assetID, Amount: amount})
188                 }
189         }
190         return receivedAmounts, priceDiffs
191 }
192
193 // IsMatched check does the orders can be exchange
194 func IsMatched(orders []*common.Order) bool {
195         sortedOrders := sortOrders(orders)
196         if len(sortedOrders) == 0 {
197                 return false
198         }
199
200         product := big.NewRat(1, 1)
201         for _, order := range orders {
202                 product.Mul(product, big.NewRat(order.RatioNumerator, order.RatioDenominator))
203         }
204         one := big.NewRat(1, 1)
205         return product.Cmp(one) <= 0
206 }
207
208 func setMatchTxArguments(txInput *types.TxInput, isPartialTrade bool, position int, receiveAmounts uint64) {
209         var arguments [][]byte
210         if isPartialTrade {
211                 arguments = [][]byte{vm.Int64Bytes(int64(receiveAmounts)), vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.PartialTradeClauseSelector)}
212         } else {
213                 arguments = [][]byte{vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.FullTradeClauseSelector)}
214         }
215         txInput.SetArguments(arguments)
216 }
217
218 func sortOrders(orders []*common.Order) []*common.Order {
219         if len(orders) == 0 {
220                 return nil
221         }
222
223         orderMap := make(map[bc.AssetID]*common.Order)
224         firstOrder := orders[0]
225         for i := 1; i < len(orders); i++ {
226                 orderMap[*orders[i].FromAssetID] = orders[i]
227         }
228
229         sortedOrders := []*common.Order{firstOrder}
230         for order := firstOrder; *order.ToAssetID != *firstOrder.FromAssetID; {
231                 nextOrder, ok := orderMap[*order.ToAssetID]
232                 if !ok {
233                         return nil
234                 }
235
236                 sortedOrders = append(sortedOrders, nextOrder)
237                 order = nextOrder
238         }
239         return sortedOrders
240 }
241
242 func validateTradePairs(tradePairs []*common.TradePair) error {
243         if len(tradePairs) < 2 {
244                 return errors.New("size of trade pairs at least 2")
245         }
246
247         assetMap := make(map[string]bool)
248         for _, tradePair := range tradePairs {
249                 assetMap[tradePair.FromAssetID.String()] = true
250                 if *tradePair.FromAssetID == *tradePair.ToAssetID {
251                         return errors.New("from asset id can't equal to asset id")
252                 }
253         }
254
255         for _, tradePair := range tradePairs {
256                 key := tradePair.ToAssetID.String()
257                 if _, ok := assetMap[key]; !ok {
258                         return errors.New("invalid trade pairs")
259                 }
260                 delete(assetMap, key)
261         }
262         return nil
263 }