OSDN Git Service

fix mov infinite loop (#461)
[bytom/vapor.git] / application / mov / match / match.go
1 package match
2
3 import (
4         "encoding/hex"
5         "math"
6         "math/big"
7
8         "github.com/vapor/application/mov/common"
9         "github.com/vapor/application/mov/contract"
10         "github.com/vapor/consensus/segwit"
11         "github.com/vapor/errors"
12         vprMath "github.com/vapor/math"
13         "github.com/vapor/protocol/bc"
14         "github.com/vapor/protocol/bc/types"
15         "github.com/vapor/protocol/vm"
16         "github.com/vapor/protocol/vm/vmutil"
17 )
18
19 // Engine is used to generate math transactions
20 type Engine struct {
21         orderTable  *OrderTable
22         maxFeeRate  float64
23         nodeProgram []byte
24 }
25
26 // NewEngine return a new Engine
27 func NewEngine(orderTable *OrderTable, maxFeeRate float64, nodeProgram []byte) *Engine {
28         return &Engine{orderTable: orderTable, maxFeeRate: maxFeeRate, nodeProgram: nodeProgram}
29 }
30
31 // HasMatchedTx check does the input trade pair can generate a match deal
32 func (e *Engine) HasMatchedTx(tradePairs ...*common.TradePair) bool {
33         if err := validateTradePairs(tradePairs); err != nil {
34                 return false
35         }
36
37         orders := e.peekOrders(tradePairs)
38         if len(orders) == 0 {
39                 return false
40         }
41
42         return isMatched(orders)
43 }
44
45 // NextMatchedTx return the next matchable transaction by the specified trade pairs
46 // the size of trade pairs at least 2, and the sequence of trade pairs can form a loop
47 // for example, [assetA -> assetB, assetB -> assetC, assetC -> assetA]
48 func (e *Engine) NextMatchedTx(tradePairs ...*common.TradePair) (*types.Tx, error) {
49         if !e.HasMatchedTx(tradePairs...) {
50                 return nil, errors.New("the specified trade pairs can not be matched")
51         }
52
53         tx, err := e.buildMatchTx(e.peekOrders(tradePairs))
54         if err != nil {
55                 return nil, err
56         }
57
58         for _, tradePair := range tradePairs {
59                 e.orderTable.PopOrder(tradePair)
60         }
61
62         if err := e.addPartialTradeOrder(tx); err != nil {
63                 return nil, err
64         }
65         return tx, nil
66 }
67
68 func (e *Engine) addMatchTxFeeOutput(txData *types.TxData) error {
69         txFee, err := CalcMatchedTxFee(txData, e.maxFeeRate)
70         if err != nil {
71                 return err
72         }
73
74         for assetID, matchTxFee := range txFee {
75                 feeAmount, reminder := matchTxFee.FeeAmount, int64(0)
76                 if matchTxFee.FeeAmount > matchTxFee.MaxFeeAmount {
77                         feeAmount = matchTxFee.MaxFeeAmount
78                         reminder = matchTxFee.FeeAmount - matchTxFee.MaxFeeAmount
79                 }
80                 txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(assetID, uint64(feeAmount), e.nodeProgram))
81
82                 // There is the remaining amount after paying the handling fee, assign it evenly to participants in the transaction
83                 averageAmount := reminder / int64(len(txData.Inputs))
84                 if averageAmount == 0 {
85                         averageAmount = 1
86                 }
87
88                 for i := 0; i < len(txData.Inputs) && reminder > 0; i++ {
89                         contractArgs, err := segwit.DecodeP2WMCProgram(txData.Inputs[i].ControlProgram())
90                         if err != nil {
91                                 return err
92                         }
93
94                         if i == len(txData.Inputs)-1 {
95                                 txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(assetID, uint64(reminder), contractArgs.SellerProgram))
96                         } else {
97                                 txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(assetID, uint64(averageAmount), contractArgs.SellerProgram))
98                         }
99                         reminder -= averageAmount
100                 }
101         }
102         return nil
103 }
104
105 func (e *Engine) addPartialTradeOrder(tx *types.Tx) error {
106         for i, output := range tx.Outputs {
107                 if !segwit.IsP2WMCScript(output.ControlProgram()) {
108                         continue
109                 }
110
111                 order, err := common.NewOrderFromOutput(tx, i)
112                 if err != nil {
113                         return err
114                 }
115
116                 if err := e.orderTable.AddOrder(order); err != nil {
117                         return err
118                 }
119         }
120         return nil
121 }
122
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)
157         }
158         return orders
159 }
160
161 // MatchedTxFee is object to record the mov tx's fee information
162 type MatchedTxFee struct {
163         MaxFeeAmount int64
164         FeeAmount    int64
165 }
166
167 // CalcMatchedTxFee is used to calculate tx's MatchedTxFees
168 func CalcMatchedTxFee(txData *types.TxData, maxFeeRate float64) (map[bc.AssetID]*MatchedTxFee, error) {
169         assetFeeMap := make(map[bc.AssetID]*MatchedTxFee)
170         dealProgMaps := make(map[string]bool)
171
172         for _, input := range txData.Inputs {
173                 assetFeeMap[input.AssetID()] = &MatchedTxFee{FeeAmount: int64(input.AssetAmount().Amount)}
174                 contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
175                 if err != nil {
176                         return nil, err
177                 }
178
179                 dealProgMaps[hex.EncodeToString(contractArgs.SellerProgram)] = true
180         }
181
182         for _, input := range txData.Inputs {
183                 contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
184                 if err != nil {
185                         return nil, err
186                 }
187
188                 oppositeAmount := uint64(assetFeeMap[contractArgs.RequestedAsset].FeeAmount)
189                 receiveAmount := vprMath.MinUint64(CalcRequestAmount(input.Amount(), contractArgs), oppositeAmount)
190                 assetFeeMap[input.AssetID()].MaxFeeAmount = calcMaxFeeAmount(calcShouldPayAmount(receiveAmount, contractArgs), maxFeeRate)
191         }
192
193         for _, output := range txData.Outputs {
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)
199                         }
200                 }
201         }
202         return assetFeeMap, nil
203 }
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
224 func CalcRequestAmount(fromAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 {
225         res := big.NewInt(0).SetUint64(fromAmount)
226         res.Mul(res, big.NewInt(contractArg.RatioNumerator)).Quo(res, big.NewInt(contractArg.RatioDenominator))
227         if !res.IsUint64() {
228                 return 0
229         }
230         return res.Uint64()
231 }
232
233 func calcShouldPayAmount(receiveAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 {
234         res := big.NewInt(0).SetUint64(receiveAmount)
235         res.Mul(res, big.NewInt(contractArg.RatioDenominator)).Quo(res, big.NewInt(contractArg.RatioNumerator))
236         if !res.IsUint64() {
237                 return 0
238         }
239         return res.Uint64()
240 }
241
242 func calcMaxFeeAmount(shouldPayAmount uint64, maxFeeRate float64) int64 {
243         return int64(math.Ceil(float64(shouldPayAmount) * maxFeeRate))
244 }
245
246 func calcOppositeIndex(size int, selfIdx int) int {
247         return (selfIdx + 1) % size
248 }
249
250 func isMatched(orders []*common.Order) bool {
251         for i, order := range orders {
252                 if oppositeOrder := orders[calcOppositeIndex(len(orders), i)]; 1/order.Rate < oppositeOrder.Rate {
253                         return false
254                 }
255         }
256         return true
257 }
258
259 func setMatchTxArguments(txInput *types.TxInput, isPartialTrade bool, position int, receiveAmounts uint64) {
260         var arguments [][]byte
261         if isPartialTrade {
262                 arguments = [][]byte{vm.Int64Bytes(int64(receiveAmounts)), vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.PartialTradeClauseSelector)}
263         } else {
264                 arguments = [][]byte{vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.FullTradeClauseSelector)}
265         }
266         txInput.SetArguments(arguments)
267 }
268
269 func validateTradePairs(tradePairs []*common.TradePair) error {
270         if len(tradePairs) < 2 {
271                 return errors.New("size of trade pairs at least 2")
272         }
273
274         for i, tradePair := range tradePairs {
275                 oppositeTradePair := tradePairs[calcOppositeIndex(len(tradePairs), i)]
276                 if *tradePair.ToAssetID != *oppositeTradePair.FromAssetID {
277                         return errors.New("specified trade pairs is invalid")
278                 }
279         }
280         return nil
281 }