)
var (
- // ErrAmountOfFeeOutOfRange represent The fee charged is out of range
- ErrAmountOfFeeOutOfRange = errors.New("amount of fee is out of range")
+ // ErrInvalidAmountOfFee represent The fee charged is invalid
+ ErrInvalidAmountOfFee = errors.New("amount of fee is invalid")
)
+const forkBlockHeightAt20201028 = 78968400
+
// 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
-
-// Add used to add a refund to specify order
-func (r RefundAssets) Add(index int, asset bc.AssetID, amount uint64) {
- if index >= len(r) {
- index = 0
- }
-
- for _, assetAmount := range r[index] {
- if *assetAmount.AssetId == asset {
- assetAmount.Amount += amount
- return
- }
- }
- r[index] = append(r[index], &bc.AssetAmount{AssetId: &asset, Amount: amount})
-}
-
// 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
+ Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount, takerPos int) *AllocatedAssets
// Validate verify that the fee charged for a matching transaction is correct
- Validate(receiveAmounts []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error
+ Validate(receiveAmounts, priceDiffs []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64, blockHeight uint64) error
}
// DefaultFeeStrategy represent the default fee charge strategy
-type DefaultFeeStrategy struct {}
+type DefaultFeeStrategy struct{}
// NewDefaultFeeStrategy return a new instance of DefaultFeeStrategy
func NewDefaultFeeStrategy() *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([][]*bc.AssetAmount, len(receiveAmounts))
+func (d *DefaultFeeStrategy) Allocate(receiveAmounts, priceDiffs []*bc.AssetAmount, takerPos int) *AllocatedAssets {
receives := make([]*bc.AssetAmount, len(receiveAmounts))
+ fees := 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
+ fee := calcMinFeeAmount(receiveAmount.Amount)
+ receives[i] = &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: receiveAmount.Amount - fee}
+ fees[i] = &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: fee}
+
+ if i == takerPos {
+ for _, priceDiff := range priceDiffs {
+ if *priceDiff.AssetId == *receiveAmount.AssetId {
+ fee = calcMinFeeAmount(priceDiff.Amount)
+ priceDiff.Amount -= fee
+ fees[i].Amount += fee
+ }
+ }
}
+ }
+ return &AllocatedAssets{Receives: receives, Fees: fees}
+}
- fees = append(fees, &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: feeAmount})
+// Validate verify that the fee charged for a matching transaction is correct
+func (d *DefaultFeeStrategy) Validate(receiveAmounts, priceDiffs []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64, blockHeight uint64) error {
+ if blockHeight < forkBlockHeightAt20201028 {
+ return legendValidateFee(receiveAmounts, feeAmounts)
+ }
+ return validateFee(receiveAmounts, priceDiffs, feeAmounts)
+}
+
+func validateFee(receiveAmounts, priceDiffs []*bc.AssetAmount, feeAmounts map[bc.AssetID]uint64) error {
+ existTaker := false
+ for _, receiveAmount := range receiveAmounts {
+ feeAmount := calcMinFeeAmount(receiveAmount.Amount)
+ realFeeAmount := feeAmounts[*receiveAmount.AssetId]
+ if equalsFeeAmount(realFeeAmount, feeAmount) {
+ continue
+ }
- // 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
+ if existTaker {
+ return ErrInvalidAmountOfFee
}
- for j := 0; j < len(receiveAmounts) && reminder > 0; j++ {
- refundAmount := averageAmount
- if j == len(receiveAmounts)-1 {
- refundAmount = reminder
+ for _, priceDiff := range priceDiffs {
+ if *priceDiff.AssetId == *receiveAmount.AssetId {
+ feeAmount += calcMinFeeAmount(priceDiff.Amount)
}
- refunds[j] = append(refunds[j], &bc.AssetAmount{AssetId: receiveAmount.AssetId, Amount: refundAmount})
- reminder -= averageAmount
}
+
+ if !equalsFeeAmount(realFeeAmount, feeAmount) {
+ return ErrInvalidAmountOfFee
+ }
+ existTaker = true
}
- return &AllocatedAssets{Receives: receives, Refunds: refunds, Fees: fees}
+ return nil
}
-// 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 {
+func equalsFeeAmount(realFeeAmount, feeAmount uint64) bool {
+ var tolerance float64 = 5
+ return math.Abs(float64(realFeeAmount)-float64(feeAmount)) < tolerance
+}
+
+func legendValidateFee(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
+ realFeeAmount := feeAmounts[*receiveAmount.AssetId]
+ minFeeAmount := calcMinFeeAmount(receiveAmount.Amount)
+ maxFeeAmount := calcMaxFeeAmount(receiveAmount.Amount)
+ if realFeeAmount < minFeeAmount || realFeeAmount > maxFeeAmount {
+ return ErrInvalidAmountOfFee
}
}
return nil
}
-func (d *DefaultFeeStrategy) calcMinFeeAmount(amount uint64) uint64 {
+func calcMinFeeAmount(amount uint64) uint64 {
return uint64(math.Ceil(float64(amount) / 1000))
}
-func (d *DefaultFeeStrategy) calcMaxFeeAmount(amount uint64) uint64 {
+func calcMaxFeeAmount(amount uint64) uint64 {
return uint64(math.Ceil(float64(amount) * 0.05))
}