OSDN Git Service

64ed4e8c0a8ad3652ed875f6a7c7478a60b00951
[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/bytom/vapor/application/mov/common"
9         "github.com/bytom/vapor/application/mov/contract"
10         "github.com/bytom/vapor/consensus/segwit"
11         "github.com/bytom/vapor/errors"
12         vprMath "github.com/bytom/vapor/math"
13         "github.com/bytom/vapor/protocol/bc"
14         "github.com/bytom/vapor/protocol/bc/types"
15         "github.com/bytom/vapor/protocol/vm"
16         "github.com/bytom/vapor/protocol/vm/vmutil"
17 )
18
19 // Engine is used to generate math transactions
20 type Engine struct {
21         orderBook   *OrderBook
22         maxFeeRate  float64
23         nodeProgram []byte
24 }
25
26 // NewEngine return a new Engine
27 func NewEngine(orderBook *OrderBook, maxFeeRate float64, nodeProgram []byte) *Engine {
28         return &Engine{orderBook: orderBook, 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.orderBook.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(sortOrders(e.orderBook.PeekOrders(tradePairs)))
54         if err != nil {
55                 return nil, err
56         }
57
58         for _, tradePair := range tradePairs {
59                 e.orderBook.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.orderBook.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 // MatchedTxFee is object to record the mov tx's fee information
149 type MatchedTxFee struct {
150         MaxFeeAmount int64
151         FeeAmount    int64
152 }
153
154 // CalcMatchedTxFee is used to calculate tx's MatchedTxFees
155 func CalcMatchedTxFee(txData *types.TxData, maxFeeRate float64) (map[bc.AssetID]*MatchedTxFee, error) {
156         assetFeeMap := make(map[bc.AssetID]*MatchedTxFee)
157         dealProgMaps := make(map[string]bool)
158
159         for _, input := range txData.Inputs {
160                 assetFeeMap[input.AssetID()] = &MatchedTxFee{FeeAmount: int64(input.AssetAmount().Amount)}
161                 contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
162                 if err != nil {
163                         return nil, err
164                 }
165
166                 dealProgMaps[hex.EncodeToString(contractArgs.SellerProgram)] = true
167         }
168
169         for _, input := range txData.Inputs {
170                 contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
171                 if err != nil {
172                         return nil, err
173                 }
174
175                 oppositeAmount := uint64(assetFeeMap[contractArgs.RequestedAsset].FeeAmount)
176                 receiveAmount := vprMath.MinUint64(CalcRequestAmount(input.Amount(), contractArgs), oppositeAmount)
177                 assetFeeMap[input.AssetID()].MaxFeeAmount = calcMaxFeeAmount(calcShouldPayAmount(receiveAmount, contractArgs), maxFeeRate)
178         }
179
180         for _, output := range txData.Outputs {
181                 assetAmount := output.AssetAmount()
182                 if _, ok := dealProgMaps[hex.EncodeToString(output.ControlProgram())]; ok || segwit.IsP2WMCScript(output.ControlProgram()) {
183                         assetFeeMap[*assetAmount.AssetId].FeeAmount -= int64(assetAmount.Amount)
184                         if assetFeeMap[*assetAmount.AssetId].FeeAmount <= 0 {
185                                 delete(assetFeeMap, *assetAmount.AssetId)
186                         }
187                 }
188         }
189         return assetFeeMap, nil
190 }
191
192 func addMatchTxOutput(txData *types.TxData, txInput *types.TxInput, order *common.Order, oppositeAmount uint64) error {
193         contractArgs, err := segwit.DecodeP2WMCProgram(order.Utxo.ControlProgram)
194         if err != nil {
195                 return err
196         }
197
198         requestAmount := CalcRequestAmount(order.Utxo.Amount, contractArgs)
199         receiveAmount := vprMath.MinUint64(requestAmount, oppositeAmount)
200         shouldPayAmount := calcShouldPayAmount(receiveAmount, contractArgs)
201         isPartialTrade := requestAmount > receiveAmount
202
203         setMatchTxArguments(txInput, isPartialTrade, len(txData.Outputs), receiveAmount)
204         txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.ToAssetID, receiveAmount, contractArgs.SellerProgram))
205         if isPartialTrade {
206                 txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.FromAssetID, order.Utxo.Amount-shouldPayAmount, order.Utxo.ControlProgram))
207         }
208         return nil
209 }
210
211 // CalcRequestAmount is from amount * numerator / ratioDenominator
212 func CalcRequestAmount(fromAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 {
213         res := big.NewInt(0).SetUint64(fromAmount)
214         res.Mul(res, big.NewInt(contractArg.RatioNumerator)).Quo(res, big.NewInt(contractArg.RatioDenominator))
215         if !res.IsUint64() {
216                 return 0
217         }
218         return res.Uint64()
219 }
220
221 func calcShouldPayAmount(receiveAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 {
222         res := big.NewInt(0).SetUint64(receiveAmount)
223         res.Mul(res, big.NewInt(contractArg.RatioDenominator)).Quo(res, big.NewInt(contractArg.RatioNumerator))
224         if !res.IsUint64() {
225                 return 0
226         }
227         return res.Uint64()
228 }
229
230 func calcMaxFeeAmount(shouldPayAmount uint64, maxFeeRate float64) int64 {
231         return int64(math.Ceil(float64(shouldPayAmount) * maxFeeRate))
232 }
233
234 func calcOppositeIndex(size int, selfIdx int) int {
235         return (selfIdx + 1) % size
236 }
237
238 // IsMatched check does the orders can be exchange
239 func IsMatched(orders []*common.Order) bool {
240         sortedOrders := sortOrders(orders)
241         if len(sortedOrders) == 0 {
242                 return false
243         }
244
245         rate := big.NewRat(sortedOrders[0].RatioDenominator, sortedOrders[0].RatioNumerator)
246         oppositeRate := big.NewRat(1, 1)
247         for i := 1; i < len(sortedOrders); i++ {
248                 oppositeRate.Mul(oppositeRate, big.NewRat(sortedOrders[i].RatioNumerator, sortedOrders[i].RatioDenominator))
249         }
250
251         return rate.Cmp(oppositeRate) >= 0
252 }
253
254 func setMatchTxArguments(txInput *types.TxInput, isPartialTrade bool, position int, receiveAmounts uint64) {
255         var arguments [][]byte
256         if isPartialTrade {
257                 arguments = [][]byte{vm.Int64Bytes(int64(receiveAmounts)), vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.PartialTradeClauseSelector)}
258         } else {
259                 arguments = [][]byte{vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.FullTradeClauseSelector)}
260         }
261         txInput.SetArguments(arguments)
262 }
263
264 func validateTradePairs(tradePairs []*common.TradePair) error {
265         if len(tradePairs) < 2 {
266                 return errors.New("size of trade pairs at least 2")
267         }
268
269         assetMap := make(map[string]bool)
270         for _, tradePair := range tradePairs {
271                 assetMap[tradePair.FromAssetID.String()] = true
272                 if *tradePair.FromAssetID == *tradePair.ToAssetID {
273                         return errors.New("from asset id can't equal to asset id")
274                 }
275         }
276
277         for _, tradePair := range tradePairs {
278                 key := tradePair.ToAssetID.String()
279                 if _, ok := assetMap[key]; !ok {
280                         return errors.New("invalid trade pairs")
281                 }
282                 delete(assetMap, key)
283         }
284         return nil
285 }
286
287 func sortOrders(orders []*common.Order) []*common.Order {
288         if len(orders) == 0 {
289                 return nil
290         }
291
292         orderMap := make(map[bc.AssetID]*common.Order)
293         firstOrder := orders[0]
294         for i := 1; i < len(orders); i++ {
295                 orderMap[*orders[i].FromAssetID] = orders[i]
296         }
297
298         sortedOrders := []*common.Order{firstOrder}
299         for order := firstOrder; *order.ToAssetID != *firstOrder.FromAssetID; {
300                 nextOrder, ok := orderMap[*order.ToAssetID]
301                 if !ok {
302                         return nil
303                 }
304
305                 sortedOrders = append(sortedOrders, nextOrder)
306                 order = nextOrder
307         }
308         return sortedOrders
309 }