OSDN Git Service

fix match engine
[bytom/vapor.git] / application / mov / match / fee_strategy.go
1 package match
2
3 import (
4         "math"
5
6         "github.com/bytom/vapor/errors"
7         "github.com/bytom/vapor/protocol/bc"
8 )
9
10 var (
11         // ErrAmountOfFeeOutOfRange represent The fee charged is out of range
12         ErrAmountOfFeeOutOfRange = errors.New("amount of fee is out of range")
13 )
14
15 // AllocatedAssets represent reallocated assets after calculating fees
16 type AllocatedAssets struct {
17         Receives []*bc.AssetAmount
18         Refunds  RefundAssets
19         Fees     []*bc.AssetAmount
20 }
21
22 // RefundAssets represent alias for assetAmount array, because each transaction participant can be refunded multiple assets
23 type RefundAssets [][]*bc.AssetAmount
24
25 // Add used to add a refund to specify order
26 func (r RefundAssets) Add(index int, asset bc.AssetID, amount uint64) {
27         if index >= len(r) {
28                 return
29         }
30
31         var found bool
32         for _, assetAmount := range r[index] {
33                 if *assetAmount.AssetId == asset {
34                         assetAmount.Amount += amount
35                         found = true
36                         break
37                 }
38         }
39         if !found {
40                 r[index] = append(r[index], &bc.AssetAmount{AssetId: &asset, Amount: amount})
41         }
42 }
43
44 // FeeStrategy used to indicate how to charge a matching fee
45 type FeeStrategy interface {
46         // Allocate will allocate the price differential in matching transaction to the participants and the fee
47         // @param receiveAmounts the amount of assets that the participants in the matching transaction can received when no fee is considered
48         // @param priceDiffs price differential of matching transaction
49         // @return reallocated assets after calculating fees
50         Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount) *AllocatedAssets
51
52         // Validate verify that the fee charged for a matching transaction is correct
53         Validate(receiveAmounts []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error
54 }
55
56 // DefaultFeeStrategy represent the default fee charge strategy
57 type DefaultFeeStrategy struct {}
58
59 // NewDefaultFeeStrategy return a new instance of DefaultFeeStrategy
60 func NewDefaultFeeStrategy() *DefaultFeeStrategy {
61         return &DefaultFeeStrategy{}
62 }
63
64 // Allocate will allocate the price differential in matching transaction to the participants and the fee
65 func (d *DefaultFeeStrategy) Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount) *AllocatedAssets {
66         feeMap := make(map[bc.AssetID]uint64)
67         for _, priceDiff := range priceDiffs {
68                 feeMap[*priceDiff.AssetId] = priceDiff.Amount
69         }
70
71         var fees []*bc.AssetAmount
72         refunds := make([][]*bc.AssetAmount, len(receiveAmounts))
73         receives := make([]*bc.AssetAmount, len(receiveAmounts))
74
75         for i, receiveAmount := range receiveAmounts {
76                 amount := receiveAmount.Amount
77                 minFeeAmount := d.calcMinFeeAmount(amount)
78                 receives[i] = &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: amount - minFeeAmount}
79                 feeMap[*receiveAmount.AssetId] += minFeeAmount
80
81                 maxFeeAmount := d.calcMaxFeeAmount(amount)
82                 feeAmount, reminder := feeMap[*receiveAmount.AssetId], uint64(0)
83                 if feeAmount > maxFeeAmount {
84                         reminder = feeAmount - maxFeeAmount
85                         feeAmount = maxFeeAmount
86                 }
87
88                 fees = append(fees, &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: feeAmount})
89
90                 // There is the remaining amount after paying the handling fee, assign it evenly to participants in the transaction
91                 averageAmount := reminder / uint64(len(receiveAmounts))
92                 if averageAmount == 0 {
93                         averageAmount = 1
94                 }
95
96                 for j := 0; j < len(receiveAmounts) && reminder > 0; j++ {
97                         refundAmount := averageAmount
98                         if j == len(receiveAmounts)-1 {
99                                 refundAmount = reminder
100                         }
101                         refunds[j] = append(refunds[j], &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: refundAmount})
102                         reminder -= averageAmount
103                 }
104         }
105         return &AllocatedAssets{Receives: receives, Refunds: refunds, Fees: fees}
106 }
107
108 // Validate verify that the fee charged for a matching transaction is correct
109 func (d *DefaultFeeStrategy) Validate(receiveAmounts []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error {
110         for _, receiveAmount := range receiveAmounts {
111                 feeAmount := feeAmounts[*receiveAmount.AssetId]
112                 maxFeeAmount := d.calcMaxFeeAmount(receiveAmount.Amount)
113                 minFeeAmount := d.calcMinFeeAmount(receiveAmount.Amount)
114                 if feeAmount < minFeeAmount || feeAmount > maxFeeAmount {
115                         return ErrAmountOfFeeOutOfRange
116                 }
117         }
118         return nil
119 }
120
121 func (d *DefaultFeeStrategy) calcMinFeeAmount(amount uint64) uint64 {
122         return uint64(math.Ceil(float64(amount) / 1000))
123 }
124
125 func (d *DefaultFeeStrategy) calcMaxFeeAmount(amount uint64) uint64 {
126         return uint64(math.Ceil(float64(amount) * 0.05))
127 }