• R/O
  • HTTP
  • SSH
  • HTTPS

vapor: 提交

Golang implemented sidechain for Bytom


Commit MetaInfo

修订版cb75951f9f5a341b13db6678f8af0715ad29e615 (tree)
时间2020-03-06 00:12:28
作者Paladz <yzhu101@uott...>
CommiterGitHub

Log Message

match_fee_strategy (#506)

* match_fee_strategy

* rename variable

* opt code

* rename

* adjust order

* add test case

更改概述

差异

--- a/application/mov/match/match.go
+++ b/application/mov/match/match.go
@@ -1,8 +1,6 @@
11 package match
22
33 import (
4- "encoding/hex"
5- "math"
64 "math/big"
75
86 "github.com/bytom/vapor/application/mov/common"
@@ -13,19 +11,18 @@ import (
1311 "github.com/bytom/vapor/protocol/bc"
1412 "github.com/bytom/vapor/protocol/bc/types"
1513 "github.com/bytom/vapor/protocol/vm"
16- "github.com/bytom/vapor/protocol/vm/vmutil"
1714 )
1815
1916 // Engine is used to generate math transactions
2017 type Engine struct {
2118 orderBook *OrderBook
22- maxFeeRate float64
19+ feeStrategy FeeStrategy
2320 rewardProgram []byte
2421 }
2522
2623 // NewEngine return a new Engine
27-func NewEngine(orderBook *OrderBook, maxFeeRate float64, rewardProgram []byte) *Engine {
28- return &Engine{orderBook: orderBook, maxFeeRate: maxFeeRate, rewardProgram: rewardProgram}
24+func NewEngine(orderBook *OrderBook, feeStrategy FeeStrategy, rewardProgram []byte) *Engine {
25+ return &Engine{orderBook: orderBook, feeStrategy: feeStrategy, rewardProgram: rewardProgram}
2926 }
3027
3128 // HasMatchedTx check does the input trade pair can generate a match deal
@@ -65,39 +62,18 @@ func (e *Engine) NextMatchedTx(tradePairs ...*common.TradePair) (*types.Tx, erro
6562 return tx, nil
6663 }
6764
68-func (e *Engine) addMatchTxFeeOutput(txData *types.TxData) error {
69- txFee, err := CalcMatchedTxFee(txData, e.maxFeeRate)
70- if err != nil {
71- return err
65+func (e *Engine) addMatchTxFeeOutput(txData *types.TxData, refundAmounts, feeAmounts []*bc.AssetAmount) error {
66+ for _, feeAmount := range feeAmounts {
67+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*feeAmount.AssetId, feeAmount.Amount, e.rewardProgram))
7268 }
7369
74- for assetID, matchTxFee := range txFee {
75- feeAmount, reminder := matchTxFee.FeeAmount, int64(0)
76- if matchTxFee.FeeAmount > matchTxFee.MaxFeeAmount {
77- feeAmount = matchTxFee.MaxFeeAmount
78- reminder = matchTxFee.FeeAmount - matchTxFee.MaxFeeAmount
79- }
80- txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(assetID, uint64(feeAmount), e.rewardProgram))
81-
82- // There is the remaining amount after paying the handling fee, assign it evenly to participants in the transaction
83- averageAmount := reminder / int64(len(txData.Inputs))
84- if averageAmount == 0 {
85- averageAmount = 1
70+ for i, refundAmount := range refundAmounts {
71+ contractArgs, err := segwit.DecodeP2WMCProgram(txData.Inputs[i].ControlProgram())
72+ if err != nil {
73+ return err
8674 }
8775
88- for i := 0; i < len(txData.Inputs) && reminder > 0; i++ {
89- contractArgs, err := segwit.DecodeP2WMCProgram(txData.Inputs[i].ControlProgram())
90- if err != nil {
91- return err
92- }
93-
94- if i == len(txData.Inputs)-1 {
95- txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(assetID, uint64(reminder), contractArgs.SellerProgram))
96- } else {
97- txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(assetID, uint64(averageAmount), contractArgs.SellerProgram))
98- }
99- reminder -= averageAmount
100- }
76+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*refundAmount.AssetId, refundAmount.Amount, contractArgs.SellerProgram))
10177 }
10278 return nil
10379 }
@@ -120,17 +96,18 @@ func (e *Engine) addPartialTradeOrder(tx *types.Tx) error {
12096
12197 func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) {
12298 txData := &types.TxData{Version: 1}
123- for i, order := range orders {
99+ for _, order := range orders {
124100 input := types.NewSpendInput(nil, *order.Utxo.SourceID, *order.FromAssetID, order.Utxo.Amount, order.Utxo.SourcePos, order.Utxo.ControlProgram)
125101 txData.Inputs = append(txData.Inputs, input)
102+ }
126103
127- oppositeOrder := orders[calcOppositeIndex(len(orders), i)]
128- if err := addMatchTxOutput(txData, input, order, oppositeOrder.Utxo.Amount); err != nil {
129- return nil, err
130- }
104+ receivedAmounts, priceDiff := CalcReceivedAmount(orders)
105+ receivedAfterDeductFee, refundAmounts, feeAmounts := e.feeStrategy.Allocate(receivedAmounts, priceDiff)
106+ if err := addMatchTxOutput(txData, orders, receivedAmounts, receivedAfterDeductFee); err != nil {
107+ return nil, err
131108 }
132109
133- if err := e.addMatchTxFeeOutput(txData); err != nil {
110+ if err := e.addMatchTxFeeOutput(txData, refundAmounts, feeAmounts); err != nil {
134111 return nil, err
135112 }
136113
@@ -143,94 +120,77 @@ func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) {
143120 return types.NewTx(*txData), nil
144121 }
145122
146-// MatchedTxFee is object to record the mov tx's fee information
147-type MatchedTxFee struct {
148- MaxFeeAmount int64
149- FeeAmount int64
150-}
151-
152-// CalcMatchedTxFee is used to calculate tx's MatchedTxFees
153-func CalcMatchedTxFee(txData *types.TxData, maxFeeRate float64) (map[bc.AssetID]*MatchedTxFee, error) {
154- assetFeeMap := make(map[bc.AssetID]*MatchedTxFee)
155- dealProgMaps := make(map[string]bool)
156-
157- for _, input := range txData.Inputs {
158- assetFeeMap[input.AssetID()] = &MatchedTxFee{FeeAmount: int64(input.AssetAmount().Amount)}
159- contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
160- if err != nil {
161- return nil, err
162- }
163-
164- dealProgMaps[hex.EncodeToString(contractArgs.SellerProgram)] = true
165- }
166-
167- for _, input := range txData.Inputs {
168- contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
123+func addMatchTxOutput(txData *types.TxData, orders []*common.Order, receivedAmounts, receivedAfterDeductFee []*bc.AssetAmount) error {
124+ for i, order := range orders {
125+ contractArgs, err := segwit.DecodeP2WMCProgram(order.Utxo.ControlProgram)
169126 if err != nil {
170- return nil, err
127+ return err
171128 }
172129
173- oppositeAmount := uint64(assetFeeMap[contractArgs.RequestedAsset].FeeAmount)
174- receiveAmount := vprMath.MinUint64(CalcRequestAmount(input.Amount(), contractArgs), oppositeAmount)
175- assetFeeMap[input.AssetID()].MaxFeeAmount = calcMaxFeeAmount(calcShouldPayAmount(receiveAmount, contractArgs), maxFeeRate)
176- }
130+ requestAmount := CalcRequestAmount(order.Utxo.Amount, contractArgs.RatioNumerator, contractArgs.RatioDenominator)
131+ receivedAmount := receivedAmounts[i].Amount
132+ shouldPayAmount := calcShouldPayAmount(receivedAmount, contractArgs.RatioNumerator, contractArgs.RatioDenominator)
133+ isPartialTrade := requestAmount > receivedAmount
177134
178- for _, output := range txData.Outputs {
179- assetAmount := output.AssetAmount()
180- if _, ok := dealProgMaps[hex.EncodeToString(output.ControlProgram())]; ok || segwit.IsP2WMCScript(output.ControlProgram()) {
181- assetFeeMap[*assetAmount.AssetId].FeeAmount -= int64(assetAmount.Amount)
182- if assetFeeMap[*assetAmount.AssetId].FeeAmount <= 0 {
183- delete(assetFeeMap, *assetAmount.AssetId)
184- }
135+ setMatchTxArguments(txData.Inputs[i], isPartialTrade, len(txData.Outputs), receivedAfterDeductFee[i].Amount)
136+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.ToAssetID, receivedAfterDeductFee[i].Amount, contractArgs.SellerProgram))
137+ if isPartialTrade {
138+ txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.FromAssetID, order.Utxo.Amount-shouldPayAmount, order.Utxo.ControlProgram))
185139 }
186140 }
187- return assetFeeMap, nil
141+ return nil
188142 }
189143
190-func addMatchTxOutput(txData *types.TxData, txInput *types.TxInput, order *common.Order, oppositeAmount uint64) error {
191- contractArgs, err := segwit.DecodeP2WMCProgram(order.Utxo.ControlProgram)
192- if err != nil {
193- return err
194- }
195-
196- requestAmount := CalcRequestAmount(order.Utxo.Amount, contractArgs)
197- receiveAmount := vprMath.MinUint64(requestAmount, oppositeAmount)
198- shouldPayAmount := calcShouldPayAmount(receiveAmount, contractArgs)
199- isPartialTrade := requestAmount > receiveAmount
200-
201- setMatchTxArguments(txInput, isPartialTrade, len(txData.Outputs), receiveAmount)
202- txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.ToAssetID, receiveAmount, contractArgs.SellerProgram))
203- if isPartialTrade {
204- txData.Outputs = append(txData.Outputs, types.NewIntraChainOutput(*order.FromAssetID, order.Utxo.Amount-shouldPayAmount, order.Utxo.ControlProgram))
205- }
206- return nil
144+func calcOppositeIndex(size int, selfIdx int) int {
145+ return (selfIdx + 1) % size
207146 }
208147
209148 // CalcRequestAmount is from amount * numerator / ratioDenominator
210-func CalcRequestAmount(fromAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 {
149+func CalcRequestAmount(fromAmount uint64, ratioNumerator, ratioDenominator int64) uint64 {
211150 res := big.NewInt(0).SetUint64(fromAmount)
212- res.Mul(res, big.NewInt(contractArg.RatioNumerator)).Quo(res, big.NewInt(contractArg.RatioDenominator))
151+ res.Mul(res, big.NewInt(ratioNumerator)).Quo(res, big.NewInt(ratioDenominator))
213152 if !res.IsUint64() {
214153 return 0
215154 }
216155 return res.Uint64()
217156 }
218157
219-func calcShouldPayAmount(receiveAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 {
158+func calcShouldPayAmount(receiveAmount uint64, ratioNumerator, ratioDenominator int64) uint64 {
220159 res := big.NewInt(0).SetUint64(receiveAmount)
221- res.Mul(res, big.NewInt(contractArg.RatioDenominator)).Quo(res, big.NewInt(contractArg.RatioNumerator))
160+ res.Mul(res, big.NewInt(ratioDenominator)).Quo(res, big.NewInt(ratioNumerator))
222161 if !res.IsUint64() {
223162 return 0
224163 }
225164 return res.Uint64()
226165 }
227166
228-func calcMaxFeeAmount(shouldPayAmount uint64, maxFeeRate float64) int64 {
229- return int64(math.Ceil(float64(shouldPayAmount) * maxFeeRate))
230-}
167+// CalcReceivedAmount return amount of assets received by each participant in the matching transaction and the price difference
168+func CalcReceivedAmount(orders []*common.Order) ([]*bc.AssetAmount, *bc.AssetAmount) {
169+ priceDiff := &bc.AssetAmount{}
170+ if len(orders) == 0 {
171+ return nil, priceDiff
172+ }
231173
232-func calcOppositeIndex(size int, selfIdx int) int {
233- return (selfIdx + 1) % size
174+ var receivedAmounts, shouldPayAmounts []*bc.AssetAmount
175+ for i, order := range orders {
176+ requestAmount := CalcRequestAmount(order.Utxo.Amount, order.RatioNumerator, order.RatioDenominator)
177+ oppositeOrder := orders[calcOppositeIndex(len(orders), i)]
178+ receiveAmount := vprMath.MinUint64(oppositeOrder.Utxo.Amount, requestAmount)
179+ shouldPayAmount := calcShouldPayAmount(receiveAmount, order.RatioNumerator, order.RatioDenominator)
180+ receivedAmounts = append(receivedAmounts, &bc.AssetAmount{AssetId: order.ToAssetID, Amount: receiveAmount})
181+ shouldPayAmounts = append(shouldPayAmounts, &bc.AssetAmount{AssetId: order.FromAssetID, Amount: shouldPayAmount})
182+ }
183+
184+ for i, receivedAmount := range receivedAmounts {
185+ oppositeShouldPayAmount := shouldPayAmounts[calcOppositeIndex(len(orders), i)]
186+ if oppositeShouldPayAmount.Amount > receivedAmount.Amount {
187+ priceDiff.AssetId = oppositeShouldPayAmount.AssetId
188+ priceDiff.Amount = oppositeShouldPayAmount.Amount - receivedAmount.Amount
189+ // price differential can only produce once
190+ break
191+ }
192+ }
193+ return receivedAmounts, priceDiff
234194 }
235195
236196 // IsMatched check does the orders can be exchange
--- /dev/null
+++ b/application/mov/match/match_fee.go
@@ -0,0 +1,102 @@
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+ // ErrAmountOfFeeExceedMaximum represent The fee charged is exceeded the maximum
12+ ErrAmountOfFeeExceedMaximum = errors.New("amount of fee greater than max fee amount")
13+ // ErrFeeMoreThanOneAsset represent the fee charged can only have one asset
14+ ErrFeeMoreThanOneAsset = errors.New("fee can only be an asset")
15+)
16+
17+// FeeStrategy used to indicate how to charge a matching fee
18+type FeeStrategy interface {
19+ // Allocate will allocate the price differential in matching transaction to the participants and the fee
20+ // @param receiveAmounts the amount of assets that the participants in the matching transaction can received when no fee is considered
21+ // @param priceDiff price differential of matching transaction
22+ // @return the amount of assets that the participants in the matching transaction can received when fee is considered
23+ // @return the amount of assets returned to the transaction participant when the fee exceeds a certain ratio
24+ // @return the amount of fees
25+ Allocate(receiveAmounts []*bc.AssetAmount, priceDiff *bc.AssetAmount) ([]*bc.AssetAmount, []*bc.AssetAmount, []*bc.AssetAmount)
26+
27+ // Validate verify that the fee charged for a matching transaction is correct
28+ Validate(receiveAmounts []*bc.AssetAmount, priceDiff *bc.AssetAmount, feeAmounts map[bc.AssetID]int64) error
29+}
30+
31+// DefaultFeeStrategy represent the default fee charge strategy
32+type DefaultFeeStrategy struct {
33+ maxFeeRate float64
34+}
35+
36+// NewDefaultFeeStrategy return a new instance of DefaultFeeStrategy
37+func NewDefaultFeeStrategy(maxFeeRate float64) *DefaultFeeStrategy {
38+ return &DefaultFeeStrategy{maxFeeRate: maxFeeRate}
39+}
40+
41+// Allocate will allocate the price differential in matching transaction to the participants and the fee
42+func (d *DefaultFeeStrategy) Allocate(receiveAmounts []*bc.AssetAmount, priceDiff *bc.AssetAmount) ([]*bc.AssetAmount, []*bc.AssetAmount, []*bc.AssetAmount) {
43+ receivedAfterDeductFee := make([]*bc.AssetAmount, len(receiveAmounts))
44+ copy(receivedAfterDeductFee, receiveAmounts)
45+
46+ if priceDiff.Amount == 0 {
47+ return receivedAfterDeductFee, nil, nil
48+ }
49+
50+ var maxFeeAmount int64
51+ for _, receiveAmount := range receiveAmounts {
52+ if *receiveAmount.AssetId == *priceDiff.AssetId {
53+ maxFeeAmount = calcMaxFeeAmount(receiveAmount.Amount, d.maxFeeRate)
54+ }
55+ }
56+
57+ priceDiffAmount := int64(priceDiff.Amount)
58+ feeAmount, reminder := priceDiffAmount, int64(0)
59+ if priceDiffAmount > maxFeeAmount {
60+ feeAmount = maxFeeAmount
61+ reminder = priceDiffAmount - maxFeeAmount
62+ }
63+
64+ // There is the remaining amount after paying the handling fee, assign it evenly to participants in the transaction
65+ averageAmount := reminder / int64(len(receiveAmounts))
66+ if averageAmount == 0 {
67+ averageAmount = 1
68+ }
69+
70+ var refundAmounts []*bc.AssetAmount
71+ for i := 0; i < len(receiveAmounts) && reminder > 0; i++ {
72+ amount := averageAmount
73+ if i == len(receiveAmounts)-1 {
74+ amount = reminder
75+ }
76+ refundAmounts = append(refundAmounts, &bc.AssetAmount{AssetId: priceDiff.AssetId, Amount: uint64(amount)})
77+ reminder -= averageAmount
78+ }
79+
80+ feeAmounts := []*bc.AssetAmount{{AssetId: priceDiff.AssetId, Amount: uint64(feeAmount)}}
81+ return receivedAfterDeductFee, refundAmounts, feeAmounts
82+}
83+
84+// Validate verify that the fee charged for a matching transaction is correct
85+func (d *DefaultFeeStrategy) Validate(receiveAmounts []*bc.AssetAmount, priceDiff *bc.AssetAmount, feeAmounts map[bc.AssetID]int64) error {
86+ if len(feeAmounts) > 1 {
87+ return ErrFeeMoreThanOneAsset
88+ }
89+
90+ for _, receiveAmount := range receiveAmounts {
91+ if feeAmount, ok := feeAmounts[*receiveAmount.AssetId]; ok {
92+ if feeAmount > calcMaxFeeAmount(receiveAmount.Amount, d.maxFeeRate) {
93+ return ErrAmountOfFeeExceedMaximum
94+ }
95+ }
96+ }
97+ return nil
98+}
99+
100+func calcMaxFeeAmount(amount uint64, maxFeeRate float64) int64 {
101+ return int64(math.Ceil(float64(amount) * maxFeeRate))
102+}
--- a/application/mov/match/match_test.go
+++ b/application/mov/match/match_test.go
@@ -8,7 +8,6 @@ import (
88 "github.com/bytom/vapor/protocol/bc"
99 "github.com/bytom/vapor/protocol/bc/types"
1010 "github.com/bytom/vapor/protocol/validation"
11- "github.com/bytom/vapor/testutil"
1211 )
1312
1413 func TestGenerateMatchedTxs(t *testing.T) {
@@ -80,7 +79,7 @@ func TestGenerateMatchedTxs(t *testing.T) {
8079
8180 for i, c := range cases {
8281 movStore := mock.NewMovStore([]*common.TradePair{btc2eth, eth2btc}, c.initStoreOrders)
83- matchEngine := NewEngine(NewOrderBook(movStore, nil, nil), 0.05, mock.RewardProgram)
82+ matchEngine := NewEngine(NewOrderBook(movStore, nil, nil), NewDefaultFeeStrategy(0.05), mock.RewardProgram)
8483 var gotMatchedTxs []*types.Tx
8584 for matchEngine.HasMatchedTx(c.tradePairs...) {
8685 matchedTx, err := matchEngine.NextMatchedTx(c.tradePairs...)
@@ -96,19 +95,19 @@ func TestGenerateMatchedTxs(t *testing.T) {
9695 continue
9796 }
9897
99- for i, gotMatchedTx := range gotMatchedTxs {
98+ for j, gotMatchedTx := range gotMatchedTxs {
10099 if _, err := validation.ValidateTx(gotMatchedTx.Tx, &bc.Block{BlockHeader: &bc.BlockHeader{Version: 1}}); err != nil {
101100 t.Fatal(err)
102101 }
103102
104- c.wantMatchedTxs[i].Version = 1
105- byteData, err := c.wantMatchedTxs[i].MarshalText()
103+ c.wantMatchedTxs[j].Version = 1
104+ byteData, err := c.wantMatchedTxs[j].MarshalText()
106105 if err != nil {
107106 t.Fatal(err)
108107 }
109108
110- c.wantMatchedTxs[i].SerializedSize = uint64(len(byteData))
111- wantMatchedTx := types.NewTx(c.wantMatchedTxs[i].TxData)
109+ c.wantMatchedTxs[j].SerializedSize = uint64(len(byteData))
110+ wantMatchedTx := types.NewTx(c.wantMatchedTxs[j].TxData)
112111 if gotMatchedTx.ID != wantMatchedTx.ID {
113112 t.Errorf("#%d(%s) the tx hash of got matched tx: %s is not equals want matched tx: %s", i, c.desc, gotMatchedTx.ID.String(), wantMatchedTx.ID.String())
114113 }
@@ -116,45 +115,6 @@ func TestGenerateMatchedTxs(t *testing.T) {
116115 }
117116 }
118117
119-func TestCalcMatchedTxFee(t *testing.T) {
120- cases := []struct {
121- desc string
122- tx *types.TxData
123- maxFeeRate float64
124- wantMatchedTxFee map[bc.AssetID]*MatchedTxFee
125- }{
126- {
127- desc: "fee less than max fee",
128- maxFeeRate: 0.05,
129- wantMatchedTxFee: map[bc.AssetID]*MatchedTxFee{mock.ETH: {FeeAmount: 10, MaxFeeAmount: 26}},
130- tx: &mock.MatchedTxs[1].TxData,
131- },
132- {
133- desc: "fee refund in tx",
134- maxFeeRate: 0.05,
135- wantMatchedTxFee: map[bc.AssetID]*MatchedTxFee{mock.ETH: {FeeAmount: 27, MaxFeeAmount: 27}},
136- tx: &mock.MatchedTxs[2].TxData,
137- },
138- {
139- desc: "fee is zero",
140- maxFeeRate: 0.05,
141- wantMatchedTxFee: map[bc.AssetID]*MatchedTxFee{},
142- tx: &mock.MatchedTxs[0].TxData,
143- },
144- }
145-
146- for i, c := range cases {
147- gotMatchedTxFee, err := CalcMatchedTxFee(c.tx, c.maxFeeRate)
148- if err != nil {
149- t.Fatal(err)
150- }
151-
152- if !testutil.DeepEqual(gotMatchedTxFee, c.wantMatchedTxFee) {
153- t.Errorf("#%d(%s):fail to caculate matched tx fee, got (%v), want (%v)", i, c.desc, gotMatchedTxFee, c.wantMatchedTxFee)
154- }
155- }
156-}
157-
158118 func TestValidateTradePairs(t *testing.T) {
159119 cases := []struct {
160120 desc string
--- a/application/mov/mock/mock.go
+++ b/application/mov/mock/mock.go
@@ -285,10 +285,10 @@ var (
285285 // re-order
286286 types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 270, Eth2BtcOrders[2].Utxo.ControlProgram),
287287 // fee
288- types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 27, RewardProgram),
288+ types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 25, RewardProgram),
289289 // refund
290- types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 6, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
291- types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 7, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
290+ types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 7, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
291+ types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 8, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
292292 },
293293 }),
294294 types.NewTx(types.TxData{
--- a/application/mov/mov_core.go
+++ b/application/mov/mov_core.go
@@ -23,7 +23,6 @@ var (
2323 errInputProgramMustP2WMCScript = errors.New("input program of trade tx must p2wmc script")
2424 errExistCancelOrderInMatchedTx = errors.New("can't exist cancel order in the matched transaction")
2525 errExistTradeInCancelOrderTx = errors.New("can't exist trade in the cancel order transaction")
26- errAmountOfFeeGreaterThanMaximum = errors.New("amount of fee greater than max fee amount")
2726 errAssetIDMustUniqueInMatchedTx = errors.New("asset id must unique in matched transaction")
2827 errRatioOfTradeLessThanZero = errors.New("ratio arguments must greater than zero")
2928 errSpendOutputIDIsIncorrect = errors.New("spend output id of matched tx is not equals to actual matched tx")
@@ -89,7 +88,7 @@ func (m *MovCore) BeforeProposalBlock(txs []*types.Tx, blockHeight uint64, gasLe
8988 return nil, err
9089 }
9190
92- matchEngine := match.NewEngine(orderBook, maxFeeRate, rewardProgram)
91+ matchEngine := match.NewEngine(orderBook, match.NewDefaultFeeStrategy(maxFeeRate), rewardProgram)
9392 tradePairIterator := database.NewTradePairIterator(m.movStore)
9493 matchCollector := newMatchTxCollector(matchEngine, tradePairIterator, gasLeft, isTimeout)
9594 return matchCollector.result()
@@ -183,6 +182,33 @@ func (m *MovCore) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) erro
183182 return nil
184183 }
185184
185+// calcFeeAmount return the amount of fee in the matching transaction
186+func calcFeeAmount(matchedTx *types.Tx) (map[bc.AssetID]int64, error) {
187+ assetFeeMap := make(map[bc.AssetID]int64)
188+ dealProgMaps := make(map[string]bool)
189+
190+ for _, input := range matchedTx.Inputs {
191+ assetFeeMap[input.AssetID()] = int64(input.AssetAmount().Amount)
192+ contractArgs, err := segwit.DecodeP2WMCProgram(input.ControlProgram())
193+ if err != nil {
194+ return nil, err
195+ }
196+
197+ dealProgMaps[hex.EncodeToString(contractArgs.SellerProgram)] = true
198+ }
199+
200+ for _, output := range matchedTx.Outputs {
201+ assetAmount := output.AssetAmount()
202+ if _, ok := dealProgMaps[hex.EncodeToString(output.ControlProgram())]; ok || segwit.IsP2WMCScript(output.ControlProgram()) {
203+ assetFeeMap[*assetAmount.AssetId] -= int64(assetAmount.Amount)
204+ if assetFeeMap[*assetAmount.AssetId] <= 0 {
205+ delete(assetFeeMap, *assetAmount.AssetId)
206+ }
207+ }
208+ }
209+ return assetFeeMap, nil
210+}
211+
186212 func validateCancelOrderTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
187213 if verifyResult.StatusFail {
188214 return errStatusFailMustFalse
@@ -214,7 +240,7 @@ func validateMagneticContractArgs(fromAssetAmount bc.AssetAmount, program []byte
214240 return errRatioOfTradeLessThanZero
215241 }
216242
217- if match.CalcRequestAmount(fromAssetAmount.Amount, contractArgs) < 1 {
243+ if match.CalcRequestAmount(fromAssetAmount.Amount, contractArgs.RatioNumerator, contractArgs.RatioDenominator) < 1 {
218244 return errRequestAmountMath
219245 }
220246 return nil
@@ -253,17 +279,19 @@ func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error {
253279 }
254280
255281 func validateMatchedTxFeeAmount(tx *types.Tx) error {
256- txFee, err := match.CalcMatchedTxFee(&tx.TxData, maxFeeRate)
282+ orders, err := getDeleteOrdersFromTx(tx)
257283 if err != nil {
258284 return err
259285 }
260286
261- for _, amount := range txFee {
262- if amount.FeeAmount > amount.MaxFeeAmount {
263- return errAmountOfFeeGreaterThanMaximum
264- }
287+ receivedAmount, priceDiff := match.CalcReceivedAmount(orders)
288+ feeAmounts, err := calcFeeAmount(tx)
289+ if err != nil {
290+ return err
265291 }
266- return nil
292+
293+ feeStrategy := match.NewDefaultFeeStrategy(maxFeeRate)
294+ return feeStrategy.Validate(receivedAmount, priceDiff, feeAmounts)
267295 }
268296
269297 func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx) error {
--- a/application/mov/mov_core_test.go
+++ b/application/mov/mov_core_test.go
@@ -8,6 +8,7 @@ import (
88
99 "github.com/bytom/vapor/application/mov/common"
1010 "github.com/bytom/vapor/application/mov/database"
11+ "github.com/bytom/vapor/application/mov/match"
1112 "github.com/bytom/vapor/application/mov/mock"
1213 "github.com/bytom/vapor/consensus"
1314 dbm "github.com/bytom/vapor/database/leveldb"
@@ -446,8 +447,8 @@ func TestValidateBlock(t *testing.T) {
446447 types.NewSpendInput([][]byte{vm.Int64Bytes(10), vm.Int64Bytes(1), vm.Int64Bytes(0)}, *mock.Eth2BtcOrders[2].Utxo.SourceID, *mock.Eth2BtcOrders[2].FromAssetID, mock.Eth2BtcOrders[2].Utxo.Amount, mock.Eth2BtcOrders[2].Utxo.SourcePos, mock.Eth2BtcOrders[2].Utxo.ControlProgram),
447448 },
448449 Outputs: []*types.TxOutput{
449- types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("51")),
450- types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].ToAssetID, 10, testutil.MustDecodeHexString("55")),
450+ types.NewIntraChainOutput(*mock.Btc2EthOrders[0].ToAssetID, 500, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19251")),
451+ types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].ToAssetID, 10, testutil.MustDecodeHexString("0014f928b723999312df4ed51cb275a2644336c19255")),
451452 // re-order
452453 types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].FromAssetID, 270, mock.Eth2BtcOrders[2].Utxo.ControlProgram),
453454 // fee
@@ -457,7 +458,7 @@ func TestValidateBlock(t *testing.T) {
457458 },
458459 },
459460 verifyResults: []*bc.TxVerifyResult{{StatusFail: false}},
460- wantError: errAmountOfFeeGreaterThanMaximum,
461+ wantError: match.ErrAmountOfFeeExceedMaximum,
461462 },
462463 {
463464 desc: "ratio numerator is zero",
@@ -508,6 +509,45 @@ func TestValidateBlock(t *testing.T) {
508509 }
509510 }
510511
512+func TestCalcMatchedTxFee(t *testing.T) {
513+ cases := []struct {
514+ desc string
515+ tx types.TxData
516+ maxFeeRate float64
517+ wantMatchedTxFee map[bc.AssetID]int64
518+ }{
519+ {
520+ desc: "fee less than max fee",
521+ maxFeeRate: 0.05,
522+ wantMatchedTxFee: map[bc.AssetID]int64{mock.ETH: 10},
523+ tx: mock.MatchedTxs[1].TxData,
524+ },
525+ {
526+ desc: "fee refund in tx",
527+ maxFeeRate: 0.05,
528+ wantMatchedTxFee: map[bc.AssetID]int64{mock.ETH: 25},
529+ tx: mock.MatchedTxs[2].TxData,
530+ },
531+ {
532+ desc: "fee is zero",
533+ maxFeeRate: 0.05,
534+ wantMatchedTxFee: map[bc.AssetID]int64{},
535+ tx: mock.MatchedTxs[0].TxData,
536+ },
537+ }
538+
539+ for i, c := range cases {
540+ gotMatchedTxFee, err := calcFeeAmount(types.NewTx(c.tx))
541+ if err != nil {
542+ t.Fatal(err)
543+ }
544+
545+ if !testutil.DeepEqual(gotMatchedTxFee, c.wantMatchedTxFee) {
546+ t.Errorf("#%d(%s):fail to caculate matched tx fee, got (%v), want (%v)", i, c.desc, gotMatchedTxFee, c.wantMatchedTxFee)
547+ }
548+ }
549+}
550+
511551 func TestBeforeProposalBlock(t *testing.T) {
512552 consensus.ActiveNetParams.MovRewardProgram = hex.EncodeToString(mock.RewardProgram)
513553
Show on old repository browser