OSDN Git Service

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