Golang implemented sidechain for Bytom
修订版 | cb75951f9f5a341b13db6678f8af0715ad29e615 (tree) |
---|---|
时间 | 2020-03-06 00:12:28 |
作者 | Paladz <yzhu101@uott...> |
Commiter | GitHub |
match_fee_strategy (#506)
* match_fee_strategy
* rename variable
* opt code
* rename
* adjust order
* add test case
@@ -1,8 +1,6 @@ | ||
1 | 1 | package match |
2 | 2 | |
3 | 3 | import ( |
4 | - "encoding/hex" | |
5 | - "math" | |
6 | 4 | "math/big" |
7 | 5 | |
8 | 6 | "github.com/bytom/vapor/application/mov/common" |
@@ -13,19 +11,18 @@ import ( | ||
13 | 11 | "github.com/bytom/vapor/protocol/bc" |
14 | 12 | "github.com/bytom/vapor/protocol/bc/types" |
15 | 13 | "github.com/bytom/vapor/protocol/vm" |
16 | - "github.com/bytom/vapor/protocol/vm/vmutil" | |
17 | 14 | ) |
18 | 15 | |
19 | 16 | // Engine is used to generate math transactions |
20 | 17 | type Engine struct { |
21 | 18 | orderBook *OrderBook |
22 | - maxFeeRate float64 | |
19 | + feeStrategy FeeStrategy | |
23 | 20 | rewardProgram []byte |
24 | 21 | } |
25 | 22 | |
26 | 23 | // 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} | |
29 | 26 | } |
30 | 27 | |
31 | 28 | // 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 | ||
65 | 62 | return tx, nil |
66 | 63 | } |
67 | 64 | |
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)) | |
72 | 68 | } |
73 | 69 | |
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 | |
86 | 74 | } |
87 | 75 | |
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)) | |
101 | 77 | } |
102 | 78 | return nil |
103 | 79 | } |
@@ -120,17 +96,18 @@ func (e *Engine) addPartialTradeOrder(tx *types.Tx) error { | ||
120 | 96 | |
121 | 97 | func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) { |
122 | 98 | txData := &types.TxData{Version: 1} |
123 | - for i, order := range orders { | |
99 | + for _, order := range orders { | |
124 | 100 | input := types.NewSpendInput(nil, *order.Utxo.SourceID, *order.FromAssetID, order.Utxo.Amount, order.Utxo.SourcePos, order.Utxo.ControlProgram) |
125 | 101 | txData.Inputs = append(txData.Inputs, input) |
102 | + } | |
126 | 103 | |
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 | |
131 | 108 | } |
132 | 109 | |
133 | - if err := e.addMatchTxFeeOutput(txData); err != nil { | |
110 | + if err := e.addMatchTxFeeOutput(txData, refundAmounts, feeAmounts); err != nil { | |
134 | 111 | return nil, err |
135 | 112 | } |
136 | 113 |
@@ -143,94 +120,77 @@ func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) { | ||
143 | 120 | return types.NewTx(*txData), nil |
144 | 121 | } |
145 | 122 | |
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) | |
169 | 126 | if err != nil { |
170 | - return nil, err | |
127 | + return err | |
171 | 128 | } |
172 | 129 | |
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 | |
177 | 134 | |
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)) | |
185 | 139 | } |
186 | 140 | } |
187 | - return assetFeeMap, nil | |
141 | + return nil | |
188 | 142 | } |
189 | 143 | |
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 | |
207 | 146 | } |
208 | 147 | |
209 | 148 | // CalcRequestAmount is from amount * numerator / ratioDenominator |
210 | -func CalcRequestAmount(fromAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 { | |
149 | +func CalcRequestAmount(fromAmount uint64, ratioNumerator, ratioDenominator int64) uint64 { | |
211 | 150 | 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)) | |
213 | 152 | if !res.IsUint64() { |
214 | 153 | return 0 |
215 | 154 | } |
216 | 155 | return res.Uint64() |
217 | 156 | } |
218 | 157 | |
219 | -func calcShouldPayAmount(receiveAmount uint64, contractArg *vmutil.MagneticContractArgs) uint64 { | |
158 | +func calcShouldPayAmount(receiveAmount uint64, ratioNumerator, ratioDenominator int64) uint64 { | |
220 | 159 | 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)) | |
222 | 161 | if !res.IsUint64() { |
223 | 162 | return 0 |
224 | 163 | } |
225 | 164 | return res.Uint64() |
226 | 165 | } |
227 | 166 | |
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 | + } | |
231 | 173 | |
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 | |
234 | 194 | } |
235 | 195 | |
236 | 196 | // IsMatched check does the orders can be exchange |
@@ -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 | +} |
@@ -8,7 +8,6 @@ import ( | ||
8 | 8 | "github.com/bytom/vapor/protocol/bc" |
9 | 9 | "github.com/bytom/vapor/protocol/bc/types" |
10 | 10 | "github.com/bytom/vapor/protocol/validation" |
11 | - "github.com/bytom/vapor/testutil" | |
12 | 11 | ) |
13 | 12 | |
14 | 13 | func TestGenerateMatchedTxs(t *testing.T) { |
@@ -80,7 +79,7 @@ func TestGenerateMatchedTxs(t *testing.T) { | ||
80 | 79 | |
81 | 80 | for i, c := range cases { |
82 | 81 | 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) | |
84 | 83 | var gotMatchedTxs []*types.Tx |
85 | 84 | for matchEngine.HasMatchedTx(c.tradePairs...) { |
86 | 85 | matchedTx, err := matchEngine.NextMatchedTx(c.tradePairs...) |
@@ -96,19 +95,19 @@ func TestGenerateMatchedTxs(t *testing.T) { | ||
96 | 95 | continue |
97 | 96 | } |
98 | 97 | |
99 | - for i, gotMatchedTx := range gotMatchedTxs { | |
98 | + for j, gotMatchedTx := range gotMatchedTxs { | |
100 | 99 | if _, err := validation.ValidateTx(gotMatchedTx.Tx, &bc.Block{BlockHeader: &bc.BlockHeader{Version: 1}}); err != nil { |
101 | 100 | t.Fatal(err) |
102 | 101 | } |
103 | 102 | |
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() | |
106 | 105 | if err != nil { |
107 | 106 | t.Fatal(err) |
108 | 107 | } |
109 | 108 | |
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) | |
112 | 111 | if gotMatchedTx.ID != wantMatchedTx.ID { |
113 | 112 | 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()) |
114 | 113 | } |
@@ -116,45 +115,6 @@ func TestGenerateMatchedTxs(t *testing.T) { | ||
116 | 115 | } |
117 | 116 | } |
118 | 117 | |
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 | - | |
158 | 118 | func TestValidateTradePairs(t *testing.T) { |
159 | 119 | cases := []struct { |
160 | 120 | desc string |
@@ -285,10 +285,10 @@ var ( | ||
285 | 285 | // re-order |
286 | 286 | types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 270, Eth2BtcOrders[2].Utxo.ControlProgram), |
287 | 287 | // fee |
288 | - types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 27, RewardProgram), | |
288 | + types.NewIntraChainOutput(*Eth2BtcOrders[2].FromAssetID, 25, RewardProgram), | |
289 | 289 | // 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")), | |
292 | 292 | }, |
293 | 293 | }), |
294 | 294 | types.NewTx(types.TxData{ |
@@ -23,7 +23,6 @@ var ( | ||
23 | 23 | errInputProgramMustP2WMCScript = errors.New("input program of trade tx must p2wmc script") |
24 | 24 | errExistCancelOrderInMatchedTx = errors.New("can't exist cancel order in the matched transaction") |
25 | 25 | errExistTradeInCancelOrderTx = errors.New("can't exist trade in the cancel order transaction") |
26 | - errAmountOfFeeGreaterThanMaximum = errors.New("amount of fee greater than max fee amount") | |
27 | 26 | errAssetIDMustUniqueInMatchedTx = errors.New("asset id must unique in matched transaction") |
28 | 27 | errRatioOfTradeLessThanZero = errors.New("ratio arguments must greater than zero") |
29 | 28 | 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 | ||
89 | 88 | return nil, err |
90 | 89 | } |
91 | 90 | |
92 | - matchEngine := match.NewEngine(orderBook, maxFeeRate, rewardProgram) | |
91 | + matchEngine := match.NewEngine(orderBook, match.NewDefaultFeeStrategy(maxFeeRate), rewardProgram) | |
93 | 92 | tradePairIterator := database.NewTradePairIterator(m.movStore) |
94 | 93 | matchCollector := newMatchTxCollector(matchEngine, tradePairIterator, gasLeft, isTimeout) |
95 | 94 | return matchCollector.result() |
@@ -183,6 +182,33 @@ func (m *MovCore) ValidateTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) erro | ||
183 | 182 | return nil |
184 | 183 | } |
185 | 184 | |
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 | + | |
186 | 212 | func validateCancelOrderTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error { |
187 | 213 | if verifyResult.StatusFail { |
188 | 214 | return errStatusFailMustFalse |
@@ -214,7 +240,7 @@ func validateMagneticContractArgs(fromAssetAmount bc.AssetAmount, program []byte | ||
214 | 240 | return errRatioOfTradeLessThanZero |
215 | 241 | } |
216 | 242 | |
217 | - if match.CalcRequestAmount(fromAssetAmount.Amount, contractArgs) < 1 { | |
243 | + if match.CalcRequestAmount(fromAssetAmount.Amount, contractArgs.RatioNumerator, contractArgs.RatioDenominator) < 1 { | |
218 | 244 | return errRequestAmountMath |
219 | 245 | } |
220 | 246 | return nil |
@@ -253,17 +279,19 @@ func validateMatchedTx(tx *types.Tx, verifyResult *bc.TxVerifyResult) error { | ||
253 | 279 | } |
254 | 280 | |
255 | 281 | func validateMatchedTxFeeAmount(tx *types.Tx) error { |
256 | - txFee, err := match.CalcMatchedTxFee(&tx.TxData, maxFeeRate) | |
282 | + orders, err := getDeleteOrdersFromTx(tx) | |
257 | 283 | if err != nil { |
258 | 284 | return err |
259 | 285 | } |
260 | 286 | |
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 | |
265 | 291 | } |
266 | - return nil | |
292 | + | |
293 | + feeStrategy := match.NewDefaultFeeStrategy(maxFeeRate) | |
294 | + return feeStrategy.Validate(receivedAmount, priceDiff, feeAmounts) | |
267 | 295 | } |
268 | 296 | |
269 | 297 | func (m *MovCore) validateMatchedTxSequence(txs []*types.Tx) error { |
@@ -8,6 +8,7 @@ import ( | ||
8 | 8 | |
9 | 9 | "github.com/bytom/vapor/application/mov/common" |
10 | 10 | "github.com/bytom/vapor/application/mov/database" |
11 | + "github.com/bytom/vapor/application/mov/match" | |
11 | 12 | "github.com/bytom/vapor/application/mov/mock" |
12 | 13 | "github.com/bytom/vapor/consensus" |
13 | 14 | dbm "github.com/bytom/vapor/database/leveldb" |
@@ -446,8 +447,8 @@ func TestValidateBlock(t *testing.T) { | ||
446 | 447 | 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), |
447 | 448 | }, |
448 | 449 | 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")), | |
451 | 452 | // re-order |
452 | 453 | types.NewIntraChainOutput(*mock.Eth2BtcOrders[2].FromAssetID, 270, mock.Eth2BtcOrders[2].Utxo.ControlProgram), |
453 | 454 | // fee |
@@ -457,7 +458,7 @@ func TestValidateBlock(t *testing.T) { | ||
457 | 458 | }, |
458 | 459 | }, |
459 | 460 | verifyResults: []*bc.TxVerifyResult{{StatusFail: false}}, |
460 | - wantError: errAmountOfFeeGreaterThanMaximum, | |
461 | + wantError: match.ErrAmountOfFeeExceedMaximum, | |
461 | 462 | }, |
462 | 463 | { |
463 | 464 | desc: "ratio numerator is zero", |
@@ -508,6 +509,45 @@ func TestValidateBlock(t *testing.T) { | ||
508 | 509 | } |
509 | 510 | } |
510 | 511 | |
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 | + | |
511 | 551 | func TestBeforeProposalBlock(t *testing.T) { |
512 | 552 | consensus.ActiveNetParams.MovRewardProgram = hex.EncodeToString(mock.RewardProgram) |
513 | 553 |