OSDN Git Service

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