OSDN Git Service

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