OSDN Git Service

Mov (#518)
[bytom/vapor.git] / application / mov / match / fee_strategy.go
diff --git a/application/mov/match/fee_strategy.go b/application/mov/match/fee_strategy.go
new file mode 100644 (file)
index 0000000..f3d1dfc
--- /dev/null
@@ -0,0 +1,108 @@
+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))
+}