OSDN Git Service

fix match engine (#522)
[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                 index = 0
29         }
30
31         for _, assetAmount := range r[index] {
32                 if *assetAmount.AssetId == asset {
33                         assetAmount.Amount += amount
34                         return
35                 }
36         }
37         r[index] = append(r[index], &bc.AssetAmount{AssetId: &asset, Amount: amount})
38 }
39
40 // FeeStrategy used to indicate how to charge a matching fee
41 type FeeStrategy interface {
42         // Allocate will allocate the price differential in matching transaction to the participants and the fee
43         // @param receiveAmounts the amount of assets that the participants in the matching transaction can received when no fee is considered
44         // @param priceDiffs price differential of matching transaction
45         // @return reallocated assets after calculating fees
46         Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount) *AllocatedAssets
47
48         // Validate verify that the fee charged for a matching transaction is correct
49         Validate(receiveAmounts []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error
50 }
51
52 // DefaultFeeStrategy represent the default fee charge strategy
53 type DefaultFeeStrategy struct {}
54
55 // NewDefaultFeeStrategy return a new instance of DefaultFeeStrategy
56 func NewDefaultFeeStrategy() *DefaultFeeStrategy {
57         return &DefaultFeeStrategy{}
58 }
59
60 // Allocate will allocate the price differential in matching transaction to the participants and the fee
61 func (d *DefaultFeeStrategy) Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount) *AllocatedAssets {
62         feeMap := make(map[bc.AssetID]uint64)
63         for _, priceDiff := range priceDiffs {
64                 feeMap[*priceDiff.AssetId] = priceDiff.Amount
65         }
66
67         var fees []*bc.AssetAmount
68         refunds := make([][]*bc.AssetAmount, len(receiveAmounts))
69         receives := make([]*bc.AssetAmount, len(receiveAmounts))
70
71         for i, receiveAmount := range receiveAmounts {
72                 amount := receiveAmount.Amount
73                 minFeeAmount := d.calcMinFeeAmount(amount)
74                 receives[i] = &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: amount - minFeeAmount}
75                 feeMap[*receiveAmount.AssetId] += minFeeAmount
76
77                 maxFeeAmount := d.calcMaxFeeAmount(amount)
78                 feeAmount, reminder := feeMap[*receiveAmount.AssetId], uint64(0)
79                 if feeAmount > maxFeeAmount {
80                         reminder = feeAmount - maxFeeAmount
81                         feeAmount = maxFeeAmount
82                 }
83
84                 fees = append(fees, &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: feeAmount})
85
86                 // There is the remaining amount after paying the handling fee, assign it evenly to participants in the transaction
87                 averageAmount := reminder / uint64(len(receiveAmounts))
88                 if averageAmount == 0 {
89                         averageAmount = 1
90                 }
91
92                 for j := 0; j < len(receiveAmounts) && reminder > 0; j++ {
93                         refundAmount := averageAmount
94                         if j == len(receiveAmounts)-1 {
95                                 refundAmount = reminder
96                         }
97                         refunds[j] = append(refunds[j], &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: refundAmount})
98                         reminder -= averageAmount
99                 }
100         }
101         return &AllocatedAssets{Receives: receives, Refunds: refunds, Fees: fees}
102 }
103
104 // Validate verify that the fee charged for a matching transaction is correct
105 func (d *DefaultFeeStrategy) Validate(receiveAmounts []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error {
106         for _, receiveAmount := range receiveAmounts {
107                 feeAmount := feeAmounts[*receiveAmount.AssetId]
108                 maxFeeAmount := d.calcMaxFeeAmount(receiveAmount.Amount)
109                 minFeeAmount := d.calcMinFeeAmount(receiveAmount.Amount)
110                 if feeAmount < minFeeAmount || feeAmount > maxFeeAmount {
111                         return ErrAmountOfFeeOutOfRange
112                 }
113         }
114         return nil
115 }
116
117 func (d *DefaultFeeStrategy) calcMinFeeAmount(amount uint64) uint64 {
118         return uint64(math.Ceil(float64(amount) / 1000))
119 }
120
121 func (d *DefaultFeeStrategy) calcMaxFeeAmount(amount uint64) uint64 {
122         return uint64(math.Ceil(float64(amount) * 0.05))
123 }