6 "github.com/bytom/vapor/errors"
7 "github.com/bytom/vapor/protocol/bc"
11 // ErrAmountOfFeeOutOfRange represent The fee charged is out of range
12 ErrAmountOfFeeOutOfRange = errors.New("amount of fee is out of range")
15 // AllocatedAssets represent reallocated assets after calculating fees
16 type AllocatedAssets struct {
17 Receives []*bc.AssetAmount
18 Refunds []RefundAssets
19 Fees []*bc.AssetAmount
22 // RefundAssets represent alias for assetAmount array, because each transaction participant can be refunded multiple assets
23 type RefundAssets []*bc.AssetAmount
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
33 // Validate verify that the fee charged for a matching transaction is correct
34 Validate(receiveAmounts []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error
37 // DefaultFeeStrategy represent the default fee charge strategy
38 type DefaultFeeStrategy struct {
42 // NewDefaultFeeStrategy return a new instance of DefaultFeeStrategy
43 func NewDefaultFeeStrategy(maxFeeRate float64) *DefaultFeeStrategy {
44 return &DefaultFeeStrategy{maxFeeRate: maxFeeRate}
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
54 var fees []*bc.AssetAmount
55 refunds := make([]RefundAssets, len(receiveAmounts))
56 receives := make([]*bc.AssetAmount, len(receiveAmounts))
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
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
72 fees = append(fees, &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: feeAmount})
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 {
81 for j := 0; j < len(receiveAmounts) && reminder > 0; j++ {
82 refundAmount := averageAmount
83 if j == len(receiveAmounts)-1 {
84 refundAmount = reminder
86 refunds[j] = append(refunds[j], &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: refundAmount})
87 reminder -= averageAmount
90 return &AllocatedAssets{Receives: receives, Refunds: refunds, Fees: fees}
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
106 func calcMinFeeAmount(amount uint64) uint64 {
107 return uint64(math.Ceil(float64(amount) / 1000))
110 func calcMaxFeeAmount(amount uint64, maxFeeRate float64) uint64 {
111 return uint64(math.Ceil(float64(amount) * maxFeeRate))