OSDN Git Service

fix match
[bytom/vapor.git] / application / mov / match / match.go
1 package match
2
3 import (
4         "encoding/hex"
5         "math"
6
7         "github.com/vapor/application/mov/common"
8         "github.com/vapor/application/mov/contract"
9         "github.com/vapor/consensus/segwit"
10         "github.com/vapor/errors"
11         vprMath "github.com/vapor/math"
12         "github.com/vapor/protocol/bc"
13         "github.com/vapor/protocol/bc/types"
14         "github.com/vapor/protocol/vm"
15         "github.com/vapor/protocol/vm/vmutil"
16 )
17
18 type Engine struct {
19         orderTable  *OrderTable
20         maxFeeRate  float64
21         nodeProgram []byte
22 }
23
24 func NewEngine(orderTable *OrderTable, maxFeeRate float64, nodeProgram []byte) *Engine {
25         return &Engine{orderTable: orderTable, maxFeeRate: maxFeeRate, nodeProgram: nodeProgram}
26 }
27
28 func (e *Engine) HasMatchedTx(tradePairs ...*common.TradePair) bool {
29         if err := validateTradePairs(tradePairs); err != nil {
30                 return false
31         }
32
33         orders := e.peekOrders(tradePairs)
34         if len(orders) == 0 {
35                 return false
36         }
37
38         return isMatched(orders)
39 }
40
41 // NextMatchedTx return the next matchable transaction by the specified trade pairs
42 // the size of trade pairs at least 2, and the sequence of trade pairs can form a loop
43 // for example, [assetA -> assetB, assetB -> assetC, assetC -> assetA]
44 func (e *Engine) NextMatchedTx(tradePairs ...*common.TradePair) (*types.Tx, error) {
45         if err := validateTradePairs(tradePairs); err != nil {
46                 return nil, err
47         }
48
49         orders := e.peekOrders(tradePairs)
50         if len(orders) == 0 {
51                 return nil, errors.New("no order for the specified trade pair in the order table")
52         }
53
54         if !isMatched(orders) {
55                 return nil, errors.New("the specified trade pairs can not be matched")
56         }
57
58         tx, err := e.buildMatchTx(orders)
59         if err != nil {
60                 return nil, err
61         }
62
63         for _, tradePair := range tradePairs {
64                 e.orderTable.PopOrder(tradePair)
65         }
66
67         if err := addPartialTradeOrder(tx, e.orderTable); err != nil {
68                 return nil, err
69         }
70         return tx, nil
71 }
72
73 func (e *Engine) peekOrders(tradePairs []*common.TradePair) []*common.Order {
74         var orders []*common.Order
75         for _, tradePair := range tradePairs {
76                 order := e.orderTable.PeekOrder(tradePair)
77                 if order == nil {
78                         return nil
79                 }
80
81                 orders = append(orders, order)
82         }
83         return orders
84 }
85
86 func validateTradePairs(tradePairs []*common.TradePair) error {
87         if len(tradePairs) < 2 {
88                 return errors.New("size of trade pairs at least 2")
89         }
90
91         for i, tradePair := range tradePairs {
92                 oppositeTradePair := tradePairs[getOppositeIndex(len(tradePairs), i)]
93                 if *tradePair.ToAssetID != *oppositeTradePair.FromAssetID {
94                         return errors.New("specified trade pairs is invalid")
95                 }
96         }
97         return nil
98 }
99
100 func isMatched(orders []*common.Order) bool {
101         for i, order := range orders {
102                 opposisteOrder := orders[getOppositeIndex(len(orders), i)]
103                 if 1 / order.Rate < opposisteOrder.Rate {
104                         return false
105                 }
106         }
107         return true
108 }
109
110 func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) {
111         txData := &types.TxData{Version: 1}
112         for i, order := range orders {
113                 input := types.NewSpendInput(nil, *order.Utxo.SourceID, *order.FromAssetID, order.Utxo.Amount, order.Utxo.SourcePos, order.Utxo.ControlProgram)
114                 txData.Inputs = append(txData.Inputs, input)
115
116                 oppositeOrder := orders[getOppositeIndex(len(orders), i)]
117                 if err := addMatchTxOutput(txData, input, order, oppositeOrder.Utxo.Amount); err != nil {
118                         return nil, err
119                 }
120         }
121
122         if err := e.addMatchTxFeeOutput(txData); err != nil {
123                 return nil, err
124         }
125
126         byteData, err := txData.MarshalText()
127         if err != nil {
128                 return nil, err
129         }
130
131         txData.SerializedSize = uint64(len(byteData))
132         return types.NewTx(*txData), nil
133 }
134
135 func addMatchTxOutput(txData *types.TxData, txInput *types.TxInput, order *common.Order, oppositeAmount uint64) error {
136         contractArgs, err := segwit.DecodeP2WMCProgram(order.Utxo.ControlProgram)
137         if err != nil {
138                 return err
139         }
140
141         requestAmount := calcRequestAmount(order.Utxo.Amount, contractArgs)
142         receiveAmount := vprMath.MinUint64(requestAmount, oppositeAmount)
143         shouldPayAmount := CalcShouldPayAmount(receiveAmount, contractArgs)
144         isPartialTrade := requestAmount > receiveAmount
145
146         setMatchTxArguments(txInput, isPartialTrade, len(txData.Outputs), receiveAmount)
147         txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.ToAssetID, receiveAmount, contractArgs.SellerProgram))
148         if isPartialTrade {
149                 txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.FromAssetID, order.Utxo.Amount-shouldPayAmount, order.Utxo.ControlProgram))
150         }
151         return nil
152 }
153
154 func (e *Engine) addMatchTxFeeOutput(txData *types.TxData) error {
155         txFee, err := CalcMatchedTxFee(txData, e.maxFeeRate)
156         if err != nil {
157                 return err
158         }
159
160         for feeAssetID, amount := range txFee {
161                 var reminder int64 = 0
162                 feeAmount := amount.FeeAmount
163                 if amount.FeeAmount > amount.MaxFeeAmount {
164                         feeAmount = amount.MaxFeeAmount
165                         reminder = amount.FeeAmount - amount.MaxFeeAmount
166                 }
167                 txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(feeAssetID, uint64(feeAmount), e.nodeProgram))
168
169                 // There is the remaining amount after paying the handling fee, assign it evenly to participants in the transaction
170                 averageAmount := reminder / int64(len(txData.Inputs))
171                 if averageAmount == 0 {
172                         averageAmount = 1
173                 }
174                 for i := 0; i < len(txData.Inputs) && reminder > 0; i++ {
175                         contractArgs, err := segwit.DecodeP2WMCProgram(txData.Inputs[i].ControlProgram())
176                         if err != nil {
177                                 return err
178                         }
179
180                         if i == len(txData.Inputs)-1 {
181                                 txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(feeAssetID, uint64(reminder), contractArgs.SellerProgram))
182                         } else {
183                                 txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(feeAssetID, uint64(averageAmount), contractArgs.SellerProgram))
184                         }
185                         reminder -= averageAmount
186                 }
187         }
188         return nil
189 }
190
191 func setMatchTxArguments(txInput *types.TxInput, isPartialTrade bool, position int, receiveAmounts uint64) {
192         var arguments [][]byte
193         if isPartialTrade {
194                 arguments = [][]byte{vm.Int64Bytes(int64(receiveAmounts)), vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.PartialTradeClauseSelector)}
195         } else {
196                 arguments = [][]byte{vm.Int64Bytes(int64(position)), vm.Int64Bytes(contract.FullTradeClauseSelector)}
197         }
198         txInput.SetArguments(arguments)
199 }
200
201 func addPartialTradeOrder(tx *types.Tx, orderTable *OrderTable) error {
202         for i, output := range tx.Outputs {
203                 if !segwit.IsP2WMCScript(output.ControlProgram()) {
204                         continue
205                 }
206
207                 order, err := common.NewOrderFromOutput(tx, i)
208                 if err != nil {
209                         return err
210                 }
211
212                 if err := orderTable.AddOrder(order); err != nil {
213                         return err
214                 }
215         }
216         return nil
217 }
218
219 func getOppositeIndex(size int, selfIdx int) int {
220         oppositeIdx := selfIdx + 1
221         if selfIdx >= size-1 {
222                 oppositeIdx = 0
223         }
224         return oppositeIdx
225 }
226
227 type MatchedTxFee struct {
228         MaxFeeAmount int64
229         FeeAmount    int64
230 }
231
232 func CalcMatchedTxFee(txData *types.TxData, maxFeeRate float64) (map[bc.AssetID]*MatchedTxFee, error) {
233         assetFeeMap := make(map[bc.AssetID]*MatchedTxFee)
234         sellerProgramMap := make(map[string]bool)
235         assetInputMap := make(map[bc.AssetID]uint64)
236
237         for _, input := range txData.Inputs {
238                 assetFeeMap[input.AssetID()] = &MatchedTxFee{}
239                 assetFeeMap[input.AssetID()].FeeAmount += int64(input.AssetAmount().Amount)
240                 contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
241                 if err != nil {
242                         return nil, err
243                 }
244
245                 sellerProgramMap[hex.EncodeToString(contractArgs.SellerProgram)] = true
246                 assetInputMap[input.AssetID()] = input.Amount()
247         }
248
249         for _, input := range txData.Inputs {
250                 contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
251                 if err != nil {
252                         return nil, err
253                 }
254
255                 oppositeAmount := assetInputMap[contractArgs.RequestedAsset]
256                 receiveAmount := vprMath.MinUint64(calcRequestAmount(input.Amount(), contractArgs), oppositeAmount)
257                 assetFeeMap[input.AssetID()].MaxFeeAmount = CalcMaxFeeAmount(CalcShouldPayAmount(receiveAmount, contractArgs), maxFeeRate)
258         }
259
260         for _, output := range txData.Outputs {
261                 // minus the amount of the re-order
262                 if segwit.IsP2WMCScript(output.ControlProgram()) {
263                         assetFeeMap[*output.AssetAmount().AssetId].FeeAmount -= int64(output.AssetAmount().Amount)
264                 }
265                 // minus the amount of seller's receiving output
266                 if _, ok := sellerProgramMap[hex.EncodeToString(output.ControlProgram())]; ok {
267                         assetID := *output.AssetAmount().AssetId
268                         fee, ok := assetFeeMap[assetID]
269                         if !ok {
270                                 continue
271                         }
272                         fee.FeeAmount -= int64(output.AssetAmount().Amount)
273                         if fee.FeeAmount == 0 {
274                                 delete(assetFeeMap, assetID)
275                         }
276                 }
277         }
278         return assetFeeMap, nil
279 }
280
281 func calcRequestAmount(fromAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 {
282         return uint64(int64(fromAmount) * contractArg.RatioNumerator / contractArg.RatioDenominator)
283 }
284
285 func CalcShouldPayAmount(receiveAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 {
286         return uint64(math.Floor(float64(receiveAmount) * float64(contractArg.RatioDenominator) / float64(contractArg.RatioNumerator)))
287 }
288
289 func CalcMaxFeeAmount(shouldPayAmount uint64, maxFeeRate float64) int64 {
290         return int64(math.Ceil(float64(shouldPayAmount) * maxFeeRate))
291 }