--- /dev/null
+package match
+
+import (
+ "math"
+
+ "github.com/bytom/vapor/errors"
+ "github.com/bytom/vapor/protocol/bc"
+)
+
+var (
+ // ErrAmountOfFeeOutOfRange represent The fee charged is out of range
+ ErrAmountOfFeeOutOfRange = errors.New("amount of fee is out of range")
+)
+
+// AllocatedAssets represent reallocated assets after calculating fees
+type AllocatedAssets struct {
+ Receives []*bc.AssetAmount
+ Refunds []RefundAssets
+ Fees []*bc.AssetAmount
+}
+
+// RefundAssets represent alias for assetAmount array, because each transaction participant can be refunded multiple assets
+type RefundAssets []*bc.AssetAmount
+
+// FeeStrategy used to indicate how to charge a matching fee
+type FeeStrategy interface {
+ // Allocate will allocate the price differential in matching transaction to the participants and the fee
+ // @param receiveAmounts the amount of assets that the participants in the matching transaction can received when no fee is considered
+ // @param priceDiffs price differential of matching transaction
+ // @return reallocated assets after calculating fees
+ Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount) *AllocatedAssets
+
+ // Validate verify that the fee charged for a matching transaction is correct
+ Validate(receiveAmounts []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error
+}
+
+// DefaultFeeStrategy represent the default fee charge strategy
+type DefaultFeeStrategy struct {}
+
+// NewDefaultFeeStrategy return a new instance of DefaultFeeStrategy
+func NewDefaultFeeStrategy() *DefaultFeeStrategy {
+ return &DefaultFeeStrategy{}
+}
+
+// Allocate will allocate the price differential in matching transaction to the participants and the fee
+func (d *DefaultFeeStrategy) Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount) *AllocatedAssets {
+ feeMap := make(map[bc.AssetID]uint64)
+ for _, priceDiff := range priceDiffs {
+ feeMap[*priceDiff.AssetId] = priceDiff.Amount
+ }
+
+ var fees []*bc.AssetAmount
+ refunds := make([]RefundAssets, len(receiveAmounts))
+ receives := make([]*bc.AssetAmount, len(receiveAmounts))
+
+ for i, receiveAmount := range receiveAmounts {
+ amount := receiveAmount.Amount
+ minFeeAmount := d.calcMinFeeAmount(amount)
+ receives[i] = &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: amount - minFeeAmount}
+ feeMap[*receiveAmount.AssetId] += minFeeAmount
+
+ maxFeeAmount := d.calcMaxFeeAmount(amount)
+ feeAmount, reminder := feeMap[*receiveAmount.AssetId], uint64(0)
+ if feeAmount > maxFeeAmount {
+ reminder = feeAmount - maxFeeAmount
+ feeAmount = maxFeeAmount
+ }
+
+ fees = append(fees, &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: feeAmount})
+
+ // There is the remaining amount after paying the handling fee, assign it evenly to participants in the transaction
+ averageAmount := reminder / uint64(len(receiveAmounts))
+ if averageAmount == 0 {
+ averageAmount = 1
+ }
+
+ for j := 0; j < len(receiveAmounts) && reminder > 0; j++ {
+ refundAmount := averageAmount
+ if j == len(receiveAmounts)-1 {
+ refundAmount = reminder
+ }
+ refunds[j] = append(refunds[j], &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: refundAmount})
+ reminder -= averageAmount
+ }
+ }
+ return &AllocatedAssets{Receives: receives, Refunds: refunds, Fees: fees}
+}
+
+// Validate verify that the fee charged for a matching transaction is correct
+func (d *DefaultFeeStrategy) Validate(receiveAmounts []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error {
+ for _, receiveAmount := range receiveAmounts {
+ feeAmount := feeAmounts[*receiveAmount.AssetId]
+ maxFeeAmount := d.calcMaxFeeAmount(receiveAmount.Amount)
+ minFeeAmount := d.calcMinFeeAmount(receiveAmount.Amount)
+ if feeAmount < minFeeAmount || feeAmount > maxFeeAmount {
+ return ErrAmountOfFeeOutOfRange
+ }
+ }
+ return nil
+}
+
+func (d *DefaultFeeStrategy) calcMinFeeAmount(amount uint64) uint64 {
+ return uint64(math.Ceil(float64(amount) / 1000))
+}
+
+func (d *DefaultFeeStrategy) calcMaxFeeAmount(amount uint64) uint64 {
+ return uint64(math.Ceil(float64(amount) * 0.05))
+}